@kubb/plugin-zod 5.0.0-alpha.9 → 5.0.0-beta.100
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/LICENSE +17 -10
- package/README.md +38 -23
- package/dist/index.cjs +1964 -133
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.ts +482 -6
- package/dist/index.js +1950 -132
- package/dist/index.js.map +1 -1
- package/package.json +36 -81
- package/dist/components-B7zUFnAm.cjs +0 -890
- package/dist/components-B7zUFnAm.cjs.map +0 -1
- package/dist/components-eECfXVou.js +0 -842
- package/dist/components-eECfXVou.js.map +0 -1
- package/dist/components.cjs +0 -4
- package/dist/components.d.ts +0 -56
- package/dist/components.js +0 -2
- package/dist/generators-BjPDdJUz.cjs +0 -301
- package/dist/generators-BjPDdJUz.cjs.map +0 -1
- package/dist/generators-lTWPS6oN.js +0 -290
- package/dist/generators-lTWPS6oN.js.map +0 -1
- package/dist/generators.cjs +0 -4
- package/dist/generators.d.ts +0 -508
- package/dist/generators.js +0 -2
- package/dist/templates/ToZod.source.cjs +0 -7
- package/dist/templates/ToZod.source.cjs.map +0 -1
- package/dist/templates/ToZod.source.d.ts +0 -7
- package/dist/templates/ToZod.source.js +0 -6
- package/dist/templates/ToZod.source.js.map +0 -1
- package/dist/types-CoCoOc2u.d.ts +0 -172
- package/src/components/Operations.tsx +0 -70
- package/src/components/Zod.tsx +0 -142
- package/src/components/index.ts +0 -2
- package/src/generators/index.ts +0 -2
- package/src/generators/operationsGenerator.tsx +0 -50
- package/src/generators/zodGenerator.tsx +0 -202
- package/src/index.ts +0 -2
- package/src/parser.ts +0 -909
- package/src/plugin.ts +0 -183
- package/src/templates/ToZod.source.ts +0 -4
- package/src/types.ts +0 -172
- package/templates/ToZod.ts +0 -61
- /package/dist/{chunk--u3MIqq1.js → rolldown-runtime-C0LytTxp.js} +0 -0
package/dist/index.js
CHANGED
|
@@ -1,10 +1,169 @@
|
|
|
1
|
-
import "./
|
|
2
|
-
import {
|
|
3
|
-
import {
|
|
4
|
-
import
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
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
|
|
8
167
|
//#region ../../internals/utils/src/casing.ts
|
|
9
168
|
/**
|
|
10
169
|
* Shared implementation for camelCase and PascalCase conversion.
|
|
@@ -16,160 +175,1819 @@ import { pluginTsName } from "@kubb/plugin-ts";
|
|
|
16
175
|
function toCamelOrPascal(text, pascal) {
|
|
17
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) => {
|
|
18
177
|
if (word.length > 1 && word === word.toUpperCase()) return word;
|
|
19
|
-
|
|
20
|
-
return word.charAt(0).toUpperCase() + word.slice(1);
|
|
178
|
+
return (i === 0 && !pascal ? word.charAt(0).toLowerCase() : word.charAt(0).toUpperCase()) + word.slice(1);
|
|
21
179
|
}).join("").replace(/[^a-zA-Z0-9]/g, "");
|
|
22
180
|
}
|
|
23
181
|
/**
|
|
24
|
-
*
|
|
25
|
-
*
|
|
26
|
-
*
|
|
182
|
+
* Converts `text` to camelCase.
|
|
183
|
+
*
|
|
184
|
+
* @example Word boundaries
|
|
185
|
+
* `camelCase('hello-world') // 'helloWorld'`
|
|
186
|
+
*
|
|
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
|
-
*
|
|
298
|
+
* ```ts
|
|
299
|
+
* isValidVarName('status') // true
|
|
300
|
+
* isValidVarName('class') // false (reserved word)
|
|
301
|
+
* isValidVarName('42foo') // false (starts with digit)
|
|
302
|
+
* ```
|
|
39
303
|
*/
|
|
40
|
-
function
|
|
41
|
-
if (
|
|
42
|
-
|
|
43
|
-
suffix
|
|
44
|
-
} : {}));
|
|
45
|
-
return toCamelOrPascal(`${prefix} ${text} ${suffix}`, false);
|
|
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
|
-
*
|
|
317
|
+
* ```ts
|
|
318
|
+
* ensureValidVarName('409') // '_409'
|
|
319
|
+
* ensureValidVarName('504AccountCancel') // '_504AccountCancel'
|
|
320
|
+
* ensureValidVarName('Pet') // 'Pet'
|
|
321
|
+
* ensureValidVarName('class') // '_class'
|
|
322
|
+
* ```
|
|
54
323
|
*/
|
|
55
|
-
function
|
|
56
|
-
if (
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
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 src/
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
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
|
+
}
|
|
360
|
+
/**
|
|
361
|
+
* Strips a single matching pair of `"..."`, `'...'`, or `` `...` `` from both ends of `text`.
|
|
362
|
+
* Returns the string unchanged when no balanced quote pair is found.
|
|
363
|
+
*
|
|
364
|
+
* @example
|
|
365
|
+
* ```ts
|
|
366
|
+
* trimQuotes('"hello"') // 'hello'
|
|
367
|
+
* trimQuotes('hello') // 'hello'
|
|
368
|
+
* ```
|
|
369
|
+
*/
|
|
370
|
+
function trimQuotes(text) {
|
|
371
|
+
if (text.length >= 2) {
|
|
372
|
+
const first = text[0];
|
|
373
|
+
const last = text[text.length - 1];
|
|
374
|
+
if (first === "\"" && last === "\"" || first === "'" && last === "'" || first === "`" && last === "`") return text.slice(1, -1);
|
|
375
|
+
}
|
|
376
|
+
return text;
|
|
377
|
+
}
|
|
378
|
+
/**
|
|
379
|
+
* Serializes a primitive to a single-quoted string literal, stripping any surrounding quotes first.
|
|
380
|
+
*
|
|
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.
|
|
383
|
+
*
|
|
384
|
+
* @example
|
|
385
|
+
* ```ts
|
|
386
|
+
* stringify('hello') // "'hello'"
|
|
387
|
+
* stringify('"hello"') // "'hello'"
|
|
388
|
+
* ```
|
|
389
|
+
*/
|
|
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, "\\'")}'`;
|
|
393
|
+
}
|
|
394
|
+
/**
|
|
395
|
+
* Converts a pattern string into a `new RegExp(...)` constructor call or a regex literal string.
|
|
396
|
+
* Inline flags expressed as a `^(?im)` prefix are extracted and applied to the resulting expression.
|
|
397
|
+
* Pass `null` as the second argument to emit a `/pattern/flags` literal instead.
|
|
398
|
+
*
|
|
399
|
+
* @example
|
|
400
|
+
* ```ts
|
|
401
|
+
* toRegExpString('^(?im)foo') // 'new RegExp("^foo", "im")'
|
|
402
|
+
* toRegExpString('^(?im)foo', null) // '/^foo/im'
|
|
403
|
+
* ```
|
|
404
|
+
*/
|
|
405
|
+
function toRegExpString(text, func = "RegExp") {
|
|
406
|
+
const raw = trimQuotes(text);
|
|
407
|
+
const match = raw.match(/^\^(\(\?([igmsuy]+)\))/i);
|
|
408
|
+
const replacementTarget = match?.[1] ?? "";
|
|
409
|
+
const matchedFlags = match?.[2];
|
|
410
|
+
const cleaned = raw.replace(/^\\?\//, "").replace(/\\?\/$/, "").replace(replacementTarget, "");
|
|
411
|
+
const { source, flags } = new RegExp(cleaned, matchedFlags);
|
|
412
|
+
if (func === null) return `/${source}/${flags}`;
|
|
413
|
+
return `new ${func}(${JSON.stringify(source)}${flags ? `, ${JSON.stringify(flags)}` : ""})`;
|
|
414
|
+
}
|
|
415
|
+
//#endregion
|
|
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() {
|
|
70
532
|
return {
|
|
71
|
-
name:
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
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`);
|
|
94
565
|
},
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
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
|
+
}));
|
|
668
|
+
}
|
|
669
|
+
//#endregion
|
|
670
|
+
//#region src/components/Zod.tsx
|
|
671
|
+
function Zod({ name, node, printer, inferTypeName, cyclic }) {
|
|
672
|
+
const output = printer.print(node);
|
|
673
|
+
if (!output) return;
|
|
674
|
+
const needsAnnotation = cyclic && node.type !== "object";
|
|
675
|
+
return /* @__PURE__ */ jsxs(Fragment, { children: [/* @__PURE__ */ jsx(File.Source, {
|
|
676
|
+
name,
|
|
677
|
+
isExportable: true,
|
|
678
|
+
isIndexable: true,
|
|
679
|
+
children: /* @__PURE__ */ jsx(Const, {
|
|
680
|
+
export: true,
|
|
681
|
+
name,
|
|
682
|
+
type: needsAnnotation ? "z.ZodType" : void 0,
|
|
683
|
+
children: output
|
|
684
|
+
})
|
|
685
|
+
}), inferTypeName && /* @__PURE__ */ jsx(File.Source, {
|
|
686
|
+
name: inferTypeName,
|
|
687
|
+
isExportable: true,
|
|
688
|
+
isIndexable: true,
|
|
689
|
+
isTypeOnly: true,
|
|
690
|
+
children: /* @__PURE__ */ jsx(Type, {
|
|
691
|
+
export: true,
|
|
692
|
+
name: inferTypeName,
|
|
693
|
+
children: `z.infer<typeof ${name}>`
|
|
694
|
+
})
|
|
695
|
+
})] });
|
|
696
|
+
}
|
|
697
|
+
//#endregion
|
|
698
|
+
//#region src/constants.ts
|
|
699
|
+
/**
|
|
700
|
+
* Import paths that use a namespace import (`import * as z from '...'`).
|
|
701
|
+
* All other import paths use a named import (`import { z } from '...'`).
|
|
702
|
+
*/
|
|
703
|
+
const ZOD_NAMESPACE_IMPORTS = /* @__PURE__ */ new Set(["zod", "zod/mini"]);
|
|
704
|
+
//#endregion
|
|
705
|
+
//#region src/utils.ts
|
|
706
|
+
/**
|
|
707
|
+
* Returns `true` when the given coercion option enables coercion for the specified type.
|
|
708
|
+
*/
|
|
709
|
+
function shouldCoerce(coercion, type) {
|
|
710
|
+
if (coercion === void 0 || coercion === false) return false;
|
|
711
|
+
if (coercion === true) return true;
|
|
712
|
+
return !!coercion[type];
|
|
713
|
+
}
|
|
714
|
+
/**
|
|
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);
|
|
757
|
+
}
|
|
758
|
+
const resolved = syncSchemaRef(node);
|
|
759
|
+
if (resolved.type === "ref") return false;
|
|
760
|
+
return containsCodec(resolved, seen);
|
|
761
|
+
}
|
|
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);
|
|
815
|
+
}
|
|
816
|
+
/**
|
|
817
|
+
* Format a default value as a code-level literal.
|
|
818
|
+
* Objects become `{}`, primitives become their string representation, strings are quoted.
|
|
819
|
+
*/
|
|
820
|
+
function formatDefault(value) {
|
|
821
|
+
if (typeof value === "string") return stringify(value);
|
|
822
|
+
if (typeof value === "object" && value !== null) return "{}";
|
|
823
|
+
return String(value ?? "");
|
|
824
|
+
}
|
|
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
|
+
/**
|
|
853
|
+
* Format a primitive enum/literal value.
|
|
854
|
+
* Strings are quoted; numbers and booleans are emitted raw.
|
|
855
|
+
*/
|
|
856
|
+
function formatLiteral(v) {
|
|
857
|
+
if (typeof v === "string") return stringify(v);
|
|
858
|
+
return String(v);
|
|
859
|
+
}
|
|
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
|
+
/**
|
|
902
|
+
* Build `.min()` / `.max()` / `.gt()` / `.lt()` constraint chains for numbers
|
|
903
|
+
* using the standard chainable Zod v4 API.
|
|
904
|
+
*/
|
|
905
|
+
function numberConstraints({ min, max, exclusiveMinimum, exclusiveMaximum, multipleOf }) {
|
|
906
|
+
return [
|
|
907
|
+
min !== void 0 ? `.min(${min})` : "",
|
|
908
|
+
max !== void 0 ? `.max(${max})` : "",
|
|
909
|
+
exclusiveMinimum !== void 0 ? `.gt(${exclusiveMinimum})` : "",
|
|
910
|
+
exclusiveMaximum !== void 0 ? `.lt(${exclusiveMaximum})` : "",
|
|
911
|
+
multipleOf !== void 0 ? `.multipleOf(${multipleOf})` : ""
|
|
912
|
+
].join("");
|
|
913
|
+
}
|
|
914
|
+
/**
|
|
915
|
+
* Build `.min()` / `.max()` / `.regex()` chains for strings/arrays
|
|
916
|
+
* using the standard chainable Zod v4 API.
|
|
917
|
+
*/
|
|
918
|
+
function lengthConstraints({ min, max, pattern, regexType }) {
|
|
919
|
+
return [
|
|
920
|
+
min !== void 0 ? `.min(${min})` : "",
|
|
921
|
+
max !== void 0 ? `.max(${max})` : "",
|
|
922
|
+
pattern !== void 0 ? `.regex(${toRegExpString(pattern, regexFunc(regexType))})` : ""
|
|
923
|
+
].join("");
|
|
924
|
+
}
|
|
925
|
+
/**
|
|
926
|
+
* Build `.check(z.minimum(), z.maximum())` for `zod/mini` numeric constraints.
|
|
927
|
+
*/
|
|
928
|
+
function numberChecksMini({ min, max, exclusiveMinimum, exclusiveMaximum, multipleOf }) {
|
|
929
|
+
const checks = [];
|
|
930
|
+
if (min !== void 0) checks.push(`z.minimum(${min})`);
|
|
931
|
+
if (max !== void 0) checks.push(`z.maximum(${max})`);
|
|
932
|
+
if (exclusiveMinimum !== void 0) checks.push(`z.minimum(${exclusiveMinimum}, { exclusive: true })`);
|
|
933
|
+
if (exclusiveMaximum !== void 0) checks.push(`z.maximum(${exclusiveMaximum}, { exclusive: true })`);
|
|
934
|
+
if (multipleOf !== void 0) checks.push(`z.multipleOf(${multipleOf})`);
|
|
935
|
+
return checks.length ? `.check(${checks.join(", ")})` : "";
|
|
936
|
+
}
|
|
937
|
+
/**
|
|
938
|
+
* Build `.check(z.minLength(), z.maxLength(), z.regex())` for `zod/mini` length constraints.
|
|
939
|
+
*/
|
|
940
|
+
function lengthChecksMini({ min, max, pattern, regexType }) {
|
|
941
|
+
const checks = [];
|
|
942
|
+
if (min !== void 0) checks.push(`z.minLength(${min})`);
|
|
943
|
+
if (max !== void 0) checks.push(`z.maxLength(${max})`);
|
|
944
|
+
if (pattern !== void 0) checks.push(`z.regex(${toRegExpString(pattern, regexFunc(regexType))})`);
|
|
945
|
+
return checks.length ? `.check(${checks.join(", ")})` : "";
|
|
946
|
+
}
|
|
947
|
+
/**
|
|
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.
|
|
950
|
+
*/
|
|
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);
|
|
983
|
+
}
|
|
984
|
+
/**
|
|
985
|
+
* Apply nullable / optional / nullish modifiers using the functional `zod/mini` API
|
|
986
|
+
* (`z.nullable()`, `z.optional()`, `z.nullish()`).
|
|
987
|
+
*/
|
|
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
|
+
});
|
|
1012
|
+
}
|
|
1013
|
+
//#endregion
|
|
1014
|
+
//#region src/printers/printerZod.ts
|
|
1015
|
+
function strictOneOfMember$1(member, node, cyclicSchemas) {
|
|
1016
|
+
if (node.type === "object" && node.additionalProperties === void 0) return `${member}.strict()`;
|
|
1017
|
+
if (node.type === "ref") {
|
|
1018
|
+
if (member.startsWith("z.lazy(")) return member;
|
|
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;
|
|
1023
|
+
if (schema.type === "object" && (schema.additionalProperties === void 0 || schema.additionalProperties === false)) return `${member}.strict()`;
|
|
1024
|
+
}
|
|
1025
|
+
return member;
|
|
1026
|
+
}
|
|
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
|
+
}
|
|
1074
|
+
/**
|
|
1075
|
+
* Zod v4 printer built with `definePrinter`.
|
|
1076
|
+
*
|
|
1077
|
+
* Converts a `SchemaNode` AST into a Zod v4 code string using the chainable API
|
|
1078
|
+
* (`.optional()`, `.nullable()`, `.omit()`, etc.). For improved tree-shaking, see {@link printerZodMini}.
|
|
1079
|
+
*
|
|
1080
|
+
* @example Chainable API
|
|
1081
|
+
* ```ts
|
|
1082
|
+
* const printer = printerZod({ coercion: false })
|
|
1083
|
+
* const code = printer.print(stringNode) // "z.string()"
|
|
1084
|
+
* ```
|
|
1085
|
+
*/
|
|
1086
|
+
const printerZod = ast.createPrinter((options) => {
|
|
1087
|
+
const cyclicSchemaNames = options.cyclicSchemas;
|
|
1088
|
+
return {
|
|
1089
|
+
name: "zod",
|
|
1090
|
+
options,
|
|
1091
|
+
nodes: {
|
|
1092
|
+
any: () => "z.any()",
|
|
1093
|
+
unknown: () => "z.unknown()",
|
|
1094
|
+
void: () => "z.void()",
|
|
1095
|
+
never: () => "z.never()",
|
|
1096
|
+
boolean: () => "z.boolean()",
|
|
1097
|
+
null: () => "z.null()",
|
|
1098
|
+
string(node) {
|
|
1099
|
+
return `${shouldCoerce(this.options.coercion, "strings") ? "z.coerce.string()" : "z.string()"}${lengthConstraints({
|
|
1100
|
+
...node,
|
|
1101
|
+
regexType: this.options.regexType
|
|
1102
|
+
})}`;
|
|
1103
|
+
},
|
|
1104
|
+
number(node) {
|
|
1105
|
+
return `${shouldCoerce(this.options.coercion, "numbers") ? "z.coerce.number()" : "z.number()"}${numberConstraints(node)}`;
|
|
1106
|
+
},
|
|
1107
|
+
integer(node) {
|
|
1108
|
+
return `${shouldCoerce(this.options.coercion, "numbers") ? "z.coerce.number().int()" : "z.int()"}${numberConstraints(node)}`;
|
|
1109
|
+
},
|
|
1110
|
+
bigint() {
|
|
1111
|
+
return shouldCoerce(this.options.coercion, "numbers") ? "z.coerce.bigint()" : "z.bigint()";
|
|
1112
|
+
},
|
|
1113
|
+
date(node) {
|
|
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()";
|
|
1120
|
+
},
|
|
1121
|
+
datetime(node) {
|
|
1122
|
+
const offset = node.offset || this.options.dateType === "stringOffset";
|
|
1123
|
+
const local = node.local || this.options.dateType === "stringLocal";
|
|
1124
|
+
if (offset) return "z.iso.datetime({ offset: true })";
|
|
1125
|
+
if (local) return "z.iso.datetime({ local: true })";
|
|
1126
|
+
return "z.iso.datetime()";
|
|
1127
|
+
},
|
|
1128
|
+
time(node) {
|
|
1129
|
+
if (node.representation === "string") return "z.iso.time()";
|
|
1130
|
+
return shouldCoerce(this.options.coercion, "dates") ? "z.coerce.date()" : "z.date()";
|
|
1131
|
+
},
|
|
1132
|
+
uuid(node) {
|
|
1133
|
+
return `${this.options.guidType === "guid" ? "z.guid()" : "z.uuid()"}${lengthConstraints({
|
|
1134
|
+
...node,
|
|
1135
|
+
regexType: this.options.regexType
|
|
1136
|
+
})}`;
|
|
1137
|
+
},
|
|
1138
|
+
email(node) {
|
|
1139
|
+
return `z.email()${lengthConstraints({
|
|
1140
|
+
...node,
|
|
1141
|
+
regexType: this.options.regexType
|
|
1142
|
+
})}`;
|
|
1143
|
+
},
|
|
1144
|
+
url(node) {
|
|
1145
|
+
return `z.url()${lengthConstraints({
|
|
1146
|
+
...node,
|
|
1147
|
+
regexType: this.options.regexType
|
|
1148
|
+
})}`;
|
|
1149
|
+
},
|
|
1150
|
+
ipv4: () => "z.ipv4()",
|
|
1151
|
+
ipv6: () => "z.ipv6()",
|
|
1152
|
+
blob: () => "z.instanceof(File)",
|
|
1153
|
+
enum(node) {
|
|
1154
|
+
const nonNullValues = (node.namedEnumValues?.map((v) => v.value) ?? node.enumValues ?? []).filter((v) => v !== null);
|
|
1155
|
+
if (node.namedEnumValues?.length) {
|
|
1156
|
+
const literals = nonNullValues.map((v) => `z.literal(${formatLiteral(v)})`);
|
|
1157
|
+
if (literals.length === 1) return literals[0];
|
|
1158
|
+
return `z.union([${literals.join(", ")}])`;
|
|
1159
|
+
}
|
|
1160
|
+
return buildEnum(nonNullValues);
|
|
1161
|
+
},
|
|
1162
|
+
ref(node) {
|
|
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;
|
|
1168
|
+
if (node.ref && this.options.cyclicSchemas?.has(refName)) return `z.lazy(() => ${resolvedName})`;
|
|
1169
|
+
return resolvedName;
|
|
1170
|
+
},
|
|
1171
|
+
object(node) {
|
|
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
|
+
})();
|
|
1197
|
+
},
|
|
1198
|
+
array(node) {
|
|
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;
|
|
1204
|
+
},
|
|
1205
|
+
tuple(node) {
|
|
1206
|
+
return `z.tuple(${buildList(mapSchemaItems(node, (item) => this.transform(item)).map(({ output }) => output).filter(Boolean))})`;
|
|
1207
|
+
},
|
|
1208
|
+
union(node) {
|
|
1209
|
+
const nodeMembers = node.members ?? [];
|
|
1210
|
+
const members = mapSchemaMembers(node, (memberNode) => this.transform(memberNode)).map(({ schema, output }) => output && node.strategy === "one" ? strictOneOfMember$1(output, schema, cyclicSchemaNames) : output).filter(Boolean);
|
|
1211
|
+
if (members.length === 0) return "";
|
|
1212
|
+
if (members.length === 1) return members[0];
|
|
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)})`;
|
|
1216
|
+
},
|
|
1217
|
+
intersection(node) {
|
|
1218
|
+
const members = node.members ?? [];
|
|
1219
|
+
if (members.length === 0) return "";
|
|
1220
|
+
const [first, ...rest] = members;
|
|
1221
|
+
if (!first) return "";
|
|
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;
|
|
1231
|
+
const transformed = this.transform(member);
|
|
1232
|
+
return transformed ? `${acc}.and(${transformed})` : acc;
|
|
1233
|
+
}, firstBase);
|
|
110
1234
|
}
|
|
111
|
-
return path.resolve(root, output.path, baseName);
|
|
112
1235
|
},
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
1236
|
+
overrides: options.nodes,
|
|
1237
|
+
print(node) {
|
|
1238
|
+
const { keysToOmit } = this.options;
|
|
1239
|
+
const transformed = this.transform(node);
|
|
1240
|
+
if (!transformed) return null;
|
|
1241
|
+
const meta = syncSchemaRef(node);
|
|
1242
|
+
return applyModifiers({
|
|
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,
|
|
1252
|
+
nullable: meta.nullable,
|
|
1253
|
+
optional: meta.optional,
|
|
1254
|
+
nullish: meta.nullish,
|
|
1255
|
+
defaultValue: meta.default,
|
|
1256
|
+
description: meta.description,
|
|
1257
|
+
examples: meta.examples
|
|
117
1258
|
});
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
1259
|
+
}
|
|
1260
|
+
};
|
|
1261
|
+
});
|
|
1262
|
+
//#endregion
|
|
1263
|
+
//#region src/printers/printerZodMini.ts
|
|
1264
|
+
function strictOneOfMember(member, node) {
|
|
1265
|
+
if (node.type === "object" && (node.additionalProperties === void 0 || node.additionalProperties === false)) return member.replace(/^z\.object\(/, "z.strictObject(");
|
|
1266
|
+
return member;
|
|
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
|
+
}
|
|
1279
|
+
/**
|
|
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`.
|
|
1313
|
+
*
|
|
1314
|
+
* Converts a `SchemaNode` AST into a Zod v4 code string using the functional API
|
|
1315
|
+
* (`z.optional(z.string())`) for improved tree-shaking. See {@link printerZod} for the chainable API.
|
|
1316
|
+
*
|
|
1317
|
+
* @example Functional Mini API
|
|
1318
|
+
* ```ts
|
|
1319
|
+
* const printer = printerZodMini({})
|
|
1320
|
+
* const code = printer.print(optionalStringNode) // "z.optional(z.string())"
|
|
1321
|
+
* ```
|
|
1322
|
+
*/
|
|
1323
|
+
const printerZodMini = ast.createPrinter((options) => {
|
|
1324
|
+
const cyclicSchemaNames = options.cyclicSchemas;
|
|
1325
|
+
return {
|
|
1326
|
+
name: "zod-mini",
|
|
1327
|
+
options,
|
|
1328
|
+
nodes: {
|
|
1329
|
+
any: () => "z.any()",
|
|
1330
|
+
unknown: () => "z.unknown()",
|
|
1331
|
+
void: () => "z.void()",
|
|
1332
|
+
never: () => "z.never()",
|
|
1333
|
+
boolean: () => "z.boolean()",
|
|
1334
|
+
null: () => "z.null()",
|
|
1335
|
+
string(node) {
|
|
1336
|
+
return `z.string()${lengthChecksMini({
|
|
1337
|
+
...node,
|
|
1338
|
+
regexType: this.options.regexType
|
|
1339
|
+
})}`;
|
|
1340
|
+
},
|
|
1341
|
+
number(node) {
|
|
1342
|
+
return `z.number()${numberChecksMini(node)}`;
|
|
1343
|
+
},
|
|
1344
|
+
integer(node) {
|
|
1345
|
+
return `z.int()${numberChecksMini(node)}`;
|
|
1346
|
+
},
|
|
1347
|
+
bigint(node) {
|
|
1348
|
+
return `z.bigint()${numberChecksMini(node)}`;
|
|
1349
|
+
},
|
|
1350
|
+
date(node) {
|
|
1351
|
+
if (node.representation === "string") return "z.iso.date()";
|
|
1352
|
+
return "z.date()";
|
|
1353
|
+
},
|
|
1354
|
+
datetime() {
|
|
1355
|
+
return "z.string()";
|
|
1356
|
+
},
|
|
1357
|
+
time(node) {
|
|
1358
|
+
if (node.representation === "string") return "z.iso.time()";
|
|
1359
|
+
return "z.date()";
|
|
1360
|
+
},
|
|
1361
|
+
uuid(node) {
|
|
1362
|
+
return `${this.options.guidType === "guid" ? "z.guid()" : "z.uuid()"}${lengthChecksMini({
|
|
1363
|
+
...node,
|
|
1364
|
+
regexType: this.options.regexType
|
|
1365
|
+
})}`;
|
|
1366
|
+
},
|
|
1367
|
+
email(node) {
|
|
1368
|
+
return `z.email()${lengthChecksMini({
|
|
1369
|
+
...node,
|
|
1370
|
+
regexType: this.options.regexType
|
|
1371
|
+
})}`;
|
|
1372
|
+
},
|
|
1373
|
+
url(node) {
|
|
1374
|
+
return `z.url()${lengthChecksMini({
|
|
1375
|
+
...node,
|
|
1376
|
+
regexType: this.options.regexType
|
|
1377
|
+
})}`;
|
|
1378
|
+
},
|
|
1379
|
+
ipv4: () => "z.ipv4()",
|
|
1380
|
+
ipv6: () => "z.ipv6()",
|
|
1381
|
+
blob: () => "z.instanceof(File)",
|
|
1382
|
+
enum(node) {
|
|
1383
|
+
const nonNullValues = (node.namedEnumValues?.map((v) => v.value) ?? node.enumValues ?? []).filter((v) => v !== null);
|
|
1384
|
+
if (node.namedEnumValues?.length) {
|
|
1385
|
+
const literals = nonNullValues.map((v) => `z.literal(${formatLiteral(v)})`);
|
|
1386
|
+
if (literals.length === 1) return literals[0];
|
|
1387
|
+
return `z.union([${literals.join(", ")}])`;
|
|
1388
|
+
}
|
|
1389
|
+
return buildEnum(nonNullValues);
|
|
1390
|
+
},
|
|
1391
|
+
ref(node) {
|
|
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;
|
|
1396
|
+
if (node.ref && this.options.cyclicSchemas?.has(refName)) return `z.lazy(() => ${resolvedName})`;
|
|
1397
|
+
return resolvedName;
|
|
1398
|
+
},
|
|
1399
|
+
object(node) {
|
|
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;
|
|
1413
|
+
});
|
|
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;
|
|
1423
|
+
},
|
|
1424
|
+
array(node) {
|
|
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;
|
|
1430
|
+
},
|
|
1431
|
+
tuple(node) {
|
|
1432
|
+
return `z.tuple(${buildList(mapSchemaItems(node, (item) => this.transform(item)).map(({ output }) => output).filter(Boolean))})`;
|
|
1433
|
+
},
|
|
1434
|
+
union(node) {
|
|
1435
|
+
const nodeMembers = node.members ?? [];
|
|
1436
|
+
const members = mapSchemaMembers(node, (memberNode) => this.transform(memberNode)).map(({ schema, output }) => output && node.strategy === "one" ? strictOneOfMember(output, schema) : output).filter(Boolean);
|
|
1437
|
+
if (members.length === 0) return "";
|
|
1438
|
+
if (members.length === 1) return members[0];
|
|
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)})`;
|
|
1442
|
+
},
|
|
1443
|
+
intersection(node) {
|
|
1444
|
+
const members = node.members ?? [];
|
|
1445
|
+
if (members.length === 0) return "";
|
|
1446
|
+
const [first, ...rest] = members;
|
|
1447
|
+
if (!first) return "";
|
|
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;
|
|
1457
|
+
const transformed = this.transform(member);
|
|
1458
|
+
return transformed ? `z.intersection(${acc}, ${transformed})` : acc;
|
|
1459
|
+
}, firstBase);
|
|
1460
|
+
}
|
|
121
1461
|
},
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
const
|
|
125
|
-
const
|
|
126
|
-
if (
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
1462
|
+
overrides: options.nodes,
|
|
1463
|
+
print(node) {
|
|
1464
|
+
const { keysToOmit } = this.options;
|
|
1465
|
+
const transformed = this.transform(node);
|
|
1466
|
+
if (!transformed) return null;
|
|
1467
|
+
const meta = syncSchemaRef(node);
|
|
1468
|
+
return applyMiniModifiers({
|
|
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,
|
|
1478
|
+
nullable: meta.nullable,
|
|
1479
|
+
optional: meta.optional,
|
|
1480
|
+
nullish: meta.nullish,
|
|
1481
|
+
defaultValue: meta.default
|
|
135
1482
|
});
|
|
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
|
-
|
|
1483
|
+
}
|
|
1484
|
+
};
|
|
1485
|
+
});
|
|
1486
|
+
//#endregion
|
|
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
|
+
*/
|
|
1546
|
+
const zodGenerator = defineGenerator({
|
|
1547
|
+
name: "zod",
|
|
1548
|
+
renderer: jsxRenderer,
|
|
1549
|
+
schema(node, ctx) {
|
|
1550
|
+
const { adapter, config, resolver, root } = ctx;
|
|
1551
|
+
const { output, coercion, guidType, regexType, mini, inferred, importPath, group, printer } = ctx.options;
|
|
1552
|
+
const dateType = getOasAdapter(adapter).options.dateType;
|
|
1553
|
+
if (!node.name) return;
|
|
1554
|
+
const isZodImport = ZOD_NAMESPACE_IMPORTS.has(importPath);
|
|
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({
|
|
1567
|
+
name: schemaName,
|
|
1568
|
+
extname: ".ts",
|
|
1569
|
+
root,
|
|
1570
|
+
output,
|
|
1571
|
+
group: group ?? void 0
|
|
1572
|
+
}).path
|
|
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
|
+
});
|
|
1581
|
+
const meta = {
|
|
1582
|
+
name: resolver.name(node.name),
|
|
1583
|
+
file: resolver.file({
|
|
1584
|
+
name: node.name,
|
|
1585
|
+
extname: ".ts",
|
|
1586
|
+
root,
|
|
1587
|
+
output,
|
|
1588
|
+
group: group ?? void 0
|
|
1589
|
+
})
|
|
1590
|
+
};
|
|
1591
|
+
const inferTypeName = inferred ? resolver.schema.typeName(node.name) : null;
|
|
1592
|
+
const stdPrinters = mini ? null : getStdPrinters(resolver, {
|
|
1593
|
+
coercion,
|
|
1594
|
+
guidType,
|
|
1595
|
+
regexType,
|
|
1596
|
+
dateType,
|
|
1597
|
+
cyclicSchemas,
|
|
1598
|
+
nodes: printer?.nodes
|
|
1599
|
+
});
|
|
1600
|
+
const schemaPrinter = mini ? getMiniPrinter(resolver, {
|
|
1601
|
+
guidType,
|
|
1602
|
+
regexType,
|
|
1603
|
+
cyclicSchemas,
|
|
1604
|
+
nodes: printer?.nodes
|
|
1605
|
+
}) : stdPrinters.output;
|
|
1606
|
+
return /* @__PURE__ */ jsxs(File, {
|
|
1607
|
+
baseName: meta.file.baseName,
|
|
1608
|
+
path: meta.file.path,
|
|
1609
|
+
meta: meta.file.meta,
|
|
1610
|
+
banner: resolver.default.banner(ctx.meta, {
|
|
1611
|
+
output,
|
|
1612
|
+
config,
|
|
1613
|
+
file: {
|
|
1614
|
+
path: meta.file.path,
|
|
1615
|
+
baseName: meta.file.baseName
|
|
1616
|
+
}
|
|
1617
|
+
}),
|
|
1618
|
+
footer: resolver.default.footer(ctx.meta, {
|
|
1619
|
+
output,
|
|
1620
|
+
config,
|
|
1621
|
+
file: {
|
|
1622
|
+
path: meta.file.path,
|
|
1623
|
+
baseName: meta.file.baseName
|
|
1624
|
+
}
|
|
1625
|
+
}),
|
|
1626
|
+
children: [
|
|
1627
|
+
/* @__PURE__ */ jsx(File.Import, {
|
|
1628
|
+
name: isZodImport ? "z" : ["z"],
|
|
1629
|
+
path: importPath,
|
|
1630
|
+
isNameSpace: isZodImport
|
|
1631
|
+
}),
|
|
1632
|
+
imports.map((imp) => /* @__PURE__ */ jsx(File.Import, {
|
|
1633
|
+
root: meta.file.path,
|
|
1634
|
+
path: imp.path,
|
|
1635
|
+
name: imp.name
|
|
1636
|
+
}, [
|
|
1637
|
+
node.name,
|
|
1638
|
+
imp.path,
|
|
1639
|
+
imp.name
|
|
1640
|
+
].join("-"))),
|
|
1641
|
+
/* @__PURE__ */ jsx(Zod, {
|
|
1642
|
+
name: meta.name,
|
|
1643
|
+
node,
|
|
1644
|
+
printer: schemaPrinter,
|
|
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)
|
|
1654
|
+
})
|
|
1655
|
+
]
|
|
1656
|
+
});
|
|
1657
|
+
},
|
|
1658
|
+
operation(node, ctx) {
|
|
1659
|
+
if (!ast.isHttpOperationNode(node)) return null;
|
|
1660
|
+
const { adapter, config, resolver, root } = ctx;
|
|
1661
|
+
const { output, coercion, guidType, regexType, mini, inferred, importPath, group, printer } = ctx.options;
|
|
1662
|
+
const dateType = getOasAdapter(adapter).options.dateType;
|
|
1663
|
+
const isZodImport = ZOD_NAMESPACE_IMPORTS.has(importPath);
|
|
1664
|
+
const meta = { file: resolver.file({
|
|
1665
|
+
name: node.operationId,
|
|
1666
|
+
extname: ".ts",
|
|
1667
|
+
tag: node.tags[0] ?? "default",
|
|
1668
|
+
path: node.path,
|
|
1669
|
+
root,
|
|
1670
|
+
output,
|
|
1671
|
+
group: group ?? void 0
|
|
1672
|
+
}) };
|
|
1673
|
+
const cyclicSchemas = new Set(ctx.meta.circularNames);
|
|
1674
|
+
function renderSchemaEntry({ schema, name, keysToOmit, direction = "output" }) {
|
|
1675
|
+
if (!schema) return null;
|
|
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,
|
|
164
1680
|
root,
|
|
165
1681
|
output,
|
|
166
|
-
|
|
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({
|
|
1686
|
+
guidType,
|
|
1687
|
+
regexType,
|
|
1688
|
+
resolver,
|
|
1689
|
+
keysToOmit,
|
|
1690
|
+
cyclicSchemas,
|
|
1691
|
+
nodes: printer?.nodes
|
|
1692
|
+
}) : getMiniPrinter(resolver, {
|
|
1693
|
+
guidType,
|
|
1694
|
+
regexType,
|
|
1695
|
+
cyclicSchemas,
|
|
1696
|
+
nodes: printer?.nodes
|
|
1697
|
+
}) : keysToOmit?.length ? printerZod({
|
|
1698
|
+
coercion,
|
|
1699
|
+
guidType,
|
|
1700
|
+
regexType,
|
|
1701
|
+
dateType,
|
|
1702
|
+
resolver,
|
|
1703
|
+
keysToOmit,
|
|
1704
|
+
cyclicSchemas,
|
|
1705
|
+
nodes: printer?.nodes,
|
|
1706
|
+
direction
|
|
1707
|
+
}) : getStdPrinters(resolver, {
|
|
1708
|
+
coercion,
|
|
1709
|
+
guidType,
|
|
1710
|
+
regexType,
|
|
1711
|
+
dateType,
|
|
1712
|
+
cyclicSchemas,
|
|
1713
|
+
nodes: printer?.nodes
|
|
1714
|
+
})[direction];
|
|
1715
|
+
return /* @__PURE__ */ jsxs(Fragment, { children: [imports.map((imp) => /* @__PURE__ */ jsx(File.Import, {
|
|
1716
|
+
root: meta.file.path,
|
|
1717
|
+
path: imp.path,
|
|
1718
|
+
name: imp.name
|
|
1719
|
+
}, [
|
|
1720
|
+
name,
|
|
1721
|
+
imp.path,
|
|
1722
|
+
imp.name
|
|
1723
|
+
].join("-"))), /* @__PURE__ */ jsx(Zod, {
|
|
1724
|
+
name,
|
|
1725
|
+
node: schema,
|
|
1726
|
+
printer: schemaPrinter,
|
|
1727
|
+
inferTypeName,
|
|
1728
|
+
cyclic: false
|
|
1729
|
+
})] });
|
|
1730
|
+
}
|
|
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({
|
|
1754
|
+
type: "ref",
|
|
1755
|
+
name: resolver.response.status(node, res.statusCode)
|
|
1756
|
+
}));
|
|
1757
|
+
if (fallbackUnknown && members.length === 0) return renderSchemaEntry({
|
|
1758
|
+
schema: ast.factory.createSchema({ type: "unknown" }),
|
|
1759
|
+
name
|
|
1760
|
+
});
|
|
1761
|
+
return renderSchemaEntry({
|
|
1762
|
+
schema: members.length === 1 ? members[0] : ast.factory.createSchema({
|
|
1763
|
+
type: "union",
|
|
1764
|
+
members
|
|
1765
|
+
}),
|
|
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
|
|
167
1782
|
});
|
|
168
|
-
|
|
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
|
|
1827
|
+
},
|
|
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"
|
|
1841
|
+
}) : null;
|
|
1842
|
+
return /* @__PURE__ */ jsxs(File, {
|
|
1843
|
+
baseName: meta.file.baseName,
|
|
1844
|
+
path: meta.file.path,
|
|
1845
|
+
meta: meta.file.meta,
|
|
1846
|
+
banner: resolver.default.banner(ctx.meta, {
|
|
1847
|
+
output,
|
|
1848
|
+
config,
|
|
1849
|
+
file: {
|
|
1850
|
+
path: meta.file.path,
|
|
1851
|
+
baseName: meta.file.baseName
|
|
1852
|
+
}
|
|
1853
|
+
}),
|
|
1854
|
+
footer: resolver.default.footer(ctx.meta, {
|
|
1855
|
+
output,
|
|
1856
|
+
config,
|
|
1857
|
+
file: {
|
|
1858
|
+
path: meta.file.path,
|
|
1859
|
+
baseName: meta.file.baseName
|
|
1860
|
+
}
|
|
1861
|
+
}),
|
|
1862
|
+
children: [
|
|
1863
|
+
/* @__PURE__ */ jsx(File.Import, {
|
|
1864
|
+
name: isZodImport ? "z" : ["z"],
|
|
1865
|
+
path: importPath,
|
|
1866
|
+
isNameSpace: isZodImport
|
|
1867
|
+
}),
|
|
1868
|
+
paramSchemas,
|
|
1869
|
+
responseSchemas,
|
|
1870
|
+
responseUnionSchema,
|
|
1871
|
+
errorUnionSchema,
|
|
1872
|
+
requestSchema,
|
|
1873
|
+
paramGroupSchemas,
|
|
1874
|
+
optionsSchema
|
|
1875
|
+
]
|
|
1876
|
+
});
|
|
1877
|
+
}
|
|
1878
|
+
});
|
|
1879
|
+
//#endregion
|
|
1880
|
+
//#region src/resolvers/resolverZod.ts
|
|
1881
|
+
/**
|
|
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.
|
|
1887
|
+
*
|
|
1888
|
+
* @example Resolve schema and type names
|
|
1889
|
+
* ```ts
|
|
1890
|
+
* import { resolverZod } from '@kubb/plugin-zod'
|
|
1891
|
+
*
|
|
1892
|
+
* resolverZod.name('list pets') // 'listPetsSchema'
|
|
1893
|
+
* resolverZod.schema.typeName('pet') // 'PetSchemaType'
|
|
1894
|
+
* ```
|
|
1895
|
+
*/
|
|
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" }));
|
|
1905
|
+
},
|
|
1906
|
+
type(name) {
|
|
1907
|
+
return ensureValidVarName(pascalCase(name, { suffix: "type" }));
|
|
1908
|
+
},
|
|
1909
|
+
inputName(name) {
|
|
1910
|
+
return this.name(`${name} input`);
|
|
1911
|
+
},
|
|
1912
|
+
inputTypeName(name) {
|
|
1913
|
+
return this.schema.typeName(`${name} input`);
|
|
169
1914
|
}
|
|
1915
|
+
},
|
|
1916
|
+
param: createOperationParamResolver(),
|
|
1917
|
+
response: {
|
|
1918
|
+
...createOperationResponseResolver(),
|
|
1919
|
+
error(node) {
|
|
1920
|
+
return this.name(`${node.operationId} Error`);
|
|
1921
|
+
},
|
|
1922
|
+
options(node) {
|
|
1923
|
+
return this.schema.type(this.name(`${node.operationId} Options`));
|
|
1924
|
+
}
|
|
1925
|
+
}
|
|
1926
|
+
});
|
|
1927
|
+
//#endregion
|
|
1928
|
+
//#region src/plugin.ts
|
|
1929
|
+
/**
|
|
1930
|
+
* Canonical plugin name for `@kubb/plugin-zod`. Used for driver lookups and
|
|
1931
|
+
* cross-plugin dependency references.
|
|
1932
|
+
*/
|
|
1933
|
+
const pluginZodName = "plugin-zod";
|
|
1934
|
+
/**
|
|
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.
|
|
1940
|
+
*
|
|
1941
|
+
* @example
|
|
1942
|
+
* ```ts
|
|
1943
|
+
* import { defineConfig } from 'kubb/config'
|
|
1944
|
+
* import { pluginTs } from '@kubb/plugin-ts'
|
|
1945
|
+
* import { pluginZod } from '@kubb/plugin-zod'
|
|
1946
|
+
*
|
|
1947
|
+
* export default defineConfig({
|
|
1948
|
+
* input: './petStore.yaml',
|
|
1949
|
+
* output: { path: './src/gen' },
|
|
1950
|
+
* plugins: [
|
|
1951
|
+
* pluginTs(),
|
|
1952
|
+
* pluginZod({
|
|
1953
|
+
* output: { path: './zod' },
|
|
1954
|
+
* inferred: true,
|
|
1955
|
+
* }),
|
|
1956
|
+
* ],
|
|
1957
|
+
* })
|
|
1958
|
+
* ```
|
|
1959
|
+
*/
|
|
1960
|
+
const pluginZod = definePlugin((options) => {
|
|
1961
|
+
const { output = {
|
|
1962
|
+
path: "zod",
|
|
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);
|
|
1966
|
+
return {
|
|
1967
|
+
name: pluginZodName,
|
|
1968
|
+
options,
|
|
1969
|
+
hooks: { "kubb:plugin:setup"(ctx) {
|
|
1970
|
+
ctx.setOptions({
|
|
1971
|
+
output,
|
|
1972
|
+
exclude,
|
|
1973
|
+
include,
|
|
1974
|
+
override,
|
|
1975
|
+
group: groupConfig,
|
|
1976
|
+
importPath,
|
|
1977
|
+
coercion,
|
|
1978
|
+
inferred,
|
|
1979
|
+
guidType,
|
|
1980
|
+
regexType,
|
|
1981
|
+
mini,
|
|
1982
|
+
printer
|
|
1983
|
+
});
|
|
1984
|
+
ctx.setResolver(userResolver ? Resolver.merge(resolverZod, userResolver) : resolverZod);
|
|
1985
|
+
if (userMacros?.length) ctx.setMacros(userMacros);
|
|
1986
|
+
ctx.addGenerator(zodGenerator);
|
|
1987
|
+
} }
|
|
170
1988
|
};
|
|
171
1989
|
});
|
|
172
1990
|
//#endregion
|
|
173
|
-
export { pluginZod, pluginZodName };
|
|
1991
|
+
export { pluginZod as default, pluginZod, pluginZodName, printerZod, printerZodMini, resolverZod, zodGenerator };
|
|
174
1992
|
|
|
175
1993
|
//# sourceMappingURL=index.js.map
|