useOutbox

Queue mutations offline, replay them in order on reconnect.

Queue mutations while offline, replay IN ORDER on reconnect — over the existing optimistic layer. Replay stops at the first conflict to preserve causality. NOT multi-device CRDT (single-user/device, honest about its limits).

const outbox = useOutbox({ send })
// → { queue, enqueue, replay, online, pending, conflicts, resolveConflict }

Durable across reloads. Pass persistence and the queue survives: entries rehydrate on mount, every transition persists (delivered entries are compacted away), and a failing backing degrades to the in-memory behaviour instead of breaking the outbox. The one real implementation is outboxPersistence() from @voltro/local-first, backed by the same PersistenceAdapter the sync engine drains — one durable queue per device:

import { createIndexedDbPersistence, outboxPersistence } from '@voltro/local-first'

const adapter = await createIndexedDbPersistence()
const outbox = useOutbox({ send, persistence: outboxPersistence(adapter) })

Resolving a conflict. A conflicted entry blocks everything behind it (causality). resolveConflict(id, input) replaces its input with the RESOLVED value, returns it to pending and replays — typically computed with resolveWithPolicy() from @voltro/local-first, where crdtText() columns merge and scalars follow the declared conflictPolicy():

outbox.resolveConflict(entry.id, resolveWithPolicy(policy, local, remote, {
  crdtColumns: ['body'],
}))