Skip to content
Buy me a coffee

Serverless (Lambda, Workers)

The problem. A circuit breaker learns from traffic, and serverless keeps throwing that knowledge away:

  • Cold starts reset everything. A new instance starts with a closed circuit, a full token bucket and an empty failure history.
  • State is per instance. Lambda runs one request at a time per instance. With 100 warm instances, each one sees 1% of the traffic, so each one needs its own run of failures before its circuit opens: 100× the failed calls a single server would make.
  • Cold starts look like failures. The first calls on a new instance pay for TLS handshakes, connection setup and a cold JIT. A breaker with consecutiveFailures: 3 can open on a perfectly healthy dependency.
  • Invocations have a hard deadline. Lambda kills the invocation at its timeout. Work still running then is lost, with no error handling.

The tools: snapshot() / initialState to carry state between instances, sharedCircuitState to share it live, warmUpDuration for cold starts, and withTimeout around the handler for the deadline.

Pipelines are stateful. Build them at module scope (or lazily on first use) so warm invocations reuse the same breaker and limiters. A pipeline built inside the handler starts from scratch on every request: its breaker can never open.

// Wrong: a new breaker per invocation.
export const handler = async () => {
const payments = new ResiliencePipelineBuilder().addCircuitBreaker().build();
…
};

CircuitBreakerStateProvider.snapshot() returns the breaker’s state as plain JSON: state, when an open circuit lets a probe through, the consecutive-failure count and the rolling window. Pass it back as initialState and a new instance resumes where the old one stopped. A circuit that was half-open resumes as open and due for a probe, because the probe that was in flight is gone.

import {
class CircuitBreakerStateProvider

Reads the state and window metrics of a single circuit breaker (health checks, dashboards, persistence).

CircuitBreakerStateProvider
,
class ResiliencePipelineBuilder<T = any, E = never, C extends string = never>

Polly-style builder with a shortcut for every strategy: addRetry(), addTimeout(), addCircuitBreaker(), … Convenient, but it bundles every strategy. For the smallest browser bundle use PipelineBuilder with retryStrategy(), timeoutStrategy(), … instead; the two are interchangeable.

The type parameters track which errors the pipeline can raise, for pipeline.settle() + Outcome.match().

ResiliencePipelineBuilder
, type
(alias) interface CircuitSnapshot
import CircuitSnapshot

JSON-serializable breaker state, for persisting across restarts and serverless invocations.

CircuitSnapshot
} from '@ylcnfrht/resilience';
interface
interface Env
Env
{
Env.BREAKERS: KVNamespace
BREAKERS
:
interface KVNamespace
KVNamespace
;
}
const
const KEY: "breaker:payments"
KEY
= 'breaker:payments';
const
const state: CircuitBreakerStateProvider
state
= new
new CircuitBreakerStateProvider(): CircuitBreakerStateProvider

Reads the state and window metrics of a single circuit breaker (health checks, dashboards, persistence).

CircuitBreakerStateProvider
();
let
let changed: boolean
changed
= false; // set by the breaker, flushed at the end of the request that changed it
function
function buildPayments(saved: CircuitSnapshot | undefined): ResiliencePipeline<Response, WithErrors<BrokenCircuitError, never, TimeoutRejectedError>>
buildPayments
(
saved: CircuitSnapshot | undefined
saved
:
(alias) interface CircuitSnapshot
import CircuitSnapshot

JSON-serializable breaker state, for persisting across restarts and serverless invocations.

CircuitSnapshot
| undefined) {
return new
new ResiliencePipelineBuilder<Response, never, never>(options?: ResiliencePipelineBuilderOptions): ResiliencePipelineBuilder<Response, never, never>

Polly-style builder with a shortcut for every strategy: addRetry(), addTimeout(), addCircuitBreaker(), … Convenient, but it bundles every strategy. For the smallest browser bundle use PipelineBuilder with retryStrategy(), timeoutStrategy(), … instead; the two are interchangeable.

The type parameters track which errors the pipeline can raise, for pipeline.settle() + Outcome.match().

ResiliencePipelineBuilder
<
interface Response

The Response interface of the Fetch API represents the response to a request.

MDN Reference

Response
>({
ResiliencePipelineBuilderOptions.name?: string | undefined

Labels telemetry events.

name
: 'payments' })
.
ResiliencePipelineBuilder<Response, never, never>.addRetry(options?: RetryOptions<Response>): ResiliencePipelineBuilder<Response, never, never>
addRetry
({
RetryOptions<Response>.maxRetryAttempts?: number | undefined

Retries in addition to the original call. Default 3. Infinity retries until aborted.

maxRetryAttempts
: 2,
RetryOptions<Response>.delay?: Duration | undefined

Base delay. Default 2000 ms.

delay
: '200ms',
RetryOptions<Response>.backoffType?: BackoffType | undefined

Default 'constant'.

backoffType
: 'exponential',
RetryOptions<Response>.useJitter?: Jitter | undefined
useJitter
: true })
.
ResiliencePipelineBuilder<Response, never, never>.addCircuitBreaker(options?: CircuitBreakerOptions<Response>): Next<Response, never, never, BrokenCircuitError>
addCircuitBreaker
({
CircuitBreakerOptions<Response>.consecutiveFailures?: number | undefined

Break after this many failures in a row (a success resets the count). Simple and good for low traffic. When set without failureRatio, the ratio check is off and only this rule applies.

consecutiveFailures
: 5, // per-isolate traffic is low: a ratio over 100 calls would never trip
CircuitBreakerOptions<Response>.breakDuration?: Duration | undefined

How long the circuit stays open. Default 5 s.

breakDuration
: '30s',
CircuitBreakerOptions<Response>.warmUpDuration?: Duration | undefined

Don't open during this period after creation (the window still fills), so a cold start's first errors can't trip it.

warmUpDuration
: '10s',
CircuitBreakerOptions<Response>.stateProvider?: CircuitBreakerStateProvider | undefined
stateProvider
:
const state: CircuitBreakerStateProvider
state
,
CircuitBreakerOptions<Response>.initialState?: CircuitSnapshot | undefined

Resume from a snapshot taken with stateProvider.snapshot(), e.g. after a serverless cold start or a restart.

initialState
:
saved: CircuitSnapshot | undefined
saved
,
CircuitBreakerOptions<Response>.onOpened?: ((args: OnCircuitOpenedArgs<Response>) => MaybePromise<void>) | undefined
onOpened
: () => void (
let changed: boolean
changed
= true),
CircuitBreakerOptions<Response>.onClosed?: ((args: OnCircuitClosedArgs<Response>) => MaybePromise<void>) | undefined
onClosed
: () => void (
let changed: boolean
changed
= true),
})
.
ResiliencePipelineBuilder<Response, BrokenCircuitError, never>.addTimeout(options: Duration | TimeoutOptions): Next<Response, BrokenCircuitError, never, TimeoutRejectedError>

addTimeout(5000), addTimeout('5s') or addTimeout({ timeout: '5s', onTimeout }).

addTimeout
('3s')
.
ResiliencePipelineBuilder<Response, WithErrors<BrokenCircuitError, never, TimeoutRejectedError>, never>.build(): ResiliencePipeline<Response, WithErrors<BrokenCircuitError, never, TimeoutRejectedError>>
build
();
}
// One build per isolate. Concurrent first requests share the same promise.
let
let payments: Promise<ResiliencePipeline<Response, WithErrors<BrokenCircuitError, never, TimeoutRejectedError>>> | undefined
payments
:
interface Promise<T>

Represents the completion of an asynchronous operation

Promise
<
type ReturnType<T extends (...args: any) => any> = T extends (...args: any) => infer R ? R : any

Obtain the return type of a function type

ReturnType
<typeof
function buildPayments(saved: CircuitSnapshot | undefined): ResiliencePipeline<Response, WithErrors<BrokenCircuitError, never, TimeoutRejectedError>>
buildPayments
>> | undefined;
export default {
async
function fetch(request: Request, env: Env, ctx: ExecutionContext): Promise<Response>
fetch
(
request: Request
request
:
interface Request

The Request interface of the Fetch API represents a resource request.

MDN Reference

Request
,
env: Env
env
:
interface Env
Env
,
ctx: ExecutionContext
ctx
:
interface ExecutionContext
ExecutionContext
):
interface Promise<T>

Represents the completion of an asynchronous operation

Promise
<
interface Response

The Response interface of the Fetch API represents the response to a request.

MDN Reference

Response
> {
let payments: Promise<ResiliencePipeline<Response, WithErrors<BrokenCircuitError, never, TimeoutRejectedError>>> | undefined
payments
??=
env: Env
env
.
Env.BREAKERS: KVNamespace
BREAKERS
.
KVNamespace.get(key: string, type: "json"): Promise<unknown>
get
(
const KEY: "breaker:payments"
KEY
, 'json')
.
Promise<unknown>.catch<null>(onrejected?: ((reason: any) => PromiseLike<null> | null) | null | undefined): Promise<unknown>

Attaches a callback for only the rejection of the Promise.

@param ― onrejected The callback to execute when the Promise is rejected.

@returns ― A Promise for the completion of the callback.

catch
(() => null) // KV unavailable: start closed rather than fail every request
.
Promise<unknown>.then<ResiliencePipeline<Response, WithErrors<BrokenCircuitError, never, TimeoutRejectedError>>, ResiliencePipeline<Response, WithErrors<BrokenCircuitError, never, TimeoutRejectedError>>>(onfulfilled?: ((value: unknown) => ResiliencePipeline<Response, WithErrors<BrokenCircuitError, never, TimeoutRejectedError>> | PromiseLike<...>) | null | undefined, onrejected?: ((reason: any) => ResiliencePipeline<...> | PromiseLike<...>) | ... 1 more ... | undefined): Promise<...>

Attaches callbacks for the resolution and/or rejection of the Promise.

@param ― onfulfilled The callback to execute when the Promise is resolved.

@param ― onrejected The callback to execute when the Promise is rejected.

@returns ― A Promise for the completion of which ever callback is executed.

then
((
saved: unknown
saved
) =>
function buildPayments(saved: CircuitSnapshot | undefined): ResiliencePipeline<Response, WithErrors<BrokenCircuitError, never, TimeoutRejectedError>>
buildPayments
((
saved: unknown
saved
as
(alias) interface CircuitSnapshot
import CircuitSnapshot

JSON-serializable breaker state, for persisting across restarts and serverless invocations.

CircuitSnapshot
| null) ??
var undefined
undefined
));
const
const pipeline: ResiliencePipeline<Response, WithErrors<BrokenCircuitError, never, TimeoutRejectedError>>
pipeline
= await
let payments: Promise<ResiliencePipeline<Response, WithErrors<BrokenCircuitError, never, TimeoutRejectedError>>>
payments
;
const
const body: any
body
= await
request: Request
request
.
Body.json(): Promise<any>
json
();
try {
return await
const pipeline: ResiliencePipeline<Response, WithErrors<BrokenCircuitError, never, TimeoutRejectedError>>
pipeline
.
ResiliencePipeline<Response, WithErrors<BrokenCircuitError, never, TimeoutRejectedError>>.execute<Response>(callback: (context: ResilienceContext) => MaybePromise<Response>, options?: ExecuteOptions): Promise<Response>

Runs the callback through the pipeline; throws the final error.

execute
(({
signal: AbortSignal

Aborted when the caller cancels, or when a strategy (timeout, hedging) gives up on this execution.

signal
}) =>
function chargeCard(body: unknown, signal: AbortSignal): Promise<Response>
chargeCard
(
const body: any
body
,
signal: AbortSignal

Aborted when the caller cancels, or when a strategy (timeout, hedging) gives up on this execution.

signal
));
} finally {
if (
let changed: boolean
changed
) {
let changed: boolean
changed
= false;
// After the response is sent, without delaying it.
ctx: ExecutionContext
ctx
.
ExecutionContext.waitUntil(promise: Promise<unknown>): void
waitUntil
(
env: Env
env
.
Env.BREAKERS: KVNamespace
BREAKERS
.
KVNamespace.put(key: string, value: string): Promise<void>
put
(
const KEY: "breaker:payments"
KEY
,
var JSON: JSON

An intrinsic object that provides functions to convert JavaScript values to and from the JavaScript Object Notation (JSON) format.

JSON
.
JSON.stringify(value: any, replacer?: (this: any, key: string, value: any) => any, space?: string | number): string (+1 overload)

Converts a JavaScript value to a JavaScript Object Notation (JSON) string.

@param ― value A JavaScript value, usually an object or array, to be converted.

@param ― replacer A function that transforms the results.

@param ― space Adds indentation, white space, and line break characters to the return-value JSON text to make it easier to read.

@throws ― {TypeError} If a circular reference or a BigInt value is found.

stringify
(
const state: CircuitBreakerStateProvider
state
.
CircuitBreakerStateProvider.snapshot(): CircuitSnapshot

Current state, rolling window included, as plain JSON; pass it back as initialState to resume.

snapshot
())));
}
}
},
};

What to save, and when:

  • Save on state changes, not per request. Writes on onOpened / onClosed are rare. Cloudflare KV in particular limits writes per key to about one per second, and reads may lag writes by up to a minute in other locations; for breaker state that lag is acceptable, for anything stricter use a Durable Object or Redis.
  • The rolling window is in the snapshot (window), so a ratio-based breaker keeps its failure history. With consecutiveFailures the count is saved too, but only as of the last write; failures between two writes are lost, which only delays an opening.
  • Only restore snapshots you wrote. initialState trusts its top-level fields. Malformed window data is ignored, but a state that isn’t one of 'closed' | 'open' | 'half-open' | 'isolated' is not rejected. Validate if the store is shared with other code.

warmUpDuration keeps the breaker from opening for a while after it is created. Failures are still recorded, so if the dependency really is down, the window is already full when warm-up ends and the circuit opens on the next failure.

const
const pipeline: ResiliencePipeline<any, WithErrors<BrokenCircuitError, never, TimeoutRejectedError>>
pipeline
= new
new ResiliencePipelineBuilder<any, never, never>(options?: ResiliencePipelineBuilderOptions): ResiliencePipelineBuilder<any, never, never>

Polly-style builder with a shortcut for every strategy: addRetry(), addTimeout(), addCircuitBreaker(), … Convenient, but it bundles every strategy. For the smallest browser bundle use PipelineBuilder with retryStrategy(), timeoutStrategy(), … instead; the two are interchangeable.

The type parameters track which errors the pipeline can raise, for pipeline.settle() + Outcome.match().

ResiliencePipelineBuilder
()
.
ResiliencePipelineBuilder<any, never, never>.addCircuitBreaker(options?: CircuitBreakerOptions<any>): Next<any, never, never, BrokenCircuitError>
addCircuitBreaker
({
CircuitBreakerOptions<any>.consecutiveFailures?: number | undefined

Break after this many failures in a row (a success resets the count). Simple and good for low traffic. When set without failureRatio, the ratio check is off and only this rule applies.

consecutiveFailures
: 3,
CircuitBreakerOptions<any>.breakDuration?: Duration | undefined

How long the circuit stays open. Default 5 s.

breakDuration
: '30s',
CircuitBreakerOptions<any>.warmUpDuration?: Duration | undefined

Don't open during this period after creation (the window still fills), so a cold start's first errors can't trip it.

warmUpDuration
: '15s' })
.
ResiliencePipelineBuilder<any, BrokenCircuitError, never>.addTimeout(options: Duration | TimeoutOptions): Next<any, BrokenCircuitError, never, TimeoutRejectedError>

addTimeout(5000), addTimeout('5s') or addTimeout({ timeout: '5s', onTimeout }).

addTimeout
('2s')
.
ResiliencePipelineBuilder<any, WithErrors<BrokenCircuitError, never, TimeoutRejectedError>, never>.build(): ResiliencePipeline<any, WithErrors<BrokenCircuitError, never, TimeoutRejectedError>>
build
();

Warm-up only prevents new openings. A circuit restored as open from initialState stays open until its break ends. CircuitBreakerStateProvider.isWarmingUp tells you whether an instance is still in warm-up, for logs or health checks.

Rate limiter state: snapshot() / restore()

Section titled “Rate limiter state: snapshot() / restore()”

A per-instance limiter that protects a quota (a cron function that may send 100 calls per minute) gets a fresh full bucket on every cold start. Save the limiter’s state at the end of the run and restore it on the next start; time that passed in between counts (tokens refill, windows expire, blocks run out).

import {
class ResiliencePipelineBuilder<T = any, E = never, C extends string = never>

Polly-style builder with a shortcut for every strategy: addRetry(), addTimeout(), addCircuitBreaker(), … Convenient, but it bundles every strategy. For the smallest browser bundle use PipelineBuilder with retryStrategy(), timeoutStrategy(), … instead; the two are interchangeable.

The type parameters track which errors the pipeline can raise, for pipeline.settle() + Outcome.match().

ResiliencePipelineBuilder
} from '@ylcnfrht/resilience';
const
const crm: ResiliencePipeline<any, RateLimiterRejectedError>
crm
= new
new ResiliencePipelineBuilder<any, never, never>(options?: ResiliencePipelineBuilderOptions): ResiliencePipelineBuilder<any, never, never>

Polly-style builder with a shortcut for every strategy: addRetry(), addTimeout(), addCircuitBreaker(), … Convenient, but it bundles every strategy. For the smallest browser bundle use PipelineBuilder with retryStrategy(), timeoutStrategy(), … instead; the two are interchangeable.

The type parameters track which errors the pipeline can raise, for pipeline.settle() + Outcome.match().

ResiliencePipelineBuilder
({
ResiliencePipelineBuilderOptions.name?: string | undefined

Labels telemetry events.

name
: 'crm',
ResiliencePipelineBuilderOptions.unrefTimers?: boolean | undefined

Don't let pending timers (retry delays, timeouts) keep the process alive. Ignored when timeProvider is set.

unrefTimers
: true })
.
ResiliencePipelineBuilder<any, never, never>.addRateLimiter(options: TokenBucketOptions): Next<any, never, never, RateLimiterRejectedError>

Token bucket rate limiter.

addRateLimiter
({
TokenBucketOptions.tokenLimit: number

Bucket size (largest burst), and the number of tokens it starts with.

tokenLimit
: 100,
TokenBucketOptions.replenishmentPeriod: Duration

Refill interval.

replenishmentPeriod
: '1m',
PermitRateLimiterOptions.queueTimeout?: Duration | undefined

Longest an execution may wait for permits before it is rejected. Waiters are served first come, first served. Default 0: reject right away.

queueTimeout
: '1m' })
.
ResiliencePipelineBuilder<any, RateLimiterRejectedError, never>.build(): ResiliencePipeline<any, RateLimiterRejectedError>
build
();
const
const limiter: TokenBucketStrategy<any>
limiter
=
const crm: ResiliencePipeline<any, RateLimiterRejectedError>
crm
.
ResiliencePipeline<any, RateLimiterRejectedError>.getStrategy<TokenBucketStrategy<any>>(nameOrKind: string): TokenBucketStrategy<any> | undefined

The strategy instance named name (or of class kind), to read its live state: getStrategy<CircuitBreakerStrategy>('CircuitBreaker')?.state.

getStrategy
<
class TokenBucketStrategy<T>

Token bucket rate limiter, e.g. for an external API quota.

TokenBucketStrategy
<any>>('RateLimiter')!;
const
const saved: string | null
saved
= await
const kv: {
get(key: string): Promise<string | null>;
put(key: string, value: string): Promise<void>;
}
kv
.
function get(key: string): Promise<string | null>
get
('limiter:crm');
if (
const saved: string | null
saved
) {
try {
const limiter: TokenBucketStrategy<any>
limiter
.
RateLimiterStrategy<any>.restore(snapshot: RateLimiterSnapshot): void

Loads a snapshot(); time passed since it was taken counts (windows expire, tokens refill, blocks run out).

restore
(
var JSON: JSON

An intrinsic object that provides functions to convert JavaScript values to and from the JavaScript Object Notation (JSON) format.

JSON
.
JSON.parse(text: string, reviver?: (this: any, key: string, value: any) => any): any

Converts a JavaScript Object Notation (JSON) string into an object.

@param ― text A valid JSON string.

@param ― reviver A function that transforms the results. This function is called for each member of the object. If a member contains nested objects, the nested objects are transformed before the parent object is.

@throws ― {SyntaxError} If text is not valid JSON.

parse
(
const saved: string
saved
));
} catch {
// Throws TypeError on a malformed or incompatible snapshot (e.g. after changing segmentsPerWindow): start fresh.
}
}
try {
for (const
const id: string
id
of
const customerIds: string[]
customerIds
) await
const crm: ResiliencePipeline<any, RateLimiterRejectedError>
crm
.
ResiliencePipeline<any, RateLimiterRejectedError>.execute<void>(callback: (context: ResilienceContext) => MaybePromise<void>, options?: ExecuteOptions): Promise<void>

Runs the callback through the pipeline; throws the final error.

execute
(({
signal: AbortSignal

Aborted when the caller cancels, or when a strategy (timeout, hedging) gives up on this execution.

signal
}) =>
function syncCustomer(id: string, signal: AbortSignal): Promise<void>
syncCustomer
(
const id: string
id
,
signal: AbortSignal

Aborted when the caller cancels, or when a strategy (timeout, hedging) gives up on this execution.

signal
));
} finally {
await
const kv: {
get(key: string): Promise<string | null>;
put(key: string, value: string): Promise<void>;
}
kv
.
function put(key: string, value: string): Promise<void>
put
('limiter:crm',
var JSON: JSON

An intrinsic object that provides functions to convert JavaScript values to and from the JavaScript Object Notation (JSON) format.

JSON
.
JSON.stringify(value: any, replacer?: (this: any, key: string, value: any) => any, space?: string | number): string (+1 overload)

Converts a JavaScript value to a JavaScript Object Notation (JSON) string.

@param ― value A JavaScript value, usually an object or array, to be converted.

@param ― replacer A function that transforms the results.

@param ― space Adds indentation, white space, and line break characters to the return-value JSON text to make it easier to read.

@throws ― {TypeError} If a circular reference or a BigInt value is found.

stringify
(
const limiter: TokenBucketStrategy<any>
limiter
.
RateLimiterStrategy<any>.snapshot(): RateLimiterSnapshot

The algorithm's state and the block, JSON-serializable, for restore after a restart.

snapshot
()));
}

Every in-memory limiter supports this: token bucket, fixed and sliding window, and BurstyRateLimiter built from them. A partitioned limiter’s snapshot(limit?) saves the limit most recently used partitions (default: all), so the saved state stays small with many keys. restore() recreates them.

If several instances run at the same time, saving and restoring doesn’t make them share a limit: each restores the same state and then spends it independently. Use a Redis-backed limiter for that (multi-instance).

const
const pipeline: ResiliencePipeline<any, TimeoutRejectedError>
pipeline
= new
new ResiliencePipelineBuilder<any, never, never>(options?: ResiliencePipelineBuilderOptions): ResiliencePipelineBuilder<any, never, never>

Polly-style builder with a shortcut for every strategy: addRetry(), addTimeout(), addCircuitBreaker(), … Convenient, but it bundles every strategy. For the smallest browser bundle use PipelineBuilder with retryStrategy(), timeoutStrategy(), … instead; the two are interchangeable.

The type parameters track which errors the pipeline can raise, for pipeline.settle() + Outcome.match().

ResiliencePipelineBuilder
({
ResiliencePipelineBuilderOptions.unrefTimers?: boolean | undefined

Don't let pending timers (retry delays, timeouts) keep the process alive. Ignored when timeProvider is set.

unrefTimers
: true }).
ResiliencePipelineBuilder<any, never, never>.addRetry(options?: RetryOptions<any>): ResiliencePipelineBuilder<any, never, never>
addRetry
({
RetryOptions<any>.delay?: Duration | undefined

Base delay. Default 2000 ms.

delay
: '30s' }).
ResiliencePipelineBuilder<any, never, never>.addTimeout(options: Duration | TimeoutOptions): Next<any, never, never, TimeoutRejectedError>

addTimeout(5000), addTimeout('5s') or addTimeout({ timeout: '5s', onTimeout }).

addTimeout
('10s').
ResiliencePipelineBuilder<any, TimeoutRejectedError, never>.build(): ResiliencePipeline<any, TimeoutRejectedError>
build
();

With unrefTimers: true, pending retry delays and timeouts don’t keep a Node or Bun process alive (or Deno ≥ 2.8; older Deno timers have no unref(), so it has no effect there). A CLI, a cron script or a queue worker exits when its real work is done, instead of waiting out a 30 s retry delay that nobody needs. The option is ignored when you pass your own timeProvider, and makes no difference on Workers and in browsers.

On Lambda, the invocation is killed when its configured timeout passes. Wrap the handler body in withTimeout with the remaining time minus a margin. Every pipeline executed underneath inherits that deadline through AsyncLocalStorage, so calls that are still waiting are aborted in time to return a proper error, release locks or requeue the message.

import {
const isTimeoutError: (error: unknown) => error is TimeoutRejectedError
isTimeoutError
,
function withTimeout<T>(fn: (context: ResilienceContext) => MaybePromise<T>, timeout: Duration | TimeoutOptions, options?: CallOptions): Promise<T>

One-off timeout: await withTimeout(({ signal }) => fetch(url, { signal }), '2s'). Rejects with TimeoutRejectedError.

withTimeout
} from '@ylcnfrht/resilience';
export const
const handler: (event: SQSEvent, context: LambdaContext) => Promise<{
batchItemFailures: {
itemIdentifier: string;
}[];
}>
handler
= async (
event: SQSEvent
event
:
interface SQSEvent
SQSEvent
,
context: LambdaContext
context
:
interface LambdaContext
LambdaContext
) => {
const
const failures: {
itemIdentifier: string;
}[]
failures
:
interface Array<T>
Array
<{
itemIdentifier: string
itemIdentifier
: string }> = [];
for (const
const record: {
messageId: string;
body: string;
}
record
of
event: SQSEvent
event
.
SQSEvent.Records: {
messageId: string;
body: string;
}[]
Records
) {
// Keep 1 s to report back. withTimeout rejects a duration <= 0, hence the floor.
const
const budget: number
budget
=
var Math: Math

An intrinsic object that provides basic mathematics functionality and constants.

Math
.
Math.max(...values: number[]): number

Returns the larger of a set of supplied numeric expressions.

@param ― values Numeric expressions to be evaluated.

max
(1,
context: LambdaContext
context
.
LambdaContext.getRemainingTimeInMillis(): number
getRemainingTimeInMillis
() - 1_000);
try {
// processOrder → paymentsPipeline.execute(…) → fetch(…, { signal }): all aborted when the budget runs out.
await
withTimeout<void>(fn: (context: ResilienceContext) => MaybePromise<void>, timeout: Duration | TimeoutOptions, options?: CallOptions): Promise<void>

One-off timeout: await withTimeout(({ signal }) => fetch(url, { signal }), '2s'). Rejects with TimeoutRejectedError.

withTimeout
(() =>
function processOrder(body: string): Promise<void>
processOrder
(
const record: {
messageId: string;
body: string;
}
record
.
body: string
body
),
const budget: number
budget
);
} catch (
function (local var) error: unknown
error
) {
if (!
function isTimeoutError(error: unknown): error is TimeoutRejectedError
isTimeoutError
(
function (local var) error: unknown
error
))
var console: Console

The console module provides a simple debugging console that is similar to the JavaScript console mechanism provided by web browsers.

The module exports two specific components:

  • A Console class with methods such as console.log(), console.error() and console.warn() that can be used to write to any Node.js stream.
  • A global console instance configured to write to process.stdout and process.stderr. The global console can be used without importing the node:console module.

Warning: The global console object's methods are neither consistently synchronous like the browser APIs they resemble, nor are they consistently asynchronous like all other Node.js streams. See the note on process I/O for more information.

Example using the global console:

console.log('hello world');
// Prints: hello world, to stdout
console.log('hello %s', 'world');
// Prints: hello world, to stdout
console.error(new Error('Whoops, something bad happened'));
// Prints error message and stack trace to stderr:
// Error: Whoops, something bad happened
// at [eval]:5:15
// at Script.runInThisContext (node:vm:132:18)
// at Object.runInThisContext (node:vm:309:38)
// at node:internal/process/execution:77:19
// at [eval]-wrapper:6:22
// at evalScript (node:internal/process/execution:76:60)
// at node:internal/main/eval_string:23:3
const name = 'Will Robinson';
console.warn(`Danger ${name}! Danger!`);
// Prints: Danger Will Robinson! Danger!, to stderr

Example using the Console class:

const out = getStreamSomehow();
const err = getStreamSomehow();
const myConsole = new console.Console(out, err);
myConsole.log('hello world');
// Prints: hello world, to out
myConsole.log('hello %s', 'world');
// Prints: hello world, to out
myConsole.error(new Error('Whoops, something bad happened'));
// Prints: [Error: Whoops, something bad happened], to err
const name = 'Will Robinson';
myConsole.warn(`Danger ${name}! Danger!`);
// Prints: Danger Will Robinson! Danger!, to err

@see ― source

console
.
Console.error(message?: any, ...optionalParams: any[]): void (+1 overload)

Prints to stderr with newline. Multiple arguments can be passed, with the first used as the primary message and all additional used as substitution values similar to printf(3) (the arguments are all passed to util.format()).

const code = 5;
console.error('error #%d', code);
// Prints: error #5, to stderr
console.error('error', code);
// Prints: error 5, to stderr

If formatting elements (e.g. %d) are not found in the first string then util.inspect() is called on each argument and the resulting string values are concatenated. See util.format() for more information.

@since ― v0.1.100

error
(
function (local var) error: unknown
error
);
const failures: {
itemIdentifier: string;
}[]
failures
.
Array<{ itemIdentifier: string; }>.push(...items: {
itemIdentifier: string;
}[]): number

Appends new elements to the end of an array, and returns the new length of the array.

@param ― items New elements to add to the array.

push
({
itemIdentifier: string
itemIdentifier
:
const record: {
messageId: string;
body: string;
}
record
.
messageId: string
messageId
}); // SQS redelivers only these
}
}
return {
batchItemFailures: {
itemIdentifier: string;
}[]
batchItemFailures
:
const failures: {
itemIdentifier: string;
}[]
failures
};
};

On Workers there’s no fixed wall-clock deadline per request, but there is a client that can go away. If your framework gives you a request signal that aborts on disconnect, pass it as execute(fn, { signal }); Hono’s middleware does that for you.

  • Pipelines at module scope, built once per instance.
  • consecutiveFailures (or samplingCount) instead of a ratio over 100 calls: per-instance traffic is low.
  • warmUpDuration so cold-start errors can’t open the circuit.
  • Breaker state persisted on change (snapshot() / initialState) or shared live (sharedCircuitState).
  • Limiter state saved and restored around runs, or a Redis limiter when instances run concurrently.
  • withTimeout around the handler with the remaining time, and unrefTimers for scripts.