gnunet-svn
[Top][All Lists]
Advanced

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

[taler-wallet-core] branch master updated (d384bd5c -> 987f22de)


From: gnunet
Subject: [taler-wallet-core] branch master updated (d384bd5c -> 987f22de)
Date: Tue, 16 Feb 2021 13:47:04 +0100

This is an automated email from the git hooks/post-receive script.

dold pushed a change to branch master
in repository wallet-core.

    from d384bd5c TWG, payments reversal testing.
     new d1f00aea get IDB tests to pass again, add new one
     new db59275b add more tests and fix various issues
     new 987f22de next batch of test cases and fixes

The 3 revisions listed above as "new" are entirely new to this
repository and will be described in separate emails.  The revisions
listed as "add" were already present in the repository and have only
been added to this reference.


Summary of changes:
 packages/idb-bridge/src/MemoryBackend.test.ts      |   2 +-
 packages/idb-bridge/src/MemoryBackend.ts           |  72 +--
 packages/idb-bridge/src/backend-interface.ts       |   1 -
 packages/idb-bridge/src/bridge-idb.ts              | 337 +++++++++-----
 .../abort-in-initial-upgradeneeded.test.ts         |  34 ++
 .../src/idb-wpt-ported/idbindex_get.test.ts        | 177 ++++++++
 .../src/idb-wpt-ported/idbobjectstore_add.test.ts  | 486 +++++++++++++++++++++
 .../idb-bridge/src/idb-wpt-ported/value.test.ts    |  37 +-
 .../idb-bridge/src/idb-wpt-ported/wptsupport.ts    |  20 +-
 packages/idb-bridge/src/util/FakeEventTarget.ts    |  15 +-
 packages/idb-bridge/src/util/cmp.ts                |   6 +-
 packages/idb-bridge/src/util/enforceRange.ts       |   4 +-
 packages/idb-bridge/src/util/extractKey.ts         |   6 +-
 packages/idb-bridge/src/util/getIndexKeys.ts       |   6 +-
 packages/idb-bridge/src/util/injectKey.ts          |  20 +-
 packages/idb-bridge/src/util/makeStoreKeyValue.ts  |   6 +-
 packages/idb-bridge/src/util/openPromise.ts        |   4 +-
 packages/idb-bridge/src/util/validateKeyPath.ts    |   7 +-
 packages/idb-bridge/src/util/valueToKey.ts         |   6 +-
 packages/idb-bridge/tsconfig.json                  |   1 +
 20 files changed, 1053 insertions(+), 194 deletions(-)
 create mode 100644 
packages/idb-bridge/src/idb-wpt-ported/abort-in-initial-upgradeneeded.test.ts
 create mode 100644 packages/idb-bridge/src/idb-wpt-ported/idbindex_get.test.ts
 create mode 100644 
packages/idb-bridge/src/idb-wpt-ported/idbobjectstore_add.test.ts

diff --git a/packages/idb-bridge/src/MemoryBackend.test.ts 
b/packages/idb-bridge/src/MemoryBackend.test.ts
index 281a72e3..8f988bb9 100644
--- a/packages/idb-bridge/src/MemoryBackend.test.ts
+++ b/packages/idb-bridge/src/MemoryBackend.test.ts
@@ -23,7 +23,7 @@ import {
   BridgeIDBRequest,
   BridgeIDBTransaction,
 } from "./bridge-idb";
-import MemoryBackend from "./MemoryBackend";
+import { MemoryBackend } from "./MemoryBackend";
 
 function promiseFromRequest(request: BridgeIDBRequest): Promise<any> {
   return new Promise((resolve, reject) => {
diff --git a/packages/idb-bridge/src/MemoryBackend.ts 
b/packages/idb-bridge/src/MemoryBackend.ts
index 2b4437bc..7107756a 100644
--- a/packages/idb-bridge/src/MemoryBackend.ts
+++ b/packages/idb-bridge/src/MemoryBackend.ts
@@ -27,11 +27,7 @@ import {
   StoreLevel,
   RecordStoreResponse,
 } from "./backend-interface";
-import {
-  structuredClone,
-  structuredEncapsulate,
-  structuredRevive,
-} from "./util/structuredClone";
+import { structuredClone, structuredRevive } from "./util/structuredClone";
 import {
   InvalidStateError,
   InvalidAccessError,
@@ -39,16 +35,11 @@ import {
   DataError,
 } from "./util/errors";
 import BTree, { ISortedMapF } from "./tree/b+tree";
-import compareKeys from "./util/cmp";
+import { compareKeys } from "./util/cmp";
 import { StoreKeyResult, makeStoreKeyValue } from "./util/makeStoreKeyValue";
-import getIndexKeys from "./util/getIndexKeys";
-import openPromise from "./util/openPromise";
-import {
-  IDBKeyPath,
-  IDBKeyRange,
-  IDBTransactionMode,
-  IDBValidKey,
-} from "./idbtypes";
+import { getIndexKeys } from "./util/getIndexKeys";
+import { openPromise } from "./util/openPromise";
+import { IDBKeyRange, IDBTransactionMode, IDBValidKey } from "./idbtypes";
 import { BridgeIDBKeyRange } from "./bridge-idb";
 
 type Key = IDBValidKey;
@@ -490,10 +481,10 @@ export class MemoryBackend implements Backend {
     objectStores: string[],
     mode: IDBTransactionMode,
   ): Promise<DatabaseTransaction> {
+    const transactionCookie = `tx-${this.transactionIdCounter++}`;
     if (this.enableTracing) {
-      console.log(`TRACING: beginTransaction`);
+      console.log(`TRACING: beginTransaction ${transactionCookie}`);
     }
-    const transactionCookie = `tx-${this.transactionIdCounter++}`;
     const myConn = this.connections[conn.connectionCookie];
     if (!myConn) {
       throw Error("connection not found");
@@ -558,7 +549,7 @@ export class MemoryBackend implements Backend {
 
   async close(conn: DatabaseConnection): Promise<void> {
     if (this.enableTracing) {
-      console.log(`TRACING: close`);
+      console.log(`TRACING: close (${conn.connectionCookie})`);
     }
     const myConn = this.connections[conn.connectionCookie];
     if (!myConn) {
@@ -642,7 +633,7 @@ export class MemoryBackend implements Backend {
     if (this.enableTracing) {
       console.log(`TRACING: deleteIndex(${indexName})`);
     }
-    const myConn = this.connections[btx.transactionCookie];
+    const myConn = this.connectionsByTransaction[btx.transactionCookie];
     if (!myConn) {
       throw Error("unknown connection");
     }
@@ -672,9 +663,11 @@ export class MemoryBackend implements Backend {
 
   deleteObjectStore(btx: DatabaseTransaction, name: string): void {
     if (this.enableTracing) {
-      console.log(`TRACING: deleteObjectStore(${name})`);
+      console.log(
+        `TRACING: deleteObjectStore(${name}) in ${btx.transactionCookie}`,
+      );
     }
-    const myConn = this.connections[btx.transactionCookie];
+    const myConn = this.connectionsByTransaction[btx.transactionCookie];
     if (!myConn) {
       throw Error("unknown connection");
     }
@@ -716,7 +709,7 @@ export class MemoryBackend implements Backend {
       console.log(`TRACING: renameObjectStore(?, ${oldName}, ${newName})`);
     }
 
-    const myConn = this.connections[btx.transactionCookie];
+    const myConn = this.connectionsByTransaction[btx.transactionCookie];
     if (!myConn) {
       throw Error("unknown connection");
     }
@@ -848,7 +841,15 @@ export class MemoryBackend implements Backend {
       objectStoreMapEntry.store.originalData;
 
     storeData.forEach((v, k) => {
-      this.insertIntoIndex(newIndex, k, v.value, indexProperties);
+      try {
+        this.insertIntoIndex(newIndex, k, v.value, indexProperties);
+      } catch (e) {
+        if (e instanceof DataError) {
+          // We don't propagate this error here.
+          return;
+        }
+        throw e;
+      }
     });
   }
 
@@ -1406,6 +1407,16 @@ export class MemoryBackend implements Backend {
       const autoIncrement =
         schema.objectStores[storeReq.objectStoreName].autoIncrement;
       const keyPath = schema.objectStores[storeReq.objectStoreName].keyPath;
+
+      if (
+        keyPath !== null &&
+        keyPath !== undefined &&
+        storeReq.key !== undefined
+      ) {
+        // If in-line keys are used, a key can't be explicitly specified.
+        throw new DataError();
+      }
+
       let storeKeyResult: StoreKeyResult;
       const revivedValue = structuredRevive(storeReq.value);
       try {
@@ -1440,7 +1451,7 @@ export class MemoryBackend implements Backend {
       const hasKey = modifiedData.has(key);
 
       if (hasKey && storeReq.storeLevel !== StoreLevel.AllowOverwrite) {
-        throw Error("refusing to overwrite");
+        throw new ConstraintError("refusing to overwrite");
       }
     }
 
@@ -1465,7 +1476,16 @@ export class MemoryBackend implements Backend {
       }
       const indexProperties =
         schema.objectStores[storeReq.objectStoreName].indexes[indexName];
-      this.insertIntoIndex(index, key, value, indexProperties);
+      try {
+        this.insertIntoIndex(index, key, value, indexProperties);
+      } catch (e) {
+        if (e instanceof DataError) {
+          // https://www.w3.org/TR/IndexedDB-2/#object-store-storage-operation
+          // Do nothing
+        } else {
+          throw e;
+        }
+      }
     }
 
     return { key };
@@ -1537,7 +1557,7 @@ export class MemoryBackend implements Backend {
     }
     const myConn = this.connectionsByTransaction[btx.transactionCookie];
     if (!myConn) {
-      throw Error("unknown connection");
+      throw Error("unknown transaction");
     }
     const db = this.databases[myConn.dbName];
     if (!db) {
@@ -1626,5 +1646,3 @@ export class MemoryBackend implements Backend {
     }
   }
 }
-
-export default MemoryBackend;
diff --git a/packages/idb-bridge/src/backend-interface.ts 
b/packages/idb-bridge/src/backend-interface.ts
index a9e3e960..14b5da64 100644
--- a/packages/idb-bridge/src/backend-interface.ts
+++ b/packages/idb-bridge/src/backend-interface.ts
@@ -17,7 +17,6 @@
 import { BridgeIDBDatabaseInfo, BridgeIDBKeyRange } from "./bridge-idb";
 import {
   IDBCursorDirection,
-  IDBKeyPath,
   IDBTransactionMode,
   IDBValidKey,
 } from "./idbtypes";
diff --git a/packages/idb-bridge/src/bridge-idb.ts 
b/packages/idb-bridge/src/bridge-idb.ts
index ce09fcb8..f518b476 100644
--- a/packages/idb-bridge/src/bridge-idb.ts
+++ b/packages/idb-bridge/src/bridge-idb.ts
@@ -42,8 +42,8 @@ import {
   IDBTransactionMode,
   IDBValidKey,
 } from "./idbtypes";
-import compareKeys from "./util/cmp";
-import enforceRange from "./util/enforceRange";
+import { compareKeys } from "./util/cmp";
+import { enforceRange } from "./util/enforceRange";
 import {
   AbortError,
   ConstraintError,
@@ -58,12 +58,17 @@ import {
 import { fakeDOMStringList } from "./util/fakeDOMStringList";
 import FakeEvent from "./util/FakeEvent";
 import FakeEventTarget from "./util/FakeEventTarget";
+import { makeStoreKeyValue } from "./util/makeStoreKeyValue";
 import { normalizeKeyPath } from "./util/normalizeKeyPath";
-import openPromise from "./util/openPromise";
+import { openPromise } from "./util/openPromise";
 import queueTask from "./util/queueTask";
-import { structuredClone, structuredEncapsulate, structuredRevive } from 
"./util/structuredClone";
-import validateKeyPath from "./util/validateKeyPath";
-import valueToKey from "./util/valueToKey";
+import {
+  structuredClone,
+  structuredEncapsulate,
+  structuredRevive,
+} from "./util/structuredClone";
+import { validateKeyPath } from "./util/validateKeyPath";
+import { valueToKey } from "./util/valueToKey";
 
 /** @public */
 export type CursorSource = BridgeIDBIndex | BridgeIDBObjectStore;
@@ -266,7 +271,7 @@ export class BridgeIDBCursor implements IDBCursor {
 
     const transaction = this._effectiveObjectStore._transaction;
 
-    if (transaction._state !== "active") {
+    if (!transaction._active) {
       throw new TransactionInactiveError();
     }
 
@@ -322,7 +327,7 @@ export class BridgeIDBCursor implements IDBCursor {
   public continue(key?: IDBValidKey) {
     const transaction = this._effectiveObjectStore._transaction;
 
-    if (transaction._state !== "active") {
+    if (!transaction._active) {
       throw new TransactionInactiveError();
     }
 
@@ -384,7 +389,7 @@ export class BridgeIDBCursor implements IDBCursor {
   public delete() {
     const transaction = this._effectiveObjectStore._transaction;
 
-    if (transaction._state !== "active") {
+    if (!transaction._active) {
       throw new TransactionInactiveError();
     }
 
@@ -455,7 +460,7 @@ export class BridgeIDBCursorWithValue extends 
BridgeIDBCursor {
  * Ensure that an active version change transaction is currently running.
  */
 const confirmActiveVersionchangeTransaction = (database: BridgeIDBDatabase) => 
{
-  if (!database._runningVersionchangeTransaction) {
+  if (!database._upgradeTransaction) {
     throw new InvalidStateError();
   }
 
@@ -467,11 +472,11 @@ const confirmActiveVersionchangeTransaction = (database: 
BridgeIDBDatabase) => {
   );
   const transaction = transactions[transactions.length - 1];
 
-  if (!transaction || transaction._state === "finished") {
+  if (!transaction || transaction._finished) {
     throw new InvalidStateError();
   }
 
-  if (transaction._state !== "active") {
+  if (!transaction._active) {
     throw new TransactionInactiveError();
   }
 
@@ -480,12 +485,13 @@ const confirmActiveVersionchangeTransaction = (database: 
BridgeIDBDatabase) => {
 
 // http://www.w3.org/TR/2015/REC-IndexedDB-20150108/#database-interface
 /** @public */
-export class BridgeIDBDatabase extends FakeEventTarget {
+export class BridgeIDBDatabase extends FakeEventTarget implements IDBDatabase {
   _closePending = false;
   _closed = false;
-  _runningVersionchangeTransaction = false;
   _transactions: Array<BridgeIDBTransaction> = [];
 
+  _upgradeTransaction: BridgeIDBTransaction | null = null;
+
   _backendConnection: DatabaseConnection;
   _backend: Backend;
 
@@ -499,8 +505,10 @@ export class BridgeIDBDatabase extends FakeEventTarget {
     return this._schema.databaseVersion;
   }
 
-  get objectStoreNames(): FakeDOMStringList {
-    return fakeDOMStringList(Object.keys(this._schema.objectStores)).sort();
+  get objectStoreNames(): DOMStringList {
+    return fakeDOMStringList(
+      Object.keys(this._schema.objectStores),
+    ).sort() as DOMStringList;
   }
 
   /**
@@ -509,9 +517,11 @@ export class BridgeIDBDatabase extends FakeEventTarget {
   _closeConnection() {
     this._closePending = true;
 
+    // Spec is unclear what "complete" means, we assume it's
+    // the same as "finished".
     const transactionsComplete = this._transactions.every(
       (transaction: BridgeIDBTransaction) => {
-        return transaction._state === "finished";
+        return transaction._finished;
       },
     );
 
@@ -525,6 +535,13 @@ export class BridgeIDBDatabase extends FakeEventTarget {
     }
   }
 
+  /**
+   * Refresh the schema by querying it from the backend.
+   */
+  _refreshSchema() {
+    this._schema = this._backend.getSchema(this._backendConnection);
+  }
+
   constructor(backend: Backend, backendConnection: DatabaseConnection) {
     super();
 
@@ -537,7 +554,10 @@ export class BridgeIDBDatabase extends FakeEventTarget {
   // http://w3c.github.io/IndexedDB/#dom-idbdatabase-createobjectstore
   public createObjectStore(
     name: string,
-    options: { autoIncrement?: boolean; keyPath?: IDBKeyPath } | null = {},
+    options: {
+      autoIncrement?: boolean;
+      keyPath?: null | IDBKeyPath | IDBKeyPath[];
+    } | null = {},
   ): BridgeIDBObjectStore {
     if (name === undefined) {
       throw new TypeError();
@@ -572,7 +592,7 @@ export class BridgeIDBDatabase extends FakeEventTarget {
     transaction._backend.createObjectStore(
       backendTx,
       name,
-      (keyPath !== null) ? normalizeKeyPath(keyPath) : null,
+      keyPath !== null ? normalizeKeyPath(keyPath) : null,
       autoIncrement,
     );
 
@@ -586,13 +606,24 @@ export class BridgeIDBDatabase extends FakeEventTarget {
       throw new TypeError();
     }
     const transaction = confirmActiveVersionchangeTransaction(this);
-    transaction._objectStoresCache.delete(name);
+    const backendTx = transaction._backendTransaction;
+    if (!backendTx) {
+      throw Error("invariant violated");
+    }
+    this._backend.deleteObjectStore(backendTx, name);
+    const os = transaction._objectStoresCache.get(name);
+    if (os) {
+      os._deleted = true;
+      transaction._objectStoresCache.delete(name);
+    }
+
   }
 
   public _internalTransaction(
     storeNames: string | string[],
     mode?: IDBTransactionMode,
     backendTransaction?: DatabaseTransaction,
+    openRequest?: BridgeIDBOpenDBRequest,
   ): BridgeIDBTransaction {
     mode = mode !== undefined ? mode : "readonly";
     if (
@@ -603,16 +634,7 @@ export class BridgeIDBDatabase extends FakeEventTarget {
       throw new TypeError("Invalid mode: " + mode);
     }
 
-    const hasActiveVersionchange = this._transactions.some(
-      (transaction: BridgeIDBTransaction) => {
-        return (
-          transaction._state === "active" &&
-          transaction.mode === "versionchange" &&
-          transaction._db === this
-        );
-      },
-    );
-    if (hasActiveVersionchange) {
+    if (this._upgradeTransaction) {
       throw new InvalidStateError();
     }
 
@@ -627,7 +649,7 @@ export class BridgeIDBDatabase extends FakeEventTarget {
       throw new InvalidAccessError();
     }
     for (const storeName of storeNames) {
-      if (this.objectStoreNames.indexOf(storeName) < 0) {
+      if (!this.objectStoreNames.contains(storeName)) {
         throw new NotFoundError(
           "No objectStore named " + storeName + " in this database",
         );
@@ -639,9 +661,12 @@ export class BridgeIDBDatabase extends FakeEventTarget {
       mode,
       this,
       backendTransaction,
+      openRequest,
     );
     this._transactions.push(tx);
     queueTask(() => tx._start());
+    // "When a transaction is created its active flag is initially set."
+    tx._active = true;
     return tx;
   }
 
@@ -809,20 +834,25 @@ export class BridgeIDBFactory {
           dbconn,
           requestedVersion,
         );
-        db._runningVersionchangeTransaction = true;
 
         const transaction = db._internalTransaction(
           [],
           "versionchange",
           backendTransaction,
+          request,
         );
+
+        db._upgradeTransaction = transaction;
+
         const event = new BridgeIDBVersionChangeEvent("upgradeneeded", {
           newVersion: version,
           oldVersion: existingVersion,
         });
 
-        request.result = db;
+        transaction._active = true;
+
         request.readyState = "done";
+        request.result = db;
         request.transaction = transaction;
         request.dispatchEvent(event);
 
@@ -832,15 +862,32 @@ export class BridgeIDBFactory {
 
         // We don't explicitly exit the versionchange transaction,
         // since this is already done by the BridgeIDBTransaction.
-        db._runningVersionchangeTransaction = false;
+        db._upgradeTransaction = null;
 
-        const event2 = new FakeEvent("success", {
-          bubbles: false,
-          cancelable: false,
-        });
-        event2.eventPath = [request];
+        // We re-use the same transaction (as per spec) here.
+        transaction._active = true;
+        if (transaction._aborted) {
+          request.result = undefined;
+          request.error = new AbortError();
+          request.readyState = "done";
+          const event2 = new FakeEvent("error", {
+            bubbles: false,
+            cancelable: false,
+          });
+          event2.eventPath = [request];
+          request.dispatchEvent(event2);
+        } else {
+          console.log(
+            `dispatching success event, _active=${transaction._active}`,
+          );
+          const event2 = new FakeEvent("success", {
+            bubbles: false,
+            cancelable: false,
+          });
+          event2.eventPath = [request];
 
-        request.dispatchEvent(event2);
+          request.dispatchEvent(event2);
+        }
       }
 
       this.connections.push(db);
@@ -871,7 +918,7 @@ const confirmActiveTransaction = (
     throw new InvalidStateError();
   }
 
-  if (index._objectStore._transaction._state !== "active") {
+  if (!index._objectStore._transaction._active) {
     throw new TransactionInactiveError();
   }
 
@@ -931,11 +978,11 @@ export class BridgeIDBIndex implements IDBIndex {
   set name(name: any) {
     const transaction = this._objectStore._transaction;
 
-    if (!transaction._db._runningVersionchangeTransaction) {
+    if (!transaction._db._upgradeTransaction) {
       throw new InvalidStateError();
     }
 
-    if (transaction._state !== "active") {
+    if (!transaction._active) {
       throw new TransactionInactiveError();
     }
 
@@ -1030,8 +1077,23 @@ export class BridgeIDBIndex implements IDBIndex {
     });
   }
 
-  public get(key: BridgeIDBKeyRange | IDBValidKey) {
+
+  private _confirmIndexExists() {
+    const storeSchema = this._schema.objectStores[this._objectStore._name];
+    if (!storeSchema) {
+      throw new InvalidStateError();
+    }
+    if (!storeSchema.indexes[this._name]) {
+      throw new InvalidStateError();
+    }
+  }
+
+  get(key: BridgeIDBKeyRange | IDBValidKey) {
     confirmActiveTransaction(this);
+    this._confirmIndexExists();
+    if (this._deleted) {
+      throw new InvalidStateError();
+    }
 
     if (!(key instanceof BridgeIDBKeyRange)) {
       key = BridgeIDBKeyRange._valueToKeyRange(key);
@@ -1282,7 +1344,7 @@ export class BridgeIDBObjectStore implements 
IDBObjectStore {
   get _indexNames(): FakeDOMStringList {
     return fakeDOMStringList(
       Object.keys(this._schema.objectStores[this._name].indexes),
-    ).sort()
+    ).sort();
   }
 
   get indexNames(): DOMStringList {
@@ -1330,7 +1392,7 @@ export class BridgeIDBObjectStore implements 
IDBObjectStore {
   set name(newName: any) {
     const transaction = this._transaction;
 
-    if (!transaction._db._runningVersionchangeTransaction) {
+    if (!transaction._db._upgradeTransaction) {
       throw new InvalidStateError();
     }
 
@@ -1350,6 +1412,7 @@ export class BridgeIDBObjectStore implements 
IDBObjectStore {
     );
   }
 
+
   public _store(value: any, key: IDBValidKey | undefined, overwrite: boolean) {
     if (BridgeIDBFactory.enableTracing) {
       console.log(`TRACE: IDBObjectStore._store`);
@@ -1357,6 +1420,22 @@ export class BridgeIDBObjectStore implements 
IDBObjectStore {
     if (this._transaction.mode === "readonly") {
       throw new ReadOnlyError();
     }
+
+    const { keyPath, autoIncrement } = this._schema.objectStores[this._name];
+
+    if (key !== null && key !== undefined) {
+      valueToKey(key);
+    }
+
+    // We only call this to synchronously verify the request.
+    makeStoreKeyValue(
+      value,
+      key,
+      1,
+      autoIncrement,
+      keyPath,
+    );
+
     const operation = async () => {
       const { btx } = this._confirmActiveTransaction();
       const result = await this._backend.storeRecord(btx, {
@@ -1377,6 +1456,9 @@ export class BridgeIDBObjectStore implements 
IDBObjectStore {
     if (arguments.length === 0) {
       throw new TypeError();
     }
+    if (this._deleted) {
+      throw new InvalidStateError("tried to call 'put' on a deleted object 
store");
+    }
     return this._store(value, key, true);
   }
 
@@ -1384,6 +1466,9 @@ export class BridgeIDBObjectStore implements 
IDBObjectStore {
     if (arguments.length === 0) {
       throw new TypeError();
     }
+    if (!this._schema.objectStores[this._name]) {
+      throw new InvalidStateError("object store does not exist");
+    }
     return this._store(value, key, false);
   }
 
@@ -1391,7 +1476,9 @@ export class BridgeIDBObjectStore implements 
IDBObjectStore {
     if (arguments.length === 0) {
       throw new TypeError();
     }
-
+    if (this._deleted) {
+      throw new InvalidStateError("tried to call 'delete' on a deleted object 
store");
+    }
     if (this._transaction.mode === "readonly") {
       throw new ReadOnlyError();
     }
@@ -1424,6 +1511,10 @@ export class BridgeIDBObjectStore implements 
IDBObjectStore {
       throw new TypeError();
     }
 
+    if (this._deleted) {
+      throw new InvalidStateError("tried to call 'delete' on a deleted object 
store");
+    }
+
     let keyRange: BridgeIDBKeyRange;
 
     if (key instanceof BridgeIDBKeyRange) {
@@ -1507,6 +1598,9 @@ export class BridgeIDBObjectStore implements 
IDBObjectStore {
     range?: IDBKeyRange | IDBValidKey,
     direction: IDBCursorDirection = "next",
   ) {
+    if (this._deleted) {
+      throw new InvalidStateError("tried to call 'openCursor' on a deleted 
object store");
+    }
     if (range === null) {
       range = undefined;
     }
@@ -1538,6 +1632,9 @@ export class BridgeIDBObjectStore implements 
IDBObjectStore {
     range?: BridgeIDBKeyRange | IDBValidKey,
     direction?: IDBCursorDirection,
   ) {
+    if (this._deleted) {
+      throw new InvalidStateError("tried to call 'openKeyCursor' on a deleted 
object store");
+    }
     if (range === null) {
       range = undefined;
     }
@@ -1581,7 +1678,7 @@ export class BridgeIDBObjectStore implements 
IDBObjectStore {
       throw new TypeError();
     }
 
-    if (!this._transaction._db._runningVersionchangeTransaction) {
+    if (!this._transaction._db._upgradeTransaction) {
       throw new InvalidStateError();
     }
 
@@ -1628,7 +1725,7 @@ export class BridgeIDBObjectStore implements 
IDBObjectStore {
       throw new TypeError();
     }
 
-    if (this._transaction._state === "finished") {
+    if (this._transaction._finished) {
       throw new InvalidStateError();
     }
 
@@ -1649,7 +1746,7 @@ export class BridgeIDBObjectStore implements 
IDBObjectStore {
       throw new InvalidStateError();
     }
 
-    if (!this._transaction._db._runningVersionchangeTransaction) {
+    if (!this._transaction._db._upgradeTransaction) {
       throw new InvalidStateError();
     }
 
@@ -1755,6 +1852,7 @@ export class BridgeIDBRequest extends FakeEventTarget 
implements IDBRequest {
       cancelable: true,
     });
     event.eventPath = [];
+
     this.dispatchEvent(event);
   }
 
@@ -1791,24 +1889,41 @@ export class BridgeIDBOpenDBRequest
 export class BridgeIDBTransaction
   extends FakeEventTarget
   implements IDBTransaction {
-  public _state: "active" | "inactive" | "committing" | "finished" = "active";
-  public _started = false;
-  public _objectStoresCache: Map<string, BridgeIDBObjectStore> = new Map();
+  _committed: boolean = false;
+  /**
+   * A transaction is active as long as new operations can be
+   * placed against it.
+   */
+  _active: boolean = false;
+  _started: boolean = false;
+  _aborted: boolean = false;
+  _objectStoresCache: Map<string, BridgeIDBObjectStore> = new Map();
+
+  /**
+   * https://www.w3.org/TR/IndexedDB-2/#transaction-lifetime-concept
+   *
+   * When a transaction is committed or aborted, it is said to be finished.
+   */
+  get _finished(): boolean {
+    return this._committed || this._aborted;
+  }
 
-  public _backendTransaction?: DatabaseTransaction;
+  _openRequest: BridgeIDBOpenDBRequest | null = null;
 
-  public _objectStoreNames: FakeDOMStringList;
+  _backendTransaction?: DatabaseTransaction;
+
+  _objectStoreNames: FakeDOMStringList;
   get objectStoreNames(): DOMStringList {
     return this._objectStoreNames as DOMStringList;
   }
-  public mode: IDBTransactionMode;
-  public _db: BridgeIDBDatabase;
+  mode: IDBTransactionMode;
+  _db: BridgeIDBDatabase;
 
   get db(): IDBDatabase {
-    return this.db;
+    return this._db;
   }
 
-  public _error: Error | null = null;
+  _error: Error | null = null;
 
   get error(): DOMException {
     return this._error as DOMException;
@@ -1823,7 +1938,7 @@ export class BridgeIDBTransaction
 
   public _scope: Set<string>;
   private _requests: Array<{
-    operation: () => void;
+    operation: () => Promise<void>;
     request: BridgeIDBRequest;
   }> = [];
 
@@ -1836,6 +1951,7 @@ export class BridgeIDBTransaction
     mode: IDBTransactionMode,
     db: BridgeIDBDatabase,
     backendTransaction?: DatabaseTransaction,
+    openRequest?: BridgeIDBOpenDBRequest,
   ) {
     super();
 
@@ -1850,11 +1966,17 @@ export class BridgeIDBTransaction
     this._objectStoreNames = fakeDOMStringList(Array.from(this._scope).sort());
 
     this._db._transactions.push(this);
+
+    this._openRequest = openRequest ?? null;
   }
 
   // 
http://www.w3.org/TR/2015/REC-IndexedDB-20150108/#dfn-steps-for-aborting-a-transaction
   async _abort(errName: string | null) {
-    this._state = "finished";
+    if (BridgeIDBFactory.enableTracing) {
+      console.log("TRACE: aborting transaction");
+    }
+
+    this._aborted = true;
 
     if (errName !== null) {
       const e = new Error();
@@ -1862,30 +1984,45 @@ export class BridgeIDBTransaction
       this._error = e;
     }
 
+    if (BridgeIDBFactory.enableTracing) {
+      console.log(`TRACE: aborting ${this._requests.length} requests`);
+    }
+
     // Should this directly remove from _requests?
     for (const { request } of this._requests) {
+      console.log("ready state:", request.readyState);
       if (request.readyState !== "done") {
-        request.readyState = "done"; // This will cancel execution of this 
request's operation
-        if (request._source) {
-          request.result = undefined;
-          request.error = new AbortError();
-
-          const event = new FakeEvent("error", {
-            bubbles: true,
-            cancelable: true,
-          });
-          event.eventPath = [this._db, this];
-          request.dispatchEvent(event);
+        // This will cancel execution of this request's operation
+        request.readyState = "done";
+        if (BridgeIDBFactory.enableTracing) {
+          console.log("dispatching error event");
         }
+        request.result = undefined;
+        request.error = new AbortError();
+
+        const event = new FakeEvent("error", {
+          bubbles: true,
+          cancelable: true,
+        });
+        event.eventPath = [request, this, this._db];
+        console.log("dispatching error event for request after abort");
+        request.dispatchEvent(event);
       }
     }
 
+    // ("abort a transaction", step 5.1)
+    if (this._openRequest) {
+      this._db._upgradeTransaction = null;
+    }
+
     // Only roll back if we actually executed the scheduled operations.
     const maybeBtx = this._backendTransaction;
     if (maybeBtx) {
       await this._backend.rollback(maybeBtx);
     }
 
+    this._db._refreshSchema();
+
     queueTask(() => {
       const event = new FakeEvent("abort", {
         bubbles: true,
@@ -1894,20 +2031,24 @@ export class BridgeIDBTransaction
       event.eventPath = [this._db];
       this.dispatchEvent(event);
     });
+
+    if (this._openRequest) {
+      this._openRequest.transaction = null;
+      this._openRequest.result = undefined;
+      this._openRequest.readyState = "pending";
+    }
   }
 
   public abort() {
-    if (this._state === "committing" || this._state === "finished") {
+    if (this._finished) {
       throw new InvalidStateError();
     }
-    this._state = "active";
-
     this._abort(null);
   }
 
   // http://w3c.github.io/IndexedDB/#dom-idbtransaction-objectstore
   public objectStore(name: string): BridgeIDBObjectStore {
-    if (this._state !== "active") {
+    if (!this._active) {
       throw new InvalidStateError();
     }
 
@@ -1925,7 +2066,7 @@ export class BridgeIDBTransaction
     const operation = obj.operation;
     let request = obj.hasOwnProperty("request") ? obj.request : null;
 
-    if (this._state !== "active") {
+    if (!this._active) {
       throw new TransactionInactiveError();
     }
 
@@ -2001,10 +2142,8 @@ export class BridgeIDBTransaction
           request.result = result;
           request.error = undefined;
 
-          // 
http://www.w3.org/TR/2015/REC-IndexedDB-20150108/#dfn-fire-a-success-event
-          if (this._state === "inactive") {
-            this._state = "active";
-          }
+          // https://www.w3.org/TR/IndexedDB-2/#fire-error-event
+          this._active = true;
           event = new FakeEvent("success", {
             bubbles: false,
             cancelable: false,
@@ -2014,9 +2153,13 @@ export class BridgeIDBTransaction
             event.eventPath = [request, this, this._db];
             request.dispatchEvent(event);
           } catch (err) {
-            if (this._state !== "committing") {
-              this._abort("AbortError");
+            if (BridgeIDBFactory.enableTracing) {
+              console.log(
+                "TRACING: caught error in transaction success event handler",
+              );
             }
+            this._abort("AbortError");
+            this._active = false;
             throw err;
           }
         } catch (err) {
@@ -2028,9 +2171,7 @@ export class BridgeIDBTransaction
           request.error = err;
 
           // 
http://www.w3.org/TR/2015/REC-IndexedDB-20150108/#dfn-fire-an-error-event
-          if (this._state === "inactive") {
-            this._state = "active";
-          }
+          this._active = true;
           event = new FakeEvent("error", {
             bubbles: true,
             cancelable: true,
@@ -2040,9 +2181,7 @@ export class BridgeIDBTransaction
             event.eventPath = [this._db, this];
             request.dispatchEvent(event);
           } catch (err) {
-            if (this._state !== "committing") {
-              this._abort("AbortError");
-            }
+            this._abort("AbortError");
             throw err;
           }
           if (!event.canceled) {
@@ -2061,17 +2200,13 @@ export class BridgeIDBTransaction
       return;
     }
 
-    if (this._state !== "finished" && this._state !== "committing") {
+    if (!this._finished && !this._committed) {
       if (BridgeIDBFactory.enableTracing) {
         console.log("finishing transaction");
       }
 
-      this._state = "committing";
-
       await this._backend.commit(this._backendTransaction);
-
-      this._state = "finished";
-
+      this._committed = true;
       if (!this._error) {
         if (BridgeIDBFactory.enableTracing) {
           console.log("dispatching 'complete' event on transaction");
@@ -2089,15 +2224,19 @@ export class BridgeIDBTransaction
 
       this._resolveWait();
     }
+    if (this._aborted) {
+      this._resolveWait();
+    }
   }
 
   public commit() {
-    if (this._state !== "active") {
+    // The current spec doesn't even have an explicit commit method.
+    // We still support it, effectively as a "no-operation" that
+    // prevents new operations from being scheduled.
+    if (!this._active) {
       throw new InvalidStateError();
     }
-
-    this._state = "committing";
-    // We now just wait for auto-commit ...
+    this._active = false;
   }
 
   public toString() {
diff --git 
a/packages/idb-bridge/src/idb-wpt-ported/abort-in-initial-upgradeneeded.test.ts 
b/packages/idb-bridge/src/idb-wpt-ported/abort-in-initial-upgradeneeded.test.ts
new file mode 100644
index 00000000..da9ed263
--- /dev/null
+++ 
b/packages/idb-bridge/src/idb-wpt-ported/abort-in-initial-upgradeneeded.test.ts
@@ -0,0 +1,34 @@
+import test from "ava";
+import { createdb } from "./wptsupport";
+
+test("WPT test abort-in-initial-upgradeneeded.htm", async (t) => {
+  await new Promise<void>((resolve, reject) => {
+    var db: any;
+    var open_rq = createdb(t, undefined, 2);
+
+    open_rq.onupgradeneeded = function (e) {
+      const tgt = e.target as any;
+      db = tgt.result;
+      t.assert(db.version === 2);
+      var transaction = tgt.transaction;
+      transaction.oncomplete = () => t.fail("unexpected transaction.complete");
+      transaction.onabort = function (e: any) {
+        console.log(`version: ${e.target.db.version}`);
+        t.deepEqual(e.target.db.version, 0);
+      };
+      db.onabort = function () {};
+      transaction.abort();
+    };
+
+    open_rq.onerror = function (e) {
+      const tgt = e.target as any;
+      t.deepEqual(open_rq, e.target);
+      t.deepEqual(tgt.result, undefined);
+      t.deepEqual(tgt.error.name, "AbortError");
+      console.log(`version (onerror): ${db.version}`);
+      t.deepEqual(db.version, 0);
+      t.deepEqual(open_rq.transaction, null);
+      resolve();
+    };
+  });
+});
diff --git a/packages/idb-bridge/src/idb-wpt-ported/idbindex_get.test.ts 
b/packages/idb-bridge/src/idb-wpt-ported/idbindex_get.test.ts
new file mode 100644
index 00000000..8a8cb312
--- /dev/null
+++ b/packages/idb-bridge/src/idb-wpt-ported/idbindex_get.test.ts
@@ -0,0 +1,177 @@
+import test from "ava";
+import { BridgeIDBKeyRange, BridgeIDBRequest } from "..";
+import { IDBDatabase } from "../idbtypes";
+import { createdb } from "./wptsupport";
+
+// IDBIndex.get() - returns the record
+test("WPT idbindex_get.htm", async (t) => {
+  await new Promise<void>((resolve, reject) => {
+    var db: any, index: any;
+    const record = { key: 1, indexedProperty: "data" };
+
+    var open_rq = createdb(t);
+    open_rq.onupgradeneeded = function (e: any) {
+      db = e.target.result;
+      var objStore = db.createObjectStore("store", { keyPath: "key" });
+      index = objStore.createIndex("index", "indexedProperty");
+
+      objStore.add(record);
+    };
+
+    open_rq.onsuccess = function (e) {
+      var rq = db
+        .transaction("store")
+        .objectStore("store")
+        .index("index")
+        .get(record.indexedProperty);
+
+      rq.onsuccess = function (e: any) {
+        t.deepEqual(e.target.result.key, record.key);
+        resolve();
+      };
+    };
+  });
+  t.pass();
+});
+
+// IDBIndex.get() - returns the record where the index contains duplicate 
values
+test("WPT idbindex_get2.htm", async (t) => {
+  await new Promise<void>((resolve, reject) => {
+    var db: any;
+    const records = [
+      { key: 1, indexedProperty: "data" },
+      { key: 2, indexedProperty: "data" },
+      { key: 3, indexedProperty: "data" },
+    ];
+
+    var open_rq = createdb(t);
+    open_rq.onupgradeneeded = function (e: any) {
+      db = e.target.result;
+      var objStore = db.createObjectStore("test", { keyPath: "key" });
+      objStore.createIndex("index", "indexedProperty");
+
+      for (var i = 0; i < records.length; i++) objStore.add(records[i]);
+    };
+
+    open_rq.onsuccess = function (e) {
+      var rq = db
+        .transaction("test")
+        .objectStore("test")
+        .index("index")
+        .get("data");
+
+      rq.onsuccess = function (e: any) {
+        t.deepEqual(e.target.result.key, records[0].key);
+        resolve();
+      };
+    };
+  });
+  t.pass();
+});
+
+// IDBIndex.get() - attempt to retrieve a record that doesn't exist
+test("WPT idbindex_get3.htm", async (t) => {
+  await new Promise<void>((resolve, reject) => {
+    var db: any;
+    var open_rq = createdb(t);
+    open_rq.onupgradeneeded = function (e: any) {
+      db = e.target.result;
+      var rq = db
+        .createObjectStore("test", { keyPath: "key" })
+        .createIndex("index", "indexedProperty")
+        .get(1);
+
+      rq.onsuccess = function (e: any) {
+        t.deepEqual(e.target.result, undefined);
+        resolve();
+      };
+    };
+  });
+  t.pass();
+});
+
+// IDBIndex.get() - returns the record with the first key in the range
+test("WPT idbindex_get4.htm", async (t) => {
+  await new Promise<void>((resolve, reject) => {
+    var db: any;
+
+    var open_rq = createdb(t);
+
+    open_rq.onupgradeneeded = function (e: any) {
+      db = e.target.result;
+      var store = db.createObjectStore("store", { keyPath: "key" });
+      store.createIndex("index", "indexedProperty");
+
+      for (var i = 0; i < 10; i++) {
+        store.add({ key: i, indexedProperty: "data" + i });
+      }
+    };
+
+    open_rq.onsuccess = function (e) {
+      var rq = db
+        .transaction("store")
+        .objectStore("store")
+        .index("index")
+        .get(BridgeIDBKeyRange.bound("data4", "data7"));
+
+      rq.onsuccess = function (e: any) {
+        t.deepEqual(e.target.result.key, 4);
+        t.deepEqual(e.target.result.indexedProperty, "data4");
+        setTimeout(function () {
+          resolve();
+        }, 4);
+      };
+    };
+  });
+  t.pass();
+});
+
+// IDBIndex.get() - throw DataError when using invalid key
+test("WPT idbindex_get5.htm", async (t) => {
+  await new Promise<void>((resolve, reject) => {
+    var db: any;
+
+    var open_rq = createdb(t);
+    open_rq.onupgradeneeded = function (e: any) {
+      db = e.target.result;
+
+      var index = db
+        .createObjectStore("test", { keyPath: "key" })
+        .createIndex("index", "indexedProperty");
+      t.throws(
+        function () {
+          index.get(NaN);
+        },
+        { name: "DataError" },
+      );
+      resolve();
+    };
+  });
+  t.pass();
+});
+
+// IDBIndex.get() - throw InvalidStateError when the index is deleted
+test("WPT idbindex_get6.htm", async (t) => {
+  await new Promise<void>((resolve, reject) => {
+    var db: any;
+
+    var open_rq = createdb(t);
+    open_rq.onupgradeneeded = function (e: any) {
+      db = e.target.result;
+      var store = db.createObjectStore("store", { keyPath: "key" });
+      var index = store.createIndex("index", "indexedProperty");
+
+      store.add({ key: 1, indexedProperty: "data" });
+      store.deleteIndex("index");
+
+      t.throws(
+        function () {
+          index.get("data");
+        },
+        { name: "InvalidStateError" },
+      );
+      resolve();
+    };
+  });
+  t.pass();
+});
diff --git a/packages/idb-bridge/src/idb-wpt-ported/idbobjectstore_add.test.ts 
b/packages/idb-bridge/src/idb-wpt-ported/idbobjectstore_add.test.ts
new file mode 100644
index 00000000..b8fdb5ac
--- /dev/null
+++ b/packages/idb-bridge/src/idb-wpt-ported/idbobjectstore_add.test.ts
@@ -0,0 +1,486 @@
+import test from "ava";
+import { BridgeIDBRequest } from "..";
+import { IDBDatabase } from "../idbtypes";
+import { createdb } from "./wptsupport";
+
+// IDBObjectStore.add() - add with an inline key
+test("WPT idbobjectstore_add.htm", async (t) => {
+  await new Promise<void>((resolve, reject) => {
+    var db: IDBDatabase | undefined;
+    const record = { key: 1, property: "data" };
+
+    var open_rq = createdb(t);
+    open_rq.onupgradeneeded = function (e: any) {
+      db = e.target.result;
+      var objStore = db!.createObjectStore("store", { keyPath: "key" });
+
+      objStore.add(record);
+    };
+
+    open_rq.onsuccess = function (e) {
+      var rq = db!.transaction("store").objectStore("store").get(record.key);
+
+      rq.onsuccess = function (e: any) {
+        t.deepEqual(e.target.result.property, record.property);
+        t.deepEqual(e.target.result.key, record.key);
+        resolve();
+      };
+    };
+  });
+  t.pass();
+});
+
+// IDBObjectStore.add() - add with an out-of-line key
+test("WPT idbobjectstore_add2.htm", async (t) => {
+  await new Promise<void>((resolve, reject) => {
+    var db: any;
+    const key = 1;
+    const record = { property: "data" };
+
+    var open_rq = createdb(t);
+    open_rq.onupgradeneeded = function (e: any) {
+      db = e.target.result;
+      var objStore = db.createObjectStore("store");
+
+      objStore.add(record, key);
+    };
+
+    open_rq.onsuccess = function (e) {
+      var rq = db.transaction("store").objectStore("store").get(key);
+
+      rq.onsuccess = function (e: any) {
+        t.deepEqual(e.target.result.property, record.property);
+        resolve();
+      };
+    };
+  });
+  t.pass();
+});
+
+// IDBObjectStore.add() - record with same key already exists
+test("WPT idbobjectstore_add3.htm", async (t) => {
+  await new Promise<void>((resolve, reject) => {
+    var db: any;
+    const record = { key: 1, property: "data" };
+
+    var open_rq = createdb(t);
+    open_rq.onupgradeneeded = function (e: any) {
+      db = e.target.result;
+      var objStore = db.createObjectStore("store", { keyPath: "key" });
+      objStore.add(record);
+
+      var rq = objStore.add(record);
+      rq.onsuccess = () => t.fail("success on adding duplicate record");
+
+      rq.onerror = function (e: any) {
+        t.deepEqual(e.target.error.name, "ConstraintError");
+        t.deepEqual(rq.error.name, "ConstraintError");
+        t.deepEqual(e.type, "error");
+        e.preventDefault();
+        e.stopPropagation();
+      };
+    };
+
+    // Defer done, giving rq.onsuccess a chance to run
+    open_rq.onsuccess = function (e) {
+      resolve();
+    };
+  });
+  t.pass();
+});
+
+// IDBObjectStore.add() - add where an index has unique:true specified
+test("WPT idbobjectstore_add4.htm", async (t) => {
+  await new Promise<void>((resolve, reject) => {
+    let db: any;
+    let record = { key: 1, property: "data" };
+
+    var open_rq = createdb(t);
+    open_rq.onupgradeneeded = function (e: any) {
+      db = e.target.result;
+      var objStore = db.createObjectStore("store", { autoIncrement: true });
+      objStore.createIndex("i1", "property", { unique: true });
+      objStore.add(record);
+
+      var rq = objStore.add(record);
+      rq.onsuccess = () => t.fail("success on adding duplicate indexed 
record");
+
+      rq.onerror = function (e: any) {
+        t.deepEqual(rq.error.name, "ConstraintError");
+        t.deepEqual(e.target.error.name, "ConstraintError");
+        t.deepEqual(e.type, "error");
+        e.preventDefault();
+        e.stopPropagation();
+      };
+    };
+
+    // Defer done, giving a spurious rq.onsuccess a chance to run
+    open_rq.onsuccess = function (e) {
+      resolve();
+    };
+  });
+  t.pass();
+});
+
+// IDBObjectStore.add() - object store's key path is an object attribute
+test("WPT idbobjectstore_add5.htm", async (t) => {
+  await new Promise<void>((resolve, reject) => {
+    var db: any;
+    const record = { test: { obj: { key: 1 } }, property: "data" };
+
+    var open_rq = createdb(t);
+    open_rq.onupgradeneeded = function (e: any) {
+      db = e.target.result;
+      var objStore = db.createObjectStore("store", { keyPath: "test.obj.key" 
});
+      objStore.add(record);
+    };
+
+    open_rq.onsuccess = function (e: any) {
+      var rq = db
+        .transaction("store")
+        .objectStore("store")
+        .get(record.test.obj.key);
+
+      rq.onsuccess = function (e: any) {
+        t.deepEqual(e.target.result.property, record.property);
+        resolve();
+      };
+    };
+  });
+  t.pass();
+});
+
+// IDBObjectStore.add() - autoIncrement and inline keys
+test("WPT idbobjectstore_add6.htm", async (t) => {
+  await new Promise<void>((resolve, reject) => {
+    var db: any;
+    const record = { property: "data" };
+    const expected_keys = [1, 2, 3, 4];
+
+    var open_rq = createdb(t);
+    open_rq.onupgradeneeded = function (e: any) {
+      db = e.target.result;
+      var objStore = db.createObjectStore("store", {
+        keyPath: "key",
+        autoIncrement: true,
+      });
+
+      objStore.add(record);
+      objStore.add(record);
+      objStore.add(record);
+      objStore.add(record);
+    };
+
+    open_rq.onsuccess = function (e) {
+      var actual_keys: any[] = [],
+        rq = db.transaction("store").objectStore("store").openCursor();
+
+      rq.onsuccess = function (e: any) {
+        var cursor = e.target.result;
+
+        if (cursor) {
+          actual_keys.push(cursor.value.key);
+          cursor.continue();
+        } else {
+          t.deepEqual(actual_keys, expected_keys);
+          resolve();
+        }
+      };
+    };
+  });
+  t.pass();
+});
+
+// IDBObjectStore.add() - autoIncrement and out-of-line keys
+test("WPT idbobjectstore_add7.htm", async (t) => {
+  await new Promise<void>((resolve, reject) => {
+    var db: any;
+    const record = { property: "data" };
+    const expected_keys = [1, 2, 3, 4];
+
+    var open_rq = createdb(t);
+    open_rq.onupgradeneeded = function (e: any) {
+      db = e.target.result;
+      var objStore = db.createObjectStore("store", { autoIncrement: true });
+
+      objStore.add(record);
+      objStore.add(record);
+      objStore.add(record);
+      objStore.add(record);
+    };
+
+    open_rq.onsuccess = function (e) {
+      var actual_keys: any[] = [],
+        rq = db.transaction("store").objectStore("store").openCursor();
+
+      rq.onsuccess = function (e: any) {
+        var cursor = e.target.result;
+
+        if (cursor) {
+          actual_keys.push(cursor.key);
+          cursor.continue();
+        } else {
+          t.deepEqual(actual_keys, expected_keys);
+          resolve();
+        }
+      };
+    };
+  });
+  t.pass();
+});
+
+// IDBObjectStore.add() - object store has autoIncrement:true and the key path
+// is an object attribute
+test("WPT idbobjectstore_add8.htm", async (t) => {
+  await new Promise<void>((resolve, reject) => {
+    var db: any;
+    const record = { property: "data" };
+    const expected_keys = [1, 2, 3, 4];
+
+    var open_rq = createdb(t);
+    open_rq.onupgradeneeded = function (e: any) {
+      db = e.target.result;
+      var objStore = db.createObjectStore("store", {
+        keyPath: "test.obj.key",
+        autoIncrement: true,
+      });
+
+      objStore.add(record);
+      objStore.add(record);
+      objStore.add(record);
+      objStore.add(record);
+    };
+
+    open_rq.onsuccess = function (e) {
+      var actual_keys: any[] = [],
+        rq = db.transaction("store").objectStore("store").openCursor();
+
+      rq.onsuccess = function (e: any) {
+        var cursor = e.target.result;
+
+        if (cursor) {
+          actual_keys.push(cursor.value.test.obj.key);
+          cursor.continue();
+        } else {
+          t.deepEqual(actual_keys, expected_keys);
+          resolve();
+        }
+      };
+    };
+  });
+  t.pass();
+});
+
+// IDBObjectStore.add() - Attempt to add a record that does not meet the
+// constraints of an object store's inline key requirements
+test("WPT idbobjectstore_add9.htm", async (t) => {
+  await new Promise<void>((resolve, reject) => {
+    const record = { key: 1, property: "data" };
+
+    var open_rq = createdb(t);
+    open_rq.onupgradeneeded = function (e: any) {
+      var rq,
+        db = e.target.result,
+        objStore = db.createObjectStore("store", { keyPath: "key" });
+
+      t.throws(
+        function () {
+          rq = objStore.add(record, 1);
+        },
+        { name: "DataError" },
+      );
+      t.deepEqual(rq, undefined);
+      resolve();
+    };
+  });
+  t.pass();
+});
+
+// IDBObjectStore.add() - Attempt to call 'add' without an key parameter when 
the
+// object store uses out-of-line keys.
+test("WPT idbobjectstore_add10.htm", async (t) => {
+  await new Promise<void>((resolve, reject) => {
+    var db: any;
+    const record = { property: "data" };
+
+    var open_rq = createdb(t);
+    open_rq.onupgradeneeded = function (e: any) {
+      db = e.target.result;
+
+      var rq,
+        objStore = db.createObjectStore("store");
+
+      t.throws(
+        function () {
+          rq = objStore.add(record);
+        },
+        { name: "DataError" },
+      );
+
+      t.deepEqual(rq, undefined);
+      resolve();
+    };
+  });
+  t.pass();
+});
+
+// IDBObjectStore.add() - Attempt to add a record where the record's key
+// does not meet the constraints of a valid key
+test("WPT idbobjectstore_add11.htm", async (t) => {
+  await new Promise<void>((resolve, reject) => {
+    var db: any;
+    const record = { key: { value: 1 }, property: "data" };
+
+    var open_rq = createdb(t);
+    open_rq.onupgradeneeded = function (e: any) {
+      db = e.target.result;
+
+      var rq,
+        objStore = db.createObjectStore("store", { keyPath: "key" });
+
+      t.throws(
+        function () {
+          rq = objStore.add(record);
+        },
+        { name: "DataError" },
+      );
+
+      t.deepEqual(rq, undefined);
+      resolve();
+    };
+  });
+  t.pass();
+});
+
+// IDBObjectStore.add() - Attempt to add a record where the
+// record's in-line key is not defined
+test("WPT idbobjectstore_add12.htm", async (t) => {
+  await new Promise<void>((resolve, reject) => {
+    var db: any;
+    const record = { property: "data" };
+
+    var open_rq = createdb(t);
+    open_rq.onupgradeneeded = function (e: any) {
+      db = e.target.result;
+
+      var rq,
+        objStore = db.createObjectStore("store", { keyPath: "key" });
+      t.throws(
+        function () {
+          rq = objStore.add(record);
+        },
+        { name: "DataError" },
+      );
+      t.deepEqual(rq, undefined);
+      resolve();
+    };
+  });
+  t.pass();
+});
+
+// IDBObjectStore.add() - Attempt to add a record where the out of line
+// key provided does not meet the constraints of a valid key
+test("WPT idbobjectstore_add13.htm", async (t) => {
+  await new Promise<void>((resolve, reject) => {
+    var db: any;
+    const record = { property: "data" };
+
+    var open_rq = createdb(t);
+    open_rq.onupgradeneeded = function (e: any) {
+      db = e.target.result;
+
+      var rq,
+        objStore = db.createObjectStore("store");
+
+      t.throws(
+        function () {
+          rq = objStore.add(record, { value: 1 });
+        },
+        { name: "DataError" },
+      );
+
+      t.deepEqual(rq, undefined);
+      resolve();
+    };
+  });
+  t.pass();
+});
+
+// IDBObjectStore.add() - Add a record where a value
+// being indexed does not meet the constraints of a valid key
+test("WPT idbobjectstore_add14.htm", async (t) => {
+  await new Promise<void>((resolve, reject) => {
+    var db: any;
+    const record = { key: 1, indexedProperty: { property: "data" } };
+
+    var open_rq = createdb(t);
+    open_rq.onupgradeneeded = function (e: any) {
+      db = e.target.result;
+
+      var rq,
+        objStore = db.createObjectStore("store", { keyPath: "key" });
+
+      objStore.createIndex("index", "indexedProperty");
+
+      rq = objStore.add(record);
+
+      t.assert(rq instanceof BridgeIDBRequest);
+      rq.onsuccess = function () {
+        resolve();
+      };
+    };
+  });
+  t.pass();
+});
+
+// IDBObjectStore.add() - If the transaction this IDBObjectStore belongs
+// to has its mode set to readonly, throw ReadOnlyError
+test("WPT idbobjectstore_add15.htm", async (t) => {
+  await new Promise<void>((resolve, reject) => {
+    var db: any;
+
+    var open_rq = createdb(t);
+    open_rq.onupgradeneeded = function (event: any) {
+      db = event.target.result;
+      db.createObjectStore("store", { keyPath: "pKey" });
+    };
+
+    open_rq.onsuccess = function (event) {
+      var txn = db.transaction("store");
+      var ostore = txn.objectStore("store");
+      t.throws(
+        function () {
+          ostore.add({ pKey: "primaryKey_0" });
+        },
+        { name: "ReadOnlyError" },
+      );
+      resolve();
+    };
+  });
+  t.pass();
+});
+
+// IDBObjectStore.add() - If the object store has been
+// deleted, the implementation must throw a DOMException of type 
InvalidStateError
+test("WPT idbobjectstore_add16.htm", async (t) => {
+  await new Promise<void>((resolve, reject) => {
+    var db: any;
+    let ostore: any;
+
+    var open_rq = createdb(t);
+    open_rq.onupgradeneeded = function (event: any) {
+      db = event.target.result;
+      ostore = db.createObjectStore("store", { keyPath: "pKey" });
+      db.deleteObjectStore("store");
+
+      t.throws(
+        function () {
+          ostore.add({ pKey: "primaryKey_0" });
+        },
+        { name: "InvalidStateError" },
+      );
+      resolve();
+    };
+  });
+  t.pass();
+});
diff --git a/packages/idb-bridge/src/idb-wpt-ported/value.test.ts 
b/packages/idb-bridge/src/idb-wpt-ported/value.test.ts
index c4a8315c..b1c2b3be 100644
--- a/packages/idb-bridge/src/idb-wpt-ported/value.test.ts
+++ b/packages/idb-bridge/src/idb-wpt-ported/value.test.ts
@@ -24,23 +24,24 @@ test.cb("WPT test value.htm, array", (t) => {
 });
 
 test.cb("WPT test value.htm, date", (t) => {
-    const value = new Date();
-    const _instanceof = Date;
-  
-    t.plan(1);
-  
-    createdb(t).onupgradeneeded = function (e: IDBVersionChangeEvent) {
-      (e.target as any).result.createObjectStore("store").add(value, 1);
-      (e.target as any).onsuccess = (e: any) => {
-        console.log("in first onsuccess");
-        e.target.result
-          .transaction("store")
-          .objectStore("store")
-          .get(1).onsuccess = (e: any) => {
-          t.assert(e.target.result instanceof _instanceof, "instanceof");
-          t.end();
-        };
+  const value = new Date();
+  const _instanceof = Date;
+
+  t.plan(1);
+
+  createdb(t).onupgradeneeded = function (e: IDBVersionChangeEvent) {
+    (e.target as any).result.createObjectStore("store").add(value, 1);
+    (e.target as any).onsuccess = (e: any) => {
+      console.log("in first onsuccess");
+      e.target.result
+        .transaction("store")
+        .objectStore("store")
+        .get(1).onsuccess = (e: any) => {
+          console.log("target", e.target);
+          console.log("result", e.target.result);
+        t.assert(e.target.result instanceof _instanceof, "instanceof");
+        t.end();
       };
     };
-  });
-  
\ No newline at end of file
+  };
+});
diff --git a/packages/idb-bridge/src/idb-wpt-ported/wptsupport.ts 
b/packages/idb-bridge/src/idb-wpt-ported/wptsupport.ts
index 10c11b7a..5716a7ae 100644
--- a/packages/idb-bridge/src/idb-wpt-ported/wptsupport.ts
+++ b/packages/idb-bridge/src/idb-wpt-ported/wptsupport.ts
@@ -1,11 +1,13 @@
 import { ExecutionContext } from "ava";
 import { BridgeIDBFactory } from "..";
 import { IDBOpenDBRequest } from "../idbtypes";
-import MemoryBackend from "../MemoryBackend";
-import compareKeys from "../util/cmp";
+import { MemoryBackend } from "../MemoryBackend";
+import { compareKeys } from "../util/cmp";
 
 BridgeIDBFactory.enableTracing = true;
-const idbFactory = new BridgeIDBFactory(new MemoryBackend());
+const backend = new MemoryBackend();
+backend.enableTracing = true;
+const idbFactory = new BridgeIDBFactory(backend);
 
 const self = {
   indexedDB: idbFactory,
@@ -23,8 +25,18 @@ export function createdb(
   return rq_open;
 }
 
-export function assert_key_equals(actual: any, expected: any, description?: 
string) {
+export function assert_key_equals(
+  actual: any,
+  expected: any,
+  description?: string,
+) {
   if (0 != compareKeys(actual, expected)) {
     throw Error("expected keys to be the same");
   }
 }
+
+export function assert_equals(actual: any, expected: any) {
+  if (actual !== expected) {
+    throw Error("assert_equals failed");
+  }
+}
diff --git a/packages/idb-bridge/src/util/FakeEventTarget.ts 
b/packages/idb-bridge/src/util/FakeEventTarget.ts
index d2f46c98..95489b4a 100644
--- a/packages/idb-bridge/src/util/FakeEventTarget.ts
+++ b/packages/idb-bridge/src/util/FakeEventTarget.ts
@@ -97,13 +97,14 @@ abstract class FakeEventTarget implements EventTarget {
   public readonly listeners: Listener[] = [];
 
   // These will be overridden in individual subclasses and made not readonly
-  public readonly onabort: EventListener | null | undefined;
-  public readonly onblocked: EventListener | null | undefined;
-  public readonly oncomplete: EventListener | null | undefined;
-  public readonly onerror: EventListener | null | undefined;
-  public readonly onsuccess: EventListener | null | undefined;
-  public readonly onupgradeneeded: EventListener | null | undefined;
-  public readonly onversionchange: EventListener | null | undefined;
+  public readonly onabort: EventListener | null = null;
+  public readonly onblocked: EventListener | null = null;
+  public readonly oncomplete: EventListener | null = null;
+  public readonly onerror: EventListener | null = null;
+  public readonly onsuccess: EventListener | null = null;
+  public readonly onclose: EventListener | null = null;
+  public readonly onupgradeneeded: EventListener | null = null;
+  public readonly onversionchange: EventListener | null = null;
 
   static enableTracing: boolean = false;
 
diff --git a/packages/idb-bridge/src/util/cmp.ts 
b/packages/idb-bridge/src/util/cmp.ts
index ddd43f2a..e7f26bf1 100644
--- a/packages/idb-bridge/src/util/cmp.ts
+++ b/packages/idb-bridge/src/util/cmp.ts
@@ -15,7 +15,7 @@
  */
 
 import { DataError } from "./errors";
-import valueToKey from "./valueToKey";
+import { valueToKey } from "./valueToKey";
 
 const getType = (x: any) => {
   if (typeof x === "number") {
@@ -38,7 +38,7 @@ const getType = (x: any) => {
 };
 
 // https://w3c.github.io/IndexedDB/#compare-two-keys
-const compareKeys = (first: any, second: any): -1 | 0 | 1 => {
+export const compareKeys = (first: any, second: any): -1 | 0 | 1 => {
   if (second === undefined) {
     throw new TypeError();
   }
@@ -104,5 +104,3 @@ const compareKeys = (first: any, second: any): -1 | 0 | 1 
=> {
 
   return first > second ? 1 : -1;
 };
-
-export default compareKeys;
diff --git a/packages/idb-bridge/src/util/enforceRange.ts 
b/packages/idb-bridge/src/util/enforceRange.ts
index 87e13579..ed463a33 100644
--- a/packages/idb-bridge/src/util/enforceRange.ts
+++ b/packages/idb-bridge/src/util/enforceRange.ts
@@ -17,7 +17,7 @@
 
 // https://heycam.github.io/webidl/#EnforceRange
 
-const enforceRange = (
+export const enforceRange = (
   num: number,
   type: "MAX_SAFE_INTEGER" | "unsigned long",
 ) => {
@@ -31,5 +31,3 @@ const enforceRange = (
     return Math.floor(num);
   }
 };
-
-export default enforceRange;
diff --git a/packages/idb-bridge/src/util/extractKey.ts 
b/packages/idb-bridge/src/util/extractKey.ts
index 7aa8bd17..09306dde 100644
--- a/packages/idb-bridge/src/util/extractKey.ts
+++ b/packages/idb-bridge/src/util/extractKey.ts
@@ -16,10 +16,10 @@
 */
 
 import { IDBKeyPath, IDBValidKey } from "../idbtypes";
-import valueToKey from "./valueToKey";
+import { valueToKey } from "./valueToKey";
 
 // 
http://www.w3.org/TR/2015/REC-IndexedDB-20150108/#dfn-steps-for-extracting-a-key-from-a-value-using-a-key-path
-const extractKey = (keyPath: IDBKeyPath | IDBKeyPath[], value: any) => {
+export const extractKey = (keyPath: IDBKeyPath | IDBKeyPath[], value: any) => {
   if (Array.isArray(keyPath)) {
     const result: IDBValidKey[] = [];
 
@@ -68,5 +68,3 @@ const extractKey = (keyPath: IDBKeyPath | IDBKeyPath[], 
value: any) => {
 
   return object;
 };
-
-export default extractKey;
diff --git a/packages/idb-bridge/src/util/getIndexKeys.ts 
b/packages/idb-bridge/src/util/getIndexKeys.ts
index 77b96b12..8515d79e 100644
--- a/packages/idb-bridge/src/util/getIndexKeys.ts
+++ b/packages/idb-bridge/src/util/getIndexKeys.ts
@@ -16,8 +16,8 @@
 */
 
 import { IDBKeyPath, IDBValidKey } from "../idbtypes";
-import extractKey from "./extractKey";
-import valueToKey from "./valueToKey";
+import { extractKey } from "./extractKey";
+import { valueToKey } from "./valueToKey";
 
 export function getIndexKeys(
   value: any,
@@ -43,5 +43,3 @@ export function getIndexKeys(
     throw Error(`unsupported key path: ${typeof keyPath}`);
   }
 }
-
-export default getIndexKeys;
diff --git a/packages/idb-bridge/src/util/injectKey.ts 
b/packages/idb-bridge/src/util/injectKey.ts
index 63c8deda..02acfaa4 100644
--- a/packages/idb-bridge/src/util/injectKey.ts
+++ b/packages/idb-bridge/src/util/injectKey.ts
@@ -30,6 +30,11 @@ export function injectKey(
     );
   }
 
+  const newValue = structuredClone(value);
+
+  // Position inside the new value where we'll place the key eventually.
+  let ptr = newValue;
+
   const identifiers = keyPath.split(".");
   if (identifiers.length === 0) {
     throw new Error("Assert: identifiers is not empty");
@@ -42,26 +47,23 @@ export function injectKey(
   }
 
   for (const identifier of identifiers) {
-    if (typeof value !== "object" && !Array.isArray(value)) {
-      return false;
+    if (typeof ptr !== "object" && !Array.isArray(ptr)) {
+      throw new Error("can't inject key");
     }
 
     const hop = value.hasOwnProperty(identifier);
     if (!hop) {
-      return true;
+      ptr[identifier] = {};
     }
 
-    value = value[identifier];
+    ptr = ptr[identifier];
   }
 
-  if (!(typeof value === "object" || Array.isArray(value))) {
+  if (!(typeof ptr === "object" || Array.isArray(ptr))) {
     throw new Error("can't inject key");
   }
 
-  const newValue = structuredClone(value);
-  newValue[lastIdentifier] = structuredClone(key);
+  ptr[lastIdentifier] = structuredClone(key);
 
   return newValue;
 }
-
-export default injectKey;
diff --git a/packages/idb-bridge/src/util/makeStoreKeyValue.ts 
b/packages/idb-bridge/src/util/makeStoreKeyValue.ts
index 2281e983..442a69ff 100644
--- a/packages/idb-bridge/src/util/makeStoreKeyValue.ts
+++ b/packages/idb-bridge/src/util/makeStoreKeyValue.ts
@@ -14,11 +14,11 @@
  permissions and limitations under the License.
 */
 
-import extractKey from "./extractKey";
+import { extractKey } from "./extractKey";
 import { DataError } from "./errors";
-import valueToKey from "./valueToKey";
+import { valueToKey } from "./valueToKey";
 import { structuredClone } from "./structuredClone";
-import injectKey from "./injectKey";
+import { injectKey } from "./injectKey";
 import { IDBKeyPath, IDBValidKey } from "../idbtypes";
 
 export interface StoreKeyResult {
diff --git a/packages/idb-bridge/src/util/openPromise.ts 
b/packages/idb-bridge/src/util/openPromise.ts
index 915060de..395d69e7 100644
--- a/packages/idb-bridge/src/util/openPromise.ts
+++ b/packages/idb-bridge/src/util/openPromise.ts
@@ -14,7 +14,7 @@
  permissions and limitations under the License.
 */
 
-function openPromise<T>(): {
+export function openPromise<T>(): {
   promise: Promise<T>;
   resolve: (v?: T | PromiseLike<T>) => void;
   reject: (err?: any) => void;
@@ -34,5 +34,3 @@ function openPromise<T>(): {
 
   return { promise, resolve, reject };
 }
-
-export default openPromise;
diff --git a/packages/idb-bridge/src/util/validateKeyPath.ts 
b/packages/idb-bridge/src/util/validateKeyPath.ts
index 8057172d..3bbe653b 100644
--- a/packages/idb-bridge/src/util/validateKeyPath.ts
+++ b/packages/idb-bridge/src/util/validateKeyPath.ts
@@ -17,7 +17,10 @@
 import { IDBKeyPath } from "../idbtypes";
 
 // http://www.w3.org/TR/2015/REC-IndexedDB-20150108/#dfn-valid-key-path
-const validateKeyPath = (keyPath: IDBKeyPath, parent?: "array" | "string") => {
+export const validateKeyPath = (
+  keyPath: IDBKeyPath | IDBKeyPath[],
+  parent?: "array" | "string",
+) => {
   // This doesn't make sense to me based on the spec, but it is needed to pass 
the W3C KeyPath tests (see same
   // comment in extractKey)
   let myKeyPath: IDBKeyPath | IDBKeyPath[] = keyPath;
@@ -74,5 +77,3 @@ const validateKeyPath = (keyPath: IDBKeyPath, parent?: 
"array" | "string") => {
 
   throw new SyntaxError();
 };
-
-export default validateKeyPath;
diff --git a/packages/idb-bridge/src/util/valueToKey.ts 
b/packages/idb-bridge/src/util/valueToKey.ts
index c3661f9a..c65604df 100644
--- a/packages/idb-bridge/src/util/valueToKey.ts
+++ b/packages/idb-bridge/src/util/valueToKey.ts
@@ -17,8 +17,8 @@
 import { IDBValidKey } from "..";
 import { DataError } from "./errors";
 
-// https://w3c.github.io/IndexedDB/#convert-a-value-to-a-input
-function valueToKey(
+// https://www.w3.org/TR/IndexedDB-2/#convert-a-value-to-a-key
+export function valueToKey(
   input: any,
   seen?: Set<object>,
 ): IDBValidKey | IDBValidKey[] {
@@ -68,5 +68,3 @@ function valueToKey(
     throw new DataError();
   }
 }
-
-export default valueToKey;
diff --git a/packages/idb-bridge/tsconfig.json 
b/packages/idb-bridge/tsconfig.json
index 99c5e6e3..4f730e1c 100644
--- a/packages/idb-bridge/tsconfig.json
+++ b/packages/idb-bridge/tsconfig.json
@@ -5,6 +5,7 @@
         "module": "ESNext",
         "moduleResolution": "node",
         "target": "ES6",
+        "allowJs": true,
         "noImplicitAny": true,
         "outDir": "lib",
         "declaration": true,

-- 
To stop receiving notification emails like this one, please contact
gnunet@gnunet.org.



reply via email to

[Prev in Thread] Current Thread [Next in Thread]