diff --git a/ROADMAP.md b/ROADMAP.md index d8f8c50..a68598f 100644 --- a/ROADMAP.md +++ b/ROADMAP.md @@ -265,18 +265,69 @@ pid) + spawns the relay, `broadcast_from(self_pid(), ..)` for no-echo. ## v0.6 — Channels -Phoenix channels: the join/leave/event protocol over WebSocket transport, -PubSub underneath. +Join/leave/event protocol over WebSocket transport, PubSub underneath. +Design decisions ratified pre-code (2026-06-12): -- `Channel` trait: `join(topic, payload, socket)`, `handle_in(event, - payload, socket)`, `handle_out`, `terminate`. -- Topic router (`"room:*"` patterns) → channel actor per (conn, topic), - spawned under the ws connection, linked so conn death reaps channels. -- Wire format: Phoenix V2 JSON serializer - (`[join_ref, ref, topic, event, payload]`) — free interop with - phoenix.js clients is the killer feature; needs a JSON dep or a - hand-rolled mini-codec (decision point — this would be dep #3). -- Presence: explicitly out of scope until distribution exists somewhere. +**Feature flags.** Two additive features: +- `"channels"` — the `Channel` trait, `TopicRouter` trait, `PrefixRouter`, + `ChannelSocket`, session machinery. Zero new deps; pubsub is already + always-on (it only exists because channels needs it). +- `"phoenix"` — implies `"channels"` + `serde`/`serde_json` + the V2 frame + codec. Opt-in phoenix.js interop; users who want protobuf or any other + wire format take `"channels"` only and supply their own codec. + +**Payload generics.** The `Channel` trait is generic over payload: +`P: Encode + Decode` where `Encode`/`Decode` are urus codec traits (one +method each). The `"phoenix"` feature provides a `JsonCodec` blanket impl +via serde. No JSON anywhere in the `"channels"` surface. + +**`Channel` trait shape.** +``` +trait Channel
: Send + 'static { + fn join(topic: &str, payload: P, socket: &ChannelSocket
) -> Result
; + fn handle_in(&mut self, event: &str, payload: P, socket: &ChannelSocket
);
+ fn terminate(&mut self) {}
+}
+```
+`handle_out` deferred — can land as a defaulted method, non-breaking.
+
+**`ChannelSocket`.** Newtype over `WsSender` + a `PubSub` handle.
+Exposes `reply(ref, status, payload)`, `push(event, payload)`,
+`broadcast(topic, payload)`. The `ref` from the incoming frame is threaded
+in by the channel actor loop, invisible to the impl. Coupling to `PubSub`
+is intentional: channels owns pubsub, and broadcast is the core use case.
+
+**Topic router.** `TopicRouter` trait: `fn route(&self, topic: &str) ->
+Option