@aligent/microservice-util-lib 1.1.0 → 1.2.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/package.json +4 -4
- package/src/index.d.ts +3 -2
- package/src/index.js +3 -1
- package/src/openapi-fetch-middlewares/retry.d.ts +54 -0
- package/src/openapi-fetch-middlewares/retry.js +218 -0
- package/src/openapi-fetch-middlewares/types/retry.d.ts +93 -0
- package/src/openapi-fetch-middlewares/types/retry.js +2 -0
- package/src/openapi-fetch-middlewares/utils/is-network-error.d.ts +5 -0
- package/src/openapi-fetch-middlewares/utils/is-network-error.js +40 -0
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@aligent/microservice-util-lib",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.2.0",
|
|
4
4
|
"description": "A set of utility functions for Aligent Microservices",
|
|
5
5
|
"type": "commonjs",
|
|
6
6
|
"main": "./src/index.js",
|
|
@@ -11,11 +11,11 @@
|
|
|
11
11
|
"directory": "packages/microservice-util-lib"
|
|
12
12
|
},
|
|
13
13
|
"dependencies": {
|
|
14
|
-
"@aws-sdk/client-s3": "^3.
|
|
15
|
-
"@aws-sdk/client-ssm": "^3.
|
|
14
|
+
"@aws-sdk/client-s3": "^3.922.0",
|
|
15
|
+
"@aws-sdk/client-ssm": "^3.922.0",
|
|
16
16
|
"oauth-sign": "^0.9.0",
|
|
17
17
|
"object-hash": "^3.0.0",
|
|
18
|
-
"openapi-fetch": "^0.
|
|
18
|
+
"openapi-fetch": "^0.15.0"
|
|
19
19
|
},
|
|
20
20
|
"author": "Aligent",
|
|
21
21
|
"license": "MIT",
|
package/src/index.d.ts
CHANGED
|
@@ -3,8 +3,9 @@ import fetchSsmParams from './fetch-ssm-params/fetch-ssm-params';
|
|
|
3
3
|
import getAwsIdFromArn from './get-aws-id-from-arn/get-aws-id-from-arn';
|
|
4
4
|
import hasDefinedProperties from './has-properties-defined/has-properties-defined';
|
|
5
5
|
import { ApiKey, Basic, OAuth10a, OAuth20, apiKeyAuthMiddleware, basicAuthMiddleware, oAuth10aAuthMiddleware, oAuth20AuthMiddleware } from './openapi-fetch-middlewares/authentications';
|
|
6
|
+
import { RetryConfig as RetryMiddlewareConfig, retryMiddleware } from './openapi-fetch-middlewares/retry';
|
|
6
7
|
import remap, { ObjectMap, Remap } from './remap/remap';
|
|
7
8
|
import retryWrapper, { RetryConfig } from './retry-wrapper/retry-wrapper';
|
|
8
9
|
import S3Dao from './s3/s3';
|
|
9
|
-
export type { ApiKey, Basic, OAuth10a, OAuth20, ObjectMap, Remap, RetryConfig, S3Dao };
|
|
10
|
-
export { apiKeyAuthMiddleware, basicAuthMiddleware, chunkBy, fetchSsmParams, getAwsIdFromArn, hasDefinedProperties, oAuth10aAuthMiddleware, oAuth20AuthMiddleware, remap, retryWrapper, };
|
|
10
|
+
export type { ApiKey, Basic, OAuth10a, OAuth20, ObjectMap, Remap, RetryConfig, RetryMiddlewareConfig, S3Dao, };
|
|
11
|
+
export { apiKeyAuthMiddleware, basicAuthMiddleware, chunkBy, fetchSsmParams, getAwsIdFromArn, hasDefinedProperties, oAuth10aAuthMiddleware, oAuth20AuthMiddleware, remap, retryMiddleware, retryWrapper, };
|
package/src/index.js
CHANGED
|
@@ -3,7 +3,7 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
|
3
3
|
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
4
|
};
|
|
5
5
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
-
exports.retryWrapper = exports.remap = exports.oAuth20AuthMiddleware = exports.oAuth10aAuthMiddleware = exports.hasDefinedProperties = exports.getAwsIdFromArn = exports.fetchSsmParams = exports.chunkBy = exports.basicAuthMiddleware = exports.apiKeyAuthMiddleware = void 0;
|
|
6
|
+
exports.retryWrapper = exports.retryMiddleware = exports.remap = exports.oAuth20AuthMiddleware = exports.oAuth10aAuthMiddleware = exports.hasDefinedProperties = exports.getAwsIdFromArn = exports.fetchSsmParams = exports.chunkBy = exports.basicAuthMiddleware = exports.apiKeyAuthMiddleware = void 0;
|
|
7
7
|
/* v8 ignore start */
|
|
8
8
|
const chunk_by_1 = __importDefault(require("./chunk-by/chunk-by"));
|
|
9
9
|
exports.chunkBy = chunk_by_1.default;
|
|
@@ -18,6 +18,8 @@ Object.defineProperty(exports, "apiKeyAuthMiddleware", { enumerable: true, get:
|
|
|
18
18
|
Object.defineProperty(exports, "basicAuthMiddleware", { enumerable: true, get: function () { return authentications_1.basicAuthMiddleware; } });
|
|
19
19
|
Object.defineProperty(exports, "oAuth10aAuthMiddleware", { enumerable: true, get: function () { return authentications_1.oAuth10aAuthMiddleware; } });
|
|
20
20
|
Object.defineProperty(exports, "oAuth20AuthMiddleware", { enumerable: true, get: function () { return authentications_1.oAuth20AuthMiddleware; } });
|
|
21
|
+
const retry_1 = require("./openapi-fetch-middlewares/retry");
|
|
22
|
+
Object.defineProperty(exports, "retryMiddleware", { enumerable: true, get: function () { return retry_1.retryMiddleware; } });
|
|
21
23
|
const remap_1 = __importDefault(require("./remap/remap"));
|
|
22
24
|
exports.remap = remap_1.default;
|
|
23
25
|
const retry_wrapper_1 = __importDefault(require("./retry-wrapper/retry-wrapper"));
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
import type { Middleware } from 'openapi-fetch';
|
|
2
|
+
import type { RetryConfig, RetryContext, RetryDelayFn } from './types/retry';
|
|
3
|
+
/**
|
|
4
|
+
* This middleware implements retry logic with support for:
|
|
5
|
+
* - Configurable number of retry attempts
|
|
6
|
+
* - Exponential backoff, linear backoff, or custom delay strategies
|
|
7
|
+
* - Custom retry conditions
|
|
8
|
+
* - Callbacks for retry events
|
|
9
|
+
* - Filtering by status codes
|
|
10
|
+
*
|
|
11
|
+
* @param {RetryConfig} [config={}] - The retry configuration.
|
|
12
|
+
* @returns {Middleware} The middleware for retry functionality.
|
|
13
|
+
*
|
|
14
|
+
* @example
|
|
15
|
+
* // Basic usage with defaults (3 retries, exponential backoff)
|
|
16
|
+
* const middleware = retryMiddleware();
|
|
17
|
+
*
|
|
18
|
+
* @example
|
|
19
|
+
* // Custom configuration
|
|
20
|
+
* const middleware = retryMiddleware({
|
|
21
|
+
* retries: 5,
|
|
22
|
+
* retryDelay: 'linear',
|
|
23
|
+
* retryDelayBase: 200,
|
|
24
|
+
* retryOn: [500, 502, 503, 504],
|
|
25
|
+
* onRetry: (context) => {
|
|
26
|
+
* console.log(`Retrying request (attempt ${context.attemptNumber})`);
|
|
27
|
+
* },
|
|
28
|
+
* });
|
|
29
|
+
*
|
|
30
|
+
* @example
|
|
31
|
+
* // Custom retry condition
|
|
32
|
+
* const middleware = retryMiddleware({
|
|
33
|
+
* retries: 3,
|
|
34
|
+
* retryCondition: async (context) => {
|
|
35
|
+
* // Only retry on 503 Service Unavailable
|
|
36
|
+
* return context.response.status === 503;
|
|
37
|
+
* },
|
|
38
|
+
* });
|
|
39
|
+
*
|
|
40
|
+
* @example
|
|
41
|
+
* // Custom delay function
|
|
42
|
+
* const middleware = retryMiddleware({
|
|
43
|
+
* retries: 3,
|
|
44
|
+
* retryDelay: (attemptNumber) => {
|
|
45
|
+
* // Custom delay with jitter
|
|
46
|
+
* const baseDelay = 100 * Math.pow(2, attemptNumber);
|
|
47
|
+
* const jitter = Math.random() * 100;
|
|
48
|
+
* return baseDelay + jitter;
|
|
49
|
+
* },
|
|
50
|
+
* });
|
|
51
|
+
*/
|
|
52
|
+
declare function retryMiddleware(config?: RetryConfig): Middleware;
|
|
53
|
+
export { retryMiddleware };
|
|
54
|
+
export type { RetryConfig, RetryContext, RetryDelayFn };
|
|
@@ -0,0 +1,218 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.retryMiddleware = retryMiddleware;
|
|
4
|
+
const is_network_error_1 = require("./utils/is-network-error");
|
|
5
|
+
const IDEMPOTENT_HTTP_METHODS = ['GET', 'HEAD', 'OPTIONS', 'PUT', 'DELETE'];
|
|
6
|
+
/**
|
|
7
|
+
* Default retry condition function.
|
|
8
|
+
* Retries on:
|
|
9
|
+
* - Network errors
|
|
10
|
+
* - 5xx server errors
|
|
11
|
+
* - 429 Too Many Requests
|
|
12
|
+
* - 408 Request Timeout
|
|
13
|
+
* - Idempotent methods only
|
|
14
|
+
*
|
|
15
|
+
* @param {RetryContext} context - The retry context.
|
|
16
|
+
* @param {boolean} idempotentOnly - Whether to retry only when the HTTP method is an idempotent methods.
|
|
17
|
+
* @returns {boolean} Whether the request should be retried.
|
|
18
|
+
*/
|
|
19
|
+
function defaultRetryCondition(context, idempotentOnly) {
|
|
20
|
+
const { request, response, error } = context;
|
|
21
|
+
if (!IDEMPOTENT_HTTP_METHODS.includes(request.method) && idempotentOnly) {
|
|
22
|
+
return false;
|
|
23
|
+
}
|
|
24
|
+
if ((0, is_network_error_1.isNetworkError)(error)) {
|
|
25
|
+
return true;
|
|
26
|
+
}
|
|
27
|
+
if (response) {
|
|
28
|
+
return response.status >= 500 || response.status === 429 || response.status === 408;
|
|
29
|
+
}
|
|
30
|
+
return false;
|
|
31
|
+
}
|
|
32
|
+
/**
|
|
33
|
+
* Calculates delay for exponential backoff strategy.
|
|
34
|
+
*
|
|
35
|
+
* @param {number} attempt - The current attempt number (1-indexed).
|
|
36
|
+
* @param {number} baseDelay - Base delay in milliseconds.
|
|
37
|
+
* @param {number} maxDelay - Maximum delay in milliseconds.
|
|
38
|
+
* @returns {number} The delay in milliseconds.
|
|
39
|
+
*/
|
|
40
|
+
function exponentialDelay(attempt, baseDelay, maxDelay) {
|
|
41
|
+
const delay = baseDelay * Math.pow(2, attempt - 1);
|
|
42
|
+
return Math.min(delay, maxDelay);
|
|
43
|
+
}
|
|
44
|
+
/**
|
|
45
|
+
* Calculates delay for linear backoff strategy.
|
|
46
|
+
*
|
|
47
|
+
* @param {number} attempt - The current attempt number (1-indexed).
|
|
48
|
+
* @param {number} baseDelay - Base delay in milliseconds.
|
|
49
|
+
* @param {number} maxDelay - Maximum delay in milliseconds.
|
|
50
|
+
* @returns {number} The delay in milliseconds.
|
|
51
|
+
*/
|
|
52
|
+
function linearDelay(attempt, baseDelay, maxDelay) {
|
|
53
|
+
const delay = baseDelay * attempt;
|
|
54
|
+
return Math.min(delay, maxDelay);
|
|
55
|
+
}
|
|
56
|
+
/**
|
|
57
|
+
* Gets the retry delay function based on the configuration.
|
|
58
|
+
*
|
|
59
|
+
* @param {RetryConfig} config - The retry configuration.
|
|
60
|
+
* @returns {RetryDelayFn} The retry delay function.
|
|
61
|
+
*/
|
|
62
|
+
function getRetryDelayFn(config) {
|
|
63
|
+
const baseDelay = config?.baseDelay ?? 100;
|
|
64
|
+
const maxDelay = config?.maxDelay ?? 30000;
|
|
65
|
+
const retryDelay = config?.retryDelay;
|
|
66
|
+
if (typeof retryDelay === 'function') {
|
|
67
|
+
return retryDelay;
|
|
68
|
+
}
|
|
69
|
+
switch (retryDelay) {
|
|
70
|
+
case 'linear':
|
|
71
|
+
return (attempt) => linearDelay(attempt, baseDelay, maxDelay);
|
|
72
|
+
case 'exponential':
|
|
73
|
+
default:
|
|
74
|
+
return (attempt) => exponentialDelay(attempt, baseDelay, maxDelay);
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
/**
|
|
78
|
+
* Checks if the response status should trigger a retry based on the retryOn configuration.
|
|
79
|
+
*
|
|
80
|
+
* @param {number} status - The HTTP status code.
|
|
81
|
+
* @param {number[]} retryOn - Array of status codes to retry on.
|
|
82
|
+
* @returns {boolean} Whether the status should trigger a retry.
|
|
83
|
+
*/
|
|
84
|
+
function shouldRetryOnStatus(status, retryOn) {
|
|
85
|
+
return retryOn.includes(status);
|
|
86
|
+
}
|
|
87
|
+
/**
|
|
88
|
+
* This middleware implements retry logic with support for:
|
|
89
|
+
* - Configurable number of retry attempts
|
|
90
|
+
* - Exponential backoff, linear backoff, or custom delay strategies
|
|
91
|
+
* - Custom retry conditions
|
|
92
|
+
* - Callbacks for retry events
|
|
93
|
+
* - Filtering by status codes
|
|
94
|
+
*
|
|
95
|
+
* @param {RetryConfig} [config={}] - The retry configuration.
|
|
96
|
+
* @returns {Middleware} The middleware for retry functionality.
|
|
97
|
+
*
|
|
98
|
+
* @example
|
|
99
|
+
* // Basic usage with defaults (3 retries, exponential backoff)
|
|
100
|
+
* const middleware = retryMiddleware();
|
|
101
|
+
*
|
|
102
|
+
* @example
|
|
103
|
+
* // Custom configuration
|
|
104
|
+
* const middleware = retryMiddleware({
|
|
105
|
+
* retries: 5,
|
|
106
|
+
* retryDelay: 'linear',
|
|
107
|
+
* retryDelayBase: 200,
|
|
108
|
+
* retryOn: [500, 502, 503, 504],
|
|
109
|
+
* onRetry: (context) => {
|
|
110
|
+
* console.log(`Retrying request (attempt ${context.attemptNumber})`);
|
|
111
|
+
* },
|
|
112
|
+
* });
|
|
113
|
+
*
|
|
114
|
+
* @example
|
|
115
|
+
* // Custom retry condition
|
|
116
|
+
* const middleware = retryMiddleware({
|
|
117
|
+
* retries: 3,
|
|
118
|
+
* retryCondition: async (context) => {
|
|
119
|
+
* // Only retry on 503 Service Unavailable
|
|
120
|
+
* return context.response.status === 503;
|
|
121
|
+
* },
|
|
122
|
+
* });
|
|
123
|
+
*
|
|
124
|
+
* @example
|
|
125
|
+
* // Custom delay function
|
|
126
|
+
* const middleware = retryMiddleware({
|
|
127
|
+
* retries: 3,
|
|
128
|
+
* retryDelay: (attemptNumber) => {
|
|
129
|
+
* // Custom delay with jitter
|
|
130
|
+
* const baseDelay = 100 * Math.pow(2, attemptNumber);
|
|
131
|
+
* const jitter = Math.random() * 100;
|
|
132
|
+
* return baseDelay + jitter;
|
|
133
|
+
* },
|
|
134
|
+
* });
|
|
135
|
+
*/
|
|
136
|
+
function retryMiddleware(config) {
|
|
137
|
+
const normalisedConfig = {
|
|
138
|
+
...config,
|
|
139
|
+
retries: config?.retries ?? 3,
|
|
140
|
+
retryCondition: config?.retryCondition ?? defaultRetryCondition,
|
|
141
|
+
retryDelay: getRetryDelayFn(config),
|
|
142
|
+
idempotentOnly: config?.idempotentOnly ?? true,
|
|
143
|
+
fetch: config?.fetch ?? fetch,
|
|
144
|
+
};
|
|
145
|
+
return {
|
|
146
|
+
async onResponse({ request, response }) {
|
|
147
|
+
const context = { attempt: 1, request, response, error: null };
|
|
148
|
+
// If retryOn is specified, only use that list
|
|
149
|
+
if (config?.retryOn && config.retryOn.length > 0) {
|
|
150
|
+
if (!shouldRetryOnStatus(response.status, config.retryOn)) {
|
|
151
|
+
return context.response;
|
|
152
|
+
}
|
|
153
|
+
return await performRetries(normalisedConfig, context);
|
|
154
|
+
}
|
|
155
|
+
// Otherwise, check if we should retry based on retry condition
|
|
156
|
+
const shouldRetry = await normalisedConfig.retryCondition(context, normalisedConfig.idempotentOnly);
|
|
157
|
+
if (!shouldRetry) {
|
|
158
|
+
return context.response;
|
|
159
|
+
}
|
|
160
|
+
return await performRetries(normalisedConfig, context);
|
|
161
|
+
},
|
|
162
|
+
async onError({ request, error }) {
|
|
163
|
+
if (!(0, is_network_error_1.isNetworkError)(error)) {
|
|
164
|
+
throw error;
|
|
165
|
+
}
|
|
166
|
+
return await performRetries(normalisedConfig, {
|
|
167
|
+
attempt: 1,
|
|
168
|
+
request,
|
|
169
|
+
response: null,
|
|
170
|
+
error,
|
|
171
|
+
});
|
|
172
|
+
},
|
|
173
|
+
};
|
|
174
|
+
}
|
|
175
|
+
/**
|
|
176
|
+
* Performs the retry attempts.
|
|
177
|
+
*/
|
|
178
|
+
async function performRetries(config, context) {
|
|
179
|
+
const maxRetries = config.retries;
|
|
180
|
+
let response = context.response;
|
|
181
|
+
let attempt = 1;
|
|
182
|
+
do {
|
|
183
|
+
const delay = await config.retryDelay(attempt, { ...context, attempt, response });
|
|
184
|
+
await new Promise(resolve => setTimeout(resolve, delay));
|
|
185
|
+
if (config.onRetry) {
|
|
186
|
+
await config.onRetry({ ...context, attempt, response });
|
|
187
|
+
}
|
|
188
|
+
try {
|
|
189
|
+
const signal = config.shouldResetTimeout ? undefined : context.request.signal;
|
|
190
|
+
response = await config.fetch(new Request(context.request, { signal }));
|
|
191
|
+
context = { ...context, attempt: attempt + 1, response, error: null };
|
|
192
|
+
attempt++;
|
|
193
|
+
}
|
|
194
|
+
catch (err) {
|
|
195
|
+
// Network error occurred during retry
|
|
196
|
+
const error = err instanceof Error ? err : new Error(String(err));
|
|
197
|
+
context = { ...context, attempt: attempt + 1, response, error };
|
|
198
|
+
attempt++;
|
|
199
|
+
}
|
|
200
|
+
if (!response) {
|
|
201
|
+
continue;
|
|
202
|
+
}
|
|
203
|
+
if (config.retryOn && !shouldRetryOnStatus(response?.status, config.retryOn)) {
|
|
204
|
+
return response;
|
|
205
|
+
}
|
|
206
|
+
const shouldRetry = await config.retryCondition(context, config.idempotentOnly);
|
|
207
|
+
if (!shouldRetry) {
|
|
208
|
+
return response;
|
|
209
|
+
}
|
|
210
|
+
} while (attempt <= maxRetries);
|
|
211
|
+
if (!response) {
|
|
212
|
+
throw context.error;
|
|
213
|
+
}
|
|
214
|
+
if (!response.ok) {
|
|
215
|
+
throw new Error(`${response.status}: ${response.statusText}`);
|
|
216
|
+
}
|
|
217
|
+
return response;
|
|
218
|
+
}
|
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Represents the context for a retry attempt.
|
|
3
|
+
*
|
|
4
|
+
* @interface RetryContext
|
|
5
|
+
* @property {number} attempt - The current attempt number (1-indexed).
|
|
6
|
+
* @property {Request} request - The original request being retried.
|
|
7
|
+
* @property {Response | null} response - The response that triggered the retry.
|
|
8
|
+
* @property {Error | null} error - The error that triggered the retry, if any.
|
|
9
|
+
*/
|
|
10
|
+
export interface RetryContext {
|
|
11
|
+
attempt: number;
|
|
12
|
+
request: Request;
|
|
13
|
+
response: Response | null;
|
|
14
|
+
error: Error | null;
|
|
15
|
+
}
|
|
16
|
+
/**
|
|
17
|
+
* Function type for custom retry condition.
|
|
18
|
+
* Returns true if the request should be retried.
|
|
19
|
+
*
|
|
20
|
+
* @param {RetryContext} context - The retry context containing attempt information.
|
|
21
|
+
* @param {boolean} idempotentOnly - Whether to retry only when the HTTP method is an idempotent methods.
|
|
22
|
+
* @returns {boolean | Promise<boolean>} Whether to retry the request.
|
|
23
|
+
*/
|
|
24
|
+
export type RetryConditionFn = (context: RetryContext, idempotentOnly: boolean) => boolean | Promise<boolean>;
|
|
25
|
+
/**
|
|
26
|
+
* Function type for custom retry delay calculation.
|
|
27
|
+
* Returns the delay in milliseconds before the next retry attempt.
|
|
28
|
+
*
|
|
29
|
+
* @param {number} attempt - The current attempt number (1-indexed).
|
|
30
|
+
* @param {RetryContext} context - The retry context containing attempt information.
|
|
31
|
+
* @returns {number | Promise<number>} The delay in milliseconds.
|
|
32
|
+
*/
|
|
33
|
+
export type RetryDelayFn = (attempt: number, context: RetryContext) => number | Promise<number>;
|
|
34
|
+
/**
|
|
35
|
+
* Function type for the onRetry callback.
|
|
36
|
+
* Called before each retry attempt.
|
|
37
|
+
*
|
|
38
|
+
* @param {RetryContext} context - The retry context containing attempt information.
|
|
39
|
+
* @returns {void | Promise<void>}
|
|
40
|
+
*/
|
|
41
|
+
export type OnRetryFn = (context: RetryContext) => void | Promise<void>;
|
|
42
|
+
/**
|
|
43
|
+
* Configuration for the retry middleware.
|
|
44
|
+
*
|
|
45
|
+
* This interface provides options to configure retry behavior, including:
|
|
46
|
+
* - Number of retry attempts
|
|
47
|
+
* - Custom retry conditions
|
|
48
|
+
* - Retry delay strategies (exponential backoff, linear, custom)
|
|
49
|
+
* - Callbacks for retry events
|
|
50
|
+
*
|
|
51
|
+
* @interface RetryConfig
|
|
52
|
+
* @property {number} [retries=3] - The maximum number of retry attempts.
|
|
53
|
+
* @property {RetryConditionFn} [retryCondition]
|
|
54
|
+
* - Custom function to determine if a request should be retried.
|
|
55
|
+
* - Defaults to retrying on 5xx, 429, 408 errors and network errors.
|
|
56
|
+
* @property {RetryDelayFn | 'exponential' | 'linear'} [retryDelay='exponential']
|
|
57
|
+
* - Strategy for calculating delay between retries.
|
|
58
|
+
* - 'exponential': Exponential backoff (100ms * 2^attemptNumber)
|
|
59
|
+
* - 'linear': Linear backoff (100ms * attemptNumber)
|
|
60
|
+
* - Custom function: Allows custom delay calculation
|
|
61
|
+
* @property {number} [retryDelayBase=100] - Base delay in milliseconds for built-in delay strategies.
|
|
62
|
+
* @property {number} [maxRetryDelay=30000] - Maximum delay in milliseconds between retry attempts.
|
|
63
|
+
* @property {boolean} [shouldResetTimeout=false] - Whether to reset the timeout between retries.
|
|
64
|
+
* @property {OnRetryFn} [onRetry] - Callback function executed before each retry attempt.
|
|
65
|
+
* @property {number[]} [retryOn]
|
|
66
|
+
* - Array of HTTP status codes that should trigger a retry.
|
|
67
|
+
* - If not specified, defaults to 5xx, 429 and 408 errors.
|
|
68
|
+
* @property {boolean} [idempotentOnly]
|
|
69
|
+
* - Whether to retry only when the HTTP method is an idempotent methods.
|
|
70
|
+
* - If not specified, defaults to true and retry only on GET, HEAD, OPTIONS, PUT or DELETE method.
|
|
71
|
+
* @property {typeof fetch} [fetch]
|
|
72
|
+
* - Custom fetch function to use for retries. Defaults to the global fetch function.
|
|
73
|
+
* - Useful for testing or using a custom fetch implementation.
|
|
74
|
+
*/
|
|
75
|
+
export interface RetryConfig {
|
|
76
|
+
retries?: number;
|
|
77
|
+
retryCondition?: RetryConditionFn;
|
|
78
|
+
retryDelay?: RetryDelayFn | 'exponential' | 'linear';
|
|
79
|
+
baseDelay?: number;
|
|
80
|
+
maxDelay?: number;
|
|
81
|
+
shouldResetTimeout?: boolean;
|
|
82
|
+
onRetry?: OnRetryFn;
|
|
83
|
+
retryOn?: number[];
|
|
84
|
+
idempotentOnly?: boolean;
|
|
85
|
+
fetch?: typeof fetch;
|
|
86
|
+
}
|
|
87
|
+
export type NormalisedConfig = Omit<RetryConfig, 'retries' | 'retryCondition' | 'retryDelay' | 'idempotentOnly' | 'fetch'> & {
|
|
88
|
+
retries: number;
|
|
89
|
+
retryCondition: RetryConditionFn;
|
|
90
|
+
retryDelay: RetryDelayFn;
|
|
91
|
+
idempotentOnly: boolean;
|
|
92
|
+
fetch: typeof fetch;
|
|
93
|
+
};
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* This is a Typescript copy of the amazing work from Sindre Sorhus
|
|
4
|
+
* https://github.com/sindresorhus/is-network-error
|
|
5
|
+
*/
|
|
6
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
7
|
+
exports.isNetworkError = isNetworkError;
|
|
8
|
+
const ERROR_MESSAGES = new Set([
|
|
9
|
+
'network error', // Chrome
|
|
10
|
+
'Failed to fetch', // Chrome
|
|
11
|
+
'NetworkError when attempting to fetch resource.', // Firefox
|
|
12
|
+
'The Internet connection appears to be offline.', // Safari 16
|
|
13
|
+
'Network request failed', // `cross-fetch`
|
|
14
|
+
'fetch failed', // Undici (Node.js)
|
|
15
|
+
'terminated', // Undici (Node.js)
|
|
16
|
+
' A network error occurred.', // Bun (WebKit)
|
|
17
|
+
'Network connection lost', // Cloudflare Workers (fetch)
|
|
18
|
+
]);
|
|
19
|
+
function isError(object) {
|
|
20
|
+
return Object.prototype.toString.call(object) === '[object Error]';
|
|
21
|
+
}
|
|
22
|
+
function isNetworkError(error) {
|
|
23
|
+
const isValid = error && isError(error) && error.name === 'TypeError' && typeof error.message === 'string';
|
|
24
|
+
if (!isValid) {
|
|
25
|
+
return false;
|
|
26
|
+
}
|
|
27
|
+
const { message, stack } = error;
|
|
28
|
+
// Safari 17+ has generic message but no stack for network errors
|
|
29
|
+
if (message === 'Load failed') {
|
|
30
|
+
return (stack === undefined ||
|
|
31
|
+
// Sentry adds its own stack trace to the fetch error, so also check for that
|
|
32
|
+
'__sentry_captured__' in error);
|
|
33
|
+
}
|
|
34
|
+
// Deno network errors start with specific text
|
|
35
|
+
if (message.startsWith('error sending request for url')) {
|
|
36
|
+
return true;
|
|
37
|
+
}
|
|
38
|
+
// Standard network error messages
|
|
39
|
+
return ERROR_MESSAGES.has(message);
|
|
40
|
+
}
|