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

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;
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 new(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 `init()`, `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::new()`
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 init(txn: &Transaction, subtree_name: String) -> Result<Self> {
116        let store = Self::new(txn, subtree_name).await?;
117        store.set_config(Self::default_config()).await?;
118        Ok(store)
119    }
120
121    /// Gets the current configuration for this Store from the `_index` subtree.
122    ///
123    /// # Returns
124    /// A `Result<Doc>` containing the configuration document.
125    ///
126    /// # Errors
127    /// Returns an error if the subtree is not registered in `_index`.
128    async fn get_config(&self) -> Result<Doc> {
129        let index = self.transaction().get_index().await?;
130        let info = index.get_entry(self.name()).await?;
131        Ok(info.config)
132    }
133
134    /// Sets the configuration for this Store in the `_index` subtree.
135    ///
136    /// This method updates the `_index` with the Store's type ID and the provided
137    /// configuration. It's called automatically by `init()` and can be used to
138    /// update configuration during a transaction.
139    ///
140    /// # Arguments
141    /// * `config` - The configuration document to store.
142    ///
143    /// # Returns
144    /// A `Result<()>` indicating success or failure.
145    async fn set_config(&self, config: Doc) -> Result<()> {
146        let index = self.transaction().get_index().await?;
147        index
148            .set_entry(self.name(), Self::type_id(), config)
149            .await?;
150        Ok(())
151    }
152
153    /// Gets the height strategy for this Store from the `_index` subtree.
154    ///
155    /// Returns `None` if no strategy is set (meaning the subtree inherits
156    /// from the database-level height strategy).
157    ///
158    /// # Returns
159    /// A `Result<Option<HeightStrategy>>` containing the strategy if set.
160    ///
161    /// # Errors
162    /// Returns an error if the subtree is not registered in `_index`.
163    async fn get_height_strategy(&self) -> Result<Option<HeightStrategy>> {
164        let index = self.transaction().get_index().await?;
165        let settings = index.get_subtree_settings(self.name()).await?;
166        Ok(settings.height_strategy)
167    }
168
169    /// Sets the height strategy for this Store in the `_index` subtree.
170    ///
171    /// Pass `None` to inherit from the database-level strategy,
172    /// or `Some(strategy)` for independent height calculation.
173    ///
174    /// # Arguments
175    /// * `strategy` - The height strategy to use, or None for inheritance.
176    ///
177    /// # Returns
178    /// A `Result<()>` indicating success or failure.
179    ///
180    /// # Errors
181    /// Returns an error if the subtree is not registered in `_index`.
182    async fn set_height_strategy(&self, strategy: Option<HeightStrategy>) -> Result<()> {
183        let index = self.transaction().get_index().await?;
184        let mut settings = index.get_subtree_settings(self.name()).await?;
185        settings.height_strategy = strategy;
186        index.set_subtree_settings(self.name(), settings).await
187    }
188
189    /// Returns the local (staged) data for this store from the current transaction.
190    ///
191    /// This is a convenience method that retrieves data staged in the transaction
192    /// for this store's subtree. Returns `Ok(None)` if no data has been staged.
193    fn local_data(&self) -> Result<Option<Self::Data>> {
194        self.transaction().get_local_data::<Self::Data>(self.name())
195    }
196}