type SyncTask = (dirPath: string, firstRun?: boolean) => Promise; export default class SyncScheduler { private pending = new Map(); private timers = new Map(); private flushing = false; constructor( private readonly task: SyncTask, private readonly debounceMs: number ) {} schedule(dirPath: string) { const existing = this.timers.get(dirPath); if (existing) clearTimeout(existing); const timer = setTimeout(() => { this.timers.delete(dirPath); this.pending.set(dirPath, false); void this.flush(); }, this.debounceMs); this.timers.set(dirPath, timer); } enqueue(dirPath: string, firstRun?: boolean) { const existing = this.timers.get(dirPath); if (existing) { clearTimeout(existing); this.timers.delete(dirPath); } this.pending.set(dirPath, !!firstRun); void this.flush(); } private async flush() { if (this.flushing) return; this.flushing = true; try { while (this.pending.size > 0) { const batch = [...this.pending.entries()]; this.pending.clear(); for (const [dirPath, firstRun] of batch) { try { await this.task(dirPath, firstRun); } catch (error: any) { console.log("ERROR:", error.message); } } } } finally { this.flushing = false; if (this.pending.size > 0) { void this.flush(); } } } }