From 1b9239c9144572784dca6d49b92179b2cb57108e Mon Sep 17 00:00:00 2001 From: tv Date: Sun, 1 Nov 2026 22:29:07 +0100 Subject: api: allow reading past versions --- src/document/at.rs | 249 +++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 249 insertions(+) create mode 100644 src/document/at.rs (limited to 'src/document/at.rs') 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, +} + +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 { + 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>(&self, obj: O) -> Result, AutomergeError> { + self.doc.parents_at(obj, &self.heads) + } + + fn parents_at>( + &self, + obj: O, + heads: &[ChangeHash], + ) -> Result, AutomergeError> { + self.doc.parents_at(obj, heads) + } + + fn keys>(&self, obj: O) -> Keys<'_> { + self.doc.keys_at(obj, &self.heads) + } + + fn keys_at>(&self, obj: O, heads: &[ChangeHash]) -> Keys<'_> { + self.doc.keys_at(obj, heads) + } + + fn iter_at>(&self, obj: O, heads: Option<&[ChangeHash]>) -> DocIter<'_> { + self.doc.iter_at(obj, self.or_ours(heads)) + } + + fn map_range<'b, O: AsRef, R: RangeBounds + '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, R: RangeBounds + 'b>( + &'b self, + obj: O, + range: R, + heads: &[ChangeHash], + ) -> MapRange<'b> { + self.doc.map_range_at(obj, range, heads) + } + + fn list_range, R: RangeBounds>( + &self, + obj: O, + range: R, + ) -> ListRange<'_> { + self.doc.list_range_at(obj, range, &self.heads) + } + + fn list_range_at, R: RangeBounds>( + &self, + obj: O, + range: R, + heads: &[ChangeHash], + ) -> ListRange<'_> { + self.doc.list_range_at(obj, range, heads) + } + + fn values>(&self, obj: O) -> Values<'_> { + self.doc.values_at(obj, &self.heads) + } + + fn values_at>(&self, obj: O, heads: &[ChangeHash]) -> Values<'_> { + self.doc.values_at(obj, heads) + } + + fn length>(&self, obj: O) -> usize { + self.doc.length_at(obj, &self.heads) + } + + fn length_at>(&self, obj: O, heads: &[ChangeHash]) -> usize { + self.doc.length_at(obj, heads) + } + + fn object_type>(&self, obj: O) -> Result { + // An object's type is fixed when it is made. + self.doc.object_type(obj) + } + + fn marks>(&self, obj: O) -> Result, AutomergeError> { + self.doc.marks_at(obj, &self.heads) + } + + fn marks_at>( + &self, + obj: O, + heads: &[ChangeHash], + ) -> Result, AutomergeError> { + self.doc.marks_at(obj, heads) + } + + fn get_marks>( + &self, + obj: O, + index: usize, + heads: Option<&[ChangeHash]>, + ) -> Result { + self.doc.get_marks(obj, index, self.or_ours(heads)) + } + + fn text>(&self, obj: O) -> Result { + self.doc.text_at(obj, &self.heads) + } + + fn text_at>( + &self, + obj: O, + heads: &[ChangeHash], + ) -> Result { + self.doc.text_at(obj, heads) + } + + fn spans>(&self, obj: O) -> Result, AutomergeError> { + self.doc.spans_at(obj, &self.heads) + } + + fn spans_at>( + &self, + obj: O, + heads: &[ChangeHash], + ) -> Result, AutomergeError> { + self.doc.spans_at(obj, heads) + } + + fn get_cursor, I: Into>( + &self, + obj: O, + position: I, + at: Option<&[ChangeHash]>, + ) -> Result { + self.doc.get_cursor(obj, position, self.or_ours(at)) + } + + fn get_cursor_moving, I: Into>( + &self, + obj: O, + position: I, + at: Option<&[ChangeHash]>, + move_cursor: MoveCursor, + ) -> Result { + self.doc + .get_cursor_moving(obj, position, self.or_ours(at), move_cursor) + } + + fn get_cursor_position>( + &self, + obj: O, + cursor: &Cursor, + at: Option<&[ChangeHash]>, + ) -> Result { + self.doc.get_cursor_position(obj, cursor, self.or_ours(at)) + } + + fn get, P: Into>( + &self, + obj: O, + prop: P, + ) -> Result, ObjId)>, AutomergeError> { + self.doc.get_at(obj, prop, &self.heads) + } + + fn get_at, P: Into>( + &self, + obj: O, + prop: P, + heads: &[ChangeHash], + ) -> Result, ObjId)>, AutomergeError> { + self.doc.get_at(obj, prop, heads) + } + + fn hydrate>( + &self, + obj: O, + heads: Option<&[ChangeHash]>, + ) -> Result { + ReadDoc::hydrate(self.doc, obj, self.or_ours(heads)) + } + + fn get_all, P: Into>( + &self, + obj: O, + prop: P, + ) -> Result, ObjId)>, AutomergeError> { + self.doc.get_all_at(obj, prop, &self.heads) + } + + fn get_all_at, P: Into>( + &self, + obj: O, + prop: P, + heads: &[ChangeHash], + ) -> Result, ObjId)>, AutomergeError> { + self.doc.get_all_at(obj, prop, heads) + } + + fn get_missing_deps(&self, heads: &[ChangeHash]) -> Vec { + self.doc.get_missing_deps(heads) + } + + fn get_change_by_hash(&self, hash: &ChangeHash) -> Option { + self.doc.get_change_by_hash(hash) + } + + fn stats(&self) -> Stats { + self.doc.stats() + } + + fn text_encoding(&self) -> TextEncoding { + self.doc.text_encoding() + } +} -- cgit v1.3.1