breaker-box 7.0.0 → 8.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md 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
- ## Composition Helpers
46
+ ## Timeout & Retry
47
47
 
48
- The circuit breaker function doesn't handle timeouts, retries, or cooldowns on
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
- Wrap the `unreliableApiCall` with a timeout using `withTimeout()`. If the timer
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
- import { createCircuitBreaker, withTimeout } from "breaker-box"
64
-
65
- const protectedApiCall = createCircuitBreaker(
66
- withTimeout(unreliableApiCall, 5000),
67
- )
55
+ const protectedApiCall = createCircuitBreaker(unreliableApiCall, {
56
+ timeout: 5000,
57
+ })
68
58
  ```
69
59
 
70
60
  ### Retries
71
61
 
72
- Wrap the `unreliableApiCall` with retry logic using `withRetry()` function. If
73
- the promise gets rejected, it will be retried up to a certain number of times.
74
- Once the max number of retries is reached, then the promise will ultimately
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
- import { createCircuitBreaker, withRetry } from "breaker-box"
82
-
83
- const protectedApiCall = createCircuitBreaker(
84
- withRetry(unreliableApiCall, {
85
- maxAttempts: 3,
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, it may be necessary to introduce a cooldown period
94
- between retries to prevent overwhelming the remote system. By default,
95
- `withRetry` will retry immediately. However, you can provide a custom
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
- withRetry(unreliableApiCall, {
120
- maxAttempts: 3,
121
- retryDelay: (attempt, signal) => delayMs(1_000, signal),
122
- }),
123
- )
124
-
125
- const protectedApiCall2 = createCircuitBreaker(
126
- withRetry(unreliableApiCall, {
127
- maxAttempts: 3,
128
- retryDelay: useExponentialBackoff(60),
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
- withRetry(withTimeout(unreliableApiCall, 4_000), {
154
- maxAttempts: 3,
155
- retryDelay: useExponentialBackoff(60),
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
- // Clean up resources when shutting down
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 |