Skip to main content

logicaffeine_compile/analysis/
relay.rs

1//! Whether a program's pages talk to the relay — and so whether the backend serving them answers
2//! `/_relay` itself.
3//!
4//! A page reaches the relay only through the server that served it: the browser dials `/_relay` on
5//! the page's own origin. `largo serve` answers it; a deployed backend answered nothing, so every
6//! `Publish`, message handler and `shared on` store died the moment an app left development. The
7//! backend serving such pages now answers it from its own `Serve`: the interpreter for the pages
8//! its program declares, a compiled binary for the `pages/` it embeds (`logos_assets::USES_RELAY`,
9//! from [`source_uses_the_relay`]).
10
11use crate::ast::component::{ComponentDef, ComponentKind, StoreMode, Trigger};
12use crate::ast::stmt::{SelectBranch, Stmt, UiStmt};
13
14/// Whether any page, component or store this program declares talks to the relay: a
15/// `## Store … shared on`, a `When a message arrives on …` handler, or a `Publish … on …` in any
16/// action or handler, at any depth.
17pub fn pages_use_the_relay(stmts: &[Stmt]) -> bool {
18    stmts
19        .iter()
20        .any(|s| matches!(s, Stmt::ComponentDef(def) if component_uses_the_relay(def)))
21}
22
23/// [`pages_use_the_relay`] for a program's source — a page file a site bundles. A source that does
24/// not parse uses nothing (its build refuses it on its own terms).
25pub fn source_uses_the_relay(source: &str) -> bool {
26    crate::ui_bridge::with_parsed_program(source, |parsed, _| {
27        parsed.is_ok_and(|(stmts, _, _)| pages_use_the_relay(stmts))
28    })
29}
30
31/// Whether one declared page, component or store talks to the relay (see [`pages_use_the_relay`]).
32pub fn component_uses_the_relay(def: &ComponentDef) -> bool {
33    matches!(
34        def.kind,
35        ComponentKind::Store {
36            mode: StoreMode::Shared { .. }
37        }
38    ) || def
39        .events
40        .iter()
41        .any(|h| matches!(h.trigger, Trigger::Message { .. }) || publishes(h.body))
42        || def.actions.iter().any(|a| publishes(a.body))
43}
44
45/// Whether these statements, at any depth, `Publish` on a topic.
46fn publishes(stmts: &[Stmt]) -> bool {
47    stmts.iter().any(|s| match s {
48        Stmt::Ui(UiStmt::Publish { .. }) => true,
49        Stmt::If {
50            then_block,
51            else_block,
52            ..
53        } => publishes(then_block) || else_block.is_some_and(publishes),
54        Stmt::While { body, .. }
55        | Stmt::Repeat { body, .. }
56        | Stmt::Zone { body, .. }
57        | Stmt::Splice { body } => publishes(body),
58        Stmt::Concurrent { tasks } | Stmt::Parallel { tasks } => publishes(tasks),
59        Stmt::Inspect { arms, .. } => arms.iter().any(|a| publishes(a.body)),
60        Stmt::Select { branches } => branches.iter().any(|b| match b {
61            SelectBranch::Receive { body, .. } | SelectBranch::Timeout { body, .. } => {
62                publishes(body)
63            }
64        }),
65        _ => false,
66    })
67}
68
69#[cfg(test)]
70#[path = "../../../logicaffeine_tests/unit/logicaffeine_compile/analysis/relay/tests.rs"]
71mod tests;