react-native-onyx 1.0.60 → 1.0.62
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/web.development.js +69 -205
- package/dist/web.development.js.map +1 -1
- package/dist/web.min.js +1 -1
- package/dist/web.min.js.map +1 -1
- package/lib/Onyx.js +1 -1
- package/lib/storage/WebStorage.js +2 -2
- package/lib/storage/__mocks__/index.js +15 -15
- package/lib/storage/providers/IDBKeyVal.js +110 -0
- package/lib/withOnyx.js +12 -1
- package/package.json +6 -10
- package/lib/storage/providers/LocalForage.js +0 -158
package/lib/Onyx.js
CHANGED
|
@@ -901,7 +901,6 @@ function broadcastUpdate(key, value, hasChanged, method) {
|
|
|
901
901
|
}
|
|
902
902
|
|
|
903
903
|
/**
|
|
904
|
-
* @private
|
|
905
904
|
* @param {String} key
|
|
906
905
|
* @returns {Boolean}
|
|
907
906
|
*/
|
|
@@ -1409,6 +1408,7 @@ const Onyx = {
|
|
|
1409
1408
|
METHOD,
|
|
1410
1409
|
setMemoryOnlyKeys,
|
|
1411
1410
|
tryGetCachedValue,
|
|
1411
|
+
hasPendingMergeForKey,
|
|
1412
1412
|
};
|
|
1413
1413
|
|
|
1414
1414
|
/**
|
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* This file is here to wrap
|
|
2
|
+
* This file is here to wrap IDBKeyVal with a layer that provides data-changed events like the ones that exist
|
|
3
3
|
* when using LocalStorage APIs in the browser. These events are great because multiple tabs can listen for when
|
|
4
4
|
* data changes and then stay up-to-date with everything happening in Onyx.
|
|
5
5
|
*/
|
|
6
6
|
import _ from 'underscore';
|
|
7
|
-
import Storage from './providers/
|
|
7
|
+
import Storage from './providers/IDBKeyVal';
|
|
8
8
|
|
|
9
9
|
const SYNC_ONYX = 'SYNC_ONYX';
|
|
10
10
|
|
|
@@ -8,7 +8,7 @@ const set = jest.fn((key, value) => {
|
|
|
8
8
|
return Promise.resolve(value);
|
|
9
9
|
});
|
|
10
10
|
|
|
11
|
-
const
|
|
11
|
+
const idbKeyvalMock = {
|
|
12
12
|
setItem(key, value) {
|
|
13
13
|
return set(key, value);
|
|
14
14
|
},
|
|
@@ -57,23 +57,23 @@ const localForageMock = {
|
|
|
57
57
|
config() {},
|
|
58
58
|
};
|
|
59
59
|
|
|
60
|
-
const
|
|
61
|
-
|
|
62
|
-
setItem: jest.fn(
|
|
63
|
-
getItem: jest.fn(
|
|
64
|
-
removeItem: jest.fn(
|
|
65
|
-
removeItems: jest.fn(
|
|
66
|
-
clear: jest.fn(
|
|
67
|
-
getAllKeys: jest.fn(
|
|
68
|
-
config: jest.fn(
|
|
69
|
-
multiGet: jest.fn(
|
|
70
|
-
multiSet: jest.fn(
|
|
71
|
-
multiMerge: jest.fn(
|
|
72
|
-
mergeItem: jest.fn(
|
|
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),
|
|
73
73
|
getStorageMap: jest.fn(() => storageMapInternal),
|
|
74
74
|
setInitialMockData: jest.fn((data) => {
|
|
75
75
|
storageMapInternal = data;
|
|
76
76
|
}),
|
|
77
77
|
};
|
|
78
78
|
|
|
79
|
-
export default
|
|
79
|
+
export default idbKeyvalMockSpy;
|
|
@@ -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;
|
package/lib/withOnyx.js
CHANGED
|
@@ -41,7 +41,18 @@ export default function (mapOnyxToState) {
|
|
|
41
41
|
const key = Str.result(mapping.key, props);
|
|
42
42
|
const value = Onyx.tryGetCachedValue(key, mapping);
|
|
43
43
|
|
|
44
|
-
|
|
44
|
+
/**
|
|
45
|
+
* If we have a pending merge for a key it could mean that data is being set via Onyx.merge() and someone expects a component to have this data immediately.
|
|
46
|
+
*
|
|
47
|
+
* @example
|
|
48
|
+
*
|
|
49
|
+
* Onyx.merge('report_123', value);
|
|
50
|
+
* Navigation.navigate(route); // Where "route" expects the "value" to be available immediately once rendered.
|
|
51
|
+
*
|
|
52
|
+
* In reality, Onyx.merge() will only update the subscriber after all merges have been batched and the previous value is retrieved via a get() (returns a promise).
|
|
53
|
+
* So, we won't use the cache optimization here as it will lead us to arbitrarily defer various actions in the application code.
|
|
54
|
+
*/
|
|
55
|
+
if (value !== undefined && !Onyx.hasPendingMergeForKey(key)) {
|
|
45
56
|
// eslint-disable-next-line no-param-reassign
|
|
46
57
|
resultObj[propertyName] = value;
|
|
47
58
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "react-native-onyx",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.62",
|
|
4
4
|
"author": "Expensify, Inc.",
|
|
5
5
|
"homepage": "https://expensify.com",
|
|
6
6
|
"description": "State management for React Native",
|
|
@@ -59,10 +59,10 @@
|
|
|
59
59
|
"eslint-config-expensify": "^2.0.38",
|
|
60
60
|
"eslint-plugin-jsx-a11y": "^6.6.1",
|
|
61
61
|
"eslint-plugin-react": "^7.31.10",
|
|
62
|
+
"idb-keyval": "^6.2.1",
|
|
62
63
|
"jest": "^26.5.2",
|
|
63
64
|
"jest-cli": "^26.5.2",
|
|
64
65
|
"jsdoc-to-markdown": "^7.1.0",
|
|
65
|
-
"localforage": "^1.10.0",
|
|
66
66
|
"metro-react-native-babel-preset": "^0.72.3",
|
|
67
67
|
"prop-types": "^15.7.2",
|
|
68
68
|
"react": "18.2.0",
|
|
@@ -76,23 +76,19 @@
|
|
|
76
76
|
"webpack-merge": "^5.8.0"
|
|
77
77
|
},
|
|
78
78
|
"peerDependencies": {
|
|
79
|
-
"
|
|
80
|
-
"localforage-removeitems": "^1.4.0",
|
|
79
|
+
"idb-keyval": "^6.2.1",
|
|
81
80
|
"react": ">=18.1.0",
|
|
82
81
|
"react-native-performance": "^4.0.0",
|
|
83
82
|
"react-native-quick-sqlite": "^8.0.0-beta.2"
|
|
84
83
|
},
|
|
85
84
|
"peerDependenciesMeta": {
|
|
86
|
-
"
|
|
87
|
-
"optional": true
|
|
88
|
-
},
|
|
89
|
-
"react-native-quick-sqlite": {
|
|
85
|
+
"idb-keyval": {
|
|
90
86
|
"optional": true
|
|
91
87
|
},
|
|
92
|
-
"
|
|
88
|
+
"react-native-performance": {
|
|
93
89
|
"optional": true
|
|
94
90
|
},
|
|
95
|
-
"
|
|
91
|
+
"react-native-quick-sqlite": {
|
|
96
92
|
"optional": true
|
|
97
93
|
}
|
|
98
94
|
},
|
|
@@ -1,158 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* @file
|
|
3
|
-
* The storage provider based on localforage allows us to store most anything in its
|
|
4
|
-
* natural form in the underlying DB without having to stringify or de-stringify it
|
|
5
|
-
*/
|
|
6
|
-
|
|
7
|
-
import localforage from 'localforage';
|
|
8
|
-
import _ from 'underscore';
|
|
9
|
-
import {extendPrototype} from 'localforage-removeitems';
|
|
10
|
-
import SyncQueue from '../../SyncQueue';
|
|
11
|
-
import * as Str from '../../Str';
|
|
12
|
-
import fastMerge from '../../fastMerge';
|
|
13
|
-
|
|
14
|
-
extendPrototype(localforage);
|
|
15
|
-
|
|
16
|
-
localforage.config({
|
|
17
|
-
name: 'OnyxDB',
|
|
18
|
-
});
|
|
19
|
-
|
|
20
|
-
/**
|
|
21
|
-
* Keys that will not ever be persisted to disk.
|
|
22
|
-
*/
|
|
23
|
-
let memoryOnlyKeys = [];
|
|
24
|
-
|
|
25
|
-
const provider = {
|
|
26
|
-
/**
|
|
27
|
-
* Writing very quickly to IndexedDB causes performance issues and can lock up the page and lead to jank.
|
|
28
|
-
* So, we are slowing this process down by waiting until one write is complete before moving on
|
|
29
|
-
* to the next.
|
|
30
|
-
*/
|
|
31
|
-
setItemQueue: new SyncQueue(({key, value, shouldMerge}) => {
|
|
32
|
-
if (_.find(memoryOnlyKeys, noCacheKey => Str.startsWith(key, noCacheKey))) {
|
|
33
|
-
return Promise.resolve();
|
|
34
|
-
}
|
|
35
|
-
|
|
36
|
-
if (shouldMerge) {
|
|
37
|
-
return localforage.getItem(key)
|
|
38
|
-
.then((existingValue) => {
|
|
39
|
-
const newValue = _.isObject(existingValue)
|
|
40
|
-
|
|
41
|
-
// lodash adds a small overhead so we don't use it here
|
|
42
|
-
// eslint-disable-next-line prefer-object-spread, rulesdir/prefer-underscore-method
|
|
43
|
-
? Object.assign({}, fastMerge(existingValue, value))
|
|
44
|
-
: value;
|
|
45
|
-
return localforage.setItem(key, newValue);
|
|
46
|
-
});
|
|
47
|
-
}
|
|
48
|
-
|
|
49
|
-
return localforage.setItem(key, value);
|
|
50
|
-
}),
|
|
51
|
-
|
|
52
|
-
/**
|
|
53
|
-
* Sets the value for a given key. The only requirement is that the value should be serializable to JSON string
|
|
54
|
-
* @param {String} key
|
|
55
|
-
* @param {*} value
|
|
56
|
-
* @return {Promise<void>}
|
|
57
|
-
*/
|
|
58
|
-
setItem(key, value) {
|
|
59
|
-
return this.setItemQueue.push({key, value});
|
|
60
|
-
},
|
|
61
|
-
|
|
62
|
-
/**
|
|
63
|
-
* Get multiple key-value pairs for the give array of keys in a batch
|
|
64
|
-
* @param {String[]} keys
|
|
65
|
-
* @return {Promise<Array<[key, value]>>}
|
|
66
|
-
*/
|
|
67
|
-
multiGet(keys) {
|
|
68
|
-
const pairs = _.map(
|
|
69
|
-
keys,
|
|
70
|
-
key => localforage.getItem(key)
|
|
71
|
-
.then(value => [key, value]),
|
|
72
|
-
);
|
|
73
|
-
|
|
74
|
-
return Promise.all(pairs);
|
|
75
|
-
},
|
|
76
|
-
|
|
77
|
-
/**
|
|
78
|
-
* Multiple merging of existing and new values in a batch
|
|
79
|
-
* @param {Array<[key, value]>} pairs
|
|
80
|
-
* @return {Promise<void>}
|
|
81
|
-
*/
|
|
82
|
-
multiMerge(pairs) {
|
|
83
|
-
const tasks = _.map(pairs, ([key, value]) => this.setItemQueue.push({key, value, shouldMerge: true}));
|
|
84
|
-
|
|
85
|
-
// We're returning Promise.resolve, otherwise the array of task results will be returned to the caller
|
|
86
|
-
return Promise.all(tasks).then(() => Promise.resolve());
|
|
87
|
-
},
|
|
88
|
-
|
|
89
|
-
/**
|
|
90
|
-
* Merging an existing value with a new one
|
|
91
|
-
* @param {String} key
|
|
92
|
-
* @param {any} _changes - not used, as we rely on the pre-merged data from the `modifiedData`
|
|
93
|
-
* @param {any} modifiedData - the pre-merged data from `Onyx.applyMerge`
|
|
94
|
-
* @return {Promise<void>}
|
|
95
|
-
*/
|
|
96
|
-
mergeItem(key, _changes, modifiedData) {
|
|
97
|
-
return this.setItem(key, modifiedData);
|
|
98
|
-
},
|
|
99
|
-
|
|
100
|
-
/**
|
|
101
|
-
* Stores multiple key-value pairs in a batch
|
|
102
|
-
* @param {Array<[key, value]>} pairs
|
|
103
|
-
* @return {Promise<void>}
|
|
104
|
-
*/
|
|
105
|
-
multiSet(pairs) {
|
|
106
|
-
// We're returning Promise.resolve, otherwise the array of task results will be returned to the caller
|
|
107
|
-
const tasks = _.map(pairs, ([key, value]) => this.setItem(key, value));
|
|
108
|
-
return Promise.all(tasks).then(() => Promise.resolve());
|
|
109
|
-
},
|
|
110
|
-
|
|
111
|
-
/**
|
|
112
|
-
* Clear everything from storage and also stops the SyncQueue from adding anything more to storage
|
|
113
|
-
* @returns {Promise<void>}
|
|
114
|
-
*/
|
|
115
|
-
clear() {
|
|
116
|
-
this.setItemQueue.abort();
|
|
117
|
-
return localforage.clear();
|
|
118
|
-
},
|
|
119
|
-
|
|
120
|
-
/**
|
|
121
|
-
* Returns all keys available in storage
|
|
122
|
-
* @returns {Promise<String[]>}
|
|
123
|
-
*/
|
|
124
|
-
getAllKeys: localforage.keys,
|
|
125
|
-
|
|
126
|
-
/**
|
|
127
|
-
* Get the value of a given key or return `null` if it's not available in storage
|
|
128
|
-
* @param {String} key
|
|
129
|
-
* @return {Promise<*>}
|
|
130
|
-
*/
|
|
131
|
-
getItem: localforage.getItem,
|
|
132
|
-
|
|
133
|
-
/**
|
|
134
|
-
* Remove given key and it's value from storage
|
|
135
|
-
* @param {String} key
|
|
136
|
-
* @returns {Promise<void>}
|
|
137
|
-
*/
|
|
138
|
-
removeItem: localforage.removeItem,
|
|
139
|
-
|
|
140
|
-
/**
|
|
141
|
-
* Remove given keys and their values from storage
|
|
142
|
-
*
|
|
143
|
-
* @param {Array} keys
|
|
144
|
-
* @returns {Promise}
|
|
145
|
-
*/
|
|
146
|
-
removeItems(keys) {
|
|
147
|
-
return localforage.removeItems(keys);
|
|
148
|
-
},
|
|
149
|
-
|
|
150
|
-
/**
|
|
151
|
-
* @param {string[]} keyList
|
|
152
|
-
*/
|
|
153
|
-
setMemoryOnlyKeys(keyList) {
|
|
154
|
-
memoryOnlyKeys = keyList;
|
|
155
|
-
},
|
|
156
|
-
};
|
|
157
|
-
|
|
158
|
-
export default provider;
|