17个版本
0.5.11 | 2024年5月7日 |
---|---|
0.5.9 | 2024年3月23日 |
0.5.6 | 2023年12月26日 |
0.5.3 | 2023年11月24日 |
0.0.2 | 2019年12月28日 |
在异步类目下排名第79
每月下载量3,209次
被14个Crate使用(11个直接使用)
1MB
31K SLoC
Automerge
Automerge是一个用于构建协作本地优先应用的库。这是Rust的实现。请参阅automerge.org
lib.rs
:
Automerge
Automerge是一个用于构建协作、本地优先应用的库。Automerge的理念是提供一个相当通用——由嵌套的键/值映射和/或列表组成——的数据结构,该数据结构可以在本地完全修改,但可以随时与其他相同数据结构的实例合并。
除了核心数据结构(我们通常称之为“文档”)之外,我们还提供了一个同步协议的实现(在crate::sync
中),该协议可以通过任何可靠的有序传输使用;以及一个高效的二进制存储格式。
此库围绕两种文档表示形式组织 - Automerge
和 AutoCommit
。这两者的区别在于 AutoCommit
会为您管理事务。这两种表示形式都实现了 ReadDoc
用于从文档中读取值,以及 sync::SyncDoc
用于参与同步协议。 AutoCommit
直接实现了 transaction::Transactable
以修改文档,而 Automerge
则需要您显式创建一个 transaction::Transaction
。
注意:该库提供的数据修改API非常底层(某种程度上类似于直接创建JSON值而不是使用 serde
derive宏或等效功能)。如果您正在编写使用Automerge的Rust应用程序,可能需要查看 autosurgeon。
数据模型
Automerge文档是一个从字符串到值的映射(Value
),其中值可以是以下之一:
- 一个嵌套的复合值,它可以是以下之一:
- 一个从字符串到值的映射(
ObjType::Map
) - 一个值的列表(
ObjType::List
) - 一个文本对象(一系列Unicode字符)(
ObjType::Text
)
- 一个从字符串到值的映射(
- 一个原始值(
ScalarValue
),它可以是以下之一:- 一个字符串
- 一个64位浮点数
- 一个有符号64位整数
- 一个无符号64位整数
- 一个布尔值
- 一个计数器对象(一个通过加法合并的64位整数)(
ScalarValue::Counter
) - 一个时间戳(自Unix纪元以来的毫秒数)(
Timestamp
)
所有复合值都有一个ID(ObjId
),它在将值插入文档时创建,或者是最初对象ID ROOT
。文档中的值通过(代码object ID,key)对引用。其中 key 由 Prop
类型表示,对于映射来说是一个字符串,对于序列来说是一个索引。
冲突
有些东西自动合并无法合理合并。例如,两个操作者同时将“名称”键设置为不同的值。在这种情况下,自动合并将以随机但确定性的方式选择获胜值,但冲突的值仍然可以通过 [ReadDoc::get_all()
] 方法获取。
更改哈希和历史值
与git类似,文档历史中的点由哈希标识。与git不同,可以存在多个哈希表示特定点(因为自动合并支持并发更改)。这些哈希可以通过 [Automerge::get_heads()
] 或 [AutoCommit::get_heads()
] 获取(注意这些方法不是ReadDoc
的一部分,因为在AutoCommit
的情况下,它需要一个对文档的可变引用)。
这些哈希可以用于使用ReadDoc
上的各种 *_at()
方法从特定历史点读取文档中的值,这些方法将ChangeHash
的切片作为参数。
操作者ID
对自动合并文档的任何更改都是由操作者进行的,操作者由ActorId
表示。操作者ID是任何随机字节序列,但同一操作者ID的每个更改必须是顺序的。这通常意味着您将想要为每个设备维护至少一个操作者ID。为每个更改生成新的操作者ID是可以的,但请注意,每个操作者ID都会占用文档中的空间,因此如果您预计文档将长期存在并且/或有很多更改,那么您应该尽可能重用操作者ID。
文本编码
默认情况下使用UTF-8进行编码,但使用wasm目标时使用UTF-16。
同步协议
请参阅sync
模块。
补丁,维护实体状态
通常您将有一些表示文档“当前”状态的实体。例如,UI中的一些文本是文档中文本对象的视图。而不是在每次更改发生时重新渲染此文本,您可以使用PatchLog
捕获对文档所做的增量更改,然后使用 [Automerge::make_patches()
] 获取要应用于实体状态的一组补丁。
许多《Automerge》库中的方法,如 Automerge
,crate::sync::SyncDoc
和 crate::transaction::Transactable
,都有一个 *_log_patches()
变体,它允许你传入一个 PatchLog
来收集这些增量更改。
Serde 序列化
有时你只是想要获取 Automerge 文档的 JSON 值。为此,你可以使用 AutoSerde
,它为 Automerge 文档实现了 serde::Serialize
。
示例
让我们创建一个表示通讯录的文档。
use automerge::{ObjType, AutoCommit, transaction::Transactable, ReadDoc};
let mut doc = AutoCommit::new();
// `put_object` creates a nested object in the root key/value map and
// returns the ID of the new object, in this case a list.
let contacts = doc.put_object(automerge::ROOT, "contacts", ObjType::List)?;
// Now we can insert objects into the list
let alice = doc.insert_object(&contacts, 0, ObjType::Map)?;
// Finally we can set keys in the "alice" map
doc.put(&alice, "name", "Alice")?;
doc.put(&alice, "email", "[email protected]")?;
// Create another contact
let bob = doc.insert_object(&contacts, 1, ObjType::Map)?;
doc.put(&bob, "name", "Bob")?;
doc.put(&bob, "email", "[email protected]")?;
// Now we save the address book, we can put this in a file
let data: Vec<u8> = doc.save();
现在在两个不同的设备上修改这个文档,并合并修改。
use std::borrow::Cow;
use automerge::{ObjType, AutoCommit, transaction::Transactable, ReadDoc};
// Load the document on the first device and change alices email
let mut doc1 = AutoCommit::load(&saved)?;
let contacts = match doc1.get(automerge::ROOT, "contacts")? {
Some((automerge::Value::Object(ObjType::List), contacts)) => contacts,
_ => panic!("contacts should be a list"),
};
let alice = match doc1.get(&contacts, 0)? {
Some((automerge::Value::Object(ObjType::Map), alice)) => alice,
_ => panic!("alice should be a map"),
};
doc1.put(&alice, "email", "[email protected]")?;
// Load the document on the second device and change bobs name
let mut doc2 = AutoCommit::load(&saved)?;
let contacts = match doc2.get(automerge::ROOT, "contacts")? {
Some((automerge::Value::Object(ObjType::List), contacts)) => contacts,
_ => panic!("contacts should be a list"),
};
let bob = match doc2.get(&contacts, 1)? {
Some((automerge::Value::Object(ObjType::Map), bob)) => bob,
_ => panic!("bob should be a map"),
};
doc2.put(&bob, "name", "Robert")?;
// Finally, we can merge the changes from the two devices
doc1.merge(&mut doc2)?;
let bobsname: Option<automerge::Value> = doc1.get(&bob, "name")?.map(|(v, _)| v);
assert_eq!(bobsname, Some(automerge::Value::Scalar(Cow::Owned("Robert".into()))));
let alices_email: Option<automerge::Value> = doc1.get(&alice, "email")?.map(|(v, _)| v);
assert_eq!(alices_email, Some(automerge::Value::Scalar(Cow::Owned("[email protected]".into()))));
游标,指向序列中的位置
当处理文本或其他序列时,在合并远程更改时能够引用序列中的特定位置通常很有用。你可以通过维护自己的偏移量并观察补丁来手动完成此操作,但这很容易出错。Cursor
类型提供了一种 API,允许 Automerge 为你进行索引转换。游标通过 [ReadDoc::get_cursor()
] 创建,并通过 [ReadDoc::get_cursor_position()
] 解引用。
依赖项
~3.5–7MB
~139K SLoC