Status and limitations
Implemented features, changes in 0.1.2, and remaining limits on scheduling, storage, and bundle size.
The current version is 0.5.1.
Check the changelog for version history and releases for published versions.
Included
createValvewithrun,wrap,wrapFetch,observe,pause,resume,stats,on,idle, andclose.- GCRA and fixed-window rate rules, with several rules checked atomically on the store's clock.
- Memory and Redis/Valkey stores. An optional subscriber wakes queues on shared pause, resume, and concurrency-slot release notifications.
- Local concurrency and global concurrency through renewable leases.
- Local priorities, job cost,
maxQueue,maxWait, andAbortSignalcancellation. - Shared pauses from
Retry-After, IETFRateLimit, andx-ratelimit-*headers, plus backoff for rate limit responses without usable headers. wrapFetchretries for 429 responses and 503 responses carrying a validRetry-After, defaulting to GET, HEAD, OPTIONS, PUT and DELETE. Other methods requireretryUnsafeMethods: true. Streamedinit.bodyrequests are not retried. Every response still updates provider feedback.- ESM and CommonJS entry points with TypeScript declarations, and zero runtime or peer dependencies. Applications supply their own Redis client.
Scoped admission in the 0.4 series
Named rules with scope and per-job scopes support atomic shared and
per-identity admission in memory and Redis/Valkey. Eligible identities can
proceed past blocked jobs. This extension preserves the static store path;
custom stores opt in through acquireScoped. Targeted pause/resume is deferred:
feedback remains Valve-ID-wide. See Scoped rate limits.
Changes in 0.1.2
The footprint update minifies embedded Redis Lua at build time and lets bundlers discard unused default feedback readers. Public exports, types, rate algorithms, and store behavior are preserved.
In the paired Node.js 24 measurements, the built Redis ESM entry decreased
from 3,521 to 3,151 gzip bytes. A consumer importing only retryAfter()
decreased from 1,089 to 445 gzip bytes. Core size is unchanged.
These are bundle-size improvements. The measurements do not establish a general startup, throughput, or latency improvement. See the 0.1.2 changelog entry and the footprint report for the measurement settings, regression controls, and build-time tradeoffs.
Not included
- Postgres or other built-in stores. You can implement a custom store.
- Provider presets with known quotas.
- Durable queued jobs. Waiting work is lost when its process exits.
- Syncing a provider's reported
remainingcount into the shared budget. Feedback produces pauses; it does not replace the configured rate rules. - Cross-process fairness or queue ordering.
- Runtime changes to rate rules. Use a coordinated policy migration; a new instance with the same Valve ID must use the pinned policy.
Known limitations
Scheduling drift
Under GCRA with burst: 1, timer delays accumulate under a saturated queue.
The original 5ms p99 ideal-sequence delay target remains unmet. valve
never starts work early to compensate for accumulated delay.
Version 0.1.1 removed positive jitter from local rate-eligibility waits and accounted for elapsed asynchronous acquisition time when scheduling the next check. Every check still requires atomic store admission. Global concurrency polling, shared pauses, and outage backoff retain their behavior.
The paced drift report contains paired measurements across Node.js 20, 22, and 24, including the additional denied checks introduced by the asynchronous timing adjustment. These scheduler measurements do not predict production HTTP throughput.
Bundle-size targets
The 0.1.2 footprint report records these minified and gzipped built ESM entry sizes, using Node.js 24.21.0 and esbuild 0.28.2:
| Entry | Recorded size | Original target | Status |
|---|---|---|---|
| Core | 8,358 bytes | 4,096 bytes | Above target |
| Redis | 3,151 bytes | 3,072 bytes | Above target |
| Headers | 1,130 bytes | 2,048 bytes | Within target |
Those historical measurements predate scoped admission. The scoped extension
uses source-entry CI ceilings of 12,000 bytes for core, 6,200 for Redis, and
2,048 for headers. It adds identity validation, a delayed queue, and a second
Lua script; it does not meet the original core or Redis size targets. See the
scoped benchmark report
for current sizes and measured growth. Consumer sizes depend on which exports
remain reachable after bundling; the full headers entry is larger than a
consumer importing only retryAfter().
Distributed guarantees
- Admission and arrival differ. Rate bounds apply at atomic store admission. Network delays can bunch requests together before they reach the provider.
- Pauses affect subsequent grants. Already-issued grants can still start.
- Leases need cooperative jobs. A worker stalled beyond its lease can outlive its ownership. Jobs must honor cancellation, and strict partition safety requires external fencing.
- Fail-open gives up shared guarantees. With
onStoreError: 'open', work proceeds without shared rate admission while the store is unavailable. Local concurrency still applies. Global concurrency rejects this policy.
Read Guarantees before choosing rate rules, concurrency scope, and store-error behavior. The benchmark comparison links the recorded performance evidence and remaining work.