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