Skip to main content

keepsake_sqlx/repository/
timed.rs

1use chrono::{DateTime, Utc};
2#[cfg(any(feature = "postgres", feature = "sqlite", feature = "mysql"))]
3use keepsake::{RelationDefinition, RelationId, RelationSpec};
4#[cfg(all(
5    any(feature = "postgres", feature = "sqlite", feature = "mysql"),
6    feature = "fulfillment-counters"
7))]
8use uuid::Uuid;
9
10#[cfg(all(
11    any(feature = "postgres", feature = "sqlite", feature = "mysql"),
12    feature = "fulfillment-counters"
13))]
14use super::FulfilledExpiryCandidate;
15#[cfg(feature = "mysql")]
16use super::MySqlBackend;
17#[cfg(feature = "postgres")]
18use super::PostgresBackend;
19#[cfg(feature = "sqlite")]
20use super::SqliteBackend;
21use super::{KeepsakeSqlxBackend, NoopRelationCache, SqlxKeepsakeRepository};
22
23/// Timestamp-scoped repository view.
24///
25/// This wrapper does not read the system clock. Callers choose the timestamp once
26/// at an operation boundary, then use the forwarding methods to keep related
27/// writes and expiry scans on the same deterministic instant.
28#[derive(Debug, Clone, Copy)]
29pub struct TimedSqlxKeepsakeRepository<'repo, B, C = NoopRelationCache>
30where
31    B: KeepsakeSqlxBackend,
32{
33    pub(super) repository: &'repo SqlxKeepsakeRepository<B, C>,
34    pub(super) at: DateTime<Utc>,
35}
36
37/// Default Postgres timestamp-scoped repository view.
38#[cfg(feature = "postgres")]
39pub type TimedKeepsakeRepository<'repo, C = NoopRelationCache> =
40    TimedSqlxKeepsakeRepository<'repo, PostgresBackend, C>;
41
42/// `SQLite` timestamp-scoped repository view.
43#[cfg(feature = "sqlite")]
44pub type TimedSqliteKeepsakeRepository<'repo, C = NoopRelationCache> =
45    TimedSqlxKeepsakeRepository<'repo, SqliteBackend, C>;
46
47/// `MySQL` timestamp-scoped repository view.
48#[cfg(feature = "mysql")]
49pub type TimedMySqlKeepsakeRepository<'repo, C = NoopRelationCache> =
50    TimedSqlxKeepsakeRepository<'repo, MySqlBackend, C>;
51
52impl<'repo, B, C> TimedSqlxKeepsakeRepository<'repo, B, C>
53where
54    B: KeepsakeSqlxBackend,
55{
56    /// Returns the repository backing this timestamp-scoped view.
57    #[must_use]
58    pub const fn repository(&self) -> &'repo SqlxKeepsakeRepository<B, C> {
59        self.repository
60    }
61
62    /// Returns the timestamp applied by forwarding methods.
63    #[must_use]
64    pub const fn timestamp(&self) -> DateTime<Utc> {
65        self.at
66    }
67}
68
69#[cfg(feature = "postgres")]
70impl<C> TimedKeepsakeRepository<'_, C>
71where
72    C: super::RelationCache,
73{
74    /// Inserts or updates a relation definition using this view's timestamp.
75    pub async fn upsert_relation(
76        &self,
77        relation: &RelationDefinition,
78    ) -> super::RepositoryResult<RelationDefinition> {
79        self.repository.upsert_relation(relation, self.at).await
80    }
81
82    /// Inserts or updates a typed relation spec using this view's timestamp.
83    pub async fn upsert_relation_spec<Spec>(&self) -> super::RepositoryResult<RelationDefinition>
84    where
85        Spec: RelationSpec,
86    {
87        self.repository.upsert_relation_spec::<Spec>(self.at).await
88    }
89
90    /// Enables or disables a relation using this view's timestamp.
91    pub async fn set_relation_enabled(
92        &self,
93        relation_id: RelationId,
94        enabled: bool,
95    ) -> super::RepositoryResult<bool> {
96        self.repository
97            .set_relation_enabled(relation_id, enabled, self.at)
98            .await
99    }
100
101    /// Lists due timed expiry candidates using this view's timestamp.
102    pub async fn due_timed_expiry(
103        &self,
104        limit: i64,
105    ) -> super::RepositoryResult<Vec<super::TimedExpiryCandidate>> {
106        self.repository.due_timed_expiry(self.at, limit).await
107    }
108
109    /// Expires a stable batch of due timed keepsakes using this view's timestamp.
110    pub async fn expire_due_timed(&self, limit: i64) -> super::RepositoryResult<u64> {
111        self.repository.expire_due_timed(self.at, limit).await
112    }
113
114    /// Reads the persisted fulfillment counter snapshot for a keepsake.
115    #[cfg(feature = "fulfillment-counters")]
116    pub async fn fulfillment_snapshot(
117        &self,
118        keepsake_id: Uuid,
119    ) -> super::RepositoryResult<keepsake::FulfillmentSnapshot> {
120        self.repository.fulfillment_snapshot(keepsake_id).await
121    }
122
123    /// Lists fulfillment expiry candidates.
124    #[cfg(feature = "fulfillment-counters")]
125    pub async fn due_fulfilled_expiry(
126        &self,
127        limit: i64,
128    ) -> super::RepositoryResult<Vec<FulfilledExpiryCandidate>> {
129        self.repository.due_fulfilled_expiry(limit).await
130    }
131
132    /// Expires fulfillment-satisfied keepsakes using this view's timestamp.
133    #[cfg(feature = "fulfillment-counters")]
134    pub async fn expire_due_fulfilled(&self, limit: i64) -> super::RepositoryResult<u64> {
135        self.repository.expire_due_fulfilled(self.at, limit).await
136    }
137
138    /// Upserts a simple fulfillment counter projection using this view's timestamp.
139    #[cfg(feature = "fulfillment-counters")]
140    pub async fn upsert_counter_projection(
141        &self,
142        keepsake_id: Uuid,
143        key: &str,
144        value: i64,
145    ) -> super::RepositoryResult<()> {
146        self.repository
147            .upsert_counter_projection(keepsake_id, key, value, self.at)
148            .await
149    }
150
151    /// Atomically increments a fulfillment counter using this view's timestamp.
152    #[cfg(feature = "fulfillment-counters")]
153    pub async fn increment_counter_projection(
154        &self,
155        keepsake_id: Uuid,
156        key: &str,
157        delta: i64,
158    ) -> super::RepositoryResult<i64> {
159        self.repository
160            .increment_counter_projection(keepsake_id, key, delta, self.at)
161            .await
162    }
163
164    /// Upserts a checklist item completion projection using this view's timestamp.
165    #[cfg(feature = "fulfillment-counters")]
166    pub async fn upsert_checklist_projection(
167        &self,
168        keepsake_id: Uuid,
169        item: &str,
170        complete: bool,
171    ) -> super::RepositoryResult<()> {
172        self.repository
173            .upsert_checklist_projection(keepsake_id, item, complete, self.at)
174            .await
175    }
176}
177
178#[cfg(feature = "sqlite")]
179impl<C> TimedSqliteKeepsakeRepository<'_, C>
180where
181    C: super::RelationCache,
182{
183    /// Inserts or updates a relation definition using this view's timestamp.
184    pub async fn upsert_relation(
185        &self,
186        relation: &RelationDefinition,
187    ) -> super::RepositoryResult<RelationDefinition> {
188        self.repository.upsert_relation(relation, self.at).await
189    }
190
191    /// Inserts or updates a typed relation spec using this view's timestamp.
192    pub async fn upsert_relation_spec<Spec>(&self) -> super::RepositoryResult<RelationDefinition>
193    where
194        Spec: RelationSpec,
195    {
196        self.repository.upsert_relation_spec::<Spec>(self.at).await
197    }
198
199    /// Enables or disables a relation using this view's timestamp.
200    pub async fn set_relation_enabled(
201        &self,
202        relation_id: RelationId,
203        enabled: bool,
204    ) -> super::RepositoryResult<bool> {
205        self.repository
206            .set_relation_enabled(relation_id, enabled, self.at)
207            .await
208    }
209
210    /// Lists due timed expiry candidates using this view's timestamp.
211    pub async fn due_timed_expiry(
212        &self,
213        limit: i64,
214    ) -> super::RepositoryResult<Vec<super::TimedExpiryCandidate>> {
215        self.repository.due_timed_expiry(self.at, limit).await
216    }
217
218    /// Expires a stable batch of due timed keepsakes using this view's timestamp.
219    pub async fn expire_due_timed(&self, limit: i64) -> super::RepositoryResult<u64> {
220        self.repository.expire_due_timed(self.at, limit).await
221    }
222
223    /// Reads the persisted fulfillment counter snapshot for a keepsake.
224    #[cfg(feature = "fulfillment-counters")]
225    pub async fn fulfillment_snapshot(
226        &self,
227        keepsake_id: Uuid,
228    ) -> super::RepositoryResult<keepsake::FulfillmentSnapshot> {
229        self.repository.fulfillment_snapshot(keepsake_id).await
230    }
231
232    /// Lists fulfillment expiry candidates.
233    #[cfg(feature = "fulfillment-counters")]
234    pub async fn due_fulfilled_expiry(
235        &self,
236        limit: i64,
237    ) -> super::RepositoryResult<Vec<FulfilledExpiryCandidate>> {
238        self.repository.due_fulfilled_expiry(limit).await
239    }
240
241    /// Expires fulfillment-satisfied keepsakes using this view's timestamp.
242    #[cfg(feature = "fulfillment-counters")]
243    pub async fn expire_due_fulfilled(&self, limit: i64) -> super::RepositoryResult<u64> {
244        self.repository.expire_due_fulfilled(self.at, limit).await
245    }
246
247    /// Upserts a simple fulfillment counter projection using this view's timestamp.
248    #[cfg(feature = "fulfillment-counters")]
249    pub async fn upsert_counter_projection(
250        &self,
251        keepsake_id: Uuid,
252        key: &str,
253        value: i64,
254    ) -> super::RepositoryResult<()> {
255        self.repository
256            .upsert_counter_projection(keepsake_id, key, value, self.at)
257            .await
258    }
259
260    /// Atomically increments a fulfillment counter using this view's timestamp.
261    #[cfg(feature = "fulfillment-counters")]
262    pub async fn increment_counter_projection(
263        &self,
264        keepsake_id: Uuid,
265        key: &str,
266        delta: i64,
267    ) -> super::RepositoryResult<i64> {
268        self.repository
269            .increment_counter_projection(keepsake_id, key, delta, self.at)
270            .await
271    }
272
273    /// Upserts a checklist item completion projection using this view's timestamp.
274    #[cfg(feature = "fulfillment-counters")]
275    pub async fn upsert_checklist_projection(
276        &self,
277        keepsake_id: Uuid,
278        item: &str,
279        complete: bool,
280    ) -> super::RepositoryResult<()> {
281        self.repository
282            .upsert_checklist_projection(keepsake_id, item, complete, self.at)
283            .await
284    }
285}
286
287#[cfg(feature = "mysql")]
288impl<C> TimedMySqlKeepsakeRepository<'_, C>
289where
290    C: super::RelationCache,
291{
292    /// Inserts or updates a relation definition using this view's timestamp.
293    pub async fn upsert_relation(
294        &self,
295        relation: &RelationDefinition,
296    ) -> super::RepositoryResult<RelationDefinition> {
297        self.repository.upsert_relation(relation, self.at).await
298    }
299
300    /// Inserts or updates a typed relation spec using this view's timestamp.
301    pub async fn upsert_relation_spec<Spec>(&self) -> super::RepositoryResult<RelationDefinition>
302    where
303        Spec: RelationSpec,
304    {
305        self.repository.upsert_relation_spec::<Spec>(self.at).await
306    }
307
308    /// Enables or disables a relation using this view's timestamp.
309    pub async fn set_relation_enabled(
310        &self,
311        relation_id: RelationId,
312        enabled: bool,
313    ) -> super::RepositoryResult<bool> {
314        self.repository
315            .set_relation_enabled(relation_id, enabled, self.at)
316            .await
317    }
318
319    /// Lists due timed expiry candidates using this view's timestamp.
320    pub async fn due_timed_expiry(
321        &self,
322        limit: i64,
323    ) -> super::RepositoryResult<Vec<super::TimedExpiryCandidate>> {
324        self.repository.due_timed_expiry(self.at, limit).await
325    }
326
327    /// Expires a stable batch of due timed keepsakes using this view's timestamp.
328    pub async fn expire_due_timed(&self, limit: i64) -> super::RepositoryResult<u64> {
329        self.repository.expire_due_timed(self.at, limit).await
330    }
331
332    /// Reads the persisted fulfillment counter snapshot for a keepsake.
333    #[cfg(feature = "fulfillment-counters")]
334    pub async fn fulfillment_snapshot(
335        &self,
336        keepsake_id: Uuid,
337    ) -> super::RepositoryResult<keepsake::FulfillmentSnapshot> {
338        self.repository.fulfillment_snapshot(keepsake_id).await
339    }
340
341    /// Lists fulfillment expiry candidates.
342    #[cfg(feature = "fulfillment-counters")]
343    pub async fn due_fulfilled_expiry(
344        &self,
345        limit: i64,
346    ) -> super::RepositoryResult<Vec<FulfilledExpiryCandidate>> {
347        self.repository.due_fulfilled_expiry(limit).await
348    }
349
350    /// Expires fulfillment-satisfied keepsakes using this view's timestamp.
351    #[cfg(feature = "fulfillment-counters")]
352    pub async fn expire_due_fulfilled(&self, limit: i64) -> super::RepositoryResult<u64> {
353        self.repository.expire_due_fulfilled(self.at, limit).await
354    }
355
356    /// Upserts a simple fulfillment counter projection using this view's timestamp.
357    #[cfg(feature = "fulfillment-counters")]
358    pub async fn upsert_counter_projection(
359        &self,
360        keepsake_id: Uuid,
361        key: &str,
362        value: i64,
363    ) -> super::RepositoryResult<()> {
364        self.repository
365            .upsert_counter_projection(keepsake_id, key, value, self.at)
366            .await
367    }
368
369    /// Atomically increments a fulfillment counter using this view's timestamp.
370    #[cfg(feature = "fulfillment-counters")]
371    pub async fn increment_counter_projection(
372        &self,
373        keepsake_id: Uuid,
374        key: &str,
375        delta: i64,
376    ) -> super::RepositoryResult<i64> {
377        self.repository
378            .increment_counter_projection(keepsake_id, key, delta, self.at)
379            .await
380    }
381
382    /// Upserts a checklist item completion projection using this view's timestamp.
383    #[cfg(feature = "fulfillment-counters")]
384    pub async fn upsert_checklist_projection(
385        &self,
386        keepsake_id: Uuid,
387        item: &str,
388        complete: bool,
389    ) -> super::RepositoryResult<()> {
390        self.repository
391            .upsert_checklist_projection(keepsake_id, item, complete, self.at)
392            .await
393    }
394}