tracks02 / 04 uifor interfaces

the full stack reactive database.

This is the track everything else grew out of, and it comes in stages. A reactive database in your browser makes UI updates simple. One in your backend makes collaboration simple. Run it on both ends and you have the full local-first stack — instant reads, optimistic writes, and sync you don't build. Start at whichever stage your app is at.

01 · reactive db in your browser

materialize a query. render it forever.

The engine ships as wasm — ~200 kB gzipped — and runs in-process over a local database. You write a query once and materialize() it; the subscription fires only when the result actually changes, with the exact delta already applied. No cache layer, no invalidation, no state-library choreography — the query is the state.

const view = store.query.issue .where.closed(false) .orderBy("priority", "desc") .limit(50) .materialize(); view.subscribe(render); // fires only when the result actually changes

02 · reactive db in your backend

one daemon, every subscriber: collaboration.

The rindled daemon holds the data and every live query; your API server stays a stateless authority that resolves named queries and runs the authoritative mutators. Every commit — whoever made it — fans out to every subscriber as ordered row deltas. Two users in the same view see each other's edits because that's what the engine does, not because you built broadcast plumbing.

// the client only ever sends { name, args } — the authority resolves the astconst api = createRindleApiServer({ daemon, queries, mutators }); // every commit streams to every subscriber, as deltas, in order

03 · reactive db on both ends

the full stack: local-first.

Give the client its own engine over its own local database, fed by the server's delta stream. Reads resolve locally and instantly. Writes are predicted mutators: they apply synchronously, the engine rebases them as the server confirms, and a rejection snaps back on its own — you never write rollback code. Same query API as stage one; the client just computes its own result now. (This same rebase is how the ai track lets agents branch reality.)

const { store, mutate } = createOptimisticStore(schema, source, mutators, { clientID }); // reads resolve locally — instantlyconst view = store.query.issue.where.closed(false).materialize(); // writes apply NOW; the engine rebases on confirm, snaps back on rejectmutate.createIssue({ id: 7, title: "ship it" });

04 · the details

the parts that make it ship.

05 · ship it

your docs path: build a synced app.

The three stages are one docs path, walked in order: the quickstart stands up all three tiers, then the client, fragments, SSR, and the API server go deep on each part. Scaffold it with create-rindle or wire it by hand.