@kubb/plugin-client 5.12.0-canary.20261109T134631 → 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.
Files changed (72) hide show
  1. package/README.md +1 -1
  2. package/dist/client-Dq0z61hu.d.ts +554 -0
  3. package/dist/index.cjs +1396 -802
  4. package/dist/index.cjs.map +1 -1
  5. package/dist/index.d.cts +126 -74
  6. package/dist/index.d.ts +126 -74
  7. package/dist/index.js +1394 -804
  8. package/dist/index.js.map +1 -1
  9. package/dist/serializers-BRgfPGr1.d.ts +119 -0
  10. package/dist/{standardSchema-BShJWz2i.d.ts → standardSchema-CGuiSK7F.d.ts} +15 -37
  11. package/dist/templates/client.d.ts +3 -2
  12. package/dist/templates/core.d.ts +3 -3
  13. package/dist/templates/mediaTypes/formData.d.ts +5 -0
  14. package/dist/templates/mediaTypes/urlencoded.d.ts +5 -0
  15. package/dist/templates/mediaTypes.d.ts +2 -0
  16. package/dist/templates/serializers/cookies.d.ts +5 -0
  17. package/dist/templates/serializers/headers.d.ts +5 -0
  18. package/dist/templates/serializers/path.d.ts +5 -0
  19. package/dist/templates/serializers/pathStyles.d.ts +5 -0
  20. package/dist/templates/serializers/query.d.ts +5 -0
  21. package/dist/templates/serializers/queryStyles.d.ts +14 -0
  22. package/dist/templates/serializers.d.ts +2 -2
  23. package/dist/templates/sse.d.ts +20 -113
  24. package/dist/templates/standardSchema.d.ts +1 -1
  25. package/docs/guides/authentication.md +9 -1
  26. package/docs/guides/migrate-to-v6.md +139 -0
  27. package/docs/guides/validate-requests-and-responses.md +8 -3
  28. package/docs/guides/write-your-client.md +117 -11
  29. package/docs/options.md +24 -0
  30. package/docs/overview.md +1 -1
  31. package/kubb-extension.json +1 -1
  32. package/package.json +8 -7
  33. package/src/builders/callConfig.ts +80 -0
  34. package/src/builders/contentType.ts +35 -0
  35. package/src/builders/generics.ts +29 -0
  36. package/src/builders/operationBody.ts +43 -0
  37. package/src/builders/operationData.ts +64 -0
  38. package/src/builders/returnStatement.ts +103 -0
  39. package/src/builders/security.ts +125 -0
  40. package/src/builders/signature.ts +71 -0
  41. package/src/builders/styles.ts +94 -0
  42. package/src/builders/validator.ts +84 -0
  43. package/src/builders/validatorOptions.ts +53 -0
  44. package/src/clientTypes.ts +195 -0
  45. package/src/components/ClientRuntime.tsx +97 -0
  46. package/src/components/Operation.tsx +72 -0
  47. package/src/components/SdkClient.tsx +100 -0
  48. package/src/components/SdkFacade.tsx +35 -0
  49. package/src/generators/clientGenerator.tsx +91 -6
  50. package/src/generators/sdkGenerator.tsx +209 -0
  51. package/src/index.ts +3 -1
  52. package/src/macros.ts +9 -0
  53. package/src/plugin.ts +9 -9
  54. package/src/resolver.ts +28 -0
  55. package/src/setup.ts +90 -0
  56. package/src/types.ts +16 -2
  57. package/templates/client.ts +2 -1
  58. package/templates/core.ts +735 -286
  59. package/templates/mediaTypes/formData.ts +59 -0
  60. package/templates/mediaTypes/urlencoded.ts +33 -0
  61. package/templates/mediaTypes.ts +147 -0
  62. package/templates/serializers/cookies.ts +58 -0
  63. package/templates/serializers/headers.ts +35 -0
  64. package/templates/serializers/path.ts +19 -0
  65. package/templates/serializers/pathStyles.ts +27 -0
  66. package/templates/serializers/query.ts +21 -0
  67. package/templates/serializers/queryStyles.ts +107 -0
  68. package/templates/serializers.ts +48 -364
  69. package/templates/sse.ts +87 -178
  70. package/templates/standardSchema.ts +16 -37
  71. package/dist/client-BxO6Dygu.d.ts +0 -355
  72. 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
- // ../../internals/shared/src/operation.ts
34
- /**
35
- * Builds the `ResolverFileParams` every operation generator passes to
36
- * `resolver.file`: a file named `name`, tagged by the operation's first
37
- * tag (or `'default'`), at the operation's path. Centralizes the entry object
38
- * that was repeated at dozens of call sites across the client and query plugins.
39
- *
40
- * @example
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,353 +177,829 @@ function ensureValidVarName(name) {
447
177
  function isIdentifier(name) {
448
178
  return /^[a-zA-Z_$][a-zA-Z0-9_$]*$/.test(name);
449
179
  }
450
- // ../../internals/shared/src/resolver.ts
180
+ // ../../internals/utils/src/codegen.ts
451
181
  /**
452
- * The shared top-level `name` of the operation plugins, computed once per operation name and build.
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
+ * ```
453
190
  */
454
- function resolveOperationName(name) {
455
- return this.memo(`operation:${name}`, () => ensureValidVarName((0, _kubb_kit.camelCase)(name)));
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}`;
456
195
  }
457
196
  /**
458
- * Builds the allocation key for a declaration an operation owns. `scope` names which declaration
459
- * it is, so two of them never share an allocation even when a custom resolver gives both the same
460
- * name. Leave `scope` empty for the operation's own declaration.
197
+ * An object key, quoted only when it is not identifier-shaped. Reserved words such as `class` stay bare.
461
198
  *
462
199
  * @example
463
200
  * ```ts
464
- * resolver.resolveName({ key: operationKey(node, 'response', 'status', 404), name })
201
+ * objectKey('x-total') // "'x-total'"
465
202
  * ```
466
203
  */
467
- function operationKey(node, ...scope) {
468
- return [
469
- "operation",
470
- node.method,
471
- node.path,
472
- ...scope.map(String)
473
- ];
204
+ function objectKey(name) {
205
+ return isIdentifier(name) ? name : _kubb_kit.text.quote(name);
474
206
  }
475
- // ../../internals/shared/src/group.ts
476
207
  /**
477
- * Builds the `group` config a Kubb plugin passes to `ctx.setOptions`, applying the
478
- * shared default naming so every plugin groups output consistently:
479
- *
480
- * - `path` groups use the second path segment (`/pet/findByStatus` → `pet`).
481
- * - other groups use the camelCased group (`pet store` → `petStore`).
208
+ * A JSON value as a JavaScript literal, with single-quoted strings and identifier keys left unquoted.
482
209
  *
483
- * A user-provided `group.name` always wins over the default namer, so callers stay in
484
- * control of their output folders. Returns `null` when grouping is disabled, matching the
485
- * per-plugin convention.
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.
486
225
  *
487
- * @param group - The user-supplied group option, or `undefined` to disable grouping.
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.
488
240
  *
489
241
  * @example
490
242
  * ```ts
491
- * createGroupConfig(group) // shared across every plugin
243
+ * resolver.file(operationFileEntry(node, node.operationId), { root, output, group })
492
244
  * ```
493
245
  */
494
- function createGroupConfig(group) {
495
- if (!group) return null;
496
- const defaultName = (ctx) => {
497
- if (group.type === "path") return `${ctx.group.split("/")[1]}`;
498
- return (0, _kubb_kit.camelCase)(ctx.group);
499
- };
246
+ function operationFileEntry(node, name, extname = ".ts") {
500
247
  return {
501
- ...group,
502
- name: group.name ? group.name : defaultName
248
+ name,
249
+ extname,
250
+ tag: node.tags[0] ?? "default",
251
+ path: node.path
503
252
  };
504
253
  }
505
- // kubb-extension.json
506
- var compatibility = {
507
- "kubb": ">=5.16.0",
508
- "node": ">=22.22"
509
- };
510
- // package.json
511
- var version = "5.12.0-canary.20261109T134631";
512
- // ../../internals/client/src/builders/validatorOptions.ts
513
254
  /**
514
- * Returns `true` when any direction of the validator uses zod (used for dependency checks).
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
+ * ```
515
269
  */
516
- function isValidatorEnabled(validator) {
517
- if (!validator) return false;
518
- if (validator === "zod") return true;
519
- return Boolean(validator.request || validator.params || validator.response);
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
+ });
520
298
  }
521
299
  /**
522
- * Returns `'zod'` when request body parsing is enabled, `null` otherwise. The string shorthand
523
- * `'zod'` validates the response only, so it does not enable request parsing.
300
+ * The operation's method and path as plain text, such as `` `GET /pet/{petId}` ``. A path is not a valid `{@link}` target.
524
301
  */
525
- function resolveRequestValidator(validator) {
526
- if (!validator || validator === "zod") return null;
527
- return validator.request ?? null;
302
+ function getOperationRequestLine(node) {
303
+ return _kubb_kit.ast.http.isOperation(node) && node.path ? `\`${node.method.toUpperCase()} ${node.path}\`` : null;
528
304
  }
529
305
  /**
530
- * Returns `'zod'` when path, query, and header params parsing is enabled, `null` otherwise. Only the
531
- * object form `{ params: 'zod' }` enables it.
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.
532
308
  */
533
- function resolveParamsValidator(validator) {
534
- if (!validator || validator === "zod") return null;
535
- return validator.params ?? null;
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) ?? []);
536
321
  }
537
322
  /**
538
- * Returns `'zod'` when response parsing is enabled, `null` otherwise. The string shorthand `'zod'`
539
- * maps to response parsing.
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.
540
325
  */
541
- function resolveResponseValidator(validator) {
542
- if (!validator) return null;
543
- if (validator === "zod") return "zod";
544
- return validator.response ?? null;
326
+ function getResponseContentTypeInfo(node) {
327
+ return buildContentTypeInfo(_kubb_kit.ast.http.responses.findPrimary(node)?.content?.map((e) => e.contentType) ?? []);
545
328
  }
546
329
  /**
547
- * Resolves the zod schema name a generated client validates an error body with on the non-throw path.
548
- * Uses the error-only `<operation>ErrorSchema` (the union of non-2xx statuses); returns `null` when the
549
- * operation documents no error responses with a schema.
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.
550
337
  */
551
- function buildZodErrorParse(node, zodResolver) {
552
- if (!node.responses.some((res) => !isSuccessStatusCode(res.statusCode) && res.content?.some((entry) => entry.schema))) return null;
553
- return zodResolver.response.error?.(node) || null;
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;
554
345
  }
555
- // ../../internals/client/src/builders/generics.ts
556
346
  /**
557
- * Builds the `RequestResult` generic arguments for one operation: the per-status responses record
558
- * plus the per-call `ThrowOnError` flag. `SuccessOf` / `ErrorOf` split the record inside the
559
- * runtime, so this only names the record and threads `ThrowOnError`.
560
- *
561
- * @example
562
- * `buildRequestResultGenerics({ node, types }) // 'AddPetResponses, ThrowOnError'`
347
+ * How a content entry is framed as a stream: `'events'` for Server-Sent Events, `'lines'` for JSON Lines.
563
348
  */
564
- function buildRequestResultGenerics({ node, types }) {
565
- return `${types.response.responses(node)}, ThrowOnError`;
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;
566
352
  }
567
353
  /**
568
- * Builds the full return type an operation's function signature uses: `Unwrappable<RequestResult>`
569
- * for the default `returnType: 'full'`, already a promise so no further wrapping is needed, a plain
570
- * `Promise<RequestResult>` for the internal `'plain'` shape, or a
571
- * `Promise` of the runtime's `UnwrappedResult` when `returnType: 'data'` narrows a resolved call
572
- * down to the bare success body.
573
- *
574
- * @example
575
- * `buildResultType({ node, types, returnType: 'data' }) // 'Promise<UnwrappedResult<AddPetResponses, ThrowOnError>>'`
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`.
576
356
  */
577
- function buildResultType({ node, types, returnType }) {
578
- const generics = buildRequestResultGenerics({
579
- node,
580
- types
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);
581
385
  });
582
- if (returnType === "data") return `Promise<UnwrappedResult<${generics}>>`;
583
- if (returnType === "plain") return `Promise<RequestResult<${generics}>>`;
584
- return `Unwrappable<RequestResult<${generics}>>`;
585
- }
586
- // ../../internals/client/src/builders/returnStatement.ts
587
- /**
588
- * Builds the return statement of a generated operation function. With the default
589
- * `returnType: 'full'` the runtime call already resolves to `{ data, error, request, response }`.
590
- * The generated code casts that result to the operation's `RequestResult`, then wraps it with
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.
601
- *
602
- * @example
603
- * `return withUnwrap(request({ method: 'POST', url: '/pet', ...config }) as Promise<RequestResult<AddPetResponses, ThrowOnError>>)`
604
- * @example
605
- * `return unwrapResult(request({ method: 'POST', url: '/pet', ...config }), config.throwOnError ?? false) as Promise<UnwrappedResult<AddPetResponses, ThrowOnError>>`
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
+ }
461
+ // ../../internals/shared/src/resolver.ts
462
+ /**
463
+ * The shared top-level `name` of the operation plugins, computed once per operation name and build.
464
+ */
465
+ function resolveOperationName(name) {
466
+ return this.memo(`operation:${name}`, () => ensureValidVarName(_kubb_kit.casing.camel(name)));
467
+ }
468
+ /**
469
+ * Builds the allocation key for a declaration an operation owns. `scope` names which declaration
470
+ * it is, so two of them never share an allocation even when a custom resolver gives both the same
471
+ * name. Leave `scope` empty for the operation's own declaration.
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
+ *
606
476
  * @example
607
- * `return request({ method: 'POST', url: '/pet', ...config }) as Promise<RequestResult<AddPetResponses, ThrowOnError>>`
477
+ * ```ts
478
+ * resolver.resolveName({ key: operationKey(node, 'response', 'status', 404), name })
479
+ * ```
608
480
  */
609
- function buildReturnStatement({ node, types, callConfig, returnType, throwOnErrorDefault }) {
610
- const request = returnType !== "plain" && hasRequestContentTypeChoice(node) ? `request<${types.response.options(node)}['body']>` : "request";
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({
481
+ function operationKey(node, ...scope) {
482
+ return [
483
+ "operation",
484
+ ...node.pointer ? [node.pointer] : [node.method, node.path],
485
+ ...scope.map(String)
486
+ ];
487
+ }
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
+ }
495
+ /**
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({
617
500
  node,
618
501
  types
619
- })}>>`;
620
- return `return withUnwrap(${request}(${callConfig}) as Promise<RequestResult<${buildRequestResultGenerics({
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({
621
526
  node,
622
527
  types
623
- })}>>)`;
528
+ }).map(([, name]) => name);
624
529
  }
625
530
  /**
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.
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.
631
533
  */
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}))`
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
+ }
640
555
  };
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}>>`;
556
+ }
557
+ /**
558
+ * Picks the plugin generated clients, query hooks, and MCP handlers take their operation types from.
559
+ *
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.
563
+ *
564
+ * @example
565
+ * ```ts
566
+ * const types = resolveOperationTypes(ctx.driver, ctx.plugin.name)
567
+ * types.response.options(node) // 'GetPetByIdOptions', or 'GetPetByIdOptionsSchemaType' from zod
568
+ * ```
569
+ */
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));
645
592
  return {
646
- returnType: result,
647
- statement: `return unwrapResult(${call}, config.throwOnError ?? ${throwOnErrorDefault}) as ${result}`
593
+ response: resolver.response,
594
+ resolver,
595
+ output: pluginZod.options.output,
596
+ group: pluginZod.options.group ?? void 0
648
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;
649
616
  }
650
- return {
651
- returnType: `Unwrappable<StreamResult<${generics}>>`,
652
- statement: `return withUnwrap(${call} as Promise<StreamResult<${generics}>>)`
653
- };
617
+ return `{ ${inlineAuthKeys.flatMap((key) => {
618
+ const entryValue = key === "scopes" ? scopes : auth[key];
619
+ return entryValue ? [`${key}: ${toLiteral(entryValue)}`] : [];
620
+ }).join(", ")} }`;
654
621
  }
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;
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
634
+ };
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]));
681
641
  return {
682
- type: "apiKey",
683
- name: scheme.name,
684
- in: scheme.in
642
+ type: "oauth2",
643
+ ...Object.keys(flows).length ? { flows } : {},
644
+ ...credential.metadataUrl ? { metadataUrl: credential.metadataUrl } : {}
685
645
  };
686
646
  }
687
- if (scheme.type === "http") return {
688
- type: "http",
689
- scheme: scheme.scheme?.toLowerCase() === "basic" ? "basic" : "bearer"
647
+ if (credential.type === "openIdConnect") return {
648
+ type: "openIdConnect",
649
+ ...credential.url ? { url: credential.url } : {}
690
650
  };
691
- if (scheme.type === "oauth2") return { type: "oauth2" };
692
- if (scheme.type === "openIdConnect") return { type: "openIdConnect" };
693
651
  return null;
694
652
  }
695
653
  /**
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.
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`.
700
658
  *
701
659
  * @example
702
- * `getOperationSecurity({ document, method: 'POST', path: '/pet' })`
660
+ * `getOperationSecurity({ auth: [[{ scheme: 'bearerAuth' }]], schemes: meta.auth })`
703
661
  * `// [{ type: 'http', scheme: 'bearer' }]`
662
+ * @example
663
+ * `// auth: [[{ scheme: 'api_key' }, { scheme: 'session' }]] gives [[{ type: 'apiKey', ... }, { type: 'apiKey', ... }]]`
704
664
  */
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
- }
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
+ });
721
681
  return security.length ? security : void 0;
722
682
  }
723
683
  /**
724
684
  * 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.
685
+ * field. The runtime `resolveAuth` helper walks it, calling the configured `auth` resolver per scheme.
726
686
  *
727
687
  * @example
728
688
  * `buildSecurityMetadata({ security: [{ type: 'http', scheme: 'bearer' }] }) // "[{ type: 'http', scheme: 'bearer' }]"`
729
689
  */
730
690
  function buildSecurityMetadata({ security }) {
731
691
  if (!security?.length) return null;
732
- return `[${security.map(serializeAuth).join(", ")}]`;
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
706
+ /**
707
+ * Returns `true` when any direction of the validator uses zod (used for dependency checks).
708
+ */
709
+ function isValidatorEnabled(validator) {
710
+ if (!validator) return false;
711
+ if (validator === "zod") return true;
712
+ return Boolean(validator.request || validator.params || validator.response);
713
+ }
714
+ /**
715
+ * Returns `'zod'` when request body parsing is enabled, `null` otherwise. The string shorthand
716
+ * `'zod'` validates the response only, so it does not enable request parsing.
717
+ */
718
+ function resolveRequestValidator(validator) {
719
+ if (!validator || validator === "zod") return null;
720
+ return validator.request ?? null;
721
+ }
722
+ /**
723
+ * Returns `'zod'` when path, query, and header params parsing is enabled, `null` otherwise. Only the
724
+ * object form `{ params: 'zod' }` enables it.
725
+ */
726
+ function resolveParamsValidator(validator) {
727
+ if (!validator || validator === "zod") return null;
728
+ return validator.params ?? null;
729
+ }
730
+ /**
731
+ * Returns `'zod'` when response parsing is enabled, `null` otherwise. The string shorthand `'zod'`
732
+ * maps to response parsing.
733
+ */
734
+ function resolveResponseValidator(validator) {
735
+ if (!validator) return null;
736
+ if (validator === "zod") return "zod";
737
+ return validator.response ?? null;
738
+ }
739
+ /**
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.
742
+ */
743
+ function buildZodErrorParse(node, zodResolver) {
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
+ };
733
753
  }
734
- // ../../internals/client/src/builders/validator.ts
754
+ // src/builders/validator.ts
735
755
  /**
736
756
  * Builds the validator-hook references for one operation. Request body and params validation run before the send.
737
757
  * Response validation runs on the success body only. Returns `null` references when the matching
738
758
  * direction is disabled or the schema is absent.
739
759
  */
740
- function buildValidatorHooks({ node, validator, zodResolver }) {
741
- const hasRequestBody = Boolean(node.requestBody?.content?.[0]?.schema);
742
- const parsesResponse = zodResolver && resolveResponseValidator(validator) === "zod";
743
- const response = parsesResponse ? zodResolver.response.response(node) || null : null;
744
- const error = parsesResponse ? buildZodErrorParse(node, zodResolver) : null;
745
- const request = zodResolver && resolveRequestValidator(validator) === "zod" && hasRequestBody ? zodResolver.response.body(node) || null : null;
746
- const paramGroups = getOperationParameters(node);
747
- const resolveParamsName = (kind, params) => zodResolver && resolveParamsValidator(validator) === "zod" && params.length > 0 ? zodResolver.param[kind](node, params[0]) : null;
748
- const path = resolveParamsName("path", paramGroups.path);
749
- const query = resolveParamsName("query", paramGroups.query);
750
- const headers = resolveParamsName("headers", paramGroups.header);
751
- const importedZodNames = [
752
- response,
753
- error,
754
- request,
755
- path,
756
- query,
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;
800
+ }
801
+ // src/builders/operationData.ts
802
+ /**
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.
857
+ *
858
+ * @example
859
+ * `buildRequestResultGenerics({ node, types }) // "LoginUserResponses, ThrowOnError, { '200': LoginUserStatus200Headers }"`
860
+ */
861
+ function buildRequestResultGenerics({ node, types }) {
862
+ return [
863
+ types.response.responses(node),
864
+ "ThrowOnError",
865
+ buildResponseHeadersType({
866
+ node,
867
+ types
868
+ })
869
+ ].filter(Boolean).join(", ");
870
+ }
871
+ /**
872
+ * Builds the full return type an operation's function signature uses: `Unwrappable<RequestResult>`
873
+ * for the default `returnType: 'full'`, already a promise so no further wrapping is needed, a plain
874
+ * `Promise<RequestResult>` for the internal `'plain'` shape, or a
875
+ * `Promise` of the runtime's `UnwrappedResult` when `returnType: 'data'` narrows a resolved call
876
+ * down to the bare success body.
877
+ *
878
+ * @example
879
+ * `buildResultType({ node, types, returnType: 'data' }) // 'Promise<UnwrappedResult<AddPetResponses, ThrowOnError>>'`
880
+ */
881
+ function buildResultType({ node, types, returnType }) {
882
+ const result = buildResultTypeRef(returnType === "data" ? "UnwrappedResult" : "RequestResult", types.response.responses(node), buildResponseHeadersType({
883
+ node,
884
+ types
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.
895
+ *
896
+ * @example
897
+ * `return withUnwrap(apiClient<AddPetResponses, ThrowOnError>({ method: 'POST', url: '/pet', ...config }))`
898
+ * @example
899
+ * `return unwrapResult(apiClient<AddPetResponses, ThrowOnError>({ ... }), resolveThrowOnError(config.throwOnError, true))`
900
+ * @example
901
+ * `return apiClient<AddPetResponses, ThrowOnError>({ method: 'POST', url: '/pet', ...config })`
902
+ */
903
+ function buildReturnStatement({ node, types, callConfig, returnType, throwOnErrorDefault }) {
904
+ const call = `apiClient<${buildRequestResultGenerics({
905
+ node,
906
+ types
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})`;
911
+ }
912
+ /**
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.
916
+ */
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((name) => Boolean(name));
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
- request,
761
- path,
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
- * Prints the `validator: { … }` entry of a call config from the hooks named in `keys`, or `null` when none is set.
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 buildValidatorLiteral(hooks, keys) {
773
- const entries = keys.flatMap((key) => hooks?.[key] ? [`${key}: ${hooks[key]}`] : []);
774
- return entries.length ? `validator: { ${entries.join(", ")} }` : null;
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
- // ../../internals/client/src/builders/contentType.ts
977
+ // src/builders/contentType.ts
777
978
  /**
778
- * Builds the `contentType` entry baked into the call config, or `null` without a request body.
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
- * merge it over the baked request default, because the caller can also pick a response content type.
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(node.requestBody?.content?.[0]?.schema) ? defaultContentType : null;
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: null,
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: { request: '${requestContentType}', ...(typeof contentType === 'string' ? { request: contentType } : contentType) }` : `contentType: { request: '${requestContentType}' }`
999
+ literal: merge ? `contentType: { ${defaults}, ...(typeof contentType === 'string' ? { request: contentType } : contentType) }` : `contentType: { ${defaults} }`
794
1000
  };
795
1001
  }
796
- // ../../internals/client/src/builders/styles.ts
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`; header and cookie use a fixed style (`simple` and `form`), so only `explode` is emitted
809
- * for them. `allowReserved` only applies to query parameters, and `contentType` to every location
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
- if ((parameter.in === "path" || parameter.in === "query") && parameter.style) parts.push(`style: '${parameter.style}'`);
815
- if (parameter.explode !== void 0) parts.push(`explode: ${parameter.explode}`);
816
- if (parameter.in === "query" && parameter.allowReserved !== void 0) parts.push(`allowReserved: ${parameter.allowReserved}`);
817
- if (parameter.in !== "path" && parameter.contentType) parts.push(`contentType: '${parameter.contentType}'`);
818
- if (parameter.in === "querystring") parts.push("querystring: true");
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
- * Serializes the `encoding` of the request body's default content type into a `{ property: { ... } }`
823
- * literal, or `null` when it carries none. Only an operation with a single request content type
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
- const entries = Object.entries(content?.length === 1 ? content[0]?.encoding ?? {} : {}).map(([property, { contentType, style, explode, allowReserved }]) => {
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.isHttpOperationNode(node)) return null;
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(getOperationParameters(node))) for (const parameter of parameters) {
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 = [...Object.keys(groups).filter((location) => groups[location].length > 0).map((location) => `${location}: { ${groups[location].join(", ")} }`), serializeBodyEncoding(node)].filter(Boolean);
866
- return entries.length > 0 ? `{ ${entries.join(", ")} }` : null;
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
- // ../../internals/client/src/builders/callConfig.ts
1086
+ // src/builders/callConfig.ts
869
1087
  /**
870
- * `responseType` baked into a call. One parser is a string. Several parsers become a map from content
871
- * type to parser so the runtime can choose from the response `Content-Type`.
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 buildResponseTypeLiteral(node) {
874
- const responseType = getResponseType(node);
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 `throwOnError` fallback is applied after the `...config` spread.
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 returns `text/event-stream`. Unless `returnType` is `'plain'`, a
886
- * stream's literal leaves the `response` validator to `toEventStream`, which runs it per event.
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 eventStream = isEventStream(node);
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 stylesLiteral = buildStyles({ node });
892
- const validatorLiteral = buildValidatorLiteral(validatorHooks, eventStream && returnType !== "plain" ? [
893
- "request",
894
- "path",
895
- "query",
896
- "headers",
897
- "error"
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: '${node.method.toUpperCase()}'`,
910
- `url: '${node.path}'`,
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
- stylesLiteral ? `styles: ${stylesLiteral}` : null,
913
- validatorLiteral,
1133
+ ownRuntime ? null : styles,
914
1134
  contentTypeLiteral,
915
- buildResponseTypeLiteral(node),
1135
+ buildParseAsLiteral(content),
1136
+ stream === "event" || stream === "data" ? `eventShape: '${stream}'` : null,
1137
+ rawData ? `rawData: ${toLiteral(rawData)}` : null,
916
1138
  "...config",
917
- `throwOnError: config.throwOnError ?? ${throwOnErrorDefault}`
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
- // ../../internals/client/src/builders/signature.ts
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
- // ../../internals/client/src/builders/operationBody.ts
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
- * `text/event-stream` operation is wired to `toEventStream` in one place.
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
- // ../../internals/client/src/components/Operation.tsx
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.isHttpOperationNode(node)) return null;
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: request = client, contentType, ...config } = options" : "const { client: request = client, ...config } = options",
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
- // ../../internals/client/src/components/StreamRuntime.tsx
1036
- /**
1037
- * The `footer` of `.kubb/client.ts`: the `baseURL` set at generation time, and the `sse.ts` re-export when the document
1038
- * streams events, so `ServerSentEvent` and `toEventStream` stay importable from the client as in earlier releases.
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
- // ../../internals/client/src/resolveOperationTypes.ts
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
- * A `plugin-ts` shaped resolver whose operation type names point at the types `pluginZod({ inferred })`
1069
- * exports next to each schema, so code written against `plugin-ts` names picks up the zod types.
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 toZodTypeResolver(resolver) {
1072
- const type = (name) => resolver.schema.type(name);
1073
- return {
1074
- ...resolver,
1075
- file: resolver.file.bind(resolver),
1076
- memo: (key, compute) => resolver.memo(`zodTypes:${key}`, compute),
1077
- param: {
1078
- name: (node, p) => type(resolver.param.name(node, p)),
1079
- path: (node, p) => type(resolver.param.path(node, p)),
1080
- query: (node, p) => type(resolver.param.query(node, p)),
1081
- headers: (node, p) => type(resolver.param.headers(node, p))
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
- response: {
1084
- body: (node) => type(resolver.response.body(node)),
1085
- status: (node, statusCode) => type(resolver.response.status(node, statusCode)),
1086
- response: (node) => type(resolver.response.response(node)),
1087
- responses: (node) => type(resolver.response.responses(node)),
1088
- options: (node) => resolver.response.options(node)
1089
- }
1090
- };
1091
- }
1092
- /**
1093
- * Picks the plugin generated clients, query hooks, and MCP handlers take their operation types from.
1094
- *
1095
- * `pluginZod({ inferred: 'codec' })` wins, so a codec param takes its domain value. Otherwise `plugin-ts`
1096
- * wins when it is in the pipeline, and without it any `pluginZod({ inferred })`. Throws
1097
- * `KUBB_MISSING_DEPENDENCY` for `pluginName` when neither is available.
1098
- *
1099
- * @example
1100
- * ```ts
1101
- * const types = resolveOperationTypes(ctx.driver, ctx.plugin.name)
1102
- * types.response.options(node) // 'GetPetByIdOptions', or 'GetPetByIdOptionsSchemaType' from zod
1103
- * ```
1104
- */
1105
- function resolveOperationTypes(driver, pluginName) {
1106
- return driver.memo(`operationTypes:${pluginName}`, () => {
1107
- const pluginTs = driver.getPlugin(_kubb_plugin_ts.pluginTsName);
1108
- const pluginZod = driver.getPlugin(_kubb_plugin_zod.pluginZodName);
1109
- if (pluginTs && pluginZod?.options?.inferred !== "codec") {
1110
- const resolver = driver.getResolver(_kubb_plugin_ts.pluginTsName);
1111
- return {
1112
- response: resolver.response,
1113
- resolver,
1114
- output: pluginTs.options?.output,
1115
- group: pluginTs.options?.group ?? void 0
1116
- };
1117
- }
1118
- if (!pluginZod?.options?.inferred) throw new _kubb_kit.Diagnostics.Error({
1119
- code: _kubb_kit.Diagnostics.code.missingDependency,
1120
- severity: "error",
1121
- message: `Plugin "${pluginName}" has no source for operation types.`,
1122
- help: `Add \`pluginTs()\` before "${pluginName}", or add \`pluginZod({ inferred: true })\`.`,
1123
- location: { kind: "config" },
1124
- plugin: pluginName
1125
- });
1126
- const resolver = toZodTypeResolver(driver.getResolver(_kubb_plugin_zod.pluginZodName));
1127
- return {
1128
- response: resolver.response,
1129
- resolver,
1130
- output: pluginZod.options.output,
1131
- group: pluginZod.options.group ?? void 0
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
- // ../../internals/client/src/generators/clientGenerator.tsx
1378
+ // src/generators/clientGenerator.tsx
1136
1379
  /**
1137
- * Builds the built-in per-operation generator shared by the client plugins (`@kubb/plugin-fetch`,
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, { sse } = {}) {
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)(StreamRuntime, {
1387
+ return /* @__PURE__ */ (0, _kubb_renderer_jsx_jsx_runtime.jsx)(ClientRuntime, {
1153
1388
  nodes,
1154
1389
  root: ctx.root,
1155
- sse,
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.isHttpOperationNode(node)) return null;
1397
+ if (!_kubb_kit.ast.http.isOperation(node)) return null;
1161
1398
  const { config, driver, resolver, root } = ctx;
1162
- const { output, validator, returnType, throwOnErrorDefault, group, importPath } = ctx.options;
1163
- const types = resolveOperationTypes(driver, ctx.plugin.name);
1164
- const pluginZod = isValidatorEnabled(validator) ? driver.getPlugin(_kubb_plugin_zod.pluginZodName) : null;
1165
- const zodResolver = pluginZod ? driver.getResolver(_kubb_plugin_zod.pluginZodName) : null;
1166
- const importedTypeNames = [types.response.options(node), types.response.responses(node)];
1167
- const validatorHooks = buildValidatorHooks({
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
- validator,
1170
- zodResolver
1410
+ ctx,
1411
+ types
1171
1412
  });
1172
- const importedZodNames = validatorHooks.importedZodNames;
1173
- const operationName = resolver.name(node.operationId);
1174
- const meta = {
1175
- name: resolver.resolveName({
1176
- key: operationKey(node),
1177
- name: operationName
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 ? meta.file.path : 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
- if (returnType === "plain") return ["client"];
1212
- if (returnType === "data") return ["client", "unwrapResult"];
1213
- return ["client", "withUnwrap"];
1214
- })();
1215
- const clientTypeImports = (() => {
1216
- if (stream) return {
1217
- plain: ["Options", "SuccessOf"],
1218
- data: ["SuccessOf"],
1219
- full: ["SuccessOf", "Unwrappable"]
1220
- }[returnType];
1221
- if (returnType === "plain") return ["Options", "RequestResult"];
1222
- if (returnType === "data") return ["Options", "UnwrappedResult"];
1223
- return [
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: meta.file.baseName,
1231
- path: meta.file.path,
1232
- meta: meta.file.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: meta.file.path,
1238
- baseName: meta.file.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: meta.file.path,
1246
- baseName: meta.file.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
- stream && /* @__PURE__ */ (0, _kubb_renderer_jsx_jsx_runtime.jsx)(_kubb_renderer_jsx.File.Import, {
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: meta.file.path,
1278
- path: meta.fileTypes.path,
1489
+ root: file.path,
1490
+ path: data.typeFile.path,
1279
1491
  isTypeOnly: true
1280
1492
  }),
1281
- meta.fileZod && importedZodNames.length > 0 && /* @__PURE__ */ (0, _kubb_renderer_jsx_jsx_runtime.jsx)(_kubb_renderer_jsx.File.Import, {
1282
- name: importedZodNames,
1283
- root: meta.file.path,
1284
- path: meta.fileZod.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: meta.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
- // ../../internals/client/src/macros.ts
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
- // ../../internals/client/src/resolver.ts
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((0, _kubb_kit.pascalCase)(name));
1540
+ return ensureValidVarName(_kubb_kit.casing.pascal(name));
1328
1541
  },
1329
1542
  groupName(name) {
1330
- return ensureValidVarName((0, _kubb_kit.pascalCase)(`${name} Client`));
1543
+ return ensureValidVarName(_kubb_kit.casing.pascal(`${name} Client`));
1331
1544
  },
1332
1545
  propertyName(name) {
1333
- return ensureValidVarName((0, _kubb_kit.camelCase)(name));
1546
+ return ensureValidVarName(_kubb_kit.casing.camel(name));
1334
1547
  }
1335
1548
  });
1336
- // src/generators/clientGenerator.tsx
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
- * Built-in operation generator for `@kubb/plugin-client`. Emits one async function per OpenAPI
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
- const clientGenerator = createClientGenerator("client");
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,37 +1955,38 @@ const pluginClient = (0, _kubb_kit.defineExtension)({
1362
1955
  version,
1363
1956
  compatibility
1364
1957
  },
1365
- dependencies: (options) => ({
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",
1372
1962
  barrel: { type: "named" }
1373
- }, exclude = [], include, override = [], importPath, throwOnErrorDefault = true, validator = false, group, resolver: userResolver } = options;
1963
+ }, exclude = [], include, override = [], importPath, throwOnErrorDefault = true, validator = false, returnType = "plain", group, resolver: userResolver } = options;
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)(clientGenerator);
1967
+ (0, _kubb_kit.addGenerator)(createClientGenerator("client"));
1378
1968
  return {
1379
1969
  output,
1380
1970
  exclude,
1381
1971
  include,
1382
1972
  override,
1383
- group: createGroupConfig(group),
1973
+ group: group ?? null,
1384
1974
  importPath,
1385
1975
  baseURL: void 0,
1386
1976
  throwOnErrorDefault,
1387
1977
  validator,
1388
- returnType: "plain",
1978
+ returnType,
1389
1979
  sdk: void 0,
1390
1980
  resolver
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