@kubb/plugin-client 5.0.0-alpha.8 → 5.0.0-beta.10

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 +1818 -97
  14. package/dist/index.cjs.map +1 -1
  15. package/dist/index.d.ts +330 -4
  16. package/dist/index.js +1804 -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 +776 -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 -488
  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,1833 @@
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, jsxRenderer } 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
+ function resolveOperationTypeNames(node, resolver, options = {}) {
399
+ const { path, query, header } = getOperationParameters(node, { paramsCasing: options.paramsCasing });
400
+ const responseStatusNames = options.responseStatusNames === "error" ? resolveErrorNames(node, resolver) : options.responseStatusNames === false ? [] : resolveStatusCodeNames(node, resolver);
401
+ const exclude = new Set(options.exclude ?? []);
402
+ const paramNames = [
403
+ ...path.map((param) => resolver.resolvePathParamsName(node, param)),
404
+ ...query.map((param) => resolver.resolveQueryParamsName(node, param)),
405
+ ...header.map((param) => resolver.resolveHeaderParamsName(node, param))
406
+ ];
407
+ const bodyAndResponseNames = [node.requestBody?.content?.[0]?.schema ? resolver.resolveDataName(node) : void 0, resolver.resolveResponseName(node)];
408
+ return (options.order === "body-response-first" ? [
409
+ ...bodyAndResponseNames,
410
+ ...paramNames,
411
+ ...responseStatusNames
412
+ ] : [
413
+ ...paramNames,
414
+ ...bodyAndResponseNames,
415
+ ...responseStatusNames
416
+ ]).filter((name) => Boolean(name) && !exclude.has(name));
417
+ }
418
+ //#endregion
419
+ //#region ../../internals/shared/src/params.ts
420
+ function buildParamsMapping(originalParams, mappedParams) {
421
+ const mapping = {};
422
+ let hasChanged = false;
423
+ originalParams.forEach((param, i) => {
424
+ const mappedName = mappedParams[i]?.name ?? param.name;
425
+ mapping[param.name] = mappedName;
426
+ if (param.name !== mappedName) hasChanged = true;
427
+ });
428
+ return hasChanged ? mapping : void 0;
429
+ }
430
+ //#endregion
431
+ //#region src/functionParams.ts
432
+ const declarationPrinter$4 = functionPrinter({ mode: "declaration" });
433
+ const callPrinter = functionPrinter({ mode: "call" });
434
+ function isGroup(spec) {
435
+ return "children" in spec;
436
+ }
437
+ function createType(type) {
438
+ return type ? ast.createParamsType({
439
+ variant: "reference",
440
+ name: type
441
+ }) : void 0;
442
+ }
443
+ function createDeclarationLeaf(name, spec) {
444
+ if (spec.default !== void 0) return ast.createFunctionParameter({
445
+ name,
446
+ type: createType(spec.type),
447
+ default: spec.default,
448
+ rest: spec.mode === "inlineSpread"
449
+ });
450
+ return ast.createFunctionParameter({
451
+ name,
452
+ type: createType(spec.type),
453
+ optional: !!spec.optional,
454
+ rest: spec.mode === "inlineSpread"
455
+ });
456
+ }
457
+ function createDeclarationParam(name, spec) {
458
+ if (isGroup(spec)) return ast.createParameterGroup({
459
+ inline: spec.mode === "inlineSpread",
460
+ default: spec.default,
461
+ properties: Object.entries(spec.children).filter(([, child]) => child !== void 0).map(([childName, child]) => createDeclarationLeaf(childName, child))
462
+ });
463
+ return createDeclarationLeaf(name, spec);
464
+ }
465
+ function createCallParam(name, spec) {
466
+ if (isGroup(spec)) return ast.createParameterGroup({
467
+ inline: spec.mode === "inlineSpread",
468
+ properties: Object.entries(spec.children).filter(([, child]) => child !== void 0).map(([childName, child]) => ast.createFunctionParameter({
469
+ name: child?.mode === "inlineSpread" ? spec.mode === "inlineSpread" ? child.value ?? childName : `...${child.value ?? childName}` : child?.value ? `${childName}: ${child.value}` : childName,
470
+ rest: spec.mode === "inlineSpread" && child?.mode === "inlineSpread"
471
+ }))
472
+ });
473
+ return ast.createFunctionParameter({
474
+ name: spec.value ?? name,
475
+ rest: spec.mode === "inlineSpread"
476
+ });
477
+ }
478
+ /**
479
+ * Creates function parameter builders for generating function signatures and calls.
480
+ * Returns utilities to output constructor signatures (`toConstructor()`) or call expressions (`toCall()`).
481
+ */
482
+ function createFunctionParams(params) {
483
+ const entries = Object.entries(params).filter(([, spec]) => spec !== void 0);
484
+ return {
485
+ toConstructor() {
486
+ return declarationPrinter$4.print(ast.createFunctionParameters({ params: entries.map(([name, spec]) => createDeclarationParam(name, spec)) })) ?? "";
487
+ },
488
+ toCall() {
489
+ return callPrinter.print(ast.createFunctionParameters({ params: entries.map(([name, spec]) => createCallParam(name, spec)) })) ?? "";
490
+ }
491
+ };
492
+ }
493
+ //#endregion
494
+ //#region src/components/Url.tsx
495
+ const declarationPrinter$3 = functionPrinter({ mode: "declaration" });
496
+ function buildUrlParamsNode({ paramsType, paramsCasing, pathParamsType, node, tsResolver }) {
497
+ const urlNode = {
498
+ ...node,
499
+ parameters: node.parameters.filter((p) => p.in === "path"),
500
+ requestBody: void 0
501
+ };
502
+ return ast.createOperationParams(urlNode, {
503
+ paramsType: paramsType === "object" ? "object" : "inline",
504
+ pathParamsType: paramsType === "object" ? "object" : pathParamsType === "object" ? "object" : "inline",
505
+ paramsCasing,
506
+ resolver: tsResolver
507
+ });
508
+ }
509
+ function Url({ name, isExportable = true, isIndexable = true, baseURL, paramsType, paramsCasing, pathParamsType, node, tsResolver }) {
510
+ const path = new URLPath(node.path);
511
+ const paramsNode = buildUrlParamsNode({
512
+ paramsType,
513
+ paramsCasing,
514
+ pathParamsType,
515
+ node,
516
+ tsResolver
517
+ });
518
+ const paramsSignature = declarationPrinter$3.print(paramsNode) ?? "";
519
+ const { path: originalPathParams } = getOperationParameters(node);
520
+ const { path: casedPathParams } = getOperationParameters(node, { paramsCasing });
521
+ const pathParamsMapping = paramsCasing ? buildParamsMapping(originalPathParams, casedPathParams) : void 0;
522
+ return /* @__PURE__ */ jsx(File.Source, {
523
+ name,
524
+ isExportable,
525
+ isIndexable,
526
+ children: /* @__PURE__ */ jsxs(Function, {
527
+ name,
528
+ export: isExportable,
529
+ params: paramsSignature,
530
+ children: [
531
+ pathParamsMapping && Object.entries(pathParamsMapping).filter(([originalName, camelCaseName]) => isValidVarName(originalName) && originalName !== camelCaseName).map(([originalName, camelCaseName]) => `const ${originalName} = ${camelCaseName}`).join("\n"),
532
+ pathParamsMapping && Object.keys(pathParamsMapping).length > 0 && /* @__PURE__ */ jsx("br", {}),
533
+ /* @__PURE__ */ jsx(Const, {
534
+ name: "res",
535
+ children: `{ method: '${node.method.toUpperCase()}', url: ${path.toTemplateString({ prefix: baseURL })} as const }`
536
+ }),
537
+ /* @__PURE__ */ jsx("br", {}),
538
+ "return res"
539
+ ]
540
+ })
541
+ });
542
+ }
543
+ //#endregion
544
+ //#region src/components/Client.tsx
545
+ const declarationPrinter$2 = functionPrinter({ mode: "declaration" });
546
+ function buildClientParamsNode({ paramsType, paramsCasing, pathParamsType, node, tsResolver, isConfigurable }) {
547
+ return ast.createOperationParams(node, {
548
+ paramsType,
549
+ pathParamsType: paramsType === "object" ? "object" : pathParamsType === "object" ? "object" : "inline",
550
+ paramsCasing,
551
+ resolver: tsResolver,
552
+ extraParams: [...isConfigurable ? [ast.createFunctionParameter({
553
+ name: "config",
554
+ type: ast.createParamsType({
555
+ variant: "reference",
556
+ name: buildRequestConfigType(node, tsResolver)
557
+ }),
558
+ default: "{}"
559
+ })] : []]
560
+ });
561
+ }
562
+ function Client({ name, isExportable = true, isIndexable = true, returnType, baseURL, dataReturnType, parser, paramsType, paramsCasing, pathParamsType, node, tsResolver, zodResolver, urlName, children, isConfigurable = true }) {
563
+ const path = new URLPath(node.path);
564
+ const { defaultContentType: contentType, isMultipleContentTypes, hasFormData } = getContentTypeInfo(node);
565
+ const isFormData = !isMultipleContentTypes && contentType === "multipart/form-data";
566
+ const { path: originalPathParams, query: originalQueryParams, header: originalHeaderParams } = getOperationParameters(node);
567
+ const { path: casedPathParams, query: casedQueryParams, header: casedHeaderParams } = getOperationParameters(node, { paramsCasing });
568
+ const pathParamsMapping = paramsCasing && !urlName ? buildParamsMapping(originalPathParams, casedPathParams) : void 0;
569
+ const queryParamsMapping = paramsCasing ? buildParamsMapping(originalQueryParams, casedQueryParams) : void 0;
570
+ const headerParamsMapping = paramsCasing ? buildParamsMapping(originalHeaderParams, casedHeaderParams) : void 0;
571
+ const requestName = node.requestBody?.content?.[0]?.schema ? tsResolver.resolveDataName(node) : void 0;
572
+ const responseName = tsResolver.resolveResponseName(node);
573
+ const queryParamsName = originalQueryParams.length > 0 ? tsResolver.resolveQueryParamsName(node, originalQueryParams[0]) : void 0;
574
+ const headerParamsName = originalHeaderParams.length > 0 ? tsResolver.resolveHeaderParamsName(node, originalHeaderParams[0]) : void 0;
575
+ const zodResponseName = zodResolver && parser === "zod" ? zodResolver.resolveResponseName?.(node) : void 0;
576
+ const zodRequestName = zodResolver && parser === "zod" && node.requestBody?.content?.[0]?.schema ? zodResolver.resolveDataName?.(node) : void 0;
577
+ const errorNames = node.responses.filter((r) => {
578
+ return Number.parseInt(r.statusCode, 10) >= 400;
579
+ }).map((r) => tsResolver.resolveResponseStatusName(node, r.statusCode));
580
+ const headers = [!isMultipleContentTypes && contentType !== "application/json" && contentType !== "multipart/form-data" ? `'Content-Type': '${contentType}'` : void 0, headerParamsName ? headerParamsMapping ? "...mappedHeaders" : "...headers" : void 0].filter(Boolean);
581
+ const generics = [
582
+ responseName,
583
+ `ResponseErrorConfig<${errorNames.length > 0 ? errorNames.join(" | ") : "Error"}>`,
584
+ requestName || "unknown"
585
+ ].filter(Boolean);
586
+ const paramsNode = buildClientParamsNode({
587
+ paramsType,
588
+ paramsCasing,
589
+ pathParamsType,
590
+ node,
591
+ tsResolver,
592
+ isConfigurable
593
+ });
594
+ const paramsSignature = declarationPrinter$2.print(paramsNode) ?? "";
595
+ const urlParamsNode = buildUrlParamsNode({
596
+ paramsType,
597
+ paramsCasing,
598
+ pathParamsType,
599
+ node,
600
+ tsResolver
601
+ });
602
+ const urlParamsCall = functionPrinter({ mode: "call" }).print(urlParamsNode) ?? "";
603
+ const clientParams = createFunctionParams({ config: {
604
+ mode: "object",
605
+ children: {
606
+ method: { value: JSON.stringify(node.method.toUpperCase()) },
607
+ url: { value: urlName ? `${urlName}(${urlParamsCall}).url.toString()` : path.template },
608
+ baseURL: baseURL && !urlName ? { value: `\`${baseURL}\`` } : void 0,
609
+ params: queryParamsName ? queryParamsMapping ? { value: "mappedParams" } : {} : void 0,
610
+ data: requestName ? { value: isMultipleContentTypes && hasFormData ? "contentType === 'multipart/form-data' ? formData as FormData : requestData" : isFormData ? "formData as FormData" : "requestData" } : void 0,
611
+ contentType: isConfigurable && isMultipleContentTypes ? {} : void 0,
612
+ requestConfig: isConfigurable ? { mode: "inlineSpread" } : void 0,
613
+ headers: headers.length ? { value: isConfigurable ? `{ ${headers.join(", ")}, ...requestConfig.headers }` : `{ ${headers.join(", ")} }` } : void 0
614
+ }
615
+ } });
616
+ const childrenElement = children ? children : /* @__PURE__ */ jsxs(Fragment, { children: [
617
+ dataReturnType === "full" && parser === "zod" && zodResponseName && `return {...res, data: ${zodResponseName}.parse(res.data)}`,
618
+ dataReturnType === "data" && parser === "zod" && zodResponseName && `return ${zodResponseName}.parse(res.data)`,
619
+ dataReturnType === "full" && parser === "client" && "return res",
620
+ dataReturnType === "data" && parser === "client" && "return res.data"
621
+ ] });
622
+ return /* @__PURE__ */ jsxs(Fragment, { children: [/* @__PURE__ */ jsx("br", {}), /* @__PURE__ */ jsx(File.Source, {
623
+ name,
624
+ isExportable,
625
+ isIndexable,
626
+ children: /* @__PURE__ */ jsxs(Function, {
627
+ name,
628
+ async: true,
629
+ export: isExportable,
630
+ params: paramsSignature,
631
+ JSDoc: { comments: buildOperationComments(node, {
632
+ link: "urlPath",
633
+ linkPosition: "beforeDeprecated",
634
+ splitLines: true
635
+ }) },
636
+ returnType,
637
+ children: [
638
+ isConfigurable ? `const { client: request = fetch, ${isMultipleContentTypes ? `contentType = ${JSON.stringify(contentType)}, ` : ""}...requestConfig } = config` : "",
639
+ /* @__PURE__ */ jsx("br", {}),
640
+ /* @__PURE__ */ jsx("br", {}),
641
+ pathParamsMapping && Object.entries(pathParamsMapping).filter(([originalName, camelCaseName]) => isValidVarName(originalName) && originalName !== camelCaseName).map(([originalName, camelCaseName]) => `const ${originalName} = ${camelCaseName}`).join("\n"),
642
+ pathParamsMapping && /* @__PURE__ */ jsxs(Fragment, { children: [/* @__PURE__ */ jsx("br", {}), /* @__PURE__ */ jsx("br", {})] }),
643
+ queryParamsMapping && queryParamsName && /* @__PURE__ */ jsxs(Fragment, { children: [
644
+ `const mappedParams = params ? { ${Object.entries(queryParamsMapping).map(([originalName, camelCaseName]) => `"${originalName}": params.${camelCaseName}`).join(", ")} } : undefined`,
645
+ /* @__PURE__ */ jsx("br", {}),
646
+ /* @__PURE__ */ jsx("br", {})
647
+ ] }),
648
+ headerParamsMapping && headerParamsName && /* @__PURE__ */ jsxs(Fragment, { children: [
649
+ `const mappedHeaders = headers ? { ${Object.entries(headerParamsMapping).map(([originalName, camelCaseName]) => `"${originalName}": headers.${camelCaseName}`).join(", ")} } : undefined`,
650
+ /* @__PURE__ */ jsx("br", {}),
651
+ /* @__PURE__ */ jsx("br", {})
652
+ ] }),
653
+ parser === "zod" && zodRequestName ? `const requestData = ${zodRequestName}.parse(data)` : requestName && "const requestData = data",
654
+ /* @__PURE__ */ jsx("br", {}),
655
+ (isFormData || isMultipleContentTypes && hasFormData) && requestName && "const formData = buildFormData(requestData)",
656
+ /* @__PURE__ */ jsx("br", {}),
657
+ isConfigurable ? `const res = await request<${generics.join(", ")}>(${clientParams.toCall()})` : `const res = await fetch<${generics.join(", ")}>(${clientParams.toCall()})`,
658
+ /* @__PURE__ */ jsx("br", {}),
659
+ childrenElement
660
+ ]
661
+ })
662
+ })] });
663
+ }
664
+ //#endregion
665
+ //#region src/utils.ts
666
+ /**
667
+ * Builds HTTP headers array for a client request.
668
+ * Includes Content-Type (if not default) and spreads header parameters if present.
669
+ */
670
+ function buildHeaders(contentType, hasHeaderParams) {
671
+ return [contentType !== "application/json" && contentType !== "multipart/form-data" ? `'Content-Type': '${contentType}'` : void 0, hasHeaderParams ? "...headers" : void 0].filter(Boolean);
672
+ }
673
+ /**
674
+ * Builds TypeScript generic parameters for a client method.
675
+ * Includes response type, error type, and optional request type.
676
+ */
677
+ function buildGenerics(node, tsResolver) {
678
+ const responseName = tsResolver.resolveResponseName(node);
679
+ const requestName = node.requestBody?.content?.[0]?.schema ? tsResolver.resolveDataName(node) : void 0;
680
+ const errorNames = node.responses.filter((r) => Number.parseInt(r.statusCode, 10) >= 400).map((r) => tsResolver.resolveResponseStatusName(node, r.statusCode));
681
+ return [
682
+ responseName,
683
+ `ResponseErrorConfig<${errorNames.length > 0 ? errorNames.join(" | ") : "Error"}>`,
684
+ requestName || "unknown"
685
+ ].filter(Boolean);
686
+ }
687
+ /**
688
+ * Builds the parameters object for a class-based client method.
689
+ * Includes URL, method, base URL, headers, and request/response data.
690
+ */
691
+ function buildClassClientParams({ node, path, baseURL, tsResolver, isFormData, isMultipleContentTypes, hasFormData, headers }) {
692
+ const { query: queryParams } = getOperationParameters(node);
693
+ const queryParamsName = queryParams.length > 0 ? tsResolver.resolveQueryParamsName(node, queryParams[0]) : void 0;
694
+ const requestName = node.requestBody?.content?.[0]?.schema ? tsResolver.resolveDataName(node) : void 0;
695
+ return createFunctionParams({ config: {
696
+ mode: "object",
697
+ children: {
698
+ requestConfig: { mode: "inlineSpread" },
699
+ method: { value: JSON.stringify(node.method.toUpperCase()) },
700
+ url: { value: path.template },
701
+ baseURL: baseURL ? { value: JSON.stringify(baseURL) } : void 0,
702
+ params: queryParamsName ? {} : void 0,
703
+ data: requestName ? { value: isMultipleContentTypes && hasFormData ? "contentType === 'multipart/form-data' ? formData as FormData : requestData" : isFormData ? "formData as FormData" : "requestData" } : void 0,
704
+ contentType: isMultipleContentTypes ? {} : void 0,
705
+ headers: headers.length ? { value: `{ ${headers.join(", ")}, ...requestConfig.headers }` } : void 0
706
+ }
707
+ } });
708
+ }
709
+ /**
710
+ * Builds the request data parsing line for client methods.
711
+ * Applies Zod validation if configured, otherwise uses data directly.
712
+ */
713
+ function buildRequestDataLine({ parser, node, zodResolver }) {
714
+ const zodRequestName = zodResolver && parser === "zod" && node.requestBody?.content?.[0]?.schema ? zodResolver.resolveDataName?.(node) : void 0;
715
+ if (parser === "zod" && zodRequestName) return `const requestData = ${zodRequestName}.parse(data)`;
716
+ if (node.requestBody?.content?.[0]?.schema) return "const requestData = data";
717
+ return "";
718
+ }
719
+ /**
720
+ * Builds the form data conversion line for file upload requests.
721
+ * Returns empty string if not applicable.
722
+ */
723
+ function buildFormDataLine(isFormData, hasRequest) {
724
+ return isFormData && hasRequest ? "const formData = buildFormData(requestData)" : "";
725
+ }
726
+ /**
727
+ * Builds the return statement for a client method.
728
+ * Applies Zod validation to response data if configured, otherwise returns raw response.
729
+ */
730
+ function buildReturnStatement({ dataReturnType, parser, node, zodResolver }) {
731
+ const zodResponseName = zodResolver && parser === "zod" ? zodResolver.resolveResponseName?.(node) : void 0;
732
+ if (dataReturnType === "full" && parser === "zod" && zodResponseName) return `return {...res, data: ${zodResponseName}.parse(res.data)}`;
733
+ if (dataReturnType === "data" && parser === "zod" && zodResponseName) return `return ${zodResponseName}.parse(res.data)`;
734
+ if (dataReturnType === "full" && parser === "client") return "return res";
735
+ return "return res.data";
736
+ }
737
+ //#endregion
738
+ //#region src/components/ClassClient.tsx
739
+ const declarationPrinter$1 = functionPrinter({ mode: "declaration" });
740
+ function generateMethod$1({ node, name, tsResolver, zodResolver, baseURL, dataReturnType, parser, paramsType, paramsCasing, pathParamsType }) {
741
+ const path = new URLPath(node.path, { casing: paramsCasing });
742
+ const { defaultContentType: contentType, isMultipleContentTypes, hasFormData } = getContentTypeInfo(node);
743
+ const isFormData = !isMultipleContentTypes && contentType === "multipart/form-data";
744
+ const { header: headerParams } = getOperationParameters(node);
745
+ const headerParamsName = headerParams.length > 0 ? tsResolver.resolveHeaderParamsName(node, headerParams[0]) : void 0;
746
+ const headers = isMultipleContentTypes ? headerParamsName ? ["...headers"] : [] : buildHeaders(contentType, !!headerParamsName);
747
+ const generics = buildGenerics(node, tsResolver);
748
+ const paramsNode = buildClientParamsNode({
749
+ paramsType,
750
+ paramsCasing,
751
+ pathParamsType,
752
+ node,
753
+ tsResolver,
754
+ isConfigurable: true
755
+ });
756
+ const paramsSignature = declarationPrinter$1.print(paramsNode) ?? "";
757
+ const clientParams = buildClassClientParams({
758
+ node,
759
+ path,
760
+ baseURL,
761
+ tsResolver,
762
+ isFormData,
763
+ isMultipleContentTypes,
764
+ hasFormData,
765
+ headers
766
+ });
767
+ const jsdoc = buildJSDoc(buildOperationComments(node, {
768
+ link: "urlPath",
769
+ linkPosition: "beforeDeprecated",
770
+ splitLines: true
771
+ }));
772
+ const requestDataLine = buildRequestDataLine({
773
+ parser,
774
+ node,
775
+ zodResolver
776
+ });
777
+ const formDataLine = buildFormDataLine(isFormData || isMultipleContentTypes && hasFormData, !!node.requestBody?.content?.[0]?.schema);
778
+ const returnStatement = buildReturnStatement({
779
+ dataReturnType,
780
+ parser,
781
+ node,
782
+ zodResolver
783
+ });
784
+ return `${jsdoc}async ${name}(${paramsSignature}) {\n${[
785
+ `const { client: request = fetch, ${isMultipleContentTypes ? `contentType = ${JSON.stringify(contentType)}, ` : ""}...requestConfig } = mergeConfig(this.#config, config)`,
786
+ "",
787
+ requestDataLine,
788
+ formDataLine,
789
+ `const res = await request<${generics.join(", ")}>(${clientParams.toCall()})`,
790
+ returnStatement
791
+ ].filter(Boolean).map((line) => ` ${line}`).join("\n")}\n }`;
792
+ }
793
+ __name(generateMethod$1, "generateMethod");
794
+ function ClassClient({ name, isExportable = true, isIndexable = true, operations, baseURL, dataReturnType, parser, paramsType, paramsCasing, pathParamsType, children }) {
795
+ const classCode = `export class ${name} {
796
+ #config: Partial<RequestConfig> & { client?: Client }
797
+
798
+ constructor(config: Partial<RequestConfig> & { client?: Client } = {}) {
799
+ this.#config = config
800
+ }
801
+
802
+ ${operations.map(({ node, name: methodName, tsResolver, zodResolver }) => generateMethod$1({
803
+ node,
804
+ name: methodName,
805
+ tsResolver,
806
+ zodResolver,
807
+ baseURL,
808
+ dataReturnType,
809
+ parser,
810
+ paramsType,
811
+ paramsCasing,
812
+ pathParamsType
813
+ })).join("\n\n")}
814
+ }`;
815
+ return /* @__PURE__ */ jsxs(File.Source, {
816
+ name,
817
+ isExportable,
818
+ isIndexable,
819
+ children: [classCode, children]
820
+ });
821
+ }
822
+ //#endregion
823
+ //#region src/components/WrapperClient.tsx
824
+ function WrapperClient({ name, controllers, isExportable = true, isIndexable = true }) {
825
+ const classCode = `export class ${name} {
826
+ ${controllers.map(({ className, propertyName }) => ` readonly ${propertyName}: ${className}`).join("\n")}
827
+
828
+ constructor(config: Partial<RequestConfig> & { client?: Client } = {}) {
829
+ ${controllers.map(({ className, propertyName }) => ` this.${propertyName} = new ${className}(config)`).join("\n")}
830
+ }
831
+ }`;
832
+ return /* @__PURE__ */ jsx(File.Source, {
833
+ name,
834
+ isExportable,
835
+ isIndexable,
836
+ children: classCode
837
+ });
838
+ }
839
+ //#endregion
840
+ //#region src/generators/classClientGenerator.tsx
841
+ function resolveTypeImportNames$1(node, tsResolver) {
842
+ return resolveOperationTypeNames(node, tsResolver, { order: "body-response-first" });
843
+ }
844
+ __name(resolveTypeImportNames$1, "resolveTypeImportNames");
845
+ function resolveZodImportNames$1(node, zodResolver) {
846
+ return [zodResolver.resolveResponseName?.(node), node.requestBody?.content?.[0]?.schema ? zodResolver.resolveDataName?.(node) : void 0].filter((n) => Boolean(n));
847
+ }
848
+ __name(resolveZodImportNames$1, "resolveZodImportNames");
849
+ const classClientGenerator = defineGenerator({
850
+ name: "classClient",
851
+ renderer: jsxRenderer,
852
+ operations(nodes, ctx) {
853
+ const { adapter, config, driver, resolver, root } = ctx;
854
+ const { output, group, dataReturnType, paramsCasing, paramsType, pathParamsType, parser, importPath, sdk } = ctx.options;
855
+ const baseURL = ctx.options.baseURL ?? adapter.inputNode?.meta?.baseURL;
856
+ const pluginTs = driver.getPlugin(pluginTsName);
857
+ if (!pluginTs) return null;
858
+ const tsResolver = driver.getResolver(pluginTsName);
859
+ const tsPluginOptions = pluginTs.options;
860
+ const pluginZod = parser === "zod" ? driver.getPlugin(pluginZodName) : void 0;
861
+ const zodResolver = pluginZod ? driver.getResolver(pluginZodName) : void 0;
862
+ function buildOperationData(node) {
863
+ const typeFile = tsResolver.resolveFile({
864
+ name: node.operationId,
865
+ extname: ".ts",
866
+ tag: node.tags[0] ?? "default",
867
+ path: node.path
868
+ }, {
869
+ root,
870
+ output: tsPluginOptions?.output ?? output,
871
+ group: tsPluginOptions?.group
872
+ });
873
+ const zodFile = zodResolver && pluginZod?.options ? zodResolver.resolveFile({
874
+ name: node.operationId,
875
+ extname: ".ts",
876
+ tag: node.tags[0] ?? "default",
877
+ path: node.path
878
+ }, {
879
+ root,
880
+ output: pluginZod.options?.output ?? output,
881
+ group: pluginZod.options?.group
882
+ }) : void 0;
883
+ return {
884
+ node,
885
+ name: resolver.resolveName(node.operationId),
886
+ tsResolver,
887
+ zodResolver,
888
+ typeFile,
889
+ zodFile
890
+ };
891
+ }
892
+ const controllers = nodes.reduce((acc, operationNode) => {
893
+ const tag = operationNode.tags[0];
894
+ const groupName = tag ? group?.name?.({ group: camelCase(tag) }) ?? resolver.resolveGroupName(tag) : resolver.resolveGroupName("Client");
895
+ if (!tag && !group) {
896
+ const name = resolver.resolveClassName("ApiClient");
897
+ const file = resolver.resolveFile({
898
+ name,
899
+ extname: ".ts"
900
+ }, {
901
+ root,
902
+ output,
903
+ group
904
+ });
905
+ const operationData = buildOperationData(operationNode);
906
+ const previous = acc.find((item) => item.file.path === file.path);
907
+ if (previous) previous.operations.push(operationData);
908
+ else acc.push({
909
+ name,
910
+ propertyName: resolver.resolveClientPropertyName(name),
911
+ file,
912
+ operations: [operationData]
913
+ });
914
+ } else if (tag) {
915
+ const name = groupName;
916
+ const file = resolver.resolveFile({
917
+ name,
918
+ extname: ".ts",
919
+ tag
920
+ }, {
921
+ root,
922
+ output,
923
+ group
924
+ });
925
+ const operationData = buildOperationData(operationNode);
926
+ const previous = acc.find((item) => item.file.path === file.path);
927
+ if (previous) previous.operations.push(operationData);
928
+ else acc.push({
929
+ name,
930
+ propertyName: resolver.resolveClientPropertyName(name),
931
+ file,
932
+ operations: [operationData]
933
+ });
934
+ }
935
+ return acc;
936
+ }, []);
937
+ function collectTypeImports(ops) {
938
+ const typeImportsByFile = /* @__PURE__ */ new Map();
939
+ const typeFilesByPath = /* @__PURE__ */ new Map();
940
+ ops.forEach((op) => {
941
+ const names = resolveTypeImportNames$1(op.node, tsResolver);
942
+ if (!typeImportsByFile.has(op.typeFile.path)) typeImportsByFile.set(op.typeFile.path, /* @__PURE__ */ new Set());
943
+ const imports = typeImportsByFile.get(op.typeFile.path);
944
+ names.forEach((n) => {
945
+ imports.add(n);
946
+ });
947
+ typeFilesByPath.set(op.typeFile.path, op.typeFile);
948
+ });
949
+ return {
950
+ typeImportsByFile,
951
+ typeFilesByPath
952
+ };
953
+ }
954
+ function collectZodImports(ops) {
955
+ const zodImportsByFile = /* @__PURE__ */ new Map();
956
+ const zodFilesByPath = /* @__PURE__ */ new Map();
957
+ ops.forEach((op) => {
958
+ if (!op.zodFile || !zodResolver) return;
959
+ const names = resolveZodImportNames$1(op.node, zodResolver);
960
+ if (!zodImportsByFile.has(op.zodFile.path)) zodImportsByFile.set(op.zodFile.path, /* @__PURE__ */ new Set());
961
+ const imports = zodImportsByFile.get(op.zodFile.path);
962
+ names.forEach((n) => {
963
+ imports.add(n);
964
+ });
965
+ zodFilesByPath.set(op.zodFile.path, op.zodFile);
966
+ });
967
+ return {
968
+ zodImportsByFile,
969
+ zodFilesByPath
970
+ };
971
+ }
972
+ const files = controllers.map(({ name, file, operations: ops }) => {
973
+ const { typeImportsByFile, typeFilesByPath } = collectTypeImports(ops);
974
+ const { zodImportsByFile, zodFilesByPath } = parser === "zod" ? collectZodImports(ops) : {
975
+ zodImportsByFile: /* @__PURE__ */ new Map(),
976
+ zodFilesByPath: /* @__PURE__ */ new Map()
977
+ };
978
+ const hasFormData = ops.some((op) => op.node.requestBody?.content?.some((e) => e.contentType === "multipart/form-data") ?? false);
979
+ return /* @__PURE__ */ jsxs(File, {
980
+ baseName: file.baseName,
981
+ path: file.path,
982
+ meta: file.meta,
983
+ banner: resolver.resolveBanner(adapter.inputNode, {
984
+ output,
985
+ config
986
+ }),
987
+ footer: resolver.resolveFooter(adapter.inputNode, {
988
+ output,
989
+ config
990
+ }),
991
+ children: [
992
+ importPath ? /* @__PURE__ */ jsxs(Fragment, { children: [
993
+ /* @__PURE__ */ jsx(File.Import, {
994
+ name: "fetch",
995
+ path: importPath
996
+ }),
997
+ /* @__PURE__ */ jsx(File.Import, {
998
+ name: ["mergeConfig"],
999
+ path: importPath
1000
+ }),
1001
+ /* @__PURE__ */ jsx(File.Import, {
1002
+ name: [
1003
+ "Client",
1004
+ "RequestConfig",
1005
+ "ResponseErrorConfig"
1006
+ ],
1007
+ path: importPath,
1008
+ isTypeOnly: true
1009
+ })
1010
+ ] }) : /* @__PURE__ */ jsxs(Fragment, { children: [
1011
+ /* @__PURE__ */ jsx(File.Import, {
1012
+ name: ["fetch"],
1013
+ root: file.path,
1014
+ path: path.resolve(root, ".kubb/client.ts")
1015
+ }),
1016
+ /* @__PURE__ */ jsx(File.Import, {
1017
+ name: ["mergeConfig"],
1018
+ root: file.path,
1019
+ path: path.resolve(root, ".kubb/client.ts")
1020
+ }),
1021
+ /* @__PURE__ */ jsx(File.Import, {
1022
+ name: [
1023
+ "Client",
1024
+ "RequestConfig",
1025
+ "ResponseErrorConfig"
1026
+ ],
1027
+ root: file.path,
1028
+ path: path.resolve(root, ".kubb/client.ts"),
1029
+ isTypeOnly: true
1030
+ })
1031
+ ] }),
1032
+ hasFormData && /* @__PURE__ */ jsx(File.Import, {
1033
+ name: ["buildFormData"],
1034
+ root: file.path,
1035
+ path: path.resolve(root, ".kubb/config.ts")
1036
+ }),
1037
+ Array.from(typeImportsByFile.entries()).map(([filePath, importSet]) => {
1038
+ const typeFile = typeFilesByPath.get(filePath);
1039
+ if (!typeFile) return null;
1040
+ const importNames = Array.from(importSet).filter(Boolean);
1041
+ if (importNames.length === 0) return null;
1042
+ return /* @__PURE__ */ jsx(File.Import, {
1043
+ name: importNames,
1044
+ root: file.path,
1045
+ path: typeFile.path,
1046
+ isTypeOnly: true
1047
+ }, filePath);
1048
+ }),
1049
+ parser === "zod" && Array.from(zodImportsByFile.entries()).map(([filePath, importSet]) => {
1050
+ const zodFile = zodFilesByPath.get(filePath);
1051
+ if (!zodFile) 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: zodFile.path
1058
+ }, filePath);
1059
+ }),
1060
+ /* @__PURE__ */ jsx(ClassClient, {
1061
+ name,
1062
+ operations: ops,
1063
+ baseURL,
1064
+ dataReturnType,
1065
+ pathParamsType,
1066
+ paramsCasing,
1067
+ paramsType,
1068
+ parser
1069
+ })
1070
+ ]
1071
+ }, file.path);
1072
+ });
1073
+ if (sdk) {
1074
+ const sdkFile = resolver.resolveFile({
1075
+ name: sdk.className,
1076
+ extname: ".ts"
1077
+ }, {
1078
+ root,
1079
+ output,
1080
+ group
1081
+ });
1082
+ files.push(/* @__PURE__ */ jsxs(File, {
1083
+ baseName: sdkFile.baseName,
1084
+ path: sdkFile.path,
1085
+ meta: sdkFile.meta,
1086
+ banner: resolver.resolveBanner(adapter.inputNode, {
1087
+ output,
1088
+ config
1089
+ }),
1090
+ footer: resolver.resolveFooter(adapter.inputNode, {
1091
+ output,
1092
+ config
1093
+ }),
1094
+ children: [
1095
+ importPath ? /* @__PURE__ */ jsx(File.Import, {
1096
+ name: ["Client", "RequestConfig"],
1097
+ path: importPath,
1098
+ isTypeOnly: true
1099
+ }) : /* @__PURE__ */ jsx(File.Import, {
1100
+ name: ["Client", "RequestConfig"],
1101
+ root: sdkFile.path,
1102
+ path: path.resolve(root, ".kubb/client.ts"),
1103
+ isTypeOnly: true
1104
+ }),
1105
+ controllers.map(({ name, file }) => /* @__PURE__ */ jsx(File.Import, {
1106
+ name: [name],
1107
+ root: sdkFile.path,
1108
+ path: file.path
1109
+ }, name)),
1110
+ /* @__PURE__ */ jsx(WrapperClient, {
1111
+ name: sdk.className,
1112
+ controllers: controllers.map(({ name, propertyName }) => ({
1113
+ className: name,
1114
+ propertyName
1115
+ }))
1116
+ })
1117
+ ]
1118
+ }, sdkFile.path));
1119
+ }
1120
+ return /* @__PURE__ */ jsx(Fragment, { children: files });
1121
+ }
1122
+ });
1123
+ //#endregion
1124
+ //#region src/generators/clientGenerator.tsx
1125
+ const clientGenerator = defineGenerator({
1126
+ name: "client",
1127
+ renderer: jsxRenderer,
1128
+ operation(node, ctx) {
1129
+ const { adapter, config, driver, resolver, root } = ctx;
1130
+ const { output, urlType, dataReturnType, paramsCasing, paramsType, pathParamsType, parser, importPath, group } = ctx.options;
1131
+ const baseURL = ctx.options.baseURL ?? adapter.inputNode?.meta?.baseURL;
1132
+ const pluginTs = driver.getPlugin(pluginTsName);
1133
+ if (!pluginTs) return null;
1134
+ const tsResolver = driver.getResolver(pluginTsName);
1135
+ const pluginZod = parser === "zod" ? driver.getPlugin(pluginZodName) : void 0;
1136
+ const zodResolver = pluginZod ? driver.getResolver(pluginZodName) : void 0;
1137
+ const importedTypeNames = resolveOperationTypeNames(node, tsResolver, { paramsCasing });
1138
+ const importedZodNames = zodResolver && parser === "zod" ? [zodResolver.resolveResponseName?.(node), node.requestBody?.content?.[0]?.schema ? zodResolver.resolveDataName?.(node) : void 0].filter((name) => Boolean(name)) : [];
1139
+ const meta = {
1140
+ name: resolver.resolveName(node.operationId),
1141
+ urlName: resolver.resolveUrlName(node),
1142
+ file: resolver.resolveFile({
1143
+ name: node.operationId,
1144
+ extname: ".ts",
1145
+ tag: node.tags[0] ?? "default",
1146
+ path: node.path
1147
+ }, {
1148
+ root,
1149
+ output,
1150
+ group
1151
+ }),
1152
+ fileTs: tsResolver.resolveFile({
1153
+ name: node.operationId,
1154
+ extname: ".ts",
1155
+ tag: node.tags[0] ?? "default",
1156
+ path: node.path
1157
+ }, {
1158
+ root,
1159
+ output: pluginTs.options?.output ?? output,
1160
+ group: pluginTs.options?.group
1161
+ }),
1162
+ fileZod: zodResolver && pluginZod?.options ? zodResolver.resolveFile({
1163
+ name: node.operationId,
1164
+ extname: ".ts",
1165
+ tag: node.tags[0] ?? "default",
1166
+ path: node.path
1167
+ }, {
1168
+ root,
1169
+ output: pluginZod.options.output ?? output,
1170
+ group: pluginZod.options?.group
1171
+ }) : void 0
1172
+ };
1173
+ const hasFormData = node.requestBody?.content?.some((e) => e.contentType === "multipart/form-data") ?? false;
1174
+ return /* @__PURE__ */ jsxs(File, {
1175
+ baseName: meta.file.baseName,
1176
+ path: meta.file.path,
1177
+ meta: meta.file.meta,
1178
+ banner: resolver.resolveBanner(adapter.inputNode, {
1179
+ output,
1180
+ config
1181
+ }),
1182
+ footer: resolver.resolveFooter(adapter.inputNode, {
1183
+ output,
1184
+ config
1185
+ }),
1186
+ children: [
1187
+ importPath ? /* @__PURE__ */ jsxs(Fragment, { children: [/* @__PURE__ */ jsx(File.Import, {
1188
+ name: "fetch",
1189
+ path: importPath
1190
+ }), /* @__PURE__ */ jsx(File.Import, {
1191
+ name: [
1192
+ "Client",
1193
+ "RequestConfig",
1194
+ "ResponseErrorConfig"
1195
+ ],
1196
+ path: importPath,
1197
+ isTypeOnly: true
1198
+ })] }) : /* @__PURE__ */ jsxs(Fragment, { children: [/* @__PURE__ */ jsx(File.Import, {
1199
+ name: ["fetch"],
1200
+ root: meta.file.path,
1201
+ path: path.resolve(root, ".kubb/client.ts")
1202
+ }), /* @__PURE__ */ jsx(File.Import, {
1203
+ name: [
1204
+ "Client",
1205
+ "RequestConfig",
1206
+ "ResponseErrorConfig"
1207
+ ],
1208
+ root: meta.file.path,
1209
+ path: path.resolve(root, ".kubb/client.ts"),
1210
+ isTypeOnly: true
1211
+ })] }),
1212
+ hasFormData && /* @__PURE__ */ jsx(File.Import, {
1213
+ name: ["buildFormData"],
1214
+ root: meta.file.path,
1215
+ path: path.resolve(root, ".kubb/config.ts")
1216
+ }),
1217
+ meta.fileZod && importedZodNames.length > 0 && /* @__PURE__ */ jsx(File.Import, {
1218
+ name: importedZodNames,
1219
+ root: meta.file.path,
1220
+ path: meta.fileZod.path
1221
+ }),
1222
+ meta.fileTs && importedTypeNames.length > 0 && /* @__PURE__ */ jsx(File.Import, {
1223
+ name: Array.from(new Set(importedTypeNames)),
1224
+ root: meta.file.path,
1225
+ path: meta.fileTs.path,
1226
+ isTypeOnly: true
1227
+ }),
1228
+ /* @__PURE__ */ jsx(Url, {
1229
+ name: meta.urlName,
1230
+ baseURL,
1231
+ pathParamsType,
1232
+ paramsCasing,
1233
+ paramsType,
1234
+ node,
1235
+ tsResolver,
1236
+ isIndexable: urlType === "export",
1237
+ isExportable: urlType === "export"
1238
+ }),
1239
+ /* @__PURE__ */ jsx(Client, {
1240
+ name: meta.name,
1241
+ urlName: meta.urlName,
1242
+ baseURL,
1243
+ dataReturnType,
1244
+ pathParamsType,
1245
+ paramsCasing,
1246
+ paramsType,
1247
+ node,
1248
+ tsResolver,
1249
+ zodResolver,
1250
+ parser
1251
+ })
1252
+ ]
1253
+ });
1254
+ }
1255
+ });
1256
+ //#endregion
1257
+ //#region src/generators/groupedClientGenerator.tsx
1258
+ const groupedClientGenerator = defineGenerator({
1259
+ name: "groupedClient",
1260
+ renderer: jsxRenderer,
1261
+ operations(nodes, ctx) {
1262
+ const { config, resolver, adapter, root } = ctx;
1263
+ const { output, group } = ctx.options;
1264
+ return /* @__PURE__ */ jsx(Fragment, { children: nodes.reduce((acc, operationNode) => {
1265
+ if (group?.type === "tag") {
1266
+ const tag = operationNode.tags[0];
1267
+ const name = tag ? group?.name?.({ group: camelCase(tag) }) : void 0;
1268
+ if (!tag || !name) return acc;
1269
+ const file = resolver.resolveFile({
1270
+ name,
1271
+ extname: ".ts",
1272
+ tag
1273
+ }, {
1274
+ root,
1275
+ output,
1276
+ group
1277
+ });
1278
+ const clientFile = resolver.resolveFile({
1279
+ name: operationNode.operationId,
1280
+ extname: ".ts",
1281
+ tag: operationNode.tags[0] ?? "default",
1282
+ path: operationNode.path
1283
+ }, {
1284
+ root,
1285
+ output,
1286
+ group
1287
+ });
1288
+ const client = {
1289
+ name: resolver.resolveName(operationNode.operationId),
1290
+ file: clientFile
1291
+ };
1292
+ const previous = acc.find((item) => item.file.path === file.path);
1293
+ if (previous) previous.clients.push(client);
1294
+ else acc.push({
1295
+ name,
1296
+ file,
1297
+ clients: [client]
1298
+ });
1299
+ }
1300
+ return acc;
1301
+ }, []).map(({ name, file, clients }) => {
1302
+ return /* @__PURE__ */ jsxs(File, {
1303
+ baseName: file.baseName,
1304
+ path: file.path,
1305
+ meta: file.meta,
1306
+ banner: resolver.resolveBanner(adapter.inputNode, {
1307
+ output,
1308
+ config
1309
+ }),
1310
+ footer: resolver.resolveFooter(adapter.inputNode, {
1311
+ output,
1312
+ config
1313
+ }),
1314
+ children: [clients.map((client) => /* @__PURE__ */ jsx(File.Import, {
1315
+ name: [client.name],
1316
+ root: file.path,
1317
+ path: client.file.path
1318
+ }, client.name)), /* @__PURE__ */ jsx(File.Source, {
1319
+ name,
1320
+ isExportable: true,
1321
+ isIndexable: true,
1322
+ children: /* @__PURE__ */ jsx(Function, {
1323
+ export: true,
1324
+ name,
1325
+ children: `return { ${clients.map((client) => client.name).join(", ")} }`
1326
+ })
1327
+ })]
1328
+ }, file.path);
1329
+ }) });
1330
+ }
1331
+ });
1332
+ //#endregion
1333
+ //#region src/components/Operations.tsx
1334
+ function Operations({ name, nodes }) {
1335
+ const operationsObject = {};
1336
+ nodes.forEach((node) => {
1337
+ operationsObject[node.operationId] = {
1338
+ path: new URLPath(node.path).URL,
1339
+ method: node.method.toLowerCase()
1340
+ };
1341
+ });
1342
+ return /* @__PURE__ */ jsx(File.Source, {
1343
+ name,
1344
+ isExportable: true,
1345
+ isIndexable: true,
1346
+ children: /* @__PURE__ */ jsx(Const, {
1347
+ name,
1348
+ export: true,
1349
+ children: JSON.stringify(operationsObject, void 0, 2)
1350
+ })
1351
+ });
1352
+ }
1353
+ //#endregion
1354
+ //#region src/generators/operationsGenerator.tsx
1355
+ const operationsGenerator = defineGenerator({
1356
+ name: "client",
1357
+ renderer: jsxRenderer,
1358
+ operations(nodes, ctx) {
1359
+ const { config, resolver, adapter, root } = ctx;
1360
+ const { output, group } = ctx.options;
1361
+ const name = "operations";
1362
+ const file = resolver.resolveFile({
1363
+ name,
1364
+ extname: ".ts"
1365
+ }, {
1366
+ root,
1367
+ output,
1368
+ group
1369
+ });
1370
+ return /* @__PURE__ */ jsx(File, {
1371
+ baseName: file.baseName,
1372
+ path: file.path,
1373
+ meta: file.meta,
1374
+ banner: resolver.resolveBanner(adapter.inputNode, {
1375
+ output,
1376
+ config
1377
+ }),
1378
+ footer: resolver.resolveFooter(adapter.inputNode, {
1379
+ output,
1380
+ config
1381
+ }),
1382
+ children: /* @__PURE__ */ jsx(Operations, {
1383
+ name,
1384
+ nodes
1385
+ })
1386
+ });
1387
+ }
1388
+ });
1389
+ //#endregion
1390
+ //#region src/components/StaticClassClient.tsx
1391
+ const declarationPrinter = functionPrinter({ mode: "declaration" });
1392
+ function generateMethod({ node, name, tsResolver, zodResolver, baseURL, dataReturnType, parser, paramsType, paramsCasing, pathParamsType }) {
1393
+ const path = new URLPath(node.path, { casing: paramsCasing });
1394
+ const { defaultContentType: contentType, isMultipleContentTypes, hasFormData } = getContentTypeInfo(node);
1395
+ const isFormData = !isMultipleContentTypes && contentType === "multipart/form-data";
1396
+ const { header: headerParams } = getOperationParameters(node);
1397
+ const headerParamsName = headerParams.length > 0 ? tsResolver.resolveHeaderParamsName(node, headerParams[0]) : void 0;
1398
+ const headers = isMultipleContentTypes ? headerParamsName ? ["...headers"] : [] : buildHeaders(contentType, !!headerParamsName);
1399
+ const generics = buildGenerics(node, tsResolver);
1400
+ const paramsNode = buildClientParamsNode({
1401
+ paramsType,
1402
+ paramsCasing,
1403
+ pathParamsType,
1404
+ node,
1405
+ tsResolver,
1406
+ isConfigurable: true
1407
+ });
1408
+ const paramsSignature = declarationPrinter.print(paramsNode) ?? "";
1409
+ const clientParams = buildClassClientParams({
1410
+ node,
1411
+ path,
1412
+ baseURL,
1413
+ tsResolver,
1414
+ isFormData,
1415
+ isMultipleContentTypes,
1416
+ hasFormData,
1417
+ headers
1418
+ });
1419
+ const jsdoc = buildJSDoc(buildOperationComments(node, {
1420
+ link: "urlPath",
1421
+ linkPosition: "beforeDeprecated",
1422
+ splitLines: true
1423
+ }));
1424
+ const requestDataLine = buildRequestDataLine({
1425
+ parser,
1426
+ node,
1427
+ zodResolver
1428
+ });
1429
+ const formDataLine = buildFormDataLine(isFormData || isMultipleContentTypes && hasFormData, !!node.requestBody?.content?.[0]?.schema);
1430
+ const returnStatement = buildReturnStatement({
1431
+ dataReturnType,
1432
+ parser,
1433
+ node,
1434
+ zodResolver
1435
+ });
1436
+ return `${jsdoc} static async ${name}(${paramsSignature}) {\n${[
1437
+ `const { client: request = fetch, ${isMultipleContentTypes ? `contentType = ${JSON.stringify(contentType)}, ` : ""}...requestConfig } = mergeConfig(this.#config, config)`,
1438
+ "",
1439
+ requestDataLine,
1440
+ formDataLine,
1441
+ `const res = await request<${generics.join(", ")}>(${clientParams.toCall()})`,
1442
+ returnStatement
1443
+ ].filter(Boolean).map((line) => ` ${line}`).join("\n")}\n }`;
1444
+ }
1445
+ function StaticClassClient({ name, isExportable = true, isIndexable = true, operations, baseURL, dataReturnType, parser, paramsType, paramsCasing, pathParamsType, children }) {
1446
+ const classCode = `export class ${name} {\n static #config: Partial<RequestConfig> & { client?: Client } = {}\n\n${operations.map(({ node, name: methodName, tsResolver, zodResolver }) => generateMethod({
1447
+ node,
1448
+ name: methodName,
1449
+ tsResolver,
1450
+ zodResolver,
1451
+ baseURL,
1452
+ dataReturnType,
1453
+ parser,
1454
+ paramsType,
1455
+ paramsCasing,
1456
+ pathParamsType
1457
+ })).join("\n\n")}\n}`;
1458
+ return /* @__PURE__ */ jsxs(File.Source, {
1459
+ name,
1460
+ isExportable,
1461
+ isIndexable,
1462
+ children: [classCode, children]
1463
+ });
1464
+ }
1465
+ //#endregion
1466
+ //#region src/generators/staticClassClientGenerator.tsx
1467
+ function resolveTypeImportNames(node, tsResolver) {
1468
+ return resolveOperationTypeNames(node, tsResolver, { order: "body-response-first" });
1469
+ }
1470
+ function resolveZodImportNames(node, zodResolver) {
1471
+ return [zodResolver.resolveResponseName?.(node), node.requestBody?.content?.[0]?.schema ? zodResolver.resolveDataName?.(node) : void 0].filter((n) => Boolean(n));
1472
+ }
1473
+ const staticClassClientGenerator = defineGenerator({
1474
+ name: "staticClassClient",
1475
+ renderer: jsxRenderer,
1476
+ operations(nodes, ctx) {
1477
+ const { adapter, config, driver, resolver, root } = ctx;
1478
+ const { output, group, dataReturnType, paramsCasing, paramsType, pathParamsType, parser, importPath } = ctx.options;
1479
+ const baseURL = ctx.options.baseURL ?? adapter.inputNode?.meta?.baseURL;
1480
+ const pluginTs = driver.getPlugin(pluginTsName);
1481
+ if (!pluginTs) return null;
1482
+ const tsResolver = driver.getResolver(pluginTsName);
1483
+ const tsPluginOptions = pluginTs.options;
1484
+ const pluginZod = parser === "zod" ? driver.getPlugin(pluginZodName) : void 0;
1485
+ const zodResolver = pluginZod ? driver.getResolver(pluginZodName) : void 0;
1486
+ function buildOperationData(node) {
1487
+ const typeFile = tsResolver.resolveFile({
1488
+ name: node.operationId,
1489
+ extname: ".ts",
1490
+ tag: node.tags[0] ?? "default",
1491
+ path: node.path
1492
+ }, {
1493
+ root,
1494
+ output: tsPluginOptions?.output ?? output,
1495
+ group: tsPluginOptions?.group
1496
+ });
1497
+ const zodFile = zodResolver && pluginZod?.options ? zodResolver.resolveFile({
1498
+ name: node.operationId,
1499
+ extname: ".ts",
1500
+ tag: node.tags[0] ?? "default",
1501
+ path: node.path
1502
+ }, {
1503
+ root,
1504
+ output: pluginZod.options?.output ?? output,
1505
+ group: pluginZod.options?.group
1506
+ }) : void 0;
1507
+ return {
1508
+ node,
1509
+ name: resolver.resolveName(node.operationId),
1510
+ tsResolver,
1511
+ zodResolver,
1512
+ typeFile,
1513
+ zodFile
1514
+ };
1515
+ }
1516
+ const controllers = nodes.reduce((acc, operationNode) => {
1517
+ const tag = operationNode.tags[0];
1518
+ const groupName = tag ? group?.name?.({ group: camelCase(tag) }) ?? resolver.resolveGroupName(tag) : resolver.resolveGroupName("Client");
1519
+ if (!tag && !group) {
1520
+ const name = resolver.resolveClassName("ApiClient");
1521
+ const file = resolver.resolveFile({
1522
+ name,
1523
+ extname: ".ts"
1524
+ }, {
1525
+ root,
1526
+ output,
1527
+ group
1528
+ });
1529
+ const operationData = buildOperationData(operationNode);
1530
+ const previous = acc.find((item) => item.file.path === file.path);
1531
+ if (previous) previous.operations.push(operationData);
1532
+ else acc.push({
1533
+ name,
1534
+ file,
1535
+ operations: [operationData]
1536
+ });
1537
+ } else if (tag) {
1538
+ const name = groupName;
1539
+ const file = resolver.resolveFile({
1540
+ name,
1541
+ extname: ".ts",
1542
+ tag
1543
+ }, {
1544
+ root,
1545
+ output,
1546
+ group
1547
+ });
1548
+ const operationData = buildOperationData(operationNode);
1549
+ const previous = acc.find((item) => item.file.path === file.path);
1550
+ if (previous) previous.operations.push(operationData);
1551
+ else acc.push({
1552
+ name,
1553
+ file,
1554
+ operations: [operationData]
1555
+ });
1556
+ }
1557
+ return acc;
1558
+ }, []);
1559
+ function collectTypeImports(ops) {
1560
+ const typeImportsByFile = /* @__PURE__ */ new Map();
1561
+ const typeFilesByPath = /* @__PURE__ */ new Map();
1562
+ ops.forEach((op) => {
1563
+ const names = resolveTypeImportNames(op.node, tsResolver);
1564
+ if (!typeImportsByFile.has(op.typeFile.path)) typeImportsByFile.set(op.typeFile.path, /* @__PURE__ */ new Set());
1565
+ const imports = typeImportsByFile.get(op.typeFile.path);
1566
+ names.forEach((n) => {
1567
+ imports.add(n);
1568
+ });
1569
+ typeFilesByPath.set(op.typeFile.path, op.typeFile);
1570
+ });
1571
+ return {
1572
+ typeImportsByFile,
1573
+ typeFilesByPath
1574
+ };
1575
+ }
1576
+ function collectZodImports(ops) {
1577
+ const zodImportsByFile = /* @__PURE__ */ new Map();
1578
+ const zodFilesByPath = /* @__PURE__ */ new Map();
1579
+ ops.forEach((op) => {
1580
+ if (!op.zodFile || !zodResolver) return;
1581
+ const names = resolveZodImportNames(op.node, zodResolver);
1582
+ if (!zodImportsByFile.has(op.zodFile.path)) zodImportsByFile.set(op.zodFile.path, /* @__PURE__ */ new Set());
1583
+ const imports = zodImportsByFile.get(op.zodFile.path);
1584
+ names.forEach((n) => {
1585
+ imports.add(n);
1586
+ });
1587
+ zodFilesByPath.set(op.zodFile.path, op.zodFile);
1588
+ });
1589
+ return {
1590
+ zodImportsByFile,
1591
+ zodFilesByPath
1592
+ };
1593
+ }
1594
+ return /* @__PURE__ */ jsx(Fragment, { children: controllers.map(({ name, file, operations: ops }) => {
1595
+ const { typeImportsByFile, typeFilesByPath } = collectTypeImports(ops);
1596
+ const { zodImportsByFile, zodFilesByPath } = parser === "zod" ? collectZodImports(ops) : {
1597
+ zodImportsByFile: /* @__PURE__ */ new Map(),
1598
+ zodFilesByPath: /* @__PURE__ */ new Map()
1599
+ };
1600
+ const hasFormData = ops.some((op) => op.node.requestBody?.content?.some((e) => e.contentType === "multipart/form-data") ?? false);
1601
+ return /* @__PURE__ */ jsxs(File, {
1602
+ baseName: file.baseName,
1603
+ path: file.path,
1604
+ meta: file.meta,
1605
+ banner: resolver.resolveBanner(adapter.inputNode, {
1606
+ output,
1607
+ config
1608
+ }),
1609
+ footer: resolver.resolveFooter(adapter.inputNode, {
1610
+ output,
1611
+ config
1612
+ }),
1613
+ children: [
1614
+ importPath ? /* @__PURE__ */ jsxs(Fragment, { children: [
1615
+ /* @__PURE__ */ jsx(File.Import, {
1616
+ name: "fetch",
1617
+ path: importPath
1618
+ }),
1619
+ /* @__PURE__ */ jsx(File.Import, {
1620
+ name: ["mergeConfig"],
1621
+ path: importPath
1622
+ }),
1623
+ /* @__PURE__ */ jsx(File.Import, {
1624
+ name: [
1625
+ "Client",
1626
+ "RequestConfig",
1627
+ "ResponseErrorConfig"
1628
+ ],
1629
+ path: importPath,
1630
+ isTypeOnly: true
1631
+ })
1632
+ ] }) : /* @__PURE__ */ jsxs(Fragment, { children: [
1633
+ /* @__PURE__ */ jsx(File.Import, {
1634
+ name: ["fetch"],
1635
+ root: file.path,
1636
+ path: path.resolve(root, ".kubb/client.ts")
1637
+ }),
1638
+ /* @__PURE__ */ jsx(File.Import, {
1639
+ name: ["mergeConfig"],
1640
+ root: file.path,
1641
+ path: path.resolve(root, ".kubb/client.ts")
1642
+ }),
1643
+ /* @__PURE__ */ jsx(File.Import, {
1644
+ name: [
1645
+ "Client",
1646
+ "RequestConfig",
1647
+ "ResponseErrorConfig"
1648
+ ],
1649
+ root: file.path,
1650
+ path: path.resolve(root, ".kubb/client.ts"),
1651
+ isTypeOnly: true
1652
+ })
1653
+ ] }),
1654
+ hasFormData && /* @__PURE__ */ jsx(File.Import, {
1655
+ name: ["buildFormData"],
1656
+ root: file.path,
1657
+ path: path.resolve(root, ".kubb/config.ts")
1658
+ }),
1659
+ Array.from(typeImportsByFile.entries()).map(([filePath, importSet]) => {
1660
+ const typeFile = typeFilesByPath.get(filePath);
1661
+ if (!typeFile) return null;
1662
+ const importNames = Array.from(importSet).filter(Boolean);
1663
+ if (importNames.length === 0) return null;
1664
+ return /* @__PURE__ */ jsx(File.Import, {
1665
+ name: importNames,
1666
+ root: file.path,
1667
+ path: typeFile.path,
1668
+ isTypeOnly: true
1669
+ }, filePath);
1670
+ }),
1671
+ parser === "zod" && Array.from(zodImportsByFile.entries()).map(([filePath, importSet]) => {
1672
+ const zodFile = zodFilesByPath.get(filePath);
1673
+ if (!zodFile) 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: zodFile.path
1680
+ }, filePath);
1681
+ }),
1682
+ /* @__PURE__ */ jsx(StaticClassClient, {
1683
+ name,
1684
+ operations: ops,
1685
+ baseURL,
1686
+ dataReturnType,
1687
+ pathParamsType,
1688
+ paramsCasing,
1689
+ paramsType,
1690
+ parser
1691
+ })
1692
+ ]
1693
+ }, file.path);
1694
+ }) });
1695
+ }
1696
+ });
1697
+ //#endregion
1698
+ //#region src/resolvers/resolverClient.ts
1699
+ /**
1700
+ * Naming convention resolver for client plugin.
1701
+ *
1702
+ * Provides default naming helpers using camelCase for functions and file paths.
1703
+ *
1704
+ * @example
1705
+ * `resolverClient.default('list pets', 'function') // → 'listPets'`
1706
+ */
1707
+ const resolverClient = defineResolver(() => ({
1708
+ name: "default",
1709
+ pluginName: "plugin-client",
1710
+ default(name, type) {
1711
+ return camelCase(name, { isFile: type === "file" });
1712
+ },
1713
+ resolveName(name) {
1714
+ return this.default(name, "function");
1715
+ },
1716
+ resolvePathName(name, type) {
1717
+ return this.default(name, type);
1718
+ },
1719
+ resolveClassName(name) {
1720
+ return pascalCase(name);
1721
+ },
1722
+ resolveGroupName(name) {
1723
+ return pascalCase(name);
1724
+ },
1725
+ resolveClientPropertyName(name) {
1726
+ return camelCase(name);
1727
+ },
1728
+ resolveUrlName(node) {
1729
+ const name = this.resolveName(node.operationId);
1730
+ return `get${name.charAt(0).toUpperCase()}${name.slice(1)}Url`;
1731
+ }
1732
+ }));
1733
+ //#endregion
11
1734
  //#region src/plugin.ts
1735
+ /**
1736
+ * Canonical plugin name for `@kubb/plugin-client`, used in driver lookups and warnings.
1737
+ */
12
1738
  const pluginClientName = "plugin-client";
13
- const pluginClient = createPlugin((options) => {
1739
+ /**
1740
+ * Generates type-safe HTTP client functions or classes from an OpenAPI specification.
1741
+ * Creates client APIs by walking operations and delegating to generators.
1742
+ * Writes barrel files based on the configured `barrelType`.
1743
+ *
1744
+ * @example Client generator
1745
+ * ```ts
1746
+ * import pluginClient from '@kubb/plugin-client'
1747
+ * export default defineConfig({
1748
+ * plugins: [pluginClient({ output: { path: 'clients' } })]
1749
+ * })
1750
+ * ```
1751
+ */
1752
+ const pluginClient = definePlugin((options) => {
14
1753
  const { output = {
15
1754
  path: "clients",
16
1755
  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;
1756
+ }, 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
1757
  const resolvedImportPath = importPath ?? (!bundle ? `@kubb/plugin-client/clients/${client}` : void 0);
19
- const defaultGenerators = [
1758
+ const selectedGenerators = options.generators ?? [
20
1759
  clientType === "staticClass" ? staticClassClientGenerator : clientType === "class" ? classClientGenerator : clientGenerator,
21
1760
  group && clientType === "function" ? groupedClientGenerator : void 0,
22
1761
  operations ? operationsGenerator : void 0
23
1762
  ].filter((x) => Boolean(x));
24
- const generators = options.generators ?? defaultGenerators;
1763
+ const groupConfig = group ? {
1764
+ ...group,
1765
+ name: group.name ? group.name : (ctx) => {
1766
+ if (group.type === "path") return `${ctx.group.split("/")[1]}`;
1767
+ return `${camelCase(ctx.group)}Controller`;
1768
+ }
1769
+ } : void 0;
25
1770
  return {
26
1771
  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: []
1772
+ options,
1773
+ dependencies: [pluginTsName, parser === "zod" ? pluginZodName : void 0].filter((dependency) => Boolean(dependency)),
1774
+ hooks: { "kubb:plugin:setup"(ctx) {
1775
+ const resolver = userResolver ? {
1776
+ ...resolverClient,
1777
+ ...userResolver
1778
+ } : resolverClient;
1779
+ ctx.setOptions({
1780
+ client,
1781
+ clientType,
1782
+ bundle,
1783
+ output,
1784
+ exclude,
1785
+ include,
1786
+ override,
1787
+ group: groupConfig,
1788
+ parser,
1789
+ dataReturnType,
1790
+ importPath: resolvedImportPath,
1791
+ baseURL,
1792
+ paramsType,
1793
+ paramsCasing,
1794
+ pathParamsType,
1795
+ urlType,
1796
+ sdk,
1797
+ resolver
82
1798
  });
83
- await this.addFile({
1799
+ ctx.setResolver(resolver);
1800
+ if (userTransformer) ctx.setTransformer(userTransformer);
1801
+ for (const gen of selectedGenerators) ctx.addGenerator(gen);
1802
+ const root = path.resolve(ctx.config.root, ctx.config.output.path);
1803
+ if (!resolvedImportPath?.startsWith(".")) {
1804
+ const isInlineSource = bundle && !resolvedImportPath;
1805
+ ctx.injectFile({
1806
+ baseName: "client.ts",
1807
+ path: path.resolve(root, ".kubb/client.ts"),
1808
+ sources: [ast.createSource({
1809
+ name: "client",
1810
+ nodes: isInlineSource ? [ast.createText(client === "fetch" ? source$1 : source)] : [],
1811
+ isExportable: true,
1812
+ isIndexable: true
1813
+ })],
1814
+ exports: !isInlineSource && resolvedImportPath ? [ast.createExport({ path: resolvedImportPath })] : []
1815
+ });
1816
+ }
1817
+ ctx.injectFile({
84
1818
  baseName: "config.ts",
85
1819
  path: path.resolve(root, ".kubb/config.ts"),
86
- sources: [{
1820
+ sources: [ast.createSource({
87
1821
  name: "config",
88
- value: source$2,
1822
+ nodes: [ast.createText(source$2)],
89
1823
  isExportable: false,
90
1824
  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 }
1825
+ })]
116
1826
  });
117
- await this.upsertFile(...barrelFiles);
118
- }
1827
+ } }
119
1828
  };
120
1829
  });
121
1830
  //#endregion
122
- export { pluginClient, pluginClientName };
1831
+ export { Client, classClientGenerator, clientGenerator, pluginClient as default, pluginClient, groupedClientGenerator, operationsGenerator, pluginClientName, resolverClient, staticClassClientGenerator };
123
1832
 
124
1833
  //# sourceMappingURL=index.js.map