@amritk/generate-parsers 0.16.3 → 0.17.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.
@@ -1,86 +1,57 @@
1
- import { generateTypeDefinition } from '@amritk/helpers/generate-type-definition';
2
- import { collectHelpers, } from '../helpers/collect-helpers.js';
3
- import { collectImports, collectImportTypeNames } from '../helpers/collect-imports.js';
4
- import { generateParserFunction, generateShapeValidator } from './generate-parser-function.js';
5
- /**
6
- * Generates a complete TypeScript file from a JSON Schema.
7
- *
8
- * The generated file contains imports, type definition, and parser function.
9
- * When typesOnly is true, only the type definition is emitted without any parser.
10
- *
11
- * @param schema - The JSON Schema to generate code from
12
- * @param typeName - The name to use for the generated TypeScript type
13
- * @param options - Optional settings to control what gets generated
14
- * @returns The generated file content and the set of runtime helpers it references
15
- *
16
- * @example
17
- * ```typescript
18
- * const schema = {
19
- * type: "object",
20
- * properties: {
21
- * name: { type: "string" },
22
- * age: { type: "number" }
23
- * },
24
- * required: ["name"]
25
- * };
26
- *
27
- * const result = generateFile(schema, "User");
28
- * // result contains imports, type User, and parseUser function
29
- *
30
- * const typesOnly = generateFile(schema, "User", { typesOnly: true });
31
- * // typesOnly contains only type-only imports and type User (no parser)
32
- * ```
33
- */
34
- export const generateFile = (schema, typeName, options) => {
35
- const typesOnly = options?.typesOnly === true;
36
- const selfRef = options?.selfRef;
37
- const rootSchema = options?.rootSchema;
38
- const helpersMode = options?.helpersMode ?? 'package';
39
- const typeSuffix = options?.typeSuffix ?? '';
40
- const importExt = options?.importExt ?? 'js';
41
- const typeDefinition = generateTypeDefinition(schema, typeName, { readonly: options?.readonly ?? false, typeSuffix });
42
- if (typesOnly) {
43
- // In types-only mode, skip the parser function and use type-only imports
44
- const imports = collectImports(schema, { typesOnly: true, selfRef, rootSchema, typeSuffix, importExt });
45
- let result = '';
46
- if (imports.length > 0) {
47
- result += imports[0];
48
- for (let i = 1; i < imports.length; i++) {
49
- result += '\n' + imports[i];
50
- }
51
- result += '\n\n';
52
- }
53
- return { content: result + typeDefinition, usedHelpers: new Set() };
1
+ import { generateTypeDefinition } from "@amritk/helpers/generate-type-definition";
2
+ import { collectHelpers } from "../helpers/collect-helpers.js";
3
+ import { collectImports, collectImportTypeNames } from "../helpers/collect-imports.js";
4
+ import { generateParserFunction, generateShapeValidator } from "./generate-parser-function.js";
5
+ const generateFile = (schema, typeName, options) => {
6
+ const typesOnly = options?.typesOnly === true;
7
+ const selfRef = options?.selfRef;
8
+ const rootSchema = options?.rootSchema;
9
+ const helpersMode = options?.helpersMode ?? "package";
10
+ const typeSuffix = options?.typeSuffix ?? "";
11
+ const importExt = options?.importExt ?? "js";
12
+ const typeDefinition = generateTypeDefinition(schema, typeName, { readonly: options?.readonly ?? false, typeSuffix });
13
+ if (typesOnly) {
14
+ const imports2 = collectImports(schema, { typesOnly: true, selfRef, rootSchema, typeSuffix, importExt });
15
+ let result2 = "";
16
+ if (imports2.length > 0) {
17
+ result2 += imports2[0];
18
+ for (let i = 1; i < imports2.length; i++) {
19
+ result2 += "\n" + imports2[i];
20
+ }
21
+ result2 += "\n\n";
54
22
  }
55
- const stripUnknown = options?.stripUnknown ?? false;
56
- // Names this file imports for its $refs: synthesized private sub-type names
57
- // dedup against them so they can never shadow an imported identifier. The
58
- // parser and the shape validator must receive the SAME set — their derived
59
- // sub-names have to agree.
60
- const reservedNames = collectImportTypeNames(schema, { selfRef, rootSchema, typeSuffix });
61
- const parserFunction = generateParserFunction(schema, typeName, {
62
- useRefImports: true,
63
- typeSuffix,
64
- reservedNames,
65
- ...(options?.isRoot !== undefined ? { isRoot: options.isRoot } : {}),
66
- ...(rootSchema !== undefined ? { rootSchema } : {}),
67
- ...(options?.logWarnings !== undefined ? { logWarnings: options.logWarnings } : {}),
68
- ...(options?.strict !== undefined ? { strict: options.strict } : {}),
69
- ...(options?.stripUnknown !== undefined ? { stripUnknown: options.stripUnknown } : {}),
70
- ...(options?.caseInsensitive !== undefined ? { caseInsensitive: options.caseInsensitive } : {}),
71
- });
72
- const shapeValidator = generateShapeValidator(schema, typeName, true, typeSuffix, true, stripUnknown, reservedNames);
73
- const combinedFunctions = `${shapeValidator}\n\n${parserFunction}`;
74
- const helpers = collectHelpers(combinedFunctions, helpersMode, options?.helpersImportPrefix, importExt);
75
- const imports = [...collectImports(schema, { selfRef, rootSchema, typeSuffix, importExt }), ...helpers.imports];
76
- // Build file output using string concatenation instead of array join for performance
77
- let result = '';
78
- if (imports.length > 0) {
79
- result += imports[0];
80
- for (let i = 1; i < imports.length; i++) {
81
- result += '\n' + imports[i];
82
- }
83
- result += '\n\n';
23
+ return { content: result2 + typeDefinition, usedHelpers: /* @__PURE__ */ new Set() };
24
+ }
25
+ const stripUnknown = options?.stripUnknown ?? false;
26
+ const reservedNames = collectImportTypeNames(schema, { selfRef, rootSchema, typeSuffix });
27
+ const shapeValidator = generateShapeValidator(schema, typeName, true, typeSuffix, true, stripUnknown, reservedNames);
28
+ const parserFunction = generateParserFunction(schema, typeName, {
29
+ useRefImports: true,
30
+ typeSuffix,
31
+ reservedNames,
32
+ shapeValidatorSource: shapeValidator,
33
+ ...options?.isRoot !== void 0 ? { isRoot: options.isRoot } : {},
34
+ ...rootSchema !== void 0 ? { rootSchema } : {},
35
+ ...options?.logWarnings !== void 0 ? { logWarnings: options.logWarnings } : {},
36
+ ...options?.strict !== void 0 ? { strict: options.strict } : {},
37
+ ...options?.stripUnknown !== void 0 ? { stripUnknown: options.stripUnknown } : {},
38
+ ...options?.caseInsensitive !== void 0 ? { caseInsensitive: options.caseInsensitive } : {}
39
+ });
40
+ const combinedFunctions = `${shapeValidator}
41
+
42
+ ${parserFunction}`;
43
+ const helpers = collectHelpers(combinedFunctions, helpersMode, options?.helpersImportPrefix, importExt);
44
+ const imports = [...collectImports(schema, { selfRef, rootSchema, typeSuffix, importExt }), ...helpers.imports];
45
+ let result = "";
46
+ if (imports.length > 0) {
47
+ result += imports[0];
48
+ for (let i = 1; i < imports.length; i++) {
49
+ result += "\n" + imports[i];
84
50
  }
85
- return { content: result + typeDefinition + '\n\n' + combinedFunctions, usedHelpers: helpers.used };
51
+ result += "\n\n";
52
+ }
53
+ return { content: result + typeDefinition + "\n\n" + combinedFunctions, usedHelpers: helpers.used };
54
+ };
55
+ export {
56
+ generateFile
86
57
  };
@@ -1,28 +1,26 @@
1
- import { generateSchemaChecks } from './generate-schema-checks.js';
2
- /**
3
- * Generates a non-discriminated union validation expression.
4
- * Tries each schema in order until one matches.
5
- */
6
- export const generateNonDiscriminatedUnionValidation = (accessor, schemas, defaultValue, isRequired) => {
7
- const cases = [];
8
- for (const schema of schemas) {
9
- const checks = generateSchemaChecks(accessor, schema);
10
- if (checks.length > 0) {
11
- let combinedChecks = checks[0];
12
- for (let i = 1; i < checks.length; i++) {
13
- combinedChecks += ' && ' + checks[i];
14
- }
15
- cases.push('(' + combinedChecks + ')');
16
- }
1
+ import { generateSchemaChecks } from "./generate-schema-checks.js";
2
+ const generateNonDiscriminatedUnionValidation = (accessor, schemas, defaultValue, isRequired) => {
3
+ const cases = [];
4
+ for (const schema of schemas) {
5
+ const checks = generateSchemaChecks(accessor, schema);
6
+ if (checks.length > 0) {
7
+ let combinedChecks = checks[0];
8
+ for (let i = 1; i < checks.length; i++) {
9
+ combinedChecks += " && " + checks[i];
10
+ }
11
+ cases.push("(" + combinedChecks + ")");
17
12
  }
18
- if (cases.length === 0) {
19
- return isRequired ? `${accessor} ?? ${defaultValue}` : accessor;
20
- }
21
- // Try each schema in order
22
- let combinedCheck = cases[0];
23
- for (let i = 1; i < cases.length; i++) {
24
- combinedCheck += ' || ' + cases[i];
25
- }
26
- const finalDefault = isRequired ? defaultValue : 'undefined';
27
- return `(${combinedCheck}) ? ${accessor} : ${finalDefault}`;
13
+ }
14
+ if (cases.length === 0) {
15
+ return isRequired ? `${accessor} ?? ${defaultValue}` : accessor;
16
+ }
17
+ let combinedCheck = cases[0];
18
+ for (let i = 1; i < cases.length; i++) {
19
+ combinedCheck += " || " + cases[i];
20
+ }
21
+ const finalDefault = isRequired ? defaultValue : "undefined";
22
+ return `(${combinedCheck}) ? ${accessor} : ${finalDefault}`;
23
+ };
24
+ export {
25
+ generateNonDiscriminatedUnionValidation
28
26
  };
@@ -67,6 +67,18 @@ type GenerateParserOptions = {
67
67
  * exact `===` fast path and the hot path is unaffected.
68
68
  */
69
69
  readonly caseInsensitive?: boolean;
70
+ /**
71
+ * The exact source of the `validate{TypeName}Shape` predicate emitted into
72
+ * the same file as this parser (generate-files produces it for the root
73
+ * type). When the parser can prove — by rendering the predicate it would
74
+ * need and comparing byte-for-byte — that this emitted predicate tests
75
+ * exactly its own fast-path guard, it emits `validate{TypeName}Shape(input)`
76
+ * instead of a second inline copy of the whole check chain, which roughly
77
+ * halves the emitted checks per object type. Any mismatch (composition
78
+ * keywords, conditional flattening, alias/union predicates, stubs) keeps
79
+ * the inline guard, so a wrong substitution is impossible by construction.
80
+ */
81
+ readonly shapeValidatorSource?: string;
70
82
  };
71
83
  /**
72
84
  * Generates a safe parser as an arrow function that never throws.