import { S as Store, C as Clock, T as Transform } from './types-DKirIBQt.cjs'; /** * The minimal slice of a `pg` (node-postgres) connection pool ThrottleKit needs. A `pg.Pool` * satisfies this structurally, so you pass one directly — no adapter. Any compatible pool (same * method shapes) works too. */ interface PgPoolLike { /** Acquire a dedicated client for a multi-statement transaction. */ connect(): Promise; /** Run a single statement on a pooled connection (used for schema setup, reset, and sweeps). */ query(text: string, values?: unknown[]): Promise; } /** A checked-out pool client. Mirrors `pg`'s `PoolClient`. */ interface PgClientLike { query(text: string, values?: unknown[]): Promise; /** Return the client to the pool. Pass a truthy arg to destroy a broken connection. */ release(err?: unknown): void; } /** The slice of a `pg` query result we read: just the rows. */ interface PgQueryResultLike { rows: unknown[]; } interface PostgresStoreOptions { /** A `pg.Pool` (or compatible). ThrottleKit never ends a pool it does not own. */ pool: PgPoolLike; /** * Unquoted table identifier holding the limiter state. Validated against * `^[A-Za-z_][A-Za-z0-9_]*$` (optionally `schema.table`) since identifiers cannot be * parameterized. Default `"throttlekit"`. */ table?: string; /** Storage key namespace, prefixed as `prefix:key`. */ prefix?: string; /** Create the table and its expiry index on first use. Default `true`. */ autoCreate?: boolean; /** * Interval in ms for the background sweep that reclaims expired rows. `0` disables it (rely on * lazy expiry — expired rows are already invisible to reads). Default `60_000`. */ sweepIntervalMs?: number; /** * Time source for expiry. Expired rows are filtered on read and reclaimed by the sweep, so this * is the clock that decides "expired". Defaults to {@link systemClock}; inject a `ManualClock` to * drive expiry deterministically in tests. */ clock?: Clock; } /** * Distributed store backed by PostgreSQL — no Redis required. * * Every backend implements one primitive, {@link Store.apply}: an atomic read-modify-write. This * store runs the limiter's **existing pure JS transform** (the same code the in-memory store runs; * there is no Postgres-specific algorithm to keep in sync) inside a transaction, serialized per key * by a transaction-scoped **advisory lock**: * * ```text * BEGIN * SELECT pg_advisory_xact_lock(hashtextextended(key, 0)) -- per-key critical section * SELECT state WHERE key = $1 AND expires_at > now -- lazy expiry on read * * INSERT .. ON CONFLICT (key) DO UPDATE -- persist if the transform asks * COMMIT -- releases the advisory lock * ``` * * The advisory lock (not `SELECT … FOR UPDATE`) is deliberate: `FOR UPDATE` cannot lock a row that * does not exist yet, so two first-touch transactions on a new key could race; an advisory lock * keyed by the key's hash serializes them whether or not the row exists, and auto-releases at * `COMMIT`/`ROLLBACK` so an error can never leak it. (Hash collisions only over-serialize unrelated * keys very rarely — correctness is unaffected.) This makes concurrent applies on one key atomic: * N concurrent increments land exactly N, like Redis. * * **State** is stored as the same JSON text the Redis optimistic-concurrency path writes, so a * double round-trips as the exact IEEE-754 value and decisions stay bit-identical across backends. * * **Expiry** mirrors how Redis actually behaves: expiry is keyed off this store's {@link Clock} * (Redis uses its *server* clock), independent of the limiter's `now`. That is safe because every * built-in strategy is idempotent w.r.t. stale state — a TAT in the past clamps to `now`, a bucket * refills, a window resets — so a slightly-late expiry can never change a decision. Expired rows * are invisible to reads immediately; the sweep just reclaims their space. * * Async-only: there is no `applySync`, so `limiter.checkSync` throws (use `await limiter.check`). */ declare class PostgresStore implements Store { #private; constructor(options: PostgresStoreOptions); apply(key: string, transform: Transform): Promise; reset(key: string): Promise; /** Stop the background sweep. Does not end the pool (ThrottleKit does not own it). */ close(): Promise; } export { type PgClientLike, type PgPoolLike, type PgQueryResultLike, PostgresStore, type PostgresStoreOptions };