@zayne-labs/callapi 1.6.22 → 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 +16 -5
- package/dist/cjs/{error-DIHsfUiJ.d.cts → error-C7M77J6L.d.cts} +59 -4
- package/dist/cjs/index.cjs +190 -52
- package/dist/cjs/index.cjs.map +1 -1
- package/dist/cjs/index.d.cts +2 -2
- package/dist/cjs/utils/index.cjs +1 -1
- package/dist/cjs/utils/index.cjs.map +1 -1
- package/dist/cjs/utils/index.d.cts +1 -1
- package/dist/esm/{chunk-JZ6RQGDZ.js → chunk-PAYXYIB5.js} +17 -7
- package/dist/esm/chunk-PAYXYIB5.js.map +1 -0
- package/dist/esm/{error-DIHsfUiJ.d.ts → error-C7M77J6L.d.ts} +59 -4
- package/dist/esm/index.d.ts +2 -2
- package/dist/esm/index.js +178 -50
- package/dist/esm/index.js.map +1 -1
- package/dist/esm/utils/index.d.ts +1 -1
- package/dist/esm/utils/index.js +1 -1
- package/package.json +5 -5
- package/dist/esm/chunk-JZ6RQGDZ.js.map +0 -1
package/README.md
CHANGED
|
@@ -1,8 +1,19 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
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
|
-
|
|
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<
|
|
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<
|
|
627
|
+
onlyResponseWithException: CallApiResultSuccessVariant<TComputedData>["response"];
|
|
573
628
|
onlySuccess: CallApiResultErrorVariant<TComputedErrorData>["data"] | CallApiResultSuccessVariant<TComputedData>["data"];
|
|
574
629
|
onlySuccessWithException: CallApiResultSuccessVariant<TComputedData>["data"];
|
|
575
630
|
}>;
|
package/dist/cjs/index.cjs
CHANGED
|
@@ -63,9 +63,15 @@ var resolveErrorResult = (info) => {
|
|
|
63
63
|
onlySuccess: apiDetails.data,
|
|
64
64
|
onlySuccessWithException: apiDetails.data
|
|
65
65
|
};
|
|
66
|
-
const getErrorResult = (
|
|
67
|
-
const
|
|
68
|
-
return
|
|
66
|
+
const getErrorResult = (customErrorInfo) => {
|
|
67
|
+
const errorVariantResult = resultModeMap[resultMode ?? "all"];
|
|
68
|
+
return customErrorInfo ? {
|
|
69
|
+
...errorVariantResult,
|
|
70
|
+
error: {
|
|
71
|
+
...errorVariantResult.error,
|
|
72
|
+
...customErrorInfo
|
|
73
|
+
}
|
|
74
|
+
} : errorVariantResult;
|
|
69
75
|
};
|
|
70
76
|
return { apiDetails, getErrorResult };
|
|
71
77
|
};
|
|
@@ -83,7 +89,7 @@ var HTTPError = class extends Error {
|
|
|
83
89
|
}
|
|
84
90
|
};
|
|
85
91
|
|
|
86
|
-
// src/utils/
|
|
92
|
+
// src/utils/guards.ts
|
|
87
93
|
var isHTTPErrorInstance = (error) => {
|
|
88
94
|
return (
|
|
89
95
|
// prettier-ignore
|
|
@@ -132,6 +138,9 @@ var isSerializable = (value) => {
|
|
|
132
138
|
var isFunction = (value) => typeof value === "function";
|
|
133
139
|
var isQueryString = (value) => isString(value) && value.includes("=");
|
|
134
140
|
var isString = (value) => typeof value === "string";
|
|
141
|
+
var isReadableStream = (value) => {
|
|
142
|
+
return value instanceof ReadableStream;
|
|
143
|
+
};
|
|
135
144
|
|
|
136
145
|
// src/auth.ts
|
|
137
146
|
var getValue = (value) => {
|
|
@@ -181,6 +190,7 @@ var optionsEnumToOmitFromBase = defineEnum(["extend", "dedupeKey"]);
|
|
|
181
190
|
var fetchSpecificKeys = defineEnum([
|
|
182
191
|
"body",
|
|
183
192
|
"integrity",
|
|
193
|
+
"duplex",
|
|
184
194
|
"method",
|
|
185
195
|
"headers",
|
|
186
196
|
"signal",
|
|
@@ -323,6 +333,108 @@ var waitUntil = (delay) => {
|
|
|
323
333
|
return promise;
|
|
324
334
|
};
|
|
325
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
|
+
|
|
326
438
|
// src/dedupe.ts
|
|
327
439
|
var createDedupeStrategy = async (context) => {
|
|
328
440
|
const { $RequestInfoCache, newFetchController, options, request } = context;
|
|
@@ -347,12 +459,29 @@ var createDedupeStrategy = async (context) => {
|
|
|
347
459
|
prevRequestInfo.controller.abort(reason);
|
|
348
460
|
return Promise.resolve();
|
|
349
461
|
};
|
|
350
|
-
const handleRequestDeferStrategy = () => {
|
|
462
|
+
const handleRequestDeferStrategy = async () => {
|
|
351
463
|
const fetchApi = getFetchImpl(options.customFetchImpl);
|
|
352
464
|
const shouldUsePromiseFromCache = prevRequestInfo && options.dedupeStrategy === "defer";
|
|
353
|
-
const
|
|
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
|
+
);
|
|
354
478
|
$RequestInfoCacheOrNull?.set(dedupeKey, { controller: newFetchController, responsePromise });
|
|
355
|
-
|
|
479
|
+
const streamableResponse = toStreamableResponse({
|
|
480
|
+
options,
|
|
481
|
+
request,
|
|
482
|
+
response: await responsePromise
|
|
483
|
+
});
|
|
484
|
+
return streamableResponse;
|
|
356
485
|
};
|
|
357
486
|
const removeDedupeKeyFromCache = () => $RequestInfoCacheOrNull?.delete(dedupeKey);
|
|
358
487
|
return {
|
|
@@ -367,7 +496,8 @@ var definePlugin = (plugin) => {
|
|
|
367
496
|
return plugin;
|
|
368
497
|
};
|
|
369
498
|
var createMergedHook = (hooks, mergedHooksExecutionMode) => {
|
|
370
|
-
|
|
499
|
+
if (hooks.length === 0) return;
|
|
500
|
+
const mergedHook = async (ctx) => {
|
|
371
501
|
if (mergedHooksExecutionMode === "sequential") {
|
|
372
502
|
for (const hook of hooks) {
|
|
373
503
|
await hook?.(ctx);
|
|
@@ -379,13 +509,16 @@ var createMergedHook = (hooks, mergedHooksExecutionMode) => {
|
|
|
379
509
|
await Promise.all(hookArray.map((uniqueHook) => uniqueHook?.(ctx)));
|
|
380
510
|
}
|
|
381
511
|
};
|
|
512
|
+
return mergedHook;
|
|
382
513
|
};
|
|
383
514
|
var hooksEnum = {
|
|
384
515
|
onError: /* @__PURE__ */ new Set(),
|
|
385
516
|
onRequest: /* @__PURE__ */ new Set(),
|
|
386
517
|
onRequestError: /* @__PURE__ */ new Set(),
|
|
518
|
+
onRequestStream: /* @__PURE__ */ new Set(),
|
|
387
519
|
onResponse: /* @__PURE__ */ new Set(),
|
|
388
520
|
onResponseError: /* @__PURE__ */ new Set(),
|
|
521
|
+
onResponseStream: /* @__PURE__ */ new Set(),
|
|
389
522
|
onRetry: /* @__PURE__ */ new Set(),
|
|
390
523
|
onSuccess: /* @__PURE__ */ new Set()
|
|
391
524
|
};
|
|
@@ -450,7 +583,7 @@ var initializePlugins = async (context) => {
|
|
|
450
583
|
}
|
|
451
584
|
const resolvedHooks = {};
|
|
452
585
|
for (const [key, hookRegistry] of Object.entries(hookRegistries)) {
|
|
453
|
-
const flattenedHookArray = [...hookRegistry].flat();
|
|
586
|
+
const flattenedHookArray = [...hookRegistry].flat().filter(Boolean);
|
|
454
587
|
const mergedHook = createMergedHook(flattenedHookArray, options.mergedHooksExecutionMode);
|
|
455
588
|
resolvedHooks[key] = mergedHook;
|
|
456
589
|
}
|
|
@@ -512,31 +645,49 @@ var getExponentialDelay = (currentAttemptCount, options) => {
|
|
|
512
645
|
return Math.min(exponentialDelay, maxDelay);
|
|
513
646
|
};
|
|
514
647
|
var createRetryStrategy = (ctx) => {
|
|
515
|
-
const
|
|
648
|
+
const { options } = ctx;
|
|
649
|
+
const currentRetryCount = options["~retryCount"] ?? 0;
|
|
516
650
|
const getDelay = () => {
|
|
517
|
-
if (
|
|
518
|
-
return getExponentialDelay(currentRetryCount,
|
|
651
|
+
if (options.retryStrategy === "exponential") {
|
|
652
|
+
return getExponentialDelay(currentRetryCount, options);
|
|
519
653
|
}
|
|
520
|
-
return getLinearDelay(
|
|
654
|
+
return getLinearDelay(options);
|
|
521
655
|
};
|
|
522
656
|
const shouldAttemptRetry = async () => {
|
|
523
|
-
const customRetryCondition = await
|
|
524
|
-
const maxRetryAttempts =
|
|
657
|
+
const customRetryCondition = await options.retryCondition?.(ctx) ?? true;
|
|
658
|
+
const maxRetryAttempts = options.retryAttempts ?? 0;
|
|
525
659
|
const baseRetryCondition = maxRetryAttempts > currentRetryCount && customRetryCondition;
|
|
526
660
|
if (ctx.error.name !== "HTTPError") {
|
|
527
661
|
return baseRetryCondition;
|
|
528
662
|
}
|
|
529
663
|
const includesMethod = (
|
|
530
664
|
// eslint-disable-next-line no-implicit-coercion -- Boolean doesn't narrow
|
|
531
|
-
!!ctx.request.method &&
|
|
665
|
+
!!ctx.request.method && options.retryMethods?.includes(ctx.request.method)
|
|
532
666
|
);
|
|
533
667
|
const includesCodes = (
|
|
534
668
|
// eslint-disable-next-line no-implicit-coercion -- Boolean doesn't narrow
|
|
535
|
-
!!ctx.response?.status &&
|
|
669
|
+
!!ctx.response?.status && options.retryStatusCodes?.includes(ctx.response.status)
|
|
536
670
|
);
|
|
537
671
|
return includesCodes && includesMethod && baseRetryCondition;
|
|
538
672
|
};
|
|
673
|
+
const executeRetryHook = async (shouldThrowOnError) => {
|
|
674
|
+
try {
|
|
675
|
+
return await executeHooks(options.onRetry?.(ctx));
|
|
676
|
+
} catch (error) {
|
|
677
|
+
const { apiDetails } = resolveErrorResult({
|
|
678
|
+
cloneResponse: options.cloneResponse,
|
|
679
|
+
defaultErrorMessage: options.defaultErrorMessage,
|
|
680
|
+
error,
|
|
681
|
+
resultMode: options.resultMode
|
|
682
|
+
});
|
|
683
|
+
if (shouldThrowOnError) {
|
|
684
|
+
throw error;
|
|
685
|
+
}
|
|
686
|
+
return apiDetails;
|
|
687
|
+
}
|
|
688
|
+
};
|
|
539
689
|
return {
|
|
690
|
+
executeRetryHook,
|
|
540
691
|
getDelay,
|
|
541
692
|
shouldAttemptRetry
|
|
542
693
|
};
|
|
@@ -675,7 +826,7 @@ var createFetchClient = (baseConfig = {}) => {
|
|
|
675
826
|
const { handleRequestCancelStrategy, handleRequestDeferStrategy, removeDedupeKeyFromCache } = await createDedupeStrategy({ $RequestInfoCache, newFetchController, options, request });
|
|
676
827
|
await handleRequestCancelStrategy();
|
|
677
828
|
try {
|
|
678
|
-
await executeHooks(options.onRequest({ options, request }));
|
|
829
|
+
await executeHooks(options.onRequest?.({ options, request }));
|
|
679
830
|
request.headers = mergeAndResolveHeaders({
|
|
680
831
|
auth: options.auth,
|
|
681
832
|
body: request.body,
|
|
@@ -711,11 +862,11 @@ var createFetchClient = (baseConfig = {}) => {
|
|
|
711
862
|
data: validSuccessData,
|
|
712
863
|
options,
|
|
713
864
|
request,
|
|
714
|
-
response
|
|
865
|
+
response
|
|
715
866
|
};
|
|
716
867
|
await executeHooks(
|
|
717
|
-
options.onSuccess(successContext),
|
|
718
|
-
options.onResponse({ ...successContext, error: null })
|
|
868
|
+
options.onSuccess?.(successContext),
|
|
869
|
+
options.onResponse?.({ ...successContext, error: null })
|
|
719
870
|
);
|
|
720
871
|
return await resolveSuccessResult({
|
|
721
872
|
data: successContext.data,
|
|
@@ -736,26 +887,11 @@ var createFetchClient = (baseConfig = {}) => {
|
|
|
736
887
|
response: apiDetails.response
|
|
737
888
|
};
|
|
738
889
|
const shouldThrowOnError = isFunction(options.throwOnError) ? options.throwOnError(errorContext) : options.throwOnError;
|
|
739
|
-
const
|
|
740
|
-
|
|
741
|
-
throw errorObject;
|
|
742
|
-
};
|
|
743
|
-
const handleRetryAndGetResult = async (customInfo = {}) => {
|
|
744
|
-
const { getDelay, shouldAttemptRetry } = createRetryStrategy(errorContext);
|
|
890
|
+
const handleRetryOrGetResult = async (customInfo) => {
|
|
891
|
+
const { executeRetryHook, getDelay, shouldAttemptRetry } = createRetryStrategy(errorContext);
|
|
745
892
|
const shouldRetry = !combinedSignal.aborted && await shouldAttemptRetry();
|
|
746
893
|
if (shouldRetry) {
|
|
747
|
-
|
|
748
|
-
await executeHooks(options.onRetry(errorContext));
|
|
749
|
-
} catch (innerError) {
|
|
750
|
-
const { apiDetails: innerApiDetails, getErrorResult: getInnerErrorResult } = resolveErrorResult({
|
|
751
|
-
cloneResponse: options.cloneResponse,
|
|
752
|
-
defaultErrorMessage: options.defaultErrorMessage,
|
|
753
|
-
error: innerError,
|
|
754
|
-
resultMode: options.resultMode
|
|
755
|
-
});
|
|
756
|
-
handleThrowOnError(innerApiDetails.error);
|
|
757
|
-
return getInnerErrorResult();
|
|
758
|
-
}
|
|
894
|
+
await executeRetryHook(shouldThrowOnError);
|
|
759
895
|
const delay = getDelay();
|
|
760
896
|
await waitUntil(delay);
|
|
761
897
|
const updatedOptions = {
|
|
@@ -764,34 +900,36 @@ var createFetchClient = (baseConfig = {}) => {
|
|
|
764
900
|
};
|
|
765
901
|
return callApi2(initURL, updatedOptions);
|
|
766
902
|
}
|
|
767
|
-
|
|
768
|
-
|
|
903
|
+
if (shouldThrowOnError) {
|
|
904
|
+
throw error;
|
|
905
|
+
}
|
|
906
|
+
return customInfo ? getErrorResult(customInfo) : getErrorResult();
|
|
769
907
|
};
|
|
770
908
|
if (isHTTPErrorInstance(error)) {
|
|
771
909
|
await executeHooks(
|
|
772
|
-
options.onResponseError(errorContext),
|
|
773
|
-
options.onError(errorContext),
|
|
774
|
-
options.onResponse({ ...errorContext, data: null })
|
|
910
|
+
options.onResponseError?.(errorContext),
|
|
911
|
+
options.onError?.(errorContext),
|
|
912
|
+
options.onResponse?.({ ...errorContext, data: null })
|
|
775
913
|
);
|
|
776
|
-
return await
|
|
914
|
+
return await handleRetryOrGetResult();
|
|
777
915
|
}
|
|
778
916
|
if (error instanceof DOMException && error.name === "AbortError") {
|
|
779
917
|
const { message, name } = error;
|
|
780
|
-
console.error(`${name}:`, message);
|
|
781
|
-
return await
|
|
918
|
+
!shouldThrowOnError && console.error(`${name}:`, message);
|
|
919
|
+
return await handleRetryOrGetResult();
|
|
782
920
|
}
|
|
783
921
|
if (error instanceof DOMException && error.name === "TimeoutError") {
|
|
784
922
|
const message = `Request timed out after ${options.timeout}ms`;
|
|
785
|
-
console.error(`${error.name}:`, message);
|
|
786
|
-
return await
|
|
923
|
+
!shouldThrowOnError && console.error(`${error.name}:`, message);
|
|
924
|
+
return await handleRetryOrGetResult({ message });
|
|
787
925
|
}
|
|
788
926
|
await executeHooks(
|
|
789
927
|
// == At this point only the request errors exist, so the request error interceptor is called
|
|
790
|
-
options.onRequestError(errorContext),
|
|
928
|
+
options.onRequestError?.(errorContext),
|
|
791
929
|
// == Also call the onError interceptor
|
|
792
|
-
options.onError(errorContext)
|
|
930
|
+
options.onError?.(errorContext)
|
|
793
931
|
);
|
|
794
|
-
return await
|
|
932
|
+
return await handleRetryOrGetResult();
|
|
795
933
|
} finally {
|
|
796
934
|
removeDedupeKeyFromCache();
|
|
797
935
|
}
|