import { S as Store, C as Clock, T as Transform } from './types-DKirIBQt.js'; /** A Deno KV key: an array of key parts (e.g. `["throttlekit", "user:42"]`). */ type KvKeyLike = readonly unknown[]; /** The result of a `kv.get`: the value (or `null` if absent) plus its versionstamp (`null` if absent). */ interface KvEntryLike { key: KvKeyLike; value: T | null; /** Opaque per-key version token; `null` means the key does not exist. The CAS check token. */ versionstamp: string | null; } /** A single optimistic-concurrency assertion: "this key is still at this versionstamp". */ interface KvCheckLike { key: KvKeyLike; versionstamp: string | null; } /** The slice of a Deno KV atomic-commit result ThrottleKit reads: whether every check held. */ interface KvCommitResultLike { ok: boolean; } /** The slice of a Deno KV atomic operation ThrottleKit uses (a fluent transaction builder). */ interface AtomicOperationLike { /** Assert each key is still at the given versionstamp; a mismatch fails the whole commit. */ check(...checks: KvCheckLike[]): AtomicOperationLike; /** Stage a write, optionally with a native TTL (`expireIn` milliseconds). */ set(key: KvKeyLike, value: unknown, options?: { expireIn?: number; }): AtomicOperationLike; /** Stage a delete. */ delete(key: KvKeyLike): AtomicOperationLike; /** Apply the staged mutations iff every check held; `ok` is `false` when any check failed. */ commit(): Promise; } /** * The minimal slice of a `Deno.Kv` handle ThrottleKit needs. A real `Deno.Kv` satisfies this * structurally, so you pass it directly — no Deno type dependency in this Node-built package. */ interface DenoKvLike { get(key: KvKeyLike): Promise>; atomic(): AtomicOperationLike; delete(key: KvKeyLike): Promise; } interface DenoKvStoreOptions { /** An open `Deno.Kv` (or compatible). ThrottleKit never closes a handle it is given. */ kv: DenoKvLike; /** Key-prefix part: keys become `[prefix, key]` (vs `[key]`), namespacing one KV across limiters. */ prefix?: string; /** * Bounded retries for the atomic compare-and-set. Default `16`. In-process applies to one key are * coalesced (see {@link DenoKvStore}), so retries are only spent on genuine cross-isolate races. */ maxRetries?: number; /** * Time source for lazy expiry. Defaults to {@link systemClock}; inject a `ManualClock` to drive * expiry deterministically in tests. (Deno KV's native `expireIn` reclaims storage on Deno's own * clock; this clock decides "expired" for reads, keeping decisions correct and deterministic.) */ clock?: Clock; } /** * Distributed store backed by **Deno KV**. * * Deno KV gives a *first-class* atomic primitive — `kv.atomic().check(...).set(...).commit()` with a * per-key **versionstamp** — so the atomic read-modify-write {@link Store.apply} demands is built on * native optimistic concurrency rather than a hand-rolled version column: * * ```text * const { value, versionstamp } = await kv.get(key) -- read; lazy-expire in JS * -- the same pure code every backend runs * await kv.atomic().check({ key, versionstamp }) -- commit iff the key is unchanged… * .set(key, next, { expireIn }).commit() -- …else ok=false ⇒ re-read and retry * ``` * * The `check` asserts the key is still at the versionstamp we read (`null` = "still absent"), so the * commit is a true compare-and-set: a concurrent write from another isolate bumps the versionstamp, * the check fails (`ok: false`), and we re-read and retry. N concurrent increments across the fleet * land exactly N — like Redis `INCR` — with no lock held; an expired entry is overwritten in place by * that same CAS (its versionstamp still matches what we read). * * **In-process coalescing.** The transform is arbitrary JS, so every apply takes the CAS loop; a hot * key hammered from one isolate would CAS-contend with itself and burn retries. Applies to the same * key *from this isolate* are therefore serialized behind a per-key promise chain — one clean commit * each — while the CAS still reconciles genuine cross-isolate races. The chain entry is dropped once * it drains, bounding the lock map by in-flight keys. * * **Expiry.** `set` carries Deno KV's native `expireIn` so KV reclaims storage automatically, *and* * the entry stores an epoch-ms expiry so reads lazily expire on the injected {@link Clock} — which is * what makes expiry deterministic under a `ManualClock` and consistent with the Redis/Postgres * backends. **State** is the same JSON text every other backend writes, so values round-trip as the * exact IEEE-754 double and decisions stay bit-identical across stores. * * Async-only: `limiter.checkSync` throws (use `await limiter.check`). * * @example * ```ts * import { rateLimit, gcra } from "throttlekit"; * import { DenoKvStore } from "throttlekit/deno"; * * const kv = await Deno.openKv(); * const limiter = rateLimit({ * strategy: gcra({ limit: 100, periodMs: 60_000, burst: 20 }), * store: new DenoKvStore({ kv, prefix: "rl" }), * }); * const d = await limiter.check(userId); * ``` */ declare class DenoKvStore implements Store { #private; constructor(options: DenoKvStoreOptions); apply(key: string, transform: Transform): Promise; reset(key: string): Promise; } export { type AtomicOperationLike, type DenoKvLike, DenoKvStore, type DenoKvStoreOptions, type KvCheckLike, type KvCommitResultLike, type KvEntryLike, type KvKeyLike };