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