@yashrsb/retry-kit 0.1.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 +196 -0
- package/dist/index.cjs +110 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.cts +10 -0
- package/dist/index.d.ts +10 -0
- package/dist/index.js +83 -0
- package/dist/index.js.map +1 -0
- package/package.json +42 -0
package/README.md
ADDED
|
@@ -0,0 +1,196 @@
|
|
|
1
|
+
# retry-kit
|
|
2
|
+
|
|
3
|
+
A small TypeScript utility for retrying failed asynchronous operations with exponential backoff.
|
|
4
|
+
|
|
5
|
+
## Installation
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
npm install retry-kit
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
## Usage
|
|
12
|
+
|
|
13
|
+
```ts
|
|
14
|
+
import { retry } from "retry-kit";
|
|
15
|
+
|
|
16
|
+
const result = await retry(
|
|
17
|
+
async () => {
|
|
18
|
+
return await fetchData();
|
|
19
|
+
},
|
|
20
|
+
{
|
|
21
|
+
retries: 3,
|
|
22
|
+
delay: 500,
|
|
23
|
+
maxDelay: 5000,
|
|
24
|
+
jitter: true,
|
|
25
|
+
},
|
|
26
|
+
);
|
|
27
|
+
|
|
28
|
+
console.log(result);
|
|
29
|
+
```
|
|
30
|
+
|
|
31
|
+
If the operation fails, `retry-kit` waits before trying again.
|
|
32
|
+
|
|
33
|
+
With the configuration above, the base exponential delays are:
|
|
34
|
+
|
|
35
|
+
```text
|
|
36
|
+
500ms → 1000ms → 2000ms → 4000ms
|
|
37
|
+
```
|
|
38
|
+
|
|
39
|
+
The `maxDelay` option limits the maximum wait time.
|
|
40
|
+
|
|
41
|
+
When `jitter` is enabled, the actual delay is randomized between `0` and the calculated backoff delay.
|
|
42
|
+
|
|
43
|
+
## API
|
|
44
|
+
|
|
45
|
+
### `retry<T>(fn, options?)`
|
|
46
|
+
|
|
47
|
+
Retries an asynchronous operation until it succeeds or the configured number of retries is exhausted.
|
|
48
|
+
|
|
49
|
+
### Options
|
|
50
|
+
|
|
51
|
+
| Option | Default | Description |
|
|
52
|
+
| ---------- | ----------: | --------------------------------------------------------- |
|
|
53
|
+
| `retries` | `3` | Number of retries after the initial attempt |
|
|
54
|
+
| `delay` | `500` | Initial delay in milliseconds |
|
|
55
|
+
| `maxDelay` | `Infinity` | Maximum delay between attempts |
|
|
56
|
+
| `jitter` | `false` | Randomizes the backoff delay |
|
|
57
|
+
| `signal` | `undefined` | Optional `AbortSignal` used to cancel the retry operation |
|
|
58
|
+
|
|
59
|
+
### Example: Retry a failing operation
|
|
60
|
+
|
|
61
|
+
```ts
|
|
62
|
+
const result = await retry(
|
|
63
|
+
async () => {
|
|
64
|
+
return await fetchData();
|
|
65
|
+
},
|
|
66
|
+
{
|
|
67
|
+
retries: 3,
|
|
68
|
+
delay: 500,
|
|
69
|
+
},
|
|
70
|
+
);
|
|
71
|
+
```
|
|
72
|
+
|
|
73
|
+
The operation is attempted once initially. If it fails, it can be retried up to three additional times.
|
|
74
|
+
|
|
75
|
+
Therefore:
|
|
76
|
+
|
|
77
|
+
```text
|
|
78
|
+
retries: 3
|
|
79
|
+
```
|
|
80
|
+
|
|
81
|
+
means a maximum of:
|
|
82
|
+
|
|
83
|
+
```text
|
|
84
|
+
4 total attempts
|
|
85
|
+
```
|
|
86
|
+
|
|
87
|
+
### Example: Exponential backoff
|
|
88
|
+
|
|
89
|
+
With:
|
|
90
|
+
|
|
91
|
+
```ts
|
|
92
|
+
{
|
|
93
|
+
retries: 4,
|
|
94
|
+
delay: 500,
|
|
95
|
+
}
|
|
96
|
+
```
|
|
97
|
+
|
|
98
|
+
the delays between attempts are:
|
|
99
|
+
|
|
100
|
+
```text
|
|
101
|
+
500ms
|
|
102
|
+
1000ms
|
|
103
|
+
2000ms
|
|
104
|
+
4000ms
|
|
105
|
+
```
|
|
106
|
+
|
|
107
|
+
### Example: Maximum delay
|
|
108
|
+
|
|
109
|
+
```ts
|
|
110
|
+
{
|
|
111
|
+
retries: 10,
|
|
112
|
+
delay: 500,
|
|
113
|
+
maxDelay: 2000,
|
|
114
|
+
}
|
|
115
|
+
```
|
|
116
|
+
|
|
117
|
+
The delay will never exceed `2000ms`:
|
|
118
|
+
|
|
119
|
+
```text
|
|
120
|
+
500ms
|
|
121
|
+
1000ms
|
|
122
|
+
2000ms
|
|
123
|
+
2000ms
|
|
124
|
+
2000ms
|
|
125
|
+
...
|
|
126
|
+
```
|
|
127
|
+
|
|
128
|
+
### Example: Jitter
|
|
129
|
+
|
|
130
|
+
```ts
|
|
131
|
+
{
|
|
132
|
+
retries: 5,
|
|
133
|
+
delay: 500,
|
|
134
|
+
jitter: true,
|
|
135
|
+
}
|
|
136
|
+
```
|
|
137
|
+
|
|
138
|
+
Jitter randomizes the calculated backoff delay. This can help avoid many clients retrying at exactly the same time.
|
|
139
|
+
|
|
140
|
+
### Example: Cancellation
|
|
141
|
+
|
|
142
|
+
```ts
|
|
143
|
+
const controller = new AbortController();
|
|
144
|
+
|
|
145
|
+
const result = retry(
|
|
146
|
+
async () => {
|
|
147
|
+
return await fetchData();
|
|
148
|
+
},
|
|
149
|
+
{
|
|
150
|
+
retries: 5,
|
|
151
|
+
delay: 500,
|
|
152
|
+
signal: controller.signal,
|
|
153
|
+
},
|
|
154
|
+
);
|
|
155
|
+
|
|
156
|
+
controller.abort();
|
|
157
|
+
```
|
|
158
|
+
|
|
159
|
+
Aborting the signal stops the retry operation.
|
|
160
|
+
|
|
161
|
+
## Behavior
|
|
162
|
+
|
|
163
|
+
The package:
|
|
164
|
+
|
|
165
|
+
- Executes the operation immediately.
|
|
166
|
+
- Retries rejected operations according to the configured retry count.
|
|
167
|
+
- Uses exponential backoff between retries.
|
|
168
|
+
- Supports a maximum backoff delay.
|
|
169
|
+
- Supports full jitter.
|
|
170
|
+
- Preserves and throws the final operation error when retries are exhausted.
|
|
171
|
+
- Supports cancellation through `AbortSignal`.
|
|
172
|
+
- Provides TypeScript declarations.
|
|
173
|
+
|
|
174
|
+
## Development
|
|
175
|
+
|
|
176
|
+
Install dependencies:
|
|
177
|
+
|
|
178
|
+
```bash
|
|
179
|
+
npm install
|
|
180
|
+
```
|
|
181
|
+
|
|
182
|
+
Run tests:
|
|
183
|
+
|
|
184
|
+
```bash
|
|
185
|
+
npm test
|
|
186
|
+
```
|
|
187
|
+
|
|
188
|
+
Build the package:
|
|
189
|
+
|
|
190
|
+
```bash
|
|
191
|
+
npm run build
|
|
192
|
+
```
|
|
193
|
+
|
|
194
|
+
## License
|
|
195
|
+
|
|
196
|
+
MIT
|
package/dist/index.cjs
ADDED
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __defProp = Object.defineProperty;
|
|
3
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
4
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
5
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
6
|
+
var __export = (target, all) => {
|
|
7
|
+
for (var name in all)
|
|
8
|
+
__defProp(target, name, { get: all[name], enumerable: true });
|
|
9
|
+
};
|
|
10
|
+
var __copyProps = (to, from, except, desc) => {
|
|
11
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
12
|
+
for (let key of __getOwnPropNames(from))
|
|
13
|
+
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
14
|
+
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
15
|
+
}
|
|
16
|
+
return to;
|
|
17
|
+
};
|
|
18
|
+
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
19
|
+
|
|
20
|
+
// src/index.ts
|
|
21
|
+
var index_exports = {};
|
|
22
|
+
__export(index_exports, {
|
|
23
|
+
retry: () => retry
|
|
24
|
+
});
|
|
25
|
+
module.exports = __toCommonJS(index_exports);
|
|
26
|
+
|
|
27
|
+
// src/retry.ts
|
|
28
|
+
function sleep(ms, signal) {
|
|
29
|
+
return new Promise((resolve, reject) => {
|
|
30
|
+
if (signal?.aborted) {
|
|
31
|
+
reject(signal.reason);
|
|
32
|
+
return;
|
|
33
|
+
}
|
|
34
|
+
const timer = setTimeout(() => {
|
|
35
|
+
cleanup();
|
|
36
|
+
resolve();
|
|
37
|
+
}, ms);
|
|
38
|
+
function onAbort() {
|
|
39
|
+
clearTimeout(timer);
|
|
40
|
+
cleanup();
|
|
41
|
+
reject(signal?.reason);
|
|
42
|
+
}
|
|
43
|
+
function cleanup() {
|
|
44
|
+
signal?.removeEventListener("abort", onAbort);
|
|
45
|
+
}
|
|
46
|
+
signal?.addEventListener("abort", onAbort, { once: true });
|
|
47
|
+
});
|
|
48
|
+
}
|
|
49
|
+
function applyJitter(delay) {
|
|
50
|
+
return Math.random() * delay;
|
|
51
|
+
}
|
|
52
|
+
function validateOptions(fn, retries, delay, maxDelay, jitter, signal) {
|
|
53
|
+
if (typeof fn !== "function") {
|
|
54
|
+
throw new TypeError("retry: fn must be a function");
|
|
55
|
+
}
|
|
56
|
+
if (!Number.isInteger(retries) || retries < 0) {
|
|
57
|
+
throw new RangeError("retry: retries must be a non-negative integer");
|
|
58
|
+
}
|
|
59
|
+
if (!Number.isFinite(delay) || delay < 0) {
|
|
60
|
+
throw new RangeError("retry: delay must be a non-negative finite number");
|
|
61
|
+
}
|
|
62
|
+
if (maxDelay !== Infinity && (!Number.isFinite(maxDelay) || maxDelay < 0)) {
|
|
63
|
+
throw new RangeError(
|
|
64
|
+
"retry: maxDelay must be a non-negative finite number"
|
|
65
|
+
);
|
|
66
|
+
}
|
|
67
|
+
if (maxDelay < delay) {
|
|
68
|
+
throw new RangeError(
|
|
69
|
+
"retry: maxDelay must be greater than or equal to delay"
|
|
70
|
+
);
|
|
71
|
+
}
|
|
72
|
+
if (typeof jitter !== "boolean") {
|
|
73
|
+
throw new TypeError("retry: jitter must be a boolean");
|
|
74
|
+
}
|
|
75
|
+
if (signal !== void 0 && !(signal instanceof AbortSignal)) {
|
|
76
|
+
throw new TypeError("retry: signal must be an AbortSignal");
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
async function retry(fn, options = {}) {
|
|
80
|
+
const {
|
|
81
|
+
retries = 3,
|
|
82
|
+
delay = 500,
|
|
83
|
+
maxDelay = Infinity,
|
|
84
|
+
jitter = false,
|
|
85
|
+
signal
|
|
86
|
+
} = options;
|
|
87
|
+
validateOptions(fn, retries, delay, maxDelay, jitter, signal);
|
|
88
|
+
let attempt = 0;
|
|
89
|
+
while (true) {
|
|
90
|
+
if (signal?.aborted) {
|
|
91
|
+
throw signal.reason;
|
|
92
|
+
}
|
|
93
|
+
try {
|
|
94
|
+
return await fn();
|
|
95
|
+
} catch (error) {
|
|
96
|
+
if (attempt >= retries) {
|
|
97
|
+
throw error;
|
|
98
|
+
}
|
|
99
|
+
const backoffDelay = Math.min(delay * 2 ** attempt, maxDelay);
|
|
100
|
+
const actualDelay = jitter ? applyJitter(backoffDelay) : backoffDelay;
|
|
101
|
+
await sleep(actualDelay, signal);
|
|
102
|
+
attempt++;
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
// Annotate the CommonJS export names for ESM import in node:
|
|
107
|
+
0 && (module.exports = {
|
|
108
|
+
retry
|
|
109
|
+
});
|
|
110
|
+
//# sourceMappingURL=index.cjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/index.ts","../src/retry.ts"],"sourcesContent":["export { retry } from \"./retry.js\";\r\nexport type { RetryOptions } from \"./retry.js\";\r\n","export interface RetryOptions {\r\n retries?: number;\r\n delay?: number;\r\n maxDelay?: number;\r\n jitter?: boolean;\r\n signal?: AbortSignal;\r\n}\r\n\r\nfunction sleep(ms: number, signal?: AbortSignal): Promise<void> {\r\n return new Promise((resolve, reject) => {\r\n if (signal?.aborted) {\r\n reject(signal.reason);\r\n return;\r\n }\r\n\r\n const timer = setTimeout(() => {\r\n cleanup();\r\n resolve();\r\n }, ms);\r\n\r\n function onAbort(): void {\r\n clearTimeout(timer);\r\n cleanup();\r\n reject(signal?.reason);\r\n }\r\n\r\n function cleanup(): void {\r\n signal?.removeEventListener(\"abort\", onAbort);\r\n }\r\n\r\n signal?.addEventListener(\"abort\", onAbort, { once: true });\r\n });\r\n}\r\n\r\nfunction applyJitter(delay: number): number {\r\n return Math.random() * delay;\r\n}\r\n\r\nfunction validateOptions(\r\n fn: unknown,\r\n retries: number,\r\n delay: number,\r\n maxDelay: number,\r\n jitter: boolean,\r\n signal?: AbortSignal,\r\n): void {\r\n if (typeof fn !== \"function\") {\r\n throw new TypeError(\"retry: fn must be a function\");\r\n }\r\n\r\n if (!Number.isInteger(retries) || retries < 0) {\r\n throw new RangeError(\"retry: retries must be a non-negative integer\");\r\n }\r\n\r\n if (!Number.isFinite(delay) || delay < 0) {\r\n throw new RangeError(\"retry: delay must be a non-negative finite number\");\r\n }\r\n\r\n if (maxDelay !== Infinity && (!Number.isFinite(maxDelay) || maxDelay < 0)) {\r\n throw new RangeError(\r\n \"retry: maxDelay must be a non-negative finite number\",\r\n );\r\n }\r\n\r\n if (maxDelay < delay) {\r\n throw new RangeError(\r\n \"retry: maxDelay must be greater than or equal to delay\",\r\n );\r\n }\r\n\r\n if (typeof jitter !== \"boolean\") {\r\n throw new TypeError(\"retry: jitter must be a boolean\");\r\n }\r\n\r\n if (signal !== undefined && !(signal instanceof AbortSignal)) {\r\n throw new TypeError(\"retry: signal must be an AbortSignal\");\r\n }\r\n}\r\n\r\nexport async function retry<T>(\r\n fn: () => Promise<T>,\r\n options: RetryOptions = {},\r\n): Promise<T> {\r\n const {\r\n retries = 3,\r\n delay = 500,\r\n maxDelay = Infinity,\r\n jitter = false,\r\n signal,\r\n } = options;\r\n\r\n validateOptions(fn, retries, delay, maxDelay, jitter, signal);\r\n\r\n let attempt = 0;\r\n\r\n while (true) {\r\n if (signal?.aborted) {\r\n throw signal.reason;\r\n }\r\n\r\n try {\r\n return await fn();\r\n } catch (error) {\r\n if (attempt >= retries) {\r\n throw error;\r\n }\r\n\r\n const backoffDelay = Math.min(delay * 2 ** attempt, maxDelay);\r\n\r\n const actualDelay = jitter ? applyJitter(backoffDelay) : backoffDelay;\r\n\r\n await sleep(actualDelay, signal);\r\n\r\n attempt++;\r\n }\r\n }\r\n}\r\n"],"mappings":";;;;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;;;ACQA,SAAS,MAAM,IAAY,QAAqC;AAC9D,SAAO,IAAI,QAAQ,CAAC,SAAS,WAAW;AACtC,QAAI,QAAQ,SAAS;AACnB,aAAO,OAAO,MAAM;AACpB;AAAA,IACF;AAEA,UAAM,QAAQ,WAAW,MAAM;AAC7B,cAAQ;AACR,cAAQ;AAAA,IACV,GAAG,EAAE;AAEL,aAAS,UAAgB;AACvB,mBAAa,KAAK;AAClB,cAAQ;AACR,aAAO,QAAQ,MAAM;AAAA,IACvB;AAEA,aAAS,UAAgB;AACvB,cAAQ,oBAAoB,SAAS,OAAO;AAAA,IAC9C;AAEA,YAAQ,iBAAiB,SAAS,SAAS,EAAE,MAAM,KAAK,CAAC;AAAA,EAC3D,CAAC;AACH;AAEA,SAAS,YAAY,OAAuB;AAC1C,SAAO,KAAK,OAAO,IAAI;AACzB;AAEA,SAAS,gBACP,IACA,SACA,OACA,UACA,QACA,QACM;AACN,MAAI,OAAO,OAAO,YAAY;AAC5B,UAAM,IAAI,UAAU,8BAA8B;AAAA,EACpD;AAEA,MAAI,CAAC,OAAO,UAAU,OAAO,KAAK,UAAU,GAAG;AAC7C,UAAM,IAAI,WAAW,+CAA+C;AAAA,EACtE;AAEA,MAAI,CAAC,OAAO,SAAS,KAAK,KAAK,QAAQ,GAAG;AACxC,UAAM,IAAI,WAAW,mDAAmD;AAAA,EAC1E;AAEA,MAAI,aAAa,aAAa,CAAC,OAAO,SAAS,QAAQ,KAAK,WAAW,IAAI;AACzE,UAAM,IAAI;AAAA,MACR;AAAA,IACF;AAAA,EACF;AAEA,MAAI,WAAW,OAAO;AACpB,UAAM,IAAI;AAAA,MACR;AAAA,IACF;AAAA,EACF;AAEA,MAAI,OAAO,WAAW,WAAW;AAC/B,UAAM,IAAI,UAAU,iCAAiC;AAAA,EACvD;AAEA,MAAI,WAAW,UAAa,EAAE,kBAAkB,cAAc;AAC5D,UAAM,IAAI,UAAU,sCAAsC;AAAA,EAC5D;AACF;AAEA,eAAsB,MACpB,IACA,UAAwB,CAAC,GACb;AACZ,QAAM;AAAA,IACJ,UAAU;AAAA,IACV,QAAQ;AAAA,IACR,WAAW;AAAA,IACX,SAAS;AAAA,IACT;AAAA,EACF,IAAI;AAEJ,kBAAgB,IAAI,SAAS,OAAO,UAAU,QAAQ,MAAM;AAE5D,MAAI,UAAU;AAEd,SAAO,MAAM;AACX,QAAI,QAAQ,SAAS;AACnB,YAAM,OAAO;AAAA,IACf;AAEA,QAAI;AACF,aAAO,MAAM,GAAG;AAAA,IAClB,SAAS,OAAO;AACd,UAAI,WAAW,SAAS;AACtB,cAAM;AAAA,MACR;AAEA,YAAM,eAAe,KAAK,IAAI,QAAQ,KAAK,SAAS,QAAQ;AAE5D,YAAM,cAAc,SAAS,YAAY,YAAY,IAAI;AAEzD,YAAM,MAAM,aAAa,MAAM;AAE/B;AAAA,IACF;AAAA,EACF;AACF;","names":[]}
|
package/dist/index.d.cts
ADDED
package/dist/index.d.ts
ADDED
package/dist/index.js
ADDED
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
// src/retry.ts
|
|
2
|
+
function sleep(ms, signal) {
|
|
3
|
+
return new Promise((resolve, reject) => {
|
|
4
|
+
if (signal?.aborted) {
|
|
5
|
+
reject(signal.reason);
|
|
6
|
+
return;
|
|
7
|
+
}
|
|
8
|
+
const timer = setTimeout(() => {
|
|
9
|
+
cleanup();
|
|
10
|
+
resolve();
|
|
11
|
+
}, ms);
|
|
12
|
+
function onAbort() {
|
|
13
|
+
clearTimeout(timer);
|
|
14
|
+
cleanup();
|
|
15
|
+
reject(signal?.reason);
|
|
16
|
+
}
|
|
17
|
+
function cleanup() {
|
|
18
|
+
signal?.removeEventListener("abort", onAbort);
|
|
19
|
+
}
|
|
20
|
+
signal?.addEventListener("abort", onAbort, { once: true });
|
|
21
|
+
});
|
|
22
|
+
}
|
|
23
|
+
function applyJitter(delay) {
|
|
24
|
+
return Math.random() * delay;
|
|
25
|
+
}
|
|
26
|
+
function validateOptions(fn, retries, delay, maxDelay, jitter, signal) {
|
|
27
|
+
if (typeof fn !== "function") {
|
|
28
|
+
throw new TypeError("retry: fn must be a function");
|
|
29
|
+
}
|
|
30
|
+
if (!Number.isInteger(retries) || retries < 0) {
|
|
31
|
+
throw new RangeError("retry: retries must be a non-negative integer");
|
|
32
|
+
}
|
|
33
|
+
if (!Number.isFinite(delay) || delay < 0) {
|
|
34
|
+
throw new RangeError("retry: delay must be a non-negative finite number");
|
|
35
|
+
}
|
|
36
|
+
if (maxDelay !== Infinity && (!Number.isFinite(maxDelay) || maxDelay < 0)) {
|
|
37
|
+
throw new RangeError(
|
|
38
|
+
"retry: maxDelay must be a non-negative finite number"
|
|
39
|
+
);
|
|
40
|
+
}
|
|
41
|
+
if (maxDelay < delay) {
|
|
42
|
+
throw new RangeError(
|
|
43
|
+
"retry: maxDelay must be greater than or equal to delay"
|
|
44
|
+
);
|
|
45
|
+
}
|
|
46
|
+
if (typeof jitter !== "boolean") {
|
|
47
|
+
throw new TypeError("retry: jitter must be a boolean");
|
|
48
|
+
}
|
|
49
|
+
if (signal !== void 0 && !(signal instanceof AbortSignal)) {
|
|
50
|
+
throw new TypeError("retry: signal must be an AbortSignal");
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
async function retry(fn, options = {}) {
|
|
54
|
+
const {
|
|
55
|
+
retries = 3,
|
|
56
|
+
delay = 500,
|
|
57
|
+
maxDelay = Infinity,
|
|
58
|
+
jitter = false,
|
|
59
|
+
signal
|
|
60
|
+
} = options;
|
|
61
|
+
validateOptions(fn, retries, delay, maxDelay, jitter, signal);
|
|
62
|
+
let attempt = 0;
|
|
63
|
+
while (true) {
|
|
64
|
+
if (signal?.aborted) {
|
|
65
|
+
throw signal.reason;
|
|
66
|
+
}
|
|
67
|
+
try {
|
|
68
|
+
return await fn();
|
|
69
|
+
} catch (error) {
|
|
70
|
+
if (attempt >= retries) {
|
|
71
|
+
throw error;
|
|
72
|
+
}
|
|
73
|
+
const backoffDelay = Math.min(delay * 2 ** attempt, maxDelay);
|
|
74
|
+
const actualDelay = jitter ? applyJitter(backoffDelay) : backoffDelay;
|
|
75
|
+
await sleep(actualDelay, signal);
|
|
76
|
+
attempt++;
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
export {
|
|
81
|
+
retry
|
|
82
|
+
};
|
|
83
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/retry.ts"],"sourcesContent":["export interface RetryOptions {\r\n retries?: number;\r\n delay?: number;\r\n maxDelay?: number;\r\n jitter?: boolean;\r\n signal?: AbortSignal;\r\n}\r\n\r\nfunction sleep(ms: number, signal?: AbortSignal): Promise<void> {\r\n return new Promise((resolve, reject) => {\r\n if (signal?.aborted) {\r\n reject(signal.reason);\r\n return;\r\n }\r\n\r\n const timer = setTimeout(() => {\r\n cleanup();\r\n resolve();\r\n }, ms);\r\n\r\n function onAbort(): void {\r\n clearTimeout(timer);\r\n cleanup();\r\n reject(signal?.reason);\r\n }\r\n\r\n function cleanup(): void {\r\n signal?.removeEventListener(\"abort\", onAbort);\r\n }\r\n\r\n signal?.addEventListener(\"abort\", onAbort, { once: true });\r\n });\r\n}\r\n\r\nfunction applyJitter(delay: number): number {\r\n return Math.random() * delay;\r\n}\r\n\r\nfunction validateOptions(\r\n fn: unknown,\r\n retries: number,\r\n delay: number,\r\n maxDelay: number,\r\n jitter: boolean,\r\n signal?: AbortSignal,\r\n): void {\r\n if (typeof fn !== \"function\") {\r\n throw new TypeError(\"retry: fn must be a function\");\r\n }\r\n\r\n if (!Number.isInteger(retries) || retries < 0) {\r\n throw new RangeError(\"retry: retries must be a non-negative integer\");\r\n }\r\n\r\n if (!Number.isFinite(delay) || delay < 0) {\r\n throw new RangeError(\"retry: delay must be a non-negative finite number\");\r\n }\r\n\r\n if (maxDelay !== Infinity && (!Number.isFinite(maxDelay) || maxDelay < 0)) {\r\n throw new RangeError(\r\n \"retry: maxDelay must be a non-negative finite number\",\r\n );\r\n }\r\n\r\n if (maxDelay < delay) {\r\n throw new RangeError(\r\n \"retry: maxDelay must be greater than or equal to delay\",\r\n );\r\n }\r\n\r\n if (typeof jitter !== \"boolean\") {\r\n throw new TypeError(\"retry: jitter must be a boolean\");\r\n }\r\n\r\n if (signal !== undefined && !(signal instanceof AbortSignal)) {\r\n throw new TypeError(\"retry: signal must be an AbortSignal\");\r\n }\r\n}\r\n\r\nexport async function retry<T>(\r\n fn: () => Promise<T>,\r\n options: RetryOptions = {},\r\n): Promise<T> {\r\n const {\r\n retries = 3,\r\n delay = 500,\r\n maxDelay = Infinity,\r\n jitter = false,\r\n signal,\r\n } = options;\r\n\r\n validateOptions(fn, retries, delay, maxDelay, jitter, signal);\r\n\r\n let attempt = 0;\r\n\r\n while (true) {\r\n if (signal?.aborted) {\r\n throw signal.reason;\r\n }\r\n\r\n try {\r\n return await fn();\r\n } catch (error) {\r\n if (attempt >= retries) {\r\n throw error;\r\n }\r\n\r\n const backoffDelay = Math.min(delay * 2 ** attempt, maxDelay);\r\n\r\n const actualDelay = jitter ? applyJitter(backoffDelay) : backoffDelay;\r\n\r\n await sleep(actualDelay, signal);\r\n\r\n attempt++;\r\n }\r\n }\r\n}\r\n"],"mappings":";AAQA,SAAS,MAAM,IAAY,QAAqC;AAC9D,SAAO,IAAI,QAAQ,CAAC,SAAS,WAAW;AACtC,QAAI,QAAQ,SAAS;AACnB,aAAO,OAAO,MAAM;AACpB;AAAA,IACF;AAEA,UAAM,QAAQ,WAAW,MAAM;AAC7B,cAAQ;AACR,cAAQ;AAAA,IACV,GAAG,EAAE;AAEL,aAAS,UAAgB;AACvB,mBAAa,KAAK;AAClB,cAAQ;AACR,aAAO,QAAQ,MAAM;AAAA,IACvB;AAEA,aAAS,UAAgB;AACvB,cAAQ,oBAAoB,SAAS,OAAO;AAAA,IAC9C;AAEA,YAAQ,iBAAiB,SAAS,SAAS,EAAE,MAAM,KAAK,CAAC;AAAA,EAC3D,CAAC;AACH;AAEA,SAAS,YAAY,OAAuB;AAC1C,SAAO,KAAK,OAAO,IAAI;AACzB;AAEA,SAAS,gBACP,IACA,SACA,OACA,UACA,QACA,QACM;AACN,MAAI,OAAO,OAAO,YAAY;AAC5B,UAAM,IAAI,UAAU,8BAA8B;AAAA,EACpD;AAEA,MAAI,CAAC,OAAO,UAAU,OAAO,KAAK,UAAU,GAAG;AAC7C,UAAM,IAAI,WAAW,+CAA+C;AAAA,EACtE;AAEA,MAAI,CAAC,OAAO,SAAS,KAAK,KAAK,QAAQ,GAAG;AACxC,UAAM,IAAI,WAAW,mDAAmD;AAAA,EAC1E;AAEA,MAAI,aAAa,aAAa,CAAC,OAAO,SAAS,QAAQ,KAAK,WAAW,IAAI;AACzE,UAAM,IAAI;AAAA,MACR;AAAA,IACF;AAAA,EACF;AAEA,MAAI,WAAW,OAAO;AACpB,UAAM,IAAI;AAAA,MACR;AAAA,IACF;AAAA,EACF;AAEA,MAAI,OAAO,WAAW,WAAW;AAC/B,UAAM,IAAI,UAAU,iCAAiC;AAAA,EACvD;AAEA,MAAI,WAAW,UAAa,EAAE,kBAAkB,cAAc;AAC5D,UAAM,IAAI,UAAU,sCAAsC;AAAA,EAC5D;AACF;AAEA,eAAsB,MACpB,IACA,UAAwB,CAAC,GACb;AACZ,QAAM;AAAA,IACJ,UAAU;AAAA,IACV,QAAQ;AAAA,IACR,WAAW;AAAA,IACX,SAAS;AAAA,IACT;AAAA,EACF,IAAI;AAEJ,kBAAgB,IAAI,SAAS,OAAO,UAAU,QAAQ,MAAM;AAE5D,MAAI,UAAU;AAEd,SAAO,MAAM;AACX,QAAI,QAAQ,SAAS;AACnB,YAAM,OAAO;AAAA,IACf;AAEA,QAAI;AACF,aAAO,MAAM,GAAG;AAAA,IAClB,SAAS,OAAO;AACd,UAAI,WAAW,SAAS;AACtB,cAAM;AAAA,MACR;AAEA,YAAM,eAAe,KAAK,IAAI,QAAQ,KAAK,SAAS,QAAQ;AAE5D,YAAM,cAAc,SAAS,YAAY,YAAY,IAAI;AAEzD,YAAM,MAAM,aAAa,MAAM;AAE/B;AAAA,IACF;AAAA,EACF;AACF;","names":[]}
|
package/package.json
ADDED
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@yashrsb/retry-kit",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "Retry asynchronous operations with exponential backoff",
|
|
5
|
+
"main": "./dist/index.cjs",
|
|
6
|
+
"module": "./dist/index.js",
|
|
7
|
+
"types": "./dist/index.d.ts",
|
|
8
|
+
"exports": {
|
|
9
|
+
".": {
|
|
10
|
+
"types": "./dist/index.d.ts",
|
|
11
|
+
"import": "./dist/index.js",
|
|
12
|
+
"require": "./dist/index.cjs"
|
|
13
|
+
}
|
|
14
|
+
},
|
|
15
|
+
"files": [
|
|
16
|
+
"dist"
|
|
17
|
+
],
|
|
18
|
+
"publishConfig": {
|
|
19
|
+
"access": "public"
|
|
20
|
+
},
|
|
21
|
+
"scripts": {
|
|
22
|
+
"test": "vitest",
|
|
23
|
+
"format": "prettier . --write",
|
|
24
|
+
"format:check": "prettier . --check",
|
|
25
|
+
"build": "tsup"
|
|
26
|
+
},
|
|
27
|
+
"keywords": [
|
|
28
|
+
"retry",
|
|
29
|
+
"exponential-backoff",
|
|
30
|
+
"backoff",
|
|
31
|
+
"async"
|
|
32
|
+
],
|
|
33
|
+
"author": "",
|
|
34
|
+
"license": "MIT",
|
|
35
|
+
"type": "module",
|
|
36
|
+
"devDependencies": {
|
|
37
|
+
"prettier": "^3.9.6",
|
|
38
|
+
"tsup": "^8.5.1",
|
|
39
|
+
"typescript": "^5.7.3",
|
|
40
|
+
"vitest": "^5.0.0"
|
|
41
|
+
}
|
|
42
|
+
}
|