@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
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,18 @@
|
|
|
1
1
|
# @gabrielbryk/json-schema-to-zod
|
|
2
2
|
|
|
3
|
+
## 2.9.0
|
|
4
|
+
|
|
5
|
+
### Minor Changes
|
|
6
|
+
|
|
7
|
+
- 0ef12db: - Extract analyzer/emitter core, refactor bundle generation to avoid recursion and preserve inline defs/definitions.
|
|
8
|
+
- Add scoped naming for inline $defs, lazy cross-ref handling, and nested types emission.
|
|
9
|
+
- Expand bundle test coverage with snapshots, inline-def fixtures, and nested-type extraction.
|
|
10
|
+
|
|
11
|
+
### Patch Changes
|
|
12
|
+
|
|
13
|
+
- b8f6248: - Commit remaining utility, ref resolution test, and config updates.
|
|
14
|
+
- 7d257dd: - Ensure dist/esm emits real ESM with NodeNext settings and type:module, and update tests to run under ESM by providing createRequire shims.
|
|
15
|
+
|
|
3
16
|
## 2.8.0
|
|
4
17
|
|
|
5
18
|
### Minor Changes
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.analyzeSchema = void 0;
|
|
4
|
+
const parseSchema_js_1 = require("../parsers/parseSchema.js");
|
|
5
|
+
const cycles_js_1 = require("../utils/cycles.js");
|
|
6
|
+
const buildRefRegistry_js_1 = require("../utils/buildRefRegistry.js");
|
|
7
|
+
const analyzeSchema = (schema, options = {}) => {
|
|
8
|
+
const { module, name, type, ...rest } = options;
|
|
9
|
+
if (type && (!name || module !== "esm")) {
|
|
10
|
+
throw new Error("Option `type` requires `name` to be set and `module` to be `esm`");
|
|
11
|
+
}
|
|
12
|
+
const normalized = {
|
|
13
|
+
module,
|
|
14
|
+
name,
|
|
15
|
+
type,
|
|
16
|
+
...rest,
|
|
17
|
+
exportRefs: rest.exportRefs ?? true,
|
|
18
|
+
withMeta: rest.withMeta ?? true,
|
|
19
|
+
};
|
|
20
|
+
const refNameByPointer = new Map();
|
|
21
|
+
const usedNames = new Set();
|
|
22
|
+
if (name) {
|
|
23
|
+
usedNames.add(name);
|
|
24
|
+
}
|
|
25
|
+
const declarations = new Map();
|
|
26
|
+
const dependencies = new Map();
|
|
27
|
+
const { registry: refRegistry, rootBaseUri } = (0, buildRefRegistry_js_1.buildRefRegistry)(schema);
|
|
28
|
+
const pass1 = {
|
|
29
|
+
module,
|
|
30
|
+
name,
|
|
31
|
+
path: [],
|
|
32
|
+
seen: new Map(),
|
|
33
|
+
declarations,
|
|
34
|
+
dependencies,
|
|
35
|
+
inProgress: new Set(),
|
|
36
|
+
refNameByPointer,
|
|
37
|
+
usedNames,
|
|
38
|
+
root: schema,
|
|
39
|
+
currentSchemaName: name,
|
|
40
|
+
refRegistry,
|
|
41
|
+
rootBaseUri,
|
|
42
|
+
...rest,
|
|
43
|
+
withMeta: normalized.withMeta,
|
|
44
|
+
};
|
|
45
|
+
(0, parseSchema_js_1.parseSchema)(schema, pass1);
|
|
46
|
+
const names = Array.from(declarations.keys());
|
|
47
|
+
const cycleRefNames = (0, cycles_js_1.detectCycles)(names, dependencies);
|
|
48
|
+
const { componentByName } = (0, cycles_js_1.computeScc)(names, dependencies);
|
|
49
|
+
return {
|
|
50
|
+
schema,
|
|
51
|
+
options: normalized,
|
|
52
|
+
refNameByPointer,
|
|
53
|
+
usedNames,
|
|
54
|
+
declarations,
|
|
55
|
+
dependencies,
|
|
56
|
+
cycleRefNames,
|
|
57
|
+
cycleComponentByName: componentByName,
|
|
58
|
+
refRegistry,
|
|
59
|
+
rootBaseUri,
|
|
60
|
+
};
|
|
61
|
+
};
|
|
62
|
+
exports.analyzeSchema = analyzeSchema;
|
|
@@ -0,0 +1,141 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.emitZod = void 0;
|
|
4
|
+
const parseSchema_js_1 = require("../parsers/parseSchema.js");
|
|
5
|
+
const jsdocs_js_1 = require("../utils/jsdocs.js");
|
|
6
|
+
const orderDeclarations = (entries, dependencies) => {
|
|
7
|
+
const valueByName = new Map(entries);
|
|
8
|
+
const depGraph = new Map();
|
|
9
|
+
for (const [from, set] of dependencies.entries()) {
|
|
10
|
+
const onlyKnown = new Set();
|
|
11
|
+
for (const dep of set) {
|
|
12
|
+
if (valueByName.has(dep) && dep !== from) {
|
|
13
|
+
onlyKnown.add(dep);
|
|
14
|
+
}
|
|
15
|
+
}
|
|
16
|
+
if (onlyKnown.size)
|
|
17
|
+
depGraph.set(from, onlyKnown);
|
|
18
|
+
}
|
|
19
|
+
const names = Array.from(valueByName.keys());
|
|
20
|
+
for (const [name, value] of entries) {
|
|
21
|
+
const deps = depGraph.get(name) ?? new Set();
|
|
22
|
+
for (const candidate of names) {
|
|
23
|
+
if (candidate === name)
|
|
24
|
+
continue;
|
|
25
|
+
const matcher = new RegExp(`\\b${candidate}\\b`);
|
|
26
|
+
if (matcher.test(value)) {
|
|
27
|
+
deps.add(candidate);
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
if (deps.size)
|
|
31
|
+
depGraph.set(name, deps);
|
|
32
|
+
}
|
|
33
|
+
const ordered = [];
|
|
34
|
+
const perm = new Set();
|
|
35
|
+
const temp = new Set();
|
|
36
|
+
const visit = (name) => {
|
|
37
|
+
if (perm.has(name))
|
|
38
|
+
return;
|
|
39
|
+
if (temp.has(name)) {
|
|
40
|
+
temp.delete(name);
|
|
41
|
+
perm.add(name);
|
|
42
|
+
ordered.push(name);
|
|
43
|
+
return;
|
|
44
|
+
}
|
|
45
|
+
temp.add(name);
|
|
46
|
+
const deps = depGraph.get(name);
|
|
47
|
+
if (deps) {
|
|
48
|
+
for (const dep of deps) {
|
|
49
|
+
if (valueByName.has(dep)) {
|
|
50
|
+
visit(dep);
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
temp.delete(name);
|
|
55
|
+
perm.add(name);
|
|
56
|
+
ordered.push(name);
|
|
57
|
+
};
|
|
58
|
+
for (const name of valueByName.keys()) {
|
|
59
|
+
visit(name);
|
|
60
|
+
}
|
|
61
|
+
const unique = [];
|
|
62
|
+
const seen = new Set();
|
|
63
|
+
for (const name of ordered) {
|
|
64
|
+
if (!seen.has(name)) {
|
|
65
|
+
seen.add(name);
|
|
66
|
+
unique.push(name);
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
return unique.map((name) => [name, valueByName.get(name)]);
|
|
70
|
+
};
|
|
71
|
+
const emitZod = (analysis) => {
|
|
72
|
+
const { schema, options, refNameByPointer, usedNames, cycleRefNames, cycleComponentByName, } = analysis;
|
|
73
|
+
const { module, name, type, noImport, exportRefs, withMeta, ...rest } = options;
|
|
74
|
+
const declarations = new Map();
|
|
75
|
+
const dependencies = new Map();
|
|
76
|
+
const parsedSchema = (0, parseSchema_js_1.parseSchema)(schema, {
|
|
77
|
+
module,
|
|
78
|
+
name,
|
|
79
|
+
path: [],
|
|
80
|
+
seen: new Map(),
|
|
81
|
+
declarations,
|
|
82
|
+
dependencies,
|
|
83
|
+
inProgress: new Set(),
|
|
84
|
+
refNameByPointer,
|
|
85
|
+
usedNames,
|
|
86
|
+
root: schema,
|
|
87
|
+
currentSchemaName: name,
|
|
88
|
+
cycleRefNames,
|
|
89
|
+
cycleComponentByName,
|
|
90
|
+
refRegistry: analysis.refRegistry,
|
|
91
|
+
rootBaseUri: analysis.rootBaseUri,
|
|
92
|
+
...rest,
|
|
93
|
+
withMeta,
|
|
94
|
+
});
|
|
95
|
+
const declarationBlock = declarations.size
|
|
96
|
+
? orderDeclarations(Array.from(declarations.entries()), dependencies)
|
|
97
|
+
.map(([refName, value]) => {
|
|
98
|
+
const shouldExport = exportRefs && module === "esm";
|
|
99
|
+
const decl = `${shouldExport ? "export " : ""}const ${refName} = ${value}`;
|
|
100
|
+
return decl;
|
|
101
|
+
})
|
|
102
|
+
.join("\n")
|
|
103
|
+
: "";
|
|
104
|
+
const jsdocs = rest.withJsdocs && typeof schema !== "boolean" && schema.description
|
|
105
|
+
? (0, jsdocs_js_1.expandJsdocs)(schema.description)
|
|
106
|
+
: "";
|
|
107
|
+
const lines = [];
|
|
108
|
+
if (module === "cjs" && !noImport) {
|
|
109
|
+
lines.push(`const { z } = require("zod")`);
|
|
110
|
+
}
|
|
111
|
+
if (module === "esm" && !noImport) {
|
|
112
|
+
lines.push(`import { z } from "zod"`);
|
|
113
|
+
}
|
|
114
|
+
if (declarationBlock) {
|
|
115
|
+
lines.push(declarationBlock);
|
|
116
|
+
}
|
|
117
|
+
if (module === "cjs") {
|
|
118
|
+
const payload = name ? `{ ${JSON.stringify(name)}: ${parsedSchema} }` : parsedSchema;
|
|
119
|
+
lines.push(`${jsdocs}module.exports = ${payload}`);
|
|
120
|
+
}
|
|
121
|
+
else if (module === "esm") {
|
|
122
|
+
const exportLine = `${jsdocs}export ${name ? `const ${name} =` : `default`} ${parsedSchema}`;
|
|
123
|
+
lines.push(exportLine);
|
|
124
|
+
}
|
|
125
|
+
else if (name) {
|
|
126
|
+
lines.push(`${jsdocs}const ${name} = ${parsedSchema}`);
|
|
127
|
+
}
|
|
128
|
+
else {
|
|
129
|
+
lines.push(`${jsdocs}${parsedSchema}`);
|
|
130
|
+
}
|
|
131
|
+
let typeLine;
|
|
132
|
+
if (type && name) {
|
|
133
|
+
const typeName = typeof type === "string" ? type : `${name[0].toUpperCase()}${name.substring(1)}`;
|
|
134
|
+
typeLine = `export type ${typeName} = z.infer<typeof ${name}>`;
|
|
135
|
+
}
|
|
136
|
+
const joined = lines.filter(Boolean).join("\n\n");
|
|
137
|
+
const combined = typeLine ? `${joined}\n${typeLine}` : joined;
|
|
138
|
+
const shouldEndWithNewline = module === "esm" || module === "cjs";
|
|
139
|
+
return `${combined}${shouldEndWithNewline ? "\n" : ""}`;
|
|
140
|
+
};
|
|
141
|
+
exports.emitZod = emitZod;
|
|
@@ -1,61 +1,34 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
exports.generateSchemaBundle = void 0;
|
|
4
|
-
const
|
|
4
|
+
const analyzeSchema_js_1 = require("../core/analyzeSchema.js");
|
|
5
|
+
const emitZod_js_1 = require("../core/emitZod.js");
|
|
5
6
|
const generateSchemaBundle = (schema, options = {}) => {
|
|
6
7
|
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
8
|
if (!schema || typeof schema !== "object") {
|
|
12
9
|
throw new Error("generateSchemaBundle requires an object schema");
|
|
13
10
|
}
|
|
14
|
-
const defs = schema.$defs ||
|
|
11
|
+
const defs = schema.$defs || {};
|
|
12
|
+
const definitions = schema.definitions || {};
|
|
15
13
|
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
|
-
}
|
|
14
|
+
const { rootName, rootTypeName, defInfoMap } = buildBundleContext(defNames, defs, options);
|
|
23
15
|
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, {
|
|
16
|
+
const targets = planBundleTargets(schema, defs, definitions, defNames, options, rootName, rootTypeName);
|
|
17
|
+
for (const target of targets) {
|
|
18
|
+
const usedRefs = target.usedRefs;
|
|
19
|
+
const analysis = (0, analyzeSchema_js_1.analyzeSchema)(target.schemaWithDefs, {
|
|
33
20
|
...options,
|
|
34
21
|
module,
|
|
35
|
-
name:
|
|
36
|
-
type:
|
|
37
|
-
parserOverride: createRefHandler(defName, defInfoMap, usedRefs,
|
|
22
|
+
name: target.schemaName,
|
|
23
|
+
type: target.typeName,
|
|
24
|
+
parserOverride: createRefHandler(target.defName, defInfoMap, usedRefs, {
|
|
25
|
+
...(target.schemaWithDefs.$defs || {}),
|
|
26
|
+
...(target.schemaWithDefs.definitions || {}),
|
|
27
|
+
}, options),
|
|
38
28
|
});
|
|
29
|
+
const zodSchema = (0, emitZod_js_1.emitZod)(analysis);
|
|
39
30
|
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 });
|
|
31
|
+
files.push({ fileName: target.fileName, contents: finalSchema });
|
|
59
32
|
}
|
|
60
33
|
// Nested types extraction (optional)
|
|
61
34
|
const nestedTypesEnabled = options.nestedTypes?.enable;
|
|
@@ -95,6 +68,20 @@ const buildDefInfoMap = (defNames, defs, options) => {
|
|
|
95
68
|
}
|
|
96
69
|
return map;
|
|
97
70
|
};
|
|
71
|
+
const buildBundleContext = (defNames, defs, options) => {
|
|
72
|
+
const defInfoMap = buildDefInfoMap(defNames, defs, options);
|
|
73
|
+
const cycles = detectCycles(defInfoMap);
|
|
74
|
+
for (const defName of cycles) {
|
|
75
|
+
const info = defInfoMap.get(defName);
|
|
76
|
+
if (info)
|
|
77
|
+
info.hasCycle = true;
|
|
78
|
+
}
|
|
79
|
+
const rootName = options.splitDefs?.rootName ?? options.name ?? "RootSchema";
|
|
80
|
+
const rootTypeName = typeof options.type === "string"
|
|
81
|
+
? options.type
|
|
82
|
+
: options.splitDefs?.rootTypeName ?? (typeof options.type === "boolean" && options.type ? rootName : undefined);
|
|
83
|
+
return { defInfoMap, rootName, rootTypeName };
|
|
84
|
+
};
|
|
98
85
|
const createRefHandler = (currentDefName, defInfoMap, usedRefs, allDefs, options) => {
|
|
99
86
|
return (schema, refs) => {
|
|
100
87
|
if (typeof schema["$ref"] === "string") {
|
|
@@ -102,6 +89,10 @@ const createRefHandler = (currentDefName, defInfoMap, usedRefs, allDefs, options
|
|
|
102
89
|
const match = refPath.match(/^#\/(?:\$defs|definitions)\/(.+)$/);
|
|
103
90
|
if (match) {
|
|
104
91
|
const refName = match[1];
|
|
92
|
+
// Only intercept top-level def refs (no nested path like a/$defs/x)
|
|
93
|
+
if (refName.includes("/")) {
|
|
94
|
+
return undefined;
|
|
95
|
+
}
|
|
105
96
|
const refInfo = defInfoMap.get(refName);
|
|
106
97
|
if (refInfo) {
|
|
107
98
|
// Track imports when referencing other defs
|
|
@@ -118,22 +109,22 @@ const createRefHandler = (currentDefName, defInfoMap, usedRefs, allDefs, options
|
|
|
118
109
|
});
|
|
119
110
|
if (resolved)
|
|
120
111
|
return resolved;
|
|
112
|
+
if (isCycle && options.refResolution?.lazyCrossRefs) {
|
|
113
|
+
return `z.lazy(() => ${refInfo.schemaName})`;
|
|
114
|
+
}
|
|
121
115
|
return refInfo.schemaName;
|
|
122
116
|
}
|
|
117
|
+
// If the ref points to a local/inline $def (not part of top-level defs),
|
|
118
|
+
// let the default parser resolve it normally.
|
|
119
|
+
if (allDefs && Object.prototype.hasOwnProperty.call(allDefs, refName)) {
|
|
120
|
+
return undefined;
|
|
121
|
+
}
|
|
123
122
|
}
|
|
124
123
|
const unknown = options.refResolution?.onUnknownRef?.({ ref: refPath, currentDef: currentDefName });
|
|
125
124
|
if (unknown)
|
|
126
125
|
return unknown;
|
|
127
126
|
return options.useUnknown ? "z.unknown()" : "z.any()";
|
|
128
127
|
}
|
|
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
128
|
return undefined;
|
|
138
129
|
};
|
|
139
130
|
};
|
|
@@ -151,6 +142,52 @@ const buildSchemaFile = (zodCode, usedRefs, defInfoMap, module) => {
|
|
|
151
142
|
return zodCode;
|
|
152
143
|
return zodCode.replace('import { z } from "zod"', `import { z } from "zod"\n${imports.join("\n")}`);
|
|
153
144
|
};
|
|
145
|
+
const planBundleTargets = (rootSchema, defs, definitions, defNames, options, rootName, rootTypeName) => {
|
|
146
|
+
const targets = [];
|
|
147
|
+
for (const defName of defNames) {
|
|
148
|
+
const defSchema = defs[defName];
|
|
149
|
+
const defSchemaWithDefs = {
|
|
150
|
+
...defSchema,
|
|
151
|
+
$defs: { ...defs, ...defSchema?.$defs },
|
|
152
|
+
definitions: {
|
|
153
|
+
...defSchema.definitions,
|
|
154
|
+
...definitions,
|
|
155
|
+
},
|
|
156
|
+
};
|
|
157
|
+
const pascalName = toPascalCase(defName);
|
|
158
|
+
const schemaName = options.splitDefs?.schemaName?.(defName, { isRoot: false }) ?? `${pascalName}Schema`;
|
|
159
|
+
const typeName = options.splitDefs?.typeName?.(defName, { isRoot: false }) ?? pascalName;
|
|
160
|
+
const fileName = options.splitDefs?.fileName?.(defName, { isRoot: false }) ?? `${defName}.schema.ts`;
|
|
161
|
+
targets.push({
|
|
162
|
+
defName,
|
|
163
|
+
schemaWithDefs: defSchemaWithDefs,
|
|
164
|
+
schemaName,
|
|
165
|
+
typeName,
|
|
166
|
+
fileName,
|
|
167
|
+
usedRefs: new Set(),
|
|
168
|
+
isRoot: false,
|
|
169
|
+
});
|
|
170
|
+
}
|
|
171
|
+
if (options.splitDefs?.includeRoot ?? true) {
|
|
172
|
+
const rootFile = options.splitDefs?.fileName?.("root", { isRoot: true }) ?? "workflow.schema.ts";
|
|
173
|
+
targets.push({
|
|
174
|
+
defName: null,
|
|
175
|
+
schemaWithDefs: {
|
|
176
|
+
...rootSchema,
|
|
177
|
+
definitions: {
|
|
178
|
+
...rootSchema.definitions,
|
|
179
|
+
...definitions,
|
|
180
|
+
},
|
|
181
|
+
},
|
|
182
|
+
schemaName: rootName,
|
|
183
|
+
typeName: rootTypeName,
|
|
184
|
+
fileName: rootFile,
|
|
185
|
+
usedRefs: new Set(),
|
|
186
|
+
isRoot: true,
|
|
187
|
+
});
|
|
188
|
+
}
|
|
189
|
+
return targets;
|
|
190
|
+
};
|
|
154
191
|
const findRefDependencies = (schema, validDefNames) => {
|
|
155
192
|
const deps = new Set();
|
|
156
193
|
function traverse(obj) {
|
|
@@ -239,7 +276,7 @@ const findNestedTypesInSchema = (schema, parentTypeName, defNames, currentPath =
|
|
|
239
276
|
if (schema === null || typeof schema !== "object")
|
|
240
277
|
return nestedTypes;
|
|
241
278
|
const record = schema;
|
|
242
|
-
if (record.title && typeof record.title === "string"
|
|
279
|
+
if (record.title && typeof record.title === "string") {
|
|
243
280
|
const title = record.title;
|
|
244
281
|
if (title !== parentTypeName && !defNames.map((d) => toPascalCase(d)).includes(title)) {
|
|
245
282
|
nestedTypes.push({
|
|
@@ -250,6 +287,12 @@ const findNestedTypesInSchema = (schema, parentTypeName, defNames, currentPath =
|
|
|
250
287
|
});
|
|
251
288
|
}
|
|
252
289
|
}
|
|
290
|
+
// inline $defs
|
|
291
|
+
if (record.$defs && typeof record.$defs === "object") {
|
|
292
|
+
for (const [_defName, defSchema] of Object.entries(record.$defs)) {
|
|
293
|
+
nestedTypes.push(...findNestedTypesInSchema(defSchema, parentTypeName, defNames, currentPath));
|
|
294
|
+
}
|
|
295
|
+
}
|
|
253
296
|
if (record.properties && typeof record.properties === "object") {
|
|
254
297
|
for (const [propName, propSchema] of Object.entries(record.properties)) {
|
|
255
298
|
nestedTypes.push(...findNestedTypesInSchema(propSchema, parentTypeName, defNames, [...currentPath, propName]));
|
|
@@ -261,10 +304,10 @@ const findNestedTypesInSchema = (schema, parentTypeName, defNames, currentPath =
|
|
|
261
304
|
}
|
|
262
305
|
}
|
|
263
306
|
if (record.items) {
|
|
264
|
-
nestedTypes.push(...findNestedTypesInSchema(record.items, parentTypeName, defNames, currentPath));
|
|
307
|
+
nestedTypes.push(...findNestedTypesInSchema(record.items, parentTypeName, defNames, [...currentPath, "items"]));
|
|
265
308
|
}
|
|
266
309
|
if (record.additionalProperties && typeof record.additionalProperties === "object") {
|
|
267
|
-
nestedTypes.push(...findNestedTypesInSchema(record.additionalProperties, parentTypeName, defNames, currentPath));
|
|
310
|
+
nestedTypes.push(...findNestedTypesInSchema(record.additionalProperties, parentTypeName, defNames, [...currentPath, "additionalProperties"]));
|
|
268
311
|
}
|
|
269
312
|
return nestedTypes;
|
|
270
313
|
};
|
|
@@ -285,12 +328,13 @@ const generateNestedTypesFile = (nestedTypes) => {
|
|
|
285
328
|
}
|
|
286
329
|
byParent.get(info.parentType).push(info);
|
|
287
330
|
}
|
|
288
|
-
const imports = new
|
|
331
|
+
const imports = new Map(); // file -> type name
|
|
289
332
|
for (const info of nestedTypes) {
|
|
290
|
-
imports.
|
|
333
|
+
if (!imports.has(info.file)) {
|
|
334
|
+
imports.set(info.file, info.parentType);
|
|
335
|
+
}
|
|
291
336
|
}
|
|
292
|
-
for (const file of [...imports].sort()) {
|
|
293
|
-
const typeName = file === "workflow" ? "Workflow" : toPascalCase(file);
|
|
337
|
+
for (const [file, typeName] of [...imports.entries()].sort()) {
|
|
294
338
|
lines.push(`import type { ${typeName} } from './${file}.schema.js';`);
|
|
295
339
|
}
|
|
296
340
|
lines.push("");
|
package/dist/cjs/index.js
CHANGED
|
@@ -15,6 +15,8 @@ var __exportStar = (this && this.__exportStar) || function(m, exports) {
|
|
|
15
15
|
};
|
|
16
16
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
17
17
|
__exportStar(require("./Types.js"), exports);
|
|
18
|
+
__exportStar(require("./core/analyzeSchema.js"), exports);
|
|
19
|
+
__exportStar(require("./core/emitZod.js"), exports);
|
|
18
20
|
__exportStar(require("./generators/generateBundle.js"), exports);
|
|
19
21
|
__exportStar(require("./jsonSchemaToZod.js"), exports);
|
|
20
22
|
__exportStar(require("./parsers/parseAllOf.js"), exports);
|
|
@@ -36,10 +38,12 @@ __exportStar(require("./parsers/parseSchema.js"), exports);
|
|
|
36
38
|
__exportStar(require("./parsers/parseSimpleDiscriminatedOneOf.js"), exports);
|
|
37
39
|
__exportStar(require("./parsers/parseString.js"), exports);
|
|
38
40
|
__exportStar(require("./utils/anyOrUnknown.js"), exports);
|
|
41
|
+
__exportStar(require("./utils/buildRefRegistry.js"), exports);
|
|
39
42
|
__exportStar(require("./utils/cycles.js"), exports);
|
|
40
43
|
__exportStar(require("./utils/half.js"), exports);
|
|
41
44
|
__exportStar(require("./utils/jsdocs.js"), exports);
|
|
42
45
|
__exportStar(require("./utils/omit.js"), exports);
|
|
46
|
+
__exportStar(require("./utils/resolveUri.js"), exports);
|
|
43
47
|
__exportStar(require("./utils/withMessage.js"), exports);
|
|
44
48
|
__exportStar(require("./zodToJsonSchema.js"), exports);
|
|
45
49
|
const jsonSchemaToZod_js_1 = require("./jsonSchemaToZod.js");
|
|
@@ -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
|
-
}
|