Skip to main content

thingd_core/
store.rs

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