/* 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 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;