breaker-box
Version: 
A zero-dependency circuit breaker implementation for Node.js
165 lines (117 loc) • 4.42 kB
Markdown

# Breaker Box
A zero-dependency [circuit breaker][0] implementation for Node.js.
[0]: https://martinfowler.com/bliki/CircuitBreaker.html
## Installation
```bash
npm install breaker-box
```
## Usage
### Basic Usage
```typescript
import { createCircuitBreaker } from "breaker-box"
// Wrap an unreliable async function
async function unreliableApiCall(data: string) {
	const response = await fetch(`https://api.example.com/data/${data}`)
	if (!response.ok) throw new Error("API call failed")
	return response.json()
}
const protectedApiCall = createCircuitBreaker(unreliableApiCall, {
	errorIsFailure: (error) => error.message.includes("404"), // Don't retry 404s
	errorThreshold: 0.5, // Open circuit when 50% of calls fail
	errorWindow: 10_000, // Track errors over 10 second window
	minimumCandidates: 6, // Need at least 6 calls before calculating error rate
	resetAfter: 30_000, // Try again after 30 seconds
})
try {
	const result = await protectedApiCall("user-123")
	console.log("Success:", result)
} catch (error) {
	console.error("Circuit breaker error:", error.message)
}
```
### Retry Strategies
```typescript
import {
	createCircuitBreaker,
	useExponentialBackoff,
	useFibonacciBackoff,
} from "breaker-box"
// Exponential backoff: 1s, 2s, 4s, 8s, up to 30s max
const protectedWithExponential = createCircuitBreaker(unreliableApiCall, {
	retryDelay: useExponentialBackoff(30),
})
// Fibonacci backoff: 1s, 2s, 3s, 5s, 8s, up to 60s max
const protectedWithFibonacci = createCircuitBreaker(unreliableApiCall, {
	retryDelay: useFibonacciBackoff(60),
})
```
### Timeout Protection
```typescript
import { createCircuitBreaker, withTimeout } from "breaker-box"
// Wrap function with 5-second timeout
const timeoutProtectedCall = withTimeout(
	unreliableApiCall,
	5_000,
	"Request timed out",
)
const protectedApiCall = createCircuitBreaker(timeoutProtectedCall, {})
```
### Event Monitoring
```typescript
const protectedFunction = createCircuitBreaker(unreliableApiCall, {
	onClose: () => {
		console.log("Circuit closed - normal operation resumed")
	},
	onOpen: (cause) => {
		console.log("Circuit opened due to:", cause.message)
	},
})
// Check current state
console.log("Current state:", protectedFunction.getState())
// Possible states: 'closed', 'open', 'halfOpen'
```
### Cleanup
```typescript
// Clean up resources when shutting down
protectedFunction.dispose()
```
## API
### `createCircuitBreaker(fn, options?)`
Creates a circuit breaker around the provided async function.
#### Parameters
- `fn`: The async function to protect
- `options`: Configuration object (optional)
  - `errorIsFailure`: Function to determine if an error is non-retryable (default: `() => false`)
  - `errorThreshold`: Percentage (0-1) of errors that triggers circuit opening (default: `0`)
  - `errorWindow`: Time window in ms for tracking errors (default: `10_000`)
  - `fallback`: Function to call when circuit is open (default: undefined)
  - `minimumCandidates`: Minimum calls before calculating error rate (default: `6`)
  - `onClose`: Function called when circuit closes (default: undefined)
  - `onOpen`: Function called when circuit opens (default: undefined)
  - `resetAfter`: Milliseconds to wait before trying half-open (default: `30_000`)
  - `retryDelay`: Function returning promise for retry delays (default: immediate retry)
#### Returns
A function with the same signature as `fn` and additional methods:
- `.dispose(message?)`: Clean up resources and reject future calls
- `.getLatestError()`: Returns the error which triggered the circuit breaker
- `.getState()`: Returns current circuit state (`'closed'`, `'open'`, `'halfOpen'`)
### Helper Functions
#### `useExponentialBackoff(maxSeconds)`
Returns a retry delay function that implements exponential backoff (2^n seconds, capped at maxSeconds).
#### `useFibonacciBackoff(maxSeconds)`
Returns a retry delay function that implements Fibonacci backoff (Fibonacci sequence in seconds, capped at maxSeconds).
#### `withTimeout(fn, timeoutMs, message?)`
Wraps a function with a timeout. Rejects with `Error(message)` if execution exceeds `timeoutMs`.
### Development
### Building the Project
```sh
npm run build
```
### Running Tests
```sh
npm test # once
npm run dev # run and watch for file changes
```
## Contributing
Contributions are welcome! Please open an issue or submit a pull request.