资讯动态

PouchDB 本地文档(Local Documents)完全指南:原理、实践与源码解析

发布时间:2026/9/21 16:09:24 来源:尧图企业网站定制
PouchDB 本地文档Local Documents完全指南原理、实践与源码解析【免费下载链接】pouchdb:kangaroo: - PouchDB is a pocket-sized database.项目地址: https://gitcode.com/gh_mirrors/po/pouchdb本地文档Local documents是 PouchDB 与 CouchDB 中一类特殊文档专用于存储数据库自身的元数据。它们不参与复制、不占用版本历史却拥有普通文档所不具备的性能优势——复制的检查点、map/reduce 的视图进度记录都依赖它们。读完本文你将掌握_local/前缀的完整语义、本地文档与普通文档的行为差异、更新时的_rev约束以及 PouchDB 核心模块如何在内部使用本地文档实现高效增量同步。一、什么是本地文档_local/前缀在 PouchDB 中任何_id以_local/开头的文档都属于本地文档local doc。CouchDB 与 PouchDB 对这一约定完全兼容因此同一套代码在浏览器端、Node.js 端以及连接远程 CouchDB 服务器时行为一致。db.put({ _id: _local/foobar, someText: yo, this is my local doc! }).then(function () { return db.get(_local/foobar); });这段代码创建一个_id为_local/foobar的本地文档随后用get()读取。从 API 使用方式上看本地文档与普通文档几乎无异——put()、get()、remove()、bulkDocs()全部适用。在源码层面PouchDB 对本地文档的识别非常直接。pouchdb-merge包中的 isLocalId.js 给出了最核心的判断逻辑function isLocalId(id) { return typeof id string id.startsWith(_local/); }只要_id字符串以_local/开头整个 PouchDB 代码库就会把它当作本地文档对待。pouchdb-merge负责文档合并与修订树revision tree管理这一判断贯穿核心适配层的多条路径。二、本地文档的行为特征不复制、不可见、可读写官方文档明确列出了本地文档的四大特征理解它们是使用本地文档的前提行为说明不参与复制本地文档不会通过replicate()/sync()同步到其他数据库不能包含附件本地文档不支持_attachments不出现在枚举 APIallDocs()、changes()、query()都不会返回本地文档可正常读写put()/remove()/bulkDocs()可修改get()可读取换句话说本地文档只存在于那一个数据库内部与普通文档完全隔离不会混入正常的文档流。这一行为在测试中有大量佐证。例如 tests/integration/test.changes.js 中构造了{_id: _local/foo}与普通文档a、b、c、d混合的bulkDocs随后监听changes()本地文档不会被作为变更事件返回。db.info()的doc_count也只统计普通文档——tests/integration/test.basics.js 验证了这一点。PouchDB 对本地文档的不可见处理是刻意为之changes()流是复制的基础本地文档被排除在变化流之外自然也就不会被复制。这也是本地文档不复制这一特性在实现层面的直接体现。HTTP 适配器中的特殊处理当 PouchDB 连接远程 CouchDB 服务器时pouchdb-adapter-http 对本地文档的_id做了单独编码if (id.startsWith(_local/)) { return _local/ encodeURIComponent(id.slice(7)); }普通文档的_id会被整体encodeURIComponent而_local/前缀本身保留原样、只编码其后部分。这确保了 HTTP 请求能命中 CouchDB 正确的 REST 端点如GET /db/_local/foobar验证了_local/约定在服务端同样成立。三、创建、读取与删除与普通文档相同的 API1. 创建与读取创建本地文档只需在put()时以_local/开头命名_id。完整流程如下const db new PouchDB(mydb); // 创建 db.put({ _id: _local/settings, theme: dark, updatedAt: Date.now() }).then(function (res) { console.log(res.id); // _local/settings console.log(res.rev); // 类似 0-1 return db.get(_local/settings); }).then(function (doc) { console.log(doc.theme); // dark });2. 更新必须携带当前_rev与普通文档一样更新本地文档时必须携带最新的_rev否则会得到冲突409错误db.get(_local/settings).then(function (doc) { doc.theme light; return db.put(doc); // doc 中已包含最新的 _rev }).then(function (res) { console.log(res.rev); // 修订号递增形如 0-2 });3. 删除删除同样遵循_rev约束先取到文档再执行remove()db.get(_local/settings).then(function (doc) { return db.remove(doc); }).then(function (res) { console.log(res.ok); // true console.log(res.rev); // 0-0删除后返回 0-0 修订号 });4. 批量操作bulkDocs()同样支持本地文档且可与普通文档混合在一个批次中提交PouchDB 会为每个文档返回独立的处理结果db.bulkDocs([ {_id: _local/bar}, {_id: baz} ]).then(function (results) { // results.length 2每个元素含 {ok, id, rev} });tests/integration/test.basics.js 验证了混合批量写入的返回结构。5. 本地文档修订号的特殊形态0-x细心观察会发现本地文档的_rev永远以0-开头如0-1、0-2删除后更是直接返回0-0。这是因为本地文档没有版本历史详见下一节修订号只用于并发控制语义上与普通文档的1-x、2-x修订树不同。删除一个本地文档后_rev归零为0-0此时重新put()会被视为创建全新文档——tests/integration/test.local_docs.js 的 put after remove 用例验证了这一行为。四、性能特性无版本历史与自动压缩本地文档与普通文档最本质的性能差异在于本地文档没有版本历史。普通文档的每次更新都会在数据库里留下一个修订revision形成一棵修订树revision tree直到执行压缩compact才会清理旧版本。而本地文档只保存最新一个修订put()和get()更快不需要维护和遍历修订树磁盘占用更少没有历史版本需要存储相当于自动压缩永远只保留最新状态甚至比压缩后的普通文档更省空间压缩后的普通文档仍需保留每个文档的修订树主干。这也解释了上一节的0-x修订号形态没有历史就不存在1-x→2-x的链式递进只需一个单调递增的数字用于乐观并发控制。从源码结构看pouchdb-merge的 isLocalId.js 被用于控制修订合并路径对本地文档跳过修订树合并逻辑直接覆盖最新值这正是无版本历史、自动压缩特性的实现基础。五、PouchDB 内部如何使用本地文档本地文档最常见的用途是存储配置与元数据。PouchDB 的许多核心组件和插件都在使用它理解这些内部用法能帮你判断自己的应用场景是否适合本地文档。1. 复制检查点Checkpoint复制replication算法使用本地文档保存检查点记录复制到哪一条 seq 了。每次复制会话结束后检查点会写入源库与目标库两侧下次复制时从断点继续避免从头读取全部变更。检查点的_id由 pouchdb-generate-replication-id 生成它把源库 id、目标库 id、过滤函数与参数等拼接后计算 MD5再转换为_local/前缀的 idmd5sum md5sum.replace(/\//g, .).replace(/\/g, _); return _local/ md5sum;替换/和是因为这两个字符在 URL 和附件路径中不友好。检查点的读写逻辑位于 pouchdb-checkpointerwriteCheckpoint()会先更新目标库再更新源库history数组保留最近 5 次CHECKPOINT_HISTORY_SIZE 5复制会话的记录用于跨会话断点续传时的比较写入遇到 409 冲突会自动重试。检查点文档的结构大致为{ _id: _local/1DB6QfM3RDEOFoOwE65CpQ, session_id: ..., last_seq: 1234, history: [{last_seq: 1234, session_id: ...}], replicator: pouchdb, version: 1 }tests/unit/test.checkpointer.js 直接验证了这套机制checkpointer.id.startsWith(_local/)为真写入检查点后rev为0-1且history中记录了last_seq。之所以选择本地文档做检查点正是因为它不复制——检查点只是某一对数据库之间的同步进度复制检查点本身毫无意义且会造成噪音。2. map/reduce 视图进度_local/lastSeqmap/reduce 视图query()的后端实现使用_local/lastSeq记录视图已经消费到源库的哪一条 seq。在 pouchdb-abstract-mapreduce 的saveKeyValues()中var seqDocId _local/lastSeq; return view.db.get(seqDocId) .catch(defaultsTo({_id: seqDocId, seq: 0})) .then(function (lastSeqDoc) { // ...写入 emitted 的 key/value 文档 lastSeqDoc.seq seq; docsToPersist.push(lastSeqDoc); return view.db.bulkDocs({docs: docsToPersist}); });视图每次增量更新时把新一批 emit 结果与_local/lastSeq放在同一个bulkDocs批次中原子写入从而保证视图数据与进度标记的一致性。视图构建时createView.js则先get(_local/lastSeq)读取上次进度从断点继续索引。3. 其他内部用途_local/purgeSeqmap/reduce 中记录 purge清除进度_local/purgespurge()操作的日志记录见 pouchdb-core_local/compaction压缩compact任务的进度标记_local/_pouch_dependentDbs记录依赖该数据库的其他 PouchDB 内部数据库见 adapter.js。4.upsert工具函数由于内部大量读写本地文档PouchDB 提供了upsert(db, docId, diffFun)工具pouchdb-utils/src/upsert.js先get()文档不存在则以{}兜底把 diff 函数的结果put()回去若遇 409 冲突则递归重试。这一模式被复制检查点、视图进度等多处复用其幂等语义恰好适配没有版本历史、只留最新值的本地文档。六、最佳实践什么场景该用本地文档适合使用本地文档的场景单库配置只对该数据库有意义、不需要同步到其他设备/服务器的配置项同步进度与游标复制的检查点、增量处理的 last_seq 游标、批量任务的处理进度内部缓存/标记去重标记、迁移版本号、应用元数据需要原子读改写配合get()put()的乐观并发模式。不适合使用本地文档的场景需要跨设备同步的数据——本地文档不复制请改用普通文档需要出现在变更流中的业务数据——changes()是事件驱动应用与复制的基础本地文档不可见需要附件的数据——本地文档不支持附件对数据安全要求高的数据——本地文档只有最新版本一旦覆盖无法找回历史版本普通文档 压缩策略更适合需要审计/回滚的场景。一个完整的实战示例下面用保存视图筛选偏好 记录最后阅读进度演示本地文档的典型用法const db new PouchDB(reader); // 1. 保存本机偏好不复制 db.put({ _id: _local/prefs, theme: sepia, fontSize: 18 }).catch(function (err) { if (err.status 409) { // 已存在则先取回再更新 return db.get(_local/prefs).then(function (doc) { Object.assign(doc, {theme: sepia, fontSize: 18}); return db.put(doc); }); } throw err; }); // 2. 记录阅读进度可随时覆盖 function saveProgress(bookId, position) { return db.get(_local/progress_ bookId).then(function (doc) { doc.position position; return db.put(doc); }).catch(function (err) { if (err.status 404) { return db.put({_id: _local/progress_ bookId, position: position}); } throw err; }); }注意每次更新都要携带最新_rev这是本地文档与普通文档一致的约束。七、相关 API 速查API说明对应文档db.put(doc)创建/更新文档含_local/前缀文档create_documentdb.get(id)按_id读取文档fetch_documentdb.remove(doc)删除文档delete_documentdb.bulkDocs(docs)批量写入支持本地文档batch_createdb.allDocs()枚举普通文档不含本地文档batch_fetchdb.changes()监听变更不含本地文档changesdb.query()map/reduce 查询不含本地文档query_database八、进一步阅读本地文档官方指南本文原始出处原始英文文档可与本文对照阅读tests/integration/test.local_docs.js本地文档的专项集成测试覆盖创建、读取、删除、冲突、0-0修订号等全部边界场景tests/unit/test.checkpointer.js复制检查点机制的单测展示_local/检查点文档的读写与冲突重试packages/node_modules/pouchdb-checkpointer/src/index.js检查点文档的完整实现packages/node_modules/pouchdb-abstract-mapreduce/src/index.js视图_local/lastSeq进度记录的实现packages/node_modules/pouchdb-utils/src/upsert.js内部常用的本地文档原子读改写工具PouchDB API 文档put()、get()、remove()等方法的完整参数说明。【免费下载链接】pouchdb:kangaroo: - PouchDB is a pocket-sized database.项目地址: https://gitcode.com/gh_mirrors/po/pouchdb创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价