aboutsummaryrefslogtreecommitdiffstats
path: root/src/document/replica.rs
diff options
context:
space:
mode:
authortv <tv@krebsco.de>2026-11-01 22:22:41 +0100
committertv <tv@krebsco.de>2026-11-01 22:22:41 +0100
commit656cd21ce2625a8a2931b5b03d1d84227dd7e3e8 (patch)
tree8441c5ea268ffde428e39b869bf95552167d03c5 /src/document/replica.rs
parentd2c05c17bc58cdbedfb0a702b2858c52aff2eedf (diff)
record node ID and API user for every write
Diffstat (limited to 'src/document/replica.rs')
-rw-r--r--src/document/replica.rs54
1 files changed, 44 insertions, 10 deletions
diff --git a/src/document/replica.rs b/src/document/replica.rs
index df2fafd..3717865 100644
--- a/src/document/replica.rs
+++ b/src/document/replica.rs
@@ -175,14 +175,16 @@ impl Replica {
}
/// The writes `f` makes, as one change stamped `at` if it succeeds (receivers tell
- /// by the stamp how long the change took to reach them, `swarm::stats`), dropped if
- /// not. Doesn't refresh the heads: the caller does, under the same lock.
+ /// by the stamp how long the change took to reach them, `swarm::stats`), by
+ /// `writer`, dropped if not. Doesn't refresh the heads: the caller does, under the
+ /// same lock.
pub(crate) fn transact<T, E>(
&mut self,
at: crate::clock::UnixMillis,
+ writer: &Writer,
f: impl FnOnce(&mut Transaction<'_>) -> Result<T, E>,
) -> Result<T, E> {
- transact(&mut self.doc, at, f)
+ transact(&mut self.doc, at, writer, f)
}
}
@@ -231,7 +233,11 @@ mod tests {
let path = vec!["a".to_owned()];
let mut replica = Replica::new(Epoch::GENESIS, Document::new());
- let refused: Result<(), &str> = replica.transact(UnixMillis(1), |doc| {
+ let writer = Writer {
+ node: "node",
+ user: None,
+ };
+ let refused: Result<(), &str> = replica.transact(UnixMillis(1), &writer, |doc| {
store::write_path(doc, &path, &json!(1)).unwrap();
Err("refused")
});
@@ -242,7 +248,7 @@ mod tests {
// The same writer commits afterwards, and that saves too.
replica
- .transact(UnixMillis(2), |doc| {
+ .transact(UnixMillis(2), &writer, |doc| {
store::write_path(doc, &path, &json!(2))
})
.unwrap();
@@ -261,20 +267,33 @@ mod tests {
}
}
-/// The writes `f` makes to `doc`, as one change stamped `at` if it succeeds, dropped if
-/// not ([`Replica::transact`], and writes into the document before a rejoin).
+/// Who makes a change, as it records: the node, as the change's author (its node ID),
+/// and the user it wrote for through the API, if any, as its message. Every node holds
+/// every change, so who wrote what is known swarm-wide.
+pub(crate) struct Writer<'a> {
+ pub(crate) node: &'a str,
+ pub(crate) user: Option<&'a str>,
+}
+
+/// The writes `f` makes to `doc`, as one change stamped `at` by `writer` if it succeeds,
+/// dropped if not ([`Replica::transact`], and writes into the document before a rejoin).
pub(crate) fn transact<T, E>(
doc: &mut Document,
at: crate::clock::UnixMillis,
+ writer: &Writer,
f: impl FnOnce(&mut Transaction<'_>) -> Result<T, E>,
) -> Result<T, E> {
+ authored(doc, writer.node);
let mut transaction = doc.transaction();
let out = f(&mut transaction);
match &out {
Ok(_) => {
- transaction.commit_with(
- automerge::transaction::CommitOptions::default().with_time(at.as_u64() as i64),
- );
+ let mut options =
+ automerge::transaction::CommitOptions::default().with_time(at.as_u64() as i64);
+ if let Some(user) = writer.user {
+ options.set_message(user);
+ }
+ transaction.commit_with(options);
}
Err(_) => {
transaction.rollback();
@@ -282,3 +301,18 @@ pub(crate) fn transact<T, E>(
}
out
}
+
+/// Makes `node` the author of `doc`'s changes from now on. Setting an author gives the
+/// document a new actor; this node keeps its own (`with_actor`), which it tracks
+/// (`RemakeState::own_actors`, the document file), as only it writes with it.
+fn authored(doc: &mut Document, node: &str) {
+ if doc
+ .get_author()
+ .is_some_and(|author| author.as_bytes() == node.as_bytes())
+ {
+ return;
+ }
+ let actor = doc.get_actor().clone();
+ doc.set_author(Some(automerge::Author::from(node.as_bytes().to_vec())));
+ doc.set_actor(actor);
+}