@kubb/plugin-vue-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.cjs +221 -403
- package/dist/index.cjs.map +1 -1
- package/dist/index.js +218 -400
- package/dist/index.js.map +1 -1
- package/package.json +5 -8
package/dist/index.js
CHANGED
|
@@ -1,327 +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/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
|
-
function transformParam(raw, casing) {
|
|
182
|
-
const param = isValidVarName(raw) ? raw : camelCase(raw);
|
|
183
|
-
return casing === "camelcase" ? camelCase(param) : param;
|
|
184
|
-
}
|
|
185
|
-
function toParamsObject(path, { replacer, casing } = {}) {
|
|
186
|
-
const params = {};
|
|
187
|
-
for (const match of path.matchAll(/\{([^}]+)\}/g)) {
|
|
188
|
-
const param = transformParam(match[1], casing);
|
|
189
|
-
const key = replacer ? replacer(param) : param;
|
|
190
|
-
params[key] = key;
|
|
191
|
-
}
|
|
192
|
-
return Object.keys(params).length > 0 ? params : null;
|
|
193
|
-
}
|
|
194
|
-
/**
|
|
195
|
-
* Helpers for OpenAPI/Swagger paths, plus a thin wrapper over the native `URL`.
|
|
196
|
-
*/
|
|
197
|
-
var Url = class Url {
|
|
198
|
-
/**
|
|
199
|
-
* Reports whether `url` is a parseable absolute URL. Delegates to the native `URL.canParse`.
|
|
200
|
-
*
|
|
201
|
-
* @example
|
|
202
|
-
* Url.canParse('https://petstore.swagger.io/v2') // true
|
|
203
|
-
* Url.canParse('/pet/{petId}') // false
|
|
204
|
-
*/
|
|
205
|
-
static canParse(url, base) {
|
|
206
|
-
return URL.canParse(url, base);
|
|
207
|
-
}
|
|
208
|
-
/**
|
|
209
|
-
* Converts an OpenAPI/Swagger path to Express-style colon syntax.
|
|
210
|
-
*
|
|
211
|
-
* @example
|
|
212
|
-
* Url.toPath('/pet/{petId}') // '/pet/:petId'
|
|
213
|
-
*/
|
|
214
|
-
static toPath(path) {
|
|
215
|
-
return path.replace(/\{([^}]+)\}/g, ":$1");
|
|
216
|
-
}
|
|
217
|
-
/**
|
|
218
|
-
* Rewrites OpenAPI placeholder names while keeping the `{...}` braces, so the generated `url`
|
|
219
|
-
* literal aligns with the grouped `path` request option that the runtime client interpolates by
|
|
220
|
-
* key.
|
|
221
|
-
*
|
|
222
|
-
* @example
|
|
223
|
-
* Url.toCasedTemplate('/projects/{project_id}', { casing: 'camelcase' }) // '/projects/{projectId}'
|
|
224
|
-
*/
|
|
225
|
-
static toCasedTemplate(path, { casing } = {}) {
|
|
226
|
-
return path.replace(/\{([^}]+)\}/g, (_, name) => `{${transformParam(name, casing)}}`);
|
|
227
|
-
}
|
|
228
|
-
/**
|
|
229
|
-
* Converts an OpenAPI/Swagger path to a TypeScript template literal string.
|
|
230
|
-
* `prefix` is prepended inside the literal, `replacer` transforms each parameter name,
|
|
231
|
-
* and `casing` controls parameter identifier casing.
|
|
232
|
-
*
|
|
233
|
-
* @example
|
|
234
|
-
* Url.toTemplateString('/pet/{petId}') // '`/pet/${petId}`'
|
|
235
|
-
*
|
|
236
|
-
* @example
|
|
237
|
-
* Url.toTemplateString('/pet/{petId}', { prefix: 'https://api' }) // '`https://api/pet/${petId}`'
|
|
238
|
-
*/
|
|
239
|
-
static toTemplateString(path, { prefix, replacer, casing } = {}) {
|
|
240
|
-
const result = path.split(/\{([^}]+)\}/).map((part, i) => {
|
|
241
|
-
if (i % 2 === 0) return part;
|
|
242
|
-
const param = transformParam(part, casing);
|
|
243
|
-
return `\${${replacer ? replacer(param) : param}}`;
|
|
244
|
-
}).join("");
|
|
245
|
-
return `\`${prefix ?? ""}${result}\``;
|
|
246
|
-
}
|
|
247
|
-
/**
|
|
248
|
-
* Converts an OpenAPI/Swagger path to a template literal that reads each parameter off the
|
|
249
|
-
* grouped `path` request option, e.g. `/pet/{petId}` becomes `` `/pet/${path.petId}` ``. Parameter
|
|
250
|
-
* names are camelCased to match the generated `path` type, and `prefix` is prepended inside the
|
|
251
|
-
* literal. Shared by the client and cypress generators that pass a grouped `path` object.
|
|
252
|
-
*
|
|
253
|
-
* @example
|
|
254
|
-
* Url.toGroupedTemplateString('/pet/{petId}') // '`/pet/${path.petId}`'
|
|
255
|
-
*/
|
|
256
|
-
static toGroupedTemplateString(path, { prefix } = {}) {
|
|
257
|
-
return Url.toTemplateString(path, {
|
|
258
|
-
prefix,
|
|
259
|
-
casing: "camelcase",
|
|
260
|
-
replacer: (name) => `path.${name}`
|
|
261
|
-
});
|
|
262
|
-
}
|
|
263
|
-
/**
|
|
264
|
-
* Returns the path and its extracted params as a structured `URLObject`, or as a stringified
|
|
265
|
-
* expression when `stringify` is set.
|
|
266
|
-
*
|
|
267
|
-
* @example
|
|
268
|
-
* Url.toObject('/pet/{petId}')
|
|
269
|
-
* // { url: '/pet/:petId', params: { petId: 'petId' } }
|
|
270
|
-
*/
|
|
271
|
-
static toObject(path, { type = "path", replacer, stringify, casing } = {}) {
|
|
272
|
-
const object = {
|
|
273
|
-
url: type === "path" ? Url.toPath(path) : Url.toTemplateString(path, {
|
|
274
|
-
replacer,
|
|
275
|
-
casing
|
|
276
|
-
}),
|
|
277
|
-
params: toParamsObject(path, {
|
|
278
|
-
replacer,
|
|
279
|
-
casing
|
|
280
|
-
})
|
|
281
|
-
};
|
|
282
|
-
if (stringify) {
|
|
283
|
-
if (type === "template") return JSON.stringify(object).replaceAll("'", "").replaceAll(`"`, "");
|
|
284
|
-
if (object.params) return `{ url: '${object.url}', params: ${JSON.stringify(object.params).replaceAll("'", "").replaceAll(`"`, "")} }`;
|
|
285
|
-
return `{ url: '${object.url}' }`;
|
|
286
|
-
}
|
|
287
|
-
return object;
|
|
288
|
-
}
|
|
289
|
-
};
|
|
290
|
-
//#endregion
|
|
291
7
|
//#region ../../internals/shared/src/params.ts
|
|
292
|
-
const caseParamsCache = /* @__PURE__ */ new WeakMap();
|
|
293
|
-
/**
|
|
294
|
-
* Applies camelCase to parameter names and returns a new array without mutating the input.
|
|
295
|
-
*
|
|
296
|
-
* Run it before handing parameters to schema builders so output property keys get the right casing
|
|
297
|
-
* while `OperationNode.parameters` stays intact for other consumers. When `casing` is unset, the
|
|
298
|
-
* original array is returned unchanged. Results are cached per input array.
|
|
299
|
-
*/
|
|
300
|
-
function caseParams(params, casing) {
|
|
301
|
-
if (!casing) return params;
|
|
302
|
-
const cached = caseParamsCache.get(params);
|
|
303
|
-
if (cached) return cached;
|
|
304
|
-
const result = params.map((param) => ({
|
|
305
|
-
...param,
|
|
306
|
-
name: camelCase(param.name)
|
|
307
|
-
}));
|
|
308
|
-
caseParamsCache.set(params, result);
|
|
309
|
-
return result;
|
|
310
|
-
}
|
|
311
8
|
/**
|
|
312
|
-
* Drops parameters that
|
|
9
|
+
* Drops parameters that share the same name, keeping the first.
|
|
313
10
|
*
|
|
314
|
-
*
|
|
315
|
-
*
|
|
316
|
-
*
|
|
317
|
-
*
|
|
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.
|
|
318
15
|
*/
|
|
319
|
-
function
|
|
16
|
+
function dedupeParams(params) {
|
|
320
17
|
const seen = /* @__PURE__ */ new Set();
|
|
321
18
|
return params.filter((param) => {
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
seen.add(key);
|
|
19
|
+
if (seen.has(param.name)) return false;
|
|
20
|
+
seen.add(param.name);
|
|
325
21
|
return true;
|
|
326
22
|
});
|
|
327
23
|
}
|
|
@@ -346,11 +42,30 @@ function operationFileEntry(node, name, extname = ".ts") {
|
|
|
346
42
|
path: node.path
|
|
347
43
|
};
|
|
348
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
|
+
}
|
|
349
65
|
function getOperationLink(node, link) {
|
|
350
66
|
if (!link) return null;
|
|
351
67
|
if (typeof link === "function") return link(node) ?? null;
|
|
352
|
-
|
|
353
|
-
return node.path ? `{@link ${node.path.replaceAll("{", ":").replaceAll("}", "")}}` : null;
|
|
68
|
+
return node.path ? `{@link ${Url.toPath(node.path)}}` : null;
|
|
354
69
|
}
|
|
355
70
|
/**
|
|
356
71
|
* Derives the shared `ContentTypeInfo` shape from a list of content types, tracking whether several
|
|
@@ -440,14 +155,24 @@ function buildOperationComments(node, options = {}) {
|
|
|
440
155
|
if (!splitLines) return filteredComments;
|
|
441
156
|
return filteredComments.flatMap((text) => text.split(/\r?\n/).map((line) => line.trim())).filter((comment) => Boolean(comment));
|
|
442
157
|
}
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
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"))
|
|
450
173
|
};
|
|
174
|
+
operationParameterGroupsByNode.set(node, groups);
|
|
175
|
+
return groups;
|
|
451
176
|
}
|
|
452
177
|
function getStatusCodeNumber(statusCode) {
|
|
453
178
|
const code = Number(statusCode);
|
|
@@ -481,7 +206,7 @@ function resolveStatusCodeNames(node, resolver) {
|
|
|
481
206
|
}
|
|
482
207
|
const typeNamesByResolver = /* @__PURE__ */ new WeakMap();
|
|
483
208
|
function resolveOperationTypeNames(node, resolver, options = {}) {
|
|
484
|
-
const cacheKey = `${node.operationId}\0${options.
|
|
209
|
+
const cacheKey = `${node.operationId}\0${options.order ?? ""}\0${options.responseStatusNames ?? ""}\0${options.includeParams === false ? "noparams" : ""}\0${(options.exclude ?? []).join(",")}`;
|
|
485
210
|
let byResolver = typeNamesByResolver.get(resolver);
|
|
486
211
|
if (byResolver) {
|
|
487
212
|
const cached = byResolver.get(cacheKey);
|
|
@@ -490,7 +215,7 @@ function resolveOperationTypeNames(node, resolver, options = {}) {
|
|
|
490
215
|
byResolver = /* @__PURE__ */ new Map();
|
|
491
216
|
typeNamesByResolver.set(resolver, byResolver);
|
|
492
217
|
}
|
|
493
|
-
const { path, query, header } = getOperationParameters(node
|
|
218
|
+
const { path, query, header } = getOperationParameters(node);
|
|
494
219
|
const responseStatusNames = options.responseStatusNames === "error" ? resolveErrorNames(node, resolver) : options.responseStatusNames === false ? [] : resolveStatusCodeNames(node, resolver);
|
|
495
220
|
const exclude = new Set(options.exclude ?? []);
|
|
496
221
|
const paramNames = options.includeParams === false ? [] : [
|
|
@@ -512,6 +237,60 @@ function resolveOperationTypeNames(node, resolver, options = {}) {
|
|
|
512
237
|
return result;
|
|
513
238
|
}
|
|
514
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
|
|
515
294
|
//#region ../../internals/shared/src/group.ts
|
|
516
295
|
/**
|
|
517
296
|
* Builds the `group` config a Kubb plugin passes to `ctx.setOptions`, applying the
|
|
@@ -621,14 +400,16 @@ function buildGroupedRequestParam(node, options) {
|
|
|
621
400
|
*/
|
|
622
401
|
function buildQueryOptionsParams(node, options) {
|
|
623
402
|
const { resolver, memberTypeWrapper } = options;
|
|
624
|
-
|
|
403
|
+
const groupedParam = buildGroupedRequestParam(node, {
|
|
625
404
|
resolver,
|
|
626
405
|
memberTypeWrapper
|
|
627
|
-
})
|
|
406
|
+
});
|
|
407
|
+
const configParam = createFunctionParameter({
|
|
628
408
|
name: "config",
|
|
629
409
|
type: `Partial<Omit<RequestConfig, 'path' | 'query' | 'body' | 'headers' | 'url'>>`,
|
|
630
410
|
default: "{}"
|
|
631
|
-
})
|
|
411
|
+
});
|
|
412
|
+
return createFunctionParameters({ params: [groupedParam, configParam].filter((param) => param !== null) });
|
|
632
413
|
}
|
|
633
414
|
/**
|
|
634
415
|
* Builds the call to a client `<op>` function inside a query/mutation hook body. The function takes
|
|
@@ -682,7 +463,7 @@ function resolveFallbackPageParamType(initialPageParam) {
|
|
|
682
463
|
* so callers can reuse it when rewriting the paginated request.
|
|
683
464
|
*/
|
|
684
465
|
function resolvePageParamType(node, { resolver, initialPageParam, queryParam }) {
|
|
685
|
-
const firstQueryParam = getOperationParameters(node
|
|
466
|
+
const firstQueryParam = getOperationParameters(node).query[0];
|
|
686
467
|
const groupName = firstQueryParam ? resolver.param.query(node, firstQueryParam) : null;
|
|
687
468
|
const queryParamsTypeName = groupName !== (firstQueryParam ? resolver.param.name(node, firstQueryParam) : null) ? groupName : null;
|
|
688
469
|
const queryParamType = queryParam && queryParamsTypeName ? `${queryParamsTypeName}['${queryParam}']` : null;
|
|
@@ -726,6 +507,19 @@ function resolveMutationConfig(mutation, options) {
|
|
|
726
507
|
};
|
|
727
508
|
}
|
|
728
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
|
+
/**
|
|
729
523
|
* Applies the shared infinite-query defaults during plugin setup: a falsy value disables infinite
|
|
730
524
|
* queries, and an object merges over `queryParam: 'id'` and `initialPageParam: 0` with the cursor
|
|
731
525
|
* paths cleared.
|
|
@@ -998,23 +792,30 @@ function resolveContractClient(options) {
|
|
|
998
792
|
* (plugin-fetch or plugin-axios). Returns `null` when no contract plugin is in play (the inline
|
|
999
793
|
* path). The plugin injects `.kubb/client.ts` at the global output root, the same path consumers
|
|
1000
794
|
* read `RequestConfig` / `ResponseErrorConfig` from.
|
|
795
|
+
*
|
|
796
|
+
* The result is cached in the current node's `cache` (`ctx.cache`) under the client plugin's name,
|
|
797
|
+
* so several dependents reading the same client plugin for one operation in a single pass
|
|
798
|
+
* (react-query's query/mutation/infinite generators, vue-query, swr, the MCP handler, ...) share
|
|
799
|
+
* one computed result instead of each re-deriving the name and path.
|
|
1001
800
|
*/
|
|
1002
801
|
function resolveClientOperation(options) {
|
|
1003
|
-
const { clientPlugin, driver, node, root, output } = options;
|
|
802
|
+
const { clientPlugin, driver, node, root, output, cache } = options;
|
|
1004
803
|
if (!clientPlugin) return null;
|
|
1005
|
-
|
|
1006
|
-
|
|
1007
|
-
|
|
1008
|
-
|
|
1009
|
-
|
|
1010
|
-
|
|
1011
|
-
|
|
804
|
+
return cache.ensureItem(`${clientPlugin.pluginName}:clientOperation`, () => {
|
|
805
|
+
const resolver = driver.getResolver(clientPlugin.pluginName);
|
|
806
|
+
const plugin = driver.getPlugin(clientPlugin.pluginName);
|
|
807
|
+
const file = resolver.file({
|
|
808
|
+
...operationFileEntry(node, node.operationId),
|
|
809
|
+
root,
|
|
810
|
+
output: plugin?.options?.output ?? output,
|
|
811
|
+
group: plugin?.options?.group ?? void 0
|
|
812
|
+
});
|
|
813
|
+
return {
|
|
814
|
+
name: resolver.name(node.operationId),
|
|
815
|
+
path: file.path,
|
|
816
|
+
clientPath: path.resolve(root, ".kubb/client.ts")
|
|
817
|
+
};
|
|
1012
818
|
});
|
|
1013
|
-
return {
|
|
1014
|
-
name: resolver.name(node.operationId),
|
|
1015
|
-
path: file.path,
|
|
1016
|
-
clientPath: path.resolve(root, ".kubb/client.ts")
|
|
1017
|
-
};
|
|
1018
819
|
}
|
|
1019
820
|
//#endregion
|
|
1020
821
|
//#region src/utils.ts
|
|
@@ -1138,10 +939,11 @@ function buildInfiniteQueryParamsNode(node, options) {
|
|
|
1138
939
|
}`,
|
|
1139
940
|
default: "{}"
|
|
1140
941
|
});
|
|
1141
|
-
|
|
942
|
+
const groupedParam = buildGroupedRequestParam(node, {
|
|
1142
943
|
resolver,
|
|
1143
944
|
memberTypeWrapper: maybeRefOrGetter
|
|
1144
|
-
})
|
|
945
|
+
});
|
|
946
|
+
return createFunctionParameters({ params: [groupedParam, optionsParam].filter((param) => param !== null) });
|
|
1145
947
|
}
|
|
1146
948
|
function InfiniteQuery({ name, queryKeyTypeName, queryOptionsName, queryKeyName, node, tsResolver }) {
|
|
1147
949
|
const { TData, TError } = buildResponseTypes(node, tsResolver);
|
|
@@ -1210,13 +1012,14 @@ function resolveMutationRequestType(node, resolver) {
|
|
|
1210
1012
|
function buildMutationParamsNode(node, options) {
|
|
1211
1013
|
const { resolver } = options;
|
|
1212
1014
|
const { TData, TError } = buildResponseTypes(node, resolver);
|
|
1015
|
+
const TRequest = resolveMutationRequestType(node, resolver);
|
|
1213
1016
|
return createFunctionParameters({ params: [createFunctionParameter({
|
|
1214
1017
|
name: "options",
|
|
1215
1018
|
type: `{
|
|
1216
1019
|
mutation?: MutationObserverOptions<${[
|
|
1217
1020
|
TData,
|
|
1218
1021
|
TError,
|
|
1219
|
-
|
|
1022
|
+
TRequest,
|
|
1220
1023
|
"TContext"
|
|
1221
1024
|
].join(", ")}> & { client?: QueryClient },
|
|
1222
1025
|
client?: ${buildRequestConfigType(node)},
|
|
@@ -1290,10 +1093,11 @@ function buildQueryParamsNode(node, options) {
|
|
|
1290
1093
|
}`,
|
|
1291
1094
|
default: "{}"
|
|
1292
1095
|
});
|
|
1293
|
-
|
|
1096
|
+
const groupedParam = buildGroupedRequestParam(node, {
|
|
1294
1097
|
resolver,
|
|
1295
1098
|
memberTypeWrapper: maybeRefOrGetter
|
|
1296
|
-
})
|
|
1099
|
+
});
|
|
1100
|
+
return createFunctionParameters({ params: [groupedParam, optionsParam].filter((param) => param !== null) });
|
|
1297
1101
|
}
|
|
1298
1102
|
function Query({ name, queryKeyTypeName, queryOptionsName, queryKeyName, node, tsResolver }) {
|
|
1299
1103
|
const { TData, TError } = buildResponseTypes(node, tsResolver);
|
|
@@ -1341,9 +1145,9 @@ function Query({ name, queryKeyTypeName, queryOptionsName, queryKeyName, node, t
|
|
|
1341
1145
|
//#region src/generators/infiniteQueryGenerator.tsx
|
|
1342
1146
|
/**
|
|
1343
1147
|
* Built-in generator for `useInfiniteQuery` composables. Enabled when
|
|
1344
|
-
* `pluginVueQuery({ infinite: { ... } })`. Emits one
|
|
1345
|
-
* composable per query operation, wiring the
|
|
1346
|
-
* TanStack Query's cursor-based pagination.
|
|
1148
|
+
* `pluginVueQuery({ infinite: { ... }, hooks: true })`. Emits one
|
|
1149
|
+
* `useFooInfiniteQuery` composable per query operation, wiring the
|
|
1150
|
+
* configured cursor path into TanStack Query's cursor-based pagination.
|
|
1347
1151
|
*/
|
|
1348
1152
|
const infiniteQueryGenerator = defineGenerator({
|
|
1349
1153
|
name: "vue-query-infinite",
|
|
@@ -1355,13 +1159,14 @@ const infiniteQueryGenerator = defineGenerator({
|
|
|
1355
1159
|
const pluginTs = driver.getPlugin(pluginTsName);
|
|
1356
1160
|
if (!pluginTs) return null;
|
|
1357
1161
|
const tsResolver = driver.getResolver(pluginTsName);
|
|
1358
|
-
const isQuery
|
|
1359
|
-
|
|
1360
|
-
|
|
1162
|
+
const { isQuery, isMutation } = classifyOperation(node, {
|
|
1163
|
+
query,
|
|
1164
|
+
mutation
|
|
1165
|
+
});
|
|
1361
1166
|
const infiniteOptions = infinite && typeof infinite === "object" ? infinite : null;
|
|
1362
|
-
if (!isQuery || isMutation || !infiniteOptions) return null;
|
|
1167
|
+
if (!isQuery || isMutation || !infiniteOptions || !hooks) return null;
|
|
1363
1168
|
const normalizeKey = (key) => key.replace(/\?$/, "");
|
|
1364
|
-
const queryParamKeys = getOperationParameters(node
|
|
1169
|
+
const queryParamKeys = getOperationParameters(node).query.map((p) => p.name);
|
|
1365
1170
|
if (!(infiniteOptions.queryParam ? queryParamKeys.some((k) => normalizeKey(k) === infiniteOptions.queryParam) : false)) return null;
|
|
1366
1171
|
const importPath = query ? query.importPath : "@tanstack/vue-query";
|
|
1367
1172
|
const contractOp = resolveClientOperation({
|
|
@@ -1369,7 +1174,8 @@ const infiniteQueryGenerator = defineGenerator({
|
|
|
1369
1174
|
driver,
|
|
1370
1175
|
node,
|
|
1371
1176
|
root,
|
|
1372
|
-
output
|
|
1177
|
+
output,
|
|
1178
|
+
cache: ctx.cache
|
|
1373
1179
|
});
|
|
1374
1180
|
if (!contractOp) return null;
|
|
1375
1181
|
const queryName = resolver.infiniteQuery.name(node);
|
|
@@ -1383,15 +1189,20 @@ const infiniteQueryGenerator = defineGenerator({
|
|
|
1383
1189
|
output,
|
|
1384
1190
|
group: group ?? void 0
|
|
1385
1191
|
}),
|
|
1386
|
-
fileTs:
|
|
1387
|
-
|
|
1192
|
+
fileTs: resolveDependencyOperationFile({
|
|
1193
|
+
cache: ctx.cache,
|
|
1194
|
+
node,
|
|
1195
|
+
resolver: tsResolver,
|
|
1388
1196
|
root,
|
|
1389
1197
|
output: pluginTs.options?.output ?? output,
|
|
1390
|
-
group: pluginTs.options?.group
|
|
1198
|
+
group: pluginTs.options?.group
|
|
1391
1199
|
})
|
|
1392
1200
|
};
|
|
1393
|
-
const
|
|
1394
|
-
|
|
1201
|
+
const { queryParamsTypeName } = resolvePageParamType(node, {
|
|
1202
|
+
resolver: tsResolver,
|
|
1203
|
+
initialPageParam: infiniteOptions.initialPageParam,
|
|
1204
|
+
queryParam: infiniteOptions.queryParam
|
|
1205
|
+
});
|
|
1395
1206
|
const importedTypeNames = [
|
|
1396
1207
|
tsResolver.response.options(node),
|
|
1397
1208
|
queryParamsTypeName,
|
|
@@ -1477,32 +1288,30 @@ const infiniteQueryGenerator = defineGenerator({
|
|
|
1477
1288
|
initialPageParam: infiniteOptions.initialPageParam,
|
|
1478
1289
|
queryParam: infiniteOptions.queryParam
|
|
1479
1290
|
}),
|
|
1480
|
-
|
|
1481
|
-
|
|
1482
|
-
|
|
1483
|
-
|
|
1484
|
-
|
|
1485
|
-
|
|
1486
|
-
|
|
1487
|
-
|
|
1488
|
-
|
|
1489
|
-
|
|
1490
|
-
|
|
1491
|
-
|
|
1492
|
-
|
|
1493
|
-
|
|
1494
|
-
|
|
1495
|
-
|
|
1496
|
-
|
|
1497
|
-
|
|
1498
|
-
|
|
1499
|
-
|
|
1500
|
-
|
|
1501
|
-
|
|
1502
|
-
|
|
1503
|
-
|
|
1504
|
-
})
|
|
1505
|
-
] })
|
|
1291
|
+
/* @__PURE__ */ jsx(File.Import, {
|
|
1292
|
+
name: ["useInfiniteQuery"],
|
|
1293
|
+
path: importPath
|
|
1294
|
+
}),
|
|
1295
|
+
/* @__PURE__ */ jsx(File.Import, {
|
|
1296
|
+
name: [
|
|
1297
|
+
"QueryKey",
|
|
1298
|
+
"QueryClient",
|
|
1299
|
+
"UseInfiniteQueryOptions",
|
|
1300
|
+
"UseInfiniteQueryReturnType"
|
|
1301
|
+
],
|
|
1302
|
+
path: importPath,
|
|
1303
|
+
isTypeOnly: true
|
|
1304
|
+
}),
|
|
1305
|
+
/* @__PURE__ */ jsx(InfiniteQuery, {
|
|
1306
|
+
name: queryName,
|
|
1307
|
+
queryOptionsName,
|
|
1308
|
+
queryKeyName,
|
|
1309
|
+
queryKeyTypeName,
|
|
1310
|
+
node,
|
|
1311
|
+
tsResolver,
|
|
1312
|
+
initialPageParam: infiniteOptions.initialPageParam,
|
|
1313
|
+
queryParam: infiniteOptions.queryParam
|
|
1314
|
+
})
|
|
1506
1315
|
]
|
|
1507
1316
|
});
|
|
1508
1317
|
}
|
|
@@ -1524,16 +1333,19 @@ const mutationGenerator = defineGenerator({
|
|
|
1524
1333
|
const pluginTs = driver.getPlugin(pluginTsName);
|
|
1525
1334
|
if (!pluginTs) return null;
|
|
1526
1335
|
const tsResolver = driver.getResolver(pluginTsName);
|
|
1527
|
-
const
|
|
1528
|
-
|
|
1529
|
-
|
|
1336
|
+
const { isMutation } = classifyOperation(node, {
|
|
1337
|
+
query,
|
|
1338
|
+
mutation
|
|
1339
|
+
});
|
|
1340
|
+
if (!isMutation) return null;
|
|
1530
1341
|
const importPath = mutation ? mutation.importPath : "@tanstack/vue-query";
|
|
1531
1342
|
const contractOp = resolveClientOperation({
|
|
1532
1343
|
clientPlugin: { pluginName: client.pluginName },
|
|
1533
1344
|
driver,
|
|
1534
1345
|
node,
|
|
1535
1346
|
root,
|
|
1536
|
-
output
|
|
1347
|
+
output,
|
|
1348
|
+
cache: ctx.cache
|
|
1537
1349
|
});
|
|
1538
1350
|
if (!contractOp) return null;
|
|
1539
1351
|
const mutationHookName = resolver.mutation.name(node);
|
|
@@ -1546,11 +1358,13 @@ const mutationGenerator = defineGenerator({
|
|
|
1546
1358
|
output,
|
|
1547
1359
|
group: group ?? void 0
|
|
1548
1360
|
}),
|
|
1549
|
-
fileTs:
|
|
1550
|
-
|
|
1361
|
+
fileTs: resolveDependencyOperationFile({
|
|
1362
|
+
cache: ctx.cache,
|
|
1363
|
+
node,
|
|
1364
|
+
resolver: tsResolver,
|
|
1551
1365
|
root,
|
|
1552
1366
|
output: pluginTs.options?.output ?? output,
|
|
1553
|
-
group: pluginTs.options?.group
|
|
1367
|
+
group: pluginTs.options?.group
|
|
1554
1368
|
})
|
|
1555
1369
|
};
|
|
1556
1370
|
const importedTypeNames = [tsResolver.response.options(node), ...resolveOperationTypeNames(node, tsResolver, {
|
|
@@ -1652,9 +1466,10 @@ const queryGenerator = defineGenerator({
|
|
|
1652
1466
|
const pluginTs = driver.getPlugin(pluginTsName);
|
|
1653
1467
|
if (!pluginTs) return null;
|
|
1654
1468
|
const tsResolver = driver.getResolver(pluginTsName);
|
|
1655
|
-
const isQuery
|
|
1656
|
-
|
|
1657
|
-
|
|
1469
|
+
const { isQuery, isMutation } = classifyOperation(node, {
|
|
1470
|
+
query,
|
|
1471
|
+
mutation
|
|
1472
|
+
});
|
|
1658
1473
|
if (!isQuery || isMutation) return null;
|
|
1659
1474
|
const importPath = query ? query.importPath : "@tanstack/vue-query";
|
|
1660
1475
|
const contractOp = resolveClientOperation({
|
|
@@ -1662,7 +1477,8 @@ const queryGenerator = defineGenerator({
|
|
|
1662
1477
|
driver,
|
|
1663
1478
|
node,
|
|
1664
1479
|
root,
|
|
1665
|
-
output
|
|
1480
|
+
output,
|
|
1481
|
+
cache: ctx.cache
|
|
1666
1482
|
});
|
|
1667
1483
|
if (!contractOp) return null;
|
|
1668
1484
|
const queryName = resolver.query.name(node);
|
|
@@ -1676,11 +1492,13 @@ const queryGenerator = defineGenerator({
|
|
|
1676
1492
|
output,
|
|
1677
1493
|
group: group ?? void 0
|
|
1678
1494
|
}),
|
|
1679
|
-
fileTs:
|
|
1680
|
-
|
|
1495
|
+
fileTs: resolveDependencyOperationFile({
|
|
1496
|
+
cache: ctx.cache,
|
|
1497
|
+
node,
|
|
1498
|
+
resolver: tsResolver,
|
|
1681
1499
|
root,
|
|
1682
1500
|
output: pluginTs.options?.output ?? output,
|
|
1683
|
-
group: pluginTs.options?.group
|
|
1501
|
+
group: pluginTs.options?.group
|
|
1684
1502
|
})
|
|
1685
1503
|
};
|
|
1686
1504
|
const importedTypeNames = [tsResolver.response.options(node), ...resolveOperationTypeNames(node, tsResolver, {
|