@hypequery/serve 0.16.0 → 0.16.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/pipeline.d.ts.map +1 -1
- package/dist/pipeline.js +11 -0
- package/dist/query-coercion.d.ts +8 -0
- package/dist/query-coercion.d.ts.map +1 -0
- package/dist/query-coercion.js +124 -0
- package/package.json +1 -1
package/dist/pipeline.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"pipeline.d.ts","sourceRoot":"","sources":["../src/pipeline.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,CAAC,EAAmB,MAAM,KAAK,CAAC;AAEzC,OAAO,KAAK,EACV,WAAW,EACX,YAAY,EACZ,WAAW,EAKX,cAAc,EACd,mBAAmB,EACnB,aAAa,EACb,YAAY,EACZ,mBAAmB,EACnB,eAAe,EACf,YAAY,EACZ,aAAa,EACb,YAAY,EAGb,MAAM,YAAY,CAAC;AAMpB,OAAO,EAAE,gBAAgB,EAAE,MAAM,mBAAmB,CAAC;AAOrD,OAAO,EAAE,KAAK,kBAAkB,EAAqB,MAAM,WAAW,CAAC;
|
|
1
|
+
{"version":3,"file":"pipeline.d.ts","sourceRoot":"","sources":["../src/pipeline.ts"],"names":[],"mappings":"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"}
|
package/dist/pipeline.js
CHANGED
|
@@ -8,6 +8,7 @@ import { ServeHttpError } from './errors.js';
|
|
|
8
8
|
import { checkRoleAuthorization, checkScopeAuthorization, AuthError, } from './auth.js';
|
|
9
9
|
import { handleCorsRequest } from './cors.js';
|
|
10
10
|
import { resolveLocalAuthRequirement } from './auth-requirement.js';
|
|
11
|
+
import { coerceQueryInput } from './query-coercion.js';
|
|
11
12
|
const safeInvokeHook = async (name, hook, payload) => {
|
|
12
13
|
if (!hook)
|
|
13
14
|
return;
|
|
@@ -23,6 +24,13 @@ const createErrorResponse = (status, type, message, details, headers) => ({
|
|
|
23
24
|
headers: headers ?? {},
|
|
24
25
|
body: { error: { type, message, ...(details ? { details } : {}) } },
|
|
25
26
|
});
|
|
27
|
+
/**
|
|
28
|
+
* True when {@link buildContextInput} sourced the input from the query string
|
|
29
|
+
* rather than a body. Query values are always strings, so they need coercing
|
|
30
|
+
* against the schema before validation; JSON bodies already carry types.
|
|
31
|
+
*/
|
|
32
|
+
const inputCameFromQuery = (request) => (request.body === undefined || request.body === null) &&
|
|
33
|
+
Boolean(request.query && Object.keys(request.query).length > 0);
|
|
26
34
|
const buildContextInput = (request) => {
|
|
27
35
|
if (request.body !== undefined && request.body !== null) {
|
|
28
36
|
return request.body;
|
|
@@ -326,6 +334,9 @@ export const executeEndpoint = async (options) => {
|
|
|
326
334
|
});
|
|
327
335
|
}
|
|
328
336
|
}
|
|
337
|
+
if (inputCameFromQuery(request)) {
|
|
338
|
+
context.input = coerceQueryInput(endpoint.inputSchema, context.input);
|
|
339
|
+
}
|
|
329
340
|
const validationResult = validateInput(endpoint.inputSchema, context.input);
|
|
330
341
|
if (!validationResult.success) {
|
|
331
342
|
await safeInvokeHook('onError', hooks.onError, {
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import type { ZodTypeAny } from "zod";
|
|
2
|
+
/**
|
|
3
|
+
* Returns a copy of `raw` with values coerced toward `schema`. Returns `raw`
|
|
4
|
+
* untouched when the schema is absent, is not an object schema, or when the
|
|
5
|
+
* payload is not a plain object.
|
|
6
|
+
*/
|
|
7
|
+
export declare function coerceQueryInput(schema: ZodTypeAny | undefined, raw: unknown): unknown;
|
|
8
|
+
//# sourceMappingURL=query-coercion.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"query-coercion.d.ts","sourceRoot":"","sources":["../src/query-coercion.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,KAAK,CAAC;AAyGtC;;;;GAIG;AACH,wBAAgB,gBAAgB,CAAC,MAAM,EAAE,UAAU,GAAG,SAAS,EAAE,GAAG,EAAE,OAAO,GAAG,OAAO,CAkCtF"}
|
|
@@ -0,0 +1,124 @@
|
|
|
1
|
+
const defOf = (schema) => schema?._def;
|
|
2
|
+
const typeNameOf = (schema) => defOf(schema)?.typeName;
|
|
3
|
+
/**
|
|
4
|
+
* Strips wrappers that do not change what a query value should be coerced to.
|
|
5
|
+
* `ZodPipeline` unwraps to its input side, since that is what validation
|
|
6
|
+
* receives. Preprocess effects stay wrapped so their callback sees the raw wire
|
|
7
|
+
* value as declared by the schema.
|
|
8
|
+
*/
|
|
9
|
+
function unwrap(schema, depth = 0) {
|
|
10
|
+
if (!schema || depth > 10)
|
|
11
|
+
return schema;
|
|
12
|
+
const def = defOf(schema);
|
|
13
|
+
switch (def?.typeName) {
|
|
14
|
+
case "ZodOptional":
|
|
15
|
+
case "ZodNullable":
|
|
16
|
+
case "ZodDefault":
|
|
17
|
+
case "ZodCatch":
|
|
18
|
+
case "ZodReadonly":
|
|
19
|
+
return unwrap(def.innerType, depth + 1);
|
|
20
|
+
case "ZodBranded":
|
|
21
|
+
return unwrap(def.type, depth + 1);
|
|
22
|
+
case "ZodEffects": {
|
|
23
|
+
const effect = def.effect;
|
|
24
|
+
if (effect?.type === "preprocess")
|
|
25
|
+
return schema;
|
|
26
|
+
return unwrap(def.schema, depth + 1);
|
|
27
|
+
}
|
|
28
|
+
case "ZodPipeline":
|
|
29
|
+
return unwrap(def.in, depth + 1);
|
|
30
|
+
default:
|
|
31
|
+
return schema;
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
function coerceScalar(target, value) {
|
|
35
|
+
switch (typeNameOf(target)) {
|
|
36
|
+
case "ZodNumber": {
|
|
37
|
+
if (value.trim() === "")
|
|
38
|
+
return value;
|
|
39
|
+
const parsed = Number(value);
|
|
40
|
+
return Number.isFinite(parsed) ? parsed : value;
|
|
41
|
+
}
|
|
42
|
+
case "ZodBigInt": {
|
|
43
|
+
if (value.trim() === "")
|
|
44
|
+
return value;
|
|
45
|
+
try {
|
|
46
|
+
return BigInt(value);
|
|
47
|
+
}
|
|
48
|
+
catch {
|
|
49
|
+
return value;
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
case "ZodBoolean": {
|
|
53
|
+
const normalized = value.trim().toLowerCase();
|
|
54
|
+
if (normalized === "true" || normalized === "1")
|
|
55
|
+
return true;
|
|
56
|
+
if (normalized === "false" || normalized === "0")
|
|
57
|
+
return false;
|
|
58
|
+
return value;
|
|
59
|
+
}
|
|
60
|
+
case "ZodDate": {
|
|
61
|
+
const parsed = new Date(value);
|
|
62
|
+
return Number.isNaN(parsed.getTime()) ? value : parsed;
|
|
63
|
+
}
|
|
64
|
+
default:
|
|
65
|
+
// Strings, enums, literals, unions, and anything unrecognised are already
|
|
66
|
+
// in their wire form or too ambiguous to guess at.
|
|
67
|
+
return value;
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
function coerceValue(target, value) {
|
|
71
|
+
const unwrapped = unwrap(target);
|
|
72
|
+
if (typeNameOf(unwrapped) === "ZodArray") {
|
|
73
|
+
const element = (defOf(unwrapped)?.type ?? undefined);
|
|
74
|
+
// A key that appears once arrives as a scalar, but the schema wants a list.
|
|
75
|
+
const items = Array.isArray(value) ? value : [value];
|
|
76
|
+
return items.map((item) => coerceValue(element, item));
|
|
77
|
+
}
|
|
78
|
+
// A repeated key produced an array the schema does not want. Leave it so zod
|
|
79
|
+
// reports the real mismatch.
|
|
80
|
+
if (Array.isArray(value))
|
|
81
|
+
return value;
|
|
82
|
+
if (typeof value !== "string")
|
|
83
|
+
return value;
|
|
84
|
+
return coerceScalar(unwrapped, value);
|
|
85
|
+
}
|
|
86
|
+
/**
|
|
87
|
+
* Returns a copy of `raw` with values coerced toward `schema`. Returns `raw`
|
|
88
|
+
* untouched when the schema is absent, is not an object schema, or when the
|
|
89
|
+
* payload is not a plain object.
|
|
90
|
+
*/
|
|
91
|
+
export function coerceQueryInput(schema, raw) {
|
|
92
|
+
if (!schema || raw === null || typeof raw !== "object" || Array.isArray(raw)) {
|
|
93
|
+
return raw;
|
|
94
|
+
}
|
|
95
|
+
try {
|
|
96
|
+
const objectSchema = unwrap(schema);
|
|
97
|
+
if (typeNameOf(objectSchema) !== "ZodObject")
|
|
98
|
+
return raw;
|
|
99
|
+
const objectDef = defOf(objectSchema);
|
|
100
|
+
const shapeFactory = objectDef?.shape;
|
|
101
|
+
const shape = (typeof shapeFactory === "function" ? shapeFactory() : shapeFactory);
|
|
102
|
+
if (!shape)
|
|
103
|
+
return raw;
|
|
104
|
+
const catchall = typeNameOf(objectDef?.catchall) === "ZodNever"
|
|
105
|
+
? undefined
|
|
106
|
+
: objectDef?.catchall;
|
|
107
|
+
const coerced = { ...raw };
|
|
108
|
+
for (const [key, value] of Object.entries(coerced)) {
|
|
109
|
+
const target = Object.prototype.hasOwnProperty.call(shape, key)
|
|
110
|
+
? shape[key]
|
|
111
|
+
: catchall;
|
|
112
|
+
// Untyped unknown keys are left for the schema to strip or reject.
|
|
113
|
+
if (!target)
|
|
114
|
+
continue;
|
|
115
|
+
coerced[key] = coerceValue(target, value);
|
|
116
|
+
}
|
|
117
|
+
return coerced;
|
|
118
|
+
}
|
|
119
|
+
catch {
|
|
120
|
+
// Coercion is a convenience, never a failure mode. Fall back to the raw
|
|
121
|
+
// payload so validation reports the genuine problem.
|
|
122
|
+
return raw;
|
|
123
|
+
}
|
|
124
|
+
}
|