react-native-nitro-storage 0.5.0 → 0.5.1
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 +40 -6
- package/docs/api-reference.md +95 -31
- package/docs/batch-transactions-migrations.md +23 -9
- package/docs/recipes.md +30 -9
- package/docs/secure-storage.md +19 -0
- package/lib/commonjs/index.js +214 -13
- package/lib/commonjs/index.js.map +1 -1
- package/lib/commonjs/index.web.js +166 -11
- package/lib/commonjs/index.web.js.map +1 -1
- package/lib/commonjs/storage-events.js +117 -0
- package/lib/commonjs/storage-events.js.map +1 -0
- package/lib/module/index.js +214 -13
- package/lib/module/index.js.map +1 -1
- package/lib/module/index.web.js +166 -11
- package/lib/module/index.web.js.map +1 -1
- package/lib/module/storage-events.js +112 -0
- package/lib/module/storage-events.js.map +1 -0
- package/lib/typescript/index.d.ts +14 -0
- package/lib/typescript/index.d.ts.map +1 -1
- package/lib/typescript/index.web.d.ts +14 -0
- package/lib/typescript/index.web.d.ts.map +1 -1
- package/lib/typescript/storage-events.d.ts +37 -0
- package/lib/typescript/storage-events.d.ts.map +1 -0
- package/package.json +5 -4
- package/src/index.ts +534 -13
- package/src/index.web.ts +459 -22
- package/src/storage-events.ts +184 -0
package/lib/commonjs/index.js
CHANGED
|
@@ -76,6 +76,7 @@ var _reactNativeNitroModules = require("react-native-nitro-modules");
|
|
|
76
76
|
var _Storage = require("./Storage.types");
|
|
77
77
|
var _internal = require("./internal");
|
|
78
78
|
var _storageRuntime = require("./storage-runtime");
|
|
79
|
+
var _storageEvents = require("./storage-events");
|
|
79
80
|
var _migration = require("./migration");
|
|
80
81
|
var _storageHooks = require("./storage-hooks");
|
|
81
82
|
var _indexeddbBackend = require("./indexeddb-backend");
|
|
@@ -111,8 +112,11 @@ let diskWritesAsync = false;
|
|
|
111
112
|
const pendingSecureWrites = new Map();
|
|
112
113
|
let secureFlushScheduled = false;
|
|
113
114
|
let secureDefaultAccessControl = _Storage.AccessControl.WhenUnlocked;
|
|
115
|
+
const suppressedNativeEvents = new Map([[_Storage.StorageScope.Disk, new Map()], [_Storage.StorageScope.Secure, new Map()]]);
|
|
114
116
|
let metricsObserver;
|
|
117
|
+
let eventObserver;
|
|
115
118
|
const metricsCounters = new Map();
|
|
119
|
+
const storageEvents = new _storageEvents.StorageEventRegistry();
|
|
116
120
|
const nativeSecureBackend = "platform-secure-storage";
|
|
117
121
|
function recordMetric(operation, scope, durationMs, keysCount = 1) {
|
|
118
122
|
const existing = metricsCounters.get(operation);
|
|
@@ -163,6 +167,23 @@ function hasCachedRawValue(scope, key) {
|
|
|
163
167
|
function clearScopeRawCache(scope) {
|
|
164
168
|
getScopeRawCache(scope).clear();
|
|
165
169
|
}
|
|
170
|
+
function suppressNativeEvent(scope, key) {
|
|
171
|
+
const suppressedEvents = suppressedNativeEvents.get(scope);
|
|
172
|
+
suppressedEvents.set(key, (suppressedEvents.get(key) ?? 0) + 1);
|
|
173
|
+
}
|
|
174
|
+
function consumeSuppressedNativeEvent(scope, key) {
|
|
175
|
+
const suppressedEvents = suppressedNativeEvents.get(scope);
|
|
176
|
+
const count = suppressedEvents.get(key);
|
|
177
|
+
if (count === undefined) {
|
|
178
|
+
return false;
|
|
179
|
+
}
|
|
180
|
+
if (count <= 1) {
|
|
181
|
+
suppressedEvents.delete(key);
|
|
182
|
+
} else {
|
|
183
|
+
suppressedEvents.set(key, count - 1);
|
|
184
|
+
}
|
|
185
|
+
return true;
|
|
186
|
+
}
|
|
166
187
|
function notifyKeyListeners(registry, key) {
|
|
167
188
|
const listeners = registry.get(key);
|
|
168
189
|
if (listeners) {
|
|
@@ -196,6 +217,52 @@ function addKeyListener(registry, key, listener) {
|
|
|
196
217
|
}
|
|
197
218
|
};
|
|
198
219
|
}
|
|
220
|
+
function getEventRawValue(scope, key) {
|
|
221
|
+
if (scope === _Storage.StorageScope.Memory) {
|
|
222
|
+
const value = memoryStore.get(key);
|
|
223
|
+
return typeof value === "string" ? value : undefined;
|
|
224
|
+
}
|
|
225
|
+
return getRawValue(key, scope);
|
|
226
|
+
}
|
|
227
|
+
function createKeyChange(scope, key, oldValue, newValue, operation, source) {
|
|
228
|
+
return {
|
|
229
|
+
type: "key",
|
|
230
|
+
scope,
|
|
231
|
+
key,
|
|
232
|
+
oldValue,
|
|
233
|
+
newValue,
|
|
234
|
+
operation,
|
|
235
|
+
source
|
|
236
|
+
};
|
|
237
|
+
}
|
|
238
|
+
function hasStorageChangeObservers(scope) {
|
|
239
|
+
return storageEvents.hasListeners(scope) || eventObserver !== undefined;
|
|
240
|
+
}
|
|
241
|
+
function emitKeyChange(scope, key, oldValue, newValue, operation, source) {
|
|
242
|
+
if (source === "native" && operation !== "external" && scope !== _Storage.StorageScope.Memory && scopedUnsubscribers.has(scope)) {
|
|
243
|
+
suppressNativeEvent(scope, key);
|
|
244
|
+
}
|
|
245
|
+
const event = createKeyChange(scope, key, oldValue, newValue, operation, source);
|
|
246
|
+
storageEvents.emitKey(event);
|
|
247
|
+
eventObserver?.(event);
|
|
248
|
+
}
|
|
249
|
+
function emitBatchChange(scope, operation, source, changes) {
|
|
250
|
+
if (changes.length === 0) {
|
|
251
|
+
return;
|
|
252
|
+
}
|
|
253
|
+
if (source === "native" && operation !== "external" && scope !== _Storage.StorageScope.Memory && scopedUnsubscribers.has(scope)) {
|
|
254
|
+
changes.forEach(change => suppressNativeEvent(scope, change.key));
|
|
255
|
+
}
|
|
256
|
+
const event = {
|
|
257
|
+
type: "batch",
|
|
258
|
+
scope,
|
|
259
|
+
operation,
|
|
260
|
+
source,
|
|
261
|
+
changes
|
|
262
|
+
};
|
|
263
|
+
storageEvents.emitBatch(event);
|
|
264
|
+
eventObserver?.(event);
|
|
265
|
+
}
|
|
199
266
|
function readPendingSecureWrite(key) {
|
|
200
267
|
return pendingSecureWrites.get(key)?.value;
|
|
201
268
|
}
|
|
@@ -332,14 +399,19 @@ function ensureNativeScopeSubscription(scope) {
|
|
|
332
399
|
notifyAllListeners(getScopedListeners(scope));
|
|
333
400
|
return;
|
|
334
401
|
}
|
|
402
|
+
const oldValue = readCachedRawValue(scope, key);
|
|
335
403
|
cacheRawValue(scope, key, value);
|
|
336
404
|
notifyKeyListeners(getScopedListeners(scope), key);
|
|
405
|
+
if (consumeSuppressedNativeEvent(scope, key)) {
|
|
406
|
+
return;
|
|
407
|
+
}
|
|
408
|
+
emitKeyChange(scope, key, oldValue, value, "external", "native");
|
|
337
409
|
});
|
|
338
|
-
scopedUnsubscribers.set(scope, unsubscribe);
|
|
410
|
+
scopedUnsubscribers.set(scope, typeof unsubscribe === "function" ? unsubscribe : () => {});
|
|
339
411
|
}
|
|
340
412
|
function maybeCleanupNativeScopeSubscription(scope) {
|
|
341
413
|
const listeners = getScopedListeners(scope);
|
|
342
|
-
if (listeners.size > 0) {
|
|
414
|
+
if (listeners.size > 0 || storageEvents.hasListeners(scope) || eventObserver !== undefined) {
|
|
343
415
|
return;
|
|
344
416
|
}
|
|
345
417
|
const unsubscribe = scopedUnsubscribers.get(scope);
|
|
@@ -365,15 +437,18 @@ function getRawValue(key, scope) {
|
|
|
365
437
|
}
|
|
366
438
|
function setRawValue(key, value, scope) {
|
|
367
439
|
(0, _internal.assertValidScope)(scope);
|
|
440
|
+
const oldValue = scope === _Storage.StorageScope.Memory ? getEventRawValue(scope, key) : undefined;
|
|
368
441
|
if (scope === _Storage.StorageScope.Memory) {
|
|
369
442
|
memoryStore.set(key, value);
|
|
370
443
|
notifyKeyListeners(memoryListeners, key);
|
|
444
|
+
emitKeyChange(scope, key, oldValue, value, "set", "memory");
|
|
371
445
|
return;
|
|
372
446
|
}
|
|
373
447
|
if (scope === _Storage.StorageScope.Disk) {
|
|
374
448
|
cacheRawValue(scope, key, value);
|
|
375
449
|
if (diskWritesAsync) {
|
|
376
450
|
scheduleDiskWrite(key, value);
|
|
451
|
+
emitKeyChange(scope, key, oldValue, value, "set", "native");
|
|
377
452
|
return;
|
|
378
453
|
}
|
|
379
454
|
flushDiskWrites();
|
|
@@ -386,18 +461,22 @@ function setRawValue(key, value, scope) {
|
|
|
386
461
|
}
|
|
387
462
|
getStorageModule().set(key, value, scope);
|
|
388
463
|
cacheRawValue(scope, key, value);
|
|
464
|
+
emitKeyChange(scope, key, oldValue, value, "set", "native");
|
|
389
465
|
}
|
|
390
466
|
function removeRawValue(key, scope) {
|
|
391
467
|
(0, _internal.assertValidScope)(scope);
|
|
468
|
+
const oldValue = getEventRawValue(scope, key);
|
|
392
469
|
if (scope === _Storage.StorageScope.Memory) {
|
|
393
470
|
memoryStore.delete(key);
|
|
394
471
|
notifyKeyListeners(memoryListeners, key);
|
|
472
|
+
emitKeyChange(scope, key, oldValue, undefined, "remove", "memory");
|
|
395
473
|
return;
|
|
396
474
|
}
|
|
397
475
|
if (scope === _Storage.StorageScope.Disk) {
|
|
398
476
|
cacheRawValue(scope, key, undefined);
|
|
399
477
|
if (diskWritesAsync) {
|
|
400
478
|
scheduleDiskWrite(key, undefined);
|
|
479
|
+
emitKeyChange(scope, key, oldValue, undefined, "remove", "native");
|
|
401
480
|
return;
|
|
402
481
|
}
|
|
403
482
|
flushDiskWrites();
|
|
@@ -409,6 +488,7 @@ function removeRawValue(key, scope) {
|
|
|
409
488
|
}
|
|
410
489
|
getStorageModule().remove(key, scope);
|
|
411
490
|
cacheRawValue(scope, key, undefined);
|
|
491
|
+
emitKeyChange(scope, key, oldValue, undefined, "remove", "native");
|
|
412
492
|
}
|
|
413
493
|
function readMigrationVersion(scope) {
|
|
414
494
|
const raw = getRawValue(_internal.MIGRATION_VERSION_KEY, scope);
|
|
@@ -422,11 +502,62 @@ function writeMigrationVersion(scope, version) {
|
|
|
422
502
|
setRawValue(_internal.MIGRATION_VERSION_KEY, String(version), scope);
|
|
423
503
|
}
|
|
424
504
|
const storage = exports.storage = {
|
|
505
|
+
subscribe: (scope, listener) => {
|
|
506
|
+
(0, _internal.assertValidScope)(scope);
|
|
507
|
+
if (scope !== _Storage.StorageScope.Memory) {
|
|
508
|
+
ensureNativeScopeSubscription(scope);
|
|
509
|
+
const unsubscribe = storageEvents.subscribe(scope, listener);
|
|
510
|
+
return () => {
|
|
511
|
+
unsubscribe();
|
|
512
|
+
maybeCleanupNativeScopeSubscription(scope);
|
|
513
|
+
};
|
|
514
|
+
}
|
|
515
|
+
return storageEvents.subscribe(scope, listener);
|
|
516
|
+
},
|
|
517
|
+
subscribeKey: (scope, key, listener) => {
|
|
518
|
+
(0, _internal.assertValidScope)(scope);
|
|
519
|
+
if (scope !== _Storage.StorageScope.Memory) {
|
|
520
|
+
ensureNativeScopeSubscription(scope);
|
|
521
|
+
const unsubscribe = storageEvents.subscribeKey(scope, key, listener);
|
|
522
|
+
return () => {
|
|
523
|
+
unsubscribe();
|
|
524
|
+
maybeCleanupNativeScopeSubscription(scope);
|
|
525
|
+
};
|
|
526
|
+
}
|
|
527
|
+
return storageEvents.subscribeKey(scope, key, listener);
|
|
528
|
+
},
|
|
529
|
+
subscribePrefix: (scope, prefix, listener) => {
|
|
530
|
+
(0, _internal.assertValidScope)(scope);
|
|
531
|
+
if (scope !== _Storage.StorageScope.Memory) {
|
|
532
|
+
ensureNativeScopeSubscription(scope);
|
|
533
|
+
const unsubscribe = storageEvents.subscribePrefix(scope, prefix, listener);
|
|
534
|
+
return () => {
|
|
535
|
+
unsubscribe();
|
|
536
|
+
maybeCleanupNativeScopeSubscription(scope);
|
|
537
|
+
};
|
|
538
|
+
}
|
|
539
|
+
return storageEvents.subscribePrefix(scope, prefix, listener);
|
|
540
|
+
},
|
|
541
|
+
subscribeNamespace: (namespace, scope, listener) => {
|
|
542
|
+
return storage.subscribePrefix(scope, (0, _internal.prefixKey)(namespace, ""), listener);
|
|
543
|
+
},
|
|
544
|
+
setEventObserver: observer => {
|
|
545
|
+
eventObserver = observer;
|
|
546
|
+
if (observer) {
|
|
547
|
+
ensureNativeScopeSubscription(_Storage.StorageScope.Disk);
|
|
548
|
+
ensureNativeScopeSubscription(_Storage.StorageScope.Secure);
|
|
549
|
+
return;
|
|
550
|
+
}
|
|
551
|
+
maybeCleanupNativeScopeSubscription(_Storage.StorageScope.Disk);
|
|
552
|
+
maybeCleanupNativeScopeSubscription(_Storage.StorageScope.Secure);
|
|
553
|
+
},
|
|
425
554
|
clear: scope => {
|
|
426
555
|
measureOperation("storage:clear", scope, () => {
|
|
556
|
+
const previousValues = hasStorageChangeObservers(scope) ? storage.getAll(scope) : {};
|
|
427
557
|
if (scope === _Storage.StorageScope.Memory) {
|
|
428
558
|
memoryStore.clear();
|
|
429
559
|
notifyAllListeners(memoryListeners);
|
|
560
|
+
emitBatchChange(scope, "clear", "memory", Object.keys(previousValues).map(key => createKeyChange(scope, key, previousValues[key], undefined, "clear", "memory")));
|
|
430
561
|
return;
|
|
431
562
|
}
|
|
432
563
|
if (scope === _Storage.StorageScope.Disk) {
|
|
@@ -439,6 +570,7 @@ const storage = exports.storage = {
|
|
|
439
570
|
}
|
|
440
571
|
clearScopeRawCache(scope);
|
|
441
572
|
getStorageModule().clear(scope);
|
|
573
|
+
emitBatchChange(scope, "clear", "native", Object.keys(previousValues).map(key => createKeyChange(scope, key, previousValues[key], undefined, "clear", "native")));
|
|
442
574
|
});
|
|
443
575
|
},
|
|
444
576
|
clearAll: () => {
|
|
@@ -452,15 +584,26 @@ const storage = exports.storage = {
|
|
|
452
584
|
measureOperation("storage:clearNamespace", scope, () => {
|
|
453
585
|
(0, _internal.assertValidScope)(scope);
|
|
454
586
|
if (scope === _Storage.StorageScope.Memory) {
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
587
|
+
const affectedKeys = Array.from(memoryStore.keys()).filter(key => (0, _internal.isNamespaced)(key, namespace));
|
|
588
|
+
const previousValues = affectedKeys.map(key => ({
|
|
589
|
+
key,
|
|
590
|
+
value: getEventRawValue(scope, key)
|
|
591
|
+
}));
|
|
592
|
+
if (affectedKeys.length === 0) {
|
|
593
|
+
return;
|
|
459
594
|
}
|
|
460
|
-
|
|
595
|
+
affectedKeys.forEach(key => {
|
|
596
|
+
memoryStore.delete(key);
|
|
597
|
+
});
|
|
598
|
+
affectedKeys.forEach(key => notifyKeyListeners(memoryListeners, key));
|
|
599
|
+
emitBatchChange(scope, "clearNamespace", "memory", previousValues.map(({
|
|
600
|
+
key,
|
|
601
|
+
value
|
|
602
|
+
}) => createKeyChange(scope, key, value, undefined, "clearNamespace", "memory")));
|
|
461
603
|
return;
|
|
462
604
|
}
|
|
463
605
|
const keyPrefix = (0, _internal.prefixKey)(namespace, "");
|
|
606
|
+
const previousValues = hasStorageChangeObservers(scope) ? storage.getByPrefix(keyPrefix, scope) : {};
|
|
464
607
|
if (scope === _Storage.StorageScope.Disk) {
|
|
465
608
|
flushDiskWrites();
|
|
466
609
|
}
|
|
@@ -474,6 +617,7 @@ const storage = exports.storage = {
|
|
|
474
617
|
}
|
|
475
618
|
}
|
|
476
619
|
getStorageModule().removeByPrefix(keyPrefix, scope);
|
|
620
|
+
emitBatchChange(scope, "clearNamespace", "native", Object.keys(previousValues).map(key => createKeyChange(scope, key, previousValues[key], undefined, "clearNamespace", "native")));
|
|
477
621
|
});
|
|
478
622
|
},
|
|
479
623
|
clearBiometric: () => {
|
|
@@ -523,7 +667,7 @@ const storage = exports.storage = {
|
|
|
523
667
|
if (scope === _Storage.StorageScope.Secure) {
|
|
524
668
|
flushSecureWrites();
|
|
525
669
|
}
|
|
526
|
-
return getStorageModule().getKeysByPrefix(prefix, scope);
|
|
670
|
+
return getStorageModule().getKeysByPrefix(prefix, scope) ?? [];
|
|
527
671
|
});
|
|
528
672
|
},
|
|
529
673
|
getByPrefix: (prefix, scope) => {
|
|
@@ -548,7 +692,7 @@ const storage = exports.storage = {
|
|
|
548
692
|
if (scope === _Storage.StorageScope.Secure) {
|
|
549
693
|
flushSecureWrites();
|
|
550
694
|
}
|
|
551
|
-
const values = getStorageModule().getBatch(keys, scope);
|
|
695
|
+
const values = getStorageModule().getBatch(keys, scope) ?? [];
|
|
552
696
|
keys.forEach((key, idx) => {
|
|
553
697
|
const value = (0, _internal.decodeNativeBatchValue)(values[idx]);
|
|
554
698
|
if (value !== undefined) {
|
|
@@ -563,9 +707,10 @@ const storage = exports.storage = {
|
|
|
563
707
|
(0, _internal.assertValidScope)(scope);
|
|
564
708
|
const result = {};
|
|
565
709
|
if (scope === _Storage.StorageScope.Memory) {
|
|
566
|
-
memoryStore.
|
|
710
|
+
for (const key of memoryStore.keys()) {
|
|
711
|
+
const value = memoryStore.get(key);
|
|
567
712
|
if (typeof value === "string") result[key] = value;
|
|
568
|
-
}
|
|
713
|
+
}
|
|
569
714
|
return result;
|
|
570
715
|
}
|
|
571
716
|
if (scope === _Storage.StorageScope.Disk) {
|
|
@@ -574,9 +719,9 @@ const storage = exports.storage = {
|
|
|
574
719
|
if (scope === _Storage.StorageScope.Secure) {
|
|
575
720
|
flushSecureWrites();
|
|
576
721
|
}
|
|
577
|
-
const keys = getStorageModule().getAllKeys(scope);
|
|
722
|
+
const keys = getStorageModule().getAllKeys(scope) ?? [];
|
|
578
723
|
if (keys.length === 0) return result;
|
|
579
|
-
const values = getStorageModule().getBatch(keys, scope);
|
|
724
|
+
const values = getStorageModule().getBatch(keys, scope) ?? [];
|
|
580
725
|
keys.forEach((key, idx) => {
|
|
581
726
|
const val = (0, _internal.decodeNativeBatchValue)(values[idx]);
|
|
582
727
|
if (val !== undefined) result[key] = val;
|
|
@@ -584,6 +729,9 @@ const storage = exports.storage = {
|
|
|
584
729
|
return result;
|
|
585
730
|
});
|
|
586
731
|
},
|
|
732
|
+
export: scope => {
|
|
733
|
+
return measureOperation("storage:export", scope, () => storage.getAll(scope));
|
|
734
|
+
},
|
|
587
735
|
size: scope => {
|
|
588
736
|
return measureOperation("storage:size", scope, () => {
|
|
589
737
|
(0, _internal.assertValidScope)(scope);
|
|
@@ -733,11 +881,13 @@ const storage = exports.storage = {
|
|
|
733
881
|
(0, _internal.assertValidScope)(scope);
|
|
734
882
|
if (keys.length === 0) return;
|
|
735
883
|
const values = keys.map(k => data[k]);
|
|
884
|
+
const changes = keys.map((key, index) => createKeyChange(scope, key, getEventRawValue(scope, key), values[index], "import", scope === _Storage.StorageScope.Memory ? "memory" : "native"));
|
|
736
885
|
if (scope === _Storage.StorageScope.Memory) {
|
|
737
886
|
keys.forEach((key, index) => {
|
|
738
887
|
memoryStore.set(key, values[index]);
|
|
739
888
|
});
|
|
740
889
|
keys.forEach(key => notifyKeyListeners(memoryListeners, key));
|
|
890
|
+
emitBatchChange(scope, "import", "memory", changes);
|
|
741
891
|
return;
|
|
742
892
|
}
|
|
743
893
|
if (scope === _Storage.StorageScope.Secure) {
|
|
@@ -746,6 +896,7 @@ const storage = exports.storage = {
|
|
|
746
896
|
}
|
|
747
897
|
getStorageModule().setBatch(keys, values, scope);
|
|
748
898
|
keys.forEach((key, index) => cacheRawValue(scope, key, values[index]));
|
|
899
|
+
emitBatchChange(scope, "import", "native", changes);
|
|
749
900
|
}, keys.length);
|
|
750
901
|
}
|
|
751
902
|
};
|
|
@@ -866,20 +1017,24 @@ function createStorageItem(config) {
|
|
|
866
1017
|
return raw;
|
|
867
1018
|
};
|
|
868
1019
|
const writeStoredRaw = rawValue => {
|
|
1020
|
+
const oldValue = undefined;
|
|
869
1021
|
if (isBiometric) {
|
|
870
1022
|
getStorageModule().setSecureBiometricWithLevel(storageKey, rawValue, resolvedBiometricLevel);
|
|
1023
|
+
emitKeyChange(config.scope, storageKey, oldValue, rawValue, "set", "native");
|
|
871
1024
|
return;
|
|
872
1025
|
}
|
|
873
1026
|
cacheRawValue(nonMemoryScope, storageKey, rawValue);
|
|
874
1027
|
if (nonMemoryScope === _Storage.StorageScope.Disk) {
|
|
875
1028
|
if (coalesceDiskWrites || diskWritesAsync) {
|
|
876
1029
|
scheduleDiskWrite(storageKey, rawValue);
|
|
1030
|
+
emitKeyChange(config.scope, storageKey, oldValue, rawValue, "set", "native");
|
|
877
1031
|
return;
|
|
878
1032
|
}
|
|
879
1033
|
clearPendingDiskWrite(storageKey);
|
|
880
1034
|
}
|
|
881
1035
|
if (coalesceSecureWrites) {
|
|
882
1036
|
scheduleSecureWrite(storageKey, rawValue, secureAccessControl ?? secureDefaultAccessControl);
|
|
1037
|
+
emitKeyChange(config.scope, storageKey, oldValue, rawValue, "set", "native");
|
|
883
1038
|
return;
|
|
884
1039
|
}
|
|
885
1040
|
if (nonMemoryScope === _Storage.StorageScope.Secure) {
|
|
@@ -887,36 +1042,44 @@ function createStorageItem(config) {
|
|
|
887
1042
|
getStorageModule().setSecureAccessControl(secureAccessControl ?? secureDefaultAccessControl);
|
|
888
1043
|
}
|
|
889
1044
|
getStorageModule().set(storageKey, rawValue, config.scope);
|
|
1045
|
+
emitKeyChange(config.scope, storageKey, oldValue, rawValue, "set", "native");
|
|
890
1046
|
};
|
|
891
1047
|
const removeStoredRaw = () => {
|
|
1048
|
+
const oldValue = getEventRawValue(config.scope, storageKey);
|
|
892
1049
|
if (isBiometric) {
|
|
893
1050
|
getStorageModule().deleteSecureBiometric(storageKey);
|
|
1051
|
+
emitKeyChange(config.scope, storageKey, oldValue, undefined, "remove", "native");
|
|
894
1052
|
return;
|
|
895
1053
|
}
|
|
896
1054
|
cacheRawValue(nonMemoryScope, storageKey, undefined);
|
|
897
1055
|
if (nonMemoryScope === _Storage.StorageScope.Disk) {
|
|
898
1056
|
if (coalesceDiskWrites || diskWritesAsync) {
|
|
899
1057
|
scheduleDiskWrite(storageKey, undefined);
|
|
1058
|
+
emitKeyChange(config.scope, storageKey, oldValue, undefined, "remove", "native");
|
|
900
1059
|
return;
|
|
901
1060
|
}
|
|
902
1061
|
clearPendingDiskWrite(storageKey);
|
|
903
1062
|
}
|
|
904
1063
|
if (coalesceSecureWrites) {
|
|
905
1064
|
scheduleSecureWrite(storageKey, undefined, secureAccessControl ?? secureDefaultAccessControl);
|
|
1065
|
+
emitKeyChange(config.scope, storageKey, oldValue, undefined, "remove", "native");
|
|
906
1066
|
return;
|
|
907
1067
|
}
|
|
908
1068
|
if (nonMemoryScope === _Storage.StorageScope.Secure) {
|
|
909
1069
|
clearPendingSecureWrite(storageKey);
|
|
910
1070
|
}
|
|
911
1071
|
getStorageModule().remove(storageKey, config.scope);
|
|
1072
|
+
emitKeyChange(config.scope, storageKey, oldValue, undefined, "remove", "native");
|
|
912
1073
|
};
|
|
913
1074
|
const writeValueWithoutValidation = value => {
|
|
914
1075
|
if (isMemory) {
|
|
1076
|
+
const oldValue = getEventRawValue(config.scope, storageKey);
|
|
915
1077
|
if (memoryExpiration) {
|
|
916
1078
|
memoryExpiration.set(storageKey, Date.now() + (expirationTtlMs ?? 0));
|
|
917
1079
|
}
|
|
918
1080
|
memoryStore.set(storageKey, value);
|
|
919
1081
|
notifyKeyListeners(memoryListeners, storageKey);
|
|
1082
|
+
emitKeyChange(config.scope, storageKey, oldValue, typeof value === "string" ? value : undefined, "set", "memory");
|
|
920
1083
|
return;
|
|
921
1084
|
}
|
|
922
1085
|
const serialized = serialize(value);
|
|
@@ -1048,11 +1211,13 @@ function createStorageItem(config) {
|
|
|
1048
1211
|
measureOperation("item:delete", config.scope, () => {
|
|
1049
1212
|
invalidateParsedCache();
|
|
1050
1213
|
if (isMemory) {
|
|
1214
|
+
const oldValue = getEventRawValue(config.scope, storageKey);
|
|
1051
1215
|
if (memoryExpiration) {
|
|
1052
1216
|
memoryExpiration.delete(storageKey);
|
|
1053
1217
|
}
|
|
1054
1218
|
memoryStore.delete(storageKey);
|
|
1055
1219
|
notifyKeyListeners(memoryListeners, storageKey);
|
|
1220
|
+
emitKeyChange(config.scope, storageKey, oldValue, undefined, "remove", "memory");
|
|
1056
1221
|
return;
|
|
1057
1222
|
}
|
|
1058
1223
|
removeStoredRaw();
|
|
@@ -1089,6 +1254,22 @@ function createStorageItem(config) {
|
|
|
1089
1254
|
}
|
|
1090
1255
|
};
|
|
1091
1256
|
};
|
|
1257
|
+
const subscribeSelector = (selector, listener, options = {}) => {
|
|
1258
|
+
const isEqual = options.isEqual ?? Object.is;
|
|
1259
|
+
let currentValue = selector(getInternal());
|
|
1260
|
+
if (options.fireImmediately === true) {
|
|
1261
|
+
listener(currentValue, currentValue);
|
|
1262
|
+
}
|
|
1263
|
+
return subscribe(() => {
|
|
1264
|
+
const nextValue = selector(getInternal());
|
|
1265
|
+
if (isEqual(currentValue, nextValue)) {
|
|
1266
|
+
return;
|
|
1267
|
+
}
|
|
1268
|
+
const previousValue = currentValue;
|
|
1269
|
+
currentValue = nextValue;
|
|
1270
|
+
listener(nextValue, previousValue);
|
|
1271
|
+
});
|
|
1272
|
+
};
|
|
1092
1273
|
const storageItem = {
|
|
1093
1274
|
get,
|
|
1094
1275
|
getWithVersion,
|
|
@@ -1097,6 +1278,7 @@ function createStorageItem(config) {
|
|
|
1097
1278
|
delete: deleteItem,
|
|
1098
1279
|
has: hasItem,
|
|
1099
1280
|
subscribe,
|
|
1281
|
+
subscribeSelector,
|
|
1100
1282
|
serialize,
|
|
1101
1283
|
deserialize,
|
|
1102
1284
|
_triggerListeners: () => {
|
|
@@ -1198,6 +1380,10 @@ function setBatch(items, scope) {
|
|
|
1198
1380
|
}) => item.set(value));
|
|
1199
1381
|
return;
|
|
1200
1382
|
}
|
|
1383
|
+
const changes = items.map(({
|
|
1384
|
+
item,
|
|
1385
|
+
value
|
|
1386
|
+
}) => createKeyChange(scope, item.key, getEventRawValue(scope, item.key), typeof value === "string" ? value : undefined, "setBatch", "memory"));
|
|
1201
1387
|
|
|
1202
1388
|
// Atomic write: update all values in memoryStore, invalidate caches, then batch-notify
|
|
1203
1389
|
items.forEach(({
|
|
@@ -1210,6 +1396,7 @@ function setBatch(items, scope) {
|
|
|
1210
1396
|
items.forEach(({
|
|
1211
1397
|
item
|
|
1212
1398
|
}) => notifyKeyListeners(memoryListeners, item.key));
|
|
1399
|
+
emitBatchChange(scope, "setBatch", "memory", changes);
|
|
1213
1400
|
return;
|
|
1214
1401
|
}
|
|
1215
1402
|
if (scope === _Storage.StorageScope.Secure) {
|
|
@@ -1233,6 +1420,10 @@ function setBatch(items, scope) {
|
|
|
1233
1420
|
}
|
|
1234
1421
|
flushSecureWrites();
|
|
1235
1422
|
const storageModule = getStorageModule();
|
|
1423
|
+
const keys = secureEntries.map(({
|
|
1424
|
+
item
|
|
1425
|
+
}) => item.key);
|
|
1426
|
+
const oldValues = hasStorageChangeObservers(scope) ? storageModule.getBatch(keys, scope) ?? [] : [];
|
|
1236
1427
|
const groupedByAccessControl = new Map();
|
|
1237
1428
|
secureEntries.forEach(({
|
|
1238
1429
|
item,
|
|
@@ -1256,6 +1447,10 @@ function setBatch(items, scope) {
|
|
|
1256
1447
|
storageModule.setBatch(group.keys, group.values, scope);
|
|
1257
1448
|
group.keys.forEach((key, index) => cacheRawValue(scope, key, group.values[index]));
|
|
1258
1449
|
});
|
|
1450
|
+
emitBatchChange(scope, "setBatch", "native", secureEntries.map(({
|
|
1451
|
+
item,
|
|
1452
|
+
value
|
|
1453
|
+
}, index) => createKeyChange(scope, item.key, oldValues[index], item.serialize(value), "setBatch", "native")));
|
|
1259
1454
|
return;
|
|
1260
1455
|
}
|
|
1261
1456
|
flushDiskWrites();
|
|
@@ -1271,15 +1466,19 @@ function setBatch(items, scope) {
|
|
|
1271
1466
|
}
|
|
1272
1467
|
const keys = items.map(entry => entry.item.key);
|
|
1273
1468
|
const values = items.map(entry => entry.item.serialize(entry.value));
|
|
1469
|
+
const oldValues = hasStorageChangeObservers(scope) ? getStorageModule().getBatch(keys, scope) ?? [] : [];
|
|
1274
1470
|
getStorageModule().setBatch(keys, values, scope);
|
|
1275
1471
|
keys.forEach((key, index) => cacheRawValue(scope, key, values[index]));
|
|
1472
|
+
emitBatchChange(scope, "setBatch", "native", keys.map((key, index) => createKeyChange(scope, key, oldValues[index], values[index], "setBatch", "native")));
|
|
1276
1473
|
}, items.length);
|
|
1277
1474
|
}
|
|
1278
1475
|
function removeBatch(items, scope) {
|
|
1279
1476
|
measureOperation("batch:remove", scope, () => {
|
|
1280
1477
|
(0, _internal.assertBatchScope)(items, scope);
|
|
1281
1478
|
if (scope === _Storage.StorageScope.Memory) {
|
|
1479
|
+
const changes = items.map(item => createKeyChange(scope, item.key, getEventRawValue(scope, item.key), undefined, "removeBatch", "memory"));
|
|
1282
1480
|
items.forEach(item => item.delete());
|
|
1481
|
+
emitBatchChange(scope, "removeBatch", "memory", changes);
|
|
1283
1482
|
return;
|
|
1284
1483
|
}
|
|
1285
1484
|
const keys = items.map(item => item.key);
|
|
@@ -1289,8 +1488,10 @@ function removeBatch(items, scope) {
|
|
|
1289
1488
|
if (scope === _Storage.StorageScope.Secure) {
|
|
1290
1489
|
flushSecureWrites();
|
|
1291
1490
|
}
|
|
1491
|
+
const oldValues = hasStorageChangeObservers(scope) ? getStorageModule().getBatch(keys, scope) ?? [] : [];
|
|
1292
1492
|
getStorageModule().removeBatch(keys, scope);
|
|
1293
1493
|
keys.forEach(key => cacheRawValue(scope, key, undefined));
|
|
1494
|
+
emitBatchChange(scope, "removeBatch", "native", keys.map((key, index) => createKeyChange(scope, key, oldValues[index], undefined, "removeBatch", "native")));
|
|
1294
1495
|
}, items.length);
|
|
1295
1496
|
}
|
|
1296
1497
|
function registerMigration(version, migration) {
|