/** * Minimal async mutex: serializes async sections via a promise chain. Each * runExclusive call waits for the previous one to settle before starting, so * critical sections never interleave across awaits. Rejections are isolated — * a failing section does not break the chain for later callers. */ export class AsyncMutex { private tail: Promise = Promise.resolve(); runExclusive(fn: () => Promise): Promise { const result = this.tail.then(() => fn()); // Keep the chain alive regardless of this section's outcome. this.tail = result.then(() => undefined, () => undefined); return result; } }