@gabrielbryk/json-schema-to-zod 2.7.4 → 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 (51) hide show
  1. package/CHANGELOG.md +23 -1
  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 +355 -0
  5. package/dist/cjs/index.js +6 -0
  6. package/dist/cjs/jsonSchemaToZod.js +5 -73
  7. package/dist/cjs/parsers/parseArray.js +34 -15
  8. package/dist/cjs/parsers/parseIfThenElse.js +2 -1
  9. package/dist/cjs/parsers/parseNumber.js +81 -39
  10. package/dist/cjs/parsers/parseObject.js +24 -0
  11. package/dist/cjs/parsers/parseSchema.js +147 -25
  12. package/dist/cjs/parsers/parseString.js +294 -54
  13. package/dist/cjs/utils/buildRefRegistry.js +56 -0
  14. package/dist/cjs/utils/cycles.js +113 -0
  15. package/dist/cjs/utils/resolveUri.js +16 -0
  16. package/dist/cjs/utils/withMessage.js +4 -5
  17. package/dist/esm/core/analyzeSchema.js +58 -0
  18. package/dist/esm/core/emitZod.js +137 -0
  19. package/dist/esm/generators/generateBundle.js +351 -0
  20. package/dist/esm/index.js +6 -0
  21. package/dist/esm/jsonSchemaToZod.js +5 -73
  22. package/dist/esm/parsers/parseArray.js +34 -15
  23. package/dist/esm/parsers/parseIfThenElse.js +2 -1
  24. package/dist/esm/parsers/parseNumber.js +81 -39
  25. package/dist/esm/parsers/parseObject.js +24 -0
  26. package/dist/esm/parsers/parseSchema.js +147 -25
  27. package/dist/esm/parsers/parseString.js +294 -54
  28. package/dist/esm/utils/buildRefRegistry.js +52 -0
  29. package/dist/esm/utils/cycles.js +107 -0
  30. package/dist/esm/utils/resolveUri.js +12 -0
  31. package/dist/esm/utils/withMessage.js +4 -5
  32. package/dist/types/Types.d.ts +36 -0
  33. package/dist/types/core/analyzeSchema.d.ts +24 -0
  34. package/dist/types/core/emitZod.d.ts +2 -0
  35. package/dist/types/generators/generateBundle.d.ts +62 -0
  36. package/dist/types/index.d.ts +6 -0
  37. package/dist/types/jsonSchemaToZod.d.ts +1 -1
  38. package/dist/types/parsers/parseSchema.d.ts +2 -1
  39. package/dist/types/parsers/parseString.d.ts +2 -2
  40. package/dist/types/utils/buildRefRegistry.d.ts +12 -0
  41. package/dist/types/utils/cycles.d.ts +7 -0
  42. package/dist/types/utils/jsdocs.d.ts +1 -1
  43. package/dist/types/utils/resolveUri.d.ts +1 -0
  44. package/dist/types/utils/withMessage.d.ts +6 -1
  45. package/docs/proposals/bundle-refactor.md +43 -0
  46. package/docs/proposals/ref-anchor-support.md +65 -0
  47. package/eslint.config.js +26 -0
  48. package/package.json +10 -4
  49. /package/{jest.config.js → jest.config.cjs} +0 -0
  50. /package/{postcjs.js → postcjs.cjs} +0 -0
  51. /package/{postesm.js → postesm.cjs} +0 -0
@@ -3,75 +3,315 @@ Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.parseString = void 0;
4
4
  const withMessage_js_1 = require("../utils/withMessage.js");
5
5
  const parseSchema_js_1 = require("./parseSchema.js");
6
- const parseString = (schema) => {
7
- let r = "z.string()";
8
- r += (0, withMessage_js_1.withMessage)(schema, "format", ({ value }) => {
9
- switch (value) {
10
- case "email":
11
- return [".email(", ")"];
12
- case "ip":
13
- return [".ip(", ")"];
14
- case "ipv4":
15
- return ['.ip({ version: "v4"', ", message: ", " })"];
16
- case "ipv6":
17
- return ['.ip({ version: "v6"', ", message: ", " })"];
18
- case "uri":
19
- return [".url(", ")"];
20
- case "uuid":
21
- return [".uuid(", ")"];
22
- case "date-time":
23
- return [".datetime({ offset: true", ", message: ", " })"];
24
- case "time":
25
- return [".time(", ")"];
26
- case "date":
27
- return [".date(", ")"];
28
- case "binary":
29
- return [".base64(", ")"];
30
- case "duration":
31
- return [".duration(", ")"];
32
- }
33
- });
34
- r += (0, withMessage_js_1.withMessage)(schema, "pattern", ({ json }) => [
35
- `.regex(new RegExp(${json})`,
36
- ", ",
37
- ")",
38
- ]);
39
- r += (0, withMessage_js_1.withMessage)(schema, "minLength", ({ json }) => [
40
- `.min(${json}`,
41
- ", ",
42
- ")",
43
- ]);
44
- r += (0, withMessage_js_1.withMessage)(schema, "maxLength", ({ json }) => [
45
- `.max(${json}`,
46
- ", ",
47
- ")",
48
- ]);
6
+ const parseString = (schema, refs) => {
7
+ const formatError = schema.errorMessage?.format;
8
+ const refContext = ensureRefs(refs);
9
+ const topLevelFormatMap = {
10
+ email: "z.email",
11
+ ipv4: "z.ipv4",
12
+ ipv6: "z.ipv6",
13
+ uri: "z.url",
14
+ uuid: "z.uuid",
15
+ cuid: "z.cuid",
16
+ cuid2: "z.cuid2",
17
+ nanoid: "z.nanoid",
18
+ ulid: "z.ulid",
19
+ jwt: "z.jwt",
20
+ e164: "z.e164",
21
+ base64url: "z.base64url",
22
+ base64: "z.base64",
23
+ emoji: "z.emoji",
24
+ "idn-email": "z.email",
25
+ };
26
+ const formatFn = schema.format && topLevelFormatMap[schema.format];
27
+ const formatParam = formatError !== undefined ? `{ error: ${JSON.stringify(formatError)} }` : "";
28
+ let r = formatFn ? `${formatFn}(${formatParam})` : "z.string()";
29
+ const formatHandled = Boolean(formatFn);
30
+ let formatWasHandled = formatHandled;
31
+ if (!formatHandled) {
32
+ r += (0, withMessage_js_1.withMessage)(schema, "format", ({ value }) => {
33
+ switch (value) {
34
+ case "email":
35
+ formatWasHandled = true;
36
+ return {
37
+ opener: ".email(",
38
+ closer: ")",
39
+ messagePrefix: "{ error: ",
40
+ messageCloser: " })",
41
+ };
42
+ case "ip":
43
+ formatWasHandled = true;
44
+ return {
45
+ opener: ".ip(",
46
+ closer: ")",
47
+ messagePrefix: "{ error: ",
48
+ messageCloser: " })",
49
+ };
50
+ case "ipv4":
51
+ formatWasHandled = true;
52
+ return {
53
+ opener: '.ip({ version: "v4"',
54
+ closer: " })",
55
+ messagePrefix: ", error: ",
56
+ messageCloser: " })",
57
+ };
58
+ case "ipv6":
59
+ formatWasHandled = true;
60
+ return {
61
+ opener: '.ip({ version: "v6"',
62
+ closer: " })",
63
+ messagePrefix: ", error: ",
64
+ messageCloser: " })",
65
+ };
66
+ case "uri":
67
+ formatWasHandled = true;
68
+ return {
69
+ opener: ".url(",
70
+ closer: ")",
71
+ messagePrefix: "{ error: ",
72
+ messageCloser: " })",
73
+ };
74
+ case "uuid":
75
+ formatWasHandled = true;
76
+ return {
77
+ opener: ".uuid(",
78
+ closer: ")",
79
+ messagePrefix: "{ error: ",
80
+ messageCloser: " })",
81
+ };
82
+ case "cuid":
83
+ formatWasHandled = true;
84
+ return {
85
+ opener: ".cuid(",
86
+ closer: ")",
87
+ messagePrefix: "{ error: ",
88
+ messageCloser: " })",
89
+ };
90
+ case "cuid2":
91
+ formatWasHandled = true;
92
+ return {
93
+ opener: ".cuid2(",
94
+ closer: ")",
95
+ messagePrefix: "{ error: ",
96
+ messageCloser: " })",
97
+ };
98
+ case "nanoid":
99
+ formatWasHandled = true;
100
+ return {
101
+ opener: ".nanoid(",
102
+ closer: ")",
103
+ messagePrefix: "{ error: ",
104
+ messageCloser: " })",
105
+ };
106
+ case "ulid":
107
+ formatWasHandled = true;
108
+ return {
109
+ opener: ".ulid(",
110
+ closer: ")",
111
+ messagePrefix: "{ error: ",
112
+ messageCloser: " })",
113
+ };
114
+ case "jwt":
115
+ formatWasHandled = true;
116
+ return {
117
+ opener: ".jwt(",
118
+ closer: ")",
119
+ messagePrefix: "{ error: ",
120
+ messageCloser: " })",
121
+ };
122
+ case "e164":
123
+ formatWasHandled = true;
124
+ return {
125
+ opener: ".e164(",
126
+ closer: ")",
127
+ messagePrefix: "{ error: ",
128
+ messageCloser: " })",
129
+ };
130
+ case "base64url":
131
+ formatWasHandled = true;
132
+ return {
133
+ opener: ".base64url(",
134
+ closer: ")",
135
+ messagePrefix: "{ error: ",
136
+ messageCloser: " })",
137
+ };
138
+ case "emoji":
139
+ formatWasHandled = true;
140
+ return {
141
+ opener: ".emoji(",
142
+ closer: ")",
143
+ messagePrefix: "{ error: ",
144
+ messageCloser: " })",
145
+ };
146
+ case "date-time":
147
+ formatWasHandled = true;
148
+ return {
149
+ opener: ".datetime({ offset: true",
150
+ closer: " })",
151
+ messagePrefix: ", error: ",
152
+ messageCloser: " })",
153
+ };
154
+ case "time":
155
+ formatWasHandled = true;
156
+ return {
157
+ opener: ".time(",
158
+ closer: ")",
159
+ messagePrefix: "{ error: ",
160
+ messageCloser: " })",
161
+ };
162
+ case "date":
163
+ formatWasHandled = true;
164
+ return {
165
+ opener: ".date(",
166
+ closer: ")",
167
+ messagePrefix: "{ error: ",
168
+ messageCloser: " })",
169
+ };
170
+ case "binary":
171
+ formatWasHandled = true;
172
+ return {
173
+ opener: ".base64(",
174
+ closer: ")",
175
+ messagePrefix: "{ error: ",
176
+ messageCloser: " })",
177
+ };
178
+ case "duration":
179
+ formatWasHandled = true;
180
+ return {
181
+ opener: ".duration(",
182
+ closer: ")",
183
+ messagePrefix: "{ error: ",
184
+ messageCloser: " })",
185
+ };
186
+ case "hostname":
187
+ case "idn-hostname":
188
+ formatWasHandled = true;
189
+ return {
190
+ opener: ".refine((val) => { if (typeof val !== \"string\" || val.length === 0 || val.length > 253) return false; return val.split(\".\").every((label) => label.length > 0 && label.length <= 63 && /^[A-Za-z0-9-]+$/.test(label) && label[0] !== \"-\" && label[label.length - 1] !== \"-\"); }",
191
+ closer: ")",
192
+ messagePrefix: ", { error: ",
193
+ messageCloser: " })",
194
+ };
195
+ case "idn-email":
196
+ formatWasHandled = true;
197
+ return {
198
+ opener: ".email(",
199
+ closer: ")",
200
+ messagePrefix: "{ error: ",
201
+ messageCloser: " })",
202
+ };
203
+ case "uri-reference":
204
+ case "iri":
205
+ case "iri-reference":
206
+ formatWasHandled = true;
207
+ return {
208
+ opener: '.refine((val) => { try { new URL(val, "http://example.com"); return true; } catch { return false; } }',
209
+ closer: ")",
210
+ messagePrefix: ", { error: ",
211
+ messageCloser: " })",
212
+ };
213
+ case "json-pointer":
214
+ formatWasHandled = true;
215
+ return {
216
+ opener: ".refine((val) => typeof val === \"string\" && /^(?:\\/(?:[^/~]|~[01])*)*$/.test(val)",
217
+ closer: ")",
218
+ messagePrefix: ", { error: ",
219
+ messageCloser: " })",
220
+ };
221
+ case "relative-json-pointer":
222
+ formatWasHandled = true;
223
+ return {
224
+ opener: ".refine((val) => typeof val === \"string\" && /^(?:0|[1-9][0-9]*)(?:#|(?:\\/(?:[^/~]|~[01])*))*$/.test(val)",
225
+ closer: ")",
226
+ messagePrefix: ", { error: ",
227
+ messageCloser: " })",
228
+ };
229
+ case "uri-template":
230
+ formatWasHandled = true;
231
+ return {
232
+ opener: ".refine((val) => { if (typeof val !== \"string\") return false; const opens = (val.match(/\\{/g) || []).length; const closes = (val.match(/\\}/g) || []).length; return opens === closes; }",
233
+ closer: ")",
234
+ messagePrefix: ", { error: ",
235
+ messageCloser: " })",
236
+ };
237
+ case "regex":
238
+ formatWasHandled = true;
239
+ return {
240
+ opener: ".refine((val) => { try { new RegExp(val); return true; } catch { return false; } }",
241
+ closer: ")",
242
+ messagePrefix: ", { error: ",
243
+ messageCloser: " })",
244
+ };
245
+ }
246
+ });
247
+ }
248
+ if (schema.format && !formatWasHandled) {
249
+ refContext.onUnknownFormat?.(schema.format, refContext.path);
250
+ }
251
+ r += (0, withMessage_js_1.withMessage)(schema, "pattern", ({ json }) => ({
252
+ opener: `.regex(new RegExp(${json})`,
253
+ closer: ")",
254
+ messagePrefix: ", { error: ",
255
+ messageCloser: " })",
256
+ }));
257
+ r += (0, withMessage_js_1.withMessage)(schema, "minLength", ({ json }) => ({
258
+ opener: `.min(${json}`,
259
+ closer: ")",
260
+ messagePrefix: ", { error: ",
261
+ messageCloser: " })",
262
+ }));
263
+ r += (0, withMessage_js_1.withMessage)(schema, "maxLength", ({ json }) => ({
264
+ opener: `.max(${json}`,
265
+ closer: ")",
266
+ messagePrefix: ", { error: ",
267
+ messageCloser: " })",
268
+ }));
49
269
  r += (0, withMessage_js_1.withMessage)(schema, "contentEncoding", ({ value }) => {
50
270
  if (value === "base64") {
51
- return [".base64(", ")"];
271
+ return {
272
+ opener: ".base64(",
273
+ closer: ")",
274
+ messagePrefix: "{ error: ",
275
+ messageCloser: " })",
276
+ };
52
277
  }
53
278
  });
54
279
  const contentMediaType = (0, withMessage_js_1.withMessage)(schema, "contentMediaType", ({ value }) => {
55
280
  if (value === "application/json") {
56
- return [
57
- ".transform((str, ctx) => { try { return JSON.parse(str); } catch (err) { ctx.addIssue({ code: \"custom\", message: \"Invalid JSON\" }); }}",
58
- ", ",
59
- ")"
60
- ];
281
+ return {
282
+ opener: '.transform((str, ctx) => { try { return JSON.parse(str); } catch (err) { ctx.addIssue({ code: "custom", message: "Invalid JSON" }); }}',
283
+ closer: ")",
284
+ messagePrefix: ", { error: ",
285
+ messageCloser: " })",
286
+ };
61
287
  }
62
288
  });
63
289
  if (contentMediaType != "") {
64
290
  r += contentMediaType;
65
291
  r += (0, withMessage_js_1.withMessage)(schema, "contentSchema", ({ value }) => {
66
292
  if (value && value instanceof Object) {
67
- return [
68
- `.pipe(${(0, parseSchema_js_1.parseSchema)(value)}`,
69
- ", ",
70
- ")"
71
- ];
293
+ return {
294
+ opener: `.pipe(${(0, parseSchema_js_1.parseSchema)(value, refContext)}`,
295
+ closer: ")",
296
+ messagePrefix: ", { error: ",
297
+ messageCloser: " })",
298
+ };
72
299
  }
73
300
  });
74
301
  }
75
302
  return r;
76
303
  };
77
304
  exports.parseString = parseString;
305
+ function ensureRefs(refs) {
306
+ if (refs)
307
+ return refs;
308
+ return {
309
+ path: [],
310
+ seen: new Map(),
311
+ declarations: new Map(),
312
+ dependencies: new Map(),
313
+ inProgress: new Set(),
314
+ refNameByPointer: new Map(),
315
+ usedNames: new Set(),
316
+ };
317
+ }
@@ -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,113 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.computeScc = exports.detectCycles = exports.findRefDependencies = void 0;
4
+ const findRefDependencies = (schema, validDefNames) => {
5
+ const deps = new Set();
6
+ function traverse(obj) {
7
+ if (obj === null || typeof obj !== "object")
8
+ return;
9
+ if (Array.isArray(obj)) {
10
+ obj.forEach(traverse);
11
+ return;
12
+ }
13
+ const record = obj;
14
+ if (typeof record["$ref"] === "string") {
15
+ const ref = record["$ref"];
16
+ const match = ref.match(/^#\/(?:\$defs|definitions)\/(.+)$/);
17
+ if (match && validDefNames.includes(match[1])) {
18
+ deps.add(match[1]);
19
+ }
20
+ }
21
+ for (const value of Object.values(record)) {
22
+ traverse(value);
23
+ }
24
+ }
25
+ traverse(schema);
26
+ return deps;
27
+ };
28
+ exports.findRefDependencies = findRefDependencies;
29
+ const detectCycles = (defNames, deps) => {
30
+ const cycleNodes = new Set();
31
+ const visited = new Set();
32
+ const recursionStack = new Set();
33
+ function dfs(node, path) {
34
+ if (recursionStack.has(node)) {
35
+ const cycleStart = path.indexOf(node);
36
+ for (let i = cycleStart; i < path.length; i++) {
37
+ cycleNodes.add(path[i]);
38
+ }
39
+ cycleNodes.add(node);
40
+ return;
41
+ }
42
+ if (visited.has(node))
43
+ return;
44
+ visited.add(node);
45
+ recursionStack.add(node);
46
+ const targets = deps.get(node);
47
+ if (targets) {
48
+ for (const dep of targets) {
49
+ dfs(dep, [...path, node]);
50
+ }
51
+ }
52
+ recursionStack.delete(node);
53
+ }
54
+ for (const defName of defNames) {
55
+ if (!visited.has(defName))
56
+ dfs(defName, []);
57
+ }
58
+ return cycleNodes;
59
+ };
60
+ exports.detectCycles = detectCycles;
61
+ const computeScc = (defNames, deps) => {
62
+ // Tarjan's algorithm, keeps mapping for quick "is this ref in my cycle?"
63
+ const index = new Map();
64
+ const lowlink = new Map();
65
+ const stack = [];
66
+ const onStack = new Set();
67
+ const componentByName = new Map();
68
+ const cycleMembers = new Set();
69
+ let currentIndex = 0;
70
+ let componentId = 0;
71
+ const strongConnect = (v) => {
72
+ index.set(v, currentIndex);
73
+ lowlink.set(v, currentIndex);
74
+ currentIndex += 1;
75
+ stack.push(v);
76
+ onStack.add(v);
77
+ const neighbors = deps.get(v);
78
+ if (neighbors) {
79
+ for (const w of neighbors) {
80
+ if (!index.has(w)) {
81
+ strongConnect(w);
82
+ lowlink.set(v, Math.min(lowlink.get(v), lowlink.get(w)));
83
+ }
84
+ else if (onStack.has(w)) {
85
+ lowlink.set(v, Math.min(lowlink.get(v), index.get(w)));
86
+ }
87
+ }
88
+ }
89
+ if (lowlink.get(v) === index.get(v)) {
90
+ const component = [];
91
+ let w;
92
+ do {
93
+ w = stack.pop();
94
+ if (w === undefined)
95
+ break;
96
+ onStack.delete(w);
97
+ component.push(w);
98
+ componentByName.set(w, componentId);
99
+ } while (w !== v);
100
+ if (component.length > 1) {
101
+ component.forEach((name) => cycleMembers.add(name));
102
+ }
103
+ componentId += 1;
104
+ }
105
+ };
106
+ for (const name of defNames) {
107
+ if (!index.has(name)) {
108
+ strongConnect(name);
109
+ }
110
+ }
111
+ return { cycleMembers, componentByName };
112
+ };
113
+ exports.computeScc = computeScc;
@@ -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;
@@ -7,14 +7,13 @@ function withMessage(schema, key, get) {
7
7
  if (value !== undefined) {
8
8
  const got = get({ value, json: JSON.stringify(value) });
9
9
  if (got) {
10
- const opener = got[0];
11
- const prefix = got.length === 3 ? got[1] : "";
12
- const closer = got.length === 3 ? got[2] : got[1];
10
+ const { opener, closer, messagePrefix = "", messageCloser } = got;
13
11
  r += opener;
14
12
  if (schema.errorMessage?.[key] !== undefined) {
15
- r += prefix + JSON.stringify(schema.errorMessage[key]);
13
+ r += messagePrefix + JSON.stringify(schema.errorMessage[key]);
14
+ r += messageCloser ?? closer;
15
+ return r;
16
16
  }
17
- r;
18
17
  r += closer;
19
18
  }
20
19
  }
@@ -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
+ };