数据库后端前端【免费下载链接】tinybaseA reactive data store sync engine.项目地址https://gitcode.com/gh_mirrors/ti/tinybase点击查看免费下载本篇指南讲解 tinybase 如何借助persister-yjs和persister-automerge两个 CRDT Persister 将 Store 数据绑定到 Yjs / Automerge 文档借助第三方 CRDT 框架实现跨系统的 local-first 同步与冲突协调。读完本文你将掌握完整的双 Store 同步方案、数据在 CRDT 文档中的存储结构、增量与原子化写入的底层机制以及并发冲突下的确定性行为与注意事项。什么是第三方 CRDT 持久化tinybase 的 Store 是纯内存数据结构Persister 模块框架负责把数据保存到各种底层存储。其中一部分 persister 模块可以读写那些自身具备同步能力的存储类型——它们提供同步、local-first 协调和 CRDT无冲突复制数据类型语义Persister底层存储YjsPersisterYjs CRDT基于yjs包AutomergePersisterAutomerge CRDT基于automerge/automerge-repo包需要特别区分的是这两个模块解决的是借助第三方 CRDT 框架做同步的问题。tinybase v5 自身还提供了一方 CRDT 与同步技术MergeableStore Synchronizer那部分不属于本文范围可参考 同步指南目录。两者的 API 与其他 persister 完全一致但底层有额外基础设施目的是让每次更新尽可能增量incremental且原子atomic从而提升 CRDT 框架的协调reconciliation能力。Persister 通用 API 回顾CRDT Persister 继承标准Persister接口其完整方法集定义在 src/types/persisters/index.d.tsload(initialContent?)/save()显式异步加载与保存均返回 PromisestartAutoLoad(initialContent?)/startAutoSave()启动自动加载监听持久层变化反映到 Store与自动保存监听 Store 变化立即持久化stopAutoLoad()/stopAutoSave()、isAutoLoading()/isAutoSaving()停止与查询startAutoPersisting(initialContent?, startSaveFirst?)/stopAutoPersisting()一键同时开启/关闭双向自动持久化getStatus()/addStatusListener(listener)查询与监听 Idle / Loading / Saving 状态机schedule(...actions)把可能产生竞态的操作按序调度执行destroy()销毁 persister停止自动加载/保存并清理监听器getStats()返回{loads, saves}统计getStore()返回被持久化的 Store。在 CRDT 场景下完整的同步方案通常需要同时开启 auto-load 与 auto-save让 Store 与底层同步框架之间形成双向闭环。将 Store 绑定到 Yjs 文档persister-yjs模块的入口函数是createYjsPersister实现位于 src/persisters/persister-yjs/index.tscreateYjsPersister( store: Store, yDoc: YDoc, yMapName?: string, // 默认 tinybase onIgnoredError?: (error: any) void, ): YjsPersister;store要持久化的普通Store不支持MergeableStore的元数据持久化yDoc要绑定的yjs的Doc实例yMapName文档内部用于承载 Store 数据的Y.Map名称默认tinybaseonIgnoredError可选的持久化静默错误处理器适合开发环境调试自 v4.0.4 起支持。返回的YjsPersister在标准接口之外只多了一个getYDoc()方法用于取回所绑定的 Yjs 文档。数据在 Yjs 文档中的存储结构从源码和单元测试可以确认Store 的内容被拆成两棵嵌套Y.Map写入yDoc.getMap(yMapName)键t存放 Tablestable → row → cell 三层嵌套键v存放 Values。这一约定定义在 src/common/strings.ts常量T t、V v、TINYBASE tinybase中。测试 test/unit/persisters/yjs.test.ts 验证了保存空 Store 后的文档形态await persister.save(); // doc1.toJSON() - {tinybase: {t: {}, v: {}}}若自定义了地图名则结构相同、只是根键名变化createYjsPersister(store, doc, test)对应{test: {t: {}, v: {}}}。一个完整的同步 Walkthrough以下示例来自官方文档site/guides/07_persistence/3_third_party_crdt_persistence.md展示了两个各自绑定了独立 Yjs 文档的 Store 如何完成双向同步。建立绑定与双向自动持久化import {createStore} from tinybase; import {createYjsPersister} from tinybase/persisters/persister-yjs; import {Doc} from yjs; const doc1 new Doc(); const store1 createStore(); const persister1 createYjsPersister(store1, doc1); await persister1.startAutoLoad(); await persister1.startAutoSave(); const doc2 new Doc(); const store2 createStore(); const persister2 createYjsPersister(store2, doc2); await persister2.startAutoLoad(); await persister2.startAutoSave();用 syncDocs 模拟连接层真实世界里 Yjs 的同步通常发生在两个系统之间经由 Yjs 的 connection provider 交换 state vector、增量 update 等。为说明问题这里用一个简单的syncDocs函数做全量状态复制来合成这一层import {applyUpdate, encodeStateAsUpdate} from yjs; const syncDocs async () { // ... applyUpdate(doc1, encodeStateAsUpdate(doc2)); applyUpdate(doc2, encodeStateAsUpdate(doc1)); };建立同步基线建立 Store 之间的同步基线是好习惯await syncDocs();变更单向传播对其中一个 Store 做修改再次同步store1.setTables({pets: {fido: {species: dog}}}); await syncDocs();一切正常的话另一个 Store 应当看到这些变更console.log(store2.getTables()); // - {pets: {fido: {species: dog}}}反向传播同样成立store2.setValue(open, true); await syncDocs(); console.log(store1.getValues()); // - {open: true}这套两个 Store、两个 Doc、双向 auto-load/auto-save的完整链路在测试用例 test/unit/persisters/yjs.test.tsTwo stores, two docs组中得到了系统验证除了setTables/setValue还逐一验证了delCell、delRow、delTable、delValue等复杂事务在同步后两侧getContent()完全一致。Automerge 版本的绑定方式Automerge 侧的入口是createAutomergePersister实现位于 src/persisters/persister-automerge/index.ts参数与 Yjs 版一一对应store、docHandle来自automerge/automerge-repo的DocHandle、docMapName默认tinybase、onIgnoredError。典型用法import {Repo} from automerge/automerge-repo; import {createStore} from tinybase; import {createAutomergePersister} from tinybase/persisters/persister-automerge; const docHandler new Repo({network: []}).create(); const store createStore().setTables({pets: {fido: {species: dog}}}); const persister createAutomergePersister(store, docHandler); await persister.save(); // 文档内容 - {tinybase: {t: {pets: {fido: {species: dog}}}, v: {}}} await persister.destroy();AutomergePersister同样只比标准Persister多一个方法——getDocHandle()。测试 test/unit/persisters/automerge.test.ts 验证了保存、加载、损坏数据容错以及两个 Repo 经网络适配器互通的完整同步链路。底层实现增量与原子化写入文档中提到API 与其他 persister 完全一致但幕后有额外基础设施保证更新尽可能增量和原子。这一说法在源码中可以直接看到。自动保存传递的是 Cell 级 Changes在共享实现 src/persisters/common/create.ts 的startAutoSave中persister 通过store.addDidFinishTransactionListener监听事务完成然后取出 Store 的增量changes并调用save(changes)——也就是说每次保存传递给底层写入函数的是一份Cell / Value 粒度的变更集而不是全量内容。Yjs 侧事务内的增量应用与全量兜底persister-yjs的setPersisted把写入包在yDoc.transact(...)中执行因此一次 Store 事务对应一个 Yjs 事务写入在 CRDT 语义上是原子的// src/persisters/persister-yjs/index.ts const setPersisted async (getContent, changes) yDoc.transact(() applyChangesToYDoc(yContent, getContent, changes));applyChangesToYDoc的策略是两段式的增量路径优先只针对changes中出现的 table / row / cell 逐个执行yRow.set(cellId, cell)、yTable.delete(rowId)、yTables.delete(tableId)等最小操作——这正是尽可能增量的体现能减少 CRDT 元数据膨胀、提升远端协调效率全量兜底若某个增量操作因目标容器不存在而无法应用例如对端已删除了该 Row 或 Table导致本地增量中的容器缺失yMapMatch会以 Store 当前全量内容做逐键比对与补齐保证最终一致。单元测试falls back when incremental containers are missingtest/unit/persisters/yjs.test.ts专门验证了这一兜底路径先手动从 Y 文档中删掉r1行再对 Store 执行setCellpersister 会自动重建缺失的容器并写入新值。Automerge 侧DocHandle.change 与标识符转义Automerge 侧的setPersisted通过docHandle.change((doc) applyChangesToDoc(...))提交一个标准 Automerge change增量/全量兜底结构与 Yjs 版完全对称docObjMatch全量比对。此外Automerge 文档是普通 JS 对象形态rowId、cellId会被用作对象键因此源码实现了encodeId/decodeId对Object.prototype上的保留键如__proto__、constructor、prototype以及以\u0000开头的 id 统一加前缀转义。测试reserved identifiers明确验证了__proto__这类保留名作为 table/row/cell/value id 也能安全往返。监听方向的变化检测Yjs 版通过yContent.observeDeep(observer)深度监听把 Y 事件解析回[tables, values, 1]形式的 Changes第三个元素1表示这是 changes 而非 content再交给PersisterListener因此远端变更以增量形式应用进 StoreAutomerge 版监听DocHandle的change事件每次变更重新读取文档内容要求tinybase节点结构合法否则抛ERROR_CONTENT并走错误处理器。防竞态调度createCustomPersister还内置了状态机防竞态load在 Saving 状态下直接跳过save在 Loading 状态下直接跳过并且所有敏感操作经过同一队列按序执行schedule。这就是文档中框架提供基础竞态防护的实现依据但作者仍建议针对自己的持久化策略做充分测试例如弱网环境下保存到远端时的数据丢失窗口。冲突Conflictstinybase 刻意不对同时发生或相互冲突的更新表达任何立场而是把该问题完全交给底层 CRDT 框架裁决。但默认情况下多数变更都会尽量原子地按 Cell 或 Value 粒度处理以在默认行为下把数据丢失限制到最小。相邻 Cell两者都保留两个 Store 分别更新同一 Row 的两个不同 Cellstore1.setCell(pets, fido, color, brown); store2.setCell(pets, fido, legs, 4); // ... await syncDocs(); console.log(store1.getTables()); // - {pets: {fido: {species: dog, color: brown, legs: 4}}} console.log(store2.getTables()); // - {pets: {fido: {species: dog, color: brown, legs: 4}}}因为两个变更落在不同的 Yjs 路径上互不冲突同步后两侧合并出完整结果——这正是 Cell 级原子写入的价值。同一 Cell 冲突Yjs 按 clientID 决出胜负如果两个端对同一个 Cell写入不同值Yjs 通常以文档的clientID做平局裁决tie-break较大者获胜。文档中强制指定 clientID 使结果可复现// Here we force the clientID so that the reconciliation is deterministic. doc1.clientID 1; doc2.clientID 2; store1.setCell(pets, fido, price, 4); store2.setCell(pets, fido, price, 5); // ... await syncDocs(); console.log(store1.getCell(pets, fido, price)); // - 5 console.log(store2.getCell(pets, fido, price)); // - 5注意生产环境中的告诫强烈不建议自己设置clientID因此在真实部署中同一 Cell 的并发冲突裁决结果不会确定需要人工介入解决冲突。健壮性与错误处理两个 CRDT persister 都以不吞异常、不污染 Store为原则处理坏数据构造时可选传入onIgnoredError处理器用于接收 persister 本会忽略的持久化错误如解析损坏的已存数据适合开发环境调试Yjs 版测试test/unit/persisters/yjs.test.ts构造了多种畸形容器如yContent.set(t, 1)、行级结构被换成标量验证 persister 会把这些事件上报为错误消息形如tinybase:1但Store 内容保持原样不被覆盖Automerge 版同理根节点结构不合法时load得到空内容auto-load 监听仅上报错误而不动 Store见 test/unit/persisters/automerge.test.ts 的malformed two-key content、reports malformed roots without changing the Store等用例load(initialContent)在持久层为空或数据损坏时可接收默认内容便于应用首次运行时注入种子数据。小结当你要复用Yjs 或 Automerge 生态现成的 provider、同步网络、持久化后端时选择persister-yjs/persister-automergeAPI 与所有 persister 一致startAutoLoadstartAutoSave或startAutoPersisting即可建立 Store 与 CRDT 文档的双向绑定数据以{tinybase: {t: tables, v: values}}结构写入名为tinybase可自定义的 Map/文档节点写入尽量按 Cell 级增量进行并在容器缺失时自动回退全量比对配合transact/change保证原子性冲突处理完全委托给底层 CRDT不同 Cell 的并发变更天然合并同一 Cell 的冲突由 CRDT 规则裁决Yjs 按 clientID 平局决胜生产环境不要手工指定 clientID若希望使用 tinybase 一方的 CRDT 与 Synchronizer 方案请参考 同步指南想自建其他存储类型的 persister可参考 自定义持久化指南。赞分享数据库后端前端【免费下载链接】tinybaseA reactive data store sync engine.项目地址https://gitcode.com/gh_mirrors/ti/tinybase点击查看免费下载相关推荐TinyBase 与其他 CRDT 库集成YJS、Automerge 等完整指南TinyBase 与其他 CRDT 库集成YJS、Automerge 等完整指南 TinyBase 是一个为本地优先应用设计的 响应式数据存储 它提供了强大数据库后端前端用 Rivet Actors 与 Yjs 构建实时协作文档CRDT 同步、光标 Presence 与 KV 持久化实战用 Rivet Actors 与 Yjs 构建实时协作文档CRDT 同步、光标 Presence 与 KV 持久化实战 本篇文章围绕仓库中的 collabor后端AI Agent人工智能流程编排WebSocketTinyBase Todo App v3 实战用 Persister 与 Schema 实现浏览器持久化TinyBase Todo App v3 实战用 Persister 与 Schema 实现浏览器持久化 本文基于 tinybase 仓库中的官方演示文档 T数据库后端前端创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考