react-native-nitro-storage 0.5.6 → 0.5.8
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 +267 -362
- package/ios/IOSStorageAdapterCpp.mm +126 -80
- package/lib/commonjs/index.js +119 -1599
- package/lib/commonjs/index.js.map +1 -1
- package/lib/commonjs/index.web.js +83 -1550
- package/lib/commonjs/index.web.js.map +1 -1
- package/lib/commonjs/shared.js +102 -0
- package/lib/commonjs/shared.js.map +1 -0
- package/lib/commonjs/storage-core.js +1505 -0
- package/lib/commonjs/storage-core.js.map +1 -0
- package/lib/module/index.js +111 -1591
- package/lib/module/index.js.map +1 -1
- package/lib/module/index.web.js +69 -1536
- package/lib/module/index.web.js.map +1 -1
- package/lib/module/shared.js +82 -0
- package/lib/module/shared.js.map +1 -0
- package/lib/module/storage-core.js +1501 -0
- package/lib/module/storage-core.js.map +1 -0
- package/lib/typescript/index.d.ts +30 -133
- package/lib/typescript/index.d.ts.map +1 -1
- package/lib/typescript/index.web.d.ts +31 -129
- package/lib/typescript/index.web.d.ts.map +1 -1
- package/lib/typescript/shared.d.ts +96 -0
- package/lib/typescript/shared.d.ts.map +1 -0
- package/lib/typescript/storage-core.d.ts +157 -0
- package/lib/typescript/storage-core.d.ts.map +1 -0
- package/package.json +7 -3
- package/react-native-nitro-storage.podspec +4 -0
- package/src/index.ts +260 -2519
- package/src/index.web.ts +133 -2326
- package/src/shared.ts +249 -0
- package/src/storage-core.ts +2349 -0
package/README.md
CHANGED
|
@@ -1,81 +1,74 @@
|
|
|
1
1
|
# react-native-nitro-storage
|
|
2
2
|
|
|
3
|
-
[](https://www.npmjs.com/package/react-native-nitro-storage)
|
|
4
|
-
[](https://www.npmjs.com/package/react-native-nitro-storage)
|
|
4
|
+
[](https://www.npmjs.com/package/react-native-nitro-storage)
|
|
5
|
+
[](https://github.com/JoaoPauloCMarra/react-native-nitro-storage/actions/workflows/ci.yml)
|
|
6
|
+
[](https://github.com/JoaoPauloCMarra/react-native-nitro-storage/blob/main/LICENSE)
|
|
5
7
|
[](https://reactnative.dev/)
|
|
8
|
+
[](https://docs.expo.dev/)
|
|
6
9
|
[](https://nitro.margelo.com/)
|
|
10
|
+
[](https://www.typescriptlang.org/)
|
|
7
11
|
|
|
8
|
-
|
|
12
|
+
Synchronous Memory, Disk, and Secure storage for React Native, Expo development
|
|
13
|
+
builds, and web. Nitro Storage is powered by
|
|
14
|
+
[Nitro Modules](https://nitro.margelo.com/) and JSI, with typed storage items,
|
|
15
|
+
React hooks, batch operations, event subscriptions, migrations, biometric secure
|
|
16
|
+
values, MMKV migration helpers, and configurable web backends.
|
|
9
17
|
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
18
|
+
Use it for startup state, preferences, feature flags, local auth state, secure
|
|
19
|
+
tokens, biometric-protected values, optimistic writes, app migrations, and
|
|
20
|
+
state-library persistence where a synchronous API is the right fit. Use a
|
|
21
|
+
database or server-state cache instead for relational queries, large collections,
|
|
22
|
+
pagination, conflict resolution, or remote synchronization.
|
|
13
23
|
|
|
14
24
|
## Contents
|
|
15
25
|
|
|
16
|
-
- [At a Glance](#at-a-glance)
|
|
17
|
-
- [Use It When](#use-it-when)
|
|
18
|
-
- [Why Nitro Storage](#why-nitro-storage)
|
|
19
26
|
- [Install](#install)
|
|
27
|
+
- [Expo Config](#expo-config)
|
|
20
28
|
- [Quick Start](#quick-start)
|
|
29
|
+
- [Typed Storage Items](#typed-storage-items)
|
|
30
|
+
- [React Hooks](#react-hooks)
|
|
21
31
|
- [Storage Scopes](#storage-scopes)
|
|
22
|
-
- [
|
|
23
|
-
- [
|
|
32
|
+
- [Secure Storage](#secure-storage)
|
|
33
|
+
- [Batch Operations](#batch-operations)
|
|
34
|
+
- [Events And Observability](#events-and-observability)
|
|
35
|
+
- [Migrations And Transactions](#migrations-and-transactions)
|
|
36
|
+
- [Web Backends](#web-backends)
|
|
24
37
|
- [Platform Support](#platform-support)
|
|
25
|
-
- [
|
|
26
|
-
- [
|
|
27
|
-
- [Choosing a Storage Library](#choosing-a-storage-library)
|
|
28
|
-
- [Production Checklist](#production-checklist)
|
|
38
|
+
- [Documentation](#documentation)
|
|
39
|
+
- [Troubleshooting](#troubleshooting)
|
|
29
40
|
- [Development](#development)
|
|
30
41
|
|
|
31
|
-
## At a Glance
|
|
32
|
-
|
|
33
|
-
| Need | API or feature |
|
|
34
|
-
| --------------------------------- | ------------------------------------------------------------- |
|
|
35
|
-
| Read preferences during startup | `createStorageItem` with `StorageScope.Disk` |
|
|
36
|
-
| Keep session-only state | `StorageScope.Memory` |
|
|
37
|
-
| Store auth tokens or secrets | `StorageScope.Secure` |
|
|
38
|
-
| Protect a value with biometrics | `biometric: true` and `biometricLevel` |
|
|
39
|
-
| Bind storage to React | `useStorage`, `useStorageSelector`, `useSetStorage` |
|
|
40
|
-
| Bootstrap several values at once | `getBatch`, `setBatch`, `removeBatch` |
|
|
41
|
-
| Roll back local writes on failure | `runTransaction` |
|
|
42
|
-
| Upgrade local schemas | `registerMigration` and `migrateToLatest` |
|
|
43
|
-
| Move existing MMKV data | `migrateFromMMKV` |
|
|
44
|
-
| Persist storage on web | `setWebDiskStorageBackend` or `createIndexedDBBackend` |
|
|
45
|
-
| Inspect secure backend state | `getSecurityCapabilities`, `getSecureMetadata`, metadata APIs |
|
|
46
|
-
| Connect external state/debug code | `subscribeNamespace`, `subscribePrefix`, `setEventObserver` |
|
|
47
|
-
|
|
48
|
-
## Use It When
|
|
49
|
-
|
|
50
|
-
Nitro Storage is a good fit when your app needs synchronous local reads and a typed API across preferences, auth state, feature flags, and secrets. It is especially useful for startup gates, theme or locale preferences, persisted onboarding state, refresh tokens, biometric-protected values, and React state that should survive reloads.
|
|
51
|
-
|
|
52
|
-
Use a database or server-state cache instead when you need relational queries, conflict resolution, large collections, sync protocols, pagination, or cache invalidation from remote APIs.
|
|
53
|
-
|
|
54
|
-
## Why Nitro Storage
|
|
55
|
-
|
|
56
|
-
- Synchronous reads for startup state, render-time preferences, and auth boundaries.
|
|
57
|
-
- Typed `StorageItem<T>` values with custom serialization, validation, TTL, optimistic writes, and subscriptions.
|
|
58
|
-
- Three scopes: in-memory session state, persisted disk state, and platform secure storage.
|
|
59
|
-
- Secure storage backed by iOS Keychain and Android Keystore/EncryptedSharedPreferences.
|
|
60
|
-
- React hooks without providers: `useStorage`, `useStorageSelector`, and `useSetStorage`.
|
|
61
|
-
- Batch reads/writes, namespace cleanup, raw import/export, event subscriptions, transactions, and migrations.
|
|
62
|
-
- Web parity with configurable Disk/Secure backends and an IndexedDB backend.
|
|
63
|
-
- MMKV migration helper for moving existing keys without rewriting app code first.
|
|
64
|
-
|
|
65
42
|
## Install
|
|
66
43
|
|
|
67
44
|
```sh
|
|
68
45
|
bun add react-native-nitro-storage react-native-nitro-modules
|
|
69
46
|
```
|
|
70
47
|
|
|
71
|
-
|
|
48
|
+
Peer dependencies:
|
|
49
|
+
|
|
50
|
+
| Package | Version |
|
|
51
|
+
| ---------------------------- | ---------- |
|
|
52
|
+
| `react` | `>=18.2.0` |
|
|
53
|
+
| `react-native` | `>=0.75.0` |
|
|
54
|
+
| `react-native-nitro-modules` | `>=0.35.7` |
|
|
55
|
+
|
|
56
|
+
Nitro peer requirement: `react-native-nitro-modules >=0.35.7`.
|
|
72
57
|
|
|
73
|
-
For Expo
|
|
58
|
+
For Expo development builds:
|
|
74
59
|
|
|
75
60
|
```sh
|
|
76
61
|
bunx expo install react-native-nitro-storage react-native-nitro-modules
|
|
62
|
+
bunx expo prebuild
|
|
77
63
|
```
|
|
78
64
|
|
|
65
|
+
Expo Go cannot load Nitro native modules. Use an Expo development build or a
|
|
66
|
+
bare React Native app.
|
|
67
|
+
|
|
68
|
+
## Expo Config
|
|
69
|
+
|
|
70
|
+
Add the config plugin before prebuilding native iOS and Android projects:
|
|
71
|
+
|
|
79
72
|
```json
|
|
80
73
|
{
|
|
81
74
|
"expo": {
|
|
@@ -83,8 +76,8 @@ bunx expo install react-native-nitro-storage react-native-nitro-modules
|
|
|
83
76
|
[
|
|
84
77
|
"react-native-nitro-storage",
|
|
85
78
|
{
|
|
86
|
-
"faceIDPermission": "Allow $(PRODUCT_NAME) to
|
|
87
|
-
"addBiometricPermissions":
|
|
79
|
+
"faceIDPermission": "Allow $(PRODUCT_NAME) to unlock secure storage.",
|
|
80
|
+
"addBiometricPermissions": false,
|
|
88
81
|
"configureAndroidBackup": true
|
|
89
82
|
}
|
|
90
83
|
]
|
|
@@ -93,165 +86,115 @@ bunx expo install react-native-nitro-storage react-native-nitro-modules
|
|
|
93
86
|
}
|
|
94
87
|
```
|
|
95
88
|
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
89
|
+
| Option | Default | What it does |
|
|
90
|
+
| ------------------------- | ------------------------ | -------------------------------------------------------------- |
|
|
91
|
+
| `faceIDPermission` | Built-in Face ID message | Sets `NSFaceIDUsageDescription`. |
|
|
92
|
+
| `addBiometricPermissions` | `false` | Adds Android biometric and fingerprint permissions. |
|
|
93
|
+
| `configureAndroidBackup` | `true` | Writes Android backup rules that exclude secure storage files. |
|
|
99
94
|
|
|
100
|
-
The
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
Bare React Native projects should install pods after adding the package:
|
|
105
|
-
|
|
106
|
-
```sh
|
|
107
|
-
cd ios && pod install
|
|
108
|
-
```
|
|
109
|
-
|
|
110
|
-
Bare Android apps must initialize the adapter from `MainApplication` before using native storage:
|
|
111
|
-
|
|
112
|
-
```kt
|
|
113
|
-
import com.nitrostorage.AndroidStorageAdapter
|
|
114
|
-
|
|
115
|
-
override fun onCreate() {
|
|
116
|
-
super.onCreate()
|
|
117
|
-
AndroidStorageAdapter.init(this)
|
|
118
|
-
}
|
|
119
|
-
```
|
|
95
|
+
The plugin also initializes the Android storage adapter in `MainApplication`.
|
|
96
|
+
Set `configureAndroidBackup: false` only when your app maintains equivalent
|
|
97
|
+
backup and device-transfer exclusions for Nitro Storage secure files.
|
|
120
98
|
|
|
121
99
|
## Quick Start
|
|
122
100
|
|
|
123
|
-
Create storage items outside React render functions, then use them from anywhere.
|
|
124
|
-
|
|
125
101
|
```ts
|
|
126
102
|
import {
|
|
127
|
-
createStorageItem,
|
|
128
103
|
StorageScope,
|
|
129
|
-
|
|
130
|
-
|
|
104
|
+
createStorageItem,
|
|
105
|
+
storage,
|
|
131
106
|
} from "react-native-nitro-storage";
|
|
132
107
|
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
const themeConfig = {
|
|
108
|
+
const themeItem = createStorageItem<"light" | "dark">({
|
|
136
109
|
key: "theme",
|
|
110
|
+
namespace: "settings",
|
|
137
111
|
scope: StorageScope.Disk,
|
|
138
|
-
defaultValue: "
|
|
139
|
-
|
|
140
|
-
value === "system" || value === "light" || value === "dark",
|
|
141
|
-
} satisfies StorageItemConfig<Theme>;
|
|
142
|
-
|
|
143
|
-
export const themeItem = createStorageItem(themeConfig);
|
|
112
|
+
defaultValue: "light",
|
|
113
|
+
});
|
|
144
114
|
|
|
145
|
-
|
|
146
|
-
const [theme, setTheme] = useStorage(themeItem);
|
|
115
|
+
themeItem.set("dark");
|
|
147
116
|
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
title={`Theme: ${theme}`}
|
|
151
|
-
onPress={() => setTheme(theme === "dark" ? "light" : "dark")}
|
|
152
|
-
/>
|
|
153
|
-
);
|
|
154
|
-
}
|
|
117
|
+
const theme = themeItem.get();
|
|
118
|
+
const raw = storage.getString("settings:theme", StorageScope.Disk);
|
|
155
119
|
```
|
|
156
120
|
|
|
157
|
-
|
|
121
|
+
## Typed Storage Items
|
|
158
122
|
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
BiometricLevel,
|
|
163
|
-
createStorageItem,
|
|
164
|
-
StorageScope,
|
|
165
|
-
} from "react-native-nitro-storage";
|
|
166
|
-
|
|
167
|
-
export const refreshTokenItem = createStorageItem<string>({
|
|
168
|
-
key: "refreshToken",
|
|
169
|
-
namespace: "auth",
|
|
170
|
-
scope: StorageScope.Secure,
|
|
171
|
-
defaultValue: "",
|
|
172
|
-
biometric: true,
|
|
173
|
-
biometricLevel: BiometricLevel.BiometryOrPasscode,
|
|
174
|
-
accessControl: AccessControl.AfterFirstUnlockThisDeviceOnly,
|
|
175
|
-
});
|
|
176
|
-
|
|
177
|
-
refreshTokenItem.set("opaque-refresh-token");
|
|
178
|
-
```
|
|
179
|
-
|
|
180
|
-
Bootstrap several values in one synchronous call:
|
|
123
|
+
`createStorageItem<T>()` is the recommended API for application code. It keeps
|
|
124
|
+
serialization, validation, default values, TTL, namespace, access-control, and
|
|
125
|
+
React hook types attached to the key.
|
|
181
126
|
|
|
182
127
|
```ts
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
128
|
+
type Preferences = {
|
|
129
|
+
theme: "system" | "light" | "dark";
|
|
130
|
+
compactMode: boolean;
|
|
131
|
+
};
|
|
187
132
|
|
|
188
|
-
|
|
133
|
+
const preferencesItem = createStorageItem<Preferences>({
|
|
134
|
+
key: "preferences",
|
|
135
|
+
namespace: "settings",
|
|
136
|
+
scope: StorageScope.Disk,
|
|
137
|
+
defaultValue: { theme: "system", compactMode: false },
|
|
138
|
+
validate: (value): value is Preferences =>
|
|
139
|
+
typeof value === "object" && value !== null && "theme" in value,
|
|
140
|
+
});
|
|
189
141
|
|
|
190
|
-
|
|
191
|
-
|
|
142
|
+
preferencesItem.set((previous) => ({
|
|
143
|
+
...previous,
|
|
144
|
+
compactMode: !previous.compactMode,
|
|
145
|
+
}));
|
|
192
146
|
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
147
|
+
const snapshot = preferencesItem.getWithVersion();
|
|
148
|
+
const didWrite = preferencesItem.setIfVersion(snapshot.version, {
|
|
149
|
+
...snapshot.value,
|
|
150
|
+
theme: "dark",
|
|
196
151
|
});
|
|
197
152
|
```
|
|
198
153
|
|
|
199
|
-
|
|
154
|
+
The package exports `StorageItem`, `StorageItemConfig`, `StorageSetter`,
|
|
155
|
+
`VersionedValue`, `StorageBatchSetItem`, web backend types, event types, secure
|
|
156
|
+
metadata types, and capability types for IDE-safe integrations.
|
|
200
157
|
|
|
201
|
-
|
|
202
|
-
import { StorageScope, storage } from "react-native-nitro-storage";
|
|
203
|
-
|
|
204
|
-
const snapshot = storage.export(StorageScope.Disk);
|
|
158
|
+
## React Hooks
|
|
205
159
|
|
|
206
|
-
|
|
207
|
-
|
|
160
|
+
```tsx
|
|
161
|
+
import { Switch } from "react-native";
|
|
162
|
+
import { useSetStorage, useStorage } from "react-native-nitro-storage";
|
|
208
163
|
|
|
209
|
-
|
|
164
|
+
export function ThemeToggle() {
|
|
165
|
+
const [theme] = useStorage(themeItem);
|
|
166
|
+
const setTheme = useSetStorage(themeItem);
|
|
210
167
|
|
|
211
|
-
|
|
168
|
+
return (
|
|
169
|
+
<Switch
|
|
170
|
+
value={theme === "dark"}
|
|
171
|
+
onValueChange={(enabled) => setTheme(enabled ? "dark" : "light")}
|
|
172
|
+
/>
|
|
173
|
+
);
|
|
174
|
+
}
|
|
175
|
+
```
|
|
212
176
|
|
|
213
|
-
|
|
214
|
-
|
|
177
|
+
Use `useStorageSelector()` when a component needs a derived value instead of the
|
|
178
|
+
whole stored object.
|
|
215
179
|
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
StorageScope.Disk,
|
|
219
|
-
(event) => {
|
|
220
|
-
if (event.type === "batch") {
|
|
221
|
-
console.log("settings changed", event.changes.length);
|
|
222
|
-
return;
|
|
223
|
-
}
|
|
180
|
+
```tsx
|
|
181
|
+
import { useStorageSelector } from "react-native-nitro-storage";
|
|
224
182
|
|
|
225
|
-
|
|
226
|
-
|
|
183
|
+
const [compactMode] = useStorageSelector(
|
|
184
|
+
preferencesItem,
|
|
185
|
+
(preferences) => preferences.compactMode,
|
|
227
186
|
);
|
|
228
187
|
```
|
|
229
188
|
|
|
230
189
|
## Storage Scopes
|
|
231
190
|
|
|
232
|
-
| Scope | Backing store
|
|
233
|
-
| --------------------- |
|
|
234
|
-
| `StorageScope.Memory` |
|
|
235
|
-
| `StorageScope.Disk` | UserDefaults on iOS, SharedPreferences on Android, web
|
|
236
|
-
| `StorageScope.Secure` | iOS
|
|
237
|
-
|
|
238
|
-
Use Secure scope only for secrets. Disk scope is faster and easier to inspect, but it is not a secret store.
|
|
239
|
-
|
|
240
|
-
## Docs
|
|
241
|
-
|
|
242
|
-
| Task | Start here |
|
|
243
|
-
| ------------------------------------------- | ------------------------------------------------------------------------------ |
|
|
244
|
-
| Learn the public API | [docs/api-reference.md](docs/api-reference.md) |
|
|
245
|
-
| Bind storage to React | [docs/react-hooks.md](docs/react-hooks.md) |
|
|
246
|
-
| Store tokens or biometric secrets | [docs/secure-storage.md](docs/secure-storage.md) |
|
|
247
|
-
| Use batch APIs, transactions, or migrations | [docs/batch-transactions-migrations.md](docs/batch-transactions-migrations.md) |
|
|
248
|
-
| Configure web Disk/Secure storage | [docs/web-backends.md](docs/web-backends.md) |
|
|
249
|
-
| Migrate from `react-native-mmkv` | [docs/mmkv-migration.md](docs/mmkv-migration.md) |
|
|
250
|
-
| Copy working snippets | [docs/recipes.md](docs/recipes.md) |
|
|
251
|
-
| Run or interpret benchmarks | [docs/benchmarks.md](docs/benchmarks.md) |
|
|
252
|
-
| Report a vulnerability | [SECURITY.md](SECURITY.md) |
|
|
191
|
+
| Scope | Backing store | Use it for |
|
|
192
|
+
| --------------------- | ------------------------------------------------------ | ---------------------------------------------------------------------------- |
|
|
193
|
+
| `StorageScope.Memory` | In-process memory | Session-only state, fast counters, and render-time caches. |
|
|
194
|
+
| `StorageScope.Disk` | UserDefaults on iOS, SharedPreferences on Android, web | Preferences, feature flags, onboarding state, and non-secret persisted data. |
|
|
195
|
+
| `StorageScope.Secure` | Keychain on iOS, Android Keystore-backed preferences | Refresh tokens, credentials, API tokens, and biometric-protected values. |
|
|
253
196
|
|
|
254
|
-
##
|
|
197
|
+
## Secure Storage
|
|
255
198
|
|
|
256
199
|
```ts
|
|
257
200
|
import {
|
|
@@ -260,247 +203,209 @@ import {
|
|
|
260
203
|
StorageScope,
|
|
261
204
|
createSecureAuthStorage,
|
|
262
205
|
createStorageItem,
|
|
263
|
-
flushWebStorageBackends,
|
|
264
|
-
getBatch,
|
|
265
|
-
getStorageErrorCode,
|
|
266
206
|
isKeychainLockedError,
|
|
267
|
-
migrateFromMMKV,
|
|
268
|
-
migrateToLatest,
|
|
269
|
-
registerMigration,
|
|
270
|
-
removeBatch,
|
|
271
|
-
runTransaction,
|
|
272
|
-
setBatch,
|
|
273
|
-
setWebDiskStorageBackend,
|
|
274
|
-
setWebSecureStorageBackend,
|
|
275
207
|
storage,
|
|
276
|
-
useSetStorage,
|
|
277
|
-
useStorage,
|
|
278
|
-
useStorageSelector,
|
|
279
208
|
} from "react-native-nitro-storage";
|
|
280
|
-
```
|
|
281
|
-
|
|
282
|
-
The main building blocks are:
|
|
283
|
-
|
|
284
|
-
- `createStorageItem<T>(config)` for typed values.
|
|
285
|
-
- `storage` for raw reads, namespace cleanup, events, secure metadata, metrics, and runtime capability checks.
|
|
286
|
-
- `getBatch`, `setBatch`, and `removeBatch` for multi-key work.
|
|
287
|
-
- `runTransaction` for synchronous rollback on failure.
|
|
288
|
-
- `registerMigration` and `migrateToLatest` for versioned local data migrations.
|
|
289
|
-
- `createSecureAuthStorage` for a compact secure-token item map.
|
|
290
|
-
- `setWebDiskStorageBackend`, `setWebSecureStorageBackend`, and `createIndexedDBBackend` for web persistence.
|
|
291
|
-
|
|
292
|
-
See the full [API reference](docs/api-reference.md).
|
|
293
|
-
|
|
294
|
-
## TypeScript And IDE Safety
|
|
295
209
|
|
|
296
|
-
|
|
210
|
+
const refreshToken = createStorageItem<string>({
|
|
211
|
+
key: "refreshToken",
|
|
212
|
+
namespace: "auth",
|
|
213
|
+
scope: StorageScope.Secure,
|
|
214
|
+
defaultValue: "",
|
|
215
|
+
accessControl: AccessControl.AfterFirstUnlockThisDeviceOnly,
|
|
216
|
+
});
|
|
297
217
|
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
218
|
+
const recoveryCode = createStorageItem<string>({
|
|
219
|
+
key: "recoveryCode",
|
|
220
|
+
namespace: "auth",
|
|
221
|
+
scope: StorageScope.Secure,
|
|
222
|
+
defaultValue: "",
|
|
223
|
+
biometric: true,
|
|
224
|
+
biometricLevel: BiometricLevel.BiometryOrPasscode,
|
|
225
|
+
});
|
|
304
226
|
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
227
|
+
const auth = createSecureAuthStorage({
|
|
228
|
+
accessToken: { ttlMs: 15 * 60_000 },
|
|
229
|
+
refreshToken: {
|
|
230
|
+
accessControl: AccessControl.AfterFirstUnlockThisDeviceOnly,
|
|
231
|
+
},
|
|
232
|
+
});
|
|
309
233
|
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
234
|
+
try {
|
|
235
|
+
recoveryCode.get();
|
|
236
|
+
} catch (error) {
|
|
237
|
+
if (isKeychainLockedError(error)) {
|
|
238
|
+
storage.getSecurityCapabilities();
|
|
313
239
|
}
|
|
314
|
-
|
|
315
|
-
const candidate = value as Partial<Preferences>;
|
|
316
|
-
return (
|
|
317
|
-
(candidate.theme === "system" ||
|
|
318
|
-
candidate.theme === "light" ||
|
|
319
|
-
candidate.theme === "dark") &&
|
|
320
|
-
typeof candidate.compactMode === "boolean"
|
|
321
|
-
);
|
|
322
240
|
}
|
|
323
|
-
|
|
324
|
-
const preferencesConfig = {
|
|
325
|
-
key: "preferences",
|
|
326
|
-
scope: StorageScope.Disk,
|
|
327
|
-
defaultValue: { theme: "system", compactMode: false },
|
|
328
|
-
validate: isPreferences,
|
|
329
|
-
} satisfies StorageItemConfig<Preferences>;
|
|
330
|
-
|
|
331
|
-
const preferencesItem = createStorageItem(preferencesConfig);
|
|
332
|
-
|
|
333
|
-
preferencesItem.set((current) => ({
|
|
334
|
-
...current,
|
|
335
|
-
compactMode: !current.compactMode,
|
|
336
|
-
}));
|
|
337
241
|
```
|
|
338
242
|
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
WebSecureStorageBackend,
|
|
345
|
-
} from "react-native-nitro-storage";
|
|
346
|
-
```
|
|
347
|
-
|
|
348
|
-
## Platform Support
|
|
243
|
+
Secure scope uses iOS Keychain and Android Keystore-backed
|
|
244
|
+
EncryptedSharedPreferences. Keep secure values small, do not log them, and avoid
|
|
245
|
+
exporting secure values unless you are intentionally doing a short-lived
|
|
246
|
+
in-memory migration. `storage.export(StorageScope.Secure)` throws unless you
|
|
247
|
+
explicitly opt into `{ includeSecureValues: true }`.
|
|
349
248
|
|
|
350
|
-
|
|
351
|
-
| -------- | --------- | ------------------------------------------------------------------------------------------------------ |
|
|
352
|
-
| iOS | Supported | Disk uses app-suite `UserDefaults`; Secure uses Keychain. |
|
|
353
|
-
| Android | Supported | Disk uses SharedPreferences; Secure uses Keystore-backed EncryptedSharedPreferences. |
|
|
354
|
-
| Expo | Supported | Add the included config plugin before prebuild. |
|
|
355
|
-
| Web | Supported | Defaults to localStorage-style backends; IndexedDB backend is available for persistent Secure storage. |
|
|
249
|
+
## Batch Operations
|
|
356
250
|
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
| Surface | Version |
|
|
360
|
-
| ------------- | ------- |
|
|
361
|
-
| Expo example | SDK 56 |
|
|
362
|
-
| React Native | 0.85.3 |
|
|
363
|
-
| React | 19.2.3 |
|
|
364
|
-
| Nitro Modules | 0.35.7 |
|
|
365
|
-
| TypeScript | 6.0.3 |
|
|
366
|
-
|
|
367
|
-
The iOS example build requires Xcode 26 or newer. GitHub Actions should use the `macos-26` runner image for SDK 56 iOS build validation.
|
|
368
|
-
|
|
369
|
-
Peer dependencies:
|
|
370
|
-
|
|
371
|
-
- `react >=18.2.0`
|
|
372
|
-
- `react-native >=0.75.0`
|
|
373
|
-
- `react-native-nitro-modules >=0.35.7`
|
|
374
|
-
|
|
375
|
-
## Security Model
|
|
376
|
-
|
|
377
|
-
Secure scope stores values in native secure storage on iOS and Android. Biometric items are stored through separate biometric paths and can require biometric or passcode access on each read.
|
|
251
|
+
`getBatch()` preserves tuple value types, so IDEs infer each result from the
|
|
252
|
+
matching item.
|
|
378
253
|
|
|
379
254
|
```ts
|
|
380
|
-
|
|
381
|
-
const metadata = storage.getSecureMetadata("auth:refreshToken");
|
|
382
|
-
```
|
|
383
|
-
|
|
384
|
-
Security metadata APIs never return stored values. They are intended for diagnostics, inventory screens, and support tooling that needs to understand which secure backend is active without exposing secrets.
|
|
255
|
+
import { getBatch, removeBatch, setBatch } from "react-native-nitro-storage";
|
|
385
256
|
|
|
386
|
-
|
|
257
|
+
const localeItem = createStorageItem({
|
|
258
|
+
key: "locale",
|
|
259
|
+
namespace: "settings",
|
|
260
|
+
scope: StorageScope.Disk,
|
|
261
|
+
defaultValue: "en-US",
|
|
262
|
+
});
|
|
387
263
|
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
264
|
+
const [theme, locale] = getBatch(
|
|
265
|
+
[themeItem, localeItem] as const,
|
|
266
|
+
StorageScope.Disk,
|
|
267
|
+
);
|
|
392
268
|
|
|
393
|
-
|
|
269
|
+
setBatch(
|
|
270
|
+
[
|
|
271
|
+
{ item: themeItem, value: "dark" },
|
|
272
|
+
{ item: localeItem, value: "en-US" },
|
|
273
|
+
],
|
|
274
|
+
StorageScope.Disk,
|
|
275
|
+
);
|
|
394
276
|
|
|
395
|
-
|
|
277
|
+
removeBatch([themeItem, localeItem], StorageScope.Disk);
|
|
278
|
+
```
|
|
396
279
|
|
|
397
|
-
|
|
280
|
+
## Events And Observability
|
|
398
281
|
|
|
399
282
|
```ts
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
migrateFromMMKV(mmkv, refreshTokenItem, true);
|
|
404
|
-
```
|
|
283
|
+
const unsubscribe = storage.subscribeNamespace("settings", (event) => {
|
|
284
|
+
console.log(event.key, event.operation, event.source);
|
|
285
|
+
});
|
|
405
286
|
|
|
406
|
-
|
|
287
|
+
storage.setEventObserver((event) => {
|
|
288
|
+
console.log(event.type, event.scope);
|
|
289
|
+
});
|
|
407
290
|
|
|
408
|
-
|
|
409
|
-
|
|
291
|
+
storage.setMetricsObserver((event) => {
|
|
292
|
+
console.log(event.operation, event.durationMs);
|
|
293
|
+
});
|
|
410
294
|
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
}
|
|
295
|
+
const metrics = storage.getMetricsSnapshot();
|
|
296
|
+
storage.resetMetrics();
|
|
297
|
+
unsubscribe();
|
|
415
298
|
```
|
|
416
299
|
|
|
417
|
-
|
|
300
|
+
Secure event observer values are redacted by default. Pass
|
|
301
|
+
`{ redactSecureValues: false }` only in trusted debug tooling where raw values
|
|
302
|
+
are safe to inspect.
|
|
303
|
+
|
|
304
|
+
## Migrations And Transactions
|
|
418
305
|
|
|
419
306
|
```ts
|
|
420
307
|
import {
|
|
421
|
-
|
|
308
|
+
migrateFromMMKV,
|
|
422
309
|
migrateToLatest,
|
|
423
310
|
registerMigration,
|
|
311
|
+
runTransaction,
|
|
424
312
|
} from "react-native-nitro-storage";
|
|
425
313
|
|
|
426
|
-
registerMigration(2, ({ getRaw, setRaw }) => {
|
|
427
|
-
|
|
428
|
-
|
|
314
|
+
registerMigration(2, ({ getRaw, setRaw, removeRaw }) => {
|
|
315
|
+
const oldTheme = getRaw("legacyTheme");
|
|
316
|
+
|
|
317
|
+
if (oldTheme) {
|
|
318
|
+
setRaw("settings:theme", oldTheme);
|
|
319
|
+
removeRaw("legacyTheme");
|
|
429
320
|
}
|
|
430
321
|
});
|
|
431
322
|
|
|
432
323
|
migrateToLatest(StorageScope.Disk);
|
|
433
|
-
```
|
|
434
324
|
|
|
435
|
-
|
|
325
|
+
runTransaction(() => {
|
|
326
|
+
themeItem.set("dark");
|
|
327
|
+
localeItem.set("en-US");
|
|
328
|
+
});
|
|
436
329
|
|
|
437
|
-
|
|
330
|
+
migrateFromMMKV(mmkvInstance, themeItem);
|
|
331
|
+
```
|
|
438
332
|
|
|
439
|
-
|
|
440
|
-
| --------------------------------------------------------------- | ------------------------------------------- |
|
|
441
|
-
| Fast synchronous typed state plus secure storage in one package | `react-native-nitro-storage` |
|
|
442
|
-
| Existing MMKV-only app with no secure storage requirement | `react-native-mmkv` can still be enough |
|
|
443
|
-
| Async key/value persistence only | `@react-native-async-storage/async-storage` |
|
|
444
|
-
| Expo-only secure token storage with async calls | `expo-secure-store` |
|
|
445
|
-
| Keychain/Keystore credentials only | A focused Keychain wrapper may be simpler |
|
|
333
|
+
Transactions roll back local writes if the callback throws.
|
|
446
334
|
|
|
447
|
-
|
|
335
|
+
## Web Backends
|
|
448
336
|
|
|
449
|
-
|
|
337
|
+
```ts
|
|
338
|
+
import {
|
|
339
|
+
setWebDiskStorageBackend,
|
|
340
|
+
setWebSecureStorageBackend,
|
|
341
|
+
} from "react-native-nitro-storage";
|
|
342
|
+
import { createIndexedDBBackend } from "react-native-nitro-storage/indexeddb-backend";
|
|
450
343
|
|
|
451
|
-
|
|
452
|
-
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
- Run the full package gate before release:
|
|
344
|
+
const backend = await createIndexedDBBackend({
|
|
345
|
+
dbName: "app-storage",
|
|
346
|
+
storeName: "kv",
|
|
347
|
+
});
|
|
456
348
|
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
bun run format:check -- --filter=react-native-nitro-storage
|
|
460
|
-
bun run typecheck -- --filter=react-native-nitro-storage
|
|
461
|
-
bun run test:types -- --filter=react-native-nitro-storage
|
|
462
|
-
bun run test -- --filter=react-native-nitro-storage
|
|
463
|
-
bun run test:coverage -- --filter=react-native-nitro-storage
|
|
464
|
-
bun run test:cpp -- --filter=react-native-nitro-storage
|
|
465
|
-
bun run test:cpp:coverage -- --filter=react-native-nitro-storage
|
|
466
|
-
bun run example:doctor
|
|
467
|
-
bun run example:typecheck
|
|
468
|
-
bun run example:prebuild:clean
|
|
469
|
-
bun run example:android:assemble
|
|
470
|
-
bun run example:ios:build
|
|
471
|
-
(cd packages/react-native-nitro-storage && bun run check:pack)
|
|
472
|
-
bun run publish-package:dry -- --yes --with-coverage
|
|
349
|
+
setWebDiskStorageBackend(backend);
|
|
350
|
+
setWebSecureStorageBackend(backend);
|
|
473
351
|
```
|
|
474
352
|
|
|
475
|
-
|
|
476
|
-
|
|
353
|
+
Browser storage cannot provide iOS Keychain or Android Keystore guarantees. Web
|
|
354
|
+
Secure scope is only as strong as the backend you configure.
|
|
355
|
+
|
|
356
|
+
## Platform Support
|
|
357
|
+
|
|
358
|
+
| Platform | Status |
|
|
359
|
+
| -------- | -------------------------------------------------- |
|
|
360
|
+
| iOS | Memory, Disk, and Keychain-backed Secure storage. |
|
|
361
|
+
| Android | Memory, Disk, and Keystore-backed Secure storage. |
|
|
362
|
+
| Web | Memory plus configurable Disk and Secure backends. |
|
|
363
|
+
| Expo | Development builds with the config plugin. |
|
|
364
|
+
|
|
365
|
+
## Documentation
|
|
366
|
+
|
|
367
|
+
| Topic | File |
|
|
368
|
+
| ----------------------------------- | ------------------------------------------------------------------------------ |
|
|
369
|
+
| API reference | [docs/api-reference.md](docs/api-reference.md) |
|
|
370
|
+
| React hooks | [docs/react-hooks.md](docs/react-hooks.md) |
|
|
371
|
+
| Secure storage | [docs/secure-storage.md](docs/secure-storage.md) |
|
|
372
|
+
| Web backends | [docs/web-backends.md](docs/web-backends.md) |
|
|
373
|
+
| Batch, transactions, and migrations | [docs/batch-transactions-migrations.md](docs/batch-transactions-migrations.md) |
|
|
374
|
+
| MMKV migration | [docs/mmkv-migration.md](docs/mmkv-migration.md) |
|
|
375
|
+
| Recipes | [docs/recipes.md](docs/recipes.md) |
|
|
376
|
+
| Benchmarks | [docs/benchmarks.md](docs/benchmarks.md) |
|
|
377
|
+
| Security policy | [SECURITY.md](SECURITY.md) |
|
|
378
|
+
|
|
379
|
+
## Troubleshooting
|
|
380
|
+
|
|
381
|
+
- **Expo Go error:** build a development client; Expo Go cannot load Nitro
|
|
382
|
+
modules.
|
|
383
|
+
- **Secure values fail after Android restore:** keep `configureAndroidBackup:
|
|
384
|
+
true` or provide equivalent backup exclusions.
|
|
385
|
+
- **Biometric prompt does not appear:** set `biometric: true` on the item and
|
|
386
|
+
add native biometric permissions when your app needs them.
|
|
387
|
+
- **Web secure storage is unavailable:** configure a secure backend before using
|
|
388
|
+
Secure scope on web.
|
|
389
|
+
- **TypeScript cannot infer `getBatch()` tuple values:** pass readonly tuples
|
|
390
|
+
with `as const`, or keep batch items in a `const` tuple.
|
|
477
391
|
|
|
478
392
|
## Development
|
|
479
393
|
|
|
480
394
|
```sh
|
|
481
395
|
bun install
|
|
482
|
-
bun run
|
|
483
|
-
bun run
|
|
484
|
-
bun run
|
|
485
|
-
bun run test:
|
|
486
|
-
bun run
|
|
487
|
-
bun run
|
|
396
|
+
bun run check
|
|
397
|
+
bun run test:cpp:asan
|
|
398
|
+
bun run test:cpp:ubsan
|
|
399
|
+
bun run test:cpp:tsan
|
|
400
|
+
bun run release:preflight
|
|
401
|
+
bun run example:android
|
|
402
|
+
bun run example:ios
|
|
488
403
|
```
|
|
489
404
|
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
bun run build -- --filter=react-native-nitro-storage
|
|
494
|
-
bun run benchmark -- --filter=react-native-nitro-storage
|
|
495
|
-
bun run example:doctor
|
|
496
|
-
bun run example:typecheck
|
|
497
|
-
bun run example:prebuild:clean
|
|
498
|
-
bun run example:android:assemble
|
|
499
|
-
bun run example:ios:build
|
|
500
|
-
(cd packages/react-native-nitro-storage && bun run check:pack)
|
|
501
|
-
bun run publish-package:dry -- --yes
|
|
502
|
-
```
|
|
405
|
+
Run native example builds before release when changing plugin, native, Nitro,
|
|
406
|
+
secure storage, or packaging files. The package release path also validates
|
|
407
|
+
package contents and dry-run publish behavior.
|
|
503
408
|
|
|
504
409
|
## License
|
|
505
410
|
|
|
506
|
-
MIT
|
|
411
|
+
[MIT](LICENSE)
|