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 /// Inventory-discovered topic descriptors.
85 fn registry(&self) -> &TopicRegistry;
86
87 /// Load the last committed checkpoint seq for a subscription partition.
88 ///
89 /// # Contract
90 ///
91 /// - Returns `None` when no checkpoint exists.
92 async fn get_checkpoint_seq(
93 &self,
94 subscription_name: &str,
95 topic_name: &str,
96 topic_key: Option<&str>,
97 ) -> Result<Option<i64>>;
98
99 /// Persist the high-water checkpoint seq for a subscription partition.
100 ///
101 /// # Contract
102 ///
103 /// - `last_seq` must not regress; coalesced writes may batch behind the scenes.
104 async fn set_checkpoint(
105 &self,
106 subscription_name: &str,
107 topic_name: &str,
108 topic_key: Option<&str>,
109 last_seq: i64,
110 ) -> Result<()>;
111}