// src/lib/offline/queue.ts import { v4 as uuidv4 } from "uuid"; import { getOfflineDB, type QueuedAction } from "./db"; export interface EnqueueOptions { id?: string; // supply for idempotency } export async function enqueueAction( actionName: string, payload: unknown, options: EnqueueOptions = {} ): Promise { const db = await getOfflineDB(); const id = options.id ?? uuidv4(); // 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, payload, createdAt: Date.now(), attempts: 0, lastAttemptAt: null, status: "pending", }; await tx.store.put(action); await tx.done; return action; } export async function getQueuedActions(): Promise { const db = await getOfflineDB(); const all = await db.getAllFromIndex("queue", "by-created"); return all.filter((a) => a.status !== "synced"); } export async function dequeueAction(id: string): Promise { const db = await getOfflineDB(); const tx = db.transaction("queue", "readwrite"); const action = await tx.store.get(id); if (!action) { await tx.done; return null; } await tx.store.delete(id); await tx.done; return action; } export async function markInFlight(id: string): Promise { const db = await getOfflineDB(); 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 tx.store.put(action); await tx.done; } export async function markSynced(id: string): Promise { const db = await getOfflineDB(); await db.delete("queue", id); } export async function markConflict(id: string, error: string): Promise { const db = await getOfflineDB(); 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 tx.store.put(action); await tx.done; } export async function markFailed(id: string, error: string): Promise { const db = await getOfflineDB(); 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 tx.store.put(action); await tx.done; }