@@ -14,6 +14,11 @@ export interface CircuitBreakerOptions<T> {
1414 /**
1515 * Number of failures before opening the circuit.
1616 *
17+ * Note: For high-volume services, a low absolute threshold may cause
18+ * premature circuit opening during normal transient errors. Consider
19+ * a higher value proportional to your request volume, or combine with
20+ * a shorter {@linkcode failureWindowMs} to implement rate-based detection.
21+ *
1722 * @default {5}
1823 */
1924 failureThreshold ?: number ;
@@ -35,7 +40,7 @@ export interface CircuitBreakerOptions<T> {
3540 /**
3641 * Maximum concurrent requests allowed in half-open state.
3742 *
38- * @default {1 }
43+ * @default {3 }
3944 */
4045 halfOpenMaxConcurrent ?: number ;
4146
@@ -296,7 +301,7 @@ export class CircuitBreaker<T = unknown> {
296301 failureThreshold = 5 ,
297302 cooldownMs = 30_000 ,
298303 successThreshold = 2 ,
299- halfOpenMaxConcurrent = 1 ,
304+ halfOpenMaxConcurrent = 3 ,
300305 failureWindowMs = 60_000 ,
301306 isFailure = ( ) => true ,
302307 isResultFailure = ( ) => false ,
@@ -582,26 +587,23 @@ export class CircuitBreaker<T = unknown> {
582587 * OPEN → HALF_OPEN after cooldown expires.
583588 */
584589 #resolveCurrentState( ) : CircuitBreakerState {
585- const currentTime = Date . now ( ) ;
590+ if ( this . #state. state !== "open" || this . #state. openedAt === null ) {
591+ return this . #state;
592+ }
586593
587- // Auto-transition from OPEN to HALF_OPEN after cooldown
588- if (
589- this . #state. state === "open" &&
590- this . #state. openedAt !== null
591- ) {
592- const cooldownEnd = this . #state. openedAt + this . #cooldownMs;
593- if ( currentTime >= cooldownEnd ) {
594- const newState : CircuitBreakerState = {
595- ...this . #state,
596- state : "half_open" ,
597- consecutiveSuccesses : 0 ,
598- halfOpenInFlight : 0 ,
599- } ;
600- this . #state = newState ;
601- this . #onStateChange?.( "open" , "half_open" ) ;
602- }
594+ const cooldownEnd = this . #state. openedAt + this . #cooldownMs;
595+ if ( Date . now ( ) < cooldownEnd ) {
596+ return this . #state;
603597 }
604598
599+ // Auto-transition from OPEN to HALF_OPEN after cooldown
600+ this . #state = {
601+ ...this . #state,
602+ state : "half_open" ,
603+ consecutiveSuccesses : 0 ,
604+ halfOpenInFlight : 0 ,
605+ } ;
606+ this . #onStateChange?.( "open" , "half_open" ) ;
605607 return this . #state;
606608 }
607609
@@ -660,26 +662,20 @@ export class CircuitBreaker<T = unknown> {
660662
661663 /** Records a success and potentially closes the circuit from half-open. */
662664 #handleSuccess( previousState : CircuitState ) : void {
663- if ( previousState === "half_open" ) {
664- const newSuccessCount = this . #state. consecutiveSuccesses + 1 ;
665-
666- if ( newSuccessCount >= this . #successThreshold) {
667- // Recovered! Close the circuit
668- this . #state = createInitialState ( ) ;
669- this . #onStateChange?.( "half_open" , "closed" ) ;
670- this . #onClose?.( ) ;
671- } else {
672- this . #state = {
673- ...this . #state,
674- consecutiveSuccesses : newSuccessCount ,
675- } ;
676- }
677- } else if ( previousState === "closed" ) {
678- // Reset consecutive success counter on success in closed state
679- this . #state = {
680- ...this . #state,
681- consecutiveSuccesses : 0 ,
682- } ;
665+ if ( previousState === "closed" ) {
666+ this . #state = { ...this . #state, consecutiveSuccesses : 0 } ;
667+ return ;
668+ }
669+
670+ const newSuccessCount = this . #state. consecutiveSuccesses + 1 ;
671+ if ( newSuccessCount >= this . #successThreshold) {
672+ // Recovered! Close the circuit
673+ this . #state = createInitialState ( ) ;
674+ this . #onStateChange?.( "half_open" , "closed" ) ;
675+ this . #onClose?.( ) ;
676+ return ;
683677 }
678+
679+ this . #state = { ...this . #state, consecutiveSuccesses : newSuccessCount } ;
684680 }
685681}
0 commit comments