@gabrielbryk/json-schema-to-zod 2.8.0 → 2.9.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +13 -0
- package/dist/cjs/core/analyzeSchema.js +62 -0
- package/dist/cjs/core/emitZod.js +141 -0
- package/dist/cjs/generators/generateBundle.js +103 -59
- package/dist/cjs/index.js +4 -0
- package/dist/cjs/jsonSchemaToZod.js +5 -167
- package/dist/cjs/parsers/parseSchema.js +124 -24
- package/dist/cjs/utils/buildRefRegistry.js +56 -0
- 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 +104 -64
- package/dist/esm/index.js +34 -46
- package/dist/esm/jsonSchemaToZod.js +5 -171
- package/dist/esm/parsers/parseAllOf.js +5 -8
- package/dist/esm/parsers/parseAnyOf.js +6 -10
- package/dist/esm/parsers/parseArray.js +11 -15
- package/dist/esm/parsers/parseBoolean.js +1 -5
- 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 +5 -9
- package/dist/esm/parsers/parseMultipleType.js +3 -7
- package/dist/esm/parsers/parseNot.js +4 -8
- package/dist/esm/parsers/parseNull.js +1 -5
- package/dist/esm/parsers/parseNullable.js +4 -8
- package/dist/esm/parsers/parseNumber.js +11 -15
- package/dist/esm/parsers/parseObject.js +25 -28
- package/dist/esm/parsers/parseOneOf.js +6 -10
- package/dist/esm/parsers/parseSchema.js +183 -87
- package/dist/esm/parsers/parseSimpleDiscriminatedOneOf.js +6 -10
- package/dist/esm/parsers/parseString.js +11 -15
- 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 +1 -5
- 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 +28 -0
- 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/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,61 +1,31 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
const jsonSchemaToZod_js_1 = require("../jsonSchemaToZod.js");
|
|
5
|
-
const generateSchemaBundle = (schema, options = {}) => {
|
|
1
|
+
import { analyzeSchema } from "../core/analyzeSchema.js";
|
|
2
|
+
import { emitZod } from "../core/emitZod.js";
|
|
3
|
+
export const generateSchemaBundle = (schema, options = {}) => {
|
|
6
4
|
const module = options.module ?? "esm";
|
|
7
|
-
const rootName = options.splitDefs?.rootName ?? options.name ?? "RootSchema";
|
|
8
|
-
const rootTypeName = typeof options.type === "string"
|
|
9
|
-
? options.type
|
|
10
|
-
: options.splitDefs?.rootTypeName ?? (typeof options.type === "boolean" && options.type ? rootName : undefined);
|
|
11
5
|
if (!schema || typeof schema !== "object") {
|
|
12
6
|
throw new Error("generateSchemaBundle requires an object schema");
|
|
13
7
|
}
|
|
14
|
-
const defs = schema.$defs ||
|
|
8
|
+
const defs = schema.$defs || {};
|
|
9
|
+
const definitions = schema.definitions || {};
|
|
15
10
|
const defNames = Object.keys(defs);
|
|
16
|
-
const defInfoMap =
|
|
17
|
-
const cycles = detectCycles(defInfoMap);
|
|
18
|
-
for (const defName of cycles) {
|
|
19
|
-
const info = defInfoMap.get(defName);
|
|
20
|
-
if (info)
|
|
21
|
-
info.hasCycle = true;
|
|
22
|
-
}
|
|
11
|
+
const { rootName, rootTypeName, defInfoMap } = buildBundleContext(defNames, defs, options);
|
|
23
12
|
const files = [];
|
|
24
|
-
|
|
25
|
-
for (const
|
|
26
|
-
const
|
|
27
|
-
const
|
|
28
|
-
const defSchemaWithDefs = {
|
|
29
|
-
...defs[defName],
|
|
30
|
-
$defs: defs,
|
|
31
|
-
};
|
|
32
|
-
const zodSchema = (0, jsonSchemaToZod_js_1.jsonSchemaToZod)(defSchemaWithDefs, {
|
|
13
|
+
const targets = planBundleTargets(schema, defs, definitions, defNames, options, rootName, rootTypeName);
|
|
14
|
+
for (const target of targets) {
|
|
15
|
+
const usedRefs = target.usedRefs;
|
|
16
|
+
const analysis = analyzeSchema(target.schemaWithDefs, {
|
|
33
17
|
...options,
|
|
34
18
|
module,
|
|
35
|
-
name:
|
|
36
|
-
type:
|
|
37
|
-
parserOverride: createRefHandler(defName, defInfoMap, usedRefs,
|
|
19
|
+
name: target.schemaName,
|
|
20
|
+
type: target.typeName,
|
|
21
|
+
parserOverride: createRefHandler(target.defName, defInfoMap, usedRefs, {
|
|
22
|
+
...(target.schemaWithDefs.$defs || {}),
|
|
23
|
+
...(target.schemaWithDefs.definitions || {}),
|
|
24
|
+
}, options),
|
|
38
25
|
});
|
|
26
|
+
const zodSchema = emitZod(analysis);
|
|
39
27
|
const finalSchema = buildSchemaFile(zodSchema, usedRefs, defInfoMap, module);
|
|
40
|
-
|
|
41
|
-
files.push({ fileName, contents: finalSchema });
|
|
42
|
-
}
|
|
43
|
-
// Generate root schema if requested
|
|
44
|
-
if (options.splitDefs?.includeRoot ?? true) {
|
|
45
|
-
const usedRefs = new Set();
|
|
46
|
-
const workflowSchemaWithDefs = {
|
|
47
|
-
...schema,
|
|
48
|
-
};
|
|
49
|
-
const workflowZodSchema = (0, jsonSchemaToZod_js_1.jsonSchemaToZod)(workflowSchemaWithDefs, {
|
|
50
|
-
...options,
|
|
51
|
-
module,
|
|
52
|
-
name: rootName,
|
|
53
|
-
type: rootTypeName,
|
|
54
|
-
parserOverride: createRefHandler(null, defInfoMap, usedRefs, defs, options),
|
|
55
|
-
});
|
|
56
|
-
const finalWorkflow = buildSchemaFile(workflowZodSchema, usedRefs, defInfoMap, module);
|
|
57
|
-
const rootFile = options.splitDefs?.fileName?.("root", { isRoot: true }) ?? "workflow.schema.ts";
|
|
58
|
-
files.push({ fileName: rootFile, contents: finalWorkflow });
|
|
28
|
+
files.push({ fileName: target.fileName, contents: finalSchema });
|
|
59
29
|
}
|
|
60
30
|
// Nested types extraction (optional)
|
|
61
31
|
const nestedTypesEnabled = options.nestedTypes?.enable;
|
|
@@ -69,7 +39,6 @@ const generateSchemaBundle = (schema, options = {}) => {
|
|
|
69
39
|
}
|
|
70
40
|
return { files, defNames };
|
|
71
41
|
};
|
|
72
|
-
exports.generateSchemaBundle = generateSchemaBundle;
|
|
73
42
|
// ---------------------------------------------------------------------------
|
|
74
43
|
// Internals
|
|
75
44
|
// ---------------------------------------------------------------------------
|
|
@@ -95,6 +64,20 @@ const buildDefInfoMap = (defNames, defs, options) => {
|
|
|
95
64
|
}
|
|
96
65
|
return map;
|
|
97
66
|
};
|
|
67
|
+
const buildBundleContext = (defNames, defs, options) => {
|
|
68
|
+
const defInfoMap = buildDefInfoMap(defNames, defs, options);
|
|
69
|
+
const cycles = detectCycles(defInfoMap);
|
|
70
|
+
for (const defName of cycles) {
|
|
71
|
+
const info = defInfoMap.get(defName);
|
|
72
|
+
if (info)
|
|
73
|
+
info.hasCycle = true;
|
|
74
|
+
}
|
|
75
|
+
const rootName = options.splitDefs?.rootName ?? options.name ?? "RootSchema";
|
|
76
|
+
const rootTypeName = typeof options.type === "string"
|
|
77
|
+
? options.type
|
|
78
|
+
: options.splitDefs?.rootTypeName ?? (typeof options.type === "boolean" && options.type ? rootName : undefined);
|
|
79
|
+
return { defInfoMap, rootName, rootTypeName };
|
|
80
|
+
};
|
|
98
81
|
const createRefHandler = (currentDefName, defInfoMap, usedRefs, allDefs, options) => {
|
|
99
82
|
return (schema, refs) => {
|
|
100
83
|
if (typeof schema["$ref"] === "string") {
|
|
@@ -102,6 +85,10 @@ const createRefHandler = (currentDefName, defInfoMap, usedRefs, allDefs, options
|
|
|
102
85
|
const match = refPath.match(/^#\/(?:\$defs|definitions)\/(.+)$/);
|
|
103
86
|
if (match) {
|
|
104
87
|
const refName = match[1];
|
|
88
|
+
// Only intercept top-level def refs (no nested path like a/$defs/x)
|
|
89
|
+
if (refName.includes("/")) {
|
|
90
|
+
return undefined;
|
|
91
|
+
}
|
|
105
92
|
const refInfo = defInfoMap.get(refName);
|
|
106
93
|
if (refInfo) {
|
|
107
94
|
// Track imports when referencing other defs
|
|
@@ -118,22 +105,22 @@ const createRefHandler = (currentDefName, defInfoMap, usedRefs, allDefs, options
|
|
|
118
105
|
});
|
|
119
106
|
if (resolved)
|
|
120
107
|
return resolved;
|
|
108
|
+
if (isCycle && options.refResolution?.lazyCrossRefs) {
|
|
109
|
+
return `z.lazy(() => ${refInfo.schemaName})`;
|
|
110
|
+
}
|
|
121
111
|
return refInfo.schemaName;
|
|
122
112
|
}
|
|
113
|
+
// If the ref points to a local/inline $def (not part of top-level defs),
|
|
114
|
+
// let the default parser resolve it normally.
|
|
115
|
+
if (allDefs && Object.prototype.hasOwnProperty.call(allDefs, refName)) {
|
|
116
|
+
return undefined;
|
|
117
|
+
}
|
|
123
118
|
}
|
|
124
119
|
const unknown = options.refResolution?.onUnknownRef?.({ ref: refPath, currentDef: currentDefName });
|
|
125
120
|
if (unknown)
|
|
126
121
|
return unknown;
|
|
127
122
|
return options.useUnknown ? "z.unknown()" : "z.any()";
|
|
128
123
|
}
|
|
129
|
-
// Inline $defs within a schema
|
|
130
|
-
if (schema["$defs"] && typeof schema["$defs"] === "object") {
|
|
131
|
-
return (0, jsonSchemaToZod_js_1.jsonSchemaToZod)(schema, {
|
|
132
|
-
...options,
|
|
133
|
-
module: options.module ?? "esm",
|
|
134
|
-
parserOverride: createRefHandler(currentDefName, defInfoMap, usedRefs, allDefs, options),
|
|
135
|
-
});
|
|
136
|
-
}
|
|
137
124
|
return undefined;
|
|
138
125
|
};
|
|
139
126
|
};
|
|
@@ -151,6 +138,52 @@ const buildSchemaFile = (zodCode, usedRefs, defInfoMap, module) => {
|
|
|
151
138
|
return zodCode;
|
|
152
139
|
return zodCode.replace('import { z } from "zod"', `import { z } from "zod"\n${imports.join("\n")}`);
|
|
153
140
|
};
|
|
141
|
+
const planBundleTargets = (rootSchema, defs, definitions, defNames, options, rootName, rootTypeName) => {
|
|
142
|
+
const targets = [];
|
|
143
|
+
for (const defName of defNames) {
|
|
144
|
+
const defSchema = defs[defName];
|
|
145
|
+
const defSchemaWithDefs = {
|
|
146
|
+
...defSchema,
|
|
147
|
+
$defs: { ...defs, ...defSchema?.$defs },
|
|
148
|
+
definitions: {
|
|
149
|
+
...defSchema.definitions,
|
|
150
|
+
...definitions,
|
|
151
|
+
},
|
|
152
|
+
};
|
|
153
|
+
const pascalName = toPascalCase(defName);
|
|
154
|
+
const schemaName = options.splitDefs?.schemaName?.(defName, { isRoot: false }) ?? `${pascalName}Schema`;
|
|
155
|
+
const typeName = options.splitDefs?.typeName?.(defName, { isRoot: false }) ?? pascalName;
|
|
156
|
+
const fileName = options.splitDefs?.fileName?.(defName, { isRoot: false }) ?? `${defName}.schema.ts`;
|
|
157
|
+
targets.push({
|
|
158
|
+
defName,
|
|
159
|
+
schemaWithDefs: defSchemaWithDefs,
|
|
160
|
+
schemaName,
|
|
161
|
+
typeName,
|
|
162
|
+
fileName,
|
|
163
|
+
usedRefs: new Set(),
|
|
164
|
+
isRoot: false,
|
|
165
|
+
});
|
|
166
|
+
}
|
|
167
|
+
if (options.splitDefs?.includeRoot ?? true) {
|
|
168
|
+
const rootFile = options.splitDefs?.fileName?.("root", { isRoot: true }) ?? "workflow.schema.ts";
|
|
169
|
+
targets.push({
|
|
170
|
+
defName: null,
|
|
171
|
+
schemaWithDefs: {
|
|
172
|
+
...rootSchema,
|
|
173
|
+
definitions: {
|
|
174
|
+
...rootSchema.definitions,
|
|
175
|
+
...definitions,
|
|
176
|
+
},
|
|
177
|
+
},
|
|
178
|
+
schemaName: rootName,
|
|
179
|
+
typeName: rootTypeName,
|
|
180
|
+
fileName: rootFile,
|
|
181
|
+
usedRefs: new Set(),
|
|
182
|
+
isRoot: true,
|
|
183
|
+
});
|
|
184
|
+
}
|
|
185
|
+
return targets;
|
|
186
|
+
};
|
|
154
187
|
const findRefDependencies = (schema, validDefNames) => {
|
|
155
188
|
const deps = new Set();
|
|
156
189
|
function traverse(obj) {
|
|
@@ -239,7 +272,7 @@ const findNestedTypesInSchema = (schema, parentTypeName, defNames, currentPath =
|
|
|
239
272
|
if (schema === null || typeof schema !== "object")
|
|
240
273
|
return nestedTypes;
|
|
241
274
|
const record = schema;
|
|
242
|
-
if (record.title && typeof record.title === "string"
|
|
275
|
+
if (record.title && typeof record.title === "string") {
|
|
243
276
|
const title = record.title;
|
|
244
277
|
if (title !== parentTypeName && !defNames.map((d) => toPascalCase(d)).includes(title)) {
|
|
245
278
|
nestedTypes.push({
|
|
@@ -250,6 +283,12 @@ const findNestedTypesInSchema = (schema, parentTypeName, defNames, currentPath =
|
|
|
250
283
|
});
|
|
251
284
|
}
|
|
252
285
|
}
|
|
286
|
+
// inline $defs
|
|
287
|
+
if (record.$defs && typeof record.$defs === "object") {
|
|
288
|
+
for (const [_defName, defSchema] of Object.entries(record.$defs)) {
|
|
289
|
+
nestedTypes.push(...findNestedTypesInSchema(defSchema, parentTypeName, defNames, currentPath));
|
|
290
|
+
}
|
|
291
|
+
}
|
|
253
292
|
if (record.properties && typeof record.properties === "object") {
|
|
254
293
|
for (const [propName, propSchema] of Object.entries(record.properties)) {
|
|
255
294
|
nestedTypes.push(...findNestedTypesInSchema(propSchema, parentTypeName, defNames, [...currentPath, propName]));
|
|
@@ -261,10 +300,10 @@ const findNestedTypesInSchema = (schema, parentTypeName, defNames, currentPath =
|
|
|
261
300
|
}
|
|
262
301
|
}
|
|
263
302
|
if (record.items) {
|
|
264
|
-
nestedTypes.push(...findNestedTypesInSchema(record.items, parentTypeName, defNames, currentPath));
|
|
303
|
+
nestedTypes.push(...findNestedTypesInSchema(record.items, parentTypeName, defNames, [...currentPath, "items"]));
|
|
265
304
|
}
|
|
266
305
|
if (record.additionalProperties && typeof record.additionalProperties === "object") {
|
|
267
|
-
nestedTypes.push(...findNestedTypesInSchema(record.additionalProperties, parentTypeName, defNames, currentPath));
|
|
306
|
+
nestedTypes.push(...findNestedTypesInSchema(record.additionalProperties, parentTypeName, defNames, [...currentPath, "additionalProperties"]));
|
|
268
307
|
}
|
|
269
308
|
return nestedTypes;
|
|
270
309
|
};
|
|
@@ -285,12 +324,13 @@ const generateNestedTypesFile = (nestedTypes) => {
|
|
|
285
324
|
}
|
|
286
325
|
byParent.get(info.parentType).push(info);
|
|
287
326
|
}
|
|
288
|
-
const imports = new
|
|
327
|
+
const imports = new Map(); // file -> type name
|
|
289
328
|
for (const info of nestedTypes) {
|
|
290
|
-
imports.
|
|
329
|
+
if (!imports.has(info.file)) {
|
|
330
|
+
imports.set(info.file, info.parentType);
|
|
331
|
+
}
|
|
291
332
|
}
|
|
292
|
-
for (const file of [...imports].sort()) {
|
|
293
|
-
const typeName = file === "workflow" ? "Workflow" : toPascalCase(file);
|
|
333
|
+
for (const [file, typeName] of [...imports.entries()].sort()) {
|
|
294
334
|
lines.push(`import type { ${typeName} } from './${file}.schema.js';`);
|
|
295
335
|
}
|
|
296
336
|
lines.push("");
|
package/dist/esm/index.js
CHANGED
|
@@ -1,46 +1,34 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
__exportStar(require("./parsers/parseSchema.js"), exports);
|
|
36
|
-
__exportStar(require("./parsers/parseSimpleDiscriminatedOneOf.js"), exports);
|
|
37
|
-
__exportStar(require("./parsers/parseString.js"), exports);
|
|
38
|
-
__exportStar(require("./utils/anyOrUnknown.js"), exports);
|
|
39
|
-
__exportStar(require("./utils/cycles.js"), exports);
|
|
40
|
-
__exportStar(require("./utils/half.js"), exports);
|
|
41
|
-
__exportStar(require("./utils/jsdocs.js"), exports);
|
|
42
|
-
__exportStar(require("./utils/omit.js"), exports);
|
|
43
|
-
__exportStar(require("./utils/withMessage.js"), exports);
|
|
44
|
-
__exportStar(require("./zodToJsonSchema.js"), exports);
|
|
45
|
-
const jsonSchemaToZod_js_1 = require("./jsonSchemaToZod.js");
|
|
46
|
-
exports.default = jsonSchemaToZod_js_1.jsonSchemaToZod;
|
|
1
|
+
export * from "./Types.js";
|
|
2
|
+
export * from "./core/analyzeSchema.js";
|
|
3
|
+
export * from "./core/emitZod.js";
|
|
4
|
+
export * from "./generators/generateBundle.js";
|
|
5
|
+
export * from "./jsonSchemaToZod.js";
|
|
6
|
+
export * from "./parsers/parseAllOf.js";
|
|
7
|
+
export * from "./parsers/parseAnyOf.js";
|
|
8
|
+
export * from "./parsers/parseArray.js";
|
|
9
|
+
export * from "./parsers/parseBoolean.js";
|
|
10
|
+
export * from "./parsers/parseConst.js";
|
|
11
|
+
export * from "./parsers/parseDefault.js";
|
|
12
|
+
export * from "./parsers/parseEnum.js";
|
|
13
|
+
export * from "./parsers/parseIfThenElse.js";
|
|
14
|
+
export * from "./parsers/parseMultipleType.js";
|
|
15
|
+
export * from "./parsers/parseNot.js";
|
|
16
|
+
export * from "./parsers/parseNull.js";
|
|
17
|
+
export * from "./parsers/parseNullable.js";
|
|
18
|
+
export * from "./parsers/parseNumber.js";
|
|
19
|
+
export * from "./parsers/parseObject.js";
|
|
20
|
+
export * from "./parsers/parseOneOf.js";
|
|
21
|
+
export * from "./parsers/parseSchema.js";
|
|
22
|
+
export * from "./parsers/parseSimpleDiscriminatedOneOf.js";
|
|
23
|
+
export * from "./parsers/parseString.js";
|
|
24
|
+
export * from "./utils/anyOrUnknown.js";
|
|
25
|
+
export * from "./utils/buildRefRegistry.js";
|
|
26
|
+
export * from "./utils/cycles.js";
|
|
27
|
+
export * from "./utils/half.js";
|
|
28
|
+
export * from "./utils/jsdocs.js";
|
|
29
|
+
export * from "./utils/omit.js";
|
|
30
|
+
export * from "./utils/resolveUri.js";
|
|
31
|
+
export * from "./utils/withMessage.js";
|
|
32
|
+
export * from "./zodToJsonSchema.js";
|
|
33
|
+
import { jsonSchemaToZod } from "./jsonSchemaToZod.js";
|
|
34
|
+
export default jsonSchemaToZod;
|
|
@@ -1,172 +1,6 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
const
|
|
5
|
-
|
|
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" : ""}`;
|
|
1
|
+
import { analyzeSchema } from "./core/analyzeSchema.js";
|
|
2
|
+
import { emitZod } from "./core/emitZod.js";
|
|
3
|
+
export const jsonSchemaToZod = (schema, options = {}) => {
|
|
4
|
+
const analysis = analyzeSchema(schema, options);
|
|
5
|
+
return emitZod(analysis);
|
|
103
6
|
};
|
|
104
|
-
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
|
-
}
|
|
@@ -1,8 +1,5 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
exports.parseAllOf = parseAllOf;
|
|
4
|
-
const parseSchema_js_1 = require("./parseSchema.js");
|
|
5
|
-
const half_js_1 = require("../utils/half.js");
|
|
1
|
+
import { parseSchema } from "./parseSchema.js";
|
|
2
|
+
import { half } from "../utils/half.js";
|
|
6
3
|
const originalIndex = Symbol("Original index");
|
|
7
4
|
const ensureOriginalIndex = (arr) => {
|
|
8
5
|
let newArr = [];
|
|
@@ -20,19 +17,19 @@ const ensureOriginalIndex = (arr) => {
|
|
|
20
17
|
}
|
|
21
18
|
return newArr;
|
|
22
19
|
};
|
|
23
|
-
function parseAllOf(schema, refs) {
|
|
20
|
+
export function parseAllOf(schema, refs) {
|
|
24
21
|
if (schema.allOf.length === 0) {
|
|
25
22
|
return "z.never()";
|
|
26
23
|
}
|
|
27
24
|
else if (schema.allOf.length === 1) {
|
|
28
25
|
const item = schema.allOf[0];
|
|
29
|
-
return
|
|
26
|
+
return parseSchema(item, {
|
|
30
27
|
...refs,
|
|
31
28
|
path: [...refs.path, "allOf", item[originalIndex]],
|
|
32
29
|
});
|
|
33
30
|
}
|
|
34
31
|
else {
|
|
35
|
-
const [left, right] =
|
|
32
|
+
const [left, right] = half(ensureOriginalIndex(schema.allOf));
|
|
36
33
|
return `z.intersection(${parseAllOf({ allOf: left }, refs)}, ${parseAllOf({
|
|
37
34
|
allOf: right,
|
|
38
35
|
}, refs)})`;
|
|
@@ -1,18 +1,14 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
const parseSchema_js_1 = require("./parseSchema.js");
|
|
5
|
-
const anyOrUnknown_js_1 = require("../utils/anyOrUnknown.js");
|
|
6
|
-
const parseAnyOf = (schema, refs) => {
|
|
1
|
+
import { parseSchema } from "./parseSchema.js";
|
|
2
|
+
import { anyOrUnknown } from "../utils/anyOrUnknown.js";
|
|
3
|
+
export const parseAnyOf = (schema, refs) => {
|
|
7
4
|
return schema.anyOf.length
|
|
8
5
|
? schema.anyOf.length === 1
|
|
9
|
-
?
|
|
6
|
+
? parseSchema(schema.anyOf[0], {
|
|
10
7
|
...refs,
|
|
11
8
|
path: [...refs.path, "anyOf", 0],
|
|
12
9
|
})
|
|
13
10
|
: `z.union([${schema.anyOf
|
|
14
|
-
.map((schema, i) =>
|
|
11
|
+
.map((schema, i) => parseSchema(schema, { ...refs, path: [...refs.path, "anyOf", i] }))
|
|
15
12
|
.join(", ")}])`
|
|
16
|
-
:
|
|
13
|
+
: anyOrUnknown(refs);
|
|
17
14
|
};
|
|
18
|
-
exports.parseAnyOf = parseAnyOf;
|
|
@@ -1,14 +1,11 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
const
|
|
5
|
-
const parseSchema_js_1 = require("./parseSchema.js");
|
|
6
|
-
const anyOrUnknown_js_1 = require("../utils/anyOrUnknown.js");
|
|
7
|
-
const parseArray = (schema, refs) => {
|
|
1
|
+
import { withMessage } from "../utils/withMessage.js";
|
|
2
|
+
import { parseSchema } from "./parseSchema.js";
|
|
3
|
+
import { anyOrUnknown } from "../utils/anyOrUnknown.js";
|
|
4
|
+
export const parseArray = (schema, refs) => {
|
|
8
5
|
if (Array.isArray(schema.items)) {
|
|
9
|
-
let tuple = `z.tuple([${schema.items.map((v, i) =>
|
|
6
|
+
let tuple = `z.tuple([${schema.items.map((v, i) => parseSchema(v, { ...refs, path: [...refs.path, "items", i] }))}])`;
|
|
10
7
|
if (schema.contains) {
|
|
11
|
-
const containsSchema =
|
|
8
|
+
const containsSchema = parseSchema(schema.contains, {
|
|
12
9
|
...refs,
|
|
13
10
|
path: [...refs.path, "contains"],
|
|
14
11
|
});
|
|
@@ -27,18 +24,18 @@ const parseArray = (schema, refs) => {
|
|
|
27
24
|
return tuple;
|
|
28
25
|
}
|
|
29
26
|
let r = !schema.items
|
|
30
|
-
? `z.array(${
|
|
31
|
-
: `z.array(${
|
|
27
|
+
? `z.array(${anyOrUnknown(refs)})`
|
|
28
|
+
: `z.array(${parseSchema(schema.items, {
|
|
32
29
|
...refs,
|
|
33
30
|
path: [...refs.path, "items"],
|
|
34
31
|
})})`;
|
|
35
|
-
r +=
|
|
32
|
+
r += withMessage(schema, "minItems", ({ json }) => ({
|
|
36
33
|
opener: `.min(${json}`,
|
|
37
34
|
closer: ")",
|
|
38
35
|
messagePrefix: ", { error: ",
|
|
39
36
|
messageCloser: " })",
|
|
40
37
|
}));
|
|
41
|
-
r +=
|
|
38
|
+
r += withMessage(schema, "maxItems", ({ json }) => ({
|
|
42
39
|
opener: `.max(${json}`,
|
|
43
40
|
closer: ")",
|
|
44
41
|
messagePrefix: ", { error: ",
|
|
@@ -69,7 +66,7 @@ const parseArray = (schema, refs) => {
|
|
|
69
66
|
})`;
|
|
70
67
|
}
|
|
71
68
|
if (schema.contains) {
|
|
72
|
-
const containsSchema =
|
|
69
|
+
const containsSchema = parseSchema(schema.contains, {
|
|
73
70
|
...refs,
|
|
74
71
|
path: [...refs.path, "contains"],
|
|
75
72
|
});
|
|
@@ -87,4 +84,3 @@ const parseArray = (schema, refs) => {
|
|
|
87
84
|
}
|
|
88
85
|
return r;
|
|
89
86
|
};
|
|
90
|
-
exports.parseArray = parseArray;
|
|
@@ -1,7 +1,3 @@
|
|
|
1
|
-
|
|
2
|
-
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.parseConst = void 0;
|
|
4
|
-
const parseConst = (schema) => {
|
|
1
|
+
export const parseConst = (schema) => {
|
|
5
2
|
return `z.literal(${JSON.stringify(schema.const)})`;
|
|
6
3
|
};
|
|
7
|
-
exports.parseConst = parseConst;
|