1use std::fmt;
13use std::path::PathBuf;
14use std::sync::Arc;
15use std::time::SystemTime;
16
17use async_trait::async_trait;
18use corium_log::{LogError, MemLogRegistry, TransactionLog, VersionedLog};
19use corium_store::{BlobId, BlobIdStream, BlobStore, FsStore, MemoryStore, RootStore, StoreError};
20
21#[cfg(feature = "postgres")]
22use corium_store::PostgresBlobStore;
23#[cfg(feature = "turso")]
24use corium_store::TursoBlobStore;
25
26#[derive(Clone, Default)]
28pub enum StoreSpec {
29 Memory,
33 #[default]
35 Fs,
36 #[cfg(feature = "postgres")]
39 Postgres {
40 connection_string: String,
42 },
43 #[cfg(feature = "turso")]
47 Turso {
48 path: String,
50 },
51}
52
53impl fmt::Debug for StoreSpec {
54 fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
55 match self {
56 Self::Memory => formatter.write_str("Memory"),
57 Self::Fs => formatter.write_str("Fs"),
58 #[cfg(feature = "postgres")]
59 Self::Postgres { .. } => formatter
60 .debug_struct("Postgres")
61 .field("connection_string", &"[REDACTED]")
62 .finish(),
63 #[cfg(feature = "turso")]
64 Self::Turso { path } => formatter.debug_struct("Turso").field("path", path).finish(),
65 }
66 }
67}
68
69pub enum NodeStore {
74 Mem(MemoryStore),
76 Fs(FsStore),
78 #[cfg(feature = "postgres")]
80 Postgres(PostgresBlobStore),
81 #[cfg(feature = "turso")]
83 Turso(TursoBlobStore),
84}
85
86impl NodeStore {
87 #[allow(clippy::unused_async)]
94 pub async fn open(spec: &StoreSpec, data_dir: &std::path::Path) -> Result<Self, StoreError> {
95 match spec {
96 StoreSpec::Memory => Ok(Self::Mem(MemoryStore::default())),
97 StoreSpec::Fs => Ok(Self::Fs(FsStore::open(data_dir.join("store"))?)),
98 #[cfg(feature = "postgres")]
99 StoreSpec::Postgres { connection_string } => Ok(Self::Postgres(
100 PostgresBlobStore::connect(connection_string).await?,
101 )),
102 #[cfg(feature = "turso")]
103 StoreSpec::Turso { path } => Ok(Self::Turso(TursoBlobStore::open(path).await?)),
104 }
105 }
106}
107
108#[async_trait]
109impl BlobStore for NodeStore {
110 async fn put(&self, bytes: &[u8]) -> Result<BlobId, StoreError> {
111 match self {
112 Self::Mem(store) => store.put(bytes).await,
113 Self::Fs(store) => store.put(bytes).await,
114 #[cfg(feature = "postgres")]
115 Self::Postgres(store) => store.put(bytes).await,
116 #[cfg(feature = "turso")]
117 Self::Turso(store) => store.put(bytes).await,
118 }
119 }
120
121 async fn get(&self, id: &BlobId) -> Result<Option<Vec<u8>>, StoreError> {
122 match self {
123 Self::Mem(store) => store.get(id).await,
124 Self::Fs(store) => store.get(id).await,
125 #[cfg(feature = "postgres")]
126 Self::Postgres(store) => store.get(id).await,
127 #[cfg(feature = "turso")]
128 Self::Turso(store) => store.get(id).await,
129 }
130 }
131
132 async fn contains(&self, id: &BlobId) -> Result<bool, StoreError> {
133 match self {
134 Self::Mem(store) => store.contains(id).await,
135 Self::Fs(store) => store.contains(id).await,
136 #[cfg(feature = "postgres")]
137 Self::Postgres(store) => store.contains(id).await,
138 #[cfg(feature = "turso")]
139 Self::Turso(store) => store.contains(id).await,
140 }
141 }
142
143 async fn delete(&self, id: &BlobId) -> Result<(), StoreError> {
144 match self {
145 Self::Mem(store) => store.delete(id).await,
146 Self::Fs(store) => store.delete(id).await,
147 #[cfg(feature = "postgres")]
148 Self::Postgres(store) => store.delete(id).await,
149 #[cfg(feature = "turso")]
150 Self::Turso(store) => store.delete(id).await,
151 }
152 }
153
154 async fn list(&self) -> Result<BlobIdStream, StoreError> {
155 match self {
156 Self::Mem(store) => store.list().await,
157 Self::Fs(store) => store.list().await,
158 #[cfg(feature = "postgres")]
159 Self::Postgres(store) => store.list().await,
160 #[cfg(feature = "turso")]
161 Self::Turso(store) => store.list().await,
162 }
163 }
164
165 async fn modified_at(&self, id: &BlobId) -> Result<Option<SystemTime>, StoreError> {
166 match self {
167 Self::Mem(store) => store.modified_at(id).await,
168 Self::Fs(store) => store.modified_at(id).await,
169 #[cfg(feature = "postgres")]
170 Self::Postgres(store) => store.modified_at(id).await,
171 #[cfg(feature = "turso")]
172 Self::Turso(store) => store.modified_at(id).await,
173 }
174 }
175}
176
177#[async_trait]
178impl RootStore for NodeStore {
179 async fn get_root(&self, name: &str) -> Result<Option<Vec<u8>>, StoreError> {
180 match self {
181 Self::Mem(store) => store.get_root(name).await,
182 Self::Fs(store) => store.get_root(name).await,
183 #[cfg(feature = "postgres")]
184 Self::Postgres(store) => store.get_root(name).await,
185 #[cfg(feature = "turso")]
186 Self::Turso(store) => store.get_root(name).await,
187 }
188 }
189
190 async fn cas_root(
191 &self,
192 name: &str,
193 expected: Option<&[u8]>,
194 new: &[u8],
195 ) -> Result<(), StoreError> {
196 match self {
197 Self::Mem(store) => store.cas_root(name, expected, new).await,
198 Self::Fs(store) => store.cas_root(name, expected, new).await,
199 #[cfg(feature = "postgres")]
200 Self::Postgres(store) => store.cas_root(name, expected, new).await,
201 #[cfg(feature = "turso")]
202 Self::Turso(store) => store.cas_root(name, expected, new).await,
203 }
204 }
205
206 async fn delete_root(&self, name: &str) -> Result<(), StoreError> {
207 match self {
208 Self::Mem(store) => store.delete_root(name).await,
209 Self::Fs(store) => store.delete_root(name).await,
210 #[cfg(feature = "postgres")]
211 Self::Postgres(store) => store.delete_root(name).await,
212 #[cfg(feature = "turso")]
213 Self::Turso(store) => store.delete_root(name).await,
214 }
215 }
216
217 async fn list_roots(&self, prefix: &str) -> Result<Vec<String>, StoreError> {
218 match self {
219 Self::Mem(store) => store.list_roots(prefix).await,
220 Self::Fs(store) => store.list_roots(prefix).await,
221 #[cfg(feature = "postgres")]
222 Self::Postgres(store) => store.list_roots(prefix).await,
223 #[cfg(feature = "turso")]
224 Self::Turso(store) => store.list_roots(prefix).await,
225 }
226 }
227}
228
229pub enum LogBackend {
233 Fs(PathBuf),
235 Mem(MemLogRegistry),
237}
238
239impl LogBackend {
240 #[must_use]
242 pub fn for_spec(spec: &StoreSpec, data_dir: &std::path::Path) -> Self {
243 match spec {
244 StoreSpec::Memory => Self::Mem(MemLogRegistry::new()),
245 StoreSpec::Fs => Self::Fs(data_dir.join("logs")),
246 #[cfg(feature = "postgres")]
247 StoreSpec::Postgres { .. } => Self::Fs(data_dir.join("logs")),
248 #[cfg(feature = "turso")]
249 StoreSpec::Turso { .. } => Self::Fs(data_dir.join("logs")),
250 }
251 }
252
253 pub fn open(
258 &self,
259 name: &str,
260 write_version: u64,
261 ) -> Result<Arc<dyn TransactionLog>, LogError> {
262 match self {
263 Self::Fs(dir) => Ok(Arc::new(VersionedLog::open(dir, name, write_version)?)),
264 Self::Mem(registry) => Ok(Arc::new(registry.open(name, write_version))),
265 }
266 }
267
268 pub fn delete_all(&self, name: &str) -> Result<(), LogError> {
273 match self {
274 Self::Fs(dir) => VersionedLog::delete_all(dir, name),
275 Self::Mem(registry) => {
276 registry.delete_all(name);
277 Ok(())
278 }
279 }
280 }
281}