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