@kubb/plugin-client 5.0.0-beta.64 → 5.7.0-canary.20260930T095256

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 (38) hide show
  1. package/README.md +8 -61
  2. package/dist/index.cjs +753 -1749
  3. package/dist/index.cjs.map +1 -1
  4. package/dist/index.d.ts +168 -347
  5. package/dist/index.js +729 -1734
  6. package/dist/index.js.map +1 -1
  7. package/package.json +12 -64
  8. package/src/generators/clientGenerator.tsx +9 -0
  9. package/src/index.ts +2 -0
  10. package/src/plugin.ts +71 -0
  11. package/src/types.ts +54 -0
  12. package/dist/chunk-Bx3C2hgW.cjs +0 -38
  13. package/dist/clients/axios.cjs +0 -68
  14. package/dist/clients/axios.cjs.map +0 -1
  15. package/dist/clients/axios.d.ts +0 -49
  16. package/dist/clients/axios.js +0 -58
  17. package/dist/clients/axios.js.map +0 -1
  18. package/dist/clients/fetch.cjs +0 -128
  19. package/dist/clients/fetch.cjs.map +0 -1
  20. package/dist/clients/fetch.d.ts +0 -50
  21. package/dist/clients/fetch.js +0 -120
  22. package/dist/clients/fetch.js.map +0 -1
  23. package/dist/templates/clients/axios.source.cjs +0 -7
  24. package/dist/templates/clients/axios.source.cjs.map +0 -1
  25. package/dist/templates/clients/axios.source.d.ts +0 -7
  26. package/dist/templates/clients/axios.source.js +0 -6
  27. package/dist/templates/clients/axios.source.js.map +0 -1
  28. package/dist/templates/clients/fetch.source.cjs +0 -7
  29. package/dist/templates/clients/fetch.source.cjs.map +0 -1
  30. package/dist/templates/clients/fetch.source.d.ts +0 -7
  31. package/dist/templates/clients/fetch.source.js +0 -6
  32. package/dist/templates/clients/fetch.source.js.map +0 -1
  33. package/dist/templates/config.source.cjs +0 -7
  34. package/dist/templates/config.source.cjs.map +0 -1
  35. package/dist/templates/config.source.d.ts +0 -7
  36. package/dist/templates/config.source.js +0 -6
  37. package/dist/templates/config.source.js.map +0 -1
  38. /package/dist/{chunk-C0LytTxp.js → rolldown-runtime-C0LytTxp.js} +0 -0
package/dist/index.js CHANGED
@@ -1,14 +1,216 @@
1
- import { t as __name } from "./chunk-C0LytTxp.js";
2
- import { source } from "./templates/clients/axios.source.js";
3
- import { source as source$1 } from "./templates/clients/fetch.source.js";
4
- import { source as source$2 } from "./templates/config.source.js";
1
+ import "./rolldown-runtime-C0LytTxp.js";
2
+ import { Resolver, Url, ast, createResolver, defineGenerator, definePlugin, macroSimplifyUnion } from "kubb/kit";
5
3
  import path from "node:path";
6
- import { ast, defineGenerator, definePlugin, defineResolver } from "@kubb/core";
7
- import { buildJSDoc, caseParams, createOperationParams, stringify } from "@kubb/ast/utils";
8
- import { functionPrinter, pluginTsName } from "@kubb/plugin-ts";
9
- import { Const, File, Function, jsxRenderer } from "@kubb/renderer-jsx";
10
- import { Fragment, jsx, jsxs } from "@kubb/renderer-jsx/jsx-runtime";
4
+ import { createFunctionParameter, createFunctionParameters, functionPrinter, pluginTsName } from "@kubb/plugin-ts";
11
5
  import { pluginZodName } from "@kubb/plugin-zod";
6
+ import { File, Function, jsxRenderer } from "kubb/jsx";
7
+ import { jsx, jsxs } from "kubb/jsx/jsx-runtime";
8
+ //#region ../../internals/shared/src/params.ts
9
+ /**
10
+ * Drops parameters that share the same name, keeping the first.
11
+ *
12
+ * A malformed spec can declare the same parameter name twice within one `in` location. Both would
13
+ * resolve to the same output property, so emitting both would yield an object type with a duplicate
14
+ * member, which TypeScript rejects. This is a defensive guard against that case, not a casing guard:
15
+ * parameter names flow through unchanged, so no two distinct names ever collide here anymore.
16
+ */
17
+ function dedupeParams(params) {
18
+ const seen = /* @__PURE__ */ new Set();
19
+ return params.filter((param) => {
20
+ if (seen.has(param.name)) return false;
21
+ seen.add(param.name);
22
+ return true;
23
+ });
24
+ }
25
+ //#endregion
26
+ //#region ../../internals/shared/src/operation.ts
27
+ /**
28
+ * Builds the `ResolverFileParams` every operation generator passes to
29
+ * `resolver.file`: a file named `name`, tagged by the operation's first
30
+ * tag (or `'default'`), at the operation's path. Centralizes the entry object
31
+ * that was repeated at dozens of call sites across the client and query plugins.
32
+ *
33
+ * @example
34
+ * ```ts
35
+ * resolver.file(operationFileEntry(node, node.operationId), { root, output, group })
36
+ * ```
37
+ */
38
+ function operationFileEntry(node, name, extname = ".ts") {
39
+ return {
40
+ name,
41
+ extname,
42
+ tag: node.tags[0] ?? "default",
43
+ path: node.path
44
+ };
45
+ }
46
+ /**
47
+ * Resolves a dependency plugin's generated file for `node.operationId`, cached in `cache` (the
48
+ * current node's `ctx.cache`) under the resolver's own plugin name. Several dependents reading the
49
+ * same dependency for the same operation in one pass (a query plugin's several hook generators, the
50
+ * MCP handler, ...) share one computed name and path instead of each calling `resolver.file` again.
51
+ *
52
+ * @example Cache `plugin-ts`'s file for the current operation
53
+ * ```ts
54
+ * const fileTs = resolveDependencyOperationFile({ cache: ctx.cache, node, resolver: tsResolver, root, output })
55
+ * ```
56
+ */
57
+ function resolveDependencyOperationFile(options) {
58
+ const { cache, node, resolver, root, output, group } = options;
59
+ return cache.ensureItem(`${resolver.pluginName}:operationFile`, () => resolver.file({
60
+ ...operationFileEntry(node, node.operationId),
61
+ root,
62
+ output,
63
+ group: group ?? void 0
64
+ }));
65
+ }
66
+ function getOperationLink(node, link) {
67
+ if (!link) return null;
68
+ if (typeof link === "function") return link(node) ?? null;
69
+ return node.path ? `{@link ${Url.toPath(node.path)}}` : null;
70
+ }
71
+ /**
72
+ * Derives the shared `ContentTypeInfo` shape from a list of content types, tracking whether several
73
+ * are present and the union, default, and form-data flags the client uses to pick one.
74
+ */
75
+ function buildContentTypeInfo(contentTypes) {
76
+ const isMultipleContentTypes = contentTypes.length > 1;
77
+ return {
78
+ contentTypes,
79
+ isMultipleContentTypes,
80
+ contentTypeUnion: isMultipleContentTypes ? contentTypes.map((ct) => JSON.stringify(ct)).join(" | ") : "",
81
+ defaultContentType: contentTypes[0] ?? "application/json",
82
+ hasFormData: contentTypes.some((ct) => ct === "multipart/form-data")
83
+ };
84
+ }
85
+ function getContentTypeInfo(node) {
86
+ return buildContentTypeInfo(node.requestBody?.content?.map((e) => e.contentType) ?? []);
87
+ }
88
+ /**
89
+ * The request-body counterpart for the primary success response: the content types it documents and
90
+ * whether several are present, so the client can let a caller pick which one to accept.
91
+ */
92
+ function getResponseContentTypeInfo(node) {
93
+ return buildContentTypeInfo(getPrimarySuccessResponse(node)?.content?.map((e) => e.contentType) ?? []);
94
+ }
95
+ /**
96
+ * Reads the single base content type of an operation's primary success response, lowercased and
97
+ * stripped of any `; charset=...` suffix. Returns `undefined` when the response declares zero or
98
+ * more than one content type, since neither case has a single type to act on.
99
+ */
100
+ function getPrimarySuccessContentType(node) {
101
+ const contentTypes = getPrimarySuccessResponse(node)?.content?.map((entry) => entry.contentType) ?? [];
102
+ if (contentTypes.length !== 1) return void 0;
103
+ return contentTypes[0].split(";")[0].trim().toLowerCase();
104
+ }
105
+ /**
106
+ * Whether an operation streams its primary success response as Server-Sent Events
107
+ * (`text/event-stream`). The client generator uses this to return a typed event stream instead of a
108
+ * one-shot `RequestResult`.
109
+ */
110
+ function isEventStream(node) {
111
+ return getPrimarySuccessContentType(node) === "text/event-stream";
112
+ }
113
+ /**
114
+ * Derives the default `responseType` for an operation from its primary success response.
115
+ *
116
+ * Returns a value only when that response declares a single non-JSON content type. `text/event-stream`
117
+ * and other binary types (`application/octet-stream`, `application/pdf`, `image/*`, `audio/*`,
118
+ * `video/*`) map to a stream or `'blob'`, and other `text/*` maps to `'text'`. Otherwise `undefined`,
119
+ * leaving the runtime client's `Content-Type` auto-detection in charge.
120
+ */
121
+ function getResponseType(node) {
122
+ const baseType = getPrimarySuccessContentType(node);
123
+ if (!baseType) return void 0;
124
+ if (baseType === "application/json" || baseType.endsWith("+json") || baseType === "text/json") return void 0;
125
+ if (baseType === "text/event-stream") return "stream";
126
+ if (baseType.startsWith("text/")) return "text";
127
+ if (baseType === "application/octet-stream" || baseType === "application/pdf" || /^(image|audio|video)\//.test(baseType)) return "blob";
128
+ }
129
+ /**
130
+ * Which of the grouped request options an operation carries.
131
+ */
132
+ function getRequestGroups(node) {
133
+ const { path, query, header } = getOperationParameters(node);
134
+ return {
135
+ path: path.length > 0,
136
+ query: query.length > 0,
137
+ body: Boolean(node.requestBody?.content?.[0]?.schema),
138
+ headers: header.length > 0
139
+ };
140
+ }
141
+ /**
142
+ * Resolves which grouped request options an operation carries together with whether each group
143
+ * holds a required member. The grouped parameter stays optional only when nothing inside it is
144
+ * required, matching the generated `RequestConfig` type.
145
+ */
146
+ function getRequestGroupOptionality(node) {
147
+ const groups = getRequestGroups(node);
148
+ const { path, query, header } = getOperationParameters(node);
149
+ const hasRequiredPath = path.some((param) => param.required);
150
+ const hasRequiredQuery = query.some((param) => param.required);
151
+ const hasRequiredHeader = header.some((param) => param.required);
152
+ return {
153
+ groups,
154
+ hasRequiredPath,
155
+ hasRequiredQuery,
156
+ hasRequiredHeader,
157
+ isOptional: !hasRequiredPath && !hasRequiredQuery && !hasRequiredHeader && !groups.body
158
+ };
159
+ }
160
+ function buildOperationComments(node, options = {}) {
161
+ const { link = "pathTemplate", linkPosition = "afterDeprecated", splitLines = false } = options;
162
+ const linkComment = getOperationLink(node, link);
163
+ const filteredComments = (linkPosition === "beforeDeprecated" ? [
164
+ node.description && `@description ${node.description}`,
165
+ node.summary && `@summary ${node.summary}`,
166
+ linkComment,
167
+ node.deprecated && "@deprecated"
168
+ ] : [
169
+ node.description && `@description ${node.description}`,
170
+ node.summary && `@summary ${node.summary}`,
171
+ node.deprecated && "@deprecated",
172
+ linkComment
173
+ ]).filter((comment) => Boolean(comment));
174
+ if (!splitLines) return filteredComments;
175
+ return filteredComments.flatMap((text) => text.split(/\r?\n/).map((line) => line.trim())).filter((comment) => Boolean(comment));
176
+ }
177
+ const operationParameterGroupsByNode = /* @__PURE__ */ new WeakMap();
178
+ /**
179
+ * Groups an operation's parameters by location (`path`/`query`/`header`/`cookie`), deduping each
180
+ * group by name. Every plugin generator visiting the same `OperationNode` shares one AST instance
181
+ * (see `KubbDriver`), so the result is cached per node to avoid re-filtering and re-deduping the
182
+ * same parameters once per plugin.
183
+ */
184
+ function getOperationParameters(node) {
185
+ const cached = operationParameterGroupsByNode.get(node);
186
+ if (cached) return cached;
187
+ const groups = {
188
+ path: dedupeParams(node.parameters.filter((param) => param.in === "path")),
189
+ query: dedupeParams(node.parameters.filter((param) => param.in === "query")),
190
+ header: dedupeParams(node.parameters.filter((param) => param.in === "header")),
191
+ cookie: dedupeParams(node.parameters.filter((param) => param.in === "cookie"))
192
+ };
193
+ operationParameterGroupsByNode.set(node, groups);
194
+ return groups;
195
+ }
196
+ function getStatusCodeNumber(statusCode) {
197
+ const code = Number(statusCode);
198
+ return Number.isNaN(code) ? null : code;
199
+ }
200
+ function isSuccessStatusCode(statusCode) {
201
+ const code = getStatusCodeNumber(statusCode);
202
+ return code !== null && code >= 200 && code < 300;
203
+ }
204
+ function getSuccessResponses(responses) {
205
+ return responses.filter((response) => isSuccessStatusCode(response.statusCode));
206
+ }
207
+ function getOperationSuccessResponses(node) {
208
+ return getSuccessResponses(node.responses);
209
+ }
210
+ function getPrimarySuccessResponse(node) {
211
+ return getOperationSuccessResponses(node)[0] ?? null;
212
+ }
213
+ //#endregion
12
214
  //#region ../../internals/utils/src/casing.ts
13
215
  /**
14
216
  * Shared implementation for camelCase and PascalCase conversion.
@@ -48,37 +250,12 @@ function pascalCase(text, { prefix = "", suffix = "" } = {}) {
48
250
  return toCamelOrPascal(`${prefix} ${text} ${suffix}`, true);
49
251
  }
50
252
  //#endregion
51
- //#region ../../internals/utils/src/fs.ts
52
- /**
53
- * Builds a nested file path from a dotted name. Splits on dots that precede a letter
54
- * (so version numbers embedded in operationIds like `v2025.0` stay intact), camelCases
55
- * every earlier segment, applies `caseLast` to the final segment, and joins with `/`.
56
- *
57
- * Empty segments are dropped before joining. They arise when the name starts with a dot
58
- * followed by a letter (e.g. `..Schema` splits into `['..', 'Schema']` and `'..'` cases to
59
- * an empty string). Without this a leading `/` would form, which `path.resolve` reads as an
60
- * absolute path, letting generated files escape the configured output directory.
61
- *
62
- * @example Nested path from a dotted name
63
- * `toFilePath('pet.petId') // 'pet/petId'`
64
- *
65
- * @example PascalCase the final segment
66
- * `toFilePath('pet.Pet', pascalCase) // 'pet/Pet'`
67
- *
68
- * @example Suffix applied to the final segment only
69
- * `toFilePath('tag.tag', (part) => camelCase(part, { suffix: 'schema' })) // 'tag/tagSchema'`
70
- */
71
- function toFilePath(name, caseLast = camelCase) {
72
- const parts = name.split(/\.(?=[a-zA-Z])/);
73
- return parts.map((part, i) => i === parts.length - 1 ? caseLast(part) : camelCase(part)).filter(Boolean).join("/");
74
- }
75
- //#endregion
76
253
  //#region ../../internals/utils/src/reserved.ts
77
254
  /**
78
255
  * JavaScript and Java reserved words.
79
256
  * @link https://github.com/jonschlinkert/reserved/blob/master/index.js
80
257
  */
81
- const reservedWords = new Set([
258
+ const reservedWords = /* @__PURE__ */ new Set([
82
259
  "abstract",
83
260
  "arguments",
84
261
  "boolean",
@@ -173,7 +350,7 @@ const reservedWords = new Set([
173
350
  */
174
351
  function isValidVarName(name) {
175
352
  if (!name || reservedWords.has(name)) return false;
176
- return /^[a-zA-Z_$][a-zA-Z0-9_$]*$/.test(name);
353
+ return isIdentifier(name);
177
354
  }
178
355
  /**
179
356
  * Returns `name` when it's a syntactically valid JavaScript variable name,
@@ -195,252 +372,21 @@ function ensureValidVarName(name) {
195
372
  if (!name || isValidVarName(name)) return name;
196
373
  return `_${name}`;
197
374
  }
198
- //#endregion
199
- //#region ../../internals/utils/src/url.ts
200
- function transformParam(raw, casing) {
201
- const param = isValidVarName(raw) ? raw : camelCase(raw);
202
- return casing === "camelcase" ? camelCase(param) : param;
203
- }
204
- function toParamsObject(path, { replacer, casing } = {}) {
205
- const params = {};
206
- for (const match of path.matchAll(/\{([^}]+)\}/g)) {
207
- const param = transformParam(match[1], casing);
208
- const key = replacer ? replacer(param) : param;
209
- params[key] = key;
210
- }
211
- return Object.keys(params).length > 0 ? params : null;
212
- }
213
- /**
214
- * Helpers for OpenAPI/Swagger paths, plus a thin wrapper over the native `URL`.
215
- */
216
- var Url$1 = class Url$1 {
217
- static {
218
- __name(this, "Url");
219
- }
220
- /**
221
- * Reports whether `url` is a parseable absolute URL. Delegates to the native `URL.canParse`.
222
- *
223
- * @example
224
- * Url.canParse('https://petstore.swagger.io/v2') // true
225
- * Url.canParse('/pet/{petId}') // false
226
- */
227
- static canParse(url, base) {
228
- return URL.canParse(url, base);
229
- }
230
- /**
231
- * Converts an OpenAPI/Swagger path to Express-style colon syntax.
232
- *
233
- * @example
234
- * Url.toPath('/pet/{petId}') // '/pet/:petId'
235
- */
236
- static toPath(path) {
237
- return path.replace(/\{([^}]+)\}/g, ":$1");
238
- }
239
- /**
240
- * Converts an OpenAPI/Swagger path to a TypeScript template literal string.
241
- * `prefix` is prepended inside the literal, `replacer` transforms each parameter name,
242
- * and `casing` controls parameter identifier casing.
243
- *
244
- * @example
245
- * Url.toTemplateString('/pet/{petId}') // '`/pet/${petId}`'
246
- *
247
- * @example
248
- * Url.toTemplateString('/pet/{petId}', { prefix: 'https://api' }) // '`https://api/pet/${petId}`'
249
- */
250
- static toTemplateString(path, { prefix, replacer, casing } = {}) {
251
- const result = path.split(/\{([^}]+)\}/).map((part, i) => {
252
- if (i % 2 === 0) return part;
253
- const param = transformParam(part, casing);
254
- return `\${${replacer ? replacer(param) : param}}`;
255
- }).join("");
256
- return `\`${prefix ?? ""}${result}\``;
257
- }
258
- /**
259
- * Returns the path and its extracted params as a structured `URLObject`, or as a stringified
260
- * expression when `stringify` is set.
261
- *
262
- * @example
263
- * Url.toObject('/pet/{petId}')
264
- * // { url: '/pet/:petId', params: { petId: 'petId' } }
265
- */
266
- static toObject(path, { type = "path", replacer, stringify, casing } = {}) {
267
- const object = {
268
- url: type === "path" ? Url$1.toPath(path) : Url$1.toTemplateString(path, {
269
- replacer,
270
- casing
271
- }),
272
- params: toParamsObject(path, {
273
- replacer,
274
- casing
275
- })
276
- };
277
- if (stringify) {
278
- if (type === "template") return JSON.stringify(object).replaceAll("'", "").replaceAll(`"`, "");
279
- if (object.params) return `{ url: '${object.url}', params: ${JSON.stringify(object.params).replaceAll("'", "").replaceAll(`"`, "")} }`;
280
- return `{ url: '${object.url}' }`;
281
- }
282
- return object;
283
- }
284
- };
285
- //#endregion
286
- //#region ../../internals/shared/src/operation.ts
287
375
  /**
288
- * Builds the `ResolverFileParams` every operation generator passes to
289
- * `resolver.resolveFile`: a file named `name`, tagged by the operation's first
290
- * tag (or `'default'`), at the operation's path. Centralizes the entry object
291
- * that was repeated at dozens of call sites across the client and query plugins.
376
+ * Returns `true` when `name` is syntactically a valid identifier, ignoring reserved words.
377
+ *
378
+ * Reserved words and globals (`class`, `name`, `Date`, …) are valid as bare object-literal keys
379
+ * even though they are not valid variable names, so use this (not {@link isValidVarName}) when
380
+ * deciding whether an object key needs quoting.
292
381
  *
293
382
  * @example
294
383
  * ```ts
295
- * resolver.resolveFile(operationFileEntry(node, node.operationId), { root, output, group })
384
+ * isIdentifier('name') // true
385
+ * isIdentifier('x-total')// false
296
386
  * ```
297
387
  */
298
- function operationFileEntry(node, name, extname = ".ts") {
299
- return {
300
- name,
301
- extname,
302
- tag: node.tags[0] ?? "default",
303
- path: node.path
304
- };
305
- }
306
- function getOperationLink(node, link) {
307
- if (!link) return null;
308
- if (typeof link === "function") return link(node) ?? null;
309
- if (link === "urlPath") return node.path ? `{@link ${Url$1.toPath(node.path)}}` : null;
310
- return node.path ? `{@link ${node.path.replaceAll("{", ":").replaceAll("}", "")}}` : null;
311
- }
312
- function getContentTypeInfo(node) {
313
- const contentTypes = node.requestBody?.content?.map((e) => e.contentType) ?? [];
314
- const isMultipleContentTypes = contentTypes.length > 1;
315
- return {
316
- contentTypes,
317
- isMultipleContentTypes,
318
- contentTypeUnion: isMultipleContentTypes ? contentTypes.map((ct) => JSON.stringify(ct)).join(" | ") : "",
319
- defaultContentType: contentTypes[0] ?? "application/json",
320
- hasFormData: contentTypes.some((ct) => ct === "multipart/form-data")
321
- };
322
- }
323
- /**
324
- * Derives the default `responseType` for an operation from its primary success response.
325
- *
326
- * Returns a value only when that response declares a single non-JSON content type — a binary type
327
- * (`application/octet-stream`, `application/pdf`, `image/*`, `audio/*`, `video/*`) maps to `'blob'`
328
- * and other `text/*` maps to `'text'`. Otherwise `undefined`, leaving the runtime client's
329
- * `Content-Type` auto-detection in charge.
330
- */
331
- function getResponseType(node) {
332
- const contentTypes = getPrimarySuccessResponse(node)?.content?.map((entry) => entry.contentType) ?? [];
333
- if (contentTypes.length !== 1) return void 0;
334
- const baseType = contentTypes[0].split(";")[0].trim().toLowerCase();
335
- if (baseType === "application/json" || baseType.endsWith("+json") || baseType === "text/json") return void 0;
336
- if (baseType.startsWith("text/")) return "text";
337
- if (baseType === "application/octet-stream" || baseType === "application/pdf" || /^(image|audio|video)\//.test(baseType)) return "blob";
338
- }
339
- function buildRequestConfigType(node, resolver) {
340
- const requestName = node.requestBody?.content?.[0]?.schema ? resolver.resolveDataName(node) : null;
341
- const { isMultipleContentTypes, contentTypeUnion } = getContentTypeInfo(node);
342
- return `${requestName ? `Partial<RequestConfig<${requestName}>>` : "Partial<RequestConfig>"} & { ${["client?: Client", isMultipleContentTypes ? `contentType?: ${contentTypeUnion}` : null].filter(Boolean).join("; ")} }`;
343
- }
344
- function buildOperationComments(node, options = {}) {
345
- const { link = "pathTemplate", linkPosition = "afterDeprecated", splitLines = false } = options;
346
- const linkComment = getOperationLink(node, link);
347
- const filteredComments = (linkPosition === "beforeDeprecated" ? [
348
- node.description && `@description ${node.description}`,
349
- node.summary && `@summary ${node.summary}`,
350
- linkComment,
351
- node.deprecated && "@deprecated"
352
- ] : [
353
- node.description && `@description ${node.description}`,
354
- node.summary && `@summary ${node.summary}`,
355
- node.deprecated && "@deprecated",
356
- linkComment
357
- ]).filter((comment) => Boolean(comment));
358
- if (!splitLines) return filteredComments;
359
- return filteredComments.flatMap((text) => text.split(/\r?\n/).map((line) => line.trim())).filter((comment) => Boolean(comment));
360
- }
361
- function getOperationParameters(node, options = {}) {
362
- const params = caseParams(node.parameters, options.paramsCasing);
363
- return {
364
- path: params.filter((param) => param.in === "path"),
365
- query: params.filter((param) => param.in === "query"),
366
- header: params.filter((param) => param.in === "header"),
367
- cookie: params.filter((param) => param.in === "cookie")
368
- };
369
- }
370
- function getStatusCodeNumber(statusCode) {
371
- const code = Number(statusCode);
372
- return Number.isNaN(code) ? null : code;
373
- }
374
- function isSuccessStatusCode(statusCode) {
375
- const code = getStatusCodeNumber(statusCode);
376
- return code !== null && code >= 200 && code < 300;
377
- }
378
- function isErrorStatusCode(statusCode) {
379
- const code = getStatusCodeNumber(statusCode);
380
- return code !== null && code >= 400;
381
- }
382
- function getSuccessResponses(responses) {
383
- return responses.filter((response) => isSuccessStatusCode(response.statusCode));
384
- }
385
- function getOperationSuccessResponses(node) {
386
- return getSuccessResponses(node.responses);
387
- }
388
- function getPrimarySuccessResponse(node) {
389
- return getOperationSuccessResponses(node)[0] ?? null;
390
- }
391
- function resolveErrorNames(node, resolver) {
392
- return node.responses.filter((response) => isErrorStatusCode(response.statusCode)).map((response) => resolver.resolveResponseStatusName(node, response.statusCode));
393
- }
394
- function resolveSuccessNames(node, resolver) {
395
- return node.responses.filter((response) => isSuccessStatusCode(response.statusCode)).map((response) => resolver.resolveResponseStatusName(node, response.statusCode));
396
- }
397
- function resolveStatusCodeNames(node, resolver) {
398
- return node.responses.map((response) => resolver.resolveResponseStatusName(node, response.statusCode));
399
- }
400
- /**
401
- * Builds the discriminated union type string for `dataReturnType: 'full'` return shapes.
402
- * Each member is `{ status: N; data: StatusNType; statusText: string }`.
403
- */
404
- function buildStatusUnionType(node, resolver) {
405
- const members = node.responses.map((r) => {
406
- const typeName = resolver.resolveResponseStatusName(node, r.statusCode);
407
- const statusCode = Number.parseInt(r.statusCode, 10);
408
- return `{ status: ${Number.isNaN(statusCode) ? "number" : String(statusCode)}; data: ${typeName}; statusText: string }`;
409
- });
410
- if (members.length === 1) return members[0];
411
- return `(${members.join(" | ")})`;
412
- }
413
- const typeNamesByResolver = /* @__PURE__ */ new WeakMap();
414
- function resolveOperationTypeNames(node, resolver, options = {}) {
415
- const cacheKey = `${node.operationId}\0${options.paramsCasing ?? ""}\0${options.order ?? ""}\0${options.responseStatusNames ?? ""}\0${(options.exclude ?? []).join(",")}`;
416
- let byResolver = typeNamesByResolver.get(resolver);
417
- if (byResolver) {
418
- const cached = byResolver.get(cacheKey);
419
- if (cached) return cached;
420
- } else {
421
- byResolver = /* @__PURE__ */ new Map();
422
- typeNamesByResolver.set(resolver, byResolver);
423
- }
424
- const { path, query, header } = getOperationParameters(node, { paramsCasing: options.paramsCasing });
425
- const responseStatusNames = options.responseStatusNames === "error" ? resolveErrorNames(node, resolver) : options.responseStatusNames === false ? [] : resolveStatusCodeNames(node, resolver);
426
- const exclude = new Set(options.exclude ?? []);
427
- const paramNames = [
428
- ...path.map((param) => resolver.resolvePathParamsName(node, param)),
429
- ...query.map((param) => resolver.resolveQueryParamsName(node, param)),
430
- ...header.map((param) => resolver.resolveHeaderParamsName(node, param))
431
- ];
432
- const bodyAndResponseNames = [node.requestBody?.content?.[0]?.schema ? resolver.resolveDataName(node) : null, resolver.resolveResponseName(node)];
433
- const result = (options.order === "body-response-first" ? [
434
- ...bodyAndResponseNames,
435
- ...paramNames,
436
- ...responseStatusNames
437
- ] : [
438
- ...paramNames,
439
- ...bodyAndResponseNames,
440
- ...responseStatusNames
441
- ]).filter((name) => Boolean(name) && !exclude.has(name));
442
- byResolver.set(cacheKey, result);
443
- return result;
388
+ function isIdentifier(name) {
389
+ return /^[a-zA-Z_$][a-zA-Z0-9_$]*$/.test(name);
444
390
  }
445
391
  //#endregion
446
392
  //#region ../../internals/shared/src/group.ts
@@ -474,1604 +420,653 @@ function createGroupConfig(group) {
474
420
  };
475
421
  }
476
422
  //#endregion
477
- //#region ../../internals/shared/src/params.ts
478
- function buildParamsMapping(originalParams, mappedParams) {
479
- const mapping = {};
480
- let hasChanged = false;
481
- originalParams.forEach((param, i) => {
482
- const mappedName = mappedParams[i]?.name ?? param.name;
483
- mapping[param.name] = mappedName;
484
- if (param.name !== mappedName) hasChanged = true;
423
+ //#region ../../internals/client/src/builders/generics.ts
424
+ /**
425
+ * Builds the `RequestResult` generic arguments for one operation: the per-status responses record
426
+ * plus the per-call `ThrowOnError` flag. `SuccessOf` / `ErrorOf` split the record inside the
427
+ * runtime, so this only names the record and threads `ThrowOnError`.
428
+ *
429
+ * @example
430
+ * `buildRequestResultGenerics({ node, types }) // 'AddPetResponses, ThrowOnError'`
431
+ */
432
+ function buildRequestResultGenerics({ node, types }) {
433
+ return `${types.response.responses(node)}, ThrowOnError`;
434
+ }
435
+ /**
436
+ * Builds the full return type an operation's function signature uses: `Unwrappable<RequestResult>`
437
+ * for the default `returnType: 'full'`, already a promise so no further wrapping is needed, a plain
438
+ * `Promise<RequestResult>` for the internal `'plain'` shape, or a
439
+ * `Promise` of the runtime's `UnwrappedResult` when `returnType: 'data'` narrows a resolved call
440
+ * down to the bare success body.
441
+ *
442
+ * @example
443
+ * `buildResultType({ node, types, returnType: 'data' }) // 'Promise<UnwrappedResult<AddPetResponses, ThrowOnError>>'`
444
+ */
445
+ function buildResultType({ node, types, returnType }) {
446
+ const generics = buildRequestResultGenerics({
447
+ node,
448
+ types
485
449
  });
486
- return hasChanged ? mapping : null;
450
+ if (returnType === "data") return `Promise<UnwrappedResult<${generics}>>`;
451
+ if (returnType === "plain") return `Promise<RequestResult<${generics}>>`;
452
+ return `Unwrappable<RequestResult<${generics}>>`;
487
453
  }
488
454
  //#endregion
489
- //#region src/functionParams.ts
490
- const declarationPrinter$4 = functionPrinter({ mode: "declaration" });
491
- const callPrinter = functionPrinter({ mode: "call" });
492
- function isGroup(spec) {
493
- return "children" in spec;
455
+ //#region ../../internals/client/src/builders/returnStatement.ts
456
+ /**
457
+ * Builds the return statement of a generated operation function. With the default
458
+ * `returnType: 'full'` the runtime call already resolves to `{ data, error, request, response }`.
459
+ * The generated code casts that result to the operation's `RequestResult`, then wraps it with
460
+ * `withUnwrap`, so the caller can `await` it directly or call `.unwrap()` for the bare success
461
+ * body. With `returnType: 'data'` it instead routes the call through the runtime's `unwrapResult`,
462
+ * which narrows the resolved value down to the bare success body the same way `RequestResult`
463
+ * already does, using the plugin default when the call leaves `throwOnError` unset.
464
+ * With `returnType: 'plain'` it returns the runtime call as is, cast to `RequestResult`, with no helper.
465
+ *
466
+ * Cast first, wrap second, for the `'full'` path. That order keeps `withUnwrap`'s generic inferred
467
+ * as `RequestResult` instead of the runtime's own internal result type. Casting an `Unwrappable<A>`
468
+ * straight to `Unwrappable<B>` does not work: the `.then` overload stays pinned to `A`, and `as`
469
+ * rejects that two-generic swap even where `A` and `B` on their own would satisfy it.
470
+ *
471
+ * @example
472
+ * `return withUnwrap(request({ method: 'POST', url: '/pet', ...config }) as Promise<RequestResult<AddPetResponses, ThrowOnError>>)`
473
+ * @example
474
+ * `return unwrapResult(request({ method: 'POST', url: '/pet', ...config }), config.throwOnError ?? false) as Promise<UnwrappedResult<AddPetResponses, ThrowOnError>>`
475
+ * @example
476
+ * `return request({ method: 'POST', url: '/pet', ...config }) as Promise<RequestResult<AddPetResponses, ThrowOnError>>`
477
+ */
478
+ function buildReturnStatement({ node, types, callConfig, returnType, throwOnErrorDefault }) {
479
+ if (returnType === "data") return `return unwrapResult(request(${callConfig}), config.throwOnError ?? ${throwOnErrorDefault}) as ${buildResultType({
480
+ node,
481
+ types,
482
+ returnType
483
+ })}`;
484
+ if (returnType === "plain") return `return request(${callConfig}) as Promise<RequestResult<${buildRequestResultGenerics({
485
+ node,
486
+ types
487
+ })}>>`;
488
+ return `return withUnwrap(request(${callConfig}) as Promise<RequestResult<${buildRequestResultGenerics({
489
+ node,
490
+ types
491
+ })}>>)`;
494
492
  }
495
- function groupEntries(group) {
496
- return Object.entries(group.children).filter(([, child]) => child != null);
493
+ //#endregion
494
+ //#region ../../internals/client/src/builders/security.ts
495
+ function serializeAuth(auth) {
496
+ const parts = [`type: '${auth.type}'`];
497
+ if (auth.scheme) parts.push(`scheme: '${auth.scheme}'`);
498
+ if (auth.name) parts.push(`name: '${auth.name}'`);
499
+ if (auth.in) parts.push(`in: '${auth.in}'`);
500
+ return `{ ${parts.join(", ")} }`;
497
501
  }
498
502
  /**
499
- * Assembles a destructured group parameter from a binding pattern and an optional
500
- * type literal. Built directly because `createFunctionParameter({ properties })`
501
- * requires every member to carry a type, while these groups also hold untyped,
502
- * value-only call entries.
503
+ * Maps an OpenAPI security scheme to the inline `Auth` object, or `null` when the runtime cannot
504
+ * place it (an unresolved `$ref`, or an `apiKey` without a name or outside `header` / `query` /
505
+ * `cookie`). `http` schemes other than `basic` are treated as bearer.
503
506
  */
504
- function createGroupParam(elements, members, default_) {
505
- return {
506
- kind: "FunctionParameter",
507
- name: ast.factory.createObjectBindingPattern({ elements }),
508
- type: members.length ? ast.factory.createTypeLiteral({ members }) : void 0,
509
- default: default_,
510
- optional: false
507
+ function resolveSecurityScheme(scheme) {
508
+ if (!scheme || "$ref" in scheme) return null;
509
+ if (scheme.type === "apiKey") {
510
+ if (!scheme.name || scheme.in !== "header" && scheme.in !== "query" && scheme.in !== "cookie") return null;
511
+ return {
512
+ type: "apiKey",
513
+ name: scheme.name,
514
+ in: scheme.in
515
+ };
516
+ }
517
+ if (scheme.type === "http") return {
518
+ type: "http",
519
+ scheme: scheme.scheme?.toLowerCase() === "basic" ? "basic" : "bearer"
511
520
  };
521
+ if (scheme.type === "oauth2") return { type: "oauth2" };
522
+ if (scheme.type === "openIdConnect") return { type: "openIdConnect" };
523
+ return null;
512
524
  }
513
- function createDeclarationLeaf(name, spec) {
514
- if (spec.default !== void 0) return ast.factory.createFunctionParameter({
515
- name,
516
- type: spec.type,
517
- default: spec.default,
518
- rest: spec.mode === "inlineSpread"
519
- });
520
- return ast.factory.createFunctionParameter({
521
- name,
522
- type: spec.type,
523
- optional: !!spec.optional,
524
- rest: spec.mode === "inlineSpread"
525
- });
526
- }
527
- function createDeclarationParam(name, spec) {
528
- if (isGroup(spec)) {
529
- const entries = groupEntries(spec);
530
- return createGroupParam(entries.map(([childName]) => ({ name: childName })), entries.filter(([, child]) => child.type).map(([childName, child]) => ({
531
- name: childName,
532
- type: child.type,
533
- optional: !!child.optional || child.default !== void 0
534
- })), spec.default);
535
- }
536
- return createDeclarationLeaf(name, spec);
537
- }
538
- function createCallParam(name, spec) {
539
- if (isGroup(spec)) return createGroupParam(groupEntries(spec).map(([childName, child]) => ({ name: child.mode === "inlineSpread" ? spec.mode === "inlineSpread" ? child.value ?? childName : `...${child.value ?? childName}` : child.value ? `${childName}: ${child.value}` : childName })), []);
540
- return ast.factory.createFunctionParameter({
541
- name: spec.value ?? name,
542
- rest: spec.mode === "inlineSpread"
543
- });
544
- }
545
- /**
546
- * Creates function parameter builders for generating function signatures and calls.
547
- * Returns utilities to output constructor signatures (`toConstructor()`) or call expressions (`toCall()`).
548
- */
549
- function createFunctionParams(params) {
550
- const entries = Object.entries(params).filter(([, spec]) => spec != null);
551
- return {
552
- toConstructor() {
553
- return declarationPrinter$4.print(ast.factory.createFunctionParameters({ params: entries.map(([name, spec]) => createDeclarationParam(name, spec)) })) ?? "";
554
- },
555
- toCall() {
556
- return callPrinter.print(ast.factory.createFunctionParameters({ params: entries.map(([name, spec]) => createCallParam(name, spec)) })) ?? "";
557
- }
558
- };
559
- }
560
- //#endregion
561
- //#region src/utils.ts
562
525
  /**
563
- * Returns `true` when any direction of the parser uses Zod (used for dependency checks).
526
+ * Derives the per-operation security metadata from the OpenAPI document. The operation's own
527
+ * `security` overrides the global `security` (an explicit empty array disables auth), and every
528
+ * referenced scheme is resolved from `components.securitySchemes` into a flat, de-duplicated list of
529
+ * `Auth` objects the runtime walks in order.
530
+ *
531
+ * @example
532
+ * `getOperationSecurity({ document, method: 'POST', path: '/pet' })`
533
+ * `// [{ type: 'http', scheme: 'bearer' }]`
564
534
  */
565
- function isParserEnabled(parser) {
566
- if (!parser) return false;
567
- if (parser === "zod") return true;
568
- return !!(parser.request || parser.response);
535
+ function getOperationSecurity({ document, method, path }) {
536
+ if (!document) return void 0;
537
+ const requirements = (document.paths?.[path]?.[method.toLowerCase()])?.security ?? document.security;
538
+ if (!requirements?.length) return void 0;
539
+ const definitions = document.components?.securitySchemes ?? {};
540
+ const security = [];
541
+ const seen = /* @__PURE__ */ new Set();
542
+ for (const requirement of requirements) for (const schemeName of Object.keys(requirement)) {
543
+ if (seen.has(schemeName)) continue;
544
+ seen.add(schemeName);
545
+ const auth = resolveSecurityScheme(definitions[schemeName]);
546
+ if (auth) security.push(auth);
547
+ }
548
+ return security.length ? security : void 0;
569
549
  }
570
550
  /**
571
- * Returns `'zod'` when request body parsing is enabled, `null` otherwise.
572
- * The string shorthand `'zod'` also enables request body parsing (existing behavior).
551
+ * Serializes the per-operation security into the literal emitted on each generated call's `security`
552
+ * field. The runtime `resolveAuth` helper walks it, calling the configured `auth` resolver per entry.
553
+ *
554
+ * @example
555
+ * `buildSecurityMetadata({ security: [{ type: 'http', scheme: 'bearer' }] }) // "[{ type: 'http', scheme: 'bearer' }]"`
573
556
  */
574
- function resolveRequestParser(parser) {
575
- if (!parser) return null;
576
- if (parser === "zod") return "zod";
577
- return parser.request ?? null;
557
+ function buildSecurityMetadata({ security }) {
558
+ if (!security?.length) return null;
559
+ return `[${security.map(serializeAuth).join(", ")}]`;
578
560
  }
561
+ //#endregion
562
+ //#region ../../internals/client/src/builders/signature.ts
563
+ const declarationPrinter = functionPrinter({ mode: "declaration" });
579
564
  /**
580
- * Returns `'zod'` when query-parameters parsing is enabled, `null` otherwise.
581
- * Only the object form `{ request: 'zod' }` enables query-params parsing.
582
- * The string shorthand `'zod'` does not, preserving its existing behavior.
565
+ * Builds the grouped-options signature for one operation: a single `options` object whose `TData`
566
+ * is the `<Name>Options` type, and a `RequestResult` return type keyed to the per-status responses
567
+ * record. There are no positional arguments.
568
+ *
569
+ * The generated file imports `<Name>Options` and `<Name>Responses` and uses them directly, so no
570
+ * per-operation input type has to be emitted. Both names come from `types`, which is `plugin-ts` or
571
+ * `plugin-zod`'s inferred types (see `resolveOperationTypes`).
583
572
  */
584
- function resolveQueryParamsParser(parser) {
585
- if (!parser || parser === "zod") return null;
586
- return parser.request ?? null;
573
+ function buildGroupedOptionsSignature({ node, types, returnType, throwOnErrorDefault }) {
574
+ const optionsName = types.response.options(node);
575
+ const { isOptional } = getRequestGroupOptionality(node);
576
+ return {
577
+ paramsSignature: declarationPrinter.print(createFunctionParameters({ params: [createFunctionParameter({
578
+ name: "options",
579
+ type: `Options<${optionsName}, ThrowOnError>`,
580
+ ...isOptional ? { default: "{}" } : {}
581
+ })] })) ?? "",
582
+ returnType: buildResultType({
583
+ node,
584
+ types,
585
+ returnType
586
+ }),
587
+ generics: [`ThrowOnError extends boolean = ${throwOnErrorDefault}`]
588
+ };
587
589
  }
590
+ //#endregion
591
+ //#region ../../internals/client/src/builders/validatorOptions.ts
588
592
  /**
589
- * Returns `'zod'` when response-direction parsing is enabled, `null` otherwise.
590
- * `parser: 'zod'` (string shorthand) maps to response parsing and returns `'zod'`.
593
+ * Returns `'zod'` when request body parsing is enabled, `null` otherwise. The string shorthand
594
+ * `'zod'` validates the response only, so it does not enable request parsing.
591
595
  */
592
- function resolveResponseParser(parser) {
593
- if (!parser) return null;
594
- if (parser === "zod") return "zod";
595
- return parser.response ?? null;
596
+ function resolveRequestValidator(validator) {
597
+ if (!validator || validator === "zod") return null;
598
+ return validator.request ?? null;
596
599
  }
597
600
  /**
598
- * Builds HTTP headers array for a client request.
599
- * Includes Content-Type (if not default) and spreads header parameters if present.
601
+ * Returns `'zod'` when response parsing is enabled, `null` otherwise. The string shorthand `'zod'`
602
+ * maps to response parsing.
600
603
  */
601
- function buildHeaders(contentType, hasHeaderParams) {
602
- return [contentType !== "application/json" && contentType !== "multipart/form-data" ? `'Content-Type': '${contentType}'` : null, hasHeaderParams ? "...headers" : null].filter(Boolean);
604
+ function resolveResponseValidator(validator) {
605
+ if (!validator) return null;
606
+ if (validator === "zod") return "zod";
607
+ return validator.response ?? null;
603
608
  }
604
609
  /**
605
- * Returns the generic type arguments — response, error, and request body — for a generated
606
- * client call.
607
- *
608
- * When `dataReturnType` is `'full'`, the response generic widens to a union of all documented
609
- * status types. When `parser` is `'zod'` and a request body schema exists, the request type
610
- * uses `z.output<typeof schema>` to reflect post-transform types (e.g. date coercion).
610
+ * Resolves the zod expression a generated client validates a success response with. Only success
611
+ * (2xx) bodies reach the parse under the throw-on-error contract, so the success-only
612
+ * `<operation>ResponseSchema` is used; error bodies are never zod-parsed.
611
613
  */
612
- function buildGenerics(node, tsResolver, options = {}) {
613
- const allStatusNames = node.responses.map((r) => tsResolver.resolveResponseStatusName(node, r.statusCode));
614
- const successNames = resolveSuccessNames(node, tsResolver);
615
- const responseName = options.dataReturnType === "full" ? allStatusNames.length > 0 ? allStatusNames.join(" | ") : tsResolver.resolveResponseName(node) : successNames.length > 0 ? successNames.join(" | ") : tsResolver.resolveResponseName(node);
616
- const requestName = node.requestBody?.content?.[0]?.schema ? tsResolver.resolveDataName(node) : null;
617
- const errorNames = node.responses.filter((r) => Number.parseInt(r.statusCode, 10) >= 400).map((r) => tsResolver.resolveResponseStatusName(node, r.statusCode));
618
- const TError = `ResponseErrorConfig<${errorNames.length > 0 ? errorNames.join(" | ") : "Error"}>`;
619
- const zodRequestName = options.parser === "zod" && options.zodResolver && node.requestBody?.content?.[0]?.schema ? options.zodResolver.resolveDataName?.(node) ?? null : null;
620
- return [
621
- responseName,
622
- TError,
623
- zodRequestName ? `z.output<typeof ${zodRequestName}>` : requestName || "unknown"
624
- ].filter(Boolean);
614
+ function buildZodResponseParse(node, zodResolver) {
615
+ const name = zodResolver.response.response(node);
616
+ return name ? {
617
+ expression: name,
618
+ importNames: [name]
619
+ } : null;
625
620
  }
626
621
  /**
627
- * Builds the parameters object for a class-based client method.
628
- * Includes URL, method, base URL, headers, and request/response data.
622
+ * Resolves the zod expression a generated client validates an error body with on the non-throw path.
623
+ * Uses the error-only `<operation>ErrorSchema` (the union of non-2xx statuses); returns `null` when the
624
+ * operation documents no error responses with a schema.
629
625
  */
630
- function buildClassClientParams({ node, url, baseURL, tsResolver, isFormData, isMultipleContentTypes, hasFormData, headers, zodQueryParamsName }) {
631
- const { query: queryParams } = getOperationParameters(node);
632
- const queryParamsName = queryParams.length > 0 ? tsResolver.resolveQueryParamsName(node, queryParams[0]) : null;
633
- const requestName = node.requestBody?.content?.[0]?.schema ? tsResolver.resolveDataName(node) : null;
634
- const responseType = getResponseType(node);
635
- return createFunctionParams({ config: {
636
- mode: "object",
637
- children: {
638
- requestConfig: { mode: "inlineSpread" },
639
- method: { value: JSON.stringify(ast.isHttpOperationNode(node) ? node.method.toUpperCase() : "") },
640
- url: { value: url },
641
- baseURL: baseURL ? { value: JSON.stringify(baseURL) } : null,
642
- params: queryParamsName ? zodQueryParamsName ? { value: "requestParams" } : {} : null,
643
- data: requestName ? { value: isMultipleContentTypes && hasFormData ? "contentType === 'multipart/form-data' ? formData as FormData : requestData" : isFormData ? "formData as FormData" : "requestData" } : null,
644
- contentType: isMultipleContentTypes ? {} : null,
645
- responseType: responseType ? { value: JSON.stringify(responseType) } : null,
646
- headers: headers.length ? { value: `{ ${headers.join(", ")}, ...requestConfig.headers }` } : null
647
- }
648
- } });
626
+ function buildZodErrorParse(node, zodResolver) {
627
+ if (!node.responses.some((res) => !isSuccessStatusCode(res.statusCode) && res.content?.some((entry) => entry.schema))) return null;
628
+ const name = zodResolver.response.error?.(node);
629
+ return name ? {
630
+ expression: name,
631
+ importNames: [name]
632
+ } : null;
649
633
  }
634
+ //#endregion
635
+ //#region ../../internals/client/src/builders/validator.ts
650
636
  /**
651
- * Builds the request data parsing line for client methods.
652
- * Applies Zod validation when `parser.request === 'zod'`, otherwise assigns data directly.
637
+ * Builds the validator-hook references for one operation. Request validation runs before the send;
638
+ * response validation runs on the success body only. Returns `null` references when the matching
639
+ * direction is disabled or the schema is absent.
653
640
  */
654
- function buildRequestDataLine({ parser, node, zodResolver }) {
655
- const requestParser = resolveRequestParser(parser);
656
- const zodRequestName = zodResolver && requestParser === "zod" && node.requestBody?.content?.[0]?.schema ? zodResolver.resolveDataName?.(node) : null;
657
- if (requestParser === "zod" && zodRequestName) return `const requestData = ${zodRequestName}.parse(data)`;
658
- if (node.requestBody?.content?.[0]?.schema) return "const requestData = data";
659
- return "";
641
+ function buildValidatorHooks({ node, validator, zodResolver }) {
642
+ const importedZodNames = [];
643
+ const hasRequestBody = Boolean(node.requestBody?.content?.[0]?.schema);
644
+ const zodRequestName = zodResolver && resolveRequestValidator(validator) === "zod" && hasRequestBody ? zodResolver.response.body(node) : null;
645
+ const request = zodRequestName ?? null;
646
+ if (zodRequestName) importedZodNames.push(zodRequestName);
647
+ const responseParse = zodResolver && resolveResponseValidator(validator) === "zod" ? buildZodResponseParse(node, zodResolver) : null;
648
+ const response = responseParse ? responseParse.expression : null;
649
+ if (responseParse) importedZodNames.push(...responseParse.importNames);
650
+ const errorParse = zodResolver && resolveResponseValidator(validator) === "zod" ? buildZodErrorParse(node, zodResolver) : null;
651
+ const error = errorParse ? errorParse.expression : null;
652
+ if (errorParse) importedZodNames.push(...errorParse.importNames);
653
+ return {
654
+ request,
655
+ response,
656
+ error,
657
+ importedZodNames
658
+ };
660
659
  }
660
+ //#endregion
661
+ //#region ../../internals/client/src/builders/styles.ts
661
662
  /**
662
- * Builds the query parameters parsing line for client methods.
663
- * Returns an empty string when no query params exist or query-params parsing is not enabled.
664
- * Only the object form `parser: { request: 'zod' }` triggers this. `parser: 'zod'` does not.
663
+ * Renders a parameter name as an object-literal key, quoted when it is not a bare identifier.
664
+ * Path keys are camelCased to match the URL template placeholders. Query, header, and cookie keys
665
+ * keep the spec name, matching the remapped keys the runtime serializes.
665
666
  */
666
- function buildQueryParamsLine({ parser, node, zodResolver }) {
667
- if (resolveQueryParamsParser(parser) !== "zod" || !zodResolver) return "";
668
- const { query: queryParams } = getOperationParameters(node);
669
- if (queryParams.length === 0) return "";
670
- const zodQueryParamsName = zodResolver.resolveQueryParamsName?.(node, queryParams[0]);
671
- if (!zodQueryParamsName) return "";
672
- return `const requestParams = ${zodQueryParamsName}.parse(params)`;
667
+ function toKey(name, location) {
668
+ const key = location === "path" ? camelCase(name) : name;
669
+ return isValidVarName(key) ? key : JSON.stringify(key);
673
670
  }
674
671
  /**
675
- * Builds the form data conversion line for file upload requests.
676
- * Returns empty string if not applicable.
672
+ * Serializes one parameter's metadata into a `{ style, explode }` literal, or `null` when the
673
+ * parameter carries neither. Path and query carry the serialization `style`; header and cookie use a
674
+ * fixed style (`simple` and `form`), so only `explode` is emitted for them.
677
675
  */
678
- function buildFormDataLine(isFormData, hasRequest) {
679
- return isFormData && hasRequest ? "const formData = buildFormData(requestData)" : "";
676
+ function serializeParameter(parameter) {
677
+ const parts = [];
678
+ if ((parameter.in === "path" || parameter.in === "query") && parameter.style) parts.push(`style: '${parameter.style}'`);
679
+ if (parameter.explode !== void 0) parts.push(`explode: ${parameter.explode}`);
680
+ return parts.length > 0 ? `{ ${parts.join(", ")} }` : null;
680
681
  }
681
682
  /**
682
- * Builds the return statement for a client method.
683
- * When `dataReturnType` is `'full'`, casts the response to the status-discriminated union type.
684
- * When `parser.response` is `'zod'`, pipes the response body through the Zod schema before returning.
683
+ * Builds the per-operation `styles` literal from the operation's parameters, grouped by location.
684
+ * Path entries are keyed by the camelCased name to match the URL template placeholders; query,
685
+ * header, and cookie entries keep the spec name to match the keys the runtime serializes.
686
+ * Only parameters whose source defines `style` or `explode` are emitted, so calls without
687
+ * serialization metadata keep the runtime defaults and existing output is unchanged. Returns `null`
688
+ * when no parameter carries metadata.
689
+ *
690
+ * @example
691
+ * ```ts
692
+ * // a path param with { style: 'matrix', explode: true } and a query param with { explode: false }
693
+ * buildStyles({ node }) // "{ path: { id: { style: 'matrix', explode: true } }, query: { tags: { explode: false } } }"
694
+ * ```
685
695
  */
686
- function buildReturnStatement({ dataReturnType, parser, node, zodResolver, tsResolver }) {
687
- const responseParser = resolveResponseParser(parser);
688
- const zodResponseName = zodResolver && responseParser === "zod" ? zodResolver.resolveResponseName?.(node) : null;
689
- if (dataReturnType === "full" && tsResolver) {
690
- const unionType = buildStatusUnionType(node, tsResolver);
691
- if (responseParser === "zod" && zodResponseName) return `return {...res, data: ${zodResponseName}.parse(res.data)} as ${unionType}`;
692
- return `return res as ${unionType}`;
696
+ function buildStyles({ node }) {
697
+ if (!ast.isHttpOperationNode(node)) return null;
698
+ const groups = {
699
+ path: [],
700
+ query: [],
701
+ header: [],
702
+ cookie: []
703
+ };
704
+ for (const parameter of node.parameters) {
705
+ const literal = serializeParameter(parameter);
706
+ if (!literal) continue;
707
+ groups[parameter.in].push(`${toKey(parameter.name, parameter.in)}: ${literal}`);
693
708
  }
694
- if (dataReturnType === "data" && responseParser === "zod" && zodResponseName) return `return ${zodResponseName}.parse(res.data)`;
695
- return "return res.data";
709
+ const locations = Object.keys(groups).filter((location) => groups[location].length > 0);
710
+ if (locations.length === 0) return null;
711
+ return `{ ${locations.map((location) => `${location}: { ${groups[location].join(", ")} }`).join(", ")} }`;
696
712
  }
697
713
  //#endregion
698
- //#region src/components/Url.tsx
699
- const declarationPrinter$3 = functionPrinter({ mode: "declaration" });
700
- function buildUrlParamsNode({ paramsType, paramsCasing, pathParamsType, node, tsResolver }) {
701
- return createOperationParams({
702
- ...node,
703
- parameters: node.parameters.filter((p) => p.in === "path"),
704
- requestBody: void 0
705
- }, {
706
- paramsType: paramsType === "object" ? "object" : "inline",
707
- pathParamsType: paramsType === "object" ? "object" : pathParamsType === "object" ? "object" : "inline",
708
- paramsCasing,
709
- resolver: tsResolver
710
- });
711
- }
712
- function Url({ name, isExportable = true, isIndexable = true, baseURL, paramsType, paramsCasing, pathParamsType, node, tsResolver }) {
714
+ //#region ../../internals/client/src/components/Operation.tsx
715
+ /**
716
+ * Renders one client operation: the grouped `<Name>Request` type and the function that forwards a
717
+ * single `options` object to the resolved client and returns the `Unwrappable<RequestResult>`. The
718
+ * type, signature, and call config are built with the AST factory, and only the jsx-renderer emits
719
+ * the source.
720
+ */
721
+ function Operation({ name, node, types, zodResolver, validator, returnType, throwOnErrorDefault, security, isExportable = true, isIndexable = true }) {
713
722
  if (!ast.isHttpOperationNode(node)) return null;
714
- const paramsNode = buildUrlParamsNode({
715
- paramsType,
716
- paramsCasing,
717
- pathParamsType,
723
+ const signature = buildGroupedOptionsSignature({
718
724
  node,
719
- tsResolver
725
+ types,
726
+ returnType,
727
+ throwOnErrorDefault
720
728
  });
721
- const paramsSignature = declarationPrinter$3.print(paramsNode) ?? "";
722
- const { path: originalPathParams } = getOperationParameters(node);
723
- const { path: casedPathParams } = getOperationParameters(node, { paramsCasing });
724
- const pathParamsMapping = paramsCasing ? buildParamsMapping(originalPathParams, casedPathParams) : null;
725
- return /* @__PURE__ */ jsx(File.Source, {
726
- name,
727
- isExportable,
728
- isIndexable,
729
- children: /* @__PURE__ */ jsxs(Function, {
730
- name,
731
- export: isExportable,
732
- params: paramsSignature,
733
- children: [
734
- pathParamsMapping && Object.entries(pathParamsMapping).filter(([originalName, camelCaseName]) => isValidVarName(originalName) && originalName !== camelCaseName).map(([originalName, camelCaseName]) => `const ${originalName} = ${camelCaseName}`).join("\n"),
735
- pathParamsMapping && Object.keys(pathParamsMapping).length > 0 && /* @__PURE__ */ jsx("br", {}),
736
- /* @__PURE__ */ jsx(Const, {
737
- name: "res",
738
- children: `{ method: '${node.method.toUpperCase()}', url: ${Url$1.toTemplateString(node.path, { prefix: baseURL })} as const }`
739
- }),
740
- /* @__PURE__ */ jsx("br", {}),
741
- "return res"
742
- ]
743
- })
744
- });
745
- }
746
- //#endregion
747
- //#region src/components/Client.tsx
748
- const declarationPrinter$2 = functionPrinter({ mode: "declaration" });
749
- function buildClientParamsNode({ paramsType, paramsCasing, pathParamsType, node, tsResolver, isConfigurable }) {
750
- return createOperationParams(node, {
751
- paramsType,
752
- pathParamsType: paramsType === "object" ? "object" : pathParamsType === "object" ? "object" : "inline",
753
- paramsCasing,
754
- resolver: tsResolver,
755
- extraParams: [...isConfigurable ? [ast.factory.createFunctionParameter({
756
- name: "config",
757
- type: buildRequestConfigType(node, tsResolver),
758
- default: "{}"
759
- })] : []]
729
+ const validators = buildValidatorHooks({
730
+ node,
731
+ validator,
732
+ zodResolver
760
733
  });
761
- }
762
- function Client({ name, isExportable = true, isIndexable = true, returnType, baseURL, dataReturnType, parser, paramsType, paramsCasing, pathParamsType, node, tsResolver, zodResolver, urlName, children, isConfigurable = true }) {
763
- if (!ast.isHttpOperationNode(node)) return null;
764
- const { defaultContentType: contentType, isMultipleContentTypes, hasFormData } = getContentTypeInfo(node);
765
- const isFormData = !isMultipleContentTypes && contentType === "multipart/form-data";
734
+ const securityLiteral = buildSecurityMetadata({ security });
735
+ const stylesLiteral = buildStyles({ node });
736
+ const { defaultContentType } = getContentTypeInfo(node);
737
+ const bakedRequestContentType = Boolean(node.requestBody?.content?.[0]?.schema) && defaultContentType !== "application/json" ? defaultContentType : null;
738
+ const mergeContentType = Boolean(bakedRequestContentType) && getResponseContentTypeInfo(node).isMultipleContentTypes;
739
+ const contentTypeLiteral = !bakedRequestContentType ? null : mergeContentType ? `contentType: { request: '${bakedRequestContentType}', ...(typeof contentType === 'string' ? { request: contentType } : contentType) }` : `contentType: { request: '${bakedRequestContentType}' }`;
740
+ const eventStream = isEventStream(node);
766
741
  const responseType = getResponseType(node);
767
- const { path: originalPathParams, query: originalQueryParams, header: originalHeaderParams } = getOperationParameters(node);
768
- const { path: casedPathParams, query: casedQueryParams, header: casedHeaderParams } = getOperationParameters(node, { paramsCasing });
769
- const pathParamsMapping = paramsCasing && !urlName ? buildParamsMapping(originalPathParams, casedPathParams) : null;
770
- const queryParamsMapping = paramsCasing ? buildParamsMapping(originalQueryParams, casedQueryParams) : null;
771
- const headerParamsMapping = paramsCasing ? buildParamsMapping(originalHeaderParams, casedHeaderParams) : null;
772
- const requestName = node.requestBody?.content?.[0]?.schema ? tsResolver.resolveDataName(node) : null;
773
- const successNames = resolveSuccessNames(node, tsResolver);
774
- const responseName = successNames.length > 0 ? successNames.join(" | ") : tsResolver.resolveResponseName(node);
775
- const queryParamsName = originalQueryParams.length > 0 ? tsResolver.resolveQueryParamsName(node, originalQueryParams[0]) : null;
776
- const headerParamsName = originalHeaderParams.length > 0 ? tsResolver.resolveHeaderParamsName(node, originalHeaderParams[0]) : null;
777
- const requestParser = resolveRequestParser(parser);
778
- const responseParser = resolveResponseParser(parser);
779
- const zodResponseName = zodResolver && responseParser === "zod" ? zodResolver.resolveResponseName?.(node) : null;
780
- const zodRequestName = zodResolver && requestParser === "zod" && node.requestBody?.content?.[0]?.schema ? zodResolver.resolveDataName?.(node) : null;
781
- const queryParamsParser = resolveQueryParamsParser(parser);
782
- const zodQueryParamsName = zodResolver && queryParamsParser === "zod" && originalQueryParams.length > 0 ? zodResolver.resolveQueryParamsName?.(node, originalQueryParams[0]) : null;
783
- const errorNames = node.responses.filter((r) => {
784
- return Number.parseInt(r.statusCode, 10) >= 400;
785
- }).map((r) => tsResolver.resolveResponseStatusName(node, r.statusCode));
786
- const headers = [!isMultipleContentTypes && contentType !== "application/json" && contentType !== "multipart/form-data" ? `'Content-Type': '${contentType}'` : null, headerParamsName ? headerParamsMapping ? "...mappedHeaders" : "...headers" : null].filter(Boolean);
787
- const TError = `ResponseErrorConfig<${errorNames.length > 0 ? errorNames.join(" | ") : "Error"}>`;
788
- const allStatusNames = node.responses.map((r) => tsResolver.resolveResponseStatusName(node, r.statusCode));
789
- const generics = [
790
- dataReturnType === "full" ? allStatusNames.length > 0 ? allStatusNames.join(" | ") : responseName : responseName,
791
- TError,
792
- parser === "zod" && zodRequestName ? `z.output<typeof ${zodRequestName}>` : requestName || "unknown"
742
+ const responseTypeLiteral = responseType ? `responseType: '${responseType}'` : null;
743
+ const validatorEntries = [
744
+ validators.request ? `request: ${validators.request}` : null,
745
+ validators.response ? `response: ${validators.response}` : null,
746
+ validators.error ? `error: ${validators.error}` : null
793
747
  ].filter(Boolean);
794
- const paramsNode = buildClientParamsNode({
795
- paramsType,
796
- paramsCasing,
797
- pathParamsType,
798
- node,
799
- tsResolver,
800
- isConfigurable
801
- });
802
- const paramsSignature = declarationPrinter$2.print(paramsNode) ?? "";
803
- const urlParamsNode = buildUrlParamsNode({
804
- paramsType,
805
- paramsCasing,
806
- pathParamsType,
748
+ const validatorLiteral = validatorEntries.length ? `validator: { ${validatorEntries.join(", ")} }` : null;
749
+ const callConfig = `{ ${[
750
+ `method: '${node.method.toUpperCase()}'`,
751
+ `url: '${node.path}'`,
752
+ securityLiteral ? `security: ${securityLiteral}` : null,
753
+ stylesLiteral ? `styles: ${stylesLiteral}` : null,
754
+ validatorLiteral,
755
+ contentTypeLiteral,
756
+ responseTypeLiteral,
757
+ "...config",
758
+ `throwOnError: config.throwOnError ?? ${throwOnErrorDefault}`
759
+ ].filter(Boolean).join(", ")} }`;
760
+ const eventType = `SuccessOf<${types.response.responses(node)}>`;
761
+ const functionReturnType = eventStream ? `Promise<EventStreamResult<${eventType}>>` : signature.returnType;
762
+ const returnStatement = eventStream ? `return toEventStream<${eventType}>(request(${callConfig}))` : buildReturnStatement({
807
763
  node,
808
- tsResolver
764
+ types,
765
+ callConfig,
766
+ returnType,
767
+ throwOnErrorDefault
809
768
  });
810
- const urlParamsCall = functionPrinter({ mode: "call" }).print(urlParamsNode) ?? "";
811
- const clientParams = createFunctionParams({ config: {
812
- mode: "object",
813
- children: {
814
- method: { value: stringify(node.method.toUpperCase()) },
815
- url: { value: urlName ? `${urlName}(${urlParamsCall}).url.toString()` : Url$1.toTemplateString(node.path) },
816
- baseURL: baseURL && !urlName ? { value: `\`${baseURL}\`` } : null,
817
- params: queryParamsName ? zodQueryParamsName ? { value: "requestParams" } : queryParamsMapping ? { value: "mappedParams" } : {} : null,
818
- data: requestName ? { value: isMultipleContentTypes && hasFormData ? "contentType === 'multipart/form-data' ? formData as FormData : requestData" : isFormData ? "formData as FormData" : "requestData" } : null,
819
- contentType: isConfigurable && isMultipleContentTypes ? {} : null,
820
- responseType: responseType ? { value: stringify(responseType) } : null,
821
- requestConfig: isConfigurable ? { mode: "inlineSpread" } : null,
822
- headers: headers.length ? { value: isConfigurable ? `{ ${headers.join(", ")}, ...requestConfig.headers }` : `{ ${headers.join(", ")} }` } : null
823
- }
824
- } });
825
- const statusUnionType = dataReturnType === "full" ? buildStatusUnionType(node, tsResolver) : null;
826
- const childrenElement = children ? children : /* @__PURE__ */ jsxs(Fragment, { children: [
827
- dataReturnType === "full" && responseParser === "zod" && zodResponseName && statusUnionType && `return {...res, data: ${zodResponseName}.parse(res.data)} as ${statusUnionType}`,
828
- dataReturnType === "full" && responseParser !== "zod" && statusUnionType && `return res as ${statusUnionType}`,
829
- dataReturnType === "data" && responseParser === "zod" && zodResponseName && `return ${zodResponseName}.parse(res.data)`,
830
- dataReturnType === "data" && responseParser !== "zod" && "return res.data"
831
- ] });
832
- return /* @__PURE__ */ jsxs(Fragment, { children: [/* @__PURE__ */ jsx("br", {}), /* @__PURE__ */ jsx(File.Source, {
769
+ return /* @__PURE__ */ jsx(File.Source, {
833
770
  name,
834
771
  isExportable,
835
772
  isIndexable,
836
773
  children: /* @__PURE__ */ jsxs(Function, {
837
774
  name,
838
- async: true,
839
775
  export: isExportable,
840
- params: paramsSignature,
776
+ generics: signature.generics,
777
+ params: signature.paramsSignature,
778
+ returnType: functionReturnType,
841
779
  JSDoc: { comments: buildOperationComments(node, {
842
780
  link: "urlPath",
843
781
  linkPosition: "beforeDeprecated",
844
782
  splitLines: true
845
783
  }) },
846
- returnType,
847
784
  children: [
848
- isConfigurable ? `const { client: request = client, ${isMultipleContentTypes ? `contentType = ${stringify(contentType)}, ` : ""}...requestConfig } = config` : "",
849
- /* @__PURE__ */ jsx("br", {}),
850
- pathParamsMapping && Object.entries(pathParamsMapping).filter(([originalName, camelCaseName]) => isValidVarName(originalName) && originalName !== camelCaseName).map(([originalName, camelCaseName]) => `const ${originalName} = ${camelCaseName}`).join("\n"),
851
- pathParamsMapping && /* @__PURE__ */ jsx("br", {}),
852
- queryParamsMapping && queryParamsName && /* @__PURE__ */ jsxs(Fragment, { children: [`const mappedParams = params ? { ${Object.entries(queryParamsMapping).map(([originalName, camelCaseName]) => `"${originalName}": params.${camelCaseName}`).join(", ")} } : undefined`, /* @__PURE__ */ jsx("br", {})] }),
853
- headerParamsMapping && headerParamsName && /* @__PURE__ */ jsxs(Fragment, { children: [`const mappedHeaders = headers ? { ${Object.entries(headerParamsMapping).map(([originalName, camelCaseName]) => `"${originalName}": headers.${camelCaseName}`).join(", ")} } : undefined`, /* @__PURE__ */ jsx("br", {})] }),
854
- requestParser === "zod" && zodRequestName ? `const requestData = ${zodRequestName}.parse(data)` : requestName && "const requestData = data",
855
- zodQueryParamsName && `const requestParams = ${zodQueryParamsName}.parse(params)`,
856
- (isFormData || isMultipleContentTypes && hasFormData) && requestName && "const formData = buildFormData(requestData)",
785
+ mergeContentType ? "const { client: request = client, contentType, ...config } = options" : "const { client: request = client, ...config } = options",
857
786
  /* @__PURE__ */ jsx("br", {}),
858
- isConfigurable ? `const res = await request<${generics.join(", ")}>(${clientParams.toCall()})` : `const res = await client<${generics.join(", ")}>(${clientParams.toCall()})`,
859
- /* @__PURE__ */ jsx("br", {}),
860
- childrenElement
787
+ returnStatement
861
788
  ]
862
789
  })
863
- })] });
864
- }
865
- //#endregion
866
- //#region src/components/ClassClient.tsx
867
- const declarationPrinter$1 = functionPrinter({ mode: "declaration" });
868
- function generateMethod$1({ node, name, tsResolver, zodResolver, baseURL, dataReturnType, parser, paramsType, paramsCasing, pathParamsType }) {
869
- if (!ast.isHttpOperationNode(node)) return "";
870
- const { defaultContentType: contentType, isMultipleContentTypes, hasFormData } = getContentTypeInfo(node);
871
- const isFormData = !isMultipleContentTypes && contentType === "multipart/form-data";
872
- const { header: headerParams } = getOperationParameters(node);
873
- const headerParamsName = headerParams.length > 0 ? tsResolver.resolveHeaderParamsName(node, headerParams[0]) : null;
874
- const headers = isMultipleContentTypes ? headerParamsName ? ["...headers"] : [] : buildHeaders(contentType, !!headerParamsName);
875
- const generics = buildGenerics(node, tsResolver, {
876
- dataReturnType,
877
- zodResolver,
878
- parser
879
- });
880
- const paramsNode = buildClientParamsNode({
881
- paramsType,
882
- paramsCasing,
883
- pathParamsType,
884
- node,
885
- tsResolver,
886
- isConfigurable: true
887
- });
888
- const paramsSignature = declarationPrinter$1.print(paramsNode) ?? "";
889
- const { query: queryParams } = getOperationParameters(node);
890
- const zodQueryParamsName = zodResolver && resolveQueryParamsParser(parser) === "zod" && queryParams.length > 0 ? zodResolver.resolveQueryParamsName?.(node, queryParams[0]) : null;
891
- const clientParams = buildClassClientParams({
892
- node,
893
- url: Url$1.toTemplateString(node.path, { casing: paramsCasing }),
894
- baseURL,
895
- tsResolver,
896
- isFormData,
897
- isMultipleContentTypes,
898
- hasFormData,
899
- headers,
900
- zodQueryParamsName
901
- });
902
- const jsdoc = buildJSDoc(buildOperationComments(node, {
903
- link: "urlPath",
904
- linkPosition: "beforeDeprecated",
905
- splitLines: true
906
- }));
907
- const requestDataLine = buildRequestDataLine({
908
- parser,
909
- node,
910
- zodResolver
911
- });
912
- const queryParamsLine = buildQueryParamsLine({
913
- parser,
914
- node,
915
- zodResolver
916
- });
917
- const formDataLine = buildFormDataLine(isFormData || isMultipleContentTypes && hasFormData, !!node.requestBody?.content?.[0]?.schema);
918
- const returnStatement = buildReturnStatement({
919
- dataReturnType,
920
- parser,
921
- node,
922
- zodResolver,
923
- tsResolver
924
- });
925
- return `${jsdoc}async ${name}(${paramsSignature}) {\n${[
926
- `const { client: request = client, ${isMultipleContentTypes ? `contentType = ${stringify(contentType)}, ` : ""}...requestConfig } = mergeConfig(this.#config, config)`,
927
- "",
928
- requestDataLine,
929
- queryParamsLine,
930
- formDataLine,
931
- `const res = await request<${generics.join(", ")}>(${clientParams.toCall()})`,
932
- returnStatement
933
- ].filter(Boolean).map((line) => ` ${line}`).join("\n")}\n }`;
934
- }
935
- __name(generateMethod$1, "generateMethod");
936
- function ClassClient({ name, isExportable = true, isIndexable = true, operations, baseURL, dataReturnType, parser, paramsType, paramsCasing, pathParamsType, children }) {
937
- const classCode = `export class ${name} {
938
- #config: Partial<RequestConfig> & { client?: Client }
939
-
940
- constructor(config: Partial<RequestConfig> & { client?: Client } = {}) {
941
- this.#config = config
942
- }
943
-
944
- ${operations.map(({ node, name: methodName, tsResolver, zodResolver }) => generateMethod$1({
945
- node,
946
- name: methodName,
947
- tsResolver,
948
- zodResolver,
949
- baseURL,
950
- dataReturnType,
951
- parser,
952
- paramsType,
953
- paramsCasing,
954
- pathParamsType
955
- })).join("\n\n")}
956
- }`;
957
- return /* @__PURE__ */ jsxs(File.Source, {
958
- name,
959
- isExportable,
960
- isIndexable,
961
- children: [classCode, children]
962
790
  });
963
791
  }
964
792
  //#endregion
965
- //#region src/components/WrapperClient.tsx
966
- function WrapperClient({ name, controllers, isExportable = true, isIndexable = true }) {
967
- const classCode = `export class ${name} {
968
- ${controllers.map(({ className, propertyName }) => ` readonly ${propertyName}: ${className}`).join("\n")}
969
-
970
- constructor(config: Partial<RequestConfig> & { client?: Client } = {}) {
971
- ${controllers.map(({ className, propertyName }) => ` this.${propertyName} = new ${className}(config)`).join("\n")}
972
- }
973
- }`;
974
- return /* @__PURE__ */ jsx(File.Source, {
975
- name,
976
- isExportable,
977
- isIndexable,
978
- children: classCode
979
- });
980
- }
981
- //#endregion
982
- //#region src/generators/classClientGenerator.tsx
983
- function resolveTypeImportNames$1(node, tsResolver) {
984
- return resolveOperationTypeNames(node, tsResolver, { order: "body-response-first" });
985
- }
986
- __name(resolveTypeImportNames$1, "resolveTypeImportNames");
987
- function resolveZodImportNames$1(node, zodResolver, parser) {
988
- const { query: queryParams } = getOperationParameters(node);
989
- return [
990
- resolveResponseParser(parser) === "zod" ? zodResolver.resolveResponseName?.(node) : null,
991
- resolveRequestParser(parser) === "zod" && node.requestBody?.content?.[0]?.schema ? zodResolver.resolveDataName?.(node) : null,
992
- resolveQueryParamsParser(parser) === "zod" && queryParams.length > 0 ? zodResolver.resolveQueryParamsName?.(node, queryParams[0]) : null
993
- ].filter((n) => Boolean(n));
994
- }
995
- __name(resolveZodImportNames$1, "resolveZodImportNames");
793
+ //#region ../../internals/client/src/resolveOperationTypes.ts
996
794
  /**
997
- * Built-in `operations` generator for `@kubb/plugin-client` when
998
- * `clientType: 'class'`. Emits one class per tag, with one instance method
999
- * per operation and a shared constructor for request configuration.
795
+ * Warning a client generator reports when neither plugin can supply the operation types, so the
796
+ * pipeline says why nothing was generated instead of silently emitting no files.
1000
797
  */
1001
- const classClientGenerator = defineGenerator({
1002
- name: "classClient",
1003
- renderer: jsxRenderer,
1004
- operations(nodes, ctx) {
1005
- const { config, driver, resolver, root } = ctx;
1006
- const { output, group, dataReturnType, paramsCasing, paramsType, pathParamsType, parser, importPath, sdk } = ctx.options;
1007
- const baseURL = ctx.options.baseURL ?? ctx.meta.baseURL;
1008
- const pluginTs = driver.getPlugin(pluginTsName);
1009
- if (!pluginTs) return null;
1010
- const tsResolver = driver.getResolver(pluginTsName);
1011
- const tsPluginOptions = pluginTs.options;
1012
- const pluginZod = isParserEnabled(parser) ? driver.getPlugin(pluginZodName) : null;
1013
- const zodResolver = pluginZod ? driver.getResolver(pluginZodName) : null;
1014
- function buildOperationData(node) {
1015
- const typeFile = tsResolver.resolveFile(operationFileEntry(node, node.operationId), {
1016
- root,
1017
- output: tsPluginOptions?.output ?? output,
1018
- group: tsPluginOptions?.group
1019
- });
1020
- const zodFile = zodResolver && pluginZod?.options ? zodResolver.resolveFile(operationFileEntry(node, node.operationId), {
1021
- root,
1022
- output: pluginZod.options?.output ?? output,
1023
- group: pluginZod.options?.group ?? void 0
1024
- }) : null;
1025
- return {
1026
- node,
1027
- name: resolver.resolveName(node.operationId),
1028
- tsResolver,
1029
- zodResolver,
1030
- typeFile,
1031
- zodFile
1032
- };
1033
- }
1034
- const controllers = nodes.reduce((acc, operationNode) => {
1035
- if (!ast.isHttpOperationNode(operationNode)) return acc;
1036
- const tag = operationNode.tags[0];
1037
- const groupName = tag ? group?.name?.({ group: camelCase(tag) }) ?? resolver.resolveGroupName(tag) : resolver.resolveClassName("Client");
1038
- if (!tag && !group) {
1039
- const name = resolver.resolveClassName("ApiClient");
1040
- const file = resolver.resolveFile({
1041
- name,
1042
- extname: ".ts"
1043
- }, {
1044
- root,
1045
- output,
1046
- group: group ?? void 0
1047
- });
1048
- const operationData = buildOperationData(operationNode);
1049
- const previous = acc.find((item) => item.file.path === file.path);
1050
- if (previous) previous.operations.push(operationData);
1051
- else acc.push({
1052
- name,
1053
- propertyName: resolver.resolveClientPropertyName(name),
1054
- file,
1055
- operations: [operationData]
1056
- });
1057
- return acc;
1058
- }
1059
- if (tag) {
1060
- const name = groupName;
1061
- const file = resolver.resolveFile({
1062
- name,
1063
- extname: ".ts",
1064
- tag
1065
- }, {
1066
- root,
1067
- output,
1068
- group: group ?? void 0
1069
- });
1070
- const operationData = buildOperationData(operationNode);
1071
- const previous = acc.find((item) => item.file.path === file.path);
1072
- if (previous) previous.operations.push(operationData);
1073
- else acc.push({
1074
- name,
1075
- propertyName: resolver.resolveClientPropertyName(name),
1076
- file,
1077
- operations: [operationData]
1078
- });
1079
- }
1080
- return acc;
1081
- }, []);
1082
- function collectTypeImports(ops) {
1083
- const typeImportsByFile = /* @__PURE__ */ new Map();
1084
- const typeFilesByPath = /* @__PURE__ */ new Map();
1085
- ops.forEach((op) => {
1086
- const names = resolveTypeImportNames$1(op.node, tsResolver);
1087
- if (!typeImportsByFile.has(op.typeFile.path)) typeImportsByFile.set(op.typeFile.path, /* @__PURE__ */ new Set());
1088
- const imports = typeImportsByFile.get(op.typeFile.path);
1089
- names.forEach((n) => {
1090
- imports.add(n);
1091
- });
1092
- typeFilesByPath.set(op.typeFile.path, op.typeFile);
1093
- });
1094
- return {
1095
- typeImportsByFile,
1096
- typeFilesByPath
1097
- };
1098
- }
1099
- function collectZodImports(ops) {
1100
- const zodImportsByFile = /* @__PURE__ */ new Map();
1101
- const zodFilesByPath = /* @__PURE__ */ new Map();
1102
- ops.forEach((op) => {
1103
- if (!op.zodFile || !zodResolver) return;
1104
- const names = resolveZodImportNames$1(op.node, zodResolver, parser);
1105
- if (!zodImportsByFile.has(op.zodFile.path)) zodImportsByFile.set(op.zodFile.path, /* @__PURE__ */ new Set());
1106
- const imports = zodImportsByFile.get(op.zodFile.path);
1107
- names.forEach((n) => {
1108
- imports.add(n);
1109
- });
1110
- zodFilesByPath.set(op.zodFile.path, op.zodFile);
1111
- });
1112
- return {
1113
- zodImportsByFile,
1114
- zodFilesByPath
1115
- };
1116
- }
1117
- const files = controllers.map(({ name, file, operations: ops }) => {
1118
- const { typeImportsByFile, typeFilesByPath } = collectTypeImports(ops);
1119
- const { zodImportsByFile, zodFilesByPath } = isParserEnabled(parser) ? collectZodImports(ops) : {
1120
- zodImportsByFile: /* @__PURE__ */ new Map(),
1121
- zodFilesByPath: /* @__PURE__ */ new Map()
1122
- };
1123
- const hasFormData = ops.some((op) => op.node.requestBody?.content?.some((e) => e.contentType === "multipart/form-data") ?? false);
1124
- return /* @__PURE__ */ jsxs(File, {
1125
- baseName: file.baseName,
1126
- path: file.path,
1127
- meta: file.meta,
1128
- banner: resolver.resolveBanner(ctx.meta, {
1129
- output,
1130
- config,
1131
- file: {
1132
- path: file.path,
1133
- baseName: file.baseName
1134
- }
1135
- }),
1136
- footer: resolver.resolveFooter(ctx.meta, {
1137
- output,
1138
- config,
1139
- file: {
1140
- path: file.path,
1141
- baseName: file.baseName
1142
- }
1143
- }),
1144
- children: [
1145
- importPath ? /* @__PURE__ */ jsxs(Fragment, { children: [
1146
- /* @__PURE__ */ jsx(File.Import, {
1147
- name: "client",
1148
- path: importPath
1149
- }),
1150
- /* @__PURE__ */ jsx(File.Import, {
1151
- name: ["mergeConfig"],
1152
- path: importPath
1153
- }),
1154
- /* @__PURE__ */ jsx(File.Import, {
1155
- name: [
1156
- "Client",
1157
- "RequestConfig",
1158
- "ResponseErrorConfig"
1159
- ],
1160
- path: importPath,
1161
- isTypeOnly: true
1162
- })
1163
- ] }) : /* @__PURE__ */ jsxs(Fragment, { children: [
1164
- /* @__PURE__ */ jsx(File.Import, {
1165
- name: ["client"],
1166
- root: file.path,
1167
- path: path.resolve(root, ".kubb/client.ts")
1168
- }),
1169
- /* @__PURE__ */ jsx(File.Import, {
1170
- name: ["mergeConfig"],
1171
- root: file.path,
1172
- path: path.resolve(root, ".kubb/client.ts")
1173
- }),
1174
- /* @__PURE__ */ jsx(File.Import, {
1175
- name: [
1176
- "Client",
1177
- "RequestConfig",
1178
- "ResponseErrorConfig"
1179
- ],
1180
- root: file.path,
1181
- path: path.resolve(root, ".kubb/client.ts"),
1182
- isTypeOnly: true
1183
- })
1184
- ] }),
1185
- hasFormData && /* @__PURE__ */ jsx(File.Import, {
1186
- name: ["buildFormData"],
1187
- root: file.path,
1188
- path: path.resolve(root, ".kubb/config.ts")
1189
- }),
1190
- parser === "zod" && zodResolver && ops.some((op) => op.node.requestBody?.content?.[0]?.schema != null) && /* @__PURE__ */ jsx(File.Import, {
1191
- name: ["z"],
1192
- path: "zod",
1193
- isTypeOnly: true
1194
- }),
1195
- Array.from(typeImportsByFile.entries()).map(([filePath, importSet]) => {
1196
- const typeFile = typeFilesByPath.get(filePath);
1197
- if (!typeFile) return null;
1198
- const importNames = Array.from(importSet).filter(Boolean);
1199
- if (importNames.length === 0) return null;
1200
- return /* @__PURE__ */ jsx(File.Import, {
1201
- name: importNames,
1202
- root: file.path,
1203
- path: typeFile.path,
1204
- isTypeOnly: true
1205
- }, filePath);
1206
- }),
1207
- isParserEnabled(parser) && Array.from(zodImportsByFile.entries()).map(([filePath, importSet]) => {
1208
- const zodFile = zodFilesByPath.get(filePath);
1209
- if (!zodFile) return null;
1210
- const importNames = Array.from(importSet).filter(Boolean);
1211
- if (importNames.length === 0) return null;
1212
- return /* @__PURE__ */ jsx(File.Import, {
1213
- name: importNames,
1214
- root: file.path,
1215
- path: zodFile.path
1216
- }, filePath);
1217
- }),
1218
- /* @__PURE__ */ jsx(ClassClient, {
1219
- name,
1220
- operations: ops,
1221
- baseURL,
1222
- dataReturnType,
1223
- pathParamsType,
1224
- paramsCasing,
1225
- paramsType,
1226
- parser
1227
- })
1228
- ]
1229
- }, file.path);
1230
- });
1231
- if (sdk) {
1232
- const sdkFile = resolver.resolveFile({
1233
- name: sdk.className,
1234
- extname: ".ts"
1235
- }, {
1236
- root,
1237
- output,
1238
- group: group ?? void 0
1239
- });
1240
- files.push(/* @__PURE__ */ jsxs(File, {
1241
- baseName: sdkFile.baseName,
1242
- path: sdkFile.path,
1243
- meta: sdkFile.meta,
1244
- banner: resolver.resolveBanner(ctx.meta, {
1245
- output,
1246
- config,
1247
- file: {
1248
- path: sdkFile.path,
1249
- baseName: sdkFile.baseName
1250
- }
1251
- }),
1252
- footer: resolver.resolveFooter(ctx.meta, {
1253
- output,
1254
- config,
1255
- file: {
1256
- path: sdkFile.path,
1257
- baseName: sdkFile.baseName
1258
- }
1259
- }),
1260
- children: [
1261
- importPath ? /* @__PURE__ */ jsx(File.Import, {
1262
- name: ["Client", "RequestConfig"],
1263
- path: importPath,
1264
- isTypeOnly: true
1265
- }) : /* @__PURE__ */ jsx(File.Import, {
1266
- name: ["Client", "RequestConfig"],
1267
- root: sdkFile.path,
1268
- path: path.resolve(root, ".kubb/client.ts"),
1269
- isTypeOnly: true
1270
- }),
1271
- controllers.map(({ name, file }) => /* @__PURE__ */ jsx(File.Import, {
1272
- name: [name],
1273
- root: sdkFile.path,
1274
- path: file.path
1275
- }, name)),
1276
- /* @__PURE__ */ jsx(WrapperClient, {
1277
- name: sdk.className,
1278
- controllers: controllers.map(({ name, propertyName }) => ({
1279
- className: name,
1280
- propertyName
1281
- }))
1282
- })
1283
- ]
1284
- }, sdkFile.path));
1285
- }
1286
- return /* @__PURE__ */ jsx(Fragment, { children: files });
1287
- }
1288
- });
1289
- //#endregion
1290
- //#region src/generators/clientGenerator.tsx
798
+ const MISSING_OPERATION_TYPES_WARNING = "Skipped: no operation types available. Add `pluginTs()`, or `pluginZod({ inferred: true })` to type the client from its zod schemas.";
1291
799
  /**
1292
- * Built-in operation generator for `@kubb/plugin-client`. Emits one async
1293
- * function per OpenAPI operation, plus the matching URL helper. Used when
1294
- * `clientType: 'function'` (the default).
800
+ * Picks the plugin a generated client takes its `Options` and `Responses` types from.
801
+ *
802
+ * `plugin-ts` wins whenever it is in the pipeline, so existing configs keep the types they generate
803
+ * today. Without it, `pluginZod({ inferred: true })` takes over: its inferred options and responses
804
+ * types are built from the same shared schemas as the `plugin-ts` ones, so a client typed off
805
+ * `z.infer` carries what the codecs decode to (a `Temporal` instance, a `Date`) rather than the raw
806
+ * wire type. Returns `null` when neither is available, leaving the caller nothing to type against.
807
+ *
808
+ * @example
809
+ * ```ts
810
+ * const types = resolveOperationTypes(ctx.driver)
811
+ * if (!types) return null
812
+ * types.response.options(node) // 'GetPetByIdOptions', or 'GetPetByIdOptionsSchemaType' from zod
813
+ * ```
1295
814
  */
1296
- const clientGenerator = defineGenerator({
1297
- name: "client",
1298
- renderer: jsxRenderer,
1299
- operation(node, ctx) {
1300
- if (!ast.isHttpOperationNode(node)) return null;
1301
- const { config, driver, resolver, root } = ctx;
1302
- const { output, urlType, dataReturnType, paramsCasing, paramsType, pathParamsType, parser, importPath, group } = ctx.options;
1303
- const baseURL = ctx.options.baseURL ?? ctx.meta.baseURL;
1304
- const pluginTs = driver.getPlugin(pluginTsName);
1305
- if (!pluginTs) return null;
1306
- const tsResolver = driver.getResolver(pluginTsName);
1307
- const pluginZod = isParserEnabled(parser) ? driver.getPlugin(pluginZodName) : null;
1308
- const zodResolver = pluginZod ? driver.getResolver(pluginZodName) : null;
1309
- const importedTypeNames = resolveOperationTypeNames(node, tsResolver, { paramsCasing });
1310
- const { query: queryParams } = getOperationParameters(node);
1311
- const importedZodNames = zodResolver ? [
1312
- resolveResponseParser(parser) === "zod" ? zodResolver.resolveResponseName?.(node) : null,
1313
- resolveRequestParser(parser) === "zod" && node.requestBody?.content?.[0]?.schema ? zodResolver.resolveDataName?.(node) : null,
1314
- resolveQueryParamsParser(parser) === "zod" && queryParams.length > 0 ? zodResolver.resolveQueryParamsName?.(node, queryParams[0]) : null
1315
- ].filter((name) => Boolean(name)) : [];
1316
- const zodRequestName = zodResolver && parser === "zod" && node.requestBody?.content?.[0]?.schema ? zodResolver.resolveDataName?.(node) ?? null : null;
1317
- const meta = {
1318
- name: resolver.resolveName(node.operationId),
1319
- urlName: resolver.resolveUrlName(node),
1320
- file: resolver.resolveFile(operationFileEntry(node, node.operationId), {
1321
- root,
1322
- output,
1323
- group: group ?? void 0
1324
- }),
1325
- fileTs: tsResolver.resolveFile(operationFileEntry(node, node.operationId), {
1326
- root,
1327
- output: pluginTs.options?.output ?? output,
1328
- group: pluginTs.options?.group ?? void 0
1329
- }),
1330
- fileZod: zodResolver && pluginZod?.options ? zodResolver.resolveFile(operationFileEntry(node, node.operationId), {
1331
- root,
1332
- output: pluginZod.options.output ?? output,
1333
- group: pluginZod.options?.group ?? void 0
1334
- }) : null
815
+ function resolveOperationTypes(driver) {
816
+ const pluginTs = driver.getPlugin(pluginTsName);
817
+ if (pluginTs) {
818
+ const resolver = driver.getResolver(pluginTsName);
819
+ return {
820
+ response: resolver.response,
821
+ resolver,
822
+ output: pluginTs.options?.output,
823
+ group: pluginTs.options?.group
1335
824
  };
1336
- const hasFormData = node.requestBody?.content?.some((e) => e.contentType === "multipart/form-data") ?? false;
1337
- return /* @__PURE__ */ jsxs(File, {
1338
- baseName: meta.file.baseName,
1339
- path: meta.file.path,
1340
- meta: meta.file.meta,
1341
- banner: resolver.resolveBanner(ctx.meta, {
1342
- output,
1343
- config,
1344
- file: {
1345
- path: meta.file.path,
1346
- baseName: meta.file.baseName
1347
- }
1348
- }),
1349
- footer: resolver.resolveFooter(ctx.meta, {
1350
- output,
1351
- config,
1352
- file: {
1353
- path: meta.file.path,
1354
- baseName: meta.file.baseName
1355
- }
1356
- }),
1357
- children: [
1358
- importPath ? /* @__PURE__ */ jsxs(Fragment, { children: [/* @__PURE__ */ jsx(File.Import, {
1359
- name: "client",
1360
- path: importPath
1361
- }), /* @__PURE__ */ jsx(File.Import, {
1362
- name: [
1363
- "Client",
1364
- "RequestConfig",
1365
- "ResponseErrorConfig"
1366
- ],
1367
- path: importPath,
1368
- isTypeOnly: true
1369
- })] }) : /* @__PURE__ */ jsxs(Fragment, { children: [/* @__PURE__ */ jsx(File.Import, {
1370
- name: ["client"],
1371
- root: meta.file.path,
1372
- path: path.resolve(root, ".kubb/client.ts")
1373
- }), /* @__PURE__ */ jsx(File.Import, {
1374
- name: [
1375
- "Client",
1376
- "RequestConfig",
1377
- "ResponseErrorConfig"
1378
- ],
1379
- root: meta.file.path,
1380
- path: path.resolve(root, ".kubb/client.ts"),
1381
- isTypeOnly: true
1382
- })] }),
1383
- hasFormData && /* @__PURE__ */ jsx(File.Import, {
1384
- name: ["buildFormData"],
1385
- root: meta.file.path,
1386
- path: path.resolve(root, ".kubb/config.ts")
1387
- }),
1388
- zodRequestName && /* @__PURE__ */ jsx(File.Import, {
1389
- name: ["z"],
1390
- path: "zod",
1391
- isTypeOnly: true
1392
- }),
1393
- meta.fileZod && importedZodNames.length > 0 && /* @__PURE__ */ jsx(File.Import, {
1394
- name: importedZodNames,
1395
- root: meta.file.path,
1396
- path: meta.fileZod.path
1397
- }),
1398
- meta.fileTs && importedTypeNames.length > 0 && /* @__PURE__ */ jsx(File.Import, {
1399
- name: Array.from(new Set(importedTypeNames)),
1400
- root: meta.file.path,
1401
- path: meta.fileTs.path,
1402
- isTypeOnly: true
1403
- }),
1404
- /* @__PURE__ */ jsx(Url, {
1405
- name: meta.urlName,
1406
- baseURL,
1407
- pathParamsType,
1408
- paramsCasing,
1409
- paramsType,
1410
- node,
1411
- tsResolver,
1412
- isIndexable: urlType === "export",
1413
- isExportable: urlType === "export"
1414
- }),
1415
- /* @__PURE__ */ jsx(Client, {
1416
- name: meta.name,
1417
- urlName: meta.urlName,
1418
- baseURL,
1419
- dataReturnType,
1420
- pathParamsType,
1421
- paramsCasing,
1422
- paramsType,
1423
- node,
1424
- tsResolver,
1425
- zodResolver,
1426
- parser
1427
- })
1428
- ]
1429
- });
1430
825
  }
1431
- });
826
+ const pluginZod = driver.getPlugin(pluginZodName);
827
+ if (!pluginZod?.options?.inferred) return null;
828
+ const resolver = driver.getResolver(pluginZodName);
829
+ return {
830
+ response: {
831
+ options: resolver.response.options,
832
+ responses: (node) => resolver.schema.type(resolver.response.responses(node))
833
+ },
834
+ resolver,
835
+ output: pluginZod.options.output,
836
+ group: pluginZod.options.group
837
+ };
838
+ }
1432
839
  //#endregion
1433
- //#region src/generators/groupedClientGenerator.tsx
840
+ //#region ../../internals/client/src/generators/clientGenerator.tsx
1434
841
  /**
1435
- * Emits one aggregate file per tag/group when `group` is configured. Each
1436
- * file re-exports every client function for that group, so callers can
1437
- * `import { petController } from './gen/clients'` instead of importing
1438
- * each operation individually.
842
+ * Builds the built-in per-operation generator shared by the client plugins (`@kubb/plugin-fetch`,
843
+ * `@kubb/plugin-axios`). Emits one async function per OpenAPI operation using the shared
844
+ * `Operation` component: a grouped `<Name>Request` type and a function that forwards a single
845
+ * `options` object to the bundled `client` and returns the `Unwrappable<RequestResult>`. Only the
846
+ * generator `name` differs between plugins. Every other resolution, import, and rendering step is
847
+ * identical.
1439
848
  */
1440
- const groupedClientGenerator = defineGenerator({
1441
- name: "groupedClient",
1442
- renderer: jsxRenderer,
1443
- operations(nodes, ctx) {
1444
- const { config, resolver, root } = ctx;
1445
- const { output, group } = ctx.options;
1446
- return /* @__PURE__ */ jsx(Fragment, { children: nodes.reduce((acc, operationNode) => {
1447
- if (group?.type === "tag") {
1448
- const tag = operationNode.tags[0];
1449
- const name = tag ? group?.name?.({ group: camelCase(tag) }) : null;
1450
- if (!tag || !name) return acc;
1451
- const file = resolver.resolveFile({
1452
- name,
1453
- extname: ".ts",
1454
- tag
1455
- }, {
1456
- root,
1457
- output,
1458
- group: group ?? void 0
1459
- });
1460
- const clientFile = resolver.resolveFile({
1461
- name: operationNode.operationId,
1462
- extname: ".ts",
1463
- tag: operationNode.tags[0] ?? "default",
1464
- path: operationNode.path
1465
- }, {
849
+ function createClientGenerator(name, resolveClientPath) {
850
+ return defineGenerator({
851
+ name,
852
+ renderer: jsxRenderer,
853
+ operation(node, ctx) {
854
+ if (!ast.isHttpOperationNode(node)) return null;
855
+ const { config, driver, resolver, root } = ctx;
856
+ const { output, validator, returnType, throwOnErrorDefault, group } = ctx.options;
857
+ const types = resolveOperationTypes(driver);
858
+ if (!types) {
859
+ ctx.warn(MISSING_OPERATION_TYPES_WARNING);
860
+ return null;
861
+ }
862
+ const pluginZod = resolveResponseValidator(validator) === "zod" || resolveRequestValidator(validator) === "zod" ? driver.getPlugin(pluginZodName) : null;
863
+ const zodResolver = pluginZod ? driver.getResolver(pluginZodName) : null;
864
+ const hasRequestBody = Boolean(node.requestBody?.content?.[0]?.schema);
865
+ const importedTypeNames = [types.response.options(node), types.response.responses(node)];
866
+ const importedZodNames = zodResolver ? [
867
+ resolveResponseValidator(validator) === "zod" ? zodResolver.response.response?.(node) : null,
868
+ resolveResponseValidator(validator) === "zod" ? buildZodErrorParse(node, zodResolver)?.expression ?? null : null,
869
+ resolveRequestValidator(validator) === "zod" && hasRequestBody ? zodResolver.response.body?.(node) : null
870
+ ].filter((name) => Boolean(name)) : [];
871
+ const meta = {
872
+ name: resolver.name(node.operationId),
873
+ file: resolver.file({
874
+ ...operationFileEntry(node, node.operationId),
1466
875
  root,
1467
876
  output,
1468
877
  group: group ?? void 0
1469
- });
1470
- const client = {
1471
- name: resolver.resolveName(operationNode.operationId),
1472
- file: clientFile
1473
- };
1474
- const previous = acc.find((item) => item.file.path === file.path);
1475
- if (previous) previous.clients.push(client);
1476
- else acc.push({
1477
- name,
1478
- file,
1479
- clients: [client]
1480
- });
1481
- }
1482
- return acc;
1483
- }, []).map(({ name, file, clients }) => {
1484
- return /* @__PURE__ */ jsxs(File, {
1485
- baseName: file.baseName,
1486
- path: file.path,
1487
- meta: file.meta,
1488
- banner: resolver.resolveBanner(ctx.meta, {
1489
- output,
1490
- config,
1491
- file: {
1492
- path: file.path,
1493
- baseName: file.baseName,
1494
- isAggregation: true
1495
- }
1496
878
  }),
1497
- footer: resolver.resolveFooter(ctx.meta, {
1498
- output,
1499
- config,
1500
- file: {
1501
- path: file.path,
1502
- baseName: file.baseName,
1503
- isAggregation: true
1504
- }
1505
- }),
1506
- children: [clients.map((client) => /* @__PURE__ */ jsx(File.Import, {
1507
- name: [client.name],
1508
- root: file.path,
1509
- path: client.file.path
1510
- }, client.name)), /* @__PURE__ */ jsx(File.Source, {
1511
- name,
1512
- isExportable: true,
1513
- isIndexable: true,
1514
- children: /* @__PURE__ */ jsx(Function, {
1515
- export: true,
1516
- name,
1517
- children: `return { ${clients.map((client) => client.name).join(", ")} }`
1518
- })
1519
- })]
1520
- }, file.path);
1521
- }) });
1522
- }
1523
- });
1524
- //#endregion
1525
- //#region src/generators/operationsGenerator.ts
1526
- /**
1527
- * Generates an `operations.ts` file that re-exports every operation grouped
1528
- * by HTTP method. Enabled when `pluginClient({ operations: true })`. Useful
1529
- * for building meta-tooling on top of the generated client (route
1530
- * registries, API explorers).
1531
- */
1532
- const operationsGenerator = defineGenerator({
1533
- name: "client",
1534
- operations(nodes, ctx) {
1535
- const { config, resolver, root } = ctx;
1536
- const { output, group } = ctx.options;
1537
- const name = "operations";
1538
- const file = resolver.resolveFile({
1539
- name,
1540
- extname: ".ts"
1541
- }, {
1542
- root,
1543
- output,
1544
- group: group ?? void 0
1545
- });
1546
- const operationsObject = {};
1547
- for (const node of nodes) {
1548
- if (!ast.isHttpOperationNode(node)) continue;
1549
- operationsObject[node.operationId] = {
1550
- path: Url$1.toPath(node.path),
1551
- method: node.method.toLowerCase()
1552
- };
1553
- }
1554
- return [ast.factory.createFile({
1555
- baseName: file.baseName,
1556
- path: file.path,
1557
- meta: file.meta,
1558
- banner: resolver.resolveBanner(ctx.meta, {
1559
- output,
1560
- config,
1561
- file: {
1562
- path: file.path,
1563
- baseName: file.baseName
1564
- }
1565
- }),
1566
- footer: resolver.resolveFooter(ctx.meta, {
1567
- output,
1568
- config,
1569
- file: {
1570
- path: file.path,
1571
- baseName: file.baseName
1572
- }
1573
- }),
1574
- sources: [ast.factory.createSource({
1575
- name,
1576
- isExportable: true,
1577
- isIndexable: true,
1578
- nodes: [ast.factory.createConst({
1579
- name,
1580
- export: true,
1581
- nodes: [ast.factory.createText(JSON.stringify(operationsObject, void 0, 2))]
1582
- })]
1583
- })]
1584
- })];
1585
- }
1586
- });
1587
- //#endregion
1588
- //#region src/components/StaticClassClient.tsx
1589
- const declarationPrinter = functionPrinter({ mode: "declaration" });
1590
- function generateMethod({ node, name, tsResolver, zodResolver, baseURL, dataReturnType, parser, paramsType, paramsCasing, pathParamsType }) {
1591
- if (!ast.isHttpOperationNode(node)) return "";
1592
- const { defaultContentType: contentType, isMultipleContentTypes, hasFormData } = getContentTypeInfo(node);
1593
- const isFormData = !isMultipleContentTypes && contentType === "multipart/form-data";
1594
- const { header: headerParams } = getOperationParameters(node);
1595
- const headerParamsName = headerParams.length > 0 ? tsResolver.resolveHeaderParamsName(node, headerParams[0]) : null;
1596
- const headers = isMultipleContentTypes ? headerParamsName ? ["...headers"] : [] : buildHeaders(contentType, !!headerParamsName);
1597
- const generics = buildGenerics(node, tsResolver, {
1598
- dataReturnType,
1599
- zodResolver,
1600
- parser
1601
- });
1602
- const paramsNode = buildClientParamsNode({
1603
- paramsType,
1604
- paramsCasing,
1605
- pathParamsType,
1606
- node,
1607
- tsResolver,
1608
- isConfigurable: true
1609
- });
1610
- const paramsSignature = declarationPrinter.print(paramsNode) ?? "";
1611
- const { query: queryParams } = getOperationParameters(node);
1612
- const zodQueryParamsName = zodResolver && resolveQueryParamsParser(parser) === "zod" && queryParams.length > 0 ? zodResolver.resolveQueryParamsName?.(node, queryParams[0]) : null;
1613
- const clientParams = buildClassClientParams({
1614
- node,
1615
- url: Url$1.toTemplateString(node.path, { casing: paramsCasing }),
1616
- baseURL,
1617
- tsResolver,
1618
- isFormData,
1619
- isMultipleContentTypes,
1620
- hasFormData,
1621
- headers,
1622
- zodQueryParamsName
1623
- });
1624
- const jsdoc = buildJSDoc(buildOperationComments(node, {
1625
- link: "urlPath",
1626
- linkPosition: "beforeDeprecated",
1627
- splitLines: true
1628
- }));
1629
- const requestDataLine = buildRequestDataLine({
1630
- parser,
1631
- node,
1632
- zodResolver
1633
- });
1634
- const queryParamsLine = buildQueryParamsLine({
1635
- parser,
1636
- node,
1637
- zodResolver
1638
- });
1639
- const formDataLine = buildFormDataLine(isFormData || isMultipleContentTypes && hasFormData, !!node.requestBody?.content?.[0]?.schema);
1640
- const returnStatement = buildReturnStatement({
1641
- dataReturnType,
1642
- parser,
1643
- node,
1644
- zodResolver,
1645
- tsResolver
1646
- });
1647
- return `${jsdoc} static async ${name}(${paramsSignature}) {\n${[
1648
- `const { client: request = client, ${isMultipleContentTypes ? `contentType = ${stringify(contentType)}, ` : ""}...requestConfig } = mergeConfig(this.#config, config)`,
1649
- "",
1650
- requestDataLine,
1651
- queryParamsLine,
1652
- formDataLine,
1653
- `const res = await request<${generics.join(", ")}>(${clientParams.toCall()})`,
1654
- returnStatement
1655
- ].filter(Boolean).map((line) => ` ${line}`).join("\n")}\n }`;
1656
- }
1657
- function StaticClassClient({ name, isExportable = true, isIndexable = true, operations, baseURL, dataReturnType, parser, paramsType, paramsCasing, pathParamsType, children }) {
1658
- const classCode = `export class ${name} {\n static #config: Partial<RequestConfig> & { client?: Client } = {}\n\n${operations.map(({ node, name: methodName, tsResolver, zodResolver }) => generateMethod({
1659
- node,
1660
- name: methodName,
1661
- tsResolver,
1662
- zodResolver,
1663
- baseURL,
1664
- dataReturnType,
1665
- parser,
1666
- paramsType,
1667
- paramsCasing,
1668
- pathParamsType
1669
- })).join("\n\n")}\n}`;
1670
- return /* @__PURE__ */ jsxs(File.Source, {
1671
- name,
1672
- isExportable,
1673
- isIndexable,
1674
- children: [classCode, children]
1675
- });
1676
- }
1677
- //#endregion
1678
- //#region src/generators/staticClassClientGenerator.tsx
1679
- function resolveTypeImportNames(node, tsResolver) {
1680
- return resolveOperationTypeNames(node, tsResolver, { order: "body-response-first" });
1681
- }
1682
- function resolveZodImportNames(node, zodResolver, parser) {
1683
- const { query: queryParams } = getOperationParameters(node);
1684
- return [
1685
- resolveResponseParser(parser) === "zod" ? zodResolver.resolveResponseName?.(node) : null,
1686
- resolveRequestParser(parser) === "zod" && node.requestBody?.content?.[0]?.schema ? zodResolver.resolveDataName?.(node) : null,
1687
- resolveQueryParamsParser(parser) === "zod" && queryParams.length > 0 ? zodResolver.resolveQueryParamsName?.(node, queryParams[0]) : null
1688
- ].filter((n) => Boolean(n));
1689
- }
1690
- /**
1691
- * Built-in `operations` generator for `@kubb/plugin-client` when
1692
- * `clientType: 'staticClass'`. Emits one class per tag, with a static method
1693
- * per operation so callers can use `Pet.getPetById(...)` without
1694
- * instantiating the class.
1695
- */
1696
- const staticClassClientGenerator = defineGenerator({
1697
- name: "staticClassClient",
1698
- renderer: jsxRenderer,
1699
- operations(nodes, ctx) {
1700
- const { config, driver, resolver, root } = ctx;
1701
- const { output, group, dataReturnType, paramsCasing, paramsType, pathParamsType, parser, importPath } = ctx.options;
1702
- const baseURL = ctx.options.baseURL ?? ctx.meta.baseURL;
1703
- const pluginTs = driver.getPlugin(pluginTsName);
1704
- if (!pluginTs) return null;
1705
- const tsResolver = driver.getResolver(pluginTsName);
1706
- const tsPluginOptions = pluginTs.options;
1707
- const pluginZod = isParserEnabled(parser) ? driver.getPlugin(pluginZodName) : null;
1708
- const zodResolver = pluginZod ? driver.getResolver(pluginZodName) : null;
1709
- function buildOperationData(node) {
1710
- const typeFile = tsResolver.resolveFile(operationFileEntry(node, node.operationId), {
1711
- root,
1712
- output: tsPluginOptions?.output ?? output,
1713
- group: tsPluginOptions?.group
1714
- });
1715
- const zodFile = zodResolver && pluginZod?.options ? zodResolver.resolveFile(operationFileEntry(node, node.operationId), {
1716
- root,
1717
- output: pluginZod.options?.output ?? output,
1718
- group: pluginZod.options?.group ?? void 0
1719
- }) : null;
1720
- return {
1721
- node,
1722
- name: resolver.resolveName(node.operationId),
1723
- tsResolver,
1724
- zodResolver,
1725
- typeFile,
1726
- zodFile
1727
- };
1728
- }
1729
- const controllers = nodes.reduce((acc, operationNode) => {
1730
- if (!ast.isHttpOperationNode(operationNode)) return acc;
1731
- const tag = operationNode.tags[0];
1732
- const groupName = tag ? group?.name?.({ group: camelCase(tag) }) ?? resolver.resolveGroupName(tag) : resolver.resolveClassName("Client");
1733
- if (!tag && !group) {
1734
- const name = resolver.resolveClassName("ApiClient");
1735
- const file = resolver.resolveFile({
1736
- name,
1737
- extname: ".ts"
1738
- }, {
879
+ fileTypes: resolveDependencyOperationFile({
880
+ cache: ctx.cache,
881
+ node,
882
+ resolver: types.resolver,
1739
883
  root,
1740
- output,
1741
- group: group ?? void 0
1742
- });
1743
- const operationData = buildOperationData(operationNode);
1744
- const previous = acc.find((item) => item.file.path === file.path);
1745
- if (previous) previous.operations.push(operationData);
1746
- else acc.push({
1747
- name,
1748
- file,
1749
- operations: [operationData]
1750
- });
1751
- return acc;
1752
- }
1753
- if (tag) {
1754
- const name = groupName;
1755
- const file = resolver.resolveFile({
1756
- name,
1757
- extname: ".ts",
1758
- tag
1759
- }, {
884
+ output: types.output ?? output,
885
+ group: types.group
886
+ }),
887
+ fileZod: zodResolver && pluginZod?.options ? zodResolver.file({
888
+ ...operationFileEntry(node, node.operationId),
1760
889
  root,
1761
- output,
1762
- group: group ?? void 0
1763
- });
1764
- const operationData = buildOperationData(operationNode);
1765
- const previous = acc.find((item) => item.file.path === file.path);
1766
- if (previous) previous.operations.push(operationData);
1767
- else acc.push({
1768
- name,
1769
- file,
1770
- operations: [operationData]
1771
- });
1772
- }
1773
- return acc;
1774
- }, []);
1775
- function collectTypeImports(ops) {
1776
- const typeImportsByFile = /* @__PURE__ */ new Map();
1777
- const typeFilesByPath = /* @__PURE__ */ new Map();
1778
- ops.forEach((op) => {
1779
- const names = resolveTypeImportNames(op.node, tsResolver);
1780
- if (!typeImportsByFile.has(op.typeFile.path)) typeImportsByFile.set(op.typeFile.path, /* @__PURE__ */ new Set());
1781
- const imports = typeImportsByFile.get(op.typeFile.path);
1782
- names.forEach((n) => {
1783
- imports.add(n);
1784
- });
1785
- typeFilesByPath.set(op.typeFile.path, op.typeFile);
1786
- });
1787
- return {
1788
- typeImportsByFile,
1789
- typeFilesByPath
890
+ output: pluginZod.options.output ?? output,
891
+ group: pluginZod.options?.group ?? void 0
892
+ }) : null
1790
893
  };
1791
- }
1792
- function collectZodImports(ops) {
1793
- const zodImportsByFile = /* @__PURE__ */ new Map();
1794
- const zodFilesByPath = /* @__PURE__ */ new Map();
1795
- ops.forEach((op) => {
1796
- if (!op.zodFile || !zodResolver) return;
1797
- const names = resolveZodImportNames(op.node, zodResolver, parser);
1798
- if (!zodImportsByFile.has(op.zodFile.path)) zodImportsByFile.set(op.zodFile.path, /* @__PURE__ */ new Set());
1799
- const imports = zodImportsByFile.get(op.zodFile.path);
1800
- names.forEach((n) => {
1801
- imports.add(n);
1802
- });
1803
- zodFilesByPath.set(op.zodFile.path, op.zodFile);
894
+ const security = getOperationSecurity({
895
+ document: ctx.adapter.document,
896
+ method: node.method,
897
+ path: node.path
1804
898
  });
1805
- return {
1806
- zodImportsByFile,
1807
- zodFilesByPath
1808
- };
1809
- }
1810
- return /* @__PURE__ */ jsx(Fragment, { children: controllers.map(({ name, file, operations: ops }) => {
1811
- const { typeImportsByFile, typeFilesByPath } = collectTypeImports(ops);
1812
- const { zodImportsByFile, zodFilesByPath } = isParserEnabled(parser) ? collectZodImports(ops) : {
1813
- zodImportsByFile: /* @__PURE__ */ new Map(),
1814
- zodFilesByPath: /* @__PURE__ */ new Map()
1815
- };
1816
- const hasFormData = ops.some((op) => op.node.requestBody?.content?.some((e) => e.contentType === "multipart/form-data") ?? false);
899
+ const customClientPath = resolveClientPath?.(ctx.options, root);
900
+ const clientPath = customClientPath ?? path.resolve(root, ".kubb/client.ts");
901
+ const clientRoot = customClientPath === void 0 ? meta.file.path : void 0;
902
+ const eventStream = isEventStream(node);
903
+ const clientValueImports = (() => {
904
+ if (eventStream) return ["client", "toEventStream"];
905
+ if (returnType === "plain") return ["client"];
906
+ if (returnType === "data") return ["client", "unwrapResult"];
907
+ return ["client", "withUnwrap"];
908
+ })();
909
+ const clientTypeImports = (() => {
910
+ if (eventStream) return [
911
+ "Options",
912
+ "EventStreamResult",
913
+ "SuccessOf"
914
+ ];
915
+ if (returnType === "plain") return ["Options", "RequestResult"];
916
+ if (returnType === "data") return ["Options", "UnwrappedResult"];
917
+ return [
918
+ "Options",
919
+ "Unwrappable",
920
+ "RequestResult"
921
+ ];
922
+ })();
1817
923
  return /* @__PURE__ */ jsxs(File, {
1818
- baseName: file.baseName,
1819
- path: file.path,
1820
- meta: file.meta,
1821
- banner: resolver.resolveBanner(ctx.meta, {
924
+ baseName: meta.file.baseName,
925
+ path: meta.file.path,
926
+ meta: meta.file.meta,
927
+ banner: resolver.default.banner(ctx.meta, {
1822
928
  output,
1823
929
  config,
1824
930
  file: {
1825
- path: file.path,
1826
- baseName: file.baseName
931
+ path: meta.file.path,
932
+ baseName: meta.file.baseName
1827
933
  }
1828
934
  }),
1829
- footer: resolver.resolveFooter(ctx.meta, {
935
+ footer: resolver.default.footer(ctx.meta, {
1830
936
  output,
1831
937
  config,
1832
938
  file: {
1833
- path: file.path,
1834
- baseName: file.baseName
939
+ path: meta.file.path,
940
+ baseName: meta.file.baseName
1835
941
  }
1836
942
  }),
1837
943
  children: [
1838
- importPath ? /* @__PURE__ */ jsxs(Fragment, { children: [
1839
- /* @__PURE__ */ jsx(File.Import, {
1840
- name: "client",
1841
- path: importPath
1842
- }),
1843
- /* @__PURE__ */ jsx(File.Import, {
1844
- name: ["mergeConfig"],
1845
- path: importPath
1846
- }),
1847
- /* @__PURE__ */ jsx(File.Import, {
1848
- name: [
1849
- "Client",
1850
- "RequestConfig",
1851
- "ResponseErrorConfig"
1852
- ],
1853
- path: importPath,
1854
- isTypeOnly: true
1855
- })
1856
- ] }) : /* @__PURE__ */ jsxs(Fragment, { children: [
1857
- /* @__PURE__ */ jsx(File.Import, {
1858
- name: ["client"],
1859
- root: file.path,
1860
- path: path.resolve(root, ".kubb/client.ts")
1861
- }),
1862
- /* @__PURE__ */ jsx(File.Import, {
1863
- name: ["mergeConfig"],
1864
- root: file.path,
1865
- path: path.resolve(root, ".kubb/client.ts")
1866
- }),
1867
- /* @__PURE__ */ jsx(File.Import, {
1868
- name: [
1869
- "Client",
1870
- "RequestConfig",
1871
- "ResponseErrorConfig"
1872
- ],
1873
- root: file.path,
1874
- path: path.resolve(root, ".kubb/client.ts"),
1875
- isTypeOnly: true
1876
- })
1877
- ] }),
1878
- hasFormData && /* @__PURE__ */ jsx(File.Import, {
1879
- name: ["buildFormData"],
1880
- root: file.path,
1881
- path: path.resolve(root, ".kubb/config.ts")
944
+ /* @__PURE__ */ jsx(File.Import, {
945
+ name: clientValueImports,
946
+ root: clientRoot,
947
+ path: clientPath
1882
948
  }),
1883
- parser === "zod" && zodResolver && ops.some((op) => op.node.requestBody?.content?.[0]?.schema != null) && /* @__PURE__ */ jsx(File.Import, {
1884
- name: ["z"],
1885
- path: "zod",
949
+ /* @__PURE__ */ jsx(File.Import, {
950
+ name: clientTypeImports,
951
+ root: clientRoot,
952
+ path: clientPath,
1886
953
  isTypeOnly: true
1887
954
  }),
1888
- Array.from(typeImportsByFile.entries()).map(([filePath, importSet]) => {
1889
- const typeFile = typeFilesByPath.get(filePath);
1890
- if (!typeFile) return null;
1891
- const importNames = Array.from(importSet).filter(Boolean);
1892
- if (importNames.length === 0) return null;
1893
- return /* @__PURE__ */ jsx(File.Import, {
1894
- name: importNames,
1895
- root: file.path,
1896
- path: typeFile.path,
1897
- isTypeOnly: true
1898
- }, filePath);
955
+ meta.fileTypes && importedTypeNames.length > 0 && /* @__PURE__ */ jsx(File.Import, {
956
+ name: Array.from(new Set(importedTypeNames)),
957
+ root: meta.file.path,
958
+ path: meta.fileTypes.path,
959
+ isTypeOnly: true
1899
960
  }),
1900
- isParserEnabled(parser) && Array.from(zodImportsByFile.entries()).map(([filePath, importSet]) => {
1901
- const zodFile = zodFilesByPath.get(filePath);
1902
- if (!zodFile) return null;
1903
- const importNames = Array.from(importSet).filter(Boolean);
1904
- if (importNames.length === 0) return null;
1905
- return /* @__PURE__ */ jsx(File.Import, {
1906
- name: importNames,
1907
- root: file.path,
1908
- path: zodFile.path
1909
- }, filePath);
961
+ meta.fileZod && importedZodNames.length > 0 && /* @__PURE__ */ jsx(File.Import, {
962
+ name: importedZodNames,
963
+ root: meta.file.path,
964
+ path: meta.fileZod.path
1910
965
  }),
1911
- /* @__PURE__ */ jsx(StaticClassClient, {
1912
- name,
1913
- operations: ops,
1914
- baseURL,
1915
- dataReturnType,
1916
- pathParamsType,
1917
- paramsCasing,
1918
- paramsType,
1919
- parser
966
+ /* @__PURE__ */ jsx(Operation, {
967
+ name: meta.name,
968
+ node,
969
+ types,
970
+ zodResolver,
971
+ validator,
972
+ returnType,
973
+ throwOnErrorDefault,
974
+ security
1920
975
  })
1921
976
  ]
1922
- }, file.path);
1923
- }) });
1924
- }
1925
- });
977
+ });
978
+ }
979
+ });
980
+ }
981
+ //#endregion
982
+ //#region ../../internals/client/src/macros.ts
983
+ /**
984
+ * Macros the client plugins apply by default, ahead of any user macros. `macroSimplifyUnion`
985
+ * drops union members a broader scalar already covers, keeping the generated response and error
986
+ * unions tidy. A plugin wires them with `ctx.setMacros([...defaultMacros, ...userMacros])`.
987
+ */
988
+ const defaultMacros = [macroSimplifyUnion];
1926
989
  //#endregion
1927
- //#region src/resolvers/resolverClient.ts
990
+ //#region ../../internals/client/src/resolver.ts
1928
991
  /**
1929
- * Default resolver used by `@kubb/plugin-client`. Decides the names and file
1930
- * paths for every generated client function or class. Functions and files use
1931
- * camelCase; classes and tag groups use PascalCase.
992
+ * Default resolver shared by the client plugins. Functions and files inherit the built-in camelCase
993
+ * `name` and `file`; classes and tag groups use PascalCase.
1932
994
  *
1933
- * @example Resolve client function and class names
995
+ * @example
1934
996
  * ```ts
1935
- * import { resolverClient } from '@kubb/plugin-client'
1936
- *
1937
- * resolverClient.default('list pets', 'function') // 'listPets'
1938
- * resolverClient.resolveClassName('pet') // 'Pet'
1939
- * resolverClient.resolveGroupName('pet') // 'PetClient'
1940
- * resolverClient.resolveUrlName(operationNode) // 'getShowPetByIdUrl'
997
+ * resolverClient.name('show pet by id') // 'showPetById'
998
+ * resolverClient.groupName('pet') // 'PetClient'
1941
999
  * ```
1942
1000
  */
1943
- const resolverClient = defineResolver(() => ({
1944
- name: "default",
1945
- pluginName: "plugin-client",
1946
- default(name, type) {
1947
- if (type === "file") return toFilePath(name);
1948
- return ensureValidVarName(camelCase(name));
1949
- },
1950
- resolveName(name) {
1951
- return this.default(name, "function");
1952
- },
1953
- resolvePathName(name, type) {
1954
- return this.default(name, type);
1955
- },
1956
- resolveClassName(name) {
1001
+ const resolverClient = createResolver({
1002
+ pluginName: "plugin-contract-client",
1003
+ className(name) {
1957
1004
  return ensureValidVarName(pascalCase(name));
1958
1005
  },
1959
- resolveGroupName(name) {
1006
+ groupName(name) {
1960
1007
  return ensureValidVarName(pascalCase(`${name} Client`));
1961
1008
  },
1962
- resolveClientPropertyName(name) {
1009
+ propertyName(name) {
1963
1010
  return ensureValidVarName(camelCase(name));
1964
- },
1965
- resolveUrlName(node) {
1966
- const name = this.resolveName(node.operationId);
1967
- return `get${name.charAt(0).toUpperCase()}${name.slice(1)}Url`;
1968
1011
  }
1969
- }));
1012
+ });
1013
+ //#endregion
1014
+ //#region src/generators/clientGenerator.tsx
1015
+ /**
1016
+ * Built-in operation generator for `@kubb/plugin-client`. Emits one async function per OpenAPI
1017
+ * operation using the shared `Operation` component: a grouped `<Name>Request` type and a function
1018
+ * that forwards a single `options` object to the user-provided client module and returns the `RequestResult`.
1019
+ */
1020
+ const clientGenerator = createClientGenerator("client", (options) => options.importPath);
1970
1021
  //#endregion
1971
1022
  //#region src/plugin.ts
1972
1023
  /**
1973
- * Canonical plugin name for `@kubb/plugin-client`. Used for driver lookups and
1974
- * cross-plugin dependency references.
1024
+ * Canonical plugin name for `@kubb/plugin-client`.
1975
1025
  */
1976
1026
  const pluginClientName = "plugin-client";
1977
1027
  /**
1978
- * Generates one HTTP client function per OpenAPI operation. Each function has
1979
- * typed path params, query params, body, and response, so callers use the API
1980
- * like any other typed function. Ships with `axios` and `fetch` runtimes; bring
1981
- * your own by setting `importPath`.
1028
+ * Generates typed operations that call a client module owned by your application.
1029
+ * The module at `importPath` must export the runtime helpers and types used by generated operations.
1030
+ * Kubb does not copy or inject a transport implementation.
1982
1031
  *
1983
1032
  * @example
1984
1033
  * ```ts
1985
- * import { defineConfig } from 'kubb'
1986
- * import { pluginTs } from '@kubb/plugin-ts'
1987
- * import { pluginClient } from '@kubb/plugin-client'
1988
- *
1989
- * export default defineConfig({
1990
- * input: { path: './petStore.yaml' },
1991
- * output: { path: './src/gen' },
1992
- * plugins: [
1993
- * pluginTs(),
1994
- * pluginClient({
1995
- * output: { path: './clients' },
1996
- * client: 'fetch',
1997
- * }),
1998
- * ],
1999
- * })
1034
+ * pluginClient({ importPath: '../api/client' })
2000
1035
  * ```
2001
1036
  */
2002
1037
  const pluginClient = definePlugin((options) => {
2003
1038
  const { output = {
2004
1039
  path: "clients",
2005
1040
  barrel: { type: "named" }
2006
- }, group, exclude = [], include, override = [], urlType = false, dataReturnType = "data", paramsType = "inline", pathParamsType = paramsType === "object" ? "object" : options.pathParamsType || "inline", operations = false, paramsCasing, clientType = options.sdk ? "class" : "function", parser = false, client = "axios", importPath, bundle = false, sdk, baseURL, resolver: userResolver, macros: userMacros } = options;
2007
- const resolvedImportPath = importPath ?? (!bundle ? `@kubb/plugin-client/clients/${client}` : void 0);
2008
- const selectedGenerators = options.generators ?? [
2009
- clientType === "staticClass" ? staticClassClientGenerator : clientType === "class" ? classClientGenerator : clientGenerator,
2010
- group && clientType === "function" ? groupedClientGenerator : null,
2011
- operations ? operationsGenerator : null
2012
- ].filter((x) => Boolean(x));
2013
- const groupConfig = createGroupConfig(group);
1041
+ }, exclude = [], include, override = [], importPath, throwOnErrorDefault = true, validator = false, group, resolver: userResolver } = options;
1042
+ const resolved = {
1043
+ output,
1044
+ exclude,
1045
+ include,
1046
+ override,
1047
+ group: createGroupConfig(group),
1048
+ importPath,
1049
+ baseURL: void 0,
1050
+ throwOnErrorDefault,
1051
+ validator,
1052
+ returnType: "plain",
1053
+ sdk: void 0,
1054
+ resolver: userResolver ? Resolver.merge(resolverClient, userResolver) : resolverClient
1055
+ };
2014
1056
  return {
2015
1057
  name: pluginClientName,
2016
1058
  options,
2017
- dependencies: [pluginTsName, isParserEnabled(parser) ? pluginZodName : null].filter((dependency) => Boolean(dependency)),
1059
+ dependencies: [pluginTsName, pluginZodName],
2018
1060
  hooks: { "kubb:plugin:setup"(ctx) {
2019
- const resolver = userResolver ? {
2020
- ...resolverClient,
2021
- ...userResolver
2022
- } : resolverClient;
2023
- ctx.setOptions({
2024
- client,
2025
- clientType,
2026
- bundle,
2027
- output,
2028
- exclude,
2029
- include,
2030
- override,
2031
- group: groupConfig,
2032
- parser,
2033
- dataReturnType,
2034
- importPath: resolvedImportPath,
2035
- baseURL,
2036
- paramsType,
2037
- paramsCasing,
2038
- pathParamsType,
2039
- urlType,
2040
- sdk,
2041
- resolver
2042
- });
2043
- ctx.setResolver(resolver);
2044
- if (userMacros?.length) ctx.setMacros(userMacros);
2045
- for (const gen of selectedGenerators) ctx.addGenerator(gen);
2046
- const root = path.resolve(ctx.config.root, ctx.config.output.path);
2047
- if (!resolvedImportPath?.startsWith(".")) {
2048
- const isInlineSource = bundle && !resolvedImportPath;
2049
- ctx.injectFile({
2050
- baseName: "client.ts",
2051
- path: path.resolve(root, ".kubb/client.ts"),
2052
- sources: [ast.factory.createSource({
2053
- name: "client",
2054
- nodes: isInlineSource ? [ast.factory.createText(client === "fetch" ? source$1 : source)] : [],
2055
- isExportable: true,
2056
- isIndexable: true
2057
- })],
2058
- exports: !isInlineSource && resolvedImportPath ? [ast.factory.createExport({ path: resolvedImportPath })] : []
2059
- });
2060
- }
2061
- ctx.injectFile({
2062
- baseName: "config.ts",
2063
- path: path.resolve(root, ".kubb/config.ts"),
2064
- sources: [ast.factory.createSource({
2065
- name: "config",
2066
- nodes: [ast.factory.createText(source$2)],
2067
- isExportable: false,
2068
- isIndexable: false
2069
- })]
2070
- });
1061
+ if (validator && !ctx.config.plugins?.some((plugin) => plugin.name === pluginZodName)) throw new Error(`${pluginClientName}: \`validator\` needs ${pluginZodName}. Add pluginZod() to the plugins or set \`validator: false\`.`);
1062
+ ctx.setOptions(resolved);
1063
+ ctx.setResolver(resolved.resolver);
1064
+ ctx.setMacros([...defaultMacros, ...options.macros ?? []]);
1065
+ ctx.addGenerator(clientGenerator);
2071
1066
  } }
2072
1067
  };
2073
1068
  });
2074
1069
  //#endregion
2075
- export { Client, classClientGenerator, clientGenerator, pluginClient as default, pluginClient, groupedClientGenerator, isParserEnabled, operationsGenerator, pluginClientName, resolveQueryParamsParser, resolveRequestParser, resolveResponseParser, resolverClient, staticClassClientGenerator };
1070
+ export { pluginClient as default, pluginClient, pluginClientName };
2076
1071
 
2077
1072
  //# sourceMappingURL=index.js.map