diff options
Diffstat (limited to 'packages/idb-bridge/src/bridge-idb.ts')
-rw-r--r-- | packages/idb-bridge/src/bridge-idb.ts | 516 |
1 files changed, 298 insertions, 218 deletions
diff --git a/packages/idb-bridge/src/bridge-idb.ts b/packages/idb-bridge/src/bridge-idb.ts index 128a6900d..8cecba534 100644 --- a/packages/idb-bridge/src/bridge-idb.ts +++ b/packages/idb-bridge/src/bridge-idb.ts @@ -17,12 +17,16 @@ import { Backend, + ConnectResult, DatabaseConnection, DatabaseTransaction, - RecordGetRequest, + IndexGetQuery, + IndexMeta, + ObjectStoreGetQuery, + ObjectStoreMeta, + RecordGetResponse, RecordStoreRequest, ResultLevel, - Schema, StoreLevel, } from "./backend-interface.js"; import { @@ -57,10 +61,7 @@ import { TransactionInactiveError, VersionError, } from "./util/errors.js"; -import { - FakeDOMStringList, - fakeDOMStringList, -} from "./util/fakeDOMStringList.js"; +import { fakeDOMStringList } from "./util/fakeDOMStringList.js"; import FakeEvent from "./util/FakeEvent.js"; import FakeEventTarget from "./util/FakeEventTarget.js"; import { makeStoreKeyValue } from "./util/makeStoreKeyValue.js"; @@ -71,17 +72,14 @@ import { checkStructuredCloneOrThrow } from "./util/structuredClone.js"; import { validateKeyPath } from "./util/validateKeyPath.js"; import { valueToKey } from "./util/valueToKey.js"; -/** @public */ export type CursorSource = BridgeIDBIndex | BridgeIDBObjectStore; -/** @public */ export interface RequestObj { operation: () => Promise<any>; request?: BridgeIDBRequest | undefined; source?: any; } -/** @public */ export interface BridgeIDBDatabaseInfo { name: string; version: number; @@ -101,8 +99,6 @@ function simplifyRange( /** * http://www.w3.org/TR/2015/REC-IndexedDB-20150108/#cursor - * - * @public */ export class BridgeIDBCursor implements IDBCursor { _request: BridgeIDBRequest | undefined; @@ -207,29 +203,56 @@ export class BridgeIDBCursor implements IDBCursor { ); BridgeIDBFactory.enableTracing && console.log("cursor type ", this.toString()); - const isIndex = this._indexName !== undefined; - 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: isIndex ? key : undefined, - advancePrimaryKey: isIndex ? primaryKey : key, - resultLevel: this._keyOnly ? ResultLevel.OnlyKeys : ResultLevel.Full, - }; + const indexName = this._indexName; const { btx } = this.source._confirmStartedBackendTransaction(); - let response = await this._backend.getRecords(btx, recordGetRequest); + let response: RecordGetResponse; + + if (indexName != null) { + const indexRecordGetRequest: IndexGetQuery = { + direction: this.direction, + indexName: 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, + }; + response = await this._backend.getIndexRecords( + btx, + indexRecordGetRequest, + ); + } else { + if (primaryKey != null) { + // Only allowed for index cursors + throw new InvalidAccessError(); + } + const objStoreGetRequest: ObjectStoreGetQuery = { + direction: this.direction, + lastObjectStorePosition: this._objectStorePosition, + limit: 1, + range: simplifyRange(this._range), + objectStoreName: this._objectStoreName, + advancePrimaryKey: key, + resultLevel: this._keyOnly ? ResultLevel.OnlyKeys : ResultLevel.Full, + }; + response = await this._backend.getObjectStoreRecords( + btx, + objStoreGetRequest, + ); + } if (response.count === 0) { if (BridgeIDBFactory.enableTracing) { console.log("cursor is returning empty result"); } this._gotValue = false; + this._key = undefined; + this._value = undefined; return null; } @@ -237,11 +260,6 @@ export class BridgeIDBCursor implements IDBCursor { 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 { @@ -550,7 +568,6 @@ const confirmActiveVersionchangeTransaction = (database: BridgeIDBDatabase) => { }; // http://www.w3.org/TR/2015/REC-IndexedDB-20150108/#database-interface -/** @public */ export class BridgeIDBDatabase extends FakeEventTarget implements IDBDatabase { _closePending = false; _closed = false; @@ -561,7 +578,16 @@ export class BridgeIDBDatabase extends FakeEventTarget implements IDBDatabase { _backendConnection: DatabaseConnection; _backend: Backend; - _schema: Schema; + _name: string; + + _initialVersion: number; + + _version: number; + + // "object store set" from the spec + _objectStoreSet: string[]; + + // _schema: Schema; /** * Name that can be set to identify the object store in logs. @@ -569,17 +595,15 @@ export class BridgeIDBDatabase extends FakeEventTarget implements IDBDatabase { _debugName: string | undefined = undefined; get name(): string { - return this._schema.databaseName; + return this._name; } get version(): number { - return this._schema.databaseVersion; + return this._version; } get objectStoreNames(): DOMStringList { - return fakeDOMStringList( - Object.keys(this._schema.objectStores), - ).sort() as DOMStringList; + return fakeDOMStringList([...this._objectStoreSet]).sort() as DOMStringList; } /** @@ -606,13 +630,13 @@ export class BridgeIDBDatabase extends FakeEventTarget implements IDBDatabase { } } - constructor(backend: Backend, backendConnection: DatabaseConnection) { + constructor(name: string, backend: Backend, connResult: ConnectResult) { super(); - - this._schema = backend.getSchema(backendConnection); - + this._name = name; + this._version = this._initialVersion = connResult.version; this._backend = backend; - this._backendConnection = backendConnection; + this._backendConnection = connResult.conn; + this._objectStoreSet = connResult.objectStores; } // http://w3c.github.io/IndexedDB/#dom-idbdatabase-createobjectstore @@ -645,7 +669,8 @@ export class BridgeIDBDatabase extends FakeEventTarget implements IDBDatabase { validateKeyPath(keyPath); } - if (Object.keys(this._schema.objectStores).includes(name)) { + if (this._objectStoreSet.includes(name)) { + // Already exists throw new ConstraintError(); } @@ -660,7 +685,9 @@ export class BridgeIDBDatabase extends FakeEventTarget implements IDBDatabase { autoIncrement, ); - this._schema = this._backend.getCurrentTransactionSchema(backendTx); + transaction._scope.add(name); + this._objectStoreSet.push(name); + this._objectStoreSet.sort(); const newObjectStore = transaction.objectStore(name); newObjectStore._justCreated = true; @@ -682,6 +709,10 @@ export class BridgeIDBDatabase extends FakeEventTarget implements IDBDatabase { os._deleted = true; transaction._objectStoresCache.delete(name); } + transaction._cachedObjectStoreNames = undefined; + transaction._scope.delete(name); + const nameIdx = this._objectStoreSet.indexOf(name); + this._objectStoreSet.splice(nameIdx, 1); } public _internalTransaction( @@ -766,10 +797,8 @@ export class BridgeIDBDatabase extends FakeEventTarget implements IDBDatabase { } } -/** @public */ export type DatabaseList = Array<{ name: string; version: number }>; -/** @public */ export class BridgeIDBFactory { public cmp = compareKeys; private backend: Backend; @@ -810,8 +839,10 @@ export class BridgeIDBFactory { }); request.dispatchEvent(event2); } catch (err: any) { - request.error = new Error(); - request.error.name = err.name; + const myErr = new Error(); + myErr.name = err.name; + myErr.message = err.message; + request.error = myErr; request.readyState = "done"; const event = new FakeEvent("error", { @@ -841,27 +872,26 @@ export class BridgeIDBFactory { const request = new BridgeIDBOpenDBRequest(); queueTask(async () => { - let dbconn: DatabaseConnection; + let dbConnRes: ConnectResult; try { if (BridgeIDBFactory.enableTracing) { console.log("TRACE: connecting to database"); } - dbconn = await this.backend.connectDatabase(name); + dbConnRes = await this.backend.connectDatabase(name); if (BridgeIDBFactory.enableTracing) { console.log("TRACE: connected!"); } } catch (err: any) { if (BridgeIDBFactory.enableTracing) { console.log( - "TRACE: caught exception while trying to connect with backend", + "TRACE: caught exception while trying to connect with backend:", + err, ); } request._finishWithError(err); return; } - - const schema = this.backend.getSchema(dbconn); - const existingVersion = schema.databaseVersion; + const existingVersion = dbConnRes.version; if (version === undefined) { version = existingVersion !== 0 ? existingVersion : 1; @@ -879,7 +909,7 @@ export class BridgeIDBFactory { return; } - const db = new BridgeIDBDatabase(this.backend, dbconn); + const db = new BridgeIDBDatabase(name, this.backend, dbConnRes); if (existingVersion == requestedVersion) { request.result = db; @@ -929,16 +959,14 @@ export class BridgeIDBFactory { } const backendTransaction = await this.backend.enterVersionChange( - dbconn, + dbConnRes.conn, requestedVersion, ); // We need to expose the new version number to the upgrade transaction. - db._schema = - this.backend.getCurrentTransactionSchema(backendTransaction); - + db._version = version; const transaction = db._internalTransaction( - [], + dbConnRes.objectStores, "versionchange", backendTransaction, request, @@ -1030,37 +1058,48 @@ export class BridgeIDBFactory { } // http://www.w3.org/TR/2015/REC-IndexedDB-20150108/#idl-def-IDBIndex -/** @public */ export class BridgeIDBIndex implements IDBIndex { _objectStore: BridgeIDBObjectStore; + _indexMeta: IndexMeta; + _originalName: string | undefined = undefined; + _deleted: boolean = false; + _name: string; + + /** + * Was this index newly created in the current transaction? + */ + _justCreated: boolean = false; get objectStore(): IDBObjectStore { return this._objectStore; } - get _schema(): Schema { - return this._objectStore._transaction._db._schema; - } - get keyPath(): IDBKeyPath | IDBKeyPath[] { - return this._schema.objectStores[this._objectStore.name].indexes[this._name] - .keyPath; + return this._indexMeta.keyPath; } get multiEntry(): boolean { - return this._schema.objectStores[this._objectStore.name].indexes[this._name] - .multiEntry; + return this._indexMeta.multiEntry; } get unique(): boolean { - return this._schema.objectStores[this._objectStore.name].indexes[this._name] - .unique; + return this._indexMeta.multiEntry; } get _backend(): Backend { return this._objectStore._backend; } + constructor( + objectStore: BridgeIDBObjectStore, + name: string, + indexMeta: IndexMeta, + ) { + this._name = name; + this._objectStore = objectStore; + this._indexMeta = indexMeta; + } + _confirmStartedBackendTransaction(): { btx: DatabaseTransaction } { return this._objectStore._confirmStartedBackendTransaction(); } @@ -1069,20 +1108,6 @@ export class BridgeIDBIndex implements IDBIndex { this._objectStore._confirmActiveTransaction(); } - private _name: string; - - public _deleted: boolean = false; - - /** - * Was this index newly created in the current transaction? - */ - _justCreated: boolean = false; - - constructor(objectStore: BridgeIDBObjectStore, name: string) { - this._name = name; - this._objectStore = objectStore; - } - get name() { return this._name; } @@ -1107,18 +1132,39 @@ export class BridgeIDBIndex implements IDBIndex { if (newName === oldName) { return; } - + if (this._originalName != null) { + this._originalName = oldName; + } this._backend.renameIndex(btx, this._objectStore.name, oldName, newName); + this._applyNameChange(oldName, newName); + if (this._objectStore._objectStoreMeta.indexSet.indexOf(name) >= 0) { + throw new Error("internal invariant violated"); + } + } - this._objectStore._transaction._db._schema = - this._backend.getCurrentTransactionSchema(btx); - - this._objectStore._indexesCache.delete(oldName); - this._objectStore._indexesCache.set(newName, this); + _applyNameChange(oldName: string, newName: string) { + this._objectStore._indexHandlesCache.delete(oldName); + this._objectStore._indexHandlesCache.set(newName, this); + const indexSet = this._objectStore._objectStoreMeta.indexSet; + const indexIdx = indexSet.indexOf(oldName); + indexSet[indexIdx] = newName; + indexSet.sort(); this._name = newName; + } - if (this._objectStore._indexNames.indexOf(name) >= 0) { - throw new Error("internal invariant violated"); + _applyDelete() { + this._objectStore._indexHandlesCache.delete(this._name); + const indexSet = this._objectStore._objectStoreMeta.indexSet; + const indexIdx = indexSet.indexOf(this._name); + indexSet.splice(indexIdx, 1); + } + + _abort() { + if (this._originalName != null) { + this._applyNameChange(this._name, this._originalName); + } + if (this._justCreated) { + this._deleted = true; } } @@ -1199,34 +1245,23 @@ export class BridgeIDBIndex implements IDBIndex { } private _confirmIndexExists() { - const storeSchema = this._schema.objectStores[this._objectStore._name]; - if (!storeSchema) { - throw new InvalidStateError( - `no schema for object store '${this._objectStore._name}'`, - ); - } - if (!storeSchema.indexes[this._name]) { - throw new InvalidStateError( - `no schema for index '${this._name}' of object store '${this._objectStore._name}'`, - ); - } - } - - get(key: BridgeIDBKeyRange | IDBValidKey) { if (this._deleted) { throw new InvalidStateError(); } if (this._objectStore._deleted) { throw new InvalidStateError(); } - this._confirmActiveTransaction(); + } + + get(key: BridgeIDBKeyRange | IDBValidKey) { this._confirmIndexExists(); + this._confirmActiveTransaction(); if (!(key instanceof BridgeIDBKeyRange)) { key = BridgeIDBKeyRange._valueToKeyRange(key); } - const getReq: RecordGetRequest = { + const getReq: IndexGetQuery = { direction: "next", indexName: this._name, limit: 1, @@ -1237,7 +1272,7 @@ export class BridgeIDBIndex implements IDBIndex { const operation = async () => { const { btx } = this._confirmStartedBackendTransaction(); - const result = await this._backend.getRecords(btx, getReq); + const result = await this._backend.getIndexRecords(btx, getReq); if (result.count == 0) { return undefined; } @@ -1273,7 +1308,7 @@ export class BridgeIDBIndex implements IDBIndex { count = -1; } - const getReq: RecordGetRequest = { + const getReq: IndexGetQuery = { direction: "next", indexName: this._name, limit: count, @@ -1284,7 +1319,7 @@ export class BridgeIDBIndex implements IDBIndex { const operation = async () => { const { btx } = this._confirmStartedBackendTransaction(); - const result = await this._backend.getRecords(btx, getReq); + const result = await this._backend.getIndexRecords(btx, getReq); const values = result.values; if (!values) { throw Error("invariant violated"); @@ -1307,7 +1342,7 @@ export class BridgeIDBIndex implements IDBIndex { key = BridgeIDBKeyRange._valueToKeyRange(key); } - const getReq: RecordGetRequest = { + const getReq: IndexGetQuery = { direction: "next", indexName: this._name, limit: 1, @@ -1318,7 +1353,7 @@ export class BridgeIDBIndex implements IDBIndex { const operation = async () => { const { btx } = this._confirmStartedBackendTransaction(); - const result = await this._backend.getRecords(btx, getReq); + const result = await this._backend.getIndexRecords(btx, getReq); if (result.count == 0) { return undefined; } @@ -1351,7 +1386,7 @@ export class BridgeIDBIndex implements IDBIndex { count = -1; } - const getReq: RecordGetRequest = { + const getReq: IndexGetQuery = { direction: "next", indexName: this._name, limit: count, @@ -1362,7 +1397,7 @@ export class BridgeIDBIndex implements IDBIndex { const operation = async () => { const { btx } = this._confirmStartedBackendTransaction(); - const result = await this._backend.getRecords(btx, getReq); + const result = await this._backend.getIndexRecords(btx, getReq); const primaryKeys = result.primaryKeys; if (!primaryKeys) { throw Error("invariant violated"); @@ -1388,7 +1423,7 @@ export class BridgeIDBIndex implements IDBIndex { key = BridgeIDBKeyRange.only(valueToKey(key)); } - const getReq: RecordGetRequest = { + const getReq: IndexGetQuery = { direction: "next", indexName: this._name, limit: 1, @@ -1399,7 +1434,7 @@ export class BridgeIDBIndex implements IDBIndex { const operation = async () => { const { btx } = this._confirmStartedBackendTransaction(); - const result = await this._backend.getRecords(btx, getReq); + const result = await this._backend.getIndexRecords(btx, getReq); return result.count; }; @@ -1415,7 +1450,6 @@ export class BridgeIDBIndex implements 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) { @@ -1525,10 +1559,8 @@ export class BridgeIDBKeyRange { } // http://www.w3.org/TR/2015/REC-IndexedDB-20150108/#object-store -/** @public */ export class BridgeIDBObjectStore implements IDBObjectStore { - _indexesCache: Map<string, BridgeIDBIndex> = new Map(); - + _indexHandlesCache: Map<string, BridgeIDBIndex> = new Map(); _transaction: BridgeIDBTransaction; /** @@ -1536,41 +1568,43 @@ export class BridgeIDBObjectStore implements IDBObjectStore { */ _debugName: string | undefined = undefined; + // Was the object store (not the handle, but the underlying store) + // created in this upgrade transaction? _justCreated: boolean = false; + _originalName: string | undefined = undefined; + _objectStoreMeta: ObjectStoreMeta; + get transaction(): IDBTransaction { return this._transaction; } get autoIncrement(): boolean { - return this._schema.objectStores[this._name].autoIncrement; - } - - get _indexNames(): FakeDOMStringList { - return fakeDOMStringList( - Object.keys(this._schema.objectStores[this._name].indexes), - ).sort(); + return this._objectStoreMeta.autoIncrement; } get indexNames(): DOMStringList { - return this._indexNames as DOMStringList; + return fakeDOMStringList([...this._objectStoreMeta.indexSet]); } get keyPath(): IDBKeyPath | IDBKeyPath[] { - return this._schema.objectStores[this._name].keyPath!; + // Bug in th official type declarations. The spec + // allows returning null here. + return this._objectStoreMeta.keyPath!; } _name: string; - get _schema(): Schema { - return this._transaction._db._schema; - } - _deleted: boolean = false; - constructor(transaction: BridgeIDBTransaction, name: string) { + constructor( + transaction: BridgeIDBTransaction, + name: string, + objectStoreMeta: ObjectStoreMeta, + ) { this._name = name; this._transaction = transaction; + this._objectStoreMeta = objectStoreMeta; } get name() { @@ -1620,26 +1654,56 @@ export class BridgeIDBObjectStore implements IDBObjectStore { let { btx } = this._confirmStartedBackendTransaction(); newName = String(newName); - const oldName = this._name; - if (newName === oldName) { return; } - + if (this._originalName == null) { + this._originalName = this._name; + } this._backend.renameObjectStore(btx, oldName, newName); - this._transaction._db._schema = - this._backend.getCurrentTransactionSchema(btx); + this._applyNameChange(oldName, newName); + } + _applyNameChange(oldName: string, newName: string) { + this._transaction._scope.delete(oldName); + this._transaction._scope.add(newName); // We don't modify scope, as the scope of the transaction // doesn't matter if we're in an upgrade transaction. this._transaction._objectStoresCache.delete(oldName); this._transaction._objectStoresCache.set(newName, this); this._transaction._cachedObjectStoreNames = undefined; - + const objectStoreSet = this._transaction._db._objectStoreSet; + const oldIdx = objectStoreSet.indexOf(oldName); + objectStoreSet[oldIdx] = newName; + objectStoreSet.sort(); this._name = newName; } + _applyDelete() { + this._deleted = true; + this._transaction._objectStoresCache.delete(this._name); + this._transaction._cachedObjectStoreNames = undefined; + const objectStoreSet = this._transaction._db._objectStoreSet; + const oldIdx = objectStoreSet.indexOf(this._name); + objectStoreSet.splice(oldIdx, 1); + } + + /** + * Roll back changes to the handle after an abort. + */ + _abort() { + if (this._originalName != null) { + this._applyNameChange(this._name, this._originalName); + } + if (this._justCreated) { + this._applyDelete(); + } + } + + /** + * "To add or put with handle, value, key, and no-overwrite flag, run these steps:" + */ public _store(value: any, key: IDBValidKey | undefined, overwrite: boolean) { if (BridgeIDBFactory.enableTracing) { console.log( @@ -1647,6 +1711,12 @@ export class BridgeIDBObjectStore implements IDBObjectStore { ); } + if (this._deleted) { + throw new InvalidStateError( + "tried to call 'put' on a deleted object store", + ); + } + if (!this._transaction._active) { throw new TransactionInactiveError(); } @@ -1655,14 +1725,21 @@ export class BridgeIDBObjectStore implements IDBObjectStore { throw new ReadOnlyError(); } - const { keyPath, autoIncrement } = this._schema.objectStores[this._name]; + const { keyPath, autoIncrement } = this._objectStoreMeta; if (key !== null && key !== undefined) { valueToKey(key); } // We only call this to synchronously verify the request. - makeStoreKeyValue(value, key, 1, autoIncrement, keyPath); + // FIXME: The backend should do that! + makeStoreKeyValue({ + value: value, + key: key, + currentKeyGenerator: 1, + autoIncrement, + keyPath, + }); const operation = async () => { const { btx } = this._confirmStartedBackendTransaction(); @@ -1684,11 +1761,6 @@ export class BridgeIDBObjectStore implements IDBObjectStore { if (arguments.length === 0) { throw new TypeError(); } - if (this._deleted) { - throw new InvalidStateError( - "tried to call 'put' on a deleted object store", - ); - } return this._store(value, key, true); } @@ -1696,9 +1768,6 @@ export class BridgeIDBObjectStore implements IDBObjectStore { if (arguments.length === 0) { throw new TypeError(); } - if (!this._schema.objectStores[this._name]) { - throw new InvalidStateError("object store does not exist"); - } return this._store(value, key, false); } @@ -1767,10 +1836,8 @@ export class BridgeIDBObjectStore implements IDBObjectStore { } } - const recordRequest: RecordGetRequest = { + const recordRequest: ObjectStoreGetQuery = { objectStoreName: this._name, - indexName: undefined, - lastIndexPosition: undefined, lastObjectStorePosition: undefined, direction: "next", limit: 1, @@ -1783,7 +1850,10 @@ export class BridgeIDBObjectStore implements IDBObjectStore { console.log("running get operation:", recordRequest); } const { btx } = this._confirmStartedBackendTransaction(); - const result = await this._backend.getRecords(btx, recordRequest); + const result = await this._backend.getObjectStoreRecords( + btx, + recordRequest, + ); if (BridgeIDBFactory.enableTracing) { console.log("get operation result count:", result.count); @@ -1833,10 +1903,8 @@ export class BridgeIDBObjectStore implements IDBObjectStore { let keyRange: BridgeIDBKeyRange | null = simplifyRange(query); - const recordRequest: RecordGetRequest = { + const recordRequest: ObjectStoreGetQuery = { objectStoreName: this._name, - indexName: undefined, - lastIndexPosition: undefined, lastObjectStorePosition: undefined, direction: "next", limit: count, @@ -1849,7 +1917,10 @@ export class BridgeIDBObjectStore implements IDBObjectStore { console.log("running getAll operation:", recordRequest); } const { btx } = this._confirmStartedBackendTransaction(); - const result = await this._backend.getRecords(btx, recordRequest); + const result = await this._backend.getObjectStoreRecords( + btx, + recordRequest, + ); if (BridgeIDBFactory.enableTracing) { console.log("get operation result count:", result.count); @@ -1887,10 +1958,8 @@ export class BridgeIDBObjectStore implements IDBObjectStore { let keyRange: BridgeIDBKeyRange | null = simplifyRange(query); - const recordRequest: RecordGetRequest = { + const recordRequest: ObjectStoreGetQuery = { objectStoreName: this._name, - indexName: undefined, - lastIndexPosition: undefined, lastObjectStorePosition: undefined, direction: "next", limit: 1, @@ -1903,7 +1972,10 @@ export class BridgeIDBObjectStore implements IDBObjectStore { console.log("running getKey operation:", recordRequest); } const { btx } = this._confirmStartedBackendTransaction(); - const result = await this._backend.getRecords(btx, recordRequest); + const result = await this._backend.getObjectStoreRecords( + btx, + recordRequest, + ); if (BridgeIDBFactory.enableTracing) { console.log("getKey operation result count:", result.count); @@ -1965,10 +2037,8 @@ export class BridgeIDBObjectStore implements IDBObjectStore { } } - const recordRequest: RecordGetRequest = { + const recordRequest: ObjectStoreGetQuery = { objectStoreName: this._name, - indexName: undefined, - lastIndexPosition: undefined, lastObjectStorePosition: undefined, direction: "next", limit: count, @@ -1978,7 +2048,10 @@ export class BridgeIDBObjectStore implements IDBObjectStore { const operation = async () => { const { btx } = this._confirmStartedBackendTransaction(); - const result = await this._backend.getRecords(btx, recordRequest); + const result = await this._backend.getObjectStoreRecords( + btx, + recordRequest, + ); const primaryKeys = result.primaryKeys; if (!primaryKeys) { @@ -2121,7 +2194,7 @@ export class BridgeIDBObjectStore implements IDBObjectStore { throw new InvalidStateError(); } - if (this._indexNames.indexOf(indexName) >= 0) { + if (this._objectStoreMeta.indexSet.indexOf(indexName) >= 0) { throw new ConstraintError(); } @@ -2140,6 +2213,9 @@ export class BridgeIDBObjectStore implements IDBObjectStore { unique, ); + this._objectStoreMeta.indexSet.push(indexName); + this._objectStoreMeta.indexSet.sort(); + const idx = this.index(indexName); idx._justCreated = true; return idx; @@ -2154,13 +2230,20 @@ export class BridgeIDBObjectStore implements IDBObjectStore { if (this._transaction._finished) { throw new InvalidStateError(); } - - const index = this._indexesCache.get(name); + const index = this._indexHandlesCache.get(name); if (index !== undefined) { return index; } - const newIndex = new BridgeIDBIndex(this, name); - this._indexesCache.set(name, newIndex); + const indexMeta = this._backend.getIndexMeta( + this._backendConnection, + this._name, + name, + ); + if (!indexMeta) { + throw new NotFoundError(); + } + const newIndex = new BridgeIDBIndex(this, name, indexMeta); + this._indexHandlesCache.set(name, newIndex); this._transaction._usedIndexes.push(newIndex); return newIndex; } @@ -2180,12 +2263,15 @@ export class BridgeIDBObjectStore implements IDBObjectStore { const { btx } = this._confirmStartedBackendTransaction(); - const index = this._indexesCache.get(indexName); + const index = this._indexHandlesCache.get(indexName); if (index !== undefined) { index._deleted = true; - this._indexesCache.delete(indexName); + this._indexHandlesCache.delete(indexName); } + const indexIdx = this._objectStoreMeta.indexSet.indexOf(indexName); + this._objectStoreMeta.indexSet.splice(indexIdx, 1); + this._backend.deleteIndex(btx, this._name, indexName); } @@ -2198,11 +2284,9 @@ export class BridgeIDBObjectStore implements IDBObjectStore { key = BridgeIDBKeyRange.only(valueToKey(key)); } - const recordGetRequest: RecordGetRequest = { + const recordGetRequest: ObjectStoreGetQuery = { direction: "next", - indexName: undefined, - lastIndexPosition: undefined, - limit: -1, + limit: 0, objectStoreName: this._name, lastObjectStorePosition: undefined, range: key, @@ -2211,7 +2295,10 @@ export class BridgeIDBObjectStore implements IDBObjectStore { const operation = async () => { const { btx } = this._confirmStartedBackendTransaction(); - const result = await this._backend.getRecords(btx, recordGetRequest); + const result = await this._backend.getObjectStoreRecords( + btx, + recordGetRequest, + ); return result.count; }; @@ -2223,7 +2310,6 @@ export class BridgeIDBObjectStore implements IDBObjectStore { } } -/** @public */ export class BridgeIDBRequest extends FakeEventTarget implements IDBRequest { _result: any = null; _error: Error | null | undefined = null; @@ -2294,7 +2380,6 @@ export class BridgeIDBRequest extends FakeEventTarget implements IDBRequest { } } -/** @public */ export class BridgeIDBOpenDBRequest extends BridgeIDBRequest implements IDBOpenDBRequest @@ -2343,7 +2428,6 @@ function waitMacroQueue(): Promise<void> { } // http://www.w3.org/TR/2015/REC-IndexedDB-20150108/#transaction -/** @public */ export class BridgeIDBTransaction extends FakeEventTarget implements IDBTransaction @@ -2390,13 +2474,9 @@ export class BridgeIDBTransaction get objectStoreNames(): DOMStringList { if (!this._cachedObjectStoreNames) { - if (this._openRequest) { - this._cachedObjectStoreNames = this._db.objectStoreNames; - } else { - this._cachedObjectStoreNames = fakeDOMStringList( - Array.from(this._scope).sort(), - ); - } + this._cachedObjectStoreNames = fakeDOMStringList( + Array.from(this._scope).sort(), + ); } return this._cachedObjectStoreNames; } @@ -2496,41 +2576,34 @@ export class BridgeIDBTransaction } } + // All steps before happened synchronously. Now + // we asynchronously roll back the backend transaction, + // if necessary/possible. + + const maybeBtx = this._backendTransaction; + if (maybeBtx) { + this._backend.rollback(maybeBtx); + } + // "Any object stores and indexes which were created during the // transaction are now considered deleted for the purposes of other // algorithms." if (this._db._upgradeTransaction) { for (const os of this._usedObjectStores) { - if (os._justCreated) { - os._deleted = true; - } + os._abort(); } for (const ind of this._usedIndexes) { - if (ind._justCreated) { - ind._deleted = true; - } + ind._abort(); } } + this._db._version = this._db._initialVersion; + // ("abort a transaction", step 5.1) if (this._openRequest) { this._db._upgradeTransaction = null; } - // All steps before happened synchronously. Now - // we asynchronously roll back the backend transaction, - // if necessary/possible. - - const maybeBtx = this._backendTransaction; - if (maybeBtx) { - this._db._schema = this._backend.getInitialTransactionSchema(maybeBtx); - // Only roll back if we actually executed the scheduled operations. - await this._backend.rollback(maybeBtx); - this._backendTransaction = undefined; - } else { - this._db._schema = this._backend.getSchema(this._db._backendConnection); - } - queueTask(() => { const event = new FakeEvent("abort", { bubbles: true, @@ -2560,22 +2633,29 @@ export class BridgeIDBTransaction throw new TransactionInactiveError(); } - if (!this._db._schema.objectStores[name]) { + if (!this._scope.has(name)) { throw new NotFoundError(); } - if (!this._db._upgradeTransaction) { - if (!this._scope.has(name)) { - throw new NotFoundError(); - } - } - const objectStore = this._objectStoresCache.get(name); if (objectStore !== undefined) { return objectStore; } - const newObjectStore = new BridgeIDBObjectStore(this, name); + const objectStoreMeta = this._backend.getObjectStoreMeta( + this._db._backendConnection, + name, + ); + + if (!objectStoreMeta) { + throw new NotFoundError(); + } + + const newObjectStore = new BridgeIDBObjectStore( + this, + name, + objectStoreMeta, + ); this._objectStoresCache.set(name, newObjectStore); this._usedObjectStores.push(newObjectStore); return newObjectStore; |