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 CHANGED
@@ -1,81 +1,73 @@
1
1
  # react-native-nitro-storage
2
2
 
3
- [![npm](https://img.shields.io/npm/v/react-native-nitro-storage)](https://www.npmjs.com/package/react-native-nitro-storage)
4
- [![MIT license](https://img.shields.io/badge/license-MIT-blue.svg)](LICENSE)
3
+ [![npm version](https://img.shields.io/npm/v/react-native-nitro-storage?color=f97316&label=npm)](https://www.npmjs.com/package/react-native-nitro-storage)
4
+ [![npm downloads](https://img.shields.io/npm/dm/react-native-nitro-storage?color=22c55e&label=downloads)](https://www.npmjs.com/package/react-native-nitro-storage)
5
+ [![CI](https://github.com/JoaoPauloCMarra/react-native-nitro-storage/actions/workflows/ci.yml/badge.svg)](https://github.com/JoaoPauloCMarra/react-native-nitro-storage/actions/workflows/ci.yml)
6
+ [![license](https://img.shields.io/npm/l/react-native-nitro-storage?color=007ec6)](https://github.com/JoaoPauloCMarra/react-native-nitro-storage/blob/main/LICENSE)
5
7
  [![React Native](https://img.shields.io/badge/react--native-%3E%3D0.75-61dafb)](https://reactnative.dev/)
6
- [![Nitro Modules](https://img.shields.io/badge/nitro--modules-%3E%3D0.35.6-black)](https://nitro.margelo.com/)
8
+ [![Expo](https://img.shields.io/badge/expo-SDK%2056-000020)](https://docs.expo.dev/)
9
+ [![Nitro Modules](https://img.shields.io/badge/nitro--modules-%3E%3D0.35.7-black)](https://nitro.margelo.com/)
7
10
 
8
- One storage layer for render-time state, persisted app state, and native secrets.
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
- Nitro Storage is a synchronous React Native storage library built on Nitro Modules and JSI. It exposes typed Memory, Disk, and Secure storage with React hooks, batch APIs, transactions, migrations, biometric access control, MMKV migration helpers, and a web IndexedDB backend.
11
-
12
- Use it when you want one storage API for React Native and web, with fast synchronous reads and native secure storage instead of mixing AsyncStorage, SecureStore, Keychain wrappers, MMKV adapters, and custom React state glue.
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
- - [Docs](#docs)
23
- - [TypeScript And IDE Safety](#typescript-and-ide-safety)
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
- - [Security Model](#security-model)
26
- - [Migration Paths](#migration-paths)
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
- Use the equivalent command for npm, Yarn, or pnpm if your app does not use Bun.
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 projects, install the native packages, add the config plugin, and prebuild:
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 protect your secure data with Face ID",
87
- "addBiometricPermissions": true,
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
- ```sh
97
- bunx expo prebuild
98
- ```
99
-
100
- The Expo plugin sets `NSFaceIDUsageDescription`, can opt into Android biometric permissions, initializes the Android storage adapter in `MainApplication`, and writes Android backup rules that exclude Nitro Storage secure preference files from cloud backup and device transfer. Set `configureAndroidBackup: false` only when you maintain equivalent backup rules yourself.
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
- ```kt
111
- import com.nitrostorage.AndroidStorageAdapter
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
- useStorage,
103
+ createStorageItem,
104
+ storage,
128
105
  } from "react-native-nitro-storage";
129
106
 
130
- type Theme = "system" | "light" | "dark";
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: "system",
136
- validate: (value): value is Theme =>
137
- value === "system" || value === "light" || value === "dark",
111
+ defaultValue: "light",
138
112
  });
139
113
 
140
- export function ThemeButton() {
141
- const [theme, setTheme] = useStorage(themeItem);
114
+ themeItem.set("dark");
142
115
 
143
- return (
144
- <Button
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
- Secure values use the same item API:
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
- refreshTokenItem.set("opaque-refresh-token");
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
- import { StorageScope, getBatch } from "react-native-nitro-storage";
179
-
180
- const [theme] = getBatch([themeItem], StorageScope.Disk);
181
- ```
127
+ type Preferences = {
128
+ theme: "system" | "light" | "dark";
129
+ compactMode: boolean;
130
+ };
182
131
 
183
- Keep multi-step local writes reversible:
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
- ```ts
186
- import { StorageScope, runTransaction } from "react-native-nitro-storage";
141
+ preferencesItem.set((previous) => ({
142
+ ...previous,
143
+ compactMode: !previous.compactMode,
144
+ }));
187
145
 
188
- runTransaction(StorageScope.Disk, (tx) => {
189
- tx.setItem(themeItem, "dark");
190
- tx.setRaw("onboarding:complete", "true");
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
- Create a raw snapshot for backups or test fixtures, then restore it later:
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
- ```ts
197
- import { StorageScope, storage } from "react-native-nitro-storage";
198
-
199
- const snapshot = storage.export(StorageScope.Disk);
157
+ ## React Hooks
200
158
 
201
- storage.import(snapshot, StorageScope.Disk);
202
- ```
159
+ ```tsx
160
+ import { Switch } from "react-native";
161
+ import { useSetStorage, useStorage } from "react-native-nitro-storage";
203
162
 
204
- `storage.export(StorageScope.Secure)` throws unless you pass `{ includeSecureValues: true }`. Prefer `storage.exportSecureUnsafe()` only for short-lived in-memory secure migration workflows. Do not log Secure exports or include them in diagnostics, analytics, crash reports, or support bundles.
163
+ export function ThemeToggle() {
164
+ const [theme] = useStorage(themeItem);
165
+ const setTheme = useSetStorage(themeItem);
205
166
 
206
- Subscribe to storage changes outside React:
167
+ return (
168
+ <Switch
169
+ value={theme === "dark"}
170
+ onValueChange={(enabled) => setTheme(enabled ? "dark" : "light")}
171
+ />
172
+ );
173
+ }
174
+ ```
207
175
 
208
- ```ts
209
- import { StorageScope, storage } from "react-native-nitro-storage";
176
+ Use `useStorageSelector()` when a component needs a derived value instead of the
177
+ whole stored object.
210
178
 
211
- const unsubscribe = storage.subscribeNamespace(
212
- "settings",
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
- console.log(event.key, event.operation);
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 | Best for |
228
- | --------------------- | ----------------------------------------------------------------------------- | ------------------------------------------------ |
229
- | `StorageScope.Memory` | JS memory | Session flags, wizard state, optimistic UI cache |
230
- | `StorageScope.Disk` | UserDefaults on iOS, SharedPreferences on Android, web Disk backend | Preferences, feature flags, non-secret app state |
231
- | `StorageScope.Secure` | iOS Keychain, Android Keystore/EncryptedSharedPreferences, web Secure backend | Tokens, credentials, device-bound secrets |
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
- | Task | Start here |
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
- The main building blocks are:
278
-
279
- - `createStorageItem<T>(config)` for typed values.
280
- - `storage` for raw reads, namespace cleanup, events, secure metadata, metrics, and runtime capability checks.
281
- - `getBatch`, `setBatch`, and `removeBatch` for multi-key work.
282
- - `runTransaction` for synchronous rollback on failure.
283
- - `registerMigration` and `migrateToLatest` for versioned local data migrations.
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
- ```ts
294
- type Preferences = {
295
- theme: "system" | "light" | "dark";
296
- compactMode: boolean;
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 preferencesItem = createStorageItem<Preferences>({
300
- key: "preferences",
301
- scope: StorageScope.Disk,
302
- defaultValue: { theme: "system", compactMode: false },
303
- validate: (value): value is Preferences =>
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
- preferencesItem.set((current) => ({
313
- ...current,
314
- compactMode: !current.compactMode,
315
- }));
233
+ try {
234
+ recoveryCode.get();
235
+ } catch (error) {
236
+ if (isKeychainLockedError(error)) {
237
+ storage.getSecurityCapabilities();
238
+ }
239
+ }
316
240
  ```
317
241
 
318
- For web backend overrides, import the backend contracts instead of using loose object shapes:
319
-
320
- ```ts
321
- import type {
322
- WebDiskStorageBackend,
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
- ## Platform Support
248
+ ## Batch Operations
328
249
 
329
- | Platform | Status | Notes |
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
- const capabilities = storage.getSecurityCapabilities();
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
- Important boundaries:
256
+ const localeItem = createStorageItem({
257
+ key: "locale",
258
+ namespace: "settings",
259
+ scope: StorageScope.Disk,
260
+ defaultValue: "en-US",
261
+ });
354
262
 
355
- - Disk and Memory scopes are not secret stores.
356
- - Web Secure storage depends on the backend you configure; browser storage cannot provide iOS Keychain or Android Keystore guarantees.
357
- - Hardware-backed secure storage is reported as `unknown` unless the platform can prove it through the native backend.
358
- - Secret values should not be logged, exported in diagnostics, or copied into crash reports.
263
+ const [theme, locale] = getBatch(
264
+ [themeItem, localeItem] as const,
265
+ StorageScope.Disk,
266
+ );
359
267
 
360
- Read [docs/secure-storage.md](docs/secure-storage.md) and [SECURITY.md](SECURITY.md) before using Secure scope for production auth tokens.
268
+ setBatch(
269
+ [
270
+ { item: themeItem, value: "dark" },
271
+ { item: localeItem, value: "en-US" },
272
+ ],
273
+ StorageScope.Disk,
274
+ );
361
275
 
362
- ## Migration Paths
276
+ removeBatch([themeItem, localeItem], StorageScope.Disk);
277
+ ```
363
278
 
364
- From `react-native-mmkv`, migrate existing keys in place and keep your typed item API as the destination:
279
+ ## Events And Observability
365
280
 
366
281
  ```ts
367
- import { migrateFromMMKV } from "react-native-nitro-storage";
368
-
369
- migrateFromMMKV(mmkv, themeItem, true);
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
- From `AsyncStorage`, `expo-secure-store`, Keychain wrappers, or a custom storage adapter, run a one-time startup migration: read the old value, validate it, write it through the matching `StorageItem`, then stop reading the old key after the migration ships broadly.
286
+ storage.setEventObserver((event) => {
287
+ console.log(event.type, event.scope);
288
+ });
374
289
 
375
- ```ts
376
- const oldTheme = await AsyncStorage.getItem("theme");
290
+ storage.setMetricsObserver((event) => {
291
+ console.log(event.operation, event.durationMs);
292
+ });
377
293
 
378
- if (oldTheme === "light" || oldTheme === "dark" || oldTheme === "system") {
379
- themeItem.set(oldTheme);
380
- await AsyncStorage.removeItem("theme");
381
- }
294
+ const metrics = storage.getMetricsSnapshot();
295
+ storage.resetMetrics();
296
+ unsubscribe();
382
297
  ```
383
298
 
384
- For versioned local data, keep migrations explicit and repeatable:
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
- StorageScope,
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
- if (getRaw("theme") === "auto") {
395
- setRaw("theme", "system");
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
- See [docs/mmkv-migration.md](docs/mmkv-migration.md) and [docs/batch-transactions-migrations.md](docs/batch-transactions-migrations.md) for the full flows.
324
+ runTransaction(() => {
325
+ themeItem.set("dark");
326
+ localeItem.set("en-US");
327
+ });
403
328
 
404
- ## Choosing a Storage Library
329
+ migrateFromMMKV(mmkvInstance, themeItem);
330
+ ```
405
331
 
406
- | Need | Good fit |
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
- Nitro Storage is strongest when the app needs synchronous reads, React bindings, typed values, secure storage, and migration utilities together. It is not trying to replace databases, query caches, or server-state libraries.
334
+ ## Web Backends
415
335
 
416
- ## Production Checklist
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
- - Use `StorageScope.Secure` only for secrets and tokens.
419
- - Keep secrets out of logs, exports, analytics, and crash reports.
420
- - Test biometric and Keychain/Keystore flows on real iOS and Android devices.
421
- - Configure a web Secure backend intentionally; browser storage does not provide native Keychain or Keystore guarantees.
422
- - Run the full package gate before release:
343
+ const backend = await createIndexedDBBackend({
344
+ dbName: "app-storage",
345
+ storeName: "kv",
346
+ });
423
347
 
424
- ```sh
425
- bun run lint -- --filter=react-native-nitro-storage
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
- Publishing a GitHub Release tagged `v<package.version>` triggers the npm workflow.
438
- The npm package must trust `.github/workflows/npm-publish.yml` in npm package settings.
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 lint -- --filter=react-native-nitro-storage
445
- bun run format:check -- --filter=react-native-nitro-storage
446
- bun run typecheck -- --filter=react-native-nitro-storage
447
- bun run test:types -- --filter=react-native-nitro-storage
448
- bun run test -- --filter=react-native-nitro-storage
449
- bun run test:cpp -- --filter=react-native-nitro-storage
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
- Release checks:
453
-
454
- ```sh
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)