@kubb/plugin-client 5.0.0-alpha.9 → 5.0.0-beta.15

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 (62) hide show
  1. package/LICENSE +17 -10
  2. package/README.md +27 -7
  3. package/dist/clients/axios.cjs +10 -3
  4. package/dist/clients/axios.cjs.map +1 -1
  5. package/dist/clients/axios.d.ts +5 -4
  6. package/dist/clients/axios.js +9 -2
  7. package/dist/clients/axios.js.map +1 -1
  8. package/dist/clients/fetch.cjs +5 -2
  9. package/dist/clients/fetch.cjs.map +1 -1
  10. package/dist/clients/fetch.d.ts +3 -2
  11. package/dist/clients/fetch.js +5 -2
  12. package/dist/clients/fetch.js.map +1 -1
  13. package/dist/index.cjs +1830 -97
  14. package/dist/index.cjs.map +1 -1
  15. package/dist/index.d.ts +330 -4
  16. package/dist/index.js +1816 -95
  17. package/dist/index.js.map +1 -1
  18. package/dist/templates/clients/axios.source.cjs +1 -1
  19. package/dist/templates/clients/axios.source.js +1 -1
  20. package/dist/templates/clients/fetch.source.cjs +1 -1
  21. package/dist/templates/clients/fetch.source.js +1 -1
  22. package/extension.yaml +779 -0
  23. package/package.json +62 -85
  24. package/src/clients/axios.ts +19 -4
  25. package/src/clients/fetch.ts +10 -2
  26. package/src/components/ClassClient.tsx +46 -145
  27. package/src/components/Client.tsx +109 -139
  28. package/src/components/Operations.tsx +10 -10
  29. package/src/components/StaticClassClient.tsx +46 -142
  30. package/src/components/Url.tsx +38 -50
  31. package/src/components/WrapperClient.tsx +11 -7
  32. package/src/functionParams.ts +118 -0
  33. package/src/generators/classClientGenerator.tsx +143 -172
  34. package/src/generators/clientGenerator.tsx +83 -81
  35. package/src/generators/groupedClientGenerator.tsx +50 -52
  36. package/src/generators/operationsGenerator.tsx +11 -18
  37. package/src/generators/staticClassClientGenerator.tsx +172 -184
  38. package/src/index.ts +9 -2
  39. package/src/plugin.ts +115 -145
  40. package/src/resolvers/resolverClient.ts +38 -0
  41. package/src/types.ts +128 -44
  42. package/src/utils.ts +156 -0
  43. package/dist/StaticClassClient-By-aMAe4.cjs +0 -677
  44. package/dist/StaticClassClient-By-aMAe4.cjs.map +0 -1
  45. package/dist/StaticClassClient-CCn9g9eF.js +0 -636
  46. package/dist/StaticClassClient-CCn9g9eF.js.map +0 -1
  47. package/dist/components.cjs +0 -7
  48. package/dist/components.d.ts +0 -216
  49. package/dist/components.js +0 -2
  50. package/dist/generators-BYUJaeZP.js +0 -723
  51. package/dist/generators-BYUJaeZP.js.map +0 -1
  52. package/dist/generators-DTxD9FDY.cjs +0 -753
  53. package/dist/generators-DTxD9FDY.cjs.map +0 -1
  54. package/dist/generators.cjs +0 -7
  55. package/dist/generators.d.ts +0 -517
  56. package/dist/generators.js +0 -2
  57. package/dist/types-DBQdg-BV.d.ts +0 -169
  58. package/src/components/index.ts +0 -5
  59. package/src/generators/index.ts +0 -5
  60. package/templates/clients/axios.ts +0 -70
  61. package/templates/clients/fetch.ts +0 -93
  62. package/templates/config.ts +0 -43
package/dist/index.js CHANGED
@@ -1,124 +1,1845 @@
1
- import "./chunk--u3MIqq1.js";
2
- import { o as camelCase } from "./StaticClassClient-CCn9g9eF.js";
3
- import { a as classClientGenerator, i as clientGenerator, n as operationsGenerator, r as groupedClientGenerator, t as staticClassClientGenerator } from "./generators-BYUJaeZP.js";
1
+ import { t as __name } from "./chunk--u3MIqq1.js";
4
2
  import { source } from "./templates/clients/axios.source.js";
5
3
  import { source as source$1 } from "./templates/clients/fetch.source.js";
6
4
  import { source as source$2 } from "./templates/config.source.js";
7
5
  import path from "node:path";
8
- import { createPlugin, getBarrelFiles, getMode } from "@kubb/core";
9
- import { OperationGenerator, pluginOasName } from "@kubb/plugin-oas";
6
+ import { ast, defineGenerator, definePlugin, defineResolver } from "@kubb/core";
7
+ import { functionPrinter, pluginTsName } from "@kubb/plugin-ts";
8
+ import { Const, File, Function, jsxRendererSync } from "@kubb/renderer-jsx";
9
+ import { Fragment, jsx, jsxs } from "@kubb/renderer-jsx/jsx-runtime";
10
10
  import { pluginZodName } from "@kubb/plugin-zod";
11
+ //#region ../../internals/utils/src/casing.ts
12
+ /**
13
+ * Shared implementation for camelCase and PascalCase conversion.
14
+ * Splits on common word boundaries (spaces, hyphens, underscores, dots, slashes, colons)
15
+ * and capitalizes each word according to `pascal`.
16
+ *
17
+ * When `pascal` is `true` the first word is also capitalized (PascalCase), otherwise only subsequent words are.
18
+ */
19
+ function toCamelOrPascal(text, pascal) {
20
+ return text.trim().replace(/([a-z\d])([A-Z])/g, "$1 $2").replace(/([A-Z]+)([A-Z][a-z])/g, "$1 $2").replace(/(\d)([a-z])/g, "$1 $2").split(/[\s\-_./\\:]+/).filter(Boolean).map((word, i) => {
21
+ if (word.length > 1 && word === word.toUpperCase()) return word;
22
+ if (i === 0 && !pascal) return word.charAt(0).toLowerCase() + word.slice(1);
23
+ return word.charAt(0).toUpperCase() + word.slice(1);
24
+ }).join("").replace(/[^a-zA-Z0-9]/g, "");
25
+ }
26
+ /**
27
+ * Splits `text` on `.` and applies `transformPart` to each segment.
28
+ * The last segment receives `isLast = true`, all earlier segments receive `false`.
29
+ * Segments are joined with `/` to form a file path.
30
+ *
31
+ * Only splits on dots followed by a letter so that version numbers
32
+ * embedded in operationIds (e.g. `v2025.0`) are kept intact.
33
+ */
34
+ function applyToFileParts(text, transformPart) {
35
+ const parts = text.split(/\.(?=[a-zA-Z])/);
36
+ return parts.map((part, i) => transformPart(part, i === parts.length - 1)).join("/");
37
+ }
38
+ /**
39
+ * Converts `text` to camelCase.
40
+ * When `isFile` is `true`, dot-separated segments are each cased independently and joined with `/`.
41
+ *
42
+ * @example
43
+ * camelCase('hello-world') // 'helloWorld'
44
+ * camelCase('pet.petId', { isFile: true }) // 'pet/petId'
45
+ */
46
+ function camelCase(text, { isFile, prefix = "", suffix = "" } = {}) {
47
+ if (isFile) return applyToFileParts(text, (part, isLast) => camelCase(part, isLast ? {
48
+ prefix,
49
+ suffix
50
+ } : {}));
51
+ return toCamelOrPascal(`${prefix} ${text} ${suffix}`, false);
52
+ }
53
+ /**
54
+ * Converts `text` to PascalCase.
55
+ * When `isFile` is `true`, the last dot-separated segment is PascalCased and earlier segments are camelCased.
56
+ *
57
+ * @example
58
+ * pascalCase('hello-world') // 'HelloWorld'
59
+ * pascalCase('pet.petId', { isFile: true }) // 'pet/PetId'
60
+ */
61
+ function pascalCase(text, { isFile, prefix = "", suffix = "" } = {}) {
62
+ if (isFile) return applyToFileParts(text, (part, isLast) => isLast ? pascalCase(part, {
63
+ prefix,
64
+ suffix
65
+ }) : camelCase(part));
66
+ return toCamelOrPascal(`${prefix} ${text} ${suffix}`, true);
67
+ }
68
+ //#endregion
69
+ //#region ../../internals/utils/src/jsdoc.ts
70
+ /**
71
+ * Builds a JSDoc comment block from an array of lines.
72
+ * Returns `fallback` when `comments` is empty so callers always get a usable string.
73
+ *
74
+ * @example
75
+ * ```ts
76
+ * buildJSDoc(['@type string', '@example hello'])
77
+ * // '/**\n * @type string\n * @example hello\n *\/\n '
78
+ * ```
79
+ */
80
+ function buildJSDoc(comments, options = {}) {
81
+ const { indent = " * ", suffix = "\n ", fallback = " " } = options;
82
+ if (comments.length === 0) return fallback;
83
+ return `/**\n${comments.map((c) => `${indent}${c}`).join("\n")}\n */${suffix}`;
84
+ }
85
+ //#endregion
86
+ //#region ../../internals/utils/src/reserved.ts
87
+ /**
88
+ * JavaScript and Java reserved words.
89
+ * @link https://github.com/jonschlinkert/reserved/blob/master/index.js
90
+ */
91
+ const reservedWords = new Set([
92
+ "abstract",
93
+ "arguments",
94
+ "boolean",
95
+ "break",
96
+ "byte",
97
+ "case",
98
+ "catch",
99
+ "char",
100
+ "class",
101
+ "const",
102
+ "continue",
103
+ "debugger",
104
+ "default",
105
+ "delete",
106
+ "do",
107
+ "double",
108
+ "else",
109
+ "enum",
110
+ "eval",
111
+ "export",
112
+ "extends",
113
+ "false",
114
+ "final",
115
+ "finally",
116
+ "float",
117
+ "for",
118
+ "function",
119
+ "goto",
120
+ "if",
121
+ "implements",
122
+ "import",
123
+ "in",
124
+ "instanceof",
125
+ "int",
126
+ "interface",
127
+ "let",
128
+ "long",
129
+ "native",
130
+ "new",
131
+ "null",
132
+ "package",
133
+ "private",
134
+ "protected",
135
+ "public",
136
+ "return",
137
+ "short",
138
+ "static",
139
+ "super",
140
+ "switch",
141
+ "synchronized",
142
+ "this",
143
+ "throw",
144
+ "throws",
145
+ "transient",
146
+ "true",
147
+ "try",
148
+ "typeof",
149
+ "var",
150
+ "void",
151
+ "volatile",
152
+ "while",
153
+ "with",
154
+ "yield",
155
+ "Array",
156
+ "Date",
157
+ "hasOwnProperty",
158
+ "Infinity",
159
+ "isFinite",
160
+ "isNaN",
161
+ "isPrototypeOf",
162
+ "length",
163
+ "Math",
164
+ "name",
165
+ "NaN",
166
+ "Number",
167
+ "Object",
168
+ "prototype",
169
+ "String",
170
+ "toString",
171
+ "undefined",
172
+ "valueOf"
173
+ ]);
174
+ /**
175
+ * Returns `true` when `name` is a syntactically valid JavaScript variable name.
176
+ *
177
+ * @example
178
+ * ```ts
179
+ * isValidVarName('status') // true
180
+ * isValidVarName('class') // false (reserved word)
181
+ * isValidVarName('42foo') // false (starts with digit)
182
+ * ```
183
+ */
184
+ function isValidVarName(name) {
185
+ if (!name || reservedWords.has(name)) return false;
186
+ return /^[a-zA-Z_$][a-zA-Z0-9_$]*$/.test(name);
187
+ }
188
+ //#endregion
189
+ //#region ../../internals/utils/src/urlPath.ts
190
+ /**
191
+ * Parses and transforms an OpenAPI/Swagger path string into various URL formats.
192
+ *
193
+ * @example
194
+ * const p = new URLPath('/pet/{petId}')
195
+ * p.URL // '/pet/:petId'
196
+ * p.template // '`/pet/${petId}`'
197
+ */
198
+ var URLPath = class {
199
+ /**
200
+ * The raw OpenAPI/Swagger path string, e.g. `/pet/{petId}`.
201
+ */
202
+ path;
203
+ #options;
204
+ constructor(path, options = {}) {
205
+ this.path = path;
206
+ this.#options = options;
207
+ }
208
+ /** Converts the OpenAPI path to Express-style colon syntax, e.g. `/pet/{petId}` → `/pet/:petId`.
209
+ *
210
+ * @example
211
+ * ```ts
212
+ * new URLPath('/pet/{petId}').URL // '/pet/:petId'
213
+ * ```
214
+ */
215
+ get URL() {
216
+ return this.toURLPath();
217
+ }
218
+ /** Returns `true` when `path` is a fully-qualified URL (e.g. starts with `https://`).
219
+ *
220
+ * @example
221
+ * ```ts
222
+ * new URLPath('https://petstore.swagger.io/v2/pet').isURL // true
223
+ * new URLPath('/pet/{petId}').isURL // false
224
+ * ```
225
+ */
226
+ get isURL() {
227
+ try {
228
+ return !!new URL(this.path).href;
229
+ } catch {
230
+ return false;
231
+ }
232
+ }
233
+ /**
234
+ * Converts the OpenAPI path to a TypeScript template literal string.
235
+ *
236
+ * @example
237
+ * new URLPath('/pet/{petId}').template // '`/pet/${petId}`'
238
+ * new URLPath('/account/monetary-accountID').template // '`/account/${monetaryAccountId}`'
239
+ */
240
+ get template() {
241
+ return this.toTemplateString();
242
+ }
243
+ /** Returns the path and its extracted params as a structured `URLObject`, or as a stringified expression when `stringify` is set.
244
+ *
245
+ * @example
246
+ * ```ts
247
+ * new URLPath('/pet/{petId}').object
248
+ * // { url: '/pet/:petId', params: { petId: 'petId' } }
249
+ * ```
250
+ */
251
+ get object() {
252
+ return this.toObject();
253
+ }
254
+ /** Returns a map of path parameter names, or `undefined` when the path has no parameters.
255
+ *
256
+ * @example
257
+ * ```ts
258
+ * new URLPath('/pet/{petId}').params // { petId: 'petId' }
259
+ * new URLPath('/pet').params // undefined
260
+ * ```
261
+ */
262
+ get params() {
263
+ return this.toParamsObject();
264
+ }
265
+ #transformParam(raw) {
266
+ const param = isValidVarName(raw) ? raw : camelCase(raw);
267
+ return this.#options.casing === "camelcase" ? camelCase(param) : param;
268
+ }
269
+ /**
270
+ * Iterates over every `{param}` token in `path`, calling `fn` with the raw token and transformed name.
271
+ */
272
+ #eachParam(fn) {
273
+ for (const match of this.path.matchAll(/\{([^}]+)\}/g)) {
274
+ const raw = match[1];
275
+ fn(raw, this.#transformParam(raw));
276
+ }
277
+ }
278
+ toObject({ type = "path", replacer, stringify } = {}) {
279
+ const object = {
280
+ url: type === "path" ? this.toURLPath() : this.toTemplateString({ replacer }),
281
+ params: this.toParamsObject()
282
+ };
283
+ if (stringify) {
284
+ if (type === "template") return JSON.stringify(object).replaceAll("'", "").replaceAll(`"`, "");
285
+ if (object.params) return `{ url: '${object.url}', params: ${JSON.stringify(object.params).replaceAll("'", "").replaceAll(`"`, "")} }`;
286
+ return `{ url: '${object.url}' }`;
287
+ }
288
+ return object;
289
+ }
290
+ /**
291
+ * Converts the OpenAPI path to a TypeScript template literal string.
292
+ * An optional `replacer` can transform each extracted parameter name before interpolation.
293
+ *
294
+ * @example
295
+ * new URLPath('/pet/{petId}').toTemplateString() // '`/pet/${petId}`'
296
+ */
297
+ toTemplateString({ prefix = "", replacer } = {}) {
298
+ return `\`${prefix}${this.path.split(/\{([^}]+)\}/).map((part, i) => {
299
+ if (i % 2 === 0) return part;
300
+ const param = this.#transformParam(part);
301
+ return `\${${replacer ? replacer(param) : param}}`;
302
+ }).join("")}\``;
303
+ }
304
+ /**
305
+ * Extracts all `{param}` segments from the path and returns them as a key-value map.
306
+ * An optional `replacer` transforms each parameter name in both key and value positions.
307
+ * Returns `undefined` when no path parameters are found.
308
+ *
309
+ * @example
310
+ * ```ts
311
+ * new URLPath('/pet/{petId}/tag/{tagId}').toParamsObject()
312
+ * // { petId: 'petId', tagId: 'tagId' }
313
+ * ```
314
+ */
315
+ toParamsObject(replacer) {
316
+ const params = {};
317
+ this.#eachParam((_raw, param) => {
318
+ const key = replacer ? replacer(param) : param;
319
+ params[key] = key;
320
+ });
321
+ return Object.keys(params).length > 0 ? params : void 0;
322
+ }
323
+ /** Converts the OpenAPI path to Express-style colon syntax.
324
+ *
325
+ * @example
326
+ * ```ts
327
+ * new URLPath('/pet/{petId}').toURLPath() // '/pet/:petId'
328
+ * ```
329
+ */
330
+ toURLPath() {
331
+ return this.path.replace(/\{([^}]+)\}/g, ":$1");
332
+ }
333
+ };
334
+ //#endregion
335
+ //#region ../../internals/shared/src/operation.ts
336
+ function getOperationLink(node, link) {
337
+ if (!link) return;
338
+ if (typeof link === "function") return link(node);
339
+ if (link === "urlPath") return node.path ? `{@link ${new URLPath(node.path).URL}}` : void 0;
340
+ return `{@link ${node.path.replaceAll("{", ":").replaceAll("}", "")}}`;
341
+ }
342
+ function getContentTypeInfo(node) {
343
+ const contentTypes = node.requestBody?.content?.map((e) => e.contentType) ?? [];
344
+ const isMultipleContentTypes = contentTypes.length > 1;
345
+ return {
346
+ contentTypes,
347
+ isMultipleContentTypes,
348
+ contentTypeUnion: isMultipleContentTypes ? contentTypes.map((ct) => JSON.stringify(ct)).join(" | ") : "",
349
+ defaultContentType: contentTypes[0] ?? "application/json",
350
+ hasFormData: contentTypes.some((ct) => ct === "multipart/form-data")
351
+ };
352
+ }
353
+ function buildRequestConfigType(node, resolver) {
354
+ const requestName = node.requestBody?.content?.[0]?.schema ? resolver.resolveDataName(node) : void 0;
355
+ const { isMultipleContentTypes, contentTypeUnion } = getContentTypeInfo(node);
356
+ return `${requestName ? `Partial<RequestConfig<${requestName}>>` : "Partial<RequestConfig>"} & { ${["client?: Client", isMultipleContentTypes ? `contentType?: ${contentTypeUnion}` : void 0].filter(Boolean).join("; ")} }`;
357
+ }
358
+ function buildOperationComments(node, options = {}) {
359
+ const { link = "pathTemplate", linkPosition = "afterDeprecated", splitLines = false } = options;
360
+ const linkComment = getOperationLink(node, link);
361
+ const filteredComments = (linkPosition === "beforeDeprecated" ? [
362
+ node.description && `@description ${node.description}`,
363
+ node.summary && `@summary ${node.summary}`,
364
+ linkComment,
365
+ node.deprecated && "@deprecated"
366
+ ] : [
367
+ node.description && `@description ${node.description}`,
368
+ node.summary && `@summary ${node.summary}`,
369
+ node.deprecated && "@deprecated",
370
+ linkComment
371
+ ]).filter((comment) => Boolean(comment));
372
+ if (!splitLines) return filteredComments;
373
+ return filteredComments.flatMap((text) => text.split(/\r?\n/).map((line) => line.trim())).filter((comment) => Boolean(comment));
374
+ }
375
+ function getOperationParameters(node, options = {}) {
376
+ const params = ast.caseParams(node.parameters, options.paramsCasing);
377
+ return {
378
+ path: params.filter((param) => param.in === "path"),
379
+ query: params.filter((param) => param.in === "query"),
380
+ header: params.filter((param) => param.in === "header"),
381
+ cookie: params.filter((param) => param.in === "cookie")
382
+ };
383
+ }
384
+ function getStatusCodeNumber(statusCode) {
385
+ const code = Number(statusCode);
386
+ return Number.isNaN(code) ? void 0 : code;
387
+ }
388
+ function isErrorStatusCode(statusCode) {
389
+ const code = getStatusCodeNumber(statusCode);
390
+ return code !== void 0 && code >= 400;
391
+ }
392
+ function resolveErrorNames(node, resolver) {
393
+ return node.responses.filter((response) => isErrorStatusCode(response.statusCode)).map((response) => resolver.resolveResponseStatusName(node, response.statusCode));
394
+ }
395
+ function resolveStatusCodeNames(node, resolver) {
396
+ return node.responses.map((response) => resolver.resolveResponseStatusName(node, response.statusCode));
397
+ }
398
+ const typeNamesByResolver = /* @__PURE__ */ new WeakMap();
399
+ function resolveOperationTypeNames(node, resolver, options = {}) {
400
+ const cacheKey = `${node.operationId}\0${options.paramsCasing ?? ""}\0${options.order ?? ""}\0${options.responseStatusNames ?? ""}\0${(options.exclude ?? []).join(",")}`;
401
+ let byResolver = typeNamesByResolver.get(resolver);
402
+ if (byResolver) {
403
+ const cached = byResolver.get(cacheKey);
404
+ if (cached) return cached;
405
+ } else {
406
+ byResolver = /* @__PURE__ */ new Map();
407
+ typeNamesByResolver.set(resolver, byResolver);
408
+ }
409
+ const { path, query, header } = getOperationParameters(node, { paramsCasing: options.paramsCasing });
410
+ const responseStatusNames = options.responseStatusNames === "error" ? resolveErrorNames(node, resolver) : options.responseStatusNames === false ? [] : resolveStatusCodeNames(node, resolver);
411
+ const exclude = new Set(options.exclude ?? []);
412
+ const paramNames = [
413
+ ...path.map((param) => resolver.resolvePathParamsName(node, param)),
414
+ ...query.map((param) => resolver.resolveQueryParamsName(node, param)),
415
+ ...header.map((param) => resolver.resolveHeaderParamsName(node, param))
416
+ ];
417
+ const bodyAndResponseNames = [node.requestBody?.content?.[0]?.schema ? resolver.resolveDataName(node) : void 0, resolver.resolveResponseName(node)];
418
+ const result = (options.order === "body-response-first" ? [
419
+ ...bodyAndResponseNames,
420
+ ...paramNames,
421
+ ...responseStatusNames
422
+ ] : [
423
+ ...paramNames,
424
+ ...bodyAndResponseNames,
425
+ ...responseStatusNames
426
+ ]).filter((name) => Boolean(name) && !exclude.has(name));
427
+ byResolver.set(cacheKey, result);
428
+ return result;
429
+ }
430
+ //#endregion
431
+ //#region ../../internals/shared/src/params.ts
432
+ function buildParamsMapping(originalParams, mappedParams) {
433
+ const mapping = {};
434
+ let hasChanged = false;
435
+ originalParams.forEach((param, i) => {
436
+ const mappedName = mappedParams[i]?.name ?? param.name;
437
+ mapping[param.name] = mappedName;
438
+ if (param.name !== mappedName) hasChanged = true;
439
+ });
440
+ return hasChanged ? mapping : void 0;
441
+ }
442
+ //#endregion
443
+ //#region src/functionParams.ts
444
+ const declarationPrinter$4 = functionPrinter({ mode: "declaration" });
445
+ const callPrinter = functionPrinter({ mode: "call" });
446
+ function isGroup(spec) {
447
+ return "children" in spec;
448
+ }
449
+ function createType(type) {
450
+ return type ? ast.createParamsType({
451
+ variant: "reference",
452
+ name: type
453
+ }) : void 0;
454
+ }
455
+ function createDeclarationLeaf(name, spec) {
456
+ if (spec.default !== void 0) return ast.createFunctionParameter({
457
+ name,
458
+ type: createType(spec.type),
459
+ default: spec.default,
460
+ rest: spec.mode === "inlineSpread"
461
+ });
462
+ return ast.createFunctionParameter({
463
+ name,
464
+ type: createType(spec.type),
465
+ optional: !!spec.optional,
466
+ rest: spec.mode === "inlineSpread"
467
+ });
468
+ }
469
+ function createDeclarationParam(name, spec) {
470
+ if (isGroup(spec)) return ast.createParameterGroup({
471
+ inline: spec.mode === "inlineSpread",
472
+ default: spec.default,
473
+ properties: Object.entries(spec.children).filter(([, child]) => child !== void 0).map(([childName, child]) => createDeclarationLeaf(childName, child))
474
+ });
475
+ return createDeclarationLeaf(name, spec);
476
+ }
477
+ function createCallParam(name, spec) {
478
+ if (isGroup(spec)) return ast.createParameterGroup({
479
+ inline: spec.mode === "inlineSpread",
480
+ properties: Object.entries(spec.children).filter(([, child]) => child !== void 0).map(([childName, child]) => ast.createFunctionParameter({
481
+ name: child?.mode === "inlineSpread" ? spec.mode === "inlineSpread" ? child.value ?? childName : `...${child.value ?? childName}` : child?.value ? `${childName}: ${child.value}` : childName,
482
+ rest: spec.mode === "inlineSpread" && child?.mode === "inlineSpread"
483
+ }))
484
+ });
485
+ return ast.createFunctionParameter({
486
+ name: spec.value ?? name,
487
+ rest: spec.mode === "inlineSpread"
488
+ });
489
+ }
490
+ /**
491
+ * Creates function parameter builders for generating function signatures and calls.
492
+ * Returns utilities to output constructor signatures (`toConstructor()`) or call expressions (`toCall()`).
493
+ */
494
+ function createFunctionParams(params) {
495
+ const entries = Object.entries(params).filter(([, spec]) => spec !== void 0);
496
+ return {
497
+ toConstructor() {
498
+ return declarationPrinter$4.print(ast.createFunctionParameters({ params: entries.map(([name, spec]) => createDeclarationParam(name, spec)) })) ?? "";
499
+ },
500
+ toCall() {
501
+ return callPrinter.print(ast.createFunctionParameters({ params: entries.map(([name, spec]) => createCallParam(name, spec)) })) ?? "";
502
+ }
503
+ };
504
+ }
505
+ //#endregion
506
+ //#region src/components/Url.tsx
507
+ const declarationPrinter$3 = functionPrinter({ mode: "declaration" });
508
+ function buildUrlParamsNode({ paramsType, paramsCasing, pathParamsType, node, tsResolver }) {
509
+ const urlNode = {
510
+ ...node,
511
+ parameters: node.parameters.filter((p) => p.in === "path"),
512
+ requestBody: void 0
513
+ };
514
+ return ast.createOperationParams(urlNode, {
515
+ paramsType: paramsType === "object" ? "object" : "inline",
516
+ pathParamsType: paramsType === "object" ? "object" : pathParamsType === "object" ? "object" : "inline",
517
+ paramsCasing,
518
+ resolver: tsResolver
519
+ });
520
+ }
521
+ function Url({ name, isExportable = true, isIndexable = true, baseURL, paramsType, paramsCasing, pathParamsType, node, tsResolver }) {
522
+ const path = new URLPath(node.path);
523
+ const paramsNode = buildUrlParamsNode({
524
+ paramsType,
525
+ paramsCasing,
526
+ pathParamsType,
527
+ node,
528
+ tsResolver
529
+ });
530
+ const paramsSignature = declarationPrinter$3.print(paramsNode) ?? "";
531
+ const { path: originalPathParams } = getOperationParameters(node);
532
+ const { path: casedPathParams } = getOperationParameters(node, { paramsCasing });
533
+ const pathParamsMapping = paramsCasing ? buildParamsMapping(originalPathParams, casedPathParams) : void 0;
534
+ return /* @__PURE__ */ jsx(File.Source, {
535
+ name,
536
+ isExportable,
537
+ isIndexable,
538
+ children: /* @__PURE__ */ jsxs(Function, {
539
+ name,
540
+ export: isExportable,
541
+ params: paramsSignature,
542
+ children: [
543
+ pathParamsMapping && Object.entries(pathParamsMapping).filter(([originalName, camelCaseName]) => isValidVarName(originalName) && originalName !== camelCaseName).map(([originalName, camelCaseName]) => `const ${originalName} = ${camelCaseName}`).join("\n"),
544
+ pathParamsMapping && Object.keys(pathParamsMapping).length > 0 && /* @__PURE__ */ jsx("br", {}),
545
+ /* @__PURE__ */ jsx(Const, {
546
+ name: "res",
547
+ children: `{ method: '${node.method.toUpperCase()}', url: ${path.toTemplateString({ prefix: baseURL })} as const }`
548
+ }),
549
+ /* @__PURE__ */ jsx("br", {}),
550
+ "return res"
551
+ ]
552
+ })
553
+ });
554
+ }
555
+ //#endregion
556
+ //#region src/components/Client.tsx
557
+ const declarationPrinter$2 = functionPrinter({ mode: "declaration" });
558
+ function buildClientParamsNode({ paramsType, paramsCasing, pathParamsType, node, tsResolver, isConfigurable }) {
559
+ return ast.createOperationParams(node, {
560
+ paramsType,
561
+ pathParamsType: paramsType === "object" ? "object" : pathParamsType === "object" ? "object" : "inline",
562
+ paramsCasing,
563
+ resolver: tsResolver,
564
+ extraParams: [...isConfigurable ? [ast.createFunctionParameter({
565
+ name: "config",
566
+ type: ast.createParamsType({
567
+ variant: "reference",
568
+ name: buildRequestConfigType(node, tsResolver)
569
+ }),
570
+ default: "{}"
571
+ })] : []]
572
+ });
573
+ }
574
+ function Client({ name, isExportable = true, isIndexable = true, returnType, baseURL, dataReturnType, parser, paramsType, paramsCasing, pathParamsType, node, tsResolver, zodResolver, urlName, children, isConfigurable = true }) {
575
+ const path = new URLPath(node.path);
576
+ const { defaultContentType: contentType, isMultipleContentTypes, hasFormData } = getContentTypeInfo(node);
577
+ const isFormData = !isMultipleContentTypes && contentType === "multipart/form-data";
578
+ const { path: originalPathParams, query: originalQueryParams, header: originalHeaderParams } = getOperationParameters(node);
579
+ const { path: casedPathParams, query: casedQueryParams, header: casedHeaderParams } = getOperationParameters(node, { paramsCasing });
580
+ const pathParamsMapping = paramsCasing && !urlName ? buildParamsMapping(originalPathParams, casedPathParams) : void 0;
581
+ const queryParamsMapping = paramsCasing ? buildParamsMapping(originalQueryParams, casedQueryParams) : void 0;
582
+ const headerParamsMapping = paramsCasing ? buildParamsMapping(originalHeaderParams, casedHeaderParams) : void 0;
583
+ const requestName = node.requestBody?.content?.[0]?.schema ? tsResolver.resolveDataName(node) : void 0;
584
+ const responseName = tsResolver.resolveResponseName(node);
585
+ const queryParamsName = originalQueryParams.length > 0 ? tsResolver.resolveQueryParamsName(node, originalQueryParams[0]) : void 0;
586
+ const headerParamsName = originalHeaderParams.length > 0 ? tsResolver.resolveHeaderParamsName(node, originalHeaderParams[0]) : void 0;
587
+ const zodResponseName = zodResolver && parser === "zod" ? zodResolver.resolveResponseName?.(node) : void 0;
588
+ const zodRequestName = zodResolver && parser === "zod" && node.requestBody?.content?.[0]?.schema ? zodResolver.resolveDataName?.(node) : void 0;
589
+ const errorNames = node.responses.filter((r) => {
590
+ return Number.parseInt(r.statusCode, 10) >= 400;
591
+ }).map((r) => tsResolver.resolveResponseStatusName(node, r.statusCode));
592
+ const headers = [!isMultipleContentTypes && contentType !== "application/json" && contentType !== "multipart/form-data" ? `'Content-Type': '${contentType}'` : void 0, headerParamsName ? headerParamsMapping ? "...mappedHeaders" : "...headers" : void 0].filter(Boolean);
593
+ const generics = [
594
+ responseName,
595
+ `ResponseErrorConfig<${errorNames.length > 0 ? errorNames.join(" | ") : "Error"}>`,
596
+ requestName || "unknown"
597
+ ].filter(Boolean);
598
+ const paramsNode = buildClientParamsNode({
599
+ paramsType,
600
+ paramsCasing,
601
+ pathParamsType,
602
+ node,
603
+ tsResolver,
604
+ isConfigurable
605
+ });
606
+ const paramsSignature = declarationPrinter$2.print(paramsNode) ?? "";
607
+ const urlParamsNode = buildUrlParamsNode({
608
+ paramsType,
609
+ paramsCasing,
610
+ pathParamsType,
611
+ node,
612
+ tsResolver
613
+ });
614
+ const urlParamsCall = functionPrinter({ mode: "call" }).print(urlParamsNode) ?? "";
615
+ const clientParams = createFunctionParams({ config: {
616
+ mode: "object",
617
+ children: {
618
+ method: { value: JSON.stringify(node.method.toUpperCase()) },
619
+ url: { value: urlName ? `${urlName}(${urlParamsCall}).url.toString()` : path.template },
620
+ baseURL: baseURL && !urlName ? { value: `\`${baseURL}\`` } : void 0,
621
+ params: queryParamsName ? queryParamsMapping ? { value: "mappedParams" } : {} : void 0,
622
+ data: requestName ? { value: isMultipleContentTypes && hasFormData ? "contentType === 'multipart/form-data' ? formData as FormData : requestData" : isFormData ? "formData as FormData" : "requestData" } : void 0,
623
+ contentType: isConfigurable && isMultipleContentTypes ? {} : void 0,
624
+ requestConfig: isConfigurable ? { mode: "inlineSpread" } : void 0,
625
+ headers: headers.length ? { value: isConfigurable ? `{ ${headers.join(", ")}, ...requestConfig.headers }` : `{ ${headers.join(", ")} }` } : void 0
626
+ }
627
+ } });
628
+ const childrenElement = children ? children : /* @__PURE__ */ jsxs(Fragment, { children: [
629
+ dataReturnType === "full" && parser === "zod" && zodResponseName && `return {...res, data: ${zodResponseName}.parse(res.data)}`,
630
+ dataReturnType === "data" && parser === "zod" && zodResponseName && `return ${zodResponseName}.parse(res.data)`,
631
+ dataReturnType === "full" && parser === "client" && "return res",
632
+ dataReturnType === "data" && parser === "client" && "return res.data"
633
+ ] });
634
+ return /* @__PURE__ */ jsxs(Fragment, { children: [/* @__PURE__ */ jsx("br", {}), /* @__PURE__ */ jsx(File.Source, {
635
+ name,
636
+ isExportable,
637
+ isIndexable,
638
+ children: /* @__PURE__ */ jsxs(Function, {
639
+ name,
640
+ async: true,
641
+ export: isExportable,
642
+ params: paramsSignature,
643
+ JSDoc: { comments: buildOperationComments(node, {
644
+ link: "urlPath",
645
+ linkPosition: "beforeDeprecated",
646
+ splitLines: true
647
+ }) },
648
+ returnType,
649
+ children: [
650
+ isConfigurable ? `const { client: request = fetch, ${isMultipleContentTypes ? `contentType = ${JSON.stringify(contentType)}, ` : ""}...requestConfig } = config` : "",
651
+ /* @__PURE__ */ jsx("br", {}),
652
+ /* @__PURE__ */ jsx("br", {}),
653
+ pathParamsMapping && Object.entries(pathParamsMapping).filter(([originalName, camelCaseName]) => isValidVarName(originalName) && originalName !== camelCaseName).map(([originalName, camelCaseName]) => `const ${originalName} = ${camelCaseName}`).join("\n"),
654
+ pathParamsMapping && /* @__PURE__ */ jsxs(Fragment, { children: [/* @__PURE__ */ jsx("br", {}), /* @__PURE__ */ jsx("br", {})] }),
655
+ queryParamsMapping && queryParamsName && /* @__PURE__ */ jsxs(Fragment, { children: [
656
+ `const mappedParams = params ? { ${Object.entries(queryParamsMapping).map(([originalName, camelCaseName]) => `"${originalName}": params.${camelCaseName}`).join(", ")} } : undefined`,
657
+ /* @__PURE__ */ jsx("br", {}),
658
+ /* @__PURE__ */ jsx("br", {})
659
+ ] }),
660
+ headerParamsMapping && headerParamsName && /* @__PURE__ */ jsxs(Fragment, { children: [
661
+ `const mappedHeaders = headers ? { ${Object.entries(headerParamsMapping).map(([originalName, camelCaseName]) => `"${originalName}": headers.${camelCaseName}`).join(", ")} } : undefined`,
662
+ /* @__PURE__ */ jsx("br", {}),
663
+ /* @__PURE__ */ jsx("br", {})
664
+ ] }),
665
+ parser === "zod" && zodRequestName ? `const requestData = ${zodRequestName}.parse(data)` : requestName && "const requestData = data",
666
+ /* @__PURE__ */ jsx("br", {}),
667
+ (isFormData || isMultipleContentTypes && hasFormData) && requestName && "const formData = buildFormData(requestData)",
668
+ /* @__PURE__ */ jsx("br", {}),
669
+ isConfigurable ? `const res = await request<${generics.join(", ")}>(${clientParams.toCall()})` : `const res = await fetch<${generics.join(", ")}>(${clientParams.toCall()})`,
670
+ /* @__PURE__ */ jsx("br", {}),
671
+ childrenElement
672
+ ]
673
+ })
674
+ })] });
675
+ }
676
+ //#endregion
677
+ //#region src/utils.ts
678
+ /**
679
+ * Builds HTTP headers array for a client request.
680
+ * Includes Content-Type (if not default) and spreads header parameters if present.
681
+ */
682
+ function buildHeaders(contentType, hasHeaderParams) {
683
+ return [contentType !== "application/json" && contentType !== "multipart/form-data" ? `'Content-Type': '${contentType}'` : void 0, hasHeaderParams ? "...headers" : void 0].filter(Boolean);
684
+ }
685
+ /**
686
+ * Builds TypeScript generic parameters for a client method.
687
+ * Includes response type, error type, and optional request type.
688
+ */
689
+ function buildGenerics(node, tsResolver) {
690
+ const responseName = tsResolver.resolveResponseName(node);
691
+ const requestName = node.requestBody?.content?.[0]?.schema ? tsResolver.resolveDataName(node) : void 0;
692
+ const errorNames = node.responses.filter((r) => Number.parseInt(r.statusCode, 10) >= 400).map((r) => tsResolver.resolveResponseStatusName(node, r.statusCode));
693
+ return [
694
+ responseName,
695
+ `ResponseErrorConfig<${errorNames.length > 0 ? errorNames.join(" | ") : "Error"}>`,
696
+ requestName || "unknown"
697
+ ].filter(Boolean);
698
+ }
699
+ /**
700
+ * Builds the parameters object for a class-based client method.
701
+ * Includes URL, method, base URL, headers, and request/response data.
702
+ */
703
+ function buildClassClientParams({ node, path, baseURL, tsResolver, isFormData, isMultipleContentTypes, hasFormData, headers }) {
704
+ const { query: queryParams } = getOperationParameters(node);
705
+ const queryParamsName = queryParams.length > 0 ? tsResolver.resolveQueryParamsName(node, queryParams[0]) : void 0;
706
+ const requestName = node.requestBody?.content?.[0]?.schema ? tsResolver.resolveDataName(node) : void 0;
707
+ return createFunctionParams({ config: {
708
+ mode: "object",
709
+ children: {
710
+ requestConfig: { mode: "inlineSpread" },
711
+ method: { value: JSON.stringify(node.method.toUpperCase()) },
712
+ url: { value: path.template },
713
+ baseURL: baseURL ? { value: JSON.stringify(baseURL) } : void 0,
714
+ params: queryParamsName ? {} : void 0,
715
+ data: requestName ? { value: isMultipleContentTypes && hasFormData ? "contentType === 'multipart/form-data' ? formData as FormData : requestData" : isFormData ? "formData as FormData" : "requestData" } : void 0,
716
+ contentType: isMultipleContentTypes ? {} : void 0,
717
+ headers: headers.length ? { value: `{ ${headers.join(", ")}, ...requestConfig.headers }` } : void 0
718
+ }
719
+ } });
720
+ }
721
+ /**
722
+ * Builds the request data parsing line for client methods.
723
+ * Applies Zod validation if configured, otherwise uses data directly.
724
+ */
725
+ function buildRequestDataLine({ parser, node, zodResolver }) {
726
+ const zodRequestName = zodResolver && parser === "zod" && node.requestBody?.content?.[0]?.schema ? zodResolver.resolveDataName?.(node) : void 0;
727
+ if (parser === "zod" && zodRequestName) return `const requestData = ${zodRequestName}.parse(data)`;
728
+ if (node.requestBody?.content?.[0]?.schema) return "const requestData = data";
729
+ return "";
730
+ }
731
+ /**
732
+ * Builds the form data conversion line for file upload requests.
733
+ * Returns empty string if not applicable.
734
+ */
735
+ function buildFormDataLine(isFormData, hasRequest) {
736
+ return isFormData && hasRequest ? "const formData = buildFormData(requestData)" : "";
737
+ }
738
+ /**
739
+ * Builds the return statement for a client method.
740
+ * Applies Zod validation to response data if configured, otherwise returns raw response.
741
+ */
742
+ function buildReturnStatement({ dataReturnType, parser, node, zodResolver }) {
743
+ const zodResponseName = zodResolver && parser === "zod" ? zodResolver.resolveResponseName?.(node) : void 0;
744
+ if (dataReturnType === "full" && parser === "zod" && zodResponseName) return `return {...res, data: ${zodResponseName}.parse(res.data)}`;
745
+ if (dataReturnType === "data" && parser === "zod" && zodResponseName) return `return ${zodResponseName}.parse(res.data)`;
746
+ if (dataReturnType === "full" && parser === "client") return "return res";
747
+ return "return res.data";
748
+ }
749
+ //#endregion
750
+ //#region src/components/ClassClient.tsx
751
+ const declarationPrinter$1 = functionPrinter({ mode: "declaration" });
752
+ function generateMethod$1({ node, name, tsResolver, zodResolver, baseURL, dataReturnType, parser, paramsType, paramsCasing, pathParamsType }) {
753
+ const path = new URLPath(node.path, { casing: paramsCasing });
754
+ const { defaultContentType: contentType, isMultipleContentTypes, hasFormData } = getContentTypeInfo(node);
755
+ const isFormData = !isMultipleContentTypes && contentType === "multipart/form-data";
756
+ const { header: headerParams } = getOperationParameters(node);
757
+ const headerParamsName = headerParams.length > 0 ? tsResolver.resolveHeaderParamsName(node, headerParams[0]) : void 0;
758
+ const headers = isMultipleContentTypes ? headerParamsName ? ["...headers"] : [] : buildHeaders(contentType, !!headerParamsName);
759
+ const generics = buildGenerics(node, tsResolver);
760
+ const paramsNode = buildClientParamsNode({
761
+ paramsType,
762
+ paramsCasing,
763
+ pathParamsType,
764
+ node,
765
+ tsResolver,
766
+ isConfigurable: true
767
+ });
768
+ const paramsSignature = declarationPrinter$1.print(paramsNode) ?? "";
769
+ const clientParams = buildClassClientParams({
770
+ node,
771
+ path,
772
+ baseURL,
773
+ tsResolver,
774
+ isFormData,
775
+ isMultipleContentTypes,
776
+ hasFormData,
777
+ headers
778
+ });
779
+ const jsdoc = buildJSDoc(buildOperationComments(node, {
780
+ link: "urlPath",
781
+ linkPosition: "beforeDeprecated",
782
+ splitLines: true
783
+ }));
784
+ const requestDataLine = buildRequestDataLine({
785
+ parser,
786
+ node,
787
+ zodResolver
788
+ });
789
+ const formDataLine = buildFormDataLine(isFormData || isMultipleContentTypes && hasFormData, !!node.requestBody?.content?.[0]?.schema);
790
+ const returnStatement = buildReturnStatement({
791
+ dataReturnType,
792
+ parser,
793
+ node,
794
+ zodResolver
795
+ });
796
+ return `${jsdoc}async ${name}(${paramsSignature}) {\n${[
797
+ `const { client: request = fetch, ${isMultipleContentTypes ? `contentType = ${JSON.stringify(contentType)}, ` : ""}...requestConfig } = mergeConfig(this.#config, config)`,
798
+ "",
799
+ requestDataLine,
800
+ formDataLine,
801
+ `const res = await request<${generics.join(", ")}>(${clientParams.toCall()})`,
802
+ returnStatement
803
+ ].filter(Boolean).map((line) => ` ${line}`).join("\n")}\n }`;
804
+ }
805
+ __name(generateMethod$1, "generateMethod");
806
+ function ClassClient({ name, isExportable = true, isIndexable = true, operations, baseURL, dataReturnType, parser, paramsType, paramsCasing, pathParamsType, children }) {
807
+ const classCode = `export class ${name} {
808
+ #config: Partial<RequestConfig> & { client?: Client }
809
+
810
+ constructor(config: Partial<RequestConfig> & { client?: Client } = {}) {
811
+ this.#config = config
812
+ }
813
+
814
+ ${operations.map(({ node, name: methodName, tsResolver, zodResolver }) => generateMethod$1({
815
+ node,
816
+ name: methodName,
817
+ tsResolver,
818
+ zodResolver,
819
+ baseURL,
820
+ dataReturnType,
821
+ parser,
822
+ paramsType,
823
+ paramsCasing,
824
+ pathParamsType
825
+ })).join("\n\n")}
826
+ }`;
827
+ return /* @__PURE__ */ jsxs(File.Source, {
828
+ name,
829
+ isExportable,
830
+ isIndexable,
831
+ children: [classCode, children]
832
+ });
833
+ }
834
+ //#endregion
835
+ //#region src/components/WrapperClient.tsx
836
+ function WrapperClient({ name, controllers, isExportable = true, isIndexable = true }) {
837
+ const classCode = `export class ${name} {
838
+ ${controllers.map(({ className, propertyName }) => ` readonly ${propertyName}: ${className}`).join("\n")}
839
+
840
+ constructor(config: Partial<RequestConfig> & { client?: Client } = {}) {
841
+ ${controllers.map(({ className, propertyName }) => ` this.${propertyName} = new ${className}(config)`).join("\n")}
842
+ }
843
+ }`;
844
+ return /* @__PURE__ */ jsx(File.Source, {
845
+ name,
846
+ isExportable,
847
+ isIndexable,
848
+ children: classCode
849
+ });
850
+ }
851
+ //#endregion
852
+ //#region src/generators/classClientGenerator.tsx
853
+ function resolveTypeImportNames$1(node, tsResolver) {
854
+ return resolveOperationTypeNames(node, tsResolver, { order: "body-response-first" });
855
+ }
856
+ __name(resolveTypeImportNames$1, "resolveTypeImportNames");
857
+ function resolveZodImportNames$1(node, zodResolver) {
858
+ return [zodResolver.resolveResponseName?.(node), node.requestBody?.content?.[0]?.schema ? zodResolver.resolveDataName?.(node) : void 0].filter((n) => Boolean(n));
859
+ }
860
+ __name(resolveZodImportNames$1, "resolveZodImportNames");
861
+ const classClientGenerator = defineGenerator({
862
+ name: "classClient",
863
+ renderer: jsxRendererSync,
864
+ operations(nodes, ctx) {
865
+ const { config, driver, resolver, root, inputNode } = ctx;
866
+ const { output, group, dataReturnType, paramsCasing, paramsType, pathParamsType, parser, importPath, sdk } = ctx.options;
867
+ const baseURL = ctx.options.baseURL ?? inputNode.meta?.baseURL;
868
+ const pluginTs = driver.getPlugin(pluginTsName);
869
+ if (!pluginTs) return null;
870
+ const tsResolver = driver.getResolver(pluginTsName);
871
+ const tsPluginOptions = pluginTs.options;
872
+ const pluginZod = parser === "zod" ? driver.getPlugin(pluginZodName) : void 0;
873
+ const zodResolver = pluginZod ? driver.getResolver(pluginZodName) : void 0;
874
+ function buildOperationData(node) {
875
+ const typeFile = tsResolver.resolveFile({
876
+ name: node.operationId,
877
+ extname: ".ts",
878
+ tag: node.tags[0] ?? "default",
879
+ path: node.path
880
+ }, {
881
+ root,
882
+ output: tsPluginOptions?.output ?? output,
883
+ group: tsPluginOptions?.group
884
+ });
885
+ const zodFile = zodResolver && pluginZod?.options ? zodResolver.resolveFile({
886
+ name: node.operationId,
887
+ extname: ".ts",
888
+ tag: node.tags[0] ?? "default",
889
+ path: node.path
890
+ }, {
891
+ root,
892
+ output: pluginZod.options?.output ?? output,
893
+ group: pluginZod.options?.group
894
+ }) : void 0;
895
+ return {
896
+ node,
897
+ name: resolver.resolveName(node.operationId),
898
+ tsResolver,
899
+ zodResolver,
900
+ typeFile,
901
+ zodFile
902
+ };
903
+ }
904
+ const controllers = nodes.reduce((acc, operationNode) => {
905
+ const tag = operationNode.tags[0];
906
+ const groupName = tag ? group?.name?.({ group: camelCase(tag) }) ?? resolver.resolveGroupName(tag) : resolver.resolveGroupName("Client");
907
+ if (!tag && !group) {
908
+ const name = resolver.resolveClassName("ApiClient");
909
+ const file = resolver.resolveFile({
910
+ name,
911
+ extname: ".ts"
912
+ }, {
913
+ root,
914
+ output,
915
+ group
916
+ });
917
+ const operationData = buildOperationData(operationNode);
918
+ const previous = acc.find((item) => item.file.path === file.path);
919
+ if (previous) previous.operations.push(operationData);
920
+ else acc.push({
921
+ name,
922
+ propertyName: resolver.resolveClientPropertyName(name),
923
+ file,
924
+ operations: [operationData]
925
+ });
926
+ } else if (tag) {
927
+ const name = groupName;
928
+ const file = resolver.resolveFile({
929
+ name,
930
+ extname: ".ts",
931
+ tag
932
+ }, {
933
+ root,
934
+ output,
935
+ group
936
+ });
937
+ const operationData = buildOperationData(operationNode);
938
+ const previous = acc.find((item) => item.file.path === file.path);
939
+ if (previous) previous.operations.push(operationData);
940
+ else acc.push({
941
+ name,
942
+ propertyName: resolver.resolveClientPropertyName(name),
943
+ file,
944
+ operations: [operationData]
945
+ });
946
+ }
947
+ return acc;
948
+ }, []);
949
+ function collectTypeImports(ops) {
950
+ const typeImportsByFile = /* @__PURE__ */ new Map();
951
+ const typeFilesByPath = /* @__PURE__ */ new Map();
952
+ ops.forEach((op) => {
953
+ const names = resolveTypeImportNames$1(op.node, tsResolver);
954
+ if (!typeImportsByFile.has(op.typeFile.path)) typeImportsByFile.set(op.typeFile.path, /* @__PURE__ */ new Set());
955
+ const imports = typeImportsByFile.get(op.typeFile.path);
956
+ names.forEach((n) => {
957
+ imports.add(n);
958
+ });
959
+ typeFilesByPath.set(op.typeFile.path, op.typeFile);
960
+ });
961
+ return {
962
+ typeImportsByFile,
963
+ typeFilesByPath
964
+ };
965
+ }
966
+ function collectZodImports(ops) {
967
+ const zodImportsByFile = /* @__PURE__ */ new Map();
968
+ const zodFilesByPath = /* @__PURE__ */ new Map();
969
+ ops.forEach((op) => {
970
+ if (!op.zodFile || !zodResolver) return;
971
+ const names = resolveZodImportNames$1(op.node, zodResolver);
972
+ if (!zodImportsByFile.has(op.zodFile.path)) zodImportsByFile.set(op.zodFile.path, /* @__PURE__ */ new Set());
973
+ const imports = zodImportsByFile.get(op.zodFile.path);
974
+ names.forEach((n) => {
975
+ imports.add(n);
976
+ });
977
+ zodFilesByPath.set(op.zodFile.path, op.zodFile);
978
+ });
979
+ return {
980
+ zodImportsByFile,
981
+ zodFilesByPath
982
+ };
983
+ }
984
+ const files = controllers.map(({ name, file, operations: ops }) => {
985
+ const { typeImportsByFile, typeFilesByPath } = collectTypeImports(ops);
986
+ const { zodImportsByFile, zodFilesByPath } = parser === "zod" ? collectZodImports(ops) : {
987
+ zodImportsByFile: /* @__PURE__ */ new Map(),
988
+ zodFilesByPath: /* @__PURE__ */ new Map()
989
+ };
990
+ const hasFormData = ops.some((op) => op.node.requestBody?.content?.some((e) => e.contentType === "multipart/form-data") ?? false);
991
+ return /* @__PURE__ */ jsxs(File, {
992
+ baseName: file.baseName,
993
+ path: file.path,
994
+ meta: file.meta,
995
+ banner: resolver.resolveBanner(inputNode, {
996
+ output,
997
+ config
998
+ }),
999
+ footer: resolver.resolveFooter(inputNode, {
1000
+ output,
1001
+ config
1002
+ }),
1003
+ children: [
1004
+ importPath ? /* @__PURE__ */ jsxs(Fragment, { children: [
1005
+ /* @__PURE__ */ jsx(File.Import, {
1006
+ name: "fetch",
1007
+ path: importPath
1008
+ }),
1009
+ /* @__PURE__ */ jsx(File.Import, {
1010
+ name: ["mergeConfig"],
1011
+ path: importPath
1012
+ }),
1013
+ /* @__PURE__ */ jsx(File.Import, {
1014
+ name: [
1015
+ "Client",
1016
+ "RequestConfig",
1017
+ "ResponseErrorConfig"
1018
+ ],
1019
+ path: importPath,
1020
+ isTypeOnly: true
1021
+ })
1022
+ ] }) : /* @__PURE__ */ jsxs(Fragment, { children: [
1023
+ /* @__PURE__ */ jsx(File.Import, {
1024
+ name: ["fetch"],
1025
+ root: file.path,
1026
+ path: path.resolve(root, ".kubb/client.ts")
1027
+ }),
1028
+ /* @__PURE__ */ jsx(File.Import, {
1029
+ name: ["mergeConfig"],
1030
+ root: file.path,
1031
+ path: path.resolve(root, ".kubb/client.ts")
1032
+ }),
1033
+ /* @__PURE__ */ jsx(File.Import, {
1034
+ name: [
1035
+ "Client",
1036
+ "RequestConfig",
1037
+ "ResponseErrorConfig"
1038
+ ],
1039
+ root: file.path,
1040
+ path: path.resolve(root, ".kubb/client.ts"),
1041
+ isTypeOnly: true
1042
+ })
1043
+ ] }),
1044
+ hasFormData && /* @__PURE__ */ jsx(File.Import, {
1045
+ name: ["buildFormData"],
1046
+ root: file.path,
1047
+ path: path.resolve(root, ".kubb/config.ts")
1048
+ }),
1049
+ Array.from(typeImportsByFile.entries()).map(([filePath, importSet]) => {
1050
+ const typeFile = typeFilesByPath.get(filePath);
1051
+ if (!typeFile) return null;
1052
+ const importNames = Array.from(importSet).filter(Boolean);
1053
+ if (importNames.length === 0) return null;
1054
+ return /* @__PURE__ */ jsx(File.Import, {
1055
+ name: importNames,
1056
+ root: file.path,
1057
+ path: typeFile.path,
1058
+ isTypeOnly: true
1059
+ }, filePath);
1060
+ }),
1061
+ parser === "zod" && Array.from(zodImportsByFile.entries()).map(([filePath, importSet]) => {
1062
+ const zodFile = zodFilesByPath.get(filePath);
1063
+ if (!zodFile) return null;
1064
+ const importNames = Array.from(importSet).filter(Boolean);
1065
+ if (importNames.length === 0) return null;
1066
+ return /* @__PURE__ */ jsx(File.Import, {
1067
+ name: importNames,
1068
+ root: file.path,
1069
+ path: zodFile.path
1070
+ }, filePath);
1071
+ }),
1072
+ /* @__PURE__ */ jsx(ClassClient, {
1073
+ name,
1074
+ operations: ops,
1075
+ baseURL,
1076
+ dataReturnType,
1077
+ pathParamsType,
1078
+ paramsCasing,
1079
+ paramsType,
1080
+ parser
1081
+ })
1082
+ ]
1083
+ }, file.path);
1084
+ });
1085
+ if (sdk) {
1086
+ const sdkFile = resolver.resolveFile({
1087
+ name: sdk.className,
1088
+ extname: ".ts"
1089
+ }, {
1090
+ root,
1091
+ output,
1092
+ group
1093
+ });
1094
+ files.push(/* @__PURE__ */ jsxs(File, {
1095
+ baseName: sdkFile.baseName,
1096
+ path: sdkFile.path,
1097
+ meta: sdkFile.meta,
1098
+ banner: resolver.resolveBanner(inputNode, {
1099
+ output,
1100
+ config
1101
+ }),
1102
+ footer: resolver.resolveFooter(inputNode, {
1103
+ output,
1104
+ config
1105
+ }),
1106
+ children: [
1107
+ importPath ? /* @__PURE__ */ jsx(File.Import, {
1108
+ name: ["Client", "RequestConfig"],
1109
+ path: importPath,
1110
+ isTypeOnly: true
1111
+ }) : /* @__PURE__ */ jsx(File.Import, {
1112
+ name: ["Client", "RequestConfig"],
1113
+ root: sdkFile.path,
1114
+ path: path.resolve(root, ".kubb/client.ts"),
1115
+ isTypeOnly: true
1116
+ }),
1117
+ controllers.map(({ name, file }) => /* @__PURE__ */ jsx(File.Import, {
1118
+ name: [name],
1119
+ root: sdkFile.path,
1120
+ path: file.path
1121
+ }, name)),
1122
+ /* @__PURE__ */ jsx(WrapperClient, {
1123
+ name: sdk.className,
1124
+ controllers: controllers.map(({ name, propertyName }) => ({
1125
+ className: name,
1126
+ propertyName
1127
+ }))
1128
+ })
1129
+ ]
1130
+ }, sdkFile.path));
1131
+ }
1132
+ return /* @__PURE__ */ jsx(Fragment, { children: files });
1133
+ }
1134
+ });
1135
+ //#endregion
1136
+ //#region src/generators/clientGenerator.tsx
1137
+ const clientGenerator = defineGenerator({
1138
+ name: "client",
1139
+ renderer: jsxRendererSync,
1140
+ operation(node, ctx) {
1141
+ const { config, driver, resolver, root, inputNode } = ctx;
1142
+ const { output, urlType, dataReturnType, paramsCasing, paramsType, pathParamsType, parser, importPath, group } = ctx.options;
1143
+ const baseURL = ctx.options.baseURL ?? inputNode.meta?.baseURL;
1144
+ const pluginTs = driver.getPlugin(pluginTsName);
1145
+ if (!pluginTs) return null;
1146
+ const tsResolver = driver.getResolver(pluginTsName);
1147
+ const pluginZod = parser === "zod" ? driver.getPlugin(pluginZodName) : void 0;
1148
+ const zodResolver = pluginZod ? driver.getResolver(pluginZodName) : void 0;
1149
+ const importedTypeNames = resolveOperationTypeNames(node, tsResolver, { paramsCasing });
1150
+ const importedZodNames = zodResolver && parser === "zod" ? [zodResolver.resolveResponseName?.(node), node.requestBody?.content?.[0]?.schema ? zodResolver.resolveDataName?.(node) : void 0].filter((name) => Boolean(name)) : [];
1151
+ const meta = {
1152
+ name: resolver.resolveName(node.operationId),
1153
+ urlName: resolver.resolveUrlName(node),
1154
+ file: resolver.resolveFile({
1155
+ name: node.operationId,
1156
+ extname: ".ts",
1157
+ tag: node.tags[0] ?? "default",
1158
+ path: node.path
1159
+ }, {
1160
+ root,
1161
+ output,
1162
+ group
1163
+ }),
1164
+ fileTs: tsResolver.resolveFile({
1165
+ name: node.operationId,
1166
+ extname: ".ts",
1167
+ tag: node.tags[0] ?? "default",
1168
+ path: node.path
1169
+ }, {
1170
+ root,
1171
+ output: pluginTs.options?.output ?? output,
1172
+ group: pluginTs.options?.group
1173
+ }),
1174
+ fileZod: zodResolver && pluginZod?.options ? zodResolver.resolveFile({
1175
+ name: node.operationId,
1176
+ extname: ".ts",
1177
+ tag: node.tags[0] ?? "default",
1178
+ path: node.path
1179
+ }, {
1180
+ root,
1181
+ output: pluginZod.options.output ?? output,
1182
+ group: pluginZod.options?.group
1183
+ }) : void 0
1184
+ };
1185
+ const hasFormData = node.requestBody?.content?.some((e) => e.contentType === "multipart/form-data") ?? false;
1186
+ return /* @__PURE__ */ jsxs(File, {
1187
+ baseName: meta.file.baseName,
1188
+ path: meta.file.path,
1189
+ meta: meta.file.meta,
1190
+ banner: resolver.resolveBanner(inputNode, {
1191
+ output,
1192
+ config
1193
+ }),
1194
+ footer: resolver.resolveFooter(inputNode, {
1195
+ output,
1196
+ config
1197
+ }),
1198
+ children: [
1199
+ importPath ? /* @__PURE__ */ jsxs(Fragment, { children: [/* @__PURE__ */ jsx(File.Import, {
1200
+ name: "fetch",
1201
+ path: importPath
1202
+ }), /* @__PURE__ */ jsx(File.Import, {
1203
+ name: [
1204
+ "Client",
1205
+ "RequestConfig",
1206
+ "ResponseErrorConfig"
1207
+ ],
1208
+ path: importPath,
1209
+ isTypeOnly: true
1210
+ })] }) : /* @__PURE__ */ jsxs(Fragment, { children: [/* @__PURE__ */ jsx(File.Import, {
1211
+ name: ["fetch"],
1212
+ root: meta.file.path,
1213
+ path: path.resolve(root, ".kubb/client.ts")
1214
+ }), /* @__PURE__ */ jsx(File.Import, {
1215
+ name: [
1216
+ "Client",
1217
+ "RequestConfig",
1218
+ "ResponseErrorConfig"
1219
+ ],
1220
+ root: meta.file.path,
1221
+ path: path.resolve(root, ".kubb/client.ts"),
1222
+ isTypeOnly: true
1223
+ })] }),
1224
+ hasFormData && /* @__PURE__ */ jsx(File.Import, {
1225
+ name: ["buildFormData"],
1226
+ root: meta.file.path,
1227
+ path: path.resolve(root, ".kubb/config.ts")
1228
+ }),
1229
+ meta.fileZod && importedZodNames.length > 0 && /* @__PURE__ */ jsx(File.Import, {
1230
+ name: importedZodNames,
1231
+ root: meta.file.path,
1232
+ path: meta.fileZod.path
1233
+ }),
1234
+ meta.fileTs && importedTypeNames.length > 0 && /* @__PURE__ */ jsx(File.Import, {
1235
+ name: Array.from(new Set(importedTypeNames)),
1236
+ root: meta.file.path,
1237
+ path: meta.fileTs.path,
1238
+ isTypeOnly: true
1239
+ }),
1240
+ /* @__PURE__ */ jsx(Url, {
1241
+ name: meta.urlName,
1242
+ baseURL,
1243
+ pathParamsType,
1244
+ paramsCasing,
1245
+ paramsType,
1246
+ node,
1247
+ tsResolver,
1248
+ isIndexable: urlType === "export",
1249
+ isExportable: urlType === "export"
1250
+ }),
1251
+ /* @__PURE__ */ jsx(Client, {
1252
+ name: meta.name,
1253
+ urlName: meta.urlName,
1254
+ baseURL,
1255
+ dataReturnType,
1256
+ pathParamsType,
1257
+ paramsCasing,
1258
+ paramsType,
1259
+ node,
1260
+ tsResolver,
1261
+ zodResolver,
1262
+ parser
1263
+ })
1264
+ ]
1265
+ });
1266
+ }
1267
+ });
1268
+ //#endregion
1269
+ //#region src/generators/groupedClientGenerator.tsx
1270
+ const groupedClientGenerator = defineGenerator({
1271
+ name: "groupedClient",
1272
+ renderer: jsxRendererSync,
1273
+ operations(nodes, ctx) {
1274
+ const { config, resolver, root, inputNode } = ctx;
1275
+ const { output, group } = ctx.options;
1276
+ return /* @__PURE__ */ jsx(Fragment, { children: nodes.reduce((acc, operationNode) => {
1277
+ if (group?.type === "tag") {
1278
+ const tag = operationNode.tags[0];
1279
+ const name = tag ? group?.name?.({ group: camelCase(tag) }) : void 0;
1280
+ if (!tag || !name) return acc;
1281
+ const file = resolver.resolveFile({
1282
+ name,
1283
+ extname: ".ts",
1284
+ tag
1285
+ }, {
1286
+ root,
1287
+ output,
1288
+ group
1289
+ });
1290
+ const clientFile = resolver.resolveFile({
1291
+ name: operationNode.operationId,
1292
+ extname: ".ts",
1293
+ tag: operationNode.tags[0] ?? "default",
1294
+ path: operationNode.path
1295
+ }, {
1296
+ root,
1297
+ output,
1298
+ group
1299
+ });
1300
+ const client = {
1301
+ name: resolver.resolveName(operationNode.operationId),
1302
+ file: clientFile
1303
+ };
1304
+ const previous = acc.find((item) => item.file.path === file.path);
1305
+ if (previous) previous.clients.push(client);
1306
+ else acc.push({
1307
+ name,
1308
+ file,
1309
+ clients: [client]
1310
+ });
1311
+ }
1312
+ return acc;
1313
+ }, []).map(({ name, file, clients }) => {
1314
+ return /* @__PURE__ */ jsxs(File, {
1315
+ baseName: file.baseName,
1316
+ path: file.path,
1317
+ meta: file.meta,
1318
+ banner: resolver.resolveBanner(inputNode, {
1319
+ output,
1320
+ config
1321
+ }),
1322
+ footer: resolver.resolveFooter(inputNode, {
1323
+ output,
1324
+ config
1325
+ }),
1326
+ children: [clients.map((client) => /* @__PURE__ */ jsx(File.Import, {
1327
+ name: [client.name],
1328
+ root: file.path,
1329
+ path: client.file.path
1330
+ }, client.name)), /* @__PURE__ */ jsx(File.Source, {
1331
+ name,
1332
+ isExportable: true,
1333
+ isIndexable: true,
1334
+ children: /* @__PURE__ */ jsx(Function, {
1335
+ export: true,
1336
+ name,
1337
+ children: `return { ${clients.map((client) => client.name).join(", ")} }`
1338
+ })
1339
+ })]
1340
+ }, file.path);
1341
+ }) });
1342
+ }
1343
+ });
1344
+ //#endregion
1345
+ //#region src/components/Operations.tsx
1346
+ function Operations({ name, nodes }) {
1347
+ const operationsObject = {};
1348
+ nodes.forEach((node) => {
1349
+ operationsObject[node.operationId] = {
1350
+ path: new URLPath(node.path).URL,
1351
+ method: node.method.toLowerCase()
1352
+ };
1353
+ });
1354
+ return /* @__PURE__ */ jsx(File.Source, {
1355
+ name,
1356
+ isExportable: true,
1357
+ isIndexable: true,
1358
+ children: /* @__PURE__ */ jsx(Const, {
1359
+ name,
1360
+ export: true,
1361
+ children: JSON.stringify(operationsObject, void 0, 2)
1362
+ })
1363
+ });
1364
+ }
1365
+ //#endregion
1366
+ //#region src/generators/operationsGenerator.tsx
1367
+ const operationsGenerator = defineGenerator({
1368
+ name: "client",
1369
+ renderer: jsxRendererSync,
1370
+ operations(nodes, ctx) {
1371
+ const { config, resolver, root, inputNode } = ctx;
1372
+ const { output, group } = ctx.options;
1373
+ const name = "operations";
1374
+ const file = resolver.resolveFile({
1375
+ name,
1376
+ extname: ".ts"
1377
+ }, {
1378
+ root,
1379
+ output,
1380
+ group
1381
+ });
1382
+ return /* @__PURE__ */ jsx(File, {
1383
+ baseName: file.baseName,
1384
+ path: file.path,
1385
+ meta: file.meta,
1386
+ banner: resolver.resolveBanner(inputNode, {
1387
+ output,
1388
+ config
1389
+ }),
1390
+ footer: resolver.resolveFooter(inputNode, {
1391
+ output,
1392
+ config
1393
+ }),
1394
+ children: /* @__PURE__ */ jsx(Operations, {
1395
+ name,
1396
+ nodes
1397
+ })
1398
+ });
1399
+ }
1400
+ });
1401
+ //#endregion
1402
+ //#region src/components/StaticClassClient.tsx
1403
+ const declarationPrinter = functionPrinter({ mode: "declaration" });
1404
+ function generateMethod({ node, name, tsResolver, zodResolver, baseURL, dataReturnType, parser, paramsType, paramsCasing, pathParamsType }) {
1405
+ const path = new URLPath(node.path, { casing: paramsCasing });
1406
+ const { defaultContentType: contentType, isMultipleContentTypes, hasFormData } = getContentTypeInfo(node);
1407
+ const isFormData = !isMultipleContentTypes && contentType === "multipart/form-data";
1408
+ const { header: headerParams } = getOperationParameters(node);
1409
+ const headerParamsName = headerParams.length > 0 ? tsResolver.resolveHeaderParamsName(node, headerParams[0]) : void 0;
1410
+ const headers = isMultipleContentTypes ? headerParamsName ? ["...headers"] : [] : buildHeaders(contentType, !!headerParamsName);
1411
+ const generics = buildGenerics(node, tsResolver);
1412
+ const paramsNode = buildClientParamsNode({
1413
+ paramsType,
1414
+ paramsCasing,
1415
+ pathParamsType,
1416
+ node,
1417
+ tsResolver,
1418
+ isConfigurable: true
1419
+ });
1420
+ const paramsSignature = declarationPrinter.print(paramsNode) ?? "";
1421
+ const clientParams = buildClassClientParams({
1422
+ node,
1423
+ path,
1424
+ baseURL,
1425
+ tsResolver,
1426
+ isFormData,
1427
+ isMultipleContentTypes,
1428
+ hasFormData,
1429
+ headers
1430
+ });
1431
+ const jsdoc = buildJSDoc(buildOperationComments(node, {
1432
+ link: "urlPath",
1433
+ linkPosition: "beforeDeprecated",
1434
+ splitLines: true
1435
+ }));
1436
+ const requestDataLine = buildRequestDataLine({
1437
+ parser,
1438
+ node,
1439
+ zodResolver
1440
+ });
1441
+ const formDataLine = buildFormDataLine(isFormData || isMultipleContentTypes && hasFormData, !!node.requestBody?.content?.[0]?.schema);
1442
+ const returnStatement = buildReturnStatement({
1443
+ dataReturnType,
1444
+ parser,
1445
+ node,
1446
+ zodResolver
1447
+ });
1448
+ return `${jsdoc} static async ${name}(${paramsSignature}) {\n${[
1449
+ `const { client: request = fetch, ${isMultipleContentTypes ? `contentType = ${JSON.stringify(contentType)}, ` : ""}...requestConfig } = mergeConfig(this.#config, config)`,
1450
+ "",
1451
+ requestDataLine,
1452
+ formDataLine,
1453
+ `const res = await request<${generics.join(", ")}>(${clientParams.toCall()})`,
1454
+ returnStatement
1455
+ ].filter(Boolean).map((line) => ` ${line}`).join("\n")}\n }`;
1456
+ }
1457
+ function StaticClassClient({ name, isExportable = true, isIndexable = true, operations, baseURL, dataReturnType, parser, paramsType, paramsCasing, pathParamsType, children }) {
1458
+ const classCode = `export class ${name} {\n static #config: Partial<RequestConfig> & { client?: Client } = {}\n\n${operations.map(({ node, name: methodName, tsResolver, zodResolver }) => generateMethod({
1459
+ node,
1460
+ name: methodName,
1461
+ tsResolver,
1462
+ zodResolver,
1463
+ baseURL,
1464
+ dataReturnType,
1465
+ parser,
1466
+ paramsType,
1467
+ paramsCasing,
1468
+ pathParamsType
1469
+ })).join("\n\n")}\n}`;
1470
+ return /* @__PURE__ */ jsxs(File.Source, {
1471
+ name,
1472
+ isExportable,
1473
+ isIndexable,
1474
+ children: [classCode, children]
1475
+ });
1476
+ }
1477
+ //#endregion
1478
+ //#region src/generators/staticClassClientGenerator.tsx
1479
+ function resolveTypeImportNames(node, tsResolver) {
1480
+ return resolveOperationTypeNames(node, tsResolver, { order: "body-response-first" });
1481
+ }
1482
+ function resolveZodImportNames(node, zodResolver) {
1483
+ return [zodResolver.resolveResponseName?.(node), node.requestBody?.content?.[0]?.schema ? zodResolver.resolveDataName?.(node) : void 0].filter((n) => Boolean(n));
1484
+ }
1485
+ const staticClassClientGenerator = defineGenerator({
1486
+ name: "staticClassClient",
1487
+ renderer: jsxRendererSync,
1488
+ operations(nodes, ctx) {
1489
+ const { config, driver, resolver, root, inputNode } = ctx;
1490
+ const { output, group, dataReturnType, paramsCasing, paramsType, pathParamsType, parser, importPath } = ctx.options;
1491
+ const baseURL = ctx.options.baseURL ?? inputNode.meta?.baseURL;
1492
+ const pluginTs = driver.getPlugin(pluginTsName);
1493
+ if (!pluginTs) return null;
1494
+ const tsResolver = driver.getResolver(pluginTsName);
1495
+ const tsPluginOptions = pluginTs.options;
1496
+ const pluginZod = parser === "zod" ? driver.getPlugin(pluginZodName) : void 0;
1497
+ const zodResolver = pluginZod ? driver.getResolver(pluginZodName) : void 0;
1498
+ function buildOperationData(node) {
1499
+ const typeFile = tsResolver.resolveFile({
1500
+ name: node.operationId,
1501
+ extname: ".ts",
1502
+ tag: node.tags[0] ?? "default",
1503
+ path: node.path
1504
+ }, {
1505
+ root,
1506
+ output: tsPluginOptions?.output ?? output,
1507
+ group: tsPluginOptions?.group
1508
+ });
1509
+ const zodFile = zodResolver && pluginZod?.options ? zodResolver.resolveFile({
1510
+ name: node.operationId,
1511
+ extname: ".ts",
1512
+ tag: node.tags[0] ?? "default",
1513
+ path: node.path
1514
+ }, {
1515
+ root,
1516
+ output: pluginZod.options?.output ?? output,
1517
+ group: pluginZod.options?.group
1518
+ }) : void 0;
1519
+ return {
1520
+ node,
1521
+ name: resolver.resolveName(node.operationId),
1522
+ tsResolver,
1523
+ zodResolver,
1524
+ typeFile,
1525
+ zodFile
1526
+ };
1527
+ }
1528
+ const controllers = nodes.reduce((acc, operationNode) => {
1529
+ const tag = operationNode.tags[0];
1530
+ const groupName = tag ? group?.name?.({ group: camelCase(tag) }) ?? resolver.resolveGroupName(tag) : resolver.resolveGroupName("Client");
1531
+ if (!tag && !group) {
1532
+ const name = resolver.resolveClassName("ApiClient");
1533
+ const file = resolver.resolveFile({
1534
+ name,
1535
+ extname: ".ts"
1536
+ }, {
1537
+ root,
1538
+ output,
1539
+ group
1540
+ });
1541
+ const operationData = buildOperationData(operationNode);
1542
+ const previous = acc.find((item) => item.file.path === file.path);
1543
+ if (previous) previous.operations.push(operationData);
1544
+ else acc.push({
1545
+ name,
1546
+ file,
1547
+ operations: [operationData]
1548
+ });
1549
+ } else if (tag) {
1550
+ const name = groupName;
1551
+ const file = resolver.resolveFile({
1552
+ name,
1553
+ extname: ".ts",
1554
+ tag
1555
+ }, {
1556
+ root,
1557
+ output,
1558
+ group
1559
+ });
1560
+ const operationData = buildOperationData(operationNode);
1561
+ const previous = acc.find((item) => item.file.path === file.path);
1562
+ if (previous) previous.operations.push(operationData);
1563
+ else acc.push({
1564
+ name,
1565
+ file,
1566
+ operations: [operationData]
1567
+ });
1568
+ }
1569
+ return acc;
1570
+ }, []);
1571
+ function collectTypeImports(ops) {
1572
+ const typeImportsByFile = /* @__PURE__ */ new Map();
1573
+ const typeFilesByPath = /* @__PURE__ */ new Map();
1574
+ ops.forEach((op) => {
1575
+ const names = resolveTypeImportNames(op.node, tsResolver);
1576
+ if (!typeImportsByFile.has(op.typeFile.path)) typeImportsByFile.set(op.typeFile.path, /* @__PURE__ */ new Set());
1577
+ const imports = typeImportsByFile.get(op.typeFile.path);
1578
+ names.forEach((n) => {
1579
+ imports.add(n);
1580
+ });
1581
+ typeFilesByPath.set(op.typeFile.path, op.typeFile);
1582
+ });
1583
+ return {
1584
+ typeImportsByFile,
1585
+ typeFilesByPath
1586
+ };
1587
+ }
1588
+ function collectZodImports(ops) {
1589
+ const zodImportsByFile = /* @__PURE__ */ new Map();
1590
+ const zodFilesByPath = /* @__PURE__ */ new Map();
1591
+ ops.forEach((op) => {
1592
+ if (!op.zodFile || !zodResolver) return;
1593
+ const names = resolveZodImportNames(op.node, zodResolver);
1594
+ if (!zodImportsByFile.has(op.zodFile.path)) zodImportsByFile.set(op.zodFile.path, /* @__PURE__ */ new Set());
1595
+ const imports = zodImportsByFile.get(op.zodFile.path);
1596
+ names.forEach((n) => {
1597
+ imports.add(n);
1598
+ });
1599
+ zodFilesByPath.set(op.zodFile.path, op.zodFile);
1600
+ });
1601
+ return {
1602
+ zodImportsByFile,
1603
+ zodFilesByPath
1604
+ };
1605
+ }
1606
+ return /* @__PURE__ */ jsx(Fragment, { children: controllers.map(({ name, file, operations: ops }) => {
1607
+ const { typeImportsByFile, typeFilesByPath } = collectTypeImports(ops);
1608
+ const { zodImportsByFile, zodFilesByPath } = parser === "zod" ? collectZodImports(ops) : {
1609
+ zodImportsByFile: /* @__PURE__ */ new Map(),
1610
+ zodFilesByPath: /* @__PURE__ */ new Map()
1611
+ };
1612
+ const hasFormData = ops.some((op) => op.node.requestBody?.content?.some((e) => e.contentType === "multipart/form-data") ?? false);
1613
+ return /* @__PURE__ */ jsxs(File, {
1614
+ baseName: file.baseName,
1615
+ path: file.path,
1616
+ meta: file.meta,
1617
+ banner: resolver.resolveBanner(inputNode, {
1618
+ output,
1619
+ config
1620
+ }),
1621
+ footer: resolver.resolveFooter(inputNode, {
1622
+ output,
1623
+ config
1624
+ }),
1625
+ children: [
1626
+ importPath ? /* @__PURE__ */ jsxs(Fragment, { children: [
1627
+ /* @__PURE__ */ jsx(File.Import, {
1628
+ name: "fetch",
1629
+ path: importPath
1630
+ }),
1631
+ /* @__PURE__ */ jsx(File.Import, {
1632
+ name: ["mergeConfig"],
1633
+ path: importPath
1634
+ }),
1635
+ /* @__PURE__ */ jsx(File.Import, {
1636
+ name: [
1637
+ "Client",
1638
+ "RequestConfig",
1639
+ "ResponseErrorConfig"
1640
+ ],
1641
+ path: importPath,
1642
+ isTypeOnly: true
1643
+ })
1644
+ ] }) : /* @__PURE__ */ jsxs(Fragment, { children: [
1645
+ /* @__PURE__ */ jsx(File.Import, {
1646
+ name: ["fetch"],
1647
+ root: file.path,
1648
+ path: path.resolve(root, ".kubb/client.ts")
1649
+ }),
1650
+ /* @__PURE__ */ jsx(File.Import, {
1651
+ name: ["mergeConfig"],
1652
+ root: file.path,
1653
+ path: path.resolve(root, ".kubb/client.ts")
1654
+ }),
1655
+ /* @__PURE__ */ jsx(File.Import, {
1656
+ name: [
1657
+ "Client",
1658
+ "RequestConfig",
1659
+ "ResponseErrorConfig"
1660
+ ],
1661
+ root: file.path,
1662
+ path: path.resolve(root, ".kubb/client.ts"),
1663
+ isTypeOnly: true
1664
+ })
1665
+ ] }),
1666
+ hasFormData && /* @__PURE__ */ jsx(File.Import, {
1667
+ name: ["buildFormData"],
1668
+ root: file.path,
1669
+ path: path.resolve(root, ".kubb/config.ts")
1670
+ }),
1671
+ Array.from(typeImportsByFile.entries()).map(([filePath, importSet]) => {
1672
+ const typeFile = typeFilesByPath.get(filePath);
1673
+ if (!typeFile) return null;
1674
+ const importNames = Array.from(importSet).filter(Boolean);
1675
+ if (importNames.length === 0) return null;
1676
+ return /* @__PURE__ */ jsx(File.Import, {
1677
+ name: importNames,
1678
+ root: file.path,
1679
+ path: typeFile.path,
1680
+ isTypeOnly: true
1681
+ }, filePath);
1682
+ }),
1683
+ parser === "zod" && Array.from(zodImportsByFile.entries()).map(([filePath, importSet]) => {
1684
+ const zodFile = zodFilesByPath.get(filePath);
1685
+ if (!zodFile) return null;
1686
+ const importNames = Array.from(importSet).filter(Boolean);
1687
+ if (importNames.length === 0) return null;
1688
+ return /* @__PURE__ */ jsx(File.Import, {
1689
+ name: importNames,
1690
+ root: file.path,
1691
+ path: zodFile.path
1692
+ }, filePath);
1693
+ }),
1694
+ /* @__PURE__ */ jsx(StaticClassClient, {
1695
+ name,
1696
+ operations: ops,
1697
+ baseURL,
1698
+ dataReturnType,
1699
+ pathParamsType,
1700
+ paramsCasing,
1701
+ paramsType,
1702
+ parser
1703
+ })
1704
+ ]
1705
+ }, file.path);
1706
+ }) });
1707
+ }
1708
+ });
1709
+ //#endregion
1710
+ //#region src/resolvers/resolverClient.ts
1711
+ /**
1712
+ * Naming convention resolver for client plugin.
1713
+ *
1714
+ * Provides default naming helpers using camelCase for functions and file paths.
1715
+ *
1716
+ * @example
1717
+ * `resolverClient.default('list pets', 'function') // → 'listPets'`
1718
+ */
1719
+ const resolverClient = defineResolver(() => ({
1720
+ name: "default",
1721
+ pluginName: "plugin-client",
1722
+ default(name, type) {
1723
+ return camelCase(name, { isFile: type === "file" });
1724
+ },
1725
+ resolveName(name) {
1726
+ return this.default(name, "function");
1727
+ },
1728
+ resolvePathName(name, type) {
1729
+ return this.default(name, type);
1730
+ },
1731
+ resolveClassName(name) {
1732
+ return pascalCase(name);
1733
+ },
1734
+ resolveGroupName(name) {
1735
+ return pascalCase(name);
1736
+ },
1737
+ resolveClientPropertyName(name) {
1738
+ return camelCase(name);
1739
+ },
1740
+ resolveUrlName(node) {
1741
+ const name = this.resolveName(node.operationId);
1742
+ return `get${name.charAt(0).toUpperCase()}${name.slice(1)}Url`;
1743
+ }
1744
+ }));
1745
+ //#endregion
11
1746
  //#region src/plugin.ts
1747
+ /**
1748
+ * Canonical plugin name for `@kubb/plugin-client`, used in driver lookups and warnings.
1749
+ */
12
1750
  const pluginClientName = "plugin-client";
13
- const pluginClient = createPlugin((options) => {
1751
+ /**
1752
+ * Generates type-safe HTTP client functions or classes from an OpenAPI specification.
1753
+ * Creates client APIs by walking operations and delegating to generators.
1754
+ * Writes barrel files based on the configured `barrelType`.
1755
+ *
1756
+ * @example Client generator
1757
+ * ```ts
1758
+ * import pluginClient from '@kubb/plugin-client'
1759
+ * export default defineConfig({
1760
+ * plugins: [pluginClient({ output: { path: 'clients' } })]
1761
+ * })
1762
+ * ```
1763
+ */
1764
+ const pluginClient = definePlugin((options) => {
14
1765
  const { output = {
15
1766
  path: "clients",
16
1767
  barrelType: "named"
17
- }, group, urlType = false, exclude = [], include, override = [], transformers = {}, dataReturnType = "data", paramsType = "inline", pathParamsType = paramsType === "object" ? "object" : options.pathParamsType || "inline", operations = false, baseURL, paramsCasing, clientType = "function", parser = "client", client = "axios", importPath, contentType, bundle = false, wrapper } = options;
1768
+ }, 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;
18
1769
  const resolvedImportPath = importPath ?? (!bundle ? `@kubb/plugin-client/clients/${client}` : void 0);
19
- const defaultGenerators = [
1770
+ const selectedGenerators = options.generators ?? [
20
1771
  clientType === "staticClass" ? staticClassClientGenerator : clientType === "class" ? classClientGenerator : clientGenerator,
21
1772
  group && clientType === "function" ? groupedClientGenerator : void 0,
22
1773
  operations ? operationsGenerator : void 0
23
1774
  ].filter((x) => Boolean(x));
24
- const generators = options.generators ?? defaultGenerators;
1775
+ const groupConfig = group ? {
1776
+ ...group,
1777
+ name: group.name ? group.name : (ctx) => {
1778
+ if (group.type === "path") return `${ctx.group.split("/")[1]}`;
1779
+ return `${camelCase(ctx.group)}Controller`;
1780
+ }
1781
+ } : void 0;
25
1782
  return {
26
1783
  name: pluginClientName,
27
- options: {
28
- client,
29
- clientType,
30
- bundle,
31
- output,
32
- group,
33
- parser,
34
- dataReturnType,
35
- importPath: resolvedImportPath,
36
- paramsType,
37
- paramsCasing,
38
- pathParamsType,
39
- baseURL,
40
- urlType,
41
- wrapper
42
- },
43
- pre: [pluginOasName, parser === "zod" ? pluginZodName : void 0].filter(Boolean),
44
- resolvePath(baseName, pathMode, options) {
45
- const root = path.resolve(this.config.root, this.config.output.path);
46
- if ((pathMode ?? getMode(path.resolve(root, output.path))) === "single")
47
- /**
48
- * when output is a file then we will always append to the same file(output file), see fileManager.addOrAppend
49
- * Other plugins then need to call addOrAppend instead of just add from the fileManager class
50
- */
51
- return path.resolve(root, output.path);
52
- if (group && (options?.group?.path || options?.group?.tag)) {
53
- const groupName = group?.name ? group.name : (ctx) => {
54
- if (group?.type === "path") return `${ctx.group.split("/")[1]}`;
55
- return `${camelCase(ctx.group)}Controller`;
56
- };
57
- return path.resolve(root, output.path, groupName({ group: group.type === "path" ? options.group.path : options.group.tag }), baseName);
58
- }
59
- return path.resolve(root, output.path, baseName);
60
- },
61
- resolveName(name, type) {
62
- const resolvedName = camelCase(name, { isFile: type === "file" });
63
- if (type) return transformers?.name?.(resolvedName, type) || resolvedName;
64
- return resolvedName;
65
- },
66
- async install() {
67
- const root = path.resolve(this.config.root, this.config.output.path);
68
- const mode = getMode(path.resolve(root, output.path));
69
- const oas = await this.getOas();
70
- const baseURL = await this.getBaseURL();
71
- if (bundle && !this.plugin.options.importPath) await this.addFile({
72
- baseName: "fetch.ts",
73
- path: path.resolve(root, ".kubb/fetch.ts"),
74
- sources: [{
75
- name: "fetch",
76
- value: this.plugin.options.client === "fetch" ? source$1 : source,
77
- isExportable: true,
78
- isIndexable: true
79
- }],
80
- imports: [],
81
- exports: []
1784
+ options,
1785
+ dependencies: [pluginTsName, parser === "zod" ? pluginZodName : void 0].filter((dependency) => Boolean(dependency)),
1786
+ hooks: { "kubb:plugin:setup"(ctx) {
1787
+ const resolver = userResolver ? {
1788
+ ...resolverClient,
1789
+ ...userResolver
1790
+ } : resolverClient;
1791
+ ctx.setOptions({
1792
+ client,
1793
+ clientType,
1794
+ bundle,
1795
+ output,
1796
+ exclude,
1797
+ include,
1798
+ override,
1799
+ group: groupConfig,
1800
+ parser,
1801
+ dataReturnType,
1802
+ importPath: resolvedImportPath,
1803
+ baseURL,
1804
+ paramsType,
1805
+ paramsCasing,
1806
+ pathParamsType,
1807
+ urlType,
1808
+ sdk,
1809
+ resolver
82
1810
  });
83
- await this.addFile({
1811
+ ctx.setResolver(resolver);
1812
+ if (userTransformer) ctx.setTransformer(userTransformer);
1813
+ for (const gen of selectedGenerators) ctx.addGenerator(gen);
1814
+ const root = path.resolve(ctx.config.root, ctx.config.output.path);
1815
+ if (!resolvedImportPath?.startsWith(".")) {
1816
+ const isInlineSource = bundle && !resolvedImportPath;
1817
+ ctx.injectFile({
1818
+ baseName: "client.ts",
1819
+ path: path.resolve(root, ".kubb/client.ts"),
1820
+ sources: [ast.createSource({
1821
+ name: "client",
1822
+ nodes: isInlineSource ? [ast.createText(client === "fetch" ? source$1 : source)] : [],
1823
+ isExportable: true,
1824
+ isIndexable: true
1825
+ })],
1826
+ exports: !isInlineSource && resolvedImportPath ? [ast.createExport({ path: resolvedImportPath })] : []
1827
+ });
1828
+ }
1829
+ ctx.injectFile({
84
1830
  baseName: "config.ts",
85
1831
  path: path.resolve(root, ".kubb/config.ts"),
86
- sources: [{
1832
+ sources: [ast.createSource({
87
1833
  name: "config",
88
- value: source$2,
1834
+ nodes: [ast.createText(source$2)],
89
1835
  isExportable: false,
90
1836
  isIndexable: false
91
- }],
92
- imports: [],
93
- exports: []
94
- });
95
- const files = await new OperationGenerator(baseURL ? {
96
- ...this.plugin.options,
97
- baseURL
98
- } : this.plugin.options, {
99
- fabric: this.fabric,
100
- oas,
101
- driver: this.driver,
102
- events: this.events,
103
- plugin: this.plugin,
104
- contentType,
105
- exclude,
106
- include,
107
- override,
108
- mode
109
- }).build(...generators);
110
- await this.upsertFile(...files);
111
- const barrelFiles = await getBarrelFiles(this.fabric.files, {
112
- type: output.barrelType ?? "named",
113
- root,
114
- output,
115
- meta: { pluginName: this.plugin.name }
1837
+ })]
116
1838
  });
117
- await this.upsertFile(...barrelFiles);
118
- }
1839
+ } }
119
1840
  };
120
1841
  });
121
1842
  //#endregion
122
- export { pluginClient, pluginClientName };
1843
+ export { Client, classClientGenerator, clientGenerator, pluginClient as default, pluginClient, groupedClientGenerator, operationsGenerator, pluginClientName, resolverClient, staticClassClientGenerator };
123
1844
 
124
1845
  //# sourceMappingURL=index.js.map