@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/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, jsxRenderer } from "@kubb/renderer-jsx";
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) : void 0,
526
+ request: node.requestBody?.content?.[0]?.schema ? resolver.resolveDataName(node) : null,
265
527
  parameters: {
266
- path: pathParam ? resolver.resolvePathParamsName(node, pathParam) : void 0,
267
- query: queryParam ? resolver.resolveQueryParamsName(node, queryParam) : void 0,
268
- header: headerParam ? resolver.resolveHeaderParamsName(node, headerParam) : void 0
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
- let result = value;
343
- if (nullish || nullable && optional) result = `${result}.nullish()`;
344
- else if (optional) result = `${result}.optional()`;
345
- else if (nullable) result = `${result}.nullable()`;
346
- if (defaultValue !== void 0) result = `${result}.default(${formatDefault(defaultValue)})`;
347
- if (description) result = `${result}.describe(${stringify(description)})`;
348
- return result;
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
- let result = value;
356
- if (nullish) result = `z.nullish(${result})`;
357
- else {
358
- if (nullable) result = `z.nullable(${result})`;
359
- if (optional) result = `z.optional(${result})`;
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
- if (node.representation === "string") return "z.iso.date()";
403
- return shouldCoerce(this.options.coercion, "dates") ? "z.coerce.date()" : "z.date()";
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 void 0;
715
+ if (!node.name) return null;
439
716
  const refName = node.ref ? ast.extractRefName(node.ref) ?? node.name : node.name;
440
- const resolvedName = node.ref ? this.options.resolver?.default(refName, "function") ?? refName : node.name;
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
- let result = `z.object({\n ${node.properties.map((prop) => {
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 = 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
- let descriptionToApply = meta.description;
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
- if (node.additionalProperties && node.additionalProperties !== true) {
473
- const catchallType = this.transform(node.additionalProperties);
474
- if (catchallType) result += `.catchall(${catchallType})`;
475
- } else if (node.additionalProperties === true) result += `.catchall(${this.transform(ast.createSchema({ type: "unknown" }))})`;
476
- else if (node.additionalProperties === false) result += ".strict()";
477
- return result;
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
- let result = `z.array(${(node.items ?? []).map((item) => this.transform(item)).filter(Boolean).join(", ") || this.transform(ast.createSchema({ type: "unknown" }))})${lengthConstraints(node)}`;
481
- if (node.unique) result += `.refine(items => new Set(items).size === items.length, { message: "Array entries must be unique" })`;
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((m) => this.transform(m)).filter(Boolean);
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
- let base = this.transform(first);
501
- if (!base) return "";
502
- for (const member of rest) {
503
- if (member.primitive === "string") {
504
- const c = lengthConstraints(ast.narrowSchema(member, "string") ?? {});
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
- if (transformed) base = `${base}.and(${transformed})`;
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
- let base = this.transform(node);
532
- if (!base) return null;
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: base,
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 void 0;
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 = 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
- let result = `z.array(${(node.items ?? []).map((item) => this.transform(item)).filter(Boolean).join(", ") || this.transform(ast.createSchema({ type: "unknown" }))})${lengthChecksMini(node)}`;
653
- if (node.unique) result += `.refine(items => new Set(items).size === items.length, { message: "Array entries must be unique" })`;
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((m) => this.transform(m)).filter(Boolean);
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
- let base = this.transform(first);
673
- if (!base) return "";
674
- for (const member of rest) {
675
- if (member.primitive === "string") {
676
- const c = lengthChecksMini(ast.narrowSchema(member, "string") ?? {});
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
- if (transformed) base = `z.intersection(${base}, ${transformed})`;
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
- let base = this.transform(node);
704
- if (!base) return null;
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: base,
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: jsxRenderer,
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 imports = adapter.getImports(node, (schemaName) => ({
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) : void 0;
756
- const cyclicSchemas = adapter.inputNode ? ast.findCircularSchemas(adapter.inputNode.schemas) : void 0;
757
- const schemaPrinter = mini ? printerZodMini({
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
- }) : printerZod({
764
- coercion,
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(adapter.inputNode, {
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(adapter.inputNode, {
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
- }, [node.name, imp.path].join("-"))),
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 = adapter.inputNode ? ast.findCircularSchemas(adapter.inputNode.schemas) : void 0;
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) : void 0;
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
- }) : printerZod({
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 responsesWithSchema = node.responses.filter((res) => res.schema);
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(res.schema, (schemaName) => ({
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 requestSchema = node.requestBody?.content?.[0]?.schema ? renderSchemaEntry({
897
- schema: {
898
- ...node.requestBody.content[0].schema,
899
- description: node.requestBody.description ?? node.requestBody.content[0].schema.description
900
- },
901
- name: resolver.resolveDataName(node),
902
- keysToOmit: node.requestBody.content[0].keysToOmit
903
- }) : null;
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(adapter.inputNode, {
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(adapter.inputNode, {
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 { adapter, config, resolver, root } = ctx;
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(adapter.inputNode, {
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(adapter.inputNode, {
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
- * Naming convention resolver for Zod plugin.
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
- * Provides default naming helpers using camelCase with a `Schema` suffix for schemas.
1430
+ * @example Resolve schema and type names
1431
+ * ```ts
1432
+ * import { resolverZod } from '@kubb/plugin-zod'
1007
1433
  *
1008
- * @example
1009
- * `resolverZod.default('list pets', 'function') // 'listPetsSchema'`
1434
+ * resolverZod.default('list pets', 'function') // 'listPetsSchema'
1435
+ * resolverZod.resolveSchemaTypeName('pet') // 'PetSchema'
1436
+ * ```
1010
1437
  */
1011
- const resolverZod = defineResolver((ctx) => {
1438
+ const resolverZod = defineResolver(() => {
1012
1439
  return {
1013
1440
  name: "default",
1014
1441
  pluginName: "plugin-zod",
1015
1442
  default(name, type) {
1016
- return camelCase(name, {
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 ctx.default(name, type);
1465
+ return this.default(name, type);
1032
1466
  },
1033
1467
  resolveParamName(node, param) {
1034
- return ctx.resolveSchemaName(`${node.operationId} ${param.in} ${param.name}`);
1468
+ return this.resolveSchemaName(`${node.operationId} ${param.in} ${param.name}`);
1035
1469
  },
1036
1470
  resolveResponseStatusName(node, statusCode) {
1037
- return ctx.resolveSchemaName(`${node.operationId} Status ${statusCode}`);
1471
+ return this.resolveSchemaName(`${node.operationId} Status ${statusCode}`);
1038
1472
  },
1039
1473
  resolveDataName(node) {
1040
- return ctx.resolveSchemaName(`${node.operationId} Data`);
1474
+ return this.resolveSchemaName(`${node.operationId} Data`);
1041
1475
  },
1042
1476
  resolveResponsesName(node) {
1043
- return ctx.resolveSchemaName(`${node.operationId} Responses`);
1477
+ return this.resolveSchemaName(`${node.operationId} Responses`);
1044
1478
  },
1045
1479
  resolveResponseName(node) {
1046
- return ctx.resolveSchemaName(`${node.operationId} Response`);
1480
+ return this.resolveSchemaName(`${node.operationId} Response`);
1047
1481
  },
1048
1482
  resolvePathParamsName(node, param) {
1049
- return ctx.resolveParamName(node, param);
1483
+ return this.resolveParamName(node, param);
1050
1484
  },
1051
1485
  resolveQueryParamsName(node, param) {
1052
- return ctx.resolveParamName(node, param);
1486
+ return this.resolveParamName(node, param);
1053
1487
  },
1054
1488
  resolveHeaderParamsName(node, param) {
1055
- return ctx.resolveParamName(node, param);
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`, used in driver lookups and warnings.
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 validation schemas from an OpenAPI specification.
1067
- * Walks schemas and operations, delegates to generators, and writes barrel files
1068
- * based on the configured `barrelType`.
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 Zod schema generator
1506
+ * @example
1071
1507
  * ```ts
1072
- * import pluginZod from '@kubb/plugin-zod'
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
- * plugins: [pluginZod({ output: { path: 'zod' } })]
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,