Use offline-first replication in your app
Keep a local copy of a person's rows on their device, work offline, and merge changes back to the server without losing edits.
Status Status is where the work is: Planned (in a milestone, not started), Building (tickets in progress), Preview (usable, may change), then the stability it ships at.
Stability The promise an interface ships with.
- Stable Follows semantic versioning; breaking changes need a new major, 12 months of overlap and a codemod.
- Beta Usable in production; may change before it becomes stable.
- Experimental May change or go away; needs an explicit opt-in in main.yaml.
You will let people edit their notes with no network: a replica on the device, a server that merges pushes, and a check that two devices keep both edits. It takes about 20 minutes.
You need
- A MainSDK app with a
main.yaml(see Thirty-minute path). - Users that have an id and a tenant. The server checks access for each row.
1. Declare the table
sync:
tables:
notes: { version: 1, conflicts: last-writer-wins }Bump version when a table's fields change in a way old apps cannot read. A device on the old version is refused until it updates.
2. Start the server half
import { SyncServer } from "mainsdk/experimental/sync-server";
declare const config: Record<string, unknown>; // the sync section of main.yaml
declare const access: ConstructorParameters<typeof SyncServer>[0]["access"];
declare const currentUser: () => { id: string; tenant: string };
const server = new SyncServer({ config, access });
export const handle = server.handler(() => currentUser());Serve handle for POST /sync/push and POST /sync/pull. access is the function collab takes; policyCollabAccess(engine) fits.
3. Open a replica on the device
import { httpTransport, sync } from "mainsdk/experimental/sync";
declare const who: { id: string; tenant: string };
const replica = await sync.open({
tables: { notes: { version: 1 } },
for: who,
transport: httpTransport("https://app.example.com/sync", { fetch }),
});Pass storage with your IndexedDB or Expo SQLite store so the replica survives a restart.
4. Read and write with no network
import type { Replica } from "mainsdk/experimental/sync";
declare const replica: Replica<"notes">;
const note = await replica.notes.insert({ title: "Buy milk" });
await replica.notes.update(note.id, { done: true });
const open = replica.notes.list();Every call returns at once. Nothing has reached the server yet.
5. Flush when the network is back
import type { Replica } from "mainsdk/experimental/sync";
declare const replica: Replica<"notes">;
const { pushed, pulled, offline } = await replica.flush();
const clashes = replica.conflicts();Call flush when the app starts, when the network returns and after a burst of writes. A lost network returns offline: true and keeps the queue.
Check it worked
- Write on two devices with the network off, flush both, and both show every edit; two edits to one field keep the later one and
conflicts()lists the other. pnpm mainsdk checkpasses, with noMAIN-SYNC-0007finding forsync.tables.- A person you erase disappears from every device on its next flush.
If it fails
MAIN-SYNC-0002: the device's table version differs frommain.yaml. Update the app.MAIN-SYNC-0001: access refused a row. The row is listed inconflicts()and the rest of the push went through.MAIN-SYNC-0003: the tenant is over its quota. Delete rows or raisesync.quota.
Prompt for your coding agent
Add offline editing for notes to my app with MainSDK's
syncinterface. Declare the table inmain.yaml, serveserver.handler, open a replica on the device with a persistent store, callflushwhen the network returns, and showconflicts()to the person.
Was this page helpful?