This repo is an alpha of the offline sync engine for Convex. See a deployed version of the demo here.
This monorepo depends on PNPM for package management.
pnpm i
cd curvilinear
npm run devNote that you can either use a local dev server without signing up for a Convex account or use a managed cloud deployment for running the backend.
Vite serves the app on port 5173. It's pretty minimal Linear clone that demonstrates:
- Fast preloading of a page's content on initial load without any spinners
- Storing application state in IndexedDB for offline support
- Fully local client navigations that don't block on the server and show a spinner.
- Mutations for creating, editing, and deleting tasks.
The server-side schema is defined at convex/schema.ts. We have two tables, issues
and comments.
The local schema is defined as a projection of the server-side schema at
convex/sync/schema.ts. There are two local tables, issues and comments, and their
relationship to the underlying server tables are at convex/sync/{issues,comments}.ts.
We currently don't have any non-trivial mapping logic between the two data models,
so the logic in convex/sync/issues.ts just directly maps queries to the local table
to queries to the underlying sync table.
On the client, the app's queries against the local store are specified in
src/local/queries.ts. These synchronous callbacks issue queries against a
ctx.localDb object, and the sync engine handles pulling in data from the
server to fulfill these queries.
React app components, like src/pages/Issue/Comments.tsx use the useLocalQuery
hook to execute one of these callbacks. The hook returns undefined if the data
isn't available yet, which may happen if not all relevant data was preloaded.
The client specifies its mutations in src/local/mutations.ts. These are centrally
defined since the sync engine needs to know how to replay in-progress mutations
on restart.
Mutations contain both a pointer to an authoritative server-side mutation (e.g.
api.issues.changeStatus) as well as a callback for mutating the local store. These
don't have to agree, and the sync engine will coordinate atomically rolling back
the local store update once the server-side change is complete and visible.
Server-side mutations are fully serializable, so a wide range of conflict resolution strategies are possible.
- Correctness. We have deterministic simulation testing set up but haven't exercised it and wrung out all the bugs yet.
- Scale and performance. We've not optimized this for large numbers of rows managed yet.
- Making the website part offline with PWA
- API for reactively querying the set of in-progress mutations
- Native client ID allocation
- Synchronization across multiple tabs
- Versioning and local database migrations
- The naming isn't great, and the APIs need a lot of polish.
- Codesharing between local optimistic updates and the authoritative server mutation.