# `idempotency-cache.ts` — bounded set-based idempotency cache ## Purpose Provides the default `IdempotencyCache` implementation used by `eventBusService` to suppress duplicate publishes. The cache satisfies the `IdempotencyCache` interface from `@suaveplan/events` (`has` / `add`), so any operator that needs a different storage strategy (Redis, in-memory LRU with TTL, etc.) drops in their own implementation without changing the service contract. The implementation is intentionally minimal: a `Set` plus a parallel insertion-order array gives O(1) `has`/`add` with bounded memory. The default capacity (4096 keys) tolerates several minutes of moderate publish traffic before evicting the oldest keys. ## Features - Implements `IdempotencyCache` from `@suaveplan/events`. - O(1) lookup, O(1) amortised insert. - Hard upper-bound on memory — the oldest key is evicted when the population exceeds `capacity`. - Validates `capacity` is a positive integer at construction. ## Usage: Basic ```ts import { LimitedSetCache } from "@suaveplan/api-plugin-event-bus/services/event-bus-service"; const cache = new LimitedSetCache(1024); cache.add("evt-1"); cache.has("evt-1"); // true ``` ## Usage: Advanced ```ts import { LimitedSetCache } from "@suaveplan/api-plugin-event-bus/services/event-bus-service"; import { idempotent } from "@suaveplan/events"; const cache = new LimitedSetCache(2); const handler = idempotent( (payload) => console.log("once", payload), { keyOf: (p) => String((p as { id: string }).id), cache } ); handler({ id: "a" }); // logs handler({ id: "a" }); // suppressed handler({ id: "b" }); handler({ id: "c" }); // adds "c", evicts "a" handler({ id: "a" }); // logs again — "a" was evicted ``` ## API Reference ### `class LimitedSetCache implements IdempotencyCache` - `constructor(capacity: number)` — throws `RangeError` if `capacity` is not a positive integer. - `has(key: string): boolean`. - `add(key: string): void` — no-op when the key is already present; evicts oldest when the population exceeds `capacity`. - `size: number` — current key count. ## Implementation Notes - Eviction is FIFO, not strictly LRU — re-`add`ing an existing key does not promote it. This matches the existing `@suaveplan/events.idempotent` cache contract (`has` is `O(1)`, `add` is `O(1)`) and avoids the doubly-linked-list overhead an LRU would incur for the same upper-memory guarantee. - The dead-store guard (`if (oldest !== undefined)`) is required because `Array.prototype.shift` types as `T | undefined` even when the call site knows the array is non-empty. - The cache lives in-process — restarting the API server clears it. Operators needing durable dedup wire a Redis-backed `IdempotencyCache` via a future plugin option (out of scope for this change).