This commit is contained in:
arne 2025-11-18 14:08:24 +01:00
commit eb2b10571d
4 changed files with 44 additions and 44 deletions

View file

@ -0,0 +1,139 @@
(ns computersandblues.lodestone.database)
(defonce +db+ (atom nil))
(defn setup! [namespace db-version migrations]
(assert (some? (migrations db-version)) "Will not increase db version as no migration is found")
(let [request (js/indexedDB.open (str namespace) db-version)]
(js/Promise.
(fn [resolve reject]
(doto request
(.addEventListener "success"
(fn [ev]
(let [db (.-result request)]
; see https://javascript.info/indexeddb#parallel-update-problem
(.addEventListener db "versionchange"
(fn []
(.. request -result close)
(js/alert "Database is outdated! Please reload the browser window.")))
(reset! +db+ db)
(resolve @+db+ ev))))
(.addEventListener "upgradeneeded"
(fn [ev]
(let [db (.-result request)
old-version (.-oldVersion ev)]
(js/console.log ::upgradeneeded ev db)
(doseq [version (range (inc old-version) (inc db-version))
:let [migration (migrations version)]]
(migration db)))))
; we don't add a listener for "blocked" events because we handle "versionchange" above
(.addEventListener "error" (fn [ev] (reject (.-result request) ev))))))))
(defn open-store
([db store-id]
(open-store db store-id "readonly"))
([db store-id permissions]
(let [store-id (str store-id) ; simplifies using keywords as store identifiers
txn (.transaction db store-id permissions)]
(.objectStore txn store-id))))
(defn create-object-store! [db store-id key-opts]
(.createObjectStore db (str store-id) (clj->js key-opts)))
(defn- promisify [request]
(js/Promise. (fn [resolve reject]
(doto request
(.addEventListener "success" (fn [] (resolve (.-result request))))
(.addEventListener "error" (fn [] (reject (.-error request))))))))
(defn add! [store-id object]
(let [store (open-store @+db+ store-id "readwrite")
request (.add store (clj->js object))]
(promisify request)))
(defn put! [store-id object]
(let [store (open-store @+db+ store-id "readwrite")
request (.put store (clj->js object))]
(promisify request)))
(defn get! [store-id k]
(let [store (open-store @+db+ store-id)
request (.get store k)]
(promisify request)))
(defn get-all! [store-id key-range]
(let [store (open-store @+db+ store-id)
request (.getAll store key-range)]
(promisify request)))
(defn open-cursor!
([store-id key-range] (open-cursor! store-id key-range "next"))
([store-id key-range direction]
(let [store (open-store @+db+ store-id)]
(.openCursor store key-range direction))))
#_(defn logging [f]
(let [n (volatile! 0)]
(fn [& args]
(when (< @n 10)
(vswap! n inc)
(js/console.log :logging args)
(apply f args)))))
(defn transduce-cursor
"Allows to transduce over all values in a cursor.
Takes a transducer `xform`, a reducing function `rf` and an initial `init`.
If no `init` is given, it will default to `(rf)`. If no `rf` is given, the
resulting value will be a persistent vector containing the result of all steps."
([cursor-req xform]
; optimization: work with a transient vector before returning the final result
(-> (transduce-cursor cursor-req xform conj! (transient []))
(.then persistent!)))
([cursor-req xform rf]
(transduce-cursor cursor-req xform rf (rf)))
([cursor-req xform rf init]
(let [result (volatile! init)
xform (xform rf)]
(js/Promise.
(fn [resolve _]
(.addEventListener cursor-req "success"
(fn [ev]
(if-let [cursor (-> ev .-target .-result)]
; NOTE: each step will work with the raw js value
; to avoid unnecessary conversion costs.
(let [step (xform @result (.-value cursor))]
(if (reduced? step)
(do
(vreset! result @step)
(resolve @result))
(do
(vreset! result step)
(.continue cursor))))
(resolve @result)))))))))
(def all (js/IDBKeyRange.lowerBound ""))
(comment
(let [re (js/RegExp. "user" "i")]
(do (print "starting…" (js/Date.))
(-> (open-cursor! ::posts all)
(transduce-cursor (comp (filter #(re-find re (.-content %)))
(take 50)
(map #(js->clj % :keywordize-keys true))))
(.then (fn [result]
(print "done!" (js/Date.))
(js/console.log (first result)))))))
)
(defn delete! [store-id k]
(let [store (open-store @+db+ store-id)
request (.delete store k)]
(promisify request)))
(defn count! [store-id]
(let [store (open-store @+db+ store-id)
request (.count store)]
(promisify request)))