Keyboard shortcuts

Press ← or → to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

Persist and update a document

You have a nested config map. You want to change one field, persist it, and run the same domain against a file or against HTTP. Dacite’s move: get-in / assoc-in / native on values, a v/root for the snapshot, store wiring in a separate section.

This example is the first claim-proving app in the roadmap: the server publishes a root hash; clients pull only what they need.

You will:

  1. Seed and edit config on disk.
  2. Point the same commands at clojure -M:service.
  3. Watch a second process pick up a new root.

Prerequisites

  • A clone of this repo (see Install)
  • JDK 17+ and the Clojure CLI for the service and --url client
  • Optional: Node.js 18+ (npm run config) or babashka (bb config) for the file store

The shape

Config is a Dacite map:

{"theme" "dark", "timeout" 30, "features" ["a" "b"]}

Domain code lives in dacite.examples.config and uses only dacite.value — get-in / assoc-in / native / v/root. It never calls dacite.convert. Store wiring (file path vs HTTP URL) is a separate section of the same namespace.

Local file

From the repo root:

cd impl/clojure
clojure -M:config -- --reset show

Or from the repo root with nbb / babashka (file store only):

npm run config -- --reset show
bb config --reset show

Expected shape:

seeded new store at target/dacite-config
theme:    dark
timeout:  30
features: a, b
root:     <64 hex chars>

Edit without leaving the value API:

clojure -M:config -- set timeout 60
clojure -M:config -- set theme light
clojure -M:config -- add-feature telemetry
clojure -M:config -- get features.0

--path DIR selects the store directory (content shards + a ROOT file). A second invocation without --reset reopens the same root.

Same commands against the service

Terminal 1 — start the HTTP content store:

cd impl/clojure
clojure -M:service --port 8080 --store mem

Terminal 2 — the same CLI, --url instead of --path:

cd impl/clojure
clojure -M:config -- --url http://127.0.0.1:8080 show
clojure -M:config -- --url http://127.0.0.1:8080 set timeout 90

s/remote implements the same root protocol as a local rooted store, so v/root, v/swap!, and v/cas! work unchanged. The app seeds with compare-and-set from nil.

A second process sees the new root

Terminal 3:

cd impl/clojure
clojure -M:config -- --url http://127.0.0.1:8080 watch

Back in terminal 2:

clojure -M:config -- --url http://127.0.0.1:8080 set theme solarized

watch polls GET /root twice a second and reprints when the hash moves. (Push watches come later, when a two-client live app needs them.)

The two clients share one content-addressed tree. After an edit they print the same root hash. That is the claim this app is here to prove.

Open local vs open remote

(require '[dacite.store :as store]
         '[dacite.value :as v]
         '[dacite.examples.config :as cfg])

;; local
(def r (v/root (cfg/open-file "target/dacite-config")))

;; remote (JVM)
(def r (v/root (store/remote-rooted-store "http://127.0.0.1:8080")))

(cfg/load-or-seed! r)
(v/swap! r cfg/set-path ["timeout"] 60)
(cfg/timeout (v/deref r))
;; => 60

One domain namespace. Two store wirings.

What this pulled from the library

WhyUtility
Read theme / timeout without a 20-line realize helperv/native
Cap how much string is realizedoptional limit or v/*string-char-limit*
Print a long string without dumping itv/pr-str → "prefix…" (n chars)
Nested features.0v/get-in, v/assoc-in, v/update
Same v/root on HTTPs/remote

Field access uses native. Debug printing uses v/pr-str.

Next

History is free — keep the previous document value; restore by hash.