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.

posts.list.query.ts
TypeScript
// 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.

PostsList.tsx
TypeScript
// 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.

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.