@amritk/generate-parsers 0.7.2 → 0.8.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 (73) hide show
  1. package/README.md +1 -1
  2. package/dist/generators/build-schema.d.ts +0 -1
  3. package/dist/generators/generate-default-from-pattern.d.ts +0 -1
  4. package/dist/generators/generate-discriminated-union-validation.d.ts +0 -1
  5. package/dist/generators/generate-enum-check.d.ts +0 -1
  6. package/dist/generators/generate-files.d.ts +0 -1
  7. package/dist/generators/generate-non-discriminated-union-validation.d.ts +0 -1
  8. package/dist/generators/generate-parser-function.d.ts +3 -3
  9. package/dist/generators/generate-parser-function.js +164 -29
  10. package/dist/generators/generate-schema-checks.d.ts +0 -1
  11. package/dist/generators/generate-strict-assertion.d.ts +0 -1
  12. package/dist/generators/generate-validation-expression.d.ts +0 -1
  13. package/dist/helpers/apply-schema-extensions.d.ts +0 -1
  14. package/dist/helpers/collect-helpers.d.ts +0 -1
  15. package/dist/helpers/collect-imports.d.ts +0 -1
  16. package/dist/helpers/find-discriminator.d.ts +0 -1
  17. package/dist/helpers/get-default-value.d.ts +0 -1
  18. package/dist/helpers/get-discriminator-value.d.ts +0 -1
  19. package/dist/index.d.ts +0 -1
  20. package/dist/types/schema-extensions.d.ts +0 -1
  21. package/package.json +3 -5
  22. package/dist/generators/build-schema.d.ts.map +0 -1
  23. package/dist/generators/generate-default-from-pattern.d.ts.map +0 -1
  24. package/dist/generators/generate-discriminated-union-validation.d.ts.map +0 -1
  25. package/dist/generators/generate-enum-check.d.ts.map +0 -1
  26. package/dist/generators/generate-files.d.ts.map +0 -1
  27. package/dist/generators/generate-non-discriminated-union-validation.d.ts.map +0 -1
  28. package/dist/generators/generate-parser-function.d.ts.map +0 -1
  29. package/dist/generators/generate-schema-checks.d.ts.map +0 -1
  30. package/dist/generators/generate-strict-assertion.d.ts.map +0 -1
  31. package/dist/generators/generate-validation-expression.d.ts.map +0 -1
  32. package/dist/helpers/apply-schema-extensions.d.ts.map +0 -1
  33. package/dist/helpers/collect-helpers.d.ts.map +0 -1
  34. package/dist/helpers/collect-imports.d.ts.map +0 -1
  35. package/dist/helpers/find-discriminator.d.ts.map +0 -1
  36. package/dist/helpers/get-default-value.d.ts.map +0 -1
  37. package/dist/helpers/get-discriminator-value.d.ts.map +0 -1
  38. package/dist/index.d.ts.map +0 -1
  39. package/dist/types/schema-extensions.d.ts.map +0 -1
  40. package/src/generators/build-schema.test.ts +0 -854
  41. package/src/generators/build-schema.ts +0 -147
  42. package/src/generators/generate-default-from-pattern.test.ts +0 -81
  43. package/src/generators/generate-default-from-pattern.ts +0 -39
  44. package/src/generators/generate-discriminated-union-validation.test.ts +0 -84
  45. package/src/generators/generate-discriminated-union-validation.ts +0 -54
  46. package/src/generators/generate-enum-check.test.ts +0 -45
  47. package/src/generators/generate-enum-check.ts +0 -8
  48. package/src/generators/generate-files.test.ts +0 -448
  49. package/src/generators/generate-files.ts +0 -155
  50. package/src/generators/generate-non-discriminated-union-validation.test.ts +0 -74
  51. package/src/generators/generate-non-discriminated-union-validation.ts +0 -40
  52. package/src/generators/generate-parser-function.test.ts +0 -1794
  53. package/src/generators/generate-parser-function.ts +0 -1340
  54. package/src/generators/generate-schema-checks.test.ts +0 -330
  55. package/src/generators/generate-schema-checks.ts +0 -128
  56. package/src/generators/generate-strict-assertion.test.ts +0 -67
  57. package/src/generators/generate-strict-assertion.ts +0 -243
  58. package/src/generators/generate-validation-expression.test.ts +0 -831
  59. package/src/generators/generate-validation-expression.ts +0 -327
  60. package/src/helpers/apply-schema-extensions.test.ts +0 -194
  61. package/src/helpers/apply-schema-extensions.ts +0 -63
  62. package/src/helpers/collect-helpers.test.ts +0 -115
  63. package/src/helpers/collect-helpers.ts +0 -79
  64. package/src/helpers/collect-imports.test.ts +0 -547
  65. package/src/helpers/collect-imports.ts +0 -275
  66. package/src/helpers/find-discriminator.test.ts +0 -88
  67. package/src/helpers/find-discriminator.ts +0 -39
  68. package/src/helpers/get-default-value.test.ts +0 -139
  69. package/src/helpers/get-default-value.ts +0 -95
  70. package/src/helpers/get-discriminator-value.test.ts +0 -83
  71. package/src/helpers/get-discriminator-value.ts +0 -21
  72. package/src/index.ts +0 -1
  73. package/src/types/schema-extensions.ts +0 -35
package/README.md CHANGED
@@ -132,7 +132,7 @@ Returns: `Promise<GeneratedFile[]>`.
132
132
  <td align="center"><code>false</code></td>
133
133
  </tr>
134
134
  <tr>
135
- <td colspan="5">Generate parsers that throw on type/shape mismatches (wrong type, missing required property, enum/pattern/min/max violations) instead of coercing invalid input to default values. Unknown extra keys are still allowed.</td>
135
+ <td colspan="5">Generate parsers that throw on type/shape mismatches (wrong type, missing required property, enum/pattern/min/max violations) instead of coercing invalid input to default values. When a schema sets additionalProperties: false, undeclared keys throw too; otherwise they are still allowed.</td>
136
136
  </tr>
137
137
  </tbody>
138
138
  </table>
@@ -74,4 +74,3 @@ export type GeneratedFile = {
74
74
  * ```
75
75
  */
76
76
  export declare const buildSchema: (rootSchema: JSONSchema, rootTypeName: string, extensions?: SchemaExtensions, typesOnly?: boolean, logWarnings?: boolean, strict?: boolean, helpersMode?: HelpersMode, helpersImportPrefix?: string, readonly?: boolean, typeSuffix?: string) => Promise<GeneratedFile[]>;
77
- //# sourceMappingURL=build-schema.d.ts.map
@@ -3,4 +3,3 @@
3
3
  * Recognizes common patterns and returns appropriate defaults.
4
4
  */
5
5
  export declare const generateDefaultFromPattern: (pattern: string) => string | null;
6
- //# sourceMappingURL=generate-default-from-pattern.d.ts.map
@@ -4,4 +4,3 @@ import type { JSONSchema } from 'json-schema-typed/draft-2020-12';
4
4
  * Uses a discriminator property to efficiently choose the correct schema.
5
5
  */
6
6
  export declare const generateDiscriminatedUnionValidation: (accessor: string, schemas: readonly JSONSchema[], discriminatorKey: string, defaultValue: string, isRequired: boolean) => string;
7
- //# sourceMappingURL=generate-discriminated-union-validation.d.ts.map
@@ -3,4 +3,3 @@
3
3
  * Returns a string that checks if the value is in the allowed enum values.
4
4
  */
5
5
  export declare const generateEnumCheck: (accessor: string, enumValues: readonly unknown[]) => string;
6
- //# sourceMappingURL=generate-enum-check.d.ts.map
@@ -91,4 +91,3 @@ export type GeneratedFileContent = {
91
91
  */
92
92
  export declare const generateFile: (schema: JSONSchema, typeName: string, options?: GenerateFileOptions) => GeneratedFileContent;
93
93
  export {};
94
- //# sourceMappingURL=generate-files.d.ts.map
@@ -4,4 +4,3 @@ import type { JSONSchema } from 'json-schema-typed/draft-2020-12';
4
4
  * Tries each schema in order until one matches.
5
5
  */
6
6
  export declare const generateNonDiscriminatedUnionValidation: (accessor: string, schemas: readonly JSONSchema[], defaultValue: string, isRequired: boolean) => string;
7
- //# sourceMappingURL=generate-non-discriminated-union-validation.d.ts.map
@@ -17,7 +17,8 @@ type GenerateParserOptions = {
17
17
  /**
18
18
  * When true, the generated parser throws on type/shape mismatches instead
19
19
  * of coercing invalid input to default values. Throws on the first violation
20
- * with a path-aware Error. Unknown extra keys are still allowed.
20
+ * with a path-aware Error. When the schema sets `additionalProperties: false`,
21
+ * undeclared keys throw too; otherwise they are still allowed.
21
22
  */
22
23
  readonly strict?: boolean;
23
24
  /**
@@ -54,6 +55,5 @@ export declare const shapeValidatorFunctionName: (typeName: string) => string;
54
55
  * in additionalProperties). The stub is safe: parents calling it will fall
55
56
  * through to the slow path, matching the pre-deep-fast-path behavior.
56
57
  */
57
- export declare const generateShapeValidator: (schema: JSONSchema, typeName: string, useRefImports: boolean, suffix?: string) => string;
58
+ export declare const generateShapeValidator: (schema: JSONSchema, typeName: string, useRefImports: boolean, suffix?: string, exported?: boolean) => string;
58
59
  export {};
59
- //# sourceMappingURL=generate-parser-function.d.ts.map
@@ -1,7 +1,7 @@
1
1
  import { escapeRegexPattern } from '@amritk/helpers/escape-regex-pattern';
2
2
  import { refToName } from '@amritk/helpers/ref-to-name';
3
3
  import { safeAccessor, safeKey } from '@amritk/helpers/safe-accessor';
4
- import { hasAllOf, hasAnyOf, hasConst, hasDefault, hasEnum, hasExamples, hasExclusiveMaximum, hasExclusiveMinimum, hasItems, hasMaxItems, hasMaximum, hasMaxLength, hasMinItems, hasMinimum, hasMinLength, hasMultipleOf, hasOneOf, hasPattern, hasProperties, hasRef, hasType, hasUniqueItems, isObjectSchema, isSchemaObject, } from '@amritk/helpers/schema-guards';
4
+ import { hasAdditionalProperties, hasAllOf, hasAnyOf, hasConst, hasDefault, hasEnum, hasExamples, hasExclusiveMaximum, hasExclusiveMinimum, hasItems, hasMaxItems, hasMaximum, hasMaxLength, hasMinItems, hasMinimum, hasMinLength, hasMultipleOf, hasOneOf, hasPattern, hasProperties, hasRef, hasType, hasUniqueItems, isObjectSchema, isSchemaObject, } from '@amritk/helpers/schema-guards';
5
5
  import { getDefaultValue } from '../helpers/get-default-value.js';
6
6
  import { generateObjectStrictAssertion, generateScalarStrictAssertion } from './generate-strict-assertion.js';
7
7
  import { generateValidationExpression } from './generate-validation-expression.js';
@@ -252,7 +252,7 @@ const generateFallbackValue = (_, propSchema, useRefImports, suffix) => {
252
252
  * Generates a fallback object with required properties filled with default values.
253
253
  * This is used when input is not an object (undefined, null, etc.).
254
254
  */
255
- const generateFallbackObject = (schema, useRefImports, typeName, suffix) => {
255
+ const generateFallbackObject = (schema, useRefImports, typeName, suffix, subTypeNames) => {
256
256
  if (!hasProperties(schema)) {
257
257
  return `{} as ${typeName}`;
258
258
  }
@@ -266,7 +266,12 @@ const generateFallbackObject = (schema, useRefImports, typeName, suffix) => {
266
266
  for (const [key, propSchema] of Object.entries(schema.properties)) {
267
267
  const isRequired = isPropertyRequired(key, schema);
268
268
  if (isRequired) {
269
- const fallbackValue = generateFallbackValue(key, propSchema, useRefImports, suffix);
269
+ // Inline nested objects delegate to their sub-parser so the fallback
270
+ // carries proper deep defaults instead of an empty object literal.
271
+ const subName = subTypeNames?.get(key);
272
+ const fallbackValue = subName
273
+ ? `${generateParserName(subName)}(undefined)`
274
+ : generateFallbackValue(key, propSchema, useRefImports, suffix);
270
275
  if (fallbackValue === 'undefined') {
271
276
  return `{} as ${typeName}`;
272
277
  }
@@ -344,6 +349,73 @@ const toVarName = (key) => {
344
349
  * whether `input` is already in the shape produced by the parser's fast path.
345
350
  */
346
351
  const shapeValidatorName = (typeName) => `validate${typeName}Shape`;
352
+ /**
353
+ * Matches an inline nested object property — an object schema written directly
354
+ * under `properties` with its own `properties`, rather than referenced via
355
+ * `$ref`. These get a private sub-parser (and shape predicate) in the same
356
+ * generated file so their fields are actually parsed; anything involving
357
+ * composition, conditionals, enums, or record semantics keeps the existing
358
+ * code paths.
359
+ */
360
+ const isInlineObjectProperty = (propSchema) => {
361
+ if (!isSchemaObject(propSchema))
362
+ return false;
363
+ if (hasRef(propSchema) || hasEnum(propSchema) || hasConst(propSchema))
364
+ return false;
365
+ if (hasOneOf(propSchema) || hasAnyOf(propSchema) || hasAllOf(propSchema))
366
+ return false;
367
+ if ('patternProperties' in propSchema || 'not' in propSchema || 'if' in propSchema)
368
+ return false;
369
+ // additionalProperties-as-schema records go through the record paths instead
370
+ if (hasAdditionalProperties(propSchema) && typeof propSchema.additionalProperties !== 'boolean')
371
+ return false;
372
+ return isObjectSchema(propSchema) && hasProperties(propSchema);
373
+ };
374
+ /**
375
+ * Builds the property-key → synthesized-type-name map for every inline nested
376
+ * object property of a schema, e.g. parent `Order` + key `shipTo` →
377
+ * `OrderShipTo`. Both the parser and the shape validator derive the map
378
+ * independently, so the naming (including collision suffixes) is a pure
379
+ * function of the schema and parent type name.
380
+ */
381
+ const collectInlineObjectProperties = (schema, typeName) => {
382
+ const subTypeNames = new Map();
383
+ if (!hasProperties(schema))
384
+ return subTypeNames;
385
+ const used = new Set();
386
+ for (const [key, propSchema] of Object.entries(schema.properties)) {
387
+ if (!isInlineObjectProperty(propSchema))
388
+ continue;
389
+ const pascal = key
390
+ .replace(/[^a-zA-Z0-9_$]/g, '_')
391
+ .split('_')
392
+ .filter((part) => part.length > 0)
393
+ .map((part) => part.charAt(0).toUpperCase() + part.slice(1))
394
+ .join('');
395
+ let subName = `${typeName}${pascal || 'Value'}`;
396
+ while (used.has(subName))
397
+ subName = `${subName}_`;
398
+ used.add(subName);
399
+ subTypeNames.set(key, subName);
400
+ }
401
+ return subTypeNames;
402
+ };
403
+ /**
404
+ * True when the parser must reject (strict mode) or strip (coerce mode) every
405
+ * undeclared key: the schema sets `additionalProperties: false` and nothing
406
+ * else (key patterns, composition) can legitimately introduce extra keys.
407
+ */
408
+ const hasStrictKeys = (schema) => {
409
+ if (!isSchemaObject(schema) || !hasProperties(schema))
410
+ return false;
411
+ if (!hasAdditionalProperties(schema) || schema.additionalProperties !== false)
412
+ return false;
413
+ if ('patternProperties' in schema)
414
+ return false;
415
+ if (hasAllOf(schema) || hasOneOf(schema) || hasAnyOf(schema))
416
+ return false;
417
+ return true;
418
+ };
347
419
  /**
348
420
  * Generates a fast-path type check expression for a property.
349
421
  * Returns null if the schema is too complex for a simple type check
@@ -457,22 +529,48 @@ const shouldCacheVariable = (propSchema, _canFastPath, _useRefImports) => {
457
529
  * - Fast path that returns input directly when all properties are already valid
458
530
  * - Slow path with optimized expressions
459
531
  */
460
- const generateObjectParser = (schema, typeName, useRefImports, suffix, logWarnings, strict) => {
532
+ const generateObjectParser = (schema, typeName, useRefImports, suffix, logWarnings, strict, exported = true) => {
461
533
  const functionName = generateParserName(typeName);
534
+ const exportPrefix = exported ? 'export ' : '';
462
535
  if (!hasProperties(schema)) {
463
536
  if (strict) {
464
- return `export const ${functionName} = (input: unknown): ${typeName} => {\n if (!isObject(input)) throw new Error(\`[${typeName}] expected object, got \${input === null ? "null" : typeof input}\`);\n return input as ${typeName};\n};`;
537
+ return `${exportPrefix}const ${functionName} = (input: unknown): ${typeName} => {\n if (!isObject(input)) throw new Error(\`[${typeName}] expected object, got \${input === null ? "null" : typeof input}\`);\n return input as ${typeName};\n};`;
465
538
  }
466
- return `export const ${functionName} = (input: unknown): ${typeName} => isObject(input) ? input as ${typeName} : {} as ${typeName};`;
539
+ return `${exportPrefix}const ${functionName} = (input: unknown): ${typeName} => isObject(input) ? input as ${typeName} : {} as ${typeName};`;
467
540
  }
468
- const fallbackObject = generateFallbackObject(schema, useRefImports, typeName, suffix);
469
541
  const properties = Object.entries(schema.properties);
542
+ // Inline nested object properties get a private sub-parser (plus shape
543
+ // predicate and type alias) in the same file, so their fields are parsed for
544
+ // real instead of only passing an isObject check. The sub-parser recurses
545
+ // through this same generator, so nesting works to any depth.
546
+ const subTypeNames = collectInlineObjectProperties(schema, typeName);
547
+ const preamble = [];
548
+ for (const [key, subName] of subTypeNames) {
549
+ const propSchema = schema.properties[key];
550
+ preamble.push(`type ${subName} = ${typeName}[${JSON.stringify(key)}];`);
551
+ preamble.push(generateShapeValidator(propSchema, subName, useRefImports, suffix, false));
552
+ preamble.push(generateObjectParser(propSchema, subName, useRefImports, suffix, logWarnings, strict, false));
553
+ }
554
+ // additionalProperties: false — hoist the declared-keys Set so the per-call
555
+ // sweep is one Set lookup per key. The predicate form is shared by the fast
556
+ // path and the shape validator; strict mode throws with the offending key.
557
+ const strictKeys = hasStrictKeys(schema);
558
+ if (strictKeys) {
559
+ preamble.push(`const _knownKeys${typeName} = new Set(${JSON.stringify(properties.map(([key]) => key))});`);
560
+ preamble.push(`const _hasOnlyKnownKeys${typeName} = (input: Record<string, unknown>): boolean => {\n for (const _k in input) if (!_knownKeys${typeName}.has(_k)) return false;\n return true;\n};`);
561
+ }
562
+ const fallbackObject = generateFallbackObject(schema, useRefImports, typeName, suffix, subTypeNames);
470
563
  const lines = [];
471
- lines.push(`export const ${functionName} = (input: unknown): ${typeName} => {`);
564
+ lines.push(`${exportPrefix}const ${functionName} = (input: unknown): ${typeName} => {`);
472
565
  if (strict) {
473
566
  for (const assertionLine of generateObjectStrictAssertion(schema, typeName)) {
474
567
  lines.push(assertionLine);
475
568
  }
569
+ if (strictKeys) {
570
+ lines.push(` for (const _k in input) {`);
571
+ lines.push(` if (!_knownKeys${typeName}.has(_k)) throw new Error(\`[${typeName}] unknown property "\${_k}"\`);`);
572
+ lines.push(` }`);
573
+ }
476
574
  }
477
575
  else {
478
576
  lines.push(` if (!isObject(input)) return ${fallbackObject};`);
@@ -499,7 +597,12 @@ const generateObjectParser = (schema, typeName, useRefImports, suffix, logWarnin
499
597
  canFastPath = false;
500
598
  }
501
599
  else {
502
- const check = generatePropertyTypeCheck(varName, propSchema, useRefImports, suffix);
600
+ const subName = subTypeNames.get(key);
601
+ // Inline nested objects fast-path through their private shape
602
+ // predicate, the same way $ref properties use the imported one.
603
+ const check = subName
604
+ ? `${shapeValidatorName(subName)}(${varName})`
605
+ : generatePropertyTypeCheck(varName, propSchema, useRefImports, suffix);
503
606
  if (check === null) {
504
607
  canFastPath = false;
505
608
  }
@@ -514,6 +617,12 @@ const generateObjectParser = (schema, typeName, useRefImports, suffix, logWarnin
514
617
  }
515
618
  }
516
619
  }
620
+ // The fast path returns `{ ...input }`, so with additionalProperties: false
621
+ // it must only fire when no undeclared keys are present. Strict mode already
622
+ // threw on them above.
623
+ if (strictKeys && !strict) {
624
+ fastPathChecks.push(`_hasOnlyKnownKeys${typeName}(input)`);
625
+ }
517
626
  // Second pass: generate variable declarations only for properties that need them
518
627
  for (const { key, varName, propSchema } of propInfo) {
519
628
  if (shouldCacheVariable(propSchema, canFastPath, useRefImports)) {
@@ -538,9 +647,13 @@ const generateObjectParser = (schema, typeName, useRefImports, suffix, logWarnin
538
647
  }
539
648
  lines.push(` if (${fastPathExpr}) return { ...input } as ${typeName};`);
540
649
  }
541
- // Generate slow-path object construction
650
+ // Generate slow-path object construction. The input spread preserves
651
+ // undeclared keys, so it is dropped when additionalProperties: false —
652
+ // building from the declared properties alone strips them from the result.
542
653
  const objectLines = [];
543
- objectLines.push(' ...input,');
654
+ if (!strictKeys) {
655
+ objectLines.push(' ...input,');
656
+ }
544
657
  // Spread allOf $ref parsers so their field coercions are applied before
545
658
  // the explicit property validations below.
546
659
  if (useRefImports && isSchemaObject(schema) && hasAllOf(schema)) {
@@ -554,6 +667,19 @@ const generateObjectParser = (schema, typeName, useRefImports, suffix, logWarnin
554
667
  for (const { key, varName, isRequired, propSchema } of propInfo) {
555
668
  const shouldCache = shouldCacheVariable(propSchema, canFastPath, useRefImports);
556
669
  const accessor = shouldCache ? varName : safeAccessor('input', key);
670
+ // Handle inline nested objects via their private sub-parser, mirroring
671
+ // how $ref properties delegate to the imported parser.
672
+ const subName = subTypeNames.get(key);
673
+ if (subName) {
674
+ const subParserName = generateParserName(subName);
675
+ if (isRequired) {
676
+ objectLines.push(` ${safeKey(key)}: ${subParserName}(${accessor}),`);
677
+ }
678
+ else {
679
+ objectLines.push(` ...(${accessor} !== undefined && { ${safeKey(key)}: ${subParserName}(${accessor}) }),`);
680
+ }
681
+ continue;
682
+ }
557
683
  // Handle direct $ref properties via imported parsers
558
684
  if (shouldUseRefImport(propSchema, useRefImports)) {
559
685
  const ref = propSchema.$ref;
@@ -613,20 +739,13 @@ const generateObjectParser = (schema, typeName, useRefImports, suffix, logWarnin
613
739
  }
614
740
  }
615
741
  lines.push(` return {`);
616
- // objectLines[0] is safe: objectLines is always non-empty (has at least the closing brace line)
617
- let objectBody = objectLines[0];
618
- for (let i = 1; i < objectLines.length; i++) {
619
- objectBody += '\n' + objectLines[i];
620
- }
621
- lines.push(objectBody);
742
+ lines.push(objectLines.join('\n'));
622
743
  lines.push(` } as unknown as ${typeName};`);
623
744
  lines.push(`}`);
624
- // lines[0] is safe: lines is always non-empty (it always has the function declaration)
625
- let result = lines[0];
626
- for (let i = 1; i < lines.length; i++) {
627
- result += '\n' + lines[i];
628
- }
629
- return result;
745
+ const fn = lines.join('\n');
746
+ if (preamble.length === 0)
747
+ return fn;
748
+ return `${preamble.join('\n\n')}\n\n${fn}`;
630
749
  };
631
750
  /**
632
751
  * Generates a parser for schemas that have both properties AND patternProperties.
@@ -1011,9 +1130,10 @@ export const shapeValidatorFunctionName = (typeName) => shapeValidatorName(typeN
1011
1130
  * in additionalProperties). The stub is safe: parents calling it will fall
1012
1131
  * through to the slow path, matching the pre-deep-fast-path behavior.
1013
1132
  */
1014
- export const generateShapeValidator = (schema, typeName, useRefImports, suffix = '') => {
1133
+ export const generateShapeValidator = (schema, typeName, useRefImports, suffix = '', exported = true) => {
1015
1134
  const fnName = shapeValidatorName(typeName);
1016
- const stub = `export const ${fnName} = (_input: unknown): boolean => false;`;
1135
+ const exportPrefix = exported ? 'export ' : '';
1136
+ const stub = `${exportPrefix}const ${fnName} = (_input: unknown): boolean => false;`;
1017
1137
  if (!isSchemaObject(schema))
1018
1138
  return stub;
1019
1139
  if (!hasProperties(schema))
@@ -1030,6 +1150,12 @@ export const generateShapeValidator = (schema, typeName, useRefImports, suffix =
1030
1150
  return stub;
1031
1151
  }
1032
1152
  const properties = Object.entries(schema.properties);
1153
+ // Same deterministic naming as the parser's sub-parser generation, so the
1154
+ // referenced private shape predicates exist in the same file.
1155
+ const subTypeNames = collectInlineObjectProperties(schema, typeName);
1156
+ // With additionalProperties: false the shape only matches when every key is
1157
+ // declared, so the parser's fast path will not smuggle extras through.
1158
+ const strictKeysGuard = hasStrictKeys(schema) ? `\n if (!_hasOnlyKnownKeys${typeName}(input)) return false;` : '';
1033
1159
  const checks = [];
1034
1160
  for (const [key, propSchema] of properties) {
1035
1161
  // Records of refs require iterating every value — too expensive for the fast path.
@@ -1038,7 +1164,10 @@ export const generateShapeValidator = (schema, typeName, useRefImports, suffix =
1038
1164
  }
1039
1165
  const accessor = safeAccessor('input', key);
1040
1166
  const isRequired = isPropertyRequired(key, schema);
1041
- const check = generatePropertyTypeCheck(accessor, propSchema, useRefImports, suffix);
1167
+ const subName = subTypeNames.get(key);
1168
+ const check = subName
1169
+ ? `${shapeValidatorName(subName)}(${accessor})`
1170
+ : generatePropertyTypeCheck(accessor, propSchema, useRefImports, suffix);
1042
1171
  if (check === null) {
1043
1172
  return stub;
1044
1173
  }
@@ -1050,14 +1179,20 @@ export const generateShapeValidator = (schema, typeName, useRefImports, suffix =
1050
1179
  }
1051
1180
  }
1052
1181
  if (checks.length === 0) {
1053
- return `export const ${fnName} = (input: unknown): boolean => isObject(input);`;
1182
+ if (strictKeysGuard) {
1183
+ return `${exportPrefix}const ${fnName} = (input: unknown): boolean => {
1184
+ if (!isObject(input)) return false;${strictKeysGuard}
1185
+ return true;
1186
+ };`;
1187
+ }
1188
+ return `${exportPrefix}const ${fnName} = (input: unknown): boolean => isObject(input);`;
1054
1189
  }
1055
1190
  let body = checks[0];
1056
1191
  for (let i = 1; i < checks.length; i++) {
1057
1192
  body += '\n && ' + checks[i];
1058
1193
  }
1059
- return `export const ${fnName} = (input: unknown): boolean => {
1060
- if (!isObject(input)) return false;
1194
+ return `${exportPrefix}const ${fnName} = (input: unknown): boolean => {
1195
+ if (!isObject(input)) return false;${strictKeysGuard}
1061
1196
  return ${body};
1062
1197
  };`;
1063
1198
  };
@@ -3,4 +3,3 @@ import type { JSONSchema } from 'json-schema-typed/draft-2020-12';
3
3
  * Generates validation checks for a single schema (used in union validation).
4
4
  */
5
5
  export declare const generateSchemaChecks: (accessor: string, schema: JSONSchema) => string[];
6
- //# sourceMappingURL=generate-schema-checks.d.ts.map
@@ -16,4 +16,3 @@ export declare const generateObjectStrictAssertion: (schema: JSONSchema, typeNam
16
16
  * Returns null when the schema has no type information to assert on.
17
17
  */
18
18
  export declare const generateScalarStrictAssertion: (schema: JSONSchema, typeName: string) => string | null;
19
- //# sourceMappingURL=generate-strict-assertion.d.ts.map
@@ -5,4 +5,3 @@ import type { JSONSchema } from 'json-schema-typed/draft-2020-12';
5
5
  * Supports type checking, pattern validation, enum validation, union types, and $ref resolution.
6
6
  */
7
7
  export declare const generateValidationExpression: (key: string, schema: JSONSchema, defaultValue: string, isRequired: boolean, rootSchema?: Record<string, unknown>, visitedRefs?: Set<string>, accessorOverride?: string, knownNotUndefined?: boolean) => string;
8
- //# sourceMappingURL=generate-validation-expression.d.ts.map
@@ -33,4 +33,3 @@ import type { SchemaExtensions } from '../types/schema-extensions.js';
33
33
  * ```
34
34
  */
35
35
  export declare const applySchemaExtensions: (schema: JSONSchema, definitionName: string, extensions: SchemaExtensions) => JSONSchema;
36
- //# sourceMappingURL=apply-schema-extensions.d.ts.map
@@ -21,4 +21,3 @@ export type CollectedHelpers = {
21
21
  * `_helpers/` directory at the output root.
22
22
  */
23
23
  export declare const collectHelpers: (parserFunction: string, mode: HelpersMode, helpersImportPrefix?: string) => CollectedHelpers;
24
- //# sourceMappingURL=collect-helpers.d.ts.map
@@ -68,4 +68,3 @@ type CollectImportsOptions = {
68
68
  */
69
69
  export declare const collectImports: (schema: JSONSchema, options?: CollectImportsOptions) => string[];
70
70
  export {};
71
- //# sourceMappingURL=collect-imports.d.ts.map
@@ -4,4 +4,3 @@ import type { JSONSchema } from 'json-schema-typed/draft-2020-12';
4
4
  * Returns the property name if a suitable discriminator is found, otherwise null.
5
5
  */
6
6
  export declare const findDiscriminator: (schemas: readonly JSONSchema[]) => string | null;
7
- //# sourceMappingURL=find-discriminator.d.ts.map
@@ -5,4 +5,3 @@ import type { JSONSchema } from 'json-schema-typed/draft-2020-12';
5
5
  * These defaults ensure that parsing never fails, even with missing data.
6
6
  */
7
7
  export declare const getDefaultValue: (schema: JSONSchema) => string;
8
- //# sourceMappingURL=get-default-value.d.ts.map
@@ -4,4 +4,3 @@ import type { JSONSchema } from 'json-schema-typed/draft-2020-12';
4
4
  * Returns the const value or single enum value if available.
5
5
  */
6
6
  export declare const getDiscriminatorValue: (schema: JSONSchema, discriminatorKey: string) => unknown | null;
7
- //# sourceMappingURL=get-discriminator-value.d.ts.map
package/dist/index.d.ts CHANGED
@@ -1,2 +1 @@
1
1
  export { buildSchema, type GeneratedFile } from './generators/build-schema.js';
2
- //# sourceMappingURL=index.d.ts.map
@@ -32,4 +32,3 @@ import type { JSONSchema } from 'json-schema-typed/draft-2020-12';
32
32
  * ```
33
33
  */
34
34
  export type SchemaExtensions = Record<string, Record<string, JSONSchema>>;
35
- //# sourceMappingURL=schema-extensions.d.ts.map
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@amritk/generate-parsers",
3
- "version": "0.7.2",
3
+ "version": "0.8.0",
4
4
  "description": "Generate TypeScript parsers and type definitions from JSON Schemas.",
5
5
  "module": "./dist/index.js",
6
6
  "type": "module",
@@ -23,8 +23,7 @@
23
23
  "url": "https://github.com/amritk/mjst/issues"
24
24
  },
25
25
  "files": [
26
- "dist",
27
- "src"
26
+ "dist"
28
27
  ],
29
28
  "publishConfig": {
30
29
  "access": "public"
@@ -42,14 +41,13 @@
42
41
  },
43
42
  "exports": {
44
43
  ".": {
45
- "development": "./src/index.ts",
46
44
  "default": "./dist/index.js",
47
45
  "types": "./dist/index.d.ts"
48
46
  }
49
47
  },
50
48
  "dependencies": {
51
49
  "json-schema-typed": "^8.0.1",
52
- "@amritk/helpers": "0.8.0",
50
+ "@amritk/helpers": "0.9.0",
53
51
  "@amritk/generate-markdown": "0.2.4"
54
52
  }
55
53
  }
@@ -1 +0,0 @@
1
- {"version":3,"file":"build-schema.d.ts","sourceRoot":"","sources":["../../src/generators/build-schema.ts"],"names":[],"mappings":"AAKA,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,iCAAiC,CAAA;AAEjE,OAAO,KAAK,EAAE,WAAW,EAAqB,MAAM,0BAA0B,CAAA;AAC9E,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,0BAA0B,CAAA;AAahE;;GAEG;AACH,MAAM,MAAM,aAAa,GAAG;IAC1B,QAAQ,EAAE,MAAM,CAAA;IAChB,OAAO,EAAE,MAAM,CAAA;CAChB,CAAA;AAED;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAgEG;AACH,eAAO,MAAM,WAAW,eACV,UAAU,gBACR,MAAM,eACP,gBAAgB,cACjB,OAAO,gBACL,OAAO,WACZ,OAAO,gBACH,WAAW,4EAIvB,OAAO,CAAC,aAAa,EAAE,CAyCzB,CAAA"}
@@ -1 +0,0 @@
1
- {"version":3,"file":"generate-default-from-pattern.d.ts","sourceRoot":"","sources":["../../src/generators/generate-default-from-pattern.ts"],"names":[],"mappings":"AAAA;;;GAGG;AACH,eAAO,MAAM,0BAA0B,YAAa,MAAM,KAAG,MAAM,GAAG,IAkCrE,CAAA"}
@@ -1 +0,0 @@
1
- {"version":3,"file":"generate-discriminated-union-validation.d.ts","sourceRoot":"","sources":["../../src/generators/generate-discriminated-union-validation.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,iCAAiC,CAAA;AAMjE;;;GAGG;AACH,eAAO,MAAM,oCAAoC,aACrC,MAAM,WACP,SAAS,UAAU,EAAE,oBACZ,MAAM,gBACV,MAAM,cACR,OAAO,KAClB,MAqCF,CAAA"}
@@ -1 +0,0 @@
1
- {"version":3,"file":"generate-enum-check.d.ts","sourceRoot":"","sources":["../../src/generators/generate-enum-check.ts"],"names":[],"mappings":"AAAA;;;GAGG;AACH,eAAO,MAAM,iBAAiB,aAAc,MAAM,cAAc,SAAS,OAAO,EAAE,KAAG,MAGpF,CAAA"}
@@ -1 +0,0 @@
1
- {"version":3,"file":"generate-files.d.ts","sourceRoot":"","sources":["../../src/generators/generate-files.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,iCAAiC,CAAA;AACjE,OAAO,EAGL,KAAK,WAAW,EAChB,KAAK,iBAAiB,EACvB,MAAM,0BAA0B,CAAA;AAKjC;;GAEG;AACH,KAAK,mBAAmB,GAAG;IACzB;;;OAGG;IACH,QAAQ,CAAC,SAAS,CAAC,EAAE,OAAO,CAAA;IAC5B;;;;OAIG;IACH,QAAQ,CAAC,OAAO,CAAC,EAAE,MAAM,CAAA;IACzB;;;OAGG;IACH,QAAQ,CAAC,UAAU,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAA;IAC7C;;;OAGG;IACH,QAAQ,CAAC,WAAW,CAAC,EAAE,OAAO,CAAA;IAC9B;;;OAGG;IACH,QAAQ,CAAC,MAAM,CAAC,EAAE,OAAO,CAAA;IACzB;;;;;OAKG;IACH,QAAQ,CAAC,WAAW,CAAC,EAAE,WAAW,CAAA;IAClC;;;;;OAKG;IACH,QAAQ,CAAC,mBAAmB,CAAC,EAAE,MAAM,CAAA;IACrC;;;OAGG;IACH,QAAQ,CAAC,QAAQ,CAAC,EAAE,OAAO,CAAA;IAC3B;;;OAGG;IACH,QAAQ,CAAC,UAAU,CAAC,EAAE,MAAM,CAAA;CAC7B,CAAA;AAED,iDAAiD;AACjD,MAAM,MAAM,oBAAoB,GAAG;IACjC,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAA;IACxB,QAAQ,CAAC,WAAW,EAAE,WAAW,CAAC,iBAAiB,CAAC,CAAA;CACrD,CAAA;AAED;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA4BG;AACH,eAAO,MAAM,YAAY,WACf,UAAU,YACR,MAAM,YACN,mBAAmB,KAC5B,oBA+CF,CAAA"}
@@ -1 +0,0 @@
1
- {"version":3,"file":"generate-non-discriminated-union-validation.d.ts","sourceRoot":"","sources":["../../src/generators/generate-non-discriminated-union-validation.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,iCAAiC,CAAA;AAIjE;;;GAGG;AACH,eAAO,MAAM,uCAAuC,aACxC,MAAM,WACP,SAAS,UAAU,EAAE,gBAChB,MAAM,cACR,OAAO,KAClB,MA0BF,CAAA"}
@@ -1 +0,0 @@
1
- {"version":3,"file":"generate-parser-function.d.ts","sourceRoot":"","sources":["../../src/generators/generate-parser-function.ts"],"names":[],"mappings":"AA6BA,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,iCAAiC,CAAA;AAMjE;;GAEG;AACH,KAAK,qBAAqB,GAAG;IAC3B;;;;OAIG;IACH,QAAQ,CAAC,aAAa,CAAC,EAAE,OAAO,CAAA;IAChC;;;OAGG;IACH,QAAQ,CAAC,WAAW,CAAC,EAAE,OAAO,CAAA;IAC9B;;;;OAIG;IACH,QAAQ,CAAC,MAAM,CAAC,EAAE,OAAO,CAAA;IACzB;;;OAGG;IACH,QAAQ,CAAC,UAAU,CAAC,EAAE,MAAM,CAAA;CAC7B,CAAA;AA4pCD;;;;;;;;GAQG;AACH,eAAO,MAAM,sBAAsB,WACzB,UAAU,YACR,MAAM,YACN,qBAAqB,KAC9B,MAEF,CAAA;AAED;;;GAGG;AACH,eAAO,MAAM,0BAA0B,aAAc,MAAM,KAAG,MAAsC,CAAA;AAEpG;;;;;;;;;;;;GAYG;AACH,eAAO,MAAM,sBAAsB,WACzB,UAAU,YACR,MAAM,iBACD,OAAO,sBAErB,MAyDF,CAAA"}
@@ -1 +0,0 @@
1
- {"version":3,"file":"generate-schema-checks.d.ts","sourceRoot":"","sources":["../../src/generators/generate-schema-checks.ts"],"names":[],"mappings":"AAsBA,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,iCAAiC,CAAA;AAIjE;;GAEG;AACH,eAAO,MAAM,oBAAoB,aAAc,MAAM,UAAU,UAAU,KAAG,MAAM,EAkGjF,CAAA"}
@@ -1 +0,0 @@
1
- {"version":3,"file":"generate-strict-assertion.d.ts","sourceRoot":"","sources":["../../src/generators/generate-strict-assertion.ts"],"names":[],"mappings":"AAmBA,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,iCAAiC,CAAA;AA+KjE;;;;;;;;;;GAUG;AACH,eAAO,MAAM,6BAA6B,WAAY,UAAU,YAAY,MAAM,KAAG,MAAM,EAe1F,CAAA;AAED;;;GAGG;AACH,eAAO,MAAM,6BAA6B,WAAY,UAAU,YAAY,MAAM,KAAG,MAAM,GAAG,IAgB7F,CAAA"}
@@ -1 +0,0 @@
1
- {"version":3,"file":"generate-validation-expression.d.ts","sourceRoot":"","sources":["../../src/generators/generate-validation-expression.ts"],"names":[],"mappings":"AA8BA,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,iCAAiC,CAAA;AAgDjE;;;;GAIG;AACH,eAAO,MAAM,4BAA4B,QAClC,MAAM,UACH,UAAU,gBACJ,MAAM,cACR,OAAO,eACN,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,gBACtB,GAAG,CAAC,MAAM,CAAC,qBACN,MAAM,sBACL,OAAO,KAC1B,MA0OF,CAAA"}
@@ -1 +0,0 @@
1
- {"version":3,"file":"apply-schema-extensions.d.ts","sourceRoot":"","sources":["../../src/helpers/apply-schema-extensions.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,iCAAiC,CAAA;AACjE,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,0BAA0B,CAAA;AAEhE;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA+BG;AACH,eAAO,MAAM,qBAAqB,WACxB,UAAU,kBACF,MAAM,cACV,gBAAgB,KAC3B,UAuBF,CAAA"}
@@ -1 +0,0 @@
1
- {"version":3,"file":"collect-helpers.d.ts","sourceRoot":"","sources":["../../src/helpers/collect-helpers.ts"],"names":[],"mappings":"AAAA,2FAA2F;AAC3F,MAAM,MAAM,iBAAiB,GAAG,WAAW,GAAG,gBAAgB,GAAG,iBAAiB,GAAG,SAAS,CAAA;AAE9F,sEAAsE;AACtE,MAAM,MAAM,WAAW,GAAG,SAAS,GAAG,UAAU,CAAA;AAEhD,MAAM,MAAM,gBAAgB,GAAG;IAC7B,sDAAsD;IACtD,QAAQ,CAAC,OAAO,EAAE,MAAM,EAAE,CAAA;IAC1B;;;;OAIG;IACH,QAAQ,CAAC,IAAI,EAAE,GAAG,CAAC,iBAAiB,CAAC,CAAA;CACtC,CAAA;AAoBD;;;;;;;GAOG;AACH,eAAO,MAAM,cAAc,mBACT,MAAM,QAChB,WAAW,mCAEhB,gBA+BF,CAAA"}
@@ -1 +0,0 @@
1
- {"version":3,"file":"collect-imports.d.ts","sourceRoot":"","sources":["../../src/helpers/collect-imports.ts"],"names":[],"mappings":"AAIA,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,iCAAiC,CAAA;AAIjE;;GAEG;AACH,KAAK,qBAAqB,GAAG;IAC3B;;;OAGG;IACH,QAAQ,CAAC,SAAS,CAAC,EAAE,OAAO,CAAA;IAC5B;;;;OAIG;IACH,QAAQ,CAAC,OAAO,CAAC,EAAE,MAAM,GAAG,SAAS,CAAA;IACrC;;;;OAIG;IACH,QAAQ,CAAC,UAAU,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,SAAS,CAAA;IACzD;;;;OAIG;IACH,QAAQ,CAAC,UAAU,CAAC,EAAE,MAAM,CAAA;CAC7B,CAAA;AAED;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAsCG;AACH,eAAO,MAAM,cAAc,WAAY,UAAU,YAAY,qBAAqB,KAAG,MAAM,EAsM1F,CAAA"}
@@ -1 +0,0 @@
1
- {"version":3,"file":"find-discriminator.d.ts","sourceRoot":"","sources":["../../src/helpers/find-discriminator.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,iCAAiC,CAAA;AAEjE;;;GAGG;AACH,eAAO,MAAM,iBAAiB,YAAa,SAAS,UAAU,EAAE,KAAG,MAAM,GAAG,IA+B3E,CAAA"}
@@ -1 +0,0 @@
1
- {"version":3,"file":"get-default-value.d.ts","sourceRoot":"","sources":["../../src/helpers/get-default-value.ts"],"names":[],"mappings":"AAYA,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,iCAAiC,CAAA;AAGjE;;;;GAIG;AACH,eAAO,MAAM,eAAe,WAAY,UAAU,KAAG,MA0EpD,CAAA"}
@@ -1 +0,0 @@
1
- {"version":3,"file":"get-discriminator-value.d.ts","sourceRoot":"","sources":["../../src/helpers/get-discriminator-value.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,iCAAiC,CAAA;AAEjE;;;GAGG;AACH,eAAO,MAAM,qBAAqB,WAAY,UAAU,oBAAoB,MAAM,KAAG,OAAO,GAAG,IAa9F,CAAA"}
@@ -1 +0,0 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,WAAW,EAAE,KAAK,aAAa,EAAE,MAAM,2BAA2B,CAAA"}
@@ -1 +0,0 @@
1
- {"version":3,"file":"schema-extensions.d.ts","sourceRoot":"","sources":["../../src/types/schema-extensions.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,iCAAiC,CAAA;AAEjE;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA+BG;AACH,MAAM,MAAM,gBAAgB,GAAG,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,UAAU,CAAC,CAAC,CAAA"}