fix(offline): harden sync catch + add queue/sync test coverage + atomic transactions
This commit is contained in:
+35
-12
@@ -13,8 +13,15 @@ export async function enqueueAction(
|
||||
): Promise<QueuedAction> {
|
||||
const db = await getOfflineDB();
|
||||
const id = options.id ?? uuidv4();
|
||||
const existing = await db.get("queue", id);
|
||||
if (existing) return existing;
|
||||
// Atomic read-then-write inside a single readwrite transaction so two
|
||||
// concurrent calls with the same explicit `id` can't both pass the
|
||||
// existence check and write twice.
|
||||
const tx = db.transaction("queue", "readwrite");
|
||||
const existing = await tx.store.get(id);
|
||||
if (existing) {
|
||||
await tx.done;
|
||||
return existing;
|
||||
}
|
||||
const action: QueuedAction = {
|
||||
id,
|
||||
actionName,
|
||||
@@ -24,7 +31,8 @@ export async function enqueueAction(
|
||||
lastAttemptAt: null,
|
||||
status: "pending",
|
||||
};
|
||||
await db.put("queue", action);
|
||||
await tx.store.put(action);
|
||||
await tx.done;
|
||||
return action;
|
||||
}
|
||||
|
||||
@@ -49,12 +57,17 @@ export async function dequeueAction(id: string): Promise<QueuedAction | null> {
|
||||
|
||||
export async function markInFlight(id: string): Promise<void> {
|
||||
const db = await getOfflineDB();
|
||||
const action = await db.get("queue", id);
|
||||
if (!action) return;
|
||||
const tx = db.transaction("queue", "readwrite");
|
||||
const action = await tx.store.get(id);
|
||||
if (!action) {
|
||||
await tx.done;
|
||||
return;
|
||||
}
|
||||
action.status = "in-flight";
|
||||
action.lastAttemptAt = Date.now();
|
||||
action.attempts += 1;
|
||||
await db.put("queue", action);
|
||||
await tx.store.put(action);
|
||||
await tx.done;
|
||||
}
|
||||
|
||||
export async function markSynced(id: string): Promise<void> {
|
||||
@@ -64,20 +77,30 @@ export async function markSynced(id: string): Promise<void> {
|
||||
|
||||
export async function markConflict(id: string, error: string): Promise<void> {
|
||||
const db = await getOfflineDB();
|
||||
const action = await db.get("queue", id);
|
||||
if (!action) return;
|
||||
const tx = db.transaction("queue", "readwrite");
|
||||
const action = await tx.store.get(id);
|
||||
if (!action) {
|
||||
await tx.done;
|
||||
return;
|
||||
}
|
||||
action.status = "conflict";
|
||||
action.error = error;
|
||||
await db.put("queue", action);
|
||||
await tx.store.put(action);
|
||||
await tx.done;
|
||||
}
|
||||
|
||||
export async function markFailed(id: string, error: string): Promise<void> {
|
||||
const db = await getOfflineDB();
|
||||
const action = await db.get("queue", id);
|
||||
if (!action) return;
|
||||
const tx = db.transaction("queue", "readwrite");
|
||||
const action = await tx.store.get(id);
|
||||
if (!action) {
|
||||
await tx.done;
|
||||
return;
|
||||
}
|
||||
action.status = "failed";
|
||||
action.error = error;
|
||||
action.attempts += 1;
|
||||
action.lastAttemptAt = Date.now();
|
||||
await db.put("queue", action);
|
||||
await tx.store.put(action);
|
||||
await tx.done;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user