Open Time Store™
Copyright © JUXT LTD 2018-2019


This page documents various code patterns and helpful functions that have been suggested by users. A broad understanding of these patterns will be useful to guide iterations on the next generation of API layers for Crux. Some of these patterns may be appropriate candidates for evolving into easily consumable Decorators.


PRs are welcome, see Contributing for guidelines. If you would prefer not to sign up to our CLA just point us towards your GitHub gist and we can link to it.

Updating Documents

; A macro for doing CAS updates to multiple documents

(defmacro update!
 [[node-binding get-node
   & bindings]
  & body]
 (let [[entity-binding get-entities] (take-last 2 bindings)
       bindings (drop-last 2 bindings)]
   `(let [node# ~get-node
          ~node-binding node#]
      (loop [count# 0]
        (if (>= count# *max-update!-count*)
          (throw (ex-info "Exhausted attempts at CAS" {:count count#}))
          (let [~@bindings
                entities# ~get-entities
                ~entity-binding entities#
                txs# (mapv
                       (fn [old-ent# ent#]
                       (do ~@body))]
            (let [submitted-tx# (crux.api/submit-tx
              (if (txs-succeeded? node# txs# submitted-tx#)
                (recur (inc count#))))))))))

;; usage for a single entity

(let [eid #uuid "6f0232d0-f3f9-4020-a75f-17b067f41203"]
     [node (::standalone dev-extras/node)
      [entity] [(crux.api/entity (crux.api/db node) eid)]]
     [(update entity :magic-value inc)]))

;; usage for a set of entities returned by a query

  [node (::standalone dev-extras/node)
   db (crux.api/db node)
   q (crux.api/q
       '{:find [?e]
         :where [[?e :name _]]})
   entities (map #(crux.api/entity db %) (map first q))]

; Works like `let`
; This macro is not ideal as it requires you to return the list of entities in
; the same order that you query them in. It is still somewhat complex in that
; it requires the first binding to be the node, and the last binding to be
; your entities (and everything in the middle will be rerun on CAS failure).
; The middle is a good place for running `(crux/db)`.

; by @SevereOverfl0w
(defn entity-update
  [entity-id new-attrs valid-time]
  (let [entity-prev-value (crux/entity (crux/db node) entity-id)]
    (crux/submit-tx node
        (merge entity-prev-value new-attrs)

; by @spacegangster

Implicit Node

(defn q
  (crux/q (crux/db node) query))

(defn entity
  (crux/entity (crux/db node) entity-id))

; by @spacegangster


(defn lookup-vector
 [db eid]
 (if (vector? eid)
   (let [[index value] eid]
         (crux.api/q db
                     {:find ['?e]
                      :where [['?e index value]]}))))
   (crux.api/entity db eid)))

; by @SevereOverfl0w
(defn entity-at
  [entity-id valid-time]
  (crux/entity (crux/db node valid-time) entity-id))

(defn entity-with-adjacent
  [entity-id keys-to-pull]
  (let [db (crux/db node)
        (fn [ids]
          (cond-> (map #(crux/entity db %) ids)
            (set? ids) set
            (vector? ids) vec))]
      (fn [e adj-k]
        (let [v (get e adj-k)]
          (assoc e adj-k
                   (keyword? v) (crux/entity db v)
                   (or (set? v)
                       (vector? v)) (ids->entities v)
                   :else v))))
      (crux/entity db entity-id)

; by @spacegangster

Transaction Ops

; Use spec to validate your operations prior to submission

   (clojure.spec.alpha/or :put :crux.tx/put-op
                          :delete :crux.tx/delete-op
                          :cas :crux.tx/cas-op
                          :evict :crux.tx/evict-op)
    {:crux.db/id #uuid "6f0232d0-f3f9-4020-a75f-17b067f41203"
     :name "John Wayne"
     :username "jwa"}
    {:crux.db/id #uuid "6f0232d0-f3f9-4020-a75f-17b067f41203"
     :name "John Wayne"
     :username "jwa"
     :new-field 2}])

; by @SevereOverfl0w