react-native-onyx
Version:
State management for React Native
228 lines (227 loc) • 12.2 kB
JavaScript
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
var desc = Object.getOwnPropertyDescriptor(m, k);
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
desc = { enumerable: true, get: function() { return m[k]; } };
}
Object.defineProperty(o, k2, desc);
}) : (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
o[k2] = m[k];
}));
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
Object.defineProperty(o, "default", { enumerable: true, value: v });
}) : function(o, v) {
o["default"] = v;
});
var __importStar = (this && this.__importStar) || function (mod) {
if (mod && mod.__esModule) return mod;
var result = {};
if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k);
__setModuleDefault(result, mod);
return result;
};
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
const bindAll_1 = __importDefault(require("lodash/bindAll"));
const Logger = __importStar(require("./Logger"));
const OnyxUtils_1 = __importDefault(require("./OnyxUtils"));
const Str = __importStar(require("./Str"));
const utils_1 = __importDefault(require("./utils"));
const OnyxCache_1 = __importDefault(require("./OnyxCache"));
/**
* Manages Onyx connections of `Onyx.connect()`, `useOnyx()` and `withOnyx()` subscribers.
*/
class OnyxConnectionManager {
constructor() {
this.connectionsMap = new Map();
this.lastCallbackID = 0;
this.sessionID = Str.guid();
// Binds all public methods to prevent problems with `this`.
(0, bindAll_1.default)(this, 'generateConnectionID', 'fireCallbacks', 'connect', 'disconnect', 'disconnectAll', 'refreshSessionID', 'addToEvictionBlockList', 'removeFromEvictionBlockList');
}
/**
* Generates a connection ID based on the `connectOptions` object passed to the function.
*
* The properties used to generate the ID are handpicked for performance reasons and
* according to their purpose and effect they produce in the Onyx connection.
*/
generateConnectionID(connectOptions) {
const { key, initWithStoredValues, reuseConnection, waitForCollectionCallback } = connectOptions;
// The current session ID is appended to the connection ID so we can have different connections
// after an `Onyx.clear()` operation.
let suffix = `,sessionID=${this.sessionID}`;
// We will generate a unique ID in any of the following situations:
// - `reuseConnection` is `false`. That means the subscriber explicitly wants the connection to not be reused.
// - `initWithStoredValues` is `false`. This flag changes the subscription flow when set to `false`, so the connection can't be reused.
// - `key` is a collection key AND `waitForCollectionCallback` is `undefined/false`. This combination needs a new connection at every subscription
// in order to send all the collection entries, so the connection can't be reused.
// - `withOnyxInstance` is defined inside `connectOptions`. That means the subscriber is a `withOnyx` HOC and therefore doesn't support connection reuse.
if (reuseConnection === false ||
initWithStoredValues === false ||
(!utils_1.default.hasWithOnyxInstance(connectOptions) && OnyxUtils_1.default.isCollectionKey(key) && (waitForCollectionCallback === undefined || waitForCollectionCallback === false)) ||
utils_1.default.hasWithOnyxInstance(connectOptions)) {
suffix += `,uniqueID=${Str.guid()}`;
}
return `onyxKey=${key},initWithStoredValues=${initWithStoredValues !== null && initWithStoredValues !== void 0 ? initWithStoredValues : true},waitForCollectionCallback=${waitForCollectionCallback !== null && waitForCollectionCallback !== void 0 ? waitForCollectionCallback : false}${suffix}`;
}
/**
* Fires all the subscribers callbacks associated with that connection ID.
*/
fireCallbacks(connectionID) {
const connection = this.connectionsMap.get(connectionID);
connection === null || connection === void 0 ? void 0 : connection.callbacks.forEach((callback) => {
if (connection.waitForCollectionCallback) {
callback(connection.cachedCallbackValue, connection.cachedCallbackKey, connection.sourceValue);
}
else {
callback(connection.cachedCallbackValue, connection.cachedCallbackKey);
}
});
}
/**
* Connects to an Onyx key given the options passed and listens to its changes.
*
* @param connectOptions The options object that will define the behavior of the connection.
* @returns The connection object to use when calling `disconnect()`.
*/
connect(connectOptions) {
const connectionID = this.generateConnectionID(connectOptions);
let connectionMetadata = this.connectionsMap.get(connectionID);
let subscriptionID;
const callbackID = String(this.lastCallbackID++);
// If there is no connection yet for that connection ID, we create a new one.
if (!connectionMetadata) {
let callback;
// If the subscriber is a `withOnyx` HOC we don't define `callback` as the HOC will use
// its own logic to handle the data.
if (!utils_1.default.hasWithOnyxInstance(connectOptions)) {
callback = (value, key, sourceValue) => {
const createdConnection = this.connectionsMap.get(connectionID);
if (createdConnection) {
// We signal that the first connection was made and now any new subscribers
// can fire their callbacks immediately with the cached value when connecting.
createdConnection.isConnectionMade = true;
createdConnection.cachedCallbackValue = value;
createdConnection.cachedCallbackKey = key;
createdConnection.sourceValue = sourceValue;
this.fireCallbacks(connectionID);
}
};
}
subscriptionID = OnyxUtils_1.default.subscribeToKey(Object.assign(Object.assign({}, connectOptions), { callback: callback }));
connectionMetadata = {
subscriptionID,
onyxKey: connectOptions.key,
isConnectionMade: false,
callbacks: new Map(),
waitForCollectionCallback: connectOptions.waitForCollectionCallback,
};
this.connectionsMap.set(connectionID, connectionMetadata);
}
// We add the subscriber's callback to the list of callbacks associated with this connection.
if (connectOptions.callback) {
connectionMetadata.callbacks.set(callbackID, connectOptions.callback);
}
// If the first connection is already made we want any new subscribers to receive the cached callback value immediately.
if (connectionMetadata.isConnectionMade) {
// Defer the callback execution to the next tick of the event loop.
// This ensures that the current execution flow completes and the result connection object is available when the callback fires.
Promise.resolve().then(() => {
var _a, _b;
(_b = (_a = connectOptions).callback) === null || _b === void 0 ? void 0 : _b.call(_a, connectionMetadata.cachedCallbackValue, connectionMetadata.cachedCallbackKey);
});
}
return { id: connectionID, callbackID };
}
/**
* Disconnects and removes the listener from the Onyx key.
*
* @param connection Connection object returned by calling `connect()`.
*/
disconnect(connection) {
if (!connection) {
Logger.logInfo(`[ConnectionManager] Attempted to disconnect passing an undefined connection object.`);
return;
}
const connectionMetadata = this.connectionsMap.get(connection.id);
if (!connectionMetadata) {
Logger.logInfo(`[ConnectionManager] Attempted to disconnect but no connection was found.`);
return;
}
// Removes the callback from the connection's callbacks map.
connectionMetadata.callbacks.delete(connection.callbackID);
// If the connection's callbacks map is empty we can safely unsubscribe from the Onyx key.
if (connectionMetadata.callbacks.size === 0) {
OnyxUtils_1.default.unsubscribeFromKey(connectionMetadata.subscriptionID);
this.removeFromEvictionBlockList(connection);
this.connectionsMap.delete(connection.id);
}
}
/**
* Disconnect all subscribers from Onyx.
*/
disconnectAll() {
this.connectionsMap.forEach((connectionMetadata, connectionID) => {
OnyxUtils_1.default.unsubscribeFromKey(connectionMetadata.subscriptionID);
connectionMetadata.callbacks.forEach((_, callbackID) => {
this.removeFromEvictionBlockList({ id: connectionID, callbackID });
});
});
this.connectionsMap.clear();
}
/**
* Refreshes the connection manager's session ID.
*/
refreshSessionID() {
this.sessionID = Str.guid();
}
/**
* Adds the connection to the eviction block list. Connections added to this list can never be evicted.
* */
addToEvictionBlockList(connection) {
var _a;
if (!connection) {
Logger.logInfo(`[ConnectionManager] Attempted to add connection to eviction block list passing an undefined connection object.`);
return;
}
const connectionMetadata = this.connectionsMap.get(connection.id);
if (!connectionMetadata) {
Logger.logInfo(`[ConnectionManager] Attempted to add connection to eviction block list but no connection was found.`);
return;
}
const evictionBlocklist = OnyxCache_1.default.getEvictionBlocklist();
if (!evictionBlocklist[connectionMetadata.onyxKey]) {
evictionBlocklist[connectionMetadata.onyxKey] = [];
}
(_a = evictionBlocklist[connectionMetadata.onyxKey]) === null || _a === void 0 ? void 0 : _a.push(`${connection.id}_${connection.callbackID}`);
}
/**
* Removes a connection previously added to this list
* which will enable it to be evicted again.
*/
removeFromEvictionBlockList(connection) {
var _a, _b, _c;
if (!connection) {
Logger.logInfo(`[ConnectionManager] Attempted to remove connection from eviction block list passing an undefined connection object.`);
return;
}
const connectionMetadata = this.connectionsMap.get(connection.id);
if (!connectionMetadata) {
Logger.logInfo(`[ConnectionManager] Attempted to remove connection from eviction block list but no connection was found.`);
return;
}
const evictionBlocklist = OnyxCache_1.default.getEvictionBlocklist();
evictionBlocklist[connectionMetadata.onyxKey] =
(_b = (_a = evictionBlocklist[connectionMetadata.onyxKey]) === null || _a === void 0 ? void 0 : _a.filter((evictionKey) => evictionKey !== `${connection.id}_${connection.callbackID}`)) !== null && _b !== void 0 ? _b : [];
// Remove the key if there are no more subscribers.
if (((_c = evictionBlocklist[connectionMetadata.onyxKey]) === null || _c === void 0 ? void 0 : _c.length) === 0) {
delete evictionBlocklist[connectionMetadata.onyxKey];
}
}
}
const connectionManager = new OnyxConnectionManager();
exports.default = connectionManager;
;