Skip to content

@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).

typescript
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

typescript
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

StateDescription
CLOSEDNormal operation, requests pass through
OPENFailure threshold exceeded, requests rejected immediately
HALF_OPENTesting recovery, limited requests allowed

Methods

MethodReturnsDescription
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.

typescript
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.

typescript
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

typescript
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.

typescript
import { calculateDelayWithJitter } from '@mantleframework/resilience'

const delay = calculateDelayWithJitter(2, 1000, 30000, 1)

sleep(ms)

Promise-based sleep utility.

typescript
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().

typescript
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:

OptionTypeDefaultDescription
timeoutMsnumber15000Timeout 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.

typescript
import { createIdempotencyStore } from '@mantleframework/resilience'

const store = createIdempotencyStore('my-idempotency-table')
// Falls back to IDEMPOTENCY_TABLE_NAME env var when tableName is omitted

createIdempotencyConfig(options?)

Create an idempotency configuration with optional TTL.

typescript
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.

typescript
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

ExportSourceDescription
DynamoDBPersistenceLayer@aws-lambda-powertools/idempotency/dynamodbDynamoDB persistence adapter
IdempotencyConfig@aws-lambda-powertools/idempotencyConfiguration class