aboutsummaryrefslogtreecommitdiffstats
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/document/mod.rs4
-rw-r--r--src/document/store.rs92
-rw-r--r--src/remake/records.rs16
-rw-r--r--src/swarm/members.rs12
4 files changed, 71 insertions, 53 deletions
diff --git a/src/document/mod.rs b/src/document/mod.rs
index a348774..9633241 100644
--- a/src/document/mod.rs
+++ b/src/document/mod.rs
@@ -2,8 +2,10 @@
//! (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 `AutoCommit`), its changes and their hashes, and its writers (actors).
+/// (Automerge's `AutoCommit`), its changes and their hashes, and its writers (actors);
+/// and what can be written like a document (`Transactable`).
/// Only `document` and `remake` use Automerge itself (`architecture.md`, The document).
+pub(crate) use automerge::transaction::Transactable;
pub(crate) use automerge::{ActorId, AutoCommit as Document, Change, ChangeHash};
/// The whole document as bytes, as the engine saves it: a backup, the document
diff --git a/src/document/store.rs b/src/document/store.rs
index c6c428c..7d54626 100644
--- a/src/document/store.rs
+++ b/src/document/store.rs
@@ -118,7 +118,7 @@ enum Resolved {
/// All concurrent values of `key` across a merged set of parent maps, newest first.
fn candidates(
- doc: &AutoCommit,
+ doc: &impl ReadDoc,
parents: &[ObjId],
prop: impl Into<Prop> + Clone,
) -> Result<Vec<(Value<'static>, ObjId)>, StoreError> {
@@ -133,7 +133,7 @@ fn candidates(
}
fn resolve(
- doc: &AutoCommit,
+ doc: &impl ReadDoc,
parents: &[ObjId],
key: &str,
log: Option<&mut ConflictLog>,
@@ -143,7 +143,7 @@ fn resolve(
/// The item at `index` of `list` (concurrent writes there resolved as for a key).
fn resolve_item(
- doc: &AutoCommit,
+ doc: &impl ReadDoc,
list: &ObjId,
index: usize,
log: Option<&mut ConflictLog>,
@@ -163,7 +163,7 @@ fn resolve_item(
/// What `segment` names within `current`: a key of merged maps, or the item of a list
/// at the index it spells (`list_index`); None for anything else, and in a scalar.
fn step(
- doc: &AutoCommit,
+ doc: &impl ReadDoc,
current: &Resolved,
segment: &str,
log: Option<&mut ConflictLog>,
@@ -197,7 +197,7 @@ fn from_the_end(segment: &str) -> Option<usize> {
/// None if it has none. An index before the start names nothing (`NotFound`). Only
/// segments within a list count: in a map, `-1` is a key like any other.
pub(crate) fn positive_path(
- doc: &AutoCommit,
+ doc: &impl ReadDoc,
path: &[String],
insertion: bool,
) -> Result<Option<Vec<String>>, StoreError> {
@@ -228,7 +228,7 @@ pub(crate) fn positive_path(
/// Resolves `key` (a map key or a list index) in `parents`; `name` is how conflict
/// reports name it.
fn resolve_prop(
- doc: &AutoCommit,
+ doc: &impl ReadDoc,
parents: &[ObjId],
prop: impl Into<Prop> + Clone,
name: &str,
@@ -261,12 +261,12 @@ fn resolve_prop(
}
}
-fn merged_keys(doc: &AutoCommit, maps: &[ObjId]) -> BTreeSet<String> {
+fn merged_keys(doc: &impl ReadDoc, maps: &[ObjId]) -> BTreeSet<String> {
maps.iter().flat_map(|map| doc.keys(map)).collect()
}
fn resolved_to_json(
- doc: &AutoCommit,
+ doc: &impl ReadDoc,
resolved: &Resolved,
mut log: Option<&mut ConflictLog>,
) -> Result<JsonValue, StoreError> {
@@ -316,7 +316,7 @@ fn resolved_to_json(
}
fn object_to_json(
- doc: &AutoCommit,
+ doc: &impl ReadDoc,
id: &ObjId,
mut log: Option<&mut ConflictLog>,
) -> Result<JsonValue, StoreError> {
@@ -340,7 +340,7 @@ fn object_to_json(
}
}
-fn delete_in_all(doc: &mut AutoCommit, maps: &[ObjId], key: &str) -> Result<(), StoreError> {
+fn delete_in_all(doc: &mut impl Transactable, maps: &[ObjId], key: &str) -> Result<(), StoreError> {
for map in maps {
if doc.get(map, key)?.is_some() {
doc.delete(map, key)?;
@@ -350,7 +350,7 @@ fn delete_in_all(doc: &mut AutoCommit, maps: &[ObjId], key: &str) -> Result<(),
}
fn read_path_inner(
- doc: &AutoCommit,
+ doc: &impl ReadDoc,
path: &[String],
mut log: Option<&mut ConflictLog>,
) -> Result<Option<JsonValue>, StoreError> {
@@ -362,14 +362,14 @@ fn read_path_inner(
/// Reads a path; the empty path is the whole store.
pub(crate) fn read_path(
- doc: &AutoCommit,
+ doc: &impl ReadDoc,
path: &[String],
) -> Result<Option<JsonValue>, StoreError> {
read_path_inner(doc, path, None)
}
pub(crate) fn read_path_with_conflicts(
- doc: &AutoCommit,
+ doc: &impl ReadDoc,
path: &[String],
) -> Result<Option<(JsonValue, Vec<ConflictReport>)>, StoreError> {
let mut log = ConflictLog {
@@ -383,7 +383,7 @@ pub(crate) fn read_path_with_conflicts(
/// For streams: the value, plus whether the path or subtree involves any conflict.
/// Reports are collected without values, so this costs little more than read_path.
pub(crate) fn read_path_traced(
- doc: &AutoCommit,
+ doc: &impl ReadDoc,
path: &[String],
) -> Result<(Option<JsonValue>, bool), StoreError> {
let mut log = ConflictLog::default();
@@ -401,7 +401,7 @@ const SHALLOW_INLINE: usize = 256;
/// scalar; a map's has its keys' summaries too, under `entries`. Concurrent values
/// resolved as a read resolves them; conflicts not reported.
pub(crate) fn read_shallow(
- doc: &AutoCommit,
+ doc: &impl ReadDoc,
path: &[String],
) -> Result<Option<JsonValue>, StoreError> {
let Some(resolved) = resolve_through(doc, path)? else {
@@ -412,14 +412,14 @@ pub(crate) fn read_shallow(
/// The resolved value at `path` (the empty path is the root map), through maps and
/// lists (`step`).
-fn resolve_through(doc: &AutoCommit, path: &[String]) -> Result<Option<Resolved>, StoreError> {
+fn resolve_through(doc: &impl ReadDoc, path: &[String]) -> Result<Option<Resolved>, StoreError> {
resolve_logged(doc, path, None)
}
/// `resolve_through`, noting the conflicts on the way in `log` (as ancestors, but the
/// last segment's: the requested value's), its path left at `path`.
fn resolve_logged(
- doc: &AutoCommit,
+ doc: &impl ReadDoc,
path: &[String],
mut log: Option<&mut ConflictLog>,
) -> Result<Option<Resolved>, StoreError> {
@@ -445,7 +445,11 @@ fn resolve_logged(
/// A summary of `resolved` (`read_shallow`); with `entries`, a map's keys' too, or a
/// list's items' (by index).
-fn summary(doc: &AutoCommit, resolved: &Resolved, entries: bool) -> Result<JsonValue, StoreError> {
+fn summary(
+ doc: &impl ReadDoc,
+ resolved: &Resolved,
+ entries: bool,
+) -> Result<JsonValue, StoreError> {
let mut out = serde_json::Map::new();
match resolved {
Resolved::Maps(maps) => {
@@ -508,7 +512,7 @@ fn summary(doc: &AutoCommit, resolved: &Resolved, entries: bool) -> Result<JsonV
}
/// Keys of the merged map at `path`; empty if it doesn't exist or isn't a map.
-pub(crate) fn keys_at(doc: &AutoCommit, path: &[String]) -> Result<Vec<String>, StoreError> {
+pub(crate) fn keys_at(doc: &impl ReadDoc, path: &[String]) -> Result<Vec<String>, StoreError> {
Ok(match resolve_through(doc, path)? {
Some(Resolved::Maps(maps)) => merged_keys(doc, &maps).into_iter().collect(),
_ => Vec::new(),
@@ -518,7 +522,7 @@ pub(crate) fn keys_at(doc: &AutoCommit, path: &[String]) -> Result<Vec<String>,
/// Every concurrent value at `path`, newest first; empty if there is none. Where
/// `read_path` picks one (or merges concurrent maps into one), this keeps them apart.
pub(crate) fn read_candidates(
- doc: &AutoCommit,
+ doc: &impl ReadDoc,
path: &[String],
) -> Result<Vec<JsonValue>, StoreError> {
let (leaf, parents) = path.split_last().ok_or(StoreError::NotFound)?;
@@ -538,7 +542,7 @@ pub(crate) fn read_candidates(
/// the segment is an index (`list_index`): an item there is replaced, one at the end
/// appended; past the end, or anything but an index, names nothing (`NotFound`).
pub(crate) fn write_path(
- doc: &mut AutoCommit,
+ doc: &mut impl Transactable,
path: &[String],
value: &JsonValue,
) -> Result<(), StoreError> {
@@ -575,7 +579,7 @@ enum Parent {
/// The parent of a write at `parents` + one more segment: missing maps are created on
/// the way; through a list only by an index of an item it has. A scalar or text on the
/// way is a `Conflict`.
-fn parent_for_write(doc: &mut AutoCommit, parents: &[String]) -> Result<Parent, StoreError> {
+fn parent_for_write(doc: &mut impl Transactable, parents: &[String]) -> Result<Parent, StoreError> {
let mut current = Parent::Maps(vec![automerge::ROOT]);
for segment in parents {
let next = match &current {
@@ -607,7 +611,7 @@ fn parent_for_write(doc: &mut AutoCommit, parents: &[String]) -> Result<Parent,
/// `PastTheEnd`; a parent that isn't a list `NotAList`; anything but an index there, or
/// no parent, `NotFound`.
pub(crate) fn insert_path(
- doc: &mut AutoCommit,
+ doc: &mut impl Transactable,
path: &[String],
value: &JsonValue,
) -> Result<(), StoreError> {
@@ -630,8 +634,8 @@ pub(crate) fn insert_path(
/// copy (see `materialize_into`) if it exists, a delete otherwise. Replaces the whole
/// subtree at `path`; the path must not be empty.
pub(crate) fn copy_path(
- src: &AutoCommit,
- dst: &mut AutoCommit,
+ src: &impl ReadDoc,
+ dst: &mut impl Transactable,
path: &[String],
) -> Result<(), StoreError> {
copy_path_to(src, path, dst, path)
@@ -639,9 +643,9 @@ pub(crate) fn copy_path(
/// Like [`copy_path`], but puts what `src` holds at `from` at `to` in `dst`.
pub(crate) fn copy_path_to(
- src: &AutoCommit,
+ src: &impl ReadDoc,
from: &[String],
- dst: &mut AutoCommit,
+ dst: &mut impl Transactable,
to: &[String],
) -> Result<(), StoreError> {
let (leaf, parents) = to.split_last().ok_or(StoreError::NotFound)?;
@@ -675,7 +679,7 @@ pub(crate) fn copy_path_to(
/// (`list_index`; the items after it move down one). With `prune`, the maps above it
/// that it leaves empty go too, up to (not including) the root or a list.
pub(crate) fn delete_path(
- doc: &mut AutoCommit,
+ doc: &mut impl Transactable,
path: &[String],
prune: bool,
) -> Result<(), StoreError> {
@@ -739,8 +743,8 @@ pub(crate) fn delete_path(
/// `skip` sees the path of every map entry and list element (list indices as decimal
/// strings); returning true leaves that subtree out.
pub(crate) fn materialize_into(
- src: &AutoCommit,
- dst: &mut AutoCommit,
+ src: &impl ReadDoc,
+ dst: &mut impl Transactable,
skip: &dyn Fn(&[String]) -> bool,
) -> Result<(), StoreError> {
let mut path = Vec::new();
@@ -755,9 +759,9 @@ pub(crate) fn materialize_into(
}
fn copy_maps(
- src: &AutoCommit,
+ src: &impl ReadDoc,
maps: &[ObjId],
- dst: &mut AutoCommit,
+ dst: &mut impl Transactable,
target: &ObjId,
path: &mut Vec<String>,
skip: &dyn Fn(&[String]) -> bool,
@@ -776,9 +780,9 @@ fn copy_maps(
}
fn copy_resolved(
- src: &AutoCommit,
+ src: &impl ReadDoc,
resolved: &Resolved,
- dst: &mut AutoCommit,
+ dst: &mut impl Transactable,
parent: &ObjId,
slot: Slot,
path: &mut Vec<String>,
@@ -806,10 +810,10 @@ fn copy_resolved(
#[allow(clippy::too_many_arguments)]
fn copy_object(
- src: &AutoCommit,
+ src: &impl ReadDoc,
obj_type: ObjType,
id: &ObjId,
- dst: &mut AutoCommit,
+ dst: &mut impl Transactable,
parent: &ObjId,
slot: Slot,
path: &mut Vec<String>,
@@ -863,7 +867,7 @@ enum Slot<'a> {
}
fn put_object(
- doc: &mut AutoCommit,
+ doc: &mut impl Transactable,
parent: &ObjId,
slot: Slot,
obj_type: ObjType,
@@ -875,7 +879,7 @@ fn put_object(
}
fn put_scalar(
- doc: &mut AutoCommit,
+ doc: &mut impl Transactable,
parent: &ObjId,
slot: Slot,
value: impl Into<ScalarValue>,
@@ -888,7 +892,7 @@ fn put_scalar(
}
fn write_value(
- doc: &mut AutoCommit,
+ doc: &mut impl Transactable,
parent: &ObjId,
slot: Slot,
value: &JsonValue,
@@ -926,7 +930,7 @@ fn write_value(
}
fn json_to_automerge(
- doc: &mut AutoCommit,
+ doc: &mut impl Transactable,
parent: &ObjId,
key: &str,
value: &JsonValue,
@@ -997,7 +1001,11 @@ fn op_id_string(id: &ObjId) -> String {
}
}
-fn value_to_json(doc: &AutoCommit, value: &Value<'_>, id: &ObjId) -> Result<JsonValue, StoreError> {
+fn value_to_json(
+ doc: &impl ReadDoc,
+ value: &Value<'_>,
+ id: &ObjId,
+) -> Result<JsonValue, StoreError> {
match value {
Value::Scalar(scalar) => Ok(scalar_to_json(scalar.clone().into_owned())?),
Value::Object(_) => object_to_json(doc, id, None),
@@ -1007,7 +1015,7 @@ fn value_to_json(doc: &AutoCommit, value: &Value<'_>, id: &ObjId) -> Result<Json
impl ConflictLog {
fn record(
&mut self,
- doc: &AutoCommit,
+ doc: &impl ReadDoc,
key: &str,
all: &[(Value<'static>, ObjId)],
merged: bool,
diff --git a/src/remake/records.rs b/src/remake/records.rs
index 40b563a..fecae4c 100644
--- a/src/remake/records.rs
+++ b/src/remake/records.rs
@@ -38,7 +38,7 @@ use crate::clock::UnixMillis;
use crate::document::meta::{self, REMAKES, VOTES};
use crate::document::replica::Epoch;
use crate::document::store::{self, StoreError};
-use crate::document::{ChangeHash, Document};
+use crate::document::{ChangeHash, Document, Transactable};
/// One day, for retention defaults.
const DAY_MS: u64 = 24 * 60 * 60 * 1000;
@@ -446,7 +446,7 @@ pub(crate) fn load_register(doc: &Document) -> Register {
}
fn write_part<T: Serialize>(
- doc: &mut Document,
+ doc: &mut impl Transactable,
id: &str,
part: &str,
value: &T,
@@ -456,7 +456,7 @@ fn write_part<T: Serialize>(
}
pub(crate) fn write_proposal(
- doc: &mut Document,
+ doc: &mut impl Transactable,
id: &str,
proposal: &Proposal,
) -> Result<(), StoreError> {
@@ -464,7 +464,7 @@ pub(crate) fn write_proposal(
}
pub(crate) fn write_decision(
- doc: &mut Document,
+ doc: &mut impl Transactable,
id: &str,
decision: &Decision,
) -> Result<(), StoreError> {
@@ -472,7 +472,7 @@ pub(crate) fn write_decision(
}
pub(crate) fn write_abort_request(
- doc: &mut Document,
+ doc: &mut impl Transactable,
id: &str,
request: &AbortRequest,
) -> Result<(), StoreError> {
@@ -480,7 +480,11 @@ pub(crate) fn write_abort_request(
}
/// Writes `voter`'s entry in the votes register (replacing its earlier one).
-pub(crate) fn write_vote(doc: &mut Document, voter: &str, vote: &Vote) -> Result<(), StoreError> {
+pub(crate) fn write_vote(
+ doc: &mut impl Transactable,
+ voter: &str,
+ vote: &Vote,
+) -> Result<(), StoreError> {
let json = serde_json::to_value(vote).map_err(anyhow::Error::from)?;
store::write_path(doc, &register_path(voter), &json)
}
diff --git a/src/swarm/members.rs b/src/swarm/members.rs
index 8d7f909..9885750 100644
--- a/src/swarm/members.rs
+++ b/src/swarm/members.rs
@@ -21,9 +21,9 @@ use std::time::Duration;
use serde_json::{Value as JsonValue, json};
use tracing::{debug, info, warn};
-use crate::document::Document;
use crate::document::meta;
use crate::document::store::{self, StoreError};
+use crate::document::{Document, Transactable};
use crate::state::AppState;
pub(crate) fn members_dir() -> Vec<String> {
@@ -74,7 +74,11 @@ pub(crate) fn tier(doc: &Document, pk: &str) -> Tier {
}
}
-pub(crate) fn set_tier(doc: &mut Document, pk: &str, tier: Tier) -> Result<(), StoreError> {
+pub(crate) fn set_tier(
+ doc: &mut impl Transactable,
+ pk: &str,
+ tier: Tier,
+) -> Result<(), StoreError> {
store::write_path(doc, &tier_path(pk), &json!(tier.as_str()))
}
@@ -100,7 +104,7 @@ pub(crate) fn aliases(doc: &Document) -> HashMap<String, String> {
.collect()
}
-fn set_alias(doc: &mut Document, pk: &str, alias: Option<&str>) -> Result<(), StoreError> {
+fn set_alias(doc: &mut impl Transactable, pk: &str, alias: Option<&str>) -> Result<(), StoreError> {
match alias {
Some(alias) => store::write_path(doc, &alias_path(pk), &json!(alias)),
None => match store::delete_path(doc, &alias_path(pk), false) {
@@ -314,7 +318,7 @@ pub(crate) async fn ensure_registered(state: &AppState) {
}
/// Removes `pk`'s entry (an operator forgot it). Absent is fine.
-pub(crate) fn unregister(doc: &mut Document, pk: &str) -> Result<(), StoreError> {
+pub(crate) fn unregister(doc: &mut impl Transactable, pk: &str) -> Result<(), StoreError> {
match store::delete_path(doc, &member_path(pk), false) {
Err(StoreError::NotFound) => Ok(()),
result => result,