news 2026/9/18 11:48:29

在 Supabase Edge Functions(Deno)中运行 RivetKit Actors:@rivetkit/supabase 集成指南

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
在 Supabase Edge Functions(Deno)中运行 RivetKit Actors:@rivetkit/supabase 集成指南

在 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,需要解决两个关键问题:

  1. 运行时适配:Edge 环境没有本地进程/原生二进制,必须使用 wasm 运行时。
  2. 依赖闭包精简: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.mjsdist/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 的stateactions
  • 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的读取优先级是"代码中显式配置优先,环境变量兜底":

配置项环境变量说明
endpointRIVET_ENDPOINTRivet Engine 端点,必填(URL 支持namespace:token@host鉴权语法)
namespaceRIVET_NAMESPACE命名空间,可选(可从 URL 内嵌)
tokenRIVET_TOKEN访问令牌,可选(可从 URL 内嵌)
envoy.poolNameRIVET_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 client

scripts/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;
  • SupabaseSetupConfigRegistryConfigInput中剥离了runtimewasm两个字段(mod.ts),因为这两个字段由适配器接管,用户无需也不应手动配置;
  • 由于 wasm 二进制由serve异步读取,setup保持同步;
  • 返回值是Registry<A>,可以用createClient<typeof registry>(...)派生类型化客户端,并可将同一 registry 传给serve

3.2serve(registryOrConfig, options?)

serve是异步的(返回Promise<void>),接受两种入参:

  1. setup返回的Registry实例;
  2. 直接传配置对象(内部自动走一遍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/rivetRivet 管理器 API 的挂载路径
fetch管理器路径之外请求的自定义处理器

managerPath的解析优先级为:config.serverless?.basePathoptions.managerPath→ 默认值/api/rivet

3.3 wasm 运行时绑定

@rivetkit/rivetkit-wasm是 wasm 核心绑定包(rivetkit-typescript/packages/rivetkit-wasm/index.js 导出pkg/rivetkit_wasm.jsrivetkit_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

serveDeno.serve处理器中执行如下路由逻辑(mod.ts):

  1. 命中管理器路径url.pathname === managerPath或以其为前缀(managerPath + "/"),直接交给registry.handler(request)
  2. Supabase 函数前缀剥离:Supabase 把函数挂载在/functions/v1/<name>下,因此管理器 API 实际可能出现在如/<name>/api/rivet/...。适配器会查找路径中第一次出现的管理器段并剥离前缀后再交给registry.handler,从而用户无需配置 Supabase 特定的 basePath;
  3. 自定义 fetch:以上都不命中且提供了options.fetch,则交给自定义处理器;
  4. 兜底响应:否则返回"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}/startGET {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 的实现揭示了"部署精简"的关键策略:

  1. 预打包 rivetkitrivetkit被 bundle 进适配器的 dist,且声明为运行时依赖,从而 Deno eszip 不会把 rivetkit 的 native 依赖闭包(@rivetkit/rivetkit-napi@rivetkit/engine-cli@rivet-dev/services@rivet-dev/agent-os-core)拖进部署产物;
  2. 外部化白名单:仅@rivetkit/rivetkit-wasm与少量 Node CommonJS 库(pinocbor-x)保持外部依赖,在运行时由 Deno 的 Node 兼容层加载;
  3. 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的配置对象继承rivetkitRegistryConfigInput(去除了runtimewasm)。以下常用字段均可显式传入,或在applyEnv中由环境变量兜底(schema 见 rivetkit-typescript/packages/rivetkit/src/registry/config/index.ts 与 serverless.ts):

配置字段类型/默认值说明
use记录(必需)Actor 定义表,键为 Actor 名
endpointstringEngine 端点,支持https://namespace:token@hostURL 鉴权语法,也可用RIVET_ENDPOINT
namespacestring,默认"default"命名空间,也可用RIVET_NAMESPACE
tokenstring访问令牌,也可用RIVET_TOKEN
envoy.poolNamestring,默认"default"运行池名,也可用RIVET_POOL
serverless.basePath/api/rivet管理器 API 基础路径,与managerPath等价
serverless.maxStartPayloadBytes16 MiBPOST /start请求体大小上限
serverless.publicEndpoint自动推断客户端应连接的公共端点,支持 URL 鉴权语法,也可用RIVET_PUBLIC_ENDPOINT
serverless.publicToken自动推断公共端点令牌,也可用RIVET_PUBLIC_TOKEN
sqlite"local"/"remote"/{ backend }SQLite 后端;wasm 运行时默认为remote且不可用local
noWelcomefalse禁用启动欢迎日志(适配器默认已设为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.tssetup({ use: {...}, sqlite: "remote" })+serve(registry, { fetch }))、functions/.env(写入RIVET_ENDPOINTRIVET_PUBLIC_ENDPOINTRIVET_NAMESPACERIVET_POOLRIVET_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(rivetkitnpm:@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),仅供参考

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/9/18 11:46:55

KV Cache 原理与显存优化:大模型推理 OOM 排查实战

前几天帮一个朋友看他本地部署的推理服务&#xff0c;8G 显存的卡&#xff0c;模型权重放进去还剩一点余量&#xff0c;单条对话跑得好好的&#xff0c;结果他把并发调到 8&#xff0c;还没跑到第二轮就开始报显存不足。他把模型换小了一档&#xff0c;问题照旧&#xff1b;把m…

作者头像 李华
网站建设 2026/9/18 11:44:38

论文自动生成软件红黑榜:哪些实用哪些踩雷

每年到毕业季&#xff0c;总有一批人被论文折磨到深夜。电脑屏幕上光标一闪一闪&#xff0c;文档还停在标题页&#xff0c;导师的消息一条接一条催提纲。于是"论文自动生成软件"成了搜索栏里的高频词。但市面上的工具鱼龙混杂&#xff0c;宣传语一个比一个响亮&#…

作者头像 李华
网站建设 2026/9/18 11:43:00

大模型聚合平台选型,AI 编程工具的 Base URL 填 TaoToken 的 API 地址

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华
网站建设 2026/9/18 11:42:36

工业无线遥控器串频掉线频繁损坏实测与排查

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华
网站建设 2026/9/18 11:41:57

MySQL服务无法启动?从错误日志到innodb_force_recovery的完整排查指南

不是所有朋友都能忍受MySQL在关键时刻突然给你一个"服务无法启动"的弹窗。尤其是刚配好的环境、正在跑的业务库&#xff0c;或者辛辛苦苦初始化好的实例&#xff0c;一夜之间全没了着落。这个错误我踩过太多次——有配置写错的、有数据目录损坏的、有RPM安装后权限不…

作者头像 李华