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
152#[cfg(feature = "sqlite")]
153impl<C> TimedSqliteKeepsakeRepository<'_, C>
154where
155    C: super::RelationCache,
156{
157    /// Inserts or updates a relation definition using this view's timestamp.
158    pub async fn upsert_relation(
159        &self,
160        relation: &RelationDefinition,
161    ) -> super::RepositoryResult<RelationDefinition> {
162        self.repository.upsert_relation(relation, self.at).await
163    }
164
165    /// Inserts or updates a typed relation spec using this view's timestamp.
166    pub async fn upsert_relation_spec<Spec>(&self) -> super::RepositoryResult<RelationDefinition>
167    where
168        Spec: RelationSpec,
169    {
170        self.repository.upsert_relation_spec::<Spec>(self.at).await
171    }
172
173    /// Enables or disables a relation using this view's timestamp.
174    pub async fn set_relation_enabled(
175        &self,
176        relation_id: RelationId,
177        enabled: bool,
178    ) -> super::RepositoryResult<bool> {
179        self.repository
180            .set_relation_enabled(relation_id, enabled, self.at)
181            .await
182    }
183
184    /// Lists due timed expiry candidates using this view's timestamp.
185    pub async fn due_timed_expiry(
186        &self,
187        limit: i64,
188    ) -> super::RepositoryResult<Vec<super::TimedExpiryCandidate>> {
189        self.repository.due_timed_expiry(self.at, limit).await
190    }
191
192    /// Expires a stable batch of due timed keepsakes using this view's timestamp.
193    pub async fn expire_due_timed(&self, limit: i64) -> super::RepositoryResult<u64> {
194        self.repository.expire_due_timed(self.at, limit).await
195    }
196
197    /// Reads the persisted fulfillment counter snapshot for a keepsake.
198    #[cfg(feature = "fulfillment-counters")]
199    pub async fn fulfillment_snapshot(
200        &self,
201        keepsake_id: Uuid,
202    ) -> super::RepositoryResult<keepsake::FulfillmentSnapshot> {
203        self.repository.fulfillment_snapshot(keepsake_id).await
204    }
205
206    /// Lists fulfillment expiry candidates.
207    #[cfg(feature = "fulfillment-counters")]
208    pub async fn due_fulfilled_expiry(
209        &self,
210        limit: i64,
211    ) -> super::RepositoryResult<Vec<FulfilledExpiryCandidate>> {
212        self.repository.due_fulfilled_expiry(limit).await
213    }
214
215    /// Expires fulfillment-satisfied keepsakes using this view's timestamp.
216    #[cfg(feature = "fulfillment-counters")]
217    pub async fn expire_due_fulfilled(&self, limit: i64) -> super::RepositoryResult<u64> {
218        self.repository.expire_due_fulfilled(self.at, limit).await
219    }
220
221    /// Upserts a simple fulfillment counter projection using this view's timestamp.
222    #[cfg(feature = "fulfillment-counters")]
223    pub async fn upsert_counter_projection(
224        &self,
225        keepsake_id: Uuid,
226        key: &str,
227        value: i64,
228    ) -> super::RepositoryResult<()> {
229        self.repository
230            .upsert_counter_projection(keepsake_id, key, value, self.at)
231            .await
232    }
233}
234
235#[cfg(feature = "mysql")]
236impl<C> TimedMySqlKeepsakeRepository<'_, C>
237where
238    C: super::RelationCache,
239{
240    /// Inserts or updates a relation definition using this view's timestamp.
241    pub async fn upsert_relation(
242        &self,
243        relation: &RelationDefinition,
244    ) -> super::RepositoryResult<RelationDefinition> {
245        self.repository.upsert_relation(relation, self.at).await
246    }
247
248    /// Inserts or updates a typed relation spec using this view's timestamp.
249    pub async fn upsert_relation_spec<Spec>(&self) -> super::RepositoryResult<RelationDefinition>
250    where
251        Spec: RelationSpec,
252    {
253        self.repository.upsert_relation_spec::<Spec>(self.at).await
254    }
255
256    /// Enables or disables a relation using this view's timestamp.
257    pub async fn set_relation_enabled(
258        &self,
259        relation_id: RelationId,
260        enabled: bool,
261    ) -> super::RepositoryResult<bool> {
262        self.repository
263            .set_relation_enabled(relation_id, enabled, self.at)
264            .await
265    }
266
267    /// Lists due timed expiry candidates using this view's timestamp.
268    pub async fn due_timed_expiry(
269        &self,
270        limit: i64,
271    ) -> super::RepositoryResult<Vec<super::TimedExpiryCandidate>> {
272        self.repository.due_timed_expiry(self.at, limit).await
273    }
274
275    /// Expires a stable batch of due timed keepsakes using this view's timestamp.
276    pub async fn expire_due_timed(&self, limit: i64) -> super::RepositoryResult<u64> {
277        self.repository.expire_due_timed(self.at, limit).await
278    }
279
280    /// Reads the persisted fulfillment counter snapshot for a keepsake.
281    #[cfg(feature = "fulfillment-counters")]
282    pub async fn fulfillment_snapshot(
283        &self,
284        keepsake_id: Uuid,
285    ) -> super::RepositoryResult<keepsake::FulfillmentSnapshot> {
286        self.repository.fulfillment_snapshot(keepsake_id).await
287    }
288
289    /// Lists fulfillment expiry candidates.
290    #[cfg(feature = "fulfillment-counters")]
291    pub async fn due_fulfilled_expiry(
292        &self,
293        limit: i64,
294    ) -> super::RepositoryResult<Vec<FulfilledExpiryCandidate>> {
295        self.repository.due_fulfilled_expiry(limit).await
296    }
297
298    /// Expires fulfillment-satisfied keepsakes using this view's timestamp.
299    #[cfg(feature = "fulfillment-counters")]
300    pub async fn expire_due_fulfilled(&self, limit: i64) -> super::RepositoryResult<u64> {
301        self.repository.expire_due_fulfilled(self.at, limit).await
302    }
303
304    /// Upserts a simple fulfillment counter projection using this view's timestamp.
305    #[cfg(feature = "fulfillment-counters")]
306    pub async fn upsert_counter_projection(
307        &self,
308        keepsake_id: Uuid,
309        key: &str,
310        value: i64,
311    ) -> super::RepositoryResult<()> {
312        self.repository
313            .upsert_counter_projection(keepsake_id, key, value, self.at)
314            .await
315    }
316}