@gabrielbryk/json-schema-to-zod 2.8.0 → 2.10.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 (69) hide show
  1. package/CHANGELOG.md +18 -0
  2. package/dist/cjs/core/analyzeSchema.js +62 -0
  3. package/dist/cjs/core/emitZod.js +141 -0
  4. package/dist/cjs/generators/generateBundle.js +117 -63
  5. package/dist/cjs/index.js +4 -0
  6. package/dist/cjs/jsonSchemaToZod.js +5 -167
  7. package/dist/cjs/parsers/parseAllOf.js +12 -6
  8. package/dist/cjs/parsers/parseBoolean.js +1 -3
  9. package/dist/cjs/parsers/parseIfThenElse.js +1 -1
  10. package/dist/cjs/parsers/parseNull.js +1 -3
  11. package/dist/cjs/parsers/parseObject.js +8 -3
  12. package/dist/cjs/parsers/parseSchema.js +130 -26
  13. package/dist/cjs/parsers/parseString.js +1 -1
  14. package/dist/cjs/utils/buildRefRegistry.js +56 -0
  15. package/dist/cjs/utils/omit.js +3 -2
  16. package/dist/cjs/utils/resolveUri.js +16 -0
  17. package/dist/esm/Types.js +1 -2
  18. package/dist/esm/cli.js +10 -12
  19. package/dist/esm/core/analyzeSchema.js +58 -0
  20. package/dist/esm/core/emitZod.js +137 -0
  21. package/dist/esm/generators/generateBundle.js +118 -68
  22. package/dist/esm/index.js +34 -46
  23. package/dist/esm/jsonSchemaToZod.js +5 -171
  24. package/dist/esm/parsers/parseAllOf.js +17 -14
  25. package/dist/esm/parsers/parseAnyOf.js +6 -10
  26. package/dist/esm/parsers/parseArray.js +11 -15
  27. package/dist/esm/parsers/parseBoolean.js +1 -7
  28. package/dist/esm/parsers/parseConst.js +1 -5
  29. package/dist/esm/parsers/parseDefault.js +3 -7
  30. package/dist/esm/parsers/parseEnum.js +1 -5
  31. package/dist/esm/parsers/parseIfThenElse.js +6 -10
  32. package/dist/esm/parsers/parseMultipleType.js +3 -7
  33. package/dist/esm/parsers/parseNot.js +4 -8
  34. package/dist/esm/parsers/parseNull.js +1 -7
  35. package/dist/esm/parsers/parseNullable.js +4 -8
  36. package/dist/esm/parsers/parseNumber.js +11 -15
  37. package/dist/esm/parsers/parseObject.js +33 -31
  38. package/dist/esm/parsers/parseOneOf.js +6 -10
  39. package/dist/esm/parsers/parseSchema.js +187 -87
  40. package/dist/esm/parsers/parseSimpleDiscriminatedOneOf.js +6 -10
  41. package/dist/esm/parsers/parseString.js +12 -16
  42. package/dist/esm/utils/anyOrUnknown.js +1 -5
  43. package/dist/esm/utils/buildRefRegistry.js +52 -0
  44. package/dist/esm/utils/cliTools.js +7 -13
  45. package/dist/esm/utils/cycles.js +3 -9
  46. package/dist/esm/utils/half.js +1 -5
  47. package/dist/esm/utils/jsdocs.js +3 -8
  48. package/dist/esm/utils/omit.js +4 -7
  49. package/dist/esm/utils/resolveUri.js +12 -0
  50. package/dist/esm/utils/withMessage.js +1 -4
  51. package/dist/esm/zodToJsonSchema.js +1 -4
  52. package/dist/types/Types.d.ts +34 -3
  53. package/dist/types/core/analyzeSchema.d.ts +24 -0
  54. package/dist/types/core/emitZod.d.ts +2 -0
  55. package/dist/types/generators/generateBundle.d.ts +5 -0
  56. package/dist/types/index.d.ts +4 -0
  57. package/dist/types/jsonSchemaToZod.d.ts +1 -1
  58. package/dist/types/parsers/parseBoolean.d.ts +1 -4
  59. package/dist/types/parsers/parseNull.d.ts +1 -4
  60. package/dist/types/parsers/parseSchema.d.ts +2 -1
  61. package/dist/types/utils/buildRefRegistry.d.ts +12 -0
  62. package/dist/types/utils/resolveUri.d.ts +1 -0
  63. package/docs/proposals/bundle-refactor.md +43 -0
  64. package/docs/proposals/ref-anchor-support.md +65 -0
  65. package/eslint.config.js +26 -0
  66. package/package.json +10 -4
  67. /package/{jest.config.js → jest.config.cjs} +0 -0
  68. /package/{postcjs.js → postcjs.cjs} +0 -0
  69. /package/{postesm.js → postesm.cjs} +0 -0
package/CHANGELOG.md CHANGED
@@ -1,5 +1,23 @@
1
1
  # @gabrielbryk/json-schema-to-zod
2
2
 
3
+ ## 2.9.0
4
+
5
+ ### Minor Changes
6
+
7
+ - 0ef12db: - Extract analyzer/emitter core, refactor bundle generation to avoid recursion and preserve inline defs/definitions.
8
+ - Add scoped naming for inline $defs, lazy cross-ref handling, and nested types emission.
9
+ - Expand bundle test coverage with snapshots, inline-def fixtures, and nested-type extraction.
10
+
11
+ ### Patch Changes
12
+
13
+ - b8f6248: - Commit remaining utility, ref resolution test, and config updates.
14
+ - 04b6c6b: Fix bundle output by emitting z.lazy for cyclical refs outside object properties, use ZodError.issues in generated conditionals, and make nested type extraction array-aware to avoid invalid indexers.
15
+ - b8f7b29: Fix type errors in CI by replacing symbol-based allOf indexing, guarding invalid refs, and tightening string content schema parsing types.
16
+ - 691cc5b: - Added ESLint (recommended + no-require-imports) and cleaned all lint issues across src/tests; tightened types and removed unused vars.
17
+ - Ensured ESM-friendly test evals and ref/anchor resolver code without require usage.
18
+ - 4d127fe: Remove fallback to the non-existent ZodError.errors property in generated conditional schemas; rely on ZodError.issues to avoid TypeScript errors.
19
+ - 7d257dd: - Ensure dist/esm emits real ESM with NodeNext settings and type:module, and update tests to run under ESM by providing createRequire shims.
20
+
3
21
  ## 2.8.0
4
22
 
5
23
  ### Minor Changes
@@ -0,0 +1,62 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.analyzeSchema = void 0;
4
+ const parseSchema_js_1 = require("../parsers/parseSchema.js");
5
+ const cycles_js_1 = require("../utils/cycles.js");
6
+ const buildRefRegistry_js_1 = require("../utils/buildRefRegistry.js");
7
+ const analyzeSchema = (schema, options = {}) => {
8
+ const { module, name, type, ...rest } = options;
9
+ if (type && (!name || module !== "esm")) {
10
+ throw new Error("Option `type` requires `name` to be set and `module` to be `esm`");
11
+ }
12
+ const normalized = {
13
+ module,
14
+ name,
15
+ type,
16
+ ...rest,
17
+ exportRefs: rest.exportRefs ?? true,
18
+ withMeta: rest.withMeta ?? true,
19
+ };
20
+ const refNameByPointer = new Map();
21
+ const usedNames = new Set();
22
+ if (name) {
23
+ usedNames.add(name);
24
+ }
25
+ const declarations = new Map();
26
+ const dependencies = new Map();
27
+ const { registry: refRegistry, rootBaseUri } = (0, buildRefRegistry_js_1.buildRefRegistry)(schema);
28
+ const pass1 = {
29
+ module,
30
+ name,
31
+ path: [],
32
+ seen: new Map(),
33
+ declarations,
34
+ dependencies,
35
+ inProgress: new Set(),
36
+ refNameByPointer,
37
+ usedNames,
38
+ root: schema,
39
+ currentSchemaName: name,
40
+ refRegistry,
41
+ rootBaseUri,
42
+ ...rest,
43
+ withMeta: normalized.withMeta,
44
+ };
45
+ (0, parseSchema_js_1.parseSchema)(schema, pass1);
46
+ const names = Array.from(declarations.keys());
47
+ const cycleRefNames = (0, cycles_js_1.detectCycles)(names, dependencies);
48
+ const { componentByName } = (0, cycles_js_1.computeScc)(names, dependencies);
49
+ return {
50
+ schema,
51
+ options: normalized,
52
+ refNameByPointer,
53
+ usedNames,
54
+ declarations,
55
+ dependencies,
56
+ cycleRefNames,
57
+ cycleComponentByName: componentByName,
58
+ refRegistry,
59
+ rootBaseUri,
60
+ };
61
+ };
62
+ exports.analyzeSchema = analyzeSchema;
@@ -0,0 +1,141 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.emitZod = void 0;
4
+ const parseSchema_js_1 = require("../parsers/parseSchema.js");
5
+ const jsdocs_js_1 = require("../utils/jsdocs.js");
6
+ const orderDeclarations = (entries, dependencies) => {
7
+ const valueByName = new Map(entries);
8
+ const depGraph = new Map();
9
+ for (const [from, set] of dependencies.entries()) {
10
+ const onlyKnown = new Set();
11
+ for (const dep of set) {
12
+ if (valueByName.has(dep) && dep !== from) {
13
+ onlyKnown.add(dep);
14
+ }
15
+ }
16
+ if (onlyKnown.size)
17
+ depGraph.set(from, onlyKnown);
18
+ }
19
+ const names = Array.from(valueByName.keys());
20
+ for (const [name, value] of entries) {
21
+ const deps = depGraph.get(name) ?? new Set();
22
+ for (const candidate of names) {
23
+ if (candidate === name)
24
+ continue;
25
+ const matcher = new RegExp(`\\b${candidate}\\b`);
26
+ if (matcher.test(value)) {
27
+ deps.add(candidate);
28
+ }
29
+ }
30
+ if (deps.size)
31
+ depGraph.set(name, deps);
32
+ }
33
+ const ordered = [];
34
+ const perm = new Set();
35
+ const temp = new Set();
36
+ const visit = (name) => {
37
+ if (perm.has(name))
38
+ return;
39
+ if (temp.has(name)) {
40
+ temp.delete(name);
41
+ perm.add(name);
42
+ ordered.push(name);
43
+ return;
44
+ }
45
+ temp.add(name);
46
+ const deps = depGraph.get(name);
47
+ if (deps) {
48
+ for (const dep of deps) {
49
+ if (valueByName.has(dep)) {
50
+ visit(dep);
51
+ }
52
+ }
53
+ }
54
+ temp.delete(name);
55
+ perm.add(name);
56
+ ordered.push(name);
57
+ };
58
+ for (const name of valueByName.keys()) {
59
+ visit(name);
60
+ }
61
+ const unique = [];
62
+ const seen = new Set();
63
+ for (const name of ordered) {
64
+ if (!seen.has(name)) {
65
+ seen.add(name);
66
+ unique.push(name);
67
+ }
68
+ }
69
+ return unique.map((name) => [name, valueByName.get(name)]);
70
+ };
71
+ const emitZod = (analysis) => {
72
+ const { schema, options, refNameByPointer, usedNames, cycleRefNames, cycleComponentByName, } = analysis;
73
+ const { module, name, type, noImport, exportRefs, withMeta, ...rest } = options;
74
+ const declarations = new Map();
75
+ const dependencies = new Map();
76
+ const parsedSchema = (0, parseSchema_js_1.parseSchema)(schema, {
77
+ module,
78
+ name,
79
+ path: [],
80
+ seen: new Map(),
81
+ declarations,
82
+ dependencies,
83
+ inProgress: new Set(),
84
+ refNameByPointer,
85
+ usedNames,
86
+ root: schema,
87
+ currentSchemaName: name,
88
+ cycleRefNames,
89
+ cycleComponentByName,
90
+ refRegistry: analysis.refRegistry,
91
+ rootBaseUri: analysis.rootBaseUri,
92
+ ...rest,
93
+ withMeta,
94
+ });
95
+ const declarationBlock = declarations.size
96
+ ? orderDeclarations(Array.from(declarations.entries()), dependencies)
97
+ .map(([refName, value]) => {
98
+ const shouldExport = exportRefs && module === "esm";
99
+ const decl = `${shouldExport ? "export " : ""}const ${refName} = ${value}`;
100
+ return decl;
101
+ })
102
+ .join("\n")
103
+ : "";
104
+ const jsdocs = rest.withJsdocs && typeof schema === "object" && schema !== null && "description" in schema
105
+ ? (0, jsdocs_js_1.expandJsdocs)(String(schema.description ?? ""))
106
+ : "";
107
+ const lines = [];
108
+ if (module === "cjs" && !noImport) {
109
+ lines.push(`const { z } = require("zod")`);
110
+ }
111
+ if (module === "esm" && !noImport) {
112
+ lines.push(`import { z } from "zod"`);
113
+ }
114
+ if (declarationBlock) {
115
+ lines.push(declarationBlock);
116
+ }
117
+ if (module === "cjs") {
118
+ const payload = name ? `{ ${JSON.stringify(name)}: ${parsedSchema} }` : parsedSchema;
119
+ lines.push(`${jsdocs}module.exports = ${payload}`);
120
+ }
121
+ else if (module === "esm") {
122
+ const exportLine = `${jsdocs}export ${name ? `const ${name} =` : `default`} ${parsedSchema}`;
123
+ lines.push(exportLine);
124
+ }
125
+ else if (name) {
126
+ lines.push(`${jsdocs}const ${name} = ${parsedSchema}`);
127
+ }
128
+ else {
129
+ lines.push(`${jsdocs}${parsedSchema}`);
130
+ }
131
+ let typeLine;
132
+ if (type && name) {
133
+ const typeName = typeof type === "string" ? type : `${name[0].toUpperCase()}${name.substring(1)}`;
134
+ typeLine = `export type ${typeName} = z.infer<typeof ${name}>`;
135
+ }
136
+ const joined = lines.filter(Boolean).join("\n\n");
137
+ const combined = typeLine ? `${joined}\n${typeLine}` : joined;
138
+ const shouldEndWithNewline = module === "esm" || module === "cjs";
139
+ return `${combined}${shouldEndWithNewline ? "\n" : ""}`;
140
+ };
141
+ exports.emitZod = emitZod;
@@ -1,61 +1,34 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.generateSchemaBundle = void 0;
4
- const jsonSchemaToZod_js_1 = require("../jsonSchemaToZod.js");
4
+ const analyzeSchema_js_1 = require("../core/analyzeSchema.js");
5
+ const emitZod_js_1 = require("../core/emitZod.js");
5
6
  const generateSchemaBundle = (schema, options = {}) => {
6
7
  const module = options.module ?? "esm";
7
- const rootName = options.splitDefs?.rootName ?? options.name ?? "RootSchema";
8
- const rootTypeName = typeof options.type === "string"
9
- ? options.type
10
- : options.splitDefs?.rootTypeName ?? (typeof options.type === "boolean" && options.type ? rootName : undefined);
11
8
  if (!schema || typeof schema !== "object") {
12
9
  throw new Error("generateSchemaBundle requires an object schema");
13
10
  }
14
- const defs = schema.$defs || schema.definitions || {};
11
+ const defs = schema.$defs || {};
12
+ const definitions = schema.definitions || {};
15
13
  const defNames = Object.keys(defs);
16
- const defInfoMap = buildDefInfoMap(defNames, defs, options);
17
- const cycles = detectCycles(defInfoMap);
18
- for (const defName of cycles) {
19
- const info = defInfoMap.get(defName);
20
- if (info)
21
- info.hasCycle = true;
22
- }
14
+ const { rootName, rootTypeName, defInfoMap } = buildBundleContext(defNames, defs, options);
23
15
  const files = [];
24
- // Generate individual $def files
25
- for (const defName of defNames) {
26
- const info = defInfoMap.get(defName);
27
- const usedRefs = new Set();
28
- const defSchemaWithDefs = {
29
- ...defs[defName],
30
- $defs: defs,
31
- };
32
- const zodSchema = (0, jsonSchemaToZod_js_1.jsonSchemaToZod)(defSchemaWithDefs, {
16
+ const targets = planBundleTargets(schema, defs, definitions, defNames, options, rootName, rootTypeName);
17
+ for (const target of targets) {
18
+ const usedRefs = target.usedRefs;
19
+ const analysis = (0, analyzeSchema_js_1.analyzeSchema)(target.schemaWithDefs, {
33
20
  ...options,
34
21
  module,
35
- name: info.schemaName,
36
- type: info.typeName,
37
- parserOverride: createRefHandler(defName, defInfoMap, usedRefs, defs, options),
22
+ name: target.schemaName,
23
+ type: target.typeName,
24
+ parserOverride: createRefHandler(target.defName, defInfoMap, usedRefs, {
25
+ ...(target.schemaWithDefs.$defs || {}),
26
+ ...(target.schemaWithDefs.definitions || {}),
27
+ }, options),
38
28
  });
29
+ const zodSchema = (0, emitZod_js_1.emitZod)(analysis);
39
30
  const finalSchema = buildSchemaFile(zodSchema, usedRefs, defInfoMap, module);
40
- const fileName = options.splitDefs?.fileName?.(defName, { isRoot: false }) ?? `${defName}.schema.ts`;
41
- files.push({ fileName, contents: finalSchema });
42
- }
43
- // Generate root schema if requested
44
- if (options.splitDefs?.includeRoot ?? true) {
45
- const usedRefs = new Set();
46
- const workflowSchemaWithDefs = {
47
- ...schema,
48
- };
49
- const workflowZodSchema = (0, jsonSchemaToZod_js_1.jsonSchemaToZod)(workflowSchemaWithDefs, {
50
- ...options,
51
- module,
52
- name: rootName,
53
- type: rootTypeName,
54
- parserOverride: createRefHandler(null, defInfoMap, usedRefs, defs, options),
55
- });
56
- const finalWorkflow = buildSchemaFile(workflowZodSchema, usedRefs, defInfoMap, module);
57
- const rootFile = options.splitDefs?.fileName?.("root", { isRoot: true }) ?? "workflow.schema.ts";
58
- files.push({ fileName: rootFile, contents: finalWorkflow });
31
+ files.push({ fileName: target.fileName, contents: finalSchema });
59
32
  }
60
33
  // Nested types extraction (optional)
61
34
  const nestedTypesEnabled = options.nestedTypes?.enable;
@@ -95,13 +68,32 @@ const buildDefInfoMap = (defNames, defs, options) => {
95
68
  }
96
69
  return map;
97
70
  };
71
+ const buildBundleContext = (defNames, defs, options) => {
72
+ const defInfoMap = buildDefInfoMap(defNames, defs, options);
73
+ const cycles = detectCycles(defInfoMap);
74
+ for (const defName of cycles) {
75
+ const info = defInfoMap.get(defName);
76
+ if (info)
77
+ info.hasCycle = true;
78
+ }
79
+ const rootName = options.splitDefs?.rootName ?? options.name ?? "RootSchema";
80
+ const rootTypeName = typeof options.type === "string"
81
+ ? options.type
82
+ : options.splitDefs?.rootTypeName ?? (typeof options.type === "boolean" && options.type ? rootName : undefined);
83
+ return { defInfoMap, rootName, rootTypeName };
84
+ };
98
85
  const createRefHandler = (currentDefName, defInfoMap, usedRefs, allDefs, options) => {
86
+ const useLazyCrossRefs = options.refResolution?.lazyCrossRefs ?? true;
99
87
  return (schema, refs) => {
100
88
  if (typeof schema["$ref"] === "string") {
101
89
  const refPath = schema["$ref"];
102
90
  const match = refPath.match(/^#\/(?:\$defs|definitions)\/(.+)$/);
103
91
  if (match) {
104
92
  const refName = match[1];
93
+ // Only intercept top-level def refs (no nested path like a/$defs/x)
94
+ if (refName.includes("/")) {
95
+ return undefined;
96
+ }
105
97
  const refInfo = defInfoMap.get(refName);
106
98
  if (refInfo) {
107
99
  // Track imports when referencing other defs
@@ -118,22 +110,22 @@ const createRefHandler = (currentDefName, defInfoMap, usedRefs, allDefs, options
118
110
  });
119
111
  if (resolved)
120
112
  return resolved;
113
+ if (isCycle && useLazyCrossRefs) {
114
+ return `z.lazy(() => ${refInfo.schemaName})`;
115
+ }
121
116
  return refInfo.schemaName;
122
117
  }
118
+ // If the ref points to a local/inline $def (not part of top-level defs),
119
+ // let the default parser resolve it normally.
120
+ if (allDefs && Object.prototype.hasOwnProperty.call(allDefs, refName)) {
121
+ return undefined;
122
+ }
123
123
  }
124
124
  const unknown = options.refResolution?.onUnknownRef?.({ ref: refPath, currentDef: currentDefName });
125
125
  if (unknown)
126
126
  return unknown;
127
127
  return options.useUnknown ? "z.unknown()" : "z.any()";
128
128
  }
129
- // Inline $defs within a schema
130
- if (schema["$defs"] && typeof schema["$defs"] === "object") {
131
- return (0, jsonSchemaToZod_js_1.jsonSchemaToZod)(schema, {
132
- ...options,
133
- module: options.module ?? "esm",
134
- parserOverride: createRefHandler(currentDefName, defInfoMap, usedRefs, allDefs, options),
135
- });
136
- }
137
129
  return undefined;
138
130
  };
139
131
  };
@@ -151,6 +143,52 @@ const buildSchemaFile = (zodCode, usedRefs, defInfoMap, module) => {
151
143
  return zodCode;
152
144
  return zodCode.replace('import { z } from "zod"', `import { z } from "zod"\n${imports.join("\n")}`);
153
145
  };
146
+ const planBundleTargets = (rootSchema, defs, definitions, defNames, options, rootName, rootTypeName) => {
147
+ const targets = [];
148
+ for (const defName of defNames) {
149
+ const defSchema = defs[defName];
150
+ const defSchemaWithDefs = {
151
+ ...defSchema,
152
+ $defs: { ...defs, ...defSchema?.$defs },
153
+ definitions: {
154
+ ...defSchema.definitions,
155
+ ...definitions,
156
+ },
157
+ };
158
+ const pascalName = toPascalCase(defName);
159
+ const schemaName = options.splitDefs?.schemaName?.(defName, { isRoot: false }) ?? `${pascalName}Schema`;
160
+ const typeName = options.splitDefs?.typeName?.(defName, { isRoot: false }) ?? pascalName;
161
+ const fileName = options.splitDefs?.fileName?.(defName, { isRoot: false }) ?? `${defName}.schema.ts`;
162
+ targets.push({
163
+ defName,
164
+ schemaWithDefs: defSchemaWithDefs,
165
+ schemaName,
166
+ typeName,
167
+ fileName,
168
+ usedRefs: new Set(),
169
+ isRoot: false,
170
+ });
171
+ }
172
+ if (options.splitDefs?.includeRoot ?? true) {
173
+ const rootFile = options.splitDefs?.fileName?.("root", { isRoot: true }) ?? "workflow.schema.ts";
174
+ targets.push({
175
+ defName: null,
176
+ schemaWithDefs: {
177
+ ...rootSchema,
178
+ definitions: {
179
+ ...rootSchema.definitions,
180
+ ...definitions,
181
+ },
182
+ },
183
+ schemaName: rootName,
184
+ typeName: rootTypeName,
185
+ fileName: rootFile,
186
+ usedRefs: new Set(),
187
+ isRoot: true,
188
+ });
189
+ }
190
+ return targets;
191
+ };
154
192
  const findRefDependencies = (schema, validDefNames) => {
155
193
  const deps = new Set();
156
194
  function traverse(obj) {
@@ -239,7 +277,7 @@ const findNestedTypesInSchema = (schema, parentTypeName, defNames, currentPath =
239
277
  if (schema === null || typeof schema !== "object")
240
278
  return nestedTypes;
241
279
  const record = schema;
242
- if (record.title && typeof record.title === "string" && record.type === "object") {
280
+ if (record.title && typeof record.title === "string") {
243
281
  const title = record.title;
244
282
  if (title !== parentTypeName && !defNames.map((d) => toPascalCase(d)).includes(title)) {
245
283
  nestedTypes.push({
@@ -250,6 +288,12 @@ const findNestedTypesInSchema = (schema, parentTypeName, defNames, currentPath =
250
288
  });
251
289
  }
252
290
  }
291
+ // inline $defs
292
+ if (record.$defs && typeof record.$defs === "object") {
293
+ for (const [, defSchema] of Object.entries(record.$defs)) {
294
+ nestedTypes.push(...findNestedTypesInSchema(defSchema, parentTypeName, defNames, currentPath));
295
+ }
296
+ }
253
297
  if (record.properties && typeof record.properties === "object") {
254
298
  for (const [propName, propSchema] of Object.entries(record.properties)) {
255
299
  nestedTypes.push(...findNestedTypesInSchema(propSchema, parentTypeName, defNames, [...currentPath, propName]));
@@ -261,10 +305,10 @@ const findNestedTypesInSchema = (schema, parentTypeName, defNames, currentPath =
261
305
  }
262
306
  }
263
307
  if (record.items) {
264
- nestedTypes.push(...findNestedTypesInSchema(record.items, parentTypeName, defNames, currentPath));
308
+ nestedTypes.push(...findNestedTypesInSchema(record.items, parentTypeName, defNames, [...currentPath, "items"]));
265
309
  }
266
310
  if (record.additionalProperties && typeof record.additionalProperties === "object") {
267
- nestedTypes.push(...findNestedTypesInSchema(record.additionalProperties, parentTypeName, defNames, currentPath));
311
+ nestedTypes.push(...findNestedTypesInSchema(record.additionalProperties, parentTypeName, defNames, [...currentPath, "additionalProperties"]));
268
312
  }
269
313
  return nestedTypes;
270
314
  };
@@ -285,23 +329,33 @@ const generateNestedTypesFile = (nestedTypes) => {
285
329
  }
286
330
  byParent.get(info.parentType).push(info);
287
331
  }
288
- const imports = new Set();
332
+ const imports = new Map(); // file -> type name
289
333
  for (const info of nestedTypes) {
290
- imports.add(info.file);
334
+ if (!imports.has(info.file)) {
335
+ imports.set(info.file, info.parentType);
336
+ }
291
337
  }
292
- for (const file of [...imports].sort()) {
293
- const typeName = file === "workflow" ? "Workflow" : toPascalCase(file);
338
+ for (const [file, typeName] of [...imports.entries()].sort()) {
294
339
  lines.push(`import type { ${typeName} } from './${file}.schema.js';`);
295
340
  }
296
341
  lines.push("");
342
+ const buildAccessExpr = (parentType, propertyPath) => {
343
+ let accessExpr = parentType;
344
+ for (const prop of propertyPath) {
345
+ const accessor = prop === "items"
346
+ ? "[number]"
347
+ : typeof prop === "number"
348
+ ? `[${prop}]`
349
+ : `[${JSON.stringify(prop)}]`;
350
+ accessExpr = `NonNullable<${accessExpr}${accessor}>`;
351
+ }
352
+ return accessExpr;
353
+ };
297
354
  for (const [parentType, types] of [...byParent.entries()].sort()) {
298
355
  lines.push(`// From ${parentType}`);
299
356
  for (const info of types.sort((a, b) => a.typeName.localeCompare(b.typeName))) {
300
357
  if (info.propertyPath.length > 0) {
301
- let accessExpr = parentType;
302
- for (const prop of info.propertyPath) {
303
- accessExpr = `NonNullable<${accessExpr}['${prop}']>`;
304
- }
358
+ const accessExpr = buildAccessExpr(parentType, info.propertyPath);
305
359
  lines.push(`export type ${info.typeName} = ${accessExpr};`);
306
360
  }
307
361
  }
package/dist/cjs/index.js CHANGED
@@ -15,6 +15,8 @@ var __exportStar = (this && this.__exportStar) || function(m, exports) {
15
15
  };
16
16
  Object.defineProperty(exports, "__esModule", { value: true });
17
17
  __exportStar(require("./Types.js"), exports);
18
+ __exportStar(require("./core/analyzeSchema.js"), exports);
19
+ __exportStar(require("./core/emitZod.js"), exports);
18
20
  __exportStar(require("./generators/generateBundle.js"), exports);
19
21
  __exportStar(require("./jsonSchemaToZod.js"), exports);
20
22
  __exportStar(require("./parsers/parseAllOf.js"), exports);
@@ -36,10 +38,12 @@ __exportStar(require("./parsers/parseSchema.js"), exports);
36
38
  __exportStar(require("./parsers/parseSimpleDiscriminatedOneOf.js"), exports);
37
39
  __exportStar(require("./parsers/parseString.js"), exports);
38
40
  __exportStar(require("./utils/anyOrUnknown.js"), exports);
41
+ __exportStar(require("./utils/buildRefRegistry.js"), exports);
39
42
  __exportStar(require("./utils/cycles.js"), exports);
40
43
  __exportStar(require("./utils/half.js"), exports);
41
44
  __exportStar(require("./utils/jsdocs.js"), exports);
42
45
  __exportStar(require("./utils/omit.js"), exports);
46
+ __exportStar(require("./utils/resolveUri.js"), exports);
43
47
  __exportStar(require("./utils/withMessage.js"), exports);
44
48
  __exportStar(require("./zodToJsonSchema.js"), exports);
45
49
  const jsonSchemaToZod_js_1 = require("./jsonSchemaToZod.js");