diff --git a/lib/Onyx.ts b/lib/Onyx.ts index bdac8c89..3bd5dd43 100644 --- a/lib/Onyx.ts +++ b/lib/Onyx.ts @@ -548,12 +548,7 @@ function update(data: Array>): Promise Promise.all(finalPromises.map((p) => p()))).then(() => undefined); + return clearPromise.then(() => Promise.all(promises.map((p) => p()))).then(() => undefined); } /** diff --git a/lib/OnyxUtils.ts b/lib/OnyxUtils.ts index 446e7bd2..4d07ab7b 100644 --- a/lib/OnyxUtils.ts +++ b/lib/OnyxUtils.ts @@ -3,7 +3,6 @@ import type {ValueOf} from 'type-fest'; import _ from 'underscore'; import DevTools from './DevTools'; import * as Logger from './Logger'; -import type Onyx from './Onyx'; import cache, {TASK} from './OnyxCache'; import * as Str from './Str'; import Storage from './storage'; @@ -23,7 +22,6 @@ import type { OnyxInputKeyValueMapping, OnyxKey, OnyxMergeCollectionInput, - OnyxUpdate, OnyxValue, Selector, MergeCollectionWithPatchesParams, @@ -92,8 +90,6 @@ let defaultKeyStates: Record> = {}; // Used for comparison with a new update to avoid invoking the Onyx.connect callback with the same data. let lastConnectionCallbackData = new Map>(); -let snapshotKey: OnyxKey | null = null; - // Keeps track of the last subscriptionID that was used so we can keep incrementing it let lastSubscriptionID = 0; @@ -105,10 +101,6 @@ let skippableCollectionMemberIDs = new Set(); // Holds a set of keys that should always be merged into snapshot entries. let snapshotMergeKeys = new Set(); -function getSnapshotKey(): OnyxKey | null { - return snapshotKey; -} - /** * Getter - returns the merge queue. */ @@ -191,10 +183,6 @@ function initStoreValues(keys: DeepRecord, initialKeyStates: Pa // Set collection keys in cache for optimized storage cache.setCollectionKeys(onyxCollectionKeySet); - - if (typeof keys.COLLECTION === 'object' && typeof keys.COLLECTION.SNAPSHOT === 'string') { - snapshotKey = keys.COLLECTION.SNAPSHOT; - } } /** @@ -1241,72 +1229,6 @@ function unsubscribeFromKey(subscriptionID: number): void { delete callbackToStateMapping[subscriptionID]; } -function updateSnapshots(data: Array>, mergeFn: typeof Onyx.merge): Array<() => Promise> { - const snapshotCollectionKey = getSnapshotKey(); - if (!snapshotCollectionKey) return []; - - const promises: Array<() => Promise> = []; - - const snapshotCollection = getCachedCollection(snapshotCollectionKey); - - for (const [snapshotEntryKey, snapshotEntryValue] of Object.entries(snapshotCollection)) { - // Snapshots may not be present in cache. We don't know how to update them so we skip. - if (!snapshotEntryValue) { - continue; - } - - let updatedData: Record = {}; - - for (const {key, value} of data) { - // snapshots are normal keys so we want to skip update if they are written to Onyx - if (isCollectionMemberKey(snapshotCollectionKey, key)) { - continue; - } - - if (typeof snapshotEntryValue !== 'object' || !('data' in snapshotEntryValue)) { - continue; - } - - const snapshotData = snapshotEntryValue.data; - if (!snapshotData || !snapshotData[key]) { - continue; - } - - if (Array.isArray(value) || Array.isArray(snapshotData[key])) { - updatedData[key] = value || []; - continue; - } - - if (value === null) { - updatedData[key] = value; - continue; - } - - const oldValue = updatedData[key] || {}; - - // Snapshot entries are stored as a "shape" of the last known data per key, so by default we only - // merge fields that already exist in the snapshot to avoid unintentionally bloating snapshot data. - // Some clients need specific fields (like pending status) even when they are missing in the snapshot, - // so we allow an explicit, opt-in list of keys to always include during snapshot merges. - const snapshotExistingKeys = Object.keys(snapshotData[key] || {}); - const allowedNewKeys = getSnapshotMergeKeys(); - const keysToCopy = new Set([...snapshotExistingKeys, ...allowedNewKeys]); - const newValue = typeof value === 'object' && value !== null ? utils.pick(value as Record, [...keysToCopy]) : {}; - - updatedData = {...updatedData, [key]: Object.assign(oldValue, newValue)}; - } - - // Skip the update if there's no data to be merged - if (utils.isEmptyObject(updatedData)) { - continue; - } - - promises.push(() => mergeFn(snapshotEntryKey, {data: updatedData})); - } - - return promises; -} - /** * Writes a value to our store with the given key. * Serves as core implementation for `Onyx.set()` public function, the difference being @@ -1777,7 +1699,6 @@ const OnyxUtils = { mergeChanges, mergeAndMarkChanges, initializeWithDefaultKeyStates, - getSnapshotKey, multiGet, tupleGet, isValidNonEmptyCollectionForMerge, @@ -1792,7 +1713,6 @@ const OnyxUtils = { deleteKeyBySubscriptions, addKeyToRecentlyAccessedIfNeeded, reduceCollectionWithSelector, - updateSnapshots, mergeCollectionWithPatches, partialSetCollection, logKeyChanged, diff --git a/tests/perf-test/OnyxUtils.perf-test.ts b/tests/perf-test/OnyxUtils.perf-test.ts index 5a00d910..3d83b437 100644 --- a/tests/perf-test/OnyxUtils.perf-test.ts +++ b/tests/perf-test/OnyxUtils.perf-test.ts @@ -7,9 +7,8 @@ import StorageMock from '../../lib/storage'; import OnyxCache from '../../lib/OnyxCache'; import OnyxUtils, {clearOnyxUtilsInternals} from '../../lib/OnyxUtils'; import type GenericCollection from '../utils/GenericCollection'; -import type {OnyxUpdate} from '../../lib/Onyx'; import createDeferredTask from '../../lib/createDeferredTask'; -import type {OnyxEntry, OnyxInputKeyValueMapping, OnyxKey, RetriableOnyxOperation} from '../../lib/types'; +import type {OnyxEntry, OnyxKey, RetriableOnyxOperation} from '../../lib/types'; const ONYXKEYS = { TEST_KEY: 'test', @@ -598,12 +597,6 @@ describe('OnyxUtils', () => { }); }); - describe('getSnapshotKey', () => { - test('one call', async () => { - await measureFunction(() => OnyxUtils.getSnapshotKey()); - }); - }); - describe('multiGet', () => { test('one call getting 10k heavy objects from storage', async () => { await measureAsyncFunction(() => OnyxUtils.multiGet(mockedReportActionsKeys), { @@ -811,37 +804,4 @@ describe('OnyxUtils', () => { await measureFunction(() => OnyxUtils.reduceCollectionWithSelector(mockedReportActionsMap, selector)); }); }); - - describe('updateSnapshots', () => { - test('one call with 100 updates', async () => { - const updates: Array> = []; - for (let i = 0; i < 100; i++) { - updates.push({ - onyxMethod: OnyxUtils.METHOD.MERGE, - key: `${collectionKey}${i}`, - value: createRandomReportAction(i), - }); - } - - await measureAsyncFunction(() => Promise.all(OnyxUtils.updateSnapshots(updates, Onyx.merge).map((p) => p())), { - beforeEach: async () => { - const searchData: Partial = {}; - const data: Partial = { - ...mockedReportActionsMap, - [`${ONYXKEYS.COLLECTION.SNAPSHOT}hash0`]: { - data: searchData, - search: {}, - }, - }; - - for (let i = 0; i < 100; i++) { - searchData[`${collectionKey}${i}`] = mockedReportActionsMap[`${collectionKey}${i}`]; - } - - await Onyx.multiSet(data); - }, - afterEach: clearOnyxAfterEachMeasure, - }); - }); - }); }); diff --git a/tests/unit/onyxTest.ts b/tests/unit/onyxTest.ts index 77842a9c..4daa855d 100644 --- a/tests/unit/onyxTest.ts +++ b/tests/unit/onyxTest.ts @@ -1556,63 +1556,6 @@ describe('Onyx', () => { }); }); - it('should update Snapshot when its data changed', async () => { - const cat = `${ONYX_KEYS.COLLECTION.ANIMALS}cat`; - const snapshot1 = `${ONYX_KEYS.COLLECTION.SNAPSHOT}1`; - - const initialValue = {name: 'Fluffy'}; - const finalValue = {name: 'Kitty'}; - - await Onyx.set(cat, initialValue); - await Onyx.set(snapshot1, {data: {[cat]: initialValue}}); - - const callback = jest.fn(); - - Onyx.connect({ - key: ONYX_KEYS.COLLECTION.SNAPSHOT, - callback, - }); - - await waitForPromisesToResolve(); - - await Onyx.update([{key: cat, value: finalValue, onyxMethod: Onyx.METHOD.MERGE}]); - - expect(callback).toBeCalledTimes(2); - expect(callback).toHaveBeenNthCalledWith(1, {data: {[cat]: initialValue}}, snapshot1); - expect(callback).toHaveBeenNthCalledWith(2, {data: {[cat]: finalValue}}, snapshot1); - }); - - it('should merge allowlisted keys into Snapshot even if they were missing', async () => { - const cat = `${ONYX_KEYS.COLLECTION.ANIMALS}cat`; - const snapshot1 = `${ONYX_KEYS.COLLECTION.SNAPSHOT}1`; - - const initialValue = {name: 'Fluffy'}; - const finalValue = { - name: 'Kitty', - pendingAction: 'delete', - pendingFields: {preview: 'delete'}, - other: 'ignored', - }; - - await Onyx.set(cat, initialValue); - await Onyx.set(snapshot1, {data: {[cat]: initialValue}}); - - const callback = jest.fn(); - - Onyx.connect({ - key: ONYX_KEYS.COLLECTION.SNAPSHOT, - callback, - }); - - await waitForPromisesToResolve(); - - await Onyx.update([{key: cat, value: finalValue, onyxMethod: Onyx.METHOD.MERGE}]); - - expect(callback).toBeCalledTimes(2); - expect(callback).toHaveBeenNthCalledWith(1, {data: {[cat]: initialValue}}, snapshot1); - expect(callback).toHaveBeenNthCalledWith(2, {data: {[cat]: {name: 'Kitty', pendingAction: 'delete', pendingFields: {preview: 'delete'}}}}, snapshot1); - }); - describe('update', () => { it('should squash all updates of collection-related keys into a single mergeCollection call', () => { const connections: Connection[] = [];