feat(sync): implement lww conflict and tombstone handling

This commit is contained in:
2026-04-06 01:33:57 +08:00
parent c48e16a977
commit c98adb3051
9 changed files with 530 additions and 19 deletions
+41
View File
@@ -7,6 +7,7 @@ import {
getLocalSyncState
} from "@/services/local-sync-repo";
import type { WebSession } from "@/services/session-storage";
import { applyPendingRemoteOperations } from "@/services/sync-merge";
import { runSyncWorkerCycle } from "@/services/sync-worker";
const PERIODIC_SYNC_INTERVAL_MS = 30_000;
@@ -70,6 +71,7 @@ export function useSyncEngine(session: WebSession | null): {
const retryAttemptRef = useRef(0);
const runningRef = useRef(false);
const mergeRunningRef = useRef(false);
useEffect(() => {
setLastSyncedAt(storedSyncState?.lastSyncedAt ?? null);
@@ -117,6 +119,37 @@ export function useSyncEngine(session: WebSession | null): {
void runCycle();
}, [runCycle]);
const runMerge = useCallback(async () => {
if (!userId || mergeRunningRef.current) {
return;
}
mergeRunningRef.current = true;
try {
await applyPendingRemoteOperations(userId);
if (!runningRef.current) {
setPhase((currentPhase) => {
if (!window.navigator.onLine) {
return "offline";
}
if (currentPhase === "backoff") {
return currentPhase;
}
return blockedCount > 0 ? "attention" : "idle";
});
}
} catch (error) {
setLastError(getErrorMessage(error));
setPhase("attention");
} finally {
mergeRunningRef.current = false;
}
}, [blockedCount, userId]);
useEffect(() => {
function handleOnline(): void {
setIsOnline(true);
@@ -190,6 +223,14 @@ export function useSyncEngine(session: WebSession | null): {
};
}, [isOnline, nextRetryAt, runCycle]);
useEffect(() => {
if (!userId || pendingRemoteCount === 0 || runningRef.current) {
return;
}
void runMerge();
}, [pendingRemoteCount, runMerge, userId]);
useEffect(() => {
if (!userId) {
setLastError(null);