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.
- Introduction — what the TypeScript target is, and how to read the rest of this section.
- Capabilities — which high-level features are possible, built in or through a library.
- Example — a full program using every keyword, and the TypeScript it renders to.
- Naming considerations — how Program's dotted names are carried into TypeScript.
- Entry point — starting a program and attaching it to a root element.
- Drivers — the native drivers that provide the interface, animation and network.
- Runtime — the Reactive and Context machinery the rendered code links against.
- Native modules — the hand-written JavaScript behind
NATIVEfunctions. - Keywords — how each of Program's keywords renders to TypeScript.
- Project setup — the files and commands to build and run the rendered program.
- Reserved words — the words TypeScript keeps for itself, and how a colliding name is escaped.
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
- Tree renderer — the DOM interface driver, reconciling the nested tree by stable id.
- Request set — network requests and socket channels, each emission an ongoing process.
- Shared resource — one
requestAnimationFrameticker for all live animations.
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.