react-native-nitro-storage 0.1.3 → 0.3.0
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/README.md +320 -391
- package/android/src/main/cpp/AndroidStorageAdapterCpp.cpp +101 -0
- package/android/src/main/cpp/AndroidStorageAdapterCpp.hpp +6 -41
- package/android/src/main/java/com/nitrostorage/AndroidStorageAdapter.kt +125 -37
- package/app.plugin.js +9 -7
- package/cpp/bindings/HybridStorage.cpp +214 -19
- package/cpp/bindings/HybridStorage.hpp +1 -0
- package/cpp/core/NativeStorageAdapter.hpp +7 -0
- package/ios/IOSStorageAdapterCpp.hpp +6 -0
- package/ios/IOSStorageAdapterCpp.mm +90 -7
- package/lib/commonjs/index.js +537 -66
- package/lib/commonjs/index.js.map +1 -1
- package/lib/commonjs/index.web.js +558 -130
- package/lib/commonjs/index.web.js.map +1 -1
- package/lib/commonjs/internal.js +102 -0
- package/lib/commonjs/internal.js.map +1 -0
- package/lib/module/index.js +528 -67
- package/lib/module/index.js.map +1 -1
- package/lib/module/index.web.js +536 -122
- package/lib/module/index.web.js.map +1 -1
- package/lib/module/internal.js +92 -0
- package/lib/module/internal.js.map +1 -0
- package/lib/typescript/index.d.ts +42 -6
- package/lib/typescript/index.d.ts.map +1 -1
- package/lib/typescript/index.web.d.ts +45 -12
- package/lib/typescript/index.web.d.ts.map +1 -1
- package/lib/typescript/internal.d.ts +19 -0
- package/lib/typescript/internal.d.ts.map +1 -0
- package/lib/typescript/migration.d.ts +2 -3
- package/lib/typescript/migration.d.ts.map +1 -1
- package/nitrogen/generated/android/NitroStorage+autolinking.cmake +1 -1
- package/nitrogen/generated/android/NitroStorage+autolinking.gradle +1 -1
- package/nitrogen/generated/android/NitroStorageOnLoad.cpp +1 -1
- package/nitrogen/generated/android/NitroStorageOnLoad.hpp +1 -1
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/com/nitrostorage/NitroStorageOnLoad.kt +1 -1
- package/nitrogen/generated/ios/NitroStorage+autolinking.rb +1 -1
- package/nitrogen/generated/ios/NitroStorage-Swift-Cxx-Bridge.cpp +1 -1
- package/nitrogen/generated/ios/NitroStorage-Swift-Cxx-Bridge.hpp +1 -1
- package/nitrogen/generated/ios/NitroStorage-Swift-Cxx-Umbrella.hpp +1 -1
- package/nitrogen/generated/ios/NitroStorageAutolinking.mm +1 -1
- package/nitrogen/generated/ios/NitroStorageAutolinking.swift +5 -1
- package/nitrogen/generated/shared/c++/HybridStorageSpec.cpp +1 -1
- package/nitrogen/generated/shared/c++/HybridStorageSpec.hpp +1 -1
- package/package.json +19 -8
- package/src/index.ts +734 -74
- package/src/index.web.ts +732 -128
- package/src/internal.ts +134 -0
- package/src/migration.ts +2 -2
package/lib/commonjs/index.js
CHANGED
|
@@ -11,14 +11,30 @@ Object.defineProperty(exports, "StorageScope", {
|
|
|
11
11
|
});
|
|
12
12
|
exports.createStorageItem = createStorageItem;
|
|
13
13
|
exports.getBatch = getBatch;
|
|
14
|
+
Object.defineProperty(exports, "migrateFromMMKV", {
|
|
15
|
+
enumerable: true,
|
|
16
|
+
get: function () {
|
|
17
|
+
return _migration.migrateFromMMKV;
|
|
18
|
+
}
|
|
19
|
+
});
|
|
20
|
+
exports.migrateToLatest = migrateToLatest;
|
|
21
|
+
exports.registerMigration = registerMigration;
|
|
14
22
|
exports.removeBatch = removeBatch;
|
|
23
|
+
exports.runTransaction = runTransaction;
|
|
15
24
|
exports.setBatch = setBatch;
|
|
16
25
|
exports.storage = void 0;
|
|
17
26
|
exports.useSetStorage = useSetStorage;
|
|
18
27
|
exports.useStorage = useStorage;
|
|
28
|
+
exports.useStorageSelector = useStorageSelector;
|
|
19
29
|
var _react = require("react");
|
|
20
30
|
var _reactNativeNitroModules = require("react-native-nitro-modules");
|
|
21
31
|
var _Storage = require("./Storage.types");
|
|
32
|
+
var _internal = require("./internal");
|
|
33
|
+
var _migration = require("./migration");
|
|
34
|
+
const registeredMigrations = new Map();
|
|
35
|
+
const runMicrotask = typeof queueMicrotask === "function" ? queueMicrotask : task => {
|
|
36
|
+
Promise.resolve().then(task);
|
|
37
|
+
};
|
|
22
38
|
let _storageModule = null;
|
|
23
39
|
function getStorageModule() {
|
|
24
40
|
if (!_storageModule) {
|
|
@@ -27,18 +43,202 @@ function getStorageModule() {
|
|
|
27
43
|
return _storageModule;
|
|
28
44
|
}
|
|
29
45
|
const memoryStore = new Map();
|
|
30
|
-
const memoryListeners = new
|
|
31
|
-
|
|
32
|
-
|
|
46
|
+
const memoryListeners = new Map();
|
|
47
|
+
const scopedListeners = new Map([[_Storage.StorageScope.Disk, new Map()], [_Storage.StorageScope.Secure, new Map()]]);
|
|
48
|
+
const scopedUnsubscribers = new Map();
|
|
49
|
+
const scopedRawCache = new Map([[_Storage.StorageScope.Disk, new Map()], [_Storage.StorageScope.Secure, new Map()]]);
|
|
50
|
+
const pendingSecureWrites = new Map();
|
|
51
|
+
let secureFlushScheduled = false;
|
|
52
|
+
function getScopedListeners(scope) {
|
|
53
|
+
return scopedListeners.get(scope);
|
|
54
|
+
}
|
|
55
|
+
function getScopeRawCache(scope) {
|
|
56
|
+
return scopedRawCache.get(scope);
|
|
57
|
+
}
|
|
58
|
+
function cacheRawValue(scope, key, value) {
|
|
59
|
+
getScopeRawCache(scope).set(key, value);
|
|
60
|
+
}
|
|
61
|
+
function readCachedRawValue(scope, key) {
|
|
62
|
+
return getScopeRawCache(scope).get(key);
|
|
63
|
+
}
|
|
64
|
+
function hasCachedRawValue(scope, key) {
|
|
65
|
+
return getScopeRawCache(scope).has(key);
|
|
66
|
+
}
|
|
67
|
+
function clearScopeRawCache(scope) {
|
|
68
|
+
getScopeRawCache(scope).clear();
|
|
69
|
+
}
|
|
70
|
+
function notifyKeyListeners(registry, key) {
|
|
71
|
+
registry.get(key)?.forEach(listener => listener());
|
|
72
|
+
}
|
|
73
|
+
function notifyAllListeners(registry) {
|
|
74
|
+
registry.forEach(listeners => {
|
|
75
|
+
listeners.forEach(listener => listener());
|
|
76
|
+
});
|
|
77
|
+
}
|
|
78
|
+
function addKeyListener(registry, key, listener) {
|
|
79
|
+
let listeners = registry.get(key);
|
|
80
|
+
if (!listeners) {
|
|
81
|
+
listeners = new Set();
|
|
82
|
+
registry.set(key, listeners);
|
|
83
|
+
}
|
|
84
|
+
listeners.add(listener);
|
|
85
|
+
return () => {
|
|
86
|
+
const scopedListeners = registry.get(key);
|
|
87
|
+
if (!scopedListeners) {
|
|
88
|
+
return;
|
|
89
|
+
}
|
|
90
|
+
scopedListeners.delete(listener);
|
|
91
|
+
if (scopedListeners.size === 0) {
|
|
92
|
+
registry.delete(key);
|
|
93
|
+
}
|
|
94
|
+
};
|
|
95
|
+
}
|
|
96
|
+
function readPendingSecureWrite(key) {
|
|
97
|
+
return pendingSecureWrites.get(key)?.value;
|
|
98
|
+
}
|
|
99
|
+
function hasPendingSecureWrite(key) {
|
|
100
|
+
return pendingSecureWrites.has(key);
|
|
101
|
+
}
|
|
102
|
+
function clearPendingSecureWrite(key) {
|
|
103
|
+
pendingSecureWrites.delete(key);
|
|
104
|
+
}
|
|
105
|
+
function flushSecureWrites() {
|
|
106
|
+
secureFlushScheduled = false;
|
|
107
|
+
if (pendingSecureWrites.size === 0) {
|
|
108
|
+
return;
|
|
109
|
+
}
|
|
110
|
+
const writes = Array.from(pendingSecureWrites.values());
|
|
111
|
+
pendingSecureWrites.clear();
|
|
112
|
+
const keysToSet = [];
|
|
113
|
+
const valuesToSet = [];
|
|
114
|
+
const keysToRemove = [];
|
|
115
|
+
writes.forEach(({
|
|
116
|
+
key,
|
|
117
|
+
value
|
|
118
|
+
}) => {
|
|
119
|
+
if (value === undefined) {
|
|
120
|
+
keysToRemove.push(key);
|
|
121
|
+
} else {
|
|
122
|
+
keysToSet.push(key);
|
|
123
|
+
valuesToSet.push(value);
|
|
124
|
+
}
|
|
125
|
+
});
|
|
126
|
+
const storageModule = getStorageModule();
|
|
127
|
+
if (keysToSet.length > 0) {
|
|
128
|
+
storageModule.setBatch(keysToSet, valuesToSet, _Storage.StorageScope.Secure);
|
|
129
|
+
}
|
|
130
|
+
if (keysToRemove.length > 0) {
|
|
131
|
+
storageModule.removeBatch(keysToRemove, _Storage.StorageScope.Secure);
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
function scheduleSecureWrite(key, value) {
|
|
135
|
+
pendingSecureWrites.set(key, {
|
|
136
|
+
key,
|
|
137
|
+
value
|
|
138
|
+
});
|
|
139
|
+
if (secureFlushScheduled) {
|
|
140
|
+
return;
|
|
141
|
+
}
|
|
142
|
+
secureFlushScheduled = true;
|
|
143
|
+
runMicrotask(flushSecureWrites);
|
|
144
|
+
}
|
|
145
|
+
function ensureNativeScopeSubscription(scope) {
|
|
146
|
+
if (scopedUnsubscribers.has(scope)) {
|
|
147
|
+
return;
|
|
148
|
+
}
|
|
149
|
+
const unsubscribe = getStorageModule().addOnChange(scope, (key, value) => {
|
|
150
|
+
if (scope === _Storage.StorageScope.Secure) {
|
|
151
|
+
if (key === "") {
|
|
152
|
+
pendingSecureWrites.clear();
|
|
153
|
+
} else {
|
|
154
|
+
clearPendingSecureWrite(key);
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
if (key === "") {
|
|
158
|
+
clearScopeRawCache(scope);
|
|
159
|
+
notifyAllListeners(getScopedListeners(scope));
|
|
160
|
+
return;
|
|
161
|
+
}
|
|
162
|
+
cacheRawValue(scope, key, value);
|
|
163
|
+
notifyKeyListeners(getScopedListeners(scope), key);
|
|
164
|
+
});
|
|
165
|
+
scopedUnsubscribers.set(scope, unsubscribe);
|
|
166
|
+
}
|
|
167
|
+
function maybeCleanupNativeScopeSubscription(scope) {
|
|
168
|
+
const listeners = getScopedListeners(scope);
|
|
169
|
+
if (listeners.size > 0) {
|
|
170
|
+
return;
|
|
171
|
+
}
|
|
172
|
+
const unsubscribe = scopedUnsubscribers.get(scope);
|
|
173
|
+
if (!unsubscribe) {
|
|
174
|
+
return;
|
|
175
|
+
}
|
|
176
|
+
unsubscribe();
|
|
177
|
+
scopedUnsubscribers.delete(scope);
|
|
178
|
+
}
|
|
179
|
+
function getRawValue(key, scope) {
|
|
180
|
+
(0, _internal.assertValidScope)(scope);
|
|
181
|
+
if (scope === _Storage.StorageScope.Memory) {
|
|
182
|
+
const value = memoryStore.get(key);
|
|
183
|
+
return typeof value === "string" ? value : undefined;
|
|
184
|
+
}
|
|
185
|
+
if (scope === _Storage.StorageScope.Secure && hasPendingSecureWrite(key)) {
|
|
186
|
+
return readPendingSecureWrite(key);
|
|
187
|
+
}
|
|
188
|
+
return getStorageModule().get(key, scope);
|
|
189
|
+
}
|
|
190
|
+
function setRawValue(key, value, scope) {
|
|
191
|
+
(0, _internal.assertValidScope)(scope);
|
|
192
|
+
if (scope === _Storage.StorageScope.Memory) {
|
|
193
|
+
memoryStore.set(key, value);
|
|
194
|
+
notifyKeyListeners(memoryListeners, key);
|
|
195
|
+
return;
|
|
196
|
+
}
|
|
197
|
+
if (scope === _Storage.StorageScope.Secure) {
|
|
198
|
+
flushSecureWrites();
|
|
199
|
+
clearPendingSecureWrite(key);
|
|
200
|
+
}
|
|
201
|
+
getStorageModule().set(key, value, scope);
|
|
202
|
+
cacheRawValue(scope, key, value);
|
|
203
|
+
}
|
|
204
|
+
function removeRawValue(key, scope) {
|
|
205
|
+
(0, _internal.assertValidScope)(scope);
|
|
206
|
+
if (scope === _Storage.StorageScope.Memory) {
|
|
207
|
+
memoryStore.delete(key);
|
|
208
|
+
notifyKeyListeners(memoryListeners, key);
|
|
209
|
+
return;
|
|
210
|
+
}
|
|
211
|
+
if (scope === _Storage.StorageScope.Secure) {
|
|
212
|
+
flushSecureWrites();
|
|
213
|
+
clearPendingSecureWrite(key);
|
|
214
|
+
}
|
|
215
|
+
getStorageModule().remove(key, scope);
|
|
216
|
+
cacheRawValue(scope, key, undefined);
|
|
217
|
+
}
|
|
218
|
+
function readMigrationVersion(scope) {
|
|
219
|
+
const raw = getRawValue(_internal.MIGRATION_VERSION_KEY, scope);
|
|
220
|
+
if (raw === undefined) {
|
|
221
|
+
return 0;
|
|
222
|
+
}
|
|
223
|
+
const parsed = Number.parseInt(raw, 10);
|
|
224
|
+
return Number.isFinite(parsed) && parsed > 0 ? parsed : 0;
|
|
225
|
+
}
|
|
226
|
+
function writeMigrationVersion(scope, version) {
|
|
227
|
+
setRawValue(_internal.MIGRATION_VERSION_KEY, String(version), scope);
|
|
33
228
|
}
|
|
34
229
|
const storage = exports.storage = {
|
|
35
230
|
clear: scope => {
|
|
36
231
|
if (scope === _Storage.StorageScope.Memory) {
|
|
37
232
|
memoryStore.clear();
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
getStorageModule().clear(scope);
|
|
233
|
+
notifyAllListeners(memoryListeners);
|
|
234
|
+
return;
|
|
41
235
|
}
|
|
236
|
+
if (scope === _Storage.StorageScope.Secure) {
|
|
237
|
+
flushSecureWrites();
|
|
238
|
+
pendingSecureWrites.clear();
|
|
239
|
+
}
|
|
240
|
+
clearScopeRawCache(scope);
|
|
241
|
+
getStorageModule().clear(scope);
|
|
42
242
|
},
|
|
43
243
|
clearAll: () => {
|
|
44
244
|
storage.clear(_Storage.StorageScope.Memory);
|
|
@@ -46,83 +246,201 @@ const storage = exports.storage = {
|
|
|
46
246
|
storage.clear(_Storage.StorageScope.Secure);
|
|
47
247
|
}
|
|
48
248
|
};
|
|
249
|
+
function canUseRawBatchPath(item) {
|
|
250
|
+
return item._hasExpiration === false && item._hasValidation === false;
|
|
251
|
+
}
|
|
49
252
|
function defaultSerialize(value) {
|
|
50
|
-
return
|
|
253
|
+
return (0, _internal.serializeWithPrimitiveFastPath)(value);
|
|
51
254
|
}
|
|
52
255
|
function defaultDeserialize(value) {
|
|
53
|
-
return
|
|
256
|
+
return (0, _internal.deserializeWithPrimitiveFastPath)(value);
|
|
54
257
|
}
|
|
55
258
|
function createStorageItem(config) {
|
|
56
259
|
const serialize = config.serialize ?? defaultSerialize;
|
|
57
260
|
const deserialize = config.deserialize ?? defaultDeserialize;
|
|
58
261
|
const isMemory = config.scope === _Storage.StorageScope.Memory;
|
|
262
|
+
const validate = config.validate;
|
|
263
|
+
const onValidationError = config.onValidationError;
|
|
264
|
+
const expiration = config.expiration;
|
|
265
|
+
const expirationTtlMs = expiration?.ttlMs;
|
|
266
|
+
const memoryExpiration = expiration && isMemory ? new Map() : null;
|
|
267
|
+
const readCache = !isMemory && config.readCache === true;
|
|
268
|
+
const coalesceSecureWrites = config.scope === _Storage.StorageScope.Secure && config.coalesceSecureWrites === true;
|
|
269
|
+
const nonMemoryScope = config.scope === _Storage.StorageScope.Disk ? _Storage.StorageScope.Disk : config.scope === _Storage.StorageScope.Secure ? _Storage.StorageScope.Secure : null;
|
|
270
|
+
if (expiration && expiration.ttlMs <= 0) {
|
|
271
|
+
throw new Error("expiration.ttlMs must be greater than 0.");
|
|
272
|
+
}
|
|
59
273
|
const listeners = new Set();
|
|
60
274
|
let unsubscribe = null;
|
|
275
|
+
let lastRaw = undefined;
|
|
276
|
+
let lastValue;
|
|
277
|
+
let hasLastValue = false;
|
|
278
|
+
const invalidateParsedCache = () => {
|
|
279
|
+
lastRaw = undefined;
|
|
280
|
+
lastValue = undefined;
|
|
281
|
+
hasLastValue = false;
|
|
282
|
+
};
|
|
61
283
|
const ensureSubscription = () => {
|
|
62
|
-
if (
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
284
|
+
if (unsubscribe) {
|
|
285
|
+
return;
|
|
286
|
+
}
|
|
287
|
+
const listener = () => {
|
|
288
|
+
invalidateParsedCache();
|
|
289
|
+
listeners.forEach(callback => callback());
|
|
290
|
+
};
|
|
291
|
+
if (isMemory) {
|
|
292
|
+
unsubscribe = addKeyListener(memoryListeners, config.key, listener);
|
|
293
|
+
return;
|
|
294
|
+
}
|
|
295
|
+
ensureNativeScopeSubscription(nonMemoryScope);
|
|
296
|
+
unsubscribe = addKeyListener(getScopedListeners(nonMemoryScope), config.key, listener);
|
|
297
|
+
};
|
|
298
|
+
const readStoredRaw = () => {
|
|
299
|
+
if (isMemory) {
|
|
300
|
+
if (memoryExpiration) {
|
|
301
|
+
const expiresAt = memoryExpiration.get(config.key);
|
|
302
|
+
if (expiresAt !== undefined && expiresAt <= Date.now()) {
|
|
303
|
+
memoryExpiration.delete(config.key);
|
|
304
|
+
memoryStore.delete(config.key);
|
|
305
|
+
notifyKeyListeners(memoryListeners, config.key);
|
|
306
|
+
return undefined;
|
|
307
|
+
}
|
|
308
|
+
}
|
|
309
|
+
return memoryStore.get(config.key);
|
|
310
|
+
}
|
|
311
|
+
if (nonMemoryScope === _Storage.StorageScope.Secure && hasPendingSecureWrite(config.key)) {
|
|
312
|
+
return readPendingSecureWrite(config.key);
|
|
313
|
+
}
|
|
314
|
+
if (readCache) {
|
|
315
|
+
if (hasCachedRawValue(nonMemoryScope, config.key)) {
|
|
316
|
+
return readCachedRawValue(nonMemoryScope, config.key);
|
|
81
317
|
}
|
|
82
318
|
}
|
|
319
|
+
const raw = getStorageModule().get(config.key, config.scope);
|
|
320
|
+
cacheRawValue(nonMemoryScope, config.key, raw);
|
|
321
|
+
return raw;
|
|
83
322
|
};
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
323
|
+
const writeStoredRaw = rawValue => {
|
|
324
|
+
cacheRawValue(nonMemoryScope, config.key, rawValue);
|
|
325
|
+
if (coalesceSecureWrites) {
|
|
326
|
+
scheduleSecureWrite(config.key, rawValue);
|
|
327
|
+
return;
|
|
328
|
+
}
|
|
329
|
+
if (nonMemoryScope === _Storage.StorageScope.Secure) {
|
|
330
|
+
clearPendingSecureWrite(config.key);
|
|
331
|
+
}
|
|
332
|
+
getStorageModule().set(config.key, rawValue, config.scope);
|
|
333
|
+
};
|
|
334
|
+
const removeStoredRaw = () => {
|
|
335
|
+
cacheRawValue(nonMemoryScope, config.key, undefined);
|
|
336
|
+
if (coalesceSecureWrites) {
|
|
337
|
+
scheduleSecureWrite(config.key, undefined);
|
|
338
|
+
return;
|
|
339
|
+
}
|
|
340
|
+
if (nonMemoryScope === _Storage.StorageScope.Secure) {
|
|
341
|
+
clearPendingSecureWrite(config.key);
|
|
342
|
+
}
|
|
343
|
+
getStorageModule().remove(config.key, config.scope);
|
|
344
|
+
};
|
|
345
|
+
const writeValueWithoutValidation = value => {
|
|
88
346
|
if (isMemory) {
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
347
|
+
if (memoryExpiration) {
|
|
348
|
+
memoryExpiration.set(config.key, Date.now() + (expirationTtlMs ?? 0));
|
|
349
|
+
}
|
|
350
|
+
memoryStore.set(config.key, value);
|
|
351
|
+
notifyKeyListeners(memoryListeners, config.key);
|
|
352
|
+
return;
|
|
353
|
+
}
|
|
354
|
+
const serialized = serialize(value);
|
|
355
|
+
if (expiration) {
|
|
356
|
+
const envelope = {
|
|
357
|
+
__nitroStorageEnvelope: true,
|
|
358
|
+
expiresAt: Date.now() + expiration.ttlMs,
|
|
359
|
+
payload: serialized
|
|
360
|
+
};
|
|
361
|
+
writeStoredRaw(JSON.stringify(envelope));
|
|
362
|
+
return;
|
|
363
|
+
}
|
|
364
|
+
writeStoredRaw(serialized);
|
|
365
|
+
};
|
|
366
|
+
const resolveInvalidValue = invalidValue => {
|
|
367
|
+
if (onValidationError) {
|
|
368
|
+
return onValidationError(invalidValue);
|
|
92
369
|
}
|
|
93
|
-
|
|
370
|
+
return config.defaultValue;
|
|
371
|
+
};
|
|
372
|
+
const ensureValidatedValue = (candidate, hadStoredValue) => {
|
|
373
|
+
if (!validate || validate(candidate)) {
|
|
374
|
+
return candidate;
|
|
375
|
+
}
|
|
376
|
+
const resolved = resolveInvalidValue(candidate);
|
|
377
|
+
if (validate && !validate(resolved)) {
|
|
378
|
+
return config.defaultValue;
|
|
379
|
+
}
|
|
380
|
+
if (hadStoredValue) {
|
|
381
|
+
writeValueWithoutValidation(resolved);
|
|
382
|
+
}
|
|
383
|
+
return resolved;
|
|
384
|
+
};
|
|
385
|
+
const get = () => {
|
|
386
|
+
const raw = readStoredRaw();
|
|
387
|
+
const canUseCachedValue = !expiration && !memoryExpiration;
|
|
388
|
+
if (canUseCachedValue && raw === lastRaw && hasLastValue) {
|
|
94
389
|
return lastValue;
|
|
95
390
|
}
|
|
96
391
|
lastRaw = raw;
|
|
97
392
|
if (raw === undefined) {
|
|
98
|
-
lastValue = config.defaultValue;
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
393
|
+
lastValue = ensureValidatedValue(config.defaultValue, false);
|
|
394
|
+
hasLastValue = true;
|
|
395
|
+
return lastValue;
|
|
396
|
+
}
|
|
397
|
+
if (isMemory) {
|
|
398
|
+
lastValue = ensureValidatedValue(raw, true);
|
|
399
|
+
hasLastValue = true;
|
|
400
|
+
return lastValue;
|
|
401
|
+
}
|
|
402
|
+
let deserializableRaw = raw;
|
|
403
|
+
if (expiration) {
|
|
404
|
+
try {
|
|
405
|
+
const parsed = JSON.parse(raw);
|
|
406
|
+
if ((0, _internal.isStoredEnvelope)(parsed)) {
|
|
407
|
+
if (parsed.expiresAt <= Date.now()) {
|
|
408
|
+
removeStoredRaw();
|
|
409
|
+
invalidateParsedCache();
|
|
410
|
+
lastValue = ensureValidatedValue(config.defaultValue, false);
|
|
411
|
+
hasLastValue = true;
|
|
412
|
+
return lastValue;
|
|
413
|
+
}
|
|
414
|
+
deserializableRaw = parsed.payload;
|
|
415
|
+
}
|
|
416
|
+
} catch {
|
|
417
|
+
// Keep backward compatibility with legacy raw values.
|
|
104
418
|
}
|
|
105
419
|
}
|
|
420
|
+
lastValue = ensureValidatedValue(deserialize(deserializableRaw), true);
|
|
421
|
+
hasLastValue = true;
|
|
106
422
|
return lastValue;
|
|
107
423
|
};
|
|
108
424
|
const set = valueOrFn => {
|
|
109
425
|
const currentValue = get();
|
|
110
426
|
const newValue = typeof valueOrFn === "function" ? valueOrFn(currentValue) : valueOrFn;
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
} else {
|
|
115
|
-
const serialized = serialize(newValue);
|
|
116
|
-
getStorageModule().set(config.key, serialized, config.scope);
|
|
427
|
+
invalidateParsedCache();
|
|
428
|
+
if (validate && !validate(newValue)) {
|
|
429
|
+
throw new Error(`Validation failed for key "${config.key}" in scope "${_Storage.StorageScope[config.scope]}".`);
|
|
117
430
|
}
|
|
431
|
+
writeValueWithoutValidation(newValue);
|
|
118
432
|
};
|
|
119
433
|
const deleteItem = () => {
|
|
434
|
+
invalidateParsedCache();
|
|
120
435
|
if (isMemory) {
|
|
436
|
+
if (memoryExpiration) {
|
|
437
|
+
memoryExpiration.delete(config.key);
|
|
438
|
+
}
|
|
121
439
|
memoryStore.delete(config.key);
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
getStorageModule().remove(config.key, config.scope);
|
|
440
|
+
notifyKeyListeners(memoryListeners, config.key);
|
|
441
|
+
return;
|
|
125
442
|
}
|
|
443
|
+
removeStoredRaw();
|
|
126
444
|
};
|
|
127
445
|
const subscribe = callback => {
|
|
128
446
|
ensureSubscription();
|
|
@@ -131,11 +449,14 @@ function createStorageItem(config) {
|
|
|
131
449
|
listeners.delete(callback);
|
|
132
450
|
if (listeners.size === 0 && unsubscribe) {
|
|
133
451
|
unsubscribe();
|
|
452
|
+
if (!isMemory) {
|
|
453
|
+
maybeCleanupNativeScopeSubscription(nonMemoryScope);
|
|
454
|
+
}
|
|
134
455
|
unsubscribe = null;
|
|
135
456
|
}
|
|
136
457
|
};
|
|
137
458
|
};
|
|
138
|
-
|
|
459
|
+
const storageItem = {
|
|
139
460
|
get,
|
|
140
461
|
set,
|
|
141
462
|
delete: deleteItem,
|
|
@@ -143,29 +464,83 @@ function createStorageItem(config) {
|
|
|
143
464
|
serialize,
|
|
144
465
|
deserialize,
|
|
145
466
|
_triggerListeners: () => {
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
listeners.forEach(l => l());
|
|
467
|
+
invalidateParsedCache();
|
|
468
|
+
listeners.forEach(listener => listener());
|
|
149
469
|
},
|
|
470
|
+
_hasValidation: validate !== undefined,
|
|
471
|
+
_hasExpiration: expiration !== undefined,
|
|
472
|
+
_readCacheEnabled: readCache,
|
|
150
473
|
scope: config.scope,
|
|
151
474
|
key: config.key
|
|
152
475
|
};
|
|
476
|
+
return storageItem;
|
|
153
477
|
}
|
|
154
478
|
function useStorage(item) {
|
|
155
479
|
const value = (0, _react.useSyncExternalStore)(item.subscribe, item.get, item.get);
|
|
156
480
|
return [value, item.set];
|
|
157
481
|
}
|
|
482
|
+
function useStorageSelector(item, selector, isEqual = Object.is) {
|
|
483
|
+
const selectedRef = (0, _react.useRef)({
|
|
484
|
+
hasValue: false
|
|
485
|
+
});
|
|
486
|
+
const getSelectedSnapshot = () => {
|
|
487
|
+
const nextSelected = selector(item.get());
|
|
488
|
+
const current = selectedRef.current;
|
|
489
|
+
if (current.hasValue && isEqual(current.value, nextSelected)) {
|
|
490
|
+
return current.value;
|
|
491
|
+
}
|
|
492
|
+
selectedRef.current = {
|
|
493
|
+
hasValue: true,
|
|
494
|
+
value: nextSelected
|
|
495
|
+
};
|
|
496
|
+
return nextSelected;
|
|
497
|
+
};
|
|
498
|
+
const selectedValue = (0, _react.useSyncExternalStore)(item.subscribe, getSelectedSnapshot, getSelectedSnapshot);
|
|
499
|
+
return [selectedValue, item.set];
|
|
500
|
+
}
|
|
158
501
|
function useSetStorage(item) {
|
|
159
502
|
return item.set;
|
|
160
503
|
}
|
|
161
504
|
function getBatch(items, scope) {
|
|
505
|
+
(0, _internal.assertBatchScope)(items, scope);
|
|
162
506
|
if (scope === _Storage.StorageScope.Memory) {
|
|
163
507
|
return items.map(item => item.get());
|
|
164
508
|
}
|
|
165
|
-
const
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
509
|
+
const useRawBatchPath = items.every(item => canUseRawBatchPath(item));
|
|
510
|
+
if (!useRawBatchPath) {
|
|
511
|
+
return items.map(item => item.get());
|
|
512
|
+
}
|
|
513
|
+
const useBatchCache = items.every(item => item._readCacheEnabled === true);
|
|
514
|
+
const rawValues = new Array(items.length);
|
|
515
|
+
const keysToFetch = [];
|
|
516
|
+
const keyIndexes = [];
|
|
517
|
+
items.forEach((item, index) => {
|
|
518
|
+
if (scope === _Storage.StorageScope.Secure) {
|
|
519
|
+
if (hasPendingSecureWrite(item.key)) {
|
|
520
|
+
rawValues[index] = readPendingSecureWrite(item.key);
|
|
521
|
+
return;
|
|
522
|
+
}
|
|
523
|
+
}
|
|
524
|
+
if (useBatchCache) {
|
|
525
|
+
if (hasCachedRawValue(scope, item.key)) {
|
|
526
|
+
rawValues[index] = readCachedRawValue(scope, item.key);
|
|
527
|
+
return;
|
|
528
|
+
}
|
|
529
|
+
}
|
|
530
|
+
keysToFetch.push(item.key);
|
|
531
|
+
keyIndexes.push(index);
|
|
532
|
+
});
|
|
533
|
+
if (keysToFetch.length > 0) {
|
|
534
|
+
const fetchedValues = getStorageModule().getBatch(keysToFetch, scope).map(value => (0, _internal.decodeNativeBatchValue)(value));
|
|
535
|
+
fetchedValues.forEach((value, index) => {
|
|
536
|
+
const key = keysToFetch[index];
|
|
537
|
+
const targetIndex = keyIndexes[index];
|
|
538
|
+
rawValues[targetIndex] = value;
|
|
539
|
+
cacheRawValue(scope, key, value);
|
|
540
|
+
});
|
|
541
|
+
}
|
|
542
|
+
return items.map((item, index) => {
|
|
543
|
+
const raw = rawValues[index];
|
|
169
544
|
if (raw === undefined) {
|
|
170
545
|
return item.get();
|
|
171
546
|
}
|
|
@@ -173,6 +548,7 @@ function getBatch(items, scope) {
|
|
|
173
548
|
});
|
|
174
549
|
}
|
|
175
550
|
function setBatch(items, scope) {
|
|
551
|
+
(0, _internal.assertBatchScope)(items.map(batchEntry => batchEntry.item), scope);
|
|
176
552
|
if (scope === _Storage.StorageScope.Memory) {
|
|
177
553
|
items.forEach(({
|
|
178
554
|
item,
|
|
@@ -180,22 +556,117 @@ function setBatch(items, scope) {
|
|
|
180
556
|
}) => item.set(value));
|
|
181
557
|
return;
|
|
182
558
|
}
|
|
183
|
-
const
|
|
184
|
-
const values = items.map(i => i.item.serialize(i.value));
|
|
185
|
-
getStorageModule().setBatch(keys, values, scope);
|
|
186
|
-
items.forEach(({
|
|
559
|
+
const useRawBatchPath = items.every(({
|
|
187
560
|
item
|
|
188
|
-
}) =>
|
|
189
|
-
|
|
190
|
-
|
|
561
|
+
}) => canUseRawBatchPath(item));
|
|
562
|
+
if (!useRawBatchPath) {
|
|
563
|
+
items.forEach(({
|
|
564
|
+
item,
|
|
565
|
+
value
|
|
566
|
+
}) => item.set(value));
|
|
567
|
+
return;
|
|
568
|
+
}
|
|
569
|
+
const keys = items.map(entry => entry.item.key);
|
|
570
|
+
const values = items.map(entry => entry.item.serialize(entry.value));
|
|
571
|
+
if (scope === _Storage.StorageScope.Secure) {
|
|
572
|
+
flushSecureWrites();
|
|
573
|
+
}
|
|
574
|
+
getStorageModule().setBatch(keys, values, scope);
|
|
575
|
+
keys.forEach((key, index) => cacheRawValue(scope, key, values[index]));
|
|
191
576
|
}
|
|
192
577
|
function removeBatch(items, scope) {
|
|
578
|
+
(0, _internal.assertBatchScope)(items, scope);
|
|
193
579
|
if (scope === _Storage.StorageScope.Memory) {
|
|
194
580
|
items.forEach(item => item.delete());
|
|
195
581
|
return;
|
|
196
582
|
}
|
|
197
583
|
const keys = items.map(item => item.key);
|
|
584
|
+
if (scope === _Storage.StorageScope.Secure) {
|
|
585
|
+
flushSecureWrites();
|
|
586
|
+
}
|
|
198
587
|
getStorageModule().removeBatch(keys, scope);
|
|
199
|
-
|
|
588
|
+
keys.forEach(key => cacheRawValue(scope, key, undefined));
|
|
589
|
+
}
|
|
590
|
+
function registerMigration(version, migration) {
|
|
591
|
+
if (!Number.isInteger(version) || version <= 0) {
|
|
592
|
+
throw new Error("Migration version must be a positive integer.");
|
|
593
|
+
}
|
|
594
|
+
if (registeredMigrations.has(version)) {
|
|
595
|
+
throw new Error(`Migration version ${version} is already registered.`);
|
|
596
|
+
}
|
|
597
|
+
registeredMigrations.set(version, migration);
|
|
598
|
+
}
|
|
599
|
+
function migrateToLatest(scope = _Storage.StorageScope.Disk) {
|
|
600
|
+
(0, _internal.assertValidScope)(scope);
|
|
601
|
+
const currentVersion = readMigrationVersion(scope);
|
|
602
|
+
const versions = Array.from(registeredMigrations.keys()).filter(version => version > currentVersion).sort((a, b) => a - b);
|
|
603
|
+
let appliedVersion = currentVersion;
|
|
604
|
+
const context = {
|
|
605
|
+
scope,
|
|
606
|
+
getRaw: key => getRawValue(key, scope),
|
|
607
|
+
setRaw: (key, value) => setRawValue(key, value, scope),
|
|
608
|
+
removeRaw: key => removeRawValue(key, scope)
|
|
609
|
+
};
|
|
610
|
+
versions.forEach(version => {
|
|
611
|
+
const migration = registeredMigrations.get(version);
|
|
612
|
+
if (!migration) {
|
|
613
|
+
return;
|
|
614
|
+
}
|
|
615
|
+
migration(context);
|
|
616
|
+
writeMigrationVersion(scope, version);
|
|
617
|
+
appliedVersion = version;
|
|
618
|
+
});
|
|
619
|
+
return appliedVersion;
|
|
620
|
+
}
|
|
621
|
+
function runTransaction(scope, transaction) {
|
|
622
|
+
(0, _internal.assertValidScope)(scope);
|
|
623
|
+
if (scope === _Storage.StorageScope.Secure) {
|
|
624
|
+
flushSecureWrites();
|
|
625
|
+
}
|
|
626
|
+
const rollback = new Map();
|
|
627
|
+
const rememberRollback = key => {
|
|
628
|
+
if (rollback.has(key)) {
|
|
629
|
+
return;
|
|
630
|
+
}
|
|
631
|
+
rollback.set(key, getRawValue(key, scope));
|
|
632
|
+
};
|
|
633
|
+
const tx = {
|
|
634
|
+
scope,
|
|
635
|
+
getRaw: key => getRawValue(key, scope),
|
|
636
|
+
setRaw: (key, value) => {
|
|
637
|
+
rememberRollback(key);
|
|
638
|
+
setRawValue(key, value, scope);
|
|
639
|
+
},
|
|
640
|
+
removeRaw: key => {
|
|
641
|
+
rememberRollback(key);
|
|
642
|
+
removeRawValue(key, scope);
|
|
643
|
+
},
|
|
644
|
+
getItem: item => {
|
|
645
|
+
(0, _internal.assertBatchScope)([item], scope);
|
|
646
|
+
return item.get();
|
|
647
|
+
},
|
|
648
|
+
setItem: (item, value) => {
|
|
649
|
+
(0, _internal.assertBatchScope)([item], scope);
|
|
650
|
+
rememberRollback(item.key);
|
|
651
|
+
item.set(value);
|
|
652
|
+
},
|
|
653
|
+
removeItem: item => {
|
|
654
|
+
(0, _internal.assertBatchScope)([item], scope);
|
|
655
|
+
rememberRollback(item.key);
|
|
656
|
+
item.delete();
|
|
657
|
+
}
|
|
658
|
+
};
|
|
659
|
+
try {
|
|
660
|
+
return transaction(tx);
|
|
661
|
+
} catch (error) {
|
|
662
|
+
Array.from(rollback.entries()).reverse().forEach(([key, previousValue]) => {
|
|
663
|
+
if (previousValue === undefined) {
|
|
664
|
+
removeRawValue(key, scope);
|
|
665
|
+
} else {
|
|
666
|
+
setRawValue(key, previousValue, scope);
|
|
667
|
+
}
|
|
668
|
+
});
|
|
669
|
+
throw error;
|
|
670
|
+
}
|
|
200
671
|
}
|
|
201
672
|
//# sourceMappingURL=index.js.map
|