Skip to content

Commit

Permalink
Merge pull request #293 from margelo/perf/idb-keyval
Browse files Browse the repository at this point in the history
Perf: use idb-keyval
  • Loading branch information
mountiny authored Aug 21, 2023
2 parents 79bfb5e + 2047f73 commit 42440e1
Show file tree
Hide file tree
Showing 8 changed files with 174 additions and 286 deletions.
2 changes: 1 addition & 1 deletion jestSetup.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
jest.mock('./lib/storage');
jest.mock('./lib/storage/NativeStorage', () => require('./lib/storage/__mocks__'));
jest.mock('./lib/storage/WebStorage', () => require('./lib/storage/__mocks__'));
jest.mock('./lib/storage/providers/LocalForage', () => require('./lib/storage/__mocks__'));
jest.mock('./lib/storage/providers/IDBKeyVal', () => require('./lib/storage/__mocks__'));
4 changes: 2 additions & 2 deletions lib/storage/WebStorage.js
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
/**
* This file is here to wrap LocalForage with a layer that provides data-changed events like the ones that exist
* This file is here to wrap IDBKeyVal with a layer that provides data-changed events like the ones that exist
* when using LocalStorage APIs in the browser. These events are great because multiple tabs can listen for when
* data changes and then stay up-to-date with everything happening in Onyx.
*/
import _ from 'underscore';
import Storage from './providers/LocalForage';
import Storage from './providers/IDBKeyVal';

const SYNC_ONYX = 'SYNC_ONYX';

Expand Down
30 changes: 15 additions & 15 deletions lib/storage/__mocks__/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ const set = jest.fn((key, value) => {
return Promise.resolve(value);
});

const localForageMock = {
const idbKeyvalMock = {
setItem(key, value) {
return set(key, value);
},
Expand Down Expand Up @@ -57,23 +57,23 @@ const localForageMock = {
config() {},
};

const localForageMockSpy = {
localForageSet: set,
setItem: jest.fn(localForageMock.setItem),
getItem: jest.fn(localForageMock.getItem),
removeItem: jest.fn(localForageMock.removeItem),
removeItems: jest.fn(localForageMock.removeItems),
clear: jest.fn(localForageMock.clear),
getAllKeys: jest.fn(localForageMock.getAllKeys),
config: jest.fn(localForageMock.config),
multiGet: jest.fn(localForageMock.multiGet),
multiSet: jest.fn(localForageMock.multiSet),
multiMerge: jest.fn(localForageMock.multiMerge),
mergeItem: jest.fn(localForageMock.mergeItem),
const idbKeyvalMockSpy = {
idbKeyvalSet: set,
setItem: jest.fn(idbKeyvalMock.setItem),
getItem: jest.fn(idbKeyvalMock.getItem),
removeItem: jest.fn(idbKeyvalMock.removeItem),
removeItems: jest.fn(idbKeyvalMock.removeItems),
clear: jest.fn(idbKeyvalMock.clear),
getAllKeys: jest.fn(idbKeyvalMock.getAllKeys),
config: jest.fn(idbKeyvalMock.config),
multiGet: jest.fn(idbKeyvalMock.multiGet),
multiSet: jest.fn(idbKeyvalMock.multiSet),
multiMerge: jest.fn(idbKeyvalMock.multiMerge),
mergeItem: jest.fn(idbKeyvalMock.mergeItem),
getStorageMap: jest.fn(() => storageMapInternal),
setInitialMockData: jest.fn((data) => {
storageMapInternal = data;
}),
};

export default localForageMockSpy;
export default idbKeyvalMockSpy;
110 changes: 110 additions & 0 deletions lib/storage/providers/IDBKeyVal.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,110 @@
import {
set,
keys,
getMany,
setMany,
get,
clear,
del,
delMany,
createStore,
promisifyRequest,
} from 'idb-keyval';
import _ from 'underscore';
import fastMerge from '../../fastMerge';

const customStore = createStore('OnyxDB', 'keyvaluepairs');

const provider = {
/**
* Sets the value for a given key. The only requirement is that the value should be serializable to JSON string
* @param {String} key
* @param {*} value
* @return {Promise<void>}
*/
setItem: (key, value) => set(key, value, customStore),

/**
* Get multiple key-value pairs for the give array of keys in a batch.
* This is optimized to use only one database transaction.
* @param {String[]} keysParam
* @return {Promise<Array<[key, value]>>}
*/
multiGet: keysParam => getMany(keysParam, customStore)
.then(values => _.map(values, (value, index) => [keysParam[index], value])),

/**
* Multiple merging of existing and new values in a batch
* @param {Array<[key, value]>} pairs
* @return {Promise<void>}
*/
multiMerge: pairs => customStore('readwrite', (store) => {
// Note: we are using the manual store transaction here, to fit the read and update
// of the items in one transaction to achieve best performance.

const getValues = Promise.all(_.map(pairs, ([key]) => promisifyRequest(store.get(key))));

return getValues.then((values) => {
const upsertMany = _.map(pairs, ([key, value], index) => {
const prev = values[index];
const newValue = _.isObject(prev) ? fastMerge(prev, value) : value;
return promisifyRequest(store.put(newValue, key));
});
return Promise.all(upsertMany);
});
}),

/**
* Merging an existing value with a new one
* @param {String} key
* @param {any} _changes - not used, as we rely on the pre-merged data from the `modifiedData`
* @param {any} modifiedData - the pre-merged data from `Onyx.applyMerge`
* @return {Promise<void>}
*/
mergeItem(key, _changes, modifiedData) {
return provider.multiMerge([[key, modifiedData]]);
},

/**
* Stores multiple key-value pairs in a batch
* @param {Array<[key, value]>} pairs
* @return {Promise<void>}
*/
multiSet: pairs => setMany(pairs, customStore),

/**
* Clear everything from storage and also stops the SyncQueue from adding anything more to storage
* @returns {Promise<void>}
*/
clear: () => clear(customStore),

/**
* Returns all keys available in storage
* @returns {Promise<String[]>}
*/
getAllKeys: () => keys(customStore),

/**
* Get the value of a given key or return `null` if it's not available in storage
* @param {String} key
* @return {Promise<*>}
*/
getItem: key => get(key, customStore),

/**
* Remove given key and it's value from storage
* @param {String} key
* @returns {Promise<void>}
*/
removeItem: key => del(key, customStore),

/**
* Remove given keys and their values from storage
*
* @param {Array} keysParam
* @returns {Promise}
*/
removeItems: keysParam => delMany(keysParam, customStore),
};

export default provider;
158 changes: 0 additions & 158 deletions lib/storage/providers/LocalForage.js

This file was deleted.

Loading

0 comments on commit 42440e1

Please sign in to comment.