Sync Engine Landscape
Comparison of sync engine alternatives. Rocicorp Zero selected; PowerSync retained as evidence.
Boja uses Rocicorp Zero. This document is the full landscape research that led to that decision.
Context: Greenfield "Discord-meets-Skool" — live chat with unbounded history (millions of messages), threads/replies, jump-to-old-message, live streams, role/channel-gated access, courses, multi-server with independent admins, billing/feature gating. Hard requirements: instant resume after offline, near-zero loading screens, native feel on React Native + Web from day one. Current stack: Bun/TypeScript/MongoDB; Postgres is on the table.
Research date: July 2026. Sources: official docs (zero.rocicorp.dev, docs.powersync.com, electric-sql.com, docs.convex.dev, instantdb.com, triplit.dev, docs.livestore.dev, jazz.tools), HN "Offline-First Landscape 2025" thread, 2025 comparison posts, vendor blogs.
The One Framing Question That Matters
Chat history is an unbounded, shared, append-mostly collection. The sync-engine field splits into three architecture families, and only one of them handles this shape natively:
- Query-driven partial sync (Zero, PowerSync Sync Streams, InstantDB infinite queries, Triplit): the client subscribes to queries ("last 100 messages in channel X"); anything not covered falls back to the server. This is the correct shape for jump-to-message and infinite history.
- Bucket / shape sync (PowerSync legacy Sync Rules, ElectricSQL shapes): the server pre-partitions rows into buckets/shapes; clients sync whole buckets. Works if you can bound bucket size, awkward for arbitrary historical windows.
- CRDT document / event-log sync (Jazz, LiveStore, Ditto, Automerge): the unit of sync is a document or event log. Great for collaboration on bounded objects; structurally wrong for a single multi-million-row message table unless you shard aggressively.
A Discord-like app also needs an authoritative server (permissions, moderation, billing, feature gating) — pure local-first data-ownership models fight you here.
1. Rocicorp Zero (zero.rocicorp.dev)
- (a) Architecture: Query-driven partial sync. Client defines Zero queries (ZQL);
zero-cachekeeps a SQLite replica of the Postgres DB (or the syncable subset) and incrementally maintains query results via IVM. Queries that miss the local cache automatically fall back to the server and sync — this is the killer feature for unbounded collections. Web persistence uses IndexedDB; native persistence useskvStorethrough SQLite adapters. - (b) Database: PostgreSQL required (logical replication into zero-cache's SQLite replica). Recommended total syncable dataset < ~100GB (official guidance) — a real constraint for millions-of-messages-per-server growth; you'd likely exclude cold history from the replicated subset or shard zero-cache per tenant.
- (c) RN + Web: Built-in React Native/Expo support (
kvStorevia expo-sqlite or op-sqlite); web uses IndexedDB. TS-only client. RN support is newer than web; officialzslacksample is the reference. Web is the primary battle-tested target. - (d) Unbounded collections: Best-in-class. Query-driven sync means "messages in channel X, limit 50, before cursor" is just a query; jump-to-old-message = run the query, Zero serves from server and syncs the window in.
zbugsdemo runs 1.2M rows / >1GB backing data with instant interactions. - (e) Permissions: Expressive read permissions (ZQL-based, row-level, relationship-aware); custom mutators run arbitrary server-side TS for write auth/validation. Column permissions still on roadmap.
- (f) Writes: Optimistic local application + custom mutators executed authoritatively on your server (any TS backend — works with Bun). Server result rebases client state. Clean server-authority story.
- (g) Scale/hosting: Self-host
zero-cache(OSS, needs fast NVMe near Postgres); zero-cache is horizontally scalable through view-syncer replicas. - (h) Maturity/risk: GA and fully supported since March 2026; Zero 1.0 is the first stable release. Rocicorp is a small, focused, VC-funded team with deep pedigree (Replicache; Boodman ex-Google Gears/Chrome). Zero itself does not queue writes once disconnected; short-offline reads remain available. Risk remains moderate because production operations and the React Native lifecycle still require product-specific validation.
- (i) Chat apps:
zslack(official Slack clone sample); Bugs/zbugs (Linear clone). Several startups building chat/collab on it in beta.
"Instant resume after offline" fits. Zero itself does not queue a send made while disconnected, so the selected architecture adds a distinct app-owned, message-only durable outbox with authoritative nonce receipts; it does not generalize offline mutations.
2. PowerSync (powersync.com)
- (a) Architecture: Two generations: legacy Sync Rules = classic bucket sync (YAML bucket definitions, parameter queries from JWT); new Sync Streams (2025, now the recommended default) = on-demand, query-shaped partial sync with JOIN support, subscription parameters, priorities, and React hooks that manage subscriptions. Streams close most of the gap with Zero's query-driven model while keeping PowerSync's proven bucket infrastructure underneath. Client is a full local SQLite DB; you query it with plain SQL.
- (b) Database: Postgres, MongoDB, or MySQL — the only serious engine with first-class MongoDB support (GA, used to onboard Atlas Device Sync refugees after MongoDB killed it in 2024). This means the team could keep their Mongo stack.
- (c) RN + Web: Among the most mature RN SDKs in the space (React Native + Expo, op-sqlite/quick-sqlite); also Flutter, Kotlin, Swift, JS/web. Web uses wa-sqlite over OPFS (IndexedDB fallback), multi-tab supported. True SQLite on every platform = identical data layer RN/web.
- (d) Unbounded collections: Good, with design work. Sync Streams support on-demand subscription with parameters — e.g. a
channel_messagesstream parameterized bychannel_id+ time window; subscribe when the user opens a channel, unsubscribe (with TTL) when they leave. Jump-to-old-message = subscribe to a stream for that window, or hit your normal backend API for cold reads (hybrid is idiomatic and documented). It's more plumbing than Zero's transparent fallback but fully production-proven. - (e) Permissions: Sync Streams/Rules define read access server-side from signed JWT claims. Write auth is entirely yours: writes go through your own backend endpoint.
- (f) Writes: Client writes to local SQLite (fully optimistic, offline-queued in an upload queue), PowerSync calls your
uploadDatahook → your Bun API applies writes to Mongo/Postgres with full server authority and any validation; changes flow back down through sync. Cleanest server-authority + offline-writes combination in the field. - (g) Scale/hosting: PowerSync Cloud (Free tier; Starter from $49/mo; Pro from $599/mo, usage-based; Enterprise) or self-hosted (open edition, Docker; service is stateless-ish over Mongo-backed bucket storage, scales horizontally). Spun out of JourneyApps, ~a decade syncing industrial apps.
- (h) Maturity/risk: Most production-mature dedicated sync engine. Years of commercial deployments (industrial, logistics, fintech, consumer), revenue-generating company, source-available core. Lowest abandonment risk of the independents.
- (i) Chat apps: Official chat demos; several production messaging/field-comms apps. No famous consumer Discord-scale app publicly named.
3. ElectricSQL (electric-sql.com)
- (a) Architecture: Read-path sync engine: Shapes = single-table subsets (
table+where+columns), streamed over plain HTTP (CDN-cacheable long-polling). Rebuilt from scratch in 2024 ("Electric Next") after abandoning the original CRDT/active-active design — current Electric does not do CRDT merge. Query-driven sync arrives via the TanStack DB integration (Electric provides collections; TanStack DB does sub-ms live queries + optimistic transactions client-side). - (b) Database: Postgres only (logical replication).
- (c) RN + Web: Web-first. Client is transport-level (fetch/HTTP), so it runs in RN, but persistence is DIY — official persistence stories are in-memory, PGlite (web/WASM, not RN), or write-your-own. RN offline persistence is the weakest of the top contenders.
- (d) Unbounded collections: Shapes are immutable once defined and single-table; the pattern for chat is one shape per channel with a
whereon channel_id (+ time window), created on demand. Doable, but you manage shape lifecycle yourself; no automatic server fallback. - (e) Permissions: No built-in rule engine — you proxy shape requests through your own API which authorizes them (documented gatekeeper/proxy patterns). Total flexibility, more code.
- (f) Writes: Bring your own write path (explicit philosophy): optimistic writes via TanStack DB transactions or their write-pattern guides, POSTed to your API.
- (g) Scale/hosting: Electric is a single Elixir service, brutally scalable on reads because shape logs are HTTP/CDN-cacheable — demonstrated millions of concurrent clients load tests. OSS (Apache-2.0) self-host + Electric Cloud SaaS.
- (h) Maturity/risk: 1.0+ GA since 2025, real production users, active company. Risk: low-moderate. Main caveat is that it's half a sync engine — reads only — and RN persistence is immature.
4. Convex (convex.dev)
- (a) Architecture: Not a sync engine: a server-first reactive database. Client subscribes to server-executed query functions (TypeScript, transactional, deterministic); results push to clients via WebSocket. Optimistic updates are an explicit client-side layer, rolled back/replaced when the authoritative result lands.
- (b) Database: Proprietary document-relational store (cloud; OSS self-host backend available, single-node). Not your Mongo, not your Postgres.
- (c) RN + Web: Excellent RN + web support (same TS client). But: no local persistence — cache is in-memory; cold start requires network; offline reads/writes are not supported beyond transient reconnect buffering. This fails the "instant resume after offline" bar.
- (d) Unbounded collections: Very good, because it's server-first: paginated queries (
usePaginatedQuery) are reactive per-page; jump-to-message is just another indexed server query. You never sync more than you render. The flip side: everything needs a round trip when not already subscribed. - (e) Permissions: Arbitrary TS in query/mutation functions — the most flexible auth model here.
- (f) Writes: Server-authoritative mutations (ACID, serializable), client optimistic-update API. No offline queue.
- (g) Scale/hosting: Mature cloud (usage-based, free tier, Pro ~$25/seat + usage); a16z-backed, well funded, large team.
- (h) Maturity/risk: GA, very low abandonment risk. DX widely praised in 2025. Chat is Convex's home turf — but all apps are online-first.
5. InstantDB (instantdb.com)
- (a) Architecture: "Modern Firebase": client-side triple store + InstaQL (relational GraphQL-like queries), real-time subscriptions, offline cache + offline write queue, server authoritative (backend on Postgres, Aurora). Query-subscription partial sync.
- (b) Database: Proprietary (their triple-store on Postgres). Fully OSS and self-hostable.
- (c) RN + Web: First-class React Native support (official,
@instantdb/react-native); web persistence via IndexedDB. Same API both platforms. - (d) Unbounded collections: Good:
useInfiniteQuery(2025/2026 feature) gives reactive cursor pagination over large collections — new items appear live at the top, older pages load on demand; jump-to-message = fresh query withbeforecursor served by server. - (e) Permissions: Declarative rule language (CEL-like expressions per-namespace:
view/create/update/delete, can reference auth + linked data). Row-level, tested at consumer scale. - (f) Writes:
db.transact— optimistic, offline-queued, replayed on reconnect; permissions enforced server-side. - (g) Scale/hosting: Cloud free tier + usage pricing (cheap); self-host possible. Built-in presence/cursors/topics (nice for "who's typing", live rooms), storage, auth.
- (h) Maturity/risk: YC S22; seed ~$3.4M from Paul Graham, James Tamplin (Firebase founder), Greg Brockman et al. "Matured massively" through 2025 (HN consensus). Small team; acquisition/abandonment risk is the main concern, mitigated by full OSS.
6. Triplit (triplit.dev)
- TS-first full-stack DB; query-driven partial sync (very Zero-like DX, plus offline writes), own storage (SQLite/IndexedDB), RN support, relational-ish schema, row-level permissions, optimistic writes. 1.0 (2025) brought ~10x perf.
- The problem: The company (Aspen Cloud) folded in 2025; Triplit became a community open-source initiative. No commercial backing, small community.
Architecturally one of the best fits on paper; not a responsible foundation for a venture-scale product in 2026. Watch, don't build.
7. LiveStore (livestore.dev)
- (a) Architecture: Event sourcing: append-only event log is the unit of sync; client materializes events into a local SQLite DB. From Johannes Schickling (Prisma founder). v0.4.0.
- (b) Database: Proprietary event log + client SQLite; sync providers: Cloudflare Durable Objects, Electric, or custom.
- (c) RN + Web: Strong: official Expo adapter (a headline feature), web via wa-sqlite/OPFS.
- (d) Unbounded collections: The dealbreaker: a client syncs a store's entire event log. Stores are meant to be small (per-user/per-workspace); their own docs warn against large multi-user datasets. For Discord-scale you'd need a store per channel(-epoch) plus cross-store orchestration. No server-query fallback.
- (g-h): OSS, sponsor/design-partner funded, pre-1.0, tiny team. Risk: high for this use case.
8. Jazz (jazz.tools)
- (a) Architecture: CRDT-based local-first: CoValues (collaborative maps/lists/streams/files) with per-value git-like edit history; 2025/2026 repositioning as "the database that syncs" with partial table sync, row-level security, per-query auth, and "tunable consistency".
- (b) Database: Proprietary (CoValue storage; Jazz Cloud or self-hosted).
- (c) RN + Web: Official React Native (incl. Expo) + web support; persistence via SQLite (RN) / IndexedDB-OPFS (web).
- (d) Unbounded collections: Historically weak (a chat = a CoList that grows forever); the new partial-table-sync + durable-streams model is aimed exactly at this, but it's new and unproven at millions of rows.
- (f) Writes: CRDT merge = every write accepted and merged; server authority is not the default model.
- (g-h): Jazz Cloud usage pricing; self-host OSS. Garden Computing: small seed-funded team. Risk: moderate-high.
9. Brief Evaluations
Replicache (legacy)
Rocicorp's predecessor: client KV store + your push/pull endpoints, per-space versioning; you write the server. Now free/MIT'd and in maintenance; Rocicorp's energy is 100% on Zero. Proven in production (Vercel used it). Fine if you want a battle-tested, DIY-heavy layer today; strategically a dead end — Zero is its successor.
Ditto
CRDT + P2P mesh sync (BLE/WiFi-Direct/LAN), edge-first; huge funding (~$145M, Series B 2023), enterprise pricing, customers = airlines (Alaska), quick-service retail (Chick-fil-A), military. Built for resilient small-data ops at the edge, not consumer-scale fan-out chat with millions of historical rows. Pass, unless offline mesh (festival/venue chat without internet) becomes a differentiator.
WatermelonDB + custom sync
RN-first reactive SQLite ORM with built-in lazy loading and a documented pull/push sync protocol you implement server-side. Web support via LokiJS/IndexedDB is its weak leg; maintenance cadence has slowed. You own: conflict handling, permissions fan-out, partial-sync windowing, jump-to-message. Realistic effort: months of sync-protocol engineering — basically "roll your own" with a nice client ORM.
Roll your own on Postgres logical replication
The Figma/Linear path: WAL → replication consumer → per-user/channel fan-out service → WebSocket + client store (SQLite/IndexedDB) + idempotent write API + rebase logic. Total control, perfect fit… and it is a multi-quarter, senior-team project whose hard parts are exactly what PowerSync/Electric/Zero sell. Only justified if sync is the product moat. Notably, Discord itself is not offline-first — it's server-first with aggressive caching; that's a legitimate architecture too.
Comparison Table
| Zero | PowerSync | ElectricSQL | Convex | InstantDB | Triplit | LiveStore | Jazz | |
|---|---|---|---|---|---|---|---|---|
| Sync model | Query-driven | Streams (query-ish) / buckets | Shapes (read-only) | Server reactive queries | Query subscriptions | Query-driven | Event-log | CRDT CoValues (+partial tables) |
| Backend DB | Postgres | PG / Mongo / MySQL | Postgres | Proprietary | Proprietary (OSS) | Proprietary (OSS) | Event log | Proprietary (OSS) |
| RN maturity | Good (newer) | Excellent | Weak | Excellent* | Excellent | OK | Good (Expo) | Good |
| Web persistence | IndexedDB (memory-first) | SQLite/OPFS | PGlite / TanStack DB | ❌ in-memory | IndexedDB | IndexedDB | SQLite/OPFS | IndexedDB/OPFS |
| Offline writes | ❌ disabled | ✅ queued | DIY | ❌ | ✅ queued | ✅ | ✅ native | ✅ native |
| Unbounded chat history | ★★★ auto server fallback | ★★★ on-demand streams + API | ★★ shape-per-window | ★★★ (server-first pagination) | ★★★ infinite queries | ★★ | ★ full-log sync | ★★ unproven |
| Jump-to-old-message | Query falls through to server | Subscribe stream / REST | New shape / REST | Native (server query) | Cursor query | Query | Hard | Immature |
| Permissions | ZQL read perms + mutators | JWT sync rules + your API | Your proxy API | TS functions (best) | Rule language | Rules | DIY | Crypto groups + new RLS |
| Server authority on writes | ✅ mutators | ✅ your API | ✅ your API | ✅ | ✅ rules+admin | ✅ | ⚠️ event-sourced | ⚠️ CRDT-merge default |
| Self-host | ✅ OSS | ✅ (+Cloud $49–599+/mo) | ✅ Apache-2 (+Cloud) | ⚠️ basic OSS | ✅ OSS (+cheap cloud) | ✅ only | ✅ | ✅ (+Cloud usage) |
| Status 2026 | GA; 1.0 stable (March 2026) | GA, proven | GA, proven (reads) | GA, well-funded | GA-ish, YC | ⚠️ company folded | v0.4 | Early |
| Abandonment risk | Med | Low | Low-Med | Low | Med (OSS hedge) | High | High | Med-High |
* Convex RN is mature but has no offline persistence.
Postgres vs Mongo Implications
The comparison below is retained as engine-landscape research.
- Choosing Postgres unlocks: Zero (hard requirement), ElectricSQL, best-in-class RLS, logical replication for any future roll-your-own, Supabase/Neon ecosystems, easy relational modeling of channels/roles/threads.
- Staying on Mongo restricts you to: PowerSync (first-class Mongo connector, GA — the standout), WatermelonDB/custom, Convex/InstantDB/Jazz (which replace Mongo anyway rather than sync it). Mongo change streams are usable for roll-your-own but resume-token + fan-out engineering is on you.
- Pragmatic read: if PowerSync wins, keep Mongo and ship sooner. If Zero/Electric wins, the Postgres migration is the tax — for a greenfield app this is cheap now and only gets more expensive later. Relational shape (memberships, roles, threads, entitlements) genuinely fits chat/community better than documents.
Hybrid Architecture (Historical Comparison; Chosen Boundary Clarified)
The engine-neutral comparison is retained as research. In the selected architecture, physically published hot rows use Zero; archived rows use an authorized read-only HistoryWindow. Archive rows are never promoted into fake Zero entities, and typing/presence remain on the ephemeral WebSocket plane.
Nobody sane syncs millions of messages to a phone. The winning 2026 pattern:
- Hot data via sync engine: channel list, membership/roles, unread counts, last N (~100–500) messages per recently active channel. This gives instant resume + zero loading screens.
- Cold history via plain server queries: REST/RPC with cursor pagination for scrollback, jump-to-message (fetch window around target), and full-text search. Render the authorized result as a distinct transient history window.
- Streams/video: always out-of-band (LiveKit/Mux/etc.); sync engine carries only signaling/metadata.
- Billing/feature gating: server-side, in the write path (mutators / upload endpoint / TS functions) + read-permission rules for entitlement-gated channels/courses.
Shortlist for a Discord-Like App
Query-driven sync with automatic server fallback is the correct primitive for published chat history and jump-to-message; custom mutators fit billing/moderation; zslack proves the shape. Costs identified by the comparison were the Postgres migration, ~100GB published-dataset guidance, and a newer RN/operational path. Zero is now GA/1.0. It still has no disconnected-write queue in the engine itself; the selected architecture mitigates message sends with the app-owned outbox.
Mature, production-proven, best RN story, true SQLite on all platforms, offline write queue with your Bun API as the authority, Sync Streams give on-demand per-channel windows, sane pricing, self-hostable. Costs: you design stream/bucket boundaries and the write API yourself; the DX is more "infrastructure" than "magic."
Offline writes, infinite queries, presence/typing built in, permissions language, cheap, OSS hedge. Costs: proprietary data model (leaves Mongo and Postgres), small company, less headroom for exotic server-side logic than raw SQL stacks.
Honorable mention — Convex: if you soften "instant offline resume" to "instant when online, graceful reconnect," its DX, TS server functions and pagination make it the fastest path to a feature-rich community product. It simply isn't offline-first.
Dark horse — ElectricSQL + TanStack DB (+ your Bun write API) if you want maximal control on Postgres with CDN-scalable reads; hold off until RN persistence matures.
Avoid for this use case: Triplit (orphaned), LiveStore (event-log-per-store wrong shape for global chat), Jazz (unproven at this scale, weak default server authority), Ditto (wrong market/pricing), full roll-your-own (only if sync is your moat).
Decision fork, in one line: Keep Mongo & ship safely → PowerSync. Move to Postgres & optimize for the ideal chat UX long-term → Zero (with a REST cold-history layer either way).