Liking cljdoc? Tell your friends :D

dev.zeko.stube.conversation

The conversation data model — pure helpers, no I/O.

A conversation is the entire server-side state of a single user's session against one mounted flow. It is a plain map; persistence, history, and concurrency are all handled by working with these values.


Shape

{:conv/id        "cv-019"
 :conv/instances {"ix-7e2" {…instance map…} …}
 :conv/stack     ["ix-7c1" "ix-7e2"]   ; bottom → top
 :conv/history   [previous-conv …]
 :conv/created   #inst "…"
 :conv/touched   #inst "…"}

An instance is the merged shape of an instantiated component:

{:instance/id        "ix-7e2"
 :instance/type      :auth/login
 :instance/parent    "ix-7c1" | nil
 :instance/resume    :on-login | nil
 :instance/rendered? false        ; toggled on first emitted patch
 …user state from (:component/init cdef)…}

The user-defined state lives at the top level of the instance map (not under a :state key). Handler functions therefore see one merged map and can both read instance metadata (:instance/id) and their own domain fields by simple keyword lookup. Handlers must not clobber the :instance/* keys.

The conversation data model — pure helpers, no I/O.

A *conversation* is the entire server-side state of a single user's
session against one mounted flow.  It is a plain map; persistence,
history, and concurrency are all handled by working with these values.

----------------------------------------------------------------------
Shape
----------------------------------------------------------------------

    {:conv/id        "cv-019"
     :conv/instances {"ix-7e2" {…instance map…} …}
     :conv/stack     ["ix-7c1" "ix-7e2"]   ; bottom → top
     :conv/history   [previous-conv …]
     :conv/created   #inst "…"
     :conv/touched   #inst "…"}

An *instance* is the merged shape of an instantiated component:

    {:instance/id        "ix-7e2"
     :instance/type      :auth/login
     :instance/parent    "ix-7c1" | nil
     :instance/resume    :on-login | nil
     :instance/rendered? false        ; toggled on first emitted patch
     …user state from (:component/init cdef)…}

The user-defined state lives at the top level of the instance map (not
under a `:state` key).  Handler functions therefore see one merged map
and can both read instance metadata (`:instance/id`) and their own
domain fields by simple keyword lookup.  Handlers must not clobber the
`:instance/*` keys.
raw docstring

child-slotclj

(child-slot parent child-iid)

Return the slot key in parent currently pointing at child-iid, or nil.

Return the slot key in `parent` currently pointing at `child-iid`, or nil.
sourceraw docstring

descendant-idsclj

(descendant-ids conv iid)

Return a vector of all instance ids transitively reachable from iid via :instance/children or :instance/keyed-slots, including iid itself, in pre-order.

Does not follow :instance/previous chains (the displaced slot occupants stashed by [:call-in-slot …] while their replacement is pending). Use this on the slot-answer path, where the previous gets restored, and for render-side walks like mark-rendered where previous-chained instances are not visible in the DOM. When the whole subtree is being destroyed (frame pop, :replace, :end, keyed removal), use subtree-ids instead.

Return a vector of all instance ids transitively reachable from `iid`
via `:instance/children` or `:instance/keyed-slots`, including `iid`
itself, in pre-order.

Does **not** follow `:instance/previous` chains (the displaced slot
occupants stashed by `[:call-in-slot …]` while their replacement is
pending).  Use this on the slot-answer path, where the previous gets
restored, and for render-side walks like [[mark-rendered]] where
previous-chained instances are not visible in the DOM.  When the
whole subtree is being destroyed (frame pop, `:replace`, `:end`,
keyed removal), use [[subtree-ids]] instead.
sourceraw docstring

embedclj

(embed type)
(embed type args)

Return an embed spec for component type initialised with args.

Return an embed spec for component `type` initialised with `args`.
sourceraw docstring

embed?clj

(embed? x)

True if x looks like an embed spec.

True if `x` looks like an embed spec.
sourceraw docstring

instanceclj

(instance conv iid)

The instance map for iid, or nil.

The instance map for `iid`, or nil.
sourceraw docstring

instance-meta-keysclj

Keys the kernel manages on every instance map. Handlers must treat these as read-only; the kernel ignores any user changes to them.

:instance/children is the slot→iid map populated when a component declares :children (lifted to :component/children) in its definition. See instantiate-tree.

:instance/slot and :instance/previous are used by the [:call-in-slot …] overlay primitive: the temporary child remembers which parent slot it occupies and which child iid should be restored when it answers.

:instance/keyed-slots is populated by the keyed-children primitive and is just as framework-owned as fixed :instance/children slots.

:stube/context is adapter-supplied request/application context. It is protected like instance metadata so handlers can read it via s/context without accidentally persisting edits to the context map.

Keys the kernel manages on every instance map.  Handlers must treat
these as read-only; the kernel ignores any user changes to them.

`:instance/children` is the slot→iid map populated when a component
declares `:children` (lifted to `:component/children`) in its
definition.  See [[instantiate-tree]].

`:instance/slot` and `:instance/previous` are used by the
`[:call-in-slot …]` overlay primitive: the temporary child remembers
which parent slot it occupies and which child iid should be restored
when it answers.

`:instance/keyed-slots` is populated by the keyed-children primitive
and is just as framework-owned as fixed `:instance/children` slots.

`:stube/context` is adapter-supplied request/application context.  It
is protected like instance metadata so handlers can read it via
`s/context` without accidentally persisting edits to the context map.
sourceraw docstring

instantiateclj

(instantiate cdef {:keys [embed/args]} parent-id resume-key)

Build a fresh instance map from a component definition and an embed spec. parent-id and resume-key may be nil for root instances.

This is the flat constructor: it does not look at :component/children. Use instantiate-tree when you want the kernel to materialise the whole subtree.

Build a fresh instance map from a component definition and an embed
spec.  `parent-id` and `resume-key` may be nil for root instances.

This is the *flat* constructor: it does not look at `:component/children`.
Use [[instantiate-tree]] when you want the kernel to materialise the
whole subtree.
sourceraw docstring

instantiate-treeclj

(instantiate-tree cdef embed-spec parent-id resume-key lookup-cdef)

Build a parent instance plus every child eagerly declared by its component definition's :component/children map (lifted from :children at registration time by registry/register!).

:component/children is either a map {slot-key embed-spec} or a function of the freshly-initialised parent state returning such a map. Slot keys are arbitrary keywords the parent can reference from its :render via (s/render-slot self slot-key).

lookup-cdef is a 1-arg function (component-id) → cdef-map. Pass dev.zeko.stube.registry/lookup! from the kernel; the indirection lets this namespace stay registry-agnostic and easy to test in isolation.

Returns [parent-inst descendants] where descendants is a flat seq of every transitively-instantiated child instance, ready to be merged into :conv/instances. The returned parent-inst carries :instance/children populated with {slot-key child-iid}.

Build a parent instance plus every child eagerly declared by its
component definition's `:component/children` map (lifted from
`:children` at registration time by `registry/register!`).

`:component/children` is either a map `{slot-key embed-spec}` or a function of
the freshly-initialised parent state returning such a map.  Slot keys
are arbitrary keywords the parent can reference from its `:render`
via `(s/render-slot self slot-key)`.

`lookup-cdef` is a 1-arg function `(component-id) → cdef-map`.  Pass
`dev.zeko.stube.registry/lookup!` from the kernel; the indirection lets this
namespace stay registry-agnostic and easy to test in isolation.

Returns `[parent-inst descendants]` where `descendants` is a
flat seq of every transitively-instantiated child instance, ready to
be merged into `:conv/instances`.  The returned `parent-inst` carries
`:instance/children` populated with `{slot-key child-iid}`.
sourceraw docstring

local-signalclj

(local-signal self signal)

Return the per-instance signal key for logical signal on self.

Datastar signals are page-global. Binding two embedded components to the same $answer would therefore make them share client-side state. A local signal keeps the user-facing logical name (:answer) while suffixing the actual wire key with the instance id:

(local-signal {:instance/id "ix-1"} :answer)
;; => :answer-ix-1

merge-kept-signals maps local signal keys back to their logical names when the component lists the logical key in :component/keep.

Return the per-instance signal key for logical `signal` on `self`.

Datastar signals are page-global.  Binding two embedded components to
the same `$answer` would therefore make them share client-side state.
A local signal keeps the user-facing logical name (`:answer`) while
suffixing the actual wire key with the instance id:

    (local-signal {:instance/id "ix-1"} :answer)
    ;; => :answer-ix-1

[[merge-kept-signals]] maps local signal keys back to their logical
names when the component lists the logical key in `:component/keep`.
sourceraw docstring

mark-renderedclj

(mark-rendered conv iid)

Set :instance/rendered? true on iid and every descendant the parent's render placed into the DOM. Called once the kernel has emitted a frame's first patch onto the wire.

Marking the whole subtree at once matches reality: Datastar morphs the parent's HTML into the page in one shot, so children that the parent inlined via s/render-slot are now in the DOM and any future render of that child can use the (cheaper) morph-by-id default instead of re-emitting the shell.

Set `:instance/rendered? true` on `iid` and *every* descendant the
parent's render placed into the DOM.  Called once the kernel has
emitted a frame's first patch onto the wire.

Marking the whole subtree at once matches reality: Datastar morphs
the parent's HTML into the page in one shot, so children that the
parent inlined via `s/render-slot` are now in the DOM and any
*future* render of that child can use the (cheaper) morph-by-id
default instead of re-emitting the shell.
sourceraw docstring

merge-kept-signalsclj

(merge-kept-signals inst signals keep-keys)

Lift the entries of signals whose keys appear in keep-keys onto the instance map. This is the per-event two-way binding step: the user types in the browser, Datastar updates the signal client-side, and on every event the relevant signals land back on self before the handler sees it.

If the browser sends a per-instance key produced by local-signal, that value is lifted onto the logical kept key. Local values win over same-named global values so a component can safely say :keep #{:answer} and render (s/local-bind self :answer).

Lift the entries of `signals` whose keys appear in `keep-keys` onto the
instance map.  This is the per-event two-way binding step: the user
types in the browser, Datastar updates the signal client-side, and on
every event the relevant signals land back on `self` before the handler
sees it.

If the browser sends a per-instance key produced by [[local-signal]],
that value is lifted onto the logical kept key.  Local values win over
same-named global values so a component can safely say `:keep #{:answer}`
and render `(s/local-bind self :answer)`.
sourceraw docstring

merged-selfclj

(merged-self conv iid signals)

Look up iid in conv, find its component definition, and return the instance with kept signals merged in. This is the value passed to :render and :handle.

Look up `iid` in `conv`, find its component definition, and return the
instance with kept signals merged in.  This is the value passed to
`:render` and `:handle`.
sourceraw docstring

new-cidclj

(new-cid)

Mint a fresh conversation id.

Mint a fresh conversation id.
sourceraw docstring

new-conversationclj

(new-conversation)

Build an empty conversation.

Build an empty conversation.
sourceraw docstring

new-instance-idclj

(new-instance-id)

Mint a fresh instance id.

Mint a fresh instance id.
sourceraw docstring

pop-topclj

(pop-top conv)

Pop the top frame and remove its entire subtree from the conversation — embedded descendants and every previous-chained slot occupant. Returns [conv' popped-id].

The wider sweep matters: a frame can have called-in-slot one or more times before being popped, leaving a previous-chain dangling off each new slot child. The narrow descendant-ids walk would miss those instances and leave them orphaned in :conv/instances.

Pop the top frame and remove its entire subtree from the
conversation — embedded descendants and every previous-chained slot
occupant.  Returns `[conv' popped-id]`.

The wider sweep matters: a frame can have called-in-slot one or
more times before being popped, leaving a previous-chain dangling
off each new slot child.  The narrow [[descendant-ids]] walk would
miss those instances and leave them orphaned in `:conv/instances`.
sourceraw docstring

preserve-metaclj

(preserve-meta old-instance new-state)

Merge new-state over old-instance while protecting the :instance/* keys. Used after a handler returns self' so a buggy handler can't break the instance metadata.

Merge `new-state` over `old-instance` while protecting the
`:instance/*` keys.  Used after a handler returns `self'` so a buggy
handler can't break the instance metadata.
sourceraw docstring

push-instanceclj

(push-instance conv inst)

Add inst to :conv/instances and push its id onto the stack.

Add `inst` to `:conv/instances` and push its id onto the stack.
sourceraw docstring

put-instanceclj

(put-instance conv inst)

Replace inst in the instances map without touching the stack.

Replace `inst` in the instances map without touching the stack.
sourceraw docstring

put-manyclj

(put-many conv instances)

Add a flat seq of instances to :conv/instances without touching the stack. Used by the kernel after instantiate-tree to deposit the eagerly-built children alongside their parent.

Add a flat seq of instances to `:conv/instances` without touching
the stack.  Used by the kernel after [[instantiate-tree]] to deposit
the eagerly-built children alongside their parent.
sourceraw docstring

remove-subtreeclj

(remove-subtree conv iid)

Remove iid and every embedded descendant from :conv/instances without touching the call stack. Mirrors descendant-ids — does not follow :instance/previous chains.

Right for the slot-answer path, where the previous occupant gets restored as the new slot value. Wrong for whole-subtree teardown; use remove-subtree+previous there.

Remove `iid` and every embedded descendant from `:conv/instances`
without touching the call stack.  Mirrors [[descendant-ids]] — does
*not* follow `:instance/previous` chains.

Right for the slot-answer path, where the previous occupant gets
restored as the new slot value.  Wrong for whole-subtree teardown;
use [[remove-subtree+previous]] there.
sourceraw docstring

remove-subtree+previousclj

(remove-subtree+previous conv iid)

Like remove-subtree but follows :instance/previous chains. Use this when the whole subtree is being destroyed (keyed-child removal, frame replace/end).

Like [[remove-subtree]] but follows `:instance/previous` chains.
Use this when the whole subtree is being destroyed (keyed-child
removal, frame replace/end).
sourceraw docstring

replay-eventclj

(replay-event conv event)

Normalise a replay event against conv: if event is a function, invoke it on the conversation; then default :instance-id to the top frame and :signals to {}. Used by core/replay and runtime/replay-with (and any future playback tool) so the event-shape rule lives in one place.

Normalise a replay event against `conv`: if `event` is a function,
invoke it on the conversation; then default `:instance-id` to the
top frame and `:signals` to `{}`.  Used by `core/replay` and
`runtime/replay-with` (and any future playback tool) so the
event-shape rule lives in one place.
sourceraw docstring

set-child-slotclj

(set-child-slot conv parent-id slot child-iid)

Point parent-id's slot at child-iid. If child-iid is nil, remove the slot.

Point `parent-id`'s `slot` at `child-iid`.  If `child-iid` is nil,
remove the slot.
sourceraw docstring

snapshotclj

(snapshot conv)

Append the current value of conv to its own :conv/history so the back button can rewind to it. Persistent maps make this essentially free in space. We strip the previous :conv/history from the snapshot so history doesn't grow quadratically.

Append the current value of `conv` to its own `:conv/history` so the
back button can rewind to it.  Persistent maps make this essentially
free in space.  We strip the previous `:conv/history` from the snapshot
so history doesn't grow quadratically.
sourceraw docstring

snapshot-for-dispatchclj

(snapshot-for-dispatch conv event-summary back?)

Apply the per-dispatch snapshot + touch + :conv/last-event update, with the [:back] carve-out: handlers that walk history backwards must NOT have their own pre-state pushed onto that history first. If it were, :back would just pop the snapshot we just took and "restore" the same state, leaving the user stuck. Every other dispatch behaves as before.

Apply the per-dispatch `snapshot` + `touch` + `:conv/last-event`
update, with the `[:back]` carve-out: handlers that walk history
backwards must NOT have their own pre-state pushed onto that
history first.  If it were, `:back` would just pop the snapshot we
just took and "restore" the same state, leaving the user stuck.
Every other dispatch behaves as before.
sourceraw docstring

subtree-idsclj

(subtree-ids conv iid)

Like descendant-ids but also follows each instance's :instance/previous chain.

[:call-in-slot …] stashes the displaced slot occupant on the new child's :instance/previous so the slot can revert on :answer. When the parent frame goes away before that answer arrives, those previous-chained instances have no live referent and would otherwise leak in :conv/instances. Frame-destruction paths use this wider walk so the chain is swept and each ancestor's :stop hook fires exactly once.

Visited iids are tracked in a set, but a previous-chain can only ever be a strict tree (each entry is set once at the moment of call-in-slot to an existing, distinct iid).

Like [[descendant-ids]] but also follows each instance's
`:instance/previous` chain.

`[:call-in-slot …]` stashes the displaced slot occupant on the new
child's `:instance/previous` so the slot can revert on `:answer`.
When the *parent* frame goes away before that answer arrives, those
previous-chained instances have no live referent and would otherwise
leak in `:conv/instances`.  Frame-destruction paths use this wider
walk so the chain is swept and each ancestor's `:stop` hook fires
exactly once.

Visited iids are tracked in a set, but a previous-chain can only
ever be a strict tree (each entry is set once at the moment of
call-in-slot to an existing, distinct iid).
sourceraw docstring

top-idclj

(top-id conv)

Id of the topmost frame on the stack, or nil if the stack is empty.

Id of the topmost frame on the stack, or nil if the stack is empty.
sourceraw docstring

top-instanceclj

(top-instance conv)

The instance map at the top of the stack, or nil if empty.

The instance map at the top of the stack, or nil if empty.
sourceraw docstring

touchclj

(touch conv)

Bump the :conv/touched timestamp.

Bump the `:conv/touched` timestamp.
sourceraw docstring

cljdoc builds & hosts documentation for Clojure/Script libraries

Keyboard shortcuts
Ctrl+kJump to recent docs
Move to previous article
Move to next article
Ctrl+/Jump to the search field
× close