react-native-onyx 2.0.22 → 2.0.23
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/API.md +49 -50
- package/dist/DevTools.js +0 -1
- package/dist/Onyx.d.ts +49 -252
- package/dist/Onyx.js +192 -1155
- package/dist/OnyxCache.d.ts +14 -15
- package/dist/OnyxUtils.d.ts +320 -0
- package/dist/OnyxUtils.js +1061 -0
- package/dist/PerformanceUtils.d.ts +3 -5
- package/dist/index.d.ts +3 -2
- package/dist/storage/InstanceSync/index.d.ts +14 -0
- package/dist/storage/InstanceSync/index.js +20 -0
- package/dist/storage/InstanceSync/index.web.d.ts +27 -0
- package/dist/storage/InstanceSync/index.web.js +59 -0
- package/dist/storage/__mocks__/index.d.ts +15 -12
- package/dist/storage/__mocks__/index.js +43 -78
- package/dist/storage/index.d.ts +6 -2
- package/dist/storage/index.js +170 -2
- package/dist/storage/platforms/index.d.ts +2 -0
- package/dist/storage/{NativeStorage.js → platforms/index.js} +2 -2
- package/dist/storage/platforms/index.native.d.ts +2 -0
- package/dist/storage/{index.native.js → platforms/index.native.js} +2 -2
- package/dist/storage/providers/{IDBKeyVal.js → IDBKeyValProvider.js} +23 -17
- package/dist/storage/providers/MemoryOnlyProvider.d.ts +9 -0
- package/dist/storage/providers/MemoryOnlyProvider.js +124 -0
- package/dist/storage/providers/NoopProvider.js +85 -0
- package/dist/storage/providers/SQLiteProvider.d.ts +3 -0
- package/dist/storage/providers/{SQLiteStorage.js → SQLiteProvider.js} +17 -9
- package/dist/storage/providers/types.d.ts +17 -10
- package/dist/types.d.ts +128 -55
- package/dist/types.js +2 -0
- package/dist/useOnyx.js +11 -10
- package/dist/utils.d.ts +2 -2
- package/dist/utils.js +29 -16
- package/dist/withOnyx.js +6 -5
- package/package.json +1 -1
- package/dist/storage/NativeStorage.d.ts +0 -2
- package/dist/storage/WebStorage.d.ts +0 -3
- package/dist/storage/WebStorage.js +0 -62
- package/dist/storage/index.native.d.ts +0 -2
- /package/dist/storage/providers/{IDBKeyVal.d.ts → IDBKeyValProvider.d.ts} +0 -0
- /package/dist/storage/providers/{SQLiteStorage.d.ts → NoopProvider.d.ts} +0 -0
package/dist/Onyx.js
CHANGED
|
@@ -27,761 +27,39 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
|
27
27
|
};
|
|
28
28
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
29
29
|
/* eslint-disable no-continue */
|
|
30
|
-
const fast_equals_1 = require("fast-equals");
|
|
31
30
|
const underscore_1 = __importDefault(require("underscore"));
|
|
32
31
|
const Logger = __importStar(require("./Logger"));
|
|
33
32
|
const OnyxCache_1 = __importDefault(require("./OnyxCache"));
|
|
34
|
-
const Str = __importStar(require("./Str"));
|
|
35
33
|
const createDeferredTask_1 = __importDefault(require("./createDeferredTask"));
|
|
36
34
|
const PerformanceUtils = __importStar(require("./PerformanceUtils"));
|
|
37
35
|
const storage_1 = __importDefault(require("./storage"));
|
|
38
36
|
const utils_1 = __importDefault(require("./utils"));
|
|
39
|
-
const batch_1 = __importDefault(require("./batch"));
|
|
40
37
|
const DevTools_1 = __importDefault(require("./DevTools"));
|
|
41
|
-
|
|
42
|
-
const METHOD = {
|
|
43
|
-
SET: 'set',
|
|
44
|
-
MERGE: 'merge',
|
|
45
|
-
MERGE_COLLECTION: 'mergecollection',
|
|
46
|
-
MULTI_SET: 'multiset',
|
|
47
|
-
CLEAR: 'clear',
|
|
48
|
-
};
|
|
49
|
-
// Key/value store of Onyx key and arrays of values to merge
|
|
50
|
-
const mergeQueue = {};
|
|
51
|
-
const mergeQueuePromise = {};
|
|
38
|
+
const OnyxUtils_1 = __importDefault(require("./OnyxUtils"));
|
|
52
39
|
// Keeps track of the last connectionID that was used so we can keep incrementing it
|
|
53
40
|
let lastConnectionID = 0;
|
|
54
|
-
// Holds a mapping of all the react components that want their state subscribed to a store key
|
|
55
|
-
const callbackToStateMapping = {};
|
|
56
|
-
// Keeps a copy of the values of the onyx collection keys as a map for faster lookups
|
|
57
|
-
let onyxCollectionKeyMap = new Map();
|
|
58
|
-
// Holds a list of keys that have been directly subscribed to or recently modified from least to most recent
|
|
59
|
-
let recentlyAccessedKeys = [];
|
|
60
|
-
// Holds a list of keys that are safe to remove when we reach max storage. If a key does not match with
|
|
61
|
-
// whatever appears in this list it will NEVER be a candidate for eviction.
|
|
62
|
-
let evictionAllowList = [];
|
|
63
|
-
// Holds a map of keys and connectionID arrays whose keys will never be automatically evicted as
|
|
64
|
-
// long as we have at least one subscriber that returns false for the canEvict property.
|
|
65
|
-
const evictionBlocklist = {};
|
|
66
|
-
// Optional user-provided key value states set when Onyx initializes or clears
|
|
67
|
-
let defaultKeyStates = {};
|
|
68
41
|
// Connections can be made before `Onyx.init`. They would wait for this task before resolving
|
|
69
42
|
const deferredInitTask = (0, createDeferredTask_1.default)();
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
*/
|
|
80
|
-
function sendActionToDevTools(method, key, value, mergedValue = undefined) {
|
|
81
|
-
DevTools_1.default.registerAction(utils_1.default.formatActionName(method, key), value, key ? { [key]: mergedValue || value } : value);
|
|
82
|
-
}
|
|
83
|
-
/**
|
|
84
|
-
* We are batching together onyx updates. This helps with use cases where we schedule onyx updates after each other.
|
|
85
|
-
* This happens for example in the Onyx.update function, where we process API responses that might contain a lot of
|
|
86
|
-
* update operations. Instead of calling the subscribers for each update operation, we batch them together which will
|
|
87
|
-
* cause react to schedule the updates at once instead of after each other. This is mainly a performance optimization.
|
|
88
|
-
* @returns {Promise}
|
|
89
|
-
*/
|
|
90
|
-
function maybeFlushBatchUpdates() {
|
|
91
|
-
if (batchUpdatesPromise) {
|
|
92
|
-
return batchUpdatesPromise;
|
|
93
|
-
}
|
|
94
|
-
batchUpdatesPromise = new Promise((resolve) => {
|
|
95
|
-
/* We use (setTimeout, 0) here which should be called once native module calls are flushed (usually at the end of the frame)
|
|
96
|
-
* We may investigate if (setTimeout, 1) (which in React Native is equal to requestAnimationFrame) works even better
|
|
97
|
-
* then the batch will be flushed on next frame.
|
|
98
|
-
*/
|
|
99
|
-
setTimeout(() => {
|
|
100
|
-
const updatesCopy = batchUpdatesQueue;
|
|
101
|
-
batchUpdatesQueue = [];
|
|
102
|
-
batchUpdatesPromise = null;
|
|
103
|
-
(0, batch_1.default)(() => {
|
|
104
|
-
updatesCopy.forEach((applyUpdates) => {
|
|
105
|
-
applyUpdates();
|
|
106
|
-
});
|
|
107
|
-
});
|
|
108
|
-
resolve();
|
|
109
|
-
}, 0);
|
|
110
|
-
});
|
|
111
|
-
return batchUpdatesPromise;
|
|
112
|
-
}
|
|
113
|
-
function batchUpdates(updates) {
|
|
114
|
-
batchUpdatesQueue.push(updates);
|
|
115
|
-
return maybeFlushBatchUpdates();
|
|
116
|
-
}
|
|
117
|
-
/**
|
|
118
|
-
* Uses a selector function to return a simplified version of sourceData
|
|
119
|
-
* @param {Mixed} sourceData
|
|
120
|
-
* @param {Function} selector Function that takes sourceData and returns a simplified version of it
|
|
121
|
-
* @param {Object} [withOnyxInstanceState]
|
|
122
|
-
* @returns {Mixed}
|
|
123
|
-
*/
|
|
124
|
-
const getSubsetOfData = (sourceData, selector, withOnyxInstanceState) => selector(sourceData, withOnyxInstanceState);
|
|
125
|
-
/**
|
|
126
|
-
* Takes a collection of items (eg. {testKey_1:{a:'a'}, testKey_2:{b:'b'}})
|
|
127
|
-
* and runs it through a reducer function to return a subset of the data according to a selector.
|
|
128
|
-
* The resulting collection will only contain items that are returned by the selector.
|
|
129
|
-
* @param {Object} collection
|
|
130
|
-
* @param {String|Function} selector (see method docs for getSubsetOfData() for full details)
|
|
131
|
-
* @param {Object} [withOnyxInstanceState]
|
|
132
|
-
* @returns {Object}
|
|
133
|
-
*/
|
|
134
|
-
const reduceCollectionWithSelector = (collection, selector, withOnyxInstanceState) => underscore_1.default.reduce(collection, (finalCollection, item, key) => {
|
|
135
|
-
// eslint-disable-next-line no-param-reassign
|
|
136
|
-
finalCollection[key] = getSubsetOfData(item, selector, withOnyxInstanceState);
|
|
137
|
-
return finalCollection;
|
|
138
|
-
}, {});
|
|
139
|
-
/**
|
|
140
|
-
* Get some data from the store
|
|
141
|
-
*
|
|
142
|
-
* @private
|
|
143
|
-
* @param {string} key
|
|
144
|
-
* @returns {Promise<*>}
|
|
145
|
-
*/
|
|
146
|
-
function get(key) {
|
|
147
|
-
// When we already have the value in cache - resolve right away
|
|
148
|
-
if (OnyxCache_1.default.hasCacheForKey(key)) {
|
|
149
|
-
return Promise.resolve(OnyxCache_1.default.getValue(key));
|
|
150
|
-
}
|
|
151
|
-
const taskName = `get:${key}`;
|
|
152
|
-
// When a value retrieving task for this key is still running hook to it
|
|
153
|
-
if (OnyxCache_1.default.hasPendingTask(taskName)) {
|
|
154
|
-
return OnyxCache_1.default.getTaskPromise(taskName);
|
|
155
|
-
}
|
|
156
|
-
// Otherwise retrieve the value from storage and capture a promise to aid concurrent usages
|
|
157
|
-
const promise = storage_1.default.getItem(key)
|
|
158
|
-
.then((val) => {
|
|
159
|
-
OnyxCache_1.default.set(key, val);
|
|
160
|
-
return val;
|
|
161
|
-
})
|
|
162
|
-
.catch((err) => Logger.logInfo(`Unable to get item from persistent storage. Key: ${key} Error: ${err}`));
|
|
163
|
-
return OnyxCache_1.default.captureTask(taskName, promise);
|
|
164
|
-
}
|
|
165
|
-
/**
|
|
166
|
-
* Returns current key names stored in persisted storage
|
|
167
|
-
* @private
|
|
168
|
-
* @returns {Promise<Set<Key>>}
|
|
169
|
-
*/
|
|
170
|
-
function getAllKeys() {
|
|
171
|
-
// When we've already read stored keys, resolve right away
|
|
172
|
-
const storedKeys = OnyxCache_1.default.getAllKeys();
|
|
173
|
-
if (storedKeys.size > 0) {
|
|
174
|
-
return Promise.resolve(storedKeys);
|
|
175
|
-
}
|
|
176
|
-
const taskName = 'getAllKeys';
|
|
177
|
-
// When a value retrieving task for all keys is still running hook to it
|
|
178
|
-
if (OnyxCache_1.default.hasPendingTask(taskName)) {
|
|
179
|
-
return OnyxCache_1.default.getTaskPromise(taskName);
|
|
180
|
-
}
|
|
181
|
-
// Otherwise retrieve the keys from storage and capture a promise to aid concurrent usages
|
|
182
|
-
const promise = storage_1.default.getAllKeys().then((keys) => {
|
|
183
|
-
OnyxCache_1.default.setAllKeys(keys);
|
|
184
|
-
// return the updated set of keys
|
|
185
|
-
return OnyxCache_1.default.getAllKeys();
|
|
186
|
-
});
|
|
187
|
-
return OnyxCache_1.default.captureTask(taskName, promise);
|
|
188
|
-
}
|
|
189
|
-
/**
|
|
190
|
-
* Checks to see if the a subscriber's supplied key
|
|
191
|
-
* is associated with a collection of keys.
|
|
192
|
-
*
|
|
193
|
-
* @param {String} key
|
|
194
|
-
* @returns {Boolean}
|
|
195
|
-
*/
|
|
196
|
-
function isCollectionKey(key) {
|
|
197
|
-
return onyxCollectionKeyMap.has(key);
|
|
198
|
-
}
|
|
199
|
-
/**
|
|
200
|
-
* @param {String} collectionKey
|
|
201
|
-
* @param {String} key
|
|
202
|
-
* @returns {Boolean}
|
|
203
|
-
*/
|
|
204
|
-
function isCollectionMemberKey(collectionKey, key) {
|
|
205
|
-
return Str.startsWith(key, collectionKey) && key.length > collectionKey.length;
|
|
206
|
-
}
|
|
207
|
-
/**
|
|
208
|
-
* Splits a collection member key into the collection key part and the ID part.
|
|
209
|
-
* @param {String} key - The collection member key to split.
|
|
210
|
-
* @returns {Array<String>} A tuple where the first element is the collection part and the second element is the ID part.
|
|
211
|
-
*/
|
|
212
|
-
function splitCollectionMemberKey(key) {
|
|
213
|
-
const underscoreIndex = key.indexOf('_');
|
|
214
|
-
if (underscoreIndex === -1) {
|
|
215
|
-
throw new Error(`Invalid ${key} key provided, only collection keys are allowed.`);
|
|
216
|
-
}
|
|
217
|
-
return [key.substring(0, underscoreIndex + 1), key.substring(underscoreIndex + 1)];
|
|
218
|
-
}
|
|
219
|
-
/**
|
|
220
|
-
* Checks to see if a provided key is the exact configured key of our connected subscriber
|
|
221
|
-
* or if the provided key is a collection member key (in case our configured key is a "collection key")
|
|
222
|
-
*
|
|
223
|
-
* @private
|
|
224
|
-
* @param {String} configKey
|
|
225
|
-
* @param {String} key
|
|
226
|
-
* @return {Boolean}
|
|
227
|
-
*/
|
|
228
|
-
function isKeyMatch(configKey, key) {
|
|
229
|
-
return isCollectionKey(configKey) ? Str.startsWith(key, configKey) : configKey === key;
|
|
230
|
-
}
|
|
231
|
-
/**
|
|
232
|
-
* Checks to see if this key has been flagged as
|
|
233
|
-
* safe for removal.
|
|
234
|
-
*
|
|
235
|
-
* @private
|
|
236
|
-
* @param {String} testKey
|
|
237
|
-
* @returns {Boolean}
|
|
238
|
-
*/
|
|
239
|
-
function isSafeEvictionKey(testKey) {
|
|
240
|
-
return underscore_1.default.some(evictionAllowList, (key) => isKeyMatch(key, testKey));
|
|
241
|
-
}
|
|
242
|
-
/**
|
|
243
|
-
* Tries to get a value from the cache. If the value is not present in cache it will return the default value or undefined.
|
|
244
|
-
* If the requested key is a collection, it will return an object with all the collection members.
|
|
245
|
-
*
|
|
246
|
-
* @param {String} key
|
|
247
|
-
* @param {Object} mapping
|
|
248
|
-
* @returns {Mixed}
|
|
249
|
-
*/
|
|
250
|
-
function tryGetCachedValue(key, mapping = {}) {
|
|
251
|
-
let val = OnyxCache_1.default.getValue(key);
|
|
252
|
-
if (isCollectionKey(key)) {
|
|
253
|
-
const allCacheKeys = OnyxCache_1.default.getAllKeys();
|
|
254
|
-
// It is possible we haven't loaded all keys yet so we do not know if the
|
|
255
|
-
// collection actually exists.
|
|
256
|
-
if (allCacheKeys.size === 0) {
|
|
257
|
-
return;
|
|
258
|
-
}
|
|
259
|
-
const matchingKeys = [];
|
|
260
|
-
allCacheKeys.forEach((k) => {
|
|
261
|
-
if (!k.startsWith(key)) {
|
|
262
|
-
return;
|
|
263
|
-
}
|
|
264
|
-
matchingKeys.push(k);
|
|
265
|
-
});
|
|
266
|
-
const values = underscore_1.default.reduce(matchingKeys, (finalObject, matchedKey) => {
|
|
267
|
-
const cachedValue = OnyxCache_1.default.getValue(matchedKey);
|
|
268
|
-
if (cachedValue) {
|
|
269
|
-
// This is permissible because we're in the process of constructing the final object in a reduce function.
|
|
270
|
-
// eslint-disable-next-line no-param-reassign
|
|
271
|
-
finalObject[matchedKey] = cachedValue;
|
|
272
|
-
}
|
|
273
|
-
return finalObject;
|
|
274
|
-
}, {});
|
|
275
|
-
val = values;
|
|
276
|
-
}
|
|
277
|
-
if (mapping.selector) {
|
|
278
|
-
const state = mapping.withOnyxInstance ? mapping.withOnyxInstance.state : undefined;
|
|
279
|
-
if (isCollectionKey(key)) {
|
|
280
|
-
return reduceCollectionWithSelector(val, mapping.selector, state);
|
|
281
|
-
}
|
|
282
|
-
return getSubsetOfData(val, mapping.selector, state);
|
|
283
|
-
}
|
|
284
|
-
return val;
|
|
285
|
-
}
|
|
286
|
-
/**
|
|
287
|
-
* Remove a key from the recently accessed key list.
|
|
288
|
-
*
|
|
289
|
-
* @private
|
|
290
|
-
* @param {String} key
|
|
291
|
-
*/
|
|
292
|
-
function removeLastAccessedKey(key) {
|
|
293
|
-
recentlyAccessedKeys = underscore_1.default.without(recentlyAccessedKeys, key);
|
|
294
|
-
}
|
|
295
|
-
/**
|
|
296
|
-
* Add a key to the list of recently accessed keys. The least
|
|
297
|
-
* recently accessed key should be at the head and the most
|
|
298
|
-
* recently accessed key at the tail.
|
|
299
|
-
*
|
|
300
|
-
* @private
|
|
301
|
-
* @param {String} key
|
|
302
|
-
*/
|
|
303
|
-
function addLastAccessedKey(key) {
|
|
304
|
-
// Only specific keys belong in this list since we cannot remove an entire collection.
|
|
305
|
-
if (isCollectionKey(key) || !isSafeEvictionKey(key)) {
|
|
306
|
-
return;
|
|
307
|
-
}
|
|
308
|
-
removeLastAccessedKey(key);
|
|
309
|
-
recentlyAccessedKeys.push(key);
|
|
310
|
-
}
|
|
311
|
-
/**
|
|
312
|
-
* Removes a key previously added to this list
|
|
313
|
-
* which will enable it to be deleted again.
|
|
314
|
-
*
|
|
315
|
-
* @private
|
|
316
|
-
* @param {String} key
|
|
317
|
-
* @param {Number} connectionID
|
|
318
|
-
*/
|
|
319
|
-
function removeFromEvictionBlockList(key, connectionID) {
|
|
320
|
-
evictionBlocklist[key] = underscore_1.default.without(evictionBlocklist[key] || [], connectionID);
|
|
321
|
-
// Remove the key if there are no more subscribers
|
|
322
|
-
if (evictionBlocklist[key].length === 0) {
|
|
323
|
-
delete evictionBlocklist[key];
|
|
324
|
-
}
|
|
325
|
-
}
|
|
326
|
-
/**
|
|
327
|
-
* Keys added to this list can never be deleted.
|
|
328
|
-
*
|
|
329
|
-
* @private
|
|
330
|
-
* @param {String} key
|
|
331
|
-
* @param {Number} connectionID
|
|
332
|
-
*/
|
|
333
|
-
function addToEvictionBlockList(key, connectionID) {
|
|
334
|
-
removeFromEvictionBlockList(key, connectionID);
|
|
335
|
-
if (!evictionBlocklist[key]) {
|
|
336
|
-
evictionBlocklist[key] = [];
|
|
337
|
-
}
|
|
338
|
-
evictionBlocklist[key].push(connectionID);
|
|
339
|
-
}
|
|
340
|
-
/**
|
|
341
|
-
* Take all the keys that are safe to evict and add them to
|
|
342
|
-
* the recently accessed list when initializing the app. This
|
|
343
|
-
* enables keys that have not recently been accessed to be
|
|
344
|
-
* removed.
|
|
345
|
-
*
|
|
346
|
-
* @private
|
|
347
|
-
* @returns {Promise}
|
|
348
|
-
*/
|
|
349
|
-
function addAllSafeEvictionKeysToRecentlyAccessedList() {
|
|
350
|
-
return getAllKeys().then((keys) => {
|
|
351
|
-
underscore_1.default.each(evictionAllowList, (safeEvictionKey) => {
|
|
352
|
-
keys.forEach((key) => {
|
|
353
|
-
if (!isKeyMatch(safeEvictionKey, key)) {
|
|
354
|
-
return;
|
|
355
|
-
}
|
|
356
|
-
addLastAccessedKey(key);
|
|
357
|
-
});
|
|
43
|
+
/** Initialize the store with actions and listening for storage events */
|
|
44
|
+
function init({ keys = {}, initialKeyStates = {}, safeEvictionKeys = [], maxCachedKeysCount = 1000, shouldSyncMultipleInstances = Boolean(global.localStorage), debugSetState = false, }) {
|
|
45
|
+
var _a;
|
|
46
|
+
storage_1.default.init();
|
|
47
|
+
if (shouldSyncMultipleInstances) {
|
|
48
|
+
(_a = storage_1.default.keepInstancesSync) === null || _a === void 0 ? void 0 : _a.call(storage_1.default, (key, value) => {
|
|
49
|
+
const prevValue = OnyxCache_1.default.getValue(key, false);
|
|
50
|
+
OnyxCache_1.default.set(key, value);
|
|
51
|
+
OnyxUtils_1.default.keyChanged(key, value, prevValue);
|
|
358
52
|
});
|
|
359
|
-
});
|
|
360
|
-
}
|
|
361
|
-
/**
|
|
362
|
-
* @private
|
|
363
|
-
* @param {String} collectionKey
|
|
364
|
-
* @returns {Object}
|
|
365
|
-
*/
|
|
366
|
-
function getCachedCollection(collectionKey) {
|
|
367
|
-
const collectionMemberKeys = [];
|
|
368
|
-
OnyxCache_1.default.getAllKeys().forEach((storedKey) => {
|
|
369
|
-
if (!isCollectionMemberKey(collectionKey, storedKey)) {
|
|
370
|
-
return;
|
|
371
|
-
}
|
|
372
|
-
collectionMemberKeys.push(storedKey);
|
|
373
|
-
});
|
|
374
|
-
return underscore_1.default.reduce(collectionMemberKeys, (prev, curr) => {
|
|
375
|
-
const cachedValue = OnyxCache_1.default.getValue(curr);
|
|
376
|
-
if (!cachedValue) {
|
|
377
|
-
return prev;
|
|
378
|
-
}
|
|
379
|
-
// eslint-disable-next-line no-param-reassign
|
|
380
|
-
prev[curr] = cachedValue;
|
|
381
|
-
return prev;
|
|
382
|
-
}, {});
|
|
383
|
-
}
|
|
384
|
-
/**
|
|
385
|
-
* When a collection of keys change, search for any callbacks matching the collection key and trigger those callbacks
|
|
386
|
-
*
|
|
387
|
-
* @private
|
|
388
|
-
* @param {String} collectionKey
|
|
389
|
-
* @param {Object} partialCollection - a partial collection of grouped member keys
|
|
390
|
-
* @param {boolean} [notifyRegularSubscibers=true]
|
|
391
|
-
* @param {boolean} [notifyWithOnyxSubscibers=true]
|
|
392
|
-
*/
|
|
393
|
-
function keysChanged(collectionKey, partialCollection, notifyRegularSubscibers = true, notifyWithOnyxSubscibers = true) {
|
|
394
|
-
// We are iterating over all subscribers similar to keyChanged(). However, we are looking for subscribers who are subscribing to either a collection key or
|
|
395
|
-
// individual collection key member for the collection that is being updated. It is important to note that the collection parameter cane be a PARTIAL collection
|
|
396
|
-
// and does not represent all of the combined keys and values for a collection key. It is just the "new" data that was merged in via mergeCollection().
|
|
397
|
-
const stateMappingKeys = underscore_1.default.keys(callbackToStateMapping);
|
|
398
|
-
for (let i = 0; i < stateMappingKeys.length; i++) {
|
|
399
|
-
const subscriber = callbackToStateMapping[stateMappingKeys[i]];
|
|
400
|
-
if (!subscriber) {
|
|
401
|
-
continue;
|
|
402
|
-
}
|
|
403
|
-
// Skip iteration if we do not have a collection key or a collection member key on this subscriber
|
|
404
|
-
if (!Str.startsWith(subscriber.key, collectionKey)) {
|
|
405
|
-
continue;
|
|
406
|
-
}
|
|
407
|
-
/**
|
|
408
|
-
* e.g. Onyx.connect({key: ONYXKEYS.COLLECTION.REPORT, callback: ...});
|
|
409
|
-
*/
|
|
410
|
-
const isSubscribedToCollectionKey = subscriber.key === collectionKey;
|
|
411
|
-
/**
|
|
412
|
-
* e.g. Onyx.connect({key: `${ONYXKEYS.COLLECTION.REPORT}{reportID}`, callback: ...});
|
|
413
|
-
*/
|
|
414
|
-
const isSubscribedToCollectionMemberKey = isCollectionMemberKey(collectionKey, subscriber.key);
|
|
415
|
-
// We prepare the "cached collection" which is the entire collection + the new partial data that
|
|
416
|
-
// was merged in via mergeCollection().
|
|
417
|
-
const cachedCollection = getCachedCollection(collectionKey);
|
|
418
|
-
// Regular Onyx.connect() subscriber found.
|
|
419
|
-
if (underscore_1.default.isFunction(subscriber.callback)) {
|
|
420
|
-
if (!notifyRegularSubscibers) {
|
|
421
|
-
continue;
|
|
422
|
-
}
|
|
423
|
-
// If they are subscribed to the collection key and using waitForCollectionCallback then we'll
|
|
424
|
-
// send the whole cached collection.
|
|
425
|
-
if (isSubscribedToCollectionKey) {
|
|
426
|
-
if (subscriber.waitForCollectionCallback) {
|
|
427
|
-
subscriber.callback(cachedCollection);
|
|
428
|
-
continue;
|
|
429
|
-
}
|
|
430
|
-
// If they are not using waitForCollectionCallback then we notify the subscriber with
|
|
431
|
-
// the new merged data but only for any keys in the partial collection.
|
|
432
|
-
const dataKeys = underscore_1.default.keys(partialCollection);
|
|
433
|
-
for (let j = 0; j < dataKeys.length; j++) {
|
|
434
|
-
const dataKey = dataKeys[j];
|
|
435
|
-
subscriber.callback(cachedCollection[dataKey], dataKey);
|
|
436
|
-
}
|
|
437
|
-
continue;
|
|
438
|
-
}
|
|
439
|
-
// And if the subscriber is specifically only tracking a particular collection member key then we will
|
|
440
|
-
// notify them with the cached data for that key only.
|
|
441
|
-
if (isSubscribedToCollectionMemberKey) {
|
|
442
|
-
subscriber.callback(cachedCollection[subscriber.key], subscriber.key);
|
|
443
|
-
continue;
|
|
444
|
-
}
|
|
445
|
-
continue;
|
|
446
|
-
}
|
|
447
|
-
// React component subscriber found.
|
|
448
|
-
if (subscriber.withOnyxInstance) {
|
|
449
|
-
if (!notifyWithOnyxSubscibers) {
|
|
450
|
-
continue;
|
|
451
|
-
}
|
|
452
|
-
// We are subscribed to a collection key so we must update the data in state with the new
|
|
453
|
-
// collection member key values from the partial update.
|
|
454
|
-
if (isSubscribedToCollectionKey) {
|
|
455
|
-
// If the subscriber has a selector, then the component's state must only be updated with the data
|
|
456
|
-
// returned by the selector.
|
|
457
|
-
if (subscriber.selector) {
|
|
458
|
-
subscriber.withOnyxInstance.setStateProxy((prevState) => {
|
|
459
|
-
const previousData = prevState[subscriber.statePropertyName];
|
|
460
|
-
const newData = reduceCollectionWithSelector(cachedCollection, subscriber.selector, subscriber.withOnyxInstance.state);
|
|
461
|
-
if (!(0, fast_equals_1.deepEqual)(previousData, newData)) {
|
|
462
|
-
return {
|
|
463
|
-
[subscriber.statePropertyName]: newData,
|
|
464
|
-
};
|
|
465
|
-
}
|
|
466
|
-
return null;
|
|
467
|
-
});
|
|
468
|
-
continue;
|
|
469
|
-
}
|
|
470
|
-
subscriber.withOnyxInstance.setStateProxy((prevState) => {
|
|
471
|
-
const finalCollection = underscore_1.default.clone(prevState[subscriber.statePropertyName] || {});
|
|
472
|
-
const dataKeys = underscore_1.default.keys(partialCollection);
|
|
473
|
-
for (let j = 0; j < dataKeys.length; j++) {
|
|
474
|
-
const dataKey = dataKeys[j];
|
|
475
|
-
finalCollection[dataKey] = cachedCollection[dataKey];
|
|
476
|
-
}
|
|
477
|
-
PerformanceUtils.logSetStateCall(subscriber, prevState[subscriber.statePropertyName], finalCollection, 'keysChanged', collectionKey);
|
|
478
|
-
return {
|
|
479
|
-
[subscriber.statePropertyName]: finalCollection,
|
|
480
|
-
};
|
|
481
|
-
});
|
|
482
|
-
continue;
|
|
483
|
-
}
|
|
484
|
-
// If a React component is only interested in a single key then we can set the cached value directly to the state name.
|
|
485
|
-
if (isSubscribedToCollectionMemberKey) {
|
|
486
|
-
// However, we only want to update this subscriber if the partial data contains a change.
|
|
487
|
-
// Otherwise, we would update them with a value they already have and trigger an unnecessary re-render.
|
|
488
|
-
const dataFromCollection = partialCollection[subscriber.key];
|
|
489
|
-
if (underscore_1.default.isUndefined(dataFromCollection)) {
|
|
490
|
-
continue;
|
|
491
|
-
}
|
|
492
|
-
// If the subscriber has a selector, then the component's state must only be updated with the data
|
|
493
|
-
// returned by the selector and the state should only change when the subset of data changes from what
|
|
494
|
-
// it was previously.
|
|
495
|
-
if (subscriber.selector) {
|
|
496
|
-
subscriber.withOnyxInstance.setStateProxy((prevState) => {
|
|
497
|
-
const prevData = prevState[subscriber.statePropertyName];
|
|
498
|
-
const newData = getSubsetOfData(cachedCollection[subscriber.key], subscriber.selector, subscriber.withOnyxInstance.state);
|
|
499
|
-
if (!(0, fast_equals_1.deepEqual)(prevData, newData)) {
|
|
500
|
-
PerformanceUtils.logSetStateCall(subscriber, prevData, newData, 'keysChanged', collectionKey);
|
|
501
|
-
return {
|
|
502
|
-
[subscriber.statePropertyName]: newData,
|
|
503
|
-
};
|
|
504
|
-
}
|
|
505
|
-
return null;
|
|
506
|
-
});
|
|
507
|
-
continue;
|
|
508
|
-
}
|
|
509
|
-
subscriber.withOnyxInstance.setStateProxy((prevState) => {
|
|
510
|
-
const data = cachedCollection[subscriber.key];
|
|
511
|
-
const previousData = prevState[subscriber.statePropertyName];
|
|
512
|
-
// Avoids triggering unnecessary re-renders when feeding empty objects
|
|
513
|
-
if (utils_1.default.isEmptyObject(data) && utils_1.default.isEmptyObject(previousData)) {
|
|
514
|
-
return null;
|
|
515
|
-
}
|
|
516
|
-
if (data === previousData) {
|
|
517
|
-
return null;
|
|
518
|
-
}
|
|
519
|
-
PerformanceUtils.logSetStateCall(subscriber, previousData, data, 'keysChanged', collectionKey);
|
|
520
|
-
return {
|
|
521
|
-
[subscriber.statePropertyName]: data,
|
|
522
|
-
};
|
|
523
|
-
});
|
|
524
|
-
}
|
|
525
|
-
}
|
|
526
|
-
}
|
|
527
|
-
}
|
|
528
|
-
/**
|
|
529
|
-
* When a key change happens, search for any callbacks matching the key or collection key and trigger those callbacks
|
|
530
|
-
*
|
|
531
|
-
* @example
|
|
532
|
-
* keyChanged(key, value, subscriber => subscriber.initWithStoredValues === false)
|
|
533
|
-
*
|
|
534
|
-
* @private
|
|
535
|
-
* @param {String} key
|
|
536
|
-
* @param {*} data
|
|
537
|
-
* @param {*} prevData
|
|
538
|
-
* @param {Function} [canUpdateSubscriber] only subscribers that pass this truth test will be updated
|
|
539
|
-
* @param {boolean} [notifyRegularSubscibers=true]
|
|
540
|
-
* @param {boolean} [notifyWithOnyxSubscibers=true]
|
|
541
|
-
*/
|
|
542
|
-
function keyChanged(key, data, prevData, canUpdateSubscriber = () => true, notifyRegularSubscibers = true, notifyWithOnyxSubscibers = true) {
|
|
543
|
-
// Add or remove this key from the recentlyAccessedKeys lists
|
|
544
|
-
if (!underscore_1.default.isNull(data)) {
|
|
545
|
-
addLastAccessedKey(key);
|
|
546
53
|
}
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
}
|
|
550
|
-
// We are iterating over all subscribers to see if they are interested in the key that has just changed. If the subscriber's key is a collection key then we will
|
|
551
|
-
// notify them if the key that changed is a collection member. Or if it is a regular key notify them when there is an exact match. Depending on whether the subscriber
|
|
552
|
-
// was connected via withOnyx we will call setState() directly on the withOnyx instance. If it is a regular connection we will pass the data to the provided callback.
|
|
553
|
-
const stateMappingKeys = underscore_1.default.keys(callbackToStateMapping);
|
|
554
|
-
for (let i = 0; i < stateMappingKeys.length; i++) {
|
|
555
|
-
const subscriber = callbackToStateMapping[stateMappingKeys[i]];
|
|
556
|
-
if (!subscriber || !isKeyMatch(subscriber.key, key) || !canUpdateSubscriber(subscriber)) {
|
|
557
|
-
continue;
|
|
558
|
-
}
|
|
559
|
-
// Subscriber is a regular call to connect() and provided a callback
|
|
560
|
-
if (underscore_1.default.isFunction(subscriber.callback)) {
|
|
561
|
-
if (!notifyRegularSubscibers) {
|
|
562
|
-
continue;
|
|
563
|
-
}
|
|
564
|
-
if (isCollectionKey(subscriber.key) && subscriber.waitForCollectionCallback) {
|
|
565
|
-
const cachedCollection = getCachedCollection(subscriber.key);
|
|
566
|
-
cachedCollection[key] = data;
|
|
567
|
-
subscriber.callback(cachedCollection);
|
|
568
|
-
continue;
|
|
569
|
-
}
|
|
570
|
-
subscriber.callback(data, key);
|
|
571
|
-
continue;
|
|
572
|
-
}
|
|
573
|
-
// Subscriber connected via withOnyx() HOC
|
|
574
|
-
if (subscriber.withOnyxInstance) {
|
|
575
|
-
if (!notifyWithOnyxSubscibers) {
|
|
576
|
-
continue;
|
|
577
|
-
}
|
|
578
|
-
// Check if we are subscribing to a collection key and overwrite the collection member key value in state
|
|
579
|
-
if (isCollectionKey(subscriber.key)) {
|
|
580
|
-
// If the subscriber has a selector, then the consumer of this data must only be given the data
|
|
581
|
-
// returned by the selector and only when the selected data has changed.
|
|
582
|
-
if (subscriber.selector) {
|
|
583
|
-
subscriber.withOnyxInstance.setStateProxy((prevState) => {
|
|
584
|
-
const prevWithOnyxData = prevState[subscriber.statePropertyName];
|
|
585
|
-
const newWithOnyxData = {
|
|
586
|
-
[key]: getSubsetOfData(data, subscriber.selector, subscriber.withOnyxInstance.state),
|
|
587
|
-
};
|
|
588
|
-
const prevDataWithNewData = Object.assign(Object.assign({}, prevWithOnyxData), newWithOnyxData);
|
|
589
|
-
if (!(0, fast_equals_1.deepEqual)(prevWithOnyxData, prevDataWithNewData)) {
|
|
590
|
-
PerformanceUtils.logSetStateCall(subscriber, prevWithOnyxData, newWithOnyxData, 'keyChanged', key);
|
|
591
|
-
return {
|
|
592
|
-
[subscriber.statePropertyName]: prevDataWithNewData,
|
|
593
|
-
};
|
|
594
|
-
}
|
|
595
|
-
return null;
|
|
596
|
-
});
|
|
597
|
-
continue;
|
|
598
|
-
}
|
|
599
|
-
subscriber.withOnyxInstance.setStateProxy((prevState) => {
|
|
600
|
-
const collection = prevState[subscriber.statePropertyName] || {};
|
|
601
|
-
const newCollection = Object.assign(Object.assign({}, collection), { [key]: data });
|
|
602
|
-
PerformanceUtils.logSetStateCall(subscriber, collection, newCollection, 'keyChanged', key);
|
|
603
|
-
return {
|
|
604
|
-
[subscriber.statePropertyName]: newCollection,
|
|
605
|
-
};
|
|
606
|
-
});
|
|
607
|
-
continue;
|
|
608
|
-
}
|
|
609
|
-
// If the subscriber has a selector, then the component's state must only be updated with the data
|
|
610
|
-
// returned by the selector and only if the selected data has changed.
|
|
611
|
-
if (subscriber.selector) {
|
|
612
|
-
subscriber.withOnyxInstance.setStateProxy(() => {
|
|
613
|
-
const previousValue = getSubsetOfData(prevData, subscriber.selector, subscriber.withOnyxInstance.state);
|
|
614
|
-
const newValue = getSubsetOfData(data, subscriber.selector, subscriber.withOnyxInstance.state);
|
|
615
|
-
if (!(0, fast_equals_1.deepEqual)(previousValue, newValue)) {
|
|
616
|
-
return {
|
|
617
|
-
[subscriber.statePropertyName]: newValue,
|
|
618
|
-
};
|
|
619
|
-
}
|
|
620
|
-
return null;
|
|
621
|
-
});
|
|
622
|
-
continue;
|
|
623
|
-
}
|
|
624
|
-
// If we did not match on a collection key then we just set the new data to the state property
|
|
625
|
-
subscriber.withOnyxInstance.setStateProxy((prevState) => {
|
|
626
|
-
const prevWithOnyxData = prevState[subscriber.statePropertyName];
|
|
627
|
-
// Avoids triggering unnecessary re-renders when feeding empty objects
|
|
628
|
-
if (utils_1.default.isEmptyObject(data) && utils_1.default.isEmptyObject(prevWithOnyxData)) {
|
|
629
|
-
return null;
|
|
630
|
-
}
|
|
631
|
-
if (prevWithOnyxData === data) {
|
|
632
|
-
return null;
|
|
633
|
-
}
|
|
634
|
-
PerformanceUtils.logSetStateCall(subscriber, prevData, data, 'keyChanged', key);
|
|
635
|
-
return {
|
|
636
|
-
[subscriber.statePropertyName]: data,
|
|
637
|
-
};
|
|
638
|
-
});
|
|
639
|
-
continue;
|
|
640
|
-
}
|
|
641
|
-
console.error('Warning: Found a matching subscriber to a key that changed, but no callback or withOnyxInstance could be found.');
|
|
642
|
-
}
|
|
643
|
-
}
|
|
644
|
-
/**
|
|
645
|
-
* Sends the data obtained from the keys to the connection. It either:
|
|
646
|
-
* - sets state on the withOnyxInstances
|
|
647
|
-
* - triggers the callback function
|
|
648
|
-
*
|
|
649
|
-
* @private
|
|
650
|
-
* @param {Object} mapping
|
|
651
|
-
* @param {Object} [mapping.withOnyxInstance]
|
|
652
|
-
* @param {String} [mapping.statePropertyName]
|
|
653
|
-
* @param {Function} [mapping.callback]
|
|
654
|
-
* @param {String} [mapping.selector]
|
|
655
|
-
* @param {*|null} val
|
|
656
|
-
* @param {String|undefined} matchedKey
|
|
657
|
-
* @param {Boolean} isBatched
|
|
658
|
-
*/
|
|
659
|
-
function sendDataToConnection(mapping, val, matchedKey, isBatched) {
|
|
660
|
-
// If the mapping no longer exists then we should not send any data.
|
|
661
|
-
// This means our subscriber disconnected or withOnyx wrapped component unmounted.
|
|
662
|
-
if (!callbackToStateMapping[mapping.connectionID]) {
|
|
663
|
-
return;
|
|
664
|
-
}
|
|
665
|
-
if (mapping.withOnyxInstance) {
|
|
666
|
-
let newData = val;
|
|
667
|
-
// If the mapping has a selector, then the component's state must only be updated with the data
|
|
668
|
-
// returned by the selector.
|
|
669
|
-
if (mapping.selector) {
|
|
670
|
-
if (isCollectionKey(mapping.key)) {
|
|
671
|
-
newData = reduceCollectionWithSelector(val, mapping.selector, mapping.withOnyxInstance.state);
|
|
672
|
-
}
|
|
673
|
-
else {
|
|
674
|
-
newData = getSubsetOfData(val, mapping.selector, mapping.withOnyxInstance.state);
|
|
675
|
-
}
|
|
676
|
-
}
|
|
677
|
-
PerformanceUtils.logSetStateCall(mapping, null, newData, 'sendDataToConnection');
|
|
678
|
-
if (isBatched) {
|
|
679
|
-
batchUpdates(() => {
|
|
680
|
-
mapping.withOnyxInstance.setWithOnyxState(mapping.statePropertyName, newData);
|
|
681
|
-
});
|
|
682
|
-
}
|
|
683
|
-
else {
|
|
684
|
-
mapping.withOnyxInstance.setWithOnyxState(mapping.statePropertyName, newData);
|
|
685
|
-
}
|
|
686
|
-
return;
|
|
687
|
-
}
|
|
688
|
-
if (underscore_1.default.isFunction(mapping.callback)) {
|
|
689
|
-
mapping.callback(val, matchedKey);
|
|
690
|
-
}
|
|
691
|
-
}
|
|
692
|
-
/**
|
|
693
|
-
* We check to see if this key is flagged as safe for eviction and add it to the recentlyAccessedKeys list so that when we
|
|
694
|
-
* run out of storage the least recently accessed key can be removed.
|
|
695
|
-
*
|
|
696
|
-
* @private
|
|
697
|
-
* @param {Object} mapping
|
|
698
|
-
*/
|
|
699
|
-
function addKeyToRecentlyAccessedIfNeeded(mapping) {
|
|
700
|
-
if (!isSafeEvictionKey(mapping.key)) {
|
|
701
|
-
return;
|
|
54
|
+
if (debugSetState) {
|
|
55
|
+
PerformanceUtils.setShouldDebugSetState(true);
|
|
702
56
|
}
|
|
703
|
-
|
|
704
|
-
|
|
705
|
-
if (mapping.withOnyxInstance && !isCollectionKey(mapping.key)) {
|
|
706
|
-
// All React components subscribing to a key flagged as a safe eviction key must implement the canEvict property.
|
|
707
|
-
if (underscore_1.default.isUndefined(mapping.canEvict)) {
|
|
708
|
-
throw new Error(`Cannot subscribe to safe eviction key '${mapping.key}' without providing a canEvict value.`);
|
|
709
|
-
}
|
|
710
|
-
addLastAccessedKey(mapping.key);
|
|
57
|
+
if (maxCachedKeysCount > 0) {
|
|
58
|
+
OnyxCache_1.default.setRecentKeysLimit(maxCachedKeysCount);
|
|
711
59
|
}
|
|
712
|
-
|
|
713
|
-
|
|
714
|
-
|
|
715
|
-
*
|
|
716
|
-
* @private
|
|
717
|
-
* @param {Array} matchingKeys
|
|
718
|
-
* @param {Object} mapping
|
|
719
|
-
*/
|
|
720
|
-
function getCollectionDataAndSendAsObject(matchingKeys, mapping) {
|
|
721
|
-
// Keys that are not in the cache
|
|
722
|
-
const missingKeys = [];
|
|
723
|
-
// Tasks that are pending
|
|
724
|
-
const pendingTasks = [];
|
|
725
|
-
// Keys for the tasks that are pending
|
|
726
|
-
const pendingKeys = [];
|
|
727
|
-
// We are going to combine all the data from the matching keys into a single object
|
|
728
|
-
const data = {};
|
|
729
|
-
/**
|
|
730
|
-
* We are going to iterate over all the matching keys and check if we have the data in the cache.
|
|
731
|
-
* If we do then we add it to the data object. If we do not then we check if there is a pending task
|
|
732
|
-
* for the key. If there is then we add the promise to the pendingTasks array and the key to the pendingKeys
|
|
733
|
-
* array. If there is no pending task then we add the key to the missingKeys array.
|
|
734
|
-
*
|
|
735
|
-
* These missingKeys will be later to use to multiGet the data from the storage.
|
|
736
|
-
*/
|
|
737
|
-
matchingKeys.forEach((key) => {
|
|
738
|
-
const cacheValue = OnyxCache_1.default.getValue(key);
|
|
739
|
-
if (cacheValue) {
|
|
740
|
-
data[key] = cacheValue;
|
|
741
|
-
return;
|
|
742
|
-
}
|
|
743
|
-
const pendingKey = `get:${key}`;
|
|
744
|
-
if (OnyxCache_1.default.hasPendingTask(pendingKey)) {
|
|
745
|
-
pendingTasks.push(OnyxCache_1.default.getTaskPromise(pendingKey));
|
|
746
|
-
pendingKeys.push(key);
|
|
747
|
-
}
|
|
748
|
-
else {
|
|
749
|
-
missingKeys.push(key);
|
|
750
|
-
}
|
|
751
|
-
});
|
|
752
|
-
Promise.all(pendingTasks)
|
|
753
|
-
// We are going to wait for all the pending tasks to resolve and then add the data to the data object.
|
|
754
|
-
.then((values) => {
|
|
755
|
-
values.forEach((value, index) => {
|
|
756
|
-
data[pendingKeys[index]] = value;
|
|
757
|
-
});
|
|
758
|
-
return Promise.resolve();
|
|
759
|
-
})
|
|
760
|
-
// We are going to get the missing keys using multiGet from the storage.
|
|
761
|
-
.then(() => {
|
|
762
|
-
if (missingKeys.length === 0) {
|
|
763
|
-
return Promise.resolve();
|
|
764
|
-
}
|
|
765
|
-
return storage_1.default.multiGet(missingKeys);
|
|
766
|
-
})
|
|
767
|
-
// We are going to add the data from the missing keys to the data object and also merge it to the cache.
|
|
768
|
-
.then((values) => {
|
|
769
|
-
if (!values || values.length === 0) {
|
|
770
|
-
return Promise.resolve();
|
|
771
|
-
}
|
|
772
|
-
// temp object is used to merge the missing data into the cache
|
|
773
|
-
const temp = {};
|
|
774
|
-
values.forEach((value) => {
|
|
775
|
-
data[value[0]] = value[1];
|
|
776
|
-
temp[value[0]] = value[1];
|
|
777
|
-
});
|
|
778
|
-
OnyxCache_1.default.merge(temp);
|
|
779
|
-
return Promise.resolve();
|
|
780
|
-
})
|
|
781
|
-
// We are going to send the data to the subscriber.
|
|
782
|
-
.finally(() => {
|
|
783
|
-
sendDataToConnection(mapping, data, undefined, true);
|
|
784
|
-
});
|
|
60
|
+
OnyxUtils_1.default.initStoreValues(keys, initialKeyStates, safeEvictionKeys);
|
|
61
|
+
// Initialize all of our keys with data provided then give green light to any pending connections
|
|
62
|
+
Promise.all([OnyxUtils_1.default.addAllSafeEvictionKeysToRecentlyAccessedList(), OnyxUtils_1.default.initializeWithDefaultKeyStates()]).then(deferredInitTask.resolve);
|
|
785
63
|
}
|
|
786
64
|
/**
|
|
787
65
|
* Subscribes a react component's state directly to a store key
|
|
@@ -792,27 +70,28 @@ function getCollectionDataAndSendAsObject(matchingKeys, mapping) {
|
|
|
792
70
|
* callback: onSessionChange,
|
|
793
71
|
* });
|
|
794
72
|
*
|
|
795
|
-
* @param
|
|
796
|
-
* @param
|
|
797
|
-
* @param
|
|
798
|
-
* @param
|
|
73
|
+
* @param mapping the mapping information to connect Onyx to the components state
|
|
74
|
+
* @param mapping.key ONYXKEY to subscribe to
|
|
75
|
+
* @param [mapping.statePropertyName] the name of the property in the state to connect the data to
|
|
76
|
+
* @param [mapping.withOnyxInstance] whose setState() method will be called with any changed data
|
|
799
77
|
* This is used by React components to connect to Onyx
|
|
800
|
-
* @param
|
|
78
|
+
* @param [mapping.callback] a method that will be called with changed data
|
|
801
79
|
* This is used by any non-React code to connect to Onyx
|
|
802
|
-
* @param
|
|
80
|
+
* @param [mapping.initWithStoredValues] If set to false, then no data will be prefilled into the
|
|
803
81
|
* component
|
|
804
|
-
* @param
|
|
805
|
-
* @param
|
|
82
|
+
* @param [mapping.waitForCollectionCallback] If set to true, it will return the entire collection to the callback as a single object
|
|
83
|
+
* @param [mapping.selector] THIS PARAM IS ONLY USED WITH withOnyx(). If included, this will be used to subscribe to a subset of an Onyx key's data.
|
|
806
84
|
* The sourceData and withOnyx state are passed to the selector and should return the simplified data. Using this setting on `withOnyx` can have very positive
|
|
807
85
|
* performance benefits because the component will only re-render when the subset of data changes. Otherwise, any change of data on any property would normally
|
|
808
86
|
* cause the component to re-render (and that can be expensive from a performance standpoint).
|
|
809
|
-
* @param
|
|
87
|
+
* @param [mapping.initialValue] THIS PARAM IS ONLY USED WITH withOnyx().
|
|
810
88
|
* If included, this will be passed to the component so that something can be rendered while data is being fetched from the DB.
|
|
811
|
-
* Note that it will not cause the component to have the loading prop set to true.
|
|
812
|
-
* @returns
|
|
89
|
+
* Note that it will not cause the component to have the loading prop set to true.
|
|
90
|
+
* @returns an ID to use when calling disconnect
|
|
813
91
|
*/
|
|
814
92
|
function connect(mapping) {
|
|
815
93
|
const connectionID = lastConnectionID++;
|
|
94
|
+
const callbackToStateMapping = OnyxUtils_1.default.getCallbackToStateMapping();
|
|
816
95
|
callbackToStateMapping[connectionID] = mapping;
|
|
817
96
|
callbackToStateMapping[connectionID].connectionID = connectionID;
|
|
818
97
|
if (mapping.initWithStoredValues === false) {
|
|
@@ -820,68 +99,63 @@ function connect(mapping) {
|
|
|
820
99
|
}
|
|
821
100
|
// Commit connection only after init passes
|
|
822
101
|
deferredInitTask.promise
|
|
823
|
-
.then(() => addKeyToRecentlyAccessedIfNeeded(mapping))
|
|
102
|
+
.then(() => OnyxUtils_1.default.addKeyToRecentlyAccessedIfNeeded(mapping))
|
|
824
103
|
.then(() => {
|
|
825
104
|
// Performance improvement
|
|
826
105
|
// If the mapping is connected to an onyx key that is not a collection
|
|
827
106
|
// we can skip the call to getAllKeys() and return an array with a single item
|
|
828
107
|
if (Boolean(mapping.key) && typeof mapping.key === 'string' && !mapping.key.endsWith('_') && OnyxCache_1.default.storageKeys.has(mapping.key)) {
|
|
829
|
-
return [mapping.key];
|
|
108
|
+
return new Set([mapping.key]);
|
|
830
109
|
}
|
|
831
|
-
return getAllKeys();
|
|
110
|
+
return OnyxUtils_1.default.getAllKeys();
|
|
832
111
|
})
|
|
833
112
|
.then((keys) => {
|
|
834
113
|
// We search all the keys in storage to see if any are a "match" for the subscriber we are connecting so that we
|
|
835
114
|
// can send data back to the subscriber. Note that multiple keys can match as a subscriber could either be
|
|
836
115
|
// subscribed to a "collection key" or a single key.
|
|
837
|
-
const matchingKeys =
|
|
838
|
-
keys.forEach((key) => {
|
|
839
|
-
if (!isKeyMatch(mapping.key, key)) {
|
|
840
|
-
return;
|
|
841
|
-
}
|
|
842
|
-
matchingKeys.push(key);
|
|
843
|
-
});
|
|
116
|
+
const matchingKeys = Array.from(keys).filter((key) => OnyxUtils_1.default.isKeyMatch(mapping.key, key));
|
|
844
117
|
// If the key being connected to does not exist we initialize the value with null. For subscribers that connected
|
|
845
118
|
// directly via connect() they will simply get a null value sent to them without any information about which key matched
|
|
846
119
|
// since there are none matched. In withOnyx() we wait for all connected keys to return a value before rendering the child
|
|
847
120
|
// component. This null value will be filtered out so that the connected component can utilize defaultProps.
|
|
848
121
|
if (matchingKeys.length === 0) {
|
|
849
|
-
if (mapping.key && !isCollectionKey(mapping.key)) {
|
|
122
|
+
if (mapping.key && !OnyxUtils_1.default.isCollectionKey(mapping.key)) {
|
|
850
123
|
OnyxCache_1.default.set(mapping.key, null);
|
|
851
124
|
}
|
|
852
125
|
// Here we cannot use batching because the null value is expected to be set immediately for default props
|
|
853
126
|
// or they will be undefined.
|
|
854
|
-
sendDataToConnection(mapping, null, undefined, false);
|
|
127
|
+
OnyxUtils_1.default.sendDataToConnection(mapping, null, undefined, false);
|
|
855
128
|
return;
|
|
856
129
|
}
|
|
857
130
|
// When using a callback subscriber we will either trigger the provided callback for each key we find or combine all values
|
|
858
131
|
// into an object and just make a single call. The latter behavior is enabled by providing a waitForCollectionCallback key
|
|
859
132
|
// combined with a subscription to a collection key.
|
|
860
|
-
if (
|
|
861
|
-
if (isCollectionKey(mapping.key)) {
|
|
133
|
+
if (typeof mapping.callback === 'function') {
|
|
134
|
+
if (OnyxUtils_1.default.isCollectionKey(mapping.key)) {
|
|
862
135
|
if (mapping.waitForCollectionCallback) {
|
|
863
|
-
getCollectionDataAndSendAsObject(matchingKeys, mapping);
|
|
136
|
+
OnyxUtils_1.default.getCollectionDataAndSendAsObject(matchingKeys, mapping);
|
|
864
137
|
return;
|
|
865
138
|
}
|
|
866
139
|
// We did not opt into using waitForCollectionCallback mode so the callback is called for every matching key.
|
|
140
|
+
// eslint-disable-next-line @typescript-eslint/prefer-for-of
|
|
867
141
|
for (let i = 0; i < matchingKeys.length; i++) {
|
|
868
|
-
get(matchingKeys[i]).then((val) => sendDataToConnection(mapping, val, matchingKeys[i], true));
|
|
142
|
+
OnyxUtils_1.default.get(matchingKeys[i]).then((val) => OnyxUtils_1.default.sendDataToConnection(mapping, val, matchingKeys[i], true));
|
|
869
143
|
}
|
|
870
144
|
return;
|
|
871
145
|
}
|
|
872
146
|
// If we are not subscribed to a collection key then there's only a single key to send an update for.
|
|
873
|
-
get(mapping.key).then((val) => sendDataToConnection(mapping, val, mapping.key, true));
|
|
147
|
+
OnyxUtils_1.default.get(mapping.key).then((val) => OnyxUtils_1.default.sendDataToConnection(mapping, val, mapping.key, true));
|
|
874
148
|
return;
|
|
875
149
|
}
|
|
876
150
|
// If we have a withOnyxInstance that means a React component has subscribed via the withOnyx() HOC and we need to
|
|
877
151
|
// group collection key member data into an object.
|
|
878
152
|
if (mapping.withOnyxInstance) {
|
|
879
|
-
if (isCollectionKey(mapping.key)) {
|
|
880
|
-
getCollectionDataAndSendAsObject(matchingKeys, mapping);
|
|
153
|
+
if (OnyxUtils_1.default.isCollectionKey(mapping.key)) {
|
|
154
|
+
OnyxUtils_1.default.getCollectionDataAndSendAsObject(matchingKeys, mapping);
|
|
881
155
|
return;
|
|
882
156
|
}
|
|
883
157
|
// If the subscriber is not using a collection key then we just send a single value back to the subscriber
|
|
884
|
-
get(mapping.key).then((val) => sendDataToConnection(mapping, val, mapping.key, true));
|
|
158
|
+
OnyxUtils_1.default.get(mapping.key).then((val) => OnyxUtils_1.default.sendDataToConnection(mapping, val, mapping.key, true));
|
|
885
159
|
return;
|
|
886
160
|
}
|
|
887
161
|
console.error('Warning: Onyx.connect() was found without a callback or withOnyxInstance');
|
|
@@ -895,247 +169,70 @@ function connect(mapping) {
|
|
|
895
169
|
* @example
|
|
896
170
|
* Onyx.disconnect(connectionID);
|
|
897
171
|
*
|
|
898
|
-
* @param
|
|
899
|
-
* @param {String} [keyToRemoveFromEvictionBlocklist]
|
|
172
|
+
* @param connectionID unique id returned by call to Onyx.connect()
|
|
900
173
|
*/
|
|
901
174
|
function disconnect(connectionID, keyToRemoveFromEvictionBlocklist) {
|
|
175
|
+
const callbackToStateMapping = OnyxUtils_1.default.getCallbackToStateMapping();
|
|
902
176
|
if (!callbackToStateMapping[connectionID]) {
|
|
903
177
|
return;
|
|
904
178
|
}
|
|
905
179
|
// Remove this key from the eviction block list as we are no longer
|
|
906
180
|
// subscribing to it and it should be safe to delete again
|
|
907
181
|
if (keyToRemoveFromEvictionBlocklist) {
|
|
908
|
-
removeFromEvictionBlockList(keyToRemoveFromEvictionBlocklist, connectionID);
|
|
182
|
+
OnyxUtils_1.default.removeFromEvictionBlockList(keyToRemoveFromEvictionBlocklist, connectionID);
|
|
909
183
|
}
|
|
910
184
|
delete callbackToStateMapping[connectionID];
|
|
911
185
|
}
|
|
912
|
-
/**
|
|
913
|
-
* Schedules an update that will be appended to the macro task queue (so it doesn't update the subscribers immediately).
|
|
914
|
-
*
|
|
915
|
-
* @example
|
|
916
|
-
* scheduleSubscriberUpdate(key, value, subscriber => subscriber.initWithStoredValues === false)
|
|
917
|
-
*
|
|
918
|
-
* @param {String} key
|
|
919
|
-
* @param {*} value
|
|
920
|
-
* @param {*} prevValue
|
|
921
|
-
* @param {Function} [canUpdateSubscriber] only subscribers that pass this truth test will be updated
|
|
922
|
-
* @returns {Promise}
|
|
923
|
-
*/
|
|
924
|
-
function scheduleSubscriberUpdate(key, value, prevValue, canUpdateSubscriber = () => true) {
|
|
925
|
-
const promise = Promise.resolve().then(() => keyChanged(key, value, prevValue, canUpdateSubscriber, true, false));
|
|
926
|
-
batchUpdates(() => keyChanged(key, value, prevValue, canUpdateSubscriber, false, true));
|
|
927
|
-
return Promise.all([maybeFlushBatchUpdates(), promise]);
|
|
928
|
-
}
|
|
929
|
-
/**
|
|
930
|
-
* This method is similar to notifySubscribersOnNextTick but it is built for working specifically with collections
|
|
931
|
-
* so that keysChanged() is triggered for the collection and not keyChanged(). If this was not done, then the
|
|
932
|
-
* subscriber callbacks receive the data in a different format than they normally expect and it breaks code.
|
|
933
|
-
*
|
|
934
|
-
* @param {String} key
|
|
935
|
-
* @param {*} value
|
|
936
|
-
* @returns {Promise}
|
|
937
|
-
*/
|
|
938
|
-
function scheduleNotifyCollectionSubscribers(key, value) {
|
|
939
|
-
const promise = Promise.resolve().then(() => keysChanged(key, value, true, false));
|
|
940
|
-
batchUpdates(() => keysChanged(key, value, false, true));
|
|
941
|
-
return Promise.all([maybeFlushBatchUpdates(), promise]);
|
|
942
|
-
}
|
|
943
|
-
/**
|
|
944
|
-
* Remove a key from Onyx and update the subscribers
|
|
945
|
-
*
|
|
946
|
-
* @private
|
|
947
|
-
* @param {String} key
|
|
948
|
-
* @return {Promise}
|
|
949
|
-
*/
|
|
950
|
-
function remove(key) {
|
|
951
|
-
const prevValue = OnyxCache_1.default.getValue(key, false);
|
|
952
|
-
OnyxCache_1.default.drop(key);
|
|
953
|
-
scheduleSubscriberUpdate(key, null, prevValue);
|
|
954
|
-
return storage_1.default.removeItem(key);
|
|
955
|
-
}
|
|
956
|
-
/**
|
|
957
|
-
* @private
|
|
958
|
-
* @returns {Promise<void>}
|
|
959
|
-
*/
|
|
960
|
-
function reportStorageQuota() {
|
|
961
|
-
return storage_1.default.getDatabaseSize()
|
|
962
|
-
.then(({ bytesUsed, bytesRemaining }) => {
|
|
963
|
-
Logger.logInfo(`Storage Quota Check -- bytesUsed: ${bytesUsed} bytesRemaining: ${bytesRemaining}`);
|
|
964
|
-
})
|
|
965
|
-
.catch((dbSizeError) => {
|
|
966
|
-
Logger.logAlert(`Unable to get database size. Error: ${dbSizeError}`);
|
|
967
|
-
});
|
|
968
|
-
}
|
|
969
|
-
/**
|
|
970
|
-
* If we fail to set or merge we must handle this by
|
|
971
|
-
* evicting some data from Onyx and then retrying to do
|
|
972
|
-
* whatever it is we attempted to do.
|
|
973
|
-
*
|
|
974
|
-
* @private
|
|
975
|
-
* @param {Error} error
|
|
976
|
-
* @param {Function} onyxMethod
|
|
977
|
-
* @param {...any} args
|
|
978
|
-
* @return {Promise}
|
|
979
|
-
*/
|
|
980
|
-
function evictStorageAndRetry(error, onyxMethod, ...args) {
|
|
981
|
-
Logger.logInfo(`Failed to save to storage. Error: ${error}. onyxMethod: ${onyxMethod.name}`);
|
|
982
|
-
if (error && Str.startsWith(error.message, "Failed to execute 'put' on 'IDBObjectStore'")) {
|
|
983
|
-
Logger.logAlert('Attempted to set invalid data set in Onyx. Please ensure all data is serializable.');
|
|
984
|
-
throw error;
|
|
985
|
-
}
|
|
986
|
-
// Find the first key that we can remove that has no subscribers in our blocklist
|
|
987
|
-
const keyForRemoval = underscore_1.default.find(recentlyAccessedKeys, (key) => !evictionBlocklist[key]);
|
|
988
|
-
if (!keyForRemoval) {
|
|
989
|
-
// If we have no acceptable keys to remove then we are possibly trying to save mission critical data. If this is the case,
|
|
990
|
-
// then we should stop retrying as there is not much the user can do to fix this. Instead of getting them stuck in an infinite loop we
|
|
991
|
-
// will allow this write to be skipped.
|
|
992
|
-
Logger.logAlert('Out of storage. But found no acceptable keys to remove.');
|
|
993
|
-
return reportStorageQuota();
|
|
994
|
-
}
|
|
995
|
-
// Remove the least recently viewed key that is not currently being accessed and retry.
|
|
996
|
-
Logger.logInfo(`Out of storage. Evicting least recently accessed key (${keyForRemoval}) and retrying.`);
|
|
997
|
-
reportStorageQuota();
|
|
998
|
-
return remove(keyForRemoval).then(() => onyxMethod(...args));
|
|
999
|
-
}
|
|
1000
|
-
/**
|
|
1001
|
-
* Notifys subscribers and writes current value to cache
|
|
1002
|
-
*
|
|
1003
|
-
* @param {String} key
|
|
1004
|
-
* @param {*} value
|
|
1005
|
-
* @param {String} method
|
|
1006
|
-
* @param {Boolean} hasChanged
|
|
1007
|
-
* @param {Boolean} wasRemoved
|
|
1008
|
-
* @returns {Promise}
|
|
1009
|
-
*/
|
|
1010
|
-
function broadcastUpdate(key, value, method, hasChanged, wasRemoved = false) {
|
|
1011
|
-
// Logging properties only since values could be sensitive things we don't want to log
|
|
1012
|
-
Logger.logInfo(`${method}() called for key: ${key}${underscore_1.default.isObject(value) ? ` properties: ${underscore_1.default.keys(value).join(',')}` : ''}`);
|
|
1013
|
-
const prevValue = OnyxCache_1.default.getValue(key, false);
|
|
1014
|
-
// Update subscribers if the cached value has changed, or when the subscriber specifically requires
|
|
1015
|
-
// all updates regardless of value changes (indicated by initWithStoredValues set to false).
|
|
1016
|
-
if (hasChanged && !wasRemoved) {
|
|
1017
|
-
OnyxCache_1.default.set(key, value);
|
|
1018
|
-
}
|
|
1019
|
-
else {
|
|
1020
|
-
OnyxCache_1.default.addToAccessedKeys(key);
|
|
1021
|
-
}
|
|
1022
|
-
return scheduleSubscriberUpdate(key, value, prevValue, (subscriber) => hasChanged || subscriber.initWithStoredValues === false);
|
|
1023
|
-
}
|
|
1024
|
-
/**
|
|
1025
|
-
* @param {String} key
|
|
1026
|
-
* @returns {Boolean}
|
|
1027
|
-
*/
|
|
1028
|
-
function hasPendingMergeForKey(key) {
|
|
1029
|
-
return Boolean(mergeQueue[key]);
|
|
1030
|
-
}
|
|
1031
|
-
/**
|
|
1032
|
-
* Removes a key from storage if the value is null.
|
|
1033
|
-
* Otherwise removes all nested null values in objects and returns the object
|
|
1034
|
-
* @param {String} key
|
|
1035
|
-
* @param {Mixed} value
|
|
1036
|
-
* @returns {Mixed} The value without null values and a boolean "wasRemoved", which indicates if the key got removed completely
|
|
1037
|
-
*/
|
|
1038
|
-
function removeNullValues(key, value) {
|
|
1039
|
-
if (underscore_1.default.isNull(value)) {
|
|
1040
|
-
remove(key);
|
|
1041
|
-
return { value, wasRemoved: true };
|
|
1042
|
-
}
|
|
1043
|
-
// We can remove all null values in an object by merging it with itself
|
|
1044
|
-
// utils.fastMerge recursively goes through the object and removes all null values
|
|
1045
|
-
// Passing two identical objects as source and target to fastMerge will not change it, but only remove the null values
|
|
1046
|
-
return { value: utils_1.default.removeNestedNullValues(value), wasRemoved: false };
|
|
1047
|
-
}
|
|
1048
186
|
/**
|
|
1049
187
|
* Write a value to our store with the given key
|
|
1050
188
|
*
|
|
1051
|
-
* @param
|
|
1052
|
-
* @param
|
|
1053
|
-
*
|
|
1054
|
-
* @returns {Promise}
|
|
189
|
+
* @param key ONYXKEY to set
|
|
190
|
+
* @param value value to store
|
|
1055
191
|
*/
|
|
1056
192
|
function set(key, value) {
|
|
1057
193
|
// If the value is null, we remove the key from storage
|
|
1058
|
-
const { value: valueAfterRemoving, wasRemoved } = removeNullValues(key, value);
|
|
1059
|
-
if (hasPendingMergeForKey(key)) {
|
|
1060
|
-
delete
|
|
194
|
+
const { value: valueAfterRemoving, wasRemoved } = OnyxUtils_1.default.removeNullValues(key, value);
|
|
195
|
+
if (OnyxUtils_1.default.hasPendingMergeForKey(key)) {
|
|
196
|
+
delete OnyxUtils_1.default.getMergeQueue()[key];
|
|
1061
197
|
}
|
|
1062
198
|
const hasChanged = OnyxCache_1.default.hasValueChanged(key, valueAfterRemoving);
|
|
199
|
+
// Logging properties only since values could be sensitive things we don't want to log
|
|
200
|
+
Logger.logInfo(`set called for key: ${key}${underscore_1.default.isObject(value) ? ` properties: ${underscore_1.default.keys(value).join(',')}` : ''} hasChanged: ${hasChanged}`);
|
|
1063
201
|
// This approach prioritizes fast UI changes without waiting for data to be stored in device storage.
|
|
1064
|
-
const updatePromise = broadcastUpdate(key, valueAfterRemoving,
|
|
202
|
+
const updatePromise = OnyxUtils_1.default.broadcastUpdate(key, valueAfterRemoving, hasChanged, wasRemoved);
|
|
1065
203
|
// If the value has not changed or the key got removed, calling Storage.setItem() would be redundant and a waste of performance, so return early instead.
|
|
1066
204
|
if (!hasChanged || wasRemoved) {
|
|
1067
205
|
return updatePromise;
|
|
1068
206
|
}
|
|
1069
207
|
return storage_1.default.setItem(key, valueAfterRemoving)
|
|
1070
|
-
.catch((error) => evictStorageAndRetry(error, set, key, valueAfterRemoving))
|
|
208
|
+
.catch((error) => OnyxUtils_1.default.evictStorageAndRetry(error, set, key, valueAfterRemoving))
|
|
1071
209
|
.then(() => {
|
|
1072
|
-
sendActionToDevTools(METHOD.SET, key, valueAfterRemoving);
|
|
210
|
+
OnyxUtils_1.default.sendActionToDevTools(OnyxUtils_1.default.METHOD.SET, key, valueAfterRemoving);
|
|
1073
211
|
return updatePromise;
|
|
1074
212
|
});
|
|
1075
213
|
}
|
|
1076
|
-
/**
|
|
1077
|
-
* Storage expects array like: [["@MyApp_user", value_1], ["@MyApp_key", value_2]]
|
|
1078
|
-
* This method transforms an object like {'@MyApp_user': myUserValue, '@MyApp_key': myKeyValue}
|
|
1079
|
-
* to an array of key-value pairs in the above format and removes key-value pairs that are being set to null
|
|
1080
|
-
* @private
|
|
1081
|
-
* @param {Record} data
|
|
1082
|
-
* @return {Array} an array of key - value pairs <[key, value]>
|
|
1083
|
-
*/
|
|
1084
|
-
function prepareKeyValuePairsForStorage(data) {
|
|
1085
|
-
const keyValuePairs = [];
|
|
1086
|
-
underscore_1.default.forEach(data, (value, key) => {
|
|
1087
|
-
const { value: valueAfterRemoving, wasRemoved } = removeNullValues(key, value);
|
|
1088
|
-
if (wasRemoved)
|
|
1089
|
-
return;
|
|
1090
|
-
keyValuePairs.push([key, valueAfterRemoving]);
|
|
1091
|
-
});
|
|
1092
|
-
return keyValuePairs;
|
|
1093
|
-
}
|
|
1094
214
|
/**
|
|
1095
215
|
* Sets multiple keys and values
|
|
1096
216
|
*
|
|
1097
217
|
* @example Onyx.multiSet({'key1': 'a', 'key2': 'b'});
|
|
1098
218
|
*
|
|
1099
|
-
* @param
|
|
1100
|
-
* @returns {Promise}
|
|
219
|
+
* @param data object keyed by ONYXKEYS and the values to set
|
|
1101
220
|
*/
|
|
1102
221
|
function multiSet(data) {
|
|
1103
|
-
const keyValuePairs = prepareKeyValuePairsForStorage(data);
|
|
1104
|
-
const updatePromises =
|
|
222
|
+
const keyValuePairs = OnyxUtils_1.default.prepareKeyValuePairsForStorage(data);
|
|
223
|
+
const updatePromises = keyValuePairs.map(([key, value]) => {
|
|
1105
224
|
const prevValue = OnyxCache_1.default.getValue(key, false);
|
|
1106
225
|
// Update cache and optimistically inform subscribers on the next tick
|
|
1107
226
|
OnyxCache_1.default.set(key, value);
|
|
1108
|
-
return scheduleSubscriberUpdate(key, value, prevValue);
|
|
227
|
+
return OnyxUtils_1.default.scheduleSubscriberUpdate(key, value, prevValue);
|
|
1109
228
|
});
|
|
1110
229
|
return storage_1.default.multiSet(keyValuePairs)
|
|
1111
|
-
.catch((error) => evictStorageAndRetry(error, multiSet, data))
|
|
230
|
+
.catch((error) => OnyxUtils_1.default.evictStorageAndRetry(error, multiSet, data))
|
|
1112
231
|
.then(() => {
|
|
1113
|
-
sendActionToDevTools(METHOD.MULTI_SET, undefined, data);
|
|
232
|
+
OnyxUtils_1.default.sendActionToDevTools(OnyxUtils_1.default.METHOD.MULTI_SET, undefined, data);
|
|
1114
233
|
return Promise.all(updatePromises);
|
|
1115
234
|
});
|
|
1116
235
|
}
|
|
1117
|
-
/**
|
|
1118
|
-
* Merges an array of changes with an existing value
|
|
1119
|
-
*
|
|
1120
|
-
* @private
|
|
1121
|
-
* @param {*} existingValue
|
|
1122
|
-
* @param {Array<*>} changes Array of changes that should be applied to the existing value
|
|
1123
|
-
* @param {Boolean} shouldRemoveNullObjectValues
|
|
1124
|
-
* @returns {*}
|
|
1125
|
-
*/
|
|
1126
|
-
function applyMerge(existingValue, changes, shouldRemoveNullObjectValues) {
|
|
1127
|
-
const lastChange = underscore_1.default.last(changes);
|
|
1128
|
-
if (underscore_1.default.isArray(lastChange)) {
|
|
1129
|
-
return lastChange;
|
|
1130
|
-
}
|
|
1131
|
-
if (underscore_1.default.some(changes, underscore_1.default.isObject)) {
|
|
1132
|
-
// Object values are then merged one after the other
|
|
1133
|
-
return underscore_1.default.reduce(changes, (modifiedData, change) => utils_1.default.fastMerge(modifiedData, change, shouldRemoveNullObjectValues), existingValue || {});
|
|
1134
|
-
}
|
|
1135
|
-
// If we have anything else we can't merge it so we'll
|
|
1136
|
-
// simply return the last value that was queued
|
|
1137
|
-
return lastChange;
|
|
1138
|
-
}
|
|
1139
236
|
/**
|
|
1140
237
|
* Merge a new value into an existing value at a key.
|
|
1141
238
|
*
|
|
@@ -1151,15 +248,13 @@ function applyMerge(existingValue, changes, shouldRemoveNullObjectValues) {
|
|
|
1151
248
|
* Onyx.merge(ONYXKEYS.EMPLOYEE_LIST, ['Jack']); // -> ['Joe', 'Jack']
|
|
1152
249
|
* Onyx.merge(ONYXKEYS.POLICY, {id: 1}); // -> {id: 1}
|
|
1153
250
|
* Onyx.merge(ONYXKEYS.POLICY, {name: 'My Workspace'}); // -> {id: 1, name: 'My Workspace'}
|
|
1154
|
-
*
|
|
1155
|
-
* @param {String} key ONYXKEYS key
|
|
1156
|
-
* @param {(Object|Array)} changes Object or Array value to merge
|
|
1157
|
-
* @returns {Promise}
|
|
1158
251
|
*/
|
|
1159
252
|
function merge(key, changes) {
|
|
253
|
+
const mergeQueue = OnyxUtils_1.default.getMergeQueue();
|
|
254
|
+
const mergeQueuePromise = OnyxUtils_1.default.getMergeQueuePromise();
|
|
1160
255
|
// Top-level undefined values are ignored
|
|
1161
256
|
// Therefore we need to prevent adding them to the merge queue
|
|
1162
|
-
if (
|
|
257
|
+
if (changes === undefined) {
|
|
1163
258
|
return mergeQueue[key] ? mergeQueuePromise[key] : Promise.resolve();
|
|
1164
259
|
}
|
|
1165
260
|
// Merge attempts are batched together. The delta should be applied after a single call to get() to prevent a race condition.
|
|
@@ -1169,42 +264,45 @@ function merge(key, changes) {
|
|
|
1169
264
|
return mergeQueuePromise[key];
|
|
1170
265
|
}
|
|
1171
266
|
mergeQueue[key] = [changes];
|
|
1172
|
-
mergeQueuePromise[key] = get(key).then((existingValue) => {
|
|
267
|
+
mergeQueuePromise[key] = OnyxUtils_1.default.get(key).then((existingValue) => {
|
|
1173
268
|
// Calls to Onyx.set after a merge will terminate the current merge process and clear the merge queue
|
|
1174
|
-
if (mergeQueue[key] == null)
|
|
1175
|
-
return;
|
|
269
|
+
if (mergeQueue[key] == null) {
|
|
270
|
+
return undefined;
|
|
271
|
+
}
|
|
1176
272
|
try {
|
|
1177
273
|
// We first only merge the changes, so we can provide these to the native implementation (SQLite uses only delta changes in "JSON_PATCH" to merge)
|
|
1178
274
|
// We don't want to remove null values from the "batchedChanges", because SQLite uses them to remove keys from storage natively.
|
|
1179
|
-
let batchedChanges = applyMerge(undefined, mergeQueue[key], false);
|
|
275
|
+
let batchedChanges = OnyxUtils_1.default.applyMerge(undefined, mergeQueue[key], false);
|
|
1180
276
|
// The presence of a `null` in the merge queue instructs us to drop the existing value.
|
|
1181
277
|
// In this case, we can't simply merge the batched changes with the existing value, because then the null in the merge queue would have no effect
|
|
1182
|
-
const shouldOverwriteExistingValue =
|
|
278
|
+
const shouldOverwriteExistingValue = mergeQueue[key].includes(null);
|
|
1183
279
|
// Clean up the write queue, so we don't apply these changes again
|
|
1184
280
|
delete mergeQueue[key];
|
|
1185
281
|
delete mergeQueuePromise[key];
|
|
1186
282
|
// If the batched changes equal null, we want to remove the key from storage, to reduce storage size
|
|
1187
|
-
const { wasRemoved } = removeNullValues(key, batchedChanges);
|
|
283
|
+
const { wasRemoved } = OnyxUtils_1.default.removeNullValues(key, batchedChanges);
|
|
1188
284
|
// After that we merge the batched changes with the existing value
|
|
1189
285
|
// We can remove null values from the "modifiedData", because "null" implicates that the user wants to remove a value from storage.
|
|
1190
286
|
// The "modifiedData" will be directly "set" in storage instead of being merged
|
|
1191
|
-
const modifiedData = shouldOverwriteExistingValue ? batchedChanges : applyMerge(existingValue, [batchedChanges], true);
|
|
287
|
+
const modifiedData = shouldOverwriteExistingValue ? batchedChanges : OnyxUtils_1.default.applyMerge(existingValue, [batchedChanges], true);
|
|
1192
288
|
// On native platforms we use SQLite which utilises JSON_PATCH to merge changes.
|
|
1193
289
|
// JSON_PATCH generally removes null values from the stored object.
|
|
1194
290
|
// When there is no existing value though, SQLite will just insert the changes as a new value and thus the null values won't be removed.
|
|
1195
291
|
// Therefore we need to remove null values from the `batchedChanges` which are sent to the SQLite, if no existing value is present.
|
|
1196
292
|
if (!existingValue) {
|
|
1197
|
-
batchedChanges = applyMerge(undefined, [batchedChanges], true);
|
|
293
|
+
batchedChanges = OnyxUtils_1.default.applyMerge(undefined, [batchedChanges], true);
|
|
1198
294
|
}
|
|
1199
295
|
const hasChanged = OnyxCache_1.default.hasValueChanged(key, modifiedData);
|
|
296
|
+
// Logging properties only since values could be sensitive things we don't want to log
|
|
297
|
+
Logger.logInfo(`merge called for key: ${key}${underscore_1.default.isObject(batchedChanges) ? ` properties: ${underscore_1.default.keys(batchedChanges).join(',')}` : ''} hasChanged: ${hasChanged}`);
|
|
1200
298
|
// This approach prioritizes fast UI changes without waiting for data to be stored in device storage.
|
|
1201
|
-
const updatePromise = broadcastUpdate(key, modifiedData,
|
|
299
|
+
const updatePromise = OnyxUtils_1.default.broadcastUpdate(key, modifiedData, hasChanged, wasRemoved);
|
|
1202
300
|
// If the value has not changed, calling Storage.setItem() would be redundant and a waste of performance, so return early instead.
|
|
1203
301
|
if (!hasChanged || wasRemoved) {
|
|
1204
302
|
return updatePromise;
|
|
1205
303
|
}
|
|
1206
304
|
return storage_1.default.mergeItem(key, batchedChanges, modifiedData).then(() => {
|
|
1207
|
-
sendActionToDevTools(METHOD.MERGE, key, changes, modifiedData);
|
|
305
|
+
OnyxUtils_1.default.sendActionToDevTools(OnyxUtils_1.default.METHOD.MERGE, key, changes, modifiedData);
|
|
1208
306
|
return updatePromise;
|
|
1209
307
|
});
|
|
1210
308
|
}
|
|
@@ -1216,16 +314,84 @@ function merge(key, changes) {
|
|
|
1216
314
|
return mergeQueuePromise[key];
|
|
1217
315
|
}
|
|
1218
316
|
/**
|
|
1219
|
-
*
|
|
1220
|
-
*
|
|
1221
|
-
* @
|
|
317
|
+
* Merges a collection based on their keys
|
|
318
|
+
*
|
|
319
|
+
* @example
|
|
320
|
+
*
|
|
321
|
+
* Onyx.mergeCollection(ONYXKEYS.COLLECTION.REPORT, {
|
|
322
|
+
* [`${ONYXKEYS.COLLECTION.REPORT}1`]: report1,
|
|
323
|
+
* [`${ONYXKEYS.COLLECTION.REPORT}2`]: report2,
|
|
324
|
+
* });
|
|
325
|
+
*
|
|
326
|
+
* @param collectionKey e.g. `ONYXKEYS.COLLECTION.REPORT`
|
|
327
|
+
* @param collection Object collection keyed by individual collection member keys and values
|
|
1222
328
|
*/
|
|
1223
|
-
function
|
|
1224
|
-
|
|
1225
|
-
|
|
1226
|
-
|
|
1227
|
-
|
|
1228
|
-
|
|
329
|
+
function mergeCollection(collectionKey, collection) {
|
|
330
|
+
if (typeof collection !== 'object' || Array.isArray(collection) || utils_1.default.isEmptyObject(collection)) {
|
|
331
|
+
Logger.logInfo('mergeCollection() called with invalid or empty value. Skipping this update.');
|
|
332
|
+
return Promise.resolve();
|
|
333
|
+
}
|
|
334
|
+
const mergedCollection = collection;
|
|
335
|
+
// Confirm all the collection keys belong to the same parent
|
|
336
|
+
let hasCollectionKeyCheckFailed = false;
|
|
337
|
+
Object.keys(mergedCollection).forEach((dataKey) => {
|
|
338
|
+
if (OnyxUtils_1.default.isKeyMatch(collectionKey, dataKey)) {
|
|
339
|
+
return;
|
|
340
|
+
}
|
|
341
|
+
if (process.env.NODE_ENV === 'development') {
|
|
342
|
+
throw new Error(`Provided collection doesn't have all its data belonging to the same parent. CollectionKey: ${collectionKey}, DataKey: ${dataKey}`);
|
|
343
|
+
}
|
|
344
|
+
hasCollectionKeyCheckFailed = true;
|
|
345
|
+
Logger.logAlert(`Provided collection doesn't have all its data belonging to the same parent. CollectionKey: ${collectionKey}, DataKey: ${dataKey}`);
|
|
346
|
+
});
|
|
347
|
+
// Gracefully handle bad mergeCollection updates so it doesn't block the merge queue
|
|
348
|
+
if (hasCollectionKeyCheckFailed) {
|
|
349
|
+
return Promise.resolve();
|
|
350
|
+
}
|
|
351
|
+
return OnyxUtils_1.default.getAllKeys().then((persistedKeys) => {
|
|
352
|
+
// Split to keys that exist in storage and keys that don't
|
|
353
|
+
const keys = Object.keys(mergedCollection).filter((key) => {
|
|
354
|
+
if (mergedCollection[key] === null) {
|
|
355
|
+
OnyxUtils_1.default.remove(key);
|
|
356
|
+
return false;
|
|
357
|
+
}
|
|
358
|
+
return true;
|
|
359
|
+
});
|
|
360
|
+
const existingKeys = keys.filter((key) => persistedKeys.has(key));
|
|
361
|
+
const newKeys = keys.filter((key) => !persistedKeys.has(key));
|
|
362
|
+
const existingKeyCollection = existingKeys.reduce((obj, key) => {
|
|
363
|
+
// eslint-disable-next-line no-param-reassign
|
|
364
|
+
obj[key] = mergedCollection[key];
|
|
365
|
+
return obj;
|
|
366
|
+
}, {});
|
|
367
|
+
const newCollection = newKeys.reduce((obj, key) => {
|
|
368
|
+
// eslint-disable-next-line no-param-reassign
|
|
369
|
+
obj[key] = mergedCollection[key];
|
|
370
|
+
return obj;
|
|
371
|
+
}, {});
|
|
372
|
+
const keyValuePairsForExistingCollection = OnyxUtils_1.default.prepareKeyValuePairsForStorage(existingKeyCollection);
|
|
373
|
+
const keyValuePairsForNewCollection = OnyxUtils_1.default.prepareKeyValuePairsForStorage(newCollection);
|
|
374
|
+
const promises = [];
|
|
375
|
+
// New keys will be added via multiSet while existing keys will be updated using multiMerge
|
|
376
|
+
// This is because setting a key that doesn't exist yet with multiMerge will throw errors
|
|
377
|
+
if (keyValuePairsForExistingCollection.length > 0) {
|
|
378
|
+
promises.push(storage_1.default.multiMerge(keyValuePairsForExistingCollection));
|
|
379
|
+
}
|
|
380
|
+
if (keyValuePairsForNewCollection.length > 0) {
|
|
381
|
+
promises.push(storage_1.default.multiSet(keyValuePairsForNewCollection));
|
|
382
|
+
}
|
|
383
|
+
// Prefill cache if necessary by calling get() on any existing keys and then merge original data to cache
|
|
384
|
+
// and update all subscribers
|
|
385
|
+
const promiseUpdate = Promise.all(existingKeys.map(OnyxUtils_1.default.get)).then(() => {
|
|
386
|
+
OnyxCache_1.default.merge(mergedCollection);
|
|
387
|
+
return OnyxUtils_1.default.scheduleNotifyCollectionSubscribers(collectionKey, mergedCollection);
|
|
388
|
+
});
|
|
389
|
+
return Promise.all(promises)
|
|
390
|
+
.catch((error) => OnyxUtils_1.default.evictStorageAndRetry(error, mergeCollection, collectionKey, mergedCollection))
|
|
391
|
+
.then(() => {
|
|
392
|
+
OnyxUtils_1.default.sendActionToDevTools(OnyxUtils_1.default.METHOD.MERGE_COLLECTION, undefined, mergedCollection);
|
|
393
|
+
return promiseUpdate;
|
|
394
|
+
});
|
|
1229
395
|
});
|
|
1230
396
|
}
|
|
1231
397
|
/**
|
|
@@ -1247,11 +413,10 @@ function initializeWithDefaultKeyStates() {
|
|
|
1247
413
|
* Storage.setItem() from Onyx.clear() will have already finished and the merged
|
|
1248
414
|
* value will be saved to storage after the default value.
|
|
1249
415
|
*
|
|
1250
|
-
* @param
|
|
1251
|
-
* @returns {Promise<void>}
|
|
416
|
+
* @param keysToPreserve is a list of ONYXKEYS that should not be cleared with the rest of the data
|
|
1252
417
|
*/
|
|
1253
418
|
function clear(keysToPreserve = []) {
|
|
1254
|
-
return getAllKeys().then((keys) => {
|
|
419
|
+
return OnyxUtils_1.default.getAllKeys().then((keys) => {
|
|
1255
420
|
const keysToBeClearedFromStorage = [];
|
|
1256
421
|
const keyValuesToResetAsCollection = {};
|
|
1257
422
|
const keyValuesToResetIndividually = {};
|
|
@@ -1261,15 +426,17 @@ function clear(keysToPreserve = []) {
|
|
|
1261
426
|
// 2. Any keys with a default state (because they need to remain in Onyx as their default, and setting them
|
|
1262
427
|
// to null would cause unknown behavior)
|
|
1263
428
|
keys.forEach((key) => {
|
|
1264
|
-
|
|
1265
|
-
const
|
|
429
|
+
var _a;
|
|
430
|
+
const isKeyToPreserve = keysToPreserve.includes(key);
|
|
431
|
+
const defaultKeyStates = OnyxUtils_1.default.getDefaultKeyStates();
|
|
432
|
+
const isDefaultKey = key in defaultKeyStates;
|
|
1266
433
|
// If the key is being removed or reset to default:
|
|
1267
434
|
// 1. Update it in the cache
|
|
1268
435
|
// 2. Figure out whether it is a collection key or not,
|
|
1269
436
|
// since collection key subscribers need to be updated differently
|
|
1270
437
|
if (!isKeyToPreserve) {
|
|
1271
438
|
const oldValue = OnyxCache_1.default.getValue(key);
|
|
1272
|
-
const newValue =
|
|
439
|
+
const newValue = (_a = defaultKeyStates[key]) !== null && _a !== void 0 ? _a : null;
|
|
1273
440
|
if (newValue !== oldValue) {
|
|
1274
441
|
OnyxCache_1.default.set(key, newValue);
|
|
1275
442
|
const collectionKey = key.substring(0, key.indexOf('_') + 1);
|
|
@@ -1292,15 +459,22 @@ function clear(keysToPreserve = []) {
|
|
|
1292
459
|
});
|
|
1293
460
|
const updatePromises = [];
|
|
1294
461
|
// Notify the subscribers for each key/value group so they can receive the new values
|
|
1295
|
-
|
|
1296
|
-
updatePromises.push(scheduleSubscriberUpdate(key, value, OnyxCache_1.default.getValue(key, false)));
|
|
462
|
+
Object.entries(keyValuesToResetIndividually).forEach(([key, value]) => {
|
|
463
|
+
updatePromises.push(OnyxUtils_1.default.scheduleSubscriberUpdate(key, value, OnyxCache_1.default.getValue(key, false)));
|
|
1297
464
|
});
|
|
1298
|
-
|
|
1299
|
-
updatePromises.push(scheduleNotifyCollectionSubscribers(key, value));
|
|
465
|
+
Object.entries(keyValuesToResetAsCollection).forEach(([key, value]) => {
|
|
466
|
+
updatePromises.push(OnyxUtils_1.default.scheduleNotifyCollectionSubscribers(key, value));
|
|
1300
467
|
});
|
|
1301
|
-
const
|
|
468
|
+
const defaultKeyStates = OnyxUtils_1.default.getDefaultKeyStates();
|
|
469
|
+
const defaultKeyValuePairs = Object.entries(Object.keys(defaultKeyStates)
|
|
470
|
+
.filter((key) => !keysToPreserve.includes(key))
|
|
471
|
+
.reduce((obj, key) => {
|
|
472
|
+
// eslint-disable-next-line no-param-reassign
|
|
473
|
+
obj[key] = defaultKeyStates[key];
|
|
474
|
+
return obj;
|
|
475
|
+
}, {}));
|
|
1302
476
|
// Remove only the items that we want cleared from storage, and reset others to default
|
|
1303
|
-
|
|
477
|
+
keysToBeClearedFromStorage.forEach((key) => OnyxCache_1.default.drop(key));
|
|
1304
478
|
return storage_1.default.removeItems(keysToBeClearedFromStorage)
|
|
1305
479
|
.then(() => storage_1.default.multiSet(defaultKeyValuePairs))
|
|
1306
480
|
.then(() => {
|
|
@@ -1309,183 +483,56 @@ function clear(keysToPreserve = []) {
|
|
|
1309
483
|
});
|
|
1310
484
|
});
|
|
1311
485
|
}
|
|
1312
|
-
/**
|
|
1313
|
-
* Merges a collection based on their keys
|
|
1314
|
-
*
|
|
1315
|
-
* @example
|
|
1316
|
-
*
|
|
1317
|
-
* Onyx.mergeCollection(ONYXKEYS.COLLECTION.REPORT, {
|
|
1318
|
-
* [`${ONYXKEYS.COLLECTION.REPORT}1`]: report1,
|
|
1319
|
-
* [`${ONYXKEYS.COLLECTION.REPORT}2`]: report2,
|
|
1320
|
-
* });
|
|
1321
|
-
*
|
|
1322
|
-
* @param {String} collectionKey e.g. `ONYXKEYS.COLLECTION.REPORT`
|
|
1323
|
-
* @param {Object} collection Object collection keyed by individual collection member keys and values
|
|
1324
|
-
* @returns {Promise}
|
|
1325
|
-
*/
|
|
1326
|
-
function mergeCollection(collectionKey, collection) {
|
|
1327
|
-
if (!underscore_1.default.isObject(collection) || underscore_1.default.isArray(collection) || underscore_1.default.isEmpty(collection)) {
|
|
1328
|
-
Logger.logInfo('mergeCollection() called with invalid or empty value. Skipping this update.');
|
|
1329
|
-
return Promise.resolve();
|
|
1330
|
-
}
|
|
1331
|
-
// Confirm all the collection keys belong to the same parent
|
|
1332
|
-
let hasCollectionKeyCheckFailed = false;
|
|
1333
|
-
underscore_1.default.each(collection, (_data, dataKey) => {
|
|
1334
|
-
if (isKeyMatch(collectionKey, dataKey)) {
|
|
1335
|
-
return;
|
|
1336
|
-
}
|
|
1337
|
-
if (process.env.NODE_ENV === 'development') {
|
|
1338
|
-
throw new Error(`Provided collection doesn't have all its data belonging to the same parent. CollectionKey: ${collectionKey}, DataKey: ${dataKey}`);
|
|
1339
|
-
}
|
|
1340
|
-
hasCollectionKeyCheckFailed = true;
|
|
1341
|
-
Logger.logAlert(`Provided collection doesn't have all its data belonging to the same parent. CollectionKey: ${collectionKey}, DataKey: ${dataKey}`);
|
|
1342
|
-
});
|
|
1343
|
-
// Gracefully handle bad mergeCollection updates so it doesn't block the merge queue
|
|
1344
|
-
if (hasCollectionKeyCheckFailed) {
|
|
1345
|
-
return Promise.resolve();
|
|
1346
|
-
}
|
|
1347
|
-
return getAllKeys().then((persistedKeys) => {
|
|
1348
|
-
// Split to keys that exist in storage and keys that don't
|
|
1349
|
-
const [existingKeys, newKeys] = underscore_1.default.chain(collection)
|
|
1350
|
-
.pick((value, key) => {
|
|
1351
|
-
if (underscore_1.default.isNull(value)) {
|
|
1352
|
-
remove(key);
|
|
1353
|
-
return false;
|
|
1354
|
-
}
|
|
1355
|
-
return true;
|
|
1356
|
-
})
|
|
1357
|
-
.keys()
|
|
1358
|
-
.partition((key) => persistedKeys.has(key))
|
|
1359
|
-
.value();
|
|
1360
|
-
const existingKeyCollection = underscore_1.default.pick(collection, existingKeys);
|
|
1361
|
-
const newCollection = underscore_1.default.pick(collection, newKeys);
|
|
1362
|
-
const keyValuePairsForExistingCollection = prepareKeyValuePairsForStorage(existingKeyCollection);
|
|
1363
|
-
const keyValuePairsForNewCollection = prepareKeyValuePairsForStorage(newCollection);
|
|
1364
|
-
const promises = [];
|
|
1365
|
-
// New keys will be added via multiSet while existing keys will be updated using multiMerge
|
|
1366
|
-
// This is because setting a key that doesn't exist yet with multiMerge will throw errors
|
|
1367
|
-
if (keyValuePairsForExistingCollection.length > 0) {
|
|
1368
|
-
promises.push(storage_1.default.multiMerge(keyValuePairsForExistingCollection));
|
|
1369
|
-
}
|
|
1370
|
-
if (keyValuePairsForNewCollection.length > 0) {
|
|
1371
|
-
promises.push(storage_1.default.multiSet(keyValuePairsForNewCollection));
|
|
1372
|
-
}
|
|
1373
|
-
// Prefill cache if necessary by calling get() on any existing keys and then merge original data to cache
|
|
1374
|
-
// and update all subscribers
|
|
1375
|
-
const promiseUpdate = Promise.all(underscore_1.default.map(existingKeys, get)).then(() => {
|
|
1376
|
-
OnyxCache_1.default.merge(collection);
|
|
1377
|
-
return scheduleNotifyCollectionSubscribers(collectionKey, collection);
|
|
1378
|
-
});
|
|
1379
|
-
return Promise.all(promises)
|
|
1380
|
-
.catch((error) => evictStorageAndRetry(error, mergeCollection, collection))
|
|
1381
|
-
.then(() => {
|
|
1382
|
-
sendActionToDevTools(METHOD.MERGE_COLLECTION, undefined, collection);
|
|
1383
|
-
return promiseUpdate;
|
|
1384
|
-
});
|
|
1385
|
-
});
|
|
1386
|
-
}
|
|
1387
486
|
/**
|
|
1388
487
|
* Insert API responses and lifecycle data into Onyx
|
|
1389
488
|
*
|
|
1390
|
-
* @param
|
|
1391
|
-
* @returns
|
|
489
|
+
* @param data An array of objects with shape {onyxMethod: oneOf('set', 'merge', 'mergeCollection', 'multiSet', 'clear'), key: string, value: *}
|
|
490
|
+
* @returns resolves when all operations are complete
|
|
1392
491
|
*/
|
|
1393
492
|
function update(data) {
|
|
1394
493
|
// First, validate the Onyx object is in the format we expect
|
|
1395
|
-
|
|
1396
|
-
if (!
|
|
494
|
+
data.forEach(({ onyxMethod, key, value }) => {
|
|
495
|
+
if (![OnyxUtils_1.default.METHOD.CLEAR, OnyxUtils_1.default.METHOD.SET, OnyxUtils_1.default.METHOD.MERGE, OnyxUtils_1.default.METHOD.MERGE_COLLECTION, OnyxUtils_1.default.METHOD.MULTI_SET].includes(onyxMethod)) {
|
|
1397
496
|
throw new Error(`Invalid onyxMethod ${onyxMethod} in Onyx update.`);
|
|
1398
497
|
}
|
|
1399
|
-
if (onyxMethod === METHOD.MULTI_SET) {
|
|
498
|
+
if (onyxMethod === OnyxUtils_1.default.METHOD.MULTI_SET) {
|
|
1400
499
|
// For multiset, we just expect the value to be an object
|
|
1401
|
-
if (
|
|
500
|
+
if (typeof value !== 'object' || Array.isArray(value) || typeof value === 'function') {
|
|
1402
501
|
throw new Error('Invalid value provided in Onyx multiSet. Onyx multiSet value must be of type object.');
|
|
1403
502
|
}
|
|
1404
503
|
}
|
|
1405
|
-
else if (onyxMethod !== METHOD.CLEAR &&
|
|
504
|
+
else if (onyxMethod !== OnyxUtils_1.default.METHOD.CLEAR && typeof key !== 'string') {
|
|
1406
505
|
throw new Error(`Invalid ${typeof key} key provided in Onyx update. Onyx key must be of type string.`);
|
|
1407
506
|
}
|
|
1408
507
|
});
|
|
1409
508
|
const promises = [];
|
|
1410
509
|
let clearPromise = Promise.resolve();
|
|
1411
|
-
|
|
510
|
+
data.forEach(({ onyxMethod, key, value }) => {
|
|
1412
511
|
switch (onyxMethod) {
|
|
1413
|
-
case METHOD.SET:
|
|
512
|
+
case OnyxUtils_1.default.METHOD.SET:
|
|
1414
513
|
promises.push(() => set(key, value));
|
|
1415
514
|
break;
|
|
1416
|
-
case METHOD.MERGE:
|
|
515
|
+
case OnyxUtils_1.default.METHOD.MERGE:
|
|
1417
516
|
promises.push(() => merge(key, value));
|
|
1418
517
|
break;
|
|
1419
|
-
case METHOD.MERGE_COLLECTION:
|
|
518
|
+
case OnyxUtils_1.default.METHOD.MERGE_COLLECTION:
|
|
519
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any -- We validated that the value is a collection
|
|
1420
520
|
promises.push(() => mergeCollection(key, value));
|
|
1421
521
|
break;
|
|
1422
|
-
case METHOD.MULTI_SET:
|
|
522
|
+
case OnyxUtils_1.default.METHOD.MULTI_SET:
|
|
1423
523
|
promises.push(() => multiSet(value));
|
|
1424
524
|
break;
|
|
1425
|
-
case METHOD.CLEAR:
|
|
525
|
+
case OnyxUtils_1.default.METHOD.CLEAR:
|
|
1426
526
|
clearPromise = clear();
|
|
1427
527
|
break;
|
|
1428
528
|
default:
|
|
1429
529
|
break;
|
|
1430
530
|
}
|
|
1431
531
|
});
|
|
1432
|
-
return clearPromise.then(() => Promise.all(
|
|
1433
|
-
}
|
|
1434
|
-
/**
|
|
1435
|
-
* Initialize the store with actions and listening for storage events
|
|
1436
|
-
*
|
|
1437
|
-
* @param {Object} [options={}] config object
|
|
1438
|
-
* @param {Object} [options.keys={}] `ONYXKEYS` constants object
|
|
1439
|
-
* @param {Object} [options.initialKeyStates={}] initial data to set when `init()` and `clear()` is called
|
|
1440
|
-
* @param {String[]} [options.safeEvictionKeys=[]] This is an array of keys
|
|
1441
|
-
* (individual or collection patterns) that when provided to Onyx are flagged
|
|
1442
|
-
* as "safe" for removal. Any components subscribing to these keys must also
|
|
1443
|
-
* implement a canEvict option. See the README for more info.
|
|
1444
|
-
* @param {Number} [options.maxCachedKeysCount=55] Sets how many recent keys should we try to keep in cache
|
|
1445
|
-
* Setting this to 0 would practically mean no cache
|
|
1446
|
-
* We try to free cache when we connect to a safe eviction key
|
|
1447
|
-
* @param {Boolean} [options.captureMetrics] Enables Onyx benchmarking and exposes the get/print/reset functions
|
|
1448
|
-
* @param {Boolean} [options.shouldSyncMultipleInstances] Auto synchronize storage events between multiple instances
|
|
1449
|
-
* of Onyx running in different tabs/windows. Defaults to true for platforms that support local storage (web/desktop)
|
|
1450
|
-
* @param {Boolean} [options.debugSetState] Enables debugging setState() calls to connected components.
|
|
1451
|
-
* @example
|
|
1452
|
-
* Onyx.init({
|
|
1453
|
-
* keys: ONYXKEYS,
|
|
1454
|
-
* initialKeyStates: {
|
|
1455
|
-
* [ONYXKEYS.SESSION]: {loading: false},
|
|
1456
|
-
* },
|
|
1457
|
-
* });
|
|
1458
|
-
*/
|
|
1459
|
-
function init({ keys = {}, initialKeyStates = {}, safeEvictionKeys = [], maxCachedKeysCount = 1000, shouldSyncMultipleInstances = Boolean(global.localStorage), debugSetState = false } = {}) {
|
|
1460
|
-
if (debugSetState) {
|
|
1461
|
-
PerformanceUtils.setShouldDebugSetState(true);
|
|
1462
|
-
}
|
|
1463
|
-
if (maxCachedKeysCount > 0) {
|
|
1464
|
-
OnyxCache_1.default.setRecentKeysLimit(maxCachedKeysCount);
|
|
1465
|
-
}
|
|
1466
|
-
// We need the value of the collection keys later for checking if a
|
|
1467
|
-
// key is a collection. We store it in a map for faster lookup.
|
|
1468
|
-
const collectionValues = underscore_1.default.values(keys.COLLECTION);
|
|
1469
|
-
onyxCollectionKeyMap = underscore_1.default.reduce(collectionValues, (acc, val) => {
|
|
1470
|
-
acc.set(val, true);
|
|
1471
|
-
return acc;
|
|
1472
|
-
}, new Map());
|
|
1473
|
-
// Set our default key states to use when initializing and clearing Onyx data
|
|
1474
|
-
defaultKeyStates = initialKeyStates;
|
|
1475
|
-
DevTools_1.default.initState(initialKeyStates);
|
|
1476
|
-
// Let Onyx know about which keys are safe to evict
|
|
1477
|
-
evictionAllowList = safeEvictionKeys;
|
|
1478
|
-
// Initialize all of our keys with data provided then give green light to any pending connections
|
|
1479
|
-
Promise.all([addAllSafeEvictionKeysToRecentlyAccessedList(), initializeWithDefaultKeyStates()]).then(deferredInitTask.resolve);
|
|
1480
|
-
if (shouldSyncMultipleInstances && underscore_1.default.isFunction(storage_1.default.keepInstancesSync)) {
|
|
1481
|
-
storage_1.default.keepInstancesSync((key, value) => {
|
|
1482
|
-
const prevValue = OnyxCache_1.default.getValue(key, false);
|
|
1483
|
-
OnyxCache_1.default.set(key, value);
|
|
1484
|
-
keyChanged(key, value, prevValue);
|
|
1485
|
-
});
|
|
1486
|
-
}
|
|
532
|
+
return clearPromise.then(() => Promise.all(promises.map((p) => p())));
|
|
1487
533
|
}
|
|
1488
534
|
const Onyx = {
|
|
535
|
+
METHOD: OnyxUtils_1.default.METHOD,
|
|
1489
536
|
connect,
|
|
1490
537
|
disconnect,
|
|
1491
538
|
set,
|
|
@@ -1494,17 +541,7 @@ const Onyx = {
|
|
|
1494
541
|
mergeCollection,
|
|
1495
542
|
update,
|
|
1496
543
|
clear,
|
|
1497
|
-
getAllKeys,
|
|
1498
544
|
init,
|
|
1499
545
|
registerLogger: Logger.registerLogger,
|
|
1500
|
-
addToEvictionBlockList,
|
|
1501
|
-
removeFromEvictionBlockList,
|
|
1502
|
-
isSafeEvictionKey,
|
|
1503
|
-
METHOD,
|
|
1504
|
-
tryGetCachedValue,
|
|
1505
|
-
hasPendingMergeForKey,
|
|
1506
|
-
isCollectionKey,
|
|
1507
|
-
isCollectionMemberKey,
|
|
1508
|
-
splitCollectionMemberKey,
|
|
1509
546
|
};
|
|
1510
547
|
exports.default = Onyx;
|