incremental view maintenance, anywhere

reactive queries, anywhere.

live queries, the cache, and optimistic updates — the three things every app gets wrong — handled by one small engine that runs wherever your data does: the browser, a node server, over the wire, or embedded in rust. write the query once; it stays live everywhere. the playground below is that engine — ~228 kb of wasm — running live in this tab.

scroll ↓

01 / 03

materialize a query

point a source at it — a table, a replication stream, an api, a queue, anything that produces rows. materialize a query and the result stays live as writes land. no polling, no re-running. the view is the cache, and it never goes stale.

02 / 03

enrich it

bring in a second source — your model’s relevance scores, joined in. the same live view, now ordered by what your model thinks matters. operational data and ai output, side by side, kept current by the engine.

03 / 03

and again

add a third source, a live reactions stream. three sources, one query, one view. each write touches one row; the engine propagates just that delta and the view updates in place. nothing re-scans — cost tracks the change, not the table.

sqlite replicaread · write · ordinary sql

04 / playground · live in your browser

now it’s real.

this is the actual @rindle/wasm engine, booted in your tab. change the query with the knobs — the engine re-materializes and streams genuine incremental deltas. the code on the left is exactly what runs.

store.query.thread .orderBy("votes", "desc") .limit(6) .sub("comments", comment, { parent: ["id"], child: ["threadID"] }, (c) => c.orderBy("id", "desc").limit(2)) .materialize();
6
thread ⟨ comments · votes · top 6
booting the real @rindle/wasm engine in your browser…
engine wasm · this tabΔ 0 rowsre-scans 0latency <0.001 mswrites 0rows 0

the payoff · local-first

give the client its own engine.

the same engine runs on the client, over its own local database fed by a stream from the server. reads resolve locally and instantly; writes apply optimistically and the engine itself rebases them as the server confirms. same store.query…materialize() as above — only now the client computes its own result.

// the client runs its OWN engine, fed by the serverconst { store, mutate } = createOptimisticStore( schema, source, mutators, { clientID }); // reads resolve locally — instantlyconst view = store.query.issue .where.closed(false) .orderBy("priority", "desc") .materialize(); // writes apply NOW — the engine rebases them// as the server confirms; rejections snap backmutate.createIssue({ id: 7, title: "ship it" });

optimistic writes

named mutators apply locally, synchronously — no spinner, no round-trip. the engine rebases them as the server confirms; a rejected write snaps back on its own.

instant local reads

a new query over already-synced rows resolves against the local db — local query resolution, zero round-trips.

no cache to invalidate

the local database is the cache; the engine keeps every view derived from it exact, by construction.

proof · measured, not promised

fast where it counts.

incremental maintenance means you pay for the change, not the table. measured end to end over a real dataset, scaled 30×.

~430nsper incremental update, in-process
flat·30×cost holds as the dataset grows 30×
228kbthe whole engine, gzipped, in a tab
0locinvalidation or rollback code you write

one engine · four homes

the same query, wherever your data lives.

one schema, one fluent builder, one materialized-view contract — view-after-write equals a fresh query, everywhere. swap the backend, keep the code.

incremental view maintenance · browser · node · network · rust