@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/dist/esm/cli.js CHANGED
@@ -1,10 +1,8 @@
1
1
  #!/usr/bin/env node
2
- "use strict";
3
- Object.defineProperty(exports, "__esModule", { value: true });
4
- const jsonSchemaToZod_js_1 = require("./jsonSchemaToZod.js");
5
- const fs_1 = require("fs");
6
- const path_1 = require("path");
7
- const cliTools_js_1 = require("./utils/cliTools.js");
2
+ import { jsonSchemaToZod } from "./jsonSchemaToZod.js";
3
+ import { writeFileSync, mkdirSync } from "fs";
4
+ import { dirname } from "path";
5
+ import { parseArgs, parseOrReadJSON, readPipe } from "./utils/cliTools.js";
8
6
  const params = {
9
7
  input: {
10
8
  shorthand: "i",
@@ -48,10 +46,10 @@ const params = {
48
46
  },
49
47
  };
50
48
  async function main() {
51
- const args = (0, cliTools_js_1.parseArgs)(params, process.argv, true);
52
- const input = args.input || (await (0, cliTools_js_1.readPipe)());
53
- const jsonSchema = (0, cliTools_js_1.parseOrReadJSON)(input);
54
- const zodSchema = (0, jsonSchemaToZod_js_1.jsonSchemaToZod)(jsonSchema, {
49
+ const args = parseArgs(params, process.argv, true);
50
+ const input = args.input || (await readPipe());
51
+ const jsonSchema = parseOrReadJSON(input);
52
+ const zodSchema = jsonSchemaToZod(jsonSchema, {
55
53
  name: args.name,
56
54
  depth: args.depth,
57
55
  module: args.module || "esm",
@@ -60,8 +58,8 @@ async function main() {
60
58
  withJsdocs: args.withJsdocs,
61
59
  });
62
60
  if (args.output) {
63
- (0, fs_1.mkdirSync)((0, path_1.dirname)(args.output), { recursive: true });
64
- (0, fs_1.writeFileSync)(args.output, zodSchema);
61
+ mkdirSync(dirname(args.output), { recursive: true });
62
+ writeFileSync(args.output, zodSchema);
65
63
  }
66
64
  else {
67
65
  console.log(zodSchema);
@@ -0,0 +1,58 @@
1
+ import { parseSchema } from "../parsers/parseSchema.js";
2
+ import { detectCycles, computeScc } from "../utils/cycles.js";
3
+ import { buildRefRegistry } from "../utils/buildRefRegistry.js";
4
+ export const analyzeSchema = (schema, options = {}) => {
5
+ const { module, name, type, ...rest } = options;
6
+ if (type && (!name || module !== "esm")) {
7
+ throw new Error("Option `type` requires `name` to be set and `module` to be `esm`");
8
+ }
9
+ const normalized = {
10
+ module,
11
+ name,
12
+ type,
13
+ ...rest,
14
+ exportRefs: rest.exportRefs ?? true,
15
+ withMeta: rest.withMeta ?? true,
16
+ };
17
+ const refNameByPointer = new Map();
18
+ const usedNames = new Set();
19
+ if (name) {
20
+ usedNames.add(name);
21
+ }
22
+ const declarations = new Map();
23
+ const dependencies = new Map();
24
+ const { registry: refRegistry, rootBaseUri } = buildRefRegistry(schema);
25
+ const pass1 = {
26
+ module,
27
+ name,
28
+ path: [],
29
+ seen: new Map(),
30
+ declarations,
31
+ dependencies,
32
+ inProgress: new Set(),
33
+ refNameByPointer,
34
+ usedNames,
35
+ root: schema,
36
+ currentSchemaName: name,
37
+ refRegistry,
38
+ rootBaseUri,
39
+ ...rest,
40
+ withMeta: normalized.withMeta,
41
+ };
42
+ parseSchema(schema, pass1);
43
+ const names = Array.from(declarations.keys());
44
+ const cycleRefNames = detectCycles(names, dependencies);
45
+ const { componentByName } = computeScc(names, dependencies);
46
+ return {
47
+ schema,
48
+ options: normalized,
49
+ refNameByPointer,
50
+ usedNames,
51
+ declarations,
52
+ dependencies,
53
+ cycleRefNames,
54
+ cycleComponentByName: componentByName,
55
+ refRegistry,
56
+ rootBaseUri,
57
+ };
58
+ };
@@ -0,0 +1,137 @@
1
+ import { parseSchema } from "../parsers/parseSchema.js";
2
+ import { expandJsdocs } from "../utils/jsdocs.js";
3
+ const orderDeclarations = (entries, dependencies) => {
4
+ const valueByName = new Map(entries);
5
+ const depGraph = new Map();
6
+ for (const [from, set] of dependencies.entries()) {
7
+ const onlyKnown = new Set();
8
+ for (const dep of set) {
9
+ if (valueByName.has(dep) && dep !== from) {
10
+ onlyKnown.add(dep);
11
+ }
12
+ }
13
+ if (onlyKnown.size)
14
+ depGraph.set(from, onlyKnown);
15
+ }
16
+ const names = Array.from(valueByName.keys());
17
+ for (const [name, value] of entries) {
18
+ const deps = depGraph.get(name) ?? new Set();
19
+ for (const candidate of names) {
20
+ if (candidate === name)
21
+ continue;
22
+ const matcher = new RegExp(`\\b${candidate}\\b`);
23
+ if (matcher.test(value)) {
24
+ deps.add(candidate);
25
+ }
26
+ }
27
+ if (deps.size)
28
+ depGraph.set(name, deps);
29
+ }
30
+ const ordered = [];
31
+ const perm = new Set();
32
+ const temp = new Set();
33
+ const visit = (name) => {
34
+ if (perm.has(name))
35
+ return;
36
+ if (temp.has(name)) {
37
+ temp.delete(name);
38
+ perm.add(name);
39
+ ordered.push(name);
40
+ return;
41
+ }
42
+ temp.add(name);
43
+ const deps = depGraph.get(name);
44
+ if (deps) {
45
+ for (const dep of deps) {
46
+ if (valueByName.has(dep)) {
47
+ visit(dep);
48
+ }
49
+ }
50
+ }
51
+ temp.delete(name);
52
+ perm.add(name);
53
+ ordered.push(name);
54
+ };
55
+ for (const name of valueByName.keys()) {
56
+ visit(name);
57
+ }
58
+ const unique = [];
59
+ const seen = new Set();
60
+ for (const name of ordered) {
61
+ if (!seen.has(name)) {
62
+ seen.add(name);
63
+ unique.push(name);
64
+ }
65
+ }
66
+ return unique.map((name) => [name, valueByName.get(name)]);
67
+ };
68
+ export const emitZod = (analysis) => {
69
+ const { schema, options, refNameByPointer, usedNames, cycleRefNames, cycleComponentByName, } = analysis;
70
+ const { module, name, type, noImport, exportRefs, withMeta, ...rest } = options;
71
+ const declarations = new Map();
72
+ const dependencies = new Map();
73
+ const parsedSchema = parseSchema(schema, {
74
+ module,
75
+ name,
76
+ path: [],
77
+ seen: new Map(),
78
+ declarations,
79
+ dependencies,
80
+ inProgress: new Set(),
81
+ refNameByPointer,
82
+ usedNames,
83
+ root: schema,
84
+ currentSchemaName: name,
85
+ cycleRefNames,
86
+ cycleComponentByName,
87
+ refRegistry: analysis.refRegistry,
88
+ rootBaseUri: analysis.rootBaseUri,
89
+ ...rest,
90
+ withMeta,
91
+ });
92
+ const declarationBlock = declarations.size
93
+ ? orderDeclarations(Array.from(declarations.entries()), dependencies)
94
+ .map(([refName, value]) => {
95
+ const shouldExport = exportRefs && module === "esm";
96
+ const decl = `${shouldExport ? "export " : ""}const ${refName} = ${value}`;
97
+ return decl;
98
+ })
99
+ .join("\n")
100
+ : "";
101
+ const jsdocs = rest.withJsdocs && typeof schema === "object" && schema !== null && "description" in schema
102
+ ? expandJsdocs(String(schema.description ?? ""))
103
+ : "";
104
+ const lines = [];
105
+ if (module === "cjs" && !noImport) {
106
+ lines.push(`const { z } = require("zod")`);
107
+ }
108
+ if (module === "esm" && !noImport) {
109
+ lines.push(`import { z } from "zod"`);
110
+ }
111
+ if (declarationBlock) {
112
+ lines.push(declarationBlock);
113
+ }
114
+ if (module === "cjs") {
115
+ const payload = name ? `{ ${JSON.stringify(name)}: ${parsedSchema} }` : parsedSchema;
116
+ lines.push(`${jsdocs}module.exports = ${payload}`);
117
+ }
118
+ else if (module === "esm") {
119
+ const exportLine = `${jsdocs}export ${name ? `const ${name} =` : `default`} ${parsedSchema}`;
120
+ lines.push(exportLine);
121
+ }
122
+ else if (name) {
123
+ lines.push(`${jsdocs}const ${name} = ${parsedSchema}`);
124
+ }
125
+ else {
126
+ lines.push(`${jsdocs}${parsedSchema}`);
127
+ }
128
+ let typeLine;
129
+ if (type && name) {
130
+ const typeName = typeof type === "string" ? type : `${name[0].toUpperCase()}${name.substring(1)}`;
131
+ typeLine = `export type ${typeName} = z.infer<typeof ${name}>`;
132
+ }
133
+ const joined = lines.filter(Boolean).join("\n\n");
134
+ const combined = typeLine ? `${joined}\n${typeLine}` : joined;
135
+ const shouldEndWithNewline = module === "esm" || module === "cjs";
136
+ return `${combined}${shouldEndWithNewline ? "\n" : ""}`;
137
+ };
@@ -1,61 +1,31 @@
1
- "use strict";
2
- Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.generateSchemaBundle = void 0;
4
- const jsonSchemaToZod_js_1 = require("../jsonSchemaToZod.js");
5
- const generateSchemaBundle = (schema, options = {}) => {
1
+ import { analyzeSchema } from "../core/analyzeSchema.js";
2
+ import { emitZod } from "../core/emitZod.js";
3
+ export const generateSchemaBundle = (schema, options = {}) => {
6
4
  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
5
  if (!schema || typeof schema !== "object") {
12
6
  throw new Error("generateSchemaBundle requires an object schema");
13
7
  }
14
- const defs = schema.$defs || schema.definitions || {};
8
+ const defs = schema.$defs || {};
9
+ const definitions = schema.definitions || {};
15
10
  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
- }
11
+ const { rootName, rootTypeName, defInfoMap } = buildBundleContext(defNames, defs, options);
23
12
  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, {
13
+ const targets = planBundleTargets(schema, defs, definitions, defNames, options, rootName, rootTypeName);
14
+ for (const target of targets) {
15
+ const usedRefs = target.usedRefs;
16
+ const analysis = analyzeSchema(target.schemaWithDefs, {
33
17
  ...options,
34
18
  module,
35
- name: info.schemaName,
36
- type: info.typeName,
37
- parserOverride: createRefHandler(defName, defInfoMap, usedRefs, defs, options),
19
+ name: target.schemaName,
20
+ type: target.typeName,
21
+ parserOverride: createRefHandler(target.defName, defInfoMap, usedRefs, {
22
+ ...(target.schemaWithDefs.$defs || {}),
23
+ ...(target.schemaWithDefs.definitions || {}),
24
+ }, options),
38
25
  });
26
+ const zodSchema = emitZod(analysis);
39
27
  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 });
28
+ files.push({ fileName: target.fileName, contents: finalSchema });
59
29
  }
60
30
  // Nested types extraction (optional)
61
31
  const nestedTypesEnabled = options.nestedTypes?.enable;
@@ -69,7 +39,6 @@ const generateSchemaBundle = (schema, options = {}) => {
69
39
  }
70
40
  return { files, defNames };
71
41
  };
72
- exports.generateSchemaBundle = generateSchemaBundle;
73
42
  // ---------------------------------------------------------------------------
74
43
  // Internals
75
44
  // ---------------------------------------------------------------------------
@@ -95,13 +64,32 @@ const buildDefInfoMap = (defNames, defs, options) => {
95
64
  }
96
65
  return map;
97
66
  };
67
+ const buildBundleContext = (defNames, defs, options) => {
68
+ const defInfoMap = buildDefInfoMap(defNames, defs, options);
69
+ const cycles = detectCycles(defInfoMap);
70
+ for (const defName of cycles) {
71
+ const info = defInfoMap.get(defName);
72
+ if (info)
73
+ info.hasCycle = true;
74
+ }
75
+ const rootName = options.splitDefs?.rootName ?? options.name ?? "RootSchema";
76
+ const rootTypeName = typeof options.type === "string"
77
+ ? options.type
78
+ : options.splitDefs?.rootTypeName ?? (typeof options.type === "boolean" && options.type ? rootName : undefined);
79
+ return { defInfoMap, rootName, rootTypeName };
80
+ };
98
81
  const createRefHandler = (currentDefName, defInfoMap, usedRefs, allDefs, options) => {
82
+ const useLazyCrossRefs = options.refResolution?.lazyCrossRefs ?? true;
99
83
  return (schema, refs) => {
100
84
  if (typeof schema["$ref"] === "string") {
101
85
  const refPath = schema["$ref"];
102
86
  const match = refPath.match(/^#\/(?:\$defs|definitions)\/(.+)$/);
103
87
  if (match) {
104
88
  const refName = match[1];
89
+ // Only intercept top-level def refs (no nested path like a/$defs/x)
90
+ if (refName.includes("/")) {
91
+ return undefined;
92
+ }
105
93
  const refInfo = defInfoMap.get(refName);
106
94
  if (refInfo) {
107
95
  // Track imports when referencing other defs
@@ -118,22 +106,22 @@ const createRefHandler = (currentDefName, defInfoMap, usedRefs, allDefs, options
118
106
  });
119
107
  if (resolved)
120
108
  return resolved;
109
+ if (isCycle && useLazyCrossRefs) {
110
+ return `z.lazy(() => ${refInfo.schemaName})`;
111
+ }
121
112
  return refInfo.schemaName;
122
113
  }
114
+ // If the ref points to a local/inline $def (not part of top-level defs),
115
+ // let the default parser resolve it normally.
116
+ if (allDefs && Object.prototype.hasOwnProperty.call(allDefs, refName)) {
117
+ return undefined;
118
+ }
123
119
  }
124
120
  const unknown = options.refResolution?.onUnknownRef?.({ ref: refPath, currentDef: currentDefName });
125
121
  if (unknown)
126
122
  return unknown;
127
123
  return options.useUnknown ? "z.unknown()" : "z.any()";
128
124
  }
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
125
  return undefined;
138
126
  };
139
127
  };
@@ -151,6 +139,52 @@ const buildSchemaFile = (zodCode, usedRefs, defInfoMap, module) => {
151
139
  return zodCode;
152
140
  return zodCode.replace('import { z } from "zod"', `import { z } from "zod"\n${imports.join("\n")}`);
153
141
  };
142
+ const planBundleTargets = (rootSchema, defs, definitions, defNames, options, rootName, rootTypeName) => {
143
+ const targets = [];
144
+ for (const defName of defNames) {
145
+ const defSchema = defs[defName];
146
+ const defSchemaWithDefs = {
147
+ ...defSchema,
148
+ $defs: { ...defs, ...defSchema?.$defs },
149
+ definitions: {
150
+ ...defSchema.definitions,
151
+ ...definitions,
152
+ },
153
+ };
154
+ const pascalName = toPascalCase(defName);
155
+ const schemaName = options.splitDefs?.schemaName?.(defName, { isRoot: false }) ?? `${pascalName}Schema`;
156
+ const typeName = options.splitDefs?.typeName?.(defName, { isRoot: false }) ?? pascalName;
157
+ const fileName = options.splitDefs?.fileName?.(defName, { isRoot: false }) ?? `${defName}.schema.ts`;
158
+ targets.push({
159
+ defName,
160
+ schemaWithDefs: defSchemaWithDefs,
161
+ schemaName,
162
+ typeName,
163
+ fileName,
164
+ usedRefs: new Set(),
165
+ isRoot: false,
166
+ });
167
+ }
168
+ if (options.splitDefs?.includeRoot ?? true) {
169
+ const rootFile = options.splitDefs?.fileName?.("root", { isRoot: true }) ?? "workflow.schema.ts";
170
+ targets.push({
171
+ defName: null,
172
+ schemaWithDefs: {
173
+ ...rootSchema,
174
+ definitions: {
175
+ ...rootSchema.definitions,
176
+ ...definitions,
177
+ },
178
+ },
179
+ schemaName: rootName,
180
+ typeName: rootTypeName,
181
+ fileName: rootFile,
182
+ usedRefs: new Set(),
183
+ isRoot: true,
184
+ });
185
+ }
186
+ return targets;
187
+ };
154
188
  const findRefDependencies = (schema, validDefNames) => {
155
189
  const deps = new Set();
156
190
  function traverse(obj) {
@@ -239,7 +273,7 @@ const findNestedTypesInSchema = (schema, parentTypeName, defNames, currentPath =
239
273
  if (schema === null || typeof schema !== "object")
240
274
  return nestedTypes;
241
275
  const record = schema;
242
- if (record.title && typeof record.title === "string" && record.type === "object") {
276
+ if (record.title && typeof record.title === "string") {
243
277
  const title = record.title;
244
278
  if (title !== parentTypeName && !defNames.map((d) => toPascalCase(d)).includes(title)) {
245
279
  nestedTypes.push({
@@ -250,6 +284,12 @@ const findNestedTypesInSchema = (schema, parentTypeName, defNames, currentPath =
250
284
  });
251
285
  }
252
286
  }
287
+ // inline $defs
288
+ if (record.$defs && typeof record.$defs === "object") {
289
+ for (const [, defSchema] of Object.entries(record.$defs)) {
290
+ nestedTypes.push(...findNestedTypesInSchema(defSchema, parentTypeName, defNames, currentPath));
291
+ }
292
+ }
253
293
  if (record.properties && typeof record.properties === "object") {
254
294
  for (const [propName, propSchema] of Object.entries(record.properties)) {
255
295
  nestedTypes.push(...findNestedTypesInSchema(propSchema, parentTypeName, defNames, [...currentPath, propName]));
@@ -261,10 +301,10 @@ const findNestedTypesInSchema = (schema, parentTypeName, defNames, currentPath =
261
301
  }
262
302
  }
263
303
  if (record.items) {
264
- nestedTypes.push(...findNestedTypesInSchema(record.items, parentTypeName, defNames, currentPath));
304
+ nestedTypes.push(...findNestedTypesInSchema(record.items, parentTypeName, defNames, [...currentPath, "items"]));
265
305
  }
266
306
  if (record.additionalProperties && typeof record.additionalProperties === "object") {
267
- nestedTypes.push(...findNestedTypesInSchema(record.additionalProperties, parentTypeName, defNames, currentPath));
307
+ nestedTypes.push(...findNestedTypesInSchema(record.additionalProperties, parentTypeName, defNames, [...currentPath, "additionalProperties"]));
268
308
  }
269
309
  return nestedTypes;
270
310
  };
@@ -285,23 +325,33 @@ const generateNestedTypesFile = (nestedTypes) => {
285
325
  }
286
326
  byParent.get(info.parentType).push(info);
287
327
  }
288
- const imports = new Set();
328
+ const imports = new Map(); // file -> type name
289
329
  for (const info of nestedTypes) {
290
- imports.add(info.file);
330
+ if (!imports.has(info.file)) {
331
+ imports.set(info.file, info.parentType);
332
+ }
291
333
  }
292
- for (const file of [...imports].sort()) {
293
- const typeName = file === "workflow" ? "Workflow" : toPascalCase(file);
334
+ for (const [file, typeName] of [...imports.entries()].sort()) {
294
335
  lines.push(`import type { ${typeName} } from './${file}.schema.js';`);
295
336
  }
296
337
  lines.push("");
338
+ const buildAccessExpr = (parentType, propertyPath) => {
339
+ let accessExpr = parentType;
340
+ for (const prop of propertyPath) {
341
+ const accessor = prop === "items"
342
+ ? "[number]"
343
+ : typeof prop === "number"
344
+ ? `[${prop}]`
345
+ : `[${JSON.stringify(prop)}]`;
346
+ accessExpr = `NonNullable<${accessExpr}${accessor}>`;
347
+ }
348
+ return accessExpr;
349
+ };
297
350
  for (const [parentType, types] of [...byParent.entries()].sort()) {
298
351
  lines.push(`// From ${parentType}`);
299
352
  for (const info of types.sort((a, b) => a.typeName.localeCompare(b.typeName))) {
300
353
  if (info.propertyPath.length > 0) {
301
- let accessExpr = parentType;
302
- for (const prop of info.propertyPath) {
303
- accessExpr = `NonNullable<${accessExpr}['${prop}']>`;
304
- }
354
+ const accessExpr = buildAccessExpr(parentType, info.propertyPath);
305
355
  lines.push(`export type ${info.typeName} = ${accessExpr};`);
306
356
  }
307
357
  }
package/dist/esm/index.js CHANGED
@@ -1,46 +1,34 @@
1
- "use strict";
2
- var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
- if (k2 === undefined) k2 = k;
4
- var desc = Object.getOwnPropertyDescriptor(m, k);
5
- if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
- desc = { enumerable: true, get: function() { return m[k]; } };
7
- }
8
- Object.defineProperty(o, k2, desc);
9
- }) : (function(o, m, k, k2) {
10
- if (k2 === undefined) k2 = k;
11
- o[k2] = m[k];
12
- }));
13
- var __exportStar = (this && this.__exportStar) || function(m, exports) {
14
- for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p);
15
- };
16
- Object.defineProperty(exports, "__esModule", { value: true });
17
- __exportStar(require("./Types.js"), exports);
18
- __exportStar(require("./generators/generateBundle.js"), exports);
19
- __exportStar(require("./jsonSchemaToZod.js"), exports);
20
- __exportStar(require("./parsers/parseAllOf.js"), exports);
21
- __exportStar(require("./parsers/parseAnyOf.js"), exports);
22
- __exportStar(require("./parsers/parseArray.js"), exports);
23
- __exportStar(require("./parsers/parseBoolean.js"), exports);
24
- __exportStar(require("./parsers/parseConst.js"), exports);
25
- __exportStar(require("./parsers/parseDefault.js"), exports);
26
- __exportStar(require("./parsers/parseEnum.js"), exports);
27
- __exportStar(require("./parsers/parseIfThenElse.js"), exports);
28
- __exportStar(require("./parsers/parseMultipleType.js"), exports);
29
- __exportStar(require("./parsers/parseNot.js"), exports);
30
- __exportStar(require("./parsers/parseNull.js"), exports);
31
- __exportStar(require("./parsers/parseNullable.js"), exports);
32
- __exportStar(require("./parsers/parseNumber.js"), exports);
33
- __exportStar(require("./parsers/parseObject.js"), exports);
34
- __exportStar(require("./parsers/parseOneOf.js"), exports);
35
- __exportStar(require("./parsers/parseSchema.js"), exports);
36
- __exportStar(require("./parsers/parseSimpleDiscriminatedOneOf.js"), exports);
37
- __exportStar(require("./parsers/parseString.js"), exports);
38
- __exportStar(require("./utils/anyOrUnknown.js"), exports);
39
- __exportStar(require("./utils/cycles.js"), exports);
40
- __exportStar(require("./utils/half.js"), exports);
41
- __exportStar(require("./utils/jsdocs.js"), exports);
42
- __exportStar(require("./utils/omit.js"), exports);
43
- __exportStar(require("./utils/withMessage.js"), exports);
44
- __exportStar(require("./zodToJsonSchema.js"), exports);
45
- const jsonSchemaToZod_js_1 = require("./jsonSchemaToZod.js");
46
- exports.default = jsonSchemaToZod_js_1.jsonSchemaToZod;
1
+ export * from "./Types.js";
2
+ export * from "./core/analyzeSchema.js";
3
+ export * from "./core/emitZod.js";
4
+ export * from "./generators/generateBundle.js";
5
+ export * from "./jsonSchemaToZod.js";
6
+ export * from "./parsers/parseAllOf.js";
7
+ export * from "./parsers/parseAnyOf.js";
8
+ export * from "./parsers/parseArray.js";
9
+ export * from "./parsers/parseBoolean.js";
10
+ export * from "./parsers/parseConst.js";
11
+ export * from "./parsers/parseDefault.js";
12
+ export * from "./parsers/parseEnum.js";
13
+ export * from "./parsers/parseIfThenElse.js";
14
+ export * from "./parsers/parseMultipleType.js";
15
+ export * from "./parsers/parseNot.js";
16
+ export * from "./parsers/parseNull.js";
17
+ export * from "./parsers/parseNullable.js";
18
+ export * from "./parsers/parseNumber.js";
19
+ export * from "./parsers/parseObject.js";
20
+ export * from "./parsers/parseOneOf.js";
21
+ export * from "./parsers/parseSchema.js";
22
+ export * from "./parsers/parseSimpleDiscriminatedOneOf.js";
23
+ export * from "./parsers/parseString.js";
24
+ export * from "./utils/anyOrUnknown.js";
25
+ export * from "./utils/buildRefRegistry.js";
26
+ export * from "./utils/cycles.js";
27
+ export * from "./utils/half.js";
28
+ export * from "./utils/jsdocs.js";
29
+ export * from "./utils/omit.js";
30
+ export * from "./utils/resolveUri.js";
31
+ export * from "./utils/withMessage.js";
32
+ export * from "./zodToJsonSchema.js";
33
+ import { jsonSchemaToZod } from "./jsonSchemaToZod.js";
34
+ export default jsonSchemaToZod;