in private development
Any SQL query, kept true as Postgres changes.
Quiver reads the write-ahead log, maintains your query against an in-memory replica, and pushes the exact changes: insert at 0, update at 3, delete at 7.
SELECT author, body FROM messages WHERE room_id = $1 ORDER BY sent_at DESC LIMIT 5
- marycanary is up
- linuswatching the graphs
- adastarting the rollout
A recorded stream, played at reading speed. Hover to hold it still.
median write to client, against 263.3 for Realtime
of a 43-query corpus of real ORM output maintained in tier 1
still keeping up when the load generator gave out
measured
A write is on screen before Realtime notices.
Supabase Realtime polls the replication slot on an interval, so its delivery is spread almost uniformly from nothing to about half a second. Quiver is woken by the stream, so it lands in single-digit milliseconds and stays there.
Both stacks on one machine, interleaved, against the same Postgres. Eight subscribers over four rooms, fifty writes a second for fifteen seconds, three repetitions. Every write was delivered by both.
One axis for both. The lit tick is where Quiver had already finished, played at a fortieth of real time.
| percentile | Quiver | Realtime |
|---|---|---|
| p50 | 4.0 ms | 263.3 ms |
| p90 | 6.3 ms | 479.8 ms |
| p99 | 10.0 ms | 526.7 ms |
and against Convex
Ahead on delivery. Most of that lead is the write, not the reactivity.
Take the write path out and Convex's push is faster on two of the three. Convex runs on Postgres here rather than its container's default SQLite, is reset by dropping its database, and gets matched container limits. 256 subscribers, delivery p50 at a hundred writes a second.
| workload | Quiver | Convex |
|---|---|---|
| newest 50 in a room | 6.7 ms | 8.9 ms |
| a maintained count | 6.9 ms | 7.7 ms |
| newest 50 joined to authors | 6.8 ms | 10.9 ms |
The large difference is write capacity. Convex stops absorbing load between two and four hundred writes a second. Quiver was still keeping up at 3,200, when the load generator gave out.
how it works
One stream, from the log to the row on screen.
Postgres publishes
Logical replication, from a temporary slot Quiver drops the moment it disconnects. A stopped Quiver pins no write-ahead log in your database. Only the tables and slices something subscribes to are held.
The replica maintains
Projections, predicates, ORDER BY with LIMIT and OFFSET, inner joins, counts and sums, DISTINCT, CTEs, case-insensitive lookups and bound parameters, all kept true incrementally. No query runs against Postgres for a change the replica can absorb.
The client is spliced
Changes are emitted at commit boundaries only, as ordered splices applied in place. Nothing mid-transaction reaches a subscriber. The React hook and the TanStack DB collection are thin layers over the same stream.
two tiers
Nothing falls off the edge.
Not every query can be maintained in memory. What cannot be — a window function, an operator the replica cannot evaluate — is re-queried upstream with the subscriber's claims and diffed to the same splices. The client is told which tier it got, and why, so a query can be rewritten into one the replica maintains.
| tier | answered by | upstream queries per change | row-level security |
|---|---|---|---|
| 1 | the replica, incrementally | none | opt-in per table, for recognised policy shapes, checked continuously |
| 2 | Postgres, re-queried and diffed | one, shared across identical queries and claims | applied by Postgres, as the subscriber's role |
write it
The way you already write queries.
The input is SQL with bound parameters, so the builders people already use are the way in. Each adapter is one function from a compiled query to a subscription, and the server decides the tier per query.
const { rows } = useQuery(
'SELECT id, author, body FROM messages WHERE room_id = $1 ORDER BY sent_at DESC LIMIT 50',
{ params: [roomId], keyColumns: ['id'] },
);Beside the Postgres you already run
Bring your own database is the first-class deployment. Quiver holds a temporary replication slot, mirrors only what is subscribed, and falls back to tier 2 rather than growing past a memory ceiling, so a spike of new users makes it slower rather than dead. Row-level security fails closed: a protected table is served from the replica only when its policy is a shape Quiver reproduces, and a continuous check takes it back to tier 2 the moment Postgres disagrees.
Beside Supabase, that is the direct connection over TLS with their root certificate, the project's public keys for tokens, and the authenticated role for anything tier 2 runs. A small instance in the project's region is enough.
What it is not, yet
There is no hosted service. Subscriptions are queries rather than row events, so moving off Realtime means rewriting subscribe calls, not wrapping them. A Postgres wire proxy and a local-first mode are designed and not built.
The differential harness that checks every answer against Postgres runs on every change, and it is what the numbers above rest on.
Put it beside your database.
Quiver is in private development. If you are running Postgres and reaching for polling, tell us what you are building.