Skip to main content

photon_backend/backend/
photon_backend.rs

1//! Object-safe backend trait for Photon publish/subscribe runtimes.
2//!
3//! Prefer wiring a [`crate::storage::StoragePort`] through
4//! [`PhotonBuilder::storage_port`](https://docs.rs/uf-photon/latest/photon/struct.PhotonBuilder.html#method.storage_port).
5//! Implement this trait only for custom delivery stacks, then install via
6//! [`PhotonBuilder::backend_with_context`](https://docs.rs/uf-photon/latest/photon/struct.PhotonBuilder.html#method.backend_with_context).
7//!
8//! See also: [`crate::storage::StoragePort`], [`crate::GenericPhotonBackend`].
9
10use std::pin::Pin;
11
12use async_trait::async_trait;
13use futures::stream::Stream;
14use serde_json::Value;
15
16use crate::backend::BackendCapabilities;
17use crate::error::Result;
18use crate::models::Event;
19use crate::registry::TopicRegistry;
20
21/// Backend for publish/subscribe delivery and checkpoint persistence.
22///
23/// Most hosts never implement this — use a storage adapter and the default
24/// [`GenericPhotonBackend`](crate::GenericPhotonBackend). Custom delivery example:
25///
26/// ```rust,ignore
27/// use std::sync::Arc;
28/// use photon_backend::{BackendContext, PhotonBackend};
29/// use photon_runtime::Photon;
30///
31/// let _photon = Photon::builder()
32///     .backend_with_context(|ctx: BackendContext| {
33///         // return Ok(Arc::new(MyBackend::new(ctx)) as Arc<dyn PhotonBackend>)
34///         photon_backend::EmbeddedBackend::install_mem(ctx)
35///     })
36///     .auto_registry()
37///     .build()?;
38/// ```
39#[async_trait]
40pub trait PhotonBackend: Send + Sync {
41    /// Stable telemetry label for ops metrics (e.g. `"mem"`, `"nats"`).
42    fn telemetry_label(&self) -> &'static str {
43        "custom"
44    }
45
46    /// Adapter capabilities (replay window, get-by-id support, …).
47    fn capabilities(&self) -> BackendCapabilities {
48        BackendCapabilities::mem()
49    }
50
51    /// Append an event and return its event id.
52    ///
53    /// # Contract
54    ///
55    /// - Returns the stable `event_id` assigned by the underlying storage port.
56    /// - Ordering and dedupe are per `(topic_name, topic_key)` partition, not global.
57    async fn publish(
58        &self,
59        topic_name: &str,
60        topic_key: Option<&str>,
61        actor_json: Value,
62        payload_json: Value,
63    ) -> Result<String>;
64
65    /// Stream events for a topic partition, optionally replaying after `after_seq`.
66    ///
67    /// # Contract
68    ///
69    /// - Same semantics as [`crate::storage::StoragePort::subscribe`].
70    fn subscribe(
71        &self,
72        topic_name: String,
73        topic_key_filter: Option<String>,
74        after_seq: Option<i64>,
75    ) -> Pin<Box<dyn Stream<Item = Result<Event>> + Send>>;
76
77    /// Load a single event by id.
78    ///
79    /// # Contract
80    ///
81    /// - Returns `None` when unknown or truncated; see backend [`BackendCapabilities`].
82    async fn get_event(&self, event_id: &str) -> Result<Option<Event>>;
83
84    /// Bounded page of events for one topic (ops browse).
85    ///
86    /// Default returns empty when the adapter does not support listing.
87    async fn list_by_topic(
88        &self,
89        _topic_name: &str,
90        _topic_key: Option<&str>,
91        _after_seq: Option<i64>,
92        _limit: usize,
93    ) -> Result<Vec<Event>> {
94        Ok(Vec::new())
95    }
96
97    /// Bounded cross-topic page of newest events (ops browse).
98    ///
99    /// Default returns empty when the adapter does not support listing.
100    async fn list_recent(&self, _limit: usize) -> Result<Vec<Event>> {
101        Ok(Vec::new())
102    }
103
104    /// Inventory-discovered topic descriptors.
105    fn registry(&self) -> &TopicRegistry;
106
107    /// Load the last committed checkpoint seq for a subscription partition.
108    ///
109    /// # Contract
110    ///
111    /// - Returns `None` when no checkpoint exists.
112    async fn get_checkpoint_seq(
113        &self,
114        subscription_name: &str,
115        topic_name: &str,
116        topic_key: Option<&str>,
117    ) -> Result<Option<i64>>;
118
119    /// Persist the high-water checkpoint seq for a subscription partition.
120    ///
121    /// # Contract
122    ///
123    /// - `last_seq` must not regress; coalesced writes may batch behind the scenes.
124    async fn set_checkpoint(
125        &self,
126        subscription_name: &str,
127        topic_name: &str,
128        topic_key: Option<&str>,
129        last_seq: i64,
130    ) -> Result<()>;
131}