react-native-onyx 2.0.63 → 2.0.65

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.
@@ -1,6 +1,7 @@
1
1
  import type { ValueOf } from 'type-fest';
2
2
  import type Onyx from './Onyx';
3
- import type { CollectionKey, CollectionKeyBase, DeepRecord, KeyValueMapping, Mapping, OnyxCollection, OnyxEntry, OnyxInput, OnyxKey, OnyxMergeCollectionInput, OnyxValue, WithOnyxConnectOptions } from './types';
3
+ import type { CollectionKey, CollectionKeyBase, ConnectOptions, DeepRecord, KeyValueMapping, Mapping, OnyxCollection, OnyxEntry, OnyxInput, OnyxKey, OnyxMergeCollectionInput, OnyxValue } from './types';
4
+ import type { DeferredTask } from './createDeferredTask';
4
5
  declare const METHOD: {
5
6
  readonly SET: "set";
6
7
  readonly MERGE: "merge";
@@ -18,14 +19,18 @@ declare function getMergeQueue(): Record<OnyxKey, Array<OnyxValue<OnyxKey>>>;
18
19
  * Getter - returns the merge queue promise.
19
20
  */
20
21
  declare function getMergeQueuePromise(): Record<OnyxKey, Promise<void>>;
21
- /**
22
- * Getter - returns the callback to state mapping.
23
- */
24
- declare function getCallbackToStateMapping(): Record<string, Mapping<OnyxKey>>;
25
22
  /**
26
23
  * Getter - returns the default key states.
27
24
  */
28
25
  declare function getDefaultKeyStates(): Record<OnyxKey, OnyxValue<OnyxKey>>;
26
+ /**
27
+ * Getter - returns the deffered init task.
28
+ */
29
+ declare function getDeferredInitTask(): DeferredTask;
30
+ /**
31
+ * Getter - returns the eviction block list.
32
+ */
33
+ declare function getEvictionBlocklist(): Record<OnyxKey, string[] | undefined>;
29
34
  /**
30
35
  * Sets the initial values for the Onyx store
31
36
  *
@@ -55,19 +60,6 @@ declare function batchUpdates(updates: () => void): Promise<void>;
55
60
  /** Get some data from the store */
56
61
  declare function get<TKey extends OnyxKey, TValue extends OnyxValue<TKey>>(key: TKey): Promise<TValue>;
57
62
  declare function multiGet<TKey extends OnyxKey>(keys: CollectionKeyBase[]): Promise<Map<OnyxKey, OnyxValue<TKey>>>;
58
- /**
59
- * Stores a connection ID associated with a given key.
60
- *
61
- * @param connectionID - a connection ID of the subscriber
62
- * @param key - a key that the subscriber is connected to
63
- */
64
- declare function storeKeyByConnections(key: OnyxKey, connectionID: number): void;
65
- /**
66
- * Deletes a connection ID associated with its corresponding key.
67
- *
68
- * @param {number} connectionID - The connection ID to be deleted.
69
- */
70
- declare function deleteKeyByConnections(connectionID: number): void;
71
63
  /** Returns current key names stored in persisted storage */
72
64
  declare function getAllKeys(): Promise<Set<OnyxKey>>;
73
65
  /**
@@ -85,7 +77,7 @@ declare function isCollectionMemberKey<TCollectionKey extends CollectionKeyBase>
85
77
  * @param key - The collection member key to split.
86
78
  * @returns A tuple where the first element is the collection part and the second element is the ID part.
87
79
  */
88
- declare function splitCollectionMemberKey<TKey extends CollectionKey>(key: TKey): [TKey extends `${infer Prefix}_${string}` ? `${Prefix}_` : never, string];
80
+ declare function splitCollectionMemberKey<TKey extends CollectionKey, CollectionKeyType = TKey extends `${infer Prefix}_${string}` ? `${Prefix}_` : never>(key: TKey): [CollectionKeyType, string];
89
81
  /**
90
82
  * Checks to see if a provided key is the exact configured key of our connected subscriber
91
83
  * or if the provided key is a collection member key (in case our configured key is a "collection key")
@@ -109,7 +101,7 @@ declare function getCollectionKey(key: OnyxKey): string;
109
101
  * Tries to get a value from the cache. If the value is not present in cache it will return the default value or undefined.
110
102
  * If the requested key is a collection, it will return an object with all the collection members.
111
103
  */
112
- declare function tryGetCachedValue<TKey extends OnyxKey>(key: TKey, mapping?: Partial<WithOnyxConnectOptions<TKey>>): OnyxValue<OnyxKey>;
104
+ declare function tryGetCachedValue<TKey extends OnyxKey>(key: TKey, mapping?: Partial<Mapping<TKey>>): OnyxValue<OnyxKey>;
113
105
  /**
114
106
  * Remove a key from the recently accessed key list.
115
107
  */
@@ -120,13 +112,6 @@ declare function removeLastAccessedKey(key: OnyxKey): void;
120
112
  * recently accessed key at the tail.
121
113
  */
122
114
  declare function addLastAccessedKey(key: OnyxKey): void;
123
- /**
124
- * Removes a key previously added to this list
125
- * which will enable it to be deleted again.
126
- */
127
- declare function removeFromEvictionBlockList(key: OnyxKey, connectionID: number): void;
128
- /** Keys added to this list can never be deleted. */
129
- declare function addToEvictionBlockList(key: OnyxKey, connectionID: number): void;
130
115
  /**
131
116
  * Take all the keys that are safe to evict and add them to
132
117
  * the recently accessed list when initializing the app. This
@@ -152,11 +137,6 @@ declare function keyChanged<TKey extends OnyxKey>(key: TKey, value: OnyxValue<TK
152
137
  * - triggers the callback function
153
138
  */
154
139
  declare function sendDataToConnection<TKey extends OnyxKey>(mapping: Mapping<TKey>, value: OnyxValue<TKey> | null, matchedKey: TKey | undefined, isBatched: boolean): void;
155
- /**
156
- * We check to see if this key is flagged as safe for eviction and add it to the recentlyAccessedKeys list so that when we
157
- * run out of storage the least recently accessed key can be removed.
158
- */
159
- declare function addKeyToRecentlyAccessedIfNeeded<TKey extends OnyxKey>(mapping: Mapping<TKey>): void;
160
140
  /**
161
141
  * Gets the data for a given an array of matching keys, combines them into an object, and sends the result back to the subscriber.
162
142
  */
@@ -228,6 +208,19 @@ declare function isValidNonEmptyCollectionForMerge<TKey extends CollectionKeyBas
228
208
  * Verify if all the collection keys belong to the same parent
229
209
  */
230
210
  declare function doAllCollectionItemsBelongToSameParent<TKey extends CollectionKeyBase>(collectionKey: TKey, collectionKeys: string[]): boolean;
211
+ /**
212
+ * Subscribes to an Onyx key and listens to its changes.
213
+ *
214
+ * @param connectOptions The options object that will define the behavior of the connection.
215
+ * @returns The subscription ID to use when calling `OnyxUtils.unsubscribeFromKey()`.
216
+ */
217
+ declare function subscribeToKey<TKey extends OnyxKey>(connectOptions: ConnectOptions<TKey>): number;
218
+ /**
219
+ * Disconnects and removes the listener from the Onyx key.
220
+ *
221
+ * @param subscriptionID Subscription ID returned by calling `OnyxUtils.subscribeToKey()`.
222
+ */
223
+ declare function unsubscribeFromKey(subscriptionID: number): void;
231
224
  declare const OnyxUtils: {
232
225
  METHOD: {
233
226
  readonly SET: "set";
@@ -238,8 +231,8 @@ declare const OnyxUtils: {
238
231
  };
239
232
  getMergeQueue: typeof getMergeQueue;
240
233
  getMergeQueuePromise: typeof getMergeQueuePromise;
241
- getCallbackToStateMapping: typeof getCallbackToStateMapping;
242
234
  getDefaultKeyStates: typeof getDefaultKeyStates;
235
+ getDeferredInitTask: typeof getDeferredInitTask;
243
236
  initStoreValues: typeof initStoreValues;
244
237
  sendActionToDevTools: typeof sendActionToDevTools;
245
238
  maybeFlushBatchUpdates: typeof maybeFlushBatchUpdates;
@@ -255,14 +248,11 @@ declare const OnyxUtils: {
255
248
  tryGetCachedValue: typeof tryGetCachedValue;
256
249
  removeLastAccessedKey: typeof removeLastAccessedKey;
257
250
  addLastAccessedKey: typeof addLastAccessedKey;
258
- removeFromEvictionBlockList: typeof removeFromEvictionBlockList;
259
- addToEvictionBlockList: typeof addToEvictionBlockList;
260
251
  addAllSafeEvictionKeysToRecentlyAccessedList: typeof addAllSafeEvictionKeysToRecentlyAccessedList;
261
252
  getCachedCollection: typeof getCachedCollection;
262
253
  keysChanged: typeof keysChanged;
263
254
  keyChanged: typeof keyChanged;
264
255
  sendDataToConnection: typeof sendDataToConnection;
265
- addKeyToRecentlyAccessedIfNeeded: typeof addKeyToRecentlyAccessedIfNeeded;
266
256
  getCollectionKey: typeof getCollectionKey;
267
257
  getCollectionDataAndSendAsObject: typeof getCollectionDataAndSendAsObject;
268
258
  scheduleSubscriberUpdate: typeof scheduleSubscriberUpdate;
@@ -276,11 +266,12 @@ declare const OnyxUtils: {
276
266
  prepareKeyValuePairsForStorage: typeof prepareKeyValuePairsForStorage;
277
267
  applyMerge: typeof applyMerge;
278
268
  initializeWithDefaultKeyStates: typeof initializeWithDefaultKeyStates;
279
- storeKeyByConnections: typeof storeKeyByConnections;
280
- deleteKeyByConnections: typeof deleteKeyByConnections;
281
269
  getSnapshotKey: typeof getSnapshotKey;
282
270
  multiGet: typeof multiGet;
283
271
  isValidNonEmptyCollectionForMerge: typeof isValidNonEmptyCollectionForMerge;
284
272
  doAllCollectionItemsBelongToSameParent: typeof doAllCollectionItemsBelongToSameParent;
273
+ subscribeToKey: typeof subscribeToKey;
274
+ unsubscribeFromKey: typeof unsubscribeFromKey;
275
+ getEvictionBlocklist: typeof getEvictionBlocklist;
285
276
  };
286
277
  export default OnyxUtils;
package/dist/OnyxUtils.js CHANGED
@@ -38,6 +38,7 @@ const Str = __importStar(require("./Str"));
38
38
  const batch_1 = __importDefault(require("./batch"));
39
39
  const storage_1 = __importDefault(require("./storage"));
40
40
  const utils_1 = __importDefault(require("./utils"));
41
+ const createDeferredTask_1 = __importDefault(require("./createDeferredTask"));
41
42
  // Method constants
42
43
  const METHOD = {
43
44
  SET: 'set',
@@ -53,14 +54,14 @@ const mergeQueuePromise = {};
53
54
  const callbackToStateMapping = {};
54
55
  // Keeps a copy of the values of the onyx collection keys as a map for faster lookups
55
56
  let onyxCollectionKeySet = new Set();
56
- // Holds a mapping of the connected key to the connectionID for faster lookups
57
- const onyxKeyToConnectionIDs = new Map();
57
+ // Holds a mapping of the connected key to the subscriptionID for faster lookups
58
+ const onyxKeyToSubscriptionIDs = new Map();
58
59
  // Holds a list of keys that have been directly subscribed to or recently modified from least to most recent
59
60
  let recentlyAccessedKeys = [];
60
61
  // Holds a list of keys that are safe to remove when we reach max storage. If a key does not match with
61
62
  // whatever appears in this list it will NEVER be a candidate for eviction.
62
63
  let evictionAllowList = [];
63
- // Holds a map of keys and connectionID arrays whose keys will never be automatically evicted as
64
+ // Holds a map of keys and connection arrays whose keys will never be automatically evicted as
64
65
  // long as we have at least one subscriber that returns false for the canEvict property.
65
66
  const evictionBlocklist = {};
66
67
  // Optional user-provided key value states set when Onyx initializes or clears
@@ -70,6 +71,10 @@ let batchUpdatesQueue = [];
70
71
  // Used for comparison with a new update to avoid invoking the Onyx.connect callback with the same data.
71
72
  const lastConnectionCallbackData = new Map();
72
73
  let snapshotKey = null;
74
+ // Keeps track of the last subscriptionID that was used so we can keep incrementing it
75
+ let lastSubscriptionID = 0;
76
+ // Connections can be made before `Onyx.init`. They would wait for this task before resolving
77
+ const deferredInitTask = (0, createDeferredTask_1.default)();
73
78
  function getSnapshotKey() {
74
79
  return snapshotKey;
75
80
  }
@@ -85,18 +90,24 @@ function getMergeQueue() {
85
90
  function getMergeQueuePromise() {
86
91
  return mergeQueuePromise;
87
92
  }
88
- /**
89
- * Getter - returns the callback to state mapping.
90
- */
91
- function getCallbackToStateMapping() {
92
- return callbackToStateMapping;
93
- }
94
93
  /**
95
94
  * Getter - returns the default key states.
96
95
  */
97
96
  function getDefaultKeyStates() {
98
97
  return defaultKeyStates;
99
98
  }
99
+ /**
100
+ * Getter - returns the deffered init task.
101
+ */
102
+ function getDeferredInitTask() {
103
+ return deferredInitTask;
104
+ }
105
+ /**
106
+ * Getter - returns the eviction block list.
107
+ */
108
+ function getEvictionBlocklist() {
109
+ return evictionBlocklist;
110
+ }
100
111
  /**
101
112
  * Sets the initial values for the Onyx store
102
113
  *
@@ -258,29 +269,29 @@ function multiGet(keys) {
258
269
  }));
259
270
  }
260
271
  /**
261
- * Stores a connection ID associated with a given key.
272
+ * Stores a subscription ID associated with a given key.
262
273
  *
263
- * @param connectionID - a connection ID of the subscriber
264
- * @param key - a key that the subscriber is connected to
274
+ * @param subscriptionID - A subscription ID of the subscriber.
275
+ * @param key - A key that the subscriber is subscribed to.
265
276
  */
266
- function storeKeyByConnections(key, connectionID) {
267
- if (!onyxKeyToConnectionIDs.has(key)) {
268
- onyxKeyToConnectionIDs.set(key, []);
277
+ function storeKeyBySubscriptions(key, subscriptionID) {
278
+ if (!onyxKeyToSubscriptionIDs.has(key)) {
279
+ onyxKeyToSubscriptionIDs.set(key, []);
269
280
  }
270
- onyxKeyToConnectionIDs.get(key).push(connectionID);
281
+ onyxKeyToSubscriptionIDs.get(key).push(subscriptionID);
271
282
  }
272
283
  /**
273
- * Deletes a connection ID associated with its corresponding key.
284
+ * Deletes a subscription ID associated with its corresponding key.
274
285
  *
275
- * @param {number} connectionID - The connection ID to be deleted.
286
+ * @param subscriptionID - The subscription ID to be deleted.
276
287
  */
277
- function deleteKeyByConnections(connectionID) {
278
- const subscriber = callbackToStateMapping[connectionID];
279
- if (subscriber && onyxKeyToConnectionIDs.has(subscriber.key)) {
280
- const updatedConnectionIDs = onyxKeyToConnectionIDs.get(subscriber.key).filter((id) => id !== connectionID);
281
- onyxKeyToConnectionIDs.set(subscriber.key, updatedConnectionIDs);
288
+ function deleteKeyBySubscriptions(subscriptionID) {
289
+ const subscriber = callbackToStateMapping[subscriptionID];
290
+ if (subscriber && onyxKeyToSubscriptionIDs.has(subscriber.key)) {
291
+ const updatedSubscriptionsIDs = onyxKeyToSubscriptionIDs.get(subscriber.key).filter((id) => id !== subscriptionID);
292
+ onyxKeyToSubscriptionIDs.set(subscriber.key, updatedSubscriptionsIDs);
282
293
  }
283
- lastConnectionCallbackData.delete(connectionID);
294
+ lastConnectionCallbackData.delete(subscriptionID);
284
295
  }
285
296
  /** Returns current key names stored in persisted storage */
286
297
  function getAllKeys() {
@@ -328,7 +339,9 @@ function splitCollectionMemberKey(key) {
328
339
  if (underscoreIndex === -1) {
329
340
  throw new Error(`Invalid ${key} key provided, only collection keys are allowed.`);
330
341
  }
331
- return [key.substring(0, underscoreIndex + 1), key.substring(underscoreIndex + 1)];
342
+ const collectionKey = key.substring(0, underscoreIndex + 1);
343
+ const memberKey = key.substring(underscoreIndex + 1);
344
+ return [collectionKey, memberKey];
332
345
  }
333
346
  /**
334
347
  * Checks to see if a provided key is the exact configured key of our connected subscriber
@@ -409,26 +422,6 @@ function addLastAccessedKey(key) {
409
422
  removeLastAccessedKey(key);
410
423
  recentlyAccessedKeys.push(key);
411
424
  }
412
- /**
413
- * Removes a key previously added to this list
414
- * which will enable it to be deleted again.
415
- */
416
- function removeFromEvictionBlockList(key, connectionID) {
417
- var _a, _b, _c;
418
- evictionBlocklist[key] = (_b = (_a = evictionBlocklist[key]) === null || _a === void 0 ? void 0 : _a.filter((evictionKey) => evictionKey !== connectionID)) !== null && _b !== void 0 ? _b : [];
419
- // Remove the key if there are no more subscribers
420
- if (((_c = evictionBlocklist[key]) === null || _c === void 0 ? void 0 : _c.length) === 0) {
421
- delete evictionBlocklist[key];
422
- }
423
- }
424
- /** Keys added to this list can never be deleted. */
425
- function addToEvictionBlockList(key, connectionID) {
426
- removeFromEvictionBlockList(key, connectionID);
427
- if (!evictionBlocklist[key]) {
428
- evictionBlocklist[key] = [];
429
- }
430
- evictionBlocklist[key].push(connectionID);
431
- }
432
425
  /**
433
426
  * Take all the keys that are safe to evict and add them to
434
427
  * the recently accessed list when initializing the app. This
@@ -506,7 +499,7 @@ function keysChanged(collectionKey, partialCollection, partialPreviousCollection
506
499
  // send the whole cached collection.
507
500
  if (isSubscribedToCollectionKey) {
508
501
  if (subscriber.waitForCollectionCallback) {
509
- subscriber.callback(cachedCollection);
502
+ subscriber.callback(cachedCollection, subscriber.key);
510
503
  continue;
511
504
  }
512
505
  // If they are not using waitForCollectionCallback then we notify the subscriber with
@@ -534,7 +527,7 @@ function keysChanged(collectionKey, partialCollection, partialPreviousCollection
534
527
  continue;
535
528
  }
536
529
  // React component subscriber found.
537
- if ('withOnyxInstance' in subscriber && subscriber.withOnyxInstance) {
530
+ if (utils_1.default.hasWithOnyxInstance(subscriber)) {
538
531
  if (!notifyWithOnyxSubscibers) {
539
532
  continue;
540
533
  }
@@ -645,12 +638,12 @@ function keyChanged(key, value, previousValue, canUpdateSubscriber = () => true,
645
638
  // 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
646
639
  // do the same in keysChanged, because we only call that function when a collection key changes, and it doesn't happen that often.
647
640
  // 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.
648
- let stateMappingKeys = (_a = onyxKeyToConnectionIDs.get(key)) !== null && _a !== void 0 ? _a : [];
641
+ let stateMappingKeys = (_a = onyxKeyToSubscriptionIDs.get(key)) !== null && _a !== void 0 ? _a : [];
649
642
  const collectionKey = getCollectionKey(key);
650
643
  const plainCollectionKey = collectionKey.lastIndexOf('_') !== -1 ? collectionKey : undefined;
651
644
  if (plainCollectionKey) {
652
645
  // Getting the collection key from the specific key because only collection keys were stored in the mapping.
653
- stateMappingKeys = [...stateMappingKeys, ...((_b = onyxKeyToConnectionIDs.get(plainCollectionKey)) !== null && _b !== void 0 ? _b : [])];
646
+ stateMappingKeys = [...stateMappingKeys, ...((_b = onyxKeyToSubscriptionIDs.get(plainCollectionKey)) !== null && _b !== void 0 ? _b : [])];
654
647
  if (stateMappingKeys.length === 0) {
655
648
  return;
656
649
  }
@@ -666,7 +659,7 @@ function keyChanged(key, value, previousValue, canUpdateSubscriber = () => true,
666
659
  if (!notifyConnectSubscribers) {
667
660
  continue;
668
661
  }
669
- if (lastConnectionCallbackData.has(subscriber.connectionID) && lastConnectionCallbackData.get(subscriber.connectionID) === value) {
662
+ if (lastConnectionCallbackData.has(subscriber.subscriptionID) && lastConnectionCallbackData.get(subscriber.subscriptionID) === value) {
670
663
  continue;
671
664
  }
672
665
  if (isCollectionKey(subscriber.key) && subscriber.waitForCollectionCallback) {
@@ -676,16 +669,16 @@ function keyChanged(key, value, previousValue, canUpdateSubscriber = () => true,
676
669
  cachedCollections[subscriber.key] = cachedCollection;
677
670
  }
678
671
  cachedCollection[key] = value;
679
- subscriber.callback(cachedCollection);
672
+ subscriber.callback(cachedCollection, subscriber.key);
680
673
  continue;
681
674
  }
682
675
  const subscriberCallback = subscriber.callback;
683
676
  subscriberCallback(value, key);
684
- lastConnectionCallbackData.set(subscriber.connectionID, value);
677
+ lastConnectionCallbackData.set(subscriber.subscriptionID, value);
685
678
  continue;
686
679
  }
687
680
  // Subscriber connected via withOnyx() HOC
688
- if ('withOnyxInstance' in subscriber && subscriber.withOnyxInstance) {
681
+ if (utils_1.default.hasWithOnyxInstance(subscriber)) {
689
682
  if (!notifyWithOnyxSubscribers) {
690
683
  continue;
691
684
  }
@@ -768,10 +761,10 @@ function sendDataToConnection(mapping, value, matchedKey, isBatched) {
768
761
  var _a, _b;
769
762
  // If the mapping no longer exists then we should not send any data.
770
763
  // This means our subscriber disconnected or withOnyx wrapped component unmounted.
771
- if (!callbackToStateMapping[mapping.connectionID]) {
764
+ if (!callbackToStateMapping[mapping.subscriptionID]) {
772
765
  return;
773
766
  }
774
- if ('withOnyxInstance' in mapping && mapping.withOnyxInstance) {
767
+ if (utils_1.default.hasWithOnyxInstance(mapping)) {
775
768
  let newData = value;
776
769
  // If the mapping has a selector, then the component's state must only be updated with the data
777
770
  // returned by the selector.
@@ -799,10 +792,10 @@ function sendDataToConnection(mapping, value, matchedKey, isBatched) {
799
792
  // withOnyx will internally replace null values with undefined and never pass null values to wrapped components.
800
793
  // For regular callbacks, we never want to pass null values, but always just undefined if a value is not set in cache or storage.
801
794
  const valueToPass = value === null ? undefined : value;
802
- const lastValue = lastConnectionCallbackData.get(mapping.connectionID);
803
- lastConnectionCallbackData.get(mapping.connectionID);
795
+ const lastValue = lastConnectionCallbackData.get(mapping.subscriptionID);
796
+ lastConnectionCallbackData.get(mapping.subscriptionID);
804
797
  // If the value has not changed we do not need to trigger the callback
805
- if (lastConnectionCallbackData.has(mapping.connectionID) && valueToPass === lastValue) {
798
+ if (lastConnectionCallbackData.has(mapping.subscriptionID) && valueToPass === lastValue) {
806
799
  return;
807
800
  }
808
801
  (_b = (_a = mapping).callback) === null || _b === void 0 ? void 0 : _b.call(_a, valueToPass, matchedKey);
@@ -817,7 +810,7 @@ function addKeyToRecentlyAccessedIfNeeded(mapping) {
817
810
  }
818
811
  // Try to free some cache whenever we connect to a safe eviction key
819
812
  OnyxCache_1.default.removeLeastRecentlyUsedKeys();
820
- if ('withOnyxInstance' in mapping && mapping.withOnyxInstance && !isCollectionKey(mapping.key)) {
813
+ if (utils_1.default.hasWithOnyxInstance(mapping) && !isCollectionKey(mapping.key)) {
821
814
  // All React components subscribing to a key flagged as a safe eviction key must implement the canEvict property.
822
815
  if (mapping.canEvict === undefined) {
823
816
  throw new Error(`Cannot subscribe to safe eviction key '${mapping.key}' without providing a canEvict value.`);
@@ -994,7 +987,7 @@ function isValidNonEmptyCollectionForMerge(collection) {
994
987
  function doAllCollectionItemsBelongToSameParent(collectionKey, collectionKeys) {
995
988
  let hasCollectionKeyCheckFailed = false;
996
989
  collectionKeys.forEach((dataKey) => {
997
- if (OnyxUtils.isKeyMatch(collectionKey, dataKey)) {
990
+ if (isKeyMatch(collectionKey, dataKey)) {
998
991
  return;
999
992
  }
1000
993
  if (process.env.NODE_ENV === 'development') {
@@ -1005,12 +998,116 @@ function doAllCollectionItemsBelongToSameParent(collectionKey, collectionKeys) {
1005
998
  });
1006
999
  return !hasCollectionKeyCheckFailed;
1007
1000
  }
1001
+ /**
1002
+ * Subscribes to an Onyx key and listens to its changes.
1003
+ *
1004
+ * @param connectOptions The options object that will define the behavior of the connection.
1005
+ * @returns The subscription ID to use when calling `OnyxUtils.unsubscribeFromKey()`.
1006
+ */
1007
+ function subscribeToKey(connectOptions) {
1008
+ const mapping = connectOptions;
1009
+ const subscriptionID = lastSubscriptionID++;
1010
+ callbackToStateMapping[subscriptionID] = mapping;
1011
+ callbackToStateMapping[subscriptionID].subscriptionID = subscriptionID;
1012
+ // 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
1013
+ // to avoid having to loop through all the Subscribers all the time (even when just one connection belongs to one key),
1014
+ // We create a mapping from key to lists of subscriptionIDs to access the specific list of subscriptionIDs.
1015
+ storeKeyBySubscriptions(mapping.key, callbackToStateMapping[subscriptionID].subscriptionID);
1016
+ if (mapping.initWithStoredValues === false) {
1017
+ return subscriptionID;
1018
+ }
1019
+ // Commit connection only after init passes
1020
+ deferredInitTask.promise
1021
+ .then(() => addKeyToRecentlyAccessedIfNeeded(mapping))
1022
+ .then(() => {
1023
+ // Performance improvement
1024
+ // If the mapping is connected to an onyx key that is not a collection
1025
+ // we can skip the call to getAllKeys() and return an array with a single item
1026
+ if (Boolean(mapping.key) && typeof mapping.key === 'string' && !mapping.key.endsWith('_') && OnyxCache_1.default.getAllKeys().has(mapping.key)) {
1027
+ return new Set([mapping.key]);
1028
+ }
1029
+ return getAllKeys();
1030
+ })
1031
+ .then((keys) => {
1032
+ // We search all the keys in storage to see if any are a "match" for the subscriber we are connecting so that we
1033
+ // can send data back to the subscriber. Note that multiple keys can match as a subscriber could either be
1034
+ // subscribed to a "collection key" or a single key.
1035
+ const matchingKeys = [];
1036
+ keys.forEach((key) => {
1037
+ if (!isKeyMatch(mapping.key, key)) {
1038
+ return;
1039
+ }
1040
+ matchingKeys.push(key);
1041
+ });
1042
+ // If the key being connected to does not exist we initialize the value with null. For subscribers that connected
1043
+ // directly via connect() they will simply get a null value sent to them without any information about which key matched
1044
+ // since there are none matched. In withOnyx() we wait for all connected keys to return a value before rendering the child
1045
+ // component. This null value will be filtered out so that the connected component can utilize defaultProps.
1046
+ if (matchingKeys.length === 0) {
1047
+ if (mapping.key && !isCollectionKey(mapping.key)) {
1048
+ OnyxCache_1.default.addNullishStorageKey(mapping.key);
1049
+ }
1050
+ // Here we cannot use batching because the nullish value is expected to be set immediately for default props
1051
+ // or they will be undefined.
1052
+ sendDataToConnection(mapping, null, undefined, false);
1053
+ return;
1054
+ }
1055
+ // When using a callback subscriber we will either trigger the provided callback for each key we find or combine all values
1056
+ // into an object and just make a single call. The latter behavior is enabled by providing a waitForCollectionCallback key
1057
+ // combined with a subscription to a collection key.
1058
+ if (typeof mapping.callback === 'function') {
1059
+ if (isCollectionKey(mapping.key)) {
1060
+ if (mapping.waitForCollectionCallback) {
1061
+ getCollectionDataAndSendAsObject(matchingKeys, mapping);
1062
+ return;
1063
+ }
1064
+ // We did not opt into using waitForCollectionCallback mode so the callback is called for every matching key.
1065
+ multiGet(matchingKeys).then((values) => {
1066
+ values.forEach((val, key) => {
1067
+ sendDataToConnection(mapping, val, key, true);
1068
+ });
1069
+ });
1070
+ return;
1071
+ }
1072
+ // If we are not subscribed to a collection key then there's only a single key to send an update for.
1073
+ get(mapping.key).then((val) => sendDataToConnection(mapping, val, mapping.key, true));
1074
+ return;
1075
+ }
1076
+ // If we have a withOnyxInstance that means a React component has subscribed via the withOnyx() HOC and we need to
1077
+ // group collection key member data into an object.
1078
+ if (utils_1.default.hasWithOnyxInstance(mapping)) {
1079
+ if (isCollectionKey(mapping.key)) {
1080
+ getCollectionDataAndSendAsObject(matchingKeys, mapping);
1081
+ return;
1082
+ }
1083
+ // If the subscriber is not using a collection key then we just send a single value back to the subscriber
1084
+ get(mapping.key).then((val) => sendDataToConnection(mapping, val, mapping.key, true));
1085
+ return;
1086
+ }
1087
+ console.error('Warning: Onyx.connect() was found without a callback or withOnyxInstance');
1088
+ });
1089
+ // 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
1090
+ // by calling OnyxUtils.unsubscribeFromKey(subscriptionID).
1091
+ return subscriptionID;
1092
+ }
1093
+ /**
1094
+ * Disconnects and removes the listener from the Onyx key.
1095
+ *
1096
+ * @param subscriptionID Subscription ID returned by calling `OnyxUtils.subscribeToKey()`.
1097
+ */
1098
+ function unsubscribeFromKey(subscriptionID) {
1099
+ if (!callbackToStateMapping[subscriptionID]) {
1100
+ return;
1101
+ }
1102
+ deleteKeyBySubscriptions(lastSubscriptionID);
1103
+ delete callbackToStateMapping[subscriptionID];
1104
+ }
1008
1105
  const OnyxUtils = {
1009
1106
  METHOD,
1010
1107
  getMergeQueue,
1011
1108
  getMergeQueuePromise,
1012
- getCallbackToStateMapping,
1013
1109
  getDefaultKeyStates,
1110
+ getDeferredInitTask,
1014
1111
  initStoreValues,
1015
1112
  sendActionToDevTools,
1016
1113
  maybeFlushBatchUpdates,
@@ -1026,14 +1123,11 @@ const OnyxUtils = {
1026
1123
  tryGetCachedValue,
1027
1124
  removeLastAccessedKey,
1028
1125
  addLastAccessedKey,
1029
- removeFromEvictionBlockList,
1030
- addToEvictionBlockList,
1031
1126
  addAllSafeEvictionKeysToRecentlyAccessedList,
1032
1127
  getCachedCollection,
1033
1128
  keysChanged,
1034
1129
  keyChanged,
1035
1130
  sendDataToConnection,
1036
- addKeyToRecentlyAccessedIfNeeded,
1037
1131
  getCollectionKey,
1038
1132
  getCollectionDataAndSendAsObject,
1039
1133
  scheduleSubscriberUpdate,
@@ -1047,11 +1141,12 @@ const OnyxUtils = {
1047
1141
  prepareKeyValuePairsForStorage,
1048
1142
  applyMerge,
1049
1143
  initializeWithDefaultKeyStates,
1050
- storeKeyByConnections,
1051
- deleteKeyByConnections,
1052
1144
  getSnapshotKey,
1053
1145
  multiGet,
1054
1146
  isValidNonEmptyCollectionForMerge,
1055
1147
  doAllCollectionItemsBelongToSameParent,
1148
+ subscribeToKey,
1149
+ unsubscribeFromKey,
1150
+ getEvictionBlocklist,
1056
1151
  };
1057
1152
  exports.default = OnyxUtils;
package/dist/Str.d.ts CHANGED
@@ -14,4 +14,8 @@ declare function startsWith(haystack: string, needle: string): boolean;
14
14
  */
15
15
  declare function result(parameter: string): string;
16
16
  declare function result<TFunction extends (...a: TArgs) => unknown, TArgs extends unknown[]>(parameter: TFunction, ...args: TArgs): ReturnType<TFunction>;
17
- export { startsWith, result };
17
+ /**
18
+ * A simple GUID generator taken from https://stackoverflow.com/a/32760401/9114791
19
+ */
20
+ declare function guid(): string;
21
+ export { guid, result, startsWith };
package/dist/Str.js CHANGED
@@ -1,6 +1,6 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.result = exports.startsWith = void 0;
3
+ exports.startsWith = exports.result = exports.guid = void 0;
4
4
  /**
5
5
  * Returns true if the haystack begins with the needle
6
6
  *
@@ -16,3 +16,15 @@ function result(parameter, ...args) {
16
16
  return typeof parameter === 'function' ? parameter(...args) : parameter;
17
17
  }
18
18
  exports.result = result;
19
+ /**
20
+ * A simple GUID generator taken from https://stackoverflow.com/a/32760401/9114791
21
+ */
22
+ function guid() {
23
+ function s4() {
24
+ return Math.floor((1 + Math.random()) * 0x10000)
25
+ .toString(16)
26
+ .substring(1);
27
+ }
28
+ return `${s4()}${s4()}-${s4()}-${s4()}-${s4()}-${s4()}${s4()}${s4()}`;
29
+ }
30
+ exports.guid = guid;
@@ -8,4 +8,4 @@ type DeferredTask = {
8
8
  * Useful when we want to wait for a tasks that is resolved from an external action
9
9
  */
10
10
  export default function createDeferredTask(): DeferredTask;
11
- export {};
11
+ export type { DeferredTask };
package/dist/index.d.ts CHANGED
@@ -2,9 +2,10 @@ import type { ConnectOptions, OnyxUpdate } from './Onyx';
2
2
  import Onyx from './Onyx';
3
3
  import type { CustomTypeOptions, KeyValueMapping, NullishDeep, OnyxCollection, OnyxEntry, OnyxKey, OnyxValue, Selector, OnyxInputValue, OnyxCollectionInputValue, OnyxInput, OnyxSetInput, OnyxMultiSetInput, OnyxMergeInput, OnyxMergeCollectionInput } from './types';
4
4
  import type { FetchStatus, ResultMetadata, UseOnyxResult } from './useOnyx';
5
+ import type { Connection } from './OnyxConnectionManager';
5
6
  import useOnyx from './useOnyx';
6
7
  import withOnyx from './withOnyx';
7
8
  import type { WithOnyxState } from './withOnyx/types';
8
9
  export default Onyx;
9
10
  export { useOnyx, withOnyx };
10
- export type { ConnectOptions, CustomTypeOptions, FetchStatus, KeyValueMapping, NullishDeep, OnyxCollection, OnyxEntry, OnyxKey, OnyxInputValue, OnyxCollectionInputValue, OnyxInput, OnyxSetInput, OnyxMultiSetInput, OnyxMergeInput, OnyxMergeCollectionInput, OnyxUpdate, OnyxValue, ResultMetadata, Selector, UseOnyxResult, WithOnyxState, };
11
+ export type { ConnectOptions, CustomTypeOptions, FetchStatus, KeyValueMapping, NullishDeep, OnyxCollection, OnyxEntry, OnyxKey, OnyxInputValue, OnyxCollectionInputValue, OnyxInput, OnyxSetInput, OnyxMultiSetInput, OnyxMergeInput, OnyxMergeCollectionInput, OnyxUpdate, OnyxValue, ResultMetadata, Selector, UseOnyxResult, WithOnyxState, Connection, };