Skip to content

Commit 2f8ab8e

Browse files
committed
refactor: move circuit breaker to a separate class
1 parent bee73b7 commit 2f8ab8e

File tree

1 file changed

+132
-0
lines changed

1 file changed

+132
-0
lines changed

CircuitBreaker.js

Lines changed: 132 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,132 @@
1+
/**
2+
* Circuit Breaker implementation with configurable failure thresholds and cooldown periods
3+
*/
4+
class CircuitBreaker {
5+
static #instances = new Map(); // bucketId -> instance
6+
7+
constructor({
8+
failureThreshold = 5,
9+
cooldownPeriod = 30000,
10+
name = 'default'
11+
}) {
12+
this.failureThreshold = failureThreshold;
13+
this.cooldownPeriod = cooldownPeriod;
14+
this.name = name;
15+
16+
// State
17+
this.failCount = 0;
18+
this.isOpen = false;
19+
this.openedAt = null;
20+
this.lastFailureTime = null;
21+
}
22+
23+
/**
24+
* Get or create a circuit breaker instance for the given bucketId
25+
* @param {string} bucketId - The service identifier
26+
* @param {Object} config - Circuit breaker configuration
27+
* @returns {CircuitBreaker} - The circuit breaker instance
28+
*/
29+
static getInstance(bucketId, config) {
30+
if (!this.#instances.has(bucketId)) {
31+
this.#instances.set(bucketId, new CircuitBreaker({
32+
...config,
33+
name: `CircuitBreaker-${bucketId}`
34+
}));
35+
}
36+
return this.#instances.get(bucketId);
37+
}
38+
39+
/**
40+
* Clear a circuit breaker instance for the given bucketId
41+
* @param {string} bucketId - The service identifier
42+
*/
43+
static clear(bucketId) {
44+
this.#instances.delete(bucketId);
45+
}
46+
47+
/**
48+
* Check if the circuit breaker is open, reset if cooldown period has expired
49+
* @returns {boolean} - True if circuit is open, false if closed
50+
*/
51+
isCircuitOpen() {
52+
if (!this.isOpen) return false;
53+
54+
// Check if cooldown period has expired
55+
if (Date.now() - this.openedAt > this.cooldownPeriod) {
56+
this._reset();
57+
return false;
58+
}
59+
60+
return true;
61+
}
62+
63+
/**
64+
* Record a successful operation
65+
*/
66+
recordSuccess() {
67+
this._reset();
68+
}
69+
70+
/**
71+
* Record a failed operation
72+
*/
73+
recordFailure() {
74+
this.failCount++;
75+
this.lastFailureTime = Date.now();
76+
77+
if (this.failCount >= this.failureThreshold) {
78+
this._open();
79+
}
80+
}
81+
82+
/**
83+
* Get current circuit breaker status
84+
* @returns {Object} - Status information
85+
*/
86+
getStatus() {
87+
return {
88+
isOpen: this.isCircuitOpen(),
89+
failCount: this.failCount,
90+
failureThreshold: this.failureThreshold,
91+
cooldownRemaining: this.isOpen ?
92+
Math.max(0, this.cooldownPeriod - (Date.now() - this.openedAt)) : 0,
93+
lastFailureTime: this.lastFailureTime,
94+
name: this.name
95+
};
96+
}
97+
98+
/**
99+
* Manually open the circuit breaker
100+
*/
101+
forceOpen() {
102+
this._open();
103+
}
104+
105+
/**
106+
* Manually close the circuit breaker
107+
*/
108+
forceClose() {
109+
this._reset();
110+
}
111+
112+
/**
113+
* Reset circuit breaker to closed state
114+
* @private
115+
*/
116+
_reset() {
117+
this.isOpen = false;
118+
this.failCount = 0;
119+
this.openedAt = null;
120+
}
121+
122+
/**
123+
* Open the circuit breaker
124+
* @private
125+
*/
126+
_open() {
127+
this.isOpen = true;
128+
this.openedAt = Date.now();
129+
}
130+
}
131+
132+
export default CircuitBreaker;

0 commit comments

Comments
 (0)