Development Documentation (main branch) - For stable release docs, see docs.rs/eidetica
Skip to main content

eidetica/instance/backend/
mod.rs

1//! The `Backend` seam: the single storage abstraction higher-level code
2//! (`Transaction`, `Store`, `Database`, `Instance`) operates through, with no
3//! branching on local vs remote.
4//!
5//! [`LocalBackend`] wraps a concrete in-process storage engine
6//! ([`BackendImpl`](crate::backend::BackendImpl)); [`RemoteBackend`] wraps a
7//! [`RemoteConnection`](crate::service::client::RemoteConnection) and
8//! translates each method to a wire RPC. The trait is the *intersection* of
9//! what both can honor with the same meaning — storage primitives that have no
10//! authorisable remote shape (secrets, verification-status mutation, raw tree
11//! dumps, scope-keyed cache) are deliberately **not** on the trait. They live
12//! on the concrete local engine, reached only where one exists via
13//! [`Backend::local_engine`].
14
15mod local;
16#[cfg(all(unix, feature = "service"))]
17mod remote;
18
19pub use local::LocalBackend;
20#[cfg(all(unix, feature = "service"))]
21pub use remote::RemoteBackend;
22
23use std::sync::Arc;
24
25use async_trait::async_trait;
26
27#[cfg(all(unix, feature = "service"))]
28use crate::service::client::RemoteConnection;
29use crate::{
30    Result,
31    backend::{
32        BackendError, BackendImpl, InstanceMetadata, RecordMutations, RecordPage, RecordRange,
33        RecordView, StagingToken, StoreStateRequest, VerificationStatus,
34    },
35    entry::{Entry, ID},
36    instance::WriteSource,
37    snapshot::Snapshot,
38};
39
40/// The inputs for materializing a multi-tip store state, resolved in one
41/// call (see [`Backend::compute_merge_state`]).
42///
43/// When `merge_base` is `Some`, `path` holds every entry between the base
44/// (exclusive) and the tips (inclusive), sorted by height then ID for the
45/// CRDT fold. When it is `None` the tips share no common ancestor and the
46/// caller materializes their full ancestry from the empty base — via a
47/// batch entry fetch ([`Backend::store_at`]) rather than a path walk, so
48/// `path` is empty.
49#[derive(Debug, Clone)]
50pub struct MergeSlice {
51    /// The common dominator of the queried tips, or `None` for disjoint
52    /// histories.
53    pub merge_base: Option<ID>,
54    /// Entries to fold on top of the base's state; empty when `merge_base`
55    /// is `None`.
56    pub path: Vec<ID>,
57}
58
59/// Storage operations shared by transactions, Stores, databases, and instances,
60/// whether storage is local or served by a daemon.
61///
62/// Tree-scoped methods take the tree explicitly; the remote implementation uses
63/// the argument directly (callers already pass the owning database's root), so
64/// no per-handle root needs to be bound. The only per-handle state a remote
65/// backend carries is its acting identity (see [`RemoteBackend`]).
66#[async_trait]
67pub trait Backend: Send + Sync + std::fmt::Debug {
68    async fn resolve_store_state(
69        &self,
70        _request: &StoreStateRequest,
71    ) -> Result<Option<RecordView>> {
72        Err(BackendError::StoreStateStorageUnsupported.into())
73    }
74    async fn begin_store_state_staging(&self, _request: StoreStateRequest) -> Result<StagingToken> {
75        Err(BackendError::StoreStateStorageUnsupported.into())
76    }
77    async fn stage_store_state_records(
78        &self,
79        _token: &StagingToken,
80        _records: RecordMutations,
81    ) -> Result<()> {
82        Err(BackendError::StoreStateStorageUnsupported.into())
83    }
84    async fn publish_store_state(&self, _token: StagingToken) -> Result<RecordView> {
85        Err(BackendError::StoreStateStorageUnsupported.into())
86    }
87    async fn abort_store_state(&self, _token: StagingToken) -> Result<()> {
88        Err(BackendError::StoreStateStorageUnsupported.into())
89    }
90    async fn store_state_record_get(
91        &self,
92        _view: &RecordView,
93        _key: &[u8],
94    ) -> Result<Option<Vec<u8>>> {
95        Err(BackendError::StoreStateStorageUnsupported.into())
96    }
97    async fn store_state_record_scan(
98        &self,
99        _view: &RecordView,
100        _range: &RecordRange,
101        _after: Option<&[u8]>,
102        _limit: usize,
103    ) -> Result<RecordPage> {
104        Err(BackendError::StoreStateStorageUnsupported.into())
105    }
106    async fn clear_derived_store_state(&self) -> Result<()> {
107        Err(BackendError::StoreStateStorageUnsupported.into())
108    }
109    /// Retrieve an entry by ID.
110    async fn get(&self, id: &ID) -> Result<Entry>;
111
112    /// Raw [`Snapshot`] of `tree` (no Verified-frontier filtering — that stays
113    /// in `Database`).
114    async fn snapshot(&self, tree: &ID) -> Result<Snapshot>;
115
116    /// Raw [`Snapshot`] of `store` within `tree`.
117    async fn store_snapshot(&self, tree: &ID, store: &str) -> Result<Snapshot>;
118
119    /// Store snapshot reachable as of a specific main-tree snapshot.
120    async fn store_snapshot_at(
121        &self,
122        tree: &ID,
123        store: &str,
124        main_snapshot: &Snapshot,
125    ) -> Result<Snapshot>;
126
127    /// Every entry of `store` reachable from `snapshot`.
128    async fn store_at(&self, tree: &ID, store: &str, snapshot: &Snapshot) -> Result<Vec<Entry>>;
129
130    /// The merge base of `entry_ids` within `store` and the path of entries
131    /// from that base to them, resolved together.
132    ///
133    /// Base and path are one query on purpose: resolving them in separate
134    /// calls lets the answers come from two different views of the store —
135    /// on a remote backend, two RPCs a sync ingest can land between — and a
136    /// path anchored at a base the caller never saw folds into a silently
137    /// truncated state.
138    async fn compute_merge_state(
139        &self,
140        tree: &ID,
141        store: &str,
142        entry_ids: &[ID],
143    ) -> Result<MergeSlice>;
144
145    /// Persist an entry. Local stores it directly; remote submits it via
146    /// `DatabaseOp::SubmitSignedEntry` (stored `Unverified`, server-verified).
147    async fn put(&self, entry: Entry) -> Result<()>;
148
149    /// Durably persist a signed entry, applying `verification` locally or
150    /// submitting it over the wire. `source` informs local callback dispatch
151    /// (handled by `Instance::put_entry`) and is unused on remote.
152    async fn write_entry(
153        &self,
154        verification: VerificationStatus,
155        entry: Entry,
156        source: WriteSource,
157    ) -> Result<()>;
158
159    /// Public instance metadata (device identity, system database IDs).
160    async fn get_instance_metadata(&self) -> Result<Option<InstanceMetadata>>;
161
162    /// Persist public instance metadata.
163    async fn set_instance_metadata(&self, metadata: &InstanceMetadata) -> Result<()>;
164
165    /// The concrete in-process storage engine, if this is a local backend.
166    ///
167    /// Off-seam local-only operations (instance secrets, verification-status
168    /// mutation, and `all_roots`/`get_tree` raw dumps) are
169    /// reached through this accessor, so they are usable only where a concrete
170    /// local backend exists. Returns `None` for remote backends.
171    fn local_engine(&self) -> Option<Arc<dyn BackendImpl>> {
172        None
173    }
174
175    /// The remote connection, if this is a remote backend. Returns `None` for
176    /// local backends.
177    #[cfg(all(unix, feature = "service"))]
178    fn remote_connection(&self) -> Option<RemoteConnection> {
179        None
180    }
181}