Photon

Realtime product state

Photon is a Rust pub/sub runtime with typed topics and durable subscriptions. Storage is pluggable via StoragePort—built-in mem, sqlite, nats, fluvio, and kafka adapters—so the same publish/subscribe surface runs embedded or brokered.

Why this family exists

Realtime product state should not force a separate broker ops story for every app. Photon models typed topics, ephemeral or durable subscriptions, checkpoints, and keyed partitions over a persistent event log. Canonical data stays in your datastore; the transport log is encrypted, transient event delivery. Leptos clients subscribe over WebSockets and refetch synced server functions when events arrive.

Where it lands

Photon topics stream progress into the browser in the counter demo and anywhere Orbital binds subscriptions—live UI without treating the transport log as the system of record.
Declare #[photon::topic] payloads and #[photon::subscribe] workers, boot Photon::builder().auto_registry(), and publish with publish_on. Durable subscriptions checkpoint last-delivered sequence per partition; keyed topics partition by field for filtered consume. Use mem for local/dev, sqlite for durable single-process, or broker adapters for fleet scale. Primary guide: docs.rs/uf-photon getting started (embedded vs brokered).

In the workspace

Photon topics stream progress into the browser in the counter demo and anywhere Orbital binds subscriptions—live UI without treating the transport log as the system of record.

Code sample

From the upstream getting-started docs — see also all code recipes on Resources.
Photon topicPhoton
Typed topic + durable subscribe
use std::sync::Arc;
use photon::{subscribe, topic, JsonIdentityFactory, Photon};
use photon_core::Actor;

#[topic(name = "orders.created", keyed_by = "order_id")]
pub struct OrderCreated {
    pub order_id: String,
    pub amount_cents: u64,
}

#[subscribe(topic = "orders.created", durable = "billing")]
async fn on_order_created(actor: Box<dyn Actor>, event: OrderCreated) -> photon::Result<()> {
    tracing::info!(actor = actor.label(), order = %event.order_id, cents = event.amount_cents);
    Ok(())
}

#[tokio::main]
async fn main() -> anyhow::Result<()> {
    let photon = Photon::builder().auto_registry().build()?;
    photon.start_executor(Arc::new(JsonIdentityFactory))?;

    OrderCreated {
        order_id: "ord-1".into(),
        amount_cents: 9900,
    }
    .publish_on(&photon)
    .await?;
    Ok(())
}