react-native-onyx 3.0.114 → 3.0.116

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/Onyx.js CHANGED
@@ -239,16 +239,18 @@ function merge(key, changes) {
239
239
  mergeQueue[key].push(changes);
240
240
  return mergeQueuePromise[key];
241
241
  }
242
- mergeQueue[key] = [changes];
242
+ const queuedChanges = [changes];
243
+ mergeQueue[key] = queuedChanges;
243
244
  mergeQueuePromise[key] = OnyxUtils_1.default.get(key).then((valueFromGet) => {
244
245
  // Calls to Onyx.set after a merge will terminate the current merge process and clear the merge queue
245
- if (mergeQueue[key] == null) {
246
+ if (mergeQueue[key] !== queuedChanges) {
246
247
  return Promise.resolve();
247
248
  }
248
249
  // Other writers (notably Onyx.update's mergeCollection path, which doesn't participate in mergeQueue)
249
250
  // can land between get() resolving and this callback running. Applying the delta on top of the value
250
251
  // captured back then and broadcasting it would overwrite those writes wholesale, so re-read the cache.
251
- const existingValue = OnyxCache_1.default.hasCacheForKey(key) ? OnyxCache_1.default.get(key) : valueFromGet;
252
+ const baseValue = OnyxUtils_1.default.hasStaleMergeRead(queuedChanges) ? undefined : valueFromGet;
253
+ const existingValue = OnyxCache_1.default.hasCacheForKey(key) ? OnyxCache_1.default.get(key) : baseValue;
252
254
  try {
253
255
  const validChanges = mergeQueue[key].filter((change) => {
254
256
  const { isCompatible, existingValueType, newValueType, isEmptyArrayCoercion } = utils_1.default.checkCompatibilityWithExistingValue(change, existingValue);
@@ -327,6 +329,7 @@ function mergeCollection(collectionKey, collection) {
327
329
  */
328
330
  function clear(keysToPreserve = []) {
329
331
  return OnyxUtils_1.default.afterInit(() => {
332
+ const pendingMergeEntries = OnyxUtils_1.default.getPendingMergeEntries(keysToPreserve);
330
333
  const defaultKeyStates = OnyxUtils_1.default.getDefaultKeyStates();
331
334
  const initialKeys = Object.keys(defaultKeyStates);
332
335
  const promise = OnyxUtils_1.default.getAllKeys()
@@ -339,6 +342,7 @@ function clear(keysToPreserve = []) {
339
342
  // because the notification process needs the old values in cache but at that point they will be already removed from it.
340
343
  const keyValuesToResetAsCollection = {};
341
344
  const allKeys = new Set([...cachedKeys, ...initialKeys]);
345
+ OnyxUtils_1.default.cancelPendingMerges(pendingMergeEntries);
342
346
  // The only keys that should not be cleared are:
343
347
  // 1. Anything specifically passed in keysToPreserve (because some keys like language preferences, offline
344
348
  // status, or activeClients need to remain in Onyx even when signed out)
@@ -164,6 +164,10 @@ declare function retryOperation<TMethod extends RetriableOnyxOperation>(error: E
164
164
  */
165
165
  declare function broadcastUpdate<TKey extends OnyxKey>(key: TKey, value: OnyxValue<TKey>, hasChanged?: boolean): void;
166
166
  declare function hasPendingMergeForKey(key: OnyxKey): boolean;
167
+ type PendingMergeEntry = [OnyxKey, Array<OnyxValue<OnyxKey>>, number];
168
+ declare function getPendingMergeEntries(keysToPreserve: OnyxKey[]): PendingMergeEntry[];
169
+ declare function cancelPendingMerges(entries: PendingMergeEntry[]): void;
170
+ declare function hasStaleMergeRead(queuedChanges: Array<OnyxValue<OnyxKey>>): boolean;
167
171
  /**
168
172
  * Storage expects array like: [["@MyApp_user", value_1], ["@MyApp_key", value_2]]
169
173
  * This method transforms an object like {'@MyApp_user': myUserValue, '@MyApp_key': myKeyValue}
@@ -305,6 +309,9 @@ declare const OnyxUtils: {
305
309
  retryOperation: typeof retryOperation;
306
310
  broadcastUpdate: typeof broadcastUpdate;
307
311
  hasPendingMergeForKey: typeof hasPendingMergeForKey;
312
+ getPendingMergeEntries: typeof getPendingMergeEntries;
313
+ cancelPendingMerges: typeof cancelPendingMerges;
314
+ hasStaleMergeRead: typeof hasStaleMergeRead;
308
315
  prepareKeyValuePairsForStorage: typeof prepareKeyValuePairsForStorage;
309
316
  mergeChanges: typeof mergeChanges;
310
317
  mergeAndMarkChanges: typeof mergeAndMarkChanges;
package/dist/OnyxUtils.js CHANGED
@@ -74,6 +74,7 @@ function formatCaughtError(error) {
74
74
  // Key/value store of Onyx key and arrays of values to merge
75
75
  let mergeQueue = {};
76
76
  let mergeQueuePromise = {};
77
+ const mergeQueuesWithStaleRead = new WeakSet();
77
78
  // Holds a mapping of all the React components that want their state subscribed to a store key
78
79
  let callbackToStateMapping = {};
79
80
  // Holds a mapping of the connected key to the subscriptionID for faster lookups
@@ -773,6 +774,52 @@ function broadcastUpdate(key, value, hasChanged) {
773
774
  function hasPendingMergeForKey(key) {
774
775
  return !!mergeQueue[key];
775
776
  }
777
+ function cancelPendingMergesForKey(key) {
778
+ delete mergeQueue[key];
779
+ delete mergeQueuePromise[key];
780
+ }
781
+ function cancelPendingMergesForCollection(collectionKey) {
782
+ for (const key of Object.keys(mergeQueue)) {
783
+ if (!OnyxKeys_1.default.isCollectionMemberKey(collectionKey, key)) {
784
+ continue;
785
+ }
786
+ cancelPendingMergesForKey(key);
787
+ }
788
+ }
789
+ function getPendingMergeEntries(keysToPreserve) {
790
+ return Object.entries(mergeQueue)
791
+ .filter(([key]) => !keysToPreserve.some((preserveKey) => OnyxKeys_1.default.isKeyMatch(preserveKey, key)))
792
+ .map(([key, queuedChanges]) => [key, queuedChanges, queuedChanges.length]);
793
+ }
794
+ function cancelPendingMerges(entries) {
795
+ for (const [key, queuedChanges, capturedLength] of entries) {
796
+ if (mergeQueue[key] !== queuedChanges) {
797
+ continue;
798
+ }
799
+ if (queuedChanges.length === capturedLength) {
800
+ cancelPendingMergesForKey(key);
801
+ continue;
802
+ }
803
+ queuedChanges.splice(0, capturedLength);
804
+ mergeQueuesWithStaleRead.add(queuedChanges);
805
+ }
806
+ }
807
+ function hasStaleMergeRead(queuedChanges) {
808
+ return mergeQueuesWithStaleRead.has(queuedChanges);
809
+ }
810
+ function cancelPendingMergesForKeys(keys) {
811
+ for (const key of keys) {
812
+ cancelPendingMergesForKey(key);
813
+ }
814
+ }
815
+ function cancelPendingMergesForNullMembers(collection) {
816
+ for (const [key, value] of Object.entries(collection)) {
817
+ if (value !== null) {
818
+ continue;
819
+ }
820
+ cancelPendingMergesForKey(key);
821
+ }
822
+ }
776
823
  /**
777
824
  * Storage expects array like: [["@MyApp_user", value_1], ["@MyApp_key", value_2]]
778
825
  * This method transforms an object like {'@MyApp_user': myUserValue, '@MyApp_key': myKeyValue}
@@ -1197,6 +1244,9 @@ function multiSetWithRetry(data, retryAttempt) {
1197
1244
  }, {});
1198
1245
  }
1199
1246
  const { pairs: keyValuePairsToSet, keysToRemove: removalCandidates } = OnyxUtils.prepareKeyValuePairsForStorage(newData, true);
1247
+ if (!retryAttempt) {
1248
+ cancelPendingMergesForKeys(removalCandidates);
1249
+ }
1200
1250
  // Removals of keys that are neither cached nor persisted are no-ops and skipped. When the key
1201
1251
  // index has not been loaded yet (empty set), keep the removal to be safe.
1202
1252
  const persistedKeys = OnyxCache_1.default.getAllKeys();
@@ -1321,6 +1371,9 @@ function setCollectionWithRetry({ collectionKey, collection }, retryAttempt) {
1321
1371
  }, {});
1322
1372
  }
1323
1373
  resultCollectionKeys = Object.keys(resultCollection);
1374
+ if (!retryAttempt) {
1375
+ cancelPendingMergesForCollection(collectionKey);
1376
+ }
1324
1377
  return OnyxUtils.getAllKeys().then((persistedKeys) => {
1325
1378
  const mutableCollection = Object.assign({}, resultCollection);
1326
1379
  for (const key of persistedKeys) {
@@ -1408,6 +1461,9 @@ function mergeCollectionWithPatches({ collectionKey, collection, mergeReplaceNul
1408
1461
  }, {});
1409
1462
  }
1410
1463
  resultCollectionKeys = Object.keys(resultCollection);
1464
+ if (!retryAttempt) {
1465
+ cancelPendingMergesForNullMembers(resultCollection);
1466
+ }
1411
1467
  return getAllKeys()
1412
1468
  .then((persistedKeys) => {
1413
1469
  // Split to keys that exist in storage and keys that don't. Null members are collected
@@ -1561,6 +1617,9 @@ function partialSetCollection({ collectionKey, collection }, retryAttempt) {
1561
1617
  }, {});
1562
1618
  }
1563
1619
  resultCollectionKeys = Object.keys(resultCollection);
1620
+ if (!retryAttempt) {
1621
+ cancelPendingMergesForKeys(resultCollectionKeys);
1622
+ }
1564
1623
  return getAllKeys().then((persistedKeys) => {
1565
1624
  const mutableCollection = Object.assign({}, resultCollection);
1566
1625
  const existingKeys = resultCollectionKeys.filter((key) => persistedKeys.has(key));
@@ -1638,6 +1697,9 @@ const OnyxUtils = {
1638
1697
  retryOperation,
1639
1698
  broadcastUpdate,
1640
1699
  hasPendingMergeForKey,
1700
+ getPendingMergeEntries,
1701
+ cancelPendingMerges,
1702
+ hasStaleMergeRead,
1641
1703
  prepareKeyValuePairsForStorage,
1642
1704
  mergeChanges,
1643
1705
  mergeAndMarkChanges,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "react-native-onyx",
3
- "version": "3.0.114",
3
+ "version": "3.0.116",
4
4
  "author": "Expensify, Inc.",
5
5
  "homepage": "https://expensify.com",
6
6
  "description": "State management for React Native",