Channels
Declare what flows in each direction between two runtimes — once.
A channel declares what flows in each direction between two runtimes, by role, so both ends derive their routing from one shared definition instead of restating domain lists.
toAcceptor— domains the connector sends to the acceptor (the classic “request”).toConnector— domains the acceptor pushes back to the connector (the classic “push”). A domain can appear in both lists if it’s bidirectional.
import { defineChannel } from "@nice-code/action";
export const appChannel = defineChannel({ toAcceptor: [userDomain, lobbyDomain], // client → server requests toConnector: [lobbyDomain], // server → client pushes (lobbyDomain is bidirectional)});Wire identity travels with the channel
Section titled “Wire identity travels with the channel”A channel carries both its routing (the toAcceptor / toConnector domains) and its wire identity — the positional binary wire dictionary and a version derived from the domains. The per-connection codec and version can never drift between the two ends, because both ends build them from the same definition.
Whether a given transport runs encrypted is a per-transport choice (secure on connectChannel’s transports, securityLevel on the acceptor) — not a property of the channel. One channel definition serves both plain and secure transports.
Positional ordering matters
Section titled “Positional ordering matters”The domain lists are positional for the binary wire dictionary.
Add new domains to the end of their list. Reordering shifts the version, and a stale peer is then cleanly rejected by the handshake instead of silently misrouting a frame.
This is the one rule to remember about channels: append, never reorder.
Next: Handlers →