(ns pocketbook.store.idb "IndexedDB store implementing the PStore protocol." (:require [pocketbook.store :as store] [pocketbook.transit :as transit] [cljs.core.async :as async :refer [chan put!]])) ;; --------------------------------------------------------------------------- ;; IDB operations ;; --------------------------------------------------------------------------- (defn- tx "Start an IDB transaction. mode is :readonly or :readwrite." [db store-name mode] (let [mode-str (case mode :readonly "readonly" :readwrite "readwrite")] (.transaction db #js [store-name] mode-str))) ;; --------------------------------------------------------------------------- ;; IDBStore ;; --------------------------------------------------------------------------- (deftype IDBStore [db] store/PStore (put-doc! [_ doc] (let [ch (chan 1) txn (tx db "docs" :readwrite) store (.objectStore txn "docs") obj #js {:id (:id doc) :value (transit/encode (:value doc)) :version (:version doc 0) :updated (:updated doc 0) :deleted (boolean (:deleted doc false)) :synced (boolean (:synced doc false))} req (.put store obj)] (set! (.-onsuccess req) (fn [_] (put! ch true) (async/close! ch))) (set! (.-onerror req) (fn [e] (js/console.error "IDB put error:" e) (async/close! ch))) ch)) (docs-by-prefix [_ prefix] (let [ch (chan 1) txn (tx db "docs" :readonly) store (.objectStore txn "docs") range (.bound js/IDBKeyRange prefix (str prefix "\uffff")) req (.openCursor store range) docs (atom [])] (set! (.-onsuccess req) (fn [e] (let [cursor (.-result (.-target e))] (if cursor (let [val (.-value cursor)] (swap! docs conj {:id (.-id val) :value (transit/decode (.-value val)) :version (.-version val) :updated (.-updated val) :deleted (.-deleted val) :synced (.-synced val)}) (.continue cursor)) (do (put! ch @docs) (async/close! ch)))))) (set! (.-onerror req) (fn [e] (js/console.error "IDB cursor error:" e) (async/close! ch))) ch)) (get-meta [_ key] (let [ch (chan 1) txn (tx db "meta" :readonly) store (.objectStore txn "meta") req (.get store key)] (set! (.-onsuccess req) (fn [e] (let [result (.-result (.-target e))] (if result (do (put! ch (.-value result)) (async/close! ch)) (async/close! ch))))) (set! (.-onerror req) (fn [_] (async/close! ch))) ch)) (set-meta! [_ key value] (let [ch (chan 1) txn (tx db "meta" :readwrite) store (.objectStore txn "meta") req (.put store #js {:key key :value value})] (set! (.-onsuccess req) (fn [_] (put! ch true) (async/close! ch))) (set! (.-onerror req) (fn [_] (async/close! ch))) ch)) (close-store! [_] (when db (.close db)))) ;; --------------------------------------------------------------------------- ;; Open ;; --------------------------------------------------------------------------- (defn open "Open an IndexedDB store. Returns a channel yielding the IDBStore." [db-name] (let [ch (chan 1) req (.open js/indexedDB db-name 1)] (set! (.-onupgradeneeded req) (fn [e] (let [db (.-result (.-target e))] (when-not (.contains (.-objectStoreNames db) "docs") (let [store (.createObjectStore db "docs" #js {:keyPath "id"})] (.createIndex store "synced" "synced" #js {:unique false}) (.createIndex store "updated" "updated" #js {:unique false}))) (when-not (.contains (.-objectStoreNames db) "meta") (.createObjectStore db "meta" #js {:keyPath "key"}))))) (set! (.-onsuccess req) (fn [e] (put! ch (IDBStore. (.-result (.-target e)))) (async/close! ch))) (set! (.-onerror req) (fn [e] (js/console.error "IDB open error:" e) (async/close! ch))) ch))