` 深度指南:列出账号应用、分页游标与流式遍历全解析)
Puterputer.apps.list()深度指南列出账号应用、分页游标与流式遍历全解析【免费下载链接】puter The Internet Computer! Free, Open-Source, and Self-Hostable.项目地址: https://gitcode.com/GitHub_Trending/pu/puterputer.apps.list()是 Puter JavaScript SDK 中用于获取当前用户拥有或有权限访问的应用列表的核心方法广泛适用于网站、Web App、Node.js 与 Workers 等各类客户端场景。本文以官方文档 list.md 为骨架结合 SDK 源码实现 与 后端 AppDriver系统讲解其调用语法、六类可选参数、三种返回形态纯数组 / 分页信封 / 异步迭代器、底层分页协议及完整可运行示例帮你彻底掌握应用列表的获取与大规模遍历方案。方法定位list 返回什么根据 list.md 的说明puter.apps.list()返回一个包含所有归属于当前用户、且当前应用有权访问的App对象数组。若用户当前没有任何应用则返回空数组。官方文档 frontmatter 声明该方法支持的平台为platforms: [websites, apps, nodejs, workers]即四类 Puter 客户端均可直接调用无需额外鉴权配置——SDK 会自动携带当前会话身份。从 SDK 源码看真正决定能列出什么的是底层驱动请求中的过滤谓词const select utils.makeDriverMethod({ iface: puter-apps, driver: es:app, method: select, argNames: [uid], puter, readonly: true, }); const base { predicate: [user-can-edit] };这段代码位于 list.js 的实现SDK 通过puter-apps接口调用es:app驱动的select方法并使用user-can-edit谓词过滤——即返回当前用户拥有编辑权拥有或被授予权限的应用集合。调用被标记为readonly: true说明它属于纯查询操作不会产生副作用或计费写入。快速上手创建 3 个应用并列出基础语法puter.apps.list(); puter.apps.list(options);两种调用形态中options均为可选。不带任何参数时方法返回一个 Promiseresolve 为一个扁平数组语义最直观const apps await puter.apps.list(); console.log(apps.map(app app.name));官方完整示例含创建与清理原文档给出了一个先造数据、再列表、最后清理的完整自洽示例这里完整继承在浏览器 HTML 页面中引入 Puter SDK v2 后执行SDK 亦可通过 src/puter-js 本地构建使用html body script srchttps://js.puter.com/v2//script script (async () { // (1) Generate 3 random app names let appName_1 puter.randName(); let appName_2 puter.randName(); let appName_3 puter.randName(); // (2) Create 3 apps await puter.apps.create(appName_1, https://example.com); await puter.apps.create(appName_2, https://example.com); await puter.apps.create(appName_3, https://example.com); // (3) Get all apps (list) let apps await puter.apps.list(); // (4) Display the names of the apps puter.print(JSON.stringify(apps.map(app app.name))); // (5) Delete the 3 apps we created earlier (cleanup) await puter.apps.delete(appName_1); await puter.apps.delete(appName_2); await puter.apps.delete(appName_3); })(); /script /body /html示例覆盖了 create、list、delete 三个方法其中puter.randName()用于生成随机应用名puter.print()用于把结果显示到 UI。注意最后的 delete 步骤是良好的清理习惯避免测试数据残留到账号中。options参数全解当传入对象形态参数时SDK 会将其中的分页相关字段limit/offset/cursor/includeTotal/stream单独解构出来用于驱动请求其余字段stats_period、icon_size作为params原样透传给后端const { limit, offset, cursor, includeTotal, stream, ...params } opts; if ( isObjectForm ) base.params params; if ( limit ! undefined ) base.limit limit;以下逐项说明各参数的取值与语义stats_period可选String指定open_count打开次数与user_count访问用户数的统计周期。可能取值如下默认值为all全时段累计取值含义today今天yesterday昨天7d最近 7 天30d最近 30 天this_month本月last_month上月this_year今年last_year去年month_to_date本月至今year_to_date今年至今last_12_months最近 12 个月all默认全时段从后端实现看统计周期对应 AppDriver.js 中的统计查询逻辑SDK 将 stats 选项打包在params之下源码注释明确说明 puter-js 的makeDriverMethod/Apps.get会把 options 打包到params后端同时兼容顶层扁平形状const stats_period params.stats_period ?? rest.stats_period; const stats_grouping params.stats_grouping ?? rest.stats_grouping; const needsStats params.stats ! false (stats_period || stats_grouping); // Detailed period/grouping is per-app only — skip the batch cache // and go straight to the live query. const hasDetailed Boolean(stats_period || stats_grouping);可以推断未指定stats_period时后端走默认的缓存批量统计路径性能更优指定了具体周期后则进入getAppStatsDetailed的实时明细查询路径换取按周期的精确计数。icon_size可选Integer返回应用图标的尺寸像素决定App.icon字段输出多大的 Data URL 图片。可选值null、16、32、64、128、256、512。默认值为null表示返回图标原始尺寸。当需要渲染列表缩略图时传64或128可显著降低传输体积。limit可选Number单次调用最多返回的应用数量。在流式与游标分页模式下它控制每一页的大小。offset可选Number跳过指定数量的应用后开始返回用于传统偏移量分页。官方文档明确提示遍历大型列表时优先使用cursor因为 offset 分页在大数据量下性能较差且容易因数据变动产生重复/遗漏。cursor可选String显式启用游标分页。第一次请求传null代表第一页之后把每次响应中的cursor字段传给下一次调用直到响应中不再包含cursor说明已到最后一页。注意只要options对象里出现了cursor键即使值为null返回形态就会从数组切换为分页信封对象——这是 SDK 源码中通过hasOwnProperty.call(opts, cursor)精确判断的const hasCursor Object.prototype.hasOwnProperty.call(opts, cursor);includeTotal可选Boolean为true时分页响应会额外携带total字段表示该用户应用的总数。官方实现与文档一致地指出只有请求包含cursor含 null、offset或includeTotal之一时返回值才是分页信封同时从 pagination.js 的实现可以看出includeTotal只在第一页请求上发送因为总数统计在条目越多时成本越高且总数不会随翻页变化。stream可选Boolean为true时方法不再返回 Promise而是返回一个异步迭代器可直接配合for await ... of逐页消费。可与limit组合控制页大小也可传cursor从指定页继续不能与offset组合使用若同时传入会抛出PuterJSError错误码invalid_requestif ( stream true ) { if ( offset ! undefined ) { throw new PuterJSError( offset cannot be combined with stream; pass cursor to resume from a position., invalid_request, ); } // ...async generator 逐页 yield }开启includeTotal时只有第一页会携带total。返回值三种形态与分页信封list()的返回形态由是否携带分页参数决定这是理解本方法的关键形态一纯数组未携带任何分页参数返回 Promiseresolve 为所有App对象组成的数组。官方文档特别强调不带分页参数的请求仍返回完整的扁平数组旧代码完全不受影响——SDK 只是在底层把它拆成了逐页请求再合并// Unbound listing: fetch page by page under the hood so no single request // carries the whole result, then return the legacy array. return fetchAllPages(fetchPage).then(items addUserIterationToApps(puter, items));对应 fetchAllPages 的实现其内部本质是把所有分页信封的 items 依次拼接async function fetchAllPages (fetchPage) { const items []; for await ( const page of iteratePages(fetchPage) ) { items.push(...(page.items ?? [])); } return items; }这就保证了无论请求是否传limit最终对调用方呈现的都是传统数组形态既避免了单请求携带全部结果的压力又不破坏既有集成。形态二分页信封对象请求包含cursor含 null、offset或includeTotal之一Promise resolve 为一个 page 对象包含字段类型说明itemsArray本页的App对象数组cursorString可选仅在还有更多页时出现将其传给下一次调用即可获取下一页totalNumber可选用户应用总数仅在设置includeTotal时出现在 SDK 源码中当请求命中分页参数时select的返回若为信封形态非数组且含items则原样返回该信封const result await select(driverArgs); if ( result !Array.isArray(result) Array.isArray(result.items) ) { addUserIterationToApps(puter, result.items); return result; }若后端返回的是裸数组例如旧版后端忽略分页参数SDK 也会优雅兼容。形态三异步迭代器stream: true返回 async iterator逐页产出 page 对象。官方示例如下page.items上的每个元素仍是App对象for await (const page of puter.apps.list({ stream: true })) { for (const app of page.items) { console.log(app.name); } }其底层由 iteratePages 驱动——一个标准的 async generator从cursor: null发起请求yield 当前页后依据page.cursor是否存在决定是否继续拉取async function* iteratePages (fetchPage, opts {}) { let pageParams { cursor: opts.cursor ?? null, ...(opts.includeTotal true ? { includeTotal: true } : {}), }; while ( true ) { const result await fetchPage(pageParams); const page Array.isArray(result) ? { items: result } : (result ?? { items: [] }); yield page; if ( ! page.cursor ) return; pageParams { cursor: page.cursor }; } }注意这里对忽略分页参数的后端做了兼容后端返回裸数组时该数组被当作唯一的一页若完全无返回则视为空页。另外无论哪种形态SDK 都会对每个应用调用addUserIterationToApps注入用户遍历辅助方法使返回的App对象可以继续调用app.users()/app.getUsers()。返回的App对象字段说明list()返回的每个元素都是标准 App 对象核心字段如下字段类型说明uidStringPuter 在应用创建时生成的全局唯一标识nameString应用名称应用 API 调用中的唯一键iconString应用图标的 Data URLbase64 编码图片尺寸受icon_size影响descriptionString应用描述titleString应用显示标题maximize_on_startBoolean启动时是否最大化窗口默认falseindex_urlString应用启动时加载的入口文件 URLcreated_atString创建时间格式YYYY-MM-DDTHH:MM:SSZbackgroundBoolean是否作为后台应用运行默认falsefiletype_associationsArray应用可打开的文件类型形如[.txt, image/png]目录关联用.directoryopen_countNumber应用被打开的次数设置stats_period后为该周期内次数user_countNumber有权访问该应用的用户数设置stats_period后为该周期内统计metadataObject应用自定义元数据任意键值对对列表元素做用户遍历列表接口返回的App对象额外支持两种用户迭代方法这正是前文提到的addUserIterationToApps所注入的// 逐页迭代全部有权用户默认每页 100可传 pageSize for await (const user of app.users()) { console.log(user); // { username, user_uuid, user_email? } } // 按 limit/offset 获取一页用户 const users await app.getUsers({ limit: 2, offset: 0 });其中user_email仅当用户授予了当前应用user:uuid:email:read权限例如通过puter.perms.request(email)时才会出现否则被省略若用户授权但没有登记邮箱则可能为null。这使列出应用 → 遍历应用用户成为一个完整的工作流。实战五种典型调用组合1. 简单列出全部应用名const apps await puter.apps.list(); console.log(apps.map(app ({ name: app.name, uid: app.uid })));2. 游标手动翻页含总数let cursor null; do { const page await puter.apps.list({ cursor, // 首轮传 null 表示第一页 limit: 10, includeTotal: true, }); for (const app of page.items) { console.log(page.total, app.name); // 仅第一页可读 total } cursor page.cursor; // 无 cursor 即最后一页 } while (cursor);3. 流式遍历全部应用for await (const page of puter.apps.list({ stream: true, limit: 20, })) { for (const app of page.items) { await doSomethingWith(app); } }4. 从某页继续断点续传// 第一次拿到 cursor const first await puter.apps.list({ cursor: null, limit: 20 }); // 后续进程恢复直接带着 cursor 继续 const next await puter.apps.list({ cursor: first.cursor, limit: 20 });5. 携带统计周期与图标尺寸const apps await puter.apps.list({ stats_period: 7d, // open_count / user_count 取最近 7 天 icon_size: 128, // 图标压缩到 128px }); for (const app of apps) { console.log(app.name, app.open_count, app.user_count); }边界行为与注意事项无应用时返回空数组[]而非报错或null。兼容性设计不带分页参数的调用行为与旧版本完全一致返回纯数组只是底层改为逐页拉取——官方文档原话Requests without pagination params keep returning the full list as a plain array, so existing code is unaffected — under the hood the SDK now fetches it page by page.offset与stream互斥同时传入会抛出invalid_request错误流式场景下要用cursor恢复位置。includeTotal成本总数统计成本随条目数量上升SDK 只在第一页请求发送includeTotal后续页不再重复统计。cursor键存在即切换形态即使显式传cursor: null返回值也是分页信封{ items, ... }而非扁平数组写代码时不要假设传null就返回数组。大列表首选 cursor官方明确提示 offset 仅适合小规模分页遍历大列表应使用cursor。相关实现与文档索引官方 API 文档原文src/docs/src/Apps/list.mdApp 对象属性与方法src/docs/src/Objects/app.md配套方法create、get、update、delete、checkNameSDK 端分页与列表实现src/puter-js/src/modules/apps/list.js、src/puter-js/src/lib/pagination.js后端统计/查询实现src/backend/drivers/apps/AppDriver.js通用分页信封协议说明{ items, cursor?, total? }doc/pagination.md综上puter.apps.list()在设计上同时照顾了简单列出与大规模遍历两类诉求日常场景直接await list()拿数组即可面对海量应用或需要增量同步时则可用cursor游标、includeTotal与stream异步迭代三件套实现稳健、可续传、后端友好的分页消费。【免费下载链接】puter The Internet Computer! Free, Open-Source, and Self-Hostable.项目地址: https://gitcode.com/GitHub_Trending/pu/puter创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考