@kubb/plugin-swr 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,310 +1,23 @@
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/url.ts
164
- function transformParam(raw, casing) {
165
- const param = isValidVarName(raw) ? raw : camelCase(raw);
166
- return casing === "camelcase" ? camelCase(param) : param;
167
- }
168
- function toParamsObject(path, { replacer, casing } = {}) {
169
- const params = {};
170
- for (const match of path.matchAll(/\{([^}]+)\}/g)) {
171
- const param = transformParam(match[1], casing);
172
- const key = replacer ? replacer(param) : param;
173
- params[key] = key;
174
- }
175
- return Object.keys(params).length > 0 ? params : null;
176
- }
177
- /**
178
- * Helpers for OpenAPI/Swagger paths, plus a thin wrapper over the native `URL`.
179
- */
180
- var Url = class Url {
181
- /**
182
- * Reports whether `url` is a parseable absolute URL. Delegates to the native `URL.canParse`.
183
- *
184
- * @example
185
- * Url.canParse('https://petstore.swagger.io/v2') // true
186
- * Url.canParse('/pet/{petId}') // false
187
- */
188
- static canParse(url, base) {
189
- return URL.canParse(url, base);
190
- }
191
- /**
192
- * Converts an OpenAPI/Swagger path to Express-style colon syntax.
193
- *
194
- * @example
195
- * Url.toPath('/pet/{petId}') // '/pet/:petId'
196
- */
197
- static toPath(path) {
198
- return path.replace(/\{([^}]+)\}/g, ":$1");
199
- }
200
- /**
201
- * Rewrites OpenAPI placeholder names while keeping the `{...}` braces, so the generated `url`
202
- * literal aligns with the grouped `path` request option that the runtime client interpolates by
203
- * key.
204
- *
205
- * @example
206
- * Url.toCasedTemplate('/projects/{project_id}', { casing: 'camelcase' }) // '/projects/{projectId}'
207
- */
208
- static toCasedTemplate(path, { casing } = {}) {
209
- return path.replace(/\{([^}]+)\}/g, (_, name) => `{${transformParam(name, casing)}}`);
210
- }
211
- /**
212
- * Converts an OpenAPI/Swagger path to a TypeScript template literal string.
213
- * `prefix` is prepended inside the literal, `replacer` transforms each parameter name,
214
- * and `casing` controls parameter identifier casing.
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, casing } = {}) {
223
- const result = path.split(/\{([^}]+)\}/).map((part, i) => {
224
- if (i % 2 === 0) return part;
225
- const param = transformParam(part, casing);
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 are camelCased to match the generated `path` type, and `prefix` is prepended inside the
234
- * literal. Shared by 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
- casing: "camelcase",
243
- replacer: (name) => `path.${name}`
244
- });
245
- }
246
- /**
247
- * Returns the path and its extracted params as a structured `URLObject`, or as a stringified
248
- * expression when `stringify` is set.
249
- *
250
- * @example
251
- * Url.toObject('/pet/{petId}')
252
- * // { url: '/pet/:petId', params: { petId: 'petId' } }
253
- */
254
- static toObject(path, { type = "path", replacer, stringify, casing } = {}) {
255
- const object = {
256
- url: type === "path" ? Url.toPath(path) : Url.toTemplateString(path, {
257
- replacer,
258
- casing
259
- }),
260
- params: toParamsObject(path, {
261
- replacer,
262
- casing
263
- })
264
- };
265
- if (stringify) {
266
- if (type === "template") return JSON.stringify(object).replaceAll("'", "").replaceAll(`"`, "");
267
- if (object.params) return `{ url: '${object.url}', params: ${JSON.stringify(object.params).replaceAll("'", "").replaceAll(`"`, "")} }`;
268
- return `{ url: '${object.url}' }`;
269
- }
270
- return object;
271
- }
272
- };
273
- //#endregion
274
7
  //#region ../../internals/shared/src/params.ts
275
- const caseParamsCache = /* @__PURE__ */ new WeakMap();
276
- /**
277
- * Applies camelCase to parameter names and returns a new array without mutating the input.
278
- *
279
- * Run it before handing parameters to schema builders so output property keys get the right casing
280
- * while `OperationNode.parameters` stays intact for other consumers. When `casing` is unset, the
281
- * original array is returned unchanged. Results are cached per input array.
282
- */
283
- function caseParams(params, casing) {
284
- if (!casing) return params;
285
- const cached = caseParamsCache.get(params);
286
- if (cached) return cached;
287
- const result = params.map((param) => ({
288
- ...param,
289
- name: camelCase(param.name)
290
- }));
291
- caseParamsCache.set(params, result);
292
- return result;
293
- }
294
8
  /**
295
- * Drops parameters that collapse to the same property identity once camelCased, keeping the first.
9
+ * Drops parameters that share the same name, keeping the first.
296
10
  *
297
- * Some specs declare the same parameter twice under different casings (for example AWS S3 lists both
298
- * `max-uploads` and `MaxUploads`). Both resolve to one output property, so emitting both would yield
299
- * an object type with a duplicate member, which TypeScript rejects. De-duplicate by the camelCased
300
- * identity so the resulting group is collision-free regardless of the names each caller carries.
11
+ * A malformed spec can declare the same parameter name twice within one `in` location. Both would
12
+ * resolve to the same output property, so emitting both would yield an object type with a duplicate
13
+ * member, which TypeScript rejects. This is a defensive guard against that case, not a casing guard:
14
+ * parameter names flow through unchanged, so no two distinct names ever collide here anymore.
301
15
  */
302
- function dedupeByCasedName(params) {
16
+ function dedupeParams(params) {
303
17
  const seen = /* @__PURE__ */ new Set();
304
18
  return params.filter((param) => {
305
- const key = camelCase(param.name);
306
- if (seen.has(key)) return false;
307
- seen.add(key);
19
+ if (seen.has(param.name)) return false;
20
+ seen.add(param.name);
308
21
  return true;
309
22
  });
310
23
  }
@@ -329,11 +42,30 @@ function operationFileEntry(node, name, extname = ".ts") {
329
42
  path: node.path
330
43
  };
331
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
+ }
332
65
  function getOperationLink(node, link) {
333
66
  if (!link) return null;
334
67
  if (typeof link === "function") return link(node) ?? null;
335
- if (link === "urlPath") return node.path ? `{@link ${Url.toPath(node.path)}}` : null;
336
- return node.path ? `{@link ${node.path.replaceAll("{", ":").replaceAll("}", "")}}` : null;
68
+ return node.path ? `{@link ${Url.toPath(node.path)}}` : null;
337
69
  }
338
70
  /**
339
71
  * Derives the shared `ContentTypeInfo` shape from a list of content types, tracking whether several
@@ -423,14 +155,24 @@ function buildOperationComments(node, options = {}) {
423
155
  if (!splitLines) return filteredComments;
424
156
  return filteredComments.flatMap((text) => text.split(/\r?\n/).map((line) => line.trim())).filter((comment) => Boolean(comment));
425
157
  }
426
- function getOperationParameters(node, options = {}) {
427
- const params = caseParams(node.parameters, options.paramsCasing === "original" ? void 0 : "camelcase");
428
- return {
429
- path: dedupeByCasedName(params.filter((param) => param.in === "path")),
430
- query: dedupeByCasedName(params.filter((param) => param.in === "query")),
431
- header: dedupeByCasedName(params.filter((param) => param.in === "header")),
432
- cookie: dedupeByCasedName(params.filter((param) => param.in === "cookie"))
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
+ */
165
+ function getOperationParameters(node) {
166
+ const cached = operationParameterGroupsByNode.get(node);
167
+ if (cached) return cached;
168
+ const groups = {
169
+ path: dedupeParams(node.parameters.filter((param) => param.in === "path")),
170
+ query: dedupeParams(node.parameters.filter((param) => param.in === "query")),
171
+ header: dedupeParams(node.parameters.filter((param) => param.in === "header")),
172
+ cookie: dedupeParams(node.parameters.filter((param) => param.in === "cookie"))
433
173
  };
174
+ operationParameterGroupsByNode.set(node, groups);
175
+ return groups;
434
176
  }
435
177
  function getStatusCodeNumber(statusCode) {
436
178
  const code = Number(statusCode);
@@ -461,7 +203,7 @@ function resolveStatusCodeNames(node, resolver) {
461
203
  }
462
204
  const typeNamesByResolver = /* @__PURE__ */ new WeakMap();
463
205
  function resolveOperationTypeNames(node, resolver, options = {}) {
464
- const cacheKey = `${node.operationId}\0${options.paramsCasing ?? ""}\0${options.order ?? ""}\0${options.responseStatusNames ?? ""}\0${options.includeParams === false ? "noparams" : ""}\0${(options.exclude ?? []).join(",")}`;
206
+ const cacheKey = `${node.operationId}\0${options.order ?? ""}\0${options.responseStatusNames ?? ""}\0${options.includeParams === false ? "noparams" : ""}\0${(options.exclude ?? []).join(",")}`;
465
207
  let byResolver = typeNamesByResolver.get(resolver);
466
208
  if (byResolver) {
467
209
  const cached = byResolver.get(cacheKey);
@@ -470,7 +212,7 @@ function resolveOperationTypeNames(node, resolver, options = {}) {
470
212
  byResolver = /* @__PURE__ */ new Map();
471
213
  typeNamesByResolver.set(resolver, byResolver);
472
214
  }
473
- const { path, query, header } = getOperationParameters(node, { paramsCasing: options.paramsCasing });
215
+ const { path, query, header } = getOperationParameters(node);
474
216
  const responseStatusNames = options.responseStatusNames === "error" ? resolveErrorNames(node, resolver) : options.responseStatusNames === false ? [] : resolveStatusCodeNames(node, resolver);
475
217
  const exclude = new Set(options.exclude ?? []);
476
218
  const paramNames = options.includeParams === false ? [] : [
@@ -492,6 +234,43 @@ function resolveOperationTypeNames(node, resolver, options = {}) {
492
234
  return result;
493
235
  }
494
236
  //#endregion
237
+ //#region ../../internals/utils/src/casing.ts
238
+ /**
239
+ * Shared implementation for camelCase and PascalCase conversion.
240
+ * Splits on common word boundaries (spaces, hyphens, underscores, dots, slashes, colons)
241
+ * and capitalizes each word according to `pascal`.
242
+ *
243
+ * When `pascal` is `true` the first word is also capitalized (PascalCase), otherwise only subsequent words are.
244
+ */
245
+ function toCamelOrPascal(text, pascal) {
246
+ 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) => {
247
+ if (word.length > 1 && word === word.toUpperCase()) return word;
248
+ return (i === 0 && !pascal ? word.charAt(0).toLowerCase() : word.charAt(0).toUpperCase()) + word.slice(1);
249
+ }).join("").replace(/[^a-zA-Z0-9]/g, "");
250
+ }
251
+ /**
252
+ * Converts `text` to camelCase.
253
+ *
254
+ * @example Word boundaries
255
+ * `camelCase('hello-world') // 'helloWorld'`
256
+ *
257
+ * @example With a prefix
258
+ * `camelCase('tag', { prefix: 'create' }) // 'createTag'`
259
+ */
260
+ function camelCase(text, { prefix = "", suffix = "" } = {}) {
261
+ return toCamelOrPascal(`${prefix} ${text} ${suffix}`, false);
262
+ }
263
+ /**
264
+ * Uppercases only the first character of `text`, leaving the rest untouched.
265
+ * Unlike {@link pascalCase} it never re-splits word boundaries or strips characters.
266
+ *
267
+ * @example
268
+ * `capitalize('getPetById') // 'GetPetById'`
269
+ */
270
+ function capitalize(text) {
271
+ return `${text.charAt(0).toUpperCase()}${text.slice(1)}`;
272
+ }
273
+ //#endregion
495
274
  //#region ../../internals/shared/src/group.ts
496
275
  /**
497
276
  * Builds the `group` config a Kubb plugin passes to `ctx.setOptions`, applying the
@@ -611,23 +390,30 @@ function resolveContractClient(options) {
611
390
  * (plugin-fetch or plugin-axios). Returns `null` when no contract plugin is in play (the inline
612
391
  * path). The plugin injects `.kubb/client.ts` at the global output root, the same path consumers
613
392
  * read `RequestConfig` / `ResponseErrorConfig` from.
393
+ *
394
+ * The result is cached in the current node's `cache` (`ctx.cache`) under the client plugin's name,
395
+ * so several dependents reading the same client plugin for one operation in a single pass
396
+ * (react-query's query/mutation/infinite generators, vue-query, swr, the MCP handler, ...) share
397
+ * one computed result instead of each re-deriving the name and path.
614
398
  */
615
399
  function resolveClientOperation(options) {
616
- const { clientPlugin, driver, node, root, output } = options;
400
+ const { clientPlugin, driver, node, root, output, cache } = options;
617
401
  if (!clientPlugin) return null;
618
- const resolver = driver.getResolver(clientPlugin.pluginName);
619
- const plugin = driver.getPlugin(clientPlugin.pluginName);
620
- const file = resolver.file({
621
- ...operationFileEntry(node, node.operationId),
622
- root,
623
- output: plugin?.options?.output ?? output,
624
- group: plugin?.options?.group ?? void 0
402
+ return cache.ensureItem(`${clientPlugin.pluginName}:clientOperation`, () => {
403
+ const resolver = driver.getResolver(clientPlugin.pluginName);
404
+ const plugin = driver.getPlugin(clientPlugin.pluginName);
405
+ const file = resolver.file({
406
+ ...operationFileEntry(node, node.operationId),
407
+ root,
408
+ output: plugin?.options?.output ?? output,
409
+ group: plugin?.options?.group ?? void 0
410
+ });
411
+ return {
412
+ name: resolver.name(node.operationId),
413
+ path: file.path,
414
+ clientPath: path.resolve(root, ".kubb/client.ts")
415
+ };
625
416
  });
626
- return {
627
- name: resolver.name(node.operationId),
628
- path: file.path,
629
- clientPath: path.resolve(root, ".kubb/client.ts")
630
- };
631
417
  }
632
418
  //#endregion
633
419
  //#region ../../internals/tanstack-query/src/utils.ts
@@ -695,14 +481,16 @@ function buildGroupedRequestParam(node, options) {
695
481
  */
696
482
  function buildQueryOptionsParams(node, options) {
697
483
  const { resolver, memberTypeWrapper } = options;
698
- return createFunctionParameters({ params: [buildGroupedRequestParam(node, {
484
+ const groupedParam = buildGroupedRequestParam(node, {
699
485
  resolver,
700
486
  memberTypeWrapper
701
- }), createFunctionParameter({
487
+ });
488
+ const configParam = createFunctionParameter({
702
489
  name: "config",
703
490
  type: `Partial<Omit<RequestConfig, 'path' | 'query' | 'body' | 'headers' | 'url'>>`,
704
491
  default: "{}"
705
- })].filter((param) => param !== null) });
492
+ });
493
+ return createFunctionParameters({ params: [groupedParam, configParam].filter((param) => param !== null) });
706
494
  }
707
495
  /**
708
496
  * Builds the call to a client `<op>` function inside a query/mutation hook body. The function takes
@@ -760,6 +548,19 @@ function resolveMutationConfig(mutation, options) {
760
548
  };
761
549
  }
762
550
  /**
551
+ * Classifies an operation as a query or a mutation from the resolved `query` / `mutation` method lists.
552
+ * `query: false` still marks the operation as a query so the query-family generators keep matching it,
553
+ * and a method already claimed by `query` never counts as a mutation.
554
+ */
555
+ function classifyOperation(node, { query, mutation }) {
556
+ const isQuery = query === false || !!query && query.methods.some((method) => node.method.toLowerCase() === method.toLowerCase());
557
+ const queryMethods = new Set(query ? query.methods : []);
558
+ return {
559
+ isQuery,
560
+ isMutation: mutation !== false && !isQuery && (mutation ? mutation.methods : []).some((method) => !queryMethods.has(method) && node.method.toLowerCase() === method.toLowerCase())
561
+ };
562
+ }
563
+ /**
763
564
  * Build QueryKey params as the grouped `{ path, query, body }` object (NO headers, NO config),
764
565
  * typed from the operation's `Options` minus `url`. The query key transformer reads the
765
566
  * grouped `path`/`query`/`body` bindings.
@@ -906,10 +707,12 @@ function buildMutationParamsNode(node, options) {
906
707
  const { mutationKeyTypeName, mutationArgTypeName, resolver } = options;
907
708
  const responseName = resolver.response.response(node);
908
709
  const errorNames = resolveErrorNames(node, resolver);
710
+ const TData = responseName;
711
+ const TError = `ResponseErrorConfig<${errorNames.length > 0 ? errorNames.join(" | ") : "Error"}>`;
909
712
  return createFunctionParameters({ params: [createFunctionParameter({
910
713
  name: "options",
911
714
  type: `{
912
- mutation?: SWRMutationConfiguration<${responseName}, ${`ResponseErrorConfig<${errorNames.length > 0 ? errorNames.join(" | ") : "Error"}>`}, ${mutationKeyTypeName} | null, ${mutationArgTypeName}> & { throwOnError?: boolean },
715
+ mutation?: SWRMutationConfiguration<${TData}, ${TError}, ${mutationKeyTypeName} | null, ${mutationArgTypeName}> & { throwOnError?: boolean },
913
716
  client?: ${buildRequestConfigType(node)},
914
717
  shouldFetch?: boolean,
915
718
  }`,
@@ -1004,17 +807,20 @@ function buildQueryParamsNode(node, options) {
1004
807
  const { resolver } = options;
1005
808
  const responseName = resolver.response.response(node);
1006
809
  const errorNames = resolveErrorNames(node, resolver);
810
+ const TData = responseName;
811
+ const TError = `ResponseErrorConfig<${errorNames.length > 0 ? errorNames.join(" | ") : "Error"}>`;
1007
812
  const optionsParam = createFunctionParameter({
1008
813
  name: "options",
1009
814
  type: `{
1010
- query?: SWRConfiguration<${[responseName, `ResponseErrorConfig<${errorNames.length > 0 ? errorNames.join(" | ") : "Error"}>`].join(", ")}>,
815
+ query?: SWRConfiguration<${[TData, TError].join(", ")}>,
1011
816
  client?: ${buildClientOptionType()},
1012
817
  shouldFetch?: boolean,
1013
818
  immutable?: boolean
1014
819
  }`,
1015
820
  default: "{}"
1016
821
  });
1017
- return createFunctionParameters({ params: [buildGroupedRequestParam(node, { resolver }), optionsParam].filter((param) => param !== null) });
822
+ const groupedParam = buildGroupedRequestParam(node, { resolver });
823
+ return createFunctionParameters({ params: [groupedParam, optionsParam].filter((param) => param !== null) });
1018
824
  }
1019
825
  function Query({ name, queryKeyTypeName, queryOptionsName, queryKeyName, node, tsResolver }) {
1020
826
  const responseName = tsResolver.response.response(node);
@@ -1106,16 +912,19 @@ const mutationGenerator = defineGenerator({
1106
912
  const pluginTs = driver.getPlugin(pluginTsName);
1107
913
  if (!pluginTs) return null;
1108
914
  const tsResolver = driver.getResolver(pluginTsName);
1109
- const isQuery = query === false || !!query && query.methods.some((method) => node.method.toLowerCase() === method.toLowerCase());
1110
- const queryMethods = new Set(query ? query.methods : []);
1111
- if (!(mutation !== false && !isQuery && (mutation ? mutation.methods : []).some((method) => !queryMethods.has(method) && node.method.toLowerCase() === method.toLowerCase()))) return null;
915
+ const { isMutation } = classifyOperation(node, {
916
+ query,
917
+ mutation
918
+ });
919
+ if (!isMutation) return null;
1112
920
  const importPath = mutation ? mutation.importPath : "swr/mutation";
1113
921
  const contractOp = resolveClientOperation({
1114
922
  clientPlugin: { pluginName: client.pluginName },
1115
923
  driver,
1116
924
  node,
1117
925
  root,
1118
- output
926
+ output,
927
+ cache: ctx.cache
1119
928
  });
1120
929
  if (!contractOp) return null;
1121
930
  const mutationHookName = resolver.mutation.name(node);
@@ -1132,11 +941,10 @@ const mutationGenerator = defineGenerator({
1132
941
  output,
1133
942
  group
1134
943
  }),
1135
- fileTs: tsResolver.file({
1136
- name: node.operationId,
1137
- extname: ".ts",
1138
- tag: node.tags[0] ?? "default",
1139
- path: node.path,
944
+ fileTs: resolveDependencyOperationFile({
945
+ cache: ctx.cache,
946
+ node,
947
+ resolver: tsResolver,
1140
948
  root,
1141
949
  output: pluginTs.options?.output ?? output,
1142
950
  group: pluginTs.options?.group
@@ -1227,18 +1035,23 @@ const queryGenerator = defineGenerator({
1227
1035
  operation(node, ctx) {
1228
1036
  if (!ast.isHttpOperationNode(node)) return null;
1229
1037
  const { config, driver, resolver, root } = ctx;
1230
- const { output, query, client, group } = ctx.options;
1038
+ const { output, query, mutation, client, group } = ctx.options;
1231
1039
  const pluginTs = driver.getPlugin(pluginTsName);
1232
1040
  if (!pluginTs) return null;
1233
1041
  const tsResolver = driver.getResolver(pluginTsName);
1234
- if (!(query === false || !!query && query.methods.some((method) => node.method.toLowerCase() === method.toLowerCase()))) return null;
1042
+ const { isQuery } = classifyOperation(node, {
1043
+ query,
1044
+ mutation
1045
+ });
1046
+ if (!isQuery) return null;
1235
1047
  const importPath = query ? query.importPath : "swr";
1236
1048
  const contractOp = resolveClientOperation({
1237
1049
  clientPlugin: { pluginName: client.pluginName },
1238
1050
  driver,
1239
1051
  node,
1240
1052
  root,
1241
- output
1053
+ output,
1054
+ cache: ctx.cache
1242
1055
  });
1243
1056
  if (!contractOp) return null;
1244
1057
  const queryName = resolver.query.name(node);
@@ -1255,11 +1068,10 @@ const queryGenerator = defineGenerator({
1255
1068
  output,
1256
1069
  group
1257
1070
  }),
1258
- fileTs: tsResolver.file({
1259
- name: node.operationId,
1260
- extname: ".ts",
1261
- tag: node.tags[0] ?? "default",
1262
- path: node.path,
1071
+ fileTs: resolveDependencyOperationFile({
1072
+ cache: ctx.cache,
1073
+ node,
1074
+ resolver: tsResolver,
1263
1075
  root,
1264
1076
  output: pluginTs.options?.output ?? output,
1265
1077
  group: pluginTs.options?.group