react-native-nitro-storage 0.5.0 → 0.5.2
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 +42 -8
- package/docs/api-reference.md +95 -31
- package/docs/batch-transactions-migrations.md +23 -9
- package/docs/benchmarks.md +1 -1
- package/docs/recipes.md +30 -9
- package/docs/secure-storage.md +20 -1
- 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/indexeddb-backend.js +7 -0
- package/lib/commonjs/indexeddb-backend.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/indexeddb-backend.js +7 -0
- package/lib/module/indexeddb-backend.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/indexeddb-backend.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/indexeddb-backend.ts +8 -0
- package/src/storage-events.ts +184 -0
|
@@ -76,6 +76,7 @@ var _Storage = require("./Storage.types");
|
|
|
76
76
|
var _internal = require("./internal");
|
|
77
77
|
var _webStorageBackend = require("./web-storage-backend");
|
|
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");
|
|
@@ -110,7 +111,9 @@ const BIOMETRIC_WEB_PREFIX = "__bio_";
|
|
|
110
111
|
let hasWarnedAboutWebBiometricFallback = false;
|
|
111
112
|
let hasWindowStorageEventSubscription = false;
|
|
112
113
|
let metricsObserver;
|
|
114
|
+
let eventObserver;
|
|
113
115
|
const metricsCounters = new Map();
|
|
116
|
+
const storageEvents = new _storageEvents.StorageEventRegistry();
|
|
114
117
|
function recordMetric(operation, scope, durationMs, keysCount = 1) {
|
|
115
118
|
const existing = metricsCounters.get(operation);
|
|
116
119
|
if (!existing) {
|
|
@@ -235,6 +238,7 @@ function applyExternalChangeEvent(scope, key, newValue) {
|
|
|
235
238
|
}
|
|
236
239
|
if (scope === _Storage.StorageScope.Secure && key.startsWith(SECURE_WEB_PREFIX)) {
|
|
237
240
|
const plainKey = fromSecureStorageKey(key);
|
|
241
|
+
const oldValue = readCachedRawValue(_Storage.StorageScope.Secure, plainKey);
|
|
238
242
|
if (newValue === null) {
|
|
239
243
|
ensureWebScopeKeyIndex(_Storage.StorageScope.Secure).delete(plainKey);
|
|
240
244
|
cacheRawValue(_Storage.StorageScope.Secure, plainKey, undefined);
|
|
@@ -243,10 +247,12 @@ function applyExternalChangeEvent(scope, key, newValue) {
|
|
|
243
247
|
cacheRawValue(_Storage.StorageScope.Secure, plainKey, newValue);
|
|
244
248
|
}
|
|
245
249
|
notifyKeyListeners(getScopedListeners(_Storage.StorageScope.Secure), plainKey);
|
|
250
|
+
emitKeyChange(_Storage.StorageScope.Secure, plainKey, oldValue, newValue ?? undefined, "external", "external");
|
|
246
251
|
return;
|
|
247
252
|
}
|
|
248
253
|
if (scope === _Storage.StorageScope.Secure && key.startsWith(BIOMETRIC_WEB_PREFIX)) {
|
|
249
254
|
const plainKey = fromBiometricStorageKey(key);
|
|
255
|
+
const oldValue = readCachedRawValue(_Storage.StorageScope.Secure, plainKey);
|
|
250
256
|
if (newValue === null) {
|
|
251
257
|
if (withWebBackendOperation(_Storage.StorageScope.Secure, "external-sync:getItem", backend => backend.getItem(toSecureStorageKey(plainKey))) === null) {
|
|
252
258
|
ensureWebScopeKeyIndex(_Storage.StorageScope.Secure).delete(plainKey);
|
|
@@ -257,8 +263,10 @@ function applyExternalChangeEvent(scope, key, newValue) {
|
|
|
257
263
|
cacheRawValue(_Storage.StorageScope.Secure, plainKey, newValue);
|
|
258
264
|
}
|
|
259
265
|
notifyKeyListeners(getScopedListeners(_Storage.StorageScope.Secure), plainKey);
|
|
266
|
+
emitKeyChange(_Storage.StorageScope.Secure, plainKey, oldValue, newValue ?? undefined, "external", "external");
|
|
260
267
|
return;
|
|
261
268
|
}
|
|
269
|
+
const oldValue = readCachedRawValue(scope, key);
|
|
262
270
|
if (newValue === null) {
|
|
263
271
|
ensureWebScopeKeyIndex(scope).delete(key);
|
|
264
272
|
cacheRawValue(scope, key, undefined);
|
|
@@ -267,6 +275,7 @@ function applyExternalChangeEvent(scope, key, newValue) {
|
|
|
267
275
|
cacheRawValue(scope, key, newValue);
|
|
268
276
|
}
|
|
269
277
|
notifyKeyListeners(getScopedListeners(scope), key);
|
|
278
|
+
emitKeyChange(scope, key, oldValue, newValue ?? undefined, "external", "external");
|
|
270
279
|
}
|
|
271
280
|
function handleWebStorageEvent(event) {
|
|
272
281
|
const key = event.key;
|
|
@@ -357,6 +366,46 @@ function addKeyListener(registry, key, listener) {
|
|
|
357
366
|
}
|
|
358
367
|
};
|
|
359
368
|
}
|
|
369
|
+
function getEventRawValue(scope, key) {
|
|
370
|
+
if (scope === _Storage.StorageScope.Memory) {
|
|
371
|
+
const value = memoryStore.get(key);
|
|
372
|
+
return typeof value === "string" ? value : undefined;
|
|
373
|
+
}
|
|
374
|
+
return getRawValue(key, scope);
|
|
375
|
+
}
|
|
376
|
+
function createKeyChange(scope, key, oldValue, newValue, operation, source) {
|
|
377
|
+
return {
|
|
378
|
+
type: "key",
|
|
379
|
+
scope,
|
|
380
|
+
key,
|
|
381
|
+
oldValue,
|
|
382
|
+
newValue,
|
|
383
|
+
operation,
|
|
384
|
+
source
|
|
385
|
+
};
|
|
386
|
+
}
|
|
387
|
+
function hasStorageChangeObservers(scope) {
|
|
388
|
+
return storageEvents.hasListeners(scope) || eventObserver !== undefined;
|
|
389
|
+
}
|
|
390
|
+
function emitKeyChange(scope, key, oldValue, newValue, operation, source) {
|
|
391
|
+
const event = createKeyChange(scope, key, oldValue, newValue, operation, source);
|
|
392
|
+
storageEvents.emitKey(event);
|
|
393
|
+
eventObserver?.(event);
|
|
394
|
+
}
|
|
395
|
+
function emitBatchChange(scope, operation, source, changes) {
|
|
396
|
+
if (changes.length === 0) {
|
|
397
|
+
return;
|
|
398
|
+
}
|
|
399
|
+
const event = {
|
|
400
|
+
type: "batch",
|
|
401
|
+
scope,
|
|
402
|
+
operation,
|
|
403
|
+
source,
|
|
404
|
+
changes
|
|
405
|
+
};
|
|
406
|
+
storageEvents.emitBatch(event);
|
|
407
|
+
eventObserver?.(event);
|
|
408
|
+
}
|
|
360
409
|
function readPendingSecureWrite(key) {
|
|
361
410
|
return pendingSecureWrites.get(key)?.value;
|
|
362
411
|
}
|
|
@@ -606,13 +655,10 @@ const WebStorage = {
|
|
|
606
655
|
return () => {};
|
|
607
656
|
},
|
|
608
657
|
has: (key, scope) => {
|
|
609
|
-
if (scope === _Storage.StorageScope.Secure) {
|
|
610
|
-
return
|
|
611
|
-
}
|
|
612
|
-
if (scope !== _Storage.StorageScope.Disk) {
|
|
613
|
-
return false;
|
|
658
|
+
if (scope === _Storage.StorageScope.Disk || scope === _Storage.StorageScope.Secure) {
|
|
659
|
+
return ensureWebScopeKeyIndex(scope).has(key);
|
|
614
660
|
}
|
|
615
|
-
return
|
|
661
|
+
return false;
|
|
616
662
|
},
|
|
617
663
|
getAllKeys: scope => {
|
|
618
664
|
if (scope !== _Storage.StorageScope.Disk && scope !== _Storage.StorageScope.Secure) {
|
|
@@ -703,15 +749,18 @@ function getRawValue(key, scope) {
|
|
|
703
749
|
}
|
|
704
750
|
function setRawValue(key, value, scope) {
|
|
705
751
|
(0, _internal.assertValidScope)(scope);
|
|
752
|
+
const oldValue = scope === _Storage.StorageScope.Memory ? getEventRawValue(scope, key) : undefined;
|
|
706
753
|
if (scope === _Storage.StorageScope.Memory) {
|
|
707
754
|
memoryStore.set(key, value);
|
|
708
755
|
notifyKeyListeners(memoryListeners, key);
|
|
756
|
+
emitKeyChange(scope, key, oldValue, value, "set", "memory");
|
|
709
757
|
return;
|
|
710
758
|
}
|
|
711
759
|
if (scope === _Storage.StorageScope.Disk) {
|
|
712
760
|
cacheRawValue(scope, key, value);
|
|
713
761
|
if (diskWritesAsync) {
|
|
714
762
|
scheduleDiskWrite(key, value);
|
|
763
|
+
emitKeyChange(scope, key, oldValue, value, "set", "web");
|
|
715
764
|
return;
|
|
716
765
|
}
|
|
717
766
|
flushDiskWrites();
|
|
@@ -723,18 +772,22 @@ function setRawValue(key, value, scope) {
|
|
|
723
772
|
}
|
|
724
773
|
WebStorage.set(key, value, scope);
|
|
725
774
|
cacheRawValue(scope, key, value);
|
|
775
|
+
emitKeyChange(scope, key, oldValue, value, "set", "web");
|
|
726
776
|
}
|
|
727
777
|
function removeRawValue(key, scope) {
|
|
728
778
|
(0, _internal.assertValidScope)(scope);
|
|
779
|
+
const oldValue = getEventRawValue(scope, key);
|
|
729
780
|
if (scope === _Storage.StorageScope.Memory) {
|
|
730
781
|
memoryStore.delete(key);
|
|
731
782
|
notifyKeyListeners(memoryListeners, key);
|
|
783
|
+
emitKeyChange(scope, key, oldValue, undefined, "remove", "memory");
|
|
732
784
|
return;
|
|
733
785
|
}
|
|
734
786
|
if (scope === _Storage.StorageScope.Disk) {
|
|
735
787
|
cacheRawValue(scope, key, undefined);
|
|
736
788
|
if (diskWritesAsync) {
|
|
737
789
|
scheduleDiskWrite(key, undefined);
|
|
790
|
+
emitKeyChange(scope, key, oldValue, undefined, "remove", "web");
|
|
738
791
|
return;
|
|
739
792
|
}
|
|
740
793
|
flushDiskWrites();
|
|
@@ -746,6 +799,7 @@ function removeRawValue(key, scope) {
|
|
|
746
799
|
}
|
|
747
800
|
WebStorage.remove(key, scope);
|
|
748
801
|
cacheRawValue(scope, key, undefined);
|
|
802
|
+
emitKeyChange(scope, key, oldValue, undefined, "remove", "web");
|
|
749
803
|
}
|
|
750
804
|
function readMigrationVersion(scope) {
|
|
751
805
|
const raw = getRawValue(_internal.MIGRATION_VERSION_KEY, scope);
|
|
@@ -759,11 +813,43 @@ function writeMigrationVersion(scope, version) {
|
|
|
759
813
|
setRawValue(_internal.MIGRATION_VERSION_KEY, String(version), scope);
|
|
760
814
|
}
|
|
761
815
|
const storage = exports.storage = {
|
|
816
|
+
subscribe: (scope, listener) => {
|
|
817
|
+
(0, _internal.assertValidScope)(scope);
|
|
818
|
+
if (scope !== _Storage.StorageScope.Memory) {
|
|
819
|
+
ensureExternalSyncSubscriptions();
|
|
820
|
+
}
|
|
821
|
+
return storageEvents.subscribe(scope, listener);
|
|
822
|
+
},
|
|
823
|
+
subscribeKey: (scope, key, listener) => {
|
|
824
|
+
(0, _internal.assertValidScope)(scope);
|
|
825
|
+
if (scope !== _Storage.StorageScope.Memory) {
|
|
826
|
+
ensureExternalSyncSubscriptions();
|
|
827
|
+
}
|
|
828
|
+
return storageEvents.subscribeKey(scope, key, listener);
|
|
829
|
+
},
|
|
830
|
+
subscribePrefix: (scope, prefix, listener) => {
|
|
831
|
+
(0, _internal.assertValidScope)(scope);
|
|
832
|
+
if (scope !== _Storage.StorageScope.Memory) {
|
|
833
|
+
ensureExternalSyncSubscriptions();
|
|
834
|
+
}
|
|
835
|
+
return storageEvents.subscribePrefix(scope, prefix, listener);
|
|
836
|
+
},
|
|
837
|
+
subscribeNamespace: (namespace, scope, listener) => {
|
|
838
|
+
return storage.subscribePrefix(scope, (0, _internal.prefixKey)(namespace, ""), listener);
|
|
839
|
+
},
|
|
840
|
+
setEventObserver: observer => {
|
|
841
|
+
eventObserver = observer;
|
|
842
|
+
if (observer) {
|
|
843
|
+
ensureExternalSyncSubscriptions();
|
|
844
|
+
}
|
|
845
|
+
},
|
|
762
846
|
clear: scope => {
|
|
763
847
|
measureOperation("storage:clear", scope, () => {
|
|
848
|
+
const previousValues = hasStorageChangeObservers(scope) ? storage.getAll(scope) : {};
|
|
764
849
|
if (scope === _Storage.StorageScope.Memory) {
|
|
765
850
|
memoryStore.clear();
|
|
766
851
|
notifyAllListeners(memoryListeners);
|
|
852
|
+
emitBatchChange(scope, "clear", "memory", Object.keys(previousValues).map(key => createKeyChange(scope, key, previousValues[key], undefined, "clear", "memory")));
|
|
767
853
|
return;
|
|
768
854
|
}
|
|
769
855
|
if (scope === _Storage.StorageScope.Disk) {
|
|
@@ -776,6 +862,7 @@ const storage = exports.storage = {
|
|
|
776
862
|
}
|
|
777
863
|
clearScopeRawCache(scope);
|
|
778
864
|
WebStorage.clear(scope);
|
|
865
|
+
emitBatchChange(scope, "clear", "web", Object.keys(previousValues).map(key => createKeyChange(scope, key, previousValues[key], undefined, "clear", "web")));
|
|
779
866
|
});
|
|
780
867
|
},
|
|
781
868
|
clearAll: () => {
|
|
@@ -789,15 +876,26 @@ const storage = exports.storage = {
|
|
|
789
876
|
measureOperation("storage:clearNamespace", scope, () => {
|
|
790
877
|
(0, _internal.assertValidScope)(scope);
|
|
791
878
|
if (scope === _Storage.StorageScope.Memory) {
|
|
792
|
-
|
|
793
|
-
|
|
794
|
-
|
|
795
|
-
|
|
879
|
+
const affectedKeys = Array.from(memoryStore.keys()).filter(key => (0, _internal.isNamespaced)(key, namespace));
|
|
880
|
+
const previousValues = affectedKeys.map(key => ({
|
|
881
|
+
key,
|
|
882
|
+
value: getEventRawValue(scope, key)
|
|
883
|
+
}));
|
|
884
|
+
if (affectedKeys.length === 0) {
|
|
885
|
+
return;
|
|
796
886
|
}
|
|
797
|
-
|
|
887
|
+
affectedKeys.forEach(key => {
|
|
888
|
+
memoryStore.delete(key);
|
|
889
|
+
});
|
|
890
|
+
affectedKeys.forEach(key => notifyKeyListeners(memoryListeners, key));
|
|
891
|
+
emitBatchChange(scope, "clearNamespace", "memory", previousValues.map(({
|
|
892
|
+
key,
|
|
893
|
+
value
|
|
894
|
+
}) => createKeyChange(scope, key, value, undefined, "clearNamespace", "memory")));
|
|
798
895
|
return;
|
|
799
896
|
}
|
|
800
897
|
const keyPrefix = (0, _internal.prefixKey)(namespace, "");
|
|
898
|
+
const previousValues = hasStorageChangeObservers(scope) ? storage.getByPrefix(keyPrefix, scope) : {};
|
|
801
899
|
if (scope === _Storage.StorageScope.Disk) {
|
|
802
900
|
flushDiskWrites();
|
|
803
901
|
}
|
|
@@ -811,6 +909,7 @@ const storage = exports.storage = {
|
|
|
811
909
|
}
|
|
812
910
|
}
|
|
813
911
|
WebStorage.removeByPrefix(keyPrefix, scope);
|
|
912
|
+
emitBatchChange(scope, "clearNamespace", "web", Object.keys(previousValues).map(key => createKeyChange(scope, key, previousValues[key], undefined, "clearNamespace", "web")));
|
|
814
913
|
});
|
|
815
914
|
},
|
|
816
915
|
clearBiometric: () => {
|
|
@@ -919,6 +1018,9 @@ const storage = exports.storage = {
|
|
|
919
1018
|
return result;
|
|
920
1019
|
});
|
|
921
1020
|
},
|
|
1021
|
+
export: scope => {
|
|
1022
|
+
return measureOperation("storage:export", scope, () => storage.getAll(scope));
|
|
1023
|
+
},
|
|
922
1024
|
size: scope => {
|
|
923
1025
|
return measureOperation("storage:size", scope, () => {
|
|
924
1026
|
(0, _internal.assertValidScope)(scope);
|
|
@@ -1062,11 +1164,13 @@ const storage = exports.storage = {
|
|
|
1062
1164
|
(0, _internal.assertValidScope)(scope);
|
|
1063
1165
|
if (keys.length === 0) return;
|
|
1064
1166
|
const values = keys.map(k => data[k]);
|
|
1167
|
+
const changes = keys.map((key, index) => createKeyChange(scope, key, getEventRawValue(scope, key), values[index], "import", scope === _Storage.StorageScope.Memory ? "memory" : "web"));
|
|
1065
1168
|
if (scope === _Storage.StorageScope.Memory) {
|
|
1066
1169
|
keys.forEach((key, index) => {
|
|
1067
1170
|
memoryStore.set(key, values[index]);
|
|
1068
1171
|
});
|
|
1069
1172
|
keys.forEach(key => notifyKeyListeners(memoryListeners, key));
|
|
1173
|
+
emitBatchChange(scope, "import", "memory", changes);
|
|
1070
1174
|
return;
|
|
1071
1175
|
}
|
|
1072
1176
|
if (scope === _Storage.StorageScope.Secure) {
|
|
@@ -1078,6 +1182,7 @@ const storage = exports.storage = {
|
|
|
1078
1182
|
}
|
|
1079
1183
|
WebStorage.setBatch(keys, values, scope);
|
|
1080
1184
|
keys.forEach((key, index) => cacheRawValue(scope, key, values[index]));
|
|
1185
|
+
emitBatchChange(scope, "import", "web", changes);
|
|
1081
1186
|
}, keys.length);
|
|
1082
1187
|
}
|
|
1083
1188
|
};
|
|
@@ -1219,56 +1324,68 @@ function createStorageItem(config) {
|
|
|
1219
1324
|
return raw;
|
|
1220
1325
|
};
|
|
1221
1326
|
const writeStoredRaw = rawValue => {
|
|
1327
|
+
const oldValue = config.scope === _Storage.StorageScope.Memory ? getEventRawValue(config.scope, storageKey) : undefined;
|
|
1222
1328
|
if (isBiometric) {
|
|
1223
1329
|
WebStorage.setSecureBiometricWithLevel(storageKey, rawValue, resolvedBiometricLevel);
|
|
1330
|
+
emitKeyChange(config.scope, storageKey, oldValue, rawValue, "set", "web");
|
|
1224
1331
|
return;
|
|
1225
1332
|
}
|
|
1226
1333
|
cacheRawValue(nonMemoryScope, storageKey, rawValue);
|
|
1227
1334
|
if (nonMemoryScope === _Storage.StorageScope.Disk) {
|
|
1228
1335
|
if (coalesceDiskWrites || diskWritesAsync) {
|
|
1229
1336
|
scheduleDiskWrite(storageKey, rawValue);
|
|
1337
|
+
emitKeyChange(config.scope, storageKey, oldValue, rawValue, "set", "web");
|
|
1230
1338
|
return;
|
|
1231
1339
|
}
|
|
1232
1340
|
clearPendingDiskWrite(storageKey);
|
|
1233
1341
|
}
|
|
1234
1342
|
if (coalesceSecureWrites) {
|
|
1235
1343
|
scheduleSecureWrite(storageKey, rawValue, secureAccessControl ?? secureDefaultAccessControl);
|
|
1344
|
+
emitKeyChange(config.scope, storageKey, oldValue, rawValue, "set", "web");
|
|
1236
1345
|
return;
|
|
1237
1346
|
}
|
|
1238
1347
|
if (nonMemoryScope === _Storage.StorageScope.Secure) {
|
|
1239
1348
|
clearPendingSecureWrite(storageKey);
|
|
1240
1349
|
}
|
|
1241
1350
|
WebStorage.set(storageKey, rawValue, config.scope);
|
|
1351
|
+
emitKeyChange(config.scope, storageKey, oldValue, rawValue, "set", "web");
|
|
1242
1352
|
};
|
|
1243
1353
|
const removeStoredRaw = () => {
|
|
1354
|
+
const oldValue = getEventRawValue(config.scope, storageKey);
|
|
1244
1355
|
if (isBiometric) {
|
|
1245
1356
|
WebStorage.deleteSecureBiometric(storageKey);
|
|
1357
|
+
emitKeyChange(config.scope, storageKey, oldValue, undefined, "remove", "web");
|
|
1246
1358
|
return;
|
|
1247
1359
|
}
|
|
1248
1360
|
cacheRawValue(nonMemoryScope, storageKey, undefined);
|
|
1249
1361
|
if (nonMemoryScope === _Storage.StorageScope.Disk) {
|
|
1250
1362
|
if (coalesceDiskWrites || diskWritesAsync) {
|
|
1251
1363
|
scheduleDiskWrite(storageKey, undefined);
|
|
1364
|
+
emitKeyChange(config.scope, storageKey, oldValue, undefined, "remove", "web");
|
|
1252
1365
|
return;
|
|
1253
1366
|
}
|
|
1254
1367
|
clearPendingDiskWrite(storageKey);
|
|
1255
1368
|
}
|
|
1256
1369
|
if (coalesceSecureWrites) {
|
|
1257
1370
|
scheduleSecureWrite(storageKey, undefined, secureAccessControl ?? secureDefaultAccessControl);
|
|
1371
|
+
emitKeyChange(config.scope, storageKey, oldValue, undefined, "remove", "web");
|
|
1258
1372
|
return;
|
|
1259
1373
|
}
|
|
1260
1374
|
if (nonMemoryScope === _Storage.StorageScope.Secure) {
|
|
1261
1375
|
clearPendingSecureWrite(storageKey);
|
|
1262
1376
|
}
|
|
1263
1377
|
WebStorage.remove(storageKey, config.scope);
|
|
1378
|
+
emitKeyChange(config.scope, storageKey, oldValue, undefined, "remove", "web");
|
|
1264
1379
|
};
|
|
1265
1380
|
const writeValueWithoutValidation = value => {
|
|
1266
1381
|
if (isMemory) {
|
|
1382
|
+
const oldValue = getEventRawValue(config.scope, storageKey);
|
|
1267
1383
|
if (memoryExpiration) {
|
|
1268
1384
|
memoryExpiration.set(storageKey, Date.now() + (expirationTtlMs ?? 0));
|
|
1269
1385
|
}
|
|
1270
1386
|
memoryStore.set(storageKey, value);
|
|
1271
1387
|
notifyKeyListeners(memoryListeners, storageKey);
|
|
1388
|
+
emitKeyChange(config.scope, storageKey, oldValue, typeof value === "string" ? value : undefined, "set", "memory");
|
|
1272
1389
|
return;
|
|
1273
1390
|
}
|
|
1274
1391
|
const serialized = serialize(value);
|
|
@@ -1400,11 +1517,13 @@ function createStorageItem(config) {
|
|
|
1400
1517
|
measureOperation("item:delete", config.scope, () => {
|
|
1401
1518
|
invalidateParsedCache();
|
|
1402
1519
|
if (isMemory) {
|
|
1520
|
+
const oldValue = getEventRawValue(config.scope, storageKey);
|
|
1403
1521
|
if (memoryExpiration) {
|
|
1404
1522
|
memoryExpiration.delete(storageKey);
|
|
1405
1523
|
}
|
|
1406
1524
|
memoryStore.delete(storageKey);
|
|
1407
1525
|
notifyKeyListeners(memoryListeners, storageKey);
|
|
1526
|
+
emitKeyChange(config.scope, storageKey, oldValue, undefined, "remove", "memory");
|
|
1408
1527
|
return;
|
|
1409
1528
|
}
|
|
1410
1529
|
removeStoredRaw();
|
|
@@ -1438,6 +1557,22 @@ function createStorageItem(config) {
|
|
|
1438
1557
|
}
|
|
1439
1558
|
};
|
|
1440
1559
|
};
|
|
1560
|
+
const subscribeSelector = (selector, listener, options = {}) => {
|
|
1561
|
+
const isEqual = options.isEqual ?? Object.is;
|
|
1562
|
+
let currentValue = selector(getInternal());
|
|
1563
|
+
if (options.fireImmediately === true) {
|
|
1564
|
+
listener(currentValue, currentValue);
|
|
1565
|
+
}
|
|
1566
|
+
return subscribe(() => {
|
|
1567
|
+
const nextValue = selector(getInternal());
|
|
1568
|
+
if (isEqual(currentValue, nextValue)) {
|
|
1569
|
+
return;
|
|
1570
|
+
}
|
|
1571
|
+
const previousValue = currentValue;
|
|
1572
|
+
currentValue = nextValue;
|
|
1573
|
+
listener(nextValue, previousValue);
|
|
1574
|
+
});
|
|
1575
|
+
};
|
|
1441
1576
|
const storageItem = {
|
|
1442
1577
|
get,
|
|
1443
1578
|
getWithVersion,
|
|
@@ -1446,6 +1581,7 @@ function createStorageItem(config) {
|
|
|
1446
1581
|
delete: deleteItem,
|
|
1447
1582
|
has: hasItem,
|
|
1448
1583
|
subscribe,
|
|
1584
|
+
subscribeSelector,
|
|
1449
1585
|
serialize,
|
|
1450
1586
|
deserialize,
|
|
1451
1587
|
_triggerListeners: () => {
|
|
@@ -1546,6 +1682,10 @@ function setBatch(items, scope) {
|
|
|
1546
1682
|
}) => item.set(value));
|
|
1547
1683
|
return;
|
|
1548
1684
|
}
|
|
1685
|
+
const changes = items.map(({
|
|
1686
|
+
item,
|
|
1687
|
+
value
|
|
1688
|
+
}) => createKeyChange(scope, item.key, getEventRawValue(scope, item.key), typeof value === "string" ? value : undefined, "setBatch", "memory"));
|
|
1549
1689
|
|
|
1550
1690
|
// Atomic write: update all values in memoryStore, invalidate caches, then batch-notify
|
|
1551
1691
|
items.forEach(({
|
|
@@ -1558,6 +1698,7 @@ function setBatch(items, scope) {
|
|
|
1558
1698
|
items.forEach(({
|
|
1559
1699
|
item
|
|
1560
1700
|
}) => notifyKeyListeners(memoryListeners, item.key));
|
|
1701
|
+
emitBatchChange(scope, "setBatch", "memory", changes);
|
|
1561
1702
|
return;
|
|
1562
1703
|
}
|
|
1563
1704
|
if (scope === _Storage.StorageScope.Secure) {
|
|
@@ -1580,6 +1721,10 @@ function setBatch(items, scope) {
|
|
|
1580
1721
|
return;
|
|
1581
1722
|
}
|
|
1582
1723
|
flushSecureWrites();
|
|
1724
|
+
const keys = secureEntries.map(({
|
|
1725
|
+
item
|
|
1726
|
+
}) => item.key);
|
|
1727
|
+
const oldValues = hasStorageChangeObservers(scope) ? WebStorage.getBatch(keys, scope) : [];
|
|
1583
1728
|
const groupedByAccessControl = new Map();
|
|
1584
1729
|
secureEntries.forEach(({
|
|
1585
1730
|
item,
|
|
@@ -1603,6 +1748,10 @@ function setBatch(items, scope) {
|
|
|
1603
1748
|
WebStorage.setBatch(group.keys, group.values, scope);
|
|
1604
1749
|
group.keys.forEach((key, index) => cacheRawValue(scope, key, group.values[index]));
|
|
1605
1750
|
});
|
|
1751
|
+
emitBatchChange(scope, "setBatch", "web", secureEntries.map(({
|
|
1752
|
+
item,
|
|
1753
|
+
value
|
|
1754
|
+
}, index) => createKeyChange(scope, item.key, oldValues[index], item.serialize(value), "setBatch", "web")));
|
|
1606
1755
|
return;
|
|
1607
1756
|
}
|
|
1608
1757
|
flushDiskWrites();
|
|
@@ -1618,15 +1767,19 @@ function setBatch(items, scope) {
|
|
|
1618
1767
|
}
|
|
1619
1768
|
const keys = items.map(entry => entry.item.key);
|
|
1620
1769
|
const values = items.map(entry => entry.item.serialize(entry.value));
|
|
1770
|
+
const oldValues = hasStorageChangeObservers(scope) ? WebStorage.getBatch(keys, scope) : [];
|
|
1621
1771
|
WebStorage.setBatch(keys, values, scope);
|
|
1622
1772
|
keys.forEach((key, index) => cacheRawValue(scope, key, values[index]));
|
|
1773
|
+
emitBatchChange(scope, "setBatch", "web", keys.map((key, index) => createKeyChange(scope, key, oldValues[index], values[index], "setBatch", "web")));
|
|
1623
1774
|
}, items.length);
|
|
1624
1775
|
}
|
|
1625
1776
|
function removeBatch(items, scope) {
|
|
1626
1777
|
measureOperation("batch:remove", scope, () => {
|
|
1627
1778
|
(0, _internal.assertBatchScope)(items, scope);
|
|
1628
1779
|
if (scope === _Storage.StorageScope.Memory) {
|
|
1780
|
+
const changes = items.map(item => createKeyChange(scope, item.key, getEventRawValue(scope, item.key), undefined, "removeBatch", "memory"));
|
|
1629
1781
|
items.forEach(item => item.delete());
|
|
1782
|
+
emitBatchChange(scope, "removeBatch", "memory", changes);
|
|
1630
1783
|
return;
|
|
1631
1784
|
}
|
|
1632
1785
|
const keys = items.map(item => item.key);
|
|
@@ -1636,8 +1789,10 @@ function removeBatch(items, scope) {
|
|
|
1636
1789
|
if (scope === _Storage.StorageScope.Secure) {
|
|
1637
1790
|
flushSecureWrites();
|
|
1638
1791
|
}
|
|
1792
|
+
const oldValues = hasStorageChangeObservers(scope) ? WebStorage.getBatch(keys, scope) : [];
|
|
1639
1793
|
WebStorage.removeBatch(keys, scope);
|
|
1640
1794
|
keys.forEach(key => cacheRawValue(scope, key, undefined));
|
|
1795
|
+
emitBatchChange(scope, "removeBatch", "web", keys.map((key, index) => createKeyChange(scope, key, oldValues[index], undefined, "removeBatch", "web")));
|
|
1641
1796
|
}, items.length);
|
|
1642
1797
|
}
|
|
1643
1798
|
function registerMigration(version, migration) {
|