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