@kubb/plugin-fetch 5.5.0-canary.20260924T111237 → 5.5.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.d.ts CHANGED
@@ -25,9 +25,8 @@ type Mode = 'tag' | 'flat';
25
25
  /**
26
26
  * Shape of the value a generated operation function resolves to.
27
27
  * - `'full'`: the complete `{ status, data, error, contentType, request, response }` result.
28
- * - `'data'`: the bare success body once `throwOnError` (on by default) narrows away the error
29
- * branch, falling back to the full result when a call sets `throwOnError: false` and still
30
- * needs `error` to discriminate a failed response.
28
+ * - `'data'`: the bare success body when `throwOnError` is true, or the full result when it is false
29
+ * so callers can inspect `error`.
31
30
  */
32
31
  type ReturnTypeOption = 'full' | 'data';
33
32
  /**
@@ -71,13 +70,20 @@ type Options = OutputOptions & {
71
70
  /**
72
71
  * Apply a different options object to operations matching a pattern.
73
72
  */
74
- override?: Array<Override<ResolvedOptions>>;
73
+ override?: Array<Override<Omit<ResolvedOptions, 'throwOnErrorDefault'>>>;
75
74
  /**
76
75
  * Base URL prepended to every request. When omitted, falls back to the adapter's server URL.
77
76
  * Values containing a `${...}` interpolation are emitted as template literals in the generated
78
77
  * client config, which keeps runtime environment reads dynamic.
79
78
  */
80
79
  baseURL?: string;
80
+ /**
81
+ * Default error behavior for generated operations and their client. Per-call `throwOnError`
82
+ * takes precedence.
83
+ *
84
+ * @default true
85
+ */
86
+ throwOnErrorDefault?: boolean;
81
87
  /**
82
88
  * Validate request and response bodies with schemas from `@kubb/plugin-zod`.
83
89
  *
@@ -160,9 +166,10 @@ type ResolvedOptions = {
160
166
  output: Output;
161
167
  exclude: Array<Exclude>;
162
168
  include: Array<Include> | undefined;
163
- override: Array<Override<ResolvedOptions>>;
169
+ override: Array<Override<Omit<ResolvedOptions, 'throwOnErrorDefault'>>>;
164
170
  group: Group | null;
165
171
  baseURL: Options['baseURL'];
172
+ throwOnErrorDefault: boolean;
166
173
  validator: NonNullable<Options['validator']>;
167
174
  returnType: ReturnTypeOption;
168
175
  sdk: {
package/dist/index.js CHANGED
@@ -476,7 +476,7 @@ function buildResultType({ node, types, returnType }) {
476
476
  * `withUnwrap`, so the caller can `await` it directly or call `.unwrap()` for the bare success
477
477
  * body. With `returnType: 'data'` it instead routes the call through the runtime's `unwrapResult`,
478
478
  * which narrows the resolved value down to the bare success body the same way `RequestResult`
479
- * already does, keeping the `throwOnError` default in one place instead of restating it per call.
479
+ * already does, using the plugin default when the call leaves `throwOnError` unset.
480
480
  *
481
481
  * Cast first, wrap second, for the `'full'` path. That order keeps `withUnwrap`'s generic inferred
482
482
  * as `RequestResult` instead of the runtime's own internal result type. Casting an `Unwrappable<A>`
@@ -486,10 +486,10 @@ function buildResultType({ node, types, returnType }) {
486
486
  * @example
487
487
  * `return withUnwrap(request({ method: 'POST', url: '/pet', ...config }) as Promise<RequestResult<AddPetResponses, ThrowOnError>>)`
488
488
  * @example
489
- * `return unwrapResult(request({ method: 'POST', url: '/pet', ...config }), config.throwOnError) as Promise<UnwrappedResult<AddPetResponses, ThrowOnError>>`
489
+ * `return unwrapResult(request({ method: 'POST', url: '/pet', ...config }), config.throwOnError ?? false) as Promise<UnwrappedResult<AddPetResponses, ThrowOnError>>`
490
490
  */
491
- function buildReturnStatement({ node, types, callConfig, returnType }) {
492
- if (returnType === "data") return `return unwrapResult(request(${callConfig}), config.throwOnError) as ${buildResultType({
491
+ function buildReturnStatement({ node, types, callConfig, returnType, throwOnErrorDefault }) {
492
+ if (returnType === "data") return `return unwrapResult(request(${callConfig}), config.throwOnError ?? ${throwOnErrorDefault}) as ${buildResultType({
493
493
  node,
494
494
  types,
495
495
  returnType
@@ -579,7 +579,7 @@ const declarationPrinter = functionPrinter({ mode: "declaration" });
579
579
  * per-operation input type has to be emitted. Both names come from `types`, which is `plugin-ts` or
580
580
  * `plugin-zod`'s inferred types (see `resolveOperationTypes`).
581
581
  */
582
- function buildGroupedOptionsSignature({ node, types, returnType }) {
582
+ function buildGroupedOptionsSignature({ node, types, returnType, throwOnErrorDefault }) {
583
583
  const optionsName = types.response.options(node);
584
584
  const { isOptional } = getRequestGroupOptionality(node);
585
585
  return {
@@ -593,7 +593,7 @@ function buildGroupedOptionsSignature({ node, types, returnType }) {
593
593
  types,
594
594
  returnType
595
595
  }),
596
- generics: ["ThrowOnError extends boolean = true"]
596
+ generics: [`ThrowOnError extends boolean = ${throwOnErrorDefault}`]
597
597
  };
598
598
  }
599
599
  //#endregion
@@ -687,9 +687,10 @@ function buildValidatorHooks({ node, validator, zodResolver }) {
687
687
  /**
688
688
  * Builds the call config literal forwarded to the contract client, mirroring the shared `Operation`
689
689
  * component: `{ method, url, security?, validator?, ...config }`. The `...config` spread carries every
690
- * per-call field (including `throwOnError`), so the method stays a thin wrapper over the contract.
690
+ * per-call field. The plugin's `throwOnError` fallback is applied after the spread so it also wins
691
+ * over a client instance's runtime config when the call omits an override.
691
692
  */
692
- function buildCallConfig({ node, validator, zodResolver, security }) {
693
+ function buildCallConfig({ node, validator, zodResolver, security, throwOnErrorDefault }) {
693
694
  const validators = buildValidatorHooks({
694
695
  node,
695
696
  validator,
@@ -703,7 +704,8 @@ function buildCallConfig({ node, validator, zodResolver, security }) {
703
704
  `url: '${node.path}'`,
704
705
  securityLiteral ? `security: ${securityLiteral}` : null,
705
706
  validatorLiteral,
706
- "...config"
707
+ "...config",
708
+ `throwOnError: config.throwOnError ?? ${throwOnErrorDefault}`
707
709
  ].filter(Boolean).join(", ")} }`;
708
710
  }
709
711
  /**
@@ -713,12 +715,13 @@ function buildCallConfig({ node, validator, zodResolver, security }) {
713
715
  * instance client, so one operation can be routed to a different environment without a new
714
716
  * instance.
715
717
  */
716
- function buildSdkMethod({ node, name, types, zodResolver, validator, security, returnType }) {
718
+ function buildSdkMethod({ node, name, types, zodResolver, validator, security, returnType, throwOnErrorDefault }) {
717
719
  if (!ast.isHttpOperationNode(node)) return "";
718
720
  const signature = buildGroupedOptionsSignature({
719
721
  node,
720
722
  types,
721
- returnType
723
+ returnType,
724
+ throwOnErrorDefault
722
725
  });
723
726
  const returnStatement = buildReturnStatement({
724
727
  node,
@@ -727,9 +730,11 @@ function buildSdkMethod({ node, name, types, zodResolver, validator, security, r
727
730
  node,
728
731
  validator,
729
732
  zodResolver,
730
- security
733
+ security,
734
+ throwOnErrorDefault
731
735
  }),
732
- returnType
736
+ returnType,
737
+ throwOnErrorDefault
733
738
  });
734
739
  const generics = signature.generics.length ? `<${signature.generics.join(", ")}>` : "";
735
740
  const jsdoc = buildJSDoc(buildOperationComments(node, {
@@ -805,12 +810,13 @@ function buildStyles({ node }) {
805
810
  * type, signature, and call config are built with the AST factory, and only the jsx-renderer emits
806
811
  * the source.
807
812
  */
808
- function Operation({ name, node, types, zodResolver, validator, returnType, security, isExportable = true, isIndexable = true }) {
813
+ function Operation({ name, node, types, zodResolver, validator, returnType, throwOnErrorDefault, security, isExportable = true, isIndexable = true }) {
809
814
  if (!ast.isHttpOperationNode(node)) return null;
810
815
  const signature = buildGroupedOptionsSignature({
811
816
  node,
812
817
  types,
813
- returnType
818
+ returnType,
819
+ throwOnErrorDefault
814
820
  });
815
821
  const validators = buildValidatorHooks({
816
822
  node,
@@ -840,7 +846,8 @@ function Operation({ name, node, types, zodResolver, validator, returnType, secu
840
846
  validatorLiteral,
841
847
  contentTypeLiteral,
842
848
  responseTypeLiteral,
843
- "...config"
849
+ "...config",
850
+ `throwOnError: config.throwOnError ?? ${throwOnErrorDefault}`
844
851
  ].filter(Boolean).join(", ")} }`;
845
852
  const eventType = `SuccessOf<${types.response.responses(node)}>`;
846
853
  const functionReturnType = eventStream ? `Promise<EventStreamResult<${eventType}>>` : signature.returnType;
@@ -848,7 +855,8 @@ function Operation({ name, node, types, zodResolver, validator, returnType, secu
848
855
  node,
849
856
  types,
850
857
  callConfig,
851
- returnType
858
+ returnType,
859
+ throwOnErrorDefault
852
860
  });
853
861
  return /* @__PURE__ */ jsx(File.Source, {
854
862
  name,
@@ -881,7 +889,7 @@ function Operation({ name, node, types, zodResolver, validator, returnType, secu
881
889
  * instance: `const api = new PetClient({ baseURL }); api.getPetById(...)`. A per-call `client` option
882
890
  * still overrides the instance client for a one-off call.
883
891
  */
884
- function SdkClient({ name, isExportable = true, isIndexable = true, operations, validator, returnType, children }) {
892
+ function SdkClient({ name, isExportable = true, isIndexable = true, operations, validator, returnType, throwOnErrorDefault, children }) {
885
893
  const methods = operations.map(({ node, name: methodName, types, zodResolver, security }) => buildSdkMethod({
886
894
  node,
887
895
  name: methodName,
@@ -889,7 +897,8 @@ function SdkClient({ name, isExportable = true, isIndexable = true, operations,
889
897
  zodResolver,
890
898
  validator,
891
899
  security,
892
- returnType
900
+ returnType,
901
+ throwOnErrorDefault
893
902
  }));
894
903
  const classCode = `export class ${name} {\n${[
895
904
  " private readonly client: ClientInstance",
@@ -969,7 +978,7 @@ function createClientGenerator(name) {
969
978
  operation(node, ctx) {
970
979
  if (!ast.isHttpOperationNode(node)) return null;
971
980
  const { config, driver, resolver, root } = ctx;
972
- const { output, validator, returnType, group } = ctx.options;
981
+ const { output, validator, returnType, throwOnErrorDefault, group } = ctx.options;
973
982
  const types = resolveOperationTypes(driver);
974
983
  if (!types) {
975
984
  ctx.warn(MISSING_OPERATION_TYPES_WARNING);
@@ -1072,6 +1081,7 @@ function createClientGenerator(name) {
1072
1081
  zodResolver,
1073
1082
  validator,
1074
1083
  returnType,
1084
+ throwOnErrorDefault,
1075
1085
  security
1076
1086
  })
1077
1087
  ]
@@ -1212,7 +1222,7 @@ function createSdkGenerator() {
1212
1222
  renderer: jsxRenderer,
1213
1223
  operations(nodes, ctx) {
1214
1224
  const { config, resolver, root } = ctx;
1215
- const { output, group, validator, returnType, sdk } = ctx.options;
1225
+ const { output, group, validator, returnType, throwOnErrorDefault, sdk } = ctx.options;
1216
1226
  if (!sdk) return null;
1217
1227
  const types = resolveOperationTypes(ctx.driver);
1218
1228
  if (!types) {
@@ -1298,7 +1308,8 @@ function createSdkGenerator() {
1298
1308
  name: className,
1299
1309
  operations: ops,
1300
1310
  validator,
1301
- returnType
1311
+ returnType,
1312
+ throwOnErrorDefault
1302
1313
  })
1303
1314
  ]
1304
1315
  }, file.path);
@@ -1384,6 +1395,27 @@ const resolverClient = createResolver({
1384
1395
  }
1385
1396
  });
1386
1397
  //#endregion
1398
+ //#region ../../internals/client/src/runtimeTemplate.ts
1399
+ /** Apply the active TypeScript parser's import extension to a copied client runtime. */
1400
+ function runtimeTemplate(path, config) {
1401
+ const parser = config.parsers.find((item) => item.extNames?.includes(".ts"));
1402
+ const probe = ast.factory.createFile({
1403
+ baseName: "probe.ts",
1404
+ path: "probe.ts",
1405
+ imports: [ast.factory.createImport({
1406
+ name: ["KubbRuntimeImport"],
1407
+ path: "/probeDependency.ts",
1408
+ root: "/"
1409
+ })],
1410
+ sources: [ast.factory.createSource({
1411
+ name: "probe",
1412
+ nodes: [ast.factory.createText("KubbRuntimeImport")]
1413
+ })]
1414
+ });
1415
+ const extension = parser?.parse(probe).match(/from ['"]\.\/probeDependency(\.[^'"]+)?['"]/)?.[1] ?? "";
1416
+ return readFileSync(path, "utf8").replace(/(from ['"]\.\/(?:serializers|standardSchema))(?:\.ts|\.js)?(['"])/g, `$1${extension}$2`);
1417
+ }
1418
+ //#endregion
1387
1419
  //#region src/generators/clientGenerator.tsx
1388
1420
  /**
1389
1421
  * Built-in operation generator for `@kubb/plugin-fetch`. Emits one async function per OpenAPI
@@ -1435,7 +1467,7 @@ const pluginFetch = definePlugin((options) => {
1435
1467
  const { output = {
1436
1468
  path: "clients",
1437
1469
  barrel: { type: "named" }
1438
- }, exclude = [], include, override = [], baseURL, validator = false, returnType = "full", group, sdk, resolver: userResolver } = options;
1470
+ }, exclude = [], include, override = [], baseURL, throwOnErrorDefault = true, validator = false, returnType = "full", group, sdk, resolver: userResolver } = options;
1439
1471
  const resolved = {
1440
1472
  output,
1441
1473
  exclude,
@@ -1443,6 +1475,7 @@ const pluginFetch = definePlugin((options) => {
1443
1475
  override,
1444
1476
  group: createGroupConfig(group),
1445
1477
  baseURL,
1478
+ throwOnErrorDefault,
1446
1479
  validator,
1447
1480
  returnType,
1448
1481
  sdk: sdk ? {
@@ -1468,52 +1501,10 @@ const pluginFetch = definePlugin((options) => {
1468
1501
  path: path.resolve(root, ".kubb/serializers.ts"),
1469
1502
  copy: fetchSerializersTemplatePath
1470
1503
  });
1471
- const clientPath = path.resolve(root, ".kubb/client.ts");
1472
- const clientSource = readFileSync(fetchClientTemplatePath, "utf8");
1473
- const clientBody = clientSource.slice(clientSource.indexOf("\n\n") + 2);
1474
- const runtimeRoot = path.dirname(clientPath);
1475
1504
  ctx.injectFile({
1476
1505
  baseName: "client.ts",
1477
- path: clientPath,
1478
- imports: [
1479
- ast.factory.createImport({
1480
- name: [
1481
- "applyHeaderStyles",
1482
- "defaultBodySerializer",
1483
- "defaultPathSerializer",
1484
- "defaultQuerySerializer",
1485
- "isDefaultJsonBody",
1486
- "serializeCookies"
1487
- ],
1488
- path: path.resolve(root, ".kubb/serializers.ts"),
1489
- root: runtimeRoot
1490
- }),
1491
- ast.factory.createImport({
1492
- name: [
1493
- "HeadersInit",
1494
- "PathParamStyle",
1495
- "PathSerializer",
1496
- "RequestBody",
1497
- "Serializers",
1498
- "Styles"
1499
- ],
1500
- path: path.resolve(root, ".kubb/serializers.ts"),
1501
- root: runtimeRoot,
1502
- isTypeOnly: true
1503
- }),
1504
- ast.factory.createImport({
1505
- name: ["ParseError", "validateStandardSchema"],
1506
- path: path.resolve(root, ".kubb/standardSchema.ts"),
1507
- root: runtimeRoot
1508
- }),
1509
- ast.factory.createImport({
1510
- name: ["StandardSchemaValidator"],
1511
- path: path.resolve(root, ".kubb/standardSchema.ts"),
1512
- root: runtimeRoot,
1513
- isTypeOnly: true
1514
- })
1515
- ],
1516
- sources: [ast.factory.createSource({ nodes: [ast.factory.createText(clientBody)] })],
1506
+ path: path.resolve(root, ".kubb/client.ts"),
1507
+ banner: runtimeTemplate(fetchClientTemplatePath, ctx.config),
1517
1508
  footer: baseURLExpression ? `client.setConfig({ baseURL: ${baseURLExpression} })` : void 0
1518
1509
  });
1519
1510
  ctx.injectFile({