@spoosh/plugin-retry 0.2.0 → 0.3.1
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 +49 -8
- package/dist/index.d.mts +41 -1
- package/dist/index.d.ts +41 -1
- package/dist/index.js +63 -43
- package/dist/index.mjs +57 -37
- package/package.json +4 -4
package/README.md
CHANGED
|
@@ -27,18 +27,59 @@ useRead((api) => api("posts").GET(), { retries: 5, retryDelay: 2000 });
|
|
|
27
27
|
useRead((api) => api("posts").GET(), { retries: false });
|
|
28
28
|
```
|
|
29
29
|
|
|
30
|
+
## Retry Behavior
|
|
31
|
+
|
|
32
|
+
By default, the plugin retries on:
|
|
33
|
+
|
|
34
|
+
- **Network errors** - Always retried (cannot be disabled via `shouldRetry`)
|
|
35
|
+
- **Status codes** - `408`, `429`, `500`, `502`, `503`, `504`
|
|
36
|
+
|
|
37
|
+
### Custom Retry Logic
|
|
38
|
+
|
|
39
|
+
Use the `shouldRetry` callback for custom retry conditions:
|
|
40
|
+
|
|
41
|
+
```typescript
|
|
42
|
+
retryPlugin({
|
|
43
|
+
retries: 3,
|
|
44
|
+
shouldRetry: ({ status, error, attempt, maxRetries }) => {
|
|
45
|
+
// Only retry on 503 Service Unavailable
|
|
46
|
+
return status === 503;
|
|
47
|
+
},
|
|
48
|
+
});
|
|
49
|
+
|
|
50
|
+
// Per-request override
|
|
51
|
+
useRead((api) => api("posts").GET(), {
|
|
52
|
+
shouldRetry: ({ status }) => status === 429,
|
|
53
|
+
});
|
|
54
|
+
```
|
|
55
|
+
|
|
56
|
+
> **Note:** Network errors are always retried regardless of the `shouldRetry` callback return value.
|
|
57
|
+
|
|
30
58
|
## Options
|
|
31
59
|
|
|
32
60
|
### Plugin Config
|
|
33
61
|
|
|
34
|
-
| Option
|
|
35
|
-
|
|
|
36
|
-
| `retries`
|
|
37
|
-
| `retryDelay`
|
|
62
|
+
| Option | Type | Default | Description |
|
|
63
|
+
| ------------- | --------------------- | --------------------------------------- | ---------------------------------------------------------------- |
|
|
64
|
+
| `retries` | `number \| false` | `3` | Number of retry attempts. Set to `false` to disable retries. |
|
|
65
|
+
| `retryDelay` | `number` | `1000` | Delay between retries in milliseconds (uses exponential backoff) |
|
|
66
|
+
| `shouldRetry` | `ShouldRetryCallback` | Retries on 408, 429, 500, 502, 503, 504 | Custom callback to determine if a request should be retried |
|
|
38
67
|
|
|
39
68
|
### Per-Request Options
|
|
40
69
|
|
|
41
|
-
| Option
|
|
42
|
-
|
|
|
43
|
-
| `retries`
|
|
44
|
-
| `retryDelay`
|
|
70
|
+
| Option | Type | Description |
|
|
71
|
+
| ------------- | --------------------- | ---------------------------------------- |
|
|
72
|
+
| `retries` | `number \| false` | Override retry attempts for this request |
|
|
73
|
+
| `retryDelay` | `number` | Override retry delay for this request |
|
|
74
|
+
| `shouldRetry` | `ShouldRetryCallback` | Override retry logic for this request |
|
|
75
|
+
|
|
76
|
+
### ShouldRetryContext
|
|
77
|
+
|
|
78
|
+
The `shouldRetry` callback receives a context object:
|
|
79
|
+
|
|
80
|
+
| Property | Type | Description |
|
|
81
|
+
| ------------ | --------- | ------------------------------------ |
|
|
82
|
+
| `status` | `number?` | HTTP status code from the response |
|
|
83
|
+
| `error` | `unknown` | The error that occurred |
|
|
84
|
+
| `attempt` | `number` | Current attempt number (0-indexed) |
|
|
85
|
+
| `maxRetries` | `number` | Maximum number of retries configured |
|
package/dist/index.d.mts
CHANGED
|
@@ -1,22 +1,62 @@
|
|
|
1
1
|
import { SpooshPlugin } from '@spoosh/core';
|
|
2
2
|
|
|
3
|
+
/**
|
|
4
|
+
* Context passed to the shouldRetry callback.
|
|
5
|
+
*/
|
|
6
|
+
interface ShouldRetryContext {
|
|
7
|
+
/** HTTP status code from the response, if available */
|
|
8
|
+
status?: number;
|
|
9
|
+
/** The error that occurred, if any */
|
|
10
|
+
error: unknown;
|
|
11
|
+
/** Current attempt number (0-indexed) */
|
|
12
|
+
attempt: number;
|
|
13
|
+
/** Maximum number of retries configured */
|
|
14
|
+
maxRetries: number;
|
|
15
|
+
}
|
|
16
|
+
/**
|
|
17
|
+
* Callback to determine if a request should be retried.
|
|
18
|
+
* Network errors are always retried regardless of this callback's return value.
|
|
19
|
+
*
|
|
20
|
+
* @returns `true` to retry, `false` to stop retrying
|
|
21
|
+
*/
|
|
22
|
+
type ShouldRetryCallback = (context: ShouldRetryContext) => boolean;
|
|
23
|
+
/** Status codes that are retried by default: 408, 429, 500, 502, 503, 504 */
|
|
24
|
+
declare const DEFAULT_RETRY_STATUS_CODES: readonly [408, 429, 500, 502, 503, 504];
|
|
3
25
|
interface RetryPluginConfig {
|
|
4
26
|
/** Number of retry attempts. Set to `false` to disable retries. Defaults to 3. */
|
|
5
27
|
retries?: number | false;
|
|
6
28
|
/** Delay between retries in milliseconds. Defaults to 1000. */
|
|
7
29
|
retryDelay?: number;
|
|
30
|
+
/**
|
|
31
|
+
* Custom callback to determine if a request should be retried.
|
|
32
|
+
* Network errors are always retried regardless of this callback.
|
|
33
|
+
* Defaults to retrying on status codes: 408, 429, 500, 502, 503, 504.
|
|
34
|
+
*/
|
|
35
|
+
shouldRetry?: ShouldRetryCallback;
|
|
8
36
|
}
|
|
9
37
|
interface RetryReadOptions {
|
|
10
38
|
/** Number of retry attempts. Set to `false` to disable retries. Overrides plugin default. */
|
|
11
39
|
retries?: number | false;
|
|
12
40
|
/** Delay between retries in milliseconds. Overrides plugin default. */
|
|
13
41
|
retryDelay?: number;
|
|
42
|
+
/**
|
|
43
|
+
* Custom callback to determine if a request should be retried.
|
|
44
|
+
* Network errors are always retried regardless of this callback.
|
|
45
|
+
* Overrides plugin default.
|
|
46
|
+
*/
|
|
47
|
+
shouldRetry?: ShouldRetryCallback;
|
|
14
48
|
}
|
|
15
49
|
interface RetryWriteOptions {
|
|
16
50
|
/** Number of retry attempts. Set to `false` to disable retries. Overrides plugin default. */
|
|
17
51
|
retries?: number | false;
|
|
18
52
|
/** Delay between retries in milliseconds. Overrides plugin default. */
|
|
19
53
|
retryDelay?: number;
|
|
54
|
+
/**
|
|
55
|
+
* Custom callback to determine if a request should be retried.
|
|
56
|
+
* Network errors are always retried regardless of this callback.
|
|
57
|
+
* Overrides plugin default.
|
|
58
|
+
*/
|
|
59
|
+
shouldRetry?: ShouldRetryCallback;
|
|
20
60
|
}
|
|
21
61
|
type RetryInfiniteReadOptions = RetryReadOptions;
|
|
22
62
|
type RetryReadResult = object;
|
|
@@ -56,4 +96,4 @@ declare function retryPlugin(config?: RetryPluginConfig): SpooshPlugin<{
|
|
|
56
96
|
writeResult: RetryWriteResult;
|
|
57
97
|
}>;
|
|
58
98
|
|
|
59
|
-
export { type RetryInfiniteReadOptions, type RetryPluginConfig, type RetryReadOptions, type RetryReadResult, type RetryWriteOptions, type RetryWriteResult, retryPlugin };
|
|
99
|
+
export { DEFAULT_RETRY_STATUS_CODES, type RetryInfiniteReadOptions, type RetryPluginConfig, type RetryReadOptions, type RetryReadResult, type RetryWriteOptions, type RetryWriteResult, type ShouldRetryCallback, type ShouldRetryContext, retryPlugin };
|
package/dist/index.d.ts
CHANGED
|
@@ -1,22 +1,62 @@
|
|
|
1
1
|
import { SpooshPlugin } from '@spoosh/core';
|
|
2
2
|
|
|
3
|
+
/**
|
|
4
|
+
* Context passed to the shouldRetry callback.
|
|
5
|
+
*/
|
|
6
|
+
interface ShouldRetryContext {
|
|
7
|
+
/** HTTP status code from the response, if available */
|
|
8
|
+
status?: number;
|
|
9
|
+
/** The error that occurred, if any */
|
|
10
|
+
error: unknown;
|
|
11
|
+
/** Current attempt number (0-indexed) */
|
|
12
|
+
attempt: number;
|
|
13
|
+
/** Maximum number of retries configured */
|
|
14
|
+
maxRetries: number;
|
|
15
|
+
}
|
|
16
|
+
/**
|
|
17
|
+
* Callback to determine if a request should be retried.
|
|
18
|
+
* Network errors are always retried regardless of this callback's return value.
|
|
19
|
+
*
|
|
20
|
+
* @returns `true` to retry, `false` to stop retrying
|
|
21
|
+
*/
|
|
22
|
+
type ShouldRetryCallback = (context: ShouldRetryContext) => boolean;
|
|
23
|
+
/** Status codes that are retried by default: 408, 429, 500, 502, 503, 504 */
|
|
24
|
+
declare const DEFAULT_RETRY_STATUS_CODES: readonly [408, 429, 500, 502, 503, 504];
|
|
3
25
|
interface RetryPluginConfig {
|
|
4
26
|
/** Number of retry attempts. Set to `false` to disable retries. Defaults to 3. */
|
|
5
27
|
retries?: number | false;
|
|
6
28
|
/** Delay between retries in milliseconds. Defaults to 1000. */
|
|
7
29
|
retryDelay?: number;
|
|
30
|
+
/**
|
|
31
|
+
* Custom callback to determine if a request should be retried.
|
|
32
|
+
* Network errors are always retried regardless of this callback.
|
|
33
|
+
* Defaults to retrying on status codes: 408, 429, 500, 502, 503, 504.
|
|
34
|
+
*/
|
|
35
|
+
shouldRetry?: ShouldRetryCallback;
|
|
8
36
|
}
|
|
9
37
|
interface RetryReadOptions {
|
|
10
38
|
/** Number of retry attempts. Set to `false` to disable retries. Overrides plugin default. */
|
|
11
39
|
retries?: number | false;
|
|
12
40
|
/** Delay between retries in milliseconds. Overrides plugin default. */
|
|
13
41
|
retryDelay?: number;
|
|
42
|
+
/**
|
|
43
|
+
* Custom callback to determine if a request should be retried.
|
|
44
|
+
* Network errors are always retried regardless of this callback.
|
|
45
|
+
* Overrides plugin default.
|
|
46
|
+
*/
|
|
47
|
+
shouldRetry?: ShouldRetryCallback;
|
|
14
48
|
}
|
|
15
49
|
interface RetryWriteOptions {
|
|
16
50
|
/** Number of retry attempts. Set to `false` to disable retries. Overrides plugin default. */
|
|
17
51
|
retries?: number | false;
|
|
18
52
|
/** Delay between retries in milliseconds. Overrides plugin default. */
|
|
19
53
|
retryDelay?: number;
|
|
54
|
+
/**
|
|
55
|
+
* Custom callback to determine if a request should be retried.
|
|
56
|
+
* Network errors are always retried regardless of this callback.
|
|
57
|
+
* Overrides plugin default.
|
|
58
|
+
*/
|
|
59
|
+
shouldRetry?: ShouldRetryCallback;
|
|
20
60
|
}
|
|
21
61
|
type RetryInfiniteReadOptions = RetryReadOptions;
|
|
22
62
|
type RetryReadResult = object;
|
|
@@ -56,4 +96,4 @@ declare function retryPlugin(config?: RetryPluginConfig): SpooshPlugin<{
|
|
|
56
96
|
writeResult: RetryWriteResult;
|
|
57
97
|
}>;
|
|
58
98
|
|
|
59
|
-
export { type RetryInfiniteReadOptions, type RetryPluginConfig, type RetryReadOptions, type RetryReadResult, type RetryWriteOptions, type RetryWriteResult, retryPlugin };
|
|
99
|
+
export { DEFAULT_RETRY_STATUS_CODES, type RetryInfiniteReadOptions, type RetryPluginConfig, type RetryReadOptions, type RetryReadResult, type RetryWriteOptions, type RetryWriteResult, type ShouldRetryCallback, type ShouldRetryContext, retryPlugin };
|
package/dist/index.js
CHANGED
|
@@ -20,81 +20,101 @@ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: tru
|
|
|
20
20
|
// src/index.ts
|
|
21
21
|
var src_exports = {};
|
|
22
22
|
__export(src_exports, {
|
|
23
|
+
DEFAULT_RETRY_STATUS_CODES: () => DEFAULT_RETRY_STATUS_CODES,
|
|
23
24
|
retryPlugin: () => retryPlugin
|
|
24
25
|
});
|
|
25
26
|
module.exports = __toCommonJS(src_exports);
|
|
26
27
|
|
|
27
|
-
// src/
|
|
28
|
-
var
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
return seen.get(value);
|
|
37
|
-
}
|
|
38
|
-
if (Array.isArray(value)) {
|
|
39
|
-
const arr = [];
|
|
40
|
-
seen.set(value, arr);
|
|
41
|
-
return value.map((v) => clone(v, seen));
|
|
42
|
-
}
|
|
43
|
-
if (value instanceof Date) {
|
|
44
|
-
return new Date(value.getTime());
|
|
45
|
-
}
|
|
46
|
-
if (value instanceof RegExp) {
|
|
47
|
-
return new RegExp(value.source, value.flags);
|
|
48
|
-
}
|
|
49
|
-
if (value.constructor !== Object) {
|
|
50
|
-
return value;
|
|
51
|
-
}
|
|
52
|
-
const obj = {};
|
|
53
|
-
seen.set(value, obj);
|
|
54
|
-
for (const key in value) {
|
|
55
|
-
if (Object.prototype.hasOwnProperty.call(value, key)) {
|
|
56
|
-
obj[key] = clone(value[key], seen);
|
|
57
|
-
}
|
|
58
|
-
}
|
|
59
|
-
return obj;
|
|
60
|
-
}
|
|
28
|
+
// src/types.ts
|
|
29
|
+
var DEFAULT_RETRY_STATUS_CODES = [
|
|
30
|
+
408,
|
|
31
|
+
429,
|
|
32
|
+
500,
|
|
33
|
+
502,
|
|
34
|
+
503,
|
|
35
|
+
504
|
|
36
|
+
];
|
|
61
37
|
|
|
62
38
|
// src/plugin.ts
|
|
39
|
+
var import_core = require("@spoosh/core");
|
|
40
|
+
var PLUGIN_NAME = "spoosh:retry";
|
|
63
41
|
var delay = (ms) => new Promise((resolve) => setTimeout(resolve, ms));
|
|
42
|
+
var defaultShouldRetry = ({ status }) => {
|
|
43
|
+
if (status === void 0) return false;
|
|
44
|
+
return DEFAULT_RETRY_STATUS_CODES.includes(status);
|
|
45
|
+
};
|
|
64
46
|
function retryPlugin(config = {}) {
|
|
65
|
-
const {
|
|
47
|
+
const {
|
|
48
|
+
retries: defaultRetries = 3,
|
|
49
|
+
retryDelay: defaultRetryDelay = 1e3,
|
|
50
|
+
shouldRetry: defaultShouldRetryFn = defaultShouldRetry
|
|
51
|
+
} = config;
|
|
66
52
|
return {
|
|
67
|
-
name:
|
|
53
|
+
name: PLUGIN_NAME,
|
|
68
54
|
operations: ["read", "write", "infiniteRead"],
|
|
55
|
+
priority: 200,
|
|
69
56
|
middleware: async (context, next) => {
|
|
57
|
+
const t = context.tracer?.(PLUGIN_NAME);
|
|
70
58
|
const pluginOptions = context.pluginOptions;
|
|
71
59
|
const retriesConfig = pluginOptions?.retries ?? defaultRetries;
|
|
72
60
|
const retryDelayConfig = pluginOptions?.retryDelay ?? defaultRetryDelay;
|
|
61
|
+
const shouldRetryFn = pluginOptions?.shouldRetry ?? defaultShouldRetryFn;
|
|
73
62
|
const maxRetries = retriesConfig === false ? 0 : retriesConfig;
|
|
74
63
|
if (!maxRetries || maxRetries < 0) {
|
|
64
|
+
t?.skip("Disabled");
|
|
75
65
|
return next();
|
|
76
66
|
}
|
|
77
67
|
const originalRequest = {
|
|
78
|
-
headers: clone(context.request.headers),
|
|
79
|
-
params: clone(context.request.params),
|
|
80
|
-
body: clone(context.request.body)
|
|
68
|
+
headers: (0, import_core.clone)(context.request.headers),
|
|
69
|
+
params: (0, import_core.clone)(context.request.params),
|
|
70
|
+
body: (0, import_core.clone)(context.request.body)
|
|
81
71
|
};
|
|
82
72
|
let res;
|
|
83
73
|
for (let attempt = 0; attempt <= maxRetries; attempt++) {
|
|
84
74
|
if (attempt > 0) {
|
|
85
|
-
context.request.headers = clone(originalRequest.headers);
|
|
86
|
-
context.request.params = clone(originalRequest.params);
|
|
87
|
-
context.request.body = clone(originalRequest.body);
|
|
75
|
+
context.request.headers = (0, import_core.clone)(originalRequest.headers);
|
|
76
|
+
context.request.params = (0, import_core.clone)(originalRequest.params);
|
|
77
|
+
context.request.body = (0, import_core.clone)(originalRequest.body);
|
|
78
|
+
t?.log(`Retry ${attempt}/${maxRetries}`, { color: "warning" });
|
|
88
79
|
}
|
|
89
80
|
res = await next();
|
|
90
81
|
if ((0, import_core.isAbortError)(res.error)) {
|
|
82
|
+
t?.log("Aborted", { color: "muted" });
|
|
91
83
|
return res;
|
|
92
84
|
}
|
|
93
|
-
|
|
85
|
+
const isLastAttempt = attempt >= maxRetries;
|
|
86
|
+
if ((0, import_core.isNetworkError)(res.error)) {
|
|
87
|
+
if (isLastAttempt) {
|
|
88
|
+
t?.log("Max retries reached (network error)", { color: "error" });
|
|
89
|
+
return res;
|
|
90
|
+
}
|
|
94
91
|
const delayMs = retryDelayConfig * Math.pow(2, attempt);
|
|
95
92
|
await delay(delayMs);
|
|
96
93
|
continue;
|
|
97
94
|
}
|
|
95
|
+
if (res.error) {
|
|
96
|
+
const shouldRetryResult = shouldRetryFn({
|
|
97
|
+
status: res.status,
|
|
98
|
+
error: res.error,
|
|
99
|
+
attempt,
|
|
100
|
+
maxRetries
|
|
101
|
+
});
|
|
102
|
+
if (shouldRetryResult && !isLastAttempt) {
|
|
103
|
+
t?.log(`Status ${res.status} - will retry`, { color: "warning" });
|
|
104
|
+
const delayMs = retryDelayConfig * Math.pow(2, attempt);
|
|
105
|
+
await delay(delayMs);
|
|
106
|
+
continue;
|
|
107
|
+
}
|
|
108
|
+
if (attempt > 0) {
|
|
109
|
+
t?.log("Max retries reached or non-retryable error", {
|
|
110
|
+
color: "error"
|
|
111
|
+
});
|
|
112
|
+
}
|
|
113
|
+
return res;
|
|
114
|
+
}
|
|
115
|
+
if (attempt > 0) {
|
|
116
|
+
t?.log("Retry succeeded", { color: "success" });
|
|
117
|
+
}
|
|
98
118
|
return res;
|
|
99
119
|
}
|
|
100
120
|
return res;
|
package/dist/index.mjs
CHANGED
|
@@ -1,51 +1,40 @@
|
|
|
1
|
-
// src/
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
return seen.get(value);
|
|
11
|
-
}
|
|
12
|
-
if (Array.isArray(value)) {
|
|
13
|
-
const arr = [];
|
|
14
|
-
seen.set(value, arr);
|
|
15
|
-
return value.map((v) => clone(v, seen));
|
|
16
|
-
}
|
|
17
|
-
if (value instanceof Date) {
|
|
18
|
-
return new Date(value.getTime());
|
|
19
|
-
}
|
|
20
|
-
if (value instanceof RegExp) {
|
|
21
|
-
return new RegExp(value.source, value.flags);
|
|
22
|
-
}
|
|
23
|
-
if (value.constructor !== Object) {
|
|
24
|
-
return value;
|
|
25
|
-
}
|
|
26
|
-
const obj = {};
|
|
27
|
-
seen.set(value, obj);
|
|
28
|
-
for (const key in value) {
|
|
29
|
-
if (Object.prototype.hasOwnProperty.call(value, key)) {
|
|
30
|
-
obj[key] = clone(value[key], seen);
|
|
31
|
-
}
|
|
32
|
-
}
|
|
33
|
-
return obj;
|
|
34
|
-
}
|
|
1
|
+
// src/types.ts
|
|
2
|
+
var DEFAULT_RETRY_STATUS_CODES = [
|
|
3
|
+
408,
|
|
4
|
+
429,
|
|
5
|
+
500,
|
|
6
|
+
502,
|
|
7
|
+
503,
|
|
8
|
+
504
|
|
9
|
+
];
|
|
35
10
|
|
|
36
11
|
// src/plugin.ts
|
|
12
|
+
import { isNetworkError, isAbortError, clone } from "@spoosh/core";
|
|
13
|
+
var PLUGIN_NAME = "spoosh:retry";
|
|
37
14
|
var delay = (ms) => new Promise((resolve) => setTimeout(resolve, ms));
|
|
15
|
+
var defaultShouldRetry = ({ status }) => {
|
|
16
|
+
if (status === void 0) return false;
|
|
17
|
+
return DEFAULT_RETRY_STATUS_CODES.includes(status);
|
|
18
|
+
};
|
|
38
19
|
function retryPlugin(config = {}) {
|
|
39
|
-
const {
|
|
20
|
+
const {
|
|
21
|
+
retries: defaultRetries = 3,
|
|
22
|
+
retryDelay: defaultRetryDelay = 1e3,
|
|
23
|
+
shouldRetry: defaultShouldRetryFn = defaultShouldRetry
|
|
24
|
+
} = config;
|
|
40
25
|
return {
|
|
41
|
-
name:
|
|
26
|
+
name: PLUGIN_NAME,
|
|
42
27
|
operations: ["read", "write", "infiniteRead"],
|
|
28
|
+
priority: 200,
|
|
43
29
|
middleware: async (context, next) => {
|
|
30
|
+
const t = context.tracer?.(PLUGIN_NAME);
|
|
44
31
|
const pluginOptions = context.pluginOptions;
|
|
45
32
|
const retriesConfig = pluginOptions?.retries ?? defaultRetries;
|
|
46
33
|
const retryDelayConfig = pluginOptions?.retryDelay ?? defaultRetryDelay;
|
|
34
|
+
const shouldRetryFn = pluginOptions?.shouldRetry ?? defaultShouldRetryFn;
|
|
47
35
|
const maxRetries = retriesConfig === false ? 0 : retriesConfig;
|
|
48
36
|
if (!maxRetries || maxRetries < 0) {
|
|
37
|
+
t?.skip("Disabled");
|
|
49
38
|
return next();
|
|
50
39
|
}
|
|
51
40
|
const originalRequest = {
|
|
@@ -59,16 +48,46 @@ function retryPlugin(config = {}) {
|
|
|
59
48
|
context.request.headers = clone(originalRequest.headers);
|
|
60
49
|
context.request.params = clone(originalRequest.params);
|
|
61
50
|
context.request.body = clone(originalRequest.body);
|
|
51
|
+
t?.log(`Retry ${attempt}/${maxRetries}`, { color: "warning" });
|
|
62
52
|
}
|
|
63
53
|
res = await next();
|
|
64
54
|
if (isAbortError(res.error)) {
|
|
55
|
+
t?.log("Aborted", { color: "muted" });
|
|
65
56
|
return res;
|
|
66
57
|
}
|
|
67
|
-
|
|
58
|
+
const isLastAttempt = attempt >= maxRetries;
|
|
59
|
+
if (isNetworkError(res.error)) {
|
|
60
|
+
if (isLastAttempt) {
|
|
61
|
+
t?.log("Max retries reached (network error)", { color: "error" });
|
|
62
|
+
return res;
|
|
63
|
+
}
|
|
68
64
|
const delayMs = retryDelayConfig * Math.pow(2, attempt);
|
|
69
65
|
await delay(delayMs);
|
|
70
66
|
continue;
|
|
71
67
|
}
|
|
68
|
+
if (res.error) {
|
|
69
|
+
const shouldRetryResult = shouldRetryFn({
|
|
70
|
+
status: res.status,
|
|
71
|
+
error: res.error,
|
|
72
|
+
attempt,
|
|
73
|
+
maxRetries
|
|
74
|
+
});
|
|
75
|
+
if (shouldRetryResult && !isLastAttempt) {
|
|
76
|
+
t?.log(`Status ${res.status} - will retry`, { color: "warning" });
|
|
77
|
+
const delayMs = retryDelayConfig * Math.pow(2, attempt);
|
|
78
|
+
await delay(delayMs);
|
|
79
|
+
continue;
|
|
80
|
+
}
|
|
81
|
+
if (attempt > 0) {
|
|
82
|
+
t?.log("Max retries reached or non-retryable error", {
|
|
83
|
+
color: "error"
|
|
84
|
+
});
|
|
85
|
+
}
|
|
86
|
+
return res;
|
|
87
|
+
}
|
|
88
|
+
if (attempt > 0) {
|
|
89
|
+
t?.log("Retry succeeded", { color: "success" });
|
|
90
|
+
}
|
|
72
91
|
return res;
|
|
73
92
|
}
|
|
74
93
|
return res;
|
|
@@ -76,5 +95,6 @@ function retryPlugin(config = {}) {
|
|
|
76
95
|
};
|
|
77
96
|
}
|
|
78
97
|
export {
|
|
98
|
+
DEFAULT_RETRY_STATUS_CODES,
|
|
79
99
|
retryPlugin
|
|
80
100
|
};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@spoosh/plugin-retry",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.3.1",
|
|
4
4
|
"description": "Automatic retry plugin for Spoosh with configurable attempts and delay",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"repository": {
|
|
@@ -33,11 +33,11 @@
|
|
|
33
33
|
}
|
|
34
34
|
},
|
|
35
35
|
"peerDependencies": {
|
|
36
|
-
"@spoosh/core": ">=0.
|
|
36
|
+
"@spoosh/core": ">=0.13.0"
|
|
37
37
|
},
|
|
38
38
|
"devDependencies": {
|
|
39
|
-
"@spoosh/core": "0.
|
|
40
|
-
"@spoosh/test-utils": "0.
|
|
39
|
+
"@spoosh/core": "0.13.0",
|
|
40
|
+
"@spoosh/test-utils": "0.2.0"
|
|
41
41
|
},
|
|
42
42
|
"scripts": {
|
|
43
43
|
"dev": "tsup --watch",
|