Live data from the database.
With no wires.
The hardest part of a real-time app is keeping the client in sync with the database. Voltro makes it the easy part: declare a query, subscribe with one hook, and every write fans out to every connected client over WebSocket. No polling. No refetch. No cache invalidation logic to forget.
// queries/posts.list.query.ts
import { defineQuery } from '@voltro/protocol'
import { Schema } from 'effect'
export const listPosts = defineQuery({
name: 'posts.list',
source: 'posts', // ← drives auto-invalidation
input: Schema.Struct({}),
output: Schema.Array(Post),
})
// queries/posts.list.query.server.ts
import { database } from '../database/schema'
export default (_input, _ctx) =>
database.posts.orderBy('createdAt', 'desc').limit(100)Reactivity is a runtime feature, not a plugin.
Every Voltro app gets these primitives by default. No extra package to install, no opt-in flag — turn off `tenant()` on a table and it still pushes; turn off the source: declaration and only the manual cache-bust path drops.
One hook, live data
useSubscription(api, tag) opens a WebSocket subscription cached by (api, tag, input). Two components asking for the same data share one upstream connection.
Postgres LISTEN/NOTIFY native
On Postgres, change events ride the database's built-in pub/sub. No Redis required. Other dialects use binlog CDC (MariaDB) or the @voltro/plugin-broadcast bus (MySQL, MSSQL).
Auto-optimistic patches
Declare `target` on a mutation; the client pre-applies the patch to every active subscription that matches. Server delta replaces the optimistic row when it lands. Failures auto-revert.
Tenant-scoped by default
When a table carries the tenant() mixin, the runtime AND-merges the tenant predicate into every subscription. Cross-tenant changes never wake the wrong subscriber.
Per-field relevance filter
The dispatcher tracks which columns each subscription depends on (projection + predicate + order + every join key). A write that touches an unrelated column skips re-query entirely — no SQL round-trip, no delta.
Computed + eager-loaded
Return a shaped value with relations: useSubscription gets the SAME object the server returns, fully typed, with eager-loaded children. The reactivity re-runs the whole shape on any change to the source table set.
The client side is the same hook you'd write anyway.
// In any React component
import { useSubscription } from '@voltro/web'
const PostsList = () => {
const { data: posts } = useSubscription<Post[]>(
'app', 'posts.list',
)
// posts updates whenever ANY mutation writes to the
// 'posts' table. No useEffect. No refetch.
return (
<ul>
{posts?.map(p => <li key={p.id}>{p.body}</li>)}
</ul>
)
}The thing you would have had to build.
Without reactive primitives, a real-time list view is a four-system integration: a database, a change-feed (LISTEN/NOTIFY, CDC, polling), a pub/sub bus (Redis, NATS), and a client cache (TanStack, SWR). Each system has its own retry semantics, its own auth story, its own observability gap, its own deploy rollback. Voltro wires them all to ONE invariant — the source: declaration on the query — and gives you reactive lists for free.
The control loop, in one sentence:
A mutation lands in Postgres → the runtime catches the change via LISTEN/NOTIFY → the matcher walks every open subscription whose source set includes the changed table → relevance pre-filter compares the mutated columns to each subscription's dependency set → matching subscriptions re-query in batch → the diff (snapshot or delta) ships to the client over WebSocket → useSubscription's data prop updates and React re-renders.
Reactivity composes with the rest of the runtime.
Mutations
Auto-optimistic patches on every subscription with a matching source. Transactional writes. Typed errors.
Multi-tenancy
The runtime AND-merges the tenant predicate into every subscription. Cross-tenant changes never wake the wrong subscriber.
Schema DSL
The .reactive() mixin opts a table into the matcher engine. Composes with audit(), tenant(), and your own mixins.
Open the framework. See it for yourself.
Every primitive on this page is in the framework today. Clone the starter, run `voltro dev`, and have it on screen in two minutes.