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

eidetica/store/
mod.rs

1use crate::HeightStrategy;
2use crate::crdt::{CRDT, Doc};
3use crate::{Result, Transaction};
4use async_trait::async_trait;
5
6mod errors;
7pub use errors::StoreError;
8
9mod docstore;
10pub use docstore::{DocStore, DocStoreInit};
11
12mod value_editor;
13pub use value_editor::ValueEditor;
14
15mod table;
16pub use table::Table;
17
18mod settings_store;
19pub use settings_store::SettingsStore;
20
21mod registry;
22pub use registry::Registered;
23pub use registry::Registry;
24pub use registry::RegistryEntry;
25pub use registry::SubtreeSettings;
26
27mod password_store;
28pub use password_store::{
29    DEFAULT_ARGON2_M_COST, DEFAULT_ARGON2_P_COST, DEFAULT_ARGON2_T_COST, EncryptedFragment,
30    EncryptionInfo, PasswordStore, PasswordStoreConfig,
31};
32
33#[cfg(feature = "y-crdt")]
34mod ydoc;
35#[cfg(feature = "y-crdt")]
36pub use ydoc::{YDoc, YrsBinary};
37
38/// A trait representing a named, CRDT-based data structure within a `Database`.
39///
40/// `Store` implementations define how data within a specific named partition of a `Database`
41/// is structured, accessed, and modified. They work in conjunction with a `Transaction`
42/// to stage changes before committing them as a single `Entry`.
43///
44/// Users typically interact with `Store` implementations obtained either via:
45/// 1. `Database::get_store_viewer`: For read-only access to the current merged state.
46/// 2. `Transaction::get_store`: For staging modifications within a transaction.
47///
48/// Store types must also implement [`Registered`] to provide their type identifier.
49#[async_trait]
50pub trait Store: Sized + Registered + Send + Sync {
51    /// The CRDT data type used for local (staged) data in this store.
52    ///
53    /// This is the type stored within each individual Entry.
54    type Data: CRDT;
55
56    /// Creates a new `Store` handle associated with a specific transaction.
57    ///
58    /// This constructor is typically called internally by `Transaction::get_store` or
59    /// `Database::get_store_viewer`. The resulting `Store` instance provides methods
60    /// to interact with the data of the specified `subtree_name`, potentially staging
61    /// changes within the provided `txn`.
62    ///
63    /// # Arguments
64    /// * `txn` - The `Transaction` this `Store` instance will read from and potentially write to.
65    /// * `subtree_name` - The name identifying this specific data partition within the `Database`.
66    async fn load(txn: &Transaction, subtree_name: String) -> Result<Self>;
67
68    /// Returns the name of this subtree.
69    fn name(&self) -> &str;
70
71    /// Returns a reference to the transaction this Store is associated with.
72    ///
73    /// This is used by the default implementations of `register()`, `get_config()`,
74    /// and `set_config()` to access the index store.
75    fn transaction(&self) -> &Transaction;
76
77    /// Returns the default configuration for this Store type as a [`Doc`].
78    ///
79    /// This configuration is stored in the `_index` subtree when a new subtree is
80    /// first created. The Store implementation owns the format and interpretation
81    /// of this configuration data.
82    ///
83    /// The default implementation returns an empty `Doc`. Store implementations
84    /// that require specific configuration should override this method.
85    ///
86    /// # Examples
87    ///
88    /// ```
89    /// # use eidetica::{Store, store::DocStore};
90    /// let config = DocStore::default_config();
91    /// assert!(config.is_empty());
92    /// ```
93    fn default_config() -> Doc {
94        Doc::new()
95    }
96
97    /// Initializes a new subtree and registers it in the `_index`.
98    ///
99    /// This method is called by `Transaction::get_store()` when accessing a subtree
100    /// that doesn't yet exist in the `_index`. It creates the Store and registers
101    /// its type and default configuration in the index.
102    ///
103    /// The default implementation:
104    /// 1. Creates the Store using `Self::load()`
105    /// 2. Registers it in `_index` with `Self::type_id()` and `Self::default_config()`
106    ///
107    /// Store implementations can override this to customize initialization behavior.
108    ///
109    /// # Arguments
110    /// * `txn` - The `Transaction` this `Store` instance will operate within.
111    /// * `subtree_name` - The name identifying this specific data partition.
112    ///
113    /// # Returns
114    /// A `Result<Self>` containing the initialized Store.
115    async fn register(txn: &Transaction, subtree_name: String) -> Result<Self> {
116        let store = Self::load(txn, subtree_name).await?;
117        store.set_config(Self::default_config()).await?;
118        Ok(store)
119    }
120
121    /// Opens this Store on a transaction, registering the subtree if it does not
122    /// yet exist.
123    ///
124    /// This is the consumer-facing entry point for attaching a Store to a
125    /// transaction. The default implementation delegates to
126    /// `Transaction::get_store`, which checks `_index` and dispatches to either
127    /// [`Self::load`] (for an existing subtree) or [`Self::register`] (for a new
128    /// one). Stores with construction needs that do not fit the load/register
129    /// split — cross-subtree merge, external state, conditional initialization —
130    /// may override this method directly.
131    ///
132    /// # Arguments
133    /// * `txn` - The transaction this Store will operate within.
134    /// * `name` - The subtree name identifying this data partition.
135    ///
136    /// # Returns
137    /// A `Result<Self>` containing the opened Store.
138    async fn open(txn: &Transaction, name: impl Into<String> + Send) -> Result<Self> {
139        txn.get_store::<Self>(name).await
140    }
141
142    /// Gets the current configuration for this Store from the `_index` subtree.
143    ///
144    /// # Returns
145    /// A `Result<Doc>` containing the configuration document.
146    ///
147    /// # Errors
148    /// Returns an error if the subtree is not registered in `_index`.
149    async fn get_config(&self) -> Result<Doc> {
150        let index = self.transaction().get_index().await?;
151        let info = index.get_entry(self.name()).await?;
152        Ok(info.config)
153    }
154
155    /// Sets the configuration for this Store in the `_index` subtree.
156    ///
157    /// This method updates the `_index` with the Store's type ID and the provided
158    /// configuration. It's called automatically by `register()` and can be used to
159    /// update configuration during a transaction.
160    ///
161    /// # Arguments
162    /// * `config` - The configuration document to store.
163    ///
164    /// # Returns
165    /// A `Result<()>` indicating success or failure.
166    async fn set_config(&self, config: Doc) -> Result<()> {
167        let index = self.transaction().get_index().await?;
168        index
169            .set_entry(self.name(), Self::type_id(), config)
170            .await?;
171        Ok(())
172    }
173
174    /// Gets the height strategy for this Store from the `_index` subtree.
175    ///
176    /// Returns `None` if no strategy is set (meaning the subtree inherits
177    /// from the database-level height strategy).
178    ///
179    /// # Returns
180    /// A `Result<Option<HeightStrategy>>` containing the strategy if set.
181    ///
182    /// # Errors
183    /// Returns an error if the subtree is not registered in `_index`.
184    async fn get_height_strategy(&self) -> Result<Option<HeightStrategy>> {
185        let index = self.transaction().get_index().await?;
186        let settings = index.get_subtree_settings(self.name()).await?;
187        Ok(settings.height_strategy)
188    }
189
190    /// Sets the height strategy for this Store in the `_index` subtree.
191    ///
192    /// Pass `None` to inherit from the database-level strategy,
193    /// or `Some(strategy)` for independent height calculation.
194    ///
195    /// # Arguments
196    /// * `strategy` - The height strategy to use, or None for inheritance.
197    ///
198    /// # Returns
199    /// A `Result<()>` indicating success or failure.
200    ///
201    /// # Errors
202    /// Returns an error if the subtree is not registered in `_index`.
203    async fn set_height_strategy(&self, strategy: Option<HeightStrategy>) -> Result<()> {
204        let index = self.transaction().get_index().await?;
205        let mut settings = index.get_subtree_settings(self.name()).await?;
206        settings.height_strategy = strategy;
207        index.set_subtree_settings(self.name(), settings).await
208    }
209
210    /// Returns the local (staged) data for this store from the current transaction.
211    ///
212    /// This is a convenience method that retrieves data staged in the transaction
213    /// for this store's subtree. Returns `Ok(None)` if no data has been staged.
214    fn local_data(&self) -> Result<Option<Self::Data>> {
215        self.transaction().get_local_data::<Self::Data>(self.name())
216    }
217}