@@ -3,6 +3,7 @@ import bindAll from 'lodash/bindAll';
33import type { ValueOf } from 'type-fest' ;
44import utils from './utils' ;
55import type { OnyxKey , OnyxValue } from './types' ;
6+ import * as Str from './Str' ;
67
78// Task constants
89const TASK = {
@@ -39,6 +40,15 @@ class OnyxCache {
3940 /** Maximum size of the keys store din cache */
4041 private maxRecentKeysSize = 0 ;
4142
43+ /** List of keys that are safe to remove when we reach max storage */
44+ private evictionAllowList : OnyxKey [ ] = [ ] ;
45+
46+ /** Map of keys and connection arrays whose keys will never be automatically evicted */
47+ private evictionBlocklist : Record < OnyxKey , string [ ] | undefined > = { } ;
48+
49+ /** List of keys that have been directly subscribed to or recently modified from least to most recent */
50+ private recentlyAccessedKeys : OnyxKey [ ] = [ ] ;
51+
4252 constructor ( ) {
4353 this . storageKeys = new Set ( ) ;
4454 this . nullishStorageKeys = new Set ( ) ;
@@ -62,9 +72,17 @@ class OnyxCache {
6272 'hasPendingTask' ,
6373 'getTaskPromise' ,
6474 'captureTask' ,
75+ 'addToAccessedKeys' ,
6576 'removeLeastRecentlyUsedKeys' ,
6677 'setRecentKeysLimit' ,
6778 'setAllKeys' ,
79+ 'setEvictionAllowList' ,
80+ 'getEvictionBlocklist' ,
81+ 'isEvictableKey' ,
82+ 'removeLastAccessedKey' ,
83+ 'addLastAccessedKey' ,
84+ 'addEvictableKeysToRecentlyAccessedList' ,
85+ 'getKeyForEviction' ,
6886 ) ;
6987 }
7088
@@ -219,19 +237,29 @@ class OnyxCache {
219237
220238 /** Remove keys that don't fall into the range of recently used keys */
221239 removeLeastRecentlyUsedKeys ( ) : void {
222- let numKeysToRemove = this . recentKeys . size - this . maxRecentKeysSize ;
240+ const numKeysToRemove = this . recentKeys . size - this . maxRecentKeysSize ;
223241 if ( numKeysToRemove <= 0 ) {
224242 return ;
225243 }
244+
226245 const iterator = this . recentKeys . values ( ) ;
227- const temp = [ ] ;
228- while ( numKeysToRemove > 0 ) {
229- const value = iterator . next ( ) . value ;
230- temp . push ( value ) ;
231- numKeysToRemove -- ;
246+ const keysToRemove : OnyxKey [ ] = [ ] ;
247+
248+ const recentKeysArray = Array . from ( this . recentKeys ) ;
249+ const mostRecentKey = recentKeysArray [ recentKeysArray . length - 1 ] ;
250+
251+ let iterResult = iterator . next ( ) ;
252+ while ( ! iterResult . done ) {
253+ const key = iterResult . value ;
254+ // Don't consider the most recently accessed key for eviction
255+ // This ensures we don't immediately evict a key we just added
256+ if ( key !== undefined && key !== mostRecentKey && this . isEvictableKey ( key ) ) {
257+ keysToRemove . push ( key ) ;
258+ }
259+ iterResult = iterator . next ( ) ;
232260 }
233261
234- for ( const key of temp ) {
262+ for ( const key of keysToRemove ) {
235263 delete this . storageMap [ key ] ;
236264 this . recentKeys . delete ( key ) ;
237265 }
@@ -246,6 +274,90 @@ class OnyxCache {
246274 hasValueChanged ( key : OnyxKey , value : OnyxValue < OnyxKey > ) : boolean {
247275 return ! deepEqual ( this . storageMap [ key ] , value ) ;
248276 }
277+
278+ /**
279+ * Sets the list of keys that are considered safe for eviction
280+ * @param keys - Array of OnyxKeys that are safe to evict
281+ */
282+ setEvictionAllowList ( keys : OnyxKey [ ] ) : void {
283+ this . evictionAllowList = keys ;
284+ }
285+
286+ /**
287+ * Get the eviction block list that prevents keys from being evicted
288+ */
289+ getEvictionBlocklist ( ) : Record < OnyxKey , string [ ] | undefined > {
290+ return this . evictionBlocklist ;
291+ }
292+
293+ /**
294+ * Checks to see if this key has been flagged as safe for removal.
295+ * @param testKey - Key to check
296+ */
297+ isEvictableKey ( testKey : OnyxKey ) : boolean {
298+ return this . evictionAllowList . some ( ( key ) => this . isKeyMatch ( key , testKey ) ) ;
299+ }
300+
301+ /**
302+ * Check if a given key matches a pattern key
303+ * @param configKey - Pattern that may contain a wildcard
304+ * @param key - Key to test against the pattern
305+ */
306+ private isKeyMatch ( configKey : OnyxKey , key : OnyxKey ) : boolean {
307+ const isCollectionKey = configKey . endsWith ( '_' ) ;
308+ return isCollectionKey ? Str . startsWith ( key , configKey ) : configKey === key ;
309+ }
310+
311+ /**
312+ * Remove a key from the recently accessed key list.
313+ */
314+ removeLastAccessedKey ( key : OnyxKey ) : void {
315+ this . recentlyAccessedKeys = this . recentlyAccessedKeys . filter ( ( recentlyAccessedKey ) => recentlyAccessedKey !== key ) ;
316+ }
317+
318+ /**
319+ * Add a key to the list of recently accessed keys. The least
320+ * recently accessed key should be at the head and the most
321+ * recently accessed key at the tail.
322+ */
323+ addLastAccessedKey ( key : OnyxKey , isCollectionKey : boolean ) : void {
324+ // Only specific keys belong in this list since we cannot remove an entire collection.
325+ if ( isCollectionKey || ! this . isEvictableKey ( key ) ) {
326+ return ;
327+ }
328+
329+ this . removeLastAccessedKey ( key ) ;
330+ this . recentlyAccessedKeys . push ( key ) ;
331+ }
332+
333+ /**
334+ * Take all the keys that are safe to evict and add them to
335+ * the recently accessed list when initializing the app. This
336+ * enables keys that have not recently been accessed to be
337+ * removed.
338+ * @param isCollectionKeyFn - Function to determine if a key is a collection key
339+ * @param getAllKeysFn - Function to get all keys, defaults to Storage.getAllKeys
340+ */
341+ addEvictableKeysToRecentlyAccessedList ( isCollectionKeyFn : ( key : OnyxKey ) => boolean , getAllKeysFn : ( ) => Promise < Set < OnyxKey > > ) : Promise < void > {
342+ return getAllKeysFn ( ) . then ( ( keys : Set < OnyxKey > ) => {
343+ this . evictionAllowList . forEach ( ( evictableKey ) => {
344+ keys . forEach ( ( key : OnyxKey ) => {
345+ if ( ! this . isKeyMatch ( evictableKey , key ) ) {
346+ return ;
347+ }
348+
349+ this . addLastAccessedKey ( key , isCollectionKeyFn ( key ) ) ;
350+ } ) ;
351+ } ) ;
352+ } ) ;
353+ }
354+
355+ /**
356+ * Finds a key that can be safely evicted
357+ */
358+ getKeyForEviction ( ) : OnyxKey | undefined {
359+ return this . recentlyAccessedKeys . find ( ( key ) => ! this . evictionBlocklist [ key ] ) ;
360+ }
249361}
250362
251363const instance = new OnyxCache ( ) ;
0 commit comments