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

eidetica/instance/backend/
local.rs

1//! [`LocalBackend`]: the seam backed by a concrete in-process storage engine.
2
3use std::sync::Arc;
4
5use async_trait::async_trait;
6
7use super::{Backend, MergeSlice};
8use crate::{
9    Result,
10    backend::{
11        BackendImpl, InstanceMetadata, RecordMutations, RecordPage, RecordRange, RecordView,
12        StagingToken, StoreStateRequest, VerificationStatus,
13    },
14    entry::{Entry, ID},
15    instance::WriteSource,
16    snapshot::Snapshot,
17};
18
19/// A [`Backend`] backed by a local [`BackendImpl`] (e.g. `InMemory`, SQLx).
20///
21/// Seam methods forward directly to the engine. The CRDT-state cache serves the
22/// trusted [`CacheScope::Shared`] scope (the daemon's own in-process path); the
23/// scope-keyed variants used by the service handlers reach the engine directly
24/// via [`Backend::local_engine`].
25#[derive(Clone)]
26pub struct LocalBackend(Arc<dyn BackendImpl>);
27
28impl LocalBackend {
29    pub fn new(engine: Arc<dyn BackendImpl>) -> Self {
30        Self(engine)
31    }
32}
33
34impl std::fmt::Debug for LocalBackend {
35    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
36        f.debug_tuple("LocalBackend").finish()
37    }
38}
39
40#[async_trait]
41impl Backend for LocalBackend {
42    async fn resolve_store_state(&self, request: &StoreStateRequest) -> Result<Option<RecordView>> {
43        self.0.resolve_store_state(request).await
44    }
45    async fn begin_store_state_staging(&self, request: StoreStateRequest) -> Result<StagingToken> {
46        self.0.begin_store_state_staging(request).await
47    }
48    async fn stage_store_state_records(
49        &self,
50        token: &StagingToken,
51        records: RecordMutations,
52    ) -> Result<()> {
53        self.0.stage_store_state_records(token, records).await
54    }
55    async fn publish_store_state(&self, token: StagingToken) -> Result<RecordView> {
56        self.0.publish_store_state(token).await
57    }
58    async fn abort_store_state(&self, token: StagingToken) -> Result<()> {
59        self.0.abort_store_state(token).await
60    }
61    async fn store_state_record_get(
62        &self,
63        view: &RecordView,
64        key: &[u8],
65    ) -> Result<Option<Vec<u8>>> {
66        self.0.store_state_record_get(view, key).await
67    }
68    async fn store_state_record_scan(
69        &self,
70        view: &RecordView,
71        range: &RecordRange,
72        after: Option<&[u8]>,
73        limit: usize,
74    ) -> Result<RecordPage> {
75        self.0
76            .store_state_record_scan(view, range, after, limit)
77            .await
78    }
79    async fn clear_derived_store_state(&self) -> Result<()> {
80        self.0.clear_derived_store_state().await
81    }
82    async fn get(&self, id: &ID) -> Result<Entry> {
83        self.0.get(id).await
84    }
85
86    async fn snapshot(&self, tree: &ID) -> Result<Snapshot> {
87        self.0.snapshot(tree).await
88    }
89
90    async fn store_snapshot(&self, tree: &ID, store: &str) -> Result<Snapshot> {
91        self.0.store_snapshot(tree, store).await
92    }
93
94    async fn store_snapshot_at(
95        &self,
96        tree: &ID,
97        store: &str,
98        main_snapshot: &Snapshot,
99    ) -> Result<Snapshot> {
100        self.0.store_snapshot_at(tree, store, main_snapshot).await
101    }
102
103    async fn store_at(&self, tree: &ID, store: &str, snapshot: &Snapshot) -> Result<Vec<Entry>> {
104        self.0.store_at(tree, store, snapshot).await
105    }
106
107    async fn compute_merge_state(
108        &self,
109        tree: &ID,
110        store: &str,
111        entry_ids: &[ID],
112    ) -> Result<MergeSlice> {
113        let merge_base = self.0.find_merge_base(tree, store, entry_ids).await?;
114        // Entries are immutable and parents precede children, so for a local
115        // engine the path is a pure function of (base, tips) — the two
116        // engine calls cannot disagree the way two remote RPCs can. With no
117        // base the caller batch-fetches the full ancestry instead of
118        // walking a path, so none is computed.
119        let path = match &merge_base {
120            Some(base) => {
121                self.0
122                    .get_path_from_to(tree, store, Some(base), entry_ids)
123                    .await?
124            }
125            None => Vec::new(),
126        };
127        Ok(MergeSlice { merge_base, path })
128    }
129
130    async fn put(&self, entry: Entry) -> Result<()> {
131        self.0.put(entry).await
132    }
133
134    async fn write_entry(
135        &self,
136        verification: VerificationStatus,
137        entry: Entry,
138        _source: WriteSource,
139    ) -> Result<()> {
140        let entry_id = entry.id();
141        self.0.put(entry).await?;
142        if verification != VerificationStatus::Unverified {
143            self.0
144                .update_verification_status(&entry_id, verification)
145                .await?;
146        }
147        Ok(())
148    }
149
150    async fn get_instance_metadata(&self) -> Result<Option<InstanceMetadata>> {
151        self.0.get_instance_metadata().await
152    }
153
154    async fn set_instance_metadata(&self, metadata: &InstanceMetadata) -> Result<()> {
155        self.0.set_instance_metadata(metadata).await
156    }
157
158    fn local_engine(&self) -> Option<Arc<dyn BackendImpl>> {
159        Some(Arc::clone(&self.0))
160    }
161}