@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.cjs +158 -346
- package/dist/index.cjs.map +1 -1
- package/dist/index.js +155 -343
- package/dist/index.js.map +1 -1
- package/package.json +5 -8
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
|
|
9
|
+
* Drops parameters that share the same name, keeping the first.
|
|
296
10
|
*
|
|
297
|
-
*
|
|
298
|
-
*
|
|
299
|
-
*
|
|
300
|
-
*
|
|
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
|
|
16
|
+
function dedupeParams(params) {
|
|
303
17
|
const seen = /* @__PURE__ */ new Set();
|
|
304
18
|
return params.filter((param) => {
|
|
305
|
-
|
|
306
|
-
|
|
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
|
-
|
|
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
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
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.
|
|
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
|
|
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
|
-
|
|
619
|
-
|
|
620
|
-
|
|
621
|
-
|
|
622
|
-
|
|
623
|
-
|
|
624
|
-
|
|
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
|
-
|
|
484
|
+
const groupedParam = buildGroupedRequestParam(node, {
|
|
699
485
|
resolver,
|
|
700
486
|
memberTypeWrapper
|
|
701
|
-
})
|
|
487
|
+
});
|
|
488
|
+
const configParam = createFunctionParameter({
|
|
702
489
|
name: "config",
|
|
703
490
|
type: `Partial<Omit<RequestConfig, 'path' | 'query' | 'body' | 'headers' | 'url'>>`,
|
|
704
491
|
default: "{}"
|
|
705
|
-
})
|
|
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<${
|
|
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<${[
|
|
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
|
-
|
|
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
|
|
1110
|
-
|
|
1111
|
-
|
|
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:
|
|
1136
|
-
|
|
1137
|
-
|
|
1138
|
-
|
|
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
|
-
|
|
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:
|
|
1259
|
-
|
|
1260
|
-
|
|
1261
|
-
|
|
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
|