Skip to content
Loess

@loess/relay

The relay passes notices about circles to the devices that follow them, and checks nothing, so it holds no rules and no state. A device checks a notice the way it checks what it reads from storage.

Interface

Relay is one Effect service with two calls:

  • publish(notice) passes a Notice to every follow of its circle. A Notice is a transaction with its inboxCounter, or with none when storage publishes it as the write arrives, before it stores it; a head with the objects its commit added; or a sealed signal.
  • follow(circles) takes each FollowedCircle, a circle with the number of the head the device holds, and opens a Follow until its scope closes. add and remove change its circles while it stays open, and notices is its stream of notices, which fails with RelayError when the connection ends.

relayOverSocket is the relay a device uses. Each follow is one WebSocket from @portable/websockets for as long as it is open. The device sends a Follow message for the circles it adds and an Unfollow for those it removes. On every new connection it sends Follow messages with all its circles, each from the newest head the follow has passed on. The device pings every 30 seconds and reconnects after a silent or dropped connection. publish sends a signal on the open follow's socket, so the relay passes it to the circle's other follows and not back to the sender. A device sends each write only to storage, and storage publishes it. A follow reads storage through a StoredReader, a type of this package that the Storage service and r2Reader both match, so the relay imports nothing from storage.

EntryGives
@loess/relay/memorylayerMemoryRelay, in process, for tests
@loess/relay/durableObjectrelayObjectClass, one hibernating WebSocket for each follow; layerDurableObjectRelay, the layer a Worker publishes through; passFollow, which hands a device's upgrade to the object; and r2Reader
@loess/relay/testtestRelay, the contract suite

What it promises

When a device starts to follow a circle, the relay first sends the circle's head if it is newer than the one the device holds, then each transaction waiting in the inbox that the head hasn't settled, in storage's order, and then each notice of the circle as it is published. A notice published while the relay reads what the device missed is sent after it. Once the device removes the circle, the relay sends none of its notices.

Tests

pnpm vitest run --project relay runs testRelay against the memory layer and against the Durable Object in a Worker in miniflare, followed over a real WebSocket.