@amritk/generate-validators 0.12.2 → 0.13.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.
@@ -0,0 +1,31 @@
1
+ /**
2
+ * How a schema spells its array positions, normalised across the two dialects
3
+ * the generator accepts.
4
+ *
5
+ * 2020-12 lists the fixed positions in `prefixItems` and types the rest with
6
+ * `items`. Draft-07 (and anything a converter left in that shape — see the note
7
+ * in `@amritk/adapters`' Valibot converter) puts the fixed positions in an
8
+ * *array* `items` and types the rest with `additionalItems`. Reading only the
9
+ * 2020-12 spelling is what let a draft-07 tuple emit no array validation at all
10
+ * while the type generator wrote out a real tuple type.
11
+ *
12
+ * - `tuple` — the fixed positions, or `undefined` when the array is not a tuple.
13
+ * - `tail` — the schema for every index past the tuple, or `undefined` when none
14
+ * is declared. `false` means "there must be no such index".
15
+ * - `tailIsClosed` — whether a tuple's length is capped: `items: false` past a
16
+ * `prefixItems`, or `additionalItems: false` past an array `items`. Each
17
+ * dialect's own spelling only — `additionalItems` is not a 2020-12 keyword, and
18
+ * honouring it next to `prefixItems` made the validator cap a length that Ajv,
19
+ * the interpreter, and the tuple type emitted beside it all leave open.
20
+ *
21
+ * `prefixItems` wins when a node carries both, which is the order
22
+ * `@amritk/helpers/generate-type-definition` and `@amritk/runtime-validators`
23
+ * already read them in. Reading the array `items` first instead made the
24
+ * validator enforce one tuple while the *type* emitted beside it described the
25
+ * other — the same type/validator split this normalisation exists to close.
26
+ */
27
+ export declare const tupleShapeOf: (schema: Record<string, unknown>) => {
28
+ tuple: unknown[] | undefined;
29
+ tail: unknown;
30
+ tailIsClosed: boolean;
31
+ };
@@ -0,0 +1,18 @@
1
+ const tupleShapeOf = (schema) => {
2
+ const items = schema["items"];
3
+ const prefix = schema["prefixItems"];
4
+ if (Array.isArray(prefix)) {
5
+ return {
6
+ tuple: prefix,
7
+ tail: "items" in schema && !Array.isArray(items) ? items : void 0,
8
+ tailIsClosed: items === false
9
+ };
10
+ }
11
+ if (Array.isArray(items)) {
12
+ return { tuple: items, tail: schema["additionalItems"], tailIsClosed: schema["additionalItems"] === false };
13
+ }
14
+ return { tuple: void 0, tail: "items" in schema ? items : void 0, tailIsClosed: items === false };
15
+ };
16
+ export {
17
+ tupleShapeOf
18
+ };
@@ -4,6 +4,7 @@ import { isSchemaObject } from "@amritk/helpers/schema-guards";
4
4
  const NONE = { all: false, terms: [] };
5
5
  const ALL = { all: true, terms: [] };
6
6
  const MAX_COVERAGE_DEPTH = 8;
7
+ const PLAIN_IDENTIFIER = /^[A-Za-z_$][\w$]*$/;
7
8
  const orJoin = (terms) => terms.length === 1 ? terms[0] : `(${terms.join(" || ")})`;
8
9
  const nest = (ctx) => ({ ...ctx, depth: ctx.depth + 1 });
9
10
  const hoistCondition = (sink, condition) => {
@@ -54,7 +55,11 @@ const ownItemCoverage = (schema, indexVar, itemVar, ignoreOwnUnevaluated, ctx, m
54
55
  if (prefix && prefix.length > 0)
55
56
  terms.push(`${indexVar} < ${prefix.length}`);
56
57
  if ("contains" in schema) {
57
- terms.push(match(itemVar, schema["contains"], ctx.depth));
58
+ const contained = match(itemVar, schema["contains"], ctx.depth);
59
+ if (contained === "true")
60
+ return ALL;
61
+ if (contained !== "false")
62
+ terms.push(contained);
58
63
  }
59
64
  return { all: false, terms };
60
65
  };
@@ -167,10 +172,14 @@ const unevaluatedPropertiesExpr = (acc, schema, rootSchema, depth, match) => {
167
172
  const expr2 = covered === null ? `Object.keys(${record}).length === 0` : `Object.keys(${record}).every((${keyVar}) => ${covered})`;
168
173
  return { setup: sink.setup, expr: expr2 };
169
174
  }
170
- const valueMatch = match(`${record}[${keyVar}]`, unevaluated, depth);
175
+ const bound = PLAIN_IDENTIFIER.test(acc) ? null : `_uo${depth}`;
176
+ if (bound !== null)
177
+ sink.setup.push(`const ${bound} = ${acc} as Record<string, unknown>`);
178
+ const source = bound ?? record;
179
+ const valueMatch = match(`${source}[${keyVar}]`, unevaluated, depth);
171
180
  if (valueMatch === "true")
172
181
  return void 0;
173
- const expr = covered === null ? `Object.keys(${record}).every((${keyVar}) => ${valueMatch})` : `Object.keys(${record}).every((${keyVar}) => ${covered} || ${valueMatch})`;
182
+ const expr = covered === null ? `Object.keys(${source}).every((${keyVar}) => ${valueMatch})` : `Object.keys(${source}).every((${keyVar}) => ${covered} || ${valueMatch})`;
174
183
  return { setup: sink.setup, expr };
175
184
  };
176
185
  const unevaluatedItemsExpr = (acc, schema, rootSchema, depth, match) => {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@amritk/generate-validators",
3
- "version": "0.12.2",
3
+ "version": "0.13.1",
4
4
  "description": "Generate TypeScript validation functions from JSON Schemas.",
5
5
  "main": "./dist/index.js",
6
6
  "module": "./dist/index.js",
@@ -54,10 +54,10 @@
54
54
  },
55
55
  "dependencies": {
56
56
  "json-schema-typed": "^8.0.1",
57
- "@amritk/helpers": "^0.15.2"
57
+ "@amritk/helpers": "^0.15.4"
58
58
  },
59
59
  "devDependencies": {
60
- "@amritk/runtime-validators": "^0.10.1",
60
+ "@amritk/runtime-validators": "^0.11.0",
61
61
  "@ryoppippi/unplugin-typia": "^2.6.5",
62
62
  "@scalar/openapi-parser": "^0.26.1",
63
63
  "@sinclair/typebox": "^0.34.49",