Pluggable persistence for conversation values.
A conversation is just a map of plain Clojure data (see
dev.zeko.stube.conversation). Slice 3 adds a small protocol for swapping
out the storage backend without touching the kernel:
(s/start! {:port 8080
:store (dev.zeko.stube.store/file-store "/var/lib/stube/convs")})
Three operations are enough:
| op | when |
|---|---|
load-all | once at startup, to repopulate memory |
save! | after every successful swap-conv! |
delete! | when a conversation ends (:end, reaper) |
The default is in-memory-store, which keeps the slice-0 behaviour
unchanged: the in-process conversation atom on the active
dev.zeko.stube.runtime kernel is the only copy of the truth and
save! is a no-op.
dev.zeko.stube.flow continuations are live cloroutine objects, not
EDN values. A conversation that contains a defflow instance is
therefore not durable: on a clean restart its on-disk copy is gone
(the file-store logs a warning and skips the save).
This is a deliberate property of the framework, not a gap. defflow
is the ergonomic for transient flows — wizards a user completes in
one sitting, multi-step UIs whose value comes from the linear-code
shape. If the flow needs to survive a deploy or a process crash,
write it as a hand-rolled task component instead: a :start hook
plus named resume keys threads the same state through an EDN-clean
map, and persists transparently through this store. See the
Durable flows: defflow vs. task components section of the tutorial
for a side-by-side example.
The kernel does not refuse to register or run a defflow-containing
conversation against a file-store; the live behaviour is normal.
Only the durable copy is skipped, and the warning fires so you can
feel the boundary.
Pluggable persistence for conversation values.
A conversation is just a map of plain Clojure data (see
[[dev.zeko.stube.conversation]]). Slice 3 adds a small protocol for swapping
out the storage backend without touching the kernel:
(s/start! {:port 8080
:store (dev.zeko.stube.store/file-store "/var/lib/stube/convs")})
Three operations are enough:
| op | when |
|--------------|---------------------------------------------|
| `load-all` | once at startup, to repopulate memory |
| `save!` | after every successful `swap-conv!` |
| `delete!` | when a conversation ends (`:end`, reaper) |
The default is [[in-memory-store]], which keeps the slice-0 behaviour
unchanged: the in-process conversation atom on the active
`dev.zeko.stube.runtime` kernel is the only copy of the truth and
`save!` is a no-op.
----------------------------------------------------------------------
defflow is in-memory only — by design
----------------------------------------------------------------------
`dev.zeko.stube.flow` continuations are live cloroutine objects, not
EDN values. A conversation that contains a `defflow` instance is
therefore not durable: on a clean restart its on-disk copy is gone
(the [[file-store]] logs a warning and skips the save).
This is a deliberate property of the framework, not a gap. `defflow`
is the ergonomic for transient flows — wizards a user completes in
one sitting, multi-step UIs whose value comes from the linear-code
shape. If the flow needs to survive a deploy or a process crash,
write it as a hand-rolled task component instead: a `:start` hook
plus named resume keys threads the same state through an EDN-clean
map, and persists transparently through this store. See the
*Durable flows: defflow vs. task components* section of the tutorial
for a side-by-side example.
The kernel does not refuse to register or run a `defflow`-containing
conversation against a [[file-store]]; the live behaviour is normal.
Only the durable copy is skipped, and the warning fires so you can
feel the boundary.Backend for persisting conversation values. See namespace docstring.
Backend for persisting conversation values. See namespace docstring.
(delete! this cid)Remove the persisted conversation with id cid. Idempotent.
Remove the persisted conversation with id `cid`. Idempotent.
(load-all this)Return a map {cid → conv} of every persisted conversation.
Called once at server startup before the http listener accepts
requests.
Return a map `{cid → conv}` of every persisted conversation.
Called once at server startup before the http listener accepts
requests.(save! this conv)Atomically replace the persisted value for (:conv/id conv).
Returns conv on success. May log + return the conv unchanged
on a non-fatal serialisation problem; should throw only on a
backend-level failure (disk full, etc.).
Atomically replace the persisted value for `(:conv/id conv)`. Returns `conv` on success. May log + return the conv unchanged on a non-fatal serialisation problem; should throw only on a backend-level failure (disk full, etc.).
(file-store dir)Persist every conversation as one EDN file in dir. The directory
is created if it does not exist.
(file-store "./conv-store")
Files are named <cid>.edn. Writes go to a sibling temp file and
are renamed atomically, so a crash mid-write never leaves a partial
read on disk. Reads use clojure.edn/read-string with no eval and
the default tagged-literal handler, so a corrupted file is the worst
attacker reach.
If a conversation contains values that are not EDN-printable (almost
always a dev.zeko.stube.flow cloroutine continuation), the store
logs a warning to *err* and skips the save without raising. The
conversation stays live in memory; only its on-disk copy is stale.
This is the documented defflow boundary — see the namespace
docstring for how to write a durable equivalent.
Persist every conversation as one EDN file in `dir`. The directory
is created if it does not exist.
(file-store "./conv-store")
Files are named `<cid>.edn`. Writes go to a sibling temp file and
are renamed atomically, so a crash mid-write never leaves a partial
read on disk. Reads use `clojure.edn/read-string` with no eval and
the default tagged-literal handler, so a corrupted file is the worst
attacker reach.
If a conversation contains values that are not EDN-printable (almost
always a `dev.zeko.stube.flow` cloroutine continuation), the store
logs a warning to `*err*` and *skips* the save without raising. The
conversation stays live in memory; only its on-disk copy is stale.
This is the documented `defflow` boundary — see the namespace
docstring for how to write a durable equivalent.(in-memory-store)The default store. Keeps no copy of its own — the runtime kernel's in-process conversation atom IS the source of truth — and treats every operation as a no-op. Use this for tests, REPL iteration, and any deployment where you genuinely don't need crash-resume.
The default store. Keeps no copy of its own — the runtime kernel's in-process conversation atom IS the source of truth — and treats every operation as a no-op. Use this for tests, REPL iteration, and any deployment where you genuinely don't need crash-resume.
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 |