Skip to main content

thingd_core/
store.rs

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