react-native-onyx 2.0.21 → 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.
Files changed (41) hide show
  1. package/API.md +49 -64
  2. package/dist/DevTools.js +0 -1
  3. package/dist/Onyx.d.ts +49 -258
  4. package/dist/Onyx.js +192 -1165
  5. package/dist/OnyxCache.d.ts +14 -15
  6. package/dist/OnyxUtils.d.ts +320 -0
  7. package/dist/OnyxUtils.js +1061 -0
  8. package/dist/PerformanceUtils.d.ts +3 -5
  9. package/dist/index.d.ts +3 -2
  10. package/dist/storage/InstanceSync/index.d.ts +14 -0
  11. package/dist/storage/InstanceSync/index.js +20 -0
  12. package/dist/storage/InstanceSync/index.web.d.ts +27 -0
  13. package/dist/storage/InstanceSync/index.web.js +59 -0
  14. package/dist/storage/__mocks__/index.d.ts +15 -13
  15. package/dist/storage/__mocks__/index.js +43 -81
  16. package/dist/storage/index.d.ts +6 -2
  17. package/dist/storage/index.js +170 -2
  18. package/dist/storage/platforms/index.d.ts +2 -0
  19. package/dist/storage/{NativeStorage.js → platforms/index.js} +2 -2
  20. package/dist/storage/platforms/index.native.d.ts +2 -0
  21. package/dist/storage/{index.native.js → platforms/index.native.js} +2 -2
  22. package/dist/storage/providers/{IDBKeyVal.js → IDBKeyValProvider.js} +23 -19
  23. package/dist/storage/providers/MemoryOnlyProvider.d.ts +9 -0
  24. package/dist/storage/providers/MemoryOnlyProvider.js +124 -0
  25. package/dist/storage/providers/NoopProvider.js +85 -0
  26. package/dist/storage/providers/SQLiteProvider.d.ts +3 -0
  27. package/dist/storage/providers/{SQLiteStorage.js → SQLiteProvider.js} +17 -11
  28. package/dist/storage/providers/types.d.ts +17 -14
  29. package/dist/types.d.ts +128 -55
  30. package/dist/types.js +2 -0
  31. package/dist/useOnyx.js +11 -10
  32. package/dist/utils.d.ts +2 -2
  33. package/dist/utils.js +29 -16
  34. package/dist/withOnyx.js +6 -5
  35. package/package.json +1 -1
  36. package/dist/storage/NativeStorage.d.ts +0 -2
  37. package/dist/storage/WebStorage.d.ts +0 -3
  38. package/dist/storage/WebStorage.js +0 -62
  39. package/dist/storage/index.native.d.ts +0 -2
  40. /package/dist/storage/providers/{IDBKeyVal.d.ts → IDBKeyValProvider.d.ts} +0 -0
  41. /package/dist/storage/providers/{SQLiteStorage.d.ts → NoopProvider.d.ts} +0 -0
@@ -0,0 +1,1061 @@
1
+ "use strict";
2
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
+ if (k2 === undefined) k2 = k;
4
+ var desc = Object.getOwnPropertyDescriptor(m, k);
5
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
+ desc = { enumerable: true, get: function() { return m[k]; } };
7
+ }
8
+ Object.defineProperty(o, k2, desc);
9
+ }) : (function(o, m, k, k2) {
10
+ if (k2 === undefined) k2 = k;
11
+ o[k2] = m[k];
12
+ }));
13
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
14
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
15
+ }) : function(o, v) {
16
+ o["default"] = v;
17
+ });
18
+ var __importStar = (this && this.__importStar) || function (mod) {
19
+ if (mod && mod.__esModule) return mod;
20
+ var result = {};
21
+ if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k);
22
+ __setModuleDefault(result, mod);
23
+ return result;
24
+ };
25
+ var __importDefault = (this && this.__importDefault) || function (mod) {
26
+ return (mod && mod.__esModule) ? mod : { "default": mod };
27
+ };
28
+ Object.defineProperty(exports, "__esModule", { value: true });
29
+ /* eslint-disable no-continue */
30
+ const fast_equals_1 = require("fast-equals");
31
+ const underscore_1 = __importDefault(require("underscore"));
32
+ const Logger = __importStar(require("./Logger"));
33
+ const OnyxCache_1 = __importDefault(require("./OnyxCache"));
34
+ const Str = __importStar(require("./Str"));
35
+ const PerformanceUtils = __importStar(require("./PerformanceUtils"));
36
+ const storage_1 = __importDefault(require("./storage"));
37
+ const utils_1 = __importDefault(require("./utils"));
38
+ const batch_1 = __importDefault(require("./batch"));
39
+ const DevTools_1 = __importDefault(require("./DevTools"));
40
+ // Method constants
41
+ const METHOD = {
42
+ SET: 'set',
43
+ MERGE: 'merge',
44
+ MERGE_COLLECTION: 'mergecollection',
45
+ MULTI_SET: 'multiset',
46
+ CLEAR: 'clear',
47
+ };
48
+ // Key/value store of Onyx key and arrays of values to merge
49
+ const mergeQueue = {};
50
+ const mergeQueuePromise = {};
51
+ // Holds a mapping of all the react components that want their state subscribed to a store key
52
+ const callbackToStateMapping = {};
53
+ // Keeps a copy of the values of the onyx collection keys as a map for faster lookups
54
+ let onyxCollectionKeyMap = new Map();
55
+ // Holds a list of keys that have been directly subscribed to or recently modified from least to most recent
56
+ let recentlyAccessedKeys = [];
57
+ // Holds a list of keys that are safe to remove when we reach max storage. If a key does not match with
58
+ // whatever appears in this list it will NEVER be a candidate for eviction.
59
+ let evictionAllowList = [];
60
+ // Holds a map of keys and connectionID arrays whose keys will never be automatically evicted as
61
+ // long as we have at least one subscriber that returns false for the canEvict property.
62
+ const evictionBlocklist = {};
63
+ // Optional user-provided key value states set when Onyx initializes or clears
64
+ let defaultKeyStates = {};
65
+ let batchUpdatesPromise = null;
66
+ let batchUpdatesQueue = [];
67
+ /**
68
+ * Getter - returns the merge queue.
69
+ *
70
+ * @returns {Object} The callback to state mapping.
71
+ */
72
+ function getMergeQueue() {
73
+ return mergeQueue;
74
+ }
75
+ /**
76
+ * Getter - returns the merge queue promise.
77
+ *
78
+ * @returns {Object} The callback to state mapping.
79
+ */
80
+ function getMergeQueuePromise() {
81
+ return mergeQueuePromise;
82
+ }
83
+ /**
84
+ * Getter - returns the callback to state mapping.
85
+ *
86
+ * @returns {Object} The callback to state mapping.
87
+ */
88
+ function getCallbackToStateMapping() {
89
+ return callbackToStateMapping;
90
+ }
91
+ /**
92
+ * Getter - returns the default key states.
93
+ *
94
+ * @returns {Object} The callback to state mapping.
95
+ */
96
+ function getDefaultKeyStates() {
97
+ return defaultKeyStates;
98
+ }
99
+ /**
100
+ * Sets the initial values for the Onyx store
101
+ *
102
+ * @param {Object} keys - `ONYXKEYS` constants object from Onyx.init()
103
+ * @param {Object} initialKeyStates - initial data to set when `init()` and `clear()` are called
104
+ * @param {Array<String>} safeEvictionKeys - This is an array of keys (individual or collection patterns) that when provided to Onyx are flagged as "safe" for removal.
105
+ */
106
+ function initStoreValues(keys, initialKeyStates, safeEvictionKeys) {
107
+ // We need the value of the collection keys later for checking if a
108
+ // key is a collection. We store it in a map for faster lookup.
109
+ const collectionValues = underscore_1.default.values(keys.COLLECTION);
110
+ onyxCollectionKeyMap = underscore_1.default.reduce(collectionValues, (acc, val) => {
111
+ acc.set(val, true);
112
+ return acc;
113
+ }, new Map());
114
+ // Set our default key states to use when initializing and clearing Onyx data
115
+ defaultKeyStates = initialKeyStates;
116
+ DevTools_1.default.initState(initialKeyStates);
117
+ // Let Onyx know about which keys are safe to evict
118
+ evictionAllowList = safeEvictionKeys;
119
+ }
120
+ /**
121
+ * Sends an action to DevTools extension
122
+ *
123
+ * @param {string} method - Onyx method from METHOD
124
+ * @param {string} key - Onyx key that was changed
125
+ * @param {any} value - contains the change that was made by the method
126
+ * @param {any} mergedValue - (optional) value that was written in the storage after a merge method was executed.
127
+ */
128
+ function sendActionToDevTools(method, key, value, mergedValue = undefined) {
129
+ DevTools_1.default.registerAction(utils_1.default.formatActionName(method, key), value, key ? { [key]: mergedValue || value } : value);
130
+ }
131
+ /**
132
+ * We are batching together onyx updates. This helps with use cases where we schedule onyx updates after each other.
133
+ * This happens for example in the Onyx.update function, where we process API responses that might contain a lot of
134
+ * update operations. Instead of calling the subscribers for each update operation, we batch them together which will
135
+ * cause react to schedule the updates at once instead of after each other. This is mainly a performance optimization.
136
+ * @returns {Promise}
137
+ */
138
+ function maybeFlushBatchUpdates() {
139
+ if (batchUpdatesPromise) {
140
+ return batchUpdatesPromise;
141
+ }
142
+ batchUpdatesPromise = new Promise((resolve) => {
143
+ /* We use (setTimeout, 0) here which should be called once native module calls are flushed (usually at the end of the frame)
144
+ * We may investigate if (setTimeout, 1) (which in React Native is equal to requestAnimationFrame) works even better
145
+ * then the batch will be flushed on next frame.
146
+ */
147
+ setTimeout(() => {
148
+ const updatesCopy = batchUpdatesQueue;
149
+ batchUpdatesQueue = [];
150
+ batchUpdatesPromise = null;
151
+ (0, batch_1.default)(() => {
152
+ updatesCopy.forEach((applyUpdates) => {
153
+ applyUpdates();
154
+ });
155
+ });
156
+ resolve();
157
+ }, 0);
158
+ });
159
+ return batchUpdatesPromise;
160
+ }
161
+ function batchUpdates(updates) {
162
+ batchUpdatesQueue.push(updates);
163
+ return maybeFlushBatchUpdates();
164
+ }
165
+ /**
166
+ * Uses a selector function to return a simplified version of sourceData
167
+ * @param {Mixed} sourceData
168
+ * @param {Function} selector Function that takes sourceData and returns a simplified version of it
169
+ * @param {Object} [withOnyxInstanceState]
170
+ * @returns {Mixed}
171
+ */
172
+ const getSubsetOfData = (sourceData, selector, withOnyxInstanceState) => selector(sourceData, withOnyxInstanceState);
173
+ /**
174
+ * Takes a collection of items (eg. {testKey_1:{a:'a'}, testKey_2:{b:'b'}})
175
+ * and runs it through a reducer function to return a subset of the data according to a selector.
176
+ * The resulting collection will only contain items that are returned by the selector.
177
+ * @param {Object} collection
178
+ * @param {String|Function} selector (see method docs for getSubsetOfData() for full details)
179
+ * @param {Object} [withOnyxInstanceState]
180
+ * @returns {Object}
181
+ */
182
+ const reduceCollectionWithSelector = (collection, selector, withOnyxInstanceState) => underscore_1.default.reduce(collection, (finalCollection, item, key) => {
183
+ // eslint-disable-next-line no-param-reassign
184
+ finalCollection[key] = getSubsetOfData(item, selector, withOnyxInstanceState);
185
+ return finalCollection;
186
+ }, {});
187
+ /**
188
+ * Get some data from the store
189
+ *
190
+ * @private
191
+ * @param {string} key
192
+ * @returns {Promise<*>}
193
+ */
194
+ function get(key) {
195
+ // When we already have the value in cache - resolve right away
196
+ if (OnyxCache_1.default.hasCacheForKey(key)) {
197
+ return Promise.resolve(OnyxCache_1.default.getValue(key));
198
+ }
199
+ const taskName = `get:${key}`;
200
+ // When a value retrieving task for this key is still running hook to it
201
+ if (OnyxCache_1.default.hasPendingTask(taskName)) {
202
+ return OnyxCache_1.default.getTaskPromise(taskName);
203
+ }
204
+ // Otherwise retrieve the value from storage and capture a promise to aid concurrent usages
205
+ const promise = storage_1.default.getItem(key)
206
+ .then((val) => {
207
+ OnyxCache_1.default.set(key, val);
208
+ return val;
209
+ })
210
+ .catch((err) => Logger.logInfo(`Unable to get item from persistent storage. Key: ${key} Error: ${err}`));
211
+ return OnyxCache_1.default.captureTask(taskName, promise);
212
+ }
213
+ /**
214
+ * Returns current key names stored in persisted storage
215
+ * @private
216
+ * @returns {Promise<Set<Key>>}
217
+ */
218
+ function getAllKeys() {
219
+ // When we've already read stored keys, resolve right away
220
+ const storedKeys = OnyxCache_1.default.getAllKeys();
221
+ if (storedKeys.size > 0) {
222
+ return Promise.resolve(storedKeys);
223
+ }
224
+ const taskName = 'getAllKeys';
225
+ // When a value retrieving task for all keys is still running hook to it
226
+ if (OnyxCache_1.default.hasPendingTask(taskName)) {
227
+ return OnyxCache_1.default.getTaskPromise(taskName);
228
+ }
229
+ // Otherwise retrieve the keys from storage and capture a promise to aid concurrent usages
230
+ const promise = storage_1.default.getAllKeys().then((keys) => {
231
+ OnyxCache_1.default.setAllKeys(keys);
232
+ // return the updated set of keys
233
+ return OnyxCache_1.default.getAllKeys();
234
+ });
235
+ return OnyxCache_1.default.captureTask(taskName, promise);
236
+ }
237
+ /**
238
+ * Checks to see if the a subscriber's supplied key
239
+ * is associated with a collection of keys.
240
+ *
241
+ * @param {String} key
242
+ * @returns {Boolean}
243
+ */
244
+ function isCollectionKey(key) {
245
+ return onyxCollectionKeyMap.has(key);
246
+ }
247
+ /**
248
+ * @param {String} collectionKey
249
+ * @param {String} key
250
+ * @returns {Boolean}
251
+ */
252
+ function isCollectionMemberKey(collectionKey, key) {
253
+ return Str.startsWith(key, collectionKey) && key.length > collectionKey.length;
254
+ }
255
+ /**
256
+ * Splits a collection member key into the collection key part and the ID part.
257
+ * @param {String} key - The collection member key to split.
258
+ * @returns {Array<String>} A tuple where the first element is the collection part and the second element is the ID part.
259
+ */
260
+ function splitCollectionMemberKey(key) {
261
+ const underscoreIndex = key.indexOf('_');
262
+ if (underscoreIndex === -1) {
263
+ throw new Error(`Invalid ${key} key provided, only collection keys are allowed.`);
264
+ }
265
+ return [key.substring(0, underscoreIndex + 1), key.substring(underscoreIndex + 1)];
266
+ }
267
+ /**
268
+ * Checks to see if a provided key is the exact configured key of our connected subscriber
269
+ * or if the provided key is a collection member key (in case our configured key is a "collection key")
270
+ *
271
+ * @private
272
+ * @param {String} configKey
273
+ * @param {String} key
274
+ * @return {Boolean}
275
+ */
276
+ function isKeyMatch(configKey, key) {
277
+ return isCollectionKey(configKey) ? Str.startsWith(key, configKey) : configKey === key;
278
+ }
279
+ /**
280
+ * Checks to see if this key has been flagged as
281
+ * safe for removal.
282
+ *
283
+ * @private
284
+ * @param {String} testKey
285
+ * @returns {Boolean}
286
+ */
287
+ function isSafeEvictionKey(testKey) {
288
+ return underscore_1.default.some(evictionAllowList, (key) => isKeyMatch(key, testKey));
289
+ }
290
+ /**
291
+ * Tries to get a value from the cache. If the value is not present in cache it will return the default value or undefined.
292
+ * If the requested key is a collection, it will return an object with all the collection members.
293
+ *
294
+ * @param {String} key
295
+ * @param {Object} mapping
296
+ * @returns {Mixed}
297
+ */
298
+ function tryGetCachedValue(key, mapping = {}) {
299
+ let val = OnyxCache_1.default.getValue(key);
300
+ if (isCollectionKey(key)) {
301
+ const allCacheKeys = OnyxCache_1.default.getAllKeys();
302
+ // It is possible we haven't loaded all keys yet so we do not know if the
303
+ // collection actually exists.
304
+ if (allCacheKeys.size === 0) {
305
+ return;
306
+ }
307
+ const matchingKeys = [];
308
+ allCacheKeys.forEach((k) => {
309
+ if (!k.startsWith(key)) {
310
+ return;
311
+ }
312
+ matchingKeys.push(k);
313
+ });
314
+ const values = underscore_1.default.reduce(matchingKeys, (finalObject, matchedKey) => {
315
+ const cachedValue = OnyxCache_1.default.getValue(matchedKey);
316
+ if (cachedValue) {
317
+ // This is permissible because we're in the process of constructing the final object in a reduce function.
318
+ // eslint-disable-next-line no-param-reassign
319
+ finalObject[matchedKey] = cachedValue;
320
+ }
321
+ return finalObject;
322
+ }, {});
323
+ val = values;
324
+ }
325
+ if (mapping.selector) {
326
+ const state = mapping.withOnyxInstance ? mapping.withOnyxInstance.state : undefined;
327
+ if (isCollectionKey(key)) {
328
+ return reduceCollectionWithSelector(val, mapping.selector, state);
329
+ }
330
+ return getSubsetOfData(val, mapping.selector, state);
331
+ }
332
+ return val;
333
+ }
334
+ /**
335
+ * Remove a key from the recently accessed key list.
336
+ *
337
+ * @private
338
+ * @param {String} key
339
+ */
340
+ function removeLastAccessedKey(key) {
341
+ recentlyAccessedKeys = underscore_1.default.without(recentlyAccessedKeys, key);
342
+ }
343
+ /**
344
+ * Add a key to the list of recently accessed keys. The least
345
+ * recently accessed key should be at the head and the most
346
+ * recently accessed key at the tail.
347
+ *
348
+ * @private
349
+ * @param {String} key
350
+ */
351
+ function addLastAccessedKey(key) {
352
+ // Only specific keys belong in this list since we cannot remove an entire collection.
353
+ if (isCollectionKey(key) || !isSafeEvictionKey(key)) {
354
+ return;
355
+ }
356
+ removeLastAccessedKey(key);
357
+ recentlyAccessedKeys.push(key);
358
+ }
359
+ /**
360
+ * Removes a key previously added to this list
361
+ * which will enable it to be deleted again.
362
+ *
363
+ * @private
364
+ * @param {String} key
365
+ * @param {Number} connectionID
366
+ */
367
+ function removeFromEvictionBlockList(key, connectionID) {
368
+ evictionBlocklist[key] = underscore_1.default.without(evictionBlocklist[key] || [], connectionID);
369
+ // Remove the key if there are no more subscribers
370
+ if (evictionBlocklist[key].length === 0) {
371
+ delete evictionBlocklist[key];
372
+ }
373
+ }
374
+ /**
375
+ * Keys added to this list can never be deleted.
376
+ *
377
+ * @private
378
+ * @param {String} key
379
+ * @param {Number} connectionID
380
+ */
381
+ function addToEvictionBlockList(key, connectionID) {
382
+ removeFromEvictionBlockList(key, connectionID);
383
+ if (!evictionBlocklist[key]) {
384
+ evictionBlocklist[key] = [];
385
+ }
386
+ evictionBlocklist[key].push(connectionID);
387
+ }
388
+ /**
389
+ * Take all the keys that are safe to evict and add them to
390
+ * the recently accessed list when initializing the app. This
391
+ * enables keys that have not recently been accessed to be
392
+ * removed.
393
+ *
394
+ * @private
395
+ * @returns {Promise}
396
+ */
397
+ function addAllSafeEvictionKeysToRecentlyAccessedList() {
398
+ return getAllKeys().then((keys) => {
399
+ underscore_1.default.each(evictionAllowList, (safeEvictionKey) => {
400
+ keys.forEach((key) => {
401
+ if (!isKeyMatch(safeEvictionKey, key)) {
402
+ return;
403
+ }
404
+ addLastAccessedKey(key);
405
+ });
406
+ });
407
+ });
408
+ }
409
+ /**
410
+ * @private
411
+ * @param {String} collectionKey
412
+ * @returns {Object}
413
+ */
414
+ function getCachedCollection(collectionKey) {
415
+ const collectionMemberKeys = [];
416
+ OnyxCache_1.default.getAllKeys().forEach((storedKey) => {
417
+ if (!isCollectionMemberKey(collectionKey, storedKey)) {
418
+ return;
419
+ }
420
+ collectionMemberKeys.push(storedKey);
421
+ });
422
+ return underscore_1.default.reduce(collectionMemberKeys, (prev, curr) => {
423
+ const cachedValue = OnyxCache_1.default.getValue(curr);
424
+ if (!cachedValue) {
425
+ return prev;
426
+ }
427
+ // eslint-disable-next-line no-param-reassign
428
+ prev[curr] = cachedValue;
429
+ return prev;
430
+ }, {});
431
+ }
432
+ /**
433
+ * When a collection of keys change, search for any callbacks matching the collection key and trigger those callbacks
434
+ *
435
+ * @private
436
+ * @param {String} collectionKey
437
+ * @param {Object} partialCollection - a partial collection of grouped member keys
438
+ * @param {boolean} [notifyRegularSubscibers=true]
439
+ * @param {boolean} [notifyWithOnyxSubscibers=true]
440
+ */
441
+ function keysChanged(collectionKey, partialCollection, notifyRegularSubscibers = true, notifyWithOnyxSubscibers = true) {
442
+ // We are iterating over all subscribers similar to keyChanged(). However, we are looking for subscribers who are subscribing to either a collection key or
443
+ // 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
444
+ // 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().
445
+ const stateMappingKeys = underscore_1.default.keys(callbackToStateMapping);
446
+ for (let i = 0; i < stateMappingKeys.length; i++) {
447
+ const subscriber = callbackToStateMapping[stateMappingKeys[i]];
448
+ if (!subscriber) {
449
+ continue;
450
+ }
451
+ // Skip iteration if we do not have a collection key or a collection member key on this subscriber
452
+ if (!Str.startsWith(subscriber.key, collectionKey)) {
453
+ continue;
454
+ }
455
+ /**
456
+ * e.g. Onyx.connect({key: ONYXKEYS.COLLECTION.REPORT, callback: ...});
457
+ */
458
+ const isSubscribedToCollectionKey = subscriber.key === collectionKey;
459
+ /**
460
+ * e.g. Onyx.connect({key: `${ONYXKEYS.COLLECTION.REPORT}{reportID}`, callback: ...});
461
+ */
462
+ const isSubscribedToCollectionMemberKey = isCollectionMemberKey(collectionKey, subscriber.key);
463
+ // We prepare the "cached collection" which is the entire collection + the new partial data that
464
+ // was merged in via mergeCollection().
465
+ const cachedCollection = getCachedCollection(collectionKey);
466
+ // Regular Onyx.connect() subscriber found.
467
+ if (underscore_1.default.isFunction(subscriber.callback)) {
468
+ if (!notifyRegularSubscibers) {
469
+ continue;
470
+ }
471
+ // If they are subscribed to the collection key and using waitForCollectionCallback then we'll
472
+ // send the whole cached collection.
473
+ if (isSubscribedToCollectionKey) {
474
+ if (subscriber.waitForCollectionCallback) {
475
+ subscriber.callback(cachedCollection);
476
+ continue;
477
+ }
478
+ // If they are not using waitForCollectionCallback then we notify the subscriber with
479
+ // the new merged data but only for any keys in the partial collection.
480
+ const dataKeys = underscore_1.default.keys(partialCollection);
481
+ for (let j = 0; j < dataKeys.length; j++) {
482
+ const dataKey = dataKeys[j];
483
+ subscriber.callback(cachedCollection[dataKey], dataKey);
484
+ }
485
+ continue;
486
+ }
487
+ // And if the subscriber is specifically only tracking a particular collection member key then we will
488
+ // notify them with the cached data for that key only.
489
+ if (isSubscribedToCollectionMemberKey) {
490
+ subscriber.callback(cachedCollection[subscriber.key], subscriber.key);
491
+ continue;
492
+ }
493
+ continue;
494
+ }
495
+ // React component subscriber found.
496
+ if (subscriber.withOnyxInstance) {
497
+ if (!notifyWithOnyxSubscibers) {
498
+ continue;
499
+ }
500
+ // We are subscribed to a collection key so we must update the data in state with the new
501
+ // collection member key values from the partial update.
502
+ if (isSubscribedToCollectionKey) {
503
+ // If the subscriber has a selector, then the component's state must only be updated with the data
504
+ // returned by the selector.
505
+ if (subscriber.selector) {
506
+ subscriber.withOnyxInstance.setStateProxy((prevState) => {
507
+ const previousData = prevState[subscriber.statePropertyName];
508
+ const newData = reduceCollectionWithSelector(cachedCollection, subscriber.selector, subscriber.withOnyxInstance.state);
509
+ if (!(0, fast_equals_1.deepEqual)(previousData, newData)) {
510
+ return {
511
+ [subscriber.statePropertyName]: newData,
512
+ };
513
+ }
514
+ return null;
515
+ });
516
+ continue;
517
+ }
518
+ subscriber.withOnyxInstance.setStateProxy((prevState) => {
519
+ const finalCollection = underscore_1.default.clone(prevState[subscriber.statePropertyName] || {});
520
+ const dataKeys = underscore_1.default.keys(partialCollection);
521
+ for (let j = 0; j < dataKeys.length; j++) {
522
+ const dataKey = dataKeys[j];
523
+ finalCollection[dataKey] = cachedCollection[dataKey];
524
+ }
525
+ PerformanceUtils.logSetStateCall(subscriber, prevState[subscriber.statePropertyName], finalCollection, 'keysChanged', collectionKey);
526
+ return {
527
+ [subscriber.statePropertyName]: finalCollection,
528
+ };
529
+ });
530
+ continue;
531
+ }
532
+ // If a React component is only interested in a single key then we can set the cached value directly to the state name.
533
+ if (isSubscribedToCollectionMemberKey) {
534
+ // However, we only want to update this subscriber if the partial data contains a change.
535
+ // Otherwise, we would update them with a value they already have and trigger an unnecessary re-render.
536
+ const dataFromCollection = partialCollection[subscriber.key];
537
+ if (underscore_1.default.isUndefined(dataFromCollection)) {
538
+ continue;
539
+ }
540
+ // If the subscriber has a selector, then the component's state must only be updated with the data
541
+ // returned by the selector and the state should only change when the subset of data changes from what
542
+ // it was previously.
543
+ if (subscriber.selector) {
544
+ subscriber.withOnyxInstance.setStateProxy((prevState) => {
545
+ const prevData = prevState[subscriber.statePropertyName];
546
+ const newData = getSubsetOfData(cachedCollection[subscriber.key], subscriber.selector, subscriber.withOnyxInstance.state);
547
+ if (!(0, fast_equals_1.deepEqual)(prevData, newData)) {
548
+ PerformanceUtils.logSetStateCall(subscriber, prevData, newData, 'keysChanged', collectionKey);
549
+ return {
550
+ [subscriber.statePropertyName]: newData,
551
+ };
552
+ }
553
+ return null;
554
+ });
555
+ continue;
556
+ }
557
+ subscriber.withOnyxInstance.setStateProxy((prevState) => {
558
+ const data = cachedCollection[subscriber.key];
559
+ const previousData = prevState[subscriber.statePropertyName];
560
+ // Avoids triggering unnecessary re-renders when feeding empty objects
561
+ if (utils_1.default.isEmptyObject(data) && utils_1.default.isEmptyObject(previousData)) {
562
+ return null;
563
+ }
564
+ if (data === previousData) {
565
+ return null;
566
+ }
567
+ PerformanceUtils.logSetStateCall(subscriber, previousData, data, 'keysChanged', collectionKey);
568
+ return {
569
+ [subscriber.statePropertyName]: data,
570
+ };
571
+ });
572
+ }
573
+ }
574
+ }
575
+ }
576
+ /**
577
+ * When a key change happens, search for any callbacks matching the key or collection key and trigger those callbacks
578
+ *
579
+ * @example
580
+ * keyChanged(key, value, subscriber => subscriber.initWithStoredValues === false)
581
+ *
582
+ * @private
583
+ * @param {String} key
584
+ * @param {*} data
585
+ * @param {*} prevData
586
+ * @param {Function} [canUpdateSubscriber] only subscribers that pass this truth test will be updated
587
+ * @param {boolean} [notifyRegularSubscibers=true]
588
+ * @param {boolean} [notifyWithOnyxSubscibers=true]
589
+ */
590
+ function keyChanged(key, data, prevData, canUpdateSubscriber = () => true, notifyRegularSubscibers = true, notifyWithOnyxSubscibers = true) {
591
+ // Add or remove this key from the recentlyAccessedKeys lists
592
+ if (!underscore_1.default.isNull(data)) {
593
+ addLastAccessedKey(key);
594
+ }
595
+ else {
596
+ removeLastAccessedKey(key);
597
+ }
598
+ // 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
599
+ // 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
600
+ // 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.
601
+ const stateMappingKeys = underscore_1.default.keys(callbackToStateMapping);
602
+ for (let i = 0; i < stateMappingKeys.length; i++) {
603
+ const subscriber = callbackToStateMapping[stateMappingKeys[i]];
604
+ if (!subscriber || !isKeyMatch(subscriber.key, key) || !canUpdateSubscriber(subscriber)) {
605
+ continue;
606
+ }
607
+ // Subscriber is a regular call to connect() and provided a callback
608
+ if (underscore_1.default.isFunction(subscriber.callback)) {
609
+ if (!notifyRegularSubscibers) {
610
+ continue;
611
+ }
612
+ if (isCollectionKey(subscriber.key) && subscriber.waitForCollectionCallback) {
613
+ const cachedCollection = getCachedCollection(subscriber.key);
614
+ cachedCollection[key] = data;
615
+ subscriber.callback(cachedCollection);
616
+ continue;
617
+ }
618
+ subscriber.callback(data, key);
619
+ continue;
620
+ }
621
+ // Subscriber connected via withOnyx() HOC
622
+ if (subscriber.withOnyxInstance) {
623
+ if (!notifyWithOnyxSubscibers) {
624
+ continue;
625
+ }
626
+ // Check if we are subscribing to a collection key and overwrite the collection member key value in state
627
+ if (isCollectionKey(subscriber.key)) {
628
+ // If the subscriber has a selector, then the consumer of this data must only be given the data
629
+ // returned by the selector and only when the selected data has changed.
630
+ if (subscriber.selector) {
631
+ subscriber.withOnyxInstance.setStateProxy((prevState) => {
632
+ const prevWithOnyxData = prevState[subscriber.statePropertyName];
633
+ const newWithOnyxData = {
634
+ [key]: getSubsetOfData(data, subscriber.selector, subscriber.withOnyxInstance.state),
635
+ };
636
+ const prevDataWithNewData = Object.assign(Object.assign({}, prevWithOnyxData), newWithOnyxData);
637
+ if (!(0, fast_equals_1.deepEqual)(prevWithOnyxData, prevDataWithNewData)) {
638
+ PerformanceUtils.logSetStateCall(subscriber, prevWithOnyxData, newWithOnyxData, 'keyChanged', key);
639
+ return {
640
+ [subscriber.statePropertyName]: prevDataWithNewData,
641
+ };
642
+ }
643
+ return null;
644
+ });
645
+ continue;
646
+ }
647
+ subscriber.withOnyxInstance.setStateProxy((prevState) => {
648
+ const collection = prevState[subscriber.statePropertyName] || {};
649
+ const newCollection = Object.assign(Object.assign({}, collection), { [key]: data });
650
+ PerformanceUtils.logSetStateCall(subscriber, collection, newCollection, 'keyChanged', key);
651
+ return {
652
+ [subscriber.statePropertyName]: newCollection,
653
+ };
654
+ });
655
+ continue;
656
+ }
657
+ // If the subscriber has a selector, then the component's state must only be updated with the data
658
+ // returned by the selector and only if the selected data has changed.
659
+ if (subscriber.selector) {
660
+ subscriber.withOnyxInstance.setStateProxy(() => {
661
+ const previousValue = getSubsetOfData(prevData, subscriber.selector, subscriber.withOnyxInstance.state);
662
+ const newValue = getSubsetOfData(data, subscriber.selector, subscriber.withOnyxInstance.state);
663
+ if (!(0, fast_equals_1.deepEqual)(previousValue, newValue)) {
664
+ return {
665
+ [subscriber.statePropertyName]: newValue,
666
+ };
667
+ }
668
+ return null;
669
+ });
670
+ continue;
671
+ }
672
+ // If we did not match on a collection key then we just set the new data to the state property
673
+ subscriber.withOnyxInstance.setStateProxy((prevState) => {
674
+ const prevWithOnyxData = prevState[subscriber.statePropertyName];
675
+ // Avoids triggering unnecessary re-renders when feeding empty objects
676
+ if (utils_1.default.isEmptyObject(data) && utils_1.default.isEmptyObject(prevWithOnyxData)) {
677
+ return null;
678
+ }
679
+ if (prevWithOnyxData === data) {
680
+ return null;
681
+ }
682
+ PerformanceUtils.logSetStateCall(subscriber, prevData, data, 'keyChanged', key);
683
+ return {
684
+ [subscriber.statePropertyName]: data,
685
+ };
686
+ });
687
+ continue;
688
+ }
689
+ console.error('Warning: Found a matching subscriber to a key that changed, but no callback or withOnyxInstance could be found.');
690
+ }
691
+ }
692
+ /**
693
+ * Sends the data obtained from the keys to the connection. It either:
694
+ * - sets state on the withOnyxInstances
695
+ * - triggers the callback function
696
+ *
697
+ * @private
698
+ * @param {Object} mapping
699
+ * @param {Object} [mapping.withOnyxInstance]
700
+ * @param {String} [mapping.statePropertyName]
701
+ * @param {Function} [mapping.callback]
702
+ * @param {String} [mapping.selector]
703
+ * @param {*|null} val
704
+ * @param {String|undefined} matchedKey
705
+ * @param {Boolean} isBatched
706
+ */
707
+ function sendDataToConnection(mapping, val, matchedKey, isBatched) {
708
+ // If the mapping no longer exists then we should not send any data.
709
+ // This means our subscriber disconnected or withOnyx wrapped component unmounted.
710
+ if (!callbackToStateMapping[mapping.connectionID]) {
711
+ return;
712
+ }
713
+ if (mapping.withOnyxInstance) {
714
+ let newData = val;
715
+ // If the mapping has a selector, then the component's state must only be updated with the data
716
+ // returned by the selector.
717
+ if (mapping.selector) {
718
+ if (isCollectionKey(mapping.key)) {
719
+ newData = reduceCollectionWithSelector(val, mapping.selector, mapping.withOnyxInstance.state);
720
+ }
721
+ else {
722
+ newData = getSubsetOfData(val, mapping.selector, mapping.withOnyxInstance.state);
723
+ }
724
+ }
725
+ PerformanceUtils.logSetStateCall(mapping, null, newData, 'sendDataToConnection');
726
+ if (isBatched) {
727
+ batchUpdates(() => {
728
+ mapping.withOnyxInstance.setWithOnyxState(mapping.statePropertyName, newData);
729
+ });
730
+ }
731
+ else {
732
+ mapping.withOnyxInstance.setWithOnyxState(mapping.statePropertyName, newData);
733
+ }
734
+ return;
735
+ }
736
+ if (underscore_1.default.isFunction(mapping.callback)) {
737
+ mapping.callback(val, matchedKey);
738
+ }
739
+ }
740
+ /**
741
+ * We check to see if this key is flagged as safe for eviction and add it to the recentlyAccessedKeys list so that when we
742
+ * run out of storage the least recently accessed key can be removed.
743
+ *
744
+ * @private
745
+ * @param {Object} mapping
746
+ */
747
+ function addKeyToRecentlyAccessedIfNeeded(mapping) {
748
+ if (!isSafeEvictionKey(mapping.key)) {
749
+ return;
750
+ }
751
+ // Try to free some cache whenever we connect to a safe eviction key
752
+ OnyxCache_1.default.removeLeastRecentlyUsedKeys();
753
+ if (mapping.withOnyxInstance && !isCollectionKey(mapping.key)) {
754
+ // All React components subscribing to a key flagged as a safe eviction key must implement the canEvict property.
755
+ if (underscore_1.default.isUndefined(mapping.canEvict)) {
756
+ throw new Error(`Cannot subscribe to safe eviction key '${mapping.key}' without providing a canEvict value.`);
757
+ }
758
+ addLastAccessedKey(mapping.key);
759
+ }
760
+ }
761
+ /**
762
+ * Gets the data for a given an array of matching keys, combines them into an object, and sends the result back to the subscriber.
763
+ *
764
+ * @private
765
+ * @param {Array} matchingKeys
766
+ * @param {Object} mapping
767
+ */
768
+ function getCollectionDataAndSendAsObject(matchingKeys, mapping) {
769
+ // Keys that are not in the cache
770
+ const missingKeys = [];
771
+ // Tasks that are pending
772
+ const pendingTasks = [];
773
+ // Keys for the tasks that are pending
774
+ const pendingKeys = [];
775
+ // We are going to combine all the data from the matching keys into a single object
776
+ const data = {};
777
+ /**
778
+ * We are going to iterate over all the matching keys and check if we have the data in the cache.
779
+ * If we do then we add it to the data object. If we do not then we check if there is a pending task
780
+ * for the key. If there is then we add the promise to the pendingTasks array and the key to the pendingKeys
781
+ * array. If there is no pending task then we add the key to the missingKeys array.
782
+ *
783
+ * These missingKeys will be later to use to multiGet the data from the storage.
784
+ */
785
+ matchingKeys.forEach((key) => {
786
+ const cacheValue = OnyxCache_1.default.getValue(key);
787
+ if (cacheValue) {
788
+ data[key] = cacheValue;
789
+ return;
790
+ }
791
+ const pendingKey = `get:${key}`;
792
+ if (OnyxCache_1.default.hasPendingTask(pendingKey)) {
793
+ pendingTasks.push(OnyxCache_1.default.getTaskPromise(pendingKey));
794
+ pendingKeys.push(key);
795
+ }
796
+ else {
797
+ missingKeys.push(key);
798
+ }
799
+ });
800
+ Promise.all(pendingTasks)
801
+ // We are going to wait for all the pending tasks to resolve and then add the data to the data object.
802
+ .then((values) => {
803
+ values.forEach((value, index) => {
804
+ data[pendingKeys[index]] = value;
805
+ });
806
+ return Promise.resolve();
807
+ })
808
+ // We are going to get the missing keys using multiGet from the storage.
809
+ .then(() => {
810
+ if (missingKeys.length === 0) {
811
+ return Promise.resolve();
812
+ }
813
+ return storage_1.default.multiGet(missingKeys);
814
+ })
815
+ // We are going to add the data from the missing keys to the data object and also merge it to the cache.
816
+ .then((values) => {
817
+ if (!values || values.length === 0) {
818
+ return Promise.resolve();
819
+ }
820
+ // temp object is used to merge the missing data into the cache
821
+ const temp = {};
822
+ values.forEach((value) => {
823
+ data[value[0]] = value[1];
824
+ temp[value[0]] = value[1];
825
+ });
826
+ OnyxCache_1.default.merge(temp);
827
+ return Promise.resolve();
828
+ })
829
+ // We are going to send the data to the subscriber.
830
+ .finally(() => {
831
+ sendDataToConnection(mapping, data, undefined, true);
832
+ });
833
+ }
834
+ /**
835
+ * Schedules an update that will be appended to the macro task queue (so it doesn't update the subscribers immediately).
836
+ *
837
+ * @example
838
+ * scheduleSubscriberUpdate(key, value, subscriber => subscriber.initWithStoredValues === false)
839
+ *
840
+ * @param {String} key
841
+ * @param {*} value
842
+ * @param {*} prevValue
843
+ * @param {Function} [canUpdateSubscriber] only subscribers that pass this truth test will be updated
844
+ * @returns {Promise}
845
+ */
846
+ function scheduleSubscriberUpdate(key, value, prevValue, canUpdateSubscriber = () => true) {
847
+ const promise = Promise.resolve().then(() => keyChanged(key, value, prevValue, canUpdateSubscriber, true, false));
848
+ batchUpdates(() => keyChanged(key, value, prevValue, canUpdateSubscriber, false, true));
849
+ return Promise.all([maybeFlushBatchUpdates(), promise]);
850
+ }
851
+ /**
852
+ * This method is similar to notifySubscribersOnNextTick but it is built for working specifically with collections
853
+ * so that keysChanged() is triggered for the collection and not keyChanged(). If this was not done, then the
854
+ * subscriber callbacks receive the data in a different format than they normally expect and it breaks code.
855
+ *
856
+ * @param {String} key
857
+ * @param {*} value
858
+ * @returns {Promise}
859
+ */
860
+ function scheduleNotifyCollectionSubscribers(key, value) {
861
+ const promise = Promise.resolve().then(() => keysChanged(key, value, true, false));
862
+ batchUpdates(() => keysChanged(key, value, false, true));
863
+ return Promise.all([maybeFlushBatchUpdates(), promise]);
864
+ }
865
+ /**
866
+ * Remove a key from Onyx and update the subscribers
867
+ *
868
+ * @private
869
+ * @param {String} key
870
+ * @return {Promise}
871
+ */
872
+ function remove(key) {
873
+ const prevValue = OnyxCache_1.default.getValue(key, false);
874
+ OnyxCache_1.default.drop(key);
875
+ scheduleSubscriberUpdate(key, null, prevValue);
876
+ return storage_1.default.removeItem(key);
877
+ }
878
+ /**
879
+ * @private
880
+ * @returns {Promise<void>}
881
+ */
882
+ function reportStorageQuota() {
883
+ return storage_1.default.getDatabaseSize()
884
+ .then(({ bytesUsed, bytesRemaining }) => {
885
+ Logger.logInfo(`Storage Quota Check -- bytesUsed: ${bytesUsed} bytesRemaining: ${bytesRemaining}`);
886
+ })
887
+ .catch((dbSizeError) => {
888
+ Logger.logAlert(`Unable to get database size. Error: ${dbSizeError}`);
889
+ });
890
+ }
891
+ /**
892
+ * If we fail to set or merge we must handle this by
893
+ * evicting some data from Onyx and then retrying to do
894
+ * whatever it is we attempted to do.
895
+ *
896
+ * @private
897
+ * @param {Error} error
898
+ * @param {Function} onyxMethod
899
+ * @param {...any} args
900
+ * @return {Promise}
901
+ */
902
+ function evictStorageAndRetry(error, onyxMethod, ...args) {
903
+ Logger.logInfo(`Failed to save to storage. Error: ${error}. onyxMethod: ${onyxMethod.name}`);
904
+ if (error && Str.startsWith(error.message, "Failed to execute 'put' on 'IDBObjectStore'")) {
905
+ Logger.logAlert('Attempted to set invalid data set in Onyx. Please ensure all data is serializable.');
906
+ throw error;
907
+ }
908
+ // Find the first key that we can remove that has no subscribers in our blocklist
909
+ const keyForRemoval = underscore_1.default.find(recentlyAccessedKeys, (key) => !evictionBlocklist[key]);
910
+ if (!keyForRemoval) {
911
+ // If we have no acceptable keys to remove then we are possibly trying to save mission critical data. If this is the case,
912
+ // 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
913
+ // will allow this write to be skipped.
914
+ Logger.logAlert('Out of storage. But found no acceptable keys to remove.');
915
+ return reportStorageQuota();
916
+ }
917
+ // Remove the least recently viewed key that is not currently being accessed and retry.
918
+ Logger.logInfo(`Out of storage. Evicting least recently accessed key (${keyForRemoval}) and retrying.`);
919
+ reportStorageQuota();
920
+ return remove(keyForRemoval).then(() => onyxMethod(...args));
921
+ }
922
+ /**
923
+ * Notifys subscribers and writes current value to cache
924
+ *
925
+ * @param {String} key
926
+ * @param {*} value
927
+ * @param {Boolean} hasChanged
928
+ * @param {Boolean} wasRemoved
929
+ * @returns {Promise}
930
+ */
931
+ function broadcastUpdate(key, value, hasChanged, wasRemoved = false) {
932
+ const prevValue = OnyxCache_1.default.getValue(key, false);
933
+ // Update subscribers if the cached value has changed, or when the subscriber specifically requires
934
+ // all updates regardless of value changes (indicated by initWithStoredValues set to false).
935
+ if (hasChanged && !wasRemoved) {
936
+ OnyxCache_1.default.set(key, value);
937
+ }
938
+ else {
939
+ OnyxCache_1.default.addToAccessedKeys(key);
940
+ }
941
+ return scheduleSubscriberUpdate(key, value, prevValue, (subscriber) => hasChanged || subscriber.initWithStoredValues === false);
942
+ }
943
+ /**
944
+ * @param {String} key
945
+ * @returns {Boolean}
946
+ */
947
+ function hasPendingMergeForKey(key) {
948
+ return Boolean(mergeQueue[key]);
949
+ }
950
+ /**
951
+ * Removes a key from storage if the value is null.
952
+ * Otherwise removes all nested null values in objects and returns the object
953
+ * @param {String} key
954
+ * @param {Mixed} value
955
+ * @returns {Mixed} The value without null values and a boolean "wasRemoved", which indicates if the key got removed completely
956
+ */
957
+ function removeNullValues(key, value) {
958
+ if (underscore_1.default.isNull(value)) {
959
+ remove(key);
960
+ return { value, wasRemoved: true };
961
+ }
962
+ // We can remove all null values in an object by merging it with itself
963
+ // utils.fastMerge recursively goes through the object and removes all null values
964
+ // Passing two identical objects as source and target to fastMerge will not change it, but only remove the null values
965
+ return { value: utils_1.default.removeNestedNullValues(value), wasRemoved: false };
966
+ }
967
+ /**
968
+ * Storage expects array like: [["@MyApp_user", value_1], ["@MyApp_key", value_2]]
969
+ * This method transforms an object like {'@MyApp_user': myUserValue, '@MyApp_key': myKeyValue}
970
+ * to an array of key-value pairs in the above format and removes key-value pairs that are being set to null
971
+ * @private
972
+ * @param {Record} data
973
+ * @return {Array} an array of key - value pairs <[key, value]>
974
+ */
975
+ function prepareKeyValuePairsForStorage(data) {
976
+ const keyValuePairs = [];
977
+ underscore_1.default.forEach(data, (value, key) => {
978
+ const { value: valueAfterRemoving, wasRemoved } = removeNullValues(key, value);
979
+ if (wasRemoved)
980
+ return;
981
+ keyValuePairs.push([key, valueAfterRemoving]);
982
+ });
983
+ return keyValuePairs;
984
+ }
985
+ /**
986
+ * Merges an array of changes with an existing value
987
+ *
988
+ * @private
989
+ * @param {*} existingValue
990
+ * @param {Array<*>} changes Array of changes that should be applied to the existing value
991
+ * @param {Boolean} shouldRemoveNullObjectValues
992
+ * @returns {*}
993
+ */
994
+ function applyMerge(existingValue, changes, shouldRemoveNullObjectValues) {
995
+ const lastChange = underscore_1.default.last(changes);
996
+ if (underscore_1.default.isArray(lastChange)) {
997
+ return lastChange;
998
+ }
999
+ if (underscore_1.default.some(changes, underscore_1.default.isObject)) {
1000
+ // Object values are then merged one after the other
1001
+ return underscore_1.default.reduce(changes, (modifiedData, change) => utils_1.default.fastMerge(modifiedData, change, shouldRemoveNullObjectValues), existingValue || {});
1002
+ }
1003
+ // If we have anything else we can't merge it so we'll
1004
+ // simply return the last value that was queued
1005
+ return lastChange;
1006
+ }
1007
+ /**
1008
+ * Merge user provided default key value pairs.
1009
+ * @private
1010
+ * @returns {Promise}
1011
+ */
1012
+ function initializeWithDefaultKeyStates() {
1013
+ return storage_1.default.multiGet(underscore_1.default.keys(defaultKeyStates)).then((pairs) => {
1014
+ const existingDataAsObject = underscore_1.default.object(pairs);
1015
+ const merged = utils_1.default.fastMerge(existingDataAsObject, defaultKeyStates);
1016
+ OnyxCache_1.default.merge(merged);
1017
+ underscore_1.default.each(merged, (val, key) => keyChanged(key, val, existingDataAsObject));
1018
+ });
1019
+ }
1020
+ const OnyxUtils = {
1021
+ METHOD,
1022
+ getMergeQueue,
1023
+ getMergeQueuePromise,
1024
+ getCallbackToStateMapping,
1025
+ getDefaultKeyStates,
1026
+ initStoreValues,
1027
+ sendActionToDevTools,
1028
+ maybeFlushBatchUpdates,
1029
+ batchUpdates,
1030
+ get,
1031
+ getAllKeys,
1032
+ isCollectionKey,
1033
+ isCollectionMemberKey,
1034
+ splitCollectionMemberKey,
1035
+ isKeyMatch,
1036
+ isSafeEvictionKey,
1037
+ tryGetCachedValue,
1038
+ removeLastAccessedKey,
1039
+ addLastAccessedKey,
1040
+ removeFromEvictionBlockList,
1041
+ addToEvictionBlockList,
1042
+ addAllSafeEvictionKeysToRecentlyAccessedList,
1043
+ getCachedCollection,
1044
+ keysChanged,
1045
+ keyChanged,
1046
+ sendDataToConnection,
1047
+ addKeyToRecentlyAccessedIfNeeded,
1048
+ getCollectionDataAndSendAsObject,
1049
+ scheduleSubscriberUpdate,
1050
+ scheduleNotifyCollectionSubscribers,
1051
+ remove,
1052
+ reportStorageQuota,
1053
+ evictStorageAndRetry,
1054
+ broadcastUpdate,
1055
+ hasPendingMergeForKey,
1056
+ removeNullValues,
1057
+ prepareKeyValuePairsForStorage,
1058
+ applyMerge,
1059
+ initializeWithDefaultKeyStates,
1060
+ };
1061
+ exports.default = OnyxUtils;