Concepts
The core mental models behind Voltro: projects, apps, descriptors, executors, reactivity, streams, workflows, and runtime context.
This page is the map before the territory. It names the framework concepts you will see everywhere else in the docs and explains how they fit together.
Project, Apps, And APIs
A project is the deployable unit under apps/<projectName>/. It can contain multiple apps:
- an
apiapp: backend procedures, database schema, workflows, plugins - a
webapp: pages, layouts, React hooks, SSR/static rendering - optional sibling apps: dashboards, docs, admin panels, extra APIs
A web app talks to one or more API apps through names configured in app.config.ts:
apis: {
app: { package: '@app/api' },
billing: { package: '@app/billing-api' },
}The first argument to client hooks is that API name:
useSubscription('app', 'todos.list', {})
useMutation('billing', 'invoices.pay')RPC Tags
Every procedure descriptor declares a globally unique name. That name is the RPC tag:
defineQuery({ name: 'todos.list', /* ... */ })
defineMutation({ name: 'todos.create', /* ... */ })
defineAction({ name: 'support.ping', /* ... */ })
defineStream({ name: 'agent.run', /* ... */ })Hooks use the API name plus RPC tag. There is no hand-written client SDK per endpoint.
Descriptor And Server Executor
Procedures are split into two files:
| File | Purpose |
|---|---|
*.query.ts, *.mutation.ts, *.action.ts, *.stream.ts |
Browser-safe descriptor: name, schema, metadata. |
*.query.server.ts, *.mutation.server.ts, *.action.server.ts, *.stream.server.ts |
Server-only executor: database, SDKs, secrets, side effects. |
This keeps the wire contract importable from the client while server code stays server-only.
The Main Primitives
| Primitive | Use it for | Client hook | Durable? | Reactive? |
|---|---|---|---|---|
| Query | Live reads and views | useSubscription |
Reads durable state | Yes |
| Mutation | Atomic database writes | useMutation().mutate |
Yes | Triggers queries |
| Action | One-shot side effects and external I/O | useAction().run |
Request-scoped | No |
| Stream | One-shot server-to-client element feeds | useAgentStream |
No, unless you persist | No |
| Workflow | Long-running multi-step work | Started from server code | Yes | Via the rows it writes |
Decision shortcut:
- Need live data on screen? Query.
- Need one atomic database commit? Mutation.
- Need HTTP/email/payment/upload/AI one-shot? Action.
- Need progressive tokens/progress/log events? Stream.
- Need retry/resume across crashes or deploys? Workflow.
Beyond the core hooks. These five are the data layer. The Schema-driven UI section builds on them:
<AutoForm>binds to a mutation,<DataTable>to a query, and a toolbox of bound hooks —useCan,useUndo,usePreview,useProvenance,useAsyncValidation,useOutbox,useWindowedSubscription, … — covers the rest. Full list: Schema-driven UI → Client utilities.
Reactivity
Voltro is reactive end-to-end. A component subscribes to a query; the server keeps it live.
const { data: todos } = useSubscription('app', 'todos.list', {})When a mutation commits, the runtime emits change events. Queries whose source matches the changed table can update automatically.
defineQuery({ name: 'todos.list', source: 'todos', /* ... */ })
defineMutation({ name: 'todos.create', target: { table: 'todos', op: 'insert' }, /* ... */ })There is no refetch as the normal path. The subscription lives for the lifetime of the component.
Streams Are Not Subscriptions
Queries stream subscription events: an initial snapshot, then deltas when durable state changes.
Streams emit plain elements and then finish:
const run = useAgentStream('app', 'agent.run')
run.start({ prompt })Use streams for transient output: LLM tokens, progress updates, import logs. If the result must survive reloads, write rows and expose them through a query.
Runtime Context
Server executors receive ctx:
export default async (input, ctx) => {
const subject = ctx.request.subject
const rows = await ctx.store.query(/* ... */)
}Important pieces:
ctx.request.subject: the authenticated user, API key, system actor, or anonymous subjectctx.store: typed data store with tenant/audit/soft-delete mixin behaviorctx.cache: async cache facade- Effect services: actions and streams can return
Effects and use platform services such asHttpClient
Tenant Scoping
Tables with the tenant() mixin are scoped by the runtime using ctx.request.subject.tenantId. Query executors do not need to repeat that predicate. Mutations should still validate any tenant IDs that arrive in input.
Generated Files
Voltro discovers files and writes generated glue such as rpcGroup.generated.ts or .framework/*. Treat those as build artifacts. Edit descriptors, server executors, schema, pages, and config instead.
Where To Go Next
- File conventions - exact suffixes and discovery rules
- Data overview - queries, mutations, actions, streams
- Runtime context - what
ctxcontains - Workflows - durable work
- AI streaming - transient vs persisted streams