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

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (43) 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 +76 -8
  8. package/dist/clients/fetch.cjs.map +1 -1
  9. package/dist/clients/fetch.d.ts +9 -2
  10. package/dist/clients/fetch.js +76 -8
  11. package/dist/clients/fetch.js.map +1 -1
  12. package/dist/index.cjs +627 -353
  13. package/dist/index.cjs.map +1 -1
  14. package/dist/index.d.ts +153 -86
  15. package/dist/index.js +628 -354
  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 +106 -6
  25. package/src/components/ClassClient.tsx +19 -20
  26. package/src/components/Client.tsx +74 -53
  27. package/src/components/Operations.tsx +2 -1
  28. package/src/components/StaticClassClient.tsx +19 -20
  29. package/src/components/Url.tsx +8 -9
  30. package/src/components/WrapperClient.tsx +9 -5
  31. package/src/functionParams.ts +8 -8
  32. package/src/generators/classClientGenerator.tsx +51 -47
  33. package/src/generators/clientGenerator.tsx +37 -48
  34. package/src/generators/groupedClientGenerator.tsx +14 -8
  35. package/src/generators/operationsGenerator.tsx +14 -8
  36. package/src/generators/staticClassClientGenerator.tsx +45 -41
  37. package/src/plugin.ts +27 -26
  38. package/src/resolvers/resolverClient.ts +31 -8
  39. package/src/types.ts +93 -55
  40. package/src/utils.ts +35 -56
  41. package/templates/clients/axios.ts +0 -73
  42. package/templates/clients/fetch.ts +0 -96
  43. 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,198 @@ var URLPath = class {
338
359
  }
339
360
  };
340
361
  //#endregion
362
+ //#region ../../internals/shared/src/operation.ts
363
+ /**
364
+ * Builds the `ResolverFileParams` every operation generator passes to
365
+ * `resolver.resolveFile`: a file named `name`, tagged by the operation's first
366
+ * tag (or `'default'`), at the operation's path. Centralizes the entry object
367
+ * that was repeated at dozens of call sites across the client and query plugins.
368
+ *
369
+ * @example
370
+ * ```ts
371
+ * resolver.resolveFile(operationFileEntry(node, node.operationId), { root, output, group })
372
+ * ```
373
+ */
374
+ function operationFileEntry(node, name, extname = ".ts") {
375
+ return {
376
+ name,
377
+ extname,
378
+ tag: node.tags[0] ?? "default",
379
+ path: node.path
380
+ };
381
+ }
382
+ function getOperationLink(node, link) {
383
+ if (!link) return null;
384
+ if (typeof link === "function") return link(node) ?? null;
385
+ if (link === "urlPath") return node.path ? `{@link ${new URLPath(node.path).URL}}` : null;
386
+ return node.path ? `{@link ${node.path.replaceAll("{", ":").replaceAll("}", "")}}` : null;
387
+ }
388
+ function getContentTypeInfo(node) {
389
+ const contentTypes = node.requestBody?.content?.map((e) => e.contentType) ?? [];
390
+ const isMultipleContentTypes = contentTypes.length > 1;
391
+ return {
392
+ contentTypes,
393
+ isMultipleContentTypes,
394
+ contentTypeUnion: isMultipleContentTypes ? contentTypes.map((ct) => JSON.stringify(ct)).join(" | ") : "",
395
+ defaultContentType: contentTypes[0] ?? "application/json",
396
+ hasFormData: contentTypes.some((ct) => ct === "multipart/form-data")
397
+ };
398
+ }
399
+ /**
400
+ * Derives the default `responseType` for an operation from its primary success response.
401
+ *
402
+ * Returns a value only when that response declares a single non-JSON content type — a binary type
403
+ * (`application/octet-stream`, `application/pdf`, `image/*`, `audio/*`, `video/*`) maps to `'blob'`
404
+ * and other `text/*` maps to `'text'`. Otherwise `undefined`, leaving the runtime client's
405
+ * `Content-Type` auto-detection in charge.
406
+ */
407
+ function getResponseType(node) {
408
+ const contentTypes = getPrimarySuccessResponse(node)?.content?.map((entry) => entry.contentType) ?? [];
409
+ if (contentTypes.length !== 1) return void 0;
410
+ const baseType = contentTypes[0].split(";")[0].trim().toLowerCase();
411
+ if (baseType === "application/json" || baseType.endsWith("+json") || baseType === "text/json") return void 0;
412
+ if (baseType.startsWith("text/")) return "text";
413
+ if (baseType === "application/octet-stream" || baseType === "application/pdf" || /^(image|audio|video)\//.test(baseType)) return "blob";
414
+ }
415
+ function buildRequestConfigType(node, resolver) {
416
+ const requestName = node.requestBody?.content?.[0]?.schema ? resolver.resolveDataName(node) : null;
417
+ const { isMultipleContentTypes, contentTypeUnion } = getContentTypeInfo(node);
418
+ return `${requestName ? `Partial<RequestConfig<${requestName}>>` : "Partial<RequestConfig>"} & { ${["client?: Client", isMultipleContentTypes ? `contentType?: ${contentTypeUnion}` : null].filter(Boolean).join("; ")} }`;
419
+ }
420
+ function buildOperationComments(node, options = {}) {
421
+ const { link = "pathTemplate", linkPosition = "afterDeprecated", splitLines = false } = options;
422
+ const linkComment = getOperationLink(node, link);
423
+ const filteredComments = (linkPosition === "beforeDeprecated" ? [
424
+ node.description && `@description ${node.description}`,
425
+ node.summary && `@summary ${node.summary}`,
426
+ linkComment,
427
+ node.deprecated && "@deprecated"
428
+ ] : [
429
+ node.description && `@description ${node.description}`,
430
+ node.summary && `@summary ${node.summary}`,
431
+ node.deprecated && "@deprecated",
432
+ linkComment
433
+ ]).filter((comment) => Boolean(comment));
434
+ if (!splitLines) return filteredComments;
435
+ return filteredComments.flatMap((text) => text.split(/\r?\n/).map((line) => line.trim())).filter((comment) => Boolean(comment));
436
+ }
437
+ function getOperationParameters(node, options = {}) {
438
+ const params = _kubb_core.ast.caseParams(node.parameters, options.paramsCasing);
439
+ return {
440
+ path: params.filter((param) => param.in === "path"),
441
+ query: params.filter((param) => param.in === "query"),
442
+ header: params.filter((param) => param.in === "header"),
443
+ cookie: params.filter((param) => param.in === "cookie")
444
+ };
445
+ }
446
+ function getStatusCodeNumber(statusCode) {
447
+ const code = Number(statusCode);
448
+ return Number.isNaN(code) ? null : code;
449
+ }
450
+ function isSuccessStatusCode(statusCode) {
451
+ const code = getStatusCodeNumber(statusCode);
452
+ return code !== null && code >= 200 && code < 300;
453
+ }
454
+ function isErrorStatusCode(statusCode) {
455
+ const code = getStatusCodeNumber(statusCode);
456
+ return code !== null && code >= 400;
457
+ }
458
+ function getSuccessResponses(responses) {
459
+ return responses.filter((response) => isSuccessStatusCode(response.statusCode));
460
+ }
461
+ function getOperationSuccessResponses(node) {
462
+ return getSuccessResponses(node.responses);
463
+ }
464
+ function getPrimarySuccessResponse(node) {
465
+ return getOperationSuccessResponses(node)[0] ?? null;
466
+ }
467
+ function resolveErrorNames(node, resolver) {
468
+ return node.responses.filter((response) => isErrorStatusCode(response.statusCode)).map((response) => resolver.resolveResponseStatusName(node, response.statusCode));
469
+ }
470
+ function resolveSuccessNames(node, resolver) {
471
+ return node.responses.filter((response) => isSuccessStatusCode(response.statusCode)).map((response) => resolver.resolveResponseStatusName(node, response.statusCode));
472
+ }
473
+ function resolveStatusCodeNames(node, resolver) {
474
+ return node.responses.map((response) => resolver.resolveResponseStatusName(node, response.statusCode));
475
+ }
476
+ const typeNamesByResolver = /* @__PURE__ */ new WeakMap();
477
+ function resolveOperationTypeNames(node, resolver, options = {}) {
478
+ const cacheKey = `${node.operationId}\0${options.paramsCasing ?? ""}\0${options.order ?? ""}\0${options.responseStatusNames ?? ""}\0${(options.exclude ?? []).join(",")}`;
479
+ let byResolver = typeNamesByResolver.get(resolver);
480
+ if (byResolver) {
481
+ const cached = byResolver.get(cacheKey);
482
+ if (cached) return cached;
483
+ } else {
484
+ byResolver = /* @__PURE__ */ new Map();
485
+ typeNamesByResolver.set(resolver, byResolver);
486
+ }
487
+ const { path, query, header } = getOperationParameters(node, { paramsCasing: options.paramsCasing });
488
+ const responseStatusNames = options.responseStatusNames === "error" ? resolveErrorNames(node, resolver) : options.responseStatusNames === false ? [] : resolveStatusCodeNames(node, resolver);
489
+ const exclude = new Set(options.exclude ?? []);
490
+ const paramNames = [
491
+ ...path.map((param) => resolver.resolvePathParamsName(node, param)),
492
+ ...query.map((param) => resolver.resolveQueryParamsName(node, param)),
493
+ ...header.map((param) => resolver.resolveHeaderParamsName(node, param))
494
+ ];
495
+ const bodyAndResponseNames = [node.requestBody?.content?.[0]?.schema ? resolver.resolveDataName(node) : null, resolver.resolveResponseName(node)];
496
+ const result = (options.order === "body-response-first" ? [
497
+ ...bodyAndResponseNames,
498
+ ...paramNames,
499
+ ...responseStatusNames
500
+ ] : [
501
+ ...paramNames,
502
+ ...bodyAndResponseNames,
503
+ ...responseStatusNames
504
+ ]).filter((name) => Boolean(name) && !exclude.has(name));
505
+ byResolver.set(cacheKey, result);
506
+ return result;
507
+ }
508
+ //#endregion
509
+ //#region ../../internals/shared/src/group.ts
510
+ /**
511
+ * Builds the `group` config a Kubb plugin passes to `ctx.setOptions`, applying the
512
+ * shared default naming so every plugin groups output consistently:
513
+ *
514
+ * - `path` groups use the second path segment (`/pet/findByStatus` → `pet`).
515
+ * - other groups use `${camelCase(group)}${suffix}` (e.g. `petController`).
516
+ *
517
+ * Returns `null` when grouping is disabled, matching the per-plugin convention.
518
+ *
519
+ * @param group - The user-supplied group option, or `undefined` to disable grouping.
520
+ * @param options.suffix - Appended to non-`path` group names, e.g. `'Controller'` or `'Requests'`.
521
+ * @param options.honorName - When `true`, a user-provided `group.name` overrides the default namer.
522
+ *
523
+ * @example
524
+ * ```ts
525
+ * createGroupConfig(group, { suffix: 'Controller' }) // plugin-ts, plugin-zod
526
+ * createGroupConfig(group, { suffix: 'Controller', honorName: true }) // plugin-faker, plugin-client, …
527
+ * createGroupConfig(group, { suffix: 'Requests', honorName: true }) // plugin-cypress, plugin-mcp
528
+ * ```
529
+ */
530
+ function createGroupConfig(group, options) {
531
+ if (!group) return null;
532
+ const defaultName = (ctx) => {
533
+ if (group.type === "path") return `${ctx.group.split("/")[1]}`;
534
+ return `${camelCase(ctx.group)}${options.suffix}`;
535
+ };
536
+ return {
537
+ ...group,
538
+ name: options.honorName && group.name ? group.name : defaultName
539
+ };
540
+ }
541
+ //#endregion
542
+ //#region ../../internals/shared/src/params.ts
543
+ function buildParamsMapping(originalParams, mappedParams) {
544
+ const mapping = {};
545
+ let hasChanged = false;
546
+ originalParams.forEach((param, i) => {
547
+ const mappedName = mappedParams[i]?.name ?? param.name;
548
+ mapping[param.name] = mappedName;
549
+ if (param.name !== mappedName) hasChanged = true;
550
+ });
551
+ return hasChanged ? mapping : null;
552
+ }
553
+ //#endregion
341
554
  //#region src/functionParams.ts
342
555
  const declarationPrinter$4 = (0, _kubb_plugin_ts.functionPrinter)({ mode: "declaration" });
343
556
  const callPrinter = (0, _kubb_plugin_ts.functionPrinter)({ mode: "call" });
@@ -348,18 +561,18 @@ function createType(type) {
348
561
  return type ? _kubb_core.ast.createParamsType({
349
562
  variant: "reference",
350
563
  name: type
351
- }) : void 0;
564
+ }) : null;
352
565
  }
353
566
  function createDeclarationLeaf(name, spec) {
354
567
  if (spec.default !== void 0) return _kubb_core.ast.createFunctionParameter({
355
568
  name,
356
- type: createType(spec.type),
569
+ type: createType(spec.type) ?? void 0,
357
570
  default: spec.default,
358
571
  rest: spec.mode === "inlineSpread"
359
572
  });
360
573
  return _kubb_core.ast.createFunctionParameter({
361
574
  name,
362
- type: createType(spec.type),
575
+ type: createType(spec.type) ?? void 0,
363
576
  optional: !!spec.optional,
364
577
  rest: spec.mode === "inlineSpread"
365
578
  });
@@ -368,14 +581,14 @@ function createDeclarationParam(name, spec) {
368
581
  if (isGroup(spec)) return _kubb_core.ast.createParameterGroup({
369
582
  inline: spec.mode === "inlineSpread",
370
583
  default: spec.default,
371
- properties: Object.entries(spec.children).filter(([, child]) => child !== void 0).map(([childName, child]) => createDeclarationLeaf(childName, child))
584
+ properties: Object.entries(spec.children).filter(([, child]) => child != null).map(([childName, child]) => createDeclarationLeaf(childName, child))
372
585
  });
373
586
  return createDeclarationLeaf(name, spec);
374
587
  }
375
588
  function createCallParam(name, spec) {
376
589
  if (isGroup(spec)) return _kubb_core.ast.createParameterGroup({
377
590
  inline: spec.mode === "inlineSpread",
378
- properties: Object.entries(spec.children).filter(([, child]) => child !== void 0).map(([childName, child]) => _kubb_core.ast.createFunctionParameter({
591
+ properties: Object.entries(spec.children).filter(([, child]) => child != null).map(([childName, child]) => _kubb_core.ast.createFunctionParameter({
379
592
  name: child?.mode === "inlineSpread" ? spec.mode === "inlineSpread" ? child.value ?? childName : `...${child.value ?? childName}` : child?.value ? `${childName}: ${child.value}` : childName,
380
593
  rest: spec.mode === "inlineSpread" && child?.mode === "inlineSpread"
381
594
  }))
@@ -390,7 +603,7 @@ function createCallParam(name, spec) {
390
603
  * Returns utilities to output constructor signatures (`toConstructor()`) or call expressions (`toCall()`).
391
604
  */
392
605
  function createFunctionParams(params) {
393
- const entries = Object.entries(params).filter(([, spec]) => spec !== void 0);
606
+ const entries = Object.entries(params).filter(([, spec]) => spec != null);
394
607
  return {
395
608
  toConstructor() {
396
609
  return declarationPrinter$4.print(_kubb_core.ast.createFunctionParameters({ params: entries.map(([name, spec]) => createDeclarationParam(name, spec)) })) ?? "";
@@ -401,106 +614,9 @@ function createFunctionParams(params) {
401
614
  };
402
615
  }
403
616
  //#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
617
  //#region src/components/Url.tsx
502
618
  const declarationPrinter$3 = (0, _kubb_plugin_ts.functionPrinter)({ mode: "declaration" });
503
- function getParams$1({ paramsType, paramsCasing, pathParamsType, node, tsResolver }) {
619
+ function buildUrlParamsNode({ paramsType, paramsCasing, pathParamsType, node, tsResolver }) {
504
620
  const urlNode = {
505
621
  ...node,
506
622
  parameters: node.parameters.filter((p) => p.in === "path"),
@@ -513,10 +629,10 @@ function getParams$1({ paramsType, paramsCasing, pathParamsType, node, tsResolve
513
629
  resolver: tsResolver
514
630
  });
515
631
  }
516
- require_chunk.__name(getParams$1, "getParams");
517
632
  function Url({ name, isExportable = true, isIndexable = true, baseURL, paramsType, paramsCasing, pathParamsType, node, tsResolver }) {
633
+ if (!_kubb_core.ast.isHttpOperationNode(node)) return null;
518
634
  const path = new URLPath(node.path);
519
- const paramsNode = getParams$1({
635
+ const paramsNode = buildUrlParamsNode({
520
636
  paramsType,
521
637
  paramsCasing,
522
638
  pathParamsType,
@@ -524,9 +640,9 @@ function Url({ name, isExportable = true, isIndexable = true, baseURL, paramsTyp
524
640
  tsResolver
525
641
  });
526
642
  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;
643
+ const { path: originalPathParams } = getOperationParameters(node);
644
+ const { path: casedPathParams } = getOperationParameters(node, { paramsCasing });
645
+ const pathParamsMapping = paramsCasing ? buildParamsMapping(originalPathParams, casedPathParams) : null;
530
646
  return /* @__PURE__ */ (0, _kubb_renderer_jsx_jsx_runtime.jsx)(_kubb_renderer_jsx.File.Source, {
531
647
  name,
532
648
  isExportable,
@@ -548,56 +664,53 @@ function Url({ name, isExportable = true, isIndexable = true, baseURL, paramsTyp
548
664
  })
549
665
  });
550
666
  }
551
- Url.getParams = getParams$1;
552
667
  //#endregion
553
668
  //#region src/components/Client.tsx
554
669
  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;
670
+ function buildClientParamsNode({ paramsType, paramsCasing, pathParamsType, node, tsResolver, isConfigurable }) {
557
671
  return _kubb_core.ast.createOperationParams(node, {
558
672
  paramsType,
559
673
  pathParamsType: paramsType === "object" ? "object" : pathParamsType === "object" ? "object" : "inline",
560
674
  paramsCasing,
561
675
  resolver: tsResolver,
562
- extraParams: isConfigurable ? [_kubb_core.ast.createFunctionParameter({
676
+ extraParams: [...isConfigurable ? [_kubb_core.ast.createFunctionParameter({
563
677
  name: "config",
564
678
  type: _kubb_core.ast.createParamsType({
565
679
  variant: "reference",
566
- name: requestName ? `Partial<RequestConfig<${requestName}>> & { client?: Client }` : "Partial<RequestConfig> & { client?: Client }"
680
+ name: buildRequestConfigType(node, tsResolver)
567
681
  }),
568
682
  default: "{}"
569
- })] : []
683
+ })] : []]
570
684
  });
571
685
  }
572
686
  function Client({ name, isExportable = true, isIndexable = true, returnType, baseURL, dataReturnType, parser, paramsType, paramsCasing, pathParamsType, node, tsResolver, zodResolver, urlName, children, isConfigurable = true }) {
687
+ if (!_kubb_core.ast.isHttpOperationNode(node)) return null;
573
688
  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;
689
+ const { defaultContentType: contentType, isMultipleContentTypes, hasFormData } = getContentTypeInfo(node);
690
+ const isFormData = !isMultipleContentTypes && contentType === "multipart/form-data";
691
+ const responseType = getResponseType(node);
692
+ const { path: originalPathParams, query: originalQueryParams, header: originalHeaderParams } = getOperationParameters(node);
693
+ const { path: casedPathParams, query: casedQueryParams, header: casedHeaderParams } = getOperationParameters(node, { paramsCasing });
694
+ const pathParamsMapping = paramsCasing && !urlName ? buildParamsMapping(originalPathParams, casedPathParams) : null;
695
+ const queryParamsMapping = paramsCasing ? buildParamsMapping(originalQueryParams, casedQueryParams) : null;
696
+ const headerParamsMapping = paramsCasing ? buildParamsMapping(originalHeaderParams, casedHeaderParams) : null;
697
+ const requestName = node.requestBody?.content?.[0]?.schema ? tsResolver.resolveDataName(node) : null;
698
+ const successNames = resolveSuccessNames(node, tsResolver);
699
+ const responseName = successNames.length > 0 ? successNames.join(" | ") : tsResolver.resolveResponseName(node);
700
+ const queryParamsName = originalQueryParams.length > 0 ? tsResolver.resolveQueryParamsName(node, originalQueryParams[0]) : null;
701
+ 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;
591
704
  const errorNames = node.responses.filter((r) => {
592
705
  return Number.parseInt(r.statusCode, 10) >= 400;
593
706
  }).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);
707
+ const headers = [!isMultipleContentTypes && contentType !== "application/json" && contentType !== "multipart/form-data" ? `'Content-Type': '${contentType}'` : null, headerParamsName ? headerParamsMapping ? "...mappedHeaders" : "...headers" : null].filter(Boolean);
595
708
  const generics = [
596
709
  responseName,
597
710
  `ResponseErrorConfig<${errorNames.length > 0 ? errorNames.join(" | ") : "Error"}>`,
598
711
  requestName || "unknown"
599
712
  ].filter(Boolean);
600
- const paramsNode = getParams({
713
+ const paramsNode = buildClientParamsNode({
601
714
  paramsType,
602
715
  paramsCasing,
603
716
  pathParamsType,
@@ -606,7 +719,7 @@ function Client({ name, isExportable = true, isIndexable = true, returnType, bas
606
719
  isConfigurable
607
720
  });
608
721
  const paramsSignature = declarationPrinter$2.print(paramsNode) ?? "";
609
- const urlParamsNode = Url.getParams({
722
+ const urlParamsNode = buildUrlParamsNode({
610
723
  paramsType,
611
724
  paramsCasing,
612
725
  pathParamsType,
@@ -619,18 +732,20 @@ function Client({ name, isExportable = true, isIndexable = true, returnType, bas
619
732
  children: {
620
733
  method: { value: JSON.stringify(node.method.toUpperCase()) },
621
734
  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
735
+ baseURL: baseURL && !urlName ? { value: `\`${baseURL}\`` } : null,
736
+ params: queryParamsName ? queryParamsMapping ? { value: "mappedParams" } : {} : null,
737
+ data: requestName ? { value: isMultipleContentTypes && hasFormData ? "contentType === 'multipart/form-data' ? formData as FormData : requestData" : isFormData ? "formData as FormData" : "requestData" } : null,
738
+ contentType: isConfigurable && isMultipleContentTypes ? {} : null,
739
+ responseType: responseType ? { value: JSON.stringify(responseType) } : null,
740
+ requestConfig: isConfigurable ? { mode: "inlineSpread" } : null,
741
+ headers: headers.length ? { value: isConfigurable ? `{ ${headers.join(", ")}, ...requestConfig.headers }` : `{ ${headers.join(", ")} }` } : null
627
742
  }
628
743
  } });
629
744
  const childrenElement = children ? children : /* @__PURE__ */ (0, _kubb_renderer_jsx_jsx_runtime.jsxs)(_kubb_renderer_jsx_jsx_runtime.Fragment, { children: [
630
745
  dataReturnType === "full" && parser === "zod" && zodResponseName && `return {...res, data: ${zodResponseName}.parse(res.data)}`,
631
746
  dataReturnType === "data" && parser === "zod" && zodResponseName && `return ${zodResponseName}.parse(res.data)`,
632
- dataReturnType === "full" && parser === "client" && "return res",
633
- dataReturnType === "data" && parser === "client" && "return res.data"
747
+ dataReturnType === "full" && parser !== "zod" && "return res",
748
+ dataReturnType === "data" && parser !== "zod" && "return res.data"
634
749
  ] });
635
750
  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, {
636
751
  name,
@@ -641,10 +756,14 @@ function Client({ name, isExportable = true, isIndexable = true, returnType, bas
641
756
  async: true,
642
757
  export: isExportable,
643
758
  params: paramsSignature,
644
- JSDoc: { comments: getComments(node) },
759
+ JSDoc: { comments: buildOperationComments(node, {
760
+ link: "urlPath",
761
+ linkPosition: "beforeDeprecated",
762
+ splitLines: true
763
+ }) },
645
764
  returnType,
646
765
  children: [
647
- isConfigurable ? "const { client: request = fetch, ...requestConfig } = config" : "",
766
+ isConfigurable ? `const { client: request = client, ${isMultipleContentTypes ? `contentType = ${JSON.stringify(contentType)}, ` : ""}...requestConfig } = config` : "",
648
767
  /* @__PURE__ */ (0, _kubb_renderer_jsx_jsx_runtime.jsx)("br", {}),
649
768
  /* @__PURE__ */ (0, _kubb_renderer_jsx_jsx_runtime.jsx)("br", {}),
650
769
  pathParamsMapping && Object.entries(pathParamsMapping).filter(([originalName, camelCaseName]) => isValidVarName(originalName) && originalName !== camelCaseName).map(([originalName, camelCaseName]) => `const ${originalName} = ${camelCaseName}`).join("\n"),
@@ -661,26 +780,104 @@ function Client({ name, isExportable = true, isIndexable = true, returnType, bas
661
780
  ] }),
662
781
  parser === "zod" && zodRequestName ? `const requestData = ${zodRequestName}.parse(data)` : requestName && "const requestData = data",
663
782
  /* @__PURE__ */ (0, _kubb_renderer_jsx_jsx_runtime.jsx)("br", {}),
664
- isFormData && requestName && "const formData = buildFormData(requestData)",
783
+ (isFormData || isMultipleContentTypes && hasFormData) && requestName && "const formData = buildFormData(requestData)",
665
784
  /* @__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()})`,
785
+ isConfigurable ? `const res = await request<${generics.join(", ")}>(${clientParams.toCall()})` : `const res = await client<${generics.join(", ")}>(${clientParams.toCall()})`,
667
786
  /* @__PURE__ */ (0, _kubb_renderer_jsx_jsx_runtime.jsx)("br", {}),
668
787
  childrenElement
669
788
  ]
670
789
  })
671
790
  })] });
672
791
  }
673
- Client.getParams = getParams;
792
+ //#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
+ }
674
868
  //#endregion
675
869
  //#region src/components/ClassClient.tsx
676
870
  const declarationPrinter$1 = (0, _kubb_plugin_ts.functionPrinter)({ mode: "declaration" });
677
871
  function generateMethod$1({ node, name, tsResolver, zodResolver, baseURL, dataReturnType, parser, paramsType, paramsCasing, pathParamsType }) {
872
+ if (!_kubb_core.ast.isHttpOperationNode(node)) return "";
678
873
  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));
874
+ const { defaultContentType: contentType, isMultipleContentTypes, hasFormData } = getContentTypeInfo(node);
875
+ const isFormData = !isMultipleContentTypes && contentType === "multipart/form-data";
876
+ const { header: headerParams } = getOperationParameters(node);
877
+ const headerParamsName = headerParams.length > 0 ? tsResolver.resolveHeaderParamsName(node, headerParams[0]) : null;
878
+ const headers = isMultipleContentTypes ? headerParamsName ? ["...headers"] : [] : buildHeaders(contentType, !!headerParamsName);
682
879
  const generics = buildGenerics(node, tsResolver);
683
- const paramsNode = ClassClient.getParams({
880
+ const paramsNode = buildClientParamsNode({
684
881
  paramsType,
685
882
  paramsCasing,
686
883
  pathParamsType,
@@ -695,15 +892,21 @@ function generateMethod$1({ node, name, tsResolver, zodResolver, baseURL, dataRe
695
892
  baseURL,
696
893
  tsResolver,
697
894
  isFormData,
895
+ isMultipleContentTypes,
896
+ hasFormData,
698
897
  headers
699
898
  });
700
- const jsdoc = buildJSDoc(getComments(node));
899
+ const jsdoc = buildJSDoc(buildOperationComments(node, {
900
+ link: "urlPath",
901
+ linkPosition: "beforeDeprecated",
902
+ splitLines: true
903
+ }));
701
904
  const requestDataLine = buildRequestDataLine({
702
905
  parser,
703
906
  node,
704
907
  zodResolver
705
908
  });
706
- const formDataLine = buildFormDataLine(isFormData, !!node.requestBody?.content?.[0]?.schema);
909
+ const formDataLine = buildFormDataLine(isFormData || isMultipleContentTypes && hasFormData, !!node.requestBody?.content?.[0]?.schema);
707
910
  const returnStatement = buildReturnStatement({
708
911
  dataReturnType,
709
912
  parser,
@@ -711,7 +914,7 @@ function generateMethod$1({ node, name, tsResolver, zodResolver, baseURL, dataRe
711
914
  zodResolver
712
915
  });
713
916
  return `${jsdoc}async ${name}(${paramsSignature}) {\n${[
714
- "const { client: request = fetch, ...requestConfig } = mergeConfig(this.#config, config)",
917
+ `const { client: request = client, ${isMultipleContentTypes ? `contentType = ${JSON.stringify(contentType)}, ` : ""}...requestConfig } = mergeConfig(this.#config, config)`,
715
918
  "",
716
919
  requestDataLine,
717
920
  formDataLine,
@@ -748,15 +951,14 @@ ${operations.map(({ node, name: methodName, tsResolver, zodResolver }) => genera
748
951
  children: [classCode, children]
749
952
  });
750
953
  }
751
- ClassClient.getParams = Client.getParams;
752
954
  //#endregion
753
955
  //#region src/components/WrapperClient.tsx
754
- function WrapperClient({ name, classNames, isExportable = true, isIndexable = true }) {
956
+ function WrapperClient({ name, controllers, isExportable = true, isIndexable = true }) {
755
957
  const classCode = `export class ${name} {
756
- ${classNames.map((className) => ` readonly ${camelCase(className)}: ${className}`).join("\n")}
958
+ ${controllers.map(({ className, propertyName }) => ` readonly ${propertyName}: ${className}`).join("\n")}
757
959
 
758
960
  constructor(config: Partial<RequestConfig> & { client?: Client } = {}) {
759
- ${classNames.map((className) => ` this.${camelCase(className)} = new ${className}(config)`).join("\n")}
961
+ ${controllers.map(({ className, propertyName }) => ` this.${propertyName} = new ${className}(config)`).join("\n")}
760
962
  }
761
963
  }`;
762
964
  return /* @__PURE__ */ (0, _kubb_renderer_jsx_jsx_runtime.jsx)(_kubb_renderer_jsx.File.Source, {
@@ -769,54 +971,42 @@ ${classNames.map((className) => ` this.${camelCase(className)} = new ${classN
769
971
  //#endregion
770
972
  //#region src/generators/classClientGenerator.tsx
771
973
  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));
974
+ return resolveOperationTypeNames(node, tsResolver, { order: "body-response-first" });
780
975
  }
781
976
  require_chunk.__name(resolveTypeImportNames$1, "resolveTypeImportNames");
782
977
  function resolveZodImportNames$1(node, zodResolver) {
783
- return [zodResolver.resolveResponseName?.(node), node.requestBody?.content?.[0]?.schema ? zodResolver.resolveDataName?.(node) : void 0].filter((n) => Boolean(n));
978
+ return [zodResolver.resolveResponseName?.(node), node.requestBody?.content?.[0]?.schema ? zodResolver.resolveDataName?.(node) : null].filter((n) => Boolean(n));
784
979
  }
785
980
  require_chunk.__name(resolveZodImportNames$1, "resolveZodImportNames");
981
+ /**
982
+ * Built-in `operations` generator for `@kubb/plugin-client` when
983
+ * `clientType: 'class'`. Emits one class per tag, with one instance method
984
+ * per operation and a shared constructor for request configuration.
985
+ */
786
986
  const classClientGenerator = (0, _kubb_core.defineGenerator)({
787
987
  name: "classClient",
788
- renderer: _kubb_renderer_jsx.jsxRenderer,
988
+ renderer: _kubb_renderer_jsx.jsxRendererSync,
789
989
  operations(nodes, ctx) {
790
- const { adapter, config, driver, resolver, root } = ctx;
990
+ const { config, driver, resolver, root } = ctx;
791
991
  const { output, group, dataReturnType, paramsCasing, paramsType, pathParamsType, parser, importPath, sdk } = ctx.options;
792
- const baseURL = ctx.options.baseURL ?? adapter.inputNode?.meta?.baseURL;
992
+ const baseURL = ctx.options.baseURL ?? ctx.meta.baseURL;
793
993
  const pluginTs = driver.getPlugin(_kubb_plugin_ts.pluginTsName);
794
994
  if (!pluginTs) return null;
795
995
  const tsResolver = driver.getResolver(_kubb_plugin_ts.pluginTsName);
796
996
  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;
997
+ const pluginZod = parser === "zod" ? driver.getPlugin(_kubb_plugin_zod.pluginZodName) : null;
998
+ const zodResolver = pluginZod ? driver.getResolver(_kubb_plugin_zod.pluginZodName) : null;
799
999
  function buildOperationData(node) {
800
- const typeFile = tsResolver.resolveFile({
801
- name: node.operationId,
802
- extname: ".ts",
803
- tag: node.tags[0] ?? "default",
804
- path: node.path
805
- }, {
1000
+ const typeFile = tsResolver.resolveFile(operationFileEntry(node, node.operationId), {
806
1001
  root,
807
1002
  output: tsPluginOptions?.output ?? output,
808
1003
  group: tsPluginOptions?.group
809
1004
  });
810
- const zodFile = zodResolver && pluginZod?.options ? zodResolver.resolveFile({
811
- name: node.operationId,
812
- extname: ".ts",
813
- tag: node.tags[0] ?? "default",
814
- path: node.path
815
- }, {
1005
+ const zodFile = zodResolver && pluginZod?.options ? zodResolver.resolveFile(operationFileEntry(node, node.operationId), {
816
1006
  root,
817
1007
  output: pluginZod.options?.output ?? output,
818
- group: pluginZod.options?.group
819
- }) : void 0;
1008
+ group: pluginZod.options?.group ?? void 0
1009
+ }) : null;
820
1010
  return {
821
1011
  node,
822
1012
  name: resolver.resolveName(node.operationId),
@@ -827,27 +1017,31 @@ const classClientGenerator = (0, _kubb_core.defineGenerator)({
827
1017
  };
828
1018
  }
829
1019
  const controllers = nodes.reduce((acc, operationNode) => {
1020
+ if (!_kubb_core.ast.isHttpOperationNode(operationNode)) return acc;
830
1021
  const tag = operationNode.tags[0];
831
- const groupName = tag ? group?.name?.({ group: camelCase(tag) }) ?? pascalCase(tag) : "Client";
1022
+ const groupName = tag ? group?.name?.({ group: camelCase(tag) }) ?? resolver.resolveGroupName(tag) : resolver.resolveGroupName("Client");
832
1023
  if (!tag && !group) {
833
- const name = "ApiClient";
1024
+ const name = resolver.resolveClassName("ApiClient");
834
1025
  const file = resolver.resolveFile({
835
1026
  name,
836
1027
  extname: ".ts"
837
1028
  }, {
838
1029
  root,
839
1030
  output,
840
- group
1031
+ group: group ?? void 0
841
1032
  });
842
1033
  const operationData = buildOperationData(operationNode);
843
1034
  const previous = acc.find((item) => item.file.path === file.path);
844
1035
  if (previous) previous.operations.push(operationData);
845
1036
  else acc.push({
846
1037
  name,
1038
+ propertyName: resolver.resolveClientPropertyName(name),
847
1039
  file,
848
1040
  operations: [operationData]
849
1041
  });
850
- } else if (tag) {
1042
+ return acc;
1043
+ }
1044
+ if (tag) {
851
1045
  const name = groupName;
852
1046
  const file = resolver.resolveFile({
853
1047
  name,
@@ -856,13 +1050,14 @@ const classClientGenerator = (0, _kubb_core.defineGenerator)({
856
1050
  }, {
857
1051
  root,
858
1052
  output,
859
- group
1053
+ group: group ?? void 0
860
1054
  });
861
1055
  const operationData = buildOperationData(operationNode);
862
1056
  const previous = acc.find((item) => item.file.path === file.path);
863
1057
  if (previous) previous.operations.push(operationData);
864
1058
  else acc.push({
865
1059
  name,
1060
+ propertyName: resolver.resolveClientPropertyName(name),
866
1061
  file,
867
1062
  operations: [operationData]
868
1063
  });
@@ -910,23 +1105,31 @@ const classClientGenerator = (0, _kubb_core.defineGenerator)({
910
1105
  zodImportsByFile: /* @__PURE__ */ new Map(),
911
1106
  zodFilesByPath: /* @__PURE__ */ new Map()
912
1107
  };
913
- const hasFormData = ops.some((op) => op.node.requestBody?.content?.[0]?.contentType === "multipart/form-data");
1108
+ const hasFormData = ops.some((op) => op.node.requestBody?.content?.some((e) => e.contentType === "multipart/form-data") ?? false);
914
1109
  return /* @__PURE__ */ (0, _kubb_renderer_jsx_jsx_runtime.jsxs)(_kubb_renderer_jsx.File, {
915
1110
  baseName: file.baseName,
916
1111
  path: file.path,
917
1112
  meta: file.meta,
918
- banner: resolver.resolveBanner(adapter.inputNode, {
1113
+ banner: resolver.resolveBanner(ctx.meta, {
919
1114
  output,
920
- config
1115
+ config,
1116
+ file: {
1117
+ path: file.path,
1118
+ baseName: file.baseName
1119
+ }
921
1120
  }),
922
- footer: resolver.resolveFooter(adapter.inputNode, {
1121
+ footer: resolver.resolveFooter(ctx.meta, {
923
1122
  output,
924
- config
1123
+ config,
1124
+ file: {
1125
+ path: file.path,
1126
+ baseName: file.baseName
1127
+ }
925
1128
  }),
926
1129
  children: [
927
1130
  importPath ? /* @__PURE__ */ (0, _kubb_renderer_jsx_jsx_runtime.jsxs)(_kubb_renderer_jsx_jsx_runtime.Fragment, { children: [
928
1131
  /* @__PURE__ */ (0, _kubb_renderer_jsx_jsx_runtime.jsx)(_kubb_renderer_jsx.File.Import, {
929
- name: "fetch",
1132
+ name: "client",
930
1133
  path: importPath
931
1134
  }),
932
1135
  /* @__PURE__ */ (0, _kubb_renderer_jsx_jsx_runtime.jsx)(_kubb_renderer_jsx.File.Import, {
@@ -944,7 +1147,7 @@ const classClientGenerator = (0, _kubb_core.defineGenerator)({
944
1147
  })
945
1148
  ] }) : /* @__PURE__ */ (0, _kubb_renderer_jsx_jsx_runtime.jsxs)(_kubb_renderer_jsx_jsx_runtime.Fragment, { children: [
946
1149
  /* @__PURE__ */ (0, _kubb_renderer_jsx_jsx_runtime.jsx)(_kubb_renderer_jsx.File.Import, {
947
- name: ["fetch"],
1150
+ name: ["client"],
948
1151
  root: file.path,
949
1152
  path: node_path.default.resolve(root, ".kubb/client.ts")
950
1153
  }),
@@ -1012,19 +1215,27 @@ const classClientGenerator = (0, _kubb_core.defineGenerator)({
1012
1215
  }, {
1013
1216
  root,
1014
1217
  output,
1015
- group
1218
+ group: group ?? void 0
1016
1219
  });
1017
1220
  files.push(/* @__PURE__ */ (0, _kubb_renderer_jsx_jsx_runtime.jsxs)(_kubb_renderer_jsx.File, {
1018
1221
  baseName: sdkFile.baseName,
1019
1222
  path: sdkFile.path,
1020
1223
  meta: sdkFile.meta,
1021
- banner: resolver.resolveBanner(adapter.inputNode, {
1224
+ banner: resolver.resolveBanner(ctx.meta, {
1022
1225
  output,
1023
- config
1226
+ config,
1227
+ file: {
1228
+ path: sdkFile.path,
1229
+ baseName: sdkFile.baseName
1230
+ }
1024
1231
  }),
1025
- footer: resolver.resolveFooter(adapter.inputNode, {
1232
+ footer: resolver.resolveFooter(ctx.meta, {
1026
1233
  output,
1027
- config
1234
+ config,
1235
+ file: {
1236
+ path: sdkFile.path,
1237
+ baseName: sdkFile.baseName
1238
+ }
1028
1239
  }),
1029
1240
  children: [
1030
1241
  importPath ? /* @__PURE__ */ (0, _kubb_renderer_jsx_jsx_runtime.jsx)(_kubb_renderer_jsx.File.Import, {
@@ -1044,7 +1255,10 @@ const classClientGenerator = (0, _kubb_core.defineGenerator)({
1044
1255
  }, name)),
1045
1256
  /* @__PURE__ */ (0, _kubb_renderer_jsx_jsx_runtime.jsx)(WrapperClient, {
1046
1257
  name: sdk.className,
1047
- classNames: controllers.map(({ name }) => name)
1258
+ controllers: controllers.map(({ name, propertyName }) => ({
1259
+ className: name,
1260
+ propertyName
1261
+ }))
1048
1262
  })
1049
1263
  ]
1050
1264
  }, sdkFile.path));
@@ -1054,81 +1268,69 @@ const classClientGenerator = (0, _kubb_core.defineGenerator)({
1054
1268
  });
1055
1269
  //#endregion
1056
1270
  //#region src/generators/clientGenerator.tsx
1271
+ /**
1272
+ * Built-in operation generator for `@kubb/plugin-client`. Emits one async
1273
+ * function per OpenAPI operation, plus the matching URL helper. Used when
1274
+ * `clientType: 'function'` (the default).
1275
+ */
1057
1276
  const clientGenerator = (0, _kubb_core.defineGenerator)({
1058
1277
  name: "client",
1059
- renderer: _kubb_renderer_jsx.jsxRenderer,
1278
+ renderer: _kubb_renderer_jsx.jsxRendererSync,
1060
1279
  operation(node, ctx) {
1061
- const { adapter, config, driver, resolver, root } = ctx;
1280
+ if (!_kubb_core.ast.isHttpOperationNode(node)) return null;
1281
+ const { config, driver, resolver, root } = ctx;
1062
1282
  const { output, urlType, dataReturnType, paramsCasing, paramsType, pathParamsType, parser, importPath, group } = ctx.options;
1063
- const baseURL = ctx.options.baseURL ?? adapter.inputNode?.meta?.baseURL;
1283
+ const baseURL = ctx.options.baseURL ?? ctx.meta.baseURL;
1064
1284
  const pluginTs = driver.getPlugin(_kubb_plugin_ts.pluginTsName);
1065
1285
  if (!pluginTs) return null;
1066
1286
  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) : [];
1287
+ const pluginZod = parser === "zod" ? driver.getPlugin(_kubb_plugin_zod.pluginZodName) : null;
1288
+ const zodResolver = pluginZod ? driver.getResolver(_kubb_plugin_zod.pluginZodName) : null;
1289
+ 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)) : [];
1082
1291
  const meta = {
1083
1292
  name: resolver.resolveName(node.operationId),
1084
- urlName: `get${resolver.resolveName(node.operationId).charAt(0).toUpperCase()}${resolver.resolveName(node.operationId).slice(1)}Url`,
1085
- file: resolver.resolveFile({
1086
- name: node.operationId,
1087
- extname: ".ts",
1088
- tag: node.tags[0] ?? "default",
1089
- path: node.path
1090
- }, {
1293
+ urlName: resolver.resolveUrlName(node),
1294
+ file: resolver.resolveFile(operationFileEntry(node, node.operationId), {
1091
1295
  root,
1092
1296
  output,
1093
- group
1297
+ group: group ?? void 0
1094
1298
  }),
1095
- fileTs: tsResolver.resolveFile({
1096
- name: node.operationId,
1097
- extname: ".ts",
1098
- tag: node.tags[0] ?? "default",
1099
- path: node.path
1100
- }, {
1299
+ fileTs: tsResolver.resolveFile(operationFileEntry(node, node.operationId), {
1101
1300
  root,
1102
1301
  output: pluginTs.options?.output ?? output,
1103
- group: pluginTs.options?.group
1302
+ group: pluginTs.options?.group ?? void 0
1104
1303
  }),
1105
- fileZod: zodResolver && pluginZod?.options ? zodResolver.resolveFile({
1106
- name: node.operationId,
1107
- extname: ".ts",
1108
- tag: node.tags[0] ?? "default",
1109
- path: node.path
1110
- }, {
1304
+ fileZod: zodResolver && pluginZod?.options ? zodResolver.resolveFile(operationFileEntry(node, node.operationId), {
1111
1305
  root,
1112
1306
  output: pluginZod.options.output ?? output,
1113
- group: pluginZod.options?.group
1114
- }) : void 0
1307
+ group: pluginZod.options?.group ?? void 0
1308
+ }) : null
1115
1309
  };
1116
- const isFormData = node.requestBody?.content?.[0]?.contentType === "multipart/form-data";
1310
+ const hasFormData = node.requestBody?.content?.some((e) => e.contentType === "multipart/form-data") ?? false;
1117
1311
  return /* @__PURE__ */ (0, _kubb_renderer_jsx_jsx_runtime.jsxs)(_kubb_renderer_jsx.File, {
1118
1312
  baseName: meta.file.baseName,
1119
1313
  path: meta.file.path,
1120
1314
  meta: meta.file.meta,
1121
- banner: resolver.resolveBanner(adapter.inputNode, {
1315
+ banner: resolver.resolveBanner(ctx.meta, {
1122
1316
  output,
1123
- config
1317
+ config,
1318
+ file: {
1319
+ path: meta.file.path,
1320
+ baseName: meta.file.baseName
1321
+ }
1124
1322
  }),
1125
- footer: resolver.resolveFooter(adapter.inputNode, {
1323
+ footer: resolver.resolveFooter(ctx.meta, {
1126
1324
  output,
1127
- config
1325
+ config,
1326
+ file: {
1327
+ path: meta.file.path,
1328
+ baseName: meta.file.baseName
1329
+ }
1128
1330
  }),
1129
1331
  children: [
1130
1332
  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",
1333
+ name: "client",
1132
1334
  path: importPath
1133
1335
  }), /* @__PURE__ */ (0, _kubb_renderer_jsx_jsx_runtime.jsx)(_kubb_renderer_jsx.File.Import, {
1134
1336
  name: [
@@ -1139,7 +1341,7 @@ const clientGenerator = (0, _kubb_core.defineGenerator)({
1139
1341
  path: importPath,
1140
1342
  isTypeOnly: true
1141
1343
  })] }) : /* @__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"],
1344
+ name: ["client"],
1143
1345
  root: meta.file.path,
1144
1346
  path: node_path.default.resolve(root, ".kubb/client.ts")
1145
1347
  }), /* @__PURE__ */ (0, _kubb_renderer_jsx_jsx_runtime.jsx)(_kubb_renderer_jsx.File.Import, {
@@ -1152,7 +1354,7 @@ const clientGenerator = (0, _kubb_core.defineGenerator)({
1152
1354
  path: node_path.default.resolve(root, ".kubb/client.ts"),
1153
1355
  isTypeOnly: true
1154
1356
  })] }),
1155
- isFormData && node.requestBody?.content?.[0]?.schema && /* @__PURE__ */ (0, _kubb_renderer_jsx_jsx_runtime.jsx)(_kubb_renderer_jsx.File.Import, {
1357
+ hasFormData && /* @__PURE__ */ (0, _kubb_renderer_jsx_jsx_runtime.jsx)(_kubb_renderer_jsx.File.Import, {
1156
1358
  name: ["buildFormData"],
1157
1359
  root: meta.file.path,
1158
1360
  path: node_path.default.resolve(root, ".kubb/config.ts")
@@ -1198,16 +1400,22 @@ const clientGenerator = (0, _kubb_core.defineGenerator)({
1198
1400
  });
1199
1401
  //#endregion
1200
1402
  //#region src/generators/groupedClientGenerator.tsx
1403
+ /**
1404
+ * Emits one aggregate file per tag/group when `group` is configured. Each
1405
+ * file re-exports every client function for that group, so callers can
1406
+ * `import { petController } from './gen/clients'` instead of importing
1407
+ * each operation individually.
1408
+ */
1201
1409
  const groupedClientGenerator = (0, _kubb_core.defineGenerator)({
1202
1410
  name: "groupedClient",
1203
- renderer: _kubb_renderer_jsx.jsxRenderer,
1411
+ renderer: _kubb_renderer_jsx.jsxRendererSync,
1204
1412
  operations(nodes, ctx) {
1205
- const { config, resolver, adapter, root } = ctx;
1413
+ const { config, resolver, root } = ctx;
1206
1414
  const { output, group } = ctx.options;
1207
1415
  return /* @__PURE__ */ (0, _kubb_renderer_jsx_jsx_runtime.jsx)(_kubb_renderer_jsx_jsx_runtime.Fragment, { children: nodes.reduce((acc, operationNode) => {
1208
1416
  if (group?.type === "tag") {
1209
1417
  const tag = operationNode.tags[0];
1210
- const name = tag ? group?.name?.({ group: camelCase(tag) }) : void 0;
1418
+ const name = tag ? group?.name?.({ group: camelCase(tag) }) : null;
1211
1419
  if (!tag || !name) return acc;
1212
1420
  const file = resolver.resolveFile({
1213
1421
  name,
@@ -1216,7 +1424,7 @@ const groupedClientGenerator = (0, _kubb_core.defineGenerator)({
1216
1424
  }, {
1217
1425
  root,
1218
1426
  output,
1219
- group
1427
+ group: group ?? void 0
1220
1428
  });
1221
1429
  const clientFile = resolver.resolveFile({
1222
1430
  name: operationNode.operationId,
@@ -1226,7 +1434,7 @@ const groupedClientGenerator = (0, _kubb_core.defineGenerator)({
1226
1434
  }, {
1227
1435
  root,
1228
1436
  output,
1229
- group
1437
+ group: group ?? void 0
1230
1438
  });
1231
1439
  const client = {
1232
1440
  name: resolver.resolveName(operationNode.operationId),
@@ -1246,13 +1454,23 @@ const groupedClientGenerator = (0, _kubb_core.defineGenerator)({
1246
1454
  baseName: file.baseName,
1247
1455
  path: file.path,
1248
1456
  meta: file.meta,
1249
- banner: resolver.resolveBanner(adapter.inputNode, {
1457
+ banner: resolver.resolveBanner(ctx.meta, {
1250
1458
  output,
1251
- config
1459
+ config,
1460
+ file: {
1461
+ path: file.path,
1462
+ baseName: file.baseName,
1463
+ isAggregation: true
1464
+ }
1252
1465
  }),
1253
- footer: resolver.resolveFooter(adapter.inputNode, {
1466
+ footer: resolver.resolveFooter(ctx.meta, {
1254
1467
  output,
1255
- config
1468
+ config,
1469
+ file: {
1470
+ path: file.path,
1471
+ baseName: file.baseName,
1472
+ isAggregation: true
1473
+ }
1256
1474
  }),
1257
1475
  children: [clients.map((client) => /* @__PURE__ */ (0, _kubb_renderer_jsx_jsx_runtime.jsx)(_kubb_renderer_jsx.File.Import, {
1258
1476
  name: [client.name],
@@ -1277,6 +1495,7 @@ const groupedClientGenerator = (0, _kubb_core.defineGenerator)({
1277
1495
  function Operations({ name, nodes }) {
1278
1496
  const operationsObject = {};
1279
1497
  nodes.forEach((node) => {
1498
+ if (!_kubb_core.ast.isHttpOperationNode(node)) return;
1280
1499
  operationsObject[node.operationId] = {
1281
1500
  path: new URLPath(node.path).URL,
1282
1501
  method: node.method.toLowerCase()
@@ -1295,11 +1514,17 @@ function Operations({ name, nodes }) {
1295
1514
  }
1296
1515
  //#endregion
1297
1516
  //#region src/generators/operationsGenerator.tsx
1517
+ /**
1518
+ * Generates an `operations.ts` file that re-exports every operation grouped
1519
+ * by HTTP method. Enabled when `pluginClient({ operations: true })`. Useful
1520
+ * for building meta-tooling on top of the generated client (route
1521
+ * registries, API explorers).
1522
+ */
1298
1523
  const operationsGenerator = (0, _kubb_core.defineGenerator)({
1299
1524
  name: "client",
1300
- renderer: _kubb_renderer_jsx.jsxRenderer,
1525
+ renderer: _kubb_renderer_jsx.jsxRendererSync,
1301
1526
  operations(nodes, ctx) {
1302
- const { config, resolver, adapter, root } = ctx;
1527
+ const { config, resolver, root } = ctx;
1303
1528
  const { output, group } = ctx.options;
1304
1529
  const name = "operations";
1305
1530
  const file = resolver.resolveFile({
@@ -1308,23 +1533,31 @@ const operationsGenerator = (0, _kubb_core.defineGenerator)({
1308
1533
  }, {
1309
1534
  root,
1310
1535
  output,
1311
- group
1536
+ group: group ?? void 0
1312
1537
  });
1313
1538
  return /* @__PURE__ */ (0, _kubb_renderer_jsx_jsx_runtime.jsx)(_kubb_renderer_jsx.File, {
1314
1539
  baseName: file.baseName,
1315
1540
  path: file.path,
1316
1541
  meta: file.meta,
1317
- banner: resolver.resolveBanner(adapter.inputNode, {
1542
+ banner: resolver.resolveBanner(ctx.meta, {
1318
1543
  output,
1319
- config
1544
+ config,
1545
+ file: {
1546
+ path: file.path,
1547
+ baseName: file.baseName
1548
+ }
1320
1549
  }),
1321
- footer: resolver.resolveFooter(adapter.inputNode, {
1550
+ footer: resolver.resolveFooter(ctx.meta, {
1322
1551
  output,
1323
- config
1552
+ config,
1553
+ file: {
1554
+ path: file.path,
1555
+ baseName: file.baseName
1556
+ }
1324
1557
  }),
1325
1558
  children: /* @__PURE__ */ (0, _kubb_renderer_jsx_jsx_runtime.jsx)(Operations, {
1326
1559
  name,
1327
- nodes
1560
+ nodes: nodes.filter(_kubb_core.ast.isHttpOperationNode)
1328
1561
  })
1329
1562
  });
1330
1563
  }
@@ -1333,12 +1566,15 @@ const operationsGenerator = (0, _kubb_core.defineGenerator)({
1333
1566
  //#region src/components/StaticClassClient.tsx
1334
1567
  const declarationPrinter = (0, _kubb_plugin_ts.functionPrinter)({ mode: "declaration" });
1335
1568
  function generateMethod({ node, name, tsResolver, zodResolver, baseURL, dataReturnType, parser, paramsType, paramsCasing, pathParamsType }) {
1569
+ if (!_kubb_core.ast.isHttpOperationNode(node)) return "";
1336
1570
  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));
1571
+ const { defaultContentType: contentType, isMultipleContentTypes, hasFormData } = getContentTypeInfo(node);
1572
+ const isFormData = !isMultipleContentTypes && contentType === "multipart/form-data";
1573
+ const { header: headerParams } = getOperationParameters(node);
1574
+ const headerParamsName = headerParams.length > 0 ? tsResolver.resolveHeaderParamsName(node, headerParams[0]) : null;
1575
+ const headers = isMultipleContentTypes ? headerParamsName ? ["...headers"] : [] : buildHeaders(contentType, !!headerParamsName);
1340
1576
  const generics = buildGenerics(node, tsResolver);
1341
- const paramsNode = Client.getParams({
1577
+ const paramsNode = buildClientParamsNode({
1342
1578
  paramsType,
1343
1579
  paramsCasing,
1344
1580
  pathParamsType,
@@ -1353,15 +1589,21 @@ function generateMethod({ node, name, tsResolver, zodResolver, baseURL, dataRetu
1353
1589
  baseURL,
1354
1590
  tsResolver,
1355
1591
  isFormData,
1592
+ isMultipleContentTypes,
1593
+ hasFormData,
1356
1594
  headers
1357
1595
  });
1358
- const jsdoc = buildJSDoc(getComments(node));
1596
+ const jsdoc = buildJSDoc(buildOperationComments(node, {
1597
+ link: "urlPath",
1598
+ linkPosition: "beforeDeprecated",
1599
+ splitLines: true
1600
+ }));
1359
1601
  const requestDataLine = buildRequestDataLine({
1360
1602
  parser,
1361
1603
  node,
1362
1604
  zodResolver
1363
1605
  });
1364
- const formDataLine = buildFormDataLine(isFormData, !!node.requestBody?.content?.[0]?.schema);
1606
+ const formDataLine = buildFormDataLine(isFormData || isMultipleContentTypes && hasFormData, !!node.requestBody?.content?.[0]?.schema);
1365
1607
  const returnStatement = buildReturnStatement({
1366
1608
  dataReturnType,
1367
1609
  parser,
@@ -1369,7 +1611,7 @@ function generateMethod({ node, name, tsResolver, zodResolver, baseURL, dataRetu
1369
1611
  zodResolver
1370
1612
  });
1371
1613
  return `${jsdoc} static async ${name}(${paramsSignature}) {\n${[
1372
- "const { client: request = fetch, ...requestConfig } = mergeConfig(this.#config, config)",
1614
+ `const { client: request = client, ${isMultipleContentTypes ? `contentType = ${JSON.stringify(contentType)}, ` : ""}...requestConfig } = mergeConfig(this.#config, config)`,
1373
1615
  "",
1374
1616
  requestDataLine,
1375
1617
  formDataLine,
@@ -1397,56 +1639,44 @@ function StaticClassClient({ name, isExportable = true, isIndexable = true, oper
1397
1639
  children: [classCode, children]
1398
1640
  });
1399
1641
  }
1400
- StaticClassClient.getParams = Client.getParams;
1401
1642
  //#endregion
1402
1643
  //#region src/generators/staticClassClientGenerator.tsx
1403
1644
  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));
1645
+ return resolveOperationTypeNames(node, tsResolver, { order: "body-response-first" });
1412
1646
  }
1413
1647
  function resolveZodImportNames(node, zodResolver) {
1414
- return [zodResolver.resolveResponseName?.(node), node.requestBody?.content?.[0]?.schema ? zodResolver.resolveDataName?.(node) : void 0].filter((n) => Boolean(n));
1648
+ return [zodResolver.resolveResponseName?.(node), node.requestBody?.content?.[0]?.schema ? zodResolver.resolveDataName?.(node) : null].filter((n) => Boolean(n));
1415
1649
  }
1650
+ /**
1651
+ * Built-in `operations` generator for `@kubb/plugin-client` when
1652
+ * `clientType: 'staticClass'`. Emits one class per tag, with a static method
1653
+ * per operation so callers can use `Pet.getPetById(...)` without
1654
+ * instantiating the class.
1655
+ */
1416
1656
  const staticClassClientGenerator = (0, _kubb_core.defineGenerator)({
1417
1657
  name: "staticClassClient",
1418
- renderer: _kubb_renderer_jsx.jsxRenderer,
1658
+ renderer: _kubb_renderer_jsx.jsxRendererSync,
1419
1659
  operations(nodes, ctx) {
1420
- const { adapter, config, driver, resolver, root } = ctx;
1660
+ const { config, driver, resolver, root } = ctx;
1421
1661
  const { output, group, dataReturnType, paramsCasing, paramsType, pathParamsType, parser, importPath } = ctx.options;
1422
- const baseURL = ctx.options.baseURL ?? adapter.inputNode?.meta?.baseURL;
1662
+ const baseURL = ctx.options.baseURL ?? ctx.meta.baseURL;
1423
1663
  const pluginTs = driver.getPlugin(_kubb_plugin_ts.pluginTsName);
1424
1664
  if (!pluginTs) return null;
1425
1665
  const tsResolver = driver.getResolver(_kubb_plugin_ts.pluginTsName);
1426
1666
  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;
1667
+ const pluginZod = parser === "zod" ? driver.getPlugin(_kubb_plugin_zod.pluginZodName) : null;
1668
+ const zodResolver = pluginZod ? driver.getResolver(_kubb_plugin_zod.pluginZodName) : null;
1429
1669
  function buildOperationData(node) {
1430
- const typeFile = tsResolver.resolveFile({
1431
- name: node.operationId,
1432
- extname: ".ts",
1433
- tag: node.tags[0] ?? "default",
1434
- path: node.path
1435
- }, {
1670
+ const typeFile = tsResolver.resolveFile(operationFileEntry(node, node.operationId), {
1436
1671
  root,
1437
1672
  output: tsPluginOptions?.output ?? output,
1438
1673
  group: tsPluginOptions?.group
1439
1674
  });
1440
- const zodFile = zodResolver && pluginZod?.options ? zodResolver.resolveFile({
1441
- name: node.operationId,
1442
- extname: ".ts",
1443
- tag: node.tags[0] ?? "default",
1444
- path: node.path
1445
- }, {
1675
+ const zodFile = zodResolver && pluginZod?.options ? zodResolver.resolveFile(operationFileEntry(node, node.operationId), {
1446
1676
  root,
1447
1677
  output: pluginZod.options?.output ?? output,
1448
- group: pluginZod.options?.group
1449
- }) : void 0;
1678
+ group: pluginZod.options?.group ?? void 0
1679
+ }) : null;
1450
1680
  return {
1451
1681
  node,
1452
1682
  name: resolver.resolveName(node.operationId),
@@ -1457,17 +1687,18 @@ const staticClassClientGenerator = (0, _kubb_core.defineGenerator)({
1457
1687
  };
1458
1688
  }
1459
1689
  const controllers = nodes.reduce((acc, operationNode) => {
1690
+ if (!_kubb_core.ast.isHttpOperationNode(operationNode)) return acc;
1460
1691
  const tag = operationNode.tags[0];
1461
- const groupName = tag ? group?.name?.({ group: camelCase(tag) }) ?? pascalCase(tag) : "Client";
1692
+ const groupName = tag ? group?.name?.({ group: camelCase(tag) }) ?? resolver.resolveGroupName(tag) : resolver.resolveGroupName("Client");
1462
1693
  if (!tag && !group) {
1463
- const name = "ApiClient";
1694
+ const name = resolver.resolveClassName("ApiClient");
1464
1695
  const file = resolver.resolveFile({
1465
1696
  name,
1466
1697
  extname: ".ts"
1467
1698
  }, {
1468
1699
  root,
1469
1700
  output,
1470
- group
1701
+ group: group ?? void 0
1471
1702
  });
1472
1703
  const operationData = buildOperationData(operationNode);
1473
1704
  const previous = acc.find((item) => item.file.path === file.path);
@@ -1477,7 +1708,9 @@ const staticClassClientGenerator = (0, _kubb_core.defineGenerator)({
1477
1708
  file,
1478
1709
  operations: [operationData]
1479
1710
  });
1480
- } else if (tag) {
1711
+ return acc;
1712
+ }
1713
+ if (tag) {
1481
1714
  const name = groupName;
1482
1715
  const file = resolver.resolveFile({
1483
1716
  name,
@@ -1486,7 +1719,7 @@ const staticClassClientGenerator = (0, _kubb_core.defineGenerator)({
1486
1719
  }, {
1487
1720
  root,
1488
1721
  output,
1489
- group
1722
+ group: group ?? void 0
1490
1723
  });
1491
1724
  const operationData = buildOperationData(operationNode);
1492
1725
  const previous = acc.find((item) => item.file.path === file.path);
@@ -1540,23 +1773,31 @@ const staticClassClientGenerator = (0, _kubb_core.defineGenerator)({
1540
1773
  zodImportsByFile: /* @__PURE__ */ new Map(),
1541
1774
  zodFilesByPath: /* @__PURE__ */ new Map()
1542
1775
  };
1543
- const hasFormData = ops.some((op) => op.node.requestBody?.content?.[0]?.contentType === "multipart/form-data");
1776
+ const hasFormData = ops.some((op) => op.node.requestBody?.content?.some((e) => e.contentType === "multipart/form-data") ?? false);
1544
1777
  return /* @__PURE__ */ (0, _kubb_renderer_jsx_jsx_runtime.jsxs)(_kubb_renderer_jsx.File, {
1545
1778
  baseName: file.baseName,
1546
1779
  path: file.path,
1547
1780
  meta: file.meta,
1548
- banner: resolver.resolveBanner(adapter.inputNode, {
1781
+ banner: resolver.resolveBanner(ctx.meta, {
1549
1782
  output,
1550
- config
1783
+ config,
1784
+ file: {
1785
+ path: file.path,
1786
+ baseName: file.baseName
1787
+ }
1551
1788
  }),
1552
- footer: resolver.resolveFooter(adapter.inputNode, {
1789
+ footer: resolver.resolveFooter(ctx.meta, {
1553
1790
  output,
1554
- config
1791
+ config,
1792
+ file: {
1793
+ path: file.path,
1794
+ baseName: file.baseName
1795
+ }
1555
1796
  }),
1556
1797
  children: [
1557
1798
  importPath ? /* @__PURE__ */ (0, _kubb_renderer_jsx_jsx_runtime.jsxs)(_kubb_renderer_jsx_jsx_runtime.Fragment, { children: [
1558
1799
  /* @__PURE__ */ (0, _kubb_renderer_jsx_jsx_runtime.jsx)(_kubb_renderer_jsx.File.Import, {
1559
- name: "fetch",
1800
+ name: "client",
1560
1801
  path: importPath
1561
1802
  }),
1562
1803
  /* @__PURE__ */ (0, _kubb_renderer_jsx_jsx_runtime.jsx)(_kubb_renderer_jsx.File.Import, {
@@ -1574,7 +1815,7 @@ const staticClassClientGenerator = (0, _kubb_core.defineGenerator)({
1574
1815
  })
1575
1816
  ] }) : /* @__PURE__ */ (0, _kubb_renderer_jsx_jsx_runtime.jsxs)(_kubb_renderer_jsx_jsx_runtime.Fragment, { children: [
1576
1817
  /* @__PURE__ */ (0, _kubb_renderer_jsx_jsx_runtime.jsx)(_kubb_renderer_jsx.File.Import, {
1577
- name: ["fetch"],
1818
+ name: ["client"],
1578
1819
  root: file.path,
1579
1820
  path: node_path.default.resolve(root, ".kubb/client.ts")
1580
1821
  }),
@@ -1640,39 +1881,75 @@ const staticClassClientGenerator = (0, _kubb_core.defineGenerator)({
1640
1881
  //#endregion
1641
1882
  //#region src/resolvers/resolverClient.ts
1642
1883
  /**
1643
- * Naming convention resolver for client plugin.
1884
+ * Default resolver used by `@kubb/plugin-client`. Decides the names and file
1885
+ * paths for every generated client function or class. Functions and files use
1886
+ * camelCase; classes and tag groups use PascalCase.
1644
1887
  *
1645
- * Provides default naming helpers using camelCase for functions and file paths.
1888
+ * @example Resolve client function and class names
1889
+ * ```ts
1890
+ * import { resolverClient } from '@kubb/plugin-client'
1646
1891
  *
1647
- * @example
1648
- * `resolverClient.default('list pets', 'function') // 'listPets'`
1892
+ * resolverClient.default('list pets', 'function') // 'listPets'
1893
+ * resolverClient.resolveClassName('pet') // 'Pet'
1894
+ * resolverClient.resolveUrlName(operationNode) // 'getShowPetByIdUrl'
1895
+ * ```
1649
1896
  */
1650
- const resolverClient = (0, _kubb_core.defineResolver)((ctx) => ({
1897
+ const resolverClient = (0, _kubb_core.defineResolver)(() => ({
1651
1898
  name: "default",
1652
1899
  pluginName: "plugin-client",
1653
1900
  default(name, type) {
1654
- return camelCase(name, { isFile: type === "file" });
1901
+ const resolved = camelCase(name, { isFile: type === "file" });
1902
+ return type === "file" ? resolved : ensureValidVarName(resolved);
1655
1903
  },
1656
1904
  resolveName(name) {
1657
- return ctx.default(name, "function");
1905
+ return this.default(name, "function");
1906
+ },
1907
+ resolvePathName(name, type) {
1908
+ return this.default(name, type);
1909
+ },
1910
+ resolveClassName(name) {
1911
+ return ensureValidVarName(pascalCase(name));
1912
+ },
1913
+ resolveGroupName(name) {
1914
+ return ensureValidVarName(pascalCase(name));
1915
+ },
1916
+ resolveClientPropertyName(name) {
1917
+ return ensureValidVarName(camelCase(name));
1918
+ },
1919
+ resolveUrlName(node) {
1920
+ const name = this.resolveName(node.operationId);
1921
+ return `get${name.charAt(0).toUpperCase()}${name.slice(1)}Url`;
1658
1922
  }
1659
1923
  }));
1660
1924
  //#endregion
1661
1925
  //#region src/plugin.ts
1662
1926
  /**
1663
- * Canonical plugin name for `@kubb/plugin-client`, used in driver lookups and warnings.
1927
+ * Canonical plugin name for `@kubb/plugin-client`. Used for driver lookups and
1928
+ * cross-plugin dependency references.
1664
1929
  */
1665
1930
  const pluginClientName = "plugin-client";
1666
1931
  /**
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`.
1932
+ * Generates one HTTP client function per OpenAPI operation. Each function has
1933
+ * typed path params, query params, body, and response, so callers use the API
1934
+ * like any other typed function. Ships with `axios` and `fetch` runtimes; bring
1935
+ * your own by setting `importPath`.
1670
1936
  *
1671
- * @example Client generator
1937
+ * @example
1672
1938
  * ```ts
1673
- * import pluginClient from '@kubb/plugin-client'
1939
+ * import { defineConfig } from 'kubb'
1940
+ * import { pluginTs } from '@kubb/plugin-ts'
1941
+ * import { pluginClient } from '@kubb/plugin-client'
1942
+ *
1674
1943
  * export default defineConfig({
1675
- * plugins: [pluginClient({ output: { path: 'clients' } })]
1944
+ * input: { path: './petStore.yaml' },
1945
+ * output: { path: './src/gen' },
1946
+ * plugins: [
1947
+ * pluginTs(),
1948
+ * pluginClient({
1949
+ * output: { path: './clients' },
1950
+ * client: 'fetch',
1951
+ * }),
1952
+ * ],
1676
1953
  * })
1677
1954
  * ```
1678
1955
  */
@@ -1680,24 +1957,21 @@ const pluginClient = (0, _kubb_core.definePlugin)((options) => {
1680
1957
  const { output = {
1681
1958
  path: "clients",
1682
1959
  barrelType: "named"
1683
- }, 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 = "client", client = "axios", importPath, bundle = false, sdk, baseURL, resolver: userResolver, transformer: userTransformer } = options;
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;
1684
1961
  const resolvedImportPath = importPath ?? (!bundle ? `@kubb/plugin-client/clients/${client}` : void 0);
1685
1962
  const selectedGenerators = options.generators ?? [
1686
1963
  clientType === "staticClass" ? staticClassClientGenerator : clientType === "class" ? classClientGenerator : clientGenerator,
1687
- group && clientType === "function" ? groupedClientGenerator : void 0,
1688
- operations ? operationsGenerator : void 0
1964
+ group && clientType === "function" ? groupedClientGenerator : null,
1965
+ operations ? operationsGenerator : null
1689
1966
  ].filter((x) => Boolean(x));
1690
- const groupConfig = group ? {
1691
- ...group,
1692
- name: group.name ? group.name : (ctx) => {
1693
- if (group.type === "path") return `${ctx.group.split("/")[1]}`;
1694
- return `${camelCase(ctx.group)}Controller`;
1695
- }
1696
- } : void 0;
1967
+ const groupConfig = createGroupConfig(group, {
1968
+ suffix: "Controller",
1969
+ honorName: true
1970
+ });
1697
1971
  return {
1698
1972
  name: pluginClientName,
1699
1973
  options,
1700
- dependencies: [_kubb_plugin_ts.pluginTsName, parser === "zod" ? _kubb_plugin_zod.pluginZodName : void 0].filter(Boolean),
1974
+ dependencies: [_kubb_plugin_ts.pluginTsName, parser === "zod" ? _kubb_plugin_zod.pluginZodName : null].filter((dependency) => Boolean(dependency)),
1701
1975
  hooks: { "kubb:plugin:setup"(ctx) {
1702
1976
  const resolver = userResolver ? {
1703
1977
  ...resolverClient,