Ein Schema.
Von Postgres bis zu React-Props.

Das Versprechen "End-to-End-TypeScript" gibt jedes moderne Framework — und bricht es dann klammheimlich an der Datenbankgrenze. Voltro hält das Versprechen: Tabellen, Mutations, Queries, Fehler, AI-Tool-Inputs, Workflow-Payloads — alles über effect/Schema definiert, alles typisiert bis zum Client, ohne einen Codegen-Schritt, den du auszuführen vergessen kannst.

users.setAge.mutation.ts
TypeScript
// 1. Define the table — types live in the schema.
export const users = table('users', {
  id:    id(),                              // UserId (branded)
  email: text().unique(),                   // string
  age:   integer().nullable(),              // number | null
})

// 2. Define the mutation — input/output schemas.
export const setAge = defineMutation({
  name:   'users.setAge',
  input:  Schema.Struct({ id: UserId, age: Schema.Number }),
  output: Schema.Struct({ id: UserId, age: Schema.Number }),
  error:  AgeOutOfRange,
})

// 3. On the client — fully typed, zero codegen.
const setAgeMutation = useMutation('app', 'users.setAge')
//    ^? MutationBuilder<{ id: UserId; age: number }, ...>

try {
  const { id, age } = await setAgeMutation.mutate({ id, age: 12 })
} catch (e) {
  if (e instanceof AgeOutOfRange) {
    // e.range is typed: { min: number; max: number }
  }
}

Sechs Garantien, die der Compiler für dich durchsetzt.

Effect Schema überall

Eine Schema-Bibliothek — effect/Schema — definiert Tabellen, Mutations, Queries, Workflow-Payloads, RPC-Fehler und AI-Tool-Inputs. Kein tRPC, kein Zod-vs-Yup-vs-Joi-Wildwuchs, keine Schema-Brücken.

Branded TypeIDs

id()-Spalten liefern Branded Types (UserId, OrgId) — nie rohe Strings. Übergibst du eine OrgId an eine Funktion, die eine UserId erwartet, schlägt die Kompilierung fehl. Fremdschlüssel tragen den Brand mit.

Kein Codegen-Schritt

Die RPC-Group schreibt sich bei jedem voltro-dev-Start selbst. Dein Client importiert einfach `useMutation('app', 'users.setAge')` — der Schema-Typ fließt durch. Kein `pnpm codegen`. Kein CI-Fehler, wenn jemand vergisst, es auszuführen.

Typisierte Fehler per Pattern-Matching

Deklariere error: AgeOutOfRange an einer Mutation. Wirf die Klasse auf dem Server. instanceof auf dem Client grenzt auf die typisierten Felder ein. Genauso für Tagged Errors via Effect.catchTag.

Relationstypen fließen durch .with()

database.users.with(...) — mit deklarierten verschachtelten Relationen wie profile und posts.author — liefert User[] mit einer voll typisierten, verschachtelten Form: Profile | null bei jedem, Author bei jedem Post. Beliebige Tiefe.

Auto-optimistische Patches sind typisiert

Die target:-Deklaration an einer Mutation steuert den Cache-Patch-Reducer mit demselben Typ wie die Row-Type der Subscription. Du kannst keine Zeile in falscher Form voranstellen; es kompiliert nicht.

Der Codegen-Schritt, den du vergisst, ist der Bug, den dein Kunde findet.

Der klassische tRPC-/GraphQL-Codegen-Workflow hat eine Schwachstelle: Jemand vergisst, vor dem Push `pnpm codegen` auszuführen. Der Client kompiliert gegen das alte Schema. CI ist grün. Production ist kaputt. Voltros Codegen läuft bei jedem voltro-dev-Start UND bei jeder Dateiänderung — es gibt keinen separaten Schritt, den du verpassen kannst.

Drei Ebenen, auf denen Voltros Typen Bugs verhindern, die deine Tests nicht fangen würden:

  • Branded IDs. Ein Handler mit orgId: string akzeptiert alles, was ein Aufrufer übergibt. Ein Handler mit orgId: OrgId fängt den Bug direkt an der Aufrufstelle, noch bevor der Test überhaupt läuft.
  • Typisierte Fehler. Ein Client, der catch (e: unknown) macht und if (e.message === '...') schreibt, driftet bei jeder Änderung der Fehlermeldung. Pattern-Matching auf eine getaggte Klasse nie.
  • Relation-Eager-Loading. Eine Query, die user.profile.firstName zurückgibt, deren Eager-Spec aber nur user.profile.id lädt, scheitert zur Laufzeit — undefined.firstName. Voltros .with()-Spec typisiert das Ergebnis, sodass der Zugriff geprüft wird.

Typen fließen durch jedes andere Primitiv.

Ö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.