ThrottleKit ships four edge-runtime stores covering the two major serverless platforms. Each store is matched to the strongest atomic primitive its runtime actually offers, so you get exact counting (or an honest approximation) without importing Redis or Postgres into a Worker.Documentation 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.
| Store | Import | Runtime | Atomicity | Exact? |
|---|---|---|---|---|
DurableObjectStore | throttlekit/cloudflare | Cloudflare Workers | blockConcurrencyWhile | ✅ |
D1Store | throttlekit/cloudflare | Cloudflare Workers | Version CAS via conditional UPDATE | ✅ |
KVStore | throttlekit/cloudflare | Cloudflare Workers | None — last-write-wins | ⚠️ approximate |
DenoKvStore | throttlekit/deno | Deno Deploy | atomic().check(versionstamp).commit() | ✅ |
Cloudflare
Durable Object Store
DurableObjectStore is the recommended Cloudflare backend. A Durable Object is a single-threaded actor with strongly-consistent transactional storage. The store wraps the rate-limit transform inside blockConcurrencyWhile, which serializes it against every other event handler in the object — making the read-modify-write atomic with no optimistic-retry loop. N concurrent increments from any number of Workers land exactly N.
Where it runs. Construct the store inside your Durable Object class, from the object’s state:
env.NS.idFromName(key). To enforce a shared global budget, route a bounded key set through a single object.
DurableObjectStore options
The Durable Object’s
state (passed to your object’s constructor). ThrottleKit
uses state.storage for persistence and state.blockConcurrencyWhile for
atomicity. A real DurableObjectState satisfies this structurally — no
@cloudflare/workers-types dependency required.Storage key namespace. Keys are stored as
prefix:key. Useful when one
Durable Object instance backs multiple limiters.Time source for lazy expiry. Defaults to the system clock. Inject a
ManualClock for deterministic tests.D1 Store
D1Store is the right backend when you have a D1 binding in your Worker but are not using a Durable Object. D1 is edge SQLite backed by Cloudflare’s global network. The store uses optimistic concurrency — a version compare-and-set via UPDATE … WHERE key = ? AND version = ? — and coalesces same-isolate applies into a single clean version bump to avoid self-contention.
autoCreate: true (default), the table and index are created on first use:
store.sweep() from a Cron Trigger to delete expired rows and reclaim space. Lazy expiry keeps every read correct without it.
D1Store options
A Cloudflare
D1Database binding (e.g. env.DB). ThrottleKit never closes
a binding it is given.Table name. Validated against
^[A-Za-z_][A-Za-z0-9_]*$ since identifiers
cannot be parameterized.Storage key namespace (
prefix:key).Create the table and index on first use. Set
false when you manage the
schema via Wrangler D1 migrations.Bounded retries for the version CAS. In-process applies are already
coalesced, so retries are spent only on genuine cross-isolate races.
Time source for lazy expiry. Defaults to the system clock.
Workers KV Store
KVStore provides a lightweight, global-CDN-backed rate-limit layer for cases where exact counting is not required and you have no DO or D1 binding. A logical epoch-ms expiry is stored alongside the state to keep window math correct on the reading side; however, KV’s 60-second minimum expirationTtl means idle keys physically linger up to one minute.
KVStore options
The bound KV namespace (e.g.
env.RATELIMIT).Key namespace so one KV namespace can back multiple limiters.
Time source for the logical expiry check. Defaults to the system clock.
Deno KV Store
DenoKvStore is the exact distributed backend for Deno Deploy. Deno KV provides a first-class atomic transaction primitive — kv.atomic().check(versionstamp).commit() — so the compare-and-set is built on native platform semantics rather than a hand-rolled version column. The check asserts the key is still at the versionstamp we read; if another isolate wrote between our read and our write the commit fails (ok: false) and we re-read and retry.
In-process coalescing (a per-key promise chain, identical to the DynamoStore pattern) serializes same-isolate applies so CAS retries are spent only on genuine cross-isolate races.
set operation carries Deno KV’s expireIn option so KV reclaims storage automatically. The entry also stores an epoch-ms expiry for lazy clock-injected expiry on reads — keeping decisions consistent with the Redis and Postgres backends and deterministic under a ManualClock in tests.
DenoKvStore options
An open
Deno.Kv handle (from await Deno.openKv()). ThrottleKit never
closes a handle it is given.Key-prefix part. Keys are stored as
[prefix, key] (a two-element KV key
tuple), namespacing one KV store across multiple limiters.Bounded retries for the versionstamp CAS. In-process applies are coalesced,
so retries are spent only on genuine cross-isolate races.
Time source for lazy expiry. Defaults to the system clock. Inject a
ManualClock for deterministic tests.Choosing a Cloudflare backend
| I have… | Best choice |
|---|---|
| A Durable Object for each identity | DurableObjectStore — no retry loop, lowest latency |
| A D1 binding in my Worker | D1Store — exact, version-CAS, edge SQLite |
| Only Workers KV, coarse protection is fine | KVStore — approximate, but simple and global |
All edge stores are async-only.
limiter.checkSync(key) throws at
runtime — always use await limiter.check(key).Failure behavior
DurableObjectStore: the RMW runs inside the Durable Object — there is no external network hop in the critical section and no retry loop. A DO relocation carries its storage, so there is no data loss.D1StoreandDenoKvStore: if the CAS retries are exhausted under extreme cross-isolate contention, aStoreUnavailableErroris thrown. The limiter’sfailpolicy ("open"or"closed") controls what that means for your application.KVStore: can over-admit under concurrent load — by design. There is no error on concurrent writes; the last write wins.