├── .github └── workflows │ └── ci.yml ├── .gitignore ├── LICENSE ├── README.md ├── build.clj ├── deps.edn ├── dev └── cljs │ └── user.cljs ├── index.html ├── indexeddb.gif ├── src └── indexed │ ├── db.cljs │ └── db │ ├── impl │ ├── cursor.cljs │ ├── database.cljs │ ├── events.cljs │ ├── factory.cljs │ ├── key_range.cljs │ ├── protocols.cljs │ ├── request.cljs │ ├── store.cljs │ └── transaction.cljs │ └── spec.cljs └── test └── indexed └── db ├── cursor_test.cljs ├── database_test.cljs ├── factory_test.cljs ├── key_range_test.cljs ├── store_test.cljs ├── test_util.cljs └── transaction_test.cljs /.github/workflows/ci.yml: -------------------------------------------------------------------------------- 1 | name: ci 2 | 3 | on: [pull_request] 4 | 5 | jobs: 6 | lint: 7 | runs-on: ubuntu-latest 8 | steps: 9 | - uses: actions/checkout@v2 10 | - uses: actions/setup-java@v2 11 | with: 12 | distribution: 'adopt' 13 | java-version: '8' 14 | - uses: DeLaGuardo/setup-clojure@master 15 | with: 16 | tools-deps: "1.10.3.1053" 17 | - name: Check format 18 | run: | 19 | clojure -M:format check src 20 | clojure -M:format check test 21 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .calva/ 2 | .clj-kondo/ 3 | .cpcache/ 4 | .lsp/ 5 | .nrepl-port 6 | target/ 7 | out/ 8 | public/ 9 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2022 Brian Scaturro 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 |

2 |
3 | 4 |

5 | 6 | # indexed.db 7 | 8 | It's just [IndexedDB](https://developer.mozilla.org/en-US/docs/Web/API/IndexedDB_API) for ClojureScript. No assumptions made. No clever macros. Just plain IndexedDB via ClojureScript. 9 | 10 | 11 | [![cljdoc badge](https://cljdoc.org/badge/com.github.brianium/indexed.db)](https://cljdoc.org/d/com.github.brianium/indexed.db/CURRENT) [![Clojars Project](https://img.shields.io/clojars/v/com.github.brianium/indexed.db.svg)](https://clojars.org/com.github.brianium/indexed.db) 12 | 13 | 14 | ## Table of contents 15 | 16 | - [Goals](#goals) 17 | - [Example](#example) 18 | - [Implementation](#implementation) 19 | 20 | ### Goals 21 | 22 | This library aims to be a straightforward ClojureScript wrapper around the browser's [IndexedDB](https://developer.mozilla.org/en-US/docs/Web/API/IndexedDB_API) API. By straightforward, I mean a literal port providing the same exact functionality as the JS API. 23 | 24 | 25 | The aim is to cover the entire API and allow ClojureScript programmers to program with little to no interop code. It is meant to provide a more pleasant experience than using the JS API. This library would make a great backbone for something more opinionated. 26 | 27 | This library provides: 28 | * A full [test suite](https://brianium.github.io/indexed.db/) 29 | * A function for every piece of functionality offered by IndexedDB 30 | * Name parity - i.e `window.indexedDB.open` becomes `(db/open)` and `transaction.objectStore` becomes `(db/object-store transaction)` 31 | * Specs! 32 | * Documentation! 33 | 34 | #### Non-goals 35 | 36 | The following are not goals of this library: 37 | 38 | * Fancy macros to make you feel like you aren't using IndexedDB 39 | * Schema management/migrations 40 | * High level functions for querying or inserting 41 | * Use [core.async](https://github.com/clojure/core.async) or promises 42 | * Manage events (this library makes no assumptions about what should be in an IDBRequest result) 43 | 44 | There is nothing wrong with these goals. It should be relatively simple to build any of these on top of this library. Please do so :) 45 | 46 | ### Example 47 | 48 | The [tests](test/indexed/db) will be the best place to see complete examples, but here is a sample of opening a database connection: 49 | 50 | ```clojure 51 | (require '[indexed.db :as db]) 52 | 53 | (-> (db/open "mydb" 1) 54 | (db/on "error" handle-error) 55 | (db/on "blocked" handle-blocked) 56 | (db/on "upgradeneeded" handle-upgrade) 57 | (db/on "success" handle-success)) 58 | ``` 59 | 60 | As mentioned in the [non-goals](#non-goals) section, this library does not make assumptions about event payloads, but it does 61 | provide factory functions for turning those payloads into something more useful for a Clojure developer. Here is how the `handle-upgrade` function in the above example might look: 62 | 63 | ```clojure 64 | (defn handle-upgrade 65 | [e] 66 | (-> (db/create-version-change-event e) 67 | (db/get-request) 68 | (db/result) 69 | (db/create-database) 70 | (db/create-object-store "toDoList" {:key-path "taskTitle"}) 71 | (db/create-index "hours" "hours" {:unique? false}))) 72 | ``` 73 | 74 | ### Implementation 75 | 76 | Every native JS type has been backed by a [ClojureScript protocol](src/indexed/db/impl/protocols.cljs). These protocols can be reified by `create-x` style factory functions: 77 | 78 | - `create-object-store` 79 | - `create-database` 80 | - `create-cursor` 81 | - `create-request` 82 | - `create-version-change-event` 83 | - etc 84 | 85 | There is a simple API over the browser's [EventTarget](https://developer.mozilla.org/en-US/docs/Web/API/EventTarget) interface in the form of `on` and `off` functions. 86 | 87 | #### Property/method collisions 88 | 89 | Many types in the IndexedDB API provide access to parent objects via property accessors. That is an `IDBRequest` object has a `transaction` property that references it's parent transaction. The `IDBDatabase` type also provides a `transaction` method for creating a new transaction. 90 | 91 | A design goal of this library is to export a single `indexed.db` namespace for use. To avoid these collisions, property access of the kind mentioned above is handled by converting the `transaction` property accessor function to `get-transaction`. If you are trying to get a parent transaction, request, store, etc... try the `get-x` variety first. 92 | -------------------------------------------------------------------------------- /build.clj: -------------------------------------------------------------------------------- 1 | (ns build 2 | (:require [clojure.tools.build.api :as b])) 3 | 4 | (def lib 'com.github.brianium/indexed.db) 5 | 6 | (def class-dir "target/classes") 7 | 8 | (def basis (b/create-basis {:project "deps.edn"})) 9 | 10 | (def version (:version basis)) 11 | 12 | (def jar-file (format "target/%s.jar" (name lib))) 13 | 14 | (defn clean [_] 15 | (doseq [path ["target" "public" "out"]] 16 | (b/delete {:path path}))) 17 | 18 | (defn jar [_] 19 | (b/write-pom {:class-dir class-dir 20 | :lib lib 21 | :version version 22 | :basis basis 23 | :src-dirs ["src"] 24 | :scm {:url "https://github.com/brianium/indexed.db" 25 | :connection "scm:git:git://github.com/brianium/indexed.db.git" 26 | :developerConnection "scm:git:git://github.com/brianium/indexed.db.git" 27 | :tag "HEAD"}}) 28 | (b/copy-dir {:src-dirs ["src"] 29 | :target-dir class-dir}) 30 | (b/jar {:class-dir class-dir 31 | :jar-file jar-file})) 32 | 33 | (defn out [_] 34 | (let [cmds (b/java-command 35 | {:basis (b/create-basis {:aliases [:dev] :project "deps.edn"}) 36 | :main 'clojure.main 37 | :main-args ["-m" "cljs.main" "--optimizations" "none" "-co" "{:closure-defines {cljs-test-display.core/notifications false}}" "-c" "cljs.user"]})] 38 | (b/process cmds))) 39 | 40 | (defn demo [_] 41 | (out _) 42 | (b/copy-dir {:src-dirs ["out"] 43 | :target-dir "public/out"}) 44 | (b/copy-file {:src "index.html" 45 | :target "public/index.html"})) 46 | -------------------------------------------------------------------------------- /deps.edn: -------------------------------------------------------------------------------- 1 | {:paths ["src"] 2 | :version "1.0.4" 3 | :deps {org.clojure/clojurescript {:mvn/version "1.11.4"}} 4 | :aliases {:build {:deps {io.github.clojure/tools.build {:git/tag "v0.7.2" 5 | :git/sha "0361dde"}} 6 | :ns-default build} 7 | 8 | :deploy {:extra-deps {slipset/deps-deploy {:mvn/version "0.2.0"}} 9 | :exec-fn deps-deploy.deps-deploy/deploy 10 | :exec-args {:installer :remote 11 | :artifact "target/indexed.db.jar" 12 | :pom-file "target/classes/META-INF/maven/com.github.brianium/indexed.db/pom.xml"}} 13 | 14 | :dev {:extra-paths ["dev" "test"] 15 | :extra-deps {org.clojure/test.check {:mvn/version "1.1.1"} 16 | com.bhauman/cljs-test-display {:mvn/version "0.1.1"}}} 17 | 18 | :format {:extra-deps {cljfmt/cljfmt {:mvn/version "0.8.0"}} 19 | :main-opts ["-m" "cljfmt.main"]}}} 20 | -------------------------------------------------------------------------------- /dev/cljs/user.cljs: -------------------------------------------------------------------------------- 1 | (ns cljs.user 2 | (:require [cljs.pprint] 3 | [cljs.test :refer [run-tests]] 4 | [cljs-test-display.core :as test.display] 5 | [indexed.db.cursor-test] 6 | [indexed.db.database-test] 7 | [indexed.db.factory-test] 8 | [indexed.db.key-range-test] 9 | [indexed.db.store-test] 10 | [indexed.db.transaction-test])) 11 | 12 | (run-tests 13 | (test.display/init! "app") 14 | 'indexed.db.cursor-test 15 | 'indexed.db.database-test 16 | 'indexed.db.factory-test 17 | 'indexed.db.key-range-test 18 | 'indexed.db.store-test 19 | 'indexed.db.transaction-test) 20 | -------------------------------------------------------------------------------- /index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 |
8 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /indexeddb.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/brianium/indexed.db/eb9ff33bf845fa297c6be7d0cb6a603aca463e31/indexeddb.gif -------------------------------------------------------------------------------- /src/indexed/db.cljs: -------------------------------------------------------------------------------- 1 | (ns indexed.db 2 | (:require [indexed.db.impl.cursor :as cursor] 3 | [indexed.db.impl.database :as database] 4 | [indexed.db.impl.events :as events] 5 | [indexed.db.impl.factory :as factory] 6 | [indexed.db.impl.key-range :as key-range] 7 | [indexed.db.impl.request :as request] 8 | [indexed.db.impl.store :as store] 9 | [indexed.db.impl.transaction :as transaction] 10 | [indexed.db.impl.protocols :as impl]) 11 | (:refer-clojure :exclude [key update get count])) 12 | 13 | ;;; IDBFactory 14 | 15 | (defn factory? 16 | "Returns true if the given value satisfies the IDBFactory protocol" 17 | [x] 18 | (factory/factory? x)) 19 | 20 | (defn create-factory 21 | "Return an implementation of the IDBFactory protocol backed by 22 | the global indexedDB object" 23 | [] 24 | (factory/create-factory)) 25 | 26 | (defn open 27 | "Return an IDBRequest whose result is a native IDBDatabase object. 28 | 29 | The version value is optional. 30 | 31 | If the version is not provided and the database exists, then a connection 32 | to the database will be opened without changing its version. 33 | 34 | If the version is not provided and the database does not exist, 35 | then it will be created with version `1` 36 | 37 | The indexed.db library makes no assumptions about how consumers want to handle 38 | events. In order to use the indexed.db API with the result of an open reqest, it 39 | will need to be converted to an implementation of the IDBDatabase protocol: 40 | 41 | ```clojure 42 | (let [open-request (db/open \"my-db\")] 43 | (-> open-request 44 | (db/on \"upgradeneeded\" handle-upgrade) 45 | (db/on \"success\" #(db/create-database 46 | (db/result open-request))))) 47 | ```" 48 | ([factory name version] 49 | (factory/open factory name version)) 50 | ([name version] 51 | (factory/open name version)) 52 | ([name] 53 | (factory/open name))) 54 | 55 | (defn delete-database 56 | "Request the deletion of a database by name. 57 | 58 | Returns an IDBRequest" 59 | ([factory name] 60 | (factory/delete-database factory name)) 61 | ([name] 62 | (factory/delete-database name))) 63 | 64 | (defn cmp 65 | "Compares two values as keys to determine equality and ordering for IndexedDB operations, 66 | such as storing and iterating. 67 | 68 | | returned value | description | 69 | |:---------------|-------------------------------------:| 70 | | -1 | 1st key is less than the 2nd key | 71 | | 0 | 1st key is equal to the 2nd key | 72 | | 1 | 1st key is greater than the 2nd key |" 73 | ([factory a b] 74 | (factory/cmp factory a b)) 75 | ([a b] 76 | (factory/cmp a b))) 77 | 78 | (defn databases 79 | "Calls the given function with an error or a sequence of all 80 | available databases, including their names and versions" 81 | ([factory fn-1] 82 | (factory/databases factory fn-1) 83 | factory) 84 | ([fn-1] 85 | (factory/databases fn-1))) 86 | 87 | ;;; IDBDatabase 88 | 89 | (defn database? 90 | "Returns true if the given value satisfies the IDBDatabase protocol" 91 | [x] 92 | (database/database? x)) 93 | 94 | (defn db 95 | "Returns an implementation of the IDBDatabase protocol that is associated 96 | with `belongs-to-database` (presumably an IDBTransaction implementation)" 97 | [belongs-to-database] 98 | (database/db belongs-to-database)) 99 | 100 | (defn get-database 101 | "An alias for the [[indexed.db/db]] function" 102 | [belongs-to-database] 103 | (db belongs-to-database)) 104 | 105 | (defn create-database 106 | "A factory function for creating an implementation of the IDBDatabase protocol 107 | from a native js/IDBDatabase" 108 | [js-idb] 109 | (database/create-database js-idb)) 110 | 111 | (defn close 112 | "Returns immediately and closes the connection in a separate thread. 113 | 114 | The connection is not actually closed until all transactions created using this 115 | connection are complete. No new transactions can be created for this connection 116 | once this method is called. Methods that create transactions throw an exception 117 | if a closing operation is pending." 118 | [db] 119 | (database/close db)) 120 | 121 | (defn delete-object-store 122 | "Destroys the object store with the given name in the connected database, along with 123 | any indexes that reference it." 124 | [db name] 125 | (database/delete-object-store db name)) 126 | 127 | (defn object-store-names 128 | "Returns a sequence of object store names from the given database or transaction" 129 | [x] 130 | (cond-> x 131 | (satisfies? impl/IDBTransaction x) (db) 132 | :always (database/object-store-names))) 133 | 134 | (defn version 135 | "Returns an integer version of the connected database" 136 | [db] 137 | (database/version db)) 138 | 139 | (defn transaction 140 | "Immediately returns an implementation of the IDBTransaction protocol. 141 | The IDBTransaction protocol provides functions that can be used to access 142 | object stores." 143 | ([db store-names mode options] 144 | (database/transaction db store-names mode options)) 145 | ([db store-names mode] 146 | (database/transaction db store-names mode)) 147 | ([db store-names] 148 | (database/transaction db store-names))) 149 | 150 | ;;; IDBCursor/IDBCursorWithValue 151 | 152 | (defn cursor? 153 | "Returns true if the given value satisfies the IDBCursor protocol" 154 | [x] 155 | (cursor/cursor? x)) 156 | 157 | (defn create-cursor 158 | "A factory function for creating an implementation of the IDBCursor protocol 159 | from a native js/IDBCursor" 160 | [js-cursor] 161 | (cursor/create-cursor js-cursor)) 162 | 163 | (defn direction 164 | "Returns the direction of traversal of the cursor. See [[indexed.db/open-cursor]] 165 | for an explanation of possible direction values" 166 | [cursor] 167 | (cursor/direction cursor)) 168 | 169 | (defn key 170 | "Returns the key for the record at the cursor's position" 171 | [cursor] 172 | (cursor/key cursor)) 173 | 174 | (defn primary-key 175 | "Interface returns the cursor's current effective key. If the cursor is currently being iterated 176 | or has iterated outside its range, this is set to undefined. " 177 | [cursor] 178 | (cursor/primary-key cursor)) 179 | 180 | (defn advance 181 | "Sets the number of times a cursor should move its position forward" 182 | [cursor count] 183 | (cursor/advance cursor count)) 184 | 185 | (defn continue 186 | "Advances the cursor to the next position along its direction, to the item whose key matches 187 | the optional key parameter" 188 | ([cursor k] 189 | (cursor/continue cursor k)) 190 | ([cursor] 191 | (continue cursor nil))) 192 | 193 | (defn continue-primary-key 194 | "Advances the cursor to the item whose key matches the key parameter as well as whose primary key 195 | matches the primary key parameter. 196 | 197 | A typical use case, is to resume the iteration where a previous cursor has been closed, without having to compare the keys one by one." 198 | [cursor k primary-key] 199 | (cursor/continue-primary-key cursor k primary-key)) 200 | 201 | (defn update 202 | "Returns an implementation of the IDBRequest protocol, and, in a separate thread, updates the value 203 | at the current position of the cursor in the object store. 204 | 205 | If the cursor points to a record that has just been deleted, a new record is created. 206 | 207 | Be aware that you can't call update (or delete) on cursors obtained from open-key-cursor. 208 | For such needs, you have to use open-cursor instead" 209 | [cursor value] 210 | (cursor/update cursor value)) 211 | 212 | (defn cursor-with-value? 213 | "Returns true if the given value satisfies the IDBCursorWithValue protocol" 214 | [x] 215 | (cursor/cursor-with-value? x)) 216 | 217 | (defn create-cursor-with-value 218 | "A factory function for creating an implementation of the IDBCursorWithValue protocol 219 | from a native js/IDBCursorWithValue" 220 | [js-cursor-with-value] 221 | (cursor/create-cursor-with-value js-cursor-with-value)) 222 | 223 | (defn value 224 | "Returns the value of the current cursor, whatever that is" 225 | [cursor-with-value] 226 | (cursor/value cursor-with-value)) 227 | 228 | ;;; IDBRequest 229 | 230 | (defn request? 231 | "Returns true if the given value satisfies the IDBRequest protocol" 232 | [x] 233 | (request/request? x)) 234 | 235 | (defn create-request 236 | "A factory function for creating an implementation of the IDBRequest protocol 237 | from a native js/IDBRequest" 238 | [js-request] 239 | (request/create-request js-request)) 240 | 241 | (defn error 242 | "If a type stores an error for failed operations, this function 243 | will return it." 244 | [has-errors] 245 | (impl/error has-errors)) 246 | 247 | (defn result 248 | "Returns the result of the request" 249 | [db-request] 250 | (request/result db-request)) 251 | 252 | (defn ready-state 253 | "Returns the state of the request" 254 | [db-request] 255 | (request/ready-state db-request)) 256 | 257 | ;;; Events 258 | 259 | (defn version-change-event? 260 | "Returns true if the given value satisfies the IDBVersionChangeEvent protocol" 261 | [x] 262 | (events/version-change-event? x)) 263 | 264 | (defn create-version-change-event 265 | "A factory function for creating an implementation of the IDBVersionChangeEvent protocol 266 | from a native js/IDBVersionChangeEvent. 267 | 268 | Note: Since this library intends to avoid making assumptions on event handling, this 269 | type is not provided automatically. You must construct it manually when handling version 270 | change events. An example from the test suite: 271 | 272 | ```clojure 273 | (defn handle-upgrade 274 | [fn-2] 275 | (fn [e] 276 | (let [event (db/create-version-change-event e) 277 | request (db/get-request event)] 278 | (fn-2 (db/result request) (db/get-transaction request))))) 279 | ```" 280 | [js-event] 281 | (events/create-version-change-event js-event)) 282 | 283 | (defn new-version 284 | "Returns the new version number of the database" 285 | [version-change-event] 286 | (events/new-version version-change-event)) 287 | 288 | (defn old-version 289 | "Returns the old version number of the database" 290 | [version-change-event] 291 | (events/old-version version-change-event)) 292 | 293 | (defn get-request 294 | "Return the IDBRequest that `belongs-to-request` belongs to. This is presumably 295 | an IDBVersionChangeEvent or IDBVersionChangeEvent implementation" 296 | [belongs-to-request] 297 | (create-request 298 | (impl/idb-request belongs-to-request))) 299 | 300 | (defn event-target? 301 | "Returns true if the given value satisfies the EventTarget protocol" 302 | [x] 303 | (events/event-target? x)) 304 | 305 | (defn on 306 | "A simple wrapper around the native addEventListener function. 307 | 308 | Adds the event listener and returns the given event target" 309 | ([event-target type listener] 310 | (events/on event-target type listener)) 311 | ([event-target type listener options] 312 | (events/on event-target type listener options))) 313 | 314 | (defn off 315 | "A simple wrapper around the native removeEventListener function. 316 | 317 | Removes the event listener and returns the given event target" 318 | ([event-target type listener] 319 | (events/off event-target type listener)) 320 | ([event-target type listener options] 321 | (events/off event-target type listener options))) 322 | 323 | ;;; IDBKeyRange 324 | 325 | (defn key-range? 326 | "Returns true if the given value satisfies the IDBKeyRange protocol" 327 | [x] 328 | (key-range/key-range? x)) 329 | 330 | (defn includes 331 | "Returns a boolean indicating whether a specified key is inside the key range" 332 | [key-range k] 333 | (key-range/includes key-range k)) 334 | 335 | (defn lower 336 | "Returns the lower bound of the key range" 337 | [key-range] 338 | (key-range/lower key-range)) 339 | 340 | (defn upper 341 | "Returns the upper bound of the key range" 342 | [key-range] 343 | (key-range/upper key-range)) 344 | 345 | (defn lower-open? 346 | "Returns false if the lower-bound value is included in the key range" 347 | [key-range] 348 | (key-range/lower-open? key-range)) 349 | 350 | (defn upper-open? 351 | "Returns false if the upper-bound value is included in the key range" 352 | [key-range] 353 | (key-range/upper-open? key-range)) 354 | 355 | (defn bound 356 | "Creates a new key range with the specified upper and lower bounds. The bounds can be open 357 | (that is, the bounds exclude the endpoint values) or closed (that is, the bounds include the endpoint values). 358 | 359 | By default, the bounds are closed." 360 | ([lower upper lower-open? upper-open?] 361 | (key-range/bound lower upper lower-open? upper-open?)) 362 | ([lower upper lower-open?] 363 | (key-range/bound lower upper lower-open?)) 364 | ([lower upper] 365 | (key-range/bound lower upper))) 366 | 367 | (defn only 368 | "Creates a new key range containing a single value" 369 | [value] 370 | (key-range/only value)) 371 | 372 | (defn lower-bound 373 | "Creates a new key range with only a lower bound. 374 | 375 | By default, it includes the lower endpoint value and is closed" 376 | ([lower open?] 377 | (key-range/lower-bound lower open?)) 378 | ([lower] 379 | (key-range/lower-bound lower))) 380 | 381 | (defn upper-bound 382 | "Creates a new upper-bound key range. 383 | 384 | By default, it includes the upper endpoint value and is closed" 385 | ([upper open?] 386 | (key-range/upper-bound upper open?)) 387 | ([upper] 388 | (key-range/upper-bound upper))) 389 | 390 | ;;; IDBTransaction 391 | 392 | (defn transaction? 393 | "Returns true if the given value satisfies the IDBTransaction protocol" 394 | [x] 395 | (transaction/transaction? x)) 396 | 397 | (defn create-transaction 398 | "A factory function for creating an implementation of the IDBTransaction protocol 399 | from a native js/IDBTransaction" 400 | [js-idb-transaction] 401 | (transaction/create-transaction js-idb-transaction)) 402 | 403 | (defn get-transaction 404 | "Return the IDBTransaction that `belongs-to-txn` belongs to. This is presumably 405 | an IDBRequest or IDBObjectStore implementation" 406 | [belongs-to-txn] 407 | (transaction/transaction belongs-to-txn)) 408 | 409 | (defn durability 410 | "Returns the durability hint the transaction was created with" 411 | [txn] 412 | (transaction/durability txn)) 413 | 414 | (defn mode 415 | "Returns the current mode for accessing the data in the object stores in the scope of the transaction" 416 | [txn] 417 | (transaction/mode txn)) 418 | 419 | (defn abort 420 | "Rolls back all the changes to objects in the database associated with this transaction" 421 | [txn] 422 | (transaction/abort txn)) 423 | 424 | (defn commit 425 | "Commits the transaction if it is called on an active transaction. 426 | 427 | Note that commit doesn't normally have to be called — a transaction will automatically 428 | commit when all outstanding requests have been satisfied and no new requests have been made." 429 | [txn] 430 | (transaction/commit txn)) 431 | 432 | ;;; IDBIndex/IDBObjectStore 433 | 434 | (defn key-path 435 | "Returns the key path of this object store" 436 | [store-or-index] 437 | (store/key-path store-or-index)) 438 | 439 | (defn count 440 | "Returns an implementation of the IDBRequest protocol, and, in a separate thread, 441 | returns the total number of records that match the provided key or IDBKeyRange. 442 | 443 | If no arguments are provided, it returns the total number of records in the store." 444 | ([store-or-index query] 445 | (store/count store-or-index query)) 446 | ([store-or-index] 447 | (store/count store-or-index))) 448 | 449 | (defn get 450 | "Returns an implementation of the IDBRequest protocol, and, in a separate thread, finds either the value in the referenced 451 | object store or index that corresponds to the given key or the first corresponding value, if key is set to an IDBKeyRange. 452 | 453 | If a value is successfully found, then a structured clone of it is created and set as the result of the request" 454 | [store-or-index k] 455 | (store/get store-or-index k)) 456 | 457 | (defn get-key 458 | "Returns an implementation of the IDBRequest protocol, and, in a separate thread, finds either the primary key 459 | that corresponds to the given key in this index (or object store) or the first corresponding primary key, if key is set to an IDBKeyRange. 460 | 461 | If a primary key is found, it is set as the result of the request object. Note that this doesn't return the whole record as get does." 462 | [store-or-index k] 463 | (store/get-key store-or-index k)) 464 | 465 | (defn get-all 466 | "Retrieves all objects that are inside the index or store. Returns an IDBRequest containing 467 | the results" 468 | ([store-or-index query count] 469 | (store/get-all store-or-index query count)) 470 | ([store-or-index query] 471 | (store/get-all store-or-index query)) 472 | ([store-or-index] 473 | (store/get-all store-or-index))) 474 | 475 | (defn get-all-keys 476 | "Asynchronously retrieves the primary keys of all objects inside the index or store, 477 | setting them as the result of the request" 478 | ([store-or-index query count] 479 | (store/get-all-keys store-or-index query count)) 480 | ([store-or-index query] 481 | (store/get-all-keys store-or-index query)) 482 | ([store-or-index] 483 | (store/get-all-keys store-or-index))) 484 | 485 | (defn open-cursor 486 | "Returns an IDBRequest, and, in a separate thread, creates a cursor over the specified key range. 487 | 488 | The method sets the position of the cursor to the appropriate record, based on the specified direction. 489 | 490 | If the key range is not specified or is null, then the range includes all the records. 491 | 492 | The value of direction can be one of \"next\", \"nextunique\", \"prev\", or \"prevunique\". 493 | 494 | \"next\": The cursor shows all records, including duplicates. It starts at the lower bound of the key range and moves upwards 495 | (monotonically increasing in the order of keys). 496 | 497 | \"nextunique\": The cursor shows all records, excluding duplicates. If multiple records exist with the same key, 498 | only the first one iterated is retrieved. It starts at the lower bound of the key range and moves upwards. 499 | 500 | \"prev\": The cursor shows all records, including duplicates. It starts at the upper bound of the key range and moves 501 | downwards (monotonically decreasing in the order of keys). 502 | 503 | \"prevunique\": The cursor shows all records, excluding duplicates. If multiple records exist with the same key, 504 | only the first one iterated is retrieved. It starts at the upper bound of the key range and moves downwards." 505 | ([store-or-index query direction] 506 | (store/open-cursor store-or-index query direction)) 507 | ([store-or-index query] 508 | (store/open-cursor store-or-index query)) 509 | ([store-or-index] 510 | (store/open-cursor store-or-index))) 511 | 512 | (defn open-key-cursor 513 | "Returns an IDBRequest, and, in a separate thread, creates a cursor over the specified key range, as arranged by this index. 514 | 515 | The method sets the position of the cursor to the appropriate key, based on the specified direction. 516 | 517 | If the key range is not specified or is null, then the range includes all the keys. 518 | 519 | See [[indexed.db/open-cursor]] for an explanation of direction." 520 | ([store-or-index query direction] 521 | (store/open-key-cursor store-or-index query direction)) 522 | ([store-or-index query] 523 | (store/open-key-cursor store-or-index query)) 524 | ([store-or-index] 525 | (store/open-key-cursor store-or-index))) 526 | 527 | (defn index? 528 | "Returns true if the given value satisfies the IDBIndex protocol" 529 | [x] 530 | (store/index? x)) 531 | 532 | (defn auto-locale? 533 | "Returns a boolean value indicating whether the index had a locale value of auto specified upon its creation. 534 | 535 | This property is [experimental](https://developer.mozilla.org/en-US/docs/MDN/Guidelines/Conventions_definitions#experimental)" 536 | [index] 537 | (store/auto-locale? index)) 538 | 539 | (defn locale 540 | "Returns the locale of the index (for example en-US, or pl) if it had a locale value specified upon its creation. 541 | 542 | This property is [experimental](https://developer.mozilla.org/en-US/docs/MDN/Guidelines/Conventions_definitions#experimental)" 543 | [index] 544 | (store/locale index)) 545 | 546 | (defn get-object-store 547 | "Returns the object store the index belongs to" 548 | [index] 549 | (store/get-object-store index)) 550 | 551 | (defn multi-entry? 552 | "Returns a boolean value that affects how the index behaves when the result of evaluating the index's 553 | key path yields an array" 554 | [index] 555 | (store/multi-entry? index)) 556 | 557 | (defn unique? 558 | "Returns a boolean that states whether the index allows duplicate keys" 559 | [index] 560 | (store/unique? index)) 561 | 562 | (defn store? 563 | "Returns true if the given value satisfies the IDBObjectStore protocol" 564 | [x] 565 | (store/store? x)) 566 | 567 | (defn index-names 568 | "Returns a sequence of the names of indexes on objects in this object store" 569 | [store] 570 | (store/index-names store)) 571 | 572 | (defn auto-increment? 573 | "Returns the value of the auto increment flag for this object store. 574 | 575 | Note that every object store has its own separate auto increment counter" 576 | [store] 577 | (store/auto-increment? store)) 578 | 579 | (defn add 580 | "Returns an implementation of the IDBRequest protocol, and, in a separate thread, creates a 581 | structured clone of the value, and stores the cloned value in the object store. 582 | 583 | This is for adding new records to an object store. 584 | 585 | If k is unspecified, it results to null" 586 | ([store value k] 587 | (store/add store value k)) 588 | ([store value] 589 | (store/add store value))) 590 | 591 | (defn clear 592 | "Creates and immediately returns an IDBRequest, and clears this object store in a separate thread. 593 | 594 | This is for deleting all the current data out of an object store" 595 | [store] 596 | (store/clear store)) 597 | 598 | (defn create-index 599 | "Creates and returns an implementation of the IDBIndex protocol in the connected database. 600 | 601 | It creates a new field/column defining a new data point for each database record to contain. 602 | 603 | Note that this method must be called only from a VersionChange transaction mode callback" 604 | ([store index-name key-path object-parameters] 605 | (store/create-index store index-name key-path object-parameters)) 606 | ([store index-name key-path] 607 | (store/create-index store index-name key-path))) 608 | 609 | (defn delete-index 610 | "Destroys the index with the specified name in the connected database, used during a version upgrade 611 | 612 | Note that this method must be called only from a VersionChange transaction mode callback" 613 | [store index-name] 614 | (store/delete-index store index-name)) 615 | 616 | (defn index 617 | "Opens a named index in the current object store, after which it can be used to, for example, 618 | return a series of records sorted by that index using a cursor" 619 | [store index-name] 620 | (store/index store index-name)) 621 | 622 | (defn put 623 | "Updates a given record in a database, or inserts a new record if the given item does not already exist" 624 | ([store item] 625 | (store/put store item)) 626 | ([store item k] 627 | (store/put store item k))) 628 | 629 | (defn delete 630 | "When given an IDBObjectStore and a key, returns an IDBRequest, 631 | and, in a separate thread, deletes the specified record or records. 632 | 633 | When given a cursor, returns an IDBRequest, and, in a separate thread, 634 | deletes the record at the cursor's position, without changing the cursor's position. 635 | Once the record is deleted, the cursor's value is set to null" 636 | ([cursor] 637 | (cursor/delete cursor)) 638 | ([store k] 639 | (store/delete store k))) 640 | 641 | (defn create-object-store 642 | "When called with an IDBDatabase, creates and returns a new IDBObjectStore. 643 | 644 | When called with a single native js/IDBObjectStore, acts as a factory function for 645 | wrapping the js/IDBObjectStore in an implementation of the IDBObjectStore protocol" 646 | ([db name options] 647 | (database/create-object-store db name options)) 648 | ([db name] 649 | (database/create-object-store db name)) 650 | ([js-idb-store] 651 | (store/create-object-store js-idb-store))) 652 | 653 | (defn object-store 654 | "When called with a transaction and object store name, returns an object store that 655 | has already been added to the scope of this transaction. 656 | 657 | When called with an index, returns the object store the index belongs to" 658 | ([txn name] 659 | (transaction/object-store txn name)) 660 | ([index] 661 | (get-object-store index))) 662 | 663 | (defn source 664 | "In the case of a cursor, returns the IDBObjectStore or IDBIndex that the cursor is iterating over. 665 | 666 | In the case of a request, returns the source of the request, such as an index or an object store." 667 | [x] 668 | (when-some [src (impl/source x)] 669 | (cond 670 | (instance? js/IDBObjectStore src) (create-object-store src) 671 | (instance? js/IDBIndex src) (store/create-index* src) 672 | :else nil))) 673 | -------------------------------------------------------------------------------- /src/indexed/db/impl/cursor.cljs: -------------------------------------------------------------------------------- 1 | (ns ^:no-doc indexed.db.impl.cursor 2 | (:require [indexed.db.impl.request :as request] 3 | [indexed.db.impl.protocols :as impl]) 4 | (:refer-clojure :exclude [key update])) 5 | 6 | (deftype Cursor [idb-cursor] 7 | impl/BelongsToRequest 8 | (idb-request [_] (.-request idb-cursor)) 9 | 10 | impl/BelongsToSource 11 | (source [_] (.-source idb-cursor)) 12 | 13 | impl/IDBCursor 14 | (direction [_] (.-direction idb-cursor)) 15 | (key [_] (.-key idb-cursor)) 16 | (primary-key [_] (.-primaryKey idb-cursor)) 17 | (advance [_ count] (.advance idb-cursor count)) 18 | (continue 19 | [_ k] 20 | (if (some? k) 21 | (.continue idb-cursor k) 22 | (.continue idb-cursor))) 23 | (continue-primary-key [_ k primary-key] (.continuePrimaryKey idb-cursor k primary-key)) 24 | (delete [_] (request/create-request (.delete idb-cursor))) 25 | (update [_ value] (request/create-request (.update idb-cursor value)))) 26 | 27 | (defn cursor? 28 | [x] 29 | (satisfies? impl/IDBCursor x)) 30 | 31 | (defn create-cursor 32 | [idb-cursor] 33 | (Cursor. idb-cursor)) 34 | 35 | (defn direction 36 | [cursor] 37 | (impl/direction cursor)) 38 | 39 | (defn key 40 | [cursor] 41 | (impl/key cursor)) 42 | 43 | (defn primary-key 44 | [cursor] 45 | (impl/primary-key cursor)) 46 | 47 | (defn advance 48 | [cursor count] 49 | (impl/advance cursor count)) 50 | 51 | (defn continue 52 | ([cursor k] 53 | (impl/continue cursor k)) 54 | ([cursor] 55 | (continue cursor nil))) 56 | 57 | (defn continue-primary-key 58 | [cursor k primary-key] 59 | (impl/continue-primary-key cursor k primary-key)) 60 | 61 | (defn delete 62 | [cursor] 63 | (impl/delete cursor)) 64 | 65 | (defn update 66 | [cursor value] 67 | (impl/update cursor value)) 68 | 69 | (deftype CursorWithValue [cursor idb-cursor] 70 | impl/BelongsToRequest 71 | (idb-request [_] (.-request idb-cursor)) 72 | 73 | impl/BelongsToSource 74 | (source [_] (.-source idb-cursor)) 75 | 76 | impl/IDBCursor 77 | (direction [_] (direction cursor)) 78 | (key [_] (key cursor)) 79 | (primary-key [_] (primary-key cursor)) 80 | (advance [_ count] (advance cursor count)) 81 | (continue [_ k] (continue cursor k)) 82 | (continue-primary-key [_ k primary-key] (continue-primary-key cursor k primary-key)) 83 | (delete [_] (delete cursor)) 84 | (update [_ value] (update cursor value)) 85 | 86 | impl/IDBCursorWithValue 87 | (value [_] (.-value idb-cursor))) 88 | 89 | (defn cursor-with-value? 90 | [x] 91 | (satisfies? impl/IDBCursorWithValue x)) 92 | 93 | (defn create-cursor-with-value 94 | [idb-cursor] 95 | (CursorWithValue. 96 | (create-cursor idb-cursor) 97 | idb-cursor)) 98 | 99 | (defn value 100 | [cursor-with-value] 101 | (impl/value cursor-with-value)) 102 | -------------------------------------------------------------------------------- /src/indexed/db/impl/database.cljs: -------------------------------------------------------------------------------- 1 | (ns ^:no-doc indexed.db.impl.database 2 | (:require [indexed.db.impl.protocols :as impl] 3 | [indexed.db.impl.store :as store] 4 | [indexed.db.impl.transaction :as transaction]) 5 | (:refer-clojure :exclude [name])) 6 | 7 | (defn clj->create-store-options 8 | [{:keys [key-path auto-increment]}] 9 | (let [options #js {}] 10 | (when key-path 11 | (set! (.-keyPath options) key-path)) 12 | (when (some? auto-increment) 13 | (set! (.-autoIncrement options) auto-increment)) 14 | options)) 15 | 16 | (defn clj->transaction-options 17 | [{:keys [durability] :or {durability "default"}}] 18 | (let [options #js {}] 19 | (set! (.-durability options) durability) 20 | options)) 21 | 22 | (deftype Database [idb] 23 | impl/EventTarget 24 | (target [_] idb) 25 | 26 | INamed 27 | (-name [_] (.-name idb)) 28 | 29 | impl/IDBDatabase 30 | (close [_] (.close idb)) 31 | (version [_] (.-version idb)) 32 | (create-object-store 33 | [_ name options] 34 | (if (some? options) 35 | (store/create-object-store (.createObjectStore idb name (clj->create-store-options options))) 36 | (store/create-object-store (.createObjectStore idb name)))) 37 | (delete-object-store 38 | [_ name] 39 | (.deleteObjectStore idb name)) 40 | (object-store-names 41 | [_] 42 | (array-seq (.-objectStoreNames idb))) 43 | (transaction 44 | [_ store-names mode options] 45 | (transaction/create-transaction (.transaction idb (apply array store-names) mode (clj->transaction-options options))))) 46 | 47 | (defn database? 48 | [x] 49 | (satisfies? impl/IDBDatabase x)) 50 | 51 | (defn create-database 52 | [idb] 53 | (Database. idb)) 54 | 55 | (defn close 56 | [db] 57 | (impl/close db)) 58 | 59 | (defn create-object-store 60 | ([db name options] 61 | (impl/create-object-store db name options)) 62 | ([db name] 63 | (create-object-store db name nil))) 64 | 65 | (defn delete-object-store 66 | [db name] 67 | (impl/delete-object-store db name) 68 | db) 69 | 70 | (defn object-store-names 71 | [db] 72 | (impl/object-store-names db)) 73 | 74 | (defn version 75 | [db] 76 | (impl/version db)) 77 | 78 | (defn transaction 79 | ([db store-names mode options] 80 | (impl/transaction db store-names mode options)) 81 | ([db store-names mode] 82 | (transaction db store-names mode {})) 83 | ([db store-names] 84 | (transaction db store-names "readonly"))) 85 | 86 | (defn db 87 | [belongs-to-database] 88 | (create-database 89 | (impl/idb-database belongs-to-database))) 90 | -------------------------------------------------------------------------------- /src/indexed/db/impl/events.cljs: -------------------------------------------------------------------------------- 1 | (ns ^:no-doc indexed.db.impl.events 2 | (:require [indexed.db.impl.protocols :as impl])) 3 | 4 | (deftype VersionChangeEvent [js-event] 5 | impl/IDBVersionChangeEvent 6 | (new-version [_] (.-newVersion js-event)) 7 | (old-version [_] (.-oldVersion js-event)) 8 | 9 | impl/BelongsToRequest 10 | (idb-request [_] (.-target js-event))) 11 | 12 | (defn version-change-event? 13 | [x] 14 | (satisfies? impl/IDBVersionChangeEvent x)) 15 | 16 | (defn create-version-change-event 17 | [js-event] 18 | (VersionChangeEvent. js-event)) 19 | 20 | (defn new-version 21 | [version-change-event] 22 | (impl/new-version version-change-event)) 23 | 24 | (defn old-version 25 | [version-change-event] 26 | (impl/old-version version-change-event)) 27 | 28 | (defn on 29 | ([event-target type listener] 30 | (.addEventListener (impl/target event-target) type listener) 31 | event-target) 32 | ([event-target type listener options] 33 | (.addEventListener (impl/target event-target) type listener (clj->js options)) 34 | event-target)) 35 | 36 | (defn off 37 | ([event-target type listener] 38 | (.removeEventListener (impl/target event-target) type listener) 39 | event-target) 40 | ([event-target type listener options] 41 | (.removeEventListener (impl/target event-target) type listener (clj->js options)) 42 | event-target)) 43 | 44 | (defn event-target? 45 | [x] 46 | (satisfies? impl/EventTarget x)) 47 | -------------------------------------------------------------------------------- /src/indexed/db/impl/factory.cljs: -------------------------------------------------------------------------------- 1 | (ns ^:no-doc indexed.db.impl.factory 2 | (:require [indexed.db.impl.request :as request] 3 | [indexed.db.impl.protocols :as impl])) 4 | 5 | (defn dict->map 6 | [obj] 7 | {:name (.-name obj) 8 | :version (.-version obj)}) 9 | 10 | (deftype Factory [factory] 11 | impl/IDBFactory 12 | (open 13 | [_ name version] 14 | (request/create-request 15 | (.open factory name version))) 16 | (delete-database 17 | [_ name] 18 | (request/create-request (.deleteDatabase factory name))) 19 | (cmp 20 | [_ a b] 21 | (.cmp factory a b)) 22 | (databases 23 | [_ fn-1] 24 | (if (type (.-databases factory)) 25 | (let [p (.databases factory)] 26 | (-> p 27 | (.then (fn [result] 28 | (cond-> result 29 | (array? result) (->> array-seq (map dict->map)) 30 | :always (fn-1)))))) 31 | (fn-1 '())))) 32 | 33 | (type (.-databases js/indexedDB)) 34 | 35 | (defn factory? 36 | [x] 37 | (satisfies? impl/IDBFactory x)) 38 | 39 | (defn create-factory [] 40 | (Factory. js/indexedDB)) 41 | 42 | (defn open 43 | ([factory name version] 44 | (impl/open factory name version)) 45 | ([name version] 46 | (open (create-factory) name version)) 47 | ([name] 48 | (open (create-factory) name))) 49 | 50 | (defn delete-database 51 | ([factory name] 52 | (impl/delete-database factory name)) 53 | ([name] 54 | (delete-database (create-factory) name))) 55 | 56 | (defn cmp 57 | ([factory a b] 58 | (impl/cmp factory a b)) 59 | ([a b] 60 | (cmp (create-factory) a b))) 61 | 62 | (defn databases 63 | ([factory fn-1] 64 | (impl/databases factory fn-1) 65 | factory) 66 | ([fn-1] 67 | (databases (create-factory) fn-1))) 68 | -------------------------------------------------------------------------------- /src/indexed/db/impl/key_range.cljs: -------------------------------------------------------------------------------- 1 | (ns ^:no-doc indexed.db.impl.key-range 2 | "@TODO add property accessors and tests" 3 | (:require [indexed.db.impl.protocols :as impl])) 4 | 5 | (deftype KeyRange [key-range] 6 | impl/IDBKeyRange 7 | (includes [_ k] (.includes key-range k)) 8 | (lower [_] (.-lower key-range)) 9 | (upper [_] (.-upper key-range)) 10 | (lower-open? [_] (.-lowerOpen key-range)) 11 | (upper-open? [_] (.-upperOpen key-range)) 12 | (idb-key-range [_] key-range)) 13 | 14 | (defn key-range? 15 | [x] 16 | (satisfies? impl/IDBKeyRange x)) 17 | 18 | (defn idb-key-range 19 | [key-range] 20 | (impl/idb-key-range key-range)) 21 | 22 | (defn create-key-range 23 | [key-range] 24 | (KeyRange. key-range)) 25 | 26 | (defn includes 27 | [key-range k] 28 | (impl/includes key-range k)) 29 | 30 | (defn lower 31 | [key-range] 32 | (impl/lower key-range)) 33 | 34 | (defn upper 35 | [key-range] 36 | (impl/upper key-range)) 37 | 38 | (defn lower-open? 39 | [key-range] 40 | (impl/lower-open? key-range)) 41 | 42 | (defn upper-open? 43 | [key-range] 44 | (impl/upper-open? key-range)) 45 | 46 | (defn bound 47 | ([lower upper lower-open? upper-open?] 48 | (create-key-range 49 | (.bound js/IDBKeyRange lower upper lower-open? upper-open?))) 50 | ([lower upper lower-open?] 51 | (bound lower upper lower-open? false)) 52 | ([lower upper] 53 | (bound lower upper false))) 54 | 55 | (defn only 56 | [value] 57 | (create-key-range 58 | (.only js/IDBKeyRange value))) 59 | 60 | (defn lower-bound 61 | ([lower open] 62 | (create-key-range 63 | (.lowerBound js/IDBKeyRange lower open))) 64 | ([lower] 65 | (lower-bound lower false))) 66 | 67 | (defn upper-bound 68 | ([upper open] 69 | (create-key-range 70 | (.upperBound js/IDBKeyRange upper open))) 71 | ([upper] 72 | (upper-bound upper false))) 73 | 74 | 75 | 76 | -------------------------------------------------------------------------------- /src/indexed/db/impl/protocols.cljs: -------------------------------------------------------------------------------- 1 | (ns indexed.db.impl.protocols 2 | "Most of the protocols in this namespace map directly to native JS types 3 | for the IndexedDB API. Where possible, JS properties and methods are mapped 4 | to conventional Clojure names (i.e kebab case vs camel case). 5 | 6 | This library adds a handful of protocols, such as HasErrors, EventTarget, and the BelongsToX 7 | protocols to address cross cutting concerns present in the native JS API." 8 | (:refer-clojure :exclude [count key get update])) 9 | 10 | (defprotocol IDBFactory 11 | (open [self name version] [self name]) 12 | (delete-database [self name]) 13 | (cmp [self a b]) 14 | (databases [self fn-1])) 15 | 16 | (defprotocol IDBDatabase 17 | (close [self]) 18 | (version [self]) 19 | (create-object-store [self name options]) 20 | (delete-object-store [self name]) 21 | (object-store-names [self]) 22 | (transaction [self store-names mode options])) 23 | 24 | (defprotocol HasErrors 25 | (error [self])) 26 | 27 | (defprotocol BelongsToDatabase 28 | (idb-database [self])) 29 | 30 | (defprotocol IDBKeyRange 31 | (includes [_ k]) 32 | (lower [_]) 33 | (upper [_]) 34 | (lower-open? [_]) 35 | (upper-open? [_]) 36 | (idb-key-range [_])) 37 | 38 | (defprotocol BelongsToSource 39 | (source [self])) 40 | 41 | (defprotocol IDBRequest 42 | (result [self]) 43 | (ready-state [self])) 44 | 45 | (defprotocol BelongsToRequest 46 | (idb-request [self])) 47 | 48 | (defprotocol IDBCursor 49 | (direction [self]) 50 | (key [self]) 51 | (primary-key [self]) 52 | (advance [self count]) 53 | (continue [self k]) 54 | (continue-primary-key [self k primary-key]) 55 | (delete [self]) 56 | (update [self value])) 57 | 58 | (defprotocol IDBCursorWithValue 59 | (value [self])) 60 | 61 | (defprotocol ReadableObjectStore 62 | (count [self query]) 63 | (key-path [self]) 64 | (get [self key]) 65 | (get-key [self key]) 66 | (get-all [self query count]) 67 | (get-all-keys [self query count]) 68 | (open-cursor [self query direction]) 69 | (open-key-cursor [self query direction])) 70 | 71 | (defprotocol IDBObjectStore 72 | (index-names [self]) 73 | (auto-increment? [self]) 74 | (add [self value key]) 75 | (clear [self]) 76 | (create-index [self index-name key-path object-parameters]) 77 | (delete-item [self k]) ;;; under one polymorphic type? 78 | (delete-index [self index-name]) 79 | (index [self index-name]) 80 | (put [self item key])) 81 | 82 | (defprotocol BelongsToObjectStore 83 | (idb-object-store [self])) 84 | 85 | (defprotocol IDBIndex 86 | (auto-locale? [self]) 87 | (locale [self]) 88 | (multi-entry? [self]) 89 | (unique? [self])) 90 | 91 | (defprotocol IDBTransaction 92 | (durability [self]) 93 | (mode [self]) 94 | (object-store [self name]) 95 | (abort [self]) 96 | (commit [self])) 97 | 98 | (defprotocol BelongsToTransaction 99 | (idb-transaction [self])) 100 | 101 | (defprotocol IDBVersionChangeEvent 102 | (new-version [self]) 103 | (old-version [self])) 104 | 105 | (defprotocol EventTarget 106 | (target [self])) 107 | -------------------------------------------------------------------------------- /src/indexed/db/impl/request.cljs: -------------------------------------------------------------------------------- 1 | (ns ^:no-doc indexed.db.impl.request 2 | (:require [indexed.db.impl.protocols :as impl])) 3 | 4 | (deftype Request [request] 5 | impl/EventTarget 6 | (target [_] request) 7 | 8 | impl/HasErrors 9 | (error 10 | [_] 11 | (.-error request)) 12 | 13 | impl/BelongsToSource 14 | (source 15 | [_] 16 | (.-source request)) 17 | 18 | impl/IDBRequest 19 | (result 20 | [_] 21 | (.-result request)) 22 | (ready-state 23 | [_] 24 | (.-readyState request)) 25 | 26 | impl/BelongsToTransaction 27 | (idb-transaction 28 | [_] 29 | (.-transaction request))) 30 | 31 | (defn request? 32 | [x] 33 | (satisfies? impl/IDBRequest x)) 34 | 35 | (defn create-request 36 | [request] 37 | (Request. request)) 38 | 39 | (defn result 40 | [db-request] 41 | (impl/result db-request)) 42 | 43 | (defn ready-state 44 | [db-request] 45 | (impl/ready-state db-request)) 46 | -------------------------------------------------------------------------------- /src/indexed/db/impl/store.cljs: -------------------------------------------------------------------------------- 1 | (ns ^:no-doc indexed.db.impl.store 2 | (:require [indexed.db.impl.key-range :as key-range] 3 | [indexed.db.impl.request :as request] 4 | [indexed.db.impl.protocols :as impl]) 5 | (:refer-clojure :exclude [count get])) 6 | 7 | (defn key* 8 | [x] 9 | (if (key-range/key-range? x) 10 | (key-range/idb-key-range x) 11 | x)) 12 | 13 | (deftype BaseObjectStore [store] 14 | INamed 15 | (-name [_] (.-name store)) 16 | 17 | impl/ReadableObjectStore 18 | (key-path 19 | [_] 20 | (let [kp (.-keyPath store)] 21 | (if (array? kp) 22 | (array-seq kp) 23 | kp))) 24 | (count 25 | [_ query] 26 | (if (some? query) 27 | (request/create-request (.count store (key* query))) 28 | (request/create-request (.count store)))) 29 | (get 30 | [_ k] 31 | (request/create-request (.get store (key* k)))) 32 | (get-key 33 | [_ k] 34 | (request/create-request (.getKey store (key* k)))) 35 | (get-all 36 | [_ query count] 37 | (cond 38 | (and query count) (request/create-request (.getAll store (key* query) count)) 39 | (some? query) (request/create-request (.getAll store (key* query))) 40 | :else (request/create-request (.getAll store)))) 41 | (get-all-keys 42 | [_ query count] 43 | (cond 44 | (and query count) (request/create-request (.getAllKeys store (key* query) count)) 45 | (some? query) (request/create-request (.getAllKeys store (key* query))) 46 | :else (request/create-request (.getAllKeys store)))) 47 | (open-cursor 48 | [_ query direction] 49 | (cond 50 | (and query direction) (request/create-request (.openCursor store (key* query) direction)) 51 | (some? query) (request/create-request (.openCursor store (key* query))) 52 | :else (request/create-request (.openCursor store)))) 53 | (open-key-cursor 54 | [_ query direction] 55 | (cond 56 | (and query direction) (request/create-request (.openKeyCursor store (key* query) direction)) 57 | (some? query) (request/create-request (.openKeyCursor store (key* query))) 58 | :else (request/create-request (.openKeyCursor store))))) 59 | 60 | (defn key-path 61 | [store] 62 | (impl/key-path store)) 63 | 64 | (defn count 65 | ([store query] 66 | (impl/count store query)) 67 | ([store] 68 | (impl/count store nil))) 69 | 70 | (defn get 71 | [store key] 72 | (impl/get store key)) 73 | 74 | (defn get-key 75 | [store key] 76 | (impl/get-key store key)) 77 | 78 | (defn get-all 79 | ([store query count] 80 | (impl/get-all store query count)) 81 | ([store query] 82 | (get-all store query nil)) 83 | ([store] 84 | (get-all store nil nil))) 85 | 86 | (defn get-all-keys 87 | ([store query count] 88 | (impl/get-all-keys store query count)) 89 | ([store query] 90 | (get-all-keys store query nil)) 91 | ([store] 92 | (get-all-keys store nil nil))) 93 | 94 | (defn open-cursor 95 | ([store query direction] 96 | (impl/open-cursor store query direction)) 97 | ([store query] 98 | (open-cursor store query nil)) 99 | ([store] 100 | (open-cursor store nil nil))) 101 | 102 | (defn open-key-cursor 103 | ([store query direction] 104 | (impl/open-key-cursor store query direction)) 105 | ([store query] 106 | (open-key-cursor store query nil)) 107 | ([store] 108 | (open-key-cursor store nil nil))) 109 | 110 | (deftype Index [object-store idb-index] 111 | INamed 112 | (-name [_] (name object-store)) 113 | 114 | impl/ReadableObjectStore 115 | (key-path [_] (key-path object-store)) 116 | (count [_ query] (count object-store query)) 117 | (get [_ key] (get object-store key)) 118 | (get-key [_ key] (get-key object-store key)) 119 | (get-all [_ query count] (get-all object-store query count)) 120 | (get-all-keys [_ query count] (get-all-keys object-store query count)) 121 | (open-cursor [_ query direction] (open-cursor object-store query direction)) 122 | (open-key-cursor [_ query direction] (open-key-cursor object-store query direction)) 123 | 124 | impl/IDBIndex 125 | (auto-locale? [_] (.-isAutoLocale idb-index)) 126 | (locale [_] (.-locale idb-index)) 127 | (multi-entry? [_] (.-multiEntry idb-index)) 128 | (unique? [_] (.-unique idb-index)) 129 | 130 | impl/BelongsToObjectStore 131 | (idb-object-store [_] (.-objectStore idb-index))) 132 | 133 | (defn index? 134 | [x] 135 | (satisfies? impl/IDBIndex x)) 136 | 137 | (defn create-index* 138 | [idb-index] 139 | (-> idb-index 140 | (BaseObjectStore.) 141 | (Index. idb-index))) 142 | 143 | (defn auto-locale? 144 | [index] 145 | (impl/auto-locale? index)) 146 | 147 | (defn locale 148 | [index] 149 | (impl/locale index)) 150 | 151 | (defn multi-entry? 152 | [index] 153 | (impl/multi-entry? index)) 154 | 155 | (defn unique? 156 | [index] 157 | (impl/unique? index)) 158 | 159 | (defn clj->index-parameters 160 | [{:keys [unique? multi-entry? locale]}] 161 | (let [params #js {}] 162 | (when (some? unique?) 163 | (set! (.-unique params) unique?)) 164 | (when (some? multi-entry?) 165 | (set! (.-multiEntry params) multi-entry?)) 166 | (when locale 167 | (set! (.-locale params) locale)) 168 | params)) 169 | 170 | (deftype ObjectStore [object-store idb-store] 171 | INamed 172 | (-name [_] (name object-store)) 173 | 174 | impl/BelongsToTransaction 175 | (idb-transaction [_] (.-transaction idb-store)) 176 | 177 | impl/ReadableObjectStore 178 | (key-path [_] (key-path object-store)) 179 | (count [_ query] (count object-store query)) 180 | (get [_ key] (get object-store key)) 181 | (get-key [_ key] (get-key object-store key)) 182 | (get-all [_ query count] (get-all object-store query count)) 183 | (get-all-keys [_ query count] (get-all-keys object-store query count)) 184 | (open-cursor [_ query direction] (open-cursor object-store query direction)) 185 | (open-key-cursor [_ query direction] (open-key-cursor object-store query direction)) 186 | 187 | impl/IDBObjectStore 188 | (index-names [_] (array-seq (.-indexNames idb-store))) 189 | (auto-increment? [_] (.-autoIncrement idb-store)) 190 | (add 191 | [_ value key] 192 | (if (some? key) 193 | (request/create-request (.add idb-store value key)) 194 | (request/create-request (.add idb-store value)))) 195 | (clear 196 | [_] 197 | (request/create-request (.clear idb-store))) 198 | (create-index 199 | [_ index-name key-path object-parameters] 200 | (let [key-path* (if (coll? key-path) 201 | (apply array key-path) 202 | key-path) 203 | idb-index (.createIndex idb-store index-name key-path* (clj->index-parameters object-parameters))] 204 | (create-index* idb-index))) 205 | (delete-item 206 | [_ k] 207 | (request/create-request 208 | (.delete idb-store (key* k)))) 209 | (delete-index 210 | [_ index-name] 211 | (request/create-request 212 | (.deleteIndex idb-store index-name))) 213 | (index 214 | [_ index-name] 215 | (create-index* 216 | (.index idb-store index-name))) 217 | (put 218 | [_ item key] 219 | (request/create-request 220 | (if (some? key) 221 | (.put idb-store item key) 222 | (.put idb-store item))))) 223 | 224 | (defn store? 225 | [x] 226 | (satisfies? impl/IDBObjectStore x)) 227 | 228 | (defn create-object-store 229 | [idb-store] 230 | (-> idb-store 231 | (BaseObjectStore.) 232 | (ObjectStore. idb-store))) 233 | 234 | (defn index-names 235 | [store] 236 | (impl/index-names store)) 237 | 238 | (defn auto-increment? 239 | [store] 240 | (impl/auto-increment? store)) 241 | 242 | (defn add 243 | ([store value key] 244 | (impl/add store value key)) 245 | ([store value] 246 | (add store value nil))) 247 | 248 | (defn clear 249 | [store] 250 | (impl/clear store)) 251 | 252 | (defn create-index 253 | ([store index-name key-path object-parameters] 254 | (impl/create-index store index-name key-path object-parameters)) 255 | ([store index-name key-path] 256 | (create-index store index-name key-path {}))) 257 | 258 | (defn delete 259 | [store k] 260 | (impl/delete-item store k)) 261 | 262 | (defn delete-index 263 | [store index-name] 264 | (impl/delete-index store index-name)) 265 | 266 | (defn index 267 | [store index-name] 268 | (impl/index store index-name)) 269 | 270 | (defn put 271 | ([store item] 272 | (impl/put store item nil)) 273 | ([store item key] 274 | (impl/put store item key))) 275 | 276 | (defn get-object-store 277 | [index] 278 | (create-object-store 279 | (impl/idb-object-store index))) 280 | -------------------------------------------------------------------------------- /src/indexed/db/impl/transaction.cljs: -------------------------------------------------------------------------------- 1 | (ns ^:no-doc indexed.db.impl.transaction 2 | (:require [indexed.db.impl.store :as store] 3 | [indexed.db.impl.protocols :as impl])) 4 | 5 | (deftype Transaction [idb-transaction] 6 | impl/BelongsToDatabase 7 | (idb-database [_] (.-db idb-transaction)) 8 | 9 | impl/EventTarget 10 | (target [_] idb-transaction) 11 | 12 | impl/HasErrors 13 | (error [_] (.-error idb-transaction)) 14 | 15 | impl/IDBTransaction 16 | (durability [_] (.-durability idb-transaction)) 17 | 18 | (mode [_] (.-mode idb-transaction)) 19 | 20 | (object-store 21 | [_ name] 22 | (store/create-object-store 23 | (.objectStore idb-transaction name))) 24 | 25 | (abort [_] (.abort idb-transaction)) 26 | 27 | (commit [_] (.commit idb-transaction))) 28 | 29 | (defn transaction? 30 | [x] 31 | (satisfies? impl/IDBTransaction x)) 32 | 33 | (defn create-transaction 34 | [idb-transaction] 35 | (Transaction. idb-transaction)) 36 | 37 | (defn object-store 38 | [txn name] 39 | (impl/object-store txn name)) 40 | 41 | (defn durability 42 | [txn] 43 | (impl/durability txn)) 44 | 45 | (defn mode 46 | [txn] 47 | (impl/mode txn)) 48 | 49 | (defn abort 50 | [txn] 51 | (impl/abort txn)) 52 | 53 | (defn commit 54 | [txn] 55 | (impl/commit txn)) 56 | 57 | (defn transaction 58 | [belongs-to-txn] 59 | (create-transaction (impl/idb-transaction belongs-to-txn))) 60 | -------------------------------------------------------------------------------- /src/indexed/db/spec.cljs: -------------------------------------------------------------------------------- 1 | (ns indexed.db.spec 2 | (:require [cljs.spec.alpha :as s] 3 | [indexed.db.impl.cursor :as cursor] 4 | [indexed.db.impl.database :as database] 5 | [indexed.db.impl.events :as events] 6 | [indexed.db.impl.factory :as factory] 7 | [indexed.db.impl.key-range :as key-range] 8 | [indexed.db.impl.request :as request] 9 | [indexed.db.impl.store :as store] 10 | [indexed.db.impl.transaction :as txn] 11 | [indexed.db.impl.protocols :as impl])) 12 | 13 | (s/def ::factory factory/factory?) 14 | 15 | (s/def ::request request/request?) 16 | 17 | (s/def ::open-db-request ::request) 18 | 19 | (s/def ::name string?) 20 | 21 | (s/def ::version nat-int?) 22 | 23 | (s/def ::dom-exception #(instance? js/DOMException %)) 24 | 25 | (s/def ::database-record (s/keys :req-un [::name ::version])) 26 | 27 | (s/def ::databases-callback 28 | (s/fspec 29 | :args (s/cat :result (s/or :error ::dom-exception :databases (s/coll-of ::database-record))) 30 | :ret any?)) 31 | 32 | (s/def ::database database/database?) 33 | 34 | (s/def ::belongs-to-database #(satisfies? impl/BelongsToDatabase %)) 35 | 36 | (s/def ::js-idb #(instance? js/IDBDatabase %)) 37 | 38 | (s/def ::transaction txn/transaction?) 39 | 40 | (s/def ::object-store-names (s/coll-of ::name)) 41 | 42 | (s/def ::mode #{"readonly" "readwrite" "readwriteflush"}) 43 | 44 | (s/def ::durability #{"default" "strict" "relaxed"}) 45 | 46 | (s/def ::transaction-options (s/keys :req-un [::durability])) 47 | 48 | (s/def ::js-cursor #(instance? js/IDBCursor %)) 49 | 50 | (s/def ::cursor cursor/cursor?) 51 | 52 | (s/def ::js-cursor-with-value #(instance? js/IDBCursorWithValue %)) 53 | 54 | (s/def ::cursor-with-value cursor/cursor-with-value?) 55 | 56 | (s/def ::direction #{"next" "nextunique" "prev" "prevunique"}) 57 | 58 | (s/def ::js-request #(instance? js/IDBRequest %)) 59 | 60 | (s/def ::has-errors #(satisfies? impl/HasErrors %)) 61 | 62 | (s/def ::ready-state #{"pending" "done"}) 63 | 64 | (s/def ::js-version-change-event #(instance? js/IDBVersionChangeEvent %)) 65 | 66 | (s/def ::version-change-event events/version-change-event?) 67 | 68 | (s/def ::belongs-to-request #(satisfies? impl/BelongsToRequest %)) 69 | 70 | (s/def ::event-target events/event-target?) 71 | 72 | (s/def ::key-range key-range/key-range?) 73 | 74 | (s/def ::js-transaction #(instance? js/IDBTransaction %)) 75 | 76 | (s/def ::belongs-to-transaction #(satisfies? impl/BelongsToTransaction %)) 77 | 78 | (s/def ::key-path (s/or :seq (s/coll-of string?) :single string?)) 79 | 80 | (s/def ::store store/store?) 81 | 82 | (s/def ::index store/index?) 83 | 84 | (s/def ::readable-store (s/or :store ::store :index ::index)) 85 | 86 | (s/def ::key any?) 87 | 88 | (s/def ::query (s/or :key ::key :key-range ::key-range)) 89 | 90 | (s/def ::unique? boolean?) 91 | 92 | (s/def ::multi-entry? boolean?) 93 | 94 | (s/def ::locale string?) 95 | 96 | (s/def ::index-options (s/keys :opt-un [::unique? ::multi-entry? ::locale])) 97 | 98 | (s/def ::auto-increment? boolean?) 99 | 100 | (s/def ::store-options (s/keys :opt-un [::key-path ::auto-increment?])) 101 | 102 | (s/def ::js-store #(instance? js/IDBObjectStore %)) 103 | -------------------------------------------------------------------------------- /test/indexed/db/cursor_test.cljs: -------------------------------------------------------------------------------- 1 | (ns indexed.db.cursor-test 2 | (:require [cljs.test :refer [deftest is async use-fixtures]] 3 | [indexed.db :as indexed.db] 4 | [indexed.db.test-util :as util])) 5 | 6 | (defonce *db (atom nil)) 7 | 8 | (def database-name "indexed.db.cursor-test") 9 | 10 | (def database-version 1) 11 | 12 | (use-fixtures :once 13 | {:before 14 | #(async 15 | done 16 | (util/create-task-db *db done {:db/name database-name :db/version database-version}))}) 17 | 18 | (use-fixtures :each 19 | {:before 20 | #(async 21 | done 22 | (util/seed-tasks @*db done {:taskTitle "Zoop" 23 | :hours 24 24 | :minutes 0 25 | :day 23 26 | :month "March" 27 | :year 2022 28 | :notified "no"})) 29 | :after 30 | (async 31 | done 32 | (util/reset-tasks @*db done))}) 33 | 34 | (deftest test-store-source 35 | (async 36 | done 37 | (-> (util/store @*db "toDoList") 38 | (indexed.db/open-cursor) 39 | (indexed.db/on 40 | "success" 41 | (fn [e] 42 | (let [cursor (util/cursor-with-value e) 43 | source (indexed.db/source cursor)] 44 | (is (indexed.db/store? source)) 45 | (done))))))) 46 | 47 | (deftest test-index-source 48 | (async 49 | done 50 | (-> (util/store @*db "toDoList") 51 | (indexed.db/index "day") 52 | (indexed.db/open-cursor) 53 | (indexed.db/on 54 | "success" 55 | (fn [e] 56 | (let [cursor (util/cursor-with-value e) 57 | source (indexed.db/source cursor)] 58 | (is (indexed.db/index? source)) 59 | (done))))))) 60 | 61 | (deftest test-direction 62 | (async 63 | done 64 | (-> (util/store @*db "toDoList") 65 | (indexed.db/open-cursor) 66 | (indexed.db/on 67 | "success" 68 | (fn [e] 69 | (let [cursor (util/cursor-with-value e)] 70 | (is (= (indexed.db/direction cursor) "next")) 71 | (done))))))) 72 | 73 | (deftest test-key 74 | (async 75 | done 76 | (-> (util/store @*db "toDoList") 77 | (indexed.db/open-cursor) 78 | (indexed.db/on 79 | "success" 80 | (fn [e] 81 | (let [cursor (util/cursor-with-value e)] 82 | (is (= (indexed.db/key cursor) "Party hard")) 83 | (done))))))) 84 | 85 | (deftest test-primary-key 86 | (async 87 | done 88 | (-> (util/store @*db "toDoList") 89 | (indexed.db/open-cursor) 90 | (indexed.db/on 91 | "success" 92 | (fn [e] 93 | (let [cursor (util/cursor-with-value e)] 94 | (is (= (indexed.db/primary-key cursor) "Party hard")) 95 | (done))))))) 96 | 97 | (deftest test-get-request 98 | (async 99 | done 100 | (-> (util/store @*db "toDoList") 101 | (indexed.db/open-cursor) 102 | (indexed.db/on 103 | "success" 104 | (fn [e] 105 | (let [cursor (util/cursor-with-value e)] 106 | (is (indexed.db/request? (indexed.db/get-request cursor))) 107 | (done))))))) 108 | 109 | (deftest test-advance 110 | (async 111 | done 112 | (let [*iteration (atom 0)] 113 | (-> (util/store @*db "toDoList") 114 | (indexed.db/open-cursor) 115 | (indexed.db/on 116 | "success" 117 | (fn [e] 118 | (when-some [cursor (util/cursor-with-value e)] 119 | (indexed.db/advance cursor 1) 120 | (when (= 2 (swap! *iteration inc)) 121 | (is (= (indexed.db/key cursor) "Read that book")) 122 | (done))))))))) 123 | 124 | (deftest test-continue 125 | (async 126 | done 127 | (let [*iteration (atom 0)] 128 | (-> (util/store @*db "toDoList") 129 | (indexed.db/open-cursor) 130 | (indexed.db/on 131 | "success" 132 | (fn [e] 133 | (when-some [cursor (util/cursor-with-value e)] 134 | (indexed.db/continue cursor) 135 | (when (= 2 (swap! *iteration inc)) 136 | (is (= (indexed.db/key cursor) "Read that book")) 137 | (done))))))))) 138 | 139 | (deftest test-continue-with-key 140 | (async 141 | done 142 | (let [*iteration (atom 0)] 143 | (-> (util/store @*db "toDoList") 144 | (indexed.db/open-cursor) 145 | (indexed.db/on 146 | "success" 147 | (fn [e] 148 | (let [cursor (util/cursor-with-value e) 149 | iterated? (< 0 @*iteration)] 150 | (when-not iterated? 151 | (indexed.db/continue cursor "Walk dog")) 152 | (when (= 2 (swap! *iteration inc)) 153 | (is (= (indexed.db/key cursor) "Walk dog")) 154 | (done))))))))) 155 | 156 | (deftest test-continue-primary-key 157 | (async 158 | done 159 | (let [*iteration (atom 0) 160 | *last-primary-key (atom nil) 161 | k 23] ;;; all records that happened on the 23rd day 162 | (-> (util/store @*db "toDoList") 163 | (indexed.db/index "day") 164 | (indexed.db/open-cursor) 165 | (indexed.db/on 166 | "success" 167 | (fn [e] 168 | (let [cursor (util/cursor-with-value e) 169 | iteration (swap! *iteration inc)] 170 | (cond 171 | (= 1 iteration) (do 172 | (reset! *last-primary-key (indexed.db/primary-key cursor)) 173 | (indexed.db/continue-primary-key cursor k @*last-primary-key)) 174 | (= 2 iteration) (do 175 | (is (= (indexed.db/primary-key cursor) "Zoop")) 176 | (is (= (indexed.db/key cursor) k)) 177 | (done)))))))))) 178 | 179 | (deftest test-delete 180 | (async 181 | done 182 | (-> (util/store @*db "toDoList" "readwrite") 183 | (indexed.db/open-cursor (indexed.db/only "Zoop")) 184 | (indexed.db/on 185 | "success" 186 | (fn [e] 187 | (-> (util/cursor-with-value e) 188 | (indexed.db/delete) 189 | (indexed.db/on "success" done))))))) 190 | 191 | (deftest test-update 192 | (async 193 | done 194 | (-> (util/store @*db "toDoList" "readwrite") 195 | (indexed.db/open-cursor (indexed.db/only "Zoop")) 196 | (indexed.db/on 197 | "success" 198 | (fn [e] 199 | (let [cursor (util/cursor-with-value e) 200 | record (indexed.db/value cursor)] 201 | (set! (.-month record) "April") 202 | (-> cursor 203 | (indexed.db/update record) 204 | (indexed.db/on "success" done)))))))) 205 | -------------------------------------------------------------------------------- /test/indexed/db/database_test.cljs: -------------------------------------------------------------------------------- 1 | (ns indexed.db.database-test 2 | (:require [cljs.test :refer [deftest is async testing use-fixtures]] 3 | [indexed.db :as db] 4 | [indexed.db.test-util :as util])) 5 | 6 | (defonce *db (atom nil)) 7 | 8 | (def database-name "indexed.db.database-test") 9 | 10 | (def database-version 1) 11 | 12 | (use-fixtures :once 13 | {:before 14 | #(async 15 | done 16 | (util/test-connect 17 | database-name 18 | database-version 19 | {:success 20 | (fn [idb] 21 | (reset! *db (db/create-database idb)) 22 | (done)) 23 | 24 | :upgradeneeded 25 | (fn [idb] 26 | (let [db (db/create-database idb)] 27 | (db/create-object-store db "toDoList" {:key-path "taskTitle"}) 28 | (db/create-object-store db "deleteMe"))) 29 | 30 | :blocked 31 | (fn [] 32 | (db/close @*db)) 33 | 34 | :error 35 | (fn [] 36 | (println "Failed test connected") 37 | (done))}))}) 38 | 39 | (deftest test-create-database 40 | (is (db/database? @*db))) 41 | 42 | (deftest test-getting-name 43 | (is (= database-name (name @*db)))) 44 | 45 | (deftest test-getting-version 46 | (is (= database-version (db/version @*db)))) 47 | 48 | (deftest test-getting-store-names 49 | (let [names (db/object-store-names @*db)] 50 | (is (seq? names)) 51 | (is (= 2 (count names))) 52 | (is (seq (filter #(= "toDoList" %) names))))) 53 | 54 | (deftest test-delete-object-store 55 | (db/close @*db) ;;; close to prevent blocking 56 | (async 57 | done 58 | (util/open 59 | database-name 60 | (inc database-version) 61 | {:success 62 | (fn [idb] 63 | (reset! *db (db/create-database idb)) 64 | (let [names (db/object-store-names @*db)] 65 | (is (= 1 (count names))) 66 | (is (seq (filter #(= "toDoList" %) names))) 67 | (done))) 68 | 69 | :upgradeneeded 70 | (fn [idb] 71 | (let [db (db/create-database idb)] 72 | (db/delete-object-store db "deleteMe")))}))) 73 | 74 | (deftest test-transaction 75 | (testing "with a valid store name" 76 | (is (some? (db/transaction @*db ["toDoList"])))) 77 | (testing "with an invalid store name" 78 | (is (thrown? js/DOMException (db/transaction @*db ["foo"])))) 79 | (testing "with an invalid mode" 80 | (is (thrown? js/TypeError (db/transaction @*db ["toDoList"] "readbologna")))) 81 | (testing "with an invalid store name" 82 | (is (thrown? js/DOMException (db/transaction @*db []))))) 83 | 84 | (deftest test-transaction-on-closed-db 85 | (async 86 | done 87 | (util/test-connect 88 | (str database-name "-transaction-test") 89 | 1 90 | {:success 91 | (fn [idb] 92 | (let [db (db/create-database idb)] 93 | (db/close db) 94 | (is (thrown? js/DOMException (db/transaction db ["test-store"]))) 95 | (done))) 96 | 97 | :upgradeneeded 98 | (fn [idb] 99 | (let [db (db/create-database idb)] 100 | (db/create-object-store db "test-store")))}))) 101 | -------------------------------------------------------------------------------- /test/indexed/db/factory_test.cljs: -------------------------------------------------------------------------------- 1 | (ns indexed.db.factory-test 2 | (:require [cljs.test :refer [deftest is async use-fixtures]] 3 | [indexed.db :as factory] 4 | [indexed.db.test-util :as util])) 5 | 6 | (defonce *idb (atom nil)) 7 | 8 | (def database-name "indexed.db.factory-test") 9 | 10 | (def database-version 1) 11 | 12 | (use-fixtures :once 13 | {:before 14 | #(async 15 | done 16 | (util/test-connect 17 | database-name 18 | database-version 19 | {:success (fn [idb] 20 | (reset! *idb idb) 21 | (done)) 22 | :blocked (fn [] 23 | (.close @*idb)) 24 | :error (fn [] 25 | (println "Failed test connected") 26 | (done))}))}) 27 | 28 | (deftest test-open 29 | (is (some? @*idb))) 30 | 31 | (deftest test-cmp 32 | (is (= -1 (factory/cmp 1 2))) 33 | (is (= 0 (factory/cmp 1 1))) 34 | (is (= 1 (factory/cmp 1 0)))) 35 | 36 | (deftest test-getting-databases 37 | (async 38 | done 39 | (factory/databases (fn [databases] 40 | (is (seq? databases)) 41 | (is (seq (filter #(and (= database-name (:name %)) (= database-version (:version %))) databases))) 42 | (done))))) 43 | -------------------------------------------------------------------------------- /test/indexed/db/key_range_test.cljs: -------------------------------------------------------------------------------- 1 | (ns indexed.db.key-range-test 2 | (:require [cljs.test :refer [deftest is testing]] 3 | [indexed.db :as indexed.db])) 4 | 5 | (deftest test-lower 6 | (testing "with a bound key range" 7 | (let [bound (indexed.db/bound "a" "b")] 8 | (is (= "a" (indexed.db/lower bound))))) 9 | (testing "with an only key range" 10 | (let [only (indexed.db/only "a")] 11 | (is (= "a" (indexed.db/lower only))))) 12 | (testing "with a lower bound key range" 13 | (let [lower (indexed.db/lower-bound "a")] 14 | (is (= "a" (indexed.db/lower lower))))) 15 | (testing "with a upper bound key range" 16 | (let [upper (indexed.db/upper-bound "a")] 17 | (is (nil? (indexed.db/lower upper)))))) 18 | 19 | (deftest test-upper 20 | (testing "with a bound key range" 21 | (let [bound (indexed.db/bound "a" "b")] 22 | (is (= "b" (indexed.db/upper bound))))) 23 | (testing "with an only key range" 24 | (let [only (indexed.db/only "a")] 25 | (is (= "a" (indexed.db/upper only))))) 26 | (testing "with a lower bound key range" 27 | (let [lower (indexed.db/lower-bound "a")] 28 | (is (nil? (indexed.db/upper lower))))) 29 | (testing "with a upper bound key range" 30 | (let [upper (indexed.db/upper-bound "a")] 31 | (is (= "a" (indexed.db/upper upper)))))) 32 | 33 | (deftest test-lower-open? 34 | (testing "with a bound key range" 35 | (let [bound (indexed.db/bound "a" "b" true)] 36 | (is (indexed.db/lower-open? bound)))) 37 | (testing "with an only key range" 38 | (let [only (indexed.db/only "a")] 39 | (is (not (indexed.db/lower-open? only))))) 40 | (testing "with a lower bound key range" 41 | (let [lower (indexed.db/lower-bound "a" true)] 42 | (is (indexed.db/lower-open? lower)))) 43 | (testing "with a upper bound key range" 44 | (let [upper (indexed.db/upper-bound "a")] 45 | (is (indexed.db/lower-open? upper))))) 46 | 47 | (deftest test-upper-open? 48 | (testing "with a bound key range" 49 | (let [bound (indexed.db/bound "a" "b" true true)] 50 | (is (indexed.db/upper-open? bound)))) 51 | (testing "with an only key range" 52 | (let [only (indexed.db/only "a")] 53 | (is (not (indexed.db/upper-open? only))))) 54 | (testing "with a lower bound key range" 55 | (let [lower (indexed.db/lower-bound "a" true)] 56 | (is (indexed.db/upper-open? lower)))) 57 | (testing "with a upper bound key range" 58 | (let [upper (indexed.db/upper-bound "a" true)] 59 | (is (indexed.db/upper-open? upper))))) 60 | -------------------------------------------------------------------------------- /test/indexed/db/store_test.cljs: -------------------------------------------------------------------------------- 1 | (ns indexed.db.store-test 2 | (:require [cljs.test :refer [deftest is async use-fixtures]] 3 | [indexed.db :as indexed.db] 4 | [indexed.db.test-util :as util])) 5 | 6 | (defonce *db (atom nil)) 7 | 8 | (def database-name "indexed.db.store-test") 9 | 10 | (def database-version 1) 11 | 12 | (use-fixtures :once 13 | {:before 14 | #(async 15 | done 16 | (util/create-task-db *db done {:db/name database-name :db/version database-version}))}) 17 | 18 | (use-fixtures :each 19 | {:before 20 | #(async 21 | done 22 | (util/seed-tasks @*db done)) 23 | :after 24 | #(async 25 | done 26 | (util/reset-tasks @*db done))}) 27 | 28 | (deftest test-index-names 29 | (let [todo-list (util/store @*db "toDoList") 30 | index-names (set (indexed.db/index-names todo-list))] 31 | (is (= #{"hours" "minutes" "day" "month" "year" "notified" "deleteme"} index-names)))) 32 | 33 | (deftest test-key-path 34 | (let [todo-list (util/store @*db "toDoList")] 35 | (is (= "taskTitle" (indexed.db/key-path todo-list))))) 36 | 37 | (deftest test-key-path-as-sequence 38 | (async 39 | done 40 | (util/test-connect 41 | (str database-name "-key-test") 42 | 1 43 | {:success 44 | (fn [idb] 45 | (let [db (indexed.db/create-database idb) 46 | txn (indexed.db/transaction db ["test-store"]) 47 | test-store (indexed.db/object-store txn "test-store")] 48 | (is (= ["a" "b"] (indexed.db/key-path test-store))) 49 | (done))) 50 | 51 | :upgradeneeded 52 | (fn [idb] 53 | (let [db (indexed.db/create-database idb)] 54 | (indexed.db/create-object-store db "test-store" {:key-path ["a" "b"]})))}))) 55 | 56 | (deftest test-name 57 | (let [todo-list (util/store @*db "toDoList")] 58 | (is (= "toDoList" (name todo-list))))) 59 | 60 | (deftest test-getting-transaction 61 | (let [todo-list (util/store @*db "toDoList") 62 | txn (indexed.db/get-transaction todo-list)] 63 | (is (indexed.db/transaction? txn)))) 64 | 65 | (deftest test-auto-increment? 66 | (let [todo-list (util/store @*db "toDoList")] 67 | (is (false? (indexed.db/auto-increment? todo-list))))) 68 | 69 | (deftest test-count-all 70 | (async 71 | done 72 | (-> (indexed.db/count (util/store @*db "toDoList")) 73 | (indexed.db/on "success" (fn [e] 74 | (is (= 3 (indexed.db/result 75 | (util/event->request e)))) 76 | (done)))))) 77 | 78 | (deftest test-count-by-key 79 | (async 80 | done 81 | (-> (indexed.db/count (util/store @*db "toDoList") "Walk dog") 82 | (indexed.db/on "success" (fn [e] 83 | (is (= 1 (indexed.db/result 84 | (util/event->request e)))) 85 | (done)))))) 86 | 87 | (deftest test-delete-by-key 88 | (async 89 | done 90 | (let [txn (util/transaction @*db "readwrite") 91 | todo-list (indexed.db/object-store txn "toDoList")] 92 | (indexed.db/delete todo-list "Walk dog") 93 | (indexed.db/on txn "complete" (fn [] 94 | (is (true? true)) 95 | (done)))))) 96 | 97 | (deftest test-delete-index 98 | (indexed.db/close @*db) ;;; close to prevent blocking 99 | (async 100 | done 101 | (util/open 102 | database-name 103 | (inc database-version) 104 | {:success 105 | (fn [idb] 106 | (reset! *db (indexed.db/create-database idb)) 107 | (let [todo-list (util/store @*db "toDoList") 108 | names (set (indexed.db/index-names todo-list))] 109 | (is (false? (contains? names "deleteme"))) 110 | (done))) 111 | 112 | :upgradeneeded 113 | (fn [_ txn] 114 | (let [todo-list (indexed.db/object-store txn "toDoList")] 115 | (indexed.db/delete-index todo-list "deleteme")))}))) 116 | 117 | (deftest test-get 118 | (async 119 | done 120 | (-> (util/store @*db "toDoList") 121 | (indexed.db/get "Walk dog") 122 | (indexed.db/on "success" (fn [e] 123 | (is (= "Walk dog" (.-taskTitle (indexed.db/result 124 | (util/event->request e))))) 125 | (done)))))) 126 | 127 | (deftest test-get-key 128 | (async 129 | done 130 | (-> (util/store @*db "toDoList") 131 | (indexed.db/get-key "Walk dog") 132 | (indexed.db/on "success" (fn [e] 133 | (is (= "Walk dog" (indexed.db/result 134 | (util/event->request e)))) 135 | (done)))))) 136 | 137 | (deftest test-get-all-no-key 138 | (async 139 | done 140 | (-> (util/store @*db "toDoList") 141 | (indexed.db/get-all) 142 | (indexed.db/on "success" (fn [e] 143 | (is (= 3 (count (indexed.db/result 144 | (util/event->request e))))) 145 | (done)))))) 146 | 147 | (deftest test-get-all-with-key 148 | (async 149 | done 150 | (-> (util/store @*db "toDoList") 151 | (indexed.db/get-all (indexed.db/bound "Party hard" "Walk dog")) 152 | (indexed.db/on "success" (fn [e] 153 | (is (= 3 (count (indexed.db/result 154 | (util/event->request e))))) 155 | (done)))))) 156 | 157 | (deftest test-get-all-with-key-and-count 158 | (async 159 | done 160 | (-> (util/store @*db "toDoList") 161 | (indexed.db/get-all (indexed.db/bound "Party hard" "Walk dog") 2) 162 | (indexed.db/on "success" (fn [e] 163 | (is (= 2 (count (indexed.db/result 164 | (util/event->request e))))) 165 | (done)))))) 166 | 167 | (deftest test-get-all-keys-no-key 168 | (async 169 | done 170 | (-> (util/store @*db "toDoList") 171 | (indexed.db/get-all-keys) 172 | (indexed.db/on "success" (fn [e] 173 | (is (= 3 (count (indexed.db/result 174 | (util/event->request e))))) 175 | (done)))))) 176 | 177 | (deftest test-get-all-keys-with-key 178 | (async 179 | done 180 | (-> (util/store @*db "toDoList") 181 | (indexed.db/get-all-keys (indexed.db/bound "Party hard" "Walk dog")) 182 | (indexed.db/on "success" (fn [e] 183 | (is (= 3 (count (indexed.db/result 184 | (util/event->request e))))) 185 | (done)))))) 186 | 187 | (deftest test-get-all-keys-with-key-and-count 188 | (async 189 | done 190 | (-> (util/store @*db "toDoList") 191 | (indexed.db/get-all-keys (indexed.db/bound "Party hard" "Walk dog") 2) 192 | (indexed.db/on "success" (fn [e] 193 | (is (= 2 (count (indexed.db/result 194 | (util/event->request e))))) 195 | (done)))))) 196 | 197 | (deftest test-getting-an-index 198 | (let [todo-list (util/store @*db "toDoList") 199 | index (indexed.db/index todo-list "hours")] 200 | (is (indexed.db/index? index)) 201 | (is (false? (indexed.db/unique? index))) 202 | (is (false? (indexed.db/multi-entry? index))) 203 | (is (indexed.db/store? (indexed.db/get-object-store index))) 204 | #_(is (some? (indexed.db/locale index))) ;;; commentig out because locale properties are non-standard 205 | #_(is (true? (indexed.db/auto-locale? index))))) 206 | 207 | (deftest test-open-cursor-no-args 208 | (async 209 | done 210 | (let [todo-list (util/store @*db "toDoList")] 211 | (-> (indexed.db/open-cursor todo-list) 212 | (indexed.db/on "success" (fn [e] 213 | (let [value (-> (util/event->request e) 214 | (indexed.db/result) 215 | (indexed.db/create-cursor-with-value) 216 | (indexed.db/value))] 217 | (is (= "Party hard" (.-taskTitle value))) 218 | (done)))))))) 219 | 220 | (deftest test-open-cursor-with-query 221 | (async 222 | done 223 | (let [todo-list (util/store @*db "toDoList") 224 | *iteration (atom 0)] 225 | (-> (indexed.db/open-cursor todo-list (indexed.db/bound "Read that book" "Walk dog")) 226 | (indexed.db/on "success" (fn [e] 227 | (let [cursor (util/cursor-with-value e) 228 | iteration (swap! *iteration inc)] 229 | (if-not (< iteration 3) 230 | (do 231 | (is (nil? cursor)) 232 | (done)) 233 | (do 234 | (cond 235 | (= iteration 1) (is (= "Read that book" (.-taskTitle (indexed.db/value cursor)))) 236 | (= iteration 2) (is (= "Walk dog" (.-taskTitle (indexed.db/value cursor)))) 237 | :else (throw (ex-info "Unexpected cursor iteration" {}))) 238 | (indexed.db/continue cursor)))))))))) 239 | 240 | (deftest test-open-cursor-with-query-and-direction 241 | (async 242 | done 243 | (let [todo-list (util/store @*db "toDoList") 244 | *iteration (atom 0)] 245 | (-> (indexed.db/open-cursor todo-list (indexed.db/upper-bound "Read that book") "prev") 246 | (indexed.db/on "success" (fn [e] 247 | (let [cursor (util/cursor-with-value e) 248 | iteration (swap! *iteration inc)] 249 | (if-not (< iteration 3) 250 | (do 251 | (is (nil? cursor)) 252 | (done)) 253 | (do 254 | (cond 255 | (= iteration 1) (is (= "Read that book" (.-taskTitle (indexed.db/value cursor)))) 256 | (= iteration 2) (is (= "Party hard" (.-taskTitle (indexed.db/value cursor)))) 257 | :else (throw (ex-info "Unexpected cursor iteration" {}))) 258 | (indexed.db/continue cursor)))))))))) 259 | 260 | (deftest test-open-key-cursor-no-args 261 | (async 262 | done 263 | (let [todo-list (util/store @*db "toDoList")] 264 | (-> (indexed.db/open-key-cursor todo-list) 265 | (indexed.db/on "success" (fn [e] 266 | (let [k (indexed.db/key (util/cursor e))] 267 | (is (= "Party hard" k)) 268 | (done)))))))) 269 | 270 | (deftest test-open-key-cursor-with-query 271 | (async 272 | done 273 | (let [todo-list (util/store @*db "toDoList") 274 | *iteration (atom 0)] 275 | (-> (indexed.db/open-key-cursor todo-list (indexed.db/bound "Read that book" "Walk dog")) 276 | (indexed.db/on "success" (fn [e] 277 | (let [cursor (util/cursor e) 278 | iteration (swap! *iteration inc)] 279 | (if-not (< iteration 3) 280 | (do 281 | (is (nil? cursor)) 282 | (done)) 283 | (do 284 | (cond 285 | (= iteration 1) (is (= "Read that book" (indexed.db/key cursor))) 286 | (= iteration 2) (is (= "Walk dog" (indexed.db/key cursor))) 287 | :else (throw (ex-info "Unexpected cursor iteration" {}))) 288 | (indexed.db/continue cursor)))))))))) 289 | 290 | (deftest test-open-key-cursor-with-query-and-direction 291 | (async 292 | done 293 | (let [todo-list (util/store @*db "toDoList") 294 | *iteration (atom 0)] 295 | (-> (indexed.db/open-key-cursor todo-list (indexed.db/upper-bound "Read that book") "prev") 296 | (indexed.db/on "success" (fn [e] 297 | (let [cursor (util/cursor e) 298 | iteration (swap! *iteration inc)] 299 | (if-not (< iteration 3) 300 | (do 301 | (is (nil? cursor)) 302 | (done)) 303 | (do 304 | (cond 305 | (= iteration 1) (is (= "Read that book" (indexed.db/key cursor))) 306 | (= iteration 2) (is (= "Party hard" (indexed.db/key cursor))) 307 | :else (throw (ex-info "Unexpected cursor iteration" {}))) 308 | (indexed.db/continue cursor)))))))))) 309 | 310 | (deftest test-put 311 | (async 312 | done 313 | (let [todo-list (util/store @*db "toDoList" "readwrite") 314 | task-req (indexed.db/get todo-list "Walk dog")] 315 | (indexed.db/on 316 | task-req 317 | "success" 318 | (fn [e] 319 | (let [request (util/event->request e) 320 | task (indexed.db/result request)] 321 | (set! (.-notified task) "yes") 322 | (-> (indexed.db/put todo-list task) 323 | (indexed.db/on "success" done)))))))) 324 | -------------------------------------------------------------------------------- /test/indexed/db/test_util.cljs: -------------------------------------------------------------------------------- 1 | (ns indexed.db.test-util 2 | (:require [indexed.db :as db])) 3 | 4 | (defn handle-upgrade 5 | [fn-2] 6 | (fn [e] 7 | (let [event (db/create-version-change-event e) 8 | request (db/get-request event)] 9 | (fn-2 (db/result request) (db/get-transaction request))))) 10 | 11 | (defn open 12 | [db-name db-version {:keys [upgradeneeded success]}] 13 | (let [open-request (db/open db-name db-version)] 14 | (-> (db/on open-request "error" #(throw %)) 15 | (db/on "upgradeneeded" (handle-upgrade upgradeneeded)) 16 | (db/on "success" #(success (db/result open-request)))))) 17 | 18 | (defn test-connect 19 | [db-name db-version {:keys [success upgradeneeded blocked error] :or {upgradeneeded constantly error #(throw %) blocked #(throw "Blocked")}}] 20 | (-> (db/delete-database db-name) 21 | (db/on "error" error) 22 | (db/on "blocked" blocked) 23 | (db/on "success" 24 | (fn [] 25 | (open db-name db-version {:success success :upgradeneeded upgradeneeded}))))) 26 | 27 | (defn transaction 28 | ([db mode] 29 | (db/transaction db (db/object-store-names db) mode)) 30 | ([db] 31 | (transaction db "readonly"))) 32 | 33 | (defn store 34 | ([db store-name mode] 35 | (db/object-store (transaction db mode) store-name)) 36 | ([db store-name] 37 | (store db store-name "readonly"))) 38 | 39 | (defn add-many 40 | [object-store done items] 41 | (let [*counter (atom 0)] 42 | (loop [item (first items) 43 | rest (next items)] 44 | (when (some? item) 45 | (db/on (db/add object-store (clj->js item)) "success" 46 | (fn [] 47 | (when (= (count items) (swap! *counter inc)) 48 | (done)))) 49 | (recur (first rest) (next rest)))))) 50 | 51 | ;;; Fixture helpers 52 | 53 | (defn create-task-db 54 | "Create a task db used for testing. This schema matches 55 | the todo list store modeled at MDN. Result of connection will be stored in the *db atom" 56 | [*db done {:db/keys [name version]}] 57 | (test-connect 58 | name 59 | version 60 | {:success 61 | (fn [idb] 62 | (reset! *db (db/create-database idb)) 63 | (done)) 64 | 65 | :upgradeneeded 66 | (fn [idb] 67 | (let [db (db/create-database idb) 68 | store (db/create-object-store db "toDoList" {:key-path "taskTitle"})] 69 | (db/create-index store "hours" "hours" {:unique? false}) 70 | (db/create-index store "minutes" "minutes" {:unique? false}) 71 | (db/create-index store "day" "day" {:unique? false}) 72 | (db/create-index store "month" "month" {:unique? false}) 73 | (db/create-index store "year" "year" {:unique? false}) 74 | (db/create-index store "notified" "notified" {:unique? false}) 75 | (db/create-index store "deleteme" "deleteme" {:unique? false}))) 76 | 77 | :blocked 78 | (fn [] 79 | (db/close @*db)) 80 | 81 | :error 82 | (fn [] 83 | (println "Failed test connection") 84 | (done))})) 85 | 86 | (defn seed-tasks 87 | [db done & tasks] 88 | (-> (store db "toDoList" "readwrite") 89 | (add-many 90 | done 91 | (concat [{:taskTitle "Walk dog" 92 | :hours 19 93 | :minutes 30 94 | :day 24 95 | :month "December" 96 | :year 2013 97 | :notified "no"} 98 | {:taskTitle "Party hard" 99 | :hours 24 100 | :minutes 0 101 | :day 23 102 | :month "March" 103 | :year 2022 104 | :notified "no"} 105 | {:taskTitle "Read that book" 106 | :hours 13 107 | :minutes 0 108 | :day 22 109 | :month "March" 110 | :year 2022 111 | :notified "no"}] tasks)))) 112 | 113 | (defn reset-tasks 114 | [db done] 115 | (-> (store db "toDoList" "readwrite") 116 | (db/clear) 117 | (db/on "success" done))) 118 | 119 | (defn event->request 120 | [e] 121 | (indexed.db/create-request (.-target e))) 122 | 123 | (defn cursor-with-value 124 | "Create a cursor with value from an event" 125 | [e] 126 | (some-> (event->request e) 127 | (indexed.db/result) 128 | (indexed.db/create-cursor-with-value))) 129 | 130 | (defn cursor 131 | "Create a cursor with value from an event" 132 | [e] 133 | (some-> (event->request e) 134 | (indexed.db/result) 135 | (indexed.db/create-cursor))) 136 | -------------------------------------------------------------------------------- /test/indexed/db/transaction_test.cljs: -------------------------------------------------------------------------------- 1 | (ns indexed.db.transaction-test 2 | (:require [cljs.test :refer [deftest is async use-fixtures]] 3 | [indexed.db :as indexed.db] 4 | [indexed.db.test-util :as util])) 5 | 6 | (defonce *db (atom nil)) 7 | 8 | (def database-name "indexed.db.txn-test") 9 | 10 | (def database-version 1) 11 | 12 | (use-fixtures :once 13 | {:before 14 | #(async 15 | done 16 | (util/create-task-db *db done {:db/name database-name :db/version database-version}))}) 17 | 18 | (use-fixtures :each 19 | {:before 20 | #(async 21 | done 22 | (util/seed-tasks @*db done)) 23 | :after 24 | (async 25 | done 26 | (util/reset-tasks @*db done))}) 27 | 28 | (deftest test-db 29 | (let [transaction (indexed.db/transaction @*db ["toDoList"])] 30 | (is (indexed.db/database? (indexed.db/db transaction))))) 31 | 32 | (deftest test-durability 33 | (let [transaction (indexed.db/transaction @*db ["toDoList"])] 34 | (is (= "default" (indexed.db/durability transaction))))) 35 | 36 | (deftest test-mode 37 | (let [transaction (indexed.db/transaction @*db ["toDoList"])] 38 | (is (= "readonly" (indexed.db/mode transaction))))) 39 | 40 | (deftest test-object-store-names 41 | (let [transaction (indexed.db/transaction @*db ["toDoList"])] 42 | (is (= ["toDoList"] (indexed.db/object-store-names transaction))))) 43 | 44 | (deftest test-abort 45 | (async 46 | done 47 | (let [transaction (indexed.db/transaction @*db ["toDoList"] "readwrite") 48 | todo-list (indexed.db/object-store transaction "toDoList") 49 | request (indexed.db/add todo-list #js {:taskTitle "Testing"})] 50 | (-> transaction 51 | (indexed.db/on 52 | "abort" 53 | (fn [] 54 | (is (instance? js/DOMException (indexed.db/error request))) 55 | (done))) 56 | (indexed.db/abort))))) 57 | 58 | (deftest test-commit 59 | (async 60 | done 61 | (let [transaction (indexed.db/transaction @*db ["toDoList"] "readwrite") 62 | todo-list (indexed.db/object-store transaction "toDoList")] 63 | (indexed.db/add todo-list #js {:taskTitle "Testing"}) 64 | (indexed.db/commit 65 | (indexed.db/on transaction "complete" done))))) 66 | --------------------------------------------------------------------------------