react-native-nitro-storage 0.4.4 → 0.5.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 (48) hide show
  1. package/README.md +237 -862
  2. package/SECURITY.md +26 -0
  3. package/android/src/main/java/com/nitrostorage/AndroidStorageAdapter.kt +61 -10
  4. package/docs/api-reference.md +217 -0
  5. package/docs/batch-transactions-migrations.md +186 -0
  6. package/docs/benchmarks.md +37 -0
  7. package/docs/mmkv-migration.md +80 -0
  8. package/docs/react-hooks.md +113 -0
  9. package/docs/recipes.md +281 -0
  10. package/docs/secure-storage.md +171 -0
  11. package/docs/web-backends.md +141 -0
  12. package/ios/IOSStorageAdapterCpp.mm +44 -14
  13. package/lib/commonjs/index.js +271 -5
  14. package/lib/commonjs/index.js.map +1 -1
  15. package/lib/commonjs/index.web.js +498 -202
  16. package/lib/commonjs/index.web.js.map +1 -1
  17. package/lib/commonjs/indexeddb-backend.js +129 -7
  18. package/lib/commonjs/indexeddb-backend.js.map +1 -1
  19. package/lib/commonjs/storage-runtime.js +41 -0
  20. package/lib/commonjs/storage-runtime.js.map +1 -0
  21. package/lib/commonjs/web-storage-backend.js +90 -0
  22. package/lib/commonjs/web-storage-backend.js.map +1 -0
  23. package/lib/module/index.js +263 -5
  24. package/lib/module/index.js.map +1 -1
  25. package/lib/module/index.web.js +490 -202
  26. package/lib/module/index.web.js.map +1 -1
  27. package/lib/module/indexeddb-backend.js +129 -7
  28. package/lib/module/indexeddb-backend.js.map +1 -1
  29. package/lib/module/storage-runtime.js +36 -0
  30. package/lib/module/storage-runtime.js.map +1 -0
  31. package/lib/module/web-storage-backend.js +86 -0
  32. package/lib/module/web-storage-backend.js.map +1 -0
  33. package/lib/typescript/index.d.ts +14 -7
  34. package/lib/typescript/index.d.ts.map +1 -1
  35. package/lib/typescript/index.web.d.ts +15 -8
  36. package/lib/typescript/index.web.d.ts.map +1 -1
  37. package/lib/typescript/indexeddb-backend.d.ts +6 -2
  38. package/lib/typescript/indexeddb-backend.d.ts.map +1 -1
  39. package/lib/typescript/storage-runtime.d.ts +48 -0
  40. package/lib/typescript/storage-runtime.d.ts.map +1 -0
  41. package/lib/typescript/web-storage-backend.d.ts +30 -0
  42. package/lib/typescript/web-storage-backend.d.ts.map +1 -0
  43. package/package.json +21 -8
  44. package/src/index.ts +330 -20
  45. package/src/index.web.ts +673 -245
  46. package/src/indexeddb-backend.ts +147 -6
  47. package/src/storage-runtime.ts +129 -0
  48. package/src/web-storage-backend.ts +129 -0
@@ -0,0 +1,90 @@
1
+ "use strict";
2
+
3
+ Object.defineProperty(exports, "__esModule", {
4
+ value: true
5
+ });
6
+ exports.createLocalStorageWebBackend = createLocalStorageWebBackend;
7
+ function getResolvedStorage(resolveStorage) {
8
+ if (resolveStorage) {
9
+ return resolveStorage();
10
+ }
11
+ if (typeof globalThis.localStorage === "undefined") {
12
+ return undefined;
13
+ }
14
+ return globalThis.localStorage;
15
+ }
16
+ function createLocalStorageWebBackend(options = {}) {
17
+ const includeKey = options.includeKey;
18
+ const resolveStorage = options.resolveStorage;
19
+ const listKeys = () => {
20
+ const storage = getResolvedStorage(resolveStorage);
21
+ if (!storage) {
22
+ return [];
23
+ }
24
+ const keys = [];
25
+ for (let index = 0; index < storage.length; index += 1) {
26
+ const key = storage.key(index);
27
+ if (!key) {
28
+ continue;
29
+ }
30
+ if (includeKey && !includeKey(key)) {
31
+ continue;
32
+ }
33
+ keys.push(key);
34
+ }
35
+ return keys;
36
+ };
37
+ return {
38
+ name: options.name ?? "localStorage",
39
+ getItem(key) {
40
+ return getResolvedStorage(resolveStorage)?.getItem(key) ?? null;
41
+ },
42
+ setItem(key, value) {
43
+ getResolvedStorage(resolveStorage)?.setItem(key, value);
44
+ },
45
+ removeItem(key) {
46
+ getResolvedStorage(resolveStorage)?.removeItem(key);
47
+ },
48
+ clear() {
49
+ const storage = getResolvedStorage(resolveStorage);
50
+ if (!storage) {
51
+ return;
52
+ }
53
+ listKeys().forEach(key => {
54
+ storage.removeItem(key);
55
+ });
56
+ },
57
+ getAllKeys() {
58
+ return listKeys();
59
+ },
60
+ getMany(keys) {
61
+ const storage = getResolvedStorage(resolveStorage);
62
+ if (!storage) {
63
+ return keys.map(() => null);
64
+ }
65
+ return keys.map(key => storage.getItem(key));
66
+ },
67
+ setMany(entries) {
68
+ const storage = getResolvedStorage(resolveStorage);
69
+ if (!storage) {
70
+ return;
71
+ }
72
+ entries.forEach(([key, value]) => {
73
+ storage.setItem(key, value);
74
+ });
75
+ },
76
+ removeMany(keys) {
77
+ const storage = getResolvedStorage(resolveStorage);
78
+ if (!storage) {
79
+ return;
80
+ }
81
+ keys.forEach(key => {
82
+ storage.removeItem(key);
83
+ });
84
+ },
85
+ size() {
86
+ return listKeys().length;
87
+ }
88
+ };
89
+ }
90
+ //# sourceMappingURL=web-storage-backend.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"names":["getResolvedStorage","resolveStorage","globalThis","localStorage","undefined","createLocalStorageWebBackend","options","includeKey","listKeys","storage","keys","index","length","key","push","name","getItem","setItem","value","removeItem","clear","forEach","getAllKeys","getMany","map","setMany","entries","removeMany","size"],"sourceRoot":"../../src","sources":["web-storage-backend.ts"],"mappings":";;;;;;AAmCA,SAASA,kBAAkBA,CACzBC,cAAuD,EAClC;EACrB,IAAIA,cAAc,EAAE;IAClB,OAAOA,cAAc,CAAC,CAAC;EACzB;EAEA,IAAI,OAAOC,UAAU,CAACC,YAAY,KAAK,WAAW,EAAE;IAClD,OAAOC,SAAS;EAClB;EAEA,OAAOF,UAAU,CAACC,YAAY;AAChC;AAEO,SAASE,4BAA4BA,CAC1CC,OAAmC,GAAG,CAAC,CAAC,EACrB;EACnB,MAAMC,UAAU,GAAGD,OAAO,CAACC,UAAU;EACrC,MAAMN,cAAc,GAAGK,OAAO,CAACL,cAAc;EAE7C,MAAMO,QAAQ,GAAGA,CAAA,KAAgB;IAC/B,MAAMC,OAAO,GAAGT,kBAAkB,CAACC,cAAc,CAAC;IAClD,IAAI,CAACQ,OAAO,EAAE;MACZ,OAAO,EAAE;IACX;IAEA,MAAMC,IAAc,GAAG,EAAE;IACzB,KAAK,IAAIC,KAAK,GAAG,CAAC,EAAEA,KAAK,GAAGF,OAAO,CAACG,MAAM,EAAED,KAAK,IAAI,CAAC,EAAE;MACtD,MAAME,GAAG,GAAGJ,OAAO,CAACI,GAAG,CAACF,KAAK,CAAC;MAC9B,IAAI,CAACE,GAAG,EAAE;QACR;MACF;MACA,IAAIN,UAAU,IAAI,CAACA,UAAU,CAACM,GAAG,CAAC,EAAE;QAClC;MACF;MACAH,IAAI,CAACI,IAAI,CAACD,GAAG,CAAC;IAChB;IACA,OAAOH,IAAI;EACb,CAAC;EAED,OAAO;IACLK,IAAI,EAAET,OAAO,CAACS,IAAI,IAAI,cAAc;IACpCC,OAAOA,CAACH,GAAW,EAAiB;MAClC,OAAOb,kBAAkB,CAACC,cAAc,CAAC,EAAEe,OAAO,CAACH,GAAG,CAAC,IAAI,IAAI;IACjE,CAAC;IACDI,OAAOA,CAACJ,GAAW,EAAEK,KAAa,EAAQ;MACxClB,kBAAkB,CAACC,cAAc,CAAC,EAAEgB,OAAO,CAACJ,GAAG,EAAEK,KAAK,CAAC;IACzD,CAAC;IACDC,UAAUA,CAACN,GAAW,EAAQ;MAC5Bb,kBAAkB,CAACC,cAAc,CAAC,EAAEkB,UAAU,CAACN,GAAG,CAAC;IACrD,CAAC;IACDO,KAAKA,CAAA,EAAS;MACZ,MAAMX,OAAO,GAAGT,kBAAkB,CAACC,cAAc,CAAC;MAClD,IAAI,CAACQ,OAAO,EAAE;QACZ;MACF;MAEAD,QAAQ,CAAC,CAAC,CAACa,OAAO,CAAER,GAAG,IAAK;QAC1BJ,OAAO,CAACU,UAAU,CAACN,GAAG,CAAC;MACzB,CAAC,CAAC;IACJ,CAAC;IACDS,UAAUA,CAAA,EAAa;MACrB,OAAOd,QAAQ,CAAC,CAAC;IACnB,CAAC;IACDe,OAAOA,CAACb,IAAc,EAAqB;MACzC,MAAMD,OAAO,GAAGT,kBAAkB,CAACC,cAAc,CAAC;MAClD,IAAI,CAACQ,OAAO,EAAE;QACZ,OAAOC,IAAI,CAACc,GAAG,CAAC,MAAM,IAAI,CAAC;MAC7B;MACA,OAAOd,IAAI,CAACc,GAAG,CAAEX,GAAG,IAAKJ,OAAO,CAACO,OAAO,CAACH,GAAG,CAAC,CAAC;IAChD,CAAC;IACDY,OAAOA,CAACC,OAAO,EAAQ;MACrB,MAAMjB,OAAO,GAAGT,kBAAkB,CAACC,cAAc,CAAC;MAClD,IAAI,CAACQ,OAAO,EAAE;QACZ;MACF;MACAiB,OAAO,CAACL,OAAO,CAAC,CAAC,CAACR,GAAG,EAAEK,KAAK,CAAC,KAAK;QAChCT,OAAO,CAACQ,OAAO,CAACJ,GAAG,EAAEK,KAAK,CAAC;MAC7B,CAAC,CAAC;IACJ,CAAC;IACDS,UAAUA,CAACjB,IAAc,EAAQ;MAC/B,MAAMD,OAAO,GAAGT,kBAAkB,CAACC,cAAc,CAAC;MAClD,IAAI,CAACQ,OAAO,EAAE;QACZ;MACF;MACAC,IAAI,CAACW,OAAO,CAAER,GAAG,IAAK;QACpBJ,OAAO,CAACU,UAAU,CAACN,GAAG,CAAC;MACzB,CAAC,CAAC;IACJ,CAAC;IACDe,IAAIA,CAAA,EAAW;MACb,OAAOpB,QAAQ,CAAC,CAAC,CAACI,MAAM;IAC1B;EACF,CAAC;AACH","ignoreList":[]}
@@ -3,8 +3,10 @@
3
3
  import { NitroModules } from "react-native-nitro-modules";
4
4
  import { StorageScope, AccessControl, BiometricLevel } from "./Storage.types";
5
5
  import { MIGRATION_VERSION_KEY, isStoredEnvelope, assertBatchScope, assertValidScope, decodeNativeBatchValue, serializeWithPrimitiveFastPath, deserializeWithPrimitiveFastPath, toVersionToken, prefixKey, isNamespaced } from "./internal";
6
+ import { getStorageErrorCode, isLockedStorageErrorCode } from "./storage-runtime";
6
7
  export { StorageScope, AccessControl, BiometricLevel } from "./Storage.types";
7
8
  export { migrateFromMMKV } from "./migration";
9
+ export { getStorageErrorCode } from "./storage-runtime";
8
10
  function asInternal(item) {
9
11
  return item;
10
12
  }
@@ -18,6 +20,7 @@ const registeredMigrations = new Map();
18
20
  const runMicrotask = typeof queueMicrotask === "function" ? queueMicrotask : task => {
19
21
  Promise.resolve().then(task);
20
22
  };
23
+ const now = typeof performance !== "undefined" && typeof performance.now === "function" ? () => performance.now() : () => Date.now();
21
24
  let _storageModule = null;
22
25
  function getStorageModule() {
23
26
  if (!_storageModule) {
@@ -30,11 +33,15 @@ const memoryListeners = new Map();
30
33
  const scopedListeners = new Map([[StorageScope.Disk, new Map()], [StorageScope.Secure, new Map()]]);
31
34
  const scopedUnsubscribers = new Map();
32
35
  const scopedRawCache = new Map([[StorageScope.Disk, new Map()], [StorageScope.Secure, new Map()]]);
36
+ const pendingDiskWrites = new Map();
37
+ let diskFlushScheduled = false;
38
+ let diskWritesAsync = false;
33
39
  const pendingSecureWrites = new Map();
34
40
  let secureFlushScheduled = false;
35
41
  let secureDefaultAccessControl = AccessControl.WhenUnlocked;
36
42
  let metricsObserver;
37
43
  const metricsCounters = new Map();
44
+ const nativeSecureBackend = "platform-secure-storage";
38
45
  function recordMetric(operation, scope, durationMs, keysCount = 1) {
39
46
  const existing = metricsCounters.get(operation);
40
47
  if (!existing) {
@@ -59,11 +66,11 @@ function measureOperation(operation, scope, fn, keysCount = 1) {
59
66
  if (!metricsObserver) {
60
67
  return fn();
61
68
  }
62
- const start = Date.now();
69
+ const start = now();
63
70
  try {
64
71
  return fn();
65
72
  } finally {
66
- recordMetric(operation, scope, Date.now() - start, keysCount);
73
+ recordMetric(operation, scope, now() - start, keysCount);
67
74
  }
68
75
  }
69
76
  function getScopedListeners(scope) {
@@ -120,12 +127,50 @@ function addKeyListener(registry, key, listener) {
120
127
  function readPendingSecureWrite(key) {
121
128
  return pendingSecureWrites.get(key)?.value;
122
129
  }
130
+ function readPendingDiskWrite(key) {
131
+ return pendingDiskWrites.get(key)?.value;
132
+ }
133
+ function hasPendingDiskWrite(key) {
134
+ return pendingDiskWrites.has(key);
135
+ }
123
136
  function hasPendingSecureWrite(key) {
124
137
  return pendingSecureWrites.has(key);
125
138
  }
139
+ function clearPendingDiskWrite(key) {
140
+ pendingDiskWrites.delete(key);
141
+ }
126
142
  function clearPendingSecureWrite(key) {
127
143
  pendingSecureWrites.delete(key);
128
144
  }
145
+ function flushDiskWrites() {
146
+ diskFlushScheduled = false;
147
+ if (pendingDiskWrites.size === 0) {
148
+ return;
149
+ }
150
+ const writes = Array.from(pendingDiskWrites.values());
151
+ pendingDiskWrites.clear();
152
+ const keysToSet = [];
153
+ const valuesToSet = [];
154
+ const keysToRemove = [];
155
+ writes.forEach(({
156
+ key,
157
+ value
158
+ }) => {
159
+ if (value === undefined) {
160
+ keysToRemove.push(key);
161
+ return;
162
+ }
163
+ keysToSet.push(key);
164
+ valuesToSet.push(value);
165
+ });
166
+ const storageModule = getStorageModule();
167
+ if (keysToSet.length > 0) {
168
+ storageModule.setBatch(keysToSet, valuesToSet, StorageScope.Disk);
169
+ }
170
+ if (keysToRemove.length > 0) {
171
+ storageModule.removeBatch(keysToRemove, StorageScope.Disk);
172
+ }
173
+ }
129
174
  function flushSecureWrites() {
130
175
  secureFlushScheduled = false;
131
176
  if (pendingSecureWrites.size === 0) {
@@ -165,6 +210,17 @@ function flushSecureWrites() {
165
210
  storageModule.removeBatch(keysToRemove, StorageScope.Secure);
166
211
  }
167
212
  }
213
+ function scheduleDiskWrite(key, value) {
214
+ pendingDiskWrites.set(key, {
215
+ key,
216
+ value
217
+ });
218
+ if (diskFlushScheduled) {
219
+ return;
220
+ }
221
+ diskFlushScheduled = true;
222
+ runMicrotask(flushDiskWrites);
223
+ }
168
224
  function scheduleSecureWrite(key, value, accessControl) {
169
225
  const pendingWrite = {
170
226
  key,
@@ -185,6 +241,13 @@ function ensureNativeScopeSubscription(scope) {
185
241
  return;
186
242
  }
187
243
  const unsubscribe = getStorageModule().addOnChange(scope, (key, value) => {
244
+ if (scope === StorageScope.Disk) {
245
+ if (key === "") {
246
+ pendingDiskWrites.clear();
247
+ } else {
248
+ clearPendingDiskWrite(key);
249
+ }
250
+ }
188
251
  if (scope === StorageScope.Secure) {
189
252
  if (key === "") {
190
253
  pendingSecureWrites.clear();
@@ -220,6 +283,9 @@ function getRawValue(key, scope) {
220
283
  const value = memoryStore.get(key);
221
284
  return typeof value === "string" ? value : undefined;
222
285
  }
286
+ if (scope === StorageScope.Disk && hasPendingDiskWrite(key)) {
287
+ return readPendingDiskWrite(key);
288
+ }
223
289
  if (scope === StorageScope.Secure && hasPendingSecureWrite(key)) {
224
290
  return readPendingSecureWrite(key);
225
291
  }
@@ -232,6 +298,15 @@ function setRawValue(key, value, scope) {
232
298
  notifyKeyListeners(memoryListeners, key);
233
299
  return;
234
300
  }
301
+ if (scope === StorageScope.Disk) {
302
+ cacheRawValue(scope, key, value);
303
+ if (diskWritesAsync) {
304
+ scheduleDiskWrite(key, value);
305
+ return;
306
+ }
307
+ flushDiskWrites();
308
+ clearPendingDiskWrite(key);
309
+ }
235
310
  if (scope === StorageScope.Secure) {
236
311
  flushSecureWrites();
237
312
  clearPendingSecureWrite(key);
@@ -247,6 +322,15 @@ function removeRawValue(key, scope) {
247
322
  notifyKeyListeners(memoryListeners, key);
248
323
  return;
249
324
  }
325
+ if (scope === StorageScope.Disk) {
326
+ cacheRawValue(scope, key, undefined);
327
+ if (diskWritesAsync) {
328
+ scheduleDiskWrite(key, undefined);
329
+ return;
330
+ }
331
+ flushDiskWrites();
332
+ clearPendingDiskWrite(key);
333
+ }
250
334
  if (scope === StorageScope.Secure) {
251
335
  flushSecureWrites();
252
336
  clearPendingSecureWrite(key);
@@ -273,6 +357,10 @@ export const storage = {
273
357
  notifyAllListeners(memoryListeners);
274
358
  return;
275
359
  }
360
+ if (scope === StorageScope.Disk) {
361
+ flushDiskWrites();
362
+ pendingDiskWrites.clear();
363
+ }
276
364
  if (scope === StorageScope.Secure) {
277
365
  flushSecureWrites();
278
366
  pendingSecureWrites.clear();
@@ -301,6 +389,9 @@ export const storage = {
301
389
  return;
302
390
  }
303
391
  const keyPrefix = prefixKey(namespace, "");
392
+ if (scope === StorageScope.Disk) {
393
+ flushDiskWrites();
394
+ }
304
395
  if (scope === StorageScope.Secure) {
305
396
  flushSecureWrites();
306
397
  }
@@ -324,6 +415,12 @@ export const storage = {
324
415
  if (scope === StorageScope.Memory) {
325
416
  return memoryStore.has(key);
326
417
  }
418
+ if (scope === StorageScope.Disk) {
419
+ flushDiskWrites();
420
+ }
421
+ if (scope === StorageScope.Secure) {
422
+ flushSecureWrites();
423
+ }
327
424
  return getStorageModule().has(key, scope);
328
425
  });
329
426
  },
@@ -333,6 +430,12 @@ export const storage = {
333
430
  if (scope === StorageScope.Memory) {
334
431
  return Array.from(memoryStore.keys());
335
432
  }
433
+ if (scope === StorageScope.Disk) {
434
+ flushDiskWrites();
435
+ }
436
+ if (scope === StorageScope.Secure) {
437
+ flushSecureWrites();
438
+ }
336
439
  return getStorageModule().getAllKeys(scope);
337
440
  });
338
441
  },
@@ -342,6 +445,12 @@ export const storage = {
342
445
  if (scope === StorageScope.Memory) {
343
446
  return Array.from(memoryStore.keys()).filter(key => key.startsWith(prefix));
344
447
  }
448
+ if (scope === StorageScope.Disk) {
449
+ flushDiskWrites();
450
+ }
451
+ if (scope === StorageScope.Secure) {
452
+ flushSecureWrites();
453
+ }
345
454
  return getStorageModule().getKeysByPrefix(prefix, scope);
346
455
  });
347
456
  },
@@ -361,6 +470,12 @@ export const storage = {
361
470
  });
362
471
  return result;
363
472
  }
473
+ if (scope === StorageScope.Disk) {
474
+ flushDiskWrites();
475
+ }
476
+ if (scope === StorageScope.Secure) {
477
+ flushSecureWrites();
478
+ }
364
479
  const values = getStorageModule().getBatch(keys, scope);
365
480
  keys.forEach((key, idx) => {
366
481
  const value = decodeNativeBatchValue(values[idx]);
@@ -381,6 +496,12 @@ export const storage = {
381
496
  });
382
497
  return result;
383
498
  }
499
+ if (scope === StorageScope.Disk) {
500
+ flushDiskWrites();
501
+ }
502
+ if (scope === StorageScope.Secure) {
503
+ flushSecureWrites();
504
+ }
384
505
  const keys = getStorageModule().getAllKeys(scope);
385
506
  if (keys.length === 0) return result;
386
507
  const values = getStorageModule().getBatch(keys, scope);
@@ -397,6 +518,12 @@ export const storage = {
397
518
  if (scope === StorageScope.Memory) {
398
519
  return memoryStore.size;
399
520
  }
521
+ if (scope === StorageScope.Disk) {
522
+ flushDiskWrites();
523
+ }
524
+ if (scope === StorageScope.Secure) {
525
+ flushSecureWrites();
526
+ }
400
527
  return getStorageModule().size(scope);
401
528
  });
402
529
  },
@@ -411,6 +538,19 @@ export const storage = {
411
538
  getStorageModule().setSecureWritesAsync(enabled);
412
539
  });
413
540
  },
541
+ setDiskWritesAsync: enabled => {
542
+ measureOperation("storage:setDiskWritesAsync", StorageScope.Disk, () => {
543
+ diskWritesAsync = enabled;
544
+ if (!enabled) {
545
+ flushDiskWrites();
546
+ }
547
+ });
548
+ },
549
+ flushDiskWrites: () => {
550
+ measureOperation("storage:flushDiskWrites", StorageScope.Disk, () => {
551
+ flushDiskWrites();
552
+ });
553
+ },
414
554
  flushSecureWrites: () => {
415
555
  measureOperation("storage:flushSecureWrites", StorageScope.Secure, () => {
416
556
  flushSecureWrites();
@@ -439,6 +579,67 @@ export const storage = {
439
579
  resetMetrics: () => {
440
580
  metricsCounters.clear();
441
581
  },
582
+ getCapabilities: () => ({
583
+ platform: "native",
584
+ backend: {
585
+ disk: "platform-preferences",
586
+ secure: nativeSecureBackend
587
+ },
588
+ writeBuffering: {
589
+ disk: true,
590
+ secure: true
591
+ },
592
+ errorClassification: true
593
+ }),
594
+ getSecurityCapabilities: () => ({
595
+ platform: "native",
596
+ secureStorage: {
597
+ backend: nativeSecureBackend,
598
+ encrypted: "available",
599
+ accessControl: "unknown",
600
+ keychainAccessGroup: "unknown",
601
+ hardwareBacked: "unknown"
602
+ },
603
+ biometric: {
604
+ storage: "unknown",
605
+ prompt: "unknown",
606
+ biometryOnly: "unknown",
607
+ biometryOrPasscode: "unknown"
608
+ },
609
+ metadata: {
610
+ perKey: true,
611
+ listsWithoutValues: true,
612
+ persistsTimestamps: false
613
+ }
614
+ }),
615
+ getSecureMetadata: key => {
616
+ return measureOperation("storage:getSecureMetadata", StorageScope.Secure, () => {
617
+ flushSecureWrites();
618
+ const storageModule = getStorageModule();
619
+ const biometricProtected = storageModule.hasSecureBiometric(key);
620
+ const exists = biometricProtected || storageModule.has(key, StorageScope.Secure);
621
+ let kind = "missing";
622
+ if (exists) {
623
+ kind = biometricProtected ? "biometric" : "secure";
624
+ }
625
+ return {
626
+ key,
627
+ exists,
628
+ kind,
629
+ backend: nativeSecureBackend,
630
+ encrypted: "available",
631
+ hardwareBacked: "unknown",
632
+ biometricProtected,
633
+ valueExposed: false
634
+ };
635
+ });
636
+ },
637
+ getAllSecureMetadata: () => {
638
+ return measureOperation("storage:getAllSecureMetadata", StorageScope.Secure, () => {
639
+ flushSecureWrites();
640
+ return getStorageModule().getAllKeys(StorageScope.Secure).map(key => storage.getSecureMetadata(key));
641
+ });
642
+ },
442
643
  getString: (key, scope) => {
443
644
  return measureOperation("storage:getString", scope, () => {
444
645
  return getRawValue(key, scope);
@@ -482,6 +683,15 @@ export function setWebSecureStorageBackend(_backend) {
482
683
  export function getWebSecureStorageBackend() {
483
684
  return undefined;
484
685
  }
686
+ export function setWebDiskStorageBackend(_backend) {
687
+ // Native platforms do not use web disk backends.
688
+ }
689
+ export function getWebDiskStorageBackend() {
690
+ return undefined;
691
+ }
692
+ export async function flushWebStorageBackends() {
693
+ // Native platforms do not use web storage backends.
694
+ }
485
695
  function canUseRawBatchPath(item) {
486
696
  return item._hasExpiration === false && item._hasValidation === false && item._isBiometric !== true && item._secureAccessControl === undefined;
487
697
  }
@@ -509,6 +719,7 @@ export function createStorageItem(config) {
509
719
  const expirationTtlMs = expiration?.ttlMs;
510
720
  const memoryExpiration = expiration && isMemory ? new Map() : null;
511
721
  const readCache = !isMemory && config.readCache === true;
722
+ const coalesceDiskWrites = config.scope === StorageScope.Disk && config.coalesceDiskWrites === true;
512
723
  const coalesceSecureWrites = config.scope === StorageScope.Secure && config.coalesceSecureWrites === true && !isBiometric;
513
724
  const defaultValue = config.defaultValue;
514
725
  const nonMemoryScope = config.scope === StorageScope.Disk ? StorageScope.Disk : config.scope === StorageScope.Secure ? StorageScope.Secure : null;
@@ -556,6 +767,12 @@ export function createStorageItem(config) {
556
767
  }
557
768
  return memoryStore.get(storageKey);
558
769
  }
770
+ if (nonMemoryScope === StorageScope.Disk) {
771
+ const pending = pendingDiskWrites.get(storageKey);
772
+ if (pending !== undefined) {
773
+ return pending.value;
774
+ }
775
+ }
559
776
  if (nonMemoryScope === StorageScope.Secure && !isBiometric) {
560
777
  const pending = pendingSecureWrites.get(storageKey);
561
778
  if (pending !== undefined) {
@@ -582,6 +799,13 @@ export function createStorageItem(config) {
582
799
  return;
583
800
  }
584
801
  cacheRawValue(nonMemoryScope, storageKey, rawValue);
802
+ if (nonMemoryScope === StorageScope.Disk) {
803
+ if (coalesceDiskWrites || diskWritesAsync) {
804
+ scheduleDiskWrite(storageKey, rawValue);
805
+ return;
806
+ }
807
+ clearPendingDiskWrite(storageKey);
808
+ }
585
809
  if (coalesceSecureWrites) {
586
810
  scheduleSecureWrite(storageKey, rawValue, secureAccessControl ?? secureDefaultAccessControl);
587
811
  return;
@@ -598,6 +822,13 @@ export function createStorageItem(config) {
598
822
  return;
599
823
  }
600
824
  cacheRawValue(nonMemoryScope, storageKey, undefined);
825
+ if (nonMemoryScope === StorageScope.Disk) {
826
+ if (coalesceDiskWrites || diskWritesAsync) {
827
+ scheduleDiskWrite(storageKey, undefined);
828
+ return;
829
+ }
830
+ clearPendingDiskWrite(storageKey);
831
+ }
601
832
  if (coalesceSecureWrites) {
602
833
  scheduleSecureWrite(storageKey, undefined, secureAccessControl ?? secureDefaultAccessControl);
603
834
  return;
@@ -758,6 +989,18 @@ export function createStorageItem(config) {
758
989
  const hasItem = () => measureOperation("item:has", config.scope, () => {
759
990
  if (isMemory) return memoryStore.has(storageKey);
760
991
  if (isBiometric) return getStorageModule().hasSecureBiometric(storageKey);
992
+ if (nonMemoryScope === StorageScope.Disk) {
993
+ const pending = pendingDiskWrites.get(storageKey);
994
+ if (pending !== undefined) {
995
+ return pending.value !== undefined;
996
+ }
997
+ }
998
+ if (nonMemoryScope === StorageScope.Secure) {
999
+ const pending = pendingSecureWrites.get(storageKey);
1000
+ if (pending !== undefined) {
1001
+ return pending.value !== undefined;
1002
+ }
1003
+ }
761
1004
  return getStorageModule().has(storageKey, config.scope);
762
1005
  });
763
1006
  const subscribe = callback => {
@@ -820,6 +1063,13 @@ export function getBatch(items, scope) {
820
1063
  const keysToFetch = [];
821
1064
  const keyIndexes = [];
822
1065
  items.forEach((item, index) => {
1066
+ if (scope === StorageScope.Disk) {
1067
+ const pending = pendingDiskWrites.get(item.key);
1068
+ if (pending !== undefined) {
1069
+ rawValues[index] = pending.value;
1070
+ return;
1071
+ }
1072
+ }
823
1073
  if (scope === StorageScope.Secure) {
824
1074
  const pending = pendingSecureWrites.get(item.key);
825
1075
  if (pending !== undefined) {
@@ -938,6 +1188,7 @@ export function setBatch(items, scope) {
938
1188
  });
939
1189
  return;
940
1190
  }
1191
+ flushDiskWrites();
941
1192
  const useRawBatchPath = items.every(({
942
1193
  item
943
1194
  }) => canUseRawBatchPath(asInternal(item)));
@@ -962,6 +1213,9 @@ export function removeBatch(items, scope) {
962
1213
  return;
963
1214
  }
964
1215
  const keys = items.map(item => item.key);
1216
+ if (scope === StorageScope.Disk) {
1217
+ flushDiskWrites();
1218
+ }
965
1219
  if (scope === StorageScope.Secure) {
966
1220
  flushSecureWrites();
967
1221
  }
@@ -1007,6 +1261,9 @@ export function migrateToLatest(scope = StorageScope.Disk) {
1007
1261
  export function runTransaction(scope, transaction) {
1008
1262
  return measureOperation("transaction:run", scope, () => {
1009
1263
  assertValidScope(scope);
1264
+ if (scope === StorageScope.Disk) {
1265
+ flushDiskWrites();
1266
+ }
1010
1267
  if (scope === StorageScope.Secure) {
1011
1268
  flushSecureWrites();
1012
1269
  }
@@ -1073,6 +1330,9 @@ export function runTransaction(scope, transaction) {
1073
1330
  valuesToSet.push(previousValue);
1074
1331
  }
1075
1332
  });
1333
+ if (scope === StorageScope.Disk) {
1334
+ flushDiskWrites();
1335
+ }
1076
1336
  if (scope === StorageScope.Secure) {
1077
1337
  flushSecureWrites();
1078
1338
  }
@@ -1090,9 +1350,7 @@ export function runTransaction(scope, transaction) {
1090
1350
  });
1091
1351
  }
1092
1352
  export function isKeychainLockedError(err) {
1093
- if (!(err instanceof Error)) return false;
1094
- const msg = err.message;
1095
- return msg.includes("errSecInteractionNotAllowed") || msg.includes("UserNotAuthenticatedException") || msg.includes("KeyStoreException") || msg.includes("KeyPermanentlyInvalidatedException") || msg.includes("InvalidKeyException") || msg.includes("android.security.keystore");
1353
+ return isLockedStorageErrorCode(getStorageErrorCode(err));
1096
1354
  }
1097
1355
  export function createSecureAuthStorage(config, options) {
1098
1356
  const ns = options?.namespace ?? "auth";