Skip to content

Commit e824ef7

Browse files
authored
Merge branch 'Expensify:main' into krishna2323/issue/84192
2 parents d5bb9b6 + b351fa5 commit e824ef7

27 files changed

Lines changed: 640 additions & 310 deletions

lib/DevTools/RealDevTools.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import type {IDevTools, DevtoolsOptions, DevtoolsConnection, ReduxDevtools} from './types';
2+
import OnyxUtils from '../OnyxUtils';
23

34
const ERROR_LABEL = 'Onyx DevTools - Error: ';
45

@@ -76,7 +77,7 @@ class RealDevTools implements IDevTools {
7677
clearState(keysToPreserve: string[] = []): void {
7778
const newState = Object.entries(this.state).reduce((obj: Record<string, unknown>, [key, value]) => {
7879
// eslint-disable-next-line no-param-reassign
79-
obj[key] = keysToPreserve.includes(key) ? value : this.defaultState[key];
80+
obj[key] = keysToPreserve.some((preserveKey) => OnyxUtils.isKeyMatch(preserveKey, key)) ? value : this.defaultState[key];
8081
return obj;
8182
}, {});
8283

lib/Onyx.ts

Lines changed: 29 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -82,10 +82,13 @@ function init({
8282

8383
OnyxUtils.initStoreValues(keys, initialKeyStates, evictableKeys);
8484

85-
// Initialize all of our keys with data provided then give green light to any pending connections
86-
Promise.all([cache.addEvictableKeysToRecentlyAccessedList(OnyxUtils.isCollectionKey, OnyxUtils.getAllKeys), OnyxUtils.initializeWithDefaultKeyStates()]).then(
87-
OnyxUtils.getDeferredInitTask().resolve,
88-
);
85+
// Initialize all of our keys with data provided then give green light to any pending connections.
86+
// addEvictableKeysToRecentlyAccessedList must run after initializeWithDefaultKeyStates because
87+
// eager cache loading populates the key index (cache.setAllKeys) inside initializeWithDefaultKeyStates,
88+
// and the evictable keys list depends on that index being populated.
89+
OnyxUtils.initializeWithDefaultKeyStates()
90+
.then(() => cache.addEvictableKeysToRecentlyAccessedList(OnyxUtils.isCollectionKey, OnyxUtils.getAllKeys))
91+
.then(OnyxUtils.getDeferredInitTask().resolve);
8992
}
9093

9194
/**
@@ -237,7 +240,13 @@ function merge<TKey extends OnyxKey>(key: TKey, changes: OnyxMergeInput<TKey>):
237240

238241
try {
239242
const validChanges = mergeQueue[key].filter((change) => {
240-
const {isCompatible, existingValueType, newValueType} = utils.checkCompatibilityWithExistingValue(change, existingValue);
243+
const {isCompatible, existingValueType, newValueType, isEmptyArrayCoercion} = utils.checkCompatibilityWithExistingValue(change, existingValue);
244+
if (isEmptyArrayCoercion) {
245+
// Merging an object into an empty array isn't semantically correct, but we allow it
246+
// in case we accidentally encoded an empty object as an empty array in PHP. If you're
247+
// looking at a bugbot from this message, we're probably missing that key in OnyxKeys::KEYS_REQUIRING_EMPTY_OBJECT
248+
Logger.logAlert(`[ENSURE_BUGBOT] Onyx merge called on key "${key}" whose existing value is an empty array. Will coerce to object.`);
249+
}
241250
if (!isCompatible) {
242251
Logger.logAlert(logMessages.incompatibleUpdateAlert(key, 'merge', existingValueType, newValueType));
243252
}
@@ -260,8 +269,9 @@ function merge<TKey extends OnyxKey>(key: TKey, changes: OnyxMergeInput<TKey>):
260269
return Promise.resolve();
261270
}
262271

263-
return OnyxMerge.applyMerge(key, existingValue, validChanges).then(({mergedValue}) => {
272+
return OnyxMerge.applyMerge(key, existingValue, validChanges).then(({mergedValue, updatePromise}) => {
264273
OnyxUtils.sendActionToDevTools(OnyxUtils.METHOD.MERGE, key, changes, mergedValue);
274+
return updatePromise;
265275
});
266276
} catch (error) {
267277
Logger.logAlert(`An error occurred while applying merge for key: ${key}, Error: ${error}`);
@@ -338,7 +348,7 @@ function clear(keysToPreserve: OnyxKey[] = []): Promise<void> {
338348
// to null would cause unknown behavior)
339349
// 2.1 However, if a default key was explicitly set to null, we need to reset it to the default value
340350
for (const key of allKeys) {
341-
const isKeyToPreserve = keysToPreserve.includes(key);
351+
const isKeyToPreserve = keysToPreserve.some((preserveKey) => OnyxUtils.isKeyMatch(preserveKey, key));
342352
const isDefaultKey = key in defaultKeyStates;
343353

344354
// If the key is being removed or reset to default:
@@ -373,10 +383,20 @@ function clear(keysToPreserve: OnyxKey[] = []): Promise<void> {
373383
keysToBeClearedFromStorage.push(key);
374384
}
375385

386+
const updatePromises: Array<Promise<void>> = [];
387+
388+
// Notify the subscribers for each key/value group so they can receive the new values
389+
for (const [key, value] of Object.entries(keyValuesToResetIndividually)) {
390+
updatePromises.push(OnyxUtils.scheduleSubscriberUpdate(key, value));
391+
}
392+
for (const [key, value] of Object.entries(keyValuesToResetAsCollection)) {
393+
updatePromises.push(OnyxUtils.scheduleNotifyCollectionSubscribers(key, value.newValues, value.oldValues));
394+
}
395+
376396
// Exclude RAM-only keys to prevent them from being saved to storage
377397
const defaultKeyValuePairs = Object.entries(
378398
Object.keys(defaultKeyStates)
379-
.filter((key) => !keysToPreserve.includes(key) && !OnyxUtils.isRamOnlyKey(key))
399+
.filter((key) => !keysToPreserve.some((preserveKey) => OnyxUtils.isKeyMatch(preserveKey, key)) && !OnyxUtils.isRamOnlyKey(key))
380400
.reduce((obj: KeyValueMapping, key) => {
381401
// eslint-disable-next-line no-param-reassign
382402
obj[key] = defaultKeyStates[key];
@@ -391,14 +411,7 @@ function clear(keysToPreserve: OnyxKey[] = []): Promise<void> {
391411
.then(() => Storage.multiSet(defaultKeyValuePairs))
392412
.then(() => {
393413
DevTools.clearState(keysToPreserve);
394-
395-
// Notify the subscribers for each key/value group so they can receive the new values
396-
for (const [key, value] of Object.entries(keyValuesToResetIndividually)) {
397-
OnyxUtils.keyChanged(key, value);
398-
}
399-
for (const [key, value] of Object.entries(keyValuesToResetAsCollection)) {
400-
OnyxUtils.keysChanged(key, value.newValues, value.oldValues);
401-
}
414+
return Promise.all(updatePromises);
402415
});
403416
})
404417
.then(() => undefined);

lib/OnyxMerge/index.native.ts

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -26,20 +26,21 @@ const applyMerge: ApplyMerge = <TKey extends OnyxKey, TValue extends OnyxInput<T
2626
OnyxUtils.logKeyChanged(OnyxUtils.METHOD.MERGE, key, mergedValue, hasChanged);
2727

2828
// This approach prioritizes fast UI changes without waiting for data to be stored in device storage.
29-
OnyxUtils.broadcastUpdate(key, mergedValue as OnyxValue<TKey>, hasChanged);
29+
const updatePromise = OnyxUtils.broadcastUpdate(key, mergedValue as OnyxValue<TKey>, hasChanged);
3030

3131
const shouldSkipStorageOperations = !hasChanged || OnyxUtils.isRamOnlyKey(key);
3232

3333
// If the value has not changed, calling Storage.setItem() would be redundant and a waste of performance, so return early instead.
3434
// If the key is marked as RAM-only, it should not be saved nor updated in the storage.
3535
if (shouldSkipStorageOperations) {
36-
return Promise.resolve({mergedValue});
36+
return Promise.resolve({mergedValue, updatePromise});
3737
}
3838

3939
// For native platforms we use `mergeItem` that will take advantage of JSON_PATCH and JSON_REPLACE SQL operations to
4040
// merge the object in a performant way.
4141
return Storage.mergeItem(key, batchedChanges as OnyxValue<TKey>, replaceNullPatches).then(() => ({
4242
mergedValue,
43+
updatePromise,
4344
}));
4445
};
4546

lib/OnyxMerge/index.ts

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -18,19 +18,20 @@ const applyMerge: ApplyMerge = <TKey extends OnyxKey, TValue extends OnyxInput<T
1818
OnyxUtils.logKeyChanged(OnyxUtils.METHOD.MERGE, key, mergedValue, hasChanged);
1919

2020
// This approach prioritizes fast UI changes without waiting for data to be stored in device storage.
21-
OnyxUtils.broadcastUpdate(key, mergedValue as OnyxValue<TKey>, hasChanged);
21+
const updatePromise = OnyxUtils.broadcastUpdate(key, mergedValue as OnyxValue<TKey>, hasChanged);
2222

2323
const shouldSkipStorageOperations = !hasChanged || OnyxUtils.isRamOnlyKey(key);
2424

2525
// If the value has not changed, calling Storage.setItem() would be redundant and a waste of performance, so return early instead.
2626
// If the key is marked as RAM-only, it should not be saved nor updated in the storage.
2727
if (shouldSkipStorageOperations) {
28-
return Promise.resolve({mergedValue});
28+
return Promise.resolve({mergedValue, updatePromise});
2929
}
3030

3131
// For web platforms we use `setItem` since the object was already merged with its changes before.
3232
return Storage.setItem(key, mergedValue as OnyxValue<TKey>).then(() => ({
3333
mergedValue,
34+
updatePromise,
3435
}));
3536
};
3637

lib/OnyxMerge/types.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ import type {OnyxInput, OnyxKey} from '../types';
22

33
type ApplyMergeResult<TValue> = {
44
mergedValue: TValue;
5+
updatePromise: Promise<void>;
56
};
67

78
type ApplyMerge = <TKey extends OnyxKey, TValue extends OnyxInput<OnyxKey> | undefined, TChange extends OnyxInput<OnyxKey> | null>(

lib/OnyxSnapshotCache.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -60,7 +60,7 @@ class OnyxSnapshotCache {
6060
* - `selector`: Different selectors produce different results, so each selector needs its own cache entry
6161
* - `initWithStoredValues`: This flag changes the initial loading behavior and affects the returned fetch status
6262
*
63-
* Other options like `canEvict`, `reuseConnection`, and `allowDynamicKey` don't affect the data transformation
63+
* Other options like `canEvict` and `reuseConnection` don't affect the data transformation
6464
* or timing behavior of getSnapshot, so they're excluded from the cache key for better cache hit rates.
6565
*/
6666
registerConsumer<TKey extends OnyxKey, TReturnValue>(options: Pick<UseOnyxOptions<TKey, TReturnValue>, 'selector' | 'initWithStoredValues'>): string {

0 commit comments

Comments
 (0)