| FazBrowse GitHub Viewer | Trending | | Home |
| Tools: [Download Repo ZIP] [Original HTTPS Page] |
| Name | Name | Last commit date | ||
|---|---|---|---|---|
Beyond rate limiting — govern rate, concurrency, and cost, provably. Meter what your LLM spends. Prove what your fleet admits. Counting requests is the easy 10%; ThrottleKit governs the three axes a real request must clear — rate, concurrency, and cost — each behind a bound you can prove.
Two engines do the hard part. GALE (provable distributed leasing) holds global overshoot to a fleet-size-independent bound, machine-checked in TLA⁺. TALE (token-budget escrow) meters an LLM's output tokens — known only as they stream — and bounds them anyway. All on one small core: a 169 ns in-process check (5.9M ops/s, effectively allocation-free), zero runtime dependencies, proven bit-identical across six stores.
Most limiters just count requests — fine in one process, "probably fine" across a fleet, and silent on the two axes that actually cost you money: concurrency and tokens. ThrottleKit states its bounds, and proves them.
And the bound is only the start. GALE and TALE ship as real features, not heuristics — window-coupled leasing, adaptive lease sizing, weighted-fair escrow, distributed adaptive concurrency, unified rate × concurrency × cost admission, and an LLM token-budget stack — each a checked guarantee, every one byte-identically verified. See how GALE & TALE work →
Site: throttlekit.in · Docs: Wiki · Benchmarks · Stability · Design · Component design · Formal model · Scoreboard · Changelog
npm i throttlekitZero runtime dependencies. Peer deps are optional — install only what your stores and adapters use (ioredis/node-redis for throttlekit/redis, pg for throttlekit/postgres, express, …). The Web fetch adapter needs none (Node 18+, Cloudflare, Deno, Bun).
In-memory, no infrastructure — and a real synchronous fast path:
import { rateLimit, gcra } from "throttlekit";
const limiter = rateLimit({
strategy: gcra({ limit: 100, periodMs: 60_000, burst: 20 }), // 100/min, instantaneous burst 20
});
const decision = limiter.checkSync(userId); // sync + allocation-free — or `await limiter.check(userId)`
if (!decision.allowed) {
throw new Error(`rate limited; retry in ${decision.retryAfterMs}ms`);
}Every check returns an immutable Decision — { allowed, limit, remaining, resetAt, retryAfterMs }. Read state without spending it via limiter.peek(key) / limiter.forecast(key).
Going distributed is the same algorithm with a store and a trade-off — lease a batch of credits in one round trip, then serve them locally at in-process speed:
import { twoTier, gcra } from "throttlekit";
import { RedisStore } from "throttlekit/redis";
const limiter = twoTier({
strategy: gcra({ limit: 10_000, periodMs: 60_000, burst: 500 }),
l2: new RedisStore({ client }), // or Postgres, DynamoDB, Deno KV, Cloudflare
mode: "leased", // "strict" | "cached-deny" | "leased"
lease: { batch: 50, windowCoupled: true }, // ~1 round trip / 50 requests; admitted ≤ Limit, any fleet size
});The incumbents are good at what they do — this is what ThrottleKit adds on top. Every row is a shipped, tested ThrottleKit feature; the comparison reflects each library's documented capabilities.
| express-rate-limit | rate-limiter-flexible | @upstash/ratelimit | ThrottleKit | |
|---|---|---|---|---|
| Provable, fleet-size-independent overshoot bound (TLA⁺-checked) | – | – | – | ✓ |
| Synchronous, allocation-free check | – | – | – | ✓ 169 ns |
| One algorithm, proven bit-identical across backends | – | – | – | ✓ (6 stores) |
| Two-tier leasing — amortized round trips, bounded overshoot | – | – | – | ✓ |
| LLM token-budget escrow (post-hoc cost axis) | – | – | – | ✓ (TALE) |
| Unified rate × concurrency × cost in one decision | – | – | – | ✓ |
| Weighted-fair share · overload shedding · fixed-memory DDoS sketch | – | – | – | ✓ |
| Polyglot from one verified core (Python today) | – | – | – | ✓ |
| Live binding-axis monitoring dashboard (which axis is throttling) | – | – | – | ✓ (Lens) |
| Plan a limit change before deploy — replay traffic → allow↔deny diff | – | – | – | ✓ (Policy Plans) |
| Framework / transport adapters | 1 (Express) | a few | – | 13 |
| Zero runtime dependencies | – | – | – | ✓ |
This table is about distributed-correctness guarantees and breadth — the benchmarks (incl. the rows an incumbent wins) are reproducible on your hardware: BENCH.md.
In-process, single hot key, Node 24 / AMD Ryzen AI 9 HX 370 (full methodology, caveats, and head-to-head in BENCH.md):
| Path | Throughput | Latency |
|---|---|---|
| checkSync (GCRA, in-process) | 5.9M ops/s | 169 ns/op, ~0 B/op |
| check (GCRA, async, in-process) | 3.3M ops/s | ~300 ns/op |
| twoTier(leased) over Redis, batch 100 | 66.4k ops/s | 1 round trip / 100 requests |
The honest head-to-head (npm run bench:compare): on Redis, level with rate-limiter-flexible (both one atomic Lua round trip) with a tighter tail; the async in-memory GCRA path edges past it (301 vs 331 ns) while computing a full Decision; a single Postgres check trails a one-statement upsert by design, but twoTier(leased) turns that into a ~35× throughput win under load.
gcra (default — tiny state, smooth pacing, controlled bursts) · tokenBucket · fixedWindow · slidingWindow · slidingWindowLog · leakyBucket (traffic shaping) · adaptiveConcurrency (backpressure when the right rate is unknown) · quota (billing-period budgets: calendar-month/-week/-day, fixed, rolling — leap-year-correct). → Strategies
| Backend | Subpath | Mechanism |
|---|---|---|
| In-memory | (built-in) | lock-free synchronous RMW, timing-wheel expiry |
| Redis | throttlekit/redis | one EVALSHA/check — ioredis, node-redis, or Upstash REST |
| Postgres | throttlekit/postgres | advisory-lock transaction — no Redis required |
| DynamoDB | throttlekit/dynamodb | conditional-write CAS with native TTL |
| Deno KV | throttlekit/deno | atomic versionstamp CAS |
| Cloudflare | throttlekit/cloudflare | Durable Object atomicity + D1 edge SQLite |
Express · Fastify · Koa · Hono · Next · NestJS · SvelteKit · Remix · Elysia · Web fetch (edge) · AWS Lambda · tRPC · gRPC — each its own subpath sharing one options surface and standards headers, plus createEnforcer() for anything else (queues, job runners, custom protocols). Serverless/edge/RPC bindings are dependency-free.
import { expressRateLimit } from "throttlekit/express"; // + /fastify /koa /hono /next /nest /sveltekit /remix /elysia
import { withRateLimit } from "throttlekit/fetch"; // Web fetch — Cloudflare / Deno / Bun / Next edgeFront a distributed store (L2) with a local in-process tier (L1) and choose the trade-off:
| Mode | Network cost | Global accuracy |
|---|---|---|
| strict | 1 round trip / request | Exact — hard quotas, billing |
| cached-deny | 1 round trip / allowed request | Exact allows, local denies — public APIs under abuse |
| leased | ~1 round trip / batch | Provably bounded overshoot — high-throughput internal APIs |
leased trades exactness for throughput with an overshoot you choose: carryover gives admitted ≤ Limit + N·(Batch−1); windowCoupled: true expires credits at the L2 window so admitted ≤ Limit, independent of fleet size N. Set lease.adaptive and each key's batch is sized online at the EOQ optimum (GALE Pillar 2), bound untouched.
Formally verified, not claimed. A TLA⁺ spec is model-checked with TLC (overshoot exactly Limit + N·(Batch−1), tight by counterexample); window-coupling tightens it to exactly Limit, and a Java-free exhaustive checker reproduces both in CI. Details in docs/FORMAL-MODEL.md.
When a store goes down: apply rejects (never silently allows/denies), your fail: "open" | "closed" policy decides, no write lands partially, durable backends never lose committed counts, and twoTier(leased) keeps serving local credits through a brief L2 blip. Full matrix: docs/FAILURE-MODES.md. → Distributed & provable
Primitives that sit upstream of per-key limiters — overload, fairness, and cost:
Changing a limit in production is usually a blind bet — ship it and watch the logs. throttlekit/policy makes it a terraform plan for rate / cost limits: replay your own recorded traffic against a candidate policy and read the exact, per-policy, per-key allow↔deny diff before you deploy.
import { recordLimiter } from "throttlekit/testkit";
import { policy, policySet, corpusFromRecordings, plan, renderPlan, assertPlanAcceptable } from "throttlekit/policy";
// record real traffic against today's limiter…
const rec = recordLimiter({ strategy: "fixedWindow", limit: 3, windowMs: 1000 });
for (let i = 0; i < 6; i++) rec.limiter.checkSync("tenant-a");
// …then ask what tightening to limit 2 would have done — before shipping it
const current = policySet([policy("api", { strategy: "fixedWindow", limit: 3, windowMs: 1000 })]);
const candidate = policySet([policy("api", { strategy: "fixedWindow", limit: 2, windowMs: 1000 })]);
const result = plan(current, candidate, corpusFromRecordings({ api: rec }));
console.log(renderPlan(result)); // "api: 1 allow→deny, 0 deny→allow over 6 arrival(s)…"
assertPlanAcceptable(result, { maxAllowToDeny: 0 }); // throws in CI — the change would 429 a live requestThe baseline is your current policy replayed cold over the recorded arrival timing — not a guess, and never a comparison against a warm production node's exact decisions (a cold replay can't reproduce those). Leaf rate + cost limiters diff exactly; a concurrency / escrow axis is reported not-replayable (observe it live), never faked. Deterministic, and built entirely on throttlekit/testkit — no new moving parts.
ThrottleKit Lens is a built-in, zero-dependency monitoring dashboard that runs right in your terminal — throttlekit-server --config x.yaml --tui, alongside gRPC, with no browser and no metrics backend. It gives every policy the full ops board across eight tabbed views — Overview · Latency · Fairness · Capacity · Guarantee · Cost Room · Replay · Plan — plus the one view no other rate-limiter dashboard renders: live binding-axis attribution. Because unifiedAdmission composes rate × concurrency × cost in one decision, the Lens shows which axis is throttling each key right now — rate, concurrency, cost, or the joint-LP policy lane.
throttlekit-server --config .throttlekit.yaml --tui
# → gRPC on :50051 + ThrottleKit Lens in your terminal (q quit · 1-8/Tab views · p pause)The Lens watches the server's decisions, so it works for Python / Go / any-language clients too. Need that state remotely, or from another language? It's also a read-only gRPC service — the Monitor door (throttlekit.v1.Monitor: GetSnapshot + Watch) — with a Prometheus /metrics endpoint and standard gRPC health, on by default and loopback-bound until you set a secret. A TUI owns the terminal, so the Lens is opt-in (--tui) and needs an interactive TTY; for fully headless monitoring, emit OpenTelemetry → Grafana, including throttlekit.denies_by_axis{lane} (the deliberate axis escape hatch from 1.2.0). → Monitoring
The old browser-based throttlekit-lens npm package is deprecated — monitoring now lives in the terminal (--tui) and over the Monitor door.
Reach the limiter from non-Node services without re-implementing a single algorithm: the Node core stays the only thing that computes a decision, and every other surface is a thin pipe conformance-checked against one set of language-neutral golden vectors.
The proto is the stable polyglot contract; the raw Lua wire is behavior-locked but deliberately not frozen. The full walkthrough is the Polyglot & Python wiki page; design + decision records live in research/polyglot/DESIGN.md.
Dual-path conformance (JS ≡ Lua over thousands of generated timelines) + shrinkable fast-check property passes, atomicity tests (N concurrent checks at limit K ⇒ exactly K allowed), the TLA⁺/TLC model re-checked by an exhaustive JS checker in CI, and federation BFS-twin coverage. All time-dependent tests use ManualClock, so the 1,500+ test suite is deterministic; CI is green across Node 20/22/24, and a bench-regression gate blocks hot-path slowdowns.
ThrottleKit is 1.0 — the public API is frozen under SemVer. STABILITY.md is the promise: the stable core (algorithms, stores, adapters, federation, unified-admission core) only grows additively, the experimental frontier (the joint-LP policy, distributed-concurrency tuning knobs, the learned escrow/sketch layer) is opt-in and carved out, and the freeze is mechanically enforced (type-level surface tests + attw + publint in CI). MIT-licensed, developed in the open.
| Back | FazBrowse Home | New Git URL |