@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.
- package/AI.md +3 -2
- package/README.md +8 -6
- package/dist/generators/assert-unevaluated-generatable.js +3 -5
- package/dist/generators/build-schema.d.ts +6 -0
- package/dist/generators/build-schema.js +22 -2
- package/dist/generators/collect-emitted-refs.d.ts +1 -1
- package/dist/generators/collect-emitted-refs.js +45 -13
- package/dist/generators/collect-validator-imports.js +3 -8
- package/dist/generators/enforced-keywords.d.ts +21 -0
- package/dist/generators/enforced-keywords.js +60 -0
- package/dist/generators/folds-to-constant.d.ts +15 -0
- package/dist/generators/folds-to-constant.js +39 -0
- package/dist/generators/generate-files.js +4 -3
- package/dist/generators/generate-validator-function.js +288 -330
- package/dist/generators/tuple-shape.d.ts +31 -0
- package/dist/generators/tuple-shape.js +18 -0
- package/dist/generators/unevaluated-match.js +12 -3
- package/package.json +3 -3
|
@@ -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
|
-
|
|
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
|
|
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(${
|
|
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.
|
|
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.
|
|
57
|
+
"@amritk/helpers": "^0.15.4"
|
|
58
58
|
},
|
|
59
59
|
"devDependencies": {
|
|
60
|
-
"@amritk/runtime-validators": "^0.
|
|
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",
|