From 29ec13bd6da9aa6d7aa9fa76ef4a45622eac1b6e Mon Sep 17 00:00:00 2001 From: kirillzyusko Date: Wed, 28 Feb 2024 16:33:51 +0100 Subject: [PATCH 1/5] feat: fallback to InMemoryProvider --- lib/storage/__mocks__/index.ts | 1 + lib/storage/index.ts | 138 +++++++++++++------ lib/storage/providers/IDBKeyValProvider.ts | 4 + lib/storage/providers/MemoryOnlyProvider.ts | 144 ++++++++++++++++++++ lib/storage/providers/NoopProvider.ts | 103 ++++++++++++++ lib/storage/providers/SQLiteProvider.ts | 4 + lib/storage/providers/types.ts | 6 +- package-lock.json | 59 +++++++- package.json | 1 + 9 files changed, 408 insertions(+), 52 deletions(-) create mode 100644 lib/storage/providers/MemoryOnlyProvider.ts create mode 100644 lib/storage/providers/NoopProvider.ts diff --git a/lib/storage/__mocks__/index.ts b/lib/storage/__mocks__/index.ts index 2c8578ea..87b6712b 100644 --- a/lib/storage/__mocks__/index.ts +++ b/lib/storage/__mocks__/index.ts @@ -10,6 +10,7 @@ const set = jest.fn((key, value) => { }); const idbKeyvalMock: StorageProvider = { + name: 'KeyValMockProvider', init: () => undefined, setItem(key, value) { return set(key, value); diff --git a/lib/storage/index.ts b/lib/storage/index.ts index a38348fc..acb5a87b 100644 --- a/lib/storage/index.ts +++ b/lib/storage/index.ts @@ -1,13 +1,52 @@ +import * as Logger from '../Logger'; + import PlatformStorage from './platforms'; import InstanceSync from './InstanceSync'; +import NoopProvider from './providers/NoopProvider'; import type StorageProvider from './providers/types'; -const provider = PlatformStorage; +let provider = PlatformStorage; let shouldKeepInstancesSync = false; +let finishInitalization: (value?: unknown) => void; +const initPromise = new Promise((resolve) => { + finishInitalization = resolve; +}); type Storage = { getStorageProvider: () => StorageProvider; -} & StorageProvider; +} & Omit; + +/** + * Degrade performance by removing the storage provider and only using cache + */ +function degradePerformance(error: Error) { + Logger.logAlert(`Error while using ${provider.name}. Falling back to only using cache and dropping storage.`); + console.error(error); + provider = NoopProvider; +} + +/** + * Runs a piece of code and degrades performance if certain errors are thrown + */ +function tryOrDegradePerformance(fn: () => Promise | T): Promise { + return new Promise((resolve, reject) => { + initPromise.then(() => { + try { + resolve(fn()); + } catch (error) { + // Test for known critical errors that the storage provider throws, e.g. when storage is full + if (error instanceof Error) { + // IndexedDB error when storage is full (https://github.com/Expensify/App/issues/29403) + if (error.message.includes('Internal error opening backing store for indexedDB.open')) { + degradePerformance(error); + } + } + + reject(error); + } + }); + }); +} const Storage: Storage = { /** @@ -22,112 +61,121 @@ const Storage: Storage = { * and enables fallback providers if necessary */ init() { - provider.init(); + tryOrDegradePerformance(() => { + provider.init(); + }).finally(() => { + finishInitalization(); + }); }, /** * Get the value of a given key or return `null` if it's not available */ - getItem: (key) => provider.getItem(key), + getItem: (key) => tryOrDegradePerformance(() => provider.getItem(key)), /** * Get multiple key-value pairs for the give array of keys in a batch */ - multiGet: (keys) => provider.multiGet(keys), + multiGet: (keys) => tryOrDegradePerformance(() => provider.multiGet(keys)), /** * Sets the value for a given key. The only requirement is that the value should be serializable to JSON string */ - setItem: (key, value) => { - const promise = provider.setItem(key, value); + setItem: (key, value) => + tryOrDegradePerformance(() => { + const promise = provider.setItem(key, value); - if (shouldKeepInstancesSync) { - return promise.then(() => InstanceSync.setItem(key)); - } + if (shouldKeepInstancesSync) { + return promise.then(() => InstanceSync.setItem(key)); + } - return promise; - }, + return promise; + }), /** * Stores multiple key-value pairs in a batch */ - multiSet: (pairs) => provider.multiSet(pairs), + multiSet: (pairs) => tryOrDegradePerformance(() => provider.multiSet(pairs)), /** * Merging an existing value with a new one */ - mergeItem: (key, changes, modifiedData) => { - const promise = provider.mergeItem(key, changes, modifiedData); + mergeItem: (key, changes, modifiedData) => + tryOrDegradePerformance(() => { + const promise = provider.mergeItem(key, changes, modifiedData); - if (shouldKeepInstancesSync) { - return promise.then(() => InstanceSync.mergeItem(key)); - } + if (shouldKeepInstancesSync) { + return promise.then(() => InstanceSync.mergeItem(key)); + } - return promise; - }, + return promise; + }), /** * Multiple merging of existing and new values in a batch * This function also removes all nested null values from an object. */ - multiMerge: (pairs) => provider.multiMerge(pairs), + multiMerge: (pairs) => tryOrDegradePerformance(() => provider.multiMerge(pairs)), /** * Removes given key and its value */ - removeItem: (key) => { - const promise = provider.removeItem(key); + removeItem: (key) => + tryOrDegradePerformance(() => { + const promise = provider.removeItem(key); - if (shouldKeepInstancesSync) { - return promise.then(() => InstanceSync.removeItem(key)); - } + if (shouldKeepInstancesSync) { + return promise.then(() => InstanceSync.removeItem(key)); + } - return promise; - }, + return promise; + }), /** * Remove given keys and their values */ - removeItems: (keys) => { - const promise = provider.removeItems(keys); + removeItems: (keys) => + tryOrDegradePerformance(() => { + const promise = provider.removeItems(keys); - if (shouldKeepInstancesSync) { - return promise.then(() => InstanceSync.removeItems(keys)); - } + if (shouldKeepInstancesSync) { + return promise.then(() => InstanceSync.removeItems(keys)); + } - return promise; - }, + return promise; + }), /** * Clears everything */ - clear: () => { - if (shouldKeepInstancesSync) { - return InstanceSync.clear(() => provider.clear()); - } + clear: () => + tryOrDegradePerformance(() => { + if (shouldKeepInstancesSync) { + return InstanceSync.clear(() => provider.clear()); + } - return provider.clear(); - }, + return provider.clear(); + }), // This is a noop for now in order to keep clients from crashing see https://github.com/Expensify/Expensify/issues/312438 - setMemoryOnlyKeys: () => provider.setMemoryOnlyKeys(), + setMemoryOnlyKeys: () => tryOrDegradePerformance(() => provider.setMemoryOnlyKeys()), /** * Returns all available keys */ - getAllKeys: () => provider.getAllKeys(), + getAllKeys: () => tryOrDegradePerformance(() => provider.getAllKeys()), /** * Gets the total bytes of the store */ - getDatabaseSize: () => provider.getDatabaseSize(), + getDatabaseSize: () => tryOrDegradePerformance(() => provider.getDatabaseSize()), /** * @param onStorageKeyChanged - Storage synchronization mechanism keeping all opened tabs in sync (web only) */ keepInstancesSync(onStorageKeyChanged) { // If InstanceSync is null, it means we're on a native platform and we don't need to keep instances in sync - if (InstanceSync == null) return; + if (InstanceSync === null) return; shouldKeepInstancesSync = true; InstanceSync.init(onStorageKeyChanged); diff --git a/lib/storage/providers/IDBKeyValProvider.ts b/lib/storage/providers/IDBKeyValProvider.ts index ea359399..fac5704d 100644 --- a/lib/storage/providers/IDBKeyValProvider.ts +++ b/lib/storage/providers/IDBKeyValProvider.ts @@ -9,6 +9,10 @@ import type {Value} from './types'; let idbKeyValStore: UseStore; const provider: StorageProvider = { + /** + * The name of the provider that can be printed to the logs + */ + name: 'IDBKeyValProvider', /** * Initializes the storage provider */ diff --git a/lib/storage/providers/MemoryOnlyProvider.ts b/lib/storage/providers/MemoryOnlyProvider.ts new file mode 100644 index 00000000..c319ee3b --- /dev/null +++ b/lib/storage/providers/MemoryOnlyProvider.ts @@ -0,0 +1,144 @@ +import _ from 'underscore'; +import sizeof from 'object-sizeof'; +import utils from '../../utils'; +import type StorageProvider from './types'; +import type {Key, KeyValuePair, Value} from './types'; + +type Store = Record; + +// eslint-disable-next-line import/no-mutable-exports +let store: Store = {}; + +const setInternal = (key: Key, value: Value) => { + store[key] = value; + return Promise.resolve(value); +}; + +const isJestRunning = typeof jest !== 'undefined'; +const set = isJestRunning ? jest.fn(setInternal) : setInternal; + +const provider: StorageProvider = { + /** + * The name of the provider that can be printed to the logs + */ + name: 'MemoryOnlyProvider', + + /** + * Initializes the storage provider + */ + init() { + // do nothing + }, + + /** + * Get the value of a given key or return `null` if it's not available in memory + */ + getItem(key) { + const value = store[key]; + + return Promise.resolve(value === undefined ? null : value); + }, + + /** + * Get multiple key-value pairs for the give array of keys in a batch. + */ + multiGet(keys) { + const getPromises = _.map(keys, (key) => new Promise((resolve) => this.getItem(key).then((value) => resolve([key, value])))) as Array>; + return Promise.all(getPromises); + }, + + /** + * Sets the value for a given key. The only requirement is that the value should be serializable to JSON string + */ + setItem(key, value) { + set(key, value); + + return Promise.resolve(); + }, + + /** + * Stores multiple key-value pairs in a batch + */ + multiSet(pairs) { + const setPromises = _.map(pairs, ([key, value]) => this.setItem(key, value)); + return new Promise((resolve) => Promise.all(setPromises).then(() => resolve())); + }, + + /** + * Merging an existing value with a new one + */ + mergeItem(key, _changes, modifiedData) { + // Since Onyx already merged the existing value with the changes, we can just set the value directly + return this.setItem(key, modifiedData); + }, + + /** + * Multiple merging of existing and new values in a batch + * This function also removes all nested null values from an object. + */ + multiMerge(pairs) { + _.forEach(pairs, ([key, value]) => { + const existingValue = store[key] as unknown as Record; + const newValue = utils.fastMerge(existingValue, value as unknown as Record) as unknown as Value; + + set(key, newValue); + }); + + return Promise.resolve([]); + }, + + /** + * Remove given key and it's value from memory + */ + removeItem(key) { + delete store[key]; + return Promise.resolve(); + }, + + /** + * Remove given keys and their values from memory + */ + removeItems(keys) { + _.each(keys, (key) => { + delete store[key]; + }); + return Promise.resolve(); + }, + + /** + * Clear everything from memory + */ + clear() { + store = {}; + return Promise.resolve(); + }, + + // This is a noop for now in order to keep clients from crashing see https://github.com/Expensify/Expensify/issues/312438 + setMemoryOnlyKeys() { + // do nothing + }, + + /** + * Returns all keys available in memory + */ + getAllKeys() { + return Promise.resolve(_.keys(store)); + }, + + /** + * Gets the total bytes of the store. + * `bytesRemaining` will always be `Number.POSITIVE_INFINITY` since we don't have a hard limit on memory. + */ + getDatabaseSize() { + const storeSize = sizeof(store); + + return Promise.resolve({bytesRemaining: Number.POSITIVE_INFINITY, bytesUsed: storeSize}); + }, +}; + +const setMockStore = (data: Store) => { + store = data; +}; + +export default provider; +export {store as mockStore, set as mockSet, setMockStore}; diff --git a/lib/storage/providers/NoopProvider.ts b/lib/storage/providers/NoopProvider.ts new file mode 100644 index 00000000..06d05e65 --- /dev/null +++ b/lib/storage/providers/NoopProvider.ts @@ -0,0 +1,103 @@ +import type StorageProvider from './types'; + +const provider: StorageProvider = { + /** + * The name of the provider that can be printed to the logs + */ + name: 'NoopProvider', + + /** + * Initializes the storage provider + */ + init() { + // do nothing + }, + + /** + * Get the value of a given key or return `null` if it's not available in memory + * @param {String} key + * @return {Promise<*>} + */ + getItem() { + return Promise.resolve(null); + }, + + /** + * Get multiple key-value pairs for the give array of keys in a batch. + */ + multiGet() { + return Promise.resolve([]); + }, + + /** + * Sets the value for a given key. The only requirement is that the value should be serializable to JSON string + */ + setItem() { + return Promise.resolve(); + }, + + /** + * Stores multiple key-value pairs in a batch + */ + multiSet() { + return Promise.resolve(); + }, + + /** + * Merging an existing value with a new one + */ + mergeItem() { + return Promise.resolve(); + }, + + /** + * Multiple merging of existing and new values in a batch + * This function also removes all nested null values from an object. + */ + multiMerge() { + return Promise.resolve([]); + }, + + /** + * Remove given key and it's value from memory + */ + removeItem() { + return Promise.resolve(); + }, + + /** + * Remove given keys and their values from memory + */ + removeItems() { + return Promise.resolve(); + }, + + /** + * Clear everything from memory + */ + clear() { + return Promise.resolve(); + }, + + // This is a noop for now in order to keep clients from crashing see https://github.com/Expensify/Expensify/issues/312438 + setMemoryOnlyKeys() { + // do nothing + }, + + /** + * Returns all keys available in memory + */ + getAllKeys() { + return Promise.resolve([]); + }, + + /** + * Gets the total bytes of the store. + * `bytesRemaining` will always be `Number.POSITIVE_INFINITY` since we don't have a hard limit on memory. + */ + getDatabaseSize() { + return Promise.resolve({bytesRemaining: Number.POSITIVE_INFINITY, bytesUsed: 0}); + }, +}; + +export default provider; diff --git a/lib/storage/providers/SQLiteProvider.ts b/lib/storage/providers/SQLiteProvider.ts index 4b93e821..1a86088b 100644 --- a/lib/storage/providers/SQLiteProvider.ts +++ b/lib/storage/providers/SQLiteProvider.ts @@ -13,6 +13,10 @@ const DB_NAME = 'OnyxDB'; let db: QuickSQLiteConnection; const provider: StorageProvider = { + /** + * The name of the provider that can be printed to the logs + */ + name: 'SQLiteProvider', /** * Initializes the storage provider */ diff --git a/lib/storage/providers/types.ts b/lib/storage/providers/types.ts index 8749034d..48ec6297 100644 --- a/lib/storage/providers/types.ts +++ b/lib/storage/providers/types.ts @@ -9,6 +9,10 @@ type KeyValuePairList = KeyValuePair[]; type OnStorageKeyChanged = (key: Key, value: Value | null) => void; type StorageProvider = { + /** + * The name of the provider that can be printed to the logs + */ + name: string; /** * Initializes the storage provider */ @@ -82,4 +86,4 @@ type StorageProvider = { }; export default StorageProvider; -export type {Value, Key, KeyList, KeyValuePairList, OnStorageKeyChanged}; +export type {Value, Key, KeyList, KeyValuePair, KeyValuePairList, OnStorageKeyChanged}; diff --git a/package-lock.json b/package-lock.json index 590ee19f..c53fc65c 100644 --- a/package-lock.json +++ b/package-lock.json @@ -11,6 +11,7 @@ "dependencies": { "ascii-table": "0.0.9", "fast-equals": "^4.0.3", + "object-sizeof": "^2.6.4", "underscore": "^1.13.6" }, "devDependencies": { @@ -5357,7 +5358,6 @@ "version": "1.5.1", "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.5.1.tgz", "integrity": "sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==", - "dev": true, "funding": [ { "type": "github", @@ -9586,7 +9586,6 @@ "version": "1.2.1", "resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.2.1.tgz", "integrity": "sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==", - "dev": true, "funding": [ { "type": "github", @@ -13898,6 +13897,37 @@ "node": ">= 0.4" } }, + "node_modules/object-sizeof": { + "version": "2.6.4", + "resolved": "https://registry.npmjs.org/object-sizeof/-/object-sizeof-2.6.4.tgz", + "integrity": "sha512-YuJAf7Bi61KROcYmXm8RCeBrBw8UOaJDzTm1gp0eU7RjYi1xEte3/Nmg/VyPaHcJZ3sNojs1Y0xvSrgwkLmcFw==", + "dependencies": { + "buffer": "^6.0.3" + } + }, + "node_modules/object-sizeof/node_modules/buffer": { + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/buffer/-/buffer-6.0.3.tgz", + "integrity": "sha512-FTiCpNxtwiZZHEZbcbTIcZjERVICn9yq/pDFkTl95/AxzD1naBctN7YO68riM/gLSDY7sdrMby8hofADYuuqOA==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "dependencies": { + "base64-js": "^1.3.1", + "ieee754": "^1.2.1" + } + }, "node_modules/object-to-spawn-args": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/object-to-spawn-args/-/object-to-spawn-args-2.0.1.tgz", @@ -22273,8 +22303,7 @@ "base64-js": { "version": "1.5.1", "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.5.1.tgz", - "integrity": "sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==", - "dev": true + "integrity": "sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==" }, "binary-extensions": { "version": "2.2.0", @@ -25529,8 +25558,7 @@ "ieee754": { "version": "1.2.1", "resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.2.1.tgz", - "integrity": "sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==", - "dev": true + "integrity": "sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==" }, "ignore": { "version": "4.0.6", @@ -28947,6 +28975,25 @@ "integrity": "sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA==", "dev": true }, + "object-sizeof": { + "version": "2.6.4", + "resolved": "https://registry.npmjs.org/object-sizeof/-/object-sizeof-2.6.4.tgz", + "integrity": "sha512-YuJAf7Bi61KROcYmXm8RCeBrBw8UOaJDzTm1gp0eU7RjYi1xEte3/Nmg/VyPaHcJZ3sNojs1Y0xvSrgwkLmcFw==", + "requires": { + "buffer": "^6.0.3" + }, + "dependencies": { + "buffer": { + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/buffer/-/buffer-6.0.3.tgz", + "integrity": "sha512-FTiCpNxtwiZZHEZbcbTIcZjERVICn9yq/pDFkTl95/AxzD1naBctN7YO68riM/gLSDY7sdrMby8hofADYuuqOA==", + "requires": { + "base64-js": "^1.3.1", + "ieee754": "^1.2.1" + } + } + } + }, "object-to-spawn-args": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/object-to-spawn-args/-/object-to-spawn-args-2.0.1.tgz", diff --git a/package.json b/package.json index 5d61479d..07b77d3e 100644 --- a/package.json +++ b/package.json @@ -40,6 +40,7 @@ "dependencies": { "ascii-table": "0.0.9", "fast-equals": "^4.0.3", + "object-sizeof": "^2.6.4", "underscore": "^1.13.6" }, "devDependencies": { From b6fb255c936dc2a00f3a070e293df41ccc1fc1a0 Mon Sep 17 00:00:00 2001 From: kirillzyusko Date: Wed, 28 Feb 2024 16:56:18 +0100 Subject: [PATCH 2/5] test: verify all methods are implemented --- tests/unit/storage/providers/StorageProviderTest.js | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/tests/unit/storage/providers/StorageProviderTest.js b/tests/unit/storage/providers/StorageProviderTest.js index 5ce43cfc..589eff96 100644 --- a/tests/unit/storage/providers/StorageProviderTest.js +++ b/tests/unit/storage/providers/StorageProviderTest.js @@ -6,10 +6,12 @@ jest.unmock('../../../../lib/storage/providers/IDBKeyValProvider'); import _ from 'underscore'; import NativeStorage from '../../../../lib/storage/platforms/index.native'; import WebStorage from '../../../../lib/storage/platforms/index'; +import MemoryOnlyProvider from '../../../../lib/storage/providers/MemoryOnlyProvider'; it('storage providers have same methods implemented', () => { + const memoryOnlyMethods = _.keys(MemoryOnlyProvider); const nativeMethods = _.keys(NativeStorage); const webMethods = _.keys(WebStorage); - const unimplementedMethods = _.difference(nativeMethods, webMethods); + const unimplementedMethods = _.difference(nativeMethods, webMethods, memoryOnlyMethods); expect(unimplementedMethods.length).toBe(0); }); From 8b6d93de98b343304e714945d2b3f5a565519afd Mon Sep 17 00:00:00 2001 From: kirillzyusko Date: Wed, 28 Feb 2024 17:00:16 +0100 Subject: [PATCH 3/5] refactor: move MemoryOnlyProvider to 3rd PR --- lib/storage/providers/MemoryOnlyProvider.ts | 144 ------------------ .../storage/providers/StorageProviderTest.js | 4 +- 2 files changed, 1 insertion(+), 147 deletions(-) delete mode 100644 lib/storage/providers/MemoryOnlyProvider.ts diff --git a/lib/storage/providers/MemoryOnlyProvider.ts b/lib/storage/providers/MemoryOnlyProvider.ts deleted file mode 100644 index c319ee3b..00000000 --- a/lib/storage/providers/MemoryOnlyProvider.ts +++ /dev/null @@ -1,144 +0,0 @@ -import _ from 'underscore'; -import sizeof from 'object-sizeof'; -import utils from '../../utils'; -import type StorageProvider from './types'; -import type {Key, KeyValuePair, Value} from './types'; - -type Store = Record; - -// eslint-disable-next-line import/no-mutable-exports -let store: Store = {}; - -const setInternal = (key: Key, value: Value) => { - store[key] = value; - return Promise.resolve(value); -}; - -const isJestRunning = typeof jest !== 'undefined'; -const set = isJestRunning ? jest.fn(setInternal) : setInternal; - -const provider: StorageProvider = { - /** - * The name of the provider that can be printed to the logs - */ - name: 'MemoryOnlyProvider', - - /** - * Initializes the storage provider - */ - init() { - // do nothing - }, - - /** - * Get the value of a given key or return `null` if it's not available in memory - */ - getItem(key) { - const value = store[key]; - - return Promise.resolve(value === undefined ? null : value); - }, - - /** - * Get multiple key-value pairs for the give array of keys in a batch. - */ - multiGet(keys) { - const getPromises = _.map(keys, (key) => new Promise((resolve) => this.getItem(key).then((value) => resolve([key, value])))) as Array>; - return Promise.all(getPromises); - }, - - /** - * Sets the value for a given key. The only requirement is that the value should be serializable to JSON string - */ - setItem(key, value) { - set(key, value); - - return Promise.resolve(); - }, - - /** - * Stores multiple key-value pairs in a batch - */ - multiSet(pairs) { - const setPromises = _.map(pairs, ([key, value]) => this.setItem(key, value)); - return new Promise((resolve) => Promise.all(setPromises).then(() => resolve())); - }, - - /** - * Merging an existing value with a new one - */ - mergeItem(key, _changes, modifiedData) { - // Since Onyx already merged the existing value with the changes, we can just set the value directly - return this.setItem(key, modifiedData); - }, - - /** - * Multiple merging of existing and new values in a batch - * This function also removes all nested null values from an object. - */ - multiMerge(pairs) { - _.forEach(pairs, ([key, value]) => { - const existingValue = store[key] as unknown as Record; - const newValue = utils.fastMerge(existingValue, value as unknown as Record) as unknown as Value; - - set(key, newValue); - }); - - return Promise.resolve([]); - }, - - /** - * Remove given key and it's value from memory - */ - removeItem(key) { - delete store[key]; - return Promise.resolve(); - }, - - /** - * Remove given keys and their values from memory - */ - removeItems(keys) { - _.each(keys, (key) => { - delete store[key]; - }); - return Promise.resolve(); - }, - - /** - * Clear everything from memory - */ - clear() { - store = {}; - return Promise.resolve(); - }, - - // This is a noop for now in order to keep clients from crashing see https://github.com/Expensify/Expensify/issues/312438 - setMemoryOnlyKeys() { - // do nothing - }, - - /** - * Returns all keys available in memory - */ - getAllKeys() { - return Promise.resolve(_.keys(store)); - }, - - /** - * Gets the total bytes of the store. - * `bytesRemaining` will always be `Number.POSITIVE_INFINITY` since we don't have a hard limit on memory. - */ - getDatabaseSize() { - const storeSize = sizeof(store); - - return Promise.resolve({bytesRemaining: Number.POSITIVE_INFINITY, bytesUsed: storeSize}); - }, -}; - -const setMockStore = (data: Store) => { - store = data; -}; - -export default provider; -export {store as mockStore, set as mockSet, setMockStore}; diff --git a/tests/unit/storage/providers/StorageProviderTest.js b/tests/unit/storage/providers/StorageProviderTest.js index 589eff96..5ce43cfc 100644 --- a/tests/unit/storage/providers/StorageProviderTest.js +++ b/tests/unit/storage/providers/StorageProviderTest.js @@ -6,12 +6,10 @@ jest.unmock('../../../../lib/storage/providers/IDBKeyValProvider'); import _ from 'underscore'; import NativeStorage from '../../../../lib/storage/platforms/index.native'; import WebStorage from '../../../../lib/storage/platforms/index'; -import MemoryOnlyProvider from '../../../../lib/storage/providers/MemoryOnlyProvider'; it('storage providers have same methods implemented', () => { - const memoryOnlyMethods = _.keys(MemoryOnlyProvider); const nativeMethods = _.keys(NativeStorage); const webMethods = _.keys(WebStorage); - const unimplementedMethods = _.difference(nativeMethods, webMethods, memoryOnlyMethods); + const unimplementedMethods = _.difference(nativeMethods, webMethods); expect(unimplementedMethods.length).toBe(0); }); From 5491ea214b802d706180975684b257eed07e545f Mon Sep 17 00:00:00 2001 From: kirillzyusko Date: Wed, 28 Feb 2024 18:49:24 +0100 Subject: [PATCH 4/5] chore: remove unused package --- package-lock.json | 59 +++++------------------------------------------ package.json | 1 - 2 files changed, 6 insertions(+), 54 deletions(-) diff --git a/package-lock.json b/package-lock.json index c53fc65c..590ee19f 100644 --- a/package-lock.json +++ b/package-lock.json @@ -11,7 +11,6 @@ "dependencies": { "ascii-table": "0.0.9", "fast-equals": "^4.0.3", - "object-sizeof": "^2.6.4", "underscore": "^1.13.6" }, "devDependencies": { @@ -5358,6 +5357,7 @@ "version": "1.5.1", "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.5.1.tgz", "integrity": "sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==", + "dev": true, "funding": [ { "type": "github", @@ -9586,6 +9586,7 @@ "version": "1.2.1", "resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.2.1.tgz", "integrity": "sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==", + "dev": true, "funding": [ { "type": "github", @@ -13897,37 +13898,6 @@ "node": ">= 0.4" } }, - "node_modules/object-sizeof": { - "version": "2.6.4", - "resolved": "https://registry.npmjs.org/object-sizeof/-/object-sizeof-2.6.4.tgz", - "integrity": "sha512-YuJAf7Bi61KROcYmXm8RCeBrBw8UOaJDzTm1gp0eU7RjYi1xEte3/Nmg/VyPaHcJZ3sNojs1Y0xvSrgwkLmcFw==", - "dependencies": { - "buffer": "^6.0.3" - } - }, - "node_modules/object-sizeof/node_modules/buffer": { - "version": "6.0.3", - "resolved": "https://registry.npmjs.org/buffer/-/buffer-6.0.3.tgz", - "integrity": "sha512-FTiCpNxtwiZZHEZbcbTIcZjERVICn9yq/pDFkTl95/AxzD1naBctN7YO68riM/gLSDY7sdrMby8hofADYuuqOA==", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/feross" - }, - { - "type": "patreon", - "url": "https://www.patreon.com/feross" - }, - { - "type": "consulting", - "url": "https://feross.org/support" - } - ], - "dependencies": { - "base64-js": "^1.3.1", - "ieee754": "^1.2.1" - } - }, "node_modules/object-to-spawn-args": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/object-to-spawn-args/-/object-to-spawn-args-2.0.1.tgz", @@ -22303,7 +22273,8 @@ "base64-js": { "version": "1.5.1", "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.5.1.tgz", - "integrity": "sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==" + "integrity": "sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==", + "dev": true }, "binary-extensions": { "version": "2.2.0", @@ -25558,7 +25529,8 @@ "ieee754": { "version": "1.2.1", "resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.2.1.tgz", - "integrity": "sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==" + "integrity": "sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==", + "dev": true }, "ignore": { "version": "4.0.6", @@ -28975,25 +28947,6 @@ "integrity": "sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA==", "dev": true }, - "object-sizeof": { - "version": "2.6.4", - "resolved": "https://registry.npmjs.org/object-sizeof/-/object-sizeof-2.6.4.tgz", - "integrity": "sha512-YuJAf7Bi61KROcYmXm8RCeBrBw8UOaJDzTm1gp0eU7RjYi1xEte3/Nmg/VyPaHcJZ3sNojs1Y0xvSrgwkLmcFw==", - "requires": { - "buffer": "^6.0.3" - }, - "dependencies": { - "buffer": { - "version": "6.0.3", - "resolved": "https://registry.npmjs.org/buffer/-/buffer-6.0.3.tgz", - "integrity": "sha512-FTiCpNxtwiZZHEZbcbTIcZjERVICn9yq/pDFkTl95/AxzD1naBctN7YO68riM/gLSDY7sdrMby8hofADYuuqOA==", - "requires": { - "base64-js": "^1.3.1", - "ieee754": "^1.2.1" - } - } - } - }, "object-to-spawn-args": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/object-to-spawn-args/-/object-to-spawn-args-2.0.1.tgz", diff --git a/package.json b/package.json index 07b77d3e..5d61479d 100644 --- a/package.json +++ b/package.json @@ -40,7 +40,6 @@ "dependencies": { "ascii-table": "0.0.9", "fast-equals": "^4.0.3", - "object-sizeof": "^2.6.4", "underscore": "^1.13.6" }, "devDependencies": { From 956193b5d8b06ac36d1ad52debaad77fcb38104e Mon Sep 17 00:00:00 2001 From: kirillzyusko Date: Fri, 1 Mar 2024 12:19:23 +0100 Subject: [PATCH 5/5] fix: simplify initizlization --- lib/storage/index.ts | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/lib/storage/index.ts b/lib/storage/index.ts index acb5a87b..7090cd6c 100644 --- a/lib/storage/index.ts +++ b/lib/storage/index.ts @@ -61,9 +61,7 @@ const Storage: Storage = { * and enables fallback providers if necessary */ init() { - tryOrDegradePerformance(() => { - provider.init(); - }).finally(() => { + tryOrDegradePerformance(provider.init).finally(() => { finishInitalization(); }); },