forked from Expensify/react-native-onyx
-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathIDBKeyValProvider.ts
99 lines (87 loc) · 3.89 KB
/
IDBKeyValProvider.ts
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
import type {UseStore} from 'idb-keyval';
import {set, keys, getMany, setMany, get, clear, del, delMany, createStore, promisifyRequest} from 'idb-keyval';
import utils from '../../utils';
import type StorageProvider from './types';
import type {OnyxKey, OnyxValue} from '../../types';
// We don't want to initialize the store while the JS bundle loads as idb-keyval will try to use global.indexedDB
// which might not be available in certain environments that load the bundle (e.g. electron main process).
let idbKeyValStore: UseStore;
const provider: StorageProvider = {
/**
* The name of the provider that can be printed to the logs
*/
name: 'IDBKeyValProvider',
/**
* Initializes the storage provider
*/
init() {
const newIdbKeyValStore = createStore('OnyxDB', 'keyvaluepairs');
if (newIdbKeyValStore == null) throw Error('IDBKeyVal store could not be created');
idbKeyValStore = newIdbKeyValStore;
},
setItem: (key, value) => {
if (value === null) {
provider.removeItem(key);
}
return set(key, value, idbKeyValStore);
},
multiGet: (keysParam) => getMany(keysParam, idbKeyValStore).then((values) => values.map((value, index) => [keysParam[index], value])),
multiMerge: (pairs) =>
idbKeyValStore('readwrite', (store) => {
// Note: we are using the manual store transaction here, to fit the read and update
// of the items in one transaction to achieve best performance.
const getValues = Promise.all(pairs.map(([key]) => promisifyRequest<OnyxValue<OnyxKey>>(store.get(key))));
return getValues.then((values) => {
const pairsWithoutNull = pairs.filter(([key, value]) => {
if (value === null) {
provider.removeItem(key);
return false;
}
return true;
});
const upsertMany = pairsWithoutNull.map(([key, value], index) => {
const prev = values[index];
const newValue = utils.fastMerge(prev as Record<string, unknown>, value as Record<string, unknown>);
return promisifyRequest(store.put(newValue, key));
});
return Promise.all(upsertMany);
});
}),
mergeItem(key, _deltaChanges, preMergedValue) {
// Since Onyx also merged the existing value with the changes, we can just set the value directly
return provider.setItem(key, preMergedValue);
},
multiSet: (pairs) => {
const pairsWithoutNull = pairs.filter(([key, value]) => {
if (value === null) {
provider.removeItem(key);
return false;
}
return true;
});
return setMany(pairsWithoutNull, idbKeyValStore);
},
clear: () => clear(idbKeyValStore),
getAllKeys: () => keys(idbKeyValStore),
getItem: (key) =>
get(key, idbKeyValStore)
// idb-keyval returns undefined for missing items, but this needs to return null so that idb-keyval does the same thing as SQLiteStorage.
.then((val) => (val === undefined ? null : val)),
removeItem: (key) => del(key, idbKeyValStore),
removeItems: (keysParam) => delMany(keysParam, idbKeyValStore),
getDatabaseSize() {
if (!window.navigator || !window.navigator.storage) {
throw new Error('StorageManager browser API unavailable');
}
return window.navigator.storage
.estimate()
.then((value) => ({
bytesUsed: value.usage ?? 0,
bytesRemaining: (value.quota ?? 0) - (value.usage ?? 0),
}))
.catch((error) => {
throw new Error(`Unable to estimate web storage quota. Original error: ${error}`);
});
},
};
export default provider;