Browser read-replica

The WASM read-replica — the same generated database.rs running in the browser, following the durable replication broker over /replicate, persisting to IndexedDB or OPFS. Read-only and local-first, not "faster".

Run the same generated database in the browser as a read-only local copy that stays in sync with the server. It compiles the generated database.rs to wasm32 and runs it in a Web Worker, follows the server's replication stream over /replicate, and persists to IndexedDB or OPFS. Your UI queries a local WASM instance instead of the network. It's for local querying and offline reads, not for beating a normal API call on speed.

It's a local-first store, not a faster store

The replica is not a "faster store." It runs the same generated code as the server, so filter, sort, index, traversal, and snapshot semantics are byte-for-byte identical. Reach for it for local querying, offline reads, and cross-entity consistency, not for out-computing the JS engine. For most apps, a well-cached TypeScript SDK plus a subscribe-WS is the lighter, faster-to-first-paint default.

How it works#

  • Same generated engine. The replica is the generated database.rs compiled to wasm32, with no codegen branches. A target-specific storage layer absorbs the browser difference. A pure-TypeScript store would be a second copy of all the query logic that has to stay bit-identical to the Rust engine forever; the replica avoids that.
  • Fed by the server's stream. The server records every committed change to a durable, ordered log. The replica connects to /replicate?after=<offset>, receives opaque change frames, and replays each one. It routes each frame by an opaque model name and orders by the log offset; it never decodes a field.
  • Persistence. The browser storage backend writes one file per column, in the same byte layout as the native engine, so partial loading and the backup format just work. OPFS holds discrete per-column files.
  • Runs in a Web Worker. The engine lives in a dedicated Worker so it can load only the columns a query touches, on demand. Untouched columns never load, and reopening rebuilds indexes by reading only the indexed columns. First paint isn't blocked, because the download, compile, and load all happen off the main thread.

Resume semantics#

Frames are ordered and idempotent, so a return visit resumes cleanly: load from the saved position, reconnect after that offset, catch up the small gap, and stay live, with 0 frames re-applied. A dropped connection resumes from the last offset with no refetch.

Cold visits pay a tax; warm visits win

A first visit pays the WASM download, compile, and initial load, plus catch-up: a real tax versus a plain network fetch. A return visit reuses the HTTP cache and the persisted OPFS data, resumes from the saved position, and skips re-downloading the dataset. Warm is where the replica clearly comes out ahead.

When to reach for it#

Reach for the replica when a query cache stops being enough: arbitrary local queries a cache hasn't fetched, offline reads over the whole dataset, or consistent local snapshots across related entities. It replaces both an SDK read-cache and its subscribe-WS with one local queryable store fed by one durable stream, moving query execution onto the client.

Otherwise the cache is the better trade. A well-cached REST client gives instant renders, stale-while-revalidate, and optimistic mutations for most app UX, with no WASM and instant cold start. The realtime SDK path also has a scaling cost: a live query re-runs all() + filter over every row per matched event per connection, which caps how far it scales before the replica becomes the only answer.

Limits#

  • Read-only. The replica is a read-only follower. Writes still round-trip to the server, so there's no write-latency or offline-write advantage yet; the advantage is entirely on the read side.
  • Not a compute win on small data. V8 is fast at object work, and crossing the JS-to-WASM-to-Worker boundary has real per-call cost. The wins are identity, scale at tens of thousands of rows and up, and the shared byte format, not raw speed.
  • Cold-start tax is real and unmeasured. The .wasm binary is a separate sibling asset (hundreds of KB to low-MB depending on schema and features); its size, compile time, and time-to-first-local-query aren't benchmarked yet.
  • No wall-clock time-travel locally either. Snapshot tokens are row-count watermarks, not timestamps (see Point-in-time reads).

See Live queries & change feed for the in-process realtime surfaces on the SDK path.

Search documentation

Find pages across the ForgeDB docs