/** * @module internal/circuit-breaker * * Orchestrator-level circuit breaker for store operations. The drain loop * polls `claim()` continuously; when the backing store goes down every poll * throws a {@link StoreError}, and without a breaker the orchestrator would * spin at the cycle cadence hammering a dead database and flooding the logs. * * The breaker collapses that into three states: * * - **closed** — normal operation; failures are counted. * - **open** — `failureThreshold` consecutive failures tripped it; attempts * are skipped for `cooldownMs` so the store gets room to recover. * - **half-open** — the cooldown elapsed; a single trial attempt is allowed. * Success closes the breaker; failure re-opens it and restarts the clock. * * Time is passed in (`now`) rather than read from the clock so the state * machine is deterministic under test. */ import type { CircuitBreakerConfig } from "./config.js"; /** Circuit breaker state. */ export type CircuitState = "closed" | "open" | "half-open"; /** * Side-effect hooks the orchestrator wires into the breaker so consumers * (drain / settle / autoclose) don't each thread their own callbacks. */ export type CircuitBreakerHooks = { /** Invoked on every {@link CircuitBreaker.failed} with the error and state. */ readonly on_error?: (error: unknown, circuit: CircuitState) => void; /** * Invoked once, `cooldownMs` after the breaker opens (and again on each * re-open). The orchestrator wires it to re-attempt a drain — so recovery * is automatic even on the default lane, which has no periodic poller. */ readonly on_retry?: () => void; }; export declare class CircuitBreaker { private _failures; private _opened_at; private _wake; private readonly _threshold; private readonly _cooldown_ms; private readonly _hooks; constructor(config: CircuitBreakerConfig, hooks?: CircuitBreakerHooks); /** * True when the store has failed at least once since the last pass that * went all the way through. The breaker can still read "closed" here: it * only opens after several failures in a row, so this catches the first * one, which `state()` cannot. */ get failing(): boolean; /** Current state given the wall-clock `now`. */ state(now: number): CircuitState; /** A store op passed — reset to closed and cancel any pending retry. */ passed(): void; /** * A store op failed. Opens the breaker when the consecutive-failure * threshold is reached, or immediately re-opens (restarting the cooldown) * if a half-open trial just failed. Returns the resulting state. * * Callers gate on the state directly — `state(now) === "open"` means skip. * On opening, schedules the `on_retry` wake so recovery is automatic; * always surfaces the failure via `on_error`. */ failed(now: number, error?: unknown): CircuitState; /** Cancel the pending retry timer. Idempotent; call on shutdown. */ stop(): void; /** * Schedule the `on_retry` wake `cooldownMs` out so the breaker re-trials * the store on its own. No-op when no `on_retry` hook is wired (e.g. unit * tests of the pure state machine). The timer is `unref`'d so it never * keeps the process alive. */ private _schedule_wake; private _clear_wake; /** Pure state transition for a failure — no side effects. */ private _advance; } //# sourceMappingURL=circuit-breaker.d.ts.map