@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
package/dist/esm/cli.js
CHANGED
|
@@ -1,10 +1,8 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
const path_1 = require("path");
|
|
7
|
-
const cliTools_js_1 = require("./utils/cliTools.js");
|
|
2
|
+
import { jsonSchemaToZod } from "./jsonSchemaToZod.js";
|
|
3
|
+
import { writeFileSync, mkdirSync } from "fs";
|
|
4
|
+
import { dirname } from "path";
|
|
5
|
+
import { parseArgs, parseOrReadJSON, readPipe } from "./utils/cliTools.js";
|
|
8
6
|
const params = {
|
|
9
7
|
input: {
|
|
10
8
|
shorthand: "i",
|
|
@@ -48,10 +46,10 @@ const params = {
|
|
|
48
46
|
},
|
|
49
47
|
};
|
|
50
48
|
async function main() {
|
|
51
|
-
const args =
|
|
52
|
-
const input = args.input || (await
|
|
53
|
-
const jsonSchema =
|
|
54
|
-
const zodSchema =
|
|
49
|
+
const args = parseArgs(params, process.argv, true);
|
|
50
|
+
const input = args.input || (await readPipe());
|
|
51
|
+
const jsonSchema = parseOrReadJSON(input);
|
|
52
|
+
const zodSchema = jsonSchemaToZod(jsonSchema, {
|
|
55
53
|
name: args.name,
|
|
56
54
|
depth: args.depth,
|
|
57
55
|
module: args.module || "esm",
|
|
@@ -60,8 +58,8 @@ async function main() {
|
|
|
60
58
|
withJsdocs: args.withJsdocs,
|
|
61
59
|
});
|
|
62
60
|
if (args.output) {
|
|
63
|
-
|
|
64
|
-
|
|
61
|
+
mkdirSync(dirname(args.output), { recursive: true });
|
|
62
|
+
writeFileSync(args.output, zodSchema);
|
|
65
63
|
}
|
|
66
64
|
else {
|
|
67
65
|
console.log(zodSchema);
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
import { parseSchema } from "../parsers/parseSchema.js";
|
|
2
|
+
import { detectCycles, computeScc } from "../utils/cycles.js";
|
|
3
|
+
import { buildRefRegistry } from "../utils/buildRefRegistry.js";
|
|
4
|
+
export const analyzeSchema = (schema, options = {}) => {
|
|
5
|
+
const { module, name, type, ...rest } = options;
|
|
6
|
+
if (type && (!name || module !== "esm")) {
|
|
7
|
+
throw new Error("Option `type` requires `name` to be set and `module` to be `esm`");
|
|
8
|
+
}
|
|
9
|
+
const normalized = {
|
|
10
|
+
module,
|
|
11
|
+
name,
|
|
12
|
+
type,
|
|
13
|
+
...rest,
|
|
14
|
+
exportRefs: rest.exportRefs ?? true,
|
|
15
|
+
withMeta: rest.withMeta ?? true,
|
|
16
|
+
};
|
|
17
|
+
const refNameByPointer = new Map();
|
|
18
|
+
const usedNames = new Set();
|
|
19
|
+
if (name) {
|
|
20
|
+
usedNames.add(name);
|
|
21
|
+
}
|
|
22
|
+
const declarations = new Map();
|
|
23
|
+
const dependencies = new Map();
|
|
24
|
+
const { registry: refRegistry, rootBaseUri } = buildRefRegistry(schema);
|
|
25
|
+
const pass1 = {
|
|
26
|
+
module,
|
|
27
|
+
name,
|
|
28
|
+
path: [],
|
|
29
|
+
seen: new Map(),
|
|
30
|
+
declarations,
|
|
31
|
+
dependencies,
|
|
32
|
+
inProgress: new Set(),
|
|
33
|
+
refNameByPointer,
|
|
34
|
+
usedNames,
|
|
35
|
+
root: schema,
|
|
36
|
+
currentSchemaName: name,
|
|
37
|
+
refRegistry,
|
|
38
|
+
rootBaseUri,
|
|
39
|
+
...rest,
|
|
40
|
+
withMeta: normalized.withMeta,
|
|
41
|
+
};
|
|
42
|
+
parseSchema(schema, pass1);
|
|
43
|
+
const names = Array.from(declarations.keys());
|
|
44
|
+
const cycleRefNames = detectCycles(names, dependencies);
|
|
45
|
+
const { componentByName } = computeScc(names, dependencies);
|
|
46
|
+
return {
|
|
47
|
+
schema,
|
|
48
|
+
options: normalized,
|
|
49
|
+
refNameByPointer,
|
|
50
|
+
usedNames,
|
|
51
|
+
declarations,
|
|
52
|
+
dependencies,
|
|
53
|
+
cycleRefNames,
|
|
54
|
+
cycleComponentByName: componentByName,
|
|
55
|
+
refRegistry,
|
|
56
|
+
rootBaseUri,
|
|
57
|
+
};
|
|
58
|
+
};
|
|
@@ -0,0 +1,137 @@
|
|
|
1
|
+
import { parseSchema } from "../parsers/parseSchema.js";
|
|
2
|
+
import { expandJsdocs } from "../utils/jsdocs.js";
|
|
3
|
+
const orderDeclarations = (entries, dependencies) => {
|
|
4
|
+
const valueByName = new Map(entries);
|
|
5
|
+
const depGraph = new Map();
|
|
6
|
+
for (const [from, set] of dependencies.entries()) {
|
|
7
|
+
const onlyKnown = new Set();
|
|
8
|
+
for (const dep of set) {
|
|
9
|
+
if (valueByName.has(dep) && dep !== from) {
|
|
10
|
+
onlyKnown.add(dep);
|
|
11
|
+
}
|
|
12
|
+
}
|
|
13
|
+
if (onlyKnown.size)
|
|
14
|
+
depGraph.set(from, onlyKnown);
|
|
15
|
+
}
|
|
16
|
+
const names = Array.from(valueByName.keys());
|
|
17
|
+
for (const [name, value] of entries) {
|
|
18
|
+
const deps = depGraph.get(name) ?? new Set();
|
|
19
|
+
for (const candidate of names) {
|
|
20
|
+
if (candidate === name)
|
|
21
|
+
continue;
|
|
22
|
+
const matcher = new RegExp(`\\b${candidate}\\b`);
|
|
23
|
+
if (matcher.test(value)) {
|
|
24
|
+
deps.add(candidate);
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
if (deps.size)
|
|
28
|
+
depGraph.set(name, deps);
|
|
29
|
+
}
|
|
30
|
+
const ordered = [];
|
|
31
|
+
const perm = new Set();
|
|
32
|
+
const temp = new Set();
|
|
33
|
+
const visit = (name) => {
|
|
34
|
+
if (perm.has(name))
|
|
35
|
+
return;
|
|
36
|
+
if (temp.has(name)) {
|
|
37
|
+
temp.delete(name);
|
|
38
|
+
perm.add(name);
|
|
39
|
+
ordered.push(name);
|
|
40
|
+
return;
|
|
41
|
+
}
|
|
42
|
+
temp.add(name);
|
|
43
|
+
const deps = depGraph.get(name);
|
|
44
|
+
if (deps) {
|
|
45
|
+
for (const dep of deps) {
|
|
46
|
+
if (valueByName.has(dep)) {
|
|
47
|
+
visit(dep);
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
temp.delete(name);
|
|
52
|
+
perm.add(name);
|
|
53
|
+
ordered.push(name);
|
|
54
|
+
};
|
|
55
|
+
for (const name of valueByName.keys()) {
|
|
56
|
+
visit(name);
|
|
57
|
+
}
|
|
58
|
+
const unique = [];
|
|
59
|
+
const seen = new Set();
|
|
60
|
+
for (const name of ordered) {
|
|
61
|
+
if (!seen.has(name)) {
|
|
62
|
+
seen.add(name);
|
|
63
|
+
unique.push(name);
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
return unique.map((name) => [name, valueByName.get(name)]);
|
|
67
|
+
};
|
|
68
|
+
export const emitZod = (analysis) => {
|
|
69
|
+
const { schema, options, refNameByPointer, usedNames, cycleRefNames, cycleComponentByName, } = analysis;
|
|
70
|
+
const { module, name, type, noImport, exportRefs, withMeta, ...rest } = options;
|
|
71
|
+
const declarations = new Map();
|
|
72
|
+
const dependencies = new Map();
|
|
73
|
+
const parsedSchema = parseSchema(schema, {
|
|
74
|
+
module,
|
|
75
|
+
name,
|
|
76
|
+
path: [],
|
|
77
|
+
seen: new Map(),
|
|
78
|
+
declarations,
|
|
79
|
+
dependencies,
|
|
80
|
+
inProgress: new Set(),
|
|
81
|
+
refNameByPointer,
|
|
82
|
+
usedNames,
|
|
83
|
+
root: schema,
|
|
84
|
+
currentSchemaName: name,
|
|
85
|
+
cycleRefNames,
|
|
86
|
+
cycleComponentByName,
|
|
87
|
+
refRegistry: analysis.refRegistry,
|
|
88
|
+
rootBaseUri: analysis.rootBaseUri,
|
|
89
|
+
...rest,
|
|
90
|
+
withMeta,
|
|
91
|
+
});
|
|
92
|
+
const declarationBlock = declarations.size
|
|
93
|
+
? orderDeclarations(Array.from(declarations.entries()), dependencies)
|
|
94
|
+
.map(([refName, value]) => {
|
|
95
|
+
const shouldExport = exportRefs && module === "esm";
|
|
96
|
+
const decl = `${shouldExport ? "export " : ""}const ${refName} = ${value}`;
|
|
97
|
+
return decl;
|
|
98
|
+
})
|
|
99
|
+
.join("\n")
|
|
100
|
+
: "";
|
|
101
|
+
const jsdocs = rest.withJsdocs && typeof schema === "object" && schema !== null && "description" in schema
|
|
102
|
+
? expandJsdocs(String(schema.description ?? ""))
|
|
103
|
+
: "";
|
|
104
|
+
const lines = [];
|
|
105
|
+
if (module === "cjs" && !noImport) {
|
|
106
|
+
lines.push(`const { z } = require("zod")`);
|
|
107
|
+
}
|
|
108
|
+
if (module === "esm" && !noImport) {
|
|
109
|
+
lines.push(`import { z } from "zod"`);
|
|
110
|
+
}
|
|
111
|
+
if (declarationBlock) {
|
|
112
|
+
lines.push(declarationBlock);
|
|
113
|
+
}
|
|
114
|
+
if (module === "cjs") {
|
|
115
|
+
const payload = name ? `{ ${JSON.stringify(name)}: ${parsedSchema} }` : parsedSchema;
|
|
116
|
+
lines.push(`${jsdocs}module.exports = ${payload}`);
|
|
117
|
+
}
|
|
118
|
+
else if (module === "esm") {
|
|
119
|
+
const exportLine = `${jsdocs}export ${name ? `const ${name} =` : `default`} ${parsedSchema}`;
|
|
120
|
+
lines.push(exportLine);
|
|
121
|
+
}
|
|
122
|
+
else if (name) {
|
|
123
|
+
lines.push(`${jsdocs}const ${name} = ${parsedSchema}`);
|
|
124
|
+
}
|
|
125
|
+
else {
|
|
126
|
+
lines.push(`${jsdocs}${parsedSchema}`);
|
|
127
|
+
}
|
|
128
|
+
let typeLine;
|
|
129
|
+
if (type && name) {
|
|
130
|
+
const typeName = typeof type === "string" ? type : `${name[0].toUpperCase()}${name.substring(1)}`;
|
|
131
|
+
typeLine = `export type ${typeName} = z.infer<typeof ${name}>`;
|
|
132
|
+
}
|
|
133
|
+
const joined = lines.filter(Boolean).join("\n\n");
|
|
134
|
+
const combined = typeLine ? `${joined}\n${typeLine}` : joined;
|
|
135
|
+
const shouldEndWithNewline = module === "esm" || module === "cjs";
|
|
136
|
+
return `${combined}${shouldEndWithNewline ? "\n" : ""}`;
|
|
137
|
+
};
|
|
@@ -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,13 +64,32 @@ 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) => {
|
|
82
|
+
const useLazyCrossRefs = options.refResolution?.lazyCrossRefs ?? true;
|
|
99
83
|
return (schema, refs) => {
|
|
100
84
|
if (typeof schema["$ref"] === "string") {
|
|
101
85
|
const refPath = schema["$ref"];
|
|
102
86
|
const match = refPath.match(/^#\/(?:\$defs|definitions)\/(.+)$/);
|
|
103
87
|
if (match) {
|
|
104
88
|
const refName = match[1];
|
|
89
|
+
// Only intercept top-level def refs (no nested path like a/$defs/x)
|
|
90
|
+
if (refName.includes("/")) {
|
|
91
|
+
return undefined;
|
|
92
|
+
}
|
|
105
93
|
const refInfo = defInfoMap.get(refName);
|
|
106
94
|
if (refInfo) {
|
|
107
95
|
// Track imports when referencing other defs
|
|
@@ -118,22 +106,22 @@ const createRefHandler = (currentDefName, defInfoMap, usedRefs, allDefs, options
|
|
|
118
106
|
});
|
|
119
107
|
if (resolved)
|
|
120
108
|
return resolved;
|
|
109
|
+
if (isCycle && useLazyCrossRefs) {
|
|
110
|
+
return `z.lazy(() => ${refInfo.schemaName})`;
|
|
111
|
+
}
|
|
121
112
|
return refInfo.schemaName;
|
|
122
113
|
}
|
|
114
|
+
// If the ref points to a local/inline $def (not part of top-level defs),
|
|
115
|
+
// let the default parser resolve it normally.
|
|
116
|
+
if (allDefs && Object.prototype.hasOwnProperty.call(allDefs, refName)) {
|
|
117
|
+
return undefined;
|
|
118
|
+
}
|
|
123
119
|
}
|
|
124
120
|
const unknown = options.refResolution?.onUnknownRef?.({ ref: refPath, currentDef: currentDefName });
|
|
125
121
|
if (unknown)
|
|
126
122
|
return unknown;
|
|
127
123
|
return options.useUnknown ? "z.unknown()" : "z.any()";
|
|
128
124
|
}
|
|
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
125
|
return undefined;
|
|
138
126
|
};
|
|
139
127
|
};
|
|
@@ -151,6 +139,52 @@ const buildSchemaFile = (zodCode, usedRefs, defInfoMap, module) => {
|
|
|
151
139
|
return zodCode;
|
|
152
140
|
return zodCode.replace('import { z } from "zod"', `import { z } from "zod"\n${imports.join("\n")}`);
|
|
153
141
|
};
|
|
142
|
+
const planBundleTargets = (rootSchema, defs, definitions, defNames, options, rootName, rootTypeName) => {
|
|
143
|
+
const targets = [];
|
|
144
|
+
for (const defName of defNames) {
|
|
145
|
+
const defSchema = defs[defName];
|
|
146
|
+
const defSchemaWithDefs = {
|
|
147
|
+
...defSchema,
|
|
148
|
+
$defs: { ...defs, ...defSchema?.$defs },
|
|
149
|
+
definitions: {
|
|
150
|
+
...defSchema.definitions,
|
|
151
|
+
...definitions,
|
|
152
|
+
},
|
|
153
|
+
};
|
|
154
|
+
const pascalName = toPascalCase(defName);
|
|
155
|
+
const schemaName = options.splitDefs?.schemaName?.(defName, { isRoot: false }) ?? `${pascalName}Schema`;
|
|
156
|
+
const typeName = options.splitDefs?.typeName?.(defName, { isRoot: false }) ?? pascalName;
|
|
157
|
+
const fileName = options.splitDefs?.fileName?.(defName, { isRoot: false }) ?? `${defName}.schema.ts`;
|
|
158
|
+
targets.push({
|
|
159
|
+
defName,
|
|
160
|
+
schemaWithDefs: defSchemaWithDefs,
|
|
161
|
+
schemaName,
|
|
162
|
+
typeName,
|
|
163
|
+
fileName,
|
|
164
|
+
usedRefs: new Set(),
|
|
165
|
+
isRoot: false,
|
|
166
|
+
});
|
|
167
|
+
}
|
|
168
|
+
if (options.splitDefs?.includeRoot ?? true) {
|
|
169
|
+
const rootFile = options.splitDefs?.fileName?.("root", { isRoot: true }) ?? "workflow.schema.ts";
|
|
170
|
+
targets.push({
|
|
171
|
+
defName: null,
|
|
172
|
+
schemaWithDefs: {
|
|
173
|
+
...rootSchema,
|
|
174
|
+
definitions: {
|
|
175
|
+
...rootSchema.definitions,
|
|
176
|
+
...definitions,
|
|
177
|
+
},
|
|
178
|
+
},
|
|
179
|
+
schemaName: rootName,
|
|
180
|
+
typeName: rootTypeName,
|
|
181
|
+
fileName: rootFile,
|
|
182
|
+
usedRefs: new Set(),
|
|
183
|
+
isRoot: true,
|
|
184
|
+
});
|
|
185
|
+
}
|
|
186
|
+
return targets;
|
|
187
|
+
};
|
|
154
188
|
const findRefDependencies = (schema, validDefNames) => {
|
|
155
189
|
const deps = new Set();
|
|
156
190
|
function traverse(obj) {
|
|
@@ -239,7 +273,7 @@ const findNestedTypesInSchema = (schema, parentTypeName, defNames, currentPath =
|
|
|
239
273
|
if (schema === null || typeof schema !== "object")
|
|
240
274
|
return nestedTypes;
|
|
241
275
|
const record = schema;
|
|
242
|
-
if (record.title && typeof record.title === "string"
|
|
276
|
+
if (record.title && typeof record.title === "string") {
|
|
243
277
|
const title = record.title;
|
|
244
278
|
if (title !== parentTypeName && !defNames.map((d) => toPascalCase(d)).includes(title)) {
|
|
245
279
|
nestedTypes.push({
|
|
@@ -250,6 +284,12 @@ const findNestedTypesInSchema = (schema, parentTypeName, defNames, currentPath =
|
|
|
250
284
|
});
|
|
251
285
|
}
|
|
252
286
|
}
|
|
287
|
+
// inline $defs
|
|
288
|
+
if (record.$defs && typeof record.$defs === "object") {
|
|
289
|
+
for (const [, defSchema] of Object.entries(record.$defs)) {
|
|
290
|
+
nestedTypes.push(...findNestedTypesInSchema(defSchema, parentTypeName, defNames, currentPath));
|
|
291
|
+
}
|
|
292
|
+
}
|
|
253
293
|
if (record.properties && typeof record.properties === "object") {
|
|
254
294
|
for (const [propName, propSchema] of Object.entries(record.properties)) {
|
|
255
295
|
nestedTypes.push(...findNestedTypesInSchema(propSchema, parentTypeName, defNames, [...currentPath, propName]));
|
|
@@ -261,10 +301,10 @@ const findNestedTypesInSchema = (schema, parentTypeName, defNames, currentPath =
|
|
|
261
301
|
}
|
|
262
302
|
}
|
|
263
303
|
if (record.items) {
|
|
264
|
-
nestedTypes.push(...findNestedTypesInSchema(record.items, parentTypeName, defNames, currentPath));
|
|
304
|
+
nestedTypes.push(...findNestedTypesInSchema(record.items, parentTypeName, defNames, [...currentPath, "items"]));
|
|
265
305
|
}
|
|
266
306
|
if (record.additionalProperties && typeof record.additionalProperties === "object") {
|
|
267
|
-
nestedTypes.push(...findNestedTypesInSchema(record.additionalProperties, parentTypeName, defNames, currentPath));
|
|
307
|
+
nestedTypes.push(...findNestedTypesInSchema(record.additionalProperties, parentTypeName, defNames, [...currentPath, "additionalProperties"]));
|
|
268
308
|
}
|
|
269
309
|
return nestedTypes;
|
|
270
310
|
};
|
|
@@ -285,23 +325,33 @@ const generateNestedTypesFile = (nestedTypes) => {
|
|
|
285
325
|
}
|
|
286
326
|
byParent.get(info.parentType).push(info);
|
|
287
327
|
}
|
|
288
|
-
const imports = new
|
|
328
|
+
const imports = new Map(); // file -> type name
|
|
289
329
|
for (const info of nestedTypes) {
|
|
290
|
-
imports.
|
|
330
|
+
if (!imports.has(info.file)) {
|
|
331
|
+
imports.set(info.file, info.parentType);
|
|
332
|
+
}
|
|
291
333
|
}
|
|
292
|
-
for (const file of [...imports].sort()) {
|
|
293
|
-
const typeName = file === "workflow" ? "Workflow" : toPascalCase(file);
|
|
334
|
+
for (const [file, typeName] of [...imports.entries()].sort()) {
|
|
294
335
|
lines.push(`import type { ${typeName} } from './${file}.schema.js';`);
|
|
295
336
|
}
|
|
296
337
|
lines.push("");
|
|
338
|
+
const buildAccessExpr = (parentType, propertyPath) => {
|
|
339
|
+
let accessExpr = parentType;
|
|
340
|
+
for (const prop of propertyPath) {
|
|
341
|
+
const accessor = prop === "items"
|
|
342
|
+
? "[number]"
|
|
343
|
+
: typeof prop === "number"
|
|
344
|
+
? `[${prop}]`
|
|
345
|
+
: `[${JSON.stringify(prop)}]`;
|
|
346
|
+
accessExpr = `NonNullable<${accessExpr}${accessor}>`;
|
|
347
|
+
}
|
|
348
|
+
return accessExpr;
|
|
349
|
+
};
|
|
297
350
|
for (const [parentType, types] of [...byParent.entries()].sort()) {
|
|
298
351
|
lines.push(`// From ${parentType}`);
|
|
299
352
|
for (const info of types.sort((a, b) => a.typeName.localeCompare(b.typeName))) {
|
|
300
353
|
if (info.propertyPath.length > 0) {
|
|
301
|
-
|
|
302
|
-
for (const prop of info.propertyPath) {
|
|
303
|
-
accessExpr = `NonNullable<${accessExpr}['${prop}']>`;
|
|
304
|
-
}
|
|
354
|
+
const accessExpr = buildAccessExpr(parentType, info.propertyPath);
|
|
305
355
|
lines.push(`export type ${info.typeName} = ${accessExpr};`);
|
|
306
356
|
}
|
|
307
357
|
}
|
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;
|