Zum Inhalt springen

TypeBox als Protocol Source of Truth

Kennst du das? Du änderst ein Feld in deiner API-Definition, vergisst aber, den Client oder die Dokumentation zu aktualisieren. Plötzlich passen Request und Response nicht mehr zusammen und die Fehlersuche beginnt. Es ist anstrengend, Schemas an mehreren Stellen synchron zu halten, besonders wenn verschiedene Sprachen wie TypeScript und Swift im Spiel sind.

Wir lösen dieses Problem mit TypeBox. Es dient als einzige Source of Truth für unser Gateway WebSocket-Protokoll. Damit steuern wir die Runtime-Validierung, den JSON Schema Export und die Swift Codegen für die macOS App. Einmal definieren, alles andere automatisch generieren.

  • src/gateway/server.ts (enthält die maßgebliche Liste von METHODS und EVENTS)
  • pnpm als Package Manager

Das Gateway nutzt ein Frame-basiertes System für die Kommunikation. Jeder Frame folgt einer klaren Struktur.

Jede Gateway WS Nachricht ist einer von drei Frames:

  • Request: { type: "req", id, method, params }
  • Response: { type: "res", id, ok, payload | error }
  • Event: { type: "event", event, payload, seq?, stateVersion? }

Der erste Frame muss ein connect Request sein. Danach kannst du Methoden aufrufen (z. B. health, send) oder Events abonnieren.

Minimaler Verbindungsablauf:

Client Gateway
|---- req:connect -------->|
|<---- res:hello-ok --------|
|<---- event:tick ----------|
|---- req:health ---------->|
|<---- res:health ----------|

Verwende diese Befehle, um die Schemas zu verwalten:

  • pnpm protocol:gen: Schreibt JSON Schema (draft‑07) nach dist/protocol.schema.json.
  • pnpm protocol:gen:swift: Generiert die Swift Gateway Modelle.
  • pnpm protocol:check: Führt beide Generatoren aus und prüft, ob der Output committet wurde.

Hier ist ein Beispiel für den kleinsten sinnvollen Flow: Connect + Health.

import { WebSocket } from "ws";
const ws = new WebSocket("ws://127.0.0.1:18789");
ws.on("open", () => {
ws.send(
JSON.stringify({
type: "req",
id: "c1",
method: "connect",
params: {
minProtocol: 3,
maxProtocol: 3,
client: {
id: "cli",
displayName: "example",
version: "dev",
platform: "node",
mode: "cli",
},
},
}),
);
});
ws.on("message", (data) => {
const msg = JSON.parse(String(data));
if (msg.type === "res" && msg.id === "c1" && msg.ok) {
ws.send(JSON.stringify({ type: "req", id: "h1", method: "health" }));
}
if (msg.type === "res" && msg.id === "h1") {
console.log("health:", msg.payload);
ws.close();
}
});

Wenn du eine neue Methode wie system.echo hinzufügen möchtest, folge diesen Schritten:

  1. Schema definieren (src/gateway/protocol/schema.ts):
export const SystemEchoParamsSchema = Type.Object(
{ text: NonEmptyString },
{ additionalProperties: false },
);
export const SystemEchoResultSchema = Type.Object(
{ ok: Type.Boolean(), text: NonEmptyString },
{ additionalProperties: false },
);

Füge beide zu ProtocolSchemas hinzu und exportiere die Typen.

  1. Validierung erstellen (src/gateway/protocol/index.ts):
export const validateSystemEchoParams = ajv.compile<SystemEchoParams>(SystemEchoParamsSchema);
  1. Server-Logik implementieren (src/gateway/server-methods/system.ts):
export const systemHandlers: GatewayRequestHandlers = {
"system.echo": ({ params, respond }) => {
const text = String(params.text ?? "");
respond(true, { ok: true, text });
},
};

Registriere den Handler und füge "system.echo" zu METHODS in src/gateway/server.ts hinzu.

  1. Regenerieren:
Terminal-Fenster
pnpm protocol:check
  • Protokoll-Mismatch: Der Server prüft minProtocol und maxProtocol. Wenn diese nicht mit der PROTOCOL_VERSION des Servers übereinstimmen, wird die Verbindung abgelehnt.
  • Unbekannte Frames: Die Swift-Modelle behalten unbekannte Frame-Typen als Raw-Payloads bei. Das verhindert Abstürze bei älteren Clients, stellt aber sicher, dass neue Features nicht sofort verarbeitet werden können.

AI Setup Assistant

What’s Next

OpenClaw

OpenClaw Expert

Noch festgefahren?

Wenn diese Seite nicht hilft, frage OpenClaw Expert nach Schritt-fuer-Schritt-Loesungen.