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.
package/API.md CHANGED
@@ -8,11 +8,11 @@
8
8
  <dt><a href="#init">init()</a></dt>
9
9
  <dd><p>Initialize the store with actions and listening for storage events</p>
10
10
  </dd>
11
- <dt><a href="#connect">connect(mapping)</a> ⇒</dt>
12
- <dd><p>Subscribes a react component&#39;s state directly to a store key</p>
11
+ <dt><a href="#connect">connect(connectOptions)</a> ⇒</dt>
12
+ <dd><p>Connects to an Onyx key given the options passed and listens to its changes.</p>
13
13
  </dd>
14
- <dt><a href="#disconnect">disconnect(connectionID)</a></dt>
15
- <dd><p>Remove the listener for a react component</p>
14
+ <dt><a href="#disconnect">disconnect(connection)</a></dt>
15
+ <dd><p>Disconnects and removes the listener from the Onyx key.</p>
16
16
  </dd>
17
17
  <dt><a href="#set">set(key, value)</a></dt>
18
18
  <dd><p>Write a value to our store with the given key</p>
@@ -60,45 +60,49 @@ Initialize the store with actions and listening for storage events
60
60
  **Kind**: global function
61
61
  <a name="connect"></a>
62
62
 
63
- ## connect(mapping) ⇒
64
- Subscribes a react component's state directly to a store key
63
+ ## connect(connectOptions) ⇒
64
+ Connects to an Onyx key given the options passed and listens to its changes.
65
65
 
66
66
  **Kind**: global function
67
- **Returns**: an ID to use when calling disconnect
67
+ **Returns**: The connection object to use when calling `Onyx.disconnect()`.
68
68
 
69
69
  | Param | Description |
70
70
  | --- | --- |
71
- | mapping | the mapping information to connect Onyx to the components state |
72
- | mapping.key | ONYXKEY to subscribe to |
73
- | [mapping.statePropertyName] | the name of the property in the state to connect the data to |
74
- | [mapping.withOnyxInstance] | whose setState() method will be called with any changed data This is used by React components to connect to Onyx |
75
- | [mapping.callback] | a method that will be called with changed data This is used by any non-React code to connect to Onyx |
76
- | [mapping.initWithStoredValues] | If set to false, then no data will be prefilled into the component |
77
- | [mapping.waitForCollectionCallback] | If set to true, it will return the entire collection to the callback as a single object |
78
- | [mapping.selector] | THIS PARAM IS ONLY USED WITH withOnyx(). If included, this will be used to subscribe to a subset of an Onyx key's data. The sourceData and withOnyx state are passed to the selector and should return the simplified data. Using this setting on `withOnyx` can have very positive performance benefits because the component will only re-render when the subset of data changes. Otherwise, any change of data on any property would normally cause the component to re-render (and that can be expensive from a performance standpoint). |
79
- | [mapping.initialValue] | THIS PARAM IS ONLY USED WITH withOnyx(). If included, this will be passed to the component so that something can be rendered while data is being fetched from the DB. Note that it will not cause the component to have the loading prop set to true. |
71
+ | connectOptions | The options object that will define the behavior of the connection. |
72
+ | connectOptions.key | The Onyx key to subscribe to. |
73
+ | connectOptions.callback | A function that will be called when the Onyx data we are subscribed changes. |
74
+ | connectOptions.waitForCollectionCallback | If set to `true`, it will return the entire collection to the callback as a single object. |
75
+ | connectOptions.withOnyxInstance | The `withOnyx` class instance to be internally passed. **Only used inside `withOnyx()` HOC.** |
76
+ | connectOptions.statePropertyName | The name of the component's prop that is connected to the Onyx key. **Only used inside `withOnyx()` HOC.** |
77
+ | connectOptions.displayName | The component's display name. **Only used inside `withOnyx()` HOC.** |
78
+ | connectOptions.selector | This will be used to subscribe to a subset of an Onyx key's data. **Only used inside `useOnyx()` hook or `withOnyx()` HOC.** Using this setting on `useOnyx()` or `withOnyx()` can have very positive performance benefits because the component will only re-render when the subset of data changes. Otherwise, any change of data on any property would normally cause the component to re-render (and that can be expensive from a performance standpoint). |
80
79
 
81
80
  **Example**
82
- ```js
83
- const connectionID = Onyx.connect({
81
+ ```ts
82
+ const connection = Onyx.connect({
84
83
  key: ONYXKEYS.SESSION,
85
84
  callback: onSessionChange,
86
85
  });
87
86
  ```
88
87
  <a name="disconnect"></a>
89
88
 
90
- ## disconnect(connectionID)
91
- Remove the listener for a react component
89
+ ## disconnect(connection)
90
+ Disconnects and removes the listener from the Onyx key.
92
91
 
93
92
  **Kind**: global function
94
93
 
95
94
  | Param | Description |
96
95
  | --- | --- |
97
- | connectionID | unique id returned by call to Onyx.connect() |
96
+ | connection | Connection object returned by calling `Onyx.connect()`. |
98
97
 
99
98
  **Example**
100
- ```js
101
- Onyx.disconnect(connectionID);
99
+ ```ts
100
+ const connection = Onyx.connect({
101
+ key: ONYXKEYS.SESSION,
102
+ callback: onSessionChange,
103
+ });
104
+
105
+ Onyx.disconnect(connection);
102
106
  ```
103
107
  <a name="set"></a>
104
108
 
package/dist/Onyx.d.ts CHANGED
@@ -1,44 +1,49 @@
1
1
  import * as Logger from './Logger';
2
2
  import type { CollectionKeyBase, ConnectOptions, InitOptions, Mapping, OnyxKey, OnyxMergeCollectionInput, OnyxMergeInput, OnyxMultiSetInput, OnyxSetInput, OnyxUpdate } from './types';
3
+ import type { Connection } from './OnyxConnectionManager';
3
4
  /** Initialize the store with actions and listening for storage events */
4
5
  declare function init({ keys, initialKeyStates, safeEvictionKeys, maxCachedKeysCount, shouldSyncMultipleInstances, debugSetState, }: InitOptions): void;
5
6
  /**
6
- * Subscribes a react component's state directly to a store key
7
+ * Connects to an Onyx key given the options passed and listens to its changes.
7
8
  *
8
9
  * @example
9
- * const connectionID = Onyx.connect({
10
+ * ```ts
11
+ * const connection = Onyx.connect({
10
12
  * key: ONYXKEYS.SESSION,
11
13
  * callback: onSessionChange,
12
14
  * });
13
- *
14
- * @param mapping the mapping information to connect Onyx to the components state
15
- * @param mapping.key ONYXKEY to subscribe to
16
- * @param [mapping.statePropertyName] the name of the property in the state to connect the data to
17
- * @param [mapping.withOnyxInstance] whose setState() method will be called with any changed data
18
- * This is used by React components to connect to Onyx
19
- * @param [mapping.callback] a method that will be called with changed data
20
- * This is used by any non-React code to connect to Onyx
21
- * @param [mapping.initWithStoredValues] If set to false, then no data will be prefilled into the
22
- * component. Default is true.
23
- * @param [mapping.waitForCollectionCallback] If set to true, it will return the entire collection to the callback as a single object
24
- * @param [mapping.selector] THIS PARAM IS ONLY USED WITH withOnyx(). If included, this will be used to subscribe to a subset of an Onyx key's data.
25
- * The sourceData and withOnyx state are passed to the selector and should return the simplified data. Using this setting on `withOnyx` can have very positive
26
- * performance benefits because the component will only re-render when the subset of data changes. Otherwise, any change of data on any property would normally
27
- * cause the component to re-render (and that can be expensive from a performance standpoint).
28
- * @param [mapping.initialValue] THIS PARAM IS ONLY USED WITH withOnyx().
29
- * If included, this will be passed to the component so that something can be rendered while data is being fetched from the DB.
30
- * Note that it will not cause the component to have the loading prop set to true.
31
- * @returns an ID to use when calling disconnect
15
+ * ```
16
+ *
17
+ * @param connectOptions The options object that will define the behavior of the connection.
18
+ * @param connectOptions.key The Onyx key to subscribe to.
19
+ * @param connectOptions.callback A function that will be called when the Onyx data we are subscribed changes.
20
+ * @param connectOptions.waitForCollectionCallback If set to `true`, it will return the entire collection to the callback as a single object.
21
+ * @param connectOptions.withOnyxInstance The `withOnyx` class instance to be internally passed. **Only used inside `withOnyx()` HOC.**
22
+ * @param connectOptions.statePropertyName The name of the component's prop that is connected to the Onyx key. **Only used inside `withOnyx()` HOC.**
23
+ * @param connectOptions.displayName The component's display name. **Only used inside `withOnyx()` HOC.**
24
+ * @param connectOptions.selector This will be used to subscribe to a subset of an Onyx key's data. **Only used inside `useOnyx()` hook or `withOnyx()` HOC.**
25
+ * Using this setting on `useOnyx()` or `withOnyx()` can have very positive performance benefits because the component will only re-render
26
+ * when the subset of data changes. Otherwise, any change of data on any property would normally
27
+ * cause the component to re-render (and that can be expensive from a performance standpoint).
28
+ * @returns The connection object to use when calling `Onyx.disconnect()`.
32
29
  */
33
- declare function connect<TKey extends OnyxKey>(connectOptions: ConnectOptions<TKey>): number;
30
+ declare function connect<TKey extends OnyxKey>(connectOptions: ConnectOptions<TKey>): Connection;
34
31
  /**
35
- * Remove the listener for a react component
32
+ * Disconnects and removes the listener from the Onyx key.
33
+ *
36
34
  * @example
37
- * Onyx.disconnect(connectionID);
35
+ * ```ts
36
+ * const connection = Onyx.connect({
37
+ * key: ONYXKEYS.SESSION,
38
+ * callback: onSessionChange,
39
+ * });
40
+ *
41
+ * Onyx.disconnect(connection);
42
+ * ```
38
43
  *
39
- * @param connectionID unique id returned by call to Onyx.connect()
44
+ * @param connection Connection object returned by calling `Onyx.connect()`.
40
45
  */
41
- declare function disconnect(connectionID: number, keyToRemoveFromEvictionBlocklist?: OnyxKey): void;
46
+ declare function disconnect(connection: Connection): void;
42
47
  /**
43
48
  * Write a value to our store with the given key
44
49
  *
package/dist/Onyx.js CHANGED
@@ -31,17 +31,13 @@ const underscore_1 = __importDefault(require("underscore"));
31
31
  const pick_1 = __importDefault(require("lodash/pick"));
32
32
  const Logger = __importStar(require("./Logger"));
33
33
  const OnyxCache_1 = __importDefault(require("./OnyxCache"));
34
- const createDeferredTask_1 = __importDefault(require("./createDeferredTask"));
35
34
  const PerformanceUtils = __importStar(require("./PerformanceUtils"));
36
35
  const storage_1 = __importDefault(require("./storage"));
37
36
  const utils_1 = __importDefault(require("./utils"));
38
37
  const DevTools_1 = __importDefault(require("./DevTools"));
39
38
  const OnyxUtils_1 = __importDefault(require("./OnyxUtils"));
40
39
  const logMessages_1 = __importDefault(require("./logMessages"));
41
- // Keeps track of the last connectionID that was used so we can keep incrementing it
42
- let lastConnectionID = 0;
43
- // Connections can be made before `Onyx.init`. They would wait for this task before resolving
44
- const deferredInitTask = (0, createDeferredTask_1.default)();
40
+ const OnyxConnectionManager_1 = __importDefault(require("./OnyxConnectionManager"));
45
41
  /** Initialize the store with actions and listening for storage events */
46
42
  function init({ keys = {}, initialKeyStates = {}, safeEvictionKeys = [], maxCachedKeysCount = 1000, shouldSyncMultipleInstances = Boolean(global.localStorage), debugSetState = false, }) {
47
43
  var _a;
@@ -61,142 +57,52 @@ function init({ keys = {}, initialKeyStates = {}, safeEvictionKeys = [], maxCach
61
57
  }
62
58
  OnyxUtils_1.default.initStoreValues(keys, initialKeyStates, safeEvictionKeys);
63
59
  // Initialize all of our keys with data provided then give green light to any pending connections
64
- Promise.all([OnyxUtils_1.default.addAllSafeEvictionKeysToRecentlyAccessedList(), OnyxUtils_1.default.initializeWithDefaultKeyStates()]).then(deferredInitTask.resolve);
60
+ Promise.all([OnyxUtils_1.default.addAllSafeEvictionKeysToRecentlyAccessedList(), OnyxUtils_1.default.initializeWithDefaultKeyStates()]).then(OnyxUtils_1.default.getDeferredInitTask().resolve);
65
61
  }
66
62
  /**
67
- * Subscribes a react component's state directly to a store key
63
+ * Connects to an Onyx key given the options passed and listens to its changes.
68
64
  *
69
65
  * @example
70
- * const connectionID = Onyx.connect({
66
+ * ```ts
67
+ * const connection = Onyx.connect({
71
68
  * key: ONYXKEYS.SESSION,
72
69
  * callback: onSessionChange,
73
70
  * });
71
+ * ```
74
72
  *
75
- * @param mapping the mapping information to connect Onyx to the components state
76
- * @param mapping.key ONYXKEY to subscribe to
77
- * @param [mapping.statePropertyName] the name of the property in the state to connect the data to
78
- * @param [mapping.withOnyxInstance] whose setState() method will be called with any changed data
79
- * This is used by React components to connect to Onyx
80
- * @param [mapping.callback] a method that will be called with changed data
81
- * This is used by any non-React code to connect to Onyx
82
- * @param [mapping.initWithStoredValues] If set to false, then no data will be prefilled into the
83
- * component. Default is true.
84
- * @param [mapping.waitForCollectionCallback] If set to true, it will return the entire collection to the callback as a single object
85
- * @param [mapping.selector] THIS PARAM IS ONLY USED WITH withOnyx(). If included, this will be used to subscribe to a subset of an Onyx key's data.
86
- * The sourceData and withOnyx state are passed to the selector and should return the simplified data. Using this setting on `withOnyx` can have very positive
87
- * performance benefits because the component will only re-render when the subset of data changes. Otherwise, any change of data on any property would normally
88
- * cause the component to re-render (and that can be expensive from a performance standpoint).
89
- * @param [mapping.initialValue] THIS PARAM IS ONLY USED WITH withOnyx().
90
- * If included, this will be passed to the component so that something can be rendered while data is being fetched from the DB.
91
- * Note that it will not cause the component to have the loading prop set to true.
92
- * @returns an ID to use when calling disconnect
73
+ * @param connectOptions The options object that will define the behavior of the connection.
74
+ * @param connectOptions.key The Onyx key to subscribe to.
75
+ * @param connectOptions.callback A function that will be called when the Onyx data we are subscribed changes.
76
+ * @param connectOptions.waitForCollectionCallback If set to `true`, it will return the entire collection to the callback as a single object.
77
+ * @param connectOptions.withOnyxInstance The `withOnyx` class instance to be internally passed. **Only used inside `withOnyx()` HOC.**
78
+ * @param connectOptions.statePropertyName The name of the component's prop that is connected to the Onyx key. **Only used inside `withOnyx()` HOC.**
79
+ * @param connectOptions.displayName The component's display name. **Only used inside `withOnyx()` HOC.**
80
+ * @param connectOptions.selector This will be used to subscribe to a subset of an Onyx key's data. **Only used inside `useOnyx()` hook or `withOnyx()` HOC.**
81
+ * Using this setting on `useOnyx()` or `withOnyx()` can have very positive performance benefits because the component will only re-render
82
+ * when the subset of data changes. Otherwise, any change of data on any property would normally
83
+ * cause the component to re-render (and that can be expensive from a performance standpoint).
84
+ * @returns The connection object to use when calling `Onyx.disconnect()`.
93
85
  */
94
86
  function connect(connectOptions) {
95
- const mapping = connectOptions;
96
- const connectionID = lastConnectionID++;
97
- const callbackToStateMapping = OnyxUtils_1.default.getCallbackToStateMapping();
98
- callbackToStateMapping[connectionID] = mapping;
99
- callbackToStateMapping[connectionID].connectionID = connectionID;
100
- // 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 connectionID
101
- // to avoid having to loop through all the Subscribers all the time (even when just one connection belongs to one key),
102
- // We create a mapping from key to lists of connectionIDs to access the specific list of connectionIDs.
103
- OnyxUtils_1.default.storeKeyByConnections(mapping.key, callbackToStateMapping[connectionID].connectionID);
104
- if (mapping.initWithStoredValues === false) {
105
- return connectionID;
106
- }
107
- // Commit connection only after init passes
108
- deferredInitTask.promise
109
- .then(() => OnyxUtils_1.default.addKeyToRecentlyAccessedIfNeeded(mapping))
110
- .then(() => {
111
- // Performance improvement
112
- // If the mapping is connected to an onyx key that is not a collection
113
- // we can skip the call to getAllKeys() and return an array with a single item
114
- if (Boolean(mapping.key) && typeof mapping.key === 'string' && !mapping.key.endsWith('_') && OnyxCache_1.default.getAllKeys().has(mapping.key)) {
115
- return new Set([mapping.key]);
116
- }
117
- return OnyxUtils_1.default.getAllKeys();
118
- })
119
- .then((keys) => {
120
- // We search all the keys in storage to see if any are a "match" for the subscriber we are connecting so that we
121
- // can send data back to the subscriber. Note that multiple keys can match as a subscriber could either be
122
- // subscribed to a "collection key" or a single key.
123
- const matchingKeys = [];
124
- keys.forEach((key) => {
125
- if (!OnyxUtils_1.default.isKeyMatch(mapping.key, key)) {
126
- return;
127
- }
128
- matchingKeys.push(key);
129
- });
130
- // If the key being connected to does not exist we initialize the value with null. For subscribers that connected
131
- // directly via connect() they will simply get a null value sent to them without any information about which key matched
132
- // since there are none matched. In withOnyx() we wait for all connected keys to return a value before rendering the child
133
- // component. This null value will be filtered out so that the connected component can utilize defaultProps.
134
- if (matchingKeys.length === 0) {
135
- if (mapping.key && !OnyxUtils_1.default.isCollectionKey(mapping.key)) {
136
- OnyxCache_1.default.addNullishStorageKey(mapping.key);
137
- }
138
- // Here we cannot use batching because the nullish value is expected to be set immediately for default props
139
- // or they will be undefined.
140
- OnyxUtils_1.default.sendDataToConnection(mapping, null, undefined, false);
141
- return;
142
- }
143
- // When using a callback subscriber we will either trigger the provided callback for each key we find or combine all values
144
- // into an object and just make a single call. The latter behavior is enabled by providing a waitForCollectionCallback key
145
- // combined with a subscription to a collection key.
146
- if (typeof mapping.callback === 'function') {
147
- if (OnyxUtils_1.default.isCollectionKey(mapping.key)) {
148
- if (mapping.waitForCollectionCallback) {
149
- OnyxUtils_1.default.getCollectionDataAndSendAsObject(matchingKeys, mapping);
150
- return;
151
- }
152
- // We did not opt into using waitForCollectionCallback mode so the callback is called for every matching key.
153
- OnyxUtils_1.default.multiGet(matchingKeys).then((values) => {
154
- values.forEach((val, key) => {
155
- OnyxUtils_1.default.sendDataToConnection(mapping, val, key, true);
156
- });
157
- });
158
- return;
159
- }
160
- // If we are not subscribed to a collection key then there's only a single key to send an update for.
161
- OnyxUtils_1.default.get(mapping.key).then((val) => OnyxUtils_1.default.sendDataToConnection(mapping, val, mapping.key, true));
162
- return;
163
- }
164
- // If we have a withOnyxInstance that means a React component has subscribed via the withOnyx() HOC and we need to
165
- // group collection key member data into an object.
166
- if ('withOnyxInstance' in mapping && mapping.withOnyxInstance) {
167
- if (OnyxUtils_1.default.isCollectionKey(mapping.key)) {
168
- OnyxUtils_1.default.getCollectionDataAndSendAsObject(matchingKeys, mapping);
169
- return;
170
- }
171
- // If the subscriber is not using a collection key then we just send a single value back to the subscriber
172
- OnyxUtils_1.default.get(mapping.key).then((val) => OnyxUtils_1.default.sendDataToConnection(mapping, val, mapping.key, true));
173
- return;
174
- }
175
- console.error('Warning: Onyx.connect() was found without a callback or withOnyxInstance');
176
- });
177
- // The connectionID is returned back to the caller so that it can be used to clean up the connection when it's no longer needed
178
- // by calling Onyx.disconnect(connectionID).
179
- return connectionID;
87
+ return OnyxConnectionManager_1.default.connect(connectOptions);
180
88
  }
181
89
  /**
182
- * Remove the listener for a react component
90
+ * Disconnects and removes the listener from the Onyx key.
91
+ *
183
92
  * @example
184
- * Onyx.disconnect(connectionID);
93
+ * ```ts
94
+ * const connection = Onyx.connect({
95
+ * key: ONYXKEYS.SESSION,
96
+ * callback: onSessionChange,
97
+ * });
98
+ *
99
+ * Onyx.disconnect(connection);
100
+ * ```
185
101
  *
186
- * @param connectionID unique id returned by call to Onyx.connect()
102
+ * @param connection Connection object returned by calling `Onyx.connect()`.
187
103
  */
188
- function disconnect(connectionID, keyToRemoveFromEvictionBlocklist) {
189
- const callbackToStateMapping = OnyxUtils_1.default.getCallbackToStateMapping();
190
- if (!callbackToStateMapping[connectionID]) {
191
- return;
192
- }
193
- // Remove this key from the eviction block list as we are no longer
194
- // subscribing to it and it should be safe to delete again
195
- if (keyToRemoveFromEvictionBlocklist) {
196
- OnyxUtils_1.default.removeFromEvictionBlockList(keyToRemoveFromEvictionBlocklist, connectionID);
197
- }
198
- OnyxUtils_1.default.deleteKeyByConnections(lastConnectionID);
199
- delete callbackToStateMapping[connectionID];
104
+ function disconnect(connection) {
105
+ OnyxConnectionManager_1.default.disconnect(connection);
200
106
  }
201
107
  /**
202
108
  * Write a value to our store with the given key
@@ -510,8 +416,8 @@ function clear(keysToPreserve = []) {
510
416
  const newValue = (_a = defaultKeyStates[key]) !== null && _a !== void 0 ? _a : null;
511
417
  if (newValue !== oldValue) {
512
418
  OnyxCache_1.default.set(key, newValue);
513
- const collectionKey = key.substring(0, key.indexOf('_') + 1);
514
- if (collectionKey) {
419
+ const collectionKey = OnyxUtils_1.default.getCollectionKey(key);
420
+ if (OnyxUtils_1.default.isCollectionKey(collectionKey)) {
515
421
  if (!keyValuesToResetAsCollection[collectionKey]) {
516
422
  keyValuesToResetAsCollection[collectionKey] = {};
517
423
  }
@@ -0,0 +1,69 @@
1
+ import type { ConnectOptions } from './Onyx';
2
+ import type { OnyxKey } from './types';
3
+ /**
4
+ * Represents the connection object returned by `Onyx.connect()`.
5
+ */
6
+ type Connection = {
7
+ /**
8
+ * The ID used to identify this particular connection.
9
+ */
10
+ id: string;
11
+ /**
12
+ * The ID of the subscriber's callback that is associated to this connection.
13
+ */
14
+ callbackID: string;
15
+ };
16
+ /**
17
+ * Manages Onyx connections of `Onyx.connect()`, `useOnyx()` and `withOnyx()` subscribers.
18
+ */
19
+ declare class OnyxConnectionManager {
20
+ /**
21
+ * A map where the key is the connection ID generated inside `connect()` and the value is the metadata of that connection.
22
+ */
23
+ private connectionsMap;
24
+ /**
25
+ * Stores the last generated callback ID which will be incremented when making a new connection.
26
+ */
27
+ private lastCallbackID;
28
+ constructor();
29
+ /**
30
+ * Generates a connection ID based on the `connectOptions` object passed to the function.
31
+ *
32
+ * The properties used to generate the ID are handpicked for performance reasons and
33
+ * according to their purpose and effect they produce in the Onyx connection.
34
+ */
35
+ private generateConnectionID;
36
+ /**
37
+ * Fires all the subscribers callbacks associated with that connection ID.
38
+ */
39
+ private fireCallbacks;
40
+ /**
41
+ * Connects to an Onyx key given the options passed and listens to its changes.
42
+ *
43
+ * @param connectOptions The options object that will define the behavior of the connection.
44
+ * @returns The connection object to use when calling `disconnect()`.
45
+ */
46
+ connect<TKey extends OnyxKey>(connectOptions: ConnectOptions<TKey>): Connection;
47
+ /**
48
+ * Disconnects and removes the listener from the Onyx key.
49
+ *
50
+ * @param connection Connection object returned by calling `connect()`.
51
+ */
52
+ disconnect(connection: Connection): void;
53
+ /**
54
+ * Disconnect all subscribers from Onyx.
55
+ */
56
+ disconnectAll(): void;
57
+ /**
58
+ * Adds the connection to the eviction block list. Connections added to this list can never be evicted.
59
+ * */
60
+ addToEvictionBlockList(connection: Connection): void;
61
+ /**
62
+ * Removes a connection previously added to this list
63
+ * which will enable it to be evicted again.
64
+ */
65
+ removeFromEvictionBlockList(connection: Connection): void;
66
+ }
67
+ declare const connectionManager: OnyxConnectionManager;
68
+ export default connectionManager;
69
+ export type { Connection };
@@ -0,0 +1,195 @@
1
+ "use strict";
2
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
+ if (k2 === undefined) k2 = k;
4
+ var desc = Object.getOwnPropertyDescriptor(m, k);
5
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
+ desc = { enumerable: true, get: function() { return m[k]; } };
7
+ }
8
+ Object.defineProperty(o, k2, desc);
9
+ }) : (function(o, m, k, k2) {
10
+ if (k2 === undefined) k2 = k;
11
+ o[k2] = m[k];
12
+ }));
13
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
14
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
15
+ }) : function(o, v) {
16
+ o["default"] = v;
17
+ });
18
+ var __importStar = (this && this.__importStar) || function (mod) {
19
+ if (mod && mod.__esModule) return mod;
20
+ var result = {};
21
+ if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k);
22
+ __setModuleDefault(result, mod);
23
+ return result;
24
+ };
25
+ var __importDefault = (this && this.__importDefault) || function (mod) {
26
+ return (mod && mod.__esModule) ? mod : { "default": mod };
27
+ };
28
+ Object.defineProperty(exports, "__esModule", { value: true });
29
+ const bindAll_1 = __importDefault(require("lodash/bindAll"));
30
+ const Logger = __importStar(require("./Logger"));
31
+ const OnyxUtils_1 = __importDefault(require("./OnyxUtils"));
32
+ const Str = __importStar(require("./Str"));
33
+ const utils_1 = __importDefault(require("./utils"));
34
+ /**
35
+ * Manages Onyx connections of `Onyx.connect()`, `useOnyx()` and `withOnyx()` subscribers.
36
+ */
37
+ class OnyxConnectionManager {
38
+ constructor() {
39
+ this.connectionsMap = new Map();
40
+ this.lastCallbackID = 0;
41
+ // Binds all public methods to prevent problems with `this`.
42
+ (0, bindAll_1.default)(this, 'generateConnectionID', 'fireCallbacks', 'connect', 'disconnect', 'disconnectAll', 'addToEvictionBlockList', 'removeFromEvictionBlockList');
43
+ }
44
+ /**
45
+ * Generates a connection ID based on the `connectOptions` object passed to the function.
46
+ *
47
+ * The properties used to generate the ID are handpicked for performance reasons and
48
+ * according to their purpose and effect they produce in the Onyx connection.
49
+ */
50
+ generateConnectionID(connectOptions) {
51
+ var _a, _b;
52
+ let suffix = '';
53
+ // We will generate a unique ID in any of the following situations:
54
+ // - `connectOptions.reuseConnection` is `false`. That means the subscriber explicitly wants the connection to not be reused.
55
+ // - `connectOptions.initWithStoredValues` is `false`. This flag changes the subscription flow when set to `false`, so the connection can't be reused.
56
+ // - `withOnyxInstance` is defined inside `connectOptions`. That means the subscriber is a `withOnyx` HOC and therefore doesn't support connection reuse.
57
+ if (connectOptions.reuseConnection === false || connectOptions.initWithStoredValues === false || utils_1.default.hasWithOnyxInstance(connectOptions)) {
58
+ suffix += `,uniqueID=${Str.guid()}`;
59
+ }
60
+ return `onyxKey=${connectOptions.key},initWithStoredValues=${(_a = connectOptions.initWithStoredValues) !== null && _a !== void 0 ? _a : true},waitForCollectionCallback=${(_b = connectOptions.waitForCollectionCallback) !== null && _b !== void 0 ? _b : false}${suffix}`;
61
+ }
62
+ /**
63
+ * Fires all the subscribers callbacks associated with that connection ID.
64
+ */
65
+ fireCallbacks(connectionID) {
66
+ const connection = this.connectionsMap.get(connectionID);
67
+ connection === null || connection === void 0 ? void 0 : connection.callbacks.forEach((callback) => {
68
+ callback(connection.cachedCallbackValue, connection.cachedCallbackKey);
69
+ });
70
+ }
71
+ /**
72
+ * Connects to an Onyx key given the options passed and listens to its changes.
73
+ *
74
+ * @param connectOptions The options object that will define the behavior of the connection.
75
+ * @returns The connection object to use when calling `disconnect()`.
76
+ */
77
+ connect(connectOptions) {
78
+ const connectionID = this.generateConnectionID(connectOptions);
79
+ let connectionMetadata = this.connectionsMap.get(connectionID);
80
+ let subscriptionID;
81
+ const callbackID = String(this.lastCallbackID++);
82
+ // If there is no connection yet for that connection ID, we create a new one.
83
+ if (!connectionMetadata) {
84
+ let callback;
85
+ // If the subscriber is a `withOnyx` HOC we don't define `callback` as the HOC will use
86
+ // its own logic to handle the data.
87
+ if (!utils_1.default.hasWithOnyxInstance(connectOptions)) {
88
+ callback = (value, key) => {
89
+ const createdConnection = this.connectionsMap.get(connectionID);
90
+ if (createdConnection) {
91
+ // We signal that the first connection was made and now any new subscribers
92
+ // can fire their callbacks immediately with the cached value when connecting.
93
+ createdConnection.isConnectionMade = true;
94
+ createdConnection.cachedCallbackValue = value;
95
+ createdConnection.cachedCallbackKey = key;
96
+ this.fireCallbacks(connectionID);
97
+ }
98
+ };
99
+ }
100
+ subscriptionID = OnyxUtils_1.default.subscribeToKey(Object.assign(Object.assign({}, connectOptions), { callback }));
101
+ connectionMetadata = {
102
+ subscriptionID,
103
+ onyxKey: connectOptions.key,
104
+ isConnectionMade: false,
105
+ callbacks: new Map(),
106
+ };
107
+ this.connectionsMap.set(connectionID, connectionMetadata);
108
+ }
109
+ // We add the subscriber's callback to the list of callbacks associated with this connection.
110
+ if (connectOptions.callback) {
111
+ connectionMetadata.callbacks.set(callbackID, connectOptions.callback);
112
+ }
113
+ // If the first connection is already made we want any new subscribers to receive the cached callback value immediately.
114
+ if (connectionMetadata.isConnectionMade) {
115
+ // Defer the callback execution to the next tick of the event loop.
116
+ // This ensures that the current execution flow completes and the result connection object is available when the callback fires.
117
+ Promise.resolve().then(() => {
118
+ var _a, _b;
119
+ (_b = (_a = connectOptions).callback) === null || _b === void 0 ? void 0 : _b.call(_a, connectionMetadata.cachedCallbackValue, connectionMetadata.cachedCallbackKey);
120
+ });
121
+ }
122
+ return { id: connectionID, callbackID };
123
+ }
124
+ /**
125
+ * Disconnects and removes the listener from the Onyx key.
126
+ *
127
+ * @param connection Connection object returned by calling `connect()`.
128
+ */
129
+ disconnect(connection) {
130
+ if (!connection) {
131
+ Logger.logInfo(`[ConnectionManager] Attempted to disconnect passing an undefined connection object.`);
132
+ return;
133
+ }
134
+ const connectionMetadata = this.connectionsMap.get(connection.id);
135
+ if (!connectionMetadata) {
136
+ Logger.logInfo(`[ConnectionManager] Attempted to disconnect but no connection was found.`);
137
+ return;
138
+ }
139
+ // Removes the callback from the connection's callbacks map.
140
+ connectionMetadata.callbacks.delete(connection.callbackID);
141
+ // If the connection's callbacks map is empty we can safely unsubscribe from the Onyx key.
142
+ if (connectionMetadata.callbacks.size === 0) {
143
+ OnyxUtils_1.default.unsubscribeFromKey(connectionMetadata.subscriptionID);
144
+ this.removeFromEvictionBlockList(connection);
145
+ this.connectionsMap.delete(connection.id);
146
+ }
147
+ }
148
+ /**
149
+ * Disconnect all subscribers from Onyx.
150
+ */
151
+ disconnectAll() {
152
+ this.connectionsMap.forEach((connectionMetadata, connectionID) => {
153
+ OnyxUtils_1.default.unsubscribeFromKey(connectionMetadata.subscriptionID);
154
+ connectionMetadata.callbacks.forEach((_, callbackID) => {
155
+ this.removeFromEvictionBlockList({ id: connectionID, callbackID });
156
+ });
157
+ });
158
+ this.connectionsMap.clear();
159
+ }
160
+ /**
161
+ * Adds the connection to the eviction block list. Connections added to this list can never be evicted.
162
+ * */
163
+ addToEvictionBlockList(connection) {
164
+ var _a;
165
+ const connectionMetadata = this.connectionsMap.get(connection.id);
166
+ if (!connectionMetadata) {
167
+ return;
168
+ }
169
+ const evictionBlocklist = OnyxUtils_1.default.getEvictionBlocklist();
170
+ if (!evictionBlocklist[connectionMetadata.onyxKey]) {
171
+ evictionBlocklist[connectionMetadata.onyxKey] = [];
172
+ }
173
+ (_a = evictionBlocklist[connectionMetadata.onyxKey]) === null || _a === void 0 ? void 0 : _a.push(`${connection.id}_${connection.callbackID}`);
174
+ }
175
+ /**
176
+ * Removes a connection previously added to this list
177
+ * which will enable it to be evicted again.
178
+ */
179
+ removeFromEvictionBlockList(connection) {
180
+ var _a, _b, _c;
181
+ const connectionMetadata = this.connectionsMap.get(connection.id);
182
+ if (!connectionMetadata) {
183
+ return;
184
+ }
185
+ const evictionBlocklist = OnyxUtils_1.default.getEvictionBlocklist();
186
+ evictionBlocklist[connectionMetadata.onyxKey] =
187
+ (_b = (_a = evictionBlocklist[connectionMetadata.onyxKey]) === null || _a === void 0 ? void 0 : _a.filter((evictionKey) => evictionKey !== `${connection.id}_${connection.callbackID}`)) !== null && _b !== void 0 ? _b : [];
188
+ // Remove the key if there are no more subscribers.
189
+ if (((_c = evictionBlocklist[connectionMetadata.onyxKey]) === null || _c === void 0 ? void 0 : _c.length) === 0) {
190
+ delete evictionBlocklist[connectionMetadata.onyxKey];
191
+ }
192
+ }
193
+ }
194
+ const connectionManager = new OnyxConnectionManager();
195
+ exports.default = connectionManager;