From 9c85f6277bf85606eb4fbbca47f1a1b5404d2a2e Mon Sep 17 00:00:00 2001 From: Florian Dold Date: Tue, 23 Feb 2021 20:16:10 +0100 Subject: idb: implement missing methods --- packages/idb-bridge/src/MemoryBackend.ts | 39 ++++ packages/idb-bridge/src/backend-interface.ts | 5 + packages/idb-bridge/src/bridge-idb.ts | 299 ++++++++++++++++++++++++++- pnpm-lock.yaml | 55 ++++- 4 files changed, 387 insertions(+), 11 deletions(-) diff --git a/packages/idb-bridge/src/MemoryBackend.ts b/packages/idb-bridge/src/MemoryBackend.ts index 2317fb163..68f60f756 100644 --- a/packages/idb-bridge/src/MemoryBackend.ts +++ b/packages/idb-bridge/src/MemoryBackend.ts @@ -860,6 +860,45 @@ export class MemoryBackend implements Backend { }); } + async clearObjectStore( + btx: DatabaseTransaction, + objectStoreName: string, + ): Promise { + const myConn = this.requireConnectionFromTransaction(btx); + const db = this.databases[myConn.dbName]; + if (!db) { + throw Error("db not found"); + } + if (db.txLevel < TransactionLevel.Write) { + throw Error("only allowed in write transaction"); + } + if ( + db.txRestrictObjectStores && + !db.txRestrictObjectStores.includes(objectStoreName) + ) { + throw Error( + `Not allowed to access store '${objectStoreName}', transaction is over ${JSON.stringify( + db.txRestrictObjectStores, + )}`, + ); + } + + const schema = myConn.modifiedSchema; + const objectStoreMapEntry = myConn.objectStoreMap[objectStoreName]; + + objectStoreMapEntry.store.modifiedData = new BTree([], compareKeys); + + for (const indexName of Object.keys( + schema.objectStores[objectStoreName].indexes, + )) { + const index = myConn.objectStoreMap[objectStoreName].indexMap[indexName]; + if (!index) { + throw Error("index referenced by object store does not exist"); + } + index.modifiedData = new BTree([], compareKeys); + } + } + async deleteRecord( btx: DatabaseTransaction, objectStoreName: string, diff --git a/packages/idb-bridge/src/backend-interface.ts b/packages/idb-bridge/src/backend-interface.ts index 3d2953847..5ca70c8a4 100644 --- a/packages/idb-bridge/src/backend-interface.ts +++ b/packages/idb-bridge/src/backend-interface.ts @@ -216,4 +216,9 @@ export interface Backend { btx: DatabaseTransaction, storeReq: RecordStoreRequest, ): Promise; + + clearObjectStore( + btx: DatabaseTransaction, + objectStoreName: string, + ): Promise } diff --git a/packages/idb-bridge/src/bridge-idb.ts b/packages/idb-bridge/src/bridge-idb.ts index 744ad1aef..3c168674d 100644 --- a/packages/idb-bridge/src/bridge-idb.ts +++ b/packages/idb-bridge/src/bridge-idb.ts @@ -1234,11 +1234,48 @@ export class BridgeIDBIndex implements IDBIndex { query?: BridgeIDBKeyRange | IDBValidKey, count?: number, ): IDBRequest { - throw Error("not implemented"); + this._confirmIndexExists(); + this._confirmActiveTransaction(); + if (this._deleted) { + throw new InvalidStateError(); + } + + if (!(query instanceof BridgeIDBKeyRange)) { + query = BridgeIDBKeyRange._valueToKeyRange(query); + } + + if (count === undefined) { + count = -1; + } + + const getReq: RecordGetRequest = { + direction: "next", + indexName: this._name, + limit: count, + range: query, + objectStoreName: this._objectStore._name, + resultLevel: ResultLevel.Full, + }; + + const operation = async () => { + const { btx } = this._confirmStartedBackendTransaction(); + const result = await this._backend.getRecords(btx, getReq); + const values = result.values; + if (!values) { + throw Error("invariant violated"); + } + return values.map((x) => structuredRevive(x)); + }; + + return this._objectStore._transaction._execRequestAsync({ + operation, + source: this, + }); } // http://www.w3.org/TR/2015/REC-IndexedDB-20150108/#widl-IDBIndex-getKey-IDBRequest-any-key public getKey(key: BridgeIDBKeyRange | IDBValidKey) { + this._confirmIndexExists(); this._confirmActiveTransaction(); if (!(key instanceof BridgeIDBKeyRange)) { @@ -1278,11 +1315,45 @@ export class BridgeIDBIndex implements IDBIndex { query?: BridgeIDBKeyRange | IDBValidKey, count?: number, ): IDBRequest { - throw Error("not implemented"); + this._confirmIndexExists(); + this._confirmActiveTransaction(); + + if (!(query instanceof BridgeIDBKeyRange)) { + query = BridgeIDBKeyRange._valueToKeyRange(query); + } + + if (count === undefined) { + count = -1; + } + + const getReq: RecordGetRequest = { + direction: "next", + indexName: this._name, + limit: count, + range: query, + objectStoreName: this._objectStore._name, + resultLevel: ResultLevel.OnlyKeys, + }; + + const operation = async () => { + const { btx } = this._confirmStartedBackendTransaction(); + const result = await this._backend.getRecords(btx, getReq); + const primaryKeys = result.primaryKeys; + if (!primaryKeys) { + throw Error("invariant violated"); + } + return primaryKeys; + }; + + return this._objectStore._transaction._execRequestAsync({ + operation, + source: this, + }); } // http://www.w3.org/TR/2015/REC-IndexedDB-20150108/#widl-IDBIndex-count-IDBRequest-any-key public count(key: BridgeIDBKeyRange | IDBValidKey | null | undefined) { + this._confirmIndexExists(); this._confirmActiveTransaction(); if (key === null) { @@ -1718,14 +1789,147 @@ export class BridgeIDBObjectStore implements IDBObjectStore { query?: BridgeIDBKeyRange | IDBValidKey, count?: number, ): IDBRequest { - throw Error("not implemented"); + if (BridgeIDBFactory.enableTracing) { + console.log(`getting from object store ${this._name} key ${query}`); + } + + if (arguments.length === 0) { + throw new TypeError(); + } + + if (!this._transaction._active) { + throw new TransactionInactiveError(); + } + + if (this._deleted) { + throw new InvalidStateError( + "tried to call 'delete' on a deleted object store", + ); + } + + if (count === undefined) { + count = -1; + } + + let keyRange: BridgeIDBKeyRange; + + if (query instanceof BridgeIDBKeyRange) { + keyRange = query; + } else { + try { + keyRange = BridgeIDBKeyRange.only(valueToKey(query)); + } catch (e) { + throw new DataError( + `invalid key (type ${typeof query}) for object store '${this._name}'`, + ); + } + } + + const recordRequest: RecordGetRequest = { + objectStoreName: this._name, + indexName: undefined, + lastIndexPosition: undefined, + lastObjectStorePosition: undefined, + direction: "next", + limit: count, + resultLevel: ResultLevel.Full, + range: keyRange, + }; + + const operation = async () => { + if (BridgeIDBFactory.enableTracing) { + console.log("running getAll operation:", recordRequest); + } + const { btx } = this._confirmStartedBackendTransaction(); + const result = await this._backend.getRecords(btx, recordRequest); + + if (BridgeIDBFactory.enableTracing) { + console.log("get operation result count:", result.count); + } + const values = result.values; + if (!values) { + throw Error("invariant violated"); + } + return values.map((x) => structuredRevive(x)); + }; + + return this._transaction._execRequestAsync({ + operation, + source: this, + }); } // http://w3c.github.io/IndexedDB/#dom-idbobjectstore-getkey public getKey( - key?: BridgeIDBKeyRange | IDBValidKey, + query?: BridgeIDBKeyRange | IDBValidKey, ): IDBRequest { - throw Error("not implemented"); + if (arguments.length === 0) { + throw new TypeError(); + } + + if (!this._transaction._active) { + throw new TransactionInactiveError(); + } + + if (this._deleted) { + throw new InvalidStateError( + "tried to call 'delete' on a deleted object store", + ); + } + + let keyRange: BridgeIDBKeyRange; + + if (query instanceof BridgeIDBKeyRange) { + keyRange = query; + } else { + try { + keyRange = BridgeIDBKeyRange.only(valueToKey(query)); + } catch (e) { + throw new DataError( + `invalid key (type ${typeof query}) for object store '${this._name}'`, + ); + } + } + + const recordRequest: RecordGetRequest = { + objectStoreName: this._name, + indexName: undefined, + lastIndexPosition: undefined, + lastObjectStorePosition: undefined, + direction: "next", + limit: 1, + resultLevel: ResultLevel.OnlyKeys, + range: keyRange, + }; + + const operation = async () => { + if (BridgeIDBFactory.enableTracing) { + console.log("running get operation:", recordRequest); + } + const { btx } = this._confirmStartedBackendTransaction(); + const result = await this._backend.getRecords(btx, recordRequest); + + if (BridgeIDBFactory.enableTracing) { + console.log("get operation result count:", result.count); + } + + if (result.count === 0) { + return undefined; + } + const primaryKeys = result.primaryKeys; + if (!primaryKeys) { + throw Error("invariant violated"); + } + if (primaryKeys.length !== 1) { + throw Error("invariant violated"); + } + return structuredRevive(primaryKeys[0]); + }; + + return this._transaction._execRequestAsync({ + operation, + source: this, + }); } // http://w3c.github.io/IndexedDB/#dom-idbobjectstore-getallkeys @@ -1733,11 +1937,86 @@ export class BridgeIDBObjectStore implements IDBObjectStore { query?: BridgeIDBKeyRange | IDBValidKey, count?: number, ): IDBRequest { - throw Error("not implemented"); + if (arguments.length === 0) { + throw new TypeError(); + } + + if (!this._transaction._active) { + throw new TransactionInactiveError(); + } + + if (this._deleted) { + throw new InvalidStateError( + "tried to call 'delete' on a deleted object store", + ); + } + + if (count === undefined) { + count = -1; + } + + let keyRange: BridgeIDBKeyRange; + + if (query instanceof BridgeIDBKeyRange) { + keyRange = query; + } else { + try { + keyRange = BridgeIDBKeyRange.only(valueToKey(query)); + } catch (e) { + throw new DataError( + `invalid key (type ${typeof query}) for object store '${this._name}'`, + ); + } + } + + const recordRequest: RecordGetRequest = { + objectStoreName: this._name, + indexName: undefined, + lastIndexPosition: undefined, + lastObjectStorePosition: undefined, + direction: "next", + limit: count, + resultLevel: ResultLevel.OnlyKeys, + range: keyRange, + }; + + const operation = async () => { + const { btx } = this._confirmStartedBackendTransaction(); + const result = await this._backend.getRecords(btx, recordRequest); + + const primaryKeys = result.primaryKeys; + if (!primaryKeys) { + throw Error("invariant violated"); + } + return primaryKeys.map((x) => structuredRevive(x)); + }; + + return this._transaction._execRequestAsync({ + operation, + source: this, + }); } - public clear(): IDBRequest { - throw Error("not implemented"); + public clear(): IDBRequest { + if (!this._transaction._active) { + throw new TransactionInactiveError(); + } + + if (this._deleted) { + throw new InvalidStateError( + "tried to call 'delete' on a deleted object store", + ); + } + + const operation = async () => { + const { btx } = this._confirmStartedBackendTransaction(); + await this._backend.clearObjectStore(btx, this._name); + }; + + return this._transaction._execRequestAsync({ + operation, + source: this, + }); } public openCursor( @@ -2228,12 +2507,12 @@ export class BridgeIDBTransaction if (this._db._upgradeTransaction) { for (const os of this._usedObjectStores) { if (os._justCreated) { - os._deleted = true + os._deleted = true; } } for (const ind of this._usedIndexes) { if (ind._justCreated) { - ind._deleted = true + ind._deleted = true; } } } diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 70a458397..be4eac3a4 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -7,6 +7,9 @@ importers: typeson: 5.18.2 typeson-registry: 1.0.0-alpha.38 devDependencies: + '@rollup/plugin-commonjs': 17.1.0_rollup@2.37.1 + '@rollup/plugin-json': 4.1.0_rollup@2.37.1 + '@rollup/plugin-node-resolve': 11.2.0_rollup@2.37.1 '@types/node': 14.14.22 ava: 3.15.0 esm: 3.2.25 @@ -15,6 +18,9 @@ importers: rollup: 2.37.1 typescript: 4.1.3 specifiers: + '@rollup/plugin-commonjs': ^17.1.0 + '@rollup/plugin-json': ^4.1.0 + '@rollup/plugin-node-resolve': ^11.2.0 '@types/node': ^14.14.22 ava: ^3.15.0 esm: ^3.2.25 @@ -520,6 +526,23 @@ packages: rollup: ^2.30.0 resolution: integrity: sha512-/omBIJG1nHQc+bgkYDuLpb/V08QyutP9amOrJRUSlYJZP+b/68gM//D8sxJe3Yry2QnYIr3QjR3x4AlxJEN3GA== + /@rollup/plugin-commonjs/17.1.0_rollup@2.37.1: + dependencies: + '@rollup/pluginutils': 3.1.0_rollup@2.37.1 + commondir: 1.0.1 + estree-walker: 2.0.2 + glob: 7.1.6 + is-reference: 1.2.1 + magic-string: 0.25.7 + resolve: 1.20.0 + rollup: 2.37.1 + dev: true + engines: + node: '>= 8.0.0' + peerDependencies: + rollup: ^2.30.0 + resolution: + integrity: sha512-PoMdXCw0ZyvjpCMT5aV4nkL0QywxP29sODQsSGeDpr/oI49Qq9tRtAsb/LbYbDzFlOydVEqHmmZWFtXJEAX9ew== /@rollup/plugin-json/4.1.0_rollup@2.37.1: dependencies: '@rollup/pluginutils': 3.1.0_rollup@2.37.1 @@ -545,6 +568,22 @@ packages: rollup: ^1.20.0||^2.0.0 resolution: integrity: sha512-ouBBppRdWJKCllDXGzJ7ZIkYbaq+5TmyP0smt1vdJCFfoZhLi31vhpmjLhyo8lreHf4RoeSNllaWrvSqHpHRog== + /@rollup/plugin-node-resolve/11.2.0_rollup@2.37.1: + dependencies: + '@rollup/pluginutils': 3.1.0_rollup@2.37.1 + '@types/resolve': 1.17.1 + builtin-modules: 3.2.0 + deepmerge: 4.2.2 + is-module: 1.0.0 + resolve: 1.20.0 + rollup: 2.37.1 + dev: true + engines: + node: '>= 10.0.0' + peerDependencies: + rollup: ^1.20.0||^2.0.0 + resolution: + integrity: sha512-qHjNIKYt5pCcn+5RUBQxK8krhRvf1HnyVgUCcFFcweDS7fhkOLZeYh0mhHK6Ery8/bb9tvN/ubPzmfF0qjDCTA== /@rollup/plugin-replace/2.3.4_rollup@2.37.1: dependencies: '@rollup/pluginutils': 3.1.0_rollup@2.37.1 @@ -684,6 +723,10 @@ packages: /@types/node/14.14.22: resolution: integrity: sha512-g+f/qj/cNcqKkc3tFqlXOYjrmZA+jNBiDzbP3kH+B+otKFqAdPgVTGP1IeKRdMml/aE69as5S4FqtxAbl+LaMw== + /@types/node/14.14.31: + dev: true + resolution: + integrity: sha512-vFHy/ezP5qI0rFgJ7aQnjDXwAMrG0KqqIH7tQG5PPv3BWBayOPIQNBjVc/P6hhdZfMx51REc6tfDNXHUio893g== /@types/normalize-package-data/2.4.0: dev: true resolution: @@ -707,7 +750,7 @@ packages: integrity: sha512-aj/L7RIMsRlWML3YB6KZiXB3fV2t41+5RBGYF8z+tAKU43Px8C3cYUZsDvf1/+Bm4FK21QWBrDutu8ZJ/70qOw== /@types/resolve/1.17.1: dependencies: - '@types/node': 14.14.22 + '@types/node': 14.14.31 dev: true resolution: integrity: sha512-yy7HuzQhj0dhGpD8RLXSZWEkLsV9ibvxvi6EiJ3bkqLAO1RGo0WbkWQiwpRlSFymTJRz0d3k5LM3kkx8ArDbLw== @@ -3296,6 +3339,7 @@ packages: resolution: integrity: sha1-QVxEePK8wwEgwizhDtMib30+GOA= /lodash.sortby/4.7.0: + dev: false resolution: integrity: sha1-7dFMgk4sycHgsKG0K7UhBRakJDg= /lodash/4.17.20: @@ -4239,6 +4283,13 @@ packages: dev: true resolution: integrity: sha512-rArEXAgsBG4UgRGcynxWIWKFvh/XZCcS8UJdHhwy91zwAvCZIbcs+vAbflgBnNjYMs/i/i+/Ux6IZhML1yPvxg== + /resolve/1.20.0: + dependencies: + is-core-module: 2.2.0 + path-parse: 1.0.6 + dev: true + resolution: + integrity: sha512-wENBPt4ySzg4ybFQW2TT1zMQucPK95HSh/nq2CFTZVOGut2+pQvSsgtda4d26YrYcr067wjbmzOG8byDPBX63A== /responselike/1.0.2: dependencies: lowercase-keys: 1.0.1 @@ -4781,6 +4832,7 @@ packages: /tr46/2.0.2: dependencies: punycode: 2.1.1 + dev: false engines: node: '>=8' resolution: @@ -5015,6 +5067,7 @@ packages: resolution: integrity: sha1-8LDc+RW8X/FSivrbLA4XtTLaL+g= /webidl-conversions/6.1.0: + dev: false engines: node: '>=10.4' resolution: -- cgit v1.2.3