@gabrielbryk/json-schema-to-zod 2.8.0 → 2.9.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 (60) hide show
  1. package/CHANGELOG.md +13 -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 +103 -59
  5. package/dist/cjs/index.js +4 -0
  6. package/dist/cjs/jsonSchemaToZod.js +5 -167
  7. package/dist/cjs/parsers/parseSchema.js +124 -24
  8. package/dist/cjs/utils/buildRefRegistry.js +56 -0
  9. package/dist/cjs/utils/resolveUri.js +16 -0
  10. package/dist/esm/Types.js +1 -2
  11. package/dist/esm/cli.js +10 -12
  12. package/dist/esm/core/analyzeSchema.js +58 -0
  13. package/dist/esm/core/emitZod.js +137 -0
  14. package/dist/esm/generators/generateBundle.js +104 -64
  15. package/dist/esm/index.js +34 -46
  16. package/dist/esm/jsonSchemaToZod.js +5 -171
  17. package/dist/esm/parsers/parseAllOf.js +5 -8
  18. package/dist/esm/parsers/parseAnyOf.js +6 -10
  19. package/dist/esm/parsers/parseArray.js +11 -15
  20. package/dist/esm/parsers/parseBoolean.js +1 -5
  21. package/dist/esm/parsers/parseConst.js +1 -5
  22. package/dist/esm/parsers/parseDefault.js +3 -7
  23. package/dist/esm/parsers/parseEnum.js +1 -5
  24. package/dist/esm/parsers/parseIfThenElse.js +5 -9
  25. package/dist/esm/parsers/parseMultipleType.js +3 -7
  26. package/dist/esm/parsers/parseNot.js +4 -8
  27. package/dist/esm/parsers/parseNull.js +1 -5
  28. package/dist/esm/parsers/parseNullable.js +4 -8
  29. package/dist/esm/parsers/parseNumber.js +11 -15
  30. package/dist/esm/parsers/parseObject.js +25 -28
  31. package/dist/esm/parsers/parseOneOf.js +6 -10
  32. package/dist/esm/parsers/parseSchema.js +183 -87
  33. package/dist/esm/parsers/parseSimpleDiscriminatedOneOf.js +6 -10
  34. package/dist/esm/parsers/parseString.js +11 -15
  35. package/dist/esm/utils/anyOrUnknown.js +1 -5
  36. package/dist/esm/utils/buildRefRegistry.js +52 -0
  37. package/dist/esm/utils/cliTools.js +7 -13
  38. package/dist/esm/utils/cycles.js +3 -9
  39. package/dist/esm/utils/half.js +1 -5
  40. package/dist/esm/utils/jsdocs.js +3 -8
  41. package/dist/esm/utils/omit.js +1 -5
  42. package/dist/esm/utils/resolveUri.js +12 -0
  43. package/dist/esm/utils/withMessage.js +1 -4
  44. package/dist/esm/zodToJsonSchema.js +1 -4
  45. package/dist/types/Types.d.ts +28 -0
  46. package/dist/types/core/analyzeSchema.d.ts +24 -0
  47. package/dist/types/core/emitZod.d.ts +2 -0
  48. package/dist/types/generators/generateBundle.d.ts +5 -0
  49. package/dist/types/index.d.ts +4 -0
  50. package/dist/types/jsonSchemaToZod.d.ts +1 -1
  51. package/dist/types/parsers/parseSchema.d.ts +2 -1
  52. package/dist/types/utils/buildRefRegistry.d.ts +12 -0
  53. package/dist/types/utils/resolveUri.d.ts +1 -0
  54. package/docs/proposals/bundle-refactor.md +43 -0
  55. package/docs/proposals/ref-anchor-support.md +65 -0
  56. package/eslint.config.js +26 -0
  57. package/package.json +10 -4
  58. /package/{jest.config.js → jest.config.cjs} +0 -0
  59. /package/{postcjs.js → postcjs.cjs} +0 -0
  60. /package/{postesm.js → postesm.cjs} +0 -0
@@ -19,9 +19,12 @@ const parseOneOf_js_1 = require("./parseOneOf.js");
19
19
  const parseSimpleDiscriminatedOneOf_js_1 = require("./parseSimpleDiscriminatedOneOf.js");
20
20
  const parseNullable_js_1 = require("./parseNullable.js");
21
21
  const anyOrUnknown_js_1 = require("../utils/anyOrUnknown.js");
22
+ const resolveUri_js_1 = require("../utils/resolveUri.js");
23
+ const buildRefRegistry_js_1 = require("../utils/buildRefRegistry.js");
22
24
  const parseSchema = (schema, refs = { seen: new Map(), path: [] }, blockMeta) => {
23
25
  // Ensure ref bookkeeping exists so $ref declarations and getter-based recursion work
24
26
  refs.root = refs.root ?? schema;
27
+ refs.rootBaseUri = refs.rootBaseUri ?? "root:///";
25
28
  refs.declarations = refs.declarations ?? new Map();
26
29
  refs.dependencies = refs.dependencies ?? new Map();
27
30
  refs.inProgress = refs.inProgress ?? new Set();
@@ -29,8 +32,20 @@ const parseSchema = (schema, refs = { seen: new Map(), path: [] }, blockMeta) =>
29
32
  refs.usedNames = refs.usedNames ?? new Set();
30
33
  if (typeof schema !== "object")
31
34
  return schema ? (0, anyOrUnknown_js_1.anyOrUnknown)(refs) : "z.never()";
35
+ const parentBase = refs.currentBaseUri ?? refs.rootBaseUri ?? "root:///";
36
+ const baseUri = typeof schema.$id === "string"
37
+ ? (0, resolveUri_js_1.resolveUri)(parentBase, schema.$id)
38
+ : parentBase;
39
+ const dynamicAnchors = Array.isArray(refs.dynamicAnchors) ? [...refs.dynamicAnchors] : [];
40
+ if (typeof schema.$dynamicAnchor === "string") {
41
+ dynamicAnchors.push({
42
+ name: schema.$dynamicAnchor,
43
+ uri: baseUri,
44
+ path: refs.path,
45
+ });
46
+ }
32
47
  if (refs.parserOverride) {
33
- const custom = refs.parserOverride(schema, refs);
48
+ const custom = refs.parserOverride(schema, { ...refs, currentBaseUri: baseUri, dynamicAnchors });
34
49
  if (typeof custom === "string") {
35
50
  return custom;
36
51
  }
@@ -50,14 +65,14 @@ const parseSchema = (schema, refs = { seen: new Map(), path: [] }, blockMeta) =>
50
65
  refs.seen.set(schema, seen);
51
66
  }
52
67
  if (exports.its.a.ref(schema)) {
53
- const parsedRef = parseRef(schema, refs);
68
+ const parsedRef = parseRef(schema, { ...refs, currentBaseUri: baseUri, dynamicAnchors });
54
69
  seen.r = parsedRef;
55
70
  return parsedRef;
56
71
  }
57
- let parsed = selectParser(schema, refs);
72
+ let parsed = selectParser(schema, { ...refs, currentBaseUri: baseUri, dynamicAnchors });
58
73
  if (!blockMeta) {
59
74
  if (!refs.withoutDescribes) {
60
- parsed = addDescribes(schema, parsed, refs);
75
+ parsed = addDescribes(schema, parsed, { ...refs, currentBaseUri: baseUri, dynamicAnchors });
61
76
  }
62
77
  if (!refs.withoutDefaults) {
63
78
  parsed = addDefaults(schema, parsed);
@@ -69,17 +84,20 @@ const parseSchema = (schema, refs = { seen: new Map(), path: [] }, blockMeta) =>
69
84
  };
70
85
  exports.parseSchema = parseSchema;
71
86
  const parseRef = (schema, refs) => {
72
- const resolved = resolveRef(refs.root, schema.$ref);
87
+ const refValue = schema.$dynamicRef ?? schema.$ref;
88
+ const resolved = resolveRef(schema, refValue, refs);
73
89
  if (!resolved) {
90
+ refs.onUnresolvedRef?.(refValue, refs.path);
74
91
  return (0, anyOrUnknown_js_1.anyOrUnknown)(refs);
75
92
  }
76
- const { schema: target, path } = resolved;
77
- const refName = getOrCreateRefName(schema.$ref, path, refs);
93
+ const { schema: target, path, pointerKey } = resolved;
94
+ const refName = getOrCreateRefName(pointerKey, path, refs);
78
95
  if (!refs.declarations.has(refName) && !refs.inProgress.has(refName)) {
79
96
  refs.inProgress.add(refName);
80
97
  const declaration = (0, exports.parseSchema)(target, {
81
98
  ...refs,
82
99
  path,
100
+ currentBaseUri: resolved.baseUri,
83
101
  currentSchemaName: refName,
84
102
  root: refs.root,
85
103
  });
@@ -132,23 +150,93 @@ const addDescribes = (schema, parsed, refs) => {
132
150
  }
133
151
  return parsed;
134
152
  };
135
- const resolveRef = (root, ref) => {
136
- if (!root || !ref.startsWith("#/"))
137
- return undefined;
138
- const rawSegments = ref
139
- .slice(2)
140
- .split("/")
141
- .filter((segment) => segment.length > 0)
142
- .map(decodePointerSegment);
143
- let current = root;
144
- for (const segment of rawSegments) {
145
- if (typeof current !== "object" || current === null)
146
- return undefined;
147
- current = current[segment];
148
- }
149
- return { schema: current, path: rawSegments };
153
+ const resolveRef = (schemaNode, ref, refs) => {
154
+ const base = refs.currentBaseUri ?? refs.rootBaseUri ?? "root:///";
155
+ // Handle dynamicRef lookup via dynamicAnchors stack
156
+ const isDynamic = typeof schemaNode.$dynamicRef === "string";
157
+ if (isDynamic && refs.dynamicAnchors && ref.startsWith("#")) {
158
+ const name = ref.slice(1);
159
+ for (let i = refs.dynamicAnchors.length - 1; i >= 0; i -= 1) {
160
+ const entry = refs.dynamicAnchors[i];
161
+ if (entry.name === name) {
162
+ const key = `${entry.uri}#${name}`;
163
+ const target = refs.refRegistry?.get(key);
164
+ if (target) {
165
+ return { schema: target.schema, path: target.path, baseUri: target.baseUri, pointerKey: key };
166
+ }
167
+ }
168
+ }
169
+ }
170
+ // Resolve URI against base
171
+ const resolvedUri = (0, resolveUri_js_1.resolveUri)(base, ref);
172
+ const [uriBase, fragment] = resolvedUri.split("#");
173
+ const key = fragment ? `${uriBase}#${fragment}` : uriBase;
174
+ let regEntry = refs.refRegistry?.get(key);
175
+ if (regEntry) {
176
+ return { schema: regEntry.schema, path: regEntry.path, baseUri: regEntry.baseUri, pointerKey: key };
177
+ }
178
+ // Legacy recursive ref: treat as dynamic to __recursive__
179
+ if (schemaNode.$recursiveRef) {
180
+ const recursiveKey = `${base}#__recursive__`;
181
+ regEntry = refs.refRegistry?.get(recursiveKey);
182
+ if (regEntry) {
183
+ return {
184
+ schema: regEntry.schema,
185
+ path: regEntry.path,
186
+ baseUri: regEntry.baseUri,
187
+ pointerKey: recursiveKey,
188
+ };
189
+ }
190
+ }
191
+ // External resolver hook
192
+ const extBase = uriBaseFromRef(resolvedUri);
193
+ if (refs.resolveExternalRef && extBase && !isLocalBase(extBase, refs.rootBaseUri ?? "")) {
194
+ const loaded = refs.resolveExternalRef(extBase);
195
+ if (loaded) {
196
+ // If async resolver is used synchronously here, it will be ignored; keep simple sync for now
197
+ const schema = loaded.then ? undefined : loaded;
198
+ if (schema) {
199
+ const { registry } = (0, buildRefRegistry_js_1.buildRefRegistry)(schema, extBase);
200
+ registry.forEach((entry, k) => refs.refRegistry?.set(k, entry));
201
+ regEntry = refs.refRegistry?.get(key);
202
+ if (regEntry) {
203
+ return {
204
+ schema: regEntry.schema,
205
+ path: regEntry.path,
206
+ baseUri: regEntry.baseUri,
207
+ pointerKey: key,
208
+ };
209
+ }
210
+ }
211
+ }
212
+ }
213
+ // Backward compatibility: JSON Pointer into root
214
+ if (refs.root && ref.startsWith("#/")) {
215
+ const rawSegments = ref
216
+ .slice(2)
217
+ .split("/")
218
+ .filter((segment) => segment.length > 0)
219
+ .map(decodePointerSegment);
220
+ let current = refs.root;
221
+ for (const segment of rawSegments) {
222
+ if (typeof current !== "object" || current === null)
223
+ return undefined;
224
+ current = current[segment];
225
+ }
226
+ return { schema: current, path: rawSegments, baseUri: base, pointerKey: ref };
227
+ }
228
+ return undefined;
150
229
  };
151
230
  const decodePointerSegment = (segment) => segment.replace(/~1/g, "/").replace(/~0/g, "~");
231
+ const uriBaseFromRef = (resolvedUri) => {
232
+ const hashIdx = resolvedUri.indexOf("#");
233
+ return hashIdx === -1 ? resolvedUri : resolvedUri.slice(0, hashIdx);
234
+ };
235
+ const isLocalBase = (base, rootBase) => {
236
+ if (!rootBase)
237
+ return false;
238
+ return base === rootBase;
239
+ };
152
240
  const getOrCreateRefName = (pointer, path, refs) => {
153
241
  if (refs.refNameByPointer?.has(pointer)) {
154
242
  return refs.refNameByPointer.get(pointer);
@@ -159,12 +247,24 @@ const getOrCreateRefName = (pointer, path, refs) => {
159
247
  return preferred;
160
248
  };
161
249
  const buildNameFromPath = (path, used) => {
162
- const filtered = path.filter((segment) => segment !== "$defs" && segment !== "definitions" && segment !== "properties");
250
+ const filtered = path
251
+ .map((segment, idx) => {
252
+ if (idx === 0 && (segment === "$defs" || segment === "definitions")) {
253
+ return undefined; // root-level defs prefix is redundant for naming
254
+ }
255
+ if (segment === "properties")
256
+ return undefined; // skip noisy properties segment
257
+ if (segment === "$defs" || segment === "definitions")
258
+ return "Defs";
259
+ return segment;
260
+ })
261
+ .filter((segment) => segment !== undefined);
163
262
  const base = filtered.length
164
263
  ? filtered
165
264
  .map((segment) => typeof segment === "number"
166
265
  ? `Ref${segment}`
167
266
  : segment
267
+ .toString()
168
268
  .replace(/[^a-zA-Z0-9_$]/g, " ")
169
269
  .split(" ")
170
270
  .filter(Boolean)
@@ -266,7 +366,7 @@ exports.its = {
266
366
  nullable: (x) => x.nullable === true,
267
367
  multipleType: (x) => Array.isArray(x.type),
268
368
  not: (x) => x.not !== undefined,
269
- ref: (x) => typeof x.$ref === "string",
369
+ ref: (x) => typeof x.$ref === "string" || typeof x.$dynamicRef === "string",
270
370
  const: (x) => x.const !== undefined,
271
371
  primitive: (x, p) => x.type === p,
272
372
  conditional: (x) => Boolean("if" in x && x.if && "then" in x && "else" in x && x.then && x.else),
@@ -0,0 +1,56 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.buildRefRegistry = void 0;
4
+ const resolveUri_js_1 = require("./resolveUri.js");
5
+ const buildRefRegistry = (schema, rootBaseUri = "root:///", opts = {}) => {
6
+ const registry = new Map();
7
+ const walk = (node, baseUri, path) => {
8
+ if (typeof node !== "object" || node === null)
9
+ return;
10
+ const obj = node;
11
+ const nextBase = obj.$id ? (0, resolveUri_js_1.resolveUri)(baseUri, obj.$id) : baseUri;
12
+ // Legacy recursive anchor
13
+ if (obj.$recursiveAnchor === true) {
14
+ const name = "__recursive__";
15
+ registry.set(`${nextBase}#${name}`, {
16
+ schema: node,
17
+ path,
18
+ baseUri: nextBase,
19
+ dynamic: true,
20
+ anchor: name,
21
+ });
22
+ }
23
+ // Register base entry
24
+ registry.set(nextBase, { schema: node, path, baseUri: nextBase });
25
+ if (typeof obj.$anchor === "string") {
26
+ registry.set(`${nextBase}#${obj.$anchor}`, {
27
+ schema: node,
28
+ path,
29
+ baseUri: nextBase,
30
+ anchor: obj.$anchor,
31
+ });
32
+ }
33
+ if (typeof obj.$dynamicAnchor === "string") {
34
+ const name = obj.$dynamicAnchor;
35
+ registry.set(`${nextBase}#${name}`, {
36
+ schema: node,
37
+ path,
38
+ baseUri: nextBase,
39
+ dynamic: true,
40
+ anchor: name,
41
+ });
42
+ }
43
+ for (const key in obj) {
44
+ const value = obj[key];
45
+ if (Array.isArray(value)) {
46
+ value.forEach((v, i) => walk(v, nextBase, [...path, key, i]));
47
+ }
48
+ else if (typeof value === "object" && value !== null) {
49
+ walk(value, nextBase, [...path, key]);
50
+ }
51
+ }
52
+ };
53
+ walk(schema, rootBaseUri, []);
54
+ return { registry, rootBaseUri };
55
+ };
56
+ exports.buildRefRegistry = buildRefRegistry;
@@ -0,0 +1,16 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.resolveUri = void 0;
4
+ const resolveUri = (base, ref) => {
5
+ try {
6
+ // If ref is absolute, new URL will accept it; otherwise resolves against base
7
+ return new URL(ref, base).toString();
8
+ }
9
+ catch {
10
+ // Fallback: simple concatenation to avoid throwing; keep ref as-is
11
+ if (ref.startsWith("#"))
12
+ return `${base}${ref}`;
13
+ return ref;
14
+ }
15
+ };
16
+ exports.resolveUri = resolveUri;
package/dist/esm/Types.js CHANGED
@@ -1,2 +1 @@
1
- "use strict";
2
- Object.defineProperty(exports, "__esModule", { value: true });
1
+ export {};
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 !== "boolean" && schema.description
102
+ ? expandJsdocs(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
+ };