Rivet Actors TypeScript SDK 破坏性变更迁移指南:ctx.db、RivetError 统一错误码与原生 Serverless 端点
2026/9/18 23:39:42 网站建设 项目流程

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.sqlctx.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.dbrivetkit/db);
  • 框架/运行时错误不再以 TypeScript 具体类(instanceof判断)暴露,而是统一为标准化的RivetError+group+code
  • 原生 Serverless 运行入口Registry.handler()Registry.serve()恢复,路由面固定为/api/rivet前缀;
  • 一批内部模块(driver-helperstopologies/*dynamicsandbox/*)确认移除且不再提供替代子路径。

这些改动要求既有 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.sqlctx.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);
  • 一组数据库相关类型,如DatabaseProviderAnyDatabaseProviderInferDatabaseClientNativeDatabaseProviderRawAccessRawDatabaseClientSqliteDatabaseSqliteQueryResultSqliteTransactionOptions等。

在 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提供execexecuteexecuteBatchbeginTransactionrunquerynativeMetricsclose
  • SqliteTransactionOptions支持name(聚合事务性能的操作名)、timeout(死锁安全超时毫秒数),以及实验性的experimental.includeState(原子包含 actor 与可休眠连接状态,仅支持单语句execute,事务进行期间并发修改状态会以actor.state_transaction_conflict失败);
  • 另有实验性的SqliteProfilingOptions(SQLite 本地 profiling 开关,如slowOperationThresholdMsbaselineSampleRatemaxPrometheusSeries等)。

这些类型说明ctx.db不仅是简单的 SQL 执行器,还承载了事务、批量执行、原生指标与 profiling 能力,迁移后可以在此基础上做更细粒度的性能观测。

4. 运行时错误行为(测试佐证)

仓库中的 native-runtime-errors.test.ts 验证了原生运行时下数据库未配置时的行为:

  • 未配置数据库时访问ctx.db抛出结构化RivetErrorgroup"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导出旧的具象错误类(如QueueFullActorNotFoundActionTimedOut)。原生运行时统一以RivetErrorgroupcode表示错误,保证同一个错误形态在 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 classUse now
QueueFullisRivetErrorCode(e, "queue", "full")
QueueMessageTooLargeisRivetErrorCode(e, "queue", "message_too_large")
QueueMessageInvalidisRivetErrorCode(e, "queue", "message_invalid")
QueuePayloadInvalidisRivetErrorCode(e, "queue", "invalid_payload")
QueueCompletionPayloadInvalidisRivetErrorCode(e, "queue", "invalid_completion_payload")
QueueAlreadyCompletedisRivetErrorCode(e, "queue", "already_completed")
ActionTimedOutisRivetErrorCode(e, "action", "timed_out")
ActionNotFoundisRivetErrorCode(e, "action", "not_found")
ActorNotFoundisRivetErrorCode(e, "actor", "not_found")
ActorStoppingisRivetErrorCode(e, "actor", "stopping")
ActorAbortedisRivetErrorCode(e, "actor", "aborted")
IncomingMessageTooLongisRivetErrorCode(e, "message", "incoming_too_long")
OutgoingMessageTooLongisRivetErrorCode(e, "message", "outgoing_too_long")
InvalidEncodingisRivetErrorCode(e, "encoding", "invalid")
InvalidRequestisRivetErrorCode(e, "request", "invalid")
InvalidQueryJSONisRivetErrorCode(e, "request", "invalid_query_json")
RequestHandlerNotDefinedisRivetErrorCode(e, "handler", "request_not_defined")
WebSocketHandlerNotDefinedisRivetErrorCode(e, "handler", "websocket_not_defined")
FeatureNotImplementedisRivetErrorCode(e, "feature", "not_implemented")
UnsupportedisRivetErrorCode(e, "feature", "unsupported")

注意:当你有意抛出面向用户的应用层错误时,仍可继续catchUserError。此次移除只影响原本包装框架/运行时故障的内置具象子类。

4. 源码级实现依据

RivetErrorisRivetErrorCode的实现位于 rivetkit-typescript/packages/rivetkit/src/actor/errors.ts,从中可以确认:

  • RivetError构造函数签名为(group, code, message, options?),实例携带groupcodemessagepublic(是否可安全序列化返回给客户端)、metadatarayId(用于关联引擎日志的请求标识)、statusCode(默认:public为 400,否则 500)、actor(产生错误的 actor 标识:actorIdgeneration、可选key)等字段;
  • isRivetErrorCode(error, group, code)的实现即isRivetErrorLike(error) && error.group === group && error.code === code,返回类型收窄为RivetError
  • isRivetErrorLike校验对象包含字符串类型的groupcodemessage,可选校验rayId__type
  • RivetError同时被导出为ActorErrorexport { RivetError as ActorError });
  • bridge 场景下错误以BRIDGE_RIVET_ERROR_PREFIX"__RIVET_ERROR_JSON__:")前缀 + JSON 序列化传输,encodeBridgeRivetError/decodeBridgeRivetError负责编解码,NativeBridgeErrorPayload会把原生桥接中缺失的rayId(序列化为 null)归一化为undefined——这正是“同一错误形态跨 HTTP、WebSocket、bridge 边界存活”的实现机制;
  • 此外还内置了一批便捷工厂:internalErrorinvalidEncodinginvalidRequestactorNotFoundactorStoppingactorRestarting(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/inspectorrivetkit/inspector/client:inspector 相关入口;
  • rivetkit导出恢复零运行时*ContextOf辅助类型,例如:
type MyActionContext = ActionContextOf<typeof myActor>;

PATH_CONNECTPATH_WEBSOCKET_PREFIXKV_KEYSActorKvActorInstanceActorRoutercreateActorRouterrouteWebSocket则保持移除状态。

七、确认永久移除的模块清单(勿继续导入)

以下子路径确认移除且没有仓库内替代品,迁移时应在应用代码中改走公共 API 或自行实现:

子路径状态迁移方向
rivetkit/driver-helpers保持移除改用公共的rivetkitrivetkit/client与 engine-client API,不要导入包内部实现
rivetkit/driver-helpers/websocket保持移除同上
rivetkit/topologies/*保持移除该分支已删除 topology helpers;如仍需自定义坐标/分区逻辑,请在应用代码中自行维护
rivetkit/dynamic永久移除无包内替代,将相关集成移出rivetkit导入
rivetkit/sandbox/*永久移除同上

八、迁移检查清单

升级到当前 Unreleased 版本时,建议按以下顺序排查既有代码:

  1. 数据库:全局搜索ctx.sql,全部改为ctx.db(从rivetkit/db导入db),Drizzle 配置保持在rivetkit/db/drizzle
  2. 错误捕获:搜索instanceof QueueFullinstanceof ActorNotFoundinstanceof ActionTimedOut等具象错误类,对照第五节映射表改用isRivetErrorCode(e, group, code);业务侧主动抛出的UserError保持不变;
  3. Serverless 部署:如使用Registry.handler(request)/Registry.serve(),确认框架层接入/api/rivet/api/rivet/health/api/rivet/metadata/api/rivet/start路由(handler单独使用时不自动安装后三个处理器);
  4. 入口与类型rivetkit/testrivetkit/inspectorrivetkit/inspector/client可直接使用;需要ActionContextOf<typeof myActor>等类型时从根rivetkit导出获取;
  5. 清理导入:删除对rivetkit/driver-helpersrivetkit/topologies/*rivetkit/dynamicrivetkit/sandbox/*的一切导入;
  6. 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),仅供参考

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询