@kubb/plugin-react-query 5.10.0-canary.20261109T201601 → 5.10.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js CHANGED
@@ -1,10 +1,293 @@
1
- import { Diagnostics, Resolver, Url, addGenerator, ast, camelCase, createResolver, defineExtension, defineGenerator, setMacros, setResolver, useConfig } from "@kubb/kit";
1
+ import { Diagnostics, Resolver, Url, addGenerator, ast, casing, createResolver, defineExtension, defineGenerator, setMacros, setResolver, useConfig } from "@kubb/kit";
2
2
  import path from "node:path";
3
- import { pluginZodName } from "@kubb/plugin-zod";
4
- import { File, Function, Type, jsxRenderer } from "@kubb/renderer-jsx";
5
3
  import { createFunctionParameter, createFunctionParameters, createObjectBindingPattern, createTypeLiteral, functionPrinter, pluginTsName } from "@kubb/plugin-ts";
4
+ import { File, Function, Type, jsxRenderer } from "@kubb/renderer-jsx";
6
5
  import { Fragment, jsx, jsxs } from "@kubb/renderer-jsx/jsx-runtime";
7
6
  import fs from "node:fs";
7
+ // kubb-extension.json
8
+ var compatibility = {
9
+ "kubb": ">=5.18.4",
10
+ "node": ">=22.22"
11
+ };
12
+ // package.json
13
+ var version = "5.10.1";
14
+ // ../../internals/client/src/resolveClient.ts
15
+ /**
16
+ * Resolves which client plugin a consumer (react-query, vue-query, swr, mcp) should call for an
17
+ * operation, shared so the selection rules and diagnostics stay in one place.
18
+ *
19
+ * Every client runtime lives in a dedicated client plugin, so a consumer always calls a registered
20
+ * contract client plugin (plugin-fetch, plugin-axios, or plugin-client) and never emits its own
21
+ * inline client:
22
+ *
23
+ * - `contract` — a registered contract client plugin owns the `<op>` functions and the consumer
24
+ * imports and calls them.
25
+ * - `error` — no client plugin is registered, several are registered without a selector, the
26
+ * requested one is missing, or `client` is not a known selector.
27
+ *
28
+ * The `client` string selects explicitly (`'fetch'` / `'axios'` / `'client'`); when it is unset a
29
+ * lone registered contract client plugin is picked up automatically.
30
+ */
31
+ const pluginFetchName = "plugin-fetch";
32
+ const pluginAxiosName = "plugin-axios";
33
+ const pluginClientName = "plugin-client";
34
+ const selectorToPlugin = {
35
+ fetch: pluginFetchName,
36
+ axios: pluginAxiosName,
37
+ client: pluginClientName
38
+ };
39
+ const contractPlugins = [
40
+ pluginFetchName,
41
+ pluginAxiosName,
42
+ pluginClientName
43
+ ];
44
+ /**
45
+ * Applies the `client` resolution rules. See the module comment for the strategy shape.
46
+ *
47
+ * @example
48
+ * ```ts
49
+ * resolveClient({ client: 'fetch', pluginNames: ['plugin-ts', 'plugin-fetch'] })
50
+ * // { kind: 'contract', pluginName: 'plugin-fetch' }
51
+ * ```
52
+ *
53
+ * @example
54
+ * ```ts
55
+ * resolveClient({ client: undefined, pluginNames: ['plugin-ts'] })
56
+ * // { kind: 'error', code: 'KUBB_MISSING_DEPENDENCY', message: 'No client plugin is registered.', help: 'Add …' }
57
+ * ```
58
+ */
59
+ function resolveClient(options) {
60
+ const { client, pluginNames } = options;
61
+ const has = (name) => pluginNames.includes(name);
62
+ if (client === "fetch" || client === "axios" || client === "client") {
63
+ const pluginName = selectorToPlugin[client];
64
+ if (!has(pluginName)) return {
65
+ kind: "error",
66
+ code: Diagnostics.code.missingDependency,
67
+ message: `\`client: '${client}'\` is set but \`@kubb/plugin-${client}\` is not registered in \`plugins\`.`,
68
+ help: `Add \`@kubb/plugin-${client}\` to \`plugins\`, or drop \`client\` to use a different client plugin.`
69
+ };
70
+ return {
71
+ kind: "contract",
72
+ pluginName
73
+ };
74
+ }
75
+ if (client !== void 0) return {
76
+ kind: "error",
77
+ code: Diagnostics.code.invalidPluginOptions,
78
+ message: `Option \`client\` is ${typeof client === "string" ? `'${client}'` : String(client)}, but it must be one of 'fetch', 'axios', 'client'.`,
79
+ help: "Set `client` to one of those values, or drop it to use the registered client plugin."
80
+ };
81
+ const registered = contractPlugins.filter(has);
82
+ if (registered.length === 1) return {
83
+ kind: "contract",
84
+ pluginName: registered[0]
85
+ };
86
+ if (registered.length > 1) return {
87
+ kind: "error",
88
+ code: Diagnostics.code.invalidPluginOptions,
89
+ message: `Multiple client plugins are registered (${registered.map((name) => `\`@kubb/${name}\``).join(", ")}) and \`client\` is not set.`,
90
+ help: "Set `client: 'fetch' | 'axios' | 'client'` to choose which client the hooks call, or register a single client plugin."
91
+ };
92
+ return {
93
+ kind: "error",
94
+ code: Diagnostics.code.missingDependency,
95
+ message: "No client plugin is registered.",
96
+ help: "Add `@kubb/plugin-fetch`, `@kubb/plugin-axios`, or `@kubb/plugin-client` to `plugins` so the generated code has an HTTP client to call."
97
+ };
98
+ }
99
+ /**
100
+ * Resolves the contract client during a consumer plugin's setup hook.
101
+ * It extracts the plugin names from the raw `plugins` config and applies {@link resolveClient}.
102
+ * On a misconfiguration it throws a config diagnostic tagged with `plugin`, so every consumer
103
+ * fails fast with the same message.
104
+ */
105
+ function resolveContractClient(options) {
106
+ const { plugin, client, plugins = [] } = options;
107
+ const resolved = resolveClient({
108
+ client,
109
+ pluginNames: plugins.map((plugin) => plugin.name).filter((name) => Boolean(name))
110
+ });
111
+ if (resolved.kind === "error") {
112
+ const { code, message, help } = resolved;
113
+ throw new Diagnostics.Error({
114
+ code,
115
+ severity: "error",
116
+ message,
117
+ help,
118
+ location: { kind: "config" },
119
+ plugin
120
+ });
121
+ }
122
+ return resolved;
123
+ }
124
+ // ../../internals/utils/src/reserved.ts
125
+ /**
126
+ * JavaScript and Java reserved words.
127
+ * @link https://github.com/jonschlinkert/reserved/blob/master/index.js
128
+ */
129
+ const reservedWords = /* @__PURE__ */ new Set([
130
+ "abstract",
131
+ "arguments",
132
+ "await",
133
+ "boolean",
134
+ "break",
135
+ "byte",
136
+ "case",
137
+ "catch",
138
+ "char",
139
+ "class",
140
+ "const",
141
+ "continue",
142
+ "debugger",
143
+ "default",
144
+ "delete",
145
+ "do",
146
+ "double",
147
+ "else",
148
+ "enum",
149
+ "eval",
150
+ "export",
151
+ "extends",
152
+ "false",
153
+ "final",
154
+ "finally",
155
+ "float",
156
+ "for",
157
+ "function",
158
+ "goto",
159
+ "if",
160
+ "implements",
161
+ "import",
162
+ "in",
163
+ "instanceof",
164
+ "int",
165
+ "interface",
166
+ "let",
167
+ "long",
168
+ "native",
169
+ "new",
170
+ "null",
171
+ "package",
172
+ "private",
173
+ "protected",
174
+ "public",
175
+ "return",
176
+ "short",
177
+ "static",
178
+ "super",
179
+ "switch",
180
+ "synchronized",
181
+ "this",
182
+ "throw",
183
+ "throws",
184
+ "transient",
185
+ "true",
186
+ "try",
187
+ "typeof",
188
+ "var",
189
+ "void",
190
+ "volatile",
191
+ "while",
192
+ "with",
193
+ "yield",
194
+ "Array",
195
+ "Date",
196
+ "hasOwnProperty",
197
+ "Infinity",
198
+ "isFinite",
199
+ "isNaN",
200
+ "isPrototypeOf",
201
+ "length",
202
+ "Math",
203
+ "name",
204
+ "NaN",
205
+ "Number",
206
+ "Object",
207
+ "prototype",
208
+ "String",
209
+ "toString",
210
+ "undefined",
211
+ "valueOf"
212
+ ]);
213
+ /**
214
+ * Returns `true` when `name` is a syntactically valid JavaScript variable name.
215
+ *
216
+ * @example
217
+ * ```ts
218
+ * isValidVarName('status') // true
219
+ * isValidVarName('class') // false (reserved word)
220
+ * isValidVarName('42foo') // false (starts with digit)
221
+ * ```
222
+ */
223
+ function isValidVarName(name) {
224
+ if (!name || reservedWords.has(name)) return false;
225
+ return isIdentifier(name);
226
+ }
227
+ /**
228
+ * Returns `name` when it's a syntactically valid JavaScript variable name,
229
+ * otherwise prefixes it with `_` so the result is a valid identifier.
230
+ *
231
+ * Useful for sanitizing OpenAPI schema names or operation IDs that start with
232
+ * a digit (e.g. `409`, `504AccountCancel`) before using them as exported
233
+ * variable, type, or function names.
234
+ *
235
+ * @example
236
+ * ```ts
237
+ * ensureValidVarName('409') // '_409'
238
+ * ensureValidVarName('504AccountCancel') // '_504AccountCancel'
239
+ * ensureValidVarName('Pet') // 'Pet'
240
+ * ensureValidVarName('class') // '_class'
241
+ * ```
242
+ */
243
+ function ensureValidVarName(name) {
244
+ if (!name || isValidVarName(name)) return name;
245
+ return `_${name}`;
246
+ }
247
+ /**
248
+ * Returns `true` when `name` is syntactically a valid identifier, ignoring reserved words.
249
+ *
250
+ * Reserved words and globals (`class`, `name`, `Date`, …) are valid as bare object-literal keys
251
+ * even though they are not valid variable names, so use this (not {@link isValidVarName}) when
252
+ * deciding whether an object key needs quoting.
253
+ *
254
+ * @example
255
+ * ```ts
256
+ * isIdentifier('name') // true
257
+ * isIdentifier('x-total')// false
258
+ * ```
259
+ */
260
+ function isIdentifier(name) {
261
+ return /^[a-zA-Z_$][a-zA-Z0-9_$]*$/.test(name);
262
+ }
263
+ // ../../internals/utils/src/strings.ts
264
+ /**
265
+ * Renders a dotted path or string array as an optional-chaining accessor expression rooted at
266
+ * `accessor`. Returns `null` for an empty path.
267
+ *
268
+ * @example
269
+ * ```ts
270
+ * getNestedAccessor('pagination.next.id', 'lastPage')
271
+ * // "lastPage?.['pagination']?.['next']?.['id']"
272
+ * ```
273
+ */
274
+ function getNestedAccessor(param, accessor) {
275
+ const parts = Array.isArray(param) ? param : param.split(".");
276
+ if (parts.length === 0 || parts.length === 1 && parts[0] === "") return null;
277
+ return `${accessor}?.['${`${parts.join("']?.['")}']`}`;
278
+ }
279
+ /**
280
+ * Escapes an `@` at the start of a line, so a multi-line description can't start a new JSDoc tag.
281
+ * Editors render `\@` as `@` in hovers.
282
+ *
283
+ * @example
284
+ * ```ts
285
+ * jsDocEscape('intro\n@example value') // 'intro\n\\@example value'
286
+ * ```
287
+ */
288
+ function jsDocEscape(input) {
289
+ return `${input}`.replace(/^(\s*)@/gm, "$1\\@");
290
+ }
8
291
  // ../../internals/shared/src/operation.ts
9
292
  /**
10
293
  * Builds the `ResolverFileParams` every operation generator passes to
@@ -26,29 +309,55 @@ function operationFileEntry(node, name, extname = ".ts") {
26
309
  };
27
310
  }
28
311
  /**
29
- * Resolves a dependency plugin's generated file for `node.operationId`, cached in `memo` (the
30
- * build's `ctx.memo`) under the resolver's own plugin name and the operation. Several dependents reading the
31
- * same dependency for the same operation in one pass (a query plugin's several hook generators, the
32
- * MCP handler, ...) share one computed name and path instead of each calling `resolver.file` again.
312
+ * Resolves the file a schema-emitting plugin (`plugin-ts`, `plugin-zod`, `plugin-faker`) writes
313
+ * `node` to, cached in `ctx.memo` under the resolver's plugin name and the
314
+ * operation. The plugin itself and every plugin that imports from it call this, so they all get
315
+ * the same path.
33
316
  *
34
- * @example Cache `plugin-ts`'s file for the current operation
317
+ * When that path matches the file of a component schema in `ctx.driver.inputNode`, ignoring case, the file name
318
+ * gets an `Operation` suffix: operation `point` goes to `PointOperation.ts` next to schema `Point.ts`.
319
+ * Otherwise the two merge into one file, or overwrite each other on a case-insensitive file system.
320
+ * Only the file name changes. The declarations keep their names.
321
+ *
322
+ * @example Resolve `plugin-ts`'s file for the current operation
35
323
  * ```ts
36
- * const fileTs = resolveDependencyOperationFile({ memo: ctx.memo, node, resolver: tsResolver, root, output })
324
+ * const fileTs = resolveOperationFile({ ctx, node, resolver: tsResolver, root, output })
37
325
  * ```
38
326
  */
39
- function resolveDependencyOperationFile(options) {
40
- const { memo, node, resolver, root, output, group } = options;
41
- return memo(`${resolver.pluginName}:operationFile:${node.operationId}`, () => resolver.file({
42
- ...operationFileEntry(node, node.operationId),
327
+ function resolveOperationFile(options) {
328
+ const { ctx, node, resolver, root, output } = options;
329
+ const { memo } = ctx;
330
+ const location = {
43
331
  root,
44
332
  output,
45
- group: group ?? void 0
46
- }));
333
+ group: options.group ?? void 0
334
+ };
335
+ return memo(`${resolver.pluginName}:operationFile:${node.operationId}`, () => {
336
+ const schemaPaths = memo(`${resolver.pluginName}:schemaFilePaths`, () => new Set((ctx.driver.inputNode?.schemas ?? []).flatMap((schema) => schema.name ? [resolver.file({
337
+ name: schema.name,
338
+ extname: ".ts",
339
+ ...location
340
+ }).path.toLowerCase()] : [])));
341
+ let file = resolver.file({
342
+ ...operationFileEntry(node, node.operationId),
343
+ ...location
344
+ });
345
+ for (let name = `${node.operationId}Operation`; schemaPaths.has(file.path.toLowerCase()); name += "Operation") {
346
+ const next = resolver.file({
347
+ ...operationFileEntry(node, name),
348
+ ...location
349
+ });
350
+ if (next.path === file.path) break;
351
+ file = next;
352
+ }
353
+ return file;
354
+ });
47
355
  }
48
- function getOperationLink(node, link) {
49
- if (!link) return null;
50
- if (typeof link === "function") return link(node) ?? null;
51
- return node.path ? `{@link ${Url.toPath(node.path)}}` : null;
356
+ /**
357
+ * The operation's method and path as plain text, such as `` `GET /pet/{petId}` ``. A path is not a valid `{@link}` target.
358
+ */
359
+ function getOperationRequestLine(node) {
360
+ return ast.http.isOperation(node) && node.path ? `\`${node.method.toUpperCase()} ${node.path}\`` : null;
52
361
  }
53
362
  /**
54
363
  * Derives the shared `ContentTypeInfo` shape from a list of content types, tracking whether several
@@ -72,18 +381,7 @@ function getContentTypeInfo(node) {
72
381
  * whether several are present, so the client can let a caller pick which one to accept.
73
382
  */
74
383
  function getResponseContentTypeInfo(node) {
75
- return buildContentTypeInfo(getPrimarySuccessResponse(node)?.content?.map((e) => e.contentType) ?? []);
76
- }
77
- function baseContentType(contentType) {
78
- return contentType.split(";")[0].trim().toLowerCase();
79
- }
80
- /**
81
- * Whether a content entry is a sequence of JSON values the adapter marks as a stream, such as `application/jsonl`,
82
- * `application/x-ndjson`, `application/json-seq`, or any media type with an OpenAPI 3.2 `itemSchema`.
83
- */
84
- function isJsonLines(entry) {
85
- const baseType = baseContentType(entry.contentType);
86
- return Boolean(entry.stream) && baseType !== "text/event-stream" && !baseType.startsWith("multipart/");
384
+ return buildContentTypeInfo(ast.http.responses.findPrimary(node)?.content?.map((e) => e.contentType) ?? []);
87
385
  }
88
386
  /**
89
387
  * How an operation streams its primary success response item by item, when that response declares one content type:
@@ -95,11 +393,19 @@ function isJsonLines(entry) {
95
393
  * call with its query hooks.
96
394
  */
97
395
  function getEventStream(node) {
98
- const content = getPrimarySuccessResponse(node)?.content ?? [];
396
+ const content = ast.http.responses.findPrimary(node)?.content ?? [];
99
397
  if (content.length !== 1) return null;
100
398
  const entry = content[0];
101
- if (baseContentType(entry.contentType) === "text/event-stream") return entry.stream?.shape ?? "data";
102
- return isJsonLines(entry) ? "line" : null;
399
+ const framing = getStreamFraming(entry);
400
+ if (framing === "events") return entry.stream?.shape ?? "data";
401
+ return framing === "lines" ? "line" : null;
402
+ }
403
+ /**
404
+ * How a content entry is framed as a stream: `'events'` for Server-Sent Events, `'lines'` for JSON Lines.
405
+ */
406
+ function getStreamFraming(entry) {
407
+ if (ast.mediaType.getBase(entry) === "text/event-stream") return "events";
408
+ return entry.stream && ast.mediaType.isStream(entry) ? "lines" : void 0;
103
409
  }
104
410
  /**
105
411
  * Whether an operation streams its primary success response, as Server-Sent Events or JSON Lines. The client generator uses
@@ -109,20 +415,42 @@ function isEventStream(node) {
109
415
  return getEventStream(node) !== null;
110
416
  }
111
417
  /**
418
+ * Whether a success response offers a stream media type next to other content types. The client returns that variant's
419
+ * body as a `ReadableStream`, so the stream runtime is registered and a hook's data type includes the stream.
420
+ */
421
+ function hasStreamVariant(node) {
422
+ return ast.http.responses.filterSuccess(node).some((response) => {
423
+ const variants = (response.content ?? []).filter((entry) => entry.schema);
424
+ return variants.length > 1 && variants.some((entry) => ast.mediaType.isStream(entry));
425
+ });
426
+ }
427
+ /**
112
428
  * Builds the `client?:` option type shared by the generated query hooks (`useQuery`,
113
- * `useInfiniteQuery`, `useSWR`, ...). Unlike {@link buildRequestConfigType}, it never adds a
114
- * `contentType?:` member: query hooks wrap GET operations, which carry no request body to select a
115
- * content type for. It drops the data-shape keys to stay assignable to `Options`, which omits them
116
- * from `RequestConfig`. `cookies` is a group only on an operation that declares one.
429
+ * `useInfiniteQuery`, `useSWR`, ...): `RequestConfig` minus what the
430
+ * hook fills in, which is every request group and `throwOnError`. Unlike {@link buildRequestConfigType},
431
+ * it never narrows `contentType`: query hooks wrap GET operations, which carry no request body to select
432
+ * a content type for.
117
433
  */
118
- function buildClientOptionType(node) {
119
- return `Partial<Omit<RequestConfig, 'path' | 'query' | 'body' | 'headers'${getOperationParameters(node).cookie.length > 0 ? ` | 'cookies'` : ""} | 'url'>>`;
434
+ function buildClientOptionType(options = {}) {
435
+ return `Partial<Omit<RequestConfig, ${[
436
+ ...requestGroupOrder,
437
+ "url",
438
+ ...options.omit ?? [],
439
+ "throwOnError"
440
+ ].map((key) => `'${key}'`).join(" | ")}>>`;
120
441
  }
442
+ /**
443
+ * The `client?:` option type of a mutation hook. When the operation has a request content type choice,
444
+ * the mutation variables carry `contentType`, so the config leaves it out. Otherwise the ambiguous side is
445
+ * narrowed to the operation's media types; a single-type side has nothing to pick, so it stays baked in the
446
+ * generated call.
447
+ */
121
448
  function buildRequestConfigType(node) {
449
+ if (hasRequestContentTypeChoice(node)) return buildClientOptionType({ omit: ["contentType"] });
122
450
  const request = getContentTypeInfo(node);
123
451
  const response = getResponseContentTypeInfo(node);
124
452
  const members = [request.isMultipleContentTypes ? `request?: ${request.contentTypeUnion}` : null, response.isMultipleContentTypes ? `response?: ${response.contentTypeUnion}` : null].filter(Boolean);
125
- const base = buildClientOptionType(node);
453
+ const base = buildClientOptionType();
126
454
  return members.length ? `${base} & { contentType?: { ${members.join("; ")} } }` : base;
127
455
  }
128
456
  /** The grouped request options, in the order generated signatures and calls list them. */
@@ -137,11 +465,11 @@ const requestGroupOrder = [
137
465
  * Which of the grouped request options an operation carries.
138
466
  */
139
467
  function getRequestGroups(node) {
140
- const { path, query, header, cookie } = getOperationParameters(node);
468
+ const { path, query, header, cookie } = ast.http.groupParameters(node);
141
469
  return {
142
470
  path: path.length > 0,
143
471
  query: query.length > 0,
144
- body: Boolean(node.requestBody?.content?.[0]?.schema),
472
+ body: Boolean(ast.http.requestBody.getSchema(node)),
145
473
  headers: header.length > 0,
146
474
  cookies: cookie.length > 0
147
475
  };
@@ -153,7 +481,7 @@ function getRequestGroups(node) {
153
481
  */
154
482
  function getRequestGroupOptionality(node) {
155
483
  const groups = getRequestGroups(node);
156
- const { path, query, header } = getOperationParameters(node);
484
+ const { path, query, header } = ast.http.groupParameters(node);
157
485
  const hasRequiredPath = path.some((param) => param.required);
158
486
  const hasRequiredQuery = query.some((param) => param.required);
159
487
  const hasRequiredHeader = header.some((param) => param.required);
@@ -166,104 +494,28 @@ function getRequestGroupOptionality(node) {
166
494
  };
167
495
  }
168
496
  function buildOperationComments(node, options = {}) {
169
- const { link = "pathTemplate", linkPosition = "afterDeprecated", splitLines = false } = options;
170
- const linkComment = getOperationLink(node, link);
171
- const filteredComments = (linkPosition === "beforeDeprecated" ? [
172
- node.description && `@description ${node.description}`,
173
- node.summary && `@summary ${node.summary}`,
174
- linkComment,
175
- node.deprecated && "@deprecated"
176
- ] : [
497
+ const { splitLines = false } = options;
498
+ const filteredComments = [
499
+ getOperationRequestLine(node),
177
500
  node.description && `@description ${node.description}`,
178
501
  node.summary && `@summary ${node.summary}`,
179
502
  node.deprecated && "@deprecated",
180
- linkComment
181
- ]).filter((comment) => Boolean(comment));
503
+ node.externalDocs && `@see ${jsDocEscape([node.externalDocs.url, node.externalDocs.description].filter(Boolean).join(" "))}`
504
+ ].filter((comment) => Boolean(comment));
182
505
  if (!splitLines) return filteredComments;
183
506
  return filteredComments.flatMap((text) => text.split(/\r?\n/).map((line) => line.trim())).filter((comment) => Boolean(comment));
184
507
  }
185
- const operationParameterGroupsByNode = /* @__PURE__ */ new WeakMap();
186
- /**
187
- * An OpenAPI 3.2 `querystring` parameter is the whole query string, so a form-encoded object becomes one query parameter
188
- * per property, typed and serialized like any other.
189
- * Another content type, such as JSON, stays one parameter named after it, and the runtime sends its value as the whole query string.
190
- */
191
- function spreadQuerystring(param) {
192
- const schema = param.schema.type === "ref" ? param.schema.schema : param.schema;
193
- const formEncoded = !param.contentType || param.contentType.split(";")[0].trim().toLowerCase() === "application/x-www-form-urlencoded";
194
- if (schema?.type !== "object" || !formEncoded) return [param];
195
- return schema.properties.map((property) => ast.factory.createParameter({
196
- name: property.name,
197
- in: "query",
198
- schema: property.schema,
199
- required: param.required && property.required
200
- }));
201
- }
202
- /**
203
- * Groups an operation's parameters by location (`path`/`query`/`header`/`cookie`), deduping each
204
- * group by name. Every plugin generator visiting the same `OperationNode` shares one AST instance
205
- * (see `KubbDriver`), so the result is cached per node to avoid re-filtering and re-deduping the
206
- * same parameters once per plugin.
207
- */
208
- function getOperationParameters(node) {
209
- const cached = operationParameterGroupsByNode.get(node);
210
- if (cached) return cached;
211
- const dedupe = (params) => {
212
- const seen = /* @__PURE__ */ new Set();
213
- return params.filter((param) => {
214
- if (seen.has(param.name)) return false;
215
- seen.add(param.name);
216
- return true;
217
- });
218
- };
219
- const group = (location) => dedupe(node.parameters.filter((param) => param.in === location));
220
- const groups = {
221
- path: group("path"),
222
- query: dedupe([...group("query"), ...group("querystring").flatMap(spreadQuerystring)]),
223
- header: group("header"),
224
- cookie: group("cookie")
225
- };
226
- operationParameterGroupsByNode.set(node, groups);
227
- return groups;
228
- }
229
508
  /**
230
509
  * Whether the operation declares more than one request media type with a schema, so a caller must pick one on `contentType`.
231
510
  */
232
511
  function hasRequestContentTypeChoice(node) {
233
512
  return (node.requestBody?.content ?? []).filter((entry) => entry.schema).length > 1;
234
513
  }
235
- /**
236
- * Casts a call argument to the client's parameter type; a `'plain'` client is non-generic.
237
- */
238
- function castToClientOptions(callArg, clientName, returnType = "full") {
239
- return `${callArg} as Parameters<typeof ${clientName}${returnType === "plain" ? "" : "<true>"}>[0]`;
240
- }
241
- function getStatusCodeNumber(statusCode) {
242
- const code = Number(statusCode);
243
- return Number.isNaN(code) ? null : code;
244
- }
245
- function isSuccessStatusCode(statusCode) {
246
- const code = getStatusCodeNumber(statusCode);
247
- return code !== null && code >= 200 && code < 300;
248
- }
249
- function isErrorStatusCode(statusCode) {
250
- const code = getStatusCodeNumber(statusCode);
251
- return code !== null && code >= 400;
252
- }
253
- function getSuccessResponses(responses) {
254
- return responses.filter((response) => isSuccessStatusCode(response.statusCode));
255
- }
256
- function getOperationSuccessResponses(node) {
257
- return getSuccessResponses(node.responses);
258
- }
259
- function getPrimarySuccessResponse(node) {
260
- return getOperationSuccessResponses(node)[0] ?? null;
261
- }
262
514
  function resolveErrorNames(node, resolver) {
263
- return node.responses.filter((response) => isErrorStatusCode(response.statusCode)).map((response) => resolver.response.status(node, response.statusCode));
515
+ return ast.http.responses.filterError(node).map((response) => resolver.response.status(node, response.statusCode));
264
516
  }
265
517
  function resolveSuccessNames(node, resolver) {
266
- return node.responses.filter((response) => isSuccessStatusCode(response.statusCode)).map((response) => resolver.response.status(node, response.statusCode));
518
+ return ast.http.responses.filterSuccess(node).map((response) => resolver.response.status(node, response.statusCode));
267
519
  }
268
520
  function resolveStatusCodeNames(node, resolver) {
269
521
  return node.responses.map((response) => resolver.response.status(node, response.statusCode));
@@ -275,7 +527,7 @@ function resolveStatusCodeNames(node, resolver) {
275
527
  function resolveOperationTypeNames(node, resolver, options = {}) {
276
528
  const key = `${node.operationId}\0${options.order ?? ""}\0${options.responseStatusNames ?? ""}\0${options.includeParams === false ? "noparams" : ""}\0${(options.exclude ?? []).join(",")}`;
277
529
  return resolver.memo(`operationTypeNames:${key}`, () => {
278
- const { path, query, header, cookie } = getOperationParameters(node);
530
+ const { path, query, header, cookie } = ast.http.groupParameters(node);
279
531
  const responseStatusNames = options.responseStatusNames === "error" ? resolveErrorNames(node, resolver) : options.responseStatusNames === false ? [] : resolveStatusCodeNames(node, resolver);
280
532
  const exclude = new Set(options.exclude ?? []);
281
533
  const paramNames = options.includeParams === false ? [] : [
@@ -284,7 +536,7 @@ function resolveOperationTypeNames(node, resolver, options = {}) {
284
536
  ...header.map((param) => resolver.param.headers(node, param)),
285
537
  ...cookie.map((param) => resolver.param.cookies(node, param))
286
538
  ];
287
- const bodyAndResponseNames = [node.requestBody?.content?.[0]?.schema ? resolver.response.body(node) : null, resolver.response.response(node)];
539
+ const bodyAndResponseNames = [ast.http.requestBody.getSchema(node) ? resolver.response.body(node) : null, resolver.response.response(node)];
288
540
  return (options.order === "body-response-first" ? [
289
541
  ...bodyAndResponseNames,
290
542
  ...paramNames,
@@ -296,235 +548,139 @@ function resolveOperationTypeNames(node, resolver, options = {}) {
296
548
  ]).filter((name) => Boolean(name) && !exclude.has(name));
297
549
  });
298
550
  }
299
- // ../../internals/utils/src/casing.ts
300
- /**
301
- * Uppercases only the first character of `text`, leaving the rest untouched.
302
- * Unlike `pascalCase` it never re-splits word boundaries or strips characters.
303
- *
304
- * @example
305
- * `capitalize('getPetById') // 'GetPetById'`
306
- */
307
- function capitalize(text) {
308
- return `${text.charAt(0).toUpperCase()}${text.slice(1)}`;
309
- }
310
- // ../../internals/utils/src/reserved.ts
311
- /**
312
- * JavaScript and Java reserved words.
313
- * @link https://github.com/jonschlinkert/reserved/blob/master/index.js
314
- */
315
- const reservedWords = /* @__PURE__ */ new Set([
316
- "abstract",
317
- "arguments",
318
- "await",
319
- "boolean",
320
- "break",
321
- "byte",
322
- "case",
323
- "catch",
324
- "char",
325
- "class",
326
- "const",
327
- "continue",
328
- "debugger",
329
- "default",
330
- "delete",
331
- "do",
332
- "double",
333
- "else",
334
- "enum",
335
- "eval",
336
- "export",
337
- "extends",
338
- "false",
339
- "final",
340
- "finally",
341
- "float",
342
- "for",
343
- "function",
344
- "goto",
345
- "if",
346
- "implements",
347
- "import",
348
- "in",
349
- "instanceof",
350
- "int",
351
- "interface",
352
- "let",
353
- "long",
354
- "native",
355
- "new",
356
- "null",
357
- "package",
358
- "private",
359
- "protected",
360
- "public",
361
- "return",
362
- "short",
363
- "static",
364
- "super",
365
- "switch",
366
- "synchronized",
367
- "this",
368
- "throw",
369
- "throws",
370
- "transient",
371
- "true",
372
- "try",
373
- "typeof",
374
- "var",
375
- "void",
376
- "volatile",
377
- "while",
378
- "with",
379
- "yield",
380
- "Array",
381
- "Date",
382
- "hasOwnProperty",
383
- "Infinity",
384
- "isFinite",
385
- "isNaN",
386
- "isPrototypeOf",
387
- "length",
388
- "Math",
389
- "name",
390
- "NaN",
391
- "Number",
392
- "Object",
393
- "prototype",
394
- "String",
395
- "toString",
396
- "undefined",
397
- "valueOf"
398
- ]);
551
+ // ../../internals/shared/src/resolver.ts
399
552
  /**
400
- * Returns `true` when `name` is a syntactically valid JavaScript variable name.
401
- *
402
- * @example
403
- * ```ts
404
- * isValidVarName('status') // true
405
- * isValidVarName('class') // false (reserved word)
406
- * isValidVarName('42foo') // false (starts with digit)
407
- * ```
553
+ * The shared top-level `name` of the operation plugins, computed once per operation name and build.
408
554
  */
409
- function isValidVarName(name) {
410
- if (!name || reservedWords.has(name)) return false;
411
- return isIdentifier(name);
555
+ function resolveOperationName(name) {
556
+ return this.memo(`operation:${name}`, () => ensureValidVarName(casing.camel(name)));
412
557
  }
413
558
  /**
414
- * Returns `name` when it's a syntactically valid JavaScript variable name,
415
- * otherwise prefixes it with `_` so the result is a valid identifier.
559
+ * Builds the allocation key for a declaration an operation owns. `scope` names which declaration
560
+ * it is, so two of them never share an allocation even when a custom resolver gives both the same
561
+ * name. Leave `scope` empty for the operation's own declaration.
416
562
  *
417
- * Useful for sanitizing OpenAPI schema names or operation IDs that start with
418
- * a digit (e.g. `409`, `504AccountCancel`) before using them as exported
419
- * variable, type, or function names.
563
+ * The key starts from the operation's input `pointer`, which macros keep, so a plugin whose macro
564
+ * rewrites the method or path still resolves another plugin's names for the same operation.
420
565
  *
421
566
  * @example
422
567
  * ```ts
423
- * ensureValidVarName('409') // '_409'
424
- * ensureValidVarName('504AccountCancel') // '_504AccountCancel'
425
- * ensureValidVarName('Pet') // 'Pet'
426
- * ensureValidVarName('class') // '_class'
568
+ * resolver.resolveName({ key: operationKey(node, 'response', 'status', 404), name })
427
569
  * ```
428
570
  */
429
- function ensureValidVarName(name) {
430
- if (!name || isValidVarName(name)) return name;
431
- return `_${name}`;
571
+ function operationKey(node, ...scope) {
572
+ return [
573
+ "operation",
574
+ ...node.pointer ? [node.pointer] : [node.method, node.path],
575
+ ...scope.map(String)
576
+ ];
577
+ }
578
+ // ../../internals/client/src/resolveClientOperation.ts
579
+ function isRelativeImport(importPath) {
580
+ return importPath.startsWith(".");
581
+ }
582
+ function resolveImportPath(options) {
583
+ const { importPath, operationFile } = options;
584
+ return isRelativeImport(importPath) ? path.resolve(path.dirname(operationFile), importPath) : importPath;
432
585
  }
433
586
  /**
434
- * Returns `true` when `name` is syntactically a valid identifier, ignoring reserved words.
587
+ * Resolves the contract client `<op>` a consumer (query hook, MCP handler) imports, by looking up
588
+ * the registered contract client plugin's resolver and output. Works for any contract client plugin
589
+ * (plugin-fetch, plugin-axios, or plugin-client). Returns `null` when no contract plugin is in play (the inline
590
+ * path), or when the client plugin's `exclude` / `include` leaves this operation out, since it then
591
+ * emits no `<op>` to import. plugin-fetch / plugin-axios inject `.kubb/client.ts` at the global
592
+ * output root, the same path consumers read `RequestConfig` / `ResponseErrorConfig` from.
593
+ * With plugin-client they come from the user's `importPath` module instead.
435
594
  *
436
- * Reserved words and globals (`class`, `name`, `Date`, …) are valid as bare object-literal keys
437
- * even though they are not valid variable names, so use this (not {@link isValidVarName}) when
438
- * deciding whether an object key needs quoting.
595
+ * The result is cached in `memo` (the build's `ctx.memo`) under the client plugin's name and the operation,
596
+ * so several dependents reading the same client plugin for one operation in a single pass
597
+ * (react-query's query/mutation/infinite generators, vue-query, swr, the MCP handler, ...) share
598
+ * one computed result instead of each re-deriving the name and path.
439
599
  *
440
- * @example
441
- * ```ts
442
- * isIdentifier('name') // true
443
- * isIdentifier('x-total')// false
444
- * ```
600
+ * `returnType`, `output`, and `group` are the client plugin's options for this operation, after its
601
+ * `override` entries, the same resolution its own generator sees. A dependent's generated call body
602
+ * and import path therefore match the `<op>` actually emitted, even when an `override` changes it.
445
603
  */
446
- function isIdentifier(name) {
447
- return /^[a-zA-Z_$][a-zA-Z0-9_$]*$/.test(name);
604
+ function resolveClientOperation(options) {
605
+ const { clientPlugin, driver, node, root, output, memo } = options;
606
+ if (!clientPlugin) return null;
607
+ return memo(`${clientPlugin.pluginName}:clientOperation:${node.operationId}`, () => {
608
+ const resolver = driver.getResolver(clientPlugin.pluginName);
609
+ const pluginOptions = driver.getPlugin(clientPlugin.pluginName)?.options ?? {};
610
+ const { exclude, include, override } = pluginOptions;
611
+ const operationOptions = Boolean(exclude?.length || include?.length || override?.length) && resolver.default ? resolver.default.options(node, {
612
+ options: pluginOptions,
613
+ exclude,
614
+ include,
615
+ override
616
+ }) : pluginOptions;
617
+ if (!operationOptions) return null;
618
+ const file = resolver.file({
619
+ ...operationFileEntry(node, node.operationId),
620
+ root,
621
+ output: operationOptions.output ?? output,
622
+ group: operationOptions.group ?? void 0
623
+ });
624
+ const { importPath } = operationOptions;
625
+ const name = resolver.name(node.operationId);
626
+ return {
627
+ name: resolver.resolveName({
628
+ key: operationKey(node),
629
+ name
630
+ }),
631
+ path: file.path,
632
+ clientPath: importPath === void 0 ? path.resolve(root, ".kubb/client.ts") : resolveImportPath({
633
+ importPath,
634
+ operationFile: file.path
635
+ }),
636
+ clientPathIsFile: importPath === void 0 || isRelativeImport(importPath),
637
+ returnType: operationOptions.returnType ?? "full",
638
+ typedHeaders: importPath === void 0
639
+ };
640
+ });
641
+ }
642
+ // ../../internals/client/src/resolveOperationTypes.ts
643
+ const pluginZodName = "plugin-zod";
644
+ function getResponseHeadersEntries({ node, types }) {
645
+ const resolve = types.response.headers;
646
+ if (!resolve) return [];
647
+ return node.responses.filter((response) => response.headers?.length).map((response) => [response.statusCode, resolve.call(types.response, node, response.statusCode)]);
448
648
  }
449
- // ../../internals/utils/src/strings.ts
450
649
  /**
451
- * Renders a dotted path or string array as an optional-chaining accessor expression rooted at
452
- * `accessor`. Returns `null` for an empty path.
453
- *
454
- * @example
455
- * ```ts
456
- * getNestedAccessor('pagination.next.id', 'lastPage')
457
- * // "lastPage?.['pagination']?.['next']?.['id']"
458
- * ```
650
+ * The headers type argument of `RequestResult`, such as `{ '200': LoginUserStatus200Headers }`, or `null` when no response declares headers.
459
651
  */
460
- function getNestedAccessor(param, accessor) {
461
- const parts = Array.isArray(param) ? param : param.split(".");
462
- if (parts.length === 0 || parts.length === 1 && parts[0] === "") return null;
463
- return `${accessor}?.['${`${parts.join("']?.['")}']`}`;
652
+ function buildResponseHeadersType({ node, types }) {
653
+ const entries = getResponseHeadersEntries({
654
+ node,
655
+ types
656
+ }).map(([statusCode, name]) => `'${statusCode}': ${name}`);
657
+ return entries.length ? `{ ${entries.join("; ")} }` : null;
464
658
  }
465
- // ../../internals/shared/src/resolver.ts
466
659
  /**
467
- * The shared top-level `name` of the operation plugins, computed once per operation name and build.
660
+ * A runtime result type, such as `RequestResult`, in its `WithHeaders` form when `headers` is set.
468
661
  */
469
- function resolveOperationName(name) {
470
- return this.memo(`operation:${name}`, () => ensureValidVarName(camelCase(name)));
662
+ function resultTypeName(name, headers) {
663
+ return headers ? `${name}WithHeaders` : name;
471
664
  }
472
665
  /**
473
- * Builds the allocation key for a declaration an operation owns. `scope` names which declaration
474
- * it is, so two of them never share an allocation even when a custom resolver gives both the same
475
- * name. Leave `scope` empty for the operation's own declaration.
476
- *
477
- * @example
478
- * ```ts
479
- * resolver.resolveName({ key: operationKey(node, 'response', 'status', 404), name })
480
- * ```
666
+ * A runtime result type with its arguments, such as `RequestResultWithHeaders<Responses, Headers, ThrowOnError>`.
481
667
  */
482
- function operationKey(node, ...scope) {
483
- return [
484
- "operation",
485
- node.method,
486
- node.path,
487
- ...scope.map(String)
488
- ];
668
+ function buildResultTypeRef(name, responses, headers, rest = []) {
669
+ return `${resultTypeName(name, headers !== null)}<${[
670
+ responses,
671
+ headers,
672
+ ...rest
673
+ ].filter(Boolean).join(", ")}>`;
489
674
  }
490
- // ../../internals/shared/src/group.ts
491
675
  /**
492
- * Builds the `group` config a Kubb plugin passes to `ctx.setOptions`, applying the
493
- * shared default naming so every plugin groups output consistently:
494
- *
495
- * - `path` groups use the second path segment (`/pet/findByStatus` → `pet`).
496
- * - other groups use the camelCased group (`pet store` → `petStore`).
497
- *
498
- * A user-provided `group.name` always wins over the default namer, so callers stay in
499
- * control of their output folders. Returns `null` when grouping is disabled, matching the
500
- * per-plugin convention.
501
- *
502
- * @param group - The user-supplied group option, or `undefined` to disable grouping.
503
- *
504
- * @example
505
- * ```ts
506
- * createGroupConfig(group) // shared across every plugin
507
- * ```
676
+ * The names of the headers types `buildResponseHeadersType` references, for the file's imports.
508
677
  */
509
- function createGroupConfig(group) {
510
- if (!group) return null;
511
- const defaultName = (ctx) => {
512
- if (group.type === "path") return `${ctx.group.split("/")[1]}`;
513
- return camelCase(ctx.group);
514
- };
515
- return {
516
- ...group,
517
- name: group.name ? group.name : defaultName
518
- };
678
+ function getResponseHeadersTypeNames({ node, types }) {
679
+ return getResponseHeadersEntries({
680
+ node,
681
+ types
682
+ }).map(([, name]) => name);
519
683
  }
520
- // kubb-extension.json
521
- var compatibility = {
522
- "kubb": ">=5.16.0",
523
- "node": ">=22.22"
524
- };
525
- // package.json
526
- var version = "5.10.0-canary.20261109T201601";
527
- // ../../internals/client/src/resolveOperationTypes.ts
528
684
  /**
529
685
  * A `plugin-ts` shaped resolver whose operation type names point at the types `pluginZod({ inferred })`
530
686
  * exports next to each schema, so code written against `plugin-ts` names picks up the zod types.
@@ -547,6 +703,7 @@ function toZodTypeResolver(resolver) {
547
703
  status: (node, statusCode) => type(resolver.response.status(node, statusCode)),
548
704
  response: (node) => type(resolver.response.response(node)),
549
705
  responses: (node) => type(resolver.response.responses(node)),
706
+ headers: (node, statusCode) => type(resolver.response.headers(node, statusCode)),
550
707
  options: (node) => resolver.response.options(node)
551
708
  }
552
709
  };
@@ -594,179 +751,6 @@ function resolveOperationTypes(driver, pluginName) {
594
751
  };
595
752
  });
596
753
  }
597
- // ../../internals/client/src/resolveClient.ts
598
- /**
599
- * Resolves which client plugin a consumer (react-query, vue-query, swr, mcp) should call for an
600
- * operation, shared so the selection rules and diagnostics stay in one place.
601
- *
602
- * Every client runtime lives in a dedicated client plugin, so a consumer always calls a registered
603
- * contract client plugin (plugin-fetch, plugin-axios, or plugin-client) and never emits its own
604
- * inline client:
605
- *
606
- * - `contract` — a registered contract client plugin owns the `<op>` functions and the consumer
607
- * imports and calls them.
608
- * - `error` — no client plugin is registered, several are registered without a selector, the
609
- * requested one is missing, or `client` is not a known selector.
610
- *
611
- * The `client` string selects explicitly (`'fetch'` / `'axios'` / `'client'`); when it is unset a
612
- * lone registered contract client plugin is picked up automatically.
613
- */
614
- const pluginFetchName = "plugin-fetch";
615
- const pluginAxiosName = "plugin-axios";
616
- const pluginClientName = "plugin-client";
617
- const selectorToPlugin = {
618
- fetch: pluginFetchName,
619
- axios: pluginAxiosName,
620
- client: pluginClientName
621
- };
622
- const contractPlugins = [
623
- pluginFetchName,
624
- pluginAxiosName,
625
- pluginClientName
626
- ];
627
- /**
628
- * Applies the `client` resolution rules. See the module comment for the strategy shape.
629
- *
630
- * @example
631
- * ```ts
632
- * resolveClient({ client: 'fetch', pluginNames: ['plugin-ts', 'plugin-fetch'] })
633
- * // { kind: 'contract', pluginName: 'plugin-fetch' }
634
- * ```
635
- *
636
- * @example
637
- * ```ts
638
- * resolveClient({ client: undefined, pluginNames: ['plugin-ts'] })
639
- * // { kind: 'error', code: 'KUBB_MISSING_DEPENDENCY', message: 'No client plugin is registered.', help: 'Add …' }
640
- * ```
641
- */
642
- function resolveClient(options) {
643
- const { client, pluginNames } = options;
644
- const has = (name) => pluginNames.includes(name);
645
- if (client === "fetch" || client === "axios" || client === "client") {
646
- const pluginName = selectorToPlugin[client];
647
- if (!has(pluginName)) return {
648
- kind: "error",
649
- code: Diagnostics.code.missingDependency,
650
- message: `\`client: '${client}'\` is set but \`@kubb/plugin-${client}\` is not registered in \`plugins\`.`,
651
- help: `Add \`@kubb/plugin-${client}\` to \`plugins\`, or drop \`client\` to use a different client plugin.`
652
- };
653
- return {
654
- kind: "contract",
655
- pluginName
656
- };
657
- }
658
- if (client !== void 0) return {
659
- kind: "error",
660
- code: Diagnostics.code.invalidPluginOptions,
661
- message: `Option \`client\` is ${typeof client === "string" ? `'${client}'` : String(client)}, but it must be one of 'fetch', 'axios', 'client'.`,
662
- help: "Set `client` to one of those values, or drop it to use the registered client plugin."
663
- };
664
- const registered = contractPlugins.filter(has);
665
- if (registered.length === 1) return {
666
- kind: "contract",
667
- pluginName: registered[0]
668
- };
669
- if (registered.length > 1) return {
670
- kind: "error",
671
- code: Diagnostics.code.invalidPluginOptions,
672
- message: `Multiple client plugins are registered (${registered.map((name) => `\`@kubb/${name}\``).join(", ")}) and \`client\` is not set.`,
673
- help: "Set `client: 'fetch' | 'axios' | 'client'` to choose which client the hooks call, or register a single client plugin."
674
- };
675
- return {
676
- kind: "error",
677
- code: Diagnostics.code.missingDependency,
678
- message: "No client plugin is registered.",
679
- help: "Add `@kubb/plugin-fetch`, `@kubb/plugin-axios`, or `@kubb/plugin-client` to `plugins` so the generated code has an HTTP client to call."
680
- };
681
- }
682
- /**
683
- * Resolves the contract client during a consumer plugin's setup hook.
684
- * It extracts the plugin names from the raw `plugins` config and applies {@link resolveClient}.
685
- * On a misconfiguration it throws a config diagnostic tagged with `plugin`, so every consumer
686
- * fails fast with the same message.
687
- */
688
- function resolveContractClient(options) {
689
- const { plugin, client, plugins = [] } = options;
690
- const resolved = resolveClient({
691
- client,
692
- pluginNames: plugins.map((plugin) => plugin.name).filter((name) => Boolean(name))
693
- });
694
- if (resolved.kind === "error") {
695
- const { code, message, help } = resolved;
696
- throw new Diagnostics.Error({
697
- code,
698
- severity: "error",
699
- message,
700
- help,
701
- location: { kind: "config" },
702
- plugin
703
- });
704
- }
705
- return resolved;
706
- }
707
- // ../../internals/client/src/resolveClientOperation.ts
708
- function isRelativeImport(importPath) {
709
- return importPath.startsWith(".");
710
- }
711
- function resolveImportPath(options) {
712
- const { importPath, operationFile } = options;
713
- return isRelativeImport(importPath) ? path.resolve(path.dirname(operationFile), importPath) : importPath;
714
- }
715
- /**
716
- * Resolves the contract client `<op>` a consumer (query hook, MCP handler) imports, by looking up
717
- * the registered contract client plugin's resolver and output. Works for any contract client plugin
718
- * (plugin-fetch, plugin-axios, or plugin-client). Returns `null` when no contract plugin is in play (the inline
719
- * path), or when the client plugin's `exclude` / `include` leaves this operation out, since it then
720
- * emits no `<op>` to import. plugin-fetch / plugin-axios inject `.kubb/client.ts` at the global
721
- * output root, the same path consumers read `RequestConfig` / `ResponseErrorConfig` from.
722
- * With plugin-client they come from the user's `importPath` module instead.
723
- *
724
- * The result is cached in `memo` (the build's `ctx.memo`) under the client plugin's name and the operation,
725
- * so several dependents reading the same client plugin for one operation in a single pass
726
- * (react-query's query/mutation/infinite generators, vue-query, swr, the MCP handler, ...) share
727
- * one computed result instead of each re-deriving the name and path.
728
- *
729
- * `returnType`, `output`, and `group` are the client plugin's options for this operation, after its
730
- * `override` entries, the same resolution its own generator sees. A dependent's generated call body
731
- * and import path therefore match the `<op>` actually emitted, even when an `override` changes it.
732
- */
733
- function resolveClientOperation(options) {
734
- const { clientPlugin, driver, node, root, output, memo } = options;
735
- if (!clientPlugin) return null;
736
- return memo(`${clientPlugin.pluginName}:clientOperation:${node.operationId}`, () => {
737
- const resolver = driver.getResolver(clientPlugin.pluginName);
738
- const pluginOptions = driver.getPlugin(clientPlugin.pluginName)?.options ?? {};
739
- const { exclude, include, override } = pluginOptions;
740
- const operationOptions = Boolean(exclude?.length || include?.length || override?.length) && resolver.default ? resolver.default.options(node, {
741
- options: pluginOptions,
742
- exclude,
743
- include,
744
- override
745
- }) : pluginOptions;
746
- if (!operationOptions) return null;
747
- const file = resolver.file({
748
- ...operationFileEntry(node, node.operationId),
749
- root,
750
- output: operationOptions.output ?? output,
751
- group: operationOptions.group ?? void 0
752
- });
753
- const { importPath } = operationOptions;
754
- const name = resolver.name(node.operationId);
755
- return {
756
- name: resolver.resolveName({
757
- key: operationKey(node),
758
- name
759
- }),
760
- path: file.path,
761
- clientPath: importPath === void 0 ? path.resolve(root, ".kubb/client.ts") : resolveImportPath({
762
- importPath,
763
- operationFile: file.path
764
- }),
765
- clientPathIsFile: importPath === void 0 || isRelativeImport(importPath),
766
- returnType: operationOptions.returnType ?? "full"
767
- };
768
- });
769
- }
770
754
  // ../../internals/tanstack-query/src/components/HookFile.tsx
771
755
  /**
772
756
  * Resolves what every TanStack hook file shares: its file, the operation types file and names (`@kubb/plugin-ts`, or
@@ -785,14 +769,18 @@ function resolveHookFile(node, ctx, options) {
785
769
  });
786
770
  const types = resolveOperationTypes(ctx.driver, resolver.pluginName);
787
771
  const tsResolver = types.resolver;
772
+ const headerTypeNames = returnType === "data" || !contractOp.typedHeaders ? [] : getResponseHeadersTypeNames({
773
+ node,
774
+ types: tsResolver
775
+ });
788
776
  const file = resolver.file({
789
777
  ...operationFileEntry(node, name),
790
778
  root,
791
779
  output,
792
780
  group: group ?? void 0
793
781
  });
794
- const fileTs = resolveDependencyOperationFile({
795
- memo: ctx.memo,
782
+ const fileTs = resolveOperationFile({
783
+ ctx,
796
784
  node,
797
785
  resolver: tsResolver,
798
786
  root,
@@ -802,6 +790,7 @@ function resolveHookFile(node, ctx, options) {
802
790
  const importedTypeNames = [
803
791
  tsResolver.response.options(node),
804
792
  tsResolver.response.responses(node),
793
+ ...headerTypeNames,
805
794
  ...extraTypeNames,
806
795
  ...resolveOperationTypeNames(node, tsResolver, {
807
796
  exclude: [keyTypeName, returnType !== "data" ? tsResolver.response.response(node) : null].filter((name) => Boolean(name)),
@@ -819,6 +808,7 @@ function resolveHookFile(node, ctx, options) {
819
808
  tsResolver,
820
809
  contractOp,
821
810
  importedTypeNames,
811
+ resultTypeName: headerTypeNames.length ? "RequestResultWithHeaders" : "RequestResult",
822
812
  returnType,
823
813
  banner: resolver.default.banner(ctx.meta, {
824
814
  output,
@@ -836,7 +826,7 @@ function resolveHookFile(node, ctx, options) {
836
826
  * The `<File>` shell shared by the TanStack hook generators; renders `children` after the imports.
837
827
  */
838
828
  function HookFile({ hook, imports, children }) {
839
- const { file, fileTs, contractOp, importedTypeNames, banner, footer } = hook;
829
+ const { file, fileTs, contractOp, importedTypeNames, resultTypeName, banner, footer } = hook;
840
830
  return /* @__PURE__ */ jsxs(File, {
841
831
  baseName: file.baseName,
842
832
  path: file.path,
@@ -853,7 +843,7 @@ function HookFile({ hook, imports, children }) {
853
843
  name: [
854
844
  "RequestConfig",
855
845
  "ResponseErrorConfig",
856
- "RequestResult"
846
+ resultTypeName
857
847
  ],
858
848
  root: contractOp.clientPathIsFile ? file.path : void 0,
859
849
  path: contractOp.clientPath,
@@ -929,8 +919,8 @@ function buildGroupedRequestParam(node, options) {
929
919
  /**
930
920
  * Builds the shared `({ path, query, body, headers, cookies }, config = {})` parameter list for a
931
921
  * TanStack query-options function. The leading parameter mirrors the client signature, and the
932
- * trailing `config` is a partial `RequestConfig` minus the grouped data-shape keys, which are passed
933
- * explicitly. Framework plugins wrap the result when needed, for example vue-query applies `MaybeRefOrGetter`.
922
+ * trailing `config` is the client's `Options` minus the request groups and `throwOnError`, which the
923
+ * hook passes itself. Framework plugins wrap the result when needed, for example vue-query applies `MaybeRefOrGetter`.
934
924
  */
935
925
  function buildQueryOptionsParams(node, options) {
936
926
  const { resolver, memberTypeWrapper } = options;
@@ -940,7 +930,7 @@ function buildQueryOptionsParams(node, options) {
940
930
  });
941
931
  const configParam = createFunctionParameter({
942
932
  name: "config",
943
- type: buildClientOptionType(node),
933
+ type: buildClientOptionType(),
944
934
  default: "{}"
945
935
  });
946
936
  return createFunctionParameters({ params: [groupedParam, configParam].filter((param) => param !== null) });
@@ -952,26 +942,30 @@ function buildQueryOptionsParams(node, options) {
952
942
  * can't flip the query's error semantics. Queries thread the TanStack `signal`; mutations omit it.
953
943
  *
954
944
  * When `unwrapName` is set, each request group is passed as an explicit member unwrapped through it,
955
- * used by vue-query to resolve refs and getters with `toValue(...)` at call time.
945
+ * used by vue-query to resolve refs and getters with `toValue(...)` at call time. `pageParam` names the
946
+ * query parameter an infinite query sets from TanStack's `pageParam`.
956
947
  *
957
948
  * @example
958
949
  * ```ts
959
950
  * buildClientCall(node, { clientName: 'getPetById', signal: true })
960
- * // getPetById({ path, ...config, signal: config.signal ?? signal, throwOnError: true })
951
+ * // getPetById({ ...config, path, signal: config.signal ?? signal, throwOnError: true })
961
952
  * ```
962
953
  */
963
954
  function buildClientCall(node, options) {
964
- const { clientName, signal = false, unwrapName, withContentType = false, returnType = "full" } = options;
955
+ const { clientName, signal = false, unwrapName, withContentType = false, pageParam } = options;
965
956
  const { groups } = getRequestGroupOptionality(node);
966
957
  const names = requestGroupOrder.filter((key) => groups[key]);
967
- const args = [
958
+ const member = (name) => {
959
+ const value = unwrapName ? unwrapName(name) : name;
960
+ if (name === "query" && pageParam) return `query: { ...${value}, ${JSON.stringify(pageParam)}: pageParam }`;
961
+ return unwrapName ? `${name}: ${value}` : name;
962
+ };
963
+ return `${clientName}({ ${[
968
964
  "...config",
969
- ...withContentType && hasRequestContentTypeChoice(node) ? ["...variables"] : names.map((name) => unwrapName ? `${name}: ${unwrapName(name)}` : name),
965
+ ...withContentType && hasRequestContentTypeChoice(node) ? ["...variables"] : names.map(member),
970
966
  signal ? "signal: config.signal ?? signal" : null,
971
967
  "throwOnError: true"
972
- ].filter((part) => part !== null);
973
- if (withContentType && hasRequestContentTypeChoice(node)) return `${clientName}(${castToClientOptions(`{ ${args.join(", ")} }`, clientName, returnType)})`;
974
- return `${clientName}({ ${args.join(", ")} })`;
968
+ ].filter((part) => part !== null).join(", ")} })`;
975
969
  }
976
970
  /**
977
971
  * Builds the query/mutation body that resolves a `buildClientCall` expression down to the bare
@@ -994,19 +988,28 @@ function buildCallResultBody(call, options = {}) {
994
988
  if (clientReturnType === "plain") return `return (await ${call}).data`;
995
989
  return `return ${call}.unwrap()`;
996
990
  }
991
+ function buildResultType(node, resolver, typedHeaders) {
992
+ const headers = typedHeaders ? buildResponseHeadersType({
993
+ node,
994
+ types: resolver
995
+ }) : null;
996
+ return buildResultTypeRef("RequestResult", resolver.response.responses(node), headers, ["true"]);
997
+ }
997
998
  /**
998
999
  * Builds the `TData` / `TError` type expressions shared by every generated hook, joining the resolved
999
1000
  * success responses into `TData` and wrapping the error responses in `ResponseErrorConfig<...>`.
1000
- * With `returnType` `'full'`, `TData` is the client's `RequestResult` over the operation's responses record.
1001
+ * With `returnType` `'full'`, `TData` is the client's `RequestResult` over the operation's responses record, with the
1002
+ * response headers the operation declares.
1001
1003
  */
1002
1004
  function buildResponseTypes(node, resolver, options = {}) {
1003
- const { returnType = "data" } = options;
1004
- return resolver.memo(`responseTypes:${returnType}:${node.method}\0${node.path}`, () => {
1005
+ const { returnType = "data", typedHeaders = true } = options;
1006
+ return resolver.memo(`responseTypes:${returnType}:${typedHeaders}:${node.method}\0${node.path}`, () => {
1005
1007
  const successNames = resolveSuccessNames(node, resolver);
1006
- const responseName = successNames.length > 0 ? successNames.join(" | ") : resolver.response.response(node);
1008
+ const bodyName = successNames.length > 0 ? successNames.join(" | ") : resolver.response.response(node);
1009
+ const responseName = hasStreamVariant(node) ? `${bodyName} | ReadableStream<Uint8Array>` : bodyName;
1007
1010
  const errorNames = resolveErrorNames(node, resolver);
1008
1011
  return {
1009
- TData: returnType !== "data" ? `RequestResult<${resolver.response.responses(node)}, true>` : responseName,
1012
+ TData: returnType === "data" ? responseName : buildResultType(node, resolver, typedHeaders),
1010
1013
  TError: `ResponseErrorConfig<${errorNames.length > 0 ? errorNames.join(" | ") : "Error"}>`
1011
1014
  };
1012
1015
  });
@@ -1025,7 +1028,7 @@ function resolveFallbackPageParamType(initialPageParam) {
1025
1028
  * so callers can reuse it when rewriting the paginated request.
1026
1029
  */
1027
1030
  function resolvePageParamType(node, { resolver, initialPageParam, queryParam }) {
1028
- const firstQueryParam = getOperationParameters(node).query[0];
1031
+ const firstQueryParam = ast.http.groupParameters(node).query[0];
1029
1032
  const groupName = firstQueryParam ? resolver.param.query(node, firstQueryParam) : null;
1030
1033
  const queryParamsTypeName = groupName !== (firstQueryParam ? resolver.param.name(node, firstQueryParam) : null) ? groupName : null;
1031
1034
  const queryParamType = queryParam && queryParamsTypeName ? `${queryParamsTypeName}['${queryParam}']` : null;
@@ -1128,7 +1131,7 @@ function resolveInfiniteConfig(infinite) {
1128
1131
  */
1129
1132
  function matchesInfinite(node, infinite) {
1130
1133
  if (!infinite.queryParam) return false;
1131
- return getOperationParameters(node).query.some((param) => param.name.replace(/\?$/, "") === infinite.queryParam);
1134
+ return ast.http.groupParameters(node).query.some((param) => param.name.replace(/\?$/, "") === infinite.queryParam);
1132
1135
  }
1133
1136
  /**
1134
1137
  * Warning for an infinite hook that falls back to the built-in page params on a response that
@@ -1136,8 +1139,8 @@ function matchesInfinite(node, infinite) {
1136
1139
  */
1137
1140
  function getDefaultPageParamsWarning(node, infinite) {
1138
1141
  if (!(infinite.getNextPageParam == null && infinite.getPreviousPageParam == null && infinite.nextParam == null && infinite.previousParam == null && !infinite.cursorParam)) return null;
1139
- const content = getPrimarySuccessResponse(node)?.content;
1140
- let resolved = (content?.find((item) => item.contentType?.includes("json")) ?? content?.[0])?.schema;
1142
+ const content = ast.http.responses.findPrimary(node)?.content;
1143
+ let resolved = (content?.find((item) => ast.mediaType.isJson(item)) ?? content?.[0])?.schema;
1141
1144
  while (resolved?.type === "ref" || resolved?.type === "intersection" && resolved.members?.length === 1) resolved = resolved.type === "ref" ? resolved.schema : resolved.members?.[0];
1142
1145
  if (!resolved || resolved.type === "array") return null;
1143
1146
  return `${node.operationId}: the default infinite page params expect an array response and never end for this one. Set \`infinite.getNextPageParam\` (or \`nextParam\`) to read the next page from the response.`;
@@ -1220,9 +1223,15 @@ function buildHookParams(node, options) {
1220
1223
  }
1221
1224
  /**
1222
1225
  * The `TRequest` type argument of a mutation hook: the options type when the operation takes a request, else `undefined`.
1226
+ * When the variables carry the request media type and the response has several, `contentType` can pick the
1227
+ * response one too, as `{ request, response }`.
1223
1228
  */
1224
1229
  function resolveMutationRequestType(node, resolver) {
1225
- return buildGroupedRequestParam(node, { resolver }) ? resolver.response.options(node) : "undefined";
1230
+ if (!buildGroupedRequestParam(node, { resolver })) return "undefined";
1231
+ const optionsName = resolver.response.options(node);
1232
+ const response = getResponseContentTypeInfo(node);
1233
+ if (!hasRequestContentTypeChoice(node) || !response.isMultipleContentTypes) return optionsName;
1234
+ return `${/^[\w$]+$/.test(optionsName) ? optionsName : `(${optionsName})`} & { contentType?: string | { response?: ${response.contentTypeUnion} } }`;
1226
1235
  }
1227
1236
  /**
1228
1237
  * Builds the object literal that unwraps each request group's getter before it reaches `queryKey`/`queryOptions`; `null` without a request group.
@@ -1236,8 +1245,11 @@ function buildResolvedRequestParams(node) {
1236
1245
  // ../../internals/tanstack-query/src/components/InfiniteQueryOptions.tsx
1237
1246
  const declarationPrinter$6 = functionPrinter({ mode: "declaration" });
1238
1247
  const callPrinter$3 = functionPrinter({ mode: "call" });
1239
- function InfiniteQueryOptions({ name, clientName, initialPageParam, hasExplicitInitialPageParam, cursorParam, nextParam, previousParam, getNextPageParam, getPreviousPageParam, node, tsResolver, queryParam, queryKeyName, queryKeyType = "typeof queryKey", memberTypeWrapper, unwrapName, clientReturnType, returnType }) {
1240
- const { TData: queryFnDataType, TError: errorType } = buildResponseTypes(node, tsResolver, { returnType });
1248
+ function InfiniteQueryOptions({ name, clientName, initialPageParam, hasExplicitInitialPageParam, cursorParam, nextParam, previousParam, getNextPageParam, getPreviousPageParam, node, tsResolver, queryParam, queryKeyName, queryKeyType = "typeof queryKey", memberTypeWrapper, unwrapName, clientReturnType, returnType, typedHeaders }) {
1249
+ const { TData: queryFnDataType, TError: errorType } = buildResponseTypes(node, tsResolver, {
1250
+ returnType,
1251
+ typedHeaders
1252
+ });
1241
1253
  const dataSuffix = returnType === "data" ? "" : ".data";
1242
1254
  const { queryParamsTypeName, pageParamType } = resolvePageParamType(node, {
1243
1255
  resolver: tsResolver,
@@ -1256,10 +1268,12 @@ function InfiniteQueryOptions({ name, clientName, initialPageParam, hasExplicitI
1256
1268
  memberTypeWrapper
1257
1269
  });
1258
1270
  const paramsSignature = declarationPrinter$6.print(paramsNode) ?? "";
1271
+ const pageParam = queryParam && queryParamsTypeName ? queryParam : void 0;
1259
1272
  const queryFnBody = buildCallResultBody(buildClientCall(node, {
1260
1273
  clientName,
1261
1274
  signal: true,
1262
- unwrapName
1275
+ unwrapName,
1276
+ pageParam
1263
1277
  }), {
1264
1278
  clientReturnType,
1265
1279
  returnType
@@ -1286,12 +1300,7 @@ function InfiniteQueryOptions({ name, clientName, initialPageParam, hasExplicitI
1286
1300
  getNextPageParamExpr,
1287
1301
  getPreviousPageParamExpr
1288
1302
  ].filter(Boolean);
1289
- const infiniteOverrideParams = queryParam && queryParamsTypeName ? `query = {
1290
- ...(query ?? {}),
1291
- ['${queryParam}']: pageParam as unknown as ${queryParamsTypeName}['${queryParam}'],
1292
- } as ${queryParamsTypeName}` : "";
1293
- const queryFnArgs = infiniteOverrideParams ? "{ signal, pageParam }" : "{ signal }";
1294
- const queryFnStatements = infiniteOverrideParams ? `${infiniteOverrideParams}\n ${queryFnBody}` : queryFnBody;
1303
+ const queryFnArgs = pageParam ? "{ signal, pageParam }" : "{ signal }";
1295
1304
  return /* @__PURE__ */ jsx(File.Source, {
1296
1305
  name,
1297
1306
  isExportable: true,
@@ -1305,7 +1314,7 @@ const queryKey = ${queryKeyName}(${queryKeyParamsCall})
1305
1314
  return infiniteQueryOptions<${queryFnDataType}, ${errorType}, InfiniteData<${queryFnDataType}>, ${queryKeyType}, ${pageParamType}>({
1306
1315
  queryKey,
1307
1316
  queryFn: async (${queryFnArgs}) => {
1308
- ${queryFnStatements}
1317
+ ${queryFnBody}
1309
1318
  },
1310
1319
  ${queryOptionsArr.join(",\n ")}
1311
1320
  })
@@ -1376,7 +1385,7 @@ function MutationKey({ name, node, transformer, typeName }) {
1376
1385
  function createQueryResolver(variant = "") {
1377
1386
  return {
1378
1387
  name(node) {
1379
- const name = `use${capitalize(this.name(node.operationId))}${variant}`;
1388
+ const name = `use${casing.capitalize(this.name(node.operationId))}${variant}`;
1380
1389
  return this.resolveName({
1381
1390
  key: operationKey(node, "query", variant, "name"),
1382
1391
  name
@@ -1397,7 +1406,7 @@ function createQueryResolver(variant = "") {
1397
1406
  });
1398
1407
  },
1399
1408
  keyTypeName(node) {
1400
- const name = `${capitalize(this.name(node.operationId))}${variant}QueryKey`;
1409
+ const name = `${casing.capitalize(this.name(node.operationId))}${variant}QueryKey`;
1401
1410
  return this.resolveName({
1402
1411
  key: operationKey(node, "query", variant, "keyTypeName"),
1403
1412
  name
@@ -1418,7 +1427,7 @@ function createQueryResolver(variant = "") {
1418
1427
  function createMutationResolver() {
1419
1428
  return {
1420
1429
  name(node) {
1421
- const name = `use${capitalize(this.name(node.operationId))}`;
1430
+ const name = `use${casing.capitalize(this.name(node.operationId))}`;
1422
1431
  return this.resolveName({
1423
1432
  key: operationKey(node, "mutation", "name"),
1424
1433
  name
@@ -1447,14 +1456,14 @@ const declarationPrinter$4 = functionPrinter({ mode: "declaration" });
1447
1456
  */
1448
1457
  const queryKeyTransformer = ({ node, variant }) => {
1449
1458
  if (!node.path) return [];
1450
- const { path, query } = getOperationParameters(node);
1459
+ const { path, query } = ast.http.groupParameters(node);
1451
1460
  const isInfinite = variant === "infiniteQuery" || variant === "suspenseInfiniteQuery";
1452
1461
  const urlFields = [`url: '${Url.toPath(node.path)}'`];
1453
1462
  if (path.length > 0) urlFields.push("params: path");
1454
1463
  if (isInfinite) urlFields.push("infinite: true");
1455
1464
  const result = [`{ ${urlFields.join(", ")} }`];
1456
1465
  if (query.length > 0) result.push("...(query ? [query] : [])");
1457
- if (node.requestBody?.content?.[0]?.schema) result.push("...(body !== undefined ? [body] : [])");
1466
+ if (ast.http.requestBody.getSchema(node)) result.push("...(body !== undefined ? [body] : [])");
1458
1467
  return result;
1459
1468
  };
1460
1469
  function QueryKey({ name, node, tsResolver, typeName, transformer, variant = "query", memberTypeWrapper, exportType }) {
@@ -1538,6 +1547,13 @@ const customHookOptionsFileGenerator = defineGenerator({
1538
1547
  const relativeToOutput = path.relative(outputDir, file.path);
1539
1548
  if (relativeToOutput === ".." || relativeToOutput.startsWith(`..${path.sep}`) || path.isAbsolute(relativeToOutput)) return null;
1540
1549
  if (fs.existsSync(file.path)) return null;
1550
+ const hookOptionsPath = resolver.file({
1551
+ name: hookOptionsName,
1552
+ extname: ".ts",
1553
+ root,
1554
+ output,
1555
+ group: group ?? void 0
1556
+ }).path.replace(/[^/\\]+\.ts$/, `${hookOptionsName}.ts`);
1541
1557
  return /* @__PURE__ */ jsxs(File, {
1542
1558
  baseName: file.baseName,
1543
1559
  path: file.path,
@@ -1554,7 +1570,8 @@ const customHookOptionsFileGenerator = defineGenerator({
1554
1570
  /* @__PURE__ */ jsx(File.Import, {
1555
1571
  name: [hookOptionsName],
1556
1572
  root: file.path,
1557
- path: path.resolve(root, "./index.ts")
1573
+ path: hookOptionsPath,
1574
+ isTypeOnly: true
1558
1575
  }),
1559
1576
  /* @__PURE__ */ jsxs(File.Source, {
1560
1577
  name,
@@ -1618,7 +1635,7 @@ const hookOptionsGenerator = defineGenerator({
1618
1635
  const imports = [];
1619
1636
  const hookOptions = {};
1620
1637
  for (const node of nodes) {
1621
- if (!ast.isHttpOperationNode(node)) continue;
1638
+ if (!ast.http.isOperation(node)) continue;
1622
1639
  const opOverrides = resolveOperationOverrides(node, override);
1623
1640
  const nodeQuery = "query" in opOverrides ? opOverrides.query : query;
1624
1641
  const nodeMutation = "mutation" in opOverrides ? opOverrides.mutation : mutation;
@@ -1741,8 +1758,11 @@ function buildHookTypes({ node, tsResolver, infinite, suspense, TData, TError, q
1741
1758
  resultType: `${resultTypeName}<TData, ${TError}> & { queryKey: TQueryKey }`
1742
1759
  };
1743
1760
  }
1744
- function Query({ name, queryKeyTypeName, queryOptionsName, queryKeyName, node, tsResolver, customOptions, suspense, infinite, returnType }) {
1745
- const { TData, TError } = buildResponseTypes(node, tsResolver, { returnType });
1761
+ function Query({ name, queryKeyTypeName, queryOptionsName, queryKeyName, node, tsResolver, customOptions, suspense, infinite, returnType, typedHeaders }) {
1762
+ const { TData, TError } = buildResponseTypes(node, tsResolver, {
1763
+ returnType,
1764
+ typedHeaders
1765
+ });
1746
1766
  const prefix = suspense ? "UseSuspense" : "Use";
1747
1767
  const hookName = `${suspense ? "useSuspense" : "use"}${infinite ? "InfiniteQuery" : "Query"}`;
1748
1768
  const observerOptionsName = OBSERVER_OPTIONS_NAMES[infinite ? "infinite" : "plain"][suspense ? "suspense" : "standard"];
@@ -1766,7 +1786,7 @@ function Query({ name, queryKeyTypeName, queryOptionsName, queryKeyName, node, t
1766
1786
  memberTypeWrapper: maybeValueOrGetter,
1767
1787
  optionsType: `{
1768
1788
  query?: Partial<${observerOptionsType}> & { client?: QueryClient },
1769
- client?: ${buildClientOptionType(node)}
1789
+ client?: ${buildClientOptionType()}
1770
1790
  }`
1771
1791
  });
1772
1792
  const paramsSignature = declarationPrinter$3.print(paramsNode) ?? "";
@@ -1802,8 +1822,11 @@ function Query({ name, queryKeyTypeName, queryOptionsName, queryKeyName, node, t
1802
1822
  // src/components/QueryOptions.tsx
1803
1823
  const declarationPrinter$2 = functionPrinter({ mode: "declaration" });
1804
1824
  const callPrinter$2 = functionPrinter({ mode: "call" });
1805
- function QueryOptions({ name, clientName, node, tsResolver, queryKeyName, clientReturnType, returnType }) {
1806
- const { TData, TError } = buildResponseTypes(node, tsResolver, { returnType });
1825
+ function QueryOptions({ name, clientName, node, tsResolver, queryKeyName, clientReturnType, returnType, typedHeaders }) {
1826
+ const { TData, TError } = buildResponseTypes(node, tsResolver, {
1827
+ returnType,
1828
+ typedHeaders
1829
+ });
1807
1830
  const queryKeyParamsNode = buildQueryKeyParams(node, { resolver: tsResolver });
1808
1831
  const queryKeyParamsCall = callPrinter$2.print(queryKeyParamsNode) ?? "";
1809
1832
  const paramsNode = buildQueryOptionsParams(node, { resolver: tsResolver });
@@ -1856,7 +1879,7 @@ function createQueryGenerator({ name, variant }) {
1856
1879
  renderer: jsxRenderer,
1857
1880
  match(node, ctx) {
1858
1881
  const operationNode = node;
1859
- if (!ast.isHttpOperationNode(operationNode)) return false;
1882
+ if (!ast.http.isOperation(operationNode)) return false;
1860
1883
  const { query, mutation, infinite: infiniteConfig, suspense: suspenseConfig, hooks } = ctx.options;
1861
1884
  const { isQuery, isMutation } = classifyOperation(operationNode, {
1862
1885
  query,
@@ -1871,7 +1894,7 @@ function createQueryGenerator({ name, variant }) {
1871
1894
  return !!infiniteOptions && matchesInfinite(operationNode, infiniteOptions);
1872
1895
  },
1873
1896
  operation(node, ctx) {
1874
- if (!ast.isHttpOperationNode(node)) return null;
1897
+ if (!ast.http.isOperation(node)) return null;
1875
1898
  const { driver, resolver, root } = ctx;
1876
1899
  const { output, query, infinite: infiniteConfig, client, group, customOptions, hooks } = ctx.options;
1877
1900
  const infiniteOptions = infinite && infiniteConfig && typeof infiniteConfig === "object" ? infiniteConfig : null;
@@ -1949,7 +1972,8 @@ function createQueryGenerator({ name, variant }) {
1949
1972
  hasExplicitInitialPageParam: infiniteOptions.hasExplicitInitialPageParam,
1950
1973
  queryParam: infiniteOptions.queryParam,
1951
1974
  clientReturnType: contractOp.returnType,
1952
- returnType: hook.returnType
1975
+ returnType: hook.returnType,
1976
+ typedHeaders: hook.contractOp.typedHeaders
1953
1977
  })
1954
1978
  ] }) : /* @__PURE__ */ jsxs(Fragment, { children: [/* @__PURE__ */ jsx(File.Import, {
1955
1979
  name: ["queryOptions"],
@@ -1961,7 +1985,8 @@ function createQueryGenerator({ name, variant }) {
1961
1985
  node,
1962
1986
  tsResolver: hook.tsResolver,
1963
1987
  clientReturnType: contractOp.returnType,
1964
- returnType: hook.returnType
1988
+ returnType: hook.returnType,
1989
+ typedHeaders: hook.contractOp.typedHeaders
1965
1990
  })] }),
1966
1991
  (variant !== "query" || query && hooks) && /* @__PURE__ */ jsxs(Fragment, { children: [
1967
1992
  /* @__PURE__ */ jsx(File.Import, {
@@ -1986,6 +2011,7 @@ function createQueryGenerator({ name, variant }) {
1986
2011
  tsResolver: hook.tsResolver,
1987
2012
  customOptions,
1988
2013
  returnType: hook.returnType,
2014
+ typedHeaders: hook.contractOp.typedHeaders,
1989
2015
  suspense,
1990
2016
  infinite: infiniteOptions ? {
1991
2017
  initialPageParam: infiniteOptions.initialPageParam,
@@ -2020,8 +2046,11 @@ function buildMutationConfigParamsNode(node) {
2020
2046
  default: "{}"
2021
2047
  })] });
2022
2048
  }
2023
- function MutationOptions({ name, clientName, node, tsResolver, mutationKeyName, clientReturnType, returnType }) {
2024
- const { TData, TError } = buildResponseTypes(node, tsResolver, { returnType });
2049
+ function MutationOptions({ name, clientName, node, tsResolver, mutationKeyName, clientReturnType, returnType, typedHeaders }) {
2050
+ const { TData, TError } = buildResponseTypes(node, tsResolver, {
2051
+ returnType,
2052
+ typedHeaders
2053
+ });
2025
2054
  const configParamsNode = buildMutationConfigParamsNode(node);
2026
2055
  const paramsSignature = declarationPrinter$1.print(configParamsNode) ?? "";
2027
2056
  const groupedParam = buildGroupedRequestParam(node, {
@@ -2030,13 +2059,12 @@ function MutationOptions({ name, clientName, node, tsResolver, mutationKeyName,
2030
2059
  });
2031
2060
  const hasMutationParams = groupedParam !== null;
2032
2061
  const groupedParamsNode = createFunctionParameters({ params: groupedParam ? [groupedParam] : [] });
2033
- const TRequest = hasMutationParams ? tsResolver.response.options(node) : "undefined";
2062
+ const TRequest = resolveMutationRequestType(node, tsResolver);
2034
2063
  const argBindingStr = hasMutationParams ? callPrinter$1.print(groupedParamsNode) ?? "" : "_";
2035
2064
  const mutationFnBody = buildCallResultBody(buildClientCall(node, {
2036
2065
  clientName,
2037
2066
  signal: false,
2038
- withContentType: true,
2039
- returnType: clientReturnType
2067
+ withContentType: true
2040
2068
  }), {
2041
2069
  clientReturnType,
2042
2070
  returnType
@@ -2066,8 +2094,11 @@ function MutationOptions({ name, clientName, node, tsResolver, mutationKeyName,
2066
2094
  const declarationPrinter = functionPrinter({ mode: "declaration" });
2067
2095
  const callPrinter = functionPrinter({ mode: "call" });
2068
2096
  function buildMutationParamsNode(node, options) {
2069
- const { resolver, returnType } = options;
2070
- const { TData, TError } = buildResponseTypes(node, resolver, { returnType });
2097
+ const { resolver, returnType, typedHeaders } = options;
2098
+ const { TData, TError } = buildResponseTypes(node, resolver, {
2099
+ returnType,
2100
+ typedHeaders
2101
+ });
2071
2102
  return buildHookParams(node, {
2072
2103
  resolver,
2073
2104
  withRequest: false,
@@ -2082,8 +2113,11 @@ function buildMutationParamsNode(node, options) {
2082
2113
  }`
2083
2114
  });
2084
2115
  }
2085
- function Mutation({ name, mutationOptionsName, node, tsResolver, mutationKeyName, customOptions, returnType }) {
2086
- const { TData, TError } = buildResponseTypes(node, tsResolver, { returnType });
2116
+ function Mutation({ name, mutationOptionsName, node, tsResolver, mutationKeyName, customOptions, returnType, typedHeaders }) {
2117
+ const { TData, TError } = buildResponseTypes(node, tsResolver, {
2118
+ returnType,
2119
+ typedHeaders
2120
+ });
2087
2121
  const generics = [
2088
2122
  TData,
2089
2123
  TError,
@@ -2095,7 +2129,8 @@ function Mutation({ name, mutationOptionsName, node, tsResolver, mutationKeyName
2095
2129
  const mutationOptionsParamsCall = callPrinter.print(mutationOptionsConfigNode) ?? "";
2096
2130
  const paramsNode = buildMutationParamsNode(node, {
2097
2131
  resolver: tsResolver,
2098
- returnType
2132
+ returnType,
2133
+ typedHeaders
2099
2134
  });
2100
2135
  const paramsSignature = declarationPrinter.print(paramsNode) ?? "";
2101
2136
  return /* @__PURE__ */ jsx(File.Source, {
@@ -2135,7 +2170,7 @@ const mutationGenerator = defineGenerator({
2135
2170
  renderer: jsxRenderer,
2136
2171
  match(node, ctx) {
2137
2172
  const operationNode = node;
2138
- if (!ast.isHttpOperationNode(operationNode)) return false;
2173
+ if (!ast.http.isOperation(operationNode)) return false;
2139
2174
  const { query, mutation } = ctx.options;
2140
2175
  return classifyOperation(operationNode, {
2141
2176
  query,
@@ -2143,7 +2178,7 @@ const mutationGenerator = defineGenerator({
2143
2178
  }).isMutation;
2144
2179
  },
2145
2180
  operation(node, ctx) {
2146
- if (!ast.isHttpOperationNode(node)) return null;
2181
+ if (!ast.http.isOperation(node)) return null;
2147
2182
  const { driver, resolver, root } = ctx;
2148
2183
  const { output, mutation, client, group, customOptions, hooks } = ctx.options;
2149
2184
  const importPath = mutation ? mutation.importPath : "@tanstack/react-query";
@@ -2190,7 +2225,8 @@ const mutationGenerator = defineGenerator({
2190
2225
  node,
2191
2226
  tsResolver,
2192
2227
  clientReturnType: contractOp.returnType,
2193
- returnType: hook.returnType
2228
+ returnType: hook.returnType,
2229
+ typedHeaders: hook.contractOp.typedHeaders
2194
2230
  }),
2195
2231
  mutation && hooks && /* @__PURE__ */ jsxs(Fragment, { children: [
2196
2232
  /* @__PURE__ */ jsx(File.Import, {
@@ -2213,7 +2249,8 @@ const mutationGenerator = defineGenerator({
2213
2249
  node,
2214
2250
  tsResolver,
2215
2251
  customOptions,
2216
- returnType: hook.returnType
2252
+ returnType: hook.returnType,
2253
+ typedHeaders: hook.contractOp.typedHeaders
2217
2254
  })
2218
2255
  ] })
2219
2256
  ]
@@ -2372,7 +2409,7 @@ const pluginReactQuery = defineExtension({
2372
2409
  ...customOptions
2373
2410
  } : null,
2374
2411
  hooks,
2375
- group: createGroupConfig(group),
2412
+ group: group ?? null,
2376
2413
  exclude,
2377
2414
  include,
2378
2415
  override,