@amritk/generate-parsers 0.17.2 → 0.19.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 (32) hide show
  1. package/AI.md +10 -0
  2. package/README.md +147 -19
  3. package/dist/generators/assert-supported-keywords.d.ts +13 -9
  4. package/dist/generators/assert-supported-keywords.js +58 -18
  5. package/dist/generators/build-schema.d.ts +9 -1
  6. package/dist/generators/build-schema.js +5 -2
  7. package/dist/generators/generate-deep-equal-check.d.ts +24 -0
  8. package/dist/generators/generate-deep-equal-check.js +35 -0
  9. package/dist/generators/generate-discriminated-union-validation.js +1 -2
  10. package/dist/generators/generate-enum-check.d.ts +8 -3
  11. package/dist/generators/generate-enum-check.js +8 -6
  12. package/dist/generators/generate-files.d.ts +7 -0
  13. package/dist/generators/generate-files.js +23 -4
  14. package/dist/generators/generate-parser-function.d.ts +1 -1
  15. package/dist/generators/generate-parser-function.js +212 -65
  16. package/dist/generators/generate-schema-checks.js +10 -8
  17. package/dist/generators/generate-strict-assertion.d.ts +89 -5
  18. package/dist/generators/generate-strict-assertion.js +429 -58
  19. package/dist/generators/generate-type-checks.d.ts +46 -10
  20. package/dist/generators/generate-type-checks.js +112 -16
  21. package/dist/generators/generate-unique-items-check.d.ts +16 -0
  22. package/dist/generators/generate-unique-items-check.js +32 -0
  23. package/dist/generators/generate-validation-expression.d.ts +29 -4
  24. package/dist/generators/generate-validation-expression.js +15 -6
  25. package/dist/generators/subschema-match.d.ts +23 -4
  26. package/dist/generators/subschema-match.js +312 -48
  27. package/dist/generators/unevaluated-match.d.ts +19 -0
  28. package/dist/generators/unevaluated-match.js +206 -0
  29. package/dist/helpers/collect-imports.d.ts +8 -0
  30. package/dist/helpers/collect-imports.js +1 -1
  31. package/dist/helpers/get-default-value.js +45 -2
  32. package/package.json +9 -3
@@ -1,17 +1,18 @@
1
- import { escapeRegexPattern } from "@amritk/helpers/escape-regex-pattern";
1
+ import { regexLiteral } from "@amritk/helpers/escape-regex-pattern";
2
2
  import { quoteJsString } from "@amritk/helpers/quote-js-string";
3
3
  import { refToName } from "@amritk/helpers/ref-to-name";
4
4
  import { resolveRef } from "@amritk/helpers/resolve-ref";
5
- import { safeAccessor, safeKey } from "@amritk/helpers/safe-accessor";
6
- import { hasAdditionalProperties, hasAllOf, hasAnyOf, hasConst, hasEnum, hasItems, hasOneOf, hasProperties, hasRef, hasRequired, hasType, isObjectSchema, isSchemaObject } from "@amritk/helpers/schema-guards";
5
+ import { missingCheck, safeAccessor, safeKey } from "@amritk/helpers/safe-accessor";
6
+ import { hasAdditionalProperties, hasAllOf, hasAnyOf, hasConst, hasEnum, hasItems, hasMaxItems, hasMaxProperties, hasMinItems, hasMinProperties, hasOneOf, hasProperties, hasRef, hasRequired, hasType, hasUniqueItems, isObjectSchema, isSchemaObject } from "@amritk/helpers/schema-guards";
7
7
  import { unknownKeyCheck } from "@amritk/helpers/unknown-key-check";
8
8
  import { findDiscriminator } from "../helpers/find-discriminator.js";
9
9
  import { getDefaultValue } from "../helpers/get-default-value.js";
10
10
  import { getDiscriminatorValue } from "../helpers/get-discriminator-value.js";
11
11
  import { assertNoUnsupportedKeywords } from "./assert-supported-keywords.js";
12
12
  import { generateEnumCaseInsensitiveCoercion } from "./generate-enum-check.js";
13
- import { generateContainsCheck, generateObjectKeywordChecks, generateObjectStrictAssertion, generateScalarStrictAssertion } from "./generate-strict-assertion.js";
14
- import { canEnforceUnion, generatePropertyTypeCheck, generateUnionCheck, getUnionBranches, hasFastPathBlockingKeyword, isInlineObjectArrayProperty, isInlineObjectProperty, shapeValidatorName } from "./generate-type-checks.js";
13
+ import { generateBackstopAssertion, generateCompositionChecks, generateContainsCheck, generateKeyedValueChecks, generateObjectKeywordChecks, generateObjectSizeChecks, generateObjectStrictAssertion, generateScalarStrictAssertion, inlineAllOfMembers } from "./generate-strict-assertion.js";
14
+ import { canEnforceUnion, generatePropertyTypeCheck, generateUnionCheck, getUnionBranches, hasFastPathBlockingKeyword, isExclusiveUnion, isInlineObjectArrayProperty, isInlineObjectProperty, shapeValidatorName } from "./generate-type-checks.js";
15
+ import { generateUniqueItemsCheck } from "./generate-unique-items-check.js";
15
16
  import { generatePrefixItemsMap, generateValidationExpression, getPrefixItems, isCoercibleItemSchema, prefixItemsCapsLength } from "./generate-validation-expression.js";
16
17
  const generateParserName = (typeName) => {
17
18
  return `parse${typeName}`;
@@ -149,6 +150,8 @@ const generateFallbackValue = (_, propSchema, useRefImports, suffix) => {
149
150
  }
150
151
  return getDefaultValue(propSchema);
151
152
  };
153
+ const PROTOTYPE_MEMBER_NAMES = new Set(Object.getOwnPropertyNames(Object.prototype));
154
+ const declaresPrototypeMemberProperty = (schema) => hasProperties(schema) && Object.keys(schema.properties).some((key) => PROTOTYPE_MEMBER_NAMES.has(key));
152
155
  const generateFallbackObject = (schema, useRefImports, typeName, suffix, subTypeNames) => {
153
156
  if (!hasProperties(schema)) {
154
157
  return `{} as ${typeName}`;
@@ -165,7 +168,8 @@ const generateFallbackObject = (schema, useRefImports, typeName, suffix, subType
165
168
  if (fallbackValue === "undefined") {
166
169
  return `{} as ${typeName}`;
167
170
  }
168
- requiredProps.push(` ${safeLiteralKey(key)}: ${fallbackValue},`);
171
+ const partialFallback = fallbackValue === "{}" || isSchemaObject(propSchema) && (hasAllOf(propSchema) || hasOneOf(propSchema) || hasAnyOf(propSchema));
172
+ requiredProps.push(` ${safeLiteralKey(key)}: ${partialFallback ? `${fallbackValue} as NonNullable<${typeName}>[${JSON.stringify(key)}]` : fallbackValue},`);
169
173
  }
170
174
  }
171
175
  if (requiredProps.length === 0) {
@@ -179,7 +183,7 @@ const generateFallbackObject = (schema, useRefImports, typeName, suffix, subType
179
183
  }
180
184
  }
181
185
  result += "\n }";
182
- return result;
186
+ return declaresPrototypeMemberProperty(schema) ? `${result} as ${typeName}` : result;
183
187
  };
184
188
  const scalarDefaultLiteral = (schema) => {
185
189
  if (!isSchemaObject(schema))
@@ -207,7 +211,7 @@ const scalarDefaultLiteral = (schema) => {
207
211
  }
208
212
  return "{}";
209
213
  };
210
- const generateRefUnionDispatch = (functionName, typeName, branches, strict, ctx) => {
214
+ const generateRefUnionDispatch = (functionName, typeName, schema, branches, strict, ctx) => {
211
215
  const rootSchema = ctx.rootSchema;
212
216
  if (!ctx.useRefImports || rootSchema === void 0)
213
217
  return null;
@@ -238,17 +242,26 @@ const generateRefUnionDispatch = (functionName, typeName, branches, strict, ctx)
238
242
  if (c)
239
243
  expr = `_disc === ${JSON.stringify(c.value)} ? ${c.parser}(input) : ${expr}`;
240
244
  }
245
+ const backstop = strict ? generateBackstopAssertion("input", schema, `[${typeName}]`, strictContext(ctx)) : [];
246
+ const prelude = backstop.length > 0 ? `${backstop.join("\n")}
247
+ ` : "";
241
248
  return `export const ${functionName} = (input: unknown): ${typeName} => {
242
- const _disc = ${discExpr};
249
+ ${prelude} const _disc = ${discExpr};
243
250
  return ${expr};
244
251
  };`;
245
252
  };
253
+ const strictContext = (ctx) => ({
254
+ useRefImports: ctx?.useRefImports ?? false,
255
+ suffix: ctx?.suffix ?? "",
256
+ ...ctx?.rootSchema !== void 0 ? { rootSchema: ctx.rootSchema } : {},
257
+ ...ctx?.stripUnknown !== void 0 ? { stripUnknown: ctx.stripUnknown } : {}
258
+ });
246
259
  const generateNonObjectParser = (typeName, schema, strict, unionCtx) => {
247
260
  const functionName = generateParserName(typeName);
248
261
  if (unionCtx && isSchemaObject(schema) && (hasOneOf(schema) || hasAnyOf(schema))) {
249
262
  const branches = hasOneOf(schema) ? schema.oneOf : hasAnyOf(schema) ? schema.anyOf : [];
250
263
  if (branches.length > 0 && branches.every((b) => isSchemaObject(b) && hasRef(b))) {
251
- const dispatch = generateRefUnionDispatch(functionName, typeName, branches, strict, unionCtx);
264
+ const dispatch = generateRefUnionDispatch(functionName, typeName, schema, branches, strict, unionCtx);
252
265
  if (dispatch !== null)
253
266
  return dispatch;
254
267
  }
@@ -256,16 +269,28 @@ const generateNonObjectParser = (typeName, schema, strict, unionCtx) => {
256
269
  if (unionCtx?.useRefImports && isSchemaObject(schema) && hasRef(schema) && !hasProperties(schema)) {
257
270
  const refName = refToName(schema.$ref, unionCtx.suffix);
258
271
  if (refName !== typeName) {
259
- return `export const ${functionName} = (input: unknown): ${typeName} => ${generateParserName(refName)}(input) as ${typeName};`;
272
+ const assertion = strict ? generateBackstopAssertion("input", schema, `[${typeName}]`, strictContext(unionCtx)) : [];
273
+ if (assertion.length === 0) {
274
+ return `export const ${functionName} = (input: unknown): ${typeName} => ${generateParserName(refName)}(input) as ${typeName};`;
275
+ }
276
+ return `export const ${functionName} = (input: unknown): ${typeName} => {
277
+ ${assertion.join("\n")}
278
+ return ${generateParserName(refName)}(input) as ${typeName};
279
+ };`;
260
280
  }
261
281
  }
262
282
  if (strict && unionCtx && isSchemaObject(schema)) {
263
283
  const branches = getUnionBranches(schema);
264
284
  if (branches && !unionCtx.stripUnknown && canEnforceUnion(branches, unionCtx.rootSchema)) {
265
- const check = generateUnionCheck("input", branches, unionCtx.useRefImports, unionCtx.suffix);
285
+ const check = generateUnionCheck("input", branches, unionCtx.useRefImports, unionCtx.suffix, isExclusiveUnion(schema));
266
286
  if (check !== null) {
287
+ const backstop = generateBackstopAssertion("input", schema, `[${typeName}]`, strictContext(unionCtx));
288
+ const assertions = [
289
+ ` if (!(${check})) throw new Error(${quoteJsString(`[${typeName}] value does not match any union branch`)});`,
290
+ ...backstop
291
+ ].join("\n");
267
292
  return `export const ${functionName} = (input: unknown): ${typeName} => {
268
- if (!(${check})) throw new Error(${quoteJsString(`[${typeName}] value does not match any union branch`)});
293
+ ${assertions}
269
294
  return input as ${typeName};
270
295
  };`;
271
296
  }
@@ -295,8 +320,8 @@ const generateNonObjectParser = (typeName, schema, strict, unionCtx) => {
295
320
  return `export const ${functionName} = (input: unknown): ${typeName} => input as ${typeName};`;
296
321
  }
297
322
  }
298
- if (strict && isSchemaObject(schema) && !hasType(schema) && (hasConst(schema) || hasEnum(schema))) {
299
- const assertion = generateScalarStrictAssertion(schema, typeName, unionCtx?.rootSchema);
323
+ if (strict && isSchemaObject(schema) && !hasType(schema) && (hasConst(schema) || hasEnum(schema) || hasAllOf(schema) || hasOneOf(schema) || hasAnyOf(schema) || "not" in schema || "if" in schema)) {
324
+ const assertion = generateScalarStrictAssertion(schema, typeName, strictContext(unionCtx));
300
325
  if (assertion !== null) {
301
326
  return `export const ${functionName} = (input: unknown): ${typeName} => {
302
327
  ${assertion}
@@ -307,7 +332,26 @@ ${assertion}
307
332
  if (isSchemaObject(schema) && hasType(schema) && schema.type === "array" && hasItems(schema) && !Array.isArray(schema.items)) {
308
333
  const items = schema.items;
309
334
  const notArrayThrow = `if (!Array.isArray(input)) throw new Error(\`[${typeName}] expected array, got \${input === null ? "null" : typeof input}\`);`;
310
- const strictArrayChecks = strict ? generateContainsCheck("input", schema, `[${typeName}]`).join("\n") : "";
335
+ const strictArrayChecks = strict ? [
336
+ // `minItems`/`maxItems` were dropped on this path entirely: a root
337
+ // array of objects or `$ref`s bypasses generateScalarStrictAssertion,
338
+ // so `{ type: 'array', minItems: 2, items: { type: 'object' } }`
339
+ // accepted a one-element array.
340
+ ...hasMinItems(schema) ? [
341
+ ` if (input.length < ${schema.minItems}) throw new Error(${quoteJsString(`[${typeName}] must have at least ${schema.minItems} items`)});`
342
+ ] : [],
343
+ ...hasMaxItems(schema) ? [
344
+ ` if (input.length > ${schema.maxItems}) throw new Error(${quoteJsString(`[${typeName}] must have at most ${schema.maxItems} items`)});`
345
+ ] : [],
346
+ ...generateContainsCheck("input", schema, `[${typeName}]`),
347
+ ...hasUniqueItems(schema) && schema.uniqueItems === true ? [
348
+ ` if (!(${generateUniqueItemsCheck("input", schema)})) throw new Error(${quoteJsString(`[${typeName}] must NOT have duplicate items`)});`
349
+ ] : [],
350
+ // This path delegates elements to another parser and so never reaches
351
+ // generateScalarStrictAssertion — `unevaluatedItems` and friends would
352
+ // be lost with it.
353
+ ...generateBackstopAssertion("input", schema, `[${typeName}]`, strictContext(unionCtx))
354
+ ].join("\n") : "";
311
355
  const delegatedBody = (itemParserName) => ` const _parsed = validateArray(input, ${itemParserName});
312
356
  return (_parsed === input ? [..._parsed] : _parsed) as ${typeName};`;
313
357
  const delegated = (itemParserName) => strict ? `export const ${functionName} = (input: unknown): ${typeName} => {
@@ -330,7 +374,7 @@ ${delegatedBody(itemParserName)}
330
374
  const stripUnknown = unionCtx?.stripUnknown ?? false;
331
375
  const itemShapeValidator = generateShapeValidator(items, itemName, useRefImports, suffix, false, stripUnknown, reserved);
332
376
  const preamble = [
333
- `type ${itemName} = ${typeName}[number];`,
377
+ `type ${itemName} = NonNullable<${typeName}>[number];`,
334
378
  itemShapeValidator,
335
379
  generateObjectParser(items, itemName, useRefImports, suffix, unionCtx?.logWarnings ?? false, strict, false, stripUnknown, unionCtx?.rootSchema, reserved, unionCtx?.caseInsensitive ?? false, itemShapeValidator)
336
380
  ].join("\n\n");
@@ -339,18 +383,29 @@ ${delegatedBody(itemParserName)}
339
383
  ${delegated(generateParserName(itemName))}`;
340
384
  }
341
385
  }
342
- if (!isSchemaObject(schema) || !hasType(schema)) {
386
+ const isMultiType = isSchemaObject(schema) && Array.isArray(schema.type);
387
+ if (!isSchemaObject(schema) || !hasType(schema) && !isMultiType) {
388
+ if (strict) {
389
+ const assertion = generateScalarStrictAssertion(schema, typeName, strictContext(unionCtx));
390
+ if (assertion !== null) {
391
+ return `export const ${functionName} = (input: unknown): ${typeName} => {
392
+ ${assertion}
393
+ return input as ${typeName};
394
+ };`;
395
+ }
396
+ }
343
397
  return `export const ${functionName} = (input: unknown): ${typeName} => input as ${typeName};`;
344
398
  }
345
399
  if (!strict && schema.type === "null") {
346
400
  return `export const ${functionName} = (input: unknown): ${typeName} => null as ${typeName};`;
347
401
  }
348
402
  if (strict) {
349
- const assertion = generateScalarStrictAssertion(schema, typeName, unionCtx?.rootSchema);
403
+ const assertion = generateScalarStrictAssertion(schema, typeName, strictContext(unionCtx));
350
404
  if (assertion === null) {
351
405
  return `export const ${functionName} = (input: unknown): ${typeName} => input as ${typeName};`;
352
406
  }
353
- const returnExpr = schema.type === "array" ? `[...(input as readonly unknown[])] as ${typeName}` : `input as ${typeName}`;
407
+ const arrayCast = getPrefixItems(schema) ? `[...(input as readonly unknown[])] as unknown as ${typeName}` : `[...(input as readonly unknown[])] as ${typeName}`;
408
+ const returnExpr = !isMultiType && schema.type === "array" ? arrayCast : `input as ${typeName}`;
354
409
  return `export const ${functionName} = (input: unknown): ${typeName} => {
355
410
  ${assertion}
356
411
  return ${returnExpr};
@@ -369,7 +424,7 @@ ${assertion}
369
424
  const prefixItems = getPrefixItems(schema);
370
425
  if (prefixItems) {
371
426
  const mapped = generatePrefixItemsMap("input", prefixItems, prefixItemsCapsLength(schema), unionCtx?.rootSchema, void 0, unionCtx?.caseInsensitive);
372
- return `export const ${functionName} = (input: unknown): ${typeName} => Array.isArray(input) ? ${mapped} as ${typeName} : [] as ${typeName};`;
427
+ return `export const ${functionName} = (input: unknown): ${typeName} => Array.isArray(input) ? ${mapped} as unknown as ${typeName} : [] as unknown as ${typeName};`;
373
428
  }
374
429
  if (hasItems(schema) && !Array.isArray(schema.items) && isCoercibleItemSchema(schema.items)) {
375
430
  const item = schema.items;
@@ -382,17 +437,50 @@ ${assertion}
382
437
  return `export const ${functionName} = (input: unknown): ${typeName} => input as ${typeName};`;
383
438
  }
384
439
  };
385
- const strictObjectKeywordPrelude = (schema, typeName) => {
440
+ const strictObjectKeywordPrelude = (schema, typeName, context = {}) => {
386
441
  if (!isSchemaObject(schema))
387
442
  return "";
388
- const lines = generateObjectKeywordChecks("input", schema, `[${typeName}]`, false);
443
+ const lines = [
444
+ ...generateObjectKeywordChecks("input", schema, `[${typeName}]`, false, context),
445
+ // `required` and the size bounds are the object-level constraints these
446
+ // specialized parsers never had a place for: `{ type: 'object', required:
447
+ // ['a'] }` and `{ type: 'object', minProperties: 2 }` carry no `properties`,
448
+ // so they land on the empty-object / record paths, which asserted only
449
+ // `isObject`. Keys that *are* declared are skipped — the per-property
450
+ // assertions of the combined parser already demand them.
451
+ ...requiredWithoutPropertyLines(schema, typeName),
452
+ ...generateObjectSizeChecks("input", schema, `[${typeName}]`, false),
453
+ // Values behind `patternProperties` / a schema-valued `additionalProperties`:
454
+ // these parsers build their result by copying or coercing, so nothing
455
+ // rejected a wrongly-typed pattern-matched value before this.
456
+ ...generateKeyedValueChecks("input", schema, `[${typeName}]`, context),
457
+ // Composition on a property-less object: these parsers delegate nothing, so
458
+ // `{ type: 'object', allOf: [{ $ref: … }] }` had no check at all.
459
+ ...generateCompositionChecks("input", schema, `[${typeName}]`, context),
460
+ // Whatever no specialized check above reaches (`unevaluated*`, a `$ref` with
461
+ // constraining siblings): these parsers never run generateObjectStrictAssertion,
462
+ // so the backstop has to be spliced in here too.
463
+ ...generateBackstopAssertion("input", schema, `[${typeName}]`, context)
464
+ ];
389
465
  return lines.length > 0 ? `
390
466
  ${lines.join("\n")}` : "";
391
467
  };
392
- const generateEmptyObjectParser = (typeName, strict, schema) => {
468
+ const requiredWithoutPropertyLines = (schema, typeName) => {
469
+ if (!isSchemaObject(schema) || !hasRequired(schema))
470
+ return [];
471
+ const declared = hasProperties(schema) ? schema.properties : {};
472
+ const lines = [];
473
+ for (const key of new Set(schema.required)) {
474
+ if (key in declared)
475
+ continue;
476
+ lines.push(` if (${missingCheck("input", key)}) throw new Error(${quoteJsString(`[${typeName}] missing required property '${key}'`)});`);
477
+ }
478
+ return lines;
479
+ };
480
+ const generateEmptyObjectParser = (typeName, strict, schema, context = {}) => {
393
481
  const functionName = generateParserName(typeName);
394
482
  if (strict) {
395
- const prelude = schema !== void 0 ? strictObjectKeywordPrelude(schema, typeName) : "";
483
+ const prelude = schema !== void 0 ? strictObjectKeywordPrelude(schema, typeName, context) : "";
396
484
  return `export const ${functionName} = (input: unknown): ${typeName} => {
397
485
  if (!isObject(input)) throw new Error(\`[${typeName}] expected object, got \${input === null ? "null" : typeof input}\`);${prelude}
398
486
  return { ...input } as ${typeName};
@@ -473,7 +561,7 @@ const generateObjectParser = (schema, typeName, useRefImports, suffix, logWarnin
473
561
  if (!hasProperties(schema)) {
474
562
  if (strict) {
475
563
  return `${exportPrefix}const ${functionName} = (input: unknown): ${typeName} => {
476
- if (!isObject(input)) throw new Error(\`[${typeName}] expected object, got \${input === null ? "null" : typeof input}\`);${strictObjectKeywordPrelude(schema, typeName)}
564
+ if (!isObject(input)) throw new Error(\`[${typeName}] expected object, got \${input === null ? "null" : typeof input}\`);${strictObjectKeywordPrelude(schema, typeName, { useRefImports, suffix, stripUnknown, ...rootSchema !== void 0 ? { rootSchema } : {} })}
477
565
  return input as ${typeName};
478
566
  };`;
479
567
  }
@@ -485,7 +573,7 @@ const generateObjectParser = (schema, typeName, useRefImports, suffix, logWarnin
485
573
  const preamble = [];
486
574
  for (const [key, subName] of subTypeNames) {
487
575
  const propSchema = schemaProps[key];
488
- preamble.push(`type ${subName} = ${typeName}[${JSON.stringify(key)}];`);
576
+ preamble.push(`type ${subName} = NonNullable<${typeName}>[${JSON.stringify(key)}];`);
489
577
  const subShapeValidator = generateShapeValidator(propSchema, subName, useRefImports, suffix, false, stripUnknown, reservedNames);
490
578
  preamble.push(subShapeValidator);
491
579
  preamble.push(generateObjectParser(propSchema, subName, useRefImports, suffix, logWarnings, strict, false, stripUnknown, rootSchema, reservedNames, caseInsensitive, subShapeValidator));
@@ -493,7 +581,7 @@ const generateObjectParser = (schema, typeName, useRefImports, suffix, logWarnin
493
581
  for (const [key, subName] of subItemNames) {
494
582
  const propSchema = schemaProps[key];
495
583
  const itemSchema = propSchema.items;
496
- preamble.push(`type ${subName} = NonNullable<${typeName}[${JSON.stringify(key)}]>[number];`);
584
+ preamble.push(`type ${subName} = NonNullable<NonNullable<${typeName}>[${JSON.stringify(key)}]>[number];`);
497
585
  const itemShapeValidator = generateShapeValidator(itemSchema, subName, useRefImports, suffix, false, stripUnknown, reservedNames);
498
586
  preamble.push(itemShapeValidator);
499
587
  preamble.push(`const _every${subName} = (arr: readonly unknown[]): boolean => {
@@ -522,7 +610,7 @@ const generateObjectParser = (schema, typeName, useRefImports, suffix, logWarnin
522
610
  const fallbackObject = generateFallbackObject(schema, useRefImports, typeName, suffix, subTypeNames);
523
611
  const propInfo = [];
524
612
  const hasAllOfRefParsers = useRefImports && isSchemaObject(schema) && hasAllOf(schema) && schema.allOf.some((entry) => isSchemaObject(entry) && hasRef(entry));
525
- let canFastPath = !hasAllOfRefParsers && !hasFastPathBlockingKeyword(schema);
613
+ let canFastPath = !hasAllOfRefParsers && !hasFastPathBlockingKeyword(schema) && !(strict && inlineAllOfMembers(schema).length > 0);
526
614
  const fastPathChecks = [];
527
615
  const fastPathAccessorChecks = [];
528
616
  const usedVarNames = /* @__PURE__ */ new Set();
@@ -563,6 +651,16 @@ const generateObjectParser = (schema, typeName, useRefImports, suffix, logWarnin
563
651
  fastPathAccessorChecks.push(`(${accessor} === undefined || ${accessorCheck})`);
564
652
  }
565
653
  }
654
+ if (canFastPath) {
655
+ if (hasMinProperties(schema)) {
656
+ fastPathChecks.push(`Object.keys(input).length >= ${schema.minProperties}`);
657
+ fastPathAccessorChecks.push(`Object.keys(input).length >= ${schema.minProperties}`);
658
+ }
659
+ if (hasMaxProperties(schema)) {
660
+ fastPathChecks.push(`Object.keys(input).length <= ${schema.maxProperties}`);
661
+ fastPathAccessorChecks.push(`Object.keys(input).length <= ${schema.maxProperties}`);
662
+ }
663
+ }
566
664
  if (stripKeys) {
567
665
  fastPathChecks.push(exactKeyCount !== null ? `Object.getPrototypeOf(input) === Object.prototype && Object.keys(input).length === ${exactKeyCount}` : `_hasOnlyKnownKeys${typeName}(input)`);
568
666
  }
@@ -697,7 +795,13 @@ const generateObjectParser = (schema, typeName, useRefImports, suffix, logWarnin
697
795
  continue;
698
796
  }
699
797
  if (!isSchemaObject(propSchema)) {
700
- if (isRequired) {
798
+ if (propSchema === true) {
799
+ if (isRequired) {
800
+ objectLines.push(` ${safeLiteralKey(key)}: ${accessor},`);
801
+ } else if (stripKeys) {
802
+ objectLines.push(` ...(${accessor} !== undefined && { ${safeLiteralKey(key)}: ${accessor} }),`);
803
+ }
804
+ } else if (isRequired) {
701
805
  objectLines.push(` ${safeLiteralKey(key)}: undefined,`);
702
806
  }
703
807
  continue;
@@ -736,8 +840,12 @@ const generateObjectParser = (schema, typeName, useRefImports, suffix, logWarnin
736
840
  const fields = [];
737
841
  for (const { key, varName, isRequired, propSchema } of propInfo) {
738
842
  if (!isSchemaObject(propSchema)) {
739
- if (isRequired)
843
+ if (propSchema === true) {
844
+ const acc = safeAccessor("input", key);
845
+ fields.push(isRequired ? ` ${safeLiteralKey(key)}: ${acc},` : ` ...(${acc} !== undefined && { ${safeLiteralKey(key)}: ${acc} }),`);
846
+ } else if (isRequired) {
740
847
  fields.push(` ${safeLiteralKey(key)}: undefined,`);
848
+ }
741
849
  continue;
742
850
  }
743
851
  const accessor = shouldCacheVariable(propSchema, canFastPath, useRefImports) ? varName : safeAccessor("input", key);
@@ -823,19 +931,19 @@ const generateObjectParser = (schema, typeName, useRefImports, suffix, logWarnin
823
931
  ${fn}`;
824
932
  };
825
933
  const safeResultAssign = (keyVar, valueExpr) => `if (${keyVar} === "__proto__") { Object.defineProperty(result, ${keyVar}, { value: ${valueExpr}, writable: true, enumerable: true, configurable: true }); } else { (result as Record<string, unknown>)[${keyVar}] = ${valueExpr}; }`;
826
- const generateStrictCombinedParser = (schema, typeName, functionName, propertyLines, patterns, useRefImports, suffix, strict) => {
934
+ const generateStrictCombinedParser = (schema, typeName, functionName, propertyLines, patterns, useRefImports, suffix, strict, context = {}) => {
827
935
  const declaredKeys = hasProperties(schema) ? Object.keys(schema.properties) : [];
828
936
  const knownKeyCheck = unknownKeyCheck(declaredKeys, "_knownKeys");
829
937
  const refPattern = useRefImports ? patterns.find(([, ps]) => isSchemaObject(ps) && hasRef(ps)) : void 0;
830
938
  const loopLines = [` if (${knownKeyCheck.isKnown("key")}) continue;`];
831
939
  if (refPattern) {
832
940
  const parserName = generateParserName(refToName(refPattern[1].$ref, suffix));
833
- loopLines.push(` if (/${escapeRegexPattern(refPattern[0])}/.test(key)) {`);
941
+ loopLines.push(` if (${regexLiteral(refPattern[0])}.test(key)) {`);
834
942
  loopLines.push(` ${safeResultAssign("key", `${parserName}(input[key])`)}`);
835
943
  loopLines.push(` continue;`);
836
944
  loopLines.push(` }`);
837
945
  }
838
- const keepConditions = patterns.filter(([p]) => !(refPattern && p === refPattern[0])).map(([p]) => `/${escapeRegexPattern(p)}/.test(key)`);
946
+ const keepConditions = patterns.filter(([p]) => !(refPattern && p === refPattern[0])).map(([p]) => `${regexLiteral(p)}.test(key)`);
839
947
  if (keepConditions.length > 0) {
840
948
  loopLines.push(` if (${keepConditions.join(" || ")}) {`);
841
949
  loopLines.push(` ${safeResultAssign("key", "input[key]")}`);
@@ -850,7 +958,7 @@ ${propertyLines.join("\n")}
850
958
  }` : "{}";
851
959
  const knownKeysDeclaration = knownKeyCheck.declarations.map((decl) => ` ${decl};
852
960
  `).join("");
853
- const assertionPrelude = strict ? generateObjectStrictAssertion(schema, typeName, { useRefImports, suffix }).slice(1) : [];
961
+ const assertionPrelude = strict ? generateObjectStrictAssertion(schema, typeName, context).slice(1) : [];
854
962
  const keywordPrelude = assertionPrelude.length > 0 ? `
855
963
  ${assertionPrelude.join("\n")}` : "";
856
964
  return `export const ${functionName} = (input: unknown): ${typeName} => {
@@ -864,7 +972,7 @@ ${loopLines.join("\n")}
864
972
  return result;
865
973
  };`;
866
974
  };
867
- const generateCombinedObjectParser = (schema, typeName, useRefImports, suffix, logWarnings, strict, stripUnknown = false, caseInsensitive = false) => {
975
+ const generateCombinedObjectParser = (schema, typeName, useRefImports, suffix, logWarnings, strict, stripUnknown = false, caseInsensitive = false, context = {}) => {
868
976
  const functionName = generateParserName(typeName);
869
977
  const entries = generatePropertyEntries(schema, useRefImports, suffix, caseInsensitive);
870
978
  const propertyLines = entries.map((entry) => {
@@ -874,26 +982,26 @@ const generateCombinedObjectParser = (schema, typeName, useRefImports, suffix, l
874
982
  return ` ${safeLiteralKey(entry.key)}: ${entry.value},`;
875
983
  });
876
984
  if (!isSchemaObject(schema) || !("patternProperties" in schema)) {
877
- return generateObjectParser(schema, typeName, useRefImports, suffix, logWarnings, strict, true, stripUnknown, void 0, NO_RESERVED_NAMES, caseInsensitive);
985
+ return generateObjectParser(schema, typeName, useRefImports, suffix, logWarnings, strict, true, stripUnknown, context.rootSchema, NO_RESERVED_NAMES, caseInsensitive);
878
986
  }
879
987
  const patternProps = schema.patternProperties;
880
988
  const patterns = Object.entries(patternProps);
881
989
  const refPattern = patterns.find(([, ps]) => isSchemaObject(ps) && hasRef(ps));
882
990
  const strictKeys = hasAdditionalProperties(schema) && schema.additionalProperties === false;
883
991
  if (strictKeys) {
884
- return generateStrictCombinedParser(schema, typeName, functionName, propertyLines, patterns, useRefImports, suffix, strict);
992
+ return generateStrictCombinedParser(schema, typeName, functionName, propertyLines, patterns, useRefImports, suffix, strict, context);
885
993
  }
886
994
  if (stripUnknown && !strict) {
887
- return generateStrictCombinedParser(schema, typeName, functionName, propertyLines, patterns, useRefImports, suffix, false);
995
+ return generateStrictCombinedParser(schema, typeName, functionName, propertyLines, patterns, useRefImports, suffix, false, context);
888
996
  }
889
997
  if (!refPattern || !useRefImports) {
890
- return generateObjectParser(schema, typeName, useRefImports, suffix, logWarnings, strict, true, stripUnknown, void 0, NO_RESERVED_NAMES, caseInsensitive);
998
+ return generateObjectParser(schema, typeName, useRefImports, suffix, logWarnings, strict, true, stripUnknown, context.rootSchema, NO_RESERVED_NAMES, caseInsensitive);
891
999
  }
892
1000
  const [pattern, patternSchema] = refPattern;
893
1001
  const ref = patternSchema.$ref;
894
1002
  const parserName = generateParserName(refToName(ref, suffix));
895
1003
  const assignmentCode = `(result as Record<string, unknown>)[key] = ${parserName}(value);`;
896
- const escapedPattern = escapeRegexPattern(pattern);
1004
+ const patternLiteral = regexLiteral(pattern);
897
1005
  const inputSpread = " ...input,";
898
1006
  let objectProperties = inputSpread;
899
1007
  if (propertyLines.length > 0) {
@@ -902,7 +1010,7 @@ const generateCombinedObjectParser = (schema, typeName, useRefImports, suffix, l
902
1010
  }
903
1011
  }
904
1012
  const notObjectBranch = strict ? ` throw new Error(\`[${typeName}] expected object, got \${input === null ? "null" : typeof input}\`);` : ` return {} as unknown as ${typeName};`;
905
- const keywordPrelude = strict ? strictObjectKeywordPrelude(schema, typeName) : "";
1013
+ const keywordPrelude = strict ? strictObjectKeywordPrelude(schema, typeName, context) : "";
906
1014
  return `export const ${functionName} = (input: unknown): ${typeName} => {
907
1015
  if (!isObject(input)) {
908
1016
  ${notObjectBranch}
@@ -911,7 +1019,7 @@ ${notObjectBranch}
911
1019
  ${objectProperties}
912
1020
  } as unknown as ${typeName};
913
1021
  for (const key in input) {
914
- if (/${escapedPattern}/.test(key)) {
1022
+ if (${patternLiteral}.test(key)) {
915
1023
  const value = input[key];
916
1024
  ${assignmentCode}
917
1025
  }
@@ -919,13 +1027,13 @@ ${objectProperties}
919
1027
  return result;
920
1028
  };`;
921
1029
  };
922
- const generateAdditionalPropertiesParser = (schema, typeName, useRefImports, suffix, strict) => {
1030
+ const generateAdditionalPropertiesParser = (schema, typeName, useRefImports, suffix, strict, context = {}) => {
923
1031
  const functionName = generateParserName(typeName);
924
1032
  const additionalProps = schema.additionalProperties;
925
1033
  if (!additionalProps) {
926
- return generateEmptyObjectParser(typeName, strict, schema);
1034
+ return generateEmptyObjectParser(typeName, strict, schema, context);
927
1035
  }
928
- const keywordPrelude = strict ? strictObjectKeywordPrelude(schema, typeName) : "";
1036
+ const keywordPrelude = strict ? strictObjectKeywordPrelude(schema, typeName, context) : "";
929
1037
  if (useRefImports && isSchemaObject(additionalProps) && hasRef(additionalProps)) {
930
1038
  const ref = additionalProps.$ref;
931
1039
  const parserName = generateParserName(refToName(ref, suffix));
@@ -993,15 +1101,15 @@ const generateInlineValueParser = (schema, strict = false, label = "") => {
993
1101
  return null;
994
1102
  }
995
1103
  };
996
- const generatePatternPropertiesParser = (schema, typeName, useRefImports, suffix, strict) => {
1104
+ const generatePatternPropertiesParser = (schema, typeName, useRefImports, suffix, strict, context = {}) => {
997
1105
  const functionName = generateParserName(typeName);
998
1106
  if (!("patternProperties" in schema) || typeof schema.patternProperties !== "object") {
999
- return generateEmptyObjectParser(typeName, strict, schema);
1107
+ return generateEmptyObjectParser(typeName, strict, schema, context);
1000
1108
  }
1001
1109
  const patternProps = schema.patternProperties;
1002
1110
  const patterns = Object.entries(patternProps);
1003
1111
  if (patterns.length === 0) {
1004
- return generateEmptyObjectParser(typeName, strict, schema);
1112
+ return generateEmptyObjectParser(typeName, strict, schema, context);
1005
1113
  }
1006
1114
  const [pattern, patternSchema] = patterns[0];
1007
1115
  let patternAssignment = "(result as Record<string, unknown>)[key] = value;";
@@ -1015,19 +1123,19 @@ const generatePatternPropertiesParser = (schema, typeName, useRefImports, suffix
1015
1123
  patternAssignment = "";
1016
1124
  patternValueExpr = null;
1017
1125
  }
1018
- const escapedPattern = escapeRegexPattern(pattern);
1126
+ const patternLiteral = regexLiteral(pattern);
1019
1127
  const notObjectBranch = strict ? ` throw new Error(\`[${typeName}] expected object, got \${input === null ? "null" : typeof input}\`);` : ` return {} as unknown as ${typeName};`;
1020
- const keywordPrelude = strict ? strictObjectKeywordPrelude(schema, typeName) : "";
1128
+ const keywordPrelude = strict ? strictObjectKeywordPrelude(schema, typeName, context) : "";
1021
1129
  const strictKeys = hasAdditionalProperties(schema) && schema.additionalProperties === false;
1022
1130
  if (strictKeys) {
1023
- const loopLines = [` if (/${escapedPattern}/.test(key)) {`];
1131
+ const loopLines = [` if (${patternLiteral}.test(key)) {`];
1024
1132
  if (patternValueExpr) {
1025
1133
  loopLines.push(` const value = input[key];`);
1026
1134
  loopLines.push(` ${safeResultAssign("key", patternValueExpr)}`);
1027
1135
  }
1028
1136
  loopLines.push(` continue;`);
1029
1137
  loopLines.push(` }`);
1030
- const keepConditions = patterns.slice(1).map(([p]) => `/${escapeRegexPattern(p)}/.test(key)`);
1138
+ const keepConditions = patterns.slice(1).map(([p]) => `${regexLiteral(p)}.test(key)`);
1031
1139
  if (keepConditions.length > 0) {
1032
1140
  loopLines.push(` if (${keepConditions.join(" || ")}) {`);
1033
1141
  loopLines.push(` ${safeResultAssign("key", "input[key]")}`);
@@ -1055,7 +1163,7 @@ ${notObjectBranch}
1055
1163
  ...input,
1056
1164
  } as unknown as ${typeName};
1057
1165
  for (const key in input) {
1058
- if (/${escapedPattern}/.test(key)) {
1166
+ if (${patternLiteral}.test(key)) {
1059
1167
  const value = input[key];
1060
1168
  ${patternAssignment}
1061
1169
  }
@@ -1166,6 +1274,12 @@ const selectParserStrategy = (schema, typeName, options) => {
1166
1274
  const stripUnknown = options?.stripUnknown ?? false;
1167
1275
  const caseInsensitive = options?.caseInsensitive ?? false;
1168
1276
  const suffix = options?.typeSuffix ?? "";
1277
+ const context = {
1278
+ useRefImports,
1279
+ suffix,
1280
+ stripUnknown,
1281
+ ...options?.rootSchema !== void 0 ? { rootSchema: options.rootSchema } : {}
1282
+ };
1169
1283
  if (!options?.isRoot && typeName === `Schema${suffix}`) {
1170
1284
  return generateSchemaObjectParser(typeName);
1171
1285
  }
@@ -1182,17 +1296,39 @@ const selectParserStrategy = (schema, typeName, options) => {
1182
1296
  });
1183
1297
  }
1184
1298
  if (hasProperties(schema) && isSchemaObject(schema) && "patternProperties" in schema) {
1185
- return generateCombinedObjectParser(schema, typeName, useRefImports, suffix, logWarnings, strict, stripUnknown, caseInsensitive);
1299
+ return generateCombinedObjectParser(schema, typeName, useRefImports, suffix, logWarnings, strict, stripUnknown, caseInsensitive, context);
1300
+ }
1301
+ if (strict && isSchemaObject(schema) && Array.isArray(schema.type) && schema.type.includes("null")) {
1302
+ return generateNonObjectParser(typeName, schema, strict, {
1303
+ useRefImports,
1304
+ suffix,
1305
+ stripUnknown,
1306
+ caseInsensitive,
1307
+ logWarnings,
1308
+ ...options?.rootSchema !== void 0 ? { rootSchema: options.rootSchema } : {},
1309
+ ...options?.reservedNames !== void 0 ? { reservedNames: options.reservedNames } : {}
1310
+ });
1186
1311
  }
1187
1312
  if (hasProperties(schema)) {
1188
1313
  return generateObjectParser(schema, typeName, useRefImports, suffix, logWarnings, strict, true, stripUnknown, options?.rootSchema, options?.reservedNames, caseInsensitive, options?.shapeValidatorSource);
1189
1314
  }
1315
+ if (strict && isSchemaObject(schema) && ("if" in schema || "then" in schema || "else" in schema)) {
1316
+ return generateNonObjectParser(typeName, schema, strict, {
1317
+ useRefImports,
1318
+ suffix,
1319
+ stripUnknown,
1320
+ caseInsensitive,
1321
+ logWarnings,
1322
+ ...options?.rootSchema !== void 0 ? { rootSchema: options.rootSchema } : {},
1323
+ ...options?.reservedNames !== void 0 ? { reservedNames: options.reservedNames } : {}
1324
+ });
1325
+ }
1190
1326
  if (isSchemaObject(schema) && "if" in schema && "then" in schema && "else" in schema) {
1191
1327
  return generateConditionalParser(schema, typeName, suffix, stripUnknown, caseInsensitive);
1192
1328
  }
1193
1329
  const conditionalObjectSchema = getConditionalObjectSchema(schema);
1194
1330
  if (conditionalObjectSchema) {
1195
- return generateObjectParser(conditionalObjectSchema, typeName, useRefImports, suffix, logWarnings, strict, true, stripUnknown, void 0, NO_RESERVED_NAMES, caseInsensitive);
1331
+ return generateObjectParser(conditionalObjectSchema, typeName, useRefImports, suffix, logWarnings, strict, true, stripUnknown, context.rootSchema, NO_RESERVED_NAMES, caseInsensitive);
1196
1332
  }
1197
1333
  if (!isObjectLikeSchema) {
1198
1334
  return generateNonObjectParser(typeName, schema, strict, {
@@ -1206,23 +1342,30 @@ const selectParserStrategy = (schema, typeName, options) => {
1206
1342
  });
1207
1343
  }
1208
1344
  if ("patternProperties" in schema) {
1209
- return generatePatternPropertiesParser(schema, typeName, useRefImports, suffix, strict);
1345
+ return generatePatternPropertiesParser(schema, typeName, useRefImports, suffix, strict, context);
1210
1346
  }
1211
1347
  if ("additionalProperties" in schema && !hasProperties(schema)) {
1212
1348
  const additionalProps = schema.additionalProperties;
1213
1349
  if (additionalProps === true || additionalProps === false) {
1214
- return generateEmptyObjectParser(typeName, strict, schema);
1350
+ return generateEmptyObjectParser(typeName, strict, schema, context);
1215
1351
  }
1216
- return generateAdditionalPropertiesParser(schema, typeName, useRefImports, suffix, strict);
1352
+ return generateAdditionalPropertiesParser(schema, typeName, useRefImports, suffix, strict, context);
1217
1353
  }
1218
1354
  if ("type" in schema && schema.type === "object" && !hasProperties(schema)) {
1219
- return generateEmptyObjectParser(typeName, strict, schema);
1355
+ return generateEmptyObjectParser(typeName, strict, schema, context);
1220
1356
  }
1221
- return generateEmptyObjectParser(typeName, strict, schema);
1357
+ return generateEmptyObjectParser(typeName, strict, schema, context);
1222
1358
  };
1223
- const generateParserFunction = (schema, typeName, options) => {
1224
- if (options?.strict)
1225
- assertNoUnsupportedKeywords(schema, typeName);
1359
+ const generateParserFunction = (schema, typeName, rawOptions) => {
1360
+ const options = rawOptions?.strict && rawOptions.rootSchema === void 0 && isSchemaObject(schema) ? { ...rawOptions, rootSchema: schema } : rawOptions;
1361
+ if (options?.strict) {
1362
+ assertNoUnsupportedKeywords(schema, typeName, {
1363
+ useRefImports: options.useRefImports ?? false,
1364
+ suffix: options.typeSuffix ?? "",
1365
+ ...options.rootSchema !== void 0 ? { rootSchema: options.rootSchema } : {},
1366
+ ...options.stripUnknown !== void 0 ? { stripUnknown: options.stripUnknown } : {}
1367
+ });
1368
+ }
1226
1369
  return selectParserStrategy(schema, typeName, options);
1227
1370
  };
1228
1371
  const shapeValidatorFunctionName = (typeName) => shapeValidatorName(typeName);
@@ -1244,7 +1387,7 @@ const generateShapeValidator = (schema, typeName, useRefImports, suffix = "", ex
1244
1387
  if (!hasProperties(schema) && !("patternProperties" in schema) && !("if" in schema) && !stripUnknown) {
1245
1388
  const branches = getUnionBranches(schema);
1246
1389
  if (branches) {
1247
- const check = generateUnionCheck("input", branches, useRefImports, suffix);
1390
+ const check = generateUnionCheck("input", branches, useRefImports, suffix, isExclusiveUnion(schema));
1248
1391
  if (check !== null) {
1249
1392
  return `${exportPrefix}const ${fnName} = (input: unknown): boolean => ${check};`;
1250
1393
  }
@@ -1286,6 +1429,10 @@ const generateShapeValidator = (schema, typeName, useRefImports, suffix = "", ex
1286
1429
  checks.push(`(${accessor} === undefined || ${check})`);
1287
1430
  }
1288
1431
  }
1432
+ if (hasMinProperties(schema))
1433
+ checks.push(`Object.keys(input).length >= ${schema.minProperties}`);
1434
+ if (hasMaxProperties(schema))
1435
+ checks.push(`Object.keys(input).length <= ${schema.maxProperties}`);
1289
1436
  if (validatorKeyCount !== null) {
1290
1437
  checks.push(`Object.getPrototypeOf(input) === Object.prototype && Object.keys(input).length === ${validatorKeyCount}`);
1291
1438
  }