react-native-nitro-storage 0.9.0 → 0.10.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.
Files changed (76) hide show
  1. package/CHANGELOG.md +89 -29
  2. package/README.md +81 -24
  3. package/android/src/main/cpp/AndroidStorageAdapterCpp.cpp +5 -2
  4. package/android/src/main/java/com/nitrostorage/AndroidStorageAdapter.kt +240 -34
  5. package/cpp/bindings/HybridStorage.cpp +106 -283
  6. package/cpp/bindings/HybridStorage.hpp +16 -9
  7. package/docs/api-reference.md +75 -19
  8. package/docs/benchmarks.md +6 -1
  9. package/docs/keychain-lifecycle-testing.md +36 -0
  10. package/docs/recipes.md +1 -2
  11. package/docs/secure-storage.md +45 -9
  12. package/ios/IOSStorageAdapterCpp.mm +391 -175
  13. package/lib/commonjs/capabilities.js +3 -1
  14. package/lib/commonjs/capabilities.js.map +1 -1
  15. package/lib/commonjs/core/durability.js +110 -43
  16. package/lib/commonjs/core/durability.js.map +1 -1
  17. package/lib/commonjs/index.js +15 -5
  18. package/lib/commonjs/index.js.map +1 -1
  19. package/lib/commonjs/index.web.js +219 -97
  20. package/lib/commonjs/index.web.js.map +1 -1
  21. package/lib/commonjs/internal.js +3 -11
  22. package/lib/commonjs/internal.js.map +1 -1
  23. package/lib/commonjs/shared.js +62 -2
  24. package/lib/commonjs/shared.js.map +1 -1
  25. package/lib/commonjs/storage-core.js +852 -141
  26. package/lib/commonjs/storage-core.js.map +1 -1
  27. package/lib/commonjs/storage-runtime.js +5 -1
  28. package/lib/commonjs/storage-runtime.js.map +1 -1
  29. package/lib/commonjs/testing.js +13 -0
  30. package/lib/commonjs/testing.js.map +1 -1
  31. package/lib/module/capabilities.js +2 -1
  32. package/lib/module/capabilities.js.map +1 -1
  33. package/lib/module/core/durability.js +110 -43
  34. package/lib/module/core/durability.js.map +1 -1
  35. package/lib/module/index.js +12 -8
  36. package/lib/module/index.js.map +1 -1
  37. package/lib/module/index.web.js +215 -99
  38. package/lib/module/index.web.js.map +1 -1
  39. package/lib/module/internal.js +2 -9
  40. package/lib/module/internal.js.map +1 -1
  41. package/lib/module/shared.js +60 -2
  42. package/lib/module/shared.js.map +1 -1
  43. package/lib/module/storage-core.js +853 -142
  44. package/lib/module/storage-core.js.map +1 -1
  45. package/lib/module/storage-runtime.js +4 -1
  46. package/lib/module/storage-runtime.js.map +1 -1
  47. package/lib/module/testing.js +8 -1
  48. package/lib/module/testing.js.map +1 -1
  49. package/lib/typescript/capabilities.d.ts +2 -1
  50. package/lib/typescript/capabilities.d.ts.map +1 -1
  51. package/lib/typescript/core/durability.d.ts +7 -2
  52. package/lib/typescript/core/durability.d.ts.map +1 -1
  53. package/lib/typescript/index.d.ts +2 -2
  54. package/lib/typescript/index.d.ts.map +1 -1
  55. package/lib/typescript/index.web.d.ts +3 -29
  56. package/lib/typescript/index.web.d.ts.map +1 -1
  57. package/lib/typescript/internal.d.ts +0 -2
  58. package/lib/typescript/internal.d.ts.map +1 -1
  59. package/lib/typescript/shared.d.ts +19 -0
  60. package/lib/typescript/shared.d.ts.map +1 -1
  61. package/lib/typescript/storage-core.d.ts +15 -6
  62. package/lib/typescript/storage-core.d.ts.map +1 -1
  63. package/lib/typescript/storage-runtime.d.ts +2 -1
  64. package/lib/typescript/storage-runtime.d.ts.map +1 -1
  65. package/lib/typescript/testing.d.ts +1 -1
  66. package/lib/typescript/testing.d.ts.map +1 -1
  67. package/package.json +1 -1
  68. package/src/capabilities.ts +3 -1
  69. package/src/core/durability.ts +139 -39
  70. package/src/index.ts +20 -10
  71. package/src/index.web.ts +328 -166
  72. package/src/internal.ts +0 -14
  73. package/src/shared.ts +85 -0
  74. package/src/storage-core.ts +1165 -202
  75. package/src/storage-runtime.ts +6 -0
  76. package/src/testing.ts +8 -1
@@ -19,6 +19,8 @@ function createStorageCore(buildAdapter) {
19
19
  const itemGroups = new Map();
20
20
  const registeredKeyCounts = new Map();
21
21
  const memoryStore = new Map();
22
+ const memoryExpirationDeadlines = new Map();
23
+ const memoryItemsByKey = new Map();
22
24
  const memoryListeners = new Map();
23
25
  const scopedListeners = {
24
26
  [_Storage.StorageScope.Disk]: new Map(),
@@ -53,15 +55,33 @@ function createStorageCore(buildAdapter) {
53
55
  function getScopeRawCache(scope) {
54
56
  return scopedRawCache[scope];
55
57
  }
56
- function cacheRawValue(scope, key, value) {
57
- getScopeRawCache(scope).set(key, value);
58
+ function getCachedRawValueEntry(scope, key, create = false) {
59
+ const scopeCache = getScopeRawCache(scope);
60
+ const existing = scopeCache.get(key);
61
+ if (existing || !create) {
62
+ return existing;
63
+ }
64
+ const entry = new Map();
65
+ scopeCache.set(key, entry);
66
+ return entry;
67
+ }
68
+ function cacheRawValue(scope, key, value, representation = "plain") {
69
+ getCachedRawValueEntry(scope, key, true)?.set(representation, value);
70
+ }
71
+ function readCachedRawValue(scope, key, representation = "plain") {
72
+ return getCachedRawValueEntry(scope, key)?.get(representation);
58
73
  }
59
- function readCachedRawValue(scope, key) {
60
- return getScopeRawCache(scope).get(key);
74
+ function invalidateRawCache(scope, key) {
75
+ getScopeRawCache(scope).delete(key);
61
76
  }
62
77
  function clearScopeRawCache(scope) {
63
78
  getScopeRawCache(scope).clear();
64
79
  }
80
+ function invalidateMemoryItemCaches(key) {
81
+ memoryItemsByKey.get(key)?.forEach(item => {
82
+ item._invalidateParsedCacheOnly();
83
+ });
84
+ }
65
85
  function addKeyListener(registry, key, listener) {
66
86
  let listeners = registry.get(key);
67
87
  if (!listeners) {
@@ -87,6 +107,13 @@ function createStorageCore(buildAdapter) {
87
107
  }
88
108
  return getRawValue(key, scope);
89
109
  }
110
+ function getEventRawValueForRepresentation(scope, key, representation) {
111
+ if (representation === "plain") {
112
+ return getEventRawValue(scope, key);
113
+ }
114
+ const raw = adapter.backend.getSecureBiometric(key);
115
+ return raw === undefined ? undefined : (0, _internal.unescapeCollidingRawValue)(raw);
116
+ }
90
117
  function shouldReadPreviousEventValues(scope) {
91
118
  if (storageEvents.hasListeners(scope)) {
92
119
  return true;
@@ -159,11 +186,14 @@ function createStorageCore(buildAdapter) {
159
186
  function flushSecureWrites() {
160
187
  durability.flushSecureWrites();
161
188
  }
189
+ function runSecurePromotion(key, promotion) {
190
+ return durability.runSecurePromotion(key, promotion);
191
+ }
162
192
  function scheduleDiskWrite(key, value) {
163
- durability.scheduleDiskWrite(key, value);
193
+ return durability.scheduleDiskWrite(key, value);
164
194
  }
165
195
  function scheduleSecureWrite(key, value, accessControl) {
166
- durability.scheduleSecureWrite(key, value, accessControl);
196
+ return durability.scheduleSecureWrite(key, value, accessControl);
167
197
  }
168
198
  function setDiskWritesAsyncMode(enabled) {
169
199
  durability.setDiskWritesAsync(enabled);
@@ -184,6 +214,12 @@ function createStorageCore(buildAdapter) {
184
214
  }
185
215
  return adapter.backend.get(key, scope);
186
216
  }
217
+ function getStoredRawValueForRepresentation(key, scope, representation) {
218
+ if (representation === "plain") {
219
+ return getStoredRawValue(key, scope);
220
+ }
221
+ return adapter.backend.getSecureBiometric(key);
222
+ }
187
223
  function getRawValue(key, scope) {
188
224
  (0, _internal.assertValidScope)(scope);
189
225
  if (scope === _Storage.StorageScope.Memory) {
@@ -222,6 +258,7 @@ function createStorageCore(buildAdapter) {
222
258
  clearPendingDiskWrite(key);
223
259
  }
224
260
  if (scope === _Storage.StorageScope.Secure) {
261
+ invalidateRawCache(scope, key);
225
262
  flushSecureWrites();
226
263
  clearPendingSecureWrite(key);
227
264
  if (adapter.applyAccessControlOnSecureRawWrite) {
@@ -252,6 +289,7 @@ function createStorageCore(buildAdapter) {
252
289
  clearPendingDiskWrite(key);
253
290
  }
254
291
  if (scope === _Storage.StorageScope.Secure) {
292
+ invalidateRawCache(scope, key);
255
293
  flushSecureWrites();
256
294
  clearPendingSecureWrite(key);
257
295
  }
@@ -271,6 +309,7 @@ function createStorageCore(buildAdapter) {
271
309
  getScopedListeners,
272
310
  cacheRawValue,
273
311
  readCachedRawValue,
312
+ invalidateRawCache,
274
313
  clearScopeRawCache,
275
314
  clearPendingDiskWrite,
276
315
  clearPendingSecureWrite,
@@ -324,6 +363,11 @@ function createStorageCore(buildAdapter) {
324
363
  return;
325
364
  }
326
365
  const previousValues = shouldReadPreviousEventValues(scope) ? adapter.backend.getBatch(removeKeys, scope).map(value => value === undefined ? undefined : (0, _internal.unescapeCollidingRawValue)(value)) : [];
366
+ if (scope === _Storage.StorageScope.Secure) {
367
+ removeKeys.forEach(key => {
368
+ invalidateRawCache(scope, key);
369
+ });
370
+ }
327
371
  adapter.backend.removeBatch(removeKeys, scope);
328
372
  removeKeys.forEach(key => {
329
373
  cacheRawValue(scope, key, undefined);
@@ -391,6 +435,7 @@ function createStorageCore(buildAdapter) {
391
435
  const previousValues = shouldReadPreviousEventValues(scope) ? storage.getAll(scope) : {};
392
436
  if (scope === _Storage.StorageScope.Memory) {
393
437
  memoryStore.clear();
438
+ memoryExpirationDeadlines.clear();
394
439
  (0, _shared.notifyAllListeners)(memoryListeners);
395
440
  emitBatchChange(scope, "clear", "memory", Object.keys(previousValues).map(key => (0, _shared.createKeyChange)(scope, key, previousValues[key], undefined, "clear", "memory")));
396
441
  return;
@@ -494,6 +539,7 @@ function createStorageCore(buildAdapter) {
494
539
  }
495
540
  affectedKeys.forEach(key => {
496
541
  memoryStore.delete(key);
542
+ memoryExpirationDeadlines.delete(key);
497
543
  });
498
544
  affectedKeys.forEach(key => {
499
545
  (0, _shared.notifyKeyListeners)(memoryListeners, key);
@@ -524,7 +570,21 @@ function createStorageCore(buildAdapter) {
524
570
  },
525
571
  clearBiometric: () => {
526
572
  measureOperation("storage:clearBiometric", _Storage.StorageScope.Secure, () => {
527
- adapter.backend.clearSecureBiometric();
573
+ flushSecureWrites();
574
+ const readEventValues = shouldReadPreviousEventValues(_Storage.StorageScope.Secure);
575
+ const shouldEmitChanges = storageEvents.hasListeners(_Storage.StorageScope.Secure) || eventObserver !== undefined;
576
+ const biometricKeys = shouldEmitChanges ? adapter.backend.getAllKeys(_Storage.StorageScope.Secure).filter(key => adapter.backend.hasSecureBiometric(key)) : [];
577
+ const previousValues = readEventValues ? biometricKeys.map(key => {
578
+ const raw = adapter.backend.getSecureBiometric(key);
579
+ return raw === undefined ? undefined : (0, _internal.unescapeCollidingRawValue)(raw);
580
+ }) : [];
581
+ clearScopeRawCache(_Storage.StorageScope.Secure);
582
+ try {
583
+ adapter.backend.clearSecureBiometric();
584
+ } finally {
585
+ clearScopeRawCache(_Storage.StorageScope.Secure);
586
+ }
587
+ emitBatchChange(_Storage.StorageScope.Secure, "clear", adapter.changeSource, biometricKeys.map((key, index) => (0, _shared.createKeyChange)(_Storage.StorageScope.Secure, key, readEventValues ? previousValues[index] : undefined, undefined, "clear", adapter.changeSource)));
528
588
  });
529
589
  },
530
590
  has: (key, scope) => {
@@ -749,6 +809,9 @@ function createStorageCore(buildAdapter) {
749
809
  if (scope === _Storage.StorageScope.Secure) {
750
810
  flushSecureWrites();
751
811
  adapter.backend.setSecureAccessControl(secureDefaultAccessControl);
812
+ keys.forEach(key => {
813
+ invalidateRawCache(scope, key);
814
+ });
752
815
  }
753
816
  if (scope === _Storage.StorageScope.Disk) {
754
817
  flushDiskWrites();
@@ -774,8 +837,9 @@ function createStorageCore(buildAdapter) {
774
837
  const expiration = config.expiration;
775
838
  const onExpired = config.onExpired;
776
839
  const expirationTtlMs = expiration?.ttlMs;
777
- const memoryExpiration = expiration && isMemory ? new Map() : null;
840
+ const memoryExpiration = expiration && isMemory ? memoryExpirationDeadlines : null;
778
841
  const readCache = !isMemory && config.readCache === true;
842
+ const rawCacheRepresentation = isBiometric ? "biometric" : "plain";
779
843
  const coalesceDiskWrites = config.scope === _Storage.StorageScope.Disk && config.coalesceDiskWrites === true;
780
844
  const coalesceSecureWrites = config.scope === _Storage.StorageScope.Secure && config.coalesceSecureWrites === true && !isBiometric;
781
845
  const defaultValue = config.defaultValue;
@@ -845,28 +909,40 @@ function createStorageCore(buildAdapter) {
845
909
  const memoryStored = memoryStore.get(storageKey);
846
910
  return typeof memoryStored === "string" ? (0, _internal.unescapeCollidingRawValue)(memoryStored) : memoryStored;
847
911
  }
912
+ if (nonMemoryScope === _Storage.StorageScope.Disk) {
913
+ if (durability.hasPendingDiskWrite(storageKey)) {
914
+ return durability.readPendingDiskWrite(storageKey);
915
+ }
916
+ }
917
+ if (nonMemoryScope === _Storage.StorageScope.Secure && !isBiometric) {
918
+ if (durability.hasPendingSecureWrite(storageKey)) {
919
+ return durability.readPendingSecureWrite(storageKey);
920
+ }
921
+ }
848
922
  migrateRenamesIfNeeded();
849
923
  if (nonMemoryScope === _Storage.StorageScope.Disk) {
850
- const pending = durability.readPendingDiskWrite(storageKey);
851
- if (pending !== undefined) {
852
- return pending;
924
+ if (durability.hasPendingDiskWrite(storageKey)) {
925
+ return durability.readPendingDiskWrite(storageKey);
853
926
  }
854
927
  }
855
928
  if (nonMemoryScope === _Storage.StorageScope.Secure && !isBiometric) {
856
- const pending = durability.readPendingSecureWrite(storageKey);
857
- if (pending !== undefined) {
858
- return pending;
929
+ if (durability.hasPendingSecureWrite(storageKey)) {
930
+ return durability.readPendingSecureWrite(storageKey);
859
931
  }
860
932
  }
861
933
  if (readCache) {
862
- const cache = getScopeRawCache(resolveNonMemoryScope());
863
- const cached = cache.get(storageKey);
864
- if (cached !== undefined || cache.has(storageKey)) {
865
- return cached;
934
+ const scope = resolveNonMemoryScope();
935
+ const cachedEntry = getCachedRawValueEntry(scope, storageKey);
936
+ if (cachedEntry?.has(rawCacheRepresentation)) {
937
+ return cachedEntry.get(rawCacheRepresentation);
866
938
  }
867
939
  }
868
940
  if (isBiometric) {
869
- return readBackendRaw(() => adapter.backend.getSecureBiometric(storageKey));
941
+ const raw = readBackendRaw(() => adapter.backend.getSecureBiometric(storageKey));
942
+ if (readCache) {
943
+ cacheRawValue(resolveNonMemoryScope(), storageKey, raw, rawCacheRepresentation);
944
+ }
945
+ return raw;
870
946
  }
871
947
  const raw = readBackendRaw(() => adapter.backend.get(storageKey, config.scope));
872
948
  cacheRawValue(resolveNonMemoryScope(), storageKey, raw);
@@ -878,7 +954,8 @@ function createStorageCore(buildAdapter) {
878
954
  } catch (error) {
879
955
  onReadError?.(error);
880
956
  if (fallbackToCacheOnReadError) {
881
- const cached = getScopeRawCache(resolveNonMemoryScope()).get(storageKey);
957
+ const scope = resolveNonMemoryScope();
958
+ const cached = readCachedRawValue(scope, storageKey, rawCacheRepresentation);
882
959
  if (cached !== undefined) {
883
960
  return cached;
884
961
  }
@@ -887,26 +964,375 @@ function createStorageCore(buildAdapter) {
887
964
  throw error;
888
965
  }
889
966
  };
890
- const writeStoredRaw = rawValue => {
967
+ function getRenameSourceKeys() {
968
+ return renameFromKeys.filter(key => key !== storageKey);
969
+ }
970
+ function getRenameSourceRaw(key) {
971
+ return isBiometric ? adapter.backend.getSecureBiometric(key) : adapter.backend.get(key, config.scope);
972
+ }
973
+ function invalidateRenameSourceCache(key) {
974
+ if (nonMemoryScope !== null) {
975
+ invalidateRawCache(nonMemoryScope, key);
976
+ }
977
+ }
978
+ function removeRenameSource(key) {
979
+ invalidateRenameSourceCache(key);
980
+ if (isBiometric) {
981
+ adapter.backend.deleteSecureBiometric(key);
982
+ invalidateRenameSourceCache(key);
983
+ return;
984
+ }
985
+ adapter.backend.remove(key, config.scope);
986
+ invalidateRenameSourceCache(key);
987
+ }
988
+ function restoreRenameSource(snapshot) {
989
+ invalidateRenameSourceCache(snapshot.key);
990
+ if (nonMemoryScope === _Storage.StorageScope.Secure) {
991
+ if (snapshot.biometricValue !== undefined) {
992
+ adapter.backend.setSecureBiometricWithLevel(snapshot.key, snapshot.biometricValue, resolvedBiometricLevel === _Storage.BiometricLevel.None ? _Storage.BiometricLevel.BiometryOnly : resolvedBiometricLevel);
993
+ } else {
994
+ adapter.backend.deleteSecureBiometric(snapshot.key);
995
+ }
996
+ if (snapshot.plainValue !== undefined) {
997
+ if (adapter.applyAccessControlOnSecureRawWrite) {
998
+ adapter.backend.setSecureAccessControl(secureAccessControl ?? secureDefaultAccessControl);
999
+ }
1000
+ adapter.backend.set(snapshot.key, snapshot.plainValue, config.scope);
1001
+ } else if (snapshot.biometricValue === undefined) {
1002
+ adapter.backend.remove(snapshot.key, config.scope);
1003
+ }
1004
+ invalidateRenameSourceCache(snapshot.key);
1005
+ return;
1006
+ }
1007
+ if (snapshot.plainValue !== undefined) {
1008
+ adapter.backend.set(snapshot.key, snapshot.plainValue, config.scope);
1009
+ } else {
1010
+ adapter.backend.remove(snapshot.key, config.scope);
1011
+ }
1012
+ invalidateRenameSourceCache(snapshot.key);
1013
+ }
1014
+ function readRenameSnapshots() {
1015
+ return getRenameSourceKeys().flatMap(key => {
1016
+ const plainValue = nonMemoryScope === _Storage.StorageScope.Secure ? adapter.backend.get(key, config.scope) : getRenameSourceRaw(key);
1017
+ const biometricValue = nonMemoryScope === _Storage.StorageScope.Secure ? adapter.backend.getSecureBiometric(key) : undefined;
1018
+ if (plainValue === undefined && biometricValue === undefined) {
1019
+ return [];
1020
+ }
1021
+ return [{
1022
+ key,
1023
+ ...(plainValue === undefined ? {} : {
1024
+ plainValue
1025
+ }),
1026
+ ...(biometricValue === undefined ? {} : {
1027
+ biometricValue
1028
+ })
1029
+ }];
1030
+ });
1031
+ }
1032
+ function selectedRenameValue(snapshot) {
1033
+ return isBiometric ? snapshot.biometricValue : snapshot.plainValue;
1034
+ }
1035
+ function hasPendingCurrentWrite() {
1036
+ if (nonMemoryScope === _Storage.StorageScope.Disk) {
1037
+ return durability.hasPendingDiskWrite(storageKey);
1038
+ }
1039
+ if (nonMemoryScope === _Storage.StorageScope.Secure && !isBiometric) {
1040
+ return durability.hasPendingSecureWrite(storageKey);
1041
+ }
1042
+ return false;
1043
+ }
1044
+ function hasPendingRenameSourceWrite() {
1045
+ if (isBiometric || nonMemoryScope === null) {
1046
+ return false;
1047
+ }
1048
+ return getRenameSourceKeys().some(key => nonMemoryScope === _Storage.StorageScope.Disk ? durability.hasPendingDiskWrite(key) : durability.hasPendingSecureWrite(key));
1049
+ }
1050
+ function flushPendingRenameSourceWrites() {
1051
+ if (!hasPendingRenameSourceWrite()) {
1052
+ return;
1053
+ }
1054
+ if (nonMemoryScope === _Storage.StorageScope.Disk) {
1055
+ flushDiskWrites();
1056
+ return;
1057
+ }
1058
+ flushSecureWrites();
1059
+ }
1060
+ function clearPendingCurrentWriteIf(write) {
1061
+ if (write === undefined) {
1062
+ return;
1063
+ }
1064
+ if (nonMemoryScope === _Storage.StorageScope.Disk && "generation" in write) {
1065
+ durability.clearPendingDiskWriteIf(write);
1066
+ return;
1067
+ }
1068
+ if (nonMemoryScope === _Storage.StorageScope.Secure && !isBiometric) {
1069
+ durability.clearPendingSecureWriteIf(write);
1070
+ }
1071
+ }
1072
+ function flushPendingCurrentWrite() {
1073
+ if (nonMemoryScope === _Storage.StorageScope.Disk) {
1074
+ flushDiskWrites();
1075
+ return;
1076
+ }
1077
+ if (nonMemoryScope === _Storage.StorageScope.Secure && !isBiometric) {
1078
+ flushSecureWrites();
1079
+ }
1080
+ }
1081
+ function removeCurrentBackendValue() {
1082
+ invalidateRawCache(resolveNonMemoryScope(), storageKey);
1083
+ if (isBiometric) {
1084
+ adapter.backend.deleteSecureBiometric(storageKey);
1085
+ return;
1086
+ }
1087
+ adapter.backend.remove(storageKey, config.scope);
1088
+ }
1089
+ function scheduleRenameSourceCleanup() {
1090
+ const sourceKeys = getRenameSourceKeys();
1091
+ sourceKeys.forEach(invalidateRenameSourceCache);
1092
+ if (isBiometric) {
1093
+ sourceKeys.forEach(removeRenameSource);
1094
+ return;
1095
+ }
1096
+ if (nonMemoryScope === _Storage.StorageScope.Disk && (coalesceDiskWrites || isDiskWritesAsync())) {
1097
+ sourceKeys.forEach(key => {
1098
+ scheduleDiskWrite(key, undefined);
1099
+ });
1100
+ return;
1101
+ }
1102
+ if (nonMemoryScope === _Storage.StorageScope.Secure && coalesceSecureWrites) {
1103
+ sourceKeys.forEach(key => {
1104
+ scheduleSecureWrite(key, undefined, secureAccessControl ?? secureDefaultAccessControl);
1105
+ });
1106
+ return;
1107
+ }
1108
+ sourceKeys.forEach(removeRenameSource);
1109
+ }
1110
+ let atomicMutationDepth = 0;
1111
+ function getItemStateKeys() {
1112
+ return Array.from(new Set([storageKey, ...getRenameSourceKeys()]));
1113
+ }
1114
+ function captureItemState() {
1115
+ const records = getItemStateKeys().map(key => {
1116
+ let pending;
1117
+ if (nonMemoryScope === _Storage.StorageScope.Disk) {
1118
+ if (durability.hasPendingDiskWrite(key)) {
1119
+ pending = {
1120
+ value: durability.readPendingDiskWrite(key)
1121
+ };
1122
+ }
1123
+ } else if (nonMemoryScope === _Storage.StorageScope.Secure && !isBiometric && durability.hasPendingSecureWrite(key)) {
1124
+ const accessControl = durability.readPendingSecureAccessControl(key);
1125
+ pending = {
1126
+ value: durability.readPendingSecureWrite(key),
1127
+ ...(accessControl === undefined ? {} : {
1128
+ accessControl
1129
+ })
1130
+ };
1131
+ }
1132
+ return {
1133
+ key,
1134
+ plainValue: nonMemoryScope === null ? undefined : adapter.backend.get(key, config.scope),
1135
+ biometricValue: nonMemoryScope === _Storage.StorageScope.Secure ? adapter.backend.getSecureBiometric(key) : undefined,
1136
+ ...(pending === undefined ? {} : {
1137
+ pending
1138
+ })
1139
+ };
1140
+ });
1141
+ return {
1142
+ records,
1143
+ renamesMigrated
1144
+ };
1145
+ }
1146
+ function clearPendingItemWrite(key) {
1147
+ if (nonMemoryScope === _Storage.StorageScope.Disk) {
1148
+ durability.clearPendingDiskWrite(key);
1149
+ } else if (nonMemoryScope === _Storage.StorageScope.Secure && !isBiometric) {
1150
+ durability.clearPendingSecureWrite(key);
1151
+ }
1152
+ }
1153
+ function restoreItemState(snapshot) {
1154
+ const rollbackErrors = [];
1155
+ const recordsByKey = new Map(snapshot.records.map(record => [record.key, record]));
1156
+ snapshot.records.forEach(({
1157
+ key
1158
+ }) => {
1159
+ clearPendingItemWrite(key);
1160
+ if (nonMemoryScope !== null) {
1161
+ invalidateRawCache(nonMemoryScope, key);
1162
+ }
1163
+ });
1164
+ const biometricRecords = snapshot.records.filter(({
1165
+ biometricValue
1166
+ }) => biometricValue !== undefined);
1167
+ if (nonMemoryScope === _Storage.StorageScope.Secure) {
1168
+ biometricRecords.forEach(({
1169
+ key,
1170
+ biometricValue
1171
+ }) => {
1172
+ try {
1173
+ adapter.backend.setSecureBiometricWithLevel(key, biometricValue, resolvedBiometricLevel === _Storage.BiometricLevel.None ? _Storage.BiometricLevel.BiometryOnly : resolvedBiometricLevel);
1174
+ cacheRawValue(_Storage.StorageScope.Secure, key, biometricValue, "biometric");
1175
+ } catch (error) {
1176
+ rollbackErrors.push({
1177
+ label: "rollback biometric",
1178
+ error
1179
+ });
1180
+ }
1181
+ });
1182
+ }
1183
+ const plainSets = new Map();
1184
+ const plainRemoves = [];
1185
+ snapshot.records.forEach(({
1186
+ key,
1187
+ plainValue,
1188
+ biometricValue
1189
+ }) => {
1190
+ if (plainValue === undefined) {
1191
+ if (biometricValue === undefined) {
1192
+ plainRemoves.push(key);
1193
+ }
1194
+ return;
1195
+ }
1196
+ const accessControl = secureAccessControl ?? secureDefaultAccessControl;
1197
+ const group = plainSets.get(accessControl) ?? {
1198
+ keys: [],
1199
+ values: []
1200
+ };
1201
+ group.keys.push(key);
1202
+ group.values.push(plainValue);
1203
+ plainSets.set(accessControl, group);
1204
+ });
1205
+ plainSets.forEach((group, accessControl) => {
1206
+ try {
1207
+ if (nonMemoryScope === _Storage.StorageScope.Secure) {
1208
+ adapter.backend.setSecureAccessControl(accessControl);
1209
+ }
1210
+ adapter.backend.setBatch(group.keys, group.values, config.scope);
1211
+ group.keys.forEach((key, index) => {
1212
+ cacheRawValue(resolveNonMemoryScope(), key, group.values[index], "plain");
1213
+ });
1214
+ } catch (error) {
1215
+ rollbackErrors.push({
1216
+ label: "rollback plain set",
1217
+ error
1218
+ });
1219
+ }
1220
+ });
1221
+ if (plainRemoves.length > 0) {
1222
+ try {
1223
+ adapter.backend.removeBatch(plainRemoves, config.scope);
1224
+ plainRemoves.forEach(key => {
1225
+ cacheRawValue(resolveNonMemoryScope(), key, undefined, "plain");
1226
+ });
1227
+ } catch (error) {
1228
+ rollbackErrors.push({
1229
+ label: "rollback plain remove",
1230
+ error
1231
+ });
1232
+ }
1233
+ }
1234
+ if (nonMemoryScope === _Storage.StorageScope.Secure) {
1235
+ snapshot.records.forEach(({
1236
+ key,
1237
+ biometricValue
1238
+ }) => {
1239
+ if (biometricValue !== undefined) {
1240
+ return;
1241
+ }
1242
+ try {
1243
+ adapter.backend.deleteSecureBiometric(key);
1244
+ cacheRawValue(_Storage.StorageScope.Secure, key, undefined, "biometric");
1245
+ } catch (error) {
1246
+ rollbackErrors.push({
1247
+ label: "rollback biometric remove",
1248
+ error
1249
+ });
1250
+ }
1251
+ });
1252
+ }
1253
+ snapshot.records.forEach(({
1254
+ key,
1255
+ pending
1256
+ }) => {
1257
+ if (pending === undefined || nonMemoryScope === null) {
1258
+ return;
1259
+ }
1260
+ if (nonMemoryScope === _Storage.StorageScope.Disk) {
1261
+ scheduleDiskWrite(key, pending.value);
1262
+ } else if (!isBiometric) {
1263
+ scheduleSecureWrite(key, pending.value, pending.accessControl ?? secureAccessControl ?? secureDefaultAccessControl);
1264
+ }
1265
+ });
1266
+ recordsByKey.forEach(({
1267
+ key
1268
+ }) => {
1269
+ if (nonMemoryScope !== null) {
1270
+ invalidateRawCache(nonMemoryScope, key);
1271
+ }
1272
+ });
1273
+ renamesMigrated = snapshot.renamesMigrated;
1274
+ invalidateParsedCache();
1275
+ return rollbackErrors;
1276
+ }
1277
+ function runAtomicItemMutation(mutation) {
1278
+ if (isMemory || renameFromKeys.length === 0 || atomicMutationDepth > 0) {
1279
+ mutation();
1280
+ return;
1281
+ }
1282
+ const snapshot = captureItemState();
1283
+ atomicMutationDepth += 1;
1284
+ try {
1285
+ mutation();
1286
+ } catch (primaryError) {
1287
+ const rollbackErrors = restoreItemState(snapshot);
1288
+ if (rollbackErrors.length > 0) {
1289
+ throw (0, _shared.createStorageCompositeError)("item mutation rollback", primaryError, rollbackErrors);
1290
+ }
1291
+ throw primaryError;
1292
+ } finally {
1293
+ atomicMutationDepth -= 1;
1294
+ }
1295
+ }
1296
+ const writeStoredRaw = (rawValue, options = {}) => {
1297
+ const cleanupRenameSources = options.cleanupRenameSources !== false;
891
1298
  const oldValue = undefined;
892
1299
  if (isBiometric) {
893
- adapter.backend.setSecureBiometricWithLevel(storageKey, rawValue, resolvedBiometricLevel);
1300
+ invalidateRawCache(_Storage.StorageScope.Secure, storageKey);
1301
+ runSecurePromotion(storageKey, () => {
1302
+ try {
1303
+ adapter.backend.setSecureBiometricWithLevel(storageKey, rawValue, resolvedBiometricLevel);
1304
+ } catch (error) {
1305
+ throw (0, _shared.normalizeStorageError)(error);
1306
+ }
1307
+ });
1308
+ if (cleanupRenameSources) {
1309
+ scheduleRenameSourceCleanup();
1310
+ }
894
1311
  emitKeyChange(config.scope, storageKey, oldValue, rawValue, "set", adapter.changeSource);
895
- return;
1312
+ return undefined;
1313
+ }
1314
+ if (nonMemoryScope === _Storage.StorageScope.Secure) {
1315
+ invalidateRawCache(_Storage.StorageScope.Secure, storageKey);
896
1316
  }
897
1317
  cacheRawValue(resolveNonMemoryScope(), storageKey, rawValue);
898
1318
  if (nonMemoryScope === _Storage.StorageScope.Disk) {
899
1319
  if (coalesceDiskWrites || isDiskWritesAsync()) {
900
- scheduleDiskWrite(storageKey, rawValue);
1320
+ const pendingWrite = scheduleDiskWrite(storageKey, rawValue);
1321
+ if (cleanupRenameSources) {
1322
+ scheduleRenameSourceCleanup();
1323
+ }
901
1324
  emitKeyChange(config.scope, storageKey, oldValue, rawValue, "set", adapter.changeSource);
902
- return;
1325
+ return pendingWrite;
903
1326
  }
904
1327
  clearPendingDiskWrite(storageKey);
905
1328
  }
906
1329
  if (coalesceSecureWrites) {
907
- scheduleSecureWrite(storageKey, rawValue, secureAccessControl ?? secureDefaultAccessControl);
1330
+ const pendingWrite = scheduleSecureWrite(storageKey, rawValue, secureAccessControl ?? secureDefaultAccessControl);
1331
+ if (cleanupRenameSources) {
1332
+ scheduleRenameSourceCleanup();
1333
+ }
908
1334
  emitKeyChange(config.scope, storageKey, oldValue, rawValue, "set", adapter.changeSource);
909
- return;
1335
+ return pendingWrite;
910
1336
  }
911
1337
  if (nonMemoryScope === _Storage.StorageScope.Secure) {
912
1338
  clearPendingSecureWrite(storageKey);
@@ -915,41 +1341,102 @@ function createStorageCore(buildAdapter) {
915
1341
  }
916
1342
  }
917
1343
  adapter.backend.set(storageKey, rawValue, config.scope);
1344
+ if (cleanupRenameSources) {
1345
+ scheduleRenameSourceCleanup();
1346
+ }
918
1347
  emitKeyChange(config.scope, storageKey, oldValue, rawValue, "set", adapter.changeSource);
1348
+ return undefined;
919
1349
  };
920
1350
  const migrateRenamesIfNeeded = () => {
921
1351
  if (renamesMigrated) {
922
1352
  return;
923
1353
  }
924
1354
  try {
1355
+ flushPendingRenameSourceWrites();
925
1356
  const hasCurrent = isBiometric ? adapter.backend.hasSecureBiometric(storageKey) : adapter.backend.has(storageKey, config.scope);
1357
+ const snapshots = readRenameSnapshots();
926
1358
  if (hasCurrent) {
927
- for (const legacyKey of renameFromKeys) {
928
- if (isBiometric) {
929
- if (adapter.backend.hasSecureBiometric(legacyKey)) {
930
- adapter.backend.deleteSecureBiometric(legacyKey);
1359
+ try {
1360
+ snapshots.forEach(({
1361
+ key
1362
+ }) => {
1363
+ removeRenameSource(key);
1364
+ });
1365
+ } catch (primaryError) {
1366
+ const rollbackErrors = [];
1367
+ snapshots.forEach(snapshot => {
1368
+ try {
1369
+ restoreRenameSource(snapshot);
1370
+ } catch (error) {
1371
+ rollbackErrors.push({
1372
+ label: "rollback rename source",
1373
+ error
1374
+ });
931
1375
  }
932
- } else if (adapter.backend.has(legacyKey, config.scope)) {
933
- adapter.backend.remove(legacyKey, config.scope);
1376
+ });
1377
+ if (rollbackErrors.length > 0) {
1378
+ throw (0, _shared.createStorageCompositeError)("rename cleanup", primaryError, rollbackErrors);
934
1379
  }
1380
+ throw primaryError;
935
1381
  }
936
1382
  renamesMigrated = true;
937
1383
  return;
938
1384
  }
939
- for (const legacyKey of renameFromKeys) {
940
- const legacyRaw = isBiometric ? adapter.backend.getSecureBiometric(legacyKey) : adapter.backend.get(legacyKey, config.scope);
941
- if (legacyRaw === undefined) {
942
- continue;
1385
+ const snapshot = snapshots.find(candidate => selectedRenameValue(candidate) !== undefined);
1386
+ if (snapshot === undefined) {
1387
+ renamesMigrated = true;
1388
+ return;
1389
+ }
1390
+ let writeAttempted = false;
1391
+ let pendingCurrentWrite;
1392
+ try {
1393
+ writeAttempted = true;
1394
+ pendingCurrentWrite = writeStoredRaw(selectedRenameValue(snapshot), {
1395
+ cleanupRenameSources: false
1396
+ });
1397
+ if (hasPendingCurrentWrite()) {
1398
+ flushPendingCurrentWrite();
943
1399
  }
944
- writeStoredRaw(legacyRaw);
945
- if (isBiometric) {
946
- adapter.backend.deleteSecureBiometric(legacyKey);
947
- } else {
948
- adapter.backend.remove(legacyKey, config.scope);
1400
+ snapshots.forEach(({
1401
+ key
1402
+ }) => {
1403
+ removeRenameSource(key);
1404
+ });
1405
+ renamesMigrated = true;
1406
+ } catch (primaryError) {
1407
+ const rollbackErrors = [];
1408
+ clearPendingCurrentWriteIf(pendingCurrentWrite);
1409
+ if (writeAttempted) {
1410
+ try {
1411
+ removeCurrentBackendValue();
1412
+ } catch (error) {
1413
+ rollbackErrors.push({
1414
+ label: "rollback current value",
1415
+ error
1416
+ });
1417
+ }
949
1418
  }
950
- break;
1419
+ snapshots.forEach(renameSnapshot => {
1420
+ try {
1421
+ restoreRenameSource(renameSnapshot);
1422
+ } catch (error) {
1423
+ rollbackErrors.push({
1424
+ label: "rollback rename source",
1425
+ error
1426
+ });
1427
+ }
1428
+ });
1429
+ invalidateRawCache(resolveNonMemoryScope(), storageKey);
1430
+ snapshots.forEach(({
1431
+ key
1432
+ }) => {
1433
+ invalidateRenameSourceCache(key);
1434
+ });
1435
+ if (rollbackErrors.length > 0) {
1436
+ throw (0, _shared.createStorageCompositeError)("rename migration", primaryError, rollbackErrors);
1437
+ }
1438
+ throw primaryError;
951
1439
  }
952
- renamesMigrated = true;
953
1440
  } catch (error) {
954
1441
  if ((0, _shared.isKeychainLockedError)(error)) {
955
1442
  onReadError?.(error);
@@ -959,16 +1446,22 @@ function createStorageCore(buildAdapter) {
959
1446
  }
960
1447
  };
961
1448
  const removeStoredRaw = (operation = "remove") => {
962
- const oldValue = getEventRawValue(config.scope, storageKey);
1449
+ const oldValue = isBiometric ? getEventRawValueForRepresentation(config.scope, storageKey, "biometric") : getEventRawValue(config.scope, storageKey);
963
1450
  if (isBiometric) {
1451
+ invalidateRawCache(_Storage.StorageScope.Secure, storageKey);
1452
+ scheduleRenameSourceCleanup();
964
1453
  adapter.backend.deleteSecureBiometric(storageKey);
965
1454
  emitKeyChange(config.scope, storageKey, oldValue, undefined, operation, adapter.changeSource);
966
1455
  return;
967
1456
  }
1457
+ if (nonMemoryScope === _Storage.StorageScope.Secure) {
1458
+ invalidateRawCache(_Storage.StorageScope.Secure, storageKey);
1459
+ }
968
1460
  cacheRawValue(resolveNonMemoryScope(), storageKey, undefined);
969
1461
  if (nonMemoryScope === _Storage.StorageScope.Disk) {
970
1462
  if (coalesceDiskWrites || isDiskWritesAsync()) {
971
1463
  scheduleDiskWrite(storageKey, undefined);
1464
+ scheduleRenameSourceCleanup();
972
1465
  emitKeyChange(config.scope, storageKey, oldValue, undefined, operation, adapter.changeSource);
973
1466
  return;
974
1467
  }
@@ -976,12 +1469,14 @@ function createStorageCore(buildAdapter) {
976
1469
  }
977
1470
  if (coalesceSecureWrites) {
978
1471
  scheduleSecureWrite(storageKey, undefined, secureAccessControl ?? secureDefaultAccessControl);
1472
+ scheduleRenameSourceCleanup();
979
1473
  emitKeyChange(config.scope, storageKey, oldValue, undefined, operation, adapter.changeSource);
980
1474
  return;
981
1475
  }
982
1476
  if (nonMemoryScope === _Storage.StorageScope.Secure) {
983
1477
  clearPendingSecureWrite(storageKey);
984
1478
  }
1479
+ scheduleRenameSourceCleanup();
985
1480
  adapter.backend.remove(storageKey, config.scope);
986
1481
  emitKeyChange(config.scope, storageKey, oldValue, undefined, operation, adapter.changeSource);
987
1482
  };
@@ -990,6 +1485,8 @@ function createStorageCore(buildAdapter) {
990
1485
  const oldValue = getEventRawValue(config.scope, storageKey);
991
1486
  if (memoryExpiration) {
992
1487
  memoryExpiration.set(storageKey, Date.now() + (expirationTtlMs ?? 0));
1488
+ } else {
1489
+ memoryExpirationDeadlines.delete(storageKey);
993
1490
  }
994
1491
  const storedValue = typeof value === "string" ? (0, _internal.escapeCollidingRawValue)(value) : value;
995
1492
  memoryStore.set(storageKey, storedValue);
@@ -1038,7 +1535,9 @@ function createStorageCore(buildAdapter) {
1038
1535
  if (lastExpiresAt > Date.now()) {
1039
1536
  return lastValue;
1040
1537
  }
1041
- removeStoredRaw("expire");
1538
+ runAtomicItemMutation(() => {
1539
+ removeStoredRaw("expire");
1540
+ });
1042
1541
  invalidateParsedCache();
1043
1542
  onExpired?.(storageKey);
1044
1543
  lastValue = ensureValidatedValue(defaultValue, false);
@@ -1076,7 +1575,9 @@ function createStorageCore(buildAdapter) {
1076
1575
  if ((0, _internal.isStoredEnvelope)(parsed)) {
1077
1576
  envelopeExpiresAt = parsed.expiresAt;
1078
1577
  if (parsed.expiresAt <= Date.now()) {
1079
- removeStoredRaw("expire");
1578
+ runAtomicItemMutation(() => {
1579
+ removeStoredRaw("expire");
1580
+ });
1080
1581
  invalidateParsedCache();
1081
1582
  onExpired?.(storageKey);
1082
1583
  lastValue = ensureValidatedValue(defaultValue, false);
@@ -1113,36 +1614,45 @@ function createStorageCore(buildAdapter) {
1113
1614
  }));
1114
1615
  const set = valueOrFn => {
1115
1616
  measureOperation("item:set", config.scope, () => {
1116
- const newValue = (0, _shared.isUpdater)(valueOrFn) ? valueOrFn(getInternal()) : valueOrFn;
1117
- if (validate && !validate(newValue)) {
1118
- throw new Error(`Validation failed for key "${storageKey}" in scope "${_Storage.StorageScope[config.scope]}".`);
1119
- }
1120
- invalidateParsedCache();
1121
- writeValueWithoutValidation(newValue);
1617
+ runAtomicItemMutation(() => {
1618
+ const newValue = (0, _shared.isUpdater)(valueOrFn) ? valueOrFn(getInternal()) : valueOrFn;
1619
+ if (validate && !validate(newValue)) {
1620
+ throw new Error(`Validation failed for key "${storageKey}" in scope "${_Storage.StorageScope[config.scope]}".`);
1621
+ }
1622
+ invalidateParsedCache();
1623
+ writeValueWithoutValidation(newValue);
1624
+ });
1122
1625
  });
1123
1626
  };
1124
1627
  const setIfVersion = (version, valueOrFn) => measureOperation("item:setIfVersion", config.scope, () => {
1125
- const currentVersion = getCurrentVersion();
1126
- if (currentVersion !== version) {
1127
- return false;
1128
- }
1129
- set(valueOrFn);
1130
- return true;
1628
+ let didSet = false;
1629
+ runAtomicItemMutation(() => {
1630
+ const currentVersion = getCurrentVersion();
1631
+ if (currentVersion !== version) {
1632
+ return;
1633
+ }
1634
+ set(valueOrFn);
1635
+ didSet = true;
1636
+ });
1637
+ return didSet;
1131
1638
  });
1132
1639
  const deleteItem = () => {
1133
1640
  measureOperation("item:delete", config.scope, () => {
1134
- invalidateParsedCache();
1135
- if (isMemory) {
1136
- const oldValue = getEventRawValue(config.scope, storageKey);
1137
- if (memoryExpiration) {
1138
- memoryExpiration.delete(storageKey);
1641
+ runAtomicItemMutation(() => {
1642
+ invalidateParsedCache();
1643
+ if (isMemory) {
1644
+ const oldValue = getEventRawValue(config.scope, storageKey);
1645
+ if (memoryExpiration) {
1646
+ memoryExpiration.delete(storageKey);
1647
+ }
1648
+ memoryExpirationDeadlines.delete(storageKey);
1649
+ memoryStore.delete(storageKey);
1650
+ (0, _shared.notifyKeyListeners)(memoryListeners, storageKey);
1651
+ emitKeyChange(config.scope, storageKey, oldValue, undefined, "remove", "memory");
1652
+ return;
1139
1653
  }
1140
- memoryStore.delete(storageKey);
1141
- (0, _shared.notifyKeyListeners)(memoryListeners, storageKey);
1142
- emitKeyChange(config.scope, storageKey, oldValue, undefined, "remove", "memory");
1143
- return;
1144
- }
1145
- removeStoredRaw();
1654
+ removeStoredRaw();
1655
+ });
1146
1656
  });
1147
1657
  };
1148
1658
  const merge = partial => {
@@ -1235,14 +1745,18 @@ function createStorageCore(buildAdapter) {
1235
1745
  invalidateParsedCache();
1236
1746
  },
1237
1747
  _deleteMemoryEntry: () => {
1238
- if (memoryExpiration) {
1239
- memoryExpiration.delete(storageKey);
1240
- }
1748
+ memoryExpirationDeadlines.delete(storageKey);
1241
1749
  memoryStore.delete(storageKey);
1242
1750
  invalidateParsedCache();
1243
1751
  },
1244
1752
  _hasValidation: validate !== undefined,
1245
1753
  _hasExpiration: expiration !== undefined,
1754
+ _hasRenameFrom: renameFromKeys.length > 0,
1755
+ _renameFromKeys: renameFromKeys,
1756
+ _getRenameMigrationState: () => renamesMigrated,
1757
+ _setRenameMigrationState: migrated => {
1758
+ renamesMigrated = migrated;
1759
+ },
1246
1760
  _readCacheEnabled: readCache,
1247
1761
  _isBiometric: isBiometric,
1248
1762
  _biometricLevel: resolvedBiometricLevel,
@@ -1264,6 +1778,14 @@ function createStorageCore(buildAdapter) {
1264
1778
  }
1265
1779
  groupSet.add(storageItem);
1266
1780
  }
1781
+ if (isMemory) {
1782
+ let items = memoryItemsByKey.get(storageKey);
1783
+ if (!items) {
1784
+ items = new Set();
1785
+ memoryItemsByKey.set(storageKey, items);
1786
+ }
1787
+ items.add(storageItem);
1788
+ }
1267
1789
  const registryKey = `${config.scope}:${storageKey}`;
1268
1790
  registeredKeyCounts.set(registryKey, (registeredKeyCounts.get(registryKey) ?? 0) + 1);
1269
1791
  return storageItem;
@@ -1283,24 +1805,21 @@ function createStorageCore(buildAdapter) {
1283
1805
  const keyIndexes = [];
1284
1806
  items.forEach((item, index) => {
1285
1807
  if (scope === _Storage.StorageScope.Disk) {
1286
- const pending = durability.readPendingDiskWrite(item.key);
1287
- if (pending !== undefined) {
1288
- rawValues[index] = pending;
1808
+ if (durability.hasPendingDiskWrite(item.key)) {
1809
+ rawValues[index] = durability.readPendingDiskWrite(item.key);
1289
1810
  return;
1290
1811
  }
1291
1812
  }
1292
1813
  if (scope === _Storage.StorageScope.Secure) {
1293
- const pending = durability.readPendingSecureWrite(item.key);
1294
- if (pending !== undefined) {
1295
- rawValues[index] = pending;
1814
+ if (durability.hasPendingSecureWrite(item.key)) {
1815
+ rawValues[index] = durability.readPendingSecureWrite(item.key);
1296
1816
  return;
1297
1817
  }
1298
1818
  }
1299
1819
  if (item._readCacheEnabled === true) {
1300
- const cache = getScopeRawCache(scope);
1301
- const cached = cache.get(item.key);
1302
- if (cached !== undefined || cache.has(item.key)) {
1303
- rawValues[index] = cached;
1820
+ const cachedEntry = getCachedRawValueEntry(scope, item.key);
1821
+ if (cachedEntry?.has("plain")) {
1822
+ rawValues[index] = cachedEntry.get("plain");
1304
1823
  return;
1305
1824
  }
1306
1825
  }
@@ -1395,6 +1914,10 @@ function createStorageCore(buildAdapter) {
1395
1914
  const keys = secureEntries.map(({
1396
1915
  item
1397
1916
  }) => item.key);
1917
+ keys.forEach(key => {
1918
+ invalidateRawCache(scope, key);
1919
+ });
1920
+ const serializedValues = [];
1398
1921
  const oldValues = shouldReadPreviousEventValues(scope) ? adapter.backend.getBatch(keys, scope).map(value => value === undefined ? undefined : (0, _internal.unescapeCollidingRawValue)(value)) : [];
1399
1922
  const groupedByAccessControl = new Map();
1400
1923
  secureEntries.forEach(({
@@ -1402,6 +1925,8 @@ function createStorageCore(buildAdapter) {
1402
1925
  value,
1403
1926
  internal
1404
1927
  }) => {
1928
+ const serialized = item.serialize(value);
1929
+ serializedValues.push(serialized);
1405
1930
  const accessControl = internal._secureAccessControl ?? secureDefaultAccessControl;
1406
1931
  const existingGroup = groupedByAccessControl.get(accessControl);
1407
1932
  const group = existingGroup ?? {
@@ -1409,7 +1934,7 @@ function createStorageCore(buildAdapter) {
1409
1934
  values: []
1410
1935
  };
1411
1936
  group.keys.push(item.key);
1412
- group.values.push(item.serialize(value));
1937
+ group.values.push(serialized);
1413
1938
  if (!existingGroup) {
1414
1939
  groupedByAccessControl.set(accessControl, group);
1415
1940
  }
@@ -1421,10 +1946,8 @@ function createStorageCore(buildAdapter) {
1421
1946
  cacheRawValue(scope, key, group.values[index]);
1422
1947
  });
1423
1948
  });
1424
- emitBatchChange(scope, "setBatch", adapter.changeSource, secureEntries.map(({
1425
- item,
1426
- value
1427
- }, index) => (0, _shared.createKeyChange)(scope, item.key, oldValues[index], item.serialize(value), "setBatch", adapter.changeSource)));
1949
+ const willEmitChanges = storageEvents.hasListeners(scope) || eventObserver !== undefined;
1950
+ emitBatchChange(scope, "setBatch", adapter.changeSource, willEmitChanges ? keys.map((key, index) => (0, _shared.createKeyChange)(scope, key, oldValues[index], serializedValues[index], "setBatch", adapter.changeSource)) : []);
1428
1951
  return;
1429
1952
  }
1430
1953
  flushDiskWrites();
@@ -1464,6 +1987,12 @@ function createStorageCore(buildAdapter) {
1464
1987
  emitBatchChange(scope, "removeBatch", "memory", changes);
1465
1988
  return;
1466
1989
  }
1990
+ if (items.some(item => asInternal(item)._hasRenameFrom)) {
1991
+ items.forEach(item => {
1992
+ asInternal(item).delete();
1993
+ });
1994
+ return;
1995
+ }
1467
1996
  const keys = items.map(item => item.key);
1468
1997
  if (scope === _Storage.StorageScope.Disk) {
1469
1998
  flushDiskWrites();
@@ -1471,7 +2000,25 @@ function createStorageCore(buildAdapter) {
1471
2000
  if (scope === _Storage.StorageScope.Secure) {
1472
2001
  flushSecureWrites();
1473
2002
  }
1474
- const oldValues = shouldReadPreviousEventValues(scope) ? adapter.backend.getBatch(keys, scope).map(value => value === undefined ? undefined : (0, _internal.unescapeCollidingRawValue)(value)) : [];
2003
+ const oldValues = shouldReadPreviousEventValues(scope) ? (() => {
2004
+ const plainItems = items.filter(item => asInternal(item)._isBiometric !== true);
2005
+ const plainValues = plainItems.length === 0 ? [] : adapter.backend.getBatch(plainItems.map(item => item.key), scope).map(value => value === undefined ? undefined : (0, _internal.unescapeCollidingRawValue)(value));
2006
+ let plainIndex = 0;
2007
+ return items.map(item => {
2008
+ const internal = asInternal(item);
2009
+ if (internal._isBiometric === true) {
2010
+ return getEventRawValueForRepresentation(scope, item.key, "biometric");
2011
+ }
2012
+ const value = plainValues[plainIndex];
2013
+ plainIndex += 1;
2014
+ return value;
2015
+ });
2016
+ })() : [];
2017
+ if (scope === _Storage.StorageScope.Secure) {
2018
+ keys.forEach(key => {
2019
+ invalidateRawCache(scope, key);
2020
+ });
2021
+ }
1475
2022
  adapter.backend.removeBatch(keys, scope);
1476
2023
  keys.forEach(key => {
1477
2024
  cacheRawValue(scope, key, undefined);
@@ -1519,33 +2066,74 @@ function createStorageCore(buildAdapter) {
1519
2066
  }
1520
2067
  const NOT_SET = Symbol();
1521
2068
  const rollback = new Map();
1522
- const rememberRollback = (key, item) => {
1523
- if (rollback.has(key)) {
1524
- return;
1525
- }
1526
- if (scope === _Storage.StorageScope.Memory) {
1527
- rollback.set(key, {
1528
- kind: "memory",
1529
- value: memoryStore.has(key) ? memoryStore.get(key) : NOT_SET
1530
- });
1531
- } else {
1532
- const internal = item ? item : undefined;
1533
- if (scope === _Storage.StorageScope.Secure && internal?._isBiometric === true) {
1534
- rollback.set(key, {
1535
- kind: "biometric",
1536
- value: adapter.backend.getSecureBiometric(key),
1537
- level: internal._biometricLevel
2069
+ const itemRenameStates = new Map();
2070
+ const rememberRollback = (key, item, includeOtherSecureRepresentation = false) => {
2071
+ const internal = item ? item : undefined;
2072
+ const representation = scope === _Storage.StorageScope.Secure && internal?._isBiometric === true ? "biometric" : "plain";
2073
+ const rememberRepresentation = representationToRemember => {
2074
+ const identity = JSON.stringify([scope, key, representationToRemember]);
2075
+ if (rollback.has(identity)) {
2076
+ return;
2077
+ }
2078
+ if (scope === _Storage.StorageScope.Memory) {
2079
+ const expiresAt = memoryExpirationDeadlines.get(key);
2080
+ rollback.set(identity, {
2081
+ key,
2082
+ representation: representationToRemember,
2083
+ record: {
2084
+ kind: "memory",
2085
+ value: memoryStore.has(key) ? memoryStore.get(key) : NOT_SET,
2086
+ ...(expiresAt === undefined ? {} : {
2087
+ expiresAt
2088
+ })
2089
+ }
2090
+ });
2091
+ return;
2092
+ }
2093
+ if (representationToRemember === "biometric") {
2094
+ rollback.set(identity, {
2095
+ key,
2096
+ representation: representationToRemember,
2097
+ record: {
2098
+ kind: "biometric",
2099
+ value: getStoredRawValueForRepresentation(key, scope, representationToRemember),
2100
+ level: internal?._biometricLevel !== undefined && internal._biometricLevel !== _Storage.BiometricLevel.None ? internal._biometricLevel : _Storage.BiometricLevel.BiometryOnly
2101
+ }
1538
2102
  });
1539
2103
  return;
1540
2104
  }
1541
- rollback.set(key, {
1542
- kind: "raw",
1543
- value: getStoredRawValue(key, scope),
1544
- ...(scope === _Storage.StorageScope.Secure && internal?._secureAccessControl !== undefined ? {
1545
- accessControl: internal._secureAccessControl
1546
- } : {})
2105
+ rollback.set(identity, {
2106
+ key,
2107
+ representation: representationToRemember,
2108
+ record: {
2109
+ kind: "raw",
2110
+ value: getStoredRawValueForRepresentation(key, scope, representationToRemember),
2111
+ ...(scope === _Storage.StorageScope.Secure && internal?._secureAccessControl !== undefined ? {
2112
+ accessControl: internal._secureAccessControl
2113
+ } : {})
2114
+ }
1547
2115
  });
2116
+ };
2117
+ if (representation === "biometric" || includeOtherSecureRepresentation) {
2118
+ rememberRepresentation("plain");
1548
2119
  }
2120
+ rememberRepresentation(representation);
2121
+ if (includeOtherSecureRepresentation) {
2122
+ rememberRepresentation("biometric");
2123
+ }
2124
+ };
2125
+ const rememberItemRollback = item => {
2126
+ const internal = item;
2127
+ const includeOtherSecureRepresentation = scope === _Storage.StorageScope.Secure && (internal._isBiometric === true || internal._hasRenameFrom === true);
2128
+ rememberRollback(item.key, item, includeOtherSecureRepresentation);
2129
+ if (internal._getRenameMigrationState) {
2130
+ if (!itemRenameStates.has(internal)) {
2131
+ itemRenameStates.set(internal, internal._getRenameMigrationState());
2132
+ }
2133
+ }
2134
+ (internal._renameFromKeys ?? EMPTY_KEYS).forEach(aliasKey => {
2135
+ rememberRollback(aliasKey, item, true);
2136
+ });
1549
2137
  };
1550
2138
  const tx = {
1551
2139
  scope,
@@ -1555,49 +2143,85 @@ function createStorageCore(buildAdapter) {
1555
2143
  setRawValue(key, value, scope);
1556
2144
  },
1557
2145
  removeRaw: key => {
1558
- rememberRollback(key);
2146
+ rememberRollback(key, undefined, scope === _Storage.StorageScope.Secure);
1559
2147
  removeRawValue(key, scope);
1560
2148
  },
1561
2149
  getItem: item => {
1562
2150
  (0, _internal.assertBatchScope)([item], scope);
2151
+ rememberItemRollback(item);
1563
2152
  return item.get();
1564
2153
  },
1565
2154
  setItem: (item, value) => {
1566
2155
  (0, _internal.assertBatchScope)([item], scope);
1567
- rememberRollback(item.key, item);
2156
+ rememberItemRollback(item);
1568
2157
  item.set(value);
1569
2158
  },
1570
2159
  removeItem: item => {
1571
2160
  (0, _internal.assertBatchScope)([item], scope);
1572
- rememberRollback(item.key, item);
2161
+ rememberItemRollback(item);
1573
2162
  item.delete();
1574
2163
  }
1575
2164
  };
1576
2165
  try {
1577
2166
  return transaction(tx);
1578
2167
  } catch (error) {
1579
- const rollbackEntries = Array.from(rollback.entries()).reverse();
2168
+ itemRenameStates.forEach((migrated, item) => {
2169
+ item._setRenameMigrationState(migrated);
2170
+ });
2171
+ const rollbackEntries = Array.from(rollback.values()).reverse();
1580
2172
  const rollbackSource = scope === _Storage.StorageScope.Memory ? "memory" : adapter.changeSource;
1581
- const preRollbackValues = rollbackEntries.length > 0 ? rollbackEntries.map(([key]) => getEventRawValue(scope, key)) : [];
2173
+ const rollbackErrors = [];
2174
+ const readRollbackEventValue = entry => {
2175
+ try {
2176
+ return getEventRawValueForRepresentation(scope, entry.key, entry.representation);
2177
+ } catch (readError) {
2178
+ rollbackErrors.push({
2179
+ label: `rollback read ${entry.representation}:${entry.key}`,
2180
+ error: readError
2181
+ });
2182
+ return undefined;
2183
+ }
2184
+ };
2185
+ const preRollbackValues = rollbackEntries.map(readRollbackEventValue);
1582
2186
  if (scope === _Storage.StorageScope.Memory) {
1583
- rollbackEntries.forEach(([key, record]) => {
1584
- if (record.value === NOT_SET) {
1585
- memoryStore.delete(key);
1586
- } else {
1587
- memoryStore.set(key, record.value);
2187
+ rollbackEntries.forEach(entry => {
2188
+ try {
2189
+ const record = entry.record;
2190
+ if (record.kind !== "memory") {
2191
+ return;
2192
+ }
2193
+ if (record.value === NOT_SET) {
2194
+ memoryStore.delete(entry.key);
2195
+ } else {
2196
+ memoryStore.set(entry.key, record.value);
2197
+ }
2198
+ if (record.expiresAt === undefined) {
2199
+ memoryExpirationDeadlines.delete(entry.key);
2200
+ } else {
2201
+ memoryExpirationDeadlines.set(entry.key, record.expiresAt);
2202
+ }
2203
+ invalidateMemoryItemCaches(entry.key);
2204
+ (0, _shared.notifyKeyListeners)(memoryListeners, entry.key);
2205
+ } catch (rollbackError) {
2206
+ rollbackErrors.push({
2207
+ label: `rollback ${entry.representation}:${entry.key}`,
2208
+ error: rollbackError
2209
+ });
1588
2210
  }
1589
- (0, _shared.notifyKeyListeners)(memoryListeners, key);
1590
2211
  });
1591
2212
  } else {
1592
2213
  const groupedKeysToSet = new Map();
1593
2214
  const keysToRemove = [];
1594
- rollbackEntries.forEach(([key, record]) => {
2215
+ const biometricEntries = [];
2216
+ const biometricValues = new Map();
2217
+ rollbackEntries.forEach(entry => {
2218
+ const {
2219
+ key,
2220
+ record
2221
+ } = entry;
1595
2222
  if (record.kind === "biometric") {
1596
- if (record.value === undefined) {
1597
- adapter.backend.deleteSecureBiometric(key);
1598
- } else {
1599
- adapter.backend.setSecureBiometricWithLevel(key, record.value, record.level);
1600
- }
2223
+ biometricEntries.push(entry);
2224
+ biometricValues.set(key, record.value);
1601
2225
  return;
1602
2226
  }
1603
2227
  if (record.kind !== "raw") {
@@ -1620,29 +2244,108 @@ function createStorageCore(buildAdapter) {
1620
2244
  }
1621
2245
  });
1622
2246
  if (scope === _Storage.StorageScope.Disk) {
1623
- flushDiskWrites();
2247
+ try {
2248
+ flushDiskWrites();
2249
+ } catch (flushError) {
2250
+ rollbackErrors.push({
2251
+ label: "rollback flush disk",
2252
+ error: flushError
2253
+ });
2254
+ }
1624
2255
  }
1625
2256
  if (scope === _Storage.StorageScope.Secure) {
1626
- flushSecureWrites();
2257
+ try {
2258
+ flushSecureWrites();
2259
+ } catch (flushError) {
2260
+ rollbackErrors.push({
2261
+ label: "rollback flush secure",
2262
+ error: flushError
2263
+ });
2264
+ }
1627
2265
  }
2266
+ if (scope === _Storage.StorageScope.Secure) {
2267
+ rollbackEntries.forEach(entry => {
2268
+ invalidateRawCache(scope, entry.key);
2269
+ });
2270
+ }
2271
+ biometricEntries.forEach(entry => {
2272
+ const {
2273
+ key,
2274
+ record
2275
+ } = entry;
2276
+ if (record.kind !== "biometric" || record.value === undefined) {
2277
+ return;
2278
+ }
2279
+ try {
2280
+ adapter.backend.setSecureBiometricWithLevel(key, record.value, record.level);
2281
+ cacheRawValue(_Storage.StorageScope.Secure, key, record.value, "biometric");
2282
+ } catch (rollbackError) {
2283
+ rollbackErrors.push({
2284
+ label: "rollback biometric",
2285
+ error: rollbackError
2286
+ });
2287
+ }
2288
+ });
1628
2289
  groupedKeysToSet.forEach((group, accessControl) => {
1629
- if (scope === _Storage.StorageScope.Secure) {
1630
- adapter.backend.setSecureAccessControl(accessControl);
2290
+ try {
2291
+ if (scope === _Storage.StorageScope.Secure) {
2292
+ adapter.backend.setSecureAccessControl(accessControl);
2293
+ }
2294
+ adapter.backend.setBatch(group.keys, group.values, scope);
2295
+ group.keys.forEach((key, index) => {
2296
+ cacheRawValue(scope, key, group.values[index], "plain");
2297
+ });
2298
+ } catch (rollbackError) {
2299
+ rollbackErrors.push({
2300
+ label: "rollback plain setBatch",
2301
+ error: rollbackError
2302
+ });
1631
2303
  }
1632
- adapter.backend.setBatch(group.keys, group.values, scope);
1633
- group.keys.forEach((key, index) => {
1634
- cacheRawValue(scope, key, group.values[index]);
1635
- });
1636
2304
  });
1637
2305
  if (keysToRemove.length > 0) {
1638
- adapter.backend.removeBatch(keysToRemove, scope);
1639
- keysToRemove.forEach(key => {
1640
- cacheRawValue(scope, key, undefined);
1641
- });
2306
+ const keysToRemoveWithoutBiometric = keysToRemove.filter(key => biometricValues.get(key) === undefined);
2307
+ try {
2308
+ if (keysToRemoveWithoutBiometric.length > 0) {
2309
+ adapter.backend.removeBatch(keysToRemoveWithoutBiometric, scope);
2310
+ }
2311
+ keysToRemove.forEach(key => {
2312
+ cacheRawValue(scope, key, undefined, "plain");
2313
+ });
2314
+ } catch (rollbackError) {
2315
+ rollbackErrors.push({
2316
+ label: "rollback plain removeBatch",
2317
+ error: rollbackError
2318
+ });
2319
+ }
1642
2320
  }
2321
+ biometricEntries.forEach(entry => {
2322
+ const {
2323
+ key,
2324
+ record
2325
+ } = entry;
2326
+ if (record.kind !== "biometric") {
2327
+ return;
2328
+ }
2329
+ if (record.value !== undefined) {
2330
+ return;
2331
+ }
2332
+ invalidateRawCache(_Storage.StorageScope.Secure, key);
2333
+ try {
2334
+ adapter.backend.deleteSecureBiometric(key);
2335
+ cacheRawValue(_Storage.StorageScope.Secure, key, record.value, "biometric");
2336
+ } catch (rollbackError) {
2337
+ rollbackErrors.push({
2338
+ label: "rollback biometric",
2339
+ error: rollbackError
2340
+ });
2341
+ }
2342
+ });
1643
2343
  }
1644
2344
  if (rollbackEntries.length > 0) {
1645
- emitBatchChange(scope, "rollback", rollbackSource, rollbackEntries.map(([key], index) => (0, _shared.createKeyChange)(scope, key, preRollbackValues[index], getEventRawValue(scope, key), "rollback", rollbackSource)));
2345
+ emitBatchChange(scope, "rollback", rollbackSource, rollbackEntries.map((entry, index) => (0, _shared.createKeyChange)(scope, entry.key, preRollbackValues[index], readRollbackEventValue(entry), "rollback", rollbackSource)));
2346
+ }
2347
+ if (rollbackErrors.length > 0) {
2348
+ throw (0, _shared.createStorageCompositeError)("transaction rollback", error, rollbackErrors);
1646
2349
  }
1647
2350
  throw error;
1648
2351
  }
@@ -1750,8 +2453,16 @@ function createStorageCore(buildAdapter) {
1750
2453
  add(id);
1751
2454
  return true;
1752
2455
  };
2456
+ const getTyped = () => {
2457
+ const typed = {};
2458
+ for (const id of Object.keys(item.get())) {
2459
+ typed[id] = true;
2460
+ }
2461
+ return typed;
2462
+ };
1753
2463
  return {
1754
2464
  get: item.get,
2465
+ getTyped,
1755
2466
  has,
1756
2467
  add,
1757
2468
  delete: deleteId,