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}