react-native-nitro-storage 0.8.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 (78) hide show
  1. package/CHANGELOG.md +107 -25
  2. package/README.md +140 -27
  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 +109 -293
  6. package/cpp/bindings/HybridStorage.hpp +17 -10
  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 +859 -145
  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 +860 -146
  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 +12 -3
  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/nitrogen/generated/shared/c++/HybridStorageSpec.hpp +1 -1
  68. package/package.json +3 -3
  69. package/react-native-nitro-storage.podspec +3 -1
  70. package/src/capabilities.ts +3 -1
  71. package/src/core/durability.ts +139 -39
  72. package/src/index.ts +20 -10
  73. package/src/index.web.ts +328 -166
  74. package/src/internal.ts +0 -14
  75. package/src/shared.ts +85 -0
  76. package/src/storage-core.ts +1165 -199
  77. package/src/storage-runtime.ts +6 -0
  78. package/src/testing.ts +8 -1
@@ -14,8 +14,16 @@
14
14
  namespace margelo::nitro::NitroStorage {
15
15
 
16
16
  namespace {
17
- constexpr auto kBatchMissingSentinel = "__nitro_storage_batch_missing__::v1";
18
17
  constexpr int kDefaultBiometricLevel = 2;
18
+
19
+ template <typename Map>
20
+ size_t mapMemorySize(const Map& map) noexcept {
21
+ size_t total = map.size() * sizeof(typename Map::value_type);
22
+ if constexpr (requires { map.bucket_count(); }) {
23
+ total += map.bucket_count() * sizeof(void*);
24
+ }
25
+ return total;
26
+ }
19
27
  } // namespace
20
28
 
21
29
  HybridStorage::HybridStorage()
@@ -57,27 +65,16 @@ void HybridStorage::set(const std::string& key, const std::string& value, double
57
65
  }
58
66
  case Scope::Disk:
59
67
  ensureAdapter();
60
- try {
61
- nativeAdapter_->setDisk(key, value);
62
- } catch (const std::exception&) {
63
- throw;
64
- } catch (...) {
65
- throw std::runtime_error("NitroStorage: Disk set failed (unknown error)");
66
- }
68
+ runAdapterOperation(
69
+ [&] { nativeAdapter_->setDisk(key, value); }, "Disk set");
67
70
  break;
68
71
  case Scope::Secure:
69
72
  ensureAdapter();
70
- try {
71
- nativeAdapter_->setSecure(key, value);
72
- } catch (const std::exception&) {
73
- throw;
74
- } catch (...) {
75
- throw std::runtime_error("NitroStorage: Secure set failed (unknown error)");
76
- }
73
+ runAdapterOperation(
74
+ [&] { nativeAdapter_->setSecure(key, value); }, "Secure set");
77
75
  break;
78
76
  }
79
77
 
80
- onKeySet(static_cast<int>(s), key);
81
78
  notifyListeners(static_cast<int>(s), key, value);
82
79
  }
83
80
 
@@ -95,22 +92,12 @@ std::optional<std::string> HybridStorage::get(const std::string& key, double sco
95
92
  }
96
93
  case Scope::Disk:
97
94
  ensureAdapter();
98
- try {
99
- return nativeAdapter_->getDisk(key);
100
- } catch (const std::exception&) {
101
- throw;
102
- } catch (...) {
103
- throw std::runtime_error("NitroStorage: Disk get failed (unknown error)");
104
- }
95
+ return runAdapterOperation(
96
+ [&] { return nativeAdapter_->getDisk(key); }, "Disk get");
105
97
  case Scope::Secure:
106
98
  ensureAdapter();
107
- try {
108
- return nativeAdapter_->getSecure(key);
109
- } catch (const std::exception&) {
110
- throw;
111
- } catch (...) {
112
- throw std::runtime_error("NitroStorage: Secure get failed (unknown error)");
113
- }
99
+ return runAdapterOperation(
100
+ [&] { return nativeAdapter_->getSecure(key); }, "Secure get");
114
101
  }
115
102
 
116
103
  return std::nullopt;
@@ -127,27 +114,16 @@ void HybridStorage::remove(const std::string& key, double scope) {
127
114
  }
128
115
  case Scope::Disk:
129
116
  ensureAdapter();
130
- try {
131
- nativeAdapter_->deleteDisk(key);
132
- } catch (const std::exception&) {
133
- throw;
134
- } catch (...) {
135
- throw std::runtime_error("NitroStorage: Disk delete failed (unknown error)");
136
- }
117
+ runAdapterOperation(
118
+ [&] { nativeAdapter_->deleteDisk(key); }, "Disk delete");
137
119
  break;
138
120
  case Scope::Secure:
139
121
  ensureAdapter();
140
- try {
141
- nativeAdapter_->deleteSecure(key);
142
- } catch (const std::exception&) {
143
- throw;
144
- } catch (...) {
145
- throw std::runtime_error("NitroStorage: Secure delete failed (unknown error)");
146
- }
122
+ runAdapterOperation(
123
+ [&] { nativeAdapter_->deleteSecure(key); }, "Secure delete");
147
124
  break;
148
125
  }
149
126
 
150
- onKeyRemove(static_cast<int>(s), key);
151
127
  notifyListeners(static_cast<int>(s), key, std::nullopt);
152
128
  }
153
129
 
@@ -160,16 +136,13 @@ bool HybridStorage::has(const std::string& key, double scope) {
160
136
  return memoryStore_.find(key) != memoryStore_.end();
161
137
  }
162
138
  case Scope::Disk:
163
- case Scope::Secure: {
164
- const int scopeValue = static_cast<int>(s);
165
- ensureKeyIndexHydrated(scopeValue);
166
- std::lock_guard<std::mutex> lock(keyIndexMutex_);
167
- auto indexIt = keyIndex_.find(scopeValue);
168
- if (indexIt == keyIndex_.end()) {
169
- return false;
170
- }
171
- return indexIt->second.count(key) > 0;
172
- }
139
+ ensureAdapter();
140
+ return runAdapterOperation(
141
+ [&] { return nativeAdapter_->hasDisk(key); }, "Disk has");
142
+ case Scope::Secure:
143
+ ensureAdapter();
144
+ return runAdapterOperation(
145
+ [&] { return nativeAdapter_->hasSecure(key); }, "Secure has");
173
146
  }
174
147
  return false;
175
148
  }
@@ -188,16 +161,13 @@ std::vector<std::string> HybridStorage::getAllKeys(double scope) {
188
161
  return keys;
189
162
  }
190
163
  case Scope::Disk:
191
- case Scope::Secure: {
192
- const int scopeValue = static_cast<int>(s);
193
- ensureKeyIndexHydrated(scopeValue);
194
- std::lock_guard<std::mutex> lock(keyIndexMutex_);
195
- auto indexIt = keyIndex_.find(scopeValue);
196
- if (indexIt == keyIndex_.end()) {
197
- return {};
198
- }
199
- return toVector(indexIt->second);
200
- }
164
+ ensureAdapter();
165
+ return runAdapterOperation(
166
+ [&] { return nativeAdapter_->getAllKeysDisk(); }, "Disk getAllKeys");
167
+ case Scope::Secure:
168
+ ensureAdapter();
169
+ return runAdapterOperation(
170
+ [&] { return nativeAdapter_->getAllKeysSecure(); }, "Secure getAllKeys");
201
171
  }
202
172
  return {};
203
173
  }
@@ -221,23 +191,13 @@ std::vector<std::string> HybridStorage::getKeysByPrefix(const std::string& prefi
221
191
  return keys;
222
192
  }
223
193
  case Scope::Disk:
224
- case Scope::Secure: {
225
- const int scopeValue = static_cast<int>(s);
226
- ensureKeyIndexHydrated(scopeValue);
227
- std::lock_guard<std::mutex> lock(keyIndexMutex_);
228
- std::vector<std::string> keys;
229
- auto indexIt = keyIndex_.find(scopeValue);
230
- if (indexIt == keyIndex_.end()) {
231
- return keys;
232
- }
233
- keys.reserve(indexIt->second.size());
234
- for (const auto& key : indexIt->second) {
235
- if (key.rfind(prefix, 0) == 0) {
236
- keys.push_back(key);
237
- }
238
- }
239
- return keys;
240
- }
194
+ ensureAdapter();
195
+ return runAdapterOperation(
196
+ [&] { return nativeAdapter_->getKeysByPrefixDisk(prefix); }, "Disk getKeysByPrefix");
197
+ case Scope::Secure:
198
+ ensureAdapter();
199
+ return runAdapterOperation(
200
+ [&] { return nativeAdapter_->getKeysByPrefixSecure(prefix); }, "Secure getKeysByPrefix");
241
201
  }
242
202
  return {};
243
203
  }
@@ -251,20 +211,46 @@ double HybridStorage::size(double scope) {
251
211
  return static_cast<double>(memoryStore_.size());
252
212
  }
253
213
  case Scope::Disk:
254
- case Scope::Secure: {
255
- const int scopeValue = static_cast<int>(s);
256
- ensureKeyIndexHydrated(scopeValue);
257
- std::lock_guard<std::mutex> lock(keyIndexMutex_);
258
- auto indexIt = keyIndex_.find(scopeValue);
259
- if (indexIt == keyIndex_.end()) {
260
- return 0.0;
261
- }
262
- return static_cast<double>(indexIt->second.size());
263
- }
214
+ ensureAdapter();
215
+ return static_cast<double>(runAdapterOperation(
216
+ [&] { return nativeAdapter_->sizeDisk(); }, "Disk size"));
217
+ case Scope::Secure:
218
+ ensureAdapter();
219
+ return static_cast<double>(runAdapterOperation(
220
+ [&] { return nativeAdapter_->sizeSecure(); }, "Secure size"));
264
221
  }
265
222
  return 0.0;
266
223
  }
267
224
 
225
+ size_t HybridStorage::getExternalMemorySize() noexcept {
226
+ return memorySize();
227
+ }
228
+
229
+ size_t HybridStorage::memorySize() noexcept {
230
+ size_t total = 0;
231
+ {
232
+ std::lock_guard<std::mutex> lock(memoryMutex_);
233
+ total += mapMemorySize(memoryStore_);
234
+ for (const auto& [key, value] : memoryStore_) {
235
+ total += key.capacity() * sizeof(std::string::value_type);
236
+ total += value.capacity() * sizeof(std::string::value_type);
237
+ }
238
+ }
239
+ {
240
+ std::lock_guard<std::mutex> lock(listenersMutex_);
241
+ total += mapMemorySize(listeners_);
242
+ for (const auto& [scope, listeners] : listeners_) {
243
+ (void)scope;
244
+ total += listeners.capacity() * sizeof(Listener);
245
+ }
246
+ }
247
+ // The adapter interface exposes no memory-sizing contract, so platform
248
+ // preference/keychain caches are intentionally outside this bounded
249
+ // HybridStorage-owned estimate. std::function target allocations are also
250
+ // opaque; the retained Listener wrapper itself is counted above.
251
+ return total;
252
+ }
253
+
268
254
  std::function<void()> HybridStorage::addOnChange(
269
255
  double scope,
270
256
  const std::function<void(const std::string&, const std::optional<std::string>&)>& callback
@@ -317,27 +303,16 @@ void HybridStorage::clear(double scope) {
317
303
  }
318
304
  case Scope::Disk:
319
305
  ensureAdapter();
320
- try {
321
- nativeAdapter_->clearDisk();
322
- } catch (const std::exception&) {
323
- throw;
324
- } catch (...) {
325
- throw std::runtime_error("NitroStorage: Disk clear failed (unknown error)");
326
- }
306
+ runAdapterOperation(
307
+ [&] { nativeAdapter_->clearDisk(); }, "Disk clear");
327
308
  break;
328
309
  case Scope::Secure:
329
310
  ensureAdapter();
330
- try {
331
- nativeAdapter_->clearSecure();
332
- } catch (const std::exception&) {
333
- throw;
334
- } catch (...) {
335
- throw std::runtime_error("NitroStorage: Secure clear failed (unknown error)");
336
- }
311
+ runAdapterOperation(
312
+ [&] { nativeAdapter_->clearSecure(); }, "Secure clear");
337
313
  break;
338
314
  }
339
315
 
340
- onScopeClear(static_cast<int>(s));
341
316
  notifyListeners(static_cast<int>(s), kClearSentinelKey, std::nullopt);
342
317
  }
343
318
 
@@ -358,38 +333,25 @@ void HybridStorage::setBatch(const std::vector<std::string>& keys, const std::ve
358
333
  }
359
334
  case Scope::Disk:
360
335
  ensureAdapter();
361
- try {
362
- nativeAdapter_->setDiskBatch(keys, values);
363
- } catch (const std::exception&) {
364
- throw;
365
- } catch (...) {
366
- throw std::runtime_error("NitroStorage: Disk setBatch failed (unknown error)");
367
- }
336
+ runAdapterOperation(
337
+ [&] { nativeAdapter_->setDiskBatch(keys, values); }, "Disk setBatch");
368
338
  break;
369
339
  case Scope::Secure:
370
340
  ensureAdapter();
371
- try {
372
- nativeAdapter_->setSecureBatch(keys, values);
373
- } catch (const std::exception&) {
374
- throw;
375
- } catch (...) {
376
- throw std::runtime_error("NitroStorage: Secure setBatch failed (unknown error)");
377
- }
341
+ runAdapterOperation(
342
+ [&] { nativeAdapter_->setSecureBatch(keys, values); }, "Secure setBatch");
378
343
  break;
379
344
  }
380
345
 
381
346
  const auto scopeValue = static_cast<int>(s);
382
- for (const auto& key : keys) {
383
- onKeySet(scopeValue, key);
384
- }
385
347
  const auto listeners = copyListenersForScope(scopeValue);
386
348
  for (size_t i = 0; i < keys.size(); ++i) {
387
349
  notifyListeners(listeners, keys[i], values[i]);
388
350
  }
389
351
  }
390
352
 
391
- std::vector<std::string> HybridStorage::getBatch(const std::vector<std::string>& keys, double scope) {
392
- std::vector<std::string> results;
353
+ std::vector<std::optional<std::string>> HybridStorage::getBatch(const std::vector<std::string>& keys, double scope) {
354
+ std::vector<std::optional<std::string>> results;
393
355
  results.reserve(keys.size());
394
356
 
395
357
  Scope s = toScope(scope);
@@ -402,42 +364,20 @@ std::vector<std::string> HybridStorage::getBatch(const std::vector<std::string>&
402
364
  if (it != memoryStore_.end()) {
403
365
  results.push_back(it->second);
404
366
  } else {
405
- results.push_back(kBatchMissingSentinel);
367
+ results.push_back(std::nullopt);
406
368
  }
407
369
  }
408
370
  return results;
409
371
  }
410
372
  case Scope::Disk: {
411
373
  ensureAdapter();
412
- std::vector<std::optional<std::string>> values;
413
- try {
414
- values = nativeAdapter_->getDiskBatch(keys);
415
- } catch (const std::exception&) {
416
- throw;
417
- } catch (...) {
418
- throw std::runtime_error("NitroStorage: Disk getBatch failed (unknown error)");
419
- }
420
-
421
- for (const auto& value : values) {
422
- results.push_back(value.has_value() ? *value : std::string(kBatchMissingSentinel));
423
- }
424
- return results;
374
+ return runAdapterOperation(
375
+ [&] { return nativeAdapter_->getDiskBatch(keys); }, "Disk getBatch");
425
376
  }
426
377
  case Scope::Secure: {
427
378
  ensureAdapter();
428
- std::vector<std::optional<std::string>> values;
429
- try {
430
- values = nativeAdapter_->getSecureBatch(keys);
431
- } catch (const std::exception&) {
432
- throw;
433
- } catch (...) {
434
- throw std::runtime_error("NitroStorage: Secure getBatch failed (unknown error)");
435
- }
436
-
437
- for (const auto& value : values) {
438
- results.push_back(value.has_value() ? *value : std::string(kBatchMissingSentinel));
439
- }
440
- return results;
379
+ return runAdapterOperation(
380
+ [&] { return nativeAdapter_->getSecureBatch(keys); }, "Secure getBatch");
441
381
  }
442
382
  }
443
383
 
@@ -457,30 +397,17 @@ void HybridStorage::removeBatch(const std::vector<std::string>& keys, double sco
457
397
  }
458
398
  case Scope::Disk:
459
399
  ensureAdapter();
460
- try {
461
- nativeAdapter_->deleteDiskBatch(keys);
462
- } catch (const std::exception&) {
463
- throw;
464
- } catch (...) {
465
- throw std::runtime_error("NitroStorage: Disk removeBatch failed (unknown error)");
466
- }
400
+ runAdapterOperation(
401
+ [&] { nativeAdapter_->deleteDiskBatch(keys); }, "Disk removeBatch");
467
402
  break;
468
403
  case Scope::Secure:
469
404
  ensureAdapter();
470
- try {
471
- nativeAdapter_->deleteSecureBatch(keys);
472
- } catch (const std::exception&) {
473
- throw;
474
- } catch (...) {
475
- throw std::runtime_error("NitroStorage: Secure removeBatch failed (unknown error)");
476
- }
405
+ runAdapterOperation(
406
+ [&] { nativeAdapter_->deleteSecureBatch(keys); }, "Secure removeBatch");
477
407
  break;
478
408
  }
479
409
 
480
410
  const auto scopeValue = static_cast<int>(s);
481
- for (const auto& key : keys) {
482
- onKeyRemove(scopeValue, key);
483
- }
484
411
  const auto listeners = copyListenersForScope(scopeValue);
485
412
  for (const auto& key : keys) {
486
413
  notifyListeners(listeners, key, std::nullopt);
@@ -550,43 +477,23 @@ void HybridStorage::setSecureBiometricWithLevel(const std::string& key, const st
550
477
  "NitroStorage: Invalid biometric level");
551
478
  }
552
479
  ensureAdapter();
553
- try {
554
- nativeAdapter_->setSecureBiometricWithLevel(
555
- key,
556
- value,
557
- intLevel
558
- );
559
- onKeySet(static_cast<int>(Scope::Secure), key);
560
- notifyListeners(static_cast<int>(Scope::Secure), key, value);
561
- } catch (const std::exception&) {
562
- throw;
563
- } catch (...) {
564
- throw std::runtime_error("NitroStorage: Biometric set failed (unknown error)");
565
- }
480
+ runAdapterOperation(
481
+ [&] { nativeAdapter_->setSecureBiometricWithLevel(key, value, intLevel); },
482
+ "Biometric set");
483
+ notifyListeners(static_cast<int>(Scope::Secure), key, value);
566
484
  }
567
485
 
568
486
  std::optional<std::string> HybridStorage::getSecureBiometric(const std::string& key) {
569
487
  ensureAdapter();
570
- try {
571
- return nativeAdapter_->getSecureBiometric(key);
572
- } catch (const std::exception&) {
573
- throw;
574
- } catch (...) {
575
- throw std::runtime_error("NitroStorage: Biometric get failed (unknown error)");
576
- }
488
+ return runAdapterOperation(
489
+ [&] { return nativeAdapter_->getSecureBiometric(key); }, "Biometric get");
577
490
  }
578
491
 
579
492
  void HybridStorage::deleteSecureBiometric(const std::string& key) {
580
493
  ensureAdapter();
581
- try {
582
- nativeAdapter_->deleteSecureBiometric(key);
583
- onKeyRemove(static_cast<int>(Scope::Secure), key);
584
- notifyListeners(static_cast<int>(Scope::Secure), key, std::nullopt);
585
- } catch (const std::exception&) {
586
- throw;
587
- } catch (...) {
588
- throw std::runtime_error("NitroStorage: Biometric delete failed (unknown error)");
589
- }
494
+ runAdapterOperation(
495
+ [&] { nativeAdapter_->deleteSecureBiometric(key); }, "Biometric delete");
496
+ notifyListeners(static_cast<int>(Scope::Secure), key, std::nullopt);
590
497
  }
591
498
 
592
499
  bool HybridStorage::hasSecureBiometric(const std::string& key) {
@@ -596,22 +503,9 @@ bool HybridStorage::hasSecureBiometric(const std::string& key) {
596
503
 
597
504
  void HybridStorage::clearSecureBiometric() {
598
505
  ensureAdapter();
599
- try {
600
- nativeAdapter_->clearSecureBiometric();
601
- // Invalidate the secure key index so next access re-hydrates from native adapter
602
- // (which will now correctly exclude the cleared biometric keys).
603
- // We do NOT call onScopeClear() here because that would also clear the index
604
- // contents for regular secure keys; marking stale is sufficient.
605
- {
606
- std::lock_guard<std::mutex> lock(keyIndexMutex_);
607
- keyIndexHydrated_[static_cast<int>(Scope::Secure)] = false;
608
- }
609
- notifyListeners(static_cast<int>(Scope::Secure), kClearSentinelKey, std::nullopt);
610
- } catch (const std::exception&) {
611
- throw;
612
- } catch (...) {
613
- throw std::runtime_error("NitroStorage: Biometric clear failed (unknown error)");
614
- }
506
+ runAdapterOperation(
507
+ [&] { nativeAdapter_->clearSecureBiometric(); }, "Biometric clear");
508
+ notifyListeners(static_cast<int>(Scope::Secure), kClearSentinelKey, std::nullopt);
615
509
  }
616
510
 
617
511
  // --- Internal ---
@@ -658,84 +552,6 @@ void HybridStorage::notifyListeners(
658
552
  notifyListeners(listeners, key, value);
659
553
  }
660
554
 
661
- std::vector<std::string> HybridStorage::toVector(const std::unordered_set<std::string>& keys) {
662
- std::vector<std::string> values;
663
- values.reserve(keys.size());
664
- for (const auto& key : keys) {
665
- values.push_back(key);
666
- }
667
- return values;
668
- }
669
-
670
- void HybridStorage::ensureKeyIndexHydrated(int scope) {
671
- if (scope != static_cast<int>(Scope::Disk) && scope != static_cast<int>(Scope::Secure)) {
672
- return;
673
- }
674
-
675
- std::lock_guard<std::mutex> lock(keyIndexMutex_);
676
- auto hydratedIt = keyIndexHydrated_.find(scope);
677
- if (hydratedIt != keyIndexHydrated_.end() && hydratedIt->second) {
678
- return;
679
- }
680
-
681
- ensureAdapter();
682
- std::vector<std::string> keys;
683
- try {
684
- if (scope == static_cast<int>(Scope::Disk)) {
685
- keys = nativeAdapter_->getAllKeysDisk();
686
- } else {
687
- keys = nativeAdapter_->getAllKeysSecure();
688
- }
689
- } catch (const std::exception&) {
690
- throw;
691
- } catch (...) {
692
- throw std::runtime_error("NitroStorage: Key index hydration failed (unknown error)");
693
- }
694
-
695
- auto& index = keyIndex_[scope];
696
- index.clear();
697
- for (const auto& key : keys) {
698
- index.insert(key);
699
- }
700
- keyIndexHydrated_[scope] = true;
701
- }
702
-
703
- void HybridStorage::onKeySet(int scope, const std::string& key) {
704
- if (scope != static_cast<int>(Scope::Disk) && scope != static_cast<int>(Scope::Secure)) {
705
- return;
706
- }
707
-
708
- std::lock_guard<std::mutex> lock(keyIndexMutex_);
709
- auto hydratedIt = keyIndexHydrated_.find(scope);
710
- if (hydratedIt != keyIndexHydrated_.end() && hydratedIt->second) {
711
- keyIndex_[scope].insert(key);
712
- }
713
- }
714
-
715
- void HybridStorage::onKeyRemove(int scope, const std::string& key) {
716
- if (scope != static_cast<int>(Scope::Disk) && scope != static_cast<int>(Scope::Secure)) {
717
- return;
718
- }
719
-
720
- std::lock_guard<std::mutex> lock(keyIndexMutex_);
721
- auto hydratedIt = keyIndexHydrated_.find(scope);
722
- if (hydratedIt != keyIndexHydrated_.end() && hydratedIt->second) {
723
- keyIndex_[scope].erase(key);
724
- }
725
- }
726
-
727
- void HybridStorage::onScopeClear(int scope) {
728
- if (scope != static_cast<int>(Scope::Disk) && scope != static_cast<int>(Scope::Secure)) {
729
- return;
730
- }
731
-
732
- std::lock_guard<std::mutex> lock(keyIndexMutex_);
733
- auto hydratedIt = keyIndexHydrated_.find(scope);
734
- if (hydratedIt != keyIndexHydrated_.end() && hydratedIt->second) {
735
- keyIndex_[scope].clear();
736
- }
737
- }
738
-
739
555
  void HybridStorage::ensureAdapter() const {
740
556
  if (!nativeAdapter_) {
741
557
  throw std::runtime_error("NitroStorage: Native adapter not initialized");
@@ -10,9 +10,9 @@
10
10
  #include <functional>
11
11
  #include <memory>
12
12
  #include <vector>
13
- #include <unordered_set>
14
13
  #include <atomic>
15
14
  #include <array>
15
+ #include <stdexcept>
16
16
 
17
17
  namespace margelo::nitro::NitroStorage {
18
18
 
@@ -38,8 +38,9 @@ public:
38
38
  std::vector<std::string> getAllKeys(double scope) override;
39
39
  std::vector<std::string> getKeysByPrefix(const std::string& prefix, double scope) override;
40
40
  double size(double scope) override;
41
+ size_t getExternalMemorySize() noexcept override;
41
42
  void setBatch(const std::vector<std::string>& keys, const std::vector<std::string>& values, double scope) override;
42
- std::vector<std::string> getBatch(const std::vector<std::string>& keys, double scope) override;
43
+ std::vector<std::optional<std::string>> getBatch(const std::vector<std::string>& keys, double scope) override;
43
44
  void removeBatch(const std::vector<std::string>& keys, double scope) override;
44
45
  void removeByPrefix(const std::string& prefix, double scope) override;
45
46
  std::function<void()> addOnChange(
@@ -79,9 +80,6 @@ private:
79
80
  // Lock-free fast path: skip locking/copying the listener vector on the
80
81
  // write/notify path when no listeners are registered for a scope.
81
82
  std::array<std::atomic<size_t>, 3> listenerScopeCounts_{};
82
- HybridStorageMap<int, std::unordered_set<std::string>> keyIndex_;
83
- HybridStorageMap<int, bool> keyIndexHydrated_;
84
- std::mutex keyIndexMutex_;
85
83
 
86
84
  std::vector<Listener> copyListenersForScope(int scope);
87
85
  void notifyListeners(
@@ -90,13 +88,22 @@ private:
90
88
  const std::optional<std::string>& value
91
89
  );
92
90
  void notifyListeners(int scope, const std::string& key, const std::optional<std::string>& value);
93
- std::vector<std::string> toVector(const std::unordered_set<std::string>& keys);
94
- void ensureKeyIndexHydrated(int scope);
95
- void onKeySet(int scope, const std::string& key);
96
- void onKeyRemove(int scope, const std::string& key);
97
- void onScopeClear(int scope);
98
91
  void ensureAdapter() const;
99
92
  Scope toScope(double scopeValue);
93
+ size_t memorySize() noexcept;
94
+
95
+ template <typename TOperation>
96
+ auto runAdapterOperation(TOperation&& operation, const char* description) -> decltype(operation()) {
97
+ try {
98
+ return operation();
99
+ } catch (const std::exception&) {
100
+ throw;
101
+ } catch (...) {
102
+ throw std::runtime_error(
103
+ std::string("NitroStorage: ") + description + " failed (unknown error)"
104
+ );
105
+ }
106
+ }
100
107
 
101
108
  static constexpr const char* kClearSentinelKey = "";
102
109
  };