@mantleframework/resilience
Circuit breaker, retry, fetch timeout, and idempotency patterns for resilient Lambda functions.
CircuitBreaker
State machine-based circuit breaker with pluggable state persistence (in-memory or DynamoDB).
import { CircuitBreaker } from '@mantleframework/resilience'
const breaker = new CircuitBreaker({
name: 'external-api',
failureThreshold: 5,
successThreshold: 2,
resetTimeoutMs: 60000,
stateStore: 'dynamodb',
tableName: 'circuit-breaker-state',
})
const result = await breaker.execute(async () => {
return await callExternalAPI()
})CircuitBreakerConfig
interface CircuitBreakerConfig {
name: string // Unique circuit name
failureThreshold: number // Failures before OPEN (default: 5)
successThreshold: number // Successes in HALF_OPEN before CLOSED (default: 2)
resetTimeoutMs: number // Ms before OPEN transitions to HALF_OPEN (default: 60000)
stateStore: 'memory' | 'dynamodb' // State backend (default: 'memory')
tableName?: string // DynamoDB table (required when stateStore is 'dynamodb')
}States
| State | Description |
|---|---|
CLOSED | Normal operation, requests pass through |
OPEN | Failure threshold exceeded, requests rejected immediately |
HALF_OPEN | Testing recovery, limited requests allowed |
Methods
| Method | Returns | Description |
|---|---|---|
execute(operation) | Promise<T> | Run operation through the circuit breaker |
getState() | Promise<CircuitState> | Current state without executing an operation |
getFailureCount() | Promise<number> | Current cumulative failure count |
reset() | Promise<void> | Reset to CLOSED with zero counts |
CircuitBreakerOpenError
Thrown when calling execute() while the circuit is open and the reset timeout has not elapsed.
import { CircuitBreakerOpenError } from '@mantleframework/resilience'
try {
await breaker.execute(() => callAPI())
} catch (error) {
if (error instanceof CircuitBreakerOpenError) {
// error.circuitName — name of the circuit
// error.retryAfterMs — ms remaining before HALF_OPEN
}
}Retry
retryWithBackoff(operation, config?)
Execute an async function with exponential backoff retry. Uses the "full jitter" strategy recommended by AWS.
import { retryWithBackoff } from '@mantleframework/resilience'
const result = await retryWithBackoff(
async () => await fetchData(),
{
maxRetries: 3,
baseDelayMs: 1000,
maxDelayMs: 5000,
jitter: 0.5,
isRetryable: (error) => error instanceof TransientError,
},
)RetryConfig
interface RetryConfig {
maxRetries: number // Max retry attempts (default: 3)
baseDelayMs: number // Base delay in ms (default: 1000)
maxDelayMs: number // Max delay cap in ms (default: 30000)
jitter: number // Jitter factor 0-1; 0 = none, 1 = full (default: 1)
isRetryable?: (error: unknown) => boolean // Predicate for retryable errors
}calculateDelayWithJitter(attempt, baseDelayMs, maxDelayMs, jitter)
Calculate delay with exponential backoff and jitter.
import { calculateDelayWithJitter } from '@mantleframework/resilience'
const delay = calculateDelayWithJitter(2, 1000, 30000, 1)sleep(ms)
Promise-based sleep utility.
import { sleep } from '@mantleframework/resilience'
await sleep(1000)fetchWithTimeout(url, options?)
Fetch with an automatic timeout using AbortSignal.timeout(). If the caller provides their own signal, the timeout and caller signal are combined via AbortSignal.any().
import { fetchWithTimeout } from '@mantleframework/resilience'
const response = await fetchWithTimeout('https://api.example.com/data', {
timeoutMs: 5000,
method: 'POST',
body: JSON.stringify({ key: 'value' }),
headers: { 'Content-Type': 'application/json' },
})FetchWithTimeoutOptions
Extends RequestInit with:
| Option | Type | Default | Description |
|---|---|---|---|
timeoutMs | number | 15000 | Timeout in milliseconds |
Throws DOMException with name 'TimeoutError' when the timeout elapses.
Idempotency
Lambda idempotency powered by AWS Lambda Powertools. Ensures that retried or duplicate invocations produce the same result.
createIdempotencyStore(tableName?)
Create a DynamoDB-backed persistence store for idempotency state.
import { createIdempotencyStore } from '@mantleframework/resilience'
const store = createIdempotencyStore('my-idempotency-table')
// Falls back to IDEMPOTENCY_TABLE_NAME env var when tableName is omittedcreateIdempotencyConfig(options?)
Create an idempotency configuration with optional TTL.
import { createIdempotencyConfig } from '@mantleframework/resilience'
const config = createIdempotencyConfig({ expiresAfterSeconds: 7200 })
// Default: 3600 seconds (1 hour)makeIdempotent
Re-exported from @aws-lambda-powertools/idempotency. Wraps a handler or function to make it idempotent.
import { createIdempotencyStore, createIdempotencyConfig, makeIdempotent } from '@mantleframework/resilience'
const store = createIdempotencyStore()
const config = createIdempotencyConfig()
export const handler = makeIdempotent(async (event) => {
// This handler returns the cached result for duplicate invocations
return await processEvent(event)
}, {
persistenceStore: store,
config,
})Additional re-exports
| Export | Source | Description |
|---|---|---|
DynamoDBPersistenceLayer | @aws-lambda-powertools/idempotency/dynamodb | DynamoDB persistence adapter |
IdempotencyConfig | @aws-lambda-powertools/idempotency | Configuration class |