Serving
A session can take charge of a prefix instead of publishing each path: whoever reads a path beneath it is answered by the session, which makes the broadcast for that path while it is read. Tablebox's programs work this way, and an app or backend can do the same to write its own program.
Serving a prefix
serve(prefix, handler) takes charge of every path at or beneath prefix, which the session's publish patterns must cover. The handler is called once for each path while it is read, with request.path, and answers before its promise settles:
request.accept(broadcast)with an official broadcast whose tracks it writes;request.refuse(name)with one of the codes below; the reader's tracks end with it.
A handler that throws, or settles without answering, refuses with failed. serve returns { close() }, which withdraws the prefix.
// Serves every path under clock/: clock/Asia/Tokyo gives a track, time, with the time there every second.
import { connect, moq } from 'tablebox.io';
import { relay } from './relay.ts';
import { sign } from './token.ts';
const connection = await connect({ ...relay, getToken: () => sign('clock', { publish: ['clock/**'] }) });
connection.serve('clock', request => {
const zone = request.path.slice('clock/'.length);
if (!Intl.supportedValuesOf('timeZone').includes(zone)) return request.refuse('bad-request');
const broadcast = new moq.Broadcast.Producer();
const time = broadcast.createTrack('time');
const timer = setInterval(() => time.writeString(new Date().toLocaleTimeString('en', { timeZone: zone })), 1_000);
request.accept(broadcast);
// Once nobody reads it, end it; the next read calls this again.
void (async () => {
while (!time.used.peek()) await time.used.changed();
await time.unused();
clearInterval(timer);
time.close();
broadcast.close();
})();
});Demand
A served path lasts while it is read. The handler learns that nobody reads a track any more from the track's official demand, track.used and track.unused(), and then closes the broadcast; the next read of the path calls the handler again.
Codes
Anything that serves paths, Tablebox's programs included, refuses a request or a track with:
| Code | Name | Meaning |
|---|---|---|
| 80 | bad-request |
The path or the input does not fit what is served there |
| 81 | missing-variable |
A project environment variable the request needs is missing |
| 82 | failed |
The server tried and could not (storage, provider, destination) |
| 83 | too-large |
The result would pass the server's size limit |
A reader gets the code and its name, not a message; a program that has more to say publishes it on a status track (Tablebox's programs). From Node, a refusal currently reaches the reader as an internal error with code 0, because @moq/web-transport 0.1.5 resets streams with 0; from browsers and Rust it carries its code.
Several servers
When several sessions serve one prefix, on one Relay or several, each read goes to one of them. When the serving one leaves, its reads end and a new read goes to another. Tablebox's programs number their groups by the time they start, in milliseconds since the Unix epoch, so that a reader that reads again gets groups that follow those it had.
Rust
serve(prefix, handler) calls the handler in a task of its own for each path read; request.accept(&broadcast) or request.refuse(Reason::BadRequest) answers, and a request dropped unanswered refuses with failed. The handler gives tracks as they are asked for through the official broadcast.dynamic(), and learns that the path is no longer read from broadcast.demand().unused(). Serving lasts as long as the returned Serving.