├── .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 | [](https://cljdoc.org/d/com.github.brianium/indexed.db/CURRENT) [](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 |
--------------------------------------------------------------------------------