尧图网站设计 尧图网站设计YAOTU DESIGN
ARTICLE DETAIL

资讯详情

深耕网站设计与一线实操的经验洞察。

Puter Events API 系列:`puter.events.list()` 持久化订阅查询完全指南

Puter Events API 系列:`puter.events.list()` 持久化订阅查询完全指南 Puter Events API 系列puter.events.list()持久化订阅查询完全指南【免费下载链接】puter The Internet Computer! Free, Open-Source, and Self-Hostable.项目地址: https://gitcode.com/GitHub_Trending/pu/puterputer.events.list()是 Puter Events APIBeta中用于查询调用方所持有的持久化订阅persistent subscriptions的入口它解决的是我或当前应用到底订阅了哪些主题、哪些订阅已停止投递及原因这一运维与调试刚需。本文以官方文档 Events/list 为骨架结合 SDK 源码、后端控制器 与 存储层实现 展开读完你将掌握该 API 的三种调用形态全量数组 / 单页信封 / 流式迭代、分页游标契约、订阅字段语义与错误处理并能直接照抄文中的可运行示例。该文档所描述的 Events API 目前处于 Beta 阶段事件形状、限额与行为可能在版本之间发生变化生产接入时请以当前部署版本为准。一、API 定位它列出什么、不列出什么puter.events.list()专门列出通过puter.events.onPersistent()创建的持久化订阅。理解它之前需要先区分两类订阅持久化订阅persistent由onPersistent()创建作为服务端数据行存在与客户端连接生命周期无关。它们是list()的唯一枚举对象。会话订阅session由onLocal()创建随承载它的 socket 连接而存在不落盘、不可路由。它们不会出现在list()结果中——正如文档所述they live with the connection and are not stored anywhere。这一点在后端有清晰的代码佐证会话订阅存放在以 socket 为 key 的 Redis 集合中见 limits.ts 中EVENTS_SESSION_SUBSCRIPTIONS_PER_SOCKET的注释而持久化订阅则是event_subscriptions数据表中的行见 DurableSubscriptionStore.ts由listDurable服务方法按持有者索引读取EventsService.ts。**可见性边界scoping**是另一个必须理解的核心语义应用视角一个应用只能看到它自己创建的订阅。账户会话视角代表账户行动的会话可以看到该账户名下全部订阅包括某个已经消失的应用遗留下来的订阅。文档明确指出the account is where a stray subscription gets revoked from——当某个应用被删除后其遗留的订阅只能通过账户会话的list()发现并从账户侧用puter.events.unsubscribe()撤销。后端在listDurable中通过actor.effectiveApp决定作用域应用上下文把appUid传给存储层做索引过滤而账户上下文省略appUid即可跨应用查看EventsService.ts、DurableSubscriptionStore.ts。二、语法与参数puter.events.list() puter.events.list(options)optionsObject可选支持以下字段它们与 ListPaginationOptions 这一 Puter 列表类 API 的通用分页约定一致参数类型默认值说明limitNumber50单次请求返回的最大订阅数服务端硬上限 200cursorString | null—上一页返回的续传令牌只要显式传入包括null返回值就切换为单页信封includeTotalBooleanfalse在信封中追加total总数建议只在第一页请求订阅越多其成本越高streamBooleanfalse为true时返回页信封的异步迭代器而非 Promise关于limit的50/200两档数值源码侧有精确对应SDK 端默认页大小与服务端一致DurableSubscriptionStore.ts 中DURABLE_LIST_DEFAULT_LIMIT 50、DURABLE_LIST_LIMIT_CAP 200控制器在入口处通过normalizeLimit(query.limit, { cap: DURABLE_LIST_LIMIT_CAP })收敛非法输入EventsController.ts。includeTotal在服务端仅接受字面量trueEventsController.ts而 SDK 侧若传入非布尔值会直接抛出invalid_request的PuterJSErrorlist.js。三、返回值三种形态与分页契约1. 无分页参数 → 全量数组Promise不传cursor/includeTotal时SDK 在底层逐页拉取page by page最终 resolve 为一个包含全部订阅的普通数组const subs await puter.events.list();这由 list.js 中的fetchAllPages实现只要响应里还有cursor就继续请求下一页直到cursor缺失为止。2. 传cursor或includeTotal→ 单页信封Promiseconst page await puter.events.list({ limit: 100, cursor: ... }); // page: { items: [...], cursor?: ..., total?: number }信封字段语义与 ListPage 一致items本页订阅数组。cursor仅当后面还有更多页时存在用它发起下一次请求即可继续翻页。total仅当请求了includeTotal时存在表示作用域内订阅总数。注意 SDK 的一个实现细节只要显式传入了cursor连null也算就走单页分支。源码通过hasOwnProperty.call(opts, cursor)判断键是否存在list.js因此list({ cursor: null })得到的是从第一页开始的单页信封而不是全量数组——这是与直觉略有出入、需要留意的行为。3.stream: true→ 异步迭代器for await (const page of puter.events.list({ stream: true })) { for (const row of page.items) { /* ... */ } }stream分支调用iteratePageslist.js返回的每个信封结构与单页模式相同。includeTotal会在第一个流式页上附带total见 ListStreamOptions。4. 关键警告页面可能是不满的Pages may be short. Never readitems.length limitas the end of the list; iterate untilcursoris absent.即绝不能把items.length limit当作列表结束的标志。服务端存储层采用的是基于id的 keyset 分页每次查询LIMIT ?实际取limit 1行用于探测下一页超过则返回游标DurableSubscriptionStore.ts。因此判断是否还有下一页的唯一依据是响应中cursor是否存在这与includeTotal不同——后者需要额外执行一次COUNT(*)这也是文档提示它成本更高的底层原因DurableSubscriptionStore.ts。四、订阅对象的字段语义list()返回的每个订阅对象就是onPersistent()返回的同一对象。除subId、subject、anchor、match、op、delivery、handlerName、appUid、createdAt、expiresAt等基本信息外文档重点强调三个需要理解其设计意图的字段contextKeys/contextHash描述而非暴露contextKeysArray | null存储的context中设置的键名列表排序后。contextHashString | null整个context的哈希值。核心设计context的值永远不会被返回。原因是context中可能存放 API key 等敏感凭据而list()恰好是应用可以反复调用的枚举接口。后端在projectContext中只解析出键名并计算哈希EventsService.ts如果存储的 context 无法按 JSON 解析仍会返回哈希而不是让列表失败。由于哈希会随任何值的变化而改变它足以用来区分两个订阅的 context 是否不同察觉某个订阅是否被重新创建例如值被刷新过。suspendedAt/suspendedReason停摆诊断suspendedAtNumber | null订阅被挂起的时间戳null表示未挂起。suspendedReasonString | null挂起原因null表示未挂起。当订阅停止投递但未被删除时这两个字段就派上用场。文档列出的四种原因在 EventsService.ts 中均有对应的挂起路径原因含义相关源码佐证handler_not_found订阅引用的处理程序events worker不存在了例如依赖的 handler 被移除时其依赖订阅随之挂起EventsService.tsfailures投递连续失败超过阈值EventsService.tsno_credit账户额度不足投递被暂停充值后可恢复EventsService.tspermission_revoked授权如共享句柄权限被撤销订阅失去观看资格EventsService.ts值得注意的是permission_revoked导致的挂起永远不会恢复同意观看的授权只能通过重新订阅重建因此这类行只会保留有限时间供持有者在list()中看到它为什么停了随后被清理——SUSPENDED_ROW_TTL_DAYS 30limits.ts。这也从侧面说明list()是发现并处置僵死订阅的官方观察窗口。targets预留的投递目标targetsArray可能包含push——订阅时接受该值但当前尚无任何投递通道真正使用它属于预留能力。后端在toView中会给出默认会话目标EventsService.ts应用侧在使用targets做判断时应意识到它不等于一定在投递。五、错误处理list()返回的 Promise 拒绝时错误对象形如{ message, code }。文档声明的三种错误码与产生位置错误码触发条件源码对应too_many_requests超过列表接口的调用预算EVENTS_LIST_LIMIT userWindow(events:list, 120)即每用户每分钟 120 次limits.tsevents_disabledEvents 功能在服务端被关闭见listDurable首行的if (!this.enabled) throw disabled()EventsService.tsevents_failed服务端返回了 SDK 无法解析的响应SDK 请求层的兜底错误SDK 侧测试也验证了too_many_requests会被正确传播events.test.js。实践中建议对列表类调用做指数退避重试并将events_failed视为需要升级处理的异常情况。六、完整示例可直接复制运行示例 1列出当前账户正在关注的所有主题html body script srchttps://js.puter.com/v2//script script (async () { const dir ~/${puter.randName()}; await puter.fs.mkdir(dir); const sub await puter.events.onPersistent({ subject: fs:${dir}, context: { label: inbox }, }); for (const row of await puter.events.list()) { puter.print(${row.subject} — ${row.delivery}); puter.print( (context: ${row.contextKeys?.join(, ) ?? none})br); } await puter.events.unsubscribe(sub.subId); })(); /script /body /html该示例演示了完整闭环先创建一个持久化订阅subject 指向新创建的目录context 带label: inbox再全量列出所有订阅并打印每条的主题、投递方式与 context 键名注意打印的是contextKeys而非值最后用sub.subId撤销。由于示例处于账户会话上下文输出会包含该账户下全部应用的持久化订阅。示例 2找出已停止的订阅及原因html body script srchttps://js.puter.com/v2//script script (async () { for await (const page of puter.events.list({ stream: true })) { for (const row of page.items) { if (!row.suspendedAt) continue; puter.print(${row.subject} stopped: ${row.suspendedReason}br); } } puter.print(donebr); })(); /script /body /html这里示范了推荐的大列表处理姿势用stream: true流式消费所有页逐行筛选suspendedAt非空的订阅并打印挂起原因。它天然遵循了以cursor缺失为结束条件的分页契约不需要手工维护游标。七、联动阅读list()只是 Events API 持久化订阅生命周期的读侧建议结合以下文档与源码形成完整认知订阅的创建与返回对象 onPersistent.md会话订阅不会出现在列表里 onLocal.md撤销订阅含撤销遗留订阅的场景 unsubscribe.md拉取错过的历史事件fetch()与list()共享信封分页风格 fetch.md 及其 SDK 实现 fetch.js后端路由与限额定义 EventsController.ts、limits.ts存储层 keyset 分页与作用域实现 DurableSubscriptionStore.ts集成测试验证分页与作用域行为 durable.integration.test.ts、DurableSubscriptionStore.integration.test.ts八、速查清单list()无参数返回全量订阅数组传cursor/includeTotal返回单页信封stream: true返回页信封异步迭代器。显式传cursor: null也会走单页信封分支。判断是否还有下一页的唯一依据是cursor是否存在不要用items.length limit。limit默认 50、上限 200includeTotal成本随订阅数量增长只在第一页请求。context的值永不返回只能看到contextKeys与contextHash哈希变化可用来探测订阅是否被重建。suspendedReason取值handler_not_found、failures、no_credit、permission_revoked其中permission_revoked不可恢复行会在约 30 天后被清理。targets中的push目前仅被接受、尚无投递实现。列表调用预算每用户每分钟 120 次超出抛too_many_requests。【免费下载链接】puter The Internet Computer! Free, Open-Source, and Self-Hostable.项目地址: https://gitcode.com/GitHub_Trending/pu/puter创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
返回列表