A strategy is the algorithm that decides whether a request should be allowed. In ThrottleKit, every strategy is a pure function ofDocumentation Index
Fetch the complete documentation index at: https://mintlify.com/AmeyaBorkar/throttlekit/llms.txt
Use this file to discover all available pages before exploring further.
(state, now, cost) — no I/O, no clock reads, no side effects. This design makes strategies trivially testable, portable to an atomic Redis Lua script, and provably bit-identical across all backends.
The Strategy interface
check method is the pure transition. It takes the previous serialized state (or undefined on first touch), the current timestamp in epoch milliseconds, and the cost of the request, and returns the next state and a Decision. The store handles persistence atomically; the strategy never touches I/O.
The cost parameter
Everycheck and checkSync call accepts an optional cost (default 1). Cost lets a single admission count for more than one unit — useful for:
- Bulk endpoints: a batch import endpoint that processes 100 records might charge
cost: 100. - LLM token budgets: charge the number of tokens consumed (known post-hoc; see
tokenBudget). - Tiered pricing: POST requests are more expensive than GETs, so
cost: (req) => req.method === "POST" ? 5 : 1.
RangeError for non-positive or non-finite values).
Built-in strategies
gcra — Generic Cell Rate Algorithm (default)
State: one timestamp per key (number). Memory: O(1).
GCRA tracks a Theoretical Arrival Time (TAT) — the earliest moment a perfectly-paced next request could arrive. One number per key, no buckets to refill, no arrays to trim. Smooth pacing with a configurable burst allowance.
burst requests instantaneously, then paces at exactly limit / periodMs. Use GCRA when you want smooth pacing, tiny state, and a great general default. It is the recommended starting point for most applications.
tokenBucket — explicit token count
State:{ tokens, last }. Memory: O(1).
A bucket of capacity tokens that refills at refillPerSec tokens per second. Each request spends cost tokens. GCRA is mathematically equivalent for burst = capacity, but token bucket surfaces a literal token count in remaining, which some teams prefer for client UX.
fixedWindow — aligned time windows
State:{ start, count }. Memory: O(1).
A counter per aligned windowMs period. Cheapest and simplest. Known limitation: up to 2× the limit may be admitted across a window boundary — two bursts of limit landing just before and just after the reset. This is a documented, understood trade-off, not a bug.
INCR + PEXPIRE (first hit) or INCR (subsequent hits) — the most efficient possible distributed implementation.
slidingWindow — near-exact rolling window
State:S sub-buckets (default 10). Memory: O(buckets).
The window is divided into buckets sub-buckets. The count is the sum of sub-buckets overlapping [now − windowMs, now], with the oldest partial bucket weighted by its overlap fraction. Error is bounded by one bucket width (≈ 1/buckets of the window) while memory stays O(buckets) regardless of the limit. This is the sweet spot between fixed window (cheap, 2× boundary error) and exact log (precise, O(limit) memory).
slidingWindowLog — exact rolling window
State: ascendingnumber[] of hit timestamps. Memory: O(limit).
Stores the timestamp of every accepted hit and counts those within the trailing windowMs. Exact — no boundary approximation — but memory grows with the limit. Use for low or moderate limits where precision matters (e.g., 5 password-reset attempts per hour).
leakyBucket — traffic shaping / queueing
State: scheduling timestamps. Memory: O(1). A shaping variant that delays rather than rejects, smoothing output to a fixed drain rate. Useful for outbound rate shaping (e.g., a third-party API budget).leakyBucket returns a Shaper — a standalone API with reserve, reserveSync, and schedule methods. shaper.schedule(key) resolves after the paced delay, or rejects with QueueFullError if the wait would exceed maxQueueMs.
adaptiveConcurrency — backpressure from latency
State: rolling latency measurements. Memory: O(window size). Not a rate — a dynamically inferred ceiling on in-flight requests. Modeled on TCP congestion control and Netflix’sconcurrency-limits. Measures the latency gradient (RTT_noload / RTT_actual) and adjusts the concurrency limit with AIMD: multiplicative decrease when latency degrades, additive increase when the service is healthy.
quota — billing-period budgets
State: per-period counters. Memory: O(1). Billing-period budgets with calendar-aware reset cadences. Supports"calendar-month", "calendar-week", "calendar-day", "fixed", and "rolling" periods. Leap-year-correct.
Isomorphic dual-path: JS + Lua, proven bit-identical
Every built-in strategy is authored once as a pure JavaScript transition and compiled to two executors:- JavaScript path — used in-process with
MemoryStore, and as an OCC fallback on any store. - Redis Lua path — a hand-verified atomic script run via
EVALSHAin a single round trip.
(arrivals, costs, clock) timelines through both paths and asserts that every decision in the stream is byte-identical. This is what backs the claim that you can develop and test in-memory with MemoryStore and deploy on Redis without any behavioral surprise.
The key mechanism: all Decision fields are integers (Redis truncates Lua numbers on reply), and internal GCRA/token-bucket state is persisted at full IEEE-754 precision via string.format('%.17g', v) so timestamps round-trip exactly.
Custom strategies do not require a Lua form. Without
lua, the strategy falls back to optimistic concurrency (WATCH/MULTI/EXEC) on Redis — correct everywhere, slightly slower on hot keys. A custom strategy that provides a Lua form can have it conformance-tested with the same vector suite.Choosing the right strategy
| Goal | Strategy |
|---|---|
| Best general default — tiny state, smooth pacing | gcra |
| Explicit “tokens remaining” count, client-friendly UX | tokenBucket |
| Shape / queue outbound calls to a fixed rate | leakyBucket |
| Cheapest coarse cap, boundary burst is acceptable | fixedWindow |
| Exact “N in the last X” at low limits (e.g. 5/hour) | slidingWindowLog |
| Near-exact rolling window at any limit, bounded memory | slidingWindow |
| Billing-period quotas with calendar-aware resets | quota |
| Protect a service from overload when the right rate is unknown | adaptiveConcurrency |