在 Supabase Edge Functions(Deno)中运行 RivetKit Actors:@rivetkit/supabase 集成指南
【免费下载链接】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/supabase是 RivetKit 官方提供的 Supabase Edge Functions(Deno 运行时)适配层:只需一行导入,即可把 RivetKit Actors 以 WebAssembly(wasm)运行时形态托管在 Supabase Edge Function 中,wasm 运行时与 wasm 二进制加载全部自动接线。阅读本文后,你将掌握:如何编写一个最小的 Actor 计数器函数、如何通过环境变量/配置注入 Rivet Engine 连接信息、如何挂载自定义路由(如/health健康检查)、理解管理器 API 路径的挂载与 Supabase 函数前缀剥离机制,以及打包体积优化的底层原理。
关联文档:rivetkit-typescript/packages/supabase/README.md,本文所有实现细节均可在 rivetkit-typescript/packages/supabase/src/mod.ts 等源码中找到证据。
一、为什么需要 @rivetkit/supabase
RivetKit Actors 是面向有状态负载(stateful workloads)的运行时原语,适用于 AI Agent、协作应用与持久化执行等场景。在服务端函数(serverless)环境中托管 Actors,需要解决两个关键问题:
- 运行时适配:Edge 环境没有本地进程/原生二进制,必须使用 wasm 运行时。
- 依赖闭包精简:Supabase Edge Function 的部署(Deno eszip)会快照整个 npm 依赖闭包。若直接声明
rivetkit为运行时依赖,会把其 native 包(engine-cli、Services、rivetkit-napi、agent-os secure-exec 等)全部拖入部署产物,而这些代码在 wasm 路径下永远不会被执行。
@rivetkit/supabase的解法是:在 tsup.config.ts 中把rivetkit预打包进自身 dist,不将其声明为运行时依赖,同时通过函数的 import map 将rivetkit指向该预打包适配器。这样部署产物只包含 wasm 路径真正用到的代码。该包在 package.json 中以Apache-2.0协议发布,导出 ESM/CJS 双格式(dist/mod.mjs与dist/mod.js)。
二、最小可运行示例:计数器 Actor
这是 README 给出的完整最小示例(行为等价于仓库示例 examples/hello-world-supabase-functions/supabase/functions/rivet/index.ts):
import { actor } from "rivetkit"; import { serve } from "@rivetkit/supabase"; const counter = actor({ state: { count: 0 }, actions: { increment: (c, amount = 1) => (c.state.count += amount), getCount: (c) => c.state.count, }, }); await serve({ use: { counter } });要点拆解:
actor(...)来自rivetkit的创作 API,声明有状态 Actor 的state与actions;serve来自@rivetkit/supabase,它完成两件事:加载 wasm 运行时、托管 Rivet 管理器 API;- 用户的源码仍然写
import { actor } from "rivetkit",由 import map 重定向到该适配器(详见下文"函数目录结构")。
连接配置
需要将RIVET_ENDPOINT配置为函数密钥(function secret)。命名空间与令牌可以内嵌在 URL 中:
https://namespace:token@host例如https://my-ns:my-token@api.rivet.dev。
从源码看,applyEnv的读取优先级是"代码中显式配置优先,环境变量兜底":
| 配置项 | 环境变量 | 说明 |
|---|---|---|
endpoint | RIVET_ENDPOINT | Rivet Engine 端点,必填(URL 支持namespace:token@host鉴权语法) |
namespace | RIVET_NAMESPACE | 命名空间,可选(可从 URL 内嵌) |
token | RIVET_TOKEN | 访问令牌,可选(可从 URL 内嵌) |
envoy.poolName | RIVET_POOL | 运行池名称,仅当配置未显式设置时写入 |
serverless.basePath | — | 管理器 API 基础路径,默认为/api/rivet |
RIVET_* 系列环境变量在 rivetkit-typescript/packages/rivetkit/src/utils/env-vars.ts 中统一定义;RIVET_ENDPOINT也由核心配置 schema 支持(见 rivetkit-typescript/packages/rivetkit/src/registry/config/index.ts)。
函数目录结构(仓库示例)
examples/hello-world-supabase-functions 给出了完整的本地可运行布局:
hello-world-supabase-functions/ ├── package.json # dev: npx @rivetkit/cli dev --provider supabase ├── scripts/client.ts # 类型化客户端,调用计数器 Actor └── supabase/ ├── config.toml # project_id、edge_runtime 开关 └── functions/rivet/ ├── index.ts # Actor 定义 + setup/serve └── deno.json # import map:rivetkit -> npm:@rivetkit/supabase其中 deno.json 是关键:将rivetkit与@rivetkit/supabase都映射到npm:@rivetkit/supabase,Deno 才能解析导入,同时保持部署产物精简:
{ "imports": { "rivetkit": "npm:@rivetkit/supabase", "@rivetkit/supabase": "npm:@rivetkit/supabase" } }本地运行与调用
README 中的运行流程(仓库示例 examples/hello-world-supabase-functions/README.md):
npm install npx supabase start # 先启动本地 Supabase 栈(Edge Functions serve 依赖它) npm run dev # rivet dev 启动本地 Engine 并拉起 supabase functions serve另开终端调用 Actor:
npm run clientscripts/client.ts 展示了类型化客户端的用法——从函数模块导出registry,客户端侧用createClient<typeof registry>(...)继承全部 Actor 类型,然后直接client.counter.getOrCreate("demo")调用increment/getCount方法:
import { createClient } from "rivetkit/client"; import type { registry } from "../supabase/functions/rivet/index.ts"; const client = createClient<typeof registry>({ endpoint: process.env.RIVET_ENDPOINT ?? "http://localhost:6420", }); const counter = client.counter.getOrCreate("demo"); console.log(`increment(3) -> ${await counter.increment(3)}`); console.log(`getCount() -> ${await counter.getCount()}`);网络注意点:本地 Edge Runtime 运行在容器中,它访问宿主机上的 Engine 需要使用http://host.docker.internal:6420而非回环地址。Linux 下supabase functions serve会提供所需的host-gateway映射,macOS/Windows 由 Docker Desktop 提供该别名,因此跨平台可用;仅当需要覆盖时才手动设置RIVET_ENDPOINT。
生产部署命令为:
npx supabase functions deploy rivet三、setup 与 serve:两个入口的职责划分
@rivetkit/supabase暴露了两个核心导出(实现在 rivetkit-typescript/packages/supabase/src/mod.ts):
3.1setup(config)
setup是对rivetkit核心setup的包装,它自动注入 wasm 运行时并返回类型化 Registry:
export function setup<A extends RegistryActors>( config: SupabaseSetupConfig<A>, ): Registry<A> { return rivetkitSetup<A>({ runtime: "wasm", wasm: { bindings: wasmBindings }, noWelcome: true, ...config, }); }关键点:
- 强制
runtime: "wasm",绑定@rivetkit/rivetkit-wasm的 wasm bindings; SupabaseSetupConfig从RegistryConfigInput中剥离了runtime与wasm两个字段(mod.ts),因为这两个字段由适配器接管,用户无需也不应手动配置;- 由于 wasm 二进制由
serve异步读取,setup保持同步; - 返回值是
Registry<A>,可以用createClient<typeof registry>(...)派生类型化客户端,并可将同一 registry 传给serve。
3.2serve(registryOrConfig, options?)
serve是异步的(返回Promise<void>),接受两种入参:
setup返回的Registry实例;- 直接传配置对象(内部自动走一遍
setup)。
它内部完成:
const wasmModule = await Deno.readFile(resolveWasmUrl()); config.wasm = { ...config.wasm, bindings: wasmBindings, initInput: wasmModule };- 通过
import.meta.resolve("@rivetkit/rivetkit-wasm/rivetkit_wasm_bg.wasm")定位 wasm 二进制,再用Deno.readFile异步读出; - 将二进制作为
initInput注入 wasm 运行时; - 最终调用
Deno.serve(...)启动 HTTP 服务。
ServeOptions支持两个字段:
| 字段 | 默认值 | 说明 |
|---|---|---|
managerPath | /api/rivet | Rivet 管理器 API 的挂载路径 |
fetch | 无 | 管理器路径之外请求的自定义处理器 |
managerPath的解析优先级为:config.serverless?.basePath→options.managerPath→ 默认值/api/rivet。
3.3 wasm 运行时绑定
@rivetkit/rivetkit-wasm是 wasm 核心绑定包(rivetkit-typescript/packages/rivetkit-wasm/index.js 导出pkg/rivetkit_wasm.js及rivetkit_wasm_bg.wasm),Rust 核心源码位于 rivetkit-typescript/packages/rivetkit-wasm/src/lib.rs,通过 wasm-pack 面向wasm32-unknown-unknown构建。这解释了为什么在无本地进程的 Edge 环境中 Actors 依然能够运行。
四、路由机制:管理器 API 与自定义 fetch
4.1 默认行为:Rivet 管理器 API
serve在Deno.serve处理器中执行如下路由逻辑(mod.ts):
- 命中管理器路径:
url.pathname === managerPath或以其为前缀(managerPath + "/"),直接交给registry.handler(request); - Supabase 函数前缀剥离:Supabase 把函数挂载在
/functions/v1/<name>下,因此管理器 API 实际可能出现在如/<name>/api/rivet/...。适配器会查找路径中第一次出现的管理器段并剥离前缀后再交给registry.handler,从而用户无需配置 Supabase 特定的 basePath; - 自定义 fetch:以上都不命中且提供了
options.fetch,则交给自定义处理器; - 兜底响应:否则返回
"This is a RivetKit server.\n\nLearn more at https://rivet.dev\n"。
registry.handler内部(rivetkit-typescript/packages/rivetkit/src/registry/index.ts)会识别 serverless 的POST {basePath}/start与GET {basePath}/metadata协议端点,校验 start 请求体大小(默认上限 16 MiB,超限返回 413),并通过流式响应处理背压。serverless.basePath的默认值与配置 schema 定义在 rivetkit-typescript/packages/rivetkit/src/registry/config/serverless.ts。
4.2 挂载自定义路由
README 展示了如何通过fetch处理管理器 API 之外的所有请求:
await serve({ use: { counter } }, { fetch: (request) => { if (new URL(request.url).pathname.endsWith("/health")) { return new Response("ok"); } return new Response("not found", { status: 404 }); }, });这是健康检查等自定义端点的标准写法:/health返回ok,其余非管理器路径返回 404。仓库测试 rivetkit-typescript/packages/rivetkit/tests/platforms/supabase-functions.test.ts 中,函数实现也采用了完全相同的模式(/health返回ok,其余 404),可作为端到端验证参考。
五、打包优化原理(源码级)
tsup.config.ts 的实现揭示了"部署精简"的关键策略:
- 预打包 rivetkit:
rivetkit被 bundle 进适配器的 dist,且不声明为运行时依赖,从而 Deno eszip 不会把 rivetkit 的 native 依赖闭包(@rivetkit/rivetkit-napi、@rivetkit/engine-cli、@rivet-dev/services、@rivet-dev/agent-os-core)拖进部署产物; - 外部化白名单:仅
@rivetkit/rivetkit-wasm与少量 Node CommonJS 库(pino、cbor-x)保持外部依赖,在运行时由 Deno 的 Node 兼容层加载; - node: 前缀重写:通过 esbuild 插件把所有裸 Node 内置模块导入重写为
node:前缀形式(如node:os),因为 esbuild 打包 CJS 依赖时可能产出裸import "module",而 Deno 会拒绝这种写法。
此外,package.json 的check-edge-closure脚本(node ../../../scripts/ci/check-edge-native-closure.mjs)会在 CI 中校验边缘部署产物不包含 native 依赖闭包,防止回归。
六、配置参考:RegistryConfigInput 常用字段
serve/setup的配置对象继承rivetkit的RegistryConfigInput(去除了runtime与wasm)。以下常用字段均可显式传入,或在applyEnv中由环境变量兜底(schema 见 rivetkit-typescript/packages/rivetkit/src/registry/config/index.ts 与 serverless.ts):
| 配置字段 | 类型/默认值 | 说明 |
|---|---|---|
use | 记录(必需) | Actor 定义表,键为 Actor 名 |
endpoint | string | Engine 端点,支持https://namespace:token@hostURL 鉴权语法,也可用RIVET_ENDPOINT |
namespace | string,默认"default" | 命名空间,也可用RIVET_NAMESPACE |
token | string | 访问令牌,也可用RIVET_TOKEN |
envoy.poolName | string,默认"default" | 运行池名,也可用RIVET_POOL |
serverless.basePath | /api/rivet | 管理器 API 基础路径,与managerPath等价 |
serverless.maxStartPayloadBytes | 16 MiB | POST /start请求体大小上限 |
serverless.publicEndpoint | 自动推断 | 客户端应连接的公共端点,支持 URL 鉴权语法,也可用RIVET_PUBLIC_ENDPOINT |
serverless.publicToken | 自动推断 | 公共端点令牌,也可用RIVET_PUBLIC_TOKEN |
sqlite | "local"/"remote"/{ backend } | SQLite 后端;wasm 运行时默认为remote且不可用local |
noWelcome | false | 禁用启动欢迎日志(适配器默认已设为true) |
logging.level | "warn" | 日志级别 |
七、端到端验证:仓库测试如何覆盖该集成
仓库提供了针对 Supabase Functions 平台的冒烟测试 rivetkit-typescript/packages/rivetkit/tests/platforms/supabase-functions.test.ts,它验证了与本文描述完全一致的链路:
- 先以
rivet-engine start --config ...启动本地 Engine(写入临时config.json,配置 guard/api-peer/metrics 端口与拓扑); - 生成临时 Supabase 项目:
supabase/config.toml(启用edge_runtime)、supabase/functions/rivet/actor.ts(SQLite 计数器 Actor)、index.ts(setup({ use: {...}, sqlite: "remote" })+serve(registry, { fetch }))、functions/.env(写入RIVET_ENDPOINT、RIVET_PUBLIC_ENDPOINT、RIVET_NAMESPACE、RIVET_POOL、RIVET_TOKEN); - 将
rivetkit、@rivetkit/rivetkit-wasm、@rivetkit/supabase等依赖拷贝进函数目录的node_modules,保证函数与测试共享同一份工作区代码; - 通过
supabase start(排除大部分不需要的服务)与supabase functions serve --no-verify-jwt --env-file supabase/functions/.env拉起本地 Supabase 栈; - 测试断言:冷启动后
increment(2)返回 2,连续increment(3)返回 5,getCount()为 5;休眠 1.5s 后再次访问,wakeCount增加(验证休眠唤醒);三个并行 Actor 各自计数正确(验证并发隔离)。
这个测试从实战角度印证了:连接配置全部来自函数环境变量、/health自定义路由可用、cold start 后 Actor 状态持久化、休眠唤醒与并行 Actor 均正常工作,与 README 中"以单一 import 托管 Actors"的描述完全吻合。
八、使用建议与限制
- 推荐写法:函数内先
const registry = setup({ use: { ... } })并导出,便于客户端createClient<typeof registry>获得全量类型;再await serve(registry, options)。 - 健康检查:通过
options.fetch在/health返回ok(Supabase 平台与本地测试均按此约定探测)。 - wasm 运行时的 SQLite 限制:wasm 运行时默认使用
remoteSQLite 后端且不能使用local(见 rivetkit-typescript/packages/rivetkit/src/registry/config/index.ts),有状态持久化依赖远端 Engine,因此RIVET_ENDPOINT是必需配置。 - 本地调试:
npx supabase start必须先于supabase functions serve执行;容器网络下 Engine 地址使用host.docker.internal主机名。 - 部署体积:务必保留
deno.jsonimport map(rivetkit→npm:@rivetkit/supabase),否则部署会拖入 native 依赖闭包。
相关资源
- 适配器源码:rivetkit-typescript/packages/supabase/src/mod.ts
- 打包配置:rivetkit-typescript/packages/supabase/tsup.config.ts
- wasm 绑定:rivetkit-typescript/packages/rivetkit-wasm
- 核心配置 schema:rivetkit-typescript/packages/rivetkit/src/registry/config/index.ts
- 可运行示例:examples/hello-world-supabase-functions
- 平台冒烟测试:rivetkit-typescript/packages/rivetkit/tests/platforms/supabase-functions.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),仅供参考