diff options
Diffstat (limited to 'ext/bg/js/database.js')
-rw-r--r-- | ext/bg/js/database.js | 807 |
1 files changed, 314 insertions, 493 deletions
diff --git a/ext/bg/js/database.js b/ext/bg/js/database.js index 65e267ab..47f1ebdd 100644 --- a/ext/bg/js/database.js +++ b/ext/bg/js/database.js @@ -16,423 +16,422 @@ */ /* global + * GenericDatabase * dictFieldSplit */ class Database { constructor() { - this.db = null; + this._db = new GenericDatabase(); + this._dbName = 'dict'; this._schemas = new Map(); } // Public async prepare() { - if (this.db !== null) { - throw new Error('Database already initialized'); - } - - try { - this.db = await Database._open('dict', 6, (db, transaction, oldVersion) => { - Database._upgrade(db, transaction, oldVersion, [ - { - version: 2, - stores: { - terms: { - primaryKey: {keyPath: 'id', autoIncrement: true}, - indices: ['dictionary', 'expression', 'reading'] - }, - kanji: { - primaryKey: {autoIncrement: true}, - indices: ['dictionary', 'character'] - }, - tagMeta: { - primaryKey: {autoIncrement: true}, - indices: ['dictionary'] - }, - dictionaries: { - primaryKey: {autoIncrement: true}, - indices: ['title', 'version'] - } + await this._db.open( + this._dbName, + 60, + [ + { + version: 20, + stores: { + terms: { + primaryKey: {keyPath: 'id', autoIncrement: true}, + indices: ['dictionary', 'expression', 'reading'] + }, + kanji: { + primaryKey: {autoIncrement: true}, + indices: ['dictionary', 'character'] + }, + tagMeta: { + primaryKey: {autoIncrement: true}, + indices: ['dictionary'] + }, + dictionaries: { + primaryKey: {autoIncrement: true}, + indices: ['title', 'version'] } - }, - { - version: 3, - stores: { - termMeta: { - primaryKey: {autoIncrement: true}, - indices: ['dictionary', 'expression'] - }, - kanjiMeta: { - primaryKey: {autoIncrement: true}, - indices: ['dictionary', 'character'] - }, - tagMeta: { - primaryKey: {autoIncrement: true}, - indices: ['dictionary', 'name'] - } + } + }, + { + version: 30, + stores: { + termMeta: { + primaryKey: {autoIncrement: true}, + indices: ['dictionary', 'expression'] + }, + kanjiMeta: { + primaryKey: {autoIncrement: true}, + indices: ['dictionary', 'character'] + }, + tagMeta: { + primaryKey: {autoIncrement: true}, + indices: ['dictionary', 'name'] } - }, - { - version: 4, - stores: { - terms: { - primaryKey: {keyPath: 'id', autoIncrement: true}, - indices: ['dictionary', 'expression', 'reading', 'sequence'] - } + } + }, + { + version: 40, + stores: { + terms: { + primaryKey: {keyPath: 'id', autoIncrement: true}, + indices: ['dictionary', 'expression', 'reading', 'sequence'] } - }, - { - version: 5, - stores: { - terms: { - primaryKey: {keyPath: 'id', autoIncrement: true}, - indices: ['dictionary', 'expression', 'reading', 'sequence', 'expressionReverse', 'readingReverse'] - } + } + }, + { + version: 50, + stores: { + terms: { + primaryKey: {keyPath: 'id', autoIncrement: true}, + indices: ['dictionary', 'expression', 'reading', 'sequence', 'expressionReverse', 'readingReverse'] } - }, - { - version: 6, - stores: { - media: { - primaryKey: {keyPath: 'id', autoIncrement: true}, - indices: ['dictionary', 'path'] - } + } + }, + { + version: 60, + stores: { + media: { + primaryKey: {keyPath: 'id', autoIncrement: true}, + indices: ['dictionary', 'path'] } } - ]); - }); - return true; - } catch (e) { - yomichan.logError(e); - return false; - } + } + ] + ); } async close() { - this._validate(); - this.db.close(); - this.db = null; + this._db.close(); } isPrepared() { - return this.db !== null; + return this._db.isOpen(); } async purge() { - this._validate(); - - this.db.close(); - await Database._deleteDatabase(this.db.name); - this.db = null; - + if (this._db.isOpening()) { + throw new Error('Cannot purge database while opening'); + } + if (this._db.isOpen()) { + this._db.close(); + } + await GenericDatabase.deleteDatabase(this._dbName); await this.prepare(); } async deleteDictionary(dictionaryName, progressSettings, onProgress) { - this._validate(); - const targets = [ ['dictionaries', 'title'], ['kanji', 'dictionary'], ['kanjiMeta', 'dictionary'], ['terms', 'dictionary'], ['termMeta', 'dictionary'], - ['tagMeta', 'dictionary'] + ['tagMeta', 'dictionary'], + ['media', 'dictionary'] ]; - const promises = []; + + const {rate} = progressSettings; const progressData = { count: 0, processed: 0, storeCount: targets.length, storesProcesed: 0 }; - let progressRate = (typeof progressSettings === 'object' && progressSettings !== null ? progressSettings.rate : 0); - if (typeof progressRate !== 'number' || progressRate <= 0) { - progressRate = 1000; - } - - for (const [objectStoreName, index] of targets) { - const dbTransaction = this.db.transaction([objectStoreName], 'readwrite'); - const dbObjectStore = dbTransaction.objectStore(objectStoreName); - const dbIndex = dbObjectStore.index(index); - const only = IDBKeyRange.only(dictionaryName); - promises.push(Database._deleteValues(dbObjectStore, dbIndex, only, onProgress, progressData, progressRate)); - } - - await Promise.all(promises); - } - - async findTermsBulk(termList, dictionaries, wildcard) { - this._validate(); - const promises = []; - const visited = new Set(); - const results = []; - const processRow = (row, index) => { - if (dictionaries.has(row.dictionary) && !visited.has(row.id)) { - visited.add(row.id); - results.push(Database._createTerm(row, index)); + const filterKeys = (keys) => { + ++progressData.storesProcesed; + progressData.count += keys.length; + onProgress(progressData); + return keys; + }; + const onProgress2 = () => { + const processed = progressData.processed + 1; + progressData.processed = processed; + if ((processed % rate) === 0 || processed === progressData.count) { + onProgress(progressData); } }; - const useWildcard = !!wildcard; - const prefixWildcard = wildcard === 'prefix'; - - const dbTransaction = this.db.transaction(['terms'], 'readonly'); - const dbTerms = dbTransaction.objectStore('terms'); - const dbIndex1 = dbTerms.index(prefixWildcard ? 'expressionReverse' : 'expression'); - const dbIndex2 = dbTerms.index(prefixWildcard ? 'readingReverse' : 'reading'); - - for (let i = 0; i < termList.length; ++i) { - const term = prefixWildcard ? stringReverse(termList[i]) : termList[i]; - const query = useWildcard ? IDBKeyRange.bound(term, `${term}\uffff`, false, false) : IDBKeyRange.only(term); - promises.push( - Database._getAll(dbIndex1, query, i, processRow), - Database._getAll(dbIndex2, query, i, processRow) - ); + const promises = []; + for (const [objectStoreName, indexName] of targets) { + const query = IDBKeyRange.only(dictionaryName); + const promise = this._db.bulkDelete(objectStoreName, indexName, query, filterKeys, onProgress2); + promises.push(promise); } - await Promise.all(promises); - - return results; } - async findTermsExactBulk(termList, readingList, dictionaries) { - this._validate(); - - const promises = []; - const results = []; - const processRow = (row, index) => { - if (row.reading === readingList[index] && dictionaries.has(row.dictionary)) { - results.push(Database._createTerm(row, index)); + findTermsBulk(termList, dictionaries, wildcard) { + return new Promise((resolve, reject) => { + const results = []; + const count = termList.length; + if (count === 0) { + resolve(results); + return; } - }; - - const dbTransaction = this.db.transaction(['terms'], 'readonly'); - const dbTerms = dbTransaction.objectStore('terms'); - const dbIndex = dbTerms.index('expression'); - for (let i = 0; i < termList.length; ++i) { - const only = IDBKeyRange.only(termList[i]); - promises.push(Database._getAll(dbIndex, only, i, processRow)); - } - - await Promise.all(promises); + const visited = new Set(); + const useWildcard = !!wildcard; + const prefixWildcard = wildcard === 'prefix'; + + const transaction = this._db.transaction(['terms'], 'readonly'); + const terms = transaction.objectStore('terms'); + const index1 = terms.index(prefixWildcard ? 'expressionReverse' : 'expression'); + const index2 = terms.index(prefixWildcard ? 'readingReverse' : 'reading'); + + const count2 = count * 2; + let completeCount = 0; + for (let i = 0; i < count; ++i) { + const inputIndex = i; + const term = prefixWildcard ? stringReverse(termList[i]) : termList[i]; + const query = useWildcard ? IDBKeyRange.bound(term, `${term}\uffff`, false, false) : IDBKeyRange.only(term); + + const onGetAll = (rows) => { + for (const row of rows) { + if (dictionaries.has(row.dictionary) && !visited.has(row.id)) { + visited.add(row.id); + results.push(this._createTerm(row, inputIndex)); + } + } + if (++completeCount >= count2) { + resolve(results); + } + }; - return results; + this._db.getAll(index1, query, onGetAll, reject); + this._db.getAll(index2, query, onGetAll, reject); + } + }); } - async findTermsBySequenceBulk(sequenceList, mainDictionary) { - this._validate(); - - const promises = []; - const results = []; - const processRow = (row, index) => { - if (row.dictionary === mainDictionary) { - results.push(Database._createTerm(row, index)); + findTermsExactBulk(termList, readingList, dictionaries) { + return new Promise((resolve, reject) => { + const results = []; + const count = termList.length; + if (count === 0) { + resolve(results); + return; } - }; - - const dbTransaction = this.db.transaction(['terms'], 'readonly'); - const dbTerms = dbTransaction.objectStore('terms'); - const dbIndex = dbTerms.index('sequence'); - - for (let i = 0; i < sequenceList.length; ++i) { - const only = IDBKeyRange.only(sequenceList[i]); - promises.push(Database._getAll(dbIndex, only, i, processRow)); - } - - await Promise.all(promises); - return results; - } - - async findTermMetaBulk(termList, dictionaries) { - return this._findGenericBulk('termMeta', 'expression', termList, dictionaries, Database._createTermMeta); - } + const transaction = this._db.transaction(['terms'], 'readonly'); + const terms = transaction.objectStore('terms'); + const index = terms.index('expression'); - async findKanjiBulk(kanjiList, dictionaries) { - return this._findGenericBulk('kanji', 'character', kanjiList, dictionaries, Database._createKanji); - } + let completeCount = 0; + for (let i = 0; i < count; ++i) { + const inputIndex = i; + const reading = readingList[i]; + const query = IDBKeyRange.only(termList[i]); - async findKanjiMetaBulk(kanjiList, dictionaries) { - return this._findGenericBulk('kanjiMeta', 'character', kanjiList, dictionaries, Database._createKanjiMeta); - } + const onGetAll = (rows) => { + for (const row of rows) { + if (row.reading === reading && dictionaries.has(row.dictionary)) { + results.push(this._createTerm(row, inputIndex)); + } + } + if (++completeCount >= count) { + resolve(results); + } + }; - async findTagForTitle(name, title) { - this._validate(); - - let result = null; - const dbTransaction = this.db.transaction(['tagMeta'], 'readonly'); - const dbTerms = dbTransaction.objectStore('tagMeta'); - const dbIndex = dbTerms.index('name'); - const only = IDBKeyRange.only(name); - await Database._getAll(dbIndex, only, null, (row) => { - if (title === row.dictionary) { - result = row; + this._db.getAll(index, query, onGetAll, reject); } }); - - return result; } - async getMedia(targets) { - this._validate(); - - const count = targets.length; - const promises = []; - const results = new Array(count).fill(null); - const createResult = Database._createMedia; - const processRow = (row, [index, dictionaryName]) => { - if (row.dictionary === dictionaryName) { - results[index] = createResult(row, index); + findTermsBySequenceBulk(sequenceList, mainDictionary) { + return new Promise((resolve, reject) => { + const results = []; + const count = sequenceList.length; + if (count === 0) { + resolve(results); + return; } - }; - const transaction = this.db.transaction(['media'], 'readonly'); - const objectStore = transaction.objectStore('media'); - const index = objectStore.index('path'); + const transaction = this._db.transaction(['terms'], 'readonly'); + const terms = transaction.objectStore('terms'); + const index = terms.index('sequence'); - for (let i = 0; i < count; ++i) { - const {path, dictionaryName} = targets[i]; - const only = IDBKeyRange.only(path); - promises.push(Database._getAll(index, only, [i, dictionaryName], processRow)); - } + let completeCount = 0; + for (let i = 0; i < count; ++i) { + const inputIndex = i; + const query = IDBKeyRange.only(sequenceList[i]); - await Promise.all(promises); + const onGetAll = (rows) => { + for (const row of rows) { + if (row.dictionary === mainDictionary) { + results.push(this._createTerm(row, inputIndex)); + } + } + if (++completeCount >= count) { + resolve(results); + } + }; - return results; + this._db.getAll(index, query, onGetAll, reject); + } + }); } - async getDictionaryInfo() { - this._validate(); + findTermMetaBulk(termList, dictionaries) { + return this._findGenericBulk('termMeta', 'expression', termList, dictionaries, this._createTermMeta.bind(this)); + } - const results = []; - const dbTransaction = this.db.transaction(['dictionaries'], 'readonly'); - const dbDictionaries = dbTransaction.objectStore('dictionaries'); + findKanjiBulk(kanjiList, dictionaries) { + return this._findGenericBulk('kanji', 'character', kanjiList, dictionaries, this._createKanji.bind(this)); + } - await Database._getAll(dbDictionaries, null, null, (info) => results.push(info)); + findKanjiMetaBulk(kanjiList, dictionaries) { + return this._findGenericBulk('kanjiMeta', 'character', kanjiList, dictionaries, this._createKanjiMeta.bind(this)); + } - return results; + findTagForTitle(name, title) { + const query = IDBKeyRange.only(name); + return this._db.find('tagMeta', 'name', query, (row) => (row.dictionary === title), null); } - async getDictionaryCounts(dictionaryNames, getTotal) { - this._validate(); + getMedia(targets) { + return new Promise((resolve, reject) => { + const count = targets.length; + const results = new Array(count).fill(null); + if (count === 0) { + resolve(results); + return; + } - const objectStoreNames = [ - 'kanji', - 'kanjiMeta', - 'terms', - 'termMeta', - 'tagMeta' - ]; - const dbCountTransaction = this.db.transaction(objectStoreNames, 'readonly'); - - const targets = []; - for (const objectStoreName of objectStoreNames) { - targets.push([ - objectStoreName, - dbCountTransaction.objectStore(objectStoreName).index('dictionary') - ]); - } + let completeCount = 0; + const transaction = this._db.transaction(['media'], 'readonly'); + const objectStore = transaction.objectStore('media'); + const index = objectStore.index('path'); - // Query is required for Edge, otherwise index.count throws an exception. - const query1 = IDBKeyRange.lowerBound('', false); - const totalPromise = getTotal ? Database._getCounts(targets, query1) : null; - - const counts = []; - const countPromises = []; - for (let i = 0; i < dictionaryNames.length; ++i) { - counts.push(null); - const index = i; - const query2 = IDBKeyRange.only(dictionaryNames[i]); - const countPromise = Database._getCounts(targets, query2).then((v) => counts[index] = v); - countPromises.push(countPromise); - } - await Promise.all(countPromises); + for (let i = 0; i < count; ++i) { + const inputIndex = i; + const {path, dictionaryName} = targets[i]; + const query = IDBKeyRange.only(path); - const result = {counts}; - if (totalPromise !== null) { - result.total = await totalPromise; - } - return result; + const onGetAll = (rows) => { + for (const row of rows) { + if (row.dictionary !== dictionaryName) { continue; } + results[inputIndex] = this._createMedia(row, inputIndex); + } + if (++completeCount >= count) { + resolve(results); + } + }; + + this._db.getAll(index, query, onGetAll, reject); + } + }); } - async dictionaryExists(title) { - this._validate(); - const transaction = this.db.transaction(['dictionaries'], 'readonly'); - const index = transaction.objectStore('dictionaries').index('title'); - const query = IDBKeyRange.only(title); - const count = await Database._getCount(index, query); - return count > 0; + getDictionaryInfo() { + return new Promise((resolve, reject) => { + const transaction = this._db.transaction(['dictionaries'], 'readonly'); + const objectStore = transaction.objectStore('dictionaries'); + this._db.getAll(objectStore, null, resolve, reject); + }); } - bulkAdd(objectStoreName, items, start, count) { + getDictionaryCounts(dictionaryNames, getTotal) { return new Promise((resolve, reject) => { - const transaction = this.db.transaction([objectStoreName], 'readwrite'); - const objectStore = transaction.objectStore(objectStoreName); + const targets = [ + ['kanji', 'dictionary'], + ['kanjiMeta', 'dictionary'], + ['terms', 'dictionary'], + ['termMeta', 'dictionary'], + ['tagMeta', 'dictionary'], + ['media', 'dictionary'] + ]; + const objectStoreNames = targets.map(([objectStoreName]) => objectStoreName); + const transaction = this._db.transaction(objectStoreNames, 'readonly'); + const databaseTargets = targets.map(([objectStoreName, indexName]) => { + const objectStore = transaction.objectStore(objectStoreName); + const index = objectStore.index(indexName); + return {objectStore, index}; + }); - if (start + count > items.length) { - count = items.length - start; + const countTargets = []; + if (getTotal) { + for (const {objectStore} of databaseTargets) { + countTargets.push([objectStore, null]); + } } - - if (count <= 0) { - resolve(); - return; + for (const dictionaryName of dictionaryNames) { + const query = IDBKeyRange.only(dictionaryName); + for (const {index} of databaseTargets) { + countTargets.push([index, query]); + } } - const end = start + count; - let completedCount = 0; - const onError = (e) => reject(e); - const onSuccess = () => { - if (++completedCount >= count) { - resolve(); + const onCountComplete = (results) => { + const resultCount = results.length; + const targetCount = targets.length; + const counts = []; + for (let i = 0; i < resultCount; i += targetCount) { + const countGroup = {}; + for (let j = 0; j < targetCount; ++j) { + countGroup[targets[j][0]] = results[i + j]; + } + counts.push(countGroup); } + const total = getTotal ? counts.shift() : null; + resolve({total, counts}); }; - for (let i = start; i < end; ++i) { - const request = objectStore.add(items[i]); - request.onerror = onError; - request.onsuccess = onSuccess; - } + this._db.bulkCount(countTargets, onCountComplete, reject); }); } - // Private + async dictionaryExists(title) { + const query = IDBKeyRange.only(title); + const result = await this._db.find('dictionaries', 'title', query); + return typeof result !== 'undefined'; + } - _validate() { - if (this.db === null) { - throw new Error('Database not initialized'); - } + bulkAdd(objectStoreName, items, start, count) { + return this._db.bulkAdd(objectStoreName, items, start, count); } - async _findGenericBulk(tableName, indexName, indexValueList, dictionaries, createResult) { - this._validate(); + // Private - const promises = []; - const results = []; - const processRow = (row, index) => { - if (dictionaries.has(row.dictionary)) { - results.push(createResult(row, index)); + async _findGenericBulk(objectStoreName, indexName, indexValueList, dictionaries, createResult) { + return new Promise((resolve, reject) => { + const results = []; + const count = indexValueList.length; + if (count === 0) { + resolve(results); + return; } - }; - const dbTransaction = this.db.transaction([tableName], 'readonly'); - const dbTerms = dbTransaction.objectStore(tableName); - const dbIndex = dbTerms.index(indexName); + const transaction = this._db.transaction([objectStoreName], 'readonly'); + const terms = transaction.objectStore(objectStoreName); + const index = terms.index(indexName); - for (let i = 0; i < indexValueList.length; ++i) { - const only = IDBKeyRange.only(indexValueList[i]); - promises.push(Database._getAll(dbIndex, only, i, processRow)); - } + let completeCount = 0; + for (let i = 0; i < count; ++i) { + const inputIndex = i; + const query = IDBKeyRange.only(indexValueList[i]); - await Promise.all(promises); + const onGetAll = (rows) => { + for (const row of rows) { + if (dictionaries.has(row.dictionary)) { + results.push(createResult(row, inputIndex)); + } + } + if (++completeCount >= count) { + resolve(results); + } + }; - return results; + this._db.getAll(index, query, onGetAll, reject); + } + }); } - static _createTerm(row, index) { + _createTerm(row, index) { return { index, expression: row.expression, @@ -448,7 +447,7 @@ class Database { }; } - static _createKanji(row, index) { + _createKanji(row, index) { return { index, character: row.character, @@ -461,193 +460,15 @@ class Database { }; } - static _createTermMeta({expression, mode, data, dictionary}, index) { + _createTermMeta({expression, mode, data, dictionary}, index) { return {expression, mode, data, dictionary, index}; } - static _createKanjiMeta({character, mode, data, dictionary}, index) { + _createKanjiMeta({character, mode, data, dictionary}, index) { return {character, mode, data, dictionary, index}; } - static _createMedia(row, index) { + _createMedia(row, index) { return Object.assign({}, row, {index}); } - - static _getAll(dbIndex, query, context, processRow) { - const fn = typeof dbIndex.getAll === 'function' ? Database._getAllFast : Database._getAllUsingCursor; - return fn(dbIndex, query, context, processRow); - } - - static _getAllFast(dbIndex, query, context, processRow) { - return new Promise((resolve, reject) => { - const request = dbIndex.getAll(query); - request.onerror = (e) => reject(e); - request.onsuccess = (e) => { - for (const row of e.target.result) { - processRow(row, context); - } - resolve(); - }; - }); - } - - static _getAllUsingCursor(dbIndex, query, context, processRow) { - return new Promise((resolve, reject) => { - const request = dbIndex.openCursor(query, 'next'); - request.onerror = (e) => reject(e); - request.onsuccess = (e) => { - const cursor = e.target.result; - if (cursor) { - processRow(cursor.value, context); - cursor.continue(); - } else { - resolve(); - } - }; - }); - } - - static _getCounts(targets, query) { - const countPromises = []; - const counts = {}; - for (const [objectStoreName, index] of targets) { - const n = objectStoreName; - const countPromise = Database._getCount(index, query).then((count) => counts[n] = count); - countPromises.push(countPromise); - } - return Promise.all(countPromises).then(() => counts); - } - - static _getCount(dbIndex, query) { - return new Promise((resolve, reject) => { - const request = dbIndex.count(query); - request.onerror = (e) => reject(e); - request.onsuccess = (e) => resolve(e.target.result); - }); - } - - static _getAllKeys(dbIndex, query) { - const fn = typeof dbIndex.getAllKeys === 'function' ? Database._getAllKeysFast : Database._getAllKeysUsingCursor; - return fn(dbIndex, query); - } - - static _getAllKeysFast(dbIndex, query) { - return new Promise((resolve, reject) => { - const request = dbIndex.getAllKeys(query); - request.onerror = (e) => reject(e); - request.onsuccess = (e) => resolve(e.target.result); - }); - } - - static _getAllKeysUsingCursor(dbIndex, query) { - return new Promise((resolve, reject) => { - const primaryKeys = []; - const request = dbIndex.openKeyCursor(query, 'next'); - request.onerror = (e) => reject(e); - request.onsuccess = (e) => { - const cursor = e.target.result; - if (cursor) { - primaryKeys.push(cursor.primaryKey); - cursor.continue(); - } else { - resolve(primaryKeys); - } - }; - }); - } - - static async _deleteValues(dbObjectStore, dbIndex, query, onProgress, progressData, progressRate) { - const hasProgress = (typeof onProgress === 'function'); - const count = await Database._getCount(dbIndex, query); - ++progressData.storesProcesed; - progressData.count += count; - if (hasProgress) { - onProgress(progressData); - } - - const onValueDeleted = ( - hasProgress ? - () => { - const p = ++progressData.processed; - if ((p % progressRate) === 0 || p === progressData.count) { - onProgress(progressData); - } - } : - () => {} - ); - - const promises = []; - const primaryKeys = await Database._getAllKeys(dbIndex, query); - for (const key of primaryKeys) { - const promise = Database._deleteValue(dbObjectStore, key).then(onValueDeleted); - promises.push(promise); - } - - await Promise.all(promises); - } - - static _deleteValue(dbObjectStore, key) { - return new Promise((resolve, reject) => { - const request = dbObjectStore.delete(key); - request.onerror = (e) => reject(e); - request.onsuccess = () => resolve(); - }); - } - - static _open(name, version, onUpgradeNeeded) { - return new Promise((resolve, reject) => { - const request = indexedDB.open(name, version * 10); - - request.onupgradeneeded = (event) => { - try { - request.transaction.onerror = (e) => reject(e); - onUpgradeNeeded(request.result, request.transaction, event.oldVersion / 10, event.newVersion / 10); - } catch (e) { - reject(e); - } - }; - - request.onerror = (e) => reject(e); - request.onsuccess = () => resolve(request.result); - }); - } - - static _upgrade(db, transaction, oldVersion, upgrades) { - for (const {version, stores} of upgrades) { - if (oldVersion >= version) { continue; } - - const objectStoreNames = Object.keys(stores); - for (const objectStoreName of objectStoreNames) { - const {primaryKey, indices} = stores[objectStoreName]; - - const objectStoreNames2 = transaction.objectStoreNames || db.objectStoreNames; - const objectStore = ( - Database._listContains(objectStoreNames2, objectStoreName) ? - transaction.objectStore(objectStoreName) : - db.createObjectStore(objectStoreName, primaryKey) - ); - - for (const indexName of indices) { - if (Database._listContains(objectStore.indexNames, indexName)) { continue; } - - objectStore.createIndex(indexName, indexName, {}); - } - } - } - } - - static _deleteDatabase(dbName) { - return new Promise((resolve, reject) => { - const request = indexedDB.deleteDatabase(dbName); - request.onerror = (e) => reject(e); - request.onsuccess = () => resolve(); - }); - } - - static _listContains(list, value) { - for (let i = 0, ii = list.length; i < ii; ++i) { - if (list[i] === value) { return true; } - } - return false; - } } |