Rivet Actors TypeScript SDK 破坏性变更迁移指南:ctx.db、RivetError 统一错误码与原生 Serverless 端点
【免费下载链接】actorsRivet Actors are the primitive for stateful workloads. Built for AI agents, collaborative apps, and durable execution.项目地址: https://gitcode.com/GitHub_Trending/riv/actors
本文基于 Rivet Actors 仓库CHANGELOG.md的 Unreleased 变更记录,系统梳理 TypeScript SDK(rivetkit)在向原生运行时迁移过程中的破坏性变更与迁移路径,涵盖ctx.sql→ctx.db的数据库访问迁移、RivetError统一错误模型与isRivetErrorCode判断方式、Rust SDK action 上限提升,以及Registry.handler()/Registry.serve()原生 Serverless 端点的恢复。读完本文,你将掌握在当前仓库版本下如何改写既有 Actor 代码、迁移错误捕获逻辑,并正确接入原生运行时提供的/api/rivet/*路由面。
一、变更背景:向原生运行时收敛
Unreleased 变更的总体方向非常明确:rivetkit(TypeScript SDK)不再维护一套独立的 TypeScript 内存运行时,而是统一收敛到 Rust 原生运行时(native runtime / envoy 子进程)之上。与此对应的 SDK 表面调整包括:
- 数据库访问统一从
ctx.sql迁移到ctx.db(rivetkit/db); - 框架/运行时错误不再以 TypeScript 具体类(
instanceof判断)暴露,而是统一为标准化的RivetError+group+code; - 原生 Serverless 运行入口
Registry.handler()与Registry.serve()恢复,路由面固定为/api/rivet前缀; - 一批内部模块(
driver-helpers、topologies/*、dynamic、sandbox/*)确认移除且不再提供替代子路径。
这些改动要求既有 Actor 代码在升级时同步调整导入路径、错误捕获与数据库访问方式。下文按主题逐一说明。
二、Rust SDK 与 TypeScript SDK 的 Action 上限差异
1. 上限变化
- Rust SDK:actor action 集合支持的上限从 16 提升至128个 action 类型。
- TypeScript SDK:actor 定义不受此类限制(“TypeScript actor definitions remain unrestricted”)。
这一差异意味着:当你在 Rust 侧编写包含较多 action 的 actor 时,不再受旧的 16 个类型上限约束;而 TypeScript 侧本就无此约束,因此两侧能力在 action 数量维度上保持一致。Rust SDK 的实现位于仓库的 rivetkit-rust/packages 目录下,其 action 协议相关定义可参考actor-persist/schemas/中的 schema 文件(如 v3.bare 与 v4.bare),其中索引字段的类型设计决定了 action 分派消息的承载能力。
2. 迁移建议
在 Rust SDK 中组织超过 16 个 action 的 actor 时,应继续使用分组明确的命名与稳定的 action 名称;在 TypeScript SDK 中则无需为此做任何特殊处理。
三、数据库访问迁移:从ctx.sql到ctx.db
1. 变更内容
rivetkit不再在 actor context 上暴露ctx.sql。原生的 SQLite 调用应迁移到ctx.db(来自rivetkit/db),Drizzle ORM 的配置保持在rivetkit/db/drizzle子路径。
2. 官方迁移示例
CHANGELOG 给出的迁移示例(完整继承):
import { db } from "rivetkit/db"; const myActor = actor({ db: db(), actions: { listTodos: async (ctx) => { return await ctx.db.execute("SELECT * FROM todos ORDER BY created_at DESC"); }, }, });要点:
- 通过
db()工厂创建数据库 provider,并在actor({ db: db() })中传入; - 在 action 内部通过
ctx.db.execute(sql, params?)执行原生 SQL; - Drizzle 相关设置放在
rivetkit/db/drizzle子路径,不要从rivetkit根导出引入。
3. 源码层面的数据库类型结构
从仓库源码看,ctx.db的能力由 rivetkit-typescript/packages/rivetkit/src/db/mod.ts 导出,它转发出:
db工厂(来自@/common/database/mod);- 一组数据库相关类型,如
DatabaseProvider、AnyDatabaseProvider、InferDatabaseClient、NativeDatabaseProvider、RawAccess、RawDatabaseClient、SqliteDatabase、SqliteQueryResult、SqliteTransactionOptions等。
在 common/database/config.ts 中可以看到:
DatabaseProvider<DB>需要实现createClient(ctx)与可选的onMigrate(client);createClient的结果会以ctx.db形式注入 actor context;RawAccess提供execute(query, ...args)、transaction(callback, options?)、nativeMetrics()与close();SqliteDatabase提供exec、execute、executeBatch、beginTransaction、run、query、nativeMetrics与close;SqliteTransactionOptions支持name(聚合事务性能的操作名)、timeout(死锁安全超时毫秒数),以及实验性的experimental.includeState(原子包含 actor 与可休眠连接状态,仅支持单语句execute,事务进行期间并发修改状态会以actor.state_transaction_conflict失败);- 另有实验性的
SqliteProfilingOptions(SQLite 本地 profiling 开关,如slowOperationThresholdMs、baselineSampleRate、maxPrometheusSeries等)。
这些类型说明ctx.db不仅是简单的 SQL 执行器,还承载了事务、批量执行、原生指标与 profiling 能力,迁移后可以在此基础上做更细粒度的性能观测。
4. 运行时错误行为(测试佐证)
仓库中的 native-runtime-errors.test.ts 验证了原生运行时下数据库未配置时的行为:
- 未配置数据库时访问
ctx.db抛出结构化RivetError,group为"actor"、code为"database_not_configured"、message 为"database is not configured for this actor"; - 未启用 state 时访问
ctx.state抛出group: "actor"、code: "state_not_enabled"的错误; - 未配置 engine client 时调用
ctx.client()抛出group: "native"、code: "client_not_configured"; - 缺少 registry endpoint 时
buildNativeRegistry拒绝启动,group: "native"、code: "endpoint_not_configured"。
可见原生运行时的配置类错误同样遵循统一的RivetError结构化错误模型(见下一节),迁移时建议用isRivetErrorCode对这些错误做精确判断。
四、错误处理标准化:RivetError+group/code+isRivetErrorCode
1. 变更内容
rivetkit不再从rivetkit/actor/errors导出旧的具象错误类(如QueueFull、ActorNotFound、ActionTimedOut)。原生运行时统一以RivetError加group与code表示错误,保证同一个错误形态在 HTTP、WebSocket 与 bridge 边界上保持一致,不再依赖跨运行时的instanceof判断。
2. 迁移示例
try { await actor.someAction(); } catch (e) { if (e instanceof QueueFull) { // old path } if (isRivetErrorCode(e, "queue", "full")) { // new path } }3. 常见类替换对照表
CHANGELOG 给出的完整替换表(全文继承,可复制使用):
| Removed class | Use now |
|---|---|
QueueFull | isRivetErrorCode(e, "queue", "full") |
QueueMessageTooLarge | isRivetErrorCode(e, "queue", "message_too_large") |
QueueMessageInvalid | isRivetErrorCode(e, "queue", "message_invalid") |
QueuePayloadInvalid | isRivetErrorCode(e, "queue", "invalid_payload") |
QueueCompletionPayloadInvalid | isRivetErrorCode(e, "queue", "invalid_completion_payload") |
QueueAlreadyCompleted | isRivetErrorCode(e, "queue", "already_completed") |
ActionTimedOut | isRivetErrorCode(e, "action", "timed_out") |
ActionNotFound | isRivetErrorCode(e, "action", "not_found") |
ActorNotFound | isRivetErrorCode(e, "actor", "not_found") |
ActorStopping | isRivetErrorCode(e, "actor", "stopping") |
ActorAborted | isRivetErrorCode(e, "actor", "aborted") |
IncomingMessageTooLong | isRivetErrorCode(e, "message", "incoming_too_long") |
OutgoingMessageTooLong | isRivetErrorCode(e, "message", "outgoing_too_long") |
InvalidEncoding | isRivetErrorCode(e, "encoding", "invalid") |
InvalidRequest | isRivetErrorCode(e, "request", "invalid") |
InvalidQueryJSON | isRivetErrorCode(e, "request", "invalid_query_json") |
RequestHandlerNotDefined | isRivetErrorCode(e, "handler", "request_not_defined") |
WebSocketHandlerNotDefined | isRivetErrorCode(e, "handler", "websocket_not_defined") |
FeatureNotImplemented | isRivetErrorCode(e, "feature", "not_implemented") |
Unsupported | isRivetErrorCode(e, "feature", "unsupported") |
注意:当你有意抛出面向用户的应用层错误时,仍可继续catchUserError。此次移除只影响原本包装框架/运行时故障的内置具象子类。
4. 源码级实现依据
RivetError与isRivetErrorCode的实现位于 rivetkit-typescript/packages/rivetkit/src/actor/errors.ts,从中可以确认:
RivetError构造函数签名为(group, code, message, options?),实例携带group、code、message、public(是否可安全序列化返回给客户端)、metadata、rayId(用于关联引擎日志的请求标识)、statusCode(默认:public为 400,否则 500)、actor(产生错误的 actor 标识:actorId、generation、可选key)等字段;isRivetErrorCode(error, group, code)的实现即isRivetErrorLike(error) && error.group === group && error.code === code,返回类型收窄为RivetError;isRivetErrorLike校验对象包含字符串类型的group、code、message,可选校验rayId与__type;RivetError同时被导出为ActorError(export { RivetError as ActorError });- bridge 场景下错误以
BRIDGE_RIVET_ERROR_PREFIX("__RIVET_ERROR_JSON__:")前缀 + JSON 序列化传输,encodeBridgeRivetError/decodeBridgeRivetError负责编解码,NativeBridgeErrorPayload会把原生桥接中缺失的rayId(序列化为 null)归一化为undefined——这正是“同一错误形态跨 HTTP、WebSocket、bridge 边界存活”的实现机制; - 此外还内置了一批便捷工厂:
internalError、invalidEncoding、invalidRequest、actorNotFound、actorStopping、actorRestarting(HTTP 503、metadata.retryable: true)、forbiddenError(403)、unsupportedFeature,以及判断 actor 休眠时“aborted”正常退出的isActorAbortedError(匹配group: "actor"、code: "aborted",用于区分真正失败与 park 的runhandler 因休眠而解除阻塞的预期行为)。
5. 保持UserError的使用方式
UserError继承自RivetError,构造时固定group为"user",默认code为"user_error",且public: true。它面向的是应用开发者主动抛出的用户可见错误,不在本次移除范围内,可继续用于业务错误表达。
五、Serverless 运行入口恢复:Registry.handler()与Registry.serve()
1. 变更内容
Registry.handler(request)与Registry.serve()已恢复,面向.agent/specs/serverless-restoration.md描述的原生 Serverless runner 端点。固定路由面为:
/api/rivet /api/rivet/health /api/rivet/metadata /api/rivet/start用户流量仍然经由 Rivet Engine gateway 进入。
2. 源码实现
在 rivetkit-typescript/packages/rivetkit/src/registry/index.ts 中可以看到:
handler(request)处理单个 HTTP 请求,典型用法是与 Hono 等框架组合:
const app = new Hono(); app.all("/api/rivet/*", (c) => registry.handler(c.req.raw)); export default app;serve()返回一个ServerlessHandler,内部fetch转发给handler:
export default registry.serve();handler内部通过isServerlessStartRequest/isServerlessMetadataRequest区分 start / metadata 请求(base path 可通过serverlessBasePath配置,默认/api/rivet);- start 请求前会触发
configureServerlessPool(每个进程只 upsert 一次,带重试以容忍引擎启动中),若池未配置返回 503 与{ group: "guard", code: "service_unavailable" }; - start 请求体超过
serverlessMaxStartPayloadBytes时返回 413 与{ group: "message", code: "incoming_too_long" }。
这些错误响应同样是统一的group/codeJSON 形态,与第四节的结构化错误模型一致。
3. 说明
Registry.handler(request)单独使用时不会安装/api/rivet/health、/api/rivet/metadata等路由处理器(从配置注释可见:“Handlers are NOT installed whenhandler(request)is used alone”),使用方需要自行在框架层接入对应路由;Registry.start()现在只启动原生 envoy 路径;内置的staticDir静态文件服务尚未接入原生引擎子进程,属于后续工作(follow-up)。
六、恢复的入口点与辅助类型
以下入口点已恢复为受支持的公共 API:
rivetkit/test:测试入口,现在会等待原生 envoy 的 metadata 端点就绪(而不是依赖已移除的 TypeScript 内存运行时);rivetkit/inspector与rivetkit/inspector/client:inspector 相关入口;- 根
rivetkit导出恢复零运行时的*ContextOf辅助类型,例如:
type MyActionContext = ActionContextOf<typeof myActor>;PATH_CONNECT、PATH_WEBSOCKET_PREFIX、KV_KEYS、ActorKv、ActorInstance、ActorRouter、createActorRouter、routeWebSocket则保持移除状态。
七、确认永久移除的模块清单(勿继续导入)
以下子路径确认移除且没有仓库内替代品,迁移时应在应用代码中改走公共 API 或自行实现:
| 子路径 | 状态 | 迁移方向 |
|---|---|---|
rivetkit/driver-helpers | 保持移除 | 改用公共的rivetkit、rivetkit/client与 engine-client API,不要导入包内部实现 |
rivetkit/driver-helpers/websocket | 保持移除 | 同上 |
rivetkit/topologies/* | 保持移除 | 该分支已删除 topology helpers;如仍需自定义坐标/分区逻辑,请在应用代码中自行维护 |
rivetkit/dynamic | 永久移除 | 无包内替代,将相关集成移出rivetkit导入 |
rivetkit/sandbox/* | 永久移除 | 同上 |
八、迁移检查清单
升级到当前 Unreleased 版本时,建议按以下顺序排查既有代码:
- 数据库:全局搜索
ctx.sql,全部改为ctx.db(从rivetkit/db导入db),Drizzle 配置保持在rivetkit/db/drizzle; - 错误捕获:搜索
instanceof QueueFull、instanceof ActorNotFound、instanceof ActionTimedOut等具象错误类,对照第五节映射表改用isRivetErrorCode(e, group, code);业务侧主动抛出的UserError保持不变; - Serverless 部署:如使用
Registry.handler(request)/Registry.serve(),确认框架层接入/api/rivet、/api/rivet/health、/api/rivet/metadata、/api/rivet/start路由(handler单独使用时不自动安装后三个处理器); - 入口与类型:
rivetkit/test、rivetkit/inspector、rivetkit/inspector/client可直接使用;需要ActionContextOf<typeof myActor>等类型时从根rivetkit导出获取; - 清理导入:删除对
rivetkit/driver-helpers、rivetkit/topologies/*、rivetkit/dynamic、rivetkit/sandbox/*的一切导入; - Rust SDK:确认 actor action 集合在 128 个类型上限内组织,无需处理 TypeScript 侧的类似限制。
以上所有变更均可对照本仓库源码继续深入:错误模型见 src/actor/errors.ts,数据库类型见 src/common/database/config.ts 与 src/db/mod.ts,Serverless 入口见 src/registry/index.ts,运行时错误行为的回归验证见 tests/native-runtime-errors.test.ts。
【免费下载链接】actorsRivet Actors are the primitive for stateful workloads. Built for AI agents, collaborative apps, and durable execution.项目地址: https://gitcode.com/GitHub_Trending/riv/actors
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考