@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.
@@ -1 +1 @@
1
- {"version":3,"file":"pipeline.d.ts","sourceRoot":"","sources":["../src/pipeline.ts"],"names":[],"mappings":"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"}
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
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@hypequery/serve",
3
- "version": "0.16.0",
3
+ "version": "0.16.1",
4
4
  "description": "Code-first TypeScript runtime for validated ClickHouse analytics APIs, OpenAPI, auth, and tenancy",
5
5
  "keywords": [
6
6
  "clickhouse",