mcpField
R
rivetkit

rivetkit

github.com/pedronauck開発64665 回インストール1 週間前に更新
リポジトリ

rivetkit について

Rivet Actors で長時間稼働するステートフルなプロセスを構築するバックエンド開発者向けのスキルです。永続化、リアルタイム通信、ワークフロー、Actor のデバッグを扱います。

主な機能

  1. 01長時間稼働するステートフル Actor の構築
  2. 02state、KV、SQLite によるデータ永続化
  3. 03WebSocket によるリアルタイムイベント配信
  4. 04キューによる永続メッセージ処理
  5. 05Inspector による実行中 Actor の調査

活用シーン

  1. 01AI エージェントの複数段階のツール処理と会話メモリーの管理
  2. 02マルチプレイヤーや共同作業アプリ、リアルタイムチャットの構築
  3. 03バックグラウンドジョブと永続キューのオーケストレーション
  4. 04Actor の状態、接続、実行履歴の調査

インストール

コマンドをコピーしてターミナルで実行します。

$npx skills add https://github.com/pedronauck/skills --skill rivetkit

インストール後、Claude Code で/rivetkitを入力して読み込まれたか確認します。

同梱ファイル

SKILL.md と一緒にインストールされる参考資料やスクリプト。

index.json
openapi.json
reference/actors/
access-control.md
actions.md
ai-and-user-generated-actors.md
appearance.md
authentication.md
communicating-between-actors.md
connections.md
debugging.md
design-patterns.md
destroy.md
ephemeral-variables.md
errors.md
events.md
fetch-and-websocket-handler.md
helper-types.md
http-api.md
input.md
keys.md
kv.md
lifecycle.md
limits.md
metadata.md
postgres.md
queues.md
reference/actors/quickstart/
backend.md
cloudflare-workers.md
next-js.md
react.md

詳細

Rivet Actors

Rivet のアクターランタイム上で、高性能かつ長時間稼働するインメモリプロセスを構築します。HTTP エンドポイント、データベース、キューでは、AI エージェント、リアルタイムコラボレーション、ワークフローオーケストレーション、または地理的に分散したデータの要件を満たせなくなった場合に、このスキルを使用してください。

行き詰まったときの対処方法

何かが意図したとおりに動作しない場合や行き詰まった場合は、Rivet Discord に参加するか、Rivet GitHub に課題を報告して、問題を報告しサポートを受けるようユーザーに案内してください。

以下を含む、生成済みのレポートをユーザーに提供してください。

  • 症状
    • ローカル開発環境、デプロイ環境、またはその両方で発生しているか
    • 表示されているエラー
    • 関連するソースコード
  • 解決するために試したこと
  • 環境
    • RivetKit のバージョン
    • ランタイム(Node、Bun など)とそのバージョン
    • 該当する場合、使用しているプロバイダー(Vercel、Railway など)
    • 該当する場合、使用している HTTP ルーター(Hono、Express、Elysia など)

アクターのデバッグ

インスペクター HTTP API を使用して、実行中のアクターを調べます。これらのエンドポイントには、ゲートウェイ経由で /gateway/{actor_id}/inspector/* からアクセスできます。主なエンドポイントは次のとおりです。

  • GET /inspector/summary - アクターの完全なスナップショット(状態、接続、RPC、キュー)
  • GET /inspector/state / PATCH /inspector/state - アクターの状態の読み取り/書き込み
  • GET /inspector/connections - アクティブな接続
  • GET /inspector/rpcs - 利用可能なアクション
  • POST /inspector/action/{name} - {"args": [...]} を指定してアクションを実行
  • GET /inspector/queue?limit=50 - キューの状態
  • GET /inspector/traces?startMs=0&endMs=...&limit=1000 - トレーススパン(OTLP JSON)
  • GET /inspector/workflow-history - ワークフローの履歴と状態

ローカル開発環境では、認証トークンは不要です。本番環境では、Authorization: Bearer <RIVET_INSPECTOR_TOKEN> を指定してください。詳しくはデバッグのドキュメントをご覧ください。

出典の引用

Rivet のドキュメントから情報を提供する際は、ユーザーが詳細を確認できるよう、正式な URL を引用してください。各リファレンスファイルのヘッダーメタデータには、正式な URL が記載されています。

引用方法:

  • 重要な概念にはインラインリンクを使用します。「アクターキーを使うと、インスタンスを一意に識別できます。」
  • 複雑なトピックの説明の後に「詳しくはこちら」のリンクを追加します

正式な URL の確認方法:

以下のリファレンスマップからリファレンスファイルに移動できます。各ファイルのヘッダーには、次の情報が含まれています。

> Canonical URL: https://rivet.dev/docs/actors/actions

引用時にはリファレンスファイルのパスではなく、その正式な URL を使用してください。

例:

  • アクション → https://rivet.dev/docs/actors/actions
  • React クライアント → https://rivet.dev/docs/clients/react
  • Kubernetes でのセルフホスティング → https://rivet.dev/docs/self-hosting/kubernetes

最初の手順

  1. RivetKit をインストールします(最新:2.1.0-rc.2)
    npm install [email protected]
    
  2. setup({ use: { /* actors */ } }) を使ってレジストリを定義します。
  3. registry.serve()、registry.handler()(サーバーレス)、または registry.startRunner()(ランナーモード)を公開します。ランナーモードを使う特別な理由がユーザーにない限り、サーバーレスモードを推奨します。
  4. デプロイ前に、/api/rivet/metadata が 200 を返すことを確認します。
  5. Rivet Cloud またはセルフホスト可能なエンジンを設定します
  6. クライアントを統合します(JavaScript、React、Swift については以下のクライアントガイドを参照してください)
  7. デプロイするかどうかをユーザーに確認します。デプロイする場合は、「Rivet バックエンドのデプロイ」に進みます。

詳しくは、ユーザーのプロジェクトに該当するクイックスタートガイドをお読みください。

エラー処理ポリシー

  • デフォルトでは、フェイルファスト動作を優先してください。
  • 実際の復旧処理、クリーンアップの境界、または実行可能なコンテキストの追加に必要な場合を除き、try/catch は避けてください。
  • エラーを決して握りつぶさないでください。catch を追加する場合は、少なくともログに記録するなど、明示的にエラーを処理する必要があります。
  • 復旧できない場合は、コンテキストを記録してから再スローしてください。

State と Vars:永続化のルール

c.vars は一時的なものです。 c.vars のデータは、再起動、クラッシュ、アップグレード、スリープ/復帰のたびに失われます。c.vars は、シリアライズできないオブジェクト(物理エンジン、WebSocket の参照、イベントエミッター、キャッシュなど)や、本当に一時的な実行時データ(切断後は不要な現在の入力方向など)にのみ使用してください。

永続ストレージの選択肢。 再起動後も保持する必要があるデータは、c.vars ではなく、以下のいずれかに保存してください。

  • c.state — 小規模で上限のあるデータセット向けの CBOR シリアライズ可能なデータです。設定、カウンター、小規模なプレイヤーリスト、フェーズフラグなどに最適です。128 KB 未満に抑えてください。上限がないデータや増加し続けるデータ(チャットログ、イベント履歴、無制限に増えるスポーン済みエンティティのリストなど)は保存しないでください。State は、永続化のサイクルごとに単一の blob として読み書きされます。
  • c.kv — 上限のないデータ向けのキー・バリューストアです。c.state は内部でこれを使用しています。バイナリ値に対応しています。ユーザーのインベントリ、ワールドチャンク、ファイル blob、時間とともに増加する可能性のあるコレクションなど、大きなデータやサイズが変動するデータに使用してください。キーはアクターインスタンスごとにスコープされます。
  • c.db — 構造化データや複雑なデータ向けの SQLite データベースです。クエリ、インデックス、結合、集計、リレーショナルモデリングが必要な場合に使用してください。リーダーボード、マッチ履歴、プレイヤープールなど、SQL の活用が有効なデータに最適です。

よくある間違い: 意味のあるゲーム/アプリケーションデータを永続化せずに c.vars に保存することです。たとえば、ユーザーが物理シミュレーション内にオブジェクトをスポーンできる場合、物理エンジンのハンドル(シリアライズ不可)は c.vars に保存するとしても、スポーン定義(位置、サイズ、種類)は c.state(上限がない場合は c.kv)に永続化する必要があります。再起動時には、run() で永続化されたデータから実行時オブジェクトを再作成してください。

Rivet バックエンドのデプロイ

特に指定がない限り、ユーザーは Rivet Cloud にデプロイするものとします。ユーザーがセルフホストする場合は、以下のセルフホストガイドを参照してください。

  1. ローカル開発環境で Rivet Actors が動作することを確認します
  2. デプロイ先のプロバイダーを選ぶようユーザーに促します(Vercel、Railway など、プロバイダーの一覧は Connect を参照してください)
  3. 選択されたプロバイダーのデプロイガイドに従います。手動での操作が必要な場合は、その都度ユーザーに指示してください。

API リファレンス

RivetKit の OpenAPI 仕様は、スキルディレクトリ内の openapi.json で確認できます。このファイルには、アクターを管理するためのすべての HTTP エンドポイントが記載されています。

その他のメモ

  • Rivet のドメインは rivet.dev であり、rivet.gg ではありません

特長

  • 長時間稼働するステートフルなコンピューティング:各コンピューティング単位は、リクエスト間で情報を保持する小さなサーバーのようなものです。データベースからデータを再取得したり、タイムアウトを心配したりする必要はありません。メモリがありタイムアウトのない AWS Lambda のようなものです。
  • 超高速な読み書き:State はコンピューティングと同じマシンに保存されるため、読み書きは非常に高速です。データベースへの往復やレイテンシーの急増はありません。State は長期保存のために Rivet に永続化されるので、サーバーの再起動後も保持されます。
  • リアルタイム:WebSocket を使って State を更新し、変更をリアルタイムでブロードキャストできます。外部の pub/sub システムもポーリングも不要で、低レイテンシーのイベント機能が組み込まれています。
  • 無限にスケーラブル:アクターをゼロから数百万の同時接続まで自動的にスケールできます。即時スケーリングとコールドスタートなしで、使用した分だけ料金が発生します。
  • 耐障害性:エラー処理と復旧機能が組み込まれています。アクターは障害発生時に自動で再起動し、State の整合性を保ちながら処理を継続します。

Rivet Actors の使用場面

  • AI エージェントとサンドボックス:複数ステップのツールチェーン、会話メモリ、サンドボックスのオーケストレーション。
  • マルチプレイヤーアプリや共同作業アプリ:CRDT ドキュメント、共有カーソル、リアルタイムダッシュボード、チャット。
  • ワークフロー自動化:バックグラウンドジョブ、cron、レートリミッター、永続キュー、バックプレッシャー制御。
  • データ集約型バックエンド:地理的に分散したデータベースやテナントごとのデータベース、インメモリキャッシュ、シャーディングされた SQL。
  • ネットワーク処理:WebSocket サーバー、カスタムプロトコル、ローカルファースト同期、エッジファンアウト。

最小プロジェクト

バックエンド

actors.ts

import { actor, event, setup } from "rivetkit";

const counter = actor({
  state: { count: 0 },
  events: {
    count: event<number>(),
  },
  actions: {
    increment: (c, amount: number) => {
      c.state.count += amount;
      c.broadcast("count", c.state.count);
      return c.state.count;
    },
  },
});

export const registry = setup({
  use: { counter },
});

server.ts

該当する場合は、ユーザーの既存のサーバーと統合してください。それ以外の場合は、Hono をデフォルトで使用してください。

フレームワークなし
import { registry } from "./actors";

export default registry.serve();
Hono
import { Hono } from "hono";
import { registry } from "./actors";

const app = new Hono();
app.all("/api/rivet/*", (c) => registry.handler(c.req.raw));

export default app;
Elysia
import { Elysia } from "elysia";
import { registry } from "./actors";

const app = new Elysia()
	.all("/api/rivet/*", (c) => registry.handler(c.request));

export default app;
クライアントのドキュメント

アプリに合ったクライアント SDK を使用してください。

アクター早見表

インメモリ状態

再起動、クラッシュ、デプロイ後も保持される永続データです。状態は Rivet Cloud または Rivet セルフホスト版に永続化されるため、現在のプロセスがクラッシュまたは終了しても保持されます。

静的な初期状態
import { actor } from "rivetkit";

const counter = actor({
  state: { count: 0 },
  actions: {
    increment: (c) => c.state.count += 1,
  },
});
動的な初期状態
import { actor } from "rivetkit";

interface CounterState {
  count: number;
}

const counter = actor({
  state: { count: 0 } as CounterState,
  createState: (c, input: { start?: number }): CounterState => ({
    count: input.start ?? 0,
  }),
  actions: {
    increment: (c) => c.state.count += 1,
  },
});

ドキュメント

キー

キーはアクターインスタンスを一意に識別します。階層的なアドレス指定には、複合キー(配列)を使用してください。

import { actor, setup } from "rivetkit";
import { createClient } from "rivetkit/client";

const chatRoom = actor({
  state: { messages: [] as string[] },
  actions: {
    getRoomInfo: (c) => ({ org: c.key[0], room: c.key[1] }),
  },
});

const registry = setup({ use: { chatRoom } });
const client = createClient<typeof registry>();

// Compound key: [org, room]
client.chatRoom.getOrCreate(["org-acme", "general"]);

// Access key inside actor via c.key

userId にユーザーデータが含まれる場合、"org:${userId}" のような文字列補間でキーを作成しないでください。キーインジェクション攻撃を防ぐため、代わりに配列を使用してください。

ドキュメント

入力

アクターの作成時に初期化データを渡します。

import { actor, setup } from "rivetkit";
import { createClient } from "rivetkit/client";

const game = actor({
  createState: (c, input: { mode: string }) => ({ mode: input.mode }),
  actions: {},
});

const registry = setup({ use: { game } });
const client = createClient<typeof registry>();

// Client usage
const gameHandle = client.game.getOrCreate(["game-1"], {
  createWithInput: { mode: "ranked" }
});

ドキュメント

一時変数

再起動後には保持されない一時データです。シリアライズできないオブジェクト(イベントエミッター、接続など)に使用します。

静的な初期変数
import { actor } from "rivetkit";

const counter = actor({
  state: { count: 0 },
  vars: { lastAccess: 0 },
  actions: {
    increment: (c) => {
      c.vars.lastAccess = Date.now();
      return c.state.count += 1;
    },
  },
});
動的な初期変数
import { actor } from "rivetkit";

const counter = actor({
  state: { count: 0 },
  createVars: () => ({
    emitter: new EventTarget(),
  }),
  actions: {
    increment: (c) => {
      c.vars.emitter.dispatchEvent(new Event("change"));
      return c.state.count += 1;
    },
  },
});

ドキュメント

アクション

アクションは、クライアントや他のアクターがアクターと通信するための主要な方法です。

import { actor } from "rivetkit";

const counter = actor({
  state: { count: 0 },
  actions: {
    increment: (c, amount: number) => (c.state.count += amount),
    getCount: (c) => c.state.count,
  },
});

ドキュメント

イベントとブロードキャスト

イベントを使用すると、アクターから接続中のクライアントへリアルタイムに通信できます。

import { actor, event } from "rivetkit";

const chatRoom = actor({
  state: { messages: [] as string[] },
  events: {
    newMessage: event<{ text: string }>(),
  },
  actions: {
    sendMessage: (c, text: string) => {
      // Broadcast to ALL connected clients
      c.broadcast("newMessage", { text });
    },
  },
});

ドキュメント

接続

現在の接続には c.conn、接続中のすべてのクライアントには c.conns でアクセスできます。アクションを呼び出しているユーザーを安全に識別するには、c.conn.id または c.conn.state を使用してください。接続状態は connState または createConnState を使って初期化します。これらには、クライアントが接続時に渡したパラメーターが渡されます。

静的な初期接続状態
import { actor } from "rivetkit";

const chatRoom = actor({
  state: {},
  connState: { visitorId: 0 },
  onConnect: (c, conn) => {
    conn.state.visitorId = Math.random();
  },
  actions: {
    whoAmI: (c) => c.conn.state.visitorId,
  },
});
動的な初期接続状態
import { actor } from "rivetkit";

const chatRoom = actor({
  state: {},
  // params passed from client
  createConnState: (c, params: { userId: string }) => ({
    userId: params.userId,
  }),
  actions: {
    // Access current connection's state and params
    whoAmI: (c) => ({
      state: c.conn.state,
      params: c.conn.params,
    }),
    // Iterate all connections with c.conns
    notifyOthers: (c, text: string) => {
      for (const conn of c.conns.values()) {
        if (conn !== c.conn) conn.send("notification", { text });
      }
    },
  },
});

ドキュメント

キュー

run ループ内で永続メッセージを順番に処理するには、キューを使用してください。

import { actor, queue } from "rivetkit";

const counter = actor({
  state: { value: 0 },
  queues: {
    increment: queue<{ amount: number }>(),
  },
  run: async (c) => {
    for await (const message of c.queue.iter()) {
      c.state.value += message.body.amount;
    }
  },
});

ドキュメント

ワークフロー

run のロジックで永続化され、再生可能な複数ステップの実行が必要な場合は、ワークフローを使用してください。

import { actor, queue } from "rivetkit";
import { workflow } from "rivetkit/workflow";

const worker = actor({
  state: { processed: 0 },
  queues: {
    tasks: queue<{ url: string }>(),
  },
  run: workflow(async (ctx) => {
    await ctx.loop("task-loop", async (loopCtx) => {
        const message = await loopCtx.queue.next("wait-task");

        await loopCtx.step("process-task", async () => {
          await processTask(message.body.url);
          loopCtx.state.processed += 1;
        });

      });
  }),
});

async function processTask(url: string): Promise<void> {
  const res = await fetch(url, { method: "POST" });
  if (!res.ok) throw new Error(`Task failed: ${res.status}`);
}

ドキュメント

アクター間通信

アクターは c.client() を使って他のアクターを呼び出せます。

import { actor, setup } from "rivetkit";

const inventory = actor({
  state: { stock: 100 },
  actions: {
    reserve: (c, amount: number) => { c.state.stock -= amount; }
  }
});

const order = actor({
  state: {},
  actions: {
    process: async (c) => {
      const client = c.client<typeof registry>();
      await client.inventory.getOrCreate(["main"]).reserve(1);
    },
  },
});

const registry = setup({ use: { inventory, order } });

ドキュメント

スケジューリング

遅延後または特定の時刻に実行するアクションをスケジュールします。スケジュールは、再起動、アップグレード、クラッシュ後も保持されます。

import { actor, event } from "rivetkit";

const reminder = actor({
  state: { message: "" },
  events: {
    reminder: event<{ message: string }>(),
  },
  actions: {
    // Schedule action to run after delay (ms)
    setReminder: (c, message: string, delayMs: number) => {
      c.state.message = message;
      c.schedule.after(delayMs, "sendReminder");
    },
    // Schedule action to run at specific timestamp
    setReminderAt: (c, message: string, timestamp: number) => {
      c.state.message = message;
      c.schedule.at(timestamp, "sendReminder");
    },
    sendReminder: (c) => {
      c.broadcast("reminder", { message: c.state.message });
    },
  },
});

ドキュメント

アクターの破棄

c.destroy() を使って、アクターとその状態を完全に削除します。

import { actor } from "rivetkit";

const userAccount = actor({
  state: { email: "", name: "" },
  onDestroy: (c) => {
    console.log(`Account ${c.state.email} deleted`);
  },
  actions: {
    deleteAccount: (c) => {
      c.destroy();
    },
  },
});

ドキュメント

ライフサイクルフック

アクターは、初期化、バックグラウンド処理、接続、ネットワーク、状態変更のためのフックをサポートしています。長時間実行されるバックグラウンドループには run を使用し、c.aborted または c.abortSignal を使ってシャットダウン時に正常に終了してください。

import { actor, event, queue } from "rivetkit";

interface RoomState {
  users: Record<string, boolean>;
  name?: string;
}

interface RoomInput {
  roomName: string;
}

interface ConnState {
  userId: string;
  joinedAt: number;
}

const chatRoom = actor({
  state: { users: {} } as RoomState,
  vars: { startTime: 0 },
  connState: { userId: "", joinedAt: 0 } as ConnState,
  events: {
    stateChanged: event<RoomState>(),
  },
  queues: {
    work: queue<{ task: string }>(),
  },

  // State & vars initialization
  createState: (c, input: RoomInput): RoomState => ({ users: {}, name: input.roomName }),
  createVars: () => ({ startTime: Date.now() }),

  // Actor lifecycle
  onCreate: (c) => console.log("created", c.key),
  onDestroy: (c) => console.log("destroyed"),
  onWake: (c) => console.log("actor started"),
  onSleep: (c) => console.log("actor sleeping"),
  run: async (c) => {
    for await (const message of c.queue.iter()) {
      console.log("processing", message.body.task);
    }
  },
  onStateChange: (c, newState) => c.broadcast("stateChanged", newState),

  // Connection lifecycle
  createConnState: (c, params): ConnState => ({ userId: (params as { userId: string }).userId, joinedAt: Date.now() }),
  onBeforeConnect: (c, params) => { /* validate auth */ },
  onConnect: (c, conn) => console.log("connected:", conn.state.userId),
  onDisconnect: (c, conn) => console.log("disconnected:", conn.state.userId),

  // Networking
  onRequest: (c, req) => new Response(JSON.stringify(c.state)),
  onWebSocket: (c, socket) => socket.addEventListener("message", console.log),

  // Response transformation
  onBeforeActionResponse: <Out>(c: unknown, name: string, args: unknown[], output: Out): Out => output,

  actions: {},
});

ドキュメント

コンテキスト型

アクター定義の外部にヘルパー関数を記述する場合は、*ContextOf<typeof myActor> を使用して適切なコンテキスト型を抽出してください。独自のコンテキストインターフェースを手動で定義せず、必ずアクター定義から導出してください。

import { actor, ActionContextOf } from "rivetkit";

const gameRoom = actor({
  state: { players: [] as string[], score: 0 },
  actions: {
    addPlayer: (c, playerId: string) => {
      validatePlayer(c, playerId);
      c.state.players.push(playerId);
    },
  },
});

// Good: derive context type from actor definition
function validatePlayer(c: ActionContextOf<typeof gameRoom>, playerId: string) {
  if (c.state.players.includes(playerId)) {
    throw new Error("Player already in room");
  }
}

// Bad: don't manually define context types like this
// type MyContext = { state: { players: string[] }; ... };

ドキュメント

エラー

クライアントに安全に返されるエラーをスローするには、UserError を使用してください。構造化データを含めるには、metadata を渡します。セキュリティ上、その他のエラーは汎用的な「内部エラー」に変換されます。

アクター
import { actor, UserError } from "rivetkit";

const user = actor({
  state: { username: "" },
  actions: {
    updateUsername: (c, username: string) => {
      if (username.length < 3) {
        throw new UserError("Username too short", {
          code: "username_too_short",
          metadata: { minLength: 3, actual: username.length },
        });
      }
      c.state.username = username;
    },
  },
});
クライアント
import { actor, setup } from "rivetkit";
import { createClient, ActorError } from "rivetkit/client";

const user = actor({
  state: { username: "" },
  actions: { updateUsername: (c, username: string) => { c.state.username = username; } }
});

const registry = setup({ use: { user } });
const client = createClient<typeof registry>();

try {
  await client.user.getOrCreate([]).updateUsername("ab");
} catch (error) {
  if (error instanceof ActorError) {
    console.log(error.code);     // "username_too_short"
    console.log(error.metadata); // { minLength: 3, actual: 2 }
  }
}

ドキュメント

低レベル HTTP・WebSocket ハンドラー

カスタムプロトコルを扱う場合や、HTTP の Request/Response、または WebSocket 接続への直接アクセスを必要とするライブラリを統合する場合は、onRequest と onWebSocket を使用します。

HTTP ハンドラーのドキュメント · WebSocket ハンドラーのドキュメント

アイコンと名前

表示名とアイコンを使って、UI でのアクターの表示をカスタマイズできます。ダッシュボードでアクターを見分けやすくするため、常に名前とアイコンを設定することをおすすめします。

import { actor } from "rivetkit";

const chatRoom = actor({
  options: {
    name: "Chat Room",
    icon: "💬",  // or FontAwesome: "comments", "chart-line", etc.
  },
  // ...
});

ドキュメント

クライアントのドキュメント

クライアントのガイドは、こちらをご覧ください。

よく使われるパターン

アクターは、分離された状態とメッセージパッシングによって自然にスケールします。次のパターンを使って、アプリケーションを構成してください。

ドキュメント

エンティティごとのアクター

ユーザー、ドキュメント、ルームごとにアクターを作成します。複合キーを使ってエンティティをスコープします。

import { createClient } from "rivetkit/client";
import type { registry } from "./actors";

const client = createClient<typeof registry>();

// Single key: one actor per user
client.user.getOrCreate(["user-123"]);

// Compound key: document scoped to an organization
client.document.getOrCreate(["org-acme", "doc-456"]);
import { actor, setup } from "rivetkit";

export const user = actor({
  state: { name: "" },
  actions: {},
});

export const document = actor({
  state: { content: "" },
  actions: {},
});

export const registry = setup({ use: { user, document } });
コーディネーターアクターとデータアクター

データアクターは、チャットルーム、ゲームセッション、ユーザーデータなどのコアロジックを処理します。コーディネーターアクターは、データアクターのコレクションを追跡・管理します。インデックスのようなものと考えてください。

import { actor, setup } from "rivetkit";

// Coordinator: tracks chat rooms within an organization
export const chatRoomList = actor({
  state: { rooms: [] as string[] },
  actions: {
    addRoom: async (c, name: string) => {
      // Create the chat room actor
      const client = c.client<typeof registry>();
      await client.chatRoom.create([c.key[0], name]);
      c.state.rooms.push(name);
    },
    listRooms: (c) => c.state.rooms,
  },
});

// Data actor: handles a single chat room
export const chatRoom = actor({
  state: { messages: [] as string[] },
  actions: {
    send: (c, msg: string) => { c.state.messages.push(msg); },
  },
});

export const registry = setup({ use: { chatRoomList, chatRoom } });
import { createClient } from "rivetkit/client";
import type { registry } from "./actors";

const client = createClient<typeof registry>();

// Coordinator per org
const coordinator = client.chatRoomList.getOrCreate(["org-acme"]);
await coordinator.addRoom("general");
await coordinator.addRoom("random");

// Access chat rooms created by coordinator
client.chatRoom.get(["org-acme", "general"]);
実行ループ

アクター内で継続的なバックグラウンド処理を行うには、run ループを使用します。キューメッセージを順番に処理したり、一定間隔でロジックを実行したり、AI の応答をストリーミングしたり、長時間実行されるタスクを調整したりできます。

import { actor, queue, setup } from "rivetkit";

const counterWorker = actor({
  state: { value: 0 },
  queues: {
    mutate: queue<{ delta: number }>(),
  },
  run: async (c) => {
    for await (const message of c.queue.iter()) {
      c.state.value += message.body.delta;
    }
  },
  actions: {
    getValue: (c) => c.state.value,
  },
});

const registry = setup({ use: { counterWorker } });
ワークフローループ

リソースを初期化し、ループ内でコマンドを処理してからクリーンアップする、長期間にわたる永続的なワークフローには、このパターンを使用します。

import { actor, queue, setup } from "rivetkit";
import { Loop, workflow } from "rivetkit/workflow";

type WorkMessage = { amount: number };
type ControlMessage = { type: "stop"; reason: string };

const worker = actor({
  state: {
    phase: "idle" as "idle" | "running" | "stopped",
    processed: 0,
    total: 0,
    stopReason: null as string | null,
  },
  queues: {
    work: queue<WorkMessage>(),
    control: queue<ControlMessage>(),
  },
  run: workflow(async (ctx) => {
    await ctx.step("setup", async () => {
      await fetch("https://api.example.com/workers/init", { method: "POST" });
      ctx.state.phase = "running";
      ctx.state.stopReason = null;
    });

    const stopReason = await ctx.loop("worker-loop", async (loopCtx) => {
        const message = await loopCtx.queue.next("wait-command", {
          names: ["work", "control"],
        });

        if (message.name === "work") {
          await loopCtx.step("apply-work", async () => {
            await fetch("https://api.example.com/workers/process", {
              method: "POST",
              body: JSON.stringify({ amount: message.body.amount }),
            });
            loopCtx.state.processed += 1;
            loopCtx.state.total += message.body.amount;
          });
          return;
        }

        return Loop.break((message.body as ControlMessage).reason);
      });

    await ctx.step("teardown", async () => {
      await fetch("https://api.example.com/workers/shutdown", { method: "POST" });
      ctx.state.phase = "stopped";
      ctx.state.stopReason = stopReason;
    });
  }),
});

const registry = setup({ use: { worker } });

ドキュメント

アクションとキューの違い
  • アクションは永続的ではありません。リアルタイムの読み取り、一時的なデータ、プレイヤー入力のような低遅延の通信に使用します。
  • キューは永続的です。実行ループを通じて変更を順序付け、SQLite やその他のローカル状態での競合状態を回避するために使用します。呼び出し元は、キューに登録された処理の応答を引き続き待つことができます。
認証、セキュリティ、CORS
  • onBeforeConnect または createConnState で認証情報を検証し、エラーをスローして未認証の接続を拒否します。
  • アクション内でユーザーを安全に識別するには c.conn.state を使用し、アクションのパラメーターを信用しないでください。
  • クロスオリジンアクセスでは、onBeforeConnect でリクエストのオリジンを検証します。

認証のドキュメント · CORS のドキュメント

バージョンとアップグレード

新しいコードをデプロイするときは、バージョン番号を設定してください。これにより、Rivet は新しいアクターを最新のランナーにルーティングし、必要に応じて古いアクターをドレインできます。バージョンには、ビルドのタイムスタンプ、git のコミット数、または CI のビルド番号を使用してください。

ドキュメント

アンチパターン
「神」アクターを作らない

すべてのロジックを単一のアクターにまとめないでください。神アクターは、すべての操作を単一のボトルネックを通して直列化し、並列性を損ない、システム全体を一つの単位として障害に陥らせます。エンティティごとに、役割を絞ったアクターに分割してください。

リクエストごとにアクターを作らない

アクターは長期間存続し、リクエストをまたいで状態を保持します。受信リクエストごとに新しいアクターを作成すると、このモデルの中核的な利点が失われ、アクターの作成と破棄にリソースを浪費します。永続的なエンティティにはアクターを、状態を持たない処理には通常の関数を使用してください。

リファレンスマップ

アクター
クライアント
接続
クックブック
一般
Self Hosting

コメント