Files
skills/docs/api-stack/api/api-plugin-event-bus/src/services/idempotency-cache.md
2026-05-29 08:20:56 +01:00

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 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

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) — 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-adding 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).