react-native-nitro-storage 0.5.5 → 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 +267 -320
- package/ios/IOSStorageAdapterCpp.mm +126 -80
- package/lib/commonjs/index.js.map +1 -1
- package/lib/commonjs/index.web.js.map +1 -1
- package/lib/commonjs/storage-hooks.js.map +1 -1
- package/lib/module/index.js.map +1 -1
- package/lib/module/index.web.js.map +1 -1
- package/lib/module/storage-hooks.js.map +1 -1
- package/lib/typescript/index.d.ts +11 -6
- package/lib/typescript/index.d.ts.map +1 -1
- package/lib/typescript/index.web.d.ts +7 -2
- package/lib/typescript/index.web.d.ts.map +1 -1
- package/lib/typescript/storage-hooks.d.ts +5 -4
- package/lib/typescript/storage-hooks.d.ts.map +1 -1
- package/package.json +9 -5
- package/react-native-nitro-storage.podspec +4 -0
- package/src/index.ts +16 -10
- package/src/index.web.ts +12 -6
- package/src/storage-hooks.ts +6 -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/)
|
|
6
|
-
[](https://docs.expo.dev/)
|
|
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,160 +85,115 @@ bunx expo install react-native-nitro-storage react-native-nitro-modules
|
|
|
93
85
|
}
|
|
94
86
|
```
|
|
95
87
|
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
Bare React Native projects should install pods after adding the package:
|
|
103
|
-
|
|
104
|
-
```sh
|
|
105
|
-
cd ios && pod install
|
|
106
|
-
```
|
|
107
|
-
|
|
108
|
-
Bare Android apps must initialize the adapter from `MainApplication` before using native storage:
|
|
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. |
|
|
109
93
|
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
override fun onCreate() {
|
|
114
|
-
super.onCreate()
|
|
115
|
-
AndroidStorageAdapter.init(this)
|
|
116
|
-
}
|
|
117
|
-
```
|
|
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.
|
|
118
97
|
|
|
119
98
|
## Quick Start
|
|
120
99
|
|
|
121
|
-
Create storage items outside React render functions, then use them from anywhere.
|
|
122
|
-
|
|
123
100
|
```ts
|
|
124
101
|
import {
|
|
125
|
-
createStorageItem,
|
|
126
102
|
StorageScope,
|
|
127
|
-
|
|
103
|
+
createStorageItem,
|
|
104
|
+
storage,
|
|
128
105
|
} from "react-native-nitro-storage";
|
|
129
106
|
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
export const themeItem = createStorageItem<Theme>({
|
|
107
|
+
const themeItem = createStorageItem<"light" | "dark">({
|
|
133
108
|
key: "theme",
|
|
109
|
+
namespace: "settings",
|
|
134
110
|
scope: StorageScope.Disk,
|
|
135
|
-
defaultValue: "
|
|
136
|
-
validate: (value): value is Theme =>
|
|
137
|
-
value === "system" || value === "light" || value === "dark",
|
|
111
|
+
defaultValue: "light",
|
|
138
112
|
});
|
|
139
113
|
|
|
140
|
-
|
|
141
|
-
const [theme, setTheme] = useStorage(themeItem);
|
|
114
|
+
themeItem.set("dark");
|
|
142
115
|
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
title={`Theme: ${theme}`}
|
|
146
|
-
onPress={() => setTheme(theme === "dark" ? "light" : "dark")}
|
|
147
|
-
/>
|
|
148
|
-
);
|
|
149
|
-
}
|
|
116
|
+
const theme = themeItem.get();
|
|
117
|
+
const raw = storage.getString("settings:theme", StorageScope.Disk);
|
|
150
118
|
```
|
|
151
119
|
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
```ts
|
|
155
|
-
import {
|
|
156
|
-
AccessControl,
|
|
157
|
-
BiometricLevel,
|
|
158
|
-
createStorageItem,
|
|
159
|
-
StorageScope,
|
|
160
|
-
} from "react-native-nitro-storage";
|
|
161
|
-
|
|
162
|
-
export const refreshTokenItem = createStorageItem<string>({
|
|
163
|
-
key: "refreshToken",
|
|
164
|
-
namespace: "auth",
|
|
165
|
-
scope: StorageScope.Secure,
|
|
166
|
-
defaultValue: "",
|
|
167
|
-
biometric: true,
|
|
168
|
-
biometricLevel: BiometricLevel.BiometryOrPasscode,
|
|
169
|
-
accessControl: AccessControl.AfterFirstUnlockThisDeviceOnly,
|
|
170
|
-
});
|
|
120
|
+
## Typed Storage Items
|
|
171
121
|
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
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.
|
|
176
125
|
|
|
177
126
|
```ts
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
127
|
+
type Preferences = {
|
|
128
|
+
theme: "system" | "light" | "dark";
|
|
129
|
+
compactMode: boolean;
|
|
130
|
+
};
|
|
182
131
|
|
|
183
|
-
|
|
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
|
+
});
|
|
184
140
|
|
|
185
|
-
|
|
186
|
-
|
|
141
|
+
preferencesItem.set((previous) => ({
|
|
142
|
+
...previous,
|
|
143
|
+
compactMode: !previous.compactMode,
|
|
144
|
+
}));
|
|
187
145
|
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
146
|
+
const snapshot = preferencesItem.getWithVersion();
|
|
147
|
+
const didWrite = preferencesItem.setIfVersion(snapshot.version, {
|
|
148
|
+
...snapshot.value,
|
|
149
|
+
theme: "dark",
|
|
191
150
|
});
|
|
192
151
|
```
|
|
193
152
|
|
|
194
|
-
|
|
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.
|
|
195
156
|
|
|
196
|
-
|
|
197
|
-
import { StorageScope, storage } from "react-native-nitro-storage";
|
|
198
|
-
|
|
199
|
-
const snapshot = storage.export(StorageScope.Disk);
|
|
157
|
+
## React Hooks
|
|
200
158
|
|
|
201
|
-
|
|
202
|
-
|
|
159
|
+
```tsx
|
|
160
|
+
import { Switch } from "react-native";
|
|
161
|
+
import { useSetStorage, useStorage } from "react-native-nitro-storage";
|
|
203
162
|
|
|
204
|
-
|
|
163
|
+
export function ThemeToggle() {
|
|
164
|
+
const [theme] = useStorage(themeItem);
|
|
165
|
+
const setTheme = useSetStorage(themeItem);
|
|
205
166
|
|
|
206
|
-
|
|
167
|
+
return (
|
|
168
|
+
<Switch
|
|
169
|
+
value={theme === "dark"}
|
|
170
|
+
onValueChange={(enabled) => setTheme(enabled ? "dark" : "light")}
|
|
171
|
+
/>
|
|
172
|
+
);
|
|
173
|
+
}
|
|
174
|
+
```
|
|
207
175
|
|
|
208
|
-
|
|
209
|
-
|
|
176
|
+
Use `useStorageSelector()` when a component needs a derived value instead of the
|
|
177
|
+
whole stored object.
|
|
210
178
|
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
StorageScope.Disk,
|
|
214
|
-
(event) => {
|
|
215
|
-
if (event.type === "batch") {
|
|
216
|
-
console.log("settings changed", event.changes.length);
|
|
217
|
-
return;
|
|
218
|
-
}
|
|
179
|
+
```tsx
|
|
180
|
+
import { useStorageSelector } from "react-native-nitro-storage";
|
|
219
181
|
|
|
220
|
-
|
|
221
|
-
|
|
182
|
+
const [compactMode] = useStorageSelector(
|
|
183
|
+
preferencesItem,
|
|
184
|
+
(preferences) => preferences.compactMode,
|
|
222
185
|
);
|
|
223
186
|
```
|
|
224
187
|
|
|
225
188
|
## Storage Scopes
|
|
226
189
|
|
|
227
|
-
| Scope | Backing store
|
|
228
|
-
| --------------------- |
|
|
229
|
-
| `StorageScope.Memory` |
|
|
230
|
-
| `StorageScope.Disk` | UserDefaults on iOS, SharedPreferences on Android, web
|
|
231
|
-
| `StorageScope.Secure` | iOS
|
|
232
|
-
|
|
233
|
-
Use Secure scope only for secrets. Disk scope is faster and easier to inspect, but it is not a secret store.
|
|
234
|
-
|
|
235
|
-
## Docs
|
|
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. |
|
|
236
195
|
|
|
237
|
-
|
|
238
|
-
| ------------------------------------------- | ------------------------------------------------------------------------------ |
|
|
239
|
-
| Learn the public API | [docs/api-reference.md](docs/api-reference.md) |
|
|
240
|
-
| Bind storage to React | [docs/react-hooks.md](docs/react-hooks.md) |
|
|
241
|
-
| Store tokens or biometric secrets | [docs/secure-storage.md](docs/secure-storage.md) |
|
|
242
|
-
| Use batch APIs, transactions, or migrations | [docs/batch-transactions-migrations.md](docs/batch-transactions-migrations.md) |
|
|
243
|
-
| Configure web Disk/Secure storage | [docs/web-backends.md](docs/web-backends.md) |
|
|
244
|
-
| Migrate from `react-native-mmkv` | [docs/mmkv-migration.md](docs/mmkv-migration.md) |
|
|
245
|
-
| Copy working snippets | [docs/recipes.md](docs/recipes.md) |
|
|
246
|
-
| Run or interpret benchmarks | [docs/benchmarks.md](docs/benchmarks.md) |
|
|
247
|
-
| Report a vulnerability | [SECURITY.md](SECURITY.md) |
|
|
248
|
-
|
|
249
|
-
## API Snapshot
|
|
196
|
+
## Secure Storage
|
|
250
197
|
|
|
251
198
|
```ts
|
|
252
199
|
import {
|
|
@@ -255,209 +202,209 @@ import {
|
|
|
255
202
|
StorageScope,
|
|
256
203
|
createSecureAuthStorage,
|
|
257
204
|
createStorageItem,
|
|
258
|
-
flushWebStorageBackends,
|
|
259
|
-
getBatch,
|
|
260
|
-
getStorageErrorCode,
|
|
261
205
|
isKeychainLockedError,
|
|
262
|
-
migrateFromMMKV,
|
|
263
|
-
migrateToLatest,
|
|
264
|
-
registerMigration,
|
|
265
|
-
removeBatch,
|
|
266
|
-
runTransaction,
|
|
267
|
-
setBatch,
|
|
268
|
-
setWebDiskStorageBackend,
|
|
269
|
-
setWebSecureStorageBackend,
|
|
270
206
|
storage,
|
|
271
|
-
useSetStorage,
|
|
272
|
-
useStorage,
|
|
273
|
-
useStorageSelector,
|
|
274
207
|
} from "react-native-nitro-storage";
|
|
275
|
-
```
|
|
276
208
|
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
- `createSecureAuthStorage` for a compact secure-token item map.
|
|
285
|
-
- `setWebDiskStorageBackend`, `setWebSecureStorageBackend`, and `createIndexedDBBackend` for web persistence.
|
|
286
|
-
|
|
287
|
-
See the full [API reference](docs/api-reference.md).
|
|
288
|
-
|
|
289
|
-
## TypeScript And IDE Safety
|
|
290
|
-
|
|
291
|
-
The public API is designed around typed storage items. Define the value type, default value, serializer, parser, and validator in one place so reads, writes, React hooks, batch APIs, migrations, and transactions all share the same contract.
|
|
209
|
+
const refreshToken = createStorageItem<string>({
|
|
210
|
+
key: "refreshToken",
|
|
211
|
+
namespace: "auth",
|
|
212
|
+
scope: StorageScope.Secure,
|
|
213
|
+
defaultValue: "",
|
|
214
|
+
accessControl: AccessControl.AfterFirstUnlockThisDeviceOnly,
|
|
215
|
+
});
|
|
292
216
|
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
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
|
+
});
|
|
298
225
|
|
|
299
|
-
const
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
typeof value === "object" &&
|
|
305
|
-
value !== null &&
|
|
306
|
-
["system", "light", "dark"].includes(
|
|
307
|
-
(value as Partial<Preferences>).theme ?? "",
|
|
308
|
-
) &&
|
|
309
|
-
typeof (value as Partial<Preferences>).compactMode === "boolean",
|
|
226
|
+
const auth = createSecureAuthStorage({
|
|
227
|
+
accessToken: { ttlMs: 15 * 60_000 },
|
|
228
|
+
refreshToken: {
|
|
229
|
+
accessControl: AccessControl.AfterFirstUnlockThisDeviceOnly,
|
|
230
|
+
},
|
|
310
231
|
});
|
|
311
232
|
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
233
|
+
try {
|
|
234
|
+
recoveryCode.get();
|
|
235
|
+
} catch (error) {
|
|
236
|
+
if (isKeychainLockedError(error)) {
|
|
237
|
+
storage.getSecurityCapabilities();
|
|
238
|
+
}
|
|
239
|
+
}
|
|
316
240
|
```
|
|
317
241
|
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
WebSecureStorageBackend,
|
|
324
|
-
} from "react-native-nitro-storage";
|
|
325
|
-
```
|
|
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 }`.
|
|
326
247
|
|
|
327
|
-
##
|
|
248
|
+
## Batch Operations
|
|
328
249
|
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
| iOS | Supported | Disk uses app-suite `UserDefaults`; Secure uses Keychain. |
|
|
332
|
-
| Android | Supported | Disk uses SharedPreferences; Secure uses Keystore-backed EncryptedSharedPreferences. |
|
|
333
|
-
| Expo | Supported | Add the included config plugin before prebuild. |
|
|
334
|
-
| Web | Supported | Defaults to localStorage-style backends; IndexedDB backend is available for persistent Secure storage. |
|
|
335
|
-
|
|
336
|
-
Peer dependencies:
|
|
337
|
-
|
|
338
|
-
- `react >=18.2.0`
|
|
339
|
-
- `react-native >=0.75.0`
|
|
340
|
-
- `react-native-nitro-modules >=0.35.6`
|
|
341
|
-
|
|
342
|
-
## Security Model
|
|
343
|
-
|
|
344
|
-
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.
|
|
345
252
|
|
|
346
253
|
```ts
|
|
347
|
-
|
|
348
|
-
const metadata = storage.getSecureMetadata("auth:refreshToken");
|
|
349
|
-
```
|
|
350
|
-
|
|
351
|
-
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";
|
|
352
255
|
|
|
353
|
-
|
|
256
|
+
const localeItem = createStorageItem({
|
|
257
|
+
key: "locale",
|
|
258
|
+
namespace: "settings",
|
|
259
|
+
scope: StorageScope.Disk,
|
|
260
|
+
defaultValue: "en-US",
|
|
261
|
+
});
|
|
354
262
|
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
263
|
+
const [theme, locale] = getBatch(
|
|
264
|
+
[themeItem, localeItem] as const,
|
|
265
|
+
StorageScope.Disk,
|
|
266
|
+
);
|
|
359
267
|
|
|
360
|
-
|
|
268
|
+
setBatch(
|
|
269
|
+
[
|
|
270
|
+
{ item: themeItem, value: "dark" },
|
|
271
|
+
{ item: localeItem, value: "en-US" },
|
|
272
|
+
],
|
|
273
|
+
StorageScope.Disk,
|
|
274
|
+
);
|
|
361
275
|
|
|
362
|
-
|
|
276
|
+
removeBatch([themeItem, localeItem], StorageScope.Disk);
|
|
277
|
+
```
|
|
363
278
|
|
|
364
|
-
|
|
279
|
+
## Events And Observability
|
|
365
280
|
|
|
366
281
|
```ts
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
migrateFromMMKV(mmkv, refreshTokenItem, true);
|
|
371
|
-
```
|
|
282
|
+
const unsubscribe = storage.subscribeNamespace("settings", (event) => {
|
|
283
|
+
console.log(event.key, event.operation, event.source);
|
|
284
|
+
});
|
|
372
285
|
|
|
373
|
-
|
|
286
|
+
storage.setEventObserver((event) => {
|
|
287
|
+
console.log(event.type, event.scope);
|
|
288
|
+
});
|
|
374
289
|
|
|
375
|
-
|
|
376
|
-
|
|
290
|
+
storage.setMetricsObserver((event) => {
|
|
291
|
+
console.log(event.operation, event.durationMs);
|
|
292
|
+
});
|
|
377
293
|
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
}
|
|
294
|
+
const metrics = storage.getMetricsSnapshot();
|
|
295
|
+
storage.resetMetrics();
|
|
296
|
+
unsubscribe();
|
|
382
297
|
```
|
|
383
298
|
|
|
384
|
-
|
|
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
|
|
385
304
|
|
|
386
305
|
```ts
|
|
387
306
|
import {
|
|
388
|
-
|
|
307
|
+
migrateFromMMKV,
|
|
389
308
|
migrateToLatest,
|
|
390
309
|
registerMigration,
|
|
310
|
+
runTransaction,
|
|
391
311
|
} from "react-native-nitro-storage";
|
|
392
312
|
|
|
393
|
-
registerMigration(2, ({ getRaw, setRaw }) => {
|
|
394
|
-
|
|
395
|
-
|
|
313
|
+
registerMigration(2, ({ getRaw, setRaw, removeRaw }) => {
|
|
314
|
+
const oldTheme = getRaw("legacyTheme");
|
|
315
|
+
|
|
316
|
+
if (oldTheme) {
|
|
317
|
+
setRaw("settings:theme", oldTheme);
|
|
318
|
+
removeRaw("legacyTheme");
|
|
396
319
|
}
|
|
397
320
|
});
|
|
398
321
|
|
|
399
322
|
migrateToLatest(StorageScope.Disk);
|
|
400
|
-
```
|
|
401
323
|
|
|
402
|
-
|
|
324
|
+
runTransaction(() => {
|
|
325
|
+
themeItem.set("dark");
|
|
326
|
+
localeItem.set("en-US");
|
|
327
|
+
});
|
|
403
328
|
|
|
404
|
-
|
|
329
|
+
migrateFromMMKV(mmkvInstance, themeItem);
|
|
330
|
+
```
|
|
405
331
|
|
|
406
|
-
|
|
407
|
-
| --------------------------------------------------------------- | ------------------------------------------- |
|
|
408
|
-
| Fast synchronous typed state plus secure storage in one package | `react-native-nitro-storage` |
|
|
409
|
-
| Existing MMKV-only app with no secure storage requirement | `react-native-mmkv` can still be enough |
|
|
410
|
-
| Async key/value persistence only | `@react-native-async-storage/async-storage` |
|
|
411
|
-
| Expo-only secure token storage with async calls | `expo-secure-store` |
|
|
412
|
-
| Keychain/Keystore credentials only | A focused Keychain wrapper may be simpler |
|
|
332
|
+
Transactions roll back local writes if the callback throws.
|
|
413
333
|
|
|
414
|
-
|
|
334
|
+
## Web Backends
|
|
415
335
|
|
|
416
|
-
|
|
336
|
+
```ts
|
|
337
|
+
import {
|
|
338
|
+
setWebDiskStorageBackend,
|
|
339
|
+
setWebSecureStorageBackend,
|
|
340
|
+
} from "react-native-nitro-storage";
|
|
341
|
+
import { createIndexedDBBackend } from "react-native-nitro-storage/indexeddb-backend";
|
|
417
342
|
|
|
418
|
-
|
|
419
|
-
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
- Run the full package gate before release:
|
|
343
|
+
const backend = await createIndexedDBBackend({
|
|
344
|
+
dbName: "app-storage",
|
|
345
|
+
storeName: "kv",
|
|
346
|
+
});
|
|
423
347
|
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
bun run format:check -- --filter=react-native-nitro-storage
|
|
427
|
-
bun run typecheck -- --filter=react-native-nitro-storage
|
|
428
|
-
bun run test:types -- --filter=react-native-nitro-storage
|
|
429
|
-
bun run test -- --filter=react-native-nitro-storage
|
|
430
|
-
bun run test:coverage -- --filter=react-native-nitro-storage
|
|
431
|
-
bun run test:cpp -- --filter=react-native-nitro-storage
|
|
432
|
-
bun run test:cpp:coverage -- --filter=react-native-nitro-storage
|
|
433
|
-
(cd packages/react-native-nitro-storage && bun run check:pack)
|
|
434
|
-
bun run publish-package:dry -- --yes --with-coverage
|
|
348
|
+
setWebDiskStorageBackend(backend);
|
|
349
|
+
setWebSecureStorageBackend(backend);
|
|
435
350
|
```
|
|
436
351
|
|
|
437
|
-
|
|
438
|
-
|
|
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.
|
|
439
390
|
|
|
440
391
|
## Development
|
|
441
392
|
|
|
442
393
|
```sh
|
|
443
394
|
bun install
|
|
444
|
-
bun run
|
|
445
|
-
bun run
|
|
446
|
-
bun run
|
|
447
|
-
bun run test:
|
|
448
|
-
bun run
|
|
449
|
-
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
|
|
450
402
|
```
|
|
451
403
|
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
bun run build -- --filter=react-native-nitro-storage
|
|
456
|
-
bun run benchmark -- --filter=react-native-nitro-storage
|
|
457
|
-
(cd packages/react-native-nitro-storage && bun run check:pack)
|
|
458
|
-
bun run publish-package:dry -- --yes
|
|
459
|
-
```
|
|
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.
|
|
460
407
|
|
|
461
408
|
## License
|
|
462
409
|
|
|
463
|
-
MIT
|
|
410
|
+
[MIT](LICENSE)
|