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