Composable state updater functions for LiveView assigns.
An updater is a pure function from one assigns-map to another, or a
function of arity 2 that takes an extra argument (an "event payload").
The point is to make state transitions composable and testable in
isolation — any LiveView handle_event can be reassembled from small
named updaters rather than inlining the logic.
When the composed updater is applied via apply/3 to a socket, the
LiveView calls assign/2 with the returned map, and LiveSvelte pushes
the resulting prop diff to the Svelte component.
Quick example
import Caravela.Live.Updater
mark_saving = fn assigns -> %{assigns | saving: true} end
clear_flash = fn assigns -> %{assigns | flash_message: nil} end
combined = mark_saving ~> clear_flash
combined.(%{saving: false, flash_message: "old"})
#=> %{saving: true, flash_message: nil}The ~> operator is sugar for compose/2 and can chain any number of
updaters into a single function.
Summary
Functions
Compose two updaters: apply a, then apply b to the result.
Narrow an updater so it operates on one nested key of the assigns map, leaving every other key untouched. Useful when a parent LiveView embeds child-domain state under a namespaced key.
Apply an updater to a LiveView socket, assigning the returned map.
Pipe operator for chaining updaters: u ~> v is equivalent to
compose(u, v). Left-associative, so a ~> b ~> c composes as
compose(compose(a, b), c).
Types
Functions
Compose two updaters: apply a, then apply b to the result.
If either side takes two arguments (an event payload), use apply/3
with the payload threaded explicitly. compose/2 itself only composes
the 1-arity shape — keep 2-arity updaters at the call boundary.
Narrow an updater so it operates on one nested key of the assigns map, leaving every other key untouched. Useful when a parent LiveView embeds child-domain state under a namespaced key.
increment = fn %{counter: n} = s -> %{s | counter: n + 1} end
scoped = Caravela.Live.Updater.embed(increment, :child_state)
scoped.(%{child_state: %{counter: 0}, other: :untouched})
#=> %{child_state: %{counter: 1}, other: :untouched}
Apply an updater to a LiveView socket, assigning the returned map.
Accepts both arities: run(socket, u) for (assigns -> assigns),
and run(socket, u, arg) for (assigns, arg -> assigns). The
result replaces socket.assigns, which means LiveSvelte re-renders
the Svelte component with the new props.
For real Phoenix.LiveView.Socket structs we route through
Phoenix.Component.assign/2 so LiveView change-tracking fires. For
plain-map sockets (used in tests / tooling) we replace :assigns
directly. That keeps Caravela.Live.Updater unit-testable without a
live LiveView process.
Pipe operator for chaining updaters: u ~> v is equivalent to
compose(u, v). Left-associative, so a ~> b ~> c composes as
compose(compose(a, b), c).
import Caravela.Live.Updater
pipeline = updater_a ~> updater_b ~> updater_c
pipeline.(assigns)