Skip to content

Channels

A channel is one shared list of which calls can go in each direction between two apps.

A channel lists which actions flow in each direction between two apps. You write it once in shared code and both sides use it, so neither side repeats it or drifts.

  • toAcceptor lists actions the connector sends to the acceptor. This is a normal request: client asks, server answers.
  • toConnector lists actions the acceptor pushes to the connector: the server calling the client. A domain can be in both lists if calls go both ways.
shared.ts
import { defineChannel } from "@nice-code/action";
export const appChannel = defineChannel({
toAcceptor: [act_user, act_lobby], // client → server requests
toConnector: [act_lobby], // server → client pushes (act_lobby goes both ways)
});

A channel holds the routing (the toAcceptor / toConnector lists) and a compact binary format it uses to send actions over the wire. It also holds a version number derived from your domains. Both sides build all of this from the same definition, so they can never disagree about the format or the version.

A channel only describes the actions and their format. It says nothing about how a connection is secured, so the same definition works whether the connection is plain or locked down. When you’re ready, Security Levels shows how the same channel gains authenticated or encrypted connections.

Both lists are optional. defineChannel({}) is a connection-only channel. It carries no actions in either direction, but the connection it establishes is real, with identity, security, and any frame protocols that use it. A realm-only app doesn’t need even that. It opens the same connection with createWireClient and no channel at all, because the two handshakes are interchangeable.

The compact binary format is built from the order of domains in each list, so the position of each one matters.

Always add new domains to the end of a list, and never reorder them. That keeps every existing action at the position it already had.

The version is a hash of every domain:action name in the channel, in order. So any change to that list gives a new version: reordering, appending a domain, or adding, removing or renaming an action. The secure handshake requires both sides to hold the same version. An out-of-date peer is cleanly rejected, instead of quietly sending things to the wrong place.

This means appending a domain also turns away clients built before the change. If old clients have to keep connecting, leave the deployed channel alone and serve a new one beside it. Evolving a deployed API shows how.