尧图精选

Puter 键值存储之 `puter.kv.list()` 完全指南:键枚举、前缀匹配模式与游标分页

🕒 发布时间:2026/9/10 2:56:56 📁 来源:尧图网络
Puter 键值存储之puter.kv.list()完全指南键枚举、前缀匹配模式与游标分页【免费下载链接】puter The Internet Computer! Free, Open-Source, and Self-Hostable.项目地址: https://gitcode.com/GitHub_Trending/pu/puterputer.kv.list()是 Puter 键值存储KV Store的核心读取方法用于以字典序lexicographic order枚举当前用户在当前应用命名空间下的全部键key支持前缀模式过滤、值value带回、游标分页与流式迭代。本指南以官方 API 文档 src/docs/src/KV/list.md 为骨架结合 SDK 源码与测试完整讲解其语法、全部可选参数、五种可运行示例并深入其底层实现与计量模型帮助你安全、高效地在大型存储上做键扫描与分页查询。puter.kv.list()是什么在 Puter 中每个应用在每个用户账号下拥有自己独立的键值存储命名空间应用之间默认互不可见除非用户通过puter.perms.request(appData, …)显式授权参见 KV/set.md 中的说明。puter.kv.list()就是针对这个命名空间的全量键枚举接口返回当前应用在该用户键值存储中的全部键数组形式如果用户没有任何键返回空数组返回结果按键的**字典序字符串顺序**排序。它与其他 KV 方法共同构成完整的读写闭环set写入、get单键读取、del删除、incr/decr计数、expire/expireAt过期、update/add/remove对象路径操作、flush清空。在 SDK 中这些方法统一挂载在puter.kv模块上list的实现位于 src/puter-js/src/modules/kv/list.js模块定义见 src/puter-js/src/modules/kv/index.js。该 API 支持websites、apps、nodejs、workers四类平台文档 frontmatter 的platforms字段。语法与参数语法形式puter.kv.list()支持五种调用形式覆盖了从最简单到最复杂的全部场景puter.kv.list() puter.kv.list(pattern) puter.kv.list(returnValues false) puter.kv.list(pattern, returnValues false) puter.kv.list(options)其中options对象可以携带pattern、returnValues、limit、cursor、offset、includeTotal、fetchUntilFull、stream等属性。此外 SDK 还允许在任何位置参数形式后追加一个可选的optConfig配置对象见下文源码分析。patternString可选如果设置则只返回匹配该模式的键。模式是基于前缀的且*通配符只能出现在末尾abc与abc*都会匹配所有以abc开头的键例如abc、abc123、abc123xyz如果前缀本身需要匹配字面量*则把*放在末尾例如key**匹配所有以key*开头的键同理k*y*匹配k*y前缀默认值是*即匹配所有键。需要注意这里的模式永远是前缀匹配无论是否带末尾的*。这一点与 Events 主题subject恰好相反——Events 中kv:cart只监听这一个键你需要追加*才能扩大为前缀监听相关文档位于 src/docs/src/Events/ 目录。SDK 在发送请求前会对模式做一次规范化normalizeListPattern见 list.js非字符串或空串/纯空白被当作不传 pattern末尾的*被剥离后作为裸前缀发送*单独出现时等价于匹配一切因此根本不发送 pattern 字段。这一点有对应的单元测试覆盖见 kv.test.js 中list(*) matches everything, so no pattern is sent等用例。returnValuesBoolean可选设为true时返回数组中的元素是同时含key与value两个属性的对象即KVPair对象设为false默认时返回数组只包含键名字符串。optionsObject可选一个包含以下可选属性的对象属性类型说明patternString与位置参数pattern相同的前缀模式。returnValuesBoolean与位置参数returnValues相同true时返回KVPair对象数组。limitNumber单次调用最多返回的条目数。cursorString上一次调用返回的分页游标。把上一页返回的cursor原样传入即可获取下一页。offsetNumber在本页开始前跳过的条目数。不推荐使用——offset 越大请求越慢、越贵优先用cursor。最大值为5000且不能与cursor同时使用。includeTotalBoolean为true时结果会附带一个total字段表示匹配该查询的全部条目数跨所有页。该计数是计量metered的成本随存储规模增长——只在第一页请求一次避免在热点路径中使用。如果你只需要知道是否还有更多页检查cursor是否存在即可不要用计数。fetchUntilFullBoolean一页返回的条目数可能少于limit即使后面还有更多数据例如过期键被排除时。设为true时会在可能的情况下把本页补满到limit条。要求必须同时指定limit。streamBoolean为true时方法不再返回 Promise而是返回一个KVListPage对象的异步迭代器配合for await ... of使用。可与limit组合控制页大小或用cursor从上一位置恢复不能与offset组合。配合includeTotal时只有第一页携带total。需要特别注意的是只要在options中使用了limit、cursor、offset、includeTotal、fetchUntilFull中的任意一个返回值就会从普通数组变为KVListPage分页对象而stream: true则进一步把返回值变成异步迭代器。这一点在 SDK 源码中体现为paginated标志位的置位逻辑见 list.js并被测试用例list(options) copies every pagination option逐字段验证。返回值puter.kv.list()返回一个Promise解析结果取决于调用方式键数组string[]即当前应用在当前用户下的全部键returnValues为false且未使用任何分页选项时KVPair对象数组每个对象含key与value两个属性returnValues: true时KVListPage对象在options中使用limit、cursor、offset、includeTotal、fetchUntilFull中任意一项时返回。如果用户没有任何键返回空数组。KVListPage对象KVListPage是分页结果的标准信封其完整定义见官方文档 src/docs/src/Objects/kvlistpage.md 与 SDK 类型声明 src/puter-js/src/modules/kv/types.js属性类型说明itemsArray本页条目。returnValues为false时是键名字符串数组为true时是KVPair对象数组。cursorString可选用于获取下一页的游标。只有当还有更多结果时才存在。把该值传给下一次puter.kv.list()调用即可拿到下一页。totalNumber可选匹配该查询的跨页总条目数。仅当请求设置了includeTotal: true时才存在。计算它是计量操作成本随存储规模增长——只在第一页请求一次避免热点路径只需判断是否有更多页时检查cursor即可。分页遍历规则进行分页遍历时一直迭代到结果中不再有cursor为止一页的条目数可能少于limit但后面仍有更多页——永远不要用items.length limit作为列表结束的信号cursor存在才代表还有下一页。这一约定同样写进了全仓库的分页规范 doc/pagination.mdPages may be short. Post-query filtering (TTL expiry, permission checks) can shrink a page belowlimit— or even to zero — while acursoris still returned.无分页全量列表的兼容性全量非分页列表仍然解析为普通数组因此既有代码不受影响——SDK 底层会改为逐页获取再拼接返回底层每次请求携带 SDK 默认页大小SDK_PAGE_LIMIT 1000并自动开启fetchUntilFull见 list.js 与 list.js但全量列表仍然会读取整个存储每一页都是计量操作所以在大型存储上裸调list()会变慢、变贵。当一次全量列表跨越多个页面时SDK 会通过console.warn输出一次性警告每个 SDK 实例只提示一次见nudgeOnce机制list.js官方建议优先使用stream: true或显式的limit/cursor分页并用pattern收窄扫描范围。流式迭代stream: truestream: true时方法返回KVListPage对象的异步迭代器可以配合for await ... of逐页消费for await (const page of puter.kv.list({ pattern: log:*, stream: true })) { for (const key of page.items) { console.log(key); } }完整示例以下五个示例直接取自官方文档 src/docs/src/KV/list.md覆盖了从入门到进阶的全部用法均可直接复制到带script srchttps://js.puter.com/v2//script的 HTML 页面中运行。示例一获取当前应用键值存储中的全部键html body script srchttps://js.puter.com/v2//script script (async () { // (1) Create a number of key-value pairs await puter.kv.set(name, Puter Smith); await puter.kv.set(age, 21); await puter.kv.set(isCool, true); puter.print(Key-value pairs created/updatedbrbr); // (2) Retrieve all keys const keys await puter.kv.list(); puter.print(Keys are: ${keys}brbr); // (3) Retrieve all keys and values const key_vals await puter.kv.list(true); puter.print(Keys and values are: ${(key_vals).map((key_val) key_val.key key_val.value)}brbr); // (4) Match keys with a pattern const keys_matching_pattern await puter.kv.list(is*); puter.print(Keys matching pattern are: ${keys_matching_pattern}br); // (5) Delete all keys (cleanup) await puter.kv.del(name); await puter.kv.del(age); await puter.kv.del(isCool); })(); /script /body /html示例二用游标分页每页 2 条html body script srchttps://js.puter.com/v2//script script (async () { // Create sample data for (let i 1; i 6; i) { await puter.kv.set(item-${i}, value-${i}); } puter.print(Created 6 key-value pairsbrbr); // Paginate with cursor (2 items per page) let currentCursor undefined; let page 1; do { const result await puter.kv.list({ limit: 2, returnValues: true, cursor: currentCursor, }); const items result.items; puter.print(bPage ${page}:/bbr); for (const item of items) { puter.print( ${item.key} ${item.value}br); } puter.print(br); currentCursor result.cursor; page; } while (currentCursor); puter.print(Done paginating.brbr); // Cleanup for (let i 1; i 6; i) { await puter.kv.del(item-${i}); } puter.print(Cleaned up sample data.); })(); /script /body /html注意do ... while (currentCursor)的终止条件只要上一页返回的cursor非空就继续取下一页这与迭代到没有cursor为止的约定完全一致。示例三利用字典序做时间序日志排序因为结果按字典序排序使用 ISO 8601 时间戳例如2025-03-15T10:00:00Z作为键前缀的一部分天然就实现了按时间顺序的输出非常适合日志、事件流水等场景html body script srchttps://js.puter.com/v2//script script (async () { await puter.kv.set(log:2025-03-15T10:00:00Z, { msg: third }); await puter.kv.set(log:2025-01-01T00:00:00Z, { msg: first }); await puter.kv.set(log:2025-02-14T08:00:00Z, { msg: second }); const logs await puter.kv.list(log:*); puter.print(Sorted keys: br/); puter.print(logs.join(br/)); // Cleanup await puter.kv.del(log:2025-03-15T10:00:00Z); await puter.kv.del(log:2025-01-01T00:00:00Z); await puter.kv.del(log:2025-02-14T08:00:00Z); })(); /script /body /html示例四数字键用零填充保证排序正确字典序下数字会按字符排序1, 10, 100, 2, 20而不是按数值排序。把数字补零到固定宽度001, 002, 010, 100即可得到正确的数值顺序html body script srchttps://js.puter.com/v2//script script (async () { // Wrong — will sort as 1, 10, 100, 2, 20 await puter.kv.set(item:1, ...); await puter.kv.set(item:10, ...); await puter.kv.set(item:2, ...); // Correct — zero-pad to a fixed width await puter.kv.set(item:001, ...); await puter.kv.set(item:002, ...); await puter.kv.set(item:010, ...); await puter.kv.set(item:100, ...); const items await puter.kv.list(item:*); puter.print(Items with zero-padding: br/); puter.print(items.join(br/)); // Cleanup await puter.kv.del(item:1); await puter.kv.del(item:10); await puter.kv.del(item:2); await puter.kv.del(item:001); await puter.kv.del(item:002); await puter.kv.del(item:010); await puter.kv.del(item:100); })(); /script /body /html示例五用前缀模式设计类查询过滤键设计即查询计划KV 没有通用查询语言——键设计就是你的查询计划。通过把同一条数据冗余写入多个前缀友好的键每个读路径就变成了一次简单的前缀查询html body script srchttps://js.puter.com/v2//script script (async () { const orders [ { id: 0001, status: pending, customer: alice, total: 48 }, { id: 0002, status: shipped, customer: alice, total: 72 }, { id: 0003, status: pending, customer: bob, total: 15 }, ]; // In KV, key design is your query plan. // We store the same order under multiple prefixes so each read path // becomes a simple prefix query with puter.kv.list(). for (const order of orders) { await puter.kv.set(demo:order:by-id:${order.id}, order); await puter.kv.set(demo:order:by-status:${order.status}:${order.id}, order); await puter.kv.set(demo:order:by-customer:${order.customer}:${order.id}, order); await puter.kv.set(demo:order:by-status-customer:${order.status}:${order.customer}:${order.id}, order); } puter.print(bStored read paths/bbr); puter.print(demo:order:by-status:pending:*br); puter.print(demo:order:by-customer:alice:*br); puter.print(demo:order:by-status-customer:pending:alice:*brbr); const pendingOrders await puter.kv.list(demo:order:by-status:pending:*, true); puter.print(bQuery: status pending/bbr); pendingOrders.forEach(({ key, value }) { puter.print(${key} ${value.customer} ($${value.total})br); }); puter.print(br); const aliceOrders await puter.kv.list(demo:order:by-customer:alice:*, true); puter.print(bQuery: customer alice/bbr); aliceOrders.forEach(({ key, value }) { puter.print(${key} ${value.status} ($${value.total})br); }); puter.print(br); const alicePendingOrders await puter.kv.list(demo:order:by-status-customer:pending:alice:*, true); puter.print(bQuery: status pending AND customer alice/bbr); alicePendingOrders.forEach(({ key, value }) { puter.print(${key} order ${value.id} ($${value.total})br); }); puter.print(br); puter.print(bTakeaway/bbr); puter.print(With puter.kv.list(), filtering comes from key prefixes.br); puter.print(If you need another query path, add another prefix-friendly key.brbr); // Cleanup for (const order of orders) { await puter.kv.del(demo:order:by-id:${order.id}); await puter.kv.del(demo:order:by-status:${order.status}:${order.id}); await puter.kv.del(demo:order:by-customer:${order.customer}:${order.id}); await puter.kv.del(demo:order:by-status-customer:${order.status}:${order.customer}:${order.id}); } })(); /script /body /html这个示例演示了三种典型查询单条件status pending、单条件customer alice以及组合条件status pending AND customer alice全部通过不同的键前缀完成且都配合returnValues: true直接拿到完整对象。底层实现与调用链源码级剖析参数解析与模式规范化puter.kv.list()的实现src/puter-js/src/modules/kv/list.js支持位置参数、options 对象以及可选的尾随optConfig三种形态内部通过大量overloadJSDoc 声明公开签名这些签名是types/自动生成的唯一事实来源见 src/puter-js/src/modules/kv/index.js 中的注释当第一个参数是纯对象且没有第二、三参数时按options对象解析读取pattern、returnValues、stream及分页字段否则按位置参数解析字符串视为patterntrue视为returnValues对象视为optConfigoptions.optConfig还支持简写形式直接传入{ appUuid: u }这类对象即被识别为optConfig见 src/puter-js/src/modules/kv/lib/args.js 的isOptConfigShorthandstream: true时会先把stream从简写对象中剔除再作为optConfig传递。optConfig是每个puter.kv操作都接受的按次配置见 types.js 的KVOptConfigappUuid指向另一个应用的命名空间需要app-data:appUuid:kv:op权限通常由puter.perms.requestAppData()向用户申请disableSharing标记条目对当前应用私有对list无直接影响主要由set使用。returnValues为false时SDK 会在请求参数中加入as: keyspattern经过normalizeListPattern规范化后以裸前缀形式发送。最终请求通过utils.makeDriverMethod({ iface: puter-kvstore, method: list, puter: this.puter, readonly: true })发出——接口名是puter-kvstore方法名是list且标记为只读操作见 list.js。三种执行路径从 list.js 可以清晰看到list()的三种执行路径流式路径stream: true客户端直接拒绝与offset组合抛出{ code: invalid_request }未显式指定limit时自动使用SDK_PAGE_LIMIT 1000并开启fetchUntilFull通过iteratePages返回异步生成器逐页跟随cursor。显式分页路径只要limit/cursor/offset/includeTotal/fetchUntilFull中任意一项存在就发单次请求原样返回后端给出的单个KVListPage。无界全量路径兼容旧行为SDK 替调用方逐页请求每页limit: 1000fetchUntilFull: true再用fetchAllPages拼接成普通数组返回。跨页时会触发一次性的全量扫描控制台警告。客户端分页引擎全仓库的列表类 API 共享同一套客户端分页引擎 src/puter-js/src/lib/pagination.jsiteratePages(fetchPage, opts)异步生成器沿cursor迭代直到其消失includeTotal只在首个请求上发送总数不随页变化且成本随条目数增长若后端忽略分页参数而返回裸数组则该数组被当作唯一的一页保证旧后端兼容fetchAllPages(fetchPage)消费全部页面并把items拼接成一个大数组返回。这也解释了全量list()为何仍然可用但代价高昂它是在客户端完成的逐页拉全量每一页网络请求都是计量操作。网络层的传输契约后端统一遵循 doc/pagination.md 定义的分页网络契约请求可携带limit、cursor不透明续传令牌null表示请求第一页、offset遗留/不推荐不能与cursor组合、includeTotal分页响应是标准信封{ items: [...], cursor: …, total: 123 }其中cursor仅在还有更多页时存在total仅在设置了includeTotal时存在。游标是不透明的 base64 编码 JSON由后端 src/backend/util/pagination.ts 生成与消费DynamoDB 后端包装LastEvaluatedKeySQL 后端包装键集keyset位置——(sortValue, id)形式的最后一行并通过WHERE (col, id) (?, ?) ORDER BY col, id向后寻位。这正是offset 越大越慢、游标分页才是正道的技术根源。测试如何锁定行为SDK 的单元测试 src/puter-js/src/modules/kv/kv.test.js 通过伪造XMLHttpRequest精确锁定每一次/drivers/call的网络载荷其中与list相关的用例第 515–702 行验证了list()发送{ as: keys }仅要键list(true)不带as: keys要键值对list(abc*)剥离末尾通配符发送裸前缀abclist(*)干脆不发送patternlist(k**)保留前缀中的字面量*list()会跟随游标并拼接出完整列表两页[a,b][c]恰好两次请求第二次携带cursor: c2后端返回裸数组时被当作完整列表单次请求全量列表跨多页时只警告一次warns once when a full listing spans multiple pages单页内不警告includeTotal只警告一次且stream模式下includeTotal只随首个请求发送stream: true与offset组合在客户端即被拒绝code: invalid_requestlist(options)会把全部六个分页/返回选项逐字段透传pattern 被规范化为p。性能与计量metering注意事项puter.kv.list()的一切昂贵特性都与计量模型相关官方文档与源码注释反复强调以下几点务必在设计中遵守每个分页请求都是计量操作。裸调list()会读取整个存储页数越多总成本越高大存储上会显著变慢。优先stream: true或显式limit/cursor分页并用pattern把扫描范围收窄到必要前缀。includeTotal是计量计数成本随匹配条目数增长。只在第一页请求一次配合stream时 SDK 也会自动这样做避免放在热点路径判断是否还有下一页应检查cursor而不是数总数。offset越大越贵后端需要跳过越来越多行最大5000且不能与cursor组合一律用cursor做深分页。警惕过期键导致的短页TTL 过期过滤会让一页少于limit条却仍有下一页所以绝不能以items.length limit判断结束。这些警告在 SDK 中以每个实例一次的方式通过console.warn输出nudgeOnce机制list.js不会刷屏但值得重视。相关键值存储能力与限制围绕list()Puter KV 还有一组配套能力与硬性限制详见 src/docs/src/KV/ 目录下的各方法文档写入/读取puter.kv.set(key, value, expireAt?)、puter.kv.get(key)键不存在时解析为undefined、puter.kv.del(key)set还支持批量与disableSharing私有标记计数puter.kv.incr(key, amount?)/puter.kv.decr(key, amount?)限定 64 位有符号整数且计数只能精确到±9,007,199,254,740,991Number.MAX_SAFE_INTEGER过期puter.kv.expire(key, ttlSeconds)、puter.kv.expireAt(key, timestamp)对象路径操作puter.kv.update(key, pathMap)、puter.kv.add(key, value)、puter.kv.remove(key, ...paths)清空puter.kv.flush()别名puter.kv.clear尺寸限制键最大1 KB、值最大400 KB对应 SDK 公开常量puter.kv.MAX_KEY_SIZE1024 字节与puter.kv.MAX_VALUE_SIZE399 * 1024字节见 src/puter-js/src/modules/kv/lib/validate.js 与官方文档 MAX_KEY_SIZE.md、MAX_VALUE_SIZE.md。超限的写入会在客户端直接抛出稳定的{ message, code }错误对象。在 CLI 中调试键值存储仓库自带的 Puter CLIsrc/cli提供了交互式 KV 调试工具puter kv connect identifier会打开一个针对指定应用键值存储的 REPL其中list方法以list([pattern], [values])形式暴露等价于puter.kv.list实现见 src/cli/src/commands/kv.js 与 src/cli/src/lib/kvbind.js。连接时 CLI 会通过一次kv.list({ limit: 101, fetchUntilFull: true })探测存储规模并显示在横幅中超过 100 个键显示100 keys并且 REPL 会自动await结果再回显方便直接验证前缀模式与分页行为。小结puter.kv.list()是 Puter KV 存储的查询入口但它只有一种查询维度——字典序 前缀匹配。掌握以下要点即可在生产中安全使用用pattern收窄扫描范围用零填充或 ISO 时间戳设计键以获得正确的排序语义大型存储上优先stream: true或显式limit/cursor分页用cursor判续页避免裸list()全量扫描includeTotal与offset都是计量/低效操作前者只在第一页用一次后者尽量不用把查询计划做进键前缀里让每个业务查询都变成一次廉价的前缀枚举。结合本文给出的官方示例、SDK 源码路径list.js、pagination.js与测试用例kv.test.js你可以放心地把puter.kv.list()用于日志扫描、订单过滤、时间线枚举等各类实际场景。【免费下载链接】puter The Internet Computer! Free, Open-Source, and Self-Hostable.项目地址: https://gitcode.com/GitHub_Trending/pu/puter创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
上一篇/下一篇内容由系统自动关联 返回资讯列表 →