Finish first working version w/ indexed db cache
This commit is contained in:
parent
8bb3a3b8d7
commit
2e48899420
4 changed files with 538 additions and 65 deletions
143
src/main/computersandblues/lodestone/database.cljs
Normal file
143
src/main/computersandblues/lodestone/database.cljs
Normal file
|
|
@ -0,0 +1,143 @@
|
|||
(ns computersandblues.lodestone.database
|
||||
(:require [clojure.string :as str]))
|
||||
|
||||
(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 cursor-value [cursor]
|
||||
(js->clj (some-> cursor .-value) :keywordize-keys true))
|
||||
|
||||
#_(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)))
|
||||
Loading…
Add table
Add a link
Reference in a new issue