import { windowsPathKey } from './pathing'; export interface CoalescedEventInput { projectRoot: string; payload: T; } interface PendingEvent { timer: NodeJS.Timeout; projectRoot: string; payload: T; } export class ProjectEventCoalescer { private readonly pending = new Map>(); private readonly debounceMs: number; private readonly onFlush: (event: CoalescedEventInput) => void; constructor(debounceMs: number, onFlush: (event: CoalescedEventInput) => void) { this.debounceMs = debounceMs; this.onFlush = onFlush; } queue(projectRoot: string, payload: T): void { const projectKey = windowsPathKey(projectRoot); const existing = this.pending.get(projectKey); if (existing) { clearTimeout(existing.timer); existing.projectRoot = projectRoot; existing.payload = payload; existing.timer = setTimeout(() => this.flush(projectKey), this.debounceMs); return; } this.pending.set(projectKey, { projectRoot, payload, timer: setTimeout(() => this.flush(projectKey), this.debounceMs), }); } cancel(projectRoot: string): void { const projectKey = windowsPathKey(projectRoot); const pending = this.pending.get(projectKey); if (!pending) { return; } clearTimeout(pending.timer); this.pending.delete(projectKey); } cancelAll(): void { for (const pending of this.pending.values()) { clearTimeout(pending.timer); } this.pending.clear(); } pendingCount(): number { return this.pending.size; } private flush(projectKey: string): void { const pending = this.pending.get(projectKey); if (!pending) { return; } this.pending.delete(projectKey); this.onFlush({ projectRoot: pending.projectRoot, payload: pending.payload, }); } }