Liking cljdoc? Tell your friends :D

dev.zeko.stube.flow

Slice-1: linear flow components via the defflow macro.

We deliberately shadow clojure.core/await (the agent-blocking primitive nobody reaches for in 2026) with our own coroutine-suspending await. The :refer-clojure :exclude keeps the compiler quiet.

A flow is a component whose role is to sequence a series of child components and return a final value. Written by hand, a flow looks like a state machine: a :start hook plus a chain of :on-step-N resume keys that thread the partial result forward. That is correct but tedious; the same logic reads naturally as straight-line code:

(s/defflow :booking/wizard []
  (let [dates   (s/await (s/embed :booking/dates))
        room    (s/await (s/embed :booking/room  {:dates dates}))
        receipt (s/await (s/embed :booking/pay   {:price (price-for room)}))]
    {:dates dates :room room :receipt receipt}))

The defflow macro compiles that body into a regular component registration. Under the hood we use cloroutine: the body becomes a stackless coroutine whose suspend point is await, and the coroutine continuation lives on the instance map as ::coro. Each user event delivered by the kernel is funnelled into one fixed resume key (:on-flow-resume) that injects the child's answer back into the coroutine and steps it forward.

The result is a component whose externally observable behaviour is identical to a hand-rolled chain of :on-step-N callbacks (modulo the resume key's name; see §13 of v2_1.md), but whose source reads as ordinary Clojure.

────────────────────────────────────────────────────────────────────── Restrictions inherited from cloroutine ──────────────────────────────────────────────────────────────────────

  • await cannot appear inside a nested fn, lazy seq, custom type method, or anywhere else the surrounding form might escape the coroutine's synchronous context. let/do/if/cond/when/ loop+recur are all fine.
  • try/catch across an await is not supported in slice 1 (open question, see v2_1.md §13).
  • Storing the coroutine on the instance map gives up strict EDN serialisability for flow instances; persistence (slice 3) will treat them as a separate concern.
Slice-1: linear flow components via the [[defflow]] macro.

We deliberately shadow `clojure.core/await` (the agent-blocking
primitive nobody reaches for in 2026) with our own coroutine-suspending
`await`.  The `:refer-clojure :exclude` keeps the compiler quiet.

A *flow* is a component whose role is to sequence a series of child
components and return a final value.  Written by hand, a flow looks
like a state machine: a `:start` hook plus a chain of `:on-step-N`
resume keys that thread the partial result forward.  That is correct
but tedious; the same logic reads naturally as straight-line code:

    (s/defflow :booking/wizard []
      (let [dates   (s/await (s/embed :booking/dates))
            room    (s/await (s/embed :booking/room  {:dates dates}))
            receipt (s/await (s/embed :booking/pay   {:price (price-for room)}))]
        {:dates dates :room room :receipt receipt}))

The `defflow` macro compiles that body into a regular component
registration.  Under the hood we use [cloroutine](https://github.com/leonoel/cloroutine):
the body becomes a stackless coroutine whose suspend point is
[[await]], and the coroutine continuation lives on the instance map as
`::coro`.  Each user event delivered by the kernel is funnelled into
one fixed resume key (`:on-flow-resume`) that injects the child's
answer back into the coroutine and steps it forward.

The result is a component whose externally observable behaviour is
identical to a hand-rolled chain of `:on-step-N` callbacks (modulo the
resume key's name; see §13 of `v2_1.md`), but whose source reads as
ordinary Clojure.

──────────────────────────────────────────────────────────────────────
Restrictions inherited from cloroutine
──────────────────────────────────────────────────────────────────────

* `await` cannot appear inside a nested `fn`, lazy seq, custom type
  method, or anywhere else the surrounding form might escape the
  coroutine's synchronous context.  `let`/`do`/`if`/`cond`/`when`/
  `loop`+`recur` are all fine.
* `try`/`catch` *across* an `await` is not supported in slice 1 (open
  question, see `v2_1.md` §13).
* Storing the coroutine on the instance map gives up strict EDN
  serialisability for flow instances; persistence (slice 3) will
  treat them as a separate concern.
raw docstring

-advanceclj

(-advance self answer)

Run self's coroutine once, injecting answer (or nil for the first step), and return [self effects] for the kernel.

Public-by-namespace-convention only — the macro emits calls to it. Application code should not call this directly.

Run `self`'s coroutine once, injecting `answer` (or `nil` for the
first step), and return `[self effects]` for the kernel.

Public-by-namespace-convention only — the macro emits calls to it.
Application code should not call this directly.
sourceraw docstring

awaitclj

(await embed-spec)

Inside a defflow body, suspend the surrounding flow until the embedded child produced by embed-spec answers, then resume with the child's answer value.

Calling await outside a defflow body has no useful meaning; it exists primarily as the cloroutine break-point marker.

Inside a [[defflow]] body, suspend the surrounding flow until the
embedded child produced by `embed-spec` answers, then resume with the
child's answer value.

Calling `await` outside a `defflow` body has no useful meaning; it
exists primarily as the cloroutine break-point marker.
sourceraw docstring

defflowcljmacro

(defflow id bindings & body)

Define and register a flow component.

(s/defflow :booking/wizard [{:keys [user-id]}]
  (let [dates (s/await (s/embed :booking/dates {:user user-id}))
        room  (s/await (s/embed :booking/room  {:dates dates}))]
    {:dates dates :room room}))
  • id — a namespaced keyword (same rule as defcomponent).
  • bindings — a destructuring vector applied to the embed args map passed at instantiation. [] is fine if the flow takes no args.
  • body — ordinary Clojure that may call await zero or more times. The body's final expression is the value the flow answers to its parent (or, for a root flow, the value of :end).

Restrictions are documented at the top of dev.zeko.stube.flow.

Define and register a flow component.

    (s/defflow :booking/wizard [{:keys [user-id]}]
      (let [dates (s/await (s/embed :booking/dates {:user user-id}))
            room  (s/await (s/embed :booking/room  {:dates dates}))]
        {:dates dates :room room}))

* `id` — a namespaced keyword (same rule as `defcomponent`).
* `bindings` — a destructuring vector applied to the embed args map
  passed at instantiation.  `[]` is fine if the flow takes no args.
* `body` — ordinary Clojure that may call [[await]] zero or more
  times.  The body's final expression is the value the flow answers
  to its parent (or, for a root flow, the value of `:end`).

Restrictions are documented at the top of [[dev.zeko.stube.flow]].
sourceraw docstring

done-tagclj

source

flow-cdefclj

(flow-cdef id init-fn)

Build the component map a defflow registers. Pulled out of the macro so the structure is easy to read and easy to test. Public so macro expansions in user namespaces can call it.

Build the component map a `defflow` registers.  Pulled out of the
macro so the structure is easy to read and easy to test.  Public so
macro expansions in user namespaces can call it.
sourceraw docstring

on-resumeclj

(on-resume)

Cloroutine resume hook: returns the value that the suspended await call should evaluate to inside the body. Always invoked synchronously by (coro) from inside step!, so *answer* is in scope.

Public so the defflow macro can name it from the user's namespace; not intended for application code.

Cloroutine resume hook: returns the value that the suspended `await`
call should evaluate to inside the body.  Always invoked synchronously
by `(coro)` from inside [[step!]], so `*answer*` is in scope.

Public so the `defflow` macro can name it from the user's namespace;
not intended for application code.
sourceraw docstring

resume-keyclj

The single resume key under which every flow's child answers are delivered. Exposed so tests and tooling can refer to it without hardcoding the keyword.

The single resume key under which every flow's child answers are
delivered.  Exposed so tests and tooling can refer to it without
hardcoding the keyword.
sourceraw docstring

step!clj

(step! coro answer)

Advance coro (a cloroutine continuation) by one fragment.

answer is the value the previously suspended await call should evaluate to; pass nil for the very first step (which has no suspended await to resume).

Returns one of:

[:yield <embed-spec>]   the body suspended at `(await embed-spec)`
[:done  <value>]        the body ran to completion with `value`
Advance `coro` (a cloroutine continuation) by one fragment.

`answer` is the value the previously suspended `await` call should
evaluate to; pass `nil` for the very first step (which has no
suspended await to resume).

Returns one of:

    [:yield <embed-spec>]   the body suspended at `(await embed-spec)`
    [:done  <value>]        the body ran to completion with `value`
sourceraw docstring

yield-tagclj

source

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