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.
- package/README.md +90 -0
- package/android/build.gradle +0 -12
- package/android/consumer-rules.pro +26 -4
- package/android/src/main/cpp/AndroidStorageAdapterCpp.cpp +7 -10
- package/android/src/main/cpp/AndroidStorageAdapterCpp.hpp +0 -4
- package/android/src/main/cpp/cpp-adapter.cpp +3 -1
- package/android/src/main/java/com/nitrostorage/AndroidStorageAdapter.kt +172 -77
- package/cpp/bindings/HybridStorage.cpp +120 -69
- package/cpp/bindings/HybridStorage.hpp +4 -0
- package/ios/IOSStorageAdapterCpp.hpp +2 -1
- package/ios/IOSStorageAdapterCpp.mm +264 -49
- package/lib/commonjs/index.js +128 -20
- package/lib/commonjs/index.js.map +1 -1
- package/lib/commonjs/index.web.js +169 -41
- package/lib/commonjs/index.web.js.map +1 -1
- package/lib/commonjs/indexeddb-backend.js +130 -0
- package/lib/commonjs/indexeddb-backend.js.map +1 -0
- package/lib/commonjs/internal.js +51 -23
- package/lib/commonjs/internal.js.map +1 -1
- package/lib/module/index.js +121 -20
- package/lib/module/index.js.map +1 -1
- package/lib/module/index.web.js +162 -41
- package/lib/module/index.web.js.map +1 -1
- package/lib/module/indexeddb-backend.js +126 -0
- package/lib/module/indexeddb-backend.js.map +1 -0
- package/lib/module/internal.js +51 -23
- package/lib/module/internal.js.map +1 -1
- package/lib/typescript/index.d.ts +6 -0
- package/lib/typescript/index.d.ts.map +1 -1
- package/lib/typescript/index.web.d.ts +7 -1
- package/lib/typescript/index.web.d.ts.map +1 -1
- package/lib/typescript/indexeddb-backend.d.ts +29 -0
- package/lib/typescript/indexeddb-backend.d.ts.map +1 -0
- package/lib/typescript/internal.d.ts.map +1 -1
- package/nitrogen/generated/android/NitroStorageOnLoad.cpp +22 -17
- package/nitrogen/generated/android/NitroStorageOnLoad.hpp +13 -4
- package/package.json +7 -3
- package/src/index.ts +137 -27
- package/src/index.web.ts +182 -49
- package/src/indexeddb-backend.ts +143 -0
- 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
|
package/android/build.gradle
CHANGED
|
@@ -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
|
-
#
|
|
2
|
-
|
|
3
|
-
-
|
|
4
|
-
|
|
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
|
-
|
|
45
|
-
|
|
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
|
-
|
|
55
|
-
|
|
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
|
|
6
|
+
return facebook::jni::initialize(vm, []() {
|
|
7
|
+
margelo::nitro::NitroStorage::registerAllNatives();
|
|
8
|
+
});
|
|
7
9
|
}
|