Skip to main content

prolly/prolly/
versioned_map.rs

1//! A low-friction, linear version catalog for application indexes.
2//!
3//! [`VersionedMap`] combines immutable prolly trees, named roots, and strict
4//! transactions into one application-facing handle. Every successful logical
5//! update atomically advances a mutable head and records an immutable,
6//! content-derived version root. It intentionally stops short of commit
7//! ancestry, branches, authors, and reflogs; those belong in a repository/VCS
8//! layer.
9
10use serde::{Deserialize, Serialize};
11use std::borrow::Borrow;
12use std::collections::HashSet;
13use std::fmt;
14use std::marker::PhantomData;
15
16use super::cid::Cid;
17use super::error::{Diff, Error, Mutation};
18use super::manifest::{ManifestStore, ManifestStoreScan, NamedRootRetention, RootManifest};
19use super::range::{CursorWindow, RangeCursor, RangeIter, RangePage, ReverseCursor, ReversePage};
20use super::read::{EntryRef, OwnedValueLease, ReadSession, ValueRefView};
21use super::secondary_index::indexed_collection_root_name;
22use super::stats::TreeStats;
23use super::store::Store;
24use super::transaction::{TransactionConflict, TransactionUpdate, TransactionalStore};
25use super::tree::Tree;
26use super::{current_unix_time_millis, KeyValue, Prolly};
27
28/// Root namespace reserved for built-in versioned maps.
29pub const VERSIONED_MAP_ROOT_PREFIX: &[u8] = b"maps/versioned/";
30
31/// Maximum optimistic attempts made by the convenience mutation methods.
32pub const DEFAULT_VERSIONED_MAP_RETRIES: usize = 8;
33
34const HEAD_SUFFIX: &[u8] = b"/head";
35const VERSIONS_SUFFIX: &[u8] = b"/versions/";
36const VERSIONED_MAP_BACKUP_FORMAT_VERSION: u64 = 1;
37
38#[allow(dead_code)]
39pub(crate) enum MapWriteAuthority {
40    Unmanaged,
41}
42async fn guard_async_managed_map_write<S>(
43    tx: &super::transaction::AsyncProllyTransaction<'_, S>,
44    map_id: &[u8],
45    authority: MapWriteAuthority,
46) -> Result<(), Error>
47where
48    S: super::store::AsyncStore
49        + super::manifest::AsyncManifestStore
50        + super::transaction::AsyncTransactionalStore,
51    <S as super::store::AsyncStore>::Error: Send + Sync,
52    <S as super::manifest::AsyncManifestStore>::Error: Send + Sync,
53{
54    let canonical_root = indexed_collection_root_name(map_id)?;
55    match (
56        tx.load_named_root(&canonical_root).await?.is_some(),
57        authority,
58    ) {
59        (true, MapWriteAuthority::Unmanaged) => Err(Error::IndexesRequireIndexedMap {
60            map_id: map_id.to_vec(),
61            active_indexes: Vec::new(),
62        }),
63        (false, MapWriteAuthority::Unmanaged) => Ok(()),
64    }
65}
66
67pub(crate) fn guard_managed_map_write<S>(
68    tx: &super::transaction::ProllyTransaction<'_, S>,
69    map_id: &[u8],
70    authority: MapWriteAuthority,
71) -> Result<(), Error>
72where
73    S: Store + ManifestStore + TransactionalStore,
74{
75    let canonical_root = indexed_collection_root_name(map_id)?;
76    match (tx.load_named_root(&canonical_root)?.is_some(), authority) {
77        (true, MapWriteAuthority::Unmanaged) => Err(Error::IndexesRequireIndexedMap {
78            map_id: map_id.to_vec(),
79            active_indexes: Vec::new(),
80        }),
81        (false, MapWriteAuthority::Unmanaged) => Ok(()),
82    }
83}
84
85/// Metadata attached when authenticating a snapshot proof bundle.
86#[derive(Clone, Debug, Default, PartialEq, Eq)]
87pub struct ProofAuthentication {
88    /// Application key identifier used to select the verification secret.
89    pub key_id: Vec<u8>,
90    /// Application-specific domain separation bytes.
91    pub context: Vec<u8>,
92    /// Optional envelope issue time.
93    pub issued_at_millis: Option<u64>,
94    /// Optional envelope expiration time.
95    pub expires_at_millis: Option<u64>,
96    /// Optional replay-prevention nonce.
97    pub nonce: Vec<u8>,
98}
99
100impl ProofAuthentication {
101    /// Start proof authentication metadata for one verification key.
102    pub fn new(key_id: impl Into<Vec<u8>>) -> Self {
103        Self {
104            key_id: key_id.into(),
105            ..Self::default()
106        }
107    }
108
109    /// Set application-specific domain separation bytes.
110    pub fn with_context(mut self, context: impl Into<Vec<u8>>) -> Self {
111        self.context = context.into();
112        self
113    }
114
115    /// Set optional issue and expiration times.
116    pub fn with_validity(
117        mut self,
118        issued_at_millis: Option<u64>,
119        expires_at_millis: Option<u64>,
120    ) -> Self {
121        self.issued_at_millis = issued_at_millis;
122        self.expires_at_millis = expires_at_millis;
123        self
124    }
125
126    /// Set replay-prevention nonce bytes.
127    pub fn with_nonce(mut self, nonce: impl Into<Vec<u8>>) -> Self {
128        self.nonce = nonce.into();
129        self
130    }
131}
132
133/// Content-derived identifier for one index snapshot.
134///
135/// The identifier hashes the complete timestamp-free [`RootManifest`], so it
136/// includes both the root CID and the tree configuration. Empty trees therefore
137/// also have a stable version identifier.
138#[derive(Clone, Debug, PartialEq, Eq, Hash, Serialize, Deserialize)]
139pub struct MapVersionId(Cid);
140
141impl MapVersionId {
142    /// Compute the stable identifier for a tree handle.
143    pub fn for_tree(tree: &Tree) -> Result<Self, Error> {
144        let bytes = RootManifest::from_tree(tree).to_bytes()?;
145        Ok(Self(Cid::from_bytes(&bytes)))
146    }
147
148    /// Borrow the underlying 32-byte content identifier.
149    pub fn as_cid(&self) -> &Cid {
150        &self.0
151    }
152
153    /// Consume this identifier and return its underlying CID.
154    pub fn into_cid(self) -> Cid {
155        self.0
156    }
157
158    /// Decode a version identifier from its portable 32-byte representation.
159    ///
160    /// This is the inverse of [`MapVersionId::as_cid`] and is intended for
161    /// persistence and language-binding boundaries. It does not hash `bytes`.
162    pub fn from_bytes(bytes: &[u8]) -> Result<Self, Error> {
163        let cid = <[u8; 32]>::try_from(bytes).map_err(|_| {
164            Error::InvalidVersionedMap(format!(
165                "map version identifier must be exactly 32 bytes, got {}",
166                bytes.len()
167            ))
168        })?;
169        Ok(Self(Cid(cid)))
170    }
171}
172
173impl fmt::Display for MapVersionId {
174    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
175        for byte in self.0.as_bytes() {
176            write!(f, "{byte:02x}")?;
177        }
178        Ok(())
179    }
180}
181
182/// One durable snapshot in a versioned map.
183#[derive(Clone, Debug, PartialEq)]
184pub struct MapVersion {
185    /// Content-derived version identifier.
186    pub id: MapVersionId,
187    /// Immutable tree handle for this version.
188    pub tree: Tree,
189    /// Creation timestamp recorded by the version root, when available.
190    pub created_at_millis: Option<u64>,
191    /// Whether this snapshot is the index's current head.
192    pub is_head: bool,
193}
194
195/// Result of pruning immutable version roots from a managed map.
196///
197/// Pruning removes catalog roots, not content-addressed nodes. Run the normal
198/// retention-aware GC flow afterward to reclaim nodes no longer reachable from
199/// the remaining versions.
200#[derive(Clone, Debug, Default, PartialEq, Eq)]
201pub struct VersionPruneResult {
202    /// Versions retained by the policy, including the current head.
203    pub retained: Vec<MapVersionId>,
204    /// Version roots removed from the catalog.
205    pub removed: Vec<MapVersionId>,
206}
207
208/// Managed-map publication plus engine batch execution statistics.
209#[derive(Clone, Debug)]
210pub struct VersionedMapBatchResult {
211    /// Published managed-map version.
212    pub version: MapVersion,
213    /// Route, rewrite, and node-write counters from the engine batch.
214    pub stats: super::batch::BatchApplyStats,
215}
216
217/// One version and its self-contained node bundle in a map catalog backup.
218#[derive(Clone, Debug, PartialEq)]
219pub struct MapBackupVersion {
220    /// Stable content-derived version identifier.
221    pub id: MapVersionId,
222    /// Original catalog creation timestamp.
223    pub created_at_millis: Option<u64>,
224    /// Complete, independently verifiable tree snapshot.
225    pub bundle: super::sync::SnapshotBundle,
226}
227
228/// Portable backup of one complete managed-map catalog.
229#[derive(Clone, Debug, PartialEq)]
230pub struct VersionedMapBackup {
231    /// Application map identifier.
232    pub map_id: Vec<u8>,
233    /// Version that was head when the backup was created.
234    pub head: MapVersionId,
235    /// Every cataloged immutable version.
236    pub versions: Vec<MapBackupVersion>,
237}
238
239#[derive(Serialize, Deserialize)]
240struct VersionedMapBackupWire {
241    version: u64,
242    map_id: Vec<u8>,
243    head: MapVersionId,
244    versions: Vec<MapBackupVersionWire>,
245}
246
247#[derive(Serialize, Deserialize)]
248struct MapBackupVersionWire {
249    id: MapVersionId,
250    created_at_millis: Option<u64>,
251    bundle: Vec<u8>,
252}
253
254impl VersionedMapBackup {
255    /// Verify version IDs, bundle completeness, uniqueness, and head presence.
256    pub fn verify(&self) -> Result<(), Error> {
257        let mut seen = HashSet::with_capacity(self.versions.len());
258        let mut found_head = false;
259        for version in &self.versions {
260            if !seen.insert(version.id.clone()) {
261                return Err(Error::InvalidVersionedMap(format!(
262                    "backup contains duplicate version {}",
263                    version.id
264                )));
265            }
266            let verification = version.bundle.verify()?;
267            if !verification.valid {
268                return Err(Error::InvalidVersionedMap(format!(
269                    "backup version {} is not self-contained",
270                    version.id
271                )));
272            }
273            let actual = MapVersionId::for_tree(&version.bundle.tree)?;
274            if actual != version.id {
275                return Err(Error::InvalidVersionedMap(format!(
276                    "backup version {} contains tree {}",
277                    version.id, actual
278                )));
279            }
280            found_head |= version.id == self.head;
281        }
282        if !found_head {
283            return Err(Error::InvalidVersionedMap(format!(
284                "backup head {} is absent from its catalog",
285                self.head
286            )));
287        }
288        Ok(())
289    }
290
291    /// Serialize this backup as deterministic versioned CBOR.
292    pub fn to_bytes(&self) -> Result<Vec<u8>, Error> {
293        self.verify()?;
294        let wire = VersionedMapBackupWire {
295            version: VERSIONED_MAP_BACKUP_FORMAT_VERSION,
296            map_id: self.map_id.clone(),
297            head: self.head.clone(),
298            versions: self
299                .versions
300                .iter()
301                .map(|version| {
302                    Ok(MapBackupVersionWire {
303                        id: version.id.clone(),
304                        created_at_millis: version.created_at_millis,
305                        bundle: version.bundle.to_bytes()?,
306                    })
307                })
308                .collect::<Result<Vec<_>, Error>>()?,
309        };
310        serde_cbor::ser::to_vec_packed(&wire).map_err(|err| Error::Serialize(err.to_string()))
311    }
312
313    /// Decode and fully verify a portable backup.
314    pub fn from_bytes(bytes: &[u8]) -> Result<Self, Error> {
315        let wire: VersionedMapBackupWire =
316            serde_cbor::from_slice(bytes).map_err(|err| Error::Deserialize(err.to_string()))?;
317        if wire.version != VERSIONED_MAP_BACKUP_FORMAT_VERSION {
318            return Err(Error::InvalidVersionedMap(format!(
319                "unsupported backup format version {}",
320                wire.version
321            )));
322        }
323        let backup = Self {
324            map_id: wire.map_id,
325            head: wire.head,
326            versions: wire
327                .versions
328                .into_iter()
329                .map(|version| {
330                    Ok(MapBackupVersion {
331                        id: version.id,
332                        created_at_millis: version.created_at_millis,
333                        bundle: super::sync::SnapshotBundle::from_bytes(&version.bundle)?,
334                    })
335                })
336                .collect::<Result<Vec<_>, Error>>()?,
337        };
338        backup.verify()?;
339        Ok(backup)
340    }
341}
342
343impl VersionPruneResult {
344    /// Number of immutable version roots removed.
345    pub fn removed_count(&self) -> usize {
346        self.removed.len()
347    }
348
349    /// Whether pruning left the catalog unchanged.
350    pub fn is_unchanged(&self) -> bool {
351        self.removed.is_empty()
352    }
353}
354
355impl MapVersion {
356    pub(crate) fn new(
357        tree: Tree,
358        created_at_millis: Option<u64>,
359        is_head: bool,
360    ) -> Result<Self, Error> {
361        Ok(Self {
362            id: MapVersionId::for_tree(&tree)?,
363            tree,
364            created_at_millis,
365            is_head,
366        })
367    }
368}
369
370/// An immutable, version-pinned view over one managed map snapshot.
371///
372/// A snapshot owns its [`MapVersion`] handle and borrows the engine. All reads
373/// stay on that tree even when another writer advances the managed map's head.
374/// This is the preferred surface for request-scoped reads, long scans, proofs,
375/// export, and diagnostics.
376pub struct MapSnapshot<'a, S: Store> {
377    prolly: &'a Prolly<S>,
378    version: MapVersion,
379}
380
381/// Lazy descending iterator backed by bounded reverse pages.
382pub struct MapReverseIter<'a, S: Store> {
383    prolly: &'a Prolly<S>,
384    tree: Tree,
385    start: Vec<u8>,
386    prefix: Option<Vec<u8>>,
387    cursor: ReverseCursor,
388    page_size: usize,
389    buffered: std::vec::IntoIter<(Vec<u8>, Vec<u8>)>,
390    finished: bool,
391}
392
393impl<'a, S: Store> MapReverseIter<'a, S> {
394    fn new(
395        prolly: &'a Prolly<S>,
396        tree: Tree,
397        start: Vec<u8>,
398        prefix: Option<Vec<u8>>,
399        page_size: usize,
400    ) -> Self {
401        Self {
402            prolly,
403            tree,
404            start,
405            prefix,
406            cursor: ReverseCursor::end(),
407            page_size: page_size.max(1),
408            buffered: Vec::new().into_iter(),
409            finished: false,
410        }
411    }
412}
413
414impl<S: Store> Iterator for MapReverseIter<'_, S> {
415    type Item = Result<(Vec<u8>, Vec<u8>), Error>;
416
417    fn next(&mut self) -> Option<Self::Item> {
418        loop {
419            if let Some(entry) = self.buffered.next() {
420                return Some(Ok(entry));
421            }
422            if self.finished {
423                return None;
424            }
425            let page = match &self.prefix {
426                Some(prefix) => self.prolly.prefix_reverse_page(
427                    &self.tree,
428                    prefix,
429                    &self.cursor,
430                    self.page_size,
431                ),
432                None => {
433                    self.prolly
434                        .reverse_page(&self.tree, &self.cursor, &self.start, self.page_size)
435                }
436            };
437            match page {
438                Ok(page) => {
439                    self.finished = page.next_cursor.is_none();
440                    if let Some(cursor) = page.next_cursor {
441                        self.cursor = cursor;
442                    }
443                    self.buffered = page.entries.into_iter();
444                }
445                Err(error) => {
446                    self.finished = true;
447                    return Some(Err(error));
448                }
449            }
450        }
451    }
452}
453
454/// A version-pinned comparison between two snapshots of the same managed map.
455pub struct MapComparison<'a, S: Store> {
456    prolly: &'a Prolly<S>,
457    base: MapVersion,
458    target: MapVersion,
459}
460
461/// A three-way merge pinned to a base, current head, and candidate version.
462pub struct MapMerge<'a, S: Store> {
463    prolly: &'a Prolly<S>,
464    map_id: Vec<u8>,
465    base: MapVersion,
466    head: MapVersion,
467    candidate: MapVersion,
468}
469
470impl<'a, S: Store> MapMerge<'a, S> {
471    fn new(
472        prolly: &'a Prolly<S>,
473        map_id: Vec<u8>,
474        base: MapVersion,
475        head: MapVersion,
476        candidate: MapVersion,
477    ) -> Self {
478        Self {
479            prolly,
480            map_id,
481            base,
482            head,
483            candidate,
484        }
485    }
486
487    /// Common ancestor selected for the merge.
488    pub fn base(&self) -> &MapVersion {
489        &self.base
490    }
491
492    /// Head that must still be current when publishing.
493    pub fn head(&self) -> &MapVersion {
494        &self.head
495    }
496
497    /// Candidate version whose changes are being merged.
498    pub fn candidate(&self) -> &MapVersion {
499        &self.candidate
500    }
501
502    /// Lazily stream conflicts without constructing a merged tree.
503    pub fn stream_conflicts<'s>(
504        &'s self,
505    ) -> Result<Box<dyn Iterator<Item = Result<super::error::Conflict, Error>> + 's>, Error> {
506        self.prolly
507            .stream_conflicts(&self.base.tree, &self.head.tree, &self.candidate.tree)
508    }
509
510    /// Build the merged tree without moving head.
511    pub fn merge(&self, resolver: Option<super::error::Resolver>) -> Result<Tree, Error> {
512        self.prolly.merge(
513            &self.base.tree,
514            &self.head.tree,
515            &self.candidate.tree,
516            resolver,
517        )
518    }
519
520    /// Build a merged tree using a prefix/exact-key policy registry.
521    pub fn merge_with_policy(
522        &self,
523        policies: &super::policy::MergePolicyRegistry,
524    ) -> Result<Tree, Error> {
525        self.merge(Some(policies.as_resolver()))
526    }
527
528    /// Build a conflict-free merged tree using CRDT semantics.
529    pub fn crdt_merge(&self, config: &super::crdt::CrdtConfig) -> Result<Tree, Error> {
530        self.prolly.crdt_merge(
531            &self.base.tree,
532            &self.head.tree,
533            &self.candidate.tree,
534            config,
535        )
536    }
537
538    /// Build a conflict-free merged tree and retain engine diagnostics.
539    pub fn crdt_merge_explain(
540        &self,
541        config: &super::crdt::CrdtConfig,
542    ) -> super::diff::MergeExplanation {
543        self.prolly.crdt_merge_explain(
544            &self.base.tree,
545            &self.head.tree,
546            &self.candidate.tree,
547            config,
548        )
549    }
550
551    /// Merge and publish only if the pinned head is still current.
552    pub fn publish(
553        &self,
554        resolver: Option<super::error::Resolver>,
555    ) -> Result<VersionedMapUpdate, Error>
556    where
557        S: ManifestStore + TransactionalStore,
558    {
559        let merged = self.merge(resolver)?;
560        let map = VersionedMap::new(self.prolly, &self.map_id);
561        map.publish_tree_if(Some(&self.head.id), &merged, current_unix_time_millis())
562    }
563
564    /// Merge with policies and CAS-publish the result.
565    pub fn publish_with_policy(
566        &self,
567        policies: &super::policy::MergePolicyRegistry,
568    ) -> Result<VersionedMapUpdate, Error>
569    where
570        S: ManifestStore + TransactionalStore,
571    {
572        self.publish(Some(policies.as_resolver()))
573    }
574
575    /// CRDT-merge and CAS-publish the result.
576    pub fn publish_crdt(
577        &self,
578        config: &super::crdt::CrdtConfig,
579    ) -> Result<VersionedMapUpdate, Error>
580    where
581        S: ManifestStore + TransactionalStore,
582    {
583        let merged = self.crdt_merge(config)?;
584        let map = VersionedMap::new(self.prolly, &self.map_id);
585        map.publish_tree_if(Some(&self.head.id), &merged, current_unix_time_millis())
586    }
587}
588
589impl<'a, S: Store> MapComparison<'a, S> {
590    fn new(prolly: &'a Prolly<S>, base: MapVersion, target: MapVersion) -> Self {
591        Self {
592            prolly,
593            base,
594            target,
595        }
596    }
597
598    /// Baseline version.
599    pub fn base(&self) -> &MapVersion {
600        &self.base
601    }
602
603    /// Target version.
604    pub fn target(&self) -> &MapVersion {
605        &self.target
606    }
607
608    /// Collect every logical difference.
609    pub fn diff(&self) -> Result<Vec<Diff>, Error> {
610        self.prolly.diff(&self.base.tree, &self.target.tree)
611    }
612
613    /// Lazily stream logical differences.
614    pub fn stream_diff<'s>(
615        &'s self,
616    ) -> Result<Box<dyn Iterator<Item = Result<Diff, Error>> + 's>, Error> {
617        self.prolly.stream_diff(&self.base.tree, &self.target.tree)
618    }
619
620    /// Read one resumable key-cursor diff page.
621    pub fn diff_page(
622        &self,
623        cursor: &RangeCursor,
624        end: Option<&[u8]>,
625        limit: usize,
626    ) -> Result<super::diff::DiffPage, Error> {
627        self.prolly
628            .diff_page(&self.base.tree, &self.target.tree, cursor, end, limit)
629    }
630
631    /// Read one structural diff page while preserving the CID frontier.
632    pub fn structural_diff_page(
633        &self,
634        cursor: Option<&super::diff::StructuralDiffCursor>,
635        limit: usize,
636    ) -> Result<super::diff::StructuralDiffPage, Error> {
637        self.prolly
638            .structural_diff_page(&self.base.tree, &self.target.tree, cursor, limit)
639    }
640
641    /// Read and prove one bounded diff page.
642    pub fn prove_diff_page(
643        &self,
644        cursor: &RangeCursor,
645        end: Option<&[u8]>,
646        limit: usize,
647    ) -> Result<super::proof::ProvedDiffPage, Error> {
648        self.prolly
649            .prove_diff_page(&self.base.tree, &self.target.tree, cursor, end, limit)
650    }
651
652    /// Compare shape, entry counts, and serialized size.
653    pub fn stats(&self) -> Result<super::stats::StatsComparison, Error> {
654        self.prolly.stats_diff(&self.base.tree, &self.target.tree)
655    }
656
657    /// Compare shared and rewritten tree nodes.
658    pub fn debug_view(&self) -> Result<super::debug::TreeDebugComparison, Error> {
659        self.prolly
660            .debug_compare_trees(&self.base.tree, &self.target.tree)
661    }
662
663    /// Publish correctness-optional changed-span hints for this transition.
664    pub fn publish_changed_spans<I>(&self, spans: I) -> Result<bool, Error>
665    where
666        I: IntoIterator<Item = super::ChangedSpan>,
667    {
668        self.prolly
669            .publish_changed_spans_hint(&self.base.tree, &self.target.tree, spans)
670    }
671
672    /// Load correctness-optional changed-span hints for this transition.
673    pub fn changed_spans(&self) -> Result<Option<super::ChangedSpanHint>, Error> {
674        self.prolly
675            .load_changed_spans_hint(&self.base.tree, &self.target.tree)
676    }
677}
678
679impl<'a, S: Store> MapSnapshot<'a, S> {
680    pub(crate) fn new(prolly: &'a Prolly<S>, version: MapVersion) -> Self {
681        Self { prolly, version }
682    }
683
684    pub(crate) fn from_tree(
685        prolly: &'a Prolly<S>,
686        tree: Tree,
687        is_head: bool,
688    ) -> Result<Self, Error> {
689        Ok(Self::new(prolly, MapVersion::new(tree, None, is_head)?))
690    }
691
692    /// Metadata and tree handle for this pinned version.
693    pub fn version(&self) -> &MapVersion {
694        &self.version
695    }
696
697    /// Stable content-derived identifier for this pinned version.
698    pub fn id(&self) -> &MapVersionId {
699        &self.version.id
700    }
701
702    /// Immutable tree handle used by this snapshot.
703    pub fn tree(&self) -> &Tree {
704        &self.version.tree
705    }
706
707    /// Open a reusable zero-copy read session pinned to this snapshot.
708    pub fn read<'snapshot>(&'snapshot self) -> Result<ReadSession<'a, 'snapshot, S>, Error> {
709        self.prolly.read(self.tree())
710    }
711
712    /// Read one key through a callback-scoped borrowed value.
713    pub fn get_with<R>(
714        &self,
715        key: &[u8],
716        read: impl FnOnce(&[u8]) -> R,
717    ) -> Result<Option<R>, Error> {
718        self.prolly.get_with(self.tree(), key, read)
719    }
720
721    /// Inspect an inline/blob envelope without copying inline payload bytes.
722    pub fn get_value_ref_with<R>(
723        &self,
724        key: &[u8],
725        read: impl for<'value> FnOnce(ValueRefView<'value>) -> R,
726    ) -> Result<Option<R>, Error> {
727        self.prolly.get_value_ref_with(self.tree(), key, read)
728    }
729
730    /// Visit ordered point-read results without owning hit values.
731    pub fn get_many_with<K, F>(&self, keys: &[K], visit: F) -> Result<(), Error>
732    where
733        K: AsRef<[u8]>,
734        F: for<'value> FnMut(usize, &[u8], Option<&'value [u8]>),
735    {
736        self.prolly.get_many_with(self.tree(), keys, visit)
737    }
738
739    /// Visit a snapshot-pinned half-open range without per-row allocation.
740    pub fn scan_range(
741        &self,
742        start: &[u8],
743        end: Option<&[u8]>,
744        visit: impl for<'entry> FnMut(EntryRef<'entry>),
745    ) -> Result<u64, Error> {
746        self.prolly.scan_range(self.tree(), start, end, visit)
747    }
748
749    /// Visit a snapshot-pinned prefix without per-row allocation.
750    pub fn scan_prefix(
751        &self,
752        prefix: &[u8],
753        visit: impl for<'entry> FnMut(EntryRef<'entry>),
754    ) -> Result<u64, Error> {
755        self.prolly.scan_prefix(self.tree(), prefix, visit)
756    }
757
758    /// Read one key.
759    pub fn get(&self, key: &[u8]) -> Result<Option<Vec<u8>>, Error> {
760        self.prolly.get(self.tree(), key)
761    }
762
763    /// Read the stored inline/blob reference without resolving blob bytes.
764    pub fn get_value_ref(&self, key: &[u8]) -> Result<Option<super::blob::ValueRef>, Error> {
765        self.prolly.get_value_ref(self.tree(), key)
766    }
767
768    /// Read one value and resolve offloaded blob content when necessary.
769    pub fn get_large_value<B: super::blob::BlobStore>(
770        &self,
771        blob_store: &B,
772        key: &[u8],
773    ) -> Result<Option<Vec<u8>>, Error> {
774        self.prolly.get_large_value(blob_store, self.tree(), key)
775    }
776
777    /// Check whether one key exists.
778    pub fn contains_key(&self, key: &[u8]) -> Result<bool, Error> {
779        Ok(self.get(key)?.is_some())
780    }
781
782    /// Read several keys while preserving caller order and duplicates.
783    pub fn get_many<K: AsRef<[u8]>>(&self, keys: &[K]) -> Result<Vec<Option<Vec<u8>>>, Error> {
784        self.prolly.get_many(self.tree(), keys)
785    }
786
787    /// Return the first entry in key order.
788    pub fn first_entry(&self) -> Result<Option<KeyValue>, Error> {
789        self.prolly.first_entry(self.tree())
790    }
791
792    /// Return the last entry in key order.
793    pub fn last_entry(&self) -> Result<Option<KeyValue>, Error> {
794        self.prolly.last_entry(self.tree())
795    }
796
797    /// Return the first entry whose key is greater than or equal to `key`.
798    pub fn lower_bound(&self, key: &[u8]) -> Result<Option<KeyValue>, Error> {
799        self.prolly.lower_bound(self.tree(), key)
800    }
801
802    /// Return the first entry whose key is strictly greater than `key`.
803    pub fn upper_bound(&self, key: &[u8]) -> Result<Option<KeyValue>, Error> {
804        self.prolly.upper_bound(self.tree(), key)
805    }
806
807    /// Lazily stream a half-open key range from this immutable snapshot.
808    pub fn range<'s>(
809        &'s self,
810        start: &[u8],
811        end: Option<&[u8]>,
812    ) -> Result<RangeIter<'s, S>, Error> {
813        self.prolly.range(self.tree(), start, end)
814    }
815
816    /// Explicit alias for [`MapSnapshot::range`] emphasizing lazy large scans.
817    pub fn stream_range<'s>(
818        &'s self,
819        start: &[u8],
820        end: Option<&[u8]>,
821    ) -> Result<RangeIter<'s, S>, Error> {
822        self.range(start, end)
823    }
824
825    /// Lazily stream every entry under `prefix`.
826    pub fn prefix<'s>(&'s self, prefix: &[u8]) -> Result<RangeIter<'s, S>, Error> {
827        self.prolly.prefix(self.tree(), prefix)
828    }
829
830    /// Explicit alias for [`MapSnapshot::prefix`] emphasizing lazy large scans.
831    pub fn stream_prefix<'s>(&'s self, prefix: &[u8]) -> Result<RangeIter<'s, S>, Error> {
832        self.prefix(prefix)
833    }
834
835    /// Read one forward cursor page.
836    pub fn range_page(
837        &self,
838        cursor: &RangeCursor,
839        end: Option<&[u8]>,
840        limit: usize,
841    ) -> Result<RangePage, Error> {
842        self.prolly.range_page(self.tree(), cursor, end, limit)
843    }
844
845    /// Read one prefix-bounded forward cursor page.
846    pub fn prefix_page(
847        &self,
848        prefix: &[u8],
849        cursor: &RangeCursor,
850        limit: usize,
851    ) -> Result<RangePage, Error> {
852        self.prolly.prefix_page(self.tree(), prefix, cursor, limit)
853    }
854
855    /// Read one reverse cursor page from the end of `[start, +inf)`.
856    pub fn reverse_page(
857        &self,
858        cursor: &ReverseCursor,
859        start: &[u8],
860        limit: usize,
861    ) -> Result<ReversePage, Error> {
862        self.prolly.reverse_page(self.tree(), cursor, start, limit)
863    }
864
865    /// Read one reverse cursor page inside `prefix`.
866    pub fn prefix_reverse_page(
867        &self,
868        prefix: &[u8],
869        cursor: &ReverseCursor,
870        limit: usize,
871    ) -> Result<ReversePage, Error> {
872        self.prolly
873            .prefix_reverse_page(self.tree(), prefix, cursor, limit)
874    }
875
876    /// Lazily scan `[start, +inf)` in descending key order.
877    ///
878    /// `page_size` bounds each internal store traversal and is clamped to one.
879    pub fn reverse_scan(&self, start: &[u8], page_size: usize) -> MapReverseIter<'a, S> {
880        MapReverseIter::new(
881            self.prolly,
882            self.tree().clone(),
883            start.to_vec(),
884            None,
885            page_size,
886        )
887    }
888
889    /// Lazily scan one prefix in descending key order.
890    pub fn prefix_reverse_scan(&self, prefix: &[u8], page_size: usize) -> MapReverseIter<'a, S> {
891        MapReverseIter::new(
892            self.prolly,
893            self.tree().clone(),
894            prefix.to_vec(),
895            Some(prefix.to_vec()),
896            page_size,
897        )
898    }
899
900    /// Seek to `key` and return a bounded forward window.
901    pub fn cursor_window(
902        &self,
903        key: &[u8],
904        end: Option<&[u8]>,
905        limit: usize,
906    ) -> Result<CursorWindow, Error> {
907        self.prolly.cursor_window(self.tree(), key, end, limit)
908    }
909
910    /// Collect structural and serialized-size statistics for this snapshot.
911    pub fn stats(&self) -> Result<TreeStats, Error> {
912        self.prolly.collect_stats(self.tree())
913    }
914
915    /// Return a deterministic diagnostic view grouped by tree level.
916    pub fn debug_view(&self) -> Result<super::debug::TreeDebugView, Error> {
917        self.prolly.debug_tree(self.tree())
918    }
919
920    /// Build a self-contained proof of one key's presence or absence.
921    pub fn prove_key(&self, key: &[u8]) -> Result<super::proof::KeyProof, Error> {
922        self.prolly.prove_key(self.tree(), key)
923    }
924
925    /// Build one shared proof for several keys.
926    pub fn prove_keys<K: AsRef<[u8]>>(
927        &self,
928        keys: &[K],
929    ) -> Result<super::proof::MultiKeyProof, Error> {
930        self.prolly.prove_keys(self.tree(), keys)
931    }
932
933    /// Build a complete proof for `[start, end)`.
934    pub fn prove_range(
935        &self,
936        start: &[u8],
937        end: Option<&[u8]>,
938    ) -> Result<super::proof::RangeProof, Error> {
939        self.prolly.prove_range(self.tree(), start, end)
940    }
941
942    /// Build a complete proof for all entries under `prefix`.
943    pub fn prove_prefix(&self, prefix: &[u8]) -> Result<super::proof::RangeProof, Error> {
944        self.prolly.prove_prefix(self.tree(), prefix)
945    }
946
947    /// Read and prove one cursor page.
948    pub fn prove_range_page(
949        &self,
950        cursor: &RangeCursor,
951        end: Option<&[u8]>,
952        limit: usize,
953    ) -> Result<super::proof::ProvedRangePage, Error> {
954        self.prolly
955            .prove_range_page(self.tree(), cursor, end, limit)
956    }
957
958    /// Authenticate canonical proof bundle bytes with HMAC-SHA256.
959    pub fn authenticate_proof_bundle(
960        &self,
961        proof_bundle: impl Into<Vec<u8>>,
962        secret: &[u8],
963        authentication: ProofAuthentication,
964    ) -> Result<super::proof::AuthenticatedProofEnvelope, Error> {
965        super::proof::sign_proof_bundle_hmac_sha256(
966            proof_bundle,
967            authentication.key_id,
968            secret,
969            authentication.context,
970            authentication.issued_at_millis,
971            authentication.expires_at_millis,
972            authentication.nonce,
973        )
974    }
975
976    /// Export this tree and every reachable node as a portable verified bundle.
977    pub fn export(&self) -> Result<super::sync::SnapshotBundle, Error> {
978        self.prolly.export_snapshot(self.tree())
979    }
980
981    /// Plan which nodes another store is missing for this snapshot.
982    pub fn plan_missing_nodes<D: Store>(
983        &self,
984        destination: &D,
985    ) -> Result<super::sync::MissingNodePlan, Error> {
986        self.prolly.plan_missing_nodes(self.tree(), destination)
987    }
988
989    /// Copy this snapshot's missing nodes into another store.
990    pub fn copy_missing_nodes<D: Store>(
991        &self,
992        destination: &D,
993    ) -> Result<super::sync::MissingNodeCopy, Error> {
994        self.prolly.copy_missing_nodes(self.tree(), destination)
995    }
996
997    /// Copy and publish this snapshot as the head of another managed map.
998    pub fn push_to<D>(&self, destination: &VersionedMap<'_, D>) -> Result<MapVersion, Error>
999    where
1000        D: Store + ManifestStore + TransactionalStore,
1001    {
1002        let bundle = self.export()?;
1003        destination.import_as_head(&bundle)
1004    }
1005
1006    /// Pin this snapshot's root in the engine cache.
1007    pub fn pin_root(&self) -> Result<usize, Error> {
1008        self.prolly.pin_tree_root(self.tree())
1009    }
1010
1011    /// Pin the root-to-leaf path for one hot key or prefix.
1012    pub fn pin_path(&self, key: &[u8]) -> Result<usize, Error> {
1013        self.prolly.pin_tree_path(self.tree(), key)
1014    }
1015
1016    /// Persist a correctness-optional hot-prefix path hint.
1017    pub fn publish_prefix_hint(&self, prefix: &[u8]) -> Result<bool, Error> {
1018        self.prolly.publish_prefix_path_hint(self.tree(), prefix)
1019    }
1020
1021    /// Hydrate the engine cache from a previously published prefix hint.
1022    pub fn hydrate_prefix_hint(&self, prefix: &[u8]) -> Result<bool, Error> {
1023        self.prolly.hydrate_prefix_path_hint(self.tree(), prefix)
1024    }
1025}
1026
1027/// Outcome of a compare-and-update operation.
1028#[derive(Clone, Debug, PartialEq)]
1029pub enum VersionedMapUpdate {
1030    /// The update committed and produced this version.
1031    Applied {
1032        /// Previous head, or `None` when the index was initialized.
1033        previous: Option<MapVersionId>,
1034        /// New current version.
1035        current: MapVersion,
1036    },
1037    /// The requested mutations did not change the current tree.
1038    Unchanged {
1039        /// Current version, or `None` for an absent index and an empty edit.
1040        current: Option<MapVersion>,
1041    },
1042    /// The caller's expected head did not match the current head.
1043    Conflict {
1044        /// Current head at the time the conflict was observed.
1045        current: Option<MapVersion>,
1046    },
1047}
1048
1049impl VersionedMapUpdate {
1050    /// Return the resulting current version for applied or unchanged updates.
1051    pub fn current(&self) -> Option<&MapVersion> {
1052        match self {
1053            Self::Applied { current, .. } => Some(current),
1054            Self::Unchanged { current } | Self::Conflict { current } => current.as_ref(),
1055        }
1056    }
1057
1058    /// Whether a new head was committed.
1059    pub fn is_applied(&self) -> bool {
1060        matches!(self, Self::Applied { .. })
1061    }
1062
1063    /// Whether the caller's expected head was stale.
1064    pub fn is_conflict(&self) -> bool {
1065        matches!(self, Self::Conflict { .. })
1066    }
1067}
1068
1069/// Mutation collector used by [`VersionedMap::edit`].
1070#[derive(Clone, Debug, Default)]
1071pub struct VersionedMapEditor {
1072    mutations: Vec<Mutation>,
1073}
1074
1075impl VersionedMapEditor {
1076    /// Create an empty edit.
1077    pub fn new() -> Self {
1078        Self::default()
1079    }
1080
1081    /// Insert or replace a key.
1082    pub fn put(&mut self, key: impl Into<Vec<u8>>, value: impl Into<Vec<u8>>) -> &mut Self {
1083        self.mutations.push(Mutation::Upsert {
1084            key: key.into(),
1085            val: value.into(),
1086        });
1087        self
1088    }
1089
1090    /// Delete a key.
1091    pub fn delete(&mut self, key: impl Into<Vec<u8>>) -> &mut Self {
1092        self.mutations.push(Mutation::Delete { key: key.into() });
1093        self
1094    }
1095
1096    /// Append an already constructed mutation.
1097    pub fn push(&mut self, mutation: Mutation) -> &mut Self {
1098        self.mutations.push(mutation);
1099        self
1100    }
1101
1102    /// Number of collected mutations.
1103    pub fn len(&self) -> usize {
1104        self.mutations.len()
1105    }
1106
1107    /// Whether no mutations have been collected.
1108    pub fn is_empty(&self) -> bool {
1109        self.mutations.is_empty()
1110    }
1111
1112    fn into_mutations(self) -> Vec<Mutation> {
1113        self.mutations
1114    }
1115}
1116
1117/// One strict transaction spanning any number of managed maps.
1118///
1119/// Use this to atomically update authoritative maps, secondary indexes, and
1120/// materialized views. All original heads are validated together and every new
1121/// node, immutable version root, and head movement commits as one backend
1122/// transaction.
1123pub struct VersionedMapsTransaction<'tx, 'engine, S>
1124where
1125    S: Store + ManifestStore + TransactionalStore,
1126{
1127    tx: &'tx super::transaction::ProllyTransaction<'engine, S>,
1128    timestamp_millis: u64,
1129}
1130
1131/// Codec for converting typed application keys to and from ordered map bytes.
1132pub trait KeyCodec<K> {
1133    /// Encode one typed key into its order-preserving byte representation.
1134    fn encode_key(&self, key: &K) -> Result<Vec<u8>, Error>;
1135
1136    /// Decode one stored key.
1137    fn decode_key(&self, bytes: &[u8]) -> Result<K, Error>;
1138}
1139
1140/// Identity codec for byte-vector keys.
1141#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
1142pub struct BytesKeyCodec;
1143
1144impl KeyCodec<Vec<u8>> for BytesKeyCodec {
1145    fn encode_key(&self, key: &Vec<u8>) -> Result<Vec<u8>, Error> {
1146        Ok(key.clone())
1147    }
1148
1149    fn decode_key(&self, bytes: &[u8]) -> Result<Vec<u8>, Error> {
1150        Ok(bytes.to_vec())
1151    }
1152}
1153
1154/// UTF-8 codec for string keys. Ordering follows UTF-8 byte order.
1155#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
1156pub struct StringKeyCodec;
1157
1158impl KeyCodec<String> for StringKeyCodec {
1159    fn encode_key(&self, key: &String) -> Result<Vec<u8>, Error> {
1160        Ok(key.as_bytes().to_vec())
1161    }
1162
1163    fn decode_key(&self, bytes: &[u8]) -> Result<String, Error> {
1164        String::from_utf8(bytes.to_vec()).map_err(|err| Error::Deserialize(err.to_string()))
1165    }
1166}
1167
1168/// Typed facade over a byte-oriented [`VersionedMap`].
1169pub struct TypedVersionedMap<'a, S: Store, K, V, KC, VC> {
1170    inner: VersionedMap<'a, S>,
1171    key_codec: KC,
1172    value_codec: VC,
1173    marker: PhantomData<fn() -> (K, V)>,
1174}
1175
1176/// Result of rewriting every typed value through a schema migration.
1177#[derive(Clone, Debug)]
1178pub struct TypedMigrationResult {
1179    /// Managed-map publication outcome.
1180    pub update: VersionedMapUpdate,
1181    /// Values decoded from the source schema.
1182    pub scanned_values: usize,
1183    /// Values rewritten into the target schema.
1184    pub rewritten_values: usize,
1185}
1186
1187/// One observed managed-map head transition.
1188#[derive(Clone, Debug, PartialEq)]
1189pub struct MapChangeEvent {
1190    /// Previously observed head, or `None` when observation began uninitialized.
1191    pub previous: Option<MapVersionId>,
1192    /// Newly observed head.
1193    pub current: MapVersion,
1194    /// Logical changes from `previous` to `current`.
1195    pub diffs: Vec<Diff>,
1196}
1197
1198/// Resumable in-process change subscription driven by explicit polling.
1199pub struct MapChangeSubscription<'a, S: Store> {
1200    map: VersionedMap<'a, S>,
1201    last_seen: Option<MapVersionId>,
1202}
1203
1204impl<'a, S> MapChangeSubscription<'a, S>
1205where
1206    S: Store + ManifestStore,
1207{
1208    /// Last head observed by this subscription.
1209    pub fn last_seen(&self) -> Option<&MapVersionId> {
1210        self.last_seen.as_ref()
1211    }
1212
1213    /// Poll once, returning `None` when head has not changed.
1214    pub fn poll(&mut self) -> Result<Option<MapChangeEvent>, Error> {
1215        let Some(current) = self.map.head()? else {
1216            return Ok(None);
1217        };
1218        if self.last_seen.as_ref() == Some(&current.id) {
1219            return Ok(None);
1220        }
1221        let previous_tree = match &self.last_seen {
1222            Some(id) => {
1223                self.map
1224                    .version(id)?
1225                    .ok_or_else(|| {
1226                        Error::InvalidVersionedMap(format!(
1227                            "subscription resume version {} was pruned",
1228                            id
1229                        ))
1230                    })?
1231                    .tree
1232            }
1233            None => self.map.prolly.create(),
1234        };
1235        let diffs = self.map.prolly.diff(&previous_tree, &current.tree)?;
1236        let previous = self.last_seen.replace(current.id.clone());
1237        Ok(Some(MapChangeEvent {
1238            previous,
1239            current,
1240            diffs,
1241        }))
1242    }
1243}
1244
1245impl<'a, S: Store, K, V, KC, VC> TypedVersionedMap<'a, S, K, V, KC, VC> {
1246    fn new(inner: VersionedMap<'a, S>, key_codec: KC, value_codec: VC) -> Self {
1247        Self {
1248            inner,
1249            key_codec,
1250            value_codec,
1251            marker: PhantomData,
1252        }
1253    }
1254
1255    /// Borrow the byte-oriented managed map.
1256    pub fn raw(&self) -> &VersionedMap<'a, S> {
1257        &self.inner
1258    }
1259}
1260
1261impl<S, K, V, KC, VC> TypedVersionedMap<'_, S, K, V, KC, VC>
1262where
1263    S: Store + ManifestStore,
1264    V: serde::de::DeserializeOwned,
1265    KC: KeyCodec<K>,
1266    VC: super::value::ValueCodec,
1267{
1268    /// Read and schema-validate one typed value from head.
1269    pub fn get(&self, key: &K) -> Result<Option<V>, Error> {
1270        let key = self.key_codec.encode_key(key)?;
1271        self.inner
1272            .get_with(&key, |bytes| self.value_codec.decode(bytes))?
1273            .transpose()
1274    }
1275
1276    /// Read and schema-validate one typed value from a historical version.
1277    pub fn get_at(&self, id: &MapVersionId, key: &K) -> Result<Option<V>, Error> {
1278        let key = self.key_codec.encode_key(key)?;
1279        self.inner
1280            .get_at_with(id, &key, |bytes| self.value_codec.decode(bytes))?
1281            .transpose()
1282    }
1283
1284    /// Decode all entries in key order.
1285    pub fn entries(&self) -> Result<Vec<(K, V)>, Error> {
1286        let Some(snapshot) = self.inner.snapshot()? else {
1287            return Ok(Vec::new());
1288        };
1289        snapshot
1290            .range(&[], None)?
1291            .map(|entry| {
1292                let (key, value) = entry?;
1293                Ok((
1294                    self.key_codec.decode_key(&key)?,
1295                    self.value_codec.decode(&value)?,
1296                ))
1297            })
1298            .collect()
1299    }
1300}
1301
1302impl<S, K, V, KC, VC> TypedVersionedMap<'_, S, K, V, KC, VC>
1303where
1304    S: Store + ManifestStore + TransactionalStore,
1305    V: serde::Serialize,
1306    KC: KeyCodec<K>,
1307    VC: super::value::ValueCodec,
1308{
1309    /// Encode and publish one typed value.
1310    pub fn put(&self, key: &K, value: &V) -> Result<MapVersion, Error> {
1311        self.inner.put(
1312            self.key_codec.encode_key(key)?,
1313            self.value_codec.encode(value)?,
1314        )
1315    }
1316
1317    /// Conditionally encode and publish one typed value.
1318    pub fn put_if(
1319        &self,
1320        expected: Option<&MapVersionId>,
1321        key: &K,
1322        value: &V,
1323    ) -> Result<VersionedMapUpdate, Error> {
1324        self.inner.put_if(
1325            expected,
1326            self.key_codec.encode_key(key)?,
1327            self.value_codec.encode(value)?,
1328        )
1329    }
1330
1331    /// Delete one typed key.
1332    pub fn delete(&self, key: &K) -> Result<MapVersion, Error> {
1333        self.inner.delete(self.key_codec.encode_key(key)?)
1334    }
1335
1336    /// Rewrite every value from `source_codec` through `migrate` and CAS-publish.
1337    pub fn migrate_from<Old, OVC>(
1338        &self,
1339        expected: &MapVersionId,
1340        source_codec: &OVC,
1341        mut migrate: impl FnMut(Old) -> Result<V, Error>,
1342    ) -> Result<TypedMigrationResult, Error>
1343    where
1344        Old: serde::de::DeserializeOwned,
1345        OVC: super::value::ValueCodec,
1346    {
1347        let snapshot = self.inner.snapshot_at(expected)?.ok_or_else(|| {
1348            Error::InvalidVersionedMap(format!("unknown migration source version {expected}"))
1349        })?;
1350        let mut mutations = Vec::new();
1351        let mut scanned_values = 0usize;
1352        for entry in snapshot.range(&[], None)? {
1353            let (key, bytes) = entry?;
1354            let old: Old = source_codec.decode(&bytes)?;
1355            let value = migrate(old)?;
1356            mutations.push(Mutation::Upsert {
1357                key,
1358                val: self.value_codec.encode(&value)?,
1359            });
1360            scanned_values += 1;
1361        }
1362        let update = self.inner.apply_if(Some(expected), mutations)?;
1363        Ok(TypedMigrationResult {
1364            update,
1365            scanned_values,
1366            rewritten_values: scanned_values,
1367        })
1368    }
1369}
1370
1371impl<'tx, 'engine, S> VersionedMapsTransaction<'tx, 'engine, S>
1372where
1373    S: Store + ManifestStore + TransactionalStore,
1374{
1375    fn new(
1376        tx: &'tx super::transaction::ProllyTransaction<'engine, S>,
1377        timestamp_millis: u64,
1378    ) -> Self {
1379        Self {
1380            tx,
1381            timestamp_millis,
1382        }
1383    }
1384
1385    /// Load the staged or original head for one map.
1386    pub fn head(&self, map_id: impl AsRef<[u8]>) -> Result<Option<MapVersion>, Error> {
1387        let (_, head_name, _) = versioned_map_names(map_id.as_ref());
1388        self.tx
1389            .load_named_root(&head_name)?
1390            .map(|tree| {
1391                Ok(MapVersion {
1392                    id: MapVersionId::for_tree(&tree)?,
1393                    tree,
1394                    created_at_millis: None,
1395                    is_head: true,
1396                })
1397            })
1398            .transpose()
1399    }
1400
1401    /// Read one key from a staged or original map head.
1402    pub fn get(&self, map_id: impl AsRef<[u8]>, key: &[u8]) -> Result<Option<Vec<u8>>, Error> {
1403        match self.head(map_id)? {
1404            Some(head) => self.tx.get(&head.tree, key),
1405            None => Ok(None),
1406        }
1407    }
1408
1409    /// Apply a logical mutation batch to one map inside this transaction.
1410    pub fn apply(
1411        &self,
1412        map_id: impl AsRef<[u8]>,
1413        mutations: Vec<Mutation>,
1414    ) -> Result<MapVersion, Error> {
1415        self.apply_with_authority(map_id.as_ref(), mutations, MapWriteAuthority::Unmanaged)
1416    }
1417
1418    fn apply_with_authority(
1419        &self,
1420        map_id: &[u8],
1421        mutations: Vec<Mutation>,
1422        authority: MapWriteAuthority,
1423    ) -> Result<MapVersion, Error> {
1424        guard_managed_map_write(self.tx, map_id, authority)?;
1425        let (_, head_name, versions_prefix) = versioned_map_names(map_id);
1426        let current = self.tx.load_named_root(&head_name)?;
1427        let base = current.clone().unwrap_or_else(|| self.tx.create());
1428        let next = self.tx.batch(&base, mutations)?;
1429        if current.as_ref() == Some(&next) {
1430            return Ok(MapVersion {
1431                id: MapVersionId::for_tree(&next)?,
1432                tree: next,
1433                created_at_millis: None,
1434                is_head: true,
1435            });
1436        }
1437
1438        let id = MapVersionId::for_tree(&next)?;
1439        let mut version_name = versions_prefix;
1440        version_name.extend_from_slice(id.as_cid().as_bytes());
1441        match self.tx.load_named_root(&version_name)? {
1442            Some(existing) if existing != next => {
1443                return Err(Error::InvalidVersionedMap(format!(
1444                    "content identifier collision for transaction version {}",
1445                    id
1446                )));
1447            }
1448            Some(_) => {}
1449            None => {
1450                self.tx
1451                    .publish_named_root_at_millis(&version_name, &next, self.timestamp_millis)?
1452            }
1453        }
1454        self.tx
1455            .publish_named_root_at_millis(&head_name, &next, self.timestamp_millis)?;
1456        Ok(MapVersion {
1457            id,
1458            tree: next,
1459            created_at_millis: Some(self.timestamp_millis),
1460            is_head: true,
1461        })
1462    }
1463
1464    /// Conditionally apply mutations when the staged/original head matches.
1465    pub fn apply_if(
1466        &self,
1467        map_id: impl AsRef<[u8]>,
1468        expected: Option<&MapVersionId>,
1469        mutations: Vec<Mutation>,
1470    ) -> Result<VersionedMapUpdate, Error> {
1471        let current = self.head(map_id.as_ref())?;
1472        if current.as_ref().map(|version| &version.id) != expected {
1473            return Ok(VersionedMapUpdate::Conflict { current });
1474        }
1475        let previous = current.map(|version| version.id);
1476        let current = self.apply(map_id, mutations)?;
1477        if previous.as_ref() == Some(&current.id) {
1478            Ok(VersionedMapUpdate::Unchanged {
1479                current: Some(current),
1480            })
1481        } else {
1482            Ok(VersionedMapUpdate::Applied { previous, current })
1483        }
1484    }
1485
1486    /// Put one key in one managed map.
1487    pub fn put(
1488        &self,
1489        map_id: impl AsRef<[u8]>,
1490        key: impl Into<Vec<u8>>,
1491        value: impl Into<Vec<u8>>,
1492    ) -> Result<MapVersion, Error> {
1493        self.apply(
1494            map_id,
1495            vec![Mutation::Upsert {
1496                key: key.into(),
1497                val: value.into(),
1498            }],
1499        )
1500    }
1501
1502    /// Delete one key from one managed map.
1503    pub fn delete(
1504        &self,
1505        map_id: impl AsRef<[u8]>,
1506        key: impl Into<Vec<u8>>,
1507    ) -> Result<MapVersion, Error> {
1508        self.apply(map_id, vec![Mutation::Delete { key: key.into() }])
1509    }
1510
1511    /// Collect and apply several edits to one managed map.
1512    pub fn edit(
1513        &self,
1514        map_id: impl AsRef<[u8]>,
1515        edit: impl FnOnce(&mut VersionedMapEditor),
1516    ) -> Result<MapVersion, Error> {
1517        let mut editor = VersionedMapEditor::new();
1518        edit(&mut editor);
1519        self.apply(map_id, editor.into_mutations())
1520    }
1521}
1522
1523/// Built-in versioned map facade over a [`Prolly`] engine.
1524///
1525/// Index names are hex-encoded before being placed in the named-root namespace,
1526/// so arbitrary application bytes cannot collide with another index's roots.
1527pub struct VersionedMap<'a, S: Store> {
1528    prolly: &'a Prolly<S>,
1529    id: Vec<u8>,
1530    root_prefix: Vec<u8>,
1531    head_name: Vec<u8>,
1532    versions_prefix: Vec<u8>,
1533}
1534
1535impl<'a, S: Store> VersionedMap<'a, S> {
1536    /// Create a handle for `id` using an existing engine.
1537    pub fn new(prolly: &'a Prolly<S>, id: impl AsRef<[u8]>) -> Self {
1538        let id = id.as_ref().to_vec();
1539        let (root_prefix, head_name, versions_prefix) = versioned_map_names(&id);
1540
1541        Self {
1542            prolly,
1543            id,
1544            root_prefix,
1545            head_name,
1546            versions_prefix,
1547        }
1548    }
1549
1550    /// Application-provided index identifier.
1551    pub fn id(&self) -> &[u8] {
1552        &self.id
1553    }
1554
1555    /// Full durable named-root key used for the current head.
1556    pub fn head_name(&self) -> &[u8] {
1557        &self.head_name
1558    }
1559
1560    /// Prefix containing the immutable version roots.
1561    pub fn versions_prefix(&self) -> &[u8] {
1562        &self.versions_prefix
1563    }
1564
1565    /// Add typed, schema-aware key and value codecs to this managed map.
1566    pub fn typed<K, V, KC, VC>(
1567        &self,
1568        key_codec: KC,
1569        value_codec: VC,
1570    ) -> TypedVersionedMap<'a, S, K, V, KC, VC> {
1571        TypedVersionedMap::new(
1572            VersionedMap::new(self.prolly, &self.id),
1573            key_codec,
1574            value_codec,
1575        )
1576    }
1577
1578    /// Retention policy that keeps this index's head and complete version catalog.
1579    pub fn retention_policy(&self) -> NamedRootRetention {
1580        let mut isolated_prefix = self.root_prefix.clone();
1581        isolated_prefix.push(b'/');
1582        NamedRootRetention::prefix(isolated_prefix)
1583    }
1584
1585    /// Pin the current head to an immutable request-scoped snapshot.
1586    pub fn snapshot(&self) -> Result<Option<MapSnapshot<'a, S>>, Error>
1587    where
1588        S: ManifestStore,
1589    {
1590        self.head()
1591            .map(|version| version.map(|version| MapSnapshot::new(self.prolly, version)))
1592    }
1593
1594    /// Pin one cataloged historical version to an immutable snapshot.
1595    pub fn snapshot_at(&self, id: &MapVersionId) -> Result<Option<MapSnapshot<'a, S>>, Error>
1596    where
1597        S: ManifestStore,
1598    {
1599        self.version(id)
1600            .map(|version| version.map(|version| MapSnapshot::new(self.prolly, version)))
1601    }
1602
1603    /// Pin two cataloged versions for repeatable comparison operations.
1604    pub fn compare(
1605        &self,
1606        base: &MapVersionId,
1607        target: &MapVersionId,
1608    ) -> Result<MapComparison<'a, S>, Error>
1609    where
1610        S: ManifestStore,
1611    {
1612        let base = self
1613            .version(base)?
1614            .ok_or_else(|| Error::InvalidVersionedMap(format!("unknown map version {base}")))?;
1615        let target = self
1616            .version(target)?
1617            .ok_or_else(|| Error::InvalidVersionedMap(format!("unknown map version {target}")))?;
1618        Ok(MapComparison::new(self.prolly, base, target))
1619    }
1620
1621    /// Pin one historical version and the current head for comparison.
1622    pub fn compare_to_head(&self, base: &MapVersionId) -> Result<MapComparison<'a, S>, Error>
1623    where
1624        S: ManifestStore,
1625    {
1626        let base = self
1627            .version(base)?
1628            .ok_or_else(|| Error::InvalidVersionedMap(format!("unknown map version {base}")))?;
1629        let target = self.head()?.ok_or_else(|| {
1630            Error::InvalidVersionedMap("map has not been initialized".to_string())
1631        })?;
1632        Ok(MapComparison::new(self.prolly, base, target))
1633    }
1634
1635    /// Pin a three-way merge between `base`, current head, and `candidate`.
1636    pub fn prepare_merge(
1637        &self,
1638        base: &MapVersionId,
1639        candidate: &MapVersionId,
1640    ) -> Result<MapMerge<'a, S>, Error>
1641    where
1642        S: ManifestStore,
1643    {
1644        let base = self
1645            .version(base)?
1646            .ok_or_else(|| Error::InvalidVersionedMap(format!("unknown map version {base}")))?;
1647        let candidate = self.version(candidate)?.ok_or_else(|| {
1648            Error::InvalidVersionedMap(format!("unknown map version {candidate}"))
1649        })?;
1650        let head = self.head()?.ok_or_else(|| {
1651            Error::InvalidVersionedMap("map has not been initialized".to_string())
1652        })?;
1653        Ok(MapMerge::new(
1654            self.prolly,
1655            self.id.clone(),
1656            base,
1657            head,
1658            candidate,
1659        ))
1660    }
1661
1662    fn version_name(&self, id: &MapVersionId) -> Vec<u8> {
1663        let mut name = self.versions_prefix.clone();
1664        name.extend_from_slice(id.as_cid().as_bytes());
1665        name
1666    }
1667
1668    fn async_service(
1669        &self,
1670    ) -> AsyncVersionedMap<'_, super::store::SyncStoreAsAsync<std::sync::Arc<S>>> {
1671        AsyncVersionedMap::new(&self.prolly.engine, &self.id)
1672    }
1673}
1674
1675impl<S> VersionedMap<'_, S>
1676where
1677    S: Store + ManifestStore,
1678{
1679    /// Start observing future head transitions from the current head.
1680    pub fn subscribe(&self) -> Result<MapChangeSubscription<'_, S>, Error> {
1681        Ok(MapChangeSubscription {
1682            map: VersionedMap::new(self.prolly, &self.id),
1683            last_seen: self.head_id()?,
1684        })
1685    }
1686
1687    /// Resume observing head transitions from a previously persisted version.
1688    pub fn subscribe_from(&self, last_seen: Option<MapVersionId>) -> MapChangeSubscription<'_, S> {
1689        MapChangeSubscription {
1690            map: VersionedMap::new(self.prolly, &self.id),
1691            last_seen,
1692        }
1693    }
1694
1695    /// Whether this managed map has a published head.
1696    pub fn is_initialized(&self) -> Result<bool, Error> {
1697        Ok(self.head()?.is_some())
1698    }
1699
1700    /// Load only the current content-derived version identifier.
1701    pub fn head_id(&self) -> Result<Option<MapVersionId>, Error> {
1702        Ok(self.head()?.map(|version| version.id))
1703    }
1704
1705    /// Load the current version, or `None` when the index has not been initialized.
1706    pub fn head(&self) -> Result<Option<MapVersion>, Error> {
1707        let service = self.async_service();
1708        let ready_store = self.prolly.engine.store.clone();
1709        let future = service.head();
1710        super::engine::ready::run_ready(ready_store.ready(future))
1711    }
1712
1713    /// Read a key from the current version. An absent index behaves like an empty map.
1714    pub fn get(&self, key: &[u8]) -> Result<Option<Vec<u8>>, Error> {
1715        match self.head()? {
1716            Some(version) => self.prolly.get(&version.tree, key),
1717            None => Ok(None),
1718        }
1719    }
1720
1721    /// Read a key from the current version through a borrowed callback.
1722    pub fn get_with<R>(
1723        &self,
1724        key: &[u8],
1725        read: impl FnOnce(&[u8]) -> R,
1726    ) -> Result<Option<R>, Error> {
1727        match self.head()? {
1728            Some(version) => self.prolly.get_with(&version.tree, key, read),
1729            None => Ok(None),
1730        }
1731    }
1732
1733    /// Retain the packed leaf containing a current-head value. Native language
1734    /// adapters use this for callback-scoped zero-copy reads and release the
1735    /// lease deterministically when the callback returns.
1736    pub fn get_lease(&self, key: &[u8]) -> Result<Option<OwnedValueLease>, Error> {
1737        match self.head()? {
1738            Some(version) => self.prolly.read(&version.tree)?.get_lease(key),
1739            None => Ok(None),
1740        }
1741    }
1742
1743    /// Inspect a current inline/blob envelope without copying inline bytes.
1744    pub fn get_value_ref_with<R>(
1745        &self,
1746        key: &[u8],
1747        read: impl for<'value> FnOnce(ValueRefView<'value>) -> R,
1748    ) -> Result<Option<R>, Error> {
1749        match self.head()? {
1750            Some(version) => self.prolly.get_value_ref_with(&version.tree, key, read),
1751            None => Ok(None),
1752        }
1753    }
1754
1755    /// Read one value from head and resolve offloaded blob content.
1756    pub fn get_large_value<B: super::blob::BlobStore>(
1757        &self,
1758        blob_store: &B,
1759        key: &[u8],
1760    ) -> Result<Option<Vec<u8>>, Error> {
1761        match self.snapshot()? {
1762            Some(snapshot) => snapshot.get_large_value(blob_store, key),
1763            None => Ok(None),
1764        }
1765    }
1766
1767    /// Check whether a key exists in the current version.
1768    pub fn contains_key(&self, key: &[u8]) -> Result<bool, Error> {
1769        Ok(self.get(key)?.is_some())
1770    }
1771
1772    /// Read several keys from one resolved current snapshot.
1773    ///
1774    /// Results preserve caller order and duplicate keys.
1775    pub fn get_many<K: AsRef<[u8]>>(&self, keys: &[K]) -> Result<Vec<Option<Vec<u8>>>, Error> {
1776        let tree = self
1777            .head()?
1778            .map(|version| version.tree)
1779            .unwrap_or_else(|| self.prolly.create());
1780        self.prolly.get_many(&tree, keys)
1781    }
1782
1783    /// Iterate over a range in the current version.
1784    ///
1785    /// An absent index behaves like an empty map. The iterator remains pinned
1786    /// to the resolved immutable snapshot even if another writer advances head.
1787    pub fn range<'a>(
1788        &'a self,
1789        start: &[u8],
1790        end: Option<&[u8]>,
1791    ) -> Result<RangeIter<'a, S>, Error> {
1792        let tree = self
1793            .head()?
1794            .map(|version| version.tree)
1795            .unwrap_or_else(|| self.prolly.create());
1796        self.prolly.range(&tree, start, end)
1797    }
1798
1799    /// Iterate over keys with `prefix` in the current version.
1800    pub fn prefix<'a>(&'a self, prefix: &[u8]) -> Result<RangeIter<'a, S>, Error> {
1801        let tree = self
1802            .head()?
1803            .map(|version| version.tree)
1804            .unwrap_or_else(|| self.prolly.create());
1805        self.prolly.prefix(&tree, prefix)
1806    }
1807
1808    /// Visit a current-snapshot range without owning rows.
1809    pub fn scan_range(
1810        &self,
1811        start: &[u8],
1812        end: Option<&[u8]>,
1813        visit: impl for<'entry> FnMut(EntryRef<'entry>),
1814    ) -> Result<u64, Error> {
1815        let tree = self
1816            .head()?
1817            .map(|version| version.tree)
1818            .unwrap_or_else(|| self.prolly.create());
1819        self.prolly.scan_range(&tree, start, end, visit)
1820    }
1821
1822    /// Visit a current-snapshot prefix without owning rows.
1823    pub fn scan_prefix(
1824        &self,
1825        prefix: &[u8],
1826        visit: impl for<'entry> FnMut(EntryRef<'entry>),
1827    ) -> Result<u64, Error> {
1828        let tree = self
1829            .head()?
1830            .map(|version| version.tree)
1831            .unwrap_or_else(|| self.prolly.create());
1832        self.prolly.scan_prefix(&tree, prefix, visit)
1833    }
1834
1835    /// Read a cursor page from the current version.
1836    ///
1837    /// The caller should keep using the same map version while consuming a
1838    /// cursor. Use [`VersionedMap::range_page_at`] when the head may advance
1839    /// between requests and repeatable pagination is required.
1840    pub fn range_page(
1841        &self,
1842        cursor: &RangeCursor,
1843        end: Option<&[u8]>,
1844        limit: usize,
1845    ) -> Result<RangePage, Error> {
1846        let tree = self
1847            .head()?
1848            .map(|version| version.tree)
1849            .unwrap_or_else(|| self.prolly.create());
1850        self.prolly.range_page(&tree, cursor, end, limit)
1851    }
1852
1853    /// Read a prefix-bounded cursor page from the current version.
1854    pub fn prefix_page(
1855        &self,
1856        prefix: &[u8],
1857        cursor: &RangeCursor,
1858        limit: usize,
1859    ) -> Result<RangePage, Error> {
1860        let tree = self
1861            .head()?
1862            .map(|version| version.tree)
1863            .unwrap_or_else(|| self.prolly.create());
1864        self.prolly.prefix_page(&tree, prefix, cursor, limit)
1865    }
1866
1867    /// Load a version by its stable identifier.
1868    pub fn version(&self, id: &MapVersionId) -> Result<Option<MapVersion>, Error> {
1869        let service = self.async_service();
1870        let ready_store = self.prolly.engine.store.clone();
1871        let future = service.version(id);
1872        super::engine::ready::run_ready(ready_store.ready(future))
1873    }
1874
1875    /// Read a key from a specific version.
1876    pub fn get_at(&self, id: &MapVersionId, key: &[u8]) -> Result<Option<Vec<u8>>, Error> {
1877        let version = self
1878            .version(id)?
1879            .ok_or_else(|| Error::InvalidVersionedMap(format!("unknown map version {id}")))?;
1880        self.prolly.get(&version.tree, key)
1881    }
1882
1883    /// Read one key from a specific version through a borrowed callback.
1884    pub fn get_at_with<R>(
1885        &self,
1886        id: &MapVersionId,
1887        key: &[u8],
1888        read: impl FnOnce(&[u8]) -> R,
1889    ) -> Result<Option<R>, Error> {
1890        let version = self
1891            .version(id)?
1892            .ok_or_else(|| Error::InvalidVersionedMap(format!("unknown map version {id}")))?;
1893        self.prolly.get_with(&version.tree, key, read)
1894    }
1895
1896    /// Inspect an inline/blob envelope at a specific immutable version.
1897    pub fn get_value_ref_at_with<R>(
1898        &self,
1899        id: &MapVersionId,
1900        key: &[u8],
1901        read: impl for<'value> FnOnce(ValueRefView<'value>) -> R,
1902    ) -> Result<Option<R>, Error> {
1903        let version = self
1904            .version(id)?
1905            .ok_or_else(|| Error::InvalidVersionedMap(format!("unknown map version {id}")))?;
1906        self.prolly.get_value_ref_with(&version.tree, key, read)
1907    }
1908
1909    /// Read several keys from a specific immutable version.
1910    pub fn get_many_at<K: AsRef<[u8]>>(
1911        &self,
1912        id: &MapVersionId,
1913        keys: &[K],
1914    ) -> Result<Vec<Option<Vec<u8>>>, Error> {
1915        let version = self
1916            .version(id)?
1917            .ok_or_else(|| Error::InvalidVersionedMap(format!("unknown map version {id}")))?;
1918        self.prolly.get_many(&version.tree, keys)
1919    }
1920
1921    /// Iterate over a range in a specific cataloged version.
1922    pub fn range_at<'a>(
1923        &'a self,
1924        id: &MapVersionId,
1925        start: &[u8],
1926        end: Option<&[u8]>,
1927    ) -> Result<RangeIter<'a, S>, Error> {
1928        let version = self
1929            .version(id)?
1930            .ok_or_else(|| Error::InvalidVersionedMap(format!("unknown map version {id}")))?;
1931        self.prolly.range(&version.tree, start, end)
1932    }
1933
1934    /// Iterate over keys with `prefix` in a specific immutable version.
1935    pub fn prefix_at<'a>(
1936        &'a self,
1937        id: &MapVersionId,
1938        prefix: &[u8],
1939    ) -> Result<RangeIter<'a, S>, Error> {
1940        let version = self
1941            .version(id)?
1942            .ok_or_else(|| Error::InvalidVersionedMap(format!("unknown map version {id}")))?;
1943        self.prolly.prefix(&version.tree, prefix)
1944    }
1945
1946    /// Visit a range at a specific immutable version without owning rows.
1947    pub fn scan_range_at(
1948        &self,
1949        id: &MapVersionId,
1950        start: &[u8],
1951        end: Option<&[u8]>,
1952        visit: impl for<'entry> FnMut(EntryRef<'entry>),
1953    ) -> Result<u64, Error> {
1954        let version = self
1955            .version(id)?
1956            .ok_or_else(|| Error::InvalidVersionedMap(format!("unknown map version {id}")))?;
1957        self.prolly.scan_range(&version.tree, start, end, visit)
1958    }
1959
1960    /// Visit a prefix at a specific immutable version without owning rows.
1961    pub fn scan_prefix_at(
1962        &self,
1963        id: &MapVersionId,
1964        prefix: &[u8],
1965        visit: impl for<'entry> FnMut(EntryRef<'entry>),
1966    ) -> Result<u64, Error> {
1967        let version = self
1968            .version(id)?
1969            .ok_or_else(|| Error::InvalidVersionedMap(format!("unknown map version {id}")))?;
1970        self.prolly.scan_prefix(&version.tree, prefix, visit)
1971    }
1972
1973    /// Read a cursor page from a specific immutable version.
1974    pub fn range_page_at(
1975        &self,
1976        id: &MapVersionId,
1977        cursor: &RangeCursor,
1978        end: Option<&[u8]>,
1979        limit: usize,
1980    ) -> Result<RangePage, Error> {
1981        let version = self
1982            .version(id)?
1983            .ok_or_else(|| Error::InvalidVersionedMap(format!("unknown map version {id}")))?;
1984        self.prolly.range_page(&version.tree, cursor, end, limit)
1985    }
1986
1987    /// Read a prefix-bounded cursor page from a specific immutable version.
1988    pub fn prefix_page_at(
1989        &self,
1990        id: &MapVersionId,
1991        prefix: &[u8],
1992        cursor: &RangeCursor,
1993        limit: usize,
1994    ) -> Result<RangePage, Error> {
1995        let version = self
1996            .version(id)?
1997            .ok_or_else(|| Error::InvalidVersionedMap(format!("unknown map version {id}")))?;
1998        self.prolly
1999            .prefix_page(&version.tree, prefix, cursor, limit)
2000    }
2001
2002    /// Diff two cataloged versions.
2003    pub fn diff(&self, base: &MapVersionId, target: &MapVersionId) -> Result<Vec<Diff>, Error> {
2004        let base = self
2005            .version(base)?
2006            .ok_or_else(|| Error::InvalidVersionedMap(format!("unknown map version {base}")))?;
2007        let target = self
2008            .version(target)?
2009            .ok_or_else(|| Error::InvalidVersionedMap(format!("unknown map version {target}")))?;
2010        self.prolly.diff(&base.tree, &target.tree)
2011    }
2012
2013    /// Diff a cataloged version against the current head.
2014    pub fn changes_since(&self, base: &MapVersionId) -> Result<Vec<Diff>, Error> {
2015        let base = self
2016            .version(base)?
2017            .ok_or_else(|| Error::InvalidVersionedMap(format!("unknown map version {base}")))?;
2018        let head = self.head()?.ok_or_else(|| {
2019            Error::InvalidVersionedMap("map has not been initialized".to_string())
2020        })?;
2021        self.prolly.diff(&base.tree, &head.tree)
2022    }
2023}
2024
2025/// Async counterpart to [`VersionedMap`] for remote and browser stores.
2026pub struct AsyncVersionedMap<'a, S: super::store::AsyncStore> {
2027    prolly: &'a super::AsyncProlly<S>,
2028    id: Vec<u8>,
2029    head_name: Vec<u8>,
2030    versions_prefix: Vec<u8>,
2031}
2032
2033/// Immutable version-pinned async read view.
2034pub struct AsyncMapSnapshot<'a, S: super::store::AsyncStore> {
2035    prolly: &'a super::AsyncProlly<S>,
2036    version: MapVersion,
2037}
2038
2039/// Resumable async change subscription driven by explicit polling.
2040pub struct AsyncMapChangeSubscription<'a, S: super::store::AsyncStore> {
2041    map: AsyncVersionedMap<'a, S>,
2042    last_seen: Option<MapVersionId>,
2043}
2044impl<'a, S: super::store::AsyncStore> AsyncVersionedMap<'a, S> {
2045    /// Create an async managed-map handle.
2046    pub fn new(prolly: &'a super::AsyncProlly<S>, id: impl AsRef<[u8]>) -> Self {
2047        let id = id.as_ref().to_vec();
2048        let (_, head_name, versions_prefix) = versioned_map_names(&id);
2049        Self {
2050            prolly,
2051            id,
2052            head_name,
2053            versions_prefix,
2054        }
2055    }
2056
2057    /// Application map identifier.
2058    pub fn id(&self) -> &[u8] {
2059        &self.id
2060    }
2061
2062    fn version_name(&self, id: &MapVersionId) -> Vec<u8> {
2063        let mut name = self.versions_prefix.clone();
2064        name.extend_from_slice(id.as_cid().as_bytes());
2065        name
2066    }
2067}
2068impl<'a, S> AsyncVersionedMap<'a, S>
2069where
2070    S: super::store::AsyncStore + super::manifest::AsyncManifestStore,
2071    <S as super::store::AsyncStore>::Error: Send + Sync,
2072    <S as super::manifest::AsyncManifestStore>::Error: Send + Sync,
2073{
2074    /// Load current async head.
2075    pub async fn head(&self) -> Result<Option<MapVersion>, Error> {
2076        self.prolly
2077            .load_named_root_manifest(&self.head_name)
2078            .await?
2079            .map(|manifest| {
2080                MapVersion::new(
2081                    manifest.to_tree(),
2082                    manifest.updated_at_millis.or(manifest.created_at_millis),
2083                    true,
2084                )
2085            })
2086            .transpose()
2087    }
2088
2089    /// Load a cataloged immutable version.
2090    pub async fn version(&self, id: &MapVersionId) -> Result<Option<MapVersion>, Error> {
2091        let manifest = self
2092            .prolly
2093            .load_named_root_manifest(&self.version_name(id))
2094            .await?;
2095        let Some(manifest) = manifest else {
2096            return Ok(None);
2097        };
2098        let tree = manifest.to_tree();
2099        let version = (|| {
2100            let actual = MapVersionId::for_tree(&tree)?;
2101            if actual != *id {
2102                return Err(Error::InvalidVersionedMap(format!(
2103                    "catalog root does not match async version {id}"
2104                )));
2105            }
2106            Ok(MapVersion {
2107                id: actual,
2108                tree,
2109                created_at_millis: manifest.created_at_millis,
2110                is_head: false,
2111            })
2112        })()?;
2113        let is_head = self.head().await?.is_some_and(|head| head.id == *id);
2114        Ok(Some(MapVersion { is_head, ..version }))
2115    }
2116
2117    /// Pin current head for repeatable async reads.
2118    pub async fn snapshot(&self) -> Result<Option<AsyncMapSnapshot<'a, S>>, Error> {
2119        Ok(self.head().await?.map(|version| AsyncMapSnapshot {
2120            prolly: self.prolly,
2121            version,
2122        }))
2123    }
2124
2125    /// Pin one cataloged historical version for repeatable async reads.
2126    pub async fn snapshot_at(
2127        &self,
2128        id: &MapVersionId,
2129    ) -> Result<Option<AsyncMapSnapshot<'a, S>>, Error> {
2130        Ok(self.version(id).await?.map(|version| AsyncMapSnapshot {
2131            prolly: self.prolly,
2132            version,
2133        }))
2134    }
2135
2136    /// Start observing future async head transitions from the current head.
2137    pub async fn subscribe(&self) -> Result<AsyncMapChangeSubscription<'a, S>, Error> {
2138        Ok(AsyncMapChangeSubscription {
2139            map: AsyncVersionedMap::new(self.prolly, &self.id),
2140            last_seen: self.head().await?.map(|version| version.id),
2141        })
2142    }
2143
2144    /// Resume async observation from a previously persisted version.
2145    pub fn subscribe_from(
2146        &self,
2147        last_seen: Option<MapVersionId>,
2148    ) -> AsyncMapChangeSubscription<'a, S> {
2149        AsyncMapChangeSubscription {
2150            map: AsyncVersionedMap::new(self.prolly, &self.id),
2151            last_seen,
2152        }
2153    }
2154
2155    /// Read one key from current head.
2156    pub async fn get(&self, key: &[u8]) -> Result<Option<Vec<u8>>, Error> {
2157        match self.snapshot().await? {
2158            Some(snapshot) => snapshot.get(key).await,
2159            None => Ok(None),
2160        }
2161    }
2162}
2163impl<'a, S> AsyncMapChangeSubscription<'a, S>
2164where
2165    S: super::store::AsyncStore + super::manifest::AsyncManifestStore,
2166    <S as super::store::AsyncStore>::Error: Send + Sync,
2167    <S as super::manifest::AsyncManifestStore>::Error: Send + Sync,
2168{
2169    /// Last head observed by this subscription.
2170    pub fn last_seen(&self) -> Option<&MapVersionId> {
2171        self.last_seen.as_ref()
2172    }
2173
2174    /// Poll once, returning `None` when the async head has not changed.
2175    pub async fn poll(&mut self) -> Result<Option<MapChangeEvent>, Error> {
2176        let Some(current) = self.map.head().await? else {
2177            return Ok(None);
2178        };
2179        if self.last_seen.as_ref() == Some(&current.id) {
2180            return Ok(None);
2181        }
2182        let previous_tree = match &self.last_seen {
2183            Some(id) => {
2184                self.map
2185                    .version(id)
2186                    .await?
2187                    .ok_or_else(|| {
2188                        Error::InvalidVersionedMap(format!(
2189                            "async subscription resume version {} was pruned",
2190                            id
2191                        ))
2192                    })?
2193                    .tree
2194            }
2195            None => self.map.prolly.create(),
2196        };
2197        let diffs = self.map.prolly.diff(&previous_tree, &current.tree).await?;
2198        let previous = self.last_seen.replace(current.id.clone());
2199        Ok(Some(MapChangeEvent {
2200            previous,
2201            current,
2202            diffs,
2203        }))
2204    }
2205}
2206impl<'a, S> AsyncMapSnapshot<'a, S>
2207where
2208    S: super::store::AsyncStore,
2209    <S as super::store::AsyncStore>::Error: Send + Sync,
2210{
2211    /// Pinned version metadata.
2212    pub fn version(&self) -> &MapVersion {
2213        &self.version
2214    }
2215
2216    /// Immutable pinned tree.
2217    pub fn tree(&self) -> &Tree {
2218        &self.version.tree
2219    }
2220
2221    /// Read one key.
2222    pub async fn get(&self, key: &[u8]) -> Result<Option<Vec<u8>>, Error> {
2223        self.prolly.get(self.tree(), key).await
2224    }
2225
2226    /// Read several keys while preserving order and duplicates.
2227    pub async fn get_many<K: AsRef<[u8]>>(
2228        &self,
2229        keys: &[K],
2230    ) -> Result<Vec<Option<Vec<u8>>>, Error> {
2231        self.prolly.get_many(self.tree(), keys).await
2232    }
2233
2234    /// Lazily stream a key range.
2235    pub async fn range<'s>(
2236        &'s self,
2237        start: &[u8],
2238        end: Option<&[u8]>,
2239    ) -> Result<super::range::AsyncRangeIter<'s, S>, Error> {
2240        self.prolly.range(self.tree(), start, end).await
2241    }
2242
2243    /// Lazily stream one key prefix.
2244    pub async fn prefix<'s>(
2245        &'s self,
2246        prefix: &[u8],
2247    ) -> Result<super::range::AsyncRangeIter<'s, S>, Error> {
2248        self.prolly.prefix(self.tree(), prefix).await
2249    }
2250
2251    /// Read one forward cursor page.
2252    pub async fn range_page(
2253        &self,
2254        cursor: &RangeCursor,
2255        end: Option<&[u8]>,
2256        limit: usize,
2257    ) -> Result<RangePage, Error> {
2258        self.prolly
2259            .range_page(self.tree(), cursor, end, limit)
2260            .await
2261    }
2262
2263    /// Read one prefix cursor page.
2264    pub async fn prefix_page(
2265        &self,
2266        prefix: &[u8],
2267        cursor: &RangeCursor,
2268        limit: usize,
2269    ) -> Result<RangePage, Error> {
2270        self.prolly
2271            .prefix_page(self.tree(), prefix, cursor, limit)
2272            .await
2273    }
2274
2275    /// Collect tree statistics asynchronously.
2276    pub async fn stats(&self) -> Result<TreeStats, Error> {
2277        self.prolly.collect_stats(self.tree()).await
2278    }
2279
2280    /// Prove one key asynchronously.
2281    pub async fn prove_key(&self, key: &[u8]) -> Result<super::proof::KeyProof, Error> {
2282        self.prolly.prove_key(self.tree(), key).await
2283    }
2284
2285    /// Prove several keys asynchronously.
2286    pub async fn prove_keys<K: AsRef<[u8]>>(
2287        &self,
2288        keys: &[K],
2289    ) -> Result<super::proof::MultiKeyProof, Error> {
2290        self.prolly.prove_keys(self.tree(), keys).await
2291    }
2292
2293    /// Prove a complete range asynchronously.
2294    pub async fn prove_range(
2295        &self,
2296        start: &[u8],
2297        end: Option<&[u8]>,
2298    ) -> Result<super::proof::RangeProof, Error> {
2299        self.prolly.prove_range(self.tree(), start, end).await
2300    }
2301
2302    /// Prove a complete prefix asynchronously.
2303    pub async fn prove_prefix(&self, prefix: &[u8]) -> Result<super::proof::RangeProof, Error> {
2304        self.prolly.prove_prefix(self.tree(), prefix).await
2305    }
2306}
2307impl<S> AsyncVersionedMap<'_, S>
2308where
2309    S: super::store::AsyncStore
2310        + super::manifest::AsyncManifestStore
2311        + super::transaction::AsyncTransactionalStore,
2312    <S as super::store::AsyncStore>::Error: Send + Sync,
2313    <S as super::manifest::AsyncManifestStore>::Error: Send + Sync,
2314{
2315    /// Atomically apply a batch and retry optimistic head conflicts.
2316    pub async fn apply(&self, mutations: Vec<Mutation>) -> Result<MapVersion, Error> {
2317        self.apply_at_millis(mutations, current_unix_time_millis())
2318            .await
2319    }
2320
2321    /// Atomically apply a batch with an explicit catalog timestamp.
2322    pub async fn apply_at_millis(
2323        &self,
2324        mutations: Vec<Mutation>,
2325        timestamp_millis: u64,
2326    ) -> Result<MapVersion, Error> {
2327        let mut last_conflict = None;
2328        for _ in 0..DEFAULT_VERSIONED_MAP_RETRIES {
2329            match self.try_apply(&mutations, None, timestamp_millis).await? {
2330                UpdateAttempt::Applied { current, .. } => return Ok(current),
2331                UpdateAttempt::Unchanged(Some(current)) => return Ok(current),
2332                UpdateAttempt::Unchanged(None) => {
2333                    return Err(Error::InvalidVersionedMap(
2334                        "empty update did not initialize the index".to_string(),
2335                    ));
2336                }
2337                UpdateAttempt::Conflict(conflict) => last_conflict = Some(conflict),
2338            }
2339        }
2340        Err(Error::transaction_conflict(
2341            last_conflict.expect("retry loop records a conflict before exhaustion"),
2342        ))
2343    }
2344
2345    /// Apply a batch only when `expected` is still the current version.
2346    pub async fn apply_if_at_millis(
2347        &self,
2348        expected: Option<&MapVersionId>,
2349        mutations: Vec<Mutation>,
2350        timestamp_millis: u64,
2351    ) -> Result<VersionedMapUpdate, Error> {
2352        match self
2353            .try_apply(&mutations, Some(expected), timestamp_millis)
2354            .await?
2355        {
2356            UpdateAttempt::Applied { previous, current } => {
2357                Ok(VersionedMapUpdate::Applied { previous, current })
2358            }
2359            UpdateAttempt::Unchanged(current) => Ok(VersionedMapUpdate::Unchanged { current }),
2360            UpdateAttempt::Conflict(_) => Ok(VersionedMapUpdate::Conflict {
2361                current: self.head().await?,
2362            }),
2363        }
2364    }
2365
2366    async fn try_apply(
2367        &self,
2368        mutations: &[Mutation],
2369        expected: Option<Option<&MapVersionId>>,
2370        timestamp_millis: u64,
2371    ) -> Result<UpdateAttempt, Error> {
2372        let tx = self.prolly.begin_transaction()?;
2373        guard_async_managed_map_write(&tx, &self.id, MapWriteAuthority::Unmanaged).await?;
2374        let current_tree = tx.load_named_root(&self.head_name).await?;
2375        let current_id = current_tree
2376            .as_ref()
2377            .map(MapVersionId::for_tree)
2378            .transpose()?;
2379
2380        if let Some(expected) = expected {
2381            if current_id.as_ref() != expected {
2382                tx.rollback();
2383                return Ok(UpdateAttempt::Conflict(TransactionConflict::new(
2384                    self.head_name.clone(),
2385                    None,
2386                    None,
2387                )));
2388            }
2389        }
2390
2391        let base = current_tree.clone().unwrap_or_else(|| tx.create());
2392        let next = tx.batch(&base, mutations.to_vec()).await?;
2393        if current_tree.as_ref() == Some(&next) {
2394            let current = Some(MapVersion {
2395                id: current_id
2396                    .clone()
2397                    .expect("an unchanged existing tree has a version id"),
2398                tree: next,
2399                created_at_millis: None,
2400                is_head: true,
2401            });
2402            return match tx.commit().await? {
2403                TransactionUpdate::Applied { .. } => Ok(UpdateAttempt::Unchanged(current)),
2404                TransactionUpdate::Conflict(conflict) => Ok(UpdateAttempt::Conflict(*conflict)),
2405            };
2406        }
2407
2408        let next_id = MapVersionId::for_tree(&next)?;
2409        let version_name = self.version_name(&next_id);
2410        match tx.load_named_root(&version_name).await? {
2411            Some(existing) if existing != next => {
2412                tx.rollback();
2413                return Err(Error::InvalidVersionedMap(format!(
2414                    "content identifier collision for version {}",
2415                    next_id
2416                )));
2417            }
2418            Some(_) => {}
2419            None => {
2420                tx.publish_named_root_at_millis(&version_name, &next, timestamp_millis)
2421                    .await?;
2422            }
2423        }
2424        tx.publish_named_root_at_millis(&self.head_name, &next, timestamp_millis)
2425            .await?;
2426
2427        match tx.commit().await? {
2428            TransactionUpdate::Applied { .. } => Ok(UpdateAttempt::Applied {
2429                previous: current_id,
2430                current: MapVersion {
2431                    id: next_id,
2432                    tree: next,
2433                    created_at_millis: Some(timestamp_millis),
2434                    is_head: true,
2435                },
2436            }),
2437            TransactionUpdate::Conflict(conflict) => Ok(UpdateAttempt::Conflict(*conflict)),
2438        }
2439    }
2440
2441    async fn publish_tree_if(
2442        &self,
2443        expected: Option<&MapVersionId>,
2444        tree: &Tree,
2445        timestamp_millis: u64,
2446    ) -> Result<VersionedMapUpdate, Error> {
2447        let tx = self.prolly.begin_transaction()?;
2448        guard_async_managed_map_write(&tx, &self.id, MapWriteAuthority::Unmanaged).await?;
2449        let current_tree = tx.load_named_root(&self.head_name).await?;
2450        let current_id = current_tree
2451            .as_ref()
2452            .map(MapVersionId::for_tree)
2453            .transpose()?;
2454        if current_id.as_ref() != expected {
2455            tx.rollback();
2456            return Ok(VersionedMapUpdate::Conflict {
2457                current: self.head().await?,
2458            });
2459        }
2460        if current_tree.as_ref() == Some(tree) {
2461            let current = self.head().await?;
2462            return match tx.commit().await? {
2463                TransactionUpdate::Applied { .. } => Ok(VersionedMapUpdate::Unchanged { current }),
2464                TransactionUpdate::Conflict(_) => Ok(VersionedMapUpdate::Conflict {
2465                    current: self.head().await?,
2466                }),
2467            };
2468        }
2469
2470        let id = MapVersionId::for_tree(tree)?;
2471        let version_name = self.version_name(&id);
2472        match tx.load_named_root(&version_name).await? {
2473            Some(existing) if existing != *tree => {
2474                tx.rollback();
2475                return Err(Error::InvalidVersionedMap(format!(
2476                    "content identifier collision for merged version {}",
2477                    id
2478                )));
2479            }
2480            Some(_) => {}
2481            None => {
2482                tx.publish_named_root_at_millis(&version_name, tree, timestamp_millis)
2483                    .await?;
2484            }
2485        }
2486        tx.publish_named_root_at_millis(&self.head_name, tree, timestamp_millis)
2487            .await?;
2488        match tx.commit().await? {
2489            TransactionUpdate::Applied { .. } => Ok(VersionedMapUpdate::Applied {
2490                previous: current_id,
2491                current: MapVersion {
2492                    id,
2493                    tree: tree.clone(),
2494                    created_at_millis: Some(timestamp_millis),
2495                    is_head: true,
2496                },
2497            }),
2498            TransactionUpdate::Conflict(_) => Ok(VersionedMapUpdate::Conflict {
2499                current: self.head().await?,
2500            }),
2501        }
2502    }
2503
2504    /// Put one key asynchronously.
2505    pub async fn put(
2506        &self,
2507        key: impl Into<Vec<u8>>,
2508        value: impl Into<Vec<u8>>,
2509    ) -> Result<MapVersion, Error> {
2510        self.apply(vec![Mutation::Upsert {
2511            key: key.into(),
2512            val: value.into(),
2513        }])
2514        .await
2515    }
2516
2517    /// Delete one key asynchronously.
2518    pub async fn delete(&self, key: impl Into<Vec<u8>>) -> Result<MapVersion, Error> {
2519        self.apply(vec![Mutation::Delete { key: key.into() }]).await
2520    }
2521
2522    /// Collect and apply several asynchronous managed-map edits.
2523    pub async fn edit(
2524        &self,
2525        edit: impl FnOnce(&mut VersionedMapEditor),
2526    ) -> Result<MapVersion, Error> {
2527        let mut editor = VersionedMapEditor::new();
2528        edit(&mut editor);
2529        self.apply(editor.into_mutations()).await
2530    }
2531}
2532impl<S: super::store::AsyncStore> super::AsyncProlly<S> {
2533    /// Open an async managed map.
2534    pub fn versioned_map(&self, id: impl AsRef<[u8]>) -> AsyncVersionedMap<'_, S> {
2535        AsyncVersionedMap::new(self, id)
2536    }
2537}
2538
2539impl<S> VersionedMap<'_, S>
2540where
2541    S: Store + ManifestStore + ManifestStoreScan,
2542{
2543    /// List cataloged versions newest first.
2544    pub fn versions(&self) -> Result<Vec<MapVersion>, Error> {
2545        let head_id = self.head()?.map(|head| head.id);
2546        let mut versions = self
2547            .prolly
2548            .list_named_root_manifests()?
2549            .into_iter()
2550            .filter_map(|named| {
2551                let suffix = named.name.strip_prefix(self.versions_prefix.as_slice())?;
2552                if suffix.len() != 32 {
2553                    return Some(Err(Error::InvalidVersionedMap(format!(
2554                        "invalid version root name under {:?}",
2555                        self.versions_prefix
2556                    ))));
2557                }
2558                let tree = named.manifest.to_tree();
2559                let actual = match MapVersionId::for_tree(&tree) {
2560                    Ok(id) => id,
2561                    Err(err) => return Some(Err(err)),
2562                };
2563                if actual.as_cid().as_bytes() != suffix {
2564                    return Some(Err(Error::InvalidVersionedMap(format!(
2565                        "version catalog key does not match tree content: {}",
2566                        actual
2567                    ))));
2568                }
2569                Some(Ok(MapVersion {
2570                    is_head: head_id.as_ref() == Some(&actual),
2571                    id: actual,
2572                    tree,
2573                    created_at_millis: named.manifest.created_at_millis,
2574                }))
2575            })
2576            .collect::<Result<Vec<_>, _>>()?;
2577
2578        versions.sort_by(|left, right| {
2579            right
2580                .created_at_millis
2581                .cmp(&left.created_at_millis)
2582                .then_with(|| {
2583                    left.id
2584                        .as_cid()
2585                        .as_bytes()
2586                        .cmp(right.id.as_cid().as_bytes())
2587                })
2588        });
2589        Ok(versions)
2590    }
2591
2592    /// Export every cataloged version and the current head as one portable backup.
2593    pub fn backup(&self) -> Result<VersionedMapBackup, Error> {
2594        let head = self.head()?.ok_or_else(|| {
2595            Error::InvalidVersionedMap("map has not been initialized".to_string())
2596        })?;
2597        let versions = self
2598            .versions()?
2599            .into_iter()
2600            .map(|version| {
2601                Ok(MapBackupVersion {
2602                    id: version.id,
2603                    created_at_millis: version.created_at_millis,
2604                    bundle: self.prolly.export_snapshot(&version.tree)?,
2605                })
2606            })
2607            .collect::<Result<Vec<_>, Error>>()?;
2608        let backup = VersionedMapBackup {
2609            map_id: self.id.clone(),
2610            head: head.id,
2611            versions,
2612        };
2613        backup.verify()?;
2614        Ok(backup)
2615    }
2616}
2617
2618#[allow(clippy::large_enum_variant)]
2619enum UpdateAttempt {
2620    Applied {
2621        previous: Option<MapVersionId>,
2622        current: MapVersion,
2623    },
2624    Unchanged(Option<MapVersion>),
2625    Conflict(TransactionConflict),
2626}
2627
2628impl<S> VersionedMap<'_, S>
2629where
2630    S: Store + ManifestStore + TransactionalStore,
2631{
2632    /// Initialize an empty index, or return its existing head.
2633    pub fn initialize(&self) -> Result<MapVersion, Error> {
2634        self.apply(Vec::new())
2635    }
2636
2637    /// Import one verified portable snapshot and atomically make it head.
2638    pub fn import_as_head(
2639        &self,
2640        bundle: &super::sync::SnapshotBundle,
2641    ) -> Result<MapVersion, Error> {
2642        self.import_as_head_at_millis(bundle, current_unix_time_millis())
2643    }
2644
2645    /// Import one verified portable snapshot with an explicit catalog timestamp.
2646    pub fn import_as_head_at_millis(
2647        &self,
2648        bundle: &super::sync::SnapshotBundle,
2649        timestamp_millis: u64,
2650    ) -> Result<MapVersion, Error> {
2651        if !bundle.verify()?.valid {
2652            return Err(Error::InvalidVersionedMap(
2653                "snapshot bundle is not self-contained".to_string(),
2654            ));
2655        }
2656        if bundle.tree.config != *self.prolly.config() {
2657            return Err(Error::InvalidVersionedMap(
2658                "snapshot config does not match the managed map engine".to_string(),
2659            ));
2660        }
2661        let tree = self.prolly.import_snapshot(bundle)?;
2662        let id = MapVersionId::for_tree(&tree)?;
2663        let version_name = self.version_name(&id);
2664        let mut last_conflict = None;
2665
2666        for _ in 0..DEFAULT_VERSIONED_MAP_RETRIES {
2667            let tx = self.prolly.begin_transaction()?;
2668            guard_managed_map_write(&tx, &self.id, MapWriteAuthority::Unmanaged)?;
2669            let current = tx.load_named_root(&self.head_name)?;
2670            if current.as_ref() == Some(&tree) {
2671                tx.rollback();
2672                return Ok(MapVersion {
2673                    id,
2674                    tree,
2675                    created_at_millis: Some(timestamp_millis),
2676                    is_head: true,
2677                });
2678            }
2679            match tx.load_named_root(&version_name)? {
2680                Some(existing) if existing != tree => {
2681                    tx.rollback();
2682                    return Err(Error::InvalidVersionedMap(format!(
2683                        "content identifier collision for imported version {}",
2684                        id
2685                    )));
2686                }
2687                Some(_) => {}
2688                None => tx.publish_named_root_at_millis(&version_name, &tree, timestamp_millis)?,
2689            }
2690            tx.publish_named_root_at_millis(&self.head_name, &tree, timestamp_millis)?;
2691            match tx.commit()? {
2692                TransactionUpdate::Applied { .. } => {
2693                    return Ok(MapVersion {
2694                        id,
2695                        tree,
2696                        created_at_millis: Some(timestamp_millis),
2697                        is_head: true,
2698                    });
2699                }
2700                TransactionUpdate::Conflict(conflict) => last_conflict = Some(*conflict),
2701            }
2702        }
2703
2704        Err(Error::transaction_conflict(
2705            last_conflict.expect("retry loop records a conflict before exhaustion"),
2706        ))
2707    }
2708
2709    /// Restore a complete portable catalog into an uninitialized managed map.
2710    pub fn restore_backup(&self, backup: &VersionedMapBackup) -> Result<MapVersion, Error> {
2711        backup.verify()?;
2712        if backup.map_id != self.id {
2713            return Err(Error::InvalidVersionedMap(format!(
2714                "backup map id {:?} does not match target {:?}",
2715                backup.map_id, self.id
2716            )));
2717        }
2718        for version in &backup.versions {
2719            if version.bundle.tree.config != *self.prolly.config() {
2720                return Err(Error::InvalidVersionedMap(format!(
2721                    "backup version {} uses a different tree config",
2722                    version.id
2723                )));
2724            }
2725            self.prolly.import_snapshot(&version.bundle)?;
2726        }
2727
2728        let tx = self.prolly.begin_transaction()?;
2729        guard_managed_map_write(&tx, &self.id, MapWriteAuthority::Unmanaged)?;
2730        if tx.load_named_root(&self.head_name)?.is_some() {
2731            tx.rollback();
2732            return Err(Error::InvalidVersionedMap(
2733                "restore target is already initialized".to_string(),
2734            ));
2735        }
2736        let mut restored_head = None;
2737        for version in &backup.versions {
2738            let tree = &version.bundle.tree;
2739            let name = self.version_name(&version.id);
2740            match tx.load_named_root(&name)? {
2741                Some(existing) if existing != *tree => {
2742                    tx.rollback();
2743                    return Err(Error::InvalidVersionedMap(format!(
2744                        "target version root {} contains different content",
2745                        version.id
2746                    )));
2747                }
2748                Some(_) => {}
2749                None => tx.publish_named_root_at_millis(
2750                    &name,
2751                    tree,
2752                    version
2753                        .created_at_millis
2754                        .unwrap_or_else(current_unix_time_millis),
2755                )?,
2756            }
2757            if version.id == backup.head {
2758                restored_head = Some(MapVersion {
2759                    id: version.id.clone(),
2760                    tree: tree.clone(),
2761                    created_at_millis: version.created_at_millis,
2762                    is_head: true,
2763                });
2764            }
2765        }
2766        let restored_head = restored_head.expect("verified backup contains its head");
2767        tx.publish_named_root_at_millis(
2768            &self.head_name,
2769            &restored_head.tree,
2770            restored_head
2771                .created_at_millis
2772                .unwrap_or_else(current_unix_time_millis),
2773        )?;
2774        match tx.commit()? {
2775            TransactionUpdate::Applied { .. } => Ok(restored_head),
2776            TransactionUpdate::Conflict(conflict) => Err(Error::TransactionConflict(conflict)),
2777        }
2778    }
2779
2780    /// Apply a mutation batch atomically, retrying optimistic conflicts.
2781    pub fn apply(&self, mutations: Vec<Mutation>) -> Result<MapVersion, Error> {
2782        self.apply_at_millis(mutations, current_unix_time_millis())
2783    }
2784
2785    /// Apply a mutation batch with an explicit timestamp.
2786    pub fn apply_at_millis(
2787        &self,
2788        mutations: Vec<Mutation>,
2789        timestamp_millis: u64,
2790    ) -> Result<MapVersion, Error> {
2791        let service = self.async_service();
2792        let ready_store = self.prolly.engine.store.clone();
2793        let future = service.apply_at_millis(mutations, timestamp_millis);
2794        super::engine::ready::run_ready(ready_store.ready(future))
2795    }
2796
2797    /// Apply mutations only when `expected` still identifies the current head.
2798    pub fn apply_if(
2799        &self,
2800        expected: Option<&MapVersionId>,
2801        mutations: Vec<Mutation>,
2802    ) -> Result<VersionedMapUpdate, Error> {
2803        self.apply_if_at_millis(expected, mutations, current_unix_time_millis())
2804    }
2805
2806    /// Apply a conditional mutation batch with an explicit timestamp.
2807    pub fn apply_if_at_millis(
2808        &self,
2809        expected: Option<&MapVersionId>,
2810        mutations: Vec<Mutation>,
2811        timestamp_millis: u64,
2812    ) -> Result<VersionedMapUpdate, Error> {
2813        let service = self.async_service();
2814        let ready_store = self.prolly.engine.store.clone();
2815        let future = service.apply_if_at_millis(expected, mutations, timestamp_millis);
2816        super::engine::ready::run_ready(ready_store.ready(future))
2817    }
2818
2819    /// Conditionally insert or replace one key.
2820    pub fn put_if(
2821        &self,
2822        expected: Option<&MapVersionId>,
2823        key: impl Into<Vec<u8>>,
2824        value: impl Into<Vec<u8>>,
2825    ) -> Result<VersionedMapUpdate, Error> {
2826        self.apply_if(
2827            expected,
2828            vec![Mutation::Upsert {
2829                key: key.into(),
2830                val: value.into(),
2831            }],
2832        )
2833    }
2834
2835    /// Conditionally delete one key.
2836    pub fn delete_if(
2837        &self,
2838        expected: Option<&MapVersionId>,
2839        key: impl Into<Vec<u8>>,
2840    ) -> Result<VersionedMapUpdate, Error> {
2841        self.apply_if(expected, vec![Mutation::Delete { key: key.into() }])
2842    }
2843
2844    /// Collect several mutations and apply them only when `expected` is current.
2845    pub fn edit_if(
2846        &self,
2847        expected: Option<&MapVersionId>,
2848        edit: impl FnOnce(&mut VersionedMapEditor),
2849    ) -> Result<VersionedMapUpdate, Error> {
2850        let mut editor = VersionedMapEditor::new();
2851        edit(&mut editor);
2852        self.apply_if(expected, editor.into_mutations())
2853    }
2854
2855    /// Insert or replace one key and return the new current version.
2856    pub fn put(
2857        &self,
2858        key: impl Into<Vec<u8>>,
2859        value: impl Into<Vec<u8>>,
2860    ) -> Result<MapVersion, Error> {
2861        self.apply(vec![Mutation::Upsert {
2862            key: key.into(),
2863            val: value.into(),
2864        }])
2865    }
2866
2867    /// Insert one value, offloading large bytes, and retry head conflicts.
2868    pub fn put_large_value<B: super::blob::BlobStore>(
2869        &self,
2870        blob_store: &B,
2871        key: impl Into<Vec<u8>>,
2872        value: impl Into<Vec<u8>>,
2873        config: super::blob::LargeValueConfig,
2874    ) -> Result<MapVersion, Error> {
2875        let key = key.into();
2876        let value = value.into();
2877        let mut last_conflict = None;
2878        for _ in 0..DEFAULT_VERSIONED_MAP_RETRIES {
2879            let current = self.head()?;
2880            let expected = current.as_ref().map(|version| &version.id);
2881            let tree = current
2882                .as_ref()
2883                .map(|version| version.tree.clone())
2884                .unwrap_or_else(|| self.prolly.create());
2885            let next = self.prolly.put_large_value(
2886                blob_store,
2887                &tree,
2888                key.clone(),
2889                value.clone(),
2890                config.clone(),
2891            )?;
2892            match self.publish_tree_if(expected, &next, current_unix_time_millis())? {
2893                VersionedMapUpdate::Applied { current, .. } => return Ok(current),
2894                VersionedMapUpdate::Unchanged {
2895                    current: Some(current),
2896                } => return Ok(current),
2897                VersionedMapUpdate::Conflict { current } => {
2898                    last_conflict = current;
2899                }
2900                VersionedMapUpdate::Unchanged { current: None } => {}
2901            }
2902        }
2903        Err(Error::InvalidVersionedMap(format!(
2904            "large-value update exhausted retries at head {:?}",
2905            last_conflict.map(|version| version.id)
2906        )))
2907    }
2908
2909    /// Conditionally insert one inline/blob-backed value.
2910    pub fn put_large_value_if<B: super::blob::BlobStore>(
2911        &self,
2912        blob_store: &B,
2913        expected: Option<&MapVersionId>,
2914        key: impl Into<Vec<u8>>,
2915        value: impl Into<Vec<u8>>,
2916        config: super::blob::LargeValueConfig,
2917    ) -> Result<VersionedMapUpdate, Error> {
2918        let current = self.head()?;
2919        if current.as_ref().map(|version| &version.id) != expected {
2920            return Ok(VersionedMapUpdate::Conflict { current });
2921        }
2922        let tree = current
2923            .map(|version| version.tree)
2924            .unwrap_or_else(|| self.prolly.create());
2925        let next =
2926            self.prolly
2927                .put_large_value(blob_store, &tree, key.into(), value.into(), config)?;
2928        self.publish_tree_if(expected, &next, current_unix_time_millis())
2929    }
2930
2931    /// Delete one key and return the new current version.
2932    pub fn delete(&self, key: impl Into<Vec<u8>>) -> Result<MapVersion, Error> {
2933        self.apply(vec![Mutation::Delete { key: key.into() }])
2934    }
2935
2936    /// Collect several mutations in a compact closure and commit them once.
2937    pub fn edit(&self, edit: impl FnOnce(&mut VersionedMapEditor)) -> Result<MapVersion, Error> {
2938        let mut editor = VersionedMapEditor::new();
2939        edit(&mut editor);
2940        self.apply(editor.into_mutations())
2941    }
2942
2943    /// Apply append-oriented mutations using the engine's right-edge fast path.
2944    pub fn append(&self, mutations: Vec<Mutation>) -> Result<MapVersion, Error> {
2945        let mut last_head = None;
2946        for _ in 0..DEFAULT_VERSIONED_MAP_RETRIES {
2947            let current = self.head()?;
2948            let expected = current.as_ref().map(|version| &version.id);
2949            let tree = current
2950                .as_ref()
2951                .map(|version| version.tree.clone())
2952                .unwrap_or_else(|| self.prolly.create());
2953            let next = self.prolly.append_batch(&tree, mutations.clone())?;
2954            match self.publish_tree_if(expected, &next, current_unix_time_millis())? {
2955                VersionedMapUpdate::Applied { current, .. }
2956                | VersionedMapUpdate::Unchanged {
2957                    current: Some(current),
2958                } => return Ok(current),
2959                VersionedMapUpdate::Conflict { current } => last_head = current,
2960                VersionedMapUpdate::Unchanged { current: None } => {}
2961            }
2962        }
2963        Err(Error::InvalidVersionedMap(format!(
2964            "append exhausted retries at head {:?}",
2965            last_head.map(|version| version.id)
2966        )))
2967    }
2968
2969    /// Apply a route-planned parallel mutation batch and publish its statistics.
2970    pub fn parallel_apply(
2971        &self,
2972        mutations: Vec<Mutation>,
2973        config: &super::parallel::ParallelConfig,
2974    ) -> Result<VersionedMapBatchResult, Error> {
2975        let mut last_head = None;
2976        for _ in 0..DEFAULT_VERSIONED_MAP_RETRIES {
2977            let current = self.head()?;
2978            let expected = current.as_ref().map(|version| &version.id);
2979            let tree = current
2980                .as_ref()
2981                .map(|version| version.tree.clone())
2982                .unwrap_or_else(|| self.prolly.create());
2983            let applied =
2984                self.prolly
2985                    .parallel_batch_with_stats(&tree, mutations.clone(), config)?;
2986            match self.publish_tree_if(expected, &applied.tree, current_unix_time_millis())? {
2987                VersionedMapUpdate::Applied { current, .. }
2988                | VersionedMapUpdate::Unchanged {
2989                    current: Some(current),
2990                } => {
2991                    return Ok(VersionedMapBatchResult {
2992                        version: current,
2993                        stats: applied.stats,
2994                    });
2995                }
2996                VersionedMapUpdate::Conflict { current } => last_head = current,
2997                VersionedMapUpdate::Unchanged { current: None } => {}
2998            }
2999        }
3000        Err(Error::InvalidVersionedMap(format!(
3001            "parallel batch exhausted retries at head {:?}",
3002            last_head.map(|version| version.id)
3003        )))
3004    }
3005
3006    /// Move the head to an existing version without deleting newer snapshots.
3007    ///
3008    /// Versions identify unique tree states rather than update events, so a
3009    /// rollback moves the head but does not create a duplicate catalog entry.
3010    pub fn rollback_to(&self, id: &MapVersionId) -> Result<MapVersion, Error> {
3011        let target = self
3012            .version(id)?
3013            .ok_or_else(|| Error::InvalidVersionedMap(format!("unknown map version {id}")))?;
3014        let timestamp_millis = current_unix_time_millis();
3015        let mut last_conflict = None;
3016
3017        for _ in 0..DEFAULT_VERSIONED_MAP_RETRIES {
3018            let tx = self.prolly.begin_transaction()?;
3019            guard_managed_map_write(&tx, &self.id, MapWriteAuthority::Unmanaged)?;
3020            let current = tx.load_named_root(&self.head_name)?;
3021            if current.as_ref() == Some(&target.tree) {
3022                return Ok(MapVersion {
3023                    is_head: true,
3024                    ..target
3025                });
3026            }
3027            tx.publish_named_root_at_millis(&self.head_name, &target.tree, timestamp_millis)?;
3028            match tx.commit()? {
3029                TransactionUpdate::Applied { .. } => {
3030                    return Ok(MapVersion {
3031                        is_head: true,
3032                        ..target
3033                    });
3034                }
3035                TransactionUpdate::Conflict(conflict) => last_conflict = Some(*conflict),
3036            }
3037        }
3038
3039        Err(Error::transaction_conflict(
3040            last_conflict.expect("retry loop records a conflict before exhaustion"),
3041        ))
3042    }
3043
3044    fn publish_tree_if(
3045        &self,
3046        expected: Option<&MapVersionId>,
3047        tree: &Tree,
3048        timestamp_millis: u64,
3049    ) -> Result<VersionedMapUpdate, Error> {
3050        let service = self.async_service();
3051        let ready_store = self.prolly.engine.store.clone();
3052        let future = service.publish_tree_if(expected, tree, timestamp_millis);
3053        super::engine::ready::run_ready(ready_store.ready(future))
3054    }
3055}
3056
3057impl<S> VersionedMap<'_, S>
3058where
3059    S: Store + ManifestStore + ManifestStoreScan + TransactionalStore,
3060{
3061    /// Keep the newest `keep_latest` cataloged versions plus the current head.
3062    ///
3063    /// The head is always retained, even after rollback when it is older than
3064    /// the newest catalog entries. `keep_latest == 0` therefore keeps exactly
3065    /// the current head. This operation removes immutable version root names in
3066    /// one strict transaction; it does not delete content-addressed nodes.
3067    pub fn prune_versions(&self, keep_latest: usize) -> Result<VersionPruneResult, Error> {
3068        self.keep_last(keep_latest)
3069    }
3070
3071    /// Retain the newest `count` versions plus the current head.
3072    pub fn keep_last(&self, count: usize) -> Result<VersionPruneResult, Error> {
3073        self.prune_with(|versions| {
3074            Ok(versions
3075                .iter()
3076                .take(count)
3077                .map(|version| version.id.clone())
3078                .collect())
3079        })
3080    }
3081
3082    /// Retain versions newer than `max_age` plus the current head.
3083    pub fn keep_for(&self, max_age: std::time::Duration) -> Result<VersionPruneResult, Error> {
3084        self.keep_for_at(current_unix_time_millis(), max_age)
3085    }
3086
3087    /// Deterministic form of [`VersionedMap::keep_for`] with an explicit clock.
3088    pub fn keep_for_at(
3089        &self,
3090        now_millis: u64,
3091        max_age: std::time::Duration,
3092    ) -> Result<VersionPruneResult, Error> {
3093        let age_millis = max_age.as_millis().min(u128::from(u64::MAX)) as u64;
3094        let cutoff = now_millis.saturating_sub(age_millis);
3095        self.prune_with(|versions| {
3096            Ok(versions
3097                .iter()
3098                .filter(|version| {
3099                    version
3100                        .created_at_millis
3101                        .map(|created| created >= cutoff)
3102                        .unwrap_or(true)
3103                })
3104                .map(|version| version.id.clone())
3105                .collect())
3106        })
3107    }
3108
3109    /// Retain an explicit version set plus the current head.
3110    ///
3111    /// Missing requested IDs are rejected so a typo cannot silently discard
3112    /// more history than intended.
3113    pub fn keep_versions<I, V>(&self, ids: I) -> Result<VersionPruneResult, Error>
3114    where
3115        I: IntoIterator<Item = V>,
3116        V: Borrow<MapVersionId>,
3117    {
3118        let requested = ids
3119            .into_iter()
3120            .map(|id| id.borrow().clone())
3121            .collect::<HashSet<_>>();
3122        self.prune_with(|versions| {
3123            let present = versions
3124                .iter()
3125                .map(|version| version.id.clone())
3126                .collect::<HashSet<_>>();
3127            let missing = requested.difference(&present).collect::<Vec<_>>();
3128            if !missing.is_empty() {
3129                return Err(Error::InvalidVersionedMap(format!(
3130                    "retention requested unknown versions: {:?}",
3131                    missing
3132                )));
3133            }
3134            Ok(requested.clone())
3135        })
3136    }
3137
3138    fn prune_with(
3139        &self,
3140        select: impl Fn(&[MapVersion]) -> Result<HashSet<MapVersionId>, Error>,
3141    ) -> Result<VersionPruneResult, Error> {
3142        let mut last_conflict = None;
3143
3144        for _ in 0..DEFAULT_VERSIONED_MAP_RETRIES {
3145            let tx = self.prolly.begin_transaction()?;
3146            guard_managed_map_write(&tx, &self.id, MapWriteAuthority::Unmanaged)?;
3147            let Some(head_tree) = tx.load_named_root(&self.head_name)? else {
3148                tx.rollback();
3149                let versions = self.versions()?;
3150                if versions.is_empty() {
3151                    return Ok(VersionPruneResult::default());
3152                }
3153                return Err(Error::InvalidVersionedMap(
3154                    "version roots exist without a current head".to_string(),
3155                ));
3156            };
3157            let head_id = MapVersionId::for_tree(&head_tree)?;
3158            let versions = self.versions()?;
3159            if !versions.iter().any(|version| version.id == head_id) {
3160                tx.rollback();
3161                return Err(Error::InvalidVersionedMap(format!(
3162                    "current head {} is absent from the version catalog",
3163                    head_id
3164                )));
3165            }
3166
3167            let mut retained_ids = select(&versions)?;
3168            retained_ids.insert(head_id);
3169
3170            let retained = versions
3171                .iter()
3172                .filter(|version| retained_ids.contains(&version.id))
3173                .map(|version| version.id.clone())
3174                .collect::<Vec<_>>();
3175            let removed = versions
3176                .iter()
3177                .filter(|version| !retained_ids.contains(&version.id))
3178                .map(|version| version.id.clone())
3179                .collect::<Vec<_>>();
3180
3181            if removed.is_empty() {
3182                tx.rollback();
3183                return Ok(VersionPruneResult { retained, removed });
3184            }
3185
3186            for id in &removed {
3187                let name = self.version_name(id);
3188                if tx.load_named_root(&name)?.is_some() {
3189                    tx.delete_named_root(&name)?;
3190                }
3191            }
3192
3193            match tx.commit()? {
3194                TransactionUpdate::Applied { .. } => {
3195                    return Ok(VersionPruneResult { retained, removed });
3196                }
3197                TransactionUpdate::Conflict(conflict) => last_conflict = Some(*conflict),
3198            }
3199        }
3200
3201        Err(Error::transaction_conflict(
3202            last_conflict.expect("retry loop records a conflict before exhaustion"),
3203        ))
3204    }
3205}
3206
3207impl<S> VersionedMap<'_, S>
3208where
3209    S: Store + ManifestStore + TransactionalStore + Clone + Send + Sync,
3210{
3211    /// Build sorted input with the streaming builder and initialize an absent map.
3212    pub fn initialize_sorted<I, K, V>(&self, entries: I) -> Result<VersionedMapUpdate, Error>
3213    where
3214        I: IntoIterator<Item = (K, V)>,
3215        K: Into<Vec<u8>>,
3216        V: Into<Vec<u8>>,
3217    {
3218        self.rebuild_sorted_if(None, entries)
3219    }
3220
3221    /// Stream sorted input into a candidate tree, then CAS-replace head.
3222    pub fn rebuild_sorted_if<I, K, V>(
3223        &self,
3224        expected: Option<&MapVersionId>,
3225        entries: I,
3226    ) -> Result<VersionedMapUpdate, Error>
3227    where
3228        I: IntoIterator<Item = (K, V)>,
3229        K: Into<Vec<u8>>,
3230        V: Into<Vec<u8>>,
3231    {
3232        let current = self.head()?;
3233        if current.as_ref().map(|version| &version.id) != expected {
3234            return Ok(VersionedMapUpdate::Conflict { current });
3235        }
3236        let mut builder = super::builder::SortedBatchBuilder::new(
3237            self.prolly.store().clone(),
3238            self.prolly.config().clone(),
3239        );
3240        for (key, value) in entries {
3241            builder.add(key.into(), value.into())?;
3242        }
3243        let tree = builder.build()?;
3244        self.publish_tree_if(expected, &tree, current_unix_time_millis())
3245    }
3246
3247    /// Build arbitrary iterator input in parallel, then CAS-replace head.
3248    pub fn rebuild_from_iter_if<I, K, V>(
3249        &self,
3250        expected: Option<&MapVersionId>,
3251        entries: I,
3252    ) -> Result<VersionedMapUpdate, Error>
3253    where
3254        I: IntoIterator<Item = (K, V)>,
3255        K: Into<Vec<u8>>,
3256        V: Into<Vec<u8>>,
3257    {
3258        let current = self.head()?;
3259        if current.as_ref().map(|version| &version.id) != expected {
3260            return Ok(VersionedMapUpdate::Conflict { current });
3261        }
3262        let mut builder = super::builder::BatchBuilder::new(
3263            self.prolly.store().clone(),
3264            self.prolly.config().clone(),
3265        );
3266        for (key, value) in entries {
3267            builder.add(key.into(), value.into());
3268        }
3269        let tree = builder.build()?;
3270        self.publish_tree_if(expected, &tree, current_unix_time_millis())
3271    }
3272}
3273
3274/// Successful integrity audit of one complete managed-map catalog.
3275#[derive(Clone, Debug, PartialEq, Eq)]
3276pub struct MapCatalogVerification {
3277    /// Current head identifier.
3278    pub head: MapVersionId,
3279    /// Number of immutable catalog entries.
3280    pub version_count: usize,
3281    /// Unique nodes reachable from all retained versions.
3282    pub reachable_nodes: usize,
3283    /// Serialized bytes reachable from all retained versions.
3284    pub reachable_bytes: usize,
3285}
3286
3287impl<S> VersionedMap<'_, S>
3288where
3289    S: Store + ManifestStore + ManifestStoreScan,
3290{
3291    /// Verify root names, version IDs, head membership, and every reachable node.
3292    pub fn verify_catalog(&self) -> Result<MapCatalogVerification, Error> {
3293        let head = self.head()?.ok_or_else(|| {
3294            Error::InvalidVersionedMap("map has not been initialized".to_string())
3295        })?;
3296        let versions = self.versions()?;
3297        if !versions.iter().any(|version| version.id == head.id) {
3298            return Err(Error::InvalidVersionedMap(format!(
3299                "current head {} is absent from the version catalog",
3300                head.id
3301            )));
3302        }
3303        let trees = versions
3304            .iter()
3305            .map(|version| version.tree.clone())
3306            .collect::<Vec<_>>();
3307        let reachable = self.prolly.mark_reachable(&trees)?;
3308        Ok(MapCatalogVerification {
3309            head: head.id,
3310            version_count: versions.len(),
3311            reachable_nodes: reachable.live_nodes,
3312            reachable_bytes: reachable.live_bytes,
3313        })
3314    }
3315}
3316
3317impl<S> VersionedMap<'_, S>
3318where
3319    S: Store + ManifestStore + ManifestStoreScan + super::store::NodeStoreScan,
3320{
3321    /// Dry-run node GC after applying this map's retention policy.
3322    ///
3323    /// Node storage is shared and content-addressed, so the safety boundary is
3324    /// necessarily store-wide: every remaining named root is retained. This
3325    /// prevents maintenance on one map from deleting another map's nodes.
3326    pub fn plan_gc(&self) -> Result<super::gc::GcPlan, Error> {
3327        self.prolly
3328            .plan_store_gc_for_retention(&NamedRootRetention::all())
3329    }
3330
3331    /// Sweep store-wide nodes unreachable from every remaining named root.
3332    pub fn sweep_gc(&self) -> Result<super::gc::GcSweep, Error> {
3333        self.prolly
3334            .sweep_store_gc_for_retention(&NamedRootRetention::all())
3335    }
3336}
3337
3338impl<S> VersionedMap<'_, S>
3339where
3340    S: Store + ManifestStore + ManifestStoreScan,
3341{
3342    /// Plan blob GC while retaining blobs from every remaining named root.
3343    ///
3344    /// Blob stores are commonly shared by several maps, so limiting reachability
3345    /// to this map would make a per-map maintenance call unsafe for its peers.
3346    pub fn plan_blob_gc<B: super::blob::BlobStoreScan>(
3347        &self,
3348        blob_store: &B,
3349    ) -> Result<super::gc::BlobGcPlan, Error> {
3350        let roots = self
3351            .prolly
3352            .load_retained_named_roots(&NamedRootRetention::all())?
3353            .trees();
3354        self.prolly.plan_blob_store_gc(blob_store, &roots)
3355    }
3356
3357    /// Sweep blobs unreachable from every remaining named root in the store.
3358    pub fn sweep_blob_gc<B: super::blob::BlobStoreScan>(
3359        &self,
3360        blob_store: &B,
3361    ) -> Result<super::gc::BlobGcSweep, Error> {
3362        let roots = self
3363            .prolly
3364            .load_retained_named_roots(&NamedRootRetention::all())?
3365            .trees();
3366        self.prolly.sweep_blob_store_gc(blob_store, &roots)
3367    }
3368}
3369
3370impl<S: Store> Prolly<S> {
3371    /// Open a built-in versioned map identified by arbitrary application bytes.
3372    pub fn versioned_map(&self, id: impl AsRef<[u8]>) -> VersionedMap<'_, S> {
3373        VersionedMap::new(self, id)
3374    }
3375}
3376
3377impl<S> Prolly<S>
3378where
3379    S: Store + ManifestStore + TransactionalStore,
3380{
3381    /// Atomically update any number of managed maps in one strict transaction.
3382    pub fn versioned_maps_transaction<T>(
3383        &self,
3384        run: impl FnOnce(&mut VersionedMapsTransaction<'_, '_, S>) -> Result<T, Error>,
3385    ) -> Result<T, Error> {
3386        let timestamp_millis = current_unix_time_millis();
3387        self.transaction(|tx| {
3388            let mut maps = VersionedMapsTransaction::new(tx, timestamp_millis);
3389            run(&mut maps)
3390        })
3391    }
3392}
3393
3394fn append_hex(output: &mut Vec<u8>, bytes: &[u8]) {
3395    const HEX: &[u8; 16] = b"0123456789abcdef";
3396    output.reserve(bytes.len() * 2);
3397    for byte in bytes {
3398        output.push(HEX[(byte >> 4) as usize]);
3399        output.push(HEX[(byte & 0x0f) as usize]);
3400    }
3401}
3402
3403fn versioned_map_names(id: &[u8]) -> (Vec<u8>, Vec<u8>, Vec<u8>) {
3404    let mut root_prefix = VERSIONED_MAP_ROOT_PREFIX.to_vec();
3405    append_hex(&mut root_prefix, id);
3406
3407    let mut head_name = root_prefix.clone();
3408    head_name.extend_from_slice(HEAD_SUFFIX);
3409
3410    let mut versions_prefix = root_prefix.clone();
3411    versions_prefix.extend_from_slice(VERSIONS_SUFFIX);
3412    (root_prefix, head_name, versions_prefix)
3413}
3414
3415#[cfg(test)]
3416mod index_fence_tests {
3417    use super::*;
3418    use crate::prolly::config::Config;
3419    use crate::prolly::secondary_index::indexed_collection_root_name;
3420    use crate::prolly::store::MemStore;
3421
3422    #[test]
3423    fn map_version_id_decodes_only_exact_cid_bytes() {
3424        let expected = MapVersionId::for_tree(&Tree::new(Config::default())).unwrap();
3425        assert_eq!(
3426            MapVersionId::from_bytes(expected.as_cid().as_bytes()).unwrap(),
3427            expected
3428        );
3429        assert!(MapVersionId::from_bytes(&[0; 31]).is_err());
3430        assert!(MapVersionId::from_bytes(&[0; 33]).is_err());
3431    }
3432
3433    #[test]
3434    fn absent_collection_state_read_conflicts_with_later_activation() {
3435        let engine = Prolly::new(MemStore::new(), Config::default());
3436        let tx = engine.begin_transaction().unwrap();
3437        guard_managed_map_write(&tx, b"users", MapWriteAuthority::Unmanaged).unwrap();
3438        let tree = engine
3439            .put(&engine.create(), b"format".to_vec(), vec![1])
3440            .unwrap();
3441        let root = indexed_collection_root_name(b"users").unwrap();
3442        engine.publish_named_root(&root, &tree).unwrap();
3443
3444        let update = tx.commit().unwrap();
3445        assert!(matches!(
3446            update,
3447            TransactionUpdate::Conflict(conflict)
3448                if conflict.name == root
3449        ));
3450    }
3451}