first commit
This commit is contained in:
@@ -0,0 +1,77 @@
|
||||
# `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).
|
||||
Reference in New Issue
Block a user