Concepts
Relay Broker
Send a request to whoever serves a topic and get the response back, point to point, streamed, never stored. Serving, standbys, claims, and when to choose it over the Wire Broker.
The Relay Broker is Thuum's request-response broker. A server serves a topic; a client sends a request on that topic and gets the response back, point to point: only the topic's active server sees the request, and only that client sees the response. Requests and responses are streamed in parts, and nothing is stored: Thuum holds a part in memory only while handing it from one connection to the other.
Choose it when you need to ask someone and wait for the answer: a language model, a computation, a lookup on a machine behind a firewall. Both sides only dial out to Thuum, so neither needs an open port or a fixed address.
Terms#
| Term | Meaning |
|---|---|
| Client | A connection that sends requests on a topic. Needs P on it. |
| Server | A connection that serves a topic: it receives the topic's requests and sends the responses. Needs S on it. |
| Request | What a client sends: one head, then zero or more body parts. |
| Response | What the server sends back for one request: one head, then zero or more body parts. |
| Head | The first part: what the request or response is (for HTTP: method, target and headers; or status and headers). |
| Body part | Each following part: content, such as a piece of a file or a chunk of a stream. |
done | true on the last part. A request or response without a body is a single head with done: true. |
Every part carries JSON data, at most 64 KiB, that Thuum never reads: what it holds is a
convention between the two apps. A request and its response may overlap: a server can start
answering before the request's last part.
Client and server are roles, not machines. One connection may send requests on some topics and serve others, as a Wire Broker connection may publish and subscribe.
Serving a topic#
- One active server per topic. The first connection to serve a topic is
active; the next ones arestandby, queued in the order they asked. - Only the active server receives requests. When it stops serving or disconnects, the first
standby becomes
active, and is told so. Run two servers with the same code and you have failover. - Exact topics. Serving
modelsreceives nothing sent tomodels:chat: serve each topic you answer. (Permissions still follow the Main Topic:Sonmodelslets a key servemodels:chat.) - Each part goes to the server active when it arrives. A server keeps answering the requests it already has, whatever its role.
A response goes back to the client connection that sent the request, and to no one else: Thuum
stamps each request part with the client's address (from), and the server addresses its response
to it. There is no fan-out; other clients and standby servers see nothing.
Claims#
Relay Broker claims are shaped like Wire Broker claims: data flags per Main Topic (covering its
sub-topics), a fallback, no topic_flags, and only two flags, read for requests and responses:
| Flag | Value | On the Relay Broker |
|---|---|---|
P | 8 | Request: send requests on the topic (head, body, cancel). |
S | 2 | Serve: serve the topic, and send the responses to its requests. |
Valid values are 0, 2, 8 and 10: nothing is stored, so there is nothing to write or read. A
client's key needs P only, and a server's S only, so a key copied from a client can never take
over a topic. See Claims and permissions.
Request and serve#
The Relay Broker is WebSocket only: there is no REST data route, because there is nothing to read.
With @thuum/client, a server answers each request with a
function, and a client gets a promise:
// The server: answers every request on `echo` with what it received.
const server = acme.relayBroker.connect();
await server.ready;
const role = await server.serve('echo', async (request) => ({
head: { echoed: request.head },
body: request.body
})); // 'active', or 'standby' behind another server
// The client, usually in another app.
const client = acme.relayBroker.connect();
await client.ready;
const response = await client.request('echo', { hello: 'world' }, ['a', 'b'], {
timeoutMs: 10_000
});
console.log(response.head); // { echoed: { hello: 'world' } }
for await (const part of response.body) console.log(part); // 'a', then 'b'request (needs P) sends the head and the body's parts, and resolves as soon as the response's
head arrives; its body yields the response's parts as they come. serve (needs S) resolves with
the server's role. A request to a topic nobody serves fails at once with unavailable: unlike the
Wire Broker, Thuum knows whether anyone is listening.
Delivery guarantees#
- Point to point. A request reaches one server, its response one client connection.
- In order. The parts of a request, and of a response, arrive in the order they were sent.
:okmeans handed over. A part's:okmeans the other side's connection took it, not that the app processed it: only the response says that.- Permissions are fixed at connect. A claim change applies to new connections.
Trade-offs#
Thuum keeps no state per request: a request exists only in the two apps at its ends. That is what makes "nothing stored" true, and it has a price:
| What happens | Result | What you do |
|---|---|---|
| The server dies mid-request | Thuum doesn't know who was waiting: nothing is reported to the client. | Run your own timeout on every request (timeoutMs). |
| The client disconnects | The server learns it at its next response part, which fails with client_gone. | Stop working on the request then; set a serve timeoutMs for requests that never finish. |
| The active server changes mid-request | The rest of the request goes to the new server, which never saw its head. | Answer such parts with an error response (an app convention), or let the client's timeout act. |
| The client gives up | Its cancel reaches the active server. | Stop working on the request. |
| A connection drops | Every unfinished request on it is lost. | Treat them as failed; on the new connection, serve again. |
Nothing is stored#
The Relay Broker keeps the same promise as the Wire Broker: no data table, no retention, no backups,
and none of the parts' contents in its logs. What Thuum sees is metadata: which key connected and
when, topics, request ids, sizes and timing. Encrypt data end to end if you like: Thuum never needs
to read it (HTTP over the Relay Broker does).
Try it#
The link below opens the Playground in a new tab, on the Relay Broker, in a sandbox of your own:
The page plays the client and the server on two connections. Open it in two windows and serve in both to see a standby take over.
Wire Broker or Relay Broker?#
The Wire Broker is for telling whoever is listening: readings, setpoints, signals, delivered to every subscriber, with no answer. The Relay Broker is for asking someone and waiting for the answer: one server, one response, to you alone. Neither stores anything, and many apps use both: the Wire Broker for a control loop, the Relay Broker for the requests of their agents. If others must read the result later, store it in the Event Broker or the State Broker.