react-native-onyx 2.0.4 → 2.0.5

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/Onyx.js CHANGED
@@ -486,7 +486,7 @@ function keysChanged(collectionKey, partialCollection, notifyRegularSubscibers =
486
486
  const data = cachedCollection[subscriber.key];
487
487
  const previousData = prevState[subscriber.statePropertyName];
488
488
  // Avoids triggering unnecessary re-renders when feeding empty objects
489
- if (utils_1.default.areObjectsEmpty(data, previousData)) {
489
+ if (utils_1.default.isEmptyObject(data) && utils_1.default.isEmptyObject(previousData)) {
490
490
  return null;
491
491
  }
492
492
  if (data === previousData) {
@@ -601,7 +601,7 @@ function keyChanged(key, data, prevData, canUpdateSubscriber = () => true, notif
601
601
  subscriber.withOnyxInstance.setStateProxy((prevState) => {
602
602
  const prevWithOnyxData = prevState[subscriber.statePropertyName];
603
603
  // Avoids triggering unnecessary re-renders when feeding empty objects
604
- if (utils_1.default.areObjectsEmpty(data, prevWithOnyxData)) {
604
+ if (utils_1.default.isEmptyObject(data) && utils_1.default.isEmptyObject(prevWithOnyxData)) {
605
605
  return null;
606
606
  }
607
607
  if (prevWithOnyxData === data) {
@@ -1,122 +1,75 @@
1
- export default instance;
2
- declare const instance: OnyxCache;
1
+ import type { Key, Value } from './storage/providers/types';
2
+ type StorageMap = Record<Key, Value>;
3
3
  /**
4
4
  * In memory cache providing data by reference
5
5
  * Encapsulates Onyx cache related functionality
6
6
  */
7
7
  declare class OnyxCache {
8
- /**
9
- * @private
10
- * Cache of all the storage keys available in persistent storage
11
- * @type {Set<string>}
12
- */
8
+ /** Cache of all the storage keys available in persistent storage */
13
9
  private storageKeys;
14
- /**
15
- * @private
16
- * Unique list of keys maintained in access order (most recent at the end)
17
- * @type {Set<string>}
18
- */
10
+ /** Unique list of keys maintained in access order (most recent at the end) */
19
11
  private recentKeys;
20
- /**
21
- * @private
22
- * A map of cached values
23
- * @type {Record<string, *>}
24
- */
12
+ /** A map of cached values */
25
13
  private storageMap;
26
14
  /**
27
- * @private
28
15
  * Captured pending tasks for already running storage methods
29
16
  * Using a map yields better performance on operations such a delete
30
- * https://www.zhenghao.io/posts/object-vs-map
31
- * @type {Map<string, Promise>}
32
17
  */
33
18
  private pendingPromises;
34
- /**
35
- * Get all the storage keys
36
- * @returns {string[]}
37
- */
38
- getAllKeys(): string[];
19
+ /** Maximum size of the keys store din cache */
20
+ private maxRecentKeysSize;
21
+ constructor();
22
+ /** Get all the storage keys */
23
+ getAllKeys(): Key[];
39
24
  /**
40
25
  * Get a cached value from storage
41
- * @param {string} key
42
- * @param {Boolean} [shouldReindexCache] – This is an LRU cache, and by default accessing a value will make it become last in line to be evicted. This flag can be used to skip that and just access the value directly without side-effects.
43
- * @returns {*}
26
+ * @param [shouldReindexCache] – This is an LRU cache, and by default accessing a value will make it become last in line to be evicted. This flag can be used to skip that and just access the value directly without side-effects.
44
27
  */
45
- getValue(key: string, shouldReindexCache?: boolean | undefined): any;
46
- /**
47
- * Check whether cache has data for the given key
48
- * @param {string} key
49
- * @returns {boolean}
50
- */
51
- hasCacheForKey(key: string): boolean;
52
- /**
53
- * Saves a key in the storage keys list
28
+ getValue(key: Key, shouldReindexCache?: boolean): Value;
29
+ /** Check whether cache has data for the given key */
30
+ hasCacheForKey(key: Key): boolean;
31
+ /** Saves a key in the storage keys list
54
32
  * Serves to keep the result of `getAllKeys` up to date
55
- * @param {string} key
56
33
  */
57
- addKey(key: string): void;
34
+ addKey(key: Key): void;
58
35
  /**
59
36
  * Set's a key value in cache
60
37
  * Adds the key to the storage keys list as well
61
- * @param {string} key
62
- * @param {*} value
63
- * @returns {*} value - returns the cache value
64
38
  */
65
- set(key: string, value: any): any;
66
- /**
67
- * Forget the cached value for the given key
68
- * @param {string} key
69
- */
70
- drop(key: string): void;
39
+ set(key: Key, value: Value): Value;
40
+ /** Forget the cached value for the given key */
41
+ drop(key: Key): void;
71
42
  /**
72
43
  * Deep merge data to cache, any non existing keys will be created
73
- * @param {Record<string, *>} data - a map of (cache) key - values
44
+ * @param data - a map of (cache) key - values
74
45
  */
75
- merge(data: Record<string, any>): void;
46
+ merge(data: StorageMap): void;
76
47
  /**
77
48
  * Check whether the given task is already running
78
- * @param {string} taskName - unique name given for the task
79
- * @returns {*}
49
+ * @param taskName - unique name given for the task
80
50
  */
81
- hasPendingTask(taskName: string): any;
51
+ hasPendingTask(taskName: string): boolean;
82
52
  /**
83
53
  * Use this method to prevent concurrent calls for the same thing
84
54
  * Instead of calling the same task again use the existing promise
85
55
  * provided from this function
86
- * @template T
87
- * @param {string} taskName - unique name given for the task
88
- * @returns {Promise<T>}
56
+ * @param taskName - unique name given for the task
89
57
  */
90
- getTaskPromise<T>(taskName: string): Promise<T>;
58
+ getTaskPromise(taskName: string): Promise<unknown> | undefined;
91
59
  /**
92
60
  * Capture a promise for a given task so other caller can
93
61
  * hook up to the promise if it's still pending
94
- * @template T
95
- * @param {string} taskName - unique name for the task
96
- * @param {Promise<T>} promise
97
- * @returns {Promise<T>}
98
- */
99
- captureTask<T_1>(taskName: string, promise: Promise<T_1>): Promise<T_1>;
100
- /**
101
- * @private
102
- * Adds a key to the top of the recently accessed keys
103
- * @param {string} key
62
+ * @param taskName - unique name for the task
104
63
  */
64
+ captureTask(taskName: string, promise: Promise<unknown>): Promise<unknown>;
65
+ /** Adds a key to the top of the recently accessed keys */
105
66
  private addToAccessedKeys;
106
- /**
107
- * Remove keys that don't fall into the range of recently used keys
108
- */
67
+ /** Remove keys that don't fall into the range of recently used keys */
109
68
  removeLeastRecentlyUsedKeys(): void;
110
- /**
111
- * Set the recent keys list size
112
- * @param {number} limit
113
- */
69
+ /** Set the recent keys list size */
114
70
  setRecentKeysLimit(limit: number): void;
115
- maxRecentKeysSize: number | undefined;
116
- /**
117
- * @param {String} key
118
- * @param {*} value
119
- * @returns {Boolean}
120
- */
121
- hasValueChanged(key: string, value: any): boolean;
71
+ /** Check if the value has changed */
72
+ hasValueChanged(key: Key, value: Value): boolean;
122
73
  }
74
+ declare const instance: OnyxCache;
75
+ export default instance;
package/dist/OnyxCache.js CHANGED
@@ -3,57 +3,31 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
3
3
  return (mod && mod.__esModule) ? mod : { "default": mod };
4
4
  };
5
5
  Object.defineProperty(exports, "__esModule", { value: true });
6
- const underscore_1 = __importDefault(require("underscore"));
7
6
  const fast_equals_1 = require("fast-equals");
7
+ const bindAll_1 = __importDefault(require("lodash/bindAll"));
8
8
  const utils_1 = __importDefault(require("./utils"));
9
- const isDefined = underscore_1.default.negate(underscore_1.default.isUndefined);
10
9
  /**
11
10
  * In memory cache providing data by reference
12
11
  * Encapsulates Onyx cache related functionality
13
12
  */
14
13
  class OnyxCache {
15
14
  constructor() {
16
- /**
17
- * @private
18
- * Cache of all the storage keys available in persistent storage
19
- * @type {Set<string>}
20
- */
15
+ /** Maximum size of the keys store din cache */
16
+ this.maxRecentKeysSize = 0;
21
17
  this.storageKeys = new Set();
22
- /**
23
- * @private
24
- * Unique list of keys maintained in access order (most recent at the end)
25
- * @type {Set<string>}
26
- */
27
18
  this.recentKeys = new Set();
28
- /**
29
- * @private
30
- * A map of cached values
31
- * @type {Record<string, *>}
32
- */
33
19
  this.storageMap = {};
34
- /**
35
- * @private
36
- * Captured pending tasks for already running storage methods
37
- * Using a map yields better performance on operations such a delete
38
- * https://www.zhenghao.io/posts/object-vs-map
39
- * @type {Map<string, Promise>}
40
- */
41
20
  this.pendingPromises = new Map();
42
21
  // bind all public methods to prevent problems with `this`
43
- underscore_1.default.bindAll(this, 'getAllKeys', 'getValue', 'hasCacheForKey', 'addKey', 'set', 'drop', 'merge', 'hasPendingTask', 'getTaskPromise', 'captureTask', 'removeLeastRecentlyUsedKeys', 'setRecentKeysLimit');
22
+ (0, bindAll_1.default)(this, 'getAllKeys', 'getValue', 'hasCacheForKey', 'addKey', 'set', 'drop', 'merge', 'hasPendingTask', 'getTaskPromise', 'captureTask', 'removeLeastRecentlyUsedKeys', 'setRecentKeysLimit');
44
23
  }
45
- /**
46
- * Get all the storage keys
47
- * @returns {string[]}
48
- */
24
+ /** Get all the storage keys */
49
25
  getAllKeys() {
50
26
  return Array.from(this.storageKeys);
51
27
  }
52
28
  /**
53
29
  * Get a cached value from storage
54
- * @param {string} key
55
- * @param {Boolean} [shouldReindexCache] – This is an LRU cache, and by default accessing a value will make it become last in line to be evicted. This flag can be used to skip that and just access the value directly without side-effects.
56
- * @returns {*}
30
+ * @param [shouldReindexCache] – This is an LRU cache, and by default accessing a value will make it become last in line to be evicted. This flag can be used to skip that and just access the value directly without side-effects.
57
31
  */
58
32
  getValue(key, shouldReindexCache = true) {
59
33
  if (shouldReindexCache) {
@@ -61,18 +35,12 @@ class OnyxCache {
61
35
  }
62
36
  return this.storageMap[key];
63
37
  }
64
- /**
65
- * Check whether cache has data for the given key
66
- * @param {string} key
67
- * @returns {boolean}
68
- */
38
+ /** Check whether cache has data for the given key */
69
39
  hasCacheForKey(key) {
70
- return isDefined(this.storageMap[key]);
40
+ return this.storageMap[key] !== undefined;
71
41
  }
72
- /**
73
- * Saves a key in the storage keys list
42
+ /** Saves a key in the storage keys list
74
43
  * Serves to keep the result of `getAllKeys` up to date
75
- * @param {string} key
76
44
  */
77
45
  addKey(key) {
78
46
  this.storageKeys.add(key);
@@ -80,9 +48,6 @@ class OnyxCache {
80
48
  /**
81
49
  * Set's a key value in cache
82
50
  * Adds the key to the storage keys list as well
83
- * @param {string} key
84
- * @param {*} value
85
- * @returns {*} value - returns the cache value
86
51
  */
87
52
  set(key, value) {
88
53
  this.addKey(key);
@@ -90,10 +55,7 @@ class OnyxCache {
90
55
  this.storageMap[key] = value;
91
56
  return value;
92
57
  }
93
- /**
94
- * Forget the cached value for the given key
95
- * @param {string} key
96
- */
58
+ /** Forget the cached value for the given key */
97
59
  drop(key) {
98
60
  delete this.storageMap[key];
99
61
  this.storageKeys.delete(key);
@@ -101,35 +63,30 @@ class OnyxCache {
101
63
  }
102
64
  /**
103
65
  * Deep merge data to cache, any non existing keys will be created
104
- * @param {Record<string, *>} data - a map of (cache) key - values
66
+ * @param data - a map of (cache) key - values
105
67
  */
106
68
  merge(data) {
107
- if (!underscore_1.default.isObject(data) || underscore_1.default.isArray(data)) {
69
+ if (typeof data !== 'object' || Array.isArray(data)) {
108
70
  throw new Error('data passed to cache.merge() must be an Object of onyx key/value pairs');
109
71
  }
110
- // lodash adds a small overhead so we don't use it here
111
- // eslint-disable-next-line prefer-object-spread, rulesdir/prefer-underscore-method
112
72
  this.storageMap = Object.assign({}, utils_1.default.fastMerge(this.storageMap, data, false));
113
73
  const storageKeys = this.getAllKeys();
114
- const mergedKeys = underscore_1.default.keys(data);
74
+ const mergedKeys = Object.keys(data);
115
75
  this.storageKeys = new Set([...storageKeys, ...mergedKeys]);
116
- underscore_1.default.each(mergedKeys, (key) => this.addToAccessedKeys(key));
76
+ mergedKeys.forEach((key) => this.addToAccessedKeys(key));
117
77
  }
118
78
  /**
119
79
  * Check whether the given task is already running
120
- * @param {string} taskName - unique name given for the task
121
- * @returns {*}
80
+ * @param taskName - unique name given for the task
122
81
  */
123
82
  hasPendingTask(taskName) {
124
- return isDefined(this.pendingPromises.get(taskName));
83
+ return this.pendingPromises.get(taskName) !== undefined;
125
84
  }
126
85
  /**
127
86
  * Use this method to prevent concurrent calls for the same thing
128
87
  * Instead of calling the same task again use the existing promise
129
88
  * provided from this function
130
- * @template T
131
- * @param {string} taskName - unique name given for the task
132
- * @returns {Promise<T>}
89
+ * @param taskName - unique name given for the task
133
90
  */
134
91
  getTaskPromise(taskName) {
135
92
  return this.pendingPromises.get(taskName);
@@ -137,10 +94,7 @@ class OnyxCache {
137
94
  /**
138
95
  * Capture a promise for a given task so other caller can
139
96
  * hook up to the promise if it's still pending
140
- * @template T
141
- * @param {string} taskName - unique name for the task
142
- * @param {Promise<T>} promise
143
- * @returns {Promise<T>}
97
+ * @param taskName - unique name for the task
144
98
  */
145
99
  captureTask(taskName, promise) {
146
100
  const returnPromise = promise.finally(() => {
@@ -149,19 +103,12 @@ class OnyxCache {
149
103
  this.pendingPromises.set(taskName, returnPromise);
150
104
  return returnPromise;
151
105
  }
152
- /**
153
- * @private
154
- * Adds a key to the top of the recently accessed keys
155
- * @param {string} key
156
- */
106
+ /** Adds a key to the top of the recently accessed keys */
157
107
  addToAccessedKeys(key) {
158
- // Removing and re-adding a key ensures it's at the end of the list
159
108
  this.recentKeys.delete(key);
160
109
  this.recentKeys.add(key);
161
110
  }
162
- /**
163
- * Remove keys that don't fall into the range of recently used keys
164
- */
111
+ /** Remove keys that don't fall into the range of recently used keys */
165
112
  removeLeastRecentlyUsedKeys() {
166
113
  let numKeysToRemove = this.recentKeys.size - this.maxRecentKeysSize;
167
114
  if (numKeysToRemove <= 0) {
@@ -174,23 +121,17 @@ class OnyxCache {
174
121
  temp.push(value);
175
122
  numKeysToRemove--;
176
123
  }
124
+ // eslint-disable-next-line @typescript-eslint/prefer-for-of
177
125
  for (let i = 0; i < temp.length; ++i) {
178
126
  delete this.storageMap[temp[i]];
179
127
  this.recentKeys.delete(temp[i]);
180
128
  }
181
129
  }
182
- /**
183
- * Set the recent keys list size
184
- * @param {number} limit
185
- */
130
+ /** Set the recent keys list size */
186
131
  setRecentKeysLimit(limit) {
187
132
  this.maxRecentKeysSize = limit;
188
133
  }
189
- /**
190
- * @param {String} key
191
- * @param {*} value
192
- * @returns {Boolean}
193
- */
134
+ /** Check if the value has changed */
194
135
  hasValueChanged(key, value) {
195
136
  return !(0, fast_equals_1.deepEqual)(this.storageMap[key], value);
196
137
  }
@@ -1,2 +1,2 @@
1
- export default SQLiteStorage;
2
1
  import SQLiteStorage from './providers/SQLiteStorage';
2
+ export default SQLiteStorage;
@@ -1,19 +1,3 @@
1
+ import type StorageProvider from './providers/types';
2
+ declare const webStorage: StorageProvider;
1
3
  export default webStorage;
2
- declare const webStorage: {
3
- /**
4
- * @param {Function} onStorageKeyChanged Storage synchronization mechanism keeping all opened tabs in sync
5
- */
6
- keepInstancesSync(onStorageKeyChanged: Function): void;
7
- setItem: (key: string, value: any) => Promise<void>;
8
- multiGet: (keysParam: string[]) => Promise<[key, value][]>;
9
- multiMerge: (pairs: [key, value][]) => Promise<void>;
10
- mergeItem(key: string, _changes: any, modifiedData: any): Promise<void>;
11
- multiSet: (pairs: [key, value][]) => Promise<void>;
12
- clear: () => Promise<void>;
13
- setMemoryOnlyKeys: () => void;
14
- getAllKeys: () => Promise<string[]>;
15
- getItem: (key: string) => Promise<any>;
16
- removeItem: (key: string) => Promise<void>;
17
- removeItems: (keysParam: any[]) => Promise<any>;
18
- getDatabaseSize(): Promise<number>;
19
- };
@@ -8,25 +8,23 @@ Object.defineProperty(exports, "__esModule", { value: true });
8
8
  * when using LocalStorage APIs in the browser. These events are great because multiple tabs can listen for when
9
9
  * data changes and then stay up-to-date with everything happening in Onyx.
10
10
  */
11
- const underscore_1 = __importDefault(require("underscore"));
12
11
  const IDBKeyVal_1 = __importDefault(require("./providers/IDBKeyVal"));
13
12
  const SYNC_ONYX = 'SYNC_ONYX';
14
13
  /**
15
14
  * Raise an event thorough `localStorage` to let other tabs know a value changed
16
- * @param {String} onyxKey
17
15
  */
18
16
  function raiseStorageSyncEvent(onyxKey) {
19
17
  global.localStorage.setItem(SYNC_ONYX, onyxKey);
20
- global.localStorage.removeItem(SYNC_ONYX, onyxKey);
18
+ global.localStorage.removeItem(SYNC_ONYX);
21
19
  }
22
20
  function raiseStorageSyncManyKeysEvent(onyxKeys) {
23
- underscore_1.default.each(onyxKeys, (onyxKey) => {
21
+ onyxKeys.forEach((onyxKey) => {
24
22
  raiseStorageSyncEvent(onyxKey);
25
23
  });
26
24
  }
27
25
  const webStorage = Object.assign(Object.assign({}, IDBKeyVal_1.default), {
28
26
  /**
29
- * @param {Function} onStorageKeyChanged Storage synchronization mechanism keeping all opened tabs in sync
27
+ * @param onStorageKeyChanged Storage synchronization mechanism keeping all opened tabs in sync
30
28
  */
31
29
  keepInstancesSync(onStorageKeyChanged) {
32
30
  // Override set, remove and clear to raise storage events that we intercept in other tabs
@@ -48,7 +46,7 @@ const webStorage = Object.assign(Object.assign({}, IDBKeyVal_1.default), {
48
46
  .then(() => {
49
47
  // Now that storage is cleared, the storage sync event can happen which is a more atomic action
50
48
  // for other browser tabs
51
- underscore_1.default.each(allKeys, raiseStorageSyncEvent);
49
+ allKeys.forEach(raiseStorageSyncEvent);
52
50
  });
53
51
  };
54
52
  // This listener will only be triggered by events coming from other tabs
@@ -1,23 +1,23 @@
1
- export default idbKeyvalMockSpy;
2
- declare namespace idbKeyvalMockSpy {
3
- export { set as idbKeyvalSet };
4
- export let setItem: jest.Mock<Promise<any>, [key?: any, value?: any]>;
5
- export let getItem: jest.Mock<Promise<any>, [key?: any]>;
6
- export let removeItem: jest.Mock<Promise<void>, [key?: any]>;
7
- export let removeItems: jest.Mock<Promise<void>, [keys?: any]>;
8
- export let clear: jest.Mock<Promise<void>, []>;
9
- export let getAllKeys: jest.Mock<Promise<any>, []>;
10
- export let config: jest.Mock<void, []>;
11
- export let multiGet: jest.Mock<Promise<any>, [keys?: any]>;
12
- export let multiSet: jest.Mock<Promise<any>, [pairs?: any]>;
13
- export let multiMerge: jest.Mock<Promise<{}>, [pairs?: any]>;
14
- export let mergeItem: jest.Mock<Promise<any>, [key?: any, _changes?: any, modifiedData?: any]>;
15
- export let getStorageMap: jest.Mock<{}, []>;
16
- export let setInitialMockData: jest.Mock<void, [data?: any]>;
17
- export let getDatabaseSize: jest.Mock<Promise<{
18
- bytesRemaining: number;
1
+ /// <reference types="jest" />
2
+ import type { KeyValuePairList } from '../providers/types';
3
+ declare const idbKeyvalMockSpy: {
4
+ idbKeyvalSet: jest.Mock<Promise<any>, [key: any, value: any]>;
5
+ setItem: jest.Mock<Promise<void | import("react-native-quick-sqlite").QueryResult>, [key: string, value: IDBValidKey]>;
6
+ getItem: jest.Mock<Promise<IDBValidKey | null>, [key: string]>;
7
+ removeItem: jest.Mock<Promise<void | import("react-native-quick-sqlite").QueryResult>, [key: string]>;
8
+ removeItems: jest.Mock<Promise<void | import("react-native-quick-sqlite").QueryResult>, [keys: import("../providers/types").KeyList]>;
9
+ clear: jest.Mock<Promise<void | import("react-native-quick-sqlite").QueryResult>, []>;
10
+ getAllKeys: jest.Mock<Promise<import("../providers/types").KeyList>, []>;
11
+ multiGet: jest.Mock<Promise<KeyValuePairList>, [keys: import("../providers/types").KeyList]>;
12
+ multiSet: jest.Mock<Promise<void | import("react-native-quick-sqlite").BatchQueryResult>, [pairs: KeyValuePairList]>;
13
+ multiMerge: jest.Mock<Promise<import("react-native-quick-sqlite").BatchQueryResult | IDBValidKey[]>, [pairs: KeyValuePairList]>;
14
+ mergeItem: jest.Mock<Promise<void | import("react-native-quick-sqlite").BatchQueryResult>, [key: string, changes: IDBValidKey, modifiedData: IDBValidKey]>;
15
+ getStorageMap: jest.Mock<Record<string, IDBValidKey>, []>;
16
+ setInitialMockData: jest.Mock<void, [data: any]>;
17
+ getDatabaseSize: jest.Mock<Promise<{
19
18
  bytesUsed: number;
19
+ bytesRemaining: number;
20
20
  }>, []>;
21
- export let setMemoryOnlyKeys: jest.Mock<void, []>;
22
- }
23
- declare const set: jest.Mock<Promise<any>, [key?: any, value?: any]>;
21
+ setMemoryOnlyKeys: jest.Mock<void, []>;
22
+ };
23
+ export default idbKeyvalMockSpy;
@@ -3,7 +3,6 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
3
3
  return (mod && mod.__esModule) ? mod : { "default": mod };
4
4
  };
5
5
  Object.defineProperty(exports, "__esModule", { value: true });
6
- const underscore_1 = __importDefault(require("underscore"));
7
6
  const utils_1 = __importDefault(require("../../utils"));
8
7
  let storageMapInternal = {};
9
8
  const set = jest.fn((key, value) => {
@@ -15,19 +14,20 @@ const idbKeyvalMock = {
15
14
  return set(key, value);
16
15
  },
17
16
  multiSet(pairs) {
18
- const setPromises = underscore_1.default.map(pairs, ([key, value]) => this.setItem(key, value));
17
+ const setPromises = pairs.map(([key, value]) => this.setItem(key, value));
19
18
  return new Promise((resolve) => Promise.all(setPromises).then(() => resolve(storageMapInternal)));
20
19
  },
21
20
  getItem(key) {
22
21
  return Promise.resolve(storageMapInternal[key]);
23
22
  },
24
23
  multiGet(keys) {
25
- const getPromises = underscore_1.default.map(keys, (key) => new Promise((resolve) => this.getItem(key).then((value) => resolve([key, value]))));
24
+ const getPromises = keys.map((key) => new Promise((resolve) => this.getItem(key).then((value) => resolve([key, value]))));
26
25
  return Promise.all(getPromises);
27
26
  },
28
27
  multiMerge(pairs) {
29
- underscore_1.default.forEach(pairs, ([key, value]) => {
28
+ pairs.forEach(([key, value]) => {
30
29
  const existingValue = storageMapInternal[key];
30
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
31
31
  const newValue = utils_1.default.fastMerge(existingValue, value);
32
32
  set(key, newValue);
33
33
  });
@@ -41,7 +41,7 @@ const idbKeyvalMock = {
41
41
  return Promise.resolve();
42
42
  },
43
43
  removeItems(keys) {
44
- underscore_1.default.each(keys, (key) => {
44
+ keys.forEach((key) => {
45
45
  delete storageMapInternal[key];
46
46
  });
47
47
  return Promise.resolve();
@@ -51,12 +51,12 @@ const idbKeyvalMock = {
51
51
  return Promise.resolve();
52
52
  },
53
53
  getAllKeys() {
54
- return Promise.resolve(underscore_1.default.keys(storageMapInternal));
54
+ return Promise.resolve(Object.keys(storageMapInternal));
55
55
  },
56
- config() { },
57
56
  getDatabaseSize() {
58
57
  return Promise.resolve({ bytesRemaining: 0, bytesUsed: 99999 });
59
58
  },
59
+ // eslint-disable-next-line @typescript-eslint/no-empty-function
60
60
  setMemoryOnlyKeys() { },
61
61
  };
62
62
  const idbKeyvalMockSpy = {
@@ -67,7 +67,6 @@ const idbKeyvalMockSpy = {
67
67
  removeItems: jest.fn(idbKeyvalMock.removeItems),
68
68
  clear: jest.fn(idbKeyvalMock.clear),
69
69
  getAllKeys: jest.fn(idbKeyvalMock.getAllKeys),
70
- config: jest.fn(idbKeyvalMock.config),
71
70
  multiGet: jest.fn(idbKeyvalMock.multiGet),
72
71
  multiSet: jest.fn(idbKeyvalMock.multiSet),
73
72
  multiMerge: jest.fn(idbKeyvalMock.multiMerge),
@@ -1,2 +1,2 @@
1
- export default WebStorage;
2
1
  import WebStorage from './WebStorage';
2
+ export default WebStorage;
@@ -1,2 +1,2 @@
1
- export default NativeStorage;
2
1
  import NativeStorage from './NativeStorage';
2
+ export default NativeStorage;
@@ -1,26 +1,3 @@
1
+ import type StorageProvider from './types';
2
+ declare const provider: StorageProvider;
1
3
  export default provider;
2
- declare namespace provider {
3
- function setItem(key: string, value: any): Promise<void>;
4
- function multiGet(keysParam: string[]): Promise<[key, value][]>;
5
- function multiMerge(pairs: [key, value][]): Promise<void>;
6
- /**
7
- * Merging an existing value with a new one
8
- * @param {String} key
9
- * @param {any} _changes - not used, as we rely on the pre-merged data from the `modifiedData`
10
- * @param {any} modifiedData - the pre-merged data from `Onyx.applyMerge`
11
- * @return {Promise<void>}
12
- */
13
- function mergeItem(key: string, _changes: any, modifiedData: any): Promise<void>;
14
- function multiSet(pairs: [key, value][]): Promise<void>;
15
- function clear(): Promise<void>;
16
- function setMemoryOnlyKeys(): void;
17
- function getAllKeys(): Promise<string[]>;
18
- function getItem(key: string): Promise<any>;
19
- function removeItem(key: string): Promise<void>;
20
- function removeItems(keysParam: any[]): Promise<any>;
21
- /**
22
- * Gets the total bytes of the database file
23
- * @returns {Promise<number>}
24
- */
25
- function getDatabaseSize(): Promise<number>;
26
- }
@@ -4,115 +4,60 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
4
4
  };
5
5
  Object.defineProperty(exports, "__esModule", { value: true });
6
6
  const idb_keyval_1 = require("idb-keyval");
7
- const underscore_1 = __importDefault(require("underscore"));
8
7
  const utils_1 = __importDefault(require("../../utils"));
9
8
  // We don't want to initialize the store while the JS bundle loads as idb-keyval will try to use global.indexedDB
10
9
  // which might not be available in certain environments that load the bundle (e.g. electron main process).
11
10
  let customStoreInstance;
12
- const getCustomStore = () => {
11
+ function getCustomStore() {
13
12
  if (!customStoreInstance) {
14
13
  customStoreInstance = (0, idb_keyval_1.createStore)('OnyxDB', 'keyvaluepairs');
15
14
  }
16
15
  return customStoreInstance;
17
- };
16
+ }
18
17
  const provider = {
19
- /**
20
- * Sets the value for a given key. The only requirement is that the value should be serializable to JSON string
21
- * @param {String} key
22
- * @param {*} value
23
- * @return {Promise<void>}
24
- */
25
18
  setItem: (key, value) => (0, idb_keyval_1.set)(key, value, getCustomStore()),
26
- /**
27
- * Get multiple key-value pairs for the give array of keys in a batch.
28
- * This is optimized to use only one database transaction.
29
- * @param {String[]} keysParam
30
- * @return {Promise<Array<[key, value]>>}
31
- */
32
- multiGet: (keysParam) => (0, idb_keyval_1.getMany)(keysParam, getCustomStore()).then((values) => underscore_1.default.map(values, (value, index) => [keysParam[index], value])),
33
- /**
34
- * Multiple merging of existing and new values in a batch
35
- * @param {Array<[key, value]>} pairs
36
- * This function also removes all nested null values from an object.
37
- * @return {Promise<void>}
38
- */
19
+ multiGet: (keysParam) => (0, idb_keyval_1.getMany)(keysParam, getCustomStore()).then((values) => values.map((value, index) => [keysParam[index], value])),
39
20
  multiMerge: (pairs) => getCustomStore()('readwrite', (store) => {
40
21
  // Note: we are using the manual store transaction here, to fit the read and update
41
22
  // of the items in one transaction to achieve best performance.
42
- const getValues = Promise.all(underscore_1.default.map(pairs, ([key]) => (0, idb_keyval_1.promisifyRequest)(store.get(key))));
23
+ const getValues = Promise.all(pairs.map(([key]) => (0, idb_keyval_1.promisifyRequest)(store.get(key))));
43
24
  return getValues.then((values) => {
44
- const upsertMany = underscore_1.default.map(pairs, ([key, value], index) => {
25
+ const upsertMany = pairs.map(([key, value], index) => {
45
26
  const prev = values[index];
27
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
46
28
  const newValue = utils_1.default.fastMerge(prev, value);
47
29
  return (0, idb_keyval_1.promisifyRequest)(store.put(newValue, key));
48
30
  });
49
31
  return Promise.all(upsertMany);
50
32
  });
51
33
  }),
52
- /**
53
- * Merging an existing value with a new one
54
- * @param {String} key
55
- * @param {any} _changes - not used, as we rely on the pre-merged data from the `modifiedData`
56
- * @param {any} modifiedData - the pre-merged data from `Onyx.applyMerge`
57
- * @return {Promise<void>}
58
- */
59
34
  mergeItem(key, _changes, modifiedData) {
60
35
  // Since Onyx also merged the existing value with the changes, we can just set the value directly
61
36
  return provider.setItem(key, modifiedData);
62
37
  },
63
- /**
64
- * Stores multiple key-value pairs in a batch
65
- * @param {Array<[key, value]>} pairs
66
- * @return {Promise<void>}
67
- */
68
38
  multiSet: (pairs) => (0, idb_keyval_1.setMany)(pairs, getCustomStore()),
69
- /**
70
- * Clear everything from storage and also stops the SyncQueue from adding anything more to storage
71
- * @returns {Promise<void>}
72
- */
73
39
  clear: () => (0, idb_keyval_1.clear)(getCustomStore()),
74
- // This is a noop for now in order to keep clients from crashing see https://github.com/Expensify/Expensify/issues/312438
40
+ // eslint-disable-next-line @typescript-eslint/no-empty-function
75
41
  setMemoryOnlyKeys: () => { },
76
- /**
77
- * Returns all keys available in storage
78
- * @returns {Promise<String[]>}
79
- */
80
42
  getAllKeys: () => (0, idb_keyval_1.keys)(getCustomStore()),
81
- /**
82
- * Get the value of a given key or return `null` if it's not available in storage
83
- * @param {String} key
84
- * @return {Promise<*>}
85
- */
86
43
  getItem: (key) => (0, idb_keyval_1.get)(key, getCustomStore())
87
44
  // idb-keyval returns undefined for missing items, but this needs to return null so that idb-keyval does the same thing as SQLiteStorage.
88
45
  .then((val) => (val === undefined ? null : val)),
89
- /**
90
- * Remove given key and it's value from storage
91
- * @param {String} key
92
- * @returns {Promise<void>}
93
- */
94
46
  removeItem: (key) => (0, idb_keyval_1.del)(key, getCustomStore()),
95
- /**
96
- * Remove given keys and their values from storage
97
- *
98
- * @param {Array} keysParam
99
- * @returns {Promise}
100
- */
101
47
  removeItems: (keysParam) => (0, idb_keyval_1.delMany)(keysParam, getCustomStore()),
102
- /**
103
- * Gets the total bytes of the database file
104
- * @returns {Promise<number>}
105
- */
106
48
  getDatabaseSize() {
107
49
  if (!window.navigator || !window.navigator.storage) {
108
50
  throw new Error('StorageManager browser API unavailable');
109
51
  }
110
52
  return window.navigator.storage
111
53
  .estimate()
112
- .then((value) => ({
113
- bytesUsed: value.usage,
114
- bytesRemaining: value.quota - value.usage,
115
- }))
54
+ .then((value) => {
55
+ var _a, _b, _c;
56
+ return ({
57
+ bytesUsed: (_a = value.usage) !== null && _a !== void 0 ? _a : 0,
58
+ bytesRemaining: ((_b = value.quota) !== null && _b !== void 0 ? _b : 0) - ((_c = value.usage) !== null && _c !== void 0 ? _c : 0),
59
+ });
60
+ })
116
61
  .catch((error) => {
117
62
  throw new Error(`Unable to estimate web storage quota. Original error: ${error}`);
118
63
  });
@@ -1,52 +1,3 @@
1
+ import type StorageProvider from './types';
2
+ declare const provider: StorageProvider;
1
3
  export default provider;
2
- declare namespace provider {
3
- /**
4
- * Get the value of a given key or return `null` if it's not available in storage
5
- * @param {String} key
6
- * @return {Promise<*>}
7
- */
8
- function getItem(key: string): Promise<any>;
9
- /**
10
- * Get multiple key-value pairs for the given array of keys in a batch
11
- * @param {String[]} keys
12
- * @return {Promise<Array<[key, value]>>}
13
- */
14
- function multiGet(keys: string[]): Promise<[key, value][]>;
15
- /**
16
- * Sets the value for a given key. The only requirement is that the value should be serializable to JSON string
17
- * @param {String} key
18
- * @param {*} value
19
- * @return {Promise<void>}
20
- */
21
- function setItem(key: string, value: any): Promise<void>;
22
- /**
23
- * Stores multiple key-value pairs in a batch
24
- * @param {Array<[key, value]>} pairs
25
- * @return {Promise<void>}
26
- */
27
- function multiSet(pairs: [key, value][]): Promise<void>;
28
- /**
29
- * Multiple merging of existing and new values in a batch
30
- * @param {Array<[key, value]>} pairs
31
- * @return {Promise<void>}
32
- */
33
- function multiMerge(pairs: [key, value][]): Promise<void>;
34
- /**
35
- * Merges an existing value with a new one by leveraging JSON_PATCH
36
- * @param {String} key
37
- * @param {*} changes - the delta for a specific key
38
- * @return {Promise<void>}
39
- */
40
- function mergeItem(key: string, changes: any): Promise<void>;
41
- function getAllKeys(): Promise<string[]>;
42
- function removeItem(key: string): Promise<void>;
43
- function removeItems(keys: string[]): Promise<void>;
44
- function clear(): Promise<void>;
45
- function setMemoryOnlyKeys(): void;
46
- /**
47
- * Gets the total bytes of the database file
48
- * @returns {Promise}
49
- */
50
- function getDatabaseSize(): Promise<any>;
51
- function keepInstancesSync(): void;
52
- }
@@ -3,13 +3,9 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
3
3
  return (mod && mod.__esModule) ? mod : { "default": mod };
4
4
  };
5
5
  Object.defineProperty(exports, "__esModule", { value: true });
6
- /**
7
- * The SQLiteStorage provider stores everything in a key/value store by
8
- * converting the value to a JSON string
9
- */
10
6
  const react_native_quick_sqlite_1 = require("react-native-quick-sqlite");
11
7
  const react_native_device_info_1 = require("react-native-device-info");
12
- const underscore_1 = __importDefault(require("underscore"));
8
+ const utils_1 = __importDefault(require("../../utils"));
13
9
  const DB_NAME = 'OnyxDB';
14
10
  const db = (0, react_native_quick_sqlite_1.open)({ name: DB_NAME });
15
11
  db.execute('CREATE TABLE IF NOT EXISTS keyvaluepairs (record_key TEXT NOT NULL PRIMARY KEY , valueJSON JSON NOT NULL) WITHOUT ROWID;');
@@ -19,60 +15,34 @@ db.execute('PRAGMA CACHE_SIZE=-20000;');
19
15
  db.execute('PRAGMA synchronous=NORMAL;');
20
16
  db.execute('PRAGMA journal_mode=WAL;');
21
17
  const provider = {
22
- /**
23
- * Get the value of a given key or return `null` if it's not available in storage
24
- * @param {String} key
25
- * @return {Promise<*>}
26
- */
27
18
  getItem(key) {
28
19
  return db.executeAsync('SELECT record_key, valueJSON FROM keyvaluepairs WHERE record_key = ?;', [key]).then(({ rows }) => {
29
- if (rows.length === 0) {
20
+ if (!rows || (rows === null || rows === void 0 ? void 0 : rows.length) === 0) {
30
21
  return null;
31
22
  }
32
- const result = rows.item(0);
23
+ const result = rows === null || rows === void 0 ? void 0 : rows.item(0);
33
24
  return JSON.parse(result.valueJSON);
34
25
  });
35
26
  },
36
- /**
37
- * Get multiple key-value pairs for the given array of keys in a batch
38
- * @param {String[]} keys
39
- * @return {Promise<Array<[key, value]>>}
40
- */
41
27
  multiGet(keys) {
42
- const placeholders = underscore_1.default.map(keys, () => '?').join(',');
28
+ const placeholders = keys.map(() => '?').join(',');
43
29
  const command = `SELECT record_key, valueJSON FROM keyvaluepairs WHERE record_key IN (${placeholders});`;
44
30
  return db.executeAsync(command, keys).then(({ rows }) => {
45
31
  // eslint-disable-next-line no-underscore-dangle
46
- const result = underscore_1.default.map(rows._array, (row) => [row.record_key, JSON.parse(row.valueJSON)]);
47
- return result;
32
+ const result = rows === null || rows === void 0 ? void 0 : rows._array.map((row) => [row.record_key, JSON.parse(row.valueJSON)]);
33
+ return (result !== null && result !== void 0 ? result : []);
48
34
  });
49
35
  },
50
- /**
51
- * Sets the value for a given key. The only requirement is that the value should be serializable to JSON string
52
- * @param {String} key
53
- * @param {*} value
54
- * @return {Promise<void>}
55
- */
56
36
  setItem(key, value) {
57
37
  return db.executeAsync('REPLACE INTO keyvaluepairs (record_key, valueJSON) VALUES (?, ?);', [key, JSON.stringify(value)]);
58
38
  },
59
- /**
60
- * Stores multiple key-value pairs in a batch
61
- * @param {Array<[key, value]>} pairs
62
- * @return {Promise<void>}
63
- */
64
39
  multiSet(pairs) {
65
- const stringifiedPairs = underscore_1.default.map(pairs, (pair) => [pair[0], JSON.stringify(underscore_1.default.isUndefined(pair[1]) ? null : pair[1])]);
66
- if (underscore_1.default.isEmpty(stringifiedPairs)) {
40
+ const stringifiedPairs = pairs.map((pair) => [pair[0], JSON.stringify(pair[1] === undefined ? null : pair[1])]);
41
+ if (utils_1.default.isEmptyObject(stringifiedPairs)) {
67
42
  return Promise.resolve();
68
43
  }
69
44
  return db.executeBatchAsync([['REPLACE INTO keyvaluepairs (record_key, valueJSON) VALUES (?, json(?));', stringifiedPairs]]);
70
45
  },
71
- /**
72
- * Multiple merging of existing and new values in a batch
73
- * @param {Array<[key, value]>} pairs
74
- * @return {Promise<void>}
75
- */
76
46
  multiMerge(pairs) {
77
47
  // Note: We use `ON CONFLICT DO UPDATE` here instead of `INSERT OR REPLACE INTO`
78
48
  // so the new JSON value is merged into the old one if there's an existing value
@@ -81,74 +51,42 @@ const provider = {
81
51
  ON CONFLICT DO UPDATE
82
52
  SET valueJSON = JSON_PATCH(valueJSON, JSON(:value));
83
53
  `;
84
- const nonNullishPairs = underscore_1.default.filter(pairs, (pair) => !underscore_1.default.isUndefined(pair[1]));
85
- const queryArguments = underscore_1.default.map(nonNullishPairs, (pair) => {
54
+ const nonNullishPairs = pairs.filter((pair) => pair[1] !== undefined);
55
+ const queryArguments = nonNullishPairs.map((pair) => {
86
56
  const value = JSON.stringify(pair[1]);
87
57
  return [pair[0], value];
88
58
  });
89
59
  return db.executeBatchAsync([[query, queryArguments]]);
90
60
  },
91
- /**
92
- * Merges an existing value with a new one by leveraging JSON_PATCH
93
- * @param {String} key
94
- * @param {*} changes - the delta for a specific key
95
- * @return {Promise<void>}
96
- */
97
61
  mergeItem(key, changes) {
98
62
  return this.multiMerge([[key, changes]]);
99
63
  },
100
- /**
101
- * Returns all keys available in storage
102
- * @returns {Promise<String[]>}
103
- */
104
64
  getAllKeys: () => db.executeAsync('SELECT record_key FROM keyvaluepairs;').then(({ rows }) => {
105
65
  // eslint-disable-next-line no-underscore-dangle
106
- const result = underscore_1.default.map(rows._array, (row) => row.record_key);
107
- return result;
66
+ const result = rows === null || rows === void 0 ? void 0 : rows._array.map((row) => row.record_key);
67
+ return (result !== null && result !== void 0 ? result : []);
108
68
  }),
109
- /**
110
- * Removes given key and it's value from storage
111
- * @param {String} key
112
- * @returns {Promise<void>}
113
- */
114
69
  removeItem: (key) => db.executeAsync('DELETE FROM keyvaluepairs WHERE record_key = ?;', [key]),
115
- /**
116
- * Removes given keys and their values from storage
117
- *
118
- * @param {Array<String>} keys
119
- * @returns {Promise<void>}
120
- */
121
70
  removeItems: (keys) => {
122
- const placeholders = underscore_1.default.map(keys, () => '?').join(',');
71
+ const placeholders = keys.map(() => '?').join(',');
123
72
  const query = `DELETE FROM keyvaluepairs WHERE record_key IN (${placeholders});`;
124
73
  return db.executeAsync(query, keys);
125
74
  },
126
- /**
127
- * Clears absolutely everything from storage
128
- * @returns {Promise<void>}
129
- */
130
75
  clear: () => db.executeAsync('DELETE FROM keyvaluepairs;', []),
131
- /**
132
- * Noop on mobile for now.
133
- */
134
- setMemoryOnlyKeys: () => { },
135
- /**
136
- * Gets the total bytes of the database file
137
- * @returns {Promise}
138
- */
139
76
  getDatabaseSize() {
140
77
  return Promise.all([db.executeAsync('PRAGMA page_size;'), db.executeAsync('PRAGMA page_count;'), (0, react_native_device_info_1.getFreeDiskStorage)()]).then(([pageSizeResult, pageCountResult, bytesRemaining]) => {
141
- const pageSize = pageSizeResult.rows.item(0).page_size;
142
- const pageCount = pageCountResult.rows.item(0).page_count;
78
+ var _a, _b;
79
+ const pageSize = (_a = pageSizeResult.rows) === null || _a === void 0 ? void 0 : _a.item(0).page_size;
80
+ const pageCount = (_b = pageCountResult.rows) === null || _b === void 0 ? void 0 : _b.item(0).page_count;
143
81
  return {
144
82
  bytesUsed: pageSize * pageCount,
145
83
  bytesRemaining,
146
84
  };
147
85
  });
148
86
  },
149
- /**
150
- * Noop on native
151
- */
87
+ // eslint-disable-next-line @typescript-eslint/no-empty-function
88
+ setMemoryOnlyKeys: () => { },
89
+ // eslint-disable-next-line @typescript-eslint/no-empty-function
152
90
  keepInstancesSync: () => { },
153
91
  };
154
92
  exports.default = provider;
@@ -0,0 +1,68 @@
1
+ import type { BatchQueryResult, QueryResult } from 'react-native-quick-sqlite';
2
+ type Key = string;
3
+ type Value = IDBValidKey;
4
+ type KeyValuePair = [Key, Value];
5
+ type KeyList = Key[];
6
+ type KeyValuePairList = KeyValuePair[];
7
+ type OnStorageKeyChanged = (key: Key, value: Value | null) => void;
8
+ type StorageProvider = {
9
+ /**
10
+ * Gets the value of a given key or return `null` if it's not available in storage
11
+ */
12
+ getItem: (key: Key) => Promise<Value | null>;
13
+ /**
14
+ * Get multiple key-value pairs for the given array of keys in a batch
15
+ */
16
+ multiGet: (keys: KeyList) => Promise<KeyValuePairList>;
17
+ /**
18
+ * Sets the value for a given key. The only requirement is that the value should be serializable to JSON string
19
+ */
20
+ setItem: (key: Key, value: Value) => Promise<QueryResult | void>;
21
+ /**
22
+ * Stores multiple key-value pairs in a batch
23
+ */
24
+ multiSet: (pairs: KeyValuePairList) => Promise<BatchQueryResult | void>;
25
+ /**
26
+ * Multiple merging of existing and new values in a batch
27
+ */
28
+ multiMerge: (pairs: KeyValuePairList) => Promise<BatchQueryResult | IDBValidKey[]>;
29
+ /**
30
+ * Merges an existing value with a new one by leveraging JSON_PATCH
31
+ * @param changes - the delta for a specific key
32
+ * @param modifiedData - the pre-merged data from `Onyx.applyMerge`
33
+ */
34
+ mergeItem: (key: Key, changes: Value, modifiedData: Value) => Promise<BatchQueryResult | void>;
35
+ /**
36
+ * Returns all keys available in storage
37
+ */
38
+ getAllKeys: () => Promise<KeyList>;
39
+ /**
40
+ * Removes given key and its value from storage
41
+ */
42
+ removeItem: (key: Key) => Promise<QueryResult | void>;
43
+ /**
44
+ * Removes given keys and their values from storage
45
+ */
46
+ removeItems: (keys: KeyList) => Promise<QueryResult | void>;
47
+ /**
48
+ * Clears absolutely everything from storage
49
+ */
50
+ clear: () => Promise<QueryResult | void>;
51
+ /**
52
+ * Sets memory only keys
53
+ */
54
+ setMemoryOnlyKeys: () => void;
55
+ /**
56
+ * Gets the total bytes of the database file
57
+ */
58
+ getDatabaseSize: () => Promise<{
59
+ bytesUsed: number;
60
+ bytesRemaining: number;
61
+ }>;
62
+ /**
63
+ * @param onStorageKeyChanged Storage synchronization mechanism keeping all opened tabs in sync
64
+ */
65
+ keepInstancesSync?: (onStorageKeyChanged: OnStorageKeyChanged) => void;
66
+ };
67
+ export default StorageProvider;
68
+ export type { Value, Key, KeyList, KeyValuePairList };
@@ -0,0 +1,2 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
package/dist/utils.d.ts CHANGED
@@ -1,5 +1,8 @@
1
- import {OnyxKey} from './types';
2
-
1
+ import type { OnyxKey } from './types';
2
+ type EmptyObject = Record<string, never>;
3
+ type EmptyValue = EmptyObject | null | undefined;
4
+ /** Checks whether the given object is an object and not null/undefined. */
5
+ declare function isEmptyObject<T>(obj: T | EmptyValue): obj is EmptyValue;
3
6
  /**
4
7
  * Merges two objects and removes null values if "shouldRemoveNullObjectValues" is set to true
5
8
  *
@@ -7,11 +10,15 @@ import {OnyxKey} from './types';
7
10
  * On native, when merging an existing value with new changes, SQLite will use JSON_PATCH, which removes top-level nullish values.
8
11
  * To be consistent with the behaviour for merge, we'll also want to remove null values for "set" operations.
9
12
  */
10
- declare function fastMerge<T>(target: T, source: T, shouldRemoveNullObjectValues: boolean = true): T;
11
-
12
- /**
13
- * Returns a formatted action name to be send to DevTools, given the method and optionally the key that was changed
14
- */
13
+ declare function fastMerge<TTarget extends unknown[] | Record<string, unknown>>(target: TTarget, source: TTarget, shouldRemoveNullObjectValues?: boolean): TTarget;
14
+ /** Deep removes the nested null values from the given value. */
15
+ declare function removeNestedNullValues(value: unknown[] | Record<string, unknown>): Record<string, unknown> | unknown[];
16
+ /** Formats the action name by uppercasing and adding the key if provided. */
15
17
  declare function formatActionName(method: string, key?: OnyxKey): string;
16
-
17
- export default {fastMerge, formatActionName};
18
+ declare const _default: {
19
+ isEmptyObject: typeof isEmptyObject;
20
+ fastMerge: typeof fastMerge;
21
+ formatActionName: typeof formatActionName;
22
+ removeNestedNullValues: typeof removeNestedNullValues;
23
+ };
24
+ export default _default;
package/dist/utils.js CHANGED
@@ -1,66 +1,58 @@
1
1
  "use strict";
2
- var __importDefault = (this && this.__importDefault) || function (mod) {
3
- return (mod && mod.__esModule) ? mod : { "default": mod };
4
- };
5
2
  Object.defineProperty(exports, "__esModule", { value: true });
6
- const underscore_1 = __importDefault(require("underscore"));
7
- function areObjectsEmpty(a, b) {
8
- return typeof a === 'object' && typeof b === 'object' && underscore_1.default.isEmpty(a) && underscore_1.default.isEmpty(b);
3
+ /** Checks whether the given object is an object and not null/undefined. */
4
+ function isEmptyObject(obj) {
5
+ return typeof obj === 'object' && Object.keys(obj || {}).length === 0;
9
6
  }
10
7
  // Mostly copied from https://medium.com/@lubaka.a/how-to-remove-lodash-performance-improvement-b306669ad0e1
11
8
  /**
12
- * @param {mixed} val
13
- * @returns {boolean}
9
+ * Checks whether the given value can be merged. It has to be an object, but not an array, RegExp or Date.
14
10
  */
15
- function isMergeableObject(val) {
16
- const nonNullObject = val != null ? typeof val === 'object' : false;
17
- return (nonNullObject &&
18
- Object.prototype.toString.call(val) !== '[object RegExp]' &&
19
- Object.prototype.toString.call(val) !== '[object Date]' &&
20
- // eslint-disable-next-line rulesdir/prefer-underscore-method
21
- !Array.isArray(val));
11
+ function isMergeableObject(value) {
12
+ const nonNullObject = value != null ? typeof value === 'object' : false;
13
+ return nonNullObject && Object.prototype.toString.call(value) !== '[object RegExp]' && Object.prototype.toString.call(value) !== '[object Date]' && !Array.isArray(value);
22
14
  }
23
15
  /**
24
- * @param {Object} target
25
- * @param {Object} source
26
- * @param {Boolean} shouldRemoveNullObjectValues
27
- * @returns {Object}
16
+ * Merges the source object into the target object.
17
+ * @param target - The target object.
18
+ * @param source - The source object.
19
+ * @param shouldRemoveNullObjectValues - If true, null object values will be removed.
20
+ * @returns - The merged object.
28
21
  */
29
22
  function mergeObject(target, source, shouldRemoveNullObjectValues = true) {
30
23
  const destination = {};
31
24
  if (isMergeableObject(target)) {
32
25
  // lodash adds a small overhead so we don't use it here
33
- // eslint-disable-next-line rulesdir/prefer-underscore-method
34
26
  const targetKeys = Object.keys(target);
35
27
  for (let i = 0; i < targetKeys.length; ++i) {
36
28
  const key = targetKeys[i];
29
+ const sourceValue = source === null || source === void 0 ? void 0 : source[key];
30
+ const targetValue = target === null || target === void 0 ? void 0 : target[key];
37
31
  // If shouldRemoveNullObjectValues is true, we want to remove null values from the merged object
38
- const isSourceOrTargetNull = target[key] === null || source[key] === null;
32
+ const isSourceOrTargetNull = targetValue === null || sourceValue === null;
39
33
  const shouldOmitSourceKey = shouldRemoveNullObjectValues && isSourceOrTargetNull;
40
34
  if (!shouldOmitSourceKey) {
41
- destination[key] = target[key];
35
+ destination[key] = targetValue;
42
36
  }
43
37
  }
44
38
  }
45
- // lodash adds a small overhead so we don't use it here
46
- // eslint-disable-next-line rulesdir/prefer-underscore-method
47
39
  const sourceKeys = Object.keys(source);
48
40
  for (let i = 0; i < sourceKeys.length; ++i) {
49
41
  const key = sourceKeys[i];
42
+ const sourceValue = source === null || source === void 0 ? void 0 : source[key];
43
+ const targetValue = target === null || target === void 0 ? void 0 : target[key];
50
44
  // If shouldRemoveNullObjectValues is true, we want to remove null values from the merged object
51
- const shouldOmitSourceKey = shouldRemoveNullObjectValues && source[key] === null;
45
+ const shouldOmitSourceKey = shouldRemoveNullObjectValues && sourceValue === null;
52
46
  // If we pass undefined as the updated value for a key, we want to generally ignore it
53
- const isSourceKeyUndefined = source[key] === undefined;
47
+ const isSourceKeyUndefined = sourceValue === undefined;
54
48
  if (!isSourceKeyUndefined && !shouldOmitSourceKey) {
55
- const isSourceKeyMergable = isMergeableObject(source[key]);
56
- if (isSourceKeyMergable && target[key]) {
57
- if (!shouldRemoveNullObjectValues || isSourceKeyMergable) {
58
- // eslint-disable-next-line no-use-before-define
59
- destination[key] = fastMerge(target[key], source[key], shouldRemoveNullObjectValues);
60
- }
49
+ const isSourceKeyMergable = isMergeableObject(sourceValue);
50
+ if (isSourceKeyMergable && targetValue) {
51
+ // eslint-disable-next-line no-use-before-define
52
+ destination[key] = fastMerge(targetValue, sourceValue, shouldRemoveNullObjectValues);
61
53
  }
62
- else if (!shouldRemoveNullObjectValues || source[key] !== null) {
63
- destination[key] = source[key];
54
+ else if (!shouldRemoveNullObjectValues || sourceValue !== null) {
55
+ destination[key] = sourceValue;
64
56
  }
65
57
  }
66
58
  }
@@ -72,28 +64,25 @@ function mergeObject(target, source, shouldRemoveNullObjectValues = true) {
72
64
  * We generally want to remove null values from objects written to disk and cache, because it decreases the amount of data stored in memory and on disk.
73
65
  * On native, when merging an existing value with new changes, SQLite will use JSON_PATCH, which removes top-level nullish values.
74
66
  * To be consistent with the behaviour for merge, we'll also want to remove null values for "set" operations.
75
- *
76
- * @param {Object|Array} target
77
- * @param {Object|Array} source
78
- * @param {Boolean} shouldRemoveNullObjectValues
79
- * @returns {Object|Array}
80
67
  */
81
68
  function fastMerge(target, source, shouldRemoveNullObjectValues = true) {
82
69
  // We have to ignore arrays and nullish values here,
83
70
  // otherwise "mergeObject" will throw an error,
84
71
  // because it expects an object as "source"
85
- if (underscore_1.default.isArray(source) || source === null || source === undefined) {
72
+ if (Array.isArray(source) || source === null || source === undefined) {
86
73
  return source;
87
74
  }
88
75
  return mergeObject(target, source, shouldRemoveNullObjectValues);
89
76
  }
77
+ /** Deep removes the nested null values from the given value. */
90
78
  function removeNestedNullValues(value) {
91
- if (typeof value === 'object' && !underscore_1.default.isArray(value)) {
79
+ if (typeof value === 'object' && !Array.isArray(value)) {
92
80
  return fastMerge(value, value);
93
81
  }
94
82
  return value;
95
83
  }
84
+ /** Formats the action name by uppercasing and adding the key if provided. */
96
85
  function formatActionName(method, key) {
97
86
  return key ? `${method.toUpperCase()}/${key}` : method.toUpperCase();
98
87
  }
99
- exports.default = { areObjectsEmpty, fastMerge, formatActionName, removeNestedNullValues };
88
+ exports.default = { isEmptyObject, fastMerge, formatActionName, removeNestedNullValues };
package/dist/withOnyx.js CHANGED
@@ -189,7 +189,7 @@ function default_1(mapOnyxToState, shouldDelayUpdates = false) {
189
189
  // This simply bypasses the loading check if the tempState is gone and the update can be safely queued with a normal setStateProxy.
190
190
  if (!this.state.loading || !this.tempState) {
191
191
  // Performance optimization, do not trigger update with same values
192
- if (prevValue === val || utils_1.default.areObjectsEmpty(prevValue, val)) {
192
+ if (prevValue === val || (utils_1.default.isEmptyObject(prevValue) && utils_1.default.isEmptyObject(val))) {
193
193
  return;
194
194
  }
195
195
  this.setStateProxy({ [statePropertyName]: val });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "react-native-onyx",
3
- "version": "2.0.4",
3
+ "version": "2.0.5",
4
4
  "author": "Expensify, Inc.",
5
5
  "homepage": "https://expensify.com",
6
6
  "description": "State management for React Native",
@@ -49,10 +49,12 @@
49
49
  "@testing-library/jest-native": "^3.4.2",
50
50
  "@testing-library/react-native": "^7.0.2",
51
51
  "@types/jest": "^28.1.8",
52
+ "@types/lodash": "^4.14.202",
52
53
  "@types/node": "^20.11.5",
53
54
  "@types/react": "^18.2.14",
54
55
  "@types/react-dom": "^18.2.18",
55
56
  "@types/react-native": "^0.70.0",
57
+ "@types/underscore": "^1.11.15",
56
58
  "@typescript-eslint/eslint-plugin": "^6.19.0",
57
59
  "@typescript-eslint/parser": "^6.19.0",
58
60
  "eslint": "^8.56.0",