The pure runtime: step, run-effects, dispatch.
Reading guide ─────────────
Everything in this namespace operates on plain values. A handler
returns [self' effects]; run-effects folds those effects into the
conversation, producing the next conversation value and the list of
fragments that must be pushed to the browser. A fragment is just a
small map describing one Datastar event — see [[fragment-shape]] for
the schema.
No I/O, no atoms, no SSE — that all lives one layer up in
dev.zeko.stube.http / dev.zeko.stube.server. This means the entire interaction
loop is testable from a REPL with dispatch and a hand-built
conversation value, with no server running.
Effect vocabulary ─────────────────
[:call <embed> :resume <k>] push a child frame; on `:answer` the
parent's `:k` function is invoked
[:call-in-slot <slot> <embed> :resume <k>]
temporarily swap one embedded slot;
the child answers back to the parent
without taking over the page
[:answer <value>] pop this frame; deliver `value` to
the parent under its resume key
[:replace <embed>] pop this frame and push another in
its place (Seaside `become:`)
[:patch <hiccup>] extra DOM patch (no stack change)
[:patch-signals <map>] push a Datastar signal patch
[:execute-script <js>] run literal JS in the browser
[:io <fn>] call `(fn)` off-thread (fire-and-forget)
[:after ms event] schedule a future event for this instance
[:subscribe topic event] subscribe this instance to published messages
[:unsubscribe topic?] remove this instance's topic subscription(s)
[:back] restore the previous conversation
from `:conv/history` (slice 3)
[:end <value>] terminate the conversation
All effects produce zero or more fragments and an updated conversation.
Component lifecycle keys
────────────────────────
Beyond the keys read directly by :render / :handle, a component
may include:
:start (fn [self] [self' effects])
:stop (fn [self] [self' effects])
:wakeup (fn [self] [self' effects])
:start runs once immediately after instantiation for both stack
frames and embedded children, which lets "task" components launch
their first :call without a synthetic user event and lets widgets
subscribe or schedule timers. :stop runs just before a frame/subtree
is removed; :wakeup runs when a persisted or history-restored frame
becomes live again.
The pure runtime: `step`, `run-effects`, `dispatch`.
Reading guide
─────────────
Everything in this namespace operates on plain values. A handler
returns `[self' effects]`; `run-effects` folds those effects into the
conversation, producing the next conversation value and the list of
*fragments* that must be pushed to the browser. A fragment is just a
small map describing one Datastar event — see [[fragment-shape]] for
the schema.
No I/O, no atoms, no SSE — that all lives one layer up in
[[dev.zeko.stube.http]] / [[dev.zeko.stube.server]]. This means the entire interaction
loop is testable from a REPL with `dispatch` and a hand-built
conversation value, with no server running.
Effect vocabulary
─────────────────
[:call <embed> :resume <k>] push a child frame; on `:answer` the
parent's `:k` function is invoked
[:call-in-slot <slot> <embed> :resume <k>]
temporarily swap one embedded slot;
the child answers back to the parent
without taking over the page
[:answer <value>] pop this frame; deliver `value` to
the parent under its resume key
[:replace <embed>] pop this frame and push another in
its place (Seaside `become:`)
[:patch <hiccup>] extra DOM patch (no stack change)
[:patch-signals <map>] push a Datastar signal patch
[:execute-script <js>] run literal JS in the browser
[:io <fn>] call `(fn)` off-thread (fire-and-forget)
[:after ms event] schedule a future event for this instance
[:subscribe topic event] subscribe this instance to published messages
[:unsubscribe topic?] remove this instance's topic subscription(s)
[:back] restore the previous conversation
from `:conv/history` (slice 3)
[:end <value>] terminate the conversation
All effects produce zero or more fragments and an updated conversation.
Component lifecycle keys
────────────────────────
Beyond the keys read directly by `:render` / `:handle`, a component
may include:
:start (fn [self] [self' effects])
:stop (fn [self] [self' effects])
:wakeup (fn [self] [self' effects])
`:start` runs once immediately after instantiation for both stack
frames and embedded children, which lets "task" components launch
their first `:call` without a synthetic user event and lets widgets
subscribe or schedule timers. `:stop` runs just before a frame/subtree
is removed; `:wakeup` runs when a persisted or history-restored frame
becomes live again.Optional side-effect hook bound by the server while folding effects. The kernel stays ignorant of threads and SSE; it only describes the delayed event that should be sent later.
Optional side-effect hook bound by the server while folding effects. The kernel stays ignorant of threads and SSE; it only describes the delayed event that should be sent later.
Optional side-effect hook bound by the server for [:subscribe …].
Optional side-effect hook bound by the server for `[:subscribe …]`.
Optional side-effect hook bound by the server for [:unsubscribe …].
Optional side-effect hook bound by the server for `[:unsubscribe …]`.
(boot flow-id)Mint the initial set of effects for a freshly minted conversation
whose root flow is flow-id. Pulled out so the http layer can ask
for them on first SSE connect.
Mint the initial set of effects for a freshly minted conversation whose root flow is `flow-id`. Pulled out so the http layer can ask for them on first SSE connect.
(dispatch conv {:keys [instance-id event payload signals] :as ev})Apply one client event to a conversation. event is the map
{:instance-id "ix-7e2"
:event :submit ; or any keyword the component knows
:payload any-edn-value ; optional, from (s/on ... :as [:event v])
:signals {:answer "42"}}
Returns [conv' fragments]. Pure: no I/O, no globals.
Stale events — those whose instance-id no longer exists in the
conversation — are dropped silently. This matters for buttons that
are still in the DOM after their owning frame has been popped (e.g.
the user double-clicks an OK button: the first click :answers and
removes the instance; the second click arrives with an iid that's
already gone). Throwing here would surface as a 500 in the http
layer for what is, semantically, a no-op.
Apply one client event to a conversation. `event` is the map
{:instance-id "ix-7e2"
:event :submit ; or any keyword the component knows
:payload any-edn-value ; optional, from (s/on ... :as [:event v])
:signals {:answer "42"}}
Returns `[conv' fragments]`. Pure: no I/O, no globals.
Stale events — those whose `instance-id` no longer exists in the
conversation — are dropped silently. This matters for buttons that
are still in the DOM after their owning frame has been popped (e.g.
the user double-clicks an OK button: the first click `:answer`s and
removes the instance; the second click arrives with an iid that's
already gone). Throwing here would surface as a 500 in the http
layer for what is, semantically, a no-op.(redraw-top conv)Re-render the current top frame in-place without running :wakeup
or snapshotting. Returns [conv' [frag]]. Used by dev-tooling
paths (e.g. enabling halos on a live conversation) that want a fresh
frame against the current state.
Re-render the current top frame in-place without running `:wakeup` or snapshotting. Returns `[conv' [frag]]`. Used by dev-tooling paths (e.g. enabling halos on a live conversation) that want a fresh frame against the *current* state.
(resume-top conv)Run :wakeup for the current top frame and render it as a restored
frame. Used by the http layer when a persisted conversation reattaches.
Run `:wakeup` for the current top frame and render it as a restored frame. Used by the http layer when a persisted conversation reattaches.
(run-effects conv effects)Apply a sequence of effects to conv left to right, collecting
fragments. Returns [conv' fragments].
Apply a sequence of effects to `conv` left to right, collecting fragments. Returns `[conv' fragments]`.
cljdoc builds & hosts documentation for Clojure/Script libraries
| Ctrl+k | Jump to recent docs |
| ← | Move to previous article |
| → | Move to next article |
| Ctrl+/ | Jump to the search field |