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