@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
|
-
|
|
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 (
|
|
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
|
-
|
|
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.
|
|
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.
|
|
59
|
+
"@amritk/helpers": "^0.17.0"
|
|
60
60
|
},
|
|
61
61
|
"devDependencies": {
|
|
62
62
|
"@amritk/generate-markdown": "^0.5.0",
|