@amritk/generate-parsers 0.19.5 → 0.20.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -311,6 +311,15 @@ still — ~51M ops/s strict, ~127M safe — but at that size the numbers swing e
311
311
  run-to-run that the ratio, not the absolute, is the honest signal: mjst lands
312
312
  ~5–29× ahead of zod across every case above.
313
313
 
314
+ What is replicated upstream is the *cases* — the two parse modes and their
315
+ shapes — not the harness. These numbers come from this repo's own measurement
316
+ core (direct calls over a pool of distinct inputs, median of many trials) and
317
+ are not comparable with the public leaderboard, which measures every operation
318
+ through benny and reports something an order of magnitude smaller for the same
319
+ function. `@amritk/generate-validators` carries the side-by-side and a
320
+ reproducible run of both harnesses:
321
+ [Against the moltar harness](../generate-validators#against-the-moltar-harness).
322
+
314
323
  The trade is a one-shot **prepare** cost that only mjst pays — generating the
315
324
  parser source — which measures **~0.2–0.8 ms** per schema here (zod and TypeBox
316
325
  author or interpret their parsers with no separate build step, so there is
@@ -551,6 +551,54 @@ const exactKeyCountOf = (schema) => {
551
551
  const shouldCacheVariable = (propSchema) => {
552
552
  return isSchemaObject(propSchema);
553
553
  };
554
+ const INLINE_NESTED_MAX_PROPERTIES = 12;
555
+ const INLINE_NESTED_MAX_SITES = 4;
556
+ const INLINE_NESTED_MAX_TOTAL_PROPERTIES = 24;
557
+ const inlineNestedFastPath = (childSchema, childTypeName, objectExpr, useRefImports, suffix, stripUnknown, reservedNames) => {
558
+ if (!isSchemaObject(childSchema) || !hasProperties(childSchema))
559
+ return null;
560
+ if (!stripUnknown)
561
+ return null;
562
+ if (hasAdditionalProperties(childSchema) && childSchema.additionalProperties === false)
563
+ return null;
564
+ if (hasAllOf(childSchema) || hasOneOf(childSchema) || hasAnyOf(childSchema))
565
+ return null;
566
+ if (hasFastPathBlockingKeyword(childSchema))
567
+ return null;
568
+ if (hasMinProperties(childSchema) || hasMaxProperties(childSchema))
569
+ return null;
570
+ if (exactKeyCountOf(childSchema) === null)
571
+ return null;
572
+ const { objects, arrayItems } = collectInlineSubTypes(childSchema, childTypeName, reservedNames);
573
+ if (objects.size > 0 || arrayItems.size > 0)
574
+ return null;
575
+ const props = childSchema.properties;
576
+ const keys = Object.keys(props);
577
+ if (keys.length === 0 || keys.length > INLINE_NESTED_MAX_PROPERTIES)
578
+ return null;
579
+ const checks = [];
580
+ const fields = [];
581
+ const readRoot = `(${objectExpr} as Record<string, any>)`;
582
+ for (const key of keys) {
583
+ const propSchema = props[key];
584
+ if (shouldUseRefImport(propSchema, useRefImports) || shouldUseArrayRefImport(propSchema, useRefImports) || shouldUseRecordRefImport(propSchema, useRefImports)) {
585
+ return null;
586
+ }
587
+ const accessor = safeAccessor(readRoot, key);
588
+ const check = generatePropertyTypeCheck(accessor, propSchema, useRefImports, suffix);
589
+ if (check === null)
590
+ return null;
591
+ checks.push(check);
592
+ fields.push(`${safeLiteralKey(key)}: ${accessor}`);
593
+ }
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 };
601
+ };
554
602
  const generateObjectParser = (schema, typeName, useRefImports, suffix, logWarnings, strict, exported = true, stripUnknown = false, rootSchema, reservedNames = NO_RESERVED_NAMES, caseInsensitive = false, shapeValidatorSource) => {
555
603
  const functionName = generateParserName(typeName);
556
604
  const exportPrefix = exported ? "export " : "";
@@ -590,9 +638,13 @@ const generateObjectParser = (schema, typeName, useRefImports, suffix, logWarnin
590
638
  const stripKeys = strictKeys || stripUnknown;
591
639
  const rejectsUnknownKeys = strict && isSchemaObject(schema) && hasAdditionalProperties(schema) && schema.additionalProperties === false && !("patternProperties" in schema) || false;
592
640
  const guardKeys = stripKeys || rejectsUnknownKeys;
641
+ const fastPathBuildsDeclaredLiteral = stripKeys && !(isSchemaObject(schema) && hasAllOf(schema));
642
+ const fastPathNeedsKnownKeys = guardKeys && (rejectsUnknownKeys || !fastPathBuildsDeclaredLiteral);
643
+ const droppedKnownKeysTerm = guardKeys && !fastPathNeedsKnownKeys;
593
644
  const exactKeyCount = guardKeys ? exactKeyCountOf(schema) : null;
594
645
  const strictKeyCheck = unknownKeyCheck(propertyKeys, `_knownKeys${typeName}`);
595
- if (guardKeys && exactKeyCount === null) {
646
+ const shapeValidatorNeedsKnownKeys = shapeValidatorSource?.includes(`_hasOnlyKnownKeys${typeName}(`) ?? false;
647
+ if ((fastPathNeedsKnownKeys || shapeValidatorNeedsKnownKeys) && exactKeyCount === null) {
596
648
  for (const declaration of strictKeyCheck.declarations) {
597
649
  preamble.push(`${declaration};`);
598
650
  }
@@ -659,13 +711,13 @@ const generateObjectParser = (schema, typeName, useRefImports, suffix, logWarnin
659
711
  fastPathAccessorChecks.push(`Object.keys(input).length <= ${schema.maxProperties}`);
660
712
  }
661
713
  }
662
- if (guardKeys) {
714
+ if (fastPathNeedsKnownKeys) {
663
715
  fastPathChecks.push(exactKeyCount !== null ? `Object.getPrototypeOf(input) === Object.prototype && Object.keys(input).length === ${exactKeyCount}` : `_hasOnlyKnownKeys${typeName}(input)`);
664
716
  }
665
717
  const deepGuard = canFastPath && fastPathChecks.length > 0 ? fastPathChecks.join(" && ") : null;
666
718
  const literalReturnUsesVars = stripKeys && !(isSchemaObject(schema) && hasAllOf(schema)) && exported;
667
719
  let deepGuardExpr = deepGuard;
668
- if (deepGuard !== null && shapeValidatorSource !== void 0 && !literalReturnUsesVars) {
720
+ if (deepGuard !== null && shapeValidatorSource !== void 0 && !literalReturnUsesVars && !droppedKnownKeysTerm) {
669
721
  const shapeFnName = shapeValidatorName(typeName);
670
722
  const shapeStrictKeysGuard = stripKeys && exactKeyCount === null ? `
671
723
  if (!_hasOnlyKnownKeys${typeName}(input)) return false;` : "";
@@ -726,6 +778,8 @@ const generateObjectParser = (schema, typeName, useRefImports, suffix, logWarnin
726
778
  }
727
779
  const buildObjectLines = (directAssign) => {
728
780
  const objectLines = [];
781
+ let inlinedSites = 0;
782
+ let inlinedProperties = 0;
729
783
  if (!stripKeys) {
730
784
  objectLines.push(" ...input,");
731
785
  }
@@ -744,6 +798,15 @@ const generateObjectParser = (schema, typeName, useRefImports, suffix, logWarnin
744
798
  if (subName) {
745
799
  const subParserName = generateParserName(subName);
746
800
  if (isRequired) {
801
+ const inlined = directAssign && !logWarnings && inlinedSites < INLINE_NESTED_MAX_SITES ? inlineNestedFastPath(propSchema, subName, accessor, useRefImports, suffix, stripUnknown, reservedNames) : null;
802
+ if (inlined !== null && inlinedProperties + inlined.propertyCount <= INLINE_NESTED_MAX_TOTAL_PROPERTIES) {
803
+ inlinedSites++;
804
+ inlinedProperties += inlined.propertyCount;
805
+ objectLines.push(` ${safeLiteralKey(key)}: (${inlined.condition})
806
+ ? ${inlined.literal}
807
+ : ${subParserName}(${accessor}),`);
808
+ continue;
809
+ }
747
810
  objectLines.push(` ${safeLiteralKey(key)}: ${subParserName}(${accessor}),`);
748
811
  } else {
749
812
  objectLines.push(` ...(${accessor} !== undefined && { ${safeLiteralKey(key)}: ${subParserName}(${accessor}) }),`);
@@ -826,12 +889,11 @@ const generateObjectParser = (schema, typeName, useRefImports, suffix, logWarnin
826
889
  lines2.push(` } as unknown as ${typeName};`);
827
890
  };
828
891
  const emitDeepGuardReturn = (lines2) => {
829
- const canLiteral = stripKeys && !(isSchemaObject(schema) && hasAllOf(schema));
830
- if (!canLiteral) {
892
+ if (!fastPathBuildsDeclaredLiteral) {
831
893
  lines2.push(` if (${deepGuardExpr}) return { ...input } as ${typeName};`);
832
894
  return;
833
895
  }
834
- if (!exported) {
896
+ if (!exported && fastPathNeedsKnownKeys) {
835
897
  lines2.push(` if (${deepGuardExpr}) return input as ${typeName};`);
836
898
  return;
837
899
  }
@@ -868,7 +930,7 @@ const generateObjectParser = (schema, typeName, useRefImports, suffix, logWarnin
868
930
  ...rootSchema !== void 0 ? { rootSchema } : {}
869
931
  }).slice(1);
870
932
  if (shallowGuard) {
871
- if (!exported && deepGuard) {
933
+ if (!exported && deepGuard && fastPathNeedsKnownKeys) {
872
934
  const residual = [];
873
935
  for (let i = 0; i < fastPathChecks.length; i++) {
874
936
  const deep = fastPathChecks[i];
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@amritk/generate-parsers",
3
- "version": "0.19.5",
3
+ "version": "0.20.0",
4
4
  "description": "Generate TypeScript parsers and type definitions from JSON Schemas.",
5
5
  "main": "./dist/index.js",
6
6
  "module": "./dist/index.js",
@@ -56,7 +56,7 @@
56
56
  },
57
57
  "dependencies": {
58
58
  "json-schema-typed": "^8.0.1",
59
- "@amritk/helpers": "^0.16.0"
59
+ "@amritk/helpers": "^0.17.0"
60
60
  },
61
61
  "devDependencies": {
62
62
  "@amritk/generate-markdown": "^0.5.0",