III Browser SDK 完整指南:在浏览器中注册函数、触发调用与实时流式通信
【免费下载链接】iiiEffortlessly compose, extend, and observe every service in real-time for the first time ever.项目地址: https://gitcode.com/GitHub_Trending/mo/iii
本文是 iii 项目 Browser SDK API 参考文档 的深度展开版。III Browser SDK(npm 包名
iii-browser-sdk)让前端页面作为一个 III Worker 直接接入 III 引擎:通过一条原生浏览器 WebSocket 长连接完成函数注册、函数触发(同步 / 异步 / 队列)、自定义触发器类型、State 状态管理与 Stream 流式订阅。读完本文,你将掌握如何在浏览器中初始化 SDK、注册可被后端反向调用的函数、用TriggerAction控制调用路由、用iii-browser-sdk/state与iii-browser-sdk/stream子路径操作共享状态与实时数据,以及如何利用连接状态监听与重连配置构建健壮的实时前端。
为什么需要浏览器端 SDK
从仓库内 iii-browser-sdk README 可以看到,Browser SDK 的设计目标是把前端变成一个 III Worker,它带来四个核心能力:
- 持久连接:一条 WebSocket 取代大量 HTTP 往返,避免轮询;
- 双向通信:引擎可以调用注册在浏览器里的函数,后端 Worker 通过
trigger()把数据实时推送到前端; - 同构 API:
registerFunction、trigger、registerTrigger等原语与服务端 SDK 完全一致; - 零 Node.js 依赖:运行在任何具备原生
WebSocket的浏览器环境,不带 OpenTelemetry 依赖(见 package.json 的description字段:"no OpenTelemetry, no Node.js dependencies")。
从源码结构看,SDK 主体位于 sdk/packages/node/iii-browser/src,由iii.ts(核心ISdk实现)、channels.ts、state.ts、stream.ts、triggers.ts、iii-constants.ts、iii-types.ts、types.ts等模块组成。本文对应的 API 参考文档 docs/reference/sdk-browser.mdx.skill.md 是由 docs/next/scripts/generate-api-docs.mts 依据这些源码中的 doc-comment 自动生成的。
安装
npm install iii-browser-sdk包提供 ESM / CJS 双格式产物,并内置了多个子路径导出(exports字段定义在 package.json):
| 导入路径 | 内容 |
|---|---|
iii-browser-sdk | 核心 API:registerWorker、ISdk、TriggerAction、MessageType、各类注册输入与句柄类型 |
iii-browser-sdk/state | 状态管理:IState、StateGetInput、StateSetInput、StateUpdateInput等 |
iii-browser-sdk/stream | 流式数据:IStream、StreamTriggerConfig、UpdateOp系列原子操作 |
iii-browser-sdk/helpers | 辅助函数:createChannel、createStream(见 helpers.ts) |
初始化:registerWorker
import { registerWorker } from 'iii-browser-sdk' const worker = registerWorker('ws://localhost:49135')签名
registerWorker(address: string, options?: InitOptions) => ISdkaddress是 III 引擎的 WebSocket 地址(如ws://localhost:49135)。SDK 构造时会自动建立 WebSocket 连接(源码见 iii.ts 构造函数中的this.connect()),无需手动拨号。
InitOptions 完整字段
| 字段 | 类型 | 默认值 | 说明 |
|---|---|---|---|
workerName | string | browser:<随机后缀> | 向引擎宣告的 Worker 名称。浏览器没有 pid 可区分,且引擎在每个命名空间内只允许一个同名的存活 Worker,因此 SDK 默认按客户端生成唯一名称,避免两个标签页共用固定名称时互相驱逐(见 iii.ts 与randomId()实现 iii.ts) |
namespace | string | 引擎default命名空间 | Worker 注册与函数、触发器注册所在的命名空间。注意:浏览器没有process.env,因此没有环境变量兜底,必须显式传参;传入空字符串会被拒绝(见 iii.ts) |
invocationTimeoutMs | number | 30000 | worker.trigger()调用的默认超时(毫秒) |
reconnectionConfig | Partial<IIIReconnectionConfig> | 见下表 | WebSocket 断线重连行为 |
headers | Record<string, string> | 忽略 | 浏览器 WebSocket 通过查询参数或 Cookie 鉴权,headers选项会被忽略 |
重连配置 IIIReconnectionConfig
重连采用指数退避 + 抖动策略,默认配置常量定义在 iii-constants.ts:
| 字段 | 默认值 | 说明 |
|---|---|---|
initialDelayMs | 1000 | 起始延迟(毫秒) |
maxDelayMs | 30000 | 最大延迟上限(毫秒) |
backoffMultiplier | 2 | 指数退避倍数 |
jitterFactor | 0.3 | 随机抖动因子(0–1),用于打散并发重连 |
maxRetries | -1 | 最大重试次数,-1表示无限重试 |
典型用法:
const worker = registerWorker('ws://localhost:49135', { invocationTimeoutMs: 10000, reconnectionConfig: { maxRetries: 5, initialDelayMs: 500 }, })连接状态 IIIConnectionState
type IIIConnectionState = "disconnected" | "connecting" | "connected" | "reconnecting" | "failed"状态机在 iii-constants.ts 定义。测试用例(tests/connection.test.ts)验证了以下关键行为:
- 订阅监听器会立即以当前状态触发一次,之后每次状态迁移都会触发;
- 支持多个监听器同时订阅;
- 存在致命错误(如
WORKER_NAMESPACE_CONFLICT工作器名称冲突):最终状态为failed,不再重连,且挂起的调用会被拒绝; - 非致命错误(如
FUNCTION_NAMESPACE_CONFLICT):连接保持connected,不影响后续使用。
核心方法
registerTrigger:注册触发器
将触发器绑定到已注册的函数,事件发生时引擎调用目标函数。
签名
registerTrigger(trigger: RegisterTriggerInput) => TriggerRegisterTriggerInput字段:
| 字段 | 类型 | 必填 | 说明 |
|---|---|---|---|
type | string | 是 | 使用的已注册触发器类型标识(如storage::object-created、http、cron) |
function_id | string | 是 | 触发器触发时调用的函数 ID |
config | unknown | 是 | 触发器类型专属配置,需匹配该类型期望的结构 |
示例
const trigger = worker.registerTrigger({ type: 'cron', function_id: 'my-service::process-batch', config: { expression: '0 */5 * * * * *' }, }) // 之后移除触发器 trigger.unregister()实现细节(iii.ts):SDK 会在注册时用randomUUID()生成触发器实例 ID,并把触发器的命名空间默认解析为当前 Worker 的命名空间(而不是引擎的default),从而保证"触发器 → 函数"在同一个命名空间内解析成功。
registerFunction:注册浏览器本地函数
注册一个在浏览器会话本地执行的异步处理器。注意:HTTP invocation 配置是 Node.js SDK 的特性,浏览器 SDK 不接受该配置。
签名
registerFunction(functionId: string, handler: RemoteFunctionHandler, options?: RegisterFunctionOptions) => FunctionRefRemoteFunctionHandler类型:
type RemoteFunctionHandler = (data: TInput) => Promise<TOutput>RegisterFunctionOptions可选字段:description(函数描述)、metadata(任意元数据)、request_format/response_format(RegisterFunctionFormat,描述请求/响应结构)。
示例
// 本地处理器 const ref = worker.registerFunction( 'greet', async (data: { name: string }) => ({ message: `Hello, ${data.name}!` }), { description: 'Returns a greeting' }, ) // 之后移除函数 ref.unregister()返回的FunctionRef包含id(函数唯一标识)和unregister()(从引擎移除该函数)。源码中的校验逻辑(iii.ts):空函数 ID 抛错、重复注册抛错,函数处理器会被包装进内部Map,等待引擎的InvokeFunction消息回调。
典型实时场景——后端推送数据到前端,无需轮询:
iii.registerFunction('ui::update-dashboard', async (metrics: { cpu: number; memory: number; requests: number }) => { document.getElementById('cpu')!.textContent = `${metrics.cpu}%` document.getElementById('memory')!.textContent = `${metrics.memory}MB` return null })trigger:触发函数调用
通过请求对象调用函数,路由行为由action字段决定。
签名
trigger(request: TriggerRequest<TInput>) => Promise<TOutput>TriggerRequest字段:
| 字段 | 类型 | 必填 | 说明 |
|---|---|---|---|
function_id | string | 是 | 要调用的函数 ID |
payload | TInput | 是 | 传给函数的输入数据 |
action | TriggerAction | 否 | 路由方式;省略则同步请求/响应 |
timeoutMs | number | 否 | 覆盖默认调用超时(毫秒) |
TriggerAction工厂对象(iii.ts 中的路由分支):
| 工厂方法 | 行为 | 返回类型 |
|---|---|---|
(省略action) | 同步:等待函数返回 | Promise<TOutput> |
TriggerAction.Enqueue({ queue }) | 经命名队列异步处理;引擎入队后先确认 | Promise<EnqueueResult>(含messageReceiptId) |
TriggerAction.Void() | 即发即忘,不等待响应 | Promise<undefined> |
示例
// 同步调用 const result = await worker.trigger<{ name: string }, { message: string }>({ function_id: 'greet', payload: { name: 'World' }, timeoutMs: 5000, }) console.log(result.message) // "Hello, World!" // 即发即忘 await worker.trigger({ function_id: 'send-email', payload: { to: 'user@example.com' }, action: TriggerAction.Void(), }) // 入队异步处理(队列必须在队列 Worker 的 queue_configs 中声明) const receipt = await worker.trigger({ function_id: 'process-order', payload: { orderId: '123' }, action: TriggerAction.Enqueue({ queue: 'orders' }), })实现要点:同步调用会生成invocation_id,将 resolve/reject 挂入内部invocations表并设置setTimeout超时(默认 30000ms,超时后拒绝并移除挂起项);Void()路由直接发送InvokeFunction消息并立即返回undefined。引擎内置函数(engine::前缀)会被路由到default命名空间,避免泄漏到 Worker 命名空间(见invocationNamespace实现 iii.ts)。
registerTriggerType:注册自定义触发器类型
触发器类型定义了外部事件(HTTP、cron、queue 等)如何映射为函数调用。
签名
registerTriggerType(triggerType: RegisterTriggerTypeInput, handler: TriggerHandler<TConfig>) => TriggerTypeRef<TConfig>RegisterTriggerTypeInput字段:id(类型唯一标识,如state、durable:subscriber)、description(人类可读描述)。
TriggerHandler字段:
| 字段 | 类型 | 必填 | 说明 |
|---|---|---|---|
registerTrigger | (config: TriggerConfig<TConfig>) => Promise<void> | 是 | 触发器实例注册时被调用 |
unregisterTrigger | (config: TriggerConfig<TConfig>) => Promise<void> | 是 | 触发器实例注销时被调用 |
示例——自定义 cron 触发器类型
type CronConfig = { expression: string } worker.registerTriggerType<CronConfig>( { id: 'cron', description: 'Fires on a cron schedule' }, { async registerTrigger({ id, function_id, config }) { startCronJob(id, config.expression, () => worker.trigger({ function_id, payload: {} }), ) }, async unregisterTrigger({ id }) { stopCronJob(id) }, }, )返回的TriggerTypeRef<TConfig>是一个带类型约束的句柄,提供便捷方法,调用方无需重复填写type字段:
| 方法 | 说明 |
|---|---|
id | 触发器类型标识 |
registerTrigger(functionId, config) | 注册绑定到该触发器类型的触发器(自动把命名空间对齐到 Worker 的命名空间) |
registerFunction(functionId, handler, config) | 注册函数并立即绑定到该触发器类型 |
unregister() | 从引擎注销该触发器类型 |
unregisterTriggerType:注销触发器类型
unregisterTriggerType(triggerType: RegisterTriggerTypeInput) => voidworker.unregisterTriggerType({ id: 'cron', description: 'Fires on a cron schedule' })addConnectionStateListener:监听连接状态
订阅连接状态迁移,处理器会立即以当前状态触发一次,之后每次迁移触发,支持多个监听器,返回取消订阅函数。
签名
addConnectionStateListener(handler: (state: IIIConnectionState) => void) => () => void示例
const unsub = worker.addConnectionStateListener((state) => { console.log('connection state:', state) }) // 之后停止接收更新 unsub()shutdown:优雅关闭
shutdown() => Promise<void>await worker.shutdown()实现(iii.ts):置位关闭标志、清除重连定时器、拒绝所有挂起调用(错误为 "iii is shutting down")并清理资源。
状态管理:iii-browser-sdk/state
IState接口提供基于scope(命名空间)+ key的状态操作,通过iii-browser-sdk/state子路径导出(类型定义见 state.ts):
| 方法 | 签名 | 说明 |
|---|---|---|
get | (input: StateGetInput) => Promise<TData \| null> | 按 scope 与 key 取值 |
set | (input: StateSetInput) => Promise<StateSetResult<TData> \| null> | 创建或覆盖状态值 |
delete | (input: StateDeleteInput) => Promise<DeleteResult> | 删除状态值 |
list | (input: StateListInput) => Promise<TData[]> | 列出 scope 内全部值 |
update | (input: StateUpdateInput) => Promise<StateUpdateResult<TData> \| null> | 对状态值应用原子更新操作 |
输入输出类型要点:
StateGetInput/StateDeleteInput:{ scope, key };StateSetInput:{ scope, key, value },StateSetResult返回new_value(新值)与old_value(旧值,若存在);StateUpdateInput:{ scope, key, ops },ops是有序的原子更新操作列表UpdateOp[];StateUpdateResult:new_value/old_value之外还有可选的errors: UpdateOpError[],目前仅merge操作在输入违反校验边界时产出。
原子更新操作 UpdateOp
type UpdateOp = UpdateSet | UpdateIncrement | UpdateDecrement | UpdateAppend | UpdateRemove | UpdateMerge| 操作 | 字段 | 语义 |
|---|---|---|
UpdateSet | path,value | 将指定路径字段设为值(空字符串 path 指向根值) |
UpdateIncrement | path,by | 数值字段增加指定量 |
UpdateDecrement | path,by | 数值字段减少指定量 |
UpdateAppend | path?,value | 向数组追加元素 / 拼接字符串 / 在嵌套路径推入新值 |
UpdateRemove | path | 移除指定路径字段 |
UpdateMerge | path?,value | 将对象浅合并进目标(根或嵌套位置) |
MergePath类型:
type MergePath = string | string[]省略path、传""或[]均指向根值;传字符串表示第一层字段;传字符串数组表示嵌套路径,每个元素是字面量键,点号不解释为分隔符(["a.b"]指向名为"a.b"的单个键,而非a → b)。
UpdateOpError提供稳定的错误信息结构:code(如"merge.path.too_deep")、message、op_index(出错操作在ops数组中的下标)、可选doc_url。
更新校验边界
UpdateMerge与UpdateAppend的引擎侧校验(见 sdk-browser.mdx.skill.md 的UpdateMerge/UpdateAppend条目):
- 路径深度 > 32、路径段 > 256 字节、值深度 > 16、顶层键 > 1024 会被结构化错误拒绝;
- 任何
__proto__/constructor/prototype路径段或顶层键会被拒绝(防原型污染); append语义:嵌套路径上缺失/为 null 的中间层自动创建;缺失叶子总是创建为数组;已存在的对象/标量叶子返回append.type_mismatch;- 结果中的
errors数组仅在出错时出现,无错时字段省略。
流式数据:iii-browser-sdk/stream
IStream<TData>接口用于自定义流实现,可覆盖引擎对某个流名的内置存储(通过helpers中的createStream传入,见 helpers.ts)。方法包括get/set/delete/list/listGroups,对应输入类型StreamGetInput、StreamSetInput、StreamDeleteInput、StreamListInput、StreamListGroupsInput,均以stream_name+group_id(+item_id)寻址。
流触发器配置
stream触发器——监听流条目变更:
| 字段 | 类型 | 必填 | 说明 |
|---|---|---|---|
stream_name | string | 是 | 要监听的流名,仅该流的变更触发处理器 |
group_id | string | 否 | 设置后仅该组内变更触发 |
item_id | string | 否 | 设置后仅该条目变更触发 |
condition_function_id | string | 否 | 条件执行函数 ID,返回false时跳过处理器 |
处理器输入StreamChangeEvent:streamName、groupId、id?、timestamp、type: "stream",以及事件详情event: { data, type: "create" | "update" | "delete" }。
stream:join/stream:leave触发器——监听订阅加入/离开:
StreamJoinLeaveTriggerConfig仅含可选condition_function_id;事件负载StreamJoinLeaveEvent含stream_name、group_id、id?、subscription_id、可选context(来自StreamAuthResult)。
流认证
StreamAuthInput(addr、headers、path、query_params)→StreamAuthResult(可选context,认证后传给流处理器)。StreamContext即StreamAuthResult["context"]的提取类型。
流式通道:Channel
Channel是用于Worker 与 Worker 之间数据传输的流式通道对,通过iii-browser-sdk/helpers的createChannel辅助函数创建(实现见 iii.ts):SDK 会调用引擎内置函数engine::channels::create,返回 writer/reader 两个端点及其可序列化引用。
import { createChannel } from 'iii-browser-sdk/helpers' const { writer, reader, writerRef, readerRef } = await createChannel(worker)| 字段 | 类型 | 说明 |
|---|---|---|
writer/reader | ChannelWriter/ChannelReader | 通道写端 / 读端,使用原生浏览器 WebSocket |
writerRef/readerRef | StreamChannelRef | 可序列化端点引用,可放进调用 payload 传给其他 Worker |
StreamChannelRef字段:channel_id(通道唯一标识)、access_key(认证访问密钥)、direction("read" | "write",标识读端或写端)。
RBAC 鉴权集成
Browser SDK 的类型体系完整覆盖 RBAC 代理 Worker 的接入契约:
AuthInput:WebSocket 升级时传给 RBAC 鉴权函数的输入,包含升级请求的headers、query_params(每个键映射为数组以支持重复键)、ip_address;AuthResult:鉴权函数返回值,控制 Worker 可调用的函数与上下文,字段与默认值:
| 字段 | 默认值 | 说明 |
|---|---|---|
allow_function_registration | true | 是否允许注册新函数 |
allow_trigger_type_registration | false | 是否允许注册新触发器类型 |
allowed_functions | [] | expose_functions之外额外允许的函数 ID |
forbidden_functions | [] | 即使匹配expose_functions也拒绝的函数 ID,优先级高于 allowed |
allowed_trigger_types | 全部允许 | 允许注册触发器的类型 ID |
function_registration_prefix | 无 | 应用于该 Worker 注册的所有函数 ID 的前缀 |
context | {} | 每次调用转发给中间件函数的任意上下文 |
MiddlewareFunctionInput:每次经 RBAC 端口的调用都会传给中间件函数,可检查、修改或拒绝调用,包含function_id、payload、context、可选action;- 注册钩子:
OnFunctionRegistrationInput/Result、OnTriggerRegistrationInput/Result、OnTriggerTypeRegistrationInput/Result分别对应函数注册、触发器注册、触发器类型注册的映射/拒绝钩子——返回(可能被映射后的)字段,或抛异常拒绝注册;结果中省略的字段保持注册请求的原始值。
引擎常量与消息类型
EngineFunctions(iii-constants.ts):引擎内置函数路径,如engine::workers::register、engine::functions::list、engine::functions::info、engine::workers::list/info、engine::triggers::list/info、engine::registered-triggers::list/info。命名注意点:LIST_TRIGGERS/INFO_TRIGGERS指触发器类型(模板),LIST_REGISTERED_TRIGGERS/INFO_REGISTERED_TRIGGERS指触发器实例(订阅行);旧的engine::trigger-types::list内置函数已移除,由engine::triggers::list承接;EngineTriggers:engine::functions-available(引擎触发器类型);MessageType(wire 判别器):invokefunction、invocationresult、registerfunction、registertrigger、registertriggertype、triggerregistrationresult、unregisterfunction、unregistertrigger、unregistertriggertype、workerregistered。
命名空间语义(浏览器特性)
与 Node / Python / Go SDK 不同,浏览器没有环境变量可用,因此命名空间只能通过InitOptions.namespace与各调用参数显式传递(iii.ts):
- 省略
namespace→ 继承 Worker 的命名空间(Worker 未指定时落入引擎default); - 显式传入空字符串会被拒绝(
namespace is empty错误)——"未设置"与"设置为空"含义相反,??会转发空串,因此 SDK 选择直接抛错,避免歧义; - 对
engine::前缀的内置函数调用,默认路由到default命名空间,防止引擎内置函数泄漏进 Worker 命名空间。
相关测试(tests/connection.test.ts)验证了:配置了命名空间时 register-worker 宣告载荷包含 namespace、未配置时省略、以及按调用序列化 per-call namespace 到InvokeFunction消息。
工程验证与测试
SDK 自带完善的测试体系(sdk/packages/node/iii-browser/tests):
- 单元测试:
connection.test.ts(连接状态机、致命/非致命命名空间冲突、重连行为)、triggers.test.ts、trigger-types.test.ts、channels.test.ts、helpers.test.ts、exports.test.ts(子路径导出完整性); - 集成测试(
tests/integration):triggers.test.ts、trigger-type-lifecycle.test.ts、functions-available-trigger.test.ts、channels.test.ts等,通过vitest.integration.config.ts运行,验证 SDK 与真实引擎的端到端交互; - 类型级测试:
trigger-typing.type-check.ts、middleware-input-namespace.type-check.ts在编译期校验泛型推导与 RBAC 中间件输入结构。
若需要修改 API 文档文案或格式,应编辑源码 doc-comment(sdk/packages/node/iii-browser/src 下的 prose)或 docs/next/scripts 中的生成脚本,再重新生成本文对应的参考文档,而不是直接编辑自动生成的 docs/reference/sdk-browser.mdx.skill.md。
小结
III Browser SDK 用"一条 WebSocket 连接"统一了前端的函数注册、调用、触发、状态与流式通信:registerWorker负责连接与重连,registerFunction让浏览器函数可被后端反向调用,trigger配合TriggerAction实现同步 / 即发即忘 / 队列三种路由,registerTriggerType支持自定义事件映射,iii-browser-sdk/state与iii-browser-sdk/stream提供带原子更新和严格校验的共享数据能力,RBAC 类型体系则保证了多租户与安全接入。配合连接状态监听与命名空间语义,你可以构建出实时、可观测、健壮的前端 Worker 应用。
【免费下载链接】iiiEffortlessly compose, extend, and observe every service in real-time for the first time ever.项目地址: https://gitcode.com/GitHub_Trending/mo/iii
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考