react-native-onyx 3.0.100 → 3.0.102
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 +1 -1
- package/dist/OnyxCache.d.ts +7 -0
- package/dist/OnyxCache.js +78 -6
- package/dist/OnyxUtils.js +22 -4
- package/dist/utils.d.ts +6 -0
- package/dist/utils.js +25 -0
- package/package.json +1 -1
package/dist/Onyx.js
CHANGED
|
@@ -103,7 +103,7 @@ function init({ keys = {}, initialKeyStates = {}, evictableKeys = [], shouldSync
|
|
|
103
103
|
OnyxUtils_1.default.initStoreValues(keys, initialKeyStates, evictableKeys);
|
|
104
104
|
// Initialize all of our keys with data provided then give green light to any pending connections.
|
|
105
105
|
// addEvictableKeysToRecentlyAccessedList must run after initializeWithDefaultKeyStates because
|
|
106
|
-
// eager cache loading populates the key index (cache.
|
|
106
|
+
// eager cache loading populates the key index (cache.hydrate) inside initializeWithDefaultKeyStates,
|
|
107
107
|
// and the evictable keys list depends on that index being populated.
|
|
108
108
|
OnyxUtils_1.default.initializeWithDefaultKeyStates()
|
|
109
109
|
.then(() => OnyxCache_1.default.addEvictableKeysToRecentlyAccessedList(OnyxKeys_1.default.isCollectionKey, OnyxUtils_1.default.getAllKeys))
|
package/dist/OnyxCache.d.ts
CHANGED
|
@@ -66,6 +66,13 @@ declare class OnyxCache {
|
|
|
66
66
|
set(key: OnyxKey, value: OnyxValue<OnyxKey>): OnyxValue<OnyxKey>;
|
|
67
67
|
/** Forget the cached value for the given key */
|
|
68
68
|
drop(key: OnyxKey): void;
|
|
69
|
+
/**
|
|
70
|
+
* Bulk-loads values into a cache that's expected to be empty, skipping merge()'s per-key clone when
|
|
71
|
+
* safe. Falls back to a real merge for any key that already has a value, in case the cache wasn't
|
|
72
|
+
* empty after all. Used only by `Onyx.init()`.
|
|
73
|
+
* @param data - a map of (cache) key - values
|
|
74
|
+
*/
|
|
75
|
+
hydrate(data: Record<OnyxKey, OnyxValue<OnyxKey>>): void;
|
|
69
76
|
/**
|
|
70
77
|
* Deep merge data to cache, any non existing keys will be created
|
|
71
78
|
* @param data - a map of (cache) key - values
|
package/dist/OnyxCache.js
CHANGED
|
@@ -14,6 +14,15 @@ const OnyxKeys_1 = __importDefault(require("./OnyxKeys"));
|
|
|
14
14
|
* which relies on === equality to detect changes.
|
|
15
15
|
*/
|
|
16
16
|
const FROZEN_EMPTY_COLLECTION = Object.freeze({});
|
|
17
|
+
/**
|
|
18
|
+
* Merge options shared by every cache write path (`merge()` and `hydrate()`'s fallback), so the
|
|
19
|
+
* three call sites can't drift apart. Cached values must never hold nested nulls, and a source
|
|
20
|
+
* object carrying the replace mark must replace the target object rather than merge into it.
|
|
21
|
+
*/
|
|
22
|
+
const CACHE_MERGE_OPTIONS = {
|
|
23
|
+
shouldRemoveNestedNulls: true,
|
|
24
|
+
objectRemovalMode: 'replace',
|
|
25
|
+
};
|
|
17
26
|
// Task constants
|
|
18
27
|
const TASK = {
|
|
19
28
|
GET: 'get',
|
|
@@ -38,7 +47,7 @@ class OnyxCache {
|
|
|
38
47
|
this.collectionSnapshots = new Map();
|
|
39
48
|
this.dirtyCollections = new Set();
|
|
40
49
|
// bind all public methods to prevent problems with `this`
|
|
41
|
-
(0, bindAll_1.default)(this, 'getAllKeys', 'get', 'hasCacheForKey', 'addKey', 'addNullishStorageKey', 'hasNullishStorageKey', 'clearNullishStorageKeys', 'set', 'drop', 'merge', 'hasPendingTask', 'getTaskPromise', 'captureTask', 'setAllKeys', 'setEvictionAllowList', 'isEvictableKey', 'removeLastAccessedKey', 'addLastAccessedKey', 'addEvictableKeysToRecentlyAccessedList', 'getKeyForEviction', 'setCollectionKeys', 'hasValueChanged', 'getCollectionData');
|
|
50
|
+
(0, bindAll_1.default)(this, 'getAllKeys', 'get', 'hasCacheForKey', 'addKey', 'addNullishStorageKey', 'hasNullishStorageKey', 'clearNullishStorageKeys', 'set', 'drop', 'merge', 'hydrate', 'hasPendingTask', 'getTaskPromise', 'captureTask', 'setAllKeys', 'setEvictionAllowList', 'isEvictableKey', 'removeLastAccessedKey', 'addLastAccessedKey', 'addEvictableKeysToRecentlyAccessedList', 'getKeyForEviction', 'setCollectionKeys', 'hasValueChanged', 'getCollectionData');
|
|
42
51
|
}
|
|
43
52
|
/** Get all the storage keys */
|
|
44
53
|
getAllKeys() {
|
|
@@ -126,6 +135,69 @@ class OnyxCache {
|
|
|
126
135
|
this.storageKeys.delete(key);
|
|
127
136
|
OnyxKeys_1.default.deregisterMemberKey(key);
|
|
128
137
|
}
|
|
138
|
+
/**
|
|
139
|
+
* Bulk-loads values into a cache that's expected to be empty, skipping merge()'s per-key clone when
|
|
140
|
+
* safe. Falls back to a real merge for any key that already has a value, in case the cache wasn't
|
|
141
|
+
* empty after all. Used only by `Onyx.init()`.
|
|
142
|
+
* @param data - a map of (cache) key - values
|
|
143
|
+
*/
|
|
144
|
+
hydrate(data) {
|
|
145
|
+
if (data === null || typeof data !== 'object' || Array.isArray(data)) {
|
|
146
|
+
throw new Error('data passed to cache.hydrate() must be an Object of onyx key/value pairs');
|
|
147
|
+
}
|
|
148
|
+
const affectedCollections = new Set();
|
|
149
|
+
// Use for-in loop to avoid an unnecessary array allocation from Object.keys()
|
|
150
|
+
// eslint-disable-next-line no-restricted-syntax, guard-for-in
|
|
151
|
+
for (const key in data) {
|
|
152
|
+
const value = data[key];
|
|
153
|
+
this.addKey(key);
|
|
154
|
+
if (value === undefined) {
|
|
155
|
+
this.addNullishStorageKey(key);
|
|
156
|
+
// undefined means "no change" — skip storageMap modification
|
|
157
|
+
continue;
|
|
158
|
+
}
|
|
159
|
+
const collectionKey = OnyxKeys_1.default.getCollectionKey(key);
|
|
160
|
+
if (value === null) {
|
|
161
|
+
this.addNullishStorageKey(key);
|
|
162
|
+
delete this.storageMap[key];
|
|
163
|
+
if (collectionKey) {
|
|
164
|
+
affectedCollections.add(collectionKey);
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
else {
|
|
168
|
+
this.nullishStorageKeys.delete(key);
|
|
169
|
+
const existing = this.storageMap[key];
|
|
170
|
+
if (existing !== undefined) {
|
|
171
|
+
// Key already has a value, so the empty-cache assumption doesn't hold here (e.g. a write
|
|
172
|
+
// landed while storage was still being read). Fall back to a real merge, which has exactly
|
|
173
|
+
// the same semantics as the old `cache.merge(allDataFromStorage)` init path: the value
|
|
174
|
+
// loaded from disk is the merge source, so it wins on any overlapping leaf key.
|
|
175
|
+
// Note: this only covers non-null writes. A `cache.set(key, null)` that lands before hydrate
|
|
176
|
+
// clears the nullish marker too, so a deletion racing init is still undone - same as before.
|
|
177
|
+
const merged = utils_1.default.fastMerge(existing, value, CACHE_MERGE_OPTIONS).result;
|
|
178
|
+
// fastMerge is reference-stable: returns the original target when
|
|
179
|
+
// nothing changed, so a simple === check detects no-ops.
|
|
180
|
+
if (merged === existing) {
|
|
181
|
+
continue;
|
|
182
|
+
}
|
|
183
|
+
this.storageMap[key] = merged;
|
|
184
|
+
}
|
|
185
|
+
else if (utils_1.default.needsNormalization(value)) {
|
|
186
|
+
this.storageMap[key] = utils_1.default.fastMerge(undefined, value, CACHE_MERGE_OPTIONS).result;
|
|
187
|
+
}
|
|
188
|
+
else {
|
|
189
|
+
this.storageMap[key] = value;
|
|
190
|
+
}
|
|
191
|
+
if (collectionKey) {
|
|
192
|
+
affectedCollections.add(collectionKey);
|
|
193
|
+
}
|
|
194
|
+
}
|
|
195
|
+
}
|
|
196
|
+
// Mark affected collections as dirty — snapshots will be lazily rebuilt on next read
|
|
197
|
+
for (const collectionKey of affectedCollections) {
|
|
198
|
+
this.dirtyCollections.add(collectionKey);
|
|
199
|
+
}
|
|
200
|
+
}
|
|
129
201
|
/**
|
|
130
202
|
* Deep merge data to cache, any non existing keys will be created
|
|
131
203
|
* @param data - a map of (cache) key - values
|
|
@@ -135,7 +207,10 @@ class OnyxCache {
|
|
|
135
207
|
throw new Error('data passed to cache.merge() must be an Object of onyx key/value pairs');
|
|
136
208
|
}
|
|
137
209
|
const affectedCollections = new Set();
|
|
138
|
-
for
|
|
210
|
+
// Use for-in loop to avoid an unnecessary array allocation from Object.entries()
|
|
211
|
+
// eslint-disable-next-line no-restricted-syntax, guard-for-in
|
|
212
|
+
for (const key in data) {
|
|
213
|
+
const value = data[key];
|
|
139
214
|
this.addKey(key);
|
|
140
215
|
const collectionKey = OnyxKeys_1.default.getCollectionKey(key);
|
|
141
216
|
if (value === undefined) {
|
|
@@ -154,10 +229,7 @@ class OnyxCache {
|
|
|
154
229
|
this.nullishStorageKeys.delete(key);
|
|
155
230
|
// Per-key merge instead of spreading the entire storageMap
|
|
156
231
|
const existing = this.storageMap[key];
|
|
157
|
-
const merged = utils_1.default.fastMerge(existing, value,
|
|
158
|
-
shouldRemoveNestedNulls: true,
|
|
159
|
-
objectRemovalMode: 'replace',
|
|
160
|
-
}).result;
|
|
232
|
+
const merged = utils_1.default.fastMerge(existing, value, CACHE_MERGE_OPTIONS).result;
|
|
161
233
|
// fastMerge is reference-stable: returns the original target when
|
|
162
234
|
// nothing changed, so a simple === check detects no-ops.
|
|
163
235
|
if (merged === existing) {
|
package/dist/OnyxUtils.js
CHANGED
|
@@ -858,9 +858,12 @@ function initializeWithDefaultKeyStates() {
|
|
|
858
858
|
}
|
|
859
859
|
allDataFromStorage[key] = value;
|
|
860
860
|
}
|
|
861
|
-
// Load all storage data into cache silently (no subscriber notifications)
|
|
862
|
-
|
|
863
|
-
|
|
861
|
+
// Load all storage data into cache silently (no subscriber notifications).
|
|
862
|
+
// hydrate() rather than merge(): the cache is empty at this point, so a per-key fastMerge
|
|
863
|
+
// would only deep-clone every row it was handed.
|
|
864
|
+
// No setAllKeys() call is needed: hydrate() calls addKey() for every key, which populates the
|
|
865
|
+
// key index and registers collection member keys itself.
|
|
866
|
+
OnyxCache_1.default.hydrate(allDataFromStorage);
|
|
864
867
|
// For keys that have a developer-defined default (via `initialKeyStates`), merge the
|
|
865
868
|
// persisted value with the default so new properties added in code updates are applied
|
|
866
869
|
// without wiping user data that already exists in storage.
|
|
@@ -1017,13 +1020,28 @@ function updateSnapshots(data, mergeFn) {
|
|
|
1017
1020
|
return [];
|
|
1018
1021
|
const promises = [];
|
|
1019
1022
|
const snapshotCollection = getCachedCollection(snapshotCollectionKey);
|
|
1023
|
+
// Multiset entries are keyless but update every key of their payload, so expand them into
|
|
1024
|
+
// per-key entries to keep cached snapshots in sync with the real Onyx data.
|
|
1025
|
+
const flattenedData = data.flatMap((entry) => {
|
|
1026
|
+
if (entry.onyxMethod === METHOD.MULTI_SET && typeof entry.key !== 'string' && entry.value && typeof entry.value === 'object' && !Array.isArray(entry.value)) {
|
|
1027
|
+
return Object.entries(entry.value).map(([key, value]) => ({ onyxMethod: METHOD.SET, key, value }));
|
|
1028
|
+
}
|
|
1029
|
+
return entry;
|
|
1030
|
+
});
|
|
1020
1031
|
for (const [snapshotEntryKey, snapshotEntryValue] of Object.entries(snapshotCollection)) {
|
|
1021
1032
|
// Snapshots may not be present in cache. We don't know how to update them so we skip.
|
|
1022
1033
|
if (!snapshotEntryValue) {
|
|
1023
1034
|
continue;
|
|
1024
1035
|
}
|
|
1025
1036
|
let updatedData = {};
|
|
1026
|
-
for (const { key, value } of
|
|
1037
|
+
for (const { key, value, onyxMethod } of flattenedData) {
|
|
1038
|
+
if (typeof key !== 'string') {
|
|
1039
|
+
// clear entries legitimately carry no key, and malformed multiset payloads are already logged by update() itself
|
|
1040
|
+
if (onyxMethod !== METHOD.CLEAR && onyxMethod !== METHOD.MULTI_SET) {
|
|
1041
|
+
Logger.logHmmm(`Invalid ${typeof key} key (method: ${onyxMethod}, key: ${String(key).slice(0, 50)}) provided in Onyx update. Skipping snapshot update for this entry.`);
|
|
1042
|
+
}
|
|
1043
|
+
continue;
|
|
1044
|
+
}
|
|
1027
1045
|
// snapshots are normal keys so we want to skip update if they are written to Onyx
|
|
1028
1046
|
if (OnyxKeys_1.default.isCollectionMemberKey(snapshotCollectionKey, key)) {
|
|
1029
1047
|
continue;
|
package/dist/utils.d.ts
CHANGED
|
@@ -37,6 +37,11 @@ type FastMergeResult<TValue> = {
|
|
|
37
37
|
declare function fastMerge<TValue>(target: TValue, source: TValue, options?: FastMergeOptions, metadata?: FastMergeMetadata, basePath?: string[]): FastMergeResult<TValue>;
|
|
38
38
|
/** Checks whether the given object is an object and not null/undefined. */
|
|
39
39
|
declare function isEmptyObject<T>(obj: T | EmptyValue): obj is EmptyValue;
|
|
40
|
+
/**
|
|
41
|
+
* Reports whether a value needs cleaning (nested null/undefined, or the replace-object mark) before it's
|
|
42
|
+
* safe to store by reference. Read-only, non-allocating.
|
|
43
|
+
*/
|
|
44
|
+
declare function needsNormalization(value: unknown): boolean;
|
|
40
45
|
/** Deep removes the nested null values from the given value. Returns the original reference if no nulls were found. */
|
|
41
46
|
declare function removeNestedNullValues<TValue extends OnyxInput<OnyxKey> | null>(value: TValue): TValue;
|
|
42
47
|
/** Formats the action name by uppercasing and adding the key if provided. */
|
|
@@ -73,6 +78,7 @@ declare const _default: {
|
|
|
73
78
|
isEmptyObject: typeof isEmptyObject;
|
|
74
79
|
formatActionName: typeof formatActionName;
|
|
75
80
|
removeNestedNullValues: typeof removeNestedNullValues;
|
|
81
|
+
needsNormalization: typeof needsNormalization;
|
|
76
82
|
checkCompatibilityWithExistingValue: typeof checkCompatibilityWithExistingValue;
|
|
77
83
|
pick: typeof pick;
|
|
78
84
|
omit: typeof omit;
|
package/dist/utils.js
CHANGED
|
@@ -129,6 +129,30 @@ function isMergeableObject(value) {
|
|
|
129
129
|
const isNonNullObject = value != null ? typeof value === 'object' : false;
|
|
130
130
|
return isNonNullObject && !(value instanceof RegExp) && !(value instanceof Date) && !Array.isArray(value);
|
|
131
131
|
}
|
|
132
|
+
/**
|
|
133
|
+
* Reports whether a value needs cleaning (nested null/undefined, or the replace-object mark) before it's
|
|
134
|
+
* safe to store by reference. Read-only, non-allocating.
|
|
135
|
+
*/
|
|
136
|
+
function needsNormalization(value) {
|
|
137
|
+
if (value === null || value === undefined || typeof value !== 'object' || Array.isArray(value)) {
|
|
138
|
+
return false;
|
|
139
|
+
}
|
|
140
|
+
// Use for-in loop to avoid an unnecessary array allocation from Object.keys()
|
|
141
|
+
// eslint-disable-next-line no-restricted-syntax, guard-for-in
|
|
142
|
+
for (const key in value) {
|
|
143
|
+
if (key === ONYX_INTERNALS__REPLACE_OBJECT_MARK) {
|
|
144
|
+
return true;
|
|
145
|
+
}
|
|
146
|
+
const propertyValue = value[key];
|
|
147
|
+
if (propertyValue === null || propertyValue === undefined) {
|
|
148
|
+
return true;
|
|
149
|
+
}
|
|
150
|
+
if (typeof propertyValue === 'object' && !Array.isArray(propertyValue) && needsNormalization(propertyValue)) {
|
|
151
|
+
return true;
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
return false;
|
|
155
|
+
}
|
|
132
156
|
/** Deep removes the nested null values from the given value. Returns the original reference if no nulls were found. */
|
|
133
157
|
function removeNestedNullValues(value) {
|
|
134
158
|
if (value === null || value === undefined || typeof value !== 'object' || Array.isArray(value)) {
|
|
@@ -259,6 +283,7 @@ exports.default = {
|
|
|
259
283
|
isEmptyObject,
|
|
260
284
|
formatActionName,
|
|
261
285
|
removeNestedNullValues,
|
|
286
|
+
needsNormalization,
|
|
262
287
|
checkCompatibilityWithExistingValue,
|
|
263
288
|
pick,
|
|
264
289
|
omit,
|