@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
@@ -1,172 +1,10 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.jsonSchemaToZod = void 0;
4
- const parseSchema_js_1 = require("./parsers/parseSchema.js");
5
- const jsdocs_js_1 = require("./utils/jsdocs.js");
6
- const cycles_js_1 = require("./utils/cycles.js");
7
- const jsonSchemaToZod = (schema, { module, name, type, noImport, ...rest } = {}) => {
8
- if (type && (!name || module !== "esm")) {
9
- throw new Error("Option `type` requires `name` to be set and `module` to be `esm`");
10
- }
11
- const refNameByPointer = new Map();
12
- const usedNames = new Set();
13
- const exportRefs = rest.exportRefs ?? true;
14
- const withMeta = rest.withMeta ?? true;
15
- if (name)
16
- usedNames.add(name);
17
- // Pass 1: collect declarations/dependencies to detect cycles before emitting
18
- const pass1 = {
19
- module,
20
- name,
21
- path: [],
22
- seen: new Map(),
23
- declarations: new Map(),
24
- dependencies: new Map(),
25
- inProgress: new Set(),
26
- refNameByPointer,
27
- usedNames,
28
- root: schema,
29
- currentSchemaName: name,
30
- ...rest,
31
- withMeta,
32
- };
33
- (0, parseSchema_js_1.parseSchema)(schema, pass1);
34
- const names = Array.from(pass1.declarations.keys());
35
- const cycleRefNames = (0, cycles_js_1.detectCycles)(names, pass1.dependencies);
36
- const { componentByName } = (0, cycles_js_1.computeScc)(names, pass1.dependencies);
37
- // Pass 2: generate with cycle awareness
38
- const declarations = new Map();
39
- const dependencies = new Map();
40
- const parsedSchema = (0, parseSchema_js_1.parseSchema)(schema, {
41
- module,
42
- name,
43
- path: [],
44
- seen: new Map(),
45
- declarations,
46
- dependencies,
47
- inProgress: new Set(),
48
- refNameByPointer,
49
- usedNames,
50
- root: schema,
51
- currentSchemaName: name,
52
- cycleRefNames,
53
- cycleComponentByName: componentByName,
54
- ...rest,
55
- withMeta,
56
- });
57
- const declarationBlock = declarations.size
58
- ? orderDeclarations(Array.from(declarations.entries()), dependencies)
59
- .map(([refName, value]) => {
60
- const shouldExport = exportRefs && module === "esm";
61
- const decl = `${shouldExport ? "export " : ""}const ${refName} = ${value}`;
62
- return decl;
63
- })
64
- .join("\n")
65
- : "";
66
- const jsdocs = rest.withJsdocs && typeof schema !== "boolean" && schema.description
67
- ? (0, jsdocs_js_1.expandJsdocs)(schema.description)
68
- : "";
69
- const lines = [];
70
- if (module === "cjs" && !noImport) {
71
- lines.push(`const { z } = require("zod")`);
72
- }
73
- if (module === "esm" && !noImport) {
74
- lines.push(`import { z } from "zod"`);
75
- }
76
- if (declarationBlock) {
77
- lines.push(declarationBlock);
78
- }
79
- if (module === "cjs") {
80
- const payload = name ? `{ ${JSON.stringify(name)}: ${parsedSchema} }` : parsedSchema;
81
- lines.push(`${jsdocs}module.exports = ${payload}`);
82
- }
83
- else if (module === "esm") {
84
- lines.push(`${jsdocs}export ${name ? `const ${name} =` : `default`} ${parsedSchema}`);
85
- }
86
- else if (name) {
87
- lines.push(`${jsdocs}const ${name} = ${parsedSchema}`);
88
- }
89
- else {
90
- lines.push(`${jsdocs}${parsedSchema}`);
91
- }
92
- let typeLine;
93
- if (type && name) {
94
- let typeName = typeof type === "string"
95
- ? type
96
- : `${name[0].toUpperCase()}${name.substring(1)}`;
97
- typeLine = `export type ${typeName} = z.infer<typeof ${name}>`;
98
- }
99
- const joined = lines.filter(Boolean).join("\n\n");
100
- const combined = typeLine ? `${joined}\n${typeLine}` : joined;
101
- const shouldEndWithNewline = module === "esm" || module === "cjs";
102
- return `${combined}${shouldEndWithNewline ? "\n" : ""}`;
4
+ const analyzeSchema_js_1 = require("./core/analyzeSchema.js");
5
+ const emitZod_js_1 = require("./core/emitZod.js");
6
+ const jsonSchemaToZod = (schema, options = {}) => {
7
+ const analysis = (0, analyzeSchema_js_1.analyzeSchema)(schema, options);
8
+ return (0, emitZod_js_1.emitZod)(analysis);
103
9
  };
104
10
  exports.jsonSchemaToZod = jsonSchemaToZod;
105
- function orderDeclarations(entries, dependencies) {
106
- const valueByName = new Map(entries);
107
- const depGraph = new Map();
108
- // Seed with explicit dependencies recorded during parsing
109
- for (const [from, set] of dependencies.entries()) {
110
- const onlyKnown = new Set();
111
- for (const dep of set) {
112
- if (valueByName.has(dep) && dep !== from) {
113
- onlyKnown.add(dep);
114
- }
115
- }
116
- if (onlyKnown.size)
117
- depGraph.set(from, onlyKnown);
118
- }
119
- // Also infer deps by scanning declaration bodies for referenced names
120
- const names = Array.from(valueByName.keys());
121
- for (const [name, value] of entries) {
122
- const deps = depGraph.get(name) ?? new Set();
123
- for (const candidate of names) {
124
- if (candidate === name)
125
- continue;
126
- const matcher = new RegExp(`\\b${candidate}\\b`);
127
- if (matcher.test(value)) {
128
- deps.add(candidate);
129
- }
130
- }
131
- if (deps.size)
132
- depGraph.set(name, deps);
133
- }
134
- const ordered = [];
135
- const perm = new Set();
136
- const temp = new Set();
137
- const visit = (name) => {
138
- if (perm.has(name))
139
- return;
140
- if (temp.has(name)) {
141
- // Cycle detected; break it but still include the node.
142
- temp.delete(name);
143
- perm.add(name);
144
- ordered.push(name);
145
- return;
146
- }
147
- temp.add(name);
148
- const deps = depGraph.get(name);
149
- if (deps) {
150
- for (const dep of deps) {
151
- if (valueByName.has(dep)) {
152
- visit(dep);
153
- }
154
- }
155
- }
156
- temp.delete(name);
157
- perm.add(name);
158
- ordered.push(name);
159
- };
160
- for (const name of valueByName.keys()) {
161
- visit(name);
162
- }
163
- const unique = [];
164
- const seen = new Set();
165
- for (const name of ordered) {
166
- if (!seen.has(name)) {
167
- seen.add(name);
168
- unique.push(name);
169
- }
170
- }
171
- return unique.map((name) => [name, valueByName.get(name)]);
172
- }
@@ -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,7 +17,7 @@ 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 ?? [];
20
+ const issues = result.error.issues;
21
21
  issues.forEach((issue) => ctx.addIssue(issue))
22
22
  }
23
23
  })`;
@@ -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]) => {
@@ -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,18 @@ 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" ? (0, resolveUri_js_1.resolveUri)(parentBase, schema.$id) : parentBase;
37
+ const dynamicAnchors = Array.isArray(refs.dynamicAnchors) ? [...refs.dynamicAnchors] : [];
38
+ if (typeof schema.$dynamicAnchor === "string") {
39
+ dynamicAnchors.push({
40
+ name: schema.$dynamicAnchor,
41
+ uri: baseUri,
42
+ path: refs.path,
43
+ });
44
+ }
32
45
  if (refs.parserOverride) {
33
- const custom = refs.parserOverride(schema, refs);
46
+ const custom = refs.parserOverride(schema, { ...refs, currentBaseUri: baseUri, dynamicAnchors });
34
47
  if (typeof custom === "string") {
35
48
  return custom;
36
49
  }
@@ -50,14 +63,14 @@ const parseSchema = (schema, refs = { seen: new Map(), path: [] }, blockMeta) =>
50
63
  refs.seen.set(schema, seen);
51
64
  }
52
65
  if (exports.its.a.ref(schema)) {
53
- const parsedRef = parseRef(schema, refs);
66
+ const parsedRef = parseRef(schema, { ...refs, currentBaseUri: baseUri, dynamicAnchors });
54
67
  seen.r = parsedRef;
55
68
  return parsedRef;
56
69
  }
57
- let parsed = selectParser(schema, refs);
70
+ let parsed = selectParser(schema, { ...refs, currentBaseUri: baseUri, dynamicAnchors });
58
71
  if (!blockMeta) {
59
72
  if (!refs.withoutDescribes) {
60
- parsed = addDescribes(schema, parsed, refs);
73
+ parsed = addDescribes(schema, parsed, { ...refs, currentBaseUri: baseUri, dynamicAnchors });
61
74
  }
62
75
  if (!refs.withoutDefaults) {
63
76
  parsed = addDefaults(schema, parsed);
@@ -69,17 +82,23 @@ const parseSchema = (schema, refs = { seen: new Map(), path: [] }, blockMeta) =>
69
82
  };
70
83
  exports.parseSchema = parseSchema;
71
84
  const parseRef = (schema, refs) => {
72
- const resolved = resolveRef(refs.root, schema.$ref);
85
+ const refValue = schema.$dynamicRef ?? schema.$ref;
86
+ if (typeof refValue !== "string") {
87
+ return (0, anyOrUnknown_js_1.anyOrUnknown)(refs);
88
+ }
89
+ const resolved = resolveRef(schema, refValue, refs);
73
90
  if (!resolved) {
91
+ refs.onUnresolvedRef?.(refValue, refs.path);
74
92
  return (0, anyOrUnknown_js_1.anyOrUnknown)(refs);
75
93
  }
76
- const { schema: target, path } = resolved;
77
- const refName = getOrCreateRefName(schema.$ref, path, refs);
94
+ const { schema: target, path, pointerKey } = resolved;
95
+ const refName = getOrCreateRefName(pointerKey, path, refs);
78
96
  if (!refs.declarations.has(refName) && !refs.inProgress.has(refName)) {
79
97
  refs.inProgress.add(refName);
80
98
  const declaration = (0, exports.parseSchema)(target, {
81
99
  ...refs,
82
100
  path,
101
+ currentBaseUri: resolved.baseUri,
83
102
  currentSchemaName: refName,
84
103
  root: refs.root,
85
104
  });
@@ -132,23 +151,96 @@ const addDescribes = (schema, parsed, refs) => {
132
151
  }
133
152
  return parsed;
134
153
  };
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 };
154
+ const resolveRef = (schemaNode, ref, refs) => {
155
+ const base = refs.currentBaseUri ?? refs.rootBaseUri ?? "root:///";
156
+ // Handle dynamicRef lookup via dynamicAnchors stack
157
+ const isDynamic = typeof schemaNode.$dynamicRef === "string";
158
+ if (isDynamic && refs.dynamicAnchors && ref.startsWith("#")) {
159
+ const name = ref.slice(1);
160
+ for (let i = refs.dynamicAnchors.length - 1; i >= 0; i -= 1) {
161
+ const entry = refs.dynamicAnchors[i];
162
+ if (entry.name === name) {
163
+ const key = `${entry.uri}#${name}`;
164
+ const target = refs.refRegistry?.get(key);
165
+ if (target) {
166
+ return { schema: target.schema, path: target.path, baseUri: target.baseUri, pointerKey: key };
167
+ }
168
+ }
169
+ }
170
+ }
171
+ // Resolve URI against base
172
+ const resolvedUri = (0, resolveUri_js_1.resolveUri)(base, ref);
173
+ const [uriBase, fragment] = resolvedUri.split("#");
174
+ const key = fragment ? `${uriBase}#${fragment}` : uriBase;
175
+ let regEntry = refs.refRegistry?.get(key);
176
+ if (regEntry) {
177
+ return { schema: regEntry.schema, path: regEntry.path, baseUri: regEntry.baseUri, pointerKey: key };
178
+ }
179
+ // Legacy recursive ref: treat as dynamic to __recursive__
180
+ if (schemaNode.$recursiveRef) {
181
+ const recursiveKey = `${base}#__recursive__`;
182
+ regEntry = refs.refRegistry?.get(recursiveKey);
183
+ if (regEntry) {
184
+ return {
185
+ schema: regEntry.schema,
186
+ path: regEntry.path,
187
+ baseUri: regEntry.baseUri,
188
+ pointerKey: recursiveKey,
189
+ };
190
+ }
191
+ }
192
+ // External resolver hook
193
+ const extBase = uriBaseFromRef(resolvedUri);
194
+ if (refs.resolveExternalRef && extBase && !isLocalBase(extBase, refs.rootBaseUri ?? "")) {
195
+ const loaded = refs.resolveExternalRef(extBase);
196
+ if (loaded) {
197
+ // If async resolver is used synchronously here, it will be ignored; keep simple sync for now
198
+ const maybePromise = loaded;
199
+ const schema = typeof maybePromise.then === "function"
200
+ ? undefined
201
+ : loaded;
202
+ if (schema) {
203
+ const { registry } = (0, buildRefRegistry_js_1.buildRefRegistry)(schema, extBase);
204
+ registry.forEach((entry, k) => refs.refRegistry?.set(k, entry));
205
+ regEntry = refs.refRegistry?.get(key);
206
+ if (regEntry) {
207
+ return {
208
+ schema: regEntry.schema,
209
+ path: regEntry.path,
210
+ baseUri: regEntry.baseUri,
211
+ pointerKey: key,
212
+ };
213
+ }
214
+ }
215
+ }
216
+ }
217
+ // Backward compatibility: JSON Pointer into root
218
+ if (refs.root && ref.startsWith("#/")) {
219
+ const rawSegments = ref
220
+ .slice(2)
221
+ .split("/")
222
+ .filter((segment) => segment.length > 0)
223
+ .map(decodePointerSegment);
224
+ let current = refs.root;
225
+ for (const segment of rawSegments) {
226
+ if (typeof current !== "object" || current === null)
227
+ return undefined;
228
+ current = current[segment];
229
+ }
230
+ return { schema: current, path: rawSegments, baseUri: base, pointerKey: ref };
231
+ }
232
+ return undefined;
150
233
  };
151
234
  const decodePointerSegment = (segment) => segment.replace(/~1/g, "/").replace(/~0/g, "~");
235
+ const uriBaseFromRef = (resolvedUri) => {
236
+ const hashIdx = resolvedUri.indexOf("#");
237
+ return hashIdx === -1 ? resolvedUri : resolvedUri.slice(0, hashIdx);
238
+ };
239
+ const isLocalBase = (base, rootBase) => {
240
+ if (!rootBase)
241
+ return false;
242
+ return base === rootBase;
243
+ };
152
244
  const getOrCreateRefName = (pointer, path, refs) => {
153
245
  if (refs.refNameByPointer?.has(pointer)) {
154
246
  return refs.refNameByPointer.get(pointer);
@@ -159,12 +251,24 @@ const getOrCreateRefName = (pointer, path, refs) => {
159
251
  return preferred;
160
252
  };
161
253
  const buildNameFromPath = (path, used) => {
162
- const filtered = path.filter((segment) => segment !== "$defs" && segment !== "definitions" && segment !== "properties");
254
+ const filtered = path
255
+ .map((segment, idx) => {
256
+ if (idx === 0 && (segment === "$defs" || segment === "definitions")) {
257
+ return undefined; // root-level defs prefix is redundant for naming
258
+ }
259
+ if (segment === "properties")
260
+ return undefined; // skip noisy properties segment
261
+ if (segment === "$defs" || segment === "definitions")
262
+ return "Defs";
263
+ return segment;
264
+ })
265
+ .filter((segment) => segment !== undefined);
163
266
  const base = filtered.length
164
267
  ? filtered
165
268
  .map((segment) => typeof segment === "number"
166
269
  ? `Ref${segment}`
167
270
  : segment
271
+ .toString()
168
272
  .replace(/[^a-zA-Z0-9_$]/g, " ")
169
273
  .split(" ")
170
274
  .filter(Boolean)
@@ -242,10 +346,10 @@ const selectParser = (schema, refs) => {
242
346
  return (0, parseNumber_js_1.parseNumber)(schema);
243
347
  }
244
348
  else if (exports.its.a.primitive(schema, "boolean")) {
245
- return (0, parseBoolean_js_1.parseBoolean)(schema);
349
+ return (0, parseBoolean_js_1.parseBoolean)();
246
350
  }
247
351
  else if (exports.its.a.primitive(schema, "null")) {
248
- return (0, parseNull_js_1.parseNull)(schema);
352
+ return (0, parseNull_js_1.parseNull)();
249
353
  }
250
354
  else if (exports.its.a.conditional(schema)) {
251
355
  return (0, parseIfThenElse_js_1.parseIfThenElse)(schema, refs);
@@ -266,7 +370,7 @@ exports.its = {
266
370
  nullable: (x) => x.nullable === true,
267
371
  multipleType: (x) => Array.isArray(x.type),
268
372
  not: (x) => x.not !== undefined,
269
- ref: (x) => typeof x.$ref === "string",
373
+ ref: (x) => typeof x.$ref === "string" || typeof x.$dynamicRef === "string",
270
374
  const: (x) => x.const !== undefined,
271
375
  primitive: (x, p) => x.type === p,
272
376
  conditional: (x) => Boolean("if" in x && x.if && "then" in x && "else" in x && x.then && x.else),
@@ -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: ")",
@@ -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:///") => {
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;
@@ -2,8 +2,9 @@
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.omit = void 0;
4
4
  const omit = (obj, ...keys) => Object.keys(obj).reduce((acc, key) => {
5
- if (!keys.includes(key)) {
6
- acc[key] = obj[key];
5
+ const typedKey = key;
6
+ if (!keys.includes(typedKey)) {
7
+ acc[typedKey] = obj[typedKey];
7
8
  }
8
9
  return acc;
9
10
  }, {});
@@ -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 {};