项目概述
本项目实现本地优先的笔记同步服务:在设备离线编辑后检测同一笔记的并发修改,并用确定性规则合并而非静默覆盖。冲突被显式上报而非隐藏,因为悄悄选一个赢家会丢掉用户的文字。
架构与关键决策
每条笔记携带版本向量与更新时间,改动以带 id 的仅追加日志记录;拉取返回游标之后的条目,使设备只收到未见过的内容。推送声明其编辑所基于的版本,若服务端已前进则该次编辑为并发,检测为冲突而非盲改。冲突以确定性规则(如三方合并)解决,使每个设备对同一输入得到相同字节。游标是日志中的单调位置,断线后可续传,重试按改动 id 去重。
实施与测试
在两台模拟设备上离线编辑同一条笔记,同步双方,断言报告冲突且合并结果一致;断言拉取只返回游标之后的内容;断言重复的改动 id 只产生一条记录;断言基于过期版本的推送被判为冲突。
限制与后续
- 合并面向文本,字段级结构化合并仅近似实现。
- 日志按周期快照压缩,陈旧游标可能需全量重同步。
- 无服务端冲突界面,用户体验取决于客户端。
代码示例
pnpm init
pnpm add pg express
pnpm add -D typescript tsx vitest
notes-sync/
├── src/pull.ts
├── src/push.ts
├── src/conflict.ts
├── src/cursor.ts
├── tests/sync.test.ts
└── package.json
const vv = { [deviceId]: previous[deviceId] + 1 };
SELECT * FROM changes WHERE seq > $1 ORDER BY seq LIMIT 500;
if (base.equals(server.version)) return apply(change); return conflict(change);
const merged = deterministicMerge(local, remote);
await db.setCursor(clientId, lastSeq);
pnpm vitest run