react-native-nitro-storage 0.4.0 → 0.4.3

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 (41) hide show
  1. package/README.md +90 -0
  2. package/android/build.gradle +0 -12
  3. package/android/consumer-rules.pro +26 -4
  4. package/android/src/main/cpp/AndroidStorageAdapterCpp.cpp +7 -10
  5. package/android/src/main/cpp/AndroidStorageAdapterCpp.hpp +0 -4
  6. package/android/src/main/cpp/cpp-adapter.cpp +3 -1
  7. package/android/src/main/java/com/nitrostorage/AndroidStorageAdapter.kt +172 -77
  8. package/cpp/bindings/HybridStorage.cpp +120 -69
  9. package/cpp/bindings/HybridStorage.hpp +4 -0
  10. package/ios/IOSStorageAdapterCpp.hpp +2 -1
  11. package/ios/IOSStorageAdapterCpp.mm +264 -49
  12. package/lib/commonjs/index.js +128 -20
  13. package/lib/commonjs/index.js.map +1 -1
  14. package/lib/commonjs/index.web.js +169 -41
  15. package/lib/commonjs/index.web.js.map +1 -1
  16. package/lib/commonjs/indexeddb-backend.js +130 -0
  17. package/lib/commonjs/indexeddb-backend.js.map +1 -0
  18. package/lib/commonjs/internal.js +51 -23
  19. package/lib/commonjs/internal.js.map +1 -1
  20. package/lib/module/index.js +121 -20
  21. package/lib/module/index.js.map +1 -1
  22. package/lib/module/index.web.js +162 -41
  23. package/lib/module/index.web.js.map +1 -1
  24. package/lib/module/indexeddb-backend.js +126 -0
  25. package/lib/module/indexeddb-backend.js.map +1 -0
  26. package/lib/module/internal.js +51 -23
  27. package/lib/module/internal.js.map +1 -1
  28. package/lib/typescript/index.d.ts +6 -0
  29. package/lib/typescript/index.d.ts.map +1 -1
  30. package/lib/typescript/index.web.d.ts +7 -1
  31. package/lib/typescript/index.web.d.ts.map +1 -1
  32. package/lib/typescript/indexeddb-backend.d.ts +29 -0
  33. package/lib/typescript/indexeddb-backend.d.ts.map +1 -0
  34. package/lib/typescript/internal.d.ts.map +1 -1
  35. package/nitrogen/generated/android/NitroStorageOnLoad.cpp +22 -17
  36. package/nitrogen/generated/android/NitroStorageOnLoad.hpp +13 -4
  37. package/package.json +7 -3
  38. package/src/index.ts +137 -27
  39. package/src/index.web.ts +182 -49
  40. package/src/indexeddb-backend.ts +143 -0
  41. package/src/internal.ts +51 -23
package/README.md CHANGED
@@ -18,6 +18,8 @@ Synchronous Memory, Disk, and Secure storage in one unified API — powered by [
18
18
  - **Versioned writes** — optimistic concurrency with `item.getWithVersion()` and `item.setIfVersion(...)`
19
19
  - **Performance metrics** — observe operation timings and aggregate snapshots
20
20
  - **Web secure backend override** — plug custom secure storage backend on web
21
+ - **IndexedDB backend** — drop-in `createIndexedDBBackend` factory for persistent web Secure storage with large payloads
22
+ - **Bulk import** — load a raw `Record<string, string>` into any scope atomically with `storage.import`
21
23
  - **Transactions** — grouped writes with automatic rollback on error
22
24
  - **Migrations** — versioned data migrations with `registerMigration` / `migrateToLatest`
23
25
  - **MMKV migration** — drop-in `migrateFromMMKV` for painless migration from MMKV
@@ -39,10 +41,14 @@ Every feature in this package is documented with at least one runnable example i
39
41
  - Versioned item API (`getWithVersion`, `setIfVersion`) — see Optimistic Versioned Writes
40
42
  - Metrics API (`setMetricsObserver`, `getMetricsSnapshot`, `resetMetrics`) — see Storage Metrics Instrumentation
41
43
  - Web secure backend override (`setWebSecureStorageBackend`, `getWebSecureStorageBackend`) — see Custom Web Secure Backend
44
+ - IndexedDB backend factory (`createIndexedDBBackend`) — see IndexedDB Backend for Web
45
+ - Bulk import (`storage.import`) — see Bulk Data Import
42
46
  - Batch APIs (`getBatch`, `setBatch`, `removeBatch`) — see Batch Operations and Bulk Bootstrap with Batch APIs
43
47
  - Transactions — see Transactions and Atomic Balance Transfer
44
48
  - Migrations (`registerMigration`, `migrateToLatest`) — see Migrations
45
49
  - MMKV migration (`migrateFromMMKV`) — see MMKV Migration and Migrating From MMKV
50
+ - Raw string API (`getString`, `setString`, `deleteString`) — see Raw String API
51
+ - Keychain locked detection (`isKeychainLockedError`) — see `isKeychainLockedError(err)`
46
52
  - Auth storage factory (`createSecureAuthStorage`) — see Auth Token Management
47
53
 
48
54
  ## Requirements
@@ -276,6 +282,10 @@ import { storage, StorageScope } from "react-native-nitro-storage";
276
282
  | `storage.setSecureWritesAsync(enabled)` | Toggle async secure writes on Android (`false` by default) |
277
283
  | `storage.flushSecureWrites()` | Force flush of queued secure writes when coalescing is enabled |
278
284
  | `storage.setKeychainAccessGroup(group)` | Set keychain access group for app sharing (native only) |
285
+ | `storage.getString(key, scope)` | Read a raw string value directly (bypasses serialization) |
286
+ | `storage.setString(key, value, scope)` | Write a raw string value directly (bypasses serialization) |
287
+ | `storage.deleteString(key, scope)` | Delete a raw string value by key |
288
+ | `storage.import(data, scope)` | Bulk-load a `Record<string, string>` of raw key/value pairs into a scope |
279
289
  | `storage.setMetricsObserver(observer?)` | Subscribe to per-operation timing events |
280
290
  | `storage.getMetricsSnapshot()` | Get aggregate counters/latency stats keyed by operation |
281
291
  | `storage.resetMetrics()` | Reset in-memory metrics counters |
@@ -349,6 +359,33 @@ console.log("custom backend active:", backend !== undefined);
349
359
 
350
360
  ---
351
361
 
362
+ ### IndexedDB Backend for Web
363
+
364
+ The default web Secure backend uses `localStorage`, which is synchronous and size-limited. For large payloads or when you need true persistence across tab reloads, use the built-in IndexedDB-backed factory.
365
+
366
+ ```ts
367
+ import { setWebSecureStorageBackend } from "react-native-nitro-storage";
368
+ import { createIndexedDBBackend } from "react-native-nitro-storage/indexeddb-backend";
369
+
370
+ // call once at app startup, before rendering any components that read secure items
371
+ const backend = await createIndexedDBBackend();
372
+ setWebSecureStorageBackend(backend);
373
+ ```
374
+
375
+ **How it works:**
376
+
377
+ - **Async init**: `createIndexedDBBackend()` opens (or creates) the IndexedDB database and hydrates an in-memory cache from all stored entries before resolving.
378
+ - **Synchronous reads**: all `getItem` calls are served from the in-memory cache — no async overhead after init.
379
+ - **Fire-and-forget writes**: `setItem`, `removeItem`, and `clear` update the cache synchronously, then persist to IndexedDB in the background. The cache is always the authoritative source.
380
+ - **Custom database/store**: optionally pass `dbName` and `storeName` to isolate databases per environment or tenant.
381
+
382
+ ```ts
383
+ const backend = await createIndexedDBBackend("my-app-db", "secure-kv");
384
+ setWebSecureStorageBackend(backend);
385
+ ```
386
+
387
+ ---
388
+
352
389
  ### `createSecureAuthStorage<K>(config, options?)`
353
390
 
354
391
  One-liner factory for authentication flows. Creates multiple `StorageItem<string>` entries in Secure scope.
@@ -477,6 +514,40 @@ const migrated = migrateFromMMKV(mmkv, myStorageItem, true);
477
514
 
478
515
  ---
479
516
 
517
+ ### Raw String API
518
+
519
+ For cases where you want to bypass `createStorageItem` serialization entirely and work with raw key/value strings:
520
+
521
+ ```ts
522
+ import { storage, StorageScope } from "react-native-nitro-storage";
523
+
524
+ storage.setString("raw-key", "raw-value", StorageScope.Disk);
525
+ const value = storage.getString("raw-key", StorageScope.Disk); // "raw-value" | undefined
526
+ storage.deleteString("raw-key", StorageScope.Disk);
527
+ ```
528
+
529
+ These are synchronous and go directly to the native backend without any serialize/deserialize step.
530
+
531
+ ---
532
+
533
+ ### `isKeychainLockedError(err)`
534
+
535
+ Utility to detect iOS Keychain locked errors and Android key invalidation errors in secure storage operations. Returns `true` if the error was caused by a locked keychain (device locked, first unlock not yet performed, etc.) or an Android `KeyPermanentlyInvalidatedException` / `InvalidKeyException`. Always returns `false` on web.
536
+
537
+ ```ts
538
+ import { isKeychainLockedError } from "react-native-nitro-storage";
539
+
540
+ try {
541
+ secureItem.get();
542
+ } catch (err) {
543
+ if (isKeychainLockedError(err)) {
544
+ // device is locked — retry after unlock
545
+ }
546
+ }
547
+ ```
548
+
549
+ ---
550
+
480
551
  ### Enums
481
552
 
482
553
  #### `AccessControl`
@@ -755,6 +826,25 @@ sessionToken.set("token-v2");
755
826
  storage.flushSecureWrites();
756
827
  ```
757
828
 
829
+ ### Bulk Data Import
830
+
831
+ Load server-fetched data into storage in one atomic call. All keys become visible simultaneously before any listener fires.
832
+
833
+ ```ts
834
+ import { storage, StorageScope } from "react-native-nitro-storage";
835
+
836
+ // seed local cache from a server response
837
+ const serverData = await fetchInitialData(); // Record<string, string>
838
+ storage.import(serverData, StorageScope.Disk);
839
+
840
+ // all imported keys are immediately readable
841
+ const value = storage.has("remote-config", StorageScope.Disk);
842
+ ```
843
+
844
+ > `storage.import` writes raw string values directly — serialization is bypassed. Use it for bootstrapping data that was already serialized by the server or exported via `storage.getAll`.
845
+
846
+ ---
847
+
758
848
  ### Storage Snapshots and Cleanup
759
849
 
760
850
  ```ts
@@ -14,19 +14,11 @@ def reactNativeArchitectures() {
14
14
  return value ? value.split(",") : ["armeabi-v7a", "x86", "x86_64", "arm64-v8a"]
15
15
  }
16
16
 
17
- def isNewArchitectureEnabled() {
18
- return rootProject.hasProperty("newArchEnabled") && rootProject.getProperty("newArchEnabled") == "true"
19
- }
20
-
21
17
  apply plugin: "com.android.library"
22
18
  apply plugin: "org.jetbrains.kotlin.android"
23
19
  // Apply autolinking script for Nitro
24
20
  apply from: "../nitrogen/generated/android/NitroStorage+autolinking.gradle"
25
21
 
26
- if (isNewArchitectureEnabled()) {
27
- apply plugin: "com.facebook.react"
28
- }
29
-
30
22
  def getExtOrDefault(name) {
31
23
  return rootProject.ext.has(name) ? rootProject.ext.get(name) : project.properties["NitroStorage_" + name]
32
24
  }
@@ -45,7 +37,6 @@ android {
45
37
  defaultConfig {
46
38
  minSdkVersion getExtOrIntegerDefault("minSdkVersion")
47
39
  targetSdkVersion getExtOrIntegerDefault("targetSdkVersion")
48
- buildConfigField "boolean", "IS_NEW_ARCHITECTURE_ENABLED", isNewArchitectureEnabled().toString()
49
40
  consumerProguardFiles "consumer-rules.pro"
50
41
 
51
42
  externalNativeBuild {
@@ -76,9 +67,6 @@ android {
76
67
  sourceSets {
77
68
  main {
78
69
  java.srcDirs += ["src/main/java"]
79
- if (isNewArchitectureEnabled()) {
80
- java.srcDirs += ["${project.buildDir}/generated/source/codegen/java"]
81
- }
82
70
  }
83
71
  }
84
72
  }
@@ -1,4 +1,26 @@
1
- # Keep AndroidStorageAdapter and all its methods - accessed via JNI reflection
2
- -keep class com.nitrostorage.AndroidStorageAdapter { *; }
3
- -keepclassmembers class com.nitrostorage.AndroidStorageAdapter { *; }
4
- -keepclassmembers class com.nitrostorage.AndroidStorageAdapter$Companion { *; }
1
+ # NitroStorage - JNI-callable methods must survive R8/ProGuard shrinking
2
+
3
+ -keep class com.nitrostorage.AndroidStorageAdapter {
4
+ public static *** set*(...);
5
+ public static *** get*(...);
6
+ public static *** delete*(...);
7
+ public static *** has*(...);
8
+ public static *** clear*(...);
9
+ public static *** size*(...);
10
+ public static *** flush*(...);
11
+ public static void init(android.content.Context);
12
+ public static void setSecureWritesAsync(boolean);
13
+ public static void setSecureAccessControl(int);
14
+ public static void removeByPrefix(java.lang.String, int);
15
+ }
16
+ -keep class com.nitrostorage.AndroidStorageAdapter$Companion {
17
+ public <methods>;
18
+ }
19
+ -keep class com.nitrostorage.NitroStoragePackage {
20
+ <init>();
21
+ <clinit>();
22
+ *;
23
+ }
24
+ -keep class com.nitrostorage.NitroStoragePackage$Companion {
25
+ *;
26
+ }
@@ -34,26 +34,23 @@ std::vector<std::optional<std::string>> fromNullableJavaStringArray(alias_ref<Ja
34
34
  }
35
35
 
36
36
  std::vector<std::string> fromJavaStringArray(alias_ref<JavaStringArray> values) {
37
+ if (!values) return {};
38
+ const jsize size = values->size();
37
39
  std::vector<std::string> result;
38
- if (!values) return result;
39
-
40
- const jsize size = static_cast<jsize>(values->size());
41
40
  result.reserve(size);
42
41
  for (jsize i = 0; i < size; ++i) {
43
42
  auto currentValue = values->getElement(i);
44
- if (currentValue) {
45
- result.push_back(currentValue->toStdString());
46
- }
43
+ // Preserve null as empty string to maintain index alignment with caller
44
+ result.push_back(currentValue ? currentValue->toStdString() : std::string());
47
45
  }
48
46
  return result;
49
47
  }
50
48
 
51
49
  } // namespace
52
50
 
53
- AndroidStorageAdapterCpp::AndroidStorageAdapterCpp(alias_ref<JObject> context) {
54
- if (!context) [[unlikely]] {
55
- throw std::runtime_error("NitroStorage: Android Context is null");
56
- }
51
+ AndroidStorageAdapterCpp::AndroidStorageAdapterCpp(alias_ref<JObject> /*context*/) {
52
+ // Context is validated by AndroidStorageAdapter.getContext() on the Java side.
53
+ // The adapter calls static Java methods directly via fbjni.
57
54
  }
58
55
 
59
56
  AndroidStorageAdapterCpp::~AndroidStorageAdapterCpp() = default;
@@ -6,10 +6,6 @@
6
6
 
7
7
  namespace NitroStorage {
8
8
 
9
- struct JContext : facebook::jni::JavaClass<JContext> {
10
- static constexpr auto kJavaDescriptor = "Landroid/content/Context;";
11
- };
12
-
13
9
  struct AndroidStorageAdapterJava : facebook::jni::JavaClass<AndroidStorageAdapterJava> {
14
10
  static constexpr auto kJavaDescriptor = "Lcom/nitrostorage/AndroidStorageAdapter;";
15
11
 
@@ -3,5 +3,7 @@
3
3
  #include "../../../nitrogen/generated/android/NitroStorageOnLoad.hpp"
4
4
 
5
5
  JNIEXPORT jint JNICALL JNI_OnLoad(JavaVM* vm, void*) {
6
- return margelo::nitro::NitroStorage::initialize(vm);
6
+ return facebook::jni::initialize(vm, []() {
7
+ margelo::nitro::NitroStorage::registerAllNatives();
8
+ });
7
9
  }