@zayne-labs/callapi 1.6.24 → 1.7.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/README.md CHANGED
@@ -1,8 +1,19 @@
1
- # CallApi
2
-
3
- [![Build Size](https://img.shields.io/bundlephobia/minzip/@zayne-labs/callapi?label=bundle%20size&style=flat&colorA=000000&colorB=000000)](https://bundlephobia.com/result?p=@zayne-labs/callapi)[![Version](https://img.shields.io/npm/v/@zayne-labs/callapi?style=flat&colorA=000000&colorB=000000)](https://www.npmjs.com/package/@zayne-labs/callapi)
4
-
5
- CallApi Fetch is an extra-lightweight wrapper over fetch that provides quality of life improvements beyond the bare fetch api, while keeping the API familiar.
1
+ <h1 align="center">CallApi - Advanced Fetch Client</h1>
2
+
3
+ <p align="center">
4
+ <img src="../../apps/docs/public/logo.png" alt="CallApi Logo" width="50%">
5
+ </p>
6
+
7
+ <p align="center">
8
+ <a href="https://deno.bundlejs.com/badge?q=@zayne-labs/callapi,@zayne-labs/callapi&treeshake=%5B*%5D,%5B%7B+createFetchClient+%7D%5D&config=%7B%22compression%22:%7B%22type%22:%22brotli%22,%22quality%22:11%7D%7D"><img src="https://deno.bundlejs.com/badge?q=@zayne-labs/callapi,@zayne-labs/callapi&treeshake=%5B*%5D,%5B%7B+createFetchClient+%7D%5D&config=%7B%22compression%22:%7B%22type%22:%22brotli%22,%22quality%22:11%7D%7D" alt="bundle size"></a>
9
+ <a href="https://www.npmjs.com/package/@zayne-labs/callapi"><img src="https://img.shields.io/npm/v/@zayne-labs/callapi?style=flat&color=EFBA5F" alt="npm version"></a>
10
+ <a href="https://github.com/zayne-labs/call-api/blob/master/LICENSE"><img src="https://img.shields.io/npm/l/@zayne-labs/callapi?style=flat&color=EFBA5F" alt="license"></a>
11
+ <a href="https://github.com/zayne-labs/call-api/graphs/commit-activity"><img src="https://img.shields.io/github/commit-activity/m/zayne-labs/call-api?style=flat&color=EFBA5F" alt="commit activity"></a>
12
+ <a href="https://www.npmjs.com/package/@zayne-labs/callapi"><img src="https://img.shields.io/npm/dm/@zayne-labs/callapi?style=flat&color=EFBA5F" alt="downloads per month"></a>
13
+ </p>
14
+
15
+ <p align="center">
16
+ CallApi Fetch is an extra-lightweight wrapper over fetch that provides quality of life improvements beyond the bare fetch api, while keeping the API familiar.</p>
6
17
 
7
18
  It takes in a url and a request options object, just like fetch, but with some additional options to make your life easier. Check out the [API Reference](https://zayne-labs-callapi.netlify.app/docs/latest/all-options) for a quick look at each option.
8
19
 
@@ -1,5 +1,41 @@
1
1
  import { StandardSchemaV1 } from '@standard-schema/spec';
2
2
 
3
+ type StreamProgressEvent = {
4
+ /**
5
+ * Current chunk of data being streamed
6
+ */
7
+ chunk: Uint8Array;
8
+ /**
9
+ * Progress in percentage
10
+ */
11
+ progress: number;
12
+ /**
13
+ * Total size of data in bytes
14
+ */
15
+ totalBytes: number;
16
+ /**
17
+ * Amount of data transferred so far
18
+ */
19
+ transferredBytes: number;
20
+ };
21
+ type RequestStreamContext = {
22
+ event: StreamProgressEvent;
23
+ options: CombinedCallApiExtraOptions;
24
+ request: CallApiRequestOptionsForHooks;
25
+ requestInstance: Request;
26
+ };
27
+ type ResponseStreamContext = {
28
+ event: StreamProgressEvent;
29
+ options: CombinedCallApiExtraOptions;
30
+ request: CallApiRequestOptionsForHooks;
31
+ response: Response;
32
+ };
33
+ declare global {
34
+ interface ReadableStream<R> {
35
+ [Symbol.asyncIterator]: () => AsyncIterableIterator<R>;
36
+ }
37
+ }
38
+
3
39
  type ValueOrFunctionResult<TValue> = TValue | (() => TValue);
4
40
  /**
5
41
  * Bearer Or Token authentication
@@ -243,7 +279,10 @@ interface CallApiPlugin<TData = never, TErrorData = never> {
243
279
  declare const definePlugin: <TPlugin extends CallApiPlugin | AnyFunction<CallApiPlugin>>(plugin: TPlugin) => TPlugin;
244
280
  type Plugins<TPluginArray extends CallApiPlugin[]> = TPluginArray;
245
281
 
246
- declare const fetchSpecificKeys: ("body" | "cache" | "credentials" | "headers" | "integrity" | "keepalive" | "method" | "mode" | "priority" | "redirect" | "referrer" | "referrerPolicy" | "signal" | "window")[];
282
+ type ModifiedRequestInit = RequestInit & {
283
+ duplex?: "full" | "half" | "none";
284
+ };
285
+ declare const fetchSpecificKeys: ("body" | "cache" | "credentials" | "headers" | "integrity" | "keepalive" | "method" | "mode" | "priority" | "redirect" | "referrer" | "referrerPolicy" | "signal" | "window" | "duplex")[];
247
286
  declare const getDefaultOptions: () => {
248
287
  baseURL: string;
249
288
  bodySerializer: {
@@ -335,7 +374,7 @@ type ResultModeOption<TErrorData, TResultMode extends ResultModeUnion> = TErrorD
335
374
  };
336
375
 
337
376
  type FetchSpecificKeysUnion = Exclude<(typeof fetchSpecificKeys)[number], "body" | "headers" | "method">;
338
- type CallApiRequestOptions<TSchemas extends CallApiSchemas = DefaultMoreOptions> = BodyOption<TSchemas> & HeadersOption<TSchemas> & MethodOption<TSchemas> & Pick<RequestInit, FetchSpecificKeysUnion>;
377
+ type CallApiRequestOptions<TSchemas extends CallApiSchemas = DefaultMoreOptions> = BodyOption<TSchemas> & HeadersOption<TSchemas> & MethodOption<TSchemas> & Pick<ModifiedRequestInit, FetchSpecificKeysUnion>;
339
378
  type CallApiRequestOptionsForHooks<TSchemas extends CallApiSchemas = DefaultMoreOptions> = Omit<CallApiRequestOptions<TSchemas>, "headers"> & {
340
379
  headers?: Record<string, string | undefined>;
341
380
  };
@@ -356,6 +395,10 @@ interface Interceptors<TData = DefaultDataType, TErrorData = DefaultDataType, TM
356
395
  * Interceptor that will be called when an error occurs during the fetch request.
357
396
  */
358
397
  onRequestError?: (context: RequestErrorContext & WithMoreOptions<TMoreOptions>) => Awaitable<unknown>;
398
+ /**
399
+ * Interceptor that will be called when upload stream progress is tracked
400
+ */
401
+ onRequestStream?: (context: RequestStreamContext & WithMoreOptions<TMoreOptions>) => Awaitable<unknown>;
359
402
  /**
360
403
  * Interceptor that will be called when any response is received from the api, whether successful or not
361
404
  */
@@ -364,6 +407,10 @@ interface Interceptors<TData = DefaultDataType, TErrorData = DefaultDataType, TM
364
407
  * Interceptor that will be called when an error response is received from the api.
365
408
  */
366
409
  onResponseError?: (context: ResponseErrorContext<TErrorData> & WithMoreOptions<TMoreOptions>) => Awaitable<unknown>;
410
+ /**
411
+ * Interceptor that will be called when download stream progress is tracked
412
+ */
413
+ onResponseStream?: (context: ResponseStreamContext & WithMoreOptions<TMoreOptions>) => Awaitable<unknown>;
367
414
  /**
368
415
  * Interceptor that will be called when a request is retried.
369
416
  */
@@ -418,6 +465,14 @@ type ExtraOptions<TData = DefaultDataType, TErrorData = DefaultDataType, TResult
418
465
  * @default "Failed to fetch data from server!"
419
466
  */
420
467
  defaultErrorMessage?: string;
468
+ /**
469
+ * If true, forces the calculation of the total byte size from the request or response body, in case the content-length header is not present or is incorrect.
470
+ * @default false
471
+ */
472
+ forceStreamSizeCalc?: boolean | {
473
+ request?: boolean;
474
+ response?: boolean;
475
+ };
421
476
  /**
422
477
  * Resolved request URL
423
478
  */
@@ -481,7 +536,7 @@ type CallApiExtraOptions<TData = DefaultDataType, TErrorData = DefaultDataType,
481
536
  };
482
537
  declare const optionsEnumToOmitFromBase: ("dedupeKey" | "extend")[];
483
538
  type BaseCallApiExtraOptions<TBaseData = DefaultDataType, TBaseErrorData = DefaultDataType, TBaseResultMode extends ResultModeUnion = ResultModeUnion, TBaseThrowOnError extends boolean = DefaultThrowOnError, TBaseResponseType extends ResponseTypeUnion = ResponseTypeUnion, TBasePluginArray extends CallApiPlugin[] = DefaultPluginArray, TBaseSchemas extends CallApiSchemas = DefaultMoreOptions> = Omit<Partial<CallApiExtraOptions<TBaseData, TBaseErrorData, TBaseResultMode, TBaseThrowOnError, TBaseResponseType, TBasePluginArray, TBaseSchemas>>, (typeof optionsEnumToOmitFromBase)[number]>;
484
- type CombinedCallApiExtraOptions = BaseCallApiExtraOptions & CallApiExtraOptions;
539
+ type CombinedCallApiExtraOptions = Interceptors & Omit<BaseCallApiExtraOptions & CallApiExtraOptions, keyof Interceptors>;
485
540
  type BaseCallApiConfig<TBaseData = DefaultDataType, TBaseErrorData = DefaultDataType, TBaseResultMode extends ResultModeUnion = ResultModeUnion, TBaseThrowOnError extends boolean = DefaultThrowOnError, TBaseResponseType extends ResponseTypeUnion = ResponseTypeUnion, TBasePluginArray extends CallApiPlugin[] = DefaultPluginArray, TBaseSchemas extends CallApiSchemas = DefaultMoreOptions> = (CallApiRequestOptions<TBaseSchemas> & BaseCallApiExtraOptions<TBaseData, TBaseErrorData, TBaseResultMode, TBaseThrowOnError, TBaseResponseType, TBasePluginArray, TBaseSchemas>) | ((context: {
486
541
  initURL: string;
487
542
  options: CallApiExtraOptions;
@@ -569,7 +624,7 @@ type ResultModeMap<TData = DefaultDataType, TErrorData = DefaultDataType, TRespo
569
624
  allWithoutResponse: CallApiResultSuccessVariant<TComputedData>["data" | "error"] | CallApiResultErrorVariant<TComputedErrorData>["data" | "error"];
570
625
  onlyError: CallApiResultSuccessVariant<TComputedData>["error"] | CallApiResultErrorVariant<TComputedErrorData>["error"];
571
626
  onlyResponse: CallApiResultErrorVariant<TComputedErrorData>["response"] | CallApiResultSuccessVariant<TComputedData>["response"];
572
- onlyResponseWithException: CallApiResultSuccessVariant<TComputedErrorData>["response"];
627
+ onlyResponseWithException: CallApiResultSuccessVariant<TComputedData>["response"];
573
628
  onlySuccess: CallApiResultErrorVariant<TComputedErrorData>["data"] | CallApiResultSuccessVariant<TComputedData>["data"];
574
629
  onlySuccessWithException: CallApiResultSuccessVariant<TComputedData>["data"];
575
630
  }>;
@@ -89,7 +89,7 @@ var HTTPError = class extends Error {
89
89
  }
90
90
  };
91
91
 
92
- // src/utils/type-guards.ts
92
+ // src/utils/guards.ts
93
93
  var isHTTPErrorInstance = (error) => {
94
94
  return (
95
95
  // prettier-ignore
@@ -97,6 +97,7 @@ var isHTTPErrorInstance = (error) => {
97
97
  );
98
98
  };
99
99
  var isArray = (value) => Array.isArray(value);
100
+ var isObject = (value) => typeof value === "object" && value !== null;
100
101
  var hasObjectPrototype = (value) => {
101
102
  return Object.prototype.toString.call(value) === "[object Object]";
102
103
  };
@@ -137,6 +138,9 @@ var isSerializable = (value) => {
137
138
  var isFunction = (value) => typeof value === "function";
138
139
  var isQueryString = (value) => isString(value) && value.includes("=");
139
140
  var isString = (value) => typeof value === "string";
141
+ var isReadableStream = (value) => {
142
+ return value instanceof ReadableStream;
143
+ };
140
144
 
141
145
  // src/auth.ts
142
146
  var getValue = (value) => {
@@ -186,6 +190,7 @@ var optionsEnumToOmitFromBase = defineEnum(["extend", "dedupeKey"]);
186
190
  var fetchSpecificKeys = defineEnum([
187
191
  "body",
188
192
  "integrity",
193
+ "duplex",
189
194
  "method",
190
195
  "headers",
191
196
  "signal",
@@ -328,6 +333,108 @@ var waitUntil = (delay) => {
328
333
  return promise;
329
334
  };
330
335
 
336
+ // src/stream.ts
337
+ var createProgressEvent = (options) => {
338
+ const { chunk, totalBytes, transferredBytes } = options;
339
+ return {
340
+ chunk,
341
+ progress: Math.round(transferredBytes / totalBytes * 100) || 0,
342
+ totalBytes,
343
+ transferredBytes
344
+ };
345
+ };
346
+ var calculateTotalBytesFromBody = async (requestBody, existingTotalBytes) => {
347
+ let totalBytes = existingTotalBytes;
348
+ if (!requestBody) {
349
+ return totalBytes;
350
+ }
351
+ for await (const chunk of requestBody) {
352
+ totalBytes += chunk.byteLength;
353
+ }
354
+ return totalBytes;
355
+ };
356
+ var toStreamableRequest = async (context) => {
357
+ const { options, request, requestInstance } = context;
358
+ if (!options.onRequestStream || !requestInstance.body) return;
359
+ const contentLength = requestInstance.headers.get("content-length") ?? new Headers(request.headers).get("content-length") ?? request.body?.size;
360
+ let totalBytes = Number(contentLength ?? 0);
361
+ const shouldForceContentLengthCalc = isObject(options.forceStreamSizeCalc) ? options.forceStreamSizeCalc.request : options.forceStreamSizeCalc;
362
+ if (!contentLength && shouldForceContentLengthCalc) {
363
+ totalBytes = await calculateTotalBytesFromBody(requestInstance.clone().body, totalBytes);
364
+ }
365
+ let transferredBytes = 0;
366
+ await executeHooks(
367
+ options.onRequestStream({
368
+ event: createProgressEvent({ chunk: new Uint8Array(), totalBytes, transferredBytes }),
369
+ options,
370
+ request,
371
+ requestInstance
372
+ })
373
+ );
374
+ const body = requestInstance.body;
375
+ void new ReadableStream({
376
+ start: async (controller) => {
377
+ if (!body) return;
378
+ for await (const chunk of body) {
379
+ transferredBytes += chunk.byteLength;
380
+ totalBytes = Math.max(totalBytes, transferredBytes);
381
+ await executeHooks(
382
+ options.onRequestStream?.({
383
+ event: createProgressEvent({ chunk, totalBytes, transferredBytes }),
384
+ options,
385
+ request,
386
+ requestInstance
387
+ })
388
+ );
389
+ controller.enqueue(chunk);
390
+ }
391
+ controller.close();
392
+ }
393
+ });
394
+ };
395
+ var toStreamableResponse = async (context) => {
396
+ const { options, request, response } = context;
397
+ if (!options.onResponseStream || !response.body) {
398
+ return response;
399
+ }
400
+ const contentLength = response.headers.get("content-length");
401
+ let totalBytes = Number(contentLength ?? 0);
402
+ const shouldForceContentLengthCalc = isObject(options.forceStreamSizeCalc) ? options.forceStreamSizeCalc.response : options.forceStreamSizeCalc;
403
+ if (!contentLength && shouldForceContentLengthCalc) {
404
+ totalBytes = await calculateTotalBytesFromBody(response.clone().body, totalBytes);
405
+ }
406
+ let transferredBytes = 0;
407
+ await executeHooks(
408
+ options.onResponseStream({
409
+ event: createProgressEvent({ chunk: new Uint8Array(), totalBytes, transferredBytes }),
410
+ options,
411
+ request,
412
+ response
413
+ })
414
+ );
415
+ const body = response.body;
416
+ const stream = new ReadableStream({
417
+ start: async (controller) => {
418
+ if (!body) return;
419
+ for await (const chunk of body) {
420
+ transferredBytes += chunk.byteLength;
421
+ totalBytes = Math.max(totalBytes, transferredBytes);
422
+ await executeHooks(
423
+ options.onResponseStream?.({
424
+ event: createProgressEvent({ chunk, totalBytes, transferredBytes }),
425
+ options,
426
+ request,
427
+ response
428
+ })
429
+ );
430
+ controller.enqueue(chunk);
431
+ }
432
+ controller.close();
433
+ }
434
+ });
435
+ return new Response(stream, response);
436
+ };
437
+
331
438
  // src/dedupe.ts
332
439
  var createDedupeStrategy = async (context) => {
333
440
  const { $RequestInfoCache, newFetchController, options, request } = context;
@@ -352,12 +459,29 @@ var createDedupeStrategy = async (context) => {
352
459
  prevRequestInfo.controller.abort(reason);
353
460
  return Promise.resolve();
354
461
  };
355
- const handleRequestDeferStrategy = () => {
462
+ const handleRequestDeferStrategy = async () => {
356
463
  const fetchApi = getFetchImpl(options.customFetchImpl);
357
464
  const shouldUsePromiseFromCache = prevRequestInfo && options.dedupeStrategy === "defer";
358
- const responsePromise = shouldUsePromiseFromCache ? prevRequestInfo.responsePromise : fetchApi(options.fullURL, request);
465
+ const requestInstance = new Request(
466
+ options.fullURL,
467
+ isReadableStream(request.body) && !request.duplex ? { ...request, duplex: "half" } : request
468
+ );
469
+ void toStreamableRequest({
470
+ options,
471
+ request,
472
+ requestInstance: requestInstance.clone()
473
+ });
474
+ const responsePromise = shouldUsePromiseFromCache ? prevRequestInfo.responsePromise : (
475
+ // eslint-disable-next-line unicorn/no-nested-ternary -- Allow
476
+ isReadableStream(request.body) ? fetchApi(requestInstance.clone()) : fetchApi(options.fullURL, request)
477
+ );
359
478
  $RequestInfoCacheOrNull?.set(dedupeKey, { controller: newFetchController, responsePromise });
360
- return responsePromise;
479
+ const streamableResponse = toStreamableResponse({
480
+ options,
481
+ request,
482
+ response: await responsePromise
483
+ });
484
+ return streamableResponse;
361
485
  };
362
486
  const removeDedupeKeyFromCache = () => $RequestInfoCacheOrNull?.delete(dedupeKey);
363
487
  return {
@@ -372,7 +496,8 @@ var definePlugin = (plugin) => {
372
496
  return plugin;
373
497
  };
374
498
  var createMergedHook = (hooks, mergedHooksExecutionMode) => {
375
- return async (ctx) => {
499
+ if (hooks.length === 0) return;
500
+ const mergedHook = async (ctx) => {
376
501
  if (mergedHooksExecutionMode === "sequential") {
377
502
  for (const hook of hooks) {
378
503
  await hook?.(ctx);
@@ -384,13 +509,16 @@ var createMergedHook = (hooks, mergedHooksExecutionMode) => {
384
509
  await Promise.all(hookArray.map((uniqueHook) => uniqueHook?.(ctx)));
385
510
  }
386
511
  };
512
+ return mergedHook;
387
513
  };
388
514
  var hooksEnum = {
389
515
  onError: /* @__PURE__ */ new Set(),
390
516
  onRequest: /* @__PURE__ */ new Set(),
391
517
  onRequestError: /* @__PURE__ */ new Set(),
518
+ onRequestStream: /* @__PURE__ */ new Set(),
392
519
  onResponse: /* @__PURE__ */ new Set(),
393
520
  onResponseError: /* @__PURE__ */ new Set(),
521
+ onResponseStream: /* @__PURE__ */ new Set(),
394
522
  onRetry: /* @__PURE__ */ new Set(),
395
523
  onSuccess: /* @__PURE__ */ new Set()
396
524
  };
@@ -455,7 +583,7 @@ var initializePlugins = async (context) => {
455
583
  }
456
584
  const resolvedHooks = {};
457
585
  for (const [key, hookRegistry] of Object.entries(hookRegistries)) {
458
- const flattenedHookArray = [...hookRegistry].flat();
586
+ const flattenedHookArray = [...hookRegistry].flat().filter(Boolean);
459
587
  const mergedHook = createMergedHook(flattenedHookArray, options.mergedHooksExecutionMode);
460
588
  resolvedHooks[key] = mergedHook;
461
589
  }
@@ -517,45 +645,45 @@ var getExponentialDelay = (currentAttemptCount, options) => {
517
645
  return Math.min(exponentialDelay, maxDelay);
518
646
  };
519
647
  var createRetryStrategy = (ctx) => {
520
- const currentRetryCount = ctx.options["~retryCount"] ?? 0;
648
+ const { options } = ctx;
649
+ const currentRetryCount = options["~retryCount"] ?? 0;
521
650
  const getDelay = () => {
522
- if (ctx.options.retryStrategy === "exponential") {
523
- return getExponentialDelay(currentRetryCount, ctx.options);
651
+ if (options.retryStrategy === "exponential") {
652
+ return getExponentialDelay(currentRetryCount, options);
524
653
  }
525
- return getLinearDelay(ctx.options);
654
+ return getLinearDelay(options);
526
655
  };
527
656
  const shouldAttemptRetry = async () => {
528
- const customRetryCondition = await ctx.options.retryCondition?.(ctx) ?? true;
529
- const maxRetryAttempts = ctx.options.retryAttempts ?? 0;
657
+ const customRetryCondition = await options.retryCondition?.(ctx) ?? true;
658
+ const maxRetryAttempts = options.retryAttempts ?? 0;
530
659
  const baseRetryCondition = maxRetryAttempts > currentRetryCount && customRetryCondition;
531
660
  if (ctx.error.name !== "HTTPError") {
532
661
  return baseRetryCondition;
533
662
  }
534
663
  const includesMethod = (
535
664
  // eslint-disable-next-line no-implicit-coercion -- Boolean doesn't narrow
536
- !!ctx.request.method && ctx.options.retryMethods?.includes(ctx.request.method)
665
+ !!ctx.request.method && options.retryMethods?.includes(ctx.request.method)
537
666
  );
538
667
  const includesCodes = (
539
668
  // eslint-disable-next-line no-implicit-coercion -- Boolean doesn't narrow
540
- !!ctx.response?.status && ctx.options.retryStatusCodes?.includes(ctx.response.status)
669
+ !!ctx.response?.status && options.retryStatusCodes?.includes(ctx.response.status)
541
670
  );
542
671
  return includesCodes && includesMethod && baseRetryCondition;
543
672
  };
544
673
  const executeRetryHook = async (shouldThrowOnError) => {
545
674
  try {
546
- if (!isFunction(ctx.options.onRetry)) return;
547
- await executeHooks(ctx.options.onRetry(ctx));
675
+ return await executeHooks(options.onRetry?.(ctx));
548
676
  } catch (error) {
549
- const { getErrorResult } = resolveErrorResult({
550
- cloneResponse: ctx.options.cloneResponse,
551
- defaultErrorMessage: ctx.options.defaultErrorMessage,
677
+ const { apiDetails } = resolveErrorResult({
678
+ cloneResponse: options.cloneResponse,
679
+ defaultErrorMessage: options.defaultErrorMessage,
552
680
  error,
553
- resultMode: ctx.options.resultMode
681
+ resultMode: options.resultMode
554
682
  });
555
683
  if (shouldThrowOnError) {
556
684
  throw error;
557
685
  }
558
- return getErrorResult();
686
+ return apiDetails;
559
687
  }
560
688
  };
561
689
  return {
@@ -698,7 +826,7 @@ var createFetchClient = (baseConfig = {}) => {
698
826
  const { handleRequestCancelStrategy, handleRequestDeferStrategy, removeDedupeKeyFromCache } = await createDedupeStrategy({ $RequestInfoCache, newFetchController, options, request });
699
827
  await handleRequestCancelStrategy();
700
828
  try {
701
- await executeHooks(options.onRequest({ options, request }));
829
+ await executeHooks(options.onRequest?.({ options, request }));
702
830
  request.headers = mergeAndResolveHeaders({
703
831
  auth: options.auth,
704
832
  body: request.body,
@@ -734,11 +862,11 @@ var createFetchClient = (baseConfig = {}) => {
734
862
  data: validSuccessData,
735
863
  options,
736
864
  request,
737
- response: options.cloneResponse ? response.clone() : response
865
+ response
738
866
  };
739
867
  await executeHooks(
740
- options.onSuccess(successContext),
741
- options.onResponse({ ...successContext, error: null })
868
+ options.onSuccess?.(successContext),
869
+ options.onResponse?.({ ...successContext, error: null })
742
870
  );
743
871
  return await resolveSuccessResult({
744
872
  data: successContext.data,
@@ -773,33 +901,33 @@ var createFetchClient = (baseConfig = {}) => {
773
901
  return callApi2(initURL, updatedOptions);
774
902
  }
775
903
  if (shouldThrowOnError) {
776
- throw apiDetails.error;
904
+ throw error;
777
905
  }
778
906
  return customInfo ? getErrorResult(customInfo) : getErrorResult();
779
907
  };
780
908
  if (isHTTPErrorInstance(error)) {
781
909
  await executeHooks(
782
- options.onResponseError(errorContext),
783
- options.onError(errorContext),
784
- options.onResponse({ ...errorContext, data: null })
910
+ options.onResponseError?.(errorContext),
911
+ options.onError?.(errorContext),
912
+ options.onResponse?.({ ...errorContext, data: null })
785
913
  );
786
914
  return await handleRetryOrGetResult();
787
915
  }
788
916
  if (error instanceof DOMException && error.name === "AbortError") {
789
917
  const { message, name } = error;
790
- console.error(`${name}:`, message);
918
+ !shouldThrowOnError && console.error(`${name}:`, message);
791
919
  return await handleRetryOrGetResult();
792
920
  }
793
921
  if (error instanceof DOMException && error.name === "TimeoutError") {
794
922
  const message = `Request timed out after ${options.timeout}ms`;
795
- console.error(`${error.name}:`, message);
923
+ !shouldThrowOnError && console.error(`${error.name}:`, message);
796
924
  return await handleRetryOrGetResult({ message });
797
925
  }
798
926
  await executeHooks(
799
927
  // == At this point only the request errors exist, so the request error interceptor is called
800
- options.onRequestError(errorContext),
928
+ options.onRequestError?.(errorContext),
801
929
  // == Also call the onError interceptor
802
- options.onError(errorContext)
930
+ options.onError?.(errorContext)
803
931
  );
804
932
  return await handleRetryOrGetResult();
805
933
  } finally {