@gabrielbryk/json-schema-to-zod 2.9.0 → 2.10.1

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.
package/CHANGELOG.md CHANGED
@@ -1,5 +1,12 @@
1
1
  # @gabrielbryk/json-schema-to-zod
2
2
 
3
+ ## 2.10.1
4
+
5
+ ### Patch Changes
6
+
7
+ - 1c318a0: Ensure bundled outputs remain type-safe by grouping strongly connected defs, emitting lazy getters for recursive object refs, and reordering bundle members to avoid duplicate exports.
8
+ - 87526f0: Preserve precise types for recursive schemas by emitting typed lazy wrappers instead of erasing types, and add a workflow regression type-check to guard against inference blowups.
9
+
3
10
  ## 2.9.0
4
11
 
5
12
  ### Minor Changes
@@ -11,6 +18,11 @@
11
18
  ### Patch Changes
12
19
 
13
20
  - b8f6248: - Commit remaining utility, ref resolution test, and config updates.
21
+ - 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.
22
+ - b8f7b29: Fix type errors in CI by replacing symbol-based allOf indexing, guarding invalid refs, and tightening string content schema parsing types.
23
+ - 691cc5b: - Added ESLint (recommended + no-require-imports) and cleaned all lint issues across src/tests; tightened types and removed unused vars.
24
+ - Ensured ESM-friendly test evals and ref/anchor resolver code without require usage.
25
+ - 4d127fe: Remove fallback to the non-existent ZodError.errors property in generated conditional schemas; rely on ZodError.issues to avoid TypeScript errors.
14
26
  - 7d257dd: - Ensure dist/esm emits real ESM with NodeNext settings and type:module, and update tests to run under ESM by providing createRequire shims.
15
27
 
16
28
  ## 2.8.0
@@ -73,6 +73,16 @@ const emitZod = (analysis) => {
73
73
  const { module, name, type, noImport, exportRefs, withMeta, ...rest } = options;
74
74
  const declarations = new Map();
75
75
  const dependencies = new Map();
76
+ const reserveName = (base) => {
77
+ let candidate = base;
78
+ let i = 1;
79
+ while (usedNames.has(candidate) || declarations.has(candidate)) {
80
+ candidate = `${base}${i}`;
81
+ i += 1;
82
+ }
83
+ usedNames.add(candidate);
84
+ return candidate;
85
+ };
76
86
  const parsedSchema = (0, parseSchema_js_1.parseSchema)(schema, {
77
87
  module,
78
88
  name,
@@ -94,15 +104,21 @@ const emitZod = (analysis) => {
94
104
  });
95
105
  const declarationBlock = declarations.size
96
106
  ? orderDeclarations(Array.from(declarations.entries()), dependencies)
97
- .map(([refName, value]) => {
107
+ .flatMap(([refName, value]) => {
98
108
  const shouldExport = exportRefs && module === "esm";
99
- const decl = `${shouldExport ? "export " : ""}const ${refName} = ${value}`;
100
- return decl;
109
+ const isCycle = cycleRefNames.has(refName);
110
+ if (!isCycle) {
111
+ return [`${shouldExport ? "export " : ""}const ${refName} = ${value}`];
112
+ }
113
+ const baseName = `${refName}Def`;
114
+ const lines = [`const ${baseName} = ${value}`];
115
+ lines.push(`${shouldExport ? "export " : ""}const ${refName} = ${baseName}`);
116
+ return lines;
101
117
  })
102
118
  .join("\n")
103
119
  : "";
104
- const jsdocs = rest.withJsdocs && typeof schema !== "boolean" && schema.description
105
- ? (0, jsdocs_js_1.expandJsdocs)(schema.description)
120
+ const jsdocs = rest.withJsdocs && typeof schema === "object" && schema !== null && "description" in schema
121
+ ? (0, jsdocs_js_1.expandJsdocs)(String(schema.description ?? ""))
106
122
  : "";
107
123
  const lines = [];
108
124
  if (module === "cjs" && !noImport) {
@@ -11,23 +11,27 @@ const generateSchemaBundle = (schema, options = {}) => {
11
11
  const defs = schema.$defs || {};
12
12
  const definitions = schema.definitions || {};
13
13
  const defNames = Object.keys(defs);
14
- const { rootName, rootTypeName, defInfoMap } = buildBundleContext(defNames, defs, options);
14
+ const { rootName, rootTypeName, defInfoMap, groups } = buildBundleContext(defNames, defs, options);
15
15
  const files = [];
16
- const targets = planBundleTargets(schema, defs, definitions, defNames, options, rootName, rootTypeName);
16
+ const targets = planBundleTargets(schema, defs, definitions, defNames, options, rootName, defInfoMap, rootTypeName);
17
17
  for (const target of targets) {
18
18
  const usedRefs = target.usedRefs;
19
- const analysis = (0, analyzeSchema_js_1.analyzeSchema)(target.schemaWithDefs, {
20
- ...options,
21
- module,
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),
28
- });
29
- const zodSchema = (0, emitZod_js_1.emitZod)(analysis);
30
- const finalSchema = buildSchemaFile(zodSchema, usedRefs, defInfoMap, module);
19
+ const zodParts = [];
20
+ for (const member of target.members) {
21
+ const analysis = (0, analyzeSchema_js_1.analyzeSchema)(member.schemaWithDefs, {
22
+ ...options,
23
+ module,
24
+ name: member.schemaName,
25
+ type: member.typeName,
26
+ parserOverride: createRefHandler(member.defName, defInfoMap, usedRefs, {
27
+ ...(member.schemaWithDefs.$defs || {}),
28
+ ...(member.schemaWithDefs.definitions || {}),
29
+ }, options, target.groupId),
30
+ });
31
+ const zodSchema = (0, emitZod_js_1.emitZod)(analysis);
32
+ zodParts.push(zodSchema);
33
+ }
34
+ const finalSchema = buildSchemaFile(zodParts, usedRefs, defInfoMap, module, target);
31
35
  files.push({ fileName: target.fileName, contents: finalSchema });
32
36
  }
33
37
  // Nested types extraction (optional)
@@ -57,13 +61,16 @@ const buildDefInfoMap = (defNames, defs, options) => {
57
61
  const pascalName = toPascalCase(defName);
58
62
  const schemaName = options.splitDefs?.schemaName?.(defName, { isRoot: false }) ?? `${pascalName}Schema`;
59
63
  const typeName = options.splitDefs?.typeName?.(defName, { isRoot: false }) ?? pascalName;
64
+ const fileName = options.splitDefs?.fileName?.(defName, { isRoot: false }) ?? `${defName}.schema.ts`;
60
65
  map.set(defName, {
61
66
  name: defName,
62
67
  pascalName,
63
68
  schemaName,
64
69
  typeName,
70
+ fileName,
65
71
  dependencies,
66
72
  hasCycle: false,
73
+ groupId: "",
67
74
  });
68
75
  }
69
76
  return map;
@@ -76,13 +83,22 @@ const buildBundleContext = (defNames, defs, options) => {
76
83
  if (info)
77
84
  info.hasCycle = true;
78
85
  }
86
+ const groups = buildSccGroups(defInfoMap);
87
+ for (const [groupId, members] of groups) {
88
+ for (const defName of members) {
89
+ const info = defInfoMap.get(defName);
90
+ if (info)
91
+ info.groupId = groupId;
92
+ }
93
+ }
79
94
  const rootName = options.splitDefs?.rootName ?? options.name ?? "RootSchema";
80
95
  const rootTypeName = typeof options.type === "string"
81
96
  ? options.type
82
97
  : options.splitDefs?.rootTypeName ?? (typeof options.type === "boolean" && options.type ? rootName : undefined);
83
- return { defInfoMap, rootName, rootTypeName };
98
+ return { defInfoMap, rootName, rootTypeName, groups };
84
99
  };
85
- const createRefHandler = (currentDefName, defInfoMap, usedRefs, allDefs, options) => {
100
+ const createRefHandler = (currentDefName, defInfoMap, usedRefs, allDefs, options, currentGroupId) => {
101
+ const useLazyCrossRefs = options.refResolution?.lazyCrossRefs ?? true;
86
102
  return (schema, refs) => {
87
103
  if (typeof schema["$ref"] === "string") {
88
104
  const refPath = schema["$ref"];
@@ -96,7 +112,7 @@ const createRefHandler = (currentDefName, defInfoMap, usedRefs, allDefs, options
96
112
  const refInfo = defInfoMap.get(refName);
97
113
  if (refInfo) {
98
114
  // Track imports when referencing other defs
99
- if (refName !== currentDefName) {
115
+ if (refName !== currentDefName && refInfo.groupId !== currentGroupId) {
100
116
  usedRefs.add(refName);
101
117
  }
102
118
  const isCycle = refName === currentDefName || (refInfo.hasCycle && !!currentDefName);
@@ -109,7 +125,14 @@ const createRefHandler = (currentDefName, defInfoMap, usedRefs, allDefs, options
109
125
  });
110
126
  if (resolved)
111
127
  return resolved;
112
- if (isCycle && options.refResolution?.lazyCrossRefs) {
128
+ if (isCycle && useLazyCrossRefs) {
129
+ const inObjectProperty = refs.path.includes("properties") ||
130
+ refs.path.includes("patternProperties") ||
131
+ refs.path.includes("additionalProperties");
132
+ if (inObjectProperty && refName === currentDefName) {
133
+ // Self-recursion inside object getters can safely reference the schema name
134
+ return refInfo.schemaName;
135
+ }
113
136
  return `z.lazy(() => ${refInfo.schemaName})`;
114
137
  }
115
138
  return refInfo.schemaName;
@@ -128,42 +151,73 @@ const createRefHandler = (currentDefName, defInfoMap, usedRefs, allDefs, options
128
151
  return undefined;
129
152
  };
130
153
  };
131
- const buildSchemaFile = (zodCode, usedRefs, defInfoMap, module) => {
154
+ const buildSchemaFile = (zodCodeParts, usedRefs, defInfoMap, module, target) => {
132
155
  if (module !== "esm")
133
- return zodCode;
134
- const imports = [];
156
+ return zodCodeParts.join("\n");
157
+ const groupFileById = new Map();
158
+ for (const info of defInfoMap.values()) {
159
+ if (!groupFileById.has(info.groupId)) {
160
+ groupFileById.set(info.groupId, info.fileName.replace(/\.ts$/, ".js"));
161
+ }
162
+ }
163
+ const importsByFile = new Map();
135
164
  for (const refName of [...usedRefs].sort()) {
136
165
  const refInfo = defInfoMap.get(refName);
137
166
  if (refInfo) {
138
- imports.push(`import { ${refInfo.schemaName} } from './${refName}.schema.js';`);
167
+ const groupFile = groupFileById.get(refInfo.groupId) ?? refInfo.fileName.replace(/\.ts$/, ".js");
168
+ const path = `./${groupFile}`;
169
+ const set = importsByFile.get(path) ?? new Set();
170
+ set.add(refInfo.schemaName);
171
+ importsByFile.set(path, set);
139
172
  }
140
173
  }
141
- if (!imports.length)
142
- return zodCode;
143
- return zodCode.replace('import { z } from "zod"', `import { z } from "zod"\n${imports.join("\n")}`);
174
+ const imports = [];
175
+ for (const [path, names] of [...importsByFile.entries()].sort(([a], [b]) => a.localeCompare(b))) {
176
+ imports.push(`import { ${[...names].sort().join(", ")} } from '${path}';`);
177
+ }
178
+ const body = zodCodeParts
179
+ .map((code, idx) => {
180
+ if (idx === 0)
181
+ return code;
182
+ return code.replace(/^import \{ z \} from "zod"\n?/, "");
183
+ })
184
+ .join("\n");
185
+ return imports.length
186
+ ? body.replace('import { z } from "zod"', `import { z } from "zod"\n${imports.join("\n")}`)
187
+ : body;
144
188
  };
145
- const planBundleTargets = (rootSchema, defs, definitions, defNames, options, rootName, rootTypeName) => {
189
+ const planBundleTargets = (rootSchema, defs, definitions, defNames, options, rootName, defInfoMap, rootTypeName) => {
146
190
  const targets = [];
191
+ const groupById = new Map();
147
192
  for (const defName of defNames) {
148
- const defSchema = defs[defName];
149
- const defSchemaWithDefs = {
150
- ...defSchema,
151
- $defs: { ...defs, ...defSchema?.$defs },
152
- definitions: {
153
- ...defSchema.definitions,
154
- ...definitions,
155
- },
156
- };
157
- const pascalName = toPascalCase(defName);
158
- const schemaName = options.splitDefs?.schemaName?.(defName, { isRoot: false }) ?? `${pascalName}Schema`;
159
- const typeName = options.splitDefs?.typeName?.(defName, { isRoot: false }) ?? pascalName;
160
- const fileName = options.splitDefs?.fileName?.(defName, { isRoot: false }) ?? `${defName}.schema.ts`;
193
+ const info = defInfoMap.get(defName);
194
+ const gid = info?.groupId || defName;
195
+ if (!groupById.has(gid))
196
+ groupById.set(gid, []);
197
+ groupById.get(gid).push(defName);
198
+ }
199
+ for (const [groupId, memberDefs] of groupById.entries()) {
200
+ const orderedDefs = orderGroupMembers(memberDefs, defInfoMap);
201
+ const members = orderedDefs.map((defName) => {
202
+ const defSchema = defs[defName];
203
+ const defSchemaWithDefs = {
204
+ ...defSchema,
205
+ $defs: { ...defs, ...defSchema?.$defs },
206
+ definitions: {
207
+ ...defSchema.definitions,
208
+ ...definitions,
209
+ },
210
+ };
211
+ const pascalName = toPascalCase(defName);
212
+ const schemaName = options.splitDefs?.schemaName?.(defName, { isRoot: false }) ?? `${pascalName}Schema`;
213
+ const typeName = options.splitDefs?.typeName?.(defName, { isRoot: false }) ?? pascalName;
214
+ return { defName, schemaWithDefs: defSchemaWithDefs, schemaName, typeName };
215
+ });
216
+ const fileName = defInfoMap.get(memberDefs[0])?.fileName ?? `${memberDefs[0]}.schema.ts`;
161
217
  targets.push({
162
- defName,
163
- schemaWithDefs: defSchemaWithDefs,
164
- schemaName,
165
- typeName,
218
+ groupId,
166
219
  fileName,
220
+ members,
167
221
  usedRefs: new Set(),
168
222
  isRoot: false,
169
223
  });
@@ -171,17 +225,22 @@ const planBundleTargets = (rootSchema, defs, definitions, defNames, options, roo
171
225
  if (options.splitDefs?.includeRoot ?? true) {
172
226
  const rootFile = options.splitDefs?.fileName?.("root", { isRoot: true }) ?? "workflow.schema.ts";
173
227
  targets.push({
174
- defName: null,
175
- schemaWithDefs: {
176
- ...rootSchema,
177
- definitions: {
178
- ...rootSchema.definitions,
179
- ...definitions,
180
- },
181
- },
182
- schemaName: rootName,
183
- typeName: rootTypeName,
228
+ groupId: "root",
184
229
  fileName: rootFile,
230
+ members: [
231
+ {
232
+ defName: null,
233
+ schemaWithDefs: {
234
+ ...rootSchema,
235
+ definitions: {
236
+ ...rootSchema.definitions,
237
+ ...definitions,
238
+ },
239
+ },
240
+ schemaName: rootName,
241
+ typeName: rootTypeName,
242
+ },
243
+ ],
185
244
  usedRefs: new Set(),
186
245
  isRoot: true,
187
246
  });
@@ -212,6 +271,35 @@ const findRefDependencies = (schema, validDefNames) => {
212
271
  traverse(schema);
213
272
  return deps;
214
273
  };
274
+ const orderGroupMembers = (defs, defInfoMap) => {
275
+ const inGroup = new Set(defs);
276
+ const visited = new Set();
277
+ const temp = new Set();
278
+ const result = [];
279
+ const visit = (name) => {
280
+ if (visited.has(name))
281
+ return;
282
+ if (temp.has(name)) {
283
+ return;
284
+ }
285
+ temp.add(name);
286
+ const info = defInfoMap.get(name);
287
+ if (info) {
288
+ for (const dep of info.dependencies) {
289
+ if (inGroup.has(dep)) {
290
+ visit(dep);
291
+ }
292
+ }
293
+ }
294
+ temp.delete(name);
295
+ visited.add(name);
296
+ result.push(name);
297
+ };
298
+ for (const name of defs) {
299
+ visit(name);
300
+ }
301
+ return result;
302
+ };
215
303
  const detectCycles = (defInfoMap) => {
216
304
  const cycleNodes = new Set();
217
305
  const visited = new Set();
@@ -245,6 +333,52 @@ const detectCycles = (defInfoMap) => {
245
333
  }
246
334
  return cycleNodes;
247
335
  };
336
+ const buildSccGroups = (defInfoMap) => {
337
+ const indexMap = new Map();
338
+ const lowLink = new Map();
339
+ const onStack = new Set();
340
+ const stack = [];
341
+ let index = 0;
342
+ const groups = new Map();
343
+ const strongConnect = (node) => {
344
+ indexMap.set(node, index);
345
+ lowLink.set(node, index);
346
+ index += 1;
347
+ stack.push(node);
348
+ onStack.add(node);
349
+ const info = defInfoMap.get(node);
350
+ if (info) {
351
+ for (const dep of info.dependencies) {
352
+ if (!indexMap.has(dep)) {
353
+ strongConnect(dep);
354
+ lowLink.set(node, Math.min(lowLink.get(node), lowLink.get(dep)));
355
+ }
356
+ else if (onStack.has(dep)) {
357
+ lowLink.set(node, Math.min(lowLink.get(node), indexMap.get(dep)));
358
+ }
359
+ }
360
+ }
361
+ if (lowLink.get(node) === indexMap.get(node)) {
362
+ const members = [];
363
+ let w;
364
+ do {
365
+ w = stack.pop();
366
+ if (w) {
367
+ onStack.delete(w);
368
+ members.push(w);
369
+ }
370
+ } while (w && w !== node);
371
+ const groupId = members.sort().join("__");
372
+ groups.set(groupId, members);
373
+ }
374
+ };
375
+ for (const name of defInfoMap.keys()) {
376
+ if (!indexMap.has(name)) {
377
+ strongConnect(name);
378
+ }
379
+ }
380
+ return groups;
381
+ };
248
382
  const collectNestedTypes = (rootSchema, defs, defNames, rootTypeName) => {
249
383
  const allNestedTypes = [];
250
384
  for (const defName of defNames) {
@@ -289,7 +423,7 @@ const findNestedTypesInSchema = (schema, parentTypeName, defNames, currentPath =
289
423
  }
290
424
  // inline $defs
291
425
  if (record.$defs && typeof record.$defs === "object") {
292
- for (const [_defName, defSchema] of Object.entries(record.$defs)) {
426
+ for (const [, defSchema] of Object.entries(record.$defs)) {
293
427
  nestedTypes.push(...findNestedTypesInSchema(defSchema, parentTypeName, defNames, currentPath));
294
428
  }
295
429
  }
@@ -320,6 +454,26 @@ const generateNestedTypesFile = (nestedTypes) => {
320
454
  " * They are extracted using TypeScript indexed access types.",
321
455
  " */",
322
456
  "",
457
+ "type Access<T, P extends readonly (string | number)[]> =",
458
+ " P extends []",
459
+ " ? NonNullable<T>",
460
+ " : P extends readonly [infer H, ...infer R]",
461
+ " ? H extends \"items\"",
462
+ " ? Access<NonNullable<T> extends Array<infer U> ? U : unknown, Extract<R, (string | number)[]>>",
463
+ " : H extends \"additionalProperties\"",
464
+ " ? Access<NonNullable<T> extends Record<string, infer V> ? V : unknown, Extract<R, (string | number)[]>>",
465
+ " : H extends number",
466
+ " ? Access<NonNullable<T> extends Array<infer U> ? U : unknown, Extract<R, (string | number)[]>>",
467
+ " : H extends string",
468
+ " ? Access<",
469
+ " H extends keyof NonNullable<T>",
470
+ " ? NonNullable<NonNullable<T>[H]>",
471
+ " : unknown,",
472
+ " Extract<R, (string | number)[]>",
473
+ " >",
474
+ " : unknown",
475
+ " : unknown;",
476
+ "",
323
477
  ];
324
478
  const byParent = new Map();
325
479
  for (const info of nestedTypes) {
@@ -338,14 +492,15 @@ const generateNestedTypesFile = (nestedTypes) => {
338
492
  lines.push(`import type { ${typeName} } from './${file}.schema.js';`);
339
493
  }
340
494
  lines.push("");
495
+ const buildAccessExpr = (parentType, propertyPath) => {
496
+ const path = propertyPath.map((prop) => (typeof prop === "number" ? prop : JSON.stringify(prop))).join(", ");
497
+ return `Access<${parentType}, [${path}]>`;
498
+ };
341
499
  for (const [parentType, types] of [...byParent.entries()].sort()) {
342
500
  lines.push(`// From ${parentType}`);
343
501
  for (const info of types.sort((a, b) => a.typeName.localeCompare(b.typeName))) {
344
502
  if (info.propertyPath.length > 0) {
345
- let accessExpr = parentType;
346
- for (const prop of info.propertyPath) {
347
- accessExpr = `NonNullable<${accessExpr}['${prop}']>`;
348
- }
503
+ const accessExpr = buildAccessExpr(parentType, info.propertyPath);
349
504
  lines.push(`export type ${info.typeName} = ${accessExpr};`);
350
505
  }
351
506
  }
@@ -3,19 +3,21 @@ Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.parseAllOf = parseAllOf;
4
4
  const parseSchema_js_1 = require("./parseSchema.js");
5
5
  const half_js_1 = require("../utils/half.js");
6
- const originalIndex = Symbol("Original index");
6
+ const originalIndexKey = "__originalIndex";
7
7
  const ensureOriginalIndex = (arr) => {
8
- let newArr = [];
8
+ const newArr = [];
9
9
  for (let i = 0; i < arr.length; i++) {
10
10
  const item = arr[i];
11
11
  if (typeof item === "boolean") {
12
- newArr.push(item ? { [originalIndex]: i } : { [originalIndex]: i, not: {} });
12
+ newArr.push(item ? { [originalIndexKey]: i } : { [originalIndexKey]: i, not: {} });
13
13
  }
14
- else if (originalIndex in item) {
14
+ else if (typeof item === "object" &&
15
+ item !== null &&
16
+ originalIndexKey in item) {
15
17
  return arr;
16
18
  }
17
19
  else {
18
- newArr.push({ ...item, [originalIndex]: i });
20
+ newArr.push({ ...item, [originalIndexKey]: i });
19
21
  }
20
22
  }
21
23
  return newArr;
@@ -28,7 +30,11 @@ function parseAllOf(schema, refs) {
28
30
  const item = schema.allOf[0];
29
31
  return (0, parseSchema_js_1.parseSchema)(item, {
30
32
  ...refs,
31
- path: [...refs.path, "allOf", item[originalIndex]],
33
+ path: [
34
+ ...refs.path,
35
+ "allOf",
36
+ item[originalIndexKey] ?? 0,
37
+ ],
32
38
  });
33
39
  }
34
40
  else {
@@ -1,7 +1,5 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.parseBoolean = void 0;
4
- const parseBoolean = (_schema) => {
5
- return "z.boolean()";
6
- };
4
+ const parseBoolean = () => "z.boolean()";
7
5
  exports.parseBoolean = parseBoolean;
@@ -17,8 +17,8 @@ const parseIfThenElse = (schema, refs) => {
17
17
  ? ${$then}.safeParse(value)
18
18
  : ${$else}.safeParse(value);
19
19
  if (!result.success) {
20
- const issues = result.error.issues ?? result.error.errors ?? [];
21
- issues.forEach((issue) => ctx.addIssue(issue))
20
+ const issues = result.error.issues;
21
+ issues.forEach((issue) => ctx.addIssue({ ...issue }))
22
22
  }
23
23
  })`;
24
24
  // Store original if/then/else for JSON Schema round-trip
@@ -1,7 +1,5 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.parseNull = void 0;
4
- const parseNull = (_schema) => {
5
- return "z.null()";
6
- };
4
+ const parseNull = () => "z.null()";
7
5
  exports.parseNull = parseNull;
@@ -200,6 +200,7 @@ function parseObject(objectSchema, refs) {
200
200
  output += `.and(${(0, parseAnyOf_js_1.parseAnyOf)({
201
201
  ...objectSchema,
202
202
  anyOf: objectSchema.anyOf.map((x) => typeof x === "object" &&
203
+ x !== null &&
203
204
  !x.type &&
204
205
  (x.properties || x.additionalProperties || x.patternProperties)
205
206
  ? { ...x, type: "object" }
@@ -210,6 +211,7 @@ function parseObject(objectSchema, refs) {
210
211
  output += `.and(${(0, parseOneOf_js_1.parseOneOf)({
211
212
  ...objectSchema,
212
213
  oneOf: objectSchema.oneOf.map((x) => typeof x === "object" &&
214
+ x !== null &&
213
215
  !x.type &&
214
216
  (x.properties || x.additionalProperties || x.patternProperties)
215
217
  ? { ...x, type: "object" }
@@ -220,6 +222,7 @@ function parseObject(objectSchema, refs) {
220
222
  output += `.and(${(0, parseAllOf_js_1.parseAllOf)({
221
223
  ...objectSchema,
222
224
  allOf: objectSchema.allOf.map((x) => typeof x === "object" &&
225
+ x !== null &&
223
226
  !x.type &&
224
227
  (x.properties || x.additionalProperties || x.patternProperties)
225
228
  ? { ...x, type: "object" }
@@ -233,6 +236,7 @@ function parseObject(objectSchema, refs) {
233
236
  // propertyNames
234
237
  if (objectSchema.propertyNames) {
235
238
  const normalizedPropNames = typeof objectSchema.propertyNames === "object" &&
239
+ objectSchema.propertyNames !== null &&
236
240
  !objectSchema.propertyNames.type &&
237
241
  objectSchema.propertyNames.pattern
238
242
  ? { ...objectSchema.propertyNames, type: "string" }
@@ -261,12 +265,12 @@ function parseObject(objectSchema, refs) {
261
265
  if (entries.length) {
262
266
  output += `.superRefine((obj, ctx) => {
263
267
  ${entries
264
- .map(([key, schema], idx) => {
268
+ .map(([key, schema]) => {
265
269
  const parsed = (0, parseSchema_js_1.parseSchema)(schema, { ...refs, path: [...refs.path, "dependentSchemas", key] });
266
270
  return `if (Object.prototype.hasOwnProperty.call(obj, ${JSON.stringify(key)})) {
267
271
  const result = ${parsed}.safeParse(obj);
268
272
  if (!result.success) {
269
- ctx.addIssue({ code: "custom", message: "Dependent schema failed", path: [], params: { issues: result.error.issues } });
273
+ ctx.addIssue({ code: "custom", message: ${objectSchema.errorMessage?.dependentSchemas ?? JSON.stringify("Dependent schema failed")}, path: [], params: { issues: result.error.issues } });
270
274
  }
271
275
  }`;
272
276
  })
@@ -278,7 +282,8 @@ function parseObject(objectSchema, refs) {
278
282
  if (objectSchema.dependentRequired && typeof objectSchema.dependentRequired === "object") {
279
283
  const entries = Object.entries(objectSchema.dependentRequired);
280
284
  if (entries.length) {
281
- const depRequiredMessage = objectSchema.errorMessage?.dependentRequired ?? "Dependent required properties missing";
285
+ const depRequiredMessage = objectSchema.errorMessage?.dependentRequired ??
286
+ "Dependent required properties missing";
282
287
  output += `.superRefine((obj, ctx) => {
283
288
  ${entries
284
289
  .map(([prop, deps]) => {
@@ -306,5 +311,8 @@ const shouldUseGetter = (parsed, refs) => {
306
311
  if (refs.currentSchemaName && parsed.includes(refs.currentSchemaName)) {
307
312
  return true;
308
313
  }
314
+ if (refs.cycleRefNames?.has(parsed)) {
315
+ return true;
316
+ }
309
317
  return Boolean(refs.inProgress && refs.inProgress.has(parsed));
310
318
  };
@@ -33,9 +33,7 @@ const parseSchema = (schema, refs = { seen: new Map(), path: [] }, blockMeta) =>
33
33
  if (typeof schema !== "object")
34
34
  return schema ? (0, anyOrUnknown_js_1.anyOrUnknown)(refs) : "z.never()";
35
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;
36
+ const baseUri = typeof schema.$id === "string" ? (0, resolveUri_js_1.resolveUri)(parentBase, schema.$id) : parentBase;
39
37
  const dynamicAnchors = Array.isArray(refs.dynamicAnchors) ? [...refs.dynamicAnchors] : [];
40
38
  if (typeof schema.$dynamicAnchor === "string") {
41
39
  dynamicAnchors.push({
@@ -85,6 +83,9 @@ const parseSchema = (schema, refs = { seen: new Map(), path: [] }, blockMeta) =>
85
83
  exports.parseSchema = parseSchema;
86
84
  const parseRef = (schema, refs) => {
87
85
  const refValue = schema.$dynamicRef ?? schema.$ref;
86
+ if (typeof refValue !== "string") {
87
+ return (0, anyOrUnknown_js_1.anyOrUnknown)(refs);
88
+ }
88
89
  const resolved = resolveRef(schema, refValue, refs);
89
90
  if (!resolved) {
90
91
  refs.onUnresolvedRef?.(refValue, refs.path);
@@ -123,6 +124,14 @@ const parseRef = (schema, refs) => {
123
124
  // (or is currently being resolved). This avoids TDZ on true cycles while
124
125
  // letting ordered, acyclic refs stay direct.
125
126
  if (isSameCycle || refs.inProgress.has(refName)) {
127
+ const inObjectProperty = refs.path.includes("properties") ||
128
+ refs.path.includes("patternProperties") ||
129
+ refs.path.includes("additionalProperties");
130
+ if (inObjectProperty && refName === refs.currentSchemaName) {
131
+ // Getter properties defer evaluation, so a direct reference avoids extra lazies
132
+ // for self-recursion.
133
+ return refName;
134
+ }
126
135
  return `z.lazy(() => ${refName})`;
127
136
  }
128
137
  return refName;
@@ -194,7 +203,10 @@ const resolveRef = (schemaNode, ref, refs) => {
194
203
  const loaded = refs.resolveExternalRef(extBase);
195
204
  if (loaded) {
196
205
  // If async resolver is used synchronously here, it will be ignored; keep simple sync for now
197
- const schema = loaded.then ? undefined : loaded;
206
+ const maybePromise = loaded;
207
+ const schema = typeof maybePromise.then === "function"
208
+ ? undefined
209
+ : loaded;
198
210
  if (schema) {
199
211
  const { registry } = (0, buildRefRegistry_js_1.buildRefRegistry)(schema, extBase);
200
212
  registry.forEach((entry, k) => refs.refRegistry?.set(k, entry));
@@ -342,10 +354,10 @@ const selectParser = (schema, refs) => {
342
354
  return (0, parseNumber_js_1.parseNumber)(schema);
343
355
  }
344
356
  else if (exports.its.a.primitive(schema, "boolean")) {
345
- return (0, parseBoolean_js_1.parseBoolean)(schema);
357
+ return (0, parseBoolean_js_1.parseBoolean)();
346
358
  }
347
359
  else if (exports.its.a.primitive(schema, "null")) {
348
- return (0, parseNull_js_1.parseNull)(schema);
360
+ return (0, parseNull_js_1.parseNull)();
349
361
  }
350
362
  else if (exports.its.a.conditional(schema)) {
351
363
  return (0, parseIfThenElse_js_1.parseIfThenElse)(schema, refs);
@@ -289,7 +289,7 @@ const parseString = (schema, refs) => {
289
289
  if (contentMediaType != "") {
290
290
  r += contentMediaType;
291
291
  r += (0, withMessage_js_1.withMessage)(schema, "contentSchema", ({ value }) => {
292
- if (value && value instanceof Object) {
292
+ if (value && typeof value === "object") {
293
293
  return {
294
294
  opener: `.pipe(${(0, parseSchema_js_1.parseSchema)(value, refContext)}`,
295
295
  closer: ")",