Drivers | JavaScript | Program targets

Targets · JavaScript · Drivers

Drivers are the bridge between a Program's reactive core and the outside world. Each cycle the program emits descriptors into named context channels; a driver drains its channel and reconciles the real resources it owns — DOM nodes, animation frames, network requests — creating, updating and destroying them to match. Events flow back the same way, re-entering the reactive cycle.

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

A driver is a small object with a fixed shape. It names the channel it drains, says whether that channel is a flat list or the nested interface tree, sets itself up once, and then reconciles real resources against the emissions of each cycle. Here is a console driver — it drains the Console.Lines channel and prints each line that has changed since the last cycle.

A program lists the drivers it uses; the runtime opens a channel for each and never hardcodes one.

Each cycle the runtime opens the driver's channel, runs the reactive program (which emits into it), drains it, and hands the list to dispatch :

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. That is the whole loop: emissions out through dispatch , events back through addUpdates .

The console driver is the whole contract in miniature. The web client driver is that same contract doing real work — and it is worth following closely, because loading , errors and retries all come from it. It drains the NetworkWeb.RequestContext array channel, and is registered like any other driver:

Each cycle the program emits one descriptor per request it wants in flight. The driver keeps its live requests keyed by a settable id (the response cell to write back to) and a signature (url, method, headers, body, and a reload counter), and compares the new emissions to what it is running:

The response lives in a LOCKED state ( Reactive.State(zero, true) ), so it starts in the loading state. While a request is in flight the cell stays locked; the program reads it and shows a spinner with no special-casing. When a reused request's parameters change, the driver re-locks by writing a fresh loading value back:

A lock with no error is the loading state. (A polling request that already has a result skips the re-lock, so it updates in place instead of flashing a spinner.)

When the response settles, the driver writes it back. A success — or a 4xx — is an ordinary unlocked value. A server error (5xx) or a failed fetch is written back locked with an error , and the error carries the id of a retry flag :

The program reads it with Reactive.HasError and Reactive.OptionalErrorMessage (see runtime ), and because the error rides on the value, anything derived from it is errored too.

The retry flag is a real settable. The program pulls it off the error and sets it true — typically from a “try again” button:

Setting it runs the request's reverse body, which bumps a reload counter. That counter is part of the request's signature — so on the next cycle the descriptor looks changed , the driver cancels and re-fetches, and the loading/error path runs again. Retry is not a special case; it is just a write that makes the request look new.

Notice what the driver never does: touch the program directly. Every result — loading, success, error — is delivered by calling addUpdates(updates) , a map from settable id to a new value. That schedules the next cycle, and Reactive.Run applies each update: an id that is a plain state cell is replaced with the new value; an id that carries a reverse callback runs that callback. This is the one path by which anything outside the program — a reply, a click, a frame — gets back in.

An embed lists exactly the drivers it needs. A static counter needs only the interface; a page with live data adds the network driver; an animated one adds the frame ticker. Nothing else is created or run.

Every driver is one of three shapes:

The interface driver can be handed custom components a module provides — a map, a chart, a payment field — anything that needs bespoke native code. A custom component gives a name and a render function; wherever a program uses that name, the driver calls the function to build, and later update, the real element.

The driver keeps a registry of these by name. A built-in component (text, button, layout) is just one that ships with the driver; a custom one is registered the same way, so program code cannot tell them apart. renderChildren lets a custom component host ordinary interface inside itself, and addUpdates lets it feed events back.

A driver never writes to the program directly. When something happens outside — a click, a response, a frame — the driver calls addUpdates , which schedules the next cycle with the new values. The reactive body runs again, the interface settles, and the drivers reconcile once more. One direction out, one direction back — see the runtime .

Recommended articles