breaker-box 4.1.0 → 5.0.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 +58 -19
- package/dist/index.cjs +112 -58
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +55 -30
- package/dist/index.d.mts +55 -30
- package/dist/index.mjs +112 -59
- package/dist/index.mjs.map +1 -1
- package/package.json +15 -12
package/README.md
CHANGED
|
@@ -27,7 +27,6 @@ async function unreliableApiCall(data: string) {
|
|
|
27
27
|
}
|
|
28
28
|
|
|
29
29
|
const protectedApiCall = createCircuitBreaker(unreliableApiCall, {
|
|
30
|
-
errorIsFailure: (error) => error.message.includes("404"), // Don't retry 404s
|
|
31
30
|
errorThreshold: 0.5, // Open circuit when 50% of calls fail
|
|
32
31
|
errorWindow: 10_000, // Track errors over 10 second window
|
|
33
32
|
minimumCandidates: 6, // Need at least 6 calls before calculating error rate
|
|
@@ -44,37 +43,54 @@ try {
|
|
|
44
43
|
|
|
45
44
|
### Retry Strategies
|
|
46
45
|
|
|
46
|
+
The circuit breaker doesn't retry - it only tracks failures and prevents calls when too many fail. Use `withRetry` to add retry logic:
|
|
47
|
+
|
|
47
48
|
```typescript
|
|
48
49
|
import {
|
|
49
50
|
createCircuitBreaker,
|
|
51
|
+
withRetry,
|
|
50
52
|
useExponentialBackoff,
|
|
51
53
|
useFibonacciBackoff,
|
|
52
54
|
} from "breaker-box"
|
|
53
55
|
|
|
54
56
|
// Exponential backoff: 1s, 2s, 4s, 8s, up to 30s max
|
|
55
|
-
const protectedWithExponential = createCircuitBreaker(
|
|
56
|
-
retryDelay: useExponentialBackoff(30),
|
|
57
|
-
|
|
57
|
+
const protectedWithExponential = createCircuitBreaker(
|
|
58
|
+
withRetry(unreliableApiCall, { retryDelay: useExponentialBackoff(30) }),
|
|
59
|
+
)
|
|
58
60
|
|
|
59
61
|
// Fibonacci backoff: 1s, 2s, 3s, 5s, 8s, up to 60s max
|
|
60
|
-
const protectedWithFibonacci = createCircuitBreaker(
|
|
61
|
-
retryDelay: useFibonacciBackoff(60),
|
|
62
|
-
|
|
62
|
+
const protectedWithFibonacci = createCircuitBreaker(
|
|
63
|
+
withRetry(unreliableApiCall, { retryDelay: useFibonacciBackoff(60) }),
|
|
64
|
+
)
|
|
63
65
|
```
|
|
64
66
|
|
|
65
67
|
### Timeout Protection
|
|
66
68
|
|
|
69
|
+
Compose `withTimeout`, `withRetry`, and circuit breaker for complete fault tolerance:
|
|
70
|
+
|
|
67
71
|
```typescript
|
|
68
|
-
import {
|
|
72
|
+
import {
|
|
73
|
+
createCircuitBreaker,
|
|
74
|
+
withRetry,
|
|
75
|
+
withTimeout,
|
|
76
|
+
useExponentialBackoff,
|
|
77
|
+
} from "breaker-box"
|
|
69
78
|
|
|
70
|
-
//
|
|
71
|
-
const
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
79
|
+
// 1. Add timeout (innermost - fails fast)
|
|
80
|
+
const timedCall = withTimeout(unreliableApiCall, 5_000, "Request timed out")
|
|
81
|
+
|
|
82
|
+
// 2. Add retry logic (middle - retries on transient errors)
|
|
83
|
+
const retryCall = withRetry(timedCall, {
|
|
84
|
+
maxAttempts: 3,
|
|
85
|
+
retryDelay: useExponentialBackoff(10),
|
|
86
|
+
shouldRetry: (error) => !error.message.includes("404"), // Don't retry 404s
|
|
87
|
+
})
|
|
76
88
|
|
|
77
|
-
|
|
89
|
+
// 3. Add circuit breaker (outermost - prevents cascading failures)
|
|
90
|
+
const protectedApiCall = createCircuitBreaker(retryCall, {
|
|
91
|
+
errorThreshold: 0.5,
|
|
92
|
+
minimumCandidates: 5,
|
|
93
|
+
})
|
|
78
94
|
```
|
|
79
95
|
|
|
80
96
|
### Event Monitoring
|
|
@@ -82,10 +98,13 @@ const protectedApiCall = createCircuitBreaker(timeoutProtectedCall, {})
|
|
|
82
98
|
```typescript
|
|
83
99
|
const protectedFunction = createCircuitBreaker(unreliableApiCall, {
|
|
84
100
|
onClose: () => {
|
|
85
|
-
console.log("Circuit closed - normal operation resumed")
|
|
101
|
+
console.log("🟢 Circuit closed - normal operation resumed")
|
|
102
|
+
},
|
|
103
|
+
onHalfOpen: () => {
|
|
104
|
+
console.log("🟡 Circuit half-opened - waiting for success")
|
|
86
105
|
},
|
|
87
106
|
onOpen: (cause) => {
|
|
88
|
-
console.log("Circuit opened due to:", cause.message)
|
|
107
|
+
console.log("🔴 Circuit opened due to:", cause.message)
|
|
89
108
|
},
|
|
90
109
|
})
|
|
91
110
|
|
|
@@ -111,15 +130,15 @@ Creates a circuit breaker around the provided async function.
|
|
|
111
130
|
|
|
112
131
|
- `fn`: The async function to protect
|
|
113
132
|
- `options`: Configuration object (optional)
|
|
114
|
-
- `errorIsFailure`: Function to determine if an error
|
|
133
|
+
- `errorIsFailure`: Function to determine if an error should not count toward circuit breaker metrics (default: `() => false`)
|
|
115
134
|
- `errorThreshold`: Percentage (0-1) of errors that triggers circuit opening (default: `0`)
|
|
116
135
|
- `errorWindow`: Time window in ms for tracking errors (default: `10_000`)
|
|
117
136
|
- `fallback`: Function to call when circuit is open (default: undefined)
|
|
118
137
|
- `minimumCandidates`: Minimum calls before calculating error rate (default: `6`)
|
|
119
138
|
- `onClose`: Function called when circuit closes (default: undefined)
|
|
139
|
+
- `onHalfOpen`: Function called when circuit enters half-open state (default: undefined)
|
|
120
140
|
- `onOpen`: Function called when circuit opens (default: undefined)
|
|
121
141
|
- `resetAfter`: Milliseconds to wait before trying half-open (default: `30_000`)
|
|
122
|
-
- `retryDelay`: Function returning promise for retry delays (default: immediate retry)
|
|
123
142
|
|
|
124
143
|
#### Returns
|
|
125
144
|
|
|
@@ -131,6 +150,26 @@ A function with the same signature as `fn` and additional methods:
|
|
|
131
150
|
|
|
132
151
|
### Helper Functions
|
|
133
152
|
|
|
153
|
+
#### `withRetry(fn, options?)`
|
|
154
|
+
|
|
155
|
+
Wraps a function with retry logic. Failures will be retried according to the provided options.
|
|
156
|
+
|
|
157
|
+
**Options:**
|
|
158
|
+
|
|
159
|
+
- `maxAttempts`: Maximum number of attempts (default: `3`)
|
|
160
|
+
- `retryDelay`: Function returning promise for when to retry (default: immediate)
|
|
161
|
+
- `shouldRetry`: Function to determine if error should be retried (default: `() => true`)
|
|
162
|
+
|
|
163
|
+
**Example:**
|
|
164
|
+
|
|
165
|
+
```typescript
|
|
166
|
+
const retryCall = withRetry(apiCall, {
|
|
167
|
+
maxAttempts: 5,
|
|
168
|
+
retryDelay: useExponentialBackoff(30),
|
|
169
|
+
shouldRetry: (error) => error.statusCode !== 404,
|
|
170
|
+
})
|
|
171
|
+
```
|
|
172
|
+
|
|
134
173
|
#### `useExponentialBackoff(maxSeconds)`
|
|
135
174
|
|
|
136
175
|
Returns a retry delay function that implements exponential backoff (2^n seconds, capped at maxSeconds).
|
package/dist/index.cjs
CHANGED
|
@@ -3,6 +3,23 @@
|
|
|
3
3
|
function assert(value, message) {
|
|
4
4
|
if (!value) throw new TypeError(message);
|
|
5
5
|
}
|
|
6
|
+
const assertNever = (val, msg = "Unexpected value") => {
|
|
7
|
+
throw new TypeError(`${msg}: ${val}`);
|
|
8
|
+
};
|
|
9
|
+
const delayMs = (ms) => new Promise((next) => setTimeout(next, ms));
|
|
10
|
+
const rejectOnAbort = (signal, pending) => {
|
|
11
|
+
let teardown;
|
|
12
|
+
return Promise.race([
|
|
13
|
+
Promise.resolve(pending).finally(() => {
|
|
14
|
+
signal.removeEventListener("abort", teardown);
|
|
15
|
+
}),
|
|
16
|
+
new Promise((_, reject) => {
|
|
17
|
+
teardown = () => reject(signal.reason);
|
|
18
|
+
signal.addEventListener("abort", teardown, { once: true });
|
|
19
|
+
})
|
|
20
|
+
]);
|
|
21
|
+
};
|
|
22
|
+
|
|
6
23
|
function parseOptions(options) {
|
|
7
24
|
const {
|
|
8
25
|
errorIsFailure = () => false,
|
|
@@ -10,9 +27,9 @@ function parseOptions(options) {
|
|
|
10
27
|
errorWindow = 1e4,
|
|
11
28
|
minimumCandidates = 6,
|
|
12
29
|
onClose,
|
|
30
|
+
onHalfOpen,
|
|
13
31
|
onOpen,
|
|
14
|
-
resetAfter = 3e4
|
|
15
|
-
retryDelay = () => void 0
|
|
32
|
+
resetAfter = 3e4
|
|
16
33
|
} = options;
|
|
17
34
|
assert(
|
|
18
35
|
typeof errorIsFailure === "function",
|
|
@@ -20,37 +37,35 @@ function parseOptions(options) {
|
|
|
20
37
|
);
|
|
21
38
|
assert(
|
|
22
39
|
errorThreshold >= 0 && errorThreshold <= 1,
|
|
23
|
-
`"errorThreshold" must be
|
|
40
|
+
`"errorThreshold" must be between 0 and 1 (received ${errorThreshold})`
|
|
24
41
|
);
|
|
25
42
|
assert(
|
|
26
|
-
errorWindow
|
|
27
|
-
`"errorWindow" must be milliseconds
|
|
43
|
+
errorWindow >= 1e3,
|
|
44
|
+
`"errorWindow" must be milliseconds of at least 1 second (received ${errorWindow})`
|
|
28
45
|
);
|
|
29
46
|
assert(
|
|
30
|
-
minimumCandidates
|
|
31
|
-
`"minimumCandidates" must be
|
|
47
|
+
minimumCandidates >= 1,
|
|
48
|
+
`"minimumCandidates" must be greater than 0 (received ${minimumCandidates})`
|
|
32
49
|
);
|
|
33
|
-
if (onClose)
|
|
34
|
-
assert(
|
|
35
|
-
typeof onClose === "function",
|
|
36
|
-
`"onClose" must be a function (received ${typeof onClose})`
|
|
37
|
-
);
|
|
38
|
-
if (onOpen)
|
|
39
|
-
assert(
|
|
40
|
-
typeof onOpen === "function",
|
|
41
|
-
`"onOpen" must be a function (received ${typeof onOpen})`
|
|
42
|
-
);
|
|
43
50
|
assert(
|
|
44
|
-
|
|
45
|
-
`"
|
|
51
|
+
!onClose || typeof onClose === "function",
|
|
52
|
+
`"onClose" must be a function (received ${typeof onClose})`
|
|
46
53
|
);
|
|
47
54
|
assert(
|
|
48
|
-
|
|
49
|
-
`"
|
|
55
|
+
!onHalfOpen || typeof onHalfOpen === "function",
|
|
56
|
+
`"onHalfOpen" must be a function (received ${typeof onHalfOpen})`
|
|
57
|
+
);
|
|
58
|
+
assert(
|
|
59
|
+
!onOpen || typeof onOpen === "function",
|
|
60
|
+
`"onOpen" must be a function (received ${typeof onOpen})`
|
|
61
|
+
);
|
|
62
|
+
assert(
|
|
63
|
+
resetAfter >= 1e3,
|
|
64
|
+
`"resetAfter" must be milliseconds of at least 1 second (received ${resetAfter})`
|
|
50
65
|
);
|
|
51
66
|
assert(
|
|
52
|
-
|
|
53
|
-
`"
|
|
67
|
+
resetAfter >= errorWindow,
|
|
68
|
+
`"resetAfter" must be greater than or equal to "errorWindow" (received ${resetAfter}, expected >= ${errorWindow})`
|
|
54
69
|
);
|
|
55
70
|
return {
|
|
56
71
|
errorIsFailure,
|
|
@@ -58,28 +73,13 @@ function parseOptions(options) {
|
|
|
58
73
|
errorWindow,
|
|
59
74
|
minimumCandidates,
|
|
60
75
|
onClose,
|
|
76
|
+
onHalfOpen,
|
|
61
77
|
onOpen,
|
|
62
|
-
resetAfter
|
|
63
|
-
retryDelay
|
|
78
|
+
resetAfter
|
|
64
79
|
};
|
|
65
80
|
}
|
|
66
81
|
|
|
67
|
-
const
|
|
68
|
-
throw new TypeError(`${msg}: ${val}`);
|
|
69
|
-
};
|
|
70
|
-
const delayMs = (ms) => new Promise((next) => setTimeout(next, ms));
|
|
71
|
-
const rejectOnAbort = (signal, pending) => {
|
|
72
|
-
let teardown;
|
|
73
|
-
return Promise.race([
|
|
74
|
-
Promise.resolve(pending).finally(
|
|
75
|
-
() => signal.removeEventListener("abort", teardown)
|
|
76
|
-
),
|
|
77
|
-
new Promise((_, reject) => {
|
|
78
|
-
teardown = () => reject(signal.reason);
|
|
79
|
-
signal.addEventListener("abort", teardown);
|
|
80
|
-
})
|
|
81
|
-
]);
|
|
82
|
-
};
|
|
82
|
+
const disposeKey = Symbol("disposeKey");
|
|
83
83
|
|
|
84
84
|
function useExponentialBackoff(maxSeconds) {
|
|
85
85
|
return function exponentialBackoff(attempt) {
|
|
@@ -96,17 +96,70 @@ function useFibonacciBackoff(maxSeconds) {
|
|
|
96
96
|
return delayMs(delay * 1e3);
|
|
97
97
|
};
|
|
98
98
|
}
|
|
99
|
+
function withRetry(main, options = {}) {
|
|
100
|
+
const {
|
|
101
|
+
shouldRetry = () => true,
|
|
102
|
+
maxAttempts = 3,
|
|
103
|
+
retryDelay = () => Promise.resolve()
|
|
104
|
+
} = options;
|
|
105
|
+
assert(maxAttempts >= 1, "maxAttempts must be a number greater than 0");
|
|
106
|
+
const controller = new AbortController();
|
|
107
|
+
const { signal } = controller;
|
|
108
|
+
async function execute(args, attempt = 1) {
|
|
109
|
+
try {
|
|
110
|
+
return await main(...args);
|
|
111
|
+
} catch (cause) {
|
|
112
|
+
if (!shouldRetry(cause, attempt)) throw cause;
|
|
113
|
+
if (attempt >= maxAttempts)
|
|
114
|
+
throw new Error(`ERR_CIRCUIT_BREAKER_MAX_ATTEMPTS (${maxAttempts})`, {
|
|
115
|
+
cause
|
|
116
|
+
});
|
|
117
|
+
await rejectOnAbort(signal, retryDelay(attempt + 1, signal));
|
|
118
|
+
return execute(args, attempt + 1);
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
return Object.assign(
|
|
122
|
+
function withRetryFunction(...args) {
|
|
123
|
+
return execute(args);
|
|
124
|
+
},
|
|
125
|
+
{
|
|
126
|
+
[disposeKey]: (disposeMessage = "ERR_CIRCUIT_BREAKER_DISPOSED") => {
|
|
127
|
+
const reason = new ReferenceError(disposeMessage);
|
|
128
|
+
main[disposeKey]?.(disposeMessage);
|
|
129
|
+
controller.abort(reason);
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
);
|
|
133
|
+
}
|
|
99
134
|
function withTimeout(main, timeoutMs, timeoutMessage = "ERR_CIRCUIT_BREAKER_TIMEOUT") {
|
|
100
135
|
const error = new Error(timeoutMessage);
|
|
101
|
-
|
|
136
|
+
const controller = new AbortController();
|
|
137
|
+
const { signal } = controller;
|
|
138
|
+
function withTimeoutFunction(...args) {
|
|
139
|
+
let teardown;
|
|
102
140
|
let timer;
|
|
103
141
|
return Promise.race([
|
|
104
|
-
main(...args).finally(() =>
|
|
142
|
+
main(...args).finally(() => {
|
|
143
|
+
clearTimeout(timer);
|
|
144
|
+
signal.removeEventListener("abort", teardown);
|
|
145
|
+
}),
|
|
105
146
|
new Promise((_, reject) => {
|
|
147
|
+
teardown = () => {
|
|
148
|
+
clearTimeout(timer);
|
|
149
|
+
reject(signal.reason);
|
|
150
|
+
};
|
|
106
151
|
timer = setTimeout(reject, timeoutMs, error);
|
|
152
|
+
signal.addEventListener("abort", teardown, { once: true });
|
|
107
153
|
})
|
|
108
154
|
]);
|
|
109
|
-
}
|
|
155
|
+
}
|
|
156
|
+
return Object.assign(withTimeoutFunction, {
|
|
157
|
+
[disposeKey]: (disposeMessage = "ERR_CIRCUIT_BREAKER_DISPOSED") => {
|
|
158
|
+
const reason = new ReferenceError(disposeMessage);
|
|
159
|
+
main[disposeKey]?.(disposeMessage);
|
|
160
|
+
controller.abort(reason);
|
|
161
|
+
}
|
|
162
|
+
});
|
|
110
163
|
}
|
|
111
164
|
|
|
112
165
|
function createCircuitBreaker(main, options = {}) {
|
|
@@ -116,9 +169,9 @@ function createCircuitBreaker(main, options = {}) {
|
|
|
116
169
|
errorWindow,
|
|
117
170
|
minimumCandidates,
|
|
118
171
|
onClose,
|
|
172
|
+
onHalfOpen,
|
|
119
173
|
onOpen,
|
|
120
|
-
resetAfter
|
|
121
|
-
retryDelay
|
|
174
|
+
resetAfter
|
|
122
175
|
} = parseOptions(options);
|
|
123
176
|
const controller = new AbortController();
|
|
124
177
|
const history = /* @__PURE__ */ new Map();
|
|
@@ -151,7 +204,10 @@ function createCircuitBreaker(main, options = {}) {
|
|
|
151
204
|
failureCause = cause;
|
|
152
205
|
state = "open";
|
|
153
206
|
clearTimeout(resetTimer);
|
|
154
|
-
resetTimer = setTimeout(() =>
|
|
207
|
+
resetTimer = setTimeout(() => {
|
|
208
|
+
state = "halfOpen";
|
|
209
|
+
onHalfOpen?.();
|
|
210
|
+
}, resetAfter);
|
|
155
211
|
onOpen?.(cause);
|
|
156
212
|
}
|
|
157
213
|
function createHistoryItem(pending) {
|
|
@@ -161,7 +217,7 @@ function createCircuitBreaker(main, options = {}) {
|
|
|
161
217
|
history.delete(pending);
|
|
162
218
|
signal.removeEventListener("abort", teardown);
|
|
163
219
|
};
|
|
164
|
-
signal.addEventListener("abort", teardown);
|
|
220
|
+
signal.addEventListener("abort", teardown, { once: true });
|
|
165
221
|
const settle = (value) => {
|
|
166
222
|
if (signal.aborted) return;
|
|
167
223
|
entry.status = value;
|
|
@@ -170,7 +226,7 @@ function createCircuitBreaker(main, options = {}) {
|
|
|
170
226
|
history.set(pending, entry);
|
|
171
227
|
return { pending, settle, teardown };
|
|
172
228
|
}
|
|
173
|
-
function execute(
|
|
229
|
+
function execute(args) {
|
|
174
230
|
if (state === "closed") {
|
|
175
231
|
const { pending, settle, teardown } = createHistoryItem(main(...args));
|
|
176
232
|
return pending.then(
|
|
@@ -178,16 +234,14 @@ function createCircuitBreaker(main, options = {}) {
|
|
|
178
234
|
settle("resolved");
|
|
179
235
|
return result;
|
|
180
236
|
},
|
|
181
|
-
|
|
237
|
+
(cause) => {
|
|
182
238
|
if (signal.aborted || errorIsFailure(cause)) {
|
|
183
239
|
teardown();
|
|
184
240
|
throw cause;
|
|
185
241
|
}
|
|
186
242
|
settle("rejected");
|
|
187
243
|
if (failureRate() > errorThreshold) openCircuit(cause);
|
|
188
|
-
|
|
189
|
-
await rejectOnAbort(signal, retryDelay(next, signal));
|
|
190
|
-
return execute(next, args);
|
|
244
|
+
return fallback(...args);
|
|
191
245
|
}
|
|
192
246
|
);
|
|
193
247
|
} else if (state === "open" || halfOpenPending) {
|
|
@@ -199,20 +253,19 @@ function createCircuitBreaker(main, options = {}) {
|
|
|
199
253
|
closeCircuit();
|
|
200
254
|
return result;
|
|
201
255
|
},
|
|
202
|
-
|
|
256
|
+
(cause) => {
|
|
203
257
|
if (signal.aborted || errorIsFailure(cause)) throw cause;
|
|
204
258
|
openCircuit(cause);
|
|
205
|
-
|
|
206
|
-
await rejectOnAbort(signal, retryDelay(next, signal));
|
|
207
|
-
return execute(next, args);
|
|
259
|
+
return fallback(...args);
|
|
208
260
|
}
|
|
209
261
|
);
|
|
210
262
|
}
|
|
211
263
|
return assertNever(state);
|
|
212
264
|
}
|
|
213
|
-
return Object.assign((...args) => execute(
|
|
265
|
+
return Object.assign((...args) => execute(args), {
|
|
214
266
|
dispose: (disposeMessage = "ERR_CIRCUIT_BREAKER_DISPOSED") => {
|
|
215
267
|
const reason = new ReferenceError(disposeMessage);
|
|
268
|
+
main[disposeKey]?.(disposeMessage);
|
|
216
269
|
clearFailure();
|
|
217
270
|
clearTimeout(resetTimer);
|
|
218
271
|
history.forEach((entry) => clearTimeout(entry.timer));
|
|
@@ -230,5 +283,6 @@ exports.createCircuitBreaker = createCircuitBreaker;
|
|
|
230
283
|
exports.delayMs = delayMs;
|
|
231
284
|
exports.useExponentialBackoff = useExponentialBackoff;
|
|
232
285
|
exports.useFibonacciBackoff = useFibonacciBackoff;
|
|
286
|
+
exports.withRetry = withRetry;
|
|
233
287
|
exports.withTimeout = withTimeout;
|
|
234
288
|
//# sourceMappingURL=index.cjs.map
|
package/dist/index.cjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.cjs","sources":["../lib/options.ts","../lib/util.ts","../lib/helpers.ts","../lib/index.ts"],"sourcesContent":["import type { CircuitBreakerOptions } from \"./types.js\"\nimport type { AnyFn } from \"./util.js\"\n\nfunction assert(value: unknown, message?: string): asserts value {\n\tif (!value) throw new TypeError(message)\n}\n\nexport function parseOptions<Fallback extends AnyFn>(\n\toptions: CircuitBreakerOptions<Fallback>,\n) {\n\tconst {\n\t\terrorIsFailure = () => false,\n\t\terrorThreshold = 0,\n\t\terrorWindow = 10_000,\n\t\tminimumCandidates = 6,\n\t\tonClose,\n\t\tonOpen,\n\t\tresetAfter = 30_000,\n\t\tretryDelay = () => undefined,\n\t} = options\n\n\t// errorIsFailure\n\tassert(\n\t\ttypeof errorIsFailure === \"function\",\n\t\t`\"errorIsFailure\" must be a function (received ${typeof errorIsFailure})`,\n\t)\n\n\t// errorThreshold\n\tassert(\n\t\terrorThreshold >= 0 && errorThreshold <= 1,\n\t\t`\"errorThreshold\" must be a number between 0 and 1 (received ${errorThreshold})`,\n\t)\n\n\t// errorWindow\n\tassert(\n\t\terrorWindow > 0,\n\t\t`\"errorWindow\" must be milliseconds greater than 0 (received ${errorWindow})`,\n\t)\n\n\t// minimumCandidates\n\tassert(\n\t\tminimumCandidates > 1,\n\t\t`\"minimumCandidates\" must be a number greater than 1 (received ${minimumCandidates})`,\n\t)\n\n\t// (optional) onClose\n\tif (onClose)\n\t\tassert(\n\t\t\ttypeof onClose === \"function\",\n\t\t\t`\"onClose\" must be a function (received ${typeof onClose})`,\n\t\t)\n\n\t// (optional) onOpen\n\tif (onOpen)\n\t\tassert(\n\t\t\ttypeof onOpen === \"function\",\n\t\t\t`\"onOpen\" must be a function (received ${typeof onOpen})`,\n\t\t)\n\n\t// resetAfter\n\tassert(\n\t\tresetAfter > 0,\n\t\t`\"resetAfter\" must be milliseconds greater than 0 (received ${resetAfter})`,\n\t)\n\tassert(\n\t\tresetAfter >= errorWindow,\n\t\t`\"resetAfter\" must be milliseconds greater than or equal to \"errorWindow\" (received ${resetAfter})`,\n\t)\n\n\t// retryDelay\n\tassert(\n\t\ttypeof retryDelay === \"function\",\n\t\t`\"retryDelay\" must be a function (received ${typeof retryDelay})`,\n\t)\n\n\treturn {\n\t\terrorIsFailure,\n\t\terrorThreshold,\n\t\terrorWindow,\n\t\tminimumCandidates,\n\t\tonClose,\n\t\tonOpen,\n\t\tresetAfter,\n\t\tretryDelay,\n\t}\n}\n","export type AnyFn = (...args: any[]) => any\n\n/**\n * `[TypeScript]` For exhaustive checks in switch statements or if/else. Add\n * this check to `default` case or final `else` to ensure all possible values\n * have been handled. If a new value is added to the type, TypeScript will\n * throw an error and the editor will underline the `value`.\n */\n/* v8 ignore next 3 */\nexport const assertNever = (val: never, msg = \"Unexpected value\") => {\n\tthrow new TypeError(`${msg}: ${val}`)\n}\n\n/**\n * Returns a promise that resolves after the specified number of milliseconds.\n */\nexport const delayMs = (ms: number): Promise<void> =>\n\tnew Promise((next) => setTimeout(next, ms))\n\n/**\n * Rejects the given promise when the abort signal is triggered.\n */\nexport const rejectOnAbort = <T extends Promise<any> | undefined>(\n\tsignal: AbortSignal,\n\tpending: T,\n): Promise<Awaited<T>> => {\n\tlet teardown: () => void\n\treturn Promise.race([\n\t\tPromise.resolve(pending).finally(() =>\n\t\t\tsignal.removeEventListener(\"abort\", teardown),\n\t\t),\n\t\tnew Promise<never>((_, reject) => {\n\t\t\tteardown = () => reject(signal.reason)\n\t\t\tsignal.addEventListener(\"abort\", teardown)\n\t\t}),\n\t])\n}\n","import type { MainFn } from \"./types.js\"\nimport { delayMs } from \"./util.js\"\n\n/**\n * Returns a function which implements exponential backoff.\n *\n * @param maxSeconds - The maximum number of seconds to wait before retrying.\n * @returns A function which takes an `attempt` number and returns a promise\n * that resolves after the calculated delay.\n */\nexport function useExponentialBackoff(maxSeconds: number) {\n\treturn function exponentialBackoff(attempt: number) {\n\t\tconst num = Math.max(attempt - 2, 0)\n\t\tconst delay = Math.min(2 ** num, maxSeconds)\n\t\treturn delayMs(delay * 1_000)\n\t}\n}\n\nconst sqrt5 = /* @__PURE__ */ Math.sqrt(5)\n/**\n * @see https://en.wikipedia.org/wiki/Fibonacci_sequence#Closed-form_expression\n */\nconst binet = (n: number) =>\n\tMath.round(((1 + sqrt5) ** n - (1 - sqrt5) ** n) / (2 ** n * sqrt5))\n\n/**\n * Returns a function which implements Fibonacci backoff.\n *\n * @param maxSeconds - The maximum number of seconds to wait before retrying.\n * @returns A function which takes an `attempt` number and returns a promise\n * that resolves after the calculated delay.\n */\nexport function useFibonacciBackoff(maxSeconds: number) {\n\treturn function fibonacciBackoff(attempt: number) {\n\t\tconst delay = Math.min(binet(attempt), maxSeconds)\n\t\treturn delayMs(delay * 1_000)\n\t}\n}\n\n/**\n * Wrap a function with a timeout. If execution of `main` exceeds `timeoutMs`,\n * then the call is rejected with `new Error(timeoutMessage)`.\n */\nexport function withTimeout<Ret, Args extends unknown[]>(\n\tmain: MainFn<Ret, Args>,\n\ttimeoutMs: number,\n\ttimeoutMessage = \"ERR_CIRCUIT_BREAKER_TIMEOUT\",\n): MainFn<Ret, Args> {\n\tconst error = new Error(timeoutMessage)\n\n\treturn function withTimeoutFunction(...args) {\n\t\tlet timer: NodeJS.Timeout\n\t\treturn Promise.race([\n\t\t\tmain(...args).finally(() => clearTimeout(timer)),\n\t\t\tnew Promise<never>((_, reject) => {\n\t\t\t\ttimer = setTimeout(reject, timeoutMs, error)\n\t\t\t}),\n\t\t])\n\t}\n}\n","import { parseOptions } from \"./options.js\"\nimport type {\n\tHistoryEntry,\n\tHistoryMap,\n\tCircuitBreakerOptions,\n\tCircuitBreakerProtectedFn,\n\tCircuitState,\n\tMainFn,\n} from \"./types.js\"\nimport { assertNever, rejectOnAbort, type AnyFn } from \"./util.js\"\n\nexport * from \"./helpers.js\"\nexport { delayMs } from \"./util.js\"\n\nexport function createCircuitBreaker<\n\tRet,\n\tArgs extends unknown[],\n\tFallback extends AnyFn = MainFn<Ret, Args>,\n>(\n\tmain: MainFn<Ret, Args>,\n\toptions: CircuitBreakerOptions<Fallback> = {},\n): CircuitBreakerProtectedFn<Ret, Args> {\n\tconst {\n\t\terrorIsFailure,\n\t\terrorThreshold,\n\t\terrorWindow,\n\t\tminimumCandidates,\n\t\tonClose,\n\t\tonOpen,\n\t\tresetAfter,\n\t\tretryDelay,\n\t} = parseOptions(options)\n\tconst controller = new AbortController()\n\tconst history: HistoryMap = new Map()\n\tconst signal = controller.signal\n\tlet failureCause: unknown\n\tlet fallback = options.fallback || (() => Promise.reject(failureCause))\n\tlet halfOpenPending: Promise<unknown> | undefined\n\tlet resetTimer: NodeJS.Timeout\n\tlet state: CircuitState = \"closed\"\n\n\tfunction clearFailure() {\n\t\tfailureCause = undefined\n\t}\n\n\tfunction closeCircuit() {\n\t\tstate = \"closed\"\n\t\tclearFailure()\n\t\tclearTimeout(resetTimer)\n\t\tonClose?.()\n\t}\n\n\tfunction failureRate() {\n\t\tlet failures = 0\n\t\tlet total = 0\n\t\tfor (const { status } of history.values()) {\n\t\t\tif (status === \"rejected\") failures++\n\t\t\tif (status !== \"pending\") total++\n\t\t}\n\t\t// Don't calculate anything until we have enough data\n\t\tif (!total || total < minimumCandidates) return 0\n\t\treturn failures / total\n\t}\n\n\t/**\n\t * Break the circuit and wait for a reset\n\t */\n\tfunction openCircuit(cause: unknown) {\n\t\tfailureCause = cause\n\t\tstate = \"open\"\n\t\tclearTimeout(resetTimer)\n\t\tresetTimer = setTimeout(() => (state = \"halfOpen\"), resetAfter)\n\t\tonOpen?.(cause)\n\t}\n\n\tfunction createHistoryItem<T>(pending: Promise<T>) {\n\t\tconst entry: HistoryEntry = { status: \"pending\", timer: undefined }\n\t\tconst teardown = () => {\n\t\t\tclearTimeout(entry.timer)\n\t\t\thistory.delete(pending)\n\t\t\tsignal.removeEventListener(\"abort\", teardown)\n\t\t}\n\t\tsignal.addEventListener(\"abort\", teardown)\n\t\tconst settle = (value: \"resolved\" | \"rejected\") => {\n\t\t\tif (signal.aborted) return\n\t\t\tentry.status = value\n\t\t\t// Remove the entry from history when it falls outside of the error window\n\t\t\tentry.timer = setTimeout(teardown, errorWindow)\n\t\t}\n\t\thistory.set(pending, entry)\n\t\treturn { pending, settle, teardown }\n\t}\n\n\t/**\n\t * Wrap calls to `main` with circuit breaker logic\n\t */\n\tfunction execute(attempt: number, args: Args): Promise<Ret> {\n\t\t// Normal operation when circuit is closed. If an error occurs, keep track\n\t\t// of the failure count and open the circuit if it exceeds the threshold.\n\t\tif (state === \"closed\") {\n\t\t\tconst { pending, settle, teardown } = createHistoryItem(main(...args))\n\t\t\treturn pending.then(\n\t\t\t\t(result) => {\n\t\t\t\t\tsettle(\"resolved\")\n\t\t\t\t\treturn result\n\t\t\t\t},\n\t\t\t\tasync (cause: unknown) => {\n\t\t\t\t\t// Was the circuit disposed, or was this a non-retryable error?\n\t\t\t\t\tif (signal.aborted || errorIsFailure(cause)) {\n\t\t\t\t\t\tteardown()\n\t\t\t\t\t\tthrow cause\n\t\t\t\t\t}\n\n\t\t\t\t\t// Should this failure open the circuit?\n\t\t\t\t\tsettle(\"rejected\")\n\t\t\t\t\tif (failureRate() > errorThreshold) openCircuit(cause)\n\n\t\t\t\t\t// Retry the call after a delay.\n\t\t\t\t\tconst next = attempt + 1\n\t\t\t\t\tawait rejectOnAbort(signal, retryDelay(next, signal))\n\t\t\t\t\treturn execute(next, args)\n\t\t\t\t},\n\t\t\t)\n\t\t}\n\n\t\t// Use the fallback while the circuit is open, or if a half-open trial\n\t\t// attempt was already made.\n\t\telse if (state === \"open\" || halfOpenPending) {\n\t\t\treturn fallback(...args)\n\t\t}\n\n\t\t// If the circuit is half-open, make one attempt. If it succeeds, close\n\t\t// the circuit and resume normal operation. If it fails, re-open the\n\t\t// circuit and run the fallback instead.\n\t\telse if (state === \"halfOpen\") {\n\t\t\treturn (halfOpenPending = main(...args))\n\t\t\t\t.finally(() => (halfOpenPending = undefined))\n\t\t\t\t.then(\n\t\t\t\t\t(result) => {\n\t\t\t\t\t\tif (signal.aborted) return result // disposed\n\t\t\t\t\t\tcloseCircuit()\n\t\t\t\t\t\treturn result\n\t\t\t\t\t},\n\t\t\t\t\tasync (cause: unknown) => {\n\t\t\t\t\t\t// Was the circuit disposed, or was this a non-retryable error?\n\t\t\t\t\t\tif (signal.aborted || errorIsFailure(cause)) throw cause\n\n\t\t\t\t\t\t// Open the circuit and try again later\n\t\t\t\t\t\topenCircuit(cause)\n\n\t\t\t\t\t\t// Retry the call after a delay.\n\t\t\t\t\t\tconst next = attempt + 1\n\t\t\t\t\t\tawait rejectOnAbort(signal, retryDelay(next, signal))\n\t\t\t\t\t\treturn execute(next, args)\n\t\t\t\t\t},\n\t\t\t\t)\n\t\t\t/* v8 ignore next */\n\t\t}\n\n\t\t// exhaustive check\n\t\t/* v8 ignore next */\n\t\treturn assertNever(state)\n\t}\n\n\treturn Object.assign((...args: Args) => execute(1, args), {\n\t\tdispose: (disposeMessage = \"ERR_CIRCUIT_BREAKER_DISPOSED\") => {\n\t\t\tconst reason = new ReferenceError(disposeMessage)\n\t\t\tclearFailure()\n\t\t\tclearTimeout(resetTimer)\n\t\t\thistory.forEach((entry) => clearTimeout(entry.timer))\n\t\t\thistory.clear()\n\t\t\tfallback = () => Promise.reject(reason)\n\t\t\tstate = \"open\"\n\t\t\tcontroller.abort(reason)\n\t\t},\n\t\tgetLatestError: () => failureCause,\n\t\tgetState: () => state,\n\t})\n}\n"],"names":[],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"index.cjs","sources":["../lib/util.ts","../lib/options.ts","../lib/types.ts","../lib/helpers.ts","../lib/index.ts"],"sourcesContent":["export type AnyFn = (...args: any[]) => any\n\n/**\n * Asserts that the given value is truthy. If not, throws a `TypeError`.\n */\nexport function assert(value: unknown, message?: string): asserts value {\n\tif (!value) throw new TypeError(message)\n}\n\n/**\n * `[TypeScript]` For exhaustive checks in switch statements or if/else. Add\n * this check to `default` case or final `else` to ensure all possible values\n * have been handled. If a new value is added to the type, TypeScript will\n * throw an error and the editor will underline the `value`.\n */\n/* v8 ignore next 3 */\nexport const assertNever = (val: never, msg = \"Unexpected value\") => {\n\tthrow new TypeError(`${msg}: ${val}`)\n}\n\n/**\n * Returns a promise that resolves after the specified number of milliseconds.\n */\nexport const delayMs = (ms: number): Promise<void> =>\n\tnew Promise((next) => setTimeout(next, ms))\n\n/**\n * Rejects the given promise when the abort signal is triggered.\n */\nexport const rejectOnAbort = <T extends Promise<unknown> | undefined>(\n\tsignal: AbortSignal,\n\tpending: T,\n): Promise<Awaited<T>> => {\n\tlet teardown: () => void\n\treturn Promise.race([\n\t\tPromise.resolve(pending).finally(() => {\n\t\t\tsignal.removeEventListener(\"abort\", teardown)\n\t\t}),\n\t\tnew Promise<never>((_, reject) => {\n\t\t\tteardown = () => reject(signal.reason)\n\t\t\tsignal.addEventListener(\"abort\", teardown, { once: true })\n\t\t}),\n\t])\n}\n","import type { CircuitBreakerOptions } from \"./types.js\"\nimport { assert, type AnyFn } from \"./util.js\"\n\nexport function parseOptions<Fallback extends AnyFn>(\n\toptions: CircuitBreakerOptions<Fallback>,\n) {\n\tconst {\n\t\terrorIsFailure = () => false,\n\t\terrorThreshold = 0,\n\t\terrorWindow = 10_000,\n\t\tminimumCandidates = 6,\n\t\tonClose,\n\t\tonHalfOpen,\n\t\tonOpen,\n\t\tresetAfter = 30_000,\n\t} = options\n\n\t// errorIsFailure\n\tassert(\n\t\ttypeof errorIsFailure === \"function\",\n\t\t`\"errorIsFailure\" must be a function (received ${typeof errorIsFailure})`,\n\t)\n\n\t// errorThreshold\n\tassert(\n\t\terrorThreshold >= 0 && errorThreshold <= 1,\n\t\t`\"errorThreshold\" must be between 0 and 1 (received ${errorThreshold})`,\n\t)\n\n\t// errorWindow\n\tassert(\n\t\terrorWindow >= 1_000,\n\t\t`\"errorWindow\" must be milliseconds of at least 1 second (received ${errorWindow})`,\n\t)\n\n\t// minimumCandidates\n\tassert(\n\t\tminimumCandidates >= 1,\n\t\t`\"minimumCandidates\" must be greater than 0 (received ${minimumCandidates})`,\n\t)\n\n\t// (optional) onClose\n\tassert(\n\t\t!onClose || typeof onClose === \"function\",\n\t\t`\"onClose\" must be a function (received ${typeof onClose})`,\n\t)\n\n\t// (optional) onHalfOpen\n\tassert(\n\t\t!onHalfOpen || typeof onHalfOpen === \"function\",\n\t\t`\"onHalfOpen\" must be a function (received ${typeof onHalfOpen})`,\n\t)\n\n\t// (optional) onOpen\n\tassert(\n\t\t!onOpen || typeof onOpen === \"function\",\n\t\t`\"onOpen\" must be a function (received ${typeof onOpen})`,\n\t)\n\n\t// resetAfter\n\tassert(\n\t\tresetAfter >= 1_000,\n\t\t`\"resetAfter\" must be milliseconds of at least 1 second (received ${resetAfter})`,\n\t)\n\tassert(\n\t\tresetAfter >= errorWindow,\n\t\t`\"resetAfter\" must be greater than or equal to \"errorWindow\" (received ${resetAfter}, expected >= ${errorWindow})`,\n\t)\n\n\treturn {\n\t\terrorIsFailure,\n\t\terrorThreshold,\n\t\terrorWindow,\n\t\tminimumCandidates,\n\t\tonClose,\n\t\tonHalfOpen,\n\t\tonOpen,\n\t\tresetAfter,\n\t}\n}\n","import type { AnyFn } from \"./util\"\n\nexport const disposeKey = Symbol(\"disposeKey\")\n\nexport type CircuitState = \"closed\" | \"halfOpen\" | \"open\"\n\nexport interface CircuitBreakerOptions<Fallback extends AnyFn = AnyFn> {\n\t/**\n\t * Whether an error should be treated as non-retryable failure. When used and\n\t * when an error is considered a failure, the error will be thrown to the\n\t * caller.\n\t *\n\t * @default () => false // Errors are retryable by default\n\t */\n\terrorIsFailure?: (error: unknown) => boolean\n\n\t/**\n\t * The percentage of errors (as a number between 0 and 1) which must occur\n\t * within the error window before the circuit breaker opens.\n\t *\n\t * @default 0 // Any error opens the circuit\n\t */\n\terrorThreshold?: number\n\n\t/**\n\t * The sliding window of time in milliseconds over which errors are counted.\n\t *\n\t * @default 10_000 // 10 seconds\n\t */\n\terrorWindow?: number\n\n\t/**\n\t * If provided, then all rejected calls to `main` will be forwarded to\n\t * this function instead.\n\t *\n\t * @default undefined // No fallback, errors are propagated\n\t */\n\tfallback?: Fallback\n\n\t/**\n\t * The minimum number of calls that must be made before calculating the\n\t * error rate and determining whether the circuit breaker should open based on\n\t * the `errorThreshold`.\n\t *\n\t * @default 6\n\t */\n\tminimumCandidates?: number\n\n\t/**\n\t * Provide a function to be called when the circuit breaker is closed.\n\t */\n\tonClose?: () => void\n\n\t/**\n\t * Provide a function to be called when the circuit breaker transitions to\n\t * half-open state.\n\t */\n\tonHalfOpen?: () => void\n\n\t/**\n\t * Provide a function to be called when the circuit breaker is opened. It\n\t * receives the error as its only argument.\n\t */\n\tonOpen?: (cause: unknown) => void\n\n\t/**\n\t * The amount of time in milliseconds to wait before transitioning to a\n\t * half-open state.\n\t *\n\t * @default 30_000 // 30 seconds\n\t */\n\tresetAfter?: number\n}\n\nexport interface CircuitBreakerProtectedFn<\n\tRet = unknown,\n\tArgs extends unknown[] = never[],\n> {\n\t(...args: Args): Promise<Ret>\n\n\t/**\n\t * Free memory and stop timers. All future calls will be rejected with the\n\t * provided message.\n\t *\n\t * @default \"ERR_CIRCUIT_BREAKER_DISPOSED\"\n\t */\n\tdispose(disposeMessage?: string): void\n\n\t/** Get the last error which triggered the circuit breaker */\n\tgetLatestError(): unknown | undefined\n\n\t/** Get the current state of the circuit breaker */\n\tgetState(): CircuitState\n}\n\nexport interface HistoryEntry {\n\ttimer: NodeJS.Timeout | undefined\n\tstatus: \"pending\" | \"resolved\" | \"rejected\"\n}\n\nexport type HistoryMap = Map<Promise<unknown>, HistoryEntry>\n\nexport interface MainFn<Ret = unknown, Args extends unknown[] = never[]> {\n\t(...args: Args): Promise<Ret>\n\n\t[disposeKey]?: (disposeMessage?: string) => void\n}\n","import { disposeKey, type MainFn } from \"./types.js\"\nimport { assert, delayMs, rejectOnAbort } from \"./util.js\"\n\n/**\n * Returns a function which implements exponential backoff.\n *\n * @param maxSeconds - The maximum number of seconds to wait before retrying.\n * @returns A function which takes an `attempt` number and returns a promise\n * that resolves after the calculated delay.\n */\nexport function useExponentialBackoff(maxSeconds: number) {\n\treturn function exponentialBackoff(attempt: number) {\n\t\tconst num = Math.max(attempt - 2, 0)\n\t\tconst delay = Math.min(2 ** num, maxSeconds)\n\t\treturn delayMs(delay * 1_000)\n\t}\n}\n\nconst sqrt5 = /* @__PURE__ */ Math.sqrt(5)\n/**\n * @see https://en.wikipedia.org/wiki/Fibonacci_sequence#Closed-form_expression\n */\nconst binet = (n: number) =>\n\tMath.round(((1 + sqrt5) ** n - (1 - sqrt5) ** n) / (2 ** n * sqrt5))\n\n/**\n * Returns a function which implements Fibonacci backoff.\n *\n * @param maxSeconds - The maximum number of seconds to wait before retrying.\n * @returns A function which takes an `attempt` number and returns a promise\n * that resolves after the calculated delay.\n */\nexport function useFibonacciBackoff(maxSeconds: number) {\n\treturn function fibonacciBackoff(attempt: number) {\n\t\tconst delay = Math.min(binet(attempt), maxSeconds)\n\t\treturn delayMs(delay * 1_000)\n\t}\n}\n\nexport interface RetryOptions {\n\t/**\n\t * Whether an error should be treated as non-retryable. When this returns\n\t * true, the error will be thrown immediately without retrying.\n\t *\n\t * @default () => false // All errors are retried\n\t */\n\tshouldRetry?: (error: unknown, attempt: number) => boolean\n\n\t/**\n\t * Maximum number of retries\n\t *\n\t * @default 3\n\t */\n\tmaxAttempts?: number\n\n\t/**\n\t * Function that returns a promise resolving when the next retry should occur.\n\t * Receives the attempt number (starting at 2) and an abort signal.\n\t *\n\t * @default () => Promise.resolve() // Immediate retry\n\t */\n\tretryDelay?: (attempt: number, signal: AbortSignal) => Promise<void>\n}\n\n/**\n * Wrap a function with retry logic. Errors will be retried according to the\n * provided options.\n *\n * @example\n * ```ts\n * // Compose with circuit breaker. Retry up to 3 times with no delay\n * const protectedA = createCircuitBreaker(\n * withRetry(unreliableApiCall, { maxAttempts: 3 })\n * )\n *\n * // Retry up to 5 times with exponential backoff\n * const protectedB = createCircuitBreaker(\n * withRetry(unreliableApiCall, {\n * maxAttempts: 5,\n * retryDelay: useExponentialBackoff(30),\n * })\n * )\n * ```\n */\nexport function withRetry<Ret, Args extends unknown[]>(\n\tmain: MainFn<Ret, Args>,\n\toptions: RetryOptions = {},\n): MainFn<Ret, Args> {\n\tconst {\n\t\tshouldRetry = () => true,\n\t\tmaxAttempts = 3,\n\t\tretryDelay = () => Promise.resolve(),\n\t} = options\n\n\tassert(maxAttempts >= 1, \"maxAttempts must be a number greater than 0\")\n\n\tconst controller = new AbortController()\n\tconst { signal } = controller\n\n\tasync function execute(args: Args, attempt = 1): Promise<Ret> {\n\t\ttry {\n\t\t\treturn await main(...args)\n\t\t} catch (cause) {\n\t\t\t// Check if we should retry this error\n\t\t\tif (!shouldRetry(cause, attempt)) throw cause\n\n\t\t\t// Check if we've exhausted attempts\n\t\t\tif (attempt >= maxAttempts)\n\t\t\t\tthrow new Error(`ERR_CIRCUIT_BREAKER_MAX_ATTEMPTS (${maxAttempts})`, {\n\t\t\t\t\tcause,\n\t\t\t\t})\n\n\t\t\t// Wait before retrying\n\t\t\tawait rejectOnAbort(signal, retryDelay(attempt + 1, signal))\n\n\t\t\t// Retry\n\t\t\treturn execute(args, attempt + 1)\n\t\t}\n\t}\n\n\treturn Object.assign(\n\t\tfunction withRetryFunction(...args: Args) {\n\t\t\treturn execute(args)\n\t\t},\n\t\t{\n\t\t\t[disposeKey]: (disposeMessage = \"ERR_CIRCUIT_BREAKER_DISPOSED\") => {\n\t\t\t\tconst reason = new ReferenceError(disposeMessage)\n\t\t\t\tmain[disposeKey]?.(disposeMessage)\n\t\t\t\tcontroller.abort(reason)\n\t\t\t},\n\t\t},\n\t)\n}\n\n/**\n * Wrap a function with a timeout. If execution of `main` exceeds `timeoutMs`,\n * then the call is rejected with `new Error(timeoutMessage)`.\n */\nexport function withTimeout<Ret, Args extends unknown[]>(\n\tmain: MainFn<Ret, Args>,\n\ttimeoutMs: number,\n\ttimeoutMessage = \"ERR_CIRCUIT_BREAKER_TIMEOUT\",\n): MainFn<Ret, Args> {\n\tconst error = new Error(timeoutMessage)\n\tconst controller = new AbortController()\n\tconst { signal } = controller\n\n\tfunction withTimeoutFunction(...args: Args) {\n\t\tlet teardown: () => void\n\t\tlet timer: NodeJS.Timeout\n\t\treturn Promise.race([\n\t\t\tmain(...args).finally(() => {\n\t\t\t\tclearTimeout(timer)\n\t\t\t\tsignal.removeEventListener(\"abort\", teardown)\n\t\t\t}),\n\t\t\tnew Promise<never>((_, reject) => {\n\t\t\t\tteardown = () => {\n\t\t\t\t\tclearTimeout(timer)\n\t\t\t\t\treject(signal.reason)\n\t\t\t\t}\n\t\t\t\ttimer = setTimeout(reject, timeoutMs, error)\n\t\t\t\tsignal.addEventListener(\"abort\", teardown, { once: true })\n\t\t\t}),\n\t\t])\n\t}\n\n\treturn Object.assign(withTimeoutFunction, {\n\t\t[disposeKey]: (disposeMessage = \"ERR_CIRCUIT_BREAKER_DISPOSED\") => {\n\t\t\tconst reason = new ReferenceError(disposeMessage)\n\t\t\tmain[disposeKey]?.(disposeMessage)\n\t\t\tcontroller.abort(reason)\n\t\t},\n\t})\n}\n","import { parseOptions } from \"./options.js\"\nimport {\n\tdisposeKey,\n\ttype CircuitBreakerOptions,\n\ttype CircuitBreakerProtectedFn,\n\ttype CircuitState,\n\ttype HistoryEntry,\n\ttype HistoryMap,\n\ttype MainFn,\n} from \"./types.js\"\nimport { assertNever, type AnyFn } from \"./util.js\"\n\nexport * from \"./helpers.js\"\nexport { delayMs } from \"./util.js\"\n\nexport function createCircuitBreaker<\n\tRet,\n\tArgs extends unknown[],\n\tFallback extends AnyFn = MainFn<Ret, Args>,\n>(\n\tmain: MainFn<Ret, Args>,\n\toptions: CircuitBreakerOptions<Fallback> = {},\n): CircuitBreakerProtectedFn<Ret, Args> {\n\tconst {\n\t\terrorIsFailure,\n\t\terrorThreshold,\n\t\terrorWindow,\n\t\tminimumCandidates,\n\t\tonClose,\n\t\tonHalfOpen,\n\t\tonOpen,\n\t\tresetAfter,\n\t} = parseOptions(options)\n\tconst controller = new AbortController()\n\tconst history: HistoryMap = new Map()\n\tconst signal = controller.signal\n\tlet failureCause: unknown\n\tlet fallback = options.fallback || (() => Promise.reject(failureCause))\n\tlet halfOpenPending: Promise<unknown> | undefined\n\tlet resetTimer: NodeJS.Timeout\n\tlet state: CircuitState = \"closed\"\n\n\tfunction clearFailure() {\n\t\tfailureCause = undefined\n\t}\n\n\tfunction closeCircuit() {\n\t\tstate = \"closed\"\n\t\tclearFailure()\n\t\tclearTimeout(resetTimer)\n\t\tonClose?.()\n\t}\n\n\tfunction failureRate() {\n\t\tlet failures = 0\n\t\tlet total = 0\n\t\tfor (const { status } of history.values()) {\n\t\t\tif (status === \"rejected\") failures++\n\t\t\tif (status !== \"pending\") total++\n\t\t}\n\t\t// Don't calculate anything until we have enough data\n\t\tif (!total || total < minimumCandidates) return 0\n\t\treturn failures / total\n\t}\n\n\t/**\n\t * Break the circuit and wait for a reset\n\t */\n\tfunction openCircuit(cause: unknown) {\n\t\tfailureCause = cause\n\t\tstate = \"open\"\n\t\tclearTimeout(resetTimer)\n\t\tresetTimer = setTimeout(() => {\n\t\t\tstate = \"halfOpen\"\n\t\t\tonHalfOpen?.()\n\t\t}, resetAfter)\n\t\tonOpen?.(cause)\n\t}\n\n\tfunction createHistoryItem<T>(pending: Promise<T>) {\n\t\tconst entry: HistoryEntry = { status: \"pending\", timer: undefined }\n\t\tconst teardown = () => {\n\t\t\tclearTimeout(entry.timer)\n\t\t\thistory.delete(pending)\n\t\t\tsignal.removeEventListener(\"abort\", teardown)\n\t\t}\n\t\tsignal.addEventListener(\"abort\", teardown, { once: true })\n\t\tconst settle = (value: \"resolved\" | \"rejected\") => {\n\t\t\tif (signal.aborted) return\n\t\t\tentry.status = value\n\t\t\t// Remove the entry from history when it falls outside of the error window\n\t\t\tentry.timer = setTimeout(teardown, errorWindow)\n\t\t}\n\t\thistory.set(pending, entry)\n\t\treturn { pending, settle, teardown }\n\t}\n\n\t/**\n\t * Wrap calls to `main` with circuit breaker logic\n\t */\n\tfunction execute(args: Args): Promise<Ret> {\n\t\t// Normal operation when circuit is closed. If an error occurs, keep track\n\t\t// of the failure count and open the circuit if it exceeds the threshold.\n\t\tif (state === \"closed\") {\n\t\t\tconst { pending, settle, teardown } = createHistoryItem(main(...args))\n\t\t\treturn pending.then(\n\t\t\t\t(result) => {\n\t\t\t\t\tsettle(\"resolved\")\n\t\t\t\t\treturn result\n\t\t\t\t},\n\t\t\t\t(cause: unknown) => {\n\t\t\t\t\t// Was the circuit disposed, or is this error considered a failure?\n\t\t\t\t\tif (signal.aborted || errorIsFailure(cause)) {\n\t\t\t\t\t\tteardown()\n\t\t\t\t\t\tthrow cause\n\t\t\t\t\t}\n\n\t\t\t\t\t// Should this error open the circuit?\n\t\t\t\t\tsettle(\"rejected\")\n\t\t\t\t\tif (failureRate() > errorThreshold) openCircuit(cause)\n\n\t\t\t\t\treturn fallback(...args)\n\t\t\t\t},\n\t\t\t)\n\t\t}\n\n\t\t// Use the fallback while the circuit is open, or if a half-open trial\n\t\t// attempt was already made.\n\t\telse if (state === \"open\" || halfOpenPending) {\n\t\t\treturn fallback(...args)\n\t\t}\n\n\t\t// If the circuit is half-open, make one attempt. If it succeeds, close\n\t\t// the circuit and resume normal operation. If it fails, re-open the\n\t\t// circuit and run the fallback instead.\n\t\telse if (state === \"halfOpen\") {\n\t\t\treturn (halfOpenPending = main(...args))\n\t\t\t\t.finally(() => (halfOpenPending = undefined))\n\t\t\t\t.then(\n\t\t\t\t\t(result) => {\n\t\t\t\t\t\tif (signal.aborted) return result // disposed\n\t\t\t\t\t\tcloseCircuit()\n\t\t\t\t\t\treturn result\n\t\t\t\t\t},\n\t\t\t\t\t(cause: unknown) => {\n\t\t\t\t\t\t// Was the circuit disposed, or was this a non-retryable error?\n\t\t\t\t\t\tif (signal.aborted || errorIsFailure(cause)) throw cause\n\n\t\t\t\t\t\t// Open the circuit and use fallback\n\t\t\t\t\t\topenCircuit(cause)\n\t\t\t\t\t\treturn fallback(...args)\n\t\t\t\t\t},\n\t\t\t\t)\n\t\t\t/* v8 ignore next */\n\t\t}\n\n\t\t// exhaustive check\n\t\t/* v8 ignore next */\n\t\treturn assertNever(state)\n\t}\n\n\treturn Object.assign((...args: Args) => execute(args), {\n\t\tdispose: (disposeMessage = \"ERR_CIRCUIT_BREAKER_DISPOSED\") => {\n\t\t\tconst reason = new ReferenceError(disposeMessage)\n\t\t\tmain[disposeKey]?.(disposeMessage)\n\t\t\tclearFailure()\n\t\t\tclearTimeout(resetTimer)\n\t\t\thistory.forEach((entry) => clearTimeout(entry.timer))\n\t\t\thistory.clear()\n\t\t\tfallback = () => Promise.reject(reason)\n\t\t\tstate = \"open\"\n\t\t\tcontroller.abort(reason)\n\t\t},\n\t\tgetLatestError: () => failureCause,\n\t\tgetState: () => state,\n\t})\n}\n"],"names":[],"mappings":"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"}
|
package/dist/index.d.cts
CHANGED
|
@@ -4,6 +4,7 @@ type AnyFn = (...args: any[]) => any;
|
|
|
4
4
|
*/
|
|
5
5
|
declare const delayMs: (ms: number) => Promise<void>;
|
|
6
6
|
|
|
7
|
+
declare const disposeKey: unique symbol;
|
|
7
8
|
type CircuitState = "closed" | "halfOpen" | "open";
|
|
8
9
|
interface CircuitBreakerOptions<Fallback extends AnyFn = AnyFn> {
|
|
9
10
|
/**
|
|
@@ -46,6 +47,11 @@ interface CircuitBreakerOptions<Fallback extends AnyFn = AnyFn> {
|
|
|
46
47
|
* Provide a function to be called when the circuit breaker is closed.
|
|
47
48
|
*/
|
|
48
49
|
onClose?: () => void;
|
|
50
|
+
/**
|
|
51
|
+
* Provide a function to be called when the circuit breaker transitions to
|
|
52
|
+
* half-open state.
|
|
53
|
+
*/
|
|
54
|
+
onHalfOpen?: () => void;
|
|
49
55
|
/**
|
|
50
56
|
* Provide a function to be called when the circuit breaker is opened. It
|
|
51
57
|
* receives the error as its only argument.
|
|
@@ -58,34 +64,6 @@ interface CircuitBreakerOptions<Fallback extends AnyFn = AnyFn> {
|
|
|
58
64
|
* @default 30_000 // 30 seconds
|
|
59
65
|
*/
|
|
60
66
|
resetAfter?: number;
|
|
61
|
-
/**
|
|
62
|
-
* Provide a function which returns a promise that resolves when the next
|
|
63
|
-
* retry attempt should be made. Use this to implement custom retry logic,
|
|
64
|
-
* such as exponential backoff.
|
|
65
|
-
*
|
|
66
|
-
* Note that `attempt` always starts at 2, since first attempts are always
|
|
67
|
-
* made as soon as they come in.
|
|
68
|
-
*
|
|
69
|
-
* @default none // Retry errors immediately
|
|
70
|
-
* @example
|
|
71
|
-
* ```ts
|
|
72
|
-
* // Constant delay of 1 second for all retries
|
|
73
|
-
* const breaker = createCircuitBreaker(main, {
|
|
74
|
-
* retryDelay: () => delayMs(1_000),
|
|
75
|
-
* })
|
|
76
|
-
*
|
|
77
|
-
* // Double the previous delay each time, up to 30 seconds
|
|
78
|
-
* const breaker = createCircuitBreaker(main, {
|
|
79
|
-
* retryDelay: useExponentialBackoff(30),
|
|
80
|
-
* })
|
|
81
|
-
*
|
|
82
|
-
* // Use Fibonacci sequence for delay, up to 90 seconds
|
|
83
|
-
* const breaker = createCircuitBreaker(main, {
|
|
84
|
-
* retryDelay: useFibonacciBackoff(90),
|
|
85
|
-
* })
|
|
86
|
-
* ```
|
|
87
|
-
*/
|
|
88
|
-
retryDelay?: (attempt: number, signal: AbortSignal) => Promise<void>;
|
|
89
67
|
}
|
|
90
68
|
interface CircuitBreakerProtectedFn<Ret = unknown, Args extends unknown[] = never[]> {
|
|
91
69
|
(...args: Args): Promise<Ret>;
|
|
@@ -101,7 +79,10 @@ interface CircuitBreakerProtectedFn<Ret = unknown, Args extends unknown[] = neve
|
|
|
101
79
|
/** Get the current state of the circuit breaker */
|
|
102
80
|
getState(): CircuitState;
|
|
103
81
|
}
|
|
104
|
-
|
|
82
|
+
interface MainFn<Ret = unknown, Args extends unknown[] = never[]> {
|
|
83
|
+
(...args: Args): Promise<Ret>;
|
|
84
|
+
[disposeKey]?: (disposeMessage?: string) => void;
|
|
85
|
+
}
|
|
105
86
|
|
|
106
87
|
/**
|
|
107
88
|
* Returns a function which implements exponential backoff.
|
|
@@ -119,6 +100,49 @@ declare function useExponentialBackoff(maxSeconds: number): (attempt: number) =>
|
|
|
119
100
|
* that resolves after the calculated delay.
|
|
120
101
|
*/
|
|
121
102
|
declare function useFibonacciBackoff(maxSeconds: number): (attempt: number) => Promise<void>;
|
|
103
|
+
interface RetryOptions {
|
|
104
|
+
/**
|
|
105
|
+
* Whether an error should be treated as non-retryable. When this returns
|
|
106
|
+
* true, the error will be thrown immediately without retrying.
|
|
107
|
+
*
|
|
108
|
+
* @default () => false // All errors are retried
|
|
109
|
+
*/
|
|
110
|
+
shouldRetry?: (error: unknown, attempt: number) => boolean;
|
|
111
|
+
/**
|
|
112
|
+
* Maximum number of retries
|
|
113
|
+
*
|
|
114
|
+
* @default 3
|
|
115
|
+
*/
|
|
116
|
+
maxAttempts?: number;
|
|
117
|
+
/**
|
|
118
|
+
* Function that returns a promise resolving when the next retry should occur.
|
|
119
|
+
* Receives the attempt number (starting at 2) and an abort signal.
|
|
120
|
+
*
|
|
121
|
+
* @default () => Promise.resolve() // Immediate retry
|
|
122
|
+
*/
|
|
123
|
+
retryDelay?: (attempt: number, signal: AbortSignal) => Promise<void>;
|
|
124
|
+
}
|
|
125
|
+
/**
|
|
126
|
+
* Wrap a function with retry logic. Errors will be retried according to the
|
|
127
|
+
* provided options.
|
|
128
|
+
*
|
|
129
|
+
* @example
|
|
130
|
+
* ```ts
|
|
131
|
+
* // Compose with circuit breaker. Retry up to 3 times with no delay
|
|
132
|
+
* const protectedA = createCircuitBreaker(
|
|
133
|
+
* withRetry(unreliableApiCall, { maxAttempts: 3 })
|
|
134
|
+
* )
|
|
135
|
+
*
|
|
136
|
+
* // Retry up to 5 times with exponential backoff
|
|
137
|
+
* const protectedB = createCircuitBreaker(
|
|
138
|
+
* withRetry(unreliableApiCall, {
|
|
139
|
+
* maxAttempts: 5,
|
|
140
|
+
* retryDelay: useExponentialBackoff(30),
|
|
141
|
+
* })
|
|
142
|
+
* )
|
|
143
|
+
* ```
|
|
144
|
+
*/
|
|
145
|
+
declare function withRetry<Ret, Args extends unknown[]>(main: MainFn<Ret, Args>, options?: RetryOptions): MainFn<Ret, Args>;
|
|
122
146
|
/**
|
|
123
147
|
* Wrap a function with a timeout. If execution of `main` exceeds `timeoutMs`,
|
|
124
148
|
* then the call is rejected with `new Error(timeoutMessage)`.
|
|
@@ -127,4 +151,5 @@ declare function withTimeout<Ret, Args extends unknown[]>(main: MainFn<Ret, Args
|
|
|
127
151
|
|
|
128
152
|
declare function createCircuitBreaker<Ret, Args extends unknown[], Fallback extends AnyFn = MainFn<Ret, Args>>(main: MainFn<Ret, Args>, options?: CircuitBreakerOptions<Fallback>): CircuitBreakerProtectedFn<Ret, Args>;
|
|
129
153
|
|
|
130
|
-
export { createCircuitBreaker, delayMs, useExponentialBackoff, useFibonacciBackoff, withTimeout };
|
|
154
|
+
export { createCircuitBreaker, delayMs, useExponentialBackoff, useFibonacciBackoff, withRetry, withTimeout };
|
|
155
|
+
export type { RetryOptions };
|
package/dist/index.d.mts
CHANGED
|
@@ -4,6 +4,7 @@ type AnyFn = (...args: any[]) => any;
|
|
|
4
4
|
*/
|
|
5
5
|
declare const delayMs: (ms: number) => Promise<void>;
|
|
6
6
|
|
|
7
|
+
declare const disposeKey: unique symbol;
|
|
7
8
|
type CircuitState = "closed" | "halfOpen" | "open";
|
|
8
9
|
interface CircuitBreakerOptions<Fallback extends AnyFn = AnyFn> {
|
|
9
10
|
/**
|
|
@@ -46,6 +47,11 @@ interface CircuitBreakerOptions<Fallback extends AnyFn = AnyFn> {
|
|
|
46
47
|
* Provide a function to be called when the circuit breaker is closed.
|
|
47
48
|
*/
|
|
48
49
|
onClose?: () => void;
|
|
50
|
+
/**
|
|
51
|
+
* Provide a function to be called when the circuit breaker transitions to
|
|
52
|
+
* half-open state.
|
|
53
|
+
*/
|
|
54
|
+
onHalfOpen?: () => void;
|
|
49
55
|
/**
|
|
50
56
|
* Provide a function to be called when the circuit breaker is opened. It
|
|
51
57
|
* receives the error as its only argument.
|
|
@@ -58,34 +64,6 @@ interface CircuitBreakerOptions<Fallback extends AnyFn = AnyFn> {
|
|
|
58
64
|
* @default 30_000 // 30 seconds
|
|
59
65
|
*/
|
|
60
66
|
resetAfter?: number;
|
|
61
|
-
/**
|
|
62
|
-
* Provide a function which returns a promise that resolves when the next
|
|
63
|
-
* retry attempt should be made. Use this to implement custom retry logic,
|
|
64
|
-
* such as exponential backoff.
|
|
65
|
-
*
|
|
66
|
-
* Note that `attempt` always starts at 2, since first attempts are always
|
|
67
|
-
* made as soon as they come in.
|
|
68
|
-
*
|
|
69
|
-
* @default none // Retry errors immediately
|
|
70
|
-
* @example
|
|
71
|
-
* ```ts
|
|
72
|
-
* // Constant delay of 1 second for all retries
|
|
73
|
-
* const breaker = createCircuitBreaker(main, {
|
|
74
|
-
* retryDelay: () => delayMs(1_000),
|
|
75
|
-
* })
|
|
76
|
-
*
|
|
77
|
-
* // Double the previous delay each time, up to 30 seconds
|
|
78
|
-
* const breaker = createCircuitBreaker(main, {
|
|
79
|
-
* retryDelay: useExponentialBackoff(30),
|
|
80
|
-
* })
|
|
81
|
-
*
|
|
82
|
-
* // Use Fibonacci sequence for delay, up to 90 seconds
|
|
83
|
-
* const breaker = createCircuitBreaker(main, {
|
|
84
|
-
* retryDelay: useFibonacciBackoff(90),
|
|
85
|
-
* })
|
|
86
|
-
* ```
|
|
87
|
-
*/
|
|
88
|
-
retryDelay?: (attempt: number, signal: AbortSignal) => Promise<void>;
|
|
89
67
|
}
|
|
90
68
|
interface CircuitBreakerProtectedFn<Ret = unknown, Args extends unknown[] = never[]> {
|
|
91
69
|
(...args: Args): Promise<Ret>;
|
|
@@ -101,7 +79,10 @@ interface CircuitBreakerProtectedFn<Ret = unknown, Args extends unknown[] = neve
|
|
|
101
79
|
/** Get the current state of the circuit breaker */
|
|
102
80
|
getState(): CircuitState;
|
|
103
81
|
}
|
|
104
|
-
|
|
82
|
+
interface MainFn<Ret = unknown, Args extends unknown[] = never[]> {
|
|
83
|
+
(...args: Args): Promise<Ret>;
|
|
84
|
+
[disposeKey]?: (disposeMessage?: string) => void;
|
|
85
|
+
}
|
|
105
86
|
|
|
106
87
|
/**
|
|
107
88
|
* Returns a function which implements exponential backoff.
|
|
@@ -119,6 +100,49 @@ declare function useExponentialBackoff(maxSeconds: number): (attempt: number) =>
|
|
|
119
100
|
* that resolves after the calculated delay.
|
|
120
101
|
*/
|
|
121
102
|
declare function useFibonacciBackoff(maxSeconds: number): (attempt: number) => Promise<void>;
|
|
103
|
+
interface RetryOptions {
|
|
104
|
+
/**
|
|
105
|
+
* Whether an error should be treated as non-retryable. When this returns
|
|
106
|
+
* true, the error will be thrown immediately without retrying.
|
|
107
|
+
*
|
|
108
|
+
* @default () => false // All errors are retried
|
|
109
|
+
*/
|
|
110
|
+
shouldRetry?: (error: unknown, attempt: number) => boolean;
|
|
111
|
+
/**
|
|
112
|
+
* Maximum number of retries
|
|
113
|
+
*
|
|
114
|
+
* @default 3
|
|
115
|
+
*/
|
|
116
|
+
maxAttempts?: number;
|
|
117
|
+
/**
|
|
118
|
+
* Function that returns a promise resolving when the next retry should occur.
|
|
119
|
+
* Receives the attempt number (starting at 2) and an abort signal.
|
|
120
|
+
*
|
|
121
|
+
* @default () => Promise.resolve() // Immediate retry
|
|
122
|
+
*/
|
|
123
|
+
retryDelay?: (attempt: number, signal: AbortSignal) => Promise<void>;
|
|
124
|
+
}
|
|
125
|
+
/**
|
|
126
|
+
* Wrap a function with retry logic. Errors will be retried according to the
|
|
127
|
+
* provided options.
|
|
128
|
+
*
|
|
129
|
+
* @example
|
|
130
|
+
* ```ts
|
|
131
|
+
* // Compose with circuit breaker. Retry up to 3 times with no delay
|
|
132
|
+
* const protectedA = createCircuitBreaker(
|
|
133
|
+
* withRetry(unreliableApiCall, { maxAttempts: 3 })
|
|
134
|
+
* )
|
|
135
|
+
*
|
|
136
|
+
* // Retry up to 5 times with exponential backoff
|
|
137
|
+
* const protectedB = createCircuitBreaker(
|
|
138
|
+
* withRetry(unreliableApiCall, {
|
|
139
|
+
* maxAttempts: 5,
|
|
140
|
+
* retryDelay: useExponentialBackoff(30),
|
|
141
|
+
* })
|
|
142
|
+
* )
|
|
143
|
+
* ```
|
|
144
|
+
*/
|
|
145
|
+
declare function withRetry<Ret, Args extends unknown[]>(main: MainFn<Ret, Args>, options?: RetryOptions): MainFn<Ret, Args>;
|
|
122
146
|
/**
|
|
123
147
|
* Wrap a function with a timeout. If execution of `main` exceeds `timeoutMs`,
|
|
124
148
|
* then the call is rejected with `new Error(timeoutMessage)`.
|
|
@@ -127,4 +151,5 @@ declare function withTimeout<Ret, Args extends unknown[]>(main: MainFn<Ret, Args
|
|
|
127
151
|
|
|
128
152
|
declare function createCircuitBreaker<Ret, Args extends unknown[], Fallback extends AnyFn = MainFn<Ret, Args>>(main: MainFn<Ret, Args>, options?: CircuitBreakerOptions<Fallback>): CircuitBreakerProtectedFn<Ret, Args>;
|
|
129
153
|
|
|
130
|
-
export { createCircuitBreaker, delayMs, useExponentialBackoff, useFibonacciBackoff, withTimeout };
|
|
154
|
+
export { createCircuitBreaker, delayMs, useExponentialBackoff, useFibonacciBackoff, withRetry, withTimeout };
|
|
155
|
+
export type { RetryOptions };
|
package/dist/index.mjs
CHANGED
|
@@ -1,6 +1,23 @@
|
|
|
1
1
|
function assert(value, message) {
|
|
2
2
|
if (!value) throw new TypeError(message);
|
|
3
3
|
}
|
|
4
|
+
const assertNever = (val, msg = "Unexpected value") => {
|
|
5
|
+
throw new TypeError(`${msg}: ${val}`);
|
|
6
|
+
};
|
|
7
|
+
const delayMs = (ms) => new Promise((next) => setTimeout(next, ms));
|
|
8
|
+
const rejectOnAbort = (signal, pending) => {
|
|
9
|
+
let teardown;
|
|
10
|
+
return Promise.race([
|
|
11
|
+
Promise.resolve(pending).finally(() => {
|
|
12
|
+
signal.removeEventListener("abort", teardown);
|
|
13
|
+
}),
|
|
14
|
+
new Promise((_, reject) => {
|
|
15
|
+
teardown = () => reject(signal.reason);
|
|
16
|
+
signal.addEventListener("abort", teardown, { once: true });
|
|
17
|
+
})
|
|
18
|
+
]);
|
|
19
|
+
};
|
|
20
|
+
|
|
4
21
|
function parseOptions(options) {
|
|
5
22
|
const {
|
|
6
23
|
errorIsFailure = () => false,
|
|
@@ -8,9 +25,9 @@ function parseOptions(options) {
|
|
|
8
25
|
errorWindow = 1e4,
|
|
9
26
|
minimumCandidates = 6,
|
|
10
27
|
onClose,
|
|
28
|
+
onHalfOpen,
|
|
11
29
|
onOpen,
|
|
12
|
-
resetAfter = 3e4
|
|
13
|
-
retryDelay = () => void 0
|
|
30
|
+
resetAfter = 3e4
|
|
14
31
|
} = options;
|
|
15
32
|
assert(
|
|
16
33
|
typeof errorIsFailure === "function",
|
|
@@ -18,37 +35,35 @@ function parseOptions(options) {
|
|
|
18
35
|
);
|
|
19
36
|
assert(
|
|
20
37
|
errorThreshold >= 0 && errorThreshold <= 1,
|
|
21
|
-
`"errorThreshold" must be
|
|
38
|
+
`"errorThreshold" must be between 0 and 1 (received ${errorThreshold})`
|
|
22
39
|
);
|
|
23
40
|
assert(
|
|
24
|
-
errorWindow
|
|
25
|
-
`"errorWindow" must be milliseconds
|
|
41
|
+
errorWindow >= 1e3,
|
|
42
|
+
`"errorWindow" must be milliseconds of at least 1 second (received ${errorWindow})`
|
|
26
43
|
);
|
|
27
44
|
assert(
|
|
28
|
-
minimumCandidates
|
|
29
|
-
`"minimumCandidates" must be
|
|
45
|
+
minimumCandidates >= 1,
|
|
46
|
+
`"minimumCandidates" must be greater than 0 (received ${minimumCandidates})`
|
|
30
47
|
);
|
|
31
|
-
if (onClose)
|
|
32
|
-
assert(
|
|
33
|
-
typeof onClose === "function",
|
|
34
|
-
`"onClose" must be a function (received ${typeof onClose})`
|
|
35
|
-
);
|
|
36
|
-
if (onOpen)
|
|
37
|
-
assert(
|
|
38
|
-
typeof onOpen === "function",
|
|
39
|
-
`"onOpen" must be a function (received ${typeof onOpen})`
|
|
40
|
-
);
|
|
41
48
|
assert(
|
|
42
|
-
|
|
43
|
-
`"
|
|
49
|
+
!onClose || typeof onClose === "function",
|
|
50
|
+
`"onClose" must be a function (received ${typeof onClose})`
|
|
44
51
|
);
|
|
45
52
|
assert(
|
|
46
|
-
|
|
47
|
-
`"
|
|
53
|
+
!onHalfOpen || typeof onHalfOpen === "function",
|
|
54
|
+
`"onHalfOpen" must be a function (received ${typeof onHalfOpen})`
|
|
55
|
+
);
|
|
56
|
+
assert(
|
|
57
|
+
!onOpen || typeof onOpen === "function",
|
|
58
|
+
`"onOpen" must be a function (received ${typeof onOpen})`
|
|
59
|
+
);
|
|
60
|
+
assert(
|
|
61
|
+
resetAfter >= 1e3,
|
|
62
|
+
`"resetAfter" must be milliseconds of at least 1 second (received ${resetAfter})`
|
|
48
63
|
);
|
|
49
64
|
assert(
|
|
50
|
-
|
|
51
|
-
`"
|
|
65
|
+
resetAfter >= errorWindow,
|
|
66
|
+
`"resetAfter" must be greater than or equal to "errorWindow" (received ${resetAfter}, expected >= ${errorWindow})`
|
|
52
67
|
);
|
|
53
68
|
return {
|
|
54
69
|
errorIsFailure,
|
|
@@ -56,28 +71,13 @@ function parseOptions(options) {
|
|
|
56
71
|
errorWindow,
|
|
57
72
|
minimumCandidates,
|
|
58
73
|
onClose,
|
|
74
|
+
onHalfOpen,
|
|
59
75
|
onOpen,
|
|
60
|
-
resetAfter
|
|
61
|
-
retryDelay
|
|
76
|
+
resetAfter
|
|
62
77
|
};
|
|
63
78
|
}
|
|
64
79
|
|
|
65
|
-
const
|
|
66
|
-
throw new TypeError(`${msg}: ${val}`);
|
|
67
|
-
};
|
|
68
|
-
const delayMs = (ms) => new Promise((next) => setTimeout(next, ms));
|
|
69
|
-
const rejectOnAbort = (signal, pending) => {
|
|
70
|
-
let teardown;
|
|
71
|
-
return Promise.race([
|
|
72
|
-
Promise.resolve(pending).finally(
|
|
73
|
-
() => signal.removeEventListener("abort", teardown)
|
|
74
|
-
),
|
|
75
|
-
new Promise((_, reject) => {
|
|
76
|
-
teardown = () => reject(signal.reason);
|
|
77
|
-
signal.addEventListener("abort", teardown);
|
|
78
|
-
})
|
|
79
|
-
]);
|
|
80
|
-
};
|
|
80
|
+
const disposeKey = Symbol("disposeKey");
|
|
81
81
|
|
|
82
82
|
function useExponentialBackoff(maxSeconds) {
|
|
83
83
|
return function exponentialBackoff(attempt) {
|
|
@@ -94,17 +94,70 @@ function useFibonacciBackoff(maxSeconds) {
|
|
|
94
94
|
return delayMs(delay * 1e3);
|
|
95
95
|
};
|
|
96
96
|
}
|
|
97
|
+
function withRetry(main, options = {}) {
|
|
98
|
+
const {
|
|
99
|
+
shouldRetry = () => true,
|
|
100
|
+
maxAttempts = 3,
|
|
101
|
+
retryDelay = () => Promise.resolve()
|
|
102
|
+
} = options;
|
|
103
|
+
assert(maxAttempts >= 1, "maxAttempts must be a number greater than 0");
|
|
104
|
+
const controller = new AbortController();
|
|
105
|
+
const { signal } = controller;
|
|
106
|
+
async function execute(args, attempt = 1) {
|
|
107
|
+
try {
|
|
108
|
+
return await main(...args);
|
|
109
|
+
} catch (cause) {
|
|
110
|
+
if (!shouldRetry(cause, attempt)) throw cause;
|
|
111
|
+
if (attempt >= maxAttempts)
|
|
112
|
+
throw new Error(`ERR_CIRCUIT_BREAKER_MAX_ATTEMPTS (${maxAttempts})`, {
|
|
113
|
+
cause
|
|
114
|
+
});
|
|
115
|
+
await rejectOnAbort(signal, retryDelay(attempt + 1, signal));
|
|
116
|
+
return execute(args, attempt + 1);
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
return Object.assign(
|
|
120
|
+
function withRetryFunction(...args) {
|
|
121
|
+
return execute(args);
|
|
122
|
+
},
|
|
123
|
+
{
|
|
124
|
+
[disposeKey]: (disposeMessage = "ERR_CIRCUIT_BREAKER_DISPOSED") => {
|
|
125
|
+
const reason = new ReferenceError(disposeMessage);
|
|
126
|
+
main[disposeKey]?.(disposeMessage);
|
|
127
|
+
controller.abort(reason);
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
);
|
|
131
|
+
}
|
|
97
132
|
function withTimeout(main, timeoutMs, timeoutMessage = "ERR_CIRCUIT_BREAKER_TIMEOUT") {
|
|
98
133
|
const error = new Error(timeoutMessage);
|
|
99
|
-
|
|
134
|
+
const controller = new AbortController();
|
|
135
|
+
const { signal } = controller;
|
|
136
|
+
function withTimeoutFunction(...args) {
|
|
137
|
+
let teardown;
|
|
100
138
|
let timer;
|
|
101
139
|
return Promise.race([
|
|
102
|
-
main(...args).finally(() =>
|
|
140
|
+
main(...args).finally(() => {
|
|
141
|
+
clearTimeout(timer);
|
|
142
|
+
signal.removeEventListener("abort", teardown);
|
|
143
|
+
}),
|
|
103
144
|
new Promise((_, reject) => {
|
|
145
|
+
teardown = () => {
|
|
146
|
+
clearTimeout(timer);
|
|
147
|
+
reject(signal.reason);
|
|
148
|
+
};
|
|
104
149
|
timer = setTimeout(reject, timeoutMs, error);
|
|
150
|
+
signal.addEventListener("abort", teardown, { once: true });
|
|
105
151
|
})
|
|
106
152
|
]);
|
|
107
|
-
}
|
|
153
|
+
}
|
|
154
|
+
return Object.assign(withTimeoutFunction, {
|
|
155
|
+
[disposeKey]: (disposeMessage = "ERR_CIRCUIT_BREAKER_DISPOSED") => {
|
|
156
|
+
const reason = new ReferenceError(disposeMessage);
|
|
157
|
+
main[disposeKey]?.(disposeMessage);
|
|
158
|
+
controller.abort(reason);
|
|
159
|
+
}
|
|
160
|
+
});
|
|
108
161
|
}
|
|
109
162
|
|
|
110
163
|
function createCircuitBreaker(main, options = {}) {
|
|
@@ -114,9 +167,9 @@ function createCircuitBreaker(main, options = {}) {
|
|
|
114
167
|
errorWindow,
|
|
115
168
|
minimumCandidates,
|
|
116
169
|
onClose,
|
|
170
|
+
onHalfOpen,
|
|
117
171
|
onOpen,
|
|
118
|
-
resetAfter
|
|
119
|
-
retryDelay
|
|
172
|
+
resetAfter
|
|
120
173
|
} = parseOptions(options);
|
|
121
174
|
const controller = new AbortController();
|
|
122
175
|
const history = /* @__PURE__ */ new Map();
|
|
@@ -149,7 +202,10 @@ function createCircuitBreaker(main, options = {}) {
|
|
|
149
202
|
failureCause = cause;
|
|
150
203
|
state = "open";
|
|
151
204
|
clearTimeout(resetTimer);
|
|
152
|
-
resetTimer = setTimeout(() =>
|
|
205
|
+
resetTimer = setTimeout(() => {
|
|
206
|
+
state = "halfOpen";
|
|
207
|
+
onHalfOpen?.();
|
|
208
|
+
}, resetAfter);
|
|
153
209
|
onOpen?.(cause);
|
|
154
210
|
}
|
|
155
211
|
function createHistoryItem(pending) {
|
|
@@ -159,7 +215,7 @@ function createCircuitBreaker(main, options = {}) {
|
|
|
159
215
|
history.delete(pending);
|
|
160
216
|
signal.removeEventListener("abort", teardown);
|
|
161
217
|
};
|
|
162
|
-
signal.addEventListener("abort", teardown);
|
|
218
|
+
signal.addEventListener("abort", teardown, { once: true });
|
|
163
219
|
const settle = (value) => {
|
|
164
220
|
if (signal.aborted) return;
|
|
165
221
|
entry.status = value;
|
|
@@ -168,7 +224,7 @@ function createCircuitBreaker(main, options = {}) {
|
|
|
168
224
|
history.set(pending, entry);
|
|
169
225
|
return { pending, settle, teardown };
|
|
170
226
|
}
|
|
171
|
-
function execute(
|
|
227
|
+
function execute(args) {
|
|
172
228
|
if (state === "closed") {
|
|
173
229
|
const { pending, settle, teardown } = createHistoryItem(main(...args));
|
|
174
230
|
return pending.then(
|
|
@@ -176,16 +232,14 @@ function createCircuitBreaker(main, options = {}) {
|
|
|
176
232
|
settle("resolved");
|
|
177
233
|
return result;
|
|
178
234
|
},
|
|
179
|
-
|
|
235
|
+
(cause) => {
|
|
180
236
|
if (signal.aborted || errorIsFailure(cause)) {
|
|
181
237
|
teardown();
|
|
182
238
|
throw cause;
|
|
183
239
|
}
|
|
184
240
|
settle("rejected");
|
|
185
241
|
if (failureRate() > errorThreshold) openCircuit(cause);
|
|
186
|
-
|
|
187
|
-
await rejectOnAbort(signal, retryDelay(next, signal));
|
|
188
|
-
return execute(next, args);
|
|
242
|
+
return fallback(...args);
|
|
189
243
|
}
|
|
190
244
|
);
|
|
191
245
|
} else if (state === "open" || halfOpenPending) {
|
|
@@ -197,20 +251,19 @@ function createCircuitBreaker(main, options = {}) {
|
|
|
197
251
|
closeCircuit();
|
|
198
252
|
return result;
|
|
199
253
|
},
|
|
200
|
-
|
|
254
|
+
(cause) => {
|
|
201
255
|
if (signal.aborted || errorIsFailure(cause)) throw cause;
|
|
202
256
|
openCircuit(cause);
|
|
203
|
-
|
|
204
|
-
await rejectOnAbort(signal, retryDelay(next, signal));
|
|
205
|
-
return execute(next, args);
|
|
257
|
+
return fallback(...args);
|
|
206
258
|
}
|
|
207
259
|
);
|
|
208
260
|
}
|
|
209
261
|
return assertNever(state);
|
|
210
262
|
}
|
|
211
|
-
return Object.assign((...args) => execute(
|
|
263
|
+
return Object.assign((...args) => execute(args), {
|
|
212
264
|
dispose: (disposeMessage = "ERR_CIRCUIT_BREAKER_DISPOSED") => {
|
|
213
265
|
const reason = new ReferenceError(disposeMessage);
|
|
266
|
+
main[disposeKey]?.(disposeMessage);
|
|
214
267
|
clearFailure();
|
|
215
268
|
clearTimeout(resetTimer);
|
|
216
269
|
history.forEach((entry) => clearTimeout(entry.timer));
|
|
@@ -224,5 +277,5 @@ function createCircuitBreaker(main, options = {}) {
|
|
|
224
277
|
});
|
|
225
278
|
}
|
|
226
279
|
|
|
227
|
-
export { createCircuitBreaker, delayMs, useExponentialBackoff, useFibonacciBackoff, withTimeout };
|
|
280
|
+
export { createCircuitBreaker, delayMs, useExponentialBackoff, useFibonacciBackoff, withRetry, withTimeout };
|
|
228
281
|
//# sourceMappingURL=index.mjs.map
|
package/dist/index.mjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.mjs","sources":["../lib/options.ts","../lib/util.ts","../lib/helpers.ts","../lib/index.ts"],"sourcesContent":["import type { CircuitBreakerOptions } from \"./types.js\"\nimport type { AnyFn } from \"./util.js\"\n\nfunction assert(value: unknown, message?: string): asserts value {\n\tif (!value) throw new TypeError(message)\n}\n\nexport function parseOptions<Fallback extends AnyFn>(\n\toptions: CircuitBreakerOptions<Fallback>,\n) {\n\tconst {\n\t\terrorIsFailure = () => false,\n\t\terrorThreshold = 0,\n\t\terrorWindow = 10_000,\n\t\tminimumCandidates = 6,\n\t\tonClose,\n\t\tonOpen,\n\t\tresetAfter = 30_000,\n\t\tretryDelay = () => undefined,\n\t} = options\n\n\t// errorIsFailure\n\tassert(\n\t\ttypeof errorIsFailure === \"function\",\n\t\t`\"errorIsFailure\" must be a function (received ${typeof errorIsFailure})`,\n\t)\n\n\t// errorThreshold\n\tassert(\n\t\terrorThreshold >= 0 && errorThreshold <= 1,\n\t\t`\"errorThreshold\" must be a number between 0 and 1 (received ${errorThreshold})`,\n\t)\n\n\t// errorWindow\n\tassert(\n\t\terrorWindow > 0,\n\t\t`\"errorWindow\" must be milliseconds greater than 0 (received ${errorWindow})`,\n\t)\n\n\t// minimumCandidates\n\tassert(\n\t\tminimumCandidates > 1,\n\t\t`\"minimumCandidates\" must be a number greater than 1 (received ${minimumCandidates})`,\n\t)\n\n\t// (optional) onClose\n\tif (onClose)\n\t\tassert(\n\t\t\ttypeof onClose === \"function\",\n\t\t\t`\"onClose\" must be a function (received ${typeof onClose})`,\n\t\t)\n\n\t// (optional) onOpen\n\tif (onOpen)\n\t\tassert(\n\t\t\ttypeof onOpen === \"function\",\n\t\t\t`\"onOpen\" must be a function (received ${typeof onOpen})`,\n\t\t)\n\n\t// resetAfter\n\tassert(\n\t\tresetAfter > 0,\n\t\t`\"resetAfter\" must be milliseconds greater than 0 (received ${resetAfter})`,\n\t)\n\tassert(\n\t\tresetAfter >= errorWindow,\n\t\t`\"resetAfter\" must be milliseconds greater than or equal to \"errorWindow\" (received ${resetAfter})`,\n\t)\n\n\t// retryDelay\n\tassert(\n\t\ttypeof retryDelay === \"function\",\n\t\t`\"retryDelay\" must be a function (received ${typeof retryDelay})`,\n\t)\n\n\treturn {\n\t\terrorIsFailure,\n\t\terrorThreshold,\n\t\terrorWindow,\n\t\tminimumCandidates,\n\t\tonClose,\n\t\tonOpen,\n\t\tresetAfter,\n\t\tretryDelay,\n\t}\n}\n","export type AnyFn = (...args: any[]) => any\n\n/**\n * `[TypeScript]` For exhaustive checks in switch statements or if/else. Add\n * this check to `default` case or final `else` to ensure all possible values\n * have been handled. If a new value is added to the type, TypeScript will\n * throw an error and the editor will underline the `value`.\n */\n/* v8 ignore next 3 */\nexport const assertNever = (val: never, msg = \"Unexpected value\") => {\n\tthrow new TypeError(`${msg}: ${val}`)\n}\n\n/**\n * Returns a promise that resolves after the specified number of milliseconds.\n */\nexport const delayMs = (ms: number): Promise<void> =>\n\tnew Promise((next) => setTimeout(next, ms))\n\n/**\n * Rejects the given promise when the abort signal is triggered.\n */\nexport const rejectOnAbort = <T extends Promise<any> | undefined>(\n\tsignal: AbortSignal,\n\tpending: T,\n): Promise<Awaited<T>> => {\n\tlet teardown: () => void\n\treturn Promise.race([\n\t\tPromise.resolve(pending).finally(() =>\n\t\t\tsignal.removeEventListener(\"abort\", teardown),\n\t\t),\n\t\tnew Promise<never>((_, reject) => {\n\t\t\tteardown = () => reject(signal.reason)\n\t\t\tsignal.addEventListener(\"abort\", teardown)\n\t\t}),\n\t])\n}\n","import type { MainFn } from \"./types.js\"\nimport { delayMs } from \"./util.js\"\n\n/**\n * Returns a function which implements exponential backoff.\n *\n * @param maxSeconds - The maximum number of seconds to wait before retrying.\n * @returns A function which takes an `attempt` number and returns a promise\n * that resolves after the calculated delay.\n */\nexport function useExponentialBackoff(maxSeconds: number) {\n\treturn function exponentialBackoff(attempt: number) {\n\t\tconst num = Math.max(attempt - 2, 0)\n\t\tconst delay = Math.min(2 ** num, maxSeconds)\n\t\treturn delayMs(delay * 1_000)\n\t}\n}\n\nconst sqrt5 = /* @__PURE__ */ Math.sqrt(5)\n/**\n * @see https://en.wikipedia.org/wiki/Fibonacci_sequence#Closed-form_expression\n */\nconst binet = (n: number) =>\n\tMath.round(((1 + sqrt5) ** n - (1 - sqrt5) ** n) / (2 ** n * sqrt5))\n\n/**\n * Returns a function which implements Fibonacci backoff.\n *\n * @param maxSeconds - The maximum number of seconds to wait before retrying.\n * @returns A function which takes an `attempt` number and returns a promise\n * that resolves after the calculated delay.\n */\nexport function useFibonacciBackoff(maxSeconds: number) {\n\treturn function fibonacciBackoff(attempt: number) {\n\t\tconst delay = Math.min(binet(attempt), maxSeconds)\n\t\treturn delayMs(delay * 1_000)\n\t}\n}\n\n/**\n * Wrap a function with a timeout. If execution of `main` exceeds `timeoutMs`,\n * then the call is rejected with `new Error(timeoutMessage)`.\n */\nexport function withTimeout<Ret, Args extends unknown[]>(\n\tmain: MainFn<Ret, Args>,\n\ttimeoutMs: number,\n\ttimeoutMessage = \"ERR_CIRCUIT_BREAKER_TIMEOUT\",\n): MainFn<Ret, Args> {\n\tconst error = new Error(timeoutMessage)\n\n\treturn function withTimeoutFunction(...args) {\n\t\tlet timer: NodeJS.Timeout\n\t\treturn Promise.race([\n\t\t\tmain(...args).finally(() => clearTimeout(timer)),\n\t\t\tnew Promise<never>((_, reject) => {\n\t\t\t\ttimer = setTimeout(reject, timeoutMs, error)\n\t\t\t}),\n\t\t])\n\t}\n}\n","import { parseOptions } from \"./options.js\"\nimport type {\n\tHistoryEntry,\n\tHistoryMap,\n\tCircuitBreakerOptions,\n\tCircuitBreakerProtectedFn,\n\tCircuitState,\n\tMainFn,\n} from \"./types.js\"\nimport { assertNever, rejectOnAbort, type AnyFn } from \"./util.js\"\n\nexport * from \"./helpers.js\"\nexport { delayMs } from \"./util.js\"\n\nexport function createCircuitBreaker<\n\tRet,\n\tArgs extends unknown[],\n\tFallback extends AnyFn = MainFn<Ret, Args>,\n>(\n\tmain: MainFn<Ret, Args>,\n\toptions: CircuitBreakerOptions<Fallback> = {},\n): CircuitBreakerProtectedFn<Ret, Args> {\n\tconst {\n\t\terrorIsFailure,\n\t\terrorThreshold,\n\t\terrorWindow,\n\t\tminimumCandidates,\n\t\tonClose,\n\t\tonOpen,\n\t\tresetAfter,\n\t\tretryDelay,\n\t} = parseOptions(options)\n\tconst controller = new AbortController()\n\tconst history: HistoryMap = new Map()\n\tconst signal = controller.signal\n\tlet failureCause: unknown\n\tlet fallback = options.fallback || (() => Promise.reject(failureCause))\n\tlet halfOpenPending: Promise<unknown> | undefined\n\tlet resetTimer: NodeJS.Timeout\n\tlet state: CircuitState = \"closed\"\n\n\tfunction clearFailure() {\n\t\tfailureCause = undefined\n\t}\n\n\tfunction closeCircuit() {\n\t\tstate = \"closed\"\n\t\tclearFailure()\n\t\tclearTimeout(resetTimer)\n\t\tonClose?.()\n\t}\n\n\tfunction failureRate() {\n\t\tlet failures = 0\n\t\tlet total = 0\n\t\tfor (const { status } of history.values()) {\n\t\t\tif (status === \"rejected\") failures++\n\t\t\tif (status !== \"pending\") total++\n\t\t}\n\t\t// Don't calculate anything until we have enough data\n\t\tif (!total || total < minimumCandidates) return 0\n\t\treturn failures / total\n\t}\n\n\t/**\n\t * Break the circuit and wait for a reset\n\t */\n\tfunction openCircuit(cause: unknown) {\n\t\tfailureCause = cause\n\t\tstate = \"open\"\n\t\tclearTimeout(resetTimer)\n\t\tresetTimer = setTimeout(() => (state = \"halfOpen\"), resetAfter)\n\t\tonOpen?.(cause)\n\t}\n\n\tfunction createHistoryItem<T>(pending: Promise<T>) {\n\t\tconst entry: HistoryEntry = { status: \"pending\", timer: undefined }\n\t\tconst teardown = () => {\n\t\t\tclearTimeout(entry.timer)\n\t\t\thistory.delete(pending)\n\t\t\tsignal.removeEventListener(\"abort\", teardown)\n\t\t}\n\t\tsignal.addEventListener(\"abort\", teardown)\n\t\tconst settle = (value: \"resolved\" | \"rejected\") => {\n\t\t\tif (signal.aborted) return\n\t\t\tentry.status = value\n\t\t\t// Remove the entry from history when it falls outside of the error window\n\t\t\tentry.timer = setTimeout(teardown, errorWindow)\n\t\t}\n\t\thistory.set(pending, entry)\n\t\treturn { pending, settle, teardown }\n\t}\n\n\t/**\n\t * Wrap calls to `main` with circuit breaker logic\n\t */\n\tfunction execute(attempt: number, args: Args): Promise<Ret> {\n\t\t// Normal operation when circuit is closed. If an error occurs, keep track\n\t\t// of the failure count and open the circuit if it exceeds the threshold.\n\t\tif (state === \"closed\") {\n\t\t\tconst { pending, settle, teardown } = createHistoryItem(main(...args))\n\t\t\treturn pending.then(\n\t\t\t\t(result) => {\n\t\t\t\t\tsettle(\"resolved\")\n\t\t\t\t\treturn result\n\t\t\t\t},\n\t\t\t\tasync (cause: unknown) => {\n\t\t\t\t\t// Was the circuit disposed, or was this a non-retryable error?\n\t\t\t\t\tif (signal.aborted || errorIsFailure(cause)) {\n\t\t\t\t\t\tteardown()\n\t\t\t\t\t\tthrow cause\n\t\t\t\t\t}\n\n\t\t\t\t\t// Should this failure open the circuit?\n\t\t\t\t\tsettle(\"rejected\")\n\t\t\t\t\tif (failureRate() > errorThreshold) openCircuit(cause)\n\n\t\t\t\t\t// Retry the call after a delay.\n\t\t\t\t\tconst next = attempt + 1\n\t\t\t\t\tawait rejectOnAbort(signal, retryDelay(next, signal))\n\t\t\t\t\treturn execute(next, args)\n\t\t\t\t},\n\t\t\t)\n\t\t}\n\n\t\t// Use the fallback while the circuit is open, or if a half-open trial\n\t\t// attempt was already made.\n\t\telse if (state === \"open\" || halfOpenPending) {\n\t\t\treturn fallback(...args)\n\t\t}\n\n\t\t// If the circuit is half-open, make one attempt. If it succeeds, close\n\t\t// the circuit and resume normal operation. If it fails, re-open the\n\t\t// circuit and run the fallback instead.\n\t\telse if (state === \"halfOpen\") {\n\t\t\treturn (halfOpenPending = main(...args))\n\t\t\t\t.finally(() => (halfOpenPending = undefined))\n\t\t\t\t.then(\n\t\t\t\t\t(result) => {\n\t\t\t\t\t\tif (signal.aborted) return result // disposed\n\t\t\t\t\t\tcloseCircuit()\n\t\t\t\t\t\treturn result\n\t\t\t\t\t},\n\t\t\t\t\tasync (cause: unknown) => {\n\t\t\t\t\t\t// Was the circuit disposed, or was this a non-retryable error?\n\t\t\t\t\t\tif (signal.aborted || errorIsFailure(cause)) throw cause\n\n\t\t\t\t\t\t// Open the circuit and try again later\n\t\t\t\t\t\topenCircuit(cause)\n\n\t\t\t\t\t\t// Retry the call after a delay.\n\t\t\t\t\t\tconst next = attempt + 1\n\t\t\t\t\t\tawait rejectOnAbort(signal, retryDelay(next, signal))\n\t\t\t\t\t\treturn execute(next, args)\n\t\t\t\t\t},\n\t\t\t\t)\n\t\t\t/* v8 ignore next */\n\t\t}\n\n\t\t// exhaustive check\n\t\t/* v8 ignore next */\n\t\treturn assertNever(state)\n\t}\n\n\treturn Object.assign((...args: Args) => execute(1, args), {\n\t\tdispose: (disposeMessage = \"ERR_CIRCUIT_BREAKER_DISPOSED\") => {\n\t\t\tconst reason = new ReferenceError(disposeMessage)\n\t\t\tclearFailure()\n\t\t\tclearTimeout(resetTimer)\n\t\t\thistory.forEach((entry) => clearTimeout(entry.timer))\n\t\t\thistory.clear()\n\t\t\tfallback = () => Promise.reject(reason)\n\t\t\tstate = \"open\"\n\t\t\tcontroller.abort(reason)\n\t\t},\n\t\tgetLatestError: () => failureCause,\n\t\tgetState: () => state,\n\t})\n}\n"],"names":[],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"index.mjs","sources":["../lib/util.ts","../lib/options.ts","../lib/types.ts","../lib/helpers.ts","../lib/index.ts"],"sourcesContent":["export type AnyFn = (...args: any[]) => any\n\n/**\n * Asserts that the given value is truthy. If not, throws a `TypeError`.\n */\nexport function assert(value: unknown, message?: string): asserts value {\n\tif (!value) throw new TypeError(message)\n}\n\n/**\n * `[TypeScript]` For exhaustive checks in switch statements or if/else. Add\n * this check to `default` case or final `else` to ensure all possible values\n * have been handled. If a new value is added to the type, TypeScript will\n * throw an error and the editor will underline the `value`.\n */\n/* v8 ignore next 3 */\nexport const assertNever = (val: never, msg = \"Unexpected value\") => {\n\tthrow new TypeError(`${msg}: ${val}`)\n}\n\n/**\n * Returns a promise that resolves after the specified number of milliseconds.\n */\nexport const delayMs = (ms: number): Promise<void> =>\n\tnew Promise((next) => setTimeout(next, ms))\n\n/**\n * Rejects the given promise when the abort signal is triggered.\n */\nexport const rejectOnAbort = <T extends Promise<unknown> | undefined>(\n\tsignal: AbortSignal,\n\tpending: T,\n): Promise<Awaited<T>> => {\n\tlet teardown: () => void\n\treturn Promise.race([\n\t\tPromise.resolve(pending).finally(() => {\n\t\t\tsignal.removeEventListener(\"abort\", teardown)\n\t\t}),\n\t\tnew Promise<never>((_, reject) => {\n\t\t\tteardown = () => reject(signal.reason)\n\t\t\tsignal.addEventListener(\"abort\", teardown, { once: true })\n\t\t}),\n\t])\n}\n","import type { CircuitBreakerOptions } from \"./types.js\"\nimport { assert, type AnyFn } from \"./util.js\"\n\nexport function parseOptions<Fallback extends AnyFn>(\n\toptions: CircuitBreakerOptions<Fallback>,\n) {\n\tconst {\n\t\terrorIsFailure = () => false,\n\t\terrorThreshold = 0,\n\t\terrorWindow = 10_000,\n\t\tminimumCandidates = 6,\n\t\tonClose,\n\t\tonHalfOpen,\n\t\tonOpen,\n\t\tresetAfter = 30_000,\n\t} = options\n\n\t// errorIsFailure\n\tassert(\n\t\ttypeof errorIsFailure === \"function\",\n\t\t`\"errorIsFailure\" must be a function (received ${typeof errorIsFailure})`,\n\t)\n\n\t// errorThreshold\n\tassert(\n\t\terrorThreshold >= 0 && errorThreshold <= 1,\n\t\t`\"errorThreshold\" must be between 0 and 1 (received ${errorThreshold})`,\n\t)\n\n\t// errorWindow\n\tassert(\n\t\terrorWindow >= 1_000,\n\t\t`\"errorWindow\" must be milliseconds of at least 1 second (received ${errorWindow})`,\n\t)\n\n\t// minimumCandidates\n\tassert(\n\t\tminimumCandidates >= 1,\n\t\t`\"minimumCandidates\" must be greater than 0 (received ${minimumCandidates})`,\n\t)\n\n\t// (optional) onClose\n\tassert(\n\t\t!onClose || typeof onClose === \"function\",\n\t\t`\"onClose\" must be a function (received ${typeof onClose})`,\n\t)\n\n\t// (optional) onHalfOpen\n\tassert(\n\t\t!onHalfOpen || typeof onHalfOpen === \"function\",\n\t\t`\"onHalfOpen\" must be a function (received ${typeof onHalfOpen})`,\n\t)\n\n\t// (optional) onOpen\n\tassert(\n\t\t!onOpen || typeof onOpen === \"function\",\n\t\t`\"onOpen\" must be a function (received ${typeof onOpen})`,\n\t)\n\n\t// resetAfter\n\tassert(\n\t\tresetAfter >= 1_000,\n\t\t`\"resetAfter\" must be milliseconds of at least 1 second (received ${resetAfter})`,\n\t)\n\tassert(\n\t\tresetAfter >= errorWindow,\n\t\t`\"resetAfter\" must be greater than or equal to \"errorWindow\" (received ${resetAfter}, expected >= ${errorWindow})`,\n\t)\n\n\treturn {\n\t\terrorIsFailure,\n\t\terrorThreshold,\n\t\terrorWindow,\n\t\tminimumCandidates,\n\t\tonClose,\n\t\tonHalfOpen,\n\t\tonOpen,\n\t\tresetAfter,\n\t}\n}\n","import type { AnyFn } from \"./util\"\n\nexport const disposeKey = Symbol(\"disposeKey\")\n\nexport type CircuitState = \"closed\" | \"halfOpen\" | \"open\"\n\nexport interface CircuitBreakerOptions<Fallback extends AnyFn = AnyFn> {\n\t/**\n\t * Whether an error should be treated as non-retryable failure. When used and\n\t * when an error is considered a failure, the error will be thrown to the\n\t * caller.\n\t *\n\t * @default () => false // Errors are retryable by default\n\t */\n\terrorIsFailure?: (error: unknown) => boolean\n\n\t/**\n\t * The percentage of errors (as a number between 0 and 1) which must occur\n\t * within the error window before the circuit breaker opens.\n\t *\n\t * @default 0 // Any error opens the circuit\n\t */\n\terrorThreshold?: number\n\n\t/**\n\t * The sliding window of time in milliseconds over which errors are counted.\n\t *\n\t * @default 10_000 // 10 seconds\n\t */\n\terrorWindow?: number\n\n\t/**\n\t * If provided, then all rejected calls to `main` will be forwarded to\n\t * this function instead.\n\t *\n\t * @default undefined // No fallback, errors are propagated\n\t */\n\tfallback?: Fallback\n\n\t/**\n\t * The minimum number of calls that must be made before calculating the\n\t * error rate and determining whether the circuit breaker should open based on\n\t * the `errorThreshold`.\n\t *\n\t * @default 6\n\t */\n\tminimumCandidates?: number\n\n\t/**\n\t * Provide a function to be called when the circuit breaker is closed.\n\t */\n\tonClose?: () => void\n\n\t/**\n\t * Provide a function to be called when the circuit breaker transitions to\n\t * half-open state.\n\t */\n\tonHalfOpen?: () => void\n\n\t/**\n\t * Provide a function to be called when the circuit breaker is opened. It\n\t * receives the error as its only argument.\n\t */\n\tonOpen?: (cause: unknown) => void\n\n\t/**\n\t * The amount of time in milliseconds to wait before transitioning to a\n\t * half-open state.\n\t *\n\t * @default 30_000 // 30 seconds\n\t */\n\tresetAfter?: number\n}\n\nexport interface CircuitBreakerProtectedFn<\n\tRet = unknown,\n\tArgs extends unknown[] = never[],\n> {\n\t(...args: Args): Promise<Ret>\n\n\t/**\n\t * Free memory and stop timers. All future calls will be rejected with the\n\t * provided message.\n\t *\n\t * @default \"ERR_CIRCUIT_BREAKER_DISPOSED\"\n\t */\n\tdispose(disposeMessage?: string): void\n\n\t/** Get the last error which triggered the circuit breaker */\n\tgetLatestError(): unknown | undefined\n\n\t/** Get the current state of the circuit breaker */\n\tgetState(): CircuitState\n}\n\nexport interface HistoryEntry {\n\ttimer: NodeJS.Timeout | undefined\n\tstatus: \"pending\" | \"resolved\" | \"rejected\"\n}\n\nexport type HistoryMap = Map<Promise<unknown>, HistoryEntry>\n\nexport interface MainFn<Ret = unknown, Args extends unknown[] = never[]> {\n\t(...args: Args): Promise<Ret>\n\n\t[disposeKey]?: (disposeMessage?: string) => void\n}\n","import { disposeKey, type MainFn } from \"./types.js\"\nimport { assert, delayMs, rejectOnAbort } from \"./util.js\"\n\n/**\n * Returns a function which implements exponential backoff.\n *\n * @param maxSeconds - The maximum number of seconds to wait before retrying.\n * @returns A function which takes an `attempt` number and returns a promise\n * that resolves after the calculated delay.\n */\nexport function useExponentialBackoff(maxSeconds: number) {\n\treturn function exponentialBackoff(attempt: number) {\n\t\tconst num = Math.max(attempt - 2, 0)\n\t\tconst delay = Math.min(2 ** num, maxSeconds)\n\t\treturn delayMs(delay * 1_000)\n\t}\n}\n\nconst sqrt5 = /* @__PURE__ */ Math.sqrt(5)\n/**\n * @see https://en.wikipedia.org/wiki/Fibonacci_sequence#Closed-form_expression\n */\nconst binet = (n: number) =>\n\tMath.round(((1 + sqrt5) ** n - (1 - sqrt5) ** n) / (2 ** n * sqrt5))\n\n/**\n * Returns a function which implements Fibonacci backoff.\n *\n * @param maxSeconds - The maximum number of seconds to wait before retrying.\n * @returns A function which takes an `attempt` number and returns a promise\n * that resolves after the calculated delay.\n */\nexport function useFibonacciBackoff(maxSeconds: number) {\n\treturn function fibonacciBackoff(attempt: number) {\n\t\tconst delay = Math.min(binet(attempt), maxSeconds)\n\t\treturn delayMs(delay * 1_000)\n\t}\n}\n\nexport interface RetryOptions {\n\t/**\n\t * Whether an error should be treated as non-retryable. When this returns\n\t * true, the error will be thrown immediately without retrying.\n\t *\n\t * @default () => false // All errors are retried\n\t */\n\tshouldRetry?: (error: unknown, attempt: number) => boolean\n\n\t/**\n\t * Maximum number of retries\n\t *\n\t * @default 3\n\t */\n\tmaxAttempts?: number\n\n\t/**\n\t * Function that returns a promise resolving when the next retry should occur.\n\t * Receives the attempt number (starting at 2) and an abort signal.\n\t *\n\t * @default () => Promise.resolve() // Immediate retry\n\t */\n\tretryDelay?: (attempt: number, signal: AbortSignal) => Promise<void>\n}\n\n/**\n * Wrap a function with retry logic. Errors will be retried according to the\n * provided options.\n *\n * @example\n * ```ts\n * // Compose with circuit breaker. Retry up to 3 times with no delay\n * const protectedA = createCircuitBreaker(\n * withRetry(unreliableApiCall, { maxAttempts: 3 })\n * )\n *\n * // Retry up to 5 times with exponential backoff\n * const protectedB = createCircuitBreaker(\n * withRetry(unreliableApiCall, {\n * maxAttempts: 5,\n * retryDelay: useExponentialBackoff(30),\n * })\n * )\n * ```\n */\nexport function withRetry<Ret, Args extends unknown[]>(\n\tmain: MainFn<Ret, Args>,\n\toptions: RetryOptions = {},\n): MainFn<Ret, Args> {\n\tconst {\n\t\tshouldRetry = () => true,\n\t\tmaxAttempts = 3,\n\t\tretryDelay = () => Promise.resolve(),\n\t} = options\n\n\tassert(maxAttempts >= 1, \"maxAttempts must be a number greater than 0\")\n\n\tconst controller = new AbortController()\n\tconst { signal } = controller\n\n\tasync function execute(args: Args, attempt = 1): Promise<Ret> {\n\t\ttry {\n\t\t\treturn await main(...args)\n\t\t} catch (cause) {\n\t\t\t// Check if we should retry this error\n\t\t\tif (!shouldRetry(cause, attempt)) throw cause\n\n\t\t\t// Check if we've exhausted attempts\n\t\t\tif (attempt >= maxAttempts)\n\t\t\t\tthrow new Error(`ERR_CIRCUIT_BREAKER_MAX_ATTEMPTS (${maxAttempts})`, {\n\t\t\t\t\tcause,\n\t\t\t\t})\n\n\t\t\t// Wait before retrying\n\t\t\tawait rejectOnAbort(signal, retryDelay(attempt + 1, signal))\n\n\t\t\t// Retry\n\t\t\treturn execute(args, attempt + 1)\n\t\t}\n\t}\n\n\treturn Object.assign(\n\t\tfunction withRetryFunction(...args: Args) {\n\t\t\treturn execute(args)\n\t\t},\n\t\t{\n\t\t\t[disposeKey]: (disposeMessage = \"ERR_CIRCUIT_BREAKER_DISPOSED\") => {\n\t\t\t\tconst reason = new ReferenceError(disposeMessage)\n\t\t\t\tmain[disposeKey]?.(disposeMessage)\n\t\t\t\tcontroller.abort(reason)\n\t\t\t},\n\t\t},\n\t)\n}\n\n/**\n * Wrap a function with a timeout. If execution of `main` exceeds `timeoutMs`,\n * then the call is rejected with `new Error(timeoutMessage)`.\n */\nexport function withTimeout<Ret, Args extends unknown[]>(\n\tmain: MainFn<Ret, Args>,\n\ttimeoutMs: number,\n\ttimeoutMessage = \"ERR_CIRCUIT_BREAKER_TIMEOUT\",\n): MainFn<Ret, Args> {\n\tconst error = new Error(timeoutMessage)\n\tconst controller = new AbortController()\n\tconst { signal } = controller\n\n\tfunction withTimeoutFunction(...args: Args) {\n\t\tlet teardown: () => void\n\t\tlet timer: NodeJS.Timeout\n\t\treturn Promise.race([\n\t\t\tmain(...args).finally(() => {\n\t\t\t\tclearTimeout(timer)\n\t\t\t\tsignal.removeEventListener(\"abort\", teardown)\n\t\t\t}),\n\t\t\tnew Promise<never>((_, reject) => {\n\t\t\t\tteardown = () => {\n\t\t\t\t\tclearTimeout(timer)\n\t\t\t\t\treject(signal.reason)\n\t\t\t\t}\n\t\t\t\ttimer = setTimeout(reject, timeoutMs, error)\n\t\t\t\tsignal.addEventListener(\"abort\", teardown, { once: true })\n\t\t\t}),\n\t\t])\n\t}\n\n\treturn Object.assign(withTimeoutFunction, {\n\t\t[disposeKey]: (disposeMessage = \"ERR_CIRCUIT_BREAKER_DISPOSED\") => {\n\t\t\tconst reason = new ReferenceError(disposeMessage)\n\t\t\tmain[disposeKey]?.(disposeMessage)\n\t\t\tcontroller.abort(reason)\n\t\t},\n\t})\n}\n","import { parseOptions } from \"./options.js\"\nimport {\n\tdisposeKey,\n\ttype CircuitBreakerOptions,\n\ttype CircuitBreakerProtectedFn,\n\ttype CircuitState,\n\ttype HistoryEntry,\n\ttype HistoryMap,\n\ttype MainFn,\n} from \"./types.js\"\nimport { assertNever, type AnyFn } from \"./util.js\"\n\nexport * from \"./helpers.js\"\nexport { delayMs } from \"./util.js\"\n\nexport function createCircuitBreaker<\n\tRet,\n\tArgs extends unknown[],\n\tFallback extends AnyFn = MainFn<Ret, Args>,\n>(\n\tmain: MainFn<Ret, Args>,\n\toptions: CircuitBreakerOptions<Fallback> = {},\n): CircuitBreakerProtectedFn<Ret, Args> {\n\tconst {\n\t\terrorIsFailure,\n\t\terrorThreshold,\n\t\terrorWindow,\n\t\tminimumCandidates,\n\t\tonClose,\n\t\tonHalfOpen,\n\t\tonOpen,\n\t\tresetAfter,\n\t} = parseOptions(options)\n\tconst controller = new AbortController()\n\tconst history: HistoryMap = new Map()\n\tconst signal = controller.signal\n\tlet failureCause: unknown\n\tlet fallback = options.fallback || (() => Promise.reject(failureCause))\n\tlet halfOpenPending: Promise<unknown> | undefined\n\tlet resetTimer: NodeJS.Timeout\n\tlet state: CircuitState = \"closed\"\n\n\tfunction clearFailure() {\n\t\tfailureCause = undefined\n\t}\n\n\tfunction closeCircuit() {\n\t\tstate = \"closed\"\n\t\tclearFailure()\n\t\tclearTimeout(resetTimer)\n\t\tonClose?.()\n\t}\n\n\tfunction failureRate() {\n\t\tlet failures = 0\n\t\tlet total = 0\n\t\tfor (const { status } of history.values()) {\n\t\t\tif (status === \"rejected\") failures++\n\t\t\tif (status !== \"pending\") total++\n\t\t}\n\t\t// Don't calculate anything until we have enough data\n\t\tif (!total || total < minimumCandidates) return 0\n\t\treturn failures / total\n\t}\n\n\t/**\n\t * Break the circuit and wait for a reset\n\t */\n\tfunction openCircuit(cause: unknown) {\n\t\tfailureCause = cause\n\t\tstate = \"open\"\n\t\tclearTimeout(resetTimer)\n\t\tresetTimer = setTimeout(() => {\n\t\t\tstate = \"halfOpen\"\n\t\t\tonHalfOpen?.()\n\t\t}, resetAfter)\n\t\tonOpen?.(cause)\n\t}\n\n\tfunction createHistoryItem<T>(pending: Promise<T>) {\n\t\tconst entry: HistoryEntry = { status: \"pending\", timer: undefined }\n\t\tconst teardown = () => {\n\t\t\tclearTimeout(entry.timer)\n\t\t\thistory.delete(pending)\n\t\t\tsignal.removeEventListener(\"abort\", teardown)\n\t\t}\n\t\tsignal.addEventListener(\"abort\", teardown, { once: true })\n\t\tconst settle = (value: \"resolved\" | \"rejected\") => {\n\t\t\tif (signal.aborted) return\n\t\t\tentry.status = value\n\t\t\t// Remove the entry from history when it falls outside of the error window\n\t\t\tentry.timer = setTimeout(teardown, errorWindow)\n\t\t}\n\t\thistory.set(pending, entry)\n\t\treturn { pending, settle, teardown }\n\t}\n\n\t/**\n\t * Wrap calls to `main` with circuit breaker logic\n\t */\n\tfunction execute(args: Args): Promise<Ret> {\n\t\t// Normal operation when circuit is closed. If an error occurs, keep track\n\t\t// of the failure count and open the circuit if it exceeds the threshold.\n\t\tif (state === \"closed\") {\n\t\t\tconst { pending, settle, teardown } = createHistoryItem(main(...args))\n\t\t\treturn pending.then(\n\t\t\t\t(result) => {\n\t\t\t\t\tsettle(\"resolved\")\n\t\t\t\t\treturn result\n\t\t\t\t},\n\t\t\t\t(cause: unknown) => {\n\t\t\t\t\t// Was the circuit disposed, or is this error considered a failure?\n\t\t\t\t\tif (signal.aborted || errorIsFailure(cause)) {\n\t\t\t\t\t\tteardown()\n\t\t\t\t\t\tthrow cause\n\t\t\t\t\t}\n\n\t\t\t\t\t// Should this error open the circuit?\n\t\t\t\t\tsettle(\"rejected\")\n\t\t\t\t\tif (failureRate() > errorThreshold) openCircuit(cause)\n\n\t\t\t\t\treturn fallback(...args)\n\t\t\t\t},\n\t\t\t)\n\t\t}\n\n\t\t// Use the fallback while the circuit is open, or if a half-open trial\n\t\t// attempt was already made.\n\t\telse if (state === \"open\" || halfOpenPending) {\n\t\t\treturn fallback(...args)\n\t\t}\n\n\t\t// If the circuit is half-open, make one attempt. If it succeeds, close\n\t\t// the circuit and resume normal operation. If it fails, re-open the\n\t\t// circuit and run the fallback instead.\n\t\telse if (state === \"halfOpen\") {\n\t\t\treturn (halfOpenPending = main(...args))\n\t\t\t\t.finally(() => (halfOpenPending = undefined))\n\t\t\t\t.then(\n\t\t\t\t\t(result) => {\n\t\t\t\t\t\tif (signal.aborted) return result // disposed\n\t\t\t\t\t\tcloseCircuit()\n\t\t\t\t\t\treturn result\n\t\t\t\t\t},\n\t\t\t\t\t(cause: unknown) => {\n\t\t\t\t\t\t// Was the circuit disposed, or was this a non-retryable error?\n\t\t\t\t\t\tif (signal.aborted || errorIsFailure(cause)) throw cause\n\n\t\t\t\t\t\t// Open the circuit and use fallback\n\t\t\t\t\t\topenCircuit(cause)\n\t\t\t\t\t\treturn fallback(...args)\n\t\t\t\t\t},\n\t\t\t\t)\n\t\t\t/* v8 ignore next */\n\t\t}\n\n\t\t// exhaustive check\n\t\t/* v8 ignore next */\n\t\treturn assertNever(state)\n\t}\n\n\treturn Object.assign((...args: Args) => execute(args), {\n\t\tdispose: (disposeMessage = \"ERR_CIRCUIT_BREAKER_DISPOSED\") => {\n\t\t\tconst reason = new ReferenceError(disposeMessage)\n\t\t\tmain[disposeKey]?.(disposeMessage)\n\t\t\tclearFailure()\n\t\t\tclearTimeout(resetTimer)\n\t\t\thistory.forEach((entry) => clearTimeout(entry.timer))\n\t\t\thistory.clear()\n\t\t\tfallback = () => Promise.reject(reason)\n\t\t\tstate = \"open\"\n\t\t\tcontroller.abort(reason)\n\t\t},\n\t\tgetLatestError: () => failureCause,\n\t\tgetState: () => state,\n\t})\n}\n"],"names":[],"mappings":"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"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "breaker-box",
|
|
3
|
-
"version": "
|
|
3
|
+
"version": "5.0.0",
|
|
4
4
|
"description": "A zero-dependency circuit breaker implementation for Node.js",
|
|
5
5
|
"repository": {
|
|
6
6
|
"type": "git",
|
|
@@ -26,26 +26,29 @@
|
|
|
26
26
|
"scripts": {
|
|
27
27
|
"build": "pkgroll --clean-dist --src lib --target=node18 --sourcemap --env.NODE_ENV=production",
|
|
28
28
|
"dev": "vitest",
|
|
29
|
-
"format": "
|
|
30
|
-
"prepublishOnly": "npm run test && npm run build",
|
|
29
|
+
"format": "prettier --cache --write .",
|
|
30
|
+
"prepublishOnly": "npm run test:ts && npm run test && npm run build",
|
|
31
31
|
"reinstall": "rm -rf node_modules package-lock.json && npm install",
|
|
32
|
-
"test": "vitest --run"
|
|
32
|
+
"test": "vitest --run",
|
|
33
|
+
"test:coverage": "vitest --run --coverage",
|
|
34
|
+
"test:ts": "tsc --noEmit"
|
|
33
35
|
},
|
|
34
36
|
"keywords": [
|
|
35
|
-
"
|
|
37
|
+
"async",
|
|
36
38
|
"fallback",
|
|
39
|
+
"fault-tolerance",
|
|
37
40
|
"reliability",
|
|
38
41
|
"try-again"
|
|
39
42
|
],
|
|
40
43
|
"author": "Matthew Pietz <sirlancelot@gmail.com>",
|
|
41
44
|
"license": "ISC",
|
|
42
45
|
"devDependencies": {
|
|
43
|
-
"@types/node": "24.
|
|
44
|
-
"@vitest/coverage-v8": "
|
|
45
|
-
"pkgroll": "2.
|
|
46
|
-
"prettier": "3.
|
|
47
|
-
"typescript": "5.9.
|
|
48
|
-
"vitest": "
|
|
49
|
-
"vitest-when": "0.
|
|
46
|
+
"@types/node": "24.10.2",
|
|
47
|
+
"@vitest/coverage-v8": "4.0.15",
|
|
48
|
+
"pkgroll": "2.21.4",
|
|
49
|
+
"prettier": "3.7.4",
|
|
50
|
+
"typescript": "5.9.3",
|
|
51
|
+
"vitest": "4.0.15",
|
|
52
|
+
"vitest-when": "0.10.0"
|
|
50
53
|
}
|
|
51
54
|
}
|