thingd_core/store.rs
1//! Storage traits implemented by thingd storage adapters.
2
3use crate::model::{ListEventsOptions, ListObjectsOptions, PutObjectOptions};
4use crate::{
5 MemoryEvent, MemoryObject, QueueClaimOptions, QueueJob, QueueNackOptions, ThingdResult,
6};
7
8/// Object storage operations.
9///
10/// # Examples
11///
12/// ```rust
13/// use thingd_core::{MemoryEngine, ObjectStore, MemoryObject};
14///
15/// let mut store = MemoryEngine::new();
16/// let obj = MemoryObject::new("users", "alice", r#"{"name":"Alice"}"#);
17/// store.put_object(obj).unwrap();
18///
19/// let user = store.get_object("users", "alice").unwrap();
20/// assert!(user.is_some());
21/// assert_eq!(store.count_objects().unwrap(), 1);
22/// ```
23pub trait ObjectStore {
24 /// Insert or replace an object.
25 ///
26 /// # Errors
27 ///
28 /// Returns an error when the backing store cannot persist the object.
29 fn put_object(&mut self, object: MemoryObject) -> ThingdResult<MemoryObject>;
30
31 /// Insert or replace multiple objects in a single transaction.
32 ///
33 /// This is significantly faster than calling `put_object` in a loop
34 /// because it avoids per-object transaction overhead.
35 ///
36 /// # Errors
37 ///
38 /// Returns an error when the backing store cannot persist any object.
39 fn put_objects_batch(&mut self, objects: Vec<MemoryObject>) -> ThingdResult<Vec<MemoryObject>> {
40 let mut results = Vec::with_capacity(objects.len());
41 for object in objects {
42 results.push(self.put_object(object)?);
43 }
44 Ok(results)
45 }
46
47 /// Insert or replace an object with explicit options.
48 ///
49 /// When `options.index` is `false`, the FTS search index is not updated.
50 /// Use this when only metadata changes (e.g. timestamp dedup) and the body
51 /// text is identical — avoids wasted FTS DELETE + INSERT.
52 ///
53 /// # Errors
54 ///
55 /// Returns an error when the backing store cannot persist the object.
56 fn put_object_with_options(
57 &mut self,
58 object: MemoryObject,
59 options: PutObjectOptions,
60 ) -> ThingdResult<MemoryObject> {
61 let _ = options;
62 self.put_object(object)
63 }
64
65 /// Read an object by collection and id.
66 ///
67 /// # Errors
68 ///
69 /// Returns an error when the backing store cannot read the object.
70 fn get_object(&self, collection: &str, id: &str) -> ThingdResult<Option<MemoryObject>>;
71
72 /// List objects in one or more collections, with optional filtering, limit, and offset.
73 ///
74 /// Pass an empty `ListObjectsOptions` to return all objects across all collections.
75 ///
76 /// # Errors
77 ///
78 /// Returns an error when the backing store cannot list objects.
79 fn list_objects(
80 &self,
81 collections: Option<&[String]>,
82 options: &ListObjectsOptions,
83 ) -> ThingdResult<Vec<MemoryObject>>;
84
85 /// Delete an object by collection and id.
86 ///
87 /// # Errors
88 ///
89 /// Returns an error when the backing store cannot delete the object.
90 fn delete_object(&mut self, collection: &str, id: &str) -> ThingdResult<bool>;
91
92 /// Delete multiple objects in a single transaction.
93 ///
94 /// Returns the number of deleted objects. The `SQLite` adapter emits a bulk
95 /// `DELETE` statement in one transaction. The default implementation loops
96 /// calling `delete_object`.
97 ///
98 /// # Errors
99 ///
100 /// Returns an error when the backing store cannot delete any object.
101 fn delete_objects_batch(&mut self, keys: &[(String, String)]) -> ThingdResult<u64> {
102 let mut count = 0u64;
103 for (collection, id) in keys {
104 if self.delete_object(collection, id)? {
105 count += 1;
106 }
107 }
108 Ok(count)
109 }
110
111 /// Count total objects across all collections.
112 ///
113 /// # Errors
114 ///
115 /// Returns an error when the backing store cannot count objects.
116 fn count_objects(&self) -> ThingdResult<u64>;
117
118 /// List all unique collection names.
119 ///
120 /// # Errors
121 ///
122 /// Returns an error when the backing store cannot list collections.
123 fn list_collections(&self) -> ThingdResult<Vec<String>>;
124}
125
126/// Append-only event log operations.
127///
128/// # Examples
129///
130/// ```rust
131/// use thingd_core::{MemoryEngine, EventLog, MemoryEvent, ListEventsOptions};
132///
133/// let mut store = MemoryEngine::new();
134/// let event = MemoryEvent::new("audit", "user.created", r#"{"user":"alice"}"#);
135/// store.append_event(event).unwrap();
136///
137/// let events = store.list_events(None, ListEventsOptions::default()).unwrap();
138/// assert_eq!(events.len(), 1);
139/// assert_eq!(events[0].event_type, "user.created");
140/// ```
141pub trait EventLog {
142 /// Append an event to a stream.
143 ///
144 /// # Errors
145 ///
146 /// Returns an error when the backing store cannot append the event.
147 fn append_event(&mut self, event: MemoryEvent) -> ThingdResult<MemoryEvent>;
148
149 /// Append multiple events to a stream in a single transaction.
150 ///
151 /// This is significantly faster than calling `append_event` in a loop.
152 ///
153 /// # Errors
154 ///
155 /// Returns an error when the backing store cannot append any event.
156 fn append_events_batch(&mut self, events: Vec<MemoryEvent>) -> ThingdResult<Vec<MemoryEvent>> {
157 let mut results = Vec::with_capacity(events.len());
158 for event in events {
159 results.push(self.append_event(event)?);
160 }
161 Ok(results)
162 }
163
164 /// List events, optionally filtered by stream, with pagination.
165 ///
166 /// # Errors
167 ///
168 /// Returns an error when the backing store cannot read events.
169 fn list_events(
170 &self,
171 stream: Option<&str>,
172 options: ListEventsOptions,
173 ) -> ThingdResult<Vec<MemoryEvent>>;
174
175 /// Count total events across all streams.
176 ///
177 /// # Errors
178 ///
179 /// Returns an error when the backing store cannot count events.
180 fn count_events(&self) -> ThingdResult<u64>;
181
182 /// List all unique stream names.
183 ///
184 /// # Errors
185 ///
186 /// Returns an error when the backing store cannot list streams.
187 fn list_streams(&self) -> ThingdResult<Vec<String>>;
188}
189
190/// Queue storage operations.
191///
192/// # Examples
193///
194/// ```rust
195/// use thingd_core::{MemoryEngine, QueueStore, QueueJob, QueueJobStatus};
196///
197/// let mut store = MemoryEngine::new();
198/// let job = QueueJob::new("emails", "job-1", r#"{"to":"alice@example.com"}"#, 3);
199/// store.push_job(job).unwrap();
200///
201/// let claimed = store.claim_job("emails").unwrap();
202/// assert!(claimed.is_some());
203/// let job = claimed.unwrap();
204/// assert_eq!(job.status, QueueJobStatus::Leased);
205///
206/// let completed = store.ack_job("emails", &job.id).unwrap();
207/// assert_eq!(completed.unwrap().status, QueueJobStatus::Completed);
208/// ```
209pub trait QueueStore {
210 /// Push a job onto a queue.
211 ///
212 /// # Errors
213 ///
214 /// Returns an error when the backing store cannot persist the job.
215 fn push_job(&mut self, job: QueueJob) -> ThingdResult<QueueJob>;
216
217 /// Push multiple jobs onto a queue in a single transaction.
218 ///
219 /// This is significantly faster than calling `push_job` in a loop.
220 ///
221 /// # Errors
222 ///
223 /// Returns an error when the backing store cannot persist any job.
224 fn push_jobs_batch(&mut self, jobs: Vec<QueueJob>) -> ThingdResult<Vec<QueueJob>> {
225 let mut results = Vec::with_capacity(jobs.len());
226 for job in jobs {
227 results.push(self.push_job(job)?);
228 }
229 Ok(results)
230 }
231
232 /// Claim the next ready job from a queue.
233 ///
234 /// # Errors
235 ///
236 /// Returns an error when the backing store cannot claim a job.
237 fn claim_job(&mut self, queue: &str) -> ThingdResult<Option<QueueJob>> {
238 self.claim_job_with_options(queue, QueueClaimOptions::default())
239 }
240
241 /// Claim the next ready job from a queue with explicit options.
242 ///
243 /// # Errors
244 ///
245 /// Returns an error when the backing store cannot claim a job.
246 fn claim_job_with_options(
247 &mut self,
248 queue: &str,
249 options: QueueClaimOptions,
250 ) -> ThingdResult<Option<QueueJob>>;
251
252 /// Acknowledge a leased job as completed.
253 ///
254 /// # Errors
255 ///
256 /// Returns an error when the backing store cannot update the job.
257 fn ack_job(&mut self, queue: &str, id: &str) -> ThingdResult<Option<QueueJob>>;
258
259 /// Claim and immediately ack a job in a single transaction.
260 ///
261 /// This is faster than calling `claim_job` + `ack_job` separately
262 /// because it avoids per-operation transaction overhead.
263 ///
264 /// # Errors
265 ///
266 /// Returns an error when the backing store cannot claim or ack the job.
267 fn claim_and_ack(
268 &mut self,
269 queue: &str,
270 options: QueueClaimOptions,
271 ) -> ThingdResult<Option<QueueJob>> {
272 if let Some(job) = self.claim_job_with_options(queue, options)? {
273 self.ack_job(queue, &job.id)
274 } else {
275 Ok(None)
276 }
277 }
278
279 /// Reject a leased job for retry or dead-letter routing.
280 ///
281 /// # Errors
282 ///
283 /// Returns an error when the backing store cannot update the job.
284 fn nack_job(&mut self, queue: &str, id: &str) -> ThingdResult<Option<QueueJob>> {
285 self.nack_job_with_options(queue, id, QueueNackOptions::default())
286 }
287
288 /// Reject a leased job for retry or dead-letter routing with explicit options.
289 ///
290 /// # Errors
291 ///
292 /// Returns an error when the backing store cannot update the job.
293 fn nack_job_with_options(
294 &mut self,
295 queue: &str,
296 id: &str,
297 options: QueueNackOptions,
298 ) -> ThingdResult<Option<QueueJob>>;
299
300 /// List all jobs in a queue.
301 ///
302 /// # Errors
303 ///
304 /// Returns an error when the backing store cannot read queue jobs.
305 fn list_jobs(&self, queue: &str) -> ThingdResult<Vec<QueueJob>>;
306
307 /// List dead-letter jobs in a queue.
308 ///
309 /// # Errors
310 ///
311 /// Returns an error when the backing store cannot read dead-letter jobs.
312 fn list_dead_jobs(&self, queue: &str) -> ThingdResult<Vec<QueueJob>>;
313
314 /// List all unique queue names.
315 ///
316 /// # Errors
317 ///
318 /// Returns an error when the backing store cannot list queues.
319 fn list_queues(&self) -> ThingdResult<Vec<String>>;
320
321 /// Count total active jobs across all queues.
322 ///
323 /// # Errors
324 ///
325 /// Returns an error when the backing store cannot count active jobs.
326 fn count_active_jobs(&self) -> ThingdResult<u64>;
327
328 /// Count total dead-letter jobs across all queues.
329 ///
330 /// # Errors
331 ///
332 /// Returns an error when the backing store cannot count dead jobs.
333 fn count_dead_jobs(&self) -> ThingdResult<u64>;
334}
335
336/// Search operations.
337///
338/// # Examples
339///
340/// ```rust
341/// use thingd_core::{MemoryEngine, ObjectStore, Searcher, MemoryObject, SearchOptions};
342///
343/// let mut store = MemoryEngine::new();
344/// store.put_object(MemoryObject::new("docs", "readme", "Getting started guide")).unwrap();
345///
346/// let results = store.search("getting started", SearchOptions::default()).unwrap();
347/// assert!(!results.is_empty());
348/// ```
349pub trait Searcher {
350 /// Search memory objects and event logs by query text.
351 ///
352 /// # Errors
353 ///
354 /// Returns an error when search query fails.
355 fn search(
356 &self,
357 query: &str,
358 options: crate::SearchOptions,
359 ) -> ThingdResult<Vec<crate::SearchHit>>;
360}
361
362/// Graph link operations.
363pub trait LinkStore {
364 /// Create a new graph link.
365 ///
366 /// # Errors
367 ///
368 /// Returns an error when the link cannot be persisted.
369 fn create_link(&mut self, link: crate::Link) -> ThingdResult<crate::Link>;
370
371 /// Delete a graph link by id.
372 ///
373 /// # Errors
374 ///
375 /// Returns an error when the link cannot be deleted.
376 fn delete_link(&mut self, id: &str) -> ThingdResult<bool>;
377
378 /// Get a graph link by id.
379 ///
380 /// # Errors
381 ///
382 /// Returns an error when the link cannot be read.
383 fn get_link(&self, id: &str) -> ThingdResult<Option<crate::Link>>;
384
385 /// Get neighbors of a reference (outgoing, incoming, or both).
386 ///
387 /// # Errors
388 ///
389 /// Returns an error when neighbors cannot be queried.
390 fn get_neighbors(
391 &self,
392 reference: &str,
393 direction: crate::LinkDirection,
394 options: crate::LinkQueryOptions,
395 ) -> ThingdResult<Vec<crate::Link>>;
396
397 /// Count total links.
398 ///
399 /// # Errors
400 ///
401 /// Returns an error when count fails.
402 fn count_links(&self) -> ThingdResult<u64>;
403}
404
405/// Full storage interface expected from thingd engine adapters.
406pub trait ThingStore: EventLog + ObjectStore + QueueStore + Searcher + LinkStore {}
407
408impl<T> ThingStore for T where T: EventLog + ObjectStore + QueueStore + Searcher + LinkStore {}