Tablebox DocsSite 日本語

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:

A handler that throws, or settles without answering, refuses with failed. serve returns { close() }, which withdraws the prefix.

TypeScript, Node · serve.ts
// 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.