Files
route-commerce/src/lib/offline/queue.ts
T

107 lines
2.8 KiB
TypeScript

// 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<QueuedAction> {
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<QueuedAction[]> {
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<QueuedAction | null> {
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<void> {
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<void> {
const db = await getOfflineDB();
await db.delete("queue", id);
}
export async function markConflict(id: string, error: string): Promise<void> {
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<void> {
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;
}