Sync
Sync
Watermelon-compatible pull/push with retry, conflicts, and checkpoints.
Melon-db sync follows a Watermelon-compatible pull/push model. This page summarizes the protocol; see also Frontend, Backend, Data types, and FAQ.
Protocol steps
| Step | Action |
|---|---|
| Pull | pullChanges({ lastPulledAt, schemaVersion, migration? }) → { changes, timestamp, schemaVersion? } |
| Apply | db.applyRemoteChanges(changes, { conflictPolicy, conflictResolver? }) |
| Push | pushChanges({ changes: await db.getLocalChanges(), lastPulledAt }) |
| Ack | db.markLocalChangesPushed() |
| Checkpoint | checkpointStore.setLastPulledAt(timestamp) + setLastSchemaVersion(version) |
Sync status
synchronize() emits:
idle→pulling→pushing→completeretryingwith{ phase, attempt }when pull/push retriespausedwith{ reason: 'offline' }when network monitor reports offlinefailedwith aSyncError(checkpoint and outbox preserved for retry)
Retry and cancellation
import { DEFAULT_RETRY_POLICY, synchronize } from '@melon-db/sync';
await synchronize({
db,
pullChanges,
pushChanges,
retryPolicy: DEFAULT_RETRY_POLICY,
signal: abortController.signal,
});Conflict policies
server-wins(default)skip-existingclient-winslast-write-winsmerge-by-field— overlay pending local field patches onto remote rowscustom— call your ownconflictResolverfor each remote create/update/delete
Custom conflict resolver
Set conflictPolicy: 'custom' and provide conflictResolver. The resolver receives local row, remote payload, and outbox entry, and returns apply (with merged record) or skip. Use clearOutbox: false on apply to keep local changes queued for push (same as merge-by-field).
import { mergeRemoteWithPendingFields, type ConflictResolver } from '@melon-db/db';
const resolver: ConflictResolver = (ctx) => ({
action: 'apply',
record: mergeRemoteWithPendingFields({
local: ctx.local,
remote: ctx.remote,
pendingFields: ctx.outboxEntry?.pendingFields,
primaryKey: ctx.primaryKey,
}),
clearOutbox: false,
});
await synchronize({
db,
pullChanges,
pushChanges,
conflictPolicy: 'custom',
conflictResolver: resolver,
});React hooks
Use @melon-db/db-react:
MelonSyncProvideruseSync()/useSyncStatus()
Reference backend
bun run sync-server
bun run postgres:up
bun run sync-server:postgresTry the Sync playground.
