@nice-code/wire
v0.61.0
Published
Readme
@nice-code/wire
Docs: nicecode.io — guides, integrations, and the full API surface. Stability: nicecode.io/production/stability — the pre-1.0 posture, lockstep versioning, and the wire-format skew promise. Working with an AI assistant? Point it at nicecode.io/llms.txt — the complete, current docs flattened into plain text.
The connectivity backbone of the nice-code stack: everything between "carrier config" and "protocols may speak" lives here, exactly once — dialing + transport fallback, the authenticated/encrypted handshake, secure sessions (duplex and request/reply), reconnection, connection bindings + hibernation rehydrate, keepalive, and the frame-protocol multiplex that lets several protocols share one connection.
Wire is protocol-neutral by construction: it never names an action or a realm. The packages above it plug into its seams:
@nice-code/actionis the one lane protocol — the owner of every unprefixed frame and the supplier of the handshake's channel payload (dictionaryVersion/channels, signed into the challenge). ItsconnectChannel/serveChannel/serveDurableObjectare the DX most apps use; they assemble the wire machinery below.@nice-code/realm(and any future module) is a prefixed protocol module riding the same connection beside the lane — one registration per side is its entire integration.
Apps rarely import wire directly. You'll touch it when you configure a carrier, write a protocol module, or serve wire's acceptor on a host of your own.
What lives here
| Area | Modules |
| --- | --- |
| Carriers | The two carrier shapes (IDuplexCarrier push-capable / IExchangeCarrier request-reply) + sources over an opaque dial context: wsCarrier, rtcCarrier, inMemoryCarrier, httpCarrier, and the byte-channel builders. Adding a transport = writing one of these; every layer above is carrier-agnostic. |
| Crypto + handshake | createClientHandshake/createServerHandshake (hello→welcome→prove→accept; three levels: none/authenticated/encrypted), per-frame AES-GCM (actionFrameCrypto), TOFU verify-key resolvers, RuntimeCoordinate peer identity. The wire format is byte-frozen and pinned by goldens. |
| Connect layer | WireClient (dial-out: transport cache + preference-ordered fallback + mux + the connection lifecycle) and its public facade createWireClient, duplexLinkSession (carrier attach, handshake pump, crypto pipe, pre-ready buffering), exchangeSession + WireExchangeAcceptor (the stateless HTTP sibling — sealed hsc/t tokens, no server session state), WireAcceptor + serveWireDurableObject (accept-in: sessions per connection, security gate, protocol lifecycle fan-out, versioned bindings + rehydrate, keepalive), err_wire_connect. |
| Protocol mux | WireProtocolMux + the reserved prefix registry 0x00–0x0F (realm = 0x01, devtools = 0x02). Prefixed frames dispatch to their module; everything else is the lane's. Negotiation rides handshake caps (proto:<id>). |
| Reliability primitives | ReliableLog / ReliableInbox / reliable-stream ids + downgrade warnings — the neutral seq/ack machinery the lane's reliable tier is built on (semantics stay lane-side). |
| Platform | @nice-code/wire/platform/cloudflare: the SQL-backed ReliableLog store for Durable Objects. |
Writing a protocol module
The end-state contract this package exists for: a new protocol module implements one
IWireFrameProtocol per side — a stable id, a reserved prefixByte, onFrame, and optional
onAttach/onDetach — and registers it at construction (connector:
connectChannel({ protocols }) or mux.registerProtocol before connecting; acceptor:
serveChannel({ protocols }) or WireAcceptor's protocols). Registration must precede the
handshake: the advertised caps are persisted on the connection's binding and drive the
hibernation wake resume.
Rules the backbone enforces for you:
- Security gate: protocol frames (and lifecycle hooks) are dropped on unauthenticated
(
none-level) connections unless the protocol opts intoallowPlain. - Lifecycle fan-out:
onAttach/onDetachfire for every registered protocol the gate admits — the hooks must be idempotent, and detach is cleanup that never skips. - Duplex only: prefixed protocols need a push-capable transport. An exchange-only (HTTP)
connection carries only the lane — registering protocols on one raises
protocol_on_exchange_only.
Dependency direction
action → wire and realm → wire; never the reverse, and realm ∅→ action in production.
Where a protocol module must influence a connection, it does so through an opaque slot wire
defines: the dial context, the lane handshake config, or the binding's lane slot.
The full design record lives in PLAN-SHARED-BASE-CONNECT-LAYER.md (the plan that built this layer) and CENSUS-SHARED-BASE-CONNECT.md (the file-by-file ownership census).
