breaker-box 1.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 +125 -0
- package/dist/index.cjs +90 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.cts +51 -0
- package/dist/index.d.mts +51 -0
- package/dist/index.mjs +88 -0
- package/dist/index.mjs.map +1 -0
- package/package.json +43 -0
package/README.md
ADDED
|
@@ -0,0 +1,125 @@
|
|
|
1
|
+

|
|
2
|
+
|
|
3
|
+
# Breaker Box
|
|
4
|
+
|
|
5
|
+
A zero-dependency [circuit breaker][0] implementation for Node.js.
|
|
6
|
+
|
|
7
|
+
[0]: https://martinfowler.com/bliki/CircuitBreaker.html
|
|
8
|
+
|
|
9
|
+
## Installation
|
|
10
|
+
|
|
11
|
+
```bash
|
|
12
|
+
npm install breaker-box
|
|
13
|
+
```
|
|
14
|
+
|
|
15
|
+
## Usage
|
|
16
|
+
|
|
17
|
+
### Basic Usage
|
|
18
|
+
|
|
19
|
+
```typescript
|
|
20
|
+
import { createCircuitBreaker } from "breaker-box"
|
|
21
|
+
|
|
22
|
+
// Wrap an unreliable async function
|
|
23
|
+
async function unreliableApiCall(data: string) {
|
|
24
|
+
const response = await fetch(`https://api.example.com/data/${data}`)
|
|
25
|
+
if (!response.ok) throw new Error("API call failed")
|
|
26
|
+
return response.json()
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
const protectedApiCall = createCircuitBreaker(unreliableApiCall, {
|
|
30
|
+
failureThreshold: 1, // Open circuit after first failure
|
|
31
|
+
resetAfter: 30_000, // Try again after 30 seconds
|
|
32
|
+
errorIsFailure: (error) => true,
|
|
33
|
+
})
|
|
34
|
+
|
|
35
|
+
try {
|
|
36
|
+
const result = await protectedApiCall("user-123")
|
|
37
|
+
console.log("Success:", result)
|
|
38
|
+
} catch (error) {
|
|
39
|
+
console.error("Circuit breaker error:", error.message)
|
|
40
|
+
}
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
### Event Monitoring
|
|
44
|
+
|
|
45
|
+
```typescript
|
|
46
|
+
const protectedFunction = createCircuitBreaker(unreliableApiCall)
|
|
47
|
+
|
|
48
|
+
protectedFunction.on("open", (cause) => {
|
|
49
|
+
console.log("Circuit opened due to:", cause.message)
|
|
50
|
+
})
|
|
51
|
+
|
|
52
|
+
protectedFunction.on("close", () => {
|
|
53
|
+
console.log("Circuit closed - normal operation resumed")
|
|
54
|
+
})
|
|
55
|
+
|
|
56
|
+
protectedFunction.on("reject", (error) => {
|
|
57
|
+
console.log("Function call rejected:", error.message)
|
|
58
|
+
})
|
|
59
|
+
|
|
60
|
+
protectedFunction.on("resolve", () => {
|
|
61
|
+
console.log("Function call succeeded")
|
|
62
|
+
})
|
|
63
|
+
|
|
64
|
+
// Check current state
|
|
65
|
+
console.log("Current state:", protectedFunction.getState())
|
|
66
|
+
// Possible states: 'closed', 'open', 'halfOpen', 'disposed'
|
|
67
|
+
```
|
|
68
|
+
|
|
69
|
+
### Cleanup
|
|
70
|
+
|
|
71
|
+
```typescript
|
|
72
|
+
// Clean up resources when done
|
|
73
|
+
protectedFunction.dispose()
|
|
74
|
+
```
|
|
75
|
+
|
|
76
|
+
## API
|
|
77
|
+
|
|
78
|
+
### `createCircuitBreaker(fn, options?)`
|
|
79
|
+
|
|
80
|
+
Creates a circuit breaker around the provided async function.
|
|
81
|
+
|
|
82
|
+
#### Parameters
|
|
83
|
+
|
|
84
|
+
- `fn`: The async function to protect
|
|
85
|
+
- `options`: Configuration object (optional)
|
|
86
|
+
- `failureThreshold`: Number of failures before opening circuit (default: 1)
|
|
87
|
+
- `resetAfter`: Milliseconds to wait before trying again (default: 30000)
|
|
88
|
+
- `errorIsFailure`: Function to determine if an error counts as failure (default: all errors)
|
|
89
|
+
- `fallback`: Function to call when circuit is open (default: throws CircuitOpenError)
|
|
90
|
+
|
|
91
|
+
#### Returns
|
|
92
|
+
|
|
93
|
+
A function with the same signature as `fn` and additional methods:
|
|
94
|
+
|
|
95
|
+
- `.getState()`: Returns current circuit state
|
|
96
|
+
- `.on(event, listener)`: Add event listener
|
|
97
|
+
- `.off(event, listener)`: Remove event listener
|
|
98
|
+
- `.dispose()`: Clean up resources
|
|
99
|
+
|
|
100
|
+
#### Events
|
|
101
|
+
|
|
102
|
+
- `open`: Circuit opened due to failures
|
|
103
|
+
- `close`: Circuit closed and resumed normal operation
|
|
104
|
+
- `reject`: Function call was rejected
|
|
105
|
+
- `resolve`: Function call succeeded
|
|
106
|
+
|
|
107
|
+
### Development
|
|
108
|
+
|
|
109
|
+
### Building the Project
|
|
110
|
+
|
|
111
|
+
```sh
|
|
112
|
+
npm run build
|
|
113
|
+
```
|
|
114
|
+
|
|
115
|
+
### Running Tests
|
|
116
|
+
|
|
117
|
+
```sh
|
|
118
|
+
npm test # once
|
|
119
|
+
|
|
120
|
+
npm run dev # run and watch for file changes
|
|
121
|
+
```
|
|
122
|
+
|
|
123
|
+
## Contributing
|
|
124
|
+
|
|
125
|
+
Contributions are welcome! Please open an issue or submit a pull request.
|
package/dist/index.cjs
ADDED
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
var EventEmitter = require('node:events');
|
|
4
|
+
|
|
5
|
+
const assertNever = (value, message = "Unexpected value") => (
|
|
6
|
+
/* v8 ignore next */
|
|
7
|
+
new TypeError(`${message}: ${value}`)
|
|
8
|
+
);
|
|
9
|
+
|
|
10
|
+
function createCircuitBreaker(main, options = {}) {
|
|
11
|
+
const events = new EventEmitter();
|
|
12
|
+
const {
|
|
13
|
+
errorIsFailure = () => true,
|
|
14
|
+
failureThreshold = 1,
|
|
15
|
+
fallback = () => {
|
|
16
|
+
const cause = failureCause;
|
|
17
|
+
const msg = cause instanceof Error ? cause.message : "Unknown";
|
|
18
|
+
throw new Error(`CircuitOpenError: ${msg}`, { cause });
|
|
19
|
+
},
|
|
20
|
+
resetAfter = 3e4
|
|
21
|
+
} = options;
|
|
22
|
+
let state = "closed";
|
|
23
|
+
let failureCause = void 0;
|
|
24
|
+
let failureCount = 0;
|
|
25
|
+
let resetTimer = void 0;
|
|
26
|
+
function openCircuit(cause) {
|
|
27
|
+
if (state === "disposed") return;
|
|
28
|
+
state = "open";
|
|
29
|
+
events.emit("open", cause);
|
|
30
|
+
failureCause = cause;
|
|
31
|
+
clearTimeout(resetTimer);
|
|
32
|
+
resetTimer = setTimeout(() => state = "halfOpen", resetAfter);
|
|
33
|
+
}
|
|
34
|
+
function closeCircuit() {
|
|
35
|
+
if (state === "disposed") return;
|
|
36
|
+
if (state === "halfOpen") events.emit("close");
|
|
37
|
+
state = "closed";
|
|
38
|
+
failureCause = void 0;
|
|
39
|
+
failureCount = 0;
|
|
40
|
+
clearTimeout(resetTimer);
|
|
41
|
+
}
|
|
42
|
+
const mainWithEmit = async (...args) => {
|
|
43
|
+
try {
|
|
44
|
+
const result = await main(...args);
|
|
45
|
+
events.emit("resolve");
|
|
46
|
+
closeCircuit();
|
|
47
|
+
return result;
|
|
48
|
+
} catch (cause) {
|
|
49
|
+
events.emit("reject", cause);
|
|
50
|
+
throw cause;
|
|
51
|
+
}
|
|
52
|
+
};
|
|
53
|
+
async function protectedFunction(...args) {
|
|
54
|
+
if (state === "open") {
|
|
55
|
+
return fallback(...args);
|
|
56
|
+
} else if (state === "halfOpen") {
|
|
57
|
+
return mainWithEmit(...args).catch((cause) => {
|
|
58
|
+
openCircuit(cause);
|
|
59
|
+
return fallback(...args);
|
|
60
|
+
});
|
|
61
|
+
} else if (state === "closed") {
|
|
62
|
+
return mainWithEmit(...args).catch((cause) => {
|
|
63
|
+
failureCause = cause;
|
|
64
|
+
failureCount += errorIsFailure(cause) ? 1 : 0;
|
|
65
|
+
if (failureCount >= failureThreshold) openCircuit(cause);
|
|
66
|
+
return fallback(...args);
|
|
67
|
+
});
|
|
68
|
+
} else if (state === "disposed")
|
|
69
|
+
throw new Error("Circuit breaker has been disposed");
|
|
70
|
+
else throw assertNever(state);
|
|
71
|
+
}
|
|
72
|
+
protectedFunction.dispose = () => {
|
|
73
|
+
events.removeAllListeners();
|
|
74
|
+
closeCircuit();
|
|
75
|
+
state = "disposed";
|
|
76
|
+
};
|
|
77
|
+
protectedFunction.getState = () => state;
|
|
78
|
+
protectedFunction.off = (event, listener) => {
|
|
79
|
+
events.removeListener(event, listener);
|
|
80
|
+
return protectedFunction;
|
|
81
|
+
};
|
|
82
|
+
protectedFunction.on = (event, listener) => {
|
|
83
|
+
events.addListener(event, listener);
|
|
84
|
+
return protectedFunction;
|
|
85
|
+
};
|
|
86
|
+
return protectedFunction;
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
exports.createCircuitBreaker = createCircuitBreaker;
|
|
90
|
+
//# sourceMappingURL=index.cjs.map
|
|
@@ -0,0 +1 @@
|
|
|
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 */\nexport const assertNever = (value: never, message = \"Unexpected value\") =>\n\t/* v8 ignore next */\n\tnew TypeError(`${message}: ${value}`)\n","import { type AnyFn, assertNever } from \"./util.js\"\nimport EventEmitter from \"node:events\"\n\nexport type CircuitState = \"closed\" | \"disposed\" | \"halfOpen\" | \"open\"\n\nexport interface CircuitBreakerOptions<Args extends unknown[], Ret> {\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, throws `CircuitOpenError`\n\t */\n\tfallback?: (...args: Args) => Promise<Ret>\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 EventMap {\n\tclose: []\n\topen: [cause: unknown]\n\treject: [cause: unknown]\n\tresolve: []\n}\n\nexport interface ProtectedFunction<Args extends unknown[], Ret> {\n\t(...args: Args): Promise<Ret>\n\n\t/** Free memory and stop timers */\n\tdispose(): void\n\n\t/** Get the current state of the circuit breaker */\n\tgetState(): CircuitState\n\n\t/** Remove a listener from the circuit breaker */\n\toff<T extends keyof EventMap>(\n\t\tevent: T,\n\t\tlistener: (...args: EventMap[T]) => void\n\t): this\n\n\t/** Add a listener to the circuit breaker */\n\ton<T extends keyof EventMap>(\n\t\tevent: T,\n\t\tlistener: (...args: EventMap[T]) => void\n\t): this\n}\n\nexport function createCircuitBreaker<Args extends unknown[], Ret>(\n\tmain: (...args: Args) => Promise<Ret>,\n\toptions: CircuitBreakerOptions<Args, Ret> = {}\n): ProtectedFunction<Args, Ret> {\n\tconst events = new EventEmitter<EventMap>()\n\tconst {\n\t\terrorIsFailure = () => true,\n\t\tfailureThreshold = 1,\n\t\tfallback = () => {\n\t\t\tconst cause = failureCause\n\t\t\tconst msg = cause instanceof Error ? cause.message : \"Unknown\"\n\t\t\tthrow new Error(`CircuitOpenError: ${msg}`, { cause })\n\t\t},\n\t\tresetAfter = 30_000,\n\t} = options\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\tif (state === \"disposed\") return\n\t\tstate = \"open\"\n\t\tevents.emit(\"open\", cause)\n\t\tfailureCause = cause\n\t\tclearTimeout(resetTimer)\n\t\tresetTimer = setTimeout(() => (state = \"halfOpen\"), resetAfter)\n\t}\n\n\t/**\n\t * Restart the circuit and resume normal operation\n\t */\n\tfunction closeCircuit() {\n\t\tif (state === \"disposed\") return\n\t\tif (state === \"halfOpen\") events.emit(\"close\")\n\t\tstate = \"closed\"\n\t\tfailureCause = undefined\n\t\tfailureCount = 0\n\t\tclearTimeout(resetTimer)\n\t}\n\n\tconst mainWithEmit = async (...args: Args) => {\n\t\ttry {\n\t\t\tconst result = await main(...args)\n\t\t\tevents.emit(\"resolve\")\n\t\t\tcloseCircuit()\n\t\t\treturn result\n\t\t} catch (cause) {\n\t\t\tevents.emit(\"reject\", cause)\n\t\t\tthrow cause\n\t\t}\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// Use the fallback while the circuit is open\n\t\tif (state === \"open\") {\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// success, 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 mainWithEmit(...args).catch((cause) => {\n\t\t\t\topenCircuit(cause)\n\t\t\t\treturn fallback(...args)\n\t\t\t})\n\t\t}\n\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\telse if (state === \"closed\") {\n\t\t\treturn mainWithEmit(...args).catch((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 fallback(...args)\n\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\telse throw assertNever(state)\n\t}\n\n\tprotectedFunction.dispose = () => {\n\t\tevents.removeAllListeners()\n\t\tcloseCircuit()\n\t\tstate = \"disposed\"\n\t}\n\n\tprotectedFunction.getState = () => state\n\n\tprotectedFunction.off = (event: keyof EventMap, listener: AnyFn) => {\n\t\tevents.removeListener(event, listener)\n\t\treturn protectedFunction\n\t}\n\n\tprotectedFunction.on = (event: keyof EventMap, listener: AnyFn) => {\n\t\tevents.addListener(event, listener)\n\t\treturn protectedFunction\n\t}\n\n\treturn protectedFunction\n}\n"],"names":[],"mappings":"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"}
|
package/dist/index.d.cts
ADDED
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
type CircuitState = "closed" | "disposed" | "halfOpen" | "open";
|
|
2
|
+
interface CircuitBreakerOptions<Args extends unknown[], Ret> {
|
|
3
|
+
/**
|
|
4
|
+
* Whether an error should be considered a failure that could trigger
|
|
5
|
+
* the circuit breaker. Use this to prevent certain errors from
|
|
6
|
+
* incrementing the failure count.
|
|
7
|
+
*
|
|
8
|
+
* @default () => true // Every error is considered a failure
|
|
9
|
+
*/
|
|
10
|
+
errorIsFailure?: (error: unknown) => boolean;
|
|
11
|
+
/**
|
|
12
|
+
* The number of failures before the circuit breaker opens.
|
|
13
|
+
*
|
|
14
|
+
* @default 1 // The first error opens the circuit
|
|
15
|
+
*/
|
|
16
|
+
failureThreshold?: number;
|
|
17
|
+
/**
|
|
18
|
+
* If provided, then all rejected calls to `main` will be forwarded to
|
|
19
|
+
* this function instead.
|
|
20
|
+
*
|
|
21
|
+
* @default undefined // No fallback, throws `CircuitOpenError`
|
|
22
|
+
*/
|
|
23
|
+
fallback?: (...args: Args) => Promise<Ret>;
|
|
24
|
+
/**
|
|
25
|
+
* The amount of time to wait before allowing a half-open state.
|
|
26
|
+
*
|
|
27
|
+
* @default 30_000 // 30 seconds
|
|
28
|
+
*/
|
|
29
|
+
resetAfter?: number;
|
|
30
|
+
}
|
|
31
|
+
interface EventMap {
|
|
32
|
+
close: [];
|
|
33
|
+
open: [cause: unknown];
|
|
34
|
+
reject: [cause: unknown];
|
|
35
|
+
resolve: [];
|
|
36
|
+
}
|
|
37
|
+
interface ProtectedFunction<Args extends unknown[], Ret> {
|
|
38
|
+
(...args: Args): Promise<Ret>;
|
|
39
|
+
/** Free memory and stop timers */
|
|
40
|
+
dispose(): void;
|
|
41
|
+
/** Get the current state of the circuit breaker */
|
|
42
|
+
getState(): CircuitState;
|
|
43
|
+
/** Remove a listener from the circuit breaker */
|
|
44
|
+
off<T extends keyof EventMap>(event: T, listener: (...args: EventMap[T]) => void): this;
|
|
45
|
+
/** Add a listener to the circuit breaker */
|
|
46
|
+
on<T extends keyof EventMap>(event: T, listener: (...args: EventMap[T]) => void): this;
|
|
47
|
+
}
|
|
48
|
+
declare function createCircuitBreaker<Args extends unknown[], Ret>(main: (...args: Args) => Promise<Ret>, options?: CircuitBreakerOptions<Args, Ret>): ProtectedFunction<Args, Ret>;
|
|
49
|
+
|
|
50
|
+
export { createCircuitBreaker };
|
|
51
|
+
export type { CircuitBreakerOptions, CircuitState, EventMap, ProtectedFunction };
|
package/dist/index.d.mts
ADDED
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
type CircuitState = "closed" | "disposed" | "halfOpen" | "open";
|
|
2
|
+
interface CircuitBreakerOptions<Args extends unknown[], Ret> {
|
|
3
|
+
/**
|
|
4
|
+
* Whether an error should be considered a failure that could trigger
|
|
5
|
+
* the circuit breaker. Use this to prevent certain errors from
|
|
6
|
+
* incrementing the failure count.
|
|
7
|
+
*
|
|
8
|
+
* @default () => true // Every error is considered a failure
|
|
9
|
+
*/
|
|
10
|
+
errorIsFailure?: (error: unknown) => boolean;
|
|
11
|
+
/**
|
|
12
|
+
* The number of failures before the circuit breaker opens.
|
|
13
|
+
*
|
|
14
|
+
* @default 1 // The first error opens the circuit
|
|
15
|
+
*/
|
|
16
|
+
failureThreshold?: number;
|
|
17
|
+
/**
|
|
18
|
+
* If provided, then all rejected calls to `main` will be forwarded to
|
|
19
|
+
* this function instead.
|
|
20
|
+
*
|
|
21
|
+
* @default undefined // No fallback, throws `CircuitOpenError`
|
|
22
|
+
*/
|
|
23
|
+
fallback?: (...args: Args) => Promise<Ret>;
|
|
24
|
+
/**
|
|
25
|
+
* The amount of time to wait before allowing a half-open state.
|
|
26
|
+
*
|
|
27
|
+
* @default 30_000 // 30 seconds
|
|
28
|
+
*/
|
|
29
|
+
resetAfter?: number;
|
|
30
|
+
}
|
|
31
|
+
interface EventMap {
|
|
32
|
+
close: [];
|
|
33
|
+
open: [cause: unknown];
|
|
34
|
+
reject: [cause: unknown];
|
|
35
|
+
resolve: [];
|
|
36
|
+
}
|
|
37
|
+
interface ProtectedFunction<Args extends unknown[], Ret> {
|
|
38
|
+
(...args: Args): Promise<Ret>;
|
|
39
|
+
/** Free memory and stop timers */
|
|
40
|
+
dispose(): void;
|
|
41
|
+
/** Get the current state of the circuit breaker */
|
|
42
|
+
getState(): CircuitState;
|
|
43
|
+
/** Remove a listener from the circuit breaker */
|
|
44
|
+
off<T extends keyof EventMap>(event: T, listener: (...args: EventMap[T]) => void): this;
|
|
45
|
+
/** Add a listener to the circuit breaker */
|
|
46
|
+
on<T extends keyof EventMap>(event: T, listener: (...args: EventMap[T]) => void): this;
|
|
47
|
+
}
|
|
48
|
+
declare function createCircuitBreaker<Args extends unknown[], Ret>(main: (...args: Args) => Promise<Ret>, options?: CircuitBreakerOptions<Args, Ret>): ProtectedFunction<Args, Ret>;
|
|
49
|
+
|
|
50
|
+
export { createCircuitBreaker };
|
|
51
|
+
export type { CircuitBreakerOptions, CircuitState, EventMap, ProtectedFunction };
|
package/dist/index.mjs
ADDED
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
import EventEmitter from 'node:events';
|
|
2
|
+
|
|
3
|
+
const assertNever = (value, message = "Unexpected value") => (
|
|
4
|
+
/* v8 ignore next */
|
|
5
|
+
new TypeError(`${message}: ${value}`)
|
|
6
|
+
);
|
|
7
|
+
|
|
8
|
+
function createCircuitBreaker(main, options = {}) {
|
|
9
|
+
const events = new EventEmitter();
|
|
10
|
+
const {
|
|
11
|
+
errorIsFailure = () => true,
|
|
12
|
+
failureThreshold = 1,
|
|
13
|
+
fallback = () => {
|
|
14
|
+
const cause = failureCause;
|
|
15
|
+
const msg = cause instanceof Error ? cause.message : "Unknown";
|
|
16
|
+
throw new Error(`CircuitOpenError: ${msg}`, { cause });
|
|
17
|
+
},
|
|
18
|
+
resetAfter = 3e4
|
|
19
|
+
} = options;
|
|
20
|
+
let state = "closed";
|
|
21
|
+
let failureCause = void 0;
|
|
22
|
+
let failureCount = 0;
|
|
23
|
+
let resetTimer = void 0;
|
|
24
|
+
function openCircuit(cause) {
|
|
25
|
+
if (state === "disposed") return;
|
|
26
|
+
state = "open";
|
|
27
|
+
events.emit("open", cause);
|
|
28
|
+
failureCause = cause;
|
|
29
|
+
clearTimeout(resetTimer);
|
|
30
|
+
resetTimer = setTimeout(() => state = "halfOpen", resetAfter);
|
|
31
|
+
}
|
|
32
|
+
function closeCircuit() {
|
|
33
|
+
if (state === "disposed") return;
|
|
34
|
+
if (state === "halfOpen") events.emit("close");
|
|
35
|
+
state = "closed";
|
|
36
|
+
failureCause = void 0;
|
|
37
|
+
failureCount = 0;
|
|
38
|
+
clearTimeout(resetTimer);
|
|
39
|
+
}
|
|
40
|
+
const mainWithEmit = async (...args) => {
|
|
41
|
+
try {
|
|
42
|
+
const result = await main(...args);
|
|
43
|
+
events.emit("resolve");
|
|
44
|
+
closeCircuit();
|
|
45
|
+
return result;
|
|
46
|
+
} catch (cause) {
|
|
47
|
+
events.emit("reject", cause);
|
|
48
|
+
throw cause;
|
|
49
|
+
}
|
|
50
|
+
};
|
|
51
|
+
async function protectedFunction(...args) {
|
|
52
|
+
if (state === "open") {
|
|
53
|
+
return fallback(...args);
|
|
54
|
+
} else if (state === "halfOpen") {
|
|
55
|
+
return mainWithEmit(...args).catch((cause) => {
|
|
56
|
+
openCircuit(cause);
|
|
57
|
+
return fallback(...args);
|
|
58
|
+
});
|
|
59
|
+
} else if (state === "closed") {
|
|
60
|
+
return mainWithEmit(...args).catch((cause) => {
|
|
61
|
+
failureCause = cause;
|
|
62
|
+
failureCount += errorIsFailure(cause) ? 1 : 0;
|
|
63
|
+
if (failureCount >= failureThreshold) openCircuit(cause);
|
|
64
|
+
return fallback(...args);
|
|
65
|
+
});
|
|
66
|
+
} else if (state === "disposed")
|
|
67
|
+
throw new Error("Circuit breaker has been disposed");
|
|
68
|
+
else throw assertNever(state);
|
|
69
|
+
}
|
|
70
|
+
protectedFunction.dispose = () => {
|
|
71
|
+
events.removeAllListeners();
|
|
72
|
+
closeCircuit();
|
|
73
|
+
state = "disposed";
|
|
74
|
+
};
|
|
75
|
+
protectedFunction.getState = () => state;
|
|
76
|
+
protectedFunction.off = (event, listener) => {
|
|
77
|
+
events.removeListener(event, listener);
|
|
78
|
+
return protectedFunction;
|
|
79
|
+
};
|
|
80
|
+
protectedFunction.on = (event, listener) => {
|
|
81
|
+
events.addListener(event, listener);
|
|
82
|
+
return protectedFunction;
|
|
83
|
+
};
|
|
84
|
+
return protectedFunction;
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
export { createCircuitBreaker };
|
|
88
|
+
//# sourceMappingURL=index.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
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 */\nexport const assertNever = (value: never, message = \"Unexpected value\") =>\n\t/* v8 ignore next */\n\tnew TypeError(`${message}: ${value}`)\n","import { type AnyFn, assertNever } from \"./util.js\"\nimport EventEmitter from \"node:events\"\n\nexport type CircuitState = \"closed\" | \"disposed\" | \"halfOpen\" | \"open\"\n\nexport interface CircuitBreakerOptions<Args extends unknown[], Ret> {\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, throws `CircuitOpenError`\n\t */\n\tfallback?: (...args: Args) => Promise<Ret>\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 EventMap {\n\tclose: []\n\topen: [cause: unknown]\n\treject: [cause: unknown]\n\tresolve: []\n}\n\nexport interface ProtectedFunction<Args extends unknown[], Ret> {\n\t(...args: Args): Promise<Ret>\n\n\t/** Free memory and stop timers */\n\tdispose(): void\n\n\t/** Get the current state of the circuit breaker */\n\tgetState(): CircuitState\n\n\t/** Remove a listener from the circuit breaker */\n\toff<T extends keyof EventMap>(\n\t\tevent: T,\n\t\tlistener: (...args: EventMap[T]) => void\n\t): this\n\n\t/** Add a listener to the circuit breaker */\n\ton<T extends keyof EventMap>(\n\t\tevent: T,\n\t\tlistener: (...args: EventMap[T]) => void\n\t): this\n}\n\nexport function createCircuitBreaker<Args extends unknown[], Ret>(\n\tmain: (...args: Args) => Promise<Ret>,\n\toptions: CircuitBreakerOptions<Args, Ret> = {}\n): ProtectedFunction<Args, Ret> {\n\tconst events = new EventEmitter<EventMap>()\n\tconst {\n\t\terrorIsFailure = () => true,\n\t\tfailureThreshold = 1,\n\t\tfallback = () => {\n\t\t\tconst cause = failureCause\n\t\t\tconst msg = cause instanceof Error ? cause.message : \"Unknown\"\n\t\t\tthrow new Error(`CircuitOpenError: ${msg}`, { cause })\n\t\t},\n\t\tresetAfter = 30_000,\n\t} = options\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\tif (state === \"disposed\") return\n\t\tstate = \"open\"\n\t\tevents.emit(\"open\", cause)\n\t\tfailureCause = cause\n\t\tclearTimeout(resetTimer)\n\t\tresetTimer = setTimeout(() => (state = \"halfOpen\"), resetAfter)\n\t}\n\n\t/**\n\t * Restart the circuit and resume normal operation\n\t */\n\tfunction closeCircuit() {\n\t\tif (state === \"disposed\") return\n\t\tif (state === \"halfOpen\") events.emit(\"close\")\n\t\tstate = \"closed\"\n\t\tfailureCause = undefined\n\t\tfailureCount = 0\n\t\tclearTimeout(resetTimer)\n\t}\n\n\tconst mainWithEmit = async (...args: Args) => {\n\t\ttry {\n\t\t\tconst result = await main(...args)\n\t\t\tevents.emit(\"resolve\")\n\t\t\tcloseCircuit()\n\t\t\treturn result\n\t\t} catch (cause) {\n\t\t\tevents.emit(\"reject\", cause)\n\t\t\tthrow cause\n\t\t}\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// Use the fallback while the circuit is open\n\t\tif (state === \"open\") {\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// success, 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 mainWithEmit(...args).catch((cause) => {\n\t\t\t\topenCircuit(cause)\n\t\t\t\treturn fallback(...args)\n\t\t\t})\n\t\t}\n\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\telse if (state === \"closed\") {\n\t\t\treturn mainWithEmit(...args).catch((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 fallback(...args)\n\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\telse throw assertNever(state)\n\t}\n\n\tprotectedFunction.dispose = () => {\n\t\tevents.removeAllListeners()\n\t\tcloseCircuit()\n\t\tstate = \"disposed\"\n\t}\n\n\tprotectedFunction.getState = () => state\n\n\tprotectedFunction.off = (event: keyof EventMap, listener: AnyFn) => {\n\t\tevents.removeListener(event, listener)\n\t\treturn protectedFunction\n\t}\n\n\tprotectedFunction.on = (event: keyof EventMap, listener: AnyFn) => {\n\t\tevents.addListener(event, listener)\n\t\treturn protectedFunction\n\t}\n\n\treturn protectedFunction\n}\n"],"names":[],"mappings":"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"}
|
package/package.json
ADDED
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "breaker-box",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"description": "A zero-dependency circuit breaker implementation for Node.js",
|
|
5
|
+
"repository": "github:sirlancelot/breaker-box",
|
|
6
|
+
"main": "./dist/index.cjs",
|
|
7
|
+
"module": "./dist/index.mjs",
|
|
8
|
+
"types": "./dist/index.d.cts",
|
|
9
|
+
"exports": {
|
|
10
|
+
"require": {
|
|
11
|
+
"default": "./dist/index.cjs",
|
|
12
|
+
"types": "./dist/index.d.cts"
|
|
13
|
+
},
|
|
14
|
+
"import": {
|
|
15
|
+
"default": "./dist/index.mjs",
|
|
16
|
+
"types": "./dist/index.d.mts"
|
|
17
|
+
}
|
|
18
|
+
},
|
|
19
|
+
"files": [
|
|
20
|
+
"dist"
|
|
21
|
+
],
|
|
22
|
+
"scripts": {
|
|
23
|
+
"build": "pkgroll --clean-dist --src lib --target=node18 --sourcemap",
|
|
24
|
+
"dev": "vitest",
|
|
25
|
+
"prepublishOnly": "npm run build",
|
|
26
|
+
"test": "vitest --run"
|
|
27
|
+
},
|
|
28
|
+
"keywords": [
|
|
29
|
+
"circuit-breaker",
|
|
30
|
+
"breaker-box"
|
|
31
|
+
],
|
|
32
|
+
"author": "Matthew Pietz <sirlancelot@gmail.com>",
|
|
33
|
+
"license": "ISC",
|
|
34
|
+
"devDependencies": {
|
|
35
|
+
"@types/node": "24.3.1",
|
|
36
|
+
"@vitest/coverage-v8": "3.2.4",
|
|
37
|
+
"pkgroll": "2.15.4",
|
|
38
|
+
"tsx": "4.20.5",
|
|
39
|
+
"typescript": "5.9.2",
|
|
40
|
+
"vitest": "3.2.4",
|
|
41
|
+
"vitest-when": "0.8.0"
|
|
42
|
+
}
|
|
43
|
+
}
|