mcpField
R
rivetkit

rivetkit

github.com/pedronauck开发64665 次安装1 周前更新
代码仓库

关于 rivetkit

rivetkit 是用于构建 Rivet Actors 的技能,面向需要长生命周期、有状态进程的后端开发者。它涵盖持久化状态、实时通信、工作流与 Actor 调试。

主要功能

  1. 01构建长生命周期有状态 Actor
  2. 02使用状态、KV 和 SQLite 持久化
  3. 03通过 WebSocket 广播实时事件
  4. 04使用队列处理持久消息
  5. 05通过 Inspector 检查运行中的 Actor

适用场景

  1. 01为 AI Agent 管理多步工具调用和会话记忆
  2. 02构建多人协作应用或实时聊天
  3. 03编排后台任务和持久队列
  4. 04检查 Actor 状态、连接和执行记录

安装

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

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

遇到问题时的处理方式

如果某些功能未按预期工作,或者你遇到困难,请提示用户加入 Rivet Discord,或在 Rivet GitHub 上提交 issue,以报告问题并获取帮助。

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

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

调试 Actors

使用检查器 HTTP API 检查正在运行的 Actor。这些端点可通过网关访问,路径为 /gateway/{actor_id}/inspector/*。主要端点:

  • GET /inspector/summary - Actor 的完整快照(状态、连接、RPC、队列)
  • GET /inspector/state / PATCH /inspector/state - 读取/写入 Actor 状态
  • 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。

引用方式:

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

查找规范 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()(Runner 模式)。除非用户有特定理由使用 Runner 模式,否则优先使用无服务器模式。
  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 作为单个数据块读取/写入。
  • c.kv — 用于无界数据的键值存储。c.state 的底层就是使用它。支持二进制值。适用于较大或大小不定的数据,例如用户物品栏、世界区块、文件数据块,或任何可能随时间增长的集合。键的作用域限定在 Actor 实例内。
  • c.db — 用于结构化或复杂数据的 SQLite 数据库。当你需要查询、索引、联接、聚合或关系建模时使用。非常适合排行榜、对战历史、玩家池,或任何能从 SQL 中受益的数据。

常见错误: 将有意义的游戏/应用数据存入 c.vars,而不是进行持久化。例如,如果用户可以在物理模拟中生成对象,那么生成定义(位置、大小、类型)必须持久化到 c.state(如果数据无界,则存入 c.kv),即使物理引擎等(不可序列化的)运行时对象存放在 c.vars 中也是如此。重启后,run() 应根据持久化数据重新创建运行时对象。

部署 Rivet 后端

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

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

API 参考

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

其他说明

  • Rivet 的域名是 rivet.dev,而不是 rivet.gg

功能

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

何时使用 Rivet Actors

  • AI Agent 与沙箱:多步骤工具链、对话记忆、沙箱编排。
  • 多人或协作应用: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:

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

文档

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

文档

销毁 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 支持用于初始化、后台处理、连接、网络和状态变更的钩子。使用 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: {},
});

文档

上下文类型

在 Actor 定义外编写辅助函数时,使用 *ContextOf<typeof myActor> 提取正确的上下文类型。不要手动定义自己的上下文接口——始终从 Actor 定义派生。

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 可包含结构化数据。出于安全考虑,其他错误会转换为通用的“内部错误”。

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 处理程序文档 · WebSocket 处理程序文档

图标与名称

通过显示名称和图标自定义 Actor 在 UI 中的显示方式。建议始终为 Actor 提供名称和图标,以便在仪表板中更轻松地区分它们。

import { actor } from "rivetkit";

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

文档

客户端文档

在此查阅完整的客户端指南:

常见模式

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"]);
运行循环

在 Actor 内使用 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 将新 Actor 路由到最新的运行器,并可选择排空旧运行器。版本可使用构建时间戳、git 提交计数或 CI 构建编号。

文档

反模式
切勿构建“全能” Actor

不要将所有逻辑都放在单个 Actor 中。全能 Actor 会让每个操作都经过同一个瓶颈,扼杀并行性,并导致整个系统作为一个整体发生故障。应按实体拆分为职责明确的 Actor。

切勿为每个请求创建一个 Actor

Actor 的生命周期较长,并会在请求之间维护状态。为每个传入请求创建新的 Actor 会舍弃该模型的核心优势,并将资源浪费在 Actor 的创建和销毁上。应使用 Actor 管理持久实体,并使用常规函数处理无状态工作。

参考索引

Actors
客户端
连接
实用指南
通用
Self Hosting

评论

支持 Markdown