Skip to content
Buy me a coffee

Rate limiting

A rate limiter caps how many executions start per unit of time. Use it to stay under a third-party API quota (outbound), or to protect your service from one client sending too much (inbound, usually per key). It does not cap how many run at once: for that, use a concurrency limiter. Every limiter here is in-process; to share one limit between instances, plug in the Redis algorithm.

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';
// 10 requests per second, with bursts of up to 10; wait up to 2 s for a token.
const
const pipeline: ResiliencePipeline<any, RateLimiterRejectedError>
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>.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
: 10,
TokenBucketOptions.replenishmentPeriod: Duration

Refill interval.

replenishmentPeriod
: '1s',
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
: '2s' })
.
ResiliencePipelineBuilder<any, RateLimiterRejectedError, never>.build(): ResiliencePipeline<any, RateLimiterRejectedError>
build
();
await
const pipeline: ResiliencePipeline<any, RateLimiterRejectedError>
pipeline
.
ResiliencePipeline<any, RateLimiterRejectedError>.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 fetch(input: string | URL | Request, init?: RequestInit): Promise<Response> (+1 overload)
fetch
('https://api.vendor.com/v1/items', {
RequestInit.signal?: AbortSignal | null | undefined

An AbortSignal to set request's signal.

signal
}));
Builder method Definition Counts
addRateLimiter tokenBucketStrategy A bucket of tokenLimit tokens, refilled with tokensPerPeriod every replenishmentPeriod.
addFixedWindowRateLimiter fixedWindowRateLimiterStrategy permitLimit per window; the count resets at each window boundary.
addSlidingWindowRateLimiter slidingWindowRateLimiterStrategy permitLimit in any window-long span, tracked in segmentsPerWindow buckets.
addPartitionedRateLimiter partitionedRateLimiterStrategy Any of the above, one per key.

Token bucket

16/21 admitted · worst 1 s: 11
addRateLimiter({ tokenLimit: 8, replenishmentPeriod: '125ms', tokensPerPeriod: 1 })

Fixed window

21/21 admitted · worst 1 s: 16
addFixedWindowRateLimiter({ permitLimit: 8, window: '1s' })

Sliding window

13/21 admitted · worst 1 s: 8
addSlidingWindowRateLimiter({ permitLimit: 8, window: '1s', segmentsPerWindow: 5 })
  • admitted
  • rejected
  • busiest 1 s span
  • window boundary

Each request asks for one permit and nothing waits (no queue). The decisions come from the library's TokenBucket and WindowCounter. A red span means more than 8 requests got through inside one second.

Requests admitted per algorithm
AlgorithmAdmittedRejectedMost admitted in any 1 s
Token bucket16511
Fixed window21016
Sliding window1388
Token bucket Fixed window Sliding window
Burst at once tokenLimit permitLimit permitLimit
Worst case in one window-length span tokenLimit + refills in that span 2 × permitLimit (edge burst) permitLimit, to one segment’s precision
Quota comes back tokensPerPeriod at each period All at once at the boundary One segment at a time
Cost per call O(1) O(1) O(segmentsPerWindow)
Good for Smooth outbound traffic with some burst Simple quotas (“1000 per day”) Inbound limits that must hold for any span

The edge burst. With a fixed window of 100 per minute, a client can send 100 requests at 0:59 and 100 more at 1:00: 200 in two seconds, all allowed. A sliding window (10 segments of 6 s) still counts the first 100 at 1:00, so the second 100 waits or is rejected. Use a sliding window when the limit protects something.

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 pipeline: ResiliencePipeline<any, RateLimiterRejectedError>
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>.addSlidingWindowRateLimiter(options: SlidingWindowRateLimiterOptions): Next<any, never, never, RateLimiterRejectedError>

Sliding-window rate limit: smooths the burst a fixed window allows at its edges.

addSlidingWindowRateLimiter
({
FixedWindowRateLimiterOptions.permitLimit: number

Permits granted per window.

permitLimit
: 100,
FixedWindowRateLimiterOptions.window: Duration
window
: '1m',
SlidingWindowRateLimiterOptions.segmentsPerWindow?: number | undefined

Buckets the window slides by. More = smoother and more exact, at O(segments) per call. Default 10.

segmentsPerWindow
: 12 })
.
ResiliencePipelineBuilder<any, RateLimiterRejectedError, never>.build(): ResiliencePipeline<any, RateLimiterRejectedError>
build
();
Option Type Default Description
permits (context) => number 1 per execution Weight of an execution (batch size, cost, tokens). Finite and ≥ 0. Above the limit’s maximum: rejected with PermitLimitExceededError.
queueTimeout Duration 0 (reject at once) Longest an execution waits for permits (same option as on the concurrency limiters, but here 0 means no waiting). Waiters are served first come, first served: a heavy request is never overtaken by lighter ones. If the permits can’t be free before the deadline, it is rejected right away instead of waiting in vain.
queueLimit number no limit Most executions waiting (with queueTimeout); beyond it, rejected at once. Unlike the concurrency limiters (default 0), the rate limiters wait only when queueTimeout is set, so with the defaults nobody queues here either (as in Polly).
spacing 'even' – Delay granted executions so the quota left is spread evenly until the limit resets, instead of bursting at the start of the window.
minTime Duration 0 Least time between the starts of two granted executions (bottleneck’s minTime). Also the floor of 'even' spacing.
blockDuration Duration 0 (off) Once an execution is rejected for exceeding the limit, reject everything for this long. Rejections during a block don’t extend it.
allow (context) => boolean – Bypass the limiter (no permits taken), e.g. internal callers or health checks.
deny (context) => boolean – Reject with RateLimiterDeniedError right away. Checked before allow.
control RateLimiterControl – Manual penalty, reward, block, unblock and delete (see below).
insurance { algorithm: RateLimiterAlgorithm; timeout?: Duration } – In-memory fallback when a remote algorithm throws or takes longer than timeout (see below).
onRejected (args: { context, retryAfter }) => void | Promise<void> – Called on every rejection. retryAfter is in ms, when known.
name string 'RateLimiter', 'FixedWindowRateLimiter' or 'SlidingWindowRateLimiter' Strategy name.
Option Type Default Description
tokenLimit number required Bucket size: the largest burst, and the tokens it starts with. Integer ≥ 1.
replenishmentPeriod Duration required Refill interval. > 0.
tokensPerPeriod number tokenLimit Tokens added each period (all at once, capped at tokenLimit). ≥ 1.

{ tokenLimit: 10, replenishmentPeriod: '1s' } refills the whole bucket every second. For a smoother rate, refill in small steps: { tokenLimit: 10, replenishmentPeriod: '100ms', tokensPerPeriod: 1 }.

Option Type Default Description
permitLimit number required Permits per window. Integer ≥ 1.
window Duration required Window length. > 0.
segmentsPerWindow number 10 Sliding only. Buckets the window slides by: more is smoother and more exact, at O(segments) per call.
alignment 'fixed' | 'first-request' 'fixed' 'fixed': windows follow each other from the limiter’s creation. 'first-request': a window starts at the first request after the previous one emptied (per key when partitioned), like “100 per hour from your first call”.
// An embeddings API that allows 10 000 inputs per minute: charge each call its batch size.
const
const pipeline: ResiliencePipeline<any, RateLimiterRejectedError>
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>.addSlidingWindowRateLimiter(options: SlidingWindowRateLimiterOptions): Next<any, never, never, RateLimiterRejectedError>

Sliding-window rate limit: smooths the burst a fixed window allows at its edges.

addSlidingWindowRateLimiter
({
FixedWindowRateLimiterOptions.permitLimit: number

Permits granted per window.

permitLimit
: 10_000,
FixedWindowRateLimiterOptions.window: Duration
window
: '1m',
PermitRateLimiterOptions.permits?: ((context: ResilienceContext) => number) | undefined

Weight of an execution (e.g. batch size, cost). Default 1.

permits
: (
ctx: ResilienceContext
ctx
) =>
ctx: ResilienceContext
ctx
.
ResilienceContext.properties: Map<string, unknown>

Shared bag for passing data between strategies and the callback.

properties
.
Map<string, unknown>.get(key: string): unknown

Returns a specified element from the Map object. If the value that is associated to the provided key is an object, then you will get a reference to that object and any change made to that object will effectively modify it inside the Map.

@returns ― Returns the element associated with the specified key. If no element is associated with the specified key, undefined is returned.

get
('batchSize') as number,
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
: '30s',
PermitRateLimiterOptions.queueLimit?: number | undefined

Most executions waiting (with queueTimeout); beyond it they are rejected at once. Default: no limit, as queueTimeout (default 0) already decides whether anyone waits.

queueLimit
: 100,
})
.
ResiliencePipelineBuilder<any, RateLimiterRejectedError, never>.build(): ResiliencePipeline<any, RateLimiterRejectedError>
build
();
const
const vectors: number[][]
vectors
= await
const pipeline: ResiliencePipeline<any, RateLimiterRejectedError>
pipeline
.
ResiliencePipeline<any, RateLimiterRejectedError>.execute<number[][]>(callback: (context: ResilienceContext) => MaybePromise<number[][]>, options?: ExecuteOptions): Promise<number[][]>

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 embed(texts: string[], signal: AbortSignal): Promise<number[][]>
embed
(
const texts: string[]
texts
,
signal: AbortSignal

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

signal
), {
ExecuteOptions.properties?: Map<string, unknown> | undefined
properties
: new
var Map: MapConstructor
new <string, number>(iterable?: Iterable<readonly [string, number]> | null | undefined) => Map<string, number> (+3 overloads)
Map
([['batchSize',
const texts: string[]
texts
.
Array<string>.length: number

Gets or sets the length of the array. This is a number one higher than the highest index in the array.

length
]]),
});

The queue is strict FIFO: the head waits until its permits fit and nobody overtakes it. A caller whose signal aborts leaves the queue at once.

A window limiter lets the whole quota through at the start of the window. Many APIs dislike that. spacing: 'even' delays each granted execution so the rest of the quota is spread until the limit resets: 4 per second runs at 0, 250, 500 and 750 ms. minTime sets a fixed gap between starts instead (or a floor under even spacing).

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 pipeline: ResiliencePipeline<any, RateLimiterRejectedError>
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>.addFixedWindowRateLimiter(options: FixedWindowRateLimiterOptions): Next<any, never, never, RateLimiterRejectedError>

Fixed-window rate limit: permitLimit per window. Optional permits(ctx) weight and queueTimeout.

addFixedWindowRateLimiter
({
FixedWindowRateLimiterOptions.permitLimit: number

Permits granted per window.

permitLimit
: 60,
FixedWindowRateLimiterOptions.window: Duration
window
: '1m',
PermitRateLimiterOptions.spacing?: "even" | undefined

'even': delays granted executions so the quota left is spread evenly until the limit resets, instead of bursting at the start of the window. The delay counts against queueTimeout when that is set; abort ends it.

spacing
: 'even',
PermitRateLimiterOptions.minTime?: Duration | undefined

Least time between the starts of two granted executions (bottleneck's minTime); also the floor of 'even' spacing.

minTime
: '200ms' })
.
ResiliencePipelineBuilder<any, RateLimiterRejectedError, never>.build(): ResiliencePipeline<any, RateLimiterRejectedError>
build
();

The pacing delay counts against queueTimeout when it is set: an execution that would be paced past its queueTimeout deadline starts at the deadline instead. An abort during the delay ends it. The permit is not refunded in that case.

One limiter per user, tenant or API key, so one noisy key can’t use up the others’ quota. Partitions are created on first use and dropped when idle.

const
const pipeline: ResiliencePipeline<any, RateLimiterRejectedError>
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>.addPartitionedRateLimiter(options: PartitionedRateLimiterOptions<any>): Next<any, never, never, RateLimiterRejectedError>

One limiter per key (user, tenant, API key…), created on demand and evicted when idle.

addPartitionedRateLimiter
({
PartitionedRateLimiterOptions<any>.partitionKey: (context: ResilienceContext) => string

Which partition an execution counts against, e.g. (ctx) => ctx.properties.get('userId') as string.

partitionKey
: (
ctx: ResilienceContext
ctx
) =>
ctx: ResilienceContext
ctx
.
ResilienceContext.properties: Map<string, unknown>

Shared bag for passing data between strategies and the callback.

properties
.
Map<string, unknown>.get(key: string): unknown

Returns a specified element from the Map object. If the value that is associated to the provided key is an object, then you will get a reference to that object and any change made to that object will effectively modify it inside the Map.

@returns ― Returns the element associated with the specified key. If no element is associated with the specified key, undefined is returned.

get
('apiKey') as string,
PartitionedRateLimiterOptions<any>.limiter: (key: string, source: StrategySource) => ResilienceStrategy<any>

Creates the limiter of a new partition, e.g. (key, s) => new SlidingWindowRateLimiterStrategy({ permitLimit: 100, window: '1m' }, s).

limiter
: (
key: string
key
,
source: StrategySource
source
) =>
new
new SlidingWindowRateLimiterStrategy<any>(options: SlidingWindowRateLimiterOptions, source: StrategySource): SlidingWindowRateLimiterStrategy<any>

permitLimit in any window-long span (to one segment's precision): no 2x burst at window edges.

SlidingWindowRateLimiterStrategy
({
FixedWindowRateLimiterOptions.permitLimit: number

Permits granted per window.

permitLimit
:
key: string
key
=== 'internal' ? 10_000 : 100,
FixedWindowRateLimiterOptions.window: Duration
window
: '1m' },
source: StrategySource
source
),
PartitionedRateLimiterOptions<any>.idleTimeout?: Duration | undefined

A partition unused this long (and with nothing running) is dropped, resetting its limit. Default 10 minutes. Keep it at least as long as the limiter's window, or an evicted partition gets a fresh quota early.

idleTimeout
: '10m',
PartitionedRateLimiterOptions<any>.maxPartitions?: number | undefined

Most partitions kept; past it the least recently used partition with nothing running is dropped (its limit resets). When all of them are running, a new key is rejected with RateLimiterRejectedError. Default: no limit.

maxPartitions
: 50_000,
})
.
ResiliencePipelineBuilder<any, RateLimiterRejectedError, never>.build(): ResiliencePipeline<any, RateLimiterRejectedError>
build
();
await
const pipeline: ResiliencePipeline<any, RateLimiterRejectedError>
pipeline
.
ResiliencePipeline<any, RateLimiterRejectedError>.execute<string>(callback: (context: ResilienceContext) => MaybePromise<string>, options?: ExecuteOptions): Promise<string>

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

execute
(
function handle(): Promise<string>
handle
, {
ExecuteOptions.properties?: Map<string, unknown> | undefined
properties
: new
var Map: MapConstructor
new <string, string>(iterable?: Iterable<readonly [string, string]> | null | undefined) => Map<string, string> (+3 overloads)
Map
([['apiKey', 'key_123']]) });
Option Type Default Description
partitionKey (context) => string required Which partition the execution counts against. If it throws, the execution fails with that error.
limiter (key, source) => ResilienceStrategy required Creates the limiter of a new partition. Any strategy works; control and snapshot need RateLimiterStrategy partitions (fixed, sliding, token bucket, custom algorithm).
idleTimeout Duration '10m' A partition unused this long (with nothing running) is dropped, resetting its limit. Keep it ≥ the window, or an evicted key gets a fresh quota early.
maxPartitions number no limit Most partitions kept; past it, the least recently used idle partition is dropped. When every partition is running, a new key is rejected with RateLimiterRejectedError instead, so memory stays bounded. Set it for keys you don’t control (IPs).
control RateLimiterControl – Penalty / reward / block per key.
name string 'PartitionedRateLimiter' Strategy name.

The strategy instance (getStrategy<PartitionedRateLimiterStrategy<unknown>>('PartitionedRateLimiter')) exposes partitions (count), keys() (least recently used first) and delete(key?) (drop one or every partition: fresh quota, no block).

RateLimiterControl changes a limiter’s state from outside. blockDuration blocks automatically once the limit is hit. Together they cover brute-force protection:

const
const control: RateLimiterControl
control
= new
new RateLimiterControl(): RateLimiterControl

Manual control of the rate limiters it is passed to (control option): penalty, reward, block, unblock, delete. key picks a partition of a partitioned limiter (omitted: every partition); single limiters ignore it. Pass it to the partitioned limiter, not to the per-key limiters it creates.

RateLimiterControl
();
const
const logins: ResiliencePipeline<any, RateLimiterRejectedError>
logins
= 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>.addPartitionedRateLimiter(options: PartitionedRateLimiterOptions<any>): Next<any, never, never, RateLimiterRejectedError>

One limiter per key (user, tenant, API key…), created on demand and evicted when idle.

addPartitionedRateLimiter
({
PartitionedRateLimiterOptions<any>.partitionKey: (context: ResilienceContext) => string

Which partition an execution counts against, e.g. (ctx) => ctx.properties.get('userId') as string.

partitionKey
: (
ctx: ResilienceContext
ctx
) =>
ctx: ResilienceContext
ctx
.
ResilienceContext.operationKey: string | undefined
operationKey
!,
// 5 attempts per 15 min per user; past that, locked out for 1 h.
PartitionedRateLimiterOptions<any>.limiter: (key: string, source: StrategySource) => ResilienceStrategy<any>

Creates the limiter of a new partition, e.g. (key, s) => new SlidingWindowRateLimiterStrategy({ permitLimit: 100, window: '1m' }, s).

limiter
: (
_: string
_
,
s: StrategySource
s
) => new
new SlidingWindowRateLimiterStrategy<any>(options: SlidingWindowRateLimiterOptions, source: StrategySource): SlidingWindowRateLimiterStrategy<any>

permitLimit in any window-long span (to one segment's precision): no 2x burst at window edges.

SlidingWindowRateLimiterStrategy
({
FixedWindowRateLimiterOptions.permitLimit: number

Permits granted per window.

permitLimit
: 5,
FixedWindowRateLimiterOptions.window: Duration
window
: '15m',
PermitRateLimiterOptions.blockDuration?: Duration | undefined

Once an execution is rejected for exceeding the limit, reject everything for this long (brute-force protection).

blockDuration
: '1h' },
s: StrategySource
s
),
PartitionedRateLimiterOptions<any>.idleTimeout?: Duration | undefined

A partition unused this long (and with nothing running) is dropped, resetting its limit. Default 10 minutes. Keep it at least as long as the limiter's window, or an evicted partition gets a fresh quota early.

idleTimeout
: '1h', // >= the block, or an evicted partition forgets it
PartitionedRateLimiterOptions<any>.control?: RateLimiterControl | undefined

Penalty / reward / block per key. The per-key limiters must be RateLimiterStrategys (fixed, sliding, token bucket). A block is lost if its partition is evicted, so keep idleTimeout >= the block.

control
,
})
.
ResiliencePipelineBuilder<any, RateLimiterRejectedError, never>.build(): ResiliencePipeline<any, RateLimiterRejectedError>
build
();
async function
function login(user: string, password: string): Promise<boolean>
login
(
user: string
user
: string,
password: string
password
: string) {
const
const ok: boolean
ok
= await
const logins: ResiliencePipeline<any, RateLimiterRejectedError>
logins
.
ResiliencePipeline<any, RateLimiterRejectedError>.execute<boolean>(callback: (context: ResilienceContext) => MaybePromise<boolean>, options?: ExecuteOptions): Promise<boolean>

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

execute
(() =>
function checkPassword(user: string, password: string): Promise<boolean>
checkPassword
(
user: string
user
,
password: string
password
), {
ExecuteOptions.operationKey?: string | undefined
operationKey
:
user: string
user
});
if (
const ok: boolean
ok
) await
const control: RateLimiterControl
control
.
RateLimiterControl.delete(key?: string): Promise<void>

Forget the state of key (or of the single limiter): fresh quota, no block.

delete
(
user: string
user
); // success: forget the failed attempts
else await
const control: RateLimiterControl
control
.
RateLimiterControl.penalty(points: number, key?: string): Promise<void>

Charge points permits, e.g. for a failed login.

penalty
(2,
user: string
user
); // a wrong password costs 3 permits in total
return
const ok: boolean
ok
;
}
Method Effect
penalty(points, key?) Charge points permits without an admission check (a token bucket can go negative).
reward(points, key?) Give points back; Infinity resets the count. Wakes waiters.
block(duration, key?) Reject everything for duration.
unblock(key?) End a block.
delete(key?) Fresh quota and no block (drops the partition when partitioned).

key picks a partition; omit it to act on every partition. Single limiters ignore key. Pass control to the partitioned limiter, not to the per-key limiters it creates. penalty and block create a missing partition; reward and unblock don’t. An algorithm without adjust throws a TypeError on penalty / reward.

const
const pipeline: ResiliencePipeline<any, RateLimiterRejectedError>
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>.addSlidingWindowRateLimiter(options: SlidingWindowRateLimiterOptions): Next<any, never, never, RateLimiterRejectedError>

Sliding-window rate limit: smooths the burst a fixed window allows at its edges.

addSlidingWindowRateLimiter
({
FixedWindowRateLimiterOptions.permitLimit: number

Permits granted per window.

permitLimit
: 100,
FixedWindowRateLimiterOptions.window: Duration
window
: '1m',
PermitRateLimiterOptions.deny?: ((context: ResilienceContext) => boolean) | undefined

Reject with RateLimiterDeniedError right away (checked before allow).

deny
: (
ctx: ResilienceContext
ctx
) =>
const bannedIps: Set<string>
bannedIps
.
Set<string>.has(value: string): boolean

@returns ― a boolean indicating whether an element with the specified value exists in the Set or not.

has
(
ctx: ResilienceContext
ctx
.
ResilienceContext.properties: Map<string, unknown>

Shared bag for passing data between strategies and the callback.

properties
.
Map<string, unknown>.get(key: string): unknown

Returns a specified element from the Map object. If the value that is associated to the provided key is an object, then you will get a reference to that object and any change made to that object will effectively modify it inside the Map.

@returns ― Returns the element associated with the specified key. If no element is associated with the specified key, undefined is returned.

get
('ip') as string), // RateLimiterDeniedError, HTTP 403
PermitRateLimiterOptions.allow?: ((context: ResilienceContext) => boolean) | undefined

Bypass the limiter, e.g. internal callers.

allow
: (
ctx: ResilienceContext
ctx
) =>
ctx: ResilienceContext
ctx
.
ResilienceContext.properties: Map<string, unknown>

Shared bag for passing data between strategies and the callback.

properties
.
Map<string, unknown>.get(key: string): unknown

Returns a specified element from the Map object. If the value that is associated to the provided key is an object, then you will get a reference to that object and any change made to that object will effectively modify it inside the Map.

@returns ― Returns the element associated with the specified key. If no element is associated with the specified key, undefined is returned.

get
('ip') === '10.0.0.1', // no limit, no permits taken
})
.
ResiliencePipelineBuilder<any, RateLimiterRejectedError, never>.build(): ResiliencePipeline<any, RateLimiterRejectedError>
build
();

After a limiter admits or rejects an execution, getRateLimitInfo(context) returns { limit, remaining, resetAfter } (resetAfter in ms; on rejection, until a retry may pass). Read it inside the callback, or pass your own properties map and read it afterwards. The /http, Fastify and Koa middlewares turn it into RateLimit-* headers for you.

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
,
function getRateLimitInfo(context: {
readonly properties: Map<string, unknown>;
}): RateLimitInfo | undefined

Limit, remaining permits and reset time the rate limiter reported for this execution, e.g. for RateLimit-* headers.

getRateLimitInfo
} from '@ylcnfrht/resilience';
const
const pipeline: ResiliencePipeline<any, RateLimiterRejectedError>
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>.addFixedWindowRateLimiter(options: FixedWindowRateLimiterOptions): Next<any, never, never, RateLimiterRejectedError>

Fixed-window rate limit: permitLimit per window. Optional permits(ctx) weight and queueTimeout.

addFixedWindowRateLimiter
({
FixedWindowRateLimiterOptions.permitLimit: number

Permits granted per window.

permitLimit
: 100,
FixedWindowRateLimiterOptions.window: Duration
window
: '1h' }).
ResiliencePipelineBuilder<any, RateLimiterRejectedError, never>.build(): ResiliencePipeline<any, RateLimiterRejectedError>
build
();
const
const properties: Map<string, unknown>
properties
= new
var Map: MapConstructor
new <string, unknown>(iterable?: Iterable<readonly [string, unknown]> | null | undefined) => Map<string, unknown> (+3 overloads)
Map
<string, unknown>();
await
const pipeline: ResiliencePipeline<any, RateLimiterRejectedError>
pipeline
.
ResiliencePipeline<any, RateLimiterRejectedError>.execute<string>(callback: (context: ResilienceContext) => MaybePromise<string>, options?: ExecuteOptions): Promise<string>

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

execute
(async () => 'ok', {
ExecuteOptions.properties?: Map<string, unknown> | undefined
properties
}).
Promise<string>.catch<undefined>(onrejected?: ((reason: any) => PromiseLike<undefined> | undefined) | null | undefined): Promise<string | undefined>

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
(() =>
var undefined
undefined
);
const
const info: RateLimitInfo | undefined
info
=
function getRateLimitInfo(context: {
readonly properties: Map<string, unknown>;
}): RateLimitInfo | undefined

Limit, remaining permits and reset time the rate limiter reported for this execution, e.g. for RateLimit-* headers.

getRateLimitInfo
({
properties: Map<string, unknown>
properties
});
// { limit: 100, remaining: 99, resetAfter: ~3_600_000 }

BurstyRateLimiter(primary, burst) combines a steady limit with a burst allowance. burst is consulted only when primary rejects: “10 per second, plus up to 20 extra per minute”. Run it with RateLimiterStrategy:

import {
class BurstyRateLimiter

A steady limit plus a burst allowance: burst is only consulted when primary rejects, e.g. 10/s that may spill into 20 extra per minute. Any algorithms work, sync or async (in-memory, Redis):

const now = s.timeProvider.now();
new RateLimiterStrategy(new BurstyRateLimiter(new WindowCounter(10, 1000, 1, now), new WindowCounter(20, 60_000, 1, now)), { queueTimeout: '1s' }, s);

BurstyRateLimiter
,
class RateLimiterStrategy<T>

Runs any RateLimiterAlgorithm as a strategy: weights, FIFO waiting up to queueTimeout, block, allow/deny, insurance, telemetry.

RateLimiterStrategy
,
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
,
class WindowCounter

Segmented sliding window: the window is split into segments buckets and slides one bucket at a time. With 1 segment it is a fixed window. Windows are aligned to origin; with 'first-request' alignment a new window starts at the first request after the window empties instead.

WindowCounter
} from '@ylcnfrht/resilience';
const
const now: number
now
=
var Date: DateConstructor

Enables basic storage and retrieval of dates and times.

Date
.
DateConstructor.now(): number

Returns the number of milliseconds elapsed since midnight, January 1, 1970 Universal Coordinated Time (UTC).

now
();
const
const pipeline: ResiliencePipeline<any, never>
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
()
.
PipelineBuilder<any, never, never>.addStrategy(create: StrategyFactory<any>, name?: string): ResiliencePipelineBuilder<any, never, never>

Plug in your own strategy.

addStrategy
(
(
s: StrategySource
s
) =>
new
new RateLimiterStrategy<any>(algorithm: RateLimiterAlgorithm, options: PermitRateLimiterOptions, source: StrategySource): RateLimiterStrategy<any>

Runs any RateLimiterAlgorithm as a strategy: weights, FIFO waiting up to queueTimeout, block, allow/deny, insurance, telemetry.

RateLimiterStrategy
(
new
new BurstyRateLimiter(primary: RateLimiterAlgorithm, burst: RateLimiterAlgorithm): BurstyRateLimiter

A steady limit plus a burst allowance: burst is only consulted when primary rejects, e.g. 10/s that may spill into 20 extra per minute. Any algorithms work, sync or async (in-memory, Redis):

const now = s.timeProvider.now();
new RateLimiterStrategy(new BurstyRateLimiter(new WindowCounter(10, 1000, 1, now), new WindowCounter(20, 60_000, 1, now)), { queueTimeout: '1s' }, s);

BurstyRateLimiter
(
new
new WindowCounter(permitLimit: number, windowMs: number, segments: number, origin: number, alignment?: "fixed" | "first-request"): WindowCounter

Segmented sliding window: the window is split into segments buckets and slides one bucket at a time. With 1 segment it is a fixed window. Windows are aligned to origin; with 'first-request' alignment a new window starts at the first request after the window empties instead.

WindowCounter
(10, 1_000, 1,
const now: number
now
), // permitLimit, windowMs, segments (1 = fixed), origin
new
new WindowCounter(permitLimit: number, windowMs: number, segments: number, origin: number, alignment?: "fixed" | "first-request"): WindowCounter

Segmented sliding window: the window is split into segments buckets and slides one bucket at a time. With 1 segment it is a fixed window. Windows are aligned to origin; with 'first-request' alignment a new window starts at the first request after the window empties instead.

WindowCounter
(20, 60_000, 1,
const now: number
now
),
),
{
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
: '1s' },
s: StrategySource
s
,
),
'BurstyRateLimiter',
)
.
ResiliencePipelineBuilder<any, never, never>.build(): ResiliencePipeline<any, never>
build
();

Both parts can be any RateLimiterAlgorithm, sync or async (in-memory or Redis). permitLimit is the larger of the two, so a weight only the burst pool can hold still gets in. Penalty / reward hit the primary; a full reset (control.delete()) also refills the burst pool.

Insurance: surviving a remote limiter outage

Section titled “Insurance: surviving a remote limiter outage”

With a remote algorithm (Redis), a store outage would fail every call. insurance answers instead when the remote acquire throws or takes longer than timeout, and reports OnRateLimiterInsuranceUsed. Size it per process: the shared limit divided by the number of instances.

const
const pipeline: ResiliencePipeline<any, never>
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
()
.
PipelineBuilder<any, never, never>.addStrategy(create: StrategyFactory<any>, name?: string): ResiliencePipelineBuilder<any, never, never>

Plug in your own strategy.

addStrategy
((
s: StrategySource
s
) =>
new
new RateLimiterStrategy<any>(algorithm: RateLimiterAlgorithm, options: PermitRateLimiterOptions, source: StrategySource): RateLimiterStrategy<any>

Runs any RateLimiterAlgorithm as a strategy: weights, FIFO waiting up to queueTimeout, block, allow/deny, insurance, telemetry.

RateLimiterStrategy
(
const redisLimiter: RateLimiterAlgorithm
redisLimiter
,
{
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
: '1s',
PermitRateLimiterOptions.insurance?: {
algorithm: RateLimiterAlgorithm;
timeout?: Duration;
} | undefined

In-memory fallback for a remote algorithm: when acquire throws or exceeds timeout, algorithm answers instead and OnRateLimiterInsuranceUsed is reported. Size it per process (e.g. the shared limit / instances).

insurance
: {
algorithm: RateLimiterAlgorithm
algorithm
: new
new WindowCounter(permitLimit: number, windowMs: number, segments: number, origin: number, alignment?: "fixed" | "first-request"): WindowCounter

Segmented sliding window: the window is split into segments buckets and slides one bucket at a time. With 1 segment it is a fixed window. Windows are aligned to origin; with 'first-request' alignment a new window starts at the first request after the window empties instead.

WindowCounter
(250, 60_000, 10,
var Date: DateConstructor

Enables basic storage and retrieval of dates and times.

Date
.
DateConstructor.now(): number

Returns the number of milliseconds elapsed since midnight, January 1, 1970 Universal Coordinated Time (UTC).

now
()),
timeout?: Duration | undefined
timeout
: '50ms' } }, // 4 instances
s: StrategySource
s
,
),
)
.
ResiliencePipelineBuilder<any, never, never>.build(): ResiliencePipeline<any, never>
build
();

A weight above the smaller insurance limit is charged the whole insurance limit rather than failing.

In serverless or across deploys, a fresh limiter means a fresh quota. snapshot() returns JSON-serializable state; restore() loads it, aging it by the time passed (windows expire, tokens refill, blocks run out). The partitioned limiter snapshots per key, snapshot(n) keeping the n most recently used.

const
const pipeline: ResiliencePipeline<any, RateLimiterRejectedError>
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>.addSlidingWindowRateLimiter(options: SlidingWindowRateLimiterOptions): Next<any, never, never, RateLimiterRejectedError>

Sliding-window rate limit: smooths the burst a fixed window allows at its edges.

addSlidingWindowRateLimiter
({
FixedWindowRateLimiterOptions.permitLimit: number

Permits granted per window.

permitLimit
: 100,
FixedWindowRateLimiterOptions.window: Duration
window
: '1m' }).
ResiliencePipelineBuilder<any, RateLimiterRejectedError, never>.build(): ResiliencePipeline<any, RateLimiterRejectedError>
build
();
const
const limiter: SlidingWindowRateLimiterStrategy<unknown>
limiter
=
const pipeline: ResiliencePipeline<any, RateLimiterRejectedError>
pipeline
.
ResiliencePipeline<any, RateLimiterRejectedError>.getStrategy<SlidingWindowRateLimiterStrategy<unknown>>(nameOrKind: string): SlidingWindowRateLimiterStrategy<unknown> | undefined

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

getStrategy
<
class SlidingWindowRateLimiterStrategy<T>

permitLimit in any window-long span (to one segment's precision): no 2x burst at window edges.

SlidingWindowRateLimiterStrategy
<unknown>>('SlidingWindowRateLimiter')!;
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');
if (
const saved: string | null
saved
)
const limiter: SlidingWindowRateLimiterStrategy<unknown>
limiter
.
RateLimiterStrategy<unknown>.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
)); // throws TypeError on a malformed snapshot
// ... on shutdown, or after each request:
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',
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: SlidingWindowRateLimiterStrategy<unknown>
limiter
.
RateLimiterStrategy<unknown>.snapshot(): RateLimiterSnapshot

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

snapshot
()));

A sliding-window snapshot only restores into a limiter with the same segmentsPerWindow.

Every limiter is RateLimiterStrategy (waiting, weights, block, allow/deny, insurance, telemetry) around a RateLimiterAlgorithm (admission only). Implement the interface to bring your own, e.g. over a database; acquire may be async.

import { type
type AcquireResult = {
readonly ok: true;
readonly remaining?: number;
readonly resetAfter?: number;
} | {
readonly ok: false;
readonly retryAfter: number;
}

On success, remaining and resetAfter (ms until everything used so far is released) feed getRateLimitInfo, when known.

AcquireResult
, type
(alias) interface RateLimiterAlgorithm
import RateLimiterAlgorithm

The admission logic of a rate limiter, without waiting or telemetry. The in-memory WindowCounter implements it; implement it over Redis (one Lua script per acquire) to share a limit between processes. acquire may be async.

RateLimiterAlgorithm
,
class RateLimiterStrategy<T>

Runs any RateLimiterAlgorithm as a strategy: weights, FIFO waiting up to queueTimeout, block, allow/deny, insurance, telemetry.

RateLimiterStrategy
,
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';
// Allows `permitLimit` per calendar day (UTC).
class
class DailyQuota
DailyQuota
implements
(alias) interface RateLimiterAlgorithm
import RateLimiterAlgorithm

The admission logic of a rate limiter, without waiting or telemetry. The in-memory WindowCounter implements it; implement it over Redis (one Lua script per acquire) to share a limit between processes. acquire may be async.

RateLimiterAlgorithm
{
#day = '';
#used = 0;
constructor(readonly
DailyQuota.permitLimit: number
permitLimit
: number) {}
DailyQuota.acquire(permits: number, now: number): AcquireResult

Takes permits if they fit, else says how many ms until they would. permits is never above permitLimit.

acquire
(
permits: number
permits
: number,
now: number
now
: number):
type AcquireResult = {
readonly ok: true;
readonly remaining?: number;
readonly resetAfter?: number;
} | {
readonly ok: false;
readonly retryAfter: number;
}

On success, remaining and resetAfter (ms until everything used so far is released) feed getRateLimitInfo, when known.

AcquireResult
{
const
const day: string
day
= new
var Date: DateConstructor
new (value: number | string | Date) => Date (+3 overloads)
Date
(
now: number
now
).
Date.toISOString(): string

Returns a date as a string value in ISO format.

toISOString
().
String.slice(start?: number, end?: number): string

Returns a section of a string.

@param ― start The index to the beginning of the specified portion of stringObj.

@param ― end The index to the end of the specified portion of stringObj. The substring includes the characters up to, but not including, the character indicated by end. If this value is not specified, the substring continues to the end of stringObj.

slice
(0, 10);
if (
const day: string
day
!== this.#day) {
this.#day =
const day: string
day
;
this.#used = 0;
}
const
const resetAfter: number
resetAfter
=
var Date: DateConstructor

Enables basic storage and retrieval of dates and times.

Date
.
DateConstructor.parse(s: string): number

Parses a string containing a date, and returns the number of milliseconds between that date and midnight, January 1, 1970.

@param ― s A date string

parse
(`${
const day: string
day
}T00:00:00Z`) + 86_400_000 -
now: number
now
;
if (this.#used +
permits: number
permits
> this.
DailyQuota.permitLimit: number
permitLimit
) return {
ok: false
ok
: false,
retryAfter: number
retryAfter
:
const resetAfter: number
resetAfter
};
this.#used +=
permits: number
permits
;
return {
ok: true
ok
: true,
remaining?: number | undefined
remaining
: this.
DailyQuota.permitLimit: number
permitLimit
- this.#used,
resetAfter?: number | undefined
resetAfter
};
}
}
const
const pipeline: ResiliencePipeline<any, never>
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
()
.
PipelineBuilder<any, never, never>.addStrategy(create: StrategyFactory<any>, name?: string): ResiliencePipelineBuilder<any, never, never>

Plug in your own strategy.

addStrategy
((
s: StrategySource
s
) => new
new RateLimiterStrategy<any>(algorithm: RateLimiterAlgorithm, options: PermitRateLimiterOptions, source: StrategySource): RateLimiterStrategy<any>

Runs any RateLimiterAlgorithm as a strategy: weights, FIFO waiting up to queueTimeout, block, allow/deny, insurance, telemetry.

RateLimiterStrategy
(new
constructor DailyQuota(permitLimit: number): DailyQuota
DailyQuota
(1000), {},
s: StrategySource
s
), 'DailyQuota')
.
ResiliencePipelineBuilder<any, never, never>.build(): ResiliencePipeline<any, never>
build
();
Member Required Description
permitLimit yes Largest weight ever granted. Larger requests are rejected with PermitLimitExceededError before acquire.
acquire(permits, now) yes Take the permits if they fit: { ok: true, remaining?, resetAfter? } (both needed for getRateLimitInfo), or { ok: false, retryAfter } in ms. A rejection must take nothing.
adjust(points, now) no Charge (> 0) or refund (< 0) without an admission check; -Infinity empties. Enables penalty / reward / delete.
snapshot(now) / restore(state, now) no Enable snapshot() / restore().

WindowCounter and TokenBucket are exported for reuse (insurance, bursty).

Telemetry event Callback When
OnRateLimiterRejected onRejected Any rejection. Args: { context, retryAfter }.
OnRateLimiterInsuranceUsed – The remote algorithm failed or timed out; insurance answered. Args: { context, error }.
Error code HTTP When
RateLimiterRejectedError RATE_LIMITED 429 Over the limit (retryAfter in ms), blocked, or the wait queue is full (retryAfter undefined).
PermitLimitExceededError PERMIT_LIMIT_EXCEEDED 429 permits above the limiter’s maximum: waiting can never help. Has permits and permitLimit.
RateLimiterDeniedError RATE_LIMIT_DENIED 403 Matched deny.

All three extend RateLimiterRejectedError, so isRateLimitError() and isRejection() match them. The default retry predicate does not retry rejections. To wait out a limit instead, use queueTimeout. For outbound quotas see calling rate-limited APIs.