Skip to main content

yeti_types/plugins/
lifecycle.rs

1//! Fire-and-forget lifecycle and telemetry taps.
2//!
3//! Two Service shapes live here, both fire-and-forget:
4//!
5//! ## `Service<LifecycleEvent>`
6//!
7//! Replaces the legacy `dispatch_hook` callback registry. Plugins
8//! that want to observe lifecycle events (`user_created`,
9//! `oauth_signup`, `magic_link_requested`, `user_verified`, etc.)
10//! register a `tower::Service<LifecycleEvent, Response = ()>`.
11//!
12//! ## `TelemetryService` (`Service<TelemetryEvent>`)
13//!
14//! Replaces the legacy `EventSubscriber` trait whose `run(self,
15//! rx: Receiver<Value>)` shape forced the subscriber to own the
16//! event stream. Per ADR-006 the host now owns the receive loop and
17//! calls each registered Service per event. Composition wins:
18//! `tower::trace::TraceLayer`, `tower::timeout::TimeoutLayer`,
19//! `tower::limit::ConcurrencyLimitLayer` all stack onto the
20//! Service before registration.
21//!
22//! Both Services log + drop on error — neither lifecycle nor
23//! telemetry dispatch should ever fail the originating call site.
24
25use serde_json::Value;
26use tower::util::BoxCloneSyncService;
27
28use crate::error::YetiError;
29
30/// A single lifecycle event. `kind` is a stable string the host
31/// uses to route events; plugins filter on `kind` themselves and
32/// no-op for events they don't care about.
33#[derive(Debug, Clone)]
34pub struct LifecycleEvent {
35    /// Event kind — stable identifier. Examples: `"user_created"`,
36    /// `"oauth_signup"`, `"magic_link_requested"`, `"user_verified"`.
37    pub kind: &'static str,
38
39    /// Event payload — opaque JSON. Schema is per-event-kind and
40    /// documented in the `yeti-auth` (or other emitting crate's) docs.
41    pub payload: Value,
42}
43
44/// A single telemetry event — opaque JSON shaped by tracing's event
45/// emitter (level, target, fields, span context). Plugins inspect
46/// fields they care about; everything else is ignored.
47pub type TelemetryEvent = Value;
48
49/// Tower-shaped telemetry-subscriber type.
50///
51/// The host owns the inbound `mpsc::Receiver<TelemetryEvent>` and
52/// drives a per-event call loop:
53///
54/// ```ignore
55/// while let Some(ev) = rx.recv().await {
56///     if let Ok(svc) = service.ready().await {
57///         let _ = svc.call(ev).await; // drop errors
58///     }
59///  }
60/// ```
61///
62/// Replaces the legacy `EventSubscriber::run(self, rx)` contract —
63/// see this module's docstring for the rationale. `BoxCloneSyncService`
64/// keeps the type `Send + Sync + Clone` so the host can spawn the
65/// drive loop on a dedicated task.
66pub type TelemetryService = BoxCloneSyncService<TelemetryEvent, (), YetiError>;