Protect calls
Timeouts, fallbacks, cancellation, and patterns
protect() wraps a function call with breaker state checks, automatic metrics, optional timeouts, and fallback handling.
Basic usage
const customer = await openfuse.breaker('stripe').protect(
() => stripe.customers.retrieve(customerId),
)Before executing your function, protect() checks the breaker's state. If the breaker is closed or half-open, your function runs. If it's open, the behavior depends on whether you provided a fallback.
Add a timeout
const customer = await openfuse.breaker('stripe').protect(
(signal) => stripe.customers.retrieve(customerId, { signal }),
{ timeout: 3000 },
)If your function doesn't resolve within 3,000ms, the SDK throws a TimeoutError. The signal is an AbortSignal for cooperative cancellation with downstream libraries.
The timeout applies to your function only, not to the SDK's internal state check.
Add a fallback
const customer = await openfuse.breaker('stripe').protect(
() => stripe.customers.retrieve(customerId),
{ fallback: () => cachedCustomer },
)When the breaker is open, the fallback runs instead of your function. No error is thrown.
Fallbacks are not wrapped with a timeout or AbortSignal. If your fallback calls external services, apply your own timeout.
Timeout + fallback
const customer = await openfuse.breaker('stripe').protect(
(signal) => stripe.customers.retrieve(customerId, { signal }),
{
timeout: 3000,
fallback: () => cachedCustomer,
},
)The fallback only runs when the breaker is open. If the breaker is closed and your function times out, a TimeoutError is thrown. The fallback is not called on timeout.
Use AbortSignal for cancellation
const controller = new AbortController()
setTimeout(() => controller.abort(), 5000)
const customer = await openfuse.breaker('stripe').protect(
(signal) => stripe.customers.retrieve(customerId, { signal }),
{ signal: controller.signal },
)When you pass a signal option, it's combined with the timeout signal. If either signal aborts, the function is cancelled and an AbortOperationError is thrown.
Behavior when the breaker is open
| Fallback provided? | Behavior |
|---|---|
| Yes | Fallback executes. No error thrown. No metrics recorded. |
| No | Function executes anyway (fail-open) with a warning logged. Metrics are recorded. |
The SDK never silently drops requests. If there's no fallback, it executes your function even when the breaker is open.
Behavior on timeout
When a timeout is set and the function exceeds it:
- The
AbortSignalpassed to your function is triggered. - A
TimeoutErroris thrown. - The execution is recorded as a
timeoutin metrics.
Reuse breaker handles
If you call the same breaker frequently, save the handle to avoid repeated lookups:
const stripeBreaker = openfuse.breaker('stripe')
// Later, in your request handler:
const customer = await stripeBreaker.protect(
() => stripe.customers.retrieve(customerId),
)breaker() returns a lightweight BreakerHandle bound to the slug. No API call is made. Breakers are automatically created in Openfuse when first seen.
Real-world example
import { OpenfuseCloud, TimeoutError } from '@openfuseio/sdk'
const openfuse = new OpenfuseCloud({
system: 'payments',
clientId: 'YOUR_CLIENT_ID',
clientSecret: 'YOUR_CLIENT_SECRET',
})
openfuse.init()
async function getCustomer(customerId: string) {
try {
return await openfuse.breaker('stripe-get-customer').protect(
(signal) => stripe.customers.retrieve(customerId, { signal }),
{
timeout: 3000,
fallback: () => cachedCustomer,
},
)
} catch (error) {
if (error instanceof TimeoutError) {
return cachedCustomer
}
throw error
}
}