aboutsummaryrefslogtreecommitdiff
path: root/packages/idb-bridge/src
diff options
context:
space:
mode:
authorFlorian Dold <florian.dold@gmail.com>2019-06-21 19:18:36 +0200
committerFlorian Dold <florian.dold@gmail.com>2019-06-21 19:18:36 +0200
commita4e4125cca8644703d7cff527a39c1a5a9842eba (patch)
treefb4de931ea0db1f314fcf6850806989a40c9e76e /packages/idb-bridge/src
parent2ee9431f1ba5bf67546bbf85758a01991c40673f (diff)
downloadwallet-core-a4e4125cca8644703d7cff527a39c1a5a9842eba.tar.xz
idb: tests working
Diffstat (limited to 'packages/idb-bridge/src')
-rw-r--r--packages/idb-bridge/src/BridgeIDBCursor.ts4
-rw-r--r--packages/idb-bridge/src/BridgeIDBDatabase.ts5
-rw-r--r--packages/idb-bridge/src/BridgeIDBFactory.ts11
-rw-r--r--packages/idb-bridge/src/BridgeIDBObjectStore.ts4
-rw-r--r--packages/idb-bridge/src/BridgeIDBTransaction.ts71
-rw-r--r--packages/idb-bridge/src/MemoryBackend.test.ts123
-rw-r--r--packages/idb-bridge/src/MemoryBackend.ts497
-rw-r--r--packages/idb-bridge/src/backend-interface.ts31
-rw-r--r--packages/idb-bridge/src/util/FakeEventTarget.ts262
-rw-r--r--packages/idb-bridge/src/util/getIndexKeys.test.ts24
-rw-r--r--packages/idb-bridge/src/util/getIndexKeys.ts28
-rw-r--r--packages/idb-bridge/src/util/makeStoreKeyValue.test.ts42
-rw-r--r--packages/idb-bridge/src/util/makeStoreKeyValue.ts24
13 files changed, 921 insertions, 205 deletions
diff --git a/packages/idb-bridge/src/BridgeIDBCursor.ts b/packages/idb-bridge/src/BridgeIDBCursor.ts
index 0120bb7d5..8321e2a1d 100644
--- a/packages/idb-bridge/src/BridgeIDBCursor.ts
+++ b/packages/idb-bridge/src/BridgeIDBCursor.ts
@@ -18,7 +18,7 @@
import BridgeIDBKeyRange from "./BridgeIDBKeyRange";
import BridgeIDBObjectStore from "./BridgeIDBObjectStore";
import BridgeIDBRequest from "./BridgeIDBRequest";
-import cmp from "./util/cmp";
+import compareKeys from "./util/cmp";
import {
DataError,
InvalidAccessError,
@@ -233,7 +233,7 @@ class BridgeIDBCursor {
if (key !== undefined) {
key = valueToKey(key);
- const cmpResult = cmp(key, this._position);
+ const cmpResult = compareKeys(key, this._position);
if (
(cmpResult <= 0 &&
diff --git a/packages/idb-bridge/src/BridgeIDBDatabase.ts b/packages/idb-bridge/src/BridgeIDBDatabase.ts
index cff2fd6e3..bc2e8acca 100644
--- a/packages/idb-bridge/src/BridgeIDBDatabase.ts
+++ b/packages/idb-bridge/src/BridgeIDBDatabase.ts
@@ -144,7 +144,7 @@ class BridgeIDBDatabase extends FakeEventTarget {
validateKeyPath(keyPath);
}
- if (!Object.keys(this._schema.objectStores).includes(name)) {
+ if (Object.keys(this._schema.objectStores).includes(name)) {
throw new ConstraintError();
}
@@ -156,7 +156,7 @@ class BridgeIDBDatabase extends FakeEventTarget {
this._schema = this._backend.getSchema(this._backendConnection);
- return transaction.objectStore("name");
+ return transaction.objectStore(name);
}
public deleteObjectStore(name: string): void {
@@ -214,6 +214,7 @@ class BridgeIDBDatabase extends FakeEventTarget {
const tx = new BridgeIDBTransaction(storeNames, mode, this, backendTransaction);
this._transactions.push(tx);
+ queueTask(() => tx._start());
return tx;
}
diff --git a/packages/idb-bridge/src/BridgeIDBFactory.ts b/packages/idb-bridge/src/BridgeIDBFactory.ts
index c2747238e..ad02be461 100644
--- a/packages/idb-bridge/src/BridgeIDBFactory.ts
+++ b/packages/idb-bridge/src/BridgeIDBFactory.ts
@@ -31,6 +31,7 @@ class BridgeIDBFactory {
public cmp = compareKeys;
private backend: Backend;
private connections: BridgeIDBDatabase[] = [];
+ static enableTracing: boolean = true;
public constructor(backend: Backend) {
this.backend = backend;
@@ -165,7 +166,17 @@ class BridgeIDBFactory {
await transaction._waitDone();
+ // We don't explicitly exit the versionchange transaction,
+ // since this is already done by the BridgeIDBTransaction.
db._runningVersionchangeTransaction = false;
+
+ const event2 = new FakeEvent("success", {
+ bubbles: false,
+ cancelable: false,
+ });
+ event2.eventPath = [request];
+
+ request.dispatchEvent(event2);
}
this.connections.push(db);
diff --git a/packages/idb-bridge/src/BridgeIDBObjectStore.ts b/packages/idb-bridge/src/BridgeIDBObjectStore.ts
index 197f06d86..eca4c1981 100644
--- a/packages/idb-bridge/src/BridgeIDBObjectStore.ts
+++ b/packages/idb-bridge/src/BridgeIDBObjectStore.ts
@@ -47,6 +47,7 @@ import {
RecordGetRequest,
ResultLevel,
} from "./backend-interface";
+import BridgeIDBFactory from "./BridgeIDBFactory";
// http://www.w3.org/TR/2015/REC-IndexedDB-20150108/#object-store
@@ -124,6 +125,9 @@ class BridgeIDBObjectStore {
}
public _store(value: Value, key: Key | undefined, overwrite: boolean) {
+ if (BridgeIDBFactory.enableTracing) {
+ console.log(`TRACE: IDBObjectStore._store`);
+ }
if (this.transaction.mode === "readonly") {
throw new ReadOnlyError();
}
diff --git a/packages/idb-bridge/src/BridgeIDBTransaction.ts b/packages/idb-bridge/src/BridgeIDBTransaction.ts
index a7057e297..09f324dfa 100644
--- a/packages/idb-bridge/src/BridgeIDBTransaction.ts
+++ b/packages/idb-bridge/src/BridgeIDBTransaction.ts
@@ -20,6 +20,7 @@ import queueTask from "./util/queueTask";
import openPromise from "./util/openPromise";
import { DatabaseTransaction, Backend } from "./backend-interface";
import { array } from "prop-types";
+import BridgeIDBFactory from "./BridgeIDBFactory";
// http://www.w3.org/TR/2015/REC-IndexedDB-20150108/#transaction
class BridgeIDBTransaction extends FakeEventTarget {
@@ -113,7 +114,6 @@ class BridgeIDBTransaction extends FakeEventTarget {
event.eventPath = [this.db];
this.dispatchEvent(event);
});
-
}
public abort() {
@@ -169,9 +169,17 @@ class BridgeIDBTransaction extends FakeEventTarget {
return request;
}
+ /**
+ * Actually execute the scheduled work for this transaction.
+ */
public async _start() {
+ if (BridgeIDBFactory.enableTracing) {
+ console.log(`TRACE: IDBTransaction._start, ${this._requests.length} queued`);
+ }
this._started = true;
+ console.log("beginning transaction");
+
if (!this._backendTransaction) {
this._backendTransaction = await this._backend.beginTransaction(
this.db._backendConnection,
@@ -180,6 +188,8 @@ class BridgeIDBTransaction extends FakeEventTarget {
);
}
+ console.log("beginTransaction completed");
+
// Remove from request queue - cursor ones will be added back if necessary by cursor.continue and such
let operation;
let request;
@@ -198,9 +208,10 @@ class BridgeIDBTransaction extends FakeEventTarget {
if (!request.source) {
// Special requests like indexes that just need to run some code, with error handling already built into
// operation
+ console.log("running operation without source");
await operation();
} else {
- let defaultAction;
+ console.log("running operation with source");
let event;
try {
const result = await operation();
@@ -216,7 +227,20 @@ class BridgeIDBTransaction extends FakeEventTarget {
bubbles: false,
cancelable: false,
});
+
+ try {
+ event.eventPath = [request, this, this.db];
+ request.dispatchEvent(event);
+ } catch (err) {
+ if (this._state !== "committing") {
+ this._abort("AbortError");
+ }
+ throw err;
+ }
} catch (err) {
+ if (BridgeIDBFactory.enableTracing) {
+ console.log("TRACING: error during operation: ", err);
+ }
request.readyState = "done";
request.result = undefined;
request.error = err;
@@ -230,23 +254,17 @@ class BridgeIDBTransaction extends FakeEventTarget {
cancelable: true,
});
- defaultAction = this._abort.bind(this, err.name);
- }
-
- try {
- event.eventPath = [this.db, this];
- request.dispatchEvent(event);
- } catch (err) {
- if (this._state !== "committing") {
- this._abort("AbortError");
+ try {
+ event.eventPath = [this.db, this];
+ request.dispatchEvent(event);
+ } catch (err) {
+ if (this._state !== "committing") {
+ this._abort("AbortError");
+ }
+ throw err;
}
- throw err;
- }
-
- // Default action of event
- if (!event.canceled) {
- if (defaultAction) {
- defaultAction();
+ if (!event.canceled) {
+ this._abort(err.name);
}
}
}
@@ -261,13 +279,23 @@ class BridgeIDBTransaction extends FakeEventTarget {
return;
}
- // Check if transaction complete event needs to be fired
- if (this._state !== "finished") {
- // Either aborted or committed already
+ if (this._state !== "finished" && this._state !== "committing") {
+ if (BridgeIDBFactory.enableTracing) {
+ console.log("finishing transaction");
+ }
+
+ this._state = "committing";
+
+ await this._backend.commit(this._backendTransaction);
+
this._state = "finished";
if (!this.error) {
+ if (BridgeIDBFactory.enableTracing) {
+ console.log("dispatching 'complete' event");
+ }
const event = new FakeEvent("complete");
+ event.eventPath = [this, this.db];
this.dispatchEvent(event);
}
@@ -287,6 +315,7 @@ class BridgeIDBTransaction extends FakeEventTarget {
}
this._state = "committing";
+ // We now just wait for auto-commit ...
}
public toString() {
diff --git a/packages/idb-bridge/src/MemoryBackend.test.ts b/packages/idb-bridge/src/MemoryBackend.test.ts
index 3d2d0fbc9..213bff750 100644
--- a/packages/idb-bridge/src/MemoryBackend.test.ts
+++ b/packages/idb-bridge/src/MemoryBackend.test.ts
@@ -1,31 +1,126 @@
-import test from 'ava';
-import MemoryBackend from './MemoryBackend';
-import BridgeIDBFactory from './BridgeIDBFactory';
+import test from "ava";
+import MemoryBackend from "./MemoryBackend";
+import BridgeIDBFactory from "./BridgeIDBFactory";
+import BridgeIDBRequest from "./BridgeIDBRequest";
+import BridgeIDBDatabase from "./BridgeIDBDatabase";
+import BridgeIDBTransaction from "./BridgeIDBTransaction";
-test.cb("basics", (t) => {
+function promiseFromRequest(request: BridgeIDBRequest): Promise<any> {
+ return new Promise((resolve, reject) => {
+ request.onsuccess = () => {
+ resolve(request.result);
+ };
+ request.onerror = () => {
+ reject(request.error);
+ };
+ });
+}
+
+function promiseFromTransaction(transaction: BridgeIDBTransaction): Promise<any> {
+ return new Promise((resolve, reject) => {
+ console.log("attaching event handlers");
+ transaction.oncomplete = () => {
+ console.log("oncomplete was called from promise")
+ resolve();
+ };
+ transaction.onerror = () => {
+ reject();
+ };
+ });
+}
+
+test("Spec: Example 1 Part 1", async t => {
const backend = new MemoryBackend();
const idb = new BridgeIDBFactory(backend);
const request = idb.open("library");
request.onupgradeneeded = () => {
const db = request.result;
- const store = db.createObjectStore("books", {keyPath: "isbn"});
- const titleIndex = store.createIndex("by_title", "title", {unique: true});
+ const store = db.createObjectStore("books", { keyPath: "isbn" });
+ const titleIndex = store.createIndex("by_title", "title", { unique: true });
const authorIndex = store.createIndex("by_author", "author");
-
+
// Populate with initial data.
- store.put({title: "Quarry Memories", author: "Fred", isbn: 123456});
- store.put({title: "Water Buffaloes", author: "Fred", isbn: 234567});
- store.put({title: "Bedrock Nights", author: "Barney", isbn: 345678});
+ store.put({ title: "Quarry Memories", author: "Fred", isbn: 123456 });
+ store.put({ title: "Water Buffaloes", author: "Fred", isbn: 234567 });
+ store.put({ title: "Bedrock Nights", author: "Barney", isbn: 345678 });
+ };
+
+ await promiseFromRequest(request);
+ t.pass();
+});
+
+
+test("Spec: Example 1 Part 2", async t => {
+ const backend = new MemoryBackend();
+ const idb = new BridgeIDBFactory(backend);
+
+ const request = idb.open("library");
+ request.onupgradeneeded = () => {
+ const db = request.result;
+ const store = db.createObjectStore("books", { keyPath: "isbn" });
+ const titleIndex = store.createIndex("by_title", "title", { unique: true });
+ const authorIndex = store.createIndex("by_author", "author");
+ };
+
+ const db: BridgeIDBDatabase = await promiseFromRequest(request);
+
+ t.is(db.name, "library");
+
+ const tx = db.transaction("books", "readwrite");
+ tx.oncomplete = () => {
+ console.log("oncomplete called")
};
- request.onsuccess = () => {
- t.end();
+ const store = tx.objectStore("books");
+
+ store.put({title: "Quarry Memories", author: "Fred", isbn: 123456});
+ store.put({title: "Water Buffaloes", author: "Fred", isbn: 234567});
+ store.put({title: "Bedrock Nights", author: "Barney", isbn: 345678});
+
+ await promiseFromTransaction(tx);
+
+ t.pass();
+});
+
+
+test("Spec: Example 1 Part 3", async t => {
+ const backend = new MemoryBackend();
+ const idb = new BridgeIDBFactory(backend);
+
+ const request = idb.open("library");
+ request.onupgradeneeded = () => {
+ const db = request.result;
+ const store = db.createObjectStore("books", { keyPath: "isbn" });
+ const titleIndex = store.createIndex("by_title", "title", { unique: true });
+ const authorIndex = store.createIndex("by_author", "author");
};
- request.onerror = () => {
- t.fail();
+ const db: BridgeIDBDatabase = await promiseFromRequest(request);
+
+ t.is(db.name, "library");
+
+ const tx = db.transaction("books", "readwrite");
+ tx.oncomplete = () => {
+ console.log("oncomplete called")
};
+ const store = tx.objectStore("books");
+
+ store.put({title: "Quarry Memories", author: "Fred", isbn: 123456});
+ store.put({title: "Water Buffaloes", author: "Fred", isbn: 234567});
+ store.put({title: "Bedrock Nights", author: "Barney", isbn: 345678});
+
+ await promiseFromTransaction(tx);
+
+ const tx2 = db.transaction("books", "readonly");
+ const store2 = tx2.objectStore("books");
+ var index2 = store2.index("by_title");
+ const request2 = index2.get("Bedrock Nights");
+ const result2: any = await promiseFromRequest(request2);
+
+ t.is(result2.author, "Barney");
+
+ t.pass();
});
diff --git a/packages/idb-bridge/src/MemoryBackend.ts b/packages/idb-bridge/src/MemoryBackend.ts
index 2d4b8ab93..831974882 100644
--- a/packages/idb-bridge/src/MemoryBackend.ts
+++ b/packages/idb-bridge/src/MemoryBackend.ts
@@ -5,14 +5,26 @@ import {
Schema,
RecordStoreRequest,
IndexProperties,
+ RecordGetRequest,
+ RecordGetResponse,
+ ResultLevel,
} from "./backend-interface";
import structuredClone from "./util/structuredClone";
-import { InvalidStateError, InvalidAccessError } from "./util/errors";
+import {
+ InvalidStateError,
+ InvalidAccessError,
+ ConstraintError,
+} from "./util/errors";
import BTree, { ISortedMap, ISortedMapF } from "./tree/b+tree";
import BridgeIDBFactory from "./BridgeIDBFactory";
import compareKeys from "./util/cmp";
import extractKey from "./util/extractKey";
import { Key, Value, KeyPath } from "./util/types";
+import { StoreKeyResult, makeStoreKeyValue } from "./util/makeStoreKeyValue";
+import getIndexKeys from "./util/getIndexKeys";
+import openPromise from "./util/openPromise";
+import BridgeIDBKeyRange from "./BridgeIDBKeyRange";
+import { resetWarningCache } from "prop-types";
enum TransactionLevel {
Disconnected = 0,
@@ -25,8 +37,8 @@ enum TransactionLevel {
interface ObjectStore {
originalName: string;
modifiedName: string | undefined;
- originalData: ISortedMapF;
- modifiedData: ISortedMapF | undefined;
+ originalData: ISortedMapF<Key, ObjectStoreRecord>;
+ modifiedData: ISortedMapF<Key, ObjectStoreRecord> | undefined;
deleted: boolean;
originalKeyGenerator: number;
modifiedKeyGenerator: number | undefined;
@@ -35,8 +47,8 @@ interface ObjectStore {
interface Index {
originalName: string;
modifiedName: string | undefined;
- originalData: ISortedMapF;
- modifiedData: ISortedMapF | undefined;
+ originalData: ISortedMapF<Key, IndexRecord>;
+ modifiedData: ISortedMapF<Key, IndexRecord> | undefined;
deleted: boolean;
}
@@ -74,28 +86,77 @@ interface Connection {
indexMap: { [currentName: string]: Index };
}
-class AsyncCondition {
- wait(): Promise<void> {
- throw Error("not implemented");
- }
+interface IndexRecord {
+ indexKey: Key;
+ primaryKeys: Key[];
+}
- trigger(): void {}
+interface ObjectStoreRecord {
+ primaryKey: Key;
+ value: Value;
}
+class AsyncCondition {
+ _waitPromise: Promise<void>;
+ _resolveWaitPromise: () => void;
+ constructor() {
+ const op = openPromise<void>();
+ this._waitPromise = op.promise;
+ this._resolveWaitPromise = op.resolve;
+ }
+ wait(): Promise<void> {
+ return this._waitPromise;
+ }
+ trigger(): void {
+ this._resolveWaitPromise();
+ const op = openPromise<void>();
+ this._waitPromise = op.promise;
+ this._resolveWaitPromise = op.resolve;
+ }
+}
-function insertIntoIndex(
- index: Index,
- value: Value,
- indexProperties: IndexProperties,
+function nextStoreKey<T>(
+ forward: boolean,
+ data: ISortedMapF<Key, ObjectStoreRecord>,
+ k: Key | undefined,
) {
- if (indexProperties.multiEntry) {
+ if (k === undefined || k === null) {
+ return undefined;
+ }
+ const res = forward ? data.nextHigherPair(k) : data.nextLowerPair(k);
+ if (!res) {
+ return undefined;
+ }
+ return res[1].primaryKey;
+}
- } else {
- const key = extractKey(value, indexProperties.keyPath);
+
+function furthestKey(forward: boolean, key1: Key | undefined, key2: Key | undefined) {
+ if (key1 === undefined) {
+ return key2;
+ }
+ if (key2 === undefined) {
+ return key1;
+ }
+ const cmpResult = compareKeys(key1, key2);
+ if (cmpResult === 0) {
+ // Same result
+ return key1;
+ }
+ if (forward && cmpResult === 1) {
+ return key1;
+ }
+ if (forward && cmpResult === -1) {
+ return key2;
+ }
+ if (!forward && cmpResult === 1) {
+ return key2;
+ }
+ if (!forward && cmpResult === -1) {
+ return key1;
}
- throw Error("not implemented");
}
/**
@@ -129,7 +190,12 @@ export class MemoryBackend implements Backend {
*/
transactionDoneCond: AsyncCondition = new AsyncCondition();
+ enableTracing: boolean = true;
+
async getDatabases(): Promise<{ name: string; version: number }[]> {
+ if (this.enableTracing) {
+ console.log("TRACING: getDatabase");
+ }
const dbList = [];
for (const name in this.databases) {
dbList.push({
@@ -141,6 +207,9 @@ export class MemoryBackend implements Backend {
}
async deleteDatabase(tx: DatabaseTransaction, name: string): Promise<void> {
+ if (this.enableTracing) {
+ console.log("TRACING: deleteDatabase");
+ }
const myConn = this.connectionsByTransaction[tx.transactionCookie];
if (!myConn) {
throw Error("no connection associated with transaction");
@@ -162,6 +231,9 @@ export class MemoryBackend implements Backend {
}
async connectDatabase(name: string): Promise<DatabaseConnection> {
+ if (this.enableTracing) {
+ console.log(`TRACING: connectDatabase(${name})`);
+ }
const connectionId = this.connectionIdCounter++;
const connectionCookie = `connection-${connectionId}`;
@@ -193,6 +265,16 @@ export class MemoryBackend implements Backend {
database.txLevel = TransactionLevel.Connected;
database.connectionCookie = connectionCookie;
+ const myConn: Connection = {
+ dbName: name,
+ deleted: false,
+ indexMap: Object.assign({}, database.committedIndexes),
+ objectStoreMap: Object.assign({}, database.committedObjectStores),
+ modifiedSchema: structuredClone(database.committedSchema),
+ };
+
+ this.connections[connectionCookie] = myConn;
+
return { connectionCookie };
}
@@ -201,6 +283,9 @@ export class MemoryBackend implements Backend {
objectStores: string[],
mode: import("./util/types").TransactionMode,
): Promise<DatabaseTransaction> {
+ if (this.enableTracing) {
+ console.log(`TRACING: beginTransaction`);
+ }
const transactionCookie = `tx-${this.transactionIdCounter++}`;
const myConn = this.connections[conn.connectionCookie];
if (!myConn) {
@@ -212,6 +297,9 @@ export class MemoryBackend implements Backend {
}
while (myDb.txLevel !== TransactionLevel.Connected) {
+ if (this.enableTracing) {
+ console.log(`TRACING: beginTransaction -- waiting for others to close`);
+ }
await this.transactionDoneCond.wait();
}
@@ -232,6 +320,9 @@ export class MemoryBackend implements Backend {
conn: DatabaseConnection,
newVersion: number,
): Promise<DatabaseTransaction> {
+ if (this.enableTracing) {
+ console.log(`TRACING: enterVersionChange`);
+ }
const transactionCookie = `tx-vc-${this.transactionIdCounter++}`;
const myConn = this.connections[conn.connectionCookie];
if (!myConn) {
@@ -254,6 +345,9 @@ export class MemoryBackend implements Backend {
}
async close(conn: DatabaseConnection): Promise<void> {
+ if (this.enableTracing) {
+ console.log(`TRACING: close`);
+ }
const myConn = this.connections[conn.connectionCookie];
if (!myConn) {
throw Error("connection not found - already closed?");
@@ -266,9 +360,13 @@ export class MemoryBackend implements Backend {
myDb.txLevel = TransactionLevel.Disconnected;
}
delete this.connections[conn.connectionCookie];
+ this.disconnectCond.trigger();
}
getSchema(dbConn: DatabaseConnection): Schema {
+ if (this.enableTracing) {
+ console.log(`TRACING: getSchema`);
+ }
const myConn = this.connections[dbConn.connectionCookie];
if (!myConn) {
throw Error("unknown connection");
@@ -288,7 +386,10 @@ export class MemoryBackend implements Backend {
oldName: string,
newName: string,
): void {
- const myConn = this.connections[btx.transactionCookie];
+ if (this.enableTracing) {
+ console.log(`TRACING: renameIndex(?, ${oldName}, ${newName})`);
+ }
+ const myConn = this.connectionsByTransaction[btx.transactionCookie];
if (!myConn) {
throw Error("unknown connection");
}
@@ -331,6 +432,9 @@ export class MemoryBackend implements Backend {
}
deleteIndex(btx: DatabaseTransaction, indexName: string): void {
+ if (this.enableTracing) {
+ console.log(`TRACING: deleteIndex(${indexName})`);
+ }
const myConn = this.connections[btx.transactionCookie];
if (!myConn) {
throw Error("unknown connection");
@@ -365,6 +469,9 @@ export class MemoryBackend implements Backend {
}
deleteObjectStore(btx: DatabaseTransaction, name: string): void {
+ if (this.enableTracing) {
+ console.log(`TRACING: deleteObjectStore(${name})`);
+ }
const myConn = this.connections[btx.transactionCookie];
if (!myConn) {
throw Error("unknown connection");
@@ -403,6 +510,10 @@ export class MemoryBackend implements Backend {
oldName: string,
newName: string,
): void {
+ if (this.enableTracing) {
+ console.log(`TRACING: renameObjectStore(?, ${oldName}, ${newName})`);
+ }
+
const myConn = this.connections[btx.transactionCookie];
if (!myConn) {
throw Error("unknown connection");
@@ -441,7 +552,12 @@ export class MemoryBackend implements Backend {
keyPath: string | string[] | null,
autoIncrement: boolean,
): void {
- const myConn = this.connections[btx.transactionCookie];
+ if (this.enableTracing) {
+ console.log(
+ `TRACING: createObjectStore(${btx.transactionCookie}, ${name})`,
+ );
+ }
+ const myConn = this.connectionsByTransaction[btx.transactionCookie];
if (!myConn) {
throw Error("unknown connection");
}
@@ -482,7 +598,10 @@ export class MemoryBackend implements Backend {
multiEntry: boolean,
unique: boolean,
): void {
- const myConn = this.connections[btx.transactionCookie];
+ if (this.enableTracing) {
+ console.log(`TRACING: createIndex(${indexName})`);
+ }
+ const myConn = this.connectionsByTransaction[btx.transactionCookie];
if (!myConn) {
throw Error("unknown connection");
}
@@ -526,7 +645,10 @@ export class MemoryBackend implements Backend {
objectStoreName: string,
range: import("./BridgeIDBKeyRange").default,
): Promise<void> {
- const myConn = this.connections[btx.transactionCookie];
+ if (this.enableTracing) {
+ console.log(`TRACING: deleteRecord`);
+ }
+ const myConn = this.connectionsByTransaction[btx.transactionCookie];
if (!myConn) {
throw Error("unknown connection");
}
@@ -537,13 +659,17 @@ export class MemoryBackend implements Backend {
if (db.txLevel < TransactionLevel.Write) {
throw Error("only allowed in write transaction");
}
+ throw Error("not implemented");
}
async getRecords(
btx: DatabaseTransaction,
- req: import("./backend-interface").RecordGetRequest,
- ): Promise<import("./backend-interface").RecordGetResponse> {
- const myConn = this.connections[btx.transactionCookie];
+ req: RecordGetRequest,
+ ): Promise<RecordGetResponse> {
+ if (this.enableTracing) {
+ console.log(`TRACING: getRecords`);
+ }
+ const myConn = this.connectionsByTransaction[btx.transactionCookie];
if (!myConn) {
throw Error("unknown connection");
}
@@ -551,17 +677,242 @@ export class MemoryBackend implements Backend {
if (!db) {
throw Error("db not found");
}
- if (db.txLevel < TransactionLevel.Write) {
+ if (db.txLevel < TransactionLevel.Read) {
throw Error("only allowed while running a transaction");
}
- throw Error("not implemented");
+ const objectStore = myConn.objectStoreMap[req.objectStoreName];
+ if (!objectStore) {
+ throw Error("object store not found");
+ }
+
+ let range;
+ if (req.range == null || req.range === undefined) {
+ range = new BridgeIDBKeyRange(null, null, true, true);
+ } else {
+ range = req.range;
+ }
+
+ let numResults = 0;
+ let indexKeys: Key[] = [];
+ let primaryKeys = [];
+ let values = [];
+
+ const forward: boolean =
+ req.direction === "next" || req.direction === "nextunique";
+ const unique: boolean =
+ req.direction === "prevunique" || req.direction === "nextunique";
+
+ const storeData = objectStore.modifiedData || objectStore.originalData;
+
+ const haveIndex = req.indexName !== undefined;
+
+ if (haveIndex) {
+ const index = myConn.indexMap[req.indexName!];
+ const indexData = index.modifiedData || index.originalData;
+ let indexPos = req.lastIndexPosition;
+
+ if (indexPos === undefined) {
+ // First time we iterate! So start at the beginning (lower/upper)
+ // of our allowed range.
+ indexPos = forward ? range.lower : range.upper;
+ }
+
+ let primaryPos = req.lastObjectStorePosition;
+
+ // We might have to advance the index key further!
+ if (req.advanceIndexKey !== undefined) {
+ const compareResult = compareKeys(req.advanceIndexKey, indexPos);
+ if ((forward && compareResult > 0) || (!forward && compareResult > 0)) {
+ indexPos = req.advanceIndexKey;
+ } else if (compareResult == 0 && req.advancePrimaryKey !== undefined) {
+ // index keys are the same, so advance the primary key
+ if (primaryPos === undefined) {
+ primaryPos = req.advancePrimaryKey;
+ } else {
+ const primCompareResult = compareKeys(
+ req.advancePrimaryKey,
+ primaryPos,
+ );
+ if (
+ (forward && primCompareResult > 0) ||
+ (!forward && primCompareResult < 0)
+ ) {
+ primaryPos = req.advancePrimaryKey;
+ }
+ }
+ }
+ }
+
+ let indexEntry;
+ indexEntry = indexData.get(indexPos);
+ if (!indexEntry) {
+ const res = indexData.nextHigherPair(indexPos);
+ if (res) {
+ indexEntry = res[1];
+ }
+ }
+
+ if (!indexEntry) {
+ // We're out of luck, no more data!
+ return { count: 0, primaryKeys: [], indexKeys: [], values: [] };
+ }
+
+ let primkeySubPos = 0;
+
+ // Sort out the case where the index key is the same, so we have
+ // to get the prev/next primary key
+ if (
+ req.lastIndexPosition !== undefined &&
+ compareKeys(indexEntry.indexKey, req.lastIndexPosition) === 0
+ ) {
+ let pos = forward ? 0 : indexEntry.primaryKeys.length - 1;
+ // Advance past the lastObjectStorePosition
+ while (pos >= 0 && pos < indexEntry.primaryKeys.length) {
+ const cmpResult = compareKeys(
+ req.lastObjectStorePosition,
+ indexEntry.primaryKeys[pos],
+ );
+ if ((forward && cmpResult < 0) || (!forward && cmpResult > 0)) {
+ break;
+ }
+ pos += forward ? 1 : -1;
+ }
+ // Make sure we're at least at advancedPrimaryPos
+ while (
+ primaryPos !== undefined &&
+ pos >= 0 &&
+ pos < indexEntry.primaryKeys.length
+ ) {
+ const cmpResult = compareKeys(
+ primaryPos,
+ indexEntry.primaryKeys[pos],
+ );
+ if ((forward && cmpResult <= 0) || (!forward && cmpResult >= 0)) {
+ break;
+ }
+ pos += forward ? 1 : -1;
+ }
+ primkeySubPos = pos;
+ } else {
+ primkeySubPos = forward ? 0 : indexEntry.primaryKeys.length - 1;
+ }
+
+ // FIXME: filter out duplicates
+
+ while (1) {
+ if (req.limit != 0 && numResults == req.limit) {
+ break;
+ }
+ if (indexPos === undefined) {
+ break;
+ }
+ if (!range.includes(indexPos)) {
+ break;
+ }
+ if (
+ primkeySubPos < 0 ||
+ primkeySubPos >= indexEntry.primaryKeys.length
+ ) {
+ primkeySubPos = forward ? 0 : indexEntry.primaryKeys.length - 1;
+ const res = indexData.nextHigherPair(indexPos);
+ if (res) {
+ indexPos = res[1].indexKey;
+ } else {
+ break;
+ }
+ }
+ primaryKeys.push(indexEntry.primaryKeys[primkeySubPos]);
+ numResults++;
+ primkeySubPos = forward ? 0 : indexEntry.primaryKeys.length - 1;
+ }
+
+ // Now we can collect the values based on the primary keys,
+ // if requested.
+ if (req.resultLevel === ResultLevel.Full) {
+ for (let i = 0; i < numResults; i++) {
+ const result = storeData.get(primaryKeys[i]);
+ if (!result) {
+ throw Error("invariant violated");
+ }
+ values.push(result);
+ }
+ }
+ } else {
+ // only based on object store, no index involved, phew!
+ let storePos = req.lastObjectStorePosition;
+ if (storePos === undefined) {
+ storePos = forward ? range.lower : range.upper;
+ }
+
+ if (req.advanceIndexKey !== undefined) {
+ throw Error("unsupported request");
+ }
+
+ storePos = furthestKey(forward, req.advancePrimaryKey, storePos);
+
+ // Advance store position if we are either still at the last returned
+ // store key, or if we are currently not on a key.
+ const storeEntry = storeData.get(storePos);
+ if (
+ !storeEntry ||
+ (req.lastObjectStorePosition !== undefined &&
+ compareKeys(req.lastObjectStorePosition, storeEntry.primaryKey))
+ ) {
+ storePos = storeData.nextHigherKey(storePos);
+ }
+
+ if (req.lastObjectStorePosition)
+ while (1) {
+ if (req.limit != 0 && numResults == req.limit) {
+ break;
+ }
+ if (storePos === null || storePos === undefined) {
+ break;
+ }
+ if (!range.includes(storePos)) {
+ break;
+ }
+
+ const res = storeData.get(storePos);
+
+ if (!res) {
+ break;
+ }
+
+ if (req.resultLevel >= ResultLevel.OnlyKeys) {
+ primaryKeys.push(res.primaryKey);
+ }
+
+ if (req.resultLevel >= ResultLevel.Full) {
+ values.push(res.value);
+ }
+ numResults++;
+ storePos = nextStoreKey(forward, storeData, storePos);
+ }
+ }
+ if (this.enableTracing) {
+ console.log(`TRACING: getRecords got ${numResults} results`)
+ }
+ return {
+ count: numResults,
+ indexKeys:
+ req.resultLevel >= ResultLevel.OnlyKeys && haveIndex
+ ? indexKeys
+ : undefined,
+ primaryKeys:
+ req.resultLevel >= ResultLevel.OnlyKeys ? primaryKeys : undefined,
+ values: req.resultLevel >= ResultLevel.Full ? values : undefined,
+ };
}
async storeRecord(
btx: DatabaseTransaction,
storeReq: RecordStoreRequest,
): Promise<void> {
- const myConn = this.connections[btx.transactionCookie];
+ if (this.enableTracing) {
+ console.log(`TRACING: storeRecord`);
+ }
+ const myConn = this.connectionsByTransaction[btx.transactionCookie];
if (!myConn) {
throw Error("unknown connection");
}
@@ -578,7 +929,7 @@ export class MemoryBackend implements Backend {
const objectStore = myConn.objectStoreMap[storeReq.objectStoreName];
- const storeKeyResult: StoreKeyResult = getStoreKey(
+ const storeKeyResult: StoreKeyResult = makeStoreKeyValue(
storeReq.value,
storeReq.key,
objectStore.modifiedKeyGenerator || objectStore.originalKeyGenerator,
@@ -607,12 +958,54 @@ export class MemoryBackend implements Backend {
throw Error("index referenced by object store does not exist");
}
const indexProperties = schema.indexes[indexName];
- insertIntoIndex(index, value, indexProperties);
+ this.insertIntoIndex(index, key, value, indexProperties);
+ }
+ }
+
+ insertIntoIndex(
+ index: Index,
+ primaryKey: Key,
+ value: Value,
+ indexProperties: IndexProperties,
+ ): void {
+ if (this.enableTracing) {
+ console.log(
+ `insertIntoIndex(${index.modifiedName || index.originalName})`,
+ );
+ }
+ let indexData = index.modifiedData || index.originalData;
+ const indexKeys = getIndexKeys(
+ value,
+ indexProperties.keyPath,
+ indexProperties.multiEntry,
+ );
+ for (const indexKey of indexKeys) {
+ const existingRecord = indexData.get(indexKey);
+ if (existingRecord) {
+ if (indexProperties.unique) {
+ throw new ConstraintError();
+ } else {
+ const newIndexRecord = {
+ indexKey: indexKey,
+ primaryKeys: [primaryKey].concat(existingRecord.primaryKeys),
+ };
+ index.modifiedData = indexData.with(indexKey, newIndexRecord, true);
+ }
+ } else {
+ const newIndexRecord: IndexRecord = {
+ indexKey: indexKey,
+ primaryKeys: [primaryKey],
+ };
+ index.modifiedData = indexData.with(indexKey, newIndexRecord, true);
+ }
}
}
async rollback(btx: DatabaseTransaction): Promise<void> {
- const myConn = this.connections[btx.transactionCookie];
+ if (this.enableTracing) {
+ console.log(`TRACING: rollback`);
+ }
+ const myConn = this.connectionsByTransaction[btx.transactionCookie];
if (!myConn) {
throw Error("unknown connection");
}
@@ -642,10 +1035,15 @@ export class MemoryBackend implements Backend {
objectStore.modifiedName = undefined;
objectStore.modifiedKeyGenerator = undefined;
}
+ delete this.connectionsByTransaction[btx.transactionCookie];
+ this.transactionDoneCond.trigger();
}
async commit(btx: DatabaseTransaction): Promise<void> {
- const myConn = this.connections[btx.transactionCookie];
+ if (this.enableTracing) {
+ console.log(`TRACING: commit`);
+ }
+ const myConn = this.connectionsByTransaction[btx.transactionCookie];
if (!myConn) {
throw Error("unknown connection");
}
@@ -656,6 +1054,41 @@ export class MemoryBackend implements Backend {
if (db.txLevel < TransactionLevel.Read) {
throw Error("only allowed while running a transaction");
}
+
+ db.committedSchema = myConn.modifiedSchema || db.committedSchema;
+ db.txLevel = TransactionLevel.Connected;
+
+ db.committedIndexes = {};
+ db.committedObjectStores = {};
+ db.modifiedIndexes = {};
+ db.committedObjectStores = {};
+
+ for (const indexName in myConn.indexMap) {
+ const index = myConn.indexMap[indexName];
+ index.deleted = false;
+ index.originalData = index.modifiedData || index.originalData;
+ index.originalName = index.modifiedName || index.originalName;
+ db.committedIndexes[indexName] = index;
+ }
+
+ for (const objectStoreName in myConn.objectStoreMap) {
+ const objectStore = myConn.objectStoreMap[objectStoreName];
+ objectStore.deleted = false;
+ objectStore.originalData =
+ objectStore.modifiedData || objectStore.originalData;
+ objectStore.originalName =
+ objectStore.modifiedName || objectStore.originalName;
+ if (objectStore.modifiedKeyGenerator !== undefined) {
+ objectStore.originalKeyGenerator = objectStore.modifiedKeyGenerator;
+ }
+ db.committedObjectStores[objectStoreName] = objectStore;
+ }
+
+ myConn.indexMap = Object.assign({}, db.committedIndexes);
+ myConn.objectStoreMap = Object.assign({}, db.committedObjectStores);
+
+ delete this.connectionsByTransaction[btx.transactionCookie];
+ this.transactionDoneCond.trigger();
}
}
diff --git a/packages/idb-bridge/src/backend-interface.ts b/packages/idb-bridge/src/backend-interface.ts
index c0f498a10..c963b1896 100644
--- a/packages/idb-bridge/src/backend-interface.ts
+++ b/packages/idb-bridge/src/backend-interface.ts
@@ -45,18 +45,47 @@ export interface RecordGetRequest {
direction: BridgeIDBCursorDirection;
objectStoreName: string;
indexName: string | undefined;
+ /**
+ * The range of keys to return.
+ * If indexName is defined, the range refers to the index keys.
+ * Otherwise it refers to the object store keys.
+ */
range: BridgeIDBKeyRange | undefined;
+ /**
+ * Last cursor position in terms of the index key.
+ * Can only be specified if indexName is defined and
+ * lastObjectStorePosition is defined.
+ *
+ * Must either be undefined or within range.
+ */
lastIndexPosition?: Key;
+ /**
+ * Last position in terms of the object store key.
+ */
lastObjectStorePosition?: Key;
+ /**
+ * If specified, the index key of the results must be
+ * greater or equal to advanceIndexKey.
+ *
+ * Only applicable if indexName is specified.
+ */
advanceIndexKey?: Key;
+ /**
+ * If specified, the primary key of the results must be greater
+ * or equal to advancePrimaryKey.
+ */
advancePrimaryKey?: Key;
+ /**
+ * Maximum number of resuts to return.
+ * If -1, return all available results
+ */
limit: number;
resultLevel: ResultLevel;
}
export interface RecordGetResponse {
values: Value[] | undefined;
- keys: Key[] | undefined;
+ indexKeys: Key[] | undefined;
primaryKeys: Key[] | undefined;
count: number;
}
diff --git a/packages/idb-bridge/src/util/FakeEventTarget.ts b/packages/idb-bridge/src/util/FakeEventTarget.ts
index 3c7eaf468..f20432df0 100644
--- a/packages/idb-bridge/src/util/FakeEventTarget.ts
+++ b/packages/idb-bridge/src/util/FakeEventTarget.ts
@@ -14,164 +14,172 @@
permissions and limitations under the License.
*/
-
import { InvalidStateError } from "./errors";
import FakeEvent from "./FakeEvent";
import { EventCallback, EventType } from "./types";
type EventTypeProp =
- | "onabort"
- | "onblocked"
- | "oncomplete"
- | "onerror"
- | "onsuccess"
- | "onupgradeneeded"
- | "onversionchange";
+ | "onabort"
+ | "onblocked"
+ | "oncomplete"
+ | "onerror"
+ | "onsuccess"
+ | "onupgradeneeded"
+ | "onversionchange";
interface Listener {
- callback: EventCallback;
- capture: boolean;
- type: EventType;
+ callback: EventCallback;
+ capture: boolean;
+ type: EventType;
}
const stopped = (event: FakeEvent, listener: Listener) => {
- return (
- event.immediatePropagationStopped ||
- (event.eventPhase === event.CAPTURING_PHASE &&
- listener.capture === false) ||
- (event.eventPhase === event.BUBBLING_PHASE && listener.capture === true)
- );
+ return (
+ event.immediatePropagationStopped ||
+ (event.eventPhase === event.CAPTURING_PHASE &&
+ listener.capture === false) ||
+ (event.eventPhase === event.BUBBLING_PHASE && listener.capture === true)
+ );
};
// http://www.w3.org/TR/dom/#concept-event-listener-invoke
const invokeEventListeners = (event: FakeEvent, obj: FakeEventTarget) => {
- event.currentTarget = obj;
-
- // The callback might cause obj.listeners to mutate as we traverse it.
- // Take a copy of the array so that nothing sneaks in and we don't lose
- // our place.
- for (const listener of obj.listeners.slice()) {
- if (event.type !== listener.type || stopped(event, listener)) {
- continue;
- }
-
- // @ts-ignore
- listener.callback.call(event.currentTarget, event);
+ event.currentTarget = obj;
+
+ // The callback might cause obj.listeners to mutate as we traverse it.
+ // Take a copy of the array so that nothing sneaks in and we don't lose
+ // our place.
+ for (const listener of obj.listeners.slice()) {
+ if (event.type !== listener.type || stopped(event, listener)) {
+ continue;
}
- const typeToProp: { [key in EventType]: EventTypeProp } = {
- abort: "onabort",
- blocked: "onblocked",
- complete: "oncomplete",
- error: "onerror",
- success: "onsuccess",
- upgradeneeded: "onupgradeneeded",
- versionchange: "onversionchange",
+ console.log(`invoking ${event.type} event listener`, listener);
+ // @ts-ignore
+ listener.callback.call(event.currentTarget, event);
+ }
+
+ const typeToProp: { [key in EventType]: EventTypeProp } = {
+ abort: "onabort",
+ blocked: "onblocked",
+ complete: "oncomplete",
+ error: "onerror",
+ success: "onsuccess",
+ upgradeneeded: "onupgradeneeded",
+ versionchange: "onversionchange",
+ };
+ const prop = typeToProp[event.type];
+ if (prop === undefined) {
+ throw new Error(`Unknown event type: "${event.type}"`);
+ }
+
+ const callback = event.currentTarget[prop];
+ if (callback) {
+ const listener = {
+ callback,
+ capture: false,
+ type: event.type,
};
- const prop = typeToProp[event.type];
- if (prop === undefined) {
- throw new Error(`Unknown event type: "${event.type}"`);
- }
-
- const callback = event.currentTarget[prop];
- if (callback) {
- const listener = {
- callback,
- capture: false,
- type: event.type,
- };
- if (!stopped(event, listener)) {
- // @ts-ignore
- listener.callback.call(event.currentTarget, event);
- }
+ if (!stopped(event, listener)) {
+ console.log(`invoking on${event.type} event listener`, listener);
+ // @ts-ignore
+ listener.callback.call(event.currentTarget, event);
}
+ }
};
abstract class FakeEventTarget {
- public readonly listeners: Listener[] = [];
-
- // These will be overridden in individual subclasses and made not readonly
- public readonly onabort: EventCallback | null | undefined;
- public readonly onblocked: EventCallback | null | undefined;
- public readonly oncomplete: EventCallback | null | undefined;
- public readonly onerror: EventCallback | null | undefined;
- public readonly onsuccess: EventCallback | null | undefined;
- public readonly onupgradeneeded: EventCallback | null | undefined;
- public readonly onversionchange: EventCallback | null | undefined;
-
- public addEventListener(
- type: EventType,
- callback: EventCallback,
- capture = false,
- ) {
- this.listeners.push({
- callback,
- capture,
- type,
- });
- }
-
- public removeEventListener(
- type: EventType,
- callback: EventCallback,
- capture = false,
- ) {
- const i = this.listeners.findIndex(listener => {
- return (
- listener.type === type &&
- listener.callback === callback &&
- listener.capture === capture
- );
- });
-
- this.listeners.splice(i, 1);
+ public readonly listeners: Listener[] = [];
+
+ // These will be overridden in individual subclasses and made not readonly
+ public readonly onabort: EventCallback | null | undefined;
+ public readonly onblocked: EventCallback | null | undefined;
+ public readonly oncomplete: EventCallback | null | undefined;
+ public readonly onerror: EventCallback | null | undefined;
+ public readonly onsuccess: EventCallback | null | undefined;
+ public readonly onupgradeneeded: EventCallback | null | undefined;
+ public readonly onversionchange: EventCallback | null | undefined;
+
+ static enableTracing: boolean = true;
+
+ public addEventListener(
+ type: EventType,
+ callback: EventCallback,
+ capture = false,
+ ) {
+ this.listeners.push({
+ callback,
+ capture,
+ type,
+ });
+ }
+
+ public removeEventListener(
+ type: EventType,
+ callback: EventCallback,
+ capture = false,
+ ) {
+ const i = this.listeners.findIndex(listener => {
+ return (
+ listener.type === type &&
+ listener.callback === callback &&
+ listener.capture === capture
+ );
+ });
+
+ this.listeners.splice(i, 1);
+ }
+
+ // http://www.w3.org/TR/dom/#dispatching-events
+ public dispatchEvent(event: FakeEvent) {
+ if (event.dispatched || !event.initialized) {
+ throw new InvalidStateError("The object is in an invalid state.");
}
+ event.isTrusted = false;
- // http://www.w3.org/TR/dom/#dispatching-events
- public dispatchEvent(event: FakeEvent) {
- if (event.dispatched || !event.initialized) {
- throw new InvalidStateError("The object is in an invalid state.");
- }
- event.isTrusted = false;
+ event.dispatched = true;
+ event.target = this;
+ // NOT SURE WHEN THIS SHOULD BE SET event.eventPath = [];
- event.dispatched = true;
- event.target = this;
- // NOT SURE WHEN THIS SHOULD BE SET event.eventPath = [];
+ event.eventPhase = event.CAPTURING_PHASE;
+ if (FakeEventTarget.enableTracing) {
+ console.log(
+ `dispatching '${event.type}' event along path with ${event.eventPath.length} elements`,
+ );
+ }
+ for (const obj of event.eventPath) {
+ if (!event.propagationStopped) {
+ invokeEventListeners(event, obj);
+ }
+ }
- event.eventPhase = event.CAPTURING_PHASE;
- for (const obj of event.eventPath) {
- if (!event.propagationStopped) {
- invokeEventListeners(event, obj);
- }
- }
+ event.eventPhase = event.AT_TARGET;
+ if (!event.propagationStopped) {
+ invokeEventListeners(event, event.target);
+ }
- event.eventPhase = event.AT_TARGET;
+ if (event.bubbles) {
+ event.eventPath.reverse();
+ event.eventPhase = event.BUBBLING_PHASE;
+ if (event.eventPath.length === 0 && event.type === "error") {
+ console.error("Unhandled error event: ", event.target);
+ }
+ for (const obj of event.eventPath) {
if (!event.propagationStopped) {
- invokeEventListeners(event, event.target);
- }
-
- if (event.bubbles) {
- event.eventPath.reverse();
- event.eventPhase = event.BUBBLING_PHASE;
- if (event.eventPath.length === 0 && event.type === "error") {
- console.error("Unhandled error event: ", event.target);
- }
- for (const obj of event.eventPath) {
- if (!event.propagationStopped) {
- invokeEventListeners(event, obj);
- }
- }
+ invokeEventListeners(event, obj);
}
+ }
+ }
- event.dispatched = false;
- event.eventPhase = event.NONE;
- event.currentTarget = null;
+ event.dispatched = false;
+ event.eventPhase = event.NONE;
+ event.currentTarget = null;
- if (event.canceled) {
- return false;
- }
- return true;
+ if (event.canceled) {
+ return false;
}
+ return true;
+ }
}
export default FakeEventTarget;
diff --git a/packages/idb-bridge/src/util/getIndexKeys.test.ts b/packages/idb-bridge/src/util/getIndexKeys.test.ts
new file mode 100644
index 000000000..e1bc9dd00
--- /dev/null
+++ b/packages/idb-bridge/src/util/getIndexKeys.test.ts
@@ -0,0 +1,24 @@
+import test from "ava";
+import { getIndexKeys } from "./getIndexKeys";
+
+test("basics", (t) => {
+ t.deepEqual(getIndexKeys({foo: 42}, "foo", false), [42]);
+ t.deepEqual(getIndexKeys({foo: {bar: 42}}, "foo.bar", false), [42]);
+ t.deepEqual(getIndexKeys({foo: [42, 43]}, "foo.0", false), [42]);
+ t.deepEqual(getIndexKeys({foo: [42, 43]}, "foo.1", false), [43]);
+
+ t.deepEqual(getIndexKeys([1, 2, 3], "", false), [[1, 2, 3]]);
+
+ t.throws(() => {
+ getIndexKeys({foo: 42}, "foo.bar", false);
+ });
+
+ t.deepEqual(getIndexKeys({foo: 42}, "foo", true), [42]);
+ t.deepEqual(getIndexKeys({foo: 42, bar: 10}, ["foo", "bar"], true), [42, 10]);
+ t.deepEqual(getIndexKeys({foo: 42, bar: 10}, ["foo", "bar"], false), [[42, 10]]);
+ t.deepEqual(getIndexKeys({foo: 42, bar: 10}, ["foo", "bar", "spam"], true), [42, 10]);
+
+ t.throws(() => {
+ getIndexKeys({foo: 42, bar: 10}, ["foo", "bar", "spam"], false);
+ });
+});
diff --git a/packages/idb-bridge/src/util/getIndexKeys.ts b/packages/idb-bridge/src/util/getIndexKeys.ts
new file mode 100644
index 000000000..416cf9ea2
--- /dev/null
+++ b/packages/idb-bridge/src/util/getIndexKeys.ts
@@ -0,0 +1,28 @@
+import { Key, Value, KeyPath } from "./types";
+import extractKey from "./extractKey";
+import valueToKey from "./valueToKey";
+
+export function getIndexKeys(
+ value: Value,
+ keyPath: KeyPath,
+ multiEntry: boolean,
+): Key[] {
+ if (multiEntry && Array.isArray(keyPath)) {
+ const keys = [];
+ for (const subkeyPath of keyPath) {
+ const key = extractKey(subkeyPath, value);
+ try {
+ const k = valueToKey(key);
+ keys.push(k);
+ } catch {
+ // Ignore invalid subkeys
+ }
+ }
+ return keys;
+ } else {
+ let key = extractKey(keyPath, value);
+ return [valueToKey(key)];
+ }
+}
+
+export default getIndexKeys;
diff --git a/packages/idb-bridge/src/util/makeStoreKeyValue.test.ts b/packages/idb-bridge/src/util/makeStoreKeyValue.test.ts
new file mode 100644
index 000000000..7820875c3
--- /dev/null
+++ b/packages/idb-bridge/src/util/makeStoreKeyValue.test.ts
@@ -0,0 +1,42 @@
+import test from 'ava';
+import { makeStoreKeyValue } from "./makeStoreKeyValue";
+
+test("basics", (t) => {
+ let result;
+
+ result = makeStoreKeyValue({ name: "Florian" }, undefined, 42, true, "id");
+ t.is(result.updatedKeyGenerator, 43);
+ t.is(result.key, 42);
+ t.is(result.value.name, "Florian");
+ t.is(result.value.id, 42);
+
+ result = makeStoreKeyValue({ name: "Florian", id: 10 }, undefined, 5, true, "id");
+ t.is(result.updatedKeyGenerator, 11);
+ t.is(result.key, 10);
+ t.is(result.value.name, "Florian");
+ t.is(result.value.id, 10);
+
+ result = makeStoreKeyValue({ name: "Florian", id: 5 }, undefined, 10, true, "id");
+ t.is(result.updatedKeyGenerator, 10);
+ t.is(result.key, 5);
+ t.is(result.value.name, "Florian");
+ t.is(result.value.id, 5);
+
+ result = makeStoreKeyValue({ name: "Florian", id: "foo" }, undefined, 10, true, "id");
+ t.is(result.updatedKeyGenerator, 10);
+ t.is(result.key, "foo");
+ t.is(result.value.name, "Florian");
+ t.is(result.value.id, "foo");
+
+ result = makeStoreKeyValue({ name: "Florian" }, "foo", 10, true, null);
+ t.is(result.updatedKeyGenerator, 10);
+ t.is(result.key, "foo");
+ t.is(result.value.name, "Florian");
+ t.is(result.value.id, undefined);
+
+ result = makeStoreKeyValue({ name: "Florian" }, undefined, 10, true, null);
+ t.is(result.updatedKeyGenerator, 11);
+ t.is(result.key, 10);
+ t.is(result.value.name, "Florian");
+ t.is(result.value.id, undefined);
+});
diff --git a/packages/idb-bridge/src/util/makeStoreKeyValue.ts b/packages/idb-bridge/src/util/makeStoreKeyValue.ts
index 4850cec26..4f45e0d8a 100644
--- a/packages/idb-bridge/src/util/makeStoreKeyValue.ts
+++ b/packages/idb-bridge/src/util/makeStoreKeyValue.ts
@@ -63,10 +63,14 @@ export function makeStoreKeyValue(
updatedKeyGenerator = currentKeyGenerator + 1;
} else if (typeof maybeInlineKey === "number") {
key = maybeInlineKey;
- updatedKeyGenerator = maybeInlineKey;
+ if (maybeInlineKey >= currentKeyGenerator) {
+ updatedKeyGenerator = maybeInlineKey + 1;
+ } else {
+ updatedKeyGenerator = currentKeyGenerator;
+ }
} else {
key = maybeInlineKey;
- updatedKeyGenerator = currentKeyGenerator + 1;
+ updatedKeyGenerator = currentKeyGenerator;
}
return {
key: key,
@@ -84,9 +88,17 @@ export function makeStoreKeyValue(
};
}
} else {
- // (no, no, yes)
- // (no, no, no)
- throw new DataError();
+ if (autoIncrement) {
+ // (no, no, yes)
+ return {
+ key: currentKeyGenerator,
+ value: value,
+ updatedKeyGenerator: currentKeyGenerator + 1,
+ }
+ } else {
+ // (no, no, no)
+ throw new DataError();
+ }
}
}
-} \ No newline at end of file
+}