@kubb/plugin-fetch 5.14.0-canary.20261109T134631 → 6.0.1
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 +11 -1771
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +7 -239
- package/dist/index.d.ts +7 -239
- package/dist/index.js +12 -1749
- package/dist/index.js.map +1 -1
- package/dist/templates/client.d.ts +47 -2
- package/docs/guides/authentication.md +16 -2
- package/docs/guides/base-url.md +46 -2
- package/docs/guides/calling-operations.md +10 -3
- package/docs/guides/error-handling.md +37 -11
- package/docs/guides/interceptors.md +36 -14
- package/docs/guides/migrate-to-v6.md +235 -0
- package/docs/guides/serialization.md +119 -57
- package/docs/guides/server-sent-events.md +134 -31
- package/docs/guides/transport.md +29 -19
- package/docs/options.md +22 -10
- package/docs/overview.md +5 -3
- package/kubb-extension.json +3 -7
- package/package.json +8 -10
- package/src/index.ts +0 -1
- package/src/plugin.ts +7 -74
- package/src/types.ts +5 -2
- package/templates/client.ts +65 -84
- package/dist/client-Dn80AjdR.d.ts +0 -39
- package/dist/core-D6FAGTsL.d.ts +0 -2
- package/dist/templates/core.d.ts +0 -2
- package/dist/templates/fetch.d.ts +0 -6
- package/dist/templates/serializers.d.ts +0 -1
- package/dist/templates/standardSchema.d.ts +0 -1
- package/src/generators/clientGenerator.tsx +0 -10
- package/src/templates.ts +0 -16
- package/templates/core.ts +0 -2
- package/templates/fetch.ts +0 -2
- package/templates/serializers.ts +0 -2
- package/templates/standardSchema.ts +0 -2
package/dist/index.js
CHANGED
|
@@ -1,1709 +1,13 @@
|
|
|
1
|
-
import { createRequire } from "node:module";
|
|
2
|
-
import { Diagnostics, Resolver, Url, addGenerator, ast, camelCase, createResolver, defineExtension, defineGenerator, injectFile, macroDedupeUnion, pascalCase, setMacros, setResolver, useConfig } from "@kubb/kit";
|
|
3
|
-
import path from "node:path";
|
|
4
|
-
import { pluginZodName } from "@kubb/plugin-zod";
|
|
5
|
-
import { File, Function, jsxRenderer } from "@kubb/renderer-jsx";
|
|
6
|
-
import { createFunctionParameter, createFunctionParameters, functionPrinter, pluginTsName } from "@kubb/plugin-ts";
|
|
7
|
-
import { Fragment, jsx, jsxs } from "@kubb/renderer-jsx/jsx-runtime";
|
|
8
1
|
import { fileURLToPath } from "node:url";
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
* Builds the `ResolverFileParams` every operation generator passes to
|
|
12
|
-
* `resolver.file`: a file named `name`, tagged by the operation's first
|
|
13
|
-
* tag (or `'default'`), at the operation's path. Centralizes the entry object
|
|
14
|
-
* that was repeated at dozens of call sites across the client and query plugins.
|
|
15
|
-
*
|
|
16
|
-
* @example
|
|
17
|
-
* ```ts
|
|
18
|
-
* resolver.file(operationFileEntry(node, node.operationId), { root, output, group })
|
|
19
|
-
* ```
|
|
20
|
-
*/
|
|
21
|
-
function operationFileEntry(node, name, extname = ".ts") {
|
|
22
|
-
return {
|
|
23
|
-
name,
|
|
24
|
-
extname,
|
|
25
|
-
tag: node.tags[0] ?? "default",
|
|
26
|
-
path: node.path
|
|
27
|
-
};
|
|
28
|
-
}
|
|
29
|
-
/**
|
|
30
|
-
* Resolves a dependency plugin's generated file for `node.operationId`, cached in `memo` (the
|
|
31
|
-
* build's `ctx.memo`) under the resolver's own plugin name and the operation. Several dependents reading the
|
|
32
|
-
* same dependency for the same operation in one pass (a query plugin's several hook generators, the
|
|
33
|
-
* MCP handler, ...) share one computed name and path instead of each calling `resolver.file` again.
|
|
34
|
-
*
|
|
35
|
-
* @example Cache `plugin-ts`'s file for the current operation
|
|
36
|
-
* ```ts
|
|
37
|
-
* const fileTs = resolveDependencyOperationFile({ memo: ctx.memo, node, resolver: tsResolver, root, output })
|
|
38
|
-
* ```
|
|
39
|
-
*/
|
|
40
|
-
function resolveDependencyOperationFile(options) {
|
|
41
|
-
const { memo, node, resolver, root, output, group } = options;
|
|
42
|
-
return memo(`${resolver.pluginName}:operationFile:${node.operationId}`, () => resolver.file({
|
|
43
|
-
...operationFileEntry(node, node.operationId),
|
|
44
|
-
root,
|
|
45
|
-
output,
|
|
46
|
-
group: group ?? void 0
|
|
47
|
-
}));
|
|
48
|
-
}
|
|
49
|
-
function getOperationLink(node, link) {
|
|
50
|
-
if (!link) return null;
|
|
51
|
-
if (typeof link === "function") return link(node) ?? null;
|
|
52
|
-
return node.path ? `{@link ${Url.toPath(node.path)}}` : null;
|
|
53
|
-
}
|
|
54
|
-
/**
|
|
55
|
-
* Derives the shared `ContentTypeInfo` shape from a list of content types, tracking whether several
|
|
56
|
-
* are present and the union, default, and form-data flags the client uses to pick one.
|
|
57
|
-
*/
|
|
58
|
-
function buildContentTypeInfo(contentTypes) {
|
|
59
|
-
const isMultipleContentTypes = contentTypes.length > 1;
|
|
60
|
-
return {
|
|
61
|
-
contentTypes,
|
|
62
|
-
isMultipleContentTypes,
|
|
63
|
-
contentTypeUnion: isMultipleContentTypes ? contentTypes.map((ct) => JSON.stringify(ct)).join(" | ") : "",
|
|
64
|
-
defaultContentType: contentTypes[0] ?? "application/json",
|
|
65
|
-
hasFormData: contentTypes.some((ct) => ct === "multipart/form-data")
|
|
66
|
-
};
|
|
67
|
-
}
|
|
68
|
-
function getContentTypeInfo(node) {
|
|
69
|
-
return buildContentTypeInfo(node.requestBody?.content?.map((e) => e.contentType) ?? []);
|
|
70
|
-
}
|
|
71
|
-
/**
|
|
72
|
-
* The request-body counterpart for the primary success response: the content types it documents and
|
|
73
|
-
* whether several are present, so the client can let a caller pick which one to accept.
|
|
74
|
-
*/
|
|
75
|
-
function getResponseContentTypeInfo(node) {
|
|
76
|
-
return buildContentTypeInfo(getPrimarySuccessResponse(node)?.content?.map((e) => e.contentType) ?? []);
|
|
77
|
-
}
|
|
78
|
-
/**
|
|
79
|
-
* Reads the single base content type of an operation's primary success response, lowercased and
|
|
80
|
-
* stripped of any `; charset=...` suffix. Returns `undefined` when the response declares zero or
|
|
81
|
-
* more than one content type, since neither case has a single type to act on.
|
|
82
|
-
*/
|
|
83
|
-
function getPrimarySuccessContentType(node) {
|
|
84
|
-
const contentTypes = getPrimarySuccessResponse(node)?.content?.map((entry) => entry.contentType) ?? [];
|
|
85
|
-
if (contentTypes.length !== 1) return void 0;
|
|
86
|
-
return contentTypes[0].split(";")[0].trim().toLowerCase();
|
|
87
|
-
}
|
|
88
|
-
/**
|
|
89
|
-
* The `text/event-stream` content of the primary success response, when it is the only content type. A response that also
|
|
90
|
-
* declares another type stays a normal call with its query hooks.
|
|
91
|
-
*/
|
|
92
|
-
function getEventStreamContent(node) {
|
|
93
|
-
return getPrimarySuccessContentType(node) === "text/event-stream" ? getPrimarySuccessResponse(node)?.content?.[0] : void 0;
|
|
94
|
-
}
|
|
95
|
-
/**
|
|
96
|
-
* Whether an operation streams its primary success response as Server-Sent Events (`text/event-stream`). The client
|
|
97
|
-
* generator uses this to return a typed event stream instead of a one-shot `RequestResult`.
|
|
98
|
-
*/
|
|
99
|
-
function isEventStream(node) {
|
|
100
|
-
return getEventStreamContent(node) !== void 0;
|
|
101
|
-
}
|
|
102
|
-
/**
|
|
103
|
-
* How the document types a `text/event-stream` response, as the adapter read it from `itemSchema`: `event` when each item is
|
|
104
|
-
* the parsed event with its own `event` and `data`, `data` when it is only the payload. Returns `null` without an event stream.
|
|
105
|
-
*/
|
|
106
|
-
function getEventStream(node) {
|
|
107
|
-
const content = getEventStreamContent(node);
|
|
108
|
-
return content ? content.stream?.shape ?? "data" : null;
|
|
109
|
-
}
|
|
110
|
-
function baseContentType(contentType) {
|
|
111
|
-
return contentType.split(";")[0].trim().toLowerCase();
|
|
112
|
-
}
|
|
113
|
-
function isJsonContentType(baseType) {
|
|
114
|
-
return baseType === "application/json" || baseType.endsWith("+json") || baseType === "text/json";
|
|
115
|
-
}
|
|
116
|
-
/**
|
|
117
|
-
* Parser for one response content entry.
|
|
118
|
-
*
|
|
119
|
-
* JSON stays unset so the runtime detects it. `text/event-stream` is a stream. A binary schema
|
|
120
|
-
* (`format: binary` / `contentMediaType`) is a blob even when the media type is not a well-known
|
|
121
|
-
* binary type. Other `text/*` is text, and `application/octet-stream`, `application/pdf`,
|
|
122
|
-
* `image/*`, `audio/*`, and `video/*` are blobs.
|
|
123
|
-
*/
|
|
124
|
-
function responseTypeForContent(contentType, schema) {
|
|
125
|
-
const baseType = baseContentType(contentType);
|
|
126
|
-
if (!baseType) return void 0;
|
|
127
|
-
if (isJsonContentType(baseType)) return void 0;
|
|
128
|
-
if (baseType === "text/event-stream") return "stream";
|
|
129
|
-
if (schema?.type === "blob") return "blob";
|
|
130
|
-
if (baseType.startsWith("text/")) return "text";
|
|
131
|
-
if (baseType === "application/octet-stream" || baseType === "application/pdf" || /^(image|audio|video)\//.test(baseType)) return "blob";
|
|
132
|
-
}
|
|
133
|
-
/**
|
|
134
|
-
* Derives the default `responseType` string for an operation from its primary success response.
|
|
135
|
-
*
|
|
136
|
-
* One content type uses {@link responseTypeForContent}. Several content types that all use the same
|
|
137
|
-
* parser collapse to that string, so a call can keep `responseType: 'blob'`. Mixed parsers return
|
|
138
|
-
* `undefined`; {@link getResponseTypeMap} carries the per-type choice.
|
|
139
|
-
*/
|
|
140
|
-
function getResponseType(node) {
|
|
141
|
-
const entries = getPrimarySuccessResponse(node)?.content ?? [];
|
|
142
|
-
if (entries.length === 0) return void 0;
|
|
143
|
-
const responseTypes = entries.map((entry) => responseTypeForContent(entry.contentType, entry.schema));
|
|
144
|
-
const first = responseTypes[0];
|
|
145
|
-
if (!first) return void 0;
|
|
146
|
-
if (responseTypes.every((responseType) => responseType === first)) return first;
|
|
147
|
-
}
|
|
148
|
-
/**
|
|
149
|
-
* Maps each base content type of a primary success response to its parser when those parsers differ.
|
|
150
|
-
*
|
|
151
|
-
* Keys are lowercased and stripped of parameters. JSON entries are kept as `'json'` so a catch-all
|
|
152
|
-
* entry cannot claim a JSON response. Returns `undefined` when {@link getResponseType} already
|
|
153
|
-
* covers the response with one string, or when no content type needs a parser other than JSON.
|
|
154
|
-
* The runtime matches the response `Content-Type` against this map instead of guessing from the media type.
|
|
155
|
-
*/
|
|
156
|
-
function getResponseTypeMap(node) {
|
|
157
|
-
if (getResponseType(node)) return void 0;
|
|
158
|
-
const entries = getPrimarySuccessResponse(node)?.content ?? [];
|
|
159
|
-
if (entries.length <= 1) return void 0;
|
|
160
|
-
const map = {};
|
|
161
|
-
for (const entry of entries) {
|
|
162
|
-
const baseType = baseContentType(entry.contentType);
|
|
163
|
-
const responseType = responseTypeForContent(entry.contentType, entry.schema) ?? (isJsonContentType(baseType) ? "json" : void 0);
|
|
164
|
-
if (baseType && responseType) map[baseType] = responseType;
|
|
165
|
-
}
|
|
166
|
-
return Object.values(map).some((responseType) => responseType !== "json") ? map : void 0;
|
|
167
|
-
}
|
|
168
|
-
/**
|
|
169
|
-
* Which of the grouped request options an operation carries.
|
|
170
|
-
*/
|
|
171
|
-
function getRequestGroups(node) {
|
|
172
|
-
const { path, query, header } = getOperationParameters(node);
|
|
173
|
-
return {
|
|
174
|
-
path: path.length > 0,
|
|
175
|
-
query: query.length > 0,
|
|
176
|
-
body: Boolean(node.requestBody?.content?.[0]?.schema),
|
|
177
|
-
headers: header.length > 0
|
|
178
|
-
};
|
|
179
|
-
}
|
|
180
|
-
/**
|
|
181
|
-
* Resolves which grouped request options an operation carries together with whether each group
|
|
182
|
-
* holds a required member. The grouped parameter stays optional only when nothing inside it is
|
|
183
|
-
* required, matching the generated `RequestConfig` type.
|
|
184
|
-
*/
|
|
185
|
-
function getRequestGroupOptionality(node) {
|
|
186
|
-
const groups = getRequestGroups(node);
|
|
187
|
-
const { path, query, header } = getOperationParameters(node);
|
|
188
|
-
const hasRequiredPath = path.some((param) => param.required);
|
|
189
|
-
const hasRequiredQuery = query.some((param) => param.required);
|
|
190
|
-
const hasRequiredHeader = header.some((param) => param.required);
|
|
191
|
-
return {
|
|
192
|
-
groups,
|
|
193
|
-
hasRequiredPath,
|
|
194
|
-
hasRequiredQuery,
|
|
195
|
-
hasRequiredHeader,
|
|
196
|
-
isOptional: !hasRequiredPath && !hasRequiredQuery && !hasRequiredHeader && !(groups.body && node.requestBody?.required === true)
|
|
197
|
-
};
|
|
198
|
-
}
|
|
199
|
-
function buildOperationComments(node, options = {}) {
|
|
200
|
-
const { link = "pathTemplate", linkPosition = "afterDeprecated", splitLines = false } = options;
|
|
201
|
-
const linkComment = getOperationLink(node, link);
|
|
202
|
-
const filteredComments = (linkPosition === "beforeDeprecated" ? [
|
|
203
|
-
node.description && `@description ${node.description}`,
|
|
204
|
-
node.summary && `@summary ${node.summary}`,
|
|
205
|
-
linkComment,
|
|
206
|
-
node.deprecated && "@deprecated"
|
|
207
|
-
] : [
|
|
208
|
-
node.description && `@description ${node.description}`,
|
|
209
|
-
node.summary && `@summary ${node.summary}`,
|
|
210
|
-
node.deprecated && "@deprecated",
|
|
211
|
-
linkComment
|
|
212
|
-
]).filter((comment) => Boolean(comment));
|
|
213
|
-
if (!splitLines) return filteredComments;
|
|
214
|
-
return filteredComments.flatMap((text) => text.split(/\r?\n/).map((line) => line.trim())).filter((comment) => Boolean(comment));
|
|
215
|
-
}
|
|
216
|
-
const operationParameterGroupsByNode = /* @__PURE__ */ new WeakMap();
|
|
217
|
-
/**
|
|
218
|
-
* An OpenAPI 3.2 `querystring` parameter is the whole query string, so a form-encoded object becomes one query parameter
|
|
219
|
-
* per property, typed and serialized like any other.
|
|
220
|
-
* Another content type, such as JSON, stays one parameter named after it, and the runtime sends its value as the whole query string.
|
|
221
|
-
*/
|
|
222
|
-
function spreadQuerystring(param) {
|
|
223
|
-
const schema = param.schema.type === "ref" ? param.schema.schema : param.schema;
|
|
224
|
-
const formEncoded = !param.contentType || param.contentType.split(";")[0].trim().toLowerCase() === "application/x-www-form-urlencoded";
|
|
225
|
-
if (schema?.type !== "object" || !formEncoded) return [param];
|
|
226
|
-
return schema.properties.map((property) => ast.factory.createParameter({
|
|
227
|
-
name: property.name,
|
|
228
|
-
in: "query",
|
|
229
|
-
schema: property.schema,
|
|
230
|
-
required: param.required && property.required
|
|
231
|
-
}));
|
|
232
|
-
}
|
|
233
|
-
/**
|
|
234
|
-
* Groups an operation's parameters by location (`path`/`query`/`header`/`cookie`), deduping each
|
|
235
|
-
* group by name. Every plugin generator visiting the same `OperationNode` shares one AST instance
|
|
236
|
-
* (see `KubbDriver`), so the result is cached per node to avoid re-filtering and re-deduping the
|
|
237
|
-
* same parameters once per plugin.
|
|
238
|
-
*/
|
|
239
|
-
function getOperationParameters(node) {
|
|
240
|
-
const cached = operationParameterGroupsByNode.get(node);
|
|
241
|
-
if (cached) return cached;
|
|
242
|
-
const dedupe = (params) => {
|
|
243
|
-
const seen = /* @__PURE__ */ new Set();
|
|
244
|
-
return params.filter((param) => {
|
|
245
|
-
if (seen.has(param.name)) return false;
|
|
246
|
-
seen.add(param.name);
|
|
247
|
-
return true;
|
|
248
|
-
});
|
|
249
|
-
};
|
|
250
|
-
const group = (location) => dedupe(node.parameters.filter((param) => param.in === location));
|
|
251
|
-
const groups = {
|
|
252
|
-
path: group("path"),
|
|
253
|
-
query: dedupe([...group("query"), ...group("querystring").flatMap(spreadQuerystring)]),
|
|
254
|
-
header: group("header"),
|
|
255
|
-
cookie: group("cookie")
|
|
256
|
-
};
|
|
257
|
-
operationParameterGroupsByNode.set(node, groups);
|
|
258
|
-
return groups;
|
|
259
|
-
}
|
|
260
|
-
/**
|
|
261
|
-
* Builds the combined `{ body, path, query, headers }` options object schema for an operation,
|
|
262
|
-
* referencing the already-resolved body and grouped param names. Shared by `@kubb/plugin-ts`'s
|
|
263
|
-
* `Options` type and `@kubb/plugin-zod`'s inferred options schema, so both printers emit the same
|
|
264
|
-
* shape from the same inputs. `primitive: 'object'` is a no-op for the TS printer and tells the Zod
|
|
265
|
-
* printer to emit `z.object(…)` rather than a record.
|
|
266
|
-
*/
|
|
267
|
-
function hasRequestContentTypeChoice(node) {
|
|
268
|
-
return (node.requestBody?.content ?? []).filter((entry) => entry.schema).length > 1;
|
|
269
|
-
}
|
|
270
|
-
function getStatusCodeNumber(statusCode) {
|
|
271
|
-
const code = Number(statusCode);
|
|
272
|
-
return Number.isNaN(code) ? null : code;
|
|
273
|
-
}
|
|
274
|
-
function isSuccessStatusCode(statusCode) {
|
|
275
|
-
const code = getStatusCodeNumber(statusCode);
|
|
276
|
-
return code !== null && code >= 200 && code < 300;
|
|
277
|
-
}
|
|
278
|
-
function getSuccessResponses(responses) {
|
|
279
|
-
return responses.filter((response) => isSuccessStatusCode(response.statusCode));
|
|
280
|
-
}
|
|
281
|
-
function getOperationSuccessResponses(node) {
|
|
282
|
-
return getSuccessResponses(node.responses);
|
|
283
|
-
}
|
|
284
|
-
function getPrimarySuccessResponse(node) {
|
|
285
|
-
return getOperationSuccessResponses(node)[0] ?? null;
|
|
286
|
-
}
|
|
287
|
-
// ../../internals/utils/src/reserved.ts
|
|
288
|
-
/**
|
|
289
|
-
* JavaScript and Java reserved words.
|
|
290
|
-
* @link https://github.com/jonschlinkert/reserved/blob/master/index.js
|
|
291
|
-
*/
|
|
292
|
-
const reservedWords = /* @__PURE__ */ new Set([
|
|
293
|
-
"abstract",
|
|
294
|
-
"arguments",
|
|
295
|
-
"await",
|
|
296
|
-
"boolean",
|
|
297
|
-
"break",
|
|
298
|
-
"byte",
|
|
299
|
-
"case",
|
|
300
|
-
"catch",
|
|
301
|
-
"char",
|
|
302
|
-
"class",
|
|
303
|
-
"const",
|
|
304
|
-
"continue",
|
|
305
|
-
"debugger",
|
|
306
|
-
"default",
|
|
307
|
-
"delete",
|
|
308
|
-
"do",
|
|
309
|
-
"double",
|
|
310
|
-
"else",
|
|
311
|
-
"enum",
|
|
312
|
-
"eval",
|
|
313
|
-
"export",
|
|
314
|
-
"extends",
|
|
315
|
-
"false",
|
|
316
|
-
"final",
|
|
317
|
-
"finally",
|
|
318
|
-
"float",
|
|
319
|
-
"for",
|
|
320
|
-
"function",
|
|
321
|
-
"goto",
|
|
322
|
-
"if",
|
|
323
|
-
"implements",
|
|
324
|
-
"import",
|
|
325
|
-
"in",
|
|
326
|
-
"instanceof",
|
|
327
|
-
"int",
|
|
328
|
-
"interface",
|
|
329
|
-
"let",
|
|
330
|
-
"long",
|
|
331
|
-
"native",
|
|
332
|
-
"new",
|
|
333
|
-
"null",
|
|
334
|
-
"package",
|
|
335
|
-
"private",
|
|
336
|
-
"protected",
|
|
337
|
-
"public",
|
|
338
|
-
"return",
|
|
339
|
-
"short",
|
|
340
|
-
"static",
|
|
341
|
-
"super",
|
|
342
|
-
"switch",
|
|
343
|
-
"synchronized",
|
|
344
|
-
"this",
|
|
345
|
-
"throw",
|
|
346
|
-
"throws",
|
|
347
|
-
"transient",
|
|
348
|
-
"true",
|
|
349
|
-
"try",
|
|
350
|
-
"typeof",
|
|
351
|
-
"var",
|
|
352
|
-
"void",
|
|
353
|
-
"volatile",
|
|
354
|
-
"while",
|
|
355
|
-
"with",
|
|
356
|
-
"yield",
|
|
357
|
-
"Array",
|
|
358
|
-
"Date",
|
|
359
|
-
"hasOwnProperty",
|
|
360
|
-
"Infinity",
|
|
361
|
-
"isFinite",
|
|
362
|
-
"isNaN",
|
|
363
|
-
"isPrototypeOf",
|
|
364
|
-
"length",
|
|
365
|
-
"Math",
|
|
366
|
-
"name",
|
|
367
|
-
"NaN",
|
|
368
|
-
"Number",
|
|
369
|
-
"Object",
|
|
370
|
-
"prototype",
|
|
371
|
-
"String",
|
|
372
|
-
"toString",
|
|
373
|
-
"undefined",
|
|
374
|
-
"valueOf"
|
|
375
|
-
]);
|
|
376
|
-
/**
|
|
377
|
-
* Returns `true` when `name` is a syntactically valid JavaScript variable name.
|
|
378
|
-
*
|
|
379
|
-
* @example
|
|
380
|
-
* ```ts
|
|
381
|
-
* isValidVarName('status') // true
|
|
382
|
-
* isValidVarName('class') // false (reserved word)
|
|
383
|
-
* isValidVarName('42foo') // false (starts with digit)
|
|
384
|
-
* ```
|
|
385
|
-
*/
|
|
386
|
-
function isValidVarName(name) {
|
|
387
|
-
if (!name || reservedWords.has(name)) return false;
|
|
388
|
-
return isIdentifier(name);
|
|
389
|
-
}
|
|
390
|
-
/**
|
|
391
|
-
* Returns `name` when it's a syntactically valid JavaScript variable name,
|
|
392
|
-
* otherwise prefixes it with `_` so the result is a valid identifier.
|
|
393
|
-
*
|
|
394
|
-
* Useful for sanitizing OpenAPI schema names or operation IDs that start with
|
|
395
|
-
* a digit (e.g. `409`, `504AccountCancel`) before using them as exported
|
|
396
|
-
* variable, type, or function names.
|
|
397
|
-
*
|
|
398
|
-
* @example
|
|
399
|
-
* ```ts
|
|
400
|
-
* ensureValidVarName('409') // '_409'
|
|
401
|
-
* ensureValidVarName('504AccountCancel') // '_504AccountCancel'
|
|
402
|
-
* ensureValidVarName('Pet') // 'Pet'
|
|
403
|
-
* ensureValidVarName('class') // '_class'
|
|
404
|
-
* ```
|
|
405
|
-
*/
|
|
406
|
-
function ensureValidVarName(name) {
|
|
407
|
-
if (!name || isValidVarName(name)) return name;
|
|
408
|
-
return `_${name}`;
|
|
409
|
-
}
|
|
410
|
-
/**
|
|
411
|
-
* Returns `true` when `name` is syntactically a valid identifier, ignoring reserved words.
|
|
412
|
-
*
|
|
413
|
-
* Reserved words and globals (`class`, `name`, `Date`, …) are valid as bare object-literal keys
|
|
414
|
-
* even though they are not valid variable names, so use this (not {@link isValidVarName}) when
|
|
415
|
-
* deciding whether an object key needs quoting.
|
|
416
|
-
*
|
|
417
|
-
* @example
|
|
418
|
-
* ```ts
|
|
419
|
-
* isIdentifier('name') // true
|
|
420
|
-
* isIdentifier('x-total')// false
|
|
421
|
-
* ```
|
|
422
|
-
*/
|
|
423
|
-
function isIdentifier(name) {
|
|
424
|
-
return /^[a-zA-Z_$][a-zA-Z0-9_$]*$/.test(name);
|
|
425
|
-
}
|
|
426
|
-
// ../../internals/utils/src/codegen.ts
|
|
427
|
-
/**
|
|
428
|
-
* Builds a JSDoc comment block from an array of lines. Returns `fallback` when there are no
|
|
429
|
-
* comments.
|
|
430
|
-
*
|
|
431
|
-
* @example
|
|
432
|
-
* ```ts
|
|
433
|
-
* buildJSDoc(['@type string', '@example hello'])
|
|
434
|
-
* // '/**\n * @type string\n * @example hello\n *\/\n '
|
|
435
|
-
* ```
|
|
436
|
-
*/
|
|
437
|
-
function buildJSDoc(comments, options = {}) {
|
|
438
|
-
const { indent = " * ", suffix = "\n ", fallback = " " } = options;
|
|
439
|
-
if (comments.length === 0) return fallback;
|
|
440
|
-
return `/**\n${comments.map((c) => `${indent}${c}`).join("\n")}\n */${suffix}`;
|
|
441
|
-
}
|
|
442
|
-
// ../../internals/shared/src/resolver.ts
|
|
443
|
-
/**
|
|
444
|
-
* The shared top-level `name` of the operation plugins, computed once per operation name and build.
|
|
445
|
-
*/
|
|
446
|
-
function resolveOperationName(name) {
|
|
447
|
-
return this.memo(`operation:${name}`, () => ensureValidVarName(camelCase(name)));
|
|
448
|
-
}
|
|
449
|
-
/**
|
|
450
|
-
* Builds the allocation key for a declaration an operation owns. `scope` names which declaration
|
|
451
|
-
* it is, so two of them never share an allocation even when a custom resolver gives both the same
|
|
452
|
-
* name. Leave `scope` empty for the operation's own declaration.
|
|
453
|
-
*
|
|
454
|
-
* @example
|
|
455
|
-
* ```ts
|
|
456
|
-
* resolver.resolveName({ key: operationKey(node, 'response', 'status', 404), name })
|
|
457
|
-
* ```
|
|
458
|
-
*/
|
|
459
|
-
function operationKey(node, ...scope) {
|
|
460
|
-
return [
|
|
461
|
-
"operation",
|
|
462
|
-
node.method,
|
|
463
|
-
node.path,
|
|
464
|
-
...scope.map(String)
|
|
465
|
-
];
|
|
466
|
-
}
|
|
467
|
-
// ../../internals/shared/src/group.ts
|
|
468
|
-
/**
|
|
469
|
-
* Builds the `group` config a Kubb plugin passes to `ctx.setOptions`, applying the
|
|
470
|
-
* shared default naming so every plugin groups output consistently:
|
|
471
|
-
*
|
|
472
|
-
* - `path` groups use the second path segment (`/pet/findByStatus` → `pet`).
|
|
473
|
-
* - other groups use the camelCased group (`pet store` → `petStore`).
|
|
474
|
-
*
|
|
475
|
-
* A user-provided `group.name` always wins over the default namer, so callers stay in
|
|
476
|
-
* control of their output folders. Returns `null` when grouping is disabled, matching the
|
|
477
|
-
* per-plugin convention.
|
|
478
|
-
*
|
|
479
|
-
* @param group - The user-supplied group option, or `undefined` to disable grouping.
|
|
480
|
-
*
|
|
481
|
-
* @example
|
|
482
|
-
* ```ts
|
|
483
|
-
* createGroupConfig(group) // shared across every plugin
|
|
484
|
-
* ```
|
|
485
|
-
*/
|
|
486
|
-
function createGroupConfig(group) {
|
|
487
|
-
if (!group) return null;
|
|
488
|
-
const defaultName = (ctx) => {
|
|
489
|
-
if (group.type === "path") return `${ctx.group.split("/")[1]}`;
|
|
490
|
-
return camelCase(ctx.group);
|
|
491
|
-
};
|
|
492
|
-
return {
|
|
493
|
-
...group,
|
|
494
|
-
name: group.name ? group.name : defaultName
|
|
495
|
-
};
|
|
496
|
-
}
|
|
497
|
-
// ../../internals/client/src/builders/validatorOptions.ts
|
|
498
|
-
/**
|
|
499
|
-
* Returns `true` when any direction of the validator uses zod (used for dependency checks).
|
|
500
|
-
*/
|
|
501
|
-
function isValidatorEnabled(validator) {
|
|
502
|
-
if (!validator) return false;
|
|
503
|
-
if (validator === "zod") return true;
|
|
504
|
-
return Boolean(validator.request || validator.params || validator.response);
|
|
505
|
-
}
|
|
506
|
-
/**
|
|
507
|
-
* Returns `'zod'` when request body parsing is enabled, `null` otherwise. The string shorthand
|
|
508
|
-
* `'zod'` validates the response only, so it does not enable request parsing.
|
|
509
|
-
*/
|
|
510
|
-
function resolveRequestValidator(validator) {
|
|
511
|
-
if (!validator || validator === "zod") return null;
|
|
512
|
-
return validator.request ?? null;
|
|
513
|
-
}
|
|
514
|
-
/**
|
|
515
|
-
* Returns `'zod'` when path, query, and header params parsing is enabled, `null` otherwise. Only the
|
|
516
|
-
* object form `{ params: 'zod' }` enables it.
|
|
517
|
-
*/
|
|
518
|
-
function resolveParamsValidator(validator) {
|
|
519
|
-
if (!validator || validator === "zod") return null;
|
|
520
|
-
return validator.params ?? null;
|
|
521
|
-
}
|
|
522
|
-
/**
|
|
523
|
-
* Returns `'zod'` when response parsing is enabled, `null` otherwise. The string shorthand `'zod'`
|
|
524
|
-
* maps to response parsing.
|
|
525
|
-
*/
|
|
526
|
-
function resolveResponseValidator(validator) {
|
|
527
|
-
if (!validator) return null;
|
|
528
|
-
if (validator === "zod") return "zod";
|
|
529
|
-
return validator.response ?? null;
|
|
530
|
-
}
|
|
531
|
-
/**
|
|
532
|
-
* Resolves the zod schema name a generated client validates an error body with on the non-throw path.
|
|
533
|
-
* Uses the error-only `<operation>ErrorSchema` (the union of non-2xx statuses); returns `null` when the
|
|
534
|
-
* operation documents no error responses with a schema.
|
|
535
|
-
*/
|
|
536
|
-
function buildZodErrorParse(node, zodResolver) {
|
|
537
|
-
if (!node.responses.some((res) => !isSuccessStatusCode(res.statusCode) && res.content?.some((entry) => entry.schema))) return null;
|
|
538
|
-
return zodResolver.response.error?.(node) || null;
|
|
539
|
-
}
|
|
540
|
-
// ../../internals/client/src/builders/generics.ts
|
|
541
|
-
/**
|
|
542
|
-
* Builds the `RequestResult` generic arguments for one operation: the per-status responses record
|
|
543
|
-
* plus the per-call `ThrowOnError` flag. `SuccessOf` / `ErrorOf` split the record inside the
|
|
544
|
-
* runtime, so this only names the record and threads `ThrowOnError`.
|
|
545
|
-
*
|
|
546
|
-
* @example
|
|
547
|
-
* `buildRequestResultGenerics({ node, types }) // 'AddPetResponses, ThrowOnError'`
|
|
548
|
-
*/
|
|
549
|
-
function buildRequestResultGenerics({ node, types }) {
|
|
550
|
-
return `${types.response.responses(node)}, ThrowOnError`;
|
|
551
|
-
}
|
|
552
|
-
/**
|
|
553
|
-
* Builds the full return type an operation's function signature uses: `Unwrappable<RequestResult>`
|
|
554
|
-
* for the default `returnType: 'full'`, already a promise so no further wrapping is needed, a plain
|
|
555
|
-
* `Promise<RequestResult>` for the internal `'plain'` shape, or a
|
|
556
|
-
* `Promise` of the runtime's `UnwrappedResult` when `returnType: 'data'` narrows a resolved call
|
|
557
|
-
* down to the bare success body.
|
|
558
|
-
*
|
|
559
|
-
* @example
|
|
560
|
-
* `buildResultType({ node, types, returnType: 'data' }) // 'Promise<UnwrappedResult<AddPetResponses, ThrowOnError>>'`
|
|
561
|
-
*/
|
|
562
|
-
function buildResultType({ node, types, returnType }) {
|
|
563
|
-
const generics = buildRequestResultGenerics({
|
|
564
|
-
node,
|
|
565
|
-
types
|
|
566
|
-
});
|
|
567
|
-
if (returnType === "data") return `Promise<UnwrappedResult<${generics}>>`;
|
|
568
|
-
if (returnType === "plain") return `Promise<RequestResult<${generics}>>`;
|
|
569
|
-
return `Unwrappable<RequestResult<${generics}>>`;
|
|
570
|
-
}
|
|
571
|
-
// ../../internals/client/src/builders/returnStatement.ts
|
|
572
|
-
/**
|
|
573
|
-
* Builds the return statement of a generated operation function. With the default
|
|
574
|
-
* `returnType: 'full'` the runtime call already resolves to `{ data, error, request, response }`.
|
|
575
|
-
* The generated code casts that result to the operation's `RequestResult`, then wraps it with
|
|
576
|
-
* `withUnwrap`, so the caller can `await` it directly or call `.unwrap()` for the bare success
|
|
577
|
-
* body. With `returnType: 'data'` it instead routes the call through the runtime's `unwrapResult`,
|
|
578
|
-
* which narrows the resolved value down to the bare success body the same way `RequestResult`
|
|
579
|
-
* already does, using the plugin default when the call leaves `throwOnError` unset.
|
|
580
|
-
* With `returnType: 'plain'` it returns the runtime call as is, cast to `RequestResult`, with no helper.
|
|
581
|
-
*
|
|
582
|
-
* Cast first, wrap second, for the `'full'` path. That order keeps `withUnwrap`'s generic inferred
|
|
583
|
-
* as `RequestResult` instead of the runtime's own internal result type. Casting an `Unwrappable<A>`
|
|
584
|
-
* straight to `Unwrappable<B>` does not work: the `.then` overload stays pinned to `A`, and `as`
|
|
585
|
-
* rejects that two-generic swap even where `A` and `B` on their own would satisfy it.
|
|
586
|
-
*
|
|
587
|
-
* @example
|
|
588
|
-
* `return withUnwrap(request({ method: 'POST', url: '/pet', ...config }) as Promise<RequestResult<AddPetResponses, ThrowOnError>>)`
|
|
589
|
-
* @example
|
|
590
|
-
* `return unwrapResult(request({ method: 'POST', url: '/pet', ...config }), config.throwOnError ?? false) as Promise<UnwrappedResult<AddPetResponses, ThrowOnError>>`
|
|
591
|
-
* @example
|
|
592
|
-
* `return request({ method: 'POST', url: '/pet', ...config }) as Promise<RequestResult<AddPetResponses, ThrowOnError>>`
|
|
593
|
-
*/
|
|
594
|
-
function buildReturnStatement({ node, types, callConfig, returnType, throwOnErrorDefault }) {
|
|
595
|
-
const request = returnType !== "plain" && hasRequestContentTypeChoice(node) ? `request<${types.response.options(node)}['body']>` : "request";
|
|
596
|
-
if (returnType === "data") return `return unwrapResult(${request}(${callConfig}), config.throwOnError ?? ${throwOnErrorDefault}) as ${buildResultType({
|
|
597
|
-
node,
|
|
598
|
-
types,
|
|
599
|
-
returnType
|
|
600
|
-
})}`;
|
|
601
|
-
if (returnType === "plain") return `return ${request}(${callConfig}) as Promise<RequestResult<${buildRequestResultGenerics({
|
|
602
|
-
node,
|
|
603
|
-
types
|
|
604
|
-
})}>>`;
|
|
605
|
-
return `return withUnwrap(${request}(${callConfig}) as Promise<RequestResult<${buildRequestResultGenerics({
|
|
606
|
-
node,
|
|
607
|
-
types
|
|
608
|
-
})}>>)`;
|
|
609
|
-
}
|
|
610
|
-
/**
|
|
611
|
-
* Builds the return type and statement of a `text/event-stream` operation. The item type is `TypedEvent<...>` when each item
|
|
612
|
-
* declares its own `event` and `data`, and otherwise the payload type, which `EventStreamResult` wraps in `ServerSentEvent`. The call's
|
|
613
|
-
* promise goes through `toEventStream` with the `client` for its defaults, the call's `config`, the shape and the per-event
|
|
614
|
-
* validator. A `returnType: 'plain'` client receives only the promise,
|
|
615
|
-
* `toEventStream(request(config))`, and keeps its response validator in the call config.
|
|
616
|
-
*/
|
|
617
|
-
function buildEventStreamReturn({ node, types, callConfig, returnType, validatorHooks, throwOnErrorDefault }) {
|
|
618
|
-
const shape = getEventStream(node) ?? "data";
|
|
619
|
-
const responses = types.response.responses(node);
|
|
620
|
-
const success = `SuccessOf<${responses}>`;
|
|
621
|
-
const eventType = shape === "event" ? `TypedEvent<${success}>` : success;
|
|
622
|
-
if (returnType === "plain") return {
|
|
623
|
-
returnType: `Promise<EventStreamResult<${eventType}>>`,
|
|
624
|
-
statement: `return toEventStream<${eventType}>(request(${callConfig}))`
|
|
625
|
-
};
|
|
626
|
-
const generics = `${responses}, ThrowOnError${shape === "event" ? `, ${eventType}` : ""}`;
|
|
627
|
-
const call = `toEventStream<${eventType}>(request(${callConfig}), { client: request, config, ${[`shape: '${shape}'`, validatorHooks?.response ? `validator: ${validatorHooks.response}` : null].filter(Boolean).join(", ")} })`;
|
|
628
|
-
if (returnType === "data") {
|
|
629
|
-
const result = `Promise<UnwrappedStreamResult<${generics}>>`;
|
|
630
|
-
return {
|
|
631
|
-
returnType: result,
|
|
632
|
-
statement: `return unwrapResult(${call}, config.throwOnError ?? ${throwOnErrorDefault}) as ${result}`
|
|
633
|
-
};
|
|
634
|
-
}
|
|
635
|
-
return {
|
|
636
|
-
returnType: `Unwrappable<StreamResult<${generics}>>`,
|
|
637
|
-
statement: `return withUnwrap(${call} as Promise<StreamResult<${generics}>>)`
|
|
638
|
-
};
|
|
639
|
-
}
|
|
640
|
-
/** The stream types an operation file imports: `StreamResult` and `StreamOptions`, `EventStreamResult` for a plain client, and `TypedEvent` for named events. */
|
|
641
|
-
function buildEventStreamImports({ returnType, typed }) {
|
|
642
|
-
return [...{
|
|
643
|
-
plain: ["EventStreamResult"],
|
|
644
|
-
full: ["StreamResult", "StreamOptions"],
|
|
645
|
-
data: ["UnwrappedStreamResult", "StreamOptions"]
|
|
646
|
-
}[returnType], ...typed ? ["TypedEvent"] : []];
|
|
647
|
-
}
|
|
648
|
-
// ../../internals/client/src/builders/security.ts
|
|
649
|
-
function serializeAuth(auth) {
|
|
650
|
-
const parts = [`type: '${auth.type}'`];
|
|
651
|
-
if (auth.scheme) parts.push(`scheme: '${auth.scheme}'`);
|
|
652
|
-
if (auth.name) parts.push(`name: '${auth.name}'`);
|
|
653
|
-
if (auth.in) parts.push(`in: '${auth.in}'`);
|
|
654
|
-
if (auth.scopes?.length) parts.push(`scopes: [${auth.scopes.map((scope) => `'${scope}'`).join(", ")}]`);
|
|
655
|
-
return `{ ${parts.join(", ")} }`;
|
|
656
|
-
}
|
|
657
|
-
/**
|
|
658
|
-
* Maps an OpenAPI security scheme to the inline `Auth` object, or `null` when the runtime cannot
|
|
659
|
-
* place it (an unresolved `$ref`, or an `apiKey` without a name or outside `header` / `query` /
|
|
660
|
-
* `cookie`). `http` schemes other than `basic` are treated as bearer.
|
|
661
|
-
*/
|
|
662
|
-
function resolveSecurityScheme(scheme) {
|
|
663
|
-
if (!scheme || "$ref" in scheme) return null;
|
|
664
|
-
if (scheme.type === "apiKey") {
|
|
665
|
-
if (!scheme.name || scheme.in !== "header" && scheme.in !== "query" && scheme.in !== "cookie") return null;
|
|
666
|
-
return {
|
|
667
|
-
type: "apiKey",
|
|
668
|
-
name: scheme.name,
|
|
669
|
-
in: scheme.in
|
|
670
|
-
};
|
|
671
|
-
}
|
|
672
|
-
if (scheme.type === "http") return {
|
|
673
|
-
type: "http",
|
|
674
|
-
scheme: scheme.scheme?.toLowerCase() === "basic" ? "basic" : "bearer"
|
|
675
|
-
};
|
|
676
|
-
if (scheme.type === "oauth2") return { type: "oauth2" };
|
|
677
|
-
if (scheme.type === "openIdConnect") return { type: "openIdConnect" };
|
|
678
|
-
return null;
|
|
679
|
-
}
|
|
680
|
-
/**
|
|
681
|
-
* Derives the per-operation security metadata from the OpenAPI document. The operation's own
|
|
682
|
-
* `security` overrides the global `security` (an explicit empty array disables auth), and every
|
|
683
|
-
* referenced scheme is resolved from `components.securitySchemes` into a flat, de-duplicated list of
|
|
684
|
-
* `Auth` objects the runtime walks in order.
|
|
685
|
-
*
|
|
686
|
-
* @example
|
|
687
|
-
* `getOperationSecurity({ document, method: 'POST', path: '/pet' })`
|
|
688
|
-
* `// [{ type: 'http', scheme: 'bearer' }]`
|
|
689
|
-
*/
|
|
690
|
-
function getOperationSecurity({ document, method, path }) {
|
|
691
|
-
if (!document) return void 0;
|
|
692
|
-
const requirements = (document.paths?.[path]?.[method.toLowerCase()])?.security ?? document.security;
|
|
693
|
-
if (!requirements?.length) return void 0;
|
|
694
|
-
const definitions = document.components?.securitySchemes ?? {};
|
|
695
|
-
const security = [];
|
|
696
|
-
const seen = /* @__PURE__ */ new Set();
|
|
697
|
-
for (const requirement of requirements) for (const [schemeName, scopes] of Object.entries(requirement)) {
|
|
698
|
-
if (seen.has(schemeName)) continue;
|
|
699
|
-
seen.add(schemeName);
|
|
700
|
-
const auth = resolveSecurityScheme(definitions[schemeName]);
|
|
701
|
-
if (auth) security.push(scopes.length ? {
|
|
702
|
-
...auth,
|
|
703
|
-
scopes
|
|
704
|
-
} : auth);
|
|
705
|
-
}
|
|
706
|
-
return security.length ? security : void 0;
|
|
707
|
-
}
|
|
708
|
-
/**
|
|
709
|
-
* Serializes the per-operation security into the literal emitted on each generated call's `security`
|
|
710
|
-
* field. The runtime `resolveAuth` helper walks it, calling the configured `auth` resolver per entry.
|
|
711
|
-
*
|
|
712
|
-
* @example
|
|
713
|
-
* `buildSecurityMetadata({ security: [{ type: 'http', scheme: 'bearer' }] }) // "[{ type: 'http', scheme: 'bearer' }]"`
|
|
714
|
-
*/
|
|
715
|
-
function buildSecurityMetadata({ security }) {
|
|
716
|
-
if (!security?.length) return null;
|
|
717
|
-
return `[${security.map(serializeAuth).join(", ")}]`;
|
|
718
|
-
}
|
|
719
|
-
// ../../internals/client/src/builders/validator.ts
|
|
720
|
-
/**
|
|
721
|
-
* Builds the validator-hook references for one operation. Request body and params validation run before the send.
|
|
722
|
-
* Response validation runs on the success body only. Returns `null` references when the matching
|
|
723
|
-
* direction is disabled or the schema is absent.
|
|
724
|
-
*/
|
|
725
|
-
function buildValidatorHooks({ node, validator, zodResolver }) {
|
|
726
|
-
const hasRequestBody = Boolean(node.requestBody?.content?.[0]?.schema);
|
|
727
|
-
const parsesResponse = zodResolver && resolveResponseValidator(validator) === "zod";
|
|
728
|
-
const response = parsesResponse ? zodResolver.response.response(node) || null : null;
|
|
729
|
-
const error = parsesResponse ? buildZodErrorParse(node, zodResolver) : null;
|
|
730
|
-
const request = zodResolver && resolveRequestValidator(validator) === "zod" && hasRequestBody ? zodResolver.response.body(node) || null : null;
|
|
731
|
-
const paramGroups = getOperationParameters(node);
|
|
732
|
-
const resolveParamsName = (kind, params) => zodResolver && resolveParamsValidator(validator) === "zod" && params.length > 0 ? zodResolver.param[kind](node, params[0]) : null;
|
|
733
|
-
const path = resolveParamsName("path", paramGroups.path);
|
|
734
|
-
const query = resolveParamsName("query", paramGroups.query);
|
|
735
|
-
const headers = resolveParamsName("headers", paramGroups.header);
|
|
736
|
-
const importedZodNames = [
|
|
737
|
-
response,
|
|
738
|
-
error,
|
|
739
|
-
request,
|
|
740
|
-
path,
|
|
741
|
-
query,
|
|
742
|
-
headers
|
|
743
|
-
].filter((name) => Boolean(name));
|
|
744
|
-
return {
|
|
745
|
-
request,
|
|
746
|
-
path,
|
|
747
|
-
query,
|
|
748
|
-
headers: headers ? `toHeadersStandardSchema(${headers}, ${headers}.shape)` : null,
|
|
749
|
-
response,
|
|
750
|
-
error,
|
|
751
|
-
importedZodNames
|
|
752
|
-
};
|
|
753
|
-
}
|
|
754
|
-
/**
|
|
755
|
-
* Prints the `validator: { … }` entry of a call config from the hooks named in `keys`, or `null` when none is set.
|
|
756
|
-
*/
|
|
757
|
-
function buildValidatorLiteral(hooks, keys) {
|
|
758
|
-
const entries = keys.flatMap((key) => hooks?.[key] ? [`${key}: ${hooks[key]}`] : []);
|
|
759
|
-
return entries.length ? `validator: { ${entries.join(", ")} }` : null;
|
|
760
|
-
}
|
|
761
|
-
// ../../internals/client/src/builders/contentType.ts
|
|
762
|
-
/**
|
|
763
|
-
* Builds the `contentType` entry baked into the call config, or `null` without a request body.
|
|
764
|
-
*
|
|
765
|
-
* `merge` is `true` when the generated function must destructure `contentType` from `options` and
|
|
766
|
-
* merge it over the baked request default, because the caller can also pick a response content type.
|
|
767
|
-
*/
|
|
768
|
-
function buildContentType({ node }) {
|
|
769
|
-
const { defaultContentType } = getContentTypeInfo(node);
|
|
770
|
-
const requestContentType = Boolean(node.requestBody?.content?.[0]?.schema) ? defaultContentType : null;
|
|
771
|
-
if (!requestContentType) return {
|
|
772
|
-
literal: null,
|
|
773
|
-
merge: false
|
|
774
|
-
};
|
|
775
|
-
const merge = getResponseContentTypeInfo(node).isMultipleContentTypes;
|
|
776
|
-
return {
|
|
777
|
-
merge,
|
|
778
|
-
literal: merge ? `contentType: { request: '${requestContentType}', ...(typeof contentType === 'string' ? { request: contentType } : contentType) }` : `contentType: { request: '${requestContentType}' }`
|
|
779
|
-
};
|
|
780
|
-
}
|
|
781
|
-
// ../../internals/client/src/builders/styles.ts
|
|
782
|
-
/**
|
|
783
|
-
* Renders a parameter name as an object-literal key, quoted when it is not a bare identifier. Every
|
|
784
|
-
* location keeps the spec name: the runtime looks path styles up by the `{name}` placeholder in the
|
|
785
|
-
* URL, and the other locations by the key it serializes.
|
|
786
|
-
*/
|
|
787
|
-
function toKey(name) {
|
|
788
|
-
return isValidVarName(name) ? name : JSON.stringify(name);
|
|
789
|
-
}
|
|
790
|
-
/**
|
|
791
|
-
* Serializes one parameter's metadata into a `{ style, explode, allowReserved, contentType }`
|
|
792
|
-
* literal, or `null` when the parameter carries none. Path and query carry the serialization
|
|
793
|
-
* `style`; header and cookie use a fixed style (`simple` and `form`), so only `explode` is emitted
|
|
794
|
-
* for them. `allowReserved` only applies to query parameters, and `contentType` to every location
|
|
795
|
-
* except path.
|
|
796
|
-
*/
|
|
797
|
-
function serializeParameter(parameter) {
|
|
798
|
-
const parts = [];
|
|
799
|
-
if ((parameter.in === "path" || parameter.in === "query") && parameter.style) parts.push(`style: '${parameter.style}'`);
|
|
800
|
-
if (parameter.explode !== void 0) parts.push(`explode: ${parameter.explode}`);
|
|
801
|
-
if (parameter.in === "query" && parameter.allowReserved !== void 0) parts.push(`allowReserved: ${parameter.allowReserved}`);
|
|
802
|
-
if (parameter.in !== "path" && parameter.contentType) parts.push(`contentType: '${parameter.contentType}'`);
|
|
803
|
-
if (parameter.in === "querystring") parts.push("querystring: true");
|
|
804
|
-
return parts.length > 0 ? `{ ${parts.join(", ")} }` : null;
|
|
805
|
-
}
|
|
806
|
-
/**
|
|
807
|
-
* Serializes the `encoding` of the request body's default content type into a `{ property: { ... } }`
|
|
808
|
-
* literal, or `null` when it carries none. Only an operation with a single request content type
|
|
809
|
-
* emits it, because the runtime applies `styles.body` to whichever type the caller selects.
|
|
810
|
-
*/
|
|
811
|
-
function serializeBodyEncoding(node) {
|
|
812
|
-
const content = node.requestBody?.content;
|
|
813
|
-
const entries = Object.entries(content?.length === 1 ? content[0]?.encoding ?? {} : {}).map(([property, { contentType, style, explode, allowReserved }]) => {
|
|
814
|
-
const parts = [
|
|
815
|
-
style && `style: ${JSON.stringify(style)}`,
|
|
816
|
-
explode !== void 0 && `explode: ${explode}`,
|
|
817
|
-
allowReserved !== void 0 && `allowReserved: ${allowReserved}`,
|
|
818
|
-
contentType && `contentType: ${JSON.stringify(contentType)}`
|
|
819
|
-
].filter(Boolean);
|
|
820
|
-
return `${isValidVarName(property) ? property : JSON.stringify(property)}: { ${parts.join(", ")} }`;
|
|
821
|
-
});
|
|
822
|
-
return entries.length > 0 ? `body: { ${entries.join(", ")} }` : null;
|
|
823
|
-
}
|
|
824
|
-
/**
|
|
825
|
-
* Builds the per-operation `styles` literal from the operation's parameters, grouped by location.
|
|
826
|
-
* Entries are keyed by the spec name of the parameter.
|
|
827
|
-
* Only parameters whose source defines `style`, `explode`, `allowReserved`, or `contentType` are
|
|
828
|
-
* emitted, so calls without serialization metadata keep the runtime defaults and existing output
|
|
829
|
-
* is unchanged. A form request body adds a `body` group keyed by property name from its `encoding`.
|
|
830
|
-
* Returns `null` when nothing carries metadata.
|
|
831
|
-
*
|
|
832
|
-
* @example
|
|
833
|
-
* ```ts
|
|
834
|
-
* // a path param with { style: 'matrix', explode: true } and a query param with { explode: false }
|
|
835
|
-
* buildStyles({ node }) // "{ path: { id: { style: 'matrix', explode: true } }, query: { tags: { explode: false } } }"
|
|
836
|
-
* ```
|
|
837
|
-
*/
|
|
838
|
-
function buildStyles({ node }) {
|
|
839
|
-
if (!ast.isHttpOperationNode(node)) return null;
|
|
840
|
-
const groups = {
|
|
841
|
-
path: [],
|
|
842
|
-
query: [],
|
|
843
|
-
header: [],
|
|
844
|
-
cookie: []
|
|
845
|
-
};
|
|
846
|
-
for (const [location, parameters] of Object.entries(getOperationParameters(node))) for (const parameter of parameters) {
|
|
847
|
-
const literal = serializeParameter(parameter);
|
|
848
|
-
if (literal) groups[location].push(`${toKey(parameter.name)}: ${literal}`);
|
|
849
|
-
}
|
|
850
|
-
const entries = [...Object.keys(groups).filter((location) => groups[location].length > 0).map((location) => `${location}: { ${groups[location].join(", ")} }`), serializeBodyEncoding(node)].filter(Boolean);
|
|
851
|
-
return entries.length > 0 ? `{ ${entries.join(", ")} }` : null;
|
|
852
|
-
}
|
|
853
|
-
// ../../internals/client/src/builders/callConfig.ts
|
|
854
|
-
/**
|
|
855
|
-
* `responseType` baked into a call. One parser is a string. Several parsers become a map from content
|
|
856
|
-
* type to parser so the runtime can choose from the response `Content-Type`.
|
|
857
|
-
*/
|
|
858
|
-
function buildResponseTypeLiteral(node) {
|
|
859
|
-
const responseType = getResponseType(node);
|
|
860
|
-
if (responseType) return `responseType: '${responseType}'`;
|
|
861
|
-
const responseTypes = getResponseTypeMap(node);
|
|
862
|
-
if (!responseTypes) return null;
|
|
863
|
-
return `responseType: { ${Object.entries(responseTypes).map(([contentType, type]) => `${JSON.stringify(contentType)}: '${type}'`).join(", ")} }`;
|
|
864
|
-
}
|
|
865
|
-
/**
|
|
866
|
-
* Builds the call config literal a generated operation forwards to the client, shared by the standalone
|
|
867
|
-
* functions and the SDK methods. The `throwOnError` fallback is applied after the `...config` spread.
|
|
868
|
-
*
|
|
869
|
-
* `mergeContentType` is `true` when the caller must destructure `contentType` from `options`, and
|
|
870
|
-
* `eventStream` is `true` when the operation returns `text/event-stream`. Unless `returnType` is `'plain'`, a
|
|
871
|
-
* stream's literal leaves the `response` validator to `toEventStream`, which runs it per event.
|
|
872
|
-
*/
|
|
873
|
-
function buildCallConfig({ node, validatorHooks, security, throwOnErrorDefault, returnType }) {
|
|
874
|
-
const eventStream = isEventStream(node);
|
|
875
|
-
const securityLiteral = buildSecurityMetadata({ security });
|
|
876
|
-
const stylesLiteral = buildStyles({ node });
|
|
877
|
-
const validatorLiteral = buildValidatorLiteral(validatorHooks, eventStream && returnType !== "plain" ? [
|
|
878
|
-
"request",
|
|
879
|
-
"path",
|
|
880
|
-
"query",
|
|
881
|
-
"headers",
|
|
882
|
-
"error"
|
|
883
|
-
] : [
|
|
884
|
-
"request",
|
|
885
|
-
"path",
|
|
886
|
-
"query",
|
|
887
|
-
"headers",
|
|
888
|
-
"response",
|
|
889
|
-
"error"
|
|
890
|
-
]);
|
|
891
|
-
const { literal: contentTypeLiteral, merge: mergeContentType } = buildContentType({ node });
|
|
892
|
-
return {
|
|
893
|
-
callConfig: `{ ${[
|
|
894
|
-
`method: '${node.method.toUpperCase()}'`,
|
|
895
|
-
`url: '${node.path}'`,
|
|
896
|
-
securityLiteral ? `security: ${securityLiteral}` : null,
|
|
897
|
-
stylesLiteral ? `styles: ${stylesLiteral}` : null,
|
|
898
|
-
validatorLiteral,
|
|
899
|
-
contentTypeLiteral,
|
|
900
|
-
buildResponseTypeLiteral(node),
|
|
901
|
-
"...config",
|
|
902
|
-
`throwOnError: config.throwOnError ?? ${throwOnErrorDefault}`
|
|
903
|
-
].filter(Boolean).join(", ")} }`,
|
|
904
|
-
mergeContentType,
|
|
905
|
-
eventStream
|
|
906
|
-
};
|
|
907
|
-
}
|
|
908
|
-
// ../../internals/client/src/builders/signature.ts
|
|
909
|
-
const declarationPrinter = functionPrinter({ mode: "declaration" });
|
|
910
|
-
/**
|
|
911
|
-
* Builds the grouped-options signature for one operation: a single `options` object whose `TData`
|
|
912
|
-
* is the `<Name>Options` type, and a `RequestResult` return type keyed to the per-status responses
|
|
913
|
-
* record. There are no positional arguments.
|
|
914
|
-
*
|
|
915
|
-
* The generated file imports `<Name>Options` and `<Name>Responses` and uses them directly, so no
|
|
916
|
-
* per-operation input type has to be emitted. Both names come from `types`, which is `plugin-ts` or
|
|
917
|
-
* `plugin-zod`'s inferred types (see `resolveOperationTypes`).
|
|
918
|
-
*/
|
|
919
|
-
function buildGroupedOptionsSignature({ node, types, returnType, throwOnErrorDefault, eventStream = false }) {
|
|
920
|
-
const optionsName = types.response.options(node);
|
|
921
|
-
const optionsType = `${eventStream && returnType !== "plain" ? "StreamOptions" : "Options"}<${optionsName}, ThrowOnError>`;
|
|
922
|
-
const { isOptional } = getRequestGroupOptionality(node);
|
|
923
|
-
return {
|
|
924
|
-
paramsSignature: declarationPrinter.print(createFunctionParameters({ params: [createFunctionParameter({
|
|
925
|
-
name: "options",
|
|
926
|
-
type: optionsType,
|
|
927
|
-
...isOptional ? { default: "{}" } : {}
|
|
928
|
-
})] })) ?? "",
|
|
929
|
-
returnType: buildResultType({
|
|
930
|
-
node,
|
|
931
|
-
types,
|
|
932
|
-
returnType
|
|
933
|
-
}),
|
|
934
|
-
generics: [`ThrowOnError extends boolean = ${throwOnErrorDefault}`]
|
|
935
|
-
};
|
|
936
|
-
}
|
|
937
|
-
// ../../internals/client/src/builders/operationBody.ts
|
|
938
|
-
/**
|
|
939
|
-
* The pieces of a generated operation: its generics, `options` parameter, return type, return statement, and whether the
|
|
940
|
-
* body destructures `contentType` from `options`. Shared by the standalone functions and the SDK methods, so a
|
|
941
|
-
* `text/event-stream` operation is wired to `toEventStream` in one place.
|
|
942
|
-
*/
|
|
943
|
-
function buildOperationBody({ node, types, validatorHooks, security, returnType, throwOnErrorDefault }) {
|
|
944
|
-
const { callConfig, mergeContentType, eventStream } = buildCallConfig({
|
|
945
|
-
node,
|
|
946
|
-
validatorHooks,
|
|
947
|
-
security,
|
|
948
|
-
throwOnErrorDefault,
|
|
949
|
-
returnType
|
|
950
|
-
});
|
|
951
|
-
const signature = buildGroupedOptionsSignature({
|
|
952
|
-
node,
|
|
953
|
-
types,
|
|
954
|
-
returnType,
|
|
955
|
-
throwOnErrorDefault,
|
|
956
|
-
eventStream
|
|
957
|
-
});
|
|
958
|
-
const stream = eventStream ? buildEventStreamReturn({
|
|
959
|
-
node,
|
|
960
|
-
types,
|
|
961
|
-
callConfig,
|
|
962
|
-
returnType,
|
|
963
|
-
validatorHooks,
|
|
964
|
-
throwOnErrorDefault
|
|
965
|
-
}) : null;
|
|
966
|
-
return {
|
|
967
|
-
generics: signature.generics,
|
|
968
|
-
paramsSignature: signature.paramsSignature,
|
|
969
|
-
returnType: stream?.returnType ?? signature.returnType,
|
|
970
|
-
statement: stream?.statement ?? buildReturnStatement({
|
|
971
|
-
node,
|
|
972
|
-
types,
|
|
973
|
-
callConfig,
|
|
974
|
-
returnType,
|
|
975
|
-
throwOnErrorDefault
|
|
976
|
-
}),
|
|
977
|
-
mergeContentType
|
|
978
|
-
};
|
|
979
|
-
}
|
|
980
|
-
// ../../internals/client/src/components/Operation.tsx
|
|
981
|
-
/**
|
|
982
|
-
* Renders one client operation: the grouped `<Name>Request` type and the function that forwards a
|
|
983
|
-
* single `options` object to the resolved client and returns the `Unwrappable<RequestResult>`. The
|
|
984
|
-
* type, signature, and call config are built with the AST factory, and only the jsx-renderer emits
|
|
985
|
-
* the source.
|
|
986
|
-
*/
|
|
987
|
-
function Operation({ name, node, types, validatorHooks, returnType, throwOnErrorDefault, security }) {
|
|
988
|
-
if (!ast.isHttpOperationNode(node)) return null;
|
|
989
|
-
const body = buildOperationBody({
|
|
990
|
-
node,
|
|
991
|
-
types,
|
|
992
|
-
validatorHooks,
|
|
993
|
-
security,
|
|
994
|
-
returnType,
|
|
995
|
-
throwOnErrorDefault
|
|
996
|
-
});
|
|
997
|
-
return /* @__PURE__ */ jsx(File.Source, {
|
|
998
|
-
name,
|
|
999
|
-
isExportable: true,
|
|
1000
|
-
isIndexable: true,
|
|
1001
|
-
children: /* @__PURE__ */ jsxs(Function, {
|
|
1002
|
-
name,
|
|
1003
|
-
export: true,
|
|
1004
|
-
generics: body.generics,
|
|
1005
|
-
params: body.paramsSignature,
|
|
1006
|
-
returnType: body.returnType,
|
|
1007
|
-
JSDoc: { comments: buildOperationComments(node, {
|
|
1008
|
-
link: "urlPath",
|
|
1009
|
-
linkPosition: "beforeDeprecated",
|
|
1010
|
-
splitLines: true
|
|
1011
|
-
}) },
|
|
1012
|
-
children: [
|
|
1013
|
-
body.mergeContentType ? "const { client: request = client, contentType, ...config } = options" : "const { client: request = client, ...config } = options",
|
|
1014
|
-
/* @__PURE__ */ jsx("br", {}),
|
|
1015
|
-
body.statement
|
|
1016
|
-
]
|
|
1017
|
-
})
|
|
1018
|
-
});
|
|
1019
|
-
}
|
|
1020
|
-
// ../../internals/client/src/components/StreamRuntime.tsx
|
|
1021
|
-
/**
|
|
1022
|
-
* The `footer` of `.kubb/client.ts`: the `baseURL` set at generation time, and the `sse.ts` re-export when the document
|
|
1023
|
-
* streams events, so `ServerSentEvent` and `toEventStream` stay importable from the client as in earlier releases.
|
|
1024
|
-
*/
|
|
1025
|
-
function buildClientFooter({ baseURL, sse }) {
|
|
1026
|
-
const baseURLExpression = baseURL ? baseURL.includes("${") ? `\`${baseURL.replaceAll("`", "\\`")}\`` : JSON.stringify(baseURL) : void 0;
|
|
1027
|
-
return [baseURLExpression ? `client.setConfig({ baseURL: ${baseURLExpression} })` : null, sse ? `export * from './sse.ts'` : null].filter(Boolean).join("\n") || void 0;
|
|
1028
|
-
}
|
|
1029
|
-
/**
|
|
1030
|
-
* Writes the footer of the `.kubb/client.ts` the plugin injected, the one place it is built, and copies `sse.ts` into
|
|
1031
|
-
* `.kubb/` only for a document with a `text/event-stream` operation, so a document without streams ships no stream runtime.
|
|
1032
|
-
* `sse` is set by the plugins that ship the runtime, so a custom client from `@kubb/plugin-client` gets neither file.
|
|
1033
|
-
*/
|
|
1034
|
-
function StreamRuntime({ nodes, root, sse, baseURL }) {
|
|
1035
|
-
if (!sse) return null;
|
|
1036
|
-
const streams = nodes.some(isEventStream);
|
|
1037
|
-
const footer = buildClientFooter({
|
|
1038
|
-
baseURL,
|
|
1039
|
-
sse: streams
|
|
1040
|
-
});
|
|
1041
|
-
return /* @__PURE__ */ jsxs(Fragment, { children: [streams && /* @__PURE__ */ jsx(File, {
|
|
1042
|
-
baseName: "sse.ts",
|
|
1043
|
-
path: path.resolve(root, ".kubb/sse.ts"),
|
|
1044
|
-
copy: sse
|
|
1045
|
-
}), footer && /* @__PURE__ */ jsx(File, {
|
|
1046
|
-
baseName: "client.ts",
|
|
1047
|
-
path: path.resolve(root, ".kubb/client.ts"),
|
|
1048
|
-
footer
|
|
1049
|
-
})] });
|
|
1050
|
-
}
|
|
1051
|
-
// ../../internals/client/src/resolveOperationTypes.ts
|
|
1052
|
-
/**
|
|
1053
|
-
* A `plugin-ts` shaped resolver whose operation type names point at the types `pluginZod({ inferred })`
|
|
1054
|
-
* exports next to each schema, so code written against `plugin-ts` names picks up the zod types.
|
|
1055
|
-
*/
|
|
1056
|
-
function toZodTypeResolver(resolver) {
|
|
1057
|
-
const type = (name) => resolver.schema.type(name);
|
|
1058
|
-
return {
|
|
1059
|
-
...resolver,
|
|
1060
|
-
file: resolver.file.bind(resolver),
|
|
1061
|
-
memo: (key, compute) => resolver.memo(`zodTypes:${key}`, compute),
|
|
1062
|
-
param: {
|
|
1063
|
-
name: (node, p) => type(resolver.param.name(node, p)),
|
|
1064
|
-
path: (node, p) => type(resolver.param.path(node, p)),
|
|
1065
|
-
query: (node, p) => type(resolver.param.query(node, p)),
|
|
1066
|
-
headers: (node, p) => type(resolver.param.headers(node, p))
|
|
1067
|
-
},
|
|
1068
|
-
response: {
|
|
1069
|
-
body: (node) => type(resolver.response.body(node)),
|
|
1070
|
-
status: (node, statusCode) => type(resolver.response.status(node, statusCode)),
|
|
1071
|
-
response: (node) => type(resolver.response.response(node)),
|
|
1072
|
-
responses: (node) => type(resolver.response.responses(node)),
|
|
1073
|
-
options: (node) => resolver.response.options(node)
|
|
1074
|
-
}
|
|
1075
|
-
};
|
|
1076
|
-
}
|
|
1077
|
-
/**
|
|
1078
|
-
* Picks the plugin generated clients, query hooks, and MCP handlers take their operation types from.
|
|
1079
|
-
*
|
|
1080
|
-
* `pluginZod({ inferred: 'codec' })` wins, so a codec param takes its domain value. Otherwise `plugin-ts`
|
|
1081
|
-
* wins when it is in the pipeline, and without it any `pluginZod({ inferred })`. Throws
|
|
1082
|
-
* `KUBB_MISSING_DEPENDENCY` for `pluginName` when neither is available.
|
|
1083
|
-
*
|
|
1084
|
-
* @example
|
|
1085
|
-
* ```ts
|
|
1086
|
-
* const types = resolveOperationTypes(ctx.driver, ctx.plugin.name)
|
|
1087
|
-
* types.response.options(node) // 'GetPetByIdOptions', or 'GetPetByIdOptionsSchemaType' from zod
|
|
1088
|
-
* ```
|
|
1089
|
-
*/
|
|
1090
|
-
function resolveOperationTypes(driver, pluginName) {
|
|
1091
|
-
return driver.memo(`operationTypes:${pluginName}`, () => {
|
|
1092
|
-
const pluginTs = driver.getPlugin(pluginTsName);
|
|
1093
|
-
const pluginZod = driver.getPlugin(pluginZodName);
|
|
1094
|
-
if (pluginTs && pluginZod?.options?.inferred !== "codec") {
|
|
1095
|
-
const resolver = driver.getResolver(pluginTsName);
|
|
1096
|
-
return {
|
|
1097
|
-
response: resolver.response,
|
|
1098
|
-
resolver,
|
|
1099
|
-
output: pluginTs.options?.output,
|
|
1100
|
-
group: pluginTs.options?.group ?? void 0
|
|
1101
|
-
};
|
|
1102
|
-
}
|
|
1103
|
-
if (!pluginZod?.options?.inferred) throw new Diagnostics.Error({
|
|
1104
|
-
code: Diagnostics.code.missingDependency,
|
|
1105
|
-
severity: "error",
|
|
1106
|
-
message: `Plugin "${pluginName}" has no source for operation types.`,
|
|
1107
|
-
help: `Add \`pluginTs()\` before "${pluginName}", or add \`pluginZod({ inferred: true })\`.`,
|
|
1108
|
-
location: { kind: "config" },
|
|
1109
|
-
plugin: pluginName
|
|
1110
|
-
});
|
|
1111
|
-
const resolver = toZodTypeResolver(driver.getResolver(pluginZodName));
|
|
1112
|
-
return {
|
|
1113
|
-
response: resolver.response,
|
|
1114
|
-
resolver,
|
|
1115
|
-
output: pluginZod.options.output,
|
|
1116
|
-
group: pluginZod.options.group ?? void 0
|
|
1117
|
-
};
|
|
1118
|
-
});
|
|
1119
|
-
}
|
|
1120
|
-
// ../../internals/client/src/generators/clientGenerator.tsx
|
|
1121
|
-
/**
|
|
1122
|
-
* Builds the built-in per-operation generator shared by the client plugins (`@kubb/plugin-fetch`,
|
|
1123
|
-
* `@kubb/plugin-axios`). Emits one async function per OpenAPI operation using the shared
|
|
1124
|
-
* `Operation` component: a grouped `<Name>Request` type and a function that forwards a single
|
|
1125
|
-
* `options` object to the bundled `client` and returns the `Unwrappable<RequestResult>`. Only the
|
|
1126
|
-
* generator `name` and the runtime templates differ between plugins. Every other resolution, import,
|
|
1127
|
-
* and rendering step is identical.
|
|
1128
|
-
*
|
|
1129
|
-
* `sse` is the Server-Sent Events runtime template, copied into `.kubb/sse.ts` only for a document with a
|
|
1130
|
-
* `text/event-stream` operation.
|
|
1131
|
-
*/
|
|
1132
|
-
function createClientGenerator(name, { sse } = {}) {
|
|
1133
|
-
return defineGenerator({
|
|
1134
|
-
name,
|
|
1135
|
-
renderer: jsxRenderer,
|
|
1136
|
-
operations(nodes, ctx) {
|
|
1137
|
-
return /* @__PURE__ */ jsx(StreamRuntime, {
|
|
1138
|
-
nodes,
|
|
1139
|
-
root: ctx.root,
|
|
1140
|
-
sse,
|
|
1141
|
-
baseURL: ctx.options.baseURL
|
|
1142
|
-
});
|
|
1143
|
-
},
|
|
1144
|
-
operation(node, ctx) {
|
|
1145
|
-
if (!ast.isHttpOperationNode(node)) return null;
|
|
1146
|
-
const { config, driver, resolver, root } = ctx;
|
|
1147
|
-
const { output, validator, returnType, throwOnErrorDefault, group, importPath } = ctx.options;
|
|
1148
|
-
const types = resolveOperationTypes(driver, ctx.plugin.name);
|
|
1149
|
-
const pluginZod = isValidatorEnabled(validator) ? driver.getPlugin(pluginZodName) : null;
|
|
1150
|
-
const zodResolver = pluginZod ? driver.getResolver(pluginZodName) : null;
|
|
1151
|
-
const importedTypeNames = [types.response.options(node), types.response.responses(node)];
|
|
1152
|
-
const validatorHooks = buildValidatorHooks({
|
|
1153
|
-
node,
|
|
1154
|
-
validator,
|
|
1155
|
-
zodResolver
|
|
1156
|
-
});
|
|
1157
|
-
const importedZodNames = validatorHooks.importedZodNames;
|
|
1158
|
-
const operationName = resolver.name(node.operationId);
|
|
1159
|
-
const meta = {
|
|
1160
|
-
name: resolver.resolveName({
|
|
1161
|
-
key: operationKey(node),
|
|
1162
|
-
name: operationName
|
|
1163
|
-
}),
|
|
1164
|
-
file: resolver.file({
|
|
1165
|
-
...operationFileEntry(node, node.operationId),
|
|
1166
|
-
root,
|
|
1167
|
-
output,
|
|
1168
|
-
group: group ?? void 0
|
|
1169
|
-
}),
|
|
1170
|
-
fileTypes: resolveDependencyOperationFile({
|
|
1171
|
-
memo: ctx.memo,
|
|
1172
|
-
node,
|
|
1173
|
-
resolver: types.resolver,
|
|
1174
|
-
root,
|
|
1175
|
-
output: types.output ?? output,
|
|
1176
|
-
group: types.group
|
|
1177
|
-
}),
|
|
1178
|
-
fileZod: zodResolver && pluginZod?.options ? zodResolver.file({
|
|
1179
|
-
...operationFileEntry(node, node.operationId),
|
|
1180
|
-
root,
|
|
1181
|
-
output: pluginZod.options.output ?? output,
|
|
1182
|
-
group: pluginZod.options?.group ?? void 0
|
|
1183
|
-
}) : null
|
|
1184
|
-
};
|
|
1185
|
-
const security = getOperationSecurity({
|
|
1186
|
-
document: ctx.adapter.document,
|
|
1187
|
-
method: node.method,
|
|
1188
|
-
path: node.path
|
|
1189
|
-
});
|
|
1190
|
-
const clientPath = importPath ?? path.resolve(root, ".kubb/client.ts");
|
|
1191
|
-
const clientRoot = importPath === void 0 ? meta.file.path : void 0;
|
|
1192
|
-
const standardSchemaPath = importPath ?? path.resolve(root, ".kubb/standardSchema.ts");
|
|
1193
|
-
const ssePath = importPath ?? path.resolve(root, ".kubb/sse.ts");
|
|
1194
|
-
const stream = getEventStream(node);
|
|
1195
|
-
const clientValueImports = (() => {
|
|
1196
|
-
if (returnType === "plain") return ["client"];
|
|
1197
|
-
if (returnType === "data") return ["client", "unwrapResult"];
|
|
1198
|
-
return ["client", "withUnwrap"];
|
|
1199
|
-
})();
|
|
1200
|
-
const clientTypeImports = (() => {
|
|
1201
|
-
if (stream) return {
|
|
1202
|
-
plain: ["Options", "SuccessOf"],
|
|
1203
|
-
data: ["SuccessOf"],
|
|
1204
|
-
full: ["SuccessOf", "Unwrappable"]
|
|
1205
|
-
}[returnType];
|
|
1206
|
-
if (returnType === "plain") return ["Options", "RequestResult"];
|
|
1207
|
-
if (returnType === "data") return ["Options", "UnwrappedResult"];
|
|
1208
|
-
return [
|
|
1209
|
-
"Options",
|
|
1210
|
-
"Unwrappable",
|
|
1211
|
-
"RequestResult"
|
|
1212
|
-
];
|
|
1213
|
-
})();
|
|
1214
|
-
return /* @__PURE__ */ jsxs(File, {
|
|
1215
|
-
baseName: meta.file.baseName,
|
|
1216
|
-
path: meta.file.path,
|
|
1217
|
-
meta: meta.file.meta,
|
|
1218
|
-
banner: resolver.default.banner(ctx.meta, {
|
|
1219
|
-
output,
|
|
1220
|
-
config,
|
|
1221
|
-
file: {
|
|
1222
|
-
path: meta.file.path,
|
|
1223
|
-
baseName: meta.file.baseName
|
|
1224
|
-
}
|
|
1225
|
-
}),
|
|
1226
|
-
footer: resolver.default.footer(ctx.meta, {
|
|
1227
|
-
output,
|
|
1228
|
-
config,
|
|
1229
|
-
file: {
|
|
1230
|
-
path: meta.file.path,
|
|
1231
|
-
baseName: meta.file.baseName
|
|
1232
|
-
}
|
|
1233
|
-
}),
|
|
1234
|
-
children: [
|
|
1235
|
-
/* @__PURE__ */ jsx(File.Import, {
|
|
1236
|
-
name: clientValueImports,
|
|
1237
|
-
root: clientRoot,
|
|
1238
|
-
path: clientPath
|
|
1239
|
-
}),
|
|
1240
|
-
/* @__PURE__ */ jsx(File.Import, {
|
|
1241
|
-
name: clientTypeImports,
|
|
1242
|
-
root: clientRoot,
|
|
1243
|
-
path: clientPath,
|
|
1244
|
-
isTypeOnly: true
|
|
1245
|
-
}),
|
|
1246
|
-
stream && /* @__PURE__ */ jsx(File.Import, {
|
|
1247
|
-
name: ["toEventStream"],
|
|
1248
|
-
root: clientRoot,
|
|
1249
|
-
path: ssePath
|
|
1250
|
-
}),
|
|
1251
|
-
stream && /* @__PURE__ */ jsx(File.Import, {
|
|
1252
|
-
name: buildEventStreamImports({
|
|
1253
|
-
returnType,
|
|
1254
|
-
typed: stream === "event"
|
|
1255
|
-
}),
|
|
1256
|
-
root: clientRoot,
|
|
1257
|
-
path: ssePath,
|
|
1258
|
-
isTypeOnly: true
|
|
1259
|
-
}),
|
|
1260
|
-
meta.fileTypes && importedTypeNames.length > 0 && /* @__PURE__ */ jsx(File.Import, {
|
|
1261
|
-
name: Array.from(new Set(importedTypeNames)),
|
|
1262
|
-
root: meta.file.path,
|
|
1263
|
-
path: meta.fileTypes.path,
|
|
1264
|
-
isTypeOnly: true
|
|
1265
|
-
}),
|
|
1266
|
-
meta.fileZod && importedZodNames.length > 0 && /* @__PURE__ */ jsx(File.Import, {
|
|
1267
|
-
name: importedZodNames,
|
|
1268
|
-
root: meta.file.path,
|
|
1269
|
-
path: meta.fileZod.path
|
|
1270
|
-
}),
|
|
1271
|
-
validatorHooks.headers && /* @__PURE__ */ jsx(File.Import, {
|
|
1272
|
-
name: ["toHeadersStandardSchema"],
|
|
1273
|
-
root: clientRoot,
|
|
1274
|
-
path: standardSchemaPath
|
|
1275
|
-
}),
|
|
1276
|
-
/* @__PURE__ */ jsx(Operation, {
|
|
1277
|
-
name: meta.name,
|
|
1278
|
-
node,
|
|
1279
|
-
types,
|
|
1280
|
-
validatorHooks,
|
|
1281
|
-
returnType,
|
|
1282
|
-
throwOnErrorDefault,
|
|
1283
|
-
security
|
|
1284
|
-
})
|
|
1285
|
-
]
|
|
1286
|
-
});
|
|
1287
|
-
}
|
|
1288
|
-
});
|
|
1289
|
-
}
|
|
1290
|
-
// ../../internals/client/src/components/SdkClient.tsx
|
|
1291
|
-
/**
|
|
1292
|
-
* Builds one instance method of a generated SDK class; a per-call `options.client` overrides `this.client`.
|
|
1293
|
-
*/
|
|
1294
|
-
function buildSdkMethod({ node, name, types, validatorHooks, security, returnType, throwOnErrorDefault }) {
|
|
1295
|
-
if (!ast.isHttpOperationNode(node)) return "";
|
|
1296
|
-
const body = buildOperationBody({
|
|
1297
|
-
node,
|
|
1298
|
-
types,
|
|
1299
|
-
validatorHooks,
|
|
1300
|
-
security,
|
|
1301
|
-
returnType,
|
|
1302
|
-
throwOnErrorDefault
|
|
1303
|
-
});
|
|
1304
|
-
const generics = body.generics.length ? `<${body.generics.join(", ")}>` : "";
|
|
1305
|
-
const jsdoc = buildJSDoc(buildOperationComments(node, {
|
|
1306
|
-
link: "urlPath",
|
|
1307
|
-
linkPosition: "beforeDeprecated",
|
|
1308
|
-
splitLines: true
|
|
1309
|
-
}));
|
|
1310
|
-
const methodBody = [
|
|
1311
|
-
body.mergeContentType ? "const { client: request = this.client, contentType, ...config } = options" : "const { client: request = this.client, ...config } = options",
|
|
1312
|
-
"",
|
|
1313
|
-
body.statement
|
|
1314
|
-
].map((line) => line ? ` ${line}` : "").join("\n");
|
|
1315
|
-
return `${jsdoc} public ${name}${generics}(${body.paramsSignature}): ${body.returnType} {\n${methodBody}\n }`;
|
|
1316
|
-
}
|
|
1317
|
-
/**
|
|
1318
|
-
* Renders one instance class per tag with one method per operation. The constructor takes a client
|
|
1319
|
-
* config object and builds its own client through `createClient`, so each environment is a separate
|
|
1320
|
-
* instance: `const api = new PetClient({ baseURL }); api.getPetById(...)`. A per-call `client` option
|
|
1321
|
-
* still overrides the instance client for a one-off call.
|
|
1322
|
-
*/
|
|
1323
|
-
function SdkClient({ name, operations, returnType, throwOnErrorDefault }) {
|
|
1324
|
-
const methods = operations.map(({ node, name: methodName, types, validatorHooks, security }) => buildSdkMethod({
|
|
1325
|
-
node,
|
|
1326
|
-
name: methodName,
|
|
1327
|
-
types,
|
|
1328
|
-
validatorHooks,
|
|
1329
|
-
security,
|
|
1330
|
-
returnType,
|
|
1331
|
-
throwOnErrorDefault
|
|
1332
|
-
}));
|
|
1333
|
-
const classCode = `export class ${name} {\n${[
|
|
1334
|
-
" private readonly client: ClientInstance",
|
|
1335
|
-
"",
|
|
1336
|
-
" constructor(config: ClientConfig = {}) {",
|
|
1337
|
-
" this.client = createClient(config)",
|
|
1338
|
-
" }"
|
|
1339
|
-
].join("\n")}\n\n${methods.join("\n\n")}\n}`;
|
|
1340
|
-
return /* @__PURE__ */ jsx(File.Source, {
|
|
1341
|
-
name,
|
|
1342
|
-
isExportable: true,
|
|
1343
|
-
isIndexable: true,
|
|
1344
|
-
children: classCode
|
|
1345
|
-
});
|
|
1346
|
-
}
|
|
1347
|
-
// ../../internals/client/src/components/SdkFacade.tsx
|
|
1348
|
-
/**
|
|
1349
|
-
* Renders a composed root SDK class that instantiates every tag client from one shared config, so
|
|
1350
|
-
* `new PetStore({ baseURL }).petClient.getPetById(...)` reaches an operation through a single entry
|
|
1351
|
-
* point bound to one environment. The per-tag clients are read-only fields built in the constructor.
|
|
1352
|
-
*/
|
|
1353
|
-
function SdkFacade({ name, members }) {
|
|
1354
|
-
const fields = members.map((member) => ` readonly ${member.propName}: ${member.className}`);
|
|
1355
|
-
const assignments = members.map((member) => ` this.${member.propName} = new ${member.className}(config)`);
|
|
1356
|
-
const classCode = `export class ${name} {\n${[
|
|
1357
|
-
...fields,
|
|
1358
|
-
"",
|
|
1359
|
-
" constructor(config: ClientConfig = {}) {",
|
|
1360
|
-
...assignments,
|
|
1361
|
-
" }"
|
|
1362
|
-
].join("\n")}\n}`;
|
|
1363
|
-
return /* @__PURE__ */ jsx(File.Source, {
|
|
1364
|
-
name,
|
|
1365
|
-
isExportable: true,
|
|
1366
|
-
isIndexable: true,
|
|
1367
|
-
children: classCode
|
|
1368
|
-
});
|
|
1369
|
-
}
|
|
1370
|
-
// ../../internals/client/src/generators/sdkGenerator.tsx
|
|
1371
|
-
function resolveTypeImportNames(node, types) {
|
|
1372
|
-
return [types.response.options(node), types.response.responses(node)];
|
|
1373
|
-
}
|
|
1374
|
-
/**
|
|
1375
|
-
* Groups operations into one controller per tag. Operations without a tag fall back to a single
|
|
1376
|
-
* `Client`/`ApiClient` controller, matching the resolver's default naming.
|
|
1377
|
-
*/
|
|
1378
|
-
function buildControllers(nodes, ctx, types, validatorEnabled) {
|
|
1379
|
-
const { driver, resolver, root } = ctx;
|
|
1380
|
-
const { output, group, validator } = ctx.options;
|
|
1381
|
-
const pluginZod = validatorEnabled ? driver.getPlugin(pluginZodName) : null;
|
|
1382
|
-
const zodResolver = pluginZod ? driver.getResolver(pluginZodName) : null;
|
|
1383
|
-
const document = ctx.adapter.document;
|
|
1384
|
-
function buildOperationData(node) {
|
|
1385
|
-
const typeFile = types.resolver.file({
|
|
1386
|
-
...operationFileEntry(node, node.operationId),
|
|
1387
|
-
root,
|
|
1388
|
-
output: types.output ?? output,
|
|
1389
|
-
group: types.group
|
|
1390
|
-
});
|
|
1391
|
-
const zodFile = zodResolver && pluginZod?.options ? zodResolver.file({
|
|
1392
|
-
...operationFileEntry(node, node.operationId),
|
|
1393
|
-
root,
|
|
1394
|
-
output: pluginZod.options?.output ?? output,
|
|
1395
|
-
group: pluginZod.options?.group ?? void 0
|
|
1396
|
-
}) : null;
|
|
1397
|
-
const security = ast.isHttpOperationNode(node) ? getOperationSecurity({
|
|
1398
|
-
document,
|
|
1399
|
-
method: node.method,
|
|
1400
|
-
path: node.path
|
|
1401
|
-
}) : void 0;
|
|
1402
|
-
const validatorHooks = zodResolver ? buildValidatorHooks({
|
|
1403
|
-
node,
|
|
1404
|
-
validator,
|
|
1405
|
-
zodResolver
|
|
1406
|
-
}) : null;
|
|
1407
|
-
const name = resolver.name(node.operationId);
|
|
1408
|
-
return {
|
|
1409
|
-
node,
|
|
1410
|
-
name: resolver.resolveName({
|
|
1411
|
-
key: operationKey(node),
|
|
1412
|
-
name
|
|
1413
|
-
}),
|
|
1414
|
-
types,
|
|
1415
|
-
typeFile,
|
|
1416
|
-
zodFile,
|
|
1417
|
-
validatorHooks,
|
|
1418
|
-
security
|
|
1419
|
-
};
|
|
1420
|
-
}
|
|
1421
|
-
return nodes.reduce((acc, operationNode) => {
|
|
1422
|
-
if (!ast.isHttpOperationNode(operationNode)) return acc;
|
|
1423
|
-
const tag = operationNode.tags[0];
|
|
1424
|
-
const name = tag ? ensureValidVarName(group?.name?.({ group: camelCase(tag) }) ?? resolver.groupName(tag)) : resolver.className("ApiClient");
|
|
1425
|
-
const file = resolver.file({
|
|
1426
|
-
name,
|
|
1427
|
-
extname: ".ts",
|
|
1428
|
-
tag,
|
|
1429
|
-
root,
|
|
1430
|
-
output,
|
|
1431
|
-
group: group ?? void 0
|
|
1432
|
-
});
|
|
1433
|
-
const operationData = buildOperationData(operationNode);
|
|
1434
|
-
const previous = acc.find((item) => item.file.path === file.path);
|
|
1435
|
-
if (previous) previous.operations.push(operationData);
|
|
1436
|
-
else acc.push({
|
|
1437
|
-
name,
|
|
1438
|
-
tag,
|
|
1439
|
-
file,
|
|
1440
|
-
operations: [operationData]
|
|
1441
|
-
});
|
|
1442
|
-
return acc;
|
|
1443
|
-
}, []);
|
|
1444
|
-
}
|
|
1445
|
-
function collectImportsByFile(ops, pick) {
|
|
1446
|
-
const namesByPath = /* @__PURE__ */ new Map();
|
|
1447
|
-
const filesByPath = /* @__PURE__ */ new Map();
|
|
1448
|
-
ops.forEach((op) => {
|
|
1449
|
-
const { file, names } = pick(op);
|
|
1450
|
-
if (!file || names.length === 0) return;
|
|
1451
|
-
if (!namesByPath.has(file.path)) namesByPath.set(file.path, /* @__PURE__ */ new Set());
|
|
1452
|
-
const set = namesByPath.get(file.path);
|
|
1453
|
-
names.forEach((n) => set.add(n));
|
|
1454
|
-
filesByPath.set(file.path, file);
|
|
1455
|
-
});
|
|
1456
|
-
return {
|
|
1457
|
-
namesByPath,
|
|
1458
|
-
filesByPath
|
|
1459
|
-
};
|
|
1460
|
-
}
|
|
1461
|
-
/**
|
|
1462
|
-
* Builds the class-based SDK generator for a client plugin (`@kubb/plugin-fetch`,
|
|
1463
|
-
* `@kubb/plugin-axios`). Only registered when `sdk` is set; otherwise the plugin keeps its
|
|
1464
|
-
* standalone per-operation functions.
|
|
1465
|
-
*
|
|
1466
|
-
* Every tag client is an instance class whose constructor takes a client config and builds its own
|
|
1467
|
-
* client, so each environment is a separate instance. With `sdk.mode: 'tag'` (the default) it
|
|
1468
|
-
* emits one class per tag and, when `sdk.name` is set, a composed root that instantiates every tag
|
|
1469
|
-
* client. With `sdk.mode: 'flat'` it emits one class named by `sdk.name`, with every operation as a
|
|
1470
|
-
* direct method.
|
|
1471
|
-
*
|
|
1472
|
-
* `sse` is the Server-Sent Events runtime template, copied into `.kubb/sse.ts` only for a document with a
|
|
1473
|
-
* `text/event-stream` operation.
|
|
1474
|
-
*/
|
|
1475
|
-
function createSdkGenerator({ sse } = {}) {
|
|
1476
|
-
return defineGenerator({
|
|
1477
|
-
name: "sdk",
|
|
1478
|
-
renderer: jsxRenderer,
|
|
1479
|
-
operations(nodes, ctx) {
|
|
1480
|
-
const { config, resolver, root } = ctx;
|
|
1481
|
-
const { output, group, validator, returnType, throwOnErrorDefault, sdk } = ctx.options;
|
|
1482
|
-
if (!sdk) return null;
|
|
1483
|
-
if (sdk.mode === "tag" && output.mode === "file") throw new Diagnostics.Error({
|
|
1484
|
-
code: Diagnostics.code.invalidPluginOptions,
|
|
1485
|
-
severity: "error",
|
|
1486
|
-
message: `Plugin "${ctx.plugin.name}" resolves \`output.mode\` to 'file' but also configures \`sdk.mode: 'tag'\`.`,
|
|
1487
|
-
help: "A single-file output has nothing to split tag classes into. Set `sdk.mode: 'flat'` to emit one class into that file, or give `output.path` an extensionless directory name (or set `output.mode: 'directory'` explicitly) so each tag gets its own file.",
|
|
1488
|
-
location: { kind: "config" },
|
|
1489
|
-
plugin: ctx.plugin.name
|
|
1490
|
-
});
|
|
1491
|
-
const types = resolveOperationTypes(ctx.driver, ctx.plugin.name);
|
|
1492
|
-
const validatorEnabled = isValidatorEnabled(validator);
|
|
1493
|
-
const controllers = buildControllers(nodes, ctx, types, validatorEnabled);
|
|
1494
|
-
const clientPath = path.resolve(root, ".kubb/client.ts");
|
|
1495
|
-
const standardSchemaPath = path.resolve(root, ".kubb/standardSchema.ts");
|
|
1496
|
-
const ssePath = path.resolve(root, ".kubb/sse.ts");
|
|
1497
|
-
const banner = (file) => resolver.default.banner(ctx.meta, {
|
|
1498
|
-
output,
|
|
1499
|
-
config,
|
|
1500
|
-
file: {
|
|
1501
|
-
path: file.path,
|
|
1502
|
-
baseName: file.baseName
|
|
1503
|
-
}
|
|
1504
|
-
});
|
|
1505
|
-
const footer = (file) => resolver.default.footer(ctx.meta, {
|
|
1506
|
-
output,
|
|
1507
|
-
config,
|
|
1508
|
-
file: {
|
|
1509
|
-
path: file.path,
|
|
1510
|
-
baseName: file.baseName
|
|
1511
|
-
}
|
|
1512
|
-
});
|
|
1513
|
-
const renderClassFile = (className, file, ops) => {
|
|
1514
|
-
const { namesByPath: typeNamesByPath, filesByPath: typeFilesByPath } = collectImportsByFile(ops, (op) => ({
|
|
1515
|
-
file: op.typeFile,
|
|
1516
|
-
names: resolveTypeImportNames(op.node, op.types)
|
|
1517
|
-
}));
|
|
1518
|
-
const { namesByPath: zodNamesByPath, filesByPath: zodFilesByPath } = validatorEnabled ? collectImportsByFile(ops, (op) => ({
|
|
1519
|
-
file: op.zodFile,
|
|
1520
|
-
names: op.validatorHooks?.importedZodNames ?? []
|
|
1521
|
-
})) : {
|
|
1522
|
-
namesByPath: /* @__PURE__ */ new Map(),
|
|
1523
|
-
filesByPath: /* @__PURE__ */ new Map()
|
|
1524
|
-
};
|
|
1525
|
-
const shapes = new Set(ops.map((op) => getEventStream(op.node)).filter(Boolean));
|
|
1526
|
-
const hasEventStream = shapes.size > 0;
|
|
1527
|
-
const hasResult = ops.some((op) => !isEventStream(op.node));
|
|
1528
|
-
return /* @__PURE__ */ jsxs(File, {
|
|
1529
|
-
baseName: file.baseName,
|
|
1530
|
-
path: file.path,
|
|
1531
|
-
meta: file.meta,
|
|
1532
|
-
banner: banner(file),
|
|
1533
|
-
footer: footer(file),
|
|
1534
|
-
children: [
|
|
1535
|
-
/* @__PURE__ */ jsx(File.Import, {
|
|
1536
|
-
name: ["createClient", returnType === "data" ? "unwrapResult" : "withUnwrap"],
|
|
1537
|
-
root: file.path,
|
|
1538
|
-
path: clientPath
|
|
1539
|
-
}),
|
|
1540
|
-
/* @__PURE__ */ jsx(File.Import, {
|
|
1541
|
-
name: [
|
|
1542
|
-
"ClientConfig",
|
|
1543
|
-
"ClientInstance",
|
|
1544
|
-
"Options",
|
|
1545
|
-
...returnType === "data" ? [] : ["Unwrappable"],
|
|
1546
|
-
...hasResult ? [returnType === "data" ? "UnwrappedResult" : "RequestResult"] : [],
|
|
1547
|
-
...hasEventStream ? ["SuccessOf"] : []
|
|
1548
|
-
],
|
|
1549
|
-
root: file.path,
|
|
1550
|
-
path: clientPath,
|
|
1551
|
-
isTypeOnly: true
|
|
1552
|
-
}),
|
|
1553
|
-
hasEventStream && /* @__PURE__ */ jsx(File.Import, {
|
|
1554
|
-
name: ["toEventStream"],
|
|
1555
|
-
root: file.path,
|
|
1556
|
-
path: ssePath
|
|
1557
|
-
}),
|
|
1558
|
-
hasEventStream && /* @__PURE__ */ jsx(File.Import, {
|
|
1559
|
-
name: buildEventStreamImports({
|
|
1560
|
-
returnType,
|
|
1561
|
-
typed: shapes.has("event")
|
|
1562
|
-
}),
|
|
1563
|
-
root: file.path,
|
|
1564
|
-
path: ssePath,
|
|
1565
|
-
isTypeOnly: true
|
|
1566
|
-
}),
|
|
1567
|
-
validator === "zod" && ops.some((op) => op.node.requestBody?.content?.[0]?.schema != null) && /* @__PURE__ */ jsx(File.Import, {
|
|
1568
|
-
name: ["z"],
|
|
1569
|
-
path: "zod",
|
|
1570
|
-
isTypeOnly: true
|
|
1571
|
-
}),
|
|
1572
|
-
Array.from(typeNamesByPath.entries()).map(([filePath, set]) => /* @__PURE__ */ jsx(File.Import, {
|
|
1573
|
-
name: Array.from(set),
|
|
1574
|
-
root: file.path,
|
|
1575
|
-
path: typeFilesByPath.get(filePath).path,
|
|
1576
|
-
isTypeOnly: true
|
|
1577
|
-
}, filePath)),
|
|
1578
|
-
validatorEnabled && Array.from(zodNamesByPath.entries()).map(([filePath, set]) => /* @__PURE__ */ jsx(File.Import, {
|
|
1579
|
-
name: Array.from(set),
|
|
1580
|
-
root: file.path,
|
|
1581
|
-
path: zodFilesByPath.get(filePath).path
|
|
1582
|
-
}, filePath)),
|
|
1583
|
-
ops.some((op) => op.validatorHooks?.headers) && /* @__PURE__ */ jsx(File.Import, {
|
|
1584
|
-
name: ["toHeadersStandardSchema"],
|
|
1585
|
-
root: file.path,
|
|
1586
|
-
path: standardSchemaPath
|
|
1587
|
-
}),
|
|
1588
|
-
/* @__PURE__ */ jsx(SdkClient, {
|
|
1589
|
-
name: className,
|
|
1590
|
-
operations: ops,
|
|
1591
|
-
returnType,
|
|
1592
|
-
throwOnErrorDefault
|
|
1593
|
-
})
|
|
1594
|
-
]
|
|
1595
|
-
}, file.path);
|
|
1596
|
-
};
|
|
1597
|
-
const renderFacadeFile = (name) => {
|
|
1598
|
-
const sdkFile = resolver.file({
|
|
1599
|
-
name,
|
|
1600
|
-
extname: ".ts",
|
|
1601
|
-
root,
|
|
1602
|
-
output,
|
|
1603
|
-
group: group ?? void 0
|
|
1604
|
-
});
|
|
1605
|
-
const members = controllers.map(({ name, tag }) => ({
|
|
1606
|
-
className: name,
|
|
1607
|
-
propName: resolver.propertyName(tag ?? name)
|
|
1608
|
-
}));
|
|
1609
|
-
return /* @__PURE__ */ jsxs(File, {
|
|
1610
|
-
baseName: sdkFile.baseName,
|
|
1611
|
-
path: sdkFile.path,
|
|
1612
|
-
meta: sdkFile.meta,
|
|
1613
|
-
banner: banner(sdkFile),
|
|
1614
|
-
footer: footer(sdkFile),
|
|
1615
|
-
children: [
|
|
1616
|
-
/* @__PURE__ */ jsx(File.Import, {
|
|
1617
|
-
name: ["ClientConfig"],
|
|
1618
|
-
root: sdkFile.path,
|
|
1619
|
-
path: clientPath,
|
|
1620
|
-
isTypeOnly: true
|
|
1621
|
-
}),
|
|
1622
|
-
controllers.map(({ name, file }) => /* @__PURE__ */ jsx(File.Import, {
|
|
1623
|
-
name: [name],
|
|
1624
|
-
root: sdkFile.path,
|
|
1625
|
-
path: file.path
|
|
1626
|
-
}, name)),
|
|
1627
|
-
/* @__PURE__ */ jsx(SdkFacade, {
|
|
1628
|
-
name: resolver.className(name),
|
|
1629
|
-
members
|
|
1630
|
-
})
|
|
1631
|
-
]
|
|
1632
|
-
}, sdkFile.path);
|
|
1633
|
-
};
|
|
1634
|
-
const files = sdk.mode === "flat" ? renderClassFile(resolver.className(sdk.name ?? "sdk"), resolver.file({
|
|
1635
|
-
name: sdk.name ?? "sdk",
|
|
1636
|
-
extname: ".ts",
|
|
1637
|
-
root,
|
|
1638
|
-
output,
|
|
1639
|
-
group: group ?? void 0
|
|
1640
|
-
}), controllers.flatMap((controller) => controller.operations)) : [...controllers.map(({ name, file, operations: ops }) => renderClassFile(name, file, ops)), sdk.name && renderFacadeFile(sdk.name)];
|
|
1641
|
-
return /* @__PURE__ */ jsxs(Fragment, { children: [/* @__PURE__ */ jsx(StreamRuntime, {
|
|
1642
|
-
nodes,
|
|
1643
|
-
root,
|
|
1644
|
-
sse,
|
|
1645
|
-
baseURL: ctx.options.baseURL
|
|
1646
|
-
}), files] });
|
|
1647
|
-
}
|
|
1648
|
-
});
|
|
1649
|
-
}
|
|
1650
|
-
// ../../internals/client/src/macros.ts
|
|
1651
|
-
/**
|
|
1652
|
-
* Macros the client plugins apply by default, ahead of any user macros. `macroDedupeUnion`
|
|
1653
|
-
* drops repeated union members, keeping the generated response and error
|
|
1654
|
-
* unions tidy. A plugin wires them with `ctx.setMacros([...defaultMacros, ...userMacros])`.
|
|
1655
|
-
*/
|
|
1656
|
-
const defaultMacros = [macroDedupeUnion];
|
|
1657
|
-
// ../../internals/client/src/resolver.ts
|
|
1658
|
-
/**
|
|
1659
|
-
* Default resolver shared by the client plugins. Functions use safe camelCase identifiers and
|
|
1660
|
-
* files inherit the built-in casing; classes and tag groups use PascalCase.
|
|
1661
|
-
*
|
|
1662
|
-
* @example
|
|
1663
|
-
* ```ts
|
|
1664
|
-
* resolverClient.name('show pet by id') // 'showPetById'
|
|
1665
|
-
* resolverClient.groupName('pet') // 'PetClient'
|
|
1666
|
-
* ```
|
|
1667
|
-
*/
|
|
1668
|
-
const resolverClient = createResolver({
|
|
1669
|
-
pluginName: "plugin-contract-client",
|
|
1670
|
-
name: resolveOperationName,
|
|
1671
|
-
className(name) {
|
|
1672
|
-
return ensureValidVarName(pascalCase(name));
|
|
1673
|
-
},
|
|
1674
|
-
groupName(name) {
|
|
1675
|
-
return ensureValidVarName(pascalCase(`${name} Client`));
|
|
1676
|
-
},
|
|
1677
|
-
propertyName(name) {
|
|
1678
|
-
return ensureValidVarName(camelCase(name));
|
|
1679
|
-
}
|
|
1680
|
-
});
|
|
1681
|
-
// src/templates.ts
|
|
1682
|
-
const require = createRequire(import.meta.url);
|
|
1683
|
-
/** Absolute path to the fetch client template, copied into `.kubb/client.ts`. */
|
|
1684
|
-
const fetchClientTemplatePath = fileURLToPath(new URL("../templates/client.ts", import.meta.url));
|
|
1685
|
-
/**
|
|
1686
|
-
* Absolute paths to the runtime templates `@kubb/plugin-client` ships for every client plugin, copied into `.kubb/core.ts`,
|
|
1687
|
-
* `.kubb/serializers.ts`, `.kubb/standardSchema.ts`, and `.kubb/sse.ts`.
|
|
1688
|
-
*/
|
|
1689
|
-
const coreTemplatePath = require.resolve("@kubb/plugin-client/templates/core.ts");
|
|
1690
|
-
const serializersTemplatePath = require.resolve("@kubb/plugin-client/templates/serializers.ts");
|
|
1691
|
-
const standardSchemaTemplatePath = require.resolve("@kubb/plugin-client/templates/standardSchema.ts");
|
|
1692
|
-
const sseTemplatePath = require.resolve("@kubb/plugin-client/templates/sse.ts");
|
|
1693
|
-
// src/generators/clientGenerator.tsx
|
|
1694
|
-
/**
|
|
1695
|
-
* Built-in operation generator for `@kubb/plugin-fetch`. Emits one async function per OpenAPI
|
|
1696
|
-
* operation using the shared `Operation` component: a grouped `<Name>Request` type and a function
|
|
1697
|
-
* that forwards a single `options` object to the bundled `client` and returns the `RequestResult`.
|
|
1698
|
-
*/
|
|
1699
|
-
const clientGenerator = createClientGenerator("fetch", { sse: sseTemplatePath });
|
|
2
|
+
import { defineExtension } from "@kubb/kit";
|
|
3
|
+
import { clientDependencies, resolveClientOptions, setupClient } from "@kubb/plugin-client";
|
|
1700
4
|
// kubb-extension.json
|
|
1701
5
|
var compatibility = {
|
|
1702
|
-
"kubb": ">=5.
|
|
6
|
+
"kubb": ">=5.18.4",
|
|
1703
7
|
"node": ">=22.22"
|
|
1704
8
|
};
|
|
1705
9
|
// package.json
|
|
1706
|
-
var version = "
|
|
10
|
+
var version = "6.0.1";
|
|
1707
11
|
// src/plugin.ts
|
|
1708
12
|
/**
|
|
1709
13
|
* Canonical plugin name for `@kubb/plugin-fetch`. Used for driver lookups and cross-plugin
|
|
@@ -1739,59 +43,18 @@ const pluginFetch = defineExtension({
|
|
|
1739
43
|
version,
|
|
1740
44
|
compatibility
|
|
1741
45
|
},
|
|
1742
|
-
dependencies:
|
|
1743
|
-
[pluginTsName]: { optional: true },
|
|
1744
|
-
[pluginZodName]: { optional: !isValidatorEnabled(options.validator) }
|
|
1745
|
-
}),
|
|
46
|
+
dependencies: clientDependencies,
|
|
1746
47
|
setup(options) {
|
|
1747
|
-
const
|
|
1748
|
-
|
|
1749
|
-
|
|
1750
|
-
|
|
1751
|
-
|
|
1752
|
-
|
|
1753
|
-
output,
|
|
1754
|
-
exclude,
|
|
1755
|
-
include,
|
|
1756
|
-
override,
|
|
1757
|
-
group: createGroupConfig(group),
|
|
1758
|
-
baseURL,
|
|
1759
|
-
throwOnErrorDefault,
|
|
1760
|
-
validator,
|
|
1761
|
-
returnType,
|
|
1762
|
-
sdk: sdk ? {
|
|
1763
|
-
mode: sdk.mode ?? "tag",
|
|
1764
|
-
name: sdk.name
|
|
1765
|
-
} : void 0,
|
|
1766
|
-
resolver: userResolver ? Resolver.merge(resolverClient, userResolver) : resolverClient
|
|
1767
|
-
};
|
|
1768
|
-
setResolver(resolved.resolver);
|
|
1769
|
-
setMacros([...defaultMacros, ...options.macros ?? []]);
|
|
1770
|
-
addGenerator(resolved.sdk ? createSdkGenerator({ sse: sseTemplatePath }) : clientGenerator);
|
|
1771
|
-
const root = path.resolve(config.root, config.output.path);
|
|
1772
|
-
injectFile({
|
|
1773
|
-
baseName: "serializers.ts",
|
|
1774
|
-
path: path.resolve(root, ".kubb/serializers.ts"),
|
|
1775
|
-
copy: serializersTemplatePath
|
|
1776
|
-
});
|
|
1777
|
-
injectFile({
|
|
1778
|
-
baseName: "core.ts",
|
|
1779
|
-
path: path.resolve(root, ".kubb/core.ts"),
|
|
1780
|
-
copy: coreTemplatePath
|
|
1781
|
-
});
|
|
1782
|
-
injectFile({
|
|
1783
|
-
baseName: "client.ts",
|
|
1784
|
-
path: path.resolve(root, ".kubb/client.ts"),
|
|
1785
|
-
copy: fetchClientTemplatePath
|
|
1786
|
-
});
|
|
1787
|
-
injectFile({
|
|
1788
|
-
baseName: "standardSchema.ts",
|
|
1789
|
-
path: path.resolve(root, ".kubb/standardSchema.ts"),
|
|
1790
|
-
copy: standardSchemaTemplatePath
|
|
48
|
+
const resolved = resolveClientOptions(options);
|
|
49
|
+
setupClient({
|
|
50
|
+
name: "fetch",
|
|
51
|
+
resolved,
|
|
52
|
+
macros: options.macros,
|
|
53
|
+
client: fileURLToPath(new URL("../templates/client.ts", import.meta.url))
|
|
1791
54
|
});
|
|
1792
55
|
return resolved;
|
|
1793
56
|
}
|
|
1794
57
|
});
|
|
1795
|
-
export {
|
|
58
|
+
export { pluginFetch as default, pluginFetch, pluginFetchName };
|
|
1796
59
|
|
|
1797
60
|
//# sourceMappingURL=index.js.map
|