valve_

API reference

createValve options, valve methods, job options, and types.

The declaration files shipped with the package document every type with examples. The generated TypeDoc reference renders them.

createValve

function createValve(options: ValveOptions): Valve;

Creates a valve. Throws ValveError with code INVALID_OPTIONS if an option is invalid. At least one of rate or a finite concurrency is required.

ValveOptions

OptionTypeDefaultDescription
idstringRequiredName of the shared budget. 1 to 200 characters, without { or }.
rateRateRule | RateRule[]NoneOne rule, or a non-empty array of rules.
concurrencynumber | ConcurrencyOptionsInfinityA number limits this instance. The object form can select global scope.
storeStoreA new private memory storeWhere the shared budget lives.
maxQueuenumberInfinityMaximum jobs waiting in this process. Beyond it, run rejects with QUEUE_FULL.
maxWaitDurationNo limitMaximum time a job may wait before starting.
feedbackFeedback | falsedefaultFeedbackReads provider responses. false disables header reading.
maxPauseDuration'15m'Cap on any one pause caused by feedback or backoff.
backoff{ base?: Duration; max?: Duration }{ base: '1s', max: '30s' }Exponential pause after a 429 with no usable header.
onStoreError'wait' | 'throw' | 'open''wait'Behavior when the store fails. See Store failures.
batchnumber32Maximum jobs requested from the store in one call.

RateRule

FieldTypeDefaultDescription
namestringNoneStable rule identity; required for scoped rules.
scopestringNoneJob identity dimension; absent means Valve-wide.
limitnumberRequiredCost units allowed per interval. Positive integer.
intervalDurationRequiredWindow length. At least 1ms.
burstnumber1Units that may start back to back. GCRA only. Positive integer.
algorithm'gcra' | 'fixed-window''gcra'See Choose an algorithm.

Unnamed rules cannot share the same algorithm and interval. Set a unique name to distinguish rules with matching algorithms and intervals. Scoped rules require names and select a job identity through scope. Both fields accept 1-64 ASCII letters, digits, underscores or hyphens, beginning with a letter. See Scoped rate limits. Scoped configurations support at most 16 rules and batch 128 (default 32).

ConcurrencyOptions

FieldTypeDefaultDescription
limitnumberRequiredMaximum running jobs. Positive integer.
scope'local' | 'global''local''global' shares the limit across every valve with the same id.
leaseTtlDuration'30s'Global lease lifetime, renewed every third. Minimum 30ms.

Global scope requires a store that implements release and renew. Both built-in stores do.

Duration

A number of milliseconds, or a string ending in ms, s, m, or h. Decimals are allowed.

250; // 250 ms
('1.5s'); // 1500 ms
('10m'); // 600000 ms
('24h'); // 86400000 ms

Negative values, NaN, Infinity, and unknown units are rejected.

Valve

run

run<R>(fn: (ctx: JobContext) => R, options?: RunOptions<Awaited<R>>): Promise<Awaited<R>>

Queues fn and calls it when every rule and the concurrency limit allow. Resolves or rejects with whatever fn returns or throws. fn receives { signal }, the signal from options.signal (or the lease signal under global concurrency). run never throws synchronously, and never calls fn synchronously.

RunOptions

OptionTypeDefaultDescription
scopesReadonly<Record<string, string>>NoneRequired identities for scoped rules; copied before enqueueing.
costnumber1Units spent from every rule.
prioritynumber0Higher starts first. Integer.
maxWaitDurationValve maxWaitQueue deadline for this job.
signalAbortSignalNoneCancels the job while queued. Passed to fn once running.
observe(result) => ResponseLike | undefinedNoneMaps a resolved result to a response for feedback.

wrap

wrap<A extends unknown[], R>(fn: (...args: A) => R, options?: Omit<RunOptions, 'signal'>): (...args: A) => Promise<Awaited<R>>

Returns a function with the same parameters as fn. Each call goes through the valve with options.

wrapFetch

wrapFetch<F extends FetchLike = typeof fetch>(fetchImpl?: F, options?: WrapFetchOptions<F>): F

Returns a function with the same type as fetchImpl (default globalThis.fetch). It queues each request, observes each response, and retries eligible rate limit responses for GET, HEAD, OPTIONS, PUT and DELETE by default. See Respect provider feedback.

WrapFetchOptions

OptionTypeDefaultDescription
scopesReadonly<Record<string, string>> | (...args) => Readonly<Record<string, string>>NoneFixed identities or a synchronous resolver, snapshotted once across retries.
retriesnumber2Retries after a 429, or a 503 with a valid Retry-After. 0 disables.
retryUnsafeMethodsbooleanfalseOpt in to retries for other methods after verifying provider-specific idempotency.
maxReplayBodySizenumber65536Finite integer byte limit for inherited Request replay. 0 sends once without probing.
costnumber | (...args) => number1Cost per request, or a function of the fetch arguments.
prioritynumber | (...args) => number0Priority per request, or a function of the fetch arguments.
maxWaitDurationValve maxWaitQueue deadline for each attempt.

The request's abort signal, from init.signal or the Request, cancels queued attempts and is passed to fetchImpl for running ones. It also cancels a running Request body probe before fetch begins.

retries counts additional attempts after the initial request. The default of 2 permits up to three total requests. Once the retry limit is exhausted, the final response is returned unchanged.

init.method overrides the Request method; otherwise the method defaults to GET. POST, PATCH and unrecognized methods do not retry unless retryUnsafeMethods: true. A supplied idempotency key does not opt in. All responses still update shared cooldowns. Streamed init.body requests never retry, including with the opt-in enabled. Use a dedicated wrapper for verified operations; see the compatibility notes.

Inherited Request bodies retry only if a probe clone fits within maxReplayBodySize. Probing occurs after admission, holds the concurrency slot, and delays upload until the body ends or exceeds the limit. Larger bodies and bodies exceeding 1,024 chunks are sent once. Empty chunks count toward this second limit. The probe can retain the limit plus one source chunk and stream prefetch. Set maxReplayBodySize: 0 to skip probing and send Request bodies once. Explicit init.body overrides bypass probing of the unused body. See Request bodies.

observe

observe(response: ResponseLike): void

Reads a response with the valve's feedback reader. If it asks for a pause, writes a shared pause in the background. Safe to call with any response.

pause

pause(duration: Duration): Promise<void>

Pauses the valve in every process sharing it. Only extends an existing pause. Resolves once the store records it. Not capped by maxPause.

resume

resume(): Promise<void>

Ends the current pause in every process sharing the valve.

stats

stats(): ValveStats

Returns a snapshot for this process. See Monitor a valve.

on

on<E extends keyof ValveEventMap>(event: E, listener: (payload: ValveEventMap[E]) => void): () => void

Adds a listener and returns a function that removes it. See Events.

idle

idle(): Promise<void>

Resolves when nothing is queued or running in this process.

close

close(options?: { drain?: boolean }): Promise<void>

Stops accepting jobs. Without drain, queued jobs reject with CLOSED. With drain: true, they run first. Resolves when running jobs finish. Repeated calls return the same promise. Never closes a client you supplied.

Valves also implement [Symbol.asyncDispose], which calls close().

id

readonly id: string

The id passed to createValve.

Other exports

ExportEntry pointDescription
memoryStore(options?)valvejsIn-process store. Share one instance to share budgets.
redisStore(options)valvejs/redisSee Redis store.
ValveError, isValveErrorvalvejsSee Errors.
defaultFeedbackBothThe default feedback reader.
Feedback readersvalvejs/headersSee Feedback readers.

memoryStore accepts { clock?: () => number }, a source of epoch milliseconds. It defaults to Date.now.

On this page