-
Notifications
You must be signed in to change notification settings - Fork 0
Getting Started
ThrottleKit is a pluggable rate-limiting toolkit: pick an algorithm, a backend, and a framework adapter, and the limiting logic stays the same across all three.
npm i throttlekitPeer dependencies are optional — install only the ones for the adapters you use (ioredis for throttlekit/redis, pg for throttlekit/postgres, express, @opentelemetry/api, …). The Web fetch adapter (throttlekit/fetch) needs none.
In-memory GCRA — no infrastructure required:
import { rateLimit, gcra } from "throttlekit";
const limiter = rateLimit({
// 100 requests per minute, with an instantaneous burst allowance of 20.
strategy: gcra({ limit: 100, periodMs: 60_000, burst: 20 }),
// `store` defaults to a fresh in-process MemoryStore.
});
const decision = await limiter.check(userId); // cost defaults to 1
if (!decision.allowed) {
// 429 with Retry-After: Math.ceil(decision.retryAfterMs / 1000)
throw new Error(`rate limited; retry in ${decision.retryAfterMs}ms`);
}Every check returns an immutable Decision:
interface Decision {
allowed: boolean; // permit or reject
limit: number; // effective ceiling (burst capacity or window quota)
remaining: number; // whole units left before the next rejection (never negative)
resetAt: number; // epoch-ms when the limiter is fully replenished
retryAfterMs: number; // 0 when allowed; otherwise how long to wait
}With the in-memory store you also get a synchronous, zero-await fast path, and a cost for weighting:
const d = limiter.checkSync(userId); // MemoryStore only; throws on async stores
await limiter.check(userId, 5); // this request costs 5 unitsEach key is evaluated at one consistent timestamp and returned in input order:
const decisions = await limiter.checkMany([ip, userId, apiKey]); // Decision[] in input order
const all = limiter.checkManySync(keys); // MemoryStore: one loop, no promisesOn an async store the checks fire concurrently — and collapse to a single round trip on clients that pipeline same-tick commands (node-redis, or ioredis with enableAutoPipelining).
Two non-consuming reads — missing from almost every limiter — for client retry/UX and capacity planning. Neither spends anything, on any backend:
const p = await limiter.peek(userId); // the current Decision (remaining now, resetAt) — spends nothing
limiter.peekSync(userId); // synchronous fast path (MemoryStore)
const f = await limiter.forecast(userId, 1);
// { spendableNow, nextReplenishAt, fullAt }:
// spendableNow — how many cost-1 requests are admissible right now
// nextReplenishAt — epoch-ms when capacity next increases (a window reset, or the next token)
// fullAt — epoch-ms when fully replenished to the ceilingpeek returns the same Decision shape as check, but its remaining/resetAt describe the current state rather than a post-consume projection. Both methods are implemented for every built-in strategy and work in-memory (sync + async) and over a distributed store: on Redis the read is a single round trip that reads the stored state without running the consuming Lua, so it can never spend a unit. (They're optional on the Limiter interface — present on rateLimit, absent where a non-consuming read isn't well-defined.)
Time is injected everywhere — no Date.now() hides inside an algorithm — so every limit is reproducible to the millisecond. This is what makes the entire test suite deterministic.
import { rateLimit, gcra, ManualClock, MemoryStore } from "throttlekit";
const clock = new ManualClock(0);
const limiter = rateLimit({
strategy: gcra({ limit: 2, periodMs: 1_000 }), // burst defaults to 2
clock,
store: new MemoryStore({ clock }),
});
(await limiter.check("k")).allowed; // true
(await limiter.check("k")).allowed; // true
(await limiter.check("k")).allowed; // false — burst exhausted
clock.advance(500); // one emission interval (1000/2) later
(await limiter.check("k")).allowed; // trueManualClock exposes .advance(ms), .set(ms), .now().
Runnable versions of every feature live in examples/.
ThrottleKit · MIT · 1.0 — API frozen under SemVer (Stability)
- Getting Started
- Choosing a strategy
- Frameworks & the edge
- Distributed & provable
- Federation
- Scaling & the Fleet
- Unified admission
- Pillar 4 — Weighted Fair Escrow
- Middleware integration
- Distributed adaptive concurrency
- Advanced limiting
- Overload, fairness & DDoS
- Operations
- Monitoring — ThrottleKit Lens
- Policy Plans
- Replay
- Performance
- Migrating
- Polyglot & Python
- GALE & TALE