react-native-nitro-storage 0.9.0 → 0.10.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (77) hide show
  1. package/CHANGELOG.md +101 -29
  2. package/README.md +83 -26
  3. package/android/build.gradle +3 -1
  4. package/android/src/main/cpp/AndroidStorageAdapterCpp.cpp +5 -2
  5. package/android/src/main/java/com/nitrostorage/AndroidStorageAdapter.kt +240 -34
  6. package/cpp/bindings/HybridStorage.cpp +106 -283
  7. package/cpp/bindings/HybridStorage.hpp +16 -9
  8. package/docs/api-reference.md +75 -19
  9. package/docs/benchmarks.md +6 -1
  10. package/docs/keychain-lifecycle-testing.md +36 -0
  11. package/docs/recipes.md +1 -2
  12. package/docs/secure-storage.md +45 -9
  13. package/ios/IOSStorageAdapterCpp.mm +391 -175
  14. package/lib/commonjs/capabilities.js +3 -1
  15. package/lib/commonjs/capabilities.js.map +1 -1
  16. package/lib/commonjs/core/durability.js +111 -45
  17. package/lib/commonjs/core/durability.js.map +1 -1
  18. package/lib/commonjs/index.js +15 -5
  19. package/lib/commonjs/index.js.map +1 -1
  20. package/lib/commonjs/index.web.js +219 -97
  21. package/lib/commonjs/index.web.js.map +1 -1
  22. package/lib/commonjs/internal.js +3 -11
  23. package/lib/commonjs/internal.js.map +1 -1
  24. package/lib/commonjs/shared.js +62 -2
  25. package/lib/commonjs/shared.js.map +1 -1
  26. package/lib/commonjs/storage-core.js +883 -163
  27. package/lib/commonjs/storage-core.js.map +1 -1
  28. package/lib/commonjs/storage-runtime.js +5 -1
  29. package/lib/commonjs/storage-runtime.js.map +1 -1
  30. package/lib/commonjs/testing.js +13 -0
  31. package/lib/commonjs/testing.js.map +1 -1
  32. package/lib/module/capabilities.js +2 -1
  33. package/lib/module/capabilities.js.map +1 -1
  34. package/lib/module/core/durability.js +111 -45
  35. package/lib/module/core/durability.js.map +1 -1
  36. package/lib/module/index.js +12 -8
  37. package/lib/module/index.js.map +1 -1
  38. package/lib/module/index.web.js +215 -99
  39. package/lib/module/index.web.js.map +1 -1
  40. package/lib/module/internal.js +2 -9
  41. package/lib/module/internal.js.map +1 -1
  42. package/lib/module/shared.js +60 -2
  43. package/lib/module/shared.js.map +1 -1
  44. package/lib/module/storage-core.js +884 -164
  45. package/lib/module/storage-core.js.map +1 -1
  46. package/lib/module/storage-runtime.js +4 -1
  47. package/lib/module/storage-runtime.js.map +1 -1
  48. package/lib/module/testing.js +8 -1
  49. package/lib/module/testing.js.map +1 -1
  50. package/lib/typescript/capabilities.d.ts +2 -1
  51. package/lib/typescript/capabilities.d.ts.map +1 -1
  52. package/lib/typescript/core/durability.d.ts +8 -4
  53. package/lib/typescript/core/durability.d.ts.map +1 -1
  54. package/lib/typescript/index.d.ts +2 -2
  55. package/lib/typescript/index.d.ts.map +1 -1
  56. package/lib/typescript/index.web.d.ts +3 -29
  57. package/lib/typescript/index.web.d.ts.map +1 -1
  58. package/lib/typescript/internal.d.ts +0 -2
  59. package/lib/typescript/internal.d.ts.map +1 -1
  60. package/lib/typescript/shared.d.ts +19 -0
  61. package/lib/typescript/shared.d.ts.map +1 -1
  62. package/lib/typescript/storage-core.d.ts +15 -6
  63. package/lib/typescript/storage-core.d.ts.map +1 -1
  64. package/lib/typescript/storage-runtime.d.ts +2 -1
  65. package/lib/typescript/storage-runtime.d.ts.map +1 -1
  66. package/lib/typescript/testing.d.ts +1 -1
  67. package/lib/typescript/testing.d.ts.map +1 -1
  68. package/package.json +2 -2
  69. package/src/capabilities.ts +3 -1
  70. package/src/core/durability.ts +140 -43
  71. package/src/index.ts +20 -10
  72. package/src/index.web.ts +328 -166
  73. package/src/internal.ts +0 -14
  74. package/src/shared.ts +85 -0
  75. package/src/storage-core.ts +1198 -232
  76. package/src/storage-runtime.ts +6 -0
  77. package/src/testing.ts +8 -1
@@ -15,6 +15,15 @@ namespace margelo::nitro::NitroStorage {
15
15
 
16
16
  namespace {
17
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
+ }
18
27
  } // namespace
19
28
 
20
29
  HybridStorage::HybridStorage()
@@ -56,27 +65,16 @@ void HybridStorage::set(const std::string& key, const std::string& value, double
56
65
  }
57
66
  case Scope::Disk:
58
67
  ensureAdapter();
59
- try {
60
- nativeAdapter_->setDisk(key, value);
61
- } catch (const std::exception&) {
62
- throw;
63
- } catch (...) {
64
- throw std::runtime_error("NitroStorage: Disk set failed (unknown error)");
65
- }
68
+ runAdapterOperation(
69
+ [&] { nativeAdapter_->setDisk(key, value); }, "Disk set");
66
70
  break;
67
71
  case Scope::Secure:
68
72
  ensureAdapter();
69
- try {
70
- nativeAdapter_->setSecure(key, value);
71
- } catch (const std::exception&) {
72
- throw;
73
- } catch (...) {
74
- throw std::runtime_error("NitroStorage: Secure set failed (unknown error)");
75
- }
73
+ runAdapterOperation(
74
+ [&] { nativeAdapter_->setSecure(key, value); }, "Secure set");
76
75
  break;
77
76
  }
78
77
 
79
- onKeySet(static_cast<int>(s), key);
80
78
  notifyListeners(static_cast<int>(s), key, value);
81
79
  }
82
80
 
@@ -94,22 +92,12 @@ std::optional<std::string> HybridStorage::get(const std::string& key, double sco
94
92
  }
95
93
  case Scope::Disk:
96
94
  ensureAdapter();
97
- try {
98
- return nativeAdapter_->getDisk(key);
99
- } catch (const std::exception&) {
100
- throw;
101
- } catch (...) {
102
- throw std::runtime_error("NitroStorage: Disk get failed (unknown error)");
103
- }
95
+ return runAdapterOperation(
96
+ [&] { return nativeAdapter_->getDisk(key); }, "Disk get");
104
97
  case Scope::Secure:
105
98
  ensureAdapter();
106
- try {
107
- return nativeAdapter_->getSecure(key);
108
- } catch (const std::exception&) {
109
- throw;
110
- } catch (...) {
111
- throw std::runtime_error("NitroStorage: Secure get failed (unknown error)");
112
- }
99
+ return runAdapterOperation(
100
+ [&] { return nativeAdapter_->getSecure(key); }, "Secure get");
113
101
  }
114
102
 
115
103
  return std::nullopt;
@@ -126,27 +114,16 @@ void HybridStorage::remove(const std::string& key, double scope) {
126
114
  }
127
115
  case Scope::Disk:
128
116
  ensureAdapter();
129
- try {
130
- nativeAdapter_->deleteDisk(key);
131
- } catch (const std::exception&) {
132
- throw;
133
- } catch (...) {
134
- throw std::runtime_error("NitroStorage: Disk delete failed (unknown error)");
135
- }
117
+ runAdapterOperation(
118
+ [&] { nativeAdapter_->deleteDisk(key); }, "Disk delete");
136
119
  break;
137
120
  case Scope::Secure:
138
121
  ensureAdapter();
139
- try {
140
- nativeAdapter_->deleteSecure(key);
141
- } catch (const std::exception&) {
142
- throw;
143
- } catch (...) {
144
- throw std::runtime_error("NitroStorage: Secure delete failed (unknown error)");
145
- }
122
+ runAdapterOperation(
123
+ [&] { nativeAdapter_->deleteSecure(key); }, "Secure delete");
146
124
  break;
147
125
  }
148
126
 
149
- onKeyRemove(static_cast<int>(s), key);
150
127
  notifyListeners(static_cast<int>(s), key, std::nullopt);
151
128
  }
152
129
 
@@ -159,16 +136,13 @@ bool HybridStorage::has(const std::string& key, double scope) {
159
136
  return memoryStore_.find(key) != memoryStore_.end();
160
137
  }
161
138
  case Scope::Disk:
162
- case Scope::Secure: {
163
- const int scopeValue = static_cast<int>(s);
164
- ensureKeyIndexHydrated(scopeValue);
165
- std::lock_guard<std::mutex> lock(keyIndexMutex_);
166
- auto indexIt = keyIndex_.find(scopeValue);
167
- if (indexIt == keyIndex_.end()) {
168
- return false;
169
- }
170
- return indexIt->second.count(key) > 0;
171
- }
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");
172
146
  }
173
147
  return false;
174
148
  }
@@ -187,16 +161,13 @@ std::vector<std::string> HybridStorage::getAllKeys(double scope) {
187
161
  return keys;
188
162
  }
189
163
  case Scope::Disk:
190
- case Scope::Secure: {
191
- const int scopeValue = static_cast<int>(s);
192
- ensureKeyIndexHydrated(scopeValue);
193
- std::lock_guard<std::mutex> lock(keyIndexMutex_);
194
- auto indexIt = keyIndex_.find(scopeValue);
195
- if (indexIt == keyIndex_.end()) {
196
- return {};
197
- }
198
- return toVector(indexIt->second);
199
- }
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");
200
171
  }
201
172
  return {};
202
173
  }
@@ -220,23 +191,13 @@ std::vector<std::string> HybridStorage::getKeysByPrefix(const std::string& prefi
220
191
  return keys;
221
192
  }
222
193
  case Scope::Disk:
223
- case Scope::Secure: {
224
- const int scopeValue = static_cast<int>(s);
225
- ensureKeyIndexHydrated(scopeValue);
226
- std::lock_guard<std::mutex> lock(keyIndexMutex_);
227
- std::vector<std::string> keys;
228
- auto indexIt = keyIndex_.find(scopeValue);
229
- if (indexIt == keyIndex_.end()) {
230
- return keys;
231
- }
232
- keys.reserve(indexIt->second.size());
233
- for (const auto& key : indexIt->second) {
234
- if (key.rfind(prefix, 0) == 0) {
235
- keys.push_back(key);
236
- }
237
- }
238
- return keys;
239
- }
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");
240
201
  }
241
202
  return {};
242
203
  }
@@ -250,20 +211,46 @@ double HybridStorage::size(double scope) {
250
211
  return static_cast<double>(memoryStore_.size());
251
212
  }
252
213
  case Scope::Disk:
253
- case Scope::Secure: {
254
- const int scopeValue = static_cast<int>(s);
255
- ensureKeyIndexHydrated(scopeValue);
256
- std::lock_guard<std::mutex> lock(keyIndexMutex_);
257
- auto indexIt = keyIndex_.find(scopeValue);
258
- if (indexIt == keyIndex_.end()) {
259
- return 0.0;
260
- }
261
- return static_cast<double>(indexIt->second.size());
262
- }
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"));
263
221
  }
264
222
  return 0.0;
265
223
  }
266
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
+
267
254
  std::function<void()> HybridStorage::addOnChange(
268
255
  double scope,
269
256
  const std::function<void(const std::string&, const std::optional<std::string>&)>& callback
@@ -316,27 +303,16 @@ void HybridStorage::clear(double scope) {
316
303
  }
317
304
  case Scope::Disk:
318
305
  ensureAdapter();
319
- try {
320
- nativeAdapter_->clearDisk();
321
- } catch (const std::exception&) {
322
- throw;
323
- } catch (...) {
324
- throw std::runtime_error("NitroStorage: Disk clear failed (unknown error)");
325
- }
306
+ runAdapterOperation(
307
+ [&] { nativeAdapter_->clearDisk(); }, "Disk clear");
326
308
  break;
327
309
  case Scope::Secure:
328
310
  ensureAdapter();
329
- try {
330
- nativeAdapter_->clearSecure();
331
- } catch (const std::exception&) {
332
- throw;
333
- } catch (...) {
334
- throw std::runtime_error("NitroStorage: Secure clear failed (unknown error)");
335
- }
311
+ runAdapterOperation(
312
+ [&] { nativeAdapter_->clearSecure(); }, "Secure clear");
336
313
  break;
337
314
  }
338
315
 
339
- onScopeClear(static_cast<int>(s));
340
316
  notifyListeners(static_cast<int>(s), kClearSentinelKey, std::nullopt);
341
317
  }
342
318
 
@@ -357,30 +333,17 @@ void HybridStorage::setBatch(const std::vector<std::string>& keys, const std::ve
357
333
  }
358
334
  case Scope::Disk:
359
335
  ensureAdapter();
360
- try {
361
- nativeAdapter_->setDiskBatch(keys, values);
362
- } catch (const std::exception&) {
363
- throw;
364
- } catch (...) {
365
- throw std::runtime_error("NitroStorage: Disk setBatch failed (unknown error)");
366
- }
336
+ runAdapterOperation(
337
+ [&] { nativeAdapter_->setDiskBatch(keys, values); }, "Disk setBatch");
367
338
  break;
368
339
  case Scope::Secure:
369
340
  ensureAdapter();
370
- try {
371
- nativeAdapter_->setSecureBatch(keys, values);
372
- } catch (const std::exception&) {
373
- throw;
374
- } catch (...) {
375
- throw std::runtime_error("NitroStorage: Secure setBatch failed (unknown error)");
376
- }
341
+ runAdapterOperation(
342
+ [&] { nativeAdapter_->setSecureBatch(keys, values); }, "Secure setBatch");
377
343
  break;
378
344
  }
379
345
 
380
346
  const auto scopeValue = static_cast<int>(s);
381
- for (const auto& key : keys) {
382
- onKeySet(scopeValue, key);
383
- }
384
347
  const auto listeners = copyListenersForScope(scopeValue);
385
348
  for (size_t i = 0; i < keys.size(); ++i) {
386
349
  notifyListeners(listeners, keys[i], values[i]);
@@ -408,29 +371,13 @@ std::vector<std::optional<std::string>> HybridStorage::getBatch(const std::vecto
408
371
  }
409
372
  case Scope::Disk: {
410
373
  ensureAdapter();
411
- std::vector<std::optional<std::string>> values;
412
- try {
413
- values = nativeAdapter_->getDiskBatch(keys);
414
- } catch (const std::exception&) {
415
- throw;
416
- } catch (...) {
417
- throw std::runtime_error("NitroStorage: Disk getBatch failed (unknown error)");
418
- }
419
-
420
- return values;
374
+ return runAdapterOperation(
375
+ [&] { return nativeAdapter_->getDiskBatch(keys); }, "Disk getBatch");
421
376
  }
422
377
  case Scope::Secure: {
423
378
  ensureAdapter();
424
- std::vector<std::optional<std::string>> values;
425
- try {
426
- values = nativeAdapter_->getSecureBatch(keys);
427
- } catch (const std::exception&) {
428
- throw;
429
- } catch (...) {
430
- throw std::runtime_error("NitroStorage: Secure getBatch failed (unknown error)");
431
- }
432
-
433
- return values;
379
+ return runAdapterOperation(
380
+ [&] { return nativeAdapter_->getSecureBatch(keys); }, "Secure getBatch");
434
381
  }
435
382
  }
436
383
 
@@ -450,30 +397,17 @@ void HybridStorage::removeBatch(const std::vector<std::string>& keys, double sco
450
397
  }
451
398
  case Scope::Disk:
452
399
  ensureAdapter();
453
- try {
454
- nativeAdapter_->deleteDiskBatch(keys);
455
- } catch (const std::exception&) {
456
- throw;
457
- } catch (...) {
458
- throw std::runtime_error("NitroStorage: Disk removeBatch failed (unknown error)");
459
- }
400
+ runAdapterOperation(
401
+ [&] { nativeAdapter_->deleteDiskBatch(keys); }, "Disk removeBatch");
460
402
  break;
461
403
  case Scope::Secure:
462
404
  ensureAdapter();
463
- try {
464
- nativeAdapter_->deleteSecureBatch(keys);
465
- } catch (const std::exception&) {
466
- throw;
467
- } catch (...) {
468
- throw std::runtime_error("NitroStorage: Secure removeBatch failed (unknown error)");
469
- }
405
+ runAdapterOperation(
406
+ [&] { nativeAdapter_->deleteSecureBatch(keys); }, "Secure removeBatch");
470
407
  break;
471
408
  }
472
409
 
473
410
  const auto scopeValue = static_cast<int>(s);
474
- for (const auto& key : keys) {
475
- onKeyRemove(scopeValue, key);
476
- }
477
411
  const auto listeners = copyListenersForScope(scopeValue);
478
412
  for (const auto& key : keys) {
479
413
  notifyListeners(listeners, key, std::nullopt);
@@ -543,43 +477,23 @@ void HybridStorage::setSecureBiometricWithLevel(const std::string& key, const st
543
477
  "NitroStorage: Invalid biometric level");
544
478
  }
545
479
  ensureAdapter();
546
- try {
547
- nativeAdapter_->setSecureBiometricWithLevel(
548
- key,
549
- value,
550
- intLevel
551
- );
552
- onKeySet(static_cast<int>(Scope::Secure), key);
553
- notifyListeners(static_cast<int>(Scope::Secure), key, value);
554
- } catch (const std::exception&) {
555
- throw;
556
- } catch (...) {
557
- throw std::runtime_error("NitroStorage: Biometric set failed (unknown error)");
558
- }
480
+ runAdapterOperation(
481
+ [&] { nativeAdapter_->setSecureBiometricWithLevel(key, value, intLevel); },
482
+ "Biometric set");
483
+ notifyListeners(static_cast<int>(Scope::Secure), key, value);
559
484
  }
560
485
 
561
486
  std::optional<std::string> HybridStorage::getSecureBiometric(const std::string& key) {
562
487
  ensureAdapter();
563
- try {
564
- return nativeAdapter_->getSecureBiometric(key);
565
- } catch (const std::exception&) {
566
- throw;
567
- } catch (...) {
568
- throw std::runtime_error("NitroStorage: Biometric get failed (unknown error)");
569
- }
488
+ return runAdapterOperation(
489
+ [&] { return nativeAdapter_->getSecureBiometric(key); }, "Biometric get");
570
490
  }
571
491
 
572
492
  void HybridStorage::deleteSecureBiometric(const std::string& key) {
573
493
  ensureAdapter();
574
- try {
575
- nativeAdapter_->deleteSecureBiometric(key);
576
- onKeyRemove(static_cast<int>(Scope::Secure), key);
577
- notifyListeners(static_cast<int>(Scope::Secure), key, std::nullopt);
578
- } catch (const std::exception&) {
579
- throw;
580
- } catch (...) {
581
- throw std::runtime_error("NitroStorage: Biometric delete failed (unknown error)");
582
- }
494
+ runAdapterOperation(
495
+ [&] { nativeAdapter_->deleteSecureBiometric(key); }, "Biometric delete");
496
+ notifyListeners(static_cast<int>(Scope::Secure), key, std::nullopt);
583
497
  }
584
498
 
585
499
  bool HybridStorage::hasSecureBiometric(const std::string& key) {
@@ -589,22 +503,9 @@ bool HybridStorage::hasSecureBiometric(const std::string& key) {
589
503
 
590
504
  void HybridStorage::clearSecureBiometric() {
591
505
  ensureAdapter();
592
- try {
593
- nativeAdapter_->clearSecureBiometric();
594
- // Invalidate the secure key index so next access re-hydrates from native adapter
595
- // (which will now correctly exclude the cleared biometric keys).
596
- // We do NOT call onScopeClear() here because that would also clear the index
597
- // contents for regular secure keys; marking stale is sufficient.
598
- {
599
- std::lock_guard<std::mutex> lock(keyIndexMutex_);
600
- keyIndexHydrated_[static_cast<int>(Scope::Secure)] = false;
601
- }
602
- notifyListeners(static_cast<int>(Scope::Secure), kClearSentinelKey, std::nullopt);
603
- } catch (const std::exception&) {
604
- throw;
605
- } catch (...) {
606
- throw std::runtime_error("NitroStorage: Biometric clear failed (unknown error)");
607
- }
506
+ runAdapterOperation(
507
+ [&] { nativeAdapter_->clearSecureBiometric(); }, "Biometric clear");
508
+ notifyListeners(static_cast<int>(Scope::Secure), kClearSentinelKey, std::nullopt);
608
509
  }
609
510
 
610
511
  // --- Internal ---
@@ -651,84 +552,6 @@ void HybridStorage::notifyListeners(
651
552
  notifyListeners(listeners, key, value);
652
553
  }
653
554
 
654
- std::vector<std::string> HybridStorage::toVector(const std::unordered_set<std::string>& keys) {
655
- std::vector<std::string> values;
656
- values.reserve(keys.size());
657
- for (const auto& key : keys) {
658
- values.push_back(key);
659
- }
660
- return values;
661
- }
662
-
663
- void HybridStorage::ensureKeyIndexHydrated(int scope) {
664
- if (scope != static_cast<int>(Scope::Disk) && scope != static_cast<int>(Scope::Secure)) {
665
- return;
666
- }
667
-
668
- std::lock_guard<std::mutex> lock(keyIndexMutex_);
669
- auto hydratedIt = keyIndexHydrated_.find(scope);
670
- if (hydratedIt != keyIndexHydrated_.end() && hydratedIt->second) {
671
- return;
672
- }
673
-
674
- ensureAdapter();
675
- std::vector<std::string> keys;
676
- try {
677
- if (scope == static_cast<int>(Scope::Disk)) {
678
- keys = nativeAdapter_->getAllKeysDisk();
679
- } else {
680
- keys = nativeAdapter_->getAllKeysSecure();
681
- }
682
- } catch (const std::exception&) {
683
- throw;
684
- } catch (...) {
685
- throw std::runtime_error("NitroStorage: Key index hydration failed (unknown error)");
686
- }
687
-
688
- auto& index = keyIndex_[scope];
689
- index.clear();
690
- for (const auto& key : keys) {
691
- index.insert(key);
692
- }
693
- keyIndexHydrated_[scope] = true;
694
- }
695
-
696
- void HybridStorage::onKeySet(int scope, const std::string& key) {
697
- if (scope != static_cast<int>(Scope::Disk) && scope != static_cast<int>(Scope::Secure)) {
698
- return;
699
- }
700
-
701
- std::lock_guard<std::mutex> lock(keyIndexMutex_);
702
- auto hydratedIt = keyIndexHydrated_.find(scope);
703
- if (hydratedIt != keyIndexHydrated_.end() && hydratedIt->second) {
704
- keyIndex_[scope].insert(key);
705
- }
706
- }
707
-
708
- void HybridStorage::onKeyRemove(int scope, const std::string& key) {
709
- if (scope != static_cast<int>(Scope::Disk) && scope != static_cast<int>(Scope::Secure)) {
710
- return;
711
- }
712
-
713
- std::lock_guard<std::mutex> lock(keyIndexMutex_);
714
- auto hydratedIt = keyIndexHydrated_.find(scope);
715
- if (hydratedIt != keyIndexHydrated_.end() && hydratedIt->second) {
716
- keyIndex_[scope].erase(key);
717
- }
718
- }
719
-
720
- void HybridStorage::onScopeClear(int scope) {
721
- if (scope != static_cast<int>(Scope::Disk) && scope != static_cast<int>(Scope::Secure)) {
722
- return;
723
- }
724
-
725
- std::lock_guard<std::mutex> lock(keyIndexMutex_);
726
- auto hydratedIt = keyIndexHydrated_.find(scope);
727
- if (hydratedIt != keyIndexHydrated_.end() && hydratedIt->second) {
728
- keyIndex_[scope].clear();
729
- }
730
- }
731
-
732
555
  void HybridStorage::ensureAdapter() const {
733
556
  if (!nativeAdapter_) {
734
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,6 +38,7 @@ 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
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;
@@ -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
  };