export type VideoCanvasSaveStatus = "saved" | "pending" | "saving" | "error"; /** Keep edits made during a request; never retry a stale revision as an overwrite. */ export class VideoCanvasSaveQueue { private latest: string; private saved: string; private running: Promise | null = null; private blocked = false; private status: VideoCanvasSaveStatus = "saved"; constructor( baseline: string, private revision: string, private readonly write: (snapshot: string, revision: string) => Promise, private readonly changed: (status: VideoCanvasSaveStatus, message?: string) => void, ) { this.latest = baseline; this.saved = baseline; } get failed() { return this.status === "error"; } get dirty() { return this.latest !== this.saved || this.running !== null; } update(snapshot: string) { this.latest = snapshot; if (!this.running && this.status !== "error") { this.setStatus(this.dirty ? "pending" : "saved"); } } private setStatus(status: VideoCanvasSaveStatus, message?: string) { this.status = status; this.changed(status, message); } flush(): Promise { if (this.running) return this.running; if (this.blocked || !this.dirty) return Promise.resolve(); this.running = this.drain().finally(() => { this.running = null; }); return this.running; } private async drain() { this.setStatus("saving"); try { while (this.latest !== this.saved) { const snapshot = this.latest; this.revision = await this.write(snapshot, this.revision); this.saved = snapshot; } this.setStatus("saved"); } catch (error) { this.blocked = error instanceof CanvasRevisionConflict; this.setStatus("error", error instanceof Error ? error.message : "保存失败,请重试"); } } } export class CanvasRevisionConflict extends Error {}