breaker-box 7.0.0 → 8.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 +66 -79
- package/dist/index.cjs +264 -208
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +127 -99
- package/dist/index.d.mts +127 -99
- package/dist/index.mjs +264 -208
- package/dist/index.mjs.map +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -43,58 +43,39 @@ try {
|
|
|
43
43
|
|
|
44
44
|
The above example creates a function named `protectedApiCall` which, when called will execute the `unreliableApiCall` function with circuit breaker protection. If the underlying function fails, then `fallback` is called instead. If **50%** of the calls fail within a **10-second sliding window**, then the circuit breaker will open and subsequent calls to `protectedApiCall` will **always** use the `fallback` for the next **30 seconds**.
|
|
45
45
|
|
|
46
|
-
##
|
|
46
|
+
## Timeout & Retry
|
|
47
47
|
|
|
48
|
-
|
|
49
|
-
its own. For these features, you need to compose with other helper functions.
|
|
50
|
-
`breaker-box` provides a set of composable functions that can wrap your API call
|
|
51
|
-
with these features.
|
|
48
|
+
`createCircuitBreaker` supports built-in timeout and retry via options:
|
|
52
49
|
|
|
53
50
|
### Timeouts
|
|
54
51
|
|
|
55
|
-
|
|
56
|
-
expires before the call completes, then the promise will reject with
|
|
57
|
-
`ERR_CIRCUIT_BREAKER_TIMEOUT`.
|
|
58
|
-
|
|
59
|
-
This wrapping may not be needed if your API call supports a timeout option
|
|
60
|
-
already (e.g. `axios` already has a `timeout` option).
|
|
52
|
+
If the call doesn't complete within `timeout` milliseconds, it rejects and counts as a failure.
|
|
61
53
|
|
|
62
54
|
```typescript
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
withTimeout(unreliableApiCall, 5000),
|
|
67
|
-
)
|
|
55
|
+
const protectedApiCall = createCircuitBreaker(unreliableApiCall, {
|
|
56
|
+
timeout: 5000,
|
|
57
|
+
})
|
|
68
58
|
```
|
|
69
59
|
|
|
70
60
|
### Retries
|
|
71
61
|
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
reject with `ERR_CIRCUIT_BREAKER_MAX_ATTEMPTS (${number})`.
|
|
76
|
-
|
|
77
|
-
This wrapping may not be needed if your API call supports a retry option already
|
|
78
|
-
(e.g. `aws-sdk` already has retry logic).
|
|
62
|
+
Failed calls can be retried automatically. Use `retryLimit` to set the maximum
|
|
63
|
+
number of attempts, `retryTest` to filter which errors are retryable, and
|
|
64
|
+
`retryDelay` to control the delay between attempts.
|
|
79
65
|
|
|
80
66
|
```typescript
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
shouldRetry: (error) => true, // Check whether error is retryable
|
|
87
|
-
}),
|
|
88
|
-
)
|
|
67
|
+
const protectedApiCall = createCircuitBreaker(unreliableApiCall, {
|
|
68
|
+
retryLimit: 3,
|
|
69
|
+
retryTest: (error) => error.statusCode !== 404,
|
|
70
|
+
retryDelay: useExponentialBackoff(60),
|
|
71
|
+
})
|
|
89
72
|
```
|
|
90
73
|
|
|
91
74
|
### Cooldowns
|
|
92
75
|
|
|
93
|
-
When using retry logic,
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
`retryDelay` function to introduce a delay before each retry attempt. The
|
|
97
|
-
function should return a promise that resolves after the desired delay.
|
|
76
|
+
When using retry logic, you can introduce a delay between retries to avoid
|
|
77
|
+
overwhelming the remote system. The `retryDelay` option accepts either a number
|
|
78
|
+
(fixed delay in milliseconds) or a function returning a promise.
|
|
98
79
|
|
|
99
80
|
`breaker-box` offers helper functions to generate retry delays:
|
|
100
81
|
|
|
@@ -102,7 +83,6 @@ function should return a promise that resolves after the desired delay.
|
|
|
102
83
|
number of milliseconds.
|
|
103
84
|
- `useExponentialBackoff(maxSeconds: number)`: Returns a function that
|
|
104
85
|
calculates the delay using exponential backoff.
|
|
105
|
-
|
|
106
86
|
- `useFibonacciBackoff(maxSeconds: number)`: Returns a function that calculates
|
|
107
87
|
the delay using Fibonacci sequence.
|
|
108
88
|
|
|
@@ -112,49 +92,39 @@ import {
|
|
|
112
92
|
delayMs,
|
|
113
93
|
useExponentialBackoff,
|
|
114
94
|
useFibonacciBackoff,
|
|
115
|
-
withRetry,
|
|
116
95
|
} from "breaker-box"
|
|
117
96
|
|
|
118
|
-
const protectedApiCall1 = createCircuitBreaker(
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
)
|
|
131
|
-
|
|
132
|
-
const protectedApiCall3 = createCircuitBreaker(
|
|
133
|
-
withRetry(unreliableApiCall, {
|
|
134
|
-
maxAttempts: 3,
|
|
135
|
-
retryDelay: useFibonacciBackoff(60),
|
|
136
|
-
}),
|
|
137
|
-
)
|
|
97
|
+
const protectedApiCall1 = createCircuitBreaker(unreliableApiCall, {
|
|
98
|
+
retryLimit: 3,
|
|
99
|
+
retryDelay: 1_000, // Fixed 1-second delay
|
|
100
|
+
})
|
|
101
|
+
|
|
102
|
+
const protectedApiCall2 = createCircuitBreaker(unreliableApiCall, {
|
|
103
|
+
retryLimit: 3,
|
|
104
|
+
retryDelay: useExponentialBackoff(60),
|
|
105
|
+
})
|
|
106
|
+
|
|
107
|
+
const protectedApiCall3 = createCircuitBreaker(unreliableApiCall, {
|
|
108
|
+
retryLimit: 3,
|
|
109
|
+
retryDelay: useFibonacciBackoff(60),
|
|
110
|
+
})
|
|
138
111
|
```
|
|
139
112
|
|
|
140
113
|
### Complete Example
|
|
141
114
|
|
|
142
|
-
The optimal composition should look like this: Circuit Breaker -> Retry -> Timeout -> Implementation. However, you may compose it in any order you prefer as long as you understand the behavior.
|
|
143
|
-
|
|
144
115
|
```typescript
|
|
145
|
-
import {
|
|
146
|
-
createCircuitBreaker,
|
|
147
|
-
useExponentialBackoff,
|
|
148
|
-
withRetry,
|
|
149
|
-
withTimeout,
|
|
150
|
-
} from "breaker-box"
|
|
116
|
+
import { createCircuitBreaker, useExponentialBackoff } from "breaker-box"
|
|
151
117
|
|
|
152
|
-
const protectedApiCall = createCircuitBreaker(
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
118
|
+
const protectedApiCall = createCircuitBreaker(unreliableApiCall, {
|
|
119
|
+
errorThreshold: 0.5,
|
|
120
|
+
errorWindow: 10_000,
|
|
121
|
+
minimumCandidates: 1,
|
|
122
|
+
resetAfter: 30_000,
|
|
123
|
+
timeout: 4_000,
|
|
124
|
+
retryLimit: 3,
|
|
125
|
+
retryDelay: useExponentialBackoff(60),
|
|
126
|
+
fallback: (data) => ({ data: "fallback data" }),
|
|
127
|
+
})
|
|
158
128
|
```
|
|
159
129
|
|
|
160
130
|
## Observability
|
|
@@ -178,7 +148,7 @@ const protectedFunction = createCircuitBreaker(unreliableApiCall, {
|
|
|
178
148
|
The following methods can retrieve information about the circuit breaker:
|
|
179
149
|
|
|
180
150
|
```typescript
|
|
181
|
-
// Check current state: "closed", "open", "halfOpen"
|
|
151
|
+
// Check current state: "closed", "open", "halfOpen", "disposed"
|
|
182
152
|
console.log("Current state:", protectedFunction.getState())
|
|
183
153
|
|
|
184
154
|
// Check failure rate: Number between 0 and 1
|
|
@@ -191,7 +161,14 @@ console.log("Last error:", protectedFunction.getLatestError())
|
|
|
191
161
|
## Cleanup
|
|
192
162
|
|
|
193
163
|
```typescript
|
|
194
|
-
//
|
|
164
|
+
// Preferred: use explicit resource management
|
|
165
|
+
{
|
|
166
|
+
using protectedFunction = createCircuitBreaker(unreliableApiCall)
|
|
167
|
+
// automatically disposed at end of block
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
// Or dispose manually (deprecated)
|
|
171
|
+
const protectedFunction = createCircuitBreaker(unreliableApiCall)
|
|
195
172
|
protectedFunction.dispose()
|
|
196
173
|
```
|
|
197
174
|
|
|
@@ -214,19 +191,26 @@ Creates a circuit breaker around the provided async function.
|
|
|
214
191
|
- `onHalfOpen`: Function called when circuit enters half-open state (default: undefined)
|
|
215
192
|
- `onOpen`: Function called when circuit opens (default: undefined)
|
|
216
193
|
- `resetAfter`: Milliseconds to wait before trying half-open (default: `30_000`)
|
|
194
|
+
- `retryDelay`: Delay between retries; a number (ms) for fixed delay or a function `(attempt, signal) => Promise<void>` (default: `0`)
|
|
195
|
+
- `retryLimit`: Maximum number of attempts per call (default: `Infinity`)
|
|
196
|
+
- `retryTest`: Function `(error) => boolean` to decide if an error is retryable (default: `() => true`)
|
|
197
|
+
- `timeout`: Per-call timeout in milliseconds; 0 disables (default: `0`)
|
|
217
198
|
|
|
218
199
|
#### Returns
|
|
219
200
|
|
|
220
201
|
A function with the same signature as `fn` and additional methods:
|
|
221
202
|
|
|
222
|
-
- `.dispose(message?)`: Clean up resources and reject future calls
|
|
203
|
+
- `.dispose(message?)`: *(Deprecated)* Clean up resources and reject future calls. Use `Symbol.dispose` / `using` keyword instead.
|
|
223
204
|
- `.getFailureRate()`: Returns the current failure rate (0-1) or 0 if fewer than `minimumCandidates` calls have been made
|
|
224
205
|
- `.getLatestError()`: Returns the error which triggered the circuit breaker
|
|
225
|
-
- `.getState()`: Returns current circuit state (`'closed'`, `'open'`, `'halfOpen'`)
|
|
206
|
+
- `.getState()`: Returns current circuit state (`'closed'`, `'open'`, `'halfOpen'`, `'disposed'`)
|
|
207
|
+
- `[Symbol.dispose]()`: Clean up resources and reject future calls. Supports `using` syntax.
|
|
226
208
|
|
|
227
209
|
### Helper Functions
|
|
228
210
|
|
|
229
|
-
#### `withRetry(fn, options?)`
|
|
211
|
+
#### `withRetry(fn, options?)` *(Deprecated)*
|
|
212
|
+
|
|
213
|
+
> **Deprecated:** Use the `retryLimit`, `retryDelay`, and `retryTest` options on `createCircuitBreaker` instead.
|
|
230
214
|
|
|
231
215
|
Wraps a function with retry logic. Failures will be retried according to the provided options.
|
|
232
216
|
|
|
@@ -248,7 +232,9 @@ const retryCall = withRetry(apiCall, {
|
|
|
248
232
|
})
|
|
249
233
|
```
|
|
250
234
|
|
|
251
|
-
#### `withTimeout(fn, timeoutMs, message?)`
|
|
235
|
+
#### `withTimeout(fn, timeoutMs, message?)` *(Deprecated)*
|
|
236
|
+
|
|
237
|
+
> **Deprecated:** Use the `timeout` option on `createCircuitBreaker` instead.
|
|
252
238
|
|
|
253
239
|
Wraps a function with a timeout. Rejects with `Error(message)` if execution exceeds `timeoutMs`.
|
|
254
240
|
|
|
@@ -296,8 +282,9 @@ Returns a promise that resolves after the specified number of milliseconds. Supp
|
|
|
296
282
|
| `npm run build` | Build with pkgroll (CJS + ESM + types) |
|
|
297
283
|
| `npm run dev` | Run tests in watch mode (vitest) |
|
|
298
284
|
| `npm run format` | Format with Prettier |
|
|
285
|
+
| `npm run lint` | Lint with ESLint (auto-fix) |
|
|
299
286
|
| `npm run test:coverage` | Run tests with coverage |
|
|
300
|
-
| `npm test` | Run tests once
|
|
287
|
+
| `npm test` | Run tests once (includes typecheck) |
|
|
301
288
|
| `npx tsc --noEmit` | Type-check without emit |
|
|
302
289
|
| `npx vitest index.test.ts` | Run single test file |
|
|
303
290
|
| `npx vitest -t "test name"` | Run specific test by name |
|