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 atom in dev.zeko.stube.server is the only copy of
the truth and save! is a no-op.
────────────────────────────────────────────────────────────────────── Cloroutine and persistence ──────────────────────────────────────────────────────────────────────
dev.zeko.stube.flow continuations are stateful objects, not EDN values. A
conversation that contains a live defflow instance therefore can't
go through the EDN file store as-is; the store will log a warning
and skip that conversation. Hand-rolled task components from slice
0 (the :start + resume-key pattern) ARE EDN-clean and persist
perfectly. Closing this gap is open work for a later slice.
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 atom in `dev.zeko.stube.server` is the only copy of
the truth and `save!` is a no-op.
──────────────────────────────────────────────────────────────────────
Cloroutine and persistence
──────────────────────────────────────────────────────────────────────
`dev.zeko.stube.flow` continuations are stateful objects, not EDN values. A
conversation that contains a live `defflow` instance therefore can't
go through the EDN file store as-is; the store will log a warning
and skip that conversation. Hand-rolled task components from slice
0 (the `:start` + resume-key pattern) ARE EDN-clean and persist
perfectly. Closing this gap is open work for a later slice.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 (the
most common cause is 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.
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 (the
most common cause is 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.(in-memory-store)The default store. Keeps no copy of its own — dev.zeko.stube.server's
in-process 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 — `dev.zeko.stube.server`'s in-process 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 |