@kubb/plugin-client 5.0.0-beta.30 → 5.0.0-beta.31

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.d.ts CHANGED
@@ -170,12 +170,13 @@ type Options = {
170
170
  paramsCasing?: 'camelcase';
171
171
  /**
172
172
  * Validator applied to response bodies before they are returned to the caller.
173
- * - `'client'` — no validation. Trusts the API.
173
+ * - `false` (default) — no validation. The client has no runtime parser; the response is
174
+ * returned as-is, cast to the generated TypeScript type.
174
175
  * - `'zod'` — pipes responses through schemas from `@kubb/plugin-zod`.
175
176
  *
176
- * @default 'client'
177
+ * @default false
177
178
  */
178
- parser?: 'client' | 'zod';
179
+ parser?: false | 'zod';
179
180
  /**
180
181
  * Shape of the generated client.
181
182
  * - `'function'` — one standalone async function per operation.
package/dist/index.js CHANGED
@@ -354,11 +354,30 @@ var URLPath = class {
354
354
  };
355
355
  //#endregion
356
356
  //#region ../../internals/shared/src/operation.ts
357
+ /**
358
+ * Builds the `ResolverFileParams` every operation generator passes to
359
+ * `resolver.resolveFile`: a file named `name`, tagged by the operation's first
360
+ * tag (or `'default'`), at the operation's path. Centralizes the entry object
361
+ * that was repeated at dozens of call sites across the client and query plugins.
362
+ *
363
+ * @example
364
+ * ```ts
365
+ * resolver.resolveFile(operationFileEntry(node, node.operationId), { root, output, group })
366
+ * ```
367
+ */
368
+ function operationFileEntry(node, name, extname = ".ts") {
369
+ return {
370
+ name,
371
+ extname,
372
+ tag: node.tags[0] ?? "default",
373
+ path: node.path
374
+ };
375
+ }
357
376
  function getOperationLink(node, link) {
358
377
  if (!link) return null;
359
378
  if (typeof link === "function") return link(node) ?? null;
360
379
  if (link === "urlPath") return node.path ? `{@link ${new URLPath(node.path).URL}}` : null;
361
- return `{@link ${node.path.replaceAll("{", ":").replaceAll("}", "")}}`;
380
+ return node.path ? `{@link ${node.path.replaceAll("{", ":").replaceAll("}", "")}}` : null;
362
381
  }
363
382
  function getContentTypeInfo(node) {
364
383
  const contentTypes = node.requestBody?.content?.map((e) => e.contentType) ?? [];
@@ -371,6 +390,22 @@ function getContentTypeInfo(node) {
371
390
  hasFormData: contentTypes.some((ct) => ct === "multipart/form-data")
372
391
  };
373
392
  }
393
+ /**
394
+ * Derives the default `responseType` for an operation from its primary success response.
395
+ *
396
+ * Returns a value only when that response declares a single non-JSON content type — a binary type
397
+ * (`application/octet-stream`, `application/pdf`, `image/*`, `audio/*`, `video/*`) maps to `'blob'`
398
+ * and other `text/*` maps to `'text'`. Otherwise `undefined`, leaving the runtime client's
399
+ * `Content-Type` auto-detection in charge.
400
+ */
401
+ function getResponseType(node) {
402
+ const contentTypes = getPrimarySuccessResponse(node)?.content?.map((entry) => entry.contentType) ?? [];
403
+ if (contentTypes.length !== 1) return void 0;
404
+ const baseType = contentTypes[0].split(";")[0].trim().toLowerCase();
405
+ if (baseType === "application/json" || baseType.endsWith("+json") || baseType === "text/json") return void 0;
406
+ if (baseType.startsWith("text/")) return "text";
407
+ if (baseType === "application/octet-stream" || baseType === "application/pdf" || /^(image|audio|video)\//.test(baseType)) return "blob";
408
+ }
374
409
  function buildRequestConfigType(node, resolver) {
375
410
  const requestName = node.requestBody?.content?.[0]?.schema ? resolver.resolveDataName(node) : null;
376
411
  const { isMultipleContentTypes, contentTypeUnion } = getContentTypeInfo(node);
@@ -414,6 +449,15 @@ function isErrorStatusCode(statusCode) {
414
449
  const code = getStatusCodeNumber(statusCode);
415
450
  return code !== null && code >= 400;
416
451
  }
452
+ function getSuccessResponses(responses) {
453
+ return responses.filter((response) => isSuccessStatusCode(response.statusCode));
454
+ }
455
+ function getOperationSuccessResponses(node) {
456
+ return getSuccessResponses(node.responses);
457
+ }
458
+ function getPrimarySuccessResponse(node) {
459
+ return getOperationSuccessResponses(node)[0] ?? null;
460
+ }
417
461
  function resolveErrorNames(node, resolver) {
418
462
  return node.responses.filter((response) => isErrorStatusCode(response.statusCode)).map((response) => resolver.resolveResponseStatusName(node, response.statusCode));
419
463
  }
@@ -456,6 +500,39 @@ function resolveOperationTypeNames(node, resolver, options = {}) {
456
500
  return result;
457
501
  }
458
502
  //#endregion
503
+ //#region ../../internals/shared/src/group.ts
504
+ /**
505
+ * Builds the `group` config a Kubb plugin passes to `ctx.setOptions`, applying the
506
+ * shared default naming so every plugin groups output consistently:
507
+ *
508
+ * - `path` groups use the second path segment (`/pet/findByStatus` → `pet`).
509
+ * - other groups use `${camelCase(group)}${suffix}` (e.g. `petController`).
510
+ *
511
+ * Returns `null` when grouping is disabled, matching the per-plugin convention.
512
+ *
513
+ * @param group - The user-supplied group option, or `undefined` to disable grouping.
514
+ * @param options.suffix - Appended to non-`path` group names, e.g. `'Controller'` or `'Requests'`.
515
+ * @param options.honorName - When `true`, a user-provided `group.name` overrides the default namer.
516
+ *
517
+ * @example
518
+ * ```ts
519
+ * createGroupConfig(group, { suffix: 'Controller' }) // plugin-ts, plugin-zod
520
+ * createGroupConfig(group, { suffix: 'Controller', honorName: true }) // plugin-faker, plugin-client, …
521
+ * createGroupConfig(group, { suffix: 'Requests', honorName: true }) // plugin-cypress, plugin-mcp
522
+ * ```
523
+ */
524
+ function createGroupConfig(group, options) {
525
+ if (!group) return null;
526
+ const defaultName = (ctx) => {
527
+ if (group.type === "path") return `${ctx.group.split("/")[1]}`;
528
+ return `${camelCase(ctx.group)}${options.suffix}`;
529
+ };
530
+ return {
531
+ ...group,
532
+ name: options.honorName && group.name ? group.name : defaultName
533
+ };
534
+ }
535
+ //#endregion
459
536
  //#region ../../internals/shared/src/params.ts
460
537
  function buildParamsMapping(originalParams, mappedParams) {
461
538
  const mapping = {};
@@ -547,6 +624,7 @@ function buildUrlParamsNode({ paramsType, paramsCasing, pathParamsType, node, ts
547
624
  });
548
625
  }
549
626
  function Url({ name, isExportable = true, isIndexable = true, baseURL, paramsType, paramsCasing, pathParamsType, node, tsResolver }) {
627
+ if (!ast.isHttpOperationNode(node)) return null;
550
628
  const path = new URLPath(node.path);
551
629
  const paramsNode = buildUrlParamsNode({
552
630
  paramsType,
@@ -600,9 +678,11 @@ function buildClientParamsNode({ paramsType, paramsCasing, pathParamsType, node,
600
678
  });
601
679
  }
602
680
  function Client({ name, isExportable = true, isIndexable = true, returnType, baseURL, dataReturnType, parser, paramsType, paramsCasing, pathParamsType, node, tsResolver, zodResolver, urlName, children, isConfigurable = true }) {
681
+ if (!ast.isHttpOperationNode(node)) return null;
603
682
  const path = new URLPath(node.path);
604
683
  const { defaultContentType: contentType, isMultipleContentTypes, hasFormData } = getContentTypeInfo(node);
605
684
  const isFormData = !isMultipleContentTypes && contentType === "multipart/form-data";
685
+ const responseType = getResponseType(node);
606
686
  const { path: originalPathParams, query: originalQueryParams, header: originalHeaderParams } = getOperationParameters(node);
607
687
  const { path: casedPathParams, query: casedQueryParams, header: casedHeaderParams } = getOperationParameters(node, { paramsCasing });
608
688
  const pathParamsMapping = paramsCasing && !urlName ? buildParamsMapping(originalPathParams, casedPathParams) : null;
@@ -650,6 +730,7 @@ function Client({ name, isExportable = true, isIndexable = true, returnType, bas
650
730
  params: queryParamsName ? queryParamsMapping ? { value: "mappedParams" } : {} : null,
651
731
  data: requestName ? { value: isMultipleContentTypes && hasFormData ? "contentType === 'multipart/form-data' ? formData as FormData : requestData" : isFormData ? "formData as FormData" : "requestData" } : null,
652
732
  contentType: isConfigurable && isMultipleContentTypes ? {} : null,
733
+ responseType: responseType ? { value: JSON.stringify(responseType) } : null,
653
734
  requestConfig: isConfigurable ? { mode: "inlineSpread" } : null,
654
735
  headers: headers.length ? { value: isConfigurable ? `{ ${headers.join(", ")}, ...requestConfig.headers }` : `{ ${headers.join(", ")} }` } : null
655
736
  }
@@ -657,8 +738,8 @@ function Client({ name, isExportable = true, isIndexable = true, returnType, bas
657
738
  const childrenElement = children ? children : /* @__PURE__ */ jsxs(Fragment, { children: [
658
739
  dataReturnType === "full" && parser === "zod" && zodResponseName && `return {...res, data: ${zodResponseName}.parse(res.data)}`,
659
740
  dataReturnType === "data" && parser === "zod" && zodResponseName && `return ${zodResponseName}.parse(res.data)`,
660
- dataReturnType === "full" && parser === "client" && "return res",
661
- dataReturnType === "data" && parser === "client" && "return res.data"
741
+ dataReturnType === "full" && parser !== "zod" && "return res",
742
+ dataReturnType === "data" && parser !== "zod" && "return res.data"
662
743
  ] });
663
744
  return /* @__PURE__ */ jsxs(Fragment, { children: [/* @__PURE__ */ jsx("br", {}), /* @__PURE__ */ jsx(File.Source, {
664
745
  name,
@@ -734,16 +815,18 @@ function buildClassClientParams({ node, path, baseURL, tsResolver, isFormData, i
734
815
  const { query: queryParams } = getOperationParameters(node);
735
816
  const queryParamsName = queryParams.length > 0 ? tsResolver.resolveQueryParamsName(node, queryParams[0]) : null;
736
817
  const requestName = node.requestBody?.content?.[0]?.schema ? tsResolver.resolveDataName(node) : null;
818
+ const responseType = getResponseType(node);
737
819
  return createFunctionParams({ config: {
738
820
  mode: "object",
739
821
  children: {
740
822
  requestConfig: { mode: "inlineSpread" },
741
- method: { value: JSON.stringify(node.method.toUpperCase()) },
823
+ method: { value: JSON.stringify(ast.isHttpOperationNode(node) ? node.method.toUpperCase() : "") },
742
824
  url: { value: path.template },
743
825
  baseURL: baseURL ? { value: JSON.stringify(baseURL) } : null,
744
826
  params: queryParamsName ? {} : null,
745
827
  data: requestName ? { value: isMultipleContentTypes && hasFormData ? "contentType === 'multipart/form-data' ? formData as FormData : requestData" : isFormData ? "formData as FormData" : "requestData" } : null,
746
828
  contentType: isMultipleContentTypes ? {} : null,
829
+ responseType: responseType ? { value: JSON.stringify(responseType) } : null,
747
830
  headers: headers.length ? { value: `{ ${headers.join(", ")}, ...requestConfig.headers }` } : null
748
831
  }
749
832
  } });
@@ -773,13 +856,14 @@ function buildReturnStatement({ dataReturnType, parser, node, zodResolver }) {
773
856
  const zodResponseName = zodResolver && parser === "zod" ? zodResolver.resolveResponseName?.(node) : null;
774
857
  if (dataReturnType === "full" && parser === "zod" && zodResponseName) return `return {...res, data: ${zodResponseName}.parse(res.data)}`;
775
858
  if (dataReturnType === "data" && parser === "zod" && zodResponseName) return `return ${zodResponseName}.parse(res.data)`;
776
- if (dataReturnType === "full" && parser === "client") return "return res";
859
+ if (dataReturnType === "full" && parser !== "zod") return "return res";
777
860
  return "return res.data";
778
861
  }
779
862
  //#endregion
780
863
  //#region src/components/ClassClient.tsx
781
864
  const declarationPrinter$1 = functionPrinter({ mode: "declaration" });
782
865
  function generateMethod$1({ node, name, tsResolver, zodResolver, baseURL, dataReturnType, parser, paramsType, paramsCasing, pathParamsType }) {
866
+ if (!ast.isHttpOperationNode(node)) return "";
783
867
  const path = new URLPath(node.path, { casing: paramsCasing });
784
868
  const { defaultContentType: contentType, isMultipleContentTypes, hasFormData } = getContentTypeInfo(node);
785
869
  const isFormData = !isMultipleContentTypes && contentType === "multipart/form-data";
@@ -907,22 +991,12 @@ const classClientGenerator = defineGenerator({
907
991
  const pluginZod = parser === "zod" ? driver.getPlugin(pluginZodName) : null;
908
992
  const zodResolver = pluginZod ? driver.getResolver(pluginZodName) : null;
909
993
  function buildOperationData(node) {
910
- const typeFile = tsResolver.resolveFile({
911
- name: node.operationId,
912
- extname: ".ts",
913
- tag: node.tags[0] ?? "default",
914
- path: node.path
915
- }, {
994
+ const typeFile = tsResolver.resolveFile(operationFileEntry(node, node.operationId), {
916
995
  root,
917
996
  output: tsPluginOptions?.output ?? output,
918
997
  group: tsPluginOptions?.group
919
998
  });
920
- const zodFile = zodResolver && pluginZod?.options ? zodResolver.resolveFile({
921
- name: node.operationId,
922
- extname: ".ts",
923
- tag: node.tags[0] ?? "default",
924
- path: node.path
925
- }, {
999
+ const zodFile = zodResolver && pluginZod?.options ? zodResolver.resolveFile(operationFileEntry(node, node.operationId), {
926
1000
  root,
927
1001
  output: pluginZod.options?.output ?? output,
928
1002
  group: pluginZod.options?.group ?? void 0
@@ -937,6 +1011,7 @@ const classClientGenerator = defineGenerator({
937
1011
  };
938
1012
  }
939
1013
  const controllers = nodes.reduce((acc, operationNode) => {
1014
+ if (!ast.isHttpOperationNode(operationNode)) return acc;
940
1015
  const tag = operationNode.tags[0];
941
1016
  const groupName = tag ? group?.name?.({ group: camelCase(tag) }) ?? resolver.resolveGroupName(tag) : resolver.resolveGroupName("Client");
942
1017
  if (!tag && !group) {
@@ -1196,6 +1271,7 @@ const clientGenerator = defineGenerator({
1196
1271
  name: "client",
1197
1272
  renderer: jsxRendererSync,
1198
1273
  operation(node, ctx) {
1274
+ if (!ast.isHttpOperationNode(node)) return null;
1199
1275
  const { config, driver, resolver, root } = ctx;
1200
1276
  const { output, urlType, dataReturnType, paramsCasing, paramsType, pathParamsType, parser, importPath, group } = ctx.options;
1201
1277
  const baseURL = ctx.options.baseURL ?? ctx.meta.baseURL;
@@ -1209,32 +1285,17 @@ const clientGenerator = defineGenerator({
1209
1285
  const meta = {
1210
1286
  name: resolver.resolveName(node.operationId),
1211
1287
  urlName: resolver.resolveUrlName(node),
1212
- file: resolver.resolveFile({
1213
- name: node.operationId,
1214
- extname: ".ts",
1215
- tag: node.tags[0] ?? "default",
1216
- path: node.path
1217
- }, {
1288
+ file: resolver.resolveFile(operationFileEntry(node, node.operationId), {
1218
1289
  root,
1219
1290
  output,
1220
1291
  group: group ?? void 0
1221
1292
  }),
1222
- fileTs: tsResolver.resolveFile({
1223
- name: node.operationId,
1224
- extname: ".ts",
1225
- tag: node.tags[0] ?? "default",
1226
- path: node.path
1227
- }, {
1293
+ fileTs: tsResolver.resolveFile(operationFileEntry(node, node.operationId), {
1228
1294
  root,
1229
1295
  output: pluginTs.options?.output ?? output,
1230
1296
  group: pluginTs.options?.group ?? void 0
1231
1297
  }),
1232
- fileZod: zodResolver && pluginZod?.options ? zodResolver.resolveFile({
1233
- name: node.operationId,
1234
- extname: ".ts",
1235
- tag: node.tags[0] ?? "default",
1236
- path: node.path
1237
- }, {
1298
+ fileZod: zodResolver && pluginZod?.options ? zodResolver.resolveFile(operationFileEntry(node, node.operationId), {
1238
1299
  root,
1239
1300
  output: pluginZod.options.output ?? output,
1240
1301
  group: pluginZod.options?.group ?? void 0
@@ -1428,6 +1489,7 @@ const groupedClientGenerator = defineGenerator({
1428
1489
  function Operations({ name, nodes }) {
1429
1490
  const operationsObject = {};
1430
1491
  nodes.forEach((node) => {
1492
+ if (!ast.isHttpOperationNode(node)) return;
1431
1493
  operationsObject[node.operationId] = {
1432
1494
  path: new URLPath(node.path).URL,
1433
1495
  method: node.method.toLowerCase()
@@ -1489,7 +1551,7 @@ const operationsGenerator = defineGenerator({
1489
1551
  }),
1490
1552
  children: /* @__PURE__ */ jsx(Operations, {
1491
1553
  name,
1492
- nodes
1554
+ nodes: nodes.filter(ast.isHttpOperationNode)
1493
1555
  })
1494
1556
  });
1495
1557
  }
@@ -1498,6 +1560,7 @@ const operationsGenerator = defineGenerator({
1498
1560
  //#region src/components/StaticClassClient.tsx
1499
1561
  const declarationPrinter = functionPrinter({ mode: "declaration" });
1500
1562
  function generateMethod({ node, name, tsResolver, zodResolver, baseURL, dataReturnType, parser, paramsType, paramsCasing, pathParamsType }) {
1563
+ if (!ast.isHttpOperationNode(node)) return "";
1501
1564
  const path = new URLPath(node.path, { casing: paramsCasing });
1502
1565
  const { defaultContentType: contentType, isMultipleContentTypes, hasFormData } = getContentTypeInfo(node);
1503
1566
  const isFormData = !isMultipleContentTypes && contentType === "multipart/form-data";
@@ -1598,22 +1661,12 @@ const staticClassClientGenerator = defineGenerator({
1598
1661
  const pluginZod = parser === "zod" ? driver.getPlugin(pluginZodName) : null;
1599
1662
  const zodResolver = pluginZod ? driver.getResolver(pluginZodName) : null;
1600
1663
  function buildOperationData(node) {
1601
- const typeFile = tsResolver.resolveFile({
1602
- name: node.operationId,
1603
- extname: ".ts",
1604
- tag: node.tags[0] ?? "default",
1605
- path: node.path
1606
- }, {
1664
+ const typeFile = tsResolver.resolveFile(operationFileEntry(node, node.operationId), {
1607
1665
  root,
1608
1666
  output: tsPluginOptions?.output ?? output,
1609
1667
  group: tsPluginOptions?.group
1610
1668
  });
1611
- const zodFile = zodResolver && pluginZod?.options ? zodResolver.resolveFile({
1612
- name: node.operationId,
1613
- extname: ".ts",
1614
- tag: node.tags[0] ?? "default",
1615
- path: node.path
1616
- }, {
1669
+ const zodFile = zodResolver && pluginZod?.options ? zodResolver.resolveFile(operationFileEntry(node, node.operationId), {
1617
1670
  root,
1618
1671
  output: pluginZod.options?.output ?? output,
1619
1672
  group: pluginZod.options?.group ?? void 0
@@ -1628,6 +1681,7 @@ const staticClassClientGenerator = defineGenerator({
1628
1681
  };
1629
1682
  }
1630
1683
  const controllers = nodes.reduce((acc, operationNode) => {
1684
+ if (!ast.isHttpOperationNode(operationNode)) return acc;
1631
1685
  const tag = operationNode.tags[0];
1632
1686
  const groupName = tag ? group?.name?.({ group: camelCase(tag) }) ?? resolver.resolveGroupName(tag) : resolver.resolveGroupName("Client");
1633
1687
  if (!tag && !group) {
@@ -1897,20 +1951,17 @@ const pluginClient = definePlugin((options) => {
1897
1951
  const { output = {
1898
1952
  path: "clients",
1899
1953
  barrelType: "named"
1900
- }, group, exclude = [], include, override = [], urlType = false, dataReturnType = "data", paramsType = "inline", pathParamsType = paramsType === "object" ? "object" : options.pathParamsType || "inline", operations = false, paramsCasing, clientType = options.sdk ? "class" : "function", parser = "client", client = "axios", importPath, bundle = false, sdk, baseURL, resolver: userResolver, transformer: userTransformer } = options;
1954
+ }, group, exclude = [], include, override = [], urlType = false, dataReturnType = "data", paramsType = "inline", pathParamsType = paramsType === "object" ? "object" : options.pathParamsType || "inline", operations = false, paramsCasing, clientType = options.sdk ? "class" : "function", parser = false, client = "axios", importPath, bundle = false, sdk, baseURL, resolver: userResolver, transformer: userTransformer } = options;
1901
1955
  const resolvedImportPath = importPath ?? (!bundle ? `@kubb/plugin-client/clients/${client}` : void 0);
1902
1956
  const selectedGenerators = options.generators ?? [
1903
1957
  clientType === "staticClass" ? staticClassClientGenerator : clientType === "class" ? classClientGenerator : clientGenerator,
1904
1958
  group && clientType === "function" ? groupedClientGenerator : null,
1905
1959
  operations ? operationsGenerator : null
1906
1960
  ].filter((x) => Boolean(x));
1907
- const groupConfig = group ? {
1908
- ...group,
1909
- name: group.name ? group.name : (ctx) => {
1910
- if (group.type === "path") return `${ctx.group.split("/")[1]}`;
1911
- return `${camelCase(ctx.group)}Controller`;
1912
- }
1913
- } : null;
1961
+ const groupConfig = createGroupConfig(group, {
1962
+ suffix: "Controller",
1963
+ honorName: true
1964
+ });
1914
1965
  return {
1915
1966
  name: pluginClientName,
1916
1967
  options,