automerge/lib.rs
1//! # Automerge
2//!
3//! Automerge is a library of data structures for building collaborative,
4//! [local-first](https://www.inkandswitch.com/local-first/) applications. The
5//! idea of automerge is to provide a data structure which is quite general
6//! \- consisting of nested key/value maps and/or lists - which can be modified
7//! entirely locally but which can at any time be merged with other instances of
8//! the same data structure.
9//!
10//! In addition to the core data structure (which we generally refer to as a
11//! "document"), we also provide an implementation of a sync protocol (in
12//! [`crate::sync`]) which can be used over any reliable in-order transport; and
13//! an efficient binary storage format.
14//!
15//! This crate is organised around two representations of a document -
16//! [`Automerge`] and [`AutoCommit`]. The difference between the two is that
17//! [`AutoCommit`] manages transactions for you. Both of these representations
18//! implement [`ReadDoc`] for reading values from a document and
19//! [`sync::SyncDoc`] for taking part in the sync protocol. [`AutoCommit`]
20//! directly implements [`transaction::Transactable`] for making changes to a
21//! document, whilst [`Automerge`] requires you to explicitly create a
22//! [`transaction::Transaction`].
23//!
24//! NOTE: The API this library provides for modifying data is quite low level
25//! (somewhat analogous to directly creating JSON values rather than using
26//! [`serde`] derive macros or equivalent). If you're writing a Rust application which uses automerge
27//! you may want to look at [autosurgeon](https://github.com/automerge/autosurgeon).
28//!
29//! ## Data Model
30//!
31//! An automerge document is a map from strings to values
32//! ([`Value`]) where values can be either
33//!
34//! * A nested composite value which is either
35//! * A map from strings to values ([`ObjType::Map`])
36//! * A list of values ([`ObjType::List`])
37//! * A text object (a sequence of unicode characters) ([`ObjType::Text`])
38//! * A primitive value ([`ScalarValue`]) which is one of
39//! * A string
40//! * A 64 bit floating point number
41//! * A signed 64 bit integer
42//! * An unsigned 64 bit integer
43//! * A boolean
44//! * A counter object (a 64 bit integer which merges by addition)
45//! ([`ScalarValue::Counter`])
46//! * A timestamp (a 64 bit integer which is milliseconds since the unix epoch)
47//!
48//! All composite values have an ID ([`ObjId`]) which is created when the value
49//! is inserted into the document or is the root object ID [`ROOT`]. Values in
50//! the document are then referred to by the pair (`object ID`, `key`). The
51//! `key` is represented by the [`Prop`] type and is either a string for a maps,
52//! or an index for sequences.
53//!
54//! ### Conflicts
55//!
56//! There are some things automerge cannot merge sensibly. For example, two
57//! actors concurrently setting the key "name" to different values. In this case
58//! automerge will pick a winning value in a random but deterministic way, but
59//! the conflicting value is still available via the [`ReadDoc::get_all()`] method.
60//!
61//! ### Change hashes and historical values
62//!
63//! Like git, points in the history of a document are identified by hash. Unlike
64//! git there can be multiple hashes representing a particular point (because
65//! automerge supports concurrent changes). These hashes can be obtained using
66//! either [`Automerge::get_heads()`] or [`AutoCommit::get_heads()`] (note these
67//! methods are not part of [`ReadDoc`] because in the case of [`AutoCommit`] it
68//! requires a mutable reference to the document).
69//!
70//! These hashes can be used to read values from the document at a particular
71//! point in history using the various `*_at()` methods on [`ReadDoc`] which take a
72//! slice of [`ChangeHash`] as an argument.
73//!
74//! ### Actor IDs
75//!
76//! Any change to an automerge document is made by an actor, represented by an
77//! [`ActorId`]. An actor ID is any random sequence of bytes but each change by
78//! the same actor ID must be sequential. This often means you will want to
79//! maintain at least one actor ID per device. It is fine to generate a new
80//! actor ID for each change, but be aware that each actor ID takes up space in
81//! a document so if you expect a document to be long lived and/or to have many
82//! changes then you should try to reuse actor IDs where possible.
83//!
84//! ### Text Encoding
85//!
86//! Text is encoded in UTF-8 by default but uses UTF-16 when using the wasm target,
87//! you can configure it with the feature `utf16-indexing`.
88//!
89//! ## Sync Protocol
90//!
91//! See the [`sync`] module.
92//!
93//! ## Patches, maintaining materialized state
94//!
95//! Often you will have some state which represents the "current" state of the document. E.g. some
96//! text in a UI which is a view of a text object in the document. Rather than re-rendering this
97//! text every single time a change comes in you can use a [`PatchLog`] to capture incremental
98//! changes made to the document and then use [`Automerge::make_patches()`] to get a set of patches
99//! to apply to the materialized state.
100//!
101//! Many of the methods on [`Automerge`], [`crate::sync::SyncDoc`] and
102//! [`crate::transaction::Transactable`] have a `*_log_patches()` variant which allow you to pass in
103//! a [`PatchLog`] to collect these incremental changes.
104//!
105//! ## Serde serialization
106//!
107//! Sometimes you just want to get the JSON value of an automerge document. For
108//! this you can use [`AutoSerde`], which implements [`serde::Serialize`] for an
109//! automerge document.
110//!
111//! ## Example
112//!
113//! Let's create a document representing an address book.
114//!
115//! ```
116//! use automerge::{ObjType, AutoCommit, transaction::Transactable, ReadDoc};
117//!
118//! # fn main() -> Result<(), Box<dyn std::error::Error>> {
119//! let mut doc = AutoCommit::new();
120//!
121//! // `put_object` creates a nested object in the root key/value map and
122//! // returns the ID of the new object, in this case a list.
123//! let contacts = doc.put_object(automerge::ROOT, "contacts", ObjType::List)?;
124//!
125//! // Now we can insert objects into the list
126//! let alice = doc.insert_object(&contacts, 0, ObjType::Map)?;
127//!
128//! // Finally we can set keys in the "alice" map
129//! doc.put(&alice, "name", "Alice")?;
130//! doc.put(&alice, "email", "alice@example.com")?;
131//!
132//! // Create another contact
133//! let bob = doc.insert_object(&contacts, 1, ObjType::Map)?;
134//! doc.put(&bob, "name", "Bob")?;
135//! doc.put(&bob, "email", "bob@example.com")?;
136//!
137//! // Now we save the address book, we can put this in a file
138//! let data: Vec<u8> = doc.save();
139//! # Ok(())
140//! # }
141//! ```
142//!
143//! Now modify this document on two separate devices and merge the modifications.
144//!
145//! ```
146//! use std::borrow::Cow;
147//! use automerge::{ObjType, AutoCommit, transaction::Transactable, ReadDoc};
148//!
149//! # fn main() -> Result<(), Box<dyn std::error::Error>> {
150//! # let mut doc = AutoCommit::new();
151//! # let contacts = doc.put_object(automerge::ROOT, "contacts", ObjType::List)?;
152//! # let alice = doc.insert_object(&contacts, 0, ObjType::Map)?;
153//! # doc.put(&alice, "name", "Alice")?;
154//! # doc.put(&alice, "email", "alice@example.com")?;
155//! # let bob = doc.insert_object(&contacts, 1, ObjType::Map)?;
156//! # doc.put(&bob, "name", "Bob")?;
157//! # doc.put(&bob, "email", "bob@example.com")?;
158//! # let saved: Vec<u8> = doc.save();
159//!
160//! // Load the document on the first device and change alices email
161//! let mut doc1 = AutoCommit::load(&saved)?;
162//! let contacts = match doc1.get(automerge::ROOT, "contacts")? {
163//! Some((automerge::Value::Object(ObjType::List), contacts)) => contacts,
164//! _ => panic!("contacts should be a list"),
165//! };
166//! let alice = match doc1.get(&contacts, 0)? {
167//! Some((automerge::Value::Object(ObjType::Map), alice)) => alice,
168//! _ => panic!("alice should be a map"),
169//! };
170//! doc1.put(&alice, "email", "alicesnewemail@example.com")?;
171//!
172//!
173//! // Load the document on the second device and change bobs name
174//! let mut doc2 = AutoCommit::load(&saved)?;
175//! let contacts = match doc2.get(automerge::ROOT, "contacts")? {
176//! Some((automerge::Value::Object(ObjType::List), contacts)) => contacts,
177//! _ => panic!("contacts should be a list"),
178//! };
179//! let bob = match doc2.get(&contacts, 1)? {
180//! Some((automerge::Value::Object(ObjType::Map), bob)) => bob,
181//! _ => panic!("bob should be a map"),
182//! };
183//! doc2.put(&bob, "name", "Robert")?;
184//!
185//! // Finally, we can merge the changes from the two devices
186//! doc1.merge(&mut doc2)?;
187//! let bobsname: Option<automerge::Value> = doc1.get(&bob, "name")?.map(|(v, _)| v);
188//! assert_eq!(bobsname, Some(automerge::Value::Scalar(Cow::Owned("Robert".into()))));
189//!
190//! let alices_email: Option<automerge::Value> = doc1.get(&alice, "email")?.map(|(v, _)| v);
191//! assert_eq!(alices_email, Some(automerge::Value::Scalar(Cow::Owned("alicesnewemail@example.com".into()))));
192//! # Ok(())
193//! # }
194//! ```
195//!
196//! ## Cursors, referring to positions in sequences
197//!
198//! When working with text or other sequences it is often useful to be able to
199//! refer to a specific position within the sequence whilst merging remote
200//! changes. You can manually do this by maintaining your own offsets and
201//! observing patches, but this is error prone. The [`Cursor`] type provides
202//! an API for allowing automerge to do the index translations for you. Cursors
203//! are created with [`ReadDoc::get_cursor()`] and dereferenced with
204//! [`ReadDoc::get_cursor_position()`].
205
206#![doc(
207 html_logo_url = "https://raw.githubusercontent.com/automerge/automerge/main/img/brandmark.svg",
208 html_favicon_url = "https:///raw.githubusercontent.com/automerge/automerge/main/img/favicon.ico"
209)]
210#![warn(
211 missing_debug_implementations,
212 // missing_docs, // TODO: add documentation!
213 rust_2018_idioms,
214 unreachable_pub,
215 bad_style,
216 dead_code,
217 improper_ctypes,
218 non_shorthand_field_patterns,
219 no_mangle_generic_items,
220 overflowing_literals,
221 path_statements,
222 patterns_in_fns_without_body,
223 unconditional_recursion,
224 unused,
225 unused_allocation,
226 unused_comparisons,
227 unused_parens,
228 while_true
229)]
230
231#[doc(hidden)]
232#[macro_export]
233macro_rules! log {
234 ( $( $t:tt )* ) => {
235 {
236 use $crate::__log;
237 __log!( $( $t )* );
238 }
239 }
240 }
241
242#[cfg(all(feature = "wasm", target_family = "wasm"))]
243#[doc(hidden)]
244#[macro_export]
245macro_rules! __log {
246 ( $( $t:tt )* ) => {
247 web_sys::console::log_1(&format!( $( $t )* ).into());
248 }
249 }
250
251#[cfg(not(all(feature = "wasm", target_family = "wasm")))]
252#[doc(hidden)]
253#[macro_export]
254macro_rules! __log {
255 ( $( $t:tt )* ) => {
256 println!( $( $t )* );
257 }
258 }
259
260mod autocommit;
261mod automerge;
262mod autoserde;
263mod change;
264mod change_graph;
265mod clock;
266mod columnar;
267mod convert;
268mod cursor;
269pub mod error;
270mod exid;
271pub mod hydrate;
272mod indexed_cache;
273pub mod iter;
274mod legacy;
275pub mod marks;
276mod op_set;
277pub mod op_tree;
278mod parents;
279pub mod patches;
280mod query;
281mod read;
282mod sequence_tree;
283mod storage;
284pub mod sync;
285mod text_diff;
286mod text_value;
287pub mod transaction;
288mod types;
289mod value;
290#[cfg(feature = "optree-visualisation")]
291mod visualisation;
292
293pub use crate::automerge::{Automerge, LoadOptions, OnPartialLoad, SaveOptions, StringMigration};
294pub use autocommit::AutoCommit;
295pub use autoserde::AutoSerde;
296pub use change::{Change, LoadError as LoadChangeError};
297pub use cursor::{Cursor, CursorPosition, MoveCursor, OpCursor};
298pub use error::AutomergeError;
299pub use error::InvalidActorId;
300pub use error::InvalidChangeHashSlice;
301pub use exid::{ExId as ObjId, ObjIdFromBytesError};
302pub use legacy::Change as ExpandedChange;
303pub use parents::{Parent, Parents};
304pub use patches::{Patch, PatchAction, PatchLog};
305pub use read::ReadDoc;
306pub use sequence_tree::SequenceTree;
307pub use storage::VerificationMode;
308pub use text_value::ConcreteTextValue;
309pub use transaction::BlockOrText;
310pub use types::{ActorId, ChangeHash, ObjType, OpType, ParseChangeHashError, Prop, TextEncoding};
311pub use value::{ScalarValue, Value};
312
313/// The object ID for the root map of a document
314pub const ROOT: ObjId = ObjId::Root;