2.8 KiB
2.8 KiB
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
IdempotencyCachefrom@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
capacityis a positive integer at construction.
Usage: Basic
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
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)— throwsRangeErrorifcapacityis 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 exceedscapacity.size: number— current key count.
Implementation Notes
- Eviction is FIFO, not strictly LRU — re-
adding an existing key does not promote it. This matches the existing@suaveplan/events.idempotentcache contract (hasisO(1),addisO(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 becauseArray.prototype.shifttypes asT | undefinedeven 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
IdempotencyCachevia a future plugin option (out of scope for this change).