@amritk/validation 0.2.0
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 +92 -0
- package/LICENSE +21 -0
- package/README.md +184 -0
- package/dist/generate.d.ts +135 -0
- package/dist/generate.js +74 -0
- package/dist/index.d.ts +1 -0
- package/dist/index.js +5 -0
- package/dist/parsers/generators/assert-supported-keywords.d.ts +30 -0
- package/dist/parsers/generators/assert-supported-keywords.js +87 -0
- package/dist/parsers/generators/build-schema.d.ts +111 -0
- package/dist/parsers/generators/build-schema.js +74 -0
- package/dist/parsers/generators/generate-deep-equal-check.d.ts +24 -0
- package/dist/parsers/generators/generate-deep-equal-check.js +35 -0
- package/dist/parsers/generators/generate-discriminated-union-validation.d.ts +15 -0
- package/dist/parsers/generators/generate-discriminated-union-validation.js +28 -0
- package/dist/parsers/generators/generate-enum-check.d.ts +35 -0
- package/dist/parsers/generators/generate-enum-check.js +31 -0
- package/dist/parsers/generators/generate-files.d.ts +138 -0
- package/dist/parsers/generators/generate-files.js +89 -0
- package/dist/parsers/generators/generate-non-discriminated-union-validation.d.ts +6 -0
- package/dist/parsers/generators/generate-non-discriminated-union-validation.js +26 -0
- package/dist/parsers/generators/generate-parser-function.d.ts +120 -0
- package/dist/parsers/generators/generate-parser-function.js +1973 -0
- package/dist/parsers/generators/generate-schema-checks.d.ts +5 -0
- package/dist/parsers/generators/generate-schema-checks.js +260 -0
- package/dist/parsers/generators/generate-strict-assertion.d.ts +155 -0
- package/dist/parsers/generators/generate-strict-assertion.js +759 -0
- package/dist/parsers/generators/generate-type-checks.d.ts +148 -0
- package/dist/parsers/generators/generate-type-checks.js +454 -0
- package/dist/parsers/generators/generate-unique-items-check.d.ts +16 -0
- package/dist/parsers/generators/generate-unique-items-check.js +32 -0
- package/dist/parsers/generators/generate-validation-expression.d.ts +79 -0
- package/dist/parsers/generators/generate-validation-expression.js +416 -0
- package/dist/parsers/generators/subschema-match.d.ts +48 -0
- package/dist/parsers/generators/subschema-match.js +485 -0
- package/dist/parsers/generators/unevaluated-match.d.ts +19 -0
- package/dist/parsers/generators/unevaluated-match.js +206 -0
- package/dist/parsers/helpers/apply-schema-extensions.d.ts +35 -0
- package/dist/parsers/helpers/apply-schema-extensions.js +20 -0
- package/dist/parsers/helpers/collect-helpers.d.ts +15 -0
- package/dist/parsers/helpers/collect-helpers.js +64 -0
- package/dist/parsers/helpers/collect-imports.d.ts +104 -0
- package/dist/parsers/helpers/collect-imports.js +227 -0
- package/dist/parsers/helpers/find-discriminator.d.ts +6 -0
- package/dist/parsers/helpers/find-discriminator.js +27 -0
- package/dist/parsers/helpers/get-discriminator-value.d.ts +6 -0
- package/dist/parsers/helpers/get-discriminator-value.js +18 -0
- package/dist/parsers/index.d.ts +2 -0
- package/dist/parsers/index.js +4 -0
- package/dist/parsers/types/schema-extensions.d.ts +34 -0
- package/dist/parsers/types/schema-extensions.js +0 -0
- package/dist/rehome-parser-file.d.ts +25 -0
- package/dist/rehome-parser-file.js +74 -0
- package/dist/validators/generators/assert-generatable-refs.d.ts +19 -0
- package/dist/validators/generators/assert-generatable-refs.js +12 -0
- package/dist/validators/generators/assert-unevaluated-generatable.d.ts +30 -0
- package/dist/validators/generators/assert-unevaluated-generatable.js +71 -0
- package/dist/validators/generators/build-schema.d.ts +61 -0
- package/dist/validators/generators/build-schema.js +666 -0
- package/dist/validators/generators/collect-emitted-refs.d.ts +26 -0
- package/dist/validators/generators/collect-emitted-refs.js +112 -0
- package/dist/validators/generators/collect-validator-imports.d.ts +86 -0
- package/dist/validators/generators/collect-validator-imports.js +62 -0
- package/dist/validators/generators/emit-format-checks.d.ts +50 -0
- package/dist/validators/generators/emit-format-checks.js +248 -0
- package/dist/validators/generators/enforced-keywords.d.ts +33 -0
- package/dist/validators/generators/enforced-keywords.js +70 -0
- package/dist/validators/generators/folds-to-constant.d.ts +15 -0
- package/dist/validators/generators/folds-to-constant.js +43 -0
- package/dist/validators/generators/generate-coerce-function.d.ts +22 -0
- package/dist/validators/generators/generate-coerce-function.js +208 -0
- package/dist/validators/generators/generate-files.d.ts +92 -0
- package/dist/validators/generators/generate-files.js +86 -0
- package/dist/validators/generators/generate-repair-function.d.ts +27 -0
- package/dist/validators/generators/generate-repair-function.js +102 -0
- package/dist/validators/generators/generate-validator-function.d.ts +58 -0
- package/dist/validators/generators/generate-validator-function.js +1691 -0
- package/dist/validators/generators/tuple-shape.d.ts +31 -0
- package/dist/validators/generators/tuple-shape.js +20 -0
- package/dist/validators/generators/unevaluated-match.d.ts +29 -0
- package/dist/validators/generators/unevaluated-match.js +223 -0
- package/dist/validators/index.d.ts +2 -0
- package/dist/validators/index.js +4 -0
- package/package.json +92 -0
|
@@ -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,20 @@
|
|
|
1
|
+
import { declaresKey, readKey } from "@amritk/helpers/read-key";
|
|
2
|
+
const tupleShapeOf = (schema) => {
|
|
3
|
+
const items = readKey(schema, "items");
|
|
4
|
+
const prefix = readKey(schema, "prefixItems");
|
|
5
|
+
if (Array.isArray(prefix)) {
|
|
6
|
+
return {
|
|
7
|
+
tuple: prefix,
|
|
8
|
+
tail: declaresKey(schema, "items") && !Array.isArray(items) ? items : void 0,
|
|
9
|
+
tailIsClosed: items === false
|
|
10
|
+
};
|
|
11
|
+
}
|
|
12
|
+
if (Array.isArray(items)) {
|
|
13
|
+
const additionalItems = readKey(schema, "additionalItems");
|
|
14
|
+
return { tuple: items, tail: additionalItems, tailIsClosed: additionalItems === false };
|
|
15
|
+
}
|
|
16
|
+
return { tuple: void 0, tail: declaresKey(schema, "items") ? items : void 0, tailIsClosed: items === false };
|
|
17
|
+
};
|
|
18
|
+
export {
|
|
19
|
+
tupleShapeOf
|
|
20
|
+
};
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import type { JSONSchema } from 'json-schema-typed/draft-2020-12';
|
|
2
|
+
/**
|
|
3
|
+
* Builds the boolean expression that is TRUE when `accessor` matches `schema`.
|
|
4
|
+
* Supplied by the caller so this module stays independent of how the validator
|
|
5
|
+
* spells a match; `depth` lets the caller mint collision-free locals.
|
|
6
|
+
*/
|
|
7
|
+
export type UnevaluatedMatchFn = (accessor: string, schema: JSONSchema, depth: number) => string;
|
|
8
|
+
/** The statements to emit before {@link UnevaluatedExpression.expr}, and the test itself. */
|
|
9
|
+
export type UnevaluatedExpression = {
|
|
10
|
+
/** `const` declarations for the branch conditions the test reads. */
|
|
11
|
+
readonly setup: readonly string[];
|
|
12
|
+
/** TRUE when the value has no unevaluated key / index left over. */
|
|
13
|
+
readonly expr: string;
|
|
14
|
+
};
|
|
15
|
+
/**
|
|
16
|
+
* The `unevaluatedProperties` test for an object accessor: every own key the
|
|
17
|
+
* schema's other keywords did not evaluate must satisfy the unevaluated
|
|
18
|
+
* subschema (and `false` means there must be no such key).
|
|
19
|
+
*
|
|
20
|
+
* Returns `undefined` when the keyword is absent or constrains nothing (a `true`
|
|
21
|
+
* schema, or a node whose other keywords already sweep every key), and `null`
|
|
22
|
+
* when it cannot be proven inline.
|
|
23
|
+
*/
|
|
24
|
+
export declare const unevaluatedPropertiesExpr: (acc: string, schema: JSONSchema, rootSchema: Record<string, unknown> | undefined, depth: number, match: UnevaluatedMatchFn) => UnevaluatedExpression | null | undefined;
|
|
25
|
+
/**
|
|
26
|
+
* The `unevaluatedItems` test for an array accessor — the index counterpart of
|
|
27
|
+
* {@link unevaluatedPropertiesExpr}.
|
|
28
|
+
*/
|
|
29
|
+
export declare const unevaluatedItemsExpr: (acc: string, schema: JSONSchema, rootSchema: Record<string, unknown> | undefined, depth: number, match: UnevaluatedMatchFn) => UnevaluatedExpression | null | undefined;
|
|
@@ -0,0 +1,223 @@
|
|
|
1
|
+
import { regexLiteral } from "@amritk/helpers/escape-regex-pattern";
|
|
2
|
+
import { declaresKey, readKey } from "@amritk/helpers/read-key";
|
|
3
|
+
import { resolveRef } from "@amritk/helpers/resolve-ref";
|
|
4
|
+
import { isSchemaObject } from "@amritk/helpers/schema-guards";
|
|
5
|
+
const NONE = { all: false, terms: [] };
|
|
6
|
+
const ALL = { all: true, terms: [] };
|
|
7
|
+
const MAX_COVERAGE_DEPTH = 8;
|
|
8
|
+
const PLAIN_IDENTIFIER = /^[A-Za-z_$][\w$]*$/;
|
|
9
|
+
const orJoin = (terms) => terms.length === 1 ? terms[0] : `(${terms.join(" || ")})`;
|
|
10
|
+
const nest = (ctx) => ({ ...ctx, depth: ctx.depth + 1 });
|
|
11
|
+
const hoistCondition = (sink, condition) => {
|
|
12
|
+
const name = `${sink.prefix}${sink.setup.length}`;
|
|
13
|
+
sink.setup.push(`const ${name} = ${condition}`);
|
|
14
|
+
return name;
|
|
15
|
+
};
|
|
16
|
+
const conditioned = (coverage, condition) => {
|
|
17
|
+
if (coverage.all)
|
|
18
|
+
return { all: false, terms: [condition] };
|
|
19
|
+
if (coverage.terms.length === 0)
|
|
20
|
+
return NONE;
|
|
21
|
+
return { all: false, terms: [`(${condition} && ${orJoin(coverage.terms)})`] };
|
|
22
|
+
};
|
|
23
|
+
const merge = (into, from) => into.all || from.all ? ALL : { all: false, terms: [...into.terms, ...from.terms] };
|
|
24
|
+
const ownPropertyCoverage = (schema, keyVar, ignoreOwnUnevaluated) => {
|
|
25
|
+
if (declaresKey(schema, "additionalProperties"))
|
|
26
|
+
return ALL;
|
|
27
|
+
if (!ignoreOwnUnevaluated && declaresKey(schema, "unevaluatedProperties") && schema["unevaluatedProperties"] !== false) {
|
|
28
|
+
return ALL;
|
|
29
|
+
}
|
|
30
|
+
const terms = [];
|
|
31
|
+
const properties = readKey(schema, "properties");
|
|
32
|
+
if (typeof properties === "object" && properties !== null && !Array.isArray(properties)) {
|
|
33
|
+
const keys = Object.keys(properties);
|
|
34
|
+
if (keys.length > 0)
|
|
35
|
+
terms.push(`${JSON.stringify(keys)}.includes(${keyVar})`);
|
|
36
|
+
}
|
|
37
|
+
const patterns = readKey(schema, "patternProperties");
|
|
38
|
+
if (typeof patterns === "object" && patterns !== null && !Array.isArray(patterns)) {
|
|
39
|
+
for (const pattern of Object.keys(patterns)) {
|
|
40
|
+
terms.push(`${regexLiteral(pattern)}.test(${keyVar})`);
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
return { all: false, terms };
|
|
44
|
+
};
|
|
45
|
+
const ownItemCoverage = (schema, indexVar, itemVar, ignoreOwnUnevaluated, ctx, match) => {
|
|
46
|
+
const tupleItems = Array.isArray(readKey(schema, "items"));
|
|
47
|
+
if (!tupleItems && declaresKey(schema, "items"))
|
|
48
|
+
return ALL;
|
|
49
|
+
if (tupleItems && declaresKey(schema, "additionalItems"))
|
|
50
|
+
return ALL;
|
|
51
|
+
if (!ignoreOwnUnevaluated && declaresKey(schema, "unevaluatedItems") && schema["unevaluatedItems"] !== false) {
|
|
52
|
+
return ALL;
|
|
53
|
+
}
|
|
54
|
+
const terms = [];
|
|
55
|
+
const declaredPrefix = readKey(schema, "prefixItems");
|
|
56
|
+
const prefix = Array.isArray(declaredPrefix) ? declaredPrefix : tupleItems ? readKey(schema, "items") : null;
|
|
57
|
+
if (prefix && prefix.length > 0)
|
|
58
|
+
terms.push(`${indexVar} < ${prefix.length}`);
|
|
59
|
+
if (declaresKey(schema, "contains")) {
|
|
60
|
+
const contained = match(itemVar, readKey(schema, "contains"), ctx.depth);
|
|
61
|
+
if (contained === "true")
|
|
62
|
+
return ALL;
|
|
63
|
+
if (contained !== "false")
|
|
64
|
+
terms.push(contained);
|
|
65
|
+
}
|
|
66
|
+
return { all: false, terms };
|
|
67
|
+
};
|
|
68
|
+
const coverageOf = (kind, schema, indexVar, itemVar, acc, ctx, sink, match, ignoreOwnUnevaluated = false) => {
|
|
69
|
+
if (typeof schema === "boolean")
|
|
70
|
+
return NONE;
|
|
71
|
+
if (!isSchemaObject(schema))
|
|
72
|
+
return null;
|
|
73
|
+
if (ctx.depth > MAX_COVERAGE_DEPTH)
|
|
74
|
+
return null;
|
|
75
|
+
const s = schema;
|
|
76
|
+
if (typeof readKey(s, "$dynamicRef") === "string")
|
|
77
|
+
return null;
|
|
78
|
+
let coverage = kind === "properties" ? ownPropertyCoverage(s, indexVar, ignoreOwnUnevaluated) : ownItemCoverage(s, indexVar, itemVar, ignoreOwnUnevaluated, ctx, match);
|
|
79
|
+
const ref = readKey(s, "$ref");
|
|
80
|
+
if (typeof ref === "string") {
|
|
81
|
+
if (ctx.rootSchema === void 0 || ctx.seen.has(ref))
|
|
82
|
+
return null;
|
|
83
|
+
const target = resolveRef(ref, ctx.rootSchema);
|
|
84
|
+
if (target === void 0)
|
|
85
|
+
return null;
|
|
86
|
+
const seen = new Set(ctx.seen);
|
|
87
|
+
seen.add(ref);
|
|
88
|
+
const refCoverage = coverageOf(kind, target, indexVar, itemVar, acc, { ...nest(ctx), seen }, sink, match);
|
|
89
|
+
if (refCoverage === null)
|
|
90
|
+
return null;
|
|
91
|
+
coverage = merge(coverage, refCoverage);
|
|
92
|
+
}
|
|
93
|
+
const allOf = readKey(s, "allOf");
|
|
94
|
+
if (Array.isArray(allOf)) {
|
|
95
|
+
for (const member of allOf) {
|
|
96
|
+
const memberCoverage = coverageOf(kind, member, indexVar, itemVar, acc, nest(ctx), sink, match);
|
|
97
|
+
if (memberCoverage === null)
|
|
98
|
+
return null;
|
|
99
|
+
coverage = merge(coverage, memberCoverage);
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
for (const keyword of ["anyOf", "oneOf"]) {
|
|
103
|
+
const branches = readKey(s, keyword);
|
|
104
|
+
if (!Array.isArray(branches))
|
|
105
|
+
continue;
|
|
106
|
+
for (const branch of branches) {
|
|
107
|
+
const branchCoverage = coverageOf(kind, branch, indexVar, itemVar, acc, nest(ctx), sink, match);
|
|
108
|
+
if (branchCoverage === null)
|
|
109
|
+
return null;
|
|
110
|
+
if (!branchCoverage.all && branchCoverage.terms.length === 0)
|
|
111
|
+
continue;
|
|
112
|
+
coverage = merge(coverage, conditioned(branchCoverage, hoistCondition(sink, match(acc, branch, ctx.depth))));
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
if (declaresKey(s, "if")) {
|
|
116
|
+
const branches = [];
|
|
117
|
+
for (const [keyword, negated] of [
|
|
118
|
+
["if", false],
|
|
119
|
+
["then", false],
|
|
120
|
+
["else", true]
|
|
121
|
+
]) {
|
|
122
|
+
if (!declaresKey(s, keyword))
|
|
123
|
+
continue;
|
|
124
|
+
const branchCoverage = coverageOf(kind, readKey(s, keyword), indexVar, itemVar, acc, nest(ctx), sink, match);
|
|
125
|
+
if (branchCoverage === null)
|
|
126
|
+
return null;
|
|
127
|
+
if (!branchCoverage.all && branchCoverage.terms.length === 0)
|
|
128
|
+
continue;
|
|
129
|
+
branches.push({ coverage: branchCoverage, negated });
|
|
130
|
+
}
|
|
131
|
+
if (branches.length > 0) {
|
|
132
|
+
const ifMatch = hoistCondition(sink, match(acc, readKey(s, "if"), ctx.depth));
|
|
133
|
+
for (const branch of branches) {
|
|
134
|
+
coverage = merge(coverage, conditioned(branch.coverage, branch.negated ? `!${ifMatch}` : ifMatch));
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
for (const keyword of ["dependentSchemas", "dependencies"]) {
|
|
139
|
+
const dependents = readKey(s, keyword);
|
|
140
|
+
if (typeof dependents !== "object" || dependents === null || Array.isArray(dependents))
|
|
141
|
+
continue;
|
|
142
|
+
for (const [trigger, sub] of Object.entries(dependents)) {
|
|
143
|
+
if (Array.isArray(sub))
|
|
144
|
+
continue;
|
|
145
|
+
const subCoverage = coverageOf(kind, sub, indexVar, itemVar, acc, nest(ctx), sink, match);
|
|
146
|
+
if (subCoverage === null)
|
|
147
|
+
return null;
|
|
148
|
+
if (!subCoverage.all && subCoverage.terms.length === 0)
|
|
149
|
+
continue;
|
|
150
|
+
const present = `Object.hasOwn(${acc} as object, ${JSON.stringify(trigger)})`;
|
|
151
|
+
coverage = merge(coverage, conditioned(subCoverage, hoistCondition(sink, present)));
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
return coverage;
|
|
155
|
+
};
|
|
156
|
+
const unevaluatedPropertiesExpr = (acc, schema, rootSchema, depth, match) => {
|
|
157
|
+
if (!isSchemaObject(schema))
|
|
158
|
+
return void 0;
|
|
159
|
+
const s = schema;
|
|
160
|
+
if (!declaresKey(s, "unevaluatedProperties"))
|
|
161
|
+
return void 0;
|
|
162
|
+
const unevaluated = readKey(s, "unevaluatedProperties");
|
|
163
|
+
if (unevaluated === true)
|
|
164
|
+
return void 0;
|
|
165
|
+
const keyVar = `_uk${depth}`;
|
|
166
|
+
const sink = { setup: [], prefix: `_uc${depth}_` };
|
|
167
|
+
const ctx = { rootSchema, depth: 0, seen: /* @__PURE__ */ new Set() };
|
|
168
|
+
const coverage = coverageOf("properties", schema, keyVar, "", acc, ctx, sink, match, true);
|
|
169
|
+
if (coverage === null)
|
|
170
|
+
return null;
|
|
171
|
+
if (coverage.all)
|
|
172
|
+
return void 0;
|
|
173
|
+
const record = `(${acc} as Record<string, unknown>)`;
|
|
174
|
+
const covered = coverage.terms.length > 0 ? orJoin(coverage.terms) : null;
|
|
175
|
+
if (unevaluated === false) {
|
|
176
|
+
const expr2 = covered === null ? `Object.keys(${record}).length === 0` : `Object.keys(${record}).every((${keyVar}) => ${covered})`;
|
|
177
|
+
return { setup: sink.setup, expr: expr2 };
|
|
178
|
+
}
|
|
179
|
+
const bound = PLAIN_IDENTIFIER.test(acc) ? null : `_uo${depth}`;
|
|
180
|
+
if (bound !== null)
|
|
181
|
+
sink.setup.push(`const ${bound} = ${acc} as Record<string, unknown>`);
|
|
182
|
+
const source = bound ?? record;
|
|
183
|
+
const valueMatch = match(`${source}[${keyVar}]`, unevaluated, depth);
|
|
184
|
+
if (valueMatch === "true")
|
|
185
|
+
return void 0;
|
|
186
|
+
const expr = covered === null ? `Object.keys(${source}).every((${keyVar}) => ${valueMatch})` : `Object.keys(${source}).every((${keyVar}) => ${covered} || ${valueMatch})`;
|
|
187
|
+
return { setup: sink.setup, expr };
|
|
188
|
+
};
|
|
189
|
+
const unevaluatedItemsExpr = (acc, schema, rootSchema, depth, match) => {
|
|
190
|
+
if (!isSchemaObject(schema))
|
|
191
|
+
return void 0;
|
|
192
|
+
const s = schema;
|
|
193
|
+
if (!declaresKey(s, "unevaluatedItems"))
|
|
194
|
+
return void 0;
|
|
195
|
+
const unevaluated = readKey(s, "unevaluatedItems");
|
|
196
|
+
if (unevaluated === true)
|
|
197
|
+
return void 0;
|
|
198
|
+
const indexVar = `_un${depth}`;
|
|
199
|
+
const itemVar = `_ue${depth}`;
|
|
200
|
+
const sink = { setup: [], prefix: `_ud${depth}_` };
|
|
201
|
+
const ctx = { rootSchema, depth: 0, seen: /* @__PURE__ */ new Set() };
|
|
202
|
+
const coverage = coverageOf("items", schema, indexVar, itemVar, acc, ctx, sink, match, true);
|
|
203
|
+
if (coverage === null)
|
|
204
|
+
return null;
|
|
205
|
+
if (coverage.all)
|
|
206
|
+
return void 0;
|
|
207
|
+
const elements = `(${acc} as unknown[])`;
|
|
208
|
+
const visited = `Array.from(${elements})`;
|
|
209
|
+
const covered = coverage.terms.length > 0 ? orJoin(coverage.terms) : null;
|
|
210
|
+
if (unevaluated === false) {
|
|
211
|
+
const expr2 = covered === null ? `${elements}.length === 0` : `${visited}.every((${itemVar}, ${indexVar}) => ${covered})`;
|
|
212
|
+
return { setup: sink.setup, expr: expr2 };
|
|
213
|
+
}
|
|
214
|
+
const valueMatch = match(itemVar, unevaluated, depth);
|
|
215
|
+
if (valueMatch === "true")
|
|
216
|
+
return void 0;
|
|
217
|
+
const expr = covered === null ? `${visited}.every((${itemVar}) => ${valueMatch})` : `${visited}.every((${itemVar}, ${indexVar}) => ${covered} || ${valueMatch})`;
|
|
218
|
+
return { setup: sink.setup, expr };
|
|
219
|
+
};
|
|
220
|
+
export {
|
|
221
|
+
unevaluatedItemsExpr,
|
|
222
|
+
unevaluatedPropertiesExpr
|
|
223
|
+
};
|
package/package.json
ADDED
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@amritk/validation",
|
|
3
|
+
"version": "0.2.0",
|
|
4
|
+
"description": "One generator surface over mjst's types, validators, coercers, repairers and parsers.",
|
|
5
|
+
"main": "./dist/index.js",
|
|
6
|
+
"module": "./dist/index.js",
|
|
7
|
+
"types": "./dist/index.d.ts",
|
|
8
|
+
"type": "module",
|
|
9
|
+
"sideEffects": false,
|
|
10
|
+
"engines": {
|
|
11
|
+
"node": ">=20"
|
|
12
|
+
},
|
|
13
|
+
"license": "MIT",
|
|
14
|
+
"author": "amritk",
|
|
15
|
+
"keywords": [
|
|
16
|
+
"json-schema",
|
|
17
|
+
"typescript",
|
|
18
|
+
"validator",
|
|
19
|
+
"parser",
|
|
20
|
+
"code-generation",
|
|
21
|
+
"mjst"
|
|
22
|
+
],
|
|
23
|
+
"repository": {
|
|
24
|
+
"type": "git",
|
|
25
|
+
"url": "git+https://github.com/amritk/mjst.git",
|
|
26
|
+
"directory": "packages/validation"
|
|
27
|
+
},
|
|
28
|
+
"homepage": "https://github.com/amritk/mjst/tree/main/packages/validation#readme",
|
|
29
|
+
"bugs": {
|
|
30
|
+
"url": "https://github.com/amritk/mjst/issues"
|
|
31
|
+
},
|
|
32
|
+
"files": [
|
|
33
|
+
"dist",
|
|
34
|
+
"AI.md"
|
|
35
|
+
],
|
|
36
|
+
"publishConfig": {
|
|
37
|
+
"access": "public"
|
|
38
|
+
},
|
|
39
|
+
"scripts": {
|
|
40
|
+
"build": "tsgo -p tsconfig.build.json && tsc-alias -p tsconfig.build.json -f && node ../../scripts/strip-comments.mjs",
|
|
41
|
+
"prepublishOnly": "node ../../scripts/check-publishable.mjs",
|
|
42
|
+
"types:check": "tsgo -p . --noEmit",
|
|
43
|
+
"test": "NODE_ENV=production vitest run --root ../.. packages/validation/",
|
|
44
|
+
"prebench": "bun run --filter='@amritk/helpers' build",
|
|
45
|
+
"bench": "bun --conditions development ./bench/compare.ts",
|
|
46
|
+
"prebench:parsers": "bun run --filter='@amritk/helpers' build && bun run build",
|
|
47
|
+
"bench:parsers": "bun --conditions development ./bench/parsers/run.ts",
|
|
48
|
+
"prebench:parsers:node": "bun run --filter='@amritk/helpers' build && bun run build",
|
|
49
|
+
"bench:parsers:node": "node ./bench/parsers/run.ts",
|
|
50
|
+
"bench:parsers:moltar": "bun --conditions development ./bench/parsers/moltar.ts",
|
|
51
|
+
"prebench:validators": "bun run --filter='@amritk/helpers' build && bun run build",
|
|
52
|
+
"bench:validators": "bun --conditions development ./bench/validators/run.ts",
|
|
53
|
+
"prebench:validators:node": "bun run --filter='@amritk/helpers' build && bun run build",
|
|
54
|
+
"bench:validators:node": "node ./bench/validators/run.ts",
|
|
55
|
+
"prebench:validators:check": "bun run --filter='@amritk/helpers' build && bun run build",
|
|
56
|
+
"bench:validators:check": "bun --conditions development ./bench/validators/check.ts",
|
|
57
|
+
"prebench:validators:coerce": "bun run --filter='@amritk/helpers' build && bun run build",
|
|
58
|
+
"bench:validators:coerce": "bun --conditions development ./bench/validators/coerce.ts",
|
|
59
|
+
"prebench:validators:coerce:node": "bun run --filter='@amritk/helpers' build && bun run build",
|
|
60
|
+
"bench:validators:coerce:node": "node ./bench/validators/coerce.ts",
|
|
61
|
+
"bench:validators:moltar": "bun --conditions development ./bench/validators/moltar.ts",
|
|
62
|
+
"bench:validators:moltar:leaderboard": "bun --conditions development ./bench/validators/moltar-leaderboard.ts"
|
|
63
|
+
},
|
|
64
|
+
"imports": {
|
|
65
|
+
"#internal/*": "./src/*.ts",
|
|
66
|
+
"#parsers/*": "./src/parsers/*.ts",
|
|
67
|
+
"#validators/*": "./src/validators/*.ts"
|
|
68
|
+
},
|
|
69
|
+
"exports": {
|
|
70
|
+
".": {
|
|
71
|
+
"types": "./dist/index.d.ts",
|
|
72
|
+
"default": "./dist/index.js"
|
|
73
|
+
}
|
|
74
|
+
},
|
|
75
|
+
"dependencies": {
|
|
76
|
+
"@amritk/helpers": "^0.23.0",
|
|
77
|
+
"json-schema-typed": "^8.0.1"
|
|
78
|
+
},
|
|
79
|
+
"devDependencies": {
|
|
80
|
+
"@amritk/runtime-validators": "^0.15.0",
|
|
81
|
+
"@ryoppippi/unplugin-typia": "^2.6.5",
|
|
82
|
+
"@scalar/openapi-parser": "^0.26.1",
|
|
83
|
+
"@sinclair/typebox": "^0.34.49",
|
|
84
|
+
"ajv": "^8.17.1",
|
|
85
|
+
"ajv-formats": "^3.0.1",
|
|
86
|
+
"benny": "^3.7.1",
|
|
87
|
+
"esbuild": "^0.28.1",
|
|
88
|
+
"typescript": "^5.9.3",
|
|
89
|
+
"typia": "^12.1.1",
|
|
90
|
+
"zod": "^4.4.3"
|
|
91
|
+
}
|
|
92
|
+
}
|