Skip to main content

origin_storage/
store.rs

1use async_trait::async_trait;
2use origin_domain::Result;
3use serde::{Deserialize, Serialize};
4use std::fmt::Debug;
5use time::OffsetDateTime;
6
7/// Addresses one record. The namespace groups records that are invalidated together,
8/// e.g. `github.notifications`.
9#[derive(Debug, Clone, PartialEq, Eq, Hash, PartialOrd, Ord, Serialize, Deserialize)]
10pub struct StorageKey {
11    namespace: String,
12    key: String,
13}
14
15impl StorageKey {
16    pub fn new(namespace: impl Into<String>, key: impl Into<String>) -> Self {
17        Self {
18            namespace: namespace.into(),
19            key: key.into(),
20        }
21    }
22
23    pub fn namespace(&self) -> &str {
24        &self.namespace
25    }
26
27    pub fn key(&self) -> &str {
28        &self.key
29    }
30}
31
32/// A stored value plus its cache metadata. `value` is JSON text.
33#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
34pub struct Record {
35    pub value: String,
36    #[serde(with = "time::serde::rfc3339")]
37    pub stored_at: OffsetDateTime,
38    #[serde(with = "time::serde::rfc3339::option")]
39    pub expires_at: Option<OffsetDateTime>,
40}
41
42impl Record {
43    pub fn new(value: impl Into<String>, stored_at: OffsetDateTime) -> Self {
44        Self {
45            value: value.into(),
46            stored_at,
47            expires_at: None,
48        }
49    }
50
51    pub fn expiring_at(mut self, expires_at: OffsetDateTime) -> Self {
52        self.expires_at = Some(expires_at);
53        self
54    }
55
56    /// Whether this record is stale at `now`. Records without an expiry never are.
57    pub fn is_expired_at(&self, now: OffsetDateTime) -> bool {
58        self.expires_at.is_some_and(|expires_at| now >= expires_at)
59    }
60}
61
62/// Persistence for cache entries, read models and local state.
63#[async_trait]
64pub trait Storage: Debug + Send + Sync + 'static {
65    /// Returns the record as stored, **including expired ones**. Callers that care
66    /// about freshness go through [`crate::Cache`].
67    async fn get(&self, key: &StorageKey) -> Result<Option<Record>>;
68
69    async fn put(&self, key: &StorageKey, record: Record) -> Result<()>;
70
71    /// Deleting a missing key succeeds.
72    async fn delete(&self, key: &StorageKey) -> Result<()>;
73
74    /// All keys in a namespace, in unspecified order.
75    async fn keys(&self, namespace: &str) -> Result<Vec<StorageKey>>;
76
77    /// Drop every record in a namespace.
78    async fn clear(&self, namespace: &str) -> Result<()>;
79
80    /// Drop every record whose namespace starts with `prefix`, and report how many.
81    ///
82    /// This is how disconnecting an account removes its data (ADR-0019): the caller
83    /// passes [`crate::namespace::account_prefix`] and needs to know nothing about
84    /// which namespaces each module wrote.
85    async fn clear_prefix(&self, prefix: &str) -> Result<usize>;
86}