Skip to content

Commit bece5e6

Browse files
authored
Merge pull request #569 from scala-js/topic/idb
Improve precision of IDB API and add tests
2 parents db25ead + fec80ff commit bece5e6

File tree

11 files changed

+657
-285
lines changed

11 files changed

+657
-285
lines changed

api-reports/2_12.txt

+130-68
Large diffs are not rendered by default.

api-reports/2_13.txt

+130-68
Large diffs are not rendered by default.

src/main/scala/org/scalajs/dom/IDBTypes.scala

+243-115
Large diffs are not rendered by default.

src/main/scala/org/scalajs/dom/idb.scala

+18-7
Original file line numberDiff line numberDiff line change
@@ -2,21 +2,32 @@ package org.scalajs.dom
22

33
/** Short aliases of all the dom.IDBThing classes */
44
object idb {
5-
type Cursor = IDBCursor
6-
@inline def CursorDirection = IDBCursorDirection
7-
type CursorWithValue = IDBCursorWithValue
5+
6+
type CreateIndexOptions = IDBCreateIndexOptions
7+
type CreateObjectStoreOptions = IDBCreateObjectStoreOptions
8+
type Cursor[+Source] = IDBCursor[Source]
9+
type CursorReadOnly[+Source] = IDBCursorReadOnly[Source]
10+
type CursorWithValue[+Source] = IDBCursorWithValue[Source]
811
type Database = IDBDatabase
12+
type DatabaseInfo = IDBDatabaseInfo
13+
type Event[+TargetResult] = IDBEvent[TargetResult]
14+
type EventTarget[+Result] = IDBEventTarget[Result]
915
type Factory = IDBFactory
1016
type Index = IDBIndex
17+
type Key = IDBKey
18+
type KeyPath = IDBKeyPath
1119
type KeyRange = IDBKeyRange
12-
@inline def KeyRange = IDBKeyRange
1320
type ObjectStore = IDBObjectStore
14-
type OpenDBRequest = IDBOpenDBRequest
15-
type Request = IDBRequest
21+
type OpenDBRequest[TargetResult] = IDBOpenDBRequest[TargetResult]
22+
type Request[+Source, TargetResult] = IDBRequest[Source, TargetResult]
1623
type Transaction = IDBTransaction
17-
@inline def TransactionMode = IDBTransactionMode
24+
type Value = IDBValue
1825
type VersionChangeEvent = IDBVersionChangeEvent
1926

27+
@inline def CursorDirection = IDBCursorDirection
28+
@inline def KeyRange = IDBKeyRange
29+
@inline def TransactionMode = IDBTransactionMode
30+
2031
@deprecated(
2132
"Removed. This feature is no longer recommended. Though some browsers might still support it, it may have already been removed from the relevant web standards, may be in the process of being dropped, or may only be kept for compatibility purposes. Avoid using it, and update existing code if possible. See https://developer.mozilla.org/en-US/docs/Web/API/IDBEnvironment",
2233
"1.2.0")

src/main/scala/org/scalajs/dom/package.scala

+12
Original file line numberDiff line numberDiff line change
@@ -71,4 +71,16 @@ package object dom {
7171

7272
@deprecated("use NodeList[T] instead", "2.0.0")
7373
type NodeListOf[+T <: Node] = NodeList[T]
74+
75+
type IDBKey = Any
76+
77+
/** A valid key path can include one of the following: an empty string, a JavaScript identifier, or multiple
78+
* JavaScript identifiers separated by periods or an array containing any of those. It cannot include spaces.
79+
*
80+
* @see
81+
* https://developer.mozilla.org/en-US/docs/Web/API/IndexedDB_API/Basic_Terminology#key_path
82+
*/
83+
type IDBKeyPath = Any
84+
85+
type IDBValue = Any
7486
}

src/main/scala/org/scalajs/dom/raw.scala

+5-4
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ package org.scalajs.dom
99
import org.scalajs.dom
1010
import scala.scalajs.js
1111
import scala.scalajs.js.annotation._
12+
import scala.scalajs.js.|
1213

1314
@deprecated("All the members of raw.* have been moved to dom.*", "2.0.0")
1415
object raw {
@@ -479,10 +480,10 @@ object raw {
479480
type HTMLVideoElement = dom.HTMLVideoElement
480481

481482
@deprecated("use dom.IDBCursor instead", "2.0.0")
482-
type IDBCursor = dom.IDBCursor
483+
type IDBCursor = dom.IDBCursor[IDBObjectStore | IDBIndex]
483484

484485
@deprecated("use dom.IDBCursorWithValue instead", "2.0.0")
485-
type IDBCursorWithValue = dom.IDBCursorWithValue
486+
type IDBCursorWithValue = dom.IDBCursorWithValue[Any]
486487

487488
@deprecated("use dom.IDBDatabase instead", "2.0.0")
488489
type IDBDatabase = dom.IDBDatabase
@@ -506,10 +507,10 @@ object raw {
506507
type IDBObjectStore = dom.IDBObjectStore
507508

508509
@deprecated("use dom.IDBOpenDBRequest instead", "2.0.0")
509-
type IDBOpenDBRequest = dom.IDBOpenDBRequest
510+
type IDBOpenDBRequest = dom.IDBOpenDBRequest[Any]
510511

511512
@deprecated("use dom.IDBRequest instead", "2.0.0")
512-
type IDBRequest = dom.IDBRequest
513+
type IDBRequest = dom.IDBRequest[Any, Any]
513514

514515
@deprecated("use dom.IDBTransaction instead", "2.0.0")
515516
type IDBTransaction = dom.IDBTransaction

tests-shared/src/main/scala/org/scalajs/dom/tests/shared/AsyncTesting.scala

+7
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ package org.scalajs.dom.tests.shared
33
import org.junit.Assert
44
import scala.concurrent._
55
import scala.util._
6+
import scala.scalajs.js
67
import scala.scalajs.js.timers._
78

89
object AsyncTesting {
@@ -12,6 +13,12 @@ object AsyncTesting {
1213
implicit def global: ExecutionContext =
1314
ExecutionContext.global
1415

16+
def asyncPass: AsyncResult =
17+
Future.successful(Success(()))
18+
19+
def asyncWhenDefined[A](o: js.UndefOr[A])(f: A => AsyncResult): AsyncResult =
20+
o.fold(asyncPass)(f)
21+
1522
def async(run: => Future[Any]): AsyncResult = {
1623
val p = Promise[Try[Unit]]()
1724
val timeout = setTimeout(1200) {
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,82 @@
1+
package org.scalajs.dom.tests.shared
2+
3+
import java.util.UUID
4+
import org.junit.Assert._
5+
import org.scalajs.dom._
6+
import org.scalajs.dom.tests.shared.AsyncTesting._
7+
import scala.concurrent._
8+
import scala.scalajs.js
9+
import scala.util.Try
10+
11+
/** Scala.js version of https://gist.github.com/JamesMessinger/a0d6389a5d0e3a24814b */
12+
object IdbTest {
13+
14+
def apply(idb: js.UndefOr[IDBFactory]): AsyncResult =
15+
asyncWhenDefined(idb)(apply(_))
16+
17+
def apply(idb: IDBFactory): AsyncResult = async {
18+
open(idb).flatMap(use)
19+
}
20+
21+
private def open(idb: IDBFactory): Future[IDBDatabase] = {
22+
val p = Promise[IDBDatabase]()
23+
val r = idb.open(UUID.randomUUID().toString)
24+
25+
r.onerror = (e: Event) => fail(p, "idb.open failed: " + r.error)
26+
27+
r.onupgradeneeded = (e: IDBEvent[IDBDatabase]) => {
28+
val db = e.target.result
29+
val opts = new IDBCreateObjectStoreOptions { override val keyPath = "id" }
30+
val store = db.createObjectStore("MyObjectStore", opts)
31+
store.createIndex("NameIndex", js.Array("name.last", "name.first"))
32+
}
33+
34+
r.onsuccess = (e: IDBEvent[IDBDatabase]) => {
35+
assertSame(r.result, e.target.result)
36+
p.success(r.result)
37+
}
38+
39+
p.future
40+
}
41+
42+
private def fail(p: Promise[_], why: Any): Unit =
43+
p.failure(new RuntimeException("" + why))
44+
45+
private def use(db: IDBDatabase): Future[Unit] = {
46+
import js.Dynamic.{literal => obj}
47+
48+
val tx = db.transaction("MyObjectStore", IDBTransactionMode.readwrite)
49+
val store = tx.objectStore("MyObjectStore")
50+
val index = store.index("NameIndex")
51+
52+
// Add some data
53+
store.put(obj(id = 12345, name = obj(first = "John", last = "Doe"), age = 42))
54+
store.put(obj(id = 67890, name = obj(first = "Bob", last = "Smith"), age = 35))
55+
56+
// Query the data
57+
val getJohn = store.get(12345)
58+
val getBob = index.get(js.Array("Smith", "Bob"))
59+
60+
// Close the db when the transaction is done
61+
tx.oncomplete = (e: Event) => {
62+
db.close()
63+
}
64+
65+
def getFirstName(r: IDBRequest[_, IDBValue]): Future[String] = {
66+
val p = Promise[String]()
67+
r.onerror = (e: Event) => fail(p, e)
68+
r.onsuccess = (e: IDBEvent[IDBValue]) => {
69+
p.complete(Try(e.target.result.asInstanceOf[js.Dynamic].name.first.asInstanceOf[String]))
70+
}
71+
p.future
72+
}
73+
74+
for {
75+
john <- getFirstName(getJohn)
76+
bob <- getFirstName(getBob)
77+
} yield {
78+
assertEquals("John", john)
79+
assertEquals("Bob", bob)
80+
}
81+
}
82+
}
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,12 @@
11
package org.scalajs.dom.tests.shared
22

3-
import java.util.UUID
3+
import org.scalajs.dom.tests.shared.AsyncTesting._
44
import org.junit.Test
55

66
trait SharedTests {
7-
import SharedTests._
7+
8+
// ===================================================================================================================
9+
// Tests WITHOUT org.scalajs.dom._ in scope
810

911
// This tests that ops are always implicitly available, no imports required
1012
@Test final def NodeListOpsTest(): Unit =
@@ -18,7 +20,8 @@ trait SharedTests {
1820
.map(_.classList.mkString)
1921
}
2022

21-
// Don't move up
23+
// ===================================================================================================================
24+
// Tests WITH org.scalajs.dom._ in scope
2225
import org.scalajs.dom._
2326

2427
// https://github.com/scala-js/scala-js-dom/issues/411 - console doesn't work in web workers
@@ -30,17 +33,6 @@ trait SharedTests {
3033
val _ = crypto.HashAlgorithm
3134
}
3235

33-
@Test final def WindowIdbTest(): Unit =
34-
window.indexedDB.foreach(testIdb)
35-
36-
}
37-
38-
object SharedTests {
39-
import org.scalajs.dom._
40-
41-
def testIdb(idb: IDBFactory): Unit = {
42-
val open = idb.open(UUID.randomUUID().toString())
43-
open.onerror = (e: Event) => sys.error("idb open failed: " + e)
44-
// TODO: Test properly in a different PR
45-
}
36+
@Test final def WindowIdbTest(): AsyncResult =
37+
IdbTest(window.indexedDB)
4638
}

tests-webworker/src/main/scala/org/scalajs/dom/tests/webworker/Server.scala

+6-3
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
package org.scalajs.dom.tests.webworker
22

33
import org.scalajs.dom._
4+
import scala.concurrent.ExecutionContext.Implicits.global
45

56
object Server extends ServerResponses {
67
import Protocol._
@@ -13,9 +14,11 @@ object Server extends ServerResponses {
1314
val id = msgIn._1
1415
val cmdId = msgIn._2
1516
val cmd = WebWorkerCmd.byId(cmdId)
16-
val output = respond(cmd)
17-
val msgOut = Message(id, output)
18-
ww.postMessage(msgOut)
17+
respond(cmd).onComplete { t =>
18+
val output = t.getOrElse(t.failed.get.toString)
19+
val msgOut = Message(id, output)
20+
ww.postMessage(msgOut)
21+
}
1922
}
2023

2124
ww.postMessage(Message(ServerStarted, ""))

tests-webworker/src/main/scala/org/scalajs/dom/tests/webworker/WebWorkerTests.scala

+16-4
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
package org.scalajs.dom.tests.webworker
22

3+
import scala.language.implicitConversions
4+
import scala.concurrent.Future
35
import org.junit.Assert._
46
import org.junit.Test
57
import org.scalajs.dom.tests.shared.AsyncTesting._
@@ -46,10 +48,21 @@ trait WebWorkerTests {
4648
// =====================================================================================================================
4749
trait ServerResponses {
4850
import org.scalajs.dom._
49-
import org.scalajs.dom.tests.shared.SharedTests._
51+
import org.scalajs.dom.tests.shared._
5052
import org.scalajs.dom.DedicatedWorkerGlobalScope.self
5153

52-
final val respond: WebWorkerCmd => String = {
54+
private implicit def autoLift(s: => String): Future[String] =
55+
Future(s)
56+
57+
private implicit class AsyncOps(r: AsyncResult) {
58+
def andReturn(s: String): Future[String] =
59+
r.map { t =>
60+
t.get
61+
s
62+
}
63+
}
64+
65+
final val respond: WebWorkerCmd => Future[String] = {
5366

5467
case SayHello =>
5568
"hello"
@@ -60,7 +73,6 @@ trait ServerResponses {
6073

6174
case TestIdb =>
6275
assertTrue(self.indexedDB.isDefined)
63-
testIdb(self.indexedDB.get)
64-
"ok"
76+
IdbTest(self.indexedDB.get).andReturn("ok")
6577
}
6678
}

0 commit comments

Comments
 (0)