Problem
execute returns a full status-map with internal structures, errors, stats, etc. But 99% of the time users only need the resolved instruction:
;; Current — verbose
(:instruction (commando/execute reg instruction))
;; Every single call site has this pattern
The status-map is valuable as a container (errors, patching, debug) but the default UX forces everyone to unwrap it.
Solution: defrecord + IDeref
Make status-map a record type that implements IDeref. @ returns :instruction on success, throws ExceptionInfo (with full status-map as ex-data) on failure.
;; Happy path — clean, 99% of use cases
@(execute reg instruction)
;; => {"1" 1, "2" 1, "3" 1}
;; Explicit error handling — full control
(let [r (execute reg instruction)]
(if (ok? r)
@r
(handle-errors (:errors r))))
;; try/catch style
(try
@(execute reg instruction)
(catch #?(:clj ExceptionInfo :cljs ExceptionInfo) e
(let [{:keys [errors status]} (ex-data e)]
...)))
;; Patching — pass the container, deref the final result
(let [r (execute reg instruction)]
@(execute reg changed {:previous r}))
Implementation sketch
(defrecord StatusMap [status instruction errors warnings successes stats uuid]
#?(:clj clojure.lang.IDeref
:cljs IDeref)
(#?(:clj deref :cljs -deref) [this]
(if (= status :failed)
(throw (ex-info "Commando execution failed" (into {} this)))
instruction)))
Why defrecord works here
assoc/update/get/destructuring — all work, extra keys (:internal/*) stored in overflow map
- No
dissoc on status-map fields anywhere in the pipeline (verified)
- Record field access (
:status, :instruction) is direct Java/JS field lookup — faster than hash-map
- CLJS has
IDeref protocol — cross-platform
(= record plain-map) breaks, but tests compare individual keys, not entire status-maps
Problem
executereturns a full status-map with internal structures, errors, stats, etc. But 99% of the time users only need the resolved instruction:The status-map is valuable as a container (errors, patching, debug) but the default UX forces everyone to unwrap it.
Solution:
defrecord+IDerefMake status-map a record type that implements
IDeref.@returns:instructionon success, throwsExceptionInfo(with full status-map asex-data) on failure.Implementation sketch
Why defrecord works here
assoc/update/get/destructuring — all work, extra keys (:internal/*) stored in overflow mapdissocon status-map fields anywhere in the pipeline (verified):status,:instruction) is direct Java/JS field lookup — faster than hash-mapIDerefprotocol — cross-platform(= record plain-map)breaks, but tests compare individual keys, not entire status-maps