# `circuit-breaker` _(no submodules)_ ## Purpose `circuit-breaker.ts` exposes `CircuitBreaker`, a per-worker failure- isolating state machine the base manager uses to short-circuit `sendRequest` calls to workers that have failed `failureThreshold` consecutive times. The breaker transitions `closed → open` after the failure budget is exhausted, `open → half-open` once `resetMs` has elapsed, and either `closed` (on success) or back to `open` (on failure) from half-open. Adding a breaker bounds the blast radius of a flaky worker: instead of every request waiting for the full timeout, the breaker rejects them synchronously with `WEB_WORKERS.CIRCUIT_OPEN` and lets the cool-down do its job. The base manager calls `beforeRequest` / `recordSuccess` / `recordFailure` at the right inflection points so consumers get the behaviour for free by adding `circuitBreaker: { ... }` to their `WorkerConfig`. ## Features - Three-state state machine: `closed`, `open`, `half-open`. - Validates configuration (`failureThreshold > 0`, `resetMs >= 0`). - `beforeRequest` throws `WebWorkersError(CIRCUIT_OPEN)` when open and the cool-down hasn't elapsed; transitions to half-open when it has. - `recordSuccess` closes a half-open breaker and resets the consecutive failure counter. - `recordFailure` re-opens a half-open breaker immediately, or opens a closed breaker once the threshold is reached. - Injectable `Clock` for deterministic tests. ## Usage: Basic Example ```ts import { WorkerManager } from "@suaveplan/web-workers"; const manager = new WorkerManager({ factory }); manager.create({ name: "flaky", url: WORKER_URL, circuitBreaker: { failureThreshold: 3, resetMs: 30_000 }, }); ``` ## Usage: Advanced Example ```ts import { CircuitBreaker } from "@suaveplan/web-workers"; class GuardedTransport { private readonly breaker = new CircuitBreaker( { failureThreshold: 5, resetMs: 60_000 }, () => Date.now() ); async call(op: () => Promise): Promise { this.breaker.beforeRequest(); try { const result = await op(); this.breaker.recordSuccess(); return result; } catch (err) { this.breaker.recordFailure(); throw err; } } } ``` ## API Reference - `class CircuitBreaker`. - `constructor(config: CircuitBreakerConfig, clock: Clock)`. - `get current: CircuitState`. - `beforeRequest(): void` — throws `WebWorkersError(CIRCUIT_OPEN)` when open and cool-down not elapsed. - `recordSuccess(): void`. - `recordFailure(): void`. - `type CircuitState = "closed" | "open" | "half-open"`. ## Implementation Notes The breaker is single-instance per worker — the base manager creates it in `create()` and holds it on the `ManagedEntry`. Failures only count when consecutive, so a successful request mid-failure-window resets the counter. The half-open state allows exactly one probe request through; the next call to `recordFailure` re-opens the breaker AND resets the cool-down timer so the next probe is `resetMs` away. `Clock` is injected (rather than reading `Date.now` directly) so tests can advance time deterministically without `vi.useFakeTimers()` machinery.