npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@beethovn/circuit-breaker

v1.0.0

Published

Circuit breaker pattern implementation with failure tracking and automatic recovery

Readme

@beethovn/circuit-breaker

Circuit breaker pattern implementation with retry policies and failure tracking for the Beethovn monorepo.

Features

  • Circuit Breaker Pattern: Prevent cascading failures with automatic circuit opening/closing
  • State Machine: CLOSED → OPEN → HALF_OPEN state transitions
  • Failure Tracking: Rolling window-based failure rate calculation
  • Retry Policies: Exponential backoff with multiple jitter strategies
  • Error Filtering: Configurable error filtering for fine-grained control
  • Event Emission: Listen to state change events
  • Resilient Executor: Combine circuit breaker and retry in one wrapper

Installation

pnpm add @beethovn/circuit-breaker

Quick Start

Basic Circuit Breaker

import { CircuitBreaker } from '@beethovn/circuit-breaker';

const breaker = new CircuitBreaker({
  name: 'payment-api',
  failureThreshold: 5,      // Open after 5 failures
  successThreshold: 2,       // Close after 2 successes in half-open
  timeout: 60000,            // Try half-open after 60s
  rollingWindowSize: 10000,  // 10s rolling window
  minimumRequests: 5,        // Minimum requests before evaluation
});

try {
  const result = await breaker.execute(() => paymentApi.charge(amount));
  console.log('Success:', result);
} catch (error) {
  console.error('Failed:', error);
}

Retry Policy

import { RetryPolicy, RetryPolicyPresets } from '@beethovn/circuit-breaker';

//Use preset
const policy = RetryPolicyPresets.conservative();

// Or custom configuration
const customPolicy = new RetryPolicy({
  maxAttempts: 3,
  initialDelay: 1000,
  maxDelay: 30000,
  backoffMultiplier: 2,
  useJitter: true,
});

const result = await policy.execute(() => fetchData());

Resilient Executor (Combined)

import { ResilientExecutor } from '@beethovn/circuit-breaker';

const executor = new ResilientExecutor({
  circuitBreaker: {
    name: 'external-api',
    failureThreshold: 5,
    timeout: 60000,
  },
  retry: {
    maxAttempts: 3,
    initialDelay: 1000,
  },
});

const result = await executor.execute(() => externalApi.call());

Circuit Breaker Pattern

States

CLOSED (Normal operation)
   ↓ (failures ≥ threshold)
OPEN (Reject immediately)
   ↓ (after timeout)
HALF_OPEN (Test recovery)
   ↓ (successes ≥ threshold)    ↓ (any failure)
CLOSED                          OPEN

State Transitions

| From | To | Trigger | |------|------------|---------| | CLOSED | OPEN | Failure threshold exceeded | | OPEN | HALF_OPEN | Timeout period elapsed | | HALF_OPEN | CLOSED | Success threshold met | | HALF_OPEN | OPEN | Any failure occurs |

Usage Examples

Monitor State Changes

const breaker = new CircuitBreaker({
  name: 'database',
  failureThreshold: 3,
  timeout: 30000,
});

breaker.onStateChange((event) => {
  console.log(`Circuit ${event.name}: ${event.from} → ${event.to}`);
  console.log('Stats:', event.stats);
  
  // Alert if circuit opened
  if (event.to === CircuitState.OPEN) {
    alerting.sendAlert(`Circuit ${event.name} opened!`);
  }
});

Custom Error Filtering

const breaker = new CircuitBreaker({
  name: 'api',
  failureThreshold: 5,
  errorFilter: (error) => {
    // Only count 5xx errors, ignore 4xx
    if (error instanceof HttpError) {
      return error.status >= 500;
    }
    return true;
  },
});

Retry with Custom Filter

const policy = new RetryPolicy({
  maxAttempts: 3,
  retryableErrorFilter: (error) => {
    // Don't retry on 4xx errors
    if (error instanceof HttpError) {
      return error.status >= 500;
    }
    return true;
  },
});

Get Circuit Statistics

const stats = breaker.getStats();

console.log({
  state: stats.state,
  totalSuccesses: stats.totalSuccesses,
  totalFailures: stats.totalFailures,
  totalRejections: stats.totalRejections,
  failureRate: stats.failureRate,
  timeUntilRetry: stats.timeUntilRetry,
});

Jitter Strategies

import { JitterStrategy } from '@beethovn/circuit-breaker';

const policy = new RetryPolicy({ maxAttempts: 3 });

// Full jitter: random(0, delay)
policy.setJitterStrategy(JitterStrategy.FULL);

// Equal jitter: delay/2 + random(0, delay/2)
policy.setJitterStrategy(JitterStrategy.EQUAL);

// No jitter
policy.setJitterStrategy(JitterStrategy.NONE);

Retry Presets

// Conservative: 3 attempts, 1s initial, 10s max
const conservative = RetryPolicyPresets.conservative();

// Aggressive: 5 attempts, 500ms initial, 5s max
const aggressive = RetryPolicyPresets.aggressive();

// Patient: 4 attempts, 2s initial, 30s max
const patient = RetryPolicyPresets.patient();

// Quick: 2 attempts, 100ms initial, 1s max
const quick = RetryPolicyPresets.quick();

Create Resilient Function

import { createResilient } from '@beethovn/circuit-breaker';

const resilientFetch = createResilient(
  () => fetch(url),
  {
    circuitBreaker: {
      name: 'fetch-api',
      failureThreshold: 5,
      timeout: 60000,
    },
    retry: {
      maxAttempts: 3,
      initialDelay: 1000,
    },
  }
);

// Use like a normal function
const data = await resilientFetch();

API Reference

CircuitBreaker

class CircuitBreaker {
  constructor(config: CircuitBreakerConfig);
  
  async execute<T>(fn: () => Promise<T>, options?: ExecuteOptions): Promise<T>;
  getStats(): CircuitBreakerStats;
  getState(): CircuitState;
  isOpen(): boolean;
  reset(): void;
  onStateChange(listener: (event: CircuitStateChangeEvent) => void): void;
  removeStateChangeListener(listener: (event: CircuitStateChangeEvent) => void): void;
}

CircuitBreakerConfig

interface CircuitBreakerConfig {
  name: string;
  failureThreshold: number;      // default: 5
  successThreshold: number;       // default: 2
  timeout: number;                // default: 60000 (60s)
  rollingWindowSize: number;      // default: 10000 (10s)
  minimumRequests: number;        // default: 5
  errorFilter?: (error: unknown) => boolean;
  enableLogging?: boolean;        // default: true
}

RetryPolicy

class RetryPolicy {
  constructor(config?: Partial<RetryPolicyConfig>);
  
  async execute<T>(fn: () => Promise<T>): Promise<T>;
  setJitterStrategy(strategy: JitterStrategy): void;
  getConfig(): Required<RetryPolicyConfig>;
}

RetryPolicyConfig

interface RetryPolicyConfig {
  maxAttempts: number;            // default: 3
  initialDelay: number;           // default: 1000 (1s)
  maxDelay: number;               // default: 30000 (30s)
  backoffMultiplier: number;      // default: 2
  useJitter: boolean;             // default: true
  retryableErrorFilter?: (error: unknown) => boolean;
}

ResilientExecutor

class ResilientExecutor {
  constructor(config: ResilienceConfig);
  
  async execute<T>(fn: () => Promise<T>): Promise<T>;
  getCircuitBreaker(): CircuitBreaker;
  getRetryPolicy(): RetryPolicy | undefined;
}

Integration with Other Packages

With @beethovn/errors

import { ErrorFactory } from '@beethovn/errors';
import { CircuitBreaker } from '@beethovn/circuit-breaker';

const breaker = new CircuitBreaker({ name: 'api', failureThreshold: 5 });

try {
  await breaker.execute(() => apiCall());
} catch (error: unknown) {
  const err = ErrorFactory.fromUnknown(error);
  logger.error('Circuit breaker execution failed', err);
}

With @beethovn/logging

import { Logger } from '@beethovn/logging';
import { CircuitBreaker, CircuitState } from '@beethovn/circuit-breaker';

const logger = new Logger({ service: 'payment-service' });
const breaker = new CircuitBreaker({ name: 'payment-api', failureThreshold: 5 });

breaker.onStateChange((event) => {
  if (event.to === CircuitState.OPEN) {
    logger.error('Circuit breaker opened', {
      circuit: event.name,
      stats: event.stats,
    });
  } else if (event.to === CircuitState.CLOSED) {
    logger.info('Circuit breaker closed', {
      circuit: event.name,
    });
  }
});

Best Practices

1. Choose Appropriate Thresholds

// ✅ Good: Balanced thresholds
const breaker = new CircuitBreaker({
  name: 'external-api',
  failureThreshold: 5,        // Open after 5 failures
  successThreshold: 2,         // Close after 2 successes
  timeout: 60000,              // Wait 60s before retry
  minimumRequests: 5,          // Need 5 requests min
});

// ❌ Bad: Too sensitive
const badBreaker = new CircuitBreaker({
  name: 'api',
  failureThreshold: 1,        // Opens after single failure
  successThreshold: 10,        // Needs 10 successes
});

2. Use Error Filters

// ✅ Good: Only count real failures
const breaker = new CircuitBreaker({
  name: 'api',
  failureThreshold: 5,
  errorFilter: (error) => {
    // Don't count validation errors (4xx)
    if (error instanceof ValidationError) return false;
    if (error instanceof NotFoundError) return false;
    return true;
  },
});

3. Monitor State Changes

// ✅ Good: Monitor and alert
breaker.onStateChange((event) => {
  metrics.record(`circuit.${event.name}.state`, event.to);
  
  if (event.to === CircuitState.OPEN) {
    alerting.critical(`Circuit ${event.name} opened`);
  }
});

4. Combine with Retry Wisely

// ✅ Good: Circuit breaker wraps retry
const executor = new ResilientExecutor({
  circuitBreaker: {
    name: 'api',
    failureThreshold: 5,      // Circuit level
  },
  retry: {
    maxAttempts: 2,            // Few retries
    initialDelay: 100,         // Quick retries
  },
});

// ❌ Bad: Too many retries can trigger circuit
const badExecutor = new ResilientExecutor({
  circuitBreaker: {
    failureThreshold: 3,
  },
  retry: {
    maxAttempts: 10,          // Will trigger circuit quickly
  },
});

5. Use Jitter in Production

// ✅ Good: Jitter prevents thundering herd
const policy = new RetryPolicy({
  maxAttempts: 3,
  useJitter: true,            // Randomize delays
});

// ❌ Bad: No jitter in distributed system
const badPolicy = new RetryPolicy({
  maxAttempts: 3,
  useJitter: false,           // All clients retry at same time
});

Exponential Backoff Calculation

Delay Formula

baseDelay = initialDelay × (backoffMultiplier ^ (attempt - 1))
finalDelay = min(baseDelay, maxDelay)

Example (initialDelay=1000, multiplier=2, maxDelay=30000)

| Attempt | Base Delay | With Jitter (Full) | |---------|------------|-------------------| | 1 | 1000ms | random(0, 1000) | | 2 | 2000ms | random(0, 2000) | | 3 | 4000ms | random(0, 4000) | | 4 | 8000ms | random(0, 8000) | | 5 | 16000ms | random(0, 16000) | | 6 | 30000ms (capped) | random(0, 30000) |

Error Handling

import { CircuitBreakerOpenError } from '@beethovn/circuit-breaker';

try {
  await breaker.execute(() => operation());
} catch (error) {
  if (error instanceof CircuitBreakerOpenError) {
    // Circuit is open, service is down
    console.log('Service unavailable, try again in:', error.metadata?.timeUntilRetry);
  } else {
    // Operation failed
    console.error('Operation error:', error);
  }
}

Performance Considerations

  • Rolling Window: Old requests are cleaned automatically, minimal memory footprint
  • State Checks: O(1) complexity for state checks
  • Failure Tracking: O(n) where n = requests in window (typically small)
  • Logging: Disable with enableLogging: false for high-throughput scenarios

Testing

import { describe, it, expect, vi } from 'vitest';
import { CircuitBreaker, CircuitState } from '@beethovn/circuit-breaker';

describe('MyService', () => {
  it('should handle circuit breaker', async () => {
    const breaker = new CircuitBreaker({
      name: 'test',
      failureThreshold: 2,
    });

    const fn = vi.fn().mockRejectedValue(new Error('fail'));

    // Trigger failures
    try { await breaker.execute(fn); } catch {}
    try { await breaker.execute(fn); } catch {}

    expect(breaker.getState()).toBe(CircuitState.OPEN);
  });
});

Migration Guide

From Manual Retry Logic

// Before: Manual retry
async function fetchWithRetry() {
  let attempt = 0;
  while (attempt < 3) {
    try {
      return await fetch(url);
    } catch (error) {
      attempt++;
      if (attempt >= 3) throw error;
      await sleep(1000 * Math.pow(2, attempt));
    }
  }
}

// After: RetryPolicy
const policy = new RetryPolicy({ maxAttempts: 3 });
const result = await policy.execute(() => fetch(url));

From Simple Circuit Breaker

// Before: Manual circuit breaker
let failures = 0;
let isOpen = false;

async function callApi() {
  if (isOpen) throw new Error('Circuit open');
  
  try {
    const result = await api.call();
    failures = 0;
    return result;
  } catch (error) {
    failures++;
    if (failures >= 5) isOpen = true;
    throw error;
  }
}

// After: CircuitBreaker
const breaker = new CircuitBreaker({
  name: 'api',
  failureThreshold: 5,
});

const result = await breaker.execute(() => api.call());

TypeScript Support

Full TypeScript support with strict types:

import type {
  CircuitBreakerConfig,
  CircuitBreakerStats,
  CircuitStateChangeEvent,
  RetryPolicyConfig,
  ResilienceConfig,
} from '@beethovn/circuit-breaker';

License

MIT


Package Version: 1.0.0
Dependencies: @beethovn/errors, @beethovn/logging
Node Version: >= 18.0.0