breaker-box 2.0.0 → 3.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.cjs CHANGED
@@ -1,66 +1,78 @@
1
1
  'use strict';
2
2
 
3
+ const assertNever = (val, msg = "Unexpected value") => {
4
+ throw new TypeError(`${msg}: ${val}`);
5
+ };
6
+ const resolvedPromise = Promise.resolve();
7
+ const nextTick = (fn) => resolvedPromise.then(fn);
8
+
3
9
  function createCircuitBreaker(main, options = {}) {
4
10
  const {
5
11
  errorIsFailure = () => true,
6
12
  failureThreshold = 1,
7
- fallback = () => Promise.reject(failureCause),
8
13
  onClose,
9
14
  onOpen,
10
15
  resetAfter = 3e4
11
16
  } = options;
17
+ let fallback = options.fallback || (() => Promise.reject(failureCause));
12
18
  let halfOpenPending;
13
19
  let state = "closed";
14
- let failureCause = void 0;
20
+ let failureCause;
15
21
  let failureCount = 0;
16
- let resetTimer = void 0;
22
+ let resetTimer;
23
+ function clearFailure() {
24
+ failureCause = void 0;
25
+ failureCount = 0;
26
+ }
17
27
  function openCircuit(cause) {
28
+ failureCause = cause;
18
29
  state = "open";
19
- onOpen?.(cause);
20
30
  clearTimeout(resetTimer);
21
31
  resetTimer = setTimeout(() => state = "halfOpen", resetAfter);
32
+ onOpen?.(cause);
22
33
  }
23
- function closeCircuit() {
24
- state = "closed";
25
- failureCause = void 0;
26
- failureCount = 0;
27
- clearTimeout(resetTimer);
28
- }
29
- async function protectedFunction(...args) {
34
+ function protectedFunction(...args) {
30
35
  if (state === "closed") {
31
- return main(...args).catch((cause) => {
32
- if (state === "disposed") throw cause;
33
- failureCause = cause;
34
- failureCount += errorIsFailure(cause) ? 1 : 0;
35
- if (failureCount >= failureThreshold) openCircuit(cause);
36
- return protectedFunction(...args);
37
- });
36
+ const thisFallback = fallback;
37
+ return main(...args).then(
38
+ (result) => {
39
+ if (state === "closed") clearFailure();
40
+ return result;
41
+ },
42
+ (cause) => {
43
+ if (thisFallback !== fallback) throw cause;
44
+ failureCount += errorIsFailure(cause) ? 1 : 0;
45
+ if (failureCount === failureThreshold) openCircuit(cause);
46
+ return nextTick(() => protectedFunction(...args));
47
+ }
48
+ );
38
49
  } else if (state === "open" || halfOpenPending) {
39
50
  return fallback(...args);
40
51
  } else if (state === "halfOpen") {
52
+ const thisFallback = fallback;
41
53
  return (halfOpenPending = main(...args)).finally(() => halfOpenPending = void 0).then(
42
54
  (result) => {
43
- if (state !== "disposed") {
44
- closeCircuit();
45
- onClose?.();
46
- }
55
+ if (thisFallback !== fallback) return result;
56
+ state = "closed";
57
+ clearFailure();
58
+ clearTimeout(resetTimer);
59
+ onClose?.();
47
60
  return result;
48
61
  },
49
62
  (cause) => {
50
- if (state === "disposed") throw cause;
63
+ if (thisFallback !== fallback) throw cause;
51
64
  openCircuit(cause);
52
- return fallback(...args);
65
+ return nextTick(() => protectedFunction(...args));
53
66
  }
54
67
  );
55
- } else if (state === "disposed") {
56
- throw new Error("Circuit breaker has been disposed");
57
- } else {
58
- throw void 0;
59
68
  }
69
+ return assertNever(state);
60
70
  }
61
71
  protectedFunction.dispose = () => {
62
- closeCircuit();
63
- state = "disposed";
72
+ clearFailure();
73
+ clearTimeout(resetTimer);
74
+ fallback = () => Promise.reject(new ReferenceError("ERR_CIRCUIT_BREAKER_DISPOSED"));
75
+ state = "open";
64
76
  };
65
77
  protectedFunction.getLatestError = () => failureCause;
66
78
  protectedFunction.getState = () => state;
@@ -1 +1 @@
1
- {"version":3,"file":"index.cjs","sources":["../lib/index.ts"],"sourcesContent":["import { type AnyFn, assertNever } from \"./util.js\"\n\nexport type CircuitState = \"closed\" | \"disposed\" | \"halfOpen\" | \"open\"\n\nexport interface CircuitBreakerOptions<Fallback extends AnyFn = AnyFn> {\n\t/**\n\t * Whether an error should be considered a failure that could trigger\n\t * the circuit breaker. Use this to prevent certain errors from\n\t * incrementing the failure count.\n\t *\n\t * @default () => true // Every error is considered a failure\n\t */\n\terrorIsFailure?: (error: unknown) => boolean\n\n\t/**\n\t * The number of failures before the circuit breaker opens.\n\t *\n\t * @default 1 // The first error opens the circuit\n\t */\n\tfailureThreshold?: 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/** Called when the circuit breaker is closed */\n\tonClose?: () => void\n\n\t/** Called when the circuit breaker is opened */\n\tonOpen?: (cause: unknown) => void\n\n\t/**\n\t * The amount of time to wait before allowing a 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/** Free memory and stop timers */\n\tdispose(): 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 function createCircuitBreaker<\n\tRet,\n\tArgs extends unknown[],\n\tFallback extends AnyFn = (...args: Args) => Promise<Ret>\n>(\n\tmain: (...args: Args) => Promise<Ret>,\n\toptions: CircuitBreakerOptions<Fallback> = {}\n): CircuitBreakerProtectedFn<Ret, Args> {\n\tconst {\n\t\terrorIsFailure = () => true,\n\t\tfailureThreshold = 1,\n\t\tfallback = () => Promise.reject(failureCause),\n\t\tonClose,\n\t\tonOpen,\n\t\tresetAfter = 30_000,\n\t} = options\n\tlet halfOpenPending: Promise<unknown> | undefined\n\tlet state: CircuitState = \"closed\"\n\tlet failureCause: unknown | undefined = undefined\n\tlet failureCount = 0\n\tlet resetTimer: NodeJS.Timeout | undefined = undefined\n\n\t/**\n\t * Break the circuit and wait for a reset\n\t */\n\tfunction openCircuit(cause: unknown) {\n\t\tstate = \"open\"\n\t\tonOpen?.(cause)\n\t\tclearTimeout(resetTimer)\n\t\tresetTimer = setTimeout(() => (state = \"halfOpen\"), resetAfter)\n\t}\n\n\t/**\n\t * Reset the circuit and resume normal operation\n\t */\n\tfunction closeCircuit() {\n\t\tstate = \"closed\"\n\t\tfailureCause = undefined\n\t\tfailureCount = 0\n\t\tclearTimeout(resetTimer)\n\t}\n\n\t/**\n\t * Wrap calls to `main` with circuit breaker logic\n\t */\n\tasync function protectedFunction(...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\treturn main(...args).catch((cause) => {\n\t\t\t\tif (state === \"disposed\") throw cause\n\t\t\t\tfailureCause = cause\n\t\t\t\tfailureCount += errorIsFailure(cause) ? 1 : 0\n\t\t\t\tif (failureCount >= failureThreshold) openCircuit(cause)\n\t\t\t\treturn protectedFunction(...args)\n\t\t\t})\n\t\t}\n\n\t\t// Use the fallback while the circuit is open\n\t\telse if (state === \"open\" || halfOpenPending) {\n\t\t\treturn fallback(...args)\n\t\t}\n\n\t\t// While the circuit is half-open, try the main function once. If it\n\t\t// succeeds, close the circuit and resume normal operation. If it fails,\n\t\t// re-open the 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 (state !== \"disposed\") {\n\t\t\t\t\t\t\tcloseCircuit()\n\t\t\t\t\t\t\tonClose?.()\n\t\t\t\t\t\t}\n\t\t\t\t\t\treturn result\n\t\t\t\t\t},\n\t\t\t\t\t(cause) => {\n\t\t\t\t\t\tif (state === \"disposed\") throw cause\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// Shutting down...\n\t\telse if (state === \"disposed\") {\n\t\t\tthrow new Error(\"Circuit breaker has been disposed\")\n\t\t\t/* v8 ignore next */\n\t\t}\n\n\t\t// exhaustive check\n\t\t/* v8 ignore next 5 */\n\t\telse {\n\t\t\tthrow process.env.NODE_ENV !== \"production\"\n\t\t\t\t? assertNever(state)\n\t\t\t\t: undefined\n\t\t}\n\t}\n\n\tprotectedFunction.dispose = () => {\n\t\tcloseCircuit()\n\t\tstate = \"disposed\"\n\t}\n\n\tprotectedFunction.getLatestError = () => failureCause\n\n\tprotectedFunction.getState = () => state\n\n\treturn protectedFunction\n}\n"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"index.cjs","sources":["../lib/util.ts","../lib/index.ts"],"sourcesContent":["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\nconst resolvedPromise = Promise.resolve()\n\nexport const nextTick = <T>(fn: () => T | PromiseLike<T>): Promise<T> =>\n\tresolvedPromise.then(fn)\n","import { type AnyFn, assertNever, nextTick } from \"./util.js\"\n\nexport type CircuitState = \"closed\" | \"halfOpen\" | \"open\"\n\nexport interface CircuitBreakerOptions<Fallback extends AnyFn = AnyFn> {\n\t/**\n\t * Whether an error should be considered a failure that could trigger\n\t * the circuit breaker. Use this to prevent certain errors from\n\t * incrementing the failure count.\n\t *\n\t * @default () => true // Every error is considered a failure\n\t */\n\terrorIsFailure?: (error: unknown) => boolean\n\n\t/**\n\t * The number of failures before the circuit breaker opens.\n\t *\n\t * @default 1 // The first error opens the circuit\n\t */\n\tfailureThreshold?: 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/** Called when the circuit breaker is closed */\n\tonClose?: () => void\n\n\t/** Called when the circuit breaker is opened */\n\tonOpen?: (cause: unknown) => void\n\n\t/**\n\t * The amount of time to wait before allowing a 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/** Free memory and stop timers */\n\tdispose(): 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 function createCircuitBreaker<\n\tRet,\n\tArgs extends unknown[],\n\tFallback extends AnyFn = (...args: Args) => Promise<Ret>\n>(\n\tmain: (...args: Args) => Promise<Ret>,\n\toptions: CircuitBreakerOptions<Fallback> = {}\n): CircuitBreakerProtectedFn<Ret, Args> {\n\tconst {\n\t\terrorIsFailure = () => true,\n\t\tfailureThreshold = 1,\n\t\tonClose,\n\t\tonOpen,\n\t\tresetAfter = 30_000,\n\t} = options\n\tlet fallback = options.fallback || (() => Promise.reject(failureCause))\n\tlet halfOpenPending: Promise<unknown> | undefined\n\tlet state: CircuitState = \"closed\"\n\tlet failureCause: unknown | undefined\n\tlet failureCount = 0\n\tlet resetTimer: NodeJS.Timeout | undefined\n\n\tfunction clearFailure() {\n\t\tfailureCause = undefined\n\t\tfailureCount = 0\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\t/**\n\t * Wrap calls to `main` with circuit breaker logic\n\t */\n\tfunction protectedFunction(...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 thisFallback = fallback\n\t\t\treturn main(...args).then(\n\t\t\t\t(result) => {\n\t\t\t\t\t// Reset accumulated failures if circuit is still closed\n\t\t\t\t\tif (state === \"closed\") clearFailure()\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 breaker disposed while the call was in flight?\n\t\t\t\t\tif (thisFallback !== fallback) throw cause\n\t\t\t\t\tfailureCount += errorIsFailure(cause) ? 1 : 0\n\t\t\t\t\tif (failureCount === failureThreshold) openCircuit(cause)\n\t\t\t\t\treturn nextTick(() => protectedFunction(...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\tconst thisFallback = fallback\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\t// Was the circuit breaker disposed while the call was\n\t\t\t\t\t\t// in flight?\n\t\t\t\t\t\tif (thisFallback !== fallback) return result\n\t\t\t\t\t\t// Close the circuit and resume normal operation\n\t\t\t\t\t\tstate = \"closed\"\n\t\t\t\t\t\tclearFailure()\n\t\t\t\t\t\tclearTimeout(resetTimer)\n\t\t\t\t\t\tonClose?.()\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 breaker disposed while the call was\n\t\t\t\t\t\t// in flight?\n\t\t\t\t\t\tif (thisFallback !== fallback) throw cause\n\t\t\t\t\t\topenCircuit(cause)\n\t\t\t\t\t\treturn nextTick(() => protectedFunction(...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\tprotectedFunction.dispose = () => {\n\t\tclearFailure()\n\t\tclearTimeout(resetTimer)\n\t\tfallback = () =>\n\t\t\tPromise.reject(new ReferenceError(\"ERR_CIRCUIT_BREAKER_DISPOSED\"))\n\t\tstate = \"open\"\n\t}\n\n\tprotectedFunction.getLatestError = () => failureCause\n\n\tprotectedFunction.getState = () => state\n\n\treturn protectedFunction\n}\n"],"names":[],"mappings":"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"}
package/dist/index.d.cts CHANGED
@@ -1,6 +1,6 @@
1
1
  type AnyFn = (...args: any[]) => any;
2
2
 
3
- type CircuitState = "closed" | "disposed" | "halfOpen" | "open";
3
+ type CircuitState = "closed" | "halfOpen" | "open";
4
4
  interface CircuitBreakerOptions<Fallback extends AnyFn = AnyFn> {
5
5
  /**
6
6
  * Whether an error should be considered a failure that could trigger
package/dist/index.d.mts CHANGED
@@ -1,6 +1,6 @@
1
1
  type AnyFn = (...args: any[]) => any;
2
2
 
3
- type CircuitState = "closed" | "disposed" | "halfOpen" | "open";
3
+ type CircuitState = "closed" | "halfOpen" | "open";
4
4
  interface CircuitBreakerOptions<Fallback extends AnyFn = AnyFn> {
5
5
  /**
6
6
  * Whether an error should be considered a failure that could trigger
package/dist/index.mjs CHANGED
@@ -1,64 +1,76 @@
1
+ const assertNever = (val, msg = "Unexpected value") => {
2
+ throw new TypeError(`${msg}: ${val}`);
3
+ };
4
+ const resolvedPromise = Promise.resolve();
5
+ const nextTick = (fn) => resolvedPromise.then(fn);
6
+
1
7
  function createCircuitBreaker(main, options = {}) {
2
8
  const {
3
9
  errorIsFailure = () => true,
4
10
  failureThreshold = 1,
5
- fallback = () => Promise.reject(failureCause),
6
11
  onClose,
7
12
  onOpen,
8
13
  resetAfter = 3e4
9
14
  } = options;
15
+ let fallback = options.fallback || (() => Promise.reject(failureCause));
10
16
  let halfOpenPending;
11
17
  let state = "closed";
12
- let failureCause = void 0;
18
+ let failureCause;
13
19
  let failureCount = 0;
14
- let resetTimer = void 0;
20
+ let resetTimer;
21
+ function clearFailure() {
22
+ failureCause = void 0;
23
+ failureCount = 0;
24
+ }
15
25
  function openCircuit(cause) {
26
+ failureCause = cause;
16
27
  state = "open";
17
- onOpen?.(cause);
18
28
  clearTimeout(resetTimer);
19
29
  resetTimer = setTimeout(() => state = "halfOpen", resetAfter);
30
+ onOpen?.(cause);
20
31
  }
21
- function closeCircuit() {
22
- state = "closed";
23
- failureCause = void 0;
24
- failureCount = 0;
25
- clearTimeout(resetTimer);
26
- }
27
- async function protectedFunction(...args) {
32
+ function protectedFunction(...args) {
28
33
  if (state === "closed") {
29
- return main(...args).catch((cause) => {
30
- if (state === "disposed") throw cause;
31
- failureCause = cause;
32
- failureCount += errorIsFailure(cause) ? 1 : 0;
33
- if (failureCount >= failureThreshold) openCircuit(cause);
34
- return protectedFunction(...args);
35
- });
34
+ const thisFallback = fallback;
35
+ return main(...args).then(
36
+ (result) => {
37
+ if (state === "closed") clearFailure();
38
+ return result;
39
+ },
40
+ (cause) => {
41
+ if (thisFallback !== fallback) throw cause;
42
+ failureCount += errorIsFailure(cause) ? 1 : 0;
43
+ if (failureCount === failureThreshold) openCircuit(cause);
44
+ return nextTick(() => protectedFunction(...args));
45
+ }
46
+ );
36
47
  } else if (state === "open" || halfOpenPending) {
37
48
  return fallback(...args);
38
49
  } else if (state === "halfOpen") {
50
+ const thisFallback = fallback;
39
51
  return (halfOpenPending = main(...args)).finally(() => halfOpenPending = void 0).then(
40
52
  (result) => {
41
- if (state !== "disposed") {
42
- closeCircuit();
43
- onClose?.();
44
- }
53
+ if (thisFallback !== fallback) return result;
54
+ state = "closed";
55
+ clearFailure();
56
+ clearTimeout(resetTimer);
57
+ onClose?.();
45
58
  return result;
46
59
  },
47
60
  (cause) => {
48
- if (state === "disposed") throw cause;
61
+ if (thisFallback !== fallback) throw cause;
49
62
  openCircuit(cause);
50
- return fallback(...args);
63
+ return nextTick(() => protectedFunction(...args));
51
64
  }
52
65
  );
53
- } else if (state === "disposed") {
54
- throw new Error("Circuit breaker has been disposed");
55
- } else {
56
- throw void 0;
57
66
  }
67
+ return assertNever(state);
58
68
  }
59
69
  protectedFunction.dispose = () => {
60
- closeCircuit();
61
- state = "disposed";
70
+ clearFailure();
71
+ clearTimeout(resetTimer);
72
+ fallback = () => Promise.reject(new ReferenceError("ERR_CIRCUIT_BREAKER_DISPOSED"));
73
+ state = "open";
62
74
  };
63
75
  protectedFunction.getLatestError = () => failureCause;
64
76
  protectedFunction.getState = () => state;
@@ -1 +1 @@
1
- {"version":3,"file":"index.mjs","sources":["../lib/index.ts"],"sourcesContent":["import { type AnyFn, assertNever } from \"./util.js\"\n\nexport type CircuitState = \"closed\" | \"disposed\" | \"halfOpen\" | \"open\"\n\nexport interface CircuitBreakerOptions<Fallback extends AnyFn = AnyFn> {\n\t/**\n\t * Whether an error should be considered a failure that could trigger\n\t * the circuit breaker. Use this to prevent certain errors from\n\t * incrementing the failure count.\n\t *\n\t * @default () => true // Every error is considered a failure\n\t */\n\terrorIsFailure?: (error: unknown) => boolean\n\n\t/**\n\t * The number of failures before the circuit breaker opens.\n\t *\n\t * @default 1 // The first error opens the circuit\n\t */\n\tfailureThreshold?: 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/** Called when the circuit breaker is closed */\n\tonClose?: () => void\n\n\t/** Called when the circuit breaker is opened */\n\tonOpen?: (cause: unknown) => void\n\n\t/**\n\t * The amount of time to wait before allowing a 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/** Free memory and stop timers */\n\tdispose(): 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 function createCircuitBreaker<\n\tRet,\n\tArgs extends unknown[],\n\tFallback extends AnyFn = (...args: Args) => Promise<Ret>\n>(\n\tmain: (...args: Args) => Promise<Ret>,\n\toptions: CircuitBreakerOptions<Fallback> = {}\n): CircuitBreakerProtectedFn<Ret, Args> {\n\tconst {\n\t\terrorIsFailure = () => true,\n\t\tfailureThreshold = 1,\n\t\tfallback = () => Promise.reject(failureCause),\n\t\tonClose,\n\t\tonOpen,\n\t\tresetAfter = 30_000,\n\t} = options\n\tlet halfOpenPending: Promise<unknown> | undefined\n\tlet state: CircuitState = \"closed\"\n\tlet failureCause: unknown | undefined = undefined\n\tlet failureCount = 0\n\tlet resetTimer: NodeJS.Timeout | undefined = undefined\n\n\t/**\n\t * Break the circuit and wait for a reset\n\t */\n\tfunction openCircuit(cause: unknown) {\n\t\tstate = \"open\"\n\t\tonOpen?.(cause)\n\t\tclearTimeout(resetTimer)\n\t\tresetTimer = setTimeout(() => (state = \"halfOpen\"), resetAfter)\n\t}\n\n\t/**\n\t * Reset the circuit and resume normal operation\n\t */\n\tfunction closeCircuit() {\n\t\tstate = \"closed\"\n\t\tfailureCause = undefined\n\t\tfailureCount = 0\n\t\tclearTimeout(resetTimer)\n\t}\n\n\t/**\n\t * Wrap calls to `main` with circuit breaker logic\n\t */\n\tasync function protectedFunction(...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\treturn main(...args).catch((cause) => {\n\t\t\t\tif (state === \"disposed\") throw cause\n\t\t\t\tfailureCause = cause\n\t\t\t\tfailureCount += errorIsFailure(cause) ? 1 : 0\n\t\t\t\tif (failureCount >= failureThreshold) openCircuit(cause)\n\t\t\t\treturn protectedFunction(...args)\n\t\t\t})\n\t\t}\n\n\t\t// Use the fallback while the circuit is open\n\t\telse if (state === \"open\" || halfOpenPending) {\n\t\t\treturn fallback(...args)\n\t\t}\n\n\t\t// While the circuit is half-open, try the main function once. If it\n\t\t// succeeds, close the circuit and resume normal operation. If it fails,\n\t\t// re-open the 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 (state !== \"disposed\") {\n\t\t\t\t\t\t\tcloseCircuit()\n\t\t\t\t\t\t\tonClose?.()\n\t\t\t\t\t\t}\n\t\t\t\t\t\treturn result\n\t\t\t\t\t},\n\t\t\t\t\t(cause) => {\n\t\t\t\t\t\tif (state === \"disposed\") throw cause\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// Shutting down...\n\t\telse if (state === \"disposed\") {\n\t\t\tthrow new Error(\"Circuit breaker has been disposed\")\n\t\t\t/* v8 ignore next */\n\t\t}\n\n\t\t// exhaustive check\n\t\t/* v8 ignore next 5 */\n\t\telse {\n\t\t\tthrow process.env.NODE_ENV !== \"production\"\n\t\t\t\t? assertNever(state)\n\t\t\t\t: undefined\n\t\t}\n\t}\n\n\tprotectedFunction.dispose = () => {\n\t\tcloseCircuit()\n\t\tstate = \"disposed\"\n\t}\n\n\tprotectedFunction.getLatestError = () => failureCause\n\n\tprotectedFunction.getState = () => state\n\n\treturn protectedFunction\n}\n"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"index.mjs","sources":["../lib/util.ts","../lib/index.ts"],"sourcesContent":["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\nconst resolvedPromise = Promise.resolve()\n\nexport const nextTick = <T>(fn: () => T | PromiseLike<T>): Promise<T> =>\n\tresolvedPromise.then(fn)\n","import { type AnyFn, assertNever, nextTick } from \"./util.js\"\n\nexport type CircuitState = \"closed\" | \"halfOpen\" | \"open\"\n\nexport interface CircuitBreakerOptions<Fallback extends AnyFn = AnyFn> {\n\t/**\n\t * Whether an error should be considered a failure that could trigger\n\t * the circuit breaker. Use this to prevent certain errors from\n\t * incrementing the failure count.\n\t *\n\t * @default () => true // Every error is considered a failure\n\t */\n\terrorIsFailure?: (error: unknown) => boolean\n\n\t/**\n\t * The number of failures before the circuit breaker opens.\n\t *\n\t * @default 1 // The first error opens the circuit\n\t */\n\tfailureThreshold?: 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/** Called when the circuit breaker is closed */\n\tonClose?: () => void\n\n\t/** Called when the circuit breaker is opened */\n\tonOpen?: (cause: unknown) => void\n\n\t/**\n\t * The amount of time to wait before allowing a 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/** Free memory and stop timers */\n\tdispose(): 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 function createCircuitBreaker<\n\tRet,\n\tArgs extends unknown[],\n\tFallback extends AnyFn = (...args: Args) => Promise<Ret>\n>(\n\tmain: (...args: Args) => Promise<Ret>,\n\toptions: CircuitBreakerOptions<Fallback> = {}\n): CircuitBreakerProtectedFn<Ret, Args> {\n\tconst {\n\t\terrorIsFailure = () => true,\n\t\tfailureThreshold = 1,\n\t\tonClose,\n\t\tonOpen,\n\t\tresetAfter = 30_000,\n\t} = options\n\tlet fallback = options.fallback || (() => Promise.reject(failureCause))\n\tlet halfOpenPending: Promise<unknown> | undefined\n\tlet state: CircuitState = \"closed\"\n\tlet failureCause: unknown | undefined\n\tlet failureCount = 0\n\tlet resetTimer: NodeJS.Timeout | undefined\n\n\tfunction clearFailure() {\n\t\tfailureCause = undefined\n\t\tfailureCount = 0\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\t/**\n\t * Wrap calls to `main` with circuit breaker logic\n\t */\n\tfunction protectedFunction(...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 thisFallback = fallback\n\t\t\treturn main(...args).then(\n\t\t\t\t(result) => {\n\t\t\t\t\t// Reset accumulated failures if circuit is still closed\n\t\t\t\t\tif (state === \"closed\") clearFailure()\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 breaker disposed while the call was in flight?\n\t\t\t\t\tif (thisFallback !== fallback) throw cause\n\t\t\t\t\tfailureCount += errorIsFailure(cause) ? 1 : 0\n\t\t\t\t\tif (failureCount === failureThreshold) openCircuit(cause)\n\t\t\t\t\treturn nextTick(() => protectedFunction(...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\tconst thisFallback = fallback\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\t// Was the circuit breaker disposed while the call was\n\t\t\t\t\t\t// in flight?\n\t\t\t\t\t\tif (thisFallback !== fallback) return result\n\t\t\t\t\t\t// Close the circuit and resume normal operation\n\t\t\t\t\t\tstate = \"closed\"\n\t\t\t\t\t\tclearFailure()\n\t\t\t\t\t\tclearTimeout(resetTimer)\n\t\t\t\t\t\tonClose?.()\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 breaker disposed while the call was\n\t\t\t\t\t\t// in flight?\n\t\t\t\t\t\tif (thisFallback !== fallback) throw cause\n\t\t\t\t\t\topenCircuit(cause)\n\t\t\t\t\t\treturn nextTick(() => protectedFunction(...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\tprotectedFunction.dispose = () => {\n\t\tclearFailure()\n\t\tclearTimeout(resetTimer)\n\t\tfallback = () =>\n\t\t\tPromise.reject(new ReferenceError(\"ERR_CIRCUIT_BREAKER_DISPOSED\"))\n\t\tstate = \"open\"\n\t}\n\n\tprotectedFunction.getLatestError = () => failureCause\n\n\tprotectedFunction.getState = () => state\n\n\treturn protectedFunction\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": "2.0.0",
3
+ "version": "3.0.0",
4
4
  "description": "A zero-dependency circuit breaker implementation for Node.js",
5
5
  "repository": "github:sirlancelot/breaker-box",
6
6
  "main": "./dist/index.cjs",