Skip to content

Commit 42440e1

Browse files
authored
Merge pull request #293 from margelo/perf/idb-keyval
Perf: use idb-keyval
2 parents 79bfb5e + 2047f73 commit 42440e1

8 files changed

Lines changed: 174 additions & 286 deletions

File tree

jestSetup.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
11
jest.mock('./lib/storage');
22
jest.mock('./lib/storage/NativeStorage', () => require('./lib/storage/__mocks__'));
33
jest.mock('./lib/storage/WebStorage', () => require('./lib/storage/__mocks__'));
4-
jest.mock('./lib/storage/providers/LocalForage', () => require('./lib/storage/__mocks__'));
4+
jest.mock('./lib/storage/providers/IDBKeyVal', () => require('./lib/storage/__mocks__'));

lib/storage/WebStorage.js

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,10 @@
11
/**
2-
* This file is here to wrap LocalForage with a layer that provides data-changed events like the ones that exist
2+
* This file is here to wrap IDBKeyVal with a layer that provides data-changed events like the ones that exist
33
* when using LocalStorage APIs in the browser. These events are great because multiple tabs can listen for when
44
* data changes and then stay up-to-date with everything happening in Onyx.
55
*/
66
import _ from 'underscore';
7-
import Storage from './providers/LocalForage';
7+
import Storage from './providers/IDBKeyVal';
88

99
const SYNC_ONYX = 'SYNC_ONYX';
1010

lib/storage/__mocks__/index.js

Lines changed: 15 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ const set = jest.fn((key, value) => {
88
return Promise.resolve(value);
99
});
1010

11-
const localForageMock = {
11+
const idbKeyvalMock = {
1212
setItem(key, value) {
1313
return set(key, value);
1414
},
@@ -57,23 +57,23 @@ const localForageMock = {
5757
config() {},
5858
};
5959

60-
const localForageMockSpy = {
61-
localForageSet: set,
62-
setItem: jest.fn(localForageMock.setItem),
63-
getItem: jest.fn(localForageMock.getItem),
64-
removeItem: jest.fn(localForageMock.removeItem),
65-
removeItems: jest.fn(localForageMock.removeItems),
66-
clear: jest.fn(localForageMock.clear),
67-
getAllKeys: jest.fn(localForageMock.getAllKeys),
68-
config: jest.fn(localForageMock.config),
69-
multiGet: jest.fn(localForageMock.multiGet),
70-
multiSet: jest.fn(localForageMock.multiSet),
71-
multiMerge: jest.fn(localForageMock.multiMerge),
72-
mergeItem: jest.fn(localForageMock.mergeItem),
60+
const idbKeyvalMockSpy = {
61+
idbKeyvalSet: set,
62+
setItem: jest.fn(idbKeyvalMock.setItem),
63+
getItem: jest.fn(idbKeyvalMock.getItem),
64+
removeItem: jest.fn(idbKeyvalMock.removeItem),
65+
removeItems: jest.fn(idbKeyvalMock.removeItems),
66+
clear: jest.fn(idbKeyvalMock.clear),
67+
getAllKeys: jest.fn(idbKeyvalMock.getAllKeys),
68+
config: jest.fn(idbKeyvalMock.config),
69+
multiGet: jest.fn(idbKeyvalMock.multiGet),
70+
multiSet: jest.fn(idbKeyvalMock.multiSet),
71+
multiMerge: jest.fn(idbKeyvalMock.multiMerge),
72+
mergeItem: jest.fn(idbKeyvalMock.mergeItem),
7373
getStorageMap: jest.fn(() => storageMapInternal),
7474
setInitialMockData: jest.fn((data) => {
7575
storageMapInternal = data;
7676
}),
7777
};
7878

79-
export default localForageMockSpy;
79+
export default idbKeyvalMockSpy;

lib/storage/providers/IDBKeyVal.js

Lines changed: 110 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,110 @@
1+
import {
2+
set,
3+
keys,
4+
getMany,
5+
setMany,
6+
get,
7+
clear,
8+
del,
9+
delMany,
10+
createStore,
11+
promisifyRequest,
12+
} from 'idb-keyval';
13+
import _ from 'underscore';
14+
import fastMerge from '../../fastMerge';
15+
16+
const customStore = createStore('OnyxDB', 'keyvaluepairs');
17+
18+
const provider = {
19+
/**
20+
* Sets the value for a given key. The only requirement is that the value should be serializable to JSON string
21+
* @param {String} key
22+
* @param {*} value
23+
* @return {Promise<void>}
24+
*/
25+
setItem: (key, value) => set(key, value, customStore),
26+
27+
/**
28+
* Get multiple key-value pairs for the give array of keys in a batch.
29+
* This is optimized to use only one database transaction.
30+
* @param {String[]} keysParam
31+
* @return {Promise<Array<[key, value]>>}
32+
*/
33+
multiGet: keysParam => getMany(keysParam, customStore)
34+
.then(values => _.map(values, (value, index) => [keysParam[index], value])),
35+
36+
/**
37+
* Multiple merging of existing and new values in a batch
38+
* @param {Array<[key, value]>} pairs
39+
* @return {Promise<void>}
40+
*/
41+
multiMerge: pairs => customStore('readwrite', (store) => {
42+
// Note: we are using the manual store transaction here, to fit the read and update
43+
// of the items in one transaction to achieve best performance.
44+
45+
const getValues = Promise.all(_.map(pairs, ([key]) => promisifyRequest(store.get(key))));
46+
47+
return getValues.then((values) => {
48+
const upsertMany = _.map(pairs, ([key, value], index) => {
49+
const prev = values[index];
50+
const newValue = _.isObject(prev) ? fastMerge(prev, value) : value;
51+
return promisifyRequest(store.put(newValue, key));
52+
});
53+
return Promise.all(upsertMany);
54+
});
55+
}),
56+
57+
/**
58+
* Merging an existing value with a new one
59+
* @param {String} key
60+
* @param {any} _changes - not used, as we rely on the pre-merged data from the `modifiedData`
61+
* @param {any} modifiedData - the pre-merged data from `Onyx.applyMerge`
62+
* @return {Promise<void>}
63+
*/
64+
mergeItem(key, _changes, modifiedData) {
65+
return provider.multiMerge([[key, modifiedData]]);
66+
},
67+
68+
/**
69+
* Stores multiple key-value pairs in a batch
70+
* @param {Array<[key, value]>} pairs
71+
* @return {Promise<void>}
72+
*/
73+
multiSet: pairs => setMany(pairs, customStore),
74+
75+
/**
76+
* Clear everything from storage and also stops the SyncQueue from adding anything more to storage
77+
* @returns {Promise<void>}
78+
*/
79+
clear: () => clear(customStore),
80+
81+
/**
82+
* Returns all keys available in storage
83+
* @returns {Promise<String[]>}
84+
*/
85+
getAllKeys: () => keys(customStore),
86+
87+
/**
88+
* Get the value of a given key or return `null` if it's not available in storage
89+
* @param {String} key
90+
* @return {Promise<*>}
91+
*/
92+
getItem: key => get(key, customStore),
93+
94+
/**
95+
* Remove given key and it's value from storage
96+
* @param {String} key
97+
* @returns {Promise<void>}
98+
*/
99+
removeItem: key => del(key, customStore),
100+
101+
/**
102+
* Remove given keys and their values from storage
103+
*
104+
* @param {Array} keysParam
105+
* @returns {Promise}
106+
*/
107+
removeItems: keysParam => delMany(keysParam, customStore),
108+
};
109+
110+
export default provider;

lib/storage/providers/LocalForage.js

Lines changed: 0 additions & 158 deletions
This file was deleted.

0 commit comments

Comments
 (0)