react-native-onyx
Version:
State management for React Native
92 lines (91 loc) • 4.33 kB
TypeScript
import type { CircuitBreakerOptions, CircuitBreakerState } from './types';
/**
* Generic circuit breaker built on {@link StateMachine}.
*
* - **closed**: requests are allowed; failures are counted.
* - **open**: requests are rejected until {@link resetTimeoutMs} elapses.
* - **half-open**: the recovery-probe state. After the open timeout, the breaker admits exactly ONE
* probe request: success means the dependency recovered, so the circuit closes. Failure means it's
* still down, so the circuit reopens. This single-request probe prevents a "thundering herd" where
* every caller fails loudly when the service hasn't recovered yet.
*
* Subclasses implement the failure-counting policy by overriding {@link recordFailureInClosed} (and
* friends) — e.g. counting consecutive failures, or failures within a rolling time window, or any
* combination of those.
*
* @example
* class MyBreaker extends AbstractCircuitBreaker {
* private failures = 0;
* protected recordFailureInClosed() {
* this.failures += 1;
* return this.failures >= 3 ? `${this.failures} failures` : null;
* }
* protected recordSuccessInClosed() { this.failures = 0; }
* protected resetFailureState() { this.failures = 0; }
* }
*
* const breaker = new MyBreaker({resetTimeoutMs: 30_000});
* if (breaker.isAllowed()) {
* try {
* doWork();
* breaker.recordSuccess();
* } catch {
* breaker.recordFailure();
* }
* }
*/
declare abstract class AbstractCircuitBreaker {
private machine;
private openedAt;
private isProbeInFlight;
private readonly resetTimeoutMs;
private readonly onTrip?;
private readonly onClose?;
constructor(options?: CircuitBreakerOptions);
/** Record a failure while the circuit is closed. Returns a trip reason when the threshold is exceeded. */
protected abstract recordFailureInClosed(): string | null;
/** Update failure state after a successful request while the circuit is closed. */
protected abstract recordSuccessInClosed(): void;
/** Clear accumulated failure state without changing circuit state. */
protected abstract resetFailureState(): void;
/**
* Whether a request may proceed.
*
* Returns `false` while open. In half-open, the FIRST caller is admitted as the recovery probe and
* `isProbeInFlight` is latched so every subsequent caller is rejected until that probe resolves
* (via {@link recordSuccess} → close, or {@link recordFailure} → reopen). That single-probe gate is
* the whole point of half-open: it tests recovery with one request instead of letting a herd of
* waiting callers stampede a dependency that may still be down.
*/
isAllowed(): boolean;
/**
* Record a failed request. May open the circuit from closed or half-open.
* @returns `true` when the circuit is open after recording (the request must not proceed).
*/
recordFailure(): boolean;
/** Record a successful request. Closes the circuit from half-open and clears failure counts. */
recordSuccess(): void;
/**
* The current state WITHOUT advancing recovery — a pure query, safe to call without side effects.
* The open→half-open transition is applied only at the admission point ({@link isAllowed}); by the
* time a caller queries state after being admitted, that transition has already happened.
*/
peekState(): CircuitBreakerState;
/**
* Force the circuit back to closed from ANY state. This is a reset, not a transition, so it
* deliberately bypasses the transition graph (and does not fire {@link onClose}). Use only to wipe
* all state — e.g. between tests or sessions.
*/
protected hardReset(): void;
private getCurrentState;
private trip;
private close;
/**
* Lazily advance open → half-open once the reset timeout has elapsed. This is checked on read
* (via {@link getCurrentState}) rather than on a timer, so there's nothing to schedule or clean up:
* the transition simply becomes visible to the next caller after the window. Entering half-open
* clears `isProbeInFlight` so the next admitted request becomes the recovery probe.
*/
private maybeRecover;
}
export default AbstractCircuitBreaker;