补丁
一个用于构建实时协作应用程序的TypeScript库. 您得到了两个同步策略: 协作文本的操作转换, 以及其它的 Last-Write-Wins .
这能解决什么问题?
建设实时协作功能难度大. 用户同时编辑,连接会降低中位变化,冲突解决速度会很快. 补丁处理这一切,所以你不必.
你的文档状态只是JSON。 用简单的回调更改它 :
doc.change(state => (state.title = 'New Title'));
更改立即适用于快速用户界面,然后同步到背景中的服务器。 离线? 没问题 重新上线时更改队列并同步 .
范围:补丁是什么和不是
补丁是一个 一般原始文献库它为实时系统提供组件(OT,LWW,同步传输,doc-store接口). 把它编成自己的后端和前端.
补丁知道:
- JSON文件、JSON补丁操作和快照
- 业务转型和最后写出冲突解决办法
- 传送面 (WebSocket, SSE+REST, WebRTC) 移动客户端和服务器之间的变化
- HTTP 状态代码以
StatusError(401, 403, 404, 410) — 如此消耗的应用程序可以在响应上分出
补丁确实如此 没有 了解情况:
- 用户、角色、邀请、成员或任何访问控制模式
- 应用软件、项目、书籍、文件成品或任何特定应用术语
- 电子邮件发送、姓名/电子邮件查询或任何身份服务
- 失去访问权限时如何操作的 UX 政策( 耗用应用程序决定 403 是否意味着“ 显示一面旗帜 ” 、 “ 踢出 医生 ” , 或是其它任何内容)
如果一个特性感觉它属于Patches,但提到了一个应用概念(例如"一个项目的邀请"),它不属于这里——它属于一个级别上,在应用自己的客户端SDK或消费应用本身. 帕奇斯表面应该足够通用,可以支持任何实时系统.
两个同步策略
补丁给你两种解决冲突的方法。 为工作选择合适的工具.
业务转型(OT) - 当用户同时编辑相同内容时
- 变化被明智地合并
- 需要合作编辑文本
- 示例: Google Docs 类协作
最后写- Wins (LWW) - 当最新的时标获胜
- 更简单,更快,更可预测
- 最适合设置、仪表板、画布对象
- 图片使用这种方法 他们的多人游戏
决定很简单: 如果用户不编辑相同 文本 合作使用 LWW。 它更快,更容易调试,并完美地处理大多数实时的情景.
需要LWW订购名单吗? 使用 分数索引 维持秩序,没有OT.
大多数应用程序都使用两种策略:OT用于文档内容,LWW用于其他一切.
目录
为什么是业务转型?
"我不该用CRDT吗?"
还有 数量 联 合 国 意见 关于 这个我们在这里学到了什么 达布尔作家: CRDTs对长寿文档不进行缩放.
我们的一些用户有48万+操作的项目. 这些怪物花了几个小时重建 叶杰斯,在最优化的Y.js中负载为~4秒,而~20ms则会添加变化. 我们的OT图书馆? 负载为1-2ms并适用了0.2ms.
随着文件体积增大或寿命延长,OT性能保持平平而CRDTs减速. 对于大多数使用的案例,CRDTs的工作都很好. 但是,如果你正在建设 规模或长寿,OT赢得.
关键概念
集中式OT - 一个服务器充当真相的唯一来源。 没有对等复杂,没有向量钟,没有分布式共识头痛. 服务器查看所有变化的顺序,并广播犬科状态.
重置基础 - 当服务器有新的更改时, 您的客户端还没有看到, 您的待定更改会得到“ 重置” 上面 。 考虑一下 git rebase,但用于实时编辑.
线性历史 - 服务器维持一个直线 没有分支,没有叉子,没有基础设施层面的合并冲突.
抓图 - OT文档随时间而累积变化。 为了避免重放480k操作载荷,我们定期快照. 装入最新的快照, 应用最近的修改, 完成 .
不稳定的国家 - 每一个变化都会产生一个新的状态对象。 未改变的部分保持不变。 这使得React/Vue/Solid变得微不足道并能够进行廉价的平等检查.
安装
npm install @dabble/patches
开始
客户端示例
import { Patches, OTStrategy, InMemoryStore } from '@dabble/patches';
import { PatchesSync } from '@dabble/patches/net';
interface MyDoc {
text: string;
count: number;
}
// 1. Create a strategy with its store
const strategy = new OTStrategy(new InMemoryStore());
// 2. Create the Patches instance
const patches = new Patches({
strategies: { ot: strategy },
defaultStrategy: 'ot',
});
// 3. Set up real-time sync
const sync = new PatchesSync(patches, 'wss://your-server-url');
await sync.connect();
// 4. Open a document
const doc = await patches.openDoc<MyDoc>('my-doc-1');
// 5. React to updates
doc.subscribe(newState => {
console.log('Document updated:', newState);
// Update your UI here
});
// 6. Make changes - they sync automatically
doc.change(draft => {
draft.text = 'Hello World!';
draft.count = (draft.count || 0) + 1;
});
见 补丁, (中文(简体) ), 补丁文件,以及 补丁同步 用于完整的API文档.
服务器示例
import express from 'express';
import { OTServer } from '@dabble/patches/server';
// Your backend store implementation
const store = new MyOTStoreBackend();
const server = new OTServer(store);
const app = express();
app.use(express.json());
// Get document state
app.get('/docs/:docId', async (req, res) => {
const { state, rev } = await server.getDoc(req.params.docId);
res.json({ state: state ?? {}, rev });
});
// Commit changes
app.post('/docs/:docId/changes', async (req, res) => {
try {
const changes = await server.commitChanges(req.params.docId, req.body.changes);
res.json(changes);
// Broadcast to other clients via WebSocket
} catch (error) {
const status = error.message.includes('out of sync') ? 409 : 500;
res.status(status).json({ error: error.message });
}
});
app.listen(3000);
见 服务员 用于完整的API文档.
LWW 快速启动
对于 Last-Write-Wins 同步, 请使用 LWW 特定商店和战略 :
// Client
import { Patches, LWWStrategy, LWWInMemoryStore } from '@dabble/patches';
import { PatchesSync } from '@dabble/patches/net';
const strategy = new LWWStrategy(new LWWInMemoryStore());
const patches = new Patches({
strategies: { lww: strategy },
defaultStrategy: 'lww',
});
const sync = new PatchesSync(patches, 'wss://your-server-url');
await sync.connect();
const doc = await patches.openDoc<UserPrefs>('user-prefs');
doc.change(draft => {
draft.theme = 'dark';
draft.fontSize = 16;
});
// Server
import { LWWServer } from '@dabble/patches/server';
const store = new MyLWWStoreBackend();
const server = new LWWServer(store);
app.post('/docs/:docId/changes', async (req, res) => {
const result = await server.commitChanges(req.params.docId, req.body.changes);
res.json(result);
});
见 劳工服务员 财务报告和审定财务报表 最后写- Wins 概念 更多细节.
核心组成部分
客户端
补丁 - 主入口点。 管理文档生命周期,协调策略,处理持久性.
补丁文件 - 一份合作文件 音轨状态,乐观地应用变化,发布更新事件.
补丁同步 - WebSocket 连接管理器。 处理重接,分批,双向同步.
战略 - 算法特定逻辑:
OTStrategy-拥有一个OTClientStore,处理重新定位和变化跟踪LWWStrategy-拥有一个LWWClientStore,处理时间戳合并
存储 - 持久性适配器:
InMemoryStore编号 :LWWInMemoryStore- 测试和简单的应用OTIndexedDBStore编号 :LWWIndexedDBStore- 有离线支持的浏览器持续
服务器侧
服务员 - OT授权。 变换并行的更改,分配修改,保持历史.
劳工服务员 - LWW权威。 比较时间戳,存储当前字段值,没有历史.
补丁历史管理器 - 查询文档版本和历史.
补丁缓冲管理器 - 创建、列出和合并分支.
后端存储 - 为数据库执行这些接口:
OTStoreBackend- 对OT:更改、快照、版本LWWStoreBackend- 关于LWW:有时间戳、快照的字段
联网
WebSocket 运输 - 通过服务器进行的标准通信 PatchesWebSocket。 。 。 .
WebRTC 运输 - 提高认识的同龄人(诅咒、在场).
JSON-RPC议定书 - 客户端和服务器之间的电线协议.
什么时候用? 文档同步的 WebSocket 。 WebRTC 用于存在/ cursers 来减少服务器负载. 见 联网概况。 。 。 .
意识( 频度和光标)
显示谁在线,光标在哪里,选择什么. WebRTCAwareness 在对等对等的WebRTC连接上携带存在,在您已经打开的同步频道上,信号握手多键 WebSocketTransport 或者说 PatchesREST (SSE+REST) (英语). 没有第二次连接, 相同 clientId。 。 。 .
见 提高认识文件 详细执行情况.
基本工作流程
客户端
- 创建一个
Patches战略实例 - 连接
PatchesSync到您的服务器 - 打开文档
patches.openDoc(docId) - 订阅更新
doc.subscribe() - 以
doc.change()- 它们自动同步
服务器
- 创建
OTServer或者说LWWServer用您的后端存储器 - 处理
commitChanges()请求 - 向其他客户广播承诺的更改
- 可选使用
PatchesHistoryManager用于版本和PatchesBranchManager用于分支
实例
完成客户端设置
import { Patches, OTStrategy, OTIndexedDBStore } from '@dabble/patches';
import { PatchesSync } from '@dabble/patches/net';
interface MyDoc {
title: string;
content: string;
}
// Production setup with IndexedDB for offline support
const strategy = new OTStrategy(new OTIndexedDBStore('my-app'));
const patches = new Patches({
strategies: { ot: strategy },
});
const sync = new PatchesSync(patches, 'wss://api.example.com/sync');
// Handle connection state
sync.subscribe(state => {
if (state.connected) {
console.log('Connected and syncing');
} else if (!state.online) {
console.log('Offline - changes saved locally');
}
});
// Handle errors
sync.onError((error, context) => {
console.error(`Sync error for ${context?.docId}:`, error);
});
await sync.connect();
// Open and use a document
const doc = await patches.openDoc<MyDoc>('doc-123');
doc.subscribe(state => {
renderUI(state);
});
doc.change(draft => {
draft.title = 'My Document';
draft.content = 'Hello, world!';
});
使用两种战略
import { Patches, OTStrategy, LWWStrategy, InMemoryStore, LWWInMemoryStore } from '@dabble/patches';
// Configure both strategies
const patches = new Patches({
strategies: {
ot: new OTStrategy(new InMemoryStore()),
lww: new LWWStrategy(new LWWInMemoryStore()),
},
defaultStrategy: 'ot',
});
// OT for collaborative document editing
const manuscript = await patches.openDoc('manuscript-123'); // Uses default (ot)
// LWW for user settings
const settings = await patches.openDoc('settings-user-456', { strategy: 'lww' });
高级主题
版本历史( H)
文档在30分钟后自动快照. 浏览版本为 PatchesHistoryManager。 。 。 .
见 OTServer 版本 财务报告和审定财务报表 补丁历史管理器。 。 。 .
分支
创建文档分支,在隔离状态下工作,后合并. 用于"如果"情景或舞台编辑.
见 分支 财务报告和审定财务报表 补丁缓冲管理器。 。 。 .
共享工人
在共享工作人中运行补丁,用于交叉调制协调并减少内存使用.
见 共享工人文档。 。 。 .
框架整合
- 第3组: 见 弧度/湿度/README.md
- 实相.js: 见 弧/固体/README.md
自定义 OT 操作
扩展操作处理器用于特定域的转换.
见 操作处理器。 。 。 .
JSON 补丁
补丁使用JSON Patch (RFC 6902) 在罩下. 你很少需要直接使用它,但它就在那里.
见 JSON 补丁文档。 。 。 .
捐款
欢迎捐款。 开放问题或提交拉力请求.