@kubb/plugin-client 5.0.0-beta.42 → 5.0.0-beta.64

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.
Files changed (39) hide show
  1. package/dist/clients/axios.cjs.map +1 -1
  2. package/dist/clients/axios.js.map +1 -1
  3. package/dist/clients/fetch.cjs.map +1 -1
  4. package/dist/clients/fetch.js.map +1 -1
  5. package/dist/index.cjs +439 -383
  6. package/dist/index.cjs.map +1 -1
  7. package/dist/index.d.ts +60 -25
  8. package/dist/index.js +435 -383
  9. package/dist/index.js.map +1 -1
  10. package/dist/templates/clients/axios.source.cjs.map +1 -1
  11. package/dist/templates/clients/axios.source.js.map +1 -1
  12. package/dist/templates/clients/fetch.source.cjs.map +1 -1
  13. package/dist/templates/clients/fetch.source.js.map +1 -1
  14. package/dist/templates/config.source.cjs.map +1 -1
  15. package/dist/templates/config.source.js.map +1 -1
  16. package/package.json +10 -18
  17. package/extension.yaml +0 -1267
  18. package/src/clients/axios.ts +0 -113
  19. package/src/clients/fetch.ts +0 -201
  20. package/src/components/ClassClient.tsx +0 -137
  21. package/src/components/Client.tsx +0 -273
  22. package/src/components/Operations.tsx +0 -29
  23. package/src/components/StaticClassClient.tsx +0 -129
  24. package/src/components/Url.tsx +0 -91
  25. package/src/components/WrapperClient.tsx +0 -33
  26. package/src/functionParams.ts +0 -118
  27. package/src/generators/classClientGenerator.tsx +0 -253
  28. package/src/generators/clientGenerator.tsx +0 -127
  29. package/src/generators/groupedClientGenerator.tsx +0 -82
  30. package/src/generators/operationsGenerator.tsx +0 -34
  31. package/src/generators/staticClassClientGenerator.tsx +0 -232
  32. package/src/index.ts +0 -9
  33. package/src/plugin.ts +0 -160
  34. package/src/resolvers/resolverClient.ts +0 -45
  35. package/src/templates/clients/axios.source.ts +0 -4
  36. package/src/templates/clients/fetch.source.ts +0 -4
  37. package/src/templates/config.source.ts +0 -4
  38. package/src/types.ts +0 -268
  39. package/src/utils.ts +0 -159
package/dist/index.cjs CHANGED
@@ -10,6 +10,7 @@ let node_path = require("node:path");
10
10
  let node_path$1 = require_chunk.__toESM(node_path, 1);
11
11
  node_path = require_chunk.__toESM(node_path);
12
12
  let _kubb_core = require("@kubb/core");
13
+ let _kubb_ast_utils = require("@kubb/ast/utils");
13
14
  let _kubb_plugin_ts = require("@kubb/plugin-ts");
14
15
  let _kubb_renderer_jsx = require("@kubb/renderer-jsx");
15
16
  let _kubb_renderer_jsx_jsx_runtime = require("@kubb/renderer-jsx/jsx-runtime");
@@ -25,68 +26,57 @@ let _kubb_plugin_zod = require("@kubb/plugin-zod");
25
26
  function toCamelOrPascal(text, pascal) {
26
27
  return text.trim().replace(/([a-z\d])([A-Z])/g, "$1 $2").replace(/([A-Z]+)([A-Z][a-z])/g, "$1 $2").replace(/(\d)([a-z])/g, "$1 $2").split(/[\s\-_./\\:]+/).filter(Boolean).map((word, i) => {
27
28
  if (word.length > 1 && word === word.toUpperCase()) return word;
28
- if (i === 0 && !pascal) return word.charAt(0).toLowerCase() + word.slice(1);
29
- return word.charAt(0).toUpperCase() + word.slice(1);
29
+ return (i === 0 && !pascal ? word.charAt(0).toLowerCase() : word.charAt(0).toUpperCase()) + word.slice(1);
30
30
  }).join("").replace(/[^a-zA-Z0-9]/g, "");
31
31
  }
32
32
  /**
33
- * Splits `text` on `.` and applies `transformPart` to each segment.
34
- * The last segment receives `isLast = true`, all earlier segments receive `false`.
35
- * Segments are joined with `/` to form a file path.
36
- *
37
- * Only splits on dots followed by a letter so that version numbers
38
- * embedded in operationIds (e.g. `v2025.0`) are kept intact.
39
- */
40
- function applyToFileParts(text, transformPart) {
41
- const parts = text.split(/\.(?=[a-zA-Z])/);
42
- return parts.map((part, i) => transformPart(part, i === parts.length - 1)).join("/");
43
- }
44
- /**
45
33
  * Converts `text` to camelCase.
46
- * When `isFile` is `true`, dot-separated segments are each cased independently and joined with `/`.
47
34
  *
48
- * @example
49
- * camelCase('hello-world') // 'helloWorld'
50
- * camelCase('pet.petId', { isFile: true }) // 'pet/petId'
35
+ * @example Word boundaries
36
+ * `camelCase('hello-world') // 'helloWorld'`
37
+ *
38
+ * @example With a prefix
39
+ * `camelCase('tag', { prefix: 'create' }) // 'createTag'`
51
40
  */
52
- function camelCase(text, { isFile, prefix = "", suffix = "" } = {}) {
53
- if (isFile) return applyToFileParts(text, (part, isLast) => camelCase(part, isLast ? {
54
- prefix,
55
- suffix
56
- } : {}));
41
+ function camelCase(text, { prefix = "", suffix = "" } = {}) {
57
42
  return toCamelOrPascal(`${prefix} ${text} ${suffix}`, false);
58
43
  }
59
44
  /**
60
45
  * Converts `text` to PascalCase.
61
- * When `isFile` is `true`, the last dot-separated segment is PascalCased and earlier segments are camelCased.
62
46
  *
63
- * @example
64
- * pascalCase('hello-world') // 'HelloWorld'
65
- * pascalCase('pet.petId', { isFile: true }) // 'pet/PetId'
47
+ * @example Word boundaries
48
+ * `pascalCase('hello-world') // 'HelloWorld'`
49
+ *
50
+ * @example With a suffix
51
+ * `pascalCase('tag', { suffix: 'schema' }) // 'TagSchema'`
66
52
  */
67
- function pascalCase(text, { isFile, prefix = "", suffix = "" } = {}) {
68
- if (isFile) return applyToFileParts(text, (part, isLast) => isLast ? pascalCase(part, {
69
- prefix,
70
- suffix
71
- }) : camelCase(part));
53
+ function pascalCase(text, { prefix = "", suffix = "" } = {}) {
72
54
  return toCamelOrPascal(`${prefix} ${text} ${suffix}`, true);
73
55
  }
74
56
  //#endregion
75
- //#region ../../internals/utils/src/jsdoc.ts
57
+ //#region ../../internals/utils/src/fs.ts
76
58
  /**
77
- * Builds a JSDoc comment block from an array of lines.
78
- * Returns `fallback` when `comments` is empty so callers always get a usable string.
59
+ * Builds a nested file path from a dotted name. Splits on dots that precede a letter
60
+ * (so version numbers embedded in operationIds like `v2025.0` stay intact), camelCases
61
+ * every earlier segment, applies `caseLast` to the final segment, and joins with `/`.
79
62
  *
80
- * @example
81
- * ```ts
82
- * buildJSDoc(['@type string', '@example hello'])
83
- * // '/**\n * @type string\n * @example hello\n *\/\n '
84
- * ```
63
+ * Empty segments are dropped before joining. They arise when the name starts with a dot
64
+ * followed by a letter (e.g. `..Schema` splits into `['..', 'Schema']` and `'..'` cases to
65
+ * an empty string). Without this a leading `/` would form, which `path.resolve` reads as an
66
+ * absolute path, letting generated files escape the configured output directory.
67
+ *
68
+ * @example Nested path from a dotted name
69
+ * `toFilePath('pet.petId') // 'pet/petId'`
70
+ *
71
+ * @example PascalCase the final segment
72
+ * `toFilePath('pet.Pet', pascalCase) // 'pet/Pet'`
73
+ *
74
+ * @example Suffix applied to the final segment only
75
+ * `toFilePath('tag.tag', (part) => camelCase(part, { suffix: 'schema' })) // 'tag/tagSchema'`
85
76
  */
86
- function buildJSDoc(comments, options = {}) {
87
- const { indent = " * ", suffix = "\n ", fallback = " " } = options;
88
- if (comments.length === 0) return fallback;
89
- return `/**\n${comments.map((c) => `${indent}${c}`).join("\n")}\n */${suffix}`;
77
+ function toFilePath(name, caseLast = camelCase) {
78
+ const parts = name.split(/\.(?=[a-zA-Z])/);
79
+ return parts.map((part, i) => i === parts.length - 1 ? caseLast(part) : camelCase(part)).filter(Boolean).join("/");
90
80
  }
91
81
  //#endregion
92
82
  //#region ../../internals/utils/src/reserved.ts
@@ -212,99 +202,83 @@ function ensureValidVarName(name) {
212
202
  return `_${name}`;
213
203
  }
214
204
  //#endregion
215
- //#region ../../internals/utils/src/urlPath.ts
205
+ //#region ../../internals/utils/src/url.ts
206
+ function transformParam(raw, casing) {
207
+ const param = isValidVarName(raw) ? raw : camelCase(raw);
208
+ return casing === "camelcase" ? camelCase(param) : param;
209
+ }
210
+ function toParamsObject(path, { replacer, casing } = {}) {
211
+ const params = {};
212
+ for (const match of path.matchAll(/\{([^}]+)\}/g)) {
213
+ const param = transformParam(match[1], casing);
214
+ const key = replacer ? replacer(param) : param;
215
+ params[key] = key;
216
+ }
217
+ return Object.keys(params).length > 0 ? params : null;
218
+ }
216
219
  /**
217
- * Parses and transforms an OpenAPI/Swagger path string into various URL formats.
218
- *
219
- * @example
220
- * const p = new URLPath('/pet/{petId}')
221
- * p.URL // '/pet/:petId'
222
- * p.template // '`/pet/${petId}`'
220
+ * Helpers for OpenAPI/Swagger paths, plus a thin wrapper over the native `URL`.
223
221
  */
224
- var URLPath = class {
225
- /**
226
- * The raw OpenAPI/Swagger path string, e.g. `/pet/{petId}`.
227
- */
228
- path;
229
- #options;
230
- constructor(path, options = {}) {
231
- this.path = path;
232
- this.#options = options;
222
+ var Url$1 = class Url$1 {
223
+ static {
224
+ require_chunk.__name(this, "Url");
233
225
  }
234
- /** Converts the OpenAPI path to Express-style colon syntax, e.g. `/pet/{petId}` → `/pet/:petId`.
226
+ /**
227
+ * Reports whether `url` is a parseable absolute URL. Delegates to the native `URL.canParse`.
235
228
  *
236
229
  * @example
237
- * ```ts
238
- * new URLPath('/pet/{petId}').URL // '/pet/:petId'
239
- * ```
230
+ * Url.canParse('https://petstore.swagger.io/v2') // true
231
+ * Url.canParse('/pet/{petId}') // false
240
232
  */
241
- get URL() {
242
- return this.toURLPath();
233
+ static canParse(url, base) {
234
+ return URL.canParse(url, base);
243
235
  }
244
- /** Returns `true` when `path` is a fully-qualified URL (e.g. starts with `https://`).
236
+ /**
237
+ * Converts an OpenAPI/Swagger path to Express-style colon syntax.
245
238
  *
246
239
  * @example
247
- * ```ts
248
- * new URLPath('https://petstore.swagger.io/v2/pet').isURL // true
249
- * new URLPath('/pet/{petId}').isURL // false
250
- * ```
240
+ * Url.toPath('/pet/{petId}') // '/pet/:petId'
251
241
  */
252
- get isURL() {
253
- try {
254
- return !!new URL(this.path).href;
255
- } catch {
256
- return false;
257
- }
242
+ static toPath(path) {
243
+ return path.replace(/\{([^}]+)\}/g, ":$1");
258
244
  }
259
245
  /**
260
- * Converts the OpenAPI path to a TypeScript template literal string.
246
+ * Converts an OpenAPI/Swagger path to a TypeScript template literal string.
247
+ * `prefix` is prepended inside the literal, `replacer` transforms each parameter name,
248
+ * and `casing` controls parameter identifier casing.
261
249
  *
262
250
  * @example
263
- * new URLPath('/pet/{petId}').template // '`/pet/${petId}`'
264
- * new URLPath('/account/monetary-accountID').template // '`/account/${monetaryAccountId}`'
265
- */
266
- get template() {
267
- return this.toTemplateString();
268
- }
269
- /** Returns the path and its extracted params as a structured `URLObject`, or as a stringified expression when `stringify` is set.
251
+ * Url.toTemplateString('/pet/{petId}') // '`/pet/${petId}`'
270
252
  *
271
253
  * @example
272
- * ```ts
273
- * new URLPath('/pet/{petId}').object
274
- * // { url: '/pet/:petId', params: { petId: 'petId' } }
275
- * ```
254
+ * Url.toTemplateString('/pet/{petId}', { prefix: 'https://api' }) // '`https://api/pet/${petId}`'
276
255
  */
277
- get object() {
278
- return this.toObject();
256
+ static toTemplateString(path, { prefix, replacer, casing } = {}) {
257
+ const result = path.split(/\{([^}]+)\}/).map((part, i) => {
258
+ if (i % 2 === 0) return part;
259
+ const param = transformParam(part, casing);
260
+ return `\${${replacer ? replacer(param) : param}}`;
261
+ }).join("");
262
+ return `\`${prefix ?? ""}${result}\``;
279
263
  }
280
- /** Returns a map of path parameter names, or `null` when the path has no parameters.
264
+ /**
265
+ * Returns the path and its extracted params as a structured `URLObject`, or as a stringified
266
+ * expression when `stringify` is set.
281
267
  *
282
268
  * @example
283
- * ```ts
284
- * new URLPath('/pet/{petId}').params // { petId: 'petId' }
285
- * new URLPath('/pet').params // null
286
- * ```
287
- */
288
- get params() {
289
- return this.toParamsObject();
290
- }
291
- #transformParam(raw) {
292
- const param = isValidVarName(raw) ? raw : camelCase(raw);
293
- return this.#options.casing === "camelcase" ? camelCase(param) : param;
294
- }
295
- /**
296
- * Iterates over every `{param}` token in `path`, calling `fn` with the raw token and transformed name.
269
+ * Url.toObject('/pet/{petId}')
270
+ * // { url: '/pet/:petId', params: { petId: 'petId' } }
297
271
  */
298
- #eachParam(fn) {
299
- for (const match of this.path.matchAll(/\{([^}]+)\}/g)) {
300
- const raw = match[1];
301
- fn(raw, this.#transformParam(raw));
302
- }
303
- }
304
- toObject({ type = "path", replacer, stringify } = {}) {
272
+ static toObject(path, { type = "path", replacer, stringify, casing } = {}) {
305
273
  const object = {
306
- url: type === "path" ? this.toURLPath() : this.toTemplateString({ replacer }),
307
- params: this.toParamsObject()
274
+ url: type === "path" ? Url$1.toPath(path) : Url$1.toTemplateString(path, {
275
+ replacer,
276
+ casing
277
+ }),
278
+ params: toParamsObject(path, {
279
+ replacer,
280
+ casing
281
+ })
308
282
  };
309
283
  if (stringify) {
310
284
  if (type === "template") return JSON.stringify(object).replaceAll("'", "").replaceAll(`"`, "");
@@ -313,50 +287,6 @@ var URLPath = class {
313
287
  }
314
288
  return object;
315
289
  }
316
- /**
317
- * Converts the OpenAPI path to a TypeScript template literal string.
318
- * An optional `replacer` can transform each extracted parameter name before interpolation.
319
- *
320
- * @example
321
- * new URLPath('/pet/{petId}').toTemplateString() // '`/pet/${petId}`'
322
- */
323
- toTemplateString({ prefix, replacer } = {}) {
324
- const result = this.path.split(/\{([^}]+)\}/).map((part, i) => {
325
- if (i % 2 === 0) return part;
326
- const param = this.#transformParam(part);
327
- return `\${${replacer ? replacer(param) : param}}`;
328
- }).join("");
329
- return `\`${prefix ?? ""}${result}\``;
330
- }
331
- /**
332
- * Extracts all `{param}` segments from the path and returns them as a key-value map.
333
- * An optional `replacer` transforms each parameter name in both key and value positions.
334
- * Returns `undefined` when no path parameters are found.
335
- *
336
- * @example
337
- * ```ts
338
- * new URLPath('/pet/{petId}/tag/{tagId}').toParamsObject()
339
- * // { petId: 'petId', tagId: 'tagId' }
340
- * ```
341
- */
342
- toParamsObject(replacer) {
343
- const params = {};
344
- this.#eachParam((_raw, param) => {
345
- const key = replacer ? replacer(param) : param;
346
- params[key] = key;
347
- });
348
- return Object.keys(params).length > 0 ? params : null;
349
- }
350
- /** Converts the OpenAPI path to Express-style colon syntax.
351
- *
352
- * @example
353
- * ```ts
354
- * new URLPath('/pet/{petId}').toURLPath() // '/pet/:petId'
355
- * ```
356
- */
357
- toURLPath() {
358
- return this.path.replace(/\{([^}]+)\}/g, ":$1");
359
- }
360
290
  };
361
291
  //#endregion
362
292
  //#region ../../internals/shared/src/operation.ts
@@ -382,7 +312,7 @@ function operationFileEntry(node, name, extname = ".ts") {
382
312
  function getOperationLink(node, link) {
383
313
  if (!link) return null;
384
314
  if (typeof link === "function") return link(node) ?? null;
385
- if (link === "urlPath") return node.path ? `{@link ${new URLPath(node.path).URL}}` : null;
315
+ if (link === "urlPath") return node.path ? `{@link ${Url$1.toPath(node.path)}}` : null;
386
316
  return node.path ? `{@link ${node.path.replaceAll("{", ":").replaceAll("}", "")}}` : null;
387
317
  }
388
318
  function getContentTypeInfo(node) {
@@ -435,7 +365,7 @@ function buildOperationComments(node, options = {}) {
435
365
  return filteredComments.flatMap((text) => text.split(/\r?\n/).map((line) => line.trim())).filter((comment) => Boolean(comment));
436
366
  }
437
367
  function getOperationParameters(node, options = {}) {
438
- const params = _kubb_core.ast.caseParams(node.parameters, options.paramsCasing);
368
+ const params = (0, _kubb_ast_utils.caseParams)(node.parameters, options.paramsCasing);
439
369
  return {
440
370
  path: params.filter((param) => param.in === "path"),
441
371
  query: params.filter((param) => param.in === "query"),
@@ -473,6 +403,19 @@ function resolveSuccessNames(node, resolver) {
473
403
  function resolveStatusCodeNames(node, resolver) {
474
404
  return node.responses.map((response) => resolver.resolveResponseStatusName(node, response.statusCode));
475
405
  }
406
+ /**
407
+ * Builds the discriminated union type string for `dataReturnType: 'full'` return shapes.
408
+ * Each member is `{ status: N; data: StatusNType; statusText: string }`.
409
+ */
410
+ function buildStatusUnionType(node, resolver) {
411
+ const members = node.responses.map((r) => {
412
+ const typeName = resolver.resolveResponseStatusName(node, r.statusCode);
413
+ const statusCode = Number.parseInt(r.statusCode, 10);
414
+ return `{ status: ${Number.isNaN(statusCode) ? "number" : String(statusCode)}; data: ${typeName}; statusText: string }`;
415
+ });
416
+ if (members.length === 1) return members[0];
417
+ return `(${members.join(" | ")})`;
418
+ }
476
419
  const typeNamesByResolver = /* @__PURE__ */ new WeakMap();
477
420
  function resolveOperationTypeNames(node, resolver, options = {}) {
478
421
  const cacheKey = `${node.operationId}\0${options.paramsCasing ?? ""}\0${options.order ?? ""}\0${options.responseStatusNames ?? ""}\0${(options.exclude ?? []).join(",")}`;
@@ -512,26 +455,24 @@ function resolveOperationTypeNames(node, resolver, options = {}) {
512
455
  * shared default naming so every plugin groups output consistently:
513
456
  *
514
457
  * - `path` groups use the second path segment (`/pet/findByStatus` → `pet`).
515
- * - other groups use `${camelCase(group)}${suffix}` (e.g. `petController`).
458
+ * - other groups use the camelCased group (`pet store``petStore`).
516
459
  *
517
460
  * A user-provided `group.name` always wins over the default namer, so callers stay in
518
461
  * control of their output folders. Returns `null` when grouping is disabled, matching the
519
462
  * per-plugin convention.
520
463
  *
521
464
  * @param group - The user-supplied group option, or `undefined` to disable grouping.
522
- * @param options.suffix - Appended to non-`path` group names, e.g. `'Controller'` or `'Requests'`.
523
465
  *
524
466
  * @example
525
467
  * ```ts
526
- * createGroupConfig(group, { suffix: 'Controller' }) // plugin-ts, plugin-client, …
527
- * createGroupConfig(group, { suffix: 'Requests' }) // plugin-cypress, plugin-mcp
468
+ * createGroupConfig(group) // shared across every plugin
528
469
  * ```
529
470
  */
530
- function createGroupConfig(group, options) {
471
+ function createGroupConfig(group) {
531
472
  if (!group) return null;
532
473
  const defaultName = (ctx) => {
533
474
  if (group.type === "path") return `${ctx.group.split("/")[1]}`;
534
- return `${camelCase(ctx.group)}${options.suffix}`;
475
+ return camelCase(ctx.group);
535
476
  };
536
477
  return {
537
478
  ...group,
@@ -557,43 +498,52 @@ const callPrinter = (0, _kubb_plugin_ts.functionPrinter)({ mode: "call" });
557
498
  function isGroup(spec) {
558
499
  return "children" in spec;
559
500
  }
560
- function createType(type) {
561
- return type ? _kubb_core.ast.createParamsType({
562
- variant: "reference",
563
- name: type
564
- }) : null;
501
+ function groupEntries(group) {
502
+ return Object.entries(group.children).filter(([, child]) => child != null);
503
+ }
504
+ /**
505
+ * Assembles a destructured group parameter from a binding pattern and an optional
506
+ * type literal. Built directly because `createFunctionParameter({ properties })`
507
+ * requires every member to carry a type, while these groups also hold untyped,
508
+ * value-only call entries.
509
+ */
510
+ function createGroupParam(elements, members, default_) {
511
+ return {
512
+ kind: "FunctionParameter",
513
+ name: _kubb_core.ast.factory.createObjectBindingPattern({ elements }),
514
+ type: members.length ? _kubb_core.ast.factory.createTypeLiteral({ members }) : void 0,
515
+ default: default_,
516
+ optional: false
517
+ };
565
518
  }
566
519
  function createDeclarationLeaf(name, spec) {
567
- if (spec.default !== void 0) return _kubb_core.ast.createFunctionParameter({
520
+ if (spec.default !== void 0) return _kubb_core.ast.factory.createFunctionParameter({
568
521
  name,
569
- type: createType(spec.type) ?? void 0,
522
+ type: spec.type,
570
523
  default: spec.default,
571
524
  rest: spec.mode === "inlineSpread"
572
525
  });
573
- return _kubb_core.ast.createFunctionParameter({
526
+ return _kubb_core.ast.factory.createFunctionParameter({
574
527
  name,
575
- type: createType(spec.type) ?? void 0,
528
+ type: spec.type,
576
529
  optional: !!spec.optional,
577
530
  rest: spec.mode === "inlineSpread"
578
531
  });
579
532
  }
580
533
  function createDeclarationParam(name, spec) {
581
- if (isGroup(spec)) return _kubb_core.ast.createParameterGroup({
582
- inline: spec.mode === "inlineSpread",
583
- default: spec.default,
584
- properties: Object.entries(spec.children).filter(([, child]) => child != null).map(([childName, child]) => createDeclarationLeaf(childName, child))
585
- });
534
+ if (isGroup(spec)) {
535
+ const entries = groupEntries(spec);
536
+ return createGroupParam(entries.map(([childName]) => ({ name: childName })), entries.filter(([, child]) => child.type).map(([childName, child]) => ({
537
+ name: childName,
538
+ type: child.type,
539
+ optional: !!child.optional || child.default !== void 0
540
+ })), spec.default);
541
+ }
586
542
  return createDeclarationLeaf(name, spec);
587
543
  }
588
544
  function createCallParam(name, spec) {
589
- if (isGroup(spec)) return _kubb_core.ast.createParameterGroup({
590
- inline: spec.mode === "inlineSpread",
591
- properties: Object.entries(spec.children).filter(([, child]) => child != null).map(([childName, child]) => _kubb_core.ast.createFunctionParameter({
592
- name: child?.mode === "inlineSpread" ? spec.mode === "inlineSpread" ? child.value ?? childName : `...${child.value ?? childName}` : child?.value ? `${childName}: ${child.value}` : childName,
593
- rest: spec.mode === "inlineSpread" && child?.mode === "inlineSpread"
594
- }))
595
- });
596
- return _kubb_core.ast.createFunctionParameter({
545
+ if (isGroup(spec)) return createGroupParam(groupEntries(spec).map(([childName, child]) => ({ name: child.mode === "inlineSpread" ? spec.mode === "inlineSpread" ? child.value ?? childName : `...${child.value ?? childName}` : child.value ? `${childName}: ${child.value}` : childName })), []);
546
+ return _kubb_core.ast.factory.createFunctionParameter({
597
547
  name: spec.value ?? name,
598
548
  rest: spec.mode === "inlineSpread"
599
549
  });
@@ -606,23 +556,159 @@ function createFunctionParams(params) {
606
556
  const entries = Object.entries(params).filter(([, spec]) => spec != null);
607
557
  return {
608
558
  toConstructor() {
609
- return declarationPrinter$4.print(_kubb_core.ast.createFunctionParameters({ params: entries.map(([name, spec]) => createDeclarationParam(name, spec)) })) ?? "";
559
+ return declarationPrinter$4.print(_kubb_core.ast.factory.createFunctionParameters({ params: entries.map(([name, spec]) => createDeclarationParam(name, spec)) })) ?? "";
610
560
  },
611
561
  toCall() {
612
- return callPrinter.print(_kubb_core.ast.createFunctionParameters({ params: entries.map(([name, spec]) => createCallParam(name, spec)) })) ?? "";
562
+ return callPrinter.print(_kubb_core.ast.factory.createFunctionParameters({ params: entries.map(([name, spec]) => createCallParam(name, spec)) })) ?? "";
613
563
  }
614
564
  };
615
565
  }
616
566
  //#endregion
567
+ //#region src/utils.ts
568
+ /**
569
+ * Returns `true` when any direction of the parser uses Zod (used for dependency checks).
570
+ */
571
+ function isParserEnabled(parser) {
572
+ if (!parser) return false;
573
+ if (parser === "zod") return true;
574
+ return !!(parser.request || parser.response);
575
+ }
576
+ /**
577
+ * Returns `'zod'` when request body parsing is enabled, `null` otherwise.
578
+ * The string shorthand `'zod'` also enables request body parsing (existing behavior).
579
+ */
580
+ function resolveRequestParser(parser) {
581
+ if (!parser) return null;
582
+ if (parser === "zod") return "zod";
583
+ return parser.request ?? null;
584
+ }
585
+ /**
586
+ * Returns `'zod'` when query-parameters parsing is enabled, `null` otherwise.
587
+ * Only the object form `{ request: 'zod' }` enables query-params parsing.
588
+ * The string shorthand `'zod'` does not, preserving its existing behavior.
589
+ */
590
+ function resolveQueryParamsParser(parser) {
591
+ if (!parser || parser === "zod") return null;
592
+ return parser.request ?? null;
593
+ }
594
+ /**
595
+ * Returns `'zod'` when response-direction parsing is enabled, `null` otherwise.
596
+ * `parser: 'zod'` (string shorthand) maps to response parsing and returns `'zod'`.
597
+ */
598
+ function resolveResponseParser(parser) {
599
+ if (!parser) return null;
600
+ if (parser === "zod") return "zod";
601
+ return parser.response ?? null;
602
+ }
603
+ /**
604
+ * Builds HTTP headers array for a client request.
605
+ * Includes Content-Type (if not default) and spreads header parameters if present.
606
+ */
607
+ function buildHeaders(contentType, hasHeaderParams) {
608
+ return [contentType !== "application/json" && contentType !== "multipart/form-data" ? `'Content-Type': '${contentType}'` : null, hasHeaderParams ? "...headers" : null].filter(Boolean);
609
+ }
610
+ /**
611
+ * Returns the generic type arguments — response, error, and request body — for a generated
612
+ * client call.
613
+ *
614
+ * When `dataReturnType` is `'full'`, the response generic widens to a union of all documented
615
+ * status types. When `parser` is `'zod'` and a request body schema exists, the request type
616
+ * uses `z.output<typeof schema>` to reflect post-transform types (e.g. date coercion).
617
+ */
618
+ function buildGenerics(node, tsResolver, options = {}) {
619
+ const allStatusNames = node.responses.map((r) => tsResolver.resolveResponseStatusName(node, r.statusCode));
620
+ const successNames = resolveSuccessNames(node, tsResolver);
621
+ const responseName = options.dataReturnType === "full" ? allStatusNames.length > 0 ? allStatusNames.join(" | ") : tsResolver.resolveResponseName(node) : successNames.length > 0 ? successNames.join(" | ") : tsResolver.resolveResponseName(node);
622
+ const requestName = node.requestBody?.content?.[0]?.schema ? tsResolver.resolveDataName(node) : null;
623
+ const errorNames = node.responses.filter((r) => Number.parseInt(r.statusCode, 10) >= 400).map((r) => tsResolver.resolveResponseStatusName(node, r.statusCode));
624
+ const TError = `ResponseErrorConfig<${errorNames.length > 0 ? errorNames.join(" | ") : "Error"}>`;
625
+ const zodRequestName = options.parser === "zod" && options.zodResolver && node.requestBody?.content?.[0]?.schema ? options.zodResolver.resolveDataName?.(node) ?? null : null;
626
+ return [
627
+ responseName,
628
+ TError,
629
+ zodRequestName ? `z.output<typeof ${zodRequestName}>` : requestName || "unknown"
630
+ ].filter(Boolean);
631
+ }
632
+ /**
633
+ * Builds the parameters object for a class-based client method.
634
+ * Includes URL, method, base URL, headers, and request/response data.
635
+ */
636
+ function buildClassClientParams({ node, url, baseURL, tsResolver, isFormData, isMultipleContentTypes, hasFormData, headers, zodQueryParamsName }) {
637
+ const { query: queryParams } = getOperationParameters(node);
638
+ const queryParamsName = queryParams.length > 0 ? tsResolver.resolveQueryParamsName(node, queryParams[0]) : null;
639
+ const requestName = node.requestBody?.content?.[0]?.schema ? tsResolver.resolveDataName(node) : null;
640
+ const responseType = getResponseType(node);
641
+ return createFunctionParams({ config: {
642
+ mode: "object",
643
+ children: {
644
+ requestConfig: { mode: "inlineSpread" },
645
+ method: { value: JSON.stringify(_kubb_core.ast.isHttpOperationNode(node) ? node.method.toUpperCase() : "") },
646
+ url: { value: url },
647
+ baseURL: baseURL ? { value: JSON.stringify(baseURL) } : null,
648
+ params: queryParamsName ? zodQueryParamsName ? { value: "requestParams" } : {} : null,
649
+ data: requestName ? { value: isMultipleContentTypes && hasFormData ? "contentType === 'multipart/form-data' ? formData as FormData : requestData" : isFormData ? "formData as FormData" : "requestData" } : null,
650
+ contentType: isMultipleContentTypes ? {} : null,
651
+ responseType: responseType ? { value: JSON.stringify(responseType) } : null,
652
+ headers: headers.length ? { value: `{ ${headers.join(", ")}, ...requestConfig.headers }` } : null
653
+ }
654
+ } });
655
+ }
656
+ /**
657
+ * Builds the request data parsing line for client methods.
658
+ * Applies Zod validation when `parser.request === 'zod'`, otherwise assigns data directly.
659
+ */
660
+ function buildRequestDataLine({ parser, node, zodResolver }) {
661
+ const requestParser = resolveRequestParser(parser);
662
+ const zodRequestName = zodResolver && requestParser === "zod" && node.requestBody?.content?.[0]?.schema ? zodResolver.resolveDataName?.(node) : null;
663
+ if (requestParser === "zod" && zodRequestName) return `const requestData = ${zodRequestName}.parse(data)`;
664
+ if (node.requestBody?.content?.[0]?.schema) return "const requestData = data";
665
+ return "";
666
+ }
667
+ /**
668
+ * Builds the query parameters parsing line for client methods.
669
+ * Returns an empty string when no query params exist or query-params parsing is not enabled.
670
+ * Only the object form `parser: { request: 'zod' }` triggers this. `parser: 'zod'` does not.
671
+ */
672
+ function buildQueryParamsLine({ parser, node, zodResolver }) {
673
+ if (resolveQueryParamsParser(parser) !== "zod" || !zodResolver) return "";
674
+ const { query: queryParams } = getOperationParameters(node);
675
+ if (queryParams.length === 0) return "";
676
+ const zodQueryParamsName = zodResolver.resolveQueryParamsName?.(node, queryParams[0]);
677
+ if (!zodQueryParamsName) return "";
678
+ return `const requestParams = ${zodQueryParamsName}.parse(params)`;
679
+ }
680
+ /**
681
+ * Builds the form data conversion line for file upload requests.
682
+ * Returns empty string if not applicable.
683
+ */
684
+ function buildFormDataLine(isFormData, hasRequest) {
685
+ return isFormData && hasRequest ? "const formData = buildFormData(requestData)" : "";
686
+ }
687
+ /**
688
+ * Builds the return statement for a client method.
689
+ * When `dataReturnType` is `'full'`, casts the response to the status-discriminated union type.
690
+ * When `parser.response` is `'zod'`, pipes the response body through the Zod schema before returning.
691
+ */
692
+ function buildReturnStatement({ dataReturnType, parser, node, zodResolver, tsResolver }) {
693
+ const responseParser = resolveResponseParser(parser);
694
+ const zodResponseName = zodResolver && responseParser === "zod" ? zodResolver.resolveResponseName?.(node) : null;
695
+ if (dataReturnType === "full" && tsResolver) {
696
+ const unionType = buildStatusUnionType(node, tsResolver);
697
+ if (responseParser === "zod" && zodResponseName) return `return {...res, data: ${zodResponseName}.parse(res.data)} as ${unionType}`;
698
+ return `return res as ${unionType}`;
699
+ }
700
+ if (dataReturnType === "data" && responseParser === "zod" && zodResponseName) return `return ${zodResponseName}.parse(res.data)`;
701
+ return "return res.data";
702
+ }
703
+ //#endregion
617
704
  //#region src/components/Url.tsx
618
705
  const declarationPrinter$3 = (0, _kubb_plugin_ts.functionPrinter)({ mode: "declaration" });
619
706
  function buildUrlParamsNode({ paramsType, paramsCasing, pathParamsType, node, tsResolver }) {
620
- const urlNode = {
707
+ return (0, _kubb_ast_utils.createOperationParams)({
621
708
  ...node,
622
709
  parameters: node.parameters.filter((p) => p.in === "path"),
623
710
  requestBody: void 0
624
- };
625
- return _kubb_core.ast.createOperationParams(urlNode, {
711
+ }, {
626
712
  paramsType: paramsType === "object" ? "object" : "inline",
627
713
  pathParamsType: paramsType === "object" ? "object" : pathParamsType === "object" ? "object" : "inline",
628
714
  paramsCasing,
@@ -631,7 +717,6 @@ function buildUrlParamsNode({ paramsType, paramsCasing, pathParamsType, node, ts
631
717
  }
632
718
  function Url({ name, isExportable = true, isIndexable = true, baseURL, paramsType, paramsCasing, pathParamsType, node, tsResolver }) {
633
719
  if (!_kubb_core.ast.isHttpOperationNode(node)) return null;
634
- const path = new URLPath(node.path);
635
720
  const paramsNode = buildUrlParamsNode({
636
721
  paramsType,
637
722
  paramsCasing,
@@ -656,7 +741,7 @@ function Url({ name, isExportable = true, isIndexable = true, baseURL, paramsTyp
656
741
  pathParamsMapping && Object.keys(pathParamsMapping).length > 0 && /* @__PURE__ */ (0, _kubb_renderer_jsx_jsx_runtime.jsx)("br", {}),
657
742
  /* @__PURE__ */ (0, _kubb_renderer_jsx_jsx_runtime.jsx)(_kubb_renderer_jsx.Const, {
658
743
  name: "res",
659
- children: `{ method: '${node.method.toUpperCase()}', url: ${path.toTemplateString({ prefix: baseURL })} as const }`
744
+ children: `{ method: '${node.method.toUpperCase()}', url: ${Url$1.toTemplateString(node.path, { prefix: baseURL })} as const }`
660
745
  }),
661
746
  /* @__PURE__ */ (0, _kubb_renderer_jsx_jsx_runtime.jsx)("br", {}),
662
747
  "return res"
@@ -668,24 +753,20 @@ function Url({ name, isExportable = true, isIndexable = true, baseURL, paramsTyp
668
753
  //#region src/components/Client.tsx
669
754
  const declarationPrinter$2 = (0, _kubb_plugin_ts.functionPrinter)({ mode: "declaration" });
670
755
  function buildClientParamsNode({ paramsType, paramsCasing, pathParamsType, node, tsResolver, isConfigurable }) {
671
- return _kubb_core.ast.createOperationParams(node, {
756
+ return (0, _kubb_ast_utils.createOperationParams)(node, {
672
757
  paramsType,
673
758
  pathParamsType: paramsType === "object" ? "object" : pathParamsType === "object" ? "object" : "inline",
674
759
  paramsCasing,
675
760
  resolver: tsResolver,
676
- extraParams: [...isConfigurable ? [_kubb_core.ast.createFunctionParameter({
761
+ extraParams: [...isConfigurable ? [_kubb_core.ast.factory.createFunctionParameter({
677
762
  name: "config",
678
- type: _kubb_core.ast.createParamsType({
679
- variant: "reference",
680
- name: buildRequestConfigType(node, tsResolver)
681
- }),
763
+ type: buildRequestConfigType(node, tsResolver),
682
764
  default: "{}"
683
765
  })] : []]
684
766
  });
685
767
  }
686
768
  function Client({ name, isExportable = true, isIndexable = true, returnType, baseURL, dataReturnType, parser, paramsType, paramsCasing, pathParamsType, node, tsResolver, zodResolver, urlName, children, isConfigurable = true }) {
687
769
  if (!_kubb_core.ast.isHttpOperationNode(node)) return null;
688
- const path = new URLPath(node.path);
689
770
  const { defaultContentType: contentType, isMultipleContentTypes, hasFormData } = getContentTypeInfo(node);
690
771
  const isFormData = !isMultipleContentTypes && contentType === "multipart/form-data";
691
772
  const responseType = getResponseType(node);
@@ -699,16 +780,22 @@ function Client({ name, isExportable = true, isIndexable = true, returnType, bas
699
780
  const responseName = successNames.length > 0 ? successNames.join(" | ") : tsResolver.resolveResponseName(node);
700
781
  const queryParamsName = originalQueryParams.length > 0 ? tsResolver.resolveQueryParamsName(node, originalQueryParams[0]) : null;
701
782
  const headerParamsName = originalHeaderParams.length > 0 ? tsResolver.resolveHeaderParamsName(node, originalHeaderParams[0]) : null;
702
- const zodResponseName = zodResolver && parser === "zod" ? zodResolver.resolveResponseName?.(node) : null;
703
- const zodRequestName = zodResolver && parser === "zod" && node.requestBody?.content?.[0]?.schema ? zodResolver.resolveDataName?.(node) : null;
783
+ const requestParser = resolveRequestParser(parser);
784
+ const responseParser = resolveResponseParser(parser);
785
+ const zodResponseName = zodResolver && responseParser === "zod" ? zodResolver.resolveResponseName?.(node) : null;
786
+ const zodRequestName = zodResolver && requestParser === "zod" && node.requestBody?.content?.[0]?.schema ? zodResolver.resolveDataName?.(node) : null;
787
+ const queryParamsParser = resolveQueryParamsParser(parser);
788
+ const zodQueryParamsName = zodResolver && queryParamsParser === "zod" && originalQueryParams.length > 0 ? zodResolver.resolveQueryParamsName?.(node, originalQueryParams[0]) : null;
704
789
  const errorNames = node.responses.filter((r) => {
705
790
  return Number.parseInt(r.statusCode, 10) >= 400;
706
791
  }).map((r) => tsResolver.resolveResponseStatusName(node, r.statusCode));
707
792
  const headers = [!isMultipleContentTypes && contentType !== "application/json" && contentType !== "multipart/form-data" ? `'Content-Type': '${contentType}'` : null, headerParamsName ? headerParamsMapping ? "...mappedHeaders" : "...headers" : null].filter(Boolean);
793
+ const TError = `ResponseErrorConfig<${errorNames.length > 0 ? errorNames.join(" | ") : "Error"}>`;
794
+ const allStatusNames = node.responses.map((r) => tsResolver.resolveResponseStatusName(node, r.statusCode));
708
795
  const generics = [
709
- responseName,
710
- `ResponseErrorConfig<${errorNames.length > 0 ? errorNames.join(" | ") : "Error"}>`,
711
- requestName || "unknown"
796
+ dataReturnType === "full" ? allStatusNames.length > 0 ? allStatusNames.join(" | ") : responseName : responseName,
797
+ TError,
798
+ parser === "zod" && zodRequestName ? `z.output<typeof ${zodRequestName}>` : requestName || "unknown"
712
799
  ].filter(Boolean);
713
800
  const paramsNode = buildClientParamsNode({
714
801
  paramsType,
@@ -730,22 +817,23 @@ function Client({ name, isExportable = true, isIndexable = true, returnType, bas
730
817
  const clientParams = createFunctionParams({ config: {
731
818
  mode: "object",
732
819
  children: {
733
- method: { value: JSON.stringify(node.method.toUpperCase()) },
734
- url: { value: urlName ? `${urlName}(${urlParamsCall}).url.toString()` : path.template },
820
+ method: { value: (0, _kubb_ast_utils.stringify)(node.method.toUpperCase()) },
821
+ url: { value: urlName ? `${urlName}(${urlParamsCall}).url.toString()` : Url$1.toTemplateString(node.path) },
735
822
  baseURL: baseURL && !urlName ? { value: `\`${baseURL}\`` } : null,
736
- params: queryParamsName ? queryParamsMapping ? { value: "mappedParams" } : {} : null,
823
+ params: queryParamsName ? zodQueryParamsName ? { value: "requestParams" } : queryParamsMapping ? { value: "mappedParams" } : {} : null,
737
824
  data: requestName ? { value: isMultipleContentTypes && hasFormData ? "contentType === 'multipart/form-data' ? formData as FormData : requestData" : isFormData ? "formData as FormData" : "requestData" } : null,
738
825
  contentType: isConfigurable && isMultipleContentTypes ? {} : null,
739
- responseType: responseType ? { value: JSON.stringify(responseType) } : null,
826
+ responseType: responseType ? { value: (0, _kubb_ast_utils.stringify)(responseType) } : null,
740
827
  requestConfig: isConfigurable ? { mode: "inlineSpread" } : null,
741
828
  headers: headers.length ? { value: isConfigurable ? `{ ${headers.join(", ")}, ...requestConfig.headers }` : `{ ${headers.join(", ")} }` } : null
742
829
  }
743
830
  } });
831
+ const statusUnionType = dataReturnType === "full" ? buildStatusUnionType(node, tsResolver) : null;
744
832
  const childrenElement = children ? children : /* @__PURE__ */ (0, _kubb_renderer_jsx_jsx_runtime.jsxs)(_kubb_renderer_jsx_jsx_runtime.Fragment, { children: [
745
- dataReturnType === "full" && parser === "zod" && zodResponseName && `return {...res, data: ${zodResponseName}.parse(res.data)}`,
746
- dataReturnType === "data" && parser === "zod" && zodResponseName && `return ${zodResponseName}.parse(res.data)`,
747
- dataReturnType === "full" && parser !== "zod" && "return res",
748
- dataReturnType === "data" && parser !== "zod" && "return res.data"
833
+ dataReturnType === "full" && responseParser === "zod" && zodResponseName && statusUnionType && `return {...res, data: ${zodResponseName}.parse(res.data)} as ${statusUnionType}`,
834
+ dataReturnType === "full" && responseParser !== "zod" && statusUnionType && `return res as ${statusUnionType}`,
835
+ dataReturnType === "data" && responseParser === "zod" && zodResponseName && `return ${zodResponseName}.parse(res.data)`,
836
+ dataReturnType === "data" && responseParser !== "zod" && "return res.data"
749
837
  ] });
750
838
  return /* @__PURE__ */ (0, _kubb_renderer_jsx_jsx_runtime.jsxs)(_kubb_renderer_jsx_jsx_runtime.Fragment, { children: [/* @__PURE__ */ (0, _kubb_renderer_jsx_jsx_runtime.jsx)("br", {}), /* @__PURE__ */ (0, _kubb_renderer_jsx_jsx_runtime.jsx)(_kubb_renderer_jsx.File.Source, {
751
839
  name,
@@ -763,23 +851,14 @@ function Client({ name, isExportable = true, isIndexable = true, returnType, bas
763
851
  }) },
764
852
  returnType,
765
853
  children: [
766
- isConfigurable ? `const { client: request = client, ${isMultipleContentTypes ? `contentType = ${JSON.stringify(contentType)}, ` : ""}...requestConfig } = config` : "",
767
- /* @__PURE__ */ (0, _kubb_renderer_jsx_jsx_runtime.jsx)("br", {}),
854
+ isConfigurable ? `const { client: request = client, ${isMultipleContentTypes ? `contentType = ${(0, _kubb_ast_utils.stringify)(contentType)}, ` : ""}...requestConfig } = config` : "",
768
855
  /* @__PURE__ */ (0, _kubb_renderer_jsx_jsx_runtime.jsx)("br", {}),
769
856
  pathParamsMapping && Object.entries(pathParamsMapping).filter(([originalName, camelCaseName]) => isValidVarName(originalName) && originalName !== camelCaseName).map(([originalName, camelCaseName]) => `const ${originalName} = ${camelCaseName}`).join("\n"),
770
- pathParamsMapping && /* @__PURE__ */ (0, _kubb_renderer_jsx_jsx_runtime.jsxs)(_kubb_renderer_jsx_jsx_runtime.Fragment, { children: [/* @__PURE__ */ (0, _kubb_renderer_jsx_jsx_runtime.jsx)("br", {}), /* @__PURE__ */ (0, _kubb_renderer_jsx_jsx_runtime.jsx)("br", {})] }),
771
- queryParamsMapping && queryParamsName && /* @__PURE__ */ (0, _kubb_renderer_jsx_jsx_runtime.jsxs)(_kubb_renderer_jsx_jsx_runtime.Fragment, { children: [
772
- `const mappedParams = params ? { ${Object.entries(queryParamsMapping).map(([originalName, camelCaseName]) => `"${originalName}": params.${camelCaseName}`).join(", ")} } : undefined`,
773
- /* @__PURE__ */ (0, _kubb_renderer_jsx_jsx_runtime.jsx)("br", {}),
774
- /* @__PURE__ */ (0, _kubb_renderer_jsx_jsx_runtime.jsx)("br", {})
775
- ] }),
776
- headerParamsMapping && headerParamsName && /* @__PURE__ */ (0, _kubb_renderer_jsx_jsx_runtime.jsxs)(_kubb_renderer_jsx_jsx_runtime.Fragment, { children: [
777
- `const mappedHeaders = headers ? { ${Object.entries(headerParamsMapping).map(([originalName, camelCaseName]) => `"${originalName}": headers.${camelCaseName}`).join(", ")} } : undefined`,
778
- /* @__PURE__ */ (0, _kubb_renderer_jsx_jsx_runtime.jsx)("br", {}),
779
- /* @__PURE__ */ (0, _kubb_renderer_jsx_jsx_runtime.jsx)("br", {})
780
- ] }),
781
- parser === "zod" && zodRequestName ? `const requestData = ${zodRequestName}.parse(data)` : requestName && "const requestData = data",
782
- /* @__PURE__ */ (0, _kubb_renderer_jsx_jsx_runtime.jsx)("br", {}),
857
+ pathParamsMapping && /* @__PURE__ */ (0, _kubb_renderer_jsx_jsx_runtime.jsx)("br", {}),
858
+ queryParamsMapping && queryParamsName && /* @__PURE__ */ (0, _kubb_renderer_jsx_jsx_runtime.jsxs)(_kubb_renderer_jsx_jsx_runtime.Fragment, { children: [`const mappedParams = params ? { ${Object.entries(queryParamsMapping).map(([originalName, camelCaseName]) => `"${originalName}": params.${camelCaseName}`).join(", ")} } : undefined`, /* @__PURE__ */ (0, _kubb_renderer_jsx_jsx_runtime.jsx)("br", {})] }),
859
+ headerParamsMapping && headerParamsName && /* @__PURE__ */ (0, _kubb_renderer_jsx_jsx_runtime.jsxs)(_kubb_renderer_jsx_jsx_runtime.Fragment, { children: [`const mappedHeaders = headers ? { ${Object.entries(headerParamsMapping).map(([originalName, camelCaseName]) => `"${originalName}": headers.${camelCaseName}`).join(", ")} } : undefined`, /* @__PURE__ */ (0, _kubb_renderer_jsx_jsx_runtime.jsx)("br", {})] }),
860
+ requestParser === "zod" && zodRequestName ? `const requestData = ${zodRequestName}.parse(data)` : requestName && "const requestData = data",
861
+ zodQueryParamsName && `const requestParams = ${zodQueryParamsName}.parse(params)`,
783
862
  (isFormData || isMultipleContentTypes && hasFormData) && requestName && "const formData = buildFormData(requestData)",
784
863
  /* @__PURE__ */ (0, _kubb_renderer_jsx_jsx_runtime.jsx)("br", {}),
785
864
  isConfigurable ? `const res = await request<${generics.join(", ")}>(${clientParams.toCall()})` : `const res = await client<${generics.join(", ")}>(${clientParams.toCall()})`,
@@ -790,93 +869,20 @@ function Client({ name, isExportable = true, isIndexable = true, returnType, bas
790
869
  })] });
791
870
  }
792
871
  //#endregion
793
- //#region src/utils.ts
794
- /**
795
- * Builds HTTP headers array for a client request.
796
- * Includes Content-Type (if not default) and spreads header parameters if present.
797
- */
798
- function buildHeaders(contentType, hasHeaderParams) {
799
- return [contentType !== "application/json" && contentType !== "multipart/form-data" ? `'Content-Type': '${contentType}'` : null, hasHeaderParams ? "...headers" : null].filter(Boolean);
800
- }
801
- /**
802
- * Builds TypeScript generic parameters for a client method.
803
- * Includes response type, error type, and optional request type.
804
- */
805
- function buildGenerics(node, tsResolver) {
806
- const successNames = resolveSuccessNames(node, tsResolver);
807
- const responseName = successNames.length > 0 ? successNames.join(" | ") : tsResolver.resolveResponseName(node);
808
- const requestName = node.requestBody?.content?.[0]?.schema ? tsResolver.resolveDataName(node) : null;
809
- const errorNames = node.responses.filter((r) => Number.parseInt(r.statusCode, 10) >= 400).map((r) => tsResolver.resolveResponseStatusName(node, r.statusCode));
810
- return [
811
- responseName,
812
- `ResponseErrorConfig<${errorNames.length > 0 ? errorNames.join(" | ") : "Error"}>`,
813
- requestName || "unknown"
814
- ].filter(Boolean);
815
- }
816
- /**
817
- * Builds the parameters object for a class-based client method.
818
- * Includes URL, method, base URL, headers, and request/response data.
819
- */
820
- function buildClassClientParams({ node, path, baseURL, tsResolver, isFormData, isMultipleContentTypes, hasFormData, headers }) {
821
- const { query: queryParams } = getOperationParameters(node);
822
- const queryParamsName = queryParams.length > 0 ? tsResolver.resolveQueryParamsName(node, queryParams[0]) : null;
823
- const requestName = node.requestBody?.content?.[0]?.schema ? tsResolver.resolveDataName(node) : null;
824
- const responseType = getResponseType(node);
825
- return createFunctionParams({ config: {
826
- mode: "object",
827
- children: {
828
- requestConfig: { mode: "inlineSpread" },
829
- method: { value: JSON.stringify(_kubb_core.ast.isHttpOperationNode(node) ? node.method.toUpperCase() : "") },
830
- url: { value: path.template },
831
- baseURL: baseURL ? { value: JSON.stringify(baseURL) } : null,
832
- params: queryParamsName ? {} : null,
833
- data: requestName ? { value: isMultipleContentTypes && hasFormData ? "contentType === 'multipart/form-data' ? formData as FormData : requestData" : isFormData ? "formData as FormData" : "requestData" } : null,
834
- contentType: isMultipleContentTypes ? {} : null,
835
- responseType: responseType ? { value: JSON.stringify(responseType) } : null,
836
- headers: headers.length ? { value: `{ ${headers.join(", ")}, ...requestConfig.headers }` } : null
837
- }
838
- } });
839
- }
840
- /**
841
- * Builds the request data parsing line for client methods.
842
- * Applies Zod validation if configured, otherwise uses data directly.
843
- */
844
- function buildRequestDataLine({ parser, node, zodResolver }) {
845
- const zodRequestName = zodResolver && parser === "zod" && node.requestBody?.content?.[0]?.schema ? zodResolver.resolveDataName?.(node) : null;
846
- if (parser === "zod" && zodRequestName) return `const requestData = ${zodRequestName}.parse(data)`;
847
- if (node.requestBody?.content?.[0]?.schema) return "const requestData = data";
848
- return "";
849
- }
850
- /**
851
- * Builds the form data conversion line for file upload requests.
852
- * Returns empty string if not applicable.
853
- */
854
- function buildFormDataLine(isFormData, hasRequest) {
855
- return isFormData && hasRequest ? "const formData = buildFormData(requestData)" : "";
856
- }
857
- /**
858
- * Builds the return statement for a client method.
859
- * Applies Zod validation to response data if configured, otherwise returns raw response.
860
- */
861
- function buildReturnStatement({ dataReturnType, parser, node, zodResolver }) {
862
- const zodResponseName = zodResolver && parser === "zod" ? zodResolver.resolveResponseName?.(node) : null;
863
- if (dataReturnType === "full" && parser === "zod" && zodResponseName) return `return {...res, data: ${zodResponseName}.parse(res.data)}`;
864
- if (dataReturnType === "data" && parser === "zod" && zodResponseName) return `return ${zodResponseName}.parse(res.data)`;
865
- if (dataReturnType === "full" && parser !== "zod") return "return res";
866
- return "return res.data";
867
- }
868
- //#endregion
869
872
  //#region src/components/ClassClient.tsx
870
873
  const declarationPrinter$1 = (0, _kubb_plugin_ts.functionPrinter)({ mode: "declaration" });
871
874
  function generateMethod$1({ node, name, tsResolver, zodResolver, baseURL, dataReturnType, parser, paramsType, paramsCasing, pathParamsType }) {
872
875
  if (!_kubb_core.ast.isHttpOperationNode(node)) return "";
873
- const path = new URLPath(node.path, { casing: paramsCasing });
874
876
  const { defaultContentType: contentType, isMultipleContentTypes, hasFormData } = getContentTypeInfo(node);
875
877
  const isFormData = !isMultipleContentTypes && contentType === "multipart/form-data";
876
878
  const { header: headerParams } = getOperationParameters(node);
877
879
  const headerParamsName = headerParams.length > 0 ? tsResolver.resolveHeaderParamsName(node, headerParams[0]) : null;
878
880
  const headers = isMultipleContentTypes ? headerParamsName ? ["...headers"] : [] : buildHeaders(contentType, !!headerParamsName);
879
- const generics = buildGenerics(node, tsResolver);
881
+ const generics = buildGenerics(node, tsResolver, {
882
+ dataReturnType,
883
+ zodResolver,
884
+ parser
885
+ });
880
886
  const paramsNode = buildClientParamsNode({
881
887
  paramsType,
882
888
  paramsCasing,
@@ -886,17 +892,20 @@ function generateMethod$1({ node, name, tsResolver, zodResolver, baseURL, dataRe
886
892
  isConfigurable: true
887
893
  });
888
894
  const paramsSignature = declarationPrinter$1.print(paramsNode) ?? "";
895
+ const { query: queryParams } = getOperationParameters(node);
896
+ const zodQueryParamsName = zodResolver && resolveQueryParamsParser(parser) === "zod" && queryParams.length > 0 ? zodResolver.resolveQueryParamsName?.(node, queryParams[0]) : null;
889
897
  const clientParams = buildClassClientParams({
890
898
  node,
891
- path,
899
+ url: Url$1.toTemplateString(node.path, { casing: paramsCasing }),
892
900
  baseURL,
893
901
  tsResolver,
894
902
  isFormData,
895
903
  isMultipleContentTypes,
896
904
  hasFormData,
897
- headers
905
+ headers,
906
+ zodQueryParamsName
898
907
  });
899
- const jsdoc = buildJSDoc(buildOperationComments(node, {
908
+ const jsdoc = (0, _kubb_ast_utils.buildJSDoc)(buildOperationComments(node, {
900
909
  link: "urlPath",
901
910
  linkPosition: "beforeDeprecated",
902
911
  splitLines: true
@@ -906,17 +915,24 @@ function generateMethod$1({ node, name, tsResolver, zodResolver, baseURL, dataRe
906
915
  node,
907
916
  zodResolver
908
917
  });
918
+ const queryParamsLine = buildQueryParamsLine({
919
+ parser,
920
+ node,
921
+ zodResolver
922
+ });
909
923
  const formDataLine = buildFormDataLine(isFormData || isMultipleContentTypes && hasFormData, !!node.requestBody?.content?.[0]?.schema);
910
924
  const returnStatement = buildReturnStatement({
911
925
  dataReturnType,
912
926
  parser,
913
927
  node,
914
- zodResolver
928
+ zodResolver,
929
+ tsResolver
915
930
  });
916
931
  return `${jsdoc}async ${name}(${paramsSignature}) {\n${[
917
- `const { client: request = client, ${isMultipleContentTypes ? `contentType = ${JSON.stringify(contentType)}, ` : ""}...requestConfig } = mergeConfig(this.#config, config)`,
932
+ `const { client: request = client, ${isMultipleContentTypes ? `contentType = ${(0, _kubb_ast_utils.stringify)(contentType)}, ` : ""}...requestConfig } = mergeConfig(this.#config, config)`,
918
933
  "",
919
934
  requestDataLine,
935
+ queryParamsLine,
920
936
  formDataLine,
921
937
  `const res = await request<${generics.join(", ")}>(${clientParams.toCall()})`,
922
938
  returnStatement
@@ -974,8 +990,13 @@ function resolveTypeImportNames$1(node, tsResolver) {
974
990
  return resolveOperationTypeNames(node, tsResolver, { order: "body-response-first" });
975
991
  }
976
992
  require_chunk.__name(resolveTypeImportNames$1, "resolveTypeImportNames");
977
- function resolveZodImportNames$1(node, zodResolver) {
978
- return [zodResolver.resolveResponseName?.(node), node.requestBody?.content?.[0]?.schema ? zodResolver.resolveDataName?.(node) : null].filter((n) => Boolean(n));
993
+ function resolveZodImportNames$1(node, zodResolver, parser) {
994
+ const { query: queryParams } = getOperationParameters(node);
995
+ return [
996
+ resolveResponseParser(parser) === "zod" ? zodResolver.resolveResponseName?.(node) : null,
997
+ resolveRequestParser(parser) === "zod" && node.requestBody?.content?.[0]?.schema ? zodResolver.resolveDataName?.(node) : null,
998
+ resolveQueryParamsParser(parser) === "zod" && queryParams.length > 0 ? zodResolver.resolveQueryParamsName?.(node, queryParams[0]) : null
999
+ ].filter((n) => Boolean(n));
979
1000
  }
980
1001
  require_chunk.__name(resolveZodImportNames$1, "resolveZodImportNames");
981
1002
  /**
@@ -985,7 +1006,7 @@ require_chunk.__name(resolveZodImportNames$1, "resolveZodImportNames");
985
1006
  */
986
1007
  const classClientGenerator = (0, _kubb_core.defineGenerator)({
987
1008
  name: "classClient",
988
- renderer: _kubb_renderer_jsx.jsxRendererSync,
1009
+ renderer: _kubb_renderer_jsx.jsxRenderer,
989
1010
  operations(nodes, ctx) {
990
1011
  const { config, driver, resolver, root } = ctx;
991
1012
  const { output, group, dataReturnType, paramsCasing, paramsType, pathParamsType, parser, importPath, sdk } = ctx.options;
@@ -994,7 +1015,7 @@ const classClientGenerator = (0, _kubb_core.defineGenerator)({
994
1015
  if (!pluginTs) return null;
995
1016
  const tsResolver = driver.getResolver(_kubb_plugin_ts.pluginTsName);
996
1017
  const tsPluginOptions = pluginTs.options;
997
- const pluginZod = parser === "zod" ? driver.getPlugin(_kubb_plugin_zod.pluginZodName) : null;
1018
+ const pluginZod = isParserEnabled(parser) ? driver.getPlugin(_kubb_plugin_zod.pluginZodName) : null;
998
1019
  const zodResolver = pluginZod ? driver.getResolver(_kubb_plugin_zod.pluginZodName) : null;
999
1020
  function buildOperationData(node) {
1000
1021
  const typeFile = tsResolver.resolveFile(operationFileEntry(node, node.operationId), {
@@ -1019,7 +1040,7 @@ const classClientGenerator = (0, _kubb_core.defineGenerator)({
1019
1040
  const controllers = nodes.reduce((acc, operationNode) => {
1020
1041
  if (!_kubb_core.ast.isHttpOperationNode(operationNode)) return acc;
1021
1042
  const tag = operationNode.tags[0];
1022
- const groupName = tag ? group?.name?.({ group: camelCase(tag) }) ?? resolver.resolveGroupName(tag) : resolver.resolveGroupName("Client");
1043
+ const groupName = tag ? group?.name?.({ group: camelCase(tag) }) ?? resolver.resolveGroupName(tag) : resolver.resolveClassName("Client");
1023
1044
  if (!tag && !group) {
1024
1045
  const name = resolver.resolveClassName("ApiClient");
1025
1046
  const file = resolver.resolveFile({
@@ -1086,7 +1107,7 @@ const classClientGenerator = (0, _kubb_core.defineGenerator)({
1086
1107
  const zodFilesByPath = /* @__PURE__ */ new Map();
1087
1108
  ops.forEach((op) => {
1088
1109
  if (!op.zodFile || !zodResolver) return;
1089
- const names = resolveZodImportNames$1(op.node, zodResolver);
1110
+ const names = resolveZodImportNames$1(op.node, zodResolver, parser);
1090
1111
  if (!zodImportsByFile.has(op.zodFile.path)) zodImportsByFile.set(op.zodFile.path, /* @__PURE__ */ new Set());
1091
1112
  const imports = zodImportsByFile.get(op.zodFile.path);
1092
1113
  names.forEach((n) => {
@@ -1101,7 +1122,7 @@ const classClientGenerator = (0, _kubb_core.defineGenerator)({
1101
1122
  }
1102
1123
  const files = controllers.map(({ name, file, operations: ops }) => {
1103
1124
  const { typeImportsByFile, typeFilesByPath } = collectTypeImports(ops);
1104
- const { zodImportsByFile, zodFilesByPath } = parser === "zod" ? collectZodImports(ops) : {
1125
+ const { zodImportsByFile, zodFilesByPath } = isParserEnabled(parser) ? collectZodImports(ops) : {
1105
1126
  zodImportsByFile: /* @__PURE__ */ new Map(),
1106
1127
  zodFilesByPath: /* @__PURE__ */ new Map()
1107
1128
  };
@@ -1172,6 +1193,11 @@ const classClientGenerator = (0, _kubb_core.defineGenerator)({
1172
1193
  root: file.path,
1173
1194
  path: node_path.default.resolve(root, ".kubb/config.ts")
1174
1195
  }),
1196
+ parser === "zod" && zodResolver && ops.some((op) => op.node.requestBody?.content?.[0]?.schema != null) && /* @__PURE__ */ (0, _kubb_renderer_jsx_jsx_runtime.jsx)(_kubb_renderer_jsx.File.Import, {
1197
+ name: ["z"],
1198
+ path: "zod",
1199
+ isTypeOnly: true
1200
+ }),
1175
1201
  Array.from(typeImportsByFile.entries()).map(([filePath, importSet]) => {
1176
1202
  const typeFile = typeFilesByPath.get(filePath);
1177
1203
  if (!typeFile) return null;
@@ -1184,7 +1210,7 @@ const classClientGenerator = (0, _kubb_core.defineGenerator)({
1184
1210
  isTypeOnly: true
1185
1211
  }, filePath);
1186
1212
  }),
1187
- parser === "zod" && Array.from(zodImportsByFile.entries()).map(([filePath, importSet]) => {
1213
+ isParserEnabled(parser) && Array.from(zodImportsByFile.entries()).map(([filePath, importSet]) => {
1188
1214
  const zodFile = zodFilesByPath.get(filePath);
1189
1215
  if (!zodFile) return null;
1190
1216
  const importNames = Array.from(importSet).filter(Boolean);
@@ -1275,7 +1301,7 @@ const classClientGenerator = (0, _kubb_core.defineGenerator)({
1275
1301
  */
1276
1302
  const clientGenerator = (0, _kubb_core.defineGenerator)({
1277
1303
  name: "client",
1278
- renderer: _kubb_renderer_jsx.jsxRendererSync,
1304
+ renderer: _kubb_renderer_jsx.jsxRenderer,
1279
1305
  operation(node, ctx) {
1280
1306
  if (!_kubb_core.ast.isHttpOperationNode(node)) return null;
1281
1307
  const { config, driver, resolver, root } = ctx;
@@ -1284,10 +1310,16 @@ const clientGenerator = (0, _kubb_core.defineGenerator)({
1284
1310
  const pluginTs = driver.getPlugin(_kubb_plugin_ts.pluginTsName);
1285
1311
  if (!pluginTs) return null;
1286
1312
  const tsResolver = driver.getResolver(_kubb_plugin_ts.pluginTsName);
1287
- const pluginZod = parser === "zod" ? driver.getPlugin(_kubb_plugin_zod.pluginZodName) : null;
1313
+ const pluginZod = isParserEnabled(parser) ? driver.getPlugin(_kubb_plugin_zod.pluginZodName) : null;
1288
1314
  const zodResolver = pluginZod ? driver.getResolver(_kubb_plugin_zod.pluginZodName) : null;
1289
1315
  const importedTypeNames = resolveOperationTypeNames(node, tsResolver, { paramsCasing });
1290
- const importedZodNames = zodResolver && parser === "zod" ? [zodResolver.resolveResponseName?.(node), node.requestBody?.content?.[0]?.schema ? zodResolver.resolveDataName?.(node) : null].filter((name) => Boolean(name)) : [];
1316
+ const { query: queryParams } = getOperationParameters(node);
1317
+ const importedZodNames = zodResolver ? [
1318
+ resolveResponseParser(parser) === "zod" ? zodResolver.resolveResponseName?.(node) : null,
1319
+ resolveRequestParser(parser) === "zod" && node.requestBody?.content?.[0]?.schema ? zodResolver.resolveDataName?.(node) : null,
1320
+ resolveQueryParamsParser(parser) === "zod" && queryParams.length > 0 ? zodResolver.resolveQueryParamsName?.(node, queryParams[0]) : null
1321
+ ].filter((name) => Boolean(name)) : [];
1322
+ const zodRequestName = zodResolver && parser === "zod" && node.requestBody?.content?.[0]?.schema ? zodResolver.resolveDataName?.(node) ?? null : null;
1291
1323
  const meta = {
1292
1324
  name: resolver.resolveName(node.operationId),
1293
1325
  urlName: resolver.resolveUrlName(node),
@@ -1359,6 +1391,11 @@ const clientGenerator = (0, _kubb_core.defineGenerator)({
1359
1391
  root: meta.file.path,
1360
1392
  path: node_path.default.resolve(root, ".kubb/config.ts")
1361
1393
  }),
1394
+ zodRequestName && /* @__PURE__ */ (0, _kubb_renderer_jsx_jsx_runtime.jsx)(_kubb_renderer_jsx.File.Import, {
1395
+ name: ["z"],
1396
+ path: "zod",
1397
+ isTypeOnly: true
1398
+ }),
1362
1399
  meta.fileZod && importedZodNames.length > 0 && /* @__PURE__ */ (0, _kubb_renderer_jsx_jsx_runtime.jsx)(_kubb_renderer_jsx.File.Import, {
1363
1400
  name: importedZodNames,
1364
1401
  root: meta.file.path,
@@ -1408,7 +1445,7 @@ const clientGenerator = (0, _kubb_core.defineGenerator)({
1408
1445
  */
1409
1446
  const groupedClientGenerator = (0, _kubb_core.defineGenerator)({
1410
1447
  name: "groupedClient",
1411
- renderer: _kubb_renderer_jsx.jsxRendererSync,
1448
+ renderer: _kubb_renderer_jsx.jsxRenderer,
1412
1449
  operations(nodes, ctx) {
1413
1450
  const { config, resolver, root } = ctx;
1414
1451
  const { output, group } = ctx.options;
@@ -1491,29 +1528,7 @@ const groupedClientGenerator = (0, _kubb_core.defineGenerator)({
1491
1528
  }
1492
1529
  });
1493
1530
  //#endregion
1494
- //#region src/components/Operations.tsx
1495
- function Operations({ name, nodes }) {
1496
- const operationsObject = {};
1497
- nodes.forEach((node) => {
1498
- if (!_kubb_core.ast.isHttpOperationNode(node)) return;
1499
- operationsObject[node.operationId] = {
1500
- path: new URLPath(node.path).URL,
1501
- method: node.method.toLowerCase()
1502
- };
1503
- });
1504
- return /* @__PURE__ */ (0, _kubb_renderer_jsx_jsx_runtime.jsx)(_kubb_renderer_jsx.File.Source, {
1505
- name,
1506
- isExportable: true,
1507
- isIndexable: true,
1508
- children: /* @__PURE__ */ (0, _kubb_renderer_jsx_jsx_runtime.jsx)(_kubb_renderer_jsx.Const, {
1509
- name,
1510
- export: true,
1511
- children: JSON.stringify(operationsObject, void 0, 2)
1512
- })
1513
- });
1514
- }
1515
- //#endregion
1516
- //#region src/generators/operationsGenerator.tsx
1531
+ //#region src/generators/operationsGenerator.ts
1517
1532
  /**
1518
1533
  * Generates an `operations.ts` file that re-exports every operation grouped
1519
1534
  * by HTTP method. Enabled when `pluginClient({ operations: true })`. Useful
@@ -1522,7 +1537,6 @@ function Operations({ name, nodes }) {
1522
1537
  */
1523
1538
  const operationsGenerator = (0, _kubb_core.defineGenerator)({
1524
1539
  name: "client",
1525
- renderer: _kubb_renderer_jsx.jsxRendererSync,
1526
1540
  operations(nodes, ctx) {
1527
1541
  const { config, resolver, root } = ctx;
1528
1542
  const { output, group } = ctx.options;
@@ -1535,7 +1549,15 @@ const operationsGenerator = (0, _kubb_core.defineGenerator)({
1535
1549
  output,
1536
1550
  group: group ?? void 0
1537
1551
  });
1538
- return /* @__PURE__ */ (0, _kubb_renderer_jsx_jsx_runtime.jsx)(_kubb_renderer_jsx.File, {
1552
+ const operationsObject = {};
1553
+ for (const node of nodes) {
1554
+ if (!_kubb_core.ast.isHttpOperationNode(node)) continue;
1555
+ operationsObject[node.operationId] = {
1556
+ path: Url$1.toPath(node.path),
1557
+ method: node.method.toLowerCase()
1558
+ };
1559
+ }
1560
+ return [_kubb_core.ast.factory.createFile({
1539
1561
  baseName: file.baseName,
1540
1562
  path: file.path,
1541
1563
  meta: file.meta,
@@ -1555,11 +1577,17 @@ const operationsGenerator = (0, _kubb_core.defineGenerator)({
1555
1577
  baseName: file.baseName
1556
1578
  }
1557
1579
  }),
1558
- children: /* @__PURE__ */ (0, _kubb_renderer_jsx_jsx_runtime.jsx)(Operations, {
1580
+ sources: [_kubb_core.ast.factory.createSource({
1559
1581
  name,
1560
- nodes: nodes.filter(_kubb_core.ast.isHttpOperationNode)
1561
- })
1562
- });
1582
+ isExportable: true,
1583
+ isIndexable: true,
1584
+ nodes: [_kubb_core.ast.factory.createConst({
1585
+ name,
1586
+ export: true,
1587
+ nodes: [_kubb_core.ast.factory.createText(JSON.stringify(operationsObject, void 0, 2))]
1588
+ })]
1589
+ })]
1590
+ })];
1563
1591
  }
1564
1592
  });
1565
1593
  //#endregion
@@ -1567,13 +1595,16 @@ const operationsGenerator = (0, _kubb_core.defineGenerator)({
1567
1595
  const declarationPrinter = (0, _kubb_plugin_ts.functionPrinter)({ mode: "declaration" });
1568
1596
  function generateMethod({ node, name, tsResolver, zodResolver, baseURL, dataReturnType, parser, paramsType, paramsCasing, pathParamsType }) {
1569
1597
  if (!_kubb_core.ast.isHttpOperationNode(node)) return "";
1570
- const path = new URLPath(node.path, { casing: paramsCasing });
1571
1598
  const { defaultContentType: contentType, isMultipleContentTypes, hasFormData } = getContentTypeInfo(node);
1572
1599
  const isFormData = !isMultipleContentTypes && contentType === "multipart/form-data";
1573
1600
  const { header: headerParams } = getOperationParameters(node);
1574
1601
  const headerParamsName = headerParams.length > 0 ? tsResolver.resolveHeaderParamsName(node, headerParams[0]) : null;
1575
1602
  const headers = isMultipleContentTypes ? headerParamsName ? ["...headers"] : [] : buildHeaders(contentType, !!headerParamsName);
1576
- const generics = buildGenerics(node, tsResolver);
1603
+ const generics = buildGenerics(node, tsResolver, {
1604
+ dataReturnType,
1605
+ zodResolver,
1606
+ parser
1607
+ });
1577
1608
  const paramsNode = buildClientParamsNode({
1578
1609
  paramsType,
1579
1610
  paramsCasing,
@@ -1583,17 +1614,20 @@ function generateMethod({ node, name, tsResolver, zodResolver, baseURL, dataRetu
1583
1614
  isConfigurable: true
1584
1615
  });
1585
1616
  const paramsSignature = declarationPrinter.print(paramsNode) ?? "";
1617
+ const { query: queryParams } = getOperationParameters(node);
1618
+ const zodQueryParamsName = zodResolver && resolveQueryParamsParser(parser) === "zod" && queryParams.length > 0 ? zodResolver.resolveQueryParamsName?.(node, queryParams[0]) : null;
1586
1619
  const clientParams = buildClassClientParams({
1587
1620
  node,
1588
- path,
1621
+ url: Url$1.toTemplateString(node.path, { casing: paramsCasing }),
1589
1622
  baseURL,
1590
1623
  tsResolver,
1591
1624
  isFormData,
1592
1625
  isMultipleContentTypes,
1593
1626
  hasFormData,
1594
- headers
1627
+ headers,
1628
+ zodQueryParamsName
1595
1629
  });
1596
- const jsdoc = buildJSDoc(buildOperationComments(node, {
1630
+ const jsdoc = (0, _kubb_ast_utils.buildJSDoc)(buildOperationComments(node, {
1597
1631
  link: "urlPath",
1598
1632
  linkPosition: "beforeDeprecated",
1599
1633
  splitLines: true
@@ -1603,17 +1637,24 @@ function generateMethod({ node, name, tsResolver, zodResolver, baseURL, dataRetu
1603
1637
  node,
1604
1638
  zodResolver
1605
1639
  });
1640
+ const queryParamsLine = buildQueryParamsLine({
1641
+ parser,
1642
+ node,
1643
+ zodResolver
1644
+ });
1606
1645
  const formDataLine = buildFormDataLine(isFormData || isMultipleContentTypes && hasFormData, !!node.requestBody?.content?.[0]?.schema);
1607
1646
  const returnStatement = buildReturnStatement({
1608
1647
  dataReturnType,
1609
1648
  parser,
1610
1649
  node,
1611
- zodResolver
1650
+ zodResolver,
1651
+ tsResolver
1612
1652
  });
1613
1653
  return `${jsdoc} static async ${name}(${paramsSignature}) {\n${[
1614
- `const { client: request = client, ${isMultipleContentTypes ? `contentType = ${JSON.stringify(contentType)}, ` : ""}...requestConfig } = mergeConfig(this.#config, config)`,
1654
+ `const { client: request = client, ${isMultipleContentTypes ? `contentType = ${(0, _kubb_ast_utils.stringify)(contentType)}, ` : ""}...requestConfig } = mergeConfig(this.#config, config)`,
1615
1655
  "",
1616
1656
  requestDataLine,
1657
+ queryParamsLine,
1617
1658
  formDataLine,
1618
1659
  `const res = await request<${generics.join(", ")}>(${clientParams.toCall()})`,
1619
1660
  returnStatement
@@ -1644,8 +1685,13 @@ function StaticClassClient({ name, isExportable = true, isIndexable = true, oper
1644
1685
  function resolveTypeImportNames(node, tsResolver) {
1645
1686
  return resolveOperationTypeNames(node, tsResolver, { order: "body-response-first" });
1646
1687
  }
1647
- function resolveZodImportNames(node, zodResolver) {
1648
- return [zodResolver.resolveResponseName?.(node), node.requestBody?.content?.[0]?.schema ? zodResolver.resolveDataName?.(node) : null].filter((n) => Boolean(n));
1688
+ function resolveZodImportNames(node, zodResolver, parser) {
1689
+ const { query: queryParams } = getOperationParameters(node);
1690
+ return [
1691
+ resolveResponseParser(parser) === "zod" ? zodResolver.resolveResponseName?.(node) : null,
1692
+ resolveRequestParser(parser) === "zod" && node.requestBody?.content?.[0]?.schema ? zodResolver.resolveDataName?.(node) : null,
1693
+ resolveQueryParamsParser(parser) === "zod" && queryParams.length > 0 ? zodResolver.resolveQueryParamsName?.(node, queryParams[0]) : null
1694
+ ].filter((n) => Boolean(n));
1649
1695
  }
1650
1696
  /**
1651
1697
  * Built-in `operations` generator for `@kubb/plugin-client` when
@@ -1655,7 +1701,7 @@ function resolveZodImportNames(node, zodResolver) {
1655
1701
  */
1656
1702
  const staticClassClientGenerator = (0, _kubb_core.defineGenerator)({
1657
1703
  name: "staticClassClient",
1658
- renderer: _kubb_renderer_jsx.jsxRendererSync,
1704
+ renderer: _kubb_renderer_jsx.jsxRenderer,
1659
1705
  operations(nodes, ctx) {
1660
1706
  const { config, driver, resolver, root } = ctx;
1661
1707
  const { output, group, dataReturnType, paramsCasing, paramsType, pathParamsType, parser, importPath } = ctx.options;
@@ -1664,7 +1710,7 @@ const staticClassClientGenerator = (0, _kubb_core.defineGenerator)({
1664
1710
  if (!pluginTs) return null;
1665
1711
  const tsResolver = driver.getResolver(_kubb_plugin_ts.pluginTsName);
1666
1712
  const tsPluginOptions = pluginTs.options;
1667
- const pluginZod = parser === "zod" ? driver.getPlugin(_kubb_plugin_zod.pluginZodName) : null;
1713
+ const pluginZod = isParserEnabled(parser) ? driver.getPlugin(_kubb_plugin_zod.pluginZodName) : null;
1668
1714
  const zodResolver = pluginZod ? driver.getResolver(_kubb_plugin_zod.pluginZodName) : null;
1669
1715
  function buildOperationData(node) {
1670
1716
  const typeFile = tsResolver.resolveFile(operationFileEntry(node, node.operationId), {
@@ -1689,7 +1735,7 @@ const staticClassClientGenerator = (0, _kubb_core.defineGenerator)({
1689
1735
  const controllers = nodes.reduce((acc, operationNode) => {
1690
1736
  if (!_kubb_core.ast.isHttpOperationNode(operationNode)) return acc;
1691
1737
  const tag = operationNode.tags[0];
1692
- const groupName = tag ? group?.name?.({ group: camelCase(tag) }) ?? resolver.resolveGroupName(tag) : resolver.resolveGroupName("Client");
1738
+ const groupName = tag ? group?.name?.({ group: camelCase(tag) }) ?? resolver.resolveGroupName(tag) : resolver.resolveClassName("Client");
1693
1739
  if (!tag && !group) {
1694
1740
  const name = resolver.resolveClassName("ApiClient");
1695
1741
  const file = resolver.resolveFile({
@@ -1754,7 +1800,7 @@ const staticClassClientGenerator = (0, _kubb_core.defineGenerator)({
1754
1800
  const zodFilesByPath = /* @__PURE__ */ new Map();
1755
1801
  ops.forEach((op) => {
1756
1802
  if (!op.zodFile || !zodResolver) return;
1757
- const names = resolveZodImportNames(op.node, zodResolver);
1803
+ const names = resolveZodImportNames(op.node, zodResolver, parser);
1758
1804
  if (!zodImportsByFile.has(op.zodFile.path)) zodImportsByFile.set(op.zodFile.path, /* @__PURE__ */ new Set());
1759
1805
  const imports = zodImportsByFile.get(op.zodFile.path);
1760
1806
  names.forEach((n) => {
@@ -1769,7 +1815,7 @@ const staticClassClientGenerator = (0, _kubb_core.defineGenerator)({
1769
1815
  }
1770
1816
  return /* @__PURE__ */ (0, _kubb_renderer_jsx_jsx_runtime.jsx)(_kubb_renderer_jsx_jsx_runtime.Fragment, { children: controllers.map(({ name, file, operations: ops }) => {
1771
1817
  const { typeImportsByFile, typeFilesByPath } = collectTypeImports(ops);
1772
- const { zodImportsByFile, zodFilesByPath } = parser === "zod" ? collectZodImports(ops) : {
1818
+ const { zodImportsByFile, zodFilesByPath } = isParserEnabled(parser) ? collectZodImports(ops) : {
1773
1819
  zodImportsByFile: /* @__PURE__ */ new Map(),
1774
1820
  zodFilesByPath: /* @__PURE__ */ new Map()
1775
1821
  };
@@ -1840,6 +1886,11 @@ const staticClassClientGenerator = (0, _kubb_core.defineGenerator)({
1840
1886
  root: file.path,
1841
1887
  path: node_path.default.resolve(root, ".kubb/config.ts")
1842
1888
  }),
1889
+ parser === "zod" && zodResolver && ops.some((op) => op.node.requestBody?.content?.[0]?.schema != null) && /* @__PURE__ */ (0, _kubb_renderer_jsx_jsx_runtime.jsx)(_kubb_renderer_jsx.File.Import, {
1890
+ name: ["z"],
1891
+ path: "zod",
1892
+ isTypeOnly: true
1893
+ }),
1843
1894
  Array.from(typeImportsByFile.entries()).map(([filePath, importSet]) => {
1844
1895
  const typeFile = typeFilesByPath.get(filePath);
1845
1896
  if (!typeFile) return null;
@@ -1852,7 +1903,7 @@ const staticClassClientGenerator = (0, _kubb_core.defineGenerator)({
1852
1903
  isTypeOnly: true
1853
1904
  }, filePath);
1854
1905
  }),
1855
- parser === "zod" && Array.from(zodImportsByFile.entries()).map(([filePath, importSet]) => {
1906
+ isParserEnabled(parser) && Array.from(zodImportsByFile.entries()).map(([filePath, importSet]) => {
1856
1907
  const zodFile = zodFilesByPath.get(filePath);
1857
1908
  if (!zodFile) return null;
1858
1909
  const importNames = Array.from(importSet).filter(Boolean);
@@ -1891,6 +1942,7 @@ const staticClassClientGenerator = (0, _kubb_core.defineGenerator)({
1891
1942
  *
1892
1943
  * resolverClient.default('list pets', 'function') // 'listPets'
1893
1944
  * resolverClient.resolveClassName('pet') // 'Pet'
1945
+ * resolverClient.resolveGroupName('pet') // 'PetClient'
1894
1946
  * resolverClient.resolveUrlName(operationNode) // 'getShowPetByIdUrl'
1895
1947
  * ```
1896
1948
  */
@@ -1898,8 +1950,8 @@ const resolverClient = (0, _kubb_core.defineResolver)(() => ({
1898
1950
  name: "default",
1899
1951
  pluginName: "plugin-client",
1900
1952
  default(name, type) {
1901
- const resolved = camelCase(name, { isFile: type === "file" });
1902
- return type === "file" ? resolved : ensureValidVarName(resolved);
1953
+ if (type === "file") return toFilePath(name);
1954
+ return ensureValidVarName(camelCase(name));
1903
1955
  },
1904
1956
  resolveName(name) {
1905
1957
  return this.default(name, "function");
@@ -1911,7 +1963,7 @@ const resolverClient = (0, _kubb_core.defineResolver)(() => ({
1911
1963
  return ensureValidVarName(pascalCase(name));
1912
1964
  },
1913
1965
  resolveGroupName(name) {
1914
- return ensureValidVarName(pascalCase(name));
1966
+ return ensureValidVarName(pascalCase(`${name} Client`));
1915
1967
  },
1916
1968
  resolveClientPropertyName(name) {
1917
1969
  return ensureValidVarName(camelCase(name));
@@ -1956,19 +2008,19 @@ const pluginClientName = "plugin-client";
1956
2008
  const pluginClient = (0, _kubb_core.definePlugin)((options) => {
1957
2009
  const { output = {
1958
2010
  path: "clients",
1959
- barrelType: "named"
1960
- }, group, exclude = [], include, override = [], urlType = false, dataReturnType = "data", paramsType = "inline", pathParamsType = paramsType === "object" ? "object" : options.pathParamsType || "inline", operations = false, paramsCasing, clientType = options.sdk ? "class" : "function", parser = false, client = "axios", importPath, bundle = false, sdk, baseURL, resolver: userResolver, transformer: userTransformer } = options;
2011
+ barrel: { type: "named" }
2012
+ }, group, exclude = [], include, override = [], urlType = false, dataReturnType = "data", paramsType = "inline", pathParamsType = paramsType === "object" ? "object" : options.pathParamsType || "inline", operations = false, paramsCasing, clientType = options.sdk ? "class" : "function", parser = false, client = "axios", importPath, bundle = false, sdk, baseURL, resolver: userResolver, macros: userMacros } = options;
1961
2013
  const resolvedImportPath = importPath ?? (!bundle ? `@kubb/plugin-client/clients/${client}` : void 0);
1962
2014
  const selectedGenerators = options.generators ?? [
1963
2015
  clientType === "staticClass" ? staticClassClientGenerator : clientType === "class" ? classClientGenerator : clientGenerator,
1964
2016
  group && clientType === "function" ? groupedClientGenerator : null,
1965
2017
  operations ? operationsGenerator : null
1966
2018
  ].filter((x) => Boolean(x));
1967
- const groupConfig = createGroupConfig(group, { suffix: "Controller" });
2019
+ const groupConfig = createGroupConfig(group);
1968
2020
  return {
1969
2021
  name: pluginClientName,
1970
2022
  options,
1971
- dependencies: [_kubb_plugin_ts.pluginTsName, parser === "zod" ? _kubb_plugin_zod.pluginZodName : null].filter((dependency) => Boolean(dependency)),
2023
+ dependencies: [_kubb_plugin_ts.pluginTsName, isParserEnabled(parser) ? _kubb_plugin_zod.pluginZodName : null].filter((dependency) => Boolean(dependency)),
1972
2024
  hooks: { "kubb:plugin:setup"(ctx) {
1973
2025
  const resolver = userResolver ? {
1974
2026
  ...resolverClient,
@@ -1995,7 +2047,7 @@ const pluginClient = (0, _kubb_core.definePlugin)((options) => {
1995
2047
  resolver
1996
2048
  });
1997
2049
  ctx.setResolver(resolver);
1998
- if (userTransformer) ctx.setTransformer(userTransformer);
2050
+ if (userMacros?.length) ctx.setMacros(userMacros);
1999
2051
  for (const gen of selectedGenerators) ctx.addGenerator(gen);
2000
2052
  const root = node_path$1.default.resolve(ctx.config.root, ctx.config.output.path);
2001
2053
  if (!resolvedImportPath?.startsWith(".")) {
@@ -2003,21 +2055,21 @@ const pluginClient = (0, _kubb_core.definePlugin)((options) => {
2003
2055
  ctx.injectFile({
2004
2056
  baseName: "client.ts",
2005
2057
  path: node_path$1.default.resolve(root, ".kubb/client.ts"),
2006
- sources: [_kubb_core.ast.createSource({
2058
+ sources: [_kubb_core.ast.factory.createSource({
2007
2059
  name: "client",
2008
- nodes: isInlineSource ? [_kubb_core.ast.createText(client === "fetch" ? require_templates_clients_fetch_source.source : require_templates_clients_axios_source.source)] : [],
2060
+ nodes: isInlineSource ? [_kubb_core.ast.factory.createText(client === "fetch" ? require_templates_clients_fetch_source.source : require_templates_clients_axios_source.source)] : [],
2009
2061
  isExportable: true,
2010
2062
  isIndexable: true
2011
2063
  })],
2012
- exports: !isInlineSource && resolvedImportPath ? [_kubb_core.ast.createExport({ path: resolvedImportPath })] : []
2064
+ exports: !isInlineSource && resolvedImportPath ? [_kubb_core.ast.factory.createExport({ path: resolvedImportPath })] : []
2013
2065
  });
2014
2066
  }
2015
2067
  ctx.injectFile({
2016
2068
  baseName: "config.ts",
2017
2069
  path: node_path$1.default.resolve(root, ".kubb/config.ts"),
2018
- sources: [_kubb_core.ast.createSource({
2070
+ sources: [_kubb_core.ast.factory.createSource({
2019
2071
  name: "config",
2020
- nodes: [_kubb_core.ast.createText(require_templates_config_source.source)],
2072
+ nodes: [_kubb_core.ast.factory.createText(require_templates_config_source.source)],
2021
2073
  isExportable: false,
2022
2074
  isIndexable: false
2023
2075
  })]
@@ -2031,9 +2083,13 @@ exports.classClientGenerator = classClientGenerator;
2031
2083
  exports.clientGenerator = clientGenerator;
2032
2084
  exports.default = pluginClient;
2033
2085
  exports.groupedClientGenerator = groupedClientGenerator;
2086
+ exports.isParserEnabled = isParserEnabled;
2034
2087
  exports.operationsGenerator = operationsGenerator;
2035
2088
  exports.pluginClient = pluginClient;
2036
2089
  exports.pluginClientName = pluginClientName;
2090
+ exports.resolveQueryParamsParser = resolveQueryParamsParser;
2091
+ exports.resolveRequestParser = resolveRequestParser;
2092
+ exports.resolveResponseParser = resolveResponseParser;
2037
2093
  exports.resolverClient = resolverClient;
2038
2094
  exports.staticClassClientGenerator = staticClassClientGenerator;
2039
2095