@kubb/plugin-zod 5.0.0-beta.30 → 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
@@ -259,6 +259,89 @@ function ensureValidVarName(name) {
259
259
  return `_${name}`;
260
260
  }
261
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
262
345
  //#region src/components/Operations.tsx
263
346
  function Operations({ name, operations }) {
264
347
  const operationsJSON = operations.reduce((prev, acc) => {
@@ -266,6 +349,7 @@ function Operations({ name, operations }) {
266
349
  return prev;
267
350
  }, {});
268
351
  const pathsJSON = operations.reduce((prev, acc) => {
352
+ if (!_kubb_core.ast.isHttpOperationNode(acc.node)) return prev;
269
353
  prev[`"${acc.node.path}"`] = {
270
354
  ...prev[`"${acc.node.path}"`] ?? {},
271
355
  [acc.node.method]: `operations["${acc.node.operationId}"]`
@@ -375,6 +459,61 @@ function shouldCoerce(coercion, type) {
375
459
  return !!coercion[type];
376
460
  }
377
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
+ /**
378
517
  * Collects all resolved schema names for an operation's parameters and responses
379
518
  * into a single lookup object, useful for building imports and type references.
380
519
  */
@@ -546,8 +685,9 @@ const printerZod = _kubb_core.ast.definePrinter((options) => {
546
685
  return shouldCoerce(this.options.coercion, "numbers") ? "z.coerce.bigint()" : "z.bigint()";
547
686
  },
548
687
  date(node) {
549
- if (node.representation === "string") return "z.iso.date()";
550
- 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()";
551
691
  },
552
692
  datetime(node) {
553
693
  const offset = node.offset || this.options.dateType === "stringOffset";
@@ -584,7 +724,8 @@ const printerZod = _kubb_core.ast.definePrinter((options) => {
584
724
  ref(node) {
585
725
  if (!node.name) return null;
586
726
  const refName = node.ref ? _kubb_core.ast.extractRefName(node.ref) ?? node.name : node.name;
587
- 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;
588
729
  if (node.ref && this.options.cyclicSchemas?.has(refName)) return `z.lazy(() => ${resolvedName})`;
589
730
  return resolvedName;
590
731
  },
@@ -851,6 +992,54 @@ const printerZodMini = _kubb_core.ast.definePrinter((options) => {
851
992
  const zodPrinterCache = /* @__PURE__ */ new WeakMap();
852
993
  const zodMiniPrinterCache = /* @__PURE__ */ new WeakMap();
853
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
+ /**
854
1043
  * Built-in generator for `@kubb/plugin-zod`. Emits one Zod schema per
855
1044
  * schema in the spec plus per-operation request/response/parameter schemas.
856
1045
  * When `mini: true`, schemas use the Zod Mini functional API instead of
@@ -866,7 +1055,10 @@ const zodGenerator = (0, _kubb_core.defineGenerator)({
866
1055
  if (!node.name) return;
867
1056
  const mode = ctx.getMode(output);
868
1057
  const isZodImport = ZOD_NAMESPACE_IMPORTS.has(importPath);
869
- 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) => ({
870
1062
  name: resolver.resolveSchemaName(schemaName),
871
1063
  path: resolver.resolveFile({
872
1064
  name: schemaName,
@@ -877,6 +1069,24 @@ const zodGenerator = (0, _kubb_core.defineGenerator)({
877
1069
  group: group ?? void 0
878
1070
  }).path
879
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
+ });
880
1090
  const meta = {
881
1091
  name: resolver.resolveSchemaName(node.name),
882
1092
  file: resolver.resolveFile({
@@ -889,44 +1099,20 @@ const zodGenerator = (0, _kubb_core.defineGenerator)({
889
1099
  })
890
1100
  };
891
1101
  const inferTypeName = inferred ? resolver.resolveSchemaTypeName(node.name) : null;
892
- const cyclicSchemas = new Set(ctx.meta.circularNames);
893
- const schemaPrinter = mini ? getCachedMiniPrinter() : getCachedStdPrinter();
894
- function getCachedStdPrinter() {
895
- const cached = zodPrinterCache.get(resolver);
896
- if (cached && cached.coercion === coercion && cached.guidType === guidType && cached.dateType === dateType) return cached.printer;
897
- const p = printerZod({
898
- coercion,
899
- guidType,
900
- dateType,
901
- wrapOutput,
902
- resolver,
903
- cyclicSchemas,
904
- nodes: printer?.nodes
905
- });
906
- zodPrinterCache.set(resolver, {
907
- printer: p,
908
- coercion,
909
- guidType,
910
- dateType
911
- });
912
- return p;
913
- }
914
- function getCachedMiniPrinter() {
915
- const cached = zodMiniPrinterCache.get(resolver);
916
- if (cached && cached.guidType === guidType) return cached.printer;
917
- const p = printerZodMini({
918
- guidType,
919
- wrapOutput,
920
- resolver,
921
- cyclicSchemas,
922
- nodes: printer?.nodes
923
- });
924
- zodMiniPrinterCache.set(resolver, {
925
- printer: p,
926
- guidType
927
- });
928
- return p;
929
- }
1102
+ const stdPrinters = mini ? null : getStdPrinters(resolver, {
1103
+ coercion,
1104
+ guidType,
1105
+ dateType,
1106
+ wrapOutput,
1107
+ cyclicSchemas,
1108
+ nodes: printer?.nodes
1109
+ });
1110
+ const schemaPrinter = mini ? getMiniPrinter(resolver, {
1111
+ guidType,
1112
+ wrapOutput,
1113
+ cyclicSchemas,
1114
+ nodes: printer?.nodes
1115
+ }) : stdPrinters.output;
930
1116
  return /* @__PURE__ */ (0, _kubb_renderer_jsx_jsx_runtime.jsxs)(_kubb_renderer_jsx.File, {
931
1117
  baseName: meta.file.baseName,
932
1118
  path: meta.file.path,
@@ -957,17 +1143,28 @@ const zodGenerator = (0, _kubb_core.defineGenerator)({
957
1143
  root: meta.file.path,
958
1144
  path: imp.path,
959
1145
  name: imp.name
960
- }, [node.name, imp.path].join("-"))),
1146
+ }, [
1147
+ node.name,
1148
+ imp.path,
1149
+ imp.name
1150
+ ].join("-"))),
961
1151
  /* @__PURE__ */ (0, _kubb_renderer_jsx_jsx_runtime.jsx)(Zod, {
962
1152
  name: meta.name,
963
1153
  node,
964
1154
  printer: schemaPrinter,
965
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
966
1162
  })
967
1163
  ]
968
1164
  });
969
1165
  },
970
1166
  operation(node, ctx) {
1167
+ if (!_kubb_core.ast.isHttpOperationNode(node)) return null;
971
1168
  const { adapter, config, resolver, root } = ctx;
972
1169
  const { output, coercion, guidType, mini, wrapOutput, inferred, importPath, group, paramsCasing, printer } = ctx.options;
973
1170
  const dateType = adapter.options.dateType;
@@ -985,11 +1182,12 @@ const zodGenerator = (0, _kubb_core.defineGenerator)({
985
1182
  group: group ?? void 0
986
1183
  }) };
987
1184
  const cyclicSchemas = new Set(ctx.meta.circularNames);
988
- function renderSchemaEntry({ schema, name, keysToOmit }) {
1185
+ function renderSchemaEntry({ schema, name, keysToOmit, direction = "output" }) {
989
1186
  if (!schema) return null;
990
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;
991
1189
  const imports = adapter.getImports(schema, (schemaName) => ({
992
- name: resolver.resolveSchemaName(schemaName),
1190
+ name: codecRefNames?.has(schemaName) ? resolver.resolveInputSchemaName(schemaName) : resolver.resolveSchemaName(schemaName),
993
1191
  path: resolver.resolveFile({
994
1192
  name: schemaName,
995
1193
  extname: ".ts"
@@ -999,8 +1197,6 @@ const zodGenerator = (0, _kubb_core.defineGenerator)({
999
1197
  group: group ?? void 0
1000
1198
  }).path
1001
1199
  }));
1002
- const cachedStd = zodPrinterCache.get(resolver);
1003
- const cachedMini = zodMiniPrinterCache.get(resolver);
1004
1200
  const schemaPrinter = mini ? keysToOmit?.length ? printerZodMini({
1005
1201
  guidType,
1006
1202
  wrapOutput,
@@ -1008,10 +1204,9 @@ const zodGenerator = (0, _kubb_core.defineGenerator)({
1008
1204
  keysToOmit,
1009
1205
  cyclicSchemas,
1010
1206
  nodes: printer?.nodes
1011
- }) : cachedMini?.guidType === guidType ? cachedMini.printer : printerZodMini({
1207
+ }) : getMiniPrinter(resolver, {
1012
1208
  guidType,
1013
1209
  wrapOutput,
1014
- resolver,
1015
1210
  cyclicSchemas,
1016
1211
  nodes: printer?.nodes
1017
1212
  }) : keysToOmit?.length ? printerZod({
@@ -1022,16 +1217,16 @@ const zodGenerator = (0, _kubb_core.defineGenerator)({
1022
1217
  resolver,
1023
1218
  keysToOmit,
1024
1219
  cyclicSchemas,
1025
- nodes: printer?.nodes
1026
- }) : cachedStd?.coercion === coercion && cachedStd?.guidType === guidType && cachedStd?.dateType === dateType ? cachedStd.printer : printerZod({
1220
+ nodes: printer?.nodes,
1221
+ direction
1222
+ }) : getStdPrinters(resolver, {
1027
1223
  coercion,
1028
1224
  guidType,
1029
1225
  dateType,
1030
1226
  wrapOutput,
1031
- resolver,
1032
1227
  cyclicSchemas,
1033
1228
  nodes: printer?.nodes
1034
- });
1229
+ })[direction];
1035
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, {
1036
1231
  root: meta.file.path,
1037
1232
  path: imp.path,
@@ -1047,22 +1242,48 @@ const zodGenerator = (0, _kubb_core.defineGenerator)({
1047
1242
  inferTypeName
1048
1243
  })] });
1049
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
+ }
1050
1265
  const paramSchemas = params.map((param) => renderSchemaEntry({
1051
1266
  schema: param.schema,
1052
- name: resolver.resolveParamName(node, param)
1267
+ name: resolver.resolveParamName(node, param),
1268
+ direction: "input"
1053
1269
  }));
1054
- const responseSchemas = node.responses.map((res) => renderSchemaEntry({
1055
- schema: res.content?.[0]?.schema ?? null,
1056
- name: resolver.resolveResponseStatusName(node, res.statusCode),
1057
- keysToOmit: res.content?.[0]?.keysToOmit
1058
- }));
1059
- const responsesWithSchema = node.responses.filter((res) => res.content?.[0]?.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));
1060
1281
  const responseUnionSchema = responsesWithSchema.length > 0 ? (() => {
1061
1282
  const responseUnionName = resolver.resolveResponseName(node);
1062
- if (new Set(responsesWithSchema.flatMap((res) => res.content?.[0]?.schema ? adapter.getImports(res.content[0].schema, (schemaName) => ({
1283
+ if (new Set(responsesWithSchema.flatMap((res) => (res.content ?? []).flatMap((entry) => entry.schema ? adapter.getImports(entry.schema, (schemaName) => ({
1063
1284
  name: resolver.resolveSchemaName(schemaName),
1064
1285
  path: ""
1065
- })).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;
1066
1287
  const members = responsesWithSchema.map((res) => _kubb_core.ast.createSchema({
1067
1288
  type: "ref",
1068
1289
  name: resolver.resolveResponseStatusName(node, res.statusCode)
@@ -1075,14 +1296,27 @@ const zodGenerator = (0, _kubb_core.defineGenerator)({
1075
1296
  name: responseUnionName
1076
1297
  });
1077
1298
  })() : null;
1078
- const requestSchema = node.requestBody?.content?.[0]?.schema ? renderSchemaEntry({
1079
- schema: {
1080
- ...node.requestBody.content[0].schema,
1081
- description: node.requestBody.description ?? node.requestBody.content[0].schema.description
1082
- },
1083
- name: resolver.resolveDataName(node),
1084
- keysToOmit: node.requestBody.content[0].keysToOmit
1085
- }) : 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
+ })();
1086
1320
  return /* @__PURE__ */ (0, _kubb_renderer_jsx_jsx_runtime.jsxs)(_kubb_renderer_jsx.File, {
1087
1321
  baseName: meta.file.baseName,
1088
1322
  path: meta.file.path,
@@ -1129,7 +1363,7 @@ const zodGenerator = (0, _kubb_core.defineGenerator)({
1129
1363
  output,
1130
1364
  group: group ?? void 0
1131
1365
  }) };
1132
- const transformedOperations = nodes.map((node) => {
1366
+ const transformedOperations = nodes.filter(_kubb_core.ast.isHttpOperationNode).map((node) => {
1133
1367
  return {
1134
1368
  node,
1135
1369
  data: buildSchemaNames(node, {
@@ -1228,6 +1462,12 @@ const resolverZod = (0, _kubb_core.defineResolver)(() => {
1228
1462
  resolveSchemaTypeName(name) {
1229
1463
  return ensureValidVarName(pascalCase(name, { suffix: "schema" }));
1230
1464
  },
1465
+ resolveInputSchemaName(name) {
1466
+ return this.resolveSchemaName(`${name} input`);
1467
+ },
1468
+ resolveInputSchemaTypeName(name) {
1469
+ return this.resolveSchemaTypeName(`${name} input`);
1470
+ },
1231
1471
  resolveTypeName(name) {
1232
1472
  return ensureValidVarName(pascalCase(name));
1233
1473
  },
@@ -1297,13 +1537,7 @@ const pluginZod = (0, _kubb_core.definePlugin)((options) => {
1297
1537
  path: "zod",
1298
1538
  barrelType: "named"
1299
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;
1300
- const groupConfig = group ? {
1301
- ...group,
1302
- name: (ctx) => {
1303
- if (group.type === "path") return `${ctx.group.split("/")[1]}`;
1304
- return `${camelCase(ctx.group)}Controller`;
1305
- }
1306
- } : null;
1540
+ const groupConfig = createGroupConfig(group, { suffix: "Controller" });
1307
1541
  return {
1308
1542
  name: pluginZodName,
1309
1543
  options,