bcardiff/elm-rad/1.0.0

Rad

elm-rad — reactive cell DSL.

type Cell a

A reactive state cell holding a value of type a.

cellKey : Cell a -> String

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.

cellId : Cell a -> Int

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.

cellCodec : Cell a -> Codec a

Inspect a cell's codec. For low-level integration; most users won't need it.

{ id : Int
, key : String
, codec : Codec a
, initial : a
}
-> Cell a

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.

type alias DebouncedCell a =
DebouncedCell a

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.

String
-> Float
-> a
-> Codec a
-> CellBuilder (DebouncedCell a -> rest)
-> CellBuilder rest

Add a debounced cell to the builder. Allocates three Registry slots (raw, settled, and a per-cell timer sequence counter) seeded from initial.

raw : DebouncedCell a -> Source a

A Source for the raw value of a debounced cell — updates on every fromDebouncedInput (or keystroke via a view binding) and via revert.

settled : DebouncedCell a -> Source a

A Source for the settled value of a debounced cell — updates on commit, revert, or after the debounce timer fires.

synced : DebouncedCell a -> Source Bool

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 : DebouncedCell a -> Action model

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 : DebouncedCell a -> Action model

Revert a debounced cell's raw value to its settled value (copies settled → raw). Dual of commit. Does not touch the timer sequence.

type alias ValidatedCell err a =
ValidatedCell err a

A cell with a reactive validation lifecycle. Holds a writable input value and a derived Validation err a state. Constructed via withValidated. Opaque.

String
-> a
-> Codec a
-> Codec err
-> Validator err a
-> CellBuilder (ValidatedCell err a -> rest)
-> CellBuilder rest

Add a validated cell to the builder. Allocates three Registry slots (input, validation state, activation sequence counter) seeded from initial and Dormant.

type alias Validator err a =
Validator err a

Opaque validator. Build via sync, async, or compose.

sync : (a -> Result (List err) a) -> Validator err a

A synchronous validator. Returns Ok a if valid; Err errs with a list of errors otherwise.

async : (a -> Request (List err) a) -> Validator err a

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.

compose : List (Validator err a) -> Validator err a

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.

input : ValidatedCell err a -> Cell a

The writable input cell of a validated cell. Use with set, modify, copy, or view bindings (bind (input vcell)).

validation : ValidatedCell err a -> Source (Validation err a)

A Source for the validation state of a validated cell.

validate : ValidatedCell err a -> Action model

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.

resetValidation : ValidatedCell err a -> Action model

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).

type Validation err a
= Dormant
| Checking
| Valid a
| Invalid (List err)

The validation lifecycle state.

validationCodec : Codec err -> Codec a -> Codec (Validation err a)

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>, ...]}.

runSyncOnly : Validator err a -> a -> Maybe (Validation err a)

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.

validationReactions : ValidatedCell err a -> List (Reaction model)

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 ]
internalValidationReactionGuts : ValidatedCell err a -> Guts

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.

type alias CellBuilder ctor =
CellBuilder ctor

An applicative builder for constructing a model made of cells.

build : ctor -> CellBuilder ctor

Start a CellBuilder from a model constructor.

String
-> a
-> Codec a
-> CellBuilder (Cell a -> rest)
-> CellBuilder rest

Add one cell to the builder, consuming one argument of the constructor.

runBuilder : CellBuilder model -> ( model, Registry )

Extract the finished model and the initial registry.

type alias Codec a =
{ encode : a -> Value
, decode : Decoder a
}

A pair of encoder and decoder for serializing cell values.

boolCodec : Codec Bool
floatCodec : Codec Float
intCodec : Codec Int
listCodec : Codec a -> Codec (List a)
maybeCodec : Codec a -> Codec (Maybe a)
stringCodec : Codec String
type Remote err a
= Idle
| Loading
| Failed err
| Done a

A remote resource in one of four states.

remoteCodec : Codec err -> Codec a -> Codec (Remote err a)

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>}.

type alias Source a =
Source a

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.

toSource : Cell a -> Source a

Convert a cell into a readable source.

readSource : Source a -> Registry -> a

Read a source against a registry. Exposed for tests and for the runtime.

derive : Codec a -> Read a -> Source a

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).

type alias Action model =
Action model

A synchronous action against the cell registry. Phantom model parameter reserves type-level differentiation for later layers.

set : Cell a -> a -> Action model

Set a cell to a given value.

modify : Cell a -> (a -> a) -> Action model

Apply a function to the current value of a cell.

copy : Source a -> Cell a -> Action model

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.

batch : List (Action model) -> Action model

Combine a sequence of actions, applied in list order.

applyAction : Action model -> Registry -> Registry

Apply an action to a registry. Exposed for tests and the runtime.

noAction : Action model

The identity Action — does nothing to the registry. Useful as a default or a no-op placeholder.

persistNow : Action model

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.

type alias Request err a =
Request err a

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.

noRequest : Request err a

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."

mapRequestError : (e -> f) -> Request e a -> Request f a

Transform a Request's error type. Useful for mapping a library-defined error (e.g., Rad.Http.RequestError) into a domain error type.

andThenRequest : (a -> Result err b) -> Request err a -> Request err b

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 ]
        )
type alias AppDef view model computed =
{ init : CellBuilder model
, computed : model -> computed
, view : model -> computed -> view
, reactions : model -> computed -> List (Reaction model)
, persist : Maybe (PersistConfig (Msg model))
}

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.
type alias AppModel model =
{ model : model
, registry : Registry
, reactions : ReactionState
, dirtyCounter : Int
}

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.

type alias PersistConfig msg =
{ key : String
, version : Int
, save : ( String, String ) -> Cmd msg
}

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 :
ViewEngine view model
-> AppDef view model computed
-> Program Value (AppModel model) (Msg model)

Run an application. Wraps Browser.element so later layers can add effects without changing the harness.

type ComponentDef model view cells computed

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.

{ init : CellBuilder cells
, computed : cells -> computed
, view : cells -> computed -> view
, reactions : cells -> computed -> List (Reaction model)
}
-> ComponentDef model view cells computed

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).

String
-> ComponentDef model view cells computed
-> CellBuilder (cells -> rest)
-> CellBuilder rest

Mount a component instance under the given namespace. Inside the parent's init pipeline:

build Model
    |> withInstance "primary" tagPicker
    |> withInstance "secondary" tagPicker

Each 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").

embed : ComponentDef model view cells computed -> cells -> view

Render a component instance's view. Reads the component's cells via def.view cells (def.computed cells).

ComponentDef model view cells computed
-> cells
-> List (Reaction model)

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.

type alias Reaction model =
Reaction model

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.

on :
Source a
-> (a -> Request err r)
-> Cell (Remote err r)
-> Reaction model

Build a reaction.

reactions =
    \_ _ ->
        [ on (toSource model.tick) (\_ -> Rad.Http.httpGet handler "/api/joke" jokeDecoder) model.joke
        ]