@@ -475,7 +475,7 @@ export class CircuitBreaker<T = unknown> {
475475 * @returns `true` if requests will be attempted, `false` if rejected.
476476 */
477477 get isAvailable ( ) : boolean {
478- const resolved = this . #resolveCurrentState( ) ;
478+ const resolved = this . #resolveCurrentState( Date . now ( ) ) ;
479479 if ( resolved . state === "closed" ) return true ;
480480 if ( resolved . state === "open" ) return false ;
481481 // half_open: available if under concurrent limit
@@ -533,7 +533,7 @@ export class CircuitBreaker<T = unknown> {
533533 options ?. signal ?. throwIfAborted ( ) ;
534534
535535 const currentTime = Date . now ( ) ;
536- const currentState = this . #resolveCurrentState( ) ;
536+ const currentState = this . #resolveCurrentState( currentTime ) ;
537537
538538 // Check if we should reject
539539 if ( currentState . state === "open" ) {
@@ -557,7 +557,7 @@ export class CircuitBreaker<T = unknown> {
557557 try {
558558 result = await fn ( ) ;
559559 } catch ( error ) {
560- this . #handleFailure( error , currentState . state ) ;
560+ this . #handleFailure( error , currentState . state , currentTime ) ;
561561 throw error ;
562562 } finally {
563563 // Decrement half-open in-flight counter
@@ -572,7 +572,7 @@ export class CircuitBreaker<T = unknown> {
572572 // Check if successful result should count as failure
573573 if ( this . #isResultFailure( result ) ) {
574574 const syntheticError = new Error ( "Result classified as failure" ) ;
575- this . #handleFailure( syntheticError , currentState . state ) ;
575+ this . #handleFailure( syntheticError , currentState . state , currentTime ) ;
576576 return result ; // Still return the result, but record the failure
577577 }
578578
@@ -689,14 +689,16 @@ export class CircuitBreaker<T = unknown> {
689689 /**
690690 * Resolves the current state, handling automatic transitions.
691691 * OPEN → HALF_OPEN after cooldown expires.
692+ *
693+ * @param now Current timestamp in milliseconds.
692694 */
693- #resolveCurrentState( ) : CircuitBreakerState {
695+ #resolveCurrentState( now : number ) : CircuitBreakerState {
694696 if ( this . #state. state !== "open" ) {
695697 return this . #state;
696698 }
697699
698700 const cooldownEnd = this . #state. openedAt + this . #cooldownMs;
699- if ( Date . now ( ) < cooldownEnd ) {
701+ if ( now < cooldownEnd ) {
700702 return this . #state;
701703 }
702704
@@ -712,22 +714,30 @@ export class CircuitBreaker<T = unknown> {
712714 return this . #state;
713715 }
714716
715- /** Records a failure and potentially opens the circuit. */
716- #handleFailure( error : unknown , previousState : CircuitState ) : void {
717+ /**
718+ * Records a failure and potentially opens the circuit.
719+ *
720+ * @param error The error that occurred.
721+ * @param previousState The state before this failure.
722+ * @param now Current timestamp in milliseconds.
723+ */
724+ #handleFailure(
725+ error : unknown ,
726+ previousState : CircuitState ,
727+ now : number ,
728+ ) : void {
717729 // Check if this error should count as a failure
718730 if ( ! this . #isFailure( error ) ) {
719731 return ;
720732 }
721733
722- const currentTime = Date . now ( ) ;
723-
724734 // Prune old failures and add new one
725735 const prunedFailures = pruneOldFailures (
726736 this . #state. failureTimestamps ,
727737 this . #failureWindowMs,
728- currentTime ,
738+ now ,
729739 ) ;
730- const newFailures = [ ...prunedFailures , currentTime ] ;
740+ const newFailures = [ ...prunedFailures , now ] ;
731741
732742 this . #onFailure?.( error , newFailures . length ) ;
733743
@@ -737,7 +747,7 @@ export class CircuitBreaker<T = unknown> {
737747 ...this . #state,
738748 state : "open" ,
739749 failureTimestamps : newFailures ,
740- openedAt : currentTime ,
750+ openedAt : now ,
741751 consecutiveSuccesses : 0 ,
742752 } ;
743753 this . #onStateChange?.( "half_open" , "open" ) ;
@@ -751,7 +761,7 @@ export class CircuitBreaker<T = unknown> {
751761 ...this . #state,
752762 state : "open" ,
753763 failureTimestamps : newFailures ,
754- openedAt : currentTime ,
764+ openedAt : now ,
755765 consecutiveSuccesses : 0 ,
756766 } ;
757767 this . #onStateChange?.( "closed" , "open" ) ;
0 commit comments