Skip to main content

cratefield_core/
events.rs

1//! The in-process event bus (issue #4, architecture section 4).
2//!
3//! It exists so `waitlist.confirmed` can feed `email-signup` without a crate
4//! dependency between them. Handlers run through the request's [`Scope`]
5//! defer (`wait_until` on Workers); errors are logged with the event name
6//! and never fail the request. There is **no ordering guarantee and no
7//! persistence** — if the isolate dies between response and deferred run,
8//! the event is lost. Durable workflows are out of scope for the harness.
9
10use crate::scope::Scope;
11use futures_core::future::BoxFuture;
12use serde_json::Value;
13use std::sync::Arc;
14use tracing::{error, warn};
15
16/// Error type for handler and scheduled-work results.
17pub type AnyError = Box<dyn std::error::Error + Send + Sync + 'static>;
18
19/// Event names are `"<module>.<event>"`, e.g. `waitlist.confirmed`.
20pub type EventName = String;
21
22/// A registered handler: receives the emitting request's scope and the
23/// payload.
24pub type EventHandler =
25    Arc<dyn Fn(&Scope, Value) -> BoxFuture<'static, Result<(), AnyError>> + Send + Sync>;
26
27/// Registry of handlers, built once by `Harness::build` from every module's
28/// `events()`. Cheap to clone (one `Arc`).
29#[derive(Clone, Default)]
30pub struct EventBus {
31    handlers: Arc<Vec<(EventName, EventHandler)>>,
32}
33
34impl EventBus {
35    /// An empty bus (a harness with no subscriptions).
36    pub fn new() -> Self {
37        Self::default()
38    }
39
40    /// Builds a bus from collected `(name, handler)` pairs, appending the
41    /// new pairs after any existing ones (copy-on-write if the bus is
42    /// already shared). Modules register via `Module::events()` at
43    /// `Harness::build`.
44    #[must_use]
45    pub fn on(self, name: impl Into<EventName>, handler: EventHandler) -> Self {
46        let mut handlers: Vec<(EventName, EventHandler)> = match Arc::try_unwrap(self.handlers) {
47            Ok(handlers) => handlers,
48            Err(shared) => (*shared).clone(),
49        };
50        handlers.push((name.into(), handler));
51        Self {
52            handlers: Arc::new(handlers),
53        }
54    }
55
56    /// The registered (name, handler) pairs, in registration order.
57    pub fn handlers(&self) -> &[(EventName, EventHandler)] {
58        &self.handlers
59    }
60
61    /// Runs every handler registered for `name` through the scope's defer,
62    /// in the emitting request's `wait_until`. Never fails the request;
63    /// handler errors are logged with the event name.
64    // The by-value payload is the API fixed by issue #4; handlers each get
65    // a clone.
66    #[allow(clippy::needless_pass_by_value)]
67    pub fn emit_in(&self, scope: &Scope, name: &str, payload: Value) {
68        let matched: Vec<&(EventName, EventHandler)> = self
69            .handlers
70            .iter()
71            .filter(|(handler_name, _)| handler_name == name)
72            .collect();
73        for (_, handler) in &matched {
74            let fut = handler(scope, payload.clone());
75            let event = name.to_string();
76            scope.defer.wait_until(Box::pin(async move {
77                if let Err(err) = fut.await {
78                    error!(event = %event, error = %err, "event handler failed");
79                }
80            }));
81        }
82        if matched.is_empty() && !self.handlers.is_empty() {
83            warn!(event = %name, "emitted event has no registered handler");
84        }
85    }
86}