rings_node/extension/ext/mod.rs
1//! Unified, effect-separated protocol abstraction shared by `native` and `browser`.
2//!
3//! Design: *functional core, imperative shell*. A protocol author writes only a **pure**
4//! state transition over its **own** typed events and effects; all IO is performed by the
5//! extension's own [`Interpret`] shell, which is handed a namespace-scoped capability
6//! ([`Scope`]) — `send`/`inject` confined to the interpreter's own namespace.
7//!
8//! Notation (used throughout the doc-comments here):
9//!
10//! ```text
11//! decode : Wire ⇀ Event
12//! step : (Ctx S, Event) → Transition (S, Effect) where Transition (S,E) ≅ (S, [E])
13//! ```
14//!
15//! The effect algebra is **not** global: each extension defines `Protocol::Effect` and the
16//! interpreter that runs it. The core owns no `Effect` enum — adding an extension never
17//! touches the core, and a protocol can only emit its own effects (no global command bus).
18//!
19//! `step` is pure (no IO, clocks, globals) and total over well-typed events; the decode
20//! boundary makes "undecodable/foreign input" an explicit [`Reject`] instead of a silent
21//! no-op. The abstraction is identical on both targets; the sole divergence is the `Send` /
22//! `?Send` bound, isolated in [`MaybeSend`].
23//!
24//! ## Module layout
25//!
26//! - `envelope` — the wire [`Envelope`].
27//! - `protocol` — the pure core: [`Wire`]/[`Reject`] (decode boundary), [`Ctx`],
28//! `Inbound` (router-internal), [`Transition`], and the [`Protocol`] trait.
29//! - `interpret` — the per-extension imperative shell ([`Interpret`]).
30//! - `registry` — the scoped capability [`Scope`] handed to shells, plus the router-internal
31//! `Core` / `Handler` and the namespace registry ([`Extensions`]).
32
33mod envelope;
34mod interpret;
35mod protocol;
36mod registry;
37
38pub use envelope::Envelope;
39pub use interpret::Interpret;
40pub use protocol::Ctx;
41// Router internals — not part of the extension-author API (which is `Protocol` / `Interpret` /
42// `Scope` / `Transition` / …). Crate-visible only, so the old ambient `Core`/`Inbound` surface
43// cannot be used to bypass the scoped-capability boundary. `Handler`/`DynHandler` stay private
44// to `registry` (the erased router ABI; protocol authors never name them).
45pub(crate) use protocol::Inbound;
46pub use protocol::Protocol;
47pub use protocol::Reject;
48pub use protocol::Transition;
49pub use protocol::Wire;
50pub(crate) use registry::Core;
51pub use registry::EffectScope;
52pub use registry::Extensions;
53pub use registry::Scope;
54
55/// Auto-trait bound that is `Send + Sync` on native and empty on browser.
56///
57/// Lets the pure-core types be written once; the `Send`-ness divergence (browser futures
58/// are not `Send`) is confined here. `∀ T` on browser; `Send + Sync` elsewhere.
59#[cfg(rings_native)]
60pub trait MaybeSend: Send + Sync {}
61#[cfg(rings_native)]
62impl<T: Send + Sync> MaybeSend for T {}
63/// Auto-trait bound that is `Send + Sync` on native and empty on browser.
64#[cfg(rings_browser)]
65pub trait MaybeSend {}
66#[cfg(rings_browser)]
67impl<T> MaybeSend for T {}