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'],
}))