From 896816513402f316a945bfff8d082b0b77e96aeb Mon Sep 17 00:00:00 2001 From: Adam Horodyski Date: Thu, 29 Aug 2024 16:05:41 +0200 Subject: [PATCH 1/2] feat(wip): init a new dexie provider for the web platform --- lib/storage/platforms/index.ts | 2 +- lib/storage/providers/DexieProvider.ts | 50 ++++++++++++++++++++++++++ package-lock.json | 11 ++++++ package.json | 1 + 4 files changed, 63 insertions(+), 1 deletion(-) create mode 100644 lib/storage/providers/DexieProvider.ts diff --git a/lib/storage/platforms/index.ts b/lib/storage/platforms/index.ts index 0b95dc97d..77112b012 100644 --- a/lib/storage/platforms/index.ts +++ b/lib/storage/platforms/index.ts @@ -1,3 +1,3 @@ -import WebStorage from '../providers/IDBKeyValProvider'; +import WebStorage from '../providers/DexieProvider'; export default WebStorage; diff --git a/lib/storage/providers/DexieProvider.ts b/lib/storage/providers/DexieProvider.ts new file mode 100644 index 000000000..c8e0321f6 --- /dev/null +++ b/lib/storage/providers/DexieProvider.ts @@ -0,0 +1,50 @@ +import Dexie from 'dexie'; +import type {EntityTable} from 'dexie'; +import type StorageProvider from './types'; +import type {OnyxKey, OnyxValue} from '../../types'; + +type DexieDatabase = Dexie & { + keyvaluepairs: EntityTable, OnyxKey>; // TODO typings +}; + +let db: DexieDatabase; + +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; + }, + setItem: (key, value) => { + if (value === null) { + 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) { + // 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, + }); + }, +}; + +export default provider; diff --git a/package-lock.json b/package-lock.json index 820608662..c68ce764d 100644 --- a/package-lock.json +++ b/package-lock.json @@ -10,6 +10,7 @@ "license": "MIT", "dependencies": { "ascii-table": "0.0.9", + "dexie": "^4.0.8", "fast-equals": "^4.0.3", "underscore": "^1.13.6" }, @@ -7296,6 +7297,11 @@ "node": ">=8" } }, + "node_modules/dexie": { + "version": "4.0.8", + "resolved": "https://registry.npmjs.org/dexie/-/dexie-4.0.8.tgz", + "integrity": "sha512-1G6cJevS17KMDK847V3OHvK2zei899GwpDiqfEXHP1ASvme6eWJmAp9AU4s1son2TeGkWmC0g3y8ezOBPnalgQ==" + }, "node_modules/diff": { "version": "4.0.2", "resolved": "https://registry.npmjs.org/diff/-/diff-4.0.2.tgz", @@ -24344,6 +24350,11 @@ "integrity": "sha512-TLz+x/vEXm/Y7P7wn1EJFNLxYpUD4TgMosxY6fAVJUnJMbupHBOncxyWUG9OpTaH9EBD7uFI5LfEgmMOc54DsA==", "dev": true }, + "dexie": { + "version": "4.0.8", + "resolved": "https://registry.npmjs.org/dexie/-/dexie-4.0.8.tgz", + "integrity": "sha512-1G6cJevS17KMDK847V3OHvK2zei899GwpDiqfEXHP1ASvme6eWJmAp9AU4s1son2TeGkWmC0g3y8ezOBPnalgQ==" + }, "diff": { "version": "4.0.2", "resolved": "https://registry.npmjs.org/diff/-/diff-4.0.2.tgz", diff --git a/package.json b/package.json index a226c7bee..ec021d95f 100644 --- a/package.json +++ b/package.json @@ -40,6 +40,7 @@ }, "dependencies": { "ascii-table": "0.0.9", + "dexie": "^4.0.8", "fast-equals": "^4.0.3", "underscore": "^1.13.6" }, From 85081c1a8b9ee0de1181fc9a42f83046165d73ab Mon Sep 17 00:00:00 2001 From: Tomasz Misiukiewicz Date: Fri, 30 Aug 2024 12:03:14 +0200 Subject: [PATCH 2/2] full Dexie provider implementation --- lib/storage/providers/DexieProvider.ts | 110 +++++++++++++++++++------ 1 file changed, 84 insertions(+), 26 deletions(-) diff --git a/lib/storage/providers/DexieProvider.ts b/lib/storage/providers/DexieProvider.ts index c8e0321f6..f5bb54e70 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;