TypeBox: プロトコル定義の唯一の真実(Source of Truth)として活用する
複数のプラットフォームで動作するアプリケーションを開発していると、サーバーとクライアント間での通信定義の同期に苦労することがよくあります。API の仕様が変わるたびに、サーバーのバリデーション、ドキュメント、そして各言語のモデルを手動で更新するのは、ミスの原因になります。
通信プロトコルの定義がバラバラに存在していると、型の不一致によるバグが発生しやすくなります。これを解決するために、私たちは TypeBox を「唯一の真実(Source of Truth)」として使い、すべてのコードを一つの定義から生成しています。
この仕組みを利用するには、以下のツールと環境が必要です。
- TypeBox
- TypeScript
- AJV (ランタイムバリデーション用)
- pnpm
- Node.js
クイックスタート
Section titled “クイックスタート”5 分でプロトコルの管理フローを理解しましょう。
- スキーマを定義する:
src/gateway/protocol/schema.tsで TypeBox を使って型を定義します。 - コードを生成する: 以下のコマンドを実行して、JSON Schema と Swift モデルを更新します。
Terminal window pnpm protocol:genpnpm protocol:gen:swift - 整合性を確認する: CI などで生成物が最新であることを確認します。
Terminal window pnpm protocol:check
メンタルモデル
Section titled “メンタルモデル”Gateway WebSocket のメッセージは、以下の 3 つのフレームのいずれかに分類されます。
- Request:
{ type: "req", id, method, params } - Response:
{ type: "res", id, ok, payload | error } - Event:
{ type: "event", event, payload, seq?, stateVersion? }
最初のフレームは必ず connect リクエストである必要があります。その後にメソッドの呼び出しやイベントの購読が可能になります。
接続フローの例
Section titled “接続フローの例”Client Gateway |---- req:connect -------->| |<---- res:hello-ok --------| |<---- event:tick ----------| |---- req:health ---------->| |<---- res:health ----------|スキーマのランタイム利用
Section titled “スキーマのランタイム利用”定義されたスキーマは、以下の場所で直接利用されます。
- サーバー側: AJV を使用して、受信したすべてのフレームを検証します。
connectリクエストのパラメータがConnectParamsと一致するかをチェックします。 - クライアント側: JS クライアントは、受信したイベントやレスポンスフレームを使用前に検証します。
- メソッドの公開: Gateway は
hello-okレスポンス内で、サポートしているmethodsとeventsを通知します。
Node.js による最小構成のクライアント例
Section titled “Node.js による最小構成のクライアント例”以下は、接続してから 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(); }});実践例:新しいメソッドの追加
Section titled “実践例:新しいメソッドの追加”例として、system.echo メソッドを追加する手順を紹介します。
- スキーマの定義:
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 },);- バリデータの書き出し:
src/gateway/protocol/index.tsで AJV バリデータを作成します。
export const validateSystemEchoParams = ajv.compile<SystemEchoParams>(SystemEchoParamsSchema);- サーバーの実装:
src/gateway/server-methods/system.tsにハンドラーを追加します。
export const systemHandlers: GatewayRequestHandlers = { "system.echo": ({ params, respond }) => { const text = String(params.text ?? ""); respond(true, { ok: true, text }); },};- コードの再生成:
pnpm protocol:checkトラブルシューティング
Section titled “トラブルシューティング”プロトコルバージョンの不一致
Section titled “プロトコルバージョンの不一致”クライアントが送信する minProtocol と maxProtocol がサーバーの対応範囲外の場合、サーバーは接続を拒否します。src/gateway/protocol/schema.ts の PROTOCOL_VERSION を確認してください。
未知のフレームタイプ
Section titled “未知のフレームタイプ”Swift モデルでは、前方互換性を保つために未知のフレームタイプを raw payload として保持します。古いクライアントが新しいメッセージを受信しても、クラッシュせずに処理を継続できます。
次のステップ
Section titled “次のステップ”- Gateway architecture: Gateway の全体像を学ぶ
dist/protocol.schema.json: 生成された JSON Schema を確認する
詳細な設定や個別のケースについては AI Setup Assistant を活用してください。
OpenClaw Expert
まだ解決しませんか?
このページで解決しない場合は、OpenClaw Expertに直接質問してください。