/** * Bounded replay cache for Discord's globally unique native message IDs. * Durable ingress idempotency is added with Tess's canonical inbox/outbox work. */ export class DiscordReplayProtector { private readonly claimedAt = new Map(); constructor( private readonly ttlMs = 15 * 60 * 1000, private readonly maxEntries = 10_000, ) {} get size(): number { return this.claimedAt.size; } /** Claims an ID exactly once within its bounded retention window. */ claim(messageId: string, now = Date.now()): boolean { this.prune(now); if (this.claimedAt.has(messageId)) return false; this.claimedAt.set(messageId, now); this.evictOverflow(); return true; } private prune(now: number): void { for (const [messageId, claimedAt] of this.claimedAt) { if (now - claimedAt >= this.ttlMs) this.claimedAt.delete(messageId); } } private evictOverflow(): void { while (this.claimedAt.size > this.maxEntries) { const oldestMessageId = this.claimedAt.keys().next().value; if (oldestMessageId === undefined) return; this.claimedAt.delete(oldestMessageId); } } }