react-native-onyx
Advanced tools
+1
-1
@@ -5,3 +5,3 @@ import * as Logger from './Logger'; | ||
| /** Initialize the store with actions and listening for storage events */ | ||
| declare function init({ keys, initialKeyStates, evictableKeys, maxCachedKeysCount, shouldSyncMultipleInstances, enableDevTools, skippableCollectionMemberIDs, ramOnlyKeys, snapshotMergeKeys, }: InitOptions): void; | ||
| declare function init({ keys, initialKeyStates, evictableKeys, shouldSyncMultipleInstances, enableDevTools, skippableCollectionMemberIDs, ramOnlyKeys, snapshotMergeKeys, }: InitOptions): void; | ||
| /** | ||
@@ -8,0 +8,0 @@ * Connects to an Onyx key given the options passed and listens to its changes. |
+1
-4
@@ -50,3 +50,3 @@ "use strict"; | ||
| /** Initialize the store with actions and listening for storage events */ | ||
| function init({ keys = {}, initialKeyStates = {}, evictableKeys = [], maxCachedKeysCount = 1000, shouldSyncMultipleInstances = !!global.localStorage, enableDevTools = true, skippableCollectionMemberIDs = [], ramOnlyKeys = [], snapshotMergeKeys = [], }) { | ||
| function init({ keys = {}, initialKeyStates = {}, evictableKeys = [], shouldSyncMultipleInstances = !!global.localStorage, enableDevTools = true, skippableCollectionMemberIDs = [], ramOnlyKeys = [], snapshotMergeKeys = [], }) { | ||
| var _a; | ||
@@ -73,5 +73,2 @@ (0, DevTools_1.initDevTools)(enableDevTools); | ||
| } | ||
| if (maxCachedKeysCount > 0) { | ||
| OnyxCache_1.default.setRecentKeysLimit(maxCachedKeysCount); | ||
| } | ||
| OnyxUtils_1.default.initStoreValues(keys, initialKeyStates, evictableKeys); | ||
@@ -78,0 +75,0 @@ // Initialize all of our keys with data provided then give green light to any pending connections. |
+3
-22
@@ -18,4 +18,2 @@ import type { ValueOf } from 'type-fest'; | ||
| private nullishStorageKeys; | ||
| /** Unique list of keys maintained in access order (most recent at the end) */ | ||
| private recentKeys; | ||
| /** A map of cached values */ | ||
@@ -30,8 +28,4 @@ private storageMap; | ||
| private pendingPromises; | ||
| /** Maximum size of the keys store din cache */ | ||
| private maxRecentKeysSize; | ||
| /** List of keys that are safe to remove when we reach max storage */ | ||
| private evictionAllowList; | ||
| /** Map of keys and connection arrays whose keys will never be automatically evicted */ | ||
| private evictionBlocklist; | ||
| /** List of keys that have been directly subscribed to or recently modified from least to most recent */ | ||
@@ -66,8 +60,5 @@ private recentlyAccessedKeys; | ||
| hasCacheForKey(key: OnyxKey): boolean; | ||
| /** Get a cached value from storage */ | ||
| get(key: OnyxKey): OnyxValue<OnyxKey>; | ||
| /** | ||
| * Get a cached value from storage | ||
| * @param [shouldReindexCache] – This is an LRU cache, and by default accessing a value will make it become last in line to be evicted. This flag can be used to skip that and just access the value directly without side-effects. | ||
| */ | ||
| get(key: OnyxKey, shouldReindexCache?: boolean): OnyxValue<OnyxKey>; | ||
| /** | ||
| * Set's a key value in cache | ||
@@ -102,8 +93,2 @@ * Adds the key to the storage keys list as well | ||
| captureTask(taskName: CacheTask, promise: Promise<OnyxValue<OnyxKey>>): Promise<OnyxValue<OnyxKey>>; | ||
| /** Adds a key to the top of the recently accessed keys */ | ||
| addToAccessedKeys(key: OnyxKey): void; | ||
| /** Remove keys that don't fall into the range of recently used keys */ | ||
| removeLeastRecentlyUsedKeys(): void; | ||
| /** Set the recent keys list size */ | ||
| setRecentKeysLimit(limit: number): void; | ||
| /** Check if the value has changed */ | ||
@@ -117,6 +102,2 @@ hasValueChanged(key: OnyxKey, value: OnyxValue<OnyxKey>): boolean; | ||
| /** | ||
| * Get the eviction block list that prevents keys from being evicted | ||
| */ | ||
| getEvictionBlocklist(): Record<OnyxKey, string[] | undefined>; | ||
| /** | ||
| * Checks to see if this key has been flagged as safe for removal. | ||
@@ -146,3 +127,3 @@ * @param testKey - Key to check | ||
| /** | ||
| * Finds a key that can be safely evicted | ||
| * Finds the least recently accessed key that can be safely evicted from storage. | ||
| */ | ||
@@ -149,0 +130,0 @@ getKeyForEviction(): OnyxKey | undefined; |
+8
-70
@@ -24,8 +24,4 @@ "use strict"; | ||
| constructor() { | ||
| /** Maximum size of the keys store din cache */ | ||
| this.maxRecentKeysSize = 0; | ||
| /** List of keys that are safe to remove when we reach max storage */ | ||
| this.evictionAllowList = []; | ||
| /** Map of keys and connection arrays whose keys will never be automatically evicted */ | ||
| this.evictionBlocklist = {}; | ||
| /** List of keys that have been directly subscribed to or recently modified from least to most recent */ | ||
@@ -35,3 +31,2 @@ this.recentlyAccessedKeys = new Set(); | ||
| this.nullishStorageKeys = new Set(); | ||
| this.recentKeys = new Set(); | ||
| this.storageMap = {}; | ||
@@ -41,3 +36,3 @@ this.collectionData = {}; | ||
| // bind all public methods to prevent problems with `this` | ||
| (0, bindAll_1.default)(this, 'getAllKeys', 'get', 'hasCacheForKey', 'addKey', 'addNullishStorageKey', 'hasNullishStorageKey', 'clearNullishStorageKeys', 'set', 'drop', 'merge', 'hasPendingTask', 'getTaskPromise', 'captureTask', 'addToAccessedKeys', 'removeLeastRecentlyUsedKeys', 'setRecentKeysLimit', 'setAllKeys', 'setEvictionAllowList', 'getEvictionBlocklist', 'isEvictableKey', 'removeLastAccessedKey', 'addLastAccessedKey', 'addEvictableKeysToRecentlyAccessedList', 'getKeyForEviction', 'setCollectionKeys', 'getCollectionData', 'hasValueChanged'); | ||
| (0, bindAll_1.default)(this, 'getAllKeys', 'get', 'hasCacheForKey', 'addKey', 'addNullishStorageKey', 'hasNullishStorageKey', 'clearNullishStorageKeys', 'set', 'drop', 'merge', 'hasPendingTask', 'getTaskPromise', 'captureTask', 'setAllKeys', 'setEvictionAllowList', 'isEvictableKey', 'removeLastAccessedKey', 'addLastAccessedKey', 'addEvictableKeysToRecentlyAccessedList', 'getKeyForEviction', 'setCollectionKeys', 'getCollectionData', 'hasValueChanged'); | ||
| } | ||
@@ -88,10 +83,4 @@ /** Get all the storage keys */ | ||
| } | ||
| /** | ||
| * Get a cached value from storage | ||
| * @param [shouldReindexCache] – This is an LRU cache, and by default accessing a value will make it become last in line to be evicted. This flag can be used to skip that and just access the value directly without side-effects. | ||
| */ | ||
| get(key, shouldReindexCache = true) { | ||
| if (shouldReindexCache) { | ||
| this.addToAccessedKeys(key); | ||
| } | ||
| /** Get a cached value from storage */ | ||
| get(key) { | ||
| return this.storageMap[key]; | ||
@@ -105,3 +94,2 @@ } | ||
| this.addKey(key); | ||
| this.addToAccessedKeys(key); | ||
| // When a key is explicitly set in cache, we can remove it from the list of nullish keys, | ||
@@ -142,3 +130,2 @@ // since it will either be set to a non nullish value or removed from the cache completely. | ||
| this.storageKeys.delete(key); | ||
| this.recentKeys.delete(key); | ||
| OnyxKeys_1.default.deregisterMemberKey(key); | ||
@@ -160,3 +147,2 @@ } | ||
| this.addKey(key); | ||
| this.addToAccessedKeys(key); | ||
| const collectionKey = OnyxKeys_1.default.getCollectionKey(key); | ||
@@ -210,44 +196,5 @@ if (value === null || value === undefined) { | ||
| } | ||
| /** Adds a key to the top of the recently accessed keys */ | ||
| addToAccessedKeys(key) { | ||
| this.recentKeys.delete(key); | ||
| this.recentKeys.add(key); | ||
| } | ||
| /** Remove keys that don't fall into the range of recently used keys */ | ||
| removeLeastRecentlyUsedKeys() { | ||
| const numKeysToRemove = this.recentKeys.size - this.maxRecentKeysSize; | ||
| if (numKeysToRemove <= 0) { | ||
| return; | ||
| } | ||
| const iterator = this.recentKeys.values(); | ||
| const keysToRemove = []; | ||
| const recentKeysArray = Array.from(this.recentKeys); | ||
| const mostRecentKey = recentKeysArray[recentKeysArray.length - 1]; | ||
| let iterResult = iterator.next(); | ||
| while (!iterResult.done) { | ||
| const key = iterResult.value; | ||
| // Don't consider the most recently accessed key for eviction | ||
| // This ensures we don't immediately evict a key we just added | ||
| if (key !== undefined && key !== mostRecentKey && this.isEvictableKey(key)) { | ||
| keysToRemove.push(key); | ||
| } | ||
| iterResult = iterator.next(); | ||
| } | ||
| for (const key of keysToRemove) { | ||
| delete this.storageMap[key]; | ||
| // Remove from collection data cache if this is a collection member | ||
| const collectionKey = OnyxKeys_1.default.getCollectionKey(key); | ||
| if (collectionKey && this.collectionData[collectionKey]) { | ||
| delete this.collectionData[collectionKey][key]; | ||
| } | ||
| this.recentKeys.delete(key); | ||
| } | ||
| } | ||
| /** Set the recent keys list size */ | ||
| setRecentKeysLimit(limit) { | ||
| this.maxRecentKeysSize = limit; | ||
| } | ||
| /** Check if the value has changed */ | ||
| hasValueChanged(key, value) { | ||
| const currentValue = this.get(key, false); | ||
| const currentValue = this.get(key); | ||
| return !(0, fast_equals_1.deepEqual)(currentValue, value); | ||
@@ -263,8 +210,2 @@ } | ||
| /** | ||
| * Get the eviction block list that prevents keys from being evicted | ||
| */ | ||
| getEvictionBlocklist() { | ||
| return this.evictionBlocklist; | ||
| } | ||
| /** | ||
| * Checks to see if this key has been flagged as safe for removal. | ||
@@ -316,11 +257,8 @@ * @param testKey - Key to check | ||
| /** | ||
| * Finds a key that can be safely evicted | ||
| * Finds the least recently accessed key that can be safely evicted from storage. | ||
| */ | ||
| getKeyForEviction() { | ||
| for (const key of this.recentlyAccessedKeys) { | ||
| if (!this.evictionBlocklist[key]) { | ||
| return key; | ||
| } | ||
| } | ||
| return undefined; | ||
| // recentlyAccessedKeys is ordered from least to most recently accessed, | ||
| // so the first element is the best candidate for eviction. | ||
| return this.recentlyAccessedKeys.values().next().value; | ||
| } | ||
@@ -327,0 +265,0 @@ /** |
@@ -75,11 +75,2 @@ import type { ConnectOptions } from './Onyx'; | ||
| refreshSessionID(): void; | ||
| /** | ||
| * Adds the connection to the eviction block list. Connections added to this list can never be evicted. | ||
| * */ | ||
| addToEvictionBlockList(connection: Connection): void; | ||
| /** | ||
| * Removes a connection previously added to this list | ||
| * which will enable it to be evicted again. | ||
| */ | ||
| removeFromEvictionBlockList(connection: Connection): void; | ||
| } | ||
@@ -86,0 +77,0 @@ declare const connectionManager: OnyxConnectionManager; |
@@ -44,3 +44,2 @@ "use strict"; | ||
| const Str = __importStar(require("./Str")); | ||
| const OnyxCache_1 = __importDefault(require("./OnyxCache")); | ||
| const OnyxSnapshotCache_1 = __importDefault(require("./OnyxSnapshotCache")); | ||
@@ -56,3 +55,3 @@ /** | ||
| // Binds all public methods to prevent problems with `this`. | ||
| (0, bindAll_1.default)(this, 'generateConnectionID', 'fireCallbacks', 'connect', 'disconnect', 'disconnectAll', 'refreshSessionID', 'addToEvictionBlockList', 'removeFromEvictionBlockList'); | ||
| (0, bindAll_1.default)(this, 'generateConnectionID', 'fireCallbacks', 'connect', 'disconnect', 'disconnectAll', 'refreshSessionID'); | ||
| } | ||
@@ -169,3 +168,2 @@ /** | ||
| OnyxUtils_1.default.unsubscribeFromKey(connectionMetadata.subscriptionID); | ||
| this.removeFromEvictionBlockList(connection); | ||
| this.connectionsMap.delete(connection.id); | ||
@@ -178,7 +176,4 @@ } | ||
| disconnectAll() { | ||
| for (const [connectionID, connectionMetadata] of this.connectionsMap.entries()) { | ||
| for (const connectionMetadata of this.connectionsMap.values()) { | ||
| OnyxUtils_1.default.unsubscribeFromKey(connectionMetadata.subscriptionID); | ||
| for (const callbackID of connectionMetadata.callbacks.keys()) { | ||
| this.removeFromEvictionBlockList({ id: connectionID, callbackID }); | ||
| } | ||
| } | ||
@@ -197,47 +192,4 @@ this.connectionsMap.clear(); | ||
| } | ||
| /** | ||
| * 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; |
@@ -43,3 +43,3 @@ import type { OnyxKey, OnyxValue } from './types'; | ||
| * | ||
| * Other options like `canEvict` and `reuseConnection` don't affect the data transformation | ||
| * Other options like `reuseConnection` don't affect the data transformation | ||
| * or timing behavior of getSnapshot, so they're excluded from the cache key for better cache hit rates. | ||
@@ -46,0 +46,0 @@ */ |
@@ -40,3 +40,3 @@ "use strict"; | ||
| * | ||
| * Other options like `canEvict` and `reuseConnection` don't affect the data transformation | ||
| * Other options like `reuseConnection` don't affect the data transformation | ||
| * or timing behavior of getSnapshot, so they're excluded from the cache key for better cache hit rates. | ||
@@ -43,0 +43,0 @@ */ |
@@ -131,7 +131,2 @@ import type { ValueOf } from 'type-fest'; | ||
| /** | ||
| * We check to see if this key is flagged as safe for eviction and add it to the recentlyAccessedKeys list so that when we | ||
| * run out of storage the least recently accessed key can be removed. | ||
| */ | ||
| declare function addKeyToRecentlyAccessedIfNeeded<TKey extends OnyxKey>(key: TKey): void; | ||
| /** | ||
| * Gets the data for a given an array of matching keys, combines them into an object, and sends the result back to the subscriber. | ||
@@ -316,3 +311,2 @@ */ | ||
| deleteKeyBySubscriptions: typeof deleteKeyBySubscriptions; | ||
| addKeyToRecentlyAccessedIfNeeded: typeof addKeyToRecentlyAccessedIfNeeded; | ||
| reduceCollectionWithSelector: typeof reduceCollectionWithSelector; | ||
@@ -319,0 +313,0 @@ updateSnapshots: typeof updateSnapshots; |
+1
-7
@@ -337,12 +337,6 @@ import type { Merge } from 'type-fest'; | ||
| * This is an array of keys (individual or collection patterns) that when provided to Onyx are flagged | ||
| * as "safe" for removal. Any components subscribing to these keys must also implement a canEvict option. See the README for more info. | ||
| * as "safe" for removal. | ||
| */ | ||
| evictableKeys?: OnyxKey[]; | ||
| /** | ||
| * Sets how many recent keys should we try to keep in cache | ||
| * Setting this to 0 would practically mean no cache | ||
| * We try to free cache when we connect to a safe eviction key | ||
| */ | ||
| maxCachedKeysCount?: number; | ||
| /** | ||
| * Auto synchronize storage events between multiple instances | ||
@@ -349,0 +343,0 @@ * of Onyx running in different tabs/windows. Defaults to true for platforms that support local storage (web/desktop) |
@@ -6,6 +6,2 @@ import type { DependencyList } from 'react'; | ||
| /** | ||
| * Determines if this key in this subscription is safe to be evicted. | ||
| */ | ||
| canEvict?: boolean; | ||
| /** | ||
| * If set to `false`, then no data will be prefilled into the component. | ||
@@ -12,0 +8,0 @@ * @deprecated This param is going to be removed soon. Use RAM-only keys instead. |
+1
-19
@@ -135,16 +135,2 @@ "use strict"; | ||
| }, [...dependencies]); | ||
| const checkEvictableKey = (0, react_1.useCallback)(() => { | ||
| if ((options === null || options === void 0 ? void 0 : options.canEvict) === undefined || !connectionRef.current) { | ||
| return; | ||
| } | ||
| if (!OnyxCache_1.default.isEvictableKey(key)) { | ||
| throw new Error(`canEvict can't be used on key '${key}'. This key must explicitly be flagged as safe for removal by adding it to Onyx.init({evictableKeys: []}).`); | ||
| } | ||
| if (options.canEvict) { | ||
| OnyxConnectionManager_1.default.removeFromEvictionBlockList(connectionRef.current); | ||
| } | ||
| else { | ||
| OnyxConnectionManager_1.default.addToEvictionBlockList(connectionRef.current); | ||
| } | ||
| }, [key, options === null || options === void 0 ? void 0 : options.canEvict]); | ||
| // Tracks the last memoizedSelector reference that getSnapshot() has computed with. | ||
@@ -266,3 +252,2 @@ // When the selector changes, this mismatch forces getSnapshot() to re-evaluate | ||
| }); | ||
| checkEvictableKey(); | ||
| return () => { | ||
@@ -277,6 +262,3 @@ if (!connectionRef.current) { | ||
| }; | ||
| }, [key, options === null || options === void 0 ? void 0 : options.initWithStoredValues, options === null || options === void 0 ? void 0 : options.reuseConnection, checkEvictableKey]); | ||
| (0, react_1.useEffect)(() => { | ||
| checkEvictableKey(); | ||
| }, [checkEvictableKey]); | ||
| }, [key, options === null || options === void 0 ? void 0 : options.initWithStoredValues, options === null || options === void 0 ? void 0 : options.reuseConnection]); | ||
| const result = (0, react_1.useSyncExternalStore)(subscribe, getSnapshot); | ||
@@ -283,0 +265,0 @@ return result; |
+1
-1
| { | ||
| "name": "react-native-onyx", | ||
| "version": "3.0.62", | ||
| "version": "3.0.63", | ||
| "author": "Expensify, Inc.", | ||
@@ -5,0 +5,0 @@ "homepage": "https://expensify.com", |
+6
-24
@@ -317,16 +317,13 @@ # `react-native-onyx` | ||
| # Cache Eviction | ||
| # Storage Eviction | ||
| Different platforms come with varying storage capacities and Onyx has a way to gracefully fail when those storage limits are encountered. When Onyx fails to set or modify a key the following steps are taken: | ||
| 1. Onyx looks at a list of recently accessed keys (access is defined as subscribed to or modified) and locates the key that was least recently accessed | ||
| 2. It then deletes this key and retries the original operation | ||
| 1. Onyx looks at a list of evictable keys ordered by recent access and locates the least recently accessed one | ||
| 2. It then deletes this key from both cache and storage, and retries the original operation | ||
| 3. This process repeats up to 5 times until the write succeeds or no more evictable keys are available | ||
| By default, Onyx will not evict anything from storage and will presume all keys are "unsafe" to remove unless explicitly told otherwise. | ||
| **To flag a key as safe for removal:** | ||
| - Add the key to the `evictableKeys` option in `Onyx.init(options)` | ||
| - Implement `canEvict` in the Onyx config for each component subscribing to a key | ||
| - The key will only be deleted when all subscribers return `true` for `canEvict` | ||
| **To flag a key as safe for removal**, add the key to the `evictableKeys` option in `Onyx.init(options)`: | ||
| e.g. | ||
| ```js | ||
@@ -338,19 +335,4 @@ Onyx.init({ | ||
| ```js | ||
| const ReportActionsView = ({reportID, isActiveReport}) => { | ||
| const [reportActions] = useOnyx( | ||
| `${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${reportID}_`, | ||
| {canEvict: () => !isActiveReport} | ||
| ); | ||
| Only individual (non-collection) keys matching the `evictableKeys` patterns will be considered for eviction. Collection keys themselves cannot be evicted — only their individual members can. | ||
| return ( | ||
| <View> | ||
| {/* Render with reportActions data */} | ||
| </View> | ||
| ); | ||
| }; | ||
| export default ReportActionsView; | ||
| ``` | ||
| # Debug mode | ||
@@ -357,0 +339,0 @@ |
Sorry, the diff of this file is too big to display
Network access
Supply chain riskThis module accesses the network.
Found 1 instance in 1 package
Environment variable access
Supply chain riskPackage accesses environment variables, which may be a sign of credential stuffing or data theft.
Found 1 instance in 1 package
Long strings
Supply chain riskContains long string literals, which may be a sign of obfuscated or packed code.
Found 1 instance in 1 package
Network access
Supply chain riskThis module accesses the network.
Found 1 instance in 1 package
Environment variable access
Supply chain riskPackage accesses environment variables, which may be a sign of credential stuffing or data theft.
Found 1 instance in 1 package
Long strings
Supply chain riskContains long string literals, which may be a sign of obfuscated or packed code.
Found 1 instance in 1 package
336228
-2.73%6438
-2.88%427
-4.04%35
9.38%