aboutsummaryrefslogtreecommitdiffstats
path: root/src/document/mod.rs
diff options
context:
space:
mode:
Diffstat (limited to 'src/document/mod.rs')
-rw-r--r--src/document/mod.rs96
1 files changed, 96 insertions, 0 deletions
diff --git a/src/document/mod.rs b/src/document/mod.rs
index b86d1cc..21050ea 100644
--- a/src/document/mod.rs
+++ b/src/document/mod.rs
@@ -1,8 +1,104 @@
//! 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<u8> {
+ 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<u8> {
+ 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<Document> {
+ 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 at;
+#[cfg(test)]
+pub(crate) mod bench;
+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;
+pub(crate) mod typed;
+
+#[cfg(test)]
+mod tests {
+ use std::path::{Path, PathBuf};
+
+ /// Every `.rs` file under `dir`.
+ fn sources(dir: &Path, out: &mut Vec<PathBuf>) {
+ 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")
+ );
+ }
+}