Skip to content

Commit

Permalink
Merge pull request #496 from Expensify/tgolen-revert-PR475
Browse files Browse the repository at this point in the history
Revert "refactor: unify storage/providers (for further InMemory storage integration) [1/3]"
  • Loading branch information
tgolen committed Mar 5, 2024
1 parent 7fee636 commit 4934859
Show file tree
Hide file tree
Showing 16 changed files with 128 additions and 301 deletions.
6 changes: 3 additions & 3 deletions jestSetup.js
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
jest.mock('./lib/storage');
jest.mock('./lib/storage/platforms/index.native', () => require('./lib/storage/__mocks__'));
jest.mock('./lib/storage/platforms/index', () => require('./lib/storage/__mocks__'));
jest.mock('./lib/storage/providers/IDBKeyValProvider', () => require('./lib/storage/__mocks__'));
jest.mock('./lib/storage/NativeStorage', () => require('./lib/storage/__mocks__'));
jest.mock('./lib/storage/WebStorage', () => require('./lib/storage/__mocks__'));
jest.mock('./lib/storage/providers/IDBKeyVal', () => require('./lib/storage/__mocks__'));

jest.mock('react-native-device-info', () => ({getFreeDiskStorage: () => {}}));
jest.mock('react-native-quick-sqlite', () => ({
Expand Down
34 changes: 9 additions & 25 deletions lib/Onyx.js
Original file line number Diff line number Diff line change
Expand Up @@ -1554,31 +1554,7 @@ function setMemoryOnlyKeys(keyList) {
* },
* });
*/
function init({
keys = {},
initialKeyStates = {},
safeEvictionKeys = [],
maxCachedKeysCount = 1000,
captureMetrics = false,
shouldSyncMultipleInstances = Boolean(global.localStorage),
debugSetState = false,
} = {}) {
Storage.init();

if (shouldSyncMultipleInstances) {
Storage.keepInstancesSync((key, value) => {
const prevValue = cache.getValue(key, false);
cache.set(key, value);
keyChanged(key, value, prevValue);
});
}

if (captureMetrics) {
// The code here is only bundled and applied when the captureMetrics is set
// eslint-disable-next-line no-use-before-define
applyDecorators();
}

function init({keys = {}, initialKeyStates = {}, safeEvictionKeys = [], maxCachedKeysCount = 1000, shouldSyncMultipleInstances = Boolean(global.localStorage), debugSetState = false} = {}) {
if (debugSetState) {
PerformanceUtils.setShouldDebugSetState(true);
}
Expand Down Expand Up @@ -1609,6 +1585,14 @@ function init({

// Initialize all of our keys with data provided then give green light to any pending connections
Promise.all([addAllSafeEvictionKeysToRecentlyAccessedList(), initializeWithDefaultKeyStates()]).then(deferredInitTask.resolve);

if (shouldSyncMultipleInstances && _.isFunction(Storage.keepInstancesSync)) {
Storage.keepInstancesSync((key, value) => {
const prevValue = cache.getValue(key, false);
cache.set(key, value);
keyChanged(key, value, prevValue);
});
}
}

const Onyx = {
Expand Down
16 changes: 0 additions & 16 deletions lib/storage/InstanceSync/index.ts

This file was deleted.

65 changes: 0 additions & 65 deletions lib/storage/InstanceSync/index.web.ts

This file was deleted.

3 changes: 3 additions & 0 deletions lib/storage/NativeStorage.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
import SQLiteStorage from './providers/SQLiteStorage';

export default SQLiteStorage;
73 changes: 73 additions & 0 deletions lib/storage/WebStorage.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
/**
* This file is here to wrap IDBKeyVal with a layer that provides data-changed events like the ones that exist
* when using LocalStorage APIs in the browser. These events are great because multiple tabs can listen for when
* data changes and then stay up-to-date with everything happening in Onyx.
*/
import Storage from './providers/IDBKeyVal';
import type {KeyList, Key} from './providers/types';
import type StorageProvider from './providers/types';

const SYNC_ONYX = 'SYNC_ONYX';

/**
* Raise an event thorough `localStorage` to let other tabs know a value changed
*/
function raiseStorageSyncEvent(onyxKey: Key) {
global.localStorage.setItem(SYNC_ONYX, onyxKey);
global.localStorage.removeItem(SYNC_ONYX);
}

function raiseStorageSyncManyKeysEvent(onyxKeys: KeyList) {
onyxKeys.forEach((onyxKey) => {
raiseStorageSyncEvent(onyxKey);
});
}

const webStorage: StorageProvider = {
...Storage,
/**
* @param onStorageKeyChanged Storage synchronization mechanism keeping all opened tabs in sync
*/
keepInstancesSync(onStorageKeyChanged) {
// Override set, remove and clear to raise storage events that we intercept in other tabs
this.setItem = (key, value) => Storage.setItem(key, value).then(() => raiseStorageSyncEvent(key));

this.removeItem = (key) => Storage.removeItem(key).then(() => raiseStorageSyncEvent(key));

this.removeItems = (keys) => Storage.removeItems(keys).then(() => raiseStorageSyncManyKeysEvent(keys));

this.mergeItem = (key, batchedChanges, modifiedData) => Storage.mergeItem(key, batchedChanges, modifiedData).then(() => raiseStorageSyncEvent(key));

// If we just call Storage.clear other tabs will have no idea which keys were available previously
// so that they can call keysChanged for them. That's why we iterate over every key and raise a storage sync
// event for each one
this.clear = () => {
let allKeys: KeyList;

// The keys must be retrieved before storage is cleared or else the list of keys would be empty
return Storage.getAllKeys()
.then((keys) => {
allKeys = keys;
})
.then(() => Storage.clear())
.then(() => {
// Now that storage is cleared, the storage sync event can happen which is a more atomic action
// for other browser tabs
allKeys.forEach(raiseStorageSyncEvent);
});
};

// This listener will only be triggered by events coming from other tabs
global.addEventListener('storage', (event) => {
// Ignore events that don't originate from the SYNC_ONYX logic
if (event.key !== SYNC_ONYX || !event.newValue) {
return;
}

const onyxKey = event.newValue;
Storage.getItem(onyxKey).then((value) => onStorageKeyChanged(onyxKey, value));
});
},
};

export default webStorage;
4 changes: 0 additions & 4 deletions lib/storage/__mocks__/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,6 @@ const set = jest.fn((key, value) => {
});

const idbKeyvalMock: StorageProvider = {
init: () => undefined,
setItem(key, value) {
return set(key, value);
},
Expand Down Expand Up @@ -61,12 +60,10 @@ const idbKeyvalMock: StorageProvider = {
},
// eslint-disable-next-line @typescript-eslint/no-empty-function
setMemoryOnlyKeys() {},
keepInstancesSync: () => undefined,
};

const idbKeyvalMockSpy = {
idbKeyvalSet: set,
init: jest.fn(idbKeyvalMock.init),
setItem: jest.fn(idbKeyvalMock.setItem),
getItem: jest.fn(idbKeyvalMock.getItem),
removeItem: jest.fn(idbKeyvalMock.removeItem),
Expand All @@ -83,7 +80,6 @@ const idbKeyvalMockSpy = {
}),
getDatabaseSize: jest.fn(idbKeyvalMock.getDatabaseSize),
setMemoryOnlyKeys: jest.fn(idbKeyvalMock.setMemoryOnlyKeys),
keepInstancesSync: jest.fn(idbKeyvalMock.keepInstancesSync),
};

export default idbKeyvalMockSpy;
3 changes: 3 additions & 0 deletions lib/storage/index.native.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
import NativeStorage from './NativeStorage';

export default NativeStorage;
138 changes: 2 additions & 136 deletions lib/storage/index.ts
Original file line number Diff line number Diff line change
@@ -1,137 +1,3 @@
import PlatformStorage from './platforms';
import InstanceSync from './InstanceSync';
import type StorageProvider from './providers/types';
import WebStorage from './WebStorage';

const provider = PlatformStorage;
let shouldKeepInstancesSync = false;

type Storage = {
getStorageProvider: () => StorageProvider;
} & StorageProvider;

const Storage: Storage = {
/**
* Returns the storage provider currently in use
*/
getStorageProvider() {
return provider;
},

/**
* Initializes all providers in the list of storage providers
* and enables fallback providers if necessary
*/
init() {
provider.init();
},

/**
* Get the value of a given key or return `null` if it's not available
*/
getItem: (key) => provider.getItem(key),

/**
* Get multiple key-value pairs for the give array of keys in a batch
*/
multiGet: (keys) => provider.multiGet(keys),

/**
* Sets the value for a given key. The only requirement is that the value should be serializable to JSON string
*/
setItem: (key, value) => {
const promise = provider.setItem(key, value);

if (shouldKeepInstancesSync) {
return promise.then(() => InstanceSync.setItem(key));
}

return promise;
},

/**
* Stores multiple key-value pairs in a batch
*/
multiSet: (pairs) => provider.multiSet(pairs),

/**
* Merging an existing value with a new one
*/
mergeItem: (key, changes, modifiedData) => {
const promise = provider.mergeItem(key, changes, modifiedData);

if (shouldKeepInstancesSync) {
return promise.then(() => InstanceSync.mergeItem(key));
}

return promise;
},

/**
* Multiple merging of existing and new values in a batch
* This function also removes all nested null values from an object.
*/
multiMerge: (pairs) => provider.multiMerge(pairs),

/**
* Removes given key and its value
*/
removeItem: (key) => {
const promise = provider.removeItem(key);

if (shouldKeepInstancesSync) {
return promise.then(() => InstanceSync.removeItem(key));
}

return promise;
},

/**
* Remove given keys and their values
*/
removeItems: (keys) => {
const promise = provider.removeItems(keys);

if (shouldKeepInstancesSync) {
return promise.then(() => InstanceSync.removeItems(keys));
}

return promise;
},

/**
* Clears everything
*/
clear: () => {
if (shouldKeepInstancesSync) {
return InstanceSync.clear(() => provider.clear());
}

return provider.clear();
},

// This is a noop for now in order to keep clients from crashing see https://github.com/Expensify/Expensify/issues/312438
setMemoryOnlyKeys: () => provider.setMemoryOnlyKeys(),

/**
* Returns all available keys
*/
getAllKeys: () => provider.getAllKeys(),

/**
* Gets the total bytes of the store
*/
getDatabaseSize: () => provider.getDatabaseSize(),

/**
* @param onStorageKeyChanged - Storage synchronization mechanism keeping all opened tabs in sync (web only)
*/
keepInstancesSync(onStorageKeyChanged) {
// If InstanceSync is null, it means we're on a native platform and we don't need to keep instances in sync
if (InstanceSync == null) return;

shouldKeepInstancesSync = true;
InstanceSync.init(onStorageKeyChanged);
},
};

export default Storage;
export default WebStorage;
3 changes: 0 additions & 3 deletions lib/storage/platforms/index.native.ts

This file was deleted.

Loading

0 comments on commit 4934859

Please sign in to comment.