GizStack
View .mdOpen in ChatGPTOpen in Claude

gizdb:relay

realtime as a position in a log the database keeps. Every committed change appends its topic and key (not its value: the store is the state, so a subscriber reads the rows a change names); a subscriber asks for a topic's changes after the position it holds and waits (a long poll) until there are some or its wait ends. Positions belong to the database, not to a process, so every worker process of the application (GIZLANG_WORKERS) and every later run answers the same position the same way. A position older than what the log keeps, or from another database, gets a snapshot answer: the caller renders the topic again. So a reconnect, a missed message, a restart and a request served by another worker are one path.

gizlang
import * as Relay from "gizdb:relay";

Types

Notice

gizlang
type Notice = { topic: string; key: string; gone: bool; }

A change to the row key of topic: written, or gone.

  • topic: string
  • key: string
  • gone: bool

Delta

gizlang
type Delta = | { kind: "changes"; changes: Vec<Notice>; position: string; } | { kind: "snapshot"; position: string; }

What a subscriber gets: the topic's changes and the position after them, or a snapshot to render at that position.

  • changes
  • snapshot

Relay

gizlang
type Relay

_store is held by reference: a feed travels in an app context (a record of fixed size).

Opaque: create and use it through this module's functions.

Functions

relay

gizlang
function relay(sys: System, store: Database, keep: i64): Result<Relay, StoreError>

The feed of store, keeping the last keep changes (its table is created when missing).

Effects: fail, read, write, console, io.

Methods

Relay.position

gizlang
function Relay.position(self: Relay): Result<string, StoreError>

The current position, for the first render of a topic.

Effects: fail, read, write, console, io.

Relay.transaction

gizlang
function Relay.transaction<F>(self: Relay, work: F): Result<Rows, StoreError>

Runs work (as Database.transaction does) and, once it committed, wakes this process's subscribers. Work writes its rows and publishes each change.

Relay.publish

gizlang
function Relay.publish(self: Relay, tx: Database, change: Notice): Result<bool, StoreError>

Inside Relay.transaction: append a change. Publishers take the feed's lock until they commit, so positions commit in order and a subscriber that read up to one never later finds an earlier one appear (PostgreSQL hands out identities to transactions that may commit in another order).

Effects: fail, read, write, console, io.

Relay.changed

gizlang
function Relay.changed(self: Relay, change: Notice, write: (tx: Database) => Result<Rows, StoreError>): Result<Rows, StoreError>

Runs write and publishes change in one Relay.transaction.

Effects: fail, read, write, console, io.

Relay.notify

gizlang
function Relay.notify(self: Relay, change: Notice): Result<Rows, StoreError>

Publishes change alone, in a Relay.transaction of its own.

Effects: fail, read, write, console, io.

Relay.since

gizlang
function Relay.since(self: Relay, topic: string, position: string, waitMs: i64): Result<Delta, StoreError>

The changes to topic after position, waiting up to waitMs for one.

Effects: fail, read, write, console, io.

Delta.position

gizlang
function Delta.position(self: Delta): string

The position a delta brings its subscriber to.