@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,148 +0,0 @@
1
- import type { JSONSchema } from 'json-schema-typed/draft-2020-12';
2
- /**
3
- * Boolean type-check expression builders shared by the shape validators, the
4
- * parsers' fast paths, and the strict-mode assertions. They live in their own
5
- * module (rather than generate-parser-function) so generate-strict-assertion
6
- * can use them without an import cycle.
7
- *
8
- * Every expression built here is *true-sound*: when it evaluates to true the
9
- * value definitely matches the schema fragment. That is all a fast path needs.
10
- * The reverse direction — false definitely means invalid — only holds when
11
- * {@link canEnforceUnion} approves the schema, because a check may call an
12
- * imported `validate{X}Shape` that was generated as a conservative `=> false`
13
- * stub. Strict-mode code that *throws* on a false check must gate on it.
14
- */
15
- /**
16
- * Returns the shape-predicate function name for a given type name.
17
- * The predicate is generated alongside each parser and tells callers
18
- * whether `input` is already in the shape produced by the parser's fast path.
19
- */
20
- export declare const shapeValidatorName: (typeName: string) => string;
21
- /**
22
- * Matches an inline nested object property — an object schema written directly
23
- * under `properties` with its own `properties`, rather than referenced via
24
- * `$ref`. These get a private sub-parser (and shape predicate) in the same
25
- * generated file so their fields are actually parsed; anything involving
26
- * composition, conditionals, enums, or record semantics keeps the existing
27
- * code paths.
28
- */
29
- export declare const isInlineObjectProperty: (propSchema: JSONSchema) => propSchema is JSONSchema.Object;
30
- /**
31
- * Matches an array property whose `items` is an inline object schema — the
32
- * array-items analogue of {@link isInlineObjectProperty}. These get a private
33
- * item sub-parser (and shape predicate) so element values — including nested
34
- * enums and `$ref`s — are actually validated instead of only passing an
35
- * `Array.isArray` check.
36
- */
37
- export declare const isInlineObjectArrayProperty: (propSchema: JSONSchema) => boolean;
38
- /**
39
- * Matches an array property whose `items` is a *union* of object branches — the
40
- * shape a coercing parser needs a private item sub-parser for, so an element can
41
- * be dispatched to (and repaired toward) a branch rather than passed through.
42
- * {@link isInlineObjectArrayProperty} deliberately does not cover this: a union
43
- * is not one inline object, and the trust walk reads that predicate to decide
44
- * what a shape validator proves.
45
- */
46
- export declare const isUnionArrayProperty: (propSchema: JSONSchema) => boolean;
47
- /**
48
- * Matches a union written directly as a property value — `logo: { anyOf: [...] }`
49
- * rather than a `$ref`, a definition, or an array's `items`. This is the one
50
- * union position the coercing dispatcher did not reach, so a value matching no
51
- * branch was passed through unrepaired.
52
- *
53
- * The union's *own* keywords are the reason for the exclusions: 2020-12 applies
54
- * them alongside the branches, and a dispatcher expresses none of them, so a
55
- * constrained union keeps the general path rather than getting a parser that
56
- * quietly drops the constraint.
57
- */
58
- export declare const isInlineUnionProperty: (propSchema: JSONSchema) => boolean;
59
- /**
60
- * Extracts the branch list of a `oneOf`/`anyOf` union, or `null` when the
61
- * schema is not a union (or mixes in other composition keywords we cannot
62
- * turn into a membership check).
63
- */
64
- export declare const getUnionBranches: (schema: JSONSchema) => readonly JSONSchema[] | null;
65
- /**
66
- * True when {@link getUnionBranches} came from a `oneOf` rather than an `anyOf` —
67
- * i.e. the value must match *exactly one* branch, not at least one.
68
- *
69
- * The distinction was dropped before: both spellings produced a plain
70
- * disjunction, so `{ oneOf: [{…a}, {…b}] }` accepted `{ a: 'x', b: 1 }`, which
71
- * matches both branches. Ajv, the runtime interpreter, and `generate-validators`
72
- * all reject it, so the two mjst engines disagreed on the same contract.
73
- */
74
- export declare const isExclusiveUnion: (schema: JSONSchema) => boolean;
75
- /**
76
- * True when a schema carries a keyword the strict slow path enforces but which
77
- * no flat type check can mirror: `contains` (array item counting),
78
- * `dependentRequired` / `dependentSchemas` (cross-property presence),
79
- * `propertyNames` (per-key constraints), the conditional and composition
80
- * keywords (`if`/`then`/`else`, `not`, a constraining `allOf`), or per-key value
81
- * schemas (`patternProperties`, a constraining `additionalProperties`). A fast
82
- * path or shape validator that accepted such a value on a bare type check would
83
- * skip the enforcement, so both must bail when this is true — the slow path then
84
- * runs the real checks.
85
- */
86
- export declare const hasFastPathBlockingKeyword: (schema: JSONSchema) => boolean;
87
- /**
88
- * The membership check for an array-form `type` (the multi-type / nullable
89
- * idiom, e.g. `["string","null"]`): the disjunction of the per-type checks.
90
- * Returns `undefined` when the schema has no array `type`, and `null` when it
91
- * has one that cannot be checked soundly — a type name we do not model, or a
92
- * constraint keyword a bare `typeof` would skip.
93
- *
94
- * Without this every nullable property fell through to `hasType`, which is false
95
- * for an array `type`, and produced no check at all: no fast path (so the shape
96
- * validator degraded to the `=> false` stub) and, in strict mode, nothing to
97
- * throw on.
98
- */
99
- export declare const multiTypeCheck: (varName: string, schema: JSONSchema, options?: {
100
- readonly ignoreConstraints?: boolean;
101
- }) => string | null | undefined;
102
- /**
103
- * Generates a fast-path type check expression for a property.
104
- * Returns null if the schema is too complex for a simple type check.
105
- *
106
- * When `useRefImports` is true, $ref properties (and arrays of $refs) are
107
- * checked by calling the imported shape predicate, allowing parent parsers
108
- * to fast-path through nested ref types without recursing into their parsers.
109
- */
110
- export declare const generatePropertyTypeCheck: (varName: string, schema: JSONSchema, useRefImports: boolean, suffix: string) => string | null;
111
- /**
112
- * Generates a boolean check that `varName` matches an inline object schema —
113
- * `isObject` plus a per-property check for every declared property (and a
114
- * presence check for `required` keys without a declared schema). Used for
115
- * union branches written inline, where no named shape validator exists to
116
- * call. Returns null when any property is too complex to check.
117
- */
118
- export declare const generateInlineObjectCheck: (varName: string, schema: JSONSchema, useRefImports: boolean, suffix: string) => string | null;
119
- /**
120
- * Generates a union membership check. Inline object branches get a per-property
121
- * check; everything else goes through {@link generatePropertyTypeCheck} ($refs
122
- * call the imported shape validator, consts/enums/scalars check directly).
123
- * Returns null when any branch cannot be checked.
124
- *
125
- * `exclusive` (a `oneOf` — see {@link isExclusiveUnion}) counts the matching
126
- * branches and demands exactly one, instead of the `anyOf` disjunction. The count
127
- * form is *tighter* than the disjunction it replaces — anything it accepts, the
128
- * disjunction accepted too — so a true-sound caller (a fast path, a shape
129
- * validator) can only get safer: a value it now declines merely takes the slow
130
- * path, which re-validates. A caller that *throws* on a false result needs each
131
- * branch check to be exact in both directions, which is precisely what
132
- * {@link canEnforceUnion} certifies.
133
- */
134
- export declare const generateUnionCheck: (varName: string, branches: readonly JSONSchema[], useRefImports: boolean, suffix: string, exclusive?: boolean) => string | null;
135
- /**
136
- * True when the union membership check emitted by {@link generateUnionCheck}
137
- * is also *false-sound* — a false result definitely means the value is invalid
138
- * — so strict mode may throw on it.
139
- *
140
- * The risk is `$ref`s: a branch (or a property inside one) whose check calls
141
- * an imported `validate{X}Shape` is only trustworthy when that validator was
142
- * generated as a real predicate. This walks every reachable ref (mirroring
143
- * generateShapeValidator's stub conditions) and rejects the union when any of
144
- * them would have produced the conservative `=> false` stub, or is itself
145
- * built on one. Cycles (e.g. a recursive `expr` union) are assumed sound —
146
- * the recursion bottoms out on the data, not the schema.
147
- */
148
- export declare const canEnforceUnion: (branches: readonly JSONSchema[], rootSchema: Record<string, unknown> | undefined) => boolean;
@@ -1,454 +0,0 @@
1
- import { regexLiteral } from "@amritk/helpers/escape-regex-pattern";
2
- import { multipleOfPassExpr } from "@amritk/helpers/multiple-of-check";
3
- import { refToName } from "@amritk/helpers/ref-to-name";
4
- import { resolveRef } from "@amritk/helpers/resolve-ref";
5
- import { hasOwnCheck, safeAccessor } from "@amritk/helpers/safe-accessor";
6
- import { hasAdditionalProperties, hasAllOf, hasAnyOf, hasConst, hasEnum, hasExclusiveMaximum, hasExclusiveMinimum, hasItems, hasMaxItems, hasMaximum, hasMaxLength, hasMaxProperties, hasMinItems, hasMinimum, hasMinLength, hasMinProperties, hasMultipleOf, hasOneOf, hasPattern, hasProperties, hasRef, hasRequired, hasType, hasUniqueItems, isObjectSchema, isSchemaObject } from "@amritk/helpers/schema-guards";
7
- import { maxLengthPassExpr, minLengthPassExpr } from "@amritk/helpers/string-length-check";
8
- import { generateEnumCheck } from "./generate-enum-check.js";
9
- import { generateUniqueItemsCheck } from "./generate-unique-items-check.js";
10
- import { everyTailItem, getPrefixItems, plainScalarItemCheck, prefixItemsCapsLength, singleTypeCheck } from "./generate-validation-expression.js";
11
- import { hasConstrainingRefSibling, subschemaMatchExpr } from "./subschema-match.js";
12
- const shapeValidatorName = (typeName) => `validate${typeName}Shape`;
13
- const isInlineObjectProperty = (propSchema) => {
14
- if (!isSchemaObject(propSchema))
15
- return false;
16
- if (hasRef(propSchema) || hasEnum(propSchema) || hasConst(propSchema))
17
- return false;
18
- if (Array.isArray(propSchema.type))
19
- return false;
20
- if (hasOneOf(propSchema) || hasAnyOf(propSchema) || hasAllOf(propSchema))
21
- return false;
22
- if ("patternProperties" in propSchema || "not" in propSchema)
23
- return false;
24
- if ("if" in propSchema || "then" in propSchema || "else" in propSchema)
25
- return false;
26
- if (hasAdditionalProperties(propSchema) && typeof propSchema.additionalProperties !== "boolean")
27
- return false;
28
- return isObjectSchema(propSchema) && hasProperties(propSchema);
29
- };
30
- const isInlineObjectArrayProperty = (propSchema) => {
31
- if (!isSchemaObject(propSchema))
32
- return false;
33
- if (!("type" in propSchema) || propSchema.type !== "array")
34
- return false;
35
- if (!hasItems(propSchema) || Array.isArray(propSchema.items))
36
- return false;
37
- return isInlineObjectProperty(propSchema.items);
38
- };
39
- const isUnionArrayProperty = (propSchema) => {
40
- if (!isSchemaObject(propSchema))
41
- return false;
42
- if (!("type" in propSchema) || propSchema.type !== "array")
43
- return false;
44
- if (!hasItems(propSchema) || Array.isArray(propSchema.items))
45
- return false;
46
- const items = propSchema.items;
47
- if (!isSchemaObject(items))
48
- return false;
49
- const branches = getUnionBranches(items);
50
- if (branches === null || branches.length < 2)
51
- return false;
52
- return branches.every((branch) => isSchemaObject(branch) && (hasRef(branch) || isInlineObjectProperty(branch)));
53
- };
54
- const isInlineUnionProperty = (propSchema) => {
55
- if (!isSchemaObject(propSchema))
56
- return false;
57
- if (hasRef(propSchema) || hasAllOf(propSchema) || "not" in propSchema)
58
- return false;
59
- if ("if" in propSchema || "then" in propSchema || "else" in propSchema)
60
- return false;
61
- if ("patternProperties" in propSchema)
62
- return false;
63
- const branches = getUnionBranches(propSchema);
64
- if (branches === null || branches.length < 2)
65
- return false;
66
- if (hasBranchSiblingConstraint(propSchema))
67
- return false;
68
- return branches.every((branch) => isSchemaObject(branch) && (hasRef(branch) || isInlineObjectProperty(branch)));
69
- };
70
- const getUnionBranches = (schema) => {
71
- if (!isSchemaObject(schema))
72
- return null;
73
- if (hasAllOf(schema) || "not" in schema)
74
- return null;
75
- if (hasOneOf(schema) && schema.oneOf.length > 0)
76
- return schema.oneOf;
77
- if (hasAnyOf(schema) && schema.anyOf.length > 0)
78
- return schema.anyOf;
79
- return null;
80
- };
81
- const isExclusiveUnion = (schema) => isSchemaObject(schema) && !hasAllOf(schema) && !("not" in schema) && hasOneOf(schema) && schema.oneOf.length > 0;
82
- const hasFastPathBlockingKeyword = (schema) => {
83
- if (!isSchemaObject(schema))
84
- return false;
85
- const record = schema;
86
- return "contains" in record || "dependentSchemas" in record || "propertyNames" in record || "dependentRequired" in record || // Composition and conditionals: the strict assertions now enforce these, but
87
- // a guard built from per-property type checks cannot mirror them — it said
88
- // "already in shape" for `{ kind: 'a' }` under `if: {kind: 'a'}, then:
89
- // {required: ['n']}` and returned before the conditional was ever tested.
90
- "if" in record || "then" in record || "else" in record || "not" in record || "patternProperties" in record || // `unevaluated*` is answered against the whole node's annotation coverage,
91
- // which a per-property guard has no way to reproduce.
92
- "unevaluatedProperties" in record && record["unevaluatedProperties"] !== true || "unevaluatedItems" in record && record["unevaluatedItems"] !== true || // A `$ref` guard calls the target's shape validator, which knows nothing
93
- // about the siblings 2020-12 still applies: `{ $ref: …, minProperties: 2 }`
94
- // was waved through on the target's shape alone.
95
- hasConstrainingRefSibling(schema) || hasConstrainingAllOf(schema) || hasConstrainingAdditionalProperties(schema);
96
- };
97
- const hasConstrainingAllOf = (schema) => hasAllOf(schema) && schema.allOf.some((member) => !(isSchemaObject(member) && hasRef(member)));
98
- const hasConstrainingAdditionalProperties = (schema) => {
99
- if (!hasAdditionalProperties(schema))
100
- return false;
101
- const additional = schema.additionalProperties;
102
- if (typeof additional === "boolean")
103
- return false;
104
- return subschemaMatchExpr("_x", additional) !== "true";
105
- };
106
- const CONSTRAINT_KEYWORDS = [
107
- "pattern",
108
- "minLength",
109
- "maxLength",
110
- "minimum",
111
- "maximum",
112
- "exclusiveMinimum",
113
- "exclusiveMaximum",
114
- "multipleOf",
115
- "minItems",
116
- "maxItems",
117
- "uniqueItems",
118
- "items",
119
- "prefixItems",
120
- "properties",
121
- "required",
122
- "additionalProperties",
123
- "patternProperties",
124
- "minProperties",
125
- "maxProperties"
126
- ];
127
- const hasBranchSiblingConstraint = (schema) => {
128
- const record = schema;
129
- return "type" in record || CONSTRAINT_KEYWORDS.some((keyword) => keyword in record);
130
- };
131
- const multiTypeCheck = (varName, schema, options = {}) => {
132
- if (!isSchemaObject(schema) || !Array.isArray(schema.type))
133
- return void 0;
134
- const record = schema;
135
- if (!options.ignoreConstraints && CONSTRAINT_KEYWORDS.some((keyword) => keyword in record))
136
- return null;
137
- const checks = [];
138
- for (const type of schema.type) {
139
- const check = singleTypeCheck(varName, type);
140
- if (check === null)
141
- return null;
142
- checks.push(`(${check})`);
143
- }
144
- if (checks.length === 0)
145
- return null;
146
- return checks.length === 1 ? checks[0] : `(${checks.join(" || ")})`;
147
- };
148
- const generatePropertyTypeCheck = (varName, schema, useRefImports, suffix) => {
149
- if (!isSchemaObject(schema))
150
- return null;
151
- if (hasFastPathBlockingKeyword(schema))
152
- return null;
153
- if (useRefImports && hasRef(schema)) {
154
- const refName = refToName(schema.$ref, suffix);
155
- return `${shapeValidatorName(refName)}(${varName})`;
156
- }
157
- if (hasRef(schema))
158
- return null;
159
- if (hasEnum(schema)) {
160
- return schema.enum.length > 0 ? generateEnumCheck(varName, schema.enum) : null;
161
- }
162
- const branches = getUnionBranches(schema);
163
- if (branches) {
164
- if (hasBranchSiblingConstraint(schema))
165
- return null;
166
- return generateUnionCheck(varName, branches, useRefImports, suffix, isExclusiveUnion(schema));
167
- }
168
- if (hasOneOf(schema) || hasAnyOf(schema) || hasAllOf(schema) || "not" in schema) {
169
- return null;
170
- }
171
- if (hasConst(schema)) {
172
- const c = schema.const;
173
- return c === null || typeof c !== "object" ? `${varName} === ${JSON.stringify(c)}` : null;
174
- }
175
- const multiType = multiTypeCheck(varName, schema);
176
- if (multiType !== void 0)
177
- return multiType;
178
- if (!hasType(schema))
179
- return null;
180
- if (useRefImports && schema.type === "array" && hasItems(schema) && isSchemaObject(schema.items) && hasRef(schema.items)) {
181
- const refName = refToName(schema.items.$ref, suffix);
182
- return `Array.isArray(${varName}) && ${varName}.every(${shapeValidatorName(refName)})`;
183
- }
184
- const checks = [];
185
- switch (schema.type) {
186
- case "string": {
187
- checks.push(`typeof ${varName} === "string"`);
188
- if (hasPattern(schema))
189
- checks.push(`${regexLiteral(schema.pattern)}.test(${varName})`);
190
- if (hasMinLength(schema))
191
- checks.push(minLengthPassExpr(varName, schema.minLength));
192
- if (hasMaxLength(schema))
193
- checks.push(maxLengthPassExpr(varName, schema.maxLength));
194
- break;
195
- }
196
- case "number":
197
- case "integer": {
198
- checks.push(`typeof ${varName} === "number"`);
199
- if (schema.type === "integer")
200
- checks.push(`Number.isInteger(${varName})`);
201
- if (hasMinimum(schema))
202
- checks.push(`${varName} >= ${schema.minimum}`);
203
- if (hasMaximum(schema))
204
- checks.push(`${varName} <= ${schema.maximum}`);
205
- if (hasExclusiveMinimum(schema))
206
- checks.push(`${varName} > ${schema.exclusiveMinimum}`);
207
- if (hasExclusiveMaximum(schema))
208
- checks.push(`${varName} < ${schema.exclusiveMaximum}`);
209
- if (hasMultipleOf(schema))
210
- checks.push(multipleOfPassExpr(varName, schema.multipleOf));
211
- break;
212
- }
213
- case "boolean":
214
- checks.push(`typeof ${varName} === "boolean"`);
215
- break;
216
- case "array": {
217
- checks.push(`Array.isArray(${varName})`);
218
- if (hasMinItems(schema))
219
- checks.push(`${varName}.length >= ${schema.minItems}`);
220
- if (hasMaxItems(schema))
221
- checks.push(`${varName}.length <= ${schema.maxItems}`);
222
- if (hasUniqueItems(schema) && schema.uniqueItems === true) {
223
- checks.push(generateUniqueItemsCheck(varName, schema));
224
- }
225
- if (hasItems(schema) && !Array.isArray(schema.items)) {
226
- const items = schema.items;
227
- const itemCheck = plainScalarItemCheck(items, "_it") ?? (isSchemaObject(items) && hasEnum(items) && items.enum.length > 0 ? generateEnumCheck("_it", items.enum) : null);
228
- if (itemCheck) {
229
- checks.push(everyTailItem(varName, itemCheck, schema));
230
- } else if (!(isSchemaObject(items) && (hasRef(items) || isInlineObjectProperty(items)))) {
231
- const match = subschemaMatchExpr("_it", items);
232
- if (match === null)
233
- return null;
234
- if (match !== "true")
235
- checks.push(everyTailItem(varName, match, schema));
236
- }
237
- }
238
- const prefix = getPrefixItems(schema);
239
- if (prefix) {
240
- for (let i = 0; i < prefix.length; i++) {
241
- const posCheck = generatePropertyTypeCheck(`${varName}[${i}]`, prefix[i], false, suffix);
242
- if (posCheck === null)
243
- return null;
244
- checks.push(`(${varName}.length <= ${i} || (${posCheck}))`);
245
- }
246
- if (prefixItemsCapsLength(schema))
247
- checks.push(`${varName}.length <= ${prefix.length}`);
248
- }
249
- break;
250
- }
251
- case "object":
252
- if (hasProperties(schema) || hasRequired(schema))
253
- return null;
254
- checks.push(`isObject(${varName})`);
255
- if (hasMinProperties(schema))
256
- checks.push(`Object.keys(${varName}).length >= ${schema.minProperties}`);
257
- if (hasMaxProperties(schema))
258
- checks.push(`Object.keys(${varName}).length <= ${schema.maxProperties}`);
259
- break;
260
- default:
261
- return null;
262
- }
263
- if (checks.length === 0)
264
- return null;
265
- let result = checks[0];
266
- for (let i = 1; i < checks.length; i++) {
267
- result += " && " + checks[i];
268
- }
269
- return result;
270
- };
271
- const generateInlineObjectCheck = (varName, schema, useRefImports, suffix) => {
272
- if (!isSchemaObject(schema) || !isObjectSchema(schema) || !hasProperties(schema))
273
- return null;
274
- if (hasAllOf(schema) || hasOneOf(schema) || hasAnyOf(schema) || "not" in schema)
275
- return null;
276
- if ("patternProperties" in schema || "if" in schema)
277
- return null;
278
- if (hasAdditionalProperties(schema) && typeof schema.additionalProperties !== "boolean")
279
- return null;
280
- const required = new Set(Array.isArray(schema.required) ? schema.required : []);
281
- const checks = [`isObject(${varName})`];
282
- for (const [key, propSchema] of Object.entries(schema.properties)) {
283
- const acc = safeAccessor(varName, key);
284
- const check = generatePropertyTypeCheck(acc, propSchema, useRefImports, suffix);
285
- if (check === null)
286
- return null;
287
- checks.push(required.has(key) ? check : `(${acc} === undefined || ${check})`);
288
- required.delete(key);
289
- }
290
- for (const key of required) {
291
- checks.push(hasOwnCheck(varName, key));
292
- }
293
- if (hasAdditionalProperties(schema) && schema.additionalProperties === false) {
294
- const allowedKeys = JSON.stringify(Object.keys(schema.properties));
295
- checks.push(`Object.keys(${varName}).every((_k) => ${allowedKeys}.includes(_k))`);
296
- }
297
- if (hasMinProperties(schema))
298
- checks.push(`Object.keys(${varName}).length >= ${schema.minProperties}`);
299
- if (hasMaxProperties(schema))
300
- checks.push(`Object.keys(${varName}).length <= ${schema.maxProperties}`);
301
- return checks.join(" && ");
302
- };
303
- const generateUnionCheck = (varName, branches, useRefImports, suffix, exclusive = false) => {
304
- if (branches.length === 0)
305
- return null;
306
- const parts = [];
307
- for (const branch of branches) {
308
- if (!isSchemaObject(branch))
309
- return null;
310
- const check = isObjectSchema(branch) && hasProperties(branch) && !hasRef(branch) ? generateInlineObjectCheck(varName, branch, useRefImports, suffix) : generatePropertyTypeCheck(varName, branch, useRefImports, suffix);
311
- if (check === null)
312
- return null;
313
- parts.push(`(${check})`);
314
- }
315
- if (parts.length === 1)
316
- return parts[0];
317
- if (exclusive)
318
- return `((${parts.map((part) => `(${part} ? 1 : 0)`).join(" + ")}) === 1)`;
319
- return `(${parts.join(" || ")})`;
320
- };
321
- const canEnforceUnion = (branches, rootSchema) => {
322
- const visiting = /* @__PURE__ */ new Set();
323
- return branches.length > 0 && branches.every((branch) => {
324
- if (!isSchemaObject(branch))
325
- return false;
326
- return isObjectSchema(branch) && hasProperties(branch) && !hasRef(branch) ? canTrustInlineObjectCheck(branch, rootSchema, visiting) : canTrustPropertyCheck(branch, rootSchema, visiting);
327
- });
328
- };
329
- const canTrustPropertyCheck = (schema, rootSchema, visiting) => {
330
- if (!isSchemaObject(schema))
331
- return false;
332
- if (hasRef(schema)) {
333
- return canTrustReferencedValidator(schema.$ref, rootSchema, visiting);
334
- }
335
- if (hasEnum(schema))
336
- return schema.enum.length > 0;
337
- const branches = getUnionBranches(schema);
338
- if (branches) {
339
- return branches.every((branch) => {
340
- if (!isSchemaObject(branch))
341
- return false;
342
- return isObjectSchema(branch) && hasProperties(branch) && !hasRef(branch) ? canTrustInlineObjectCheck(branch, rootSchema, visiting) : canTrustPropertyCheck(branch, rootSchema, visiting);
343
- });
344
- }
345
- if (hasOneOf(schema) || hasAnyOf(schema) || hasAllOf(schema) || "not" in schema)
346
- return false;
347
- if (hasConst(schema))
348
- return schema.const === null || typeof schema.const !== "object";
349
- const multiType = multiTypeCheck("_x", schema);
350
- if (multiType !== void 0)
351
- return multiType !== null;
352
- if (!hasType(schema))
353
- return false;
354
- if (schema.type === "array" && hasItems(schema) && isSchemaObject(schema.items) && hasRef(schema.items)) {
355
- return canTrustReferencedValidator(schema.items.$ref, rootSchema, visiting);
356
- }
357
- switch (schema.type) {
358
- case "string":
359
- case "number":
360
- case "integer":
361
- case "boolean":
362
- case "array":
363
- case "object":
364
- return true;
365
- default:
366
- return false;
367
- }
368
- };
369
- const canTrustShapeProperty = (propSchema, rootSchema, visiting) => {
370
- if (!isSchemaObject(propSchema))
371
- return false;
372
- if (hasAdditionalProperties(propSchema) && typeof propSchema.additionalProperties !== "boolean")
373
- return false;
374
- if (isInlineObjectProperty(propSchema)) {
375
- return Object.values(propSchema.properties ?? {}).every((sub) => canTrustShapeProperty(sub, rootSchema, visiting));
376
- }
377
- if (isInlineObjectArrayProperty(propSchema)) {
378
- const items = propSchema.items;
379
- return Object.values(items.properties ?? {}).every((sub) => canTrustShapeProperty(sub, rootSchema, visiting));
380
- }
381
- return canTrustPropertyCheck(propSchema, rootSchema, visiting);
382
- };
383
- const KEYWORDS_INLINE_CHECK_IGNORES = [
384
- "propertyNames",
385
- "dependentRequired",
386
- "dependentSchemas",
387
- "contains",
388
- "unevaluatedProperties"
389
- ];
390
- const canTrustInlineObjectCheck = (schema, rootSchema, visiting) => {
391
- if (!isSchemaObject(schema) || !isObjectSchema(schema) || !hasProperties(schema))
392
- return false;
393
- if (hasAllOf(schema) || hasOneOf(schema) || hasAnyOf(schema) || "not" in schema)
394
- return false;
395
- if ("patternProperties" in schema || "if" in schema)
396
- return false;
397
- if (hasAdditionalProperties(schema) && typeof schema.additionalProperties !== "boolean")
398
- return false;
399
- const record = schema;
400
- if (KEYWORDS_INLINE_CHECK_IGNORES.some((keyword) => keyword in record))
401
- return false;
402
- return Object.values(schema.properties).every((propSchema) => canTrustPropertyCheck(propSchema, rootSchema, visiting));
403
- };
404
- const canTrustReferencedValidator = (ref, rootSchema, visiting) => {
405
- if (visiting.has(ref))
406
- return true;
407
- if (!rootSchema)
408
- return false;
409
- const resolved = resolveRef(ref, rootSchema);
410
- if (!resolved || !isSchemaObject(resolved))
411
- return false;
412
- visiting.add(ref);
413
- try {
414
- if (hasRef(resolved) && !hasProperties(resolved)) {
415
- return canTrustReferencedValidator(resolved.$ref, rootSchema, visiting);
416
- }
417
- if (!hasProperties(resolved)) {
418
- const branches = getUnionBranches(resolved);
419
- if (!branches) {
420
- if ("patternProperties" in resolved || "if" in resolved)
421
- return false;
422
- return canTrustPropertyCheck(resolved, rootSchema, visiting);
423
- }
424
- if ("patternProperties" in resolved || "if" in resolved)
425
- return false;
426
- return branches.every((branch) => {
427
- if (!isSchemaObject(branch))
428
- return false;
429
- return isObjectSchema(branch) && hasProperties(branch) && !hasRef(branch) ? canTrustInlineObjectCheck(branch, rootSchema, visiting) : canTrustPropertyCheck(branch, rootSchema, visiting);
430
- });
431
- }
432
- if (hasOneOf(resolved) || hasAnyOf(resolved) || hasAllOf(resolved) || "not" in resolved || "patternProperties" in resolved || "if" in resolved || "then" in resolved || "else" in resolved) {
433
- return false;
434
- }
435
- return Object.values(resolved.properties).every((propSchema) => canTrustShapeProperty(propSchema, rootSchema, visiting));
436
- } finally {
437
- visiting.delete(ref);
438
- }
439
- };
440
- export {
441
- canEnforceUnion,
442
- generateInlineObjectCheck,
443
- generatePropertyTypeCheck,
444
- generateUnionCheck,
445
- getUnionBranches,
446
- hasFastPathBlockingKeyword,
447
- isExclusiveUnion,
448
- isInlineObjectArrayProperty,
449
- isInlineObjectProperty,
450
- isInlineUnionProperty,
451
- isUnionArrayProperty,
452
- multiTypeCheck,
453
- shapeValidatorName
454
- };
@@ -1,16 +0,0 @@
1
- import type { JSONSchema } from 'json-schema-typed/draft-2020-12';
2
- /**
3
- * Builds the boolean expression that is TRUE when every element of `accessor` is
4
- * distinct — the `uniqueItems: true` contract.
5
- *
6
- * Scalar-only arrays take the cheap native `Set` (exact for primitives, and
7
- * allocation is a single pass). Anything that might hold an object or an array
8
- * falls back to the canonical-JSON projection above, because JSON Schema compares
9
- * items by *structural* equality and neither a plain `Set` (reference equality)
10
- * nor a raw `JSON.stringify` key (order sensitive) answers that question.
11
- *
12
- * @param accessor - Expression yielding the array; the caller must already have
13
- * proven `Array.isArray`.
14
- * @param schema - The array schema, read for its item types.
15
- */
16
- export declare const generateUniqueItemsCheck: (accessor: string, schema: JSONSchema) => string;
@@ -1,32 +0,0 @@
1
- import { hasType, isSchemaObject } from "@amritk/helpers/schema-guards";
2
- const SCALAR_TYPES = /* @__PURE__ */ new Set(["string", "number", "integer", "boolean", "null"]);
3
- const schemaIsScalarOnly = (schema) => {
4
- if (schema === void 0 || !isSchemaObject(schema) || !hasType(schema))
5
- return false;
6
- const type = schema["type"];
7
- const types = Array.isArray(type) ? type : [type];
8
- return types.length > 0 && types.every((t) => typeof t === "string" && SCALAR_TYPES.has(t));
9
- };
10
- const arrayItemsAreScalarOnly = (schema) => {
11
- if (!isSchemaObject(schema))
12
- return false;
13
- const record = schema;
14
- const prefix = record["prefixItems"];
15
- if (Array.isArray(prefix)) {
16
- if (!prefix.every((position) => schemaIsScalarOnly(position)))
17
- return false;
18
- const tail = "items" in record ? record["items"] : record["additionalItems"];
19
- return tail === false ? true : schemaIsScalarOnly(tail);
20
- }
21
- if (Array.isArray(record["items"])) {
22
- if (!record["items"].every((position) => schemaIsScalarOnly(position)))
23
- return false;
24
- return record["additionalItems"] === false ? true : schemaIsScalarOnly(record["additionalItems"]);
25
- }
26
- return schemaIsScalarOnly(record["items"]);
27
- };
28
- const CANONICAL_JSON = 'JSON.stringify(_u, (_k, _v) => (_v !== null && typeof _v === "object" && !Array.isArray(_v)) ? Object.fromEntries(Object.keys(_v).sort().map((_sk) => [_sk, (_v as Record<string, unknown>)[_sk]])) : _v)';
29
- const generateUniqueItemsCheck = (accessor, schema) => arrayItemsAreScalarOnly(schema) ? `new Set(${accessor}).size === ${accessor}.length` : `new Set((${accessor} as unknown[]).map((_u) => ${CANONICAL_JSON})).size === ${accessor}.length`;
30
- export {
31
- generateUniqueItemsCheck
32
- };