Skip to main content

Custom sync adapter

@monlite/sync talks to any backend through a small SyncAdapter interface. Implement it to replicate to a store we don't ship.

import type { SyncAdapter, Cursor, PullResult, PushResult, LocalChange } from "@monlite/sync";

class MyAdapter implements SyncAdapter {
readonly name = "my-backend";

// Fetch remote changes since `cursor` (null = from the beginning).
async pull(cursor: Cursor, opts): Promise<PullResult> {
const rows = await myBackend.changesSince(cursor, opts.collections, opts.limit);
return { changes: rows.map(toRemoteChange), cursor: rows.at(-1)?.version ?? cursor };
}

// Apply local changes; return which were acked (LWW upsert by _id + version).
async push(changes: LocalChange[]): Promise<PushResult> {
for (const c of changes) await myBackend.upsert(c._id, c.doc, c.version, c.deleted);
return { acked: changes };
}

// Optional: live streaming
watch?(cursor, onChange, opts) { /* return an unsubscribe */ }
}

Each adapter stores (_id, doc, version, deleted) per document. monlite's engine handles cursors, retries, conflict resolution, and tombstones — your adapter just moves changes. The built-in MemoryAdapter is a complete reference implementation.

Then wire it up like any other adapter:

sync(db, { adapter: new MyAdapter(), mode: "two-way" });