go-yjs-relay

A pure-Go Yjs y-websocket relay hub โ€” transport-agnostic, forward-compatible, no cgo.

pure Go ยท zero cgo Yjs y-websocket relay, not a CRDT transport-agnostic room multiplexing fan-out broadcast slow-peer drop room GC context-driven leave 100% coverage ยท -race 6 arches
Documentation GitHub
Documentation (MkDocs Material + mike) License: BSD-3-Clause Go 1.26.4+ Coverage 100%

go-yjs-relay is a pure-Go (no cgo) implementation of the server side of the Yjs y-websocket protocol as a relay hub. It multiplexes collaborative-editing connections into rooms and fans each client's binary frames out to the rest of the room โ€” the exact relay-only deployment mode the upstream y-websocket server uses. It is transport-agnostic: it imports no WebSocket library, so the caller drives a Membership handle from any Go WebSocket stack. It is a relay, not a server-side CRDT โ€” it does not decode Yjs updates; clients reconcile among themselves via Yjs's update + sync-step algebra, which keeps the server forward-compatible with new y-protocols versions for free and a few hundred lines instead of a several-thousand-line Go Yjs implementation. Pure stdlib (context + sync), 100% coverage under -race, CI green across 6 arches.

Rooms & Hub multiplexing ready

One Room per collaborative document, keyed by room ID. Hub.Join inserts a connection and hands back a Membership handle; Hub.MembersCount surfaces the live peer count that is the bidirectional-sync precondition.

Fan-out broadcast ready

Every binary frame a member Sends is delivered to every other member’s Recv channel โ€” echo-suppressed, so a client never sees its own frame. The fan-out runs under a tight per-room lock, microseconds even at hundreds of members.

Slow-peer drop & room GC ready

A member whose receive buffer is full has the frame dropped rather than blocking the hub โ€” Yjs re-syncs it via the next sync-step handshake. One straggler never stalls the room, and the room is reclaimed when its last member leaves.

Lifecycle: idempotent & context-driven leave ready

Membership.Leave is idempotent (safe from a deferred cleanup and a watcher at once); LeaveOnContextDone(ctx) unregisters the connection when its request context cancels, saving the caller a goroutine.

Transport-agnostic core & coverage ready

No WebSocket library is imported: the Membership handle is the seam any transport plugs into (nhooyr, gorilla, net/http, or an in-memory pipe). 100% coverage under -race, gofmt + go vet clean, green across all six 64-bit Go arches.

Persistence & snapshotting planned

Downstream by design: the relay holds no durable state. Persistence is bolted on by serialising the last update from any one client to a backing store โ€” an opt-in layer above the relay, not part of the hub.

A compact, robust relay: room multiplexing, echo-suppressed fan-out, non-blocking slow-peer drop, automatic room GC on the last leave, idempotent and context-driven Leave. No WebSocket library is baked in โ€” the Membership handle is the seam any transport plugs into (nhooyr.io/websocket, gorilla/websocket, net/http, or an in-memory pipe in tests). CGO-free, so it cross-compiles and embeds anywhere. Part of the github.com/go-yjs-relay org.