Skip to main content

demo_feed_publisher/
main.rs

1//! Demo graph feed publisher over the SDK's opaque-byte TCP publish path.
2//!
3//! This is deliberately an example target, not part of `liminal-sdk`'s public API.
4//! Run it with `cargo run -p liminal-sdk --example demo_feed_publisher`.
5//! `LIMINAL_ADDRESS`, `LIMINAL_DEMO_CHANNEL`, `LIMINAL_DEMO_COMPONENT_ID`, and
6//! `LIMINAL_DEMO_GENERATION_FILE` override the documented defaults below.
7
8mod authority;
9mod cadence;
10mod envelope;
11mod graph;
12mod jcs;
13
14use std::env;
15use std::path::PathBuf;
16use std::process::ExitCode;
17use std::thread;
18
19use authority::{FeedAuthority, FileGenerationStore};
20use cadence::{CadenceEngine, SNAPSHOT_PERIOD, TICK_INTERVAL};
21use envelope::{ComponentId, FrameEnvelopeCodec};
22use graph::GraphViewState;
23use liminal_sdk::PushClient;
24
25const DEFAULT_ADDRESS: &str = "127.0.0.1:9000";
26const DEFAULT_CHANNEL: &str = "frame.demo.graph-view";
27const DEFAULT_COMPONENT_ID: &str = "graph-view-demo";
28const DEFAULT_GENERATION_FILE: &str = ".liminal-demo-feed-generation";
29
30#[derive(Debug, thiserror::Error)]
31enum AppError {
32    #[error("configuration variable {name} is not valid Unicode: {source}")]
33    Configuration {
34        name: &'static str,
35        source: env::VarError,
36    },
37    #[error(transparent)]
38    Authority(#[from] authority::AuthorityError),
39    #[error(transparent)]
40    Contract(#[from] envelope::EnvelopeError),
41    #[error(transparent)]
42    Graph(#[from] graph::GraphError),
43    #[error("failed to connect demo publisher: {0}")]
44    Connect(liminal_sdk::SdkError),
45    #[error(transparent)]
46    Cadence(#[from] cadence::CadenceError),
47}
48
49fn configured(name: &'static str, default: &str) -> Result<String, AppError> {
50    match env::var(name) {
51        Ok(value) => Ok(value),
52        Err(env::VarError::NotPresent) => Ok(default.to_owned()),
53        Err(source) => Err(AppError::Configuration { name, source }),
54    }
55}
56
57fn run() -> Result<(), AppError> {
58    let address = configured("LIMINAL_ADDRESS", DEFAULT_ADDRESS)?;
59    let channel = configured("LIMINAL_DEMO_CHANNEL", DEFAULT_CHANNEL)?;
60    let component_id = ComponentId::new(&configured(
61        "LIMINAL_DEMO_COMPONENT_ID",
62        DEFAULT_COMPONENT_ID,
63    )?)?;
64    let generation_file = PathBuf::from(configured(
65        "LIMINAL_DEMO_GENERATION_FILE",
66        DEFAULT_GENERATION_FILE,
67    )?);
68
69    let authority = FeedAuthority::start(FileGenerationStore::new(generation_file))?;
70
71    // PushWriter is the existing SDK path that preserves these opaque bytes exactly.
72    // Its schema id is zero and its `Result<(), SdkError>` is a write outcome, not a
73    // delivery acknowledgement; periodic snapshots provide demo resynchronization.
74    let client = PushClient::connect(&address).map_err(AppError::Connect)?;
75    let writer = client.writer_handle();
76    let mut cadence = CadenceEngine::new(
77        channel,
78        component_id,
79        authority,
80        FrameEnvelopeCodec,
81        GraphViewState::new()?,
82        writer,
83        SNAPSHOT_PERIOD,
84    )?;
85
86    cadence.emit_initial_snapshot()?;
87    loop {
88        // This wall clock is demo content pacing only. It is not protocol authority,
89        // retry authority, reconnect backoff, or a delivery timer.
90        thread::sleep(TICK_INTERVAL);
91        cadence.emit_tick()?;
92    }
93}
94
95fn main() -> ExitCode {
96    match run() {
97        Ok(()) => ExitCode::SUCCESS,
98        Err(error) => {
99            eprintln!("demo feed publisher failed: {error}");
100            ExitCode::FAILURE
101        }
102    }
103}