Leg eine Datei ab.
Die Runtime verdrahtet sie.

Jede Fähigkeit ist ein typisierter Descriptor, per Datei-Konvention erkannt. Nenn eine Datei *.query.ts, *.mutation.ts, *.workflow.tsx — der Discovery-Walker der CLI findet sie beim nächsten Boot und verdrahtet sie in die typisierte RPC-Gruppe. Keine Import-Barrels, keine Registrierung, kein Codegen-Befehl zum Merken.

file conventions
Discovery
// Drop a file with the right suffix — that's it.
todos.list.query.ts          → useSubscription
todos.create.mutation.ts     → useMutation
sendEmail.action.ts          → useAction
order.fulfil.workflow.tsx    → useWorkflow
nightly.cron.tsx             → scheduled
stripe.webhook.tsx           → signed inbound

// Each: a browser-safe descriptor + a .server executor.
// The discovery walker wires it in on next boot.

Das Primitive-Set, nach Datei-Suffix.

Descriptor + Server-Executor

Jede Prozedur sind zwei Dateien: ein browser-sicherer Descriptor (Name, effect/Schema, Metadata) und ein server-only Executor (*.server.ts — DB, SDKs, Secrets). Die Browser-Safe-Regel ist transitiv, sodass deine Schemas zum Client shippen, ohne Node-Internals mitzuschleppen.

Das Kern-Quartett

query (Live-reaktiver Read → useSubscription), mutation (atomarer transaktionaler Write → useMutation), action (One-Shot-externes-I/O → useAction), stream (Server→Client-Push → useAgentStream). Input, Output und Errors sind alle effect/Schema.

Durable & geplant

workflow (durable mehrstufig), cron (geplant), trigger (Domain-Event → Fan-out), subscribe (Pro-Tabelle-Post-Commit-Reaktion), aggregate (materialisierte Query). Die durablen überleben Deploys, Retries und Crashes.

AI & Tools

agent (persistierter AI-Chat → useAgent), tool (model-aufrufbare Funktion), reaction (eine Änderung feuert einen Agent hinter verpflichtenden Spend-Guards). Agents auto-migrieren ihre Thread- + Message-Tabellen, tenant-scoped.

HTTP, Webhooks & E-Mail

route (öffentlicher Raw-HTTP-Endpunkt), webhook (signiertes Inbound + durable Outbound), email (React-Email-Template), plus seed- und startup-Hooks — dasselbe Discovery-Modell, anderes Suffix.

Eindeutige RPC-Tags, beim Boot geprüft

Jeder Descriptor hat einen global eindeutigen Punkt-Namen (todos.list → todosListRpc). Kollisionen lassen den Boot fehlschlagen; es gibt kein handgeschriebenes SDK pro Endpunkt, und voltro dev regeneriert den typisierten Client bei jeder Änderung.

Konvention statt Registrierung.

Der Discovery-Walker macht die Verdrahtung

Du pflegst nie eine Registry oder ein Import-Barrel. Die CLI läuft den Baum ab, findet jede Datei mit bekanntem Suffix und verdrahtet sie in die generierte RPC-Gruppe — Typen fließen über Vites Modul-Graph zum Client. Die eine Regel: Ein Descriptor und alles, was er importiert, muss browser-sicher bleiben, also lebt server-only Code in der *.server-Datei daneben.

Die ganze Familie, je eine Datei.

queries/orders.list.query.ts
ts
// queries/orders.list.query.ts
export const listOrders = defineQuery({
  name:   'orders.list',
  source: 'orders',            // live: any write to orders re-pushes this
  input:  Schema.Struct({ status: Schema.optional(Schema.String) }),
  output: Schema.Array(Order),
})

Jedes ist eine Datei, deren NAME die Registrierung ist. Es gibt keine Registry zum Anhängen und keinen Decorator zum Erinnern — eine Prozedur, die existiert, kennt die Runtime, und eine gelöschte ist beim nächsten Boot aus der Client-Oberfläche verschwunden.

Die Primitive-Familie im Detail.

Warum hat das Framework überhaupt benannte Primitive?

Weil die Alternative ist, dass jedes Team sein eigenes Vokabular für dieselben sechs oder sieben Dinge erfindet — und nichts davon inspizierbar ist. Eine Query, eine Mutation, eine Action, ein Stream, ein Workflow, ein Schedule, ein Event: sie zu benennen heißt, dass die Runtime weiß, was jedes ist, und Dinge für dich tun kann, die eine schlichte Funktion nicht kann.

Eine Query deklariert die Tabellen, die sie liest — die Runtime kann ihren Cache invalidieren und Deltas pushen, wenn die sich ändern. Eine Mutation deklariert, was sie schreibt — optimistische Updates werden abgeleitet statt geschrieben. Ein Workflow deklariert seine Schritte — ein Absturz setzt fort statt neu zu starten. Die Deklaration ist es, die das Verhalten erkauft.

Es heißt außerdem, dass Werkzeuge Fragen über deine App beantworten können, ohne sie zu starten: welche Procedures es gibt, was jede annimmt und zurückgibt, wer sie aufrufen darf, was sie berührt. So kann die CLI deine Oberfläche typechecken, ein OpenAPI-Dokument erzeugen oder dir sagen, dass ein Guard einen Scope verlangt, den keine Rolle vergibt.

Wie wähle ich zwischen Query, Action und Mutation?

Eine Query LIEST und ist abonnierbar — deklarier sie, wenn ein Client die Antwort sich über die Zeit ändern sehen soll. Eine Mutation SCHREIBT in deklarierte Tabellen und bekommt optimistisches Patchen und Change-Broadcast geschenkt. Eine Action ist die Notluke: sie tut etwas, das weder ein reiner Read noch ein Tabellenschreibvorgang ist — eine Fremd-API rufen, ein Token erzeugen, einen Job anstoßen.

Die falsche Wahl ist meist eine Action, wo eine Mutation hingehört hätte — denn die Action überspringt die Maschinerie: ohne deklarierte Ziele kein optimistisches Update und keine automatische Invalidierung, der Screen wird schal, und jemand baut zum Ausgleich ein manuelles Refetch ein.

Ein Stream ist für eine Antwort, die in Stücken kommt — eine Token-für-Token-KI-Vervollständigung, ein langer Export. Ein Workflow ist für Arbeit, die einen Absturz überleben muss. Wenn du zwischen Mutation und Workflow schwankst, frag, ob ein Fehler auf halbem Weg das System inkonsistent zurücklässt; wenn ja, ist es ein Workflow.

Was enthält eine Primitiv-Deklaration eigentlich?

Einen Namen, ein Input-Schema, ein Output-Schema, eine Zugriffsentscheidung und einen Handler. Die Schemas sind echte Laufzeit-Validatoren, keine Typannotationen, die beim Build verschwinden: ein Input, den das Schema nicht beschreibt, wird abgelehnt, und ein nicht passender Output ist ein Defekt, der an der Grenze auffällt statt in einem Client drei Schichten weiter.

Die Zugriffsentscheidung ist Pflicht. Jedes über die Leitung erreichbare Primitiv deklariert entweder einen Guard oder einen ausgeschriebenen Grund, offen zu sein, und der Boot verweigert den Start, wenn eines unentschieden ist — denn der Fehlermodus eines vergessenen Guards ist still und vollständig.

Alles Weitere sind optionale Metadaten, die Verhalten freischalten: die Tabellen, die eine Query liest, die Ziele, die eine Mutation schreibt, Retry-Policy, Rate-Limits, ob sie in der generierten REST-Oberfläche auftaucht oder einem KI-Agenten als Werkzeug angeboten wird. Du optierst hinein, was du brauchst, der Rest kostet nichts.

Sind Primitive einfach Dateien, oder gibt es eine Registry?

Dateien. Eine `*.query.ts` ist eine Query, eine `*.mutation.ts` eine Mutation, eine `*.workflow.tsx` ein Workflow — per Konvention entdeckt, ohne Registry zum Anhängen und ohne etwas, woran man beim Boot denken müsste. Eine Datei zu verschieben verschiebt das Primitiv; eine zu löschen löscht es.

Die Konvention trägt clientseitig eine echte Einschränkung: alles, was die Codegen ins Browser-Bundle zieht, muss browser-sicher sein — ein Descriptor und seine transitiven Importe dürfen also weder den Datenbank-Handle noch ein Node-Built-in erreichen. Deshalb sind Workflows in Descriptor und serverseitigen Executor geteilt.

Das Framework erzwingt das strukturell statt per Review — ein Boot-Guard läuft den generierten Graphen ab und bricht mit der exakten Import-Kette ab, wenn etwas Serverseitiges im Browser landen würde. Früher zeigte sich ein Leck als Bundle, das auf zig Megabyte explodierte, oder als Crash über ein externalisiertes Node-Modul.

Zu welchem Primitiv du greifst.

Die Primitive und was ihre Deklaration einbringt
PrimitivNimm es, wenn
QueryEin Client Daten liest, die live bleiben sollen — deklariert Tabellen, bekommt Subscriptions und Cache-Invalidierung.
MutationDu in deklarierte Tabellen schreibst — bekommt abgeleitete optimistische Updates und Change-Broadcast.
ActionDie Arbeit weder Read noch Tabellenschreibvorgang ist: ein Fremdaufruf, ein Token, ein Seiteneffekt.
StreamDie Antwort in Stücken kommt — KI-Vervollständigungen, lange Exporte, fortschreitende Ergebnisse.
WorkflowDie Arbeit einen Absturz mittendrin überleben muss oder auf einen Menschen wartet.
Schedule / EventArbeit nach der Uhr läuft oder sich aus etwas auffächert, das anderswo passiert ist.

Häufige Fragen

Kann ich nicht einfach eine normale Funktion schreiben?

Kannst du, und für interne Helfer solltest du das auch. Ein Primitiv verdient seine Deklaration, sobald etwas die Leitung überquert oder koordiniert werden muss: dann muss die Runtime wissen, was es ist, um es zu validieren, zu gaten, zu abonnieren oder fortzusetzen.

Wird die Schema-Validierung wirklich zur Laufzeit durchgesetzt?

Ja, in beide Richtungen. Ein Input, den das Schema nicht beschreibt, wird abgelehnt statt beschnitten — seit 0.37 lässt ein nicht deklariertes Feld den Aufruf scheitern, statt still verworfen zu werden — und ein nicht passender Output fällt an der Grenze auf.

Was passiert, wenn ich die Zugriffsentscheidung vergesse?

Die Anwendung verweigert den Boot und benennt das Primitiv. Das ist Absicht: ein unentschiedener Guard scheitert offen, und ein Fehler, der sich nur als unbefugter Zugriff zeigt, fällt niemandem auf, bevor es zählt.

Wie erreichen Primitive einen REST-Client oder einen KI-Agenten?

Per Opt-in an der Deklaration. Ein Primitiv kann in eine generierte OpenAPI-Oberfläche projiziert oder einem Agenten als aufrufbares Werkzeug angeboten werden — mit den Schemas, die es ohnehin trägt. Es gibt keine zweite Definition zum Synchronhalten.

Muss ich Effect können, um einen Handler zu schreiben?

Handler komponieren mit Effect, und so werden Services erreicht, aber der Normalfall liest sich wie gewöhnlicher async-Code. Du musst nicht das ganze Ökosystem lernen, um eine Query zu schreiben; du brauchst es für Retries, Layer oder strukturierte Nebenläufigkeit — und dort verdient es sich selbst.

Primitives sind die Oberfläche der Runtime.

Öffne das Framework. Schau es dir selbst an.

Jede Primitive auf dieser Seite ist heute im Framework. Klone den Starter, lass `voltro dev` laufen, in zwei Minuten ist es auf dem Bildschirm.