breaker-box 4.2.0 → 6.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/dist/index.mjs CHANGED
@@ -1,34 +1,47 @@
1
1
  function assert(value, message) {
2
2
  if (!value) throw new TypeError(message);
3
3
  }
4
+ /* v8 ignore next -- @preserve */
4
5
  const assertNever = (val, msg = "Unexpected value") => {
5
6
  throw new TypeError(`${msg}: ${val}`);
6
7
  };
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);
17
- })
18
- ]);
8
+ const delayMs = (ms, signal) => {
9
+ if (!Number.isFinite(ms) || ms < 0) {
10
+ throw new RangeError(
11
+ `"ms" must be a finite, non-negative number (received ${ms})`
12
+ );
13
+ }
14
+ return signal ? new Promise((resolve, reject) => {
15
+ signal.throwIfAborted();
16
+ const timer = setTimeout(() => {
17
+ signal.removeEventListener("abort", onAbort);
18
+ resolve();
19
+ }, ms);
20
+ const onAbort = () => {
21
+ clearTimeout(timer);
22
+ reject(signal.reason);
23
+ };
24
+ signal.addEventListener("abort", onAbort, { once: true });
25
+ }) : new Promise((next) => setTimeout(next, ms));
19
26
  };
27
+ const abortable = (signal, pending) => new Promise((resolve, reject) => {
28
+ signal.throwIfAborted();
29
+ const onAbort = () => reject(signal.reason);
30
+ signal.addEventListener("abort", onAbort, { once: true });
31
+ Promise.resolve(pending).then(resolve, reject).finally(() => signal.removeEventListener("abort", onAbort));
32
+ });
20
33
 
21
34
  function parseOptions(options) {
22
35
  const {
23
36
  errorIsFailure = () => false,
24
37
  errorThreshold = 0,
25
38
  errorWindow = 1e4,
26
- minimumCandidates = 6,
39
+ fallback,
40
+ minimumCandidates = 1,
27
41
  onClose,
28
42
  onHalfOpen,
29
43
  onOpen,
30
- resetAfter = 3e4,
31
- retryDelay = () => void 0
44
+ resetAfter = 3e4
32
45
  } = options;
33
46
  assert(
34
47
  typeof errorIsFailure === "function",
@@ -42,6 +55,10 @@ function parseOptions(options) {
42
55
  errorWindow >= 1e3,
43
56
  `"errorWindow" must be milliseconds of at least 1 second (received ${errorWindow})`
44
57
  );
58
+ assert(
59
+ !fallback || typeof fallback === "function",
60
+ `"fallback" must be a function (received ${typeof fallback})`
61
+ );
45
62
  assert(
46
63
  minimumCandidates >= 1,
47
64
  `"minimumCandidates" must be greater than 0 (received ${minimumCandidates})`
@@ -66,10 +83,6 @@ function parseOptions(options) {
66
83
  resetAfter >= errorWindow,
67
84
  `"resetAfter" must be greater than or equal to "errorWindow" (received ${resetAfter}, expected >= ${errorWindow})`
68
85
  );
69
- assert(
70
- typeof retryDelay === "function",
71
- `"retryDelay" must be a function (received ${typeof retryDelay})`
72
- );
73
86
  return {
74
87
  errorIsFailure,
75
88
  errorThreshold,
@@ -78,37 +91,78 @@ function parseOptions(options) {
78
91
  onClose,
79
92
  onHalfOpen,
80
93
  onOpen,
81
- resetAfter,
82
- retryDelay
94
+ resetAfter
83
95
  };
84
96
  }
85
97
 
98
+ const disposeKey = Symbol("disposeKey");
99
+
86
100
  function useExponentialBackoff(maxSeconds) {
87
- return function exponentialBackoff(attempt) {
101
+ return function exponentialBackoff(attempt, signal) {
88
102
  const num = Math.max(attempt - 2, 0);
89
103
  const delay = Math.min(2 ** num, maxSeconds);
90
- return delayMs(delay * 1e3);
104
+ return delayMs(delay * 1e3, signal);
91
105
  };
92
106
  }
93
107
  const sqrt5 = /* @__PURE__ */ Math.sqrt(5);
94
108
  const binet = (n) => Math.round(((1 + sqrt5) ** n - (1 - sqrt5) ** n) / (2 ** n * sqrt5));
95
109
  function useFibonacciBackoff(maxSeconds) {
96
- return function fibonacciBackoff(attempt) {
110
+ return function fibonacciBackoff(attempt, signal) {
97
111
  const delay = Math.min(binet(attempt), maxSeconds);
98
- return delayMs(delay * 1e3);
112
+ return delayMs(delay * 1e3, signal);
99
113
  };
100
114
  }
115
+ function withRetry(main, options = {}) {
116
+ const {
117
+ shouldRetry = () => true,
118
+ maxAttempts = 3,
119
+ retryDelay = () => Promise.resolve()
120
+ } = options;
121
+ assert(maxAttempts >= 1, "maxAttempts must be a number greater than 0");
122
+ const controller = new AbortController();
123
+ const { signal } = controller;
124
+ async function withRetryFunction(...args) {
125
+ let attempt = 1;
126
+ while (true) {
127
+ try {
128
+ return await main(...args);
129
+ } catch (cause) {
130
+ if (attempt >= maxAttempts) {
131
+ throw new Error(`ERR_CIRCUIT_BREAKER_MAX_ATTEMPTS (${maxAttempts})`, {
132
+ cause
133
+ });
134
+ }
135
+ if (!shouldRetry(cause, attempt)) throw cause;
136
+ }
137
+ attempt++;
138
+ await abortable(signal, retryDelay(attempt, signal));
139
+ }
140
+ }
141
+ return Object.assign(withRetryFunction, {
142
+ [disposeKey]: (disposeMessage = "ERR_CIRCUIT_BREAKER_DISPOSED") => {
143
+ const reason = new ReferenceError(disposeMessage);
144
+ main[disposeKey]?.(disposeMessage);
145
+ controller.abort(reason);
146
+ }
147
+ });
148
+ }
101
149
  function withTimeout(main, timeoutMs, timeoutMessage = "ERR_CIRCUIT_BREAKER_TIMEOUT") {
102
150
  const error = new Error(timeoutMessage);
103
- return function withTimeoutFunction(...args) {
104
- let timer;
105
- return Promise.race([
106
- main(...args).finally(() => clearTimeout(timer)),
107
- new Promise((_, reject) => {
108
- timer = setTimeout(reject, timeoutMs, error);
109
- })
110
- ]);
111
- };
151
+ const controller = new AbortController();
152
+ const { signal } = controller;
153
+ function withTimeoutFunction(...args) {
154
+ return new Promise((resolve, reject) => {
155
+ const timer = setTimeout(reject, timeoutMs, error);
156
+ abortable(signal, main(...args)).then(resolve, reject).finally(() => clearTimeout(timer));
157
+ });
158
+ }
159
+ return Object.assign(withTimeoutFunction, {
160
+ [disposeKey]: (disposeMessage = "ERR_CIRCUIT_BREAKER_DISPOSED") => {
161
+ const reason = new ReferenceError(disposeMessage);
162
+ main[disposeKey]?.(disposeMessage);
163
+ controller.abort(reason);
164
+ }
165
+ });
112
166
  }
113
167
 
114
168
  function createCircuitBreaker(main, options = {}) {
@@ -120,27 +174,28 @@ function createCircuitBreaker(main, options = {}) {
120
174
  onClose,
121
175
  onHalfOpen,
122
176
  onOpen,
123
- resetAfter,
124
- retryDelay
177
+ resetAfter
125
178
  } = parseOptions(options);
126
179
  const controller = new AbortController();
127
180
  const history = /* @__PURE__ */ new Map();
128
181
  const signal = controller.signal;
129
182
  let failureCause;
183
+ let failureRate = 0;
130
184
  let fallback = options.fallback || (() => Promise.reject(failureCause));
131
185
  let halfOpenPending;
132
186
  let resetTimer;
133
187
  let state = "closed";
134
188
  function clearFailure() {
135
189
  failureCause = void 0;
190
+ clearTimeout(resetTimer);
191
+ resetTimer = void 0;
136
192
  }
137
193
  function closeCircuit() {
138
194
  state = "closed";
139
195
  clearFailure();
140
- clearTimeout(resetTimer);
141
- onClose?.();
196
+ if (onClose) setImmediate(onClose);
142
197
  }
143
- function failureRate() {
198
+ function calculateFailureRate() {
144
199
  let failures = 0;
145
200
  let total = 0;
146
201
  for (const { status } of history.values()) {
@@ -151,14 +206,16 @@ function createCircuitBreaker(main, options = {}) {
151
206
  return failures / total;
152
207
  }
153
208
  function openCircuit(cause) {
154
- failureCause = cause;
155
209
  state = "open";
210
+ failureCause = cause;
156
211
  clearTimeout(resetTimer);
157
- resetTimer = setTimeout(() => {
158
- state = "halfOpen";
159
- onHalfOpen?.();
160
- }, resetAfter);
161
- onOpen?.(cause);
212
+ resetTimer = setTimeout(() => halfOpenCircuit(), resetAfter);
213
+ if (onOpen) setImmediate(onOpen, cause);
214
+ }
215
+ function halfOpenCircuit() {
216
+ state = "halfOpen";
217
+ halfOpenPending = void 0;
218
+ if (onHalfOpen) setImmediate(onHalfOpen);
162
219
  }
163
220
  function createHistoryItem(pending) {
164
221
  const entry = { status: "pending", timer: void 0 };
@@ -167,16 +224,17 @@ function createCircuitBreaker(main, options = {}) {
167
224
  history.delete(pending);
168
225
  signal.removeEventListener("abort", teardown);
169
226
  };
170
- signal.addEventListener("abort", teardown);
227
+ signal.addEventListener("abort", teardown, { once: true });
171
228
  const settle = (value) => {
172
- if (signal.aborted) return;
229
+ if (signal.aborted) return 0;
173
230
  entry.status = value;
174
231
  entry.timer = setTimeout(teardown, errorWindow);
232
+ return failureRate = calculateFailureRate();
175
233
  };
176
234
  history.set(pending, entry);
177
235
  return { pending, settle, teardown };
178
236
  }
179
- function execute(attempt, args) {
237
+ function execute(...args) {
180
238
  if (state === "closed") {
181
239
  const { pending, settle, teardown } = createHistoryItem(main(...args));
182
240
  return pending.then(
@@ -184,16 +242,14 @@ function createCircuitBreaker(main, options = {}) {
184
242
  settle("resolved");
185
243
  return result;
186
244
  },
187
- async (cause) => {
245
+ (cause) => {
188
246
  if (signal.aborted || errorIsFailure(cause)) {
189
247
  teardown();
190
248
  throw cause;
191
249
  }
192
250
  settle("rejected");
193
- if (failureRate() > errorThreshold) openCircuit(cause);
194
- const next = attempt + 1;
195
- await rejectOnAbort(signal, retryDelay(next, signal));
196
- return execute(next, args);
251
+ if (failureRate > errorThreshold) openCircuit(cause);
252
+ return fallback(...args);
197
253
  }
198
254
  );
199
255
  } else if (state === "open" || halfOpenPending) {
@@ -205,32 +261,36 @@ function createCircuitBreaker(main, options = {}) {
205
261
  closeCircuit();
206
262
  return result;
207
263
  },
208
- async (cause) => {
264
+ (cause) => {
209
265
  if (signal.aborted || errorIsFailure(cause)) throw cause;
210
266
  openCircuit(cause);
211
- const next = attempt + 1;
212
- await rejectOnAbort(signal, retryDelay(next, signal));
213
- return execute(next, args);
267
+ return fallback(...args);
214
268
  }
215
269
  );
216
270
  }
271
+ /* v8 ignore next -- @preserve */
217
272
  return assertNever(state);
218
273
  }
219
- return Object.assign((...args) => execute(1, args), {
274
+ return Object.assign(execute, {
220
275
  dispose: (disposeMessage = "ERR_CIRCUIT_BREAKER_DISPOSED") => {
276
+ /* v8 ignore if -- @preserve */
277
+ if (signal.aborted) return;
221
278
  const reason = new ReferenceError(disposeMessage);
279
+ main[disposeKey]?.(disposeMessage);
222
280
  clearFailure();
223
- clearTimeout(resetTimer);
281
+ halfOpenPending = void 0;
224
282
  history.forEach((entry) => clearTimeout(entry.timer));
225
283
  history.clear();
284
+ failureRate = 0;
226
285
  fallback = () => Promise.reject(reason);
227
286
  state = "open";
228
287
  controller.abort(reason);
229
288
  },
289
+ getFailureRate: () => failureRate,
230
290
  getLatestError: () => failureCause,
231
291
  getState: () => state
232
292
  });
233
293
  }
234
294
 
235
- export { createCircuitBreaker, delayMs, useExponentialBackoff, useFibonacciBackoff, withTimeout };
295
+ export { createCircuitBreaker, delayMs, useExponentialBackoff, useFibonacciBackoff, withRetry, withTimeout };
236
296
  //# sourceMappingURL=index.mjs.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.mjs","sources":["../lib/util.ts","../lib/options.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)\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\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 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\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\tonHalfOpen,\n\t\tonOpen,\n\t\tresetAfter,\n\t\tretryDelay,\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\tonHalfOpen,\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(() => {\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)\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: never) => unknown\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 -- @preserve */\nexport const assertNever = (val: never, msg = \"Unexpected value\"): never => {\n\tthrow new TypeError(`${msg}: ${val as string}`)\n}\n\n/**\n * Returns a promise that resolves after the specified number of milliseconds.\n */\nexport const delayMs = (ms: number, signal?: AbortSignal): Promise<void> => {\n\tif (!Number.isFinite(ms) || ms < 0) {\n\t\tthrow new RangeError(\n\t\t\t`\"ms\" must be a finite, non-negative number (received ${ms})`,\n\t\t)\n\t}\n\n\treturn signal\n\t\t? new Promise((resolve, reject) => {\n\t\t\t\tsignal.throwIfAborted()\n\n\t\t\t\tconst timer = setTimeout(() => {\n\t\t\t\t\tsignal.removeEventListener(\"abort\", onAbort)\n\t\t\t\t\tresolve()\n\t\t\t\t}, ms)\n\n\t\t\t\tconst onAbort = () => {\n\t\t\t\t\tclearTimeout(timer)\n\t\t\t\t\treject(signal.reason)\n\t\t\t\t}\n\n\t\t\t\tsignal.addEventListener(\"abort\", onAbort, { once: true })\n\t\t\t})\n\t\t: new Promise((next) => setTimeout(next, ms))\n}\n\n/**\n * Returns a promise which rejects when the abort signal is triggered or\n * resolves when the promise is fulfilled.\n */\nexport const abortable = <T>(\n\tsignal: AbortSignal,\n\tpending: PromiseLike<T>,\n): Promise<T> =>\n\tnew Promise((resolve, reject) => {\n\t\tsignal.throwIfAborted()\n\n\t\tconst onAbort = () => reject(signal.reason)\n\t\tsignal.addEventListener(\"abort\", onAbort, { once: true })\n\n\t\tPromise.resolve(pending)\n\t\t\t.then(resolve, reject)\n\t\t\t.finally(() => signal.removeEventListener(\"abort\", onAbort))\n\t})\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\tfallback,\n\t\tminimumCandidates = 1,\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// (optional) fallback\n\tassert(\n\t\t!fallback || typeof fallback === \"function\",\n\t\t`\"fallback\" must be a function (received ${typeof fallback})`,\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\n/**\n * Symbol key for internal disposal protocol. Functions implementing this key\n * can be disposed by circuit breaker wrappers.\n */\nexport const disposeKey = Symbol(\"disposeKey\")\n\n/**\n * The three possible states of a circuit breaker.\n *\n * - `closed`: Normal operation, tracking failures\n * - `open`: Failing state, rejecting calls or using fallback\n * - `halfOpen`: Testing recovery with a single trial call\n */\nexport type CircuitState = \"closed\" | \"halfOpen\" | \"open\"\n\n/**\n * Configuration options for circuit breaker behavior.\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\n/**\n * A function wrapped with circuit breaker protection. Includes methods for\n * state inspection and resource cleanup.\n */\nexport interface CircuitBreakerProtectedFn<\n\tRet = unknown,\n\tArgs extends readonly unknown[] = readonly [],\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(this: void, disposeMessage?: string): void\n\n\t/** Get the current failure rate of the circuit breaker */\n\tgetFailureRate(this: void): number\n\n\t/** Get the last error which triggered the circuit breaker */\n\tgetLatestError(this: void): unknown\n\n\t/** Get the current state of the circuit breaker */\n\tgetState(this: void): CircuitState\n}\n\n/**\n * Tracks the status of a single call within the error window. Contains a timer\n * for automatic cleanup and the current resolution status.\n */\nexport interface HistoryEntry {\n\ttimer: NodeJS.Timeout | undefined\n\tstatus: \"pending\" | \"resolved\" | \"rejected\"\n}\n\n/**\n * Map tracking all in-flight and recent promises within the error window. Used\n * to calculate failure rates for circuit breaker decisions.\n */\nexport type HistoryMap<T = unknown> = Map<Promise<T>, HistoryEntry>\n\n/**\n * The main function signature that can be protected by a circuit breaker. May\n * optionally implement the disposal protocol via the `disposeKey` symbol.\n */\nexport interface MainFn<\n\tRet = unknown,\n\tArgs extends readonly unknown[] = never[],\n> {\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, abortable } from \"./util.js\"\n\n/**\n * Creates an exponential backoff strategy for retry delays.\n * Delay grows as 2^(attempt-2) seconds, capped at maxSeconds.\n *\n * The sequence is: 1s, 2s, 4s, 8s, 16s, 32s, etc.\n *\n * @param maxSeconds - Maximum delay in seconds before capping\n * @returns Function accepting attempt number and returning delay promise\n *\n * @example\n * ```ts\n * const backoff = useExponentialBackoff(30)\n * await backoff(2) // waits 1 second\n * await backoff(3) // waits 2 seconds\n * await backoff(10) // waits 30 seconds (capped)\n * ```\n */\nexport function useExponentialBackoff(maxSeconds: number) {\n\treturn function exponentialBackoff(attempt: number, signal?: AbortSignal) {\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, signal)\n\t}\n}\n\nconst sqrt5 = /* @__PURE__ */ Math.sqrt(5)\n/**\n * Binet's formula for calculating Fibonacci numbers in constant time.\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 * Creates a Fibonacci backoff strategy for retry delays.\n * Delay follows the Fibonacci sequence: 1s, 2s, 3s, 5s, 8s, 13s, etc.\n *\n * More gradual than exponential backoff, useful for less aggressive retry patterns.\n *\n * @param maxSeconds - Maximum delay in seconds before capping\n * @returns Function accepting attempt number and returning delay promise\n *\n * @example\n * ```ts\n * const backoff = useFibonacciBackoff(60)\n * await backoff(2) // waits 1 second\n * await backoff(5) // waits 5 seconds\n * await backoff(10) // waits 55 seconds\n * ```\n */\nexport function useFibonacciBackoff(maxSeconds: number) {\n\treturn function fibonacciBackoff(attempt: number, signal?: AbortSignal) {\n\t\tconst delay = Math.min(binet(attempt), maxSeconds)\n\t\treturn delayMs(delay * 1_000, signal)\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 readonly unknown[]>(\n\tmain: MainFn<Ret, Args>,\n\toptions: Readonly<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 withRetryFunction(...args: Args): Promise<Ret> {\n\t\tlet attempt = 1\n\t\twhile (true) {\n\t\t\ttry {\n\t\t\t\treturn await main(...args)\n\t\t\t} catch (cause) {\n\t\t\t\tif (attempt >= maxAttempts) {\n\t\t\t\t\tthrow new Error(`ERR_CIRCUIT_BREAKER_MAX_ATTEMPTS (${maxAttempts})`, {\n\t\t\t\t\t\tcause,\n\t\t\t\t\t})\n\t\t\t\t}\n\n\t\t\t\tif (!shouldRetry(cause, attempt)) throw cause\n\t\t\t}\n\n\t\t\tattempt++\n\t\t\tawait abortable(signal, retryDelay(attempt, signal))\n\t\t}\n\t}\n\n\treturn Object.assign(withRetryFunction, {\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\n/**\n * Wraps an async function with a timeout constraint. Rejects with an Error if\n * execution exceeds the specified timeout.\n *\n * @example\n * ```ts\n * const fetchWithTimeout = withTimeout(fetchData, 5000, \"Fetch timed out\")\n * try {\n * const data = await fetchWithTimeout(url)\n * } catch (error) {\n * console.error(error.message) // \"Fetch timed out\" after 5 seconds\n * }\n * ```\n */\nexport function withTimeout<Ret, Args extends readonly 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): Promise<Ret> {\n\t\treturn new Promise((resolve, reject) => {\n\t\t\tconst timer = setTimeout(reject, timeoutMs, error)\n\n\t\t\tabortable(signal, main(...args))\n\t\t\t\t.then(resolve, reject)\n\t\t\t\t.finally(() => clearTimeout(timer))\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 } from \"./util.js\"\n\nexport * from \"./helpers.js\"\nexport {\n\ttype CircuitBreakerOptions,\n\ttype CircuitBreakerProtectedFn,\n\ttype CircuitState,\n\ttype MainFn,\n} from \"./types.js\"\nexport { delayMs } from \"./util.js\"\n\n/**\n * Creates a circuit breaker that wraps an async function with failure tracking\n * and automatic fallback behavior.\n *\n * The circuit breaker operates in three states:\n *\n * - `closed`: Normal operation, tracks failures in a sliding window\n * - `open`: Failed state, fallback is used until `resetAfter` milliseconds\n * - `halfOpen`: Testing recovery, allows one trial call\n *\n * When the failure rate exceeds `errorThreshold` within the `errorWindow`, the\n * circuit opens and rejects calls (using fallback if provided) for `resetAfter`\n * milliseconds. After this period, it transitions to half-open and allows one\n * trial call. Success closes the circuit; failure reopens it.\n *\n * @example\n * ```ts\n * const protectedFn = createCircuitBreaker(unreliableApiCall, {\n * errorThreshold: 0.5,\n * errorWindow: 10_000,\n * resetAfter: 30_000,\n * fallback: () => cachedResponse,\n * })\n *\n * try {\n * const result = await protectedFn(arg1, arg2)\n * } catch (error) {\n * console.error('Circuit breaker rejected call:', error)\n * }\n *\n * console.log(protectedFn.getState()) // 'closed' | 'open' | 'halfOpen'\n * protectedFn.dispose() // Clean up timers and resources\n * ```\n */\nexport function createCircuitBreaker<Ret, Args extends unknown[]>(\n\tmain: MainFn<Ret, Args>,\n\toptions: CircuitBreakerOptions<MainFn<Ret, Args>> = {},\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 failureRate = 0\n\tlet fallback: typeof main =\n\t\toptions.fallback || (() => Promise.reject(failureCause))\n\tlet halfOpenPending: Promise<unknown> | undefined\n\tlet resetTimer: NodeJS.Timeout | undefined\n\tlet state: CircuitState = \"closed\"\n\n\tfunction clearFailure() {\n\t\tfailureCause = undefined\n\t\tclearTimeout(resetTimer)\n\t\tresetTimer = undefined\n\t}\n\n\tfunction closeCircuit() {\n\t\tstate = \"closed\"\n\t\tclearFailure()\n\t\tif (onClose) setImmediate(onClose)\n\t}\n\n\tfunction calculateFailureRate(): number {\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\tfunction openCircuit(cause: unknown) {\n\t\tstate = \"open\"\n\t\tfailureCause = cause\n\t\tclearTimeout(resetTimer)\n\t\tresetTimer = setTimeout(() => halfOpenCircuit(), resetAfter)\n\t\tif (onOpen) setImmediate(onOpen, cause)\n\t}\n\n\tfunction halfOpenCircuit() {\n\t\tstate = \"halfOpen\"\n\t\thalfOpenPending = undefined\n\t\tif (onHalfOpen) setImmediate(onHalfOpen)\n\t}\n\n\tfunction createHistoryItem<T>(pending: Promise<T>) {\n\t\tconst entry: HistoryEntry = { status: \"pending\", timer: undefined }\n\t\tconst teardown = (): void => {\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\"): number => {\n\t\t\tif (signal.aborted) return 0\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\treturn (failureRate = calculateFailureRate())\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}\n\n\t\t// exhaustive check\n\t\t/* v8 ignore next -- @preserve */\n\t\treturn assertNever(state)\n\t}\n\n\treturn Object.assign(execute, {\n\t\tdispose: (disposeMessage = \"ERR_CIRCUIT_BREAKER_DISPOSED\") => {\n\t\t\t/* v8 ignore if -- @preserve */\n\t\t\tif (signal.aborted) return\n\t\t\tconst reason = new ReferenceError(disposeMessage)\n\t\t\tmain[disposeKey]?.(disposeMessage)\n\t\t\tclearFailure()\n\t\t\thalfOpenPending = undefined\n\t\t\thistory.forEach((entry) => clearTimeout(entry.timer))\n\t\t\thistory.clear()\n\t\t\tfailureRate = 0\n\t\t\tfallback = () => Promise.reject(reason)\n\t\t\tstate = \"open\"\n\t\t\tcontroller.abort(reason)\n\t\t},\n\t\tgetFailureRate: () => failureRate,\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": "4.2.0",
3
+ "version": "6.0.0",
4
4
  "description": "A zero-dependency circuit breaker implementation for Node.js",
5
5
  "repository": {
6
6
  "type": "git",
@@ -27,27 +27,33 @@
27
27
  "build": "pkgroll --clean-dist --src lib --target=node18 --sourcemap --env.NODE_ENV=production",
28
28
  "dev": "vitest",
29
29
  "format": "prettier --cache --write .",
30
- "prepublishOnly": "npm run test:ts && npm run test && npm run build",
30
+ "lint": "eslint . --fix",
31
+ "prepublishOnly": "npm run test && npm run build",
31
32
  "reinstall": "rm -rf node_modules package-lock.json && npm install",
32
- "test": "vitest --run",
33
- "test:coverage": "vitest --run --coverage",
34
- "test:ts": "tsc --noEmit"
33
+ "test": "vitest --run --typecheck",
34
+ "test:coverage": "vitest --run --coverage"
35
35
  },
36
36
  "keywords": [
37
- "circuit-breaker",
37
+ "async",
38
38
  "fallback",
39
+ "fault-tolerance",
39
40
  "reliability",
40
41
  "try-again"
41
42
  ],
42
43
  "author": "Matthew Pietz <sirlancelot@gmail.com>",
43
44
  "license": "ISC",
44
45
  "devDependencies": {
45
- "@types/node": "24.7.0",
46
- "@vitest/coverage-v8": "3.2.4",
47
- "pkgroll": "2.18.0",
48
- "prettier": "3.6.2",
46
+ "@eslint/js": "9.39.1",
47
+ "@eslint/json": "0.14.0",
48
+ "@eslint/markdown": "7.5.1",
49
+ "@types/node": "25.0.1",
50
+ "@vitest/coverage-v8": "4.0.15",
51
+ "eslint": "9.39.1",
52
+ "pkgroll": "2.21.4",
53
+ "prettier": "3.7.4",
49
54
  "typescript": "5.9.3",
50
- "vitest": "3.2.4",
51
- "vitest-when": "0.8.1"
55
+ "typescript-eslint": "8.49.0",
56
+ "vitest": "4.0.15",
57
+ "vitest-when": "0.10.0"
52
58
  }
53
59
  }