@gabrielbryk/json-schema-to-zod 2.12.1 → 2.14.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/.github/RELEASE_SETUP.md +120 -0
- package/.github/TOOLING_GUIDE.md +169 -0
- package/.github/dependabot.yml +52 -0
- package/.github/workflows/ci.yml +33 -0
- package/.github/workflows/release.yml +12 -4
- package/.github/workflows/security.yml +40 -0
- package/.husky/commit-msg +1 -0
- package/.husky/pre-commit +1 -0
- package/.lintstagedrc.json +3 -0
- package/.prettierrc +20 -0
- package/AGENTS.md +7 -0
- package/CHANGELOG.md +13 -4
- package/README.md +24 -9
- package/commitlint.config.js +24 -0
- package/createIndex.ts +4 -4
- package/dist/cli.js +3 -4
- package/dist/core/analyzeSchema.js +56 -11
- package/dist/core/emitZod.js +43 -12
- package/dist/generators/generateBundle.js +67 -92
- package/dist/index.js +1 -0
- package/dist/parsers/parseAllOf.js +11 -12
- package/dist/parsers/parseAnyOf.js +2 -2
- package/dist/parsers/parseArray.js +38 -12
- package/dist/parsers/parseMultipleType.js +2 -2
- package/dist/parsers/parseNumber.js +44 -102
- package/dist/parsers/parseObject.js +136 -443
- package/dist/parsers/parseOneOf.js +57 -110
- package/dist/parsers/parseSchema.js +176 -71
- package/dist/parsers/parseSimpleDiscriminatedOneOf.js +2 -2
- package/dist/parsers/parseString.js +113 -253
- package/dist/types/Types.d.ts +37 -1
- package/dist/types/core/analyzeSchema.d.ts +4 -0
- package/dist/types/generators/generateBundle.d.ts +1 -1
- package/dist/types/index.d.ts +1 -0
- package/dist/types/utils/schemaNaming.d.ts +6 -0
- package/dist/utils/cliTools.js +1 -2
- package/dist/utils/esmEmitter.js +6 -2
- package/dist/utils/extractInlineObject.js +1 -3
- package/dist/utils/jsdocs.js +1 -4
- package/dist/utils/liftInlineObjects.js +76 -15
- package/dist/utils/resolveRef.js +35 -10
- package/dist/utils/schemaNaming.js +31 -0
- package/dist/utils/schemaRepresentation.js +35 -66
- package/dist/zodToJsonSchema.js +1 -2
- package/docs/IMPROVEMENT-PLAN.md +30 -12
- package/docs/ZOD-V4-RECURSIVE-TYPE-LIMITATIONS.md +70 -25
- package/docs/proposals/allof-required-merging.md +10 -4
- package/docs/proposals/bundle-refactor.md +10 -4
- package/docs/proposals/discriminated-union-with-default.md +18 -14
- package/docs/proposals/inline-object-lifting.md +15 -5
- package/docs/proposals/ref-anchor-support.md +11 -0
- package/output.txt +67 -0
- package/package.json +18 -5
- package/scripts/generateWorkflowSchema.ts +5 -14
- package/scripts/regenerate_bundle.ts +25 -0
- package/tsc_output.txt +542 -0
- package/tsc_output_2.txt +489 -0
|
@@ -3,246 +3,114 @@ import { parseSchema } from "./parseSchema.js";
|
|
|
3
3
|
export const parseString = (schema, refs) => {
|
|
4
4
|
const formatError = schema.errorMessage?.format;
|
|
5
5
|
const refContext = ensureRefs(refs);
|
|
6
|
-
// Map formats to
|
|
6
|
+
// Map formats to Zod string methods
|
|
7
7
|
const topLevelFormatMap = {
|
|
8
|
-
email: { fn: "z.email", zodType: "z.
|
|
9
|
-
ipv4: { fn: "z.ipv4", zodType: "z.
|
|
10
|
-
ipv6: { fn: "z.ipv6", zodType: "z.
|
|
11
|
-
uri: { fn: "z.url", zodType: "z.
|
|
12
|
-
uuid: { fn: "z.uuid", zodType: "z.
|
|
13
|
-
cuid: { fn: "z.cuid", zodType: "z.
|
|
14
|
-
cuid2: { fn: "z.cuid2", zodType: "z.
|
|
15
|
-
nanoid: { fn: "z.nanoid", zodType: "z.
|
|
16
|
-
ulid: { fn: "z.ulid", zodType: "z.
|
|
17
|
-
jwt: { fn: "z.jwt", zodType: "z.
|
|
18
|
-
e164: { fn: "z.e164", zodType: "z.
|
|
19
|
-
base64url: { fn: "z.base64url", zodType: "z.
|
|
20
|
-
base64: { fn: "z.base64", zodType: "z.
|
|
21
|
-
emoji: { fn: "z.emoji", zodType: "z.
|
|
22
|
-
"idn-email": { fn: "z.email", zodType: "z.
|
|
8
|
+
email: { fn: "z.email", zodType: "z.ZodString" },
|
|
9
|
+
ipv4: { fn: "z.ipv4", zodType: "z.ZodString" },
|
|
10
|
+
ipv6: { fn: "z.ipv6", zodType: "z.ZodString" },
|
|
11
|
+
uri: { fn: "z.url", zodType: "z.ZodString" },
|
|
12
|
+
uuid: { fn: "z.uuid", zodType: "z.ZodString" },
|
|
13
|
+
cuid: { fn: "z.cuid", zodType: "z.ZodString" },
|
|
14
|
+
cuid2: { fn: "z.cuid2", zodType: "z.ZodString" },
|
|
15
|
+
nanoid: { fn: "z.nanoid", zodType: "z.ZodString" },
|
|
16
|
+
ulid: { fn: "z.ulid", zodType: "z.ZodString" },
|
|
17
|
+
jwt: { fn: "z.jwt", zodType: "z.ZodString" },
|
|
18
|
+
e164: { fn: "z.e164", zodType: "z.ZodString" },
|
|
19
|
+
base64url: { fn: "z.base64url", zodType: "z.ZodString" },
|
|
20
|
+
base64: { fn: "z.base64", zodType: "z.ZodString" },
|
|
21
|
+
emoji: { fn: "z.emoji", zodType: "z.ZodString" },
|
|
22
|
+
"idn-email": { fn: "z.email", zodType: "z.ZodString" },
|
|
23
|
+
"date-time": { fn: "z.iso.datetime", zodType: "z.ZodString" },
|
|
24
|
+
date: { fn: "z.iso.date", zodType: "z.ZodString" },
|
|
25
|
+
time: { fn: "z.iso.time", zodType: "z.ZodString" },
|
|
26
|
+
duration: { fn: "z.iso.duration", zodType: "z.ZodString" },
|
|
23
27
|
};
|
|
24
28
|
const formatInfo = schema.format ? topLevelFormatMap[schema.format] : undefined;
|
|
25
29
|
const formatFn = formatInfo?.fn;
|
|
26
|
-
|
|
27
|
-
let
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
messagePrefix: "{ error: ",
|
|
111
|
-
messageCloser: " })",
|
|
112
|
-
};
|
|
113
|
-
case "jwt":
|
|
114
|
-
formatWasHandled = true;
|
|
115
|
-
return {
|
|
116
|
-
opener: ".jwt(",
|
|
117
|
-
closer: ")",
|
|
118
|
-
messagePrefix: "{ error: ",
|
|
119
|
-
messageCloser: " })",
|
|
120
|
-
};
|
|
121
|
-
case "e164":
|
|
122
|
-
formatWasHandled = true;
|
|
123
|
-
return {
|
|
124
|
-
opener: ".e164(",
|
|
125
|
-
closer: ")",
|
|
126
|
-
messagePrefix: "{ error: ",
|
|
127
|
-
messageCloser: " })",
|
|
128
|
-
};
|
|
129
|
-
case "base64url":
|
|
130
|
-
formatWasHandled = true;
|
|
131
|
-
return {
|
|
132
|
-
opener: ".base64url(",
|
|
133
|
-
closer: ")",
|
|
134
|
-
messagePrefix: "{ error: ",
|
|
135
|
-
messageCloser: " })",
|
|
136
|
-
};
|
|
137
|
-
case "emoji":
|
|
138
|
-
formatWasHandled = true;
|
|
139
|
-
return {
|
|
140
|
-
opener: ".emoji(",
|
|
141
|
-
closer: ")",
|
|
142
|
-
messagePrefix: "{ error: ",
|
|
143
|
-
messageCloser: " })",
|
|
144
|
-
};
|
|
145
|
-
case "date-time":
|
|
146
|
-
formatWasHandled = true;
|
|
147
|
-
return {
|
|
148
|
-
opener: ".datetime({ offset: true",
|
|
149
|
-
closer: " })",
|
|
150
|
-
messagePrefix: ", error: ",
|
|
151
|
-
messageCloser: " })",
|
|
152
|
-
};
|
|
153
|
-
case "time":
|
|
154
|
-
formatWasHandled = true;
|
|
155
|
-
return {
|
|
156
|
-
opener: ".time(",
|
|
157
|
-
closer: ")",
|
|
158
|
-
messagePrefix: "{ error: ",
|
|
159
|
-
messageCloser: " })",
|
|
160
|
-
};
|
|
161
|
-
case "date":
|
|
162
|
-
formatWasHandled = true;
|
|
163
|
-
return {
|
|
164
|
-
opener: ".date(",
|
|
165
|
-
closer: ")",
|
|
166
|
-
messagePrefix: "{ error: ",
|
|
167
|
-
messageCloser: " })",
|
|
168
|
-
};
|
|
169
|
-
case "binary":
|
|
170
|
-
formatWasHandled = true;
|
|
171
|
-
return {
|
|
172
|
-
opener: ".base64(",
|
|
173
|
-
closer: ")",
|
|
174
|
-
messagePrefix: "{ error: ",
|
|
175
|
-
messageCloser: " })",
|
|
176
|
-
};
|
|
177
|
-
case "duration":
|
|
178
|
-
formatWasHandled = true;
|
|
179
|
-
return {
|
|
180
|
-
opener: ".duration(",
|
|
181
|
-
closer: ")",
|
|
182
|
-
messagePrefix: "{ error: ",
|
|
183
|
-
messageCloser: " })",
|
|
184
|
-
};
|
|
185
|
-
case "hostname":
|
|
186
|
-
case "idn-hostname":
|
|
187
|
-
formatWasHandled = true;
|
|
188
|
-
return {
|
|
189
|
-
opener: ".refine((val) => { if (typeof val !== \"string\" || val.length === 0 || val.length > 253) return false; return val.split(\".\").every((label) => label.length > 0 && label.length <= 63 && /^[A-Za-z0-9-]+$/.test(label) && label[0] !== \"-\" && label[label.length - 1] !== \"-\"); }",
|
|
190
|
-
closer: ")",
|
|
191
|
-
messagePrefix: ", { error: ",
|
|
192
|
-
messageCloser: " })",
|
|
193
|
-
};
|
|
194
|
-
case "idn-email":
|
|
195
|
-
formatWasHandled = true;
|
|
196
|
-
return {
|
|
197
|
-
opener: ".email(",
|
|
198
|
-
closer: ")",
|
|
199
|
-
messagePrefix: "{ error: ",
|
|
200
|
-
messageCloser: " })",
|
|
201
|
-
};
|
|
202
|
-
case "uri-reference":
|
|
203
|
-
case "iri":
|
|
204
|
-
case "iri-reference":
|
|
205
|
-
formatWasHandled = true;
|
|
206
|
-
return {
|
|
207
|
-
opener: '.refine((val) => { try { new URL(val, "http://example.com"); return true; } catch { return false; } }',
|
|
208
|
-
closer: ")",
|
|
209
|
-
messagePrefix: ", { error: ",
|
|
210
|
-
messageCloser: " })",
|
|
211
|
-
};
|
|
212
|
-
case "json-pointer":
|
|
213
|
-
formatWasHandled = true;
|
|
214
|
-
return {
|
|
215
|
-
opener: ".refine((val) => typeof val === \"string\" && /^(?:\\/(?:[^/~]|~[01])*)*$/.test(val)",
|
|
216
|
-
closer: ")",
|
|
217
|
-
messagePrefix: ", { error: ",
|
|
218
|
-
messageCloser: " })",
|
|
219
|
-
};
|
|
220
|
-
case "relative-json-pointer":
|
|
221
|
-
formatWasHandled = true;
|
|
222
|
-
return {
|
|
223
|
-
opener: ".refine((val) => typeof val === \"string\" && /^(?:0|[1-9][0-9]*)(?:#|(?:\\/(?:[^/~]|~[01])*))*$/.test(val)",
|
|
224
|
-
closer: ")",
|
|
225
|
-
messagePrefix: ", { error: ",
|
|
226
|
-
messageCloser: " })",
|
|
227
|
-
};
|
|
228
|
-
case "uri-template":
|
|
229
|
-
formatWasHandled = true;
|
|
230
|
-
return {
|
|
231
|
-
opener: ".refine((val) => { if (typeof val !== \"string\") return false; const opens = (val.match(/\\{/g) || []).length; const closes = (val.match(/\\}/g) || []).length; return opens === closes; }",
|
|
232
|
-
closer: ")",
|
|
233
|
-
messagePrefix: ", { error: ",
|
|
234
|
-
messageCloser: " })",
|
|
235
|
-
};
|
|
236
|
-
case "regex":
|
|
237
|
-
formatWasHandled = true;
|
|
238
|
-
return {
|
|
239
|
-
opener: ".refine((val) => { try { new RegExp(val); return true; } catch { return false; } }",
|
|
240
|
-
closer: ")",
|
|
241
|
-
messagePrefix: ", { error: ",
|
|
242
|
-
messageCloser: " })",
|
|
243
|
-
};
|
|
244
|
-
}
|
|
245
|
-
});
|
|
30
|
+
let r = "z.string()";
|
|
31
|
+
let zodType = "z.ZodString";
|
|
32
|
+
if (formatFn) {
|
|
33
|
+
const params = formatError !== undefined ? `{ message: ${JSON.stringify(formatError)} }` : "";
|
|
34
|
+
if (schema.format === "date-time") {
|
|
35
|
+
r = `z.iso.datetime({ offset: true${formatError ? `, message: ${JSON.stringify(formatError)}` : ""} })`;
|
|
36
|
+
}
|
|
37
|
+
else if (schema.format === "ipv4") {
|
|
38
|
+
r = `z.ipv4(${formatError ? `{ message: ${JSON.stringify(formatError)} }` : ""})`;
|
|
39
|
+
}
|
|
40
|
+
else if (schema.format === "ipv6") {
|
|
41
|
+
r = `z.ipv6(${formatError ? `{ message: ${JSON.stringify(formatError)} }` : ""})`;
|
|
42
|
+
}
|
|
43
|
+
else {
|
|
44
|
+
r = `${formatFn}(${params})`;
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
let formatWasHandled = Boolean(formatFn);
|
|
48
|
+
if (!formatWasHandled && schema.format) {
|
|
49
|
+
switch (schema.format) {
|
|
50
|
+
case "ip":
|
|
51
|
+
r += `.refine((val) => {
|
|
52
|
+
const v4 = z.ipv4().safeParse(val).success;
|
|
53
|
+
const v6 = z.ipv6().safeParse(val).success;
|
|
54
|
+
return v4 || v6;
|
|
55
|
+
}${formatError ? `, { message: ${JSON.stringify(formatError)} }` : ""})`;
|
|
56
|
+
formatWasHandled = true;
|
|
57
|
+
break;
|
|
58
|
+
case "binary":
|
|
59
|
+
r = `z.base64(${formatError ? `{ message: ${JSON.stringify(formatError)} }` : ""})`;
|
|
60
|
+
formatWasHandled = true;
|
|
61
|
+
break;
|
|
62
|
+
case "hostname":
|
|
63
|
+
case "idn-hostname":
|
|
64
|
+
r += `.refine((val) => {
|
|
65
|
+
if (typeof val !== "string" || val.length === 0 || val.length > 253) return false;
|
|
66
|
+
return val.split(".").every((label) => {
|
|
67
|
+
return label.length > 0 && label.length <= 63 && /^[A-Za-z0-9-]+$/.test(label) && label[0] !== "-" && label[label.length - 1] !== "-";
|
|
68
|
+
});
|
|
69
|
+
}${formatError ? `, { message: ${JSON.stringify(formatError)} }` : ""})`;
|
|
70
|
+
formatWasHandled = true;
|
|
71
|
+
break;
|
|
72
|
+
case "uri-reference":
|
|
73
|
+
case "iri":
|
|
74
|
+
case "iri-reference":
|
|
75
|
+
r += `.refine((val) => {
|
|
76
|
+
try {
|
|
77
|
+
new URL(val, "http://example.com");
|
|
78
|
+
return true;
|
|
79
|
+
} catch {
|
|
80
|
+
return false;
|
|
81
|
+
}
|
|
82
|
+
}${formatError ? `, { message: ${JSON.stringify(formatError)} }` : ""})`;
|
|
83
|
+
formatWasHandled = true;
|
|
84
|
+
break;
|
|
85
|
+
case "json-pointer":
|
|
86
|
+
r += `.refine((val) => typeof val === "string" && /^(?:\\/(?:[^/~]|~[01])*)*$/.test(val)${formatError ? `, { message: ${JSON.stringify(formatError)} }` : ""})`;
|
|
87
|
+
formatWasHandled = true;
|
|
88
|
+
break;
|
|
89
|
+
case "relative-json-pointer":
|
|
90
|
+
r += `.refine((val) => typeof val === "string" && /^(?:0|[1-9][0-9]*)(?:#|(?:\\/(?:[^/~]|~[01])*))*$/.test(val)${formatError ? `, { message: ${JSON.stringify(formatError)} }` : ""})`;
|
|
91
|
+
formatWasHandled = true;
|
|
92
|
+
break;
|
|
93
|
+
case "uri-template":
|
|
94
|
+
r += `.refine((val) => {
|
|
95
|
+
if (typeof val !== "string") return false;
|
|
96
|
+
const opens = (val.match(/\\{/g) || []).length;
|
|
97
|
+
const closes = (val.match(/\\}/g) || []).length;
|
|
98
|
+
return opens === closes;
|
|
99
|
+
}${formatError ? `, { message: ${JSON.stringify(formatError)} }` : ""})`;
|
|
100
|
+
formatWasHandled = true;
|
|
101
|
+
break;
|
|
102
|
+
case "regex":
|
|
103
|
+
r += `.refine((val) => {
|
|
104
|
+
try {
|
|
105
|
+
new RegExp(val);
|
|
106
|
+
return true;
|
|
107
|
+
} catch {
|
|
108
|
+
return false;
|
|
109
|
+
}
|
|
110
|
+
}${formatError ? `, { message: ${JSON.stringify(formatError)} }` : ""})`;
|
|
111
|
+
formatWasHandled = true;
|
|
112
|
+
break;
|
|
113
|
+
}
|
|
246
114
|
}
|
|
247
115
|
if (schema.format && !formatWasHandled) {
|
|
248
116
|
refContext.onUnknownFormat?.(schema.format, refContext.path);
|
|
@@ -250,37 +118,31 @@ export const parseString = (schema, refs) => {
|
|
|
250
118
|
r += withMessage(schema, "pattern", ({ json }) => ({
|
|
251
119
|
opener: `.regex(new RegExp(${json})`,
|
|
252
120
|
closer: ")",
|
|
253
|
-
messagePrefix: ", {
|
|
121
|
+
messagePrefix: ", { message: ",
|
|
254
122
|
messageCloser: " })",
|
|
255
123
|
}));
|
|
256
124
|
r += withMessage(schema, "minLength", ({ json }) => ({
|
|
257
125
|
opener: `.min(${json}`,
|
|
258
126
|
closer: ")",
|
|
259
|
-
messagePrefix: ", {
|
|
127
|
+
messagePrefix: ", { message: ",
|
|
260
128
|
messageCloser: " })",
|
|
261
129
|
}));
|
|
262
130
|
r += withMessage(schema, "maxLength", ({ json }) => ({
|
|
263
131
|
opener: `.max(${json}`,
|
|
264
132
|
closer: ")",
|
|
265
|
-
messagePrefix: ", {
|
|
133
|
+
messagePrefix: ", { message: ",
|
|
266
134
|
messageCloser: " })",
|
|
267
135
|
}));
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
closer: ")",
|
|
273
|
-
messagePrefix: "{ error: ",
|
|
274
|
-
messageCloser: " })",
|
|
275
|
-
};
|
|
276
|
-
}
|
|
277
|
-
});
|
|
136
|
+
if (schema.contentEncoding === "base64" && schema.format !== "base64") {
|
|
137
|
+
const encodingError = schema.errorMessage?.contentEncoding;
|
|
138
|
+
r = `z.base64(${encodingError ? `{ message: ${JSON.stringify(encodingError)} }` : ""})`;
|
|
139
|
+
}
|
|
278
140
|
const contentMediaType = withMessage(schema, "contentMediaType", ({ value }) => {
|
|
279
141
|
if (value === "application/json") {
|
|
280
142
|
return {
|
|
281
143
|
opener: '.transform((str, ctx) => { try { return JSON.parse(str); } catch (err) { ctx.addIssue({ code: "custom", message: "Invalid JSON" }); }}',
|
|
282
144
|
closer: ")",
|
|
283
|
-
messagePrefix: ", {
|
|
145
|
+
messagePrefix: ", { message: ",
|
|
284
146
|
messageCloser: " })",
|
|
285
147
|
};
|
|
286
148
|
}
|
|
@@ -296,14 +158,12 @@ export const parseString = (schema, refs) => {
|
|
|
296
158
|
return {
|
|
297
159
|
opener: `.pipe(${contentExpr}`,
|
|
298
160
|
closer: ")",
|
|
299
|
-
messagePrefix: ", {
|
|
161
|
+
messagePrefix: ", { message: ",
|
|
300
162
|
messageCloser: " })",
|
|
301
163
|
};
|
|
302
164
|
}
|
|
303
165
|
});
|
|
304
166
|
}
|
|
305
|
-
// Use the correct Zod type based on whether a format function was used
|
|
306
|
-
const zodType = formatInfo?.zodType ?? "z.ZodString";
|
|
307
167
|
return {
|
|
308
168
|
expression: r,
|
|
309
169
|
type: zodType,
|
package/dist/types/Types.d.ts
CHANGED
|
@@ -25,9 +25,10 @@ export type JsonSchemaObject = {
|
|
|
25
25
|
definitions?: Record<string, JsonSchema>;
|
|
26
26
|
title?: string;
|
|
27
27
|
description?: string;
|
|
28
|
-
examples?: Serializable[];
|
|
28
|
+
examples?: Serializable | Serializable[];
|
|
29
29
|
deprecated?: boolean;
|
|
30
30
|
dependentSchemas?: Record<string, JsonSchema>;
|
|
31
|
+
dependentRequired?: Record<string, string[]>;
|
|
31
32
|
contains?: JsonSchema;
|
|
32
33
|
minContains?: number;
|
|
33
34
|
maxContains?: number;
|
|
@@ -44,6 +45,7 @@ export type JsonSchemaObject = {
|
|
|
44
45
|
required?: string[] | boolean;
|
|
45
46
|
propertyNames?: JsonSchema;
|
|
46
47
|
items?: JsonSchema | JsonSchema[];
|
|
48
|
+
prefixItems?: JsonSchema[];
|
|
47
49
|
additionalItems?: JsonSchema;
|
|
48
50
|
minItems?: number;
|
|
49
51
|
maxItems?: number;
|
|
@@ -71,6 +73,16 @@ export type JsonSchemaObject = {
|
|
|
71
73
|
} & Record<string, unknown>;
|
|
72
74
|
export type ParserSelector = (schema: JsonSchemaObject, refs: Refs) => SchemaRepresentation;
|
|
73
75
|
export type ParserOverride = (schema: JsonSchemaObject, refs: Refs) => string | void;
|
|
76
|
+
export type NamingContext = {
|
|
77
|
+
isRoot: boolean;
|
|
78
|
+
isLifted: boolean;
|
|
79
|
+
};
|
|
80
|
+
export type NamingOptions = {
|
|
81
|
+
/** Customize the const name for schemas. Defaults to appending "Schema". */
|
|
82
|
+
schemaName?: (baseName: string, ctx: NamingContext) => string;
|
|
83
|
+
/** Customize the type name for schemas. Defaults to baseName when naming is enabled. */
|
|
84
|
+
typeName?: (baseName: string, ctx: NamingContext) => string | undefined;
|
|
85
|
+
};
|
|
74
86
|
export type Options = {
|
|
75
87
|
name?: string;
|
|
76
88
|
withoutDefaults?: boolean;
|
|
@@ -78,6 +90,8 @@ export type Options = {
|
|
|
78
90
|
withJsdocs?: boolean;
|
|
79
91
|
/** Use .meta() instead of .describe() - includes id, title, description */
|
|
80
92
|
withMeta?: boolean;
|
|
93
|
+
/** Customize schema and type naming for root and lifted schemas. */
|
|
94
|
+
naming?: NamingOptions;
|
|
81
95
|
parserOverride?: ParserOverride;
|
|
82
96
|
depth?: number;
|
|
83
97
|
type?: boolean | string;
|
|
@@ -118,6 +132,14 @@ export type Options = {
|
|
|
118
132
|
* @default false
|
|
119
133
|
*/
|
|
120
134
|
strictOneOf?: boolean;
|
|
135
|
+
/**
|
|
136
|
+
* Root schema instance for JSON Pointer resolution (#/...).
|
|
137
|
+
*/
|
|
138
|
+
root?: JsonSchema;
|
|
139
|
+
/**
|
|
140
|
+
* Full document root schema instance for cross-reference resolution.
|
|
141
|
+
*/
|
|
142
|
+
documentRoot?: JsonSchema;
|
|
121
143
|
/**
|
|
122
144
|
* Called when a string format is encountered that has no built-in mapping.
|
|
123
145
|
* Can be used to log or throw on unknown formats.
|
|
@@ -133,6 +155,16 @@ export type Options = {
|
|
|
133
155
|
* Return a JsonSchema to register, or undefined if not found.
|
|
134
156
|
*/
|
|
135
157
|
resolveExternalRef?: (uri: string) => JsonSchema | Promise<JsonSchema> | undefined;
|
|
158
|
+
/** Root/base URI for the document */
|
|
159
|
+
rootBaseUri?: string;
|
|
160
|
+
/** Prebuilt registry of resolved URIs/anchors */
|
|
161
|
+
refRegistry?: Map<string, {
|
|
162
|
+
schema: JsonSchema;
|
|
163
|
+
path: (string | number)[];
|
|
164
|
+
baseUri: string;
|
|
165
|
+
dynamic?: boolean;
|
|
166
|
+
anchor?: string;
|
|
167
|
+
}>;
|
|
136
168
|
/**
|
|
137
169
|
* Lift inline object schemas into top-level defs to improve reusability.
|
|
138
170
|
* Default is ON; set enable: false to opt out.
|
|
@@ -164,7 +196,10 @@ export type Refs = Options & {
|
|
|
164
196
|
dependencies?: Map<string, Set<string>>;
|
|
165
197
|
inProgress?: Set<string>;
|
|
166
198
|
refNameByPointer?: Map<string, string>;
|
|
199
|
+
refBaseNameByPointer?: Map<string, string>;
|
|
200
|
+
baseNameBySchema?: Map<string, string>;
|
|
167
201
|
usedNames?: Set<string>;
|
|
202
|
+
usedBaseNames?: Set<string>;
|
|
168
203
|
currentSchemaName?: string;
|
|
169
204
|
cycleRefNames?: Set<string>;
|
|
170
205
|
cycleComponentByName?: Map<string, number>;
|
|
@@ -180,6 +215,7 @@ export type Refs = Options & {
|
|
|
180
215
|
dynamic?: boolean;
|
|
181
216
|
anchor?: string;
|
|
182
217
|
}>;
|
|
218
|
+
definitions?: Record<string, JsonSchema>;
|
|
183
219
|
/** Stack of active dynamic anchors (nearest last) */
|
|
184
220
|
dynamicAnchors?: {
|
|
185
221
|
name: string;
|
|
@@ -8,6 +8,9 @@ export type AnalysisResult = {
|
|
|
8
8
|
schema: JsonSchema;
|
|
9
9
|
options: NormalizedOptions;
|
|
10
10
|
refNameByPointer: Map<string, string>;
|
|
11
|
+
refBaseNameByPointer: Map<string, string>;
|
|
12
|
+
baseNameBySchema: Map<string, string>;
|
|
13
|
+
rootBaseName?: string;
|
|
11
14
|
usedNames: Set<string>;
|
|
12
15
|
declarations: Map<string, SchemaRepresentation>;
|
|
13
16
|
dependencies: Map<string, Set<string>>;
|
|
@@ -21,5 +24,6 @@ export type AnalysisResult = {
|
|
|
21
24
|
anchor?: string;
|
|
22
25
|
}>;
|
|
23
26
|
rootBaseUri: string;
|
|
27
|
+
definitions?: Record<string, JsonSchema>;
|
|
24
28
|
};
|
|
25
29
|
export declare const analyzeSchema: (schema: JsonSchema, options?: Options) => AnalysisResult;
|
|
@@ -2,7 +2,7 @@ import { JsonSchema, Options } from "../Types.js";
|
|
|
2
2
|
export type SplitDefsOptions = {
|
|
3
3
|
/** Include a root schema file in addition to $defs */
|
|
4
4
|
includeRoot?: boolean;
|
|
5
|
-
/** Override file name for each schema (default: `${def}.schema.ts`) */
|
|
5
|
+
/** Override file name for each schema (default: `${ def }.schema.ts`) */
|
|
6
6
|
fileName?: (defName: string, ctx: {
|
|
7
7
|
isRoot: boolean;
|
|
8
8
|
}) => string;
|
package/dist/types/index.d.ts
CHANGED
|
@@ -33,6 +33,7 @@ export * from "./utils/namingService.js";
|
|
|
33
33
|
export * from "./utils/omit.js";
|
|
34
34
|
export * from "./utils/resolveRef.js";
|
|
35
35
|
export * from "./utils/resolveUri.js";
|
|
36
|
+
export * from "./utils/schemaNaming.js";
|
|
36
37
|
export * from "./utils/schemaRepresentation.js";
|
|
37
38
|
export * from "./utils/withMessage.js";
|
|
38
39
|
export * from "./zodToJsonSchema.js";
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
import { NamingContext, NamingOptions } from "../Types.js";
|
|
2
|
+
export declare const defaultSchemaName: (baseName: string) => string;
|
|
3
|
+
export declare const sanitizeIdentifier: (value: string) => string;
|
|
4
|
+
export declare const ensureUnique: (candidate: string, used?: Set<string>) => string;
|
|
5
|
+
export declare const resolveSchemaName: (baseName: string, naming: NamingOptions, ctx: NamingContext, used?: Set<string>) => string;
|
|
6
|
+
export declare const resolveTypeName: (baseName: string, naming: NamingOptions, ctx: NamingContext, used?: Set<string>) => string | undefined;
|
package/dist/utils/cliTools.js
CHANGED
|
@@ -58,8 +58,7 @@ export function parseArgs(params, args, help) {
|
|
|
58
58
|
}
|
|
59
59
|
export function parseOrReadJSON(jsonOrPath) {
|
|
60
60
|
jsonOrPath = jsonOrPath.trim();
|
|
61
|
-
if (jsonOrPath.length < 255 &&
|
|
62
|
-
statSync(jsonOrPath, { throwIfNoEntry: false })?.isFile()) {
|
|
61
|
+
if (jsonOrPath.length < 255 && statSync(jsonOrPath, { throwIfNoEntry: false })?.isFile()) {
|
|
63
62
|
jsonOrPath = readFileSync(jsonOrPath, "utf-8");
|
|
64
63
|
}
|
|
65
64
|
return JSON.parse(jsonOrPath);
|
package/dist/utils/esmEmitter.js
CHANGED
|
@@ -52,7 +52,9 @@ export class EsmEmitter {
|
|
|
52
52
|
const initializer = parseExpression(statement.expression);
|
|
53
53
|
const typeNode = statement.typeAnnotation ? parseType(statement.typeAnnotation) : undefined;
|
|
54
54
|
const decl = ts.factory.createVariableDeclaration(statement.name, undefined, typeNode, initializer);
|
|
55
|
-
const modifiers = statement.exported
|
|
55
|
+
const modifiers = statement.exported
|
|
56
|
+
? [ts.factory.createModifier(ts.SyntaxKind.ExportKeyword)]
|
|
57
|
+
: undefined;
|
|
56
58
|
const varStmt = ts.factory.createVariableStatement(modifiers, ts.factory.createVariableDeclarationList([decl], ts.NodeFlags.Const));
|
|
57
59
|
__classPrivateFieldGet(this, _EsmEmitter_statements, "f").push({ node: attachJsdoc(varStmt, statement.jsdoc) });
|
|
58
60
|
}
|
|
@@ -72,7 +74,9 @@ export class EsmEmitter {
|
|
|
72
74
|
const sf = ts.createSourceFile("out.ts", "", ts.ScriptTarget.ES2020, false, ts.ScriptKind.TS);
|
|
73
75
|
const importStmts = [...__classPrivateFieldGet(this, _EsmEmitter_imports, "f").entries()]
|
|
74
76
|
.sort(([a], [b]) => a.localeCompare(b))
|
|
75
|
-
.map(([source, names]) => ts.factory.createImportDeclaration(undefined, ts.factory.createImportClause(false, undefined, ts.factory.createNamedImports([...names]
|
|
77
|
+
.map(([source, names]) => ts.factory.createImportDeclaration(undefined, ts.factory.createImportClause(false, undefined, ts.factory.createNamedImports([...names]
|
|
78
|
+
.sort()
|
|
79
|
+
.map((name) => ts.factory.createImportSpecifier(false, undefined, ts.factory.createIdentifier(name))))), ts.factory.createStringLiteral(source)));
|
|
76
80
|
const allStatements = [
|
|
77
81
|
...importStmts.map((node) => ({ node })),
|
|
78
82
|
...__classPrivateFieldGet(this, _EsmEmitter_statements, "f"),
|
|
@@ -99,9 +99,7 @@ const sanitizeIdentifier = (value) => {
|
|
|
99
99
|
.replace(/[^a-zA-Z0-9_$\s]/g, " ")
|
|
100
100
|
.split(/\s+/)
|
|
101
101
|
.filter(Boolean);
|
|
102
|
-
const pascalCase = words
|
|
103
|
-
.map((word) => word.charAt(0).toUpperCase() + word.slice(1))
|
|
104
|
-
.join("");
|
|
102
|
+
const pascalCase = words.map((word) => word.charAt(0).toUpperCase() + word.slice(1)).join("");
|
|
105
103
|
const cleaned = pascalCase.replace(/^[^a-zA-Z_$]+/, "").replace(/[^a-zA-Z0-9_$]/g, "");
|
|
106
104
|
return cleaned || "InlineSchema";
|
|
107
105
|
};
|
package/dist/utils/jsdocs.js
CHANGED
|
@@ -1,9 +1,6 @@
|
|
|
1
1
|
export const expandJsdocs = (jsdocs) => {
|
|
2
2
|
const lines = jsdocs.split("\n");
|
|
3
|
-
const result = lines.length === 1
|
|
4
|
-
? lines[0]
|
|
5
|
-
: `\n${lines.map(x => `* ${x}`)
|
|
6
|
-
.join("\n")}\n`;
|
|
3
|
+
const result = lines.length === 1 ? lines[0] : `\n${lines.map((x) => `* ${x}`).join("\n")}\n`;
|
|
7
4
|
return `/**${result}*/\n`;
|
|
8
5
|
};
|
|
9
6
|
export const addJsdocs = (schema, parsed) => {
|