elm-rad — reactive cell DSL.
Inspect a cell's persistence key. The key is the user-supplied string,
prefixed with any namespace segments introduced by withInstance. Useful for
Layer 7 persistence integration and testing.
Inspect a cell's runtime ID. Useful for low-level integration (Form membership, persistence keying). Most users won't need this.
Inspect a cell's initial value, already encoded via its codec. Used by
Rad.Form to capture pristine snapshot values without exposing the codec.
Inspect a cell's codec. For low-level integration; most users won't need it.
Internal helper for builder modules outside Rad.elm (Rad.Form's
withState). Constructs a Cell from an already-resolved id, key, codec,
and initial. Most users have no reason to call this — use with,
withDebounced, withValidated, or Form.withState instead.
A cell with settle semantics. Holds two observable values — raw (updates
on every keystroke or fromDebouncedInput) and settled (updates on commit,
revert, or after a timer). Constructed via withDebounced. Opaque.
Add a debounced cell to the builder. Allocates three Registry slots
(raw, settled, and a per-cell timer sequence counter) seeded from initial.
A Source for the raw value of a debounced cell — updates on every
fromDebouncedInput (or keystroke via a view binding) and via revert.
A Source for the settled value of a debounced cell — updates on
commit, revert, or after the debounce timer fires.
A derived Source Bool reporting whether a debounced cell's raw and
settled values are equal. Uses JSON-encoded equality via the cell's codec,
consistent with Layer 2 trigger-change detection.
Commit a debounced cell's raw value to its settled value (copies raw →
settled). A pure Action; does not touch the timer sequence, so any in-flight
timer will fire harmlessly (raw and settled already match).
Revert a debounced cell's raw value to its settled value (copies settled
→ raw). Dual of commit. Does not touch the timer sequence.
A cell with a reactive validation lifecycle. Holds a writable input value
and a derived Validation err a state. Constructed via withValidated.
Opaque.
Add a validated cell to the builder. Allocates three Registry slots
(input, validation state, activation sequence counter) seeded from initial
and Dormant.
Opaque validator. Build via sync, async, or compose.
A synchronous validator. Returns Ok a if valid; Err errs with a list
of errors otherwise.
An asynchronous validator. Builds a Request whose success value is the
valid a; failure is a list of errors. HTTP-backed validators typically
build via Rad.Http.httpGet + mapRequestError.
A composed validator. Runs the validators left-to-right; if any one
returns Invalid, subsequent validators are skipped. Valid propagates the
(possibly transformed) value to the next validator.
The writable input cell of a validated cell. Use with set, modify,
copy, or view bindings (bind (input vcell)).
A Source for the validation state of a validated cell.
Activate validation. Bumps the activation sequence counter; the
validation reaction re-fires with the current input, transitioning from
Dormant through Checking to Valid / Invalid. Repeated calls bump
the counter again, forcing re-validation even if the input is unchanged.
Reset validation to Dormant. Writes Dormant to the validation state
and resets the activation sequence to 0. Any in-flight async validator's
result is discarded by the validation reaction (it sees SkipRequest).
The validation lifecycle state.
A codec for Validation err a given codecs for the error and value types.
The wire format is a tagged object: {"tag":"Dormant"}, {"tag":"Checking"},
{"tag":"Valid","value":<valueEncoded>},
{"tag":"Invalid","errors":[<errEncoded>, ...]}.
Run the sync portions of a validator without dispatching any async work.
Returns Just settled if a final state is reachable (all Sync, with or
without short-circuiting Compose), or Nothing if an Async must run.
This is exposed primarily for tests and for a potential future sync fast-path in the runtime.
The reaction(s) powering a validated cell's lifecycle. Returns a single-element list so users can concatenate multiple validated cells' reactions with their own:
reactions =
\model _ ->
validationReactions model.name
++ validationReactions model.email
++ [ myOtherReaction ]Internal: produces the raw fields of a validated cell's reaction. Used by
Rad.Form to compose Form.reactions. Not part of the supported public API.
An applicative builder for constructing a model made of cells.
Start a CellBuilder from a model constructor.
Add one cell to the builder, consuming one argument of the constructor.
Extract the finished model and the initial registry.
A pair of encoder and decoder for serializing cell values.
A remote resource in one of four states.
A codec for Remote err a given codecs for the error and value types.
The wire format is a tagged object: {"tag":"Idle"}, {"tag":"Loading"},
{"tag":"Failed","value":<errEncoded>}, {"tag":"Done","value":<valueEncoded>}.
Anything readable. Cells, derived values, and later debounced/validated
accessors all convert to Source. The constructor is not re-exported from
Rad, keeping the type opaque to user code.
Read a source against a registry. Exposed for tests and for the runtime.
Turn a Read into a Source. The resulting source recomputes its value
from the registry on every read, and carries the supplied codec so the runtime
can encode derived values (for trigger-change detection).
A synchronous action against the cell registry. Phantom model parameter
reserves type-level differentiation for later layers.
Copy the current value of a source into a cell.
If the source and target have different codecs for the same value type, the target's codec is used for encoding.
Combine a sequence of actions, applied in list order.
Apply an action to a registry. Exposed for tests and the runtime.
The identity Action — does nothing to the registry. Useful as a default or a no-op placeholder.
Action that triggers an immediate persist save, bypassing the 500ms
auto-save debounce. Use as an onClick payload like any other Action.
button { label = "Save now", onClick = Rad.persistNow }A pending debounced timer becomes stale once persistNow saves — when the
timer eventually fires its counter check rejects it, so there's no
double-save. No-op when AppDef.persist is Nothing.
An asynchronous request produced by an effect library (e.g., Rad.Http).
Opaque; Layer 2 constructors are noRequest and values returned by
effect-library functions like Rad.Http.httpGet.
A Request that does nothing. Returned from the a -> Request err r
transform in a reaction to signal "don't dispatch for this trigger value."
Transform a Request's error type. Useful for mapping a library-defined
error (e.g., Rad.Http.RequestError) into a domain error type.
Transform a Request's success value via a function that may itself short-circuit to the error channel.
Useful for chaining a sync check after an async request. Example: an HTTP response whose body indicates whether the action succeeded at the domain level:
Http.httpGet handler url decoder
|> mapRequestError (\netErr -> [ NetworkError netErr ])
|> andThenRequest
(\resp ->
if resp.ok then
Ok resp.value
else
Err [ DomainFailure ]
)An application definition.
init — the cell builder; produces the initial model + registry.computed — derived values rebuilt on every render.view — turns the model + computed values into a view.reactions — async effects rebuilt on every render.persist — opt-in localStorage persistence; Nothing means no save/restore.A public alias for the runtime's internal model record. Used as the model
type of a Program so user code does not have to name Rad.Internal.Registry.
The dirtyCounter field is a monotonic counter bumped on every registry
mutation so the runtime's debounced-save flow can detect stale timers.
Configuration for opt-in localStorage persistence. Pass via
AppDef.persist. The save field is a user-supplied port.
port persistSave : ( String, String ) -> Cmd msg
persist =
Just { key = "myapp", version = 1, save = persistSave }Run an application. Wraps Browser.element so later layers can add
effects without changing the harness.
A reusable bundle of cells, computed values, a view, and reactions. Build
via defineComponent, mount via withInstance, render via embed, and
compose reactions via include. Opaque.
The model type parameter carries through to the Reaction model values the
component produces. At use sites, model unifies with the outer app's model
type.
Build a ComponentDef from its parts.
tagPicker : { endpoint : String } -> ComponentDef model (SimpleView model) TagPickerCells {}
tagPicker config =
defineComponent
{ init = build TagPickerCells |> with "query" "" stringCodec |> ...
, computed = \_ -> {}
, view = \c _ -> ...
, reactions = \c _ -> [ ... ]
}Recommended pattern: bind parameterized ComponentDef values at module level
and reference them by name to avoid reconstructing at every use site
(withInstance, embed, include).
Mount a component instance under the given namespace. Inside the
parent's init pipeline:
build Model
|> withInstance "primary" tagPicker
|> withInstance "secondary" tagPickerEach call extends the persistence-key prefix for the component's cells
("primary.selected", "secondary.selected") and advances the parent's ID
counter past the component's cells.
Works inside another component's init too — nested components compose the
prefix ("outer.inner.field").
Render a component instance's view. Reads the component's cells via
def.view cells (def.computed cells).
Collect a component instance's reactions. Returns the list produced by
def.reactions cells (def.computed cells). Concatenate with other reactions
in the parent's reactions function.
A reaction — a rule that says "when this source's value changes, run
this request and store the result in this cell." Constructed via on.
Opaque.
A reactive state cell holding a value of type
a.