///|
/// Owner-bound event capability. `publish` is fixed to the namespace the
/// Agent granted — an extension's manifest id — while `subscribe` may listen
/// to any namespace. Product code obtains the value from
/// `CompositionView::events`; only Posoco core holds the core publish handle.
pub struct Events {
priv publish_ : (Event, @types.EventScope?) -> Result[Unit, EventError]
priv subscribe_ : ((EventEnvelope) -> Unit, EventNamespace?) -> Result[
Subscription,
EventError,
]
}
///|
/// Framework construction hook. Product code obtains the value through
/// `CompositionView::events` and does not need this constructor.
pub fn Events::from_callbacks(
publish~ : (Event, @types.EventScope?) -> Result[Unit, EventError],
subscribe~ : ((EventEnvelope) -> Unit, EventNamespace?) -> Result[
Subscription,
EventError,
],
) -> Events {
{ publish_: publish, subscribe_: subscribe, }
}
///|
/// Publish one event in the namespace this capability is bound to. `scope`
/// carries session/run/turn attribution when the notification belongs to a
/// run. An extension capability holding a core payload fails with
/// `ReservedEvent`; a closed Agent fails with `Closed`; no listener succeeds
/// with no side effect. A community payload is delivered as published, so it
/// must be immutable data rather than a value the publisher mutates later.
///
/// # Example
/// ```mbt check
/// test {
/// // This fake models the real Agent's reserved-payload rejection.
/// // Extensions receive their capability from CompositionView, not this factory.
/// let events = @events.Events::from_callbacks(
/// publish=fn(event, _scope) {
/// match event {
/// @events.Core(_) => Err(@events.ReservedEvent)
/// _ => Ok(())
/// }
/// },
/// subscribe=fn(_handler, _namespace_) { Err(@events.Closed) },
/// )
/// guard events.publish(@events.Core(TurnStarted)) is Err(ReservedEvent) else {
/// fail("extensions must not publish core notifications")
/// }
/// }
/// ```
pub fn Events::publish(
self : Events,
event : Event,
scope~ : @types.EventScope? = None,
) -> Result[Unit, EventError] {
(self.publish_)(event, scope)
}
///|
/// Register one synchronous, non-raising listener. Delivery follows
/// registration order; the `namespace_` argument narrows delivery to one
/// namespace, and `None` listens to every namespace including Posoco. A
/// listener added or cancelled inside a delivery batch takes effect from the
/// next batch, and a new listener receives no history.
///
/// # Example
/// ```mbt check
/// test {
/// let seen_filter : Ref[@events.EventNamespace?] = Ref(None)
/// let events = @events.Events::from_callbacks(
/// publish=fn(_event, _scope) { Ok(()) },
/// subscribe=fn(_handler, namespace_) {
/// seen_filter.val = namespace_
/// Ok(@events.Subscription::from_cancel(fn() { () }))
/// },
/// )
/// let subscription = events.subscribe(
/// fn(_envelope) { () },
/// namespace_=Some(@events.Posoco),
/// )
/// guard subscription is Ok(_) else { fail("expected a subscription") }
/// assert_eq(seen_filter.val, Some(@events.Posoco))
/// }
/// ```
pub fn Events::subscribe(
self : Events,
handler : (EventEnvelope) -> Unit,
namespace_~ : EventNamespace? = None,
) -> Result[Subscription, EventError] {
(self.subscribe_)(handler, namespace_)
}