@kubb/plugin-fetch 5.0.0-beta.99 → 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 +333 -359
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.ts +3 -3
- package/dist/index.js +332 -358
- package/dist/index.js.map +1 -1
- package/package.json +7 -10
- package/src/generators/clientGenerator.tsx +4 -101
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,252 +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
|
-
/**
|
|
232
|
-
* Keeps the OpenAPI parameter name as-is when it is already a valid JS identifier, and
|
|
233
|
-
* camelCases it only enough to become one otherwise (for example a hyphenated path segment).
|
|
234
|
-
*/
|
|
235
|
-
function transformParam(raw) {
|
|
236
|
-
return isValidVarName(raw) ? raw : camelCase(raw);
|
|
237
|
-
}
|
|
238
|
-
/**
|
|
239
|
-
* Helpers for OpenAPI/Swagger paths.
|
|
240
|
-
*/
|
|
241
|
-
var Url = class Url {
|
|
242
|
-
/**
|
|
243
|
-
* Converts an OpenAPI/Swagger path to Express-style colon syntax.
|
|
244
|
-
*
|
|
245
|
-
* @example
|
|
246
|
-
* Url.toPath('/pet/{petId}') // '/pet/:petId'
|
|
247
|
-
*/
|
|
248
|
-
static toPath(path) {
|
|
249
|
-
return path.replace(/\{([^}]+)\}/g, ":$1");
|
|
250
|
-
}
|
|
251
|
-
/**
|
|
252
|
-
* Rewrites OpenAPI placeholder names while keeping the `{...}` braces, so the generated `url`
|
|
253
|
-
* literal aligns with the grouped `path` request option that the runtime client interpolates by
|
|
254
|
-
* key.
|
|
255
|
-
*
|
|
256
|
-
* @example
|
|
257
|
-
* Url.toSafeTemplate('/user/{monetary-account-id}') // '/user/{monetaryAccountId}'
|
|
258
|
-
*/
|
|
259
|
-
static toSafeTemplate(path) {
|
|
260
|
-
return path.replace(/\{([^}]+)\}/g, (_, name) => `{${transformParam(name)}}`);
|
|
261
|
-
}
|
|
262
|
-
/**
|
|
263
|
-
* Converts an OpenAPI/Swagger path to a TypeScript template literal string.
|
|
264
|
-
* `prefix` is prepended inside the literal, and `replacer` transforms each parameter name.
|
|
265
|
-
*
|
|
266
|
-
* @example
|
|
267
|
-
* Url.toTemplateString('/pet/{petId}') // '`/pet/${petId}`'
|
|
268
|
-
*
|
|
269
|
-
* @example
|
|
270
|
-
* Url.toTemplateString('/pet/{petId}', { prefix: 'https://api' }) // '`https://api/pet/${petId}`'
|
|
271
|
-
*/
|
|
272
|
-
static toTemplateString(path, { prefix, replacer } = {}) {
|
|
273
|
-
const result = path.split(/\{([^}]+)\}/).map((part, i) => {
|
|
274
|
-
if (i % 2 === 0) return part;
|
|
275
|
-
const param = transformParam(part);
|
|
276
|
-
return `\${${replacer ? replacer(param) : param}}`;
|
|
277
|
-
}).join("");
|
|
278
|
-
return `\`${prefix ?? ""}${result}\``;
|
|
279
|
-
}
|
|
280
|
-
/**
|
|
281
|
-
* Converts an OpenAPI/Swagger path to a template literal that reads each parameter off the
|
|
282
|
-
* grouped `path` request option, e.g. `/pet/{petId}` becomes `` `/pet/${path.petId}` ``. Parameter
|
|
283
|
-
* names match the generated `path` type, and `prefix` is prepended inside the literal. Shared by
|
|
284
|
-
* the client and cypress generators that pass a grouped `path` object.
|
|
285
|
-
*
|
|
286
|
-
* @example
|
|
287
|
-
* Url.toGroupedTemplateString('/pet/{petId}') // '`/pet/${path.petId}`'
|
|
288
|
-
*/
|
|
289
|
-
static toGroupedTemplateString(path, { prefix } = {}) {
|
|
290
|
-
return Url.toTemplateString(path, {
|
|
291
|
-
prefix,
|
|
292
|
-
replacer: (name) => `path.${name}`
|
|
293
|
-
});
|
|
294
|
-
}
|
|
295
|
-
};
|
|
296
|
-
//#endregion
|
|
297
|
-
//#region ../../internals/shared/src/params.ts
|
|
298
|
-
/**
|
|
299
|
-
* Drops parameters that share the same name, keeping the first.
|
|
300
|
-
*
|
|
301
|
-
* A malformed spec can declare the same parameter name twice within one `in` location. Both would
|
|
302
|
-
* resolve to the same output property, so emitting both would yield an object type with a duplicate
|
|
303
|
-
* member, which TypeScript rejects. This is a defensive guard against that case, not a casing guard:
|
|
304
|
-
* parameter names flow through unchanged, so no two distinct names ever collide here anymore.
|
|
305
|
-
*/
|
|
306
|
-
function dedupeParams(params) {
|
|
307
|
-
const seen = /* @__PURE__ */ new Set();
|
|
308
|
-
return params.filter((param) => {
|
|
309
|
-
if (seen.has(param.name)) return false;
|
|
310
|
-
seen.add(param.name);
|
|
311
|
-
return true;
|
|
312
|
-
});
|
|
313
|
-
}
|
|
314
|
-
//#endregion
|
|
315
|
-
//#region ../../internals/shared/src/operation.ts
|
|
316
|
-
/**
|
|
317
|
-
* Builds the `ResolverFileParams` every operation generator passes to
|
|
318
|
-
* `resolver.file`: a file named `name`, tagged by the operation's first
|
|
319
|
-
* tag (or `'default'`), at the operation's path. Centralizes the entry object
|
|
320
|
-
* that was repeated at dozens of call sites across the client and query plugins.
|
|
321
|
-
*
|
|
322
|
-
* @example
|
|
323
|
-
* ```ts
|
|
324
|
-
* resolver.file(operationFileEntry(node, node.operationId), { root, output, group })
|
|
325
|
-
* ```
|
|
326
|
-
*/
|
|
327
|
-
function operationFileEntry(node, name, extname = ".ts") {
|
|
328
|
-
return {
|
|
329
|
-
name,
|
|
330
|
-
extname,
|
|
331
|
-
tag: node.tags[0] ?? "default",
|
|
332
|
-
path: node.path
|
|
333
|
-
};
|
|
334
|
-
}
|
|
335
|
-
function getOperationLink(node, link) {
|
|
336
|
-
if (!link) return null;
|
|
337
|
-
if (typeof link === "function") return link(node) ?? null;
|
|
338
|
-
if (link === "urlPath") return node.path ? `{@link ${Url.toPath(node.path)}}` : null;
|
|
339
|
-
return node.path ? `{@link ${node.path.replaceAll("{", ":").replaceAll("}", "")}}` : null;
|
|
340
|
-
}
|
|
341
|
-
/**
|
|
342
|
-
* Derives the shared `ContentTypeInfo` shape from a list of content types, tracking whether several
|
|
343
|
-
* are present and the union, default, and form-data flags the client uses to pick one.
|
|
344
|
-
*/
|
|
345
|
-
function buildContentTypeInfo(contentTypes) {
|
|
346
|
-
const isMultipleContentTypes = contentTypes.length > 1;
|
|
347
|
-
return {
|
|
348
|
-
contentTypes,
|
|
349
|
-
isMultipleContentTypes,
|
|
350
|
-
contentTypeUnion: isMultipleContentTypes ? contentTypes.map((ct) => JSON.stringify(ct)).join(" | ") : "",
|
|
351
|
-
defaultContentType: contentTypes[0] ?? "application/json",
|
|
352
|
-
hasFormData: contentTypes.some((ct) => ct === "multipart/form-data")
|
|
353
|
-
};
|
|
354
|
-
}
|
|
355
|
-
function getContentTypeInfo(node) {
|
|
356
|
-
return buildContentTypeInfo(node.requestBody?.content?.map((e) => e.contentType) ?? []);
|
|
357
|
-
}
|
|
358
|
-
/**
|
|
359
|
-
* The request-body counterpart for the primary success response: the content types it documents and
|
|
360
|
-
* whether several are present, so the client can let a caller pick which one to accept.
|
|
361
|
-
*/
|
|
362
|
-
function getResponseContentTypeInfo(node) {
|
|
363
|
-
return buildContentTypeInfo(getPrimarySuccessResponse(node)?.content?.map((e) => e.contentType) ?? []);
|
|
364
|
-
}
|
|
365
|
-
/**
|
|
366
|
-
* Reads the single base content type of an operation's primary success response, lowercased and
|
|
367
|
-
* stripped of any `; charset=...` suffix. Returns `undefined` when the response declares zero or
|
|
368
|
-
* more than one content type, since neither case has a single type to act on.
|
|
369
|
-
*/
|
|
370
|
-
function getPrimarySuccessContentType(node) {
|
|
371
|
-
const contentTypes = getPrimarySuccessResponse(node)?.content?.map((entry) => entry.contentType) ?? [];
|
|
372
|
-
if (contentTypes.length !== 1) return void 0;
|
|
373
|
-
return contentTypes[0].split(";")[0].trim().toLowerCase();
|
|
374
|
-
}
|
|
375
|
-
/**
|
|
376
|
-
* Whether an operation streams its primary success response as Server-Sent Events
|
|
377
|
-
* (`text/event-stream`). The client generator uses this to return a typed event stream instead of a
|
|
378
|
-
* one-shot `RequestResult`.
|
|
379
|
-
*/
|
|
380
|
-
function isEventStream(node) {
|
|
381
|
-
return getPrimarySuccessContentType(node) === "text/event-stream";
|
|
382
|
-
}
|
|
383
|
-
/**
|
|
384
|
-
* Derives the default `responseType` for an operation from its primary success response.
|
|
385
|
-
*
|
|
386
|
-
* Returns a value only when that response declares a single non-JSON content type. `text/event-stream`
|
|
387
|
-
* and other binary types (`application/octet-stream`, `application/pdf`, `image/*`, `audio/*`,
|
|
388
|
-
* `video/*`) map to a stream or `'blob'`, and other `text/*` maps to `'text'`. Otherwise `undefined`,
|
|
389
|
-
* leaving the runtime client's `Content-Type` auto-detection in charge.
|
|
390
|
-
*/
|
|
391
|
-
function getResponseType(node) {
|
|
392
|
-
const baseType = getPrimarySuccessContentType(node);
|
|
393
|
-
if (!baseType) return void 0;
|
|
394
|
-
if (baseType === "application/json" || baseType.endsWith("+json") || baseType === "text/json") return void 0;
|
|
395
|
-
if (baseType === "text/event-stream") return "stream";
|
|
396
|
-
if (baseType.startsWith("text/")) return "text";
|
|
397
|
-
if (baseType === "application/octet-stream" || baseType === "application/pdf" || /^(image|audio|video)\//.test(baseType)) return "blob";
|
|
398
|
-
}
|
|
399
|
-
/**
|
|
400
|
-
* Which of the grouped request options an operation carries.
|
|
401
|
-
*/
|
|
402
|
-
function getRequestGroups(node) {
|
|
403
|
-
const { path, query, header } = getOperationParameters(node);
|
|
404
|
-
return {
|
|
405
|
-
path: path.length > 0,
|
|
406
|
-
query: query.length > 0,
|
|
407
|
-
body: Boolean(node.requestBody?.content?.[0]?.schema),
|
|
408
|
-
headers: header.length > 0
|
|
409
|
-
};
|
|
410
|
-
}
|
|
411
|
-
/**
|
|
412
|
-
* Resolves which grouped request options an operation carries together with whether each group
|
|
413
|
-
* holds a required member. The grouped parameter stays optional only when nothing inside it is
|
|
414
|
-
* required, matching the generated `RequestConfig` type.
|
|
415
|
-
*/
|
|
416
|
-
function getRequestGroupOptionality(node) {
|
|
417
|
-
const groups = getRequestGroups(node);
|
|
418
|
-
const { path, query, header } = getOperationParameters(node);
|
|
419
|
-
const hasRequiredPath = path.some((param) => param.required);
|
|
420
|
-
const hasRequiredQuery = query.some((param) => param.required);
|
|
421
|
-
const hasRequiredHeader = header.some((param) => param.required);
|
|
422
|
-
return {
|
|
423
|
-
groups,
|
|
424
|
-
hasRequiredPath,
|
|
425
|
-
hasRequiredQuery,
|
|
426
|
-
hasRequiredHeader,
|
|
427
|
-
isOptional: !hasRequiredPath && !hasRequiredQuery && !hasRequiredHeader && !groups.body
|
|
428
|
-
};
|
|
429
|
-
}
|
|
430
|
-
function buildOperationComments(node, options = {}) {
|
|
431
|
-
const { link = "pathTemplate", linkPosition = "afterDeprecated", splitLines = false } = options;
|
|
432
|
-
const linkComment = getOperationLink(node, link);
|
|
433
|
-
const filteredComments = (linkPosition === "beforeDeprecated" ? [
|
|
434
|
-
node.description && `@description ${node.description}`,
|
|
435
|
-
node.summary && `@summary ${node.summary}`,
|
|
436
|
-
linkComment,
|
|
437
|
-
node.deprecated && "@deprecated"
|
|
438
|
-
] : [
|
|
439
|
-
node.description && `@description ${node.description}`,
|
|
440
|
-
node.summary && `@summary ${node.summary}`,
|
|
441
|
-
node.deprecated && "@deprecated",
|
|
442
|
-
linkComment
|
|
443
|
-
]).filter((comment) => Boolean(comment));
|
|
444
|
-
if (!splitLines) return filteredComments;
|
|
445
|
-
return filteredComments.flatMap((text) => text.split(/\r?\n/).map((line) => line.trim())).filter((comment) => Boolean(comment));
|
|
446
|
-
}
|
|
447
|
-
function getOperationParameters(node) {
|
|
448
|
-
return {
|
|
449
|
-
path: dedupeParams(node.parameters.filter((param) => param.in === "path").map((param) => isValidVarName(param.name) ? param : {
|
|
450
|
-
...param,
|
|
451
|
-
name: camelCase(param.name)
|
|
452
|
-
})),
|
|
453
|
-
query: dedupeParams(node.parameters.filter((param) => param.in === "query")),
|
|
454
|
-
header: dedupeParams(node.parameters.filter((param) => param.in === "header")),
|
|
455
|
-
cookie: dedupeParams(node.parameters.filter((param) => param.in === "cookie"))
|
|
456
|
-
};
|
|
457
|
-
}
|
|
458
|
-
function getStatusCodeNumber(statusCode) {
|
|
459
|
-
const code = Number(statusCode);
|
|
460
|
-
return Number.isNaN(code) ? null : code;
|
|
461
|
-
}
|
|
462
|
-
function isSuccessStatusCode(statusCode) {
|
|
463
|
-
const code = getStatusCodeNumber(statusCode);
|
|
464
|
-
return code !== null && code >= 200 && code < 300;
|
|
465
|
-
}
|
|
466
|
-
function getSuccessResponses(responses) {
|
|
467
|
-
return responses.filter((response) => isSuccessStatusCode(response.statusCode));
|
|
468
|
-
}
|
|
469
|
-
function getOperationSuccessResponses(node) {
|
|
470
|
-
return getSuccessResponses(node.responses);
|
|
471
|
-
}
|
|
472
|
-
function getPrimarySuccessResponse(node) {
|
|
473
|
-
return getOperationSuccessResponses(node)[0] ?? null;
|
|
474
|
-
}
|
|
475
|
-
//#endregion
|
|
476
436
|
//#region ../../internals/shared/src/group.ts
|
|
477
437
|
/**
|
|
478
438
|
* Builds the `group` config a Kubb plugin passes to `ctx.setOptions`, applying the
|
|
@@ -735,7 +695,7 @@ function buildCallConfig({ node, validator, zodResolver, security }) {
|
|
|
735
695
|
const securityLiteral = buildSecurityMetadata({ security });
|
|
736
696
|
return `{ ${[
|
|
737
697
|
`method: '${node.method.toUpperCase()}'`,
|
|
738
|
-
`url: '${
|
|
698
|
+
`url: '${node.path}'`,
|
|
739
699
|
securityLiteral ? `security: ${securityLiteral}` : null,
|
|
740
700
|
validatorLiteral,
|
|
741
701
|
"...config"
|
|
@@ -864,7 +824,7 @@ function Operation({ name, node, tsResolver, zodResolver, validator, security, i
|
|
|
864
824
|
const validatorLiteral = validatorEntries.length ? `validator: { ${validatorEntries.join(", ")} }` : null;
|
|
865
825
|
const callConfig = `{ ${[
|
|
866
826
|
`method: '${node.method.toUpperCase()}'`,
|
|
867
|
-
`url: '${
|
|
827
|
+
`url: '${node.path}'`,
|
|
868
828
|
securityLiteral ? `security: ${securityLiteral}` : null,
|
|
869
829
|
stylesLiteral ? `styles: ${stylesLiteral}` : null,
|
|
870
830
|
validatorLiteral,
|
|
@@ -934,6 +894,125 @@ function SdkClient({ name, isExportable = true, isIndexable = true, operations,
|
|
|
934
894
|
});
|
|
935
895
|
}
|
|
936
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
|
|
937
1016
|
//#region ../../internals/client/src/components/SdkFacade.tsx
|
|
938
1017
|
/**
|
|
939
1018
|
* Renders a composed root SDK class that instantiates every tag client from one shared config, so
|
|
@@ -1203,7 +1282,7 @@ function createSdkGenerator() {
|
|
|
1203
1282
|
* drops union members a broader scalar already covers, keeping the generated response and error
|
|
1204
1283
|
* unions tidy. A plugin wires them with `ctx.setMacros([...defaultMacros, ...userMacros])`.
|
|
1205
1284
|
*/
|
|
1206
|
-
const defaultMacros = [kubb_kit.
|
|
1285
|
+
const defaultMacros = [kubb_kit.macroSimplifyUnion];
|
|
1207
1286
|
//#endregion
|
|
1208
1287
|
//#region ../../internals/client/src/resolver.ts
|
|
1209
1288
|
/**
|
|
@@ -1232,115 +1311,10 @@ const resolverClient = (0, kubb_kit.createResolver)({
|
|
|
1232
1311
|
//#region src/generators/clientGenerator.tsx
|
|
1233
1312
|
/**
|
|
1234
1313
|
* Built-in operation generator for `@kubb/plugin-fetch`. Emits one async function per OpenAPI
|
|
1235
|
-
* operation using the shared `Operation` component: a grouped `<Name>Request` type and a function
|
|
1236
|
-
* 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`.
|
|
1237
1316
|
*/
|
|
1238
|
-
const clientGenerator = (
|
|
1239
|
-
name: "fetch",
|
|
1240
|
-
renderer: kubb_jsx.jsxRenderer,
|
|
1241
|
-
operation(node, ctx) {
|
|
1242
|
-
if (!kubb_kit.ast.isHttpOperationNode(node)) return null;
|
|
1243
|
-
const { config, driver, resolver, root } = ctx;
|
|
1244
|
-
const { output, validator, group } = ctx.options;
|
|
1245
|
-
const pluginTs = driver.getPlugin(_kubb_plugin_ts.pluginTsName);
|
|
1246
|
-
if (!pluginTs) return null;
|
|
1247
|
-
const tsResolver = driver.getResolver(_kubb_plugin_ts.pluginTsName);
|
|
1248
|
-
const pluginZod = resolveResponseValidator(validator) === "zod" || resolveRequestValidator(validator) === "zod" ? driver.getPlugin(_kubb_plugin_zod.pluginZodName) : null;
|
|
1249
|
-
const zodResolver = pluginZod ? driver.getResolver(_kubb_plugin_zod.pluginZodName) : null;
|
|
1250
|
-
const hasRequestBody = Boolean(node.requestBody?.content?.[0]?.schema);
|
|
1251
|
-
const importedTypeNames = [tsResolver.response.options(node), tsResolver.response.responses(node)];
|
|
1252
|
-
const importedZodNames = zodResolver ? [
|
|
1253
|
-
resolveResponseValidator(validator) === "zod" ? zodResolver.response.response?.(node) : null,
|
|
1254
|
-
resolveResponseValidator(validator) === "zod" ? buildZodErrorParse(node, zodResolver)?.expression ?? null : null,
|
|
1255
|
-
resolveRequestValidator(validator) === "zod" && hasRequestBody ? zodResolver.response.body?.(node) : null
|
|
1256
|
-
].filter((name) => Boolean(name)) : [];
|
|
1257
|
-
const meta = {
|
|
1258
|
-
name: resolver.name(node.operationId),
|
|
1259
|
-
file: resolver.file({
|
|
1260
|
-
...operationFileEntry(node, node.operationId),
|
|
1261
|
-
root,
|
|
1262
|
-
output,
|
|
1263
|
-
group: group ?? void 0
|
|
1264
|
-
}),
|
|
1265
|
-
fileTs: tsResolver.file({
|
|
1266
|
-
...operationFileEntry(node, node.operationId),
|
|
1267
|
-
root,
|
|
1268
|
-
output: pluginTs.options?.output ?? output,
|
|
1269
|
-
group: pluginTs.options?.group ?? void 0
|
|
1270
|
-
}),
|
|
1271
|
-
fileZod: zodResolver && pluginZod?.options ? zodResolver.file({
|
|
1272
|
-
...operationFileEntry(node, node.operationId),
|
|
1273
|
-
root,
|
|
1274
|
-
output: pluginZod.options.output ?? output,
|
|
1275
|
-
group: pluginZod.options?.group ?? void 0
|
|
1276
|
-
}) : null
|
|
1277
|
-
};
|
|
1278
|
-
const security = getOperationSecurity({
|
|
1279
|
-
document: ctx.adapter.document,
|
|
1280
|
-
method: node.method,
|
|
1281
|
-
path: node.path
|
|
1282
|
-
});
|
|
1283
|
-
const clientPath = node_path.default.resolve(root, ".kubb/client.ts");
|
|
1284
|
-
const eventStream = isEventStream(node);
|
|
1285
|
-
return /* @__PURE__ */ (0, kubb_jsx_jsx_runtime.jsxs)(kubb_jsx.File, {
|
|
1286
|
-
baseName: meta.file.baseName,
|
|
1287
|
-
path: meta.file.path,
|
|
1288
|
-
meta: meta.file.meta,
|
|
1289
|
-
banner: resolver.default.banner(ctx.meta, {
|
|
1290
|
-
output,
|
|
1291
|
-
config,
|
|
1292
|
-
file: {
|
|
1293
|
-
path: meta.file.path,
|
|
1294
|
-
baseName: meta.file.baseName
|
|
1295
|
-
}
|
|
1296
|
-
}),
|
|
1297
|
-
footer: resolver.default.footer(ctx.meta, {
|
|
1298
|
-
output,
|
|
1299
|
-
config,
|
|
1300
|
-
file: {
|
|
1301
|
-
path: meta.file.path,
|
|
1302
|
-
baseName: meta.file.baseName
|
|
1303
|
-
}
|
|
1304
|
-
}),
|
|
1305
|
-
children: [
|
|
1306
|
-
/* @__PURE__ */ (0, kubb_jsx_jsx_runtime.jsx)(kubb_jsx.File.Import, {
|
|
1307
|
-
name: eventStream ? ["client", "toEventStream"] : ["client"],
|
|
1308
|
-
root: meta.file.path,
|
|
1309
|
-
path: clientPath
|
|
1310
|
-
}),
|
|
1311
|
-
/* @__PURE__ */ (0, kubb_jsx_jsx_runtime.jsx)(kubb_jsx.File.Import, {
|
|
1312
|
-
name: eventStream ? [
|
|
1313
|
-
"Options",
|
|
1314
|
-
"EventStreamResult",
|
|
1315
|
-
"SuccessOf"
|
|
1316
|
-
] : ["Options", "RequestResult"],
|
|
1317
|
-
root: meta.file.path,
|
|
1318
|
-
path: clientPath,
|
|
1319
|
-
isTypeOnly: true
|
|
1320
|
-
}),
|
|
1321
|
-
meta.fileTs && importedTypeNames.length > 0 && /* @__PURE__ */ (0, kubb_jsx_jsx_runtime.jsx)(kubb_jsx.File.Import, {
|
|
1322
|
-
name: Array.from(new Set(importedTypeNames)),
|
|
1323
|
-
root: meta.file.path,
|
|
1324
|
-
path: meta.fileTs.path,
|
|
1325
|
-
isTypeOnly: true
|
|
1326
|
-
}),
|
|
1327
|
-
meta.fileZod && importedZodNames.length > 0 && /* @__PURE__ */ (0, kubb_jsx_jsx_runtime.jsx)(kubb_jsx.File.Import, {
|
|
1328
|
-
name: importedZodNames,
|
|
1329
|
-
root: meta.file.path,
|
|
1330
|
-
path: meta.fileZod.path
|
|
1331
|
-
}),
|
|
1332
|
-
/* @__PURE__ */ (0, kubb_jsx_jsx_runtime.jsx)(Operation, {
|
|
1333
|
-
name: meta.name,
|
|
1334
|
-
node,
|
|
1335
|
-
tsResolver,
|
|
1336
|
-
zodResolver,
|
|
1337
|
-
validator,
|
|
1338
|
-
security
|
|
1339
|
-
})
|
|
1340
|
-
]
|
|
1341
|
-
});
|
|
1342
|
-
}
|
|
1343
|
-
});
|
|
1317
|
+
const clientGenerator = createClientGenerator("fetch");
|
|
1344
1318
|
//#endregion
|
|
1345
1319
|
//#region src/templates.ts
|
|
1346
1320
|
/** Absolute path to the fetch client template, copied into `.kubb/client.ts`. */
|