@kubb/plugin-client 5.0.0-beta.3 → 5.0.0-beta.30

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 (42) hide show
  1. package/README.md +24 -4
  2. package/dist/clients/axios.cjs +25 -3
  3. package/dist/clients/axios.cjs.map +1 -1
  4. package/dist/clients/axios.d.ts +9 -2
  5. package/dist/clients/axios.js +25 -3
  6. package/dist/clients/axios.js.map +1 -1
  7. package/dist/clients/fetch.cjs +20 -2
  8. package/dist/clients/fetch.cjs.map +1 -1
  9. package/dist/clients/fetch.d.ts +9 -2
  10. package/dist/clients/fetch.js +20 -2
  11. package/dist/clients/fetch.js.map +1 -1
  12. package/dist/index.cjs +524 -301
  13. package/dist/index.cjs.map +1 -1
  14. package/dist/index.d.ts +150 -84
  15. package/dist/index.js +525 -302
  16. package/dist/index.js.map +1 -1
  17. package/dist/templates/clients/axios.source.cjs +1 -1
  18. package/dist/templates/clients/axios.source.js +1 -1
  19. package/dist/templates/clients/fetch.source.cjs +1 -1
  20. package/dist/templates/clients/fetch.source.js +1 -1
  21. package/extension.yaml +1293 -0
  22. package/package.json +11 -17
  23. package/src/clients/axios.ts +41 -7
  24. package/src/clients/fetch.ts +30 -3
  25. package/src/components/ClassClient.tsx +17 -19
  26. package/src/components/Client.tsx +68 -51
  27. package/src/components/StaticClassClient.tsx +17 -19
  28. package/src/components/Url.tsx +7 -9
  29. package/src/components/WrapperClient.tsx +9 -5
  30. package/src/functionParams.ts +8 -8
  31. package/src/generators/classClientGenerator.tsx +40 -38
  32. package/src/generators/clientGenerator.tsx +32 -35
  33. package/src/generators/groupedClientGenerator.tsx +14 -8
  34. package/src/generators/operationsGenerator.tsx +12 -6
  35. package/src/generators/staticClassClientGenerator.tsx +34 -32
  36. package/src/plugin.ts +24 -11
  37. package/src/resolvers/resolverClient.ts +31 -8
  38. package/src/types.ts +90 -53
  39. package/src/utils.ts +30 -53
  40. package/templates/clients/axios.ts +0 -73
  41. package/templates/clients/fetch.ts +0 -96
  42. package/templates/config.ts +0 -43
package/dist/index.cjs CHANGED
@@ -191,6 +191,26 @@ function isValidVarName(name) {
191
191
  if (!name || reservedWords.has(name)) return false;
192
192
  return /^[a-zA-Z_$][a-zA-Z0-9_$]*$/.test(name);
193
193
  }
194
+ /**
195
+ * Returns `name` when it's a syntactically valid JavaScript variable name,
196
+ * otherwise prefixes it with `_` so the result is a valid identifier.
197
+ *
198
+ * Useful for sanitizing OpenAPI schema names or operation IDs that start with
199
+ * a digit (e.g. `409`, `504AccountCancel`) before using them as exported
200
+ * variable, type, or function names.
201
+ *
202
+ * @example
203
+ * ```ts
204
+ * ensureValidVarName('409') // '_409'
205
+ * ensureValidVarName('504AccountCancel') // '_504AccountCancel'
206
+ * ensureValidVarName('Pet') // 'Pet'
207
+ * ensureValidVarName('class') // '_class'
208
+ * ```
209
+ */
210
+ function ensureValidVarName(name) {
211
+ if (!name || isValidVarName(name)) return name;
212
+ return `_${name}`;
213
+ }
194
214
  //#endregion
195
215
  //#region ../../internals/utils/src/urlPath.ts
196
216
  /**
@@ -257,16 +277,16 @@ var URLPath = class {
257
277
  get object() {
258
278
  return this.toObject();
259
279
  }
260
- /** Returns a map of path parameter names, or `undefined` when the path has no parameters.
280
+ /** Returns a map of path parameter names, or `null` when the path has no parameters.
261
281
  *
262
282
  * @example
263
283
  * ```ts
264
284
  * new URLPath('/pet/{petId}').params // { petId: 'petId' }
265
- * new URLPath('/pet').params // undefined
285
+ * new URLPath('/pet').params // null
266
286
  * ```
267
287
  */
268
288
  get params() {
269
- return this.getParams();
289
+ return this.toParamsObject();
270
290
  }
271
291
  #transformParam(raw) {
272
292
  const param = isValidVarName(raw) ? raw : camelCase(raw);
@@ -284,7 +304,7 @@ var URLPath = class {
284
304
  toObject({ type = "path", replacer, stringify } = {}) {
285
305
  const object = {
286
306
  url: type === "path" ? this.toURLPath() : this.toTemplateString({ replacer }),
287
- params: this.getParams()
307
+ params: this.toParamsObject()
288
308
  };
289
309
  if (stringify) {
290
310
  if (type === "template") return JSON.stringify(object).replaceAll("'", "").replaceAll(`"`, "");
@@ -300,12 +320,13 @@ var URLPath = class {
300
320
  * @example
301
321
  * new URLPath('/pet/{petId}').toTemplateString() // '`/pet/${petId}`'
302
322
  */
303
- toTemplateString({ prefix = "", replacer } = {}) {
304
- return `\`${prefix}${this.path.split(/\{([^}]+)\}/).map((part, i) => {
323
+ toTemplateString({ prefix, replacer } = {}) {
324
+ const result = this.path.split(/\{([^}]+)\}/).map((part, i) => {
305
325
  if (i % 2 === 0) return part;
306
326
  const param = this.#transformParam(part);
307
327
  return `\${${replacer ? replacer(param) : param}}`;
308
- }).join("")}\``;
328
+ }).join("");
329
+ return `\`${prefix ?? ""}${result}\``;
309
330
  }
310
331
  /**
311
332
  * Extracts all `{param}` segments from the path and returns them as a key-value map.
@@ -314,17 +335,17 @@ var URLPath = class {
314
335
  *
315
336
  * @example
316
337
  * ```ts
317
- * new URLPath('/pet/{petId}/tag/{tagId}').getParams()
338
+ * new URLPath('/pet/{petId}/tag/{tagId}').toParamsObject()
318
339
  * // { petId: 'petId', tagId: 'tagId' }
319
340
  * ```
320
341
  */
321
- getParams(replacer) {
342
+ toParamsObject(replacer) {
322
343
  const params = {};
323
344
  this.#eachParam((_raw, param) => {
324
345
  const key = replacer ? replacer(param) : param;
325
346
  params[key] = key;
326
347
  });
327
- return Object.keys(params).length > 0 ? params : void 0;
348
+ return Object.keys(params).length > 0 ? params : null;
328
349
  }
329
350
  /** Converts the OpenAPI path to Express-style colon syntax.
330
351
  *
@@ -338,6 +359,121 @@ var URLPath = class {
338
359
  }
339
360
  };
340
361
  //#endregion
362
+ //#region ../../internals/shared/src/operation.ts
363
+ function getOperationLink(node, link) {
364
+ if (!link) return null;
365
+ if (typeof link === "function") return link(node) ?? null;
366
+ if (link === "urlPath") return node.path ? `{@link ${new URLPath(node.path).URL}}` : null;
367
+ return `{@link ${node.path.replaceAll("{", ":").replaceAll("}", "")}}`;
368
+ }
369
+ function getContentTypeInfo(node) {
370
+ const contentTypes = node.requestBody?.content?.map((e) => e.contentType) ?? [];
371
+ const isMultipleContentTypes = contentTypes.length > 1;
372
+ return {
373
+ contentTypes,
374
+ isMultipleContentTypes,
375
+ contentTypeUnion: isMultipleContentTypes ? contentTypes.map((ct) => JSON.stringify(ct)).join(" | ") : "",
376
+ defaultContentType: contentTypes[0] ?? "application/json",
377
+ hasFormData: contentTypes.some((ct) => ct === "multipart/form-data")
378
+ };
379
+ }
380
+ function buildRequestConfigType(node, resolver) {
381
+ const requestName = node.requestBody?.content?.[0]?.schema ? resolver.resolveDataName(node) : null;
382
+ const { isMultipleContentTypes, contentTypeUnion } = getContentTypeInfo(node);
383
+ return `${requestName ? `Partial<RequestConfig<${requestName}>>` : "Partial<RequestConfig>"} & { ${["client?: Client", isMultipleContentTypes ? `contentType?: ${contentTypeUnion}` : null].filter(Boolean).join("; ")} }`;
384
+ }
385
+ function buildOperationComments(node, options = {}) {
386
+ const { link = "pathTemplate", linkPosition = "afterDeprecated", splitLines = false } = options;
387
+ const linkComment = getOperationLink(node, link);
388
+ const filteredComments = (linkPosition === "beforeDeprecated" ? [
389
+ node.description && `@description ${node.description}`,
390
+ node.summary && `@summary ${node.summary}`,
391
+ linkComment,
392
+ node.deprecated && "@deprecated"
393
+ ] : [
394
+ node.description && `@description ${node.description}`,
395
+ node.summary && `@summary ${node.summary}`,
396
+ node.deprecated && "@deprecated",
397
+ linkComment
398
+ ]).filter((comment) => Boolean(comment));
399
+ if (!splitLines) return filteredComments;
400
+ return filteredComments.flatMap((text) => text.split(/\r?\n/).map((line) => line.trim())).filter((comment) => Boolean(comment));
401
+ }
402
+ function getOperationParameters(node, options = {}) {
403
+ const params = _kubb_core.ast.caseParams(node.parameters, options.paramsCasing);
404
+ return {
405
+ path: params.filter((param) => param.in === "path"),
406
+ query: params.filter((param) => param.in === "query"),
407
+ header: params.filter((param) => param.in === "header"),
408
+ cookie: params.filter((param) => param.in === "cookie")
409
+ };
410
+ }
411
+ function getStatusCodeNumber(statusCode) {
412
+ const code = Number(statusCode);
413
+ return Number.isNaN(code) ? null : code;
414
+ }
415
+ function isSuccessStatusCode(statusCode) {
416
+ const code = getStatusCodeNumber(statusCode);
417
+ return code !== null && code >= 200 && code < 300;
418
+ }
419
+ function isErrorStatusCode(statusCode) {
420
+ const code = getStatusCodeNumber(statusCode);
421
+ return code !== null && code >= 400;
422
+ }
423
+ function resolveErrorNames(node, resolver) {
424
+ return node.responses.filter((response) => isErrorStatusCode(response.statusCode)).map((response) => resolver.resolveResponseStatusName(node, response.statusCode));
425
+ }
426
+ function resolveSuccessNames(node, resolver) {
427
+ return node.responses.filter((response) => isSuccessStatusCode(response.statusCode)).map((response) => resolver.resolveResponseStatusName(node, response.statusCode));
428
+ }
429
+ function resolveStatusCodeNames(node, resolver) {
430
+ return node.responses.map((response) => resolver.resolveResponseStatusName(node, response.statusCode));
431
+ }
432
+ const typeNamesByResolver = /* @__PURE__ */ new WeakMap();
433
+ function resolveOperationTypeNames(node, resolver, options = {}) {
434
+ const cacheKey = `${node.operationId}\0${options.paramsCasing ?? ""}\0${options.order ?? ""}\0${options.responseStatusNames ?? ""}\0${(options.exclude ?? []).join(",")}`;
435
+ let byResolver = typeNamesByResolver.get(resolver);
436
+ if (byResolver) {
437
+ const cached = byResolver.get(cacheKey);
438
+ if (cached) return cached;
439
+ } else {
440
+ byResolver = /* @__PURE__ */ new Map();
441
+ typeNamesByResolver.set(resolver, byResolver);
442
+ }
443
+ const { path, query, header } = getOperationParameters(node, { paramsCasing: options.paramsCasing });
444
+ const responseStatusNames = options.responseStatusNames === "error" ? resolveErrorNames(node, resolver) : options.responseStatusNames === false ? [] : resolveStatusCodeNames(node, resolver);
445
+ const exclude = new Set(options.exclude ?? []);
446
+ const paramNames = [
447
+ ...path.map((param) => resolver.resolvePathParamsName(node, param)),
448
+ ...query.map((param) => resolver.resolveQueryParamsName(node, param)),
449
+ ...header.map((param) => resolver.resolveHeaderParamsName(node, param))
450
+ ];
451
+ const bodyAndResponseNames = [node.requestBody?.content?.[0]?.schema ? resolver.resolveDataName(node) : null, resolver.resolveResponseName(node)];
452
+ const result = (options.order === "body-response-first" ? [
453
+ ...bodyAndResponseNames,
454
+ ...paramNames,
455
+ ...responseStatusNames
456
+ ] : [
457
+ ...paramNames,
458
+ ...bodyAndResponseNames,
459
+ ...responseStatusNames
460
+ ]).filter((name) => Boolean(name) && !exclude.has(name));
461
+ byResolver.set(cacheKey, result);
462
+ return result;
463
+ }
464
+ //#endregion
465
+ //#region ../../internals/shared/src/params.ts
466
+ function buildParamsMapping(originalParams, mappedParams) {
467
+ const mapping = {};
468
+ let hasChanged = false;
469
+ originalParams.forEach((param, i) => {
470
+ const mappedName = mappedParams[i]?.name ?? param.name;
471
+ mapping[param.name] = mappedName;
472
+ if (param.name !== mappedName) hasChanged = true;
473
+ });
474
+ return hasChanged ? mapping : null;
475
+ }
476
+ //#endregion
341
477
  //#region src/functionParams.ts
342
478
  const declarationPrinter$4 = (0, _kubb_plugin_ts.functionPrinter)({ mode: "declaration" });
343
479
  const callPrinter = (0, _kubb_plugin_ts.functionPrinter)({ mode: "call" });
@@ -348,18 +484,18 @@ function createType(type) {
348
484
  return type ? _kubb_core.ast.createParamsType({
349
485
  variant: "reference",
350
486
  name: type
351
- }) : void 0;
487
+ }) : null;
352
488
  }
353
489
  function createDeclarationLeaf(name, spec) {
354
490
  if (spec.default !== void 0) return _kubb_core.ast.createFunctionParameter({
355
491
  name,
356
- type: createType(spec.type),
492
+ type: createType(spec.type) ?? void 0,
357
493
  default: spec.default,
358
494
  rest: spec.mode === "inlineSpread"
359
495
  });
360
496
  return _kubb_core.ast.createFunctionParameter({
361
497
  name,
362
- type: createType(spec.type),
498
+ type: createType(spec.type) ?? void 0,
363
499
  optional: !!spec.optional,
364
500
  rest: spec.mode === "inlineSpread"
365
501
  });
@@ -368,14 +504,14 @@ function createDeclarationParam(name, spec) {
368
504
  if (isGroup(spec)) return _kubb_core.ast.createParameterGroup({
369
505
  inline: spec.mode === "inlineSpread",
370
506
  default: spec.default,
371
- properties: Object.entries(spec.children).filter(([, child]) => child !== void 0).map(([childName, child]) => createDeclarationLeaf(childName, child))
507
+ properties: Object.entries(spec.children).filter(([, child]) => child != null).map(([childName, child]) => createDeclarationLeaf(childName, child))
372
508
  });
373
509
  return createDeclarationLeaf(name, spec);
374
510
  }
375
511
  function createCallParam(name, spec) {
376
512
  if (isGroup(spec)) return _kubb_core.ast.createParameterGroup({
377
513
  inline: spec.mode === "inlineSpread",
378
- properties: Object.entries(spec.children).filter(([, child]) => child !== void 0).map(([childName, child]) => _kubb_core.ast.createFunctionParameter({
514
+ properties: Object.entries(spec.children).filter(([, child]) => child != null).map(([childName, child]) => _kubb_core.ast.createFunctionParameter({
379
515
  name: child?.mode === "inlineSpread" ? spec.mode === "inlineSpread" ? child.value ?? childName : `...${child.value ?? childName}` : child?.value ? `${childName}: ${child.value}` : childName,
380
516
  rest: spec.mode === "inlineSpread" && child?.mode === "inlineSpread"
381
517
  }))
@@ -390,7 +526,7 @@ function createCallParam(name, spec) {
390
526
  * Returns utilities to output constructor signatures (`toConstructor()`) or call expressions (`toCall()`).
391
527
  */
392
528
  function createFunctionParams(params) {
393
- const entries = Object.entries(params).filter(([, spec]) => spec !== void 0);
529
+ const entries = Object.entries(params).filter(([, spec]) => spec != null);
394
530
  return {
395
531
  toConstructor() {
396
532
  return declarationPrinter$4.print(_kubb_core.ast.createFunctionParameters({ params: entries.map(([name, spec]) => createDeclarationParam(name, spec)) })) ?? "";
@@ -401,106 +537,9 @@ function createFunctionParams(params) {
401
537
  };
402
538
  }
403
539
  //#endregion
404
- //#region src/utils.ts
405
- /**
406
- * Extracts documentation comments from an operation node.
407
- * Includes description, summary, link, and deprecation information.
408
- */
409
- function getComments(node) {
410
- return [
411
- node.description && `@description ${node.description}`,
412
- node.summary && `@summary ${node.summary}`,
413
- node.path && `{@link ${new URLPath(node.path).URL}}`,
414
- node.deprecated && "@deprecated"
415
- ].filter((x) => Boolean(x)).flatMap((text) => text.split(/\r?\n/).map((line) => line.trim())).filter((x) => Boolean(x));
416
- }
417
- /**
418
- * Builds a mapping of original parameter names to their transformed (cased) names.
419
- * Returns undefined if no names have changed.
420
- */
421
- function buildParamsMapping(originalParams, casedParams) {
422
- const mapping = {};
423
- let hasChanged = false;
424
- originalParams.forEach((param, i) => {
425
- const casedName = casedParams[i]?.name ?? param.name;
426
- mapping[param.name] = casedName;
427
- if (param.name !== casedName) hasChanged = true;
428
- });
429
- return hasChanged ? mapping : void 0;
430
- }
431
- /**
432
- * Builds HTTP headers array for a client request.
433
- * Includes Content-Type (if not default) and spreads header parameters if present.
434
- */
435
- function buildHeaders(contentType, hasHeaderParams) {
436
- return [contentType !== "application/json" && contentType !== "multipart/form-data" ? `'Content-Type': '${contentType}'` : void 0, hasHeaderParams ? "...headers" : void 0].filter(Boolean);
437
- }
438
- /**
439
- * Builds TypeScript generic parameters for a client method.
440
- * Includes response type, error type, and optional request type.
441
- */
442
- function buildGenerics(node, tsResolver) {
443
- const responseName = tsResolver.resolveResponseName(node);
444
- const requestName = node.requestBody?.content?.[0]?.schema ? tsResolver.resolveDataName(node) : void 0;
445
- const errorNames = node.responses.filter((r) => Number.parseInt(r.statusCode, 10) >= 400).map((r) => tsResolver.resolveResponseStatusName(node, r.statusCode));
446
- return [
447
- responseName,
448
- `ResponseErrorConfig<${errorNames.length > 0 ? errorNames.join(" | ") : "Error"}>`,
449
- requestName || "unknown"
450
- ].filter(Boolean);
451
- }
452
- /**
453
- * Builds the parameters object for a class-based client method.
454
- * Includes URL, method, base URL, headers, and request/response data.
455
- */
456
- function buildClassClientParams({ node, path, baseURL, tsResolver, isFormData, headers }) {
457
- const queryParamsName = node.parameters.filter((p) => p.in === "query").length > 0 ? tsResolver.resolveQueryParamsName(node, node.parameters.filter((p) => p.in === "query")[0]) : void 0;
458
- const requestName = node.requestBody?.content?.[0]?.schema ? tsResolver.resolveDataName(node) : void 0;
459
- return createFunctionParams({ config: {
460
- mode: "object",
461
- children: {
462
- requestConfig: { mode: "inlineSpread" },
463
- method: { value: JSON.stringify(node.method.toUpperCase()) },
464
- url: { value: path.template },
465
- baseURL: baseURL ? { value: JSON.stringify(baseURL) } : void 0,
466
- params: queryParamsName ? {} : void 0,
467
- data: requestName ? { value: isFormData ? "formData as FormData" : "requestData" } : void 0,
468
- headers: headers.length ? { value: `{ ${headers.join(", ")}, ...requestConfig.headers }` } : void 0
469
- }
470
- } });
471
- }
472
- /**
473
- * Builds the request data parsing line for client methods.
474
- * Applies Zod validation if configured, otherwise uses data directly.
475
- */
476
- function buildRequestDataLine({ parser, node, zodResolver }) {
477
- const zodRequestName = zodResolver && parser === "zod" && node.requestBody?.content?.[0]?.schema ? zodResolver.resolveDataName?.(node) : void 0;
478
- if (parser === "zod" && zodRequestName) return `const requestData = ${zodRequestName}.parse(data)`;
479
- if (node.requestBody?.content?.[0]?.schema) return "const requestData = data";
480
- return "";
481
- }
482
- /**
483
- * Builds the form data conversion line for file upload requests.
484
- * Returns empty string if not applicable.
485
- */
486
- function buildFormDataLine(isFormData, hasRequest) {
487
- return isFormData && hasRequest ? "const formData = buildFormData(requestData)" : "";
488
- }
489
- /**
490
- * Builds the return statement for a client method.
491
- * Applies Zod validation to response data if configured, otherwise returns raw response.
492
- */
493
- function buildReturnStatement({ dataReturnType, parser, node, zodResolver }) {
494
- const zodResponseName = zodResolver && parser === "zod" ? zodResolver.resolveResponseName?.(node) : void 0;
495
- if (dataReturnType === "full" && parser === "zod" && zodResponseName) return `return {...res, data: ${zodResponseName}.parse(res.data)}`;
496
- if (dataReturnType === "data" && parser === "zod" && zodResponseName) return `return ${zodResponseName}.parse(res.data)`;
497
- if (dataReturnType === "full" && parser === "client") return "return res";
498
- return "return res.data";
499
- }
500
- //#endregion
501
540
  //#region src/components/Url.tsx
502
541
  const declarationPrinter$3 = (0, _kubb_plugin_ts.functionPrinter)({ mode: "declaration" });
503
- function getParams$1({ paramsType, paramsCasing, pathParamsType, node, tsResolver }) {
542
+ function buildUrlParamsNode({ paramsType, paramsCasing, pathParamsType, node, tsResolver }) {
504
543
  const urlNode = {
505
544
  ...node,
506
545
  parameters: node.parameters.filter((p) => p.in === "path"),
@@ -513,10 +552,9 @@ function getParams$1({ paramsType, paramsCasing, pathParamsType, node, tsResolve
513
552
  resolver: tsResolver
514
553
  });
515
554
  }
516
- require_chunk.__name(getParams$1, "getParams");
517
555
  function Url({ name, isExportable = true, isIndexable = true, baseURL, paramsType, paramsCasing, pathParamsType, node, tsResolver }) {
518
556
  const path = new URLPath(node.path);
519
- const paramsNode = getParams$1({
557
+ const paramsNode = buildUrlParamsNode({
520
558
  paramsType,
521
559
  paramsCasing,
522
560
  pathParamsType,
@@ -524,9 +562,9 @@ function Url({ name, isExportable = true, isIndexable = true, baseURL, paramsTyp
524
562
  tsResolver
525
563
  });
526
564
  const paramsSignature = declarationPrinter$3.print(paramsNode) ?? "";
527
- const originalPathParams = node.parameters.filter((p) => p.in === "path");
528
- const casedPathParams = _kubb_core.ast.caseParams(originalPathParams, paramsCasing);
529
- const pathParamsMapping = paramsCasing ? buildParamsMapping(originalPathParams, casedPathParams) : void 0;
565
+ const { path: originalPathParams } = getOperationParameters(node);
566
+ const { path: casedPathParams } = getOperationParameters(node, { paramsCasing });
567
+ const pathParamsMapping = paramsCasing ? buildParamsMapping(originalPathParams, casedPathParams) : null;
530
568
  return /* @__PURE__ */ (0, _kubb_renderer_jsx_jsx_runtime.jsx)(_kubb_renderer_jsx.File.Source, {
531
569
  name,
532
570
  isExportable,
@@ -548,56 +586,51 @@ function Url({ name, isExportable = true, isIndexable = true, baseURL, paramsTyp
548
586
  })
549
587
  });
550
588
  }
551
- Url.getParams = getParams$1;
552
589
  //#endregion
553
590
  //#region src/components/Client.tsx
554
591
  const declarationPrinter$2 = (0, _kubb_plugin_ts.functionPrinter)({ mode: "declaration" });
555
- function getParams({ paramsType, paramsCasing, pathParamsType, node, tsResolver, isConfigurable }) {
556
- const requestName = node.requestBody?.content?.[0]?.schema ? tsResolver.resolveDataName(node) : void 0;
592
+ function buildClientParamsNode({ paramsType, paramsCasing, pathParamsType, node, tsResolver, isConfigurable }) {
557
593
  return _kubb_core.ast.createOperationParams(node, {
558
594
  paramsType,
559
595
  pathParamsType: paramsType === "object" ? "object" : pathParamsType === "object" ? "object" : "inline",
560
596
  paramsCasing,
561
597
  resolver: tsResolver,
562
- extraParams: isConfigurable ? [_kubb_core.ast.createFunctionParameter({
598
+ extraParams: [...isConfigurable ? [_kubb_core.ast.createFunctionParameter({
563
599
  name: "config",
564
600
  type: _kubb_core.ast.createParamsType({
565
601
  variant: "reference",
566
- name: requestName ? `Partial<RequestConfig<${requestName}>> & { client?: Client }` : "Partial<RequestConfig> & { client?: Client }"
602
+ name: buildRequestConfigType(node, tsResolver)
567
603
  }),
568
604
  default: "{}"
569
- })] : []
605
+ })] : []]
570
606
  });
571
607
  }
572
608
  function Client({ name, isExportable = true, isIndexable = true, returnType, baseURL, dataReturnType, parser, paramsType, paramsCasing, pathParamsType, node, tsResolver, zodResolver, urlName, children, isConfigurable = true }) {
573
609
  const path = new URLPath(node.path);
574
- const contentType = node.requestBody?.content?.[0]?.contentType ?? "application/json";
575
- const isFormData = contentType === "multipart/form-data";
576
- const originalPathParams = node.parameters.filter((p) => p.in === "path");
577
- const casedPathParams = _kubb_core.ast.caseParams(originalPathParams, paramsCasing);
578
- const originalQueryParams = node.parameters.filter((p) => p.in === "query");
579
- const casedQueryParams = _kubb_core.ast.caseParams(originalQueryParams, paramsCasing);
580
- const originalHeaderParams = node.parameters.filter((p) => p.in === "header");
581
- const casedHeaderParams = _kubb_core.ast.caseParams(originalHeaderParams, paramsCasing);
582
- const pathParamsMapping = paramsCasing && !urlName ? buildParamsMapping(originalPathParams, casedPathParams) : void 0;
583
- const queryParamsMapping = paramsCasing ? buildParamsMapping(originalQueryParams, casedQueryParams) : void 0;
584
- const headerParamsMapping = paramsCasing ? buildParamsMapping(originalHeaderParams, casedHeaderParams) : void 0;
585
- const requestName = node.requestBody?.content?.[0]?.schema ? tsResolver.resolveDataName(node) : void 0;
586
- const responseName = tsResolver.resolveResponseName(node);
587
- const queryParamsName = originalQueryParams.length > 0 ? tsResolver.resolveQueryParamsName(node, originalQueryParams[0]) : void 0;
588
- const headerParamsName = originalHeaderParams.length > 0 ? tsResolver.resolveHeaderParamsName(node, originalHeaderParams[0]) : void 0;
589
- const zodResponseName = zodResolver && parser === "zod" ? zodResolver.resolveResponseName?.(node) : void 0;
590
- const zodRequestName = zodResolver && parser === "zod" && node.requestBody?.content?.[0]?.schema ? zodResolver.resolveDataName?.(node) : void 0;
610
+ const { defaultContentType: contentType, isMultipleContentTypes, hasFormData } = getContentTypeInfo(node);
611
+ const isFormData = !isMultipleContentTypes && contentType === "multipart/form-data";
612
+ const { path: originalPathParams, query: originalQueryParams, header: originalHeaderParams } = getOperationParameters(node);
613
+ const { path: casedPathParams, query: casedQueryParams, header: casedHeaderParams } = getOperationParameters(node, { paramsCasing });
614
+ const pathParamsMapping = paramsCasing && !urlName ? buildParamsMapping(originalPathParams, casedPathParams) : null;
615
+ const queryParamsMapping = paramsCasing ? buildParamsMapping(originalQueryParams, casedQueryParams) : null;
616
+ const headerParamsMapping = paramsCasing ? buildParamsMapping(originalHeaderParams, casedHeaderParams) : null;
617
+ const requestName = node.requestBody?.content?.[0]?.schema ? tsResolver.resolveDataName(node) : null;
618
+ const successNames = resolveSuccessNames(node, tsResolver);
619
+ const responseName = successNames.length > 0 ? successNames.join(" | ") : tsResolver.resolveResponseName(node);
620
+ const queryParamsName = originalQueryParams.length > 0 ? tsResolver.resolveQueryParamsName(node, originalQueryParams[0]) : null;
621
+ const headerParamsName = originalHeaderParams.length > 0 ? tsResolver.resolveHeaderParamsName(node, originalHeaderParams[0]) : null;
622
+ const zodResponseName = zodResolver && parser === "zod" ? zodResolver.resolveResponseName?.(node) : null;
623
+ const zodRequestName = zodResolver && parser === "zod" && node.requestBody?.content?.[0]?.schema ? zodResolver.resolveDataName?.(node) : null;
591
624
  const errorNames = node.responses.filter((r) => {
592
625
  return Number.parseInt(r.statusCode, 10) >= 400;
593
626
  }).map((r) => tsResolver.resolveResponseStatusName(node, r.statusCode));
594
- const headers = [contentType !== "application/json" && contentType !== "multipart/form-data" ? `'Content-Type': '${contentType}'` : void 0, headerParamsName ? headerParamsMapping ? "...mappedHeaders" : "...headers" : void 0].filter(Boolean);
627
+ const headers = [!isMultipleContentTypes && contentType !== "application/json" && contentType !== "multipart/form-data" ? `'Content-Type': '${contentType}'` : null, headerParamsName ? headerParamsMapping ? "...mappedHeaders" : "...headers" : null].filter(Boolean);
595
628
  const generics = [
596
629
  responseName,
597
630
  `ResponseErrorConfig<${errorNames.length > 0 ? errorNames.join(" | ") : "Error"}>`,
598
631
  requestName || "unknown"
599
632
  ].filter(Boolean);
600
- const paramsNode = getParams({
633
+ const paramsNode = buildClientParamsNode({
601
634
  paramsType,
602
635
  paramsCasing,
603
636
  pathParamsType,
@@ -606,7 +639,7 @@ function Client({ name, isExportable = true, isIndexable = true, returnType, bas
606
639
  isConfigurable
607
640
  });
608
641
  const paramsSignature = declarationPrinter$2.print(paramsNode) ?? "";
609
- const urlParamsNode = Url.getParams({
642
+ const urlParamsNode = buildUrlParamsNode({
610
643
  paramsType,
611
644
  paramsCasing,
612
645
  pathParamsType,
@@ -619,11 +652,12 @@ function Client({ name, isExportable = true, isIndexable = true, returnType, bas
619
652
  children: {
620
653
  method: { value: JSON.stringify(node.method.toUpperCase()) },
621
654
  url: { value: urlName ? `${urlName}(${urlParamsCall}).url.toString()` : path.template },
622
- baseURL: baseURL && !urlName ? { value: `\`${baseURL}\`` } : void 0,
623
- params: queryParamsName ? queryParamsMapping ? { value: "mappedParams" } : {} : void 0,
624
- data: requestName ? { value: isFormData ? "formData as FormData" : "requestData" } : void 0,
625
- requestConfig: isConfigurable ? { mode: "inlineSpread" } : void 0,
626
- headers: headers.length ? { value: isConfigurable ? `{ ${headers.join(", ")}, ...requestConfig.headers }` : `{ ${headers.join(", ")} }` } : void 0
655
+ baseURL: baseURL && !urlName ? { value: `\`${baseURL}\`` } : null,
656
+ params: queryParamsName ? queryParamsMapping ? { value: "mappedParams" } : {} : null,
657
+ data: requestName ? { value: isMultipleContentTypes && hasFormData ? "contentType === 'multipart/form-data' ? formData as FormData : requestData" : isFormData ? "formData as FormData" : "requestData" } : null,
658
+ contentType: isConfigurable && isMultipleContentTypes ? {} : null,
659
+ requestConfig: isConfigurable ? { mode: "inlineSpread" } : null,
660
+ headers: headers.length ? { value: isConfigurable ? `{ ${headers.join(", ")}, ...requestConfig.headers }` : `{ ${headers.join(", ")} }` } : null
627
661
  }
628
662
  } });
629
663
  const childrenElement = children ? children : /* @__PURE__ */ (0, _kubb_renderer_jsx_jsx_runtime.jsxs)(_kubb_renderer_jsx_jsx_runtime.Fragment, { children: [
@@ -641,10 +675,14 @@ function Client({ name, isExportable = true, isIndexable = true, returnType, bas
641
675
  async: true,
642
676
  export: isExportable,
643
677
  params: paramsSignature,
644
- JSDoc: { comments: getComments(node) },
678
+ JSDoc: { comments: buildOperationComments(node, {
679
+ link: "urlPath",
680
+ linkPosition: "beforeDeprecated",
681
+ splitLines: true
682
+ }) },
645
683
  returnType,
646
684
  children: [
647
- isConfigurable ? "const { client: request = fetch, ...requestConfig } = config" : "",
685
+ isConfigurable ? `const { client: request = client, ${isMultipleContentTypes ? `contentType = ${JSON.stringify(contentType)}, ` : ""}...requestConfig } = config` : "",
648
686
  /* @__PURE__ */ (0, _kubb_renderer_jsx_jsx_runtime.jsx)("br", {}),
649
687
  /* @__PURE__ */ (0, _kubb_renderer_jsx_jsx_runtime.jsx)("br", {}),
650
688
  pathParamsMapping && Object.entries(pathParamsMapping).filter(([originalName, camelCaseName]) => isValidVarName(originalName) && originalName !== camelCaseName).map(([originalName, camelCaseName]) => `const ${originalName} = ${camelCaseName}`).join("\n"),
@@ -661,26 +699,101 @@ function Client({ name, isExportable = true, isIndexable = true, returnType, bas
661
699
  ] }),
662
700
  parser === "zod" && zodRequestName ? `const requestData = ${zodRequestName}.parse(data)` : requestName && "const requestData = data",
663
701
  /* @__PURE__ */ (0, _kubb_renderer_jsx_jsx_runtime.jsx)("br", {}),
664
- isFormData && requestName && "const formData = buildFormData(requestData)",
702
+ (isFormData || isMultipleContentTypes && hasFormData) && requestName && "const formData = buildFormData(requestData)",
665
703
  /* @__PURE__ */ (0, _kubb_renderer_jsx_jsx_runtime.jsx)("br", {}),
666
- isConfigurable ? `const res = await request<${generics.join(", ")}>(${clientParams.toCall()})` : `const res = await fetch<${generics.join(", ")}>(${clientParams.toCall()})`,
704
+ isConfigurable ? `const res = await request<${generics.join(", ")}>(${clientParams.toCall()})` : `const res = await client<${generics.join(", ")}>(${clientParams.toCall()})`,
667
705
  /* @__PURE__ */ (0, _kubb_renderer_jsx_jsx_runtime.jsx)("br", {}),
668
706
  childrenElement
669
707
  ]
670
708
  })
671
709
  })] });
672
710
  }
673
- Client.getParams = getParams;
711
+ //#endregion
712
+ //#region src/utils.ts
713
+ /**
714
+ * Builds HTTP headers array for a client request.
715
+ * Includes Content-Type (if not default) and spreads header parameters if present.
716
+ */
717
+ function buildHeaders(contentType, hasHeaderParams) {
718
+ return [contentType !== "application/json" && contentType !== "multipart/form-data" ? `'Content-Type': '${contentType}'` : null, hasHeaderParams ? "...headers" : null].filter(Boolean);
719
+ }
720
+ /**
721
+ * Builds TypeScript generic parameters for a client method.
722
+ * Includes response type, error type, and optional request type.
723
+ */
724
+ function buildGenerics(node, tsResolver) {
725
+ const successNames = resolveSuccessNames(node, tsResolver);
726
+ const responseName = successNames.length > 0 ? successNames.join(" | ") : tsResolver.resolveResponseName(node);
727
+ const requestName = node.requestBody?.content?.[0]?.schema ? tsResolver.resolveDataName(node) : null;
728
+ const errorNames = node.responses.filter((r) => Number.parseInt(r.statusCode, 10) >= 400).map((r) => tsResolver.resolveResponseStatusName(node, r.statusCode));
729
+ return [
730
+ responseName,
731
+ `ResponseErrorConfig<${errorNames.length > 0 ? errorNames.join(" | ") : "Error"}>`,
732
+ requestName || "unknown"
733
+ ].filter(Boolean);
734
+ }
735
+ /**
736
+ * Builds the parameters object for a class-based client method.
737
+ * Includes URL, method, base URL, headers, and request/response data.
738
+ */
739
+ function buildClassClientParams({ node, path, baseURL, tsResolver, isFormData, isMultipleContentTypes, hasFormData, headers }) {
740
+ const { query: queryParams } = getOperationParameters(node);
741
+ const queryParamsName = queryParams.length > 0 ? tsResolver.resolveQueryParamsName(node, queryParams[0]) : null;
742
+ const requestName = node.requestBody?.content?.[0]?.schema ? tsResolver.resolveDataName(node) : null;
743
+ return createFunctionParams({ config: {
744
+ mode: "object",
745
+ children: {
746
+ requestConfig: { mode: "inlineSpread" },
747
+ method: { value: JSON.stringify(node.method.toUpperCase()) },
748
+ url: { value: path.template },
749
+ baseURL: baseURL ? { value: JSON.stringify(baseURL) } : null,
750
+ params: queryParamsName ? {} : null,
751
+ data: requestName ? { value: isMultipleContentTypes && hasFormData ? "contentType === 'multipart/form-data' ? formData as FormData : requestData" : isFormData ? "formData as FormData" : "requestData" } : null,
752
+ contentType: isMultipleContentTypes ? {} : null,
753
+ headers: headers.length ? { value: `{ ${headers.join(", ")}, ...requestConfig.headers }` } : null
754
+ }
755
+ } });
756
+ }
757
+ /**
758
+ * Builds the request data parsing line for client methods.
759
+ * Applies Zod validation if configured, otherwise uses data directly.
760
+ */
761
+ function buildRequestDataLine({ parser, node, zodResolver }) {
762
+ const zodRequestName = zodResolver && parser === "zod" && node.requestBody?.content?.[0]?.schema ? zodResolver.resolveDataName?.(node) : null;
763
+ if (parser === "zod" && zodRequestName) return `const requestData = ${zodRequestName}.parse(data)`;
764
+ if (node.requestBody?.content?.[0]?.schema) return "const requestData = data";
765
+ return "";
766
+ }
767
+ /**
768
+ * Builds the form data conversion line for file upload requests.
769
+ * Returns empty string if not applicable.
770
+ */
771
+ function buildFormDataLine(isFormData, hasRequest) {
772
+ return isFormData && hasRequest ? "const formData = buildFormData(requestData)" : "";
773
+ }
774
+ /**
775
+ * Builds the return statement for a client method.
776
+ * Applies Zod validation to response data if configured, otherwise returns raw response.
777
+ */
778
+ function buildReturnStatement({ dataReturnType, parser, node, zodResolver }) {
779
+ const zodResponseName = zodResolver && parser === "zod" ? zodResolver.resolveResponseName?.(node) : null;
780
+ if (dataReturnType === "full" && parser === "zod" && zodResponseName) return `return {...res, data: ${zodResponseName}.parse(res.data)}`;
781
+ if (dataReturnType === "data" && parser === "zod" && zodResponseName) return `return ${zodResponseName}.parse(res.data)`;
782
+ if (dataReturnType === "full" && parser === "client") return "return res";
783
+ return "return res.data";
784
+ }
674
785
  //#endregion
675
786
  //#region src/components/ClassClient.tsx
676
787
  const declarationPrinter$1 = (0, _kubb_plugin_ts.functionPrinter)({ mode: "declaration" });
677
788
  function generateMethod$1({ node, name, tsResolver, zodResolver, baseURL, dataReturnType, parser, paramsType, paramsCasing, pathParamsType }) {
678
789
  const path = new URLPath(node.path, { casing: paramsCasing });
679
- const contentType = node.requestBody?.content?.[0]?.contentType ?? "application/json";
680
- const isFormData = contentType === "multipart/form-data";
681
- const headers = buildHeaders(contentType, !!(node.parameters.filter((p) => p.in === "header").length > 0 ? tsResolver.resolveHeaderParamsName(node, node.parameters.filter((p) => p.in === "header")[0]) : void 0));
790
+ const { defaultContentType: contentType, isMultipleContentTypes, hasFormData } = getContentTypeInfo(node);
791
+ const isFormData = !isMultipleContentTypes && contentType === "multipart/form-data";
792
+ const { header: headerParams } = getOperationParameters(node);
793
+ const headerParamsName = headerParams.length > 0 ? tsResolver.resolveHeaderParamsName(node, headerParams[0]) : null;
794
+ const headers = isMultipleContentTypes ? headerParamsName ? ["...headers"] : [] : buildHeaders(contentType, !!headerParamsName);
682
795
  const generics = buildGenerics(node, tsResolver);
683
- const paramsNode = ClassClient.getParams({
796
+ const paramsNode = buildClientParamsNode({
684
797
  paramsType,
685
798
  paramsCasing,
686
799
  pathParamsType,
@@ -695,15 +808,21 @@ function generateMethod$1({ node, name, tsResolver, zodResolver, baseURL, dataRe
695
808
  baseURL,
696
809
  tsResolver,
697
810
  isFormData,
811
+ isMultipleContentTypes,
812
+ hasFormData,
698
813
  headers
699
814
  });
700
- const jsdoc = buildJSDoc(getComments(node));
815
+ const jsdoc = buildJSDoc(buildOperationComments(node, {
816
+ link: "urlPath",
817
+ linkPosition: "beforeDeprecated",
818
+ splitLines: true
819
+ }));
701
820
  const requestDataLine = buildRequestDataLine({
702
821
  parser,
703
822
  node,
704
823
  zodResolver
705
824
  });
706
- const formDataLine = buildFormDataLine(isFormData, !!node.requestBody?.content?.[0]?.schema);
825
+ const formDataLine = buildFormDataLine(isFormData || isMultipleContentTypes && hasFormData, !!node.requestBody?.content?.[0]?.schema);
707
826
  const returnStatement = buildReturnStatement({
708
827
  dataReturnType,
709
828
  parser,
@@ -711,7 +830,7 @@ function generateMethod$1({ node, name, tsResolver, zodResolver, baseURL, dataRe
711
830
  zodResolver
712
831
  });
713
832
  return `${jsdoc}async ${name}(${paramsSignature}) {\n${[
714
- "const { client: request = fetch, ...requestConfig } = mergeConfig(this.#config, config)",
833
+ `const { client: request = client, ${isMultipleContentTypes ? `contentType = ${JSON.stringify(contentType)}, ` : ""}...requestConfig } = mergeConfig(this.#config, config)`,
715
834
  "",
716
835
  requestDataLine,
717
836
  formDataLine,
@@ -748,15 +867,14 @@ ${operations.map(({ node, name: methodName, tsResolver, zodResolver }) => genera
748
867
  children: [classCode, children]
749
868
  });
750
869
  }
751
- ClassClient.getParams = Client.getParams;
752
870
  //#endregion
753
871
  //#region src/components/WrapperClient.tsx
754
- function WrapperClient({ name, classNames, isExportable = true, isIndexable = true }) {
872
+ function WrapperClient({ name, controllers, isExportable = true, isIndexable = true }) {
755
873
  const classCode = `export class ${name} {
756
- ${classNames.map((className) => ` readonly ${camelCase(className)}: ${className}`).join("\n")}
874
+ ${controllers.map(({ className, propertyName }) => ` readonly ${propertyName}: ${className}`).join("\n")}
757
875
 
758
876
  constructor(config: Partial<RequestConfig> & { client?: Client } = {}) {
759
- ${classNames.map((className) => ` this.${camelCase(className)} = new ${className}(config)`).join("\n")}
877
+ ${controllers.map(({ className, propertyName }) => ` this.${propertyName} = new ${className}(config)`).join("\n")}
760
878
  }
761
879
  }`;
762
880
  return /* @__PURE__ */ (0, _kubb_renderer_jsx_jsx_runtime.jsx)(_kubb_renderer_jsx.File.Source, {
@@ -769,33 +887,31 @@ ${classNames.map((className) => ` this.${camelCase(className)} = new ${classN
769
887
  //#endregion
770
888
  //#region src/generators/classClientGenerator.tsx
771
889
  function resolveTypeImportNames$1(node, tsResolver) {
772
- return [
773
- node.requestBody?.content?.[0]?.schema ? tsResolver.resolveDataName(node) : void 0,
774
- tsResolver.resolveResponseName(node),
775
- ...node.parameters.filter((p) => p.in === "path").map((p) => tsResolver.resolvePathParamsName(node, p)),
776
- ...node.parameters.filter((p) => p.in === "query").map((p) => tsResolver.resolveQueryParamsName(node, p)),
777
- ...node.parameters.filter((p) => p.in === "header").map((p) => tsResolver.resolveHeaderParamsName(node, p)),
778
- ...node.responses.map((res) => tsResolver.resolveResponseStatusName(node, res.statusCode))
779
- ].filter((n) => Boolean(n));
890
+ return resolveOperationTypeNames(node, tsResolver, { order: "body-response-first" });
780
891
  }
781
892
  require_chunk.__name(resolveTypeImportNames$1, "resolveTypeImportNames");
782
893
  function resolveZodImportNames$1(node, zodResolver) {
783
- return [zodResolver.resolveResponseName?.(node), node.requestBody?.content?.[0]?.schema ? zodResolver.resolveDataName?.(node) : void 0].filter((n) => Boolean(n));
894
+ return [zodResolver.resolveResponseName?.(node), node.requestBody?.content?.[0]?.schema ? zodResolver.resolveDataName?.(node) : null].filter((n) => Boolean(n));
784
895
  }
785
896
  require_chunk.__name(resolveZodImportNames$1, "resolveZodImportNames");
897
+ /**
898
+ * Built-in `operations` generator for `@kubb/plugin-client` when
899
+ * `clientType: 'class'`. Emits one class per tag, with one instance method
900
+ * per operation and a shared constructor for request configuration.
901
+ */
786
902
  const classClientGenerator = (0, _kubb_core.defineGenerator)({
787
903
  name: "classClient",
788
- renderer: _kubb_renderer_jsx.jsxRenderer,
904
+ renderer: _kubb_renderer_jsx.jsxRendererSync,
789
905
  operations(nodes, ctx) {
790
- const { adapter, config, driver, resolver, root } = ctx;
906
+ const { config, driver, resolver, root } = ctx;
791
907
  const { output, group, dataReturnType, paramsCasing, paramsType, pathParamsType, parser, importPath, sdk } = ctx.options;
792
- const baseURL = ctx.options.baseURL ?? adapter.inputNode?.meta?.baseURL;
908
+ const baseURL = ctx.options.baseURL ?? ctx.meta.baseURL;
793
909
  const pluginTs = driver.getPlugin(_kubb_plugin_ts.pluginTsName);
794
910
  if (!pluginTs) return null;
795
911
  const tsResolver = driver.getResolver(_kubb_plugin_ts.pluginTsName);
796
912
  const tsPluginOptions = pluginTs.options;
797
- const pluginZod = parser === "zod" ? driver.getPlugin(_kubb_plugin_zod.pluginZodName) : void 0;
798
- const zodResolver = pluginZod ? driver.getResolver(_kubb_plugin_zod.pluginZodName) : void 0;
913
+ const pluginZod = parser === "zod" ? driver.getPlugin(_kubb_plugin_zod.pluginZodName) : null;
914
+ const zodResolver = pluginZod ? driver.getResolver(_kubb_plugin_zod.pluginZodName) : null;
799
915
  function buildOperationData(node) {
800
916
  const typeFile = tsResolver.resolveFile({
801
917
  name: node.operationId,
@@ -815,8 +931,8 @@ const classClientGenerator = (0, _kubb_core.defineGenerator)({
815
931
  }, {
816
932
  root,
817
933
  output: pluginZod.options?.output ?? output,
818
- group: pluginZod.options?.group
819
- }) : void 0;
934
+ group: pluginZod.options?.group ?? void 0
935
+ }) : null;
820
936
  return {
821
937
  node,
822
938
  name: resolver.resolveName(node.operationId),
@@ -828,26 +944,29 @@ const classClientGenerator = (0, _kubb_core.defineGenerator)({
828
944
  }
829
945
  const controllers = nodes.reduce((acc, operationNode) => {
830
946
  const tag = operationNode.tags[0];
831
- const groupName = tag ? group?.name?.({ group: camelCase(tag) }) ?? pascalCase(tag) : "Client";
947
+ const groupName = tag ? group?.name?.({ group: camelCase(tag) }) ?? resolver.resolveGroupName(tag) : resolver.resolveGroupName("Client");
832
948
  if (!tag && !group) {
833
- const name = "ApiClient";
949
+ const name = resolver.resolveClassName("ApiClient");
834
950
  const file = resolver.resolveFile({
835
951
  name,
836
952
  extname: ".ts"
837
953
  }, {
838
954
  root,
839
955
  output,
840
- group
956
+ group: group ?? void 0
841
957
  });
842
958
  const operationData = buildOperationData(operationNode);
843
959
  const previous = acc.find((item) => item.file.path === file.path);
844
960
  if (previous) previous.operations.push(operationData);
845
961
  else acc.push({
846
962
  name,
963
+ propertyName: resolver.resolveClientPropertyName(name),
847
964
  file,
848
965
  operations: [operationData]
849
966
  });
850
- } else if (tag) {
967
+ return acc;
968
+ }
969
+ if (tag) {
851
970
  const name = groupName;
852
971
  const file = resolver.resolveFile({
853
972
  name,
@@ -856,13 +975,14 @@ const classClientGenerator = (0, _kubb_core.defineGenerator)({
856
975
  }, {
857
976
  root,
858
977
  output,
859
- group
978
+ group: group ?? void 0
860
979
  });
861
980
  const operationData = buildOperationData(operationNode);
862
981
  const previous = acc.find((item) => item.file.path === file.path);
863
982
  if (previous) previous.operations.push(operationData);
864
983
  else acc.push({
865
984
  name,
985
+ propertyName: resolver.resolveClientPropertyName(name),
866
986
  file,
867
987
  operations: [operationData]
868
988
  });
@@ -910,23 +1030,31 @@ const classClientGenerator = (0, _kubb_core.defineGenerator)({
910
1030
  zodImportsByFile: /* @__PURE__ */ new Map(),
911
1031
  zodFilesByPath: /* @__PURE__ */ new Map()
912
1032
  };
913
- const hasFormData = ops.some((op) => op.node.requestBody?.content?.[0]?.contentType === "multipart/form-data");
1033
+ const hasFormData = ops.some((op) => op.node.requestBody?.content?.some((e) => e.contentType === "multipart/form-data") ?? false);
914
1034
  return /* @__PURE__ */ (0, _kubb_renderer_jsx_jsx_runtime.jsxs)(_kubb_renderer_jsx.File, {
915
1035
  baseName: file.baseName,
916
1036
  path: file.path,
917
1037
  meta: file.meta,
918
- banner: resolver.resolveBanner(adapter.inputNode, {
1038
+ banner: resolver.resolveBanner(ctx.meta, {
919
1039
  output,
920
- config
1040
+ config,
1041
+ file: {
1042
+ path: file.path,
1043
+ baseName: file.baseName
1044
+ }
921
1045
  }),
922
- footer: resolver.resolveFooter(adapter.inputNode, {
1046
+ footer: resolver.resolveFooter(ctx.meta, {
923
1047
  output,
924
- config
1048
+ config,
1049
+ file: {
1050
+ path: file.path,
1051
+ baseName: file.baseName
1052
+ }
925
1053
  }),
926
1054
  children: [
927
1055
  importPath ? /* @__PURE__ */ (0, _kubb_renderer_jsx_jsx_runtime.jsxs)(_kubb_renderer_jsx_jsx_runtime.Fragment, { children: [
928
1056
  /* @__PURE__ */ (0, _kubb_renderer_jsx_jsx_runtime.jsx)(_kubb_renderer_jsx.File.Import, {
929
- name: "fetch",
1057
+ name: "client",
930
1058
  path: importPath
931
1059
  }),
932
1060
  /* @__PURE__ */ (0, _kubb_renderer_jsx_jsx_runtime.jsx)(_kubb_renderer_jsx.File.Import, {
@@ -944,7 +1072,7 @@ const classClientGenerator = (0, _kubb_core.defineGenerator)({
944
1072
  })
945
1073
  ] }) : /* @__PURE__ */ (0, _kubb_renderer_jsx_jsx_runtime.jsxs)(_kubb_renderer_jsx_jsx_runtime.Fragment, { children: [
946
1074
  /* @__PURE__ */ (0, _kubb_renderer_jsx_jsx_runtime.jsx)(_kubb_renderer_jsx.File.Import, {
947
- name: ["fetch"],
1075
+ name: ["client"],
948
1076
  root: file.path,
949
1077
  path: node_path.default.resolve(root, ".kubb/client.ts")
950
1078
  }),
@@ -1012,19 +1140,27 @@ const classClientGenerator = (0, _kubb_core.defineGenerator)({
1012
1140
  }, {
1013
1141
  root,
1014
1142
  output,
1015
- group
1143
+ group: group ?? void 0
1016
1144
  });
1017
1145
  files.push(/* @__PURE__ */ (0, _kubb_renderer_jsx_jsx_runtime.jsxs)(_kubb_renderer_jsx.File, {
1018
1146
  baseName: sdkFile.baseName,
1019
1147
  path: sdkFile.path,
1020
1148
  meta: sdkFile.meta,
1021
- banner: resolver.resolveBanner(adapter.inputNode, {
1149
+ banner: resolver.resolveBanner(ctx.meta, {
1022
1150
  output,
1023
- config
1151
+ config,
1152
+ file: {
1153
+ path: sdkFile.path,
1154
+ baseName: sdkFile.baseName
1155
+ }
1024
1156
  }),
1025
- footer: resolver.resolveFooter(adapter.inputNode, {
1157
+ footer: resolver.resolveFooter(ctx.meta, {
1026
1158
  output,
1027
- config
1159
+ config,
1160
+ file: {
1161
+ path: sdkFile.path,
1162
+ baseName: sdkFile.baseName
1163
+ }
1028
1164
  }),
1029
1165
  children: [
1030
1166
  importPath ? /* @__PURE__ */ (0, _kubb_renderer_jsx_jsx_runtime.jsx)(_kubb_renderer_jsx.File.Import, {
@@ -1044,7 +1180,10 @@ const classClientGenerator = (0, _kubb_core.defineGenerator)({
1044
1180
  }, name)),
1045
1181
  /* @__PURE__ */ (0, _kubb_renderer_jsx_jsx_runtime.jsx)(WrapperClient, {
1046
1182
  name: sdk.className,
1047
- classNames: controllers.map(({ name }) => name)
1183
+ controllers: controllers.map(({ name, propertyName }) => ({
1184
+ className: name,
1185
+ propertyName
1186
+ }))
1048
1187
  })
1049
1188
  ]
1050
1189
  }, sdkFile.path));
@@ -1054,34 +1193,28 @@ const classClientGenerator = (0, _kubb_core.defineGenerator)({
1054
1193
  });
1055
1194
  //#endregion
1056
1195
  //#region src/generators/clientGenerator.tsx
1196
+ /**
1197
+ * Built-in operation generator for `@kubb/plugin-client`. Emits one async
1198
+ * function per OpenAPI operation, plus the matching URL helper. Used when
1199
+ * `clientType: 'function'` (the default).
1200
+ */
1057
1201
  const clientGenerator = (0, _kubb_core.defineGenerator)({
1058
1202
  name: "client",
1059
- renderer: _kubb_renderer_jsx.jsxRenderer,
1203
+ renderer: _kubb_renderer_jsx.jsxRendererSync,
1060
1204
  operation(node, ctx) {
1061
- const { adapter, config, driver, resolver, root } = ctx;
1205
+ const { config, driver, resolver, root } = ctx;
1062
1206
  const { output, urlType, dataReturnType, paramsCasing, paramsType, pathParamsType, parser, importPath, group } = ctx.options;
1063
- const baseURL = ctx.options.baseURL ?? adapter.inputNode?.meta?.baseURL;
1207
+ const baseURL = ctx.options.baseURL ?? ctx.meta.baseURL;
1064
1208
  const pluginTs = driver.getPlugin(_kubb_plugin_ts.pluginTsName);
1065
1209
  if (!pluginTs) return null;
1066
1210
  const tsResolver = driver.getResolver(_kubb_plugin_ts.pluginTsName);
1067
- const pluginZod = parser === "zod" ? driver.getPlugin(_kubb_plugin_zod.pluginZodName) : void 0;
1068
- const zodResolver = pluginZod ? driver.getResolver(_kubb_plugin_zod.pluginZodName) : void 0;
1069
- const casedParams = _kubb_core.ast.caseParams(node.parameters, paramsCasing);
1070
- const pathParams = casedParams.filter((p) => p.in === "path");
1071
- const queryParams = casedParams.filter((p) => p.in === "query");
1072
- const headerParams = casedParams.filter((p) => p.in === "header");
1073
- const importedTypeNames = [
1074
- ...pathParams.map((p) => tsResolver.resolvePathParamsName(node, p)),
1075
- ...queryParams.map((p) => tsResolver.resolveQueryParamsName(node, p)),
1076
- ...headerParams.map((p) => tsResolver.resolveHeaderParamsName(node, p)),
1077
- node.requestBody?.content?.[0]?.schema ? tsResolver.resolveDataName(node) : void 0,
1078
- tsResolver.resolveResponseName(node),
1079
- ...node.responses.map((res) => tsResolver.resolveResponseStatusName(node, res.statusCode))
1080
- ].filter(Boolean);
1081
- const importedZodNames = zodResolver && parser === "zod" ? [zodResolver.resolveResponseName?.(node), node.requestBody?.content?.[0]?.schema ? zodResolver.resolveDataName?.(node) : void 0].filter(Boolean) : [];
1211
+ const pluginZod = parser === "zod" ? driver.getPlugin(_kubb_plugin_zod.pluginZodName) : null;
1212
+ const zodResolver = pluginZod ? driver.getResolver(_kubb_plugin_zod.pluginZodName) : null;
1213
+ const importedTypeNames = resolveOperationTypeNames(node, tsResolver, { paramsCasing });
1214
+ const importedZodNames = zodResolver && parser === "zod" ? [zodResolver.resolveResponseName?.(node), node.requestBody?.content?.[0]?.schema ? zodResolver.resolveDataName?.(node) : null].filter((name) => Boolean(name)) : [];
1082
1215
  const meta = {
1083
1216
  name: resolver.resolveName(node.operationId),
1084
- urlName: `get${resolver.resolveName(node.operationId).charAt(0).toUpperCase()}${resolver.resolveName(node.operationId).slice(1)}Url`,
1217
+ urlName: resolver.resolveUrlName(node),
1085
1218
  file: resolver.resolveFile({
1086
1219
  name: node.operationId,
1087
1220
  extname: ".ts",
@@ -1090,7 +1223,7 @@ const clientGenerator = (0, _kubb_core.defineGenerator)({
1090
1223
  }, {
1091
1224
  root,
1092
1225
  output,
1093
- group
1226
+ group: group ?? void 0
1094
1227
  }),
1095
1228
  fileTs: tsResolver.resolveFile({
1096
1229
  name: node.operationId,
@@ -1100,7 +1233,7 @@ const clientGenerator = (0, _kubb_core.defineGenerator)({
1100
1233
  }, {
1101
1234
  root,
1102
1235
  output: pluginTs.options?.output ?? output,
1103
- group: pluginTs.options?.group
1236
+ group: pluginTs.options?.group ?? void 0
1104
1237
  }),
1105
1238
  fileZod: zodResolver && pluginZod?.options ? zodResolver.resolveFile({
1106
1239
  name: node.operationId,
@@ -1110,25 +1243,33 @@ const clientGenerator = (0, _kubb_core.defineGenerator)({
1110
1243
  }, {
1111
1244
  root,
1112
1245
  output: pluginZod.options.output ?? output,
1113
- group: pluginZod.options?.group
1114
- }) : void 0
1246
+ group: pluginZod.options?.group ?? void 0
1247
+ }) : null
1115
1248
  };
1116
- const isFormData = node.requestBody?.content?.[0]?.contentType === "multipart/form-data";
1249
+ const hasFormData = node.requestBody?.content?.some((e) => e.contentType === "multipart/form-data") ?? false;
1117
1250
  return /* @__PURE__ */ (0, _kubb_renderer_jsx_jsx_runtime.jsxs)(_kubb_renderer_jsx.File, {
1118
1251
  baseName: meta.file.baseName,
1119
1252
  path: meta.file.path,
1120
1253
  meta: meta.file.meta,
1121
- banner: resolver.resolveBanner(adapter.inputNode, {
1254
+ banner: resolver.resolveBanner(ctx.meta, {
1122
1255
  output,
1123
- config
1256
+ config,
1257
+ file: {
1258
+ path: meta.file.path,
1259
+ baseName: meta.file.baseName
1260
+ }
1124
1261
  }),
1125
- footer: resolver.resolveFooter(adapter.inputNode, {
1262
+ footer: resolver.resolveFooter(ctx.meta, {
1126
1263
  output,
1127
- config
1264
+ config,
1265
+ file: {
1266
+ path: meta.file.path,
1267
+ baseName: meta.file.baseName
1268
+ }
1128
1269
  }),
1129
1270
  children: [
1130
1271
  importPath ? /* @__PURE__ */ (0, _kubb_renderer_jsx_jsx_runtime.jsxs)(_kubb_renderer_jsx_jsx_runtime.Fragment, { children: [/* @__PURE__ */ (0, _kubb_renderer_jsx_jsx_runtime.jsx)(_kubb_renderer_jsx.File.Import, {
1131
- name: "fetch",
1272
+ name: "client",
1132
1273
  path: importPath
1133
1274
  }), /* @__PURE__ */ (0, _kubb_renderer_jsx_jsx_runtime.jsx)(_kubb_renderer_jsx.File.Import, {
1134
1275
  name: [
@@ -1139,7 +1280,7 @@ const clientGenerator = (0, _kubb_core.defineGenerator)({
1139
1280
  path: importPath,
1140
1281
  isTypeOnly: true
1141
1282
  })] }) : /* @__PURE__ */ (0, _kubb_renderer_jsx_jsx_runtime.jsxs)(_kubb_renderer_jsx_jsx_runtime.Fragment, { children: [/* @__PURE__ */ (0, _kubb_renderer_jsx_jsx_runtime.jsx)(_kubb_renderer_jsx.File.Import, {
1142
- name: ["fetch"],
1283
+ name: ["client"],
1143
1284
  root: meta.file.path,
1144
1285
  path: node_path.default.resolve(root, ".kubb/client.ts")
1145
1286
  }), /* @__PURE__ */ (0, _kubb_renderer_jsx_jsx_runtime.jsx)(_kubb_renderer_jsx.File.Import, {
@@ -1152,7 +1293,7 @@ const clientGenerator = (0, _kubb_core.defineGenerator)({
1152
1293
  path: node_path.default.resolve(root, ".kubb/client.ts"),
1153
1294
  isTypeOnly: true
1154
1295
  })] }),
1155
- isFormData && node.requestBody?.content?.[0]?.schema && /* @__PURE__ */ (0, _kubb_renderer_jsx_jsx_runtime.jsx)(_kubb_renderer_jsx.File.Import, {
1296
+ hasFormData && /* @__PURE__ */ (0, _kubb_renderer_jsx_jsx_runtime.jsx)(_kubb_renderer_jsx.File.Import, {
1156
1297
  name: ["buildFormData"],
1157
1298
  root: meta.file.path,
1158
1299
  path: node_path.default.resolve(root, ".kubb/config.ts")
@@ -1198,16 +1339,22 @@ const clientGenerator = (0, _kubb_core.defineGenerator)({
1198
1339
  });
1199
1340
  //#endregion
1200
1341
  //#region src/generators/groupedClientGenerator.tsx
1342
+ /**
1343
+ * Emits one aggregate file per tag/group when `group` is configured. Each
1344
+ * file re-exports every client function for that group, so callers can
1345
+ * `import { petController } from './gen/clients'` instead of importing
1346
+ * each operation individually.
1347
+ */
1201
1348
  const groupedClientGenerator = (0, _kubb_core.defineGenerator)({
1202
1349
  name: "groupedClient",
1203
- renderer: _kubb_renderer_jsx.jsxRenderer,
1350
+ renderer: _kubb_renderer_jsx.jsxRendererSync,
1204
1351
  operations(nodes, ctx) {
1205
- const { config, resolver, adapter, root } = ctx;
1352
+ const { config, resolver, root } = ctx;
1206
1353
  const { output, group } = ctx.options;
1207
1354
  return /* @__PURE__ */ (0, _kubb_renderer_jsx_jsx_runtime.jsx)(_kubb_renderer_jsx_jsx_runtime.Fragment, { children: nodes.reduce((acc, operationNode) => {
1208
1355
  if (group?.type === "tag") {
1209
1356
  const tag = operationNode.tags[0];
1210
- const name = tag ? group?.name?.({ group: camelCase(tag) }) : void 0;
1357
+ const name = tag ? group?.name?.({ group: camelCase(tag) }) : null;
1211
1358
  if (!tag || !name) return acc;
1212
1359
  const file = resolver.resolveFile({
1213
1360
  name,
@@ -1216,7 +1363,7 @@ const groupedClientGenerator = (0, _kubb_core.defineGenerator)({
1216
1363
  }, {
1217
1364
  root,
1218
1365
  output,
1219
- group
1366
+ group: group ?? void 0
1220
1367
  });
1221
1368
  const clientFile = resolver.resolveFile({
1222
1369
  name: operationNode.operationId,
@@ -1226,7 +1373,7 @@ const groupedClientGenerator = (0, _kubb_core.defineGenerator)({
1226
1373
  }, {
1227
1374
  root,
1228
1375
  output,
1229
- group
1376
+ group: group ?? void 0
1230
1377
  });
1231
1378
  const client = {
1232
1379
  name: resolver.resolveName(operationNode.operationId),
@@ -1246,13 +1393,23 @@ const groupedClientGenerator = (0, _kubb_core.defineGenerator)({
1246
1393
  baseName: file.baseName,
1247
1394
  path: file.path,
1248
1395
  meta: file.meta,
1249
- banner: resolver.resolveBanner(adapter.inputNode, {
1396
+ banner: resolver.resolveBanner(ctx.meta, {
1250
1397
  output,
1251
- config
1398
+ config,
1399
+ file: {
1400
+ path: file.path,
1401
+ baseName: file.baseName,
1402
+ isAggregation: true
1403
+ }
1252
1404
  }),
1253
- footer: resolver.resolveFooter(adapter.inputNode, {
1405
+ footer: resolver.resolveFooter(ctx.meta, {
1254
1406
  output,
1255
- config
1407
+ config,
1408
+ file: {
1409
+ path: file.path,
1410
+ baseName: file.baseName,
1411
+ isAggregation: true
1412
+ }
1256
1413
  }),
1257
1414
  children: [clients.map((client) => /* @__PURE__ */ (0, _kubb_renderer_jsx_jsx_runtime.jsx)(_kubb_renderer_jsx.File.Import, {
1258
1415
  name: [client.name],
@@ -1295,11 +1452,17 @@ function Operations({ name, nodes }) {
1295
1452
  }
1296
1453
  //#endregion
1297
1454
  //#region src/generators/operationsGenerator.tsx
1455
+ /**
1456
+ * Generates an `operations.ts` file that re-exports every operation grouped
1457
+ * by HTTP method. Enabled when `pluginClient({ operations: true })`. Useful
1458
+ * for building meta-tooling on top of the generated client (route
1459
+ * registries, API explorers).
1460
+ */
1298
1461
  const operationsGenerator = (0, _kubb_core.defineGenerator)({
1299
1462
  name: "client",
1300
- renderer: _kubb_renderer_jsx.jsxRenderer,
1463
+ renderer: _kubb_renderer_jsx.jsxRendererSync,
1301
1464
  operations(nodes, ctx) {
1302
- const { config, resolver, adapter, root } = ctx;
1465
+ const { config, resolver, root } = ctx;
1303
1466
  const { output, group } = ctx.options;
1304
1467
  const name = "operations";
1305
1468
  const file = resolver.resolveFile({
@@ -1308,19 +1471,27 @@ const operationsGenerator = (0, _kubb_core.defineGenerator)({
1308
1471
  }, {
1309
1472
  root,
1310
1473
  output,
1311
- group
1474
+ group: group ?? void 0
1312
1475
  });
1313
1476
  return /* @__PURE__ */ (0, _kubb_renderer_jsx_jsx_runtime.jsx)(_kubb_renderer_jsx.File, {
1314
1477
  baseName: file.baseName,
1315
1478
  path: file.path,
1316
1479
  meta: file.meta,
1317
- banner: resolver.resolveBanner(adapter.inputNode, {
1480
+ banner: resolver.resolveBanner(ctx.meta, {
1318
1481
  output,
1319
- config
1482
+ config,
1483
+ file: {
1484
+ path: file.path,
1485
+ baseName: file.baseName
1486
+ }
1320
1487
  }),
1321
- footer: resolver.resolveFooter(adapter.inputNode, {
1488
+ footer: resolver.resolveFooter(ctx.meta, {
1322
1489
  output,
1323
- config
1490
+ config,
1491
+ file: {
1492
+ path: file.path,
1493
+ baseName: file.baseName
1494
+ }
1324
1495
  }),
1325
1496
  children: /* @__PURE__ */ (0, _kubb_renderer_jsx_jsx_runtime.jsx)(Operations, {
1326
1497
  name,
@@ -1334,11 +1505,13 @@ const operationsGenerator = (0, _kubb_core.defineGenerator)({
1334
1505
  const declarationPrinter = (0, _kubb_plugin_ts.functionPrinter)({ mode: "declaration" });
1335
1506
  function generateMethod({ node, name, tsResolver, zodResolver, baseURL, dataReturnType, parser, paramsType, paramsCasing, pathParamsType }) {
1336
1507
  const path = new URLPath(node.path, { casing: paramsCasing });
1337
- const contentType = node.requestBody?.content?.[0]?.contentType ?? "application/json";
1338
- const isFormData = contentType === "multipart/form-data";
1339
- const headers = buildHeaders(contentType, !!(node.parameters.filter((p) => p.in === "header").length > 0 ? tsResolver.resolveHeaderParamsName(node, node.parameters.filter((p) => p.in === "header")[0]) : void 0));
1508
+ const { defaultContentType: contentType, isMultipleContentTypes, hasFormData } = getContentTypeInfo(node);
1509
+ const isFormData = !isMultipleContentTypes && contentType === "multipart/form-data";
1510
+ const { header: headerParams } = getOperationParameters(node);
1511
+ const headerParamsName = headerParams.length > 0 ? tsResolver.resolveHeaderParamsName(node, headerParams[0]) : null;
1512
+ const headers = isMultipleContentTypes ? headerParamsName ? ["...headers"] : [] : buildHeaders(contentType, !!headerParamsName);
1340
1513
  const generics = buildGenerics(node, tsResolver);
1341
- const paramsNode = Client.getParams({
1514
+ const paramsNode = buildClientParamsNode({
1342
1515
  paramsType,
1343
1516
  paramsCasing,
1344
1517
  pathParamsType,
@@ -1353,15 +1526,21 @@ function generateMethod({ node, name, tsResolver, zodResolver, baseURL, dataRetu
1353
1526
  baseURL,
1354
1527
  tsResolver,
1355
1528
  isFormData,
1529
+ isMultipleContentTypes,
1530
+ hasFormData,
1356
1531
  headers
1357
1532
  });
1358
- const jsdoc = buildJSDoc(getComments(node));
1533
+ const jsdoc = buildJSDoc(buildOperationComments(node, {
1534
+ link: "urlPath",
1535
+ linkPosition: "beforeDeprecated",
1536
+ splitLines: true
1537
+ }));
1359
1538
  const requestDataLine = buildRequestDataLine({
1360
1539
  parser,
1361
1540
  node,
1362
1541
  zodResolver
1363
1542
  });
1364
- const formDataLine = buildFormDataLine(isFormData, !!node.requestBody?.content?.[0]?.schema);
1543
+ const formDataLine = buildFormDataLine(isFormData || isMultipleContentTypes && hasFormData, !!node.requestBody?.content?.[0]?.schema);
1365
1544
  const returnStatement = buildReturnStatement({
1366
1545
  dataReturnType,
1367
1546
  parser,
@@ -1369,7 +1548,7 @@ function generateMethod({ node, name, tsResolver, zodResolver, baseURL, dataRetu
1369
1548
  zodResolver
1370
1549
  });
1371
1550
  return `${jsdoc} static async ${name}(${paramsSignature}) {\n${[
1372
- "const { client: request = fetch, ...requestConfig } = mergeConfig(this.#config, config)",
1551
+ `const { client: request = client, ${isMultipleContentTypes ? `contentType = ${JSON.stringify(contentType)}, ` : ""}...requestConfig } = mergeConfig(this.#config, config)`,
1373
1552
  "",
1374
1553
  requestDataLine,
1375
1554
  formDataLine,
@@ -1397,35 +1576,33 @@ function StaticClassClient({ name, isExportable = true, isIndexable = true, oper
1397
1576
  children: [classCode, children]
1398
1577
  });
1399
1578
  }
1400
- StaticClassClient.getParams = Client.getParams;
1401
1579
  //#endregion
1402
1580
  //#region src/generators/staticClassClientGenerator.tsx
1403
1581
  function resolveTypeImportNames(node, tsResolver) {
1404
- return [
1405
- node.requestBody?.content?.[0]?.schema ? tsResolver.resolveDataName(node) : void 0,
1406
- tsResolver.resolveResponseName(node),
1407
- ...node.parameters.filter((p) => p.in === "path").map((p) => tsResolver.resolvePathParamsName(node, p)),
1408
- ...node.parameters.filter((p) => p.in === "query").map((p) => tsResolver.resolveQueryParamsName(node, p)),
1409
- ...node.parameters.filter((p) => p.in === "header").map((p) => tsResolver.resolveHeaderParamsName(node, p)),
1410
- ...node.responses.map((res) => tsResolver.resolveResponseStatusName(node, res.statusCode))
1411
- ].filter((n) => Boolean(n));
1582
+ return resolveOperationTypeNames(node, tsResolver, { order: "body-response-first" });
1412
1583
  }
1413
1584
  function resolveZodImportNames(node, zodResolver) {
1414
- return [zodResolver.resolveResponseName?.(node), node.requestBody?.content?.[0]?.schema ? zodResolver.resolveDataName?.(node) : void 0].filter((n) => Boolean(n));
1585
+ return [zodResolver.resolveResponseName?.(node), node.requestBody?.content?.[0]?.schema ? zodResolver.resolveDataName?.(node) : null].filter((n) => Boolean(n));
1415
1586
  }
1587
+ /**
1588
+ * Built-in `operations` generator for `@kubb/plugin-client` when
1589
+ * `clientType: 'staticClass'`. Emits one class per tag, with a static method
1590
+ * per operation so callers can use `Pet.getPetById(...)` without
1591
+ * instantiating the class.
1592
+ */
1416
1593
  const staticClassClientGenerator = (0, _kubb_core.defineGenerator)({
1417
1594
  name: "staticClassClient",
1418
- renderer: _kubb_renderer_jsx.jsxRenderer,
1595
+ renderer: _kubb_renderer_jsx.jsxRendererSync,
1419
1596
  operations(nodes, ctx) {
1420
- const { adapter, config, driver, resolver, root } = ctx;
1597
+ const { config, driver, resolver, root } = ctx;
1421
1598
  const { output, group, dataReturnType, paramsCasing, paramsType, pathParamsType, parser, importPath } = ctx.options;
1422
- const baseURL = ctx.options.baseURL ?? adapter.inputNode?.meta?.baseURL;
1599
+ const baseURL = ctx.options.baseURL ?? ctx.meta.baseURL;
1423
1600
  const pluginTs = driver.getPlugin(_kubb_plugin_ts.pluginTsName);
1424
1601
  if (!pluginTs) return null;
1425
1602
  const tsResolver = driver.getResolver(_kubb_plugin_ts.pluginTsName);
1426
1603
  const tsPluginOptions = pluginTs.options;
1427
- const pluginZod = parser === "zod" ? driver.getPlugin(_kubb_plugin_zod.pluginZodName) : void 0;
1428
- const zodResolver = pluginZod ? driver.getResolver(_kubb_plugin_zod.pluginZodName) : void 0;
1604
+ const pluginZod = parser === "zod" ? driver.getPlugin(_kubb_plugin_zod.pluginZodName) : null;
1605
+ const zodResolver = pluginZod ? driver.getResolver(_kubb_plugin_zod.pluginZodName) : null;
1429
1606
  function buildOperationData(node) {
1430
1607
  const typeFile = tsResolver.resolveFile({
1431
1608
  name: node.operationId,
@@ -1445,8 +1622,8 @@ const staticClassClientGenerator = (0, _kubb_core.defineGenerator)({
1445
1622
  }, {
1446
1623
  root,
1447
1624
  output: pluginZod.options?.output ?? output,
1448
- group: pluginZod.options?.group
1449
- }) : void 0;
1625
+ group: pluginZod.options?.group ?? void 0
1626
+ }) : null;
1450
1627
  return {
1451
1628
  node,
1452
1629
  name: resolver.resolveName(node.operationId),
@@ -1458,16 +1635,16 @@ const staticClassClientGenerator = (0, _kubb_core.defineGenerator)({
1458
1635
  }
1459
1636
  const controllers = nodes.reduce((acc, operationNode) => {
1460
1637
  const tag = operationNode.tags[0];
1461
- const groupName = tag ? group?.name?.({ group: camelCase(tag) }) ?? pascalCase(tag) : "Client";
1638
+ const groupName = tag ? group?.name?.({ group: camelCase(tag) }) ?? resolver.resolveGroupName(tag) : resolver.resolveGroupName("Client");
1462
1639
  if (!tag && !group) {
1463
- const name = "ApiClient";
1640
+ const name = resolver.resolveClassName("ApiClient");
1464
1641
  const file = resolver.resolveFile({
1465
1642
  name,
1466
1643
  extname: ".ts"
1467
1644
  }, {
1468
1645
  root,
1469
1646
  output,
1470
- group
1647
+ group: group ?? void 0
1471
1648
  });
1472
1649
  const operationData = buildOperationData(operationNode);
1473
1650
  const previous = acc.find((item) => item.file.path === file.path);
@@ -1477,7 +1654,9 @@ const staticClassClientGenerator = (0, _kubb_core.defineGenerator)({
1477
1654
  file,
1478
1655
  operations: [operationData]
1479
1656
  });
1480
- } else if (tag) {
1657
+ return acc;
1658
+ }
1659
+ if (tag) {
1481
1660
  const name = groupName;
1482
1661
  const file = resolver.resolveFile({
1483
1662
  name,
@@ -1486,7 +1665,7 @@ const staticClassClientGenerator = (0, _kubb_core.defineGenerator)({
1486
1665
  }, {
1487
1666
  root,
1488
1667
  output,
1489
- group
1668
+ group: group ?? void 0
1490
1669
  });
1491
1670
  const operationData = buildOperationData(operationNode);
1492
1671
  const previous = acc.find((item) => item.file.path === file.path);
@@ -1540,23 +1719,31 @@ const staticClassClientGenerator = (0, _kubb_core.defineGenerator)({
1540
1719
  zodImportsByFile: /* @__PURE__ */ new Map(),
1541
1720
  zodFilesByPath: /* @__PURE__ */ new Map()
1542
1721
  };
1543
- const hasFormData = ops.some((op) => op.node.requestBody?.content?.[0]?.contentType === "multipart/form-data");
1722
+ const hasFormData = ops.some((op) => op.node.requestBody?.content?.some((e) => e.contentType === "multipart/form-data") ?? false);
1544
1723
  return /* @__PURE__ */ (0, _kubb_renderer_jsx_jsx_runtime.jsxs)(_kubb_renderer_jsx.File, {
1545
1724
  baseName: file.baseName,
1546
1725
  path: file.path,
1547
1726
  meta: file.meta,
1548
- banner: resolver.resolveBanner(adapter.inputNode, {
1727
+ banner: resolver.resolveBanner(ctx.meta, {
1549
1728
  output,
1550
- config
1729
+ config,
1730
+ file: {
1731
+ path: file.path,
1732
+ baseName: file.baseName
1733
+ }
1551
1734
  }),
1552
- footer: resolver.resolveFooter(adapter.inputNode, {
1735
+ footer: resolver.resolveFooter(ctx.meta, {
1553
1736
  output,
1554
- config
1737
+ config,
1738
+ file: {
1739
+ path: file.path,
1740
+ baseName: file.baseName
1741
+ }
1555
1742
  }),
1556
1743
  children: [
1557
1744
  importPath ? /* @__PURE__ */ (0, _kubb_renderer_jsx_jsx_runtime.jsxs)(_kubb_renderer_jsx_jsx_runtime.Fragment, { children: [
1558
1745
  /* @__PURE__ */ (0, _kubb_renderer_jsx_jsx_runtime.jsx)(_kubb_renderer_jsx.File.Import, {
1559
- name: "fetch",
1746
+ name: "client",
1560
1747
  path: importPath
1561
1748
  }),
1562
1749
  /* @__PURE__ */ (0, _kubb_renderer_jsx_jsx_runtime.jsx)(_kubb_renderer_jsx.File.Import, {
@@ -1574,7 +1761,7 @@ const staticClassClientGenerator = (0, _kubb_core.defineGenerator)({
1574
1761
  })
1575
1762
  ] }) : /* @__PURE__ */ (0, _kubb_renderer_jsx_jsx_runtime.jsxs)(_kubb_renderer_jsx_jsx_runtime.Fragment, { children: [
1576
1763
  /* @__PURE__ */ (0, _kubb_renderer_jsx_jsx_runtime.jsx)(_kubb_renderer_jsx.File.Import, {
1577
- name: ["fetch"],
1764
+ name: ["client"],
1578
1765
  root: file.path,
1579
1766
  path: node_path.default.resolve(root, ".kubb/client.ts")
1580
1767
  }),
@@ -1640,39 +1827,75 @@ const staticClassClientGenerator = (0, _kubb_core.defineGenerator)({
1640
1827
  //#endregion
1641
1828
  //#region src/resolvers/resolverClient.ts
1642
1829
  /**
1643
- * Naming convention resolver for client plugin.
1830
+ * Default resolver used by `@kubb/plugin-client`. Decides the names and file
1831
+ * paths for every generated client function or class. Functions and files use
1832
+ * camelCase; classes and tag groups use PascalCase.
1644
1833
  *
1645
- * Provides default naming helpers using camelCase for functions and file paths.
1834
+ * @example Resolve client function and class names
1835
+ * ```ts
1836
+ * import { resolverClient } from '@kubb/plugin-client'
1646
1837
  *
1647
- * @example
1648
- * `resolverClient.default('list pets', 'function') // 'listPets'`
1838
+ * resolverClient.default('list pets', 'function') // 'listPets'
1839
+ * resolverClient.resolveClassName('pet') // 'Pet'
1840
+ * resolverClient.resolveUrlName(operationNode) // 'getShowPetByIdUrl'
1841
+ * ```
1649
1842
  */
1650
- const resolverClient = (0, _kubb_core.defineResolver)((ctx) => ({
1843
+ const resolverClient = (0, _kubb_core.defineResolver)(() => ({
1651
1844
  name: "default",
1652
1845
  pluginName: "plugin-client",
1653
1846
  default(name, type) {
1654
- return camelCase(name, { isFile: type === "file" });
1847
+ const resolved = camelCase(name, { isFile: type === "file" });
1848
+ return type === "file" ? resolved : ensureValidVarName(resolved);
1655
1849
  },
1656
1850
  resolveName(name) {
1657
- return ctx.default(name, "function");
1851
+ return this.default(name, "function");
1852
+ },
1853
+ resolvePathName(name, type) {
1854
+ return this.default(name, type);
1855
+ },
1856
+ resolveClassName(name) {
1857
+ return ensureValidVarName(pascalCase(name));
1858
+ },
1859
+ resolveGroupName(name) {
1860
+ return ensureValidVarName(pascalCase(name));
1861
+ },
1862
+ resolveClientPropertyName(name) {
1863
+ return ensureValidVarName(camelCase(name));
1864
+ },
1865
+ resolveUrlName(node) {
1866
+ const name = this.resolveName(node.operationId);
1867
+ return `get${name.charAt(0).toUpperCase()}${name.slice(1)}Url`;
1658
1868
  }
1659
1869
  }));
1660
1870
  //#endregion
1661
1871
  //#region src/plugin.ts
1662
1872
  /**
1663
- * Canonical plugin name for `@kubb/plugin-client`, used in driver lookups and warnings.
1873
+ * Canonical plugin name for `@kubb/plugin-client`. Used for driver lookups and
1874
+ * cross-plugin dependency references.
1664
1875
  */
1665
1876
  const pluginClientName = "plugin-client";
1666
1877
  /**
1667
- * Generates type-safe HTTP client functions or classes from an OpenAPI specification.
1668
- * Creates client APIs by walking operations and delegating to generators.
1669
- * Writes barrel files based on the configured `barrelType`.
1878
+ * Generates one HTTP client function per OpenAPI operation. Each function has
1879
+ * typed path params, query params, body, and response, so callers use the API
1880
+ * like any other typed function. Ships with `axios` and `fetch` runtimes; bring
1881
+ * your own by setting `importPath`.
1670
1882
  *
1671
- * @example Client generator
1883
+ * @example
1672
1884
  * ```ts
1673
- * import pluginClient from '@kubb/plugin-client'
1885
+ * import { defineConfig } from 'kubb'
1886
+ * import { pluginTs } from '@kubb/plugin-ts'
1887
+ * import { pluginClient } from '@kubb/plugin-client'
1888
+ *
1674
1889
  * export default defineConfig({
1675
- * plugins: [pluginClient({ output: { path: 'clients' } })]
1890
+ * input: { path: './petStore.yaml' },
1891
+ * output: { path: './src/gen' },
1892
+ * plugins: [
1893
+ * pluginTs(),
1894
+ * pluginClient({
1895
+ * output: { path: './clients' },
1896
+ * client: 'fetch',
1897
+ * }),
1898
+ * ],
1676
1899
  * })
1677
1900
  * ```
1678
1901
  */
@@ -1684,8 +1907,8 @@ const pluginClient = (0, _kubb_core.definePlugin)((options) => {
1684
1907
  const resolvedImportPath = importPath ?? (!bundle ? `@kubb/plugin-client/clients/${client}` : void 0);
1685
1908
  const selectedGenerators = options.generators ?? [
1686
1909
  clientType === "staticClass" ? staticClassClientGenerator : clientType === "class" ? classClientGenerator : clientGenerator,
1687
- group && clientType === "function" ? groupedClientGenerator : void 0,
1688
- operations ? operationsGenerator : void 0
1910
+ group && clientType === "function" ? groupedClientGenerator : null,
1911
+ operations ? operationsGenerator : null
1689
1912
  ].filter((x) => Boolean(x));
1690
1913
  const groupConfig = group ? {
1691
1914
  ...group,
@@ -1693,11 +1916,11 @@ const pluginClient = (0, _kubb_core.definePlugin)((options) => {
1693
1916
  if (group.type === "path") return `${ctx.group.split("/")[1]}`;
1694
1917
  return `${camelCase(ctx.group)}Controller`;
1695
1918
  }
1696
- } : void 0;
1919
+ } : null;
1697
1920
  return {
1698
1921
  name: pluginClientName,
1699
1922
  options,
1700
- dependencies: [_kubb_plugin_ts.pluginTsName, parser === "zod" ? _kubb_plugin_zod.pluginZodName : void 0].filter(Boolean),
1923
+ dependencies: [_kubb_plugin_ts.pluginTsName, parser === "zod" ? _kubb_plugin_zod.pluginZodName : null].filter((dependency) => Boolean(dependency)),
1701
1924
  hooks: { "kubb:plugin:setup"(ctx) {
1702
1925
  const resolver = userResolver ? {
1703
1926
  ...resolverClient,