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 {}