1"use client";
2
3
14import { blockApi, type BatchBlockOp } from "./api";
15import { BLOCK_SAVE_DEBOUNCE_MS } from "./constants";
16
17export class BlockBatchQueue {
18 private ops: BatchBlockOp[] = [];
19 private timer: ReturnType<typeof setTimeout> | null = null;
20 private inFlight: Promise<void> | null = null;
21
22 constructor(
23 private readonly pageId: string,
24 private readonly debounceMs: number = BLOCK_SAVE_DEBOUNCE_MS
25 ) {}
26
27 get pendingCount(): number {
28 return this.ops.length;
29 }
30
31
32 queue(op: BatchBlockOp | BatchBlockOp[]): void {
33 this.ops.push(...(Array.isArray(op) ? op : [op]));
34 if (this.timer) clearTimeout(this.timer);
35 this.timer = setTimeout(() => {
36 void this.flush();
37 }, this.debounceMs);
38 }
39
40
41 flush(): Promise<void> {
42 if (this.timer) {
43 clearTimeout(this.timer);
44 this.timer = null;
45 }
46 if (this.ops.length === 0) return Promise.resolve();
47 const ops = this.ops.splice(0, this.ops.length);
48 this.inFlight = blockApi
49 .batch(this.pageId, ops)
50 .catch(() => {
51
52
53
54 this.ops = [...ops, ...this.ops];
55 })
56 .finally(() => {
57 this.inFlight = null;
58 });
59 return this.inFlight;
60 }
61
62
70 dropBlock(blockId: string): void {
71 if (this.ops.length === 0) return;
72 this.ops = this.ops.filter((op) => op.blockId !== blockId && op.block?.id !== blockId);
73 }
74
75
76 dispose(): void {
77 if (this.timer) {
78 clearTimeout(this.timer);
79 this.timer = null;
80 }
81 }
82}
83
84const queues = new Map<string, BlockBatchQueue>();
85
86
87export function getBlockBatchQueue(pageId: string, debounceMs?: number): BlockBatchQueue {
88 let q = queues.get(pageId);
89 if (!q) {
90 q = new BlockBatchQueue(pageId, debounceMs);
91 queues.set(pageId, q);
92 }
93 return q;
94}
95
96
97export function resetBlockBatchQueues(): void {
98 queues.clear();
99}
100