@kubb/plugin-react-query 5.0.0-beta.98 → 5.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js CHANGED
@@ -1,328 +1,24 @@
1
1
  import "./rolldown-runtime-C0LytTxp.js";
2
+ import { Resolver, Url, ast, createResolver, defineGenerator, definePlugin } from "kubb/kit";
2
3
  import path from "node:path";
3
- import { Resolver, ast, createResolver, defineGenerator, definePlugin } from "kubb/kit";
4
4
  import { createFunctionParameter, createFunctionParameters, createObjectBindingPattern, createTypeLiteral, functionPrinter, pluginTsName } from "@kubb/plugin-ts";
5
5
  import { File, Function, Type, jsxRenderer } from "kubb/jsx";
6
6
  import { Fragment, jsx, jsxs } from "kubb/jsx/jsx-runtime";
7
7
  import fs from "node:fs";
8
- //#region ../../internals/utils/src/casing.ts
9
- /**
10
- * Shared implementation for camelCase and PascalCase conversion.
11
- * Splits on common word boundaries (spaces, hyphens, underscores, dots, slashes, colons)
12
- * and capitalizes each word according to `pascal`.
13
- *
14
- * When `pascal` is `true` the first word is also capitalized (PascalCase), otherwise only subsequent words are.
15
- */
16
- function toCamelOrPascal(text, pascal) {
17
- return text.trim().replace(/([a-z\d])([A-Z])/g, "$1 $2").replace(/([A-Z]+)([A-Z][a-z])/g, "$1 $2").replace(/(\d)([a-z])/g, "$1 $2").split(/[\s\-_./\\:]+/).filter(Boolean).map((word, i) => {
18
- if (word.length > 1 && word === word.toUpperCase()) return word;
19
- return (i === 0 && !pascal ? word.charAt(0).toLowerCase() : word.charAt(0).toUpperCase()) + word.slice(1);
20
- }).join("").replace(/[^a-zA-Z0-9]/g, "");
21
- }
22
- /**
23
- * Converts `text` to camelCase.
24
- *
25
- * @example Word boundaries
26
- * `camelCase('hello-world') // 'helloWorld'`
27
- *
28
- * @example With a prefix
29
- * `camelCase('tag', { prefix: 'create' }) // 'createTag'`
30
- */
31
- function camelCase(text, { prefix = "", suffix = "" } = {}) {
32
- return toCamelOrPascal(`${prefix} ${text} ${suffix}`, false);
33
- }
34
- /**
35
- * Uppercases only the first character of `text`, leaving the rest untouched.
36
- * Unlike {@link pascalCase} it never re-splits word boundaries or strips characters.
37
- *
38
- * @example
39
- * `capitalize('getPetById') // 'GetPetById'`
40
- */
41
- function capitalize(text) {
42
- return `${text.charAt(0).toUpperCase()}${text.slice(1)}`;
43
- }
44
- //#endregion
45
- //#region ../../internals/utils/src/reserved.ts
46
- /**
47
- * JavaScript and Java reserved words.
48
- * @link https://github.com/jonschlinkert/reserved/blob/master/index.js
49
- */
50
- const reservedWords = /* @__PURE__ */ new Set([
51
- "abstract",
52
- "arguments",
53
- "boolean",
54
- "break",
55
- "byte",
56
- "case",
57
- "catch",
58
- "char",
59
- "class",
60
- "const",
61
- "continue",
62
- "debugger",
63
- "default",
64
- "delete",
65
- "do",
66
- "double",
67
- "else",
68
- "enum",
69
- "eval",
70
- "export",
71
- "extends",
72
- "false",
73
- "final",
74
- "finally",
75
- "float",
76
- "for",
77
- "function",
78
- "goto",
79
- "if",
80
- "implements",
81
- "import",
82
- "in",
83
- "instanceof",
84
- "int",
85
- "interface",
86
- "let",
87
- "long",
88
- "native",
89
- "new",
90
- "null",
91
- "package",
92
- "private",
93
- "protected",
94
- "public",
95
- "return",
96
- "short",
97
- "static",
98
- "super",
99
- "switch",
100
- "synchronized",
101
- "this",
102
- "throw",
103
- "throws",
104
- "transient",
105
- "true",
106
- "try",
107
- "typeof",
108
- "var",
109
- "void",
110
- "volatile",
111
- "while",
112
- "with",
113
- "yield",
114
- "Array",
115
- "Date",
116
- "hasOwnProperty",
117
- "Infinity",
118
- "isFinite",
119
- "isNaN",
120
- "isPrototypeOf",
121
- "length",
122
- "Math",
123
- "name",
124
- "NaN",
125
- "Number",
126
- "Object",
127
- "prototype",
128
- "String",
129
- "toString",
130
- "undefined",
131
- "valueOf"
132
- ]);
133
- /**
134
- * Returns `true` when `name` is a syntactically valid JavaScript variable name.
135
- *
136
- * @example
137
- * ```ts
138
- * isValidVarName('status') // true
139
- * isValidVarName('class') // false (reserved word)
140
- * isValidVarName('42foo') // false (starts with digit)
141
- * ```
142
- */
143
- function isValidVarName(name) {
144
- if (!name || reservedWords.has(name)) return false;
145
- return isIdentifier(name);
146
- }
147
- /**
148
- * Returns `true` when `name` is syntactically a valid identifier, ignoring reserved words.
149
- *
150
- * Reserved words and globals (`class`, `name`, `Date`, …) are valid as bare object-literal keys
151
- * even though they are not valid variable names, so use this (not {@link isValidVarName}) when
152
- * deciding whether an object key needs quoting.
153
- *
154
- * @example
155
- * ```ts
156
- * isIdentifier('name') // true
157
- * isIdentifier('x-total')// false
158
- * ```
159
- */
160
- function isIdentifier(name) {
161
- return /^[a-zA-Z_$][a-zA-Z0-9_$]*$/.test(name);
162
- }
163
- //#endregion
164
- //#region ../../internals/utils/src/strings.ts
165
- /**
166
- * Renders a dotted path or string array as an optional-chaining accessor expression rooted at
167
- * `accessor`. Returns `null` for an empty path.
168
- *
169
- * @example
170
- * ```ts
171
- * getNestedAccessor('pagination.next.id', 'lastPage')
172
- * // "lastPage?.['pagination']?.['next']?.['id']"
173
- * ```
174
- */
175
- function getNestedAccessor(param, accessor) {
176
- const parts = Array.isArray(param) ? param : param.split(".");
177
- if (parts.length === 0 || parts.length === 1 && parts[0] === "") return null;
178
- return `${accessor}?.['${`${parts.join("']?.['")}']`}`;
179
- }
180
- //#endregion
181
- //#region ../../internals/utils/src/url.ts
182
- function transformParam(raw, casing) {
183
- const param = isValidVarName(raw) ? raw : camelCase(raw);
184
- return casing === "camelcase" ? camelCase(param) : param;
185
- }
186
- function toParamsObject(path, { replacer, casing } = {}) {
187
- const params = {};
188
- for (const match of path.matchAll(/\{([^}]+)\}/g)) {
189
- const param = transformParam(match[1], casing);
190
- const key = replacer ? replacer(param) : param;
191
- params[key] = key;
192
- }
193
- return Object.keys(params).length > 0 ? params : null;
194
- }
195
- /**
196
- * Helpers for OpenAPI/Swagger paths, plus a thin wrapper over the native `URL`.
197
- */
198
- var Url = class Url {
199
- /**
200
- * Reports whether `url` is a parseable absolute URL. Delegates to the native `URL.canParse`.
201
- *
202
- * @example
203
- * Url.canParse('https://petstore.swagger.io/v2') // true
204
- * Url.canParse('/pet/{petId}') // false
205
- */
206
- static canParse(url, base) {
207
- return URL.canParse(url, base);
208
- }
209
- /**
210
- * Converts an OpenAPI/Swagger path to Express-style colon syntax.
211
- *
212
- * @example
213
- * Url.toPath('/pet/{petId}') // '/pet/:petId'
214
- */
215
- static toPath(path) {
216
- return path.replace(/\{([^}]+)\}/g, ":$1");
217
- }
218
- /**
219
- * Rewrites OpenAPI placeholder names while keeping the `{...}` braces, so the generated `url`
220
- * literal aligns with the grouped `path` request option that the runtime client interpolates by
221
- * key.
222
- *
223
- * @example
224
- * Url.toCasedTemplate('/projects/{project_id}', { casing: 'camelcase' }) // '/projects/{projectId}'
225
- */
226
- static toCasedTemplate(path, { casing } = {}) {
227
- return path.replace(/\{([^}]+)\}/g, (_, name) => `{${transformParam(name, casing)}}`);
228
- }
229
- /**
230
- * Converts an OpenAPI/Swagger path to a TypeScript template literal string.
231
- * `prefix` is prepended inside the literal, `replacer` transforms each parameter name,
232
- * and `casing` controls parameter identifier casing.
233
- *
234
- * @example
235
- * Url.toTemplateString('/pet/{petId}') // '`/pet/${petId}`'
236
- *
237
- * @example
238
- * Url.toTemplateString('/pet/{petId}', { prefix: 'https://api' }) // '`https://api/pet/${petId}`'
239
- */
240
- static toTemplateString(path, { prefix, replacer, casing } = {}) {
241
- const result = path.split(/\{([^}]+)\}/).map((part, i) => {
242
- if (i % 2 === 0) return part;
243
- const param = transformParam(part, casing);
244
- return `\${${replacer ? replacer(param) : param}}`;
245
- }).join("");
246
- return `\`${prefix ?? ""}${result}\``;
247
- }
248
- /**
249
- * Converts an OpenAPI/Swagger path to a template literal that reads each parameter off the
250
- * grouped `path` request option, e.g. `/pet/{petId}` becomes `` `/pet/${path.petId}` ``. Parameter
251
- * names are camelCased to match the generated `path` type, and `prefix` is prepended inside the
252
- * literal. Shared by the client and cypress generators that pass a grouped `path` object.
253
- *
254
- * @example
255
- * Url.toGroupedTemplateString('/pet/{petId}') // '`/pet/${path.petId}`'
256
- */
257
- static toGroupedTemplateString(path, { prefix } = {}) {
258
- return Url.toTemplateString(path, {
259
- prefix,
260
- casing: "camelcase",
261
- replacer: (name) => `path.${name}`
262
- });
263
- }
264
- /**
265
- * Returns the path and its extracted params as a structured `URLObject`, or as a stringified
266
- * expression when `stringify` is set.
267
- *
268
- * @example
269
- * Url.toObject('/pet/{petId}')
270
- * // { url: '/pet/:petId', params: { petId: 'petId' } }
271
- */
272
- static toObject(path, { type = "path", replacer, stringify, casing } = {}) {
273
- const object = {
274
- url: type === "path" ? Url.toPath(path) : Url.toTemplateString(path, {
275
- replacer,
276
- casing
277
- }),
278
- params: toParamsObject(path, {
279
- replacer,
280
- casing
281
- })
282
- };
283
- if (stringify) {
284
- if (type === "template") return JSON.stringify(object).replaceAll("'", "").replaceAll(`"`, "");
285
- if (object.params) return `{ url: '${object.url}', params: ${JSON.stringify(object.params).replaceAll("'", "").replaceAll(`"`, "")} }`;
286
- return `{ url: '${object.url}' }`;
287
- }
288
- return object;
289
- }
290
- };
291
- //#endregion
292
8
  //#region ../../internals/shared/src/params.ts
293
- const caseParamsCache = /* @__PURE__ */ new WeakMap();
294
9
  /**
295
- * Applies camelCase to parameter names and returns a new array without mutating the input.
10
+ * Drops parameters that share the same name, keeping the first.
296
11
  *
297
- * Run it before handing parameters to schema builders so output property keys get the right casing
298
- * while `OperationNode.parameters` stays intact for other consumers. When `casing` is unset, the
299
- * original array is returned unchanged. Results are cached per input array.
12
+ * A malformed spec can declare the same parameter name twice within one `in` location. Both would
13
+ * resolve to the same output property, so emitting both would yield an object type with a duplicate
14
+ * member, which TypeScript rejects. This is a defensive guard against that case, not a casing guard:
15
+ * parameter names flow through unchanged, so no two distinct names ever collide here anymore.
300
16
  */
301
- function caseParams(params, casing) {
302
- if (!casing) return params;
303
- const cached = caseParamsCache.get(params);
304
- if (cached) return cached;
305
- const result = params.map((param) => ({
306
- ...param,
307
- name: camelCase(param.name)
308
- }));
309
- caseParamsCache.set(params, result);
310
- return result;
311
- }
312
- /**
313
- * Drops parameters that collapse to the same property identity once camelCased, keeping the first.
314
- *
315
- * Some specs declare the same parameter twice under different casings (for example AWS S3 lists both
316
- * `max-uploads` and `MaxUploads`). Both resolve to one output property, so emitting both would yield
317
- * an object type with a duplicate member, which TypeScript rejects. De-duplicate by the camelCased
318
- * identity so the resulting group is collision-free regardless of the names each caller carries.
319
- */
320
- function dedupeByCasedName(params) {
17
+ function dedupeParams(params) {
321
18
  const seen = /* @__PURE__ */ new Set();
322
19
  return params.filter((param) => {
323
- const key = camelCase(param.name);
324
- if (seen.has(key)) return false;
325
- seen.add(key);
20
+ if (seen.has(param.name)) return false;
21
+ seen.add(param.name);
326
22
  return true;
327
23
  });
328
24
  }
@@ -347,11 +43,30 @@ function operationFileEntry(node, name, extname = ".ts") {
347
43
  path: node.path
348
44
  };
349
45
  }
46
+ /**
47
+ * Resolves a dependency plugin's generated file for `node.operationId`, cached in `cache` (the
48
+ * current node's `ctx.cache`) under the resolver's own plugin name. Several dependents reading the
49
+ * same dependency for the same operation in one pass (a query plugin's several hook generators, the
50
+ * MCP handler, ...) share one computed name and path instead of each calling `resolver.file` again.
51
+ *
52
+ * @example Cache `plugin-ts`'s file for the current operation
53
+ * ```ts
54
+ * const fileTs = resolveDependencyOperationFile({ cache: ctx.cache, node, resolver: tsResolver, root, output })
55
+ * ```
56
+ */
57
+ function resolveDependencyOperationFile(options) {
58
+ const { cache, node, resolver, root, output, group } = options;
59
+ return cache.ensureItem(`${resolver.pluginName}:operationFile`, () => resolver.file({
60
+ ...operationFileEntry(node, node.operationId),
61
+ root,
62
+ output,
63
+ group: group ?? void 0
64
+ }));
65
+ }
350
66
  function getOperationLink(node, link) {
351
67
  if (!link) return null;
352
68
  if (typeof link === "function") return link(node) ?? null;
353
- if (link === "urlPath") return node.path ? `{@link ${Url.toPath(node.path)}}` : null;
354
- return node.path ? `{@link ${node.path.replaceAll("{", ":").replaceAll("}", "")}}` : null;
69
+ return node.path ? `{@link ${Url.toPath(node.path)}}` : null;
355
70
  }
356
71
  /**
357
72
  * Derives the shared `ContentTypeInfo` shape from a list of content types, tracking whether several
@@ -441,14 +156,24 @@ function buildOperationComments(node, options = {}) {
441
156
  if (!splitLines) return filteredComments;
442
157
  return filteredComments.flatMap((text) => text.split(/\r?\n/).map((line) => line.trim())).filter((comment) => Boolean(comment));
443
158
  }
444
- function getOperationParameters(node, options = {}) {
445
- const params = caseParams(node.parameters, options.paramsCasing === "original" ? void 0 : "camelcase");
446
- return {
447
- path: dedupeByCasedName(params.filter((param) => param.in === "path")),
448
- query: dedupeByCasedName(params.filter((param) => param.in === "query")),
449
- header: dedupeByCasedName(params.filter((param) => param.in === "header")),
450
- cookie: dedupeByCasedName(params.filter((param) => param.in === "cookie"))
159
+ const operationParameterGroupsByNode = /* @__PURE__ */ new WeakMap();
160
+ /**
161
+ * Groups an operation's parameters by location (`path`/`query`/`header`/`cookie`), deduping each
162
+ * group by name. Every plugin generator visiting the same `OperationNode` shares one AST instance
163
+ * (see `KubbDriver`), so the result is cached per node to avoid re-filtering and re-deduping the
164
+ * same parameters once per plugin.
165
+ */
166
+ function getOperationParameters(node) {
167
+ const cached = operationParameterGroupsByNode.get(node);
168
+ if (cached) return cached;
169
+ const groups = {
170
+ path: dedupeParams(node.parameters.filter((param) => param.in === "path")),
171
+ query: dedupeParams(node.parameters.filter((param) => param.in === "query")),
172
+ header: dedupeParams(node.parameters.filter((param) => param.in === "header")),
173
+ cookie: dedupeParams(node.parameters.filter((param) => param.in === "cookie"))
451
174
  };
175
+ operationParameterGroupsByNode.set(node, groups);
176
+ return groups;
452
177
  }
453
178
  function getStatusCodeNumber(statusCode) {
454
179
  const code = Number(statusCode);
@@ -482,7 +207,7 @@ function resolveStatusCodeNames(node, resolver) {
482
207
  }
483
208
  const typeNamesByResolver = /* @__PURE__ */ new WeakMap();
484
209
  function resolveOperationTypeNames(node, resolver, options = {}) {
485
- const cacheKey = `${node.operationId}\0${options.paramsCasing ?? ""}\0${options.order ?? ""}\0${options.responseStatusNames ?? ""}\0${options.includeParams === false ? "noparams" : ""}\0${(options.exclude ?? []).join(",")}`;
210
+ const cacheKey = `${node.operationId}\0${options.order ?? ""}\0${options.responseStatusNames ?? ""}\0${options.includeParams === false ? "noparams" : ""}\0${(options.exclude ?? []).join(",")}`;
486
211
  let byResolver = typeNamesByResolver.get(resolver);
487
212
  if (byResolver) {
488
213
  const cached = byResolver.get(cacheKey);
@@ -491,7 +216,7 @@ function resolveOperationTypeNames(node, resolver, options = {}) {
491
216
  byResolver = /* @__PURE__ */ new Map();
492
217
  typeNamesByResolver.set(resolver, byResolver);
493
218
  }
494
- const { path, query, header } = getOperationParameters(node, { paramsCasing: options.paramsCasing });
219
+ const { path, query, header } = getOperationParameters(node);
495
220
  const responseStatusNames = options.responseStatusNames === "error" ? resolveErrorNames(node, resolver) : options.responseStatusNames === false ? [] : resolveStatusCodeNames(node, resolver);
496
221
  const exclude = new Set(options.exclude ?? []);
497
222
  const paramNames = options.includeParams === false ? [] : [
@@ -513,6 +238,60 @@ function resolveOperationTypeNames(node, resolver, options = {}) {
513
238
  return result;
514
239
  }
515
240
  //#endregion
241
+ //#region ../../internals/utils/src/casing.ts
242
+ /**
243
+ * Shared implementation for camelCase and PascalCase conversion.
244
+ * Splits on common word boundaries (spaces, hyphens, underscores, dots, slashes, colons)
245
+ * and capitalizes each word according to `pascal`.
246
+ *
247
+ * When `pascal` is `true` the first word is also capitalized (PascalCase), otherwise only subsequent words are.
248
+ */
249
+ function toCamelOrPascal(text, pascal) {
250
+ return text.trim().replace(/([a-z\d])([A-Z])/g, "$1 $2").replace(/([A-Z]+)([A-Z][a-z])/g, "$1 $2").replace(/(\d)([a-z])/g, "$1 $2").split(/[\s\-_./\\:]+/).filter(Boolean).map((word, i) => {
251
+ if (word.length > 1 && word === word.toUpperCase()) return word;
252
+ return (i === 0 && !pascal ? word.charAt(0).toLowerCase() : word.charAt(0).toUpperCase()) + word.slice(1);
253
+ }).join("").replace(/[^a-zA-Z0-9]/g, "");
254
+ }
255
+ /**
256
+ * Converts `text` to camelCase.
257
+ *
258
+ * @example Word boundaries
259
+ * `camelCase('hello-world') // 'helloWorld'`
260
+ *
261
+ * @example With a prefix
262
+ * `camelCase('tag', { prefix: 'create' }) // 'createTag'`
263
+ */
264
+ function camelCase(text, { prefix = "", suffix = "" } = {}) {
265
+ return toCamelOrPascal(`${prefix} ${text} ${suffix}`, false);
266
+ }
267
+ /**
268
+ * Uppercases only the first character of `text`, leaving the rest untouched.
269
+ * Unlike {@link pascalCase} it never re-splits word boundaries or strips characters.
270
+ *
271
+ * @example
272
+ * `capitalize('getPetById') // 'GetPetById'`
273
+ */
274
+ function capitalize(text) {
275
+ return `${text.charAt(0).toUpperCase()}${text.slice(1)}`;
276
+ }
277
+ //#endregion
278
+ //#region ../../internals/utils/src/strings.ts
279
+ /**
280
+ * Renders a dotted path or string array as an optional-chaining accessor expression rooted at
281
+ * `accessor`. Returns `null` for an empty path.
282
+ *
283
+ * @example
284
+ * ```ts
285
+ * getNestedAccessor('pagination.next.id', 'lastPage')
286
+ * // "lastPage?.['pagination']?.['next']?.['id']"
287
+ * ```
288
+ */
289
+ function getNestedAccessor(param, accessor) {
290
+ const parts = Array.isArray(param) ? param : param.split(".");
291
+ if (parts.length === 0 || parts.length === 1 && parts[0] === "") return null;
292
+ return `${accessor}?.['${`${parts.join("']?.['")}']`}`;
293
+ }
294
+ //#endregion
516
295
  //#region ../../internals/shared/src/group.ts
517
296
  /**
518
297
  * Builds the `group` config a Kubb plugin passes to `ctx.setOptions`, applying the
@@ -632,23 +411,30 @@ function resolveContractClient(options) {
632
411
  * (plugin-fetch or plugin-axios). Returns `null` when no contract plugin is in play (the inline
633
412
  * path). The plugin injects `.kubb/client.ts` at the global output root, the same path consumers
634
413
  * read `RequestConfig` / `ResponseErrorConfig` from.
414
+ *
415
+ * The result is cached in the current node's `cache` (`ctx.cache`) under the client plugin's name,
416
+ * so several dependents reading the same client plugin for one operation in a single pass
417
+ * (react-query's query/mutation/infinite generators, vue-query, swr, the MCP handler, ...) share
418
+ * one computed result instead of each re-deriving the name and path.
635
419
  */
636
420
  function resolveClientOperation(options) {
637
- const { clientPlugin, driver, node, root, output } = options;
421
+ const { clientPlugin, driver, node, root, output, cache } = options;
638
422
  if (!clientPlugin) return null;
639
- const resolver = driver.getResolver(clientPlugin.pluginName);
640
- const plugin = driver.getPlugin(clientPlugin.pluginName);
641
- const file = resolver.file({
642
- ...operationFileEntry(node, node.operationId),
643
- root,
644
- output: plugin?.options?.output ?? output,
645
- group: plugin?.options?.group ?? void 0
423
+ return cache.ensureItem(`${clientPlugin.pluginName}:clientOperation`, () => {
424
+ const resolver = driver.getResolver(clientPlugin.pluginName);
425
+ const plugin = driver.getPlugin(clientPlugin.pluginName);
426
+ const file = resolver.file({
427
+ ...operationFileEntry(node, node.operationId),
428
+ root,
429
+ output: plugin?.options?.output ?? output,
430
+ group: plugin?.options?.group ?? void 0
431
+ });
432
+ return {
433
+ name: resolver.name(node.operationId),
434
+ path: file.path,
435
+ clientPath: path.resolve(root, ".kubb/client.ts")
436
+ };
646
437
  });
647
- return {
648
- name: resolver.name(node.operationId),
649
- path: file.path,
650
- clientPath: path.resolve(root, ".kubb/client.ts")
651
- };
652
438
  }
653
439
  //#endregion
654
440
  //#region ../../internals/tanstack-query/src/utils.ts
@@ -719,14 +505,16 @@ function buildGroupedRequestParam(node, options) {
719
505
  */
720
506
  function buildQueryOptionsParams(node, options) {
721
507
  const { resolver, memberTypeWrapper } = options;
722
- return createFunctionParameters({ params: [buildGroupedRequestParam(node, {
508
+ const groupedParam = buildGroupedRequestParam(node, {
723
509
  resolver,
724
510
  memberTypeWrapper
725
- }), createFunctionParameter({
511
+ });
512
+ const configParam = createFunctionParameter({
726
513
  name: "config",
727
514
  type: `Partial<Omit<RequestConfig, 'path' | 'query' | 'body' | 'headers' | 'url'>>`,
728
515
  default: "{}"
729
- })].filter((param) => param !== null) });
516
+ });
517
+ return createFunctionParameters({ params: [groupedParam, configParam].filter((param) => param !== null) });
730
518
  }
731
519
  /**
732
520
  * Builds the call to a client `<op>` function inside a query/mutation hook body. The function takes
@@ -780,7 +568,7 @@ function resolveFallbackPageParamType(initialPageParam) {
780
568
  * so callers can reuse it when rewriting the paginated request.
781
569
  */
782
570
  function resolvePageParamType(node, { resolver, initialPageParam, queryParam }) {
783
- const firstQueryParam = getOperationParameters(node, { paramsCasing: "original" }).query[0];
571
+ const firstQueryParam = getOperationParameters(node).query[0];
784
572
  const groupName = firstQueryParam ? resolver.param.query(node, firstQueryParam) : null;
785
573
  const queryParamsTypeName = groupName !== (firstQueryParam ? resolver.param.name(node, firstQueryParam) : null) ? groupName : null;
786
574
  const queryParamType = queryParam && queryParamsTypeName ? `${queryParamsTypeName}['${queryParam}']` : null;
@@ -824,6 +612,19 @@ function resolveMutationConfig(mutation, options) {
824
612
  };
825
613
  }
826
614
  /**
615
+ * Classifies an operation as a query or a mutation from the resolved `query` / `mutation` method lists.
616
+ * `query: false` still marks the operation as a query so the query-family generators keep matching it,
617
+ * and a method already claimed by `query` never counts as a mutation.
618
+ */
619
+ function classifyOperation(node, { query, mutation }) {
620
+ const isQuery = query === false || !!query && query.methods.some((method) => node.method.toLowerCase() === method.toLowerCase());
621
+ const queryMethods = new Set(query ? query.methods : []);
622
+ return {
623
+ isQuery,
624
+ isMutation: mutation !== false && !isQuery && (mutation ? mutation.methods : []).some((method) => !queryMethods.has(method) && node.method.toLowerCase() === method.toLowerCase())
625
+ };
626
+ }
627
+ /**
827
628
  * Applies the shared infinite-query defaults during plugin setup: a falsy value disables infinite
828
629
  * queries, and an object merges over `queryParam: 'id'` and `initialPageParam: 0` with the cursor
829
630
  * paths cleared.
@@ -1193,19 +994,6 @@ function buildResolvedRequestParams(node) {
1193
994
  if (names.length === 0) return null;
1194
995
  return `{ ${names.map((name) => `${name}: typeof ${name} === 'function' ? ${name}() : ${name}`).join(", ")} }`;
1195
996
  }
1196
- /**
1197
- * Classifies an operation as a query or a mutation from the resolved `query` / `mutation` method lists.
1198
- * `query: false` still marks the operation as a query so the query-family generators keep matching it,
1199
- * and a method already claimed by `query` never counts as a mutation.
1200
- */
1201
- function classifyOperation(node, { query, mutation }) {
1202
- const isQuery = query === false || !!query && query.methods.some((method) => node.method.toLowerCase() === method.toLowerCase());
1203
- const queryMethods = new Set(query ? query.methods : []);
1204
- return {
1205
- isQuery,
1206
- isMutation: mutation !== false && !isQuery && (mutation ? mutation.methods : []).some((method) => !queryMethods.has(method) && node.method.toLowerCase() === method.toLowerCase())
1207
- };
1208
- }
1209
997
  //#endregion
1210
998
  //#region src/generators/hookOptionsGenerator.tsx
1211
999
  /**
@@ -1281,7 +1069,7 @@ const hookOptionsGenerator = defineGenerator({
1281
1069
  }
1282
1070
  if (isInfiniteOp) {
1283
1071
  const normalizeKey = (key) => key.replace(/\?$/, "");
1284
- const queryParamKeys = getOperationParameters(node, { paramsCasing: "original" }).query.map((p) => p.name);
1072
+ const queryParamKeys = getOperationParameters(node).query.map((p) => p.name);
1285
1073
  if (nodeInfiniteOptions.queryParam ? queryParamKeys.some((k) => normalizeKey(k) === nodeInfiniteOptions.queryParam) : false) {
1286
1074
  const infiniteOptionsName = resolver.infiniteQuery.optionsName(node);
1287
1075
  const infiniteHookName = resolver.infiniteQuery.name(node);
@@ -1380,10 +1168,11 @@ function buildInfiniteQueryParamsNode(node, options) {
1380
1168
  }`,
1381
1169
  default: "{}"
1382
1170
  });
1383
- return createFunctionParameters({ params: [buildGroupedRequestParam(node, {
1171
+ const groupedParam = buildGroupedRequestParam(node, {
1384
1172
  resolver,
1385
1173
  memberTypeWrapper: maybeValueOrGetter
1386
- }), optionsParam].filter((param) => param !== null) });
1174
+ });
1175
+ return createFunctionParameters({ params: [groupedParam, optionsParam].filter((param) => param !== null) });
1387
1176
  }
1388
1177
  function InfiniteQuery({ name, queryKeyTypeName, queryOptionsName, queryKeyName, node, tsResolver, initialPageParam, queryParam, customOptions, suspense }) {
1389
1178
  const { TData: responseType, TError: errorType } = buildResponseTypes(node, tsResolver);
@@ -1496,15 +1285,16 @@ function resolveMutationRequestType(node, resolver) {
1496
1285
  function buildMutationParamsNode(node, options) {
1497
1286
  const { resolver } = options;
1498
1287
  const { TData, TError } = buildResponseTypes(node, resolver);
1288
+ const generics = [
1289
+ TData,
1290
+ TError,
1291
+ resolveMutationRequestType(node, resolver),
1292
+ "TContext"
1293
+ ].join(", ");
1499
1294
  return createFunctionParameters({ params: [createFunctionParameter({
1500
1295
  name: "options",
1501
1296
  type: `{
1502
- mutation?: UseMutationOptions<${[
1503
- TData,
1504
- TError,
1505
- resolveMutationRequestType(node, resolver),
1506
- "TContext"
1507
- ].join(", ")}> & { client?: QueryClient },
1297
+ mutation?: UseMutationOptions<${generics}> & { client?: QueryClient },
1508
1298
  client?: ${buildRequestConfigType(node)},
1509
1299
  }`,
1510
1300
  default: "{}"
@@ -1562,10 +1352,11 @@ function buildQueryParamsNode(node, options) {
1562
1352
  }`,
1563
1353
  default: "{}"
1564
1354
  });
1565
- return createFunctionParameters({ params: [buildGroupedRequestParam(node, {
1355
+ const groupedParam = buildGroupedRequestParam(node, {
1566
1356
  resolver,
1567
1357
  memberTypeWrapper: maybeValueOrGetter
1568
- }), optionsParam].filter((param) => param !== null) });
1358
+ });
1359
+ return createFunctionParameters({ params: [groupedParam, optionsParam].filter((param) => param !== null) });
1569
1360
  }
1570
1361
  function Query({ name, queryKeyTypeName, queryOptionsName, queryKeyName, node, tsResolver, customOptions, suspense }) {
1571
1362
  const { TData, TError } = buildResponseTypes(node, tsResolver);
@@ -1630,14 +1421,11 @@ function Query({ name, queryKeyTypeName, queryOptionsName, queryKeyName, node, t
1630
1421
  //#region src/components/QueryOptions.tsx
1631
1422
  const declarationPrinter = functionPrinter({ mode: "declaration" });
1632
1423
  const callPrinter = functionPrinter({ mode: "call" });
1633
- function getQueryOptionsParams(node, options) {
1634
- return buildQueryOptionsParams(node, options);
1635
- }
1636
1424
  function QueryOptions({ name, clientName, node, tsResolver, queryKeyName }) {
1637
1425
  const { TData, TError } = buildResponseTypes(node, tsResolver);
1638
1426
  const queryKeyParamsNode = buildQueryKeyParams(node, { resolver: tsResolver });
1639
1427
  const queryKeyParamsCall = callPrinter.print(queryKeyParamsNode) ?? "";
1640
- const paramsNode = getQueryOptionsParams(node, { resolver: tsResolver });
1428
+ const paramsNode = buildQueryOptionsParams(node, { resolver: tsResolver });
1641
1429
  const paramsSignature = declarationPrinter.print(paramsNode) ?? "";
1642
1430
  const queryFnBody = `const { data } = await ${buildClientCall(node, {
1643
1431
  clientName,
@@ -1668,36 +1456,44 @@ function QueryOptions({ name, clientName, node, tsResolver, queryKeyName }) {
1668
1456
  //#region src/generators/infiniteQueryGenerator.tsx
1669
1457
  /**
1670
1458
  * Built-in generator for `useInfiniteQuery` hooks. Enabled when
1671
- * `pluginReactQuery({ infinite: { ... } })`. Emits one `useFooInfiniteQuery`
1672
- * hook per query operation, wiring the configured `nextParam` /
1673
- * `previousParam` paths into TanStack Query's cursor-based pagination.
1459
+ * `pluginReactQuery({ infinite: { ... }, hooks: true })`. Emits one
1460
+ * `useFooInfiniteQuery` hook per query operation, wiring the configured
1461
+ * `nextParam` / `previousParam` paths into TanStack Query's cursor-based
1462
+ * pagination.
1674
1463
  */
1675
1464
  const infiniteQueryGenerator = defineGenerator({
1676
1465
  name: "react-infinite-query",
1677
1466
  renderer: jsxRenderer,
1467
+ match(node, ctx) {
1468
+ const operationNode = node;
1469
+ if (!ast.isHttpOperationNode(operationNode)) return false;
1470
+ const { query, mutation, infinite, hooks } = ctx.options;
1471
+ const { isQuery, isMutation } = classifyOperation(operationNode, {
1472
+ query,
1473
+ mutation
1474
+ });
1475
+ const infiniteOptions = infinite && typeof infinite === "object" ? infinite : null;
1476
+ if (!isQuery || isMutation || !infiniteOptions || !hooks) return false;
1477
+ const queryParamKeys = getOperationParameters(operationNode).query.map((p) => p.name);
1478
+ return infiniteOptions.queryParam ? queryParamKeys.includes(infiniteOptions.queryParam) || queryParamKeys.includes(`${infiniteOptions.queryParam}?`) : false;
1479
+ },
1678
1480
  operation(node, ctx) {
1679
1481
  if (!ast.isHttpOperationNode(node)) return null;
1680
1482
  const { config, driver, resolver, root } = ctx;
1681
- const { output, query, mutation, infinite, client, group, customOptions, hooks } = ctx.options;
1483
+ const { output, query, infinite, client, group, customOptions } = ctx.options;
1682
1484
  const pluginTs = driver.getPlugin(pluginTsName);
1683
1485
  if (!pluginTs) return null;
1684
1486
  const tsResolver = driver.getResolver(pluginTsName);
1685
- const { isQuery, isMutation } = classifyOperation(node, {
1686
- query,
1687
- mutation
1688
- });
1689
1487
  const infiniteOptions = infinite && typeof infinite === "object" ? infinite : null;
1690
- if (!isQuery || isMutation || !infiniteOptions) return null;
1691
- const normalizeKey = (key) => key.replace(/\?$/, "");
1692
- const queryParamKeys = getOperationParameters(node, { paramsCasing: "original" }).query.map((p) => p.name);
1693
- if (!(infiniteOptions.queryParam ? queryParamKeys.some((k) => normalizeKey(k) === infiniteOptions.queryParam) : false)) return null;
1488
+ if (!infiniteOptions) return null;
1694
1489
  const importPath = query ? query.importPath : "@tanstack/react-query";
1695
1490
  const contractOp = resolveClientOperation({
1696
1491
  clientPlugin: { pluginName: client.pluginName },
1697
1492
  driver,
1698
1493
  node,
1699
1494
  root,
1700
- output
1495
+ output,
1496
+ cache: ctx.cache
1701
1497
  });
1702
1498
  if (!contractOp) return null;
1703
1499
  const queryName = resolver.infiniteQuery.name(node);
@@ -1711,15 +1507,20 @@ const infiniteQueryGenerator = defineGenerator({
1711
1507
  output,
1712
1508
  group: group ?? void 0
1713
1509
  }),
1714
- fileTs: tsResolver.file({
1715
- ...operationFileEntry(node, node.operationId),
1510
+ fileTs: resolveDependencyOperationFile({
1511
+ cache: ctx.cache,
1512
+ node,
1513
+ resolver: tsResolver,
1716
1514
  root,
1717
1515
  output: pluginTs.options?.output ?? output,
1718
- group: pluginTs.options?.group ?? void 0
1516
+ group: pluginTs.options?.group
1719
1517
  })
1720
1518
  };
1721
- const rawQueryParams = getOperationParameters(node, { paramsCasing: "original" }).query;
1722
- const queryParamsTypeName = rawQueryParams.length > 0 && tsResolver.param.query(node, rawQueryParams[0]) !== tsResolver.param.name(node, rawQueryParams[0]) ? tsResolver.param.query(node, rawQueryParams[0]) : null;
1519
+ const { queryParamsTypeName } = resolvePageParamType(node, {
1520
+ resolver: tsResolver,
1521
+ initialPageParam: infiniteOptions.initialPageParam,
1522
+ queryParam: infiniteOptions.queryParam
1523
+ });
1723
1524
  const importedTypeNames = [
1724
1525
  tsResolver.response.options(node),
1725
1526
  queryParamsTypeName,
@@ -1800,33 +1601,31 @@ const infiniteQueryGenerator = defineGenerator({
1800
1601
  initialPageParam: infiniteOptions.initialPageParam,
1801
1602
  queryParam: infiniteOptions.queryParam
1802
1603
  }),
1803
- hooks && /* @__PURE__ */ jsxs(Fragment, { children: [
1804
- /* @__PURE__ */ jsx(File.Import, {
1805
- name: ["useInfiniteQuery"],
1806
- path: importPath
1807
- }),
1808
- /* @__PURE__ */ jsx(File.Import, {
1809
- name: [
1810
- "QueryKey",
1811
- "QueryClient",
1812
- "InfiniteQueryObserverOptions",
1813
- "UseInfiniteQueryResult"
1814
- ],
1815
- path: importPath,
1816
- isTypeOnly: true
1817
- }),
1818
- /* @__PURE__ */ jsx(InfiniteQuery, {
1819
- name: queryName,
1820
- queryOptionsName,
1821
- queryKeyName,
1822
- queryKeyTypeName,
1823
- node,
1824
- tsResolver,
1825
- initialPageParam: infiniteOptions.initialPageParam,
1826
- queryParam: infiniteOptions.queryParam,
1827
- customOptions
1828
- })
1829
- ] })
1604
+ /* @__PURE__ */ jsx(File.Import, {
1605
+ name: ["useInfiniteQuery"],
1606
+ path: importPath
1607
+ }),
1608
+ /* @__PURE__ */ jsx(File.Import, {
1609
+ name: [
1610
+ "QueryKey",
1611
+ "QueryClient",
1612
+ "InfiniteQueryObserverOptions",
1613
+ "UseInfiniteQueryResult"
1614
+ ],
1615
+ path: importPath,
1616
+ isTypeOnly: true
1617
+ }),
1618
+ /* @__PURE__ */ jsx(InfiniteQuery, {
1619
+ name: queryName,
1620
+ queryOptionsName,
1621
+ queryKeyName,
1622
+ queryKeyTypeName,
1623
+ node,
1624
+ tsResolver,
1625
+ initialPageParam: infiniteOptions.initialPageParam,
1626
+ queryParam: infiniteOptions.queryParam,
1627
+ customOptions
1628
+ })
1830
1629
  ]
1831
1630
  });
1832
1631
  }
@@ -1841,25 +1640,30 @@ const infiniteQueryGenerator = defineGenerator({
1841
1640
  const mutationGenerator = defineGenerator({
1842
1641
  name: "react-query-mutation",
1843
1642
  renderer: jsxRenderer,
1643
+ match(node, ctx) {
1644
+ const operationNode = node;
1645
+ if (!ast.isHttpOperationNode(operationNode)) return false;
1646
+ const { query, mutation } = ctx.options;
1647
+ return classifyOperation(operationNode, {
1648
+ query,
1649
+ mutation
1650
+ }).isMutation;
1651
+ },
1844
1652
  operation(node, ctx) {
1845
1653
  if (!ast.isHttpOperationNode(node)) return null;
1846
1654
  const { config, driver, resolver, root } = ctx;
1847
- const { output, query, mutation, client, group, customOptions, hooks } = ctx.options;
1655
+ const { output, mutation, client, group, customOptions, hooks } = ctx.options;
1848
1656
  const pluginTs = driver.getPlugin(pluginTsName);
1849
1657
  if (!pluginTs) return null;
1850
1658
  const tsResolver = driver.getResolver(pluginTsName);
1851
- const { isMutation } = classifyOperation(node, {
1852
- query,
1853
- mutation
1854
- });
1855
- if (!isMutation) return null;
1856
1659
  const importPath = mutation ? mutation.importPath : "@tanstack/react-query";
1857
1660
  const contractOp = resolveClientOperation({
1858
1661
  clientPlugin: { pluginName: client.pluginName },
1859
1662
  driver,
1860
1663
  node,
1861
1664
  root,
1862
- output
1665
+ output,
1666
+ cache: ctx.cache
1863
1667
  });
1864
1668
  if (!contractOp) return null;
1865
1669
  const mutationHookName = resolver.mutation.name(node);
@@ -1873,11 +1677,13 @@ const mutationGenerator = defineGenerator({
1873
1677
  output,
1874
1678
  group: group ?? void 0
1875
1679
  }),
1876
- fileTs: tsResolver.file({
1877
- ...operationFileEntry(node, node.operationId),
1680
+ fileTs: resolveDependencyOperationFile({
1681
+ cache: ctx.cache,
1682
+ node,
1683
+ resolver: tsResolver,
1878
1684
  root,
1879
1685
  output: pluginTs.options?.output ?? output,
1880
- group: pluginTs.options?.group ?? void 0
1686
+ group: pluginTs.options?.group
1881
1687
  })
1882
1688
  };
1883
1689
  const importedTypeNames = [tsResolver.response.options(node), ...resolveOperationTypeNames(node, tsResolver, {
@@ -1981,25 +1787,31 @@ const mutationGenerator = defineGenerator({
1981
1787
  const queryGenerator = defineGenerator({
1982
1788
  name: "react-query",
1983
1789
  renderer: jsxRenderer,
1790
+ match(node, ctx) {
1791
+ const operationNode = node;
1792
+ if (!ast.isHttpOperationNode(operationNode)) return false;
1793
+ const { query, mutation } = ctx.options;
1794
+ const { isQuery, isMutation } = classifyOperation(operationNode, {
1795
+ query,
1796
+ mutation
1797
+ });
1798
+ return isQuery && !isMutation;
1799
+ },
1984
1800
  operation(node, ctx) {
1985
1801
  if (!ast.isHttpOperationNode(node)) return null;
1986
1802
  const { config, driver, resolver, root } = ctx;
1987
- const { output, query, mutation, client, group, customOptions, hooks } = ctx.options;
1803
+ const { output, query, client, group, customOptions, hooks } = ctx.options;
1988
1804
  const pluginTs = driver.getPlugin(pluginTsName);
1989
1805
  if (!pluginTs) return null;
1990
1806
  const tsResolver = driver.getResolver(pluginTsName);
1991
- const { isQuery, isMutation } = classifyOperation(node, {
1992
- query,
1993
- mutation
1994
- });
1995
- if (!isQuery || isMutation) return null;
1996
1807
  const importPath = query ? query.importPath : "@tanstack/react-query";
1997
1808
  const contractOp = resolveClientOperation({
1998
1809
  clientPlugin: { pluginName: client.pluginName },
1999
1810
  driver,
2000
1811
  node,
2001
1812
  root,
2002
- output
1813
+ output,
1814
+ cache: ctx.cache
2003
1815
  });
2004
1816
  if (!contractOp) return null;
2005
1817
  const queryName = resolver.query.name(node);
@@ -2013,11 +1825,13 @@ const queryGenerator = defineGenerator({
2013
1825
  output,
2014
1826
  group: group ?? void 0
2015
1827
  }),
2016
- fileTs: tsResolver.file({
2017
- ...operationFileEntry(node, node.operationId),
1828
+ fileTs: resolveDependencyOperationFile({
1829
+ cache: ctx.cache,
1830
+ node,
1831
+ resolver: tsResolver,
2018
1832
  root,
2019
1833
  output: pluginTs.options?.output ?? output,
2020
- group: pluginTs.options?.group ?? void 0
1834
+ group: pluginTs.options?.group
2021
1835
  })
2022
1836
  };
2023
1837
  const importedTypeNames = [tsResolver.response.options(node), ...resolveOperationTypeNames(node, tsResolver, {
@@ -2118,38 +1932,44 @@ const queryGenerator = defineGenerator({
2118
1932
  //#endregion
2119
1933
  //#region src/generators/suspenseInfiniteQueryGenerator.tsx
2120
1934
  /**
2121
- * Built-in generator for `useSuspenseInfiniteQuery` hooks. Enabled when both
2122
- * `suspense` and `infinite` are configured. Combines suspense semantics with
2123
- * cursor-based pagination. Handlers throw promises while loading and pull
2124
- * additional pages on demand.
1935
+ * Built-in generator for `useSuspenseInfiniteQuery` hooks. Enabled when
1936
+ * `suspense`, `infinite`, and `hooks` are all configured. Combines suspense
1937
+ * semantics with cursor-based pagination. Handlers throw promises while
1938
+ * loading and pull additional pages on demand.
2125
1939
  */
2126
1940
  const suspenseInfiniteQueryGenerator = defineGenerator({
2127
1941
  name: "react-suspense-infinite-query",
2128
1942
  renderer: jsxRenderer,
1943
+ match(node, ctx) {
1944
+ const operationNode = node;
1945
+ if (!ast.isHttpOperationNode(operationNode)) return false;
1946
+ const { query, mutation, infinite, suspense, hooks } = ctx.options;
1947
+ const { isQuery, isMutation } = classifyOperation(operationNode, {
1948
+ query,
1949
+ mutation
1950
+ });
1951
+ const infiniteOptions = infinite && typeof infinite === "object" ? infinite : null;
1952
+ if (!isQuery || isMutation || !suspense || !infiniteOptions || !hooks) return false;
1953
+ const queryParamKeys = getOperationParameters(operationNode).query.map((p) => p.name);
1954
+ return infiniteOptions.queryParam ? queryParamKeys.includes(infiniteOptions.queryParam) || queryParamKeys.includes(`${infiniteOptions.queryParam}?`) : false;
1955
+ },
2129
1956
  operation(node, ctx) {
2130
1957
  if (!ast.isHttpOperationNode(node)) return null;
2131
1958
  const { config, driver, resolver, root } = ctx;
2132
- const { output, query, mutation, infinite, suspense, client, group, customOptions, hooks } = ctx.options;
1959
+ const { output, query, infinite, client, group, customOptions } = ctx.options;
2133
1960
  const pluginTs = driver.getPlugin(pluginTsName);
2134
1961
  if (!pluginTs) return null;
2135
1962
  const tsResolver = driver.getResolver(pluginTsName);
2136
- const { isQuery, isMutation } = classifyOperation(node, {
2137
- query,
2138
- mutation
2139
- });
2140
- const isSuspense = !!suspense;
2141
1963
  const infiniteOptions = infinite && typeof infinite === "object" ? infinite : null;
2142
- if (!isQuery || isMutation || !isSuspense || !infiniteOptions) return null;
2143
- const normalizeKey = (key) => key.replace(/\?$/, "");
2144
- const queryParamKeys = getOperationParameters(node, { paramsCasing: "original" }).query.map((p) => p.name);
2145
- if (!(infiniteOptions.queryParam ? queryParamKeys.some((k) => normalizeKey(k) === infiniteOptions.queryParam) : false)) return null;
1964
+ if (!infiniteOptions) return null;
2146
1965
  const importPath = query ? query.importPath : "@tanstack/react-query";
2147
1966
  const contractOp = resolveClientOperation({
2148
1967
  clientPlugin: { pluginName: client.pluginName },
2149
1968
  driver,
2150
1969
  node,
2151
1970
  root,
2152
- output
1971
+ output,
1972
+ cache: ctx.cache
2153
1973
  });
2154
1974
  if (!contractOp) return null;
2155
1975
  const queryName = resolver.suspenseInfiniteQuery.name(node);
@@ -2163,14 +1983,16 @@ const suspenseInfiniteQueryGenerator = defineGenerator({
2163
1983
  output,
2164
1984
  group: group ?? void 0
2165
1985
  }),
2166
- fileTs: tsResolver.file({
2167
- ...operationFileEntry(node, node.operationId),
1986
+ fileTs: resolveDependencyOperationFile({
1987
+ cache: ctx.cache,
1988
+ node,
1989
+ resolver: tsResolver,
2168
1990
  root,
2169
1991
  output: pluginTs.options?.output ?? output,
2170
- group: pluginTs.options?.group ?? void 0
1992
+ group: pluginTs.options?.group
2171
1993
  })
2172
1994
  };
2173
- const rawQueryParams = getOperationParameters(node, { paramsCasing: "original" }).query;
1995
+ const rawQueryParams = getOperationParameters(node).query;
2174
1996
  const queryParamsTypeName = rawQueryParams.length > 0 && tsResolver.param.query(node, rawQueryParams[0]) !== tsResolver.param.name(node, rawQueryParams[0]) ? tsResolver.param.query(node, rawQueryParams[0]) : null;
2175
1997
  const importedTypeNames = [
2176
1998
  tsResolver.response.options(node),
@@ -2251,34 +2073,32 @@ const suspenseInfiniteQueryGenerator = defineGenerator({
2251
2073
  initialPageParam: infiniteOptions.initialPageParam,
2252
2074
  queryParam: infiniteOptions.queryParam
2253
2075
  }),
2254
- hooks && /* @__PURE__ */ jsxs(Fragment, { children: [
2255
- /* @__PURE__ */ jsx(File.Import, {
2256
- name: ["useSuspenseInfiniteQuery"],
2257
- path: importPath
2258
- }),
2259
- /* @__PURE__ */ jsx(File.Import, {
2260
- name: [
2261
- "QueryKey",
2262
- "QueryClient",
2263
- "UseSuspenseInfiniteQueryOptions",
2264
- "UseSuspenseInfiniteQueryResult"
2265
- ],
2266
- path: importPath,
2267
- isTypeOnly: true
2268
- }),
2269
- /* @__PURE__ */ jsx(InfiniteQuery, {
2270
- name: queryName,
2271
- queryOptionsName,
2272
- queryKeyName,
2273
- queryKeyTypeName,
2274
- node,
2275
- tsResolver,
2276
- initialPageParam: infiniteOptions.initialPageParam,
2277
- queryParam: infiniteOptions.queryParam,
2278
- customOptions,
2279
- suspense: true
2280
- })
2281
- ] })
2076
+ /* @__PURE__ */ jsx(File.Import, {
2077
+ name: ["useSuspenseInfiniteQuery"],
2078
+ path: importPath
2079
+ }),
2080
+ /* @__PURE__ */ jsx(File.Import, {
2081
+ name: [
2082
+ "QueryKey",
2083
+ "QueryClient",
2084
+ "UseSuspenseInfiniteQueryOptions",
2085
+ "UseSuspenseInfiniteQueryResult"
2086
+ ],
2087
+ path: importPath,
2088
+ isTypeOnly: true
2089
+ }),
2090
+ /* @__PURE__ */ jsx(InfiniteQuery, {
2091
+ name: queryName,
2092
+ queryOptionsName,
2093
+ queryKeyName,
2094
+ queryKeyTypeName,
2095
+ node,
2096
+ tsResolver,
2097
+ initialPageParam: infiniteOptions.initialPageParam,
2098
+ queryParam: infiniteOptions.queryParam,
2099
+ customOptions,
2100
+ suspense: true
2101
+ })
2282
2102
  ]
2283
2103
  });
2284
2104
  }
@@ -2287,32 +2107,39 @@ const suspenseInfiniteQueryGenerator = defineGenerator({
2287
2107
  //#region src/generators/suspenseQueryGenerator.tsx
2288
2108
  /**
2289
2109
  * Built-in generator for `useSuspenseQuery` hooks. Enabled when
2290
- * `pluginReactQuery({ suspense: {} })`. Emits one `useFooSuspenseQuery` hook
2291
- * per query operation. Suspense queries throw promises while loading and
2292
- * require a `<Suspense>` boundary in the React tree. TanStack Query v5+ only.
2110
+ * `pluginReactQuery({ suspense: {}, hooks: true })`. Emits one
2111
+ * `useFooSuspenseQuery` hook per query operation. Suspense queries throw
2112
+ * promises while loading and require a `<Suspense>` boundary in the React
2113
+ * tree. TanStack Query v5+ only.
2293
2114
  */
2294
2115
  const suspenseQueryGenerator = defineGenerator({
2295
2116
  name: "react-suspense-query",
2296
2117
  renderer: jsxRenderer,
2118
+ match(node, ctx) {
2119
+ const operationNode = node;
2120
+ if (!ast.isHttpOperationNode(operationNode)) return false;
2121
+ const { query, mutation, suspense, hooks } = ctx.options;
2122
+ const { isQuery, isMutation } = classifyOperation(operationNode, {
2123
+ query,
2124
+ mutation
2125
+ });
2126
+ return isQuery && !isMutation && !!suspense && hooks;
2127
+ },
2297
2128
  operation(node, ctx) {
2298
2129
  if (!ast.isHttpOperationNode(node)) return null;
2299
2130
  const { config, driver, resolver, root } = ctx;
2300
- const { output, query, mutation, suspense, client, group, customOptions, hooks } = ctx.options;
2131
+ const { output, query, client, group, customOptions } = ctx.options;
2301
2132
  const pluginTs = driver.getPlugin(pluginTsName);
2302
2133
  if (!pluginTs) return null;
2303
2134
  const tsResolver = driver.getResolver(pluginTsName);
2304
- const { isQuery, isMutation } = classifyOperation(node, {
2305
- query,
2306
- mutation
2307
- });
2308
- if (!isQuery || isMutation || !!!suspense) return null;
2309
2135
  const importPath = query ? query.importPath : "@tanstack/react-query";
2310
2136
  const contractOp = resolveClientOperation({
2311
2137
  clientPlugin: { pluginName: client.pluginName },
2312
2138
  driver,
2313
2139
  node,
2314
2140
  root,
2315
- output
2141
+ output,
2142
+ cache: ctx.cache
2316
2143
  });
2317
2144
  if (!contractOp) return null;
2318
2145
  const queryName = resolver.suspenseQuery.name(node);
@@ -2326,11 +2153,13 @@ const suspenseQueryGenerator = defineGenerator({
2326
2153
  output,
2327
2154
  group: group ?? void 0
2328
2155
  }),
2329
- fileTs: tsResolver.file({
2330
- ...operationFileEntry(node, node.operationId),
2156
+ fileTs: resolveDependencyOperationFile({
2157
+ cache: ctx.cache,
2158
+ node,
2159
+ resolver: tsResolver,
2331
2160
  root,
2332
2161
  output: pluginTs.options?.output ?? output,
2333
- group: pluginTs.options?.group ?? void 0
2162
+ group: pluginTs.options?.group
2334
2163
  })
2335
2164
  };
2336
2165
  const importedTypeNames = [tsResolver.response.options(node), ...resolveOperationTypeNames(node, tsResolver, {
@@ -2399,32 +2228,30 @@ const suspenseQueryGenerator = defineGenerator({
2399
2228
  node,
2400
2229
  tsResolver
2401
2230
  }),
2402
- suspense && hooks && /* @__PURE__ */ jsxs(Fragment, { children: [
2403
- /* @__PURE__ */ jsx(File.Import, {
2404
- name: ["useSuspenseQuery"],
2405
- path: importPath
2406
- }),
2407
- /* @__PURE__ */ jsx(File.Import, {
2408
- name: [
2409
- "QueryKey",
2410
- "QueryClient",
2411
- "UseSuspenseQueryOptions",
2412
- "UseSuspenseQueryResult"
2413
- ],
2414
- path: importPath,
2415
- isTypeOnly: true
2416
- }),
2417
- /* @__PURE__ */ jsx(Query, {
2418
- name: queryName,
2419
- queryOptionsName,
2420
- queryKeyName,
2421
- queryKeyTypeName,
2422
- node,
2423
- tsResolver,
2424
- customOptions,
2425
- suspense: true
2426
- })
2427
- ] })
2231
+ /* @__PURE__ */ jsx(File.Import, {
2232
+ name: ["useSuspenseQuery"],
2233
+ path: importPath
2234
+ }),
2235
+ /* @__PURE__ */ jsx(File.Import, {
2236
+ name: [
2237
+ "QueryKey",
2238
+ "QueryClient",
2239
+ "UseSuspenseQueryOptions",
2240
+ "UseSuspenseQueryResult"
2241
+ ],
2242
+ path: importPath,
2243
+ isTypeOnly: true
2244
+ }),
2245
+ /* @__PURE__ */ jsx(Query, {
2246
+ name: queryName,
2247
+ queryOptionsName,
2248
+ queryKeyName,
2249
+ queryKeyTypeName,
2250
+ node,
2251
+ tsResolver,
2252
+ customOptions,
2253
+ suspense: true
2254
+ })
2428
2255
  ]
2429
2256
  });
2430
2257
  }
@@ -2510,7 +2337,7 @@ const pluginReactQuery = definePlugin((options) => {
2510
2337
  const { output = {
2511
2338
  path: "hooks",
2512
2339
  barrel: { type: "named" }
2513
- }, group, exclude = [], include, override = [], suspense = {}, infinite = false, mutation = {}, query = {}, mutationKey = mutationKeyTransformer, queryKey = queryKeyTransformer, customOptions, hooks = false, client, resolver: userResolver, macros: userMacros } = options;
2340
+ }, group, exclude = [], include, override = [], suspense = false, infinite = false, mutation = {}, query = {}, mutationKey = mutationKeyTransformer, queryKey = queryKeyTransformer, customOptions, hooks = false, client, resolver: userResolver, macros: userMacros } = options;
2514
2341
  const selectedGenerators = [
2515
2342
  queryGenerator,
2516
2343
  suspenseQueryGenerator,