@kubb/plugin-fetch 5.0.0-beta.98 → 5.0.0

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.
package/dist/index.cjs CHANGED
@@ -19,19 +19,225 @@ var __copyProps = (to, from, except, desc) => {
19
19
  }
20
20
  return to;
21
21
  };
22
- var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", {
22
+ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(isNodeMode || !mod || !mod.__esModule || !__hasOwnProp.call(mod, "default") ? __defProp(target, "default", {
23
23
  value: mod,
24
24
  enumerable: true
25
25
  }) : target, mod));
26
26
  //#endregion
27
+ let kubb_kit = require("kubb/kit");
27
28
  let node_path = require("node:path");
28
29
  node_path = __toESM(node_path, 1);
29
- let kubb_kit = require("kubb/kit");
30
30
  let _kubb_plugin_ts = require("@kubb/plugin-ts");
31
31
  let kubb_jsx = require("kubb/jsx");
32
32
  let kubb_jsx_jsx_runtime = require("kubb/jsx/jsx-runtime");
33
33
  let _kubb_plugin_zod = require("@kubb/plugin-zod");
34
34
  let node_url = require("node:url");
35
+ //#region ../../internals/shared/src/params.ts
36
+ /**
37
+ * Drops parameters that share the same name, keeping the first.
38
+ *
39
+ * A malformed spec can declare the same parameter name twice within one `in` location. Both would
40
+ * resolve to the same output property, so emitting both would yield an object type with a duplicate
41
+ * member, which TypeScript rejects. This is a defensive guard against that case, not a casing guard:
42
+ * parameter names flow through unchanged, so no two distinct names ever collide here anymore.
43
+ */
44
+ function dedupeParams(params) {
45
+ const seen = /* @__PURE__ */ new Set();
46
+ return params.filter((param) => {
47
+ if (seen.has(param.name)) return false;
48
+ seen.add(param.name);
49
+ return true;
50
+ });
51
+ }
52
+ //#endregion
53
+ //#region ../../internals/shared/src/operation.ts
54
+ /**
55
+ * Builds the `ResolverFileParams` every operation generator passes to
56
+ * `resolver.file`: a file named `name`, tagged by the operation's first
57
+ * tag (or `'default'`), at the operation's path. Centralizes the entry object
58
+ * that was repeated at dozens of call sites across the client and query plugins.
59
+ *
60
+ * @example
61
+ * ```ts
62
+ * resolver.file(operationFileEntry(node, node.operationId), { root, output, group })
63
+ * ```
64
+ */
65
+ function operationFileEntry(node, name, extname = ".ts") {
66
+ return {
67
+ name,
68
+ extname,
69
+ tag: node.tags[0] ?? "default",
70
+ path: node.path
71
+ };
72
+ }
73
+ /**
74
+ * Resolves a dependency plugin's generated file for `node.operationId`, cached in `cache` (the
75
+ * current node's `ctx.cache`) under the resolver's own plugin name. Several dependents reading the
76
+ * same dependency for the same operation in one pass (a query plugin's several hook generators, the
77
+ * MCP handler, ...) share one computed name and path instead of each calling `resolver.file` again.
78
+ *
79
+ * @example Cache `plugin-ts`'s file for the current operation
80
+ * ```ts
81
+ * const fileTs = resolveDependencyOperationFile({ cache: ctx.cache, node, resolver: tsResolver, root, output })
82
+ * ```
83
+ */
84
+ function resolveDependencyOperationFile(options) {
85
+ const { cache, node, resolver, root, output, group } = options;
86
+ return cache.ensureItem(`${resolver.pluginName}:operationFile`, () => resolver.file({
87
+ ...operationFileEntry(node, node.operationId),
88
+ root,
89
+ output,
90
+ group: group ?? void 0
91
+ }));
92
+ }
93
+ function getOperationLink(node, link) {
94
+ if (!link) return null;
95
+ if (typeof link === "function") return link(node) ?? null;
96
+ return node.path ? `{@link ${kubb_kit.Url.toPath(node.path)}}` : null;
97
+ }
98
+ /**
99
+ * Derives the shared `ContentTypeInfo` shape from a list of content types, tracking whether several
100
+ * are present and the union, default, and form-data flags the client uses to pick one.
101
+ */
102
+ function buildContentTypeInfo(contentTypes) {
103
+ const isMultipleContentTypes = contentTypes.length > 1;
104
+ return {
105
+ contentTypes,
106
+ isMultipleContentTypes,
107
+ contentTypeUnion: isMultipleContentTypes ? contentTypes.map((ct) => JSON.stringify(ct)).join(" | ") : "",
108
+ defaultContentType: contentTypes[0] ?? "application/json",
109
+ hasFormData: contentTypes.some((ct) => ct === "multipart/form-data")
110
+ };
111
+ }
112
+ function getContentTypeInfo(node) {
113
+ return buildContentTypeInfo(node.requestBody?.content?.map((e) => e.contentType) ?? []);
114
+ }
115
+ /**
116
+ * The request-body counterpart for the primary success response: the content types it documents and
117
+ * whether several are present, so the client can let a caller pick which one to accept.
118
+ */
119
+ function getResponseContentTypeInfo(node) {
120
+ return buildContentTypeInfo(getPrimarySuccessResponse(node)?.content?.map((e) => e.contentType) ?? []);
121
+ }
122
+ /**
123
+ * Reads the single base content type of an operation's primary success response, lowercased and
124
+ * stripped of any `; charset=...` suffix. Returns `undefined` when the response declares zero or
125
+ * more than one content type, since neither case has a single type to act on.
126
+ */
127
+ function getPrimarySuccessContentType(node) {
128
+ const contentTypes = getPrimarySuccessResponse(node)?.content?.map((entry) => entry.contentType) ?? [];
129
+ if (contentTypes.length !== 1) return void 0;
130
+ return contentTypes[0].split(";")[0].trim().toLowerCase();
131
+ }
132
+ /**
133
+ * Whether an operation streams its primary success response as Server-Sent Events
134
+ * (`text/event-stream`). The client generator uses this to return a typed event stream instead of a
135
+ * one-shot `RequestResult`.
136
+ */
137
+ function isEventStream(node) {
138
+ return getPrimarySuccessContentType(node) === "text/event-stream";
139
+ }
140
+ /**
141
+ * Derives the default `responseType` for an operation from its primary success response.
142
+ *
143
+ * Returns a value only when that response declares a single non-JSON content type. `text/event-stream`
144
+ * and other binary types (`application/octet-stream`, `application/pdf`, `image/*`, `audio/*`,
145
+ * `video/*`) map to a stream or `'blob'`, and other `text/*` maps to `'text'`. Otherwise `undefined`,
146
+ * leaving the runtime client's `Content-Type` auto-detection in charge.
147
+ */
148
+ function getResponseType(node) {
149
+ const baseType = getPrimarySuccessContentType(node);
150
+ if (!baseType) return void 0;
151
+ if (baseType === "application/json" || baseType.endsWith("+json") || baseType === "text/json") return void 0;
152
+ if (baseType === "text/event-stream") return "stream";
153
+ if (baseType.startsWith("text/")) return "text";
154
+ if (baseType === "application/octet-stream" || baseType === "application/pdf" || /^(image|audio|video)\//.test(baseType)) return "blob";
155
+ }
156
+ /**
157
+ * Which of the grouped request options an operation carries.
158
+ */
159
+ function getRequestGroups(node) {
160
+ const { path, query, header } = getOperationParameters(node);
161
+ return {
162
+ path: path.length > 0,
163
+ query: query.length > 0,
164
+ body: Boolean(node.requestBody?.content?.[0]?.schema),
165
+ headers: header.length > 0
166
+ };
167
+ }
168
+ /**
169
+ * Resolves which grouped request options an operation carries together with whether each group
170
+ * holds a required member. The grouped parameter stays optional only when nothing inside it is
171
+ * required, matching the generated `RequestConfig` type.
172
+ */
173
+ function getRequestGroupOptionality(node) {
174
+ const groups = getRequestGroups(node);
175
+ const { path, query, header } = getOperationParameters(node);
176
+ const hasRequiredPath = path.some((param) => param.required);
177
+ const hasRequiredQuery = query.some((param) => param.required);
178
+ const hasRequiredHeader = header.some((param) => param.required);
179
+ return {
180
+ groups,
181
+ hasRequiredPath,
182
+ hasRequiredQuery,
183
+ hasRequiredHeader,
184
+ isOptional: !hasRequiredPath && !hasRequiredQuery && !hasRequiredHeader && !groups.body
185
+ };
186
+ }
187
+ function buildOperationComments(node, options = {}) {
188
+ const { link = "pathTemplate", linkPosition = "afterDeprecated", splitLines = false } = options;
189
+ const linkComment = getOperationLink(node, link);
190
+ const filteredComments = (linkPosition === "beforeDeprecated" ? [
191
+ node.description && `@description ${node.description}`,
192
+ node.summary && `@summary ${node.summary}`,
193
+ linkComment,
194
+ node.deprecated && "@deprecated"
195
+ ] : [
196
+ node.description && `@description ${node.description}`,
197
+ node.summary && `@summary ${node.summary}`,
198
+ node.deprecated && "@deprecated",
199
+ linkComment
200
+ ]).filter((comment) => Boolean(comment));
201
+ if (!splitLines) return filteredComments;
202
+ return filteredComments.flatMap((text) => text.split(/\r?\n/).map((line) => line.trim())).filter((comment) => Boolean(comment));
203
+ }
204
+ const operationParameterGroupsByNode = /* @__PURE__ */ new WeakMap();
205
+ /**
206
+ * Groups an operation's parameters by location (`path`/`query`/`header`/`cookie`), deduping each
207
+ * group by name. Every plugin generator visiting the same `OperationNode` shares one AST instance
208
+ * (see `KubbDriver`), so the result is cached per node to avoid re-filtering and re-deduping the
209
+ * same parameters once per plugin.
210
+ */
211
+ function getOperationParameters(node) {
212
+ const cached = operationParameterGroupsByNode.get(node);
213
+ if (cached) return cached;
214
+ const groups = {
215
+ path: dedupeParams(node.parameters.filter((param) => param.in === "path")),
216
+ query: dedupeParams(node.parameters.filter((param) => param.in === "query")),
217
+ header: dedupeParams(node.parameters.filter((param) => param.in === "header")),
218
+ cookie: dedupeParams(node.parameters.filter((param) => param.in === "cookie"))
219
+ };
220
+ operationParameterGroupsByNode.set(node, groups);
221
+ return groups;
222
+ }
223
+ function getStatusCodeNumber(statusCode) {
224
+ const code = Number(statusCode);
225
+ return Number.isNaN(code) ? null : code;
226
+ }
227
+ function isSuccessStatusCode(statusCode) {
228
+ const code = getStatusCodeNumber(statusCode);
229
+ return code !== null && code >= 200 && code < 300;
230
+ }
231
+ function getSuccessResponses(responses) {
232
+ return responses.filter((response) => isSuccessStatusCode(response.statusCode));
233
+ }
234
+ function getOperationSuccessResponses(node) {
235
+ return getSuccessResponses(node.responses);
236
+ }
237
+ function getPrimarySuccessResponse(node) {
238
+ return getOperationSuccessResponses(node)[0] ?? null;
239
+ }
240
+ //#endregion
35
241
  //#region ../../internals/utils/src/casing.ts
36
242
  /**
37
243
  * Shared implementation for camelCase and PascalCase conversion.
@@ -227,342 +433,6 @@ function buildJSDoc(comments, options = {}) {
227
433
  return `/**\n${comments.map((c) => `${indent}${c}`).join("\n")}\n */${suffix}`;
228
434
  }
229
435
  //#endregion
230
- //#region ../../internals/utils/src/url.ts
231
- function transformParam(raw, casing) {
232
- const param = isValidVarName(raw) ? raw : camelCase(raw);
233
- return casing === "camelcase" ? camelCase(param) : param;
234
- }
235
- function toParamsObject(path, { replacer, casing } = {}) {
236
- const params = {};
237
- for (const match of path.matchAll(/\{([^}]+)\}/g)) {
238
- const param = transformParam(match[1], casing);
239
- const key = replacer ? replacer(param) : param;
240
- params[key] = key;
241
- }
242
- return Object.keys(params).length > 0 ? params : null;
243
- }
244
- /**
245
- * Helpers for OpenAPI/Swagger paths, plus a thin wrapper over the native `URL`.
246
- */
247
- var Url = class Url {
248
- /**
249
- * Reports whether `url` is a parseable absolute URL. Delegates to the native `URL.canParse`.
250
- *
251
- * @example
252
- * Url.canParse('https://petstore.swagger.io/v2') // true
253
- * Url.canParse('/pet/{petId}') // false
254
- */
255
- static canParse(url, base) {
256
- return URL.canParse(url, base);
257
- }
258
- /**
259
- * Converts an OpenAPI/Swagger path to Express-style colon syntax.
260
- *
261
- * @example
262
- * Url.toPath('/pet/{petId}') // '/pet/:petId'
263
- */
264
- static toPath(path) {
265
- return path.replace(/\{([^}]+)\}/g, ":$1");
266
- }
267
- /**
268
- * Rewrites OpenAPI placeholder names while keeping the `{...}` braces, so the generated `url`
269
- * literal aligns with the grouped `path` request option that the runtime client interpolates by
270
- * key.
271
- *
272
- * @example
273
- * Url.toCasedTemplate('/projects/{project_id}', { casing: 'camelcase' }) // '/projects/{projectId}'
274
- */
275
- static toCasedTemplate(path, { casing } = {}) {
276
- return path.replace(/\{([^}]+)\}/g, (_, name) => `{${transformParam(name, casing)}}`);
277
- }
278
- /**
279
- * Converts an OpenAPI/Swagger path to a TypeScript template literal string.
280
- * `prefix` is prepended inside the literal, `replacer` transforms each parameter name,
281
- * and `casing` controls parameter identifier casing.
282
- *
283
- * @example
284
- * Url.toTemplateString('/pet/{petId}') // '`/pet/${petId}`'
285
- *
286
- * @example
287
- * Url.toTemplateString('/pet/{petId}', { prefix: 'https://api' }) // '`https://api/pet/${petId}`'
288
- */
289
- static toTemplateString(path, { prefix, replacer, casing } = {}) {
290
- const result = path.split(/\{([^}]+)\}/).map((part, i) => {
291
- if (i % 2 === 0) return part;
292
- const param = transformParam(part, casing);
293
- return `\${${replacer ? replacer(param) : param}}`;
294
- }).join("");
295
- return `\`${prefix ?? ""}${result}\``;
296
- }
297
- /**
298
- * Converts an OpenAPI/Swagger path to a template literal that reads each parameter off the
299
- * grouped `path` request option, e.g. `/pet/{petId}` becomes `` `/pet/${path.petId}` ``. Parameter
300
- * names are camelCased to match the generated `path` type, and `prefix` is prepended inside the
301
- * literal. Shared by the client and cypress generators that pass a grouped `path` object.
302
- *
303
- * @example
304
- * Url.toGroupedTemplateString('/pet/{petId}') // '`/pet/${path.petId}`'
305
- */
306
- static toGroupedTemplateString(path, { prefix } = {}) {
307
- return Url.toTemplateString(path, {
308
- prefix,
309
- casing: "camelcase",
310
- replacer: (name) => `path.${name}`
311
- });
312
- }
313
- /**
314
- * Returns the path and its extracted params as a structured `URLObject`, or as a stringified
315
- * expression when `stringify` is set.
316
- *
317
- * @example
318
- * Url.toObject('/pet/{petId}')
319
- * // { url: '/pet/:petId', params: { petId: 'petId' } }
320
- */
321
- static toObject(path, { type = "path", replacer, stringify, casing } = {}) {
322
- const object = {
323
- url: type === "path" ? Url.toPath(path) : Url.toTemplateString(path, {
324
- replacer,
325
- casing
326
- }),
327
- params: toParamsObject(path, {
328
- replacer,
329
- casing
330
- })
331
- };
332
- if (stringify) {
333
- if (type === "template") return JSON.stringify(object).replaceAll("'", "").replaceAll(`"`, "");
334
- if (object.params) return `{ url: '${object.url}', params: ${JSON.stringify(object.params).replaceAll("'", "").replaceAll(`"`, "")} }`;
335
- return `{ url: '${object.url}' }`;
336
- }
337
- return object;
338
- }
339
- };
340
- //#endregion
341
- //#region ../../internals/shared/src/params.ts
342
- const caseParamsCache = /* @__PURE__ */ new WeakMap();
343
- /**
344
- * Applies camelCase to parameter names and returns a new array without mutating the input.
345
- *
346
- * Run it before handing parameters to schema builders so output property keys get the right casing
347
- * while `OperationNode.parameters` stays intact for other consumers. When `casing` is unset, the
348
- * original array is returned unchanged. Results are cached per input array.
349
- */
350
- function caseParams(params, casing) {
351
- if (!casing) return params;
352
- const cached = caseParamsCache.get(params);
353
- if (cached) return cached;
354
- const result = params.map((param) => ({
355
- ...param,
356
- name: camelCase(param.name)
357
- }));
358
- caseParamsCache.set(params, result);
359
- return result;
360
- }
361
- /**
362
- * Drops parameters that collapse to the same property identity once camelCased, keeping the first.
363
- *
364
- * Some specs declare the same parameter twice under different casings (for example AWS S3 lists both
365
- * `max-uploads` and `MaxUploads`). Both resolve to one output property, so emitting both would yield
366
- * an object type with a duplicate member, which TypeScript rejects. De-duplicate by the camelCased
367
- * identity so the resulting group is collision-free regardless of the names each caller carries.
368
- */
369
- function dedupeByCasedName(params) {
370
- const seen = /* @__PURE__ */ new Set();
371
- return params.filter((param) => {
372
- const key = camelCase(param.name);
373
- if (seen.has(key)) return false;
374
- seen.add(key);
375
- return true;
376
- });
377
- }
378
- function buildParamsMapping(originalParams, mappedParams) {
379
- const mapping = {};
380
- let hasChanged = false;
381
- originalParams.forEach((param, i) => {
382
- const mappedName = mappedParams[i]?.name ?? param.name;
383
- mapping[param.name] = mappedName;
384
- if (param.name !== mappedName) hasChanged = true;
385
- });
386
- return hasChanged ? mapping : null;
387
- }
388
- function toAccess(object, name) {
389
- return isValidVarName(name) ? `${object}.${name}` : `${object}[${JSON.stringify(name)}]`;
390
- }
391
- /**
392
- * Renders the object-literal expression that renames the camelCased keys of a grouped request
393
- * option back to the names the OpenAPI document declares, guarded so an omitted optional group
394
- * stays omitted. Shared by the client and cypress generators, which pass a `buildParamsMapping`
395
- * result and the source expression to read the keys from.
396
- *
397
- * @example
398
- * ```ts
399
- * buildParamsRemapExpression({ source: 'config.query', mapping: { include_deleted: 'includeDeleted' } })
400
- * // 'config.query ? { "include_deleted": config.query.includeDeleted } : config.query'
401
- * ```
402
- */
403
- function buildParamsRemapExpression({ source, mapping }) {
404
- return `${source} ? { ${Object.entries(mapping).map(([originalName, casedName]) => `${JSON.stringify(originalName)}: ${toAccess(source, casedName)}`).join(", ")} } : ${source}`;
405
- }
406
- //#endregion
407
- //#region ../../internals/shared/src/operation.ts
408
- /**
409
- * Builds the `ResolverFileParams` every operation generator passes to
410
- * `resolver.file`: a file named `name`, tagged by the operation's first
411
- * tag (or `'default'`), at the operation's path. Centralizes the entry object
412
- * that was repeated at dozens of call sites across the client and query plugins.
413
- *
414
- * @example
415
- * ```ts
416
- * resolver.file(operationFileEntry(node, node.operationId), { root, output, group })
417
- * ```
418
- */
419
- function operationFileEntry(node, name, extname = ".ts") {
420
- return {
421
- name,
422
- extname,
423
- tag: node.tags[0] ?? "default",
424
- path: node.path
425
- };
426
- }
427
- function getOperationLink(node, link) {
428
- if (!link) return null;
429
- if (typeof link === "function") return link(node) ?? null;
430
- if (link === "urlPath") return node.path ? `{@link ${Url.toPath(node.path)}}` : null;
431
- return node.path ? `{@link ${node.path.replaceAll("{", ":").replaceAll("}", "")}}` : null;
432
- }
433
- /**
434
- * Derives the shared `ContentTypeInfo` shape from a list of content types, tracking whether several
435
- * are present and the union, default, and form-data flags the client uses to pick one.
436
- */
437
- function buildContentTypeInfo(contentTypes) {
438
- const isMultipleContentTypes = contentTypes.length > 1;
439
- return {
440
- contentTypes,
441
- isMultipleContentTypes,
442
- contentTypeUnion: isMultipleContentTypes ? contentTypes.map((ct) => JSON.stringify(ct)).join(" | ") : "",
443
- defaultContentType: contentTypes[0] ?? "application/json",
444
- hasFormData: contentTypes.some((ct) => ct === "multipart/form-data")
445
- };
446
- }
447
- function getContentTypeInfo(node) {
448
- return buildContentTypeInfo(node.requestBody?.content?.map((e) => e.contentType) ?? []);
449
- }
450
- /**
451
- * The request-body counterpart for the primary success response: the content types it documents and
452
- * whether several are present, so the client can let a caller pick which one to accept.
453
- */
454
- function getResponseContentTypeInfo(node) {
455
- return buildContentTypeInfo(getPrimarySuccessResponse(node)?.content?.map((e) => e.contentType) ?? []);
456
- }
457
- /**
458
- * Reads the single base content type of an operation's primary success response, lowercased and
459
- * stripped of any `; charset=...` suffix. Returns `undefined` when the response declares zero or
460
- * more than one content type, since neither case has a single type to act on.
461
- */
462
- function getPrimarySuccessContentType(node) {
463
- const contentTypes = getPrimarySuccessResponse(node)?.content?.map((entry) => entry.contentType) ?? [];
464
- if (contentTypes.length !== 1) return void 0;
465
- return contentTypes[0].split(";")[0].trim().toLowerCase();
466
- }
467
- /**
468
- * Whether an operation streams its primary success response as Server-Sent Events
469
- * (`text/event-stream`). The client generator uses this to return a typed event stream instead of a
470
- * one-shot `RequestResult`.
471
- */
472
- function isEventStream(node) {
473
- return getPrimarySuccessContentType(node) === "text/event-stream";
474
- }
475
- /**
476
- * Derives the default `responseType` for an operation from its primary success response.
477
- *
478
- * Returns a value only when that response declares a single non-JSON content type. `text/event-stream`
479
- * and other binary types (`application/octet-stream`, `application/pdf`, `image/*`, `audio/*`,
480
- * `video/*`) map to a stream or `'blob'`, and other `text/*` maps to `'text'`. Otherwise `undefined`,
481
- * leaving the runtime client's `Content-Type` auto-detection in charge.
482
- */
483
- function getResponseType(node) {
484
- const baseType = getPrimarySuccessContentType(node);
485
- if (!baseType) return void 0;
486
- if (baseType === "application/json" || baseType.endsWith("+json") || baseType === "text/json") return void 0;
487
- if (baseType === "text/event-stream") return "stream";
488
- if (baseType.startsWith("text/")) return "text";
489
- if (baseType === "application/octet-stream" || baseType === "application/pdf" || /^(image|audio|video)\//.test(baseType)) return "blob";
490
- }
491
- /**
492
- * Which of the grouped request options an operation carries.
493
- */
494
- function getRequestGroups(node) {
495
- const { path, query, header } = getOperationParameters(node);
496
- return {
497
- path: path.length > 0,
498
- query: query.length > 0,
499
- body: Boolean(node.requestBody?.content?.[0]?.schema),
500
- headers: header.length > 0
501
- };
502
- }
503
- /**
504
- * Resolves which grouped request options an operation carries together with whether each group
505
- * holds a required member. The grouped parameter stays optional only when nothing inside it is
506
- * required, matching the generated `RequestConfig` type.
507
- */
508
- function getRequestGroupOptionality(node) {
509
- const groups = getRequestGroups(node);
510
- const { path, query, header } = getOperationParameters(node);
511
- const hasRequiredPath = path.some((param) => param.required);
512
- const hasRequiredQuery = query.some((param) => param.required);
513
- const hasRequiredHeader = header.some((param) => param.required);
514
- return {
515
- groups,
516
- hasRequiredPath,
517
- hasRequiredQuery,
518
- hasRequiredHeader,
519
- isOptional: !hasRequiredPath && !hasRequiredQuery && !hasRequiredHeader && !groups.body
520
- };
521
- }
522
- function buildOperationComments(node, options = {}) {
523
- const { link = "pathTemplate", linkPosition = "afterDeprecated", splitLines = false } = options;
524
- const linkComment = getOperationLink(node, link);
525
- const filteredComments = (linkPosition === "beforeDeprecated" ? [
526
- node.description && `@description ${node.description}`,
527
- node.summary && `@summary ${node.summary}`,
528
- linkComment,
529
- node.deprecated && "@deprecated"
530
- ] : [
531
- node.description && `@description ${node.description}`,
532
- node.summary && `@summary ${node.summary}`,
533
- node.deprecated && "@deprecated",
534
- linkComment
535
- ]).filter((comment) => Boolean(comment));
536
- if (!splitLines) return filteredComments;
537
- return filteredComments.flatMap((text) => text.split(/\r?\n/).map((line) => line.trim())).filter((comment) => Boolean(comment));
538
- }
539
- function getOperationParameters(node, options = {}) {
540
- const params = caseParams(node.parameters, options.paramsCasing === "original" ? void 0 : "camelcase");
541
- return {
542
- path: dedupeByCasedName(params.filter((param) => param.in === "path")),
543
- query: dedupeByCasedName(params.filter((param) => param.in === "query")),
544
- header: dedupeByCasedName(params.filter((param) => param.in === "header")),
545
- cookie: dedupeByCasedName(params.filter((param) => param.in === "cookie"))
546
- };
547
- }
548
- function getStatusCodeNumber(statusCode) {
549
- const code = Number(statusCode);
550
- return Number.isNaN(code) ? null : code;
551
- }
552
- function isSuccessStatusCode(statusCode) {
553
- const code = getStatusCodeNumber(statusCode);
554
- return code !== null && code >= 200 && code < 300;
555
- }
556
- function getSuccessResponses(responses) {
557
- return responses.filter((response) => isSuccessStatusCode(response.statusCode));
558
- }
559
- function getOperationSuccessResponses(node) {
560
- return getSuccessResponses(node.responses);
561
- }
562
- function getPrimarySuccessResponse(node) {
563
- return getOperationSuccessResponses(node)[0] ?? null;
564
- }
565
- //#endregion
566
436
  //#region ../../internals/shared/src/group.ts
567
437
  /**
568
438
  * Builds the `group` config a Kubb plugin passes to `ctx.setOptions`, applying the
@@ -594,38 +464,6 @@ function createGroupConfig(group) {
594
464
  };
595
465
  }
596
466
  //#endregion
597
- //#region ../../internals/client/src/builders/paramsRemap.ts
598
- /**
599
- * Builds the call-config entries that rename the camelCased `query` and `headers` keys back to the
600
- * names the OpenAPI document declares, so the wire format follows the spec while the generated
601
- * types keep camelCase keys. Returns an empty array when no name changes. Path parameters need no
602
- * remap because the URL template placeholders are renamed in sync with the `path` keys. Emit the
603
- * entries after the `...config` spread so they override the camelCased groups the caller passes in.
604
- *
605
- * @example
606
- * ```ts
607
- * // a query param named include_deleted in the spec
608
- * buildParamsRemap({ node }) // ['query: config.query ? { "include_deleted": config.query.includeDeleted } : config.query']
609
- * ```
610
- */
611
- function buildParamsRemap({ node }) {
612
- if (!kubb_kit.ast.isHttpOperationNode(node)) return [];
613
- const original = getOperationParameters(node, { paramsCasing: "original" });
614
- const cased = getOperationParameters(node);
615
- const queryMapping = buildParamsMapping(original.query, cased.query);
616
- const headerMapping = buildParamsMapping(original.header, cased.header);
617
- const entries = [];
618
- if (queryMapping) entries.push(`query: ${buildParamsRemapExpression({
619
- source: "config.query",
620
- mapping: queryMapping
621
- })}`);
622
- if (headerMapping) entries.push(`headers: ${buildParamsRemapExpression({
623
- source: "config.headers",
624
- mapping: headerMapping
625
- })}`);
626
- return entries;
627
- }
628
- //#endregion
629
467
  //#region ../../internals/client/src/builders/generics.ts
630
468
  /**
631
469
  * Builds the `RequestResult` generic arguments for one operation: the plugin-ts per-status responses
@@ -857,11 +695,10 @@ function buildCallConfig({ node, validator, zodResolver, security }) {
857
695
  const securityLiteral = buildSecurityMetadata({ security });
858
696
  return `{ ${[
859
697
  `method: '${node.method.toUpperCase()}'`,
860
- `url: '${Url.toCasedTemplate(node.path, { casing: "camelcase" })}'`,
698
+ `url: '${node.path}'`,
861
699
  securityLiteral ? `security: ${securityLiteral}` : null,
862
700
  validatorLiteral,
863
- "...config",
864
- ...buildParamsRemap({ node })
701
+ "...config"
865
702
  ].filter(Boolean).join(", ")} }`;
866
703
  }
867
704
  /**
@@ -987,14 +824,13 @@ function Operation({ name, node, tsResolver, zodResolver, validator, security, i
987
824
  const validatorLiteral = validatorEntries.length ? `validator: { ${validatorEntries.join(", ")} }` : null;
988
825
  const callConfig = `{ ${[
989
826
  `method: '${node.method.toUpperCase()}'`,
990
- `url: '${Url.toCasedTemplate(node.path, { casing: "camelcase" })}'`,
827
+ `url: '${node.path}'`,
991
828
  securityLiteral ? `security: ${securityLiteral}` : null,
992
829
  stylesLiteral ? `styles: ${stylesLiteral}` : null,
993
830
  validatorLiteral,
994
831
  contentTypeLiteral,
995
832
  responseTypeLiteral,
996
- "...config",
997
- ...buildParamsRemap({ node })
833
+ "...config"
998
834
  ].filter(Boolean).join(", ")} }`;
999
835
  const eventType = `SuccessOf<${tsResolver.response.responses(node)}>`;
1000
836
  const returnType = eventStream ? `Promise<EventStreamResult<${eventType}>>` : signature.returnType;
@@ -1058,6 +894,125 @@ function SdkClient({ name, isExportable = true, isIndexable = true, operations,
1058
894
  });
1059
895
  }
1060
896
  //#endregion
897
+ //#region ../../internals/client/src/generators/clientGenerator.tsx
898
+ /**
899
+ * Builds the built-in per-operation generator shared by the client plugins (`@kubb/plugin-fetch`,
900
+ * `@kubb/plugin-axios`). Emits one async function per OpenAPI operation using the shared
901
+ * `Operation` component: a grouped `<Name>Request` type and a function that forwards a single
902
+ * `options` object to the bundled `client` and returns the `RequestResult`. Only the generator
903
+ * `name` differs between plugins; every other resolution, import, and rendering step is identical.
904
+ */
905
+ function createClientGenerator(name) {
906
+ return (0, kubb_kit.defineGenerator)({
907
+ name,
908
+ renderer: kubb_jsx.jsxRenderer,
909
+ operation(node, ctx) {
910
+ if (!kubb_kit.ast.isHttpOperationNode(node)) return null;
911
+ const { config, driver, resolver, root } = ctx;
912
+ const { output, validator, group } = ctx.options;
913
+ const pluginTs = driver.getPlugin(_kubb_plugin_ts.pluginTsName);
914
+ if (!pluginTs) return null;
915
+ const tsResolver = driver.getResolver(_kubb_plugin_ts.pluginTsName);
916
+ const pluginZod = resolveResponseValidator(validator) === "zod" || resolveRequestValidator(validator) === "zod" ? driver.getPlugin(_kubb_plugin_zod.pluginZodName) : null;
917
+ const zodResolver = pluginZod ? driver.getResolver(_kubb_plugin_zod.pluginZodName) : null;
918
+ const hasRequestBody = Boolean(node.requestBody?.content?.[0]?.schema);
919
+ const importedTypeNames = [tsResolver.response.options(node), tsResolver.response.responses(node)];
920
+ const importedZodNames = zodResolver ? [
921
+ resolveResponseValidator(validator) === "zod" ? zodResolver.response.response?.(node) : null,
922
+ resolveResponseValidator(validator) === "zod" ? buildZodErrorParse(node, zodResolver)?.expression ?? null : null,
923
+ resolveRequestValidator(validator) === "zod" && hasRequestBody ? zodResolver.response.body?.(node) : null
924
+ ].filter((name) => Boolean(name)) : [];
925
+ const meta = {
926
+ name: resolver.name(node.operationId),
927
+ file: resolver.file({
928
+ ...operationFileEntry(node, node.operationId),
929
+ root,
930
+ output,
931
+ group: group ?? void 0
932
+ }),
933
+ fileTs: resolveDependencyOperationFile({
934
+ cache: ctx.cache,
935
+ node,
936
+ resolver: tsResolver,
937
+ root,
938
+ output: pluginTs.options?.output ?? output,
939
+ group: pluginTs.options?.group
940
+ }),
941
+ fileZod: zodResolver && pluginZod?.options ? zodResolver.file({
942
+ ...operationFileEntry(node, node.operationId),
943
+ root,
944
+ output: pluginZod.options.output ?? output,
945
+ group: pluginZod.options?.group ?? void 0
946
+ }) : null
947
+ };
948
+ const security = getOperationSecurity({
949
+ document: ctx.adapter.document,
950
+ method: node.method,
951
+ path: node.path
952
+ });
953
+ const clientPath = node_path.default.resolve(root, ".kubb/client.ts");
954
+ const eventStream = isEventStream(node);
955
+ return /* @__PURE__ */ (0, kubb_jsx_jsx_runtime.jsxs)(kubb_jsx.File, {
956
+ baseName: meta.file.baseName,
957
+ path: meta.file.path,
958
+ meta: meta.file.meta,
959
+ banner: resolver.default.banner(ctx.meta, {
960
+ output,
961
+ config,
962
+ file: {
963
+ path: meta.file.path,
964
+ baseName: meta.file.baseName
965
+ }
966
+ }),
967
+ footer: resolver.default.footer(ctx.meta, {
968
+ output,
969
+ config,
970
+ file: {
971
+ path: meta.file.path,
972
+ baseName: meta.file.baseName
973
+ }
974
+ }),
975
+ children: [
976
+ /* @__PURE__ */ (0, kubb_jsx_jsx_runtime.jsx)(kubb_jsx.File.Import, {
977
+ name: eventStream ? ["client", "toEventStream"] : ["client"],
978
+ root: meta.file.path,
979
+ path: clientPath
980
+ }),
981
+ /* @__PURE__ */ (0, kubb_jsx_jsx_runtime.jsx)(kubb_jsx.File.Import, {
982
+ name: eventStream ? [
983
+ "Options",
984
+ "EventStreamResult",
985
+ "SuccessOf"
986
+ ] : ["Options", "RequestResult"],
987
+ root: meta.file.path,
988
+ path: clientPath,
989
+ isTypeOnly: true
990
+ }),
991
+ meta.fileTs && importedTypeNames.length > 0 && /* @__PURE__ */ (0, kubb_jsx_jsx_runtime.jsx)(kubb_jsx.File.Import, {
992
+ name: Array.from(new Set(importedTypeNames)),
993
+ root: meta.file.path,
994
+ path: meta.fileTs.path,
995
+ isTypeOnly: true
996
+ }),
997
+ meta.fileZod && importedZodNames.length > 0 && /* @__PURE__ */ (0, kubb_jsx_jsx_runtime.jsx)(kubb_jsx.File.Import, {
998
+ name: importedZodNames,
999
+ root: meta.file.path,
1000
+ path: meta.fileZod.path
1001
+ }),
1002
+ /* @__PURE__ */ (0, kubb_jsx_jsx_runtime.jsx)(Operation, {
1003
+ name: meta.name,
1004
+ node,
1005
+ tsResolver,
1006
+ zodResolver,
1007
+ validator,
1008
+ security
1009
+ })
1010
+ ]
1011
+ });
1012
+ }
1013
+ });
1014
+ }
1015
+ //#endregion
1061
1016
  //#region ../../internals/client/src/components/SdkFacade.tsx
1062
1017
  /**
1063
1018
  * Renders a composed root SDK class that instantiates every tag client from one shared config, so
@@ -1087,7 +1042,7 @@ function resolveTypeImportNames(node, tsResolver) {
1087
1042
  return [tsResolver.response.options(node), tsResolver.response.responses(node)];
1088
1043
  }
1089
1044
  function resolveZodImportNames(node, zodResolver, validator) {
1090
- const { query: queryParams } = getOperationParameters(node, { paramsCasing: "original" });
1045
+ const { query: queryParams } = getOperationParameters(node);
1091
1046
  return [
1092
1047
  resolveResponseValidator(validator) === "zod" ? zodResolver.response.response(node) : null,
1093
1048
  resolveResponseValidator(validator) === "zod" ? buildZodErrorParse(node, zodResolver)?.expression ?? null : null,
@@ -1327,7 +1282,7 @@ function createSdkGenerator() {
1327
1282
  * drops union members a broader scalar already covers, keeping the generated response and error
1328
1283
  * unions tidy. A plugin wires them with `ctx.setMacros([...defaultMacros, ...userMacros])`.
1329
1284
  */
1330
- const defaultMacros = [kubb_kit.ast.macroSimplifyUnion];
1285
+ const defaultMacros = [kubb_kit.macroSimplifyUnion];
1331
1286
  //#endregion
1332
1287
  //#region ../../internals/client/src/resolver.ts
1333
1288
  /**
@@ -1356,115 +1311,10 @@ const resolverClient = (0, kubb_kit.createResolver)({
1356
1311
  //#region src/generators/clientGenerator.tsx
1357
1312
  /**
1358
1313
  * Built-in operation generator for `@kubb/plugin-fetch`. Emits one async function per OpenAPI
1359
- * operation using the shared `Operation` component: a grouped `<Name>Request` type and a function that
1360
- * forwards a single `options` object to the bundled `client` and returns the `RequestResult`.
1314
+ * operation using the shared `Operation` component: a grouped `<Name>Request` type and a function
1315
+ * that forwards a single `options` object to the bundled `client` and returns the `RequestResult`.
1361
1316
  */
1362
- const clientGenerator = (0, kubb_kit.defineGenerator)({
1363
- name: "fetch",
1364
- renderer: kubb_jsx.jsxRenderer,
1365
- operation(node, ctx) {
1366
- if (!kubb_kit.ast.isHttpOperationNode(node)) return null;
1367
- const { config, driver, resolver, root } = ctx;
1368
- const { output, validator, group } = ctx.options;
1369
- const pluginTs = driver.getPlugin(_kubb_plugin_ts.pluginTsName);
1370
- if (!pluginTs) return null;
1371
- const tsResolver = driver.getResolver(_kubb_plugin_ts.pluginTsName);
1372
- const pluginZod = resolveResponseValidator(validator) === "zod" || resolveRequestValidator(validator) === "zod" ? driver.getPlugin(_kubb_plugin_zod.pluginZodName) : null;
1373
- const zodResolver = pluginZod ? driver.getResolver(_kubb_plugin_zod.pluginZodName) : null;
1374
- const hasRequestBody = Boolean(node.requestBody?.content?.[0]?.schema);
1375
- const importedTypeNames = [tsResolver.response.options(node), tsResolver.response.responses(node)];
1376
- const importedZodNames = zodResolver ? [
1377
- resolveResponseValidator(validator) === "zod" ? zodResolver.response.response?.(node) : null,
1378
- resolveResponseValidator(validator) === "zod" ? buildZodErrorParse(node, zodResolver)?.expression ?? null : null,
1379
- resolveRequestValidator(validator) === "zod" && hasRequestBody ? zodResolver.response.body?.(node) : null
1380
- ].filter((name) => Boolean(name)) : [];
1381
- const meta = {
1382
- name: resolver.name(node.operationId),
1383
- file: resolver.file({
1384
- ...operationFileEntry(node, node.operationId),
1385
- root,
1386
- output,
1387
- group: group ?? void 0
1388
- }),
1389
- fileTs: tsResolver.file({
1390
- ...operationFileEntry(node, node.operationId),
1391
- root,
1392
- output: pluginTs.options?.output ?? output,
1393
- group: pluginTs.options?.group ?? void 0
1394
- }),
1395
- fileZod: zodResolver && pluginZod?.options ? zodResolver.file({
1396
- ...operationFileEntry(node, node.operationId),
1397
- root,
1398
- output: pluginZod.options.output ?? output,
1399
- group: pluginZod.options?.group ?? void 0
1400
- }) : null
1401
- };
1402
- const security = getOperationSecurity({
1403
- document: ctx.adapter.document,
1404
- method: node.method,
1405
- path: node.path
1406
- });
1407
- const clientPath = node_path.default.resolve(root, ".kubb/client.ts");
1408
- const eventStream = isEventStream(node);
1409
- return /* @__PURE__ */ (0, kubb_jsx_jsx_runtime.jsxs)(kubb_jsx.File, {
1410
- baseName: meta.file.baseName,
1411
- path: meta.file.path,
1412
- meta: meta.file.meta,
1413
- banner: resolver.default.banner(ctx.meta, {
1414
- output,
1415
- config,
1416
- file: {
1417
- path: meta.file.path,
1418
- baseName: meta.file.baseName
1419
- }
1420
- }),
1421
- footer: resolver.default.footer(ctx.meta, {
1422
- output,
1423
- config,
1424
- file: {
1425
- path: meta.file.path,
1426
- baseName: meta.file.baseName
1427
- }
1428
- }),
1429
- children: [
1430
- /* @__PURE__ */ (0, kubb_jsx_jsx_runtime.jsx)(kubb_jsx.File.Import, {
1431
- name: eventStream ? ["client", "toEventStream"] : ["client"],
1432
- root: meta.file.path,
1433
- path: clientPath
1434
- }),
1435
- /* @__PURE__ */ (0, kubb_jsx_jsx_runtime.jsx)(kubb_jsx.File.Import, {
1436
- name: eventStream ? [
1437
- "Options",
1438
- "EventStreamResult",
1439
- "SuccessOf"
1440
- ] : ["Options", "RequestResult"],
1441
- root: meta.file.path,
1442
- path: clientPath,
1443
- isTypeOnly: true
1444
- }),
1445
- meta.fileTs && importedTypeNames.length > 0 && /* @__PURE__ */ (0, kubb_jsx_jsx_runtime.jsx)(kubb_jsx.File.Import, {
1446
- name: Array.from(new Set(importedTypeNames)),
1447
- root: meta.file.path,
1448
- path: meta.fileTs.path,
1449
- isTypeOnly: true
1450
- }),
1451
- meta.fileZod && importedZodNames.length > 0 && /* @__PURE__ */ (0, kubb_jsx_jsx_runtime.jsx)(kubb_jsx.File.Import, {
1452
- name: importedZodNames,
1453
- root: meta.file.path,
1454
- path: meta.fileZod.path
1455
- }),
1456
- /* @__PURE__ */ (0, kubb_jsx_jsx_runtime.jsx)(Operation, {
1457
- name: meta.name,
1458
- node,
1459
- tsResolver,
1460
- zodResolver,
1461
- validator,
1462
- security
1463
- })
1464
- ]
1465
- });
1466
- }
1467
- });
1317
+ const clientGenerator = createClientGenerator("fetch");
1468
1318
  //#endregion
1469
1319
  //#region src/templates.ts
1470
1320
  /** Absolute path to the fetch client template, copied into `.kubb/client.ts`. */