Queue Offline Mutations
Queue writes explicitly. Do not rely on a background API to remember business data for you.
Mutation Flow
type OutboxItem = {
id: string;
url: string;
method: 'POST' | 'PUT' | 'PATCH' | 'DELETE';
body: unknown;
createdAt: string;
attempts: number;
};
async function createEntry(entry: unknown) {
const item: OutboxItem = {
id: crypto.randomUUID(),
url: '/api/entries',
method: 'POST',
body: entry,
createdAt: new Date().toISOString(),
attempts: 0,
};
await saveToIndexedDb('outbox', item);
await replayOutbox();
}
Replay Rules
async function sendOutboxItem(item: OutboxItem) {
const response = await fetch(item.url, {
method: item.method,
headers: {
'content-type': 'application/json',
'idempotency-key': item.id,
},
body: JSON.stringify(item.body),
});
if (!response.ok) {
throw new Error(`Replay failed: ${response.status}`);
}
await removeFromIndexedDb('outbox', item.id);
}
Conflict Checklist
- Use client-generated IDs or idempotency keys.
- Store server version, etag, timestamp, or revision if conflicts are possible.
- Show pending, failed, and synced states.
- Keep failed writes editable or exportable.
- Retry in foreground whenever the app opens or regains network.
- Add Background Sync only as an optimization.