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.
@@ -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
- for (const key of memoryStore.keys()) {
456
- if ((0, _internal.isNamespaced)(key, namespace)) {
457
- memoryStore.delete(key);
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
- notifyAllListeners(memoryListeners);
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.forEach((value, key) => {
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) {