@kubb/plugin-zod 5.0.0-beta.3 → 5.0.0-beta.31
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 +25 -5
- package/dist/index.cjs +644 -197
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.ts +103 -34
- package/dist/index.js +640 -199
- package/dist/index.js.map +1 -1
- package/extension.yaml +968 -0
- package/package.json +10 -13
- package/src/components/Operations.tsx +6 -5
- package/src/components/Zod.tsx +1 -1
- package/src/generators/zodGenerator.tsx +210 -60
- package/src/plugin.ts +23 -20
- package/src/printers/printerZod.ts +91 -68
- package/src/printers/printerZodMini.ts +46 -49
- package/src/resolvers/resolverZod.ts +31 -19
- package/src/types.ts +57 -21
- package/src/utils.ts +113 -36
package/dist/index.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
import "./chunk--u3MIqq1.js";
|
|
1
|
+
import { t as __name } from "./chunk--u3MIqq1.js";
|
|
2
2
|
import { ast, defineGenerator, definePlugin, defineResolver } from "@kubb/core";
|
|
3
|
-
import { Const, File, Type,
|
|
3
|
+
import { Const, File, Type, jsxRendererSync } from "@kubb/renderer-jsx";
|
|
4
4
|
import { Fragment, jsx, jsxs } from "@kubb/renderer-jsx/jsx-runtime";
|
|
5
5
|
//#region ../../internals/utils/src/casing.ts
|
|
6
6
|
/**
|
|
@@ -126,6 +126,212 @@ function toRegExpString(text, func = "RegExp") {
|
|
|
126
126
|
return `new ${func}(${JSON.stringify(source)}${flags ? `, ${JSON.stringify(flags)}` : ""})`;
|
|
127
127
|
}
|
|
128
128
|
//#endregion
|
|
129
|
+
//#region ../../internals/utils/src/reserved.ts
|
|
130
|
+
/**
|
|
131
|
+
* JavaScript and Java reserved words.
|
|
132
|
+
* @link https://github.com/jonschlinkert/reserved/blob/master/index.js
|
|
133
|
+
*/
|
|
134
|
+
const reservedWords = new Set([
|
|
135
|
+
"abstract",
|
|
136
|
+
"arguments",
|
|
137
|
+
"boolean",
|
|
138
|
+
"break",
|
|
139
|
+
"byte",
|
|
140
|
+
"case",
|
|
141
|
+
"catch",
|
|
142
|
+
"char",
|
|
143
|
+
"class",
|
|
144
|
+
"const",
|
|
145
|
+
"continue",
|
|
146
|
+
"debugger",
|
|
147
|
+
"default",
|
|
148
|
+
"delete",
|
|
149
|
+
"do",
|
|
150
|
+
"double",
|
|
151
|
+
"else",
|
|
152
|
+
"enum",
|
|
153
|
+
"eval",
|
|
154
|
+
"export",
|
|
155
|
+
"extends",
|
|
156
|
+
"false",
|
|
157
|
+
"final",
|
|
158
|
+
"finally",
|
|
159
|
+
"float",
|
|
160
|
+
"for",
|
|
161
|
+
"function",
|
|
162
|
+
"goto",
|
|
163
|
+
"if",
|
|
164
|
+
"implements",
|
|
165
|
+
"import",
|
|
166
|
+
"in",
|
|
167
|
+
"instanceof",
|
|
168
|
+
"int",
|
|
169
|
+
"interface",
|
|
170
|
+
"let",
|
|
171
|
+
"long",
|
|
172
|
+
"native",
|
|
173
|
+
"new",
|
|
174
|
+
"null",
|
|
175
|
+
"package",
|
|
176
|
+
"private",
|
|
177
|
+
"protected",
|
|
178
|
+
"public",
|
|
179
|
+
"return",
|
|
180
|
+
"short",
|
|
181
|
+
"static",
|
|
182
|
+
"super",
|
|
183
|
+
"switch",
|
|
184
|
+
"synchronized",
|
|
185
|
+
"this",
|
|
186
|
+
"throw",
|
|
187
|
+
"throws",
|
|
188
|
+
"transient",
|
|
189
|
+
"true",
|
|
190
|
+
"try",
|
|
191
|
+
"typeof",
|
|
192
|
+
"var",
|
|
193
|
+
"void",
|
|
194
|
+
"volatile",
|
|
195
|
+
"while",
|
|
196
|
+
"with",
|
|
197
|
+
"yield",
|
|
198
|
+
"Array",
|
|
199
|
+
"Date",
|
|
200
|
+
"hasOwnProperty",
|
|
201
|
+
"Infinity",
|
|
202
|
+
"isFinite",
|
|
203
|
+
"isNaN",
|
|
204
|
+
"isPrototypeOf",
|
|
205
|
+
"length",
|
|
206
|
+
"Math",
|
|
207
|
+
"name",
|
|
208
|
+
"NaN",
|
|
209
|
+
"Number",
|
|
210
|
+
"Object",
|
|
211
|
+
"prototype",
|
|
212
|
+
"String",
|
|
213
|
+
"toString",
|
|
214
|
+
"undefined",
|
|
215
|
+
"valueOf"
|
|
216
|
+
]);
|
|
217
|
+
/**
|
|
218
|
+
* Returns `true` when `name` is a syntactically valid JavaScript variable name.
|
|
219
|
+
*
|
|
220
|
+
* @example
|
|
221
|
+
* ```ts
|
|
222
|
+
* isValidVarName('status') // true
|
|
223
|
+
* isValidVarName('class') // false (reserved word)
|
|
224
|
+
* isValidVarName('42foo') // false (starts with digit)
|
|
225
|
+
* ```
|
|
226
|
+
*/
|
|
227
|
+
function isValidVarName(name) {
|
|
228
|
+
if (!name || reservedWords.has(name)) return false;
|
|
229
|
+
return /^[a-zA-Z_$][a-zA-Z0-9_$]*$/.test(name);
|
|
230
|
+
}
|
|
231
|
+
/**
|
|
232
|
+
* Returns `name` when it's a syntactically valid JavaScript variable name,
|
|
233
|
+
* otherwise prefixes it with `_` so the result is a valid identifier.
|
|
234
|
+
*
|
|
235
|
+
* Useful for sanitizing OpenAPI schema names or operation IDs that start with
|
|
236
|
+
* a digit (e.g. `409`, `504AccountCancel`) before using them as exported
|
|
237
|
+
* variable, type, or function names.
|
|
238
|
+
*
|
|
239
|
+
* @example
|
|
240
|
+
* ```ts
|
|
241
|
+
* ensureValidVarName('409') // '_409'
|
|
242
|
+
* ensureValidVarName('504AccountCancel') // '_504AccountCancel'
|
|
243
|
+
* ensureValidVarName('Pet') // 'Pet'
|
|
244
|
+
* ensureValidVarName('class') // '_class'
|
|
245
|
+
* ```
|
|
246
|
+
*/
|
|
247
|
+
function ensureValidVarName(name) {
|
|
248
|
+
if (!name || isValidVarName(name)) return name;
|
|
249
|
+
return `_${name}`;
|
|
250
|
+
}
|
|
251
|
+
//#endregion
|
|
252
|
+
//#region ../../internals/shared/src/operation.ts
|
|
253
|
+
/**
|
|
254
|
+
* Maps a content type to the PascalCase suffix used to name per-content-type variants
|
|
255
|
+
* (e.g. `application/json` → `Json`, `application/xml` → `Xml`, `multipart/form-data` → `FormData`).
|
|
256
|
+
*/
|
|
257
|
+
function getContentTypeSuffix(contentType) {
|
|
258
|
+
const baseType = contentType.split(";")[0].trim();
|
|
259
|
+
if (baseType === "application/json") return "Json";
|
|
260
|
+
if (baseType === "multipart/form-data") return "FormData";
|
|
261
|
+
if (baseType === "application/x-www-form-urlencoded") return "FormUrlEncoded";
|
|
262
|
+
const parts = (baseType.split("/").pop() ?? baseType).split(/[^a-zA-Z0-9]+/).filter(Boolean);
|
|
263
|
+
if (parts.length === 0) return "Unknown";
|
|
264
|
+
return parts.map((part) => part.charAt(0).toUpperCase() + part.slice(1)).join("");
|
|
265
|
+
}
|
|
266
|
+
/**
|
|
267
|
+
* Appends a content-type suffix to a base name, keeping a trailing `Data` segment last
|
|
268
|
+
* (e.g. `AddPetData` + `Json` → `AddPetJsonData`, `AddPetStatus200` + `Xml` → `AddPetStatus200Xml`).
|
|
269
|
+
*/
|
|
270
|
+
function getPerContentTypeName(baseName, suffix) {
|
|
271
|
+
if (baseName.endsWith("Data")) return suffix.endsWith("Data") ? baseName.slice(0, -4) + suffix : `${baseName.slice(0, -4)}${suffix}Data`;
|
|
272
|
+
return baseName + suffix;
|
|
273
|
+
}
|
|
274
|
+
/**
|
|
275
|
+
* Resolves per-content-type variant names for a set of content entries, deduplicating suffix
|
|
276
|
+
* collisions with a numeric counter. Entries without a schema are skipped. The returned `suffix` is
|
|
277
|
+
* the final (possibly counter-augmented) value, so callers can derive parallel names in another
|
|
278
|
+
* namespace (e.g. plugin-faker deriving the matching plugin-ts type name).
|
|
279
|
+
*/
|
|
280
|
+
function resolveContentTypeVariants(entries, baseName) {
|
|
281
|
+
const usedNames = /* @__PURE__ */ new Set();
|
|
282
|
+
return entries.filter((entry) => entry.schema).map((entry) => {
|
|
283
|
+
const baseSuffix = getContentTypeSuffix(entry.contentType);
|
|
284
|
+
let suffix = baseSuffix;
|
|
285
|
+
let name = getPerContentTypeName(baseName, suffix);
|
|
286
|
+
let counter = 2;
|
|
287
|
+
while (usedNames.has(name)) {
|
|
288
|
+
suffix = `${baseSuffix}${counter++}`;
|
|
289
|
+
name = getPerContentTypeName(baseName, suffix);
|
|
290
|
+
}
|
|
291
|
+
usedNames.add(name);
|
|
292
|
+
return {
|
|
293
|
+
name,
|
|
294
|
+
suffix,
|
|
295
|
+
schema: entry.schema,
|
|
296
|
+
keysToOmit: entry.keysToOmit,
|
|
297
|
+
contentType: entry.contentType
|
|
298
|
+
};
|
|
299
|
+
});
|
|
300
|
+
}
|
|
301
|
+
//#endregion
|
|
302
|
+
//#region ../../internals/shared/src/group.ts
|
|
303
|
+
/**
|
|
304
|
+
* Builds the `group` config a Kubb plugin passes to `ctx.setOptions`, applying the
|
|
305
|
+
* shared default naming so every plugin groups output consistently:
|
|
306
|
+
*
|
|
307
|
+
* - `path` groups use the second path segment (`/pet/findByStatus` → `pet`).
|
|
308
|
+
* - other groups use `${camelCase(group)}${suffix}` (e.g. `petController`).
|
|
309
|
+
*
|
|
310
|
+
* Returns `null` when grouping is disabled, matching the per-plugin convention.
|
|
311
|
+
*
|
|
312
|
+
* @param group - The user-supplied group option, or `undefined` to disable grouping.
|
|
313
|
+
* @param options.suffix - Appended to non-`path` group names, e.g. `'Controller'` or `'Requests'`.
|
|
314
|
+
* @param options.honorName - When `true`, a user-provided `group.name` overrides the default namer.
|
|
315
|
+
*
|
|
316
|
+
* @example
|
|
317
|
+
* ```ts
|
|
318
|
+
* createGroupConfig(group, { suffix: 'Controller' }) // plugin-ts, plugin-zod
|
|
319
|
+
* createGroupConfig(group, { suffix: 'Controller', honorName: true }) // plugin-faker, plugin-client, …
|
|
320
|
+
* createGroupConfig(group, { suffix: 'Requests', honorName: true }) // plugin-cypress, plugin-mcp
|
|
321
|
+
* ```
|
|
322
|
+
*/
|
|
323
|
+
function createGroupConfig(group, options) {
|
|
324
|
+
if (!group) return null;
|
|
325
|
+
const defaultName = (ctx) => {
|
|
326
|
+
if (group.type === "path") return `${ctx.group.split("/")[1]}`;
|
|
327
|
+
return `${camelCase(ctx.group)}${options.suffix}`;
|
|
328
|
+
};
|
|
329
|
+
return {
|
|
330
|
+
...group,
|
|
331
|
+
name: options.honorName && group.name ? group.name : defaultName
|
|
332
|
+
};
|
|
333
|
+
}
|
|
334
|
+
//#endregion
|
|
129
335
|
//#region src/components/Operations.tsx
|
|
130
336
|
function Operations({ name, operations }) {
|
|
131
337
|
const operationsJSON = operations.reduce((prev, acc) => {
|
|
@@ -133,6 +339,7 @@ function Operations({ name, operations }) {
|
|
|
133
339
|
return prev;
|
|
134
340
|
}, {});
|
|
135
341
|
const pathsJSON = operations.reduce((prev, acc) => {
|
|
342
|
+
if (!ast.isHttpOperationNode(acc.node)) return prev;
|
|
136
343
|
prev[`"${acc.node.path}"`] = {
|
|
137
344
|
...prev[`"${acc.node.path}"`] ?? {},
|
|
138
345
|
[acc.node.method]: `operations["${acc.node.operationId}"]`
|
|
@@ -242,6 +449,61 @@ function shouldCoerce(coercion, type) {
|
|
|
242
449
|
return !!coercion[type];
|
|
243
450
|
}
|
|
244
451
|
/**
|
|
452
|
+
* Registered codecs, checked in order.
|
|
453
|
+
*/
|
|
454
|
+
const codecs = [{
|
|
455
|
+
matches(node) {
|
|
456
|
+
return node.type === "date" && node.representation === "date";
|
|
457
|
+
},
|
|
458
|
+
decode(node) {
|
|
459
|
+
return node.format === "date" ? "z.iso.date().transform((value) => new Date(value))" : "z.iso.datetime().transform((value) => new Date(value))";
|
|
460
|
+
},
|
|
461
|
+
encode(node) {
|
|
462
|
+
return node.format === "date" ? "z.date().transform((value) => value.toISOString().slice(0, 10))" : "z.date().transform((value) => value.toISOString())";
|
|
463
|
+
}
|
|
464
|
+
}];
|
|
465
|
+
/**
|
|
466
|
+
* Returns the codec for this node, or `undefined` when the node needs no
|
|
467
|
+
* encode/decode (its wire and runtime types match).
|
|
468
|
+
*/
|
|
469
|
+
function getCodec(node) {
|
|
470
|
+
if (!node) return void 0;
|
|
471
|
+
return codecs.find((codec) => codec.matches(node));
|
|
472
|
+
}
|
|
473
|
+
/**
|
|
474
|
+
* Returns `true` when the node itself is encoded/decoded by a codec.
|
|
475
|
+
*/
|
|
476
|
+
function hasCodec(node) {
|
|
477
|
+
return getCodec(node) !== void 0;
|
|
478
|
+
}
|
|
479
|
+
/**
|
|
480
|
+
* Returns `true` when the schema transitively contains a codec node —
|
|
481
|
+
* a value whose runtime type differs from its wire type (see {@link hasCodec}),
|
|
482
|
+
* so it must be decoded (response) or encoded (request) at the validation boundary.
|
|
483
|
+
* `$ref`s are followed via their resolved schema; a `seen` set guards cycles.
|
|
484
|
+
*/
|
|
485
|
+
function containsCodec(node, seen = /* @__PURE__ */ new Set()) {
|
|
486
|
+
if (!node) return false;
|
|
487
|
+
if (hasCodec(node)) return true;
|
|
488
|
+
if (node.type === "ref") {
|
|
489
|
+
if (!node.ref) return false;
|
|
490
|
+
const refName = ast.extractRefName(node.ref);
|
|
491
|
+
if (refName) {
|
|
492
|
+
if (seen.has(refName)) return false;
|
|
493
|
+
seen.add(refName);
|
|
494
|
+
}
|
|
495
|
+
const resolved = ast.syncSchemaRef(node);
|
|
496
|
+
if (resolved.type === "ref") return false;
|
|
497
|
+
return containsCodec(resolved, seen);
|
|
498
|
+
}
|
|
499
|
+
const children = [];
|
|
500
|
+
if ("properties" in node && node.properties) children.push(...node.properties.map((prop) => prop.schema));
|
|
501
|
+
if ("items" in node && node.items) children.push(...node.items);
|
|
502
|
+
if ("members" in node && node.members) children.push(...node.members);
|
|
503
|
+
if ("additionalProperties" in node && node.additionalProperties && node.additionalProperties !== true) children.push(node.additionalProperties);
|
|
504
|
+
return children.some((child) => containsCodec(child, seen));
|
|
505
|
+
}
|
|
506
|
+
/**
|
|
245
507
|
* Collects all resolved schema names for an operation's parameters and responses
|
|
246
508
|
* into a single lookup object, useful for building imports and type references.
|
|
247
509
|
*/
|
|
@@ -261,11 +523,11 @@ function buildSchemaNames(node, { params, resolver }) {
|
|
|
261
523
|
}
|
|
262
524
|
responses["default"] = resolver.resolveResponseName(node);
|
|
263
525
|
return {
|
|
264
|
-
request: node.requestBody?.content?.[0]?.schema ? resolver.resolveDataName(node) :
|
|
526
|
+
request: node.requestBody?.content?.[0]?.schema ? resolver.resolveDataName(node) : null,
|
|
265
527
|
parameters: {
|
|
266
|
-
path: pathParam ? resolver.resolvePathParamsName(node, pathParam) :
|
|
267
|
-
query: queryParam ? resolver.resolveQueryParamsName(node, queryParam) :
|
|
268
|
-
header: headerParam ? resolver.resolveHeaderParamsName(node, headerParam) :
|
|
528
|
+
path: pathParam ? resolver.resolvePathParamsName(node, pathParam) : null,
|
|
529
|
+
query: queryParam ? resolver.resolveQueryParamsName(node, queryParam) : null,
|
|
530
|
+
header: headerParam ? resolver.resolveHeaderParamsName(node, headerParam) : null
|
|
269
531
|
},
|
|
270
532
|
responses,
|
|
271
533
|
errors
|
|
@@ -339,30 +601,44 @@ function lengthChecksMini({ min, max, pattern }) {
|
|
|
339
601
|
* to a schema value string using the chainable Zod v4 API.
|
|
340
602
|
*/
|
|
341
603
|
function applyModifiers({ value, nullable, optional, nullish, defaultValue, description }) {
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
604
|
+
const withModifier = (() => {
|
|
605
|
+
if (nullish || nullable && optional) return `${value}.nullish()`;
|
|
606
|
+
if (optional) return `${value}.optional()`;
|
|
607
|
+
if (nullable) return `${value}.nullable()`;
|
|
608
|
+
return value;
|
|
609
|
+
})();
|
|
610
|
+
const withDefault = defaultValue !== void 0 ? `${withModifier}.default(${formatDefault(defaultValue)})` : withModifier;
|
|
611
|
+
return description ? `${withDefault}.describe(${stringify(description)})` : withDefault;
|
|
349
612
|
}
|
|
350
613
|
/**
|
|
351
614
|
* Apply nullable / optional / nullish modifiers using the functional `zod/mini` API
|
|
352
615
|
* (`z.nullable()`, `z.optional()`, `z.nullish()`).
|
|
353
616
|
*/
|
|
354
617
|
function applyMiniModifiers({ value, nullable, optional, nullish, defaultValue }) {
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
}
|
|
361
|
-
if (defaultValue !== void 0) result = `z._default(${result}, ${formatDefault(defaultValue)})`;
|
|
362
|
-
return result;
|
|
618
|
+
const withModifier = (() => {
|
|
619
|
+
if (nullish) return `z.nullish(${value})`;
|
|
620
|
+
const withNullable = nullable ? `z.nullable(${value})` : value;
|
|
621
|
+
return optional ? `z.optional(${withNullable})` : withNullable;
|
|
622
|
+
})();
|
|
623
|
+
return defaultValue !== void 0 ? `z._default(${withModifier}, ${formatDefault(defaultValue)})` : withModifier;
|
|
363
624
|
}
|
|
364
625
|
//#endregion
|
|
365
626
|
//#region src/printers/printerZod.ts
|
|
627
|
+
function strictOneOfMember$1(member, node) {
|
|
628
|
+
if (node.type === "object" && node.additionalProperties === void 0) return `${member}.strict()`;
|
|
629
|
+
if (node.type === "ref") {
|
|
630
|
+
if (member.startsWith("z.lazy(")) return member;
|
|
631
|
+
const schema = ast.syncSchemaRef(node);
|
|
632
|
+
if (schema.type === "object" && (schema.additionalProperties === void 0 || schema.additionalProperties === false)) return `${member}.strict()`;
|
|
633
|
+
}
|
|
634
|
+
return member;
|
|
635
|
+
}
|
|
636
|
+
__name(strictOneOfMember$1, "strictOneOfMember");
|
|
637
|
+
function getMemberConstraint(member) {
|
|
638
|
+
if (member.primitive === "string") return lengthConstraints(ast.narrowSchema(member, "string") ?? {}) || void 0;
|
|
639
|
+
if (member.primitive === "number" || member.primitive === "integer") return numberConstraints(ast.narrowSchema(member, "number") ?? ast.narrowSchema(member, "integer") ?? {}) || void 0;
|
|
640
|
+
if (member.primitive === "array") return lengthConstraints(ast.narrowSchema(member, "array") ?? {}) || void 0;
|
|
641
|
+
}
|
|
366
642
|
/**
|
|
367
643
|
* Zod v4 printer built with `definePrinter`.
|
|
368
644
|
*
|
|
@@ -399,8 +675,9 @@ const printerZod = ast.definePrinter((options) => {
|
|
|
399
675
|
return shouldCoerce(this.options.coercion, "numbers") ? "z.coerce.bigint()" : "z.bigint()";
|
|
400
676
|
},
|
|
401
677
|
date(node) {
|
|
402
|
-
|
|
403
|
-
return
|
|
678
|
+
const codec = getCodec(node);
|
|
679
|
+
if (codec) return this.options.direction === "input" ? codec.encode(node) : codec.decode(node);
|
|
680
|
+
return "z.iso.date()";
|
|
404
681
|
},
|
|
405
682
|
datetime(node) {
|
|
406
683
|
const offset = node.offset || this.options.dateType === "stringOffset";
|
|
@@ -435,29 +712,30 @@ const printerZod = ast.definePrinter((options) => {
|
|
|
435
712
|
return `z.enum([${nonNullValues.map(formatLiteral).join(", ")}])`;
|
|
436
713
|
},
|
|
437
714
|
ref(node) {
|
|
438
|
-
if (!node.name) return
|
|
715
|
+
if (!node.name) return null;
|
|
439
716
|
const refName = node.ref ? ast.extractRefName(node.ref) ?? node.name : node.name;
|
|
440
|
-
const
|
|
717
|
+
const useInputVariant = node.ref != null && this.options.direction === "input" && containsCodec(node);
|
|
718
|
+
const resolvedName = node.ref ? useInputVariant ? this.options.resolver?.resolveInputSchemaName(refName) ?? refName : this.options.resolver?.default(refName, "function") ?? refName : node.name;
|
|
441
719
|
if (node.ref && this.options.cyclicSchemas?.has(refName)) return `z.lazy(() => ${resolvedName})`;
|
|
442
720
|
return resolvedName;
|
|
443
721
|
},
|
|
444
722
|
object(node) {
|
|
445
|
-
|
|
723
|
+
const objectBase = `z.object({\n ${node.properties.map((prop) => {
|
|
446
724
|
const { name: propName, schema } = prop;
|
|
447
725
|
const meta = ast.syncSchemaRef(schema);
|
|
448
726
|
const isNullable = meta.nullable;
|
|
449
727
|
const isOptional = schema.optional;
|
|
450
728
|
const isNullish = schema.nullish;
|
|
451
729
|
const hasSelfRef = this.options.cyclicSchemas != null && ast.containsCircularRef(schema, { circularSchemas: this.options.cyclicSchemas });
|
|
730
|
+
const savedCyclicSchemas = this.options.cyclicSchemas;
|
|
452
731
|
if (hasSelfRef) this.options.cyclicSchemas = void 0;
|
|
453
732
|
const baseOutput = this.transform(schema) ?? this.transform(ast.createSchema({ type: "unknown" }));
|
|
454
|
-
if (hasSelfRef) this.options.cyclicSchemas =
|
|
733
|
+
if (hasSelfRef) this.options.cyclicSchemas = savedCyclicSchemas;
|
|
455
734
|
const wrappedOutput = this.options.wrapOutput ? this.options.wrapOutput({
|
|
456
735
|
output: baseOutput,
|
|
457
736
|
schema
|
|
458
737
|
}) || baseOutput : baseOutput;
|
|
459
|
-
|
|
460
|
-
if (schema.type !== "ref" && meta.type === "ref") descriptionToApply = void 0;
|
|
738
|
+
const descriptionToApply = schema.type !== "ref" && meta.type === "ref" ? void 0 : meta.description;
|
|
461
739
|
const value = applyModifiers({
|
|
462
740
|
value: wrappedOutput,
|
|
463
741
|
nullable: isNullable,
|
|
@@ -469,24 +747,29 @@ const printerZod = ast.definePrinter((options) => {
|
|
|
469
747
|
if (hasSelfRef) return `get "${propName}"() { return ${value} }`;
|
|
470
748
|
return `"${propName}": ${value}`;
|
|
471
749
|
}).join(",\n ")}\n })`;
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
750
|
+
return (() => {
|
|
751
|
+
if (node.additionalProperties && node.additionalProperties !== true) {
|
|
752
|
+
const catchallType = this.transform(node.additionalProperties);
|
|
753
|
+
return catchallType ? `${objectBase}.catchall(${catchallType})` : objectBase;
|
|
754
|
+
}
|
|
755
|
+
if (node.additionalProperties === true) return `${objectBase}.catchall(${this.transform(ast.createSchema({ type: "unknown" }))})`;
|
|
756
|
+
if (node.additionalProperties === false) return `${objectBase}.strict()`;
|
|
757
|
+
return objectBase;
|
|
758
|
+
})();
|
|
478
759
|
},
|
|
479
760
|
array(node) {
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
return result;
|
|
761
|
+
const base = `z.array(${(node.items ?? []).map((item) => this.transform(item)).filter(Boolean).join(", ") || this.transform(ast.createSchema({ type: "unknown" }))})${lengthConstraints(node)}`;
|
|
762
|
+
return node.unique ? `${base}.refine(items => new Set(items).size === items.length, { message: "Array entries must be unique" })` : base;
|
|
483
763
|
},
|
|
484
764
|
tuple(node) {
|
|
485
765
|
return `z.tuple([${(node.items ?? []).map((item) => this.transform(item)).filter(Boolean).join(", ")}])`;
|
|
486
766
|
},
|
|
487
767
|
union(node) {
|
|
488
768
|
const nodeMembers = node.members ?? [];
|
|
489
|
-
const members = nodeMembers.map((
|
|
769
|
+
const members = nodeMembers.map((memberNode) => {
|
|
770
|
+
const member = this.transform(memberNode);
|
|
771
|
+
return member && node.strategy === "one" ? strictOneOfMember$1(member, memberNode) : member;
|
|
772
|
+
}).filter(Boolean);
|
|
490
773
|
if (members.length === 0) return "";
|
|
491
774
|
if (members.length === 1) return members[0];
|
|
492
775
|
if (node.discriminatorPropertyName && !nodeMembers.some((m) => m.type === "intersection")) return `z.discriminatedUnion(${stringify(node.discriminatorPropertyName)}, [${members.join(", ")}])`;
|
|
@@ -497,47 +780,29 @@ const printerZod = ast.definePrinter((options) => {
|
|
|
497
780
|
if (members.length === 0) return "";
|
|
498
781
|
const [first, ...rest] = members;
|
|
499
782
|
if (!first) return "";
|
|
500
|
-
|
|
501
|
-
if (!
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
if (c) {
|
|
506
|
-
base += c;
|
|
507
|
-
continue;
|
|
508
|
-
}
|
|
509
|
-
} else if (member.primitive === "number" || member.primitive === "integer") {
|
|
510
|
-
const c = numberConstraints(ast.narrowSchema(member, "number") ?? ast.narrowSchema(member, "integer") ?? {});
|
|
511
|
-
if (c) {
|
|
512
|
-
base += c;
|
|
513
|
-
continue;
|
|
514
|
-
}
|
|
515
|
-
} else if (member.primitive === "array") {
|
|
516
|
-
const c = lengthConstraints(ast.narrowSchema(member, "array") ?? {});
|
|
517
|
-
if (c) {
|
|
518
|
-
base += c;
|
|
519
|
-
continue;
|
|
520
|
-
}
|
|
521
|
-
}
|
|
783
|
+
const firstBase = this.transform(first);
|
|
784
|
+
if (!firstBase) return "";
|
|
785
|
+
return rest.reduce((acc, member) => {
|
|
786
|
+
const constraint = getMemberConstraint(member);
|
|
787
|
+
if (constraint) return acc + constraint;
|
|
522
788
|
const transformed = this.transform(member);
|
|
523
|
-
|
|
524
|
-
}
|
|
525
|
-
return base;
|
|
789
|
+
return transformed ? `${acc}.and(${transformed})` : acc;
|
|
790
|
+
}, firstBase);
|
|
526
791
|
},
|
|
527
792
|
...options.nodes
|
|
528
793
|
},
|
|
529
794
|
print(node) {
|
|
530
795
|
const { keysToOmit } = this.options;
|
|
531
|
-
|
|
532
|
-
if (!
|
|
796
|
+
const transformed = this.transform(node);
|
|
797
|
+
if (!transformed) return null;
|
|
533
798
|
const meta = ast.syncSchemaRef(node);
|
|
534
|
-
if (keysToOmit?.length && meta.primitive === "object" && !(meta.type === "union" && meta.discriminatorPropertyName)) {
|
|
535
|
-
const lazyMatch = base.match(/^z\.lazy\(\(\)\s*=>\s*(.+)\)$/);
|
|
536
|
-
if (lazyMatch) base = `z.lazy(() => ${lazyMatch[1]}.omit({ ${keysToOmit.map((k) => `"${k}": true`).join(", ")} }))`;
|
|
537
|
-
else base = `${base}.omit({ ${keysToOmit.map((k) => `"${k}": true`).join(", ")} })`;
|
|
538
|
-
}
|
|
539
799
|
return applyModifiers({
|
|
540
|
-
value:
|
|
800
|
+
value: (() => {
|
|
801
|
+
if (!keysToOmit?.length || meta.primitive !== "object" || meta.type === "union" && meta.discriminatorPropertyName) return transformed;
|
|
802
|
+
const lazyMatch = transformed.match(/^z\.lazy\(\(\)\s*=>\s*(.+)\)$/);
|
|
803
|
+
if (lazyMatch) return `z.lazy(() => ${lazyMatch[1]}.omit({ ${keysToOmit.map((k) => `"${k}": true`).join(", ")} }))`;
|
|
804
|
+
return `${transformed}.omit({ ${keysToOmit.map((k) => `"${k}": true`).join(", ")} })`;
|
|
805
|
+
})(),
|
|
541
806
|
nullable: meta.nullable,
|
|
542
807
|
optional: meta.optional,
|
|
543
808
|
nullish: meta.nullish,
|
|
@@ -549,6 +814,15 @@ const printerZod = ast.definePrinter((options) => {
|
|
|
549
814
|
});
|
|
550
815
|
//#endregion
|
|
551
816
|
//#region src/printers/printerZodMini.ts
|
|
817
|
+
function strictOneOfMember(member, node) {
|
|
818
|
+
if (node.type === "object" && (node.additionalProperties === void 0 || node.additionalProperties === false)) return member.replace(/^z\.object\(/, "z.strictObject(");
|
|
819
|
+
return member;
|
|
820
|
+
}
|
|
821
|
+
function getMemberConstraintMini(member) {
|
|
822
|
+
if (member.primitive === "string") return lengthChecksMini(ast.narrowSchema(member, "string") ?? {}) || void 0;
|
|
823
|
+
if (member.primitive === "number" || member.primitive === "integer") return numberChecksMini(ast.narrowSchema(member, "number") ?? ast.narrowSchema(member, "integer") ?? {}) || void 0;
|
|
824
|
+
if (member.primitive === "array") return lengthChecksMini(ast.narrowSchema(member, "array") ?? {}) || void 0;
|
|
825
|
+
}
|
|
552
826
|
/**
|
|
553
827
|
* Zod v4 **Mini** printer built with `definePrinter`.
|
|
554
828
|
*
|
|
@@ -617,7 +891,7 @@ const printerZodMini = ast.definePrinter((options) => {
|
|
|
617
891
|
return `z.enum([${nonNullValues.map(formatLiteral).join(", ")}])`;
|
|
618
892
|
},
|
|
619
893
|
ref(node) {
|
|
620
|
-
if (!node.name) return
|
|
894
|
+
if (!node.name) return null;
|
|
621
895
|
const refName = node.ref ? ast.extractRefName(node.ref) ?? node.name : node.name;
|
|
622
896
|
const resolvedName = node.ref ? this.options.resolver?.default(refName, "function") ?? refName : node.name;
|
|
623
897
|
if (node.ref && this.options.cyclicSchemas?.has(refName)) return `z.lazy(() => ${resolvedName})`;
|
|
@@ -631,9 +905,10 @@ const printerZodMini = ast.definePrinter((options) => {
|
|
|
631
905
|
const isOptional = schema.optional;
|
|
632
906
|
const isNullish = schema.nullish;
|
|
633
907
|
const hasSelfRef = this.options.cyclicSchemas != null && ast.containsCircularRef(schema, { circularSchemas: this.options.cyclicSchemas });
|
|
908
|
+
const savedCyclicSchemas = this.options.cyclicSchemas;
|
|
634
909
|
if (hasSelfRef) this.options.cyclicSchemas = void 0;
|
|
635
910
|
const baseOutput = this.transform(schema) ?? this.transform(ast.createSchema({ type: "unknown" }));
|
|
636
|
-
if (hasSelfRef) this.options.cyclicSchemas =
|
|
911
|
+
if (hasSelfRef) this.options.cyclicSchemas = savedCyclicSchemas;
|
|
637
912
|
const value = applyMiniModifiers({
|
|
638
913
|
value: this.options.wrapOutput ? this.options.wrapOutput({
|
|
639
914
|
output: baseOutput,
|
|
@@ -649,16 +924,18 @@ const printerZodMini = ast.definePrinter((options) => {
|
|
|
649
924
|
}).join(",\n ")}\n })`;
|
|
650
925
|
},
|
|
651
926
|
array(node) {
|
|
652
|
-
|
|
653
|
-
|
|
654
|
-
return result;
|
|
927
|
+
const base = `z.array(${(node.items ?? []).map((item) => this.transform(item)).filter(Boolean).join(", ") || this.transform(ast.createSchema({ type: "unknown" }))})${lengthChecksMini(node)}`;
|
|
928
|
+
return node.unique ? `${base}.refine(items => new Set(items).size === items.length, { message: "Array entries must be unique" })` : base;
|
|
655
929
|
},
|
|
656
930
|
tuple(node) {
|
|
657
931
|
return `z.tuple([${(node.items ?? []).map((item) => this.transform(item)).filter(Boolean).join(", ")}])`;
|
|
658
932
|
},
|
|
659
933
|
union(node) {
|
|
660
934
|
const nodeMembers = node.members ?? [];
|
|
661
|
-
const members = nodeMembers.map((
|
|
935
|
+
const members = nodeMembers.map((memberNode) => {
|
|
936
|
+
const member = this.transform(memberNode);
|
|
937
|
+
return member && node.strategy === "one" ? strictOneOfMember(member, memberNode) : member;
|
|
938
|
+
}).filter(Boolean);
|
|
662
939
|
if (members.length === 0) return "";
|
|
663
940
|
if (members.length === 1) return members[0];
|
|
664
941
|
if (node.discriminatorPropertyName && !nodeMembers.some((m) => m.type === "intersection")) return `z.discriminatedUnion(${stringify(node.discriminatorPropertyName)}, [${members.join(", ")}])`;
|
|
@@ -669,47 +946,29 @@ const printerZodMini = ast.definePrinter((options) => {
|
|
|
669
946
|
if (members.length === 0) return "";
|
|
670
947
|
const [first, ...rest] = members;
|
|
671
948
|
if (!first) return "";
|
|
672
|
-
|
|
673
|
-
if (!
|
|
674
|
-
|
|
675
|
-
|
|
676
|
-
|
|
677
|
-
if (c) {
|
|
678
|
-
base += c;
|
|
679
|
-
continue;
|
|
680
|
-
}
|
|
681
|
-
} else if (member.primitive === "number" || member.primitive === "integer") {
|
|
682
|
-
const c = numberChecksMini(ast.narrowSchema(member, "number") ?? ast.narrowSchema(member, "integer") ?? {});
|
|
683
|
-
if (c) {
|
|
684
|
-
base += c;
|
|
685
|
-
continue;
|
|
686
|
-
}
|
|
687
|
-
} else if (member.primitive === "array") {
|
|
688
|
-
const c = lengthChecksMini(ast.narrowSchema(member, "array") ?? {});
|
|
689
|
-
if (c) {
|
|
690
|
-
base += c;
|
|
691
|
-
continue;
|
|
692
|
-
}
|
|
693
|
-
}
|
|
949
|
+
const firstBase = this.transform(first);
|
|
950
|
+
if (!firstBase) return "";
|
|
951
|
+
return rest.reduce((acc, member) => {
|
|
952
|
+
const constraint = getMemberConstraintMini(member);
|
|
953
|
+
if (constraint) return acc + constraint;
|
|
694
954
|
const transformed = this.transform(member);
|
|
695
|
-
|
|
696
|
-
}
|
|
697
|
-
return base;
|
|
955
|
+
return transformed ? `z.intersection(${acc}, ${transformed})` : acc;
|
|
956
|
+
}, firstBase);
|
|
698
957
|
},
|
|
699
958
|
...options.nodes
|
|
700
959
|
},
|
|
701
960
|
print(node) {
|
|
702
961
|
const { keysToOmit } = this.options;
|
|
703
|
-
|
|
704
|
-
if (!
|
|
962
|
+
const transformed = this.transform(node);
|
|
963
|
+
if (!transformed) return null;
|
|
705
964
|
const meta = ast.syncSchemaRef(node);
|
|
706
|
-
if (keysToOmit?.length && meta.primitive === "object" && !(meta.type === "union" && meta.discriminatorPropertyName)) {
|
|
707
|
-
const lazyMatch = base.match(/^z\.lazy\(\(\)\s*=>\s*(.+)\)$/);
|
|
708
|
-
if (lazyMatch) base = `z.lazy(() => ${lazyMatch[1]}.omit({ ${keysToOmit.map((k) => `"${k}": true`).join(", ")} }))`;
|
|
709
|
-
else base = `${base}.omit({ ${keysToOmit.map((k) => `"${k}": true`).join(", ")} })`;
|
|
710
|
-
}
|
|
711
965
|
return applyMiniModifiers({
|
|
712
|
-
value:
|
|
966
|
+
value: (() => {
|
|
967
|
+
if (!keysToOmit?.length || meta.primitive !== "object" || meta.type === "union" && meta.discriminatorPropertyName) return transformed;
|
|
968
|
+
const lazyMatch = transformed.match(/^z\.lazy\(\(\)\s*=>\s*(.+)\)$/);
|
|
969
|
+
if (lazyMatch) return `z.lazy(() => ${lazyMatch[1]}.omit({ ${keysToOmit.map((k) => `"${k}": true`).join(", ")} }))`;
|
|
970
|
+
return `${transformed}.omit({ ${keysToOmit.map((k) => `"${k}": true`).join(", ")} })`;
|
|
971
|
+
})(),
|
|
713
972
|
nullable: meta.nullable,
|
|
714
973
|
optional: meta.optional,
|
|
715
974
|
nullish: meta.nullish,
|
|
@@ -720,9 +979,65 @@ const printerZodMini = ast.definePrinter((options) => {
|
|
|
720
979
|
});
|
|
721
980
|
//#endregion
|
|
722
981
|
//#region src/generators/zodGenerator.tsx
|
|
982
|
+
const zodPrinterCache = /* @__PURE__ */ new WeakMap();
|
|
983
|
+
const zodMiniPrinterCache = /* @__PURE__ */ new WeakMap();
|
|
984
|
+
/**
|
|
985
|
+
* Returns the cached `output`/`input` direction printers for a resolver, building them on
|
|
986
|
+
* first use. The `input` printer encodes `Date → string` for request bodies; `output` decodes
|
|
987
|
+
* `string → Date` for responses. Schemas without `dateType: 'date'` fields print identically.
|
|
988
|
+
*/
|
|
989
|
+
function getStdPrinters(resolver, params) {
|
|
990
|
+
const cached = zodPrinterCache.get(resolver);
|
|
991
|
+
if (cached && cached.coercion === params.coercion && cached.guidType === params.guidType && cached.dateType === params.dateType) return {
|
|
992
|
+
output: cached.output,
|
|
993
|
+
input: cached.input
|
|
994
|
+
};
|
|
995
|
+
const base = {
|
|
996
|
+
...params,
|
|
997
|
+
resolver
|
|
998
|
+
};
|
|
999
|
+
const output = printerZod({
|
|
1000
|
+
...base,
|
|
1001
|
+
direction: "output"
|
|
1002
|
+
});
|
|
1003
|
+
const input = printerZod({
|
|
1004
|
+
...base,
|
|
1005
|
+
direction: "input"
|
|
1006
|
+
});
|
|
1007
|
+
zodPrinterCache.set(resolver, {
|
|
1008
|
+
output,
|
|
1009
|
+
input,
|
|
1010
|
+
coercion: params.coercion,
|
|
1011
|
+
guidType: params.guidType,
|
|
1012
|
+
dateType: params.dateType
|
|
1013
|
+
});
|
|
1014
|
+
return {
|
|
1015
|
+
output,
|
|
1016
|
+
input
|
|
1017
|
+
};
|
|
1018
|
+
}
|
|
1019
|
+
function getMiniPrinter(resolver, params) {
|
|
1020
|
+
const cached = zodMiniPrinterCache.get(resolver);
|
|
1021
|
+
if (cached && cached.guidType === params.guidType) return cached.printer;
|
|
1022
|
+
const p = printerZodMini({
|
|
1023
|
+
...params,
|
|
1024
|
+
resolver
|
|
1025
|
+
});
|
|
1026
|
+
zodMiniPrinterCache.set(resolver, {
|
|
1027
|
+
printer: p,
|
|
1028
|
+
guidType: params.guidType
|
|
1029
|
+
});
|
|
1030
|
+
return p;
|
|
1031
|
+
}
|
|
1032
|
+
/**
|
|
1033
|
+
* Built-in generator for `@kubb/plugin-zod`. Emits one Zod schema per
|
|
1034
|
+
* schema in the spec plus per-operation request/response/parameter schemas.
|
|
1035
|
+
* When `mini: true`, schemas use the Zod Mini functional API instead of
|
|
1036
|
+
* chainable methods.
|
|
1037
|
+
*/
|
|
723
1038
|
const zodGenerator = defineGenerator({
|
|
724
1039
|
name: "zod",
|
|
725
|
-
renderer:
|
|
1040
|
+
renderer: jsxRendererSync,
|
|
726
1041
|
schema(node, ctx) {
|
|
727
1042
|
const { adapter, config, resolver, root } = ctx;
|
|
728
1043
|
const { output, coercion, guidType, mini, wrapOutput, inferred, importPath, group, printer } = ctx.options;
|
|
@@ -730,7 +1045,10 @@ const zodGenerator = defineGenerator({
|
|
|
730
1045
|
if (!node.name) return;
|
|
731
1046
|
const mode = ctx.getMode(output);
|
|
732
1047
|
const isZodImport = ZOD_NAMESPACE_IMPORTS.has(importPath);
|
|
733
|
-
const
|
|
1048
|
+
const cyclicSchemas = new Set(ctx.meta.circularNames);
|
|
1049
|
+
const hasCodec = !mini && containsCodec(node);
|
|
1050
|
+
const codecRefNames = new Set(hasCodec ? ast.collect(node, { schema: (n) => n.type === "ref" && n.ref && containsCodec(n) ? ast.extractRefName(n.ref) ?? void 0 : void 0 }) : []);
|
|
1051
|
+
const importEntries = adapter.getImports(node, (schemaName) => ({
|
|
734
1052
|
name: resolver.resolveSchemaName(schemaName),
|
|
735
1053
|
path: resolver.resolveFile({
|
|
736
1054
|
name: schemaName,
|
|
@@ -738,9 +1056,27 @@ const zodGenerator = defineGenerator({
|
|
|
738
1056
|
}, {
|
|
739
1057
|
root,
|
|
740
1058
|
output,
|
|
741
|
-
group
|
|
1059
|
+
group: group ?? void 0
|
|
742
1060
|
}).path
|
|
743
1061
|
}));
|
|
1062
|
+
const inputImportEntries = hasCodec ? [...codecRefNames].map((schemaName) => ({
|
|
1063
|
+
name: resolver.resolveInputSchemaName(schemaName),
|
|
1064
|
+
path: resolver.resolveFile({
|
|
1065
|
+
name: schemaName,
|
|
1066
|
+
extname: ".ts"
|
|
1067
|
+
}, {
|
|
1068
|
+
root,
|
|
1069
|
+
output,
|
|
1070
|
+
group: group ?? void 0
|
|
1071
|
+
}).path
|
|
1072
|
+
})) : [];
|
|
1073
|
+
const seenImports = /* @__PURE__ */ new Set();
|
|
1074
|
+
const imports = [...importEntries, ...inputImportEntries].filter((imp) => {
|
|
1075
|
+
const key = `${Array.isArray(imp.name) ? imp.name.join(",") : imp.name}|${imp.path}`;
|
|
1076
|
+
if (seenImports.has(key)) return false;
|
|
1077
|
+
seenImports.add(key);
|
|
1078
|
+
return true;
|
|
1079
|
+
});
|
|
744
1080
|
const meta = {
|
|
745
1081
|
name: resolver.resolveSchemaName(node.name),
|
|
746
1082
|
file: resolver.resolveFile({
|
|
@@ -749,37 +1085,43 @@ const zodGenerator = defineGenerator({
|
|
|
749
1085
|
}, {
|
|
750
1086
|
root,
|
|
751
1087
|
output,
|
|
752
|
-
group
|
|
1088
|
+
group: group ?? void 0
|
|
753
1089
|
})
|
|
754
1090
|
};
|
|
755
|
-
const inferTypeName = inferred ? resolver.resolveSchemaTypeName(node.name) :
|
|
756
|
-
const
|
|
757
|
-
|
|
1091
|
+
const inferTypeName = inferred ? resolver.resolveSchemaTypeName(node.name) : null;
|
|
1092
|
+
const stdPrinters = mini ? null : getStdPrinters(resolver, {
|
|
1093
|
+
coercion,
|
|
758
1094
|
guidType,
|
|
1095
|
+
dateType,
|
|
759
1096
|
wrapOutput,
|
|
760
|
-
resolver,
|
|
761
1097
|
cyclicSchemas,
|
|
762
1098
|
nodes: printer?.nodes
|
|
763
|
-
})
|
|
764
|
-
|
|
1099
|
+
});
|
|
1100
|
+
const schemaPrinter = mini ? getMiniPrinter(resolver, {
|
|
765
1101
|
guidType,
|
|
766
|
-
dateType,
|
|
767
1102
|
wrapOutput,
|
|
768
|
-
resolver,
|
|
769
1103
|
cyclicSchemas,
|
|
770
1104
|
nodes: printer?.nodes
|
|
771
|
-
});
|
|
1105
|
+
}) : stdPrinters.output;
|
|
772
1106
|
return /* @__PURE__ */ jsxs(File, {
|
|
773
1107
|
baseName: meta.file.baseName,
|
|
774
1108
|
path: meta.file.path,
|
|
775
1109
|
meta: meta.file.meta,
|
|
776
|
-
banner: resolver.resolveBanner(
|
|
1110
|
+
banner: resolver.resolveBanner(ctx.meta, {
|
|
777
1111
|
output,
|
|
778
|
-
config
|
|
1112
|
+
config,
|
|
1113
|
+
file: {
|
|
1114
|
+
path: meta.file.path,
|
|
1115
|
+
baseName: meta.file.baseName
|
|
1116
|
+
}
|
|
779
1117
|
}),
|
|
780
|
-
footer: resolver.resolveFooter(
|
|
1118
|
+
footer: resolver.resolveFooter(ctx.meta, {
|
|
781
1119
|
output,
|
|
782
|
-
config
|
|
1120
|
+
config,
|
|
1121
|
+
file: {
|
|
1122
|
+
path: meta.file.path,
|
|
1123
|
+
baseName: meta.file.baseName
|
|
1124
|
+
}
|
|
783
1125
|
}),
|
|
784
1126
|
children: [
|
|
785
1127
|
/* @__PURE__ */ jsx(File.Import, {
|
|
@@ -791,17 +1133,28 @@ const zodGenerator = defineGenerator({
|
|
|
791
1133
|
root: meta.file.path,
|
|
792
1134
|
path: imp.path,
|
|
793
1135
|
name: imp.name
|
|
794
|
-
}, [
|
|
1136
|
+
}, [
|
|
1137
|
+
node.name,
|
|
1138
|
+
imp.path,
|
|
1139
|
+
imp.name
|
|
1140
|
+
].join("-"))),
|
|
795
1141
|
/* @__PURE__ */ jsx(Zod, {
|
|
796
1142
|
name: meta.name,
|
|
797
1143
|
node,
|
|
798
1144
|
printer: schemaPrinter,
|
|
799
1145
|
inferTypeName
|
|
1146
|
+
}),
|
|
1147
|
+
hasCodec && stdPrinters && /* @__PURE__ */ jsx(Zod, {
|
|
1148
|
+
name: resolver.resolveInputSchemaName(node.name),
|
|
1149
|
+
node,
|
|
1150
|
+
printer: stdPrinters.input,
|
|
1151
|
+
inferTypeName: inferred ? resolver.resolveInputSchemaTypeName(node.name) : null
|
|
800
1152
|
})
|
|
801
1153
|
]
|
|
802
1154
|
});
|
|
803
1155
|
},
|
|
804
1156
|
operation(node, ctx) {
|
|
1157
|
+
if (!ast.isHttpOperationNode(node)) return null;
|
|
805
1158
|
const { adapter, config, resolver, root } = ctx;
|
|
806
1159
|
const { output, coercion, guidType, mini, wrapOutput, inferred, importPath, group, paramsCasing, printer } = ctx.options;
|
|
807
1160
|
const dateType = adapter.options.dateType;
|
|
@@ -816,31 +1169,37 @@ const zodGenerator = defineGenerator({
|
|
|
816
1169
|
}, {
|
|
817
1170
|
root,
|
|
818
1171
|
output,
|
|
819
|
-
group
|
|
1172
|
+
group: group ?? void 0
|
|
820
1173
|
}) };
|
|
821
|
-
const cyclicSchemas =
|
|
822
|
-
function renderSchemaEntry({ schema, name, keysToOmit }) {
|
|
1174
|
+
const cyclicSchemas = new Set(ctx.meta.circularNames);
|
|
1175
|
+
function renderSchemaEntry({ schema, name, keysToOmit, direction = "output" }) {
|
|
823
1176
|
if (!schema) return null;
|
|
824
|
-
const inferTypeName = inferred ? resolver.resolveTypeName(name) :
|
|
1177
|
+
const inferTypeName = inferred ? resolver.resolveTypeName(name) : null;
|
|
1178
|
+
const codecRefNames = direction === "input" && !mini ? new Set(ast.collect(schema, { schema: (n) => n.type === "ref" && n.ref && containsCodec(n) ? ast.extractRefName(n.ref) ?? void 0 : void 0 })) : null;
|
|
825
1179
|
const imports = adapter.getImports(schema, (schemaName) => ({
|
|
826
|
-
name: resolver.resolveSchemaName(schemaName),
|
|
1180
|
+
name: codecRefNames?.has(schemaName) ? resolver.resolveInputSchemaName(schemaName) : resolver.resolveSchemaName(schemaName),
|
|
827
1181
|
path: resolver.resolveFile({
|
|
828
1182
|
name: schemaName,
|
|
829
1183
|
extname: ".ts"
|
|
830
1184
|
}, {
|
|
831
1185
|
root,
|
|
832
1186
|
output,
|
|
833
|
-
group
|
|
1187
|
+
group: group ?? void 0
|
|
834
1188
|
}).path
|
|
835
1189
|
}));
|
|
836
|
-
const schemaPrinter = mini ? printerZodMini({
|
|
1190
|
+
const schemaPrinter = mini ? keysToOmit?.length ? printerZodMini({
|
|
837
1191
|
guidType,
|
|
838
1192
|
wrapOutput,
|
|
839
1193
|
resolver,
|
|
840
1194
|
keysToOmit,
|
|
841
1195
|
cyclicSchemas,
|
|
842
1196
|
nodes: printer?.nodes
|
|
843
|
-
}) :
|
|
1197
|
+
}) : getMiniPrinter(resolver, {
|
|
1198
|
+
guidType,
|
|
1199
|
+
wrapOutput,
|
|
1200
|
+
cyclicSchemas,
|
|
1201
|
+
nodes: printer?.nodes
|
|
1202
|
+
}) : keysToOmit?.length ? printerZod({
|
|
844
1203
|
coercion,
|
|
845
1204
|
guidType,
|
|
846
1205
|
dateType,
|
|
@@ -848,8 +1207,16 @@ const zodGenerator = defineGenerator({
|
|
|
848
1207
|
resolver,
|
|
849
1208
|
keysToOmit,
|
|
850
1209
|
cyclicSchemas,
|
|
1210
|
+
nodes: printer?.nodes,
|
|
1211
|
+
direction
|
|
1212
|
+
}) : getStdPrinters(resolver, {
|
|
1213
|
+
coercion,
|
|
1214
|
+
guidType,
|
|
1215
|
+
dateType,
|
|
1216
|
+
wrapOutput,
|
|
1217
|
+
cyclicSchemas,
|
|
851
1218
|
nodes: printer?.nodes
|
|
852
|
-
});
|
|
1219
|
+
})[direction];
|
|
853
1220
|
return /* @__PURE__ */ jsxs(Fragment, { children: [mode === "split" && imports.map((imp) => /* @__PURE__ */ jsx(File.Import, {
|
|
854
1221
|
root: meta.file.path,
|
|
855
1222
|
path: imp.path,
|
|
@@ -865,22 +1232,48 @@ const zodGenerator = defineGenerator({
|
|
|
865
1232
|
inferTypeName
|
|
866
1233
|
})] });
|
|
867
1234
|
}
|
|
1235
|
+
function buildContentTypeVariants(entries, baseName, decorate, direction) {
|
|
1236
|
+
const variants = resolveContentTypeVariants(entries, baseName);
|
|
1237
|
+
const unionSchema = ast.createSchema({
|
|
1238
|
+
type: "union",
|
|
1239
|
+
members: variants.map((variant) => ast.createSchema({
|
|
1240
|
+
type: "ref",
|
|
1241
|
+
name: variant.name
|
|
1242
|
+
}))
|
|
1243
|
+
});
|
|
1244
|
+
return /* @__PURE__ */ jsxs(Fragment, { children: [variants.map((variant) => renderSchemaEntry({
|
|
1245
|
+
schema: decorate ? decorate(variant.schema) : variant.schema,
|
|
1246
|
+
name: variant.name,
|
|
1247
|
+
keysToOmit: variant.keysToOmit,
|
|
1248
|
+
direction
|
|
1249
|
+
})), renderSchemaEntry({
|
|
1250
|
+
schema: unionSchema,
|
|
1251
|
+
name: baseName,
|
|
1252
|
+
direction
|
|
1253
|
+
})] });
|
|
1254
|
+
}
|
|
868
1255
|
const paramSchemas = params.map((param) => renderSchemaEntry({
|
|
869
1256
|
schema: param.schema,
|
|
870
|
-
name: resolver.resolveParamName(node, param)
|
|
871
|
-
|
|
872
|
-
const responseSchemas = node.responses.map((res) => renderSchemaEntry({
|
|
873
|
-
schema: res.schema,
|
|
874
|
-
name: resolver.resolveResponseStatusName(node, res.statusCode),
|
|
875
|
-
keysToOmit: res.keysToOmit
|
|
1257
|
+
name: resolver.resolveParamName(node, param),
|
|
1258
|
+
direction: "input"
|
|
876
1259
|
}));
|
|
877
|
-
const
|
|
1260
|
+
const responseSchemas = node.responses.map((res) => {
|
|
1261
|
+
const variants = (res.content ?? []).filter((entry) => entry.schema);
|
|
1262
|
+
if (variants.length > 1) return buildContentTypeVariants(res.content, resolver.resolveResponseStatusName(node, res.statusCode));
|
|
1263
|
+
const primary = variants[0] ?? res.content?.[0];
|
|
1264
|
+
return renderSchemaEntry({
|
|
1265
|
+
schema: primary?.schema ?? null,
|
|
1266
|
+
name: resolver.resolveResponseStatusName(node, res.statusCode),
|
|
1267
|
+
keysToOmit: primary?.keysToOmit
|
|
1268
|
+
});
|
|
1269
|
+
});
|
|
1270
|
+
const responsesWithSchema = node.responses.filter((res) => res.content?.some((entry) => entry.schema));
|
|
878
1271
|
const responseUnionSchema = responsesWithSchema.length > 0 ? (() => {
|
|
879
1272
|
const responseUnionName = resolver.resolveResponseName(node);
|
|
880
|
-
if (new Set(responsesWithSchema.flatMap((res) => res.schema ? adapter.getImports(
|
|
1273
|
+
if (new Set(responsesWithSchema.flatMap((res) => (res.content ?? []).flatMap((entry) => entry.schema ? adapter.getImports(entry.schema, (schemaName) => ({
|
|
881
1274
|
name: resolver.resolveSchemaName(schemaName),
|
|
882
1275
|
path: ""
|
|
883
|
-
})).flatMap((imp) => Array.isArray(imp.name) ? imp.name : [imp.name]) : [])).has(responseUnionName)) return null;
|
|
1276
|
+
})).flatMap((imp) => Array.isArray(imp.name) ? imp.name : [imp.name]) : []))).has(responseUnionName)) return null;
|
|
884
1277
|
const members = responsesWithSchema.map((res) => ast.createSchema({
|
|
885
1278
|
type: "ref",
|
|
886
1279
|
name: resolver.resolveResponseStatusName(node, res.statusCode)
|
|
@@ -893,25 +1286,46 @@ const zodGenerator = defineGenerator({
|
|
|
893
1286
|
name: responseUnionName
|
|
894
1287
|
});
|
|
895
1288
|
})() : null;
|
|
896
|
-
const
|
|
897
|
-
|
|
898
|
-
|
|
899
|
-
|
|
900
|
-
|
|
901
|
-
|
|
902
|
-
|
|
903
|
-
|
|
1289
|
+
const requestBodyContent = node.requestBody?.content ?? [];
|
|
1290
|
+
const requestSchema = (() => {
|
|
1291
|
+
if (requestBodyContent.length === 0) return null;
|
|
1292
|
+
if (requestBodyContent.length === 1) {
|
|
1293
|
+
const entry = requestBodyContent[0];
|
|
1294
|
+
if (!entry.schema) return null;
|
|
1295
|
+
return renderSchemaEntry({
|
|
1296
|
+
schema: {
|
|
1297
|
+
...entry.schema,
|
|
1298
|
+
description: node.requestBody.description ?? entry.schema.description
|
|
1299
|
+
},
|
|
1300
|
+
name: resolver.resolveDataName(node),
|
|
1301
|
+
keysToOmit: entry.keysToOmit,
|
|
1302
|
+
direction: "input"
|
|
1303
|
+
});
|
|
1304
|
+
}
|
|
1305
|
+
return buildContentTypeVariants(requestBodyContent, resolver.resolveDataName(node), (schema) => ({
|
|
1306
|
+
...schema,
|
|
1307
|
+
description: node.requestBody.description ?? schema.description
|
|
1308
|
+
}), "input");
|
|
1309
|
+
})();
|
|
904
1310
|
return /* @__PURE__ */ jsxs(File, {
|
|
905
1311
|
baseName: meta.file.baseName,
|
|
906
1312
|
path: meta.file.path,
|
|
907
1313
|
meta: meta.file.meta,
|
|
908
|
-
banner: resolver.resolveBanner(
|
|
1314
|
+
banner: resolver.resolveBanner(ctx.meta, {
|
|
909
1315
|
output,
|
|
910
|
-
config
|
|
1316
|
+
config,
|
|
1317
|
+
file: {
|
|
1318
|
+
path: meta.file.path,
|
|
1319
|
+
baseName: meta.file.baseName
|
|
1320
|
+
}
|
|
911
1321
|
}),
|
|
912
|
-
footer: resolver.resolveFooter(
|
|
1322
|
+
footer: resolver.resolveFooter(ctx.meta, {
|
|
913
1323
|
output,
|
|
914
|
-
config
|
|
1324
|
+
config,
|
|
1325
|
+
file: {
|
|
1326
|
+
path: meta.file.path,
|
|
1327
|
+
baseName: meta.file.baseName
|
|
1328
|
+
}
|
|
915
1329
|
}),
|
|
916
1330
|
children: [
|
|
917
1331
|
/* @__PURE__ */ jsx(File.Import, {
|
|
@@ -927,7 +1341,7 @@ const zodGenerator = defineGenerator({
|
|
|
927
1341
|
});
|
|
928
1342
|
},
|
|
929
1343
|
operations(nodes, ctx) {
|
|
930
|
-
const {
|
|
1344
|
+
const { config, resolver, root } = ctx;
|
|
931
1345
|
const { output, importPath, group, operations, paramsCasing } = ctx.options;
|
|
932
1346
|
if (!operations) return;
|
|
933
1347
|
const isZodImport = ZOD_NAMESPACE_IMPORTS.has(importPath);
|
|
@@ -937,9 +1351,9 @@ const zodGenerator = defineGenerator({
|
|
|
937
1351
|
}, {
|
|
938
1352
|
root,
|
|
939
1353
|
output,
|
|
940
|
-
group
|
|
1354
|
+
group: group ?? void 0
|
|
941
1355
|
}) };
|
|
942
|
-
const transformedOperations = nodes.map((node) => {
|
|
1356
|
+
const transformedOperations = nodes.filter(ast.isHttpOperationNode).map((node) => {
|
|
943
1357
|
return {
|
|
944
1358
|
node,
|
|
945
1359
|
data: buildSchemaNames(node, {
|
|
@@ -962,7 +1376,7 @@ const zodGenerator = defineGenerator({
|
|
|
962
1376
|
}, {
|
|
963
1377
|
root,
|
|
964
1378
|
output,
|
|
965
|
-
group
|
|
1379
|
+
group: group ?? void 0
|
|
966
1380
|
});
|
|
967
1381
|
return names.map((name) => /* @__PURE__ */ jsx(File.Import, {
|
|
968
1382
|
name: [name],
|
|
@@ -974,13 +1388,21 @@ const zodGenerator = defineGenerator({
|
|
|
974
1388
|
baseName: meta.file.baseName,
|
|
975
1389
|
path: meta.file.path,
|
|
976
1390
|
meta: meta.file.meta,
|
|
977
|
-
banner: resolver.resolveBanner(
|
|
1391
|
+
banner: resolver.resolveBanner(ctx.meta, {
|
|
978
1392
|
output,
|
|
979
|
-
config
|
|
1393
|
+
config,
|
|
1394
|
+
file: {
|
|
1395
|
+
path: meta.file.path,
|
|
1396
|
+
baseName: meta.file.baseName
|
|
1397
|
+
}
|
|
980
1398
|
}),
|
|
981
|
-
footer: resolver.resolveFooter(
|
|
1399
|
+
footer: resolver.resolveFooter(ctx.meta, {
|
|
982
1400
|
output,
|
|
983
|
-
config
|
|
1401
|
+
config,
|
|
1402
|
+
file: {
|
|
1403
|
+
path: meta.file.path,
|
|
1404
|
+
baseName: meta.file.baseName
|
|
1405
|
+
}
|
|
984
1406
|
}),
|
|
985
1407
|
children: [
|
|
986
1408
|
/* @__PURE__ */ jsx(File.Import, {
|
|
@@ -1001,77 +1423,102 @@ const zodGenerator = defineGenerator({
|
|
|
1001
1423
|
//#endregion
|
|
1002
1424
|
//#region src/resolvers/resolverZod.ts
|
|
1003
1425
|
/**
|
|
1004
|
-
*
|
|
1426
|
+
* Default resolver used by `@kubb/plugin-zod`. Decides the names and file
|
|
1427
|
+
* paths for every generated Zod schema. Schemas use camelCase with a
|
|
1428
|
+
* `Schema` suffix (`listPetsSchema`); their inferred types use PascalCase.
|
|
1005
1429
|
*
|
|
1006
|
-
*
|
|
1430
|
+
* @example Resolve schema and type names
|
|
1431
|
+
* ```ts
|
|
1432
|
+
* import { resolverZod } from '@kubb/plugin-zod'
|
|
1007
1433
|
*
|
|
1008
|
-
*
|
|
1009
|
-
*
|
|
1434
|
+
* resolverZod.default('list pets', 'function') // 'listPetsSchema'
|
|
1435
|
+
* resolverZod.resolveSchemaTypeName('pet') // 'PetSchema'
|
|
1436
|
+
* ```
|
|
1010
1437
|
*/
|
|
1011
|
-
const resolverZod = defineResolver((
|
|
1438
|
+
const resolverZod = defineResolver(() => {
|
|
1012
1439
|
return {
|
|
1013
1440
|
name: "default",
|
|
1014
1441
|
pluginName: "plugin-zod",
|
|
1015
1442
|
default(name, type) {
|
|
1016
|
-
|
|
1443
|
+
const resolved = camelCase(name, {
|
|
1017
1444
|
isFile: type === "file",
|
|
1018
1445
|
suffix: type ? "schema" : void 0
|
|
1019
1446
|
});
|
|
1447
|
+
return type === "file" ? resolved : ensureValidVarName(resolved);
|
|
1020
1448
|
},
|
|
1021
1449
|
resolveSchemaName(name) {
|
|
1022
|
-
return camelCase(name, { suffix: "schema" });
|
|
1450
|
+
return ensureValidVarName(camelCase(name, { suffix: "schema" }));
|
|
1023
1451
|
},
|
|
1024
1452
|
resolveSchemaTypeName(name) {
|
|
1025
|
-
return pascalCase(name, { suffix: "schema" });
|
|
1453
|
+
return ensureValidVarName(pascalCase(name, { suffix: "schema" }));
|
|
1454
|
+
},
|
|
1455
|
+
resolveInputSchemaName(name) {
|
|
1456
|
+
return this.resolveSchemaName(`${name} input`);
|
|
1457
|
+
},
|
|
1458
|
+
resolveInputSchemaTypeName(name) {
|
|
1459
|
+
return this.resolveSchemaTypeName(`${name} input`);
|
|
1026
1460
|
},
|
|
1027
1461
|
resolveTypeName(name) {
|
|
1028
|
-
return pascalCase(name);
|
|
1462
|
+
return ensureValidVarName(pascalCase(name));
|
|
1029
1463
|
},
|
|
1030
1464
|
resolvePathName(name, type) {
|
|
1031
|
-
return
|
|
1465
|
+
return this.default(name, type);
|
|
1032
1466
|
},
|
|
1033
1467
|
resolveParamName(node, param) {
|
|
1034
|
-
return
|
|
1468
|
+
return this.resolveSchemaName(`${node.operationId} ${param.in} ${param.name}`);
|
|
1035
1469
|
},
|
|
1036
1470
|
resolveResponseStatusName(node, statusCode) {
|
|
1037
|
-
return
|
|
1471
|
+
return this.resolveSchemaName(`${node.operationId} Status ${statusCode}`);
|
|
1038
1472
|
},
|
|
1039
1473
|
resolveDataName(node) {
|
|
1040
|
-
return
|
|
1474
|
+
return this.resolveSchemaName(`${node.operationId} Data`);
|
|
1041
1475
|
},
|
|
1042
1476
|
resolveResponsesName(node) {
|
|
1043
|
-
return
|
|
1477
|
+
return this.resolveSchemaName(`${node.operationId} Responses`);
|
|
1044
1478
|
},
|
|
1045
1479
|
resolveResponseName(node) {
|
|
1046
|
-
return
|
|
1480
|
+
return this.resolveSchemaName(`${node.operationId} Response`);
|
|
1047
1481
|
},
|
|
1048
1482
|
resolvePathParamsName(node, param) {
|
|
1049
|
-
return
|
|
1483
|
+
return this.resolveParamName(node, param);
|
|
1050
1484
|
},
|
|
1051
1485
|
resolveQueryParamsName(node, param) {
|
|
1052
|
-
return
|
|
1486
|
+
return this.resolveParamName(node, param);
|
|
1053
1487
|
},
|
|
1054
1488
|
resolveHeaderParamsName(node, param) {
|
|
1055
|
-
return
|
|
1489
|
+
return this.resolveParamName(node, param);
|
|
1056
1490
|
}
|
|
1057
1491
|
};
|
|
1058
1492
|
});
|
|
1059
1493
|
//#endregion
|
|
1060
1494
|
//#region src/plugin.ts
|
|
1061
1495
|
/**
|
|
1062
|
-
* Canonical plugin name for `@kubb/plugin-zod
|
|
1496
|
+
* Canonical plugin name for `@kubb/plugin-zod`. Used for driver lookups and
|
|
1497
|
+
* cross-plugin dependency references.
|
|
1063
1498
|
*/
|
|
1064
1499
|
const pluginZodName = "plugin-zod";
|
|
1065
1500
|
/**
|
|
1066
|
-
* Generates Zod
|
|
1067
|
-
*
|
|
1068
|
-
*
|
|
1501
|
+
* Generates Zod v4 schemas from an OpenAPI spec. Use them to validate API
|
|
1502
|
+
* responses at runtime, build form schemas, or feed back into router libraries
|
|
1503
|
+
* that consume Zod (tRPC, Hono, Elysia). Pair with `@kubb/plugin-client` and
|
|
1504
|
+
* set the client's `parser: 'zod'` to validate every response automatically.
|
|
1069
1505
|
*
|
|
1070
|
-
* @example
|
|
1506
|
+
* @example
|
|
1071
1507
|
* ```ts
|
|
1072
|
-
* import
|
|
1508
|
+
* import { defineConfig } from 'kubb'
|
|
1509
|
+
* import { pluginTs } from '@kubb/plugin-ts'
|
|
1510
|
+
* import { pluginZod } from '@kubb/plugin-zod'
|
|
1511
|
+
*
|
|
1073
1512
|
* export default defineConfig({
|
|
1074
|
-
*
|
|
1513
|
+
* input: { path: './petStore.yaml' },
|
|
1514
|
+
* output: { path: './src/gen' },
|
|
1515
|
+
* plugins: [
|
|
1516
|
+
* pluginTs(),
|
|
1517
|
+
* pluginZod({
|
|
1518
|
+
* output: { path: './zod' },
|
|
1519
|
+
* typed: true,
|
|
1520
|
+
* }),
|
|
1521
|
+
* ],
|
|
1075
1522
|
* })
|
|
1076
1523
|
* ```
|
|
1077
1524
|
*/
|
|
@@ -1080,13 +1527,7 @@ const pluginZod = definePlugin((options) => {
|
|
|
1080
1527
|
path: "zod",
|
|
1081
1528
|
barrelType: "named"
|
|
1082
1529
|
}, group, exclude = [], include, override = [], typed = false, operations = false, mini = false, guidType = "uuid", importPath = mini ? "zod/mini" : "zod", coercion = false, inferred = false, wrapOutput = void 0, paramsCasing, printer, resolver: userResolver, transformer: userTransformer, generators: userGenerators = [] } = options;
|
|
1083
|
-
const groupConfig = group
|
|
1084
|
-
...group,
|
|
1085
|
-
name: (ctx) => {
|
|
1086
|
-
if (group.type === "path") return `${ctx.group.split("/")[1]}`;
|
|
1087
|
-
return `${camelCase(ctx.group)}Controller`;
|
|
1088
|
-
}
|
|
1089
|
-
} : void 0;
|
|
1530
|
+
const groupConfig = createGroupConfig(group, { suffix: "Controller" });
|
|
1090
1531
|
return {
|
|
1091
1532
|
name: pluginZodName,
|
|
1092
1533
|
options,
|