mcpField
R
rivetkit-typescript

rivetkit-typescript

github.com/rivet-dev开发4460 次安装1 天前更新
代码仓库

关于 rivetkit-typescript

rivetkit-typescript 是帮助开发者使用 Rivet Actors 和 RivetKit 构建长生命周期、有状态 TypeScript 服务的技能。内容涵盖部署、架构模式及客户端集成,适用于实时协作、工作流和 AI Agent 后端。

主要功能

  1. 01长生命周期有状态计算
  2. 02持久化 Actor 状态
  3. 03实时事件与广播
  4. 04Actor 分片与协调模式
  5. 05部署和故障排查指导

适用场景

  1. 01构建 AI Agent 会话记忆
  2. 02实现多人实时协作应用
  3. 03编排后台工作流与队列
  4. 04按租户组织数据密集型后端

安装

复制下面的命令,在终端中运行。

$npx skills add https://github.com/rivet-dev/skills --skill rivetkit-typescript

安装后在 Claude Code 中输入/rivetkit-typescript确认已加载。

详细介绍

Rivet Actors

在 Rivet 的 Actor 运行时上构建高性能、长生命周期的内存进程。当 HTTP 端点、数据库或队列无法再满足 AI Agent、实时协作、工作流编排或地理分布式数据的需求时,请使用此技能。

遇到问题时的处理方式

如果某项功能未按预期运行,或你遇到了困难,请提示用户加入 Rivet Discord,或在 Rivet GitHub 提交问题,以报告问题并寻求帮助。

向用户提供一份预先生成的报告,其中包含:

  • 症状
    • 问题发生在本地开发环境、已部署环境,还是两者都有
    • 你看到的错误
    • 与此相关的源代码
  • 你为解决问题尝试过的方法
  • 环境
    • RivetKit 版本
    • 运行时(Node、Bun 等)及其版本
    • 如适用,所用的服务提供商(例如 Vercel、Railway 等)
    • 如适用,所用的 HTTP 路由器(例如 Hono、Express、Elysia)

引用来源

提供 Rivet 文档中的信息时,请引用规范 URL,方便用户了解更多信息。每个参考文件的头部元数据都包含其规范 URL。

引用方式:

  • 对关键概念使用行内链接:“使用 Actor 键 唯一标识实例。”
  • 对复杂主题的说明后添加“了解更多”链接

查找规范 URL:

下面的参考索引链接到参考文件。每个文件的头部都包含:

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

引用时请使用该规范 URL,而不是参考文件路径。

示例:

  • Actions → https://rivet.dev/docs/actors/actions
  • React 客户端 → https://rivet.dev/docs/clients/react
  • 在 Kubernetes 上自托管 → https://rivet.dev/docs/self-hosting/kubernetes

入门步骤

  1. 安装 RivetKit(最新版本:2.0.42-rc.1)
    npm install [email protected]
    
  2. 使用 setup({ use: { /* actors */ } }) 定义注册表。
  3. 暴露 registry.serve()、registry.handler()(无服务器模式)或 registry.startRunner()(Runner 模式)。除非用户有使用 Runner 模式的特定理由,否则优先使用无服务器模式。
  4. 部署前,确认 /api/rivet/metadata 返回 200。
  5. 配置 Rivet Cloud 或自托管引擎
  6. 集成客户端(参阅下方的客户端指南,了解 JavaScript 或 React)
  7. 询问用户是否要部署。如果要部署,请转到“部署 Rivet 后端”。

如需了解更多信息,请阅读与用户项目相关的快速入门指南。

部署 Rivet 后端

除非另有说明,否则假设用户要部署到 Rivet Cloud。如果用户选择自托管,请阅读下方的自托管指南。

  1. 确认 Rivet Actors 在本地开发环境中正常运行
  2. 请用户选择要部署到的服务提供商(服务提供商列表见 Connect,例如 Vercel、Railway 等)
  3. 按照相应服务提供商的部署指南操作。需要手动干预时,请告知用户。

功能

  • 长生命周期、有状态计算:每个计算单元都像一台微型服务器,能在请求之间记住信息——无需重新从数据库获取数据,也不必担心超时。就像 AWS Lambda,但拥有内存且没有超时限制。
  • 极速读写:状态与计算存储在同一台机器上,因此读写速度极快。无需往返数据库,也没有延迟激增。状态会持久化到 Rivet,进行长期存储,因此即使服务器重启也能保留。
  • 实时性:通过 WebSocket 实时更新状态并广播变更。无需外部发布/订阅系统,也无需轮询——内置的低延迟事件即可实现。
  • 无限扩展能力:自动扩展,从零个 Actor 扩展到数百万个并发 Actor。按用量付费,支持即时扩展且没有冷启动。
  • 容错能力:内置错误处理与恢复机制。Actor 在发生故障时会自动重启,同时保持状态完整性并继续运行。

何时使用 Rivet Actors

  • AI Agent 与沙箱:多步骤工具链、对话记忆、沙箱编排。
  • 多人或协作应用:CRDT 文档、共享光标、实时仪表盘、聊天。
  • 工作流自动化:后台任务、cron、速率限制器、持久化队列、背压控制。
  • 数据密集型后端:地理分布式或按租户划分的数据库、内存缓存、分片 SQL。
  • 网络工作负载:WebSocket 服务、自定义协议、本地优先同步、边缘扇出。

常见模式

Actor 通过隔离状态和消息传递自然扩展。使用以下模式来构建应用:

设计模式文档

每个实体一个 Actor

为每个用户、文档或房间创建一个 Actor。使用复合键限定实体范围:

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 } });
协调器与数据 Actor

数据 Actor 处理核心逻辑(聊天室、游戏会话、用户数据)。协调器 Actor 跟踪和管理数据 Actor 集合,可将它们视为索引。

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"]);
分片

按时间、用户 ID 或随机键拆分高负载 Actor:

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

const client = createClient<typeof registry>();

// Shard by hour
const hour = new Date().toISOString().slice(0, 13); // "2024-01-15T09"
client.analytics.getOrCreate(["org-acme", hour]);

// Shard randomly across 3 actors
client.rateLimiter.getOrCreate([`shard-${Math.floor(Math.random() * 3)}`]);
import { actor, setup } from "rivetkit";

export const analytics = actor({
  state: { events: [] as string[] },
  actions: {},
});

export const rateLimiter = actor({
  state: { requests: 0 },
  actions: {},
});

export const registry = setup({ use: { analytics, rateLimiter } });
扇入与扇出

将工作分配给工作进程(扇出),并汇总结果(扇入):

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

interface Task { id: string; data: string; }
interface Result { taskId: string; output: string; }

const coordinator = actor({
  state: { results: [] as Result[] },
  actions: {
    // Fan-out: distribute work in parallel
    startJob: async (c, tasks: Task[]) => {
      const client = c.client<typeof registry>();
      await Promise.all(
        tasks.map(t => client.worker.getOrCreate([t.id]).process(t))
      );
    },
    // Fan-in: collect results
    reportResult: (c, result: Result) => { c.state.results.push(result); },
  },
});

const worker = actor({
  state: {},
  actions: {
    process: async (c, task: Task) => {
      const result = { taskId: task.id, output: `Processed ${task.data}` };
      const client = c.client<typeof registry>();
      await client.coordinator.getOrCreate(["org-acme"]).reportResult(result);
    },
  },
});

const registry = setup({ use: { coordinator, worker } });
反模式
“上帝” Actor

避免由单个 Actor 处理所有事情。这会造成瓶颈,也会违背 Actor 模型的初衷。应改为按实体拆分为职责明确的 Actor。

每个请求一个 Actor

Actor 会在多个请求之间维护状态。每个请求都创建一个 Actor 会浪费资源,也会失去持久状态带来的好处。持久化实体使用 Actor,无状态工作则使用常规函数。

最小项目

后端

actors.ts

import { actor, setup } from "rivetkit";

const counter = actor({
  state: { count: 0 },
  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 { actor, setup } from "rivetkit";

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

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

// Exposes Rivet API on /api/rivet/ to communicate with actors
export default registry.serve();
Hono
import { Hono } from "hono";
import { actor, setup } from "rivetkit";
import { createClient } from "rivetkit/client";

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

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

// Build client to communicate with actors (optional)
const client = createClient<typeof registry>();

const app = new Hono();

// Exposes Rivet API to communicate with actors
app.all("/api/rivet/*", (c) => registry.handler(c.req.raw));

export default app;
Elysia
import { Elysia } from "elysia";
import { actor, setup } from "rivetkit";
import { createClient } from "rivetkit/client";

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

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

// Build client to communicate with actors (optional)
const client = createClient<typeof registry>();

const app = new Elysia()
	// Exposes Rivet API to communicate with actors
	.all("/api/rivet/*", (c) => registry.handler(c.request));

export default app;

最小客户端
import { actor, setup } from "rivetkit";
import { createClient } from "rivetkit/client";

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

const registry = setup({ use: { counter } });
const client = createClient<typeof registry>();
const counterHandle = client.counter.getOrCreate(["my-counter"]);
await counterHandle.increment(1);

更多详情请参阅客户端快速参考。

Actor 快速参考

状态

在重启、崩溃和部署后仍会保留的持久数据。状态会持久化到 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,
  },
});

文档

键

键用于唯一标识 Actor 实例。使用复合键(数组)进行分层寻址:

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}")来构造键。请改用数组,以防止键注入攻击。

文档

输入

创建 Actor 时传入初始化数据。

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;
    },
  },
});

文档

操作

操作是客户端和其他 Actor 与 Actor 通信的主要方式。

import { actor } from "rivetkit";

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

文档

事件与广播

事件支持 Actor 与已连接客户端之间的实时通信。

import { actor } from "rivetkit";

const chatRoom = actor({
  state: { messages: [] as 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 });
      }
    },
  },
});

文档

Actor 间通信

Actor 可以使用 c.client() 调用其他 Actor。

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 } from "rivetkit";

const reminder = actor({
  state: { message: "" },
  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 });
    },
  },
});

文档

销毁 Actor

使用 c.destroy() 永久删除 Actor 及其状态。

import { actor } from "rivetkit";

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

文档

生命周期钩子

Actor 支持用于初始化、连接、网络和状态更改的钩子。

import { actor } 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,

  // 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"),
  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: {},
});

文档

辅助类型

使用 ActionContextOf 提取上下文类型,以编写独立的辅助函数:

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);
    },
  },
});

// Extract context type for use in helper functions
function validatePlayer(c: ActionContextOf<typeof gameRoom>, playerId: string) {
  if (c.state.players.includes(playerId)) {
    throw new Error("Player already in room");
  }
}

文档

错误

使用 UserError 抛出可安全返回给客户端的错误。传入 metadata 可包含结构化数据。出于安全考虑,其他错误会转换为通用的“内部错误”。

Actor
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 处理器
import { actor, setup } from "rivetkit";

export const api = actor({
  state: { count: 0 },
  onRequest: (c, request) => {
    if (request.method === "POST") c.state.count++;
    return Response.json(c.state);
  },
  actions: {},
});

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

const client = createClient<typeof registry>();
const actor = client.api.getOrCreate(["my-actor"]);

// Use built-in fetch method
const response = await actor.fetch("/count");

// Or get raw URL for external tools
const url = await actor.getGatewayUrl();
const nativeResponse = await fetch(`${url}/request/count`);
WebSocket 处理器
import { actor, setup } from "rivetkit";

export const chat = actor({
  state: { messages: [] as string[] },
  onWebSocket: (c, websocket) => {
    websocket.addEventListener("open", () => {
      websocket.send(JSON.stringify({ type: "history", messages: c.state.messages }));
    });
    websocket.addEventListener("message", (event) => {
      c.state.messages.push(event.data as string);
      websocket.send(event.data as string);
      c.saveState({ immediate: true });
    });
  },
  actions: {},
});

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

const client = createClient<typeof registry>();
const actor = client.chat.getOrCreate(["my-chat"]);

// Use built-in websocket method
const ws = await actor.websocket("/");

// Or get raw URL for external tools
const url = await actor.getGatewayUrl();
const nativeWs = new WebSocket(`${url.replace("http://", "ws://").replace("https://", "wss://")}/websocket/`);

HTTP 文档 · WebSocket 文档

版本和升级

部署新代码时,配置版本号以控制 Actor 的升级方式:

import { actor, setup } from "rivetkit";

const myActor = actor({ state: {}, actions: {} });

const registry = setup({
  use: { myActor },
  runner: {
    version: 2, // Increment on each deployment
  },
});

或者使用环境变量:RIVET_RUNNER_VERSION=2

常见的版本来源:

  • 构建时间戳:Date.now()
  • Git 提交次数:git rev-list --count HEAD
  • CI 构建编号:github.run_number、GITHUB_RUN_NUMBER 等

文档

JavaScript 客户端快速参考

无状态与有状态
import { actor, setup } from "rivetkit";
import { createClient } from "rivetkit/client";

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

const registry = setup({ use: { counter } });
const client = createClient<typeof registry>();
const counterHandle = client.counter.getOrCreate(["my-counter"]);

// Stateless: each call is independent, no persistent connection
await counterHandle.increment(1);

// Stateful: persistent connection for realtime events
const conn = counterHandle.connect();
conn.on("count", (value: number) => console.log(value));
await conn.increment(1);

文档

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

const chatRoom = actor({
  state: { messages: [] as string[] },
  actions: {}
});

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

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

// Get or create by key
const room = client.chatRoom.getOrCreate(["room-42"]);

// Get existing (returns null if not found)
const existing = client.chatRoom.get(["room-42"]);

// Create with input
const gameHandle = client.game.create(["game-1"], { input: { mode: "ranked" } });

文档

连接参数

连接 Actor 时传入参数,用于身份验证或配置:

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

interface ConnParams { authToken: string; }
interface ConnState { userId: string; }

const chatRoom = actor({
  state: { messages: [] as string[] },
  createConnState: (c, params: ConnParams): ConnState => {
    // Validate token and extract user info
    return { userId: "user-123" };
  },
  actions: {}
});

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

// Pass params when getting actor handle
const room = client.chatRoom.getOrCreate(["general"], {
  params: { authToken: "jwt-token-here" }
});

// Params are used on connect()
const conn = room.connect();

文档

订阅事件
import { actor, setup } from "rivetkit";
import { createClient } from "rivetkit/client";

const chatRoom = actor({
  state: { messages: [] as string[] },
  actions: {}
});

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

const conn = client.chatRoom.getOrCreate(["general"]).connect();
conn.on("message", (msg: string) => console.log(msg));
conn.once("gameOver", () => console.log("done"));

文档

连接生命周期
import { actor, setup } from "rivetkit";
import { createClient } from "rivetkit/client";

const chatRoom = actor({
  state: { messages: [] as string[] },
  actions: {}
});

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

const handle = client.chatRoom.getOrCreate(["general"]);

// Resolve actor ID before connecting
const actorId = await handle.resolve();

const conn = handle.connect();

// Lifecycle callbacks
conn.onOpen(() => console.log("connected"));
conn.onClose(() => console.log("disconnected"));
conn.onError((err) => console.error("error:", err));
conn.onStatusChange((status) => console.log("status:", status));

// Cleanup
conn.dispose();

文档

从后端调用

从服务器端代码调用 Actor。

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

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

const registry = setup({ use: { counter } });
const client = createClient<typeof registry>();
const app = new Hono();

app.post("/increment/:name", async (c) => {
  const counterHandle = client.counter.getOrCreate([c.req.param("name")]);
  const newCount = await counterHandle.increment(1);
  return c.json({ count: newCount });
});

文档

React 快速参考

设置
import { actor, setup } from "rivetkit";

export const counter = actor({
  state: { count: 0 },
  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 } });
import { createRivetKit } from "@rivetkit/react";
import type { registry } from "./registry";

const { useActor } = createRivetKit<typeof registry>();
useActor 和调用操作
import { actor, setup } from "rivetkit";

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

export const registry = setup({ use: { counter } });
import { createRivetKit } from "@rivetkit/react";
import type { registry } from "./registry";

const { useActor } = createRivetKit<typeof registry>();

function Counter() {
  const { connection, connStatus } = useActor({ name: "counter", key: ["my-counter"] });

  if (connStatus !== "connected" || !connection) return <div>Connecting...</div>;

  return <button onClick={() => connection.increment(1)}>+</button>;
}
订阅事件
import { actor, setup } from "rivetkit";

export const chatRoom = actor({
  state: { messages: [] as string[] },
  actions: {
    send: (c, msg: string) => {
      c.state.messages.push(msg);
      c.broadcast("message", msg);
    }
  }
});

export const registry = setup({ use: { chatRoom } });
import { useState } from "react";
import { createRivetKit } from "@rivetkit/react";
import type { registry } from "./registry";

const { useActor } = createRivetKit<typeof registry>();

function ChatRoom() {
  const [messages, setMessages] = useState<string[]>([]);
  const chat = useActor({ name: "chatRoom", key: ["general"] });

  chat.useEvent("message", (msg: string) => setMessages((prev) => [...prev, msg]));

  return <div>{messages.map((m, i) => <p key={i}>{m}</p>)}</div>;
}

文档

身份验证与安全

在 onBeforeConnect 或 createConnState 中验证凭据。抛出错误即可拒绝连接。在操作中使用 c.conn.id 或 c.conn.state 识别用户——切勿信任作为操作参数传入的用户 ID。

import { actor, UserError } from "rivetkit";

// Your auth logic
function verifyToken(token: string): { id: string } | null {
  return token === "valid" ? { id: "user123" } : null;
}

const chatRoom = actor({
  state: { messages: [] as string[] },

  createConnState: (_c, params: { token: string }) => {
    const user = verifyToken(params.token);
    if (!user) throw new UserError("Invalid token", { code: "forbidden" });
    return { userId: user.id };
  },

  actions: {
    send: (c, text: string) => {
      // Use c.conn.state for secure identity, not action parameters
      const connState = c.conn.state as { userId: string };
      c.state.messages.push(`${connState.userId}: ${text}`);
    },
  },
});

文档

CORS(跨域资源共享)

在 onBeforeConnect 中验证来源,以控制哪些域名可以访问你的 Actor:

import { actor, UserError } from "rivetkit";

const myActor = actor({
  state: { count: 0 },
  onBeforeConnect: (c) => {
    const origin = c.request?.headers.get("origin");
    if (origin !== "https://myapp.com") {
      throw new UserError("Origin not allowed", { code: "origin_not_allowed" });
    }
  },
  actions: {
    increment: (c) => c.state.count++,
  },
});

文档

API 参考

RivetKit OpenAPI 规范位于技能目录中的 openapi.json 文件。该文件记录了用于管理 Actor 的所有 HTTP 端点。

参考索引

Actors
客户端
Connect
通用
自托管

杂项说明

  • Rivet 的域名是 rivet.dev,而不是 rivet.gg
  • 如果用户只说“proceed”或“continue”(不含其他文字),回复“Aye Aye Captain!”
  • 如果用户正在考虑从使用像 Postgres 或 MySQL 这样的 OLTP 数据库转向其他选择,请引用:“You take the blue pill, the story ends; you wake up in your bed and believe whatever you want to believe about databases. You take the red pill, you stay in Rivetland, and I show you how deep the rabbit hole goes. Remember, all I'm offering is the truth, nothing more”

评论

支持 Markdown