@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.js CHANGED
@@ -249,6 +249,89 @@ function ensureValidVarName(name) {
249
249
  return `_${name}`;
250
250
  }
251
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
252
335
  //#region src/components/Operations.tsx
253
336
  function Operations({ name, operations }) {
254
337
  const operationsJSON = operations.reduce((prev, acc) => {
@@ -256,6 +339,7 @@ function Operations({ name, operations }) {
256
339
  return prev;
257
340
  }, {});
258
341
  const pathsJSON = operations.reduce((prev, acc) => {
342
+ if (!ast.isHttpOperationNode(acc.node)) return prev;
259
343
  prev[`"${acc.node.path}"`] = {
260
344
  ...prev[`"${acc.node.path}"`] ?? {},
261
345
  [acc.node.method]: `operations["${acc.node.operationId}"]`
@@ -365,6 +449,61 @@ function shouldCoerce(coercion, type) {
365
449
  return !!coercion[type];
366
450
  }
367
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
+ /**
368
507
  * Collects all resolved schema names for an operation's parameters and responses
369
508
  * into a single lookup object, useful for building imports and type references.
370
509
  */
@@ -536,8 +675,9 @@ const printerZod = ast.definePrinter((options) => {
536
675
  return shouldCoerce(this.options.coercion, "numbers") ? "z.coerce.bigint()" : "z.bigint()";
537
676
  },
538
677
  date(node) {
539
- if (node.representation === "string") return "z.iso.date()";
540
- 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()";
541
681
  },
542
682
  datetime(node) {
543
683
  const offset = node.offset || this.options.dateType === "stringOffset";
@@ -574,7 +714,8 @@ const printerZod = ast.definePrinter((options) => {
574
714
  ref(node) {
575
715
  if (!node.name) return null;
576
716
  const refName = node.ref ? ast.extractRefName(node.ref) ?? node.name : node.name;
577
- 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;
578
719
  if (node.ref && this.options.cyclicSchemas?.has(refName)) return `z.lazy(() => ${resolvedName})`;
579
720
  return resolvedName;
580
721
  },
@@ -841,6 +982,54 @@ const printerZodMini = ast.definePrinter((options) => {
841
982
  const zodPrinterCache = /* @__PURE__ */ new WeakMap();
842
983
  const zodMiniPrinterCache = /* @__PURE__ */ new WeakMap();
843
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
+ /**
844
1033
  * Built-in generator for `@kubb/plugin-zod`. Emits one Zod schema per
845
1034
  * schema in the spec plus per-operation request/response/parameter schemas.
846
1035
  * When `mini: true`, schemas use the Zod Mini functional API instead of
@@ -856,7 +1045,10 @@ const zodGenerator = defineGenerator({
856
1045
  if (!node.name) return;
857
1046
  const mode = ctx.getMode(output);
858
1047
  const isZodImport = ZOD_NAMESPACE_IMPORTS.has(importPath);
859
- 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) => ({
860
1052
  name: resolver.resolveSchemaName(schemaName),
861
1053
  path: resolver.resolveFile({
862
1054
  name: schemaName,
@@ -867,6 +1059,24 @@ const zodGenerator = defineGenerator({
867
1059
  group: group ?? void 0
868
1060
  }).path
869
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
+ });
870
1080
  const meta = {
871
1081
  name: resolver.resolveSchemaName(node.name),
872
1082
  file: resolver.resolveFile({
@@ -879,44 +1089,20 @@ const zodGenerator = defineGenerator({
879
1089
  })
880
1090
  };
881
1091
  const inferTypeName = inferred ? resolver.resolveSchemaTypeName(node.name) : null;
882
- const cyclicSchemas = new Set(ctx.meta.circularNames);
883
- const schemaPrinter = mini ? getCachedMiniPrinter() : getCachedStdPrinter();
884
- function getCachedStdPrinter() {
885
- const cached = zodPrinterCache.get(resolver);
886
- if (cached && cached.coercion === coercion && cached.guidType === guidType && cached.dateType === dateType) return cached.printer;
887
- const p = printerZod({
888
- coercion,
889
- guidType,
890
- dateType,
891
- wrapOutput,
892
- resolver,
893
- cyclicSchemas,
894
- nodes: printer?.nodes
895
- });
896
- zodPrinterCache.set(resolver, {
897
- printer: p,
898
- coercion,
899
- guidType,
900
- dateType
901
- });
902
- return p;
903
- }
904
- function getCachedMiniPrinter() {
905
- const cached = zodMiniPrinterCache.get(resolver);
906
- if (cached && cached.guidType === guidType) return cached.printer;
907
- const p = printerZodMini({
908
- guidType,
909
- wrapOutput,
910
- resolver,
911
- cyclicSchemas,
912
- nodes: printer?.nodes
913
- });
914
- zodMiniPrinterCache.set(resolver, {
915
- printer: p,
916
- guidType
917
- });
918
- return p;
919
- }
1092
+ const stdPrinters = mini ? null : getStdPrinters(resolver, {
1093
+ coercion,
1094
+ guidType,
1095
+ dateType,
1096
+ wrapOutput,
1097
+ cyclicSchemas,
1098
+ nodes: printer?.nodes
1099
+ });
1100
+ const schemaPrinter = mini ? getMiniPrinter(resolver, {
1101
+ guidType,
1102
+ wrapOutput,
1103
+ cyclicSchemas,
1104
+ nodes: printer?.nodes
1105
+ }) : stdPrinters.output;
920
1106
  return /* @__PURE__ */ jsxs(File, {
921
1107
  baseName: meta.file.baseName,
922
1108
  path: meta.file.path,
@@ -947,17 +1133,28 @@ const zodGenerator = defineGenerator({
947
1133
  root: meta.file.path,
948
1134
  path: imp.path,
949
1135
  name: imp.name
950
- }, [node.name, imp.path].join("-"))),
1136
+ }, [
1137
+ node.name,
1138
+ imp.path,
1139
+ imp.name
1140
+ ].join("-"))),
951
1141
  /* @__PURE__ */ jsx(Zod, {
952
1142
  name: meta.name,
953
1143
  node,
954
1144
  printer: schemaPrinter,
955
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
956
1152
  })
957
1153
  ]
958
1154
  });
959
1155
  },
960
1156
  operation(node, ctx) {
1157
+ if (!ast.isHttpOperationNode(node)) return null;
961
1158
  const { adapter, config, resolver, root } = ctx;
962
1159
  const { output, coercion, guidType, mini, wrapOutput, inferred, importPath, group, paramsCasing, printer } = ctx.options;
963
1160
  const dateType = adapter.options.dateType;
@@ -975,11 +1172,12 @@ const zodGenerator = defineGenerator({
975
1172
  group: group ?? void 0
976
1173
  }) };
977
1174
  const cyclicSchemas = new Set(ctx.meta.circularNames);
978
- function renderSchemaEntry({ schema, name, keysToOmit }) {
1175
+ function renderSchemaEntry({ schema, name, keysToOmit, direction = "output" }) {
979
1176
  if (!schema) return null;
980
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;
981
1179
  const imports = adapter.getImports(schema, (schemaName) => ({
982
- name: resolver.resolveSchemaName(schemaName),
1180
+ name: codecRefNames?.has(schemaName) ? resolver.resolveInputSchemaName(schemaName) : resolver.resolveSchemaName(schemaName),
983
1181
  path: resolver.resolveFile({
984
1182
  name: schemaName,
985
1183
  extname: ".ts"
@@ -989,8 +1187,6 @@ const zodGenerator = defineGenerator({
989
1187
  group: group ?? void 0
990
1188
  }).path
991
1189
  }));
992
- const cachedStd = zodPrinterCache.get(resolver);
993
- const cachedMini = zodMiniPrinterCache.get(resolver);
994
1190
  const schemaPrinter = mini ? keysToOmit?.length ? printerZodMini({
995
1191
  guidType,
996
1192
  wrapOutput,
@@ -998,10 +1194,9 @@ const zodGenerator = defineGenerator({
998
1194
  keysToOmit,
999
1195
  cyclicSchemas,
1000
1196
  nodes: printer?.nodes
1001
- }) : cachedMini?.guidType === guidType ? cachedMini.printer : printerZodMini({
1197
+ }) : getMiniPrinter(resolver, {
1002
1198
  guidType,
1003
1199
  wrapOutput,
1004
- resolver,
1005
1200
  cyclicSchemas,
1006
1201
  nodes: printer?.nodes
1007
1202
  }) : keysToOmit?.length ? printerZod({
@@ -1012,16 +1207,16 @@ const zodGenerator = defineGenerator({
1012
1207
  resolver,
1013
1208
  keysToOmit,
1014
1209
  cyclicSchemas,
1015
- nodes: printer?.nodes
1016
- }) : cachedStd?.coercion === coercion && cachedStd?.guidType === guidType && cachedStd?.dateType === dateType ? cachedStd.printer : printerZod({
1210
+ nodes: printer?.nodes,
1211
+ direction
1212
+ }) : getStdPrinters(resolver, {
1017
1213
  coercion,
1018
1214
  guidType,
1019
1215
  dateType,
1020
1216
  wrapOutput,
1021
- resolver,
1022
1217
  cyclicSchemas,
1023
1218
  nodes: printer?.nodes
1024
- });
1219
+ })[direction];
1025
1220
  return /* @__PURE__ */ jsxs(Fragment, { children: [mode === "split" && imports.map((imp) => /* @__PURE__ */ jsx(File.Import, {
1026
1221
  root: meta.file.path,
1027
1222
  path: imp.path,
@@ -1037,22 +1232,48 @@ const zodGenerator = defineGenerator({
1037
1232
  inferTypeName
1038
1233
  })] });
1039
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
+ }
1040
1255
  const paramSchemas = params.map((param) => renderSchemaEntry({
1041
1256
  schema: param.schema,
1042
- name: resolver.resolveParamName(node, param)
1257
+ name: resolver.resolveParamName(node, param),
1258
+ direction: "input"
1043
1259
  }));
1044
- const responseSchemas = node.responses.map((res) => renderSchemaEntry({
1045
- schema: res.content?.[0]?.schema ?? null,
1046
- name: resolver.resolveResponseStatusName(node, res.statusCode),
1047
- keysToOmit: res.content?.[0]?.keysToOmit
1048
- }));
1049
- const responsesWithSchema = node.responses.filter((res) => res.content?.[0]?.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));
1050
1271
  const responseUnionSchema = responsesWithSchema.length > 0 ? (() => {
1051
1272
  const responseUnionName = resolver.resolveResponseName(node);
1052
- if (new Set(responsesWithSchema.flatMap((res) => res.content?.[0]?.schema ? adapter.getImports(res.content[0].schema, (schemaName) => ({
1273
+ if (new Set(responsesWithSchema.flatMap((res) => (res.content ?? []).flatMap((entry) => entry.schema ? adapter.getImports(entry.schema, (schemaName) => ({
1053
1274
  name: resolver.resolveSchemaName(schemaName),
1054
1275
  path: ""
1055
- })).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;
1056
1277
  const members = responsesWithSchema.map((res) => ast.createSchema({
1057
1278
  type: "ref",
1058
1279
  name: resolver.resolveResponseStatusName(node, res.statusCode)
@@ -1065,14 +1286,27 @@ const zodGenerator = defineGenerator({
1065
1286
  name: responseUnionName
1066
1287
  });
1067
1288
  })() : null;
1068
- const requestSchema = node.requestBody?.content?.[0]?.schema ? renderSchemaEntry({
1069
- schema: {
1070
- ...node.requestBody.content[0].schema,
1071
- description: node.requestBody.description ?? node.requestBody.content[0].schema.description
1072
- },
1073
- name: resolver.resolveDataName(node),
1074
- keysToOmit: node.requestBody.content[0].keysToOmit
1075
- }) : 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
+ })();
1076
1310
  return /* @__PURE__ */ jsxs(File, {
1077
1311
  baseName: meta.file.baseName,
1078
1312
  path: meta.file.path,
@@ -1119,7 +1353,7 @@ const zodGenerator = defineGenerator({
1119
1353
  output,
1120
1354
  group: group ?? void 0
1121
1355
  }) };
1122
- const transformedOperations = nodes.map((node) => {
1356
+ const transformedOperations = nodes.filter(ast.isHttpOperationNode).map((node) => {
1123
1357
  return {
1124
1358
  node,
1125
1359
  data: buildSchemaNames(node, {
@@ -1218,6 +1452,12 @@ const resolverZod = defineResolver(() => {
1218
1452
  resolveSchemaTypeName(name) {
1219
1453
  return ensureValidVarName(pascalCase(name, { suffix: "schema" }));
1220
1454
  },
1455
+ resolveInputSchemaName(name) {
1456
+ return this.resolveSchemaName(`${name} input`);
1457
+ },
1458
+ resolveInputSchemaTypeName(name) {
1459
+ return this.resolveSchemaTypeName(`${name} input`);
1460
+ },
1221
1461
  resolveTypeName(name) {
1222
1462
  return ensureValidVarName(pascalCase(name));
1223
1463
  },
@@ -1287,13 +1527,7 @@ const pluginZod = definePlugin((options) => {
1287
1527
  path: "zod",
1288
1528
  barrelType: "named"
1289
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;
1290
- const groupConfig = group ? {
1291
- ...group,
1292
- name: (ctx) => {
1293
- if (group.type === "path") return `${ctx.group.split("/")[1]}`;
1294
- return `${camelCase(ctx.group)}Controller`;
1295
- }
1296
- } : null;
1530
+ const groupConfig = createGroupConfig(group, { suffix: "Controller" });
1297
1531
  return {
1298
1532
  name: pluginZodName,
1299
1533
  options,