git.iko.soy/purescript-indexed-DB
on master help sign in
37 files changed+3285−2312
since splitsnapshots
unifiedsplit
.eslintignore+1−1 .github/browserstack.png .gitignore+3 .travis.yml+37 README.md+75−251 bower.json−19 index.js+4 karma.conf.js−25 package.json+8−39 packages.dhall+105 spago.dhall+42 src/Database/IndexedDB/Core.js+6−6 src/Database/IndexedDB/Core.purs+163−58 src/Database/IndexedDB/IDBCursor.js+95−32 src/Database/IndexedDB/IDBCursor.purs+154−108 src/Database/IndexedDB/IDBDatabase.js+72−29 src/Database/IndexedDB/IDBDatabase.purs+160−74 src/Database/IndexedDB/IDBFactory.js+42−21 src/Database/IndexedDB/IDBFactory.purs+61−21 src/Database/IndexedDB/IDBIndex.js+171 src/Database/IndexedDB/IDBIndex.purs+199−7 src/Database/IndexedDB/IDBIndex/Internal.js−109 src/Database/IndexedDB/IDBIndex/Internal.purs−145 src/Database/IndexedDB/IDBKey.purs+11−1 src/Database/IndexedDB/IDBKey/Internal.js+3−3 src/Database/IndexedDB/IDBKey/Internal.purs+114−93 src/Database/IndexedDB/IDBKeyRange.js+9−9 src/Database/IndexedDB/IDBKeyRange.purs+120−60 src/Database/IndexedDB/IDBObjectStore.js+84−38 src/Database/IndexedDB/IDBObjectStore.purs+217−109 src/Database/IndexedDB/IDBTransaction.js+50−17 src/Database/IndexedDB/IDBTransaction.purs+111−51 src/Main.purs−24 src/Test/Spec/Mocha.js−46 src/Test/Spec/Mocha.purs−41 test/Main.purs+1087−875 yarn.lock+81
.eslintignore+1−1
@@ -1 +1 @@
1−/dist/*
1+dist/*
.github/browserstack.pngnew file

Binary file not shown.

.gitignore+3
@@ -71,8 +71,11 @@ typings/
7171# Generated files
7272output
7373generated-docs
74+releases
7475.pulp-cache
7576.psc*
7677.purs*
7778.psa*
7879dist
80+.spago
81+test.js
.travis.ymlnew file+37
@@ -0,0 +1,37 @@
1+language: node_js
2+node_js:
3+ - node
4+
5+addons:
6+ browserstack:
7+ username: $BROWSERSTACK_USERNAME
8+ access_key: $BROWSERSTACK_ACCESSKEY
9+
10+before_install:
11+ - export PATH=./node_modules/.bin:$PATH
12+
13+install:
14+ - npm install
15+ - bower install
16+
17+script:
18+ - npm run test:browserstack
19+
20+after_script:
21+ - npm run prepare:release
22+
23+deploy:
24+ - provider: releases
25+ api_key: $GITHUB_TOKEN
26+ skip_cleanup: true
27+ file_glob: true
28+ file: releases/github/*
29+ on:
30+ tags: true
31+ branch: master
32+
33+ - provider: script
34+ script: deploy.sh
35+ on:
36+ tags: true
37+ branch: master
README.md+75−251
@@ -1,283 +1,107 @@
1−## API
1+PureScript IndexedDB [![](https://img.shields.io/badge/doc-pursuit-60b5cc.svg)](http://pursuit.purescript.org/packages/purescript-indexeddb) [![Build Status](https://travis-ci.org/truqu/purescript-indexedDB.svg?branch=master)](https://travis-ci.org/truqu/purescript-indexedDB)
2+=====
23
3−This document is a general grooming of the current state of the IndexedDB API. It covers only
4−the features specified in the official specs.
4+This package offers complete bindings and type-safety upon the [IndexedDB API](https://w3c.github.io/IndexedDB).
55
6−### Remarks
6+## Overview
77
8−- Better define a 'all' range instead of using Maybe `IDBKeyRange` in function
9−signatures (Nothing meaning 'all').
8+The `IDBCore` and `IDBFactory` are the two entry points required to create and connect to an
9+indexed database. From there, modules are divided such that each of them covers a specific IDB
10+interface.
1011
11−- Some errors (like TypeError or DataError) can be avoided via static typing
12+They are designed to be used as qualified imports such that each method gets prefixed with a
13+menaingful namespace (e.g `IDBIndex.get`, `IDBObjectStore.openCursor` ...)
1214
13−- The `IDBRequest` is mostly a callback result holding an error or a value. We probably want to
14− use a typed Except or ExceptT instead.
15+Here's a quick example of what it look likes.
16+```purescript
17+module Main where
1518
19+import Prelude
1620
17−### Table of Contents
21+import Control.Monad.Aff (Aff, launchAff_)
22+import Control.Monad.Aff.Console (CONSOLE, log)
23+import Control.Monad.Eff.Exception (EXCEPTION)
24+import Control.Monad.Eff (Eff)
25+import Data.Maybe (Maybe(..), maybe)
1826
19−- [IDBCursor](#idbcursor)
20−- [IDBCursorWithValue](#idbcursorwithvalue)
21−- [IDBCursorDirection](#idbcursordirection)
22−- [IDBDatabase](#idbdatabase)
23−- [IDBError](#idberror)
24−- [IDBFactory](#idbfactory)
25−- [IDBIndex](#idbindex)
26−- [IDBKeyRange](#idbkeyrange)
27−- [IDBRequest](#idbrequest)
28−- [IDBOpenDBRequest](#idbopendbrequest)
29−- [IDBObjectStore](#idbobjectstore)
30−- [IDBTransaction](#idbtransaction)
31−- [IDBTransactionMode](#idbtransactionmode)
27+import Database.IndexedDB.Core
28+import Database.IndexedDB.IDBFactory as IDBFactory
29+import Database.IndexedDB.IDBDatabase as IDBDatabase
30+import Database.IndexedDB.IDBObjectStore as IDBObjectStore
31+import Database.IndexedDB.IDBIndex as IDBIndex
32+import Database.IndexedDB.IDBTransaction as IDBTransaction
33+import Database.IndexedDB.IDBKeyRange as IDBKeyRange
3234
3335
34−---
36+main :: Eff (idb :: IDB, exception :: EXCEPTION, console :: CONSOLE) Unit
37+main = launchAff_ do
38+ db <- IDBFactory.open "db" Nothing { onBlocked : Nothing
39+ , onUpgradeNeeded : Just onUpgradeNeeded
40+ }
3541
42+ tx <- IDBDatabase.transaction db ["store"] ReadOnly
43+ store <- IDBTransaction.objectStore tx "store"
44+ (val :: Maybe String) <- IDBObjectStore.get store (IDBKeyRange.only 1)
45+ log $ maybe "not found" id val
3646
37−#### IDBCursor
38−> https://developer.mozilla.org/en-US/docs/Web/API/IDBCursor
3947
40−- source :: [IDBIndex](#idbindex), [IDBObjectStore](#idbobjectstore)
41−- direction :: [IDBCursorDirection](#idbcursordirection)
42−- key :: [IDBKey](#idbkey)
43−- primaryKey :: [IDBKey](#idbkey)
44−- value :: Any
45−- advance :: [IDBCursor](#idbcursor) -> Int -> ()
46− > `throw` TransactionInactiveError, TypeError, InvalidStateError
47−- continue :: [IDBCursor](#idbcursor) -> Maybe [IDBKey](#idbkey) -> ()
48− > `throw` TransactionInactivError, DataError, InvalidStateError
49−- continuePrimaryKey :: [IDBCursor](#idbcursor) -> [IDBKey](#idbkey) -> [IDBKey](#idbkey) -> ()
50− > `throw` TransactionInactiveError, DataError, InvalidStateError, InvalidAccessError
48+onUpgradeNeeded :: forall e. Database -> Transaction -> { oldVersion :: Int } -> Eff (idb :: IDB, exception :: EXCEPTION | e) Unit
49+onUpgradeNeeded db _ _ = launchAff_ do
50+ store <- IDBDatabase.createObjectStore db "store" IDBDatabase.defaultParameters
51+ _ <- IDBObjectStore.add store "patate" (Just 1)
52+ _ <- IDBObjectStore.add store { property: 42 } (Just 2)
53+ _ <- IDBObjectStore.createIndex store "index" ["property"] IDBObjectStore.defaultParameters
54+ pure unit
55+```
5156
57+## Notes
5258
53−---
59+### Errors
60+Errors normally thrown by the IDB\* interfaces are wrapped in the `Aff` Monad as `Error` where
61+the `name` corresponds to the error's name (e.g. "InvalidStateError"). Pattern matching can
62+therefore be done on any error to handle specific errors thrown by the API.
5463
64+### Examples
65+The `test` folder contains a great amount of examples showing practical usage of the IDB\*
66+interfaces. Do not hesitate to have a peek should you wonder how to use one of the module. The
67+wrapper tries to keep as much as possible an API consistent with the original IndexedDB API.
68+Hence, it should be quite straightforward to translate any JavaScript example to a PureScript
69+one.
5570
56−#### IDBCursorWithValue
57−> https://developer.mozilla.org/en-US/docs/Web/API/IDBCursorwithValue
71+## Changelog
5872
59−Inherit from [IDBCursor](#idbcursor)
73+#### v3.0.0
6074
61−- delete :: [IDBCursor](#idbcursor) -> [IDBRequest](#idbrequest) ()
62− > `throw` TransactionInactiveError, ReadOnlyError, InvalidStateError
63−- update :: [IDBCursor](#idbcursor) -> a -> [IDBRequest](#idbrequest) a
64− > `throw` TransactionInactiveError, ReadOnlyError, InvalidStateError, DataError, DataCloneError
75+- callback to `onUpgradeNeeded` event now provide a record with the database old version.
6576
77+#### v2.0.0
6678
67−---
79+- review interface implementation (use of opaque classes to improve readability without compromising
80+ the reusability). The API doesn't really change from a user perspective though.
6881
82+- make the Key more opaque (by having an IDBKey instance for Foreign types)
6983
70−#### IDBCursorDirection
71−> https://www.w3.org/TR/IndexedDB/#idl-def-IDBCursorDirection
84+- Upgrade purescript-exceptions to 3.1.0 and leverage the new `name` accessor
7285
73−data [IDBCursorDirection](#idbcursordirection)
74− = Next
75− | NextUnique
76− | Prev
77− | PrevUnique
7886
87+#### v1.0.0
7988
80−---
89+- [Indexed Database API 2.0](https://w3c.github.io/IndexedDB/) totally covered apart from
90+ - `index.getAll` method (and the associated one for the IDBObjectStore)
91+ - binary keys
8192
93+## Documentation
8294
83−#### IDBDatabase
84−> https://www.w3.org/TR/IndexedDB/#idl-def-IDBDatabase
95+Module documentation is [published on Pursuit](http://pursuit.purescript.org/packages/purescript-indexeddb).
8596
86−- name :: String
87−- version :: Long
88−- objectStoreNames :: [String]
89−- close :: [IDBDatabase](#idbdatabase) -> ()
90−- createObjectStore :: [IDBDatabase](#idbdatabase) -> String -> { keyPath :: String, autoIncrement :: Bool } -> [IDBObjectStore](#idbobjectstore)
91− > `throw` InvalidStateError, TransactionInactiveError, ConstraintError, InvalidAccessError
92−- deleteObjectStore :: [IDBDatabase](#idbdatabase) -> String -> ()
93− > `throw` InvalidSateError, TransactionInactiveError, NotFoundError
94−- transaction :: [IDBDatabase](#idbdatabase) -> [String] -> [IDBTransactionMode](#idbtransactionmode) -> [IDBTransaction](#idbtransaction)
95− > `throw` InvalidStateError, NotFoundError, TypeError, InvalidAccessError
97+## Testing
98+Tested in the cloud on multiple browsers and operating systems thanks to [BrowserStack](https://www.browserstack.com)
9699
97−##### Events
98−- onabort
99−- onerror
100−- onversionchange
101100
101+| IE / Edge | Chrome | Firefox | Safari | Opera | Android | iOS Safari |
102+| ----------| ------ | ------- | ------- | ----- | ------- | ---------- |
103+| - | >= 57 | >= 51 | - | >= 46 | - | - |
102104
103−---
104−
105−
106−#### IDBError
107−> https://www.w3.org/TR/IndexedDB/#exceptions
108−
109−data IDBError
110− = AbortError
111− | ConstraintError
112− | QuotaExceededError
113− | UnknownError
114− | NoError
115− | VersionError
116−
117−
118−---
119−
120−
121−#### IDBFactory
122−> https://www.w3.org/TR/IndexedDB/#idl-def-IDBFactory
123−
124−The method `cmp` isn't included; instead, we consider [IDBKey](#idbkey) to be comparable.
125−
126−- open :: String -> Maybe Long -> [IDBOpenDBRequest](#idbopendbrequest) ()
127−- deleteDatabase :: String -> [IDBOpenDBRequest](#idbopendbrequest) ()
128−
129−
130−---
131−
132−
133−#### IDBIndex
134−> https://www.w3.org/TR/IndexedDB/#index-concept
135−> https://www.w3.org/TR/IndexedDB/#index
136−
137−- name :: String
138−- objectStore :: [IDBObjectStore](#idbobjectstore)
139−- keyPath :: String
140−- multiEntry :: Bool
141−- unique :: Bool
142−- count :: [IDBIndex](#idbindex) -> Maybe [IDBKeyRange](#idbkeyrange) -> [IDBRequest](#idbrequest) Int
143− > `throw` TransactionInactiveError, DataError, InvalidStateError
144−- get :: [IDBIndex](#idbindex) -> [IDBKeyRange](#idbkeyrange) -> [IDBRequest](#idbrequest) a
145− > `throw` TransactionInactiveError, DataError, InvalidStateError
146−- getKey :: [IDBIndex](#idbindex) -> [IDBKeyRange](#idbkeyrange) -> [IDBRequest](#idbrequest) [IDBKey](#idbkey)
147− > `throw` TransactionInactiveError, DataError, InvalidStateError
148−- openCursor :: [IDBIndex](#idbindex) -> Maybe [IDBKeyRange](#idbkeyrange) -> Maybe Direction -> [IDBRequest](#idbrequest) CursorWithValue
149− > `throw` TransactionInactiveError, DataError, TypeError, InvalidStateError
150−- openKeyCursor :: [IDBIndex](#idbindex) -> Maybe [IDBKeyRange](#idbkeyrange)-> Maybe Direction -> [IDBRequest](#idbrequest) Cursor
151− > `throw` TransactionInactiveError, DataError, TypeError, InvalidStateError
152−
153−
154−---
155−
156−
157−#### IDBKey
158−> https://www.w3.org/TR/IndexedDB/#key-construct
159−
160−Should be comparable / derive Ord
161−
162−data IDBKey
163− = Int Int
164− | Float Float
165− | String String
166− | Date Date
167− | Array [[IDBKey](#idbkey)]
168−
169−
170−---
171−
172−
173−#### IDBKeyRange
174−> https://www.w3.org/TR/IndexedDB/#idl-def-IDBKeyRange
175−
176−- lower :: [IDBKey](#idbkey)
177−- upper :: [IDBKey](#idbkey)
178−- lowerOpen :: Boolean
179−- upperOpen :: Boolean
180−- bound :: [IDBKey](#idbkey) -> [IDBKey](#idbkey) -> Bool -> Bool -> [IDBKeyRange](#idbkeyrange)
181− > `throw` DataError
182−- only :: [IDBKey](#idbkey) -> [IDBKeyRange](#idbkeyrange)
183− > `throw` DataError
184−- lowerBound :: [IDBKey](#idbkey) -> Bool -> [IDBKeyRange](#idbkeyrange)
185− > `throw` DataError
186−- upperBound :: [IDBKey](#idbkey) -> Bool -> [IDBKeyRange](#idbkeyrange)
187− > `throw` DataError
188−- includes :: [IDBKey](#idbkey) -> Boolean
189− > `throw` DataError
190−
191−
192−---
193−
194−
195−#### IDBRequest
196−> https://www.w3.org/TR/IndexedDB/#idl-def-IDBRequest
197−
198−- error :: [IDBError](#idberror)
199−- result :: a
200−- source :: [IDBIndex](#idbindex) | [IDBObjectStore](#idbobjectstore) | [IDBCursor](#idbcursor)
201−- readyState :: Bool
202−- transaction :: Maybe [IDBTransaction](#idbtransaction)
203−
204−##### Events
205−- onerror
206−- onsuccess
207−
208−
209−---
210−
211−
212−#### IDBOpenDBRequest
213−> https://www.w3.org/TR/IndexedDB/#idl-def-IDBRequest
214−
215−Inherit from [IDBRequest](#idbrequest)
216−
217−##### Events
218− - onblocked
219− - onupgradeneeded
220−
221−
222−---
223−
224−
225−#### IDBObjectStore
226−> https://www.w3.org/TR/IndexedDB/#idl-def-IDBObjectStore
227−
228−- indexNames:: [String]
229−- keyPath :: String
230−- name :: String
231−- transaction :: [IDBTransaction](#idbtransaction)
232−- autoIncrement :: Bool
233−- add :: [IDBObjectStore](#idbobjectstore) -> Value -> Maybe [IDBKey](#idbkey) -> [IDBRequest](#idbrequest) ()
234− > `throw` ReadOnlyError, TransactionInactiveError, DataError, InvalidStateError, DataCloneError
235−- clear :: [IDBObjectStore](#idbobjectstore) -> [IDBRequest](#idbrequest) ()
236− > `throw` ReadOnlyError, TransactionInactiveError
237−- count :: [IDBObjectStore](#idbobjectstore) -> [IDBKeyRange](#idbkeyrange) -> [IDBRequest](#idbrequest) Int
238− > `throw` InvalidStateError, TransactionInactiveError, DataError
239−- createIndex :: [IDBObjectStore](#idbobjectstore) -> String -> String -> { unique :: Bool, multiEntry :: true } -> [IDBIndex](#idbindex)
240− > `throw` ConstraintError, InvalidAccessError, InvalidStateError, SyntaxError, TransactionInactiveError
241−- delete :: [IDBObjectStore](#idbobjectstore) -> [IDBKeyRange](#idbkeyrange) -> [IDBRequest](#idbrequest) ()
242− > `throw` TransactionInactiveError, ReadOnlyError, InvalidStateError, DataError
243−- deleteIndex :: [IDBObjectStore](#idbobjectstore) -> String -> ()
244− > `throw` InvalidStateError, TransactionInactiveError, NotFoundError
245−- get :: [IDBObjectStore](#idbobjectstore) -> [IDBKeyRange](#idbkeyrange) -> [IDBRequest](#idbrequest) a
246− > `throw` InvalidStateError, TransactionInactiveError, DataError
247−- index :: [IDBObjectStore](#idbobjectstore) -> String -> [IDBIndex](#idbindex)
248− > `throw` InvalidStateError, NotfoundError
249−- openCursor :: [IDBObjectStore](#idbobjectstore) -> Maybe [IDBKeyRange](#idbkeyrange) -> Maybe [IDBCursorDirection](#idbcursordirection) -> [IDBRequest](#idbrequest) [IDBCursorWithValue](#idbcursorwithvalue)
250− > `throw` TransactionInactiveError, InvalidStateError, DataError
251−- put :: [IDBObjectStore](#idbobjectstore) -> a -> [IDBKey](#idbkey) -> [IDBRequest](#idbrequest) ()
252− > `throw` ReadOnlyError, TransactionInactiveError, DataError, InvalidStateError, DataCloneError
253−
254−
255−---
256−
257−
258−#### IDBTransaction
259−> https://www.w3.org/TR/IndexedDB/#idl-def-IDBTransaction
260−
261−- db :: [IDBDatabase](#idbdatabase)
262−- error :: [IDBError](#idberror)
263−- mode :: [IDBTransactionMode](#idbtransactionmode)
264−- abort :: [IDBTransaction](#idbtransaction) -> ()
265− > `throw` InvalidStateError
266−- objectStore :: [IDBTransaction](#idbtransaction) -> String -> [IDBObjectStore](#idbobjectstore)
267− > `throw` InvalidStateError, NotFoundError
268−
269−##### Events
270−- onabort
271−- oncomplete
272−- onerror
273−
274−---
275−
276−
277−#### IDBTransactionMode
278−> https://www.w3.org/TR/IndexedDB/#idl-def-IDBTransactionMode
279−
280−data IDBTransactionMode
281− = ReadOnly
282− | ReadWrite
283− | VersionChange
105+<p align="center">
106+ <a href="https://www.browserstack.com"><img alt="browserstack" src=".github/browserstack.png" /></a>
107+</p>
bower.jsondeleted−19
@@ -1,19 +0,0 @@
1−{
2− "name": "purescript-indexeddb",
3− "ignore": [
4− "**/.*",
5− "node_modules",
6− "bower_components",
7− "output"
8− ],
9− "dependencies": {
10− "purescript-maybe": "3.0.0",
11− "purescript-aff": "3.1.0",
12− "purescript-test-unit": "11.0.0",
13− "purescript-spec": "1.0.0",
14− "purescript-datetime": "3.2.0"
15− },
16− "devDependencies": {
17− "purescript-psci-support": "3.0.0"
18− }
19−}
index.jsnew file+4
@@ -0,0 +1,4 @@
1+import "fake-indexeddb/auto";
2+import * as Main from './output/Test.Main/index.js';
3+
4+Main.main();
karma.conf.jsdeleted−25
@@ -1,25 +0,0 @@
1−module.exports = config => {
2− config.set({
3− autoWatch: true,
4− browsers: ["Chrome"],
5− files: [
6− "dist/karma/index.js",
7− ],
8− frameworks: [
9− "mocha",
10− ],
11− plugins: [
12− "karma-chrome-launcher",
13− "karma-firefox-launcher",
14− "karma-spec-reporter",
15− "karma-mocha",
16− ],
17− reporters: ["spec"],
18− singleRun: false,
19− client: {
20− mocha: {
21− timeout: 10000
22− }
23− }
24− });
25−};
package.json+8−39
@@ -1,46 +1,15 @@
11{
2− "name": "purescript-indexeddb",
2+ "name": "purescript-indexed-DB",
33 "version": "1.0.0",
4− "description": "This document is a general grooming of the current state of the IndexedDB API. It covers only the features specified in the official specs.",
5− "main": "bundle.js",
4+ "main": "index.js",
5+ "repository": "https://github.com/ilyakooo0/purescript-indexed-DB.git",
6+ "author": "iko <mail@iko.soy>",
7+ "license": "MIT",
68 "scripts": {
7− "test:build": "pulp browserify -j 4 --main \"Test.Main\" -I test --to dist/karma/index.js",
8− "test:run": "karma start --single-run",
9− "test:watch": "find . -type f -regex '.*\\(src\\|test\\).*' ! -regex '.*bower_components.*\\|.*node_modules.*|.*\\.sw.*' | entr -s 'npm run test'",
10− "test": "npm run test:build && npm run test:run",
11− "build": "pulp browserify -j 4 -O --to dist/bundle.js",
12− "start": "find . -type f -regex '.*\\(src\\|test\\).*' ! -regex '.*bower_components.*\\|.*node_modules.*|.*\\.sw.*' | entr -s 'clear; pulp browserify -j 4 -t dist/bundle.js'",
13− "start:test": "karma start"
9+ "test": "spago build && node index.js"
1410 },
1511 "dependencies": {
16− "eslint": "^3.19.0",
17− "eslint-config-airbnb": "^15.0.1",
18− "eslint-plugin-import": "^2.3.0",
19− "eslint-plugin-jsx-a11y": "^5.0.3",
20− "eslint-plugin-react": "^7.1.0",
21− "karma": "^1.7.0"
12+ "fake-indexeddb": "^4.0.1"
2213 },
23− "devDependencies": {
24− "eslint": "3.19.0",
25− "eslint-config-airbnb": "15.0.1",
26− "eslint-plugin-import": "2.5.0",
27− "eslint-plugin-jsx-a11y": "5.0.3",
28− "eslint-plugin-react": "7.1.0",
29− "karma": "1.7.0",
30− "karma-chrome-launcher": "2.1.1",
31− "karma-firefox-launcher": "1.0.1",
32− "karma-mocha": "^1.3.0",
33− "karma-spec-reporter": "0.0.31",
34− "mocha": "^3.4.2"
35− },
36− "repository": {
37− "type": "git",
38− "url": "git+https://github.com/Truqu/purescript-indexeddb.git"
39− },
40− "author": "",
41− "license": "ISC",
42− "bugs": {
43− "url": "https://github.com/Truqu/purescript-indexeddb/issues"
44− },
45− "homepage": "https://github.com/Truqu/purescript-indexeddb#readme"
14+ "type": "module"
4615}
packages.dhallnew file+105
@@ -0,0 +1,105 @@
1+{-
2+Welcome to your new Dhall package-set!
3+
4+Below are instructions for how to edit this file for most use
5+cases, so that you don't need to know Dhall to use it.
6+
7+## Use Cases
8+
9+Most will want to do one or both of these options:
10+1. Override/Patch a package's dependency
11+2. Add a package not already in the default package set
12+
13+This file will continue to work whether you use one or both options.
14+Instructions for each option are explained below.
15+
16+### Overriding/Patching a package
17+
18+Purpose:
19+- Change a package's dependency to a newer/older release than the
20+ default package set's release
21+- Use your own modified version of some dependency that may
22+ include new API, changed API, removed API by
23+ using your custom git repo of the library rather than
24+ the package set's repo
25+
26+Syntax:
27+where `entityName` is one of the following:
28+- dependencies
29+- repo
30+- version
31+-------------------------------
32+let upstream = --
33+in upstream
34+ with packageName.entityName = "new value"
35+-------------------------------
36+
37+Example:
38+-------------------------------
39+let upstream = --
40+in upstream
41+ with halogen.version = "master"
42+ with halogen.repo = "https://example.com/path/to/git/repo.git"
43+
44+ with halogen-vdom.version = "v4.0.0"
45+ with halogen-vdom.dependencies = [ "extra-dependency" ] # halogen-vdom.dependencies
46+-------------------------------
47+
48+### Additions
49+
50+Purpose:
51+- Add packages that aren't already included in the default package set
52+
53+Syntax:
54+where `<version>` is:
55+- a tag (i.e. "v4.0.0")
56+- a branch (i.e. "master")
57+- commit hash (i.e. "701f3e44aafb1a6459281714858fadf2c4c2a977")
58+-------------------------------
59+let upstream = --
60+in upstream
61+ with new-package-name =
62+ { dependencies =
63+ [ "dependency1"
64+ , "dependency2"
65+ ]
66+ , repo =
67+ "https://example.com/path/to/git/repo.git"
68+ , version =
69+ "<version>"
70+ }
71+-------------------------------
72+
73+Example:
74+-------------------------------
75+let upstream = --
76+in upstream
77+ with benchotron =
78+ { dependencies =
79+ [ "arrays"
80+ , "exists"
81+ , "profunctor"
82+ , "strings"
83+ , "quickcheck"
84+ , "lcg"
85+ , "transformers"
86+ , "foldable-traversable"
87+ , "exceptions"
88+ , "node-fs"
89+ , "node-buffer"
90+ , "node-readline"
91+ , "datetime"
92+ , "now"
93+ ]
94+ , repo =
95+ "https://github.com/hdgarrood/purescript-benchotron.git"
96+ , version =
97+ "v7.0.0"
98+ }
99+-------------------------------
100+-}
101+
102+let upstream =
103+ https://github.com/purescript/package-sets/releases/download/psc-0.15.4-20221124/packages.dhall
104+ sha256:792255bbd8e2141468d967325f3e78246cb7e500bf3ab6d76d5b22dffbb09d49
105+in upstream
spago.dhallnew file+42
@@ -0,0 +1,42 @@
1+{-
2+Welcome to a Spago project!
3+You can edit this file as you like.
4+
5+Need help? See the following resources:
6+- Spago documentation: https://github.com/purescript/spago
7+- Dhall language tour: https://docs.dhall-lang.org/tutorials/Language-Tour.html
8+
9+When creating a new Spago project, you can use
10+`spago init --no-comments` or `spago init -C`
11+to generate this file without the comments in this block.
12+-}
13+{ name = "indexed-db"
14+, dependencies =
15+ [ "aff"
16+ , "arrays"
17+ , "avar"
18+ , "console"
19+ , "control"
20+ , "datetime"
21+ , "effect"
22+ , "either"
23+ , "enums"
24+ , "exceptions"
25+ , "foldable-traversable"
26+ , "foreign"
27+ , "functions"
28+ , "identity"
29+ , "lists"
30+ , "maybe"
31+ , "nonempty"
32+ , "now"
33+ , "nullable"
34+ , "prelude"
35+ , "read"
36+ , "spec"
37+ , "transformers"
38+ , "tuples"
39+ ]
40+, packages = ./packages.dhall
41+, sources = [ "src/**/*.purs", "test/**/*.purs" ]
42+}
src/Database/IndexedDB/Core.js+6−6
@@ -3,7 +3,7 @@ const toArray = function toArray(xs) {
33};
44
55
6−exports._showCursor = function _showCursor(cursor) {
6+export function _showCursor(cursor) {
77 return '(IDBCursor ' +
88 '{ direction: ' + cursor.direction +
99 ', key: ' + cursor.key +
@@ -11,7 +11,7 @@ exports._showCursor = function _showCursor(cursor) {
1111 ' })';
1212};
1313
14−exports._showDatabase = function _showDatabase(db) {
14+export function _showDatabase(db) {
1515 return '(IDBDatabase ' +
1616 '{ name: ' + db.name +
1717 ', objectStoreNames: [' + toArray(db.objectStoreNames).join(', ') + ']' +
@@ -19,7 +19,7 @@ exports._showDatabase = function _showDatabase(db) {
1919 ' })';
2020};
2121
22−exports._showIndex = function _showIndex(index) {
22+export function _showIndex(index) {
2323 return '(IDBIndex ' +
2424 '{ name: ' + index.name +
2525 ', keyPath: ' + index.keyPath +
@@ -28,7 +28,7 @@ exports._showIndex = function _showIndex(index) {
2828 ' })';
2929};
3030
31−exports._showKeyRange = function _showKeyRange(range) {
31+export function _showKeyRange(range) {
3232 return '(IDBKeyRange ' +
3333 '{ lower: ' + range.lower +
3434 ', upper: ' + range.upper +
@@ -37,7 +37,7 @@ exports._showKeyRange = function _showKeyRange(range) {
3737 ' })';
3838};
3939
40−exports._showObjectStore = function _showObjectStore(store) {
40+export function _showObjectStore(store) {
4141 return '(IDBObjectStore ' +
4242 '{ autoIncrement: ' + store.autoIncrement +
4343 ', indexNames: [' + toArray(store.indexNames).join(', ') + ']' +
@@ -46,7 +46,7 @@ exports._showObjectStore = function _showObjectStore(store) {
4646 ' })';
4747};
4848
49−exports._showTransaction = function _showTransaction(tx) {
49+export function _showTransaction(tx) {
5050 return '(IDBTransaction ' +
5151 '{ error: ' + tx.error +
5252 ', mode: ' + tx.mode +
src/Database/IndexedDB/Core.purs+163−58
@@ -1,137 +1,242 @@
1+-- | The Core module gathers types used across the library and provides basic Show instances for
2+-- | those types.
13module Database.IndexedDB.Core
2− ( class FromString, parse
3− , INDEXED_DB
4− , IDB
5− , CursorDirection(..)
6− , CursorSource(..)
7− , Database
4+ -- * Effects
5+ (
6+ -- IDB
7+
8+ -- * Types
9+ Database
810 , Index
911 , KeyCursor
1012 , KeyRange
11− , KeyPath
1213 , ObjectStore
1314 , Transaction
14− , TransactionMode(..)
1515 , ValueCursor
16+ , CursorDirection(..)
17+ , CursorSource(..)
18+ , KeyPath
19+ , TransactionMode(..)
20+
21+ -- * Classes
22+ , class IDBConcreteCursor
23+ , class IDBCursor
24+ , class IDBDatabase
25+ , class IDBIndex
26+ , class IDBKeyRange
27+ , class IDBTransaction
28+ , class IDBObjectStore
29+
30+ -- * Re-Exports
1631 , module Database.IndexedDB.IDBKey
1732 ) where
1833
19−import Prelude (class Show)
34+import Prelude (class Show)
2035
21−import Control.Monad.Eff (kind Effect)
22−import Data.Maybe (Maybe(..))
36+import Data.Maybe (Maybe(..))
37+import Data.String.Read (class Read)
2338
2439import Database.IndexedDB.IDBKey
2540
41+--------------------
42+-- TYPES
43+--
2644
27−foreign import data INDEXED_DB :: Effect
45+-- | Each origin has an associated set of databases. A database has zero or more object
46+-- | stores which hold the data stored in the database.
47+foreign import data Database :: Type
2848
49+-- | An index allows looking up records in an object store using properties of the values
50+-- | in the object stores records.
51+foreign import data Index :: Type
2952
30−foreign import data IDB :: Effect
53+-- | A key range is a continuous interval over some data type used for keys.
54+foreign import data KeyRange :: Type
3155
56+-- | A cursor is used to iterate over a range of records in an index or an object store
57+-- | in a specific direction. A KeyCursor doesn't hold any value.
58+foreign import data KeyCursor :: Type
3259
33−data CursorDirection = Next | NextUnique | Prev | PrevUnique
60+-- | A Transaction is used to interact with the data in a database.
61+-- | Whenever data is read or written to the database it is done by using a transaction.
62+foreign import data Transaction :: Type
3463
64+-- | An object store is the primary storage mechanism for storing data in a database.
65+foreign import data ObjectStore :: Type
3566
36−data CursorSource = ObjectStore ObjectStore | Index Index
67+-- | A cursor is used to iterate over a range of records in an index or an object store
68+-- | in a specific direction. A ValueCursor also holds the value corresponding to matching key.
69+foreign import data ValueCursor :: Type
3770
71+-- | A cursor has a direction that determines whether it moves in monotonically
72+-- | increasing or decreasing order of the record keys when iterated, and if it
73+-- | skips duplicated values when iterating indexes.
74+-- | The direction of a cursor also determines if the cursor initial position is at
75+-- | the start of its source or at its end.
76+data CursorDirection
77+ = Next
78+ | NextUnique
79+ | Prev
80+ | PrevUnique
3881
39−data TransactionMode = ReadOnly | ReadWrite | VersionChange
82+-- | If the source of a cursor is an object store, the effective object store of
83+-- | the cursor is that object store and the effective key of the cursor is the
84+-- | cursor’s position. If the source of a cursor is an index, the effective object
85+-- | store of the cursor is that index’s referenced object store and the effective key
86+-- | is the cursor’s object store position.
87+data CursorSource
88+ = ObjectStore ObjectStore
89+ | Index Index
4090
91+-- | A key path is a list of strings that defines how to extract a key from a value.
92+-- | A valid key path is one of:
93+-- |
94+-- | - An empty list.
95+-- | - An singleton identifier, which is a string matching the IdentifierName production from the ECMAScript Language Specification [ECMA-262].
96+-- | - A singleton string consisting of two or more identifiers separated by periods (U+002E FULL STOP).
97+-- | - A non-empty list containing only strings conforming to the above requirements.
98+type KeyPath = Array String
4199
42−type KeyPath = Array String
100+-- | A transaction has a mode that determines which types of interactions can be performed
101+-- | upon that transaction. The mode is set when the transaction is created and remains
102+-- | fixed for the life of the transaction.
103+data TransactionMode
104+ = ReadOnly -- ^ The transaction is only allowed to read data.
105+ | ReadWrite -- ^ The transaction is allowed to read, modify and delete data from existing object stores
106+ | VersionChange -- ^ The transaction is allowed to read, modify and delete data from existing object stores, and can also create and remove object stores and indexes.
43107
108+--------------------
109+-- Classes
110+--
44111
45−foreign import data Database :: Type
112+-- | A concrete cursor not only shares IDBCursor properties, but also some
113+-- | specific attributes (see KeyCursor or CursorWithValue).
114+class (IDBCursor cursor) <= IDBConcreteCursor cursor
46115
116+-- | Cursor objects implement the IDBCursor interface.
117+-- | There is only ever one IDBCursor instance representing a given cursor.
118+-- | There is no limit on how many cursors can be used at the same time.
119+class IDBCursor cursor
47120
48−foreign import data Index :: Type
121+-- | The IDBDatabase interface represents a connection to a database.
122+class IDBDatabase db
49123
124+-- | The IDBIndex interface represents an index handle.
125+-- | Any of these methods throw an "TransactionInactiveError" DOMException
126+-- | if called when the transaction is not active.
127+class IDBIndex index
50128
51−foreign import data KeyCursor :: Type
129+-- | The IDBKeyRange interface represents a key range.
130+class IDBKeyRange range
52131
132+-- | The IDBtransaction interface.
133+class IDBTransaction tx
53134
54−foreign import data KeyRange :: Type
135+-- | The IDBObjectStore interface represents an object store handle.
136+class IDBObjectStore store
55137
138+--------------------
139+-- INSTANCES
140+--
56141
57−foreign import data ObjectStore :: Type
142+instance idbCursorKeyCursor :: IDBCursor KeyCursor
58143
144+instance idbCursorValueCursor :: IDBCursor ValueCursor
59145
60−foreign import data Transaction :: Type
146+instance idbConcreteCursorKeyCursor :: IDBConcreteCursor KeyCursor
61147
148+instance idbConcreteCursorValueCursor :: IDBConcreteCursor ValueCursor
62149
63−foreign import data ValueCursor :: Type
150+instance idbDatabaseDatabase :: IDBDatabase Database
151+
152+instance idbIndexIndex :: IDBIndex Index
153+
154+instance idbIndexObjectStore :: IDBIndex ObjectStore
64155
156+instance idbKeyRangeKeyRange :: IDBKeyRange KeyRange
65157
66−class FromString a where
67− parse :: String -> Maybe a
158+instance idbObjectStoreObjectStore :: IDBObjectStore ObjectStore
68159
160+instance idbTransactionTransaction :: IDBTransaction Transaction
69161
70−foreign import _showCursor :: forall cursor. cursor -> String
71162instance showKeyCursor :: Show KeyCursor where
72163 show = _showCursor
73164
74−
75165instance showValueCursor :: Show ValueCursor where
76166 show = _showCursor
77167
78−
79−foreign import _showDatabase :: Database -> String
80−instance showDatabase :: Show Database where
81− show = _showDatabase
82−
83−
84−foreign import _showIndex :: Index -> String
85168instance showIndex :: Show Index where
86169 show = _showIndex
87170
88−
89−foreign import _showKeyRange :: KeyRange -> String
90171instance showKeyRange :: Show KeyRange where
91172 show = _showKeyRange
92173
93−
94−foreign import _showObjectStore :: ObjectStore -> String
95174instance showObjectStore :: Show ObjectStore where
96175 show = _showObjectStore
97176
98−
99−foreign import _showTransaction :: Transaction -> String
100177instance showTransaction :: Show Transaction where
101178 show = _showTransaction
102179
180+instance showDatabase :: Show Database where
181+ show = _showDatabase
103182
104183instance showCursorDirection :: Show CursorDirection where
105184 show x =
106185 case x of
107− Next -> "next"
186+ Next -> "next"
108187 NextUnique -> "nextunique"
109− Prev -> "prev"
188+ Prev -> "prev"
110189 PrevUnique -> "prevunique"
111190
112−
113191instance showTransactionMode :: Show TransactionMode where
114192 show x =
115193 case x of
116− ReadOnly -> "readonly"
117− ReadWrite -> "readwrite"
194+ ReadOnly -> "readonly"
195+ ReadWrite -> "readwrite"
118196 VersionChange -> "versionchange"
119197
120−
121−instance fromStringCursorDirection :: FromString CursorDirection where
122− parse s =
198+instance readCursorDirection :: Read CursorDirection where
199+ read s =
123200 case s of
124− "next" -> Just Next
201+ "next" -> Just Next
125202 "nextunique" -> Just NextUnique
126− "prev" -> Just Prev
203+ "prev" -> Just Prev
127204 "prevunique" -> Just PrevUnique
128− _ -> Nothing
129−
205+ _ -> Nothing
130206
131−instance fromStringTransactionMode :: FromString TransactionMode where
132− parse s =
207+instance readTransactionMode :: Read TransactionMode where
208+ read s =
133209 case s of
134− "readonly" -> Just ReadOnly
135− "readwrite" -> Just ReadWrite
210+ "readonly" -> Just ReadOnly
211+ "readwrite" -> Just ReadWrite
136212 "versionchange" -> Just VersionChange
137− _ -> Nothing
213+ _ -> Nothing
214+
215+--------------------
216+-- FFI
217+--
218+
219+foreign import _showCursor
220+ :: forall cursor
221+ . cursor
222+ -> String
223+
224+foreign import _showDatabase
225+ :: Database
226+ -> String
227+
228+foreign import _showIndex
229+ :: Index
230+ -> String
231+
232+foreign import _showKeyRange
233+ :: KeyRange
234+ -> String
235+
236+foreign import _showObjectStore
237+ :: ObjectStore
238+ -> String
239+
240+foreign import _showTransaction
241+ :: Transaction
242+ -> String
src/Database/IndexedDB/IDBCursor.js+95−32
@@ -1,6 +1,6 @@
11const errorHandler = function errorHandler(cb) {
22 return function _handler(e) {
3− cb(new Error(e.target.error.name));
3+ cb(e.target.error);
44 };
55};
66
@@ -10,72 +10,135 @@ const successHandler = function successHandler(cb) {
1010 };
1111};
1212
13−
14−exports._advance = function _advance(cursor, count) {
15− return function eff() {
13+export function _advance(cursor, count) {
14+ return function aff(error, success) {
1615 try {
1716 cursor.advance(count);
17+ success();
1818 } catch (e) {
19− throw new Error(e.name);
19+ error(e);
2020 }
21+
22+ return function canceler(_, cancelerError) {
23+ cancelerError(new Error("Can't cancel IDB Effects"));
24+ };
2125 };
2226};
2327
24−exports._continue = function _continue(cursor, key) {
25− return function eff() {
28+export function _continue(cursor, key) {
29+ return function aff(error, success) {
2630 try {
2731 cursor.continue(key || undefined);
32+ success();
2833 } catch (e) {
29− throw new Error(e.name);
34+ error(e);
3035 }
36+
37+ return function canceler(_, cancelerError) {
38+ cancelerError(new Error("Can't cancel IDB Effects"));
39+ };
3140 };
3241};
3342
34−exports._continuePrimaryKey = function _continuePrimaryKey(cursor, key, primaryKey) {
35− return function eff() {
43+export function _continuePrimaryKey(cursor, key, primaryKey) {
44+ return function aff(error, success) {
3645 try {
3746 cursor.continuePrimaryKey(key, primaryKey);
47+ success();
3848 } catch (e) {
39− throw new Error(e.name);
49+ error(e);
4050 }
51+
52+ return function canceler(_, cancelerError) {
53+ cancelerError(new Error("Can't cancel IDB Effects"));
54+ };
4155 };
4256};
4357
44−exports._delete = function _delete(cursor) {
45− return function aff(success, error) {
46− const request = cursor.delete();
47− request.onsuccess = successHandler(success);
48− request.onerror = errorHandler(error);
58+export function _delete(cursor) {
59+ return function aff(error, success) {
60+ try {
61+ const request = cursor.delete();
62+ request.onsuccess = successHandler(success);
63+ request.onerror = errorHandler(error);
64+ } catch (e) {
65+ error(e);
66+ }
67+
68+ return function canceler(_, cancelerError) {
69+ cancelerError(new Error("Can't cancel IDB Effects"));
70+ };
4971 };
5072};
5173
52−exports._direction = function _direction(fromString, cursor) {
74+export function _direction(fromString, cursor) {
5375 return fromString(cursor.direction);
5476};
5577
56−exports._key = function _key(cursor) {
57− return cursor.key;
78+export function _key(cursor) {
79+ return function aff(error, success) {
80+ try {
81+ success(cursor.key);
82+ } catch (e) {
83+ error(e);
84+ }
85+
86+ return function canceler(_, cancelerError) {
87+ cancelerError(new Error("Can't cancel IDB Effects"));
88+ };
89+ };
5890};
5991
60−exports._primaryKey = function _primaryKey(cursor) {
61− return cursor.primaryKey;
92+export function _primaryKey(cursor) {
93+ return function aff(error, success) {
94+ try {
95+ success(cursor.primaryKey);
96+ } catch (e) {
97+ error(e);
98+ }
99+
100+ return function canceler(_, cancelerError) {
101+ cancelerError(new Error("Can't cancel IDB Effects"));
102+ };
103+ };
62104};
63105
64−exports._source = function _source(IDBObjectStore, IDBIndex, cursor) {
65− console.log(typeof cursor.source);
66− console.log(Object.getPrototypeOf(cursor.source));
67− console.log(cursor.source);
68− throw new Error('TODO');
106+export function _source(IDBObjectStore, IDBIndex, cursor) {
107+ switch (cursor.source.constructor.name) {
108+ case 'IDBIndex':
109+ return IDBIndex(cursor.source);
110+ case 'IDBObjectStore':
111+ return IDBObjectStore(cursor.source);
112+ default:
113+ throw Object.create(Error.prototype, {
114+ message: {
115+ enumerable: true,
116+ value: 'Unable to retrieve the cursor\'s source constructor.',
117+ },
118+ name: {
119+ enumerable: true,
120+ value: 'UnexpectedCursorSource',
121+ },
122+ });
123+ }
69124};
70125
71−exports._update = function _update(cursor, value) {
72− return function aff(success, error) {
73− const request = cursor.update(value);
74− request.onsuccess = successHandler(success);
75− request.onerror = errorHandler(error);
126+export function _update(cursor, value) {
127+ return function aff(error, success) {
128+ try {
129+ const request = cursor.update(value);
130+ request.onsuccess = successHandler(success);
131+ request.onerror = errorHandler(error);
132+ } catch (e) {
133+ error(e);
134+ }
135+
136+ return function canceler(_, cancelerError) {
137+ cancelerError(new Error("Can't cancel IDB Effects"));
138+ };
76139 };
77140};
78141
79−exports._value = function _value(cursor) {
142+export function _value(cursor) {
80143 return cursor.value;
81144};
src/Database/IndexedDB/IDBCursor.purs+154−108
@@ -1,140 +1,186 @@
1+-- | A cursor is used to iterate over a range of records in an index or
2+-- | an object store in a specific direction.
13module Database.IndexedDB.IDBCursor
2− ( class IDBCursor, advance, continue, continuePrimaryKey, delete, update
4+ -- * Interface
5+ ( advance
6+ , continue
7+ , continuePrimaryKey
8+ , delete
9+ , update
10+
11+ -- * Attributes
312 , direction
413 , key
514 , primaryKey
615 , source
7− , direction'
8− , key'
9− , primaryKey'
10− , source'
1116 , value
1217 ) where
1318
14−import Prelude (Unit, (>>>))
15−
16−import Control.Monad.Aff (Aff)
17−import Control.Monad.Eff (Eff)
18−import Control.Monad.Eff.Exception (EXCEPTION)
19−import Data.Function.Uncurried as Fn
20−import Data.Function.Uncurried (Fn2, Fn3)
21−import Data.Maybe (Maybe)
22−import Data.Nullable (Nullable, toNullable)
23−import Data.Foreign (Foreign, toForeign, unsafeFromForeign)
24−
2519import Database.IndexedDB.Core
2620
21+import Data.Function.Uncurried (Fn2, Fn3)
22+import Data.Function.Uncurried as Fn
23+import Data.Maybe (Maybe)
24+import Data.Nullable (Nullable, toNullable)
25+import Data.String.Read (read)
26+import Database.IndexedDB.IDBKey.Internal (class IDBKey, Key, toKey, unsafeFromKey)
27+import Effect.Aff (Aff)
28+import Effect.Aff.Compat (EffectFnAff, fromEffectFnAff)
29+import Foreign (Foreign, unsafeFromForeign, unsafeToForeign)
30+import Prelude (Unit, ($), (>>>), (<<<), map)
2731
2832--------------------
2933-- INTERFACES
3034--
31−class IDBCursor cursor where
32− advance :: forall e. cursor -> Int -> Eff (idb :: IDB, exception :: EXCEPTION | e) Unit
33− continue :: forall e. cursor -> Maybe Key -> Eff (idb :: IDB, exception :: EXCEPTION | e) Unit
34− continuePrimaryKey :: forall e. cursor -> Key -> Key -> Eff (idb :: IDB, exception :: EXCEPTION | e) Unit
35− delete :: forall e. cursor -> Aff (idb ::IDB, exception :: EXCEPTION | e) Unit
36− update :: forall val e. cursor -> val -> Aff (idb :: IDB, exception :: EXCEPTION | e) Key
3735
36+-- | Advances the cursor through the next count records in range.
37+advance
38+ :: forall cursor
39+ . (IDBCursor cursor)
40+ => cursor
41+ -> Int
42+ -> Aff Unit
43+advance c =
44+ fromEffectFnAff <<< Fn.runFn2 _advance c
45+
46+-- | Advances the cursor to the next record in range matching or after key.
47+continue
48+ :: forall k cursor
49+ . (IDBKey k)
50+ => (IDBCursor cursor)
51+ => cursor
52+ -> Maybe k
53+ -> Aff Unit
54+continue c mk =
55+ fromEffectFnAff $ Fn.runFn2 _continue c (toNullable $ map (toKey >>> unsafeFromKey) mk)
56+
57+-- | Advances the cursor to the next record in range matching or after key and primaryKey. Throws an "InvalidAccessError" DOMException if the source is not an index.
58+continuePrimaryKey
59+ :: forall k cursor
60+ . (IDBKey k)
61+ => (IDBCursor cursor)
62+ => cursor
63+ -> k
64+ -> k
65+ -> Aff Unit
66+continuePrimaryKey c k1 k2 =
67+ fromEffectFnAff $ Fn.runFn3 _continuePrimaryKey c (unsafeFromKey $ toKey k1) (unsafeFromKey $ toKey k2)
68+
69+-- | Delete the record pointed at by the cursor with a new value.
70+delete
71+ :: forall cursor
72+ . (IDBCursor cursor)
73+ => cursor
74+ -> Aff Unit
75+delete =
76+ fromEffectFnAff <<< _delete
77+
78+-- | Update the record pointed at by the cursor with a new value.
79+-- |
80+-- | Throws a "DataError" DOMException if the effective object store uses
81+-- | in-line keys and the key would have changed.
82+update
83+ :: forall val cursor
84+ . (IDBCursor cursor)
85+ => cursor
86+ -> val
87+ -> Aff Key
88+update c =
89+ map toKey <<< fromEffectFnAff <<< Fn.runFn2 _update c <<< unsafeToForeign
3890
3991--------------------
4092-- ATTRIBUTES
4193--
42−direction :: KeyCursor -> CursorDirection
43−direction =
44− Fn.runFn2 _direction (parse >>> toNullable)
4594
46−
47−key :: KeyCursor -> Key
95+-- | Returns the direction (Next|NextUnique|Prev|PrevUnique) of the cursor.
96+direction
97+ :: forall cursor
98+ . (IDBConcreteCursor cursor)
99+ => cursor
100+ -> CursorDirection
101+direction =
102+ Fn.runFn2 _direction (read >>> toNullable)
103+
104+-- | Returns the key of the cursor. Throws a "InvalidStateError" DOMException
105+-- | if the cursor is advancing or is finished.
106+key
107+ :: forall cursor
108+ . (IDBConcreteCursor cursor)
109+ => cursor
110+ -> Aff Key
48111key =
49− _key
50−
51−
52−primaryKey :: KeyCursor -> Key
112+ map toKey <<< fromEffectFnAff <<< _key
113+
114+-- | Returns the effective key of the cursor. Throws a "InvalidStateError" DOMException
115+-- | if the cursor is advancing or is finished.
116+primaryKey
117+ :: forall cursor
118+ . (IDBConcreteCursor cursor)
119+ => cursor
120+ -> Aff Key
53121primaryKey =
54− _primaryKey
55−
56−
57−source :: KeyCursor -> CursorSource
122+ map toKey <<< fromEffectFnAff <<< _primaryKey
123+
124+-- | Returns the IDBObjectStore or IDBIndex the cursor was opened from.
125+source
126+ :: forall cursor
127+ . (IDBConcreteCursor cursor)
128+ => cursor
129+ -> CursorSource
58130source =
59131 Fn.runFn3 _source ObjectStore Index
60132
61−
62−-- NOTE: Polymorphism not used here cause *we want* those methods to be distincs for
63−-- the concrete types mostly for consistency with the Database.IndexedDB.IDBIndex module
64−-- where the IDBIndex interfaces is shared by Index and ObjectStore, but attributes of
65−-- respective concrete types aren't shared.
66−direction' :: ValueCursor -> CursorDirection
67−direction' =
68− Fn.runFn2 _direction (parse >>> toNullable)
69−
70−
71−key' :: ValueCursor -> Key
72−key' =
73− _key
74−
75−
76−primaryKey' :: ValueCursor -> Key
77−primaryKey' =
78− _primaryKey
79−
80−
81−source' :: ValueCursor -> CursorSource
82−source' =
83− Fn.runFn3 _source ObjectStore Index
84−
85−
86−value :: forall val. ValueCursor -> val
133+value
134+ :: forall val
135+ . ValueCursor
136+ -> val
87137value =
88138 _value >>> unsafeFromForeign
89139
90−
91−--------------------
92−-- INSTANCES
93−--
94−instance keyCursorKeyCursor :: IDBCursor KeyCursor where
95− advance = Fn.runFn2 _advance
96− continue c mk = Fn.runFn2 _continue c (toNullable mk)
97− continuePrimaryKey = Fn.runFn3 _continuePrimaryKey
98− delete = _delete
99− update c = toForeign >>> Fn.runFn2 _update c
100−
101−
102−instance valueCursorKeyCursor :: IDBCursor ValueCursor where
103− advance = Fn.runFn2 _advance
104− continue c mk = Fn.runFn2 _continue c (toNullable mk)
105− continuePrimaryKey = Fn.runFn3 _continuePrimaryKey
106− delete = _delete
107− update c = toForeign >>> Fn.runFn2 _update c
108−
109−
110140--------------------
111141-- FFI
112142--
113−foreign import _advance :: forall cursor e. Fn2 cursor Int (Eff (idb :: IDB, exception :: EXCEPTION | e) Unit)
114−
115−
116−foreign import _continue :: forall cursor e. Fn2 cursor (Nullable Key) (Eff (idb :: IDB, exception :: EXCEPTION | e) Unit)
117−
118−
119−foreign import _continuePrimaryKey :: forall cursor e. Fn3 cursor Key Key (Eff (idb :: IDB, exception :: EXCEPTION | e) Unit)
120−
121−
122−foreign import _delete :: forall cursor e. cursor -> (Aff (idb ::IDB, exception :: EXCEPTION | e) Unit)
123−
124−
125−foreign import _direction :: forall cursor. Fn2 (String -> Nullable CursorDirection) cursor CursorDirection
126−
127−
128−foreign import _key :: forall cursor. cursor -> Key
129−
130−
131−foreign import _primaryKey :: forall cursor. cursor -> Key
132−
133−
134−foreign import _source :: forall cursor. Fn3 (ObjectStore -> CursorSource) (Index -> CursorSource) cursor CursorSource
135−
136−
137−foreign import _update :: forall cursor e. Fn2 cursor Foreign (Aff (idb :: IDB, exception :: EXCEPTION | e) Key)
138−
139143
140−foreign import _value :: forall cursor val. cursor -> val
144+foreign import _advance
145+ :: forall cursor
146+ . Fn2 cursor Int (EffectFnAff Unit)
147+
148+foreign import _continue
149+ :: forall cursor
150+ . Fn2 cursor (Nullable Foreign) (EffectFnAff Unit)
151+
152+foreign import _continuePrimaryKey
153+ :: forall cursor
154+ . Fn3 cursor Foreign Foreign (EffectFnAff Unit)
155+
156+foreign import _delete
157+ :: forall cursor
158+ . cursor
159+ -> (EffectFnAff Unit)
160+
161+foreign import _direction
162+ :: forall cursor
163+ . Fn2 (String -> Nullable CursorDirection) cursor CursorDirection
164+
165+foreign import _key
166+ :: forall cursor
167+ . cursor
168+ -> EffectFnAff Key
169+
170+foreign import _primaryKey
171+ :: forall cursor
172+ . cursor
173+ -> EffectFnAff Key
174+
175+foreign import _source
176+ :: forall cursor
177+ . Fn3 (ObjectStore -> CursorSource) (Index -> CursorSource) cursor CursorSource
178+
179+foreign import _update
180+ :: forall cursor
181+ . Fn2 cursor Foreign (EffectFnAff Foreign)
182+
183+foreign import _value
184+ :: forall cursor val
185+ . cursor
186+ -> val
src/Database/IndexedDB/IDBDatabase.js+72−29
@@ -1,20 +1,25 @@
1−const $Core = require('Database.IndexedDB.Core/foreign');
2−
3−const toArray = $Core.toArray;
1+const toArray = function toArray(xs) {
2+ return Array.prototype.slice.apply(xs);
3+};
44
55
6−exports._close = function _close(db) {
7− return function eff() {
6+export function _close(db) {
7+ return function aff(error, success) {
88 try {
99 db.close();
10+ success();
1011 } catch (e) {
11− throw new Error(e.name);
12+ error(e);
1213 }
14+
15+ return function canceler(_, cancelerError) {
16+ cancelerError(new Error("Can't cancel IDB Effects"));
17+ };
1318 };
1419};
1520
16−exports._createObjectStore = function _createObjectStore(db, name, opts) {
17− return function eff() {
21+export function _createObjectStore(db, name, opts) {
22+ return function aff(error, success) {
1823 var keyPath;
1924
2025 try {
@@ -31,76 +36,114 @@ exports._createObjectStore = function _createObjectStore(db, name, opts) {
3136 default:
3237 keyPath = opts.keyPath;
3338 }
34− return db.createObjectStore(name, {
39+
40+ const store = db.createObjectStore(name, {
3541 autoIncrement: opts.autoIncrement,
3642 keyPath: keyPath,
3743 });
44+ success(store);
3845 } catch (e) {
39− throw new Error(e.name);
46+ error(e);
4047 }
48+
49+ return function canceler(_, cancelerError) {
50+ cancelerError(new Error("Can't cancel IDB Effects"));
51+ };
4152 };
4253};
4354
44−exports._deleteObjectStore = function _deleteObjectStore(db, name) {
45− return function eff() {
55+export function _deleteObjectStore(db, name) {
56+ return function aff(error, success) {
4657 try {
4758 db.deleteObjectStore(name);
59+ success();
4860 } catch (e) {
49− throw new Error(e.name);
61+ error(e);
5062 }
63+
64+ return function canceler(_, cancelerError) {
65+ cancelerError(new Error("Can't cancel IDB Effects"));
66+ };
5167 };
5268};
5369
54−exports._name = function _name(db) {
70+export function _name(db) {
5571 return db.name;
5672};
5773
58−exports._objectStoreNames = function _objectStoreNames(db) {
74+export function _objectStoreNames(db) {
5975 return toArray(db.objectStoreNames);
6076};
6177
62−exports._onAbort = function _onAbort(db, f) {
63− return function eff() {
78+export function _onAbort(db, f) {
79+ return function aff(error, success) {
6480 db.onabort = function onabort() {
6581 f();
6682 };
83+ success();
84+
85+ return function canceler(_, cancelerError) {
86+ cancelerError(new Error("Can't cancel IDB Effects"));
87+ };
6788 };
6889};
6990
70−exports._onClose = function _onClose(db, f) {
71− return function eff() {
91+export function _onClose(db, f) {
92+ return function aff(error, success) {
7293 db.onclose = function onclose() {
7394 f();
7495 };
96+ success();
97+
98+ return function canceler(_, cancelerError) {
99+ cancelerError(new Error("Can't cancel IDB Effects"));
100+ };
75101 };
76102};
77103
78−exports._onError = function _onError(db, f) {
79− return function eff() {
104+export function _onError(db, f) {
105+ return function aff(error, success) {
80106 db.onerror = function onerror(e) {
81− f(new Error(e.target.error.name))();
107+ f(e.target.error)();
108+ };
109+ success();
110+
111+ return function canceler(_, cancelerError) {
112+ cancelerError(new Error("Can't cancel IDB Effects"));
82113 };
83114 };
84115};
85116
86−exports._onVersionChange = function _onVersionChange(db, f) {
87− return function eff() {
117+export function _onVersionChange(db, f) {
118+ return function aff(error, success) {
88119 db.onversionchange = function onversionchange(e) {
89120 f({ oldVersion: e.oldVersion, newVersion: e.newVersion })();
90121 };
122+ success();
123+
124+ return function canceler(_, cancelerError) {
125+ cancelerError(new Error("Can't cancel IDB Effects"));
126+ };
91127 };
92128};
93129
94−exports._transaction = function _transaction(show, db, stores, mode) {
95− return function eff() {
130+export function _transaction(db, stores, mode) {
131+ return function aff(error, success) {
132+ var transaction;
96133 try {
97− return db.transaction(stores, show(mode));
134+ transaction = db.transaction(stores, mode);
135+ success(transaction);
98136 } catch (e) {
99− throw new Error(e.name);
137+ error(e);
100138 }
139+
140+ return function canceler(_, cancelerError, cancelerSuccess) {
141+ transaction.abort();
142+ cancelerSuccess();
143+ };
101144 };
102145};
103146
104−exports._version = function _version(db) {
147+export function _version(db) {
105148 return db.version;
106149};
src/Database/IndexedDB/IDBDatabase.purs+160−74
@@ -1,129 +1,215 @@
1+-- | Each origin has an associated set of databases. A database has zero or more object
2+-- | stores which hold the data stored in the database.
13module Database.IndexedDB.IDBDatabase
2− ( class IDBDatabase, close, createObjectStore, deleteObjectStore, transaction
3− , StoreName
4+ -- * Types
5+ ( StoreName
6+ , ObjectStoreParameters
7+ , defaultParameters
8+
9+ -- * Interface
10+ , close
11+ , createObjectStore
12+ , deleteObjectStore
13+ , transaction
14+
15+ -- * Attributes
416 , name
517 , objectStoreNames
618 , version
19+
20+ -- * Event handlers
721 , onAbort
822 , onClose
923 , onError
1024 , onVersionChange
1125 ) where
1226
13−import Prelude (Unit, show)
14−
15−import Control.Monad.Eff (Eff)
16−import Control.Monad.Eff.Exception (EXCEPTION, Error)
17−import Data.Function.Uncurried as Fn
18−import Data.Function.Uncurried (Fn2, Fn3, Fn4)
19−
2027import Database.IndexedDB.Core
21−import Database.IndexedDB.IDBObjectStore (IDBObjectStoreParameters)
2228
29+import Data.Function.Uncurried (Fn2, Fn3)
30+import Data.Function.Uncurried as Fn
31+import Effect (Effect)
32+import Effect.Aff (Aff)
33+import Effect.Aff.Compat (EffectFnAff, fromEffectFnAff)
34+import Effect.Exception (Error)
35+import Prelude (Unit, show, (<<<), ($))
2336
2437--------------------
25−-- INTERFACE
38+-- TYPES
2639--
27−class IDBDatabase db where
28− close :: forall e. db -> Eff (idb :: IDB, exception :: EXCEPTION | e) Unit
29− createObjectStore :: forall e. db -> StoreName -> IDBObjectStoreParameters -> Eff (idb :: IDB, exception :: EXCEPTION | e) ObjectStore
30− deleteObjectStore :: forall e. db -> StoreName -> Eff (idb :: IDB, exception :: EXCEPTION | e) ObjectStore
31− transaction :: forall e. db -> KeyPath -> TransactionMode -> Eff (idb :: IDB, exception :: EXCEPTION | e) Transaction
32−
3340
41+-- | Type alias for StoreName
3442type StoreName = String
3543
44+-- | Options provided when creating an object store.
45+type ObjectStoreParameters =
46+ { keyPath :: KeyPath
47+ , autoIncrement :: Boolean
48+ }
49+
50+defaultParameters :: ObjectStoreParameters
51+defaultParameters =
52+ { keyPath: []
53+ , autoIncrement: false
54+ }
55+
56+--------------------
57+-- INTERFACE
58+--
59+
60+-- | Closes the connection once all running transactions have finished.
61+close
62+ :: forall db
63+ . (IDBDatabase db)
64+ => db
65+ -> Aff Unit
66+close =
67+ fromEffectFnAff <<< _close
68+
69+-- | Creates a new object store with the given name and options and returns a new IDBObjectStore.
70+-- |
71+-- | Throws a "InvalidStateError" DOMException if not called within an upgrade transaction
72+createObjectStore
73+ :: forall db
74+ . (IDBDatabase db)
75+ => db
76+ -> StoreName
77+ -> ObjectStoreParameters
78+ -> Aff ObjectStore
79+createObjectStore db name' opts =
80+ fromEffectFnAff $ Fn.runFn3 _createObjectStore db name' opts
81+
82+-- | Deletes the object store with the given name.
83+-- |
84+-- | Throws a "InvalidStateError" DOMException if not called within an upgrade transaction.
85+deleteObjectStore
86+ :: forall db
87+ . (IDBDatabase db)
88+ => db
89+ -> StoreName
90+ -> Aff ObjectStore
91+deleteObjectStore db name' =
92+ fromEffectFnAff $ Fn.runFn2 _deleteObjectStore db name'
93+
94+-- | Returns a new transaction with the given mode (ReadOnly|ReadWrite)
95+-- | and scope which in the form of an array of object store names.
96+transaction
97+ :: forall db
98+ . (IDBDatabase db)
99+ => db
100+ -> Array StoreName
101+ -> TransactionMode
102+ -> Aff Transaction
103+transaction db stores mode' =
104+ fromEffectFnAff $ Fn.runFn3 _transaction db stores (show mode')
36105
37106--------------------
38107-- ATTRIBUTES
39108--
40−name :: Database -> String
109+
110+-- | Returns the name of the database.
111+name
112+ :: Database
113+ -> String
41114name =
42115 _name
43116
44−
45−objectStoreNames :: Database -> Array String
117+-- | Returns a list of the names of object stores in the database.
118+objectStoreNames
119+ :: Database
120+ -> Array String
46121objectStoreNames =
47122 _objectStoreNames
48123
49−
50−version :: Database -> Int
124+-- | Returns the version of the database.
125+version
126+ :: Database
127+ -> Int
51128version =
52129 _version
53130
54−
55131--------------------
56132-- EVENT HANDLERS
57133--
58−onAbort :: forall e e'. Database -> Eff ( | e') Unit -> Eff (idb :: IDB | e) Unit
134+-- | Event handler for the `abort` event.
135+onAbort
136+ :: Database
137+ -> Effect Unit
138+ -> Aff Unit
59139onAbort db f =
60− Fn.runFn2 _onAbort db f
140+ fromEffectFnAff $ Fn.runFn2 _onAbort db f
61141
62−
63−onClose :: forall e e'. Database -> Eff ( | e') Unit -> Eff (idb :: IDB | e) Unit
142+-- | Event handler for the `close` event.
143+onClose
144+ :: Database
145+ -> Effect Unit
146+ -> Aff Unit
64147onClose db f =
65− Fn.runFn2 _onClose db f
66−
148+ fromEffectFnAff $ Fn.runFn2 _onClose db f
67149
68−onError :: forall e e'. Database -> (Error -> Eff ( | e') Unit) -> Eff (idb :: IDB | e) Unit
150+-- | Event handler for the `error` event.
151+onError
152+ :: Database
153+ -> (Error -> Effect Unit)
154+ -> Aff Unit
69155onError db f =
70− Fn.runFn2 _onError db f
71−
72−
73−onVersionChange :: forall e e'. Database -> ({ oldVersion :: Int, newVersion :: Int } -> Eff ( | e') Unit) -> Eff (idb :: IDB | e) Unit
156+ fromEffectFnAff $ Fn.runFn2 _onError db f
157+
158+-- | Event handler for the `versionchange` event.
159+onVersionChange
160+ :: Database
161+ -> ( { oldVersion :: Int, newVersion :: Int }
162+ -> Effect Unit
163+ )
164+ -> Aff Unit
74165onVersionChange db f =
75− Fn.runFn2 _onVersionChange db f
76−
77−
78−
79−--------------------
80−-- INSTANCES
81−--
82−instance idbDatabaseDatabase :: IDBDatabase Database where
83− close =
84− _close
85−
86− createObjectStore db name' opts =
87− Fn.runFn3 _createObjectStore db name' opts
88−
89− deleteObjectStore db name' =
90− Fn.runFn2 _deleteObjectStore db name'
91−
92− transaction db stores mode' =
93− Fn.runFn4 _transaction show db stores mode'
94−
166+ fromEffectFnAff $ Fn.runFn2 _onVersionChange db f
95167
96168--------------------
97169-- FFI
98170--
99−foreign import _close :: forall db e. db -> Eff (idb :: IDB, exception :: EXCEPTION | e) Unit
100−
101−
102−foreign import _createObjectStore :: forall db e. Fn3 db String { keyPath :: Array String, autoIncrement :: Boolean } (Eff (idb :: IDB, exception :: EXCEPTION | e) ObjectStore)
103−
104−
105−foreign import _deleteObjectStore :: forall db e. Fn2 db String (Eff (idb :: IDB, exception :: EXCEPTION | e) ObjectStore)
106−
107−
108−foreign import _name :: Database -> String
109−
110−
111−foreign import _objectStoreNames :: Database -> Array String
112−
113171
114−foreign import _onAbort :: forall db e e'. Fn2 db (Eff ( | e') Unit) (Eff (idb :: IDB | e) Unit)
172+foreign import _close
173+ :: forall db
174+ . db
175+ -> EffectFnAff Unit
115176
177+foreign import _createObjectStore
178+ :: forall db
179+ . Fn3 db String { keyPath :: Array String, autoIncrement :: Boolean } (EffectFnAff ObjectStore)
116180
117−foreign import _onClose :: forall db e e'. Fn2 db (Eff ( | e') Unit) (Eff (idb :: IDB | e) Unit)
181+foreign import _deleteObjectStore
182+ :: forall db
183+ . Fn2 db String (EffectFnAff ObjectStore)
118184
185+foreign import _name
186+ :: Database
187+ -> String
119188
120−foreign import _onError :: forall db e e'. Fn2 db (Error -> Eff ( | e') Unit) (Eff (idb :: IDB | e) Unit)
189+foreign import _objectStoreNames
190+ :: Database
191+ -> Array String
121192
193+foreign import _onAbort
194+ :: forall db
195+ . Fn2 db (Effect Unit) (EffectFnAff Unit)
122196
123−foreign import _onVersionChange :: forall db e e'. Fn2 db ({ oldVersion :: Int, newVersion :: Int } -> Eff ( | e') Unit) (Eff (idb :: IDB | e) Unit)
197+foreign import _onClose
198+ :: forall db
199+ . Fn2 db (Effect Unit) (EffectFnAff Unit)
124200
201+foreign import _onError
202+ :: forall db
203+ . Fn2 db (Error -> Effect Unit) (EffectFnAff Unit)
125204
126−foreign import _transaction :: forall db e. Fn4 (db -> String) db (Array String) TransactionMode (Eff (idb :: IDB, exception :: EXCEPTION | e) Transaction)
205+foreign import _onVersionChange
206+ :: forall db
207+ . Fn2 db ({ oldVersion :: Int, newVersion :: Int } -> Effect Unit) (EffectFnAff Unit)
127208
209+foreign import _transaction
210+ :: forall db
211+ . Fn3 db (Array String) String (EffectFnAff Transaction)
128212
129−foreign import _version :: Database -> Int
213+foreign import _version
214+ :: Database
215+ -> Int
src/Database/IndexedDB/IDBFactory.js+42−21
@@ -1,6 +1,6 @@
11const errorHandler = function errorHandler(cb) {
22 return function _handler(e) {
3− cb(new Error(e.target.error.name));
3+ cb(e.target.error);
44 };
55};
66
@@ -14,36 +14,57 @@ const noOp2 = function noOp2() {
1414 return noOp;
1515};
1616
17+const noOp3 = function noOp3() {
18+ return noOp2;
19+};
1720
18−exports._deleteDatabase = function _deleteDatabase(name) {
19− return function aff(success, error) {
20− const request = indexedDB.deleteDatabase(name);
21+export function _deleteDatabase(name) {
22+ return function aff(error, success) {
23+ try {
24+ const request = indexedDB.deleteDatabase(name);
2125
22− request.onsuccess = function onSuccess(e) {
23− success(e.oldVersion);
24− };
26+ request.onsuccess = function onSuccess(e) {
27+ success(e.oldVersion);
28+ };
29+
30+ request.onerror = errorHandler(error);
31+ } catch (e) {
32+ error(e);
33+ }
2534
26− request.onerror = errorHandler(error);
35+ return function canceler(_, cancelerError) {
36+ cancelerError(new Error("Can't cancel IDB Effects"));
37+ };
2738 };
2839};
2940
30−exports._open = function _open(fromMaybe, name, mver, req) {
41+export function _open(fromMaybe, name, mver, req) {
3142 const ver = fromMaybe(undefined)(mver);
3243
33− return function aff(success, error) {
34− const request = indexedDB.open(name, ver);
35− request.onsuccess = function onSuccess(e) {
36− success(e.target.result);
37− };
44+ return function aff(error, success) {
45+ try {
46+ const request = indexedDB.open(name, ver);
47+ request.onsuccess = function onSuccess(e) {
48+ success(e.target.result);
49+ };
3850
39− request.onblocked = function onBlocked() {
40− fromMaybe(noOp)(req.onBlocked)();
41− };
51+ request.onblocked = function onBlocked() {
52+ fromMaybe(noOp)(req.onBlocked)();
53+ };
4254
43− request.onupgradeneeded = function onUpgradeNeeded(e) {
44− fromMaybe(noOp2)(req.onUpgradeNeeded)(e.target.result)(e.target.transaction)();
45− };
55+ request.onupgradeneeded = function onUpgradeNeeded(e) {
56+ const meta = { oldVersion: e.oldVersion };
57+ // eslint-disable-next-line max-len
58+ fromMaybe(noOp3)(req.onUpgradeNeeded)(e.target.result)(e.target.transaction)(meta)();
59+ };
4660
47− request.onerror = errorHandler(error);
61+ request.onerror = errorHandler(error);
62+ } catch (e) {
63+ error(e);
64+ }
65+
66+ return function canceler(_, cancelerError) {
67+ cancelerError(new Error("Can't cancel IDB Effects"));
68+ };
4869 };
4970};
src/Database/IndexedDB/IDBFactory.purs+61−21
@@ -1,39 +1,79 @@
1−module Database.IndexedDB.IDBFactory where
1+-- | Database objects are accessed through methods on the IDBFactory interface.
2+-- | A single object implementing this interface is present in the global scope
3+-- | of environments that support Indexed DB operations.
4+module Database.IndexedDB.IDBFactory
5+ -- * Types
6+ ( Callbacks
7+ , DatabaseName
8+ , Version
29
3−import Prelude (Unit)
4−
5−import Control.Monad.Aff (Aff)
6−import Control.Monad.Eff (Eff)
7−import Data.Function.Uncurried as Fn
8−import Data.Function.Uncurried (Fn4)
9−import Data.Maybe (Maybe, fromMaybe)
10+ -- * Interface
11+ , deleteDatabase
12+ , open
13+ ) where
1014
1115import Database.IndexedDB.Core
1216
17+import Data.Function.Uncurried (Fn4)
18+import Data.Function.Uncurried as Fn
19+import Data.Maybe (Maybe, fromMaybe)
20+import Effect (Effect)
21+import Effect.Aff (Aff)
22+import Effect.Aff.Compat (EffectFnAff, fromEffectFnAff)
23+import Prelude (Unit, ($), (<<<))
1324
1425--------------------
15−-- INTERFACE
26+-- TYPES
1627--
17−type OpenRequest e =
18− { onBlocked :: Maybe (Eff (| e) Unit)
19− , onUpgradeNeeded :: Maybe (Database -> Eff (| e) Unit)
28+
29+-- Type alias for binding listeners to an initial open action.
30+type Callbacks =
31+ { onBlocked :: Maybe (Effect Unit)
32+ , onUpgradeNeeded :: Maybe (Database -> Transaction -> { oldVersion :: Int } -> Effect Unit)
2033 }
2134
35+-- | Type alias for DatabaseName.
36+type DatabaseName = String
2237
23−deleteDatabase :: forall e. String -> Aff (idb :: IDB | e) Int
24−deleteDatabase =
25− _deleteDatabase
38+-- | Type alias for Version.
39+type Version = Int
2640
41+--------------------
42+-- INTERFACE
43+--
2744
28−open :: forall e e'. String -> Maybe Int -> OpenRequest e' -> Aff (idb :: IDB | e) Database
29−open name mver req =
30− Fn.runFn4 _open fromMaybe name mver req
45+-- | Attempts to delete the named database. If the database already exists
46+-- | and there are open connections that don’t close in response to a
47+-- | `versionchange` event, the request will be blocked until all they close.
48+deleteDatabase
49+ :: DatabaseName
50+ -> Aff Int
51+deleteDatabase =
52+ fromEffectFnAff <<< _deleteDatabase
3153
54+-- | Attempts to open a connection to the named database with the specified version.
55+-- | If the database already exists with a lower version and there are open connections
56+-- | that don’t close in response to a versionchange event, the request will be blocked
57+-- | until all they close, then an upgrade will occur. If the database already exists with
58+-- | a higher version the request will fail.
59+-- |
60+-- | When the version isn't provided (`Nothing`), attempts to open a connection to the
61+-- | named database with the current version, or 1 if it does not already exist.
62+open
63+ :: DatabaseName
64+ -> Maybe Version
65+ -> Callbacks
66+ -> Aff Database
67+open name mver req =
68+ fromEffectFnAff $ Fn.runFn4 _open fromMaybe name mver req
3269
3370--------------------
3471-- FFI
3572--
36−foreign import _deleteDatabase :: forall e. String -> Aff (idb :: IDB | e) Int
37−
73+foreign import _deleteDatabase
74+ :: String
75+ -> EffectFnAff Int
3876
39−foreign import _open :: forall a e e'. Fn4 (a -> Maybe a -> a) String (Maybe Int) (OpenRequest e') (Aff (idb :: IDB | e) Database)
77+foreign import _open
78+ :: forall a
79+ . Fn4 (a -> Maybe a -> a) String (Maybe Int) (Callbacks) (EffectFnAff Database)
src/Database/IndexedDB/IDBIndex.jsnew file+171
@@ -0,0 +1,171 @@
1+const errorHandler = function errorHandler(cb) {
2+ return function _handler(e) {
3+ cb(e.target.error);
4+ };
5+};
6+
7+const successHandler = function successHandler(cb) {
8+ return function _handler(e) {
9+ cb(e.target.result);
10+ };
11+};
12+
13+
14+export function _keyPath(index) {
15+ const path = index.keyPath;
16+
17+ if (Array.isArray(path)) {
18+ return path;
19+ }
20+
21+ if (typeof path === 'string' && path !== '') {
22+ return [path];
23+ }
24+
25+ return [];
26+};
27+
28+export function _multiEntry(index) {
29+ return index.multiEntry;
30+};
31+
32+export function _name(index) {
33+ return index.name;
34+};
35+
36+export function _objectStore(index) {
37+ return index.obectStore;
38+};
39+
40+export function _unique(index) {
41+ return index.unique;
42+};
43+
44+export function _count(index, query) {
45+ return function aff(error, success) {
46+ try {
47+ const request = index.count(query);
48+ request.onsuccess = successHandler(success);
49+ request.onerror = errorHandler(error);
50+ } catch (e) {
51+ error(e);
52+ }
53+
54+ return function canceler(_, cancelerError) {
55+ cancelerError(new Error("Can't cancel IDB Effects"));
56+ };
57+ };
58+};
59+
60+export function _get(index, range) {
61+ return function aff(error, success) {
62+ try {
63+ const request = index.get(range);
64+ request.onsuccess = successHandler(success);
65+ request.onerror = errorHandler(error);
66+ } catch (e) {
67+ error(e);
68+ }
69+
70+ return function canceler(_, cancelerError) {
71+ cancelerError(new Error("Can't cancel IDB Effects"));
72+ };
73+ };
74+};
75+
76+/*
77+ * NOTE: Require some additional work. The array (which isn't necessarily a list of
78+ * polymorphic types in js) can't be easily translated to a PureScript array.
79+ *
80+ * However, it may be doable to convert the result to some key / value structure with values of
81+ * different types.
82+export function _getAll(index, query, count) {
83+ return function aff(error, success) {
84+ const request = index.getAll(query, count);
85+ request.onsuccess = successHandler(success);
86+ request.onerror = errorHandler(error);
87+ };
88+};
89+*/
90+
91+export function _getAllKeys(index, range, count) {
92+ return function aff(error, success) {
93+ try {
94+ const request = index.getAllKeys(range, count || undefined);
95+ request.onsuccess = successHandler(success);
96+ request.onerror = errorHandler(error);
97+ } catch (e) {
98+ error(e);
99+ }
100+
101+ return function canceler(_, cancelerError) {
102+ cancelerError(new Error("Can't cancel IDB Effects"));
103+ };
104+ };
105+};
106+
107+export function _getKey(index, range) {
108+ return function aff(error, success) {
109+ try {
110+ const request = index.getKey(range);
111+ request.onsuccess = successHandler(success);
112+ request.onerror = errorHandler(error);
113+ } catch (e) {
114+ error(e);
115+ }
116+
117+ return function canceler(_, cancelerError) {
118+ cancelerError(new Error("Can't cancel IDB Effects"));
119+ };
120+ };
121+};
122+
123+export function _openCursor(index, query, dir, cb) {
124+ return function aff(error, success) {
125+ try {
126+ const request = index.openCursor(query, dir);
127+ request.onsuccess = function onSuccess(e) {
128+ if (e.target.result != null) {
129+ cb.onSuccess(e.target.result)();
130+ } else {
131+ cb.onComplete();
132+ }
133+ };
134+ request.onerror = function onError(e) {
135+ cb.onError(e.target.error);
136+ };
137+ success();
138+ } catch (e) {
139+ error(e);
140+ }
141+
142+ return function canceler(_, cancelerError) {
143+ cancelerError(new Error("Can't cancel IDB Effects"));
144+ };
145+ };
146+};
147+
148+export function _openKeyCursor(index, query, dir, cb) {
149+ return function aff(error, success) {
150+ try {
151+ const request = index.openKeyCursor(query, dir);
152+ request.onsuccess = function onSuccess(e) {
153+ if (e.target.result != null) {
154+ cb.onSuccess(e.target.result)();
155+ } else {
156+ cb.onComplete();
157+ }
158+ };
159+ request.onerror = function onError(e) {
160+ cb.onError(e.target.error);
161+ };
162+ success();
163+ } catch (e) {
164+ error(e);
165+ }
166+
167+ return function canceler(_, cancelerError) {
168+ cancelerError(new Error("Can't cancel IDB Effects"));
169+ };
170+ };
171+};
src/Database/IndexedDB/IDBIndex.purs+199−7
@@ -1,14 +1,206 @@
1+-- | An index allows looking up records in an object store using properties of the values
2+-- | in the object stores records.
13module Database.IndexedDB.IDBIndex
2− ( module Database.IndexedDB.IDBIndex.Internal
3− ) where
4+ -- * Types
5+ ( Callbacks
6+
7+ -- * Interface
8+ , count
9+ , get
10+ , getAllKeys
11+ , getKey
12+ , openCursor
13+ , openKeyCursor
414
5−import Database.IndexedDB.IDBIndex.Internal
6− ( class IDBIndex, count, get, getAllKeys, getKey, openCursor, openKeyCursor
7− , IDBIndexParameters
15+ -- * Attributes
816 , keyPath
917 , multiEntry
1018 , name
1119 , objectStore
1220 , unique
13− , defaultParameters
14− )
21+ ) where
22+
23+import Database.IndexedDB.Core
24+
25+import Data.Function.Uncurried (Fn2, Fn3, Fn4)
26+import Data.Function.Uncurried as Fn
27+import Data.Maybe (Maybe)
28+import Data.Nullable (Nullable, toMaybe, toNullable)
29+import Database.IndexedDB.IDBKey.Internal (Key, toKey)
30+import Effect (Effect)
31+import Effect.Aff (Aff)
32+import Effect.Aff.Compat (EffectFnAff, fromEffectFnAff)
33+import Effect.Exception (Error)
34+import Foreign (Foreign, unsafeFromForeign)
35+import Prelude (Unit, map, show, ($), (>>>))
36+
37+--------------------
38+-- TYPES
39+--
40+
41+-- | Callbacks to manipulate a cursor from an Open*Cursor call
42+type Callbacks cursor =
43+ { onSuccess :: cursor -> Effect Unit
44+ , onError :: Error -> Effect Unit
45+ , onComplete :: Effect Unit
46+ }
47+
48+--------------------
49+-- INTERFACES
50+--
51+
52+-- | Retrieves the number of records matching the key range in query.
53+count
54+ :: forall index
55+ . (IDBIndex index)
56+ => index
57+ -> Maybe KeyRange
58+ -> Aff Int
59+count index range =
60+ fromEffectFnAff $ Fn.runFn2 _count index (toNullable range)
61+
62+-- | Retrieves the value of the first record matching the given key range in query.
63+-- |
64+-- | NOTE
65+-- | The coercion from `a` to any type is unsafe and might throw a runtime error if incorrect.
66+get
67+ :: forall a index
68+ . (IDBIndex index)
69+ => index
70+ -> KeyRange
71+ -> Aff (Maybe a)
72+get index range =
73+ map (toMaybe >>> map unsafeFromForeign) $ fromEffectFnAff $ Fn.runFn2 _get index range
74+
75+-- | Retrieves the keys of records matching the given key range in query
76+-- | (up to the number given if given).
77+getAllKeys
78+ :: forall index
79+ . (IDBIndex index)
80+ => index
81+ -> Maybe KeyRange
82+ -> Maybe Int
83+ -> Aff (Array Key)
84+getAllKeys index range n =
85+ map (map toKey) $ fromEffectFnAff $ Fn.runFn3 _getAllKeys index (toNullable range) (toNullable n)
86+
87+-- | Retrieves the key of the first record matching the given key or key range in query.
88+getKey
89+ :: forall index
90+ . (IDBIndex index)
91+ => index
92+ -> KeyRange
93+ -> Aff (Maybe Key)
94+getKey index range =
95+ map (toMaybe >>> map toKey) $ fromEffectFnAff $ Fn.runFn2 _getKey index range
96+
97+-- | Opens a ValueCursor over the records matching query, ordered by direction.
98+-- | If query is `Nothing`, all records in index are matched.
99+openCursor
100+ :: forall index
101+ . (IDBIndex index)
102+ => index
103+ -> Maybe KeyRange
104+ -> CursorDirection
105+ -> Callbacks ValueCursor
106+ -> Aff Unit
107+openCursor index range dir cb =
108+ fromEffectFnAff $ Fn.runFn4 _openCursor index (toNullable range) (show dir) cb
109+
110+-- | Opens a KeyCursor over the records matching query, ordered by direction.
111+-- | If query is `Nothing`, all records in index are matched.
112+openKeyCursor
113+ :: forall index
114+ . (IDBIndex index)
115+ => index
116+ -> Maybe KeyRange
117+ -> CursorDirection
118+ -> Callbacks KeyCursor
119+ -> Aff Unit
120+openKeyCursor index range dir cb =
121+ fromEffectFnAff $ Fn.runFn4 _openKeyCursor index (toNullable range) (show dir) cb
122+
123+--------------------
124+-- ATTRIBUTES
125+--
126+-- | Returns the key path of the index.
127+keyPath
128+ :: Index
129+ -> KeyPath
130+keyPath =
131+ _keyPath
132+
133+-- | Returns true if the index's multiEntry flag is set.
134+multiEntry
135+ :: Index
136+ -> Boolean
137+multiEntry =
138+ _multiEntry
139+
140+-- | Returns the name of the index.
141+name
142+ :: Index
143+ -> String
144+name =
145+ _name
146+
147+-- | Returns the IDBObjectStore the index belongs to.
148+objectStore
149+ :: Index
150+ -> ObjectStore
151+objectStore =
152+ _objectStore
153+
154+-- | Returns true if the index's unique flag is set.
155+unique
156+ :: Index
157+ -> Boolean
158+unique =
159+ _unique
160+
161+--------------------
162+-- FFI
163+--
164+foreign import _keyPath
165+ :: Index
166+ -> Array String
167+
168+foreign import _multiEntry
169+ :: Index
170+ -> Boolean
171+
172+foreign import _name
173+ :: Index
174+ -> String
175+
176+foreign import _objectStore
177+ :: Index
178+ -> ObjectStore
179+
180+foreign import _unique
181+ :: Index
182+ -> Boolean
183+
184+foreign import _count
185+ :: forall index
186+ . Fn2 index (Nullable KeyRange) (EffectFnAff Int)
187+
188+foreign import _get
189+ :: forall index
190+ . Fn2 index KeyRange (EffectFnAff (Nullable Foreign))
191+
192+foreign import _getAllKeys
193+ :: forall index
194+ . Fn3 index (Nullable KeyRange) (Nullable Int) (EffectFnAff (Array Foreign))
195+
196+foreign import _getKey
197+ :: forall index
198+ . Fn2 index KeyRange (EffectFnAff (Nullable Foreign))
199+
200+foreign import _openCursor
201+ :: forall index
202+ . Fn4 index (Nullable KeyRange) String (Callbacks ValueCursor) (EffectFnAff Unit)
203+
204+foreign import _openKeyCursor
205+ :: forall index
206+ . Fn4 index (Nullable KeyRange) String (Callbacks KeyCursor) (EffectFnAff Unit)
src/Database/IndexedDB/IDBIndex/Internal.jsdeleted−109
@@ -1,109 +0,0 @@
1−const errorHandler = function errorHandler(cb) {
2− return function _handler(e) {
3− cb(new Error(e.target.error.name));
4− };
5−};
6−
7−const successHandler = function successHandler(cb) {
8− return function _handler(e) {
9− cb(e.target.result);
10− };
11−};
12−
13−
14−exports._keyPath = function _keyPath(index) {
15− const path = index.keyPath;
16−
17− if (Array.isArray(path)) {
18− return path;
19− }
20−
21− if (typeof path === 'string' && path !== '') {
22− return [path];
23− }
24−
25− return [];
26−};
27−
28−exports._multiEntry = function _multiEntry(index) {
29− return index.multiEntry;
30−};
31−
32−exports._name = function _name(index) {
33− return index.name;
34−};
35−
36−exports._objectStore = function _objectStore(index) {
37− return index.obectStore;
38−};
39−
40−exports._unique = function _unique(index) {
41− return index.unique;
42−};
43−
44−exports._count = function _count(index, query) {
45− return function aff(success, error) {
46− try {
47− const request = index.count(query);
48− request.onsuccess = successHandler(success);
49− request.onerror = errorHandler(error);
50− } catch (e) {
51− error(new Error(e.name));
52− }
53− };
54−};
55−
56−exports._get = function _get(index, range) {
57− return function aff(success, error) {
58− const request = index.get(range);
59− request.onsuccess = successHandler(success);
60− request.onerror = errorHandler(error);
61− };
62−};
63−
64−/*
65− * NOTE: Require some additional work. The array (which isn't necessarily a list of
66− * polymorphic types in js) can't be easily translated to a PureScript array.
67− *
68− * However, it may be doable to convert the result to some key / value structure with values of
69− * different types.
70−exports._getAll = function _getAll(index, query, count) {
71− return function aff(success, error) {
72− const request = index.getAll(query, count);
73− request.onsuccess = successHandler(success);
74− request.onerror = errorHandler(error);
75− };
76−};
77−*/
78−
79−exports._getAllKeys = function _getAllKeys(index, range, count) {
80− return function aff(success, error) {
81− const request = index.getAllKeys(range, count || undefined);
82− request.onsuccess = successHandler(success);
83− request.onerror = errorHandler(error);
84− };
85−};
86−
87−exports._getKey = function _getKey(index, range) {
88− return function aff(success, error) {
89− const request = index.getKey(range);
90− request.onsuccess = successHandler(success);
91− request.onerror = errorHandler(error);
92− };
93−};
94−
95−exports._openCursor = function _openCursor(index, query, dir) {
96− return function aff(success, error) {
97− const request = index.openCursor(query, dir);
98− request.onsuccess = successHandler(success);
99− request.onerror = errorHandler(error);
100− };
101−};
102−
103−exports._openKeyCursor = function _openKeyCursor(index, query, dir) {
104− return function aff(success, error) {
105− const request = index.openKeyCursor(query, dir);
106− request.onsuccess = successHandler(success);
107− request.onerror = errorHandler(error);
108− };
109−};
src/Database/IndexedDB/IDBIndex/Internal.pursdeleted−145
@@ -1,145 +0,0 @@
1−module Database.IndexedDB.IDBIndex.Internal where
2−
3−import Prelude
4−
5−import Control.Monad.Aff (Aff)
6−import Data.Foreign (Foreign, unsafeFromForeign)
7−import Data.Function.Uncurried as Fn
8−import Data.Function.Uncurried (Fn2, Fn3)
9−import Data.Maybe (Maybe)
10−import Data.Nullable (Nullable, toMaybe, toNullable)
11−
12−import Database.IndexedDB.Core (IDB, CursorDirection, Index, Key, KeyCursor, KeyRange,
13− KeyPath, ObjectStore, ValueCursor)
14−
15−
16−--------------------
17−-- INTERFACES
18−--
19−class IDBIndex index where
20− count :: forall e. index -> Maybe KeyRange -> Aff (idb :: IDB | e) Int
21− get :: forall a e. index -> KeyRange -> Aff (idb :: IDB | e) (Maybe a)
22− getAllKeys :: forall e. index -> Maybe KeyRange -> Maybe Int -> Aff (idb :: IDB | e) (Array Key)
23− getKey :: forall e. index -> KeyRange -> Aff (idb :: IDB | e) (Maybe Key)
24− openCursor :: forall e. index -> Maybe KeyRange -> CursorDirection -> Aff (idb :: IDB | e) ValueCursor
25− openKeyCursor :: forall e. index -> Maybe KeyRange -> CursorDirection -> Aff (idb :: IDB | e) KeyCursor
26−
27−
28−type IDBIndexParameters =
29− { unique :: Boolean
30− , multiEntry :: Boolean
31− }
32−
33−
34−--------------------
35−-- ATTRIBUTES
36−--
37−keyPath :: Index -> KeyPath
38−keyPath =
39− _keyPath
40−
41−
42−multiEntry :: Index -> Boolean
43−multiEntry =
44− _multiEntry
45−
46−
47−name :: Index -> String
48−name =
49− _name
50−
51−
52−objectStore :: Index -> ObjectStore
53−objectStore =
54− _objectStore
55−
56−
57−unique :: Index -> Boolean
58−unique =
59− _unique
60−
61−
62−--------------------
63−-- INSTANCES
64−--
65−instance idbIndexIndex :: IDBIndex Index where
66− count index range =
67− Fn.runFn2 _count index (toNullable range)
68−
69− get index range =
70− (toMaybe >>> map unsafeFromForeign) <$> Fn.runFn2 _get index range
71−
72− getAllKeys index range count =
73− Fn.runFn3 _getAllKeys index (toNullable range) (toNullable count)
74−
75− getKey index range =
76− toMaybe <$> Fn.runFn2 _getKey index range
77−
78− openCursor index range dir =
79− Fn.runFn3 _openCursor index (toNullable range) (show dir)
80−
81− openKeyCursor index range dir =
82− Fn.runFn3 _openKeyCursor index (toNullable range) (show dir)
83−
84−
85−instance idbIndexObjectStore :: IDBIndex ObjectStore where
86− count store range =
87− Fn.runFn2 _count store (toNullable range)
88−
89− get store range =
90− (toMaybe >>> map unsafeFromForeign) <$> Fn.runFn2 _get store range
91−
92− getAllKeys store range count =
93− Fn.runFn3 _getAllKeys store (toNullable range) (toNullable count)
94−
95− getKey store range =
96− toMaybe <$> Fn.runFn2 _getKey store range
97−
98− openCursor store range dir =
99− Fn.runFn3 _openCursor store (toNullable range) (show dir)
100−
101− openKeyCursor store range dir =
102− Fn.runFn3 _openKeyCursor store (toNullable range) (show dir)
103−
104−
105−defaultParameters :: IDBIndexParameters
106−defaultParameters =
107− { unique : false
108− , multiEntry : false
109− }
110−
111−
112−--------------------
113−-- FFI
114−--
115−foreign import _keyPath :: Index -> Array String
116−
117−
118−foreign import _multiEntry :: Index -> Boolean
119−
120−
121−foreign import _name :: Index -> String
122−
123−
124−foreign import _objectStore :: Index -> ObjectStore
125−
126−
127−foreign import _unique :: Index -> Boolean
128−
129−
130−foreign import _count :: forall index e. Fn2 index (Nullable KeyRange) (Aff (idb :: IDB | e) Int)
131−
132−
133−foreign import _get :: forall index e. Fn2 index KeyRange (Aff (idb :: IDB | e) (Nullable Foreign))
134−
135−
136−foreign import _getAllKeys :: forall index e. Fn3 index (Nullable KeyRange) (Nullable Int) (Aff (idb :: IDB | e) (Array Key))
137−
138−
139−foreign import _getKey :: forall index e. Fn2 index KeyRange (Aff (idb :: IDB | e) (Nullable Key))
140−
141−
142−foreign import _openCursor :: forall index e. Fn3 index (Nullable KeyRange) String (Aff (idb :: IDB | e) ValueCursor)
143−
144−
145−foreign import _openKeyCursor :: forall index e. Fn3 index (Nullable KeyRange) String (Aff (idb :: IDB | e) KeyCursor)
src/Database/IndexedDB/IDBKey.purs+11−1
@@ -1,5 +1,15 @@
1+-- | A key has an associated type which is one of: number, date, string, or array.
2+-- |
3+-- | NOTE: Binary keys aren't supported yet.
14module Database.IndexedDB.IDBKey
25 ( module Database.IndexedDB.IDBKey.Internal
36 ) where
47
5−import Database.IndexedDB.IDBKey.Internal(Key, class IDBKey, toKey, fromKey, unsafeFromKey)
8+import Database.IndexedDB.IDBKey.Internal
9+ ( class IDBKey
10+ , Key
11+ , none
12+ , toKey
13+ , fromKey
14+ , unsafeFromKey
15+ )
src/Database/IndexedDB/IDBKey/Internal.js+3−3
@@ -1,8 +1,8 @@
1−exports._dateTimeToForeign = function _dateTimeToForeign(y, m, d, h, mi, s, ms) {
1+export function _dateTimeToForeign(y, m, d, h, mi, s, ms) {
22 return new Date(y, m, d, h, mi, s, ms);
33};
44
5−exports._readDateTime = function _readDateTime(parse, right, left, date) {
5+export function _readDateTime(parse, right, left, date) {
66 if (Object.getPrototypeOf(date) !== Date.prototype) {
77 return left(typeof date);
88 }
@@ -24,7 +24,7 @@ exports._readDateTime = function _readDateTime(parse, right, left, date) {
2424 return right(mdate);
2525};
2626
27−exports._unsafeReadDateTime = function _unsafeReadDateTime(parse, date) {
27+export function _unsafeReadDateTime(parse, date) {
2828 const y = date.getFullYear();
2929 const m = date.getMonth() + 1;
3030 const d = date.getDate();
src/Database/IndexedDB/IDBKey/Internal.purs+114−93
@@ -1,119 +1,129 @@
1+-- | A key has an associated type which is one of: number, date, string, or array.
2+-- |
3+-- | NOTE: Binary keys aren't supported yet.
14module Database.IndexedDB.IDBKey.Internal
2− ( Key(..)
3− , class IDBKey, toKey , fromKey , unsafeFromKey
4− , extractForeign
5− ) where
5+ ( Key
6+ , class IDBKey
7+ , toKey
8+ , fromKey
9+ , unsafeFromKey
10+ , none
11+ ) where
612
713import Prelude
814
9−import Control.Alt ((<|>))
10−import Control.Monad.Except (ExceptT(..), runExceptT)
11−import Data.Date as Date
12−import Data.DateTime (DateTime(..), Time(..))
13−import Data.Enum (fromEnum, toEnum)
14−import Data.Foreign as Foreign
15−import Data.Foreign (Foreign, F)
16−import Data.Function.Uncurried as Fn
17−import Data.Function.Uncurried (Fn2, Fn4, Fn7)
18−import Data.List.NonEmpty (NonEmptyList(..))
19−import Data.List.Types (List(..))
20−import Data.NonEmpty (NonEmpty(..))
21−import Data.Either (Either(..), either, isRight)
22−import Data.Identity (Identity(..))
23−import Data.Nullable (Nullable, toNullable)
24−import Data.Time as Time
25−import Data.Traversable (traverse)
26−
15+import Control.Alt ((<|>))
16+import Control.Monad.Except (ExceptT(..), runExceptT)
17+import Data.Date as Date
18+import Data.DateTime (DateTime(..), Time(..))
19+import Data.Either (Either(..), either, isRight)
20+import Data.Enum (fromEnum, toEnum)
21+import Foreign as Foreign
22+import Foreign (Foreign, F)
23+import Data.Function.Uncurried as Fn
24+import Data.Function.Uncurried (Fn2, Fn4, Fn7)
25+import Data.Identity (Identity(..))
26+import Data.List.NonEmpty (NonEmptyList(..))
27+import Data.List.Types (List(..))
28+import Data.Maybe (Maybe(..))
29+import Data.NonEmpty (NonEmpty(..))
30+import Data.Nullable (Nullable, toNullable)
31+import Data.Time as Time
32+import Data.Traversable (traverse)
2733
2834newtype Key = Key Foreign
2935
30−
31−extractForeign :: Key -> Foreign
32−extractForeign (Key f) =
33− f
34−
35−
3636--------------------
3737-- INTERFACES
3838--
39+-- | Interface describing a key. Use the `unsafeFromKey` to convert a key
40+-- | to a known type (e.g if you only strings as keys, or perfectly knows the
41+-- | type of a given key).
3942class IDBKey a where
40− toKey :: a -> Key
41− fromKey :: Key -> F a
43+ toKey :: a -> Key
44+ fromKey :: Key -> F a
4245 unsafeFromKey :: Key -> a
4346
47+none :: Maybe Key
48+none =
49+ Nothing
4450
4551--------------------
4652-- INSTANCES
4753--
4854instance eqKey :: Eq Key where
4955 eq a b = (runExceptT >>> runIdentity >>> isRight) $
50− eq <$> ((fromKey a) :: F Int) <*> fromKey b
51− <|>
52− eq <$> ((fromKey a) :: F Number) <*> fromKey b
53− <|>
54− eq <$> ((fromKey a) :: F String) <*> fromKey b
55− <|>
56− eq <$> ((fromKey a) :: F DateTime) <*> fromKey b
56+ eq <$> ((fromKey a) :: F Int) <*> fromKey b
57+ <|>
58+ eq <$> ((fromKey a) :: F Number) <*> fromKey b
59+ <|>
60+ eq <$> ((fromKey a) :: F String) <*> fromKey b
61+ <|>
62+ eq <$> ((fromKey a) :: F DateTime) <*> fromKey b
63+ <|>
64+ eq <$> ((fromKey a) :: F (Array Key)) <*> fromKey b
5765 where
58− runIdentity :: forall a. Identity a -> a
59− runIdentity (Identity x) = x
60−
66+ runIdentity :: forall a. Identity a -> a
67+ runIdentity (Identity x) = x
6168
6269instance ordKey :: Ord Key where
63− compare a b = (runExceptT >>> runIdentity >>> either (const LT) id) $
64− compare <$> ((fromKey a) :: F Int) <*> fromKey b
65− <|>
66− compare <$> ((fromKey a) :: F Number) <*> fromKey b
67− <|>
68− compare <$> ((fromKey a) :: F String) <*> fromKey b
69− <|>
70− compare <$> ((fromKey a) :: F DateTime) <*> fromKey b
70+ compare a b = (runExceptT >>> runIdentity >>> either (const LT) identity) $
71+ compare <$> ((fromKey a) :: F Int) <*> fromKey b
72+ <|>
73+ compare <$> ((fromKey a) :: F Number) <*> fromKey b
74+ <|>
75+ compare <$> ((fromKey a) :: F String) <*> fromKey b
76+ <|>
77+ compare <$> ((fromKey a) :: F DateTime) <*> fromKey b
78+ <|>
79+ compare <$> ((fromKey a) :: F (Array Key)) <*> fromKey b
7180 where
72− runIdentity :: forall a. Identity a -> a
73− runIdentity (Identity x) = x
74−
81+ runIdentity :: forall a. Identity a -> a
82+ runIdentity (Identity x) = x
7583
7684instance showKey :: Show Key where
7785 show a = (runExceptT >>> format) $
78− (show <$> (fromKey a :: F Int))
79− <|>
80− (show <$> (fromKey a :: F Number))
81− <|>
82− (show <$> (fromKey a :: F String))
83− <|>
84− (show <$> (fromKey a :: F DateTime))
86+ (show <$> (fromKey a :: F Int))
87+ <|>
88+ (show <$> (fromKey a :: F Number))
89+ <|>
90+ (show <$> (fromKey a :: F String))
91+ <|>
92+ (show <$> (fromKey a :: F DateTime))
93+ <|>
94+ (show <$> (fromKey a :: F (Array Key)))
8595 where
86− format :: forall a. Identity (Either a String) -> String
87− format (Identity x) =
88− either (const "(Key)") (\s -> "(Key " <> s <> ")") x
89−
96+ format :: forall a. Identity (Either a String) -> String
97+ format (Identity x) =
98+ either (const "(Key)") (\s -> "(Key " <> s <> ")") x
9099
91100instance idbKeyKey :: IDBKey Key where
92− toKey = id
93− fromKey = pure
94− unsafeFromKey = id
101+ toKey = identity
102+ fromKey = pure
103+ unsafeFromKey = identity
95104
105+instance idbKeyForeign :: IDBKey Foreign where
106+ toKey = Key
107+ fromKey (Key f) = pure f
108+ unsafeFromKey (Key f) = f
96109
97110instance idbKeyInt :: IDBKey Int where
98− toKey = Foreign.toForeign >>> Key
99− fromKey (Key f) = Foreign.readInt f
111+ toKey = Foreign.unsafeToForeign >>> Key
112+ fromKey (Key f) = Foreign.readInt f
100113 unsafeFromKey (Key f) = Foreign.unsafeFromForeign f
101114
102−
103115instance idbKeyNumber :: IDBKey Number where
104− toKey = Foreign.toForeign >>> Key
105− fromKey (Key f) = Foreign.readNumber f
116+ toKey = Foreign.unsafeToForeign >>> Key
117+ fromKey (Key f) = Foreign.readNumber f
106118 unsafeFromKey (Key f) = Foreign.unsafeFromForeign f
107119
108−
109120instance idbKeyString :: IDBKey String where
110− toKey = Foreign.toForeign >>> Key
111− fromKey (Key f) = Foreign.readString f
121+ toKey = Foreign.unsafeToForeign >>> Key
122+ fromKey (Key f) = Foreign.readString f
112123 unsafeFromKey (Key f) = Foreign.unsafeFromForeign f
113124
114−
115125instance idbKeyDate :: IDBKey DateTime where
116− toKey (DateTime d t) = Key $ Fn.runFn7 _dateTimeToForeign
126+ toKey (DateTime d t) = Key $ Fn.runFn7 _dateTimeToForeign
117127 (fromEnum $ Date.year d)
118128 (fromEnum $ Date.month d)
119129 (fromEnum $ Date.day d)
@@ -121,40 +131,51 @@ instance idbKeyDate :: IDBKey DateTime where
121131 (fromEnum $ Time.minute t)
122132 (fromEnum $ Time.second t)
123133 (fromEnum $ Time.millisecond t)
124− fromKey (Key f) = Fn.runFn4 _readDateTime dateTime dateTimeF dateTimeE f
134+ fromKey (Key f) = Fn.runFn4 _readDateTime dateTime dateTimeF dateTimeE f
125135 unsafeFromKey (Key f) = Fn.runFn2 _unsafeReadDateTime dateTime f
126136
127−
128137instance idbKeyArray :: IDBKey a => IDBKey (Array a) where
129− toKey = Foreign.toForeign >>> Key
130− fromKey (Key f) = Foreign.readArray f >>= traverse (Key >>> fromKey)
138+ toKey = Foreign.unsafeToForeign >>> Key
139+ fromKey (Key f) = Foreign.readArray f >>= traverse (Key >>> fromKey)
131140 unsafeFromKey (Key f) = map unsafeFromKey (Foreign.unsafeFromForeign f)
132141
133−
134−dateTime :: Int -> Int -> Int -> Int -> Int -> Int -> Int -> Nullable DateTime
142+-- FFI constructor to build a DateTime from years, months, days, hours, minutes, seconds and millis
143+dateTime
144+ :: Int -- ^ years
145+ -> Int -- ^ months
146+ -> Int -- ^ days
147+ -> Int -- ^ hours
148+ -> Int -- ^ minutes
149+ -> Int -- ^ seconds
150+ -> Int -- ^ milliseconds
151+ -> Nullable DateTime
135152dateTime y m d h mi s ms =
136153 toNullable $ DateTime
137− <$> (Date.canonicalDate <$> toEnum y <*> toEnum m <*> toEnum d)
138− <*> (Time <$> toEnum h <*> toEnum mi <*> toEnum s <*> toEnum ms)
139−
154+ <$> (Date.canonicalDate <$> toEnum y <*> toEnum m <*> toEnum d)
155+ <*> (Time <$> toEnum h <*> toEnum mi <*> toEnum s <*> toEnum ms)
140156
141−dateTimeF :: DateTime -> F DateTime
157+-- FFI constructor to convert a JS `Date` into a successful `F DateTime`
158+dateTimeF
159+ :: DateTime
160+ -> F DateTime
142161dateTimeF =
143162 Right >>> Identity >>> ExceptT
144163
145−
146−dateTimeE :: String -> F DateTime
164+-- FFI constructor to convert a string into an errored `F DateTime`
165+dateTimeE
166+ :: String
167+ -> F DateTime
147168dateTimeE =
148169 Foreign.TypeMismatch "Date" >>> flip NonEmpty Nil >>> NonEmptyList >>> Left >>> Identity >>> ExceptT
149170
150−
151171--------------------
152172-- FFI
153173--
154−foreign import _dateTimeToForeign :: Fn7 Int Int Int Int Int Int Int Foreign
155−
156−
157−foreign import _readDateTime :: Fn4 (Int -> Int -> Int -> Int -> Int -> Int -> Int -> Nullable DateTime) (DateTime -> F DateTime) (String -> F DateTime) Foreign (F DateTime)
174+foreign import _dateTimeToForeign
175+ :: Fn7 Int Int Int Int Int Int Int Foreign
158176
177+foreign import _readDateTime
178+ :: Fn4 (Int -> Int -> Int -> Int -> Int -> Int -> Int -> Nullable DateTime) (DateTime -> F DateTime) (String -> F DateTime) Foreign (F DateTime)
159179
160−foreign import _unsafeReadDateTime :: Fn2 (Int -> Int -> Int -> Int -> Int -> Int -> Int -> Nullable DateTime) Foreign DateTime
180+foreign import _unsafeReadDateTime
181+ :: Fn2 (Int -> Int -> Int -> Int -> Int -> Int -> Int -> Nullable DateTime) Foreign DateTime
src/Database/IndexedDB/IDBKeyRange.js+9−9
@@ -1,4 +1,4 @@
1−exports._bound = function _bound(lower, upper, lowerOpen, upperOpen) {
1+export function _bound(lower, upper, lowerOpen, upperOpen) {
22 try {
33 return IDBKeyRange.bound(lower, upper, lowerOpen, upperOpen);
44 } catch (e) {
@@ -6,34 +6,34 @@ exports._bound = function _bound(lower, upper, lowerOpen, upperOpen) {
66 }
77};
88
9−exports._includes = function _includes(range, key) {
9+export function _includes(range, key) {
1010 return range.includes(key);
1111};
1212
13−exports._lower = function _lower(range) {
13+export function _lower(range) {
1414 return range.lower;
1515};
1616
17−exports._lowerBound = function _lowerBound(lower, open) {
17+export function _lowerBound(lower, open) {
1818 return IDBKeyRange.lowerBound(lower, open);
1919};
2020
21−exports._lowerOpen = function _lowerOpen(range) {
21+export function _lowerOpen(range) {
2222 return range.lowerOpen;
2323};
2424
25−exports._only = function _only(key) {
25+export function _only(key) {
2626 return IDBKeyRange.only(key);
2727};
2828
29−exports._upper = function _upper(range) {
29+export function _upper(range) {
3030 return range.upper;
3131};
3232
33−exports._upperBound = function _upperBound(upper, open) {
33+export function _upperBound(upper, open) {
3434 return IDBKeyRange.upperBound(upper, open);
3535};
3636
37−exports._upperOpen = function _upperOpen(range) {
37+export function _upperOpen(range) {
3838 return range.upperOpen;
3939};
src/Database/IndexedDB/IDBKeyRange.purs+120−60
@@ -1,110 +1,170 @@
1+-- | A key has an associated type which is one of: number, date, string, binary, or array.
12module Database.IndexedDB.IDBKeyRange
2− ( class IDBKeyRange, includes
3+ -- * Types
4+ ( Open
5+
6+ -- * Constructors
37 , only
48 , lowerBound
59 , upperBound
610 , bound
11+
12+ -- * Interface
13+ , includes
14+
15+ -- * Attributes
716 , lower
817 , upper
918 , lowerOpen
1019 , upperOpen
1120 ) where
1221
13−import Prelude
14−
15−import Data.Foreign (Foreign)
16−import Data.Function.Uncurried as Fn
17−import Data.Function.Uncurried (Fn2, Fn4)
18−import Data.Maybe (Maybe)
19−import Data.Nullable (Nullable, toMaybe)
22+import Prelude (($), (>>>), map)
2023
21−import Database.IndexedDB.Core (KeyRange)
22−import Database.IndexedDB.IDBKey.Internal (class IDBKey, Key(..), toKey, extractForeign)
24+import Foreign (Foreign)
25+import Data.Function.Uncurried as Fn
26+import Data.Function.Uncurried (Fn2, Fn4)
27+import Data.Maybe (Maybe)
28+import Data.Nullable (Nullable, toMaybe)
2329
30+import Database.IndexedDB.Core (class IDBKeyRange, KeyRange)
31+import Database.IndexedDB.IDBKey.Internal (class IDBKey, Key, toKey, unsafeFromKey)
2432
2533--------------------
26−-- INTERFACES
34+-- TYPES
2735--
28−class IDBKeyRange range where
29− includes :: range -> Key -> Boolean
3036
37+-- | Type alias for open
38+type Open = Boolean
3139
32−only :: forall a. (IDBKey a) => a -> KeyRange
33−only key =
34− _only (extractForeign $ toKey key)
35−
40+--------------------
41+-- CONSTRUCTORS
42+--
3643
37−lowerBound :: forall a. (IDBKey a) => a -> Boolean -> KeyRange
44+-- | Returns a new IDBKeyRange spanning only key.
45+only
46+ :: forall a
47+ . (IDBKey a)
48+ => a
49+ -> KeyRange
50+only key =
51+ _only (unsafeFromKey $ toKey key)
52+
53+-- | Returns a new IDBKeyRange starting at key with no upper bound.
54+-- | If `Open` is `true`, key is not included in the range.
55+lowerBound
56+ :: forall a
57+ . (IDBKey a)
58+ => a
59+ -> Open
60+ -> KeyRange
3861lowerBound key open =
39− Fn.runFn2 _lowerBound (extractForeign $ toKey key) open
40−
41−
42−upperBound :: forall a. (IDBKey a) => a -> Boolean -> KeyRange
62+ Fn.runFn2 _lowerBound (unsafeFromKey $ toKey key) open
63+
64+-- | Returns a new IDBKeyRange with no lower bound and ending at key.
65+-- | If `Open` is `true`, key is not included in the range.
66+upperBound
67+ :: forall a
68+ . (IDBKey a)
69+ => a
70+ -> Open
71+ -> KeyRange
4372upperBound key open =
44− Fn.runFn2 _upperBound (extractForeign $ toKey key) open
45−
46−bound :: forall a. (IDBKey a) => { lower :: a, upper :: a, lowerOpen :: Boolean, upperOpen :: Boolean } -> Maybe KeyRange
73+ Fn.runFn2 _upperBound (unsafeFromKey $ toKey key) open
74+
75+-- | Returns a new IDBKeyRange spanning from `lower` to `upper`.
76+-- | If `lowerOpen` is `true`, `lower` is not included in the range.
77+-- | If `upperOpen` is `true`, `upper` is not included in the range.
78+-- |
79+-- | It throws a `DataError` if the bound is invalid.
80+bound
81+ :: forall key
82+ . (IDBKey key)
83+ => { lower :: key, upper :: key, lowerOpen :: Boolean, upperOpen :: Boolean }
84+ -> Maybe KeyRange
4785bound { lower: key1, upper: key2, lowerOpen: open1, upperOpen: open2 } =
4886 toMaybe
49− $ Fn.runFn4 _bound (extractForeign $ toKey key1) (extractForeign $ toKey key2) open1 open2
87+ $ Fn.runFn4 _bound (unsafeFromKey $ toKey key1) (unsafeFromKey $ toKey key2) open1 open2
88+
89+--------------------
90+-- INTERFACE
91+--
5092
93+-- | Returns true if key is included in the range, and false otherwise.
94+includes
95+ :: forall key range
96+ . (IDBKey key)
97+ => (IDBKeyRange range)
98+ => range
99+ -> key
100+ -> Boolean
101+includes range =
102+ toKey >>> unsafeFromKey >>> Fn.runFn2 _includes range
51103
52104--------------------
53105-- ATTRIBUTES
54106--
55−lower :: KeyRange -> Maybe Key
107+-- | Returns lower bound if any.
108+lower
109+ :: KeyRange
110+ -> Maybe Key
56111lower =
57− _lower >>> toMaybe >>> map Key
112+ _lower >>> toMaybe >>> map toKey
58113
59−
60−upper :: KeyRange -> Maybe Key
114+-- | Returns upper bound if any.
115+upper
116+ :: KeyRange
117+ -> Maybe Key
61118upper =
62− _upper >>> toMaybe >>> map Key
63−
119+ _upper >>> toMaybe >>> map toKey
64120
65−lowerOpen :: KeyRange -> Boolean
121+-- | Returns true if the lower open flag is set, false otherwise.
122+lowerOpen
123+ :: KeyRange
124+ -> Boolean
66125lowerOpen =
67126 _lowerOpen
68127
69−
70−upperOpen :: KeyRange -> Boolean
128+-- | Returns true if the upper open flag is set, false otherwise.
129+upperOpen
130+ :: KeyRange
131+ -> Boolean
71132upperOpen =
72133 _upperOpen
73134
74−
75−--------------------
76−-- INSTANCES
77−--
78−instance idbKeyRangeKeyRange :: IDBKeyRange KeyRange where
79− includes range =
80− extractForeign >>> Fn.runFn2 _includes range
81−
82−
83135--------------------
84136-- FFI
85137--
86−foreign import _bound :: Fn4 Foreign Foreign Boolean Boolean (Nullable KeyRange)
87−
88−
89−foreign import _includes :: forall range. Fn2 range Foreign Boolean
90−
91−
92−foreign import _lower :: KeyRange -> Nullable Foreign
93−
94−
95−foreign import _lowerBound :: Fn2 Foreign Boolean KeyRange
96−
97138
98−foreign import _lowerOpen :: KeyRange -> Boolean
139+foreign import _only
140+ :: Foreign
141+ -> KeyRange
99142
143+foreign import _lowerBound
144+ :: Fn2 Foreign Boolean KeyRange
100145
101−foreign import _only :: Foreign -> KeyRange
146+foreign import _upperBound
147+ :: Fn2 Foreign Boolean KeyRange
102148
149+foreign import _bound
150+ :: Fn4 Foreign Foreign Boolean Boolean (Nullable KeyRange)
103151
104−foreign import _upper :: KeyRange -> Nullable Foreign
152+foreign import _includes
153+ :: forall range
154+ . Fn2 range Foreign Boolean
105155
156+foreign import _lower
157+ :: KeyRange
158+ -> Nullable Foreign
106159
107−foreign import _upperBound :: Fn2 Foreign Boolean KeyRange
160+foreign import _upper
161+ :: KeyRange
162+ -> Nullable Foreign
108163
164+foreign import _lowerOpen
165+ :: KeyRange
166+ -> Boolean
109167
110−foreign import _upperOpen :: KeyRange -> Boolean
168+foreign import _upperOpen
169+ :: KeyRange
170+ -> Boolean
src/Database/IndexedDB/IDBObjectStore.js+84−38
@@ -1,6 +1,6 @@
11const errorHandler = function errorHandler(cb) {
22 return function _handler(e) {
3− cb(new Error(e.target.error.name));
3+ cb(e.target.error);
44 };
55};
66
@@ -15,28 +15,44 @@ const toArray = function toArray(xs) {
1515};
1616
1717
18−exports._add = function _add(store, value, key) {
19− return function aff(success, error) {
20− const request = store.add(value, key || undefined);
21− request.onsuccess = successHandler(success);
22− request.onerror = errorHandler(error);
18+export function _add(store, value, key) {
19+ return function aff(error, success) {
20+ try {
21+ const request = store.add(value, key || undefined);
22+ request.onsuccess = successHandler(success);
23+ request.onerror = errorHandler(error);
24+ } catch (e) {
25+ error(e);
26+ }
27+
28+ return function canceler(_, cancelerError) {
29+ cancelerError(new Error("Can't cancel IDB Effects"));
30+ };
2331 };
2432};
2533
26−exports._autoIncrement = function _autoIncrement(store) {
34+export function _autoIncrement(store) {
2735 return store.autoIncrement;
2836};
2937
30−exports._clear = function _clear(store) {
31− return function aff(success, error) {
32− const request = store.clear();
33− request.onsuccess = successHandler(success);
34− request.onerror = errorHandler(error);
38+export function _clear(store) {
39+ return function aff(error, success) {
40+ try {
41+ const request = store.clear();
42+ request.onsuccess = successHandler(success);
43+ request.onerror = errorHandler(error);
44+ } catch (e) {
45+ error(e);
46+ }
47+
48+ return function canceler(_, cancelerError) {
49+ cancelerError(new Error("Can't cancel IDB Effects"));
50+ };
3551 };
3652};
3753
38−exports._createIndex = function _createIndex(store, name, path, params) {
39− return function eff() {
54+export function _createIndex(store, name, path, params) {
55+ return function aff(error, success) {
4056 var keyPath;
4157
4258 try {
@@ -53,46 +69,69 @@ exports._createIndex = function _createIndex(store, name, path, params) {
5369 keyPath = path;
5470 }
5571
56− return store.createIndex(name, keyPath, params);
72+ const index = store.createIndex(name, keyPath, params);
73+ success(index);
5774 } catch (e) {
58− throw new Error(e.name);
75+ error(e);
5976 }
77+
78+ return function canceler(_, cancelerError) {
79+ cancelerError(new Error("Can't cancel IDB Effects"));
80+ };
6081 };
6182};
6283
63−exports._deleteIndex = function _deleteIndex(store, name) {
64− return function eff() {
84+export function _deleteIndex(store, name) {
85+ return function aff(error, success) {
6586 try {
6687 store.deleteIndex(name);
88+ success();
6789 } catch (e) {
68− throw new Error(e.name);
90+ error(e);
6991 }
92+
93+ return function canceler(_, cancelerError) {
94+ cancelerError(new Error("Can't cancel IDB Effects"));
95+ };
7096 };
7197};
7298
73−exports._delete = function _delete(store, query) {
74− return function aff(success, error) {
75− const request = store.delete(query);
76− request.onsuccess = successHandler(success);
77− request.onerror = errorHandler(error);
99+export function _delete(store, query) {
100+ return function aff(error, success) {
101+ try {
102+ const request = store.delete(query);
103+ request.onsuccess = successHandler(success);
104+ request.onerror = errorHandler(error);
105+ } catch (e) {
106+ error(e);
107+ }
108+
109+ return function canceler(_, cancelerError) {
110+ cancelerError(new Error("Can't cancel IDB Effects"));
111+ };
78112 };
79113};
80114
81−exports._index = function _index(store, name) {
82− return function eff() {
115+export function _index(store, name) {
116+ return function aff(error, success) {
83117 try {
84− return store.index(name);
118+ const index = store.index(name);
119+ success(index);
85120 } catch (e) {
86− throw new Error(e.name);
121+ error(e);
87122 }
123+
124+ return function canceler(_, cancelerError) {
125+ cancelerError(new Error("Can't cancel IDB Effects"));
126+ };
88127 };
89128};
90129
91−exports._indexNames = function _indexNames(store) {
130+export function _indexNames(store) {
92131 return toArray(store.indexNames);
93132};
94133
95−exports._keyPath = function _keyPath(store) {
134+export function _keyPath(store) {
96135 const path = store.keyPath;
97136
98137 if (Array.isArray(path)) {
@@ -106,19 +145,26 @@ exports._keyPath = function _keyPath(store) {
106145 return [];
107146};
108147
109−exports._name = function _name(store) {
148+export function _name(store) {
110149 return store.name;
111150};
112151
113−exports._put = function _put(store, value, key) {
114− return function aff(success, error) {
115− const request = store.put(value, key || undefined);
116− request.onsuccess = successHandler(success);
117− request.onerror = errorHandler(error);
152+export function _put(store, value, key) {
153+ return function aff(error, success) {
154+ try {
155+ const request = store.put(value, key || undefined);
156+ request.onsuccess = successHandler(success);
157+ request.onerror = errorHandler(error);
158+ } catch (e) {
159+ error(e);
160+ }
161+
162+ return function canceler(_, cancelerError) {
163+ cancelerError(new Error("Can't cancel IDB Effects"));
164+ };
118165 };
119166};
120167
121−exports._transaction = function _transaction(store) {
168+export function _transaction(store) {
122169 return store.transaction;
123170};
124−
src/Database/IndexedDB/IDBObjectStore.purs+217−109
@@ -1,149 +1,257 @@
1+-- | An object store is the primary storage mechanism for storing data in a database.
12module Database.IndexedDB.IDBObjectStore
2− ( class IDBObjectStore, add, clear, createIndex, delete, deleteIndex, index, put
3− , module Database.IndexedDB.IDBIndex.Internal
4− , IDBObjectStoreParameters
5− , IndexName
3+ -- * Types
4+ ( IndexName
5+ , IndexParameters
6+ , defaultParameters
7+
8+ -- * Interface
9+ , add
10+ , clear
11+ , createIndex
12+ , delete
13+ , deleteIndex
14+ , index
15+ , put
16+
17+ -- * Attributes
618 , autoIncrement
719 , indexNames
820 , keyPath
921 , name
1022 , transaction
11− , defaultParameters
12− ) where
13−
14−import Prelude (Unit, ($), (<$>))
1523
16−import Control.Monad.Aff (Aff)
17−import Control.Monad.Eff (Eff)
18−import Control.Monad.Eff.Exception (EXCEPTION)
19−import Data.Foreign (Foreign)
20−import Data.Function.Uncurried as Fn
21−import Data.Function.Uncurried (Fn2, Fn3, Fn4)
22−import Data.Maybe (Maybe)
23−import Data.Nullable (Nullable, toNullable)
24+ -- * Re-Exports
25+ , module Database.IndexedDB.IDBIndex
26+ ) where
2427
25−import Database.IndexedDB.Core (IDB, Index, KeyRange, KeyPath, ObjectStore, Transaction)
26−import Database.IndexedDB.IDBIndex.Internal (class IDBIndex, IDBIndexParameters, count, get, getAllKeys, getKey, openCursor, openKeyCursor)
27−import Database.IndexedDB.IDBKey.Internal (Key(Key), extractForeign)
28+import Database.IndexedDB.Core
2829
30+import Data.Function.Uncurried (Fn2, Fn3, Fn4)
31+import Data.Function.Uncurried as Fn
32+import Data.Maybe (Maybe)
33+import Data.Nullable (Nullable, toNullable)
34+import Database.IndexedDB.IDBIndex (count, get, getAllKeys, getKey, openCursor, openKeyCursor)
35+import Database.IndexedDB.IDBKey.Internal (class IDBKey, Key, unsafeFromKey, toKey)
36+import Effect.Aff (Aff)
37+import Effect.Aff.Compat (EffectFnAff, fromEffectFnAff)
38+import Foreign (Foreign)
39+import Prelude (Unit, map, ($), (<$>), (>>>), (<<<))
2940
3041--------------------
31−-- INTERFACES
42+-- TYPES
3243--
33−class IDBObjectStore store where
34− add :: forall value e. store -> value -> Maybe Key -> Aff (idb :: IDB | e) Key
35− clear :: forall e. store -> Aff (idb :: IDB | e) Unit
36− createIndex :: forall e. store -> IndexName -> KeyPath -> IDBIndexParameters -> Eff (idb :: IDB, exception :: EXCEPTION | e) Index
37− delete :: forall e. store -> KeyRange -> Aff (idb :: IDB | e) Unit
38− deleteIndex :: forall e. store -> IndexName -> Eff (idb :: IDB, exception :: EXCEPTION | e) Unit
39− index :: forall e. store -> IndexName -> Eff (idb :: IDB, exception :: EXCEPTION | e) Index
40− put :: forall value e. store -> value -> Maybe Key -> Aff (idb :: IDB | e) Key
41−
4244
45+-- | Type alias for IndexName
4346type IndexName = String
4447
48+-- | Flags to set on the index.
49+-- |
50+-- | An index has a `unique` flag. When this flag is set, the index enforces that no
51+-- | two records in the index has the same key. If a record in the index’s referenced
52+-- | object store is attempted to be inserted or modified such that evaluating the index’s
53+-- | key path on the records new value yields a result which already exists in the index,
54+-- | then the attempted modification to the object store fails.
55+-- |
56+-- | An index has a `multiEntry` flag. This flag affects how the index behaves when the
57+-- | result of evaluating the index’s key path yields an array key. If the `multiEntry` flag
58+-- | is unset, then a single record whose key is an array key is added to the index.
59+-- | If the `multiEntry` flag is true, then the one record is added to the index for each
60+-- | of the subkeys.
61+type IndexParameters =
62+ { unique :: Boolean
63+ , multiEntry :: Boolean
64+ }
4565
46−type IDBObjectStoreParameters =
47− { keyPath :: KeyPath
48− , autoIncrement :: Boolean
66+defaultParameters :: IndexParameters
67+defaultParameters =
68+ { unique: false
69+ , multiEntry: false
4970 }
5071
72+--------------------
73+-- INTERFACES
74+--
75+
76+-- | Adds or updates a record in store with the given value and key.
77+-- |
78+-- | If the store uses in-line keys and key is specified a "DataError" DOMException
79+-- | will be thrown.
80+-- |
81+-- | If add() is used, and if a record with the key already exists the request will fail,
82+-- | with a "ConstraintError" DOMException.
83+add
84+ :: forall key val store
85+ . (IDBKey key)
86+ => (IDBObjectStore store)
87+ => store
88+ -> val
89+ -> Maybe key
90+ -> Aff Key
91+add store value key =
92+ map toKey $ fromEffectFnAff $ Fn.runFn3 _add store value (toNullable $ (toKey >>> unsafeFromKey) <$> key)
93+
94+-- | Deletes all records in store.
95+clear
96+ :: forall store
97+ . (IDBObjectStore store)
98+ => store
99+ -> Aff Unit
100+clear =
101+ fromEffectFnAff <<< _clear
102+
103+-- | Creates a new index in store with the given name, keyPath and options and
104+-- | returns a new IDBIndex. If the keyPath and options define constraints that
105+-- | cannot be satisfied with the data already in store the upgrade transaction
106+-- | will abort with a "ConstraintError" DOMException.
107+-- |
108+-- | Throws an "InvalidStateError" DOMException if not called within an upgrade transaction.
109+createIndex
110+ :: forall store
111+ . (IDBObjectStore store)
112+ => store
113+ -> IndexName
114+ -> KeyPath
115+ -> IndexParameters
116+ -> Aff Index
117+createIndex store name' path params =
118+ fromEffectFnAff $ Fn.runFn4 _createIndex store name' path params
119+
120+-- | Deletes records in store with the given key or in the given key range in query.
121+delete
122+ :: forall store
123+ . (IDBObjectStore store)
124+ => store
125+ -> KeyRange
126+ -> Aff Unit
127+delete store range =
128+ fromEffectFnAff $ Fn.runFn2 _delete store range
129+
130+-- | Deletes the index in store with the given name.
131+-- |
132+-- | Throws an "InvalidStateError" DOMException if not called within an upgrade transaction.
133+deleteIndex
134+ :: forall store
135+ . (IDBObjectStore store)
136+ => store
137+ -> IndexName
138+ -> Aff Unit
139+deleteIndex store name' =
140+ fromEffectFnAff $ Fn.runFn2 _deleteIndex store name'
141+
142+-- | Returns an IDBIndex for the index named name in store.
143+index
144+ :: forall store
145+ . (IDBObjectStore store)
146+ => store
147+ -> IndexName
148+ -> Aff Index
149+index store name' =
150+ fromEffectFnAff $ Fn.runFn2 _index store name'
151+
152+-- | Adds or updates a record in store with the given value and key.
153+-- |
154+-- | If the store uses in-line keys and key is specified a "DataError" DOMException
155+-- | will be thrown.
156+-- |
157+-- | If put() is used, any existing record with the key will be replaced.
158+put
159+ :: forall val key store
160+ . (IDBKey key)
161+ => (IDBObjectStore store)
162+ => store
163+ -> val
164+ -> Maybe key
165+ -> Aff Key
166+put store value key =
167+ map toKey $ fromEffectFnAff $ Fn.runFn3 _put store value (toNullable $ (toKey >>> unsafeFromKey) <$> key)
51168
52169--------------------
53170-- ATTRIBUTES
54171--
55−autoIncrement :: ObjectStore -> Boolean
172+-- | Returns `true` if the store has a key generator, and `false` otherwise.
173+autoIncrement
174+ :: ObjectStore
175+ -> Boolean
56176autoIncrement =
57177 _autoIncrement
58178
59−
60−indexNames :: ObjectStore -> Array String
179+--| Returns a list of the names of indexes in the store.
180+indexNames
181+ :: ObjectStore
182+ -> Array String
61183indexNames =
62184 _indexNames
63185
64−
65−keyPath :: ObjectStore -> Array String
186+-- | Returns the key path of the store, or empty array if none
187+keyPath
188+ :: ObjectStore
189+ -> Array String
66190keyPath =
67191 _keyPath
68192
69−
70−name :: ObjectStore -> String
193+-- | Returns the name of the store.
194+name
195+ :: ObjectStore
196+ -> String
71197name =
72198 _name
73199
74−
75−transaction :: ObjectStore -> Transaction
200+-- | Returns the associated transaction.
201+transaction
202+ :: ObjectStore
203+ -> Transaction
76204transaction =
77205 _transaction
78206
79−
80−--------------------
81−-- INSTANCES
82−--
83−instance idbObjectStoreObjectStore :: IDBObjectStore ObjectStore where
84− add store value key =
85− Key <$> Fn.runFn3 _add store value (toNullable $ extractForeign <$> key)
86−
87− clear =
88− _clear
89−
90− createIndex store name' path params =
91− Fn.runFn4 _createIndex store name' path params
92−
93− delete store range =
94− Fn.runFn2 _delete store range
95−
96− deleteIndex store name' =
97− Fn.runFn2 _deleteIndex store name'
98−
99− index store name' =
100− Fn.runFn2 _index store name'
101−
102− put store value key =
103− Key <$> Fn.runFn3 _put store value (toNullable $ extractForeign <$> key)
104−
105−
106−defaultParameters :: IDBObjectStoreParameters
107−defaultParameters =
108− { keyPath : []
109− , autoIncrement : false
110− }
111−
112−
113207--------------------
114208-- FFI
115209--
116−foreign import _add :: forall value e. Fn3 ObjectStore value (Nullable Foreign) (Aff (idb :: IDB | e) Foreign)
117−
118−
119−foreign import _autoIncrement :: ObjectStore -> Boolean
120−
121−
122−foreign import _clear :: forall e. ObjectStore -> Aff (idb :: IDB | e) Unit
123−
124−
125−foreign import _createIndex :: forall e. Fn4 ObjectStore String (Array String) { unique :: Boolean, multiEntry :: Boolean } (Eff (idb :: IDB | e) Index)
126−
127−
128−foreign import _delete :: forall e. Fn2 ObjectStore KeyRange (Aff (idb :: IDB | e) Unit)
129−
130−
131−foreign import _deleteIndex :: forall e. Fn2 ObjectStore String (Eff (idb :: IDB | e) Unit)
132−
133−
134−foreign import _index :: forall e. Fn2 ObjectStore String (Eff (idb :: IDB | e) Index)
135−
136−
137−foreign import _indexNames :: ObjectStore -> Array String
138−
139−
140−foreign import _keyPath :: ObjectStore -> Array String
141−
142−
143−foreign import _name :: ObjectStore -> String
144−
145−
146−foreign import _put :: forall value e. Fn3 ObjectStore value (Nullable Foreign) (Aff (idb :: IDB | e) Foreign)
147−
148−
149−foreign import _transaction :: ObjectStore -> Transaction
210+foreign import _add
211+ :: forall val store
212+ . Fn3 store val (Nullable Foreign) (EffectFnAff Foreign)
213+
214+foreign import _autoIncrement
215+ :: ObjectStore
216+ -> Boolean
217+
218+foreign import _clear
219+ :: forall store
220+ . store
221+ -> EffectFnAff Unit
222+
223+foreign import _createIndex
224+ :: forall store
225+ . Fn4 store String (Array String) { unique :: Boolean, multiEntry :: Boolean } (EffectFnAff Index)
226+
227+foreign import _delete
228+ :: forall store
229+ . Fn2 store KeyRange (EffectFnAff Unit)
230+
231+foreign import _deleteIndex
232+ :: forall store
233+ . Fn2 store String (EffectFnAff Unit)
234+
235+foreign import _index
236+ :: forall store
237+ . Fn2 store String (EffectFnAff Index)
238+
239+foreign import _indexNames
240+ :: ObjectStore
241+ -> Array String
242+
243+foreign import _keyPath
244+ :: ObjectStore
245+ -> Array String
246+
247+foreign import _name
248+ :: ObjectStore
249+ -> String
250+
251+foreign import _put
252+ :: forall val store
253+ . Fn3 store val (Nullable Foreign) (EffectFnAff Foreign)
254+
255+foreign import _transaction
256+ :: ObjectStore
257+ -> Transaction
src/Database/IndexedDB/IDBTransaction.js+50−17
@@ -1,20 +1,29 @@
1−exports._abort = function _abort(tx) {
2− return function eff() {
1+export function _abort(tx) {
2+ return function aff(error, success) {
33 try {
44 tx.abort();
5+ success();
56 } catch (e) {
6− throw new Error(e.name);
7+ error(e);
78 }
9+
10+ return function canceler(_, cancelerError) {
11+ cancelerError(new Error("Can't cancel IDB Effects"));
12+ };
813 };
914};
1015
11−exports._error = function _error(tx) {
16+export function _db(tx) {
17+ return tx.db;
18+};
19+
20+export function _error(tx) {
1221 return tx.error == null
1322 ? null
14− : new Error(tx.error.name);
23+ : tx.error;
1524};
1625
17−exports._mode = function _mode(ReadOnly, ReadWrite, VersionChange, tx) {
26+export function _mode(ReadOnly, ReadWrite, VersionChange, tx) {
1827 if (tx.mode === 'readwrite') {
1928 return ReadWrite;
2029 } else if (tx.mode === 'versionchange') {
@@ -24,36 +33,60 @@ exports._mode = function _mode(ReadOnly, ReadWrite, VersionChange, tx) {
2433 return ReadOnly;
2534};
2635
27−exports._objectStore = function _objectStore(tx, name) {
28− return function eff() {
36+export function _objectStore(tx, name) {
37+ return function aff(error, success) {
2938 try {
30− return tx.objectStore(name);
39+ const store = tx.objectStore(name);
40+ success(store);
3141 } catch (e) {
32− throw new Error(e.name);
42+ error(e);
3343 }
44+
45+ return function canceler(_, cancelerError) {
46+ cancelerError(new Error("Can't cancel IDB Effects"));
47+ };
3448 };
3549};
3650
37−exports._onAbort = function _onAbort(tx, f) {
38− return function eff() {
51+export function _objectStoreNames(tx) {
52+ return tx.objectStoreNames;
53+};
54+
55+export function _onAbort(tx, f) {
56+ return function aff(error, success) {
3957 tx.onabort = function onabort() {
4058 f();
4159 };
60+ success();
61+
62+ return function canceler(_, cancelerError) {
63+ cancelerError(new Error("Can't cancel IDB Effects"));
64+ };
4265 };
4366};
4467
45−exports._onComplete = function _onComplete(tx, f) {
46− return function eff() {
68+export function _onComplete(tx, f) {
69+ return function aff(error, success) {
4770 tx.oncomplete = function oncomplete() {
4871 f();
4972 };
73+ success();
74+
75+ return function canceler(_, cancelerError) {
76+ cancelerError(new Error("Can't cancel IDB Effects"));
77+ };
5078 };
5179};
5280
53−exports._onError = function _onError(tx, f) {
54− return function eff() {
81+export function _onError(tx, f) {
82+ return function aff(error, success) {
5583 tx.onerror = function onerror(e) {
56− f(new Error(e.target.error.name))();
84+ f(e.target.error)();
85+ };
86+ success();
87+
88+ return function canceler(_, cancelerError) {
89+ cancelerError(new Error("Can't cancel IDB Effects"));
5790 };
5891 };
5992};
src/Database/IndexedDB/IDBTransaction.purs+111−51
@@ -1,93 +1,153 @@
1+-- | An object store is the primary storage mechanism for storing data in a database.
12module Database.IndexedDB.IDBTransaction
2− (class IDBTransaction, abort, objectStore
3+ -- * Interface
4+ ( abort
5+ , objectStore
6+
7+ -- * Attributes
38 , error
49 , mode
10+
11+ -- * Event Handlers
512 , onAbort
613 , onComplete
714 , onError
815 ) where
916
10−import Prelude (Unit, (>>>))
11−
12−import Control.Monad.Eff (Eff)
13−import Control.Monad.Eff.Exception (EXCEPTION, Error)
14−import Data.Function.Uncurried as Fn
15−import Data.Function.Uncurried (Fn2, Fn4)
16−import Data.Maybe (Maybe)
17−import Data.Nullable (Nullable, toMaybe)
18−
19−import Database.IndexedDB.Core (IDB, ObjectStore, Transaction, TransactionMode(..))
17+import Database.IndexedDB.Core
2018
19+import Data.Function.Uncurried (Fn2, Fn4)
20+import Data.Function.Uncurried as Fn
21+import Data.Maybe (Maybe)
22+import Data.Nullable (Nullable, toMaybe)
23+import Effect (Effect)
24+import Effect.Aff (Aff)
25+import Effect.Aff.Compat (EffectFnAff, fromEffectFnAff)
26+import Effect.Exception (Error)
27+import Prelude (Unit, ($), (<<<), (>>>))
2128
2229--------------------
2330-- INTERFACES
2431--
25−class IDBTransaction tx where
26− abort :: forall e. tx -> Eff (idb :: IDB, exception :: EXCEPTION | e) Unit
27− objectStore :: forall e. tx -> String -> Eff (idb :: IDB, exception :: EXCEPTION | e) ObjectStore
2832
33+-- | Aborts the transaction. All pending requests will fail with a "AbortError"
34+-- | DOMException and all changes made to the database will be reverted.
35+abort
36+ :: forall tx
37+ . (IDBTransaction tx)
38+ => tx
39+ -> Aff Unit
40+abort =
41+ fromEffectFnAff <<< _abort
42+
43+-- | Returns an IDBObjectStore in the transaction's scope.
44+objectStore
45+ :: forall tx
46+ . (IDBTransaction tx)
47+ => tx
48+ -> String
49+ -> Aff ObjectStore
50+objectStore tx name =
51+ fromEffectFnAff $ Fn.runFn2 _objectStore tx name
2952
3053--------------------
3154-- ATTRIBUTES
3255--
33−error :: Transaction -> Maybe Error
56+
57+--- | Returns the transaction’s connection.
58+db
59+ :: Transaction
60+ -> Database
61+db =
62+ _db
63+
64+-- | If the transaction was aborted, returns the error (a DOMException) providing the reason.
65+error
66+ :: Transaction
67+ -> Maybe Error
3468error =
3569 _error >>> toMaybe
3670
37−
38−mode :: Transaction -> TransactionMode
71+-- | Returns the mode the transaction was created with (`ReadOnly|ReadWrite`)
72+-- | , or `VersionChange` for an upgrade transaction.
73+mode
74+ :: Transaction
75+ -> TransactionMode
3976mode =
4077 Fn.runFn4 _mode ReadOnly ReadWrite VersionChange
4178
79+-- | Returns a list of the names of object stores in the transaction’s scope.
80+-- | For an upgrade transaction this is all object stores in the database.
81+objectStoreNames
82+ :: Transaction
83+ -> Array String
84+objectStoreNames =
85+ _objectStoreNames
4286
4387--------------------
4488-- EVENT HANDLERS
4589--
46−onAbort :: forall e e'. Transaction -> Eff ( | e') Unit -> Eff (idb :: IDB | e) Unit
47−onAbort db f =
48− Fn.runFn2 _onAbort db f
49−
50−
51−onComplete :: forall e e'. Transaction -> Eff ( | e') Unit -> Eff (idb :: IDB | e) Unit
52−onComplete db f =
53− Fn.runFn2 _onComplete db f
54−
55−
56−onError :: forall e e'. Transaction -> (Error -> Eff ( | e') Unit) -> Eff (idb :: IDB | e) Unit
57−onError db f =
58− Fn.runFn2 _onError db f
59−
60−
61−--------------------
62−-- INSTANCES
63−--
64−instance idbTransactionTransaction :: IDBTransaction Transaction where
65− objectStore tx name =
66− Fn.runFn2 _objectStore tx name
67−
68− abort =
69− _abort
7090
91+-- | Event handler for the `abort` event.
92+onAbort
93+ :: Transaction
94+ -> Effect Unit
95+ -> Aff Unit
96+onAbort db' f =
97+ fromEffectFnAff $ Fn.runFn2 _onAbort db' f
98+
99+-- | Event handler for the `complete` event.
100+onComplete
101+ :: Transaction
102+ -> Effect Unit
103+ -> Aff Unit
104+onComplete db' f =
105+ fromEffectFnAff $ Fn.runFn2 _onComplete db' f
106+
107+-- | Event handler for the `error` event.
108+onError
109+ :: Transaction
110+ -> (Error -> Effect Unit)
111+ -> Aff Unit
112+onError db' f =
113+ fromEffectFnAff $ Fn.runFn2 _onError db' f
71114
72115--------------------
73116-- FFI
74117--
75−foreign import _abort :: forall tx e. tx -> Eff (idb :: IDB, exception :: EXCEPTION | e) Unit
76−
77−
78−foreign import _error :: Transaction -> (Nullable Error)
79−
80118
81−foreign import _mode :: Fn4 TransactionMode TransactionMode TransactionMode Transaction TransactionMode
119+foreign import _abort
120+ :: forall tx
121+ . tx
122+ -> EffectFnAff Unit
82123
124+foreign import _db
125+ :: Transaction
126+ -> Database
83127
84−foreign import _objectStore :: forall tx e. Fn2 tx String (Eff (idb :: IDB, exception :: EXCEPTION | e) ObjectStore)
128+foreign import _error
129+ :: Transaction
130+ -> (Nullable Error)
85131
132+foreign import _mode
133+ :: Fn4 TransactionMode TransactionMode TransactionMode Transaction TransactionMode
86134
87−foreign import _onAbort :: forall tx e e'. Fn2 tx (Eff ( | e') Unit) (Eff (idb :: IDB | e) Unit)
135+foreign import _objectStoreNames
136+ :: Transaction
137+ -> Array String
88138
139+foreign import _objectStore
140+ :: forall tx
141+ . Fn2 tx String (EffectFnAff ObjectStore)
89142
90−foreign import _onComplete :: forall tx e e'. Fn2 tx (Eff ( | e') Unit) (Eff (idb :: IDB | e) Unit)
143+foreign import _onAbort
144+ :: forall tx
145+ . Fn2 tx (Effect Unit) (EffectFnAff Unit)
91146
147+foreign import _onComplete
148+ :: forall tx
149+ . Fn2 tx (Effect Unit) (EffectFnAff Unit)
92150
93−foreign import _onError :: forall tx e e'. Fn2 tx (Error -> Eff ( | e') Unit) (Eff (idb :: IDB | e) Unit)
151+foreign import _onError
152+ :: forall tx
153+ . Fn2 tx (Error -> Effect Unit) (EffectFnAff Unit)
src/Main.pursdeleted−24
@@ -1,24 +0,0 @@
1−module Main where
2−
3−import Prelude
4−
5−import Control.Monad.Aff as Aff
6−import Control.Monad.Eff(kind Effect, Eff)
7−import Control.Monad.Eff.Console as Console
8−import Control.Monad.Eff.Console(CONSOLE)
9−import Control.Monad.Eff.Exception(EXCEPTION, catchException)
10−import Data.Maybe(Maybe(..))
11−import Data.Either(Either(..))
12−
13−import Core
14−import IDBDatabase as IDBDatabase
15−
16−
17−main :: Eff (exception :: EXCEPTION, idb :: INDEXED_DB, console :: CONSOLE) Unit
18−main = do
19− _ <- Aff.runAff (show >>> Console.log) (IDBDatabase.name >>> Console.log) $ IDBDatabase.open "myDatabase" Nothing
20− { onBlocked : Nothing
21− , onUpgradeNeeded : Nothing
22− }
23−
24− pure unit
src/Test/Spec/Mocha.jsdeleted−46
@@ -1,46 +0,0 @@
1−/* global exports, it */
2−
3−// module Test.Spec.Mocha
4−
5−if (typeof describe !== 'function' || typeof it !== 'function') {
6− throw new Error('Mocha globals seem to be unavailable!');
7−}
8−
9−exports.itAsync = function (only) {
10− "use strict";
11− return function (name) {
12− return function (aff) {
13− return function () {
14− var f = only ? it.only : it;
15− f(name, function (done) {
16− aff(function () {
17− done();
18− }, function (err) {
19− done(err);
20− });
21− });
22− };
23− };
24− };
25−};
26−
27−exports.itPending = function (name) {
28− "use strict";
29− return function () {
30− it(name);
31− };
32−};
33−
34−exports.describe = function (only) {
35− "use strict";
36− return function (name) {
37− return function (nested) {
38− return function () {
39− var f = only ? describe : describe.only;
40− f(name, function () {
41− nested();
42− });
43− };
44− };
45− };
46−};
src/Test/Spec/Mocha.pursdeleted−41
@@ -1,41 +0,0 @@
1−module Test.Spec.Mocha (
2− runMocha,
3− MOCHA()
4− ) where
5−
6−import Prelude
7−
8−import Control.Monad.Aff (Aff())
9−import Control.Monad.Eff (kind Effect, Eff())
10−
11−import Data.Foldable (traverse_)
12−
13−import Test.Spec (Spec, Group(..), collect)
14−
15−foreign import data MOCHA :: Effect
16−
17−foreign import itAsync :: forall e.
18− Boolean
19− -> String
20− -> Aff e Unit
21− -> Eff (mocha :: MOCHA | e) Unit
22−
23−foreign import itPending :: forall e. String
24− -> Eff (mocha :: MOCHA | e) Unit
25−
26−foreign import describe :: forall e.
27− Boolean
28− -> String
29− -> Eff (mocha :: MOCHA | e) Unit
30− -> Eff (mocha :: MOCHA | e) Unit
31−
32−registerGroup :: forall e. (Group (Aff e Unit))
33− -> Eff (mocha :: MOCHA | e) Unit
34−registerGroup (It only name test) = itAsync only name test
35−registerGroup (Pending name) = itPending name
36−registerGroup (Describe only name groups) =
37− describe only name (traverse_ registerGroup groups)
38−
39−runMocha :: forall e. Spec e Unit
40− -> Eff (mocha :: MOCHA | e) Unit
41−runMocha spec = traverse_ registerGroup (collect spec)
test/Main.purs+1087−875
@@ -2,928 +2,1140 @@ module Test.Main where
22
33import Prelude
44
5−import Control.Monad.Aff (Aff, launchAff, forkAff, delay)
6−import Control.Monad.Aff.Console (log)
7−import Control.Monad.Aff.AVar (AVAR, makeVar', modifyVar, peekVar, killVar)
8−import Control.Monad.Eff (Eff)
9−import Control.Monad.Eff.Class (liftEff)
10−import Control.Monad.Eff.Exception (EXCEPTION)
11−import Data.Date as Date
12−import Data.DateTime as DateTime
13−import Data.DateTime (DateTime(..), Date(..), Time(..))
14−import Data.Enum (toEnum)
15−import Data.Maybe (Maybe(..), isNothing)
16−import Data.Time.Duration (Milliseconds(..))
17−import Test.Spec (describe, it)
18−import Test.Spec.Assertions (shouldEqual, fail)
19−import Test.Spec.Mocha (MOCHA, runMocha)
20−
21−import Database.IndexedDB.Core
22−import Database.IndexedDB.IDBKey
23−import Database.IndexedDB.IDBDatabase as IDBDatabase
24−import Database.IndexedDB.IDBFactory as IDBFactory
25−import Database.IndexedDB.IDBObjectStore as IDBObjectStore
26−import Database.IndexedDB.IDBKeyRange as IDBKeyRange
27−
28−
29−tearDown :: forall eff. String -> Int -> Database -> Aff (idb :: INDEXED_DB, exception :: EXCEPTION | eff) Unit
30−tearDown name version db = do
31− liftEff $ IDBDatabase.close db
32− version' <- IDBFactory.deleteDatabase name
33− version' `shouldEqual` version
34−
35−-- main :: forall eff. Eff (mocha :: MOCHA, idb :: INDEXED_DB, exception :: EXCEPTION, avar :: AVAR | eff) Unit
36−main = runMocha do
37− describe "IDBFactory" do
38− it "open default" do
39− let name = "db-default"
5+import Data.Array (head, drop)
6+import Data.Date as Date
7+import Data.DateTime (DateTime(..), Time(..))
8+import Data.DateTime.Instant (toDateTime)
9+import Data.Either (Either(..))
10+import Data.Enum (toEnum)
11+import Data.Maybe (Maybe(..), isNothing, maybe)
12+import Data.Time.Duration (Milliseconds(..))
13+import Data.Traversable (traverse)
14+import Data.Tuple (Tuple(..), uncurry)
15+import Database.IndexedDB.Core (CursorDirection(..), Database, Index, ObjectStore, Transaction, TransactionMode(..))
16+import Database.IndexedDB.IDBCursor as IDBCursor
17+import Database.IndexedDB.IDBDatabase as IDBDatabase
18+import Database.IndexedDB.IDBFactory as IDBFactory
19+import Database.IndexedDB.IDBIndex as IDBIndex
20+import Database.IndexedDB.IDBKey (Key, none, toKey)
21+import Database.IndexedDB.IDBKeyRange as IDBKeyRange
22+import Database.IndexedDB.IDBObjectStore as IDBObjectStore
23+import Database.IndexedDB.IDBTransaction as IDBTransaction
24+import Effect (Effect)
25+import Effect.Aff (Aff, attempt, delay, forkAff, launchAff, launchAff_)
26+import Effect.Aff.AVar (AVar)
27+import Effect.Aff.AVar as AVar
28+import Effect.Class (liftEffect)
29+import Effect.Console (log)
30+import Effect.Exception (name)
31+import Effect.Now (now)
32+import Test.Spec (describe, it)
33+import Test.Spec.Assertions (shouldEqual, fail)
34+import Test.Spec.Reporter (consoleReporter)
35+import Test.Spec.Runner (runSpec)
36+
37+infixr 7 Tuple as :+:
38+
39+launchAff' :: forall a. Aff a -> Effect Unit
40+launchAff' =
41+ launchAff_ <<< void
42+
43+modifyAVar :: forall a. (a -> a) -> AVar a -> Aff Unit
44+modifyAVar fn v = do
45+ val <- AVar.take v
46+ AVar.put (fn val) v
47+
48+main :: Effect Unit
49+main = do
50+ launchAff_ $ runSpec [ consoleReporter ] $ do
51+ describe "IDBFactory" do
52+ let
53+ tearDown name version db = do
54+ IDBDatabase.close db
55+ version' <- IDBFactory.deleteDatabase name
56+ liftEffect $ log $ show version'
57+ version' `shouldEqual` version
58+
59+ it "open default" do
60+ let
61+ name = "db-default"
4062 version = 1
41− db <- IDBFactory.open name Nothing
42− { onUpgradeNeeded : Nothing
43− , onBlocked : Nothing
44− }
45− IDBDatabase.name db `shouldEqual` name
46− tearDown name version db
47−
48−
49− it "open specific version" do
50− let name = "db-specific"
63+ db <- IDBFactory.open name Nothing
64+ { onUpgradeNeeded: Nothing
65+ , onBlocked: Nothing
66+ }
67+ IDBDatabase.name db `shouldEqual` name
68+ tearDown name version db
69+
70+ it "open specific version" do
71+ let
72+ name = "db-specific"
5173 version = 14
52− db <- IDBFactory.open name (Just version)
53− { onUpgradeNeeded : Nothing
54− , onBlocked : Nothing
55− }
56− IDBDatabase.name db `shouldEqual` name
57− tearDown name version db
58−
59−
60− it "open specific version -> close -> open latest" do
61− let name = "db-latest"
74+ db <- IDBFactory.open name (Just version)
75+ { onUpgradeNeeded: Nothing
76+ , onBlocked: Nothing
77+ }
78+ IDBDatabase.name db `shouldEqual` name
79+ tearDown name version db
80+
81+ it "open specific version -> close -> open latest" do
82+ let
83+ name = "db-latest"
6284 version = 14
63− db <- IDBFactory.open name (Just version)
64− { onUpgradeNeeded : Nothing
65− , onBlocked : Nothing
66− }
67− IDBDatabase.name db `shouldEqual` name
68− liftEff $ IDBDatabase.close db
69− db <- IDBFactory.open name Nothing
70− { onUpgradeNeeded : Nothing
71− , onBlocked : Nothing
72− }
73− tearDown name version db
74−
75−
76− it "open + onUpgradeNeed" do
77− let name = "db-upgrade-needed"
85+ db01 <- IDBFactory.open name (Just version)
86+ { onUpgradeNeeded: Nothing
87+ , onBlocked: Nothing
88+ }
89+ IDBDatabase.name db01 `shouldEqual` name
90+ IDBDatabase.close db01
91+ db02 <- IDBFactory.open name Nothing
92+ { onUpgradeNeeded: Nothing
93+ , onBlocked: Nothing
94+ }
95+ tearDown name version db02
96+
97+ it "open + onUpgradeNeed" do
98+ let
99+ name = "db-upgrade-needed"
78100 version = 1
79− callback var db = do
80− _ <- launchAff $ modifyVar (const $ IDBDatabase.name db) var
101+ callback (Tuple varName varVersion) db _ { oldVersion } = do
102+ _ <- launchAff $ modifyAVar (const $ IDBDatabase.name db) varName
103+ _ <- launchAff $ modifyAVar (const $ oldVersion) varVersion
81104 pure unit
82− var <- makeVar' "-"
83− db <- IDBFactory.open name Nothing
84− { onUpgradeNeeded : Just (callback var)
85− , onBlocked : Nothing
86− }
87− name <- peekVar var
88− name `shouldEqual` name
89− tearDown name version db
90−
91−
92− it "open + onBlocked" do
93− let name = "db-blocked"
105+ varName <- AVar.new "-"
106+ varVersion <- AVar.new (-1)
107+ db <- IDBFactory.open name Nothing
108+ { onUpgradeNeeded: Just (callback (Tuple varName varVersion))
109+ , onBlocked: Nothing
110+ }
111+ name' <- AVar.read varName
112+ version' <- AVar.read varVersion
113+ name' `shouldEqual` name
114+ version' `shouldEqual` 0
115+ tearDown name version db
116+
117+ it "open + onBlocked" do
118+ let
119+ name = "db-blocked"
94120 version = 14
95121 callback var = do
96− _ <- launchAff $ modifyVar (const $ "db-blocked") var
122+ _ <- launchAff $ modifyAVar (const $ "db-blocked") var
97123 pure unit
98124
99− var <- makeVar' "-"
100− db01 <- IDBFactory.open name Nothing
101− { onUpgradeNeeded : Nothing
102− , onBlocked : Nothing
103− }
104− _ <- forkAff do
105− delay (Milliseconds 100.0)
106− liftEff $ IDBDatabase.close db01
107−
108− db02 <- IDBFactory.open name (Just version)
109− { onUpgradeNeeded : Nothing
110− , onBlocked : Just (callback var)
111− }
112− name <- peekVar var
113− name `shouldEqual` name
114− tearDown name version db02
115−
116− describe "IDBKeyRange" do
117− it "only(int)" do
118− let key = 14
125+ var <- AVar.new "-"
126+ db01 <- IDBFactory.open name Nothing
127+ { onUpgradeNeeded: Nothing
128+ , onBlocked: Nothing
129+ }
130+ _ <- forkAff do
131+ delay (Milliseconds 100.0)
132+ IDBDatabase.close db01
133+
134+ db02 <- IDBFactory.open name (Just version)
135+ { onUpgradeNeeded: Nothing
136+ , onBlocked: Just (callback var)
137+ }
138+ name' <- AVar.read var
139+ name' `shouldEqual` name
140+ tearDown name version db02
141+
142+ describe "IDBKeyRange" do
143+ it "only(int)" do
144+ let
145+ key = 14
119146 range = IDBKeyRange.only key
120− IDBKeyRange.includes range (toKey key) `shouldEqual` true
147+ IDBKeyRange.includes range (toKey key) `shouldEqual` true
121148
122− it "only(string)" do
123− let key = "patate"
149+ it "only(string)" do
150+ let
151+ key = "patate"
124152 range = IDBKeyRange.only key
125− IDBKeyRange.includes range (toKey key) `shouldEqual` true
153+ IDBKeyRange.includes range (toKey key) `shouldEqual` true
126154
127− it "only(float)" do
128− let key = 14.42
155+ it "only(float)" do
156+ let
157+ key = 14.42
129158 range = IDBKeyRange.only key
130− IDBKeyRange.includes range (toKey key) `shouldEqual` true
159+ IDBKeyRange.includes range (toKey key) `shouldEqual` true
131160
132− it "only(date)" do
133− let mkey = DateTime
161+ it "only(date)" do
162+ let
163+ mkey = DateTime
134164 <$> (Date.canonicalDate <$> toEnum 2017 <*> toEnum 6 <*> toEnum 23)
135165 <*> (Time <$> toEnum 17 <*> toEnum 59 <*> toEnum 34 <*> toEnum 42)
136166
137− case mkey of
138− Nothing ->
139− fail "unable to create datetime"
140− Just key -> do
141− let range = IDBKeyRange.only key
142− IDBKeyRange.includes range (toKey key) `shouldEqual` true
167+ case mkey of
168+ Nothing ->
169+ fail "unable to create datetime"
170+ Just key -> do
171+ let range = IDBKeyRange.only key
172+ IDBKeyRange.includes range (toKey key) `shouldEqual` true
143173
144− it "only([int])" do
145− let key = [14, 42]
174+ it "only([int])" do
175+ let
176+ key = [ 14, 42 ]
146177 range = IDBKeyRange.only key
147− IDBKeyRange.includes range (toKey key) `shouldEqual` true
178+ IDBKeyRange.includes range (toKey key) `shouldEqual` true
148179
149− it "only([string])" do
150− let key = ["patate", "autruche"]
180+ it "only([string])" do
181+ let
182+ key = [ "patate", "autruche" ]
151183 range = IDBKeyRange.only key
152− IDBKeyRange.includes range (toKey key) `shouldEqual` true
184+ IDBKeyRange.includes range (toKey key) `shouldEqual` true
153185
154− it "lowerBound(14, open)" do
155− let key = 14
156− open = true
186+ it "lowerBound(14, open)" do
187+ let
188+ key = 14
189+ open = true
157190 range = IDBKeyRange.lowerBound key open
158− IDBKeyRange.includes range (toKey (key + 1)) `shouldEqual` true
159− IDBKeyRange.includes range (toKey key) `shouldEqual` (not open)
160− IDBKeyRange.includes range (toKey (key - 1)) `shouldEqual` false
161−
162− it "lowerBound(14, close)" do
163− let key = 14
164− open = false
191+ IDBKeyRange.includes range (toKey (key + 1)) `shouldEqual` true
192+ IDBKeyRange.includes range (toKey key) `shouldEqual` (not open)
193+ IDBKeyRange.includes range (toKey (key - 1)) `shouldEqual` false
194+
195+ it "lowerBound(14, close)" do
196+ let
197+ key = 14
198+ open = false
165199 range = IDBKeyRange.lowerBound key open
166− IDBKeyRange.includes range (toKey (key + 1)) `shouldEqual` true
167− IDBKeyRange.includes range (toKey key) `shouldEqual` (not open)
168− IDBKeyRange.includes range (toKey (key - 1)) `shouldEqual` false
169−
170− it "upperBound(14, open)" do
171− let key = 14
172− open = true
200+ IDBKeyRange.includes range (toKey (key + 1)) `shouldEqual` true
201+ IDBKeyRange.includes range (toKey key) `shouldEqual` (not open)
202+ IDBKeyRange.includes range (toKey (key - 1)) `shouldEqual` false
203+
204+ it "upperBound(14, open)" do
205+ let
206+ key = 14
207+ open = true
173208 range = IDBKeyRange.upperBound key open
174− IDBKeyRange.includes range (toKey (key + 1)) `shouldEqual` false
175− IDBKeyRange.includes range (toKey key) `shouldEqual` (not open)
176− IDBKeyRange.includes range (toKey (key - 1)) `shouldEqual` true
177−
178− it "upperBound(14, close)" do
179− let key = 14
180− open = false
209+ IDBKeyRange.includes range (toKey (key + 1)) `shouldEqual` false
210+ IDBKeyRange.includes range (toKey key) `shouldEqual` (not open)
211+ IDBKeyRange.includes range (toKey (key - 1)) `shouldEqual` true
212+
213+ it "upperBound(14, close)" do
214+ let
215+ key = 14
216+ open = false
181217 range = IDBKeyRange.upperBound key open
182− IDBKeyRange.includes range (toKey (key + 1)) `shouldEqual` false
183− IDBKeyRange.includes range (toKey key) `shouldEqual` (not open)
184− IDBKeyRange.includes range (toKey (key - 1)) `shouldEqual` true
185−
186− it "bound(42, 14, open, open) => Nothing" do
187− let lower = 42
188− upper = 14
218+ IDBKeyRange.includes range (toKey (key + 1)) `shouldEqual` false
219+ IDBKeyRange.includes range (toKey key) `shouldEqual` (not open)
220+ IDBKeyRange.includes range (toKey (key - 1)) `shouldEqual` true
221+
222+ it "bound(42, 14, open, open) => Nothing" do
223+ let
224+ lower = 42
225+ upper = 14
189226 lowerOpen = true
190227 upperOpen = true
191− mrange = IDBKeyRange.bound { lower, upper, lowerOpen, upperOpen }
192− isNothing mrange `shouldEqual` true
228+ mrange = IDBKeyRange.bound { lower, upper, lowerOpen, upperOpen }
229+ isNothing mrange `shouldEqual` true
193230
194− it "bound(14, 42, open, open)" do
195− let lower = 14
196− upper = 42
231+ it "bound(14, 42, open, open)" do
232+ let
233+ lower = 14
234+ upper = 42
197235 lowerOpen = true
198236 upperOpen = true
199− mrange = IDBKeyRange.bound { lower, upper, lowerOpen, upperOpen }
200− case mrange of
201− Nothing ->
202− fail "invalid range provided"
203− Just range -> do
204− IDBKeyRange.includes range (toKey (lower + 1)) `shouldEqual` true
205− IDBKeyRange.includes range (toKey (upper - 1)) `shouldEqual` true
206− IDBKeyRange.includes range (toKey (lower - 1)) `shouldEqual` false
207− IDBKeyRange.includes range (toKey (upper + 1)) `shouldEqual` false
208− IDBKeyRange.includes range (toKey lower) `shouldEqual` (not lowerOpen)
209− IDBKeyRange.includes range (toKey upper) `shouldEqual` (not upperOpen)
210−
211− it "bound(14, 42, open, close)" do
212− let lower = 14
213− upper = 42
237+ mrange = IDBKeyRange.bound { lower, upper, lowerOpen, upperOpen }
238+ case mrange of
239+ Nothing ->
240+ fail "invalid range provided"
241+ Just range -> do
242+ IDBKeyRange.includes range (toKey (lower + 1)) `shouldEqual` true
243+ IDBKeyRange.includes range (toKey (upper - 1)) `shouldEqual` true
244+ IDBKeyRange.includes range (toKey (lower - 1)) `shouldEqual` false
245+ IDBKeyRange.includes range (toKey (upper + 1)) `shouldEqual` false
246+ IDBKeyRange.includes range (toKey lower) `shouldEqual` (not lowerOpen)
247+ IDBKeyRange.includes range (toKey upper) `shouldEqual` (not upperOpen)
248+
249+ it "bound(14, 42, open, close)" do
250+ let
251+ lower = 14
252+ upper = 42
214253 lowerOpen = true
215254 upperOpen = false
216− mrange = IDBKeyRange.bound { lower, upper, lowerOpen, upperOpen }
217− case mrange of
218− Nothing ->
219− fail "invalid range provided"
220− Just range -> do
221− IDBKeyRange.includes range (toKey (lower + 1)) `shouldEqual` true
222− IDBKeyRange.includes range (toKey (upper - 1)) `shouldEqual` true
223− IDBKeyRange.includes range (toKey (lower - 1)) `shouldEqual` false
224− IDBKeyRange.includes range (toKey (upper + 1)) `shouldEqual` false
225− IDBKeyRange.includes range (toKey lower) `shouldEqual` (not lowerOpen)
226− IDBKeyRange.includes range (toKey upper) `shouldEqual` (not upperOpen)
227−
228− it "bound(14, 42, close, open)" do
229− let lower = 14
230− upper = 42
255+ mrange = IDBKeyRange.bound { lower, upper, lowerOpen, upperOpen }
256+ case mrange of
257+ Nothing ->
258+ fail "invalid range provided"
259+ Just range -> do
260+ IDBKeyRange.includes range (toKey (lower + 1)) `shouldEqual` true
261+ IDBKeyRange.includes range (toKey (upper - 1)) `shouldEqual` true
262+ IDBKeyRange.includes range (toKey (lower - 1)) `shouldEqual` false
263+ IDBKeyRange.includes range (toKey (upper + 1)) `shouldEqual` false
264+ IDBKeyRange.includes range (toKey lower) `shouldEqual` (not lowerOpen)
265+ IDBKeyRange.includes range (toKey upper) `shouldEqual` (not upperOpen)
266+
267+ it "bound(14, 42, close, open)" do
268+ let
269+ lower = 14
270+ upper = 42
231271 lowerOpen = false
232272 upperOpen = true
233− mrange = IDBKeyRange.bound { lower, upper, lowerOpen, upperOpen }
234− case mrange of
235− Nothing ->
236− fail "invalid range provided"
237− Just range -> do
238− IDBKeyRange.includes range (toKey (lower + 1)) `shouldEqual` true
239− IDBKeyRange.includes range (toKey (upper - 1)) `shouldEqual` true
240− IDBKeyRange.includes range (toKey (lower - 1)) `shouldEqual` false
241− IDBKeyRange.includes range (toKey (upper + 1)) `shouldEqual` false
242− IDBKeyRange.includes range (toKey lower) `shouldEqual` (not lowerOpen)
243− IDBKeyRange.includes range (toKey upper) `shouldEqual` (not upperOpen)
244−
245− it "bound(14, 42, close, close)" do
246− let lower = 14
247− upper = 42
273+ mrange = IDBKeyRange.bound { lower, upper, lowerOpen, upperOpen }
274+ case mrange of
275+ Nothing ->
276+ fail "invalid range provided"
277+ Just range -> do
278+ IDBKeyRange.includes range (toKey (lower + 1)) `shouldEqual` true
279+ IDBKeyRange.includes range (toKey (upper - 1)) `shouldEqual` true
280+ IDBKeyRange.includes range (toKey (lower - 1)) `shouldEqual` false
281+ IDBKeyRange.includes range (toKey (upper + 1)) `shouldEqual` false
282+ IDBKeyRange.includes range (toKey lower) `shouldEqual` (not lowerOpen)
283+ IDBKeyRange.includes range (toKey upper) `shouldEqual` (not upperOpen)
284+
285+ it "bound(14, 42, close, close)" do
286+ let
287+ lower = 14
288+ upper = 42
248289 lowerOpen = false
249290 upperOpen = false
250− mrange = IDBKeyRange.bound { lower, upper, lowerOpen, upperOpen }
251− case mrange of
252− Nothing ->
253− fail "invalid range provided"
254− Just range -> do
255− IDBKeyRange.includes range (toKey (lower + 1)) `shouldEqual` true
256− IDBKeyRange.includes range (toKey (upper - 1)) `shouldEqual` true
257− IDBKeyRange.includes range (toKey (lower - 1)) `shouldEqual` false
258− IDBKeyRange.includes range (toKey (upper + 1)) `shouldEqual` false
259− IDBKeyRange.includes range (toKey lower) `shouldEqual` (not lowerOpen)
260− IDBKeyRange.includes range (toKey upper) `shouldEqual` (not upperOpen)
261−
262− it "can access attributes of a range" do
263− let range = IDBKeyRange.lowerBound 14 true
264− IDBKeyRange.lower range `shouldEqual` (Just $ toKey 14)
265− IDBKeyRange.upper range `shouldEqual` (Nothing :: Maybe Key)
266− IDBKeyRange.lowerOpen range `shouldEqual` true
267− -- NOTE Not true on Chrome 56.x => The default value for the flag is `true`
268− -- IDBKeyRange.upperOpen range `shouldEqual` false
269−
270− describe "IDBDatabase" do
271− it "createObjectStore (keyPath: [], autoIncrement: true)" do
272− let dbName = "db-create-store-01"
273− dbVersion = 1
274− storeName = "store"
275− keyPath = []
276− autoIncrement = true
277− callback var db = do
278− store <- IDBDatabase.createObjectStore db storeName { keyPath, autoIncrement }
279− _ <- launchAff $ IDBObjectStore.name store `shouldEqual` storeName
280− _ <- launchAff $ IDBObjectStore.keyPath store `shouldEqual` keyPath
281− _ <- launchAff $ IDBObjectStore.autoIncrement store `shouldEqual` autoIncrement
282− _ <- launchAff $ IDBObjectStore.indexNames store `shouldEqual` []
283− _ <- launchAff $ modifyVar (const true) var
284− pure unit
285−
286− var <- makeVar' false
287− db <- IDBFactory.open dbName Nothing
288− { onUpgradeNeeded : Just (callback var)
289− , onBlocked : Nothing
290− }
291− hasBeenCalled <- peekVar var
292− hasBeenCalled `shouldEqual` true
293− tearDown dbName dbVersion db
294−
295−
296− it "createObjectStore (keyPath: [\"field\"], autoIncrement: true)" do
297− let dbName = "db-create-store-02"
298− dbVersion = 1
299− storeName = "store"
300− keyPath = ["field"]
301− autoIncrement = true
302− callback var db = do
303− store <- IDBDatabase.createObjectStore db storeName { keyPath, autoIncrement }
304− _ <- launchAff $ IDBObjectStore.name store `shouldEqual` storeName
305− _ <- launchAff $ IDBObjectStore.keyPath store `shouldEqual` keyPath
306− _ <- launchAff $ IDBObjectStore.autoIncrement store `shouldEqual` autoIncrement
307− _ <- launchAff $ IDBObjectStore.indexNames store `shouldEqual` []
308− _ <- launchAff $ modifyVar (const true) var
309− pure unit
310−
311− var <- makeVar' false
312− db <- IDBFactory.open dbName Nothing
313− { onUpgradeNeeded : Just (callback var)
314− , onBlocked : Nothing
315− }
316− hasBeenCalled <- peekVar var
317− hasBeenCalled `shouldEqual` true
318− tearDown dbName dbVersion db
319−
320−
321− it "createObjectStore (keyPath: [\"a\", \"b\"], autoIncrement: false)" do
322− let dbName = "db-create-store-03"
323− dbVersion = 1
324− storeName = "store"
325− keyPath = ["a", "b"]
326− autoIncrement = false
327− callback var db = do
328− store <- IDBDatabase.createObjectStore db storeName { keyPath, autoIncrement }
329− _ <- launchAff $ IDBObjectStore.name store `shouldEqual` storeName
330− _ <- launchAff $ IDBObjectStore.keyPath store `shouldEqual` keyPath
331− _ <- launchAff $ IDBObjectStore.autoIncrement store `shouldEqual` autoIncrement
332− _ <- launchAff $ IDBObjectStore.indexNames store `shouldEqual` []
333− _ <- launchAff $ modifyVar (const true) var
334− pure unit
335−
336− var <- makeVar' false
337− db <- IDBFactory.open dbName Nothing
338− { onUpgradeNeeded : Just (callback var)
339− , onBlocked : Nothing
340− }
341− hasBeenCalled <- peekVar var
342− hasBeenCalled `shouldEqual` true
343− tearDown dbName dbVersion db
344−
345−
346− it "createObjectStore (keyPath: [\"field\"], autoIncrement: false)" do
347− let dbName = "db-create-store-04"
348− dbVersion = 1
349− storeName = "store"
350− keyPath = ["field"]
351− autoIncrement = false
352− callback var db = do
353− store <- IDBDatabase.createObjectStore db storeName { keyPath, autoIncrement }
354− _ <- launchAff $ IDBObjectStore.name store `shouldEqual` storeName
355− _ <- launchAff $ IDBObjectStore.keyPath store `shouldEqual` keyPath
356− _ <- launchAff $ IDBObjectStore.autoIncrement store `shouldEqual` autoIncrement
357− _ <- launchAff $ IDBObjectStore.indexNames store `shouldEqual` []
358− _ <- launchAff $ modifyVar (const true) var
359− pure unit
360−
361− var <- makeVar' false
362− db <- IDBFactory.open dbName Nothing
363− { onUpgradeNeeded : Just (callback var)
364− , onBlocked : Nothing
365− }
366− hasBeenCalled <- peekVar var
367− hasBeenCalled `shouldEqual` true
368− tearDown dbName dbVersion db
369−
370−
371− it "deleteObjectStore" do
372− let dbName = "db-delete-store"
373− dbVersion = 2
374− storeName = "store"
375− keyPath = []
376− autoIncrement = true
377− callback db = do
378− _ <- IDBDatabase.createObjectStore db storeName { keyPath, autoIncrement }
379− pure unit
380− callback2 var db = do
381− _ <- IDBDatabase.deleteObjectStore db storeName
382− _ <- launchAff $ modifyVar (const true) var
383− pure unit
384−
385− var <- makeVar' false
386− db <- IDBFactory.open dbName Nothing
387− { onUpgradeNeeded : Just callback
388− , onBlocked : Nothing
389− }
390− liftEff $ IDBDatabase.close db
391− db <- IDBFactory.open dbName (Just dbVersion)
392− { onUpgradeNeeded : Just (callback2 var)
393− , onBlocked : Nothing
394− }
395− hasBeenCalled <- peekVar var
396− hasBeenCalled `shouldEqual` true
397− tearDown dbName dbVersion db
398−
399− describe "IDBObjectStore" do
400− it "add(\"patate\", Nothing )" do
401− let dbName = "db-add"
402− dbVersion = 1
403− storeName = "store"
404− keyPath = []
405− autoIncrement = true
406− key = 1
407− callback db = do
408− store <- IDBDatabase.createObjectStore db storeName { keyPath, autoIncrement }
409− key <- launchAff $ ((unsafeFromKey >>> shouldEqual key)
410− <$> IDBObjectStore.add store "patate" Nothing)
411− pure unit
412−
413− db <- IDBFactory.open dbName Nothing
414− { onUpgradeNeeded : Just callback
415− , onBlocked : Nothing
416− }
417− tearDown dbName dbVersion db
418−
419− it "add(\"patate\", Just 14 )" do
420− let dbName = "db-add"
421− dbVersion = 1
422− storeName = "store"
423− keyPath = []
424− autoIncrement = true
425− key = 14
426− callback db = do
427− store <- IDBDatabase.createObjectStore db storeName { keyPath, autoIncrement }
428− key <- launchAff $ ((unsafeFromKey >>> shouldEqual key)
429− <$> IDBObjectStore.add store "patate" (Just $ toKey key))
430− pure unit
431−
432− db <- IDBFactory.open dbName Nothing
433− { onUpgradeNeeded : Just callback
434− , onBlocked : Nothing
435− }
436− tearDown dbName dbVersion db
437−
438− it "add(\"patate\", Just \"key\" )" do
439− let dbName = "db-add"
440− dbVersion = 1
441− storeName = "store"
442− keyPath = []
443− autoIncrement = true
444− key = "key"
445− callback db = do
446− store <- IDBDatabase.createObjectStore db storeName { keyPath, autoIncrement }
447− key <- launchAff $ ((unsafeFromKey >>> shouldEqual key)
448− <$> IDBObjectStore.add store "patate" (Just $ toKey key))
449− pure unit
291+ mrange = IDBKeyRange.bound { lower, upper, lowerOpen, upperOpen }
292+ case mrange of
293+ Nothing ->
294+ fail "invalid range provided"
295+ Just range -> do
296+ IDBKeyRange.includes range (toKey (lower + 1)) `shouldEqual` true
297+ IDBKeyRange.includes range (toKey (upper - 1)) `shouldEqual` true
298+ IDBKeyRange.includes range (toKey (lower - 1)) `shouldEqual` false
299+ IDBKeyRange.includes range (toKey (upper + 1)) `shouldEqual` false
300+ IDBKeyRange.includes range (toKey lower) `shouldEqual` (not lowerOpen)
301+ IDBKeyRange.includes range (toKey upper) `shouldEqual` (not upperOpen)
302+
303+ it "can access attributes of a range" do
304+ let range = IDBKeyRange.lowerBound 14 false
305+ IDBKeyRange.lower range `shouldEqual` (Just $ toKey 14)
306+ IDBKeyRange.upper range `shouldEqual` none
307+ IDBKeyRange.lowerOpen range `shouldEqual` false
308+ IDBKeyRange.upperOpen range `shouldEqual` true
309+
310+ describe "IDBDatabase" do
311+ let
312+ tearDown db = do
313+ IDBDatabase.close db
314+ _ <- IDBFactory.deleteDatabase (IDBDatabase.name db)
315+ pure unit
316+
317+ setup storeParams = do
318+ let
319+ onUpgradeNeeded var db _ _ = launchAff' do
320+ store <- IDBDatabase.createObjectStore db "store" storeParams
321+ _ <- AVar.put { db, store } var
322+ pure unit
450323
451− db <- IDBFactory.open dbName Nothing
452− { onUpgradeNeeded : Just callback
453− , onBlocked : Nothing
454− }
455− tearDown dbName dbVersion db
456−
457− it "add(\"patate\", Just <date> )" do
458− let dbName = "db-add"
459− dbVersion = 1
460− storeName = "store"
461− keyPath = []
462− autoIncrement = true
463− mkey = DateTime
464− <$> (Date.canonicalDate <$> toEnum 2017 <*> toEnum 6 <*> toEnum 23)
465− <*> (Time <$> toEnum 17 <*> toEnum 59 <*> toEnum 34 <*> toEnum 42)
324+ var <- AVar.empty
325+ _ <- IDBFactory.open "db" Nothing
326+ { onUpgradeNeeded: Just (onUpgradeNeeded var)
327+ , onBlocked: Nothing
328+ }
466329
467− case mkey of
468− Nothing ->
469− fail "unable to create datetime"
470− Just key -> do
471− let callback db = do
472− store <- IDBDatabase.createObjectStore db storeName { keyPath, autoIncrement }
473− key <- launchAff $ ((unsafeFromKey >>> shouldEqual key)
474− <$> IDBObjectStore.add store "patate" (Just $ toKey key))
475− pure unit
476−
477− db <- IDBFactory.open dbName Nothing
478− { onUpgradeNeeded : Just callback
479− , onBlocked : Nothing
330+ AVar.take var
331+
332+ it "createObjectStore (keyPath: [], autoIncrement: true)" do
333+ { db, store } <- setup { keyPath: [], autoIncrement: true }
334+ IDBObjectStore.name store `shouldEqual` "store"
335+ IDBObjectStore.keyPath store `shouldEqual` []
336+ IDBObjectStore.autoIncrement store `shouldEqual` true
337+ IDBObjectStore.indexNames store `shouldEqual` []
338+ tearDown db
339+
340+ it "createObjectStore (keyPath: [\"patate\"], autoIncrement: true)" do
341+ { db, store } <- setup { keyPath: [ "patate" ], autoIncrement: true }
342+ IDBObjectStore.name store `shouldEqual` "store"
343+ IDBObjectStore.keyPath store `shouldEqual` [ "patate" ]
344+ IDBObjectStore.autoIncrement store `shouldEqual` true
345+ IDBObjectStore.indexNames store `shouldEqual` []
346+ tearDown db
347+
348+ it "createObjectStore (keyPath: [\"a\", \"b\"], autoIncrement: false)" do
349+ { db, store } <- setup { keyPath: [ "patate", "autruche" ], autoIncrement: false }
350+ IDBObjectStore.name store `shouldEqual` "store"
351+ IDBObjectStore.keyPath store `shouldEqual` [ "patate", "autruche" ]
352+ IDBObjectStore.autoIncrement store `shouldEqual` false
353+ IDBObjectStore.indexNames store `shouldEqual` []
354+ tearDown db
355+
356+ it "deleteObjectStore" do
357+ let
358+ onUpgradeNeeded var db _ _ = launchAff' do
359+ _ <- IDBDatabase.deleteObjectStore db "store"
360+ AVar.put true var
361+
362+ var <- AVar.empty
363+ { db } <- setup IDBDatabase.defaultParameters
364+ IDBDatabase.close db
365+ db' <- IDBFactory.open "db" (Just 999)
366+ { onUpgradeNeeded: Just (onUpgradeNeeded var)
367+ , onBlocked: Nothing
368+ }
369+ deleted <- AVar.take var
370+ deleted `shouldEqual` true
371+ tearDown db'
372+
373+ describe "IDBObjectStore" do
374+ let
375+ tearDown db = do
376+ IDBDatabase.close db
377+ _ <- IDBFactory.deleteDatabase (IDBDatabase.name db)
378+ pure unit
379+
380+ setup { storeParams, onUpgradeNeeded } = do
381+ let
382+ onUpgradeNeeded' var db _ _ = launchAff' do
383+ store <- IDBDatabase.createObjectStore db "store" storeParams
384+ liftEffect $ maybe (pure unit) identity (onUpgradeNeeded <*> pure db <*> pure store)
385+ AVar.put { db, store } var
386+
387+ var <- AVar.empty
388+ _ <- IDBFactory.open "db" Nothing
389+ { onUpgradeNeeded: Just (onUpgradeNeeded' var)
390+ , onBlocked: Nothing
480391 }
481− tearDown dbName dbVersion db
482−
483− it "add(\"patate\", Just [14, 42] )" do
484− let dbName = "db-add"
485− dbVersion = 1
486− storeName = "store"
487− keyPath = []
488− autoIncrement = true
489− key = [14, 42]
490− callback db = do
491− store <- IDBDatabase.createObjectStore db storeName { keyPath, autoIncrement }
492− key <- launchAff $ ((unsafeFromKey >>> shouldEqual key)
493− <$> IDBObjectStore.add store "patate" (Just $ toKey key))
494− pure unit
495392
496− db <- IDBFactory.open dbName Nothing
497− { onUpgradeNeeded : Just callback
498− , onBlocked : Nothing
499− }
500− tearDown dbName dbVersion db
501−
502− it "clear()" do
503− let dbName = "db-clear"
504− dbVersion = 1
505− storeName = "store"
506− keyPath = []
507− autoIncrement = true
508− callback db = do
509− store <- IDBDatabase.createObjectStore db storeName { keyPath, autoIncrement }
510− _ <- launchAff $ do
511− _ <- IDBObjectStore.add store "patate" (Just $ toKey 14)
393+ AVar.take var
394+
395+ it "add()" do
396+ date <- liftEffect $ toDateTime <$> now
397+ { db } <- setup
398+ { storeParams: { autoIncrement: true, keyPath: [] }
399+ , onUpgradeNeeded: Just $ \_ store -> launchAff' do
400+ -- no key
401+ key <- IDBObjectStore.add store "patate" none
402+ (toKey 1) `shouldEqual` key
403+
404+ -- int key
405+ key' <- IDBObjectStore.add store "patate" (Just 14)
406+ (toKey 14) `shouldEqual` key'
407+
408+ -- number key
409+ key'' <- IDBObjectStore.add store "patate" (Just 14.42)
410+ (toKey 14.42) `shouldEqual` key''
411+
412+ -- string key
413+ key''' <- IDBObjectStore.add store "patate" (Just "key")
414+ (toKey "key") `shouldEqual` key'''
415+
416+ -- date key
417+ key'''' <- IDBObjectStore.add store "patate" (Just date)
418+ (toKey date) `shouldEqual` key''''
419+
420+ -- array key
421+ key''''' <- IDBObjectStore.add store "patate" (Just $ toKey [ 14, 42 ])
422+ (toKey [ 14, 42 ]) `shouldEqual` key'''''
423+ }
424+ tearDown db
425+
426+ it "clear()" do
427+ { db } <- setup
428+ { storeParams: IDBDatabase.defaultParameters
429+ , onUpgradeNeeded: Just $ \_ store -> launchAff' do
430+ key <- IDBObjectStore.add store "patate" (Just 14)
512431 _ <- IDBObjectStore.clear store
513− v <- IDBObjectStore.get store (IDBKeyRange.only 14)
514− v `shouldEqual` (Nothing :: Maybe Int)
515−
516− pure unit
517−
518− db <- IDBFactory.open dbName Nothing
519− { onUpgradeNeeded : Just callback
520− , onBlocked : Nothing
521− }
522− tearDown dbName dbVersion db
523−
524− it "count(Nothing) " do
525− let dbName = "db-count"
526− dbVersion = 1
527− storeName = "store"
528− keyPath = []
529− autoIncrement = true
530− callback db = do
531− store <- IDBDatabase.createObjectStore db storeName { keyPath, autoIncrement }
532− _ <- launchAff $ do
533− _ <- IDBObjectStore.add store "patate" Nothing
534− _ <- IDBObjectStore.add store 14 Nothing
432+ val <- IDBObjectStore.get store (IDBKeyRange.only key)
433+ val `shouldEqual` (Nothing :: Maybe String)
434+ }
435+ tearDown db
436+
437+ it "count()" do
438+ { db } <- setup
439+ { storeParams: IDBDatabase.defaultParameters
440+ , onUpgradeNeeded: Just $ \_ store -> launchAff' do
441+ _ <- IDBObjectStore.add store "patate" (Just 14)
442+ _ <- IDBObjectStore.add store "autruche" (Just 42)
535443 n <- IDBObjectStore.count store Nothing
536444 n `shouldEqual` 2
537− pure unit
538−
539− db <- IDBFactory.open dbName Nothing
540− { onUpgradeNeeded : Just callback
541− , onBlocked : Nothing
542− }
543− tearDown dbName dbVersion db
544−
545−
546− it "getKey() (fixed)" do
547− let dbName = "db-getKey"
548− dbVersion = 1
549− storeName = "store"
550− keyPath = ["id"]
551− key = "patate"
552− autoIncrement = false
553− callback db = do
554− store <- IDBDatabase.createObjectStore db storeName { keyPath, autoIncrement }
555− _ <- launchAff $ do
556− key' <- IDBObjectStore.add store { id: key, value: 14 } Nothing
557− (unsafeFromKey key') `shouldEqual` key
558− key' <- IDBObjectStore.getKey store (IDBKeyRange.only key)
559− (unsafeFromKey <$> key') `shouldEqual` (Just key)
560− pure unit
561−
562− db <- IDBFactory.open dbName Nothing
563− { onUpgradeNeeded : Just callback
564− , onBlocked : Nothing
565− }
566− tearDown dbName dbVersion db
567−
568− it "getKey() (given)" do
569− let dbName = "db-getKey"
570− dbVersion = 1
571− storeName = "store"
572− keyPath = []
573− autoIncrement = true
574− callback db = do
575− store <- IDBDatabase.createObjectStore db storeName { keyPath, autoIncrement }
576− _ <- launchAff $ do
577− key <- IDBObjectStore.add store "patate" Nothing
578− mkey <- IDBObjectStore.getKey store (IDBKeyRange.only key)
579− mkey `shouldEqual` Just key
580− pure unit
581−
582− db <- IDBFactory.open dbName Nothing
583− { onUpgradeNeeded : Just callback
584− , onBlocked : Nothing
585− }
586− tearDown dbName dbVersion db
587−
588− it "getKey() unknown" do
589− let dbName = "db-getKey"
590− dbVersion = 1
591− storeName = "store"
592− keyPath = []
593− autoIncrement = true
594− callback db = do
595− store <- IDBDatabase.createObjectStore db storeName { keyPath, autoIncrement }
596− _ <- launchAff $ do
597− key <- IDBObjectStore.add store "patate" Nothing
445+ }
446+ tearDown db
447+
448+ it "getKey()" do
449+ { db } <- setup
450+ { storeParams: IDBDatabase.defaultParameters
451+ , onUpgradeNeeded: Just $ \_ store -> launchAff' do
452+ key <- IDBObjectStore.add store "patate" (Just 14)
598453 mkey <- IDBObjectStore.getKey store (IDBKeyRange.only 14)
599− mkey `shouldEqual` Nothing
600− pure unit
601−
602− db <- IDBFactory.open dbName Nothing
603− { onUpgradeNeeded : Just callback
604− , onBlocked : Nothing
605− }
606− tearDown dbName dbVersion db
607−
608− it "getAllKeys(nothing, nothing)" do
609− let dbName = "db-getAllKeys"
610− dbVersion = 1
611− storeName = "store"
612− keyPath = []
613− autoIncrement = true
614− callback db = do
615− store <- IDBDatabase.createObjectStore db storeName { keyPath, autoIncrement }
616− _ <- launchAff $ do
617− k1 <- IDBObjectStore.add store "patate" Nothing
618− k2 <- IDBObjectStore.add store "autruche" Nothing
454+ mkey `shouldEqual` (Just key)
455+
456+ mkey' <- IDBObjectStore.getKey store (IDBKeyRange.only 42)
457+ mkey' `shouldEqual` none
458+ }
459+ tearDown db
460+
461+ it "getAllKeys()" do
462+ { db } <- setup
463+ { storeParams: IDBDatabase.defaultParameters
464+ , onUpgradeNeeded: Just $ \_ store -> launchAff' do
465+ key1 <- IDBObjectStore.add store "patate" (Just 14)
466+ key2 <- IDBObjectStore.add store "autruche" (Just 42)
467+ key3 <- IDBObjectStore.add store 14 (Just 1337)
468+
469+ -- no bounds
619470 keys <- IDBObjectStore.getAllKeys store Nothing Nothing
620− keys `shouldEqual` [k1,k2]
621− pure unit
622−
623− db <- IDBFactory.open dbName Nothing
624− { onUpgradeNeeded : Just callback
625− , onBlocked : Nothing
626− }
627− tearDown dbName dbVersion db
628−
629− it "getAllKeys(lowerBound(1, true), nothing)" do
630− let dbName = "db-getAllKeys"
631− dbVersion = 1
632− storeName = "store"
633− keyPath = []
634− autoIncrement = true
635− callback db = do
636− store <- IDBDatabase.createObjectStore db storeName { keyPath, autoIncrement }
637− _ <- launchAff $ do
638− k1 <- IDBObjectStore.add store "patate" Nothing
639− k2 <- IDBObjectStore.add store "autruche" Nothing
640− k3 <- IDBObjectStore.add store 14 Nothing
641− keys <- IDBObjectStore.getAllKeys store (Just $ IDBKeyRange.lowerBound 1 true) Nothing
642− keys `shouldEqual` [k2, k3]
643− pure unit
644−
645− db <- IDBFactory.open dbName Nothing
646− { onUpgradeNeeded : Just callback
647− , onBlocked : Nothing
648− }
649− tearDown dbName dbVersion db
650−
651− it "openCursor(Nothing, Next)" do
652− let dbName = "db-openCursor"
653− dbVersion = 1
654− storeName = "store"
655− keyPath = []
656− autoIncrement = true
657− callback db = do
658− store <- IDBDatabase.createObjectStore db storeName { keyPath, autoIncrement }
659− _ <- launchAff $ do
660− _ <- IDBObjectStore.openCursor store Nothing Next
661− pure unit
662− pure unit
663−
664− db <- IDBFactory.open dbName Nothing
665− { onUpgradeNeeded : Just callback
666− , onBlocked : Nothing
667− }
668− tearDown dbName dbVersion db
669−
670− it "openCursor(Nothing, NextUnique)" do
671− let dbName = "db-openCursor"
672− dbVersion = 1
673− storeName = "store"
674− keyPath = []
675− autoIncrement = true
676− callback db = do
677− store <- IDBDatabase.createObjectStore db storeName { keyPath, autoIncrement }
678− _ <- launchAff $ do
679− _ <- IDBObjectStore.openCursor store Nothing NextUnique
680− pure unit
681− pure unit
682−
683− db <- IDBFactory.open dbName Nothing
684− { onUpgradeNeeded : Just callback
685− , onBlocked : Nothing
686− }
687− tearDown dbName dbVersion db
688−
689− it "openCursor(Nothing, Prev)" do
690− let dbName = "db-openCursor"
691− dbVersion = 1
692− storeName = "store"
693− keyPath = []
694− autoIncrement = true
695− callback db = do
696− store <- IDBDatabase.createObjectStore db storeName { keyPath, autoIncrement }
697− _ <- launchAff $ do
698− _ <- IDBObjectStore.openCursor store Nothing Prev
699− pure unit
700− pure unit
701−
702− db <- IDBFactory.open dbName Nothing
703− { onUpgradeNeeded : Just callback
704− , onBlocked : Nothing
705− }
706− tearDown dbName dbVersion db
707−
708− it "openCursor(Nothing, PrevUnique)" do
709− let dbName = "db-openCursor"
710− dbVersion = 1
711− storeName = "store"
712− keyPath = []
713− autoIncrement = true
714− callback db = do
715− store <- IDBDatabase.createObjectStore db storeName { keyPath, autoIncrement }
716− _ <- launchAff $ do
717− _ <- IDBObjectStore.openCursor store Nothing PrevUnique
718− pure unit
719− pure unit
720−
721− db <- IDBFactory.open dbName Nothing
722− { onUpgradeNeeded : Just callback
723− , onBlocked : Nothing
724− }
725− tearDown dbName dbVersion db
726−
727− it "openCursor(Just upperBound, Next)" do
728− let dbName = "db-openCursor"
729− dbVersion = 1
730− storeName = "store"
731− keyPath = []
732− autoIncrement = true
733− callback db = do
734− store <- IDBDatabase.createObjectStore db storeName { keyPath, autoIncrement }
735− _ <- launchAff $ do
736− _ <- IDBObjectStore.openCursor store (Just $ IDBKeyRange.upperBound 10 true) Next
737− pure unit
738− pure unit
739−
740− db <- IDBFactory.open dbName Nothing
741− { onUpgradeNeeded : Just callback
742− , onBlocked : Nothing
743− }
744− tearDown dbName dbVersion db
745−
746− it "openKeyCursor(Nothing, Next)" do
747− let dbName = "db-openKeyCursor"
748− dbVersion = 1
749− storeName = "store"
750− keyPath = []
751− autoIncrement = true
752− callback db = do
753− store <- IDBDatabase.createObjectStore db storeName { keyPath, autoIncrement }
754− _ <- launchAff $ do
755− _ <- IDBObjectStore.openKeyCursor store Nothing Next
756− pure unit
757− pure unit
758−
759− db <- IDBFactory.open dbName Nothing
760− { onUpgradeNeeded : Just callback
761− , onBlocked : Nothing
762− }
763− tearDown dbName dbVersion db
764−
765− it "openKeyCursor(Nothing, NextUnique)" do
766− let dbName = "db-openKeyCursor"
767− dbVersion = 1
768− storeName = "store"
769− keyPath = []
770− autoIncrement = true
771− callback db = do
772− store <- IDBDatabase.createObjectStore db storeName { keyPath, autoIncrement }
773− _ <- launchAff $ do
774− _ <- IDBObjectStore.openKeyCursor store Nothing NextUnique
775− pure unit
776− pure unit
777−
778− db <- IDBFactory.open dbName Nothing
779− { onUpgradeNeeded : Just callback
780− , onBlocked : Nothing
781− }
782− tearDown dbName dbVersion db
783−
784− it "openKeyCursor(Nothing, Prev)" do
785− let dbName = "db-openKeyCursor"
786− dbVersion = 1
787− storeName = "store"
788− keyPath = []
789− autoIncrement = true
790− callback db = do
791− store <- IDBDatabase.createObjectStore db storeName { keyPath, autoIncrement }
792− _ <- launchAff $ do
793− _ <- IDBObjectStore.openKeyCursor store Nothing Prev
794− pure unit
795− pure unit
796−
797− db <- IDBFactory.open dbName Nothing
798− { onUpgradeNeeded : Just callback
799− , onBlocked : Nothing
800− }
801− tearDown dbName dbVersion db
802−
803− it "openKeyCursor(Nothing, PrevUnique)" do
804− let dbName = "db-openKeyCursor"
805− dbVersion = 1
806− storeName = "store"
807− keyPath = []
808− autoIncrement = true
809− callback db = do
810− store <- IDBDatabase.createObjectStore db storeName { keyPath, autoIncrement }
811− _ <- launchAff $ do
812− _ <- IDBObjectStore.openKeyCursor store Nothing PrevUnique
471+ keys `shouldEqual` [ key1, key2, key3 ]
472+
473+ -- lower bound
474+ keys' <- IDBObjectStore.getAllKeys store (Just $ IDBKeyRange.lowerBound 14 true) Nothing
475+ keys' `shouldEqual` [ key2, key3 ]
476+
477+ -- upper bound
478+ keys'' <- IDBObjectStore.getAllKeys store (Just $ IDBKeyRange.upperBound 42 false) Nothing
479+ keys'' `shouldEqual` [ key1, key2 ]
480+
481+ -- count
482+ keys''' <- IDBObjectStore.getAllKeys store (Just $ IDBKeyRange.lowerBound 1 true) (Just 2)
483+ keys''' `shouldEqual` [ key1, key2 ]
484+ }
485+ tearDown db
486+
487+ it "openCursor()" do
488+ let
489+ cb =
490+ { onSuccess: const $ pure unit
491+ , onError: show >>> fail >>> launchAff'
492+ , onComplete: pure unit
493+ }
494+ { db } <- setup
495+ { storeParams: IDBDatabase.defaultParameters
496+ , onUpgradeNeeded: Just $ \_ store -> launchAff' do
497+ _ <- IDBObjectStore.openCursor store Nothing Next cb
498+ _ <- IDBObjectStore.openCursor store Nothing NextUnique cb
499+ _ <- IDBObjectStore.openCursor store Nothing Prev cb
500+ _ <- IDBObjectStore.openCursor store Nothing PrevUnique cb
501+ _ <- IDBObjectStore.openCursor store (Just $ IDBKeyRange.upperBound 1 true) Next cb
813502 pure unit
814− pure unit
815−
816− db <- IDBFactory.open dbName Nothing
817− { onUpgradeNeeded : Just callback
818− , onBlocked : Nothing
819− }
820− tearDown dbName dbVersion db
821−
822− it "openKeyCursor(Just upperBound, Next)" do
823− let dbName = "db-openKeyCursor"
824− dbVersion = 1
825− storeName = "store"
826− keyPath = []
827− autoIncrement = true
828− callback db = do
829− store <- IDBDatabase.createObjectStore db storeName { keyPath, autoIncrement }
830− _ <- launchAff $ do
831− _ <- IDBObjectStore.openKeyCursor store (Just $ IDBKeyRange.upperBound 10 true) Next
503+ }
504+ tearDown db
505+
506+ it "openKeyCursor()" do
507+ let
508+ cb =
509+ { onSuccess: const $ pure unit
510+ , onError: show >>> fail >>> launchAff'
511+ , onComplete: pure unit
512+ }
513+ { db } <- setup
514+ { storeParams: IDBDatabase.defaultParameters
515+ , onUpgradeNeeded: Just $ \_ store -> launchAff' do
516+ _ <- IDBObjectStore.openKeyCursor store Nothing Next cb
517+ _ <- IDBObjectStore.openKeyCursor store Nothing NextUnique cb
518+ _ <- IDBObjectStore.openKeyCursor store Nothing Prev cb
519+ _ <- IDBObjectStore.openKeyCursor store Nothing PrevUnique cb
520+ _ <- IDBObjectStore.openKeyCursor store (Just $ IDBKeyRange.lowerBound 1 true) Next cb
832521 pure unit
833− pure unit
834−
835− db <- IDBFactory.open dbName Nothing
836− { onUpgradeNeeded : Just callback
837− , onBlocked : Nothing
838− }
839− tearDown dbName dbVersion db
840−
841− it "store.autoIncrement[true]" do
842− let dbName = "db-autoIncrement"
843− dbVersion = 1
844− storeName = "store"
845− keyPath = []
846− autoIncrement = true
847− callback db = do
848− store <- IDBDatabase.createObjectStore db storeName { keyPath, autoIncrement }
849− let autoIncrement' = IDBObjectStore.autoIncrement store
850− _ <- launchAff $ autoIncrement `shouldEqual` autoIncrement'
851− pure unit
852−
853− db <- IDBFactory.open dbName Nothing
854− { onUpgradeNeeded : Just callback
855− , onBlocked : Nothing
856− }
857− tearDown dbName dbVersion db
858−
859− it "store.autoIncrement[false]" do
860− let dbName = "db-autoIncrement"
861− dbVersion = 1
862− storeName = "store"
863− keyPath = []
864− autoIncrement = false
865− callback db = do
866− store <- IDBDatabase.createObjectStore db storeName { keyPath, autoIncrement }
867− let autoIncrement' = IDBObjectStore.autoIncrement store
868− _ <- launchAff $ autoIncrement `shouldEqual` autoIncrement'
869− pure unit
870−
871− db <- IDBFactory.open dbName Nothing
872− { onUpgradeNeeded : Just callback
873− , onBlocked : Nothing
874− }
875− tearDown dbName dbVersion db
876−
877− it "store.keyPath[\"patate\"]" do
878− let dbName = "db-keyPath"
879− dbVersion = 1
880− storeName = "store"
881− keyPath = ["patate"]
882− autoIncrement = false
883− callback db = do
884− store <- IDBDatabase.createObjectStore db storeName { keyPath, autoIncrement }
885− let keyPath' = IDBObjectStore.keyPath store
886− _ <- launchAff $ keyPath `shouldEqual` keyPath'
887− pure unit
888−
889− db <- IDBFactory.open dbName Nothing
890− { onUpgradeNeeded : Just callback
891− , onBlocked : Nothing
892− }
893− tearDown dbName dbVersion db
894−
895− it "store.indexNames[]" do
896− let dbName = "db-indexNames"
897− dbVersion = 1
898− storeName = "store"
899− keyPath = []
900− autoIncrement = false
901− callback db = do
902− store <- IDBDatabase.createObjectStore db storeName { keyPath, autoIncrement }
903− let indexNames = IDBObjectStore.indexNames store
904− _ <- launchAff $ indexNames `shouldEqual` []
905− pure unit
906−
907− db <- IDBFactory.open dbName Nothing
908− { onUpgradeNeeded : Just callback
909− , onBlocked : Nothing
910− }
911− tearDown dbName dbVersion db
912−
913− it "store.name[\"store\"]" do
914− let dbName = "db-indexNames"
915− dbVersion = 1
916− storeName = "store"
917− keyPath = []
918− autoIncrement = false
919− callback db = do
920− store <- IDBDatabase.createObjectStore db storeName { keyPath, autoIncrement }
921− let storeName' = IDBObjectStore.name store
922− _ <- launchAff $ storeName `shouldEqual` storeName'
923− pure unit
522+ }
523+ tearDown db
524+
525+ describe "IDBIndex" do
526+ let
527+ tearDown db = do
528+ IDBDatabase.close db
529+ _ <- IDBFactory.deleteDatabase (IDBDatabase.name db)
530+ pure unit
531+
532+ setup
533+ :: forall value
534+ . { storeParams :: { keyPath :: Array String, autoIncrement :: Boolean }
535+ , indexParams :: { unique :: Boolean, multiEntry :: Boolean }
536+ , values :: Array (Tuple value (Maybe Key))
537+ , keyPath :: Array String
538+ , onUpgradeNeeded :: Maybe (Database -> Transaction -> Index -> Effect Unit)
539+ }
540+ -> Aff { db :: Database, index :: Index, store :: ObjectStore }
541+ setup { storeParams, indexParams, values, keyPath, onUpgradeNeeded } = do
542+ let
543+ onUpgradeNeeded' var db tx _ = launchAff' do
544+ store <- IDBDatabase.createObjectStore db "store" storeParams
545+ _ <- traverse (uncurry (IDBObjectStore.add store)) values
546+ index <- IDBObjectStore.createIndex store "index" keyPath indexParams
547+ liftEffect $ maybe (pure unit) identity (onUpgradeNeeded <*> pure db <*> pure tx <*> pure index)
548+ AVar.put { db, index, store } var
549+
550+ var <- AVar.empty
551+ _ <- IDBFactory.open "db" Nothing
552+ { onUpgradeNeeded: Just (onUpgradeNeeded' var)
553+ , onBlocked: Nothing
554+ }
555+ AVar.take var
556+
557+ it "returns an IDBIndex and the properties are set correctly" do
558+ { db, index } <- setup
559+ { storeParams: IDBDatabase.defaultParameters
560+ , indexParams: IDBObjectStore.defaultParameters
561+ , onUpgradeNeeded: Nothing
562+ , keyPath: []
563+ , values: []
564+ }
565+ IDBIndex.name index `shouldEqual` "index"
566+ IDBIndex.keyPath index `shouldEqual` []
567+ IDBIndex.unique index `shouldEqual` IDBObjectStore.defaultParameters.unique
568+ IDBIndex.multiEntry index `shouldEqual` IDBObjectStore.defaultParameters.multiEntry
569+ tearDown db
570+
571+ it "attempt to create an index that requires unique values on an object store already contains duplicates" do
572+ let onAbort var = launchAff' (AVar.put true var)
573+ txVar <- AVar.empty
574+ dbVar <- AVar.empty
575+ res <- attempt $ setup
576+ { storeParams: IDBDatabase.defaultParameters
577+ , indexParams:
578+ { unique: true
579+ , multiEntry: false
580+ }
581+ , keyPath: [ "indexedProperty" ]
582+ , values:
583+ [ { indexedProperty: "bar" } :+: (Just $ toKey 1)
584+ , { indexedProperty: "bar" } :+: (Just $ toKey 2)
585+ ]
586+ , onUpgradeNeeded: Just $ \db tx _ -> launchAff' do
587+ IDBTransaction.onAbort tx (onAbort txVar)
588+ IDBDatabase.onAbort db (onAbort dbVar)
589+ }
590+ case res of
591+ Right _ ->
592+ fail "expected abort"
593+ _ -> do
594+ shouldEqual true =<< AVar.take txVar
595+ shouldEqual true =<< AVar.take dbVar
596+
597+ it "the index is usable right after being made" do
598+ { db } <- setup
599+ { storeParams:
600+ { keyPath: [ "key" ]
601+ , autoIncrement: false
602+ }
603+ , indexParams: IDBObjectStore.defaultParameters
604+ , keyPath: [ "indexedProperty" ]
605+ , values:
606+ [ { key: "key1", indexedProperty: "indexed_1" } :+: Nothing
607+ , { key: "key2", indexedProperty: "indexed_2" } :+: Nothing
608+ , { key: "key3", indexedProperty: "indexed_3" } :+: Nothing
609+ ]
610+ , onUpgradeNeeded: Just $ \_ _ index -> launchAff' do
611+ val <- IDBIndex.get index (IDBKeyRange.only "indexed_2")
612+ ((\r -> r.key) <$> val) `shouldEqual` (Just $ toKey "key2")
613+ }
614+ tearDown db
615+
616+ it "empty keyPath" do
617+ { db } <- setup
618+ { storeParams: IDBDatabase.defaultParameters
619+ , indexParams: IDBObjectStore.defaultParameters
620+ , keyPath: []
621+ , values:
622+ [ "object_1" :+: (Just $ toKey 1)
623+ , "object_2" :+: (Just $ toKey 2)
624+ , "object_3" :+: (Just $ toKey 3)
625+ ]
626+ , onUpgradeNeeded: Just $ \_ _ index -> launchAff' do
627+ val <- IDBIndex.get index (IDBKeyRange.only "object_3")
628+ val `shouldEqual` (Just "object_3")
629+ }
630+ tearDown db
631+
632+ it "index can be valid keys [date]" do
633+ date <- liftEffect $ toDateTime <$> now
634+ { db } <- setup
635+ { storeParams:
636+ { keyPath: [ "key" ]
637+ , autoIncrement: false
638+ }
639+ , indexParams: IDBObjectStore.defaultParameters
640+ , keyPath: [ "i" ]
641+ , values:
642+ [ { key: "date", i: (toKey date) } :+: Nothing
643+ ]
644+ , onUpgradeNeeded: Just $ \_ _ index -> launchAff' do
645+ val <- IDBIndex.get index (IDBKeyRange.only date)
646+ ((\r -> r.key) <$> val) `shouldEqual` (Just "date")
647+ }
648+ tearDown db
649+
650+ it "index can be valid keys [num]" do
651+ let num = 14
652+ { db } <- setup
653+ { storeParams:
654+ { keyPath: [ "key" ]
655+ , autoIncrement: false
656+ }
657+ , indexParams: IDBObjectStore.defaultParameters
658+ , keyPath: [ "i" ]
659+ , values:
660+ [ { key: "num", i: (toKey num) } :+: Nothing
661+ ]
662+ , onUpgradeNeeded: Just $ \_ _ index -> launchAff' do
663+ val <- IDBIndex.get index (IDBKeyRange.only num)
664+ ((\r -> r.key) <$> val) `shouldEqual` (Just "num")
665+ }
666+ tearDown db
667+
668+ it "index can be valid keys [array]" do
669+ let array = [ "patate", "autruche" ]
670+ { db } <- setup
671+ { storeParams:
672+ { keyPath: [ "key" ]
673+ , autoIncrement: false
674+ }
675+ , indexParams: IDBObjectStore.defaultParameters
676+ , keyPath: [ "i" ]
677+ , values:
678+ [ { key: "array", i: (toKey array) } :+: Nothing
679+ ]
680+ , onUpgradeNeeded: Just $ \_ _ index -> launchAff' do
681+ val <- IDBIndex.get index (IDBKeyRange.only array)
682+ ((\r -> r.key) <$> val) `shouldEqual` (Just "array")
683+ }
684+ tearDown db
685+
686+ it "openKeyCursor() - throw InvalidStateError on index deleted by aborted upgrade" do
687+ res <- attempt $ setup
688+ { storeParams: { keyPath: [ "key" ], autoIncrement: false }
689+ , indexParams: IDBObjectStore.defaultParameters
690+ , keyPath: [ "indexedProperty" ]
691+ , values:
692+ [ { key: 14, indexedProperty: "patate" } :+: Nothing
693+ ]
694+ , onUpgradeNeeded: Just $ \db tx index -> launchAff' do
695+ let
696+ cb =
697+ { onSuccess: const $ pure unit
698+ , onError: show >>> fail >>> launchAff'
699+ , onComplete: pure unit
700+ }
701+ IDBTransaction.onAbort tx (pure unit)
702+ IDBDatabase.onAbort db (pure unit)
703+ IDBTransaction.abort tx
704+ cursor <- attempt $ IDBIndex.openKeyCursor index Nothing Next cb
705+ case cursor of
706+ Right _ -> fail "expected InvalidStateError"
707+ Left err -> name err `shouldEqual` "InvalidStateError"
708+ }
709+ case res of
710+ Right _ -> fail "expected InvalidStateError"
711+ _ -> pure unit
712+
713+ it "openKeyCursor() - throw TransactionInactiveError on aborted transaction" do
714+ { db } <- setup
715+ { storeParams: { keyPath: [ "key" ], autoIncrement: false }
716+ , indexParams: IDBObjectStore.defaultParameters
717+ , keyPath: [ "indexedProperty" ]
718+ , values:
719+ [ { key: 14, indexedProperty: "patate" } :+: Nothing
720+ ]
721+ , onUpgradeNeeded: Nothing
722+ }
723+ let
724+ cb =
725+ { onSuccess: const $ pure unit
726+ , onError: show >>> fail >>> launchAff'
727+ , onComplete: pure unit
728+ }
729+ tx <- IDBDatabase.transaction db [ "store" ] ReadOnly
730+ store <- IDBTransaction.objectStore tx "store"
731+ index <- IDBObjectStore.index store "index"
732+ IDBTransaction.onAbort tx (pure unit)
733+ IDBTransaction.abort tx
734+ cursor <- attempt $ IDBIndex.openKeyCursor index Nothing Next cb
735+ case cursor of
736+ Right _ -> fail "expected TransactionInactiveError"
737+ Left err -> name err `shouldEqual` "TransactionInactiveError"
738+
739+ tearDown db
740+
741+ it "openKeyCursor() - throw InvalidStateError when the index is deleted" do
742+ { db } <- setup
743+ { storeParams: { keyPath: [ "key" ], autoIncrement: false }
744+ , indexParams: IDBObjectStore.defaultParameters
745+ , keyPath: [ "indexedProperty" ]
746+ , values:
747+ [ { key: 14, indexedProperty: "patate" } :+: Nothing
748+ ]
749+ , onUpgradeNeeded: Just $ \_ tx index -> launchAff' do
750+ let
751+ cb =
752+ { onSuccess: const $ pure unit
753+ , onError: show >>> fail >>> launchAff'
754+ , onComplete: pure unit
755+ }
756+ store <- IDBTransaction.objectStore tx "store"
757+ IDBObjectStore.deleteIndex store "index"
758+ cursor <- attempt $ IDBIndex.openKeyCursor index Nothing Next cb
759+ case cursor of
760+ Right _ -> fail "expected InvalidStateError"
761+ Left err -> name err `shouldEqual` "InvalidStateError"
762+ }
763+
764+ tearDown db
765+
766+ it "openCursor() - throw InvalidStateError on index deleted by aborted upgrade" do
767+ res <- attempt $ setup
768+ { storeParams: { keyPath: [ "key" ], autoIncrement: false }
769+ , indexParams: IDBObjectStore.defaultParameters
770+ , keyPath: [ "indexedProperty" ]
771+ , values:
772+ [ { key: 14, indexedProperty: "patate" } :+: Nothing
773+ ]
774+ , onUpgradeNeeded: Just $ \db tx index -> launchAff' do
775+ let
776+ cb =
777+ { onSuccess: const $ pure unit
778+ , onError: show >>> fail >>> launchAff'
779+ , onComplete: pure unit
780+ }
781+ IDBTransaction.onAbort tx (pure unit)
782+ IDBDatabase.onAbort db (pure unit)
783+ IDBTransaction.abort tx
784+ cursor <- attempt $ IDBIndex.openCursor index Nothing Next cb
785+ case cursor of
786+ Right _ -> fail "expected InvalidStateError"
787+ Left err -> name err `shouldEqual` "InvalidStateError"
788+ }
789+ case res of
790+ Right _ -> fail "expected InvalidStateError"
791+ _ -> pure unit
792+
793+ it "openCursor() - throw TransactionInactiveError on aborted transaction" do
794+ { db } <- setup
795+ { storeParams: { keyPath: [ "key" ], autoIncrement: false }
796+ , indexParams: IDBObjectStore.defaultParameters
797+ , keyPath: [ "indexedProperty" ]
798+ , values:
799+ [ { key: 14, indexedProperty: "patate" } :+: Nothing
800+ ]
801+ , onUpgradeNeeded: Nothing
802+ }
803+ let
804+ cb =
805+ { onSuccess: const $ pure unit
806+ , onError: show >>> fail >>> launchAff'
807+ , onComplete: pure unit
808+ }
809+ tx <- IDBDatabase.transaction db [ "store" ] ReadOnly
810+ store <- IDBTransaction.objectStore tx "store"
811+ index <- IDBObjectStore.index store "index"
812+ IDBTransaction.onAbort tx (pure unit)
813+ IDBTransaction.abort tx
814+ cursor <- attempt $ IDBIndex.openCursor index Nothing Next cb
815+ case cursor of
816+ Right _ -> fail "expected TransactionInactiveError"
817+ Left err -> name err `shouldEqual` "TransactionInactiveError"
818+
819+ tearDown db
820+
821+ it "openCursor() - throw InvalidStateError when the index is deleted" do
822+ { db } <- setup
823+ { storeParams: { keyPath: [ "key" ], autoIncrement: false }
824+ , indexParams: IDBObjectStore.defaultParameters
825+ , keyPath: [ "indexedProperty" ]
826+ , values:
827+ [ { key: 14, indexedProperty: "patate" } :+: Nothing
828+ ]
829+ , onUpgradeNeeded: Just $ \db tx index -> launchAff' do
830+ let
831+ cb =
832+ { onSuccess: const $ pure unit
833+ , onError: show >>> fail >>> launchAff'
834+ , onComplete: pure unit
835+ }
836+ store <- IDBTransaction.objectStore tx "store"
837+ IDBObjectStore.deleteIndex store "index"
838+ cursor <- attempt $ IDBIndex.openCursor index Nothing Next cb
839+ case cursor of
840+ Right _ -> fail "expected InvalidStateError"
841+ Left err -> name err `shouldEqual` "InvalidStateError"
842+ }
843+
844+ tearDown db
845+
846+ it "getKey() - multiEntry - adding keys" do
847+ { db } <- setup
848+ { storeParams: IDBDatabase.defaultParameters
849+ , indexParams: { unique: false, multiEntry: true }
850+ , keyPath: [ "name" ]
851+ , values:
852+ [ { name: [ "patate", "autruche" ] } :+: (Just $ toKey 1)
853+ , { name: [ "bob" ] } :+: (Just $ toKey 2)
854+ ]
855+ , onUpgradeNeeded: Just $ \_ _ index -> launchAff' do
856+ key <- IDBIndex.getKey index (IDBKeyRange.only "patate")
857+ key `shouldEqual` (Just $ toKey 1)
858+
859+ key' <- IDBIndex.getKey index (IDBKeyRange.only "autruche")
860+ key' `shouldEqual` (Just $ toKey 1)
861+
862+ key'' <- IDBIndex.getKey index (IDBKeyRange.only "bob")
863+ key'' `shouldEqual` (Just $ toKey 2)
864+ }
865+ tearDown db
866+
867+ it "get() - returns the record with the first key in the range" do
868+ { db } <- setup
869+ { storeParams: { keyPath: [ "key" ], autoIncrement: false }
870+ , indexParams: IDBObjectStore.defaultParameters
871+ , keyPath: [ "indexedProperty" ]
872+ , values:
873+ [ { key: 14, indexedProperty: "patate" } :+: Nothing
874+ , { key: 42, indexedProperty: "autruche" } :+: Nothing
875+ , { key: 1337, indexedProperty: "baguette" } :+: Nothing
876+ ]
877+ , onUpgradeNeeded: Nothing
878+ }
879+
880+ tx <- IDBDatabase.transaction db [ "store" ] ReadOnly
881+ store <- IDBTransaction.objectStore tx "store"
882+ index <- IDBObjectStore.index store "index"
883+ val <- IDBIndex.get index (IDBKeyRange.lowerBound "autruche" false)
884+ ((\r -> r.key) <$> val) `shouldEqual` (Just 42)
885+
886+ tearDown db
887+
888+ describe "IDBCursor" do
889+ let
890+ tearDown db = do
891+ IDBDatabase.close db
892+ _ <- IDBFactory.deleteDatabase (IDBDatabase.name db)
893+ pure unit
894+
895+ setup
896+ :: forall value
897+ . { storeParams :: { keyPath :: Array String, autoIncrement :: Boolean }
898+ , indexParams :: { unique :: Boolean, multiEntry :: Boolean }
899+ , values :: Array (Tuple value (Maybe Key))
900+ , keyPath :: Array String
901+ , onUpgradeNeeded :: Maybe (Database -> Transaction -> Index -> Effect Unit)
902+ }
903+ -> Aff { db :: Database, index :: Index, store :: ObjectStore }
904+ setup { storeParams, indexParams, values, keyPath, onUpgradeNeeded } = do
905+ let
906+ onUpgradeNeeded' var db tx _ = launchAff' do
907+ store <- IDBDatabase.createObjectStore db "store" storeParams
908+ _ <- traverse (uncurry (IDBObjectStore.add store)) values
909+ index <- IDBObjectStore.createIndex store "index" keyPath indexParams
910+ liftEffect $ maybe (pure unit) identity (onUpgradeNeeded <*> pure db <*> pure tx <*> pure index)
911+ AVar.put { db, index, store } var
912+
913+ var <- AVar.empty
914+ db <- IDBFactory.open "db" Nothing
915+ { onUpgradeNeeded: Just (onUpgradeNeeded' var)
916+ , onBlocked: Nothing
917+ }
918+ AVar.take var
919+
920+ it "continue() - iterate to the next record" do
921+ { db } <- setup
922+ { storeParams: IDBDatabase.defaultParameters
923+ , indexParams: IDBObjectStore.defaultParameters
924+ , keyPath: []
925+ , values:
926+ [ "cupcake" :+: (Just $ toKey 4)
927+ , "pancake" :+: (Just $ toKey 2)
928+ , "pie" :+: (Just $ toKey 1)
929+ , "pie" :+: (Just $ toKey 3)
930+ ]
931+ , onUpgradeNeeded: Nothing
932+ }
933+ let
934+ cb vdone vvals =
935+ { onComplete: launchAff' do
936+ AVar.put unit vdone
937+
938+ , onError: \error -> launchAff' do
939+ fail $ "unexpected error: " <> show error
940+
941+ , onSuccess: \cursor -> launchAff' do
942+ vals <- AVar.take vvals
943+ pure (IDBCursor.value cursor) >>= shouldEqual (maybe "" _.v $ head vals)
944+ IDBCursor.primaryKey cursor >>= shouldEqual (maybe (toKey 0) _.k $ head vals)
945+ AVar.put (drop 1 vals) vvals
946+ IDBCursor.continue cursor none
947+ }
948+ vdone <- AVar.empty
949+ vvals <- AVar.new
950+ [ { v: "pie", k: toKey 1 }
951+ , { v: "pancake", k: toKey 2 }
952+ , { v: "pie", k: toKey 3 }
953+ , { v: "cupcake", k: toKey 4 }
954+ ]
955+ tx <- IDBDatabase.transaction db [ "store" ] ReadOnly
956+ store <- IDBTransaction.objectStore tx "store"
957+ IDBObjectStore.openCursor store Nothing Next (cb vdone vvals)
958+ AVar.take vdone
959+ tearDown db
960+
961+ it "continue() - attempt to iterate in the wrong direction" do
962+ { db } <- setup
963+ { storeParams: IDBDatabase.defaultParameters
964+ , indexParams: IDBObjectStore.defaultParameters
965+ , keyPath: []
966+ , values:
967+ [ "cupcake" :+: (Just $ toKey 4)
968+ , "pancake" :+: (Just $ toKey 2)
969+ , "pie" :+: (Just $ toKey 1)
970+ , "pie" :+: (Just $ toKey 3)
971+ ]
972+ , onUpgradeNeeded: Nothing
973+ }
974+ let
975+ cb vdone =
976+ { onComplete: launchAff' do
977+ fail $ "shouldn't complete"
978+
979+ , onError: \error -> launchAff' do
980+ fail $ "unexpected error: " <> show error
981+
982+ , onSuccess: \cursor -> launchAff' do
983+ res <- attempt $ IDBCursor.continue cursor (Just 1)
984+ case res of
985+ Left err -> do
986+ name err `shouldEqual` "DataError"
987+ AVar.put unit vdone
988+ Right _ -> do
989+ fail "expected continue to fail"
990+ }
991+ vdone <- AVar.empty
992+ tx <- IDBDatabase.transaction db [ "store" ] ReadOnly
993+ store <- IDBTransaction.objectStore tx "store"
994+ IDBObjectStore.openCursor store Nothing Next (cb vdone)
995+ AVar.take vdone
996+ tearDown db
997+
998+ it "advance() - iterate cursor number of times specified by count" do
999+ { db } <- setup
1000+ { storeParams: { keyPath: [ "pKey" ], autoIncrement: false }
1001+ , indexParams: IDBObjectStore.defaultParameters
1002+ , keyPath: []
1003+ , values:
1004+ [ { pKey: "pkey_0", iKey: "ikey_0" } :+: Nothing
1005+ , { pKey: "pkey_1", iKey: "ikey_1" } :+: Nothing
1006+ , { pKey: "pkey_2", iKey: "ikey_2" } :+: Nothing
1007+ , { pKey: "pkey_3", iKey: "ikey_3" } :+: Nothing
1008+ ]
1009+ , onUpgradeNeeded: Nothing
1010+ }
1011+ let
1012+ cb vdone vjump =
1013+ { onComplete: launchAff' do
1014+ AVar.put unit vdone
1015+
1016+ , onError: \error -> launchAff' do
1017+ fail $ "unexpected error: " <> show error
1018+
1019+ , onSuccess: \cursor -> launchAff' do
1020+ jump <- AVar.take vjump
1021+ if jump then do
1022+ IDBCursor.advance cursor 3
1023+ else do
1024+ let value = IDBCursor.value cursor
1025+ value.pKey `shouldEqual` "pkey_3"
1026+ value.iKey `shouldEqual` "ikey_3"
1027+ IDBCursor.continue cursor none
1028+ AVar.put false vjump
1029+ }
1030+ vdone <- AVar.empty
1031+ vjump <- AVar.new true
1032+ tx <- IDBDatabase.transaction db [ "store" ] ReadOnly
1033+ store <- IDBTransaction.objectStore tx "store"
1034+ IDBObjectStore.openCursor store Nothing Next (cb vdone vjump)
1035+ AVar.take vdone
1036+ tearDown db
1037+
1038+ it "delete() - remove a record from the object store" do
1039+ { db } <- setup
1040+ { storeParams: { keyPath: [ "pKey" ], autoIncrement: false }
1041+ , indexParams: IDBObjectStore.defaultParameters
1042+ , keyPath: []
1043+ , values:
1044+ [ { pKey: "pkey_0", iKey: "ikey_0" } :+: Nothing
1045+ , { pKey: "pkey_1", iKey: "ikey_1" } :+: Nothing
1046+ , { pKey: "pkey_2", iKey: "ikey_2" } :+: Nothing
1047+ , { pKey: "pkey_3", iKey: "ikey_3" } :+: Nothing
1048+ ]
1049+ , onUpgradeNeeded: Nothing
1050+ }
1051+ let
1052+ cb vdone store =
1053+ { onComplete: launchAff' do
1054+ mval <- map _.pKey <$> IDBIndex.get store (IDBKeyRange.only "pkey_0")
1055+ mval `shouldEqual` (Nothing :: Maybe String)
1056+ AVar.put unit vdone
1057+
1058+ , onError: \error -> launchAff' do
1059+ fail $ "unexpected error: " <> show error
1060+
1061+ , onSuccess: \cursor -> launchAff' do
1062+ let value = IDBCursor.value cursor
1063+ value.pKey `shouldEqual` "pkey_0"
1064+ IDBCursor.delete cursor
1065+ IDBCursor.advance cursor 4
1066+ }
1067+ vdone <- AVar.empty
1068+ tx <- IDBDatabase.transaction db [ "store" ] ReadWrite
1069+ store <- IDBTransaction.objectStore tx "store"
1070+ IDBObjectStore.openCursor store Nothing Next (cb vdone store)
1071+ AVar.take vdone
1072+ tearDown db
1073+
1074+ it "update() - modify a record in the object store" do
1075+ { db } <- setup
1076+ { storeParams: { keyPath: [ "pKey" ], autoIncrement: false }
1077+ , indexParams: IDBObjectStore.defaultParameters
1078+ , keyPath: []
1079+ , values:
1080+ [ { pKey: "pkey_0", iKey: "ikey_0" } :+: Nothing
1081+ ]
1082+ , onUpgradeNeeded: Nothing
1083+ }
1084+ let
1085+ cb vdone store =
1086+ { onComplete: launchAff' do
1087+ mval <- map _.iKey <$> IDBIndex.get store (IDBKeyRange.only "pkey_0")
1088+ mval `shouldEqual` (Just "patate")
1089+ AVar.put unit vdone
1090+
1091+ , onError: \error -> launchAff' do
1092+ fail $ "unexpected error: " <> show error
1093+
1094+ , onSuccess: \cursor -> launchAff' do
1095+ let value = IDBCursor.value cursor
1096+ value.pKey `shouldEqual` "pkey_0"
1097+ key <- IDBCursor.update cursor { pKey: "pkey_0", iKey: "patate" }
1098+ key `shouldEqual` toKey "pkey_0"
1099+ IDBCursor.advance cursor 4
1100+ }
1101+ vdone <- AVar.empty
1102+ tx <- IDBDatabase.transaction db [ "store" ] ReadWrite
1103+ store <- IDBTransaction.objectStore tx "store"
1104+ IDBObjectStore.openCursor store Nothing Next (cb vdone store)
1105+ AVar.take vdone
1106+ tearDown db
1107+
1108+ it "update() - throw ReadOnlyError after update on ReadOnly transaction" do
1109+ { db } <- setup
1110+ { storeParams: { keyPath: [ "pKey" ], autoIncrement: false }
1111+ , indexParams: IDBObjectStore.defaultParameters
1112+ , keyPath: []
1113+ , values:
1114+ [ { pKey: "pkey_0", iKey: "ikey_0" } :+: Nothing
1115+ ]
1116+ , onUpgradeNeeded: Nothing
1117+ }
1118+ let
1119+ cb vdone =
1120+ { onComplete: launchAff' do
1121+ fail $ "shouldn't complete"
1122+
1123+ , onError: \error -> launchAff' do
1124+ fail $ "unexpected error: " <> show error
1125+
1126+ , onSuccess: \cursor -> launchAff' do
1127+ res <- attempt $ IDBCursor.update cursor "patate"
1128+ case res of
1129+ Left err -> do
1130+ name err `shouldEqual` "ReadOnlyError"
1131+ AVar.put unit vdone
1132+ Right _ ->
1133+ fail $ "expected ReadOnlyError"
1134+ }
1135+ vdone <- AVar.empty
1136+ tx <- IDBDatabase.transaction db [ "store" ] ReadOnly
1137+ store <- IDBTransaction.objectStore tx "store"
1138+ IDBObjectStore.openCursor store Nothing Next (cb vdone)
1139+ AVar.take vdone
1140+ tearDown db
9241141
925− db <- IDBFactory.open dbName Nothing
926− { onUpgradeNeeded : Just callback
927− , onBlocked : Nothing
928− }
929− tearDown dbName dbVersion db
yarn.locknew file+81
@@ -0,0 +1,81 @@
1+# THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY.
2+# yarn lockfile v1
3+
4+
5+base64-arraybuffer-es6@^0.7.0:
6+ version "0.7.0"
7+ resolved "https://registry.yarnpkg.com/base64-arraybuffer-es6/-/base64-arraybuffer-es6-0.7.0.tgz#dbe1e6c87b1bf1ca2875904461a7de40f21abc86"
8+ integrity sha512-ESyU/U1CFZDJUdr+neHRhNozeCv72Y7Vm0m1DCbjX3KBjT6eYocvAJlSk6+8+HkVwXlT1FNxhGW6q3UKAlCvvw==
9+
10+domexception@^1.0.1:
11+ version "1.0.1"
12+ resolved "https://registry.yarnpkg.com/domexception/-/domexception-1.0.1.tgz#937442644ca6a31261ef36e3ec677fe805582c90"
13+ integrity sha512-raigMkn7CJNNo6Ihro1fzG7wr3fHuYVytzquZKX5n0yizGsTcYgzdIUwj1X9pK0VvjeihV+XiclP+DjwbsSKug==
14+ dependencies:
15+ webidl-conversions "^4.0.2"
16+
17+fake-indexeddb@^4.0.1:
18+ version "4.0.1"
19+ resolved "https://registry.yarnpkg.com/fake-indexeddb/-/fake-indexeddb-4.0.1.tgz#09bb2468e21d0832b2177e894765fb109edac8fb"
20+ integrity sha512-hFRyPmvEZILYgdcLBxVdHLik4Tj3gDTu/g7s9ZDOiU3sTNiGx+vEu1ri/AMsFJUZ/1sdRbAVrEcKndh3sViBcA==
21+ dependencies:
22+ realistic-structured-clone "^3.0.0"
23+
24+lodash@^4.7.0:
25+ version "4.17.21"
26+ resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.21.tgz#679591c564c3bffaae8454cf0b3df370c3d6911c"
27+ integrity sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==
28+
29+punycode@^2.1.1:
30+ version "2.3.0"
31+ resolved "https://registry.yarnpkg.com/punycode/-/punycode-2.3.0.tgz#f67fa67c94da8f4d0cfff981aee4118064199b8f"
32+ integrity sha512-rRV+zQD8tVFys26lAGR9WUuS4iUAngJScM+ZRSKtvl5tKeZ2t5bvdNFdNHBW9FWR4guGHlgmsZ1G7BSm2wTbuA==
33+
34+realistic-structured-clone@^3.0.0:
35+ version "3.0.0"
36+ resolved "https://registry.yarnpkg.com/realistic-structured-clone/-/realistic-structured-clone-3.0.0.tgz#7b518049ce2dad41ac32b421cd297075b00e3e35"
37+ integrity sha512-rOjh4nuWkAqf9PWu6JVpOWD4ndI+JHfgiZeMmujYcPi+fvILUu7g6l26TC1K5aBIp34nV+jE1cDO75EKOfHC5Q==
38+ dependencies:
39+ domexception "^1.0.1"
40+ typeson "^6.1.0"
41+ typeson-registry "^1.0.0-alpha.20"
42+
43+tr46@^2.1.0:
44+ version "2.1.0"
45+ resolved "https://registry.yarnpkg.com/tr46/-/tr46-2.1.0.tgz#fa87aa81ca5d5941da8cbf1f9b749dc969a4e240"
46+ integrity sha512-15Ih7phfcdP5YxqiB+iDtLoaTz4Nd35+IiAv0kQ5FNKHzXgdWqPoTIqEDDJmXceQt4JZk6lVPT8lnDlPpGDppw==
47+ dependencies:
48+ punycode "^2.1.1"
49+
50+typeson-registry@^1.0.0-alpha.20:
51+ version "1.0.0-alpha.39"
52+ resolved "https://registry.yarnpkg.com/typeson-registry/-/typeson-registry-1.0.0-alpha.39.tgz#9e0f5aabd5eebfcffd65a796487541196f4b1211"
53+ integrity sha512-NeGDEquhw+yfwNhguLPcZ9Oj0fzbADiX4R0WxvoY8nGhy98IbzQy1sezjoEFWOywOboj/DWehI+/aUlRVrJnnw==
54+ dependencies:
55+ base64-arraybuffer-es6 "^0.7.0"
56+ typeson "^6.0.0"
57+ whatwg-url "^8.4.0"
58+
59+typeson@^6.0.0, typeson@^6.1.0:
60+ version "6.1.0"
61+ resolved "https://registry.yarnpkg.com/typeson/-/typeson-6.1.0.tgz#5b2a53705a5f58ff4d6f82f965917cabd0d7448b"
62+ integrity sha512-6FTtyGr8ldU0pfbvW/eOZrEtEkczHRUtduBnA90Jh9kMPCiFNnXIon3vF41N0S4tV1HHQt4Hk1j4srpESziCaA==
63+
64+webidl-conversions@^4.0.2:
65+ version "4.0.2"
66+ resolved "https://registry.yarnpkg.com/webidl-conversions/-/webidl-conversions-4.0.2.tgz#a855980b1f0b6b359ba1d5d9fb39ae941faa63ad"
67+ integrity sha512-YQ+BmxuTgd6UXZW3+ICGfyqRyHXVlD5GtQr5+qjiNW7bF0cqrzX500HVXPBOvgXb5YnzDd+h0zqyv61KUD7+Sg==
68+
69+webidl-conversions@^6.1.0:
70+ version "6.1.0"
71+ resolved "https://registry.yarnpkg.com/webidl-conversions/-/webidl-conversions-6.1.0.tgz#9111b4d7ea80acd40f5270d666621afa78b69514"
72+ integrity sha512-qBIvFLGiBpLjfwmYAaHPXsn+ho5xZnGvyGvsarywGNc8VyQJUMHJ8OBKGGrPER0okBeMDaan4mNBlgBROxuI8w==
73+
74+whatwg-url@^8.4.0:
75+ version "8.7.0"
76+ resolved "https://registry.yarnpkg.com/whatwg-url/-/whatwg-url-8.7.0.tgz#656a78e510ff8f3937bc0bcbe9f5c0ac35941b77"
77+ integrity sha512-gAojqb/m9Q8a5IV96E3fHJM70AzCkgt4uXYX2O7EmuyOnLrViCQlsEBmF9UQIu3/aeAIp2U17rtbpZWNntQqdg==
78+ dependencies:
79+ lodash "^4.7.0"
80+ tr46 "^2.1.0"
81+ webidl-conversions "^6.1.0"