const MAX_PROCESSED_ENTRY_COUNT = 10_000 const PROCESSED_COMMAND_TTL_MS = 30_000 function pruneExpiredEntries(entries: Map, now: number): Map { return new Map(Array.from(entries.entries()).filter(([, expiresAt]) => expiresAt > now)) } function trimProcessedEntries(entries: Map): Map { if (entries.size <= MAX_PROCESSED_ENTRY_COUNT) { return entries } return new Map( Array.from(entries.entries()) .sort((left, right) => left[1] - right[1]) .slice(Math.floor(entries.size / 2)) ) } function removeSessionEntries(entries: Map, sessionID: string): Map { const sessionPrefix = `${sessionID}:` return new Map(Array.from(entries.entries()).filter(([entry]) => !entry.startsWith(sessionPrefix))) } export interface ProcessedCommandStore { has(commandKey: string): boolean add(commandKey: string): void cleanupSession(sessionID: string): void clear(): void } export function createProcessedCommandStore(): ProcessedCommandStore { let entries = new Map() return { has(commandKey: string): boolean { const now = Date.now() entries = pruneExpiredEntries(entries, now) return entries.has(commandKey) }, add(commandKey: string): void { const now = Date.now() entries = pruneExpiredEntries(entries, now) entries.delete(commandKey) entries.set(commandKey, now + PROCESSED_COMMAND_TTL_MS) entries = trimProcessedEntries(entries) }, cleanupSession(sessionID: string): void { entries = removeSessionEntries(entries, sessionID) }, clear(): void { entries.clear() }, } }