Entry points
All modules are ES modules with TypeScript declarations. Browser code never imports the server entry.
| Import | Runs in | What's in it |
|---|---|---|
twinwire/client | Browser | GameClient, EventClient, createEventClient, StateReceiver, LobbyClient, BrowserRtcPeer, StateTraceBuffer, BulkClient, BrowserWebTransportPeer |
twinwire/server | Node 22+ | createGameServer, createEventServer, GameServer, GameSession, Lobby, Room, createNativeRtcFactory, turnCredentials, cloudflareTurn |
twinwire/worker | Worker / Durable Object | Standard-WebSocket SocketSessionServer, GameSession; no Node/native imports |
twinwire/netcode | Both | Clock, timing, interpolation, prediction, inputs, snapshots, replication, codecs, spatial |
twinwire/browser | Browser | One file with the client, the lobby client and the netcode, for a script tag (window.Twinwire) or an import |
twinwire | Both | The wire protocol (encodeFrame, decodeFrame, FrameKind, VERSION), Endpoint, ReliableEvents, TokenBucket, TrafficBudget, StateTraceBuffer, estimateEgress, and the lobby's shared helpers |
The server needs ws (installed with Twinwire). WebRTC on the server needs node-datachannel 0.33.x, which is optional. WebTransport is a separate optional HTTP/3 provider. Without a proven datagram path, sessions use WebSocket.
For named JSON APIs and retained room membership, start with named channels. For a complete runnable game, use the starter.
Messages, in one table
sendEvent(json) | sendState(stream, bytes, options?) | |
|---|---|---|
| Delivery | Ordered, acknowledged, deduplicated, replayed after reconnects | Latest wins per stream (0 to 65535); older ones dropped |
| Size | JSON up to 64 KiB | Up to maxStateBytes (64 KiB; one datagram up to 1086 bytes) |
| Path | WebSocket, with a copy on the datagrams while they are proven | Datagrams while proven, else the WebSocket; over 8 KiB always the WebSocket |
| Receiver | onEvent(data, seq): each event once, in order | onState({ stream, seq, time, data, via }): newer than any seen on that stream |
| Options | ttlMs (100), priority (1), ack (false) |
Limits worth knowing
- No order holds between events and states. Stamp events with the server tick or a map generation when a snapshot depends on them.
- An event is acknowledged when your callback returns, not when a database commits. Give purchases an action id and make handlers idempotent.
- Sessions live in the server's memory: a restart starts everyone on a new session.
Optional WebTransport, bounded state tracing and independent HTTP resources explain the provider requirements, path selection, payload limits and measurement boundaries introduced in 3.0.