The first principle is not "make SQLite reactive." SQLite already knows when a transaction commits. React already knows how to rerender from cached data. The missing piece is a narrow contract between them:
- accept only subscribable SQL,
- map that SQL to the tables it depends on,
- publish a cheap table-level signal after commit,
- rerun the subscribed query and let SQLite be the source of truth.
The demo below runs that loop in the browser. The page itself is useful before JavaScript loads; once hydrated, a Rust WebAssembly bundle opens an in-memory SQLite database and exposes the same DbClient shape that the native app uses.
1. A live query is SQL plus an invalidation signal
packages/db-react adapts the runtime contract to TanStack Query. The hook derives a stable query key from Drizzle SQL and transport params, subscribes in changes mode through LiveQueryClient, and invalidates the key when the runtime reports a relevant commit.
2. Classify the SQL, then ask SQLite what it touches
crates/db-sql answers the cheap boundary questions first: is the leading statement a read, is there more than one statement, which parameters appear, and which aliases point at known objects? If that passes, crates/db-reactive uses SQLite catalog information plus EXPLAIN QUERY PLAN to produce conservative dependency targets.
3. Commits are the only signal you need
That signal is intentionally small: table plus sequence number. It is not a row diff, not a predicate proof, and not a replacement for reading SQLite again.
The native stack rhymes
The browser demo uses crates/reactive-sqlite-demo-wasm because there is no Tokio/sqlx pool inside a blog post. The production stack keeps the same mental model with crates/db-change, crates/db-reactive, crates/db-reactive-sqlx, crates/db-runtime, and packages/db-react.
The important constraint is the one the demo keeps repeating: every write that should wake a live query must go through the runtime that owns the change source. Outside writers can still modify the SQLite file, but they are invisible to this process's hooks. That is why the native stack centralizes writes through db-runtime, and why the browser demo routes every button through the wasm DbClient.