Skip to content

Service API

Inject CIRCUIT_BREAKER_SERVICE for full programmatic control when a decorator is not a good fit.

typescript
import { Injectable, Inject } from '@nestjs/common';
import { CIRCUIT_BREAKER_SERVICE, ICircuitBreakerService } from '@nestjs-redisx/circuit-breaker';
import { UsersApi, UserCache, User } from './types';

@Injectable()
export class UsersService {
  constructor(
    @Inject(CIRCUIT_BREAKER_SERVICE)
    private readonly breaker: ICircuitBreakerService,
    private readonly usersApi: UsersApi,
    private readonly userCache: UserCache,
  ) {}

  async getUser(id: string): Promise<User> {
    // Guard the call; serve a cached value if the breaker is OPEN.
    return this.breaker.execute(`users-api:${id}`, () => this.usersApi.getUser(id), {
      fallback: async () => {
        const cached = await this.userCache.get(id);
        return cached ?? { id, name: 'unknown' };
      },
    });
  }

  async status(id: string): Promise<string> {
    const snapshot = await this.breaker.getState(`users-api:${id}`);
    return snapshot.state; // 'closed' | 'open' | 'half-open'
  }

  async reportOutcome(id: string, ok: boolean): Promise<void> {
    // Manual recording (e.g. from a health probe) without execute().
    if (ok) {
      await this.breaker.recordSuccess(`users-api:${id}`);
    } else {
      await this.breaker.recordFailure(`users-api:${id}`);
    }
  }

  async clear(id: string): Promise<void> {
    await this.breaker.reset(`users-api:${id}`);
  }
}

ICircuitBreakerService

  • execute<T>(key, fn, options?): Promise<T> — run fn guarded by the breaker. On success records success; on throw records failure and rethrows. When the breaker rejects the call, returns options.fallback() if provided, otherwise throws the errorFactory error or CircuitBreakerOpenError.
  • recordSuccess(key, options?): Promise<ICircuitSnapshot> — manually record a success (e.g. from an external health probe).
  • recordFailure(key, options?): Promise<ICircuitSnapshot> — manually record a failure.
  • getState(key, options?): Promise<ICircuitSnapshot> — read the committed state without mutating it (does not flip OPEN → HALF_OPEN).
  • reset(key): Promise<void> — return the circuit to CLOSED and clear all state.

ICircuitSnapshot

typescript
interface ICircuitSnapshot {
  state: 'closed' | 'open' | 'half-open';
  failuresInWindow: number; // CLOSED: failures still inside the window
  halfOpenSuccesses: number; // HALF_OPEN: probes that have succeeded
  halfOpenInFlight: number; // HALF_OPEN: unresolved probes still within probeTimeoutMs
}

Execute options

ICircuitBreakerExecuteOptions extends the per-call overrides (failureThreshold, windowMs, openDurationMs, halfOpenMaxCalls, successThreshold, probeTimeoutMs) with:

  • fallback?: () => T | Promise<T> — returned instead of throwing when rejected.
  • errorFactory?: (key, snapshot) => Error — custom rejection error (overrides the plugin-level factory).

Store errors vs open rejections

A rejection because the breaker is OPEN always surfaces as a fallback/CircuitBreakerOpenError. A failure of the state store (Redis) on the execute() gate is governed by errorPolicyfail-open runs fn anyway, fail-closed throws CircuitBreakerStoreError. Recording success/failure never masks your function's own result or error.

The manual API is always strict

recordSuccess, recordFailure, getState, and reset are not subject to errorPolicy: when the state store fails they always throw CircuitBreakerStoreError. There is no meaningful "fail-open" result for an explicit state operation — silently dropping a manual recordFailure would corrupt operator expectations.

Invalid configuration always throws

Plugin options and per-call overrides are validated (integers ≥ 1, successThreshold <= halfOpenMaxCalls). An invalid config throws InvalidCircuitBreakerConfigError immediately — it is a programmer error and is never subject to errorPolicy.

Released under the MIT License.