Skip to main content

Documentation Index

Fetch the complete documentation index at: https://mintlify.com/visible/cruel/llms.txt

Use this file to discover all available pages before exploring further.

Overview

The Circuit Breaker pattern prevents cascading failures by monitoring function errors and “opening” the circuit when a failure threshold is reached. This gives failing services time to recover while preventing resource exhaustion.

When to Use

  • Calling external APIs or microservices that may fail
  • Protecting against cascading failures in distributed systems
  • Preventing resource exhaustion from repeated failed requests
  • Services that need time to recover from overload

How It Works

The circuit breaker has three states:
1

Closed (Normal)

All requests pass through. Failures are counted.
2

Open (Blocking)

When failures reach the threshold, the circuit opens and all requests fail immediately with CRUEL_CIRCUIT_OPEN.
3

Half-Open (Testing)

After the timeout period, one request is allowed through to test if the service has recovered.

API Reference

Function Signature

function createCircuitBreaker<T extends AnyFn>(
  fn: T,
  options: CircuitBreakerOptions
): T & { getState: () => CircuitBreakerState; reset: () => void }

Options

threshold
number
required
Number of consecutive failures before opening the circuit. Must be at least 1.
timeout
number
required
Time in milliseconds to wait before attempting to close the circuit. Must be at least 1.
onOpen
() => void
Callback function executed when the circuit opens.
onClose
() => void
Callback function executed when the circuit closes after recovery.
onHalfOpen
() => void
Callback function executed when the circuit enters half-open state.

Return Type

Returns the wrapped function with additional methods:
getState
() => CircuitBreakerState
Returns the current circuit breaker state including:
  • failures: Number of consecutive failures
  • state: Current state (“closed” | “open” | “half-open”)
  • lastFailure: Timestamp of the last failure
reset
() => void
Manually reset the circuit breaker to closed state with zero failures.

Examples

Basic Usage

import { createCircuitBreaker } from 'cruel'

const fetchUser = async (id: string) => {
  const response = await fetch(`https://api.example.com/users/${id}`)
  return response.json()
}

const protectedFetch = createCircuitBreaker(fetchUser, {
  threshold: 5,      // Open after 5 failures
  timeout: 30000,    // Try again after 30 seconds
  onOpen: () => console.log('Circuit opened - service is down'),
  onClose: () => console.log('Circuit closed - service recovered'),
})

// Use it like the original function
try {
  const user = await protectedFetch('123')
} catch (error) {
  if (error.code === 'CRUEL_CIRCUIT_OPEN') {
    console.log('Service unavailable, circuit is open')
  }
}

Monitoring Circuit State

const protectedFetch = createCircuitBreaker(fetchUser, {
  threshold: 3,
  timeout: 10000,
})

// Check current state
const state = protectedFetch.getState()
console.log(`State: ${state.state}, Failures: ${state.failures}`)

// Manually reset if needed
if (state.state === 'open') {
  console.log('Manually resetting circuit breaker')
  protectedFetch.reset()
}

With State Callbacks

let isServiceHealthy = true

const apiCall = createCircuitBreaker(fetchData, {
  threshold: 5,
  timeout: 60000,
  onOpen: () => {
    isServiceHealthy = false
    // Trigger alerts, switch to fallback service, etc.
    sendAlert('API service is down')
  },
  onHalfOpen: () => {
    console.log('Testing if service has recovered...')
  },
  onClose: () => {
    isServiceHealthy = true
    console.log('Service recovered successfully')
  },
})

Database Connection Example

const queryDatabase = async (query: string) => {
  const connection = await pool.getConnection()
  try {
    return await connection.query(query)
  } finally {
    connection.release()
  }
}

const protectedQuery = createCircuitBreaker(queryDatabase, {
  threshold: 10,     // Allow 10 failures before opening
  timeout: 5000,     // Try to reconnect after 5 seconds
  onOpen: () => {
    console.error('Database connection circuit opened')
    metrics.increment('circuit_breaker.database.open')
  },
})

Combining with Other Patterns

Circuit Breaker + Retry

import { createCircuitBreaker, withRetry } from 'cruel'

// First wrap with retry, then circuit breaker
const resilientFetch = createCircuitBreaker(
  withRetry(fetchData, {
    attempts: 3,
    delay: 1000,
    backoff: 'exponential',
  }),
  {
    threshold: 5,
    timeout: 30000,
  }
)

Circuit Breaker + Fallback

import { createCircuitBreaker, withFallback } from 'cruel'

const getConfig = async () => {
  return fetch('https://api.example.com/config').then(r => r.json())
}

const resilientConfig = withFallback(
  createCircuitBreaker(getConfig, {
    threshold: 3,
    timeout: 60000,
  }),
  {
    fallback: { mode: 'safe', features: [] },
    onFallback: (error) => {
      console.log('Using fallback config:', error.message)
    },
  }
)

With Compose

import { cruel } from 'cruel'

const resilientAPI = cruel.compose(fetchData, {
  circuitBreaker: {
    threshold: 5,
    timeout: 30000,
  },
  retry: {
    attempts: 3,
    backoff: 'exponential',
  },
  timeoutMs: 5000,
})

Error Handling

The circuit breaker throws CruelError with code CRUEL_CIRCUIT_OPEN when the circuit is open:
import { CruelError } from 'cruel'

try {
  await protectedFetch()
} catch (error) {
  if (error instanceof CruelError && error.code === 'CRUEL_CIRCUIT_OPEN') {
    // Circuit is open - service is down
    return fallbackResponse
  }
  throw error
}

Best Practices

  • Choose appropriate thresholds: Too low and you’ll open prematurely, too high and you’ll let too many failures through
  • Set reasonable timeouts: Give the service enough time to recover, but not so long that users wait indefinitely
  • Monitor circuit state: Use callbacks to track when circuits open/close for observability
  • Combine with retries: Use retries for transient failures, circuit breakers for sustained issues
  • Use fallbacks: Provide graceful degradation when the circuit is open
  • Test recovery: Ensure your service can actually recover when the circuit closes

Configuration Tips

ScenarioThresholdTimeoutReasoning
External API5-1030-60sAPIs may have rate limits or temporary issues
Database3-55-10sDatabase issues often resolve quickly or need intervention
Microservice10-2010-30sInternal services may have higher tolerance
Critical service2-360s+Fail fast and give more recovery time

Build docs developers (and LLMs) love