@kubb/plugin-zod 5.0.0-beta.10 → 5.0.0-beta.103
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/README.md +22 -25
- package/dist/index.cjs +1454 -605
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.ts +255 -144
- package/dist/index.js +1442 -593
- package/dist/index.js.map +1 -1
- package/package.json +8 -23
- package/extension.yaml +0 -485
- package/src/components/Operations.tsx +0 -77
- package/src/components/Zod.tsx +0 -42
- package/src/constants.ts +0 -5
- package/src/generators/zodGenerator.tsx +0 -216
- package/src/index.ts +0 -11
- package/src/plugin.ts +0 -94
- package/src/printers/printerZod.ts +0 -365
- package/src/printers/printerZodMini.ts +0 -310
- package/src/resolvers/resolverZod.ts +0 -57
- package/src/types.ts +0 -187
- package/src/utils.ts +0 -222
- /package/dist/{chunk--u3MIqq1.js → rolldown-runtime-C0LytTxp.js} +0 -0
package/dist/index.js
CHANGED
|
@@ -1,7 +1,169 @@
|
|
|
1
|
-
import { t as __name } from "./
|
|
2
|
-
import { ast, defineGenerator, definePlugin,
|
|
3
|
-
import { Const, File, Type, jsxRenderer } from "
|
|
4
|
-
import { Fragment, jsx, jsxs } from "
|
|
1
|
+
import { t as __name } from "./rolldown-runtime-C0LytTxp.js";
|
|
2
|
+
import { Resolver, ast, containsCircularRef, createResolver, defineGenerator, definePlugin, extractRefName, syncSchemaRef } from "kubb/kit";
|
|
3
|
+
import { Const, File, Type, jsxRenderer } from "kubb/jsx";
|
|
4
|
+
import { Fragment, jsx, jsxs } from "kubb/jsx/jsx-runtime";
|
|
5
|
+
//#region ../../internals/shared/src/params.ts
|
|
6
|
+
/**
|
|
7
|
+
* Drops parameters that share the same name, keeping the first.
|
|
8
|
+
*
|
|
9
|
+
* A malformed spec can declare the same parameter name twice within one `in` location. Both would
|
|
10
|
+
* resolve to the same output property, so emitting both would yield an object type with a duplicate
|
|
11
|
+
* member, which TypeScript rejects. This is a defensive guard against that case, not a casing guard:
|
|
12
|
+
* parameter names flow through unchanged, so no two distinct names ever collide here anymore.
|
|
13
|
+
*/
|
|
14
|
+
function dedupeParams(params) {
|
|
15
|
+
const seen = /* @__PURE__ */ new Set();
|
|
16
|
+
return params.filter((param) => {
|
|
17
|
+
if (seen.has(param.name)) return false;
|
|
18
|
+
seen.add(param.name);
|
|
19
|
+
return true;
|
|
20
|
+
});
|
|
21
|
+
}
|
|
22
|
+
//#endregion
|
|
23
|
+
//#region ../../internals/shared/src/operation.ts
|
|
24
|
+
/**
|
|
25
|
+
* Maps a content type to the PascalCase suffix used to name per-content-type variants
|
|
26
|
+
* (e.g. `application/json` → `Json`, `application/xml` → `Xml`, `multipart/form-data` → `FormData`).
|
|
27
|
+
*/
|
|
28
|
+
function getContentTypeSuffix(contentType) {
|
|
29
|
+
const baseType = contentType.split(";")[0].trim();
|
|
30
|
+
if (baseType === "application/json") return "Json";
|
|
31
|
+
if (baseType === "multipart/form-data") return "FormData";
|
|
32
|
+
if (baseType === "application/x-www-form-urlencoded") return "FormUrlEncoded";
|
|
33
|
+
const parts = (baseType.split("/").pop() ?? baseType).split(/[^a-zA-Z0-9]+/).filter(Boolean);
|
|
34
|
+
if (parts.length === 0) return "Unknown";
|
|
35
|
+
return parts.map((part) => part.charAt(0).toUpperCase() + part.slice(1)).join("");
|
|
36
|
+
}
|
|
37
|
+
/**
|
|
38
|
+
* Appends a content-type suffix to a base name, keeping a trailing `Data` segment last
|
|
39
|
+
* (e.g. `AddPetData` + `Json` → `AddPetJsonData`, `AddPetStatus200` + `Xml` → `AddPetStatus200Xml`).
|
|
40
|
+
*/
|
|
41
|
+
function getPerContentTypeName(baseName, suffix) {
|
|
42
|
+
if (baseName.endsWith("Data")) return suffix.endsWith("Data") ? baseName.slice(0, -4) + suffix : `${baseName.slice(0, -4)}${suffix}Data`;
|
|
43
|
+
return baseName + suffix;
|
|
44
|
+
}
|
|
45
|
+
/**
|
|
46
|
+
* Resolves per-content-type variant names for a set of content entries, deduplicating suffix
|
|
47
|
+
* collisions with a numeric counter. Entries without a schema are skipped. The returned `suffix` is
|
|
48
|
+
* the final (possibly counter-augmented) value, so callers can derive parallel names in another
|
|
49
|
+
* namespace (e.g. plugin-faker deriving the matching plugin-ts type name).
|
|
50
|
+
*/
|
|
51
|
+
function resolveContentTypeVariants(entries, baseName) {
|
|
52
|
+
const usedNames = /* @__PURE__ */ new Set();
|
|
53
|
+
return entries.filter((entry) => entry.schema).map((entry) => {
|
|
54
|
+
const baseSuffix = getContentTypeSuffix(entry.contentType);
|
|
55
|
+
let suffix = baseSuffix;
|
|
56
|
+
let name = getPerContentTypeName(baseName, suffix);
|
|
57
|
+
let counter = 2;
|
|
58
|
+
while (usedNames.has(name)) {
|
|
59
|
+
suffix = `${baseSuffix}${counter++}`;
|
|
60
|
+
name = getPerContentTypeName(baseName, suffix);
|
|
61
|
+
}
|
|
62
|
+
usedNames.add(name);
|
|
63
|
+
return {
|
|
64
|
+
name,
|
|
65
|
+
suffix,
|
|
66
|
+
schema: entry.schema,
|
|
67
|
+
keysToOmit: entry.keysToOmit,
|
|
68
|
+
contentType: entry.contentType
|
|
69
|
+
};
|
|
70
|
+
});
|
|
71
|
+
}
|
|
72
|
+
function getOperationParameters(node) {
|
|
73
|
+
return {
|
|
74
|
+
path: dedupeParams(node.parameters.filter((param) => param.in === "path")),
|
|
75
|
+
query: dedupeParams(node.parameters.filter((param) => param.in === "query")),
|
|
76
|
+
header: dedupeParams(node.parameters.filter((param) => param.in === "header")),
|
|
77
|
+
cookie: dedupeParams(node.parameters.filter((param) => param.in === "cookie"))
|
|
78
|
+
};
|
|
79
|
+
}
|
|
80
|
+
/**
|
|
81
|
+
* Builds the combined `{ body, path, query, headers }` options object schema for an operation,
|
|
82
|
+
* referencing the already-resolved body and grouped param names. Shared by `@kubb/plugin-ts`'s
|
|
83
|
+
* `Options` type and `@kubb/plugin-zod`'s inferred options schema, so both printers emit the same
|
|
84
|
+
* shape from the same inputs. `primitive: 'object'` is a no-op for the TS printer and tells the Zod
|
|
85
|
+
* printer to emit `z.object(…)` rather than a record.
|
|
86
|
+
*/
|
|
87
|
+
function buildOptionsSchema(node, resolver) {
|
|
88
|
+
const { path, query, header } = getOperationParameters(node);
|
|
89
|
+
const hasBody = Boolean(node.requestBody?.content?.[0]?.schema);
|
|
90
|
+
const createNever = () => ast.factory.createSchema({
|
|
91
|
+
type: "never",
|
|
92
|
+
primitive: void 0,
|
|
93
|
+
optional: true
|
|
94
|
+
});
|
|
95
|
+
const groups = [
|
|
96
|
+
{
|
|
97
|
+
name: "path",
|
|
98
|
+
params: path,
|
|
99
|
+
resolve: resolver.param.path
|
|
100
|
+
},
|
|
101
|
+
{
|
|
102
|
+
name: "query",
|
|
103
|
+
params: query,
|
|
104
|
+
resolve: resolver.param.query
|
|
105
|
+
},
|
|
106
|
+
{
|
|
107
|
+
name: "headers",
|
|
108
|
+
params: header,
|
|
109
|
+
resolve: resolver.param.headers
|
|
110
|
+
}
|
|
111
|
+
];
|
|
112
|
+
return ast.factory.createSchema({
|
|
113
|
+
type: "object",
|
|
114
|
+
primitive: "object",
|
|
115
|
+
deprecated: node.deprecated,
|
|
116
|
+
properties: [ast.factory.createProperty({
|
|
117
|
+
name: "body",
|
|
118
|
+
required: hasBody,
|
|
119
|
+
schema: hasBody ? ast.factory.createSchema({
|
|
120
|
+
type: "ref",
|
|
121
|
+
name: resolver.response.body(node)
|
|
122
|
+
}) : createNever()
|
|
123
|
+
}), ...groups.map(({ name, params, resolve }) => {
|
|
124
|
+
const required = params.some((param) => param.required);
|
|
125
|
+
return ast.factory.createProperty({
|
|
126
|
+
name,
|
|
127
|
+
required,
|
|
128
|
+
schema: params.length > 0 ? ast.factory.createSchema({
|
|
129
|
+
type: "ref",
|
|
130
|
+
name: resolve.call(resolver.param, node, params[0]),
|
|
131
|
+
optional: !required
|
|
132
|
+
}) : createNever()
|
|
133
|
+
});
|
|
134
|
+
})]
|
|
135
|
+
});
|
|
136
|
+
}
|
|
137
|
+
function getStatusCodeNumber(statusCode) {
|
|
138
|
+
const code = Number(statusCode);
|
|
139
|
+
return Number.isNaN(code) ? null : code;
|
|
140
|
+
}
|
|
141
|
+
function isSuccessStatusCode(statusCode) {
|
|
142
|
+
const code = getStatusCodeNumber(statusCode);
|
|
143
|
+
return code !== null && code >= 200 && code < 300;
|
|
144
|
+
}
|
|
145
|
+
function getSuccessResponses(responses) {
|
|
146
|
+
return responses.filter((response) => isSuccessStatusCode(response.statusCode));
|
|
147
|
+
}
|
|
148
|
+
//#endregion
|
|
149
|
+
//#region ../../internals/shared/src/adapter.ts
|
|
150
|
+
/**
|
|
151
|
+
* Narrows the generic `Adapter` from a generator context to the OpenAPI adapter,
|
|
152
|
+
* so OAS-only options (`dateType`, `enums`) and the parsed `document` are typed.
|
|
153
|
+
*
|
|
154
|
+
* Throws when a non-OAS adapter is configured, turning a silently wrong cast into a
|
|
155
|
+
* clear, actionable error at the point of use.
|
|
156
|
+
*
|
|
157
|
+
* @example
|
|
158
|
+
* ```ts
|
|
159
|
+
* const { dateType } = getOasAdapter(ctx.adapter).options
|
|
160
|
+
* ```
|
|
161
|
+
*/
|
|
162
|
+
function getOasAdapter(adapter) {
|
|
163
|
+
if (adapter.name !== "oas") throw new Error(`Expected the OpenAPI adapter (adapterOas), but received "${adapter.name}". Configure \`adapter: adapterOas()\` in your Kubb config.`);
|
|
164
|
+
return adapter;
|
|
165
|
+
}
|
|
166
|
+
//#endregion
|
|
5
167
|
//#region ../../internals/utils/src/casing.ts
|
|
6
168
|
/**
|
|
7
169
|
* Shared implementation for camelCase and PascalCase conversion.
|
|
@@ -13,61 +175,197 @@ import { Fragment, jsx, jsxs } from "@kubb/renderer-jsx/jsx-runtime";
|
|
|
13
175
|
function toCamelOrPascal(text, pascal) {
|
|
14
176
|
return text.trim().replace(/([a-z\d])([A-Z])/g, "$1 $2").replace(/([A-Z]+)([A-Z][a-z])/g, "$1 $2").replace(/(\d)([a-z])/g, "$1 $2").split(/[\s\-_./\\:]+/).filter(Boolean).map((word, i) => {
|
|
15
177
|
if (word.length > 1 && word === word.toUpperCase()) return word;
|
|
16
|
-
|
|
17
|
-
return word.charAt(0).toUpperCase() + word.slice(1);
|
|
178
|
+
return (i === 0 && !pascal ? word.charAt(0).toLowerCase() : word.charAt(0).toUpperCase()) + word.slice(1);
|
|
18
179
|
}).join("").replace(/[^a-zA-Z0-9]/g, "");
|
|
19
180
|
}
|
|
20
181
|
/**
|
|
21
|
-
*
|
|
22
|
-
*
|
|
23
|
-
*
|
|
182
|
+
* Converts `text` to camelCase.
|
|
183
|
+
*
|
|
184
|
+
* @example Word boundaries
|
|
185
|
+
* `camelCase('hello-world') // 'helloWorld'`
|
|
24
186
|
*
|
|
25
|
-
*
|
|
26
|
-
*
|
|
187
|
+
* @example With a prefix
|
|
188
|
+
* `camelCase('tag', { prefix: 'create' }) // 'createTag'`
|
|
27
189
|
*/
|
|
28
|
-
function
|
|
29
|
-
|
|
30
|
-
return parts.map((part, i) => transformPart(part, i === parts.length - 1)).join("/");
|
|
190
|
+
function camelCase(text, { prefix = "", suffix = "" } = {}) {
|
|
191
|
+
return toCamelOrPascal(`${prefix} ${text} ${suffix}`, false);
|
|
31
192
|
}
|
|
32
193
|
/**
|
|
33
|
-
* Converts `text` to
|
|
34
|
-
*
|
|
194
|
+
* Converts `text` to PascalCase.
|
|
195
|
+
*
|
|
196
|
+
* @example Word boundaries
|
|
197
|
+
* `pascalCase('hello-world') // 'HelloWorld'`
|
|
198
|
+
*
|
|
199
|
+
* @example With a suffix
|
|
200
|
+
* `pascalCase('tag', { suffix: 'schema' }) // 'TagSchema'`
|
|
201
|
+
*/
|
|
202
|
+
function pascalCase(text, { prefix = "", suffix = "" } = {}) {
|
|
203
|
+
return toCamelOrPascal(`${prefix} ${text} ${suffix}`, true);
|
|
204
|
+
}
|
|
205
|
+
//#endregion
|
|
206
|
+
//#region ../../internals/utils/src/reserved.ts
|
|
207
|
+
/**
|
|
208
|
+
* JavaScript and Java reserved words.
|
|
209
|
+
* @link https://github.com/jonschlinkert/reserved/blob/master/index.js
|
|
210
|
+
*/
|
|
211
|
+
const reservedWords = /* @__PURE__ */ new Set([
|
|
212
|
+
"abstract",
|
|
213
|
+
"arguments",
|
|
214
|
+
"boolean",
|
|
215
|
+
"break",
|
|
216
|
+
"byte",
|
|
217
|
+
"case",
|
|
218
|
+
"catch",
|
|
219
|
+
"char",
|
|
220
|
+
"class",
|
|
221
|
+
"const",
|
|
222
|
+
"continue",
|
|
223
|
+
"debugger",
|
|
224
|
+
"default",
|
|
225
|
+
"delete",
|
|
226
|
+
"do",
|
|
227
|
+
"double",
|
|
228
|
+
"else",
|
|
229
|
+
"enum",
|
|
230
|
+
"eval",
|
|
231
|
+
"export",
|
|
232
|
+
"extends",
|
|
233
|
+
"false",
|
|
234
|
+
"final",
|
|
235
|
+
"finally",
|
|
236
|
+
"float",
|
|
237
|
+
"for",
|
|
238
|
+
"function",
|
|
239
|
+
"goto",
|
|
240
|
+
"if",
|
|
241
|
+
"implements",
|
|
242
|
+
"import",
|
|
243
|
+
"in",
|
|
244
|
+
"instanceof",
|
|
245
|
+
"int",
|
|
246
|
+
"interface",
|
|
247
|
+
"let",
|
|
248
|
+
"long",
|
|
249
|
+
"native",
|
|
250
|
+
"new",
|
|
251
|
+
"null",
|
|
252
|
+
"package",
|
|
253
|
+
"private",
|
|
254
|
+
"protected",
|
|
255
|
+
"public",
|
|
256
|
+
"return",
|
|
257
|
+
"short",
|
|
258
|
+
"static",
|
|
259
|
+
"super",
|
|
260
|
+
"switch",
|
|
261
|
+
"synchronized",
|
|
262
|
+
"this",
|
|
263
|
+
"throw",
|
|
264
|
+
"throws",
|
|
265
|
+
"transient",
|
|
266
|
+
"true",
|
|
267
|
+
"try",
|
|
268
|
+
"typeof",
|
|
269
|
+
"var",
|
|
270
|
+
"void",
|
|
271
|
+
"volatile",
|
|
272
|
+
"while",
|
|
273
|
+
"with",
|
|
274
|
+
"yield",
|
|
275
|
+
"Array",
|
|
276
|
+
"Date",
|
|
277
|
+
"hasOwnProperty",
|
|
278
|
+
"Infinity",
|
|
279
|
+
"isFinite",
|
|
280
|
+
"isNaN",
|
|
281
|
+
"isPrototypeOf",
|
|
282
|
+
"length",
|
|
283
|
+
"Math",
|
|
284
|
+
"name",
|
|
285
|
+
"NaN",
|
|
286
|
+
"Number",
|
|
287
|
+
"Object",
|
|
288
|
+
"prototype",
|
|
289
|
+
"String",
|
|
290
|
+
"toString",
|
|
291
|
+
"undefined",
|
|
292
|
+
"valueOf"
|
|
293
|
+
]);
|
|
294
|
+
/**
|
|
295
|
+
* Returns `true` when `name` is a syntactically valid JavaScript variable name.
|
|
35
296
|
*
|
|
36
297
|
* @example
|
|
37
|
-
*
|
|
38
|
-
*
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
return
|
|
298
|
+
* ```ts
|
|
299
|
+
* isValidVarName('status') // true
|
|
300
|
+
* isValidVarName('class') // false (reserved word)
|
|
301
|
+
* isValidVarName('42foo') // false (starts with digit)
|
|
302
|
+
* ```
|
|
303
|
+
*/
|
|
304
|
+
function isValidVarName(name) {
|
|
305
|
+
if (!name || reservedWords.has(name)) return false;
|
|
306
|
+
return isIdentifier(name);
|
|
46
307
|
}
|
|
47
308
|
/**
|
|
48
|
-
*
|
|
49
|
-
*
|
|
309
|
+
* Returns `name` when it's a syntactically valid JavaScript variable name,
|
|
310
|
+
* otherwise prefixes it with `_` so the result is a valid identifier.
|
|
311
|
+
*
|
|
312
|
+
* Useful for sanitizing OpenAPI schema names or operation IDs that start with
|
|
313
|
+
* a digit (e.g. `409`, `504AccountCancel`) before using them as exported
|
|
314
|
+
* variable, type, or function names.
|
|
50
315
|
*
|
|
51
316
|
* @example
|
|
52
|
-
*
|
|
53
|
-
*
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
317
|
+
* ```ts
|
|
318
|
+
* ensureValidVarName('409') // '_409'
|
|
319
|
+
* ensureValidVarName('504AccountCancel') // '_504AccountCancel'
|
|
320
|
+
* ensureValidVarName('Pet') // 'Pet'
|
|
321
|
+
* ensureValidVarName('class') // '_class'
|
|
322
|
+
* ```
|
|
323
|
+
*/
|
|
324
|
+
function ensureValidVarName(name) {
|
|
325
|
+
if (!name || isValidVarName(name)) return name;
|
|
326
|
+
return `_${name}`;
|
|
327
|
+
}
|
|
328
|
+
/**
|
|
329
|
+
* Returns `true` when `name` is syntactically a valid identifier, ignoring reserved words.
|
|
330
|
+
*
|
|
331
|
+
* Reserved words and globals (`class`, `name`, `Date`, …) are valid as bare object-literal keys
|
|
332
|
+
* even though they are not valid variable names, so use this (not {@link isValidVarName}) when
|
|
333
|
+
* deciding whether an object key needs quoting.
|
|
334
|
+
*
|
|
335
|
+
* @example
|
|
336
|
+
* ```ts
|
|
337
|
+
* isIdentifier('name') // true
|
|
338
|
+
* isIdentifier('x-total')// false
|
|
339
|
+
* ```
|
|
340
|
+
*/
|
|
341
|
+
function isIdentifier(name) {
|
|
342
|
+
return /^[a-zA-Z_$][a-zA-Z0-9_$]*$/.test(name);
|
|
61
343
|
}
|
|
62
344
|
//#endregion
|
|
63
|
-
//#region ../../internals/utils/src/
|
|
345
|
+
//#region ../../internals/utils/src/strings.ts
|
|
346
|
+
/**
|
|
347
|
+
* Wraps a value in single quotes for emitting a single-quoted JavaScript string literal, escaping
|
|
348
|
+
* any backslash or single quote in the content.
|
|
349
|
+
*
|
|
350
|
+
* @example
|
|
351
|
+
* ```ts
|
|
352
|
+
* singleQuote('foo') // "'foo'"
|
|
353
|
+
* singleQuote("o'clock") // "'o\\'clock'"
|
|
354
|
+
* ```
|
|
355
|
+
*/
|
|
356
|
+
function singleQuote(value) {
|
|
357
|
+
if (value === void 0 || value === null) return "''";
|
|
358
|
+
return `'${String(value).replace(/\\/g, "\\\\").replace(/'/g, "\\'")}'`;
|
|
359
|
+
}
|
|
64
360
|
/**
|
|
65
361
|
* Strips a single matching pair of `"..."`, `'...'`, or `` `...` `` from both ends of `text`.
|
|
66
362
|
* Returns the string unchanged when no balanced quote pair is found.
|
|
67
363
|
*
|
|
68
364
|
* @example
|
|
365
|
+
* ```ts
|
|
69
366
|
* trimQuotes('"hello"') // 'hello'
|
|
70
367
|
* trimQuotes('hello') // 'hello'
|
|
368
|
+
* ```
|
|
71
369
|
*/
|
|
72
370
|
function trimQuotes(text) {
|
|
73
371
|
if (text.length >= 2) {
|
|
@@ -77,43 +375,32 @@ function trimQuotes(text) {
|
|
|
77
375
|
}
|
|
78
376
|
return text;
|
|
79
377
|
}
|
|
80
|
-
//#endregion
|
|
81
|
-
//#region ../../internals/utils/src/object.ts
|
|
82
378
|
/**
|
|
83
|
-
* Serializes a primitive
|
|
379
|
+
* Serializes a primitive to a single-quoted string literal, stripping any surrounding quotes first.
|
|
84
380
|
*
|
|
85
|
-
*
|
|
86
|
-
*
|
|
87
|
-
* stringify('"hello"') // '"hello"'
|
|
88
|
-
*/
|
|
89
|
-
function stringify(value) {
|
|
90
|
-
if (value === void 0 || value === null) return "\"\"";
|
|
91
|
-
return JSON.stringify(trimQuotes(value.toString()));
|
|
92
|
-
}
|
|
93
|
-
/**
|
|
94
|
-
* Converts a plain object into a multiline key-value string suitable for embedding in generated code.
|
|
95
|
-
* Nested objects are recursively stringified with indentation.
|
|
381
|
+
* Escaping runs through `JSON.stringify`, then the result switches to single quotes so the generated
|
|
382
|
+
* code matches the repo style without a formatter.
|
|
96
383
|
*
|
|
97
384
|
* @example
|
|
98
|
-
*
|
|
99
|
-
* // '
|
|
385
|
+
* ```ts
|
|
386
|
+
* stringify('hello') // "'hello'"
|
|
387
|
+
* stringify('"hello"') // "'hello'"
|
|
388
|
+
* ```
|
|
100
389
|
*/
|
|
101
|
-
function
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
return `${key}: ${val}`;
|
|
105
|
-
}).filter(Boolean).join(",\n");
|
|
390
|
+
function stringify(value) {
|
|
391
|
+
if (value === void 0 || value === null) return "''";
|
|
392
|
+
return `'${JSON.stringify(trimQuotes(value.toString())).slice(1, -1).replace(/\\"/g, "\"").replace(/'/g, "\\'")}'`;
|
|
106
393
|
}
|
|
107
|
-
//#endregion
|
|
108
|
-
//#region ../../internals/utils/src/regexp.ts
|
|
109
394
|
/**
|
|
110
395
|
* Converts a pattern string into a `new RegExp(...)` constructor call or a regex literal string.
|
|
111
|
-
* Inline flags expressed as `^(?im)`
|
|
396
|
+
* Inline flags expressed as a `^(?im)` prefix are extracted and applied to the resulting expression.
|
|
112
397
|
* Pass `null` as the second argument to emit a `/pattern/flags` literal instead.
|
|
113
398
|
*
|
|
114
399
|
* @example
|
|
115
|
-
*
|
|
116
|
-
* toRegExpString('^(?im)foo'
|
|
400
|
+
* ```ts
|
|
401
|
+
* toRegExpString('^(?im)foo') // 'new RegExp("^foo", "im")'
|
|
402
|
+
* toRegExpString('^(?im)foo', null) // '/^foo/im'
|
|
403
|
+
* ```
|
|
117
404
|
*/
|
|
118
405
|
function toRegExpString(text, func = "RegExp") {
|
|
119
406
|
const raw = trimQuotes(text);
|
|
@@ -126,83 +413,265 @@ function toRegExpString(text, func = "RegExp") {
|
|
|
126
413
|
return `new ${func}(${JSON.stringify(source)}${flags ? `, ${JSON.stringify(flags)}` : ""})`;
|
|
127
414
|
}
|
|
128
415
|
//#endregion
|
|
129
|
-
//#region src/
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
}
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
416
|
+
//#region ../../internals/utils/src/codegen.ts
|
|
417
|
+
const INDENT = " ";
|
|
418
|
+
/**
|
|
419
|
+
* Indents every non-empty line of `text` by one indent level, leaving blank lines empty.
|
|
420
|
+
*/
|
|
421
|
+
function indentLines(text) {
|
|
422
|
+
if (!text) return "";
|
|
423
|
+
return text.split("\n").map((line) => line.trim() ? `${INDENT}${line}` : "").join("\n");
|
|
424
|
+
}
|
|
425
|
+
/**
|
|
426
|
+
* Renders an object key, quoting it with single quotes only when it is not a valid identifier.
|
|
427
|
+
* Reserved words and globals (`name`, `class`, …) are valid bare keys and stay unquoted.
|
|
428
|
+
*
|
|
429
|
+
* @example
|
|
430
|
+
* ```ts
|
|
431
|
+
* objectKey('name') // 'name'
|
|
432
|
+
* objectKey('x-total') // "'x-total'"
|
|
433
|
+
* ```
|
|
434
|
+
*/
|
|
435
|
+
function objectKey(name) {
|
|
436
|
+
return isIdentifier(name) ? name : singleQuote(name);
|
|
437
|
+
}
|
|
438
|
+
/**
|
|
439
|
+
* Assembles a multi-line object literal from already-rendered `entries`, indenting each entry one
|
|
440
|
+
* level and closing the brace at column zero. Entries that are themselves multi-line objects indent
|
|
441
|
+
* cumulatively. Each entry ends with a trailing comma to match the formatter's multi-line style.
|
|
442
|
+
*
|
|
443
|
+
* @example
|
|
444
|
+
* ```ts
|
|
445
|
+
* buildObject(['id: z.number()', 'name: z.string()'])
|
|
446
|
+
* // '{\n id: z.number(),\n name: z.string(),\n}'
|
|
447
|
+
* ```
|
|
448
|
+
*/
|
|
449
|
+
function buildObject(entries) {
|
|
450
|
+
if (entries.length === 0) return "{}";
|
|
451
|
+
return `{\n${entries.map((entry) => `${indentLines(entry)},`).join("\n")}\n}`;
|
|
452
|
+
}
|
|
453
|
+
/**
|
|
454
|
+
* Assembles a bracketed list (array by default) from already-rendered `items`. Keeps everything on
|
|
455
|
+
* one line when no item spans multiple lines, and otherwise puts each item on its own line, indented
|
|
456
|
+
* one level with a trailing comma and the closing bracket at column zero. Used for member lists such
|
|
457
|
+
* as `z.union([…])` and `z.array([…])`.
|
|
458
|
+
*
|
|
459
|
+
* @example
|
|
460
|
+
* ```ts
|
|
461
|
+
* buildList(['z.string()', 'z.number()'])
|
|
462
|
+
* // '[z.string(), z.number()]'
|
|
463
|
+
* ```
|
|
464
|
+
*/
|
|
465
|
+
function buildList(items, brackets = ["[", "]"]) {
|
|
466
|
+
const [open, close] = brackets;
|
|
467
|
+
if (items.length === 0) return `${open}${close}`;
|
|
468
|
+
if (!items.some((item) => item.includes("\n"))) return `${open}${items.join(", ")}${close}`;
|
|
469
|
+
return `${open}\n${items.map((item) => `${indentLines(item)},`).join("\n")}\n${close}`;
|
|
470
|
+
}
|
|
471
|
+
/**
|
|
472
|
+
* Emits a lazy getter for a circular-ref property position, `get name() { return body }`. The key
|
|
473
|
+
* is quoted only when it is not a valid identifier. Used by the string printers to defer evaluation
|
|
474
|
+
* of a recursive schema until first access.
|
|
475
|
+
*
|
|
476
|
+
* @example
|
|
477
|
+
* ```ts
|
|
478
|
+
* lazyGetter({ name: 'parent', body: 'z.lazy(() => Pet)' })
|
|
479
|
+
* // "get parent() { return z.lazy(() => Pet) }"
|
|
480
|
+
* ```
|
|
481
|
+
*/
|
|
482
|
+
function lazyGetter({ name, body }) {
|
|
483
|
+
return `get ${objectKey(name)}() { return ${body} }`;
|
|
484
|
+
}
|
|
485
|
+
//#endregion
|
|
486
|
+
//#region ../../internals/utils/src/fs.ts
|
|
487
|
+
/**
|
|
488
|
+
* Builds a nested file path from a dotted name. Splits on dots that precede a letter
|
|
489
|
+
* (so version numbers embedded in operationIds like `v2025.0` stay intact), camelCases
|
|
490
|
+
* every earlier segment, applies `caseLast` to the final segment, and joins with `/`.
|
|
491
|
+
*
|
|
492
|
+
* Empty segments are dropped before joining. They arise when the name starts with a dot
|
|
493
|
+
* followed by a letter (e.g. `..Schema` splits into `['..', 'Schema']` and `'..'` cases to
|
|
494
|
+
* an empty string). Without this a leading `/` would form, which `path.resolve` reads as an
|
|
495
|
+
* absolute path, letting generated files escape the configured output directory.
|
|
496
|
+
*
|
|
497
|
+
* @example Nested path from a dotted name
|
|
498
|
+
* `toFilePath('pet.petId') // 'pet/petId'`
|
|
499
|
+
*
|
|
500
|
+
* @example PascalCase the final segment
|
|
501
|
+
* `toFilePath('pet.Pet', pascalCase) // 'pet/Pet'`
|
|
502
|
+
*
|
|
503
|
+
* @example Suffix applied to the final segment only
|
|
504
|
+
* `toFilePath('tag.tag', (part) => camelCase(part, { suffix: 'schema' })) // 'tag/tagSchema'`
|
|
505
|
+
*/
|
|
506
|
+
function toFilePath(name, caseLast = camelCase) {
|
|
507
|
+
const parts = name.split(/\.(?=[a-zA-Z])/);
|
|
508
|
+
return parts.map((part, i) => i === parts.length - 1 ? caseLast(part) : camelCase(part)).filter(Boolean).join("/");
|
|
509
|
+
}
|
|
510
|
+
//#endregion
|
|
511
|
+
//#region ../../internals/shared/src/resolver.ts
|
|
512
|
+
/**
|
|
513
|
+
* Resolves a single operation parameter name with the
|
|
514
|
+
* `<operationId> <in> <name>` template.
|
|
515
|
+
*
|
|
516
|
+
* @example
|
|
517
|
+
* `operationParamName.call(resolver, node, param) // → 'DeletePetPathPetId'`
|
|
518
|
+
*/
|
|
519
|
+
function operationParamName(node, param) {
|
|
520
|
+
return this.name(`${node.operationId} ${param.in} ${param.name}`);
|
|
521
|
+
}
|
|
522
|
+
/**
|
|
523
|
+
* Builds the shared `param` namespace. Spread the result into `createResolver`
|
|
524
|
+
* and override individual methods next to it when a plugin deviates.
|
|
525
|
+
*
|
|
526
|
+
* @example
|
|
527
|
+
* ```ts
|
|
528
|
+
* createResolver<PluginTs>({ param: createOperationParamResolver(), ... })
|
|
529
|
+
* ```
|
|
530
|
+
*/
|
|
531
|
+
function createOperationParamResolver() {
|
|
532
|
+
return {
|
|
533
|
+
name: operationParamName,
|
|
534
|
+
path(node) {
|
|
535
|
+
return this.name(`${node.operationId} Path`);
|
|
536
|
+
},
|
|
537
|
+
query(node) {
|
|
538
|
+
return this.name(`${node.operationId} Query`);
|
|
539
|
+
},
|
|
540
|
+
headers(node) {
|
|
541
|
+
return this.name(`${node.operationId} Headers`);
|
|
542
|
+
}
|
|
543
|
+
};
|
|
544
|
+
}
|
|
545
|
+
/**
|
|
546
|
+
* Builds the shared `response` namespace. Spread the result into
|
|
547
|
+
* `createResolver` and add plugin-specific methods (`options`, `error`) next
|
|
548
|
+
* to it.
|
|
549
|
+
*
|
|
550
|
+
* @example
|
|
551
|
+
* ```ts
|
|
552
|
+
* createResolver<PluginTs>({ response: { ...createOperationResponseResolver(), options(node) {...} }, ... })
|
|
553
|
+
* ```
|
|
554
|
+
*/
|
|
555
|
+
function createOperationResponseResolver() {
|
|
556
|
+
return {
|
|
557
|
+
status(node, statusCode) {
|
|
558
|
+
return this.name(`${node.operationId} Status ${statusCode}`);
|
|
559
|
+
},
|
|
560
|
+
body(node) {
|
|
561
|
+
return this.name(`${node.operationId} Body`);
|
|
562
|
+
},
|
|
563
|
+
responses(node) {
|
|
564
|
+
return this.name(`${node.operationId} Responses`);
|
|
565
|
+
},
|
|
566
|
+
response(node) {
|
|
567
|
+
return this.name(`${node.operationId} Response`);
|
|
568
|
+
}
|
|
569
|
+
};
|
|
570
|
+
}
|
|
571
|
+
/**
|
|
572
|
+
* Builds a resolver `file` override whose base name runs every path segment
|
|
573
|
+
* through `toFilePath`, casing the final segment with `caseLast`.
|
|
574
|
+
*
|
|
575
|
+
* @example
|
|
576
|
+
* ```ts
|
|
577
|
+
* createResolver<PluginTs>({ file: createCasedFile(pascalCase), ... })
|
|
578
|
+
* ```
|
|
579
|
+
*/
|
|
580
|
+
function createCasedFile(caseLast) {
|
|
581
|
+
return { baseName({ name, extname }) {
|
|
582
|
+
return `${toFilePath(name, caseLast)}${extname}`;
|
|
583
|
+
} };
|
|
584
|
+
}
|
|
585
|
+
//#endregion
|
|
586
|
+
//#region ../../internals/shared/src/refs.ts
|
|
587
|
+
/**
|
|
588
|
+
* Collects the resolved target name of every pointer-carrying ref in a schema tree, in
|
|
589
|
+
* first-occurrence order. Use this for name-only checks (e.g. redeclaration detection) where
|
|
590
|
+
* `resolver.imports` would resolve file paths that are then discarded.
|
|
591
|
+
*/
|
|
592
|
+
function collectRefNames(schema) {
|
|
593
|
+
return ast.collectSync(schema, { schema: (node) => {
|
|
594
|
+
const refNode = ast.narrowSchema(node, "ref");
|
|
595
|
+
if (!refNode?.ref) return null;
|
|
596
|
+
return ast.resolveRefName(refNode);
|
|
597
|
+
} });
|
|
598
|
+
}
|
|
599
|
+
//#endregion
|
|
600
|
+
//#region ../../internals/shared/src/group.ts
|
|
601
|
+
/**
|
|
602
|
+
* Builds the `group` config a Kubb plugin passes to `ctx.setOptions`, applying the
|
|
603
|
+
* shared default naming so every plugin groups output consistently:
|
|
604
|
+
*
|
|
605
|
+
* - `path` groups use the second path segment (`/pet/findByStatus` → `pet`).
|
|
606
|
+
* - other groups use the camelCased group (`pet store` → `petStore`).
|
|
607
|
+
*
|
|
608
|
+
* A user-provided `group.name` always wins over the default namer, so callers stay in
|
|
609
|
+
* control of their output folders. Returns `null` when grouping is disabled, matching the
|
|
610
|
+
* per-plugin convention.
|
|
611
|
+
*
|
|
612
|
+
* @param group - The user-supplied group option, or `undefined` to disable grouping.
|
|
613
|
+
*
|
|
614
|
+
* @example
|
|
615
|
+
* ```ts
|
|
616
|
+
* createGroupConfig(group) // shared across every plugin
|
|
617
|
+
* ```
|
|
618
|
+
*/
|
|
619
|
+
function createGroupConfig(group) {
|
|
620
|
+
if (!group) return null;
|
|
621
|
+
const defaultName = (ctx) => {
|
|
622
|
+
if (group.type === "path") return `${ctx.group.split("/")[1]}`;
|
|
623
|
+
return camelCase(ctx.group);
|
|
624
|
+
};
|
|
625
|
+
return {
|
|
626
|
+
...group,
|
|
627
|
+
name: group.name ? group.name : defaultName
|
|
628
|
+
};
|
|
629
|
+
}
|
|
630
|
+
//#endregion
|
|
631
|
+
//#region ../../internals/shared/src/schemaTraversal.ts
|
|
632
|
+
/**
|
|
633
|
+
* Maps each property of an object schema to its transformed output. Pairs every result with the
|
|
634
|
+
* original property so the printer keeps full control over modifiers, getters, and key syntax.
|
|
635
|
+
*
|
|
636
|
+
* @example
|
|
637
|
+
* ```ts
|
|
638
|
+
* const entries = mapSchemaProperties(node, (schema) => this.transform(schema))
|
|
639
|
+
* // entries: [{ name: 'id', property, output: 'z.number()' }, ...]
|
|
640
|
+
* ```
|
|
641
|
+
*/
|
|
642
|
+
function mapSchemaProperties(node, transform) {
|
|
643
|
+
return node.properties.map((property) => ({
|
|
644
|
+
name: property.name,
|
|
645
|
+
property,
|
|
646
|
+
output: transform(property.schema)
|
|
647
|
+
}));
|
|
648
|
+
}
|
|
649
|
+
/**
|
|
650
|
+
* Maps each member of a union or intersection schema to its transformed output, pairing every
|
|
651
|
+
* result with the original member.
|
|
652
|
+
*/
|
|
653
|
+
function mapSchemaMembers(node, transform) {
|
|
654
|
+
return (node.members ?? []).map((schema) => ({
|
|
655
|
+
schema,
|
|
656
|
+
output: transform(schema)
|
|
657
|
+
}));
|
|
658
|
+
}
|
|
659
|
+
/**
|
|
660
|
+
* Maps each item of an array or tuple schema to its transformed output, pairing every result with
|
|
661
|
+
* the original item.
|
|
662
|
+
*/
|
|
663
|
+
function mapSchemaItems(node, transform) {
|
|
664
|
+
return (node.items ?? []).map((schema) => ({
|
|
665
|
+
schema,
|
|
666
|
+
output: transform(schema)
|
|
667
|
+
}));
|
|
200
668
|
}
|
|
201
669
|
//#endregion
|
|
202
670
|
//#region src/components/Zod.tsx
|
|
203
|
-
function Zod({ name, node, printer, inferTypeName }) {
|
|
671
|
+
function Zod({ name, node, printer, inferTypeName, cyclic }) {
|
|
204
672
|
const output = printer.print(node);
|
|
205
673
|
if (!output) return;
|
|
674
|
+
const needsAnnotation = cyclic && node.type !== "object";
|
|
206
675
|
return /* @__PURE__ */ jsxs(Fragment, { children: [/* @__PURE__ */ jsx(File.Source, {
|
|
207
676
|
name,
|
|
208
677
|
isExportable: true,
|
|
@@ -210,6 +679,7 @@ function Zod({ name, node, printer, inferTypeName }) {
|
|
|
210
679
|
children: /* @__PURE__ */ jsx(Const, {
|
|
211
680
|
export: true,
|
|
212
681
|
name,
|
|
682
|
+
type: needsAnnotation ? "z.ZodType" : void 0,
|
|
213
683
|
children: output
|
|
214
684
|
})
|
|
215
685
|
}), inferTypeName && /* @__PURE__ */ jsx(File.Source, {
|
|
@@ -230,7 +700,7 @@ function Zod({ name, node, printer, inferTypeName }) {
|
|
|
230
700
|
* Import paths that use a namespace import (`import * as z from '...'`).
|
|
231
701
|
* All other import paths use a named import (`import { z } from '...'`).
|
|
232
702
|
*/
|
|
233
|
-
const ZOD_NAMESPACE_IMPORTS = new Set(["zod", "zod/mini"]);
|
|
703
|
+
const ZOD_NAMESPACE_IMPORTS = /* @__PURE__ */ new Set(["zod", "zod/mini"]);
|
|
234
704
|
//#endregion
|
|
235
705
|
//#region src/utils.ts
|
|
236
706
|
/**
|
|
@@ -242,34 +712,106 @@ function shouldCoerce(coercion, type) {
|
|
|
242
712
|
return !!coercion[type];
|
|
243
713
|
}
|
|
244
714
|
/**
|
|
245
|
-
*
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
715
|
+
* Registered codecs, checked in order.
|
|
716
|
+
*/
|
|
717
|
+
const codecs = [{
|
|
718
|
+
matches(node) {
|
|
719
|
+
return node.type === "date" && node.representation === "date";
|
|
720
|
+
},
|
|
721
|
+
decode(node) {
|
|
722
|
+
return node.format === "date" ? "z.iso.date().transform((value) => new Date(value))" : "z.iso.datetime().transform((value) => new Date(value))";
|
|
723
|
+
},
|
|
724
|
+
encode(node) {
|
|
725
|
+
return node.format === "date" ? "z.date().transform((value) => value.toISOString().slice(0, 10))" : "z.date().transform((value) => value.toISOString())";
|
|
726
|
+
}
|
|
727
|
+
}];
|
|
728
|
+
/**
|
|
729
|
+
* Returns the codec for this node, or `undefined` when the node needs no
|
|
730
|
+
* encode/decode (its wire and runtime types match).
|
|
731
|
+
*/
|
|
732
|
+
function getCodec(node) {
|
|
733
|
+
if (!node) return void 0;
|
|
734
|
+
return codecs.find((codec) => codec.matches(node));
|
|
735
|
+
}
|
|
736
|
+
/**
|
|
737
|
+
* Returns `true` when the node itself is encoded/decoded by a codec.
|
|
738
|
+
*/
|
|
739
|
+
function hasCodec(node) {
|
|
740
|
+
return getCodec(node) !== void 0;
|
|
741
|
+
}
|
|
742
|
+
/**
|
|
743
|
+
* Returns `true` when the schema transitively contains a codec node —
|
|
744
|
+
* a value whose runtime type differs from its wire type (see {@link hasCodec}),
|
|
745
|
+
* so it must be decoded (response) or encoded (request) at the validation boundary.
|
|
746
|
+
* `$ref`s are followed via their resolved schema; a `seen` set guards cycles.
|
|
747
|
+
*/
|
|
748
|
+
function containsCodec(node, seen = /* @__PURE__ */ new Set()) {
|
|
749
|
+
if (!node) return false;
|
|
750
|
+
if (hasCodec(node)) return true;
|
|
751
|
+
if (node.type === "ref") {
|
|
752
|
+
if (!node.ref) return false;
|
|
753
|
+
const refName = extractRefName(node.ref);
|
|
754
|
+
if (refName) {
|
|
755
|
+
if (seen.has(refName)) return false;
|
|
756
|
+
seen.add(refName);
|
|
260
757
|
}
|
|
758
|
+
const resolved = syncSchemaRef(node);
|
|
759
|
+
if (resolved.type === "ref") return false;
|
|
760
|
+
return containsCodec(resolved, seen);
|
|
261
761
|
}
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
762
|
+
const children = [];
|
|
763
|
+
if ("properties" in node && node.properties) children.push(...node.properties.map((prop) => prop.schema));
|
|
764
|
+
if ("items" in node && node.items) children.push(...node.items);
|
|
765
|
+
if ("members" in node && node.members) children.push(...node.members);
|
|
766
|
+
if ("additionalProperties" in node && node.additionalProperties && node.additionalProperties !== true) children.push(node.additionalProperties);
|
|
767
|
+
return children.some((child) => containsCodec(child, seen));
|
|
768
|
+
}
|
|
769
|
+
/**
|
|
770
|
+
* Collects the names of `$ref` schemas that transitively contain a codec, so the generator can route
|
|
771
|
+
* them to their input (encode) variant.
|
|
772
|
+
*/
|
|
773
|
+
function collectCodecRefNames(node) {
|
|
774
|
+
return ast.collectSync(node, { schema: (n) => n.type === "ref" && n.ref && containsCodec(n) ? ast.resolveRefName(n) ?? void 0 : void 0 });
|
|
775
|
+
}
|
|
776
|
+
/**
|
|
777
|
+
* Whether the node is a plain inline object whose shape can be lifted into an `.extend({ … })`
|
|
778
|
+
* argument. A catchall, `patternProperties`, or a nullable/optional wrapper cannot, so those stay
|
|
779
|
+
* on `.and(…)`.
|
|
780
|
+
*/
|
|
781
|
+
function isPlainInlineObject(node) {
|
|
782
|
+
return node.type === "object" && !node.nullable && !node.optional && !node.nullish && node.additionalProperties === void 0 && !node.patternProperties;
|
|
783
|
+
}
|
|
784
|
+
/**
|
|
785
|
+
* Whether a node renders as a bare Zod object — one that accepts `.extend(…)` and can be a
|
|
786
|
+
* `z.discriminatedUnion` option. Covers object nodes, `$ref`s that resolve to (or are still
|
|
787
|
+
* unresolved) objects, single-member unions of an object, and object-composable `allOf`.
|
|
788
|
+
*
|
|
789
|
+
* `cyclicSchemas` bounds the recursion: a ref into a cycle renders as `z.lazy(…)`, not an object.
|
|
790
|
+
*/
|
|
791
|
+
function isObjectSchemaNode(node, cyclicSchemas) {
|
|
792
|
+
if (node.nullable || node.optional || node.nullish) return false;
|
|
793
|
+
if (node.type === "object") return true;
|
|
794
|
+
if (node.type === "ref") {
|
|
795
|
+
const refName = ast.resolveRefName(node);
|
|
796
|
+
if (refName && cyclicSchemas?.has(refName)) return false;
|
|
797
|
+
const resolved = syncSchemaRef(node);
|
|
798
|
+
return resolved.type === "ref" || isObjectSchemaNode(resolved, cyclicSchemas);
|
|
799
|
+
}
|
|
800
|
+
if (node.type === "union") {
|
|
801
|
+
const members = node.members ?? [];
|
|
802
|
+
return members.length === 1 && isObjectSchemaNode(members[0], cyclicSchemas);
|
|
803
|
+
}
|
|
804
|
+
return isObjectComposableIntersection(node, cyclicSchemas);
|
|
805
|
+
}
|
|
806
|
+
/**
|
|
807
|
+
* Whether an `allOf` is a pure object composition — an object base plus inline object members whose
|
|
808
|
+
* shapes merge with `.extend(…)`. These stay a Zod object instead of a `ZodIntersection`, which
|
|
809
|
+
* `z.discriminatedUnion` rejects.
|
|
810
|
+
*/
|
|
811
|
+
function isObjectComposableIntersection(node, cyclicSchemas) {
|
|
812
|
+
if (node.type !== "intersection") return false;
|
|
813
|
+
const [first, ...rest] = node.members ?? [];
|
|
814
|
+
return !!first && isObjectSchemaNode(first, cyclicSchemas) && rest.every(isPlainInlineObject);
|
|
273
815
|
}
|
|
274
816
|
/**
|
|
275
817
|
* Format a default value as a code-level literal.
|
|
@@ -281,6 +823,33 @@ function formatDefault(value) {
|
|
|
281
823
|
return String(value ?? "");
|
|
282
824
|
}
|
|
283
825
|
/**
|
|
826
|
+
* Format a default for `.default(...)` so the literal matches the generated schema's type.
|
|
827
|
+
*
|
|
828
|
+
* An OpenAPI `default` does not always agree with the schema it sits on: a `bigint` field
|
|
829
|
+
* (`format: int64`) carries a `number` default, and a spec may put `default: {}` on an `array`.
|
|
830
|
+
* Emitting those verbatim produces a Zod schema that does not typecheck, so coerce the bigint case
|
|
831
|
+
* to a `BigInt(...)` literal and emit an array literal for arrays (dropping a non-array default).
|
|
832
|
+
* Returns `null` when no `.default(...)` should be emitted. Keys off the schema node's type.
|
|
833
|
+
*/
|
|
834
|
+
function defaultLiteral(node, value) {
|
|
835
|
+
if (value === null) return null;
|
|
836
|
+
if (node && ast.narrowSchema(node, "bigint")) {
|
|
837
|
+
if (typeof value === "bigint") return `BigInt(${value})`;
|
|
838
|
+
if (typeof value === "number" && Number.isInteger(value)) return `BigInt(${value})`;
|
|
839
|
+
return null;
|
|
840
|
+
}
|
|
841
|
+
if (node && ast.narrowSchema(node, "array")) return Array.isArray(value) ? JSON.stringify(value) : null;
|
|
842
|
+
const enumNode = node ? ast.narrowSchema(node, "enum") : void 0;
|
|
843
|
+
if (enumNode) {
|
|
844
|
+
const values = enumNode.namedEnumValues?.map((member) => member.value) ?? enumNode.enumValues ?? [];
|
|
845
|
+
if (values.length) {
|
|
846
|
+
const match = values.find((member) => member === value || String(member) === String(value));
|
|
847
|
+
return match != null ? formatLiteral(match) : null;
|
|
848
|
+
}
|
|
849
|
+
}
|
|
850
|
+
return formatDefault(value);
|
|
851
|
+
}
|
|
852
|
+
/**
|
|
284
853
|
* Format a primitive enum/literal value.
|
|
285
854
|
* Strings are quoted; numbers and booleans are emitted raw.
|
|
286
855
|
*/
|
|
@@ -289,6 +858,47 @@ function formatLiteral(v) {
|
|
|
289
858
|
return String(v);
|
|
290
859
|
}
|
|
291
860
|
/**
|
|
861
|
+
* Build the Zod schema for a set of literal enum values.
|
|
862
|
+
* `z.enum()` only accepts string members in Zod v4, so a numeric, boolean, or
|
|
863
|
+
* mixed set is emitted as a single `z.literal(…)` or a `z.union([z.literal(…), …])`.
|
|
864
|
+
* An all-string set keeps the more compact `z.enum([…])`.
|
|
865
|
+
*/
|
|
866
|
+
function buildEnum(values) {
|
|
867
|
+
if (values.every((v) => typeof v === "string")) return `z.enum([${values.map(formatLiteral).join(", ")}])`;
|
|
868
|
+
const literals = values.map((v) => `z.literal(${formatLiteral(v)})`);
|
|
869
|
+
if (literals.length === 1) return literals[0];
|
|
870
|
+
return `z.union([${literals.join(", ")}])`;
|
|
871
|
+
}
|
|
872
|
+
/**
|
|
873
|
+
* Map a `regexType` to the `func` argument of `toRegExpString`: `'constructor'` emits
|
|
874
|
+
* `new RegExp(...)`, while `'literal'` (the default) emits a regex literal.
|
|
875
|
+
*/
|
|
876
|
+
function regexFunc(regexType) {
|
|
877
|
+
return regexType === "constructor" ? "RegExp" : null;
|
|
878
|
+
}
|
|
879
|
+
/**
|
|
880
|
+
* Builds a Zod record key schema that enforces the `patternProperties` regexes a plain
|
|
881
|
+
* `.catchall()` would drop. Several patterns combine into one alternation (`(^a)|(^b)`).
|
|
882
|
+
*/
|
|
883
|
+
function patternKeySchema({ patterns, regexType }) {
|
|
884
|
+
return `z.string().regex(${patternKeySource({
|
|
885
|
+
patterns,
|
|
886
|
+
regexType
|
|
887
|
+
})})`;
|
|
888
|
+
}
|
|
889
|
+
/**
|
|
890
|
+
* `zod/mini` variant of {@link patternKeySchema}, wrapping the regex in `.check(z.regex(...))`.
|
|
891
|
+
*/
|
|
892
|
+
function patternKeySchemaMini({ patterns, regexType }) {
|
|
893
|
+
return `z.string().check(z.regex(${patternKeySource({
|
|
894
|
+
patterns,
|
|
895
|
+
regexType
|
|
896
|
+
})}))`;
|
|
897
|
+
}
|
|
898
|
+
function patternKeySource({ patterns, regexType }) {
|
|
899
|
+
return toRegExpString(patterns.length === 1 ? patterns[0] : patterns.map((pattern) => `(${pattern})`).join("|"), regexFunc(regexType));
|
|
900
|
+
}
|
|
901
|
+
/**
|
|
292
902
|
* Build `.min()` / `.max()` / `.gt()` / `.lt()` constraint chains for numbers
|
|
293
903
|
* using the standard chainable Zod v4 API.
|
|
294
904
|
*/
|
|
@@ -305,11 +915,11 @@ function numberConstraints({ min, max, exclusiveMinimum, exclusiveMaximum, multi
|
|
|
305
915
|
* Build `.min()` / `.max()` / `.regex()` chains for strings/arrays
|
|
306
916
|
* using the standard chainable Zod v4 API.
|
|
307
917
|
*/
|
|
308
|
-
function lengthConstraints({ min, max, pattern }) {
|
|
918
|
+
function lengthConstraints({ min, max, pattern, regexType }) {
|
|
309
919
|
return [
|
|
310
920
|
min !== void 0 ? `.min(${min})` : "",
|
|
311
921
|
max !== void 0 ? `.max(${max})` : "",
|
|
312
|
-
pattern !== void 0 ? `.regex(${toRegExpString(pattern,
|
|
922
|
+
pattern !== void 0 ? `.regex(${toRegExpString(pattern, regexFunc(regexType))})` : ""
|
|
313
923
|
].join("");
|
|
314
924
|
}
|
|
315
925
|
/**
|
|
@@ -327,52 +937,140 @@ function numberChecksMini({ min, max, exclusiveMinimum, exclusiveMaximum, multip
|
|
|
327
937
|
/**
|
|
328
938
|
* Build `.check(z.minLength(), z.maxLength(), z.regex())` for `zod/mini` length constraints.
|
|
329
939
|
*/
|
|
330
|
-
function lengthChecksMini({ min, max, pattern }) {
|
|
940
|
+
function lengthChecksMini({ min, max, pattern, regexType }) {
|
|
331
941
|
const checks = [];
|
|
332
942
|
if (min !== void 0) checks.push(`z.minLength(${min})`);
|
|
333
943
|
if (max !== void 0) checks.push(`z.maxLength(${max})`);
|
|
334
|
-
if (pattern !== void 0) checks.push(`z.regex(${toRegExpString(pattern,
|
|
944
|
+
if (pattern !== void 0) checks.push(`z.regex(${toRegExpString(pattern, regexFunc(regexType))})`);
|
|
335
945
|
return checks.length ? `.check(${checks.join(", ")})` : "";
|
|
336
946
|
}
|
|
337
947
|
/**
|
|
338
|
-
* Apply nullable / optional / nullish modifiers
|
|
339
|
-
* to a schema value string using the chainable Zod v4 API.
|
|
948
|
+
* Apply nullable / optional / nullish modifiers, an optional `.describe()` call, and an
|
|
949
|
+
* optional `.meta({ examples })` call to a schema value string using the chainable Zod v4 API.
|
|
340
950
|
*/
|
|
341
|
-
function applyModifiers({ value, nullable, optional, nullish, defaultValue, description }) {
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
951
|
+
function applyModifiers({ value, schema, nullable, optional, nullish, defaultValue, description, examples }) {
|
|
952
|
+
const withModifier = (() => {
|
|
953
|
+
if (nullish || nullable && optional) return `${value}.nullish()`;
|
|
954
|
+
if (optional) return `${value}.optional()`;
|
|
955
|
+
if (nullable) return `${value}.nullable()`;
|
|
956
|
+
return value;
|
|
957
|
+
})();
|
|
958
|
+
const literal = defaultValue !== void 0 ? defaultLiteral(schema, defaultValue) : null;
|
|
959
|
+
const withDefault = literal !== null ? `${withModifier}.default(${literal})` : withModifier;
|
|
960
|
+
const withDescription = description ? `${withDefault}.describe(${stringify(description)})` : withDefault;
|
|
961
|
+
return examples?.length ? `${withDescription}.meta({ examples: [${examples.map(formatDefault).join(", ")}] })` : withDescription;
|
|
962
|
+
}
|
|
963
|
+
function modifierDepth(schema) {
|
|
964
|
+
if (schema.nullish || schema.nullable && schema.optional) return 2;
|
|
965
|
+
if (schema.nullable || schema.optional) return 1;
|
|
966
|
+
return 0;
|
|
967
|
+
}
|
|
968
|
+
/**
|
|
969
|
+
* Build the `.unwrap()` chain to insert before `.omit()` when the schema is a `$ref`.
|
|
970
|
+
*
|
|
971
|
+
* A `$ref` resolves to a named schema variable that already carries its own `.nullable()` /
|
|
972
|
+
* `.optional()` / `.nullish()` / `.default()` wrappers. `.omit()` lives on the inner `ZodObject`,
|
|
973
|
+
* not on those `ZodNullable` / `ZodOptional` / `ZodDefault` wrappers, so the omit has to unwrap
|
|
974
|
+
* down to the object first. This mirrors plugin-ts emitting `Omit<NonNullable<T>, …>`. Inline
|
|
975
|
+
* objects need no unwrap because the printer adds their modifiers after `.omit()`.
|
|
976
|
+
*/
|
|
977
|
+
function omitUnwrapChain(node) {
|
|
978
|
+
const ref = ast.narrowSchema(node, "ref");
|
|
979
|
+
if (!ref) return "";
|
|
980
|
+
const target = ref.schema ?? ref;
|
|
981
|
+
const depth = modifierDepth(target) + (target.default !== void 0 ? 1 : 0);
|
|
982
|
+
return ".unwrap()".repeat(depth);
|
|
349
983
|
}
|
|
350
984
|
/**
|
|
351
985
|
* Apply nullable / optional / nullish modifiers using the functional `zod/mini` API
|
|
352
986
|
* (`z.nullable()`, `z.optional()`, `z.nullish()`).
|
|
353
987
|
*/
|
|
354
|
-
function applyMiniModifiers({ value, nullable, optional, nullish, defaultValue }) {
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
988
|
+
function applyMiniModifiers({ value, schema, nullable, optional, nullish, defaultValue }) {
|
|
989
|
+
const withModifier = (() => {
|
|
990
|
+
if (nullish) return `z.nullish(${value})`;
|
|
991
|
+
const withNullable = nullable ? `z.nullable(${value})` : value;
|
|
992
|
+
return optional ? `z.optional(${withNullable})` : withNullable;
|
|
993
|
+
})();
|
|
994
|
+
const literal = defaultValue !== void 0 ? defaultLiteral(schema, defaultValue) : null;
|
|
995
|
+
return literal !== null ? `z._default(${withModifier}, ${literal})` : withModifier;
|
|
996
|
+
}
|
|
997
|
+
/**
|
|
998
|
+
* Builds an `object` schema node grouping the given parameter nodes.
|
|
999
|
+
* The `primitive: 'object'` marker ensures the Zod printer emits `z.object(…)` rather than a record.
|
|
1000
|
+
*/
|
|
1001
|
+
function buildGroupedParamsSchema({ params, optional }) {
|
|
1002
|
+
return ast.factory.createSchema({
|
|
1003
|
+
type: "object",
|
|
1004
|
+
optional,
|
|
1005
|
+
primitive: "object",
|
|
1006
|
+
properties: params.map((param) => ast.factory.createProperty({
|
|
1007
|
+
name: param.name,
|
|
1008
|
+
required: param.required,
|
|
1009
|
+
schema: param.schema
|
|
1010
|
+
}))
|
|
1011
|
+
});
|
|
363
1012
|
}
|
|
364
1013
|
//#endregion
|
|
365
1014
|
//#region src/printers/printerZod.ts
|
|
366
|
-
function strictOneOfMember$1(member, node) {
|
|
1015
|
+
function strictOneOfMember$1(member, node, cyclicSchemas) {
|
|
367
1016
|
if (node.type === "object" && node.additionalProperties === void 0) return `${member}.strict()`;
|
|
368
1017
|
if (node.type === "ref") {
|
|
369
1018
|
if (member.startsWith("z.lazy(")) return member;
|
|
370
|
-
const
|
|
1019
|
+
const refName = ast.resolveRefName(node);
|
|
1020
|
+
if (refName && cyclicSchemas?.has(refName)) return member;
|
|
1021
|
+
const schema = syncSchemaRef(node);
|
|
1022
|
+
if (schema.nullable || schema.optional || node.nullable || node.optional) return member;
|
|
371
1023
|
if (schema.type === "object" && (schema.additionalProperties === void 0 || schema.additionalProperties === false)) return `${member}.strict()`;
|
|
372
1024
|
}
|
|
373
1025
|
return member;
|
|
374
1026
|
}
|
|
375
1027
|
__name(strictOneOfMember$1, "strictOneOfMember");
|
|
1028
|
+
function getMemberConstraint({ member, regexType }) {
|
|
1029
|
+
if (member.primitive === "string") return lengthConstraints({
|
|
1030
|
+
...ast.narrowSchema(member, "string") ?? {},
|
|
1031
|
+
regexType
|
|
1032
|
+
}) || void 0;
|
|
1033
|
+
if (member.primitive === "number" || member.primitive === "integer") return numberConstraints(ast.narrowSchema(member, "number") ?? ast.narrowSchema(member, "integer") ?? {}) || void 0;
|
|
1034
|
+
if (member.primitive === "array") return lengthConstraints({
|
|
1035
|
+
...ast.narrowSchema(member, "array") ?? {},
|
|
1036
|
+
regexType
|
|
1037
|
+
}) || void 0;
|
|
1038
|
+
}
|
|
1039
|
+
/**
|
|
1040
|
+
* Builds the `{ key: value, … }` shape for an object node, shared by the `z.object(...)` and
|
|
1041
|
+
* `.extend(...)` renderings so they stay in lockstep.
|
|
1042
|
+
*/
|
|
1043
|
+
function buildZodObjectShape(ctx, node) {
|
|
1044
|
+
const objectNode = ast.narrowSchema(node, "object");
|
|
1045
|
+
if (!objectNode) return "{}";
|
|
1046
|
+
const isCyclic = (schema) => ctx.options.cyclicSchemas != null && containsCircularRef(schema, { circularSchemas: ctx.options.cyclicSchemas });
|
|
1047
|
+
return buildObject(mapSchemaProperties(objectNode, (schema) => {
|
|
1048
|
+
const hasSelfRef = isCyclic(schema);
|
|
1049
|
+
const savedCyclicSchemas = ctx.options.cyclicSchemas;
|
|
1050
|
+
if (hasSelfRef) ctx.options.cyclicSchemas = void 0;
|
|
1051
|
+
const baseOutput = ctx.transform(schema) ?? ctx.transform(ast.factory.createSchema({ type: "unknown" }));
|
|
1052
|
+
if (hasSelfRef) ctx.options.cyclicSchemas = savedCyclicSchemas;
|
|
1053
|
+
return baseOutput;
|
|
1054
|
+
}).map(({ name: propName, property, output: baseOutput }) => {
|
|
1055
|
+
const { schema } = property;
|
|
1056
|
+
const meta = syncSchemaRef(schema);
|
|
1057
|
+
const descriptionToApply = schema.type !== "ref" && meta.type === "ref" ? void 0 : meta.description;
|
|
1058
|
+
const value = applyModifiers({
|
|
1059
|
+
value: baseOutput,
|
|
1060
|
+
schema,
|
|
1061
|
+
nullable: meta.nullable,
|
|
1062
|
+
optional: schema.optional || property.required === false,
|
|
1063
|
+
nullish: schema.nullish,
|
|
1064
|
+
defaultValue: meta.default,
|
|
1065
|
+
description: descriptionToApply,
|
|
1066
|
+
examples: meta.examples
|
|
1067
|
+
});
|
|
1068
|
+
return isCyclic(schema) ? lazyGetter({
|
|
1069
|
+
name: propName,
|
|
1070
|
+
body: value
|
|
1071
|
+
}) : `${objectKey(propName)}: ${value}`;
|
|
1072
|
+
}));
|
|
1073
|
+
}
|
|
376
1074
|
/**
|
|
377
1075
|
* Zod v4 printer built with `definePrinter`.
|
|
378
1076
|
*
|
|
@@ -385,7 +1083,8 @@ __name(strictOneOfMember$1, "strictOneOfMember");
|
|
|
385
1083
|
* const code = printer.print(stringNode) // "z.string()"
|
|
386
1084
|
* ```
|
|
387
1085
|
*/
|
|
388
|
-
const printerZod = ast.
|
|
1086
|
+
const printerZod = ast.createPrinter((options) => {
|
|
1087
|
+
const cyclicSchemaNames = options.cyclicSchemas;
|
|
389
1088
|
return {
|
|
390
1089
|
name: "zod",
|
|
391
1090
|
options,
|
|
@@ -397,7 +1096,10 @@ const printerZod = ast.definePrinter((options) => {
|
|
|
397
1096
|
boolean: () => "z.boolean()",
|
|
398
1097
|
null: () => "z.null()",
|
|
399
1098
|
string(node) {
|
|
400
|
-
return `${shouldCoerce(this.options.coercion, "strings") ? "z.coerce.string()" : "z.string()"}${lengthConstraints(
|
|
1099
|
+
return `${shouldCoerce(this.options.coercion, "strings") ? "z.coerce.string()" : "z.string()"}${lengthConstraints({
|
|
1100
|
+
...node,
|
|
1101
|
+
regexType: this.options.regexType
|
|
1102
|
+
})}`;
|
|
401
1103
|
},
|
|
402
1104
|
number(node) {
|
|
403
1105
|
return `${shouldCoerce(this.options.coercion, "numbers") ? "z.coerce.number()" : "z.number()"}${numberConstraints(node)}`;
|
|
@@ -409,8 +1111,12 @@ const printerZod = ast.definePrinter((options) => {
|
|
|
409
1111
|
return shouldCoerce(this.options.coercion, "numbers") ? "z.coerce.bigint()" : "z.bigint()";
|
|
410
1112
|
},
|
|
411
1113
|
date(node) {
|
|
412
|
-
|
|
413
|
-
|
|
1114
|
+
const codec = getCodec(node);
|
|
1115
|
+
if (codec) {
|
|
1116
|
+
if (this.options.direction === "input") return codec.encode(node);
|
|
1117
|
+
return shouldCoerce(this.options.coercion, "dates") ? "z.coerce.date()" : codec.decode(node);
|
|
1118
|
+
}
|
|
1119
|
+
return "z.iso.date()";
|
|
414
1120
|
},
|
|
415
1121
|
datetime(node) {
|
|
416
1122
|
const offset = node.offset || this.options.dateType === "stringOffset";
|
|
@@ -424,13 +1130,22 @@ const printerZod = ast.definePrinter((options) => {
|
|
|
424
1130
|
return shouldCoerce(this.options.coercion, "dates") ? "z.coerce.date()" : "z.date()";
|
|
425
1131
|
},
|
|
426
1132
|
uuid(node) {
|
|
427
|
-
return `${this.options.guidType === "guid" ? "z.guid()" : "z.uuid()"}${lengthConstraints(
|
|
1133
|
+
return `${this.options.guidType === "guid" ? "z.guid()" : "z.uuid()"}${lengthConstraints({
|
|
1134
|
+
...node,
|
|
1135
|
+
regexType: this.options.regexType
|
|
1136
|
+
})}`;
|
|
428
1137
|
},
|
|
429
1138
|
email(node) {
|
|
430
|
-
return `z.email()${lengthConstraints(
|
|
1139
|
+
return `z.email()${lengthConstraints({
|
|
1140
|
+
...node,
|
|
1141
|
+
regexType: this.options.regexType
|
|
1142
|
+
})}`;
|
|
431
1143
|
},
|
|
432
1144
|
url(node) {
|
|
433
|
-
return `z.url()${lengthConstraints(
|
|
1145
|
+
return `z.url()${lengthConstraints({
|
|
1146
|
+
...node,
|
|
1147
|
+
regexType: this.options.regexType
|
|
1148
|
+
})}`;
|
|
434
1149
|
},
|
|
435
1150
|
ipv4: () => "z.ipv4()",
|
|
436
1151
|
ipv6: () => "z.ipv6()",
|
|
@@ -442,120 +1157,104 @@ const printerZod = ast.definePrinter((options) => {
|
|
|
442
1157
|
if (literals.length === 1) return literals[0];
|
|
443
1158
|
return `z.union([${literals.join(", ")}])`;
|
|
444
1159
|
}
|
|
445
|
-
return
|
|
1160
|
+
return buildEnum(nonNullValues);
|
|
446
1161
|
},
|
|
447
1162
|
ref(node) {
|
|
448
|
-
if (!node.name) return
|
|
449
|
-
const refName =
|
|
450
|
-
|
|
1163
|
+
if (!node.name) return null;
|
|
1164
|
+
const refName = ast.resolveRefName(node);
|
|
1165
|
+
if (!refName) return null;
|
|
1166
|
+
const useInputVariant = node.ref != null && this.options.direction === "input" && containsCodec(node);
|
|
1167
|
+
const resolvedName = node.ref ? useInputVariant ? this.options.resolver?.schema.inputName(refName) ?? refName : this.options.resolver?.name(refName) ?? refName : node.name;
|
|
451
1168
|
if (node.ref && this.options.cyclicSchemas?.has(refName)) return `z.lazy(() => ${resolvedName})`;
|
|
452
1169
|
return resolvedName;
|
|
453
1170
|
},
|
|
454
1171
|
object(node) {
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
const
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
if (
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
value
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
return `"${propName}": ${value}`;
|
|
481
|
-
}).join(",\n ")}\n })`;
|
|
482
|
-
if (node.additionalProperties && node.additionalProperties !== true) {
|
|
483
|
-
const catchallType = this.transform(node.additionalProperties);
|
|
484
|
-
if (catchallType) result += `.catchall(${catchallType})`;
|
|
485
|
-
} else if (node.additionalProperties === true) result += `.catchall(${this.transform(ast.createSchema({ type: "unknown" }))})`;
|
|
486
|
-
else if (node.additionalProperties === false) result += ".strict()";
|
|
487
|
-
return result;
|
|
1172
|
+
const entries = node.properties ?? [];
|
|
1173
|
+
const objectBase = `z.object(${buildZodObjectShape(this, node)})`;
|
|
1174
|
+
return (() => {
|
|
1175
|
+
const patterns = node.patternProperties ? Object.entries(node.patternProperties) : [];
|
|
1176
|
+
if (node.additionalProperties && node.additionalProperties !== true) {
|
|
1177
|
+
const catchallType = this.transform(node.additionalProperties);
|
|
1178
|
+
return catchallType ? `${objectBase}.catchall(${catchallType})` : objectBase;
|
|
1179
|
+
}
|
|
1180
|
+
if (node.additionalProperties === true) return `${objectBase}.catchall(${this.transform(ast.factory.createSchema({ type: "unknown" }))})`;
|
|
1181
|
+
if (node.additionalProperties === false && patterns.length === 0) return `${objectBase}.strict()`;
|
|
1182
|
+
if (patterns.length > 0) {
|
|
1183
|
+
const values = patterns.map(([, valueSchema]) => {
|
|
1184
|
+
const valueType = this.transform(valueSchema) ?? this.transform(ast.factory.createSchema({ type: "unknown" }));
|
|
1185
|
+
return valueSchema.nullable ? `${valueType}.nullable()` : valueType;
|
|
1186
|
+
});
|
|
1187
|
+
const distinct = [...new Set(values)];
|
|
1188
|
+
const value = distinct.length === 1 ? distinct[0] : `z.union([${distinct.join(", ")}])`;
|
|
1189
|
+
if (entries.length > 0) return `${objectBase}.catchall(${value})`;
|
|
1190
|
+
return `z.record(${patternKeySchema({
|
|
1191
|
+
patterns: patterns.map(([pattern]) => pattern),
|
|
1192
|
+
regexType: this.options.regexType
|
|
1193
|
+
})}, ${value})`;
|
|
1194
|
+
}
|
|
1195
|
+
return objectBase;
|
|
1196
|
+
})();
|
|
488
1197
|
},
|
|
489
1198
|
array(node) {
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
1199
|
+
const base = `z.array(${mapSchemaItems(node, (item) => this.transform(item)).map(({ output }) => output).filter(Boolean).join(", ") || this.transform(ast.factory.createSchema({ type: "unknown" }))})${lengthConstraints({
|
|
1200
|
+
...node,
|
|
1201
|
+
regexType: this.options.regexType
|
|
1202
|
+
})}`;
|
|
1203
|
+
return node.unique ? `${base}.refine(items => new Set(items).size === items.length, { message: "Array entries must be unique" })` : base;
|
|
493
1204
|
},
|
|
494
1205
|
tuple(node) {
|
|
495
|
-
return `z.tuple(
|
|
1206
|
+
return `z.tuple(${buildList(mapSchemaItems(node, (item) => this.transform(item)).map(({ output }) => output).filter(Boolean))})`;
|
|
496
1207
|
},
|
|
497
1208
|
union(node) {
|
|
498
1209
|
const nodeMembers = node.members ?? [];
|
|
499
|
-
const members =
|
|
500
|
-
const member = this.transform(memberNode);
|
|
501
|
-
return member && node.strategy === "one" ? strictOneOfMember$1(member, memberNode) : member;
|
|
502
|
-
}).filter(Boolean);
|
|
1210
|
+
const members = mapSchemaMembers(node, (memberNode) => this.transform(memberNode)).map(({ schema, output }) => output && node.strategy === "one" ? strictOneOfMember$1(output, schema, cyclicSchemaNames) : output).filter(Boolean);
|
|
503
1211
|
if (members.length === 0) return "";
|
|
504
1212
|
if (members.length === 1) return members[0];
|
|
505
|
-
|
|
506
|
-
return `z.
|
|
1213
|
+
const allDiscriminable = nodeMembers.every((m) => isObjectSchemaNode(m, cyclicSchemaNames));
|
|
1214
|
+
if (node.discriminatorPropertyName && allDiscriminable) return `z.discriminatedUnion(${stringify(node.discriminatorPropertyName)}, ${buildList(members)})`;
|
|
1215
|
+
return `z.union(${buildList(members)})`;
|
|
507
1216
|
},
|
|
508
1217
|
intersection(node) {
|
|
509
1218
|
const members = node.members ?? [];
|
|
510
1219
|
if (members.length === 0) return "";
|
|
511
1220
|
const [first, ...rest] = members;
|
|
512
1221
|
if (!first) return "";
|
|
513
|
-
|
|
514
|
-
if (!
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
} else if (member.primitive === "number" || member.primitive === "integer") {
|
|
523
|
-
const c = numberConstraints(ast.narrowSchema(member, "number") ?? ast.narrowSchema(member, "integer") ?? {});
|
|
524
|
-
if (c) {
|
|
525
|
-
base += c;
|
|
526
|
-
continue;
|
|
527
|
-
}
|
|
528
|
-
} else if (member.primitive === "array") {
|
|
529
|
-
const c = lengthConstraints(ast.narrowSchema(member, "array") ?? {});
|
|
530
|
-
if (c) {
|
|
531
|
-
base += c;
|
|
532
|
-
continue;
|
|
533
|
-
}
|
|
534
|
-
}
|
|
1222
|
+
const firstBase = this.transform(first);
|
|
1223
|
+
if (!firstBase) return "";
|
|
1224
|
+
if (rest.length > 0 && isObjectComposableIntersection(node, cyclicSchemaNames)) return rest.reduce((acc, member) => `${acc}.extend(${buildZodObjectShape(this, member)})`, firstBase);
|
|
1225
|
+
return rest.reduce((acc, member) => {
|
|
1226
|
+
const constraint = getMemberConstraint({
|
|
1227
|
+
member,
|
|
1228
|
+
regexType: this.options.regexType
|
|
1229
|
+
});
|
|
1230
|
+
if (constraint) return acc + constraint;
|
|
535
1231
|
const transformed = this.transform(member);
|
|
536
|
-
|
|
537
|
-
}
|
|
538
|
-
|
|
539
|
-
},
|
|
540
|
-
...options.nodes
|
|
1232
|
+
return transformed ? `${acc}.and(${transformed})` : acc;
|
|
1233
|
+
}, firstBase);
|
|
1234
|
+
}
|
|
541
1235
|
},
|
|
1236
|
+
overrides: options.nodes,
|
|
542
1237
|
print(node) {
|
|
543
1238
|
const { keysToOmit } = this.options;
|
|
544
|
-
|
|
545
|
-
if (!
|
|
546
|
-
const meta =
|
|
547
|
-
if (keysToOmit?.length && meta.primitive === "object" && !(meta.type === "union" && meta.discriminatorPropertyName)) {
|
|
548
|
-
const lazyMatch = base.match(/^z\.lazy\(\(\)\s*=>\s*(.+)\)$/);
|
|
549
|
-
if (lazyMatch) base = `z.lazy(() => ${lazyMatch[1]}.omit({ ${keysToOmit.map((k) => `"${k}": true`).join(", ")} }))`;
|
|
550
|
-
else base = `${base}.omit({ ${keysToOmit.map((k) => `"${k}": true`).join(", ")} })`;
|
|
551
|
-
}
|
|
1239
|
+
const transformed = this.transform(node);
|
|
1240
|
+
if (!transformed) return null;
|
|
1241
|
+
const meta = syncSchemaRef(node);
|
|
552
1242
|
return applyModifiers({
|
|
553
|
-
value:
|
|
1243
|
+
value: (() => {
|
|
1244
|
+
if (!keysToOmit?.length || meta.primitive !== "object" || meta.type === "union" && meta.discriminatorPropertyName) return transformed;
|
|
1245
|
+
const unwrap = omitUnwrapChain(node);
|
|
1246
|
+
const omit = `.omit({ ${keysToOmit.map((k) => `"${k}": true`).join(", ")} })`;
|
|
1247
|
+
const lazyMatch = transformed.match(/^z\.lazy\(\(\)\s*=>\s*(.+)\)$/);
|
|
1248
|
+
if (lazyMatch) return `z.lazy(() => ${lazyMatch[1]}${unwrap}${omit})`;
|
|
1249
|
+
return `${transformed}${unwrap}${omit}`;
|
|
1250
|
+
})(),
|
|
1251
|
+
schema: node,
|
|
554
1252
|
nullable: meta.nullable,
|
|
555
1253
|
optional: meta.optional,
|
|
556
1254
|
nullish: meta.nullish,
|
|
557
1255
|
defaultValue: meta.default,
|
|
558
|
-
description: meta.description
|
|
1256
|
+
description: meta.description,
|
|
1257
|
+
examples: meta.examples
|
|
559
1258
|
});
|
|
560
1259
|
}
|
|
561
1260
|
};
|
|
@@ -566,8 +1265,51 @@ function strictOneOfMember(member, node) {
|
|
|
566
1265
|
if (node.type === "object" && (node.additionalProperties === void 0 || node.additionalProperties === false)) return member.replace(/^z\.object\(/, "z.strictObject(");
|
|
567
1266
|
return member;
|
|
568
1267
|
}
|
|
1268
|
+
function getMemberConstraintMini({ member, regexType }) {
|
|
1269
|
+
if (member.primitive === "string") return lengthChecksMini({
|
|
1270
|
+
...ast.narrowSchema(member, "string") ?? {},
|
|
1271
|
+
regexType
|
|
1272
|
+
}) || void 0;
|
|
1273
|
+
if (member.primitive === "number" || member.primitive === "integer") return numberChecksMini(ast.narrowSchema(member, "number") ?? ast.narrowSchema(member, "integer") ?? {}) || void 0;
|
|
1274
|
+
if (member.primitive === "array") return lengthChecksMini({
|
|
1275
|
+
...ast.narrowSchema(member, "array") ?? {},
|
|
1276
|
+
regexType
|
|
1277
|
+
}) || void 0;
|
|
1278
|
+
}
|
|
569
1279
|
/**
|
|
570
|
-
*
|
|
1280
|
+
* Builds the `{ key: value, … }` shape for an object node with the functional `zod/mini` modifiers,
|
|
1281
|
+
* shared by the `z.object(...)` and `z.extend(...)` renderings so they stay in lockstep.
|
|
1282
|
+
*/
|
|
1283
|
+
function buildZodMiniObjectShape(ctx, node) {
|
|
1284
|
+
const objectNode = ast.narrowSchema(node, "object");
|
|
1285
|
+
if (!objectNode) return "{}";
|
|
1286
|
+
const isCyclic = (schema) => ctx.options.cyclicSchemas != null && containsCircularRef(schema, { circularSchemas: ctx.options.cyclicSchemas });
|
|
1287
|
+
return buildObject(mapSchemaProperties(objectNode, (schema) => {
|
|
1288
|
+
const hasSelfRef = isCyclic(schema);
|
|
1289
|
+
const savedCyclicSchemas = ctx.options.cyclicSchemas;
|
|
1290
|
+
if (hasSelfRef) ctx.options.cyclicSchemas = void 0;
|
|
1291
|
+
const baseOutput = ctx.transform(schema) ?? ctx.transform(ast.factory.createSchema({ type: "unknown" }));
|
|
1292
|
+
if (hasSelfRef) ctx.options.cyclicSchemas = savedCyclicSchemas;
|
|
1293
|
+
return baseOutput;
|
|
1294
|
+
}).map(({ name: propName, property, output: baseOutput }) => {
|
|
1295
|
+
const { schema } = property;
|
|
1296
|
+
const meta = syncSchemaRef(schema);
|
|
1297
|
+
const value = applyMiniModifiers({
|
|
1298
|
+
value: baseOutput,
|
|
1299
|
+
schema,
|
|
1300
|
+
nullable: meta.nullable,
|
|
1301
|
+
optional: schema.optional || property.required === false,
|
|
1302
|
+
nullish: schema.nullish,
|
|
1303
|
+
defaultValue: meta.default
|
|
1304
|
+
});
|
|
1305
|
+
return isCyclic(schema) ? lazyGetter({
|
|
1306
|
+
name: propName,
|
|
1307
|
+
body: value
|
|
1308
|
+
}) : `${objectKey(propName)}: ${value}`;
|
|
1309
|
+
}));
|
|
1310
|
+
}
|
|
1311
|
+
/**
|
|
1312
|
+
* Zod v4 Mini printer built with `definePrinter`.
|
|
571
1313
|
*
|
|
572
1314
|
* Converts a `SchemaNode` AST into a Zod v4 code string using the functional API
|
|
573
1315
|
* (`z.optional(z.string())`) for improved tree-shaking. See {@link printerZod} for the chainable API.
|
|
@@ -578,7 +1320,8 @@ function strictOneOfMember(member, node) {
|
|
|
578
1320
|
* const code = printer.print(optionalStringNode) // "z.optional(z.string())"
|
|
579
1321
|
* ```
|
|
580
1322
|
*/
|
|
581
|
-
const printerZodMini = ast.
|
|
1323
|
+
const printerZodMini = ast.createPrinter((options) => {
|
|
1324
|
+
const cyclicSchemaNames = options.cyclicSchemas;
|
|
582
1325
|
return {
|
|
583
1326
|
name: "zod-mini",
|
|
584
1327
|
options,
|
|
@@ -590,7 +1333,10 @@ const printerZodMini = ast.definePrinter((options) => {
|
|
|
590
1333
|
boolean: () => "z.boolean()",
|
|
591
1334
|
null: () => "z.null()",
|
|
592
1335
|
string(node) {
|
|
593
|
-
return `z.string()${lengthChecksMini(
|
|
1336
|
+
return `z.string()${lengthChecksMini({
|
|
1337
|
+
...node,
|
|
1338
|
+
regexType: this.options.regexType
|
|
1339
|
+
})}`;
|
|
594
1340
|
},
|
|
595
1341
|
number(node) {
|
|
596
1342
|
return `z.number()${numberChecksMini(node)}`;
|
|
@@ -613,13 +1359,22 @@ const printerZodMini = ast.definePrinter((options) => {
|
|
|
613
1359
|
return "z.date()";
|
|
614
1360
|
},
|
|
615
1361
|
uuid(node) {
|
|
616
|
-
return `${this.options.guidType === "guid" ? "z.guid()" : "z.uuid()"}${lengthChecksMini(
|
|
1362
|
+
return `${this.options.guidType === "guid" ? "z.guid()" : "z.uuid()"}${lengthChecksMini({
|
|
1363
|
+
...node,
|
|
1364
|
+
regexType: this.options.regexType
|
|
1365
|
+
})}`;
|
|
617
1366
|
},
|
|
618
1367
|
email(node) {
|
|
619
|
-
return `z.email()${lengthChecksMini(
|
|
1368
|
+
return `z.email()${lengthChecksMini({
|
|
1369
|
+
...node,
|
|
1370
|
+
regexType: this.options.regexType
|
|
1371
|
+
})}`;
|
|
620
1372
|
},
|
|
621
1373
|
url(node) {
|
|
622
|
-
return `z.url()${lengthChecksMini(
|
|
1374
|
+
return `z.url()${lengthChecksMini({
|
|
1375
|
+
...node,
|
|
1376
|
+
regexType: this.options.regexType
|
|
1377
|
+
})}`;
|
|
623
1378
|
},
|
|
624
1379
|
ipv4: () => "z.ipv4()",
|
|
625
1380
|
ipv6: () => "z.ipv6()",
|
|
@@ -631,105 +1386,95 @@ const printerZodMini = ast.definePrinter((options) => {
|
|
|
631
1386
|
if (literals.length === 1) return literals[0];
|
|
632
1387
|
return `z.union([${literals.join(", ")}])`;
|
|
633
1388
|
}
|
|
634
|
-
return
|
|
1389
|
+
return buildEnum(nonNullValues);
|
|
635
1390
|
},
|
|
636
1391
|
ref(node) {
|
|
637
|
-
if (!node.name) return
|
|
638
|
-
const refName =
|
|
639
|
-
|
|
1392
|
+
if (!node.name) return null;
|
|
1393
|
+
const refName = ast.resolveRefName(node);
|
|
1394
|
+
if (!refName) return null;
|
|
1395
|
+
const resolvedName = node.ref ? this.options.resolver?.name(refName) ?? refName : node.name;
|
|
640
1396
|
if (node.ref && this.options.cyclicSchemas?.has(refName)) return `z.lazy(() => ${resolvedName})`;
|
|
641
1397
|
return resolvedName;
|
|
642
1398
|
},
|
|
643
1399
|
object(node) {
|
|
644
|
-
|
|
645
|
-
|
|
646
|
-
|
|
647
|
-
|
|
648
|
-
const
|
|
649
|
-
|
|
650
|
-
|
|
651
|
-
|
|
652
|
-
|
|
653
|
-
|
|
654
|
-
const
|
|
655
|
-
|
|
656
|
-
|
|
657
|
-
schema
|
|
658
|
-
}) || baseOutput : baseOutput,
|
|
659
|
-
nullable: isNullable,
|
|
660
|
-
optional: isOptional,
|
|
661
|
-
nullish: isNullish,
|
|
662
|
-
defaultValue: meta.default
|
|
1400
|
+
const entries = node.properties ?? [];
|
|
1401
|
+
const objectBase = `z.object(${buildZodMiniObjectShape(this, node)})`;
|
|
1402
|
+
const patterns = node.patternProperties ? Object.entries(node.patternProperties) : [];
|
|
1403
|
+
if (node.additionalProperties && node.additionalProperties !== true) {
|
|
1404
|
+
const catchallType = this.transform(node.additionalProperties);
|
|
1405
|
+
return catchallType ? `z.catchall(${objectBase}, ${catchallType})` : objectBase;
|
|
1406
|
+
}
|
|
1407
|
+
if (node.additionalProperties === true) return `z.catchall(${objectBase}, ${this.transform(ast.factory.createSchema({ type: "unknown" }))})`;
|
|
1408
|
+
if (node.additionalProperties === false && patterns.length === 0) return objectBase.replace(/^z\.object\(/, "z.strictObject(");
|
|
1409
|
+
if (patterns.length > 0) {
|
|
1410
|
+
const values = patterns.map(([, valueSchema]) => {
|
|
1411
|
+
const valueType = this.transform(valueSchema) ?? this.transform(ast.factory.createSchema({ type: "unknown" }));
|
|
1412
|
+
return valueSchema.nullable ? `z.nullable(${valueType})` : valueType;
|
|
663
1413
|
});
|
|
664
|
-
|
|
665
|
-
|
|
666
|
-
|
|
1414
|
+
const distinct = [...new Set(values)];
|
|
1415
|
+
const value = distinct.length === 1 ? distinct[0] : `z.union([${distinct.join(", ")}])`;
|
|
1416
|
+
if (entries.length > 0) return `z.catchall(${objectBase}, ${value})`;
|
|
1417
|
+
return `z.record(${patternKeySchemaMini({
|
|
1418
|
+
patterns: patterns.map(([pattern]) => pattern),
|
|
1419
|
+
regexType: this.options.regexType
|
|
1420
|
+
})}, ${value})`;
|
|
1421
|
+
}
|
|
1422
|
+
return objectBase;
|
|
667
1423
|
},
|
|
668
1424
|
array(node) {
|
|
669
|
-
|
|
670
|
-
|
|
671
|
-
|
|
1425
|
+
const base = `z.array(${mapSchemaItems(node, (item) => this.transform(item)).map(({ output }) => output).filter(Boolean).join(", ") || this.transform(ast.factory.createSchema({ type: "unknown" }))})${lengthChecksMini({
|
|
1426
|
+
...node,
|
|
1427
|
+
regexType: this.options.regexType
|
|
1428
|
+
})}`;
|
|
1429
|
+
return node.unique ? `${base}.refine(items => new Set(items).size === items.length, { message: "Array entries must be unique" })` : base;
|
|
672
1430
|
},
|
|
673
1431
|
tuple(node) {
|
|
674
|
-
return `z.tuple(
|
|
1432
|
+
return `z.tuple(${buildList(mapSchemaItems(node, (item) => this.transform(item)).map(({ output }) => output).filter(Boolean))})`;
|
|
675
1433
|
},
|
|
676
1434
|
union(node) {
|
|
677
1435
|
const nodeMembers = node.members ?? [];
|
|
678
|
-
const members =
|
|
679
|
-
const member = this.transform(memberNode);
|
|
680
|
-
return member && node.strategy === "one" ? strictOneOfMember(member, memberNode) : member;
|
|
681
|
-
}).filter(Boolean);
|
|
1436
|
+
const members = mapSchemaMembers(node, (memberNode) => this.transform(memberNode)).map(({ schema, output }) => output && node.strategy === "one" ? strictOneOfMember(output, schema) : output).filter(Boolean);
|
|
682
1437
|
if (members.length === 0) return "";
|
|
683
1438
|
if (members.length === 1) return members[0];
|
|
684
|
-
|
|
685
|
-
return `z.
|
|
1439
|
+
const allDiscriminable = nodeMembers.every((m) => isObjectSchemaNode(m, cyclicSchemaNames));
|
|
1440
|
+
if (node.discriminatorPropertyName && allDiscriminable) return `z.discriminatedUnion(${stringify(node.discriminatorPropertyName)}, ${buildList(members)})`;
|
|
1441
|
+
return `z.union(${buildList(members)})`;
|
|
686
1442
|
},
|
|
687
1443
|
intersection(node) {
|
|
688
1444
|
const members = node.members ?? [];
|
|
689
1445
|
if (members.length === 0) return "";
|
|
690
1446
|
const [first, ...rest] = members;
|
|
691
1447
|
if (!first) return "";
|
|
692
|
-
|
|
693
|
-
if (!
|
|
694
|
-
|
|
695
|
-
|
|
696
|
-
|
|
697
|
-
|
|
698
|
-
|
|
699
|
-
|
|
700
|
-
|
|
701
|
-
} else if (member.primitive === "number" || member.primitive === "integer") {
|
|
702
|
-
const c = numberChecksMini(ast.narrowSchema(member, "number") ?? ast.narrowSchema(member, "integer") ?? {});
|
|
703
|
-
if (c) {
|
|
704
|
-
base += c;
|
|
705
|
-
continue;
|
|
706
|
-
}
|
|
707
|
-
} else if (member.primitive === "array") {
|
|
708
|
-
const c = lengthChecksMini(ast.narrowSchema(member, "array") ?? {});
|
|
709
|
-
if (c) {
|
|
710
|
-
base += c;
|
|
711
|
-
continue;
|
|
712
|
-
}
|
|
713
|
-
}
|
|
1448
|
+
const firstBase = this.transform(first);
|
|
1449
|
+
if (!firstBase) return "";
|
|
1450
|
+
if (rest.length > 0 && isObjectComposableIntersection(node, cyclicSchemaNames)) return rest.reduce((acc, member) => `z.extend(${acc}, ${buildZodMiniObjectShape(this, member)})`, firstBase);
|
|
1451
|
+
return rest.reduce((acc, member) => {
|
|
1452
|
+
const constraint = getMemberConstraintMini({
|
|
1453
|
+
member,
|
|
1454
|
+
regexType: this.options.regexType
|
|
1455
|
+
});
|
|
1456
|
+
if (constraint) return acc + constraint;
|
|
714
1457
|
const transformed = this.transform(member);
|
|
715
|
-
|
|
716
|
-
}
|
|
717
|
-
|
|
718
|
-
},
|
|
719
|
-
...options.nodes
|
|
1458
|
+
return transformed ? `z.intersection(${acc}, ${transformed})` : acc;
|
|
1459
|
+
}, firstBase);
|
|
1460
|
+
}
|
|
720
1461
|
},
|
|
1462
|
+
overrides: options.nodes,
|
|
721
1463
|
print(node) {
|
|
722
1464
|
const { keysToOmit } = this.options;
|
|
723
|
-
|
|
724
|
-
if (!
|
|
725
|
-
const meta =
|
|
726
|
-
if (keysToOmit?.length && meta.primitive === "object" && !(meta.type === "union" && meta.discriminatorPropertyName)) {
|
|
727
|
-
const lazyMatch = base.match(/^z\.lazy\(\(\)\s*=>\s*(.+)\)$/);
|
|
728
|
-
if (lazyMatch) base = `z.lazy(() => ${lazyMatch[1]}.omit({ ${keysToOmit.map((k) => `"${k}": true`).join(", ")} }))`;
|
|
729
|
-
else base = `${base}.omit({ ${keysToOmit.map((k) => `"${k}": true`).join(", ")} })`;
|
|
730
|
-
}
|
|
1465
|
+
const transformed = this.transform(node);
|
|
1466
|
+
if (!transformed) return null;
|
|
1467
|
+
const meta = syncSchemaRef(node);
|
|
731
1468
|
return applyMiniModifiers({
|
|
732
|
-
value:
|
|
1469
|
+
value: (() => {
|
|
1470
|
+
if (!keysToOmit?.length || meta.primitive !== "object" || meta.type === "union" && meta.discriminatorPropertyName) return transformed;
|
|
1471
|
+
const unwrap = omitUnwrapChain(node);
|
|
1472
|
+
const omit = `.omit({ ${keysToOmit.map((k) => `"${k}": true`).join(", ")} })`;
|
|
1473
|
+
const lazyMatch = transformed.match(/^z\.lazy\(\(\)\s*=>\s*(.+)\)$/);
|
|
1474
|
+
if (lazyMatch) return `z.lazy(() => ${lazyMatch[1]}${unwrap}${omit})`;
|
|
1475
|
+
return `${transformed}${unwrap}${omit}`;
|
|
1476
|
+
})(),
|
|
1477
|
+
schema: node,
|
|
733
1478
|
nullable: meta.nullable,
|
|
734
1479
|
optional: meta.optional,
|
|
735
1480
|
nullish: meta.nullish,
|
|
@@ -740,66 +1485,143 @@ const printerZodMini = ast.definePrinter((options) => {
|
|
|
740
1485
|
});
|
|
741
1486
|
//#endregion
|
|
742
1487
|
//#region src/generators/zodGenerator.tsx
|
|
1488
|
+
const zodPrinterCache = /* @__PURE__ */ new WeakMap();
|
|
1489
|
+
const zodMiniPrinterCache = /* @__PURE__ */ new WeakMap();
|
|
1490
|
+
/**
|
|
1491
|
+
* Returns the cached `output`/`input` direction printers for a resolver, building them on
|
|
1492
|
+
* first use. The `input` printer encodes `Date → string` for request bodies, and `output` decodes
|
|
1493
|
+
* `string → Date` for responses. Schemas without `dateType: 'date'` fields print identically.
|
|
1494
|
+
*/
|
|
1495
|
+
function getStdPrinters(resolver, params) {
|
|
1496
|
+
const cached = zodPrinterCache.get(resolver);
|
|
1497
|
+
if (cached && cached.coercion === params.coercion && cached.guidType === params.guidType && cached.regexType === params.regexType && cached.dateType === params.dateType && cached.nodes === params.nodes) return {
|
|
1498
|
+
output: cached.output,
|
|
1499
|
+
input: cached.input
|
|
1500
|
+
};
|
|
1501
|
+
const output = printerZod({
|
|
1502
|
+
...params,
|
|
1503
|
+
resolver,
|
|
1504
|
+
direction: "output"
|
|
1505
|
+
});
|
|
1506
|
+
const input = printerZod({
|
|
1507
|
+
...params,
|
|
1508
|
+
resolver,
|
|
1509
|
+
direction: "input"
|
|
1510
|
+
});
|
|
1511
|
+
zodPrinterCache.set(resolver, {
|
|
1512
|
+
output,
|
|
1513
|
+
input,
|
|
1514
|
+
coercion: params.coercion,
|
|
1515
|
+
guidType: params.guidType,
|
|
1516
|
+
regexType: params.regexType,
|
|
1517
|
+
dateType: params.dateType,
|
|
1518
|
+
nodes: params.nodes
|
|
1519
|
+
});
|
|
1520
|
+
return {
|
|
1521
|
+
output,
|
|
1522
|
+
input
|
|
1523
|
+
};
|
|
1524
|
+
}
|
|
1525
|
+
function getMiniPrinter(resolver, params) {
|
|
1526
|
+
const cached = zodMiniPrinterCache.get(resolver);
|
|
1527
|
+
if (cached && cached.guidType === params.guidType && cached.regexType === params.regexType && cached.nodes === params.nodes) return cached.printer;
|
|
1528
|
+
const p = printerZodMini({
|
|
1529
|
+
...params,
|
|
1530
|
+
resolver
|
|
1531
|
+
});
|
|
1532
|
+
zodMiniPrinterCache.set(resolver, {
|
|
1533
|
+
printer: p,
|
|
1534
|
+
guidType: params.guidType,
|
|
1535
|
+
regexType: params.regexType,
|
|
1536
|
+
nodes: params.nodes
|
|
1537
|
+
});
|
|
1538
|
+
return p;
|
|
1539
|
+
}
|
|
1540
|
+
/**
|
|
1541
|
+
* Built-in generator for `@kubb/plugin-zod`. Emits one Zod schema per
|
|
1542
|
+
* schema in the spec plus per-operation request/response/parameter schemas.
|
|
1543
|
+
* When `mini: true`, schemas use the Zod Mini functional API instead of
|
|
1544
|
+
* chainable methods.
|
|
1545
|
+
*/
|
|
743
1546
|
const zodGenerator = defineGenerator({
|
|
744
1547
|
name: "zod",
|
|
745
1548
|
renderer: jsxRenderer,
|
|
746
1549
|
schema(node, ctx) {
|
|
747
1550
|
const { adapter, config, resolver, root } = ctx;
|
|
748
|
-
const { output, coercion, guidType,
|
|
749
|
-
const dateType = adapter.options.dateType;
|
|
1551
|
+
const { output, coercion, guidType, regexType, mini, inferred, importPath, group, printer } = ctx.options;
|
|
1552
|
+
const dateType = getOasAdapter(adapter).options.dateType;
|
|
750
1553
|
if (!node.name) return;
|
|
751
|
-
const mode = ctx.getMode(output);
|
|
752
1554
|
const isZodImport = ZOD_NAMESPACE_IMPORTS.has(importPath);
|
|
753
|
-
const
|
|
754
|
-
|
|
755
|
-
|
|
1555
|
+
const cyclicSchemas = new Set(ctx.meta.circularNames);
|
|
1556
|
+
const hasCodec = !mini && containsCodec(node);
|
|
1557
|
+
const codecRefNames = new Set(hasCodec ? collectCodecRefNames(node) : []);
|
|
1558
|
+
const importEntries = resolver.imports({
|
|
1559
|
+
node,
|
|
1560
|
+
root,
|
|
1561
|
+
output,
|
|
1562
|
+
group: group ?? void 0
|
|
1563
|
+
});
|
|
1564
|
+
const inputImportEntries = hasCodec ? [...codecRefNames].map((schemaName) => ({
|
|
1565
|
+
name: [resolver.schema.inputName(schemaName)],
|
|
1566
|
+
path: resolver.file({
|
|
756
1567
|
name: schemaName,
|
|
757
|
-
extname: ".ts"
|
|
758
|
-
}, {
|
|
1568
|
+
extname: ".ts",
|
|
759
1569
|
root,
|
|
760
1570
|
output,
|
|
761
|
-
group
|
|
1571
|
+
group: group ?? void 0
|
|
762
1572
|
}).path
|
|
763
|
-
}));
|
|
1573
|
+
})) : [];
|
|
1574
|
+
const seenImports = /* @__PURE__ */ new Set();
|
|
1575
|
+
const imports = [...importEntries, ...inputImportEntries].filter((imp) => {
|
|
1576
|
+
const key = `${Array.isArray(imp.name) ? imp.name.join(",") : imp.name}|${imp.path}`;
|
|
1577
|
+
if (seenImports.has(key)) return false;
|
|
1578
|
+
seenImports.add(key);
|
|
1579
|
+
return true;
|
|
1580
|
+
});
|
|
764
1581
|
const meta = {
|
|
765
|
-
name: resolver.
|
|
766
|
-
file: resolver.
|
|
1582
|
+
name: resolver.name(node.name),
|
|
1583
|
+
file: resolver.file({
|
|
767
1584
|
name: node.name,
|
|
768
|
-
extname: ".ts"
|
|
769
|
-
}, {
|
|
1585
|
+
extname: ".ts",
|
|
770
1586
|
root,
|
|
771
1587
|
output,
|
|
772
|
-
group
|
|
1588
|
+
group: group ?? void 0
|
|
773
1589
|
})
|
|
774
1590
|
};
|
|
775
|
-
const inferTypeName = inferred ? resolver.
|
|
776
|
-
const
|
|
777
|
-
const schemaPrinter = mini ? printerZodMini({
|
|
778
|
-
guidType,
|
|
779
|
-
wrapOutput,
|
|
780
|
-
resolver,
|
|
781
|
-
cyclicSchemas,
|
|
782
|
-
nodes: printer?.nodes
|
|
783
|
-
}) : printerZod({
|
|
1591
|
+
const inferTypeName = inferred ? resolver.schema.typeName(node.name) : null;
|
|
1592
|
+
const stdPrinters = mini ? null : getStdPrinters(resolver, {
|
|
784
1593
|
coercion,
|
|
785
1594
|
guidType,
|
|
1595
|
+
regexType,
|
|
786
1596
|
dateType,
|
|
787
|
-
wrapOutput,
|
|
788
|
-
resolver,
|
|
789
1597
|
cyclicSchemas,
|
|
790
1598
|
nodes: printer?.nodes
|
|
791
1599
|
});
|
|
1600
|
+
const schemaPrinter = mini ? getMiniPrinter(resolver, {
|
|
1601
|
+
guidType,
|
|
1602
|
+
regexType,
|
|
1603
|
+
cyclicSchemas,
|
|
1604
|
+
nodes: printer?.nodes
|
|
1605
|
+
}) : stdPrinters.output;
|
|
792
1606
|
return /* @__PURE__ */ jsxs(File, {
|
|
793
1607
|
baseName: meta.file.baseName,
|
|
794
1608
|
path: meta.file.path,
|
|
795
1609
|
meta: meta.file.meta,
|
|
796
|
-
banner: resolver.
|
|
1610
|
+
banner: resolver.default.banner(ctx.meta, {
|
|
797
1611
|
output,
|
|
798
|
-
config
|
|
1612
|
+
config,
|
|
1613
|
+
file: {
|
|
1614
|
+
path: meta.file.path,
|
|
1615
|
+
baseName: meta.file.baseName
|
|
1616
|
+
}
|
|
799
1617
|
}),
|
|
800
|
-
footer: resolver.
|
|
1618
|
+
footer: resolver.default.footer(ctx.meta, {
|
|
801
1619
|
output,
|
|
802
|
-
config
|
|
1620
|
+
config,
|
|
1621
|
+
file: {
|
|
1622
|
+
path: meta.file.path,
|
|
1623
|
+
baseName: meta.file.baseName
|
|
1624
|
+
}
|
|
803
1625
|
}),
|
|
804
1626
|
children: [
|
|
805
1627
|
/* @__PURE__ */ jsx(File.Import, {
|
|
@@ -807,70 +1629,90 @@ const zodGenerator = defineGenerator({
|
|
|
807
1629
|
path: importPath,
|
|
808
1630
|
isNameSpace: isZodImport
|
|
809
1631
|
}),
|
|
810
|
-
|
|
1632
|
+
imports.map((imp) => /* @__PURE__ */ jsx(File.Import, {
|
|
811
1633
|
root: meta.file.path,
|
|
812
1634
|
path: imp.path,
|
|
813
1635
|
name: imp.name
|
|
814
|
-
}, [
|
|
1636
|
+
}, [
|
|
1637
|
+
node.name,
|
|
1638
|
+
imp.path,
|
|
1639
|
+
imp.name
|
|
1640
|
+
].join("-"))),
|
|
815
1641
|
/* @__PURE__ */ jsx(Zod, {
|
|
816
1642
|
name: meta.name,
|
|
817
1643
|
node,
|
|
818
1644
|
printer: schemaPrinter,
|
|
819
|
-
inferTypeName
|
|
1645
|
+
inferTypeName,
|
|
1646
|
+
cyclic: cyclicSchemas.has(node.name)
|
|
1647
|
+
}),
|
|
1648
|
+
hasCodec && stdPrinters && /* @__PURE__ */ jsx(Zod, {
|
|
1649
|
+
name: resolver.schema.inputName(node.name),
|
|
1650
|
+
node,
|
|
1651
|
+
printer: stdPrinters.input,
|
|
1652
|
+
inferTypeName: inferred ? resolver.schema.inputTypeName(node.name) : null,
|
|
1653
|
+
cyclic: cyclicSchemas.has(node.name)
|
|
820
1654
|
})
|
|
821
1655
|
]
|
|
822
1656
|
});
|
|
823
1657
|
},
|
|
824
1658
|
operation(node, ctx) {
|
|
1659
|
+
if (!ast.isHttpOperationNode(node)) return null;
|
|
825
1660
|
const { adapter, config, resolver, root } = ctx;
|
|
826
|
-
const { output, coercion, guidType,
|
|
827
|
-
const dateType = adapter.options.dateType;
|
|
828
|
-
const mode = ctx.getMode(output);
|
|
1661
|
+
const { output, coercion, guidType, regexType, mini, inferred, importPath, group, printer } = ctx.options;
|
|
1662
|
+
const dateType = getOasAdapter(adapter).options.dateType;
|
|
829
1663
|
const isZodImport = ZOD_NAMESPACE_IMPORTS.has(importPath);
|
|
830
|
-
const
|
|
831
|
-
const meta = { file: resolver.resolveFile({
|
|
1664
|
+
const meta = { file: resolver.file({
|
|
832
1665
|
name: node.operationId,
|
|
833
1666
|
extname: ".ts",
|
|
834
1667
|
tag: node.tags[0] ?? "default",
|
|
835
|
-
path: node.path
|
|
836
|
-
}, {
|
|
1668
|
+
path: node.path,
|
|
837
1669
|
root,
|
|
838
1670
|
output,
|
|
839
|
-
group
|
|
1671
|
+
group: group ?? void 0
|
|
840
1672
|
}) };
|
|
841
|
-
const cyclicSchemas =
|
|
842
|
-
function renderSchemaEntry({ schema, name, keysToOmit }) {
|
|
1673
|
+
const cyclicSchemas = new Set(ctx.meta.circularNames);
|
|
1674
|
+
function renderSchemaEntry({ schema, name, keysToOmit, direction = "output" }) {
|
|
843
1675
|
if (!schema) return null;
|
|
844
|
-
const inferTypeName = inferred ? resolver.
|
|
845
|
-
const
|
|
846
|
-
|
|
847
|
-
|
|
848
|
-
|
|
849
|
-
|
|
850
|
-
|
|
851
|
-
|
|
852
|
-
|
|
853
|
-
|
|
854
|
-
}).path
|
|
855
|
-
}));
|
|
856
|
-
const schemaPrinter = mini ? printerZodMini({
|
|
1676
|
+
const inferTypeName = inferred ? resolver.schema.type(name) : null;
|
|
1677
|
+
const codecRefNames = direction === "input" && !mini ? new Set(collectCodecRefNames(schema)) : null;
|
|
1678
|
+
const imports = resolver.imports({
|
|
1679
|
+
node: schema,
|
|
1680
|
+
root,
|
|
1681
|
+
output,
|
|
1682
|
+
group: group ?? void 0,
|
|
1683
|
+
name: (schemaName) => codecRefNames?.has(schemaName) ? resolver.schema.inputName(schemaName) : resolver.name(schemaName)
|
|
1684
|
+
});
|
|
1685
|
+
const schemaPrinter = mini ? keysToOmit?.length ? printerZodMini({
|
|
857
1686
|
guidType,
|
|
858
|
-
|
|
1687
|
+
regexType,
|
|
859
1688
|
resolver,
|
|
860
1689
|
keysToOmit,
|
|
861
1690
|
cyclicSchemas,
|
|
862
1691
|
nodes: printer?.nodes
|
|
863
|
-
}) :
|
|
1692
|
+
}) : getMiniPrinter(resolver, {
|
|
1693
|
+
guidType,
|
|
1694
|
+
regexType,
|
|
1695
|
+
cyclicSchemas,
|
|
1696
|
+
nodes: printer?.nodes
|
|
1697
|
+
}) : keysToOmit?.length ? printerZod({
|
|
864
1698
|
coercion,
|
|
865
1699
|
guidType,
|
|
1700
|
+
regexType,
|
|
866
1701
|
dateType,
|
|
867
|
-
wrapOutput,
|
|
868
1702
|
resolver,
|
|
869
1703
|
keysToOmit,
|
|
870
1704
|
cyclicSchemas,
|
|
1705
|
+
nodes: printer?.nodes,
|
|
1706
|
+
direction
|
|
1707
|
+
}) : getStdPrinters(resolver, {
|
|
1708
|
+
coercion,
|
|
1709
|
+
guidType,
|
|
1710
|
+
regexType,
|
|
1711
|
+
dateType,
|
|
1712
|
+
cyclicSchemas,
|
|
871
1713
|
nodes: printer?.nodes
|
|
872
|
-
});
|
|
873
|
-
return /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
1714
|
+
})[direction];
|
|
1715
|
+
return /* @__PURE__ */ jsxs(Fragment, { children: [imports.map((imp) => /* @__PURE__ */ jsx(File.Import, {
|
|
874
1716
|
root: meta.file.path,
|
|
875
1717
|
path: imp.path,
|
|
876
1718
|
name: imp.name
|
|
@@ -882,56 +1724,140 @@ const zodGenerator = defineGenerator({
|
|
|
882
1724
|
name,
|
|
883
1725
|
node: schema,
|
|
884
1726
|
printer: schemaPrinter,
|
|
885
|
-
inferTypeName
|
|
1727
|
+
inferTypeName,
|
|
1728
|
+
cyclic: false
|
|
886
1729
|
})] });
|
|
887
1730
|
}
|
|
888
|
-
|
|
889
|
-
|
|
890
|
-
|
|
891
|
-
|
|
892
|
-
|
|
893
|
-
|
|
894
|
-
|
|
895
|
-
|
|
896
|
-
|
|
897
|
-
|
|
898
|
-
|
|
899
|
-
|
|
900
|
-
|
|
901
|
-
|
|
902
|
-
|
|
903
|
-
|
|
904
|
-
|
|
1731
|
+
function buildContentTypeVariants(entries, baseName, decorate, direction) {
|
|
1732
|
+
const variants = resolveContentTypeVariants(entries, baseName);
|
|
1733
|
+
const unionSchema = ast.factory.createSchema({
|
|
1734
|
+
type: "union",
|
|
1735
|
+
members: variants.map((variant) => ast.factory.createSchema({
|
|
1736
|
+
type: "ref",
|
|
1737
|
+
name: variant.name
|
|
1738
|
+
}))
|
|
1739
|
+
});
|
|
1740
|
+
return /* @__PURE__ */ jsxs(Fragment, { children: [variants.map((variant) => renderSchemaEntry({
|
|
1741
|
+
schema: decorate ? decorate(variant.schema) : variant.schema,
|
|
1742
|
+
name: variant.name,
|
|
1743
|
+
keysToOmit: variant.keysToOmit,
|
|
1744
|
+
direction
|
|
1745
|
+
})), renderSchemaEntry({
|
|
1746
|
+
schema: unionSchema,
|
|
1747
|
+
name: baseName,
|
|
1748
|
+
direction
|
|
1749
|
+
})] });
|
|
1750
|
+
}
|
|
1751
|
+
function buildResponseUnion({ responses, name, fallbackUnknown }) {
|
|
1752
|
+
if (new Set(responses.flatMap((res) => (res.content ?? []).flatMap((entry) => entry.schema ? collectRefNames(entry.schema).map((refName) => resolver.name(refName)) : []))).has(name)) return null;
|
|
1753
|
+
const members = responses.map((res) => ast.factory.createSchema({
|
|
905
1754
|
type: "ref",
|
|
906
|
-
name: resolver.
|
|
1755
|
+
name: resolver.response.status(node, res.statusCode)
|
|
907
1756
|
}));
|
|
1757
|
+
if (fallbackUnknown && members.length === 0) return renderSchemaEntry({
|
|
1758
|
+
schema: ast.factory.createSchema({ type: "unknown" }),
|
|
1759
|
+
name
|
|
1760
|
+
});
|
|
908
1761
|
return renderSchemaEntry({
|
|
909
|
-
schema: members.length === 1 ? members[0] : ast.createSchema({
|
|
1762
|
+
schema: members.length === 1 ? members[0] : ast.factory.createSchema({
|
|
910
1763
|
type: "union",
|
|
911
1764
|
members
|
|
912
1765
|
}),
|
|
913
|
-
name
|
|
1766
|
+
name
|
|
1767
|
+
});
|
|
1768
|
+
}
|
|
1769
|
+
const paramSchemas = node.parameters.map((param) => renderSchemaEntry({
|
|
1770
|
+
schema: param.schema,
|
|
1771
|
+
name: resolver.param.name(node, param),
|
|
1772
|
+
direction: "input"
|
|
1773
|
+
}));
|
|
1774
|
+
const responseSchemas = node.responses.map((res) => {
|
|
1775
|
+
const variants = (res.content ?? []).filter((entry) => entry.schema);
|
|
1776
|
+
if (variants.length > 1) return buildContentTypeVariants(res.content, resolver.response.status(node, res.statusCode));
|
|
1777
|
+
const primary = variants[0] ?? res.content?.[0];
|
|
1778
|
+
return renderSchemaEntry({
|
|
1779
|
+
schema: primary?.schema ?? null,
|
|
1780
|
+
name: resolver.response.status(node, res.statusCode),
|
|
1781
|
+
keysToOmit: primary?.keysToOmit
|
|
914
1782
|
});
|
|
915
|
-
})
|
|
916
|
-
const
|
|
917
|
-
|
|
918
|
-
|
|
919
|
-
|
|
1783
|
+
});
|
|
1784
|
+
const responsesWithSchema = node.responses.filter((res) => res.content?.some((entry) => entry.schema));
|
|
1785
|
+
const successResponsesWithSchema = getSuccessResponses(responsesWithSchema);
|
|
1786
|
+
const responseUnionSchema = responsesWithSchema.length > 0 ? buildResponseUnion({
|
|
1787
|
+
responses: successResponsesWithSchema,
|
|
1788
|
+
name: resolver.response.response(node),
|
|
1789
|
+
fallbackUnknown: true
|
|
1790
|
+
}) : null;
|
|
1791
|
+
const errorResponsesWithSchema = responsesWithSchema.filter((res) => !isSuccessStatusCode(res.statusCode));
|
|
1792
|
+
const errorUnionSchema = errorResponsesWithSchema.length > 0 ? buildResponseUnion({
|
|
1793
|
+
responses: errorResponsesWithSchema,
|
|
1794
|
+
name: resolver.response.error(node),
|
|
1795
|
+
fallbackUnknown: false
|
|
1796
|
+
}) : null;
|
|
1797
|
+
const requestBodyContent = node.requestBody?.content ?? [];
|
|
1798
|
+
const requestSchema = (() => {
|
|
1799
|
+
if (requestBodyContent.length === 0) return null;
|
|
1800
|
+
if (requestBodyContent.length === 1) {
|
|
1801
|
+
const entry = requestBodyContent[0];
|
|
1802
|
+
if (!entry.schema) return null;
|
|
1803
|
+
return renderSchemaEntry({
|
|
1804
|
+
schema: {
|
|
1805
|
+
...entry.schema,
|
|
1806
|
+
description: node.requestBody.description ?? entry.schema.description
|
|
1807
|
+
},
|
|
1808
|
+
name: resolver.response.body(node),
|
|
1809
|
+
keysToOmit: entry.keysToOmit,
|
|
1810
|
+
direction: "input"
|
|
1811
|
+
});
|
|
1812
|
+
}
|
|
1813
|
+
return buildContentTypeVariants(requestBodyContent, resolver.response.body(node), (schema) => ({
|
|
1814
|
+
...schema,
|
|
1815
|
+
description: node.requestBody.description ?? schema.description
|
|
1816
|
+
}), "input");
|
|
1817
|
+
})();
|
|
1818
|
+
const { path, query, header } = getOperationParameters(node);
|
|
1819
|
+
const paramGroupSchemas = inferred ? [
|
|
1820
|
+
{
|
|
1821
|
+
kind: "path",
|
|
1822
|
+
params: path
|
|
1823
|
+
},
|
|
1824
|
+
{
|
|
1825
|
+
kind: "query",
|
|
1826
|
+
params: query
|
|
920
1827
|
},
|
|
921
|
-
|
|
922
|
-
|
|
1828
|
+
{
|
|
1829
|
+
kind: "headers",
|
|
1830
|
+
params: header
|
|
1831
|
+
}
|
|
1832
|
+
].filter(({ params }) => params.length > 0).map(({ kind, params }) => renderSchemaEntry({
|
|
1833
|
+
schema: buildGroupedParamsSchema({ params }),
|
|
1834
|
+
name: resolver.param[kind](node, params[0]),
|
|
1835
|
+
direction: "input"
|
|
1836
|
+
})) : [];
|
|
1837
|
+
const optionsSchema = inferred ? renderSchemaEntry({
|
|
1838
|
+
schema: buildOptionsSchema(node, resolver),
|
|
1839
|
+
name: resolver.name(`${node.operationId} Options`),
|
|
1840
|
+
direction: "input"
|
|
923
1841
|
}) : null;
|
|
924
1842
|
return /* @__PURE__ */ jsxs(File, {
|
|
925
1843
|
baseName: meta.file.baseName,
|
|
926
1844
|
path: meta.file.path,
|
|
927
1845
|
meta: meta.file.meta,
|
|
928
|
-
banner: resolver.
|
|
1846
|
+
banner: resolver.default.banner(ctx.meta, {
|
|
929
1847
|
output,
|
|
930
|
-
config
|
|
1848
|
+
config,
|
|
1849
|
+
file: {
|
|
1850
|
+
path: meta.file.path,
|
|
1851
|
+
baseName: meta.file.baseName
|
|
1852
|
+
}
|
|
931
1853
|
}),
|
|
932
|
-
footer: resolver.
|
|
1854
|
+
footer: resolver.default.footer(ctx.meta, {
|
|
933
1855
|
output,
|
|
934
|
-
config
|
|
1856
|
+
config,
|
|
1857
|
+
file: {
|
|
1858
|
+
path: meta.file.path,
|
|
1859
|
+
baseName: meta.file.baseName
|
|
1860
|
+
}
|
|
935
1861
|
}),
|
|
936
1862
|
children: [
|
|
937
1863
|
/* @__PURE__ */ jsx(File.Import, {
|
|
@@ -942,78 +1868,10 @@ const zodGenerator = defineGenerator({
|
|
|
942
1868
|
paramSchemas,
|
|
943
1869
|
responseSchemas,
|
|
944
1870
|
responseUnionSchema,
|
|
945
|
-
|
|
946
|
-
|
|
947
|
-
|
|
948
|
-
|
|
949
|
-
operations(nodes, ctx) {
|
|
950
|
-
const { adapter, config, resolver, root } = ctx;
|
|
951
|
-
const { output, importPath, group, operations, paramsCasing } = ctx.options;
|
|
952
|
-
if (!operations) return;
|
|
953
|
-
const isZodImport = ZOD_NAMESPACE_IMPORTS.has(importPath);
|
|
954
|
-
const meta = { file: resolver.resolveFile({
|
|
955
|
-
name: "operations",
|
|
956
|
-
extname: ".ts"
|
|
957
|
-
}, {
|
|
958
|
-
root,
|
|
959
|
-
output,
|
|
960
|
-
group
|
|
961
|
-
}) };
|
|
962
|
-
const transformedOperations = nodes.map((node) => {
|
|
963
|
-
return {
|
|
964
|
-
node,
|
|
965
|
-
data: buildSchemaNames(node, {
|
|
966
|
-
params: ast.caseParams(node.parameters, paramsCasing),
|
|
967
|
-
resolver
|
|
968
|
-
})
|
|
969
|
-
};
|
|
970
|
-
});
|
|
971
|
-
const imports = transformedOperations.flatMap(({ node, data }) => {
|
|
972
|
-
const names = [
|
|
973
|
-
data.request,
|
|
974
|
-
...Object.values(data.responses),
|
|
975
|
-
...Object.values(data.parameters)
|
|
976
|
-
].filter(Boolean);
|
|
977
|
-
const opFile = resolver.resolveFile({
|
|
978
|
-
name: node.operationId,
|
|
979
|
-
extname: ".ts",
|
|
980
|
-
tag: node.tags[0] ?? "default",
|
|
981
|
-
path: node.path
|
|
982
|
-
}, {
|
|
983
|
-
root,
|
|
984
|
-
output,
|
|
985
|
-
group
|
|
986
|
-
});
|
|
987
|
-
return names.map((name) => /* @__PURE__ */ jsx(File.Import, {
|
|
988
|
-
name: [name],
|
|
989
|
-
root: meta.file.path,
|
|
990
|
-
path: opFile.path
|
|
991
|
-
}, [name, opFile.path].join("-")));
|
|
992
|
-
});
|
|
993
|
-
return /* @__PURE__ */ jsxs(File, {
|
|
994
|
-
baseName: meta.file.baseName,
|
|
995
|
-
path: meta.file.path,
|
|
996
|
-
meta: meta.file.meta,
|
|
997
|
-
banner: resolver.resolveBanner(adapter.inputNode, {
|
|
998
|
-
output,
|
|
999
|
-
config
|
|
1000
|
-
}),
|
|
1001
|
-
footer: resolver.resolveFooter(adapter.inputNode, {
|
|
1002
|
-
output,
|
|
1003
|
-
config
|
|
1004
|
-
}),
|
|
1005
|
-
children: [
|
|
1006
|
-
/* @__PURE__ */ jsx(File.Import, {
|
|
1007
|
-
isTypeOnly: true,
|
|
1008
|
-
name: isZodImport ? "z" : ["z"],
|
|
1009
|
-
path: importPath,
|
|
1010
|
-
isNameSpace: isZodImport
|
|
1011
|
-
}),
|
|
1012
|
-
imports,
|
|
1013
|
-
/* @__PURE__ */ jsx(Operations, {
|
|
1014
|
-
name: "operations",
|
|
1015
|
-
operations: transformedOperations
|
|
1016
|
-
})
|
|
1871
|
+
errorUnionSchema,
|
|
1872
|
+
requestSchema,
|
|
1873
|
+
paramGroupSchemas,
|
|
1874
|
+
optionsSchema
|
|
1017
1875
|
]
|
|
1018
1876
|
});
|
|
1019
1877
|
}
|
|
@@ -1021,92 +1879,90 @@ const zodGenerator = defineGenerator({
|
|
|
1021
1879
|
//#endregion
|
|
1022
1880
|
//#region src/resolvers/resolverZod.ts
|
|
1023
1881
|
/**
|
|
1024
|
-
*
|
|
1882
|
+
* Default resolver used by `@kubb/plugin-zod`. Decides the names and file
|
|
1883
|
+
* paths for every generated Zod schema. Schemas use camelCase with a
|
|
1884
|
+
* `Schema` suffix (`listPetsSchema`); their inferred types use PascalCase
|
|
1885
|
+
* with a `SchemaType` suffix (`PetSchemaType`), so the value and the type
|
|
1886
|
+
* never share an identifier even when the schema name is all-uppercase.
|
|
1025
1887
|
*
|
|
1026
|
-
*
|
|
1888
|
+
* @example Resolve schema and type names
|
|
1889
|
+
* ```ts
|
|
1890
|
+
* import { resolverZod } from '@kubb/plugin-zod'
|
|
1027
1891
|
*
|
|
1028
|
-
*
|
|
1029
|
-
*
|
|
1892
|
+
* resolverZod.name('list pets') // 'listPetsSchema'
|
|
1893
|
+
* resolverZod.schema.typeName('pet') // 'PetSchemaType'
|
|
1894
|
+
* ```
|
|
1030
1895
|
*/
|
|
1031
|
-
const resolverZod =
|
|
1032
|
-
|
|
1033
|
-
|
|
1034
|
-
|
|
1035
|
-
|
|
1036
|
-
|
|
1037
|
-
|
|
1038
|
-
|
|
1039
|
-
});
|
|
1040
|
-
},
|
|
1041
|
-
resolveSchemaName(name) {
|
|
1042
|
-
return camelCase(name, { suffix: "schema" });
|
|
1043
|
-
},
|
|
1044
|
-
resolveSchemaTypeName(name) {
|
|
1045
|
-
return pascalCase(name, { suffix: "schema" });
|
|
1046
|
-
},
|
|
1047
|
-
resolveTypeName(name) {
|
|
1048
|
-
return pascalCase(name);
|
|
1049
|
-
},
|
|
1050
|
-
resolvePathName(name, type) {
|
|
1051
|
-
return this.default(name, type);
|
|
1052
|
-
},
|
|
1053
|
-
resolveParamName(node, param) {
|
|
1054
|
-
return this.resolveSchemaName(`${node.operationId} ${param.in} ${param.name}`);
|
|
1055
|
-
},
|
|
1056
|
-
resolveResponseStatusName(node, statusCode) {
|
|
1057
|
-
return this.resolveSchemaName(`${node.operationId} Status ${statusCode}`);
|
|
1058
|
-
},
|
|
1059
|
-
resolveDataName(node) {
|
|
1060
|
-
return this.resolveSchemaName(`${node.operationId} Data`);
|
|
1061
|
-
},
|
|
1062
|
-
resolveResponsesName(node) {
|
|
1063
|
-
return this.resolveSchemaName(`${node.operationId} Responses`);
|
|
1896
|
+
const resolverZod = createResolver({
|
|
1897
|
+
pluginName: "plugin-zod",
|
|
1898
|
+
name(name) {
|
|
1899
|
+
return ensureValidVarName(camelCase(name, { suffix: "schema" }));
|
|
1900
|
+
},
|
|
1901
|
+
file: createCasedFile((part) => camelCase(part, { suffix: "schema" })),
|
|
1902
|
+
schema: {
|
|
1903
|
+
typeName(name) {
|
|
1904
|
+
return ensureValidVarName(pascalCase(name, { suffix: "schema type" }));
|
|
1064
1905
|
},
|
|
1065
|
-
|
|
1066
|
-
return
|
|
1906
|
+
type(name) {
|
|
1907
|
+
return ensureValidVarName(pascalCase(name, { suffix: "type" }));
|
|
1067
1908
|
},
|
|
1068
|
-
|
|
1069
|
-
return this.
|
|
1909
|
+
inputName(name) {
|
|
1910
|
+
return this.name(`${name} input`);
|
|
1070
1911
|
},
|
|
1071
|
-
|
|
1072
|
-
return this.
|
|
1912
|
+
inputTypeName(name) {
|
|
1913
|
+
return this.schema.typeName(`${name} input`);
|
|
1914
|
+
}
|
|
1915
|
+
},
|
|
1916
|
+
param: createOperationParamResolver(),
|
|
1917
|
+
response: {
|
|
1918
|
+
...createOperationResponseResolver(),
|
|
1919
|
+
error(node) {
|
|
1920
|
+
return this.name(`${node.operationId} Error`);
|
|
1073
1921
|
},
|
|
1074
|
-
|
|
1075
|
-
return this.
|
|
1922
|
+
options(node) {
|
|
1923
|
+
return this.schema.type(this.name(`${node.operationId} Options`));
|
|
1076
1924
|
}
|
|
1077
|
-
}
|
|
1925
|
+
}
|
|
1078
1926
|
});
|
|
1079
1927
|
//#endregion
|
|
1080
1928
|
//#region src/plugin.ts
|
|
1081
1929
|
/**
|
|
1082
|
-
* Canonical plugin name for `@kubb/plugin-zod
|
|
1930
|
+
* Canonical plugin name for `@kubb/plugin-zod`. Used for driver lookups and
|
|
1931
|
+
* cross-plugin dependency references.
|
|
1083
1932
|
*/
|
|
1084
1933
|
const pluginZodName = "plugin-zod";
|
|
1085
1934
|
/**
|
|
1086
|
-
* Generates Zod
|
|
1087
|
-
*
|
|
1088
|
-
*
|
|
1935
|
+
* Generates Zod v4 schemas from an OpenAPI spec. Use them to validate API
|
|
1936
|
+
* responses at runtime, build form schemas, or feed back into router libraries
|
|
1937
|
+
* that consume Zod (tRPC, Hono, Elysia). Pair with `@kubb/plugin-axios` or
|
|
1938
|
+
* `@kubb/plugin-fetch` and set the client's `validator: 'zod'` to validate every response
|
|
1939
|
+
* automatically.
|
|
1089
1940
|
*
|
|
1090
|
-
* @example
|
|
1941
|
+
* @example
|
|
1091
1942
|
* ```ts
|
|
1092
|
-
* import
|
|
1943
|
+
* import { defineConfig } from 'kubb/config'
|
|
1944
|
+
* import { pluginTs } from '@kubb/plugin-ts'
|
|
1945
|
+
* import { pluginZod } from '@kubb/plugin-zod'
|
|
1946
|
+
*
|
|
1093
1947
|
* export default defineConfig({
|
|
1094
|
-
*
|
|
1948
|
+
* input: './petStore.yaml',
|
|
1949
|
+
* output: { path: './src/gen' },
|
|
1950
|
+
* plugins: [
|
|
1951
|
+
* pluginTs(),
|
|
1952
|
+
* pluginZod({
|
|
1953
|
+
* output: { path: './zod' },
|
|
1954
|
+
* inferred: true,
|
|
1955
|
+
* }),
|
|
1956
|
+
* ],
|
|
1095
1957
|
* })
|
|
1096
1958
|
* ```
|
|
1097
1959
|
*/
|
|
1098
1960
|
const pluginZod = definePlugin((options) => {
|
|
1099
1961
|
const { output = {
|
|
1100
1962
|
path: "zod",
|
|
1101
|
-
|
|
1102
|
-
}, group, exclude = [], include, override = [],
|
|
1103
|
-
const groupConfig = group
|
|
1104
|
-
...group,
|
|
1105
|
-
name: (ctx) => {
|
|
1106
|
-
if (group.type === "path") return `${ctx.group.split("/")[1]}`;
|
|
1107
|
-
return `${camelCase(ctx.group)}Controller`;
|
|
1108
|
-
}
|
|
1109
|
-
} : void 0;
|
|
1963
|
+
barrel: { type: "named" }
|
|
1964
|
+
}, group, exclude = [], include, override = [], mini = false, guidType = "uuid", regexType = "literal", importPath = mini ? "zod/mini" : "zod", coercion = false, inferred = false, printer, resolver: userResolver, macros: userMacros } = options;
|
|
1965
|
+
const groupConfig = createGroupConfig(group);
|
|
1110
1966
|
return {
|
|
1111
1967
|
name: pluginZodName,
|
|
1112
1968
|
options,
|
|
@@ -1117,24 +1973,17 @@ const pluginZod = definePlugin((options) => {
|
|
|
1117
1973
|
include,
|
|
1118
1974
|
override,
|
|
1119
1975
|
group: groupConfig,
|
|
1120
|
-
typed,
|
|
1121
1976
|
importPath,
|
|
1122
1977
|
coercion,
|
|
1123
|
-
operations,
|
|
1124
1978
|
inferred,
|
|
1125
1979
|
guidType,
|
|
1980
|
+
regexType,
|
|
1126
1981
|
mini,
|
|
1127
|
-
wrapOutput,
|
|
1128
|
-
paramsCasing,
|
|
1129
1982
|
printer
|
|
1130
1983
|
});
|
|
1131
|
-
ctx.setResolver(userResolver ?
|
|
1132
|
-
|
|
1133
|
-
...userResolver
|
|
1134
|
-
} : resolverZod);
|
|
1135
|
-
if (userTransformer) ctx.setTransformer(userTransformer);
|
|
1984
|
+
ctx.setResolver(userResolver ? Resolver.merge(resolverZod, userResolver) : resolverZod);
|
|
1985
|
+
if (userMacros?.length) ctx.setMacros(userMacros);
|
|
1136
1986
|
ctx.addGenerator(zodGenerator);
|
|
1137
|
-
for (const gen of userGenerators) ctx.addGenerator(gen);
|
|
1138
1987
|
} }
|
|
1139
1988
|
};
|
|
1140
1989
|
});
|