rivetkit-typescript 架构边界与工程规范解读:Rust 核心驱动的 Actors SDK 双运行时设计
发布时间:2026/9/18 1:27:53 锦皓数字建站

rivetkit-typescript 架构边界与工程规范解读Rust 核心驱动的 Actors SDK 双运行时设计【免费下载链接】actorsRivet Actors are the primitive for stateful workloads. Built for AI agents, collaborative apps, and durable execution.项目地址: https://gitcode.com/GitHub_Trending/riv/actors本篇技术指南以仓库中 rivetkit-typescript/AGENTS.md 为骨架系统讲解 rivetkit-typescript SDK 的模块边界、构建流程、运行时抽象、网关协议、KV 限额与生命周期语义。读者可以借此掌握该 SDK 从纯 TypeScript 迁移到 Rust 核心 NAPI/Wasm 绑定后的内部设计约束理解为什么某些代码必须放在特定文件里为什么某些接口要这样调用从而避免踩中构建空包、运行时错位、状态泄漏等深坑。一、构建与产物Inspector UI 的两步嵌入流程文档首先指出一个反直觉的构建依赖Inspector UI 被嵌入到rivetkit/rivetkit-napi与rivetkit/rivetkit-wasm两个原生二进制中嵌入动作由 rivetkit-core 的build.rs完成但 Inspector UI 本身是原生包的下游产物——这形成了一个环。解法是引入一个作用域限定的build:embed任务它会在build:inspector-ui之后重新执行一次原生构建。仓库根执行pnpm -w build等价于turbo build build:embed即可正确完成两步构建。文档特别警告任何只运行rivetkit/rivetkit-napi#build而没有运行build:embed的局部构建都会留下一个内置 bundle 为空的.node文件导致/inspector/ui/*返回ui_asset_not_found404。也就是说构建成功但 Inspector 打不开几乎可以断定是只跑了单步构建。完整的排查上下文记录在 .claude/reference/build-troubleshooting.md 的 Inspector UI embed empty bundle 一节遇到空 bundle 时优先对照该文档处理。从包导出配置看packages/rivetkit/package.json 使用 tsup 将src/inspector/mod.ts、src/inspector/workflow.ts等作为独立入口打包Inspector 相关能力以./inspector、./inspector-tab子路径对外暴露与两步嵌入流程配合保证 UI 资源与原生模块同时就绪。二、运行时演进从纯 TypeScript 到 Rust 核心 NAPI 绑定文档记录了一个关键的历史分界点提交1761e6f0fchore: rivetkit to rust2026-04-16是从全 TypeScript 运行时切换到 Rust 核心 NAPI 绑定的切割点。这条信息的实际价值在于代码考古当需要对比当前行为与原 TypeScript 实现的差异时——例如生命周期顺序lifecycle ordering、持久化布局persistence layout——应该检出1761e6f0f^或更早的提交来对照而不是在当前分支上猜测旧行为。这种核心下移、绑定上浮的架构在源码中随处可见。例如 packages/rivetkit/src/registry/runtime.ts 定义了RuntimeBytes Uint8Array、RuntimeRequestSaveOpts { immediate?, maxWaitMs? }等可移植的运行时类型而 NAPI 专属的Buffer转换则被隔离在NapiCoreRuntime内部确保共享胶水层shared actor glue不依赖 Node 专属类型。三、模块边界与 Tree-Shaking 纪律文档对打包边界提出了几条硬性约束直接关系到产物体积与平台兼容性不要在任何非 workflow 入口导入rivetkit/workflow-engine。它只能由rivetkit/workflow入口引用否则会破坏 tree-shaking把整个工作流引擎拖进所有使用者的包中。对应地package.json 为 workflow 单独声明了./workflow导出与主入口平级隔离。SQLite 运行时只走原生rivetkit/rivetkit-napi路径。不允许重新引入 WebAssembly SQLite 或基于 KV 的 VFS 回退方案import rivetkit/db是显式 opt-in不能从这个入口惰性加载额外 SQLite 运行时。核心驱动必须保持 SQLite 无关SQLite-agnostic任何 SQLite 专属接线都只能放在原生数据库提供方的边界之后。删除rivetkit/*导出前必须先 grepexamples/、website/、frontend/中的自我导入self-imports这些消费方属于本分支支持的公开包面package surface。这四条规则共同回答了同一个问题为什么 SDK 能同时服务 Node、边缘平台与浏览器场景——因为平台相关的重量级依赖都被严格锁在特定导出子路径之后。四、运行时边界用 CoreRuntime.kind 而非 instanceof 选择行为文档规定运行时行为的选择必须依据CoreRuntime.kind而不是对 adapter 类做instanceof判断。NAPI 映射到 native 运行时类型wasm 映射到 wasm 类型。这是一条面向未来的约定——当新增运行时种类时只需要扩展kind枚举而不用在每个调用点追加分支。配套的字节契约约定包括CoreRuntime的 SQL 方法应保持在RuntimeSql*可移植结构体上见 packages/rivetkit/src/registry/runtime.tsNAPI 专属的Buffer转换属于NapiCoreRuntimeRuntimeSqlBindParam的变体必须保持精确RuntimeSqlExecuteResult.route只允许read、write、writeFallback三个值生成适配器输出前必须规范化wasm 绑定对 NAPI 支持的运行时 API 应转发到rivetkit-core不能返回占位结果破坏运行时对等性runtime paritywasm 的registerTask必须转发到核心的ActorContext::register_task(...)而不是waitUntil这样关闭时的 drain 语义才能与 NAPI 一致也不会把公共waitUntil工作混入其中桥接层的 HTTP 错误状态提升status promotion归属rivetkit-coreTS 侧适配器只负责解码编码后的状态字段不维护自己的提升表使用公共sqlite配置选择运行时 SQLite 后端wasm 在未配置 SQLite 时默认走 remote并且必须在运行时构造前拒绝 local 配置。从实现看runtime.ts 还定义了RuntimeStateDeltaPayloadstate 快照 connHibernation连接休眠字节 connHibernationRemoved移除列表、RuntimeInspectorSnapshotstate/connections/queue 各 revision 计数等统一数据结构这些结构同时被 native 与 wasm 两条路径消费正是运行时对等性落地的载体。五、Native SQLite v2页面重建与致命错误处理文档用一整节描述 SQLite v2 VFS 的实现红线对理解该 SDK 的 KV 与数据库可靠性设计至关重要部分读写必须重建完整 4 KiB 页SQLite 即使提交保持页级page-based也可能发出 sub-page 头部 I/O所以 v2 VFS 的xRead/xWrite回调必须能基于部分数据重建完整 4 KiB 页。head_txid与db_size_pages是 VFS 拥有的状态读侧get_pages(...)响应可以刷新max_delta_bytes但只有提交响应以及本地xWrite/xTruncate路径才允许推进或收缩这两个字段——防止读路径意外改变元数据。构建联动修改packages/rivetkit-napi或packages/sqlite-native下的 Rust 代码后必须从packages/rivetkit-napi执行pnpm build:force让原生.node产物重新生成否则测试到的还是旧二进制。测试运行时要求真正驱动 v2 VFS 的sqlite-native测试通过直接SqliteEngine需要 multithread Tokio runtimecurrent_thread只适用于 mock 传输测试否则真实引擎回调可能停滞。致命错误策略任何 sqlite v2 传输或提交错误对当前 VFS 实例都是致命的——标记 dead、通过take_last_kv_error()暴露、依赖 reopen takeover 恢复绝不带着脏页继续向前走致命提交清理集中在flush_dirty_pages与commit_atomic_write回调包装层只负责把 fence 不匹配翻译成 SQLite I/O 返回码。可观测性透传原生 SQLite 层新增 introspection / metrics getter 时必须通过wrapJsNativeDatabase(...)转发否则 actor inspector 指标会静默丢失。六、网关协议GatewayTarget 与 rvt-* 查询参数网关目标是客户端与引擎控制面的核心衔接点。文档规定使用共享的GatewayTarget类型定义于 packages/rivetkit/src/engine-client/driver.ts而不是临时拼string | ActorQuery联合类型export type GatewayTarget { directId: string } | ActorQuery;客户端实现client/actor-query.ts 中的getGatewayTarget(state)负责把ActorResolutionState收敛为GatewayTargetgetForId状态映射为{ directId }其余则保留ActorQuery交给网关在请求/重连时重新解析。这样高层客户端流程可以拓宽目标类型而无需复制查询解析逻辑。6.1 查询式网关 URL 协议基于查询参数的远端网关 URL 使用rvt-*前缀参数路径形状为/gateway/{name}/{path}?rvt-namespace...rvt-method...rvt-key...其中 actor 名是干净的路径段所有路由参数都是带rvt-前缀的普通查询参数。目前已知的rvt-*参数全集如下参数语义rvt-namespace命名空间rvt-method目标方法rvt-runner目标 runnergetOrCreate必需get禁止rvt-keyactor key多段 key 用单个逗号分隔参数如rvt-keytenant,roomrvt-input输入负载rvt-region区域选择rvt-crash-policy崩溃策略rvt-token访问令牌构建与解析一律使用URLSearchParams。buildGatewayUrl()对get()/getOrCreate()保持查询式构造不预解析为 actor ID仅直接 actor ID 目标走/gateway/{actorId}。6.2 网关路径解析的七条铁律在 actor-gateway/gateway.ts或对等实现中解析查询路径时通过任一查询参数以rvt-开头来识别查询路径用URLSearchParams解析查询参数把参数划分为rvt-*参数与 actor 参数两组拒绝裸token语法、未知rvt-*参数、重复的标量rvt-*参数转发给 actor 前从查询串中剥离所有rvt-*参数仅用 actor 参数重建查询串解析完成后在共享的基于路径的 HTTP / WebSocket 网关辅助函数中把查询路径解析为 actor ID再调用proxyRequest/proxyWebSocket解析后复用已有的直接 ID 代理流程并保留剥离rvt-*参数后的剩余原始路径。6.3 客户端侧的配套约束输入大小校验时机在 engine-client/actor-websocket-client.ts 中ClientConfig.maxInputSize必须针对 base64url 编码前的原始 CBOR 字节长度校验让限额与实际序列化负载对齐而非编码膨胀后的 URL 长度。不要缓存解析结果ClientRaw.get()/ClientRaw.getOrCreate()不把已解析的 actor ID 缓存在ActorResolutionState上。基于 key 的 handle 与连接应每次操作都重新解析避免在 destroy / recreate 之后仍钉在旧 actor 选择上。网关目标推导面向网关的客户端辅助函数packages/rivetkit/src/client内从getGatewayTarget()推导EngineControlClient目标而不是提前resolveActorId()。get()/getOrCreate()handle 必须把ActorQuery一路传给sendRequest、openWebSocket、buildGatewayUrl保证每次请求与重连都在网关处重新解析只有getForId()与 create 类 handle 才收敛为纯 actor ID 目标。actionId 可空语义actor-connect 协议的actionId是可空的0是合法 action ID只有null才视为连接级错误。七、Raw KV 限额硬性约束与失败语义文档给出了使用原始 actor KV 时必须遵守的引擎限额这些是硬限制维度限额单个 key 最大长度2048 字节单次批量写入kv put总负载976 KiBkeys values 合计单批最大条数kv put128 对 key-value单 actor KV 总存储10 GiB设计约定若单请求可能超过上述任一限额必须拆分为多个请求总存储 10 GiB 是硬限制超限时必须以显式错误 fail closed不允许吞掉、截断或忽略 KV 写入失败。此外文档要求任何涉及 KV、队列、workflow 持久化、SQLite-over-KV 或相关限额的 actor 行为变更必须同步更新website/src/content/docs/actors/limits.mdx对应仓库中的 docs/content/docs 文档站点。限额条款被当成与实现变更绑定的契约来维护。八、启动阶段日志perf internal 与 perf user 双前缀约定为了让启动耗时可 grep、可区分框架与用户代码文档规定 actor 启动的每个离散阶段必须有对应的 debug 日志且命名约定与ActorMetrics.startup的 key 严格一致DEBUG perf internal: loadStateMs durationMs... DEBUG perf internal: initQueueMs durationMs... DEBUG perf user: onCreateMs durationMs... DEBUG perf user: dbMigrateMs durationMs...框架 / 基础设施阶段用perf internal:前缀用户代码回调用perf user:前缀新增启动阶段时必须补上对应前缀的日志若该阶段运行用户代码还要更新ActorInstance中的#userStartupKeys集合。该约定在源码中有直接落地证据packages/rivetkit/src/registry/native.ts 中可以看到perf user: createStateMs、perf user: createVarsMs、perf user: onWakeMs等日志均以logger().debug({ msg: ..., durationMs: performance.now() - startedAt })的形式输出前后缀命名与文档完全一致。九、NAPI 接收循环任务回收、超时与结束原因NAPI 接收循环receive loop是原生侧事件分发的心脏文档给出了四条关键实现纪律长生命周期任务句柄归 adapter 所有NAPIrunhandler 这类 adapter 持有的长任务句柄放在 packages/rivetkit-napi/src/napi_actor_events.rs只通过共享ActorContext状态暴露同步重启钩子JS 侧重启方法不得依赖异步锁。生命周期门闩单源化不要在 packages/rivetkit-napi/src/actor_context.rs 镜像 actor 的ready/started标志读写都走核心ActorContext保证 sleep 门控只有一个事实来源。优雅 drain 用while let Some(...) tasks.join_next().awaitJoinSet::shutdown()会 abort 进行中的任务破坏 Sleep/Destroy 的顺序。源码中确实可以看到这样的循环一边join_next()回收完成的后台任务避免 JoinSet 在整个 actor 生命周期内持续膨胀造成原生 RSS 增长一边在主事件上检查ctx.has_end_reason()决定是否退出。Sleep/Destroy必须在成功和错误两条回复路径上都设置共享 adapter 的end_reason否则外层接收循环在关闭失败后还会继续消费排队事件。另外两条与异步安全直接相关的纪律带回复的 TSF 分发必须通过共享 timed-spawn 辅助函数用with_timeout(...)包裹回调 future源码中onMigrate、createState等回调正是用with_timeout(onMigrate, config.on_migrate_timeout, ...)形式调度的直接spawn_reply(...)可能让卡死的 JS Promise 泄漏到关闭为止取消语义必须在 NAPI 层与 TS 层之间端到端传递CancellationToken对象禁止回归 BigInt token 注册表或轮询循环。十、Sleep 关闭语义与持久化胶水Sleep 关闭的等待范围等待进行中的 HTTP action 工作与挂起的 disconnect 回调完成后再进入onSleep但不阻塞在开放的休眠连接hibernatable connections上——因为现有连接上的 action 在优雅关闭窗口内仍可能完成。测试纪律等待目标 actor 进入 sleep 的 driver 测试应把生命周期事件记录到独立的 observer actor而不是轮询目标 actor 的 action 或保持普通目标连接。持久化胶水仍在 TS 侧本分支上原生 TS actor/连接持久化胶水还位于 packages/rivetkit/src/registry/native.ts文档提醒 PRD 中拆分state-manager.ts/connection-manager.ts的引用可能已过时除非这些模块重新出现否则等价行为落在registry/native.ts中。onWake 映射公共 TS actor 的onWake映射到原生回调包的onWakeonBeforeActorStart是内部 driver/NAPI 启动钩子不是公共 actor 配置。静态 state 必须结构化克隆native.ts 中 actor 的静态state值必须按 actor 实例structuredClone(...)直接复用字面量会让不同 keyed actor 之间泄漏变更。缓存位置JS-only 的原生 actor 缓存应放在ActorContext.runtimeState()而不是以 actorId 为 key 的模块级全局同 key 重建必须拿到全新 bag。连接休眠追踪每条NativeConnAdapter构造路径都必须保留CONN_STATE_MANAGER_SYMBOL挂接休眠连接变更依赖核心ConnHandle::set_state的 dirty 追踪来请求持久化。持久化保存原生 actor 持久化保存必须用ctx.requestSaveAndWait({ immediate: true })状态字节只通过serializeState回调收集。undefined 保真需要在 Rust JSON/CBOR 桥上保留 JSundefined的不透明用户负载应走encodeCborCompat/decodeCborCompat结构性 JSON 信封不要用这两个辅助函数其中省略的字段必须保持省略。十一、Workflow 上下文的访问守卫文档把 workflow 上下文拆成两半packages/rivetkit/src/workflow/context.ts这是理解 workflow 可重放性设计的钥匙WorkflowContext供 workflow 函数及try/loop/race/join回调使用只暴露可重放原语不暴露任何 actor 数据。actor 数据成员state、vars、db、client、broadcast严禁加入WorkflowContext。WorkflowStepContext供step/tryStep/rollback回调使用是唯一能触达 actor 数据与副作用的地方。守卫机制在源码中是真实存在的WorkflowStepContext的state/vars等 getter 都会先调用#ensureActive(state)/#ensureActive(vars)当 step 上下文在 step 落定之后仍被捕获使用时会抛出错误只有只读属性如actorId、name、key、log、abortSignal豁免。十二、Drizzle 兼容性测试rivetkit/db/drizzle公共面需要跨多个 drizzle-orm 版本验证。仓库提供了现成脚本 scripts/test-drizzle-compat.shcd rivetkit-typescript/packages/rivetkit ./scripts/test-drizzle-compat.sh # 测试全部默认版本 ./scripts/test-drizzle-compat.sh 0.44.2 0.45.1 # 测试指定版本脚本行为逐个安装指定 drizzle-orm 版本针对rivetkit/db/drizzle公共面对scripts/drizzle-compat-smoke.ts做类型检查按版本报告 pass/fail退出时恢复原始 package.json 与 lockfile。当前DEFAULT_VERSIONS数组为(0.44 0.45)支持新 drizzle 版本发布时需同步更新该数组。脚本内部确实实现了备份package.json.drizzle-compat-bak、pnpm-lock.yaml.drizzle-compat-bak与trap cleanup EXIT恢复逻辑。十三、测试基础设施与平台测试纪律共享引擎生命周期TypeScript 测试共享的本地rivet-engine生命周期位于 packages/rivetkit/tests/shared-engine.tsdriver 与平台测试应复用它而不是各自启动独立引擎。运行时对等性测试可用 fake binding 类实例化NapiCoreRuntime与WasmCoreRuntime再通过buildNativeFactory(...)驱动共享 actor 胶水——这样不需要生成真实的 NAPI / wasm 产物。平台 wasm 冒烟测试复用 packages/rivetkit/tests/platforms/shared-registry.ts 中的 raw-SQL SQLite 计数器 actor 与公共 wasmsetup(...)形态。平台冒烟测试基础设施tests/platforms/shared-platform-harness.ts 负责 serverless runner 的启动、app 进程日志、临时 app 目录、健康检查以及固定的pnpm dlxCLI 启动。测试文件清单同步新增 / 删除 / 重命名packages/rivetkit/tests/driver/*.test.ts使用describeDriverMatrix的时必须同步更新.claude/skills/driver-test-runner/SKILL.md中的 Fast/Slow/Excluded 测试列表与套件描述表。十四、平台兼容Cloudflare Workers 与 Wasm 绑定约束14.1 Cloudflare Workers构造器不得在全局作用域做异步 I/OCloudflare Workers 禁止在全局作用域request handler 之外使用setTimeout、fetch、connect等异步 I/O。而Registry构造函数运行在全局作用域因此绝不能无条件调用这些 API。任何延迟工作例如预启动运行时都必须先通过同步配置检查再调度 timer。参考实现见 packages/rivetkit/src/registry/index.ts外层if守卫setTimeout的注册内层if在 tick 之后复查以拾取迟到的配置变更。14.2 Wasm 绑定包的纪律packages/rivetkit-wasm/pkg/是 wasm-pack 输出目录提交源码与构建脚本包构建时再重新生成产物。wasm raw WebSocket handle 导出为WebSocketHandle而非WebSocket——wasm-bindgen 会拒绝遮蔽宿主全局类的类名。wasm 运行时适配器的字节归一化保持在Uint8Array上不得给 packages/rivetkit/src/registry/wasm-runtime.ts 引入 NodeBuffer依赖。平台 wasm 绑定通过setup({ wasm: { bindings, initInput } })传入不添加隐藏的globalThis绑定钩子。wasmCoreRegistry的 serverless 启动必须使用BuildingServerlesswaiter 状态构建期间 shutdown 必须唤醒 waiters 并 drain 新建的运行时。在转换为核心ServeConfig或启动 registry 副作用前先校验 wasm-only serve 配置约束。wasm 包导出或文件变更后运行pnpm --filter rivetkit/rivetkit-wasm run check:package验证发布 tarball 包含根入口与 wasm 产物。wasm 构建使用包内固定的wasm-pack依赖禁止npx -y wasm-pack。wasm 桥接的RivetErrorSchema只按(group, code)内部化动态错误信息必须留在RivetError.message不能膨胀 schema 缓存 key。wasm websocket 回调区域callback region用按活跃 region ID 的 map 追踪并在结束时移除条目防止重复回调残留空槽region ID0是未追踪哨兵u32分配通过跳过活跃 ID 来避免 panic。十五、文档同步约定Context 类型与内部架构文档最后是两条防止文档漂移的约定Context 类型三处同步*ContextOf类型从 packages/rivetkit/src/actor/contexts/index.ts 导出并由packages/rivetkit/src/actor/mod.ts再导出增删改 context 类型时必须同步更新website/src/content/docs/actors/types.mdx公开文档页与website/src/content/docs/actors/index.mdxcrash course 的 Context Types 一节。动态 actor 架构文档涉及动态 actor 行为、桥接契约、isolate 生命周期或运行时沙箱接线时先参考 docs-internal/rivetkit-typescript/DYNAMIC_ACTORS_ARCHITECTURE.md任何动态 actor 架构、生命周期、桥接负载、安全行为或临时兼容路径的变更都必须在同一变更中更新该文档。总结rivetkit-typescript 的这份工程规范看似只是给 AI 代理与维护者的工作守则实则浓缩了该 SDK 最核心的架构决策Rust 核心统一语义、NAPI 与 Wasm 双运行时对等、SQLite v2 的可恢复性设计、查询式网关协议的参数契约、KV 硬限额以及 workflow 可重放性驱动的上下文守卫。理解这些边界无论是在 Node、边缘平台还是 Cloudflare Workers 上构建 stateful actor都能更准确地预判 SDK 行为、定位构建与运行时问题并写出与引擎契约一致的客户端代码。【免费下载链接】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),仅供参考
锦
锦皓数字建站
深耕本土企业品牌数字化升级,专注原创端正雅致商务官网,从视觉设计到稳定运维全程保驾护航。