Mental Model
Runtimes, peers, carriers, and channels — the four ideas behind nice-action.
@nice-code/action is a typed, transport-agnostic action system for calling functions across runtime boundaries (client/server, worker/worker, peer/peer) with full inference — including bi-directional calls where the acceptor pushes actions back over the same connection.
One sentence
Section titled “One sentence”A runtime links to a peer over a carrier, and the routing between them is declared once as a channel.
Identity, auth, and encryption work the same regardless of carrier. Only two distinctions survive every carrier:
- role — who dials (connector) vs who accepts and can push back (acceptor).
- shape — duplex (a WebSocket / WebRTC channel: push-capable) vs exchange (HTTP: one request, one reply).
The pieces
Section titled “The pieces”| Concept | What it is |
|---|---|
| ActionDomain | A named group of typed actions — your API surface. |
| ActionSchema | Input/output schema + declared error types for one action. |
| ActionRuntime | One per runtime; identifies it and dispatches actions to handlers. |
| Channel | The transport-agnostic routing contract + binary wire identity between two runtimes, declared by role (toAcceptor / toConnector). |
| Carrier | How bytes actually move: wsCarrier, httpCarrier, inMemoryCarrier, rtcCarrier (connector); wsAcceptorCarrier, httpAcceptorCarrier (acceptor). |
| Transport | A carrier wrapped with a security policy. You don’t build these directly — connectChannel / serveChannel apply the policy for you. |
| RuntimeCoordinate | Identifies an environment (frontend, backend, worker…) — how actions are routed. |
One runtime per client
Section titled “One runtime per client”A client — a frontend, a backend, a worker — has a single ActionRuntime identifying it across every peer it talks to. Not one per feature, not one per backend. Register your local handlers on it, then connectChannel(...) once per peer (or serveChannel(...) to accept). This keeps one identity (and one crypto identity, for secure channels) per client and avoids routing ambiguity.
The two entry points
Section titled “The two entry points”connectChannel (dial out) and serveChannel (accept) are what you reach for 95% of the time. They bind the channel, runtime, and crypto identity for you and desugar to lower-level handler/carrier/transport objects underneath.
The three-file shape
Section titled “The three-file shape”A typical app is exactly three files:
shared.ts— imported by both ends: the domains, the channel.server.ts— the acceptor: handlers +serveChannel.client.ts— the connector:connectChannel+ action calls.
The doc pages that follow are labelled by where each block runs.
Next: Defining actions →