react-native-onyx 3.0.116 → 3.0.118

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/OnyxUtils.js CHANGED
@@ -37,13 +37,15 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
37
37
  };
38
38
  Object.defineProperty(exports, "__esModule", { value: true });
39
39
  exports.clearOnyxUtilsInternals = clearOnyxUtilsInternals;
40
- const fast_equals_1 = require("fast-equals");
40
+ exports.resetDeferredInitTask = resetDeferredInitTask;
41
41
  const underscore_1 = __importDefault(require("underscore"));
42
42
  const DevTools_1 = __importDefault(require("./DevTools"));
43
43
  const Logger = __importStar(require("./Logger"));
44
44
  const OnyxCache_1 = __importStar(require("./OnyxCache"));
45
45
  const OnyxKeys_1 = __importDefault(require("./OnyxKeys"));
46
+ const PendingWrites_1 = __importDefault(require("./PendingWrites"));
46
47
  const StorageCircuitBreaker_1 = __importDefault(require("./StorageCircuitBreaker"));
48
+ const OnyxSubscriptionManager_1 = __importDefault(require("./OnyxSubscriptionManager"));
47
49
  const storage_1 = __importDefault(require("./storage"));
48
50
  const errors_1 = require("./storage/errors");
49
51
  const utils_1 = __importDefault(require("./utils"));
@@ -68,26 +70,24 @@ let lastDiskPressureLogTime = 0;
68
70
  function resetDiskPressureLogThrottle() {
69
71
  lastDiskPressureLogTime = 0;
70
72
  }
71
- function formatCaughtError(error) {
72
- return error instanceof Error ? error.toString() : String(error);
73
- }
74
73
  // Key/value store of Onyx key and arrays of values to merge
75
74
  let mergeQueue = {};
76
75
  let mergeQueuePromise = {};
77
76
  const mergeQueuesWithStaleRead = new WeakSet();
78
- // Holds a mapping of all the React components that want their state subscribed to a store key
79
- let callbackToStateMapping = {};
80
- // Holds a mapping of the connected key to the subscriptionID for faster lookups
81
- let onyxKeyToSubscriptionIDs = new Map();
82
77
  // Optional user-provided key value states set when Onyx initializes or clears
83
78
  let defaultKeyStates = {};
84
- // Used for comparison with a new update to avoid invoking the Onyx.connect callback with the same data.
85
- let lastConnectionCallbackData = new Map();
86
79
  let snapshotKey = null;
87
- // Keeps track of the last subscriptionID that was used so we can keep incrementing it
88
- let lastSubscriptionID = 0;
89
- // Connections can be made before `Onyx.init`. They would wait for this task before resolving
90
- const deferredInitTask = (0, createDeferredTask_1.default)();
80
+ // Connections can be made before `Onyx.init`. They would wait for this task before resolving.
81
+ let deferredInitTask = (0, createDeferredTask_1.default)();
82
+ /**
83
+ * Sentinel for "nothing delivered yet" in `connect()`'s per-subscription dedup. A Symbol
84
+ * can't collide with any real Onyx value, so the first `Object.is` check never matches and
85
+ * the initial fire runs even when a key's genuine first value is `undefined`. It only needs
86
+ * to be distinct from real values, not unique per subscription, so one module-level instance
87
+ * is reused by every connection.
88
+ */
89
+ // eslint-disable-next-line rulesdir/no-negated-variables
90
+ const NOT_DELIVERED = Symbol('NOT_DELIVERED');
91
91
  // Collection member IDs that Onyx should silently ignore across all operations — reads, writes, cache, and subscriber
92
92
  // notifications. This is used to filter out keys formed from invalid/default IDs (e.g. "-1", "0",
93
93
  // "undefined", "null", "NaN") that can appear when an ID variable is accidentally coerced to string.
@@ -349,31 +349,6 @@ function multiGet(keys) {
349
349
  function tupleGet(keys) {
350
350
  return Promise.all(keys.map((key) => get(key)));
351
351
  }
352
- /**
353
- * Stores a subscription ID associated with a given key.
354
- *
355
- * @param subscriptionID - A subscription ID of the subscriber.
356
- * @param key - A key that the subscriber is subscribed to.
357
- */
358
- function storeKeyBySubscriptions(key, subscriptionID) {
359
- if (!onyxKeyToSubscriptionIDs.has(key)) {
360
- onyxKeyToSubscriptionIDs.set(key, []);
361
- }
362
- onyxKeyToSubscriptionIDs.get(key).push(subscriptionID);
363
- }
364
- /**
365
- * Deletes a subscription ID associated with its corresponding key.
366
- *
367
- * @param subscriptionID - The subscription ID to be deleted.
368
- */
369
- function deleteKeyBySubscriptions(subscriptionID) {
370
- const subscriber = callbackToStateMapping[subscriptionID];
371
- if (subscriber && onyxKeyToSubscriptionIDs.has(subscriber.key)) {
372
- const updatedSubscriptionsIDs = onyxKeyToSubscriptionIDs.get(subscriber.key).filter((id) => id !== subscriptionID);
373
- onyxKeyToSubscriptionIDs.set(subscriber.key, updatedSubscriptionsIDs);
374
- }
375
- lastConnectionCallbackData.delete(subscriptionID);
376
- }
377
352
  /** Returns current key names stored in persisted storage */
378
353
  function getAllKeys() {
379
354
  // When we've already read stored keys, resolve right away
@@ -396,28 +371,6 @@ function getAllKeys() {
396
371
  });
397
372
  return OnyxCache_1.default.captureTask(OnyxCache_1.TASK.GET_ALL_KEYS, promise);
398
373
  }
399
- /**
400
- * Tries to get a value from the cache. If the value is not present in cache it will return the default value or undefined.
401
- * If the requested key is a collection, it will return an object with all the collection members.
402
- */
403
- function tryGetCachedValue(key) {
404
- let val = OnyxCache_1.default.get(key);
405
- if (OnyxKeys_1.default.isCollectionKey(key)) {
406
- const collectionData = OnyxCache_1.default.getCollectionData(key);
407
- if (collectionData !== undefined) {
408
- val = collectionData;
409
- }
410
- else {
411
- // If we haven't loaded all keys yet, we can't determine if the collection exists
412
- if (OnyxCache_1.default.getAllKeys().size === 0) {
413
- return;
414
- }
415
- // Set an empty collection object for collections that exist but have no data
416
- val = {};
417
- }
418
- }
419
- return val;
420
- }
421
374
  function getCachedCollection(collectionKey, collectionMemberKeys) {
422
375
  // Use optimized collection data retrieval when cache is populated
423
376
  const collectionData = OnyxCache_1.default.getCollectionData(collectionKey);
@@ -458,187 +411,42 @@ function getCachedCollection(collectionKey, collectionMemberKeys) {
458
411
  return collection;
459
412
  }
460
413
  /**
461
- * When a collection of keys change, search for any callbacks matching the collection key and trigger those callbacks
462
- */
463
- function keysChanged(collectionKey, partialCollection, partialPreviousCollection) {
464
- var _a;
465
- const cachedCollection = getCachedCollection(collectionKey);
466
- const previousCollection = partialPreviousCollection !== null && partialPreviousCollection !== void 0 ? partialPreviousCollection : {};
467
- const changedMemberKeys = Object.keys(partialCollection !== null && partialCollection !== void 0 ? partialCollection : {});
468
- // Add or remove the keys from the recentlyAccessedKeys list
469
- for (const memberKey of changedMemberKeys) {
470
- const value = partialCollection === null || partialCollection === void 0 ? void 0 : partialCollection[memberKey];
471
- if (value !== null && value !== undefined) {
472
- OnyxCache_1.default.addLastAccessedKey(memberKey, false);
473
- }
474
- else {
475
- OnyxCache_1.default.removeLastAccessedKey(memberKey);
476
- }
477
- }
478
- // Use indexed lookup instead of scanning all subscribers.
479
- // We need subscribers for: (1) the collection key itself, and (2) individual changed member keys.
480
- const collectionSubscriberIDs = (_a = onyxKeyToSubscriptionIDs.get(collectionKey)) !== null && _a !== void 0 ? _a : [];
481
- const memberSubscriberIDs = [];
482
- for (const memberKey of changedMemberKeys) {
483
- const ids = onyxKeyToSubscriptionIDs.get(memberKey);
484
- if (ids) {
485
- for (const id of ids) {
486
- memberSubscriberIDs.push(id);
487
- }
488
- }
489
- }
490
- // Notify collection-level subscribers
491
- for (const subID of collectionSubscriberIDs) {
492
- const subscriber = callbackToStateMapping[subID];
493
- if (!subscriber || typeof subscriber.callback !== 'function') {
494
- continue;
495
- }
496
- try {
497
- lastConnectionCallbackData.set(subscriber.subscriptionID, { value: cachedCollection, matchedKey: subscriber.key });
498
- subscriber.callback(cachedCollection, subscriber.key);
499
- }
500
- catch (error) {
501
- Logger.logAlert(`[OnyxUtils.keysChanged] Subscriber callback threw an error for key '${collectionKey}': ${formatCaughtError(error)}`);
502
- }
503
- }
504
- // Notify member-level subscribers (e.g. subscribed to `report_123`)
505
- for (const subID of memberSubscriberIDs) {
506
- const subscriber = callbackToStateMapping[subID];
507
- if (!subscriber || typeof subscriber.callback !== 'function') {
508
- continue;
509
- }
510
- if (cachedCollection[subscriber.key] === previousCollection[subscriber.key]) {
511
- continue;
512
- }
513
- try {
514
- const subscriberCallback = subscriber.callback;
515
- subscriberCallback(cachedCollection[subscriber.key], subscriber.key);
516
- lastConnectionCallbackData.set(subscriber.subscriptionID, {
517
- value: cachedCollection[subscriber.key],
518
- matchedKey: subscriber.key,
519
- });
520
- }
521
- catch (error) {
522
- Logger.logAlert(`[OnyxUtils.keysChanged] Subscriber callback threw an error for key '${collectionKey}': ${formatCaughtError(error)}`);
523
- }
524
- }
525
- }
526
- /**
527
- * When a key change happens, search for any callbacks matching the key or collection key and trigger those callbacks
414
+ * Notify subscribers of a single-key write. Wrapper over `onyxSubscriptionManager.notifyKey()`
415
+ * that also performs LRU bookkeeping for eviction.
528
416
  */
529
- function keyChanged(key, value, canUpdateSubscriber = () => true) {
530
- var _a, _b;
531
- // Add or remove this key from the recentlyAccessedKeys list
417
+ function notifyKey(key, value) {
532
418
  if (value !== null && value !== undefined) {
533
419
  OnyxCache_1.default.addLastAccessedKey(key, OnyxKeys_1.default.isCollectionKey(key));
534
420
  }
535
421
  else {
536
422
  OnyxCache_1.default.removeLastAccessedKey(key);
537
423
  }
538
- // We get the subscribers interested in the key that has just changed. If the subscriber's key is a collection key then we will
539
- // 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.
540
- // Given the amount of times this function is called we need to make sure we are not iterating over all subscribers every time. On the other hand, we don't need to
541
- // do the same in keysChanged, because we only call that function when a collection key changes, and it doesn't happen that often.
542
- // For performance reason, we look for the given key and later if don't find it we look for the collection key, instead of checking if it is a collection key first.
543
- let stateMappingKeys = (_a = onyxKeyToSubscriptionIDs.get(key)) !== null && _a !== void 0 ? _a : [];
544
- const collectionKey = OnyxKeys_1.default.getCollectionKey(key);
545
- if (collectionKey) {
546
- // Getting the collection key from the specific key because only collection keys were stored in the mapping.
547
- stateMappingKeys = [...stateMappingKeys, ...((_b = onyxKeyToSubscriptionIDs.get(collectionKey)) !== null && _b !== void 0 ? _b : [])];
548
- if (stateMappingKeys.length === 0) {
549
- return;
550
- }
551
- }
552
- // Cache the collection snapshot per dispatch so all subscribers to the same collection
553
- // see a consistent view, even if an earlier subscriber's callback synchronously writes
554
- // to the same collection.
555
- const cachedCollections = {};
556
- for (const stateMappingKey of stateMappingKeys) {
557
- const subscriber = callbackToStateMapping[stateMappingKey];
558
- if (!subscriber || !OnyxKeys_1.default.isKeyMatch(subscriber.key, key) || !canUpdateSubscriber(subscriber)) {
559
- continue;
560
- }
561
- // Subscriber is a regular call to connect() and provided a callback
562
- if (typeof subscriber.callback === 'function') {
563
- try {
564
- const lastData = lastConnectionCallbackData.get(subscriber.subscriptionID);
565
- if (lastData && lastData.matchedKey === key && lastData.value === value) {
566
- continue;
567
- }
568
- if (OnyxKeys_1.default.isCollectionKey(subscriber.key)) {
569
- // Cache once per dispatch to ensure all subscribers see a consistent snapshot
570
- // even if a previous callback synchronously wrote to the same collection.
571
- let cachedCollection = cachedCollections[subscriber.key];
572
- if (!cachedCollection) {
573
- cachedCollection = getCachedCollection(subscriber.key);
574
- cachedCollections[subscriber.key] = cachedCollection;
575
- }
576
- lastConnectionCallbackData.set(subscriber.subscriptionID, { value: cachedCollection, matchedKey: subscriber.key });
577
- subscriber.callback(cachedCollection, subscriber.key);
578
- continue;
579
- }
580
- const subscriberCallback = subscriber.callback;
581
- subscriberCallback(value, key);
582
- lastConnectionCallbackData.set(subscriber.subscriptionID, {
583
- value,
584
- matchedKey: key,
585
- });
586
- continue;
587
- }
588
- catch (error) {
589
- Logger.logAlert(`[OnyxUtils.keyChanged] Subscriber callback threw an error for key '${key}': ${formatCaughtError(error)}`);
590
- }
591
- continue;
592
- }
593
- console.error('Warning: Found a matching subscriber to a key that changed, but no callback could be found.');
594
- }
424
+ OnyxSubscriptionManager_1.default.notifyKey(key, value);
595
425
  }
596
426
  /**
597
- * Sends the data obtained from the keys to the connection.
427
+ * Notify subscribers of a batch collection update. Wrapper over
428
+ * `onyxSubscriptionManager.notifyCollection()` that also performs LRU bookkeeping per
429
+ * changed member.
598
430
  */
599
- function sendDataToConnection(mapping, matchedKey) {
600
- var _a;
601
- // If the mapping no longer exists then we should not send any data.
602
- // This means our subscriber was disconnected.
603
- if (!callbackToStateMapping[mapping.subscriptionID]) {
604
- return;
605
- }
606
- // Always read the latest value from cache to avoid stale or duplicate data.
607
- // For collection-root subscribers, read the full collection.
608
- // For individual key subscribers, read just that key's value.
609
- let value;
610
- if (OnyxKeys_1.default.isCollectionKey(mapping.key)) {
611
- const collection = getCachedCollection(mapping.key);
612
- value = Object.keys(collection).length > 0 ? collection : undefined;
613
- }
614
- else {
615
- value = OnyxCache_1.default.get(matchedKey !== null && matchedKey !== void 0 ? matchedKey : mapping.key);
616
- }
617
- // For regular callbacks, we never want to pass null values, but always just undefined if a value is not set in cache or storage.
618
- value = value === null ? undefined : value;
619
- const lastData = lastConnectionCallbackData.get(mapping.subscriptionID);
620
- // If the value has not changed for the same key we do not need to trigger the callback.
621
- // We compare matchedKey to avoid suppressing callbacks for different collection members
622
- // that happen to have shallow-equal values (e.g. during hydration racing with set()).
623
- if (lastData && lastData.matchedKey === matchedKey && (0, fast_equals_1.shallowEqual)(lastData.value, value)) {
624
- return;
431
+ function notifyCollection(collectionKey, partialCollection, partialPreviousCollection) {
432
+ const changedKeys = Object.keys(partialCollection !== null && partialCollection !== void 0 ? partialCollection : {});
433
+ for (const memberKey of changedKeys) {
434
+ const value = partialCollection === null || partialCollection === void 0 ? void 0 : partialCollection[memberKey];
435
+ if (value !== null && value !== undefined) {
436
+ OnyxCache_1.default.addLastAccessedKey(memberKey, false);
437
+ }
438
+ else {
439
+ OnyxCache_1.default.removeLastAccessedKey(memberKey);
440
+ }
625
441
  }
626
- (_a = mapping.callback) === null || _a === void 0 ? void 0 : _a.call(mapping, value, matchedKey);
442
+ OnyxSubscriptionManager_1.default.notifyCollection(collectionKey, partialCollection, partialPreviousCollection);
627
443
  }
628
444
  /**
629
- * Gets the data for a given an array of matching keys, combines them into an object, and sends the result back to the subscriber.
630
- */
631
- function getCollectionDataAndSendAsObject(matchingKeys, mapping) {
632
- multiGet(matchingKeys).then(() => {
633
- sendDataToConnection(mapping, mapping.key);
634
- });
635
- }
636
- /**
637
- * Remove a key from Onyx and update the subscribers
445
+ * Remove a key from Onyx and update the subscribers.
638
446
  */
639
447
  function remove(key) {
640
448
  OnyxCache_1.default.drop(key);
641
- keyChanged(key, undefined);
449
+ notifyKey(key, undefined);
642
450
  if (OnyxKeys_1.default.isRamOnlyKey(key)) {
643
451
  return Promise.resolve();
644
452
  }
@@ -769,7 +577,7 @@ function broadcastUpdate(key, value, hasChanged) {
769
577
  return;
770
578
  }
771
579
  OnyxCache_1.default.set(key, value);
772
- keyChanged(key, value);
580
+ notifyKey(key, value);
773
581
  }
774
582
  function hasPendingMergeForKey(key) {
775
583
  return !!mergeQueue[key];
@@ -939,7 +747,7 @@ function initializeWithDefaultKeyStates() {
939
747
  // Notify subscribers about default key states so that any subscriber that connected
940
748
  // before init (e.g. during module load) receives the merged default values immediately
941
749
  for (const [key, value] of Object.entries(merged !== null && merged !== void 0 ? merged : {})) {
942
- keyChanged(key, value);
750
+ notifyKey(key, value);
943
751
  }
944
752
  })
945
753
  .catch((error) => {
@@ -954,7 +762,7 @@ function initializeWithDefaultKeyStates() {
954
762
  // Notify subscribers about default key states so that any subscriber that connected
955
763
  // before init (e.g. during module load) receives the merged default values immediately
956
764
  for (const [key, value] of Object.entries(defaultKeyStates)) {
957
- keyChanged(key, value);
765
+ notifyKey(key, value);
958
766
  }
959
767
  });
960
768
  }
@@ -981,97 +789,6 @@ function doAllCollectionItemsBelongToSameParent(collectionKey, collectionKeys) {
981
789
  }
982
790
  return !hasCollectionKeyCheckFailed;
983
791
  }
984
- /**
985
- * Subscribes to an Onyx key and listens to its changes.
986
- *
987
- * @param connectOptions The options object that will define the behavior of the connection.
988
- * @returns The subscription ID to use when calling `OnyxUtils.unsubscribeFromKey()`.
989
- */
990
- function subscribeToKey(connectOptions) {
991
- const mapping = connectOptions;
992
- const subscriptionID = lastSubscriptionID++;
993
- callbackToStateMapping[subscriptionID] = mapping;
994
- callbackToStateMapping[subscriptionID].subscriptionID = subscriptionID;
995
- // When keyChanged is called, a key is passed and the method looks through all the Subscribers in callbackToStateMapping for the matching key to get the subscriptionID
996
- // to avoid having to loop through all the Subscribers all the time (even when just one connection belongs to one key),
997
- // We create a mapping from key to lists of subscriptionIDs to access the specific list of subscriptionIDs.
998
- storeKeyBySubscriptions(mapping.key, callbackToStateMapping[subscriptionID].subscriptionID);
999
- // Commit connection only after init passes
1000
- deferredInitTask.promise
1001
- // This first .then() adds a microtask tick for compatibility reasons and
1002
- // to ensure subscribers don't receive an extra initial callback before Onyx.update() data arrives.
1003
- .then(() => undefined)
1004
- .then(() => {
1005
- // Performance improvement
1006
- // If the mapping is connected to an onyx key that is not a collection
1007
- // we can skip the call to getAllKeys() and return an array with a single item
1008
- if (!!mapping.key && typeof mapping.key === 'string' && !OnyxKeys_1.default.isCollectionKey(mapping.key) && OnyxCache_1.default.getAllKeys().has(mapping.key)) {
1009
- return new Set([mapping.key]);
1010
- }
1011
- return getAllKeys();
1012
- })
1013
- .then((keys) => {
1014
- // We search all the keys in storage to see if any are a "match" for the subscriber we are connecting so that we
1015
- // can send data back to the subscriber. Note that multiple keys can match as a subscriber could either be
1016
- // subscribed to a "collection key" or a single key.
1017
- const matchingKeys = [];
1018
- // Performance optimization: For single key subscriptions, avoid O(n) iteration
1019
- if (!OnyxKeys_1.default.isCollectionKey(mapping.key)) {
1020
- if (keys.has(mapping.key)) {
1021
- matchingKeys.push(mapping.key);
1022
- }
1023
- }
1024
- else {
1025
- // Collection case - need to iterate through all keys to find matches (O(n))
1026
- for (const key of keys) {
1027
- if (!OnyxKeys_1.default.isKeyMatch(mapping.key, key)) {
1028
- continue;
1029
- }
1030
- matchingKeys.push(key);
1031
- }
1032
- }
1033
- // If the key being connected to does not exist we initialize the value with null. For subscribers that connected
1034
- // directly via connect() they will simply get a null value sent to them without any information about which key matched
1035
- // since there are none matched.
1036
- if (matchingKeys.length === 0) {
1037
- if (mapping.key) {
1038
- OnyxCache_1.default.addNullishStorageKey(mapping.key);
1039
- }
1040
- const matchedKey = OnyxKeys_1.default.isCollectionKey(mapping.key) ? mapping.key : undefined;
1041
- // Here we cannot use batching because the nullish value is expected to be set immediately for default props
1042
- // or they will be undefined.
1043
- sendDataToConnection(mapping, matchedKey);
1044
- return;
1045
- }
1046
- // When using a callback subscriber, a subscription to a collection key combines all matching
1047
- // member values into a single object and makes one call with the whole collection object.
1048
- if (typeof mapping.callback === 'function') {
1049
- if (OnyxKeys_1.default.isCollectionKey(mapping.key)) {
1050
- getCollectionDataAndSendAsObject(matchingKeys, mapping);
1051
- return;
1052
- }
1053
- // If we are not subscribed to a collection key then there's only a single key to send an update for.
1054
- get(mapping.key).then(() => sendDataToConnection(mapping, mapping.key));
1055
- return;
1056
- }
1057
- console.error('Warning: Onyx.connect() was found without a callback');
1058
- });
1059
- // The subscriptionID is returned back to the caller so that it can be used to clean up the connection when it's no longer needed
1060
- // by calling OnyxUtils.unsubscribeFromKey(subscriptionID).
1061
- return subscriptionID;
1062
- }
1063
- /**
1064
- * Disconnects and removes the listener from the Onyx key.
1065
- *
1066
- * @param subscriptionID Subscription ID returned by calling `OnyxUtils.subscribeToKey()`.
1067
- */
1068
- function unsubscribeFromKey(subscriptionID) {
1069
- if (!callbackToStateMapping[subscriptionID]) {
1070
- return;
1071
- }
1072
- deleteKeyBySubscriptions(subscriptionID);
1073
- delete callbackToStateMapping[subscriptionID];
1074
- }
1075
792
  function updateSnapshots(data, mergeFn) {
1076
793
  const snapshotCollectionKey = getSnapshotKey();
1077
794
  if (!snapshotCollectionKey)
@@ -1252,9 +969,9 @@ function multiSetWithRetry(data, retryAttempt) {
1252
969
  const persistedKeys = OnyxCache_1.default.getAllKeys();
1253
970
  const keysToRemove = removalCandidates.filter((key) => OnyxCache_1.default.get(key) !== undefined || persistedKeys.size === 0 || persistedKeys.has(key));
1254
971
  // Group collection members by their parent collection key so each collection can be notified
1255
- // via a single batched keysChanged() call instead of one keyChanged() per member. For each
972
+ // via a single batched notifyCollection() call instead of one notifyKey() per member. For each
1256
973
  // collection, `partial` holds the new values being set and `previous` holds the cached values
1257
- // from before the set, which keysChanged() uses to skip subscribers whose value didn't change.
974
+ // from before the set, which notifyCollection() uses to skip subscribers whose value didn't change.
1258
975
  const collectionBatches = new Map();
1259
976
  for (const [key, value] of keyValuePairsToSet) {
1260
977
  // When we use multiSet to set a key we want to clear the current delta changes from Onyx.merge that were queued
@@ -1264,7 +981,7 @@ function multiSetWithRetry(data, retryAttempt) {
1264
981
  }
1265
982
  const collectionKey = OnyxKeys_1.default.getCollectionKey(key);
1266
983
  if (collectionKey && OnyxKeys_1.default.isCollectionMemberKey(collectionKey, key)) {
1267
- // Capture the previous cached value BEFORE calling cache.set() so keysChanged()
984
+ // Capture the previous cached value before calling cache.set() so notifyCollection()
1268
985
  // can diff old vs new per-member.
1269
986
  const previousValue = OnyxCache_1.default.get(key);
1270
987
  OnyxCache_1.default.set(key, value);
@@ -1277,14 +994,13 @@ function multiSetWithRetry(data, retryAttempt) {
1277
994
  batch.previous[key] = previousValue;
1278
995
  }
1279
996
  else {
1280
- // Non-collection keys are notified inline (cache.set + keyChanged in iteration order)
997
+ // Non-collection keys are notified inline (cache.set + notifyKey in iteration order)
1281
998
  // so re-entrant callbacks (e.g. Onyx.set inside a callback) see consistent cache
1282
999
  // and subscriber state, matching the original per-key notification semantics.
1283
1000
  OnyxCache_1.default.set(key, value);
1284
1001
  // Skip subscriber notification on retry — already notified on attempt 0.
1285
- // Collection-root subscribers re-fire on every keyChanged by contract.
1286
1002
  if (!retryAttempt) {
1287
- keyChanged(key, value);
1003
+ notifyKey(key, value);
1288
1004
  }
1289
1005
  }
1290
1006
  }
@@ -1305,15 +1021,15 @@ function multiSetWithRetry(data, retryAttempt) {
1305
1021
  }
1306
1022
  else if (!retryAttempt) {
1307
1023
  // Skip subscriber notification on retry — already notified on attempt 0.
1308
- keyChanged(key, undefined);
1024
+ notifyKey(key, undefined);
1309
1025
  }
1310
1026
  }
1311
- // One keysChanged() per collection — fires each collection-level subscriber once and lets
1312
- // keysChanged() internally decide which individual member subscribers need notification.
1027
+ // One notifyCollection() per collection: fires each collection-level subscriber once and lets
1028
+ // notifyCollection() internally decide which individual member subscribers need notification.
1313
1029
  // Skip on retry — already notified on attempt 0 (see same-reason comment above).
1314
1030
  if (!retryAttempt) {
1315
1031
  for (const [collectionKey, batch] of collectionBatches) {
1316
- keysChanged(collectionKey, batch.partial, batch.previous);
1032
+ notifyCollection(collectionKey, batch.partial, batch.previous);
1317
1033
  }
1318
1034
  }
1319
1035
  const keyValuePairsToStore = keyValuePairsToSet.filter((keyValuePair) => {
@@ -1388,18 +1104,17 @@ function setCollectionWithRetry({ collectionKey, collection }, retryAttempt) {
1388
1104
  const { pairs: keyValuePairs, keysToRemove: removalCandidates } = OnyxUtils.prepareKeyValuePairsForStorage(mutableCollection, true);
1389
1105
  // Removals of keys that are neither cached nor persisted are no-ops and skipped.
1390
1106
  const keysToRemove = removalCandidates.filter((key) => OnyxCache_1.default.get(key) !== undefined || persistedKeys.has(key));
1391
- // Snapshot before cache mutations so keysChanged() can diff removed members.
1107
+ // Capture the previous collection before cache mutations so notifyCollection() can diff removed members.
1392
1108
  const previousCollection = OnyxUtils.getCachedCollection(collectionKey);
1393
1109
  for (const [key, value] of keyValuePairs)
1394
1110
  OnyxCache_1.default.set(key, value);
1395
1111
  for (const key of keysToRemove)
1396
1112
  OnyxCache_1.default.drop(key);
1397
1113
  // Skip subscriber notification on retry — already notified on attempt 0.
1398
- // Collection-root subscribers re-fire on every keysChanged by contract.
1399
1114
  if (!retryAttempt) {
1400
1115
  // Removed members are notified as undefined, matching mergeCollection/multiSet.
1401
1116
  const partialForNotify = Object.fromEntries(Object.entries(mutableCollection).map(([key, value]) => [key, value !== null && value !== void 0 ? value : undefined]));
1402
- keysChanged(collectionKey, partialForNotify, previousCollection);
1117
+ notifyCollection(collectionKey, partialForNotify, previousCollection);
1403
1118
  }
1404
1119
  // RAM-only keys are not supposed to be saved to storage
1405
1120
  if (OnyxKeys_1.default.isRamOnlyKey(collectionKey)) {
@@ -1529,13 +1244,13 @@ function mergeCollectionWithPatches({ collectionKey, collection, mergeReplaceNul
1529
1244
  // promise-chain depth; slow path batches the misses into one Storage.multiGet.
1530
1245
  const hasColdExistingKey = existingKeys.some((key) => !OnyxCache_1.default.hasCacheForKey(key));
1531
1246
  // Swallow pre-warm read failures so a transient Storage.multiGet rejection doesn't
1532
- // skip the cache.merge() + keysChanged() below. Subscribers still see the merge even
1247
+ // skip the cache.merge() + notifyCollection() below. Subscribers still see the merge even
1533
1248
  // when storage reads fail.
1534
1249
  const prewarmPromise = hasColdExistingKey
1535
1250
  ? multiGet(existingKeys).catch((err) => Logger.logInfo(`mergeCollectionWithPatches pre-warm failed; proceeding with cache-only merge. Error: ${err}`))
1536
1251
  : Promise.resolve();
1537
1252
  return prewarmPromise.then(() => {
1538
- // Snapshot previous values from the (now-warm) cache for keysChanged's diff, then update
1253
+ // Capture previous values from the (now-warm) cache for the subscriber diff, then update
1539
1254
  // cache and notify subscribers synchronously BEFORE issuing storage writes. This matches
1540
1255
  // the cache-first / storage-second invariant followed by every other Onyx write method
1541
1256
  // (setWithRetry, applyMerge, setCollectionWithRetry, partialSetCollection, clear),
@@ -1544,12 +1259,11 @@ function mergeCollectionWithPatches({ collectionKey, collection, mergeReplaceNul
1544
1259
  const previousCollection = getCachedCollection(collectionKey, existingKeys);
1545
1260
  OnyxCache_1.default.merge(finalMergedCollection);
1546
1261
  // Skip subscriber notification on retry — already notified on attempt 0.
1547
- // Collection-root subscribers re-fire on every keysChanged by contract.
1548
1262
  if (!retryAttempt) {
1549
1263
  const partialForNotify = keysToRemove.length > 0 ? Object.assign(Object.assign({}, finalMergedCollection), Object.fromEntries(keysToRemove.map((key) => [key, undefined]))) : finalMergedCollection;
1550
1264
  const previousForNotify = keysToRemove.length > 0 ? Object.assign(Object.assign({}, previousCollection), removedPreviousValues) : previousCollection;
1551
1265
  if (Object.keys(partialForNotify).length > 0) {
1552
- keysChanged(collectionKey, partialForNotify, previousForNotify);
1266
+ notifyCollection(collectionKey, partialForNotify, previousForNotify);
1553
1267
  }
1554
1268
  }
1555
1269
  const promises = [];
@@ -1626,18 +1340,17 @@ function partialSetCollection({ collectionKey, collection }, retryAttempt) {
1626
1340
  const { pairs: keyValuePairs, keysToRemove: removalCandidates } = prepareKeyValuePairsForStorage(mutableCollection, true);
1627
1341
  // Removals of keys that are neither cached nor persisted are no-ops and skipped.
1628
1342
  const keysToRemove = removalCandidates.filter((key) => OnyxCache_1.default.get(key) !== undefined || persistedKeys.has(key));
1629
- // Snapshot before cache mutations so keysChanged() can diff removed members.
1343
+ // Capture the previous collection before cache mutations so notifyCollection() can diff removed members.
1630
1344
  const previousCollection = getCachedCollection(collectionKey, existingKeys);
1631
1345
  for (const [key, value] of keyValuePairs)
1632
1346
  OnyxCache_1.default.set(key, value);
1633
1347
  for (const key of keysToRemove)
1634
1348
  OnyxCache_1.default.drop(key);
1635
1349
  // Skip subscriber notification on retry — already notified on attempt 0.
1636
- // Collection-root subscribers re-fire on every keysChanged by contract.
1637
1350
  if (!retryAttempt) {
1638
1351
  // Removed members are notified as undefined, matching mergeCollection/multiSet.
1639
1352
  const partialForNotify = Object.fromEntries(Object.entries(mutableCollection).map(([key, value]) => [key, value !== null && value !== void 0 ? value : undefined]));
1640
- keysChanged(collectionKey, partialForNotify, previousCollection);
1353
+ notifyCollection(collectionKey, partialForNotify, previousCollection);
1641
1354
  }
1642
1355
  if (OnyxKeys_1.default.isRamOnlyKey(collectionKey)) {
1643
1356
  sendActionToDevTools(METHOD.SET_COLLECTION, undefined, mutableCollection);
@@ -1664,18 +1377,23 @@ function logKeyChanged(onyxMethod, key, value, hasChanged) {
1664
1377
  function logKeyRemoved(onyxMethod, key) {
1665
1378
  Logger.logInfo(`${onyxMethod} called for key: ${key} => null passed, so key was removed`);
1666
1379
  }
1380
+ /**
1381
+ * Put Onyx back to "not initialised" state, useful for tests that need a cold start.
1382
+ */
1383
+ function resetDeferredInitTask() {
1384
+ deferredInitTask = (0, createDeferredTask_1.default)();
1385
+ }
1667
1386
  /**
1668
1387
  * Clear internal variables used in this file, useful in test environments.
1669
1388
  */
1670
1389
  function clearOnyxUtilsInternals() {
1671
1390
  mergeQueue = {};
1672
1391
  mergeQueuePromise = {};
1673
- callbackToStateMapping = {};
1674
- onyxKeyToSubscriptionIDs = new Map();
1675
- lastConnectionCallbackData = new Map();
1392
+ PendingWrites_1.default.clearPendingWrites();
1676
1393
  }
1677
1394
  const OnyxUtils = {
1678
1395
  METHOD,
1396
+ NOT_DELIVERED,
1679
1397
  getMergeQueue,
1680
1398
  getMergeQueuePromise,
1681
1399
  getDefaultKeyStates,
@@ -1685,12 +1403,9 @@ const OnyxUtils = {
1685
1403
  sendActionToDevTools,
1686
1404
  get,
1687
1405
  getAllKeys,
1688
- tryGetCachedValue,
1689
1406
  getCachedCollection,
1690
- keysChanged,
1691
- keyChanged,
1692
- sendDataToConnection,
1693
- getCollectionDataAndSendAsObject,
1407
+ notifyKey,
1408
+ notifyCollection,
1694
1409
  remove,
1695
1410
  reportStorageQuota,
1696
1411
  resetDiskPressureLogThrottle,
@@ -1709,14 +1424,10 @@ const OnyxUtils = {
1709
1424
  tupleGet,
1710
1425
  isValidNonEmptyCollectionForMerge,
1711
1426
  doAllCollectionItemsBelongToSameParent,
1712
- subscribeToKey,
1713
- unsubscribeFromKey,
1714
1427
  getSkippableCollectionMemberIDs,
1715
1428
  setSkippableCollectionMemberIDs,
1716
1429
  getSnapshotMergeKeys,
1717
1430
  setSnapshotMergeKeys,
1718
- storeKeyBySubscriptions,
1719
- deleteKeyBySubscriptions,
1720
1431
  reduceCollectionWithSelector,
1721
1432
  updateSnapshots,
1722
1433
  mergeCollectionWithPatches,