Shared action execution runtime for both LiveView and Component.
This module contains the common logic for executing actions:
guards_pass?/3- Check if action guards are satisfiedapply_sets/4- Apply set operations to stateapply_runs/4- Apply run operations (fn assigns -> assigns end)apply_effects/3- Execute side effect functionscoerce_value/2- Coerce values to declared types
Runtime-specific operations (invoke, navigate, flash) remain in their respective runtime modules.
Summary
Functions
Execute side effect functions.
Apply pre-cascade run operations. Each body takes a socket and returns a socket; the body runs BEFORE the reactive cascade.
Apply post-cascade run operations. Each body takes a socket and returns a socket; the returned socket replaces the current one.
Apply set operations to state.
Build params map from action params spec and event params.
Coerce a value to the declared type of a state field.
Check if all guard conditions pass.
Functions
Execute side effect functions.
Each effect has a function that receives the current full state. Effects are executed for their side effects; the socket is returned unchanged.
Apply pre-cascade run operations. Each body takes a socket and returns a socket; the body runs BEFORE the reactive cascade.
The runtime sweeps socket.assigns.__changed__ after the body
returns and threads any not-yet-dirty fields through
LSocket.put_state/3 so the cascade sees them. This means
pre-cascade bodies can use either Lavash.Socket.put_state/3
(explicit) or Phoenix.Component.assign/3 (raw) and both end
up in lavash's dirty set.
Event params are merged into socket.assigns for the body's
duration so socket.assigns.body, .id, etc. resolve from
phx-value-* payloads — matches the contract apply_runs/5
uses for post-cascade bodies.
Apply post-cascade run operations. Each body takes a socket and returns a socket; the returned socket replaces the current one.
Runs AFTER the reactive cascade has settled. The body sees
consistent calc values and can do socket-level LV ops
(stream_insert/4, allow_upload/3,
consume_uploaded_entries/3, cancel_upload/3) that don't fit
the declarative state-mutation shape.
Writes the body makes to socket.assigns (via put_state,
assign/3, or LV ops) land for Phoenix's render diff but do
NOT trigger a re-cascade. Calcs depending on what run wrote
are stale until the next user event. If you need a derived
value of a write, use pre_run instead.
Apply set operations to state.
Each set has a field and a value. The value can be:
- A literal value
- An rx() struct (reactive expression with @field syntax)
- A function that receives
%{params: params, state: state}(legacy)
Values are coerced to the field's declared type.
Build params map from action params spec and event params.
Extracts named parameters from the event payload.
Coerce a value to the declared type of a state field.
Handles:
- nil state field (no coercion)
- nil values (pass through)
- Empty strings for non-string types (convert to nil)
- String values parsed via Type.parse/2
Check if all guard conditions pass.
Guards are atoms referencing derived boolean fields that must all be true.