diff --git a/lib/storage/providers/DexieProvider.ts b/lib/storage/providers/DexieProvider.ts index c8e0321f..f5bb54e7 100644 --- a/lib/storage/providers/DexieProvider.ts +++ b/lib/storage/providers/DexieProvider.ts @@ -1,50 +1,108 @@ +// TODO: Fix types import Dexie from 'dexie'; -import type {EntityTable} from 'dexie'; +import utils from '../../utils'; import type StorageProvider from './types'; import type {OnyxKey, OnyxValue} from '../../types'; -type DexieDatabase = Dexie & { - keyvaluepairs: EntityTable, OnyxKey>; // TODO typings -}; +class OnyxDatabase extends Dexie { + keyvaluepairs!: Dexie.Table, OnyxKey>; + + constructor() { + super('OnyxDB'); + this.version(0.1).stores({ + keyvaluepairs: '', + }); + } +} -let db: DexieDatabase; +let db: OnyxDatabase; + +const initDB = () => { + if (!db) { + db = new OnyxDatabase(); + return db.open(); + } + return Promise.resolve(); +}; const provider: StorageProvider = { /** * The name of the provider that can be printed to the logs */ name: 'DexieProvider', - /** - * Initializes the storage provider - */ - init() { - db = new Dexie('OnyxDB') as DexieDatabase; - }, + init: initDB, + setItem: (key, value) => { if (value === null) { - provider.removeItem(key); + return provider.removeItem(key); } - return db.keyvaluepairs.put(value, key); }, - // multiGet: (keys) => db.keyvaluepairs.bulkGet(keys), - // multiMerge: (pairs) => db.keyvaluepairs.bulkPut(items, keys, options), - mergeItem(key, _deltaChanges, preMergedValue) { + multiGet: (keysParam) => { + return db.keyvaluepairs.bulkGet(keysParam).then((results) => { + return results.map((result, index) => [keysParam[index], result ?? null]); + }); + }, + multiMerge: (pairs) => { + return db.transaction('rw', db.keyvaluepairs, () => { + return Promise.all( + pairs.map(([key, value]) => { + if (value === null) { + return provider.removeItem(key); + } + return db.keyvaluepairs.get(key).then((existingItem) => { + const newValue = utils.fastMerge(existingItem as Record, value as Record); + return db.keyvaluepairs.put(newValue, key); + }); + }), + ); + }); + }, + mergeItem: (key, _deltaChanges, preMergedValue) => { // Since Onyx also merged the existing value with the changes, we can just set the value directly return provider.setItem(key, preMergedValue); }, - // multiSet: (pairs) => {}, - clear: () => db.keyvaluepairs.clear(), - getAllKeys: () => db.keyvaluepairs.toCollection().keys(), - getItem: (key) => db.keyvaluepairs.get(key), - removeItem: (key) => db.keyvaluepairs.delete(key), - removeItems: (keys) => db.keyvaluepairs.bulkDelete(keys), - getDatabaseSize() { - return Promise.resolve({ - bytesUsed: 0, - bytesRemaining: 0, + multiSet: (pairs) => { + const pairsWithoutNull = pairs.filter(([, value]) => value !== null); + return db.keyvaluepairs.bulkPut( + pairsWithoutNull.map(([, value]) => value), + pairsWithoutNull.map(([key]) => key), + ); + }, + clear: () => { + return db.keyvaluepairs.clear(); + }, + getAllKeys: () => { + return db.keyvaluepairs.toCollection().keys(); + }, + getItem: (key) => { + return db.keyvaluepairs.get(key).then((result) => { + return result ?? null; }); }, + removeItem: (key) => { + return db.keyvaluepairs.delete(key); + }, + removeItems: (keysParam) => { + return db.keyvaluepairs.bulkDelete(keysParam); + }, + getDatabaseSize: () => { + if (!window.navigator || !window.navigator.storage) { + return Promise.reject(new Error('StorageManager browser API unavailable')); + } + + return window.navigator.storage + .estimate() + .then((estimate) => { + return { + bytesUsed: estimate.usage ?? 0, + bytesRemaining: (estimate.quota ?? 0) - (estimate.usage ?? 0), + }; + }) + .catch((error) => { + throw new Error(`Unable to estimate web storage quota. Original error: ${error}`); + }); + }, }; export default provider;