@kubb/plugin-client 5.0.0-beta.3 → 5.0.0-beta.30
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/README.md +24 -4
- package/dist/clients/axios.cjs +25 -3
- package/dist/clients/axios.cjs.map +1 -1
- package/dist/clients/axios.d.ts +9 -2
- package/dist/clients/axios.js +25 -3
- package/dist/clients/axios.js.map +1 -1
- package/dist/clients/fetch.cjs +20 -2
- package/dist/clients/fetch.cjs.map +1 -1
- package/dist/clients/fetch.d.ts +9 -2
- package/dist/clients/fetch.js +20 -2
- package/dist/clients/fetch.js.map +1 -1
- package/dist/index.cjs +524 -301
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.ts +150 -84
- package/dist/index.js +525 -302
- package/dist/index.js.map +1 -1
- package/dist/templates/clients/axios.source.cjs +1 -1
- package/dist/templates/clients/axios.source.js +1 -1
- package/dist/templates/clients/fetch.source.cjs +1 -1
- package/dist/templates/clients/fetch.source.js +1 -1
- package/extension.yaml +1293 -0
- package/package.json +11 -17
- package/src/clients/axios.ts +41 -7
- package/src/clients/fetch.ts +30 -3
- package/src/components/ClassClient.tsx +17 -19
- package/src/components/Client.tsx +68 -51
- package/src/components/StaticClassClient.tsx +17 -19
- package/src/components/Url.tsx +7 -9
- package/src/components/WrapperClient.tsx +9 -5
- package/src/functionParams.ts +8 -8
- package/src/generators/classClientGenerator.tsx +40 -38
- package/src/generators/clientGenerator.tsx +32 -35
- package/src/generators/groupedClientGenerator.tsx +14 -8
- package/src/generators/operationsGenerator.tsx +12 -6
- package/src/generators/staticClassClientGenerator.tsx +34 -32
- package/src/plugin.ts +24 -11
- package/src/resolvers/resolverClient.ts +31 -8
- package/src/types.ts +90 -53
- package/src/utils.ts +30 -53
- package/templates/clients/axios.ts +0 -73
- package/templates/clients/fetch.ts +0 -96
- package/templates/config.ts +0 -43
package/dist/index.js
CHANGED
|
@@ -5,7 +5,7 @@ import { source as source$2 } from "./templates/config.source.js";
|
|
|
5
5
|
import path from "node:path";
|
|
6
6
|
import { ast, defineGenerator, definePlugin, defineResolver } from "@kubb/core";
|
|
7
7
|
import { functionPrinter, pluginTsName } from "@kubb/plugin-ts";
|
|
8
|
-
import { Const, File, Function,
|
|
8
|
+
import { Const, File, Function, jsxRendererSync } from "@kubb/renderer-jsx";
|
|
9
9
|
import { Fragment, jsx, jsxs } from "@kubb/renderer-jsx/jsx-runtime";
|
|
10
10
|
import { pluginZodName } from "@kubb/plugin-zod";
|
|
11
11
|
//#region ../../internals/utils/src/casing.ts
|
|
@@ -185,6 +185,26 @@ function isValidVarName(name) {
|
|
|
185
185
|
if (!name || reservedWords.has(name)) return false;
|
|
186
186
|
return /^[a-zA-Z_$][a-zA-Z0-9_$]*$/.test(name);
|
|
187
187
|
}
|
|
188
|
+
/**
|
|
189
|
+
* Returns `name` when it's a syntactically valid JavaScript variable name,
|
|
190
|
+
* otherwise prefixes it with `_` so the result is a valid identifier.
|
|
191
|
+
*
|
|
192
|
+
* Useful for sanitizing OpenAPI schema names or operation IDs that start with
|
|
193
|
+
* a digit (e.g. `409`, `504AccountCancel`) before using them as exported
|
|
194
|
+
* variable, type, or function names.
|
|
195
|
+
*
|
|
196
|
+
* @example
|
|
197
|
+
* ```ts
|
|
198
|
+
* ensureValidVarName('409') // '_409'
|
|
199
|
+
* ensureValidVarName('504AccountCancel') // '_504AccountCancel'
|
|
200
|
+
* ensureValidVarName('Pet') // 'Pet'
|
|
201
|
+
* ensureValidVarName('class') // '_class'
|
|
202
|
+
* ```
|
|
203
|
+
*/
|
|
204
|
+
function ensureValidVarName(name) {
|
|
205
|
+
if (!name || isValidVarName(name)) return name;
|
|
206
|
+
return `_${name}`;
|
|
207
|
+
}
|
|
188
208
|
//#endregion
|
|
189
209
|
//#region ../../internals/utils/src/urlPath.ts
|
|
190
210
|
/**
|
|
@@ -251,16 +271,16 @@ var URLPath = class {
|
|
|
251
271
|
get object() {
|
|
252
272
|
return this.toObject();
|
|
253
273
|
}
|
|
254
|
-
/** Returns a map of path parameter names, or `
|
|
274
|
+
/** Returns a map of path parameter names, or `null` when the path has no parameters.
|
|
255
275
|
*
|
|
256
276
|
* @example
|
|
257
277
|
* ```ts
|
|
258
278
|
* new URLPath('/pet/{petId}').params // { petId: 'petId' }
|
|
259
|
-
* new URLPath('/pet').params //
|
|
279
|
+
* new URLPath('/pet').params // null
|
|
260
280
|
* ```
|
|
261
281
|
*/
|
|
262
282
|
get params() {
|
|
263
|
-
return this.
|
|
283
|
+
return this.toParamsObject();
|
|
264
284
|
}
|
|
265
285
|
#transformParam(raw) {
|
|
266
286
|
const param = isValidVarName(raw) ? raw : camelCase(raw);
|
|
@@ -278,7 +298,7 @@ var URLPath = class {
|
|
|
278
298
|
toObject({ type = "path", replacer, stringify } = {}) {
|
|
279
299
|
const object = {
|
|
280
300
|
url: type === "path" ? this.toURLPath() : this.toTemplateString({ replacer }),
|
|
281
|
-
params: this.
|
|
301
|
+
params: this.toParamsObject()
|
|
282
302
|
};
|
|
283
303
|
if (stringify) {
|
|
284
304
|
if (type === "template") return JSON.stringify(object).replaceAll("'", "").replaceAll(`"`, "");
|
|
@@ -294,12 +314,13 @@ var URLPath = class {
|
|
|
294
314
|
* @example
|
|
295
315
|
* new URLPath('/pet/{petId}').toTemplateString() // '`/pet/${petId}`'
|
|
296
316
|
*/
|
|
297
|
-
toTemplateString({ prefix
|
|
298
|
-
|
|
317
|
+
toTemplateString({ prefix, replacer } = {}) {
|
|
318
|
+
const result = this.path.split(/\{([^}]+)\}/).map((part, i) => {
|
|
299
319
|
if (i % 2 === 0) return part;
|
|
300
320
|
const param = this.#transformParam(part);
|
|
301
321
|
return `\${${replacer ? replacer(param) : param}}`;
|
|
302
|
-
}).join("")
|
|
322
|
+
}).join("");
|
|
323
|
+
return `\`${prefix ?? ""}${result}\``;
|
|
303
324
|
}
|
|
304
325
|
/**
|
|
305
326
|
* Extracts all `{param}` segments from the path and returns them as a key-value map.
|
|
@@ -308,17 +329,17 @@ var URLPath = class {
|
|
|
308
329
|
*
|
|
309
330
|
* @example
|
|
310
331
|
* ```ts
|
|
311
|
-
* new URLPath('/pet/{petId}/tag/{tagId}').
|
|
332
|
+
* new URLPath('/pet/{petId}/tag/{tagId}').toParamsObject()
|
|
312
333
|
* // { petId: 'petId', tagId: 'tagId' }
|
|
313
334
|
* ```
|
|
314
335
|
*/
|
|
315
|
-
|
|
336
|
+
toParamsObject(replacer) {
|
|
316
337
|
const params = {};
|
|
317
338
|
this.#eachParam((_raw, param) => {
|
|
318
339
|
const key = replacer ? replacer(param) : param;
|
|
319
340
|
params[key] = key;
|
|
320
341
|
});
|
|
321
|
-
return Object.keys(params).length > 0 ? params :
|
|
342
|
+
return Object.keys(params).length > 0 ? params : null;
|
|
322
343
|
}
|
|
323
344
|
/** Converts the OpenAPI path to Express-style colon syntax.
|
|
324
345
|
*
|
|
@@ -332,6 +353,121 @@ var URLPath = class {
|
|
|
332
353
|
}
|
|
333
354
|
};
|
|
334
355
|
//#endregion
|
|
356
|
+
//#region ../../internals/shared/src/operation.ts
|
|
357
|
+
function getOperationLink(node, link) {
|
|
358
|
+
if (!link) return null;
|
|
359
|
+
if (typeof link === "function") return link(node) ?? null;
|
|
360
|
+
if (link === "urlPath") return node.path ? `{@link ${new URLPath(node.path).URL}}` : null;
|
|
361
|
+
return `{@link ${node.path.replaceAll("{", ":").replaceAll("}", "")}}`;
|
|
362
|
+
}
|
|
363
|
+
function getContentTypeInfo(node) {
|
|
364
|
+
const contentTypes = node.requestBody?.content?.map((e) => e.contentType) ?? [];
|
|
365
|
+
const isMultipleContentTypes = contentTypes.length > 1;
|
|
366
|
+
return {
|
|
367
|
+
contentTypes,
|
|
368
|
+
isMultipleContentTypes,
|
|
369
|
+
contentTypeUnion: isMultipleContentTypes ? contentTypes.map((ct) => JSON.stringify(ct)).join(" | ") : "",
|
|
370
|
+
defaultContentType: contentTypes[0] ?? "application/json",
|
|
371
|
+
hasFormData: contentTypes.some((ct) => ct === "multipart/form-data")
|
|
372
|
+
};
|
|
373
|
+
}
|
|
374
|
+
function buildRequestConfigType(node, resolver) {
|
|
375
|
+
const requestName = node.requestBody?.content?.[0]?.schema ? resolver.resolveDataName(node) : null;
|
|
376
|
+
const { isMultipleContentTypes, contentTypeUnion } = getContentTypeInfo(node);
|
|
377
|
+
return `${requestName ? `Partial<RequestConfig<${requestName}>>` : "Partial<RequestConfig>"} & { ${["client?: Client", isMultipleContentTypes ? `contentType?: ${contentTypeUnion}` : null].filter(Boolean).join("; ")} }`;
|
|
378
|
+
}
|
|
379
|
+
function buildOperationComments(node, options = {}) {
|
|
380
|
+
const { link = "pathTemplate", linkPosition = "afterDeprecated", splitLines = false } = options;
|
|
381
|
+
const linkComment = getOperationLink(node, link);
|
|
382
|
+
const filteredComments = (linkPosition === "beforeDeprecated" ? [
|
|
383
|
+
node.description && `@description ${node.description}`,
|
|
384
|
+
node.summary && `@summary ${node.summary}`,
|
|
385
|
+
linkComment,
|
|
386
|
+
node.deprecated && "@deprecated"
|
|
387
|
+
] : [
|
|
388
|
+
node.description && `@description ${node.description}`,
|
|
389
|
+
node.summary && `@summary ${node.summary}`,
|
|
390
|
+
node.deprecated && "@deprecated",
|
|
391
|
+
linkComment
|
|
392
|
+
]).filter((comment) => Boolean(comment));
|
|
393
|
+
if (!splitLines) return filteredComments;
|
|
394
|
+
return filteredComments.flatMap((text) => text.split(/\r?\n/).map((line) => line.trim())).filter((comment) => Boolean(comment));
|
|
395
|
+
}
|
|
396
|
+
function getOperationParameters(node, options = {}) {
|
|
397
|
+
const params = ast.caseParams(node.parameters, options.paramsCasing);
|
|
398
|
+
return {
|
|
399
|
+
path: params.filter((param) => param.in === "path"),
|
|
400
|
+
query: params.filter((param) => param.in === "query"),
|
|
401
|
+
header: params.filter((param) => param.in === "header"),
|
|
402
|
+
cookie: params.filter((param) => param.in === "cookie")
|
|
403
|
+
};
|
|
404
|
+
}
|
|
405
|
+
function getStatusCodeNumber(statusCode) {
|
|
406
|
+
const code = Number(statusCode);
|
|
407
|
+
return Number.isNaN(code) ? null : code;
|
|
408
|
+
}
|
|
409
|
+
function isSuccessStatusCode(statusCode) {
|
|
410
|
+
const code = getStatusCodeNumber(statusCode);
|
|
411
|
+
return code !== null && code >= 200 && code < 300;
|
|
412
|
+
}
|
|
413
|
+
function isErrorStatusCode(statusCode) {
|
|
414
|
+
const code = getStatusCodeNumber(statusCode);
|
|
415
|
+
return code !== null && code >= 400;
|
|
416
|
+
}
|
|
417
|
+
function resolveErrorNames(node, resolver) {
|
|
418
|
+
return node.responses.filter((response) => isErrorStatusCode(response.statusCode)).map((response) => resolver.resolveResponseStatusName(node, response.statusCode));
|
|
419
|
+
}
|
|
420
|
+
function resolveSuccessNames(node, resolver) {
|
|
421
|
+
return node.responses.filter((response) => isSuccessStatusCode(response.statusCode)).map((response) => resolver.resolveResponseStatusName(node, response.statusCode));
|
|
422
|
+
}
|
|
423
|
+
function resolveStatusCodeNames(node, resolver) {
|
|
424
|
+
return node.responses.map((response) => resolver.resolveResponseStatusName(node, response.statusCode));
|
|
425
|
+
}
|
|
426
|
+
const typeNamesByResolver = /* @__PURE__ */ new WeakMap();
|
|
427
|
+
function resolveOperationTypeNames(node, resolver, options = {}) {
|
|
428
|
+
const cacheKey = `${node.operationId}\0${options.paramsCasing ?? ""}\0${options.order ?? ""}\0${options.responseStatusNames ?? ""}\0${(options.exclude ?? []).join(",")}`;
|
|
429
|
+
let byResolver = typeNamesByResolver.get(resolver);
|
|
430
|
+
if (byResolver) {
|
|
431
|
+
const cached = byResolver.get(cacheKey);
|
|
432
|
+
if (cached) return cached;
|
|
433
|
+
} else {
|
|
434
|
+
byResolver = /* @__PURE__ */ new Map();
|
|
435
|
+
typeNamesByResolver.set(resolver, byResolver);
|
|
436
|
+
}
|
|
437
|
+
const { path, query, header } = getOperationParameters(node, { paramsCasing: options.paramsCasing });
|
|
438
|
+
const responseStatusNames = options.responseStatusNames === "error" ? resolveErrorNames(node, resolver) : options.responseStatusNames === false ? [] : resolveStatusCodeNames(node, resolver);
|
|
439
|
+
const exclude = new Set(options.exclude ?? []);
|
|
440
|
+
const paramNames = [
|
|
441
|
+
...path.map((param) => resolver.resolvePathParamsName(node, param)),
|
|
442
|
+
...query.map((param) => resolver.resolveQueryParamsName(node, param)),
|
|
443
|
+
...header.map((param) => resolver.resolveHeaderParamsName(node, param))
|
|
444
|
+
];
|
|
445
|
+
const bodyAndResponseNames = [node.requestBody?.content?.[0]?.schema ? resolver.resolveDataName(node) : null, resolver.resolveResponseName(node)];
|
|
446
|
+
const result = (options.order === "body-response-first" ? [
|
|
447
|
+
...bodyAndResponseNames,
|
|
448
|
+
...paramNames,
|
|
449
|
+
...responseStatusNames
|
|
450
|
+
] : [
|
|
451
|
+
...paramNames,
|
|
452
|
+
...bodyAndResponseNames,
|
|
453
|
+
...responseStatusNames
|
|
454
|
+
]).filter((name) => Boolean(name) && !exclude.has(name));
|
|
455
|
+
byResolver.set(cacheKey, result);
|
|
456
|
+
return result;
|
|
457
|
+
}
|
|
458
|
+
//#endregion
|
|
459
|
+
//#region ../../internals/shared/src/params.ts
|
|
460
|
+
function buildParamsMapping(originalParams, mappedParams) {
|
|
461
|
+
const mapping = {};
|
|
462
|
+
let hasChanged = false;
|
|
463
|
+
originalParams.forEach((param, i) => {
|
|
464
|
+
const mappedName = mappedParams[i]?.name ?? param.name;
|
|
465
|
+
mapping[param.name] = mappedName;
|
|
466
|
+
if (param.name !== mappedName) hasChanged = true;
|
|
467
|
+
});
|
|
468
|
+
return hasChanged ? mapping : null;
|
|
469
|
+
}
|
|
470
|
+
//#endregion
|
|
335
471
|
//#region src/functionParams.ts
|
|
336
472
|
const declarationPrinter$4 = functionPrinter({ mode: "declaration" });
|
|
337
473
|
const callPrinter = functionPrinter({ mode: "call" });
|
|
@@ -342,18 +478,18 @@ function createType(type) {
|
|
|
342
478
|
return type ? ast.createParamsType({
|
|
343
479
|
variant: "reference",
|
|
344
480
|
name: type
|
|
345
|
-
}) :
|
|
481
|
+
}) : null;
|
|
346
482
|
}
|
|
347
483
|
function createDeclarationLeaf(name, spec) {
|
|
348
484
|
if (spec.default !== void 0) return ast.createFunctionParameter({
|
|
349
485
|
name,
|
|
350
|
-
type: createType(spec.type),
|
|
486
|
+
type: createType(spec.type) ?? void 0,
|
|
351
487
|
default: spec.default,
|
|
352
488
|
rest: spec.mode === "inlineSpread"
|
|
353
489
|
});
|
|
354
490
|
return ast.createFunctionParameter({
|
|
355
491
|
name,
|
|
356
|
-
type: createType(spec.type),
|
|
492
|
+
type: createType(spec.type) ?? void 0,
|
|
357
493
|
optional: !!spec.optional,
|
|
358
494
|
rest: spec.mode === "inlineSpread"
|
|
359
495
|
});
|
|
@@ -362,14 +498,14 @@ function createDeclarationParam(name, spec) {
|
|
|
362
498
|
if (isGroup(spec)) return ast.createParameterGroup({
|
|
363
499
|
inline: spec.mode === "inlineSpread",
|
|
364
500
|
default: spec.default,
|
|
365
|
-
properties: Object.entries(spec.children).filter(([, child]) => child
|
|
501
|
+
properties: Object.entries(spec.children).filter(([, child]) => child != null).map(([childName, child]) => createDeclarationLeaf(childName, child))
|
|
366
502
|
});
|
|
367
503
|
return createDeclarationLeaf(name, spec);
|
|
368
504
|
}
|
|
369
505
|
function createCallParam(name, spec) {
|
|
370
506
|
if (isGroup(spec)) return ast.createParameterGroup({
|
|
371
507
|
inline: spec.mode === "inlineSpread",
|
|
372
|
-
properties: Object.entries(spec.children).filter(([, child]) => child
|
|
508
|
+
properties: Object.entries(spec.children).filter(([, child]) => child != null).map(([childName, child]) => ast.createFunctionParameter({
|
|
373
509
|
name: child?.mode === "inlineSpread" ? spec.mode === "inlineSpread" ? child.value ?? childName : `...${child.value ?? childName}` : child?.value ? `${childName}: ${child.value}` : childName,
|
|
374
510
|
rest: spec.mode === "inlineSpread" && child?.mode === "inlineSpread"
|
|
375
511
|
}))
|
|
@@ -384,7 +520,7 @@ function createCallParam(name, spec) {
|
|
|
384
520
|
* Returns utilities to output constructor signatures (`toConstructor()`) or call expressions (`toCall()`).
|
|
385
521
|
*/
|
|
386
522
|
function createFunctionParams(params) {
|
|
387
|
-
const entries = Object.entries(params).filter(([, spec]) => spec
|
|
523
|
+
const entries = Object.entries(params).filter(([, spec]) => spec != null);
|
|
388
524
|
return {
|
|
389
525
|
toConstructor() {
|
|
390
526
|
return declarationPrinter$4.print(ast.createFunctionParameters({ params: entries.map(([name, spec]) => createDeclarationParam(name, spec)) })) ?? "";
|
|
@@ -395,106 +531,9 @@ function createFunctionParams(params) {
|
|
|
395
531
|
};
|
|
396
532
|
}
|
|
397
533
|
//#endregion
|
|
398
|
-
//#region src/utils.ts
|
|
399
|
-
/**
|
|
400
|
-
* Extracts documentation comments from an operation node.
|
|
401
|
-
* Includes description, summary, link, and deprecation information.
|
|
402
|
-
*/
|
|
403
|
-
function getComments(node) {
|
|
404
|
-
return [
|
|
405
|
-
node.description && `@description ${node.description}`,
|
|
406
|
-
node.summary && `@summary ${node.summary}`,
|
|
407
|
-
node.path && `{@link ${new URLPath(node.path).URL}}`,
|
|
408
|
-
node.deprecated && "@deprecated"
|
|
409
|
-
].filter((x) => Boolean(x)).flatMap((text) => text.split(/\r?\n/).map((line) => line.trim())).filter((x) => Boolean(x));
|
|
410
|
-
}
|
|
411
|
-
/**
|
|
412
|
-
* Builds a mapping of original parameter names to their transformed (cased) names.
|
|
413
|
-
* Returns undefined if no names have changed.
|
|
414
|
-
*/
|
|
415
|
-
function buildParamsMapping(originalParams, casedParams) {
|
|
416
|
-
const mapping = {};
|
|
417
|
-
let hasChanged = false;
|
|
418
|
-
originalParams.forEach((param, i) => {
|
|
419
|
-
const casedName = casedParams[i]?.name ?? param.name;
|
|
420
|
-
mapping[param.name] = casedName;
|
|
421
|
-
if (param.name !== casedName) hasChanged = true;
|
|
422
|
-
});
|
|
423
|
-
return hasChanged ? mapping : void 0;
|
|
424
|
-
}
|
|
425
|
-
/**
|
|
426
|
-
* Builds HTTP headers array for a client request.
|
|
427
|
-
* Includes Content-Type (if not default) and spreads header parameters if present.
|
|
428
|
-
*/
|
|
429
|
-
function buildHeaders(contentType, hasHeaderParams) {
|
|
430
|
-
return [contentType !== "application/json" && contentType !== "multipart/form-data" ? `'Content-Type': '${contentType}'` : void 0, hasHeaderParams ? "...headers" : void 0].filter(Boolean);
|
|
431
|
-
}
|
|
432
|
-
/**
|
|
433
|
-
* Builds TypeScript generic parameters for a client method.
|
|
434
|
-
* Includes response type, error type, and optional request type.
|
|
435
|
-
*/
|
|
436
|
-
function buildGenerics(node, tsResolver) {
|
|
437
|
-
const responseName = tsResolver.resolveResponseName(node);
|
|
438
|
-
const requestName = node.requestBody?.content?.[0]?.schema ? tsResolver.resolveDataName(node) : void 0;
|
|
439
|
-
const errorNames = node.responses.filter((r) => Number.parseInt(r.statusCode, 10) >= 400).map((r) => tsResolver.resolveResponseStatusName(node, r.statusCode));
|
|
440
|
-
return [
|
|
441
|
-
responseName,
|
|
442
|
-
`ResponseErrorConfig<${errorNames.length > 0 ? errorNames.join(" | ") : "Error"}>`,
|
|
443
|
-
requestName || "unknown"
|
|
444
|
-
].filter(Boolean);
|
|
445
|
-
}
|
|
446
|
-
/**
|
|
447
|
-
* Builds the parameters object for a class-based client method.
|
|
448
|
-
* Includes URL, method, base URL, headers, and request/response data.
|
|
449
|
-
*/
|
|
450
|
-
function buildClassClientParams({ node, path, baseURL, tsResolver, isFormData, headers }) {
|
|
451
|
-
const queryParamsName = node.parameters.filter((p) => p.in === "query").length > 0 ? tsResolver.resolveQueryParamsName(node, node.parameters.filter((p) => p.in === "query")[0]) : void 0;
|
|
452
|
-
const requestName = node.requestBody?.content?.[0]?.schema ? tsResolver.resolveDataName(node) : void 0;
|
|
453
|
-
return createFunctionParams({ config: {
|
|
454
|
-
mode: "object",
|
|
455
|
-
children: {
|
|
456
|
-
requestConfig: { mode: "inlineSpread" },
|
|
457
|
-
method: { value: JSON.stringify(node.method.toUpperCase()) },
|
|
458
|
-
url: { value: path.template },
|
|
459
|
-
baseURL: baseURL ? { value: JSON.stringify(baseURL) } : void 0,
|
|
460
|
-
params: queryParamsName ? {} : void 0,
|
|
461
|
-
data: requestName ? { value: isFormData ? "formData as FormData" : "requestData" } : void 0,
|
|
462
|
-
headers: headers.length ? { value: `{ ${headers.join(", ")}, ...requestConfig.headers }` } : void 0
|
|
463
|
-
}
|
|
464
|
-
} });
|
|
465
|
-
}
|
|
466
|
-
/**
|
|
467
|
-
* Builds the request data parsing line for client methods.
|
|
468
|
-
* Applies Zod validation if configured, otherwise uses data directly.
|
|
469
|
-
*/
|
|
470
|
-
function buildRequestDataLine({ parser, node, zodResolver }) {
|
|
471
|
-
const zodRequestName = zodResolver && parser === "zod" && node.requestBody?.content?.[0]?.schema ? zodResolver.resolveDataName?.(node) : void 0;
|
|
472
|
-
if (parser === "zod" && zodRequestName) return `const requestData = ${zodRequestName}.parse(data)`;
|
|
473
|
-
if (node.requestBody?.content?.[0]?.schema) return "const requestData = data";
|
|
474
|
-
return "";
|
|
475
|
-
}
|
|
476
|
-
/**
|
|
477
|
-
* Builds the form data conversion line for file upload requests.
|
|
478
|
-
* Returns empty string if not applicable.
|
|
479
|
-
*/
|
|
480
|
-
function buildFormDataLine(isFormData, hasRequest) {
|
|
481
|
-
return isFormData && hasRequest ? "const formData = buildFormData(requestData)" : "";
|
|
482
|
-
}
|
|
483
|
-
/**
|
|
484
|
-
* Builds the return statement for a client method.
|
|
485
|
-
* Applies Zod validation to response data if configured, otherwise returns raw response.
|
|
486
|
-
*/
|
|
487
|
-
function buildReturnStatement({ dataReturnType, parser, node, zodResolver }) {
|
|
488
|
-
const zodResponseName = zodResolver && parser === "zod" ? zodResolver.resolveResponseName?.(node) : void 0;
|
|
489
|
-
if (dataReturnType === "full" && parser === "zod" && zodResponseName) return `return {...res, data: ${zodResponseName}.parse(res.data)}`;
|
|
490
|
-
if (dataReturnType === "data" && parser === "zod" && zodResponseName) return `return ${zodResponseName}.parse(res.data)`;
|
|
491
|
-
if (dataReturnType === "full" && parser === "client") return "return res";
|
|
492
|
-
return "return res.data";
|
|
493
|
-
}
|
|
494
|
-
//#endregion
|
|
495
534
|
//#region src/components/Url.tsx
|
|
496
535
|
const declarationPrinter$3 = functionPrinter({ mode: "declaration" });
|
|
497
|
-
function
|
|
536
|
+
function buildUrlParamsNode({ paramsType, paramsCasing, pathParamsType, node, tsResolver }) {
|
|
498
537
|
const urlNode = {
|
|
499
538
|
...node,
|
|
500
539
|
parameters: node.parameters.filter((p) => p.in === "path"),
|
|
@@ -507,10 +546,9 @@ function getParams$1({ paramsType, paramsCasing, pathParamsType, node, tsResolve
|
|
|
507
546
|
resolver: tsResolver
|
|
508
547
|
});
|
|
509
548
|
}
|
|
510
|
-
__name(getParams$1, "getParams");
|
|
511
549
|
function Url({ name, isExportable = true, isIndexable = true, baseURL, paramsType, paramsCasing, pathParamsType, node, tsResolver }) {
|
|
512
550
|
const path = new URLPath(node.path);
|
|
513
|
-
const paramsNode =
|
|
551
|
+
const paramsNode = buildUrlParamsNode({
|
|
514
552
|
paramsType,
|
|
515
553
|
paramsCasing,
|
|
516
554
|
pathParamsType,
|
|
@@ -518,9 +556,9 @@ function Url({ name, isExportable = true, isIndexable = true, baseURL, paramsTyp
|
|
|
518
556
|
tsResolver
|
|
519
557
|
});
|
|
520
558
|
const paramsSignature = declarationPrinter$3.print(paramsNode) ?? "";
|
|
521
|
-
const originalPathParams = node
|
|
522
|
-
const casedPathParams =
|
|
523
|
-
const pathParamsMapping = paramsCasing ? buildParamsMapping(originalPathParams, casedPathParams) :
|
|
559
|
+
const { path: originalPathParams } = getOperationParameters(node);
|
|
560
|
+
const { path: casedPathParams } = getOperationParameters(node, { paramsCasing });
|
|
561
|
+
const pathParamsMapping = paramsCasing ? buildParamsMapping(originalPathParams, casedPathParams) : null;
|
|
524
562
|
return /* @__PURE__ */ jsx(File.Source, {
|
|
525
563
|
name,
|
|
526
564
|
isExportable,
|
|
@@ -542,56 +580,51 @@ function Url({ name, isExportable = true, isIndexable = true, baseURL, paramsTyp
|
|
|
542
580
|
})
|
|
543
581
|
});
|
|
544
582
|
}
|
|
545
|
-
Url.getParams = getParams$1;
|
|
546
583
|
//#endregion
|
|
547
584
|
//#region src/components/Client.tsx
|
|
548
585
|
const declarationPrinter$2 = functionPrinter({ mode: "declaration" });
|
|
549
|
-
function
|
|
550
|
-
const requestName = node.requestBody?.content?.[0]?.schema ? tsResolver.resolveDataName(node) : void 0;
|
|
586
|
+
function buildClientParamsNode({ paramsType, paramsCasing, pathParamsType, node, tsResolver, isConfigurable }) {
|
|
551
587
|
return ast.createOperationParams(node, {
|
|
552
588
|
paramsType,
|
|
553
589
|
pathParamsType: paramsType === "object" ? "object" : pathParamsType === "object" ? "object" : "inline",
|
|
554
590
|
paramsCasing,
|
|
555
591
|
resolver: tsResolver,
|
|
556
|
-
extraParams: isConfigurable ? [ast.createFunctionParameter({
|
|
592
|
+
extraParams: [...isConfigurable ? [ast.createFunctionParameter({
|
|
557
593
|
name: "config",
|
|
558
594
|
type: ast.createParamsType({
|
|
559
595
|
variant: "reference",
|
|
560
|
-
name:
|
|
596
|
+
name: buildRequestConfigType(node, tsResolver)
|
|
561
597
|
}),
|
|
562
598
|
default: "{}"
|
|
563
|
-
})] : []
|
|
599
|
+
})] : []]
|
|
564
600
|
});
|
|
565
601
|
}
|
|
566
602
|
function Client({ name, isExportable = true, isIndexable = true, returnType, baseURL, dataReturnType, parser, paramsType, paramsCasing, pathParamsType, node, tsResolver, zodResolver, urlName, children, isConfigurable = true }) {
|
|
567
603
|
const path = new URLPath(node.path);
|
|
568
|
-
const contentType = node
|
|
569
|
-
const isFormData = contentType === "multipart/form-data";
|
|
570
|
-
const originalPathParams
|
|
571
|
-
const casedPathParams =
|
|
572
|
-
const
|
|
573
|
-
const
|
|
574
|
-
const
|
|
575
|
-
const
|
|
576
|
-
const
|
|
577
|
-
const
|
|
578
|
-
const
|
|
579
|
-
const
|
|
580
|
-
const
|
|
581
|
-
const
|
|
582
|
-
const headerParamsName = originalHeaderParams.length > 0 ? tsResolver.resolveHeaderParamsName(node, originalHeaderParams[0]) : void 0;
|
|
583
|
-
const zodResponseName = zodResolver && parser === "zod" ? zodResolver.resolveResponseName?.(node) : void 0;
|
|
584
|
-
const zodRequestName = zodResolver && parser === "zod" && node.requestBody?.content?.[0]?.schema ? zodResolver.resolveDataName?.(node) : void 0;
|
|
604
|
+
const { defaultContentType: contentType, isMultipleContentTypes, hasFormData } = getContentTypeInfo(node);
|
|
605
|
+
const isFormData = !isMultipleContentTypes && contentType === "multipart/form-data";
|
|
606
|
+
const { path: originalPathParams, query: originalQueryParams, header: originalHeaderParams } = getOperationParameters(node);
|
|
607
|
+
const { path: casedPathParams, query: casedQueryParams, header: casedHeaderParams } = getOperationParameters(node, { paramsCasing });
|
|
608
|
+
const pathParamsMapping = paramsCasing && !urlName ? buildParamsMapping(originalPathParams, casedPathParams) : null;
|
|
609
|
+
const queryParamsMapping = paramsCasing ? buildParamsMapping(originalQueryParams, casedQueryParams) : null;
|
|
610
|
+
const headerParamsMapping = paramsCasing ? buildParamsMapping(originalHeaderParams, casedHeaderParams) : null;
|
|
611
|
+
const requestName = node.requestBody?.content?.[0]?.schema ? tsResolver.resolveDataName(node) : null;
|
|
612
|
+
const successNames = resolveSuccessNames(node, tsResolver);
|
|
613
|
+
const responseName = successNames.length > 0 ? successNames.join(" | ") : tsResolver.resolveResponseName(node);
|
|
614
|
+
const queryParamsName = originalQueryParams.length > 0 ? tsResolver.resolveQueryParamsName(node, originalQueryParams[0]) : null;
|
|
615
|
+
const headerParamsName = originalHeaderParams.length > 0 ? tsResolver.resolveHeaderParamsName(node, originalHeaderParams[0]) : null;
|
|
616
|
+
const zodResponseName = zodResolver && parser === "zod" ? zodResolver.resolveResponseName?.(node) : null;
|
|
617
|
+
const zodRequestName = zodResolver && parser === "zod" && node.requestBody?.content?.[0]?.schema ? zodResolver.resolveDataName?.(node) : null;
|
|
585
618
|
const errorNames = node.responses.filter((r) => {
|
|
586
619
|
return Number.parseInt(r.statusCode, 10) >= 400;
|
|
587
620
|
}).map((r) => tsResolver.resolveResponseStatusName(node, r.statusCode));
|
|
588
|
-
const headers = [contentType !== "application/json" && contentType !== "multipart/form-data" ? `'Content-Type': '${contentType}'` :
|
|
621
|
+
const headers = [!isMultipleContentTypes && contentType !== "application/json" && contentType !== "multipart/form-data" ? `'Content-Type': '${contentType}'` : null, headerParamsName ? headerParamsMapping ? "...mappedHeaders" : "...headers" : null].filter(Boolean);
|
|
589
622
|
const generics = [
|
|
590
623
|
responseName,
|
|
591
624
|
`ResponseErrorConfig<${errorNames.length > 0 ? errorNames.join(" | ") : "Error"}>`,
|
|
592
625
|
requestName || "unknown"
|
|
593
626
|
].filter(Boolean);
|
|
594
|
-
const paramsNode =
|
|
627
|
+
const paramsNode = buildClientParamsNode({
|
|
595
628
|
paramsType,
|
|
596
629
|
paramsCasing,
|
|
597
630
|
pathParamsType,
|
|
@@ -600,7 +633,7 @@ function Client({ name, isExportable = true, isIndexable = true, returnType, bas
|
|
|
600
633
|
isConfigurable
|
|
601
634
|
});
|
|
602
635
|
const paramsSignature = declarationPrinter$2.print(paramsNode) ?? "";
|
|
603
|
-
const urlParamsNode =
|
|
636
|
+
const urlParamsNode = buildUrlParamsNode({
|
|
604
637
|
paramsType,
|
|
605
638
|
paramsCasing,
|
|
606
639
|
pathParamsType,
|
|
@@ -613,11 +646,12 @@ function Client({ name, isExportable = true, isIndexable = true, returnType, bas
|
|
|
613
646
|
children: {
|
|
614
647
|
method: { value: JSON.stringify(node.method.toUpperCase()) },
|
|
615
648
|
url: { value: urlName ? `${urlName}(${urlParamsCall}).url.toString()` : path.template },
|
|
616
|
-
baseURL: baseURL && !urlName ? { value: `\`${baseURL}\`` } :
|
|
617
|
-
params: queryParamsName ? queryParamsMapping ? { value: "mappedParams" } : {} :
|
|
618
|
-
data: requestName ? { value: isFormData ? "formData as FormData" : "requestData" } :
|
|
619
|
-
|
|
620
|
-
|
|
649
|
+
baseURL: baseURL && !urlName ? { value: `\`${baseURL}\`` } : null,
|
|
650
|
+
params: queryParamsName ? queryParamsMapping ? { value: "mappedParams" } : {} : null,
|
|
651
|
+
data: requestName ? { value: isMultipleContentTypes && hasFormData ? "contentType === 'multipart/form-data' ? formData as FormData : requestData" : isFormData ? "formData as FormData" : "requestData" } : null,
|
|
652
|
+
contentType: isConfigurable && isMultipleContentTypes ? {} : null,
|
|
653
|
+
requestConfig: isConfigurable ? { mode: "inlineSpread" } : null,
|
|
654
|
+
headers: headers.length ? { value: isConfigurable ? `{ ${headers.join(", ")}, ...requestConfig.headers }` : `{ ${headers.join(", ")} }` } : null
|
|
621
655
|
}
|
|
622
656
|
} });
|
|
623
657
|
const childrenElement = children ? children : /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
@@ -635,10 +669,14 @@ function Client({ name, isExportable = true, isIndexable = true, returnType, bas
|
|
|
635
669
|
async: true,
|
|
636
670
|
export: isExportable,
|
|
637
671
|
params: paramsSignature,
|
|
638
|
-
JSDoc: { comments:
|
|
672
|
+
JSDoc: { comments: buildOperationComments(node, {
|
|
673
|
+
link: "urlPath",
|
|
674
|
+
linkPosition: "beforeDeprecated",
|
|
675
|
+
splitLines: true
|
|
676
|
+
}) },
|
|
639
677
|
returnType,
|
|
640
678
|
children: [
|
|
641
|
-
isConfigurable ?
|
|
679
|
+
isConfigurable ? `const { client: request = client, ${isMultipleContentTypes ? `contentType = ${JSON.stringify(contentType)}, ` : ""}...requestConfig } = config` : "",
|
|
642
680
|
/* @__PURE__ */ jsx("br", {}),
|
|
643
681
|
/* @__PURE__ */ jsx("br", {}),
|
|
644
682
|
pathParamsMapping && Object.entries(pathParamsMapping).filter(([originalName, camelCaseName]) => isValidVarName(originalName) && originalName !== camelCaseName).map(([originalName, camelCaseName]) => `const ${originalName} = ${camelCaseName}`).join("\n"),
|
|
@@ -655,26 +693,101 @@ function Client({ name, isExportable = true, isIndexable = true, returnType, bas
|
|
|
655
693
|
] }),
|
|
656
694
|
parser === "zod" && zodRequestName ? `const requestData = ${zodRequestName}.parse(data)` : requestName && "const requestData = data",
|
|
657
695
|
/* @__PURE__ */ jsx("br", {}),
|
|
658
|
-
isFormData && requestName && "const formData = buildFormData(requestData)",
|
|
696
|
+
(isFormData || isMultipleContentTypes && hasFormData) && requestName && "const formData = buildFormData(requestData)",
|
|
659
697
|
/* @__PURE__ */ jsx("br", {}),
|
|
660
|
-
isConfigurable ? `const res = await request<${generics.join(", ")}>(${clientParams.toCall()})` : `const res = await
|
|
698
|
+
isConfigurable ? `const res = await request<${generics.join(", ")}>(${clientParams.toCall()})` : `const res = await client<${generics.join(", ")}>(${clientParams.toCall()})`,
|
|
661
699
|
/* @__PURE__ */ jsx("br", {}),
|
|
662
700
|
childrenElement
|
|
663
701
|
]
|
|
664
702
|
})
|
|
665
703
|
})] });
|
|
666
704
|
}
|
|
667
|
-
|
|
705
|
+
//#endregion
|
|
706
|
+
//#region src/utils.ts
|
|
707
|
+
/**
|
|
708
|
+
* Builds HTTP headers array for a client request.
|
|
709
|
+
* Includes Content-Type (if not default) and spreads header parameters if present.
|
|
710
|
+
*/
|
|
711
|
+
function buildHeaders(contentType, hasHeaderParams) {
|
|
712
|
+
return [contentType !== "application/json" && contentType !== "multipart/form-data" ? `'Content-Type': '${contentType}'` : null, hasHeaderParams ? "...headers" : null].filter(Boolean);
|
|
713
|
+
}
|
|
714
|
+
/**
|
|
715
|
+
* Builds TypeScript generic parameters for a client method.
|
|
716
|
+
* Includes response type, error type, and optional request type.
|
|
717
|
+
*/
|
|
718
|
+
function buildGenerics(node, tsResolver) {
|
|
719
|
+
const successNames = resolveSuccessNames(node, tsResolver);
|
|
720
|
+
const responseName = successNames.length > 0 ? successNames.join(" | ") : tsResolver.resolveResponseName(node);
|
|
721
|
+
const requestName = node.requestBody?.content?.[0]?.schema ? tsResolver.resolveDataName(node) : null;
|
|
722
|
+
const errorNames = node.responses.filter((r) => Number.parseInt(r.statusCode, 10) >= 400).map((r) => tsResolver.resolveResponseStatusName(node, r.statusCode));
|
|
723
|
+
return [
|
|
724
|
+
responseName,
|
|
725
|
+
`ResponseErrorConfig<${errorNames.length > 0 ? errorNames.join(" | ") : "Error"}>`,
|
|
726
|
+
requestName || "unknown"
|
|
727
|
+
].filter(Boolean);
|
|
728
|
+
}
|
|
729
|
+
/**
|
|
730
|
+
* Builds the parameters object for a class-based client method.
|
|
731
|
+
* Includes URL, method, base URL, headers, and request/response data.
|
|
732
|
+
*/
|
|
733
|
+
function buildClassClientParams({ node, path, baseURL, tsResolver, isFormData, isMultipleContentTypes, hasFormData, headers }) {
|
|
734
|
+
const { query: queryParams } = getOperationParameters(node);
|
|
735
|
+
const queryParamsName = queryParams.length > 0 ? tsResolver.resolveQueryParamsName(node, queryParams[0]) : null;
|
|
736
|
+
const requestName = node.requestBody?.content?.[0]?.schema ? tsResolver.resolveDataName(node) : null;
|
|
737
|
+
return createFunctionParams({ config: {
|
|
738
|
+
mode: "object",
|
|
739
|
+
children: {
|
|
740
|
+
requestConfig: { mode: "inlineSpread" },
|
|
741
|
+
method: { value: JSON.stringify(node.method.toUpperCase()) },
|
|
742
|
+
url: { value: path.template },
|
|
743
|
+
baseURL: baseURL ? { value: JSON.stringify(baseURL) } : null,
|
|
744
|
+
params: queryParamsName ? {} : null,
|
|
745
|
+
data: requestName ? { value: isMultipleContentTypes && hasFormData ? "contentType === 'multipart/form-data' ? formData as FormData : requestData" : isFormData ? "formData as FormData" : "requestData" } : null,
|
|
746
|
+
contentType: isMultipleContentTypes ? {} : null,
|
|
747
|
+
headers: headers.length ? { value: `{ ${headers.join(", ")}, ...requestConfig.headers }` } : null
|
|
748
|
+
}
|
|
749
|
+
} });
|
|
750
|
+
}
|
|
751
|
+
/**
|
|
752
|
+
* Builds the request data parsing line for client methods.
|
|
753
|
+
* Applies Zod validation if configured, otherwise uses data directly.
|
|
754
|
+
*/
|
|
755
|
+
function buildRequestDataLine({ parser, node, zodResolver }) {
|
|
756
|
+
const zodRequestName = zodResolver && parser === "zod" && node.requestBody?.content?.[0]?.schema ? zodResolver.resolveDataName?.(node) : null;
|
|
757
|
+
if (parser === "zod" && zodRequestName) return `const requestData = ${zodRequestName}.parse(data)`;
|
|
758
|
+
if (node.requestBody?.content?.[0]?.schema) return "const requestData = data";
|
|
759
|
+
return "";
|
|
760
|
+
}
|
|
761
|
+
/**
|
|
762
|
+
* Builds the form data conversion line for file upload requests.
|
|
763
|
+
* Returns empty string if not applicable.
|
|
764
|
+
*/
|
|
765
|
+
function buildFormDataLine(isFormData, hasRequest) {
|
|
766
|
+
return isFormData && hasRequest ? "const formData = buildFormData(requestData)" : "";
|
|
767
|
+
}
|
|
768
|
+
/**
|
|
769
|
+
* Builds the return statement for a client method.
|
|
770
|
+
* Applies Zod validation to response data if configured, otherwise returns raw response.
|
|
771
|
+
*/
|
|
772
|
+
function buildReturnStatement({ dataReturnType, parser, node, zodResolver }) {
|
|
773
|
+
const zodResponseName = zodResolver && parser === "zod" ? zodResolver.resolveResponseName?.(node) : null;
|
|
774
|
+
if (dataReturnType === "full" && parser === "zod" && zodResponseName) return `return {...res, data: ${zodResponseName}.parse(res.data)}`;
|
|
775
|
+
if (dataReturnType === "data" && parser === "zod" && zodResponseName) return `return ${zodResponseName}.parse(res.data)`;
|
|
776
|
+
if (dataReturnType === "full" && parser === "client") return "return res";
|
|
777
|
+
return "return res.data";
|
|
778
|
+
}
|
|
668
779
|
//#endregion
|
|
669
780
|
//#region src/components/ClassClient.tsx
|
|
670
781
|
const declarationPrinter$1 = functionPrinter({ mode: "declaration" });
|
|
671
782
|
function generateMethod$1({ node, name, tsResolver, zodResolver, baseURL, dataReturnType, parser, paramsType, paramsCasing, pathParamsType }) {
|
|
672
783
|
const path = new URLPath(node.path, { casing: paramsCasing });
|
|
673
|
-
const contentType = node
|
|
674
|
-
const isFormData = contentType === "multipart/form-data";
|
|
675
|
-
const
|
|
784
|
+
const { defaultContentType: contentType, isMultipleContentTypes, hasFormData } = getContentTypeInfo(node);
|
|
785
|
+
const isFormData = !isMultipleContentTypes && contentType === "multipart/form-data";
|
|
786
|
+
const { header: headerParams } = getOperationParameters(node);
|
|
787
|
+
const headerParamsName = headerParams.length > 0 ? tsResolver.resolveHeaderParamsName(node, headerParams[0]) : null;
|
|
788
|
+
const headers = isMultipleContentTypes ? headerParamsName ? ["...headers"] : [] : buildHeaders(contentType, !!headerParamsName);
|
|
676
789
|
const generics = buildGenerics(node, tsResolver);
|
|
677
|
-
const paramsNode =
|
|
790
|
+
const paramsNode = buildClientParamsNode({
|
|
678
791
|
paramsType,
|
|
679
792
|
paramsCasing,
|
|
680
793
|
pathParamsType,
|
|
@@ -689,15 +802,21 @@ function generateMethod$1({ node, name, tsResolver, zodResolver, baseURL, dataRe
|
|
|
689
802
|
baseURL,
|
|
690
803
|
tsResolver,
|
|
691
804
|
isFormData,
|
|
805
|
+
isMultipleContentTypes,
|
|
806
|
+
hasFormData,
|
|
692
807
|
headers
|
|
693
808
|
});
|
|
694
|
-
const jsdoc = buildJSDoc(
|
|
809
|
+
const jsdoc = buildJSDoc(buildOperationComments(node, {
|
|
810
|
+
link: "urlPath",
|
|
811
|
+
linkPosition: "beforeDeprecated",
|
|
812
|
+
splitLines: true
|
|
813
|
+
}));
|
|
695
814
|
const requestDataLine = buildRequestDataLine({
|
|
696
815
|
parser,
|
|
697
816
|
node,
|
|
698
817
|
zodResolver
|
|
699
818
|
});
|
|
700
|
-
const formDataLine = buildFormDataLine(isFormData, !!node.requestBody?.content?.[0]?.schema);
|
|
819
|
+
const formDataLine = buildFormDataLine(isFormData || isMultipleContentTypes && hasFormData, !!node.requestBody?.content?.[0]?.schema);
|
|
701
820
|
const returnStatement = buildReturnStatement({
|
|
702
821
|
dataReturnType,
|
|
703
822
|
parser,
|
|
@@ -705,7 +824,7 @@ function generateMethod$1({ node, name, tsResolver, zodResolver, baseURL, dataRe
|
|
|
705
824
|
zodResolver
|
|
706
825
|
});
|
|
707
826
|
return `${jsdoc}async ${name}(${paramsSignature}) {\n${[
|
|
708
|
-
|
|
827
|
+
`const { client: request = client, ${isMultipleContentTypes ? `contentType = ${JSON.stringify(contentType)}, ` : ""}...requestConfig } = mergeConfig(this.#config, config)`,
|
|
709
828
|
"",
|
|
710
829
|
requestDataLine,
|
|
711
830
|
formDataLine,
|
|
@@ -742,15 +861,14 @@ ${operations.map(({ node, name: methodName, tsResolver, zodResolver }) => genera
|
|
|
742
861
|
children: [classCode, children]
|
|
743
862
|
});
|
|
744
863
|
}
|
|
745
|
-
ClassClient.getParams = Client.getParams;
|
|
746
864
|
//#endregion
|
|
747
865
|
//#region src/components/WrapperClient.tsx
|
|
748
|
-
function WrapperClient({ name,
|
|
866
|
+
function WrapperClient({ name, controllers, isExportable = true, isIndexable = true }) {
|
|
749
867
|
const classCode = `export class ${name} {
|
|
750
|
-
${
|
|
868
|
+
${controllers.map(({ className, propertyName }) => ` readonly ${propertyName}: ${className}`).join("\n")}
|
|
751
869
|
|
|
752
870
|
constructor(config: Partial<RequestConfig> & { client?: Client } = {}) {
|
|
753
|
-
${
|
|
871
|
+
${controllers.map(({ className, propertyName }) => ` this.${propertyName} = new ${className}(config)`).join("\n")}
|
|
754
872
|
}
|
|
755
873
|
}`;
|
|
756
874
|
return /* @__PURE__ */ jsx(File.Source, {
|
|
@@ -763,33 +881,31 @@ ${classNames.map((className) => ` this.${camelCase(className)} = new ${classN
|
|
|
763
881
|
//#endregion
|
|
764
882
|
//#region src/generators/classClientGenerator.tsx
|
|
765
883
|
function resolveTypeImportNames$1(node, tsResolver) {
|
|
766
|
-
return
|
|
767
|
-
node.requestBody?.content?.[0]?.schema ? tsResolver.resolveDataName(node) : void 0,
|
|
768
|
-
tsResolver.resolveResponseName(node),
|
|
769
|
-
...node.parameters.filter((p) => p.in === "path").map((p) => tsResolver.resolvePathParamsName(node, p)),
|
|
770
|
-
...node.parameters.filter((p) => p.in === "query").map((p) => tsResolver.resolveQueryParamsName(node, p)),
|
|
771
|
-
...node.parameters.filter((p) => p.in === "header").map((p) => tsResolver.resolveHeaderParamsName(node, p)),
|
|
772
|
-
...node.responses.map((res) => tsResolver.resolveResponseStatusName(node, res.statusCode))
|
|
773
|
-
].filter((n) => Boolean(n));
|
|
884
|
+
return resolveOperationTypeNames(node, tsResolver, { order: "body-response-first" });
|
|
774
885
|
}
|
|
775
886
|
__name(resolveTypeImportNames$1, "resolveTypeImportNames");
|
|
776
887
|
function resolveZodImportNames$1(node, zodResolver) {
|
|
777
|
-
return [zodResolver.resolveResponseName?.(node), node.requestBody?.content?.[0]?.schema ? zodResolver.resolveDataName?.(node) :
|
|
888
|
+
return [zodResolver.resolveResponseName?.(node), node.requestBody?.content?.[0]?.schema ? zodResolver.resolveDataName?.(node) : null].filter((n) => Boolean(n));
|
|
778
889
|
}
|
|
779
890
|
__name(resolveZodImportNames$1, "resolveZodImportNames");
|
|
891
|
+
/**
|
|
892
|
+
* Built-in `operations` generator for `@kubb/plugin-client` when
|
|
893
|
+
* `clientType: 'class'`. Emits one class per tag, with one instance method
|
|
894
|
+
* per operation and a shared constructor for request configuration.
|
|
895
|
+
*/
|
|
780
896
|
const classClientGenerator = defineGenerator({
|
|
781
897
|
name: "classClient",
|
|
782
|
-
renderer:
|
|
898
|
+
renderer: jsxRendererSync,
|
|
783
899
|
operations(nodes, ctx) {
|
|
784
|
-
const {
|
|
900
|
+
const { config, driver, resolver, root } = ctx;
|
|
785
901
|
const { output, group, dataReturnType, paramsCasing, paramsType, pathParamsType, parser, importPath, sdk } = ctx.options;
|
|
786
|
-
const baseURL = ctx.options.baseURL ??
|
|
902
|
+
const baseURL = ctx.options.baseURL ?? ctx.meta.baseURL;
|
|
787
903
|
const pluginTs = driver.getPlugin(pluginTsName);
|
|
788
904
|
if (!pluginTs) return null;
|
|
789
905
|
const tsResolver = driver.getResolver(pluginTsName);
|
|
790
906
|
const tsPluginOptions = pluginTs.options;
|
|
791
|
-
const pluginZod = parser === "zod" ? driver.getPlugin(pluginZodName) :
|
|
792
|
-
const zodResolver = pluginZod ? driver.getResolver(pluginZodName) :
|
|
907
|
+
const pluginZod = parser === "zod" ? driver.getPlugin(pluginZodName) : null;
|
|
908
|
+
const zodResolver = pluginZod ? driver.getResolver(pluginZodName) : null;
|
|
793
909
|
function buildOperationData(node) {
|
|
794
910
|
const typeFile = tsResolver.resolveFile({
|
|
795
911
|
name: node.operationId,
|
|
@@ -809,8 +925,8 @@ const classClientGenerator = defineGenerator({
|
|
|
809
925
|
}, {
|
|
810
926
|
root,
|
|
811
927
|
output: pluginZod.options?.output ?? output,
|
|
812
|
-
group: pluginZod.options?.group
|
|
813
|
-
}) :
|
|
928
|
+
group: pluginZod.options?.group ?? void 0
|
|
929
|
+
}) : null;
|
|
814
930
|
return {
|
|
815
931
|
node,
|
|
816
932
|
name: resolver.resolveName(node.operationId),
|
|
@@ -822,26 +938,29 @@ const classClientGenerator = defineGenerator({
|
|
|
822
938
|
}
|
|
823
939
|
const controllers = nodes.reduce((acc, operationNode) => {
|
|
824
940
|
const tag = operationNode.tags[0];
|
|
825
|
-
const groupName = tag ? group?.name?.({ group: camelCase(tag) }) ??
|
|
941
|
+
const groupName = tag ? group?.name?.({ group: camelCase(tag) }) ?? resolver.resolveGroupName(tag) : resolver.resolveGroupName("Client");
|
|
826
942
|
if (!tag && !group) {
|
|
827
|
-
const name = "ApiClient";
|
|
943
|
+
const name = resolver.resolveClassName("ApiClient");
|
|
828
944
|
const file = resolver.resolveFile({
|
|
829
945
|
name,
|
|
830
946
|
extname: ".ts"
|
|
831
947
|
}, {
|
|
832
948
|
root,
|
|
833
949
|
output,
|
|
834
|
-
group
|
|
950
|
+
group: group ?? void 0
|
|
835
951
|
});
|
|
836
952
|
const operationData = buildOperationData(operationNode);
|
|
837
953
|
const previous = acc.find((item) => item.file.path === file.path);
|
|
838
954
|
if (previous) previous.operations.push(operationData);
|
|
839
955
|
else acc.push({
|
|
840
956
|
name,
|
|
957
|
+
propertyName: resolver.resolveClientPropertyName(name),
|
|
841
958
|
file,
|
|
842
959
|
operations: [operationData]
|
|
843
960
|
});
|
|
844
|
-
|
|
961
|
+
return acc;
|
|
962
|
+
}
|
|
963
|
+
if (tag) {
|
|
845
964
|
const name = groupName;
|
|
846
965
|
const file = resolver.resolveFile({
|
|
847
966
|
name,
|
|
@@ -850,13 +969,14 @@ const classClientGenerator = defineGenerator({
|
|
|
850
969
|
}, {
|
|
851
970
|
root,
|
|
852
971
|
output,
|
|
853
|
-
group
|
|
972
|
+
group: group ?? void 0
|
|
854
973
|
});
|
|
855
974
|
const operationData = buildOperationData(operationNode);
|
|
856
975
|
const previous = acc.find((item) => item.file.path === file.path);
|
|
857
976
|
if (previous) previous.operations.push(operationData);
|
|
858
977
|
else acc.push({
|
|
859
978
|
name,
|
|
979
|
+
propertyName: resolver.resolveClientPropertyName(name),
|
|
860
980
|
file,
|
|
861
981
|
operations: [operationData]
|
|
862
982
|
});
|
|
@@ -904,23 +1024,31 @@ const classClientGenerator = defineGenerator({
|
|
|
904
1024
|
zodImportsByFile: /* @__PURE__ */ new Map(),
|
|
905
1025
|
zodFilesByPath: /* @__PURE__ */ new Map()
|
|
906
1026
|
};
|
|
907
|
-
const hasFormData = ops.some((op) => op.node.requestBody?.content?.
|
|
1027
|
+
const hasFormData = ops.some((op) => op.node.requestBody?.content?.some((e) => e.contentType === "multipart/form-data") ?? false);
|
|
908
1028
|
return /* @__PURE__ */ jsxs(File, {
|
|
909
1029
|
baseName: file.baseName,
|
|
910
1030
|
path: file.path,
|
|
911
1031
|
meta: file.meta,
|
|
912
|
-
banner: resolver.resolveBanner(
|
|
1032
|
+
banner: resolver.resolveBanner(ctx.meta, {
|
|
913
1033
|
output,
|
|
914
|
-
config
|
|
1034
|
+
config,
|
|
1035
|
+
file: {
|
|
1036
|
+
path: file.path,
|
|
1037
|
+
baseName: file.baseName
|
|
1038
|
+
}
|
|
915
1039
|
}),
|
|
916
|
-
footer: resolver.resolveFooter(
|
|
1040
|
+
footer: resolver.resolveFooter(ctx.meta, {
|
|
917
1041
|
output,
|
|
918
|
-
config
|
|
1042
|
+
config,
|
|
1043
|
+
file: {
|
|
1044
|
+
path: file.path,
|
|
1045
|
+
baseName: file.baseName
|
|
1046
|
+
}
|
|
919
1047
|
}),
|
|
920
1048
|
children: [
|
|
921
1049
|
importPath ? /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
922
1050
|
/* @__PURE__ */ jsx(File.Import, {
|
|
923
|
-
name: "
|
|
1051
|
+
name: "client",
|
|
924
1052
|
path: importPath
|
|
925
1053
|
}),
|
|
926
1054
|
/* @__PURE__ */ jsx(File.Import, {
|
|
@@ -938,7 +1066,7 @@ const classClientGenerator = defineGenerator({
|
|
|
938
1066
|
})
|
|
939
1067
|
] }) : /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
940
1068
|
/* @__PURE__ */ jsx(File.Import, {
|
|
941
|
-
name: ["
|
|
1069
|
+
name: ["client"],
|
|
942
1070
|
root: file.path,
|
|
943
1071
|
path: path.resolve(root, ".kubb/client.ts")
|
|
944
1072
|
}),
|
|
@@ -1006,19 +1134,27 @@ const classClientGenerator = defineGenerator({
|
|
|
1006
1134
|
}, {
|
|
1007
1135
|
root,
|
|
1008
1136
|
output,
|
|
1009
|
-
group
|
|
1137
|
+
group: group ?? void 0
|
|
1010
1138
|
});
|
|
1011
1139
|
files.push(/* @__PURE__ */ jsxs(File, {
|
|
1012
1140
|
baseName: sdkFile.baseName,
|
|
1013
1141
|
path: sdkFile.path,
|
|
1014
1142
|
meta: sdkFile.meta,
|
|
1015
|
-
banner: resolver.resolveBanner(
|
|
1143
|
+
banner: resolver.resolveBanner(ctx.meta, {
|
|
1016
1144
|
output,
|
|
1017
|
-
config
|
|
1145
|
+
config,
|
|
1146
|
+
file: {
|
|
1147
|
+
path: sdkFile.path,
|
|
1148
|
+
baseName: sdkFile.baseName
|
|
1149
|
+
}
|
|
1018
1150
|
}),
|
|
1019
|
-
footer: resolver.resolveFooter(
|
|
1151
|
+
footer: resolver.resolveFooter(ctx.meta, {
|
|
1020
1152
|
output,
|
|
1021
|
-
config
|
|
1153
|
+
config,
|
|
1154
|
+
file: {
|
|
1155
|
+
path: sdkFile.path,
|
|
1156
|
+
baseName: sdkFile.baseName
|
|
1157
|
+
}
|
|
1022
1158
|
}),
|
|
1023
1159
|
children: [
|
|
1024
1160
|
importPath ? /* @__PURE__ */ jsx(File.Import, {
|
|
@@ -1038,7 +1174,10 @@ const classClientGenerator = defineGenerator({
|
|
|
1038
1174
|
}, name)),
|
|
1039
1175
|
/* @__PURE__ */ jsx(WrapperClient, {
|
|
1040
1176
|
name: sdk.className,
|
|
1041
|
-
|
|
1177
|
+
controllers: controllers.map(({ name, propertyName }) => ({
|
|
1178
|
+
className: name,
|
|
1179
|
+
propertyName
|
|
1180
|
+
}))
|
|
1042
1181
|
})
|
|
1043
1182
|
]
|
|
1044
1183
|
}, sdkFile.path));
|
|
@@ -1048,34 +1187,28 @@ const classClientGenerator = defineGenerator({
|
|
|
1048
1187
|
});
|
|
1049
1188
|
//#endregion
|
|
1050
1189
|
//#region src/generators/clientGenerator.tsx
|
|
1190
|
+
/**
|
|
1191
|
+
* Built-in operation generator for `@kubb/plugin-client`. Emits one async
|
|
1192
|
+
* function per OpenAPI operation, plus the matching URL helper. Used when
|
|
1193
|
+
* `clientType: 'function'` (the default).
|
|
1194
|
+
*/
|
|
1051
1195
|
const clientGenerator = defineGenerator({
|
|
1052
1196
|
name: "client",
|
|
1053
|
-
renderer:
|
|
1197
|
+
renderer: jsxRendererSync,
|
|
1054
1198
|
operation(node, ctx) {
|
|
1055
|
-
const {
|
|
1199
|
+
const { config, driver, resolver, root } = ctx;
|
|
1056
1200
|
const { output, urlType, dataReturnType, paramsCasing, paramsType, pathParamsType, parser, importPath, group } = ctx.options;
|
|
1057
|
-
const baseURL = ctx.options.baseURL ??
|
|
1201
|
+
const baseURL = ctx.options.baseURL ?? ctx.meta.baseURL;
|
|
1058
1202
|
const pluginTs = driver.getPlugin(pluginTsName);
|
|
1059
1203
|
if (!pluginTs) return null;
|
|
1060
1204
|
const tsResolver = driver.getResolver(pluginTsName);
|
|
1061
|
-
const pluginZod = parser === "zod" ? driver.getPlugin(pluginZodName) :
|
|
1062
|
-
const zodResolver = pluginZod ? driver.getResolver(pluginZodName) :
|
|
1063
|
-
const
|
|
1064
|
-
const
|
|
1065
|
-
const queryParams = casedParams.filter((p) => p.in === "query");
|
|
1066
|
-
const headerParams = casedParams.filter((p) => p.in === "header");
|
|
1067
|
-
const importedTypeNames = [
|
|
1068
|
-
...pathParams.map((p) => tsResolver.resolvePathParamsName(node, p)),
|
|
1069
|
-
...queryParams.map((p) => tsResolver.resolveQueryParamsName(node, p)),
|
|
1070
|
-
...headerParams.map((p) => tsResolver.resolveHeaderParamsName(node, p)),
|
|
1071
|
-
node.requestBody?.content?.[0]?.schema ? tsResolver.resolveDataName(node) : void 0,
|
|
1072
|
-
tsResolver.resolveResponseName(node),
|
|
1073
|
-
...node.responses.map((res) => tsResolver.resolveResponseStatusName(node, res.statusCode))
|
|
1074
|
-
].filter(Boolean);
|
|
1075
|
-
const importedZodNames = zodResolver && parser === "zod" ? [zodResolver.resolveResponseName?.(node), node.requestBody?.content?.[0]?.schema ? zodResolver.resolveDataName?.(node) : void 0].filter(Boolean) : [];
|
|
1205
|
+
const pluginZod = parser === "zod" ? driver.getPlugin(pluginZodName) : null;
|
|
1206
|
+
const zodResolver = pluginZod ? driver.getResolver(pluginZodName) : null;
|
|
1207
|
+
const importedTypeNames = resolveOperationTypeNames(node, tsResolver, { paramsCasing });
|
|
1208
|
+
const importedZodNames = zodResolver && parser === "zod" ? [zodResolver.resolveResponseName?.(node), node.requestBody?.content?.[0]?.schema ? zodResolver.resolveDataName?.(node) : null].filter((name) => Boolean(name)) : [];
|
|
1076
1209
|
const meta = {
|
|
1077
1210
|
name: resolver.resolveName(node.operationId),
|
|
1078
|
-
urlName:
|
|
1211
|
+
urlName: resolver.resolveUrlName(node),
|
|
1079
1212
|
file: resolver.resolveFile({
|
|
1080
1213
|
name: node.operationId,
|
|
1081
1214
|
extname: ".ts",
|
|
@@ -1084,7 +1217,7 @@ const clientGenerator = defineGenerator({
|
|
|
1084
1217
|
}, {
|
|
1085
1218
|
root,
|
|
1086
1219
|
output,
|
|
1087
|
-
group
|
|
1220
|
+
group: group ?? void 0
|
|
1088
1221
|
}),
|
|
1089
1222
|
fileTs: tsResolver.resolveFile({
|
|
1090
1223
|
name: node.operationId,
|
|
@@ -1094,7 +1227,7 @@ const clientGenerator = defineGenerator({
|
|
|
1094
1227
|
}, {
|
|
1095
1228
|
root,
|
|
1096
1229
|
output: pluginTs.options?.output ?? output,
|
|
1097
|
-
group: pluginTs.options?.group
|
|
1230
|
+
group: pluginTs.options?.group ?? void 0
|
|
1098
1231
|
}),
|
|
1099
1232
|
fileZod: zodResolver && pluginZod?.options ? zodResolver.resolveFile({
|
|
1100
1233
|
name: node.operationId,
|
|
@@ -1104,25 +1237,33 @@ const clientGenerator = defineGenerator({
|
|
|
1104
1237
|
}, {
|
|
1105
1238
|
root,
|
|
1106
1239
|
output: pluginZod.options.output ?? output,
|
|
1107
|
-
group: pluginZod.options?.group
|
|
1108
|
-
}) :
|
|
1240
|
+
group: pluginZod.options?.group ?? void 0
|
|
1241
|
+
}) : null
|
|
1109
1242
|
};
|
|
1110
|
-
const
|
|
1243
|
+
const hasFormData = node.requestBody?.content?.some((e) => e.contentType === "multipart/form-data") ?? false;
|
|
1111
1244
|
return /* @__PURE__ */ jsxs(File, {
|
|
1112
1245
|
baseName: meta.file.baseName,
|
|
1113
1246
|
path: meta.file.path,
|
|
1114
1247
|
meta: meta.file.meta,
|
|
1115
|
-
banner: resolver.resolveBanner(
|
|
1248
|
+
banner: resolver.resolveBanner(ctx.meta, {
|
|
1116
1249
|
output,
|
|
1117
|
-
config
|
|
1250
|
+
config,
|
|
1251
|
+
file: {
|
|
1252
|
+
path: meta.file.path,
|
|
1253
|
+
baseName: meta.file.baseName
|
|
1254
|
+
}
|
|
1118
1255
|
}),
|
|
1119
|
-
footer: resolver.resolveFooter(
|
|
1256
|
+
footer: resolver.resolveFooter(ctx.meta, {
|
|
1120
1257
|
output,
|
|
1121
|
-
config
|
|
1258
|
+
config,
|
|
1259
|
+
file: {
|
|
1260
|
+
path: meta.file.path,
|
|
1261
|
+
baseName: meta.file.baseName
|
|
1262
|
+
}
|
|
1122
1263
|
}),
|
|
1123
1264
|
children: [
|
|
1124
1265
|
importPath ? /* @__PURE__ */ jsxs(Fragment, { children: [/* @__PURE__ */ jsx(File.Import, {
|
|
1125
|
-
name: "
|
|
1266
|
+
name: "client",
|
|
1126
1267
|
path: importPath
|
|
1127
1268
|
}), /* @__PURE__ */ jsx(File.Import, {
|
|
1128
1269
|
name: [
|
|
@@ -1133,7 +1274,7 @@ const clientGenerator = defineGenerator({
|
|
|
1133
1274
|
path: importPath,
|
|
1134
1275
|
isTypeOnly: true
|
|
1135
1276
|
})] }) : /* @__PURE__ */ jsxs(Fragment, { children: [/* @__PURE__ */ jsx(File.Import, {
|
|
1136
|
-
name: ["
|
|
1277
|
+
name: ["client"],
|
|
1137
1278
|
root: meta.file.path,
|
|
1138
1279
|
path: path.resolve(root, ".kubb/client.ts")
|
|
1139
1280
|
}), /* @__PURE__ */ jsx(File.Import, {
|
|
@@ -1146,7 +1287,7 @@ const clientGenerator = defineGenerator({
|
|
|
1146
1287
|
path: path.resolve(root, ".kubb/client.ts"),
|
|
1147
1288
|
isTypeOnly: true
|
|
1148
1289
|
})] }),
|
|
1149
|
-
|
|
1290
|
+
hasFormData && /* @__PURE__ */ jsx(File.Import, {
|
|
1150
1291
|
name: ["buildFormData"],
|
|
1151
1292
|
root: meta.file.path,
|
|
1152
1293
|
path: path.resolve(root, ".kubb/config.ts")
|
|
@@ -1192,16 +1333,22 @@ const clientGenerator = defineGenerator({
|
|
|
1192
1333
|
});
|
|
1193
1334
|
//#endregion
|
|
1194
1335
|
//#region src/generators/groupedClientGenerator.tsx
|
|
1336
|
+
/**
|
|
1337
|
+
* Emits one aggregate file per tag/group when `group` is configured. Each
|
|
1338
|
+
* file re-exports every client function for that group, so callers can
|
|
1339
|
+
* `import { petController } from './gen/clients'` instead of importing
|
|
1340
|
+
* each operation individually.
|
|
1341
|
+
*/
|
|
1195
1342
|
const groupedClientGenerator = defineGenerator({
|
|
1196
1343
|
name: "groupedClient",
|
|
1197
|
-
renderer:
|
|
1344
|
+
renderer: jsxRendererSync,
|
|
1198
1345
|
operations(nodes, ctx) {
|
|
1199
|
-
const { config, resolver,
|
|
1346
|
+
const { config, resolver, root } = ctx;
|
|
1200
1347
|
const { output, group } = ctx.options;
|
|
1201
1348
|
return /* @__PURE__ */ jsx(Fragment, { children: nodes.reduce((acc, operationNode) => {
|
|
1202
1349
|
if (group?.type === "tag") {
|
|
1203
1350
|
const tag = operationNode.tags[0];
|
|
1204
|
-
const name = tag ? group?.name?.({ group: camelCase(tag) }) :
|
|
1351
|
+
const name = tag ? group?.name?.({ group: camelCase(tag) }) : null;
|
|
1205
1352
|
if (!tag || !name) return acc;
|
|
1206
1353
|
const file = resolver.resolveFile({
|
|
1207
1354
|
name,
|
|
@@ -1210,7 +1357,7 @@ const groupedClientGenerator = defineGenerator({
|
|
|
1210
1357
|
}, {
|
|
1211
1358
|
root,
|
|
1212
1359
|
output,
|
|
1213
|
-
group
|
|
1360
|
+
group: group ?? void 0
|
|
1214
1361
|
});
|
|
1215
1362
|
const clientFile = resolver.resolveFile({
|
|
1216
1363
|
name: operationNode.operationId,
|
|
@@ -1220,7 +1367,7 @@ const groupedClientGenerator = defineGenerator({
|
|
|
1220
1367
|
}, {
|
|
1221
1368
|
root,
|
|
1222
1369
|
output,
|
|
1223
|
-
group
|
|
1370
|
+
group: group ?? void 0
|
|
1224
1371
|
});
|
|
1225
1372
|
const client = {
|
|
1226
1373
|
name: resolver.resolveName(operationNode.operationId),
|
|
@@ -1240,13 +1387,23 @@ const groupedClientGenerator = defineGenerator({
|
|
|
1240
1387
|
baseName: file.baseName,
|
|
1241
1388
|
path: file.path,
|
|
1242
1389
|
meta: file.meta,
|
|
1243
|
-
banner: resolver.resolveBanner(
|
|
1390
|
+
banner: resolver.resolveBanner(ctx.meta, {
|
|
1244
1391
|
output,
|
|
1245
|
-
config
|
|
1392
|
+
config,
|
|
1393
|
+
file: {
|
|
1394
|
+
path: file.path,
|
|
1395
|
+
baseName: file.baseName,
|
|
1396
|
+
isAggregation: true
|
|
1397
|
+
}
|
|
1246
1398
|
}),
|
|
1247
|
-
footer: resolver.resolveFooter(
|
|
1399
|
+
footer: resolver.resolveFooter(ctx.meta, {
|
|
1248
1400
|
output,
|
|
1249
|
-
config
|
|
1401
|
+
config,
|
|
1402
|
+
file: {
|
|
1403
|
+
path: file.path,
|
|
1404
|
+
baseName: file.baseName,
|
|
1405
|
+
isAggregation: true
|
|
1406
|
+
}
|
|
1250
1407
|
}),
|
|
1251
1408
|
children: [clients.map((client) => /* @__PURE__ */ jsx(File.Import, {
|
|
1252
1409
|
name: [client.name],
|
|
@@ -1289,11 +1446,17 @@ function Operations({ name, nodes }) {
|
|
|
1289
1446
|
}
|
|
1290
1447
|
//#endregion
|
|
1291
1448
|
//#region src/generators/operationsGenerator.tsx
|
|
1449
|
+
/**
|
|
1450
|
+
* Generates an `operations.ts` file that re-exports every operation grouped
|
|
1451
|
+
* by HTTP method. Enabled when `pluginClient({ operations: true })`. Useful
|
|
1452
|
+
* for building meta-tooling on top of the generated client (route
|
|
1453
|
+
* registries, API explorers).
|
|
1454
|
+
*/
|
|
1292
1455
|
const operationsGenerator = defineGenerator({
|
|
1293
1456
|
name: "client",
|
|
1294
|
-
renderer:
|
|
1457
|
+
renderer: jsxRendererSync,
|
|
1295
1458
|
operations(nodes, ctx) {
|
|
1296
|
-
const { config, resolver,
|
|
1459
|
+
const { config, resolver, root } = ctx;
|
|
1297
1460
|
const { output, group } = ctx.options;
|
|
1298
1461
|
const name = "operations";
|
|
1299
1462
|
const file = resolver.resolveFile({
|
|
@@ -1302,19 +1465,27 @@ const operationsGenerator = defineGenerator({
|
|
|
1302
1465
|
}, {
|
|
1303
1466
|
root,
|
|
1304
1467
|
output,
|
|
1305
|
-
group
|
|
1468
|
+
group: group ?? void 0
|
|
1306
1469
|
});
|
|
1307
1470
|
return /* @__PURE__ */ jsx(File, {
|
|
1308
1471
|
baseName: file.baseName,
|
|
1309
1472
|
path: file.path,
|
|
1310
1473
|
meta: file.meta,
|
|
1311
|
-
banner: resolver.resolveBanner(
|
|
1474
|
+
banner: resolver.resolveBanner(ctx.meta, {
|
|
1312
1475
|
output,
|
|
1313
|
-
config
|
|
1476
|
+
config,
|
|
1477
|
+
file: {
|
|
1478
|
+
path: file.path,
|
|
1479
|
+
baseName: file.baseName
|
|
1480
|
+
}
|
|
1314
1481
|
}),
|
|
1315
|
-
footer: resolver.resolveFooter(
|
|
1482
|
+
footer: resolver.resolveFooter(ctx.meta, {
|
|
1316
1483
|
output,
|
|
1317
|
-
config
|
|
1484
|
+
config,
|
|
1485
|
+
file: {
|
|
1486
|
+
path: file.path,
|
|
1487
|
+
baseName: file.baseName
|
|
1488
|
+
}
|
|
1318
1489
|
}),
|
|
1319
1490
|
children: /* @__PURE__ */ jsx(Operations, {
|
|
1320
1491
|
name,
|
|
@@ -1328,11 +1499,13 @@ const operationsGenerator = defineGenerator({
|
|
|
1328
1499
|
const declarationPrinter = functionPrinter({ mode: "declaration" });
|
|
1329
1500
|
function generateMethod({ node, name, tsResolver, zodResolver, baseURL, dataReturnType, parser, paramsType, paramsCasing, pathParamsType }) {
|
|
1330
1501
|
const path = new URLPath(node.path, { casing: paramsCasing });
|
|
1331
|
-
const contentType = node
|
|
1332
|
-
const isFormData = contentType === "multipart/form-data";
|
|
1333
|
-
const
|
|
1502
|
+
const { defaultContentType: contentType, isMultipleContentTypes, hasFormData } = getContentTypeInfo(node);
|
|
1503
|
+
const isFormData = !isMultipleContentTypes && contentType === "multipart/form-data";
|
|
1504
|
+
const { header: headerParams } = getOperationParameters(node);
|
|
1505
|
+
const headerParamsName = headerParams.length > 0 ? tsResolver.resolveHeaderParamsName(node, headerParams[0]) : null;
|
|
1506
|
+
const headers = isMultipleContentTypes ? headerParamsName ? ["...headers"] : [] : buildHeaders(contentType, !!headerParamsName);
|
|
1334
1507
|
const generics = buildGenerics(node, tsResolver);
|
|
1335
|
-
const paramsNode =
|
|
1508
|
+
const paramsNode = buildClientParamsNode({
|
|
1336
1509
|
paramsType,
|
|
1337
1510
|
paramsCasing,
|
|
1338
1511
|
pathParamsType,
|
|
@@ -1347,15 +1520,21 @@ function generateMethod({ node, name, tsResolver, zodResolver, baseURL, dataRetu
|
|
|
1347
1520
|
baseURL,
|
|
1348
1521
|
tsResolver,
|
|
1349
1522
|
isFormData,
|
|
1523
|
+
isMultipleContentTypes,
|
|
1524
|
+
hasFormData,
|
|
1350
1525
|
headers
|
|
1351
1526
|
});
|
|
1352
|
-
const jsdoc = buildJSDoc(
|
|
1527
|
+
const jsdoc = buildJSDoc(buildOperationComments(node, {
|
|
1528
|
+
link: "urlPath",
|
|
1529
|
+
linkPosition: "beforeDeprecated",
|
|
1530
|
+
splitLines: true
|
|
1531
|
+
}));
|
|
1353
1532
|
const requestDataLine = buildRequestDataLine({
|
|
1354
1533
|
parser,
|
|
1355
1534
|
node,
|
|
1356
1535
|
zodResolver
|
|
1357
1536
|
});
|
|
1358
|
-
const formDataLine = buildFormDataLine(isFormData, !!node.requestBody?.content?.[0]?.schema);
|
|
1537
|
+
const formDataLine = buildFormDataLine(isFormData || isMultipleContentTypes && hasFormData, !!node.requestBody?.content?.[0]?.schema);
|
|
1359
1538
|
const returnStatement = buildReturnStatement({
|
|
1360
1539
|
dataReturnType,
|
|
1361
1540
|
parser,
|
|
@@ -1363,7 +1542,7 @@ function generateMethod({ node, name, tsResolver, zodResolver, baseURL, dataRetu
|
|
|
1363
1542
|
zodResolver
|
|
1364
1543
|
});
|
|
1365
1544
|
return `${jsdoc} static async ${name}(${paramsSignature}) {\n${[
|
|
1366
|
-
|
|
1545
|
+
`const { client: request = client, ${isMultipleContentTypes ? `contentType = ${JSON.stringify(contentType)}, ` : ""}...requestConfig } = mergeConfig(this.#config, config)`,
|
|
1367
1546
|
"",
|
|
1368
1547
|
requestDataLine,
|
|
1369
1548
|
formDataLine,
|
|
@@ -1391,35 +1570,33 @@ function StaticClassClient({ name, isExportable = true, isIndexable = true, oper
|
|
|
1391
1570
|
children: [classCode, children]
|
|
1392
1571
|
});
|
|
1393
1572
|
}
|
|
1394
|
-
StaticClassClient.getParams = Client.getParams;
|
|
1395
1573
|
//#endregion
|
|
1396
1574
|
//#region src/generators/staticClassClientGenerator.tsx
|
|
1397
1575
|
function resolveTypeImportNames(node, tsResolver) {
|
|
1398
|
-
return
|
|
1399
|
-
node.requestBody?.content?.[0]?.schema ? tsResolver.resolveDataName(node) : void 0,
|
|
1400
|
-
tsResolver.resolveResponseName(node),
|
|
1401
|
-
...node.parameters.filter((p) => p.in === "path").map((p) => tsResolver.resolvePathParamsName(node, p)),
|
|
1402
|
-
...node.parameters.filter((p) => p.in === "query").map((p) => tsResolver.resolveQueryParamsName(node, p)),
|
|
1403
|
-
...node.parameters.filter((p) => p.in === "header").map((p) => tsResolver.resolveHeaderParamsName(node, p)),
|
|
1404
|
-
...node.responses.map((res) => tsResolver.resolveResponseStatusName(node, res.statusCode))
|
|
1405
|
-
].filter((n) => Boolean(n));
|
|
1576
|
+
return resolveOperationTypeNames(node, tsResolver, { order: "body-response-first" });
|
|
1406
1577
|
}
|
|
1407
1578
|
function resolveZodImportNames(node, zodResolver) {
|
|
1408
|
-
return [zodResolver.resolveResponseName?.(node), node.requestBody?.content?.[0]?.schema ? zodResolver.resolveDataName?.(node) :
|
|
1579
|
+
return [zodResolver.resolveResponseName?.(node), node.requestBody?.content?.[0]?.schema ? zodResolver.resolveDataName?.(node) : null].filter((n) => Boolean(n));
|
|
1409
1580
|
}
|
|
1581
|
+
/**
|
|
1582
|
+
* Built-in `operations` generator for `@kubb/plugin-client` when
|
|
1583
|
+
* `clientType: 'staticClass'`. Emits one class per tag, with a static method
|
|
1584
|
+
* per operation so callers can use `Pet.getPetById(...)` without
|
|
1585
|
+
* instantiating the class.
|
|
1586
|
+
*/
|
|
1410
1587
|
const staticClassClientGenerator = defineGenerator({
|
|
1411
1588
|
name: "staticClassClient",
|
|
1412
|
-
renderer:
|
|
1589
|
+
renderer: jsxRendererSync,
|
|
1413
1590
|
operations(nodes, ctx) {
|
|
1414
|
-
const {
|
|
1591
|
+
const { config, driver, resolver, root } = ctx;
|
|
1415
1592
|
const { output, group, dataReturnType, paramsCasing, paramsType, pathParamsType, parser, importPath } = ctx.options;
|
|
1416
|
-
const baseURL = ctx.options.baseURL ??
|
|
1593
|
+
const baseURL = ctx.options.baseURL ?? ctx.meta.baseURL;
|
|
1417
1594
|
const pluginTs = driver.getPlugin(pluginTsName);
|
|
1418
1595
|
if (!pluginTs) return null;
|
|
1419
1596
|
const tsResolver = driver.getResolver(pluginTsName);
|
|
1420
1597
|
const tsPluginOptions = pluginTs.options;
|
|
1421
|
-
const pluginZod = parser === "zod" ? driver.getPlugin(pluginZodName) :
|
|
1422
|
-
const zodResolver = pluginZod ? driver.getResolver(pluginZodName) :
|
|
1598
|
+
const pluginZod = parser === "zod" ? driver.getPlugin(pluginZodName) : null;
|
|
1599
|
+
const zodResolver = pluginZod ? driver.getResolver(pluginZodName) : null;
|
|
1423
1600
|
function buildOperationData(node) {
|
|
1424
1601
|
const typeFile = tsResolver.resolveFile({
|
|
1425
1602
|
name: node.operationId,
|
|
@@ -1439,8 +1616,8 @@ const staticClassClientGenerator = defineGenerator({
|
|
|
1439
1616
|
}, {
|
|
1440
1617
|
root,
|
|
1441
1618
|
output: pluginZod.options?.output ?? output,
|
|
1442
|
-
group: pluginZod.options?.group
|
|
1443
|
-
}) :
|
|
1619
|
+
group: pluginZod.options?.group ?? void 0
|
|
1620
|
+
}) : null;
|
|
1444
1621
|
return {
|
|
1445
1622
|
node,
|
|
1446
1623
|
name: resolver.resolveName(node.operationId),
|
|
@@ -1452,16 +1629,16 @@ const staticClassClientGenerator = defineGenerator({
|
|
|
1452
1629
|
}
|
|
1453
1630
|
const controllers = nodes.reduce((acc, operationNode) => {
|
|
1454
1631
|
const tag = operationNode.tags[0];
|
|
1455
|
-
const groupName = tag ? group?.name?.({ group: camelCase(tag) }) ??
|
|
1632
|
+
const groupName = tag ? group?.name?.({ group: camelCase(tag) }) ?? resolver.resolveGroupName(tag) : resolver.resolveGroupName("Client");
|
|
1456
1633
|
if (!tag && !group) {
|
|
1457
|
-
const name = "ApiClient";
|
|
1634
|
+
const name = resolver.resolveClassName("ApiClient");
|
|
1458
1635
|
const file = resolver.resolveFile({
|
|
1459
1636
|
name,
|
|
1460
1637
|
extname: ".ts"
|
|
1461
1638
|
}, {
|
|
1462
1639
|
root,
|
|
1463
1640
|
output,
|
|
1464
|
-
group
|
|
1641
|
+
group: group ?? void 0
|
|
1465
1642
|
});
|
|
1466
1643
|
const operationData = buildOperationData(operationNode);
|
|
1467
1644
|
const previous = acc.find((item) => item.file.path === file.path);
|
|
@@ -1471,7 +1648,9 @@ const staticClassClientGenerator = defineGenerator({
|
|
|
1471
1648
|
file,
|
|
1472
1649
|
operations: [operationData]
|
|
1473
1650
|
});
|
|
1474
|
-
|
|
1651
|
+
return acc;
|
|
1652
|
+
}
|
|
1653
|
+
if (tag) {
|
|
1475
1654
|
const name = groupName;
|
|
1476
1655
|
const file = resolver.resolveFile({
|
|
1477
1656
|
name,
|
|
@@ -1480,7 +1659,7 @@ const staticClassClientGenerator = defineGenerator({
|
|
|
1480
1659
|
}, {
|
|
1481
1660
|
root,
|
|
1482
1661
|
output,
|
|
1483
|
-
group
|
|
1662
|
+
group: group ?? void 0
|
|
1484
1663
|
});
|
|
1485
1664
|
const operationData = buildOperationData(operationNode);
|
|
1486
1665
|
const previous = acc.find((item) => item.file.path === file.path);
|
|
@@ -1534,23 +1713,31 @@ const staticClassClientGenerator = defineGenerator({
|
|
|
1534
1713
|
zodImportsByFile: /* @__PURE__ */ new Map(),
|
|
1535
1714
|
zodFilesByPath: /* @__PURE__ */ new Map()
|
|
1536
1715
|
};
|
|
1537
|
-
const hasFormData = ops.some((op) => op.node.requestBody?.content?.
|
|
1716
|
+
const hasFormData = ops.some((op) => op.node.requestBody?.content?.some((e) => e.contentType === "multipart/form-data") ?? false);
|
|
1538
1717
|
return /* @__PURE__ */ jsxs(File, {
|
|
1539
1718
|
baseName: file.baseName,
|
|
1540
1719
|
path: file.path,
|
|
1541
1720
|
meta: file.meta,
|
|
1542
|
-
banner: resolver.resolveBanner(
|
|
1721
|
+
banner: resolver.resolveBanner(ctx.meta, {
|
|
1543
1722
|
output,
|
|
1544
|
-
config
|
|
1723
|
+
config,
|
|
1724
|
+
file: {
|
|
1725
|
+
path: file.path,
|
|
1726
|
+
baseName: file.baseName
|
|
1727
|
+
}
|
|
1545
1728
|
}),
|
|
1546
|
-
footer: resolver.resolveFooter(
|
|
1729
|
+
footer: resolver.resolveFooter(ctx.meta, {
|
|
1547
1730
|
output,
|
|
1548
|
-
config
|
|
1731
|
+
config,
|
|
1732
|
+
file: {
|
|
1733
|
+
path: file.path,
|
|
1734
|
+
baseName: file.baseName
|
|
1735
|
+
}
|
|
1549
1736
|
}),
|
|
1550
1737
|
children: [
|
|
1551
1738
|
importPath ? /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
1552
1739
|
/* @__PURE__ */ jsx(File.Import, {
|
|
1553
|
-
name: "
|
|
1740
|
+
name: "client",
|
|
1554
1741
|
path: importPath
|
|
1555
1742
|
}),
|
|
1556
1743
|
/* @__PURE__ */ jsx(File.Import, {
|
|
@@ -1568,7 +1755,7 @@ const staticClassClientGenerator = defineGenerator({
|
|
|
1568
1755
|
})
|
|
1569
1756
|
] }) : /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
1570
1757
|
/* @__PURE__ */ jsx(File.Import, {
|
|
1571
|
-
name: ["
|
|
1758
|
+
name: ["client"],
|
|
1572
1759
|
root: file.path,
|
|
1573
1760
|
path: path.resolve(root, ".kubb/client.ts")
|
|
1574
1761
|
}),
|
|
@@ -1634,39 +1821,75 @@ const staticClassClientGenerator = defineGenerator({
|
|
|
1634
1821
|
//#endregion
|
|
1635
1822
|
//#region src/resolvers/resolverClient.ts
|
|
1636
1823
|
/**
|
|
1637
|
-
*
|
|
1824
|
+
* Default resolver used by `@kubb/plugin-client`. Decides the names and file
|
|
1825
|
+
* paths for every generated client function or class. Functions and files use
|
|
1826
|
+
* camelCase; classes and tag groups use PascalCase.
|
|
1638
1827
|
*
|
|
1639
|
-
*
|
|
1828
|
+
* @example Resolve client function and class names
|
|
1829
|
+
* ```ts
|
|
1830
|
+
* import { resolverClient } from '@kubb/plugin-client'
|
|
1640
1831
|
*
|
|
1641
|
-
*
|
|
1642
|
-
*
|
|
1832
|
+
* resolverClient.default('list pets', 'function') // 'listPets'
|
|
1833
|
+
* resolverClient.resolveClassName('pet') // 'Pet'
|
|
1834
|
+
* resolverClient.resolveUrlName(operationNode) // 'getShowPetByIdUrl'
|
|
1835
|
+
* ```
|
|
1643
1836
|
*/
|
|
1644
|
-
const resolverClient = defineResolver((
|
|
1837
|
+
const resolverClient = defineResolver(() => ({
|
|
1645
1838
|
name: "default",
|
|
1646
1839
|
pluginName: "plugin-client",
|
|
1647
1840
|
default(name, type) {
|
|
1648
|
-
|
|
1841
|
+
const resolved = camelCase(name, { isFile: type === "file" });
|
|
1842
|
+
return type === "file" ? resolved : ensureValidVarName(resolved);
|
|
1649
1843
|
},
|
|
1650
1844
|
resolveName(name) {
|
|
1651
|
-
return
|
|
1845
|
+
return this.default(name, "function");
|
|
1846
|
+
},
|
|
1847
|
+
resolvePathName(name, type) {
|
|
1848
|
+
return this.default(name, type);
|
|
1849
|
+
},
|
|
1850
|
+
resolveClassName(name) {
|
|
1851
|
+
return ensureValidVarName(pascalCase(name));
|
|
1852
|
+
},
|
|
1853
|
+
resolveGroupName(name) {
|
|
1854
|
+
return ensureValidVarName(pascalCase(name));
|
|
1855
|
+
},
|
|
1856
|
+
resolveClientPropertyName(name) {
|
|
1857
|
+
return ensureValidVarName(camelCase(name));
|
|
1858
|
+
},
|
|
1859
|
+
resolveUrlName(node) {
|
|
1860
|
+
const name = this.resolveName(node.operationId);
|
|
1861
|
+
return `get${name.charAt(0).toUpperCase()}${name.slice(1)}Url`;
|
|
1652
1862
|
}
|
|
1653
1863
|
}));
|
|
1654
1864
|
//#endregion
|
|
1655
1865
|
//#region src/plugin.ts
|
|
1656
1866
|
/**
|
|
1657
|
-
* Canonical plugin name for `@kubb/plugin-client
|
|
1867
|
+
* Canonical plugin name for `@kubb/plugin-client`. Used for driver lookups and
|
|
1868
|
+
* cross-plugin dependency references.
|
|
1658
1869
|
*/
|
|
1659
1870
|
const pluginClientName = "plugin-client";
|
|
1660
1871
|
/**
|
|
1661
|
-
* Generates
|
|
1662
|
-
*
|
|
1663
|
-
*
|
|
1872
|
+
* Generates one HTTP client function per OpenAPI operation. Each function has
|
|
1873
|
+
* typed path params, query params, body, and response, so callers use the API
|
|
1874
|
+
* like any other typed function. Ships with `axios` and `fetch` runtimes; bring
|
|
1875
|
+
* your own by setting `importPath`.
|
|
1664
1876
|
*
|
|
1665
|
-
* @example
|
|
1877
|
+
* @example
|
|
1666
1878
|
* ```ts
|
|
1667
|
-
* import
|
|
1879
|
+
* import { defineConfig } from 'kubb'
|
|
1880
|
+
* import { pluginTs } from '@kubb/plugin-ts'
|
|
1881
|
+
* import { pluginClient } from '@kubb/plugin-client'
|
|
1882
|
+
*
|
|
1668
1883
|
* export default defineConfig({
|
|
1669
|
-
*
|
|
1884
|
+
* input: { path: './petStore.yaml' },
|
|
1885
|
+
* output: { path: './src/gen' },
|
|
1886
|
+
* plugins: [
|
|
1887
|
+
* pluginTs(),
|
|
1888
|
+
* pluginClient({
|
|
1889
|
+
* output: { path: './clients' },
|
|
1890
|
+
* client: 'fetch',
|
|
1891
|
+
* }),
|
|
1892
|
+
* ],
|
|
1670
1893
|
* })
|
|
1671
1894
|
* ```
|
|
1672
1895
|
*/
|
|
@@ -1678,8 +1901,8 @@ const pluginClient = definePlugin((options) => {
|
|
|
1678
1901
|
const resolvedImportPath = importPath ?? (!bundle ? `@kubb/plugin-client/clients/${client}` : void 0);
|
|
1679
1902
|
const selectedGenerators = options.generators ?? [
|
|
1680
1903
|
clientType === "staticClass" ? staticClassClientGenerator : clientType === "class" ? classClientGenerator : clientGenerator,
|
|
1681
|
-
group && clientType === "function" ? groupedClientGenerator :
|
|
1682
|
-
operations ? operationsGenerator :
|
|
1904
|
+
group && clientType === "function" ? groupedClientGenerator : null,
|
|
1905
|
+
operations ? operationsGenerator : null
|
|
1683
1906
|
].filter((x) => Boolean(x));
|
|
1684
1907
|
const groupConfig = group ? {
|
|
1685
1908
|
...group,
|
|
@@ -1687,11 +1910,11 @@ const pluginClient = definePlugin((options) => {
|
|
|
1687
1910
|
if (group.type === "path") return `${ctx.group.split("/")[1]}`;
|
|
1688
1911
|
return `${camelCase(ctx.group)}Controller`;
|
|
1689
1912
|
}
|
|
1690
|
-
} :
|
|
1913
|
+
} : null;
|
|
1691
1914
|
return {
|
|
1692
1915
|
name: pluginClientName,
|
|
1693
1916
|
options,
|
|
1694
|
-
dependencies: [pluginTsName, parser === "zod" ? pluginZodName :
|
|
1917
|
+
dependencies: [pluginTsName, parser === "zod" ? pluginZodName : null].filter((dependency) => Boolean(dependency)),
|
|
1695
1918
|
hooks: { "kubb:plugin:setup"(ctx) {
|
|
1696
1919
|
const resolver = userResolver ? {
|
|
1697
1920
|
...resolverClient,
|