UNPKG

@aws-amplify/datastore

Version:

AppSyncLocal support for aws-amplify

914 lines (913 loc) • 52.7 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); var tslib_1 = require("tslib"); var core_1 = require("@aws-amplify/core"); var idb = tslib_1.__importStar(require("idb")); var types_1 = require("../../types"); var util_1 = require("../../util"); var StorageAdapterBase_1 = require("./StorageAdapterBase"); var logger = new core_1.ConsoleLogger('DataStore'); /** * The point after which queries composed of multiple simple OR conditions * should scan-and-filter instead of individual queries for each condition. * * At some point, this should be configurable and/or dynamic based on table * size and possibly even on observed average seek latency. For now, it's * based on an manual "binary search" for the breakpoint as measured in the * unit test suite. This isn't necessarily optimal. But, it's at least derived * empirically, rather than theoretically and without any verification! * * REMEMBER! If you run more realistic benchmarks and update this value, update * this comment so the validity and accuracy of future query tuning exercises * can be compared to the methods used to derive the current value. E.g., * * 1. In browser benchmark > unit test benchmark * 2. Multi-browser benchmark > single browser benchmark * 3. Benchmarks of various table sizes > static table size benchmark * * etc... * */ var MULTI_OR_CONDITION_SCAN_BREAKPOINT = 7; // var DB_VERSION = 3; var IndexedDBAdapter = /** @class */ (function (_super) { tslib_1.__extends(IndexedDBAdapter, _super); function IndexedDBAdapter() { var _this = _super !== null && _super.apply(this, arguments) || this; _this.safariCompatabilityMode = false; /** * Checks the given path against the browser's IndexedDB implementation for * necessary compatibility transformations, applying those transforms if needed. * * @param `keyArr` strings to compatibilize for browser-indexeddb index operations * @returns An array or string, depending on and given key, * that is ensured to be compatible with the IndexedDB implementation's nuances. */ _this.canonicalKeyPath = function (keyArr) { if (_this.safariCompatabilityMode) { return keyArr.length > 1 ? keyArr : keyArr[0]; } return keyArr; }; return _this; //#endregion } // checks are called by StorageAdapterBase class IndexedDBAdapter.prototype.preSetUpChecks = function () { return tslib_1.__awaiter(this, void 0, void 0, function () { return tslib_1.__generator(this, function (_a) { switch (_a.label) { case 0: return [4 /*yield*/, this.checkPrivate()]; case 1: _a.sent(); return [4 /*yield*/, this.setSafariCompatabilityMode()]; case 2: _a.sent(); return [2 /*return*/]; } }); }); }; IndexedDBAdapter.prototype.preOpCheck = function () { return tslib_1.__awaiter(this, void 0, void 0, function () { return tslib_1.__generator(this, function (_a) { switch (_a.label) { case 0: return [4 /*yield*/, this.checkPrivate()]; case 1: _a.sent(); return [2 /*return*/]; } }); }); }; /** * Initialize IndexedDB database * Create new DB if one doesn't exist * Upgrade outdated DB * * Called by `StorageAdapterBase.setUp()` * * @returns IDB Database instance */ IndexedDBAdapter.prototype.initDb = function () { return tslib_1.__awaiter(this, void 0, void 0, function () { var _this = this; return tslib_1.__generator(this, function (_a) { switch (_a.label) { case 0: return [4 /*yield*/, idb.openDB(this.dbName, DB_VERSION, { upgrade: function (db, oldVersion, newVersion, txn) { return tslib_1.__awaiter(_this, void 0, void 0, function () { var _a, _b, storeName, origStore, tmpName, _c, namespaceName, modelName, modelInCurrentSchema, newStore, cursor, count, e_1_1, error_1; var e_1, _d; var _this = this; return tslib_1.__generator(this, function (_e) { switch (_e.label) { case 0: // create new database if (oldVersion === 0) { Object.keys(this.schema.namespaces).forEach(function (namespaceName) { var namespace = _this.schema.namespaces[namespaceName]; Object.keys(namespace.models).forEach(function (modelName) { var storeName = util_1.getStorename(namespaceName, modelName); _this.createObjectStoreForModel(db, namespaceName, storeName, modelName); }); }); return [2 /*return*/]; } if (!((oldVersion === 1 || oldVersion === 2) && newVersion === 3)) return [3 /*break*/, 16]; _e.label = 1; case 1: _e.trys.push([1, 14, , 15]); _e.label = 2; case 2: _e.trys.push([2, 11, 12, 13]); _a = tslib_1.__values(txn.objectStoreNames), _b = _a.next(); _e.label = 3; case 3: if (!!_b.done) return [3 /*break*/, 10]; storeName = _b.value; origStore = txn.objectStore(storeName); tmpName = "tmp_" + storeName; origStore.name = tmpName; _c = this.getNamespaceAndModelFromStorename(storeName), namespaceName = _c.namespaceName, modelName = _c.modelName; modelInCurrentSchema = modelName in this.schema.namespaces[namespaceName].models; if (!modelInCurrentSchema) { // delete original db.deleteObjectStore(tmpName); return [3 /*break*/, 9]; } newStore = this.createObjectStoreForModel(db, namespaceName, storeName, modelName); return [4 /*yield*/, origStore.openCursor()]; case 4: cursor = _e.sent(); count = 0; _e.label = 5; case 5: if (!(cursor && cursor.value)) return [3 /*break*/, 8]; // we don't pass key, since they are all new entries in the new store return [4 /*yield*/, newStore.put(cursor.value)]; case 6: // we don't pass key, since they are all new entries in the new store _e.sent(); return [4 /*yield*/, cursor.continue()]; case 7: cursor = _e.sent(); count++; return [3 /*break*/, 5]; case 8: // delete original db.deleteObjectStore(tmpName); logger.debug(count + " " + storeName + " records migrated"); _e.label = 9; case 9: _b = _a.next(); return [3 /*break*/, 3]; case 10: return [3 /*break*/, 13]; case 11: e_1_1 = _e.sent(); e_1 = { error: e_1_1 }; return [3 /*break*/, 13]; case 12: try { if (_b && !_b.done && (_d = _a.return)) _d.call(_a); } finally { if (e_1) throw e_1.error; } return [7 /*endfinally*/]; case 13: // add new models created after IndexedDB, but before migration // this case may happen when a user has not opened an app for // some time and a new model is added during that time Object.keys(this.schema.namespaces).forEach(function (namespaceName) { var namespace = _this.schema.namespaces[namespaceName]; var objectStoreNames = new Set(txn.objectStoreNames); Object.keys(namespace.models) .map(function (modelName) { return [modelName, util_1.getStorename(namespaceName, modelName)]; }) .filter(function (_a) { var _b = tslib_1.__read(_a, 2), storeName = _b[1]; return !objectStoreNames.has(storeName); }) .forEach(function (_a) { var _b = tslib_1.__read(_a, 2), modelName = _b[0], storeName = _b[1]; _this.createObjectStoreForModel(db, namespaceName, storeName, modelName); }); }); return [3 /*break*/, 15]; case 14: error_1 = _e.sent(); logger.error('Error migrating IndexedDB data', error_1); txn.abort(); throw error_1; case 15: return [2 /*return*/]; case 16: return [2 /*return*/]; } }); }); }, })]; case 1: return [2 /*return*/, _a.sent()]; } }); }); }; IndexedDBAdapter.prototype._get = function (storeOrStoreName, keyArr) { return tslib_1.__awaiter(this, void 0, void 0, function () { var index, storeName, store, result; return tslib_1.__generator(this, function (_a) { switch (_a.label) { case 0: if (typeof storeOrStoreName === 'string') { storeName = storeOrStoreName; index = this.db.transaction(storeName, 'readonly').store.index('byPk'); } else { store = storeOrStoreName; index = store.index('byPk'); } return [4 /*yield*/, index.get(this.canonicalKeyPath(keyArr))]; case 1: result = _a.sent(); return [2 /*return*/, result]; } }); }); }; IndexedDBAdapter.prototype.clear = function () { var _a; return tslib_1.__awaiter(this, void 0, void 0, function () { return tslib_1.__generator(this, function (_b) { switch (_b.label) { case 0: return [4 /*yield*/, this.checkPrivate()]; case 1: _b.sent(); (_a = this.db) === null || _a === void 0 ? void 0 : _a.close(); return [4 /*yield*/, idb.deleteDB(this.dbName)]; case 2: _b.sent(); this.db = undefined; this.initPromise = undefined; return [2 /*return*/]; } }); }); }; IndexedDBAdapter.prototype.save = function (model, condition) { var e_2, _a; return tslib_1.__awaiter(this, void 0, void 0, function () { var _b, storeName, set, connectionStoreNames, modelKeyValues, tx, store, fromDB, result, connectionStoreNames_1, connectionStoreNames_1_1, resItem, storeName_1, item, instance, keys, store_1, itemKeyValues, fromDB_1, opType, key, e_2_1; return tslib_1.__generator(this, function (_c) { switch (_c.label) { case 0: return [4 /*yield*/, this.checkPrivate()]; case 1: _c.sent(); _b = this.saveMetadata(model), storeName = _b.storeName, set = _b.set, connectionStoreNames = _b.connectionStoreNames, modelKeyValues = _b.modelKeyValues; tx = this.db.transaction(tslib_1.__spread([storeName], Array.from(set.values())), 'readwrite'); store = tx.objectStore(storeName); return [4 /*yield*/, this._get(store, modelKeyValues)]; case 2: fromDB = _c.sent(); this.validateSaveCondition(condition, fromDB); result = []; _c.label = 3; case 3: _c.trys.push([3, 11, 12, 17]); connectionStoreNames_1 = tslib_1.__asyncValues(connectionStoreNames); _c.label = 4; case 4: return [4 /*yield*/, connectionStoreNames_1.next()]; case 5: if (!(connectionStoreNames_1_1 = _c.sent(), !connectionStoreNames_1_1.done)) return [3 /*break*/, 10]; resItem = connectionStoreNames_1_1.value; storeName_1 = resItem.storeName, item = resItem.item, instance = resItem.instance, keys = resItem.keys; store_1 = tx.objectStore(storeName_1); itemKeyValues = keys.map(function (key) { return item[key]; }); return [4 /*yield*/, this._get(store_1, itemKeyValues)]; case 6: fromDB_1 = _c.sent(); opType = fromDB_1 ? types_1.OpType.UPDATE : types_1.OpType.INSERT; if (!(util_1.keysEqual(itemKeyValues, modelKeyValues) || opType === types_1.OpType.INSERT)) return [3 /*break*/, 9]; return [4 /*yield*/, store_1 .index('byPk') .getKey(this.canonicalKeyPath(itemKeyValues))]; case 7: key = _c.sent(); return [4 /*yield*/, store_1.put(item, key)]; case 8: _c.sent(); result.push([instance, opType]); _c.label = 9; case 9: return [3 /*break*/, 4]; case 10: return [3 /*break*/, 17]; case 11: e_2_1 = _c.sent(); e_2 = { error: e_2_1 }; return [3 /*break*/, 17]; case 12: _c.trys.push([12, , 15, 16]); if (!(connectionStoreNames_1_1 && !connectionStoreNames_1_1.done && (_a = connectionStoreNames_1.return))) return [3 /*break*/, 14]; return [4 /*yield*/, _a.call(connectionStoreNames_1)]; case 13: _c.sent(); _c.label = 14; case 14: return [3 /*break*/, 16]; case 15: if (e_2) throw e_2.error; return [7 /*endfinally*/]; case 16: return [7 /*endfinally*/]; case 17: return [4 /*yield*/, tx.done]; case 18: _c.sent(); return [2 /*return*/, result]; } }); }); }; IndexedDBAdapter.prototype.query = function (modelConstructor, predicate, pagination) { return tslib_1.__awaiter(this, void 0, void 0, function () { var _a, storeName, namespaceName, queryByKey, predicates, hasSort, hasPagination, records; var _this = this; return tslib_1.__generator(this, function (_b) { switch (_b.label) { case 0: return [4 /*yield*/, this.checkPrivate()]; case 1: _b.sent(); _a = this.queryMetadata(modelConstructor, predicate, pagination), storeName = _a.storeName, namespaceName = _a.namespaceName, queryByKey = _a.queryByKey, predicates = _a.predicates, hasSort = _a.hasSort, hasPagination = _a.hasPagination; return [4 /*yield*/, (function () { return tslib_1.__awaiter(_this, void 0, void 0, function () { var record, filtered, all; return tslib_1.__generator(this, function (_a) { switch (_a.label) { case 0: if (!queryByKey) return [3 /*break*/, 2]; return [4 /*yield*/, this.getByKey(storeName, queryByKey)]; case 1: record = _a.sent(); return [2 /*return*/, record ? [record] : []]; case 2: if (!predicates) return [3 /*break*/, 4]; return [4 /*yield*/, this.filterOnPredicate(storeName, predicates)]; case 3: filtered = _a.sent(); return [2 /*return*/, this.inMemoryPagination(filtered, pagination)]; case 4: if (!hasSort) return [3 /*break*/, 6]; return [4 /*yield*/, this.getAll(storeName)]; case 5: all = _a.sent(); return [2 /*return*/, this.inMemoryPagination(all, pagination)]; case 6: if (hasPagination) { return [2 /*return*/, this.enginePagination(storeName, pagination)]; } return [2 /*return*/, this.getAll(storeName)]; } }); }); })()]; case 2: records = (_b.sent()); return [4 /*yield*/, this.load(namespaceName, modelConstructor.name, records)]; case 3: return [2 /*return*/, _b.sent()]; } }); }); }; IndexedDBAdapter.prototype.queryOne = function (modelConstructor, firstOrLast) { if (firstOrLast === void 0) { firstOrLast = types_1.QueryOne.FIRST; } return tslib_1.__awaiter(this, void 0, void 0, function () { var storeName, cursor, result; return tslib_1.__generator(this, function (_a) { switch (_a.label) { case 0: return [4 /*yield*/, this.checkPrivate()]; case 1: _a.sent(); storeName = this.getStorenameForModel(modelConstructor); return [4 /*yield*/, this.db .transaction([storeName], 'readonly') .objectStore(storeName) .openCursor(undefined, firstOrLast === types_1.QueryOne.FIRST ? 'next' : 'prev')]; case 2: cursor = _a.sent(); result = cursor ? cursor.value : undefined; return [2 /*return*/, result && this.modelInstanceCreator(modelConstructor, result)]; } }); }); }; IndexedDBAdapter.prototype.batchSave = function (modelConstructor, items) { return tslib_1.__awaiter(this, void 0, void 0, function () { var modelName, namespaceName, storeName, result, txn, store, _loop_1, this_1, items_1, items_1_1, item, e_3_1; var e_3, _a; var _this = this; return tslib_1.__generator(this, function (_b) { switch (_b.label) { case 0: return [4 /*yield*/, this.checkPrivate()]; case 1: _b.sent(); if (items.length === 0) { return [2 /*return*/, []]; } modelName = modelConstructor.name; namespaceName = this.namespaceResolver(modelConstructor); storeName = this.getStorenameForModel(modelConstructor); result = []; txn = this.db.transaction(storeName, 'readwrite'); store = txn.store; _loop_1 = function (item) { var model, connectedModels, keyValues, _deleted, index, key, instance; return tslib_1.__generator(this, function (_a) { switch (_a.label) { case 0: model = this_1.modelInstanceCreator(modelConstructor, item); connectedModels = util_1.traverseModel(modelName, model, this_1.schema.namespaces[namespaceName], this_1.modelInstanceCreator, this_1.getModelConstructorByModelName); keyValues = this_1.getIndexKeyValuesFromModel(model); _deleted = item._deleted; index = store.index('byPk'); return [4 /*yield*/, index.getKey(this_1.canonicalKeyPath(keyValues))]; case 1: key = _a.sent(); if (!!_deleted) return [3 /*break*/, 3]; instance = connectedModels.find(function (_a) { var instance = _a.instance; var instanceKeyValues = _this.getIndexKeyValuesFromModel(instance); return util_1.keysEqual(instanceKeyValues, keyValues); }).instance; result.push([ instance, key ? types_1.OpType.UPDATE : types_1.OpType.INSERT, ]); return [4 /*yield*/, store.put(instance, key)]; case 2: _a.sent(); return [3 /*break*/, 5]; case 3: result.push([item, types_1.OpType.DELETE]); if (!key) return [3 /*break*/, 5]; return [4 /*yield*/, store.delete(key)]; case 4: _a.sent(); _a.label = 5; case 5: return [2 /*return*/]; } }); }; this_1 = this; _b.label = 2; case 2: _b.trys.push([2, 7, 8, 9]); items_1 = tslib_1.__values(items), items_1_1 = items_1.next(); _b.label = 3; case 3: if (!!items_1_1.done) return [3 /*break*/, 6]; item = items_1_1.value; return [5 /*yield**/, _loop_1(item)]; case 4: _b.sent(); _b.label = 5; case 5: items_1_1 = items_1.next(); return [3 /*break*/, 3]; case 6: return [3 /*break*/, 9]; case 7: e_3_1 = _b.sent(); e_3 = { error: e_3_1 }; return [3 /*break*/, 9]; case 8: try { if (items_1_1 && !items_1_1.done && (_a = items_1.return)) _a.call(items_1); } finally { if (e_3) throw e_3.error; } return [7 /*endfinally*/]; case 9: return [4 /*yield*/, txn.done]; case 10: _b.sent(); return [2 /*return*/, result]; } }); }); }; IndexedDBAdapter.prototype.deleteItem = function (deleteQueue) { var e_4, _a, e_5, _b; return tslib_1.__awaiter(this, void 0, void 0, function () { var connectionStoreNames, tx, _c, _d, deleteItem, storeName, items, store, items_2, items_2_1, item, key, keyValues, itemKey, e_5_1, e_4_1; return tslib_1.__generator(this, function (_e) { switch (_e.label) { case 0: connectionStoreNames = deleteQueue.map(function (_a) { var storeName = _a.storeName; return storeName; }); tx = this.db.transaction(tslib_1.__spread(connectionStoreNames), 'readwrite'); _e.label = 1; case 1: _e.trys.push([1, 22, 23, 28]); _c = tslib_1.__asyncValues(deleteQueue); _e.label = 2; case 2: return [4 /*yield*/, _c.next()]; case 3: if (!(_d = _e.sent(), !_d.done)) return [3 /*break*/, 21]; deleteItem = _d.value; storeName = deleteItem.storeName, items = deleteItem.items; store = tx.objectStore(storeName); _e.label = 4; case 4: _e.trys.push([4, 14, 15, 20]); items_2 = tslib_1.__asyncValues(items); _e.label = 5; case 5: return [4 /*yield*/, items_2.next()]; case 6: if (!(items_2_1 = _e.sent(), !items_2_1.done)) return [3 /*break*/, 13]; item = items_2_1.value; if (!item) return [3 /*break*/, 12]; key = void 0; if (!(typeof item === 'object')) return [3 /*break*/, 8]; keyValues = this.getIndexKeyValuesFromModel(item); return [4 /*yield*/, store .index('byPk') .getKey(this.canonicalKeyPath(keyValues))]; case 7: key = _e.sent(); return [3 /*break*/, 10]; case 8: itemKey = item.toString(); return [4 /*yield*/, store.index('byPk').getKey(itemKey)]; case 9: key = _e.sent(); _e.label = 10; case 10: if (!(key !== undefined)) return [3 /*break*/, 12]; return [4 /*yield*/, store.delete(key)]; case 11: _e.sent(); _e.label = 12; case 12: return [3 /*break*/, 5]; case 13: return [3 /*break*/, 20]; case 14: e_5_1 = _e.sent(); e_5 = { error: e_5_1 }; return [3 /*break*/, 20]; case 15: _e.trys.push([15, , 18, 19]); if (!(items_2_1 && !items_2_1.done && (_b = items_2.return))) return [3 /*break*/, 17]; return [4 /*yield*/, _b.call(items_2)]; case 16: _e.sent(); _e.label = 17; case 17: return [3 /*break*/, 19]; case 18: if (e_5) throw e_5.error; return [7 /*endfinally*/]; case 19: return [7 /*endfinally*/]; case 20: return [3 /*break*/, 2]; case 21: return [3 /*break*/, 28]; case 22: e_4_1 = _e.sent(); e_4 = { error: e_4_1 }; return [3 /*break*/, 28]; case 23: _e.trys.push([23, , 26, 27]); if (!(_d && !_d.done && (_a = _c.return))) return [3 /*break*/, 25]; return [4 /*yield*/, _a.call(_c)]; case 24: _e.sent(); _e.label = 25; case 25: return [3 /*break*/, 27]; case 26: if (e_4) throw e_4.error; return [7 /*endfinally*/]; case 27: return [7 /*endfinally*/]; case 28: return [2 /*return*/]; } }); }); }; //#region platform-specific helper methods IndexedDBAdapter.prototype.checkPrivate = function () { return tslib_1.__awaiter(this, void 0, void 0, function () { var isPrivate; return tslib_1.__generator(this, function (_a) { switch (_a.label) { case 0: return [4 /*yield*/, util_1.isPrivateMode().then(function (isPrivate) { return isPrivate; })]; case 1: isPrivate = _a.sent(); if (isPrivate) { logger.error("IndexedDB not supported in this browser's private mode"); return [2 /*return*/, Promise.reject("IndexedDB not supported in this browser's private mode")]; } else { return [2 /*return*/, Promise.resolve()]; } return [2 /*return*/]; } }); }); }; /** * Whether the browser's implementation of IndexedDB is coercing single-field * indexes to a scalar key. * * If this returns `true`, we need to treat indexes containing a single field * as scalars. * * See PR description for reference: * https://github.com/aws-amplify/amplify-js/pull/10527 */ IndexedDBAdapter.prototype.setSafariCompatabilityMode = function () { return tslib_1.__awaiter(this, void 0, void 0, function () { var _a; return tslib_1.__generator(this, function (_b) { switch (_b.label) { case 0: _a = this; return [4 /*yield*/, util_1.isSafariCompatabilityMode()]; case 1: _a.safariCompatabilityMode = _b.sent(); if (this.safariCompatabilityMode === true) { logger.debug('IndexedDB Adapter is running in Safari Compatability Mode'); } return [2 /*return*/]; } }); }); }; IndexedDBAdapter.prototype.getNamespaceAndModelFromStorename = function (storeName) { var _a = tslib_1.__read(storeName.split('_')), namespaceName = _a[0], modelNameArr = _a.slice(1); return { namespaceName: namespaceName, modelName: modelNameArr.join('_'), }; }; IndexedDBAdapter.prototype.createObjectStoreForModel = function (db, namespaceName, storeName, modelName) { var store = db.createObjectStore(storeName, { autoIncrement: true, }); var indexes = this.schema.namespaces[namespaceName].relationships[modelName].indexes; indexes.forEach(function (_a) { var _b = tslib_1.__read(_a, 3), idxName = _b[0], keyPath = _b[1], options = _b[2]; store.createIndex(idxName, keyPath, options); }); return store; }; IndexedDBAdapter.prototype.getByKey = function (storeName, keyValue) { return tslib_1.__awaiter(this, void 0, void 0, function () { return tslib_1.__generator(this, function (_a) { switch (_a.label) { case 0: return [4 /*yield*/, this._get(storeName, keyValue)]; case 1: return [2 /*return*/, _a.sent()]; } }); }); }; IndexedDBAdapter.prototype.getAll = function (storeName) { return tslib_1.__awaiter(this, void 0, void 0, function () { return tslib_1.__generator(this, function (_a) { switch (_a.label) { case 0: return [4 /*yield*/, this.db.getAll(storeName)]; case 1: return [2 /*return*/, _a.sent()]; } }); }); }; /** * Tries to generate an index fetcher for the given predicates. Assumes * that the given predicate conditions are contained by an AND group and * should therefore all match a single record. * * @param storeName The table to query. * @param predicates The predicates to try to AND together. * @param transaction */ IndexedDBAdapter.prototype.matchingIndexQueries = function (storeName, predicates, transaction) { var e_6, _a, e_7, _b; var _this = this; // could be expanded later to include `exec()` and a `cardinality` estimate? var queries = []; var predicateIndex = new Map(); try { for (var predicates_1 = tslib_1.__values(predicates), predicates_1_1 = predicates_1.next(); !predicates_1_1.done; predicates_1_1 = predicates_1.next()) { var predicate = predicates_1_1.value; predicateIndex.set(String(predicate.field), predicate); } } catch (e_6_1) { e_6 = { error: e_6_1 }; } finally { try { if (predicates_1_1 && !predicates_1_1.done && (_a = predicates_1.return)) _a.call(predicates_1); } finally { if (e_6) throw e_6.error; } } var store = transaction.objectStore(storeName); var _loop_2 = function (name_1) { var e_8, _a; var idx = store.index(name_1); var keypath = Array.isArray(idx.keyPath) ? idx.keyPath : [idx.keyPath]; var matchingPredicateValues = []; try { for (var keypath_1 = (e_8 = void 0, tslib_1.__values(keypath)), keypath_1_1 = keypath_1.next(); !keypath_1_1.done; keypath_1_1 = keypath_1.next()) { var field = keypath_1_1.value; var p = predicateIndex.get(field); if (p && p.operand !== null && p.operand !== undefined) { matchingPredicateValues.push(p.operand); } else { break; } } } catch (e_8_1) { e_8 = { error: e_8_1 }; } finally { try { if (keypath_1_1 && !keypath_1_1.done && (_a = keypath_1.return)) _a.call(keypath_1); } finally { if (e_8) throw e_8.error; } } // if we have a matching predicate field for each component of this index, // we can build a query for it. otherwise, we can't. if (matchingPredicateValues.length === keypath.length) { // re-create a transaction, because the transaction used to fetch the // indexes may no longer be active. queries.push(function () { return _this.db .transaction(storeName) .objectStore(storeName) .index(name_1) .getAll(_this.canonicalKeyPath(matchingPredicateValues)); }); } }; try { for (var _c = tslib_1.__values(store.indexNames), _d = _c.next(); !_d.done; _d = _c.next()) { var name_1 = _d.value; _loop_2(name_1); } } catch (e_7_1) { e_7 = { error: e_7_1 }; } finally { try { if (_d && !_d.done && (_b = _c.return)) _b.call(_c); } finally { if (e_7) throw e_7.error; } } return queries; }; IndexedDBAdapter.prototype.baseQueryIndex = function (storeName, predicates, transaction) { return tslib_1.__awaiter(this, void 0, void 0, function () { var predicateObjs, type, fieldPredicates, txn, result, groupQueries, objectQueries, indexedQueries; var _this = this; return tslib_1.__generator(this, function (_a) { switch (_a.label) { case 0: predicateObjs = predicates.predicates, type = predicates.type; // the predicate objects we care about tend to be nested at least // one level down: `{and: {or: {and: { <the predicates we want> }}}}` // so, we unpack and/or groups until we find a group with more than 1 // child OR a child that is not a group (and is therefore a predicate "object"). while (predicateObjs.length === 1 && types_1.isPredicateGroup(predicateObjs[0]) && predicateObjs[0].type !== 'not') { type = predicateObjs[0].type; predicateObjs = predicateObjs[0].predicates; } fieldPredicates = predicateObjs.filter(function (p) { return types_1.isPredicateObj(p) && p.operator === 'eq'; }); txn = transaction || this.db.transaction(storeName); result = {}; if (!(type === 'or')) return [3 /*break*/, 2]; return [4 /*yield*/, Promise.all(predicateObjs .filter(function (o) { return types_1.isPredicateGroup(o) && o.type === 'and'; }) .map(function (o) { return _this.baseQueryIndex(storeName, o, txn); })).then(function (queries) { return queries .filter(function (q) { return q.indexedQueries.length === 1; }) .map(function (i) { return i.indexedQueries; }); })]; case 1: groupQueries = _a.sent(); objectQueries = predicateObjs .filter(function (o) { return types_1.isPredicateObj(o); }) .map(function (o) { return _this.matchingIndexQueries(storeName, [o], txn); }); indexedQueries = tslib_1.__spread(groupQueries, objectQueries).map(function (q) { return q[0]; }) .filter(function (i) { return i; }); // if, after hunting for base queries, we don't have exactly 1 base query // for each child group + object, stop trying to optimize. we're not dealing // with a simple query that fits the intended optimization path. if (predicateObjs.length > indexedQueries.length) { result = { groupType: null, indexedQueries: [], }; } else { result = { groupType: 'or', indexedQueries: indexedQueries, }; } return [3 /*break*/, 3]; case 2: if (type === 'and') { // our potential indexes or lacks thereof. // note that we're only optimizing for `eq` right now. result = { groupType: type, indexedQueries: this.matchingIndexQueries(storeName, fieldPredicates, txn), }; } else { result = { groupType: null, indexedQueries: [], }; } _a.label = 3; case 3: if (!!transaction) return [3 /*break*/, 5]; return [4 /*yield*/, txn.done]; case 4: _a.sent(); _a.label = 5; case 5: return [2 /*return*/, result]; } }); }); }; IndexedDBAdapter.prototype.filterOnPredicate = function (storeName, predicates) { return tslib_1.__awaiter(this, void 0, void 0, function () { var predicateObjs, type, _a, groupType, indexedQueries, candidateResults, distinctResults, indexedQueries_1, indexedQueries_1_1, query, resultGroup, resultGroup_1, resultGroup_1_1, item, distinctificationString, e_9_1, filtered; var e_9, _b, e_10, _c; return tslib_1.__generator(this, function (_d) { switch (_d.label) { case 0: predicateObjs = predicates.predicates, type = predicates.type; return [4 /*yield*/, this.baseQueryIndex(storeName, predicates)]; case 1: _a = _d.sent(), groupType = _a.groupType, indexedQueries = _a.indexedQueries; if (!(groupType === 'and' && indexedQueries.length > 0)) return [3 /*break*/, 3]; return [4 /*yield*/, indexedQueries[0]()]; case 2: // each condition must be satsified, we can form a base set with any // ONE of those conditions and then filter. candidateResults = _d.sent(); return [3 /*break*/, 14]; case 3: if (!(groupType === 'or' && indexedQueries.length > 0 && indexedQueries.length <= MULTI_OR_CONDITION_SCAN_BREAKPOINT)) return [3 /*break*/, 12]; distinctResults = new Map(); _d.label = 4; case 4: _d.trys.push([4, 9, 10, 11]); indexedQueries_1 = tslib_1.__values(indexedQueries), indexedQueries_1_1 = indexedQueries_1.next(); _d.label = 5; case 5: if (!!indexedQueries_1_1.done) return [3 /*break*/, 8]; query = indexedQueries_1_1.value; return [4 /*yield*/, query()]; case 6: resultGroup = _d.sent(); try { for (resultGroup_1 = (e_10 = void 0, tslib_1.__values(resultGroup)), resultGroup_1_1 = resultGroup_1.next(); !resultGroup_1_1.done; resultGroup_1_1 = resultGroup_1.next()) { item = resultGroup_1_1.value; distinctificationString = JSON.stringify(item); distinctResults.set(distinctificationString, item); } } catch (e_10_1) { e_10 = { error: e_10_1 }; } finally { try { if (resultGroup_1_1 && !resultGroup_1_1.done && (_c = resultGroup_1.return)) _c.call(resultGroup_1); } finally { if (e_10) throw e_10.error; } } _d.label = 7; case 7: indexedQueries_1_1 = indexedQueries_1.next(); return [3 /*break*/, 5]; case 8: return [3 /*break*/, 11]; case 9: e_9_1 = _d.sent(); e_9 = { error: e_9_1 }; return [3 /*break*/, 11]; case 10: try { if (indexedQueries_1_1 && !indexedQueries_1_1.done && (_b = indexedQueries_1.return)) _b.call(indexedQueries_1); } finally { if (e_9) throw e_9.error; } return [7 /*endfinally*/]; case 11: // we could conceivably check for special conditions and return early here. // but, this is simpler and has not yet had a measurable performance impact. candidateResults = Array.from(distinctResults.values()); return [3 /*break*/, 14]; case 12: return [4 /*yield*/, this.getAll(storeName)]; case 13: // nothing intelligent we can do with `not` groups unless or until we start // smashing comparison operators against indexes -- at which point we could // perform some reversal here. candidateResults = (_d.sent()); _d.label = 14; case 14: filtered = predicateObjs ? candidateResults.filter(function (m) { return util_1.validatePredicate(m, type, predicateObjs); })