@amritk/generate-parsers 0.20.0 → 0.21.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.
@@ -5,6 +5,7 @@ import { resolveRef } from "@amritk/helpers/resolve-ref";
5
5
  import { missingCheck, safeAccessor, safeKey } from "@amritk/helpers/safe-accessor";
6
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
+ import { DEFAULT_UNKNOWN_KEYS } from "@amritk/helpers/unknown-keys-strategy";
8
9
  import { findDiscriminator } from "../helpers/find-discriminator.js";
9
10
  import { getDefaultValue } from "../helpers/get-default-value.js";
10
11
  import { getDiscriminatorValue } from "../helpers/get-discriminator-value.js";
@@ -177,6 +178,11 @@ const generateFallbackObject = (schema, useRefImports, typeName, suffix, subType
177
178
  if (isSchemaObject(schema) && ("if" in schema || "then" in schema || "else" in schema)) {
178
179
  return `{} as ${typeName}`;
179
180
  }
181
+ if (isSchemaObject(schema) && hasAllOf(schema)) {
182
+ const composesConditional = schema.allOf.some((member) => isSchemaObject(member) && (hasRef(member) || "if" in member || "then" in member || "else" in member));
183
+ if (composesConditional)
184
+ return `{} as ${typeName}`;
185
+ }
180
186
  const requiredProps = [];
181
187
  for (const [key, propSchema] of Object.entries(schema.properties)) {
182
188
  const isRequired = isPropertyRequired(key, schema);
@@ -250,7 +256,8 @@ const strictContext = (ctx) => ({
250
256
  useRefImports: ctx?.useRefImports ?? false,
251
257
  suffix: ctx?.suffix ?? "",
252
258
  ...ctx?.rootSchema !== void 0 ? { rootSchema: ctx.rootSchema } : {},
253
- ...ctx?.stripUnknown !== void 0 ? { stripUnknown: ctx.stripUnknown } : {}
259
+ ...ctx?.stripUnknown !== void 0 ? { stripUnknown: ctx.stripUnknown } : {},
260
+ ...ctx?.unknownKeys !== void 0 ? { unknownKeys: ctx.unknownKeys } : {}
254
261
  });
255
262
  const generateNonObjectParser = (typeName, schema, strict, unionCtx) => {
256
263
  const functionName = generateParserName(typeName);
@@ -368,11 +375,12 @@ ${delegatedBody(itemParserName)}
368
375
  const useRefImports = unionCtx?.useRefImports ?? false;
369
376
  const suffix = unionCtx?.suffix ?? "";
370
377
  const stripUnknown = unionCtx?.stripUnknown ?? false;
371
- const itemShapeValidator = generateShapeValidator(items, itemName, useRefImports, suffix, false, stripUnknown, reserved);
378
+ const unknownKeys = unionCtx?.unknownKeys ?? DEFAULT_UNKNOWN_KEYS;
379
+ const itemShapeValidator = generateShapeValidator(items, itemName, useRefImports, suffix, false, stripUnknown, reserved, unknownKeys);
372
380
  const preamble = [
373
381
  `type ${itemName} = NonNullable<${typeName}>[number];`,
374
382
  itemShapeValidator,
375
- generateObjectParser(items, itemName, useRefImports, suffix, unionCtx?.logWarnings ?? false, strict, false, stripUnknown, unionCtx?.rootSchema, reserved, unionCtx?.caseInsensitive ?? false, itemShapeValidator)
383
+ generateObjectParser(items, itemName, useRefImports, suffix, unionCtx?.logWarnings ?? false, strict, false, stripUnknown, unionCtx?.rootSchema, reserved, unionCtx?.caseInsensitive ?? false, itemShapeValidator, unknownKeys)
376
384
  ].join("\n\n");
377
385
  return `${preamble}
378
386
 
@@ -548,13 +556,21 @@ const exactKeyCountOf = (schema) => {
548
556
  return null;
549
557
  return declaredCount;
550
558
  };
559
+ const KEY_COUNT_VAR = "_keyCount";
560
+ const keyCountCheck = (unknownKeys, count, indent, obj = "input") => unknownKeys === "count-keys" ? {
561
+ statements: [],
562
+ verdict: `Object.getPrototypeOf(${obj}) === Object.prototype && Object.keys(${obj}).length === ${count}`
563
+ } : {
564
+ statements: [`${indent}let ${KEY_COUNT_VAR} = 0;`, `${indent}for (const _k in ${obj}) ${KEY_COUNT_VAR}++;`],
565
+ verdict: `${KEY_COUNT_VAR} === ${count}`
566
+ };
551
567
  const shouldCacheVariable = (propSchema) => {
552
568
  return isSchemaObject(propSchema);
553
569
  };
554
570
  const INLINE_NESTED_MAX_PROPERTIES = 12;
555
571
  const INLINE_NESTED_MAX_SITES = 4;
556
572
  const INLINE_NESTED_MAX_TOTAL_PROPERTIES = 24;
557
- const inlineNestedFastPath = (childSchema, childTypeName, objectExpr, useRefImports, suffix, stripUnknown, reservedNames) => {
573
+ const inlineNestedFastPath = (childSchema, childTypeName, objectExpr, useRefImports, suffix, stripUnknown, reservedNames, claimVarName) => {
558
574
  if (!isSchemaObject(childSchema) || !hasProperties(childSchema))
559
575
  return null;
560
576
  if (!stripUnknown)
@@ -576,6 +592,7 @@ const inlineNestedFastPath = (childSchema, childTypeName, objectExpr, useRefImpo
576
592
  const keys = Object.keys(props);
577
593
  if (keys.length === 0 || keys.length > INLINE_NESTED_MAX_PROPERTIES)
578
594
  return null;
595
+ const declarations = [];
579
596
  const checks = [];
580
597
  const fields = [];
581
598
  const readRoot = `(${objectExpr} as Record<string, any>)`;
@@ -584,22 +601,64 @@ const inlineNestedFastPath = (childSchema, childTypeName, objectExpr, useRefImpo
584
601
  if (shouldUseRefImport(propSchema, useRefImports) || shouldUseArrayRefImport(propSchema, useRefImports) || shouldUseRecordRefImport(propSchema, useRefImports)) {
585
602
  return null;
586
603
  }
587
- const accessor = safeAccessor(readRoot, key);
604
+ const accessor = claimVarName(`${objectExpr}_${toVarName(key).slice(1)}`);
605
+ declarations.push(` const ${accessor} = ${safeAccessor(readRoot, key)};`);
588
606
  const check = generatePropertyTypeCheck(accessor, propSchema, useRefImports, suffix);
589
607
  if (check === null)
590
608
  return null;
591
609
  checks.push(check);
592
610
  fields.push(`${safeLiteralKey(key)}: ${accessor}`);
593
611
  }
594
- const condition = [
595
- `typeof ${objectExpr} === "object"`,
596
- `${objectExpr} !== null`,
597
- `!Array.isArray(${objectExpr})`,
598
- ...checks
599
- ].join(" && ");
600
- return { condition, literal: `{ ${fields.join(", ")} }`, propertyCount: keys.length };
612
+ const condition = checks.join(" && ");
613
+ return { declarations, condition, literal: `{ ${fields.join(", ")} }`, propertyCount: keys.length };
614
+ };
615
+ const RESULT_VARIABLE = "out";
616
+ const PROTOTYPE_MEMBERS = new Set(Object.getOwnPropertyNames(Object.prototype));
617
+ const resultMember = (key) => /^[a-zA-Z_$][a-zA-Z0-9_$]*$/.test(key) ? `${RESULT_VARIABLE}.${key}` : `${RESULT_VARIABLE}[${JSON.stringify(key)}]`;
618
+ const renderLiteralEntry = (entry, indent) => {
619
+ if (entry.kind === "field")
620
+ return `${indent}${safeLiteralKey(entry.key)}: ${entry.value},`;
621
+ if (entry.kind === "spread")
622
+ return `${indent}...${entry.expression},`;
623
+ return `${indent}...(${entry.read} !== undefined && { ${safeLiteralKey(entry.key)}: ${entry.value} }),`;
624
+ };
625
+ const renderResultObject = (entries, typeName, guard) => {
626
+ const firstOptional = entries.findIndex((entry) => entry.kind === "optional");
627
+ const assignable = entries.every((entry, index) => index < firstOptional || entry.kind === "spread" || !PROTOTYPE_MEMBERS.has(entry.key));
628
+ if (firstOptional === -1 || !assignable) {
629
+ const open = guard === void 0 ? " return {" : ` if (${guard}) return {`;
630
+ return [
631
+ open,
632
+ entries.map((entry) => renderLiteralEntry(entry, " ")).join("\n"),
633
+ ` } as unknown as ${typeName};`
634
+ ];
635
+ }
636
+ const indent = guard === void 0 ? " " : " ";
637
+ const lines = [];
638
+ if (guard !== void 0)
639
+ lines.push(` if (${guard}) {`);
640
+ const literal = entries.slice(0, firstOptional);
641
+ if (literal.length === 0) {
642
+ lines.push(`${indent}const ${RESULT_VARIABLE}: Record<string, unknown> = {};`);
643
+ } else {
644
+ lines.push(`${indent}const ${RESULT_VARIABLE}: Record<string, unknown> = {`);
645
+ lines.push(literal.map((entry) => renderLiteralEntry(entry, `${indent} `)).join("\n"));
646
+ lines.push(`${indent}};`);
647
+ }
648
+ for (const entry of entries.slice(firstOptional)) {
649
+ if (entry.kind === "field")
650
+ lines.push(`${indent}${resultMember(entry.key)} = ${entry.value};`);
651
+ else if (entry.kind === "optional") {
652
+ lines.push(`${indent}if (${entry.read} !== undefined) ${resultMember(entry.key)} = ${entry.value};`);
653
+ } else
654
+ lines.push(`${indent}Object.assign(${RESULT_VARIABLE}, ${entry.expression});`);
655
+ }
656
+ lines.push(`${indent}return ${RESULT_VARIABLE} as unknown as ${typeName};`);
657
+ if (guard !== void 0)
658
+ lines.push(" }");
659
+ return lines;
601
660
  };
602
- const generateObjectParser = (schema, typeName, useRefImports, suffix, logWarnings, strict, exported = true, stripUnknown = false, rootSchema, reservedNames = NO_RESERVED_NAMES, caseInsensitive = false, shapeValidatorSource) => {
661
+ const generateObjectParser = (schema, typeName, useRefImports, suffix, logWarnings, strict, exported = true, stripUnknown = false, rootSchema, reservedNames = NO_RESERVED_NAMES, caseInsensitive = false, shapeValidatorSource, unknownKeys = DEFAULT_UNKNOWN_KEYS) => {
603
662
  const functionName = generateParserName(typeName);
604
663
  const exportPrefix = exported ? "export " : "";
605
664
  if (!hasProperties(schema)) {
@@ -618,21 +677,21 @@ const generateObjectParser = (schema, typeName, useRefImports, suffix, logWarnin
618
677
  for (const [key, subName] of subTypeNames) {
619
678
  const propSchema = schemaProps[key];
620
679
  preamble.push(`type ${subName} = NonNullable<${typeName}>[${JSON.stringify(key)}];`);
621
- const subShapeValidator = generateShapeValidator(propSchema, subName, useRefImports, suffix, false, stripUnknown, reservedNames);
680
+ const subShapeValidator = generateShapeValidator(propSchema, subName, useRefImports, suffix, false, stripUnknown, reservedNames, unknownKeys);
622
681
  preamble.push(subShapeValidator);
623
- preamble.push(generateObjectParser(propSchema, subName, useRefImports, suffix, logWarnings, strict, false, stripUnknown, rootSchema, reservedNames, caseInsensitive, subShapeValidator));
682
+ preamble.push(generateObjectParser(propSchema, subName, useRefImports, suffix, logWarnings, strict, false, stripUnknown, rootSchema, reservedNames, caseInsensitive, subShapeValidator, unknownKeys));
624
683
  }
625
684
  for (const [key, subName] of subItemNames) {
626
685
  const propSchema = schemaProps[key];
627
686
  const itemSchema = propSchema.items;
628
687
  preamble.push(`type ${subName} = NonNullable<NonNullable<${typeName}>[${JSON.stringify(key)}]>[number];`);
629
- const itemShapeValidator = generateShapeValidator(itemSchema, subName, useRefImports, suffix, false, stripUnknown, reservedNames);
688
+ const itemShapeValidator = generateShapeValidator(itemSchema, subName, useRefImports, suffix, false, stripUnknown, reservedNames, unknownKeys);
630
689
  preamble.push(itemShapeValidator);
631
690
  preamble.push(`const _every${subName} = (arr: readonly unknown[]): boolean => {
632
691
  for (let i = 0; i < arr.length; i++) if (!${shapeValidatorName(subName)}(arr[i])) return false;
633
692
  return true;
634
693
  };`);
635
- preamble.push(generateObjectParser(itemSchema, subName, useRefImports, suffix, logWarnings, strict, false, stripUnknown, rootSchema, reservedNames, caseInsensitive, itemShapeValidator));
694
+ preamble.push(generateObjectParser(itemSchema, subName, useRefImports, suffix, logWarnings, strict, false, stripUnknown, rootSchema, reservedNames, caseInsensitive, itemShapeValidator, unknownKeys));
636
695
  }
637
696
  const strictKeys = hasStrictKeys(schema);
638
697
  const stripKeys = strictKeys || stripUnknown;
@@ -663,7 +722,9 @@ const generateObjectParser = (schema, typeName, useRefImports, suffix, logWarnin
663
722
  let canFastPath = !hasAllOfRefParsers && !hasFastPathBlockingKeyword(schema) && !(strict && inlineAllOfMembers(schema).length > 0);
664
723
  const fastPathChecks = [];
665
724
  const fastPathAccessorChecks = [];
666
- const usedVarNames = /* @__PURE__ */ new Set();
725
+ const usedVarNames = /* @__PURE__ */ new Set([KEY_COUNT_VAR]);
726
+ let inlinedGuardSites = 0;
727
+ let inlinedGuardProperties = 0;
667
728
  for (const key of propertyKeys) {
668
729
  const propSchema = schemaProps[key];
669
730
  const isRequired = isPropertyRequired(key, schema);
@@ -679,7 +740,13 @@ const generateObjectParser = (schema, typeName, useRefImports, suffix, logWarnin
679
740
  continue;
680
741
  }
681
742
  const subName = subTypeNames.get(key);
682
- let check = subName ? `${shapeValidatorName(subName)}(${varName})` : generatePropertyTypeCheck(varName, propSchema, useRefImports, suffix);
743
+ const inlined = subName !== void 0 && inlinedGuardSites < INLINE_NESTED_MAX_SITES ? inlineNestedShapeCheck(propSchema, subName, varName, useRefImports, suffix, stripUnknown, reservedNames, unknownKeys) : null;
744
+ const withinBudget = inlined !== null && inlinedGuardProperties + inlined.propertyCount <= INLINE_NESTED_MAX_TOTAL_PROPERTIES;
745
+ if (withinBudget) {
746
+ inlinedGuardSites++;
747
+ inlinedGuardProperties += inlined.propertyCount;
748
+ }
749
+ let check = withinBudget ? inlined.check : subName ? `${shapeValidatorName(subName)}(${varName})` : generatePropertyTypeCheck(varName, propSchema, useRefImports, suffix);
683
750
  const itemSubName = subItemNames.get(key);
684
751
  if (check !== null && itemSubName) {
685
752
  check = `${check} && _every${itemSubName}(${varName})`;
@@ -711,32 +778,24 @@ const generateObjectParser = (schema, typeName, useRefImports, suffix, logWarnin
711
778
  fastPathAccessorChecks.push(`Object.keys(input).length <= ${schema.maxProperties}`);
712
779
  }
713
780
  }
714
- if (fastPathNeedsKnownKeys) {
715
- fastPathChecks.push(exactKeyCount !== null ? `Object.getPrototypeOf(input) === Object.prototype && Object.keys(input).length === ${exactKeyCount}` : `_hasOnlyKnownKeys${typeName}(input)`);
781
+ const fastPathKeyCount = fastPathNeedsKnownKeys ? exactKeyCount : null;
782
+ if (fastPathNeedsKnownKeys && exactKeyCount === null) {
783
+ fastPathChecks.push(`_hasOnlyKnownKeys${typeName}(input)`);
716
784
  }
717
- const deepGuard = canFastPath && fastPathChecks.length > 0 ? fastPathChecks.join(" && ") : null;
785
+ const deepGuard = canFastPath && (fastPathChecks.length > 0 || fastPathKeyCount !== null) ? fastPathChecks.join(" && ") : null;
718
786
  const literalReturnUsesVars = stripKeys && !(isSchemaObject(schema) && hasAllOf(schema)) && exported;
719
787
  let deepGuardExpr = deepGuard;
720
788
  if (deepGuard !== null && shapeValidatorSource !== void 0 && !literalReturnUsesVars && !droppedKnownKeysTerm) {
721
789
  const shapeFnName = shapeValidatorName(typeName);
722
790
  const shapeStrictKeysGuard = stripKeys && exactKeyCount === null ? `
723
791
  if (!_hasOnlyKnownKeys${typeName}(input)) return false;` : "";
724
- const shapeChecks = [...fastPathAccessorChecks];
725
- if (stripKeys && exactKeyCount !== null) {
726
- shapeChecks.push(`Object.getPrototypeOf(input) === Object.prototype && Object.keys(input).length === ${exactKeyCount}`);
727
- }
728
- const expectedShapeValidator = shapeChecks.length === 0 ? shapeStrictKeysGuard ? `${exportPrefix}const ${shapeFnName} = (input: unknown): boolean => {
729
- if (!isObject(input)) return false;${shapeStrictKeysGuard}
730
- return true;
731
- };` : `${exportPrefix}const ${shapeFnName} = (input: unknown): boolean => isObject(input);` : `${exportPrefix}const ${shapeFnName} = (input: unknown): boolean => {
732
- if (!isObject(input)) return false;${shapeStrictKeysGuard}
733
- return ${shapeChecks.join("\n && ")};
734
- };`;
792
+ const expectedShapeValidator = renderShapeValidator(exportPrefix, shapeFnName, fastPathAccessorChecks, stripKeys ? exactKeyCount : null, shapeStrictKeysGuard, unknownKeys);
735
793
  if (shapeValidatorSource === expectedShapeValidator) {
736
794
  deepGuardExpr = `${shapeFnName}(input)`;
737
795
  }
738
796
  }
739
797
  const deepGuardCallsShape = deepGuardExpr !== deepGuard;
798
+ const inlineKeyCount = deepGuardCallsShape ? null : fastPathKeyCount;
740
799
  let canShallowGuard = strict && stripUnknown && !strictKeys && !hasFastPathBlockingKeyword(schema);
741
800
  const shallowChecks = [];
742
801
  if (canShallowGuard) {
@@ -776,18 +835,25 @@ const generateObjectParser = (schema, typeName, useRefImports, suffix, logWarnin
776
835
  warnLines.push(` }`);
777
836
  warnLines.push(` }`);
778
837
  }
779
- const buildObjectLines = (directAssign) => {
780
- const objectLines = [];
838
+ const claimNestedVarName = (base) => {
839
+ let name = base;
840
+ while (usedVarNames.has(name))
841
+ name = `${name}_`;
842
+ usedVarNames.add(name);
843
+ return name;
844
+ };
845
+ const buildObjectLines = (directAssign, hoistLines) => {
846
+ const entries = [];
781
847
  let inlinedSites = 0;
782
848
  let inlinedProperties = 0;
783
849
  if (!stripKeys) {
784
- objectLines.push(" ...input,");
850
+ entries.push({ kind: "spread", expression: "input" });
785
851
  }
786
852
  if (useRefImports && isSchemaObject(schema) && hasAllOf(schema)) {
787
853
  for (const entry of schema.allOf) {
788
854
  if (isSchemaObject(entry) && hasRef(entry)) {
789
855
  const parserName = generateParserName(refToName(entry.$ref, suffix));
790
- objectLines.push(` ...(${parserName}(input) as Record<string, unknown>),`);
856
+ entries.push({ kind: "spread", expression: `(${parserName}(input) as Record<string, unknown>)` });
791
857
  }
792
858
  }
793
859
  }
@@ -798,18 +864,23 @@ const generateObjectParser = (schema, typeName, useRefImports, suffix, logWarnin
798
864
  if (subName) {
799
865
  const subParserName = generateParserName(subName);
800
866
  if (isRequired) {
801
- const inlined = directAssign && !logWarnings && inlinedSites < INLINE_NESTED_MAX_SITES ? inlineNestedFastPath(propSchema, subName, accessor, useRefImports, suffix, stripUnknown, reservedNames) : null;
867
+ const inlined = hoistLines !== void 0 && directAssign && !logWarnings && inlinedSites < INLINE_NESTED_MAX_SITES ? inlineNestedFastPath(propSchema, subName, accessor, useRefImports, suffix, stripUnknown, reservedNames, claimNestedVarName) : null;
802
868
  if (inlined !== null && inlinedProperties + inlined.propertyCount <= INLINE_NESTED_MAX_TOTAL_PROPERTIES) {
803
869
  inlinedSites++;
804
870
  inlinedProperties += inlined.propertyCount;
805
- objectLines.push(` ${safeLiteralKey(key)}: (${inlined.condition})
871
+ hoistLines?.push(...inlined.declarations);
872
+ entries.push({
873
+ kind: "field",
874
+ key,
875
+ value: `(${inlined.condition})
806
876
  ? ${inlined.literal}
807
- : ${subParserName}(${accessor}),`);
877
+ : ${subParserName}(${accessor})`
878
+ });
808
879
  continue;
809
880
  }
810
- objectLines.push(` ${safeLiteralKey(key)}: ${subParserName}(${accessor}),`);
881
+ entries.push({ kind: "field", key, value: `${subParserName}(${accessor})` });
811
882
  } else {
812
- objectLines.push(` ...(${accessor} !== undefined && { ${safeLiteralKey(key)}: ${subParserName}(${accessor}) }),`);
883
+ entries.push({ kind: "optional", key, read: accessor, value: `${subParserName}(${accessor})` });
813
884
  }
814
885
  continue;
815
886
  }
@@ -817,9 +888,14 @@ const generateObjectParser = (schema, typeName, useRefImports, suffix, logWarnin
817
888
  if (itemSubName) {
818
889
  const itemParserName = generateParserName(itemSubName);
819
890
  if (isRequired) {
820
- objectLines.push(` ${safeLiteralKey(key)}: validateArray(${accessor}, ${itemParserName}),`);
891
+ entries.push({ kind: "field", key, value: `validateArray(${accessor}, ${itemParserName})` });
821
892
  } else {
822
- objectLines.push(` ...(${accessor} !== undefined && { ${safeLiteralKey(key)}: validateArray(${accessor}, ${itemParserName}) }),`);
893
+ entries.push({
894
+ kind: "optional",
895
+ key,
896
+ read: accessor,
897
+ value: `validateArray(${accessor}, ${itemParserName})`
898
+ });
823
899
  }
824
900
  continue;
825
901
  }
@@ -827,9 +903,9 @@ const generateObjectParser = (schema, typeName, useRefImports, suffix, logWarnin
827
903
  const ref = propSchema.$ref;
828
904
  const parserName = generateParserName(refToName(ref, suffix));
829
905
  if (isRequired) {
830
- objectLines.push(` ${safeLiteralKey(key)}: ${parserName}(${accessor}),`);
906
+ entries.push({ kind: "field", key, value: `${parserName}(${accessor})` });
831
907
  } else {
832
- objectLines.push(` ...(${accessor} !== undefined && { ${safeLiteralKey(key)}: ${parserName}(${accessor}) }),`);
908
+ entries.push({ kind: "optional", key, read: accessor, value: `${parserName}(${accessor})` });
833
909
  }
834
910
  continue;
835
911
  }
@@ -838,9 +914,9 @@ const generateObjectParser = (schema, typeName, useRefImports, suffix, logWarnin
838
914
  const ref = items.$ref;
839
915
  const parserName = generateParserName(refToName(ref, suffix));
840
916
  if (isRequired) {
841
- objectLines.push(` ${safeLiteralKey(key)}: validateArray(${accessor}, ${parserName}),`);
917
+ entries.push({ kind: "field", key, value: `validateArray(${accessor}, ${parserName})` });
842
918
  } else {
843
- objectLines.push(` ...(${accessor} !== undefined && { ${safeLiteralKey(key)}: validateArray(${accessor}, ${parserName}) }),`);
919
+ entries.push({ kind: "optional", key, read: accessor, value: `validateArray(${accessor}, ${parserName})` });
844
920
  }
845
921
  continue;
846
922
  }
@@ -849,21 +925,21 @@ const generateObjectParser = (schema, typeName, useRefImports, suffix, logWarnin
849
925
  const ref = additionalProps.$ref;
850
926
  const parserName = generateParserName(refToName(ref, suffix));
851
927
  if (isRequired) {
852
- objectLines.push(` ${safeLiteralKey(key)}: validateRecord(${accessor}, ${parserName}),`);
928
+ entries.push({ kind: "field", key, value: `validateRecord(${accessor}, ${parserName})` });
853
929
  } else {
854
- objectLines.push(` ...(${accessor} !== undefined && { ${safeLiteralKey(key)}: validateRecord(${accessor}, ${parserName}) }),`);
930
+ entries.push({ kind: "optional", key, read: accessor, value: `validateRecord(${accessor}, ${parserName})` });
855
931
  }
856
932
  continue;
857
933
  }
858
934
  if (!isSchemaObject(propSchema)) {
859
935
  if (propSchema === true) {
860
936
  if (isRequired) {
861
- objectLines.push(` ${safeLiteralKey(key)}: ${accessor},`);
937
+ entries.push({ kind: "field", key, value: accessor });
862
938
  } else if (stripKeys) {
863
- objectLines.push(` ...(${accessor} !== undefined && { ${safeLiteralKey(key)}: ${accessor} }),`);
939
+ entries.push({ kind: "optional", key, read: accessor, value: accessor });
864
940
  }
865
941
  } else if (isRequired) {
866
- objectLines.push(` ${safeLiteralKey(key)}: undefined,`);
942
+ entries.push({ kind: "field", key, value: "undefined" });
867
943
  }
868
944
  continue;
869
945
  }
@@ -876,25 +952,47 @@ const generateObjectParser = (schema, typeName, useRefImports, suffix, logWarnin
876
952
  valueExpr = generateValidationExpression(key, propSchema, defaultValue, true, void 0, void 0, shouldCache ? varName : void 0, knownNotUndefined, caseInsensitive);
877
953
  }
878
954
  if (isRequired) {
879
- objectLines.push(` ${safeLiteralKey(key)}: ${valueExpr},`);
955
+ entries.push({ kind: "field", key, value: valueExpr });
880
956
  } else {
881
- objectLines.push(` ...(${accessor} !== undefined && { ${safeLiteralKey(key)}: ${valueExpr} }),`);
957
+ entries.push({ kind: "optional", key, read: accessor, value: valueExpr });
882
958
  }
883
959
  }
884
- return objectLines;
960
+ return entries;
885
961
  };
886
- const emitReturn = (lines2, objectLines) => {
887
- lines2.push(` return {`);
888
- lines2.push(objectLines.join("\n"));
889
- lines2.push(` } as unknown as ${typeName};`);
962
+ const emitReturn = (lines2, entries) => {
963
+ lines2.push(...renderResultObject(entries, typeName));
964
+ };
965
+ const withKeyCount = (lines2, guarded) => {
966
+ if (inlineKeyCount === null) {
967
+ lines2.push(...guarded(deepGuardExpr));
968
+ return;
969
+ }
970
+ const count = keyCountCheck(unknownKeys, inlineKeyCount, " ");
971
+ if (count.statements.length === 0) {
972
+ lines2.push(...guarded(deepGuardExpr === "" ? count.verdict : `${deepGuardExpr} && ${count.verdict}`));
973
+ return;
974
+ }
975
+ const counted = guarded(count.verdict);
976
+ if (deepGuardExpr === "") {
977
+ lines2.push(...keyCountCheck(unknownKeys, inlineKeyCount, " ").statements, ...counted);
978
+ return;
979
+ }
980
+ const nested = counted.map((line) => line.split("\n").map((part) => ` ${part}`).join("\n"));
981
+ lines2.push(` if (${deepGuardExpr}) {`, ...count.statements, ...nested, ` }`);
982
+ };
983
+ const emitGuardedReturn = (lines2, statement) => {
984
+ withKeyCount(lines2, (guard) => [` if (${guard}) ${statement}`]);
985
+ };
986
+ const emitGuardedResult = (lines2, fields) => {
987
+ withKeyCount(lines2, (guard) => renderResultObject(fields, typeName, guard));
890
988
  };
891
989
  const emitDeepGuardReturn = (lines2) => {
892
990
  if (!fastPathBuildsDeclaredLiteral) {
893
- lines2.push(` if (${deepGuardExpr}) return { ...input } as ${typeName};`);
991
+ emitGuardedReturn(lines2, `return { ...input } as ${typeName};`);
894
992
  return;
895
993
  }
896
994
  if (!exported && fastPathNeedsKnownKeys) {
897
- lines2.push(` if (${deepGuardExpr}) return input as ${typeName};`);
995
+ emitGuardedReturn(lines2, `return input as ${typeName};`);
898
996
  return;
899
997
  }
900
998
  const fields = [];
@@ -902,33 +1000,90 @@ const generateObjectParser = (schema, typeName, useRefImports, suffix, logWarnin
902
1000
  if (!isSchemaObject(propSchema)) {
903
1001
  if (propSchema === true) {
904
1002
  const acc = safeAccessor("input", key);
905
- fields.push(isRequired ? ` ${safeLiteralKey(key)}: ${acc},` : ` ...(${acc} !== undefined && { ${safeLiteralKey(key)}: ${acc} }),`);
1003
+ fields.push(isRequired ? { kind: "field", key, value: acc } : { kind: "optional", key, read: acc, value: acc });
906
1004
  } else if (isRequired) {
907
- fields.push(` ${safeLiteralKey(key)}: undefined,`);
1005
+ fields.push({ kind: "field", key, value: "undefined" });
908
1006
  }
909
1007
  continue;
910
1008
  }
911
1009
  const accessor = shouldCacheVariable(propSchema) ? varName : safeAccessor("input", key);
912
- fields.push(isRequired ? ` ${safeLiteralKey(key)}: ${accessor},` : ` ...(${accessor} !== undefined && { ${safeLiteralKey(key)}: ${accessor} }),`);
1010
+ fields.push(isRequired ? { kind: "field", key, value: accessor } : { kind: "optional", key, read: accessor, value: accessor });
913
1011
  }
914
- lines2.push(` if (${deepGuardExpr}) return {`);
915
- lines2.push(fields.join("\n"));
916
- lines2.push(` } as unknown as ${typeName};`);
1012
+ emitGuardedResult(lines2, fields);
917
1013
  };
1014
+ const assertionLines = strict ? generateObjectStrictAssertion(schema, typeName, {
1015
+ useRefImports,
1016
+ suffix,
1017
+ stripUnknown,
1018
+ ...rootSchema !== void 0 ? { rootSchema } : {}
1019
+ }) : [];
1020
+ const unknownKeyThrowLines = () => rejectsUnknownKeys ? [
1021
+ ` for (const _k in input) {`,
1022
+ ` if (${strictKeyCheck.isUnknown("_k")}) throw new Error(\`[${typeName}] unknown property "\${_k}"\`);`,
1023
+ ` }`
1024
+ ] : [];
1025
+ const varsAfterGuard = deepGuardCallsShape && shallowGuard === null;
1026
+ const assertBodyLines = [...assertionLines.slice(1), ...shallowGuard === null ? unknownKeyThrowLines() : []];
1027
+ const canSplit = !logWarnings && strict === true && (deepGuard !== null || shallowGuard !== null) && assertBodyLines.length > 0;
1028
+ const assertName = `_assert${typeName}`;
1029
+ const assertObjectName = `_assert${typeName}Object`;
1030
+ const emitAssertObjectFunction = () => `const ${assertObjectName}: (input: unknown) => asserts input is Record<string, unknown> = (input) => {
1031
+ ${assertionLines[0]}
1032
+ };`;
1033
+ const emitAssertFunction = () => `const ${assertName} = (input: Record<string, unknown>): void => {
1034
+ ${assertBodyLines.join("\n")}
1035
+ };`;
918
1036
  const lines = [];
919
1037
  lines.push(`${exportPrefix}const ${functionName} = (input: unknown): ${typeName} => {`);
920
- const varsAfterGuard = deepGuardCallsShape && shallowGuard === null;
921
- if (strict) {
1038
+ if (canSplit) {
1039
+ preamble.push(emitAssertObjectFunction());
1040
+ preamble.push(emitAssertFunction());
1041
+ lines.push(` if (!isObject(input)) ${assertObjectName}(input);`);
1042
+ if (!varsAfterGuard)
1043
+ lines.push(...varDeclLines);
1044
+ if (shallowGuard) {
1045
+ lines.push(` if (!(${shallowGuard})) ${assertName}(input);`);
1046
+ if (!exported && deepGuard !== null && fastPathNeedsKnownKeys) {
1047
+ const residual = [];
1048
+ for (let i = 0; i < fastPathChecks.length; i++) {
1049
+ const deep = fastPathChecks[i];
1050
+ const shallow = shallowChecks[i];
1051
+ if (deep === shallow)
1052
+ continue;
1053
+ residual.push(shallow !== void 0 && deep.startsWith(`${shallow} && `) ? deep.slice(shallow.length + 4) : deep);
1054
+ }
1055
+ const returnInput = `return input as ${typeName};`;
1056
+ if (fastPathKeyCount === null) {
1057
+ lines.push(residual.length > 0 ? ` if (${residual.join(" && ")}) ${returnInput}` : ` ${returnInput}`);
1058
+ } else {
1059
+ const count = keyCountCheck(unknownKeys, fastPathKeyCount, residual.length === 0 ? " " : " ");
1060
+ if (count.statements.length === 0) {
1061
+ lines.push(` if (${[...residual, count.verdict].join(" && ")}) ${returnInput}`);
1062
+ } else if (residual.length === 0) {
1063
+ lines.push(...count.statements, ` if (${count.verdict}) ${returnInput}`);
1064
+ } else {
1065
+ lines.push(` if (${residual.join(" && ")}) {`);
1066
+ lines.push(...count.statements, ` if (${count.verdict}) ${returnInput}`);
1067
+ lines.push(` }`);
1068
+ }
1069
+ }
1070
+ }
1071
+ const hoisted = [];
1072
+ const objectLines = buildObjectLines(true, hoisted);
1073
+ lines.push(...hoisted);
1074
+ emitReturn(lines, objectLines);
1075
+ } else {
1076
+ emitDeepGuardReturn(lines);
1077
+ if (varsAfterGuard)
1078
+ lines.push(...varDeclLines);
1079
+ lines.push(` ${assertName}(input);`);
1080
+ emitReturn(lines, buildObjectLines(true));
1081
+ }
1082
+ } else if (strict) {
922
1083
  lines.push(` if (!isObject(input)) throw new Error(\`[${typeName}] expected object, got \${input === null ? "null" : typeof input}\`);`);
923
1084
  if (!varsAfterGuard)
924
1085
  lines.push(...varDeclLines);
925
1086
  lines.push(...warnLines);
926
- const assertionLines = generateObjectStrictAssertion(schema, typeName, {
927
- useRefImports,
928
- suffix,
929
- stripUnknown,
930
- ...rootSchema !== void 0 ? { rootSchema } : {}
931
- }).slice(1);
932
1087
  if (shallowGuard) {
933
1088
  if (!exported && deepGuard && fastPathNeedsKnownKeys) {
934
1089
  const residual = [];
@@ -940,34 +1095,41 @@ const generateObjectParser = (schema, typeName, useRefImports, suffix, logWarnin
940
1095
  residual.push(shallow !== void 0 && deep.startsWith(`${shallow} && `) ? deep.slice(shallow.length + 4) : deep);
941
1096
  }
942
1097
  lines.push(` if (${shallowGuard}) {`);
943
- lines.push(residual.length > 0 ? ` if (${residual.join(" && ")}) return input as ${typeName};` : ` return input as ${typeName};`);
1098
+ const returnInput = `return input as ${typeName};`;
1099
+ if (fastPathKeyCount === null) {
1100
+ lines.push(residual.length > 0 ? ` if (${residual.join(" && ")}) ${returnInput}` : ` ${returnInput}`);
1101
+ } else {
1102
+ const count = keyCountCheck(unknownKeys, fastPathKeyCount, residual.length === 0 ? " " : " ");
1103
+ if (count.statements.length === 0) {
1104
+ lines.push(` if (${[...residual, count.verdict].join(" && ")}) ${returnInput}`);
1105
+ } else if (residual.length === 0) {
1106
+ lines.push(...count.statements, ` if (${count.verdict}) ${returnInput}`);
1107
+ } else {
1108
+ lines.push(` if (${residual.join(" && ")}) {`);
1109
+ lines.push(...count.statements, ` if (${count.verdict}) ${returnInput}`);
1110
+ lines.push(` }`);
1111
+ }
1112
+ }
944
1113
  lines.push(` } else {`);
945
- for (const assertionLine of assertionLines) {
1114
+ for (const assertionLine of assertionLines.slice(1))
946
1115
  lines.push(` ${assertionLine}`);
947
- }
948
1116
  lines.push(` }`);
949
1117
  } else {
950
1118
  lines.push(` if (!(${shallowGuard})) {`);
951
- for (const assertionLine of assertionLines) {
1119
+ for (const assertionLine of assertionLines.slice(1))
952
1120
  lines.push(` ${assertionLine}`);
953
- }
954
1121
  lines.push(` }`);
955
1122
  }
956
1123
  emitReturn(lines, buildObjectLines(true));
957
1124
  } else {
958
- if (deepGuard) {
1125
+ if (deepGuard !== null) {
959
1126
  emitDeepGuardReturn(lines);
960
1127
  }
961
1128
  if (varsAfterGuard)
962
1129
  lines.push(...varDeclLines);
963
- for (const assertionLine of assertionLines) {
1130
+ for (const assertionLine of assertionLines.slice(1))
964
1131
  lines.push(assertionLine);
965
- }
966
- if (rejectsUnknownKeys) {
967
- lines.push(` for (const _k in input) {`);
968
- lines.push(` if (${strictKeyCheck.isUnknown("_k")}) throw new Error(\`[${typeName}] unknown property "\${_k}"\`);`);
969
- lines.push(` }`);
970
- }
1132
+ lines.push(...unknownKeyThrowLines());
971
1133
  emitReturn(lines, buildObjectLines(true));
972
1134
  }
973
1135
  } else {
@@ -975,7 +1137,7 @@ const generateObjectParser = (schema, typeName, useRefImports, suffix, logWarnin
975
1137
  if (!varsAfterGuard)
976
1138
  lines.push(...varDeclLines);
977
1139
  lines.push(...warnLines);
978
- if (deepGuard) {
1140
+ if (deepGuard !== null) {
979
1141
  emitDeepGuardReturn(lines);
980
1142
  }
981
1143
  if (varsAfterGuard)
@@ -1034,6 +1196,7 @@ ${loopLines.join("\n")}
1034
1196
  };
1035
1197
  const generateCombinedObjectParser = (schema, typeName, useRefImports, suffix, logWarnings, strict, stripUnknown = false, caseInsensitive = false, context = {}) => {
1036
1198
  const functionName = generateParserName(typeName);
1199
+ const unknownKeys = context.unknownKeys ?? DEFAULT_UNKNOWN_KEYS;
1037
1200
  const entries = generatePropertyEntries(schema, useRefImports, suffix, caseInsensitive);
1038
1201
  const propertyLines = entries.map((entry) => {
1039
1202
  if (entry.isOptional) {
@@ -1042,7 +1205,7 @@ const generateCombinedObjectParser = (schema, typeName, useRefImports, suffix, l
1042
1205
  return ` ${safeLiteralKey(entry.key)}: ${entry.value},`;
1043
1206
  });
1044
1207
  if (!isSchemaObject(schema) || !("patternProperties" in schema)) {
1045
- return generateObjectParser(schema, typeName, useRefImports, suffix, logWarnings, strict, true, stripUnknown, context.rootSchema, NO_RESERVED_NAMES, caseInsensitive);
1208
+ return generateObjectParser(schema, typeName, useRefImports, suffix, logWarnings, strict, true, stripUnknown, context.rootSchema, NO_RESERVED_NAMES, caseInsensitive, void 0, unknownKeys);
1046
1209
  }
1047
1210
  const patternProps = schema.patternProperties;
1048
1211
  const patterns = Object.entries(patternProps);
@@ -1055,7 +1218,7 @@ const generateCombinedObjectParser = (schema, typeName, useRefImports, suffix, l
1055
1218
  return generateStrictCombinedParser(schema, typeName, functionName, propertyLines, patterns, useRefImports, suffix, false, context);
1056
1219
  }
1057
1220
  if (!refPattern || !useRefImports) {
1058
- return generateObjectParser(schema, typeName, useRefImports, suffix, logWarnings, strict, true, stripUnknown, context.rootSchema, NO_RESERVED_NAMES, caseInsensitive);
1221
+ return generateObjectParser(schema, typeName, useRefImports, suffix, logWarnings, strict, true, stripUnknown, context.rootSchema, NO_RESERVED_NAMES, caseInsensitive, void 0, unknownKeys);
1059
1222
  }
1060
1223
  const [pattern, patternSchema] = refPattern;
1061
1224
  const ref = patternSchema.$ref;
@@ -1231,7 +1394,7 @@ ${notObjectBranch}
1231
1394
  return result;
1232
1395
  };`;
1233
1396
  };
1234
- const generateConditionalParser = (schema, typeName, suffix, stripUnknown = false, caseInsensitive = false) => {
1397
+ const generateConditionalParser = (schema, typeName, suffix, stripUnknown = false, caseInsensitive = false, unknownKeys = DEFAULT_UNKNOWN_KEYS) => {
1235
1398
  const functionName = generateParserName(typeName);
1236
1399
  const ifSchema = schema.if;
1237
1400
  const thenSchema = schema.then;
@@ -1241,7 +1404,7 @@ const generateConditionalParser = (schema, typeName, suffix, stripUnknown = fals
1241
1404
  if (!thenHasRef || !elseHasRef) {
1242
1405
  const mergedSchema = getConditionalObjectSchema(schema);
1243
1406
  if (mergedSchema) {
1244
- return generateObjectParser(mergedSchema, typeName, false, suffix, false, false, true, stripUnknown, void 0, NO_RESERVED_NAMES, caseInsensitive);
1407
+ return generateObjectParser(mergedSchema, typeName, false, suffix, false, false, true, stripUnknown, void 0, NO_RESERVED_NAMES, caseInsensitive, void 0, unknownKeys);
1245
1408
  }
1246
1409
  return generateEmptyObjectParser(typeName);
1247
1410
  }
@@ -1249,7 +1412,7 @@ const generateConditionalParser = (schema, typeName, suffix, stripUnknown = fals
1249
1412
  if (!isRefCondition) {
1250
1413
  const mergedSchema = getConditionalObjectSchema(schema);
1251
1414
  if (mergedSchema) {
1252
- return generateObjectParser(mergedSchema, typeName, false, suffix, false, false, true, stripUnknown, void 0, NO_RESERVED_NAMES, caseInsensitive);
1415
+ return generateObjectParser(mergedSchema, typeName, false, suffix, false, false, true, stripUnknown, void 0, NO_RESERVED_NAMES, caseInsensitive, void 0, unknownKeys);
1253
1416
  }
1254
1417
  return generateEmptyObjectParser(typeName);
1255
1418
  }
@@ -1333,11 +1496,13 @@ const selectParserStrategy = (schema, typeName, options) => {
1333
1496
  const strict = options?.strict ?? false;
1334
1497
  const stripUnknown = options?.stripUnknown ?? false;
1335
1498
  const caseInsensitive = options?.caseInsensitive ?? false;
1499
+ const unknownKeys = options?.unknownKeys ?? DEFAULT_UNKNOWN_KEYS;
1336
1500
  const suffix = options?.typeSuffix ?? "";
1337
1501
  const context = {
1338
1502
  useRefImports,
1339
1503
  suffix,
1340
1504
  stripUnknown,
1505
+ unknownKeys,
1341
1506
  ...options?.rootSchema !== void 0 ? { rootSchema: options.rootSchema } : {}
1342
1507
  };
1343
1508
  const copyLoopContext = { ...context, subParsers: false };
@@ -1352,6 +1517,7 @@ const selectParserStrategy = (schema, typeName, options) => {
1352
1517
  stripUnknown,
1353
1518
  caseInsensitive,
1354
1519
  logWarnings,
1520
+ unknownKeys,
1355
1521
  ...options?.rootSchema !== void 0 ? { rootSchema: options.rootSchema } : {},
1356
1522
  ...options?.reservedNames !== void 0 ? { reservedNames: options.reservedNames } : {}
1357
1523
  });
@@ -1366,12 +1532,13 @@ const selectParserStrategy = (schema, typeName, options) => {
1366
1532
  stripUnknown,
1367
1533
  caseInsensitive,
1368
1534
  logWarnings,
1535
+ unknownKeys,
1369
1536
  ...options?.rootSchema !== void 0 ? { rootSchema: options.rootSchema } : {},
1370
1537
  ...options?.reservedNames !== void 0 ? { reservedNames: options.reservedNames } : {}
1371
1538
  });
1372
1539
  }
1373
1540
  if (hasProperties(schema)) {
1374
- return generateObjectParser(schema, typeName, useRefImports, suffix, logWarnings, strict, true, stripUnknown, options?.rootSchema, options?.reservedNames, caseInsensitive, options?.shapeValidatorSource);
1541
+ return generateObjectParser(schema, typeName, useRefImports, suffix, logWarnings, strict, true, stripUnknown, options?.rootSchema, options?.reservedNames, caseInsensitive, options?.shapeValidatorSource, unknownKeys);
1375
1542
  }
1376
1543
  if (strict && isSchemaObject(schema) && ("if" in schema || "then" in schema || "else" in schema)) {
1377
1544
  return generateNonObjectParser(typeName, schema, strict, {
@@ -1380,16 +1547,17 @@ const selectParserStrategy = (schema, typeName, options) => {
1380
1547
  stripUnknown,
1381
1548
  caseInsensitive,
1382
1549
  logWarnings,
1550
+ unknownKeys,
1383
1551
  ...options?.rootSchema !== void 0 ? { rootSchema: options.rootSchema } : {},
1384
1552
  ...options?.reservedNames !== void 0 ? { reservedNames: options.reservedNames } : {}
1385
1553
  });
1386
1554
  }
1387
1555
  if (isSchemaObject(schema) && "if" in schema && "then" in schema && "else" in schema) {
1388
- return generateConditionalParser(schema, typeName, suffix, stripUnknown, caseInsensitive);
1556
+ return generateConditionalParser(schema, typeName, suffix, stripUnknown, caseInsensitive, unknownKeys);
1389
1557
  }
1390
1558
  const conditionalObjectSchema = getConditionalObjectSchema(schema);
1391
1559
  if (conditionalObjectSchema) {
1392
- return generateObjectParser(conditionalObjectSchema, typeName, useRefImports, suffix, logWarnings, strict, true, stripUnknown, context.rootSchema, NO_RESERVED_NAMES, caseInsensitive);
1560
+ return generateObjectParser(conditionalObjectSchema, typeName, useRefImports, suffix, logWarnings, strict, true, stripUnknown, context.rootSchema, NO_RESERVED_NAMES, caseInsensitive, void 0, unknownKeys);
1393
1561
  }
1394
1562
  if (!isObjectLikeSchema) {
1395
1563
  return generateNonObjectParser(typeName, schema, strict, {
@@ -1398,6 +1566,7 @@ const selectParserStrategy = (schema, typeName, options) => {
1398
1566
  stripUnknown,
1399
1567
  caseInsensitive,
1400
1568
  logWarnings,
1569
+ unknownKeys,
1401
1570
  ...options?.rootSchema !== void 0 ? { rootSchema: options.rootSchema } : {},
1402
1571
  ...options?.reservedNames !== void 0 ? { reservedNames: options.reservedNames } : {}
1403
1572
  });
@@ -1430,7 +1599,7 @@ const generateParserFunction = (schema, typeName, rawOptions) => {
1430
1599
  return selectParserStrategy(schema, typeName, options);
1431
1600
  };
1432
1601
  const shapeValidatorFunctionName = (typeName) => shapeValidatorName(typeName);
1433
- const generateShapeValidator = (schema, typeName, useRefImports, suffix = "", exported = true, stripUnknown = false, reservedNames = NO_RESERVED_NAMES) => {
1602
+ const generateShapeValidator = (schema, typeName, useRefImports, suffix = "", exported = true, stripUnknown = false, reservedNames = NO_RESERVED_NAMES, unknownKeys = DEFAULT_UNKNOWN_KEYS) => {
1434
1603
  const fnName = shapeValidatorName(typeName);
1435
1604
  const exportPrefix = exported ? "export " : "";
1436
1605
  const stub = `${exportPrefix}const ${fnName} = (_input: unknown): boolean => false;`;
@@ -1455,25 +1624,34 @@ const generateShapeValidator = (schema, typeName, useRefImports, suffix = "", ex
1455
1624
  }
1456
1625
  return stub;
1457
1626
  }
1458
- if (!hasProperties(schema))
1627
+ const shape = shapeChecksOf(schema, typeName, "input", useRefImports, suffix, stripUnknown, reservedNames);
1628
+ if (shape === null)
1459
1629
  return stub;
1630
+ const strictKeysGuard = shape.walksKeys ? `
1631
+ if (!_hasOnlyKnownKeys${typeName}(input)) return false;` : "";
1632
+ return renderShapeValidator(exportPrefix, fnName, shape.checks, shape.keyCount, strictKeysGuard, unknownKeys);
1633
+ };
1634
+ const shapeChecksOf = (schema, typeName, root, useRefImports, suffix, stripUnknown, reservedNames) => {
1635
+ if (!isSchemaObject(schema))
1636
+ return null;
1637
+ if (hasFastPathBlockingKeyword(schema))
1638
+ return null;
1639
+ if (!hasProperties(schema))
1640
+ return null;
1460
1641
  if (hasOneOf(schema) || hasAnyOf(schema) || hasAllOf(schema) || "not" in schema || "patternProperties" in schema || "if" in schema || "then" in schema || "else" in schema) {
1461
- return stub;
1642
+ return null;
1462
1643
  }
1463
1644
  const schemaProps = schema.properties;
1464
1645
  const propertyKeys = Object.keys(schemaProps);
1465
1646
  const { objects: subTypeNames, arrayItems: subItemNames } = collectInlineSubTypes(schema, typeName, reservedNames);
1466
1647
  const validatorStripKeys = hasStrictKeys(schema) || stripUnknown;
1467
- const validatorKeyCount = validatorStripKeys ? exactKeyCountOf(schema) : null;
1468
- const strictKeysGuard = validatorStripKeys && validatorKeyCount === null ? `
1469
- if (!_hasOnlyKnownKeys${typeName}(input)) return false;` : "";
1648
+ const keyCount = validatorStripKeys ? exactKeyCountOf(schema) : null;
1470
1649
  const checks = [];
1471
1650
  for (const key of propertyKeys) {
1472
1651
  const propSchema = schemaProps[key];
1473
- if (shouldUseRecordRefImport(propSchema, useRefImports)) {
1474
- return stub;
1475
- }
1476
- const accessor = safeAccessor("input", key);
1652
+ if (shouldUseRecordRefImport(propSchema, useRefImports))
1653
+ return null;
1654
+ const accessor = safeAccessor(root, key);
1477
1655
  const isRequired = isPropertyRequired(key, schema);
1478
1656
  const subName = subTypeNames.get(key);
1479
1657
  let check = subName ? `${shapeValidatorName(subName)}(${accessor})` : generatePropertyTypeCheck(accessor, propSchema, useRefImports, suffix);
@@ -1481,9 +1659,8 @@ const generateShapeValidator = (schema, typeName, useRefImports, suffix = "", ex
1481
1659
  if (check !== null && itemSubName) {
1482
1660
  check = `${check} && _every${itemSubName}(${accessor})`;
1483
1661
  }
1484
- if (check === null) {
1485
- return stub;
1486
- }
1662
+ if (check === null)
1663
+ return null;
1487
1664
  if (isRequired) {
1488
1665
  checks.push(check);
1489
1666
  } else {
@@ -1491,28 +1668,56 @@ const generateShapeValidator = (schema, typeName, useRefImports, suffix = "", ex
1491
1668
  }
1492
1669
  }
1493
1670
  if (hasMinProperties(schema))
1494
- checks.push(`Object.keys(input).length >= ${schema.minProperties}`);
1671
+ checks.push(`Object.keys(${root}).length >= ${schema.minProperties}`);
1495
1672
  if (hasMaxProperties(schema))
1496
- checks.push(`Object.keys(input).length <= ${schema.maxProperties}`);
1497
- if (validatorKeyCount !== null) {
1498
- checks.push(`Object.getPrototypeOf(input) === Object.prototype && Object.keys(input).length === ${validatorKeyCount}`);
1499
- }
1500
- if (checks.length === 0) {
1501
- if (strictKeysGuard) {
1502
- return `${exportPrefix}const ${fnName} = (input: unknown): boolean => {
1503
- if (!isObject(input)) return false;${strictKeysGuard}
1673
+ checks.push(`Object.keys(${root}).length <= ${schema.maxProperties}`);
1674
+ return { checks, keyCount, walksKeys: validatorStripKeys && keyCount === null, propertyCount: propertyKeys.length };
1675
+ };
1676
+ const inlineNestedShapeCheck = (childSchema, childTypeName, local, useRefImports, suffix, stripUnknown, reservedNames, unknownKeys) => {
1677
+ const readRoot = `(${local} as Record<string, any>)`;
1678
+ const shape = shapeChecksOf(childSchema, childTypeName, readRoot, useRefImports, suffix, stripUnknown, reservedNames);
1679
+ if (shape === null || shape.walksKeys)
1680
+ return null;
1681
+ if (shape.propertyCount === 0 || shape.propertyCount > INLINE_NESTED_MAX_PROPERTIES)
1682
+ return null;
1683
+ const count = shape.keyCount === null ? null : keyCountCheck(unknownKeys, shape.keyCount, "", local);
1684
+ if (count !== null && count.statements.length > 0)
1685
+ return null;
1686
+ const parts = [
1687
+ `typeof ${local} === "object"`,
1688
+ `${local} !== null`,
1689
+ `!Array.isArray(${local})`,
1690
+ ...shape.checks,
1691
+ ...count === null ? [] : [count.verdict]
1692
+ ];
1693
+ return { check: `(${parts.join(" && ")})`, propertyCount: shape.propertyCount };
1694
+ };
1695
+ const renderShapeValidator = (exportPrefix, fnName, checks, keyCount, strictKeysGuard, unknownKeys) => {
1696
+ const head = `${exportPrefix}const ${fnName} = (input: unknown): boolean => {
1697
+ if (!isObject(input)) return false;${strictKeysGuard}`;
1698
+ const chain = checks.join("\n && ");
1699
+ if (keyCount === null) {
1700
+ if (checks.length === 0) {
1701
+ return strictKeysGuard ? `${head}
1504
1702
  return true;
1505
- };`;
1703
+ };` : `${exportPrefix}const ${fnName} = (input: unknown): boolean => isObject(input);`;
1506
1704
  }
1507
- return `${exportPrefix}const ${fnName} = (input: unknown): boolean => isObject(input);`;
1705
+ return `${head}
1706
+ return ${chain};
1707
+ };`;
1508
1708
  }
1509
- let body = checks[0];
1510
- for (let i = 1; i < checks.length; i++) {
1511
- body += "\n && " + checks[i];
1709
+ const count = keyCountCheck(unknownKeys, keyCount, " ");
1710
+ if (count.statements.length === 0) {
1711
+ return `${head}
1712
+ return ${checks.length === 0 ? count.verdict : `${chain}
1713
+ && ${count.verdict}`};
1714
+ };`;
1512
1715
  }
1513
- return `${exportPrefix}const ${fnName} = (input: unknown): boolean => {
1514
- if (!isObject(input)) return false;${strictKeysGuard}
1515
- return ${body};
1716
+ const gate = checks.length === 0 ? "" : `
1717
+ if (!(${chain})) return false;`;
1718
+ return `${head}${gate}
1719
+ ${count.statements.join("\n")}
1720
+ return ${count.verdict};
1516
1721
  };`;
1517
1722
  };
1518
1723
  export {