@@ -1075,19 +1075,72 @@ function mergeInternal<TValue extends OnyxInput<OnyxKey> | undefined, TChange ex
10751075 * Merge user provided default key value pairs.
10761076 */
10771077function initializeWithDefaultKeyStates ( ) : Promise < void > {
1078- // Filter out RAM-only keys from storage reads as they may have stale persisted data
1079- // from before the key was migrated to RAM-only.
1080- const keysToFetch = Object . keys ( defaultKeyStates ) . filter ( ( key ) => ! isRamOnlyKey ( key ) ) ;
1081- return Storage . multiGet ( keysToFetch ) . then ( ( pairs ) => {
1082- const existingDataAsObject = Object . fromEntries ( pairs ) as Record < string , unknown > ;
1083-
1084- const merged = utils . fastMerge ( existingDataAsObject , defaultKeyStates , {
1085- shouldRemoveNestedNulls : true ,
1086- } ) . result ;
1087- cache . merge ( merged ?? { } ) ;
1088-
1089- for ( const [ key , value ] of Object . entries ( merged ?? { } ) ) keyChanged ( key , value ) ;
1090- } ) ;
1078+ // Eagerly load the entire database into cache in a single batch read.
1079+ // This is faster than lazy-loading individual keys because:
1080+ // 1. One DB transaction instead of hundreds
1081+ // 2. All subsequent reads are synchronous cache hits
1082+ return Storage . getAll ( )
1083+ . then ( ( pairs ) => {
1084+ const allDataFromStorage : Record < string , unknown > = { } ;
1085+ for ( const [ key , value ] of pairs ) {
1086+ // RAM-only keys should never be loaded from storage as they may have stale persisted data
1087+ // from before the key was migrated to RAM-only.
1088+ if ( isRamOnlyKey ( key ) ) {
1089+ continue ;
1090+ }
1091+
1092+ // Skip collection members that are marked as skippable
1093+ if ( skippableCollectionMemberIDs . size && getCollectionKey ( key ) ) {
1094+ const [ , collectionMemberID ] = splitCollectionMemberKey ( key ) ;
1095+
1096+ if ( skippableCollectionMemberIDs . has ( collectionMemberID ) ) {
1097+ continue ;
1098+ }
1099+ }
1100+
1101+ allDataFromStorage [ key ] = value ;
1102+ }
1103+
1104+ // Load all storage data into cache silently (no subscriber notifications)
1105+ cache . setAllKeys ( Object . keys ( allDataFromStorage ) ) ;
1106+ cache . merge ( allDataFromStorage ) ;
1107+
1108+ // For keys that have a developer-defined default (via `initialKeyStates`), merge the
1109+ // persisted value with the default so new properties added in code updates are applied
1110+ // without wiping user data that already exists in storage.
1111+ const defaultKeysFromStorage = Object . keys ( defaultKeyStates ) . reduce ( ( obj : Record < string , unknown > , key ) => {
1112+ if ( key in allDataFromStorage ) {
1113+ // eslint-disable-next-line no-param-reassign
1114+ obj [ key ] = allDataFromStorage [ key ] ;
1115+ }
1116+ return obj ;
1117+ } , { } ) ;
1118+
1119+ const merged = utils . fastMerge ( defaultKeysFromStorage , defaultKeyStates , {
1120+ shouldRemoveNestedNulls : true ,
1121+ } ) . result ;
1122+ cache . merge ( merged ?? { } ) ;
1123+
1124+ // Notify subscribers about default key states so that any subscriber that connected
1125+ // before init (e.g. during module load) receives the merged default values immediately
1126+ for ( const [ key , value ] of Object . entries ( merged ?? { } ) ) keyChanged ( key , value ) ;
1127+ } )
1128+ . catch ( ( error ) => {
1129+ Logger . logAlert ( `Failed to load data from storage during init. The app will boot with default key states only. Error: ${ error } ` ) ;
1130+
1131+ // Populate the key index so getAllKeys() returns correct results for default keys.
1132+ // Without this, subscribers that check getAllKeys() would see an empty set even
1133+ // though we have default values in cache.
1134+ cache . setAllKeys ( Object . keys ( defaultKeyStates ) ) ;
1135+
1136+ // Boot with defaults so the app renders instead of deadlocking.
1137+ // Users will get a fresh-install experience but the app won't be bricked.
1138+ cache . merge ( defaultKeyStates ) ;
1139+
1140+ // Notify subscribers about default key states so that any subscriber that connected
1141+ // before init (e.g. during module load) receives the merged default values immediately
1142+ for ( const [ key , value ] of Object . entries ( defaultKeyStates ) ) keyChanged ( key , value ) ;
1143+ } ) ;
10911144}
10921145
10931146/**
0 commit comments