# What is Rindle?

Rindle keeps your queries live with one engine — from a browser tab to a Node server to a synced, local-first app, behind one query language and one correctness contract.

Register a query once and the engine **maintains** its result as the data changes —
recomputing only what each write affects, never the whole query. Its core contract is
**view-after-write == fresh-query**: the deltas you receive, applied in order, always
equal running the query from scratch.

The engine is `rindle`, an incremental view maintenance (IVM) engine written in
Rust. Its one trick is the thing read models, caches, and dashboards are forever
reinventing:

```text
re-run on every write     -> cost grows with the table
maintain on every write   -> cost grows with the change
```

## Rows in, view out

That's the whole mental model: **rows change in, the view updates out.** Query
once, maintain forever. It's a small primitive you can drop into what you already
have — no networking, no sync, no architecture to adopt — and it already solves
problems most apps hand-roll:

- expensive recomputation and cron-refreshed tables;
- cache invalidation (there is no separate cache — the view *is* the result);
- read models and derived state;
- live dashboards and materialized views.

The lowest-friction taste is the engine compiled to wasm, running in-process in a
browser tab. The same engine is the whole API — four steps: schema, store, query,
write.

```ts
import { table, string, number, boolean, createSchema, createWasmStore } from "@rindle/wasm";

const issue = table("issue")
  .columns({ id: number(), title: string(), closed: boolean() })
  .primaryKey("id");
const store = await createWasmStore(createSchema({ tables: [issue] }));

const view = store.query.issue.where.closed(false).orderBy("id", "desc").materialize();
view.subscribe((rows) => render(rows));  // fires now, then after every write that affects it

await store.write((tx) => tx.add("issue", { id: 1, title: "first", closed: false }));
```

Here the schema is written in TypeScript because this in-tab engine has no database — the
rows come from `tx.add`. The moment your data lives in SQLite (a [daemon](/docs/daemon) or a
synced app), **SQL is the source of truth**: you define tables with ordinary DDL and
*generate* this same schema from them with `rindle schema gen`. The typed builder is a facade
over your tables, never where they're defined — see [schema & migrations](/docs/schema).

## One engine, every home

The same engine runs wherever your data lives, behind one schema and one query
language — **Deltic**, a fluent builder in Rust or JS — with one materialized-view
contract. Use any one home on its own, or compose them:

| Home | Package / crate | What it is |
| --- | --- | --- |
| **Browser** | [`@rindle/wasm`](/docs/wasm-client) | the engine compiled to wasm, queries in-process — reactive reads with no server (~200 kB gz) |
| **Node** | [`@rindle/replica`](/docs/replica-and-views) | native SQLite + `BEGIN CONCURRENT` CDC, in-process |
| **Rust** | [`rindle`](/docs/crates) / [`rindle-replica`](/docs/replica-and-views) | the std-only IVM core (open source), embedded directly; `rindle-replica` adds the live-replica runtime (commercial) |

Start with the engine in-process (Level 1); run it as the always-up **`rindled`**
daemon when you want the same live queries shared across processes (Level 2). The
core is **open source** (Apache-2.0): the `rindle` engine crate, the `rindle-sqlite`
backend, and the JS/TS packages, at
[`github.com/rindle-sh/rindle`](https://github.com/rindle-sh/rindle). The
high-concurrency server runtime — `rindle-replica`, `rindled`, and the replicated
fleet — is commercial and ships as prebuilt binaries. Both routes are just the
engine — [use the engine →](/docs/how-it-works).

## The change model

You never receive a re-fetched result — you receive the **delta**. A change event is
one of four shapes: a row entered (`Add`), left (`Remove`), changed in place
(`Edit`), or a nested relationship of an in-view row changed (`Child`). There is no
`Insert` / `Delete` / `Update` — those are the writes you make, not the deltas you
receive. The JS view folds the stream for you; applying it reconstructs exactly a
fresh query — see [the change model](/docs/change-model).

## Scale up to a synced app (Level 3)

Because the same engine runs in the browser *and* on the server, the most
sophisticated thing it does falls out as a **consequence**, not a prerequisite: a
**synced, local-first app in three tiers** — the same shape as client / stateless
app server / database, but live end to end.

- a **browser client** that runs its **own** engine over its **own** local database:
  reads resolve locally and instantly, writes apply optimistically;
- a **stateless API server** that is your app's authority — it resolves named
  queries and runs the authoritative mutators;
- an always-up **Rindle data tier** — a `rindle-replicator` write-master plus one or
  more **`rindled`** read-followers (the smallest is a *colocated pair* on one box) —
  that holds the data and the live queries and streams normalized updates to every
  subscriber.

The client is one call:

```ts
import { createRindleClient } from "@rindle/optimistic";
import { mutators, schema } from "./app-def.ts";                  // schema + isomorphic mutators
import { issuesPageQuery } from "./IssueListItem.queries.ts";     // a co-located named query

export const app = await createRindleClient({
  schema,
  mutators,
  user: () => currentUser(),                                      // a mutator's ctx.user
  api: { url: "", headers: () => ({ "x-user": currentUser() }) }, // your API authority
  daemon: { wsUrl: "ws://127.0.0.1:7601" },                       // the rindled follower's public ws
  onRejected: (envelope, reason) => showToast(`${envelope.name}: ${reason}`),
});

const view = app.store.materialize(issuesPageQuery({ limit: 50 })); // live, local, instant
app.mutate.createIssue({ id, title: "ship it", /* … */ });          // optimistic; rebased on confirm
```

At this altitude the engine takes over three things every app reinvents and most
get subtly wrong — **live queries**, **the cache**, and **optimistic updates** —
with no polling, nothing to invalidate, and no rollback code. When you want it,
start with **[the architecture](/docs/architecture)**.

## Choose your path

- **Use the engine** *(Levels 1–2, start here)* → start with
  [how it works](/docs/how-it-works), then
  [reactive reads in the browser](/docs/wasm-client), the
  [Rust quickstart](/docs/quickstart), [replica & views](/docs/replica-and-views),
  and the [crate map](/docs/crates).
- **Build a synced app** *(Level 3)* → start with
  [the synced-app quickstart](/docs/synced-app-quickstart), scaffold a TanStack app with
  [`create-rindle`](/docs/create-rindle), then read [the architecture](/docs/architecture),
  [the client](/docs/client), [the API server](/docs/api-server), and
  [the daemon](/docs/daemon) — or [see it whole](/docs/example-issue-tracker).

## Learn the concepts

- [How it works](/docs/how-it-works) — build → lower → hydrate → push, and why
  incremental beats recompute.
- [Schema & migrations](/docs/schema) — SQL is the source of truth; migrate it and
  generate the typed schema your query builder needs.
- [The change model](/docs/change-model) — the `Add` / `Remove` / `Edit` / `Child`
  vocabulary and the replay-equivalence invariant.
- [Supported queries](/docs/supported-queries) — the honest matrix of what builds,
  pushes, and materializes today.
- [Performance](/docs/performance) — sub-microsecond incremental maintenance that
  stays flat as data grows, measured end to end.
- [Is Rindle for you?](/docs/compare) — what it replaces and how it compares.

---

[View this page on Rindle](https://rindle.sh/docs/overview)
