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}