@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.
- package/CHANGELOG.md +18 -0
- package/dist/cjs/core/analyzeSchema.js +62 -0
- package/dist/cjs/core/emitZod.js +141 -0
- package/dist/cjs/generators/generateBundle.js +117 -63
- package/dist/cjs/index.js +4 -0
- package/dist/cjs/jsonSchemaToZod.js +5 -167
- package/dist/cjs/parsers/parseAllOf.js +12 -6
- package/dist/cjs/parsers/parseBoolean.js +1 -3
- package/dist/cjs/parsers/parseIfThenElse.js +1 -1
- package/dist/cjs/parsers/parseNull.js +1 -3
- package/dist/cjs/parsers/parseObject.js +8 -3
- package/dist/cjs/parsers/parseSchema.js +130 -26
- package/dist/cjs/parsers/parseString.js +1 -1
- package/dist/cjs/utils/buildRefRegistry.js +56 -0
- package/dist/cjs/utils/omit.js +3 -2
- package/dist/cjs/utils/resolveUri.js +16 -0
- package/dist/esm/Types.js +1 -2
- package/dist/esm/cli.js +10 -12
- package/dist/esm/core/analyzeSchema.js +58 -0
- package/dist/esm/core/emitZod.js +137 -0
- package/dist/esm/generators/generateBundle.js +118 -68
- package/dist/esm/index.js +34 -46
- package/dist/esm/jsonSchemaToZod.js +5 -171
- package/dist/esm/parsers/parseAllOf.js +17 -14
- package/dist/esm/parsers/parseAnyOf.js +6 -10
- package/dist/esm/parsers/parseArray.js +11 -15
- package/dist/esm/parsers/parseBoolean.js +1 -7
- package/dist/esm/parsers/parseConst.js +1 -5
- package/dist/esm/parsers/parseDefault.js +3 -7
- package/dist/esm/parsers/parseEnum.js +1 -5
- package/dist/esm/parsers/parseIfThenElse.js +6 -10
- package/dist/esm/parsers/parseMultipleType.js +3 -7
- package/dist/esm/parsers/parseNot.js +4 -8
- package/dist/esm/parsers/parseNull.js +1 -7
- package/dist/esm/parsers/parseNullable.js +4 -8
- package/dist/esm/parsers/parseNumber.js +11 -15
- package/dist/esm/parsers/parseObject.js +33 -31
- package/dist/esm/parsers/parseOneOf.js +6 -10
- package/dist/esm/parsers/parseSchema.js +187 -87
- package/dist/esm/parsers/parseSimpleDiscriminatedOneOf.js +6 -10
- package/dist/esm/parsers/parseString.js +12 -16
- package/dist/esm/utils/anyOrUnknown.js +1 -5
- package/dist/esm/utils/buildRefRegistry.js +52 -0
- package/dist/esm/utils/cliTools.js +7 -13
- package/dist/esm/utils/cycles.js +3 -9
- package/dist/esm/utils/half.js +1 -5
- package/dist/esm/utils/jsdocs.js +3 -8
- package/dist/esm/utils/omit.js +4 -7
- package/dist/esm/utils/resolveUri.js +12 -0
- package/dist/esm/utils/withMessage.js +1 -4
- package/dist/esm/zodToJsonSchema.js +1 -4
- package/dist/types/Types.d.ts +34 -3
- package/dist/types/core/analyzeSchema.d.ts +24 -0
- package/dist/types/core/emitZod.d.ts +2 -0
- package/dist/types/generators/generateBundle.d.ts +5 -0
- package/dist/types/index.d.ts +4 -0
- package/dist/types/jsonSchemaToZod.d.ts +1 -1
- package/dist/types/parsers/parseBoolean.d.ts +1 -4
- package/dist/types/parsers/parseNull.d.ts +1 -4
- package/dist/types/parsers/parseSchema.d.ts +2 -1
- package/dist/types/utils/buildRefRegistry.d.ts +12 -0
- package/dist/types/utils/resolveUri.d.ts +1 -0
- package/docs/proposals/bundle-refactor.md +43 -0
- package/docs/proposals/ref-anchor-support.md +65 -0
- package/eslint.config.js +26 -0
- package/package.json +10 -4
- /package/{jest.config.js → jest.config.cjs} +0 -0
- /package/{postcjs.js → postcjs.cjs} +0 -0
- /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
|
|
5
|
-
const
|
|
6
|
-
const
|
|
7
|
-
const
|
|
8
|
-
|
|
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
|
|
6
|
+
const originalIndexKey = "__originalIndex";
|
|
7
7
|
const ensureOriginalIndex = (arr) => {
|
|
8
|
-
|
|
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 ? { [
|
|
12
|
+
newArr.push(item ? { [originalIndexKey]: i } : { [originalIndexKey]: i, not: {} });
|
|
13
13
|
}
|
|
14
|
-
else if (
|
|
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, [
|
|
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: [
|
|
33
|
+
path: [
|
|
34
|
+
...refs.path,
|
|
35
|
+
"allOf",
|
|
36
|
+
item[originalIndexKey] ?? 0,
|
|
37
|
+
],
|
|
32
38
|
});
|
|
33
39
|
}
|
|
34
40
|
else {
|
|
@@ -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
|
|
20
|
+
const issues = result.error.issues;
|
|
21
21
|
issues.forEach((issue) => ctx.addIssue(issue))
|
|
22
22
|
}
|
|
23
23
|
})`;
|
|
@@ -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]
|
|
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 ??
|
|
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
|
|
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(
|
|
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 = (
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
const
|
|
139
|
-
|
|
140
|
-
.
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
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
|
|
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)(
|
|
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)(
|
|
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
|
|
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;
|
package/dist/cjs/utils/omit.js
CHANGED
|
@@ -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
|
-
|
|
6
|
-
|
|
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
|
-
|
|
2
|
-
Object.defineProperty(exports, "__esModule", { value: true });
|
|
1
|
+
export {};
|