aboutsummaryrefslogtreecommitdiff
path: root/packages/idb-bridge/src
diff options
context:
space:
mode:
authorFlorian Dold <florian.dold@gmail.com>2019-07-31 01:33:23 +0200
committerFlorian Dold <florian.dold@gmail.com>2019-07-31 01:33:23 +0200
commitbcefbd7aab5f33f93d626c6421a1a1218c1a91a2 (patch)
treec0ac6e5e5fde9f51024ad5409c87b7e01c4af60a /packages/idb-bridge/src
parent16ecbc9f177f1f71048840edf9b7af20ace3aad8 (diff)
idb-bridge: test cases, package structure and missing functionality
Diffstat (limited to 'packages/idb-bridge/src')
-rw-r--r--packages/idb-bridge/src/BridgeIDBCursor.ts36
-rw-r--r--packages/idb-bridge/src/BridgeIDBCursorWithValue.ts49
-rw-r--r--packages/idb-bridge/src/BridgeIDBIndex.ts2
-rw-r--r--packages/idb-bridge/src/BridgeIDBKeyRange.ts11
-rw-r--r--packages/idb-bridge/src/BridgeIDBObjectStore.ts39
-rw-r--r--packages/idb-bridge/src/BridgeIDBTransaction.ts21
-rw-r--r--packages/idb-bridge/src/MemoryBackend.test.ts57
-rw-r--r--packages/idb-bridge/src/MemoryBackend.ts211
-rw-r--r--packages/idb-bridge/src/backend-interface.ts8
-rw-r--r--packages/idb-bridge/src/index.ts60
-rw-r--r--packages/idb-bridge/src/util/FakeEventTarget.ts4
-rw-r--r--packages/idb-bridge/src/util/makeStoreKeyValue.ts2
12 files changed, 406 insertions, 94 deletions
diff --git a/packages/idb-bridge/src/BridgeIDBCursor.ts b/packages/idb-bridge/src/BridgeIDBCursor.ts
index ed5aa3e88..6c3139083 100644
--- a/packages/idb-bridge/src/BridgeIDBCursor.ts
+++ b/packages/idb-bridge/src/BridgeIDBCursor.ts
@@ -42,12 +42,14 @@ import {
Backend,
DatabaseTransaction,
RecordStoreRequest,
+ StoreLevel,
} from "./backend-interface";
+import BridgeIDBFactory from "./BridgeIDBFactory";
/**
* http://www.w3.org/TR/2015/REC-IndexedDB-20150108/#cursor
*/
-class BridgeIDBCursor {
+export class BridgeIDBCursor {
_request: BridgeIDBRequest | undefined;
private _gotValue: boolean = false;
@@ -119,14 +121,24 @@ class BridgeIDBCursor {
get primaryKey() {
return this._primaryKey;
}
+
set primaryKey(val) {
/* For babel */
}
+ protected get _isValueCursor(): boolean {
+ return false;
+ }
+
/**
* https://w3c.github.io/IndexedDB/#iterate-a-cursor
*/
async _iterate(key?: Key, primaryKey?: Key): Promise<any> {
+ BridgeIDBFactory.enableTracing &&
+ console.log(
+ `iterating cursor os=${this._objectStoreName},idx=${this._indexName}`,
+ );
+ BridgeIDBFactory.enableTracing && console.log("cursor type ", this.toString());
const recordGetRequest: RecordGetRequest = {
direction: this.direction,
indexName: this._indexName,
@@ -145,7 +157,10 @@ class BridgeIDBCursor {
let response = await this._backend.getRecords(btx, recordGetRequest);
if (response.count === 0) {
- console.log("cursor is returning empty result");
+ if (BridgeIDBFactory.enableTracing) {
+ console.log("cursor is returning empty result");
+ }
+ this._gotValue = false;
return null;
}
@@ -153,8 +168,10 @@ class BridgeIDBCursor {
throw Error("invariant failed");
}
- console.log("request is:", JSON.stringify(recordGetRequest));
- console.log("get response is:", JSON.stringify(response));
+ if (BridgeIDBFactory.enableTracing) {
+ console.log("request is:", JSON.stringify(recordGetRequest));
+ console.log("get response is:", JSON.stringify(response));
+ }
if (this._indexName !== undefined) {
this._key = response.indexKeys![0];
@@ -204,20 +221,23 @@ class BridgeIDBCursor {
throw new InvalidStateError();
}
- if (!this._gotValue || !this.hasOwnProperty("value")) {
+ if (!this._gotValue || !this._isValueCursor) {
throw new InvalidStateError();
}
const storeReq: RecordStoreRequest = {
- overwrite: true,
key: this._primaryKey,
value: value,
objectStoreName: this._objectStoreName,
+ storeLevel: StoreLevel.UpdateExisting,
};
const operation = async () => {
+ if (BridgeIDBFactory.enableTracing) {
+ console.log("updating at cursor")
+ }
const { btx } = this.source._confirmActiveTransaction();
- this._backend.storeRecord(btx, storeReq);
+ await this._backend.storeRecord(btx, storeReq);
};
return transaction._execRequestAsync({
operation,
@@ -318,7 +338,7 @@ class BridgeIDBCursor {
throw new InvalidStateError();
}
- if (!this._gotValue || !this.hasOwnProperty("value")) {
+ if (!this._gotValue || !this._isValueCursor) {
throw new InvalidStateError();
}
diff --git a/packages/idb-bridge/src/BridgeIDBCursorWithValue.ts b/packages/idb-bridge/src/BridgeIDBCursorWithValue.ts
index b2f23147f..d75bd21e6 100644
--- a/packages/idb-bridge/src/BridgeIDBCursorWithValue.ts
+++ b/packages/idb-bridge/src/BridgeIDBCursorWithValue.ts
@@ -16,32 +16,35 @@
import BridgeIDBCursor from "./BridgeIDBCursor";
import {
- CursorRange,
- CursorSource,
- BridgeIDBCursorDirection,
- Value,
+ CursorRange,
+ CursorSource,
+ BridgeIDBCursorDirection,
+ Value,
} from "./util/types";
class BridgeIDBCursorWithValue extends BridgeIDBCursor {
-
- get value(): Value {
- return this._value;
- }
-
- constructor(
- source: CursorSource,
- objectStoreName: string,
- indexName: string | undefined,
- range: CursorRange,
- direction: BridgeIDBCursorDirection,
- request?: any,
- ) {
- super(source, objectStoreName, indexName, range, direction, request, false);
- }
-
- public toString() {
- return "[object IDBCursorWithValue]";
- }
+ get value(): Value {
+ return this._value;
+ }
+
+ protected get _isValueCursor(): boolean {
+ return true;
+ }
+
+ constructor(
+ source: CursorSource,
+ objectStoreName: string,
+ indexName: string | undefined,
+ range: CursorRange,
+ direction: BridgeIDBCursorDirection,
+ request?: any,
+ ) {
+ super(source, objectStoreName, indexName, range, direction, request, false);
+ }
+
+ public toString() {
+ return "[object IDBCursorWithValue]";
+ }
}
export default BridgeIDBCursorWithValue;
diff --git a/packages/idb-bridge/src/BridgeIDBIndex.ts b/packages/idb-bridge/src/BridgeIDBIndex.ts
index 8179be83f..4d2022d3a 100644
--- a/packages/idb-bridge/src/BridgeIDBIndex.ts
+++ b/packages/idb-bridge/src/BridgeIDBIndex.ts
@@ -50,7 +50,7 @@ const confirmActiveTransaction = (
};
// http://www.w3.org/TR/2015/REC-IndexedDB-20150108/#idl-def-IDBIndex
-class BridgeIDBIndex {
+export class BridgeIDBIndex {
objectStore: BridgeIDBObjectStore;
get _schema(): Schema {
diff --git a/packages/idb-bridge/src/BridgeIDBKeyRange.ts b/packages/idb-bridge/src/BridgeIDBKeyRange.ts
index 2b32c7e0a..4055e092a 100644
--- a/packages/idb-bridge/src/BridgeIDBKeyRange.ts
+++ b/packages/idb-bridge/src/BridgeIDBKeyRange.ts
@@ -113,21 +113,20 @@ class BridgeIDBKeyRange {
static _valueToKeyRange(value: any, nullDisallowedFlag: boolean = false) {
if (value instanceof BridgeIDBKeyRange) {
- return value;
+ return value;
}
if (value === null || value === undefined) {
- if (nullDisallowedFlag) {
- throw new DataError();
- }
- return new BridgeIDBKeyRange(undefined, undefined, false, false);
+ if (nullDisallowedFlag) {
+ throw new DataError();
+ }
+ return new BridgeIDBKeyRange(undefined, undefined, false, false);
}
const key = valueToKey(value);
return BridgeIDBKeyRange.only(key);
}
-
}
export default BridgeIDBKeyRange;
diff --git a/packages/idb-bridge/src/BridgeIDBObjectStore.ts b/packages/idb-bridge/src/BridgeIDBObjectStore.ts
index eca4c1981..af5f80511 100644
--- a/packages/idb-bridge/src/BridgeIDBObjectStore.ts
+++ b/packages/idb-bridge/src/BridgeIDBObjectStore.ts
@@ -46,6 +46,7 @@ import {
DatabaseTransaction,
RecordGetRequest,
ResultLevel,
+ StoreLevel,
} from "./backend-interface";
import BridgeIDBFactory from "./BridgeIDBFactory";
@@ -137,7 +138,7 @@ class BridgeIDBObjectStore {
objectStoreName: this._name,
key: key,
value: value,
- overwrite,
+ storeLevel: overwrite ? StoreLevel.AllowOverwrite : StoreLevel.NoOverwrite,
});
};
@@ -158,7 +159,7 @@ class BridgeIDBObjectStore {
return this._store(value, key, false);
}
- public delete(key: Key) {
+ public delete(key: Key | BridgeIDBKeyRange) {
if (arguments.length === 0) {
throw new TypeError();
}
@@ -167,13 +168,17 @@ class BridgeIDBObjectStore {
throw new ReadOnlyError();
}
- if (!(key instanceof BridgeIDBKeyRange)) {
- key = valueToKey(key);
+ let keyRange: BridgeIDBKeyRange;
+
+ if (key instanceof BridgeIDBKeyRange) {
+ keyRange = key;
+ } else {
+ keyRange = BridgeIDBKeyRange.only(valueToKey(key));
}
const operation = async () => {
const { btx } = this._confirmActiveTransaction();
- return this._backend.deleteRecord(btx, this._name, key);
+ return this._backend.deleteRecord(btx, this._name, keyRange);
}
return this.transaction._execRequestAsync({
@@ -183,12 +188,20 @@ class BridgeIDBObjectStore {
}
public get(key?: BridgeIDBKeyRange | Key) {
+ if (BridgeIDBFactory.enableTracing) {
+ console.log(`getting from object store ${this._name} key ${key}`);
+ }
+
if (arguments.length === 0) {
throw new TypeError();
}
- if (!(key instanceof BridgeIDBKeyRange)) {
- key = valueToKey(key);
+ let keyRange: BridgeIDBKeyRange;
+
+ if (key instanceof BridgeIDBKeyRange) {
+ keyRange = key;
+ } else {
+ keyRange = BridgeIDBKeyRange.only(valueToKey(key));
}
const recordRequest: RecordGetRequest = {
@@ -199,16 +212,24 @@ class BridgeIDBObjectStore {
direction: "next",
limit: 1,
resultLevel: ResultLevel.Full,
- range: key,
+ range: keyRange,
};
const operation = async () => {
+ if (BridgeIDBFactory.enableTracing) {
+ console.log("running get operation:", recordRequest);
+ }
const { btx } = this._confirmActiveTransaction();
const result = await this._backend.getRecords(
btx,
recordRequest,
);
- if (result.count == 0) {
+
+ if (BridgeIDBFactory.enableTracing) {
+ console.log("get operation result count:", result.count);
+ }
+
+ if (result.count === 0) {
return undefined;
}
const values = result.values;
diff --git a/packages/idb-bridge/src/BridgeIDBTransaction.ts b/packages/idb-bridge/src/BridgeIDBTransaction.ts
index a9f0201d3..250e27149 100644
--- a/packages/idb-bridge/src/BridgeIDBTransaction.ts
+++ b/packages/idb-bridge/src/BridgeIDBTransaction.ts
@@ -174,12 +174,12 @@ class BridgeIDBTransaction extends FakeEventTarget {
*/
public async _start() {
if (BridgeIDBFactory.enableTracing) {
- console.log(`TRACE: IDBTransaction._start, ${this._requests.length} queued`);
+ 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,
@@ -188,8 +188,6 @@ 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;
@@ -208,16 +206,17 @@ 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 {
- console.log("running operation with source");
let event;
try {
+ BridgeIDBFactory.enableTracing &&
+ console.log("TRACE: running operation in transaction");
const result = await operation();
- if (BridgeIDBFactory.enableTracing) {
- console.log("TRACE: tx operation finished with success");
- }
+ BridgeIDBFactory.enableTracing &&
+ console.log(
+ "TRACE: operation in transaction finished with success",
+ );
request.readyState = "done";
request.result = result;
request.error = undefined;
@@ -295,7 +294,7 @@ class BridgeIDBTransaction extends FakeEventTarget {
if (!this.error) {
if (BridgeIDBFactory.enableTracing) {
- console.log("dispatching 'complete' event");
+ console.log("dispatching 'complete' event on transaction");
}
const event = new FakeEvent("complete");
event.eventPath = [this, this.db];
diff --git a/packages/idb-bridge/src/MemoryBackend.test.ts b/packages/idb-bridge/src/MemoryBackend.test.ts
index c21c2d064..5ec818f53 100644
--- a/packages/idb-bridge/src/MemoryBackend.test.ts
+++ b/packages/idb-bridge/src/MemoryBackend.test.ts
@@ -235,3 +235,60 @@ test("Spec: Example 1 Part 3", async t => {
t.pass();
});
+
+
+test("simple deletion", 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");
+ };
+
+ 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", "readwrite");
+
+ const store2 = tx2.objectStore("books");
+
+ const req1 = store2.get(234567);
+ await promiseFromRequest(req1);
+ t.is(req1.readyState, "done");
+ t.is(req1.result.author, "Fred");
+
+ store2.delete(123456);
+
+ const req2 = store2.get(123456);
+ await promiseFromRequest(req2);
+ t.is(req2.readyState, "done");
+ t.is(req2.result, undefined);
+
+ const req3 = store2.get(234567);
+ await promiseFromRequest(req3);
+ t.is(req3.readyState, "done");
+ t.is(req3.result.author, "Fred");
+
+ await promiseFromTransaction(tx2);
+
+ t.pass();
+});
+
diff --git a/packages/idb-bridge/src/MemoryBackend.ts b/packages/idb-bridge/src/MemoryBackend.ts
index 1a85a7397..a31adb826 100644
--- a/packages/idb-bridge/src/MemoryBackend.ts
+++ b/packages/idb-bridge/src/MemoryBackend.ts
@@ -8,6 +8,7 @@ import {
RecordGetRequest,
RecordGetResponse,
ResultLevel,
+ StoreLevel,
} from "./backend-interface";
import structuredClone from "./util/structuredClone";
import {
@@ -655,10 +656,10 @@ export class MemoryBackend implements Backend {
async deleteRecord(
btx: DatabaseTransaction,
objectStoreName: string,
- range: import("./BridgeIDBKeyRange").default,
+ range: BridgeIDBKeyRange,
): Promise<void> {
if (this.enableTracing) {
- console.log(`TRACING: deleteRecord`);
+ console.log(`TRACING: deleteRecord from store ${objectStoreName}`);
}
const myConn = this.connectionsByTransaction[btx.transactionCookie];
if (!myConn) {
@@ -671,7 +672,112 @@ export class MemoryBackend implements Backend {
if (db.txLevel < TransactionLevel.Write) {
throw Error("only allowed in write transaction");
}
- throw Error("not implemented");
+ if (typeof range !== "object") {
+ throw Error("deleteRecord got invalid range (must be object)");
+ }
+ if (!("lowerOpen" in range)) {
+ throw Error("deleteRecord got invalid range (sanity check failed, 'lowerOpen' missing)");
+ }
+
+ const schema = myConn.modifiedSchema
+ ? myConn.modifiedSchema
+ : db.committedSchema;
+ const objectStore = myConn.objectStoreMap[objectStoreName];
+
+ if (!objectStore.modifiedData) {
+ objectStore.modifiedData = objectStore.originalData;
+ }
+
+ let modifiedData = objectStore.modifiedData;
+ let currKey: Key | undefined;
+
+ if (range.lower === undefined || range.lower === null) {
+ currKey = modifiedData.minKey();
+ } else {
+ currKey = range.lower;
+ // We have a range with an lowerOpen lower bound, so don't start
+ // deleting the upper bound. Instead start with the next higher key.
+ if (range.lowerOpen && currKey !== undefined) {
+ currKey = modifiedData.nextHigherKey(currKey);
+ }
+ }
+
+ // invariant: (currKey is undefined) or (currKey is a valid key)
+
+ while (true) {
+ if (currKey === undefined) {
+ // nothing more to delete!
+ break;
+ }
+ if (range.upper !== null && range.upper !== undefined) {
+ if (range.upperOpen && compareKeys(currKey, range.upper) === 0) {
+ // We have a range that's upperOpen, so stop before we delete the upper bound.
+ break;
+ }
+ if ((!range.upperOpen) && compareKeys(currKey, range.upper) > 0) {
+ // The upper range is inclusive, only stop if we're after the upper range.
+ break;
+ }
+ }
+
+ const storeEntry = modifiedData.get(currKey);
+ if (!storeEntry) {
+ throw Error("assertion failed");
+ }
+
+ for (const indexName of schema.objectStores[objectStoreName].indexes) {
+ const index = myConn.indexMap[indexName];
+ if (!index) {
+ throw Error("index referenced by object store does not exist");
+ }
+ const indexProperties = schema.indexes[indexName];
+ this.deleteFromIndex(index, storeEntry.primaryKey, storeEntry.value, indexProperties);
+ }
+
+ modifiedData = modifiedData.without(currKey);
+
+ currKey = modifiedData.nextHigherKey(currKey);
+ }
+
+ objectStore.modifiedData = modifiedData;
+ }
+
+ private deleteFromIndex(
+ index: Index,
+ primaryKey: Key,
+ value: Value,
+ indexProperties: IndexProperties,
+ ): void {
+ if (this.enableTracing) {
+ console.log(
+ `deleteFromIndex(${index.modifiedName || index.originalName})`,
+ );
+ }
+ if (value === undefined || value === null) {
+ throw Error("cannot delete null/undefined value from index");
+ }
+ 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) {
+ throw Error("db inconsistent: expected index entry missing");
+ }
+ const newPrimaryKeys = existingRecord.primaryKeys.filter((x) => compareKeys(x, primaryKey) !== 0);
+ if (newPrimaryKeys.length === 0) {
+ index.originalData = indexData.without(indexKey);
+ } else {
+ const newIndexRecord = {
+ indexKey,
+ primaryKeys: newPrimaryKeys,
+ }
+ index.modifiedData = indexData.with(indexKey, newIndexRecord, true);
+ }
+ }
}
async getRecords(
@@ -705,6 +811,18 @@ export class MemoryBackend implements Backend {
range = req.range;
}
+ if (typeof range !== "object") {
+ throw Error(
+ "getRecords was given an invalid range (sanity check failed, not an object)",
+ );
+ }
+
+ if (!("lowerOpen" in range)) {
+ throw Error(
+ "getRecords was given an invalid range (sanity check failed, lowerOpen missing)",
+ );
+ }
+
let numResults = 0;
let indexKeys: Key[] = [];
let primaryKeys: Key[] = [];
@@ -779,20 +897,21 @@ export class MemoryBackend implements Backend {
compareKeys(indexEntry.indexKey, req.lastIndexPosition) === 0
) {
let pos = forward ? 0 : indexEntry.primaryKeys.length - 1;
- console.log("number of primary keys", indexEntry.primaryKeys.length);
- console.log("start pos is", pos);
+ this.enableTracing &&
+ console.log("number of primary keys", indexEntry.primaryKeys.length);
+ this.enableTracing && console.log("start pos is", pos);
// Advance past the lastObjectStorePosition
do {
const cmpResult = compareKeys(
req.lastObjectStorePosition,
indexEntry.primaryKeys[pos],
);
- console.log("cmp result is", cmpResult);
+ this.enableTracing && console.log("cmp result is", cmpResult);
if ((forward && cmpResult < 0) || (!forward && cmpResult > 0)) {
break;
}
pos += forward ? 1 : -1;
- console.log("now pos is", pos);
+ this.enableTracing && console.log("now pos is", pos);
} while (pos >= 0 && pos < indexEntry.primaryKeys.length);
// Make sure we're at least at advancedPrimaryPos
@@ -815,8 +934,10 @@ export class MemoryBackend implements Backend {
primkeySubPos = forward ? 0 : indexEntry.primaryKeys.length - 1;
}
- console.log("subPos=", primkeySubPos);
- console.log("indexPos=", indexPos);
+ if (this.enableTracing) {
+ console.log("subPos=", primkeySubPos);
+ console.log("indexPos=", indexPos);
+ }
while (1) {
if (req.limit != 0 && numResults == req.limit) {
@@ -867,12 +988,16 @@ export class MemoryBackend implements Backend {
}
}
if (!skip) {
- console.log(`not skipping!, subPos=${primkeySubPos}`);
+ if (this.enableTracing) {
+ console.log(`not skipping!, subPos=${primkeySubPos}`);
+ }
indexKeys.push(indexEntry.indexKey);
primaryKeys.push(indexEntry.primaryKeys[primkeySubPos]);
numResults++;
} else {
- console.log("skipping!");
+ if (this.enableTracing) {
+ console.log("skipping!");
+ }
}
primkeySubPos += forward ? 1 : -1;
}
@@ -885,7 +1010,7 @@ export class MemoryBackend implements Backend {
if (!result) {
throw Error("invariant violated");
}
- values.push(result);
+ values.push(result.value);
}
}
} else {
@@ -905,7 +1030,9 @@ export class MemoryBackend implements Backend {
// 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);
- console.log("store entry:", storeEntry);
+ if (this.enableTracing) {
+ console.log("store entry:", storeEntry);
+ }
if (
!storeEntry ||
(req.lastObjectStorePosition !== undefined &&
@@ -915,7 +1042,9 @@ export class MemoryBackend implements Backend {
}
} else {
storePos = forward ? storeData.minKey() : storeData.maxKey();
- console.log("setting starting store store pos to", storePos);
+ if (this.enableTracing) {
+ console.log("setting starting store pos to", storePos);
+ }
}
while (1) {
@@ -940,7 +1069,7 @@ export class MemoryBackend implements Backend {
}
if (req.resultLevel >= ResultLevel.Full) {
- values.push(res);
+ values.push(res.value);
}
numResults++;
@@ -983,30 +1112,50 @@ export class MemoryBackend implements Backend {
const schema = myConn.modifiedSchema
? myConn.modifiedSchema
: db.committedSchema;
-
const objectStore = myConn.objectStoreMap[storeReq.objectStoreName];
- const storeKeyResult: StoreKeyResult = makeStoreKeyValue(
- storeReq.value,
- storeReq.key,
- objectStore.modifiedKeyGenerator || objectStore.originalKeyGenerator,
- schema.objectStores[storeReq.objectStoreName].autoIncrement,
- schema.objectStores[storeReq.objectStoreName].keyPath,
- );
- let key = storeKeyResult.key;
- let value = storeKeyResult.value;
- objectStore.modifiedKeyGenerator = storeKeyResult.updatedKeyGenerator;
-
if (!objectStore.modifiedData) {
objectStore.modifiedData = objectStore.originalData;
}
const modifiedData = objectStore.modifiedData;
- const hasKey = modifiedData.has(key);
- if (hasKey && !storeReq.overwrite) {
- throw Error("refusing to overwrite");
+
+ let key;
+ let value;
+
+ if (storeReq.storeLevel === StoreLevel.UpdateExisting) {
+ if (storeReq.key === null || storeReq.key === undefined) {
+ throw Error("invalid update request (key not given)");
+ }
+
+ if (!objectStore.modifiedData.has(storeReq.key)) {
+ throw Error("invalid update request (record does not exist)");
+ }
+ key = storeReq.key;
+ value = storeReq.value;
+ } else {
+ const storeKeyResult: StoreKeyResult = makeStoreKeyValue(
+ storeReq.value,
+ storeReq.key,
+ objectStore.modifiedKeyGenerator || objectStore.originalKeyGenerator,
+ schema.objectStores[storeReq.objectStoreName].autoIncrement,
+ schema.objectStores[storeReq.objectStoreName].keyPath,
+ );
+ key = storeKeyResult.key;
+ value = storeKeyResult.value;
+ objectStore.modifiedKeyGenerator = storeKeyResult.updatedKeyGenerator;
+ const hasKey = modifiedData.has(key);
+
+ if (hasKey && storeReq.storeLevel !== StoreLevel.AllowOverwrite) {
+ throw Error("refusing to overwrite");
+ }
}
- objectStore.modifiedData = modifiedData.with(key, value, true);
+ const objectStoreRecord: ObjectStoreRecord = {
+ primaryKey: key,
+ value: value,
+ };
+
+ objectStore.modifiedData = modifiedData.with(key, objectStoreRecord, true);
for (const indexName of schema.objectStores[storeReq.objectStoreName]
.indexes) {
diff --git a/packages/idb-bridge/src/backend-interface.ts b/packages/idb-bridge/src/backend-interface.ts
index ab093d9cb..7329ed96a 100644
--- a/packages/idb-bridge/src/backend-interface.ts
+++ b/packages/idb-bridge/src/backend-interface.ts
@@ -41,6 +41,12 @@ export enum ResultLevel {
Full,
}
+export enum StoreLevel {
+ NoOverwrite,
+ AllowOverwrite,
+ UpdateExisting,
+}
+
export interface RecordGetRequest {
direction: BridgeIDBCursorDirection;
objectStoreName: string;
@@ -94,7 +100,7 @@ export interface RecordStoreRequest {
objectStoreName: string;
value: Value;
key: Key | undefined;
- overwrite: boolean;
+ storeLevel: StoreLevel;
}
export interface Backend {
diff --git a/packages/idb-bridge/src/index.ts b/packages/idb-bridge/src/index.ts
new file mode 100644
index 000000000..a65458748
--- /dev/null
+++ b/packages/idb-bridge/src/index.ts
@@ -0,0 +1,60 @@
+import { BridgeIDBFactory } from "./BridgeIDBFactory";
+import { BridgeIDBCursor } from "./BridgeIDBCursor";
+import { BridgeIDBIndex } from "./BridgeIDBIndex";
+import BridgeIDBDatabase from "./BridgeIDBDatabase";
+import BridgeIDBKeyRange from "./BridgeIDBKeyRange";
+import BridgeIDBObjectStore from "./BridgeIDBObjectStore";
+import BridgeIDBOpenDBRequest from "./BridgeIDBOpenDBRequest";
+import BridgeIDBRequest from "./BridgeIDBRequest";
+import BridgeIDBTransaction from "./BridgeIDBTransaction";
+import BridgeIDBVersionChangeEvent from "./BridgeIDBVersionChangeEvent";
+
+export { BridgeIDBFactory, BridgeIDBCursor };
+
+export { MemoryBackend } from "./MemoryBackend";
+
+// globalThis polyfill, see https://mathiasbynens.be/notes/globalthis
+(function() {
+ if (typeof globalThis === "object") return;
+ Object.defineProperty(Object.prototype, "__magic__", {
+ get: function() {
+ return this;
+ },
+ configurable: true, // This makes it possible to `delete` the getter later.
+ });
+ // @ts-ignore: polyfill magic
+ __magic__.globalThis = __magic__; // lolwat
+ // @ts-ignore: polyfill magic
+ delete Object.prototype.__magic__;
+})();
+
+/**
+ * Populate the global name space such that the given IndexedDB factory is made
+ * available globally.
+ */
+export function shimIndexedDB(factory: BridgeIDBFactory): void {
+ // @ts-ignore: shimming
+ globalThis.indexedDB = factory;
+ // @ts-ignore: shimming
+ globalThis.IDBCursor = BridgeIDBCursor;
+ // @ts-ignore: shimming
+ globalThis.IDBKeyRange = BridgeIDBKeyRange;
+ // @ts-ignore: shimming
+ globalThis.IDBDatabase = BridgeIDBDatabase;
+ // @ts-ignore: shimming
+ globalThis.IDBFactory = BridgeIDBFactory;
+ // @ts-ignore: shimming
+ globalThis.IDBIndex = BridgeIDBIndex;
+ // @ts-ignore: shimming
+ globalThis.IDBKeyRange = BridgeIDBKeyRange;
+ // @ts-ignore: shimming
+ globalThis.IDBObjectStore = BridgeIDBObjectStore;
+ // @ts-ignore: shimming
+ globalThis.IDBOpenDBRequest = BridgeIDBOpenDBRequest;
+ // @ts-ignore: shimming
+ globalThis.IDBRequest = BridgeIDBRequest;
+ // @ts-ignore: shimming
+ globalThis.IDBTransaction = BridgeIDBTransaction;
+ // @ts-ignore: shimming
+ globalThis.IDBVersionChangeEvent = BridgeIDBVersionChangeEvent;
+}
diff --git a/packages/idb-bridge/src/util/FakeEventTarget.ts b/packages/idb-bridge/src/util/FakeEventTarget.ts
index f20432df0..025f21b4c 100644
--- a/packages/idb-bridge/src/util/FakeEventTarget.ts
+++ b/packages/idb-bridge/src/util/FakeEventTarget.ts
@@ -54,7 +54,6 @@ const invokeEventListeners = (event: FakeEvent, obj: FakeEventTarget) => {
continue;
}
- console.log(`invoking ${event.type} event listener`, listener);
// @ts-ignore
listener.callback.call(event.currentTarget, event);
}
@@ -81,7 +80,6 @@ const invokeEventListeners = (event: FakeEvent, obj: FakeEventTarget) => {
type: event.type,
};
if (!stopped(event, listener)) {
- console.log(`invoking on${event.type} event listener`, listener);
// @ts-ignore
listener.callback.call(event.currentTarget, event);
}
@@ -100,7 +98,7 @@ abstract class FakeEventTarget {
public readonly onupgradeneeded: EventCallback | null | undefined;
public readonly onversionchange: EventCallback | null | undefined;
- static enableTracing: boolean = true;
+ static enableTracing: boolean = false;
public addEventListener(
type: EventType,
diff --git a/packages/idb-bridge/src/util/makeStoreKeyValue.ts b/packages/idb-bridge/src/util/makeStoreKeyValue.ts
index 4f45e0d8a..845634ac2 100644
--- a/packages/idb-bridge/src/util/makeStoreKeyValue.ts
+++ b/packages/idb-bridge/src/util/makeStoreKeyValue.ts
@@ -18,7 +18,7 @@ export function makeStoreKeyValue(
autoIncrement: boolean,
keyPath: KeyPath | null,
): StoreKeyResult {
- const haveKey = key !== undefined && key !== null;
+ const haveKey = key !== null && key !== undefined;
const haveKeyPath = keyPath !== null && keyPath !== undefined;
// This models a decision table on (haveKey, haveKeyPath, autoIncrement)