Skip to main content

rings_node/extension/ext/
mod.rs

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