@kubb/plugin-vue-query 5.0.0-beta.99 → 5.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js CHANGED
@@ -1,249 +1,9 @@
1
1
  import { t as __name } from "./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
- //#region ../../internals/utils/src/casing.ts
8
- /**
9
- * Shared implementation for camelCase and PascalCase conversion.
10
- * Splits on common word boundaries (spaces, hyphens, underscores, dots, slashes, colons)
11
- * and capitalizes each word according to `pascal`.
12
- *
13
- * When `pascal` is `true` the first word is also capitalized (PascalCase), otherwise only subsequent words are.
14
- */
15
- function toCamelOrPascal(text, pascal) {
16
- 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) => {
17
- if (word.length > 1 && word === word.toUpperCase()) return word;
18
- return (i === 0 && !pascal ? word.charAt(0).toLowerCase() : word.charAt(0).toUpperCase()) + word.slice(1);
19
- }).join("").replace(/[^a-zA-Z0-9]/g, "");
20
- }
21
- /**
22
- * Converts `text` to camelCase.
23
- *
24
- * @example Word boundaries
25
- * `camelCase('hello-world') // 'helloWorld'`
26
- *
27
- * @example With a prefix
28
- * `camelCase('tag', { prefix: 'create' }) // 'createTag'`
29
- */
30
- function camelCase(text, { prefix = "", suffix = "" } = {}) {
31
- return toCamelOrPascal(`${prefix} ${text} ${suffix}`, false);
32
- }
33
- /**
34
- * Uppercases only the first character of `text`, leaving the rest untouched.
35
- * Unlike {@link pascalCase} it never re-splits word boundaries or strips characters.
36
- *
37
- * @example
38
- * `capitalize('getPetById') // 'GetPetById'`
39
- */
40
- function capitalize(text) {
41
- return `${text.charAt(0).toUpperCase()}${text.slice(1)}`;
42
- }
43
- //#endregion
44
- //#region ../../internals/utils/src/reserved.ts
45
- /**
46
- * JavaScript and Java reserved words.
47
- * @link https://github.com/jonschlinkert/reserved/blob/master/index.js
48
- */
49
- const reservedWords = /* @__PURE__ */ new Set([
50
- "abstract",
51
- "arguments",
52
- "boolean",
53
- "break",
54
- "byte",
55
- "case",
56
- "catch",
57
- "char",
58
- "class",
59
- "const",
60
- "continue",
61
- "debugger",
62
- "default",
63
- "delete",
64
- "do",
65
- "double",
66
- "else",
67
- "enum",
68
- "eval",
69
- "export",
70
- "extends",
71
- "false",
72
- "final",
73
- "finally",
74
- "float",
75
- "for",
76
- "function",
77
- "goto",
78
- "if",
79
- "implements",
80
- "import",
81
- "in",
82
- "instanceof",
83
- "int",
84
- "interface",
85
- "let",
86
- "long",
87
- "native",
88
- "new",
89
- "null",
90
- "package",
91
- "private",
92
- "protected",
93
- "public",
94
- "return",
95
- "short",
96
- "static",
97
- "super",
98
- "switch",
99
- "synchronized",
100
- "this",
101
- "throw",
102
- "throws",
103
- "transient",
104
- "true",
105
- "try",
106
- "typeof",
107
- "var",
108
- "void",
109
- "volatile",
110
- "while",
111
- "with",
112
- "yield",
113
- "Array",
114
- "Date",
115
- "hasOwnProperty",
116
- "Infinity",
117
- "isFinite",
118
- "isNaN",
119
- "isPrototypeOf",
120
- "length",
121
- "Math",
122
- "name",
123
- "NaN",
124
- "Number",
125
- "Object",
126
- "prototype",
127
- "String",
128
- "toString",
129
- "undefined",
130
- "valueOf"
131
- ]);
132
- /**
133
- * Returns `true` when `name` is a syntactically valid JavaScript variable name.
134
- *
135
- * @example
136
- * ```ts
137
- * isValidVarName('status') // true
138
- * isValidVarName('class') // false (reserved word)
139
- * isValidVarName('42foo') // false (starts with digit)
140
- * ```
141
- */
142
- function isValidVarName(name) {
143
- if (!name || reservedWords.has(name)) return false;
144
- return isIdentifier(name);
145
- }
146
- /**
147
- * Returns `true` when `name` is syntactically a valid identifier, ignoring reserved words.
148
- *
149
- * Reserved words and globals (`class`, `name`, `Date`, …) are valid as bare object-literal keys
150
- * even though they are not valid variable names, so use this (not {@link isValidVarName}) when
151
- * deciding whether an object key needs quoting.
152
- *
153
- * @example
154
- * ```ts
155
- * isIdentifier('name') // true
156
- * isIdentifier('x-total')// false
157
- * ```
158
- */
159
- function isIdentifier(name) {
160
- return /^[a-zA-Z_$][a-zA-Z0-9_$]*$/.test(name);
161
- }
162
- //#endregion
163
- //#region ../../internals/utils/src/strings.ts
164
- /**
165
- * Renders a dotted path or string array as an optional-chaining accessor expression rooted at
166
- * `accessor`. Returns `null` for an empty path.
167
- *
168
- * @example
169
- * ```ts
170
- * getNestedAccessor('pagination.next.id', 'lastPage')
171
- * // "lastPage?.['pagination']?.['next']?.['id']"
172
- * ```
173
- */
174
- function getNestedAccessor(param, accessor) {
175
- const parts = Array.isArray(param) ? param : param.split(".");
176
- if (parts.length === 0 || parts.length === 1 && parts[0] === "") return null;
177
- return `${accessor}?.['${`${parts.join("']?.['")}']`}`;
178
- }
179
- //#endregion
180
- //#region ../../internals/utils/src/url.ts
181
- /**
182
- * Keeps the OpenAPI parameter name as-is when it is already a valid JS identifier, and
183
- * camelCases it only enough to become one otherwise (for example a hyphenated path segment).
184
- */
185
- function transformParam(raw) {
186
- return isValidVarName(raw) ? raw : camelCase(raw);
187
- }
188
- /**
189
- * Helpers for OpenAPI/Swagger paths.
190
- */
191
- var Url = class Url {
192
- /**
193
- * Converts an OpenAPI/Swagger path to Express-style colon syntax.
194
- *
195
- * @example
196
- * Url.toPath('/pet/{petId}') // '/pet/:petId'
197
- */
198
- static toPath(path) {
199
- return path.replace(/\{([^}]+)\}/g, ":$1");
200
- }
201
- /**
202
- * Rewrites OpenAPI placeholder names while keeping the `{...}` braces, so the generated `url`
203
- * literal aligns with the grouped `path` request option that the runtime client interpolates by
204
- * key.
205
- *
206
- * @example
207
- * Url.toSafeTemplate('/user/{monetary-account-id}') // '/user/{monetaryAccountId}'
208
- */
209
- static toSafeTemplate(path) {
210
- return path.replace(/\{([^}]+)\}/g, (_, name) => `{${transformParam(name)}}`);
211
- }
212
- /**
213
- * Converts an OpenAPI/Swagger path to a TypeScript template literal string.
214
- * `prefix` is prepended inside the literal, and `replacer` transforms each parameter name.
215
- *
216
- * @example
217
- * Url.toTemplateString('/pet/{petId}') // '`/pet/${petId}`'
218
- *
219
- * @example
220
- * Url.toTemplateString('/pet/{petId}', { prefix: 'https://api' }) // '`https://api/pet/${petId}`'
221
- */
222
- static toTemplateString(path, { prefix, replacer } = {}) {
223
- const result = path.split(/\{([^}]+)\}/).map((part, i) => {
224
- if (i % 2 === 0) return part;
225
- const param = transformParam(part);
226
- return `\${${replacer ? replacer(param) : param}}`;
227
- }).join("");
228
- return `\`${prefix ?? ""}${result}\``;
229
- }
230
- /**
231
- * Converts an OpenAPI/Swagger path to a template literal that reads each parameter off the
232
- * grouped `path` request option, e.g. `/pet/{petId}` becomes `` `/pet/${path.petId}` ``. Parameter
233
- * names match the generated `path` type, and `prefix` is prepended inside the literal. Shared by
234
- * the client and cypress generators that pass a grouped `path` object.
235
- *
236
- * @example
237
- * Url.toGroupedTemplateString('/pet/{petId}') // '`/pet/${path.petId}`'
238
- */
239
- static toGroupedTemplateString(path, { prefix } = {}) {
240
- return Url.toTemplateString(path, {
241
- prefix,
242
- replacer: (name) => `path.${name}`
243
- });
244
- }
245
- };
246
- //#endregion
247
7
  //#region ../../internals/shared/src/params.ts
248
8
  /**
249
9
  * Drops parameters that share the same name, keeping the first.
@@ -282,11 +42,30 @@ function operationFileEntry(node, name, extname = ".ts") {
282
42
  path: node.path
283
43
  };
284
44
  }
45
+ /**
46
+ * Resolves a dependency plugin's generated file for `node.operationId`, cached in `cache` (the
47
+ * current node's `ctx.cache`) under the resolver's own plugin name. Several dependents reading the
48
+ * same dependency for the same operation in one pass (a query plugin's several hook generators, the
49
+ * MCP handler, ...) share one computed name and path instead of each calling `resolver.file` again.
50
+ *
51
+ * @example Cache `plugin-ts`'s file for the current operation
52
+ * ```ts
53
+ * const fileTs = resolveDependencyOperationFile({ cache: ctx.cache, node, resolver: tsResolver, root, output })
54
+ * ```
55
+ */
56
+ function resolveDependencyOperationFile(options) {
57
+ const { cache, node, resolver, root, output, group } = options;
58
+ return cache.ensureItem(`${resolver.pluginName}:operationFile`, () => resolver.file({
59
+ ...operationFileEntry(node, node.operationId),
60
+ root,
61
+ output,
62
+ group: group ?? void 0
63
+ }));
64
+ }
285
65
  function getOperationLink(node, link) {
286
66
  if (!link) return null;
287
67
  if (typeof link === "function") return link(node) ?? null;
288
- if (link === "urlPath") return node.path ? `{@link ${Url.toPath(node.path)}}` : null;
289
- return node.path ? `{@link ${node.path.replaceAll("{", ":").replaceAll("}", "")}}` : null;
68
+ return node.path ? `{@link ${Url.toPath(node.path)}}` : null;
290
69
  }
291
70
  /**
292
71
  * Derives the shared `ContentTypeInfo` shape from a list of content types, tracking whether several
@@ -376,16 +155,24 @@ function buildOperationComments(node, options = {}) {
376
155
  if (!splitLines) return filteredComments;
377
156
  return filteredComments.flatMap((text) => text.split(/\r?\n/).map((line) => line.trim())).filter((comment) => Boolean(comment));
378
157
  }
158
+ const operationParameterGroupsByNode = /* @__PURE__ */ new WeakMap();
159
+ /**
160
+ * Groups an operation's parameters by location (`path`/`query`/`header`/`cookie`), deduping each
161
+ * group by name. Every plugin generator visiting the same `OperationNode` shares one AST instance
162
+ * (see `KubbDriver`), so the result is cached per node to avoid re-filtering and re-deduping the
163
+ * same parameters once per plugin.
164
+ */
379
165
  function getOperationParameters(node) {
380
- return {
381
- path: dedupeParams(node.parameters.filter((param) => param.in === "path").map((param) => isValidVarName(param.name) ? param : {
382
- ...param,
383
- name: camelCase(param.name)
384
- })),
166
+ const cached = operationParameterGroupsByNode.get(node);
167
+ if (cached) return cached;
168
+ const groups = {
169
+ path: dedupeParams(node.parameters.filter((param) => param.in === "path")),
385
170
  query: dedupeParams(node.parameters.filter((param) => param.in === "query")),
386
171
  header: dedupeParams(node.parameters.filter((param) => param.in === "header")),
387
172
  cookie: dedupeParams(node.parameters.filter((param) => param.in === "cookie"))
388
173
  };
174
+ operationParameterGroupsByNode.set(node, groups);
175
+ return groups;
389
176
  }
390
177
  function getStatusCodeNumber(statusCode) {
391
178
  const code = Number(statusCode);
@@ -450,6 +237,60 @@ function resolveOperationTypeNames(node, resolver, options = {}) {
450
237
  return result;
451
238
  }
452
239
  //#endregion
240
+ //#region ../../internals/utils/src/casing.ts
241
+ /**
242
+ * Shared implementation for camelCase and PascalCase conversion.
243
+ * Splits on common word boundaries (spaces, hyphens, underscores, dots, slashes, colons)
244
+ * and capitalizes each word according to `pascal`.
245
+ *
246
+ * When `pascal` is `true` the first word is also capitalized (PascalCase), otherwise only subsequent words are.
247
+ */
248
+ function toCamelOrPascal(text, pascal) {
249
+ 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) => {
250
+ if (word.length > 1 && word === word.toUpperCase()) return word;
251
+ return (i === 0 && !pascal ? word.charAt(0).toLowerCase() : word.charAt(0).toUpperCase()) + word.slice(1);
252
+ }).join("").replace(/[^a-zA-Z0-9]/g, "");
253
+ }
254
+ /**
255
+ * Converts `text` to camelCase.
256
+ *
257
+ * @example Word boundaries
258
+ * `camelCase('hello-world') // 'helloWorld'`
259
+ *
260
+ * @example With a prefix
261
+ * `camelCase('tag', { prefix: 'create' }) // 'createTag'`
262
+ */
263
+ function camelCase(text, { prefix = "", suffix = "" } = {}) {
264
+ return toCamelOrPascal(`${prefix} ${text} ${suffix}`, false);
265
+ }
266
+ /**
267
+ * Uppercases only the first character of `text`, leaving the rest untouched.
268
+ * Unlike {@link pascalCase} it never re-splits word boundaries or strips characters.
269
+ *
270
+ * @example
271
+ * `capitalize('getPetById') // 'GetPetById'`
272
+ */
273
+ function capitalize(text) {
274
+ return `${text.charAt(0).toUpperCase()}${text.slice(1)}`;
275
+ }
276
+ //#endregion
277
+ //#region ../../internals/utils/src/strings.ts
278
+ /**
279
+ * Renders a dotted path or string array as an optional-chaining accessor expression rooted at
280
+ * `accessor`. Returns `null` for an empty path.
281
+ *
282
+ * @example
283
+ * ```ts
284
+ * getNestedAccessor('pagination.next.id', 'lastPage')
285
+ * // "lastPage?.['pagination']?.['next']?.['id']"
286
+ * ```
287
+ */
288
+ function getNestedAccessor(param, accessor) {
289
+ const parts = Array.isArray(param) ? param : param.split(".");
290
+ if (parts.length === 0 || parts.length === 1 && parts[0] === "") return null;
291
+ return `${accessor}?.['${`${parts.join("']?.['")}']`}`;
292
+ }
293
+ //#endregion
453
294
  //#region ../../internals/shared/src/group.ts
454
295
  /**
455
296
  * Builds the `group` config a Kubb plugin passes to `ctx.setOptions`, applying the
@@ -559,14 +400,16 @@ function buildGroupedRequestParam(node, options) {
559
400
  */
560
401
  function buildQueryOptionsParams(node, options) {
561
402
  const { resolver, memberTypeWrapper } = options;
562
- return createFunctionParameters({ params: [buildGroupedRequestParam(node, {
403
+ const groupedParam = buildGroupedRequestParam(node, {
563
404
  resolver,
564
405
  memberTypeWrapper
565
- }), createFunctionParameter({
406
+ });
407
+ const configParam = createFunctionParameter({
566
408
  name: "config",
567
409
  type: `Partial<Omit<RequestConfig, 'path' | 'query' | 'body' | 'headers' | 'url'>>`,
568
410
  default: "{}"
569
- })].filter((param) => param !== null) });
411
+ });
412
+ return createFunctionParameters({ params: [groupedParam, configParam].filter((param) => param !== null) });
570
413
  }
571
414
  /**
572
415
  * Builds the call to a client `<op>` function inside a query/mutation hook body. The function takes
@@ -664,6 +507,19 @@ function resolveMutationConfig(mutation, options) {
664
507
  };
665
508
  }
666
509
  /**
510
+ * Classifies an operation as a query or a mutation from the resolved `query` / `mutation` method lists.
511
+ * `query: false` still marks the operation as a query so the query-family generators keep matching it,
512
+ * and a method already claimed by `query` never counts as a mutation.
513
+ */
514
+ function classifyOperation(node, { query, mutation }) {
515
+ const isQuery = query === false || !!query && query.methods.some((method) => node.method.toLowerCase() === method.toLowerCase());
516
+ const queryMethods = new Set(query ? query.methods : []);
517
+ return {
518
+ isQuery,
519
+ isMutation: mutation !== false && !isQuery && (mutation ? mutation.methods : []).some((method) => !queryMethods.has(method) && node.method.toLowerCase() === method.toLowerCase())
520
+ };
521
+ }
522
+ /**
667
523
  * Applies the shared infinite-query defaults during plugin setup: a falsy value disables infinite
668
524
  * queries, and an object merges over `queryParam: 'id'` and `initialPageParam: 0` with the cursor
669
525
  * paths cleared.
@@ -679,6 +535,16 @@ function resolveInfiniteConfig(infinite) {
679
535
  ...infinite
680
536
  };
681
537
  }
538
+ /**
539
+ * The request groups a query key is built from, matching `buildQueryKeyParams`. Headers are
540
+ * deliberately absent: they do not identify a cache entry, so the key factory takes no parameter
541
+ * for an operation that only carries headers.
542
+ */
543
+ const queryKeyGroupOrder = [
544
+ "path",
545
+ "query",
546
+ "body"
547
+ ];
682
548
  //#endregion
683
549
  //#region ../../internals/tanstack-query/src/components/InfiniteQueryOptions.tsx
684
550
  const declarationPrinter$7 = functionPrinter({ mode: "declaration" });
@@ -692,11 +558,7 @@ function InfiniteQueryOptions$1({ name, clientName, initialPageParam, cursorPara
692
558
  });
693
559
  const groupedKeyParam = buildGroupedRequestParam(node, {
694
560
  resolver: tsResolver,
695
- keys: [
696
- "path",
697
- "query",
698
- "body"
699
- ],
561
+ keys: queryKeyGroupOrder,
700
562
  memberTypeWrapper
701
563
  });
702
564
  const queryKeyParamsNode = createFunctionParameters({ params: groupedKeyParam ? [groupedKeyParam] : [] });
@@ -706,12 +568,11 @@ function InfiniteQueryOptions$1({ name, clientName, initialPageParam, cursorPara
706
568
  memberTypeWrapper
707
569
  });
708
570
  const paramsSignature = declarationPrinter$7.print(paramsNode) ?? "";
709
- const queryFnBody = `const { data } = await ${buildClientCall(node, {
571
+ const queryFnBody = `return ${buildClientCall(node, {
710
572
  clientName,
711
573
  signal: true,
712
574
  unwrapName
713
- })}
714
- return data`;
575
+ })}.unwrap()`;
715
576
  const hasNewParams = nextParam != null || previousParam != null;
716
577
  const [getNextPageParamExpr, getPreviousPageParamExpr] = (() => {
717
578
  if (hasNewParams) {
@@ -936,23 +797,30 @@ function resolveContractClient(options) {
936
797
  * (plugin-fetch or plugin-axios). Returns `null` when no contract plugin is in play (the inline
937
798
  * path). The plugin injects `.kubb/client.ts` at the global output root, the same path consumers
938
799
  * read `RequestConfig` / `ResponseErrorConfig` from.
800
+ *
801
+ * The result is cached in the current node's `cache` (`ctx.cache`) under the client plugin's name,
802
+ * so several dependents reading the same client plugin for one operation in a single pass
803
+ * (react-query's query/mutation/infinite generators, vue-query, swr, the MCP handler, ...) share
804
+ * one computed result instead of each re-deriving the name and path.
939
805
  */
940
806
  function resolveClientOperation(options) {
941
- const { clientPlugin, driver, node, root, output } = options;
807
+ const { clientPlugin, driver, node, root, output, cache } = options;
942
808
  if (!clientPlugin) return null;
943
- const resolver = driver.getResolver(clientPlugin.pluginName);
944
- const plugin = driver.getPlugin(clientPlugin.pluginName);
945
- const file = resolver.file({
946
- ...operationFileEntry(node, node.operationId),
947
- root,
948
- output: plugin?.options?.output ?? output,
949
- group: plugin?.options?.group ?? void 0
809
+ return cache.ensureItem(`${clientPlugin.pluginName}:clientOperation`, () => {
810
+ const resolver = driver.getResolver(clientPlugin.pluginName);
811
+ const plugin = driver.getPlugin(clientPlugin.pluginName);
812
+ const file = resolver.file({
813
+ ...operationFileEntry(node, node.operationId),
814
+ root,
815
+ output: plugin?.options?.output ?? output,
816
+ group: plugin?.options?.group ?? void 0
817
+ });
818
+ return {
819
+ name: resolver.name(node.operationId),
820
+ path: file.path,
821
+ clientPath: path.resolve(root, ".kubb/client.ts")
822
+ };
950
823
  });
951
- return {
952
- name: resolver.name(node.operationId),
953
- path: file.path,
954
- clientPath: path.resolve(root, ".kubb/client.ts")
955
- };
956
824
  }
957
825
  //#endregion
958
826
  //#region src/utils.ts
@@ -975,11 +843,7 @@ const declarationPrinter$4 = functionPrinter({ mode: "declaration" });
975
843
  function buildQueryKeyParamsNode(node, options) {
976
844
  const groupedParam = buildGroupedRequestParam(node, {
977
845
  resolver: options.resolver,
978
- keys: [
979
- "path",
980
- "query",
981
- "body"
982
- ],
846
+ keys: queryKeyGroupOrder,
983
847
  memberTypeWrapper: maybeRefOrGetter
984
848
  });
985
849
  return createFunctionParameters({ params: groupedParam ? [groupedParam] : [] });
@@ -1030,11 +894,10 @@ function QueryOptions({ name, clientName, node, tsResolver, queryKeyName }) {
1030
894
  const queryKeyParamsCall = callPrinter$3.print(queryKeyParamsNode) ?? "";
1031
895
  const paramsNode = getQueryOptionsParams(node, { resolver: tsResolver });
1032
896
  const paramsSignature = declarationPrinter$3.print(paramsNode) ?? "";
1033
- const queryFnBody = `const { data } = await ${buildVueClientCall(node, {
897
+ const queryFnBody = `return ${buildVueClientCall(node, {
1034
898
  clientName,
1035
899
  signal: true
1036
- })}
1037
- return data`;
900
+ })}.unwrap()`;
1038
901
  return /* @__PURE__ */ jsx(File.Source, {
1039
902
  name,
1040
903
  isExportable: true,
@@ -1076,10 +939,11 @@ function buildInfiniteQueryParamsNode(node, options) {
1076
939
  }`,
1077
940
  default: "{}"
1078
941
  });
1079
- return createFunctionParameters({ params: [buildGroupedRequestParam(node, {
942
+ const groupedParam = buildGroupedRequestParam(node, {
1080
943
  resolver,
1081
944
  memberTypeWrapper: maybeRefOrGetter
1082
- }), optionsParam].filter((param) => param !== null) });
945
+ });
946
+ return createFunctionParameters({ params: [groupedParam, optionsParam].filter((param) => param !== null) });
1083
947
  }
1084
948
  function InfiniteQuery({ name, queryKeyTypeName, queryOptionsName, queryKeyName, node, tsResolver }) {
1085
949
  const { TData, TError } = buildResponseTypes(node, tsResolver);
@@ -1148,13 +1012,14 @@ function resolveMutationRequestType(node, resolver) {
1148
1012
  function buildMutationParamsNode(node, options) {
1149
1013
  const { resolver } = options;
1150
1014
  const { TData, TError } = buildResponseTypes(node, resolver);
1015
+ const TRequest = resolveMutationRequestType(node, resolver);
1151
1016
  return createFunctionParameters({ params: [createFunctionParameter({
1152
1017
  name: "options",
1153
1018
  type: `{
1154
1019
  mutation?: MutationObserverOptions<${[
1155
1020
  TData,
1156
1021
  TError,
1157
- resolveMutationRequestType(node, resolver),
1022
+ TRequest,
1158
1023
  "TContext"
1159
1024
  ].join(", ")}> & { client?: QueryClient },
1160
1025
  client?: ${buildRequestConfigType(node)},
@@ -1168,11 +1033,10 @@ function Mutation({ name, clientName, node, tsResolver, mutationKeyName }) {
1168
1033
  const hasMutationParams = groupedParam !== null;
1169
1034
  const groupedParamsNode = createFunctionParameters({ params: groupedParam ? [groupedParam] : [] });
1170
1035
  const argBindingStr = hasMutationParams ? callPrinter$1.print(groupedParamsNode) ?? "" : "";
1171
- const mutationFnBody = `const { data } = await ${buildVueClientCall(node, {
1036
+ const mutationFnBody = `return ${buildVueClientCall(node, {
1172
1037
  clientName,
1173
1038
  signal: false
1174
- })}
1175
- return data`;
1039
+ })}.unwrap()`;
1176
1040
  const generics = [
1177
1041
  TData,
1178
1042
  TError,
@@ -1228,10 +1092,11 @@ function buildQueryParamsNode(node, options) {
1228
1092
  }`,
1229
1093
  default: "{}"
1230
1094
  });
1231
- return createFunctionParameters({ params: [buildGroupedRequestParam(node, {
1095
+ const groupedParam = buildGroupedRequestParam(node, {
1232
1096
  resolver,
1233
1097
  memberTypeWrapper: maybeRefOrGetter
1234
- }), optionsParam].filter((param) => param !== null) });
1098
+ });
1099
+ return createFunctionParameters({ params: [groupedParam, optionsParam].filter((param) => param !== null) });
1235
1100
  }
1236
1101
  function Query({ name, queryKeyTypeName, queryOptionsName, queryKeyName, node, tsResolver }) {
1237
1102
  const { TData, TError } = buildResponseTypes(node, tsResolver);
@@ -1279,9 +1144,9 @@ function Query({ name, queryKeyTypeName, queryOptionsName, queryKeyName, node, t
1279
1144
  //#region src/generators/infiniteQueryGenerator.tsx
1280
1145
  /**
1281
1146
  * Built-in generator for `useInfiniteQuery` composables. Enabled when
1282
- * `pluginVueQuery({ infinite: { ... } })`. Emits one `useFooInfiniteQuery`
1283
- * composable per query operation, wiring the configured cursor path into
1284
- * TanStack Query's cursor-based pagination.
1147
+ * `pluginVueQuery({ infinite: { ... }, hooks: true })`. Emits one
1148
+ * `useFooInfiniteQuery` composable per query operation, wiring the
1149
+ * configured cursor path into TanStack Query's cursor-based pagination.
1285
1150
  */
1286
1151
  const infiniteQueryGenerator = defineGenerator({
1287
1152
  name: "vue-query-infinite",
@@ -1293,11 +1158,12 @@ const infiniteQueryGenerator = defineGenerator({
1293
1158
  const pluginTs = driver.getPlugin(pluginTsName);
1294
1159
  if (!pluginTs) return null;
1295
1160
  const tsResolver = driver.getResolver(pluginTsName);
1296
- const isQuery = query === false || !!query && query.methods.some((method) => node.method.toLowerCase() === method.toLowerCase());
1297
- const queryMethods = new Set(query ? query.methods : []);
1298
- const isMutation = mutation !== false && !isQuery && (mutation ? mutation.methods : []).some((method) => !queryMethods.has(method) && node.method.toLowerCase() === method.toLowerCase());
1161
+ const { isQuery, isMutation } = classifyOperation(node, {
1162
+ query,
1163
+ mutation
1164
+ });
1299
1165
  const infiniteOptions = infinite && typeof infinite === "object" ? infinite : null;
1300
- if (!isQuery || isMutation || !infiniteOptions) return null;
1166
+ if (!isQuery || isMutation || !infiniteOptions || !hooks) return null;
1301
1167
  const normalizeKey = (key) => key.replace(/\?$/, "");
1302
1168
  const queryParamKeys = getOperationParameters(node).query.map((p) => p.name);
1303
1169
  if (!(infiniteOptions.queryParam ? queryParamKeys.some((k) => normalizeKey(k) === infiniteOptions.queryParam) : false)) return null;
@@ -1307,7 +1173,8 @@ const infiniteQueryGenerator = defineGenerator({
1307
1173
  driver,
1308
1174
  node,
1309
1175
  root,
1310
- output
1176
+ output,
1177
+ cache: ctx.cache
1311
1178
  });
1312
1179
  if (!contractOp) return null;
1313
1180
  const queryName = resolver.infiniteQuery.name(node);
@@ -1321,15 +1188,20 @@ const infiniteQueryGenerator = defineGenerator({
1321
1188
  output,
1322
1189
  group: group ?? void 0
1323
1190
  }),
1324
- fileTs: tsResolver.file({
1325
- ...operationFileEntry(node, node.operationId),
1191
+ fileTs: resolveDependencyOperationFile({
1192
+ cache: ctx.cache,
1193
+ node,
1194
+ resolver: tsResolver,
1326
1195
  root,
1327
1196
  output: pluginTs.options?.output ?? output,
1328
- group: pluginTs.options?.group ?? void 0
1197
+ group: pluginTs.options?.group
1329
1198
  })
1330
1199
  };
1331
- const rawQueryParams = getOperationParameters(node).query;
1332
- const queryParamsTypeName = rawQueryParams.length > 0 && tsResolver.param.query(node, rawQueryParams[0]) !== tsResolver.param.name(node, rawQueryParams[0]) ? tsResolver.param.query(node, rawQueryParams[0]) : null;
1200
+ const { queryParamsTypeName } = resolvePageParamType(node, {
1201
+ resolver: tsResolver,
1202
+ initialPageParam: infiniteOptions.initialPageParam,
1203
+ queryParam: infiniteOptions.queryParam
1204
+ });
1333
1205
  const importedTypeNames = [
1334
1206
  tsResolver.response.options(node),
1335
1207
  queryParamsTypeName,
@@ -1415,32 +1287,30 @@ const infiniteQueryGenerator = defineGenerator({
1415
1287
  initialPageParam: infiniteOptions.initialPageParam,
1416
1288
  queryParam: infiniteOptions.queryParam
1417
1289
  }),
1418
- hooks && /* @__PURE__ */ jsxs(Fragment, { children: [
1419
- /* @__PURE__ */ jsx(File.Import, {
1420
- name: ["useInfiniteQuery"],
1421
- path: importPath
1422
- }),
1423
- /* @__PURE__ */ jsx(File.Import, {
1424
- name: [
1425
- "QueryKey",
1426
- "QueryClient",
1427
- "UseInfiniteQueryOptions",
1428
- "UseInfiniteQueryReturnType"
1429
- ],
1430
- path: importPath,
1431
- isTypeOnly: true
1432
- }),
1433
- /* @__PURE__ */ jsx(InfiniteQuery, {
1434
- name: queryName,
1435
- queryOptionsName,
1436
- queryKeyName,
1437
- queryKeyTypeName,
1438
- node,
1439
- tsResolver,
1440
- initialPageParam: infiniteOptions.initialPageParam,
1441
- queryParam: infiniteOptions.queryParam
1442
- })
1443
- ] })
1290
+ /* @__PURE__ */ jsx(File.Import, {
1291
+ name: ["useInfiniteQuery"],
1292
+ path: importPath
1293
+ }),
1294
+ /* @__PURE__ */ jsx(File.Import, {
1295
+ name: [
1296
+ "QueryKey",
1297
+ "QueryClient",
1298
+ "UseInfiniteQueryOptions",
1299
+ "UseInfiniteQueryReturnType"
1300
+ ],
1301
+ path: importPath,
1302
+ isTypeOnly: true
1303
+ }),
1304
+ /* @__PURE__ */ jsx(InfiniteQuery, {
1305
+ name: queryName,
1306
+ queryOptionsName,
1307
+ queryKeyName,
1308
+ queryKeyTypeName,
1309
+ node,
1310
+ tsResolver,
1311
+ initialPageParam: infiniteOptions.initialPageParam,
1312
+ queryParam: infiniteOptions.queryParam
1313
+ })
1444
1314
  ]
1445
1315
  });
1446
1316
  }
@@ -1462,16 +1332,19 @@ const mutationGenerator = defineGenerator({
1462
1332
  const pluginTs = driver.getPlugin(pluginTsName);
1463
1333
  if (!pluginTs) return null;
1464
1334
  const tsResolver = driver.getResolver(pluginTsName);
1465
- const isQuery = query === false || !!query && query.methods.some((method) => node.method.toLowerCase() === method.toLowerCase());
1466
- const queryMethods = new Set(query ? query.methods : []);
1467
- if (!(mutation !== false && !isQuery && (mutation ? mutation.methods : []).some((method) => !queryMethods.has(method) && node.method.toLowerCase() === method.toLowerCase()))) return null;
1335
+ const { isMutation } = classifyOperation(node, {
1336
+ query,
1337
+ mutation
1338
+ });
1339
+ if (!isMutation) return null;
1468
1340
  const importPath = mutation ? mutation.importPath : "@tanstack/vue-query";
1469
1341
  const contractOp = resolveClientOperation({
1470
1342
  clientPlugin: { pluginName: client.pluginName },
1471
1343
  driver,
1472
1344
  node,
1473
1345
  root,
1474
- output
1346
+ output,
1347
+ cache: ctx.cache
1475
1348
  });
1476
1349
  if (!contractOp) return null;
1477
1350
  const mutationHookName = resolver.mutation.name(node);
@@ -1484,11 +1357,13 @@ const mutationGenerator = defineGenerator({
1484
1357
  output,
1485
1358
  group: group ?? void 0
1486
1359
  }),
1487
- fileTs: tsResolver.file({
1488
- ...operationFileEntry(node, node.operationId),
1360
+ fileTs: resolveDependencyOperationFile({
1361
+ cache: ctx.cache,
1362
+ node,
1363
+ resolver: tsResolver,
1489
1364
  root,
1490
1365
  output: pluginTs.options?.output ?? output,
1491
- group: pluginTs.options?.group ?? void 0
1366
+ group: pluginTs.options?.group
1492
1367
  })
1493
1368
  };
1494
1369
  const importedTypeNames = [tsResolver.response.options(node), ...resolveOperationTypeNames(node, tsResolver, {
@@ -1590,9 +1465,10 @@ const queryGenerator = defineGenerator({
1590
1465
  const pluginTs = driver.getPlugin(pluginTsName);
1591
1466
  if (!pluginTs) return null;
1592
1467
  const tsResolver = driver.getResolver(pluginTsName);
1593
- const isQuery = query === false || !!query && query.methods.some((method) => node.method.toLowerCase() === method.toLowerCase());
1594
- const queryMethods = new Set(query ? query.methods : []);
1595
- const isMutation = mutation !== false && !isQuery && (mutation ? mutation.methods : []).some((method) => !queryMethods.has(method) && node.method.toLowerCase() === method.toLowerCase());
1468
+ const { isQuery, isMutation } = classifyOperation(node, {
1469
+ query,
1470
+ mutation
1471
+ });
1596
1472
  if (!isQuery || isMutation) return null;
1597
1473
  const importPath = query ? query.importPath : "@tanstack/vue-query";
1598
1474
  const contractOp = resolveClientOperation({
@@ -1600,7 +1476,8 @@ const queryGenerator = defineGenerator({
1600
1476
  driver,
1601
1477
  node,
1602
1478
  root,
1603
- output
1479
+ output,
1480
+ cache: ctx.cache
1604
1481
  });
1605
1482
  if (!contractOp) return null;
1606
1483
  const queryName = resolver.query.name(node);
@@ -1614,11 +1491,13 @@ const queryGenerator = defineGenerator({
1614
1491
  output,
1615
1492
  group: group ?? void 0
1616
1493
  }),
1617
- fileTs: tsResolver.file({
1618
- ...operationFileEntry(node, node.operationId),
1494
+ fileTs: resolveDependencyOperationFile({
1495
+ cache: ctx.cache,
1496
+ node,
1497
+ resolver: tsResolver,
1619
1498
  root,
1620
1499
  output: pluginTs.options?.output ?? output,
1621
- group: pluginTs.options?.group ?? void 0
1500
+ group: pluginTs.options?.group
1622
1501
  })
1623
1502
  };
1624
1503
  const importedTypeNames = [tsResolver.response.options(node), ...resolveOperationTypeNames(node, tsResolver, {