A document that doesn't need a sequencer
Every other sheet on this page assumes a server assigns the order that resolves concurrent writes. A CRDT document drops that assumption: browsers discover one another over WebRTC, edit while disconnected, reconnect in any order, and converge once they exchange state. The structures involved define their own merge rule instead of depending on somebody stamping a sequence number. The clap counter in the examples index is this running with the sequencer never in the picture at all: two tabs holding the clap button down both land, and nothing acknowledges either one.
Signaling introduces peers; it never sequences
A signaling service exchanges offers, answers, and ICE candidates so two browsers can open a direct connection (and admits a room, if it chooses to authenticate). It never assigns operation order, never stores document state, and cannot see a delta once the data channel is open: by protocol shape, an opaque envelope is the only thing that ever crosses it. A signaling outage blocks new peers from finding each other; it does not touch a document already converged.
The first topology is a small full mesh: every peer opens one data
channel to every other peer, capped at eight, and the ninth arrival is
refused with RoomFull rather than degrading the mesh
underneath everyone already in it. Applications supply their own STUN
and TURN servers (watershed ships neither); host candidates alone only
connect peers with no NAT between them.
Not every structure is eligible
A merge rule is what makes order-independence possible, so only the
channels built as CRDTs may run this way:
PN counter,
OR-set,
the OR-map,
G-set,
2P-set,
SharedSequence, and
SharedText. A p2p document's
root is one of these, chosen explicitly at config time,
never the ordinary last-write-wins SharedMap.
Everything that depends on server order or acknowledgement stays out: SharedMap, SharedCounter, and SharedDirectory resolve by sequence number; claims and registers persist one; TaskManager, pact maps, and ordered collections depend on ordered membership; and the OT-transformed json_ot and rich-text channels transform against sequenced history. None of these have a merge function to fall back on, so none of them are safe with no sequencer in the room.
One document, three policies
A CrdtDocument chooses its transports once, at
config time:
import watershed/crdt_js
import watershed/p2p
// Auto starts on WebRTC and attempts the relay in parallel — it never
// waits for one, and prefers it while healthy.
let config =
crdt_js.config(
room_id: "retro-board-9c2",
replica_label: "ada",
compatibility_tag: "retro-board/v1",
root: p2p.pn_counter_root(),
signaling: signaling,
)
|> crdt_js.with_transport_policy(crdt_js.Auto)
|> crdt_js.with_sequencer(crdt_js.sequencer("wss://relay.example/room")) Auto is the default: the mesh and any
configured relay each come up on their own schedule, readiness waits
only for the mesh, and the relay becomes the durable delta path once it
proves itself. P2pOnly
opens no relay socket and ignores a configured one entirely.
SequencedOnly is the inverse: no
signaling, no RTCPeerConnection, and readiness waits on the
relay alone, under a bounded deadline, because there is nothing else to
wait on. All three run the identical document over the identical wire
envelopes, so a snapshot exported under one policy imports under
either of the others.
Losing the relay doesn't lose the document. When a
primary relay connection drops, the document reports the failover,
pushes the digest it owed the mesh, asks its peers for state, and
keeps running on WebRTC: same public handles, same converged state,
one transport quietly retired. Readiness under
Auto never waited on the relay in the first place.
An optional durable relay
A relay speaking crdt_relay_v1 is a durable fan-out point,
not a sequencer transplanted into this mode: it stamps a diagnostic
order for its own log, broadcasts what it accepts, and answers a
request for the state it holds. It never merges, never decodes a
kernel payload, and never decides which of two replicas is right. That
stays the mesh's job. A room with no relay simply keeps its state in
whichever replicas are connected; one that attaches a relay keeps
running exactly the same after it disconnects again, minus the
durability.
A CRDT document trades the server's order for a merge rule, so it can run on a WebRTC mesh with no sequencer at all. Signaling only introduces peers; eligible structures converge by merge; and an optional relay attaches later purely for durability and reach, never changing what the document means.