Targets · JavaScript · Runtime
Runtime
The rendered JavaScript stays small because it links against a runtime. That runtime is three cooperating parts: the Reactive graph that tracks derived values and write-backs, the Context channels that carry emissions out to drivers, and the Native layer the drivers plug into. None of it is user-visible — a Program only ever names the small surface it needs.
Contents
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.
- Introduction — what the JavaScript target is, and how to read the rest of this section.
- Capabilities — which high-level features are possible, built in or through a library.
- Coding standards — the Frenzi coding standards the generated code follows, global and JavaScript-specific.
- Example — a full program using every keyword, and the JavaScript it renders to.
- Naming considerations — how Program's dotted names are carried into JavaScript.
- Entry point — the generated
main()that wires up the drivers and starts the reactive cycle. - Drivers — the native drivers that provide capabilities like the user interface and animation, draining each cycle's emissions.
- Runtime — the Reactive and Context machinery the rendered code links against.
- Native modules — the hand-written, per-platform JavaScript behind
NATIVEfunctions. - Keywords — how each of Program's keywords renders to JavaScript.
- Project setup — the files and commands to build and run the rendered program.
- Reserved words — the words JavaScript keeps for itself, and how a Program name that collides with one is escaped.
Three parts
The runtime is small and has three parts. Reactive holds state and derives values from it. Context carries what the program emits — the interface and other effects — out to the drivers. Native is the drivers and the hand-written pieces they are built from. Rendered code names only a thin surface of the first two; the rest is machinery.
Reactive
Reactive is the heart. A STATE is the one place a value really changes; everything else is derived from it and recomputed only when something it read has changed. Each call-site keeps a stable identity across cycles — state is tracked by position, which is why the rendered code must place its reactive calls in the same order every time (see coding standards).
var count = Reactive.State(0); // STATE
var text = Reactive.Calculate(() => MathsInteger.ToString(Reactive.Value(count))); // LET
// a write from the interface arrives as Reactive.Set(count, next)
Everything the rendered program names is a static function on the Reactive class; the
values that flow between them are instances of a handful of small classes. Both are inventoried
below.
A reactive value
A ReactiveValue is the one thing that flows through the whole system. It has two optional
halves — a gettable (its readable side) and a settable (its
writable side) — and which halves are present says what it is:
- gettable only — a read (a derived value, a constant);
- settable only — a write-only target (a REVERSE);
- both — a two-way binding.
The gettable carries the native value and, optionally, a lock. The
settable carries a stable id and, optionally, a reverse callback — if it has one, a
write runs that callback; if it does not, the write lands in a plain state slot.
ReactiveValue
├─ optionalGettable : ReactiveGettable // present ⇒ readable
│ ├─ value // the native payload
│ └─ optionalLock : ReactiveLock // present ⇒ LOADING
│ └─ optionalError : ReactiveError // present ⇒ ERROR
│ ├─ message
│ └─ optionalRetryFlagId // a settable id ⇒ RETRY
└─ optionalSettable : ReactiveSettable // present ⇒ writable
├─ id
└─ optionalReverseCallback // present ⇒ reverse; absent ⇒ state slot
Loading, error and retry
There is no separate “async” type. Loading and failure are just a lock on an ordinary value, and a lock travels with anything derived from it — so the interface shows a spinner or an error without any code special-casing it.
- Loading — the gettable has a lock with no error. This is the
LOCKED state; a
Reactive.State(initial, true)starts life locked, and a driver clears the lock when the real value arrives. - Error — the lock carries a
ReactiveErrorwith a message. A value can be both: locked and errored. - Retry — the error may carry an
optionalRetryFlagId, the id of a settable flag. Setting that flagtruere-runs the work that failed.
if (Reactive.IsLocked(response)) { /* loading or error */ }
if (Reactive.HasError(response)) {
var message = Reactive.OptionalErrorMessage(response); // the message
var retry = Reactive.OptionalRetryFlag(response); // the settable flag, or undefined
// wire a button to: Reactive.Set(retry, true)
}
The propagation is done once, in Reactive.Calculate: it remembers every value read
through Reactive.Value during its callback and, if any of them is locked or errored, its
own result is locked or errored too — with the inputs' retry flags combined into one, so a single
retry fans back out to every request that failed. Because construction and field access both go through
Calculate (see TYPE), a record
built from a still-loading field is itself loading, automatically. The
web client driver shows the whole cycle end to
end.
The Reactive class
These are the functions the rendered code (and the native drivers) call. Everything else in the module is machinery reached through them. A function is marked native (callable anywhere) or reactive (only valid inside a running cycle) where it matters.
Program and cycle
Program(rootCallback)— builds a program value from a root callback.Run(program, updates?)— runs one cycle: applies write-backs, then the forward body; returns the next program value.
State and values
State(initial, locked?)— a mutable value at this call-site;lockedstarts it in the loading state.Value(v)(native, Calculate-only) — the native value inside a reactive value, recording it as a dependency of the surroundingCalculate.Create(v, locked?, errorMessage?, retryFlagId?)— wraps a plain value as a reactive value (used to seed a locked placeholder).Set(target, value)— writes: runs the reverse callback if the target has one, otherwise updates its state slot.
Calculation and derived values
Calculate(callback)— runs a pure native callback and combines the locks, errors and retry flags of everything it read into the result.GetObjectField(object, get, change)— a reactive, locked-aware field accessor; read-only for a plain object, two-way for a settable one.Lock(value, locked)— conditionally puts a value in the loading state.WithLockFrom(value, source)— copies another value's lock onto this one (error beats loading).AddError(value, message, retryFlagId?)/AddErrorOptional(value, message?)— attach an error (unconditionally, or only when a message is present).
Reverse and binding
Reverse(callback)— a write-only value whose callback runs on SET.Binding(gettable, settable)— joins a read and a write into one two-way value.
Scope, conditionals and lists
Branches(callback)— the primitive for stable-identity sub-scopes.If(condition, onTrue)— a conditional branch.Each(array, itemCallback)— iterate a reactive array, one keyed branch per element.
Identity and references
StableId()/StableKey(prefix?)— a stable id (or string key) for the current call-site.Reference(settable)/Dereference(id)— turn a settable into a plain id and back, for passing writable values through channels that only carry data.
Loading / error / retry inspection
IsLocked(value)— loading or errored.HasError(value)— errored (not merely loading).OptionalErrorMessage(value)— the message, orundefined.HasRetryFlag(value)/OptionalRetryFlag(value)— whether the error carries a retry flag, and the settable flag itself.
A layer of _-prefixed helpers (lock/error collection, scope-item bookkeeping, update
routing) sits under these; being underscore-named, they are internal machinery, never part of the
surface a program touches.
Classes in the reactive module
The Reactive functions move instances of these small classes around. Most a program never
names directly — they are what a reactive value is made of.
ReactiveValue— the value that flows everywhere: an optional gettable and an optional settable.ReactiveGettable— the readable side: a nativevalueand an optional lock.ReactiveSettable— the writable side: a stableidand an optional reverse callback.ReactiveLock— marks a value locked; present means loading, and if it holds an error, errored.ReactiveError— amessageand an optional retry-flag id.ReactiveProgram— a running program: its forward process, its reverse processes, and the state memory carried between cycles.ReactiveProcess— one unit of execution (the forward body, or a reverse body) with its scope tree.ReactiveScopeItem— one node of the scope tree, carrying a call-site's stable identity and its named branches.ReactiveEvent— small composable reverse helpers (a no-op, a fixed setter, a listener) for side effects.ReactiveArray/ReactiveArrayElement— a keyed list and its elements, the input toEach.ReactiveSecurity— the validation guards (Gettable,Settable,Binding…) that every reactive function calls first.
Context
A program does not call the DOM, the network, or the animation clock. It emits into named channels, and a driver drains each channel. The interface is one channel — a nested tree, so components can contain components; most others are flat lists. Channels are cheap to snapshot, which is what lets a cycle be built up and then handed off whole.
One cycle
The entry point drives a loop. Each turn:
- open a channel for each active driver;
- run the reactive program — write-backs first, then the forward body that emits the interface and effects;
- drain the channels;
- hand each driver its emissions, which it reconciles against the real world;
- wait. When a driver calls
addUpdates, the loop turns again — and only then.
Nothing polls. A program at rest costs nothing; a cycle happens exactly when a value it depends on is written from outside.
Kept small
The runtime ships as one bundle the rendered program links against, so an embedded example is the program plus a fixed, shared core — not a framework per page. It is deliberately lean: the reactive value, the context channels, and the driver contract are the whole of it, with validation kept to development builds.