forked from Expensify/react-native-onyx
-
Notifications
You must be signed in to change notification settings - Fork 2
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request Expensify#485 from margelo/feat/split-up-in-memory…
…-pr-part-3 feat: fallback to `NoopProvider` if OOM happens
- Loading branch information
Showing
19 changed files
with
641 additions
and
234 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,17 @@ | ||
import NOOP from 'lodash/noop'; | ||
|
||
/** | ||
* This is used to keep multiple browser tabs in sync, therefore only needed on web | ||
* On native platforms, we omit this syncing logic by setting this to mock implementation. | ||
*/ | ||
const InstanceSync = { | ||
shouldBeUsed: false, | ||
init: NOOP, | ||
setItem: NOOP, | ||
removeItem: NOOP, | ||
removeItems: NOOP, | ||
mergeItem: NOOP, | ||
clear: <T extends () => void>(callback: T) => Promise.resolve(callback()), | ||
}; | ||
|
||
export default InstanceSync; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,72 @@ | ||
/** | ||
* The InstancesSync object 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 type {OnyxKey} from '../../types'; | ||
import NoopProvider from '../providers/NoopProvider'; | ||
import type {KeyList, OnStorageKeyChanged} from '../providers/types'; | ||
import type StorageProvider from '../providers/types'; | ||
|
||
const SYNC_ONYX = 'SYNC_ONYX'; | ||
|
||
/** | ||
* Raise an event through `localStorage` to let other tabs know a value changed | ||
* @param {String} onyxKey | ||
*/ | ||
function raiseStorageSyncEvent(onyxKey: OnyxKey) { | ||
global.localStorage.setItem(SYNC_ONYX, onyxKey); | ||
global.localStorage.removeItem(SYNC_ONYX); | ||
} | ||
|
||
function raiseStorageSyncManyKeysEvent(onyxKeys: KeyList) { | ||
onyxKeys.forEach((onyxKey) => { | ||
raiseStorageSyncEvent(onyxKey); | ||
}); | ||
} | ||
|
||
let storage = NoopProvider; | ||
|
||
const InstanceSync = { | ||
shouldBeUsed: true, | ||
/** | ||
* @param {Function} onStorageKeyChanged Storage synchronization mechanism keeping all opened tabs in sync | ||
*/ | ||
init: (onStorageKeyChanged: OnStorageKeyChanged, store: StorageProvider) => { | ||
storage = store; | ||
|
||
// 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)); | ||
}); | ||
}, | ||
setItem: raiseStorageSyncEvent, | ||
removeItem: raiseStorageSyncEvent, | ||
removeItems: raiseStorageSyncManyKeysEvent, | ||
mergeItem: raiseStorageSyncEvent, | ||
clear: (clearImplementation: () => void) => { | ||
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: KeyList) => { | ||
allKeys = keys; | ||
}) | ||
.then(() => clearImplementation()) | ||
.then(() => { | ||
// Now that storage is cleared, the storage sync event can happen which is a more atomic action | ||
// for other browser tabs | ||
raiseStorageSyncManyKeysEvent(allKeys); | ||
}); | ||
}, | ||
}; | ||
|
||
export default InstanceSync; |
This file was deleted.
Oops, something went wrong.
This file was deleted.
Oops, something went wrong.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,89 +1,26 @@ | ||
import type {OnyxKey, OnyxValue} from '../../types'; | ||
import utils from '../../utils'; | ||
import type {KeyValuePairList} from '../providers/types'; | ||
import type StorageProvider from '../providers/types'; | ||
import MemoryOnlyProvider, {mockStore, mockSet, setMockStore} from '../providers/MemoryOnlyProvider'; | ||
|
||
let storageMapInternal: Record<OnyxKey, OnyxValue<OnyxKey>> = {}; | ||
const init = jest.fn(MemoryOnlyProvider.init); | ||
|
||
const set = jest.fn((key, value) => { | ||
storageMapInternal[key] = value; | ||
return Promise.resolve(value); | ||
}); | ||
init(); | ||
|
||
const idbKeyvalMock: StorageProvider = { | ||
setItem(key, value) { | ||
return set(key, value); | ||
}, | ||
multiSet(pairs) { | ||
const setPromises = pairs.map(([key, value]) => this.setItem(key, value)); | ||
return new Promise((resolve) => { | ||
Promise.all(setPromises).then(() => resolve(storageMapInternal)); | ||
}); | ||
}, | ||
getItem<TKey extends OnyxKey>(key: TKey) { | ||
return Promise.resolve(storageMapInternal[key] as OnyxValue<TKey>); | ||
}, | ||
multiGet(keys) { | ||
const getPromises = keys.map( | ||
(key) => | ||
new Promise((resolve) => { | ||
this.getItem(key).then((value) => resolve([key, value])); | ||
}), | ||
); | ||
return Promise.all(getPromises) as Promise<KeyValuePairList>; | ||
}, | ||
multiMerge(pairs) { | ||
pairs.forEach(([key, value]) => { | ||
const existingValue = storageMapInternal[key]; | ||
const newValue = utils.fastMerge(existingValue as Record<string, unknown>, value as Record<string, unknown>); | ||
|
||
set(key, newValue); | ||
}); | ||
|
||
return Promise.resolve(storageMapInternal); | ||
}, | ||
mergeItem(key, _changes, modifiedData) { | ||
return this.setItem(key, modifiedData); | ||
}, | ||
removeItem(key) { | ||
delete storageMapInternal[key]; | ||
return Promise.resolve(); | ||
}, | ||
removeItems(keys) { | ||
keys.forEach((key) => { | ||
delete storageMapInternal[key]; | ||
}); | ||
return Promise.resolve(); | ||
}, | ||
clear() { | ||
storageMapInternal = {}; | ||
return Promise.resolve(); | ||
}, | ||
getAllKeys() { | ||
return Promise.resolve(Object.keys(storageMapInternal)); | ||
}, | ||
getDatabaseSize() { | ||
return Promise.resolve({bytesRemaining: 0, bytesUsed: 99999}); | ||
}, | ||
}; | ||
|
||
const idbKeyvalMockSpy = { | ||
idbKeyvalSet: set, | ||
setItem: jest.fn(idbKeyvalMock.setItem), | ||
getItem: jest.fn(idbKeyvalMock.getItem), | ||
removeItem: jest.fn(idbKeyvalMock.removeItem), | ||
removeItems: jest.fn(idbKeyvalMock.removeItems), | ||
clear: jest.fn(idbKeyvalMock.clear), | ||
getAllKeys: jest.fn(idbKeyvalMock.getAllKeys), | ||
multiGet: jest.fn(idbKeyvalMock.multiGet), | ||
multiSet: jest.fn(idbKeyvalMock.multiSet), | ||
multiMerge: jest.fn(idbKeyvalMock.multiMerge), | ||
mergeItem: jest.fn(idbKeyvalMock.mergeItem), | ||
getStorageMap: jest.fn(() => storageMapInternal), | ||
setInitialMockData: jest.fn((data) => { | ||
storageMapInternal = data; | ||
}), | ||
getDatabaseSize: jest.fn(idbKeyvalMock.getDatabaseSize), | ||
const StorageMock = { | ||
init, | ||
getItem: jest.fn(MemoryOnlyProvider.getItem), | ||
multiGet: jest.fn(MemoryOnlyProvider.multiGet), | ||
setItem: jest.fn(MemoryOnlyProvider.setItem), | ||
multiSet: jest.fn(MemoryOnlyProvider.multiSet), | ||
mergeItem: jest.fn(MemoryOnlyProvider.mergeItem), | ||
multiMerge: jest.fn(MemoryOnlyProvider.multiMerge), | ||
removeItem: jest.fn(MemoryOnlyProvider.removeItem), | ||
removeItems: jest.fn(MemoryOnlyProvider.removeItems), | ||
clear: jest.fn(MemoryOnlyProvider.clear), | ||
getAllKeys: jest.fn(MemoryOnlyProvider.getAllKeys), | ||
getDatabaseSize: jest.fn(MemoryOnlyProvider.getDatabaseSize), | ||
keepInstancesSync: jest.fn(), | ||
mockSet, | ||
getMockStore: jest.fn(() => mockStore), | ||
setMockStore: jest.fn((data) => setMockStore(data)), | ||
}; | ||
|
||
export default idbKeyvalMockSpy; | ||
export default StorageMock; |
This file was deleted.
Oops, something went wrong.
Oops, something went wrong.