85 lines
2.7 KiB
TypeScript
85 lines
2.7 KiB
TypeScript
import type { DocumentId, RelativePath } from "../persistence/database";
|
|
|
|
export type SyncEvent =
|
|
| { type: "file-create"; path: RelativePath }
|
|
| { type: "local-content-update"; documentId: DocumentId }
|
|
| { type: "remote-content-update"; documentId: DocumentId }
|
|
| { type: "move"; documentId: DocumentId }
|
|
| { type: "delete"; documentId: DocumentId };
|
|
|
|
export class SyncEventQueue {
|
|
private readonly events: SyncEvent[] = [];
|
|
|
|
public get size(): number {
|
|
return this.events.length;
|
|
}
|
|
|
|
public clear(): void {
|
|
this.events.length = 0;
|
|
}
|
|
|
|
public enqueue(event: SyncEvent): void {
|
|
this.events.push(event);
|
|
}
|
|
|
|
public next(): SyncEvent | undefined {
|
|
if (this.events.length === 0) return undefined;
|
|
|
|
const first = this.events[0];
|
|
if (first.type === "file-create") {
|
|
this.events.shift();
|
|
return first;
|
|
}
|
|
|
|
const { documentId } = first;
|
|
|
|
// If there's an eventual delete, discard everything for this document
|
|
const deleteEvent = this.events.find(
|
|
(e) => e.type === "delete" && e.documentId === documentId
|
|
);
|
|
if (deleteEvent) {
|
|
this.removeAllForDocument(documentId);
|
|
return deleteEvent;
|
|
}
|
|
|
|
// Coalesce updates: return the last update before the next move for this document.
|
|
// Moves act as barriers since they depend on each other
|
|
const moveIndex = this.events.findIndex(
|
|
(e) => e.type === "move" && e.documentId === documentId
|
|
);
|
|
const boundary = moveIndex === -1 ? this.events.length : moveIndex;
|
|
|
|
const updateIndices: number[] = [];
|
|
for (let i = 0; i < boundary; i++) {
|
|
const e = this.events[i];
|
|
if (
|
|
(e.type === "local-content-update" ||
|
|
e.type === "remote-content-update") &&
|
|
e.documentId === documentId
|
|
) {
|
|
updateIndices.push(i);
|
|
}
|
|
}
|
|
|
|
if (updateIndices.length > 0) {
|
|
const result = this.events[updateIndices[updateIndices.length - 1]];
|
|
for (let i = updateIndices.length - 1; i >= 0; i--) {
|
|
this.events.splice(updateIndices[i], 1);
|
|
}
|
|
return result;
|
|
}
|
|
|
|
// First event is a move with no preceding updates
|
|
this.events.shift();
|
|
return first;
|
|
}
|
|
|
|
private removeAllForDocument(documentId: DocumentId): void {
|
|
for (let i = this.events.length - 1; i >= 0; i--) {
|
|
const e = this.events[i];
|
|
if (e.type !== "file-create" && e.documentId === documentId) {
|
|
this.events.splice(i, 1);
|
|
}
|
|
}
|
|
}
|
|
}
|