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}