From 4452984a24334e3b7afb60e3db9dc12db02d65ba Mon Sep 17 00:00:00 2001 From: Florian Dold Date: Mon, 8 Feb 2021 15:23:44 +0100 Subject: idb-bridge: remove cyclic dependencies, rip out api extractor --- packages/idb-bridge/api-extractor.json | 131 -- packages/idb-bridge/package.json | 11 +- packages/idb-bridge/src/BridgeIDBCursor.ts | 364 ---- .../idb-bridge/src/BridgeIDBCursorWithValue.ts | 48 - packages/idb-bridge/src/BridgeIDBDatabase.ts | 249 --- packages/idb-bridge/src/BridgeIDBFactory.ts | 226 --- packages/idb-bridge/src/BridgeIDBIndex.ts | 319 --- packages/idb-bridge/src/BridgeIDBKeyRange.ts | 131 -- packages/idb-bridge/src/BridgeIDBObjectStore.ts | 467 ----- packages/idb-bridge/src/BridgeIDBOpenDBRequest.ts | 35 - packages/idb-bridge/src/BridgeIDBRequest.ts | 86 - packages/idb-bridge/src/BridgeIDBTransaction.ts | 326 ---- .../idb-bridge/src/BridgeIDBVersionChangeEvent.ts | 39 - packages/idb-bridge/src/MemoryBackend.test.ts | 14 +- packages/idb-bridge/src/MemoryBackend.ts | 28 +- packages/idb-bridge/src/backend-interface.ts | 47 +- packages/idb-bridge/src/bridge-idb.ts | 2053 ++++++++++++++++++++ packages/idb-bridge/src/index.ts | 53 +- packages/idb-bridge/src/tree/b+tree.ts | 2 +- packages/idb-bridge/src/util/FakeEvent.ts | 11 +- packages/idb-bridge/src/util/FakeEventTarget.ts | 3 +- packages/idb-bridge/src/util/canInjectKey.ts | 4 +- packages/idb-bridge/src/util/deepEquals.ts | 2 +- packages/idb-bridge/src/util/extractKey.ts | 6 +- packages/idb-bridge/src/util/fakeDOMStringList.ts | 10 +- packages/idb-bridge/src/util/getIndexKeys.ts | 12 +- packages/idb-bridge/src/util/injectKey.ts | 10 +- packages/idb-bridge/src/util/makeStoreKeyValue.ts | 14 +- packages/idb-bridge/src/util/types.ts | 84 - packages/idb-bridge/src/util/validateKeyPath.ts | 35 +- packages/idb-bridge/src/util/valueToKey.ts | 7 +- packages/idb-bridge/tsconfig.json | 1 + 32 files changed, 2195 insertions(+), 2633 deletions(-) delete mode 100644 packages/idb-bridge/api-extractor.json delete mode 100644 packages/idb-bridge/src/BridgeIDBCursor.ts delete mode 100644 packages/idb-bridge/src/BridgeIDBCursorWithValue.ts delete mode 100644 packages/idb-bridge/src/BridgeIDBDatabase.ts delete mode 100644 packages/idb-bridge/src/BridgeIDBFactory.ts delete mode 100644 packages/idb-bridge/src/BridgeIDBIndex.ts delete mode 100644 packages/idb-bridge/src/BridgeIDBKeyRange.ts delete mode 100644 packages/idb-bridge/src/BridgeIDBObjectStore.ts delete mode 100644 packages/idb-bridge/src/BridgeIDBOpenDBRequest.ts delete mode 100644 packages/idb-bridge/src/BridgeIDBRequest.ts delete mode 100644 packages/idb-bridge/src/BridgeIDBTransaction.ts delete mode 100644 packages/idb-bridge/src/BridgeIDBVersionChangeEvent.ts create mode 100644 packages/idb-bridge/src/bridge-idb.ts delete mode 100644 packages/idb-bridge/src/util/types.ts (limited to 'packages/idb-bridge') diff --git a/packages/idb-bridge/api-extractor.json b/packages/idb-bridge/api-extractor.json deleted file mode 100644 index f57d81d8c..000000000 --- a/packages/idb-bridge/api-extractor.json +++ /dev/null @@ -1,131 +0,0 @@ -/** - * Config file for API Extractor. For more info, please visit: https://api-extractor.com - */ -{ - "$schema": "https://developer.microsoft.com/json-schemas/api-extractor/v7/api-extractor.schema.json", - - /** - * (REQUIRED) Specifies the .d.ts file to be used as the starting point for analysis. API Extractor - * analyzes the symbols exported by this module. - * - * The file extension must be ".d.ts" and not ".ts". - * - * The path is resolved relative to the folder of the config file that contains the setting; to change this, - * prepend a folder token such as "". - * - * SUPPORTED TOKENS: , , - */ - "mainEntryPointFilePath": "/lib/index.d.ts", - - /** - * A list of NPM package names whose exports should be treated as part of this package. - * - * For example, suppose that Webpack is used to generate a distributed bundle for the project "library1", - * and another NPM package "library2" is embedded in this bundle. Some types from library2 may become part - * of the exported API for library1, but by default API Extractor would generate a .d.ts rollup that explicitly - * imports library2. To avoid this, we can specify: - * - * "bundledPackages": [ "library2" ], - * - * This would direct API Extractor to embed those types directly in the .d.ts rollup, as if they had been - * local files for library1. - */ - "bundledPackages": [], - - /** - * Configures how the API report file (*.api.md) will be generated. - */ - "apiReport": { - /** - * (REQUIRED) Whether to generate an API report. - */ - "enabled": false - }, - - /** - * Configures how the doc model file (*.api.json) will be generated. - */ - "docModel": { - /** - * (REQUIRED) Whether to generate a doc model file. - */ - "enabled": false - }, - - /** - * Configures how the .d.ts rollup file will be generated. - */ - "dtsRollup": { - /** - * (REQUIRED) Whether to generate the .d.ts rollup file. - */ - "enabled": true - }, - - /** - * Configures how the tsdoc-metadata.json file will be generated. - */ - "tsdocMetadata": { - /** - * Whether to generate the tsdoc-metadata.json file. - * - * DEFAULT VALUE: true - */ - "enabled": false - }, - - /** - * Specifies what type of newlines API Extractor should use when writing output files. By default, the output files - * will be written with Windows-style newlines. To use POSIX-style newlines, specify "lf" instead. - * To use the OS's default newline kind, specify "os". - * - * DEFAULT VALUE: "crlf" - */ - "newlineKind": "lf", - - /** - * Configures how API Extractor reports error and warning messages produced during analysis. - * - * There are three sources of messages: compiler messages, API Extractor messages, and TSDoc messages. - */ - "messages": { - /** - * Configures handling of diagnostic messages reported by the TypeScript compiler engine while analyzing - * the input .d.ts files. - * - * TypeScript message identifiers start with "TS" followed by an integer. For example: "TS2551" - * - * DEFAULT VALUE: A single "default" entry with logLevel=warning. - */ - "compilerMessageReporting": { - /** - * Configures the default routing for messages that don't match an explicit rule in this table. - */ - "default": { - "logLevel": "warning" - } - }, - - /** - * Configures handling of messages reported by API Extractor during its analysis. - * - * API Extractor message identifiers start with "ae-". For example: "ae-extra-release-tag" - * - * DEFAULT VALUE: See api-extractor-defaults.json for the complete table of extractorMessageReporting mappings - */ - "extractorMessageReporting": { - "default": { - "logLevel": "warning" - } - }, - - /** - * Configures handling of messages reported by the TSDoc parser when analyzing code comments. - */ - "tsdocMessageReporting": { - "default": { - "logLevel": "warning" - } - } - } -} diff --git a/packages/idb-bridge/package.json b/packages/idb-bridge/package.json index f8be82987..cfa0fa9f6 100644 --- a/packages/idb-bridge/package.json +++ b/packages/idb-bridge/package.json @@ -4,19 +4,19 @@ "description": "IndexedDB implementation that uses SQLite3 as storage", "main": "./dist/idb-bridge.js", "module": "./lib/index.js", - "types": "./dist/idb-bridge.d.ts", + "types": "./lib/index.d.ts", "author": "Florian Dold", "license": "AGPL-3.0-or-later", "private": false, "scripts": { "test": "tsc && ava", - "prepare": "tsc && rollup -c && api-extractor run", - "compile": "tsc && rollup -c && api-extractor run", + "prepare": "tsc && rollup -c", + "compile": "tsc && rollup -c", "clean": "rimraf dist lib tsconfig.tsbuildinfo", "pretty": "prettier --write src" }, "devDependencies": { - "@microsoft/api-extractor": "^7.13.0", + "@types/node": "^14.14.22", "ava": "^3.15.0", "esm": "^3.2.25", "prettier": "^2.2.1", @@ -25,7 +25,6 @@ "typescript": "^4.1.3" }, "dependencies": { - "@types/node": "^14.14.22", "tslib": "^2.1.0" }, "ava": { @@ -33,4 +32,4 @@ "esm" ] } -} \ No newline at end of file +} diff --git a/packages/idb-bridge/src/BridgeIDBCursor.ts b/packages/idb-bridge/src/BridgeIDBCursor.ts deleted file mode 100644 index a18c0bebc..000000000 --- a/packages/idb-bridge/src/BridgeIDBCursor.ts +++ /dev/null @@ -1,364 +0,0 @@ -/* - - Copyright 2017 Jeremy Scheff - Copyright 2019 Florian Dold - - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express - or implied. See the License for the specific language governing - permissions and limitations under the License. - */ - -import { BridgeIDBKeyRange } from "./BridgeIDBKeyRange"; -import { BridgeIDBObjectStore } from "./BridgeIDBObjectStore"; -import { BridgeIDBRequest } from "./BridgeIDBRequest"; -import compareKeys from "./util/cmp"; -import { - DataError, - InvalidStateError, - ReadOnlyError, - TransactionInactiveError, -} from "./util/errors"; -import structuredClone from "./util/structuredClone"; -import { - CursorRange, - CursorSource, - Key, - Value, - BridgeIDBCursorDirection, -} from "./util/types"; -import valueToKey from "./util/valueToKey"; -import { - RecordGetRequest, - ResultLevel, - Backend, - RecordStoreRequest, - StoreLevel, -} from "./backend-interface"; -import { BridgeIDBFactory } from "./BridgeIDBFactory"; - -/** - * http://www.w3.org/TR/2015/REC-IndexedDB-20150108/#cursor - * - * @public - */ -export class BridgeIDBCursor { - _request: BridgeIDBRequest | undefined; - - private _gotValue: boolean = false; - private _range: CursorRange; - private _indexPosition = undefined; // Key of previously returned record - private _objectStorePosition = undefined; - private _keyOnly: boolean; - - private _source: CursorSource; - private _direction: BridgeIDBCursorDirection; - private _key = undefined; - private _primaryKey: Key | undefined = undefined; - private _indexName: string | undefined; - private _objectStoreName: string; - - protected _value: Value = undefined; - - constructor( - source: CursorSource, - objectStoreName: string, - indexName: string | undefined, - range: CursorRange, - direction: BridgeIDBCursorDirection, - request: BridgeIDBRequest, - keyOnly: boolean, - ) { - this._indexName = indexName; - this._objectStoreName = objectStoreName; - this._range = range; - this._source = source; - this._direction = direction; - this._request = request; - this._keyOnly = keyOnly; - } - - get _effectiveObjectStore(): BridgeIDBObjectStore { - if (this.source instanceof BridgeIDBObjectStore) { - return this.source; - } - return this.source.objectStore; - } - - get _backend(): Backend { - return this._source._backend; - } - - // Read only properties - get source() { - return this._source; - } - set source(val) { - /* For babel */ - } - - get direction() { - return this._direction; - } - set direction(val) { - /* For babel */ - } - - get key() { - return this._key; - } - set key(val) { - /* For babel */ - } - - 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 { - 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, - lastIndexPosition: this._indexPosition, - lastObjectStorePosition: this._objectStorePosition, - limit: 1, - range: this._range, - objectStoreName: this._objectStoreName, - advanceIndexKey: key, - advancePrimaryKey: primaryKey, - resultLevel: this._keyOnly ? ResultLevel.OnlyKeys : ResultLevel.Full, - }; - - const { btx } = this.source._confirmActiveTransaction(); - - let response = await this._backend.getRecords(btx, recordGetRequest); - - if (response.count === 0) { - if (BridgeIDBFactory.enableTracing) { - console.log("cursor is returning empty result"); - } - this._gotValue = false; - return null; - } - - if (response.count !== 1) { - throw Error("invariant failed"); - } - - 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]; - } else { - this._key = response.primaryKeys![0]; - } - - this._primaryKey = response.primaryKeys![0]; - - if (!this._keyOnly) { - this._value = response.values![0]; - } - - this._gotValue = true; - this._objectStorePosition = structuredClone(response.primaryKeys![0]); - if (response.indexKeys !== undefined && response.indexKeys.length > 0) { - this._indexPosition = structuredClone(response.indexKeys[0]); - } - - return this; - } - - // http://www.w3.org/TR/2015/REC-IndexedDB-20150108/#widl-IDBCursor-update-IDBRequest-any-value - public update(value: Value) { - if (value === undefined) { - throw new TypeError(); - } - - const transaction = this._effectiveObjectStore.transaction; - - if (transaction._state !== "active") { - throw new TransactionInactiveError(); - } - - if (transaction.mode === "readonly") { - throw new ReadOnlyError(); - } - - if (this._effectiveObjectStore._deleted) { - throw new InvalidStateError(); - } - - if ( - !(this.source instanceof BridgeIDBObjectStore) && - this.source._deleted - ) { - throw new InvalidStateError(); - } - - if (!this._gotValue || !this._isValueCursor) { - throw new InvalidStateError(); - } - - const storeReq: RecordStoreRequest = { - 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(); - await this._backend.storeRecord(btx, storeReq); - }; - return transaction._execRequestAsync({ - operation, - source: this, - }); - } - - /** - * http://www.w3.org/TR/2015/REC-IndexedDB-20150108/#widl-IDBCursor-advance-void-unsigned-long-count - */ - public advance(count: number) { - throw Error("not implemented"); - } - - /** - * http://www.w3.org/TR/2015/REC-IndexedDB-20150108/#widl-IDBCursor-continue-void-any-key - */ - public continue(key?: Key) { - const transaction = this._effectiveObjectStore.transaction; - - if (transaction._state !== "active") { - throw new TransactionInactiveError(); - } - - if (this._effectiveObjectStore._deleted) { - throw new InvalidStateError(); - } - if ( - !(this.source instanceof BridgeIDBObjectStore) && - this.source._deleted - ) { - throw new InvalidStateError(); - } - - if (!this._gotValue) { - throw new InvalidStateError(); - } - - if (key !== undefined) { - key = valueToKey(key); - let lastKey = - this._indexName === undefined - ? this._objectStorePosition - : this._indexPosition; - - const cmpResult = compareKeys(key, lastKey); - - if ( - (cmpResult <= 0 && - (this.direction === "next" || this.direction === "nextunique")) || - (cmpResult >= 0 && - (this.direction === "prev" || this.direction === "prevunique")) - ) { - throw new DataError(); - } - } - - if (this._request) { - this._request.readyState = "pending"; - } - - const operation = async () => { - return this._iterate(key); - }; - - transaction._execRequestAsync({ - operation, - request: this._request, - source: this.source, - }); - - this._gotValue = false; - } - - // https://w3c.github.io/IndexedDB/#dom-idbcursor-continueprimarykey - public continuePrimaryKey(key: Key, primaryKey: Key) { - throw Error("not implemented"); - } - - public delete() { - const transaction = this._effectiveObjectStore.transaction; - - if (transaction._state !== "active") { - throw new TransactionInactiveError(); - } - - if (transaction.mode === "readonly") { - throw new ReadOnlyError(); - } - - if (this._effectiveObjectStore._deleted) { - throw new InvalidStateError(); - } - if ( - !(this.source instanceof BridgeIDBObjectStore) && - this.source._deleted - ) { - throw new InvalidStateError(); - } - - if (!this._gotValue || !this._isValueCursor) { - throw new InvalidStateError(); - } - - const operation = async () => { - const { btx } = this.source._confirmActiveTransaction(); - this._backend.deleteRecord( - btx, - this._objectStoreName, - BridgeIDBKeyRange._valueToKeyRange(this._primaryKey), - ); - }; - - return transaction._execRequestAsync({ - operation, - source: this, - }); - } - - public toString() { - return "[object IDBCursor]"; - } -} diff --git a/packages/idb-bridge/src/BridgeIDBCursorWithValue.ts b/packages/idb-bridge/src/BridgeIDBCursorWithValue.ts deleted file mode 100644 index 8561879cf..000000000 --- a/packages/idb-bridge/src/BridgeIDBCursorWithValue.ts +++ /dev/null @@ -1,48 +0,0 @@ -/* - Copyright 2017 Jeremy Scheff - - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express - or implied. See the License for the specific language governing - permissions and limitations under the License. - */ - -import { BridgeIDBCursor } from "./BridgeIDBCursor"; -import { - CursorRange, - CursorSource, - BridgeIDBCursorDirection, - Value, -} from "./util/types"; - -export class BridgeIDBCursorWithValue extends BridgeIDBCursor { - 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]"; - } -} diff --git a/packages/idb-bridge/src/BridgeIDBDatabase.ts b/packages/idb-bridge/src/BridgeIDBDatabase.ts deleted file mode 100644 index ffd897f9c..000000000 --- a/packages/idb-bridge/src/BridgeIDBDatabase.ts +++ /dev/null @@ -1,249 +0,0 @@ -/* - * Copyright 2017 Jeremy Scheff - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express - * or implied. See the License for the specific language governing - * permissions and limitations under the License. - */ - -import { BridgeIDBTransaction } from "./BridgeIDBTransaction"; -import { - ConstraintError, - InvalidAccessError, - InvalidStateError, - NotFoundError, - TransactionInactiveError, -} from "./util/errors"; -import fakeDOMStringList from "./util/fakeDOMStringList"; -import FakeEventTarget from "./util/FakeEventTarget"; -import { FakeDOMStringList, KeyPath, TransactionMode } from "./util/types"; -import validateKeyPath from "./util/validateKeyPath"; -import queueTask from "./util/queueTask"; -import { - Backend, - DatabaseConnection, - Schema, - DatabaseTransaction, -} from "./backend-interface"; -import { BridgeIDBObjectStore } from "./BridgeIDBObjectStore"; - -/** - * Ensure that an active version change transaction is currently running. - */ -const confirmActiveVersionchangeTransaction = (database: BridgeIDBDatabase) => { - if (!database._runningVersionchangeTransaction) { - throw new InvalidStateError(); - } - - // Find the latest versionchange transaction - const transactions = database._transactions.filter( - (tx: BridgeIDBTransaction) => { - return tx.mode === "versionchange"; - }, - ); - const transaction = transactions[transactions.length - 1]; - - if (!transaction || transaction._state === "finished") { - throw new InvalidStateError(); - } - - if (transaction._state !== "active") { - throw new TransactionInactiveError(); - } - - return transaction; -}; - -// http://www.w3.org/TR/2015/REC-IndexedDB-20150108/#database-interface -/** @public */ -export class BridgeIDBDatabase extends FakeEventTarget { - _closePending = false; - _closed = false; - _runningVersionchangeTransaction = false; - _transactions: Array = []; - - _backendConnection: DatabaseConnection; - _backend: Backend; - - _schema: Schema; - - get name(): string { - return this._schema.databaseName; - } - - get version(): number { - return this._schema.databaseVersion; - } - - get objectStoreNames(): FakeDOMStringList { - return fakeDOMStringList(Object.keys(this._schema.objectStores)).sort(); - } - - /** - * http://www.w3.org/TR/2015/REC-IndexedDB-20150108/#database-closing-steps - */ - _closeConnection() { - this._closePending = true; - - const transactionsComplete = this._transactions.every( - (transaction: BridgeIDBTransaction) => { - return transaction._state === "finished"; - }, - ); - - if (transactionsComplete) { - this._closed = true; - this._backend.close(this._backendConnection); - } else { - queueTask(() => { - this._closeConnection(); - }); - } - } - - constructor(backend: Backend, backendConnection: DatabaseConnection) { - super(); - - this._schema = backend.getSchema(backendConnection); - - this._backend = backend; - this._backendConnection = backendConnection; - } - - // http://w3c.github.io/IndexedDB/#dom-idbdatabase-createobjectstore - public createObjectStore( - name: string, - options: { autoIncrement?: boolean; keyPath?: KeyPath } | null = {}, - ): BridgeIDBObjectStore { - if (name === undefined) { - throw new TypeError(); - } - const transaction = confirmActiveVersionchangeTransaction(this); - const backendTx = transaction._backendTransaction; - if (!backendTx) { - throw Error("invariant violated"); - } - - const keyPath = - options !== null && options.keyPath !== undefined - ? options.keyPath - : null; - const autoIncrement = - options !== null && options.autoIncrement !== undefined - ? options.autoIncrement - : false; - - if (keyPath !== null) { - validateKeyPath(keyPath); - } - - if (Object.keys(this._schema.objectStores).includes(name)) { - throw new ConstraintError(); - } - - if (autoIncrement && (keyPath === "" || Array.isArray(keyPath))) { - throw new InvalidAccessError(); - } - - transaction._backend.createObjectStore( - backendTx, - name, - keyPath, - autoIncrement, - ); - - this._schema = this._backend.getSchema(this._backendConnection); - - return transaction.objectStore(name); - } - - public deleteObjectStore(name: string): void { - if (name === undefined) { - throw new TypeError(); - } - const transaction = confirmActiveVersionchangeTransaction(this); - transaction._objectStoresCache.delete(name); - } - - public _internalTransaction( - storeNames: string | string[], - mode?: TransactionMode, - backendTransaction?: DatabaseTransaction, - ): BridgeIDBTransaction { - mode = mode !== undefined ? mode : "readonly"; - if ( - mode !== "readonly" && - mode !== "readwrite" && - mode !== "versionchange" - ) { - 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) { - throw new InvalidStateError(); - } - - if (this._closePending) { - throw new InvalidStateError(); - } - - if (!Array.isArray(storeNames)) { - storeNames = [storeNames]; - } - if (storeNames.length === 0 && mode !== "versionchange") { - throw new InvalidAccessError(); - } - for (const storeName of storeNames) { - if (this.objectStoreNames.indexOf(storeName) < 0) { - throw new NotFoundError( - "No objectStore named " + storeName + " in this database", - ); - } - } - - const tx = new BridgeIDBTransaction( - storeNames, - mode, - this, - backendTransaction, - ); - this._transactions.push(tx); - queueTask(() => tx._start()); - return tx; - } - - public transaction( - storeNames: string | string[], - mode?: TransactionMode, - ): BridgeIDBTransaction { - if (mode === "versionchange") { - throw new TypeError("Invalid mode: " + mode); - } - return this._internalTransaction(storeNames, mode); - } - - public close() { - this._closeConnection(); - } - - public toString() { - return "[object IDBDatabase]"; - } -} diff --git a/packages/idb-bridge/src/BridgeIDBFactory.ts b/packages/idb-bridge/src/BridgeIDBFactory.ts deleted file mode 100644 index 7954cdd99..000000000 --- a/packages/idb-bridge/src/BridgeIDBFactory.ts +++ /dev/null @@ -1,226 +0,0 @@ -/* - * Copyright 2017 Jeremy Scheff - * Copyright 2019 Florian Dold - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express - * or implied. See the License for the specific language governing - * permissions and limitations under the License. - */ - -import { BridgeIDBDatabase } from "./BridgeIDBDatabase"; -import { BridgeIDBOpenDBRequest } from "./BridgeIDBOpenDBRequest"; -import { BridgeIDBVersionChangeEvent } from "./BridgeIDBVersionChangeEvent"; -import compareKeys from "./util/cmp"; -import enforceRange from "./util/enforceRange"; -import { AbortError, VersionError } from "./util/errors"; -import FakeEvent from "./util/FakeEvent"; -import { Backend, DatabaseConnection } from "./backend-interface"; -import queueTask from "./util/queueTask"; - -/** @public */ -export type DatabaseList = Array<{ name: string; version: number }>; - -/** @public */ -export class BridgeIDBFactory { - public cmp = compareKeys; - private backend: Backend; - private connections: BridgeIDBDatabase[] = []; - static enableTracing: boolean = false; - - public constructor(backend: Backend) { - this.backend = backend; - } - - // http://www.w3.org/TR/2015/REC-IndexedDB-20150108/#widl-IDBFactory-deleteDatabase-IDBOpenDBRequest-DOMString-name - public deleteDatabase(name: string): BridgeIDBOpenDBRequest { - const request = new BridgeIDBOpenDBRequest(); - request.source = null; - - queueTask(async () => { - const databases = await this.backend.getDatabases(); - const dbInfo = databases.find((x) => x.name == name); - if (!dbInfo) { - // Database already doesn't exist, success! - const event = new BridgeIDBVersionChangeEvent("success", { - newVersion: null, - oldVersion: 0, - }); - request.dispatchEvent(event); - return; - } - const oldVersion = dbInfo.version; - - try { - const dbconn = await this.backend.connectDatabase(name); - const backendTransaction = await this.backend.enterVersionChange( - dbconn, - 0, - ); - await this.backend.deleteDatabase(backendTransaction, name); - await this.backend.commit(backendTransaction); - await this.backend.close(dbconn); - - request.result = undefined; - request.readyState = "done"; - - const event2 = new BridgeIDBVersionChangeEvent("success", { - newVersion: null, - oldVersion, - }); - request.dispatchEvent(event2); - } catch (err) { - request.error = new Error(); - request.error.name = err.name; - request.readyState = "done"; - - const event = new FakeEvent("error", { - bubbles: true, - cancelable: true, - }); - event.eventPath = []; - request.dispatchEvent(event); - } - }); - - return request; - } - - // tslint:disable-next-line max-line-length - // http://www.w3.org/TR/2015/REC-IndexedDB-20150108/#widl-IDBFactory-open-IDBOpenDBRequest-DOMString-name-unsigned-long-long-version - public open(name: string, version?: number) { - if (arguments.length > 1 && version !== undefined) { - // Based on spec, not sure why "MAX_SAFE_INTEGER" instead of "unsigned long long", but it's needed to pass - // tests - version = enforceRange(version, "MAX_SAFE_INTEGER"); - } - if (version === 0) { - throw new TypeError(); - } - - const request = new BridgeIDBOpenDBRequest(); - - queueTask(async () => { - let dbconn: DatabaseConnection; - try { - dbconn = await this.backend.connectDatabase(name); - } catch (err) { - request._finishWithError(err); - return; - } - - const schema = this.backend.getSchema(dbconn); - const existingVersion = schema.databaseVersion; - - if (version === undefined) { - version = existingVersion !== 0 ? existingVersion : 1; - } - - const requestedVersion = version; - - BridgeIDBFactory.enableTracing && - console.log( - `TRACE: existing version ${existingVersion}, requested version ${requestedVersion}`, - ); - - if (existingVersion > requestedVersion) { - request._finishWithError(new VersionError()); - return; - } - - const db = new BridgeIDBDatabase(this.backend, dbconn); - - if (existingVersion == requestedVersion) { - request.result = db; - request.readyState = "done"; - - const event2 = new FakeEvent("success", { - bubbles: false, - cancelable: false, - }); - event2.eventPath = [request]; - request.dispatchEvent(event2); - } - - if (existingVersion < requestedVersion) { - // http://www.w3.org/TR/2015/REC-IndexedDB-20150108/#dfn-steps-for-running-a-versionchange-transaction - - for (const otherConn of this.connections) { - const event = new BridgeIDBVersionChangeEvent("versionchange", { - newVersion: version, - oldVersion: existingVersion, - }); - otherConn.dispatchEvent(event); - } - - if (this._anyOpen()) { - const event = new BridgeIDBVersionChangeEvent("blocked", { - newVersion: version, - oldVersion: existingVersion, - }); - request.dispatchEvent(event); - } - - const backendTransaction = await this.backend.enterVersionChange( - dbconn, - requestedVersion, - ); - db._runningVersionchangeTransaction = true; - - const transaction = db._internalTransaction( - [], - "versionchange", - backendTransaction, - ); - const event = new BridgeIDBVersionChangeEvent("upgradeneeded", { - newVersion: version, - oldVersion: existingVersion, - }); - - request.result = db; - request.readyState = "done"; - request.transaction = transaction; - request.dispatchEvent(event); - - 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); - return db; - }); - - return request; - } - - // https://w3c.github.io/IndexedDB/#dom-idbfactory-databases - public databases(): Promise { - return this.backend.getDatabases(); - } - - public toString(): string { - return "[object IDBFactory]"; - } - - private _anyOpen(): boolean { - return this.connections.some((c) => !c._closed && !c._closePending); - } -} diff --git a/packages/idb-bridge/src/BridgeIDBIndex.ts b/packages/idb-bridge/src/BridgeIDBIndex.ts deleted file mode 100644 index 9b214a234..000000000 --- a/packages/idb-bridge/src/BridgeIDBIndex.ts +++ /dev/null @@ -1,319 +0,0 @@ -/* - Copyright 2017 Jeremy Scheff - Copyright 2019 Florian Dold - - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express - or implied. See the License for the specific language governing - permissions and limitations under the License. - */ - -import { BridgeIDBCursor } from "./BridgeIDBCursor"; -import { BridgeIDBCursorWithValue } from "./BridgeIDBCursorWithValue"; -import { BridgeIDBKeyRange } from "./BridgeIDBKeyRange"; -import { BridgeIDBObjectStore } from "./BridgeIDBObjectStore"; -import { BridgeIDBRequest } from "./BridgeIDBRequest"; -import { - ConstraintError, - InvalidStateError, - TransactionInactiveError, -} from "./util/errors"; -import { BridgeIDBCursorDirection, Key, KeyPath } from "./util/types"; -import valueToKey from "./util/valueToKey"; -import { BridgeIDBTransaction } from "./BridgeIDBTransaction"; -import { - Schema, - Backend, - DatabaseTransaction, - RecordGetRequest, - ResultLevel, -} from "./backend-interface"; - -const confirmActiveTransaction = ( - index: BridgeIDBIndex, -): BridgeIDBTransaction => { - if (index._deleted || index.objectStore._deleted) { - throw new InvalidStateError(); - } - - if (index.objectStore.transaction._state !== "active") { - throw new TransactionInactiveError(); - } - - return index.objectStore.transaction; -}; - -// http://www.w3.org/TR/2015/REC-IndexedDB-20150108/#idl-def-IDBIndex -/** @public */ -export class BridgeIDBIndex { - objectStore: BridgeIDBObjectStore; - - get _schema(): Schema { - return this.objectStore.transaction.db._schema; - } - - get keyPath(): KeyPath { - return this._schema.objectStores[this.objectStore.name].indexes[this._name] - .keyPath; - } - - get multiEntry(): boolean { - return this._schema.objectStores[this.objectStore.name].indexes[this._name] - .multiEntry; - } - - get unique(): boolean { - return this._schema.objectStores[this.objectStore.name].indexes[this._name] - .unique; - } - - get _backend(): Backend { - return this.objectStore._backend; - } - - _confirmActiveTransaction(): { btx: DatabaseTransaction } { - return this.objectStore._confirmActiveTransaction(); - } - - private _name: string; - - public _deleted: boolean = false; - - constructor(objectStore: BridgeIDBObjectStore, name: string) { - this._name = name; - this.objectStore = objectStore; - } - - get name() { - return this._name; - } - - // https://w3c.github.io/IndexedDB/#dom-idbindex-name - set name(name: any) { - const transaction = this.objectStore.transaction; - - if (!transaction.db._runningVersionchangeTransaction) { - throw new InvalidStateError(); - } - - if (transaction._state !== "active") { - throw new TransactionInactiveError(); - } - - const { btx } = this._confirmActiveTransaction(); - - const oldName = this._name; - const newName = String(name); - - if (newName === oldName) { - return; - } - - this._backend.renameIndex(btx, this.objectStore.name, oldName, newName); - - if (this.objectStore.indexNames.indexOf(name) >= 0) { - throw new ConstraintError(); - } - } - - // tslint:disable-next-line max-line-length - // http://www.w3.org/TR/2015/REC-IndexedDB-20150108/#widl-IDBIndex-openCursor-IDBRequest-any-range-IDBCursorDirection-direction - public openCursor( - range?: BridgeIDBKeyRange | Key | null | undefined, - direction: BridgeIDBCursorDirection = "next", - ) { - confirmActiveTransaction(this); - - if (range === null) { - range = undefined; - } - if (range !== undefined && !(range instanceof BridgeIDBKeyRange)) { - range = BridgeIDBKeyRange.only(valueToKey(range)); - } - - const request = new BridgeIDBRequest(); - request.source = this; - request.transaction = this.objectStore.transaction; - - const cursor = new BridgeIDBCursorWithValue( - this, - this.objectStore.name, - this._name, - range, - direction, - request, - ); - - const operation = async () => { - return cursor._iterate(); - }; - - return this.objectStore.transaction._execRequestAsync({ - operation, - request, - source: this, - }); - } - - // tslint:disable-next-line max-line-length - // http://www.w3.org/TR/2015/REC-IndexedDB-20150108/#widl-IDBIndex-openKeyCursor-IDBRequest-any-range-IDBCursorDirection-direction - public openKeyCursor( - range?: BridgeIDBKeyRange | Key | null | undefined, - direction: BridgeIDBCursorDirection = "next", - ) { - confirmActiveTransaction(this); - - if (range === null) { - range = undefined; - } - if (range !== undefined && !(range instanceof BridgeIDBKeyRange)) { - range = BridgeIDBKeyRange.only(valueToKey(range)); - } - - const request = new BridgeIDBRequest(); - request.source = this; - request.transaction = this.objectStore.transaction; - - const cursor = new BridgeIDBCursor( - this, - this.objectStore.name, - this._name, - range, - direction, - request, - true, - ); - - return this.objectStore.transaction._execRequestAsync({ - operation: cursor._iterate.bind(cursor), - request, - source: this, - }); - } - - public get(key: BridgeIDBKeyRange | Key) { - confirmActiveTransaction(this); - - if (!(key instanceof BridgeIDBKeyRange)) { - key = BridgeIDBKeyRange._valueToKeyRange(key); - } - - const getReq: RecordGetRequest = { - direction: "next", - indexName: this._name, - limit: 1, - range: key, - objectStoreName: this.objectStore._name, - resultLevel: ResultLevel.Full, - }; - - const operation = async () => { - const { btx } = this._confirmActiveTransaction(); - const result = await this._backend.getRecords(btx, getReq); - if (result.count == 0) { - return undefined; - } - const values = result.values; - if (!values) { - throw Error("invariant violated"); - } - return values[0]; - }; - - return this.objectStore.transaction._execRequestAsync({ - operation, - source: this, - }); - } - - // http://w3c.github.io/IndexedDB/#dom-idbindex-getall - public getAll(query?: BridgeIDBKeyRange | Key, count?: number) { - throw Error("not implemented"); - } - - // http://www.w3.org/TR/2015/REC-IndexedDB-20150108/#widl-IDBIndex-getKey-IDBRequest-any-key - public getKey(key: BridgeIDBKeyRange | Key) { - confirmActiveTransaction(this); - - if (!(key instanceof BridgeIDBKeyRange)) { - key = BridgeIDBKeyRange._valueToKeyRange(key); - } - - const getReq: RecordGetRequest = { - direction: "next", - indexName: this._name, - limit: 1, - range: key, - objectStoreName: this.objectStore._name, - resultLevel: ResultLevel.OnlyKeys, - }; - - const operation = async () => { - const { btx } = this._confirmActiveTransaction(); - const result = await this._backend.getRecords(btx, getReq); - if (result.count == 0) { - return undefined; - } - const primaryKeys = result.primaryKeys; - if (!primaryKeys) { - throw Error("invariant violated"); - } - return primaryKeys[0]; - }; - - return this.objectStore.transaction._execRequestAsync({ - operation, - source: this, - }); - } - - // http://w3c.github.io/IndexedDB/#dom-idbindex-getallkeys - public getAllKeys(query?: BridgeIDBKeyRange | Key, count?: number) { - throw Error("not implemented"); - } - - // http://www.w3.org/TR/2015/REC-IndexedDB-20150108/#widl-IDBIndex-count-IDBRequest-any-key - public count(key: BridgeIDBKeyRange | Key | null | undefined) { - confirmActiveTransaction(this); - - if (key === null) { - key = undefined; - } - if (key !== undefined && !(key instanceof BridgeIDBKeyRange)) { - key = BridgeIDBKeyRange.only(valueToKey(key)); - } - - const getReq: RecordGetRequest = { - direction: "next", - indexName: this._name, - limit: 1, - range: key, - objectStoreName: this.objectStore._name, - resultLevel: ResultLevel.OnlyCount, - }; - - const operation = async () => { - const { btx } = this._confirmActiveTransaction(); - const result = await this._backend.getRecords(btx, getReq); - return result.count; - }; - - return this.objectStore.transaction._execRequestAsync({ - operation, - source: this, - }); - } - - public toString() { - return "[object IDBIndex]"; - } -} - -export default BridgeIDBIndex; diff --git a/packages/idb-bridge/src/BridgeIDBKeyRange.ts b/packages/idb-bridge/src/BridgeIDBKeyRange.ts deleted file mode 100644 index d2eaa2d1b..000000000 --- a/packages/idb-bridge/src/BridgeIDBKeyRange.ts +++ /dev/null @@ -1,131 +0,0 @@ -/* - Copyright 2019 Florian Dold - Copyright 2017 Jeremy Scheff - - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express - or implied. See the License for the specific language governing - permissions and limitations under the License. - */ - -import compareKeys from "./util/cmp"; -import { DataError } from "./util/errors"; -import { Key } from "./util/types"; -import valueToKey from "./util/valueToKey"; - -// http://www.w3.org/TR/2015/REC-IndexedDB-20150108/#range-concept -/** @public */ -export class BridgeIDBKeyRange { - public static only(value: Key) { - if (arguments.length === 0) { - throw new TypeError(); - } - value = valueToKey(value); - return new BridgeIDBKeyRange(value, value, false, false); - } - - static lowerBound(lower: Key, open: boolean = false) { - if (arguments.length === 0) { - throw new TypeError(); - } - lower = valueToKey(lower); - return new BridgeIDBKeyRange(lower, undefined, open, true); - } - - static upperBound(upper: Key, open: boolean = false) { - if (arguments.length === 0) { - throw new TypeError(); - } - upper = valueToKey(upper); - return new BridgeIDBKeyRange(undefined, upper, true, open); - } - - static bound( - lower: Key, - upper: Key, - lowerOpen: boolean = false, - upperOpen: boolean = false, - ) { - if (arguments.length < 2) { - throw new TypeError(); - } - - const cmpResult = compareKeys(lower, upper); - if (cmpResult === 1 || (cmpResult === 0 && (lowerOpen || upperOpen))) { - throw new DataError(); - } - - lower = valueToKey(lower); - upper = valueToKey(upper); - return new BridgeIDBKeyRange(lower, upper, lowerOpen, upperOpen); - } - - readonly lower: Key | undefined; - readonly upper: Key | undefined; - readonly lowerOpen: boolean; - readonly upperOpen: boolean; - - constructor( - lower: Key | undefined, - upper: Key | undefined, - lowerOpen: boolean, - upperOpen: boolean, - ) { - this.lower = lower; - this.upper = upper; - this.lowerOpen = lowerOpen; - this.upperOpen = upperOpen; - } - - // https://w3c.github.io/IndexedDB/#dom-idbkeyrange-includes - includes(key: Key) { - if (arguments.length === 0) { - throw new TypeError(); - } - key = valueToKey(key); - - if (this.lower !== undefined) { - const cmpResult = compareKeys(this.lower, key); - - if (cmpResult === 1 || (cmpResult === 0 && this.lowerOpen)) { - return false; - } - } - if (this.upper !== undefined) { - const cmpResult = compareKeys(this.upper, key); - - if (cmpResult === -1 || (cmpResult === 0 && this.upperOpen)) { - return false; - } - } - return true; - } - - toString() { - return "[object IDBKeyRange]"; - } - - static _valueToKeyRange(value: any, nullDisallowedFlag: boolean = false) { - if (value instanceof BridgeIDBKeyRange) { - return value; - } - - if (value === null || value === undefined) { - if (nullDisallowedFlag) { - throw new DataError(); - } - return new BridgeIDBKeyRange(undefined, undefined, false, false); - } - - const key = valueToKey(value); - - return BridgeIDBKeyRange.only(key); - } -} diff --git a/packages/idb-bridge/src/BridgeIDBObjectStore.ts b/packages/idb-bridge/src/BridgeIDBObjectStore.ts deleted file mode 100644 index 6fdf35200..000000000 --- a/packages/idb-bridge/src/BridgeIDBObjectStore.ts +++ /dev/null @@ -1,467 +0,0 @@ -/* - Copyright 2019 Florian Dold - Copyright 2017 Jeremy Scheff - - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express - or implied. See the License for the specific language governing - permissions and limitations under the License. - */ - -import { BridgeIDBCursor } from "./BridgeIDBCursor"; -import { BridgeIDBCursorWithValue } from "./BridgeIDBCursorWithValue"; -import { BridgeIDBIndex } from "./BridgeIDBIndex"; -import { BridgeIDBKeyRange } from "./BridgeIDBKeyRange"; -import { BridgeIDBRequest } from "./BridgeIDBRequest"; -import { BridgeIDBTransaction } from "./BridgeIDBTransaction"; - -import { - ConstraintError, - InvalidAccessError, - InvalidStateError, - ReadOnlyError, -} from "./util/errors"; -import fakeDOMStringList from "./util/fakeDOMStringList"; -import { - FakeDOMStringList, - BridgeIDBCursorDirection, - Key, - KeyPath, - Value, -} from "./util/types"; -import validateKeyPath from "./util/validateKeyPath"; -import valueToKey from "./util/valueToKey"; -import { - DatabaseTransaction, - RecordGetRequest, - ResultLevel, - StoreLevel, - Schema, - Backend, - DatabaseConnection, -} from "./backend-interface"; -import { BridgeIDBFactory } from "./BridgeIDBFactory"; - -// http://www.w3.org/TR/2015/REC-IndexedDB-20150108/#object-store -/** @public */ -export class BridgeIDBObjectStore { - _indexesCache: Map = new Map(); - - transaction: BridgeIDBTransaction; - - get autoIncrement(): boolean { - return this._schema.objectStores[this._name].autoIncrement; - } - - get indexNames(): FakeDOMStringList { - return fakeDOMStringList( - Object.keys(this._schema.objectStores[this._name].indexes), - ).sort(); - } - - get keyPath(): KeyPath | null { - return this._schema.objectStores[this._name].keyPath; - } - - _name: string; - - get _schema(): Schema { - return this.transaction.db._schema; - } - - _deleted: boolean = false; - - constructor(transaction: BridgeIDBTransaction, name: string) { - this._name = name; - this.transaction = transaction; - } - - get name() { - return this._name; - } - - get _backend(): Backend { - return this.transaction.db._backend; - } - - get _backendConnection(): DatabaseConnection { - return this.transaction.db._backendConnection; - } - - _confirmActiveTransaction(): { btx: DatabaseTransaction } { - const btx = this.transaction._backendTransaction; - if (!btx) { - throw new InvalidStateError(); - } - return { btx }; - } - - // http://w3c.github.io/IndexedDB/#dom-idbobjectstore-name - set name(newName: any) { - const transaction = this.transaction; - - if (!transaction.db._runningVersionchangeTransaction) { - throw new InvalidStateError(); - } - - let { btx } = this._confirmActiveTransaction(); - - newName = String(newName); - - const oldName = this._name; - - if (newName === oldName) { - return; - } - - this._backend.renameObjectStore(btx, oldName, newName); - this.transaction.db._schema = this._backend.getSchema( - this._backendConnection, - ); - } - - 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(); - } - const operation = async () => { - const { btx } = this._confirmActiveTransaction(); - const result = await this._backend.storeRecord(btx, { - objectStoreName: this._name, - key: key, - value: value, - storeLevel: overwrite - ? StoreLevel.AllowOverwrite - : StoreLevel.NoOverwrite, - }); - return result.key; - }; - - return this.transaction._execRequestAsync({ operation, source: this }); - } - - public put(value: Value, key?: Key) { - if (arguments.length === 0) { - throw new TypeError(); - } - return this._store(value, key, true); - } - - public add(value: Value, key?: Key) { - if (arguments.length === 0) { - throw new TypeError(); - } - return this._store(value, key, false); - } - - public delete(key: Key | BridgeIDBKeyRange) { - if (arguments.length === 0) { - throw new TypeError(); - } - - if (this.transaction.mode === "readonly") { - throw new ReadOnlyError(); - } - - 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, keyRange); - }; - - return this.transaction._execRequestAsync({ - operation, - source: this, - }); - } - - 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(); - } - - let keyRange: BridgeIDBKeyRange; - - if (key instanceof BridgeIDBKeyRange) { - keyRange = key; - } else { - try { - keyRange = BridgeIDBKeyRange.only(valueToKey(key)); - } catch (e) { - throw Error( - `invalid key (type ${typeof key}) for object store ${this._name}`, - ); - } - } - - const recordRequest: RecordGetRequest = { - objectStoreName: this._name, - indexName: undefined, - lastIndexPosition: undefined, - lastObjectStorePosition: undefined, - direction: "next", - limit: 1, - resultLevel: ResultLevel.Full, - 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 (BridgeIDBFactory.enableTracing) { - console.log("get operation result count:", result.count); - } - - if (result.count === 0) { - return undefined; - } - const values = result.values; - if (!values) { - throw Error("invariant violated"); - } - return values[0]; - }; - - return this.transaction._execRequestAsync({ - operation, - source: this, - }); - } - - // http://w3c.github.io/IndexedDB/#dom-idbobjectstore-getall - public getAll(query?: BridgeIDBKeyRange | Key, count?: number) { - throw Error("not implemented"); - } - - // http://w3c.github.io/IndexedDB/#dom-idbobjectstore-getkey - public getKey(key?: BridgeIDBKeyRange | Key) { - throw Error("not implemented"); - } - - // http://w3c.github.io/IndexedDB/#dom-idbobjectstore-getallkeys - public getAllKeys(query?: BridgeIDBKeyRange | Key, count?: number) { - throw Error("not implemented"); - } - - public clear() { - throw Error("not implemented"); - } - - public openCursor( - range?: BridgeIDBKeyRange | Key, - direction: BridgeIDBCursorDirection = "next", - ) { - if (range === null) { - range = undefined; - } - if (range !== undefined && !(range instanceof BridgeIDBKeyRange)) { - range = BridgeIDBKeyRange.only(valueToKey(range)); - } - - const request = new BridgeIDBRequest(); - request.source = this; - request.transaction = this.transaction; - - const cursor = new BridgeIDBCursorWithValue( - this, - this._name, - undefined, - range, - direction, - request, - ); - - return this.transaction._execRequestAsync({ - operation: () => cursor._iterate(), - request, - source: this, - }); - } - - public openKeyCursor( - range?: BridgeIDBKeyRange | Key, - direction?: BridgeIDBCursorDirection, - ) { - if (range === null) { - range = undefined; - } - if (range !== undefined && !(range instanceof BridgeIDBKeyRange)) { - range = BridgeIDBKeyRange.only(valueToKey(range)); - } - - if (!direction) { - direction = "next"; - } - - const request = new BridgeIDBRequest(); - request.source = this; - request.transaction = this.transaction; - - const cursor = new BridgeIDBCursor( - this, - this._name, - undefined, - range, - direction, - request, - true, - ); - - return this.transaction._execRequestAsync({ - operation: cursor._iterate.bind(cursor), - request, - source: this, - }); - } - - // tslint:disable-next-line max-line-length - // http://www.w3.org/TR/2015/REC-IndexedDB-20150108/#widl-IDBObjectStore-createIndex-IDBIndex-DOMString-name-DOMString-sequence-DOMString--keyPath-IDBIndexParameters-optionalParameters - public createIndex( - indexName: string, - keyPath: KeyPath, - optionalParameters: { multiEntry?: boolean; unique?: boolean } = {}, - ) { - if (arguments.length < 2) { - throw new TypeError(); - } - - if (!this.transaction.db._runningVersionchangeTransaction) { - throw new InvalidStateError(); - } - - const { btx } = this._confirmActiveTransaction(); - - const multiEntry = - optionalParameters.multiEntry !== undefined - ? optionalParameters.multiEntry - : false; - const unique = - optionalParameters.unique !== undefined - ? optionalParameters.unique - : false; - - if (this.transaction.mode !== "versionchange") { - throw new InvalidStateError(); - } - - if (this.indexNames.indexOf(indexName) >= 0) { - throw new ConstraintError(); - } - - validateKeyPath(keyPath); - - if (Array.isArray(keyPath) && multiEntry) { - throw new InvalidAccessError(); - } - - this._backend.createIndex( - btx, - indexName, - this._name, - keyPath, - multiEntry, - unique, - ); - - return new BridgeIDBIndex(this, indexName); - } - - // https://w3c.github.io/IndexedDB/#dom-idbobjectstore-index - public index(name: string) { - if (arguments.length === 0) { - throw new TypeError(); - } - - if (this.transaction._state === "finished") { - throw new InvalidStateError(); - } - - const index = this._indexesCache.get(name); - if (index !== undefined) { - return index; - } - - return new BridgeIDBIndex(this, name); - } - - public deleteIndex(indexName: string) { - if (arguments.length === 0) { - throw new TypeError(); - } - - if (this.transaction.mode !== "versionchange") { - throw new InvalidStateError(); - } - - if (!this.transaction.db._runningVersionchangeTransaction) { - throw new InvalidStateError(); - } - - const { btx } = this._confirmActiveTransaction(); - - const index = this._indexesCache.get(indexName); - if (index !== undefined) { - index._deleted = true; - } - - this._backend.deleteIndex(btx, this._name, indexName); - } - - // http://www.w3.org/TR/2015/REC-IndexedDB-20150108/#widl-IDBObjectStore-count-IDBRequest-any-key - public count(key?: Key | BridgeIDBKeyRange) { - if (key === null) { - key = undefined; - } - if (key !== undefined && !(key instanceof BridgeIDBKeyRange)) { - key = BridgeIDBKeyRange.only(valueToKey(key)); - } - - const recordGetRequest: RecordGetRequest = { - direction: "next", - indexName: undefined, - lastIndexPosition: undefined, - limit: -1, - objectStoreName: this._name, - lastObjectStorePosition: undefined, - range: key, - resultLevel: ResultLevel.OnlyCount, - }; - - const operation = async () => { - const { btx } = this._confirmActiveTransaction(); - const result = await this._backend.getRecords(btx, recordGetRequest); - return result.count; - }; - - return this.transaction._execRequestAsync({ operation, source: this }); - } - - public toString() { - return "[object IDBObjectStore]"; - } -} diff --git a/packages/idb-bridge/src/BridgeIDBOpenDBRequest.ts b/packages/idb-bridge/src/BridgeIDBOpenDBRequest.ts deleted file mode 100644 index 306edcb7d..000000000 --- a/packages/idb-bridge/src/BridgeIDBOpenDBRequest.ts +++ /dev/null @@ -1,35 +0,0 @@ -/* - Copyright 2019 Florian Dold - Copyright 2017 Jeremy Scheff - - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express - or implied. See the License for the specific language governing - permissions and limitations under the License. -*/ - -import { BridgeIDBRequest } from "./BridgeIDBRequest"; -import { EventListener } from "./idbtypes"; - -/** @public */ -export class BridgeIDBOpenDBRequest extends BridgeIDBRequest { - public onupgradeneeded: EventListener | null = null; - public onblocked: EventListener | null = null; - - constructor() { - super(); - // https://www.w3.org/TR/IndexedDB/#open-requests - this.source = null; - } - - public toString() { - return "[object IDBOpenDBRequest]"; - } -} diff --git a/packages/idb-bridge/src/BridgeIDBRequest.ts b/packages/idb-bridge/src/BridgeIDBRequest.ts deleted file mode 100644 index 4800a0582..000000000 --- a/packages/idb-bridge/src/BridgeIDBRequest.ts +++ /dev/null @@ -1,86 +0,0 @@ -/* - * Copyright 2017 Jeremy Scheff - * Copyright 2019 Florian Dold - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express - * or implied. See the License for the specific language governing - * permissions and limitations under the License. - */ - -import { BridgeIDBCursor as BridgeFIBCursor } from "./BridgeIDBCursor"; -import { BridgeIDBIndex } from "./BridgeIDBIndex"; -import { BridgeIDBObjectStore } from "./BridgeIDBObjectStore"; -import { BridgeIDBTransaction } from "./BridgeIDBTransaction"; -import { InvalidStateError } from "./util/errors"; -import FakeEventTarget from "./util/FakeEventTarget"; -import FakeEvent from "./util/FakeEvent"; -import { EventListener } from "./idbtypes"; - -/** @public */ -export class BridgeIDBRequest extends FakeEventTarget { - _result: any = null; - _error: Error | null | undefined = null; - source: BridgeFIBCursor | BridgeIDBIndex | BridgeIDBObjectStore | null = null; - transaction: BridgeIDBTransaction | null = null; - readyState: "done" | "pending" = "pending"; - onsuccess: EventListener | null = null; - onerror: EventListener | null = null; - - get error() { - if (this.readyState === "pending") { - throw new InvalidStateError(); - } - return this._error; - } - - set error(value: any) { - this._error = value; - } - - get result() { - if (this.readyState === "pending") { - throw new InvalidStateError(); - } - return this._result; - } - - set result(value: any) { - this._result = value; - } - - toString() { - return "[object IDBRequest]"; - } - - _finishWithError(err: Error) { - this.result = undefined; - this.readyState = "done"; - - this.error = new Error(err.message); - this.error.name = err.name; - - const event = new FakeEvent("error", { - bubbles: true, - cancelable: true, - }); - event.eventPath = []; - this.dispatchEvent(event); - } - - _finishWithResult(result: any) { - this.result = result; - this.readyState = "done"; - - const event = new FakeEvent("success"); - event.eventPath = []; - this.dispatchEvent(event); - } -} diff --git a/packages/idb-bridge/src/BridgeIDBTransaction.ts b/packages/idb-bridge/src/BridgeIDBTransaction.ts deleted file mode 100644 index b064d069e..000000000 --- a/packages/idb-bridge/src/BridgeIDBTransaction.ts +++ /dev/null @@ -1,326 +0,0 @@ -import { BridgeIDBDatabase } from "./BridgeIDBDatabase"; -import { BridgeIDBObjectStore } from "./BridgeIDBObjectStore"; -import { BridgeIDBRequest } from "./BridgeIDBRequest"; -import { - AbortError, - InvalidStateError, - NotFoundError, - TransactionInactiveError, -} from "./util/errors"; -import fakeDOMStringList from "./util/fakeDOMStringList"; -import FakeEvent from "./util/FakeEvent"; -import FakeEventTarget from "./util/FakeEventTarget"; -import { FakeDOMStringList, RequestObj, TransactionMode } from "./util/types"; -import queueTask from "./util/queueTask"; -import openPromise from "./util/openPromise"; -import { DatabaseTransaction, Backend } from "./backend-interface"; -import { BridgeIDBFactory } from "./BridgeIDBFactory"; -import { EventListener } from "./idbtypes"; - -// http://www.w3.org/TR/2015/REC-IndexedDB-20150108/#transaction -/** @public */ -export class BridgeIDBTransaction extends FakeEventTarget { - public _state: "active" | "inactive" | "committing" | "finished" = "active"; - public _started = false; - public _objectStoresCache: Map = new Map(); - - public _backendTransaction?: DatabaseTransaction; - - public objectStoreNames: FakeDOMStringList; - public mode: TransactionMode; - public db: BridgeIDBDatabase; - public error: Error | null = null; - public onabort: EventListener | null = null; - public oncomplete: EventListener | null = null; - public onerror: EventListener | null = null; - - private _waitPromise: Promise; - private _resolveWait: () => void; - - public _scope: Set; - private _requests: Array<{ - operation: () => void; - request: BridgeIDBRequest; - }> = []; - - get _backend(): Backend { - return this.db._backend; - } - - constructor( - storeNames: string[], - mode: TransactionMode, - db: BridgeIDBDatabase, - backendTransaction?: DatabaseTransaction, - ) { - super(); - - const myOpenPromise = openPromise(); - this._waitPromise = myOpenPromise.promise; - this._resolveWait = myOpenPromise.resolve; - - this._scope = new Set(storeNames); - this._backendTransaction = backendTransaction; - this.mode = mode; - this.db = db; - this.objectStoreNames = fakeDOMStringList(Array.from(this._scope).sort()); - - this.db._transactions.push(this); - } - - // http://www.w3.org/TR/2015/REC-IndexedDB-20150108/#dfn-steps-for-aborting-a-transaction - async _abort(errName: string | null) { - this._state = "finished"; - - if (errName !== null) { - const e = new Error(); - e.name = errName; - this.error = e; - } - - // Should this directly remove from _requests? - for (const { request } of this._requests) { - 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); - } - } - } - - // Only roll back if we actually executed the scheduled operations. - const maybeBtx = this._backendTransaction; - if (maybeBtx) { - await this._backend.rollback(maybeBtx); - } - - queueTask(() => { - const event = new FakeEvent("abort", { - bubbles: true, - cancelable: false, - }); - event.eventPath = [this.db]; - this.dispatchEvent(event); - }); - } - - public abort() { - if (this._state === "committing" || this._state === "finished") { - throw new InvalidStateError(); - } - this._state = "active"; - - this._abort(null); - } - - // http://w3c.github.io/IndexedDB/#dom-idbtransaction-objectstore - public objectStore(name: string) { - if (this._state !== "active") { - throw new InvalidStateError(); - } - - const objectStore = this._objectStoresCache.get(name); - if (objectStore !== undefined) { - return objectStore; - } - - return new BridgeIDBObjectStore(this, name); - } - - // http://www.w3.org/TR/2015/REC-IndexedDB-20150108/#dfn-steps-for-asynchronously-executing-a-request - public _execRequestAsync(obj: RequestObj) { - const source = obj.source; - const operation = obj.operation; - let request = obj.hasOwnProperty("request") ? obj.request : null; - - if (this._state !== "active") { - throw new TransactionInactiveError(); - } - - // Request should only be passed for cursors - if (!request) { - if (!source) { - // Special requests like indexes that just need to run some code - request = new BridgeIDBRequest(); - } else { - request = new BridgeIDBRequest(); - request.source = source; - request.transaction = (source as any).transaction; - } - } - - this._requests.push({ - operation, - request, - }); - - 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; - - if (!this._backendTransaction) { - this._backendTransaction = await this._backend.beginTransaction( - this.db._backendConnection, - Array.from(this._scope), - this.mode, - ); - } - - // Remove from request queue - cursor ones will be added back if necessary by cursor.continue and such - let operation; - let request; - while (this._requests.length > 0) { - const r = this._requests.shift(); - - // This should only be false if transaction was aborted - if (r && r.request.readyState !== "done") { - request = r.request; - operation = r.operation; - break; - } - } - - if (request && operation) { - if (!request.source) { - // Special requests like indexes that just need to run some code, with error handling already built into - // operation - await operation(); - } else { - let event; - try { - BridgeIDBFactory.enableTracing && - console.log("TRACE: running operation in transaction"); - const result = await operation(); - BridgeIDBFactory.enableTracing && - console.log( - "TRACE: operation in transaction finished with success", - ); - request.readyState = "done"; - 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"; - } - event = new FakeEvent("success", { - 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; - - // http://www.w3.org/TR/2015/REC-IndexedDB-20150108/#dfn-fire-an-error-event - if (this._state === "inactive") { - this._state = "active"; - } - event = new FakeEvent("error", { - bubbles: true, - cancelable: true, - }); - - try { - event.eventPath = [this.db, this]; - request.dispatchEvent(event); - } catch (err) { - if (this._state !== "committing") { - this._abort("AbortError"); - } - throw err; - } - if (!event.canceled) { - this._abort(err.name); - } - } - } - - // On to the next one - if (this._requests.length > 0) { - this._start(); - } else { - // Give it another chance for new handlers to be set before finishing - queueTask(() => this._start()); - } - return; - } - - 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 on transaction"); - } - const event = new FakeEvent("complete"); - event.eventPath = [this, this.db]; - this.dispatchEvent(event); - } - - const idx = this.db._transactions.indexOf(this); - if (idx < 0) { - throw Error("invariant failed"); - } - this.db._transactions.splice(idx, 1); - - this._resolveWait(); - } - } - - public commit() { - if (this._state !== "active") { - throw new InvalidStateError(); - } - - this._state = "committing"; - // We now just wait for auto-commit ... - } - - public toString() { - return "[object IDBRequest]"; - } - - _waitDone(): Promise { - return this._waitPromise; - } -} diff --git a/packages/idb-bridge/src/BridgeIDBVersionChangeEvent.ts b/packages/idb-bridge/src/BridgeIDBVersionChangeEvent.ts deleted file mode 100644 index 43e822d86..000000000 --- a/packages/idb-bridge/src/BridgeIDBVersionChangeEvent.ts +++ /dev/null @@ -1,39 +0,0 @@ -/* - Copyright 2019 Florian Dold - Copyright 2017 Jeremy Scheff - - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express - or implied. See the License for the specific language governing - permissions and limitations under the License. - */ - -import FakeEvent from "./util/FakeEvent"; - -export class BridgeIDBVersionChangeEvent extends FakeEvent { - public newVersion: number | null; - public oldVersion: number; - - constructor( - type: "blocked" | "success" | "upgradeneeded" | "versionchange", - parameters: { newVersion?: number | null; oldVersion?: number } = {}, - ) { - super(type); - - this.newVersion = - parameters.newVersion !== undefined ? parameters.newVersion : null; - this.oldVersion = - parameters.oldVersion !== undefined ? parameters.oldVersion : 0; - } - - public toString() { - return "[object IDBVersionChangeEvent]"; - } -} diff --git a/packages/idb-bridge/src/MemoryBackend.test.ts b/packages/idb-bridge/src/MemoryBackend.test.ts index 737b83d9c..281a72e36 100644 --- a/packages/idb-bridge/src/MemoryBackend.test.ts +++ b/packages/idb-bridge/src/MemoryBackend.test.ts @@ -15,13 +15,15 @@ */ import test from "ava"; +import { + BridgeIDBCursorWithValue, + BridgeIDBDatabase, + BridgeIDBFactory, + BridgeIDBKeyRange, + BridgeIDBRequest, + BridgeIDBTransaction, +} from "./bridge-idb"; import MemoryBackend from "./MemoryBackend"; -import { BridgeIDBFactory } from "./BridgeIDBFactory"; -import { BridgeIDBRequest } from "./BridgeIDBRequest"; -import { BridgeIDBDatabase } from "./BridgeIDBDatabase"; -import { BridgeIDBTransaction } from "./BridgeIDBTransaction"; -import { BridgeIDBKeyRange } from "./BridgeIDBKeyRange"; -import { BridgeIDBCursorWithValue } from "./BridgeIDBCursorWithValue"; function promiseFromRequest(request: BridgeIDBRequest): Promise { return new Promise((resolve, reject) => { diff --git a/packages/idb-bridge/src/MemoryBackend.ts b/packages/idb-bridge/src/MemoryBackend.ts index 531a7f299..6a52a555f 100644 --- a/packages/idb-bridge/src/MemoryBackend.ts +++ b/packages/idb-bridge/src/MemoryBackend.ts @@ -36,11 +36,14 @@ import { } from "./util/errors"; import BTree, { ISortedMapF } from "./tree/b+tree"; import compareKeys from "./util/cmp"; -import { Key, Value, KeyPath, TransactionMode } from "./util/types"; import { StoreKeyResult, makeStoreKeyValue } from "./util/makeStoreKeyValue"; import getIndexKeys from "./util/getIndexKeys"; import openPromise from "./util/openPromise"; -import { BridgeIDBKeyRange } from "./BridgeIDBKeyRange"; +import { IDBKeyPath, IDBKeyRange, IDBTransactionMode, IDBValidKey } from "./idbtypes"; +import { BridgeIDBKeyRange } from "./bridge-idb"; + +type Key = IDBValidKey; +type Value = unknown; enum TransactionLevel { Disconnected = 0, @@ -476,7 +479,7 @@ export class MemoryBackend implements Backend { async beginTransaction( conn: DatabaseConnection, objectStores: string[], - mode: TransactionMode, + mode: IDBTransactionMode, ): Promise { if (this.enableTracing) { console.log(`TRACING: beginTransaction`); @@ -773,6 +776,9 @@ export class MemoryBackend implements Backend { if (!schema) { throw Error("no schema for versionchange tx"); } + if (Array.isArray(keyPath)) { + throw Error("array key path not supported for object stores"); + } schema.objectStores[name] = { autoIncrement, keyPath, @@ -785,7 +791,7 @@ export class MemoryBackend implements Backend { btx: DatabaseTransaction, indexName: string, objectStoreName: string, - keyPath: KeyPath, + keyPath: IDBKeyPath, multiEntry: boolean, unique: boolean, ): void { @@ -843,7 +849,7 @@ export class MemoryBackend implements Backend { async deleteRecord( btx: DatabaseTransaction, objectStoreName: string, - range: BridgeIDBKeyRange, + range: IDBKeyRange, ): Promise { if (this.enableTracing) { console.log(`TRACING: deleteRecord from store ${objectStoreName}`); @@ -900,6 +906,10 @@ export class MemoryBackend implements Backend { } } + if (currKey === undefined) { + throw Error("invariant violated"); + } + // make sure that currKey is either undefined or pointing to an // existing object. let firstValue = modifiedData.get(currKey); @@ -1112,6 +1122,10 @@ export class MemoryBackend implements Backend { indexPos = forward ? indexData.minKey() : indexData.maxKey(); } + if (indexPos === undefined) { + throw Error("invariant violated"); + } + let indexEntry: IndexRecord | undefined; indexEntry = indexData.get(indexPos); if (!indexEntry) { @@ -1191,13 +1205,13 @@ export class MemoryBackend implements Backend { primkeySubPos < 0 || primkeySubPos >= indexEntry.primaryKeys.length ) { - const res = forward + const res: any = forward ? indexData.nextHigherPair(indexPos) : indexData.nextLowerPair(indexPos); if (res) { indexPos = res[1].indexKey; indexEntry = res[1]; - primkeySubPos = forward ? 0 : indexEntry.primaryKeys.length - 1; + primkeySubPos = forward ? 0 : indexEntry!.primaryKeys.length - 1; continue; } else { break; diff --git a/packages/idb-bridge/src/backend-interface.ts b/packages/idb-bridge/src/backend-interface.ts index 4d1f107ba..756a5b967 100644 --- a/packages/idb-bridge/src/backend-interface.ts +++ b/packages/idb-bridge/src/backend-interface.ts @@ -14,26 +14,25 @@ permissions and limitations under the License. */ +import { BridgeIDBDatabaseInfo, BridgeIDBKeyRange } from "./bridge-idb"; import { - TransactionMode, - Value, - BridgeIDBCursorDirection, - Key, - KeyPath, - BridgeIDBDatabaseInfo, -} from "./util/types"; -import { BridgeIDBKeyRange } from "./BridgeIDBKeyRange"; + IDBCursorDirection, + IDBKeyPath, + IDBTransactionMode, + IDBValidKey, +} from "./idbtypes"; + /** @public */ export interface ObjectStoreProperties { - keyPath: KeyPath | null; + keyPath: IDBKeyPath | null; autoIncrement: boolean; indexes: { [nameame: string]: IndexProperties }; } /** @public */ export interface IndexProperties { - keyPath: KeyPath; + keyPath: IDBKeyPath; multiEntry: boolean; unique: boolean; } @@ -71,7 +70,7 @@ export enum StoreLevel { /** @public */ export interface RecordGetRequest { - direction: BridgeIDBCursorDirection; + direction: IDBCursorDirection; objectStoreName: string; indexName: string | undefined; /** @@ -79,7 +78,7 @@ export interface RecordGetRequest { * If indexName is defined, the range refers to the index keys. * Otherwise it refers to the object store keys. */ - range: BridgeIDBKeyRange | undefined; + range: BridgeIDBKeyRange | undefined | null; /** * Last cursor position in terms of the index key. * Can only be specified if indexName is defined and @@ -87,23 +86,23 @@ export interface RecordGetRequest { * * Must either be undefined or within range. */ - lastIndexPosition?: Key; + lastIndexPosition?: IDBValidKey; /** * Last position in terms of the object store key. */ - lastObjectStorePosition?: Key; + lastObjectStorePosition?: IDBValidKey; /** * If specified, the index key of the results must be * greater or equal to advanceIndexKey. * * Only applicable if indexName is specified. */ - advanceIndexKey?: Key; + advanceIndexKey?: IDBValidKey; /** * If specified, the primary key of the results must be greater * or equal to advancePrimaryKey. */ - advancePrimaryKey?: Key; + advancePrimaryKey?: IDBValidKey; /** * Maximum number of resuts to return. * If -1, return all available results @@ -114,17 +113,17 @@ export interface RecordGetRequest { /** @public */ export interface RecordGetResponse { - values: Value[] | undefined; - indexKeys: Key[] | undefined; - primaryKeys: Key[] | undefined; + values: any[] | undefined; + indexKeys: IDBValidKey[] | undefined; + primaryKeys: IDBValidKey[] | undefined; count: number; } /** @public */ export interface RecordStoreRequest { objectStoreName: string; - value: Value; - key: Key | undefined; + value: any; + key: IDBValidKey | undefined; storeLevel: StoreLevel; } @@ -133,7 +132,7 @@ export interface RecordStoreResponse { /** * Key that the record was stored under in the object store. */ - key: Key; + key: IDBValidKey; } /** @public */ @@ -145,7 +144,7 @@ export interface Backend { beginTransaction( conn: DatabaseConnection, objectStores: string[], - mode: TransactionMode, + mode: IDBTransactionMode, ): Promise; enterVersionChange( @@ -200,7 +199,7 @@ export interface Backend { btx: DatabaseTransaction, indexName: string, objectStoreName: string, - keyPath: KeyPath, + keyPath: IDBKeyPath, multiEntry: boolean, unique: boolean, ): void; diff --git a/packages/idb-bridge/src/bridge-idb.ts b/packages/idb-bridge/src/bridge-idb.ts new file mode 100644 index 000000000..2bced800d --- /dev/null +++ b/packages/idb-bridge/src/bridge-idb.ts @@ -0,0 +1,2053 @@ +/* + Copyright 2017 Jeremy Scheff + Copyright 2019-2021 Taler Systems S.A. + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express + or implied. See the License for the specific language governing + permissions and limitations under the License. + */ + +import { + Backend, + DatabaseConnection, + DatabaseTransaction, + RecordGetRequest, + RecordStoreRequest, + ResultLevel, + Schema, + StoreLevel, +} from "./backend-interface"; +import { EventListener, IDBCursorDirection, IDBKeyPath, IDBKeyRange, IDBTransactionMode, IDBValidKey } from "./idbtypes"; +import compareKeys from "./util/cmp"; +import enforceRange from "./util/enforceRange"; +import { + AbortError, + ConstraintError, + DataError, + InvalidAccessError, + InvalidStateError, + NotFoundError, + ReadOnlyError, + TransactionInactiveError, + VersionError, +} from "./util/errors"; +import { fakeDOMStringList } from "./util/fakeDOMStringList"; +import FakeEvent from "./util/FakeEvent"; +import FakeEventTarget from "./util/FakeEventTarget"; +import openPromise from "./util/openPromise"; +import queueTask from "./util/queueTask"; +import structuredClone from "./util/structuredClone"; +import validateKeyPath from "./util/validateKeyPath"; +import valueToKey from "./util/valueToKey"; + +/** @public */ +export type CursorSource = BridgeIDBIndex | BridgeIDBObjectStore; + +/** @public */ +export interface FakeDOMStringList extends Array { + contains: (value: string) => boolean; + item: (i: number) => string | undefined; +} + +/** @public */ +export interface RequestObj { + operation: () => Promise; + request?: BridgeIDBRequest | undefined; + source?: any; +} + +/** @public */ +export interface BridgeIDBDatabaseInfo { + name: string; + version: number; +} + +function simplifyRange( + r: IDBValidKey | IDBKeyRange | undefined | null, +): IDBKeyRange | null { + if (r && typeof r === "object" && "lower" in r) { + return r; + } + if (r === undefined || r === null) { + return null; + } + return BridgeIDBKeyRange.bound(r, r, false, false); +} + +/** + * http://www.w3.org/TR/2015/REC-IndexedDB-20150108/#cursor + * + * @public + */ +export class BridgeIDBCursor { + _request: BridgeIDBRequest | undefined; + + private _gotValue: boolean = false; + private _range: IDBValidKey | IDBKeyRange | undefined | null; + private _indexPosition = undefined; // Key of previously returned record + private _objectStorePosition = undefined; + private _keyOnly: boolean; + + private _source: CursorSource; + private _direction: IDBCursorDirection; + private _key: IDBValidKey | undefined = undefined; + private _primaryKey: IDBValidKey | undefined = undefined; + private _indexName: string | undefined; + private _objectStoreName: string; + + protected _value: any = undefined; + + constructor( + source: CursorSource, + objectStoreName: string, + indexName: string | undefined, + range: IDBValidKey | IDBKeyRange | null | undefined, + direction: IDBCursorDirection, + request: BridgeIDBRequest, + keyOnly: boolean, + ) { + this._indexName = indexName; + this._objectStoreName = objectStoreName; + this._range = range; + this._source = source; + this._direction = direction; + this._request = request; + this._keyOnly = keyOnly; + } + + get _effectiveObjectStore(): BridgeIDBObjectStore { + if (this.source instanceof BridgeIDBObjectStore) { + return this.source; + } + return this.source.objectStore; + } + + get _backend(): Backend { + return this._source._backend; + } + + // Read only properties + get source() { + return this._source; + } + set source(val) { + /* For babel */ + } + + get direction() { + return this._direction; + } + set direction(val) { + /* For babel */ + } + + get key() { + return this._key; + } + set key(val) { + /* For babel */ + } + + 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?: IDBValidKey, primaryKey?: IDBValidKey): Promise { + 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, + lastIndexPosition: this._indexPosition, + lastObjectStorePosition: this._objectStorePosition, + limit: 1, + range: simplifyRange(this._range), + objectStoreName: this._objectStoreName, + advanceIndexKey: key, + advancePrimaryKey: primaryKey, + resultLevel: this._keyOnly ? ResultLevel.OnlyKeys : ResultLevel.Full, + }; + + const { btx } = this.source._confirmActiveTransaction(); + + let response = await this._backend.getRecords(btx, recordGetRequest); + + if (response.count === 0) { + if (BridgeIDBFactory.enableTracing) { + console.log("cursor is returning empty result"); + } + this._gotValue = false; + return null; + } + + if (response.count !== 1) { + throw Error("invariant failed"); + } + + 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]; + } else { + this._key = response.primaryKeys![0]; + } + + this._primaryKey = response.primaryKeys![0]; + + if (!this._keyOnly) { + this._value = response.values![0]; + } + + this._gotValue = true; + this._objectStorePosition = structuredClone(response.primaryKeys![0]); + if (response.indexKeys !== undefined && response.indexKeys.length > 0) { + this._indexPosition = structuredClone(response.indexKeys[0]); + } + + return this; + } + + // http://www.w3.org/TR/2015/REC-IndexedDB-20150108/#widl-IDBCursor-update-IDBRequest-any-value + public update(value: any) { + if (value === undefined) { + throw new TypeError(); + } + + const transaction = this._effectiveObjectStore.transaction; + + if (transaction._state !== "active") { + throw new TransactionInactiveError(); + } + + if (transaction.mode === "readonly") { + throw new ReadOnlyError(); + } + + if (this._effectiveObjectStore._deleted) { + throw new InvalidStateError(); + } + + if ( + !(this.source instanceof BridgeIDBObjectStore) && + this.source._deleted + ) { + throw new InvalidStateError(); + } + + if (!this._gotValue || !this._isValueCursor) { + throw new InvalidStateError(); + } + + const storeReq: RecordStoreRequest = { + 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(); + await this._backend.storeRecord(btx, storeReq); + }; + return transaction._execRequestAsync({ + operation, + source: this, + }); + } + + /** + * http://www.w3.org/TR/2015/REC-IndexedDB-20150108/#widl-IDBCursor-advance-void-unsigned-long-count + */ + public advance(count: number) { + throw Error("not implemented"); + } + + /** + * http://www.w3.org/TR/2015/REC-IndexedDB-20150108/#widl-IDBCursor-continue-void-any-key + */ + public continue(key?: IDBValidKey) { + const transaction = this._effectiveObjectStore.transaction; + + if (transaction._state !== "active") { + throw new TransactionInactiveError(); + } + + if (this._effectiveObjectStore._deleted) { + throw new InvalidStateError(); + } + if ( + !(this.source instanceof BridgeIDBObjectStore) && + this.source._deleted + ) { + throw new InvalidStateError(); + } + + if (!this._gotValue) { + throw new InvalidStateError(); + } + + if (key !== undefined) { + key = valueToKey(key); + let lastKey = + this._indexName === undefined + ? this._objectStorePosition + : this._indexPosition; + + const cmpResult = compareKeys(key, lastKey); + + if ( + (cmpResult <= 0 && + (this.direction === "next" || this.direction === "nextunique")) || + (cmpResult >= 0 && + (this.direction === "prev" || this.direction === "prevunique")) + ) { + throw new DataError(); + } + } + + if (this._request) { + this._request.readyState = "pending"; + } + + const operation = async () => { + return this._iterate(key); + }; + + transaction._execRequestAsync({ + operation, + request: this._request, + source: this.source, + }); + + this._gotValue = false; + } + + // https://w3c.github.io/IndexedDB/#dom-idbcursor-continueprimarykey + public continuePrimaryKey(key: IDBValidKey, primaryKey: IDBValidKey) { + throw Error("not implemented"); + } + + public delete() { + const transaction = this._effectiveObjectStore.transaction; + + if (transaction._state !== "active") { + throw new TransactionInactiveError(); + } + + if (transaction.mode === "readonly") { + throw new ReadOnlyError(); + } + + if (this._effectiveObjectStore._deleted) { + throw new InvalidStateError(); + } + if ( + !(this.source instanceof BridgeIDBObjectStore) && + this.source._deleted + ) { + throw new InvalidStateError(); + } + + if (!this._gotValue || !this._isValueCursor) { + throw new InvalidStateError(); + } + + const operation = async () => { + const { btx } = this.source._confirmActiveTransaction(); + this._backend.deleteRecord( + btx, + this._objectStoreName, + BridgeIDBKeyRange._valueToKeyRange(this._primaryKey), + ); + }; + + return transaction._execRequestAsync({ + operation, + source: this, + }); + } + + public toString() { + return "[object IDBCursor]"; + } +} + +export class BridgeIDBCursorWithValue extends BridgeIDBCursor { + get value(): any { + return this._value; + } + + protected get _isValueCursor(): boolean { + return true; + } + + constructor( + source: CursorSource, + objectStoreName: string, + indexName: string | undefined, + range: IDBValidKey | IDBKeyRange | undefined | null, + direction: IDBCursorDirection, + request?: any, + ) { + super(source, objectStoreName, indexName, range, direction, request, false); + } + + public toString() { + return "[object IDBCursorWithValue]"; + } +} + +/** + * Ensure that an active version change transaction is currently running. + */ +const confirmActiveVersionchangeTransaction = (database: BridgeIDBDatabase) => { + if (!database._runningVersionchangeTransaction) { + throw new InvalidStateError(); + } + + // Find the latest versionchange transaction + const transactions = database._transactions.filter( + (tx: BridgeIDBTransaction) => { + return tx.mode === "versionchange"; + }, + ); + const transaction = transactions[transactions.length - 1]; + + if (!transaction || transaction._state === "finished") { + throw new InvalidStateError(); + } + + if (transaction._state !== "active") { + throw new TransactionInactiveError(); + } + + return transaction; +}; + +// http://www.w3.org/TR/2015/REC-IndexedDB-20150108/#database-interface +/** @public */ +export class BridgeIDBDatabase extends FakeEventTarget { + _closePending = false; + _closed = false; + _runningVersionchangeTransaction = false; + _transactions: Array = []; + + _backendConnection: DatabaseConnection; + _backend: Backend; + + _schema: Schema; + + get name(): string { + return this._schema.databaseName; + } + + get version(): number { + return this._schema.databaseVersion; + } + + get objectStoreNames(): FakeDOMStringList { + return fakeDOMStringList(Object.keys(this._schema.objectStores)).sort(); + } + + /** + * http://www.w3.org/TR/2015/REC-IndexedDB-20150108/#database-closing-steps + */ + _closeConnection() { + this._closePending = true; + + const transactionsComplete = this._transactions.every( + (transaction: BridgeIDBTransaction) => { + return transaction._state === "finished"; + }, + ); + + if (transactionsComplete) { + this._closed = true; + this._backend.close(this._backendConnection); + } else { + queueTask(() => { + this._closeConnection(); + }); + } + } + + constructor(backend: Backend, backendConnection: DatabaseConnection) { + super(); + + this._schema = backend.getSchema(backendConnection); + + this._backend = backend; + this._backendConnection = backendConnection; + } + + // http://w3c.github.io/IndexedDB/#dom-idbdatabase-createobjectstore + public createObjectStore( + name: string, + options: { autoIncrement?: boolean; keyPath?: IDBKeyPath } | null = {}, + ): BridgeIDBObjectStore { + if (name === undefined) { + throw new TypeError(); + } + const transaction = confirmActiveVersionchangeTransaction(this); + const backendTx = transaction._backendTransaction; + if (!backendTx) { + throw Error("invariant violated"); + } + + const keyPath = + options !== null && options.keyPath !== undefined + ? options.keyPath + : null; + const autoIncrement = + options !== null && options.autoIncrement !== undefined + ? options.autoIncrement + : false; + + if (keyPath !== null) { + validateKeyPath(keyPath); + } + + if (Object.keys(this._schema.objectStores).includes(name)) { + throw new ConstraintError(); + } + + if (autoIncrement && (keyPath === "" || Array.isArray(keyPath))) { + throw new InvalidAccessError(); + } + + transaction._backend.createObjectStore( + backendTx, + name, + keyPath, + autoIncrement, + ); + + this._schema = this._backend.getSchema(this._backendConnection); + + return transaction.objectStore(name); + } + + public deleteObjectStore(name: string): void { + if (name === undefined) { + throw new TypeError(); + } + const transaction = confirmActiveVersionchangeTransaction(this); + transaction._objectStoresCache.delete(name); + } + + public _internalTransaction( + storeNames: string | string[], + mode?: IDBTransactionMode, + backendTransaction?: DatabaseTransaction, + ): BridgeIDBTransaction { + mode = mode !== undefined ? mode : "readonly"; + if ( + mode !== "readonly" && + mode !== "readwrite" && + mode !== "versionchange" + ) { + 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) { + throw new InvalidStateError(); + } + + if (this._closePending) { + throw new InvalidStateError(); + } + + if (!Array.isArray(storeNames)) { + storeNames = [storeNames]; + } + if (storeNames.length === 0 && mode !== "versionchange") { + throw new InvalidAccessError(); + } + for (const storeName of storeNames) { + if (this.objectStoreNames.indexOf(storeName) < 0) { + throw new NotFoundError( + "No objectStore named " + storeName + " in this database", + ); + } + } + + const tx = new BridgeIDBTransaction( + storeNames, + mode, + this, + backendTransaction, + ); + this._transactions.push(tx); + queueTask(() => tx._start()); + return tx; + } + + public transaction( + storeNames: string | string[], + mode?: IDBTransactionMode, + ): BridgeIDBTransaction { + if (mode === "versionchange") { + throw new TypeError("Invalid mode: " + mode); + } + return this._internalTransaction(storeNames, mode); + } + + public close() { + this._closeConnection(); + } + + public toString() { + return "[object IDBDatabase]"; + } +} + +/** @public */ +export type DatabaseList = Array<{ name: string; version: number }>; + +/** @public */ +export class BridgeIDBFactory { + public cmp = compareKeys; + private backend: Backend; + private connections: BridgeIDBDatabase[] = []; + static enableTracing: boolean = false; + + public constructor(backend: Backend) { + this.backend = backend; + } + + // http://www.w3.org/TR/2015/REC-IndexedDB-20150108/#widl-IDBFactory-deleteDatabase-IDBOpenDBRequest-DOMString-name + public deleteDatabase(name: string): BridgeIDBOpenDBRequest { + const request = new BridgeIDBOpenDBRequest(); + request.source = null; + + queueTask(async () => { + const databases = await this.backend.getDatabases(); + const dbInfo = databases.find((x) => x.name == name); + if (!dbInfo) { + // Database already doesn't exist, success! + const event = new BridgeIDBVersionChangeEvent("success", { + newVersion: null, + oldVersion: 0, + }); + request.dispatchEvent(event); + return; + } + const oldVersion = dbInfo.version; + + try { + const dbconn = await this.backend.connectDatabase(name); + const backendTransaction = await this.backend.enterVersionChange( + dbconn, + 0, + ); + await this.backend.deleteDatabase(backendTransaction, name); + await this.backend.commit(backendTransaction); + await this.backend.close(dbconn); + + request.result = undefined; + request.readyState = "done"; + + const event2 = new BridgeIDBVersionChangeEvent("success", { + newVersion: null, + oldVersion, + }); + request.dispatchEvent(event2); + } catch (err) { + request.error = new Error(); + request.error.name = err.name; + request.readyState = "done"; + + const event = new FakeEvent("error", { + bubbles: true, + cancelable: true, + }); + event.eventPath = []; + request.dispatchEvent(event); + } + }); + + return request; + } + + // tslint:disable-next-line max-line-length + // http://www.w3.org/TR/2015/REC-IndexedDB-20150108/#widl-IDBFactory-open-IDBOpenDBRequest-DOMString-name-unsigned-long-long-version + public open(name: string, version?: number) { + if (arguments.length > 1 && version !== undefined) { + // Based on spec, not sure why "MAX_SAFE_INTEGER" instead of "unsigned long long", but it's needed to pass + // tests + version = enforceRange(version, "MAX_SAFE_INTEGER"); + } + if (version === 0) { + throw new TypeError(); + } + + const request = new BridgeIDBOpenDBRequest(); + + queueTask(async () => { + let dbconn: DatabaseConnection; + try { + dbconn = await this.backend.connectDatabase(name); + } catch (err) { + request._finishWithError(err); + return; + } + + const schema = this.backend.getSchema(dbconn); + const existingVersion = schema.databaseVersion; + + if (version === undefined) { + version = existingVersion !== 0 ? existingVersion : 1; + } + + const requestedVersion = version; + + BridgeIDBFactory.enableTracing && + console.log( + `TRACE: existing version ${existingVersion}, requested version ${requestedVersion}`, + ); + + if (existingVersion > requestedVersion) { + request._finishWithError(new VersionError()); + return; + } + + const db = new BridgeIDBDatabase(this.backend, dbconn); + + if (existingVersion == requestedVersion) { + request.result = db; + request.readyState = "done"; + + const event2 = new FakeEvent("success", { + bubbles: false, + cancelable: false, + }); + event2.eventPath = [request]; + request.dispatchEvent(event2); + } + + if (existingVersion < requestedVersion) { + // http://www.w3.org/TR/2015/REC-IndexedDB-20150108/#dfn-steps-for-running-a-versionchange-transaction + + for (const otherConn of this.connections) { + const event = new BridgeIDBVersionChangeEvent("versionchange", { + newVersion: version, + oldVersion: existingVersion, + }); + otherConn.dispatchEvent(event); + } + + if (this._anyOpen()) { + const event = new BridgeIDBVersionChangeEvent("blocked", { + newVersion: version, + oldVersion: existingVersion, + }); + request.dispatchEvent(event); + } + + const backendTransaction = await this.backend.enterVersionChange( + dbconn, + requestedVersion, + ); + db._runningVersionchangeTransaction = true; + + const transaction = db._internalTransaction( + [], + "versionchange", + backendTransaction, + ); + const event = new BridgeIDBVersionChangeEvent("upgradeneeded", { + newVersion: version, + oldVersion: existingVersion, + }); + + request.result = db; + request.readyState = "done"; + request.transaction = transaction; + request.dispatchEvent(event); + + 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); + return db; + }); + + return request; + } + + // https://w3c.github.io/IndexedDB/#dom-idbfactory-databases + public databases(): Promise { + return this.backend.getDatabases(); + } + + public toString(): string { + return "[object IDBFactory]"; + } + + private _anyOpen(): boolean { + return this.connections.some((c) => !c._closed && !c._closePending); + } +} + +const confirmActiveTransaction = ( + index: BridgeIDBIndex, +): BridgeIDBTransaction => { + if (index._deleted || index.objectStore._deleted) { + throw new InvalidStateError(); + } + + if (index.objectStore.transaction._state !== "active") { + throw new TransactionInactiveError(); + } + + return index.objectStore.transaction; +}; + +// http://www.w3.org/TR/2015/REC-IndexedDB-20150108/#idl-def-IDBIndex +/** @public */ +export class BridgeIDBIndex { + objectStore: BridgeIDBObjectStore; + + get _schema(): Schema { + return this.objectStore.transaction.db._schema; + } + + get keyPath(): IDBKeyPath { + return this._schema.objectStores[this.objectStore.name].indexes[this._name] + .keyPath; + } + + get multiEntry(): boolean { + return this._schema.objectStores[this.objectStore.name].indexes[this._name] + .multiEntry; + } + + get unique(): boolean { + return this._schema.objectStores[this.objectStore.name].indexes[this._name] + .unique; + } + + get _backend(): Backend { + return this.objectStore._backend; + } + + _confirmActiveTransaction(): { btx: DatabaseTransaction } { + return this.objectStore._confirmActiveTransaction(); + } + + private _name: string; + + public _deleted: boolean = false; + + constructor(objectStore: BridgeIDBObjectStore, name: string) { + this._name = name; + this.objectStore = objectStore; + } + + get name() { + return this._name; + } + + // https://w3c.github.io/IndexedDB/#dom-idbindex-name + set name(name: any) { + const transaction = this.objectStore.transaction; + + if (!transaction.db._runningVersionchangeTransaction) { + throw new InvalidStateError(); + } + + if (transaction._state !== "active") { + throw new TransactionInactiveError(); + } + + const { btx } = this._confirmActiveTransaction(); + + const oldName = this._name; + const newName = String(name); + + if (newName === oldName) { + return; + } + + this._backend.renameIndex(btx, this.objectStore.name, oldName, newName); + + if (this.objectStore.indexNames.indexOf(name) >= 0) { + throw new ConstraintError(); + } + } + + // tslint:disable-next-line max-line-length + // http://www.w3.org/TR/2015/REC-IndexedDB-20150108/#widl-IDBIndex-openCursor-IDBRequest-any-range-IDBCursorDirection-direction + public openCursor( + range?: BridgeIDBKeyRange | IDBValidKey | null | undefined, + direction: IDBCursorDirection = "next", + ) { + confirmActiveTransaction(this); + + if (range === null) { + range = undefined; + } + if (range !== undefined && !(range instanceof BridgeIDBKeyRange)) { + range = BridgeIDBKeyRange.only(valueToKey(range)); + } + + const request = new BridgeIDBRequest(); + request.source = this; + request.transaction = this.objectStore.transaction; + + const cursor = new BridgeIDBCursorWithValue( + this, + this.objectStore.name, + this._name, + range, + direction, + request, + ); + + const operation = async () => { + return cursor._iterate(); + }; + + return this.objectStore.transaction._execRequestAsync({ + operation, + request, + source: this, + }); + } + + // tslint:disable-next-line max-line-length + // http://www.w3.org/TR/2015/REC-IndexedDB-20150108/#widl-IDBIndex-openKeyCursor-IDBRequest-any-range-IDBCursorDirection-direction + public openKeyCursor( + range?: BridgeIDBKeyRange | IDBValidKey | null | undefined, + direction: IDBCursorDirection = "next", + ) { + confirmActiveTransaction(this); + + if (range === null) { + range = undefined; + } + if (range !== undefined && !(range instanceof BridgeIDBKeyRange)) { + range = BridgeIDBKeyRange.only(valueToKey(range)); + } + + const request = new BridgeIDBRequest(); + request.source = this; + request.transaction = this.objectStore.transaction; + + const cursor = new BridgeIDBCursor( + this, + this.objectStore.name, + this._name, + range, + direction, + request, + true, + ); + + return this.objectStore.transaction._execRequestAsync({ + operation: cursor._iterate.bind(cursor), + request, + source: this, + }); + } + + public get(key: BridgeIDBKeyRange | IDBValidKey) { + confirmActiveTransaction(this); + + if (!(key instanceof BridgeIDBKeyRange)) { + key = BridgeIDBKeyRange._valueToKeyRange(key); + } + + const getReq: RecordGetRequest = { + direction: "next", + indexName: this._name, + limit: 1, + range: key, + objectStoreName: this.objectStore._name, + resultLevel: ResultLevel.Full, + }; + + const operation = async () => { + const { btx } = this._confirmActiveTransaction(); + const result = await this._backend.getRecords(btx, getReq); + if (result.count == 0) { + return undefined; + } + const values = result.values; + if (!values) { + throw Error("invariant violated"); + } + return values[0]; + }; + + return this.objectStore.transaction._execRequestAsync({ + operation, + source: this, + }); + } + + // http://w3c.github.io/IndexedDB/#dom-idbindex-getall + public getAll(query?: BridgeIDBKeyRange | IDBValidKey, count?: number) { + throw Error("not implemented"); + } + + // http://www.w3.org/TR/2015/REC-IndexedDB-20150108/#widl-IDBIndex-getKey-IDBRequest-any-key + public getKey(key: BridgeIDBKeyRange | IDBValidKey) { + confirmActiveTransaction(this); + + if (!(key instanceof BridgeIDBKeyRange)) { + key = BridgeIDBKeyRange._valueToKeyRange(key); + } + + const getReq: RecordGetRequest = { + direction: "next", + indexName: this._name, + limit: 1, + range: key, + objectStoreName: this.objectStore._name, + resultLevel: ResultLevel.OnlyKeys, + }; + + const operation = async () => { + const { btx } = this._confirmActiveTransaction(); + const result = await this._backend.getRecords(btx, getReq); + if (result.count == 0) { + return undefined; + } + const primaryKeys = result.primaryKeys; + if (!primaryKeys) { + throw Error("invariant violated"); + } + return primaryKeys[0]; + }; + + return this.objectStore.transaction._execRequestAsync({ + operation, + source: this, + }); + } + + // http://w3c.github.io/IndexedDB/#dom-idbindex-getallkeys + public getAllKeys(query?: BridgeIDBKeyRange | IDBValidKey, count?: number) { + throw Error("not implemented"); + } + + // http://www.w3.org/TR/2015/REC-IndexedDB-20150108/#widl-IDBIndex-count-IDBRequest-any-key + public count(key: BridgeIDBKeyRange | IDBValidKey | null | undefined) { + confirmActiveTransaction(this); + + if (key === null) { + key = undefined; + } + if (key !== undefined && !(key instanceof BridgeIDBKeyRange)) { + key = BridgeIDBKeyRange.only(valueToKey(key)); + } + + const getReq: RecordGetRequest = { + direction: "next", + indexName: this._name, + limit: 1, + range: key, + objectStoreName: this.objectStore._name, + resultLevel: ResultLevel.OnlyCount, + }; + + const operation = async () => { + const { btx } = this._confirmActiveTransaction(); + const result = await this._backend.getRecords(btx, getReq); + return result.count; + }; + + return this.objectStore.transaction._execRequestAsync({ + operation, + source: this, + }); + } + + public toString() { + return "[object IDBIndex]"; + } +} + +// http://www.w3.org/TR/2015/REC-IndexedDB-20150108/#range-concept +/** @public */ +export class BridgeIDBKeyRange { + public static only(value: IDBValidKey) { + if (arguments.length === 0) { + throw new TypeError(); + } + value = valueToKey(value); + return new BridgeIDBKeyRange(value, value, false, false); + } + + static lowerBound(lower: IDBValidKey, open: boolean = false) { + if (arguments.length === 0) { + throw new TypeError(); + } + lower = valueToKey(lower); + return new BridgeIDBKeyRange(lower, undefined, open, true); + } + + static upperBound(upper: IDBValidKey, open: boolean = false) { + if (arguments.length === 0) { + throw new TypeError(); + } + upper = valueToKey(upper); + return new BridgeIDBKeyRange(undefined, upper, true, open); + } + + static bound( + lower: IDBValidKey, + upper: IDBValidKey, + lowerOpen: boolean = false, + upperOpen: boolean = false, + ) { + if (arguments.length < 2) { + throw new TypeError(); + } + + const cmpResult = compareKeys(lower, upper); + if (cmpResult === 1 || (cmpResult === 0 && (lowerOpen || upperOpen))) { + throw new DataError(); + } + + lower = valueToKey(lower); + upper = valueToKey(upper); + return new BridgeIDBKeyRange(lower, upper, lowerOpen, upperOpen); + } + + readonly lower: IDBValidKey | undefined; + readonly upper: IDBValidKey | undefined; + readonly lowerOpen: boolean; + readonly upperOpen: boolean; + + constructor( + lower: IDBValidKey | undefined, + upper: IDBValidKey | undefined, + lowerOpen: boolean, + upperOpen: boolean, + ) { + this.lower = lower; + this.upper = upper; + this.lowerOpen = lowerOpen; + this.upperOpen = upperOpen; + } + + // https://w3c.github.io/IndexedDB/#dom-idbkeyrange-includes + includes(key: IDBValidKey) { + if (arguments.length === 0) { + throw new TypeError(); + } + key = valueToKey(key); + + if (this.lower !== undefined) { + const cmpResult = compareKeys(this.lower, key); + + if (cmpResult === 1 || (cmpResult === 0 && this.lowerOpen)) { + return false; + } + } + if (this.upper !== undefined) { + const cmpResult = compareKeys(this.upper, key); + + if (cmpResult === -1 || (cmpResult === 0 && this.upperOpen)) { + return false; + } + } + return true; + } + + toString() { + return "[object IDBKeyRange]"; + } + + static _valueToKeyRange(value: any, nullDisallowedFlag: boolean = false) { + if (value instanceof BridgeIDBKeyRange) { + return value; + } + + if (value === null || value === undefined) { + if (nullDisallowedFlag) { + throw new DataError(); + } + return new BridgeIDBKeyRange(undefined, undefined, false, false); + } + + const key = valueToKey(value); + + return BridgeIDBKeyRange.only(key); + } +} + +// http://www.w3.org/TR/2015/REC-IndexedDB-20150108/#object-store +/** @public */ +export class BridgeIDBObjectStore { + _indexesCache: Map = new Map(); + + transaction: BridgeIDBTransaction; + + get autoIncrement(): boolean { + return this._schema.objectStores[this._name].autoIncrement; + } + + get indexNames(): FakeDOMStringList { + return fakeDOMStringList( + Object.keys(this._schema.objectStores[this._name].indexes), + ).sort(); + } + + get keyPath(): IDBKeyPath | null { + return this._schema.objectStores[this._name].keyPath; + } + + _name: string; + + get _schema(): Schema { + return this.transaction.db._schema; + } + + _deleted: boolean = false; + + constructor(transaction: BridgeIDBTransaction, name: string) { + this._name = name; + this.transaction = transaction; + } + + get name() { + return this._name; + } + + get _backend(): Backend { + return this.transaction.db._backend; + } + + get _backendConnection(): DatabaseConnection { + return this.transaction.db._backendConnection; + } + + _confirmActiveTransaction(): { btx: DatabaseTransaction } { + const btx = this.transaction._backendTransaction; + if (!btx) { + throw new InvalidStateError(); + } + return { btx }; + } + + // http://w3c.github.io/IndexedDB/#dom-idbobjectstore-name + set name(newName: any) { + const transaction = this.transaction; + + if (!transaction.db._runningVersionchangeTransaction) { + throw new InvalidStateError(); + } + + let { btx } = this._confirmActiveTransaction(); + + newName = String(newName); + + const oldName = this._name; + + if (newName === oldName) { + return; + } + + this._backend.renameObjectStore(btx, oldName, newName); + this.transaction.db._schema = this._backend.getSchema( + this._backendConnection, + ); + } + + public _store(value: any, key: IDBValidKey | undefined, overwrite: boolean) { + if (BridgeIDBFactory.enableTracing) { + console.log(`TRACE: IDBObjectStore._store`); + } + if (this.transaction.mode === "readonly") { + throw new ReadOnlyError(); + } + const operation = async () => { + const { btx } = this._confirmActiveTransaction(); + const result = await this._backend.storeRecord(btx, { + objectStoreName: this._name, + key: key, + value: value, + storeLevel: overwrite + ? StoreLevel.AllowOverwrite + : StoreLevel.NoOverwrite, + }); + return result.key; + }; + + return this.transaction._execRequestAsync({ operation, source: this }); + } + + public put(value: any, key?: IDBValidKey) { + if (arguments.length === 0) { + throw new TypeError(); + } + return this._store(value, key, true); + } + + public add(value: any, key?: IDBValidKey) { + if (arguments.length === 0) { + throw new TypeError(); + } + return this._store(value, key, false); + } + + public delete(key: IDBValidKey | BridgeIDBKeyRange) { + if (arguments.length === 0) { + throw new TypeError(); + } + + if (this.transaction.mode === "readonly") { + throw new ReadOnlyError(); + } + + 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, keyRange); + }; + + return this.transaction._execRequestAsync({ + operation, + source: this, + }); + } + + public get(key?: BridgeIDBKeyRange | IDBValidKey) { + if (BridgeIDBFactory.enableTracing) { + console.log(`getting from object store ${this._name} key ${key}`); + } + + if (arguments.length === 0) { + throw new TypeError(); + } + + let keyRange: BridgeIDBKeyRange; + + if (key instanceof BridgeIDBKeyRange) { + keyRange = key; + } else { + try { + keyRange = BridgeIDBKeyRange.only(valueToKey(key)); + } catch (e) { + throw Error( + `invalid key (type ${typeof key}) for object store ${this._name}`, + ); + } + } + + const recordRequest: RecordGetRequest = { + objectStoreName: this._name, + indexName: undefined, + lastIndexPosition: undefined, + lastObjectStorePosition: undefined, + direction: "next", + limit: 1, + resultLevel: ResultLevel.Full, + 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 (BridgeIDBFactory.enableTracing) { + console.log("get operation result count:", result.count); + } + + if (result.count === 0) { + return undefined; + } + const values = result.values; + if (!values) { + throw Error("invariant violated"); + } + return values[0]; + }; + + return this.transaction._execRequestAsync({ + operation, + source: this, + }); + } + + // http://w3c.github.io/IndexedDB/#dom-idbobjectstore-getall + public getAll(query?: BridgeIDBKeyRange | IDBValidKey, count?: number) { + throw Error("not implemented"); + } + + // http://w3c.github.io/IndexedDB/#dom-idbobjectstore-getkey + public getKey(key?: BridgeIDBKeyRange | IDBValidKey) { + throw Error("not implemented"); + } + + // http://w3c.github.io/IndexedDB/#dom-idbobjectstore-getallkeys + public getAllKeys(query?: BridgeIDBKeyRange | IDBValidKey, count?: number) { + throw Error("not implemented"); + } + + public clear() { + throw Error("not implemented"); + } + + public openCursor( + range?: IDBKeyRange | IDBValidKey, + direction: IDBCursorDirection = "next", + ) { + if (range === null) { + range = undefined; + } + if (range !== undefined && !(range instanceof BridgeIDBKeyRange)) { + range = BridgeIDBKeyRange.only(valueToKey(range)); + } + + const request = new BridgeIDBRequest(); + request.source = this; + request.transaction = this.transaction; + + const cursor = new BridgeIDBCursorWithValue( + this, + this._name, + undefined, + range, + direction, + request, + ); + + return this.transaction._execRequestAsync({ + operation: () => cursor._iterate(), + request, + source: this, + }); + } + + public openKeyCursor( + range?: BridgeIDBKeyRange | IDBValidKey, + direction?: IDBCursorDirection, + ) { + if (range === null) { + range = undefined; + } + if (range !== undefined && !(range instanceof BridgeIDBKeyRange)) { + range = BridgeIDBKeyRange.only(valueToKey(range)); + } + + if (!direction) { + direction = "next"; + } + + const request = new BridgeIDBRequest(); + request.source = this; + request.transaction = this.transaction; + + const cursor = new BridgeIDBCursor( + this, + this._name, + undefined, + range, + direction, + request, + true, + ); + + return this.transaction._execRequestAsync({ + operation: cursor._iterate.bind(cursor), + request, + source: this, + }); + } + + // tslint:disable-next-line max-line-length + // http://www.w3.org/TR/2015/REC-IndexedDB-20150108/#widl-IDBObjectStore-createIndex-IDBIndex-DOMString-name-DOMString-sequence-DOMString--keyPath-IDBIndexParameters-optionalParameters + public createIndex( + indexName: string, + keyPath: IDBKeyPath, + optionalParameters: { multiEntry?: boolean; unique?: boolean } = {}, + ) { + if (arguments.length < 2) { + throw new TypeError(); + } + + if (!this.transaction.db._runningVersionchangeTransaction) { + throw new InvalidStateError(); + } + + const { btx } = this._confirmActiveTransaction(); + + const multiEntry = + optionalParameters.multiEntry !== undefined + ? optionalParameters.multiEntry + : false; + const unique = + optionalParameters.unique !== undefined + ? optionalParameters.unique + : false; + + if (this.transaction.mode !== "versionchange") { + throw new InvalidStateError(); + } + + if (this.indexNames.indexOf(indexName) >= 0) { + throw new ConstraintError(); + } + + validateKeyPath(keyPath); + + if (Array.isArray(keyPath) && multiEntry) { + throw new InvalidAccessError(); + } + + this._backend.createIndex( + btx, + indexName, + this._name, + keyPath, + multiEntry, + unique, + ); + + return new BridgeIDBIndex(this, indexName); + } + + // https://w3c.github.io/IndexedDB/#dom-idbobjectstore-index + public index(name: string) { + if (arguments.length === 0) { + throw new TypeError(); + } + + if (this.transaction._state === "finished") { + throw new InvalidStateError(); + } + + const index = this._indexesCache.get(name); + if (index !== undefined) { + return index; + } + + return new BridgeIDBIndex(this, name); + } + + public deleteIndex(indexName: string) { + if (arguments.length === 0) { + throw new TypeError(); + } + + if (this.transaction.mode !== "versionchange") { + throw new InvalidStateError(); + } + + if (!this.transaction.db._runningVersionchangeTransaction) { + throw new InvalidStateError(); + } + + const { btx } = this._confirmActiveTransaction(); + + const index = this._indexesCache.get(indexName); + if (index !== undefined) { + index._deleted = true; + } + + this._backend.deleteIndex(btx, this._name, indexName); + } + + // http://www.w3.org/TR/2015/REC-IndexedDB-20150108/#widl-IDBObjectStore-count-IDBRequest-any-key + public count(key?: IDBValidKey | BridgeIDBKeyRange) { + if (key === null) { + key = undefined; + } + if (key !== undefined && !(key instanceof BridgeIDBKeyRange)) { + key = BridgeIDBKeyRange.only(valueToKey(key)); + } + + const recordGetRequest: RecordGetRequest = { + direction: "next", + indexName: undefined, + lastIndexPosition: undefined, + limit: -1, + objectStoreName: this._name, + lastObjectStorePosition: undefined, + range: key, + resultLevel: ResultLevel.OnlyCount, + }; + + const operation = async () => { + const { btx } = this._confirmActiveTransaction(); + const result = await this._backend.getRecords(btx, recordGetRequest); + return result.count; + }; + + return this.transaction._execRequestAsync({ operation, source: this }); + } + + public toString() { + return "[object IDBObjectStore]"; + } +} + +/** @public */ +export class BridgeIDBRequest extends FakeEventTarget { + _result: any = null; + _error: Error | null | undefined = null; + source: BridgeIDBCursor | BridgeIDBIndex | BridgeIDBObjectStore | null = null; + transaction: BridgeIDBTransaction | null = null; + readyState: "done" | "pending" = "pending"; + onsuccess: EventListener | null = null; + onerror: EventListener | null = null; + + get error() { + if (this.readyState === "pending") { + throw new InvalidStateError(); + } + return this._error; + } + + set error(value: any) { + this._error = value; + } + + get result() { + if (this.readyState === "pending") { + throw new InvalidStateError(); + } + return this._result; + } + + set result(value: any) { + this._result = value; + } + + toString() { + return "[object IDBRequest]"; + } + + _finishWithError(err: Error) { + this.result = undefined; + this.readyState = "done"; + + this.error = new Error(err.message); + this.error.name = err.name; + + const event = new FakeEvent("error", { + bubbles: true, + cancelable: true, + }); + event.eventPath = []; + this.dispatchEvent(event); + } + + _finishWithResult(result: any) { + this.result = result; + this.readyState = "done"; + + const event = new FakeEvent("success"); + event.eventPath = []; + this.dispatchEvent(event); + } +} + +/** @public */ +export class BridgeIDBOpenDBRequest extends BridgeIDBRequest { + public onupgradeneeded: EventListener | null = null; + public onblocked: EventListener | null = null; + + constructor() { + super(); + // https://www.w3.org/TR/IndexedDB/#open-requests + this.source = null; + } + + public toString() { + return "[object IDBOpenDBRequest]"; + } +} + +// http://www.w3.org/TR/2015/REC-IndexedDB-20150108/#transaction +/** @public */ +export class BridgeIDBTransaction extends FakeEventTarget { + public _state: "active" | "inactive" | "committing" | "finished" = "active"; + public _started = false; + public _objectStoresCache: Map = new Map(); + + public _backendTransaction?: DatabaseTransaction; + + public objectStoreNames: FakeDOMStringList; + public mode: IDBTransactionMode; + public db: BridgeIDBDatabase; + public error: Error | null = null; + public onabort: EventListener | null = null; + public oncomplete: EventListener | null = null; + public onerror: EventListener | null = null; + + private _waitPromise: Promise; + private _resolveWait: () => void; + + public _scope: Set; + private _requests: Array<{ + operation: () => void; + request: BridgeIDBRequest; + }> = []; + + get _backend(): Backend { + return this.db._backend; + } + + constructor( + storeNames: string[], + mode: IDBTransactionMode, + db: BridgeIDBDatabase, + backendTransaction?: DatabaseTransaction, + ) { + super(); + + const myOpenPromise = openPromise(); + this._waitPromise = myOpenPromise.promise; + this._resolveWait = myOpenPromise.resolve; + + this._scope = new Set(storeNames); + this._backendTransaction = backendTransaction; + this.mode = mode; + this.db = db; + this.objectStoreNames = fakeDOMStringList(Array.from(this._scope).sort()); + + this.db._transactions.push(this); + } + + // http://www.w3.org/TR/2015/REC-IndexedDB-20150108/#dfn-steps-for-aborting-a-transaction + async _abort(errName: string | null) { + this._state = "finished"; + + if (errName !== null) { + const e = new Error(); + e.name = errName; + this.error = e; + } + + // Should this directly remove from _requests? + for (const { request } of this._requests) { + 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); + } + } + } + + // Only roll back if we actually executed the scheduled operations. + const maybeBtx = this._backendTransaction; + if (maybeBtx) { + await this._backend.rollback(maybeBtx); + } + + queueTask(() => { + const event = new FakeEvent("abort", { + bubbles: true, + cancelable: false, + }); + event.eventPath = [this.db]; + this.dispatchEvent(event); + }); + } + + public abort() { + if (this._state === "committing" || this._state === "finished") { + throw new InvalidStateError(); + } + this._state = "active"; + + this._abort(null); + } + + // http://w3c.github.io/IndexedDB/#dom-idbtransaction-objectstore + public objectStore(name: string) { + if (this._state !== "active") { + throw new InvalidStateError(); + } + + const objectStore = this._objectStoresCache.get(name); + if (objectStore !== undefined) { + return objectStore; + } + + return new BridgeIDBObjectStore(this, name); + } + + // http://www.w3.org/TR/2015/REC-IndexedDB-20150108/#dfn-steps-for-asynchronously-executing-a-request + public _execRequestAsync(obj: RequestObj) { + const source = obj.source; + const operation = obj.operation; + let request = obj.hasOwnProperty("request") ? obj.request : null; + + if (this._state !== "active") { + throw new TransactionInactiveError(); + } + + // Request should only be passed for cursors + if (!request) { + if (!source) { + // Special requests like indexes that just need to run some code + request = new BridgeIDBRequest(); + } else { + request = new BridgeIDBRequest(); + request.source = source; + request.transaction = (source as any).transaction; + } + } + + this._requests.push({ + operation, + request, + }); + + 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; + + if (!this._backendTransaction) { + this._backendTransaction = await this._backend.beginTransaction( + this.db._backendConnection, + Array.from(this._scope), + this.mode, + ); + } + + // Remove from request queue - cursor ones will be added back if necessary by cursor.continue and such + let operation; + let request; + while (this._requests.length > 0) { + const r = this._requests.shift(); + + // This should only be false if transaction was aborted + if (r && r.request.readyState !== "done") { + request = r.request; + operation = r.operation; + break; + } + } + + if (request && operation) { + if (!request.source) { + // Special requests like indexes that just need to run some code, with error handling already built into + // operation + await operation(); + } else { + let event; + try { + BridgeIDBFactory.enableTracing && + console.log("TRACE: running operation in transaction"); + const result = await operation(); + BridgeIDBFactory.enableTracing && + console.log( + "TRACE: operation in transaction finished with success", + ); + request.readyState = "done"; + 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"; + } + event = new FakeEvent("success", { + 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; + + // http://www.w3.org/TR/2015/REC-IndexedDB-20150108/#dfn-fire-an-error-event + if (this._state === "inactive") { + this._state = "active"; + } + event = new FakeEvent("error", { + bubbles: true, + cancelable: true, + }); + + try { + event.eventPath = [this.db, this]; + request.dispatchEvent(event); + } catch (err) { + if (this._state !== "committing") { + this._abort("AbortError"); + } + throw err; + } + if (!event.canceled) { + this._abort(err.name); + } + } + } + + // On to the next one + if (this._requests.length > 0) { + this._start(); + } else { + // Give it another chance for new handlers to be set before finishing + queueTask(() => this._start()); + } + return; + } + + 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 on transaction"); + } + const event = new FakeEvent("complete"); + event.eventPath = [this, this.db]; + this.dispatchEvent(event); + } + + const idx = this.db._transactions.indexOf(this); + if (idx < 0) { + throw Error("invariant failed"); + } + this.db._transactions.splice(idx, 1); + + this._resolveWait(); + } + } + + public commit() { + if (this._state !== "active") { + throw new InvalidStateError(); + } + + this._state = "committing"; + // We now just wait for auto-commit ... + } + + public toString() { + return "[object IDBRequest]"; + } + + _waitDone(): Promise { + return this._waitPromise; + } +} + +export class BridgeIDBVersionChangeEvent extends FakeEvent { + public newVersion: number | null; + public oldVersion: number; + + constructor( + type: "blocked" | "success" | "upgradeneeded" | "versionchange", + parameters: { newVersion?: number | null; oldVersion?: number } = {}, + ) { + super(type); + + this.newVersion = + parameters.newVersion !== undefined ? parameters.newVersion : null; + this.oldVersion = + parameters.oldVersion !== undefined ? parameters.oldVersion : 0; + } + + public toString() { + return "[object IDBVersionChangeEvent]"; + } +} diff --git a/packages/idb-bridge/src/index.ts b/packages/idb-bridge/src/index.ts index 4c29be968..b6c15249d 100644 --- a/packages/idb-bridge/src/index.ts +++ b/packages/idb-bridge/src/index.ts @@ -1,26 +1,3 @@ -import { BridgeIDBFactory, DatabaseList } 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"; -import { - Value, - CursorSource, - CursorRange, - BridgeIDBCursorDirection, - Key, - KeyPath, - TransactionMode, - FakeDOMStringList, - RequestObj, - BridgeIDBDatabaseInfo, - EventType, -} from "./util/types"; import { DatabaseTransaction, RecordGetResponse, @@ -45,12 +22,23 @@ import { MemoryBackendDump, } from "./MemoryBackend"; import { Event } from "./idbtypes"; +import { + BridgeIDBCursor, + BridgeIDBDatabase, + BridgeIDBFactory, + BridgeIDBIndex, + BridgeIDBKeyRange, + BridgeIDBObjectStore, + BridgeIDBOpenDBRequest, + BridgeIDBRequest, + BridgeIDBTransaction, + DatabaseList, + RequestObj, +} from "./bridge-idb"; export { BridgeIDBCursor, - BridgeIDBCursorDirection, BridgeIDBDatabase, - BridgeIDBDatabaseInfo, BridgeIDBFactory, BridgeIDBIndex, BridgeIDBKeyRange, @@ -58,33 +46,26 @@ export { BridgeIDBOpenDBRequest, BridgeIDBRequest, BridgeIDBTransaction, - Value, - CursorSource, - CursorRange, - Key, + StoreLevel, + ResultLevel, +}; +export type { DatabaseTransaction, RecordGetRequest, RecordGetResponse, - KeyPath, Schema, Backend, - TransactionMode, DatabaseList, RecordStoreRequest, RecordStoreResponse, - FakeEventTarget, DatabaseConnection, - FakeDOMStringList, ObjectStoreProperties, RequestObj, - StoreLevel, - ResultLevel, DatabaseDump, ObjectStoreDump, IndexDump, IndexRecord, ObjectStoreRecord, - EventType, IndexProperties, MemoryBackendDump, Event, diff --git a/packages/idb-bridge/src/tree/b+tree.ts b/packages/idb-bridge/src/tree/b+tree.ts index ea09d0c0c..a45a98620 100644 --- a/packages/idb-bridge/src/tree/b+tree.ts +++ b/packages/idb-bridge/src/tree/b+tree.ts @@ -25,7 +25,7 @@ SPDX-License-Identifier: MIT // Original repository: https://github.com/qwertie/btree-typescript import { ISortedMap, ISortedMapF } from "./interfaces"; -export { +export type { ISetSource, ISetSink, ISet, diff --git a/packages/idb-bridge/src/util/FakeEvent.ts b/packages/idb-bridge/src/util/FakeEvent.ts index 1c558d8a0..c16a58fd3 100644 --- a/packages/idb-bridge/src/util/FakeEvent.ts +++ b/packages/idb-bridge/src/util/FakeEvent.ts @@ -15,9 +15,18 @@ */ import FakeEventTarget from "./FakeEventTarget"; -import { EventType } from "./types"; import { Event, EventTarget } from "../idbtypes"; +/** @public */ +export type EventType = + | "abort" + | "blocked" + | "complete" + | "error" + | "success" + | "upgradeneeded" + | "versionchange"; + export class FakeEvent implements Event { public eventPath: FakeEventTarget[] = []; public type: EventType; diff --git a/packages/idb-bridge/src/util/FakeEventTarget.ts b/packages/idb-bridge/src/util/FakeEventTarget.ts index 77df768f2..d2f46c98f 100644 --- a/packages/idb-bridge/src/util/FakeEventTarget.ts +++ b/packages/idb-bridge/src/util/FakeEventTarget.ts @@ -15,8 +15,7 @@ */ import { InvalidStateError } from "./errors"; -import FakeEvent from "./FakeEvent"; -import { EventType } from "./types"; +import FakeEvent, { EventType } from "./FakeEvent"; import { EventTarget, Event, diff --git a/packages/idb-bridge/src/util/canInjectKey.ts b/packages/idb-bridge/src/util/canInjectKey.ts index 8a9666901..09ecbd3ad 100644 --- a/packages/idb-bridge/src/util/canInjectKey.ts +++ b/packages/idb-bridge/src/util/canInjectKey.ts @@ -14,10 +14,10 @@ permissions and limitations under the License. */ -import { KeyPath, Value } from "./types"; +import { IDBKeyPath } from "../idbtypes"; // http://w3c.github.io/IndexedDB/#check-that-a-key-could-be-injected-into-a-value -const canInjectKey = (keyPath: KeyPath, value: Value) => { +const canInjectKey = (keyPath: IDBKeyPath, value: any) => { if (Array.isArray(keyPath)) { // tslint:disable-next-line max-line-length throw new Error( diff --git a/packages/idb-bridge/src/util/deepEquals.ts b/packages/idb-bridge/src/util/deepEquals.ts index 716786aba..bb7c0269c 100644 --- a/packages/idb-bridge/src/util/deepEquals.ts +++ b/packages/idb-bridge/src/util/deepEquals.ts @@ -24,7 +24,7 @@ const isArray = Array.isArray; const keyList = Object.keys; const hasProp = Object.prototype.hasOwnProperty; -function deepEquals(a: any, b: any): boolean { +export function deepEquals(a: any, b: any): boolean { if (a === b) return true; if (a && b && typeof a == "object" && typeof b == "object") { diff --git a/packages/idb-bridge/src/util/extractKey.ts b/packages/idb-bridge/src/util/extractKey.ts index 27f20310c..7aa8bd173 100644 --- a/packages/idb-bridge/src/util/extractKey.ts +++ b/packages/idb-bridge/src/util/extractKey.ts @@ -15,13 +15,13 @@ permissions and limitations under the License. */ -import { Key, KeyPath, Value } from "./types"; +import { IDBKeyPath, IDBValidKey } from "../idbtypes"; 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: KeyPath, value: Value) => { +const extractKey = (keyPath: IDBKeyPath | IDBKeyPath[], value: any) => { if (Array.isArray(keyPath)) { - const result: Key[] = []; + const result: IDBValidKey[] = []; for (let item of keyPath) { // This doesn't make sense to me based on the spec, but it is needed to pass the W3C KeyPath tests (see same diff --git a/packages/idb-bridge/src/util/fakeDOMStringList.ts b/packages/idb-bridge/src/util/fakeDOMStringList.ts index 5add17588..09ef77003 100644 --- a/packages/idb-bridge/src/util/fakeDOMStringList.ts +++ b/packages/idb-bridge/src/util/fakeDOMStringList.ts @@ -14,11 +14,15 @@ * permissions and limitations under the License. */ -import { FakeDOMStringList } from "./types"; +/** @public */ +export interface FakeDOMStringList extends Array { + contains: (value: string) => boolean; + item: (i: number) => string | undefined; +} // Would be nicer to sublcass Array, but I'd have to sacrifice Node 4 support to do that. -const fakeDOMStringList = (arr: string[]): FakeDOMStringList => { +export const fakeDOMStringList = (arr: string[]): FakeDOMStringList => { const arr2 = arr.slice(); Object.defineProperty(arr2, "contains", { @@ -33,5 +37,3 @@ const fakeDOMStringList = (arr: string[]): FakeDOMStringList => { return arr2 as FakeDOMStringList; }; - -export default fakeDOMStringList; diff --git a/packages/idb-bridge/src/util/getIndexKeys.ts b/packages/idb-bridge/src/util/getIndexKeys.ts index 253dc57bd..77b96b12f 100644 --- a/packages/idb-bridge/src/util/getIndexKeys.ts +++ b/packages/idb-bridge/src/util/getIndexKeys.ts @@ -15,15 +15,15 @@ permissions and limitations under the License. */ -import { Key, Value, KeyPath } from "./types"; +import { IDBKeyPath, IDBValidKey } from "../idbtypes"; import extractKey from "./extractKey"; import valueToKey from "./valueToKey"; export function getIndexKeys( - value: Value, - keyPath: KeyPath, + value: any, + keyPath: IDBKeyPath | IDBKeyPath[], multiEntry: boolean, -): Key[] { +): IDBValidKey[] { if (multiEntry && Array.isArray(keyPath)) { const keys = []; for (const subkeyPath of keyPath) { @@ -36,9 +36,11 @@ export function getIndexKeys( } } return keys; - } else { + } else if (typeof keyPath === "string" || Array.isArray(keyPath)) { let key = extractKey(keyPath, value); return [valueToKey(key)]; + } else { + throw Error(`unsupported key path: ${typeof keyPath}`); } } diff --git a/packages/idb-bridge/src/util/injectKey.ts b/packages/idb-bridge/src/util/injectKey.ts index 38add33bd..678f42d28 100644 --- a/packages/idb-bridge/src/util/injectKey.ts +++ b/packages/idb-bridge/src/util/injectKey.ts @@ -15,12 +15,14 @@ permissions and limitations under the License. */ -import { KeyPath, Value, Key } from "./types"; -import canInjectKey from "./canInjectKey"; -import { DataError } from "./errors"; +import { IDBKeyPath, IDBValidKey } from "../idbtypes"; import structuredClone from "./structuredClone"; -export function injectKey(keyPath: KeyPath, value: Value, key: Key): Value { +export function injectKey( + keyPath: IDBKeyPath, + value: any, + key: IDBValidKey, +): any { if (Array.isArray(keyPath)) { // tslint:disable-next-line max-line-length throw new Error( diff --git a/packages/idb-bridge/src/util/makeStoreKeyValue.ts b/packages/idb-bridge/src/util/makeStoreKeyValue.ts index f9006ef51..b535bced5 100644 --- a/packages/idb-bridge/src/util/makeStoreKeyValue.ts +++ b/packages/idb-bridge/src/util/makeStoreKeyValue.ts @@ -14,25 +14,25 @@ permissions and limitations under the License. */ -import { Value, Key, KeyPath } from "./types"; import extractKey from "./extractKey"; import { DataError } from "./errors"; import valueToKey from "./valueToKey"; import structuredClone from "./structuredClone"; import injectKey from "./injectKey"; +import { IDBKeyPath, IDBValidKey } from "../idbtypes"; export interface StoreKeyResult { updatedKeyGenerator: number; - key: Key; - value: Value; + key: IDBValidKey; + value: any; } export function makeStoreKeyValue( - value: Value, - key: Key | undefined, + value: any, + key: IDBValidKey | undefined, currentKeyGenerator: number, autoIncrement: boolean, - keyPath: KeyPath | null, + keyPath: IDBKeyPath | null, ): StoreKeyResult { const haveKey = key !== null && key !== undefined; const haveKeyPath = keyPath !== null && keyPath !== undefined; @@ -89,7 +89,7 @@ export function makeStoreKeyValue( updatedKeyGenerator = currentKeyGenerator; } return { - key: key, + key: key!, value: value, updatedKeyGenerator, }; diff --git a/packages/idb-bridge/src/util/types.ts b/packages/idb-bridge/src/util/types.ts deleted file mode 100644 index b07f5ad00..000000000 --- a/packages/idb-bridge/src/util/types.ts +++ /dev/null @@ -1,84 +0,0 @@ -/* - Copyright 2017 Jeremy Scheff - Copyright 2019 Florian Dold - - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express - or implied. See the License for the specific language governing - permissions and limitations under the License. -*/ - -import { BridgeIDBRequest } from "../BridgeIDBRequest"; -import { BridgeIDBKeyRange } from "../BridgeIDBKeyRange"; -import { BridgeIDBIndex } from "../BridgeIDBIndex"; -import { BridgeIDBObjectStore } from "../BridgeIDBObjectStore"; -import { Event } from "../idbtypes"; - -/** @public */ -export type EventType = - | "abort" - | "blocked" - | "complete" - | "error" - | "success" - | "upgradeneeded" - | "versionchange"; - -/** @public */ -export type CursorSource = BridgeIDBIndex | BridgeIDBObjectStore; - -/** @public */ -export interface FakeDOMStringList extends Array { - contains: (value: string) => boolean; - item: (i: number) => string | undefined; -} - -/** - * @public - */ -export type BridgeIDBCursorDirection = - | "next" - | "nextunique" - | "prev" - | "prevunique"; - -/** @public */ -export type KeyPath = string | string[]; - -/** @public */ -export type Key = any; - -/** @public */ -export type CursorRange = Key | BridgeIDBKeyRange | undefined; - -/** @public */ -export type Value = any; - -/** @public */ -export interface Record { - key: Key; - value: Key | Value; // For indexes, will be Key. For object stores, will be Value. -} - -/** @public */ -export type TransactionMode = "readonly" | "readwrite" | "versionchange"; - -/** @public */ -export interface BridgeIDBDatabaseInfo { - name: string; - version: number; -} - -/** @public */ -export interface RequestObj { - operation: () => Promise; - request?: BridgeIDBRequest | undefined; - source?: any; -} diff --git a/packages/idb-bridge/src/util/validateKeyPath.ts b/packages/idb-bridge/src/util/validateKeyPath.ts index 072832190..8057172df 100644 --- a/packages/idb-bridge/src/util/validateKeyPath.ts +++ b/packages/idb-bridge/src/util/validateKeyPath.ts @@ -14,24 +14,25 @@ permissions and limitations under the License. */ -import { KeyPath } from "./types"; +import { IDBKeyPath } from "../idbtypes"; // http://www.w3.org/TR/2015/REC-IndexedDB-20150108/#dfn-valid-key-path -const validateKeyPath = (keyPath: KeyPath, parent?: "array" | "string") => { +const validateKeyPath = (keyPath: 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; if ( - keyPath !== undefined && - keyPath !== null && - typeof keyPath !== "string" && - keyPath.toString && - (parent === "array" || !Array.isArray(keyPath)) + myKeyPath !== undefined && + myKeyPath !== null && + typeof myKeyPath !== "string" && + (myKeyPath as any).toString && + (parent === "array" || !Array.isArray(myKeyPath)) ) { - keyPath = keyPath.toString(); + myKeyPath = (myKeyPath as any).toString(); } - if (typeof keyPath === "string") { - if (keyPath === "" && parent !== "string") { + if (typeof myKeyPath === "string") { + if (myKeyPath === "" && parent !== "string") { return; } try { @@ -39,33 +40,33 @@ const validateKeyPath = (keyPath: KeyPath, parent?: "array" | "string") => { // reserved words at beginning removed // tslint:disable-next-line max-line-length const validIdentifierRegex = /^(?:[\$A-Z_a-z\xAA\xB5\xBA\xC0-\xD6\xD8-\xF6\xF8-\u02C1\u02C6-\u02D1\u02E0-\u02E4\u02EC\u02EE\u0370-\u0374\u0376\u0377\u037A-\u037D\u037F\u0386\u0388-\u038A\u038C\u038E-\u03A1\u03A3-\u03F5\u03F7-\u0481\u048A-\u052F\u0531-\u0556\u0559\u0561-\u0587\u05D0-\u05EA\u05F0-\u05F2\u0620-\u064A\u066E\u066F\u0671-\u06D3\u06D5\u06E5\u06E6\u06EE\u06EF\u06FA-\u06FC\u06FF\u0710\u0712-\u072F\u074D-\u07A5\u07B1\u07CA-\u07EA\u07F4\u07F5\u07FA\u0800-\u0815\u081A\u0824\u0828\u0840-\u0858\u08A0-\u08B2\u0904-\u0939\u093D\u0950\u0958-\u0961\u0971-\u0980\u0985-\u098C\u098F\u0990\u0993-\u09A8\u09AA-\u09B0\u09B2\u09B6-\u09B9\u09BD\u09CE\u09DC\u09DD\u09DF-\u09E1\u09F0\u09F1\u0A05-\u0A0A\u0A0F\u0A10\u0A13-\u0A28\u0A2A-\u0A30\u0A32\u0A33\u0A35\u0A36\u0A38\u0A39\u0A59-\u0A5C\u0A5E\u0A72-\u0A74\u0A85-\u0A8D\u0A8F-\u0A91\u0A93-\u0AA8\u0AAA-\u0AB0\u0AB2\u0AB3\u0AB5-\u0AB9\u0ABD\u0AD0\u0AE0\u0AE1\u0B05-\u0B0C\u0B0F\u0B10\u0B13-\u0B28\u0B2A-\u0B30\u0B32\u0B33\u0B35-\u0B39\u0B3D\u0B5C\u0B5D\u0B5F-\u0B61\u0B71\u0B83\u0B85-\u0B8A\u0B8E-\u0B90\u0B92-\u0B95\u0B99\u0B9A\u0B9C\u0B9E\u0B9F\u0BA3\u0BA4\u0BA8-\u0BAA\u0BAE-\u0BB9\u0BD0\u0C05-\u0C0C\u0C0E-\u0C10\u0C12-\u0C28\u0C2A-\u0C39\u0C3D\u0C58\u0C59\u0C60\u0C61\u0C85-\u0C8C\u0C8E-\u0C90\u0C92-\u0CA8\u0CAA-\u0CB3\u0CB5-\u0CB9\u0CBD\u0CDE\u0CE0\u0CE1\u0CF1\u0CF2\u0D05-\u0D0C\u0D0E-\u0D10\u0D12-\u0D3A\u0D3D\u0D4E\u0D60\u0D61\u0D7A-\u0D7F\u0D85-\u0D96\u0D9A-\u0DB1\u0DB3-\u0DBB\u0DBD\u0DC0-\u0DC6\u0E01-\u0E30\u0E32\u0E33\u0E40-\u0E46\u0E81\u0E82\u0E84\u0E87\u0E88\u0E8A\u0E8D\u0E94-\u0E97\u0E99-\u0E9F\u0EA1-\u0EA3\u0EA5\u0EA7\u0EAA\u0EAB\u0EAD-\u0EB0\u0EB2\u0EB3\u0EBD\u0EC0-\u0EC4\u0EC6\u0EDC-\u0EDF\u0F00\u0F40-\u0F47\u0F49-\u0F6C\u0F88-\u0F8C\u1000-\u102A\u103F\u1050-\u1055\u105A-\u105D\u1061\u1065\u1066\u106E-\u1070\u1075-\u1081\u108E\u10A0-\u10C5\u10C7\u10CD\u10D0-\u10FA\u10FC-\u1248\u124A-\u124D\u1250-\u1256\u1258\u125A-\u125D\u1260-\u1288\u128A-\u128D\u1290-\u12B0\u12B2-\u12B5\u12B8-\u12BE\u12C0\u12C2-\u12C5\u12C8-\u12D6\u12D8-\u1310\u1312-\u1315\u1318-\u135A\u1380-\u138F\u13A0-\u13F4\u1401-\u166C\u166F-\u167F\u1681-\u169A\u16A0-\u16EA\u16EE-\u16F8\u1700-\u170C\u170E-\u1711\u1720-\u1731\u1740-\u1751\u1760-\u176C\u176E-\u1770\u1780-\u17B3\u17D7\u17DC\u1820-\u1877\u1880-\u18A8\u18AA\u18B0-\u18F5\u1900-\u191E\u1950-\u196D\u1970-\u1974\u1980-\u19AB\u19C1-\u19C7\u1A00-\u1A16\u1A20-\u1A54\u1AA7\u1B05-\u1B33\u1B45-\u1B4B\u1B83-\u1BA0\u1BAE\u1BAF\u1BBA-\u1BE5\u1C00-\u1C23\u1C4D-\u1C4F\u1C5A-\u1C7D\u1CE9-\u1CEC\u1CEE-\u1CF1\u1CF5\u1CF6\u1D00-\u1DBF\u1E00-\u1F15\u1F18-\u1F1D\u1F20-\u1F45\u1F48-\u1F4D\u1F50-\u1F57\u1F59\u1F5B\u1F5D\u1F5F-\u1F7D\u1F80-\u1FB4\u1FB6-\u1FBC\u1FBE\u1FC2-\u1FC4\u1FC6-\u1FCC\u1FD0-\u1FD3\u1FD6-\u1FDB\u1FE0-\u1FEC\u1FF2-\u1FF4\u1FF6-\u1FFC\u2071\u207F\u2090-\u209C\u2102\u2107\u210A-\u2113\u2115\u2119-\u211D\u2124\u2126\u2128\u212A-\u212D\u212F-\u2139\u213C-\u213F\u2145-\u2149\u214E\u2160-\u2188\u2C00-\u2C2E\u2C30-\u2C5E\u2C60-\u2CE4\u2CEB-\u2CEE\u2CF2\u2CF3\u2D00-\u2D25\u2D27\u2D2D\u2D30-\u2D67\u2D6F\u2D80-\u2D96\u2DA0-\u2DA6\u2DA8-\u2DAE\u2DB0-\u2DB6\u2DB8-\u2DBE\u2DC0-\u2DC6\u2DC8-\u2DCE\u2DD0-\u2DD6\u2DD8-\u2DDE\u2E2F\u3005-\u3007\u3021-\u3029\u3031-\u3035\u3038-\u303C\u3041-\u3096\u309D-\u309F\u30A1-\u30FA\u30FC-\u30FF\u3105-\u312D\u3131-\u318E\u31A0-\u31BA\u31F0-\u31FF\u3400-\u4DB5\u4E00-\u9FCC\uA000-\uA48C\uA4D0-\uA4FD\uA500-\uA60C\uA610-\uA61F\uA62A\uA62B\uA640-\uA66E\uA67F-\uA69D\uA6A0-\uA6EF\uA717-\uA71F\uA722-\uA788\uA78B-\uA78E\uA790-\uA7AD\uA7B0\uA7B1\uA7F7-\uA801\uA803-\uA805\uA807-\uA80A\uA80C-\uA822\uA840-\uA873\uA882-\uA8B3\uA8F2-\uA8F7\uA8FB\uA90A-\uA925\uA930-\uA946\uA960-\uA97C\uA984-\uA9B2\uA9CF\uA9E0-\uA9E4\uA9E6-\uA9EF\uA9FA-\uA9FE\uAA00-\uAA28\uAA40-\uAA42\uAA44-\uAA4B\uAA60-\uAA76\uAA7A\uAA7E-\uAAAF\uAAB1\uAAB5\uAAB6\uAAB9-\uAABD\uAAC0\uAAC2\uAADB-\uAADD\uAAE0-\uAAEA\uAAF2-\uAAF4\uAB01-\uAB06\uAB09-\uAB0E\uAB11-\uAB16\uAB20-\uAB26\uAB28-\uAB2E\uAB30-\uAB5A\uAB5C-\uAB5F\uAB64\uAB65\uABC0-\uABE2\uAC00-\uD7A3\uD7B0-\uD7C6\uD7CB-\uD7FB\uF900-\uFA6D\uFA70-\uFAD9\uFB00-\uFB06\uFB13-\uFB17\uFB1D\uFB1F-\uFB28\uFB2A-\uFB36\uFB38-\uFB3C\uFB3E\uFB40\uFB41\uFB43\uFB44\uFB46-\uFBB1\uFBD3-\uFD3D\uFD50-\uFD8F\uFD92-\uFDC7\uFDF0-\uFDFB\uFE70-\uFE74\uFE76-\uFEFC\uFF21-\uFF3A\uFF41-\uFF5A\uFF66-\uFFBE\uFFC2-\uFFC7\uFFCA-\uFFCF\uFFD2-\uFFD7\uFFDA-\uFFDC])(?:[\$0-9A-Z_a-z\xAA\xB5\xBA\xC0-\xD6\xD8-\xF6\xF8-\u02C1\u02C6-\u02D1\u02E0-\u02E4\u02EC\u02EE\u0300-\u0374\u0376\u0377\u037A-\u037D\u037F\u0386\u0388-\u038A\u038C\u038E-\u03A1\u03A3-\u03F5\u03F7-\u0481\u0483-\u0487\u048A-\u052F\u0531-\u0556\u0559\u0561-\u0587\u0591-\u05BD\u05BF\u05C1\u05C2\u05C4\u05C5\u05C7\u05D0-\u05EA\u05F0-\u05F2\u0610-\u061A\u0620-\u0669\u066E-\u06D3\u06D5-\u06DC\u06DF-\u06E8\u06EA-\u06FC\u06FF\u0710-\u074A\u074D-\u07B1\u07C0-\u07F5\u07FA\u0800-\u082D\u0840-\u085B\u08A0-\u08B2\u08E4-\u0963\u0966-\u096F\u0971-\u0983\u0985-\u098C\u098F\u0990\u0993-\u09A8\u09AA-\u09B0\u09B2\u09B6-\u09B9\u09BC-\u09C4\u09C7\u09C8\u09CB-\u09CE\u09D7\u09DC\u09DD\u09DF-\u09E3\u09E6-\u09F1\u0A01-\u0A03\u0A05-\u0A0A\u0A0F\u0A10\u0A13-\u0A28\u0A2A-\u0A30\u0A32\u0A33\u0A35\u0A36\u0A38\u0A39\u0A3C\u0A3E-\u0A42\u0A47\u0A48\u0A4B-\u0A4D\u0A51\u0A59-\u0A5C\u0A5E\u0A66-\u0A75\u0A81-\u0A83\u0A85-\u0A8D\u0A8F-\u0A91\u0A93-\u0AA8\u0AAA-\u0AB0\u0AB2\u0AB3\u0AB5-\u0AB9\u0ABC-\u0AC5\u0AC7-\u0AC9\u0ACB-\u0ACD\u0AD0\u0AE0-\u0AE3\u0AE6-\u0AEF\u0B01-\u0B03\u0B05-\u0B0C\u0B0F\u0B10\u0B13-\u0B28\u0B2A-\u0B30\u0B32\u0B33\u0B35-\u0B39\u0B3C-\u0B44\u0B47\u0B48\u0B4B-\u0B4D\u0B56\u0B57\u0B5C\u0B5D\u0B5F-\u0B63\u0B66-\u0B6F\u0B71\u0B82\u0B83\u0B85-\u0B8A\u0B8E-\u0B90\u0B92-\u0B95\u0B99\u0B9A\u0B9C\u0B9E\u0B9F\u0BA3\u0BA4\u0BA8-\u0BAA\u0BAE-\u0BB9\u0BBE-\u0BC2\u0BC6-\u0BC8\u0BCA-\u0BCD\u0BD0\u0BD7\u0BE6-\u0BEF\u0C00-\u0C03\u0C05-\u0C0C\u0C0E-\u0C10\u0C12-\u0C28\u0C2A-\u0C39\u0C3D-\u0C44\u0C46-\u0C48\u0C4A-\u0C4D\u0C55\u0C56\u0C58\u0C59\u0C60-\u0C63\u0C66-\u0C6F\u0C81-\u0C83\u0C85-\u0C8C\u0C8E-\u0C90\u0C92-\u0CA8\u0CAA-\u0CB3\u0CB5-\u0CB9\u0CBC-\u0CC4\u0CC6-\u0CC8\u0CCA-\u0CCD\u0CD5\u0CD6\u0CDE\u0CE0-\u0CE3\u0CE6-\u0CEF\u0CF1\u0CF2\u0D01-\u0D03\u0D05-\u0D0C\u0D0E-\u0D10\u0D12-\u0D3A\u0D3D-\u0D44\u0D46-\u0D48\u0D4A-\u0D4E\u0D57\u0D60-\u0D63\u0D66-\u0D6F\u0D7A-\u0D7F\u0D82\u0D83\u0D85-\u0D96\u0D9A-\u0DB1\u0DB3-\u0DBB\u0DBD\u0DC0-\u0DC6\u0DCA\u0DCF-\u0DD4\u0DD6\u0DD8-\u0DDF\u0DE6-\u0DEF\u0DF2\u0DF3\u0E01-\u0E3A\u0E40-\u0E4E\u0E50-\u0E59\u0E81\u0E82\u0E84\u0E87\u0E88\u0E8A\u0E8D\u0E94-\u0E97\u0E99-\u0E9F\u0EA1-\u0EA3\u0EA5\u0EA7\u0EAA\u0EAB\u0EAD-\u0EB9\u0EBB-\u0EBD\u0EC0-\u0EC4\u0EC6\u0EC8-\u0ECD\u0ED0-\u0ED9\u0EDC-\u0EDF\u0F00\u0F18\u0F19\u0F20-\u0F29\u0F35\u0F37\u0F39\u0F3E-\u0F47\u0F49-\u0F6C\u0F71-\u0F84\u0F86-\u0F97\u0F99-\u0FBC\u0FC6\u1000-\u1049\u1050-\u109D\u10A0-\u10C5\u10C7\u10CD\u10D0-\u10FA\u10FC-\u1248\u124A-\u124D\u1250-\u1256\u1258\u125A-\u125D\u1260-\u1288\u128A-\u128D\u1290-\u12B0\u12B2-\u12B5\u12B8-\u12BE\u12C0\u12C2-\u12C5\u12C8-\u12D6\u12D8-\u1310\u1312-\u1315\u1318-\u135A\u135D-\u135F\u1380-\u138F\u13A0-\u13F4\u1401-\u166C\u166F-\u167F\u1681-\u169A\u16A0-\u16EA\u16EE-\u16F8\u1700-\u170C\u170E-\u1714\u1720-\u1734\u1740-\u1753\u1760-\u176C\u176E-\u1770\u1772\u1773\u1780-\u17D3\u17D7\u17DC\u17DD\u17E0-\u17E9\u180B-\u180D\u1810-\u1819\u1820-\u1877\u1880-\u18AA\u18B0-\u18F5\u1900-\u191E\u1920-\u192B\u1930-\u193B\u1946-\u196D\u1970-\u1974\u1980-\u19AB\u19B0-\u19C9\u19D0-\u19D9\u1A00-\u1A1B\u1A20-\u1A5E\u1A60-\u1A7C\u1A7F-\u1A89\u1A90-\u1A99\u1AA7\u1AB0-\u1ABD\u1B00-\u1B4B\u1B50-\u1B59\u1B6B-\u1B73\u1B80-\u1BF3\u1C00-\u1C37\u1C40-\u1C49\u1C4D-\u1C7D\u1CD0-\u1CD2\u1CD4-\u1CF6\u1CF8\u1CF9\u1D00-\u1DF5\u1DFC-\u1F15\u1F18-\u1F1D\u1F20-\u1F45\u1F48-\u1F4D\u1F50-\u1F57\u1F59\u1F5B\u1F5D\u1F5F-\u1F7D\u1F80-\u1FB4\u1FB6-\u1FBC\u1FBE\u1FC2-\u1FC4\u1FC6-\u1FCC\u1FD0-\u1FD3\u1FD6-\u1FDB\u1FE0-\u1FEC\u1FF2-\u1FF4\u1FF6-\u1FFC\u200C\u200D\u203F\u2040\u2054\u2071\u207F\u2090-\u209C\u20D0-\u20DC\u20E1\u20E5-\u20F0\u2102\u2107\u210A-\u2113\u2115\u2119-\u211D\u2124\u2126\u2128\u212A-\u212D\u212F-\u2139\u213C-\u213F\u2145-\u2149\u214E\u2160-\u2188\u2C00-\u2C2E\u2C30-\u2C5E\u2C60-\u2CE4\u2CEB-\u2CF3\u2D00-\u2D25\u2D27\u2D2D\u2D30-\u2D67\u2D6F\u2D7F-\u2D96\u2DA0-\u2DA6\u2DA8-\u2DAE\u2DB0-\u2DB6\u2DB8-\u2DBE\u2DC0-\u2DC6\u2DC8-\u2DCE\u2DD0-\u2DD6\u2DD8-\u2DDE\u2DE0-\u2DFF\u2E2F\u3005-\u3007\u3021-\u302F\u3031-\u3035\u3038-\u303C\u3041-\u3096\u3099\u309A\u309D-\u309F\u30A1-\u30FA\u30FC-\u30FF\u3105-\u312D\u3131-\u318E\u31A0-\u31BA\u31F0-\u31FF\u3400-\u4DB5\u4E00-\u9FCC\uA000-\uA48C\uA4D0-\uA4FD\uA500-\uA60C\uA610-\uA62B\uA640-\uA66F\uA674-\uA67D\uA67F-\uA69D\uA69F-\uA6F1\uA717-\uA71F\uA722-\uA788\uA78B-\uA78E\uA790-\uA7AD\uA7B0\uA7B1\uA7F7-\uA827\uA840-\uA873\uA880-\uA8C4\uA8D0-\uA8D9\uA8E0-\uA8F7\uA8FB\uA900-\uA92D\uA930-\uA953\uA960-\uA97C\uA980-\uA9C0\uA9CF-\uA9D9\uA9E0-\uA9FE\uAA00-\uAA36\uAA40-\uAA4D\uAA50-\uAA59\uAA60-\uAA76\uAA7A-\uAAC2\uAADB-\uAADD\uAAE0-\uAAEF\uAAF2-\uAAF6\uAB01-\uAB06\uAB09-\uAB0E\uAB11-\uAB16\uAB20-\uAB26\uAB28-\uAB2E\uAB30-\uAB5A\uAB5C-\uAB5F\uAB64\uAB65\uABC0-\uABEA\uABEC\uABED\uABF0-\uABF9\uAC00-\uD7A3\uD7B0-\uD7C6\uD7CB-\uD7FB\uF900-\uFA6D\uFA70-\uFAD9\uFB00-\uFB06\uFB13-\uFB17\uFB1D-\uFB28\uFB2A-\uFB36\uFB38-\uFB3C\uFB3E\uFB40\uFB41\uFB43\uFB44\uFB46-\uFBB1\uFBD3-\uFD3D\uFD50-\uFD8F\uFD92-\uFDC7\uFDF0-\uFDFB\uFE00-\uFE0F\uFE20-\uFE2D\uFE33\uFE34\uFE4D-\uFE4F\uFE70-\uFE74\uFE76-\uFEFC\uFF10-\uFF19\uFF21-\uFF3A\uFF3F\uFF41-\uFF5A\uFF66-\uFFBE\uFFC2-\uFFC7\uFFCA-\uFFCF\uFFD2-\uFFD7\uFFDA-\uFFDC])*$/; - if (keyPath.length >= 1 && validIdentifierRegex.test(keyPath)) { + if (myKeyPath.length >= 1 && validIdentifierRegex.test(myKeyPath)) { return; } } catch (err) { throw new SyntaxError(err.message); } - if (keyPath.indexOf(" ") >= 0) { + if (myKeyPath.indexOf(" ") >= 0) { throw new SyntaxError( "The keypath argument contains an invalid key path (no spaces allowed).", ); } } - if (Array.isArray(keyPath) && keyPath.length > 0) { + if (Array.isArray(myKeyPath) && myKeyPath.length > 0) { if (parent) { // No nested arrays throw new SyntaxError( "The keypath argument contains an invalid key path (nested arrays).", ); } - for (const part of keyPath) { + for (const part of myKeyPath) { validateKeyPath(part, "array"); } return; - } else if (typeof keyPath === "string" && keyPath.indexOf(".") >= 0) { - keyPath = keyPath.split("."); - for (const part of keyPath) { + } else if (typeof myKeyPath === "string" && myKeyPath.indexOf(".") >= 0) { + myKeyPath = myKeyPath.split("."); + for (const part of myKeyPath) { validateKeyPath(part, "string"); } return; diff --git a/packages/idb-bridge/src/util/valueToKey.ts b/packages/idb-bridge/src/util/valueToKey.ts index 5cf5b2b1b..c3661f9a1 100644 --- a/packages/idb-bridge/src/util/valueToKey.ts +++ b/packages/idb-bridge/src/util/valueToKey.ts @@ -14,11 +14,14 @@ permissions and limitations under the License. */ +import { IDBValidKey } from ".."; import { DataError } from "./errors"; -import { Key } from "./types"; // https://w3c.github.io/IndexedDB/#convert-a-value-to-a-input -function valueToKey(input: any, seen?: Set): Key | Key[] { +function valueToKey( + input: any, + seen?: Set, +): IDBValidKey | IDBValidKey[] { if (typeof input === "number") { if (isNaN(input)) { throw new DataError(); diff --git a/packages/idb-bridge/tsconfig.json b/packages/idb-bridge/tsconfig.json index a385e964a..99c5e6e3c 100644 --- a/packages/idb-bridge/tsconfig.json +++ b/packages/idb-bridge/tsconfig.json @@ -16,6 +16,7 @@ "rootDir": "./src", "esModuleInterop": true, "importHelpers": true, + "isolatedModules": true, "typeRoots": ["./node_modules/@types"] }, "include": ["src/**/*"] -- cgit v1.2.3