aboutsummaryrefslogtreecommitdiffstats
path: root/src/document
diff options
context:
space:
mode:
Diffstat (limited to 'src/document')
-rw-r--r--src/document/at.rs249
-rw-r--r--src/document/mod.rs1
-rw-r--r--src/document/store.rs3
3 files changed, 253 insertions, 0 deletions
diff --git a/src/document/at.rs b/src/document/at.rs
new file mode 100644
index 0000000..d318f17
--- /dev/null
+++ b/src/document/at.rs
@@ -0,0 +1,249 @@
+//! The document as it was at given heads: every read of `ReadDoc` made at them (its
+//! `*_at` variant), so whatever reads a document (`store`) reads it as it was then.
+
+use std::ops::RangeBounds;
+
+use automerge::iter::{DocIter, Keys, ListRange, MapRange, Spans, Values};
+use automerge::marks::{Mark, MarkSet};
+use automerge::{
+ AutomergeError, Change, ChangeHash, Cursor, CursorPosition, MoveCursor, ObjId, ObjType,
+ Parents, Prop, ReadDoc, Stats, TextEncoding, Value, hydrate,
+};
+
+use super::Document;
+
+pub(crate) struct At<'a> {
+ doc: &'a Document,
+ heads: Vec<ChangeHash>,
+}
+
+impl<'a> At<'a> {
+ /// `doc` at `heads`; None if it lacks one of them (it hasn't synced it yet, or
+ /// never will: of another epoch, or no change at all).
+ pub(crate) fn new(doc: &'a Document, heads: &[ChangeHash]) -> Option<Self> {
+ heads
+ .iter()
+ .all(|head| doc.get_change_by_hash(head).is_some())
+ .then(|| Self {
+ doc,
+ heads: heads.to_vec(),
+ })
+ }
+
+ fn or_ours<'h>(&'h self, heads: Option<&'h [ChangeHash]>) -> Option<&'h [ChangeHash]> {
+ Some(heads.unwrap_or(&self.heads))
+ }
+}
+
+impl ReadDoc for At<'_> {
+ fn parents<O: AsRef<ObjId>>(&self, obj: O) -> Result<Parents<'_>, AutomergeError> {
+ self.doc.parents_at(obj, &self.heads)
+ }
+
+ fn parents_at<O: AsRef<ObjId>>(
+ &self,
+ obj: O,
+ heads: &[ChangeHash],
+ ) -> Result<Parents<'_>, AutomergeError> {
+ self.doc.parents_at(obj, heads)
+ }
+
+ fn keys<O: AsRef<ObjId>>(&self, obj: O) -> Keys<'_> {
+ self.doc.keys_at(obj, &self.heads)
+ }
+
+ fn keys_at<O: AsRef<ObjId>>(&self, obj: O, heads: &[ChangeHash]) -> Keys<'_> {
+ self.doc.keys_at(obj, heads)
+ }
+
+ fn iter_at<O: AsRef<ObjId>>(&self, obj: O, heads: Option<&[ChangeHash]>) -> DocIter<'_> {
+ self.doc.iter_at(obj, self.or_ours(heads))
+ }
+
+ fn map_range<'b, O: AsRef<ObjId>, R: RangeBounds<String> + 'b>(
+ &'b self,
+ obj: O,
+ range: R,
+ ) -> MapRange<'b> {
+ self.doc.map_range_at(obj, range, &self.heads)
+ }
+
+ fn map_range_at<'b, O: AsRef<ObjId>, R: RangeBounds<String> + 'b>(
+ &'b self,
+ obj: O,
+ range: R,
+ heads: &[ChangeHash],
+ ) -> MapRange<'b> {
+ self.doc.map_range_at(obj, range, heads)
+ }
+
+ fn list_range<O: AsRef<ObjId>, R: RangeBounds<usize>>(
+ &self,
+ obj: O,
+ range: R,
+ ) -> ListRange<'_> {
+ self.doc.list_range_at(obj, range, &self.heads)
+ }
+
+ fn list_range_at<O: AsRef<ObjId>, R: RangeBounds<usize>>(
+ &self,
+ obj: O,
+ range: R,
+ heads: &[ChangeHash],
+ ) -> ListRange<'_> {
+ self.doc.list_range_at(obj, range, heads)
+ }
+
+ fn values<O: AsRef<ObjId>>(&self, obj: O) -> Values<'_> {
+ self.doc.values_at(obj, &self.heads)
+ }
+
+ fn values_at<O: AsRef<ObjId>>(&self, obj: O, heads: &[ChangeHash]) -> Values<'_> {
+ self.doc.values_at(obj, heads)
+ }
+
+ fn length<O: AsRef<ObjId>>(&self, obj: O) -> usize {
+ self.doc.length_at(obj, &self.heads)
+ }
+
+ fn length_at<O: AsRef<ObjId>>(&self, obj: O, heads: &[ChangeHash]) -> usize {
+ self.doc.length_at(obj, heads)
+ }
+
+ fn object_type<O: AsRef<ObjId>>(&self, obj: O) -> Result<ObjType, AutomergeError> {
+ // An object's type is fixed when it is made.
+ self.doc.object_type(obj)
+ }
+
+ fn marks<O: AsRef<ObjId>>(&self, obj: O) -> Result<Vec<Mark>, AutomergeError> {
+ self.doc.marks_at(obj, &self.heads)
+ }
+
+ fn marks_at<O: AsRef<ObjId>>(
+ &self,
+ obj: O,
+ heads: &[ChangeHash],
+ ) -> Result<Vec<Mark>, AutomergeError> {
+ self.doc.marks_at(obj, heads)
+ }
+
+ fn get_marks<O: AsRef<ObjId>>(
+ &self,
+ obj: O,
+ index: usize,
+ heads: Option<&[ChangeHash]>,
+ ) -> Result<MarkSet, AutomergeError> {
+ self.doc.get_marks(obj, index, self.or_ours(heads))
+ }
+
+ fn text<O: AsRef<ObjId>>(&self, obj: O) -> Result<String, AutomergeError> {
+ self.doc.text_at(obj, &self.heads)
+ }
+
+ fn text_at<O: AsRef<ObjId>>(
+ &self,
+ obj: O,
+ heads: &[ChangeHash],
+ ) -> Result<String, AutomergeError> {
+ self.doc.text_at(obj, heads)
+ }
+
+ fn spans<O: AsRef<ObjId>>(&self, obj: O) -> Result<Spans<'_>, AutomergeError> {
+ self.doc.spans_at(obj, &self.heads)
+ }
+
+ fn spans_at<O: AsRef<ObjId>>(
+ &self,
+ obj: O,
+ heads: &[ChangeHash],
+ ) -> Result<Spans<'_>, AutomergeError> {
+ self.doc.spans_at(obj, heads)
+ }
+
+ fn get_cursor<O: AsRef<ObjId>, I: Into<CursorPosition>>(
+ &self,
+ obj: O,
+ position: I,
+ at: Option<&[ChangeHash]>,
+ ) -> Result<Cursor, AutomergeError> {
+ self.doc.get_cursor(obj, position, self.or_ours(at))
+ }
+
+ fn get_cursor_moving<O: AsRef<ObjId>, I: Into<CursorPosition>>(
+ &self,
+ obj: O,
+ position: I,
+ at: Option<&[ChangeHash]>,
+ move_cursor: MoveCursor,
+ ) -> Result<Cursor, AutomergeError> {
+ self.doc
+ .get_cursor_moving(obj, position, self.or_ours(at), move_cursor)
+ }
+
+ fn get_cursor_position<O: AsRef<ObjId>>(
+ &self,
+ obj: O,
+ cursor: &Cursor,
+ at: Option<&[ChangeHash]>,
+ ) -> Result<usize, AutomergeError> {
+ self.doc.get_cursor_position(obj, cursor, self.or_ours(at))
+ }
+
+ fn get<O: AsRef<ObjId>, P: Into<Prop>>(
+ &self,
+ obj: O,
+ prop: P,
+ ) -> Result<Option<(Value<'_>, ObjId)>, AutomergeError> {
+ self.doc.get_at(obj, prop, &self.heads)
+ }
+
+ fn get_at<O: AsRef<ObjId>, P: Into<Prop>>(
+ &self,
+ obj: O,
+ prop: P,
+ heads: &[ChangeHash],
+ ) -> Result<Option<(Value<'_>, ObjId)>, AutomergeError> {
+ self.doc.get_at(obj, prop, heads)
+ }
+
+ fn hydrate<O: AsRef<ObjId>>(
+ &self,
+ obj: O,
+ heads: Option<&[ChangeHash]>,
+ ) -> Result<hydrate::Value, AutomergeError> {
+ ReadDoc::hydrate(self.doc, obj, self.or_ours(heads))
+ }
+
+ fn get_all<O: AsRef<ObjId>, P: Into<Prop>>(
+ &self,
+ obj: O,
+ prop: P,
+ ) -> Result<Vec<(Value<'_>, ObjId)>, AutomergeError> {
+ self.doc.get_all_at(obj, prop, &self.heads)
+ }
+
+ fn get_all_at<O: AsRef<ObjId>, P: Into<Prop>>(
+ &self,
+ obj: O,
+ prop: P,
+ heads: &[ChangeHash],
+ ) -> Result<Vec<(Value<'_>, ObjId)>, AutomergeError> {
+ self.doc.get_all_at(obj, prop, heads)
+ }
+
+ fn get_missing_deps(&self, heads: &[ChangeHash]) -> Vec<ChangeHash> {
+ self.doc.get_missing_deps(heads)
+ }
+
+ fn get_change_by_hash(&self, hash: &ChangeHash) -> Option<Change> {
+ self.doc.get_change_by_hash(hash)
+ }
+
+ fn stats(&self) -> Stats {
+ self.doc.stats()
+ }
+
+ fn text_encoding(&self) -> TextEncoding {
+ self.doc.text_encoding()
+ }
+}
diff --git a/src/document/mod.rs b/src/document/mod.rs
index fa1fa77..b9a8b6b 100644
--- a/src/document/mod.rs
+++ b/src/document/mod.rs
@@ -34,6 +34,7 @@ pub(crate) fn actor(doc: &Document) -> ActorId {
doc.get_actor().clone()
}
+pub(crate) mod at;
pub(crate) mod changes;
pub(crate) mod file;
pub(crate) mod lock;
diff --git a/src/document/store.rs b/src/document/store.rs
index c2c0622..c16767b 100644
--- a/src/document/store.rs
+++ b/src/document/store.rs
@@ -94,6 +94,9 @@ pub(crate) enum StoreError {
/// An index past a list's end (`length` items).
#[error("index {index} is past the list's end ({length} items)")]
PastTheEnd { index: usize, length: usize },
+ /// A read at heads the document doesn't have (`at::At`).
+ #[error("this node doesn't have these heads (yet)")]
+ UnknownHeads,
/// The node is rejoining a newer epoch: no writes until it is done
/// (`Replica::writable`).
#[error("the node is rejoining a newer epoch; writes are refused until it is done")]