Targets · TypeScript · Drivers

Drivers

The native drivers that provide the interface, animation and network.

Contents

How a Program becomes TypeScript, in parts. Start with the introduction, then follow each part into the rendered output and the runtime it links against.

Defining a driver

A driver is a small typed object with a fixed shape — the channel it drains, whether that channel is a flat list or the nested tree, a setup hook, and a reconcile step:

interface Driver<E> {
  channel: string;
  kind: 'array' | 'tree';
  init(addUpdates: (updates: Updates) => void): void;
  dispatch(emissions: E[]): void;
}

Here is a console driver that satisfies it — it drains the Console.Lines channel and prints each line that has changed since the last cycle.

function ConsoleDriver(): Driver<Line> {
  const lastById: Record<number, string> = {};
  return {
    channel: 'Console.Lines',
    kind: 'array',
    init(addUpdates) {},
    dispatch(emissions: Line[]) {
      for (const line of emissions) {
        if (lastById[line.id] !== line.text) {
          console.log(line.text);
          lastById[line.id] = line.text;
        }
      }
    },
  };
}

Registering it, and how the runtime runs it

A program lists the drivers it uses; the runtime opens a channel for each and never hardcodes one. Each cycle it opens the channel, runs the reactive program (which emits into it), drains it, and hands the typed list to dispatch.

Program.Application(() => Main(), [ ConsoleDriver() ]);

// inside the runtime, each cycle:
Context.openArray('Console.Lines');
program = Reactive.Run(program, updates);
driver.dispatch(Context.drainArray('Console.Lines'));

A driver that produces events — a timer, a network reply, an animation frame — calls the addUpdates it was given in init, which schedules the next cycle.

Choosing drivers for an embed

An embed lists exactly the drivers it needs.

Program.Application(() => Dashboard(), [
  Ui.Dom.Driver('app'),            // the interface
  Network.Web.Driver(),            // http requests
  Animate.Frame.Driver(),          // a shared animation ticker
]);

Three shapes of driver

Custom interface components

The interface driver can be handed custom components a module provides — a map, a chart, a payment field. A custom component gives a name and a typed render that returns an HTMLElement.

const MapView: UiComponent = {
  name: 'Map.View',
  render(attributes: Attributes, renderChildren: RenderChildren, addUpdates: AddUpdates): HTMLElement {
    const el = document.createElement('div');
    // … create the map inside el …
    return el;
  },
};

Program.Application(() => App(), [
  Ui.Dom.Driver('app', { components: [MapView] }),
]);

Built-in and custom components register the same way, so program code cannot tell them apart. When something happens outside — a click, a response, a frame — the driver calls addUpdates, which schedules the next cycle: one direction out, one direction back.