@amritk/generate-parsers 0.23.0 → 0.25.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.
Files changed (51) hide show
  1. package/README.md +37 -463
  2. package/dist/index.d.ts +22 -2
  3. package/dist/index.js +18 -1
  4. package/package.json +15 -40
  5. package/AI.md +0 -52
  6. package/dist/generators/assert-supported-keywords.d.ts +0 -30
  7. package/dist/generators/assert-supported-keywords.js +0 -87
  8. package/dist/generators/build-schema.d.ts +0 -111
  9. package/dist/generators/build-schema.js +0 -74
  10. package/dist/generators/generate-deep-equal-check.d.ts +0 -24
  11. package/dist/generators/generate-deep-equal-check.js +0 -35
  12. package/dist/generators/generate-default-from-pattern.d.ts +0 -26
  13. package/dist/generators/generate-default-from-pattern.js +0 -223
  14. package/dist/generators/generate-discriminated-union-validation.d.ts +0 -15
  15. package/dist/generators/generate-discriminated-union-validation.js +0 -28
  16. package/dist/generators/generate-enum-check.d.ts +0 -35
  17. package/dist/generators/generate-enum-check.js +0 -31
  18. package/dist/generators/generate-files.d.ts +0 -138
  19. package/dist/generators/generate-files.js +0 -79
  20. package/dist/generators/generate-non-discriminated-union-validation.d.ts +0 -6
  21. package/dist/generators/generate-non-discriminated-union-validation.js +0 -26
  22. package/dist/generators/generate-parser-function.d.ts +0 -120
  23. package/dist/generators/generate-parser-function.js +0 -1955
  24. package/dist/generators/generate-schema-checks.d.ts +0 -5
  25. package/dist/generators/generate-schema-checks.js +0 -260
  26. package/dist/generators/generate-strict-assertion.d.ts +0 -155
  27. package/dist/generators/generate-strict-assertion.js +0 -759
  28. package/dist/generators/generate-type-checks.d.ts +0 -148
  29. package/dist/generators/generate-type-checks.js +0 -454
  30. package/dist/generators/generate-unique-items-check.d.ts +0 -16
  31. package/dist/generators/generate-unique-items-check.js +0 -32
  32. package/dist/generators/generate-validation-expression.d.ts +0 -79
  33. package/dist/generators/generate-validation-expression.js +0 -416
  34. package/dist/generators/subschema-match.d.ts +0 -48
  35. package/dist/generators/subschema-match.js +0 -485
  36. package/dist/generators/unevaluated-match.d.ts +0 -19
  37. package/dist/generators/unevaluated-match.js +0 -206
  38. package/dist/helpers/apply-schema-extensions.d.ts +0 -35
  39. package/dist/helpers/apply-schema-extensions.js +0 -20
  40. package/dist/helpers/collect-helpers.d.ts +0 -15
  41. package/dist/helpers/collect-helpers.js +0 -64
  42. package/dist/helpers/collect-imports.d.ts +0 -93
  43. package/dist/helpers/collect-imports.js +0 -197
  44. package/dist/helpers/find-discriminator.d.ts +0 -6
  45. package/dist/helpers/find-discriminator.js +0 -27
  46. package/dist/helpers/get-default-value.d.ts +0 -7
  47. package/dist/helpers/get-default-value.js +0 -155
  48. package/dist/helpers/get-discriminator-value.d.ts +0 -6
  49. package/dist/helpers/get-discriminator-value.js +0 -18
  50. package/dist/types/schema-extensions.d.ts +0 -34
  51. package/dist/types/schema-extensions.js +0 -0
@@ -1,5 +0,0 @@
1
- import type { JSONSchema } from 'json-schema-typed/draft-2020-12';
2
- /**
3
- * Generates validation checks for a single schema (used in union validation).
4
- */
5
- export declare const generateSchemaChecks: (accessor: string, schema: JSONSchema) => string[];
@@ -1,260 +0,0 @@
1
- import { regexLiteral } from "@amritk/helpers/escape-regex-pattern";
2
- import { getMjstInstanceOf, getMjstPrimitive } from "@amritk/helpers/mjst-extension";
3
- import { multipleOfPassExpr } from "@amritk/helpers/multiple-of-check";
4
- import { boundPassExpr } from "@amritk/helpers/numeric-bound-check";
5
- import { hasOwnCheck } from "@amritk/helpers/safe-accessor";
6
- import { hasAdditionalProperties, hasConst, hasEnum, hasExclusiveMaximum, hasExclusiveMinimum, hasMaxItems, hasMaximum, hasMaxLength, hasMaxProperties, hasMinItems, hasMinimum, hasMinLength, hasMinProperties, hasMultipleOf, hasPattern, hasProperties, hasRequired, hasType, hasUniqueItems, isSchemaObject } from "@amritk/helpers/schema-guards";
7
- import { maxLengthPassExpr, minLengthPassExpr } from "@amritk/helpers/string-length-check";
8
- import { generateDeepEqualCheck } from "./generate-deep-equal-check.js";
9
- import { generateEnumCheck } from "./generate-enum-check.js";
10
- import { generateUniqueItemsCheck } from "./generate-unique-items-check.js";
11
- const OBJECT_KEYWORDS = [
12
- "properties",
13
- "required",
14
- "minProperties",
15
- "maxProperties",
16
- "additionalProperties",
17
- "patternProperties",
18
- "dependentRequired",
19
- "propertyNames"
20
- ];
21
- const ARRAY_KEYWORDS = ["items", "prefixItems", "minItems", "maxItems", "uniqueItems", "contains"];
22
- const STRING_KEYWORDS = ["minLength", "maxLength", "pattern", "format"];
23
- const NUMBER_KEYWORDS = ["minimum", "maximum", "exclusiveMinimum", "exclusiveMaximum", "multipleOf"];
24
- const countKeywords = (schema, keywords) => keywords.reduce((total, keyword) => keyword in schema ? total + 1 : total, 0);
25
- const typeOfLiteral = (value) => {
26
- if (value === null)
27
- return "null";
28
- if (Array.isArray(value))
29
- return "array";
30
- const type = typeof value;
31
- if (type === "boolean")
32
- return "boolean";
33
- if (type === "number" || type === "bigint")
34
- return "number";
35
- if (type === "string")
36
- return "string";
37
- return "object";
38
- };
39
- const inferSchemaType = (schema) => {
40
- if (!isSchemaObject(schema))
41
- return void 0;
42
- if (hasConst(schema)) {
43
- return typeOfLiteral(schema.const);
44
- }
45
- if (hasEnum(schema) && schema.enum.length > 0 && schema.enum.every((value) => value === null)) {
46
- return "null";
47
- }
48
- const scores = {
49
- object: countKeywords(schema, OBJECT_KEYWORDS),
50
- array: countKeywords(schema, ARRAY_KEYWORDS),
51
- string: countKeywords(schema, STRING_KEYWORDS),
52
- number: countKeywords(schema, NUMBER_KEYWORDS)
53
- };
54
- let best;
55
- for (const type of ["object", "array", "string", "number"]) {
56
- if (scores[type] > 0 && (best === void 0 || scores[type] > scores[best])) {
57
- best = type;
58
- }
59
- }
60
- return best;
61
- };
62
- const generateInferredChecks = (accessor, schema, type) => {
63
- const checks = [];
64
- switch (type) {
65
- case "object":
66
- checks.push(`typeof ${accessor} === "object" && ${accessor} !== null && !Array.isArray(${accessor})`);
67
- if (hasRequired(schema) && schema.required.length > 0) {
68
- for (const requiredKey of schema.required) {
69
- checks.push(hasOwnCheck(accessor, requiredKey));
70
- }
71
- }
72
- if (hasMinProperties(schema)) {
73
- checks.push(`Object.keys(${accessor}).length >= ${schema.minProperties}`);
74
- }
75
- if (hasMaxProperties(schema)) {
76
- checks.push(`Object.keys(${accessor}).length <= ${schema.maxProperties}`);
77
- }
78
- if (hasAdditionalProperties(schema) && schema.additionalProperties === false && hasProperties(schema)) {
79
- const allowedKeysStr = JSON.stringify(Object.keys(schema.properties));
80
- checks.push(`Object.keys(${accessor}).every(k => ${allowedKeysStr}.includes(k))`);
81
- }
82
- break;
83
- case "array":
84
- checks.push(`Array.isArray(${accessor})`);
85
- if (hasMinItems(schema)) {
86
- checks.push(`${accessor}.length >= ${schema.minItems}`);
87
- }
88
- if (hasMaxItems(schema)) {
89
- checks.push(`${accessor}.length <= ${schema.maxItems}`);
90
- }
91
- if (hasUniqueItems(schema) && schema.uniqueItems === true) {
92
- checks.push(generateUniqueItemsCheck(accessor, schema));
93
- }
94
- break;
95
- case "string":
96
- checks.push(`typeof ${accessor} === "string"`);
97
- if (hasPattern(schema)) {
98
- checks.push(`${regexLiteral(schema.pattern)}.test(${accessor})`);
99
- }
100
- if (hasMinLength(schema)) {
101
- checks.push(minLengthPassExpr(accessor, schema.minLength));
102
- }
103
- if (hasMaxLength(schema)) {
104
- checks.push(maxLengthPassExpr(accessor, schema.maxLength));
105
- }
106
- break;
107
- case "number":
108
- checks.push(`typeof ${accessor} === "number"`);
109
- if (hasMinimum(schema)) {
110
- checks.push(boundPassExpr(accessor, "minimum", schema.minimum));
111
- }
112
- if (hasMaximum(schema)) {
113
- checks.push(boundPassExpr(accessor, "maximum", schema.maximum));
114
- }
115
- if (hasExclusiveMinimum(schema)) {
116
- checks.push(boundPassExpr(accessor, "minimum", schema.exclusiveMinimum, true));
117
- }
118
- if (hasExclusiveMaximum(schema)) {
119
- checks.push(boundPassExpr(accessor, "maximum", schema.exclusiveMaximum, true));
120
- }
121
- if (hasMultipleOf(schema)) {
122
- checks.push(`${accessor} % ${schema.multipleOf} === 0`);
123
- }
124
- break;
125
- case "boolean":
126
- checks.push(`typeof ${accessor} === "boolean"`);
127
- break;
128
- case "null":
129
- if (hasConst(schema)) {
130
- checks.push(`${accessor} === null`);
131
- }
132
- break;
133
- }
134
- return checks;
135
- };
136
- const generateSchemaChecks = (accessor, schema) => {
137
- const checks = [];
138
- const instanceOf = getMjstInstanceOf(schema);
139
- if (instanceOf) {
140
- return [`${accessor} instanceof ${instanceOf}`];
141
- }
142
- const primitive = getMjstPrimitive(schema);
143
- if (primitive) {
144
- return [`typeof ${accessor} === "${primitive}"`];
145
- }
146
- if (hasConst(schema)) {
147
- return [generateDeepEqualCheck(accessor, schema.const)];
148
- }
149
- if (isSchemaObject(schema) && Array.isArray(schema.type)) {
150
- const branches = [];
151
- for (const type of schema.type) {
152
- if (typeof type !== "string")
153
- continue;
154
- const branch = generateSchemaChecks(accessor, { ...schema, type });
155
- if (branch.length === 0)
156
- continue;
157
- branches.push(branch.length === 1 ? branch[0] : `(${branch.join(" && ")})`);
158
- }
159
- if (branches.length === 0)
160
- return checks;
161
- return [branches.length === 1 ? branches[0] : `(${branches.join(" || ")})`];
162
- }
163
- if (!hasType(schema)) {
164
- const inferred = inferSchemaType(schema);
165
- if (!inferred) {
166
- return checks;
167
- }
168
- checks.push(...generateInferredChecks(accessor, schema, inferred));
169
- if (hasEnum(schema) && schema.enum.length > 0) {
170
- checks.push(generateEnumCheck(accessor, schema.enum));
171
- }
172
- return checks;
173
- }
174
- switch (schema.type) {
175
- case "string": {
176
- checks.push(`typeof ${accessor} === "string"`);
177
- if (hasPattern(schema)) {
178
- checks.push(`${regexLiteral(schema.pattern)}.test(${accessor})`);
179
- }
180
- if (hasMinLength(schema)) {
181
- checks.push(minLengthPassExpr(accessor, schema.minLength));
182
- }
183
- if (hasMaxLength(schema)) {
184
- checks.push(maxLengthPassExpr(accessor, schema.maxLength));
185
- }
186
- break;
187
- }
188
- case "number":
189
- case "integer":
190
- checks.push(`typeof ${accessor} === "number"`);
191
- if (schema.type === "integer") {
192
- checks.push(`Number.isInteger(${accessor})`);
193
- }
194
- if (hasMinimum(schema)) {
195
- checks.push(boundPassExpr(accessor, "minimum", schema.minimum));
196
- }
197
- if (hasMaximum(schema)) {
198
- checks.push(boundPassExpr(accessor, "maximum", schema.maximum));
199
- }
200
- if (hasExclusiveMinimum(schema)) {
201
- checks.push(boundPassExpr(accessor, "minimum", schema.exclusiveMinimum, true));
202
- }
203
- if (hasExclusiveMaximum(schema)) {
204
- checks.push(boundPassExpr(accessor, "maximum", schema.exclusiveMaximum, true));
205
- }
206
- if (hasMultipleOf(schema)) {
207
- checks.push(multipleOfPassExpr(accessor, schema.multipleOf));
208
- }
209
- break;
210
- case "boolean":
211
- checks.push(`typeof ${accessor} === "boolean"`);
212
- break;
213
- // Without this the `null` branch of a union produced NO check, which reads
214
- // as "this branch matches nothing": `{ anyOf: [{ type: 'string' },
215
- // { type: 'null' }] }` rejected `null` — the one value the branch exists to
216
- // admit — and the coercing build dropped the property instead of keeping it.
217
- case "null":
218
- checks.push(`${accessor} === null`);
219
- break;
220
- case "array":
221
- checks.push(`Array.isArray(${accessor})`);
222
- if (hasMinItems(schema)) {
223
- checks.push(`${accessor}.length >= ${schema.minItems}`);
224
- }
225
- if (hasMaxItems(schema)) {
226
- checks.push(`${accessor}.length <= ${schema.maxItems}`);
227
- }
228
- if (hasUniqueItems(schema) && schema.uniqueItems === true) {
229
- checks.push(generateUniqueItemsCheck(accessor, schema));
230
- }
231
- break;
232
- case "object": {
233
- checks.push(`typeof ${accessor} === "object" && ${accessor} !== null && !Array.isArray(${accessor})`);
234
- if (hasRequired(schema) && schema.required.length > 0) {
235
- for (const requiredKey of schema.required) {
236
- checks.push(hasOwnCheck(accessor, requiredKey));
237
- }
238
- }
239
- if (hasMinProperties(schema)) {
240
- checks.push(`Object.keys(${accessor}).length >= ${schema.minProperties}`);
241
- }
242
- if (hasMaxProperties(schema)) {
243
- checks.push(`Object.keys(${accessor}).length <= ${schema.maxProperties}`);
244
- }
245
- if (hasAdditionalProperties(schema) && schema.additionalProperties === false && hasProperties(schema)) {
246
- const allowedKeys = Object.keys(schema.properties);
247
- const allowedKeysStr = JSON.stringify(allowedKeys);
248
- checks.push(`Object.keys(${accessor}).every(k => ${allowedKeysStr}.includes(k))`);
249
- }
250
- break;
251
- }
252
- }
253
- if (hasEnum(schema) && schema.enum.length > 0) {
254
- checks.push(generateEnumCheck(accessor, schema.enum));
255
- }
256
- return checks;
257
- };
258
- export {
259
- generateSchemaChecks
260
- };
@@ -1,155 +0,0 @@
1
- import type { UnknownKeysStrategy } from '@amritk/helpers/unknown-keys-strategy';
2
- import type { JSONSchema } from 'json-schema-typed/draft-2020-12';
3
- /**
4
- * Context for assertions that reach beyond the property's own schema: union
5
- * membership checks may call imported `validate{X}Shape` predicates ($ref
6
- * branches), which requires knowing the ref-import mode, the type-name suffix,
7
- * and the root document (to prove those validators are real — see
8
- * canEnforceUnion). `stripUnknown` disables union enforcement entirely, since
9
- * shape validators then treat undeclared keys as a mismatch while the
10
- * stripUnknown contract is to drop them.
11
- */
12
- export type StrictAssertionContext = {
13
- readonly useRefImports?: boolean;
14
- readonly suffix?: string;
15
- readonly rootSchema?: Record<string, unknown>;
16
- readonly stripUnknown?: boolean;
17
- /**
18
- * The parser's `unknownKeys` option, carried so every object parser one of
19
- * these contexts reaches counts keys on its fast path the same way.
20
- */
21
- readonly unknownKeys?: UnknownKeysStrategy;
22
- /**
23
- * Whether the parser these assertions run inside also emits the private
24
- * sub-parsers for its inline object properties (and inline-object array
25
- * items). `generateObjectParser` does, so the shape of such a property is
26
- * proven by the call it delegates to and asserting it here would be
27
- * redundant. The pattern-property parsers do NOT — they build their result
28
- * with a copy loop — so a nested object property there is proven by nobody
29
- * unless these assertions prove it. Defaults to true, the historical
30
- * assumption.
31
- */
32
- readonly subParsers?: boolean;
33
- };
34
- /**
35
- * Why a node needs the whole-schema backstop, or `null` when the specialized
36
- * assertions above already cover it. Each reason is a keyword whose enforcement
37
- * no per-property / per-type check can express:
38
- *
39
- * - `unevaluated*` depends on which *other* keywords evaluated which keys, so it
40
- * can only be answered against the whole node at once.
41
- * - a `$ref` is normally enforced by the parser generated for its target, but
42
- * that delegation covers neither an inlined build (no ref imports) nor the
43
- * 2020-12 rule that a `$ref`'s *siblings* still apply.
44
- * - `items: false` (with no tuple positions) admits only the empty array, and
45
- * the item-check builder reads `items` through a guard that a boolean fails.
46
- * - a constraint keyword with no `type` never reaches the typed constraint pass.
47
- */
48
- export declare const backstopReason: (schema: JSONSchema, context: StrictAssertionContext) => string | null;
49
- /**
50
- * The slice of a node the backstop actually has to prove.
51
- *
52
- * `unevaluated*` and `$ref` need the node whole — the first because its answer
53
- * depends on what every sibling keyword evaluated, the second because a ref's
54
- * siblings apply alongside its target. A bare constraint needs only itself, and
55
- * proving it in isolation is what keeps an ordinary
56
- * `{ allOf: [{…}, { $ref: … }], required: ['role'] }` — the OpenAPI idiom —
57
- * generating: the `required` is provable even when the surrounding `allOf`
58
- * member is too deep (or too unresolvable) to inline, and that member has its
59
- * own enforcement anyway.
60
- *
61
- * `properties` rides along, flattened to `true` schemas, only because
62
- * `additionalProperties` needs the declared key names to know which keys it
63
- * owns. Their values are irrelevant to that question and are asserted
64
- * per-property elsewhere.
65
- */
66
- export declare const backstopSchema: (schema: JSONSchema, reason: string) => JSONSchema;
67
- /**
68
- * The strict-mode backstop: a single whole-node match, emitted only for the
69
- * keywords {@link backstopReason} identifies as unreachable by the specialized
70
- * assertions. It is the same exact matcher `contains` and `propertyNames` use, so
71
- * a `true` verdict means valid and a `false` one means invalid — which is what
72
- * lets it stand alone as the enforcement for those keywords.
73
- *
74
- * Emitting nothing here is only safe because `assertNoUnsupportedKeywords`
75
- * refuses generation for any node that needs a backstop the matcher cannot
76
- * build.
77
- */
78
- export declare const generateBackstopAssertion: (acc: string, schema: JSONSchema, label: string, context: StrictAssertionContext, optional?: boolean) => string[];
79
- /**
80
- * Strict-mode `minProperties` / `maxProperties`. Both were read by the coercing
81
- * expression builder and the subschema matcher but by nothing on the strict
82
- * path, so a strict parser accepted `{}` against `{ minProperties: 2 }` — at the
83
- * root, on a property, and inside the empty-object/record parsers alike. `guard`
84
- * wraps the comparison in a runtime `isObject` for accessors whose type is
85
- * reported separately; callers that have already proven an object pass `false`.
86
- */
87
- export declare const generateObjectSizeChecks: (acc: string, schema: JSONSchema, label: string, guard: boolean) => string[];
88
- /**
89
- * Strict-mode value checks for keys the schema constrains by *pattern* rather
90
- * than by name: `patternProperties` (every key matching the regex) and a
91
- * schema-valued `additionalProperties` (every key matched by neither
92
- * `properties` nor a pattern). Both were read only by the *building* code paths
93
- * — which coerce or copy — so a strict parser accepted `{ s_a: 1 }` against
94
- * `patternProperties: { '^s_': { type: 'string' } }`.
95
- *
96
- * One `Object.keys` walk covers both, and a term is emitted only when
97
- * {@link subschemaMatchExpr} proves its subschema exactly. `additionalProperties:
98
- * false` is not handled here — that is the unknown-key rejection the parsers
99
- * already emit.
100
- */
101
- export declare const generateKeyedValueChecks: (obj: string, schema: JSONSchema, label: string, context?: StrictAssertionContext) => string[];
102
- /**
103
- * Strict-mode `contains` / `minContains` / `maxContains`: at least `minContains`
104
- * (default 1) and at most `maxContains` (default ∞) items of the array must
105
- * match the `contains` subschema. Runtime-gated on `Array.isArray` so a
106
- * non-array value is left to the type check, and wrapped in a block so the
107
- * per-array count variable never collides with a sibling check. `minContains: 0`
108
- * makes the lower bound trivially satisfied. Emits nothing when there is no
109
- * `contains` or its subschema is not matchable inline (the generation-time guard
110
- * rejects the latter, so a strict parser never silently drops the constraint).
111
- */
112
- export declare const generateContainsCheck: (acc: string, schema: JSONSchema, label: string, context?: StrictAssertionContext) => string[];
113
- /**
114
- * The composition checks (`not`, `allOf`, `if`/`then`/`else`) for a value the
115
- * caller has already proven present. Exported for the specialized object parsers
116
- * (empty-object, pattern-properties, additionalProperties), which build their
117
- * result outside {@link generateObjectStrictAssertion} and so would otherwise
118
- * emit no composition check at all.
119
- */
120
- export declare const generateCompositionChecks: (acc: string, schema: JSONSchema, label: string, context?: StrictAssertionContext) => string[];
121
- /**
122
- * Emits the object-level keyword checks (`dependentRequired`, `dependentSchemas`,
123
- * `propertyNames`) for the object reached by `obj`. When `guard` is true the
124
- * whole block is wrapped in a runtime `isObject` check — used for object-typed
125
- * *properties*, whose value may be absent or a non-object (the type check
126
- * reports that separately). The root object and inline sub-parsers pass
127
- * `guard = false` because the enclosing parser has already proven the value is
128
- * an object.
129
- */
130
- export declare const generateObjectKeywordChecks: (obj: string, schema: JSONSchema, label: string, guard: boolean, context?: StrictAssertionContext) => string[];
131
- /**
132
- * Generates strict-mode assertions for the body of an object parser.
133
- * Throws on:
134
- * - non-object input
135
- * - missing required property
136
- * - property of the wrong primitive type
137
- * - enum / pattern / length / min / max / multipleOf violations
138
- *
139
- * Properties with a `$ref` are validated by the nested parser's own strict
140
- * check when that parser is invoked downstream.
141
- */
142
- /**
143
- * The `allOf` members this assertion must enforce itself: plain object schemas
144
- * written inline. A `$ref` member is enforced by the parser generated for its
145
- * target, and a member that composes further (nested `allOf`/`oneOf`/`if`) is
146
- * left alone rather than half-checked.
147
- */
148
- export declare const inlineAllOfMembers: (schema: JSONSchema) => JSONSchema[];
149
- export declare const generateObjectStrictAssertion: (schema: JSONSchema, typeName: string, context?: StrictAssertionContext) => string[];
150
- /**
151
- * Generates the strict-mode assertion lines for a non-object (scalar, array,
152
- * type-less) parser. Returns null when the schema constrains nothing this
153
- * generator can assert on, which is the caller's cue to emit a bare cast.
154
- */
155
- export declare const generateScalarStrictAssertion: (schema: JSONSchema, typeName: string, context?: StrictAssertionContext) => string | null;