The Nonchalant wire protocol (rev 3)

  1. Messages
  2. A session
  3. Patch grammar
  4. Errors
  5. Semantics
  6. Transports

The protocol works over any ordered, reliable transport, including WebSocket, BroadcastChannel, worker ports, and in-memory channels. It carries plain-data state patches rather than markup or code. Any language can implement a host; this file and the conformance vectors (packages/wire/spec/vectors/*.json, format in packages/wire/spec/README.md) are the contract. External hosts certify against the same vectors the reference implementation runs in CI (packages/wire/test/vectors.test.ts). How a whole session behaves across drops, crashes, and reconnects (calls settle once, casts arrive in order, the client converges on the host's state) is model-checked in Quint, and the model's traces replay against the reference client and host in CI (packages/wire/spec/quint/).

Messages

Client → host:

{ op: "lookup", ref, name, v, args? }  // get-or-spawn by schema name; v is 3; args are data
{ op: "cast",   ref, msg? }            // fire-and-forget
{ op: "call",   ref, id, msg? }        // correlated request
{ op: "exit",   ref }                  // release this watch; see Semantics for reclamation

Host → client:

{ op: "yield",  ref, patch }           // reconcile(prev, next) since the previous yield
{ op: "reply",  ref, id, value? }
{ op: "done",   ref, value? }          // normal completion
{ op: "raise",  ref, error }           // failure; see the error shape below

ref is a client-chosen opaque string. On shared-bus transports, clients make refs globally unique (the reference implementation prefixes a per-session id).

v is the protocol revision the client speaks. A host answers a lookup whose v is missing or is not 3 with a raise (see Errors) naming the revision it speaks, so mismatched peers fail loudly on their first lookup.

An absent msg or value means "no value" (JavaScript's undefined); null is a value and is always sent. A call whose handler replies with nothing still gets a reply, one without a value field.

id is a safe integer (|id| ≤ 2^53 − 1), unique among the in-flight calls on its ref; it may be reused once answered. Decoders drop a call or reply whose id is not one.

A session

Diagram
sequenceDiagram
    participant C as client
    participant H as host
    C->>H: lookup ref=r1 name=cart v=3
    H->>C: yield r1 [full snapshot: ops against nothing]
    C->>H: cast r1 {type: add, ...}
    H->>C: yield r1 [set /items/0 ..., set /total ...]
    C->>H: call r1 id=1 {type: checkout}
    H->>C: reply r1 id=1 {ok: true}
    Note over C,H: connection drops and returns
    C->>H: lookup ref=r1 name=cart v=3
    H->>C: yield r1 [full snapshot again]
    C->>H: exit r1

Patch grammar

Patch = Op[]                        // applied in order
Op = ["set",    path, value]
   | ["del",    path]
   | ["splice", path, start, remove, insert[]]

Errors

error is a Json value. Two conventions:

The reference implementation emits { message, id? }; hosts may add fields. A lookup for a name outside the schema MUST answer with a process-level raise, as MUST a lookup with the wrong v. A lookup a host refuses for resource reasons (a per-session watch cap or lookup rate, for example) answers the same way. The ref never becomes watched.

Semantics

Transports

A transport carries opaque strings, ordered and reliably, and reports open/close. A message in flight when the connection drops is lost: a transport that reconnects MUST NOT deliver into the new connection anything sent on the old one. A socket gets this by being a new socket; the reference in-memory pair numbers its connections and drops a message sent on an earlier one. A late delivery would land a patch on a snapshot it was not computed against, or a done on a facade that has already looked its process up again. Two additional rules for shared buses (e.g. BroadcastChannel), where every peer hears every message:

The Node host (@nonchalant/host) additionally serves GET /schema over HTTP: { "protocol": 3, "names": [...] }, which exposes the whitelist for discovery.

The protocol does not define identity or grant access. A published schema only limits process names; it does not prove who the client is or whether that client may use particular lookup arguments and messages. The Node host can reject WebSocket upgrades by browser origin, authenticate requests, and give each connection a gateway that decides what it reaches and which messages it may send, while application processes remain responsible for record- and operation-level authorization. See Hosting safely.

This page's source on GitHub