import type { StoreProvider } from '@kubun/db'; import type { Logger } from '@kubun/logger'; /** * The replay-cache shape Enkaku's server consumes (`replay: { cache }`), * restated here so the engine can hand one to every plugin without depending on * the server package. */ export type ReplayCache = { checkAndRecord(key: string, expiresAt: number): Promise; }; export type StoreReplayCacheParams = { /** The top-level store provider. Never a transaction: the check runs outside any. */ stores: StoreProvider; /** Clock in epoch milliseconds, the same one the server's replay check reads. */ now?: () => number; /** * Minimum gap between two purges of expired entries. A purge runs on the first * check after the gap elapses, so an idle device does no work at all. */ purgeIntervalMs?: number; /** Refuse new tokens at this cap. Never evict a live token. Defaults to 100,000. */ maxEntries?: number; logger?: Logger; }; /** * The persisted seed could not be loaded, or holds more live entries than the * memory cap. The cache then refuses every admission. */ export declare class ReplayCacheSeedError extends Error { constructor(message: string, options?: ErrorOptions); } /** * Enkaku's replay cache, persisted in the engine store. * * The server keys each signed message on `:` and refuses * a repeat inside the message's freshness window. Enkaku's default in-memory * cache is per Server and forgets every key on restart, so a message captured * and replayed after a restart, or into a new session's Server, ran twice. The * engine builds one of these and hands it to every plugin, so every * network-reachable server on the device dedups against one ledger, and the * ledger outlives the process. * * Device-wide replay admission for a single process per database. Requests * await the one-time persisted seed, then check and record synchronously in * memory, even while the database is busy. * * Memory retains at most 100,000 entries (configurable), evicting only past the * store's expiry grace, at the purge interval. A full ledger or 1,024 pending * writes refuses new requests rather than forgetting a still-valid token. * A failed or oversized seed rejects `ready()` with `ReplayCacheSeedError` and * refuses every admission for this cache's lifetime. These bounds trade * availability for replay safety. * * Ordered persistence retries once, logging and dropping failed writes without * undoing admission. A crash before persistence can allow that token to replay * after restart, until it expires. This includes writes abandoned at shutdown. */ export declare class StoreReplayCache implements ReplayCache { #private; constructor(params: StoreReplayCacheParams); /** * Seed before serving requests. Rejects with `ReplayCacheSeedError` when the * seed fails or exceeds the cap; admission is then refused, never guessed. */ ready(): Promise; checkAndRecord(key: string, expiresAt: number): Promise; /** Drain first, then abandon blocked writes. An already-issued query may still land. */ dispose(): Promise; }