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/API.md +15 -5
- package/README.md +8 -8
- package/dist/Onyx.d.ts +17 -14
- package/dist/Onyx.js +125 -38
- package/dist/OnyxCache.d.ts +2 -2
- package/dist/OnyxCache.js +4 -9
- package/dist/OnyxUtils.d.ts +17 -55
- package/dist/OnyxUtils.js +63 -352
- package/dist/PendingWrites.d.ts +35 -0
- package/dist/PendingWrites.js +103 -0
- package/dist/index.d.ts +1 -2
- package/dist/storage/providers/IDBKeyValProvider/classifyError.js +4 -0
- package/dist/storage/providers/IDBKeyValProvider/createStore.js +1 -0
- package/dist/types.d.ts +10 -13
- package/dist/useOnyx.d.ts +11 -10
- package/dist/useOnyx.js +68 -193
- package/package.json +4 -2
- package/dist/OnyxConnectionManager.d.ts +0 -78
- package/dist/OnyxConnectionManager.js +0 -189
- package/dist/OnyxSnapshotCache.d.ts +0 -75
- package/dist/OnyxSnapshotCache.js +0 -125
- package/dist/createMemoizedSelector.d.ts +0 -18
- package/dist/createMemoizedSelector.js +0 -37
- package/dist/memoizedShallowEqual.d.ts +0 -5
- package/dist/memoizedShallowEqual.js +0 -34
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
|
-
|
|
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
|
-
//
|
|
88
|
-
let
|
|
89
|
-
|
|
90
|
-
|
|
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
|
-
*
|
|
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
|
|
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
|
-
|
|
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
|
-
*
|
|
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
|
|
600
|
-
|
|
601
|
-
|
|
602
|
-
|
|
603
|
-
|
|
604
|
-
|
|
605
|
-
|
|
606
|
-
|
|
607
|
-
|
|
608
|
-
|
|
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
|
-
|
|
442
|
+
OnyxSubscriptionManager_1.default.notifyCollection(collectionKey, partialCollection, partialPreviousCollection);
|
|
627
443
|
}
|
|
628
444
|
/**
|
|
629
|
-
*
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
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
|
|
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
|
|
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 +
|
|
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
|
-
|
|
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
|
-
|
|
1024
|
+
notifyKey(key, undefined);
|
|
1309
1025
|
}
|
|
1310
1026
|
}
|
|
1311
|
-
// One
|
|
1312
|
-
//
|
|
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
|
-
|
|
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
|
-
//
|
|
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
|
-
|
|
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() +
|
|
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
|
-
//
|
|
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
|
-
|
|
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
|
-
//
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
1691
|
-
|
|
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,
|