//! The replicated document: the replica with its epoch, and the store semantics //! (merged-view paths over JSON) the HTTP API and remakes work with. /// The engine's types, under the names the rest of the crate uses: the document /// (Automerge's `Automerge`), its changes and their hashes, and its writers (actors); /// a write to it (`Transaction`: one change, committed or dropped whole), and what can /// be read like a document (`ReadDoc`: the document, or a write in progress) and written /// like one (`Transactable`). /// Only `document` and `remake` use Automerge itself (`architecture.md`, The document). pub(crate) use automerge::transaction::{Transactable, Transaction}; pub(crate) use automerge::{ActorId, Automerge as Document, Change, ChangeHash, ReadDoc}; /// The whole document as bytes, as the engine saves it: a backup, the document /// file's full save, the size a joining node downloads. pub(crate) fn save(doc: &Document) -> Vec { doc.save() } /// The changes after `since` as bytes that, appended to a save, load with it (the /// document file's appends). pub(crate) fn save_since(doc: &Document, since: &[ChangeHash]) -> Vec { doc.save_after(since) } /// A saved document (`save`), loaded to write to under a writer ID of its own: one ID /// must never write in two places, and a save may be loaded on several nodes, or again /// after the node that saved it wrote on. pub(crate) fn load(bytes: &[u8]) -> anyhow::Result { Ok(Document::load(bytes)?.with_actor(ActorId::random())) } /// The actor (writer ID) `doc` writes under. pub(crate) fn actor(doc: &Document) -> ActorId { doc.get_actor().clone() } pub(crate) mod changes; pub(crate) mod file; pub(crate) mod lock; pub(crate) mod meta; pub(crate) mod replica; pub(crate) mod store; pub(crate) mod sync; #[cfg(test)] pub(crate) mod testing; #[cfg(test)] mod tests { use std::path::{Path, PathBuf}; /// Every `.rs` file under `dir`. fn sources(dir: &Path, out: &mut Vec) { for entry in std::fs::read_dir(dir).unwrap() { let path = entry.unwrap().path(); if path.is_dir() { sources(&path, out); } else if path.extension().is_some_and(|extension| extension == "rs") { out.push(path); } } } /// Automerge is used only in `document` and in what remakes do to documents /// (snapshots, replaying late writes, catching up, the kinds, quarantined writes); /// the rest of the node, remakes' coordination included, names its types as /// `document` re-exports them and works through `document` (`architecture.md`, The /// document). #[test] fn automerge_stays_behind_document() { let src = Path::new(env!("CARGO_MANIFEST_DIR")).join("src"); let mut allowed = vec![src.join("document")]; for engine in ["snapshot", "delta", "catchup", "kind", "quarantine"] { allowed.push(src.join("remake").join(format!("{engine}.rs"))); } let mut files = Vec::new(); sources(&src, &mut files); let mut found = Vec::new(); for file in files .iter() .filter(|file| !allowed.iter().any(|allowed| file.starts_with(allowed))) { let text = std::fs::read_to_string(file).unwrap(); for (number, line) in text.lines().enumerate() { if line.contains("automerge::") || line.trim_start().starts_with("use automerge") { found.push(format!( "{}:{}: {}", file.display(), number + 1, line.trim() )); } } } assert!( found.is_empty(), "Automerge used outside `document`:\n{}", found.join("\n") ); } }