react-native-nitro-storage 0.4.4 → 0.4.5

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 (39) hide show
  1. package/README.md +107 -7
  2. package/android/src/main/java/com/nitrostorage/AndroidStorageAdapter.kt +61 -10
  3. package/ios/IOSStorageAdapterCpp.mm +44 -14
  4. package/lib/commonjs/index.js +221 -5
  5. package/lib/commonjs/index.js.map +1 -1
  6. package/lib/commonjs/index.web.js +444 -202
  7. package/lib/commonjs/index.web.js.map +1 -1
  8. package/lib/commonjs/indexeddb-backend.js +129 -7
  9. package/lib/commonjs/indexeddb-backend.js.map +1 -1
  10. package/lib/commonjs/storage-runtime.js +41 -0
  11. package/lib/commonjs/storage-runtime.js.map +1 -0
  12. package/lib/commonjs/web-storage-backend.js +90 -0
  13. package/lib/commonjs/web-storage-backend.js.map +1 -0
  14. package/lib/module/index.js +213 -5
  15. package/lib/module/index.js.map +1 -1
  16. package/lib/module/index.web.js +436 -202
  17. package/lib/module/index.web.js.map +1 -1
  18. package/lib/module/indexeddb-backend.js +129 -7
  19. package/lib/module/indexeddb-backend.js.map +1 -1
  20. package/lib/module/storage-runtime.js +36 -0
  21. package/lib/module/storage-runtime.js.map +1 -0
  22. package/lib/module/web-storage-backend.js +86 -0
  23. package/lib/module/web-storage-backend.js.map +1 -0
  24. package/lib/typescript/index.d.ts +11 -7
  25. package/lib/typescript/index.d.ts.map +1 -1
  26. package/lib/typescript/index.web.d.ts +12 -8
  27. package/lib/typescript/index.web.d.ts.map +1 -1
  28. package/lib/typescript/indexeddb-backend.d.ts +6 -2
  29. package/lib/typescript/indexeddb-backend.d.ts.map +1 -1
  30. package/lib/typescript/storage-runtime.d.ts +16 -0
  31. package/lib/typescript/storage-runtime.d.ts.map +1 -0
  32. package/lib/typescript/web-storage-backend.d.ts +30 -0
  33. package/lib/typescript/web-storage-backend.d.ts.map +1 -0
  34. package/package.json +1 -1
  35. package/src/index.ts +264 -20
  36. package/src/index.web.ts +597 -245
  37. package/src/indexeddb-backend.ts +147 -6
  38. package/src/storage-runtime.ts +94 -0
  39. package/src/web-storage-backend.ts +129 -0
@@ -29,7 +29,15 @@ Object.defineProperty(exports, "createIndexedDBBackend", {
29
29
  });
30
30
  exports.createSecureAuthStorage = createSecureAuthStorage;
31
31
  exports.createStorageItem = createStorageItem;
32
+ exports.flushWebStorageBackends = flushWebStorageBackends;
32
33
  exports.getBatch = getBatch;
34
+ Object.defineProperty(exports, "getStorageErrorCode", {
35
+ enumerable: true,
36
+ get: function () {
37
+ return _storageRuntime.getStorageErrorCode;
38
+ }
39
+ });
40
+ exports.getWebDiskStorageBackend = getWebDiskStorageBackend;
33
41
  exports.getWebSecureStorageBackend = getWebSecureStorageBackend;
34
42
  exports.isKeychainLockedError = isKeychainLockedError;
35
43
  Object.defineProperty(exports, "migrateFromMMKV", {
@@ -43,6 +51,7 @@ exports.registerMigration = registerMigration;
43
51
  exports.removeBatch = removeBatch;
44
52
  exports.runTransaction = runTransaction;
45
53
  exports.setBatch = setBatch;
54
+ exports.setWebDiskStorageBackend = setWebDiskStorageBackend;
46
55
  exports.setWebSecureStorageBackend = setWebSecureStorageBackend;
47
56
  exports.storage = void 0;
48
57
  Object.defineProperty(exports, "useSetStorage", {
@@ -65,6 +74,8 @@ Object.defineProperty(exports, "useStorageSelector", {
65
74
  });
66
75
  var _Storage = require("./Storage.types");
67
76
  var _internal = require("./internal");
77
+ var _webStorageBackend = require("./web-storage-backend");
78
+ var _storageRuntime = require("./storage-runtime");
68
79
  var _migration = require("./migration");
69
80
  var _storageHooks = require("./storage-hooks");
70
81
  var _indexeddbBackend = require("./indexeddb-backend");
@@ -81,20 +92,23 @@ const registeredMigrations = new Map();
81
92
  const runMicrotask = typeof queueMicrotask === "function" ? queueMicrotask : task => {
82
93
  Promise.resolve().then(task);
83
94
  };
95
+ const now = typeof performance !== "undefined" && typeof performance.now === "function" ? () => performance.now() : () => Date.now();
84
96
  const memoryStore = new Map();
85
97
  const memoryListeners = new Map();
86
98
  const webScopeListeners = new Map([[_Storage.StorageScope.Disk, new Map()], [_Storage.StorageScope.Secure, new Map()]]);
87
99
  const scopedRawCache = new Map([[_Storage.StorageScope.Disk, new Map()], [_Storage.StorageScope.Secure, new Map()]]);
88
100
  const webScopeKeyIndex = new Map([[_Storage.StorageScope.Disk, new Set()], [_Storage.StorageScope.Secure, new Set()]]);
89
101
  const hydratedWebScopeKeyIndex = new Set();
102
+ const pendingDiskWrites = new Map();
103
+ let diskFlushScheduled = false;
104
+ let diskWritesAsync = false;
90
105
  const pendingSecureWrites = new Map();
91
106
  let secureFlushScheduled = false;
92
107
  let secureDefaultAccessControl = _Storage.AccessControl.WhenUnlocked;
93
108
  const SECURE_WEB_PREFIX = "__secure_";
94
109
  const BIOMETRIC_WEB_PREFIX = "__bio_";
95
110
  let hasWarnedAboutWebBiometricFallback = false;
96
- let hasWebStorageEventSubscription = false;
97
- let webStorageSubscriberCount = 0;
111
+ let hasWindowStorageEventSubscription = false;
98
112
  let metricsObserver;
99
113
  const metricsCounters = new Map();
100
114
  function recordMetric(operation, scope, durationMs, keysCount = 1) {
@@ -121,61 +135,51 @@ function measureOperation(operation, scope, fn, keysCount = 1) {
121
135
  if (!metricsObserver) {
122
136
  return fn();
123
137
  }
124
- const start = Date.now();
138
+ const start = now();
125
139
  try {
126
140
  return fn();
127
141
  } finally {
128
- recordMetric(operation, scope, Date.now() - start, keysCount);
142
+ recordMetric(operation, scope, now() - start, keysCount);
129
143
  }
130
144
  }
131
- function createLocalStorageWebSecureBackend() {
132
- return {
133
- getItem: key => globalThis.localStorage?.getItem(key) ?? null,
134
- setItem: (key, value) => globalThis.localStorage?.setItem(key, value),
135
- removeItem: key => globalThis.localStorage?.removeItem(key),
136
- clear: () => globalThis.localStorage?.clear(),
137
- getAllKeys: () => {
138
- const storage = globalThis.localStorage;
139
- if (!storage) return [];
140
- const keys = [];
141
- for (let index = 0; index < storage.length; index += 1) {
142
- const key = storage.key(index);
143
- if (key) {
144
- keys.push(key);
145
- }
146
- }
147
- return keys;
148
- }
149
- };
145
+ function createDefaultDiskBackend() {
146
+ return (0, _webStorageBackend.createLocalStorageWebBackend)({
147
+ name: "localStorage:disk",
148
+ includeKey: key => !key.startsWith(SECURE_WEB_PREFIX) && !key.startsWith(BIOMETRIC_WEB_PREFIX)
149
+ });
150
150
  }
151
- let webSecureStorageBackend = createLocalStorageWebSecureBackend();
152
- let cachedSecureBrowserStorage;
153
- let cachedSecureBackendRef;
154
- function getBrowserStorage(scope) {
155
- if (scope === _Storage.StorageScope.Disk) {
156
- return globalThis.localStorage;
151
+ function createDefaultSecureBackend() {
152
+ return (0, _webStorageBackend.createLocalStorageWebBackend)({
153
+ name: "localStorage:secure",
154
+ includeKey: key => key.startsWith(SECURE_WEB_PREFIX) || key.startsWith(BIOMETRIC_WEB_PREFIX)
155
+ });
156
+ }
157
+ let webDiskStorageBackend = createDefaultDiskBackend();
158
+ let webSecureStorageBackend = createDefaultSecureBackend();
159
+ const externalSyncUnsubscribers = new Map();
160
+ function getBackendName(scope, backend) {
161
+ const scopeName = scope === _Storage.StorageScope.Disk ? "disk" : "secure";
162
+ return backend?.name ?? `web:${scopeName}`;
163
+ }
164
+ function createWebStorageError(scope, operation, error, backend) {
165
+ const backendName = getBackendName(scope, backend);
166
+ const message = error instanceof Error ? error.message : String(error ?? "Unknown error");
167
+ return new Error(`NitroStorage(web): ${operation} failed for ${backendName}: ${message}`);
168
+ }
169
+ function withWebBackendOperation(scope, operation, fn) {
170
+ const backend = scope === _Storage.StorageScope.Disk ? webDiskStorageBackend : webSecureStorageBackend;
171
+ if (!backend) {
172
+ throw new Error(`NitroStorage(web): ${operation} failed because no ${scope === _Storage.StorageScope.Disk ? "disk" : "secure"} backend is configured.`);
157
173
  }
158
- if (scope === _Storage.StorageScope.Secure) {
159
- if (!webSecureStorageBackend) {
160
- return undefined;
161
- }
162
- if (cachedSecureBackendRef === webSecureStorageBackend && cachedSecureBrowserStorage) {
163
- return cachedSecureBrowserStorage;
164
- }
165
- cachedSecureBackendRef = webSecureStorageBackend;
166
- cachedSecureBrowserStorage = {
167
- setItem: (key, value) => webSecureStorageBackend.setItem(key, value),
168
- getItem: key => webSecureStorageBackend.getItem(key) ?? null,
169
- removeItem: key => webSecureStorageBackend.removeItem(key),
170
- clear: () => webSecureStorageBackend.clear(),
171
- key: index => webSecureStorageBackend.getAllKeys()[index] ?? null,
172
- get length() {
173
- return webSecureStorageBackend.getAllKeys().length;
174
- }
175
- };
176
- return cachedSecureBrowserStorage;
174
+ try {
175
+ ensureExternalSyncSubscriptions();
176
+ return fn(backend);
177
+ } catch (error) {
178
+ throw createWebStorageError(scope, operation, error, backend);
177
179
  }
178
- return undefined;
180
+ }
181
+ function getWebBackend(scope) {
182
+ return scope === _Storage.StorageScope.Disk ? webDiskStorageBackend : webSecureStorageBackend;
179
183
  }
180
184
  function toSecureStorageKey(key) {
181
185
  return `${SECURE_WEB_PREFIX}${key}`;
@@ -196,28 +200,21 @@ function hydrateWebScopeKeyIndex(scope) {
196
200
  if (hydratedWebScopeKeyIndex.has(scope)) {
197
201
  return;
198
202
  }
199
- const storage = getBrowserStorage(scope);
203
+ const backend = getWebBackend(scope);
200
204
  const keyIndex = getWebScopeKeyIndex(scope);
201
205
  keyIndex.clear();
202
- if (storage) {
203
- for (let index = 0; index < storage.length; index += 1) {
204
- const key = storage.key(index);
205
- if (!key) {
206
- continue;
207
- }
208
- if (scope === _Storage.StorageScope.Disk) {
209
- if (!key.startsWith(SECURE_WEB_PREFIX) && !key.startsWith(BIOMETRIC_WEB_PREFIX)) {
210
- keyIndex.add(key);
211
- }
212
- continue;
213
- }
214
- if (key.startsWith(SECURE_WEB_PREFIX)) {
215
- keyIndex.add(fromSecureStorageKey(key));
216
- continue;
217
- }
218
- if (key.startsWith(BIOMETRIC_WEB_PREFIX)) {
219
- keyIndex.add(fromBiometricStorageKey(key));
220
- }
206
+ const keys = backend?.getAllKeys() ?? [];
207
+ for (const key of keys) {
208
+ if (scope === _Storage.StorageScope.Disk) {
209
+ keyIndex.add(key);
210
+ continue;
211
+ }
212
+ if (key.startsWith(SECURE_WEB_PREFIX)) {
213
+ keyIndex.add(fromSecureStorageKey(key));
214
+ continue;
215
+ }
216
+ if (key.startsWith(BIOMETRIC_WEB_PREFIX)) {
217
+ keyIndex.add(fromBiometricStorageKey(key));
221
218
  }
222
219
  }
223
220
  hydratedWebScopeKeyIndex.add(scope);
@@ -226,65 +223,85 @@ function ensureWebScopeKeyIndex(scope) {
226
223
  hydrateWebScopeKeyIndex(scope);
227
224
  return getWebScopeKeyIndex(scope);
228
225
  }
229
- function handleWebStorageEvent(event) {
230
- const key = event.key;
226
+ function applyExternalChangeEvent(scope, key, newValue) {
231
227
  if (key === null) {
232
- clearScopeRawCache(_Storage.StorageScope.Disk);
233
- clearScopeRawCache(_Storage.StorageScope.Secure);
234
- ensureWebScopeKeyIndex(_Storage.StorageScope.Disk).clear();
235
- ensureWebScopeKeyIndex(_Storage.StorageScope.Secure).clear();
236
- notifyAllListeners(getScopedListeners(_Storage.StorageScope.Disk));
237
- notifyAllListeners(getScopedListeners(_Storage.StorageScope.Secure));
228
+ clearScopeRawCache(scope);
229
+ ensureWebScopeKeyIndex(scope).clear();
230
+ notifyAllListeners(getScopedListeners(scope));
238
231
  return;
239
232
  }
240
- if (key.startsWith(SECURE_WEB_PREFIX)) {
233
+ if (scope === _Storage.StorageScope.Secure && key.startsWith(SECURE_WEB_PREFIX)) {
241
234
  const plainKey = fromSecureStorageKey(key);
242
- if (event.newValue === null) {
235
+ if (newValue === null) {
243
236
  ensureWebScopeKeyIndex(_Storage.StorageScope.Secure).delete(plainKey);
244
237
  cacheRawValue(_Storage.StorageScope.Secure, plainKey, undefined);
245
238
  } else {
246
239
  ensureWebScopeKeyIndex(_Storage.StorageScope.Secure).add(plainKey);
247
- cacheRawValue(_Storage.StorageScope.Secure, plainKey, event.newValue);
240
+ cacheRawValue(_Storage.StorageScope.Secure, plainKey, newValue);
248
241
  }
249
242
  notifyKeyListeners(getScopedListeners(_Storage.StorageScope.Secure), plainKey);
250
243
  return;
251
244
  }
252
- if (key.startsWith(BIOMETRIC_WEB_PREFIX)) {
245
+ if (scope === _Storage.StorageScope.Secure && key.startsWith(BIOMETRIC_WEB_PREFIX)) {
253
246
  const plainKey = fromBiometricStorageKey(key);
254
- if (event.newValue === null) {
255
- if (getBrowserStorage(_Storage.StorageScope.Secure)?.getItem(toSecureStorageKey(plainKey)) === null) {
247
+ if (newValue === null) {
248
+ if (withWebBackendOperation(_Storage.StorageScope.Secure, "external-sync:getItem", backend => backend.getItem(toSecureStorageKey(plainKey))) === null) {
256
249
  ensureWebScopeKeyIndex(_Storage.StorageScope.Secure).delete(plainKey);
257
250
  }
258
251
  cacheRawValue(_Storage.StorageScope.Secure, plainKey, undefined);
259
252
  } else {
260
253
  ensureWebScopeKeyIndex(_Storage.StorageScope.Secure).add(plainKey);
261
- cacheRawValue(_Storage.StorageScope.Secure, plainKey, event.newValue);
254
+ cacheRawValue(_Storage.StorageScope.Secure, plainKey, newValue);
262
255
  }
263
256
  notifyKeyListeners(getScopedListeners(_Storage.StorageScope.Secure), plainKey);
264
257
  return;
265
258
  }
266
- if (event.newValue === null) {
267
- ensureWebScopeKeyIndex(_Storage.StorageScope.Disk).delete(key);
268
- cacheRawValue(_Storage.StorageScope.Disk, key, undefined);
259
+ if (newValue === null) {
260
+ ensureWebScopeKeyIndex(scope).delete(key);
261
+ cacheRawValue(scope, key, undefined);
269
262
  } else {
270
- ensureWebScopeKeyIndex(_Storage.StorageScope.Disk).add(key);
271
- cacheRawValue(_Storage.StorageScope.Disk, key, event.newValue);
263
+ ensureWebScopeKeyIndex(scope).add(key);
264
+ cacheRawValue(scope, key, newValue);
272
265
  }
273
- notifyKeyListeners(getScopedListeners(_Storage.StorageScope.Disk), key);
266
+ notifyKeyListeners(getScopedListeners(scope), key);
274
267
  }
275
- function ensureWebStorageEventSubscription() {
276
- webStorageSubscriberCount += 1;
277
- if (webStorageSubscriberCount === 1 && typeof window !== "undefined" && typeof window.addEventListener === "function") {
278
- window.addEventListener("storage", handleWebStorageEvent);
279
- hasWebStorageEventSubscription = true;
268
+ function handleWebStorageEvent(event) {
269
+ const key = event.key;
270
+ if (key === null) {
271
+ applyExternalChangeEvent(_Storage.StorageScope.Disk, null, null);
272
+ applyExternalChangeEvent(_Storage.StorageScope.Secure, null, null);
273
+ return;
274
+ }
275
+ if (key.startsWith(SECURE_WEB_PREFIX) || key.startsWith(BIOMETRIC_WEB_PREFIX)) {
276
+ applyExternalChangeEvent(_Storage.StorageScope.Secure, key, event.newValue);
277
+ return;
278
+ }
279
+ applyExternalChangeEvent(_Storage.StorageScope.Disk, key, event.newValue);
280
+ }
281
+ function subscribeToBackendChanges(scope) {
282
+ if (externalSyncUnsubscribers.has(scope)) {
283
+ return;
284
+ }
285
+ const backend = getWebBackend(scope);
286
+ if (!backend?.subscribe) {
287
+ return;
280
288
  }
289
+ const unsubscribe = backend.subscribe(event => {
290
+ applyExternalChangeEvent(scope, event.key, event.newValue);
291
+ });
292
+ externalSyncUnsubscribers.set(scope, unsubscribe);
281
293
  }
282
- function maybeCleanupWebStorageSubscription() {
283
- webStorageSubscriberCount = Math.max(0, webStorageSubscriberCount - 1);
284
- if (webStorageSubscriberCount === 0 && hasWebStorageEventSubscription && typeof window !== "undefined") {
285
- window.removeEventListener("storage", handleWebStorageEvent);
286
- hasWebStorageEventSubscription = false;
294
+ function resetBackendChangeSubscription(scope) {
295
+ externalSyncUnsubscribers.get(scope)?.();
296
+ externalSyncUnsubscribers.delete(scope);
297
+ }
298
+ function ensureExternalSyncSubscriptions() {
299
+ if (!hasWindowStorageEventSubscription && typeof window !== "undefined" && typeof window.addEventListener === "function") {
300
+ window.addEventListener("storage", handleWebStorageEvent);
301
+ hasWindowStorageEventSubscription = true;
287
302
  }
303
+ subscribeToBackendChanges(_Storage.StorageScope.Disk);
304
+ subscribeToBackendChanges(_Storage.StorageScope.Secure);
288
305
  }
289
306
  function getScopedListeners(scope) {
290
307
  return webScopeListeners.get(scope);
@@ -340,12 +357,49 @@ function addKeyListener(registry, key, listener) {
340
357
  function readPendingSecureWrite(key) {
341
358
  return pendingSecureWrites.get(key)?.value;
342
359
  }
360
+ function readPendingDiskWrite(key) {
361
+ return pendingDiskWrites.get(key)?.value;
362
+ }
363
+ function hasPendingDiskWrite(key) {
364
+ return pendingDiskWrites.has(key);
365
+ }
343
366
  function hasPendingSecureWrite(key) {
344
367
  return pendingSecureWrites.has(key);
345
368
  }
369
+ function clearPendingDiskWrite(key) {
370
+ pendingDiskWrites.delete(key);
371
+ }
346
372
  function clearPendingSecureWrite(key) {
347
373
  pendingSecureWrites.delete(key);
348
374
  }
375
+ function flushDiskWrites() {
376
+ diskFlushScheduled = false;
377
+ if (pendingDiskWrites.size === 0) {
378
+ return;
379
+ }
380
+ const writes = Array.from(pendingDiskWrites.values());
381
+ pendingDiskWrites.clear();
382
+ const keysToSet = [];
383
+ const valuesToSet = [];
384
+ const keysToRemove = [];
385
+ writes.forEach(({
386
+ key,
387
+ value
388
+ }) => {
389
+ if (value === undefined) {
390
+ keysToRemove.push(key);
391
+ return;
392
+ }
393
+ keysToSet.push(key);
394
+ valuesToSet.push(value);
395
+ });
396
+ if (keysToSet.length > 0) {
397
+ WebStorage.setBatch(keysToSet, valuesToSet, _Storage.StorageScope.Disk);
398
+ }
399
+ if (keysToRemove.length > 0) {
400
+ WebStorage.removeBatch(keysToRemove, _Storage.StorageScope.Disk);
401
+ }
402
+ }
349
403
  function flushSecureWrites() {
350
404
  secureFlushScheduled = false;
351
405
  if (pendingSecureWrites.size === 0) {
@@ -384,6 +438,17 @@ function flushSecureWrites() {
384
438
  WebStorage.removeBatch(keysToRemove, _Storage.StorageScope.Secure);
385
439
  }
386
440
  }
441
+ function scheduleDiskWrite(key, value) {
442
+ pendingDiskWrites.set(key, {
443
+ key,
444
+ value
445
+ });
446
+ if (diskFlushScheduled) {
447
+ return;
448
+ }
449
+ diskFlushScheduled = true;
450
+ runMicrotask(flushDiskWrites);
451
+ }
387
452
  function scheduleSecureWrite(key, value, accessControl) {
388
453
  const pendingWrite = {
389
454
  key,
@@ -404,117 +469,124 @@ const WebStorage = {
404
469
  equals: other => other === WebStorage,
405
470
  dispose: () => {},
406
471
  set: (key, value, scope) => {
407
- const storage = getBrowserStorage(scope);
408
- if (!storage) {
472
+ if (scope !== _Storage.StorageScope.Disk && scope !== _Storage.StorageScope.Secure) {
409
473
  return;
410
474
  }
411
475
  const storageKey = scope === _Storage.StorageScope.Secure ? toSecureStorageKey(key) : key;
412
- storage.setItem(storageKey, value);
413
- if (scope === _Storage.StorageScope.Disk || scope === _Storage.StorageScope.Secure) {
414
- ensureWebScopeKeyIndex(scope).add(key);
415
- notifyKeyListeners(getScopedListeners(scope), key);
416
- }
476
+ withWebBackendOperation(scope, "set", backend => {
477
+ backend.setItem(storageKey, value);
478
+ });
479
+ ensureWebScopeKeyIndex(scope).add(key);
480
+ notifyKeyListeners(getScopedListeners(scope), key);
417
481
  },
418
482
  get: (key, scope) => {
419
- const storage = getBrowserStorage(scope);
483
+ if (scope !== _Storage.StorageScope.Disk && scope !== _Storage.StorageScope.Secure) {
484
+ return undefined;
485
+ }
420
486
  const storageKey = scope === _Storage.StorageScope.Secure ? toSecureStorageKey(key) : key;
421
- return storage?.getItem(storageKey) ?? undefined;
487
+ const value = withWebBackendOperation(scope, "get", backend => backend.getItem(storageKey));
488
+ return value ?? undefined;
422
489
  },
423
490
  remove: (key, scope) => {
424
- const storage = getBrowserStorage(scope);
425
- if (!storage) {
491
+ if (scope !== _Storage.StorageScope.Disk && scope !== _Storage.StorageScope.Secure) {
426
492
  return;
427
493
  }
428
494
  if (scope === _Storage.StorageScope.Secure) {
429
- storage.removeItem(toSecureStorageKey(key));
430
- storage.removeItem(toBiometricStorageKey(key));
495
+ withWebBackendOperation(scope, "remove", backend => {
496
+ if (backend.removeMany) {
497
+ backend.removeMany([toSecureStorageKey(key), toBiometricStorageKey(key)]);
498
+ return;
499
+ }
500
+ backend.removeItem(toSecureStorageKey(key));
501
+ backend.removeItem(toBiometricStorageKey(key));
502
+ });
431
503
  } else {
432
- storage.removeItem(key);
433
- }
434
- if (scope === _Storage.StorageScope.Disk || scope === _Storage.StorageScope.Secure) {
435
- ensureWebScopeKeyIndex(scope).delete(key);
436
- notifyKeyListeners(getScopedListeners(scope), key);
504
+ withWebBackendOperation(scope, "remove", backend => {
505
+ backend.removeItem(key);
506
+ });
437
507
  }
508
+ ensureWebScopeKeyIndex(scope).delete(key);
509
+ notifyKeyListeners(getScopedListeners(scope), key);
438
510
  },
439
511
  clear: scope => {
440
- const storage = getBrowserStorage(scope);
441
- if (!storage) {
512
+ if (scope !== _Storage.StorageScope.Disk && scope !== _Storage.StorageScope.Secure) {
442
513
  return;
443
514
  }
444
- if (scope === _Storage.StorageScope.Secure) {
445
- const keysToRemove = [];
446
- for (let i = 0; i < storage.length; i++) {
447
- const key = storage.key(i);
448
- if (key?.startsWith(SECURE_WEB_PREFIX) || key?.startsWith(BIOMETRIC_WEB_PREFIX)) {
449
- keysToRemove.push(key);
450
- }
451
- }
452
- keysToRemove.forEach(key => storage.removeItem(key));
453
- } else if (scope === _Storage.StorageScope.Disk) {
454
- const keysToRemove = [];
455
- for (let i = 0; i < storage.length; i++) {
456
- const key = storage.key(i);
457
- if (key && !key.startsWith(SECURE_WEB_PREFIX) && !key.startsWith(BIOMETRIC_WEB_PREFIX)) {
458
- keysToRemove.push(key);
459
- }
460
- }
461
- keysToRemove.forEach(key => storage.removeItem(key));
462
- } else {
463
- storage.clear();
464
- }
465
- if (scope === _Storage.StorageScope.Disk || scope === _Storage.StorageScope.Secure) {
466
- ensureWebScopeKeyIndex(scope).clear();
467
- notifyAllListeners(getScopedListeners(scope));
468
- }
515
+ withWebBackendOperation(scope, "clear", backend => {
516
+ backend.clear();
517
+ });
518
+ ensureWebScopeKeyIndex(scope).clear();
519
+ notifyAllListeners(getScopedListeners(scope));
469
520
  },
470
521
  setBatch: (keys, values, scope) => {
471
- const storage = getBrowserStorage(scope);
472
- if (!storage) {
522
+ if (scope !== _Storage.StorageScope.Disk && scope !== _Storage.StorageScope.Secure) {
473
523
  return;
474
524
  }
525
+ const entries = [];
475
526
  keys.forEach((key, index) => {
476
527
  const value = values[index];
477
528
  if (value === undefined) {
478
529
  return;
479
530
  }
480
- const storageKey = scope === _Storage.StorageScope.Secure ? toSecureStorageKey(key) : key;
481
- storage.setItem(storageKey, value);
531
+ entries.push([scope === _Storage.StorageScope.Secure ? toSecureStorageKey(key) : key, value]);
482
532
  });
483
- if (scope === _Storage.StorageScope.Disk || scope === _Storage.StorageScope.Secure) {
484
- const keyIndex = ensureWebScopeKeyIndex(scope);
485
- keys.forEach(key => keyIndex.add(key));
486
- const listeners = getScopedListeners(scope);
487
- keys.forEach(key => notifyKeyListeners(listeners, key));
488
- }
533
+ withWebBackendOperation(scope, "setBatch", backend => {
534
+ if (backend.setMany) {
535
+ backend.setMany(entries);
536
+ return;
537
+ }
538
+ entries.forEach(([storageKey, value]) => {
539
+ backend.setItem(storageKey, value);
540
+ });
541
+ });
542
+ const keyIndex = ensureWebScopeKeyIndex(scope);
543
+ keys.forEach(key => keyIndex.add(key));
544
+ const listeners = getScopedListeners(scope);
545
+ keys.forEach(key => notifyKeyListeners(listeners, key));
489
546
  },
490
547
  getBatch: (keys, scope) => {
491
- const storage = getBrowserStorage(scope);
492
- return keys.map(key => {
493
- const storageKey = scope === _Storage.StorageScope.Secure ? toSecureStorageKey(key) : key;
494
- return storage?.getItem(storageKey) ?? undefined;
548
+ if (scope !== _Storage.StorageScope.Disk && scope !== _Storage.StorageScope.Secure) {
549
+ return keys.map(() => undefined);
550
+ }
551
+ const storageKeys = keys.map(key => scope === _Storage.StorageScope.Secure ? toSecureStorageKey(key) : key);
552
+ const values = withWebBackendOperation(scope, "getBatch", backend => {
553
+ if (backend.getMany) {
554
+ return backend.getMany(storageKeys);
555
+ }
556
+ return storageKeys.map(storageKey => backend.getItem(storageKey));
495
557
  });
558
+ return values.map(value => value ?? undefined);
496
559
  },
497
560
  removeBatch: (keys, scope) => {
498
- const storage = getBrowserStorage(scope);
499
- if (!storage) {
561
+ if (scope !== _Storage.StorageScope.Disk && scope !== _Storage.StorageScope.Secure) {
500
562
  return;
501
563
  }
502
564
  if (scope === _Storage.StorageScope.Secure) {
503
- keys.forEach(key => {
504
- storage.removeItem(toSecureStorageKey(key));
505
- storage.removeItem(toBiometricStorageKey(key));
565
+ const storageKeys = keys.flatMap(key => [toSecureStorageKey(key), toBiometricStorageKey(key)]);
566
+ withWebBackendOperation(scope, "removeBatch", backend => {
567
+ if (backend.removeMany) {
568
+ backend.removeMany(storageKeys);
569
+ return;
570
+ }
571
+ storageKeys.forEach(storageKey => {
572
+ backend.removeItem(storageKey);
573
+ });
506
574
  });
507
575
  } else {
508
- keys.forEach(key => {
509
- storage.removeItem(key);
576
+ withWebBackendOperation(scope, "removeBatch", backend => {
577
+ if (backend.removeMany) {
578
+ backend.removeMany(keys);
579
+ return;
580
+ }
581
+ keys.forEach(key => {
582
+ backend.removeItem(key);
583
+ });
510
584
  });
511
585
  }
512
- if (scope === _Storage.StorageScope.Disk || scope === _Storage.StorageScope.Secure) {
513
- const keyIndex = ensureWebScopeKeyIndex(scope);
514
- keys.forEach(key => keyIndex.delete(key));
515
- const listeners = getScopedListeners(scope);
516
- keys.forEach(key => notifyKeyListeners(listeners, key));
517
- }
586
+ const keyIndex = ensureWebScopeKeyIndex(scope);
587
+ keys.forEach(key => keyIndex.delete(key));
588
+ const listeners = getScopedListeners(scope);
589
+ keys.forEach(key => notifyKeyListeners(listeners, key));
518
590
  },
519
591
  removeByPrefix: (prefix, scope) => {
520
592
  if (scope !== _Storage.StorageScope.Disk && scope !== _Storage.StorageScope.Secure) {
@@ -531,11 +603,13 @@ const WebStorage = {
531
603
  return () => {};
532
604
  },
533
605
  has: (key, scope) => {
534
- const storage = getBrowserStorage(scope);
535
606
  if (scope === _Storage.StorageScope.Secure) {
536
- return storage?.getItem(toSecureStorageKey(key)) !== null || storage?.getItem(toBiometricStorageKey(key)) !== null;
607
+ return withWebBackendOperation(scope, "has", backend => backend.getItem(toSecureStorageKey(key))) !== null || withWebBackendOperation(scope, "has", backend => backend.getItem(toBiometricStorageKey(key))) !== null;
608
+ }
609
+ if (scope !== _Storage.StorageScope.Disk) {
610
+ return false;
537
611
  }
538
- return storage?.getItem(key) !== null;
612
+ return withWebBackendOperation(scope, "has", backend => backend.getItem(key)) !== null;
539
613
  },
540
614
  getAllKeys: scope => {
541
615
  if (scope !== _Storage.StorageScope.Disk && scope !== _Storage.StorageScope.Secure) {
@@ -566,40 +640,43 @@ const WebStorage = {
566
640
  hasWarnedAboutWebBiometricFallback = true;
567
641
  console.warn("[NitroStorage] Biometric storage is not supported on web. Using localStorage.");
568
642
  }
569
- getBrowserStorage(_Storage.StorageScope.Secure)?.setItem(toBiometricStorageKey(key), value);
643
+ withWebBackendOperation(_Storage.StorageScope.Secure, "setSecureBiometric", backend => backend.setItem(toBiometricStorageKey(key), value));
570
644
  ensureWebScopeKeyIndex(_Storage.StorageScope.Secure).add(key);
571
645
  notifyKeyListeners(getScopedListeners(_Storage.StorageScope.Secure), key);
572
646
  },
573
647
  getSecureBiometric: key => {
574
- return getBrowserStorage(_Storage.StorageScope.Secure)?.getItem(toBiometricStorageKey(key)) ?? undefined;
648
+ const value = withWebBackendOperation(_Storage.StorageScope.Secure, "getSecureBiometric", backend => backend.getItem(toBiometricStorageKey(key)));
649
+ return value ?? undefined;
575
650
  },
576
651
  deleteSecureBiometric: key => {
577
- const storage = getBrowserStorage(_Storage.StorageScope.Secure);
578
- storage?.removeItem(toBiometricStorageKey(key));
579
- if (storage?.getItem(toSecureStorageKey(key)) === null) {
652
+ withWebBackendOperation(_Storage.StorageScope.Secure, "deleteSecureBiometric", backend => backend.removeItem(toBiometricStorageKey(key)));
653
+ if (withWebBackendOperation(_Storage.StorageScope.Secure, "deleteSecureBiometric:getItem", backend => backend.getItem(toSecureStorageKey(key))) === null) {
580
654
  ensureWebScopeKeyIndex(_Storage.StorageScope.Secure).delete(key);
581
655
  }
582
656
  notifyKeyListeners(getScopedListeners(_Storage.StorageScope.Secure), key);
583
657
  },
584
658
  hasSecureBiometric: key => {
585
- return getBrowserStorage(_Storage.StorageScope.Secure)?.getItem(toBiometricStorageKey(key)) !== null;
659
+ return withWebBackendOperation(_Storage.StorageScope.Secure, "hasSecureBiometric", backend => backend.getItem(toBiometricStorageKey(key))) !== null;
586
660
  },
587
661
  clearSecureBiometric: () => {
588
- const storage = getBrowserStorage(_Storage.StorageScope.Secure);
589
- if (!storage) return;
590
- const keysToNotify = [];
591
- const toRemove = [];
592
- for (let i = 0; i < storage.length; i++) {
593
- const k = storage.key(i);
594
- if (k?.startsWith(BIOMETRIC_WEB_PREFIX)) {
595
- toRemove.push(k);
596
- keysToNotify.push(fromBiometricStorageKey(k));
597
- }
662
+ const storageKeys = withWebBackendOperation(_Storage.StorageScope.Secure, "clearSecureBiometric:getAllKeys", backend => backend.getAllKeys());
663
+ const keysToNotify = storageKeys.filter(key => key.startsWith(BIOMETRIC_WEB_PREFIX)).map(key => fromBiometricStorageKey(key));
664
+ if (keysToNotify.length === 0) {
665
+ return;
598
666
  }
599
- toRemove.forEach(k => storage.removeItem(k));
667
+ withWebBackendOperation(_Storage.StorageScope.Secure, "clearSecureBiometric", backend => {
668
+ const biometricKeys = keysToNotify.map(key => toBiometricStorageKey(key));
669
+ if (backend.removeMany) {
670
+ backend.removeMany(biometricKeys);
671
+ return;
672
+ }
673
+ biometricKeys.forEach(storageKey => {
674
+ backend.removeItem(storageKey);
675
+ });
676
+ });
600
677
  const keyIndex = ensureWebScopeKeyIndex(_Storage.StorageScope.Secure);
601
678
  keysToNotify.forEach(key => {
602
- if (storage.getItem(toSecureStorageKey(key)) === null) {
679
+ if (withWebBackendOperation(_Storage.StorageScope.Secure, "clearSecureBiometric:getItem", backend => backend.getItem(toSecureStorageKey(key))) === null) {
603
680
  keyIndex.delete(key);
604
681
  }
605
682
  });
@@ -613,6 +690,9 @@ function getRawValue(key, scope) {
613
690
  const value = memoryStore.get(key);
614
691
  return typeof value === "string" ? value : undefined;
615
692
  }
693
+ if (scope === _Storage.StorageScope.Disk && hasPendingDiskWrite(key)) {
694
+ return readPendingDiskWrite(key);
695
+ }
616
696
  if (scope === _Storage.StorageScope.Secure && hasPendingSecureWrite(key)) {
617
697
  return readPendingSecureWrite(key);
618
698
  }
@@ -625,6 +705,15 @@ function setRawValue(key, value, scope) {
625
705
  notifyKeyListeners(memoryListeners, key);
626
706
  return;
627
707
  }
708
+ if (scope === _Storage.StorageScope.Disk) {
709
+ cacheRawValue(scope, key, value);
710
+ if (diskWritesAsync) {
711
+ scheduleDiskWrite(key, value);
712
+ return;
713
+ }
714
+ flushDiskWrites();
715
+ clearPendingDiskWrite(key);
716
+ }
628
717
  if (scope === _Storage.StorageScope.Secure) {
629
718
  flushSecureWrites();
630
719
  clearPendingSecureWrite(key);
@@ -639,6 +728,15 @@ function removeRawValue(key, scope) {
639
728
  notifyKeyListeners(memoryListeners, key);
640
729
  return;
641
730
  }
731
+ if (scope === _Storage.StorageScope.Disk) {
732
+ cacheRawValue(scope, key, undefined);
733
+ if (diskWritesAsync) {
734
+ scheduleDiskWrite(key, undefined);
735
+ return;
736
+ }
737
+ flushDiskWrites();
738
+ clearPendingDiskWrite(key);
739
+ }
642
740
  if (scope === _Storage.StorageScope.Secure) {
643
741
  flushSecureWrites();
644
742
  clearPendingSecureWrite(key);
@@ -665,6 +763,10 @@ const storage = exports.storage = {
665
763
  notifyAllListeners(memoryListeners);
666
764
  return;
667
765
  }
766
+ if (scope === _Storage.StorageScope.Disk) {
767
+ flushDiskWrites();
768
+ pendingDiskWrites.clear();
769
+ }
668
770
  if (scope === _Storage.StorageScope.Secure) {
669
771
  flushSecureWrites();
670
772
  pendingSecureWrites.clear();
@@ -693,6 +795,9 @@ const storage = exports.storage = {
693
795
  return;
694
796
  }
695
797
  const keyPrefix = (0, _internal.prefixKey)(namespace, "");
798
+ if (scope === _Storage.StorageScope.Disk) {
799
+ flushDiskWrites();
800
+ }
696
801
  if (scope === _Storage.StorageScope.Secure) {
697
802
  flushSecureWrites();
698
803
  }
@@ -714,6 +819,12 @@ const storage = exports.storage = {
714
819
  return measureOperation("storage:has", scope, () => {
715
820
  (0, _internal.assertValidScope)(scope);
716
821
  if (scope === _Storage.StorageScope.Memory) return memoryStore.has(key);
822
+ if (scope === _Storage.StorageScope.Disk) {
823
+ flushDiskWrites();
824
+ }
825
+ if (scope === _Storage.StorageScope.Secure) {
826
+ flushSecureWrites();
827
+ }
717
828
  return WebStorage.has(key, scope);
718
829
  });
719
830
  },
@@ -721,6 +832,12 @@ const storage = exports.storage = {
721
832
  return measureOperation("storage:getAllKeys", scope, () => {
722
833
  (0, _internal.assertValidScope)(scope);
723
834
  if (scope === _Storage.StorageScope.Memory) return Array.from(memoryStore.keys());
835
+ if (scope === _Storage.StorageScope.Disk) {
836
+ flushDiskWrites();
837
+ }
838
+ if (scope === _Storage.StorageScope.Secure) {
839
+ flushSecureWrites();
840
+ }
724
841
  return WebStorage.getAllKeys(scope);
725
842
  });
726
843
  },
@@ -730,6 +847,12 @@ const storage = exports.storage = {
730
847
  if (scope === _Storage.StorageScope.Memory) {
731
848
  return Array.from(memoryStore.keys()).filter(key => key.startsWith(prefix));
732
849
  }
850
+ if (scope === _Storage.StorageScope.Disk) {
851
+ flushDiskWrites();
852
+ }
853
+ if (scope === _Storage.StorageScope.Secure) {
854
+ flushSecureWrites();
855
+ }
733
856
  return WebStorage.getKeysByPrefix(prefix, scope);
734
857
  });
735
858
  },
@@ -749,6 +872,12 @@ const storage = exports.storage = {
749
872
  });
750
873
  return result;
751
874
  }
875
+ if (scope === _Storage.StorageScope.Disk) {
876
+ flushDiskWrites();
877
+ }
878
+ if (scope === _Storage.StorageScope.Secure) {
879
+ flushSecureWrites();
880
+ }
752
881
  const values = WebStorage.getBatch(keys, scope);
753
882
  keys.forEach((key, index) => {
754
883
  const value = values[index];
@@ -769,6 +898,12 @@ const storage = exports.storage = {
769
898
  });
770
899
  return result;
771
900
  }
901
+ if (scope === _Storage.StorageScope.Disk) {
902
+ flushDiskWrites();
903
+ }
904
+ if (scope === _Storage.StorageScope.Secure) {
905
+ flushSecureWrites();
906
+ }
772
907
  const keys = WebStorage.getAllKeys(scope);
773
908
  if (keys.length === 0) return {};
774
909
  const values = WebStorage.getBatch(keys, scope);
@@ -785,6 +920,12 @@ const storage = exports.storage = {
785
920
  return measureOperation("storage:size", scope, () => {
786
921
  (0, _internal.assertValidScope)(scope);
787
922
  if (scope === _Storage.StorageScope.Memory) return memoryStore.size;
923
+ if (scope === _Storage.StorageScope.Disk) {
924
+ flushDiskWrites();
925
+ }
926
+ if (scope === _Storage.StorageScope.Secure) {
927
+ flushSecureWrites();
928
+ }
788
929
  return WebStorage.size(scope);
789
930
  });
790
931
  },
@@ -795,6 +936,19 @@ const storage = exports.storage = {
795
936
  setSecureWritesAsync: _enabled => {
796
937
  recordMetric("storage:setSecureWritesAsync", _Storage.StorageScope.Secure, 0);
797
938
  },
939
+ setDiskWritesAsync: enabled => {
940
+ measureOperation("storage:setDiskWritesAsync", _Storage.StorageScope.Disk, () => {
941
+ diskWritesAsync = enabled;
942
+ if (!enabled) {
943
+ flushDiskWrites();
944
+ }
945
+ });
946
+ },
947
+ flushDiskWrites: () => {
948
+ measureOperation("storage:flushDiskWrites", _Storage.StorageScope.Disk, () => {
949
+ flushDiskWrites();
950
+ });
951
+ },
798
952
  flushSecureWrites: () => {
799
953
  measureOperation("storage:flushSecureWrites", _Storage.StorageScope.Secure, () => {
800
954
  flushSecureWrites();
@@ -821,6 +975,18 @@ const storage = exports.storage = {
821
975
  resetMetrics: () => {
822
976
  metricsCounters.clear();
823
977
  },
978
+ getCapabilities: () => ({
979
+ platform: "web",
980
+ backend: {
981
+ disk: getBackendName(_Storage.StorageScope.Disk, webDiskStorageBackend),
982
+ secure: getBackendName(_Storage.StorageScope.Secure, webSecureStorageBackend)
983
+ },
984
+ writeBuffering: {
985
+ disk: true,
986
+ secure: true
987
+ },
988
+ errorClassification: true
989
+ }),
824
990
  getString: (key, scope) => {
825
991
  return measureOperation("storage:getString", scope, () => {
826
992
  return getRawValue(key, scope);
@@ -853,21 +1019,50 @@ const storage = exports.storage = {
853
1019
  flushSecureWrites();
854
1020
  WebStorage.setSecureAccessControl(secureDefaultAccessControl);
855
1021
  }
1022
+ if (scope === _Storage.StorageScope.Disk) {
1023
+ flushDiskWrites();
1024
+ }
856
1025
  WebStorage.setBatch(keys, values, scope);
857
1026
  keys.forEach((key, index) => cacheRawValue(scope, key, values[index]));
858
1027
  }, keys.length);
859
1028
  }
860
1029
  };
861
1030
  function setWebSecureStorageBackend(backend) {
862
- webSecureStorageBackend = backend ?? createLocalStorageWebSecureBackend();
863
- cachedSecureBrowserStorage = undefined;
864
- cachedSecureBackendRef = undefined;
1031
+ pendingSecureWrites.clear();
1032
+ webSecureStorageBackend = backend ?? createDefaultSecureBackend();
1033
+ resetBackendChangeSubscription(_Storage.StorageScope.Secure);
865
1034
  hydratedWebScopeKeyIndex.delete(_Storage.StorageScope.Secure);
866
1035
  clearScopeRawCache(_Storage.StorageScope.Secure);
1036
+ ensureExternalSyncSubscriptions();
867
1037
  }
868
1038
  function getWebSecureStorageBackend() {
869
1039
  return webSecureStorageBackend;
870
1040
  }
1041
+ function setWebDiskStorageBackend(backend) {
1042
+ pendingDiskWrites.clear();
1043
+ webDiskStorageBackend = backend ?? createDefaultDiskBackend();
1044
+ resetBackendChangeSubscription(_Storage.StorageScope.Disk);
1045
+ hydratedWebScopeKeyIndex.delete(_Storage.StorageScope.Disk);
1046
+ clearScopeRawCache(_Storage.StorageScope.Disk);
1047
+ ensureExternalSyncSubscriptions();
1048
+ }
1049
+ function getWebDiskStorageBackend() {
1050
+ return webDiskStorageBackend;
1051
+ }
1052
+ async function flushWebStorageBackends() {
1053
+ flushDiskWrites();
1054
+ flushSecureWrites();
1055
+ const flushes = [];
1056
+ const diskFlush = webDiskStorageBackend?.flush;
1057
+ const secureFlush = webSecureStorageBackend?.flush;
1058
+ if (diskFlush) {
1059
+ flushes.push(diskFlush());
1060
+ }
1061
+ if (secureFlush) {
1062
+ flushes.push(secureFlush());
1063
+ }
1064
+ await Promise.all(flushes);
1065
+ }
871
1066
  function canUseRawBatchPath(item) {
872
1067
  return item._hasExpiration === false && item._hasValidation === false && item._isBiometric !== true && item._secureAccessControl === undefined;
873
1068
  }
@@ -895,6 +1090,7 @@ function createStorageItem(config) {
895
1090
  const expirationTtlMs = expiration?.ttlMs;
896
1091
  const memoryExpiration = expiration && isMemory ? new Map() : null;
897
1092
  const readCache = !isMemory && config.readCache === true;
1093
+ const coalesceDiskWrites = config.scope === _Storage.StorageScope.Disk && config.coalesceDiskWrites === true;
898
1094
  const coalesceSecureWrites = config.scope === _Storage.StorageScope.Secure && config.coalesceSecureWrites === true && !isBiometric;
899
1095
  const defaultValue = config.defaultValue;
900
1096
  const nonMemoryScope = config.scope === _Storage.StorageScope.Disk ? _Storage.StorageScope.Disk : config.scope === _Storage.StorageScope.Secure ? _Storage.StorageScope.Secure : null;
@@ -925,7 +1121,7 @@ function createStorageItem(config) {
925
1121
  unsubscribe = addKeyListener(memoryListeners, storageKey, listener);
926
1122
  return;
927
1123
  }
928
- ensureWebStorageEventSubscription();
1124
+ ensureExternalSyncSubscriptions();
929
1125
  unsubscribe = addKeyListener(getScopedListeners(nonMemoryScope), storageKey, listener);
930
1126
  };
931
1127
  const readStoredRaw = () => {
@@ -942,6 +1138,12 @@ function createStorageItem(config) {
942
1138
  }
943
1139
  return memoryStore.get(storageKey);
944
1140
  }
1141
+ if (nonMemoryScope === _Storage.StorageScope.Disk) {
1142
+ const pending = pendingDiskWrites.get(storageKey);
1143
+ if (pending !== undefined) {
1144
+ return pending.value;
1145
+ }
1146
+ }
945
1147
  if (nonMemoryScope === _Storage.StorageScope.Secure && !isBiometric) {
946
1148
  const pending = pendingSecureWrites.get(storageKey);
947
1149
  if (pending !== undefined) {
@@ -968,6 +1170,13 @@ function createStorageItem(config) {
968
1170
  return;
969
1171
  }
970
1172
  cacheRawValue(nonMemoryScope, storageKey, rawValue);
1173
+ if (nonMemoryScope === _Storage.StorageScope.Disk) {
1174
+ if (coalesceDiskWrites || diskWritesAsync) {
1175
+ scheduleDiskWrite(storageKey, rawValue);
1176
+ return;
1177
+ }
1178
+ clearPendingDiskWrite(storageKey);
1179
+ }
971
1180
  if (coalesceSecureWrites) {
972
1181
  scheduleSecureWrite(storageKey, rawValue, secureAccessControl ?? secureDefaultAccessControl);
973
1182
  return;
@@ -983,6 +1192,13 @@ function createStorageItem(config) {
983
1192
  return;
984
1193
  }
985
1194
  cacheRawValue(nonMemoryScope, storageKey, undefined);
1195
+ if (nonMemoryScope === _Storage.StorageScope.Disk) {
1196
+ if (coalesceDiskWrites || diskWritesAsync) {
1197
+ scheduleDiskWrite(storageKey, undefined);
1198
+ return;
1199
+ }
1200
+ clearPendingDiskWrite(storageKey);
1201
+ }
986
1202
  if (coalesceSecureWrites) {
987
1203
  scheduleSecureWrite(storageKey, undefined, secureAccessControl ?? secureDefaultAccessControl);
988
1204
  return;
@@ -1143,6 +1359,18 @@ function createStorageItem(config) {
1143
1359
  const hasItem = () => measureOperation("item:has", config.scope, () => {
1144
1360
  if (isMemory) return memoryStore.has(storageKey);
1145
1361
  if (isBiometric) return WebStorage.hasSecureBiometric(storageKey);
1362
+ if (nonMemoryScope === _Storage.StorageScope.Disk) {
1363
+ const pending = pendingDiskWrites.get(storageKey);
1364
+ if (pending !== undefined) {
1365
+ return pending.value !== undefined;
1366
+ }
1367
+ }
1368
+ if (nonMemoryScope === _Storage.StorageScope.Secure) {
1369
+ const pending = pendingSecureWrites.get(storageKey);
1370
+ if (pending !== undefined) {
1371
+ return pending.value !== undefined;
1372
+ }
1373
+ }
1146
1374
  return WebStorage.has(storageKey, config.scope);
1147
1375
  });
1148
1376
  const subscribe = callback => {
@@ -1153,9 +1381,6 @@ function createStorageItem(config) {
1153
1381
  if (listeners.size === 0 && unsubscribe) {
1154
1382
  unsubscribe();
1155
1383
  unsubscribe = null;
1156
- if (!isMemory) {
1157
- maybeCleanupWebStorageSubscription();
1158
- }
1159
1384
  }
1160
1385
  };
1161
1386
  };
@@ -1203,6 +1428,13 @@ function getBatch(items, scope) {
1203
1428
  const keysToFetch = [];
1204
1429
  const keyIndexes = [];
1205
1430
  items.forEach((item, index) => {
1431
+ if (scope === _Storage.StorageScope.Disk) {
1432
+ const pending = pendingDiskWrites.get(item.key);
1433
+ if (pending !== undefined) {
1434
+ rawValues[index] = pending.value;
1435
+ return;
1436
+ }
1437
+ }
1206
1438
  if (scope === _Storage.StorageScope.Secure) {
1207
1439
  const pending = pendingSecureWrites.get(item.key);
1208
1440
  if (pending !== undefined) {
@@ -1319,6 +1551,7 @@ function setBatch(items, scope) {
1319
1551
  });
1320
1552
  return;
1321
1553
  }
1554
+ flushDiskWrites();
1322
1555
  const useRawBatchPath = items.every(({
1323
1556
  item
1324
1557
  }) => canUseRawBatchPath(asInternal(item)));
@@ -1343,6 +1576,9 @@ function removeBatch(items, scope) {
1343
1576
  return;
1344
1577
  }
1345
1578
  const keys = items.map(item => item.key);
1579
+ if (scope === _Storage.StorageScope.Disk) {
1580
+ flushDiskWrites();
1581
+ }
1346
1582
  if (scope === _Storage.StorageScope.Secure) {
1347
1583
  flushSecureWrites();
1348
1584
  }
@@ -1388,6 +1624,9 @@ function migrateToLatest(scope = _Storage.StorageScope.Disk) {
1388
1624
  function runTransaction(scope, transaction) {
1389
1625
  return measureOperation("transaction:run", scope, () => {
1390
1626
  (0, _internal.assertValidScope)(scope);
1627
+ if (scope === _Storage.StorageScope.Disk) {
1628
+ flushDiskWrites();
1629
+ }
1391
1630
  if (scope === _Storage.StorageScope.Secure) {
1392
1631
  flushSecureWrites();
1393
1632
  }
@@ -1454,6 +1693,9 @@ function runTransaction(scope, transaction) {
1454
1693
  valuesToSet.push(previousValue);
1455
1694
  }
1456
1695
  });
1696
+ if (scope === _Storage.StorageScope.Disk) {
1697
+ flushDiskWrites();
1698
+ }
1457
1699
  if (scope === _Storage.StorageScope.Secure) {
1458
1700
  flushSecureWrites();
1459
1701
  }
@@ -1499,7 +1741,7 @@ function createSecureAuthStorage(config, options) {
1499
1741
  }
1500
1742
  return result;
1501
1743
  }
1502
- function isKeychainLockedError(_err) {
1503
- return false;
1744
+ function isKeychainLockedError(err) {
1745
+ return (0, _storageRuntime.isLockedStorageErrorCode)((0, _storageRuntime.getStorageErrorCode)(err));
1504
1746
  }
1505
1747
  //# sourceMappingURL=index.web.js.map