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 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/)
8
+ [![Expo](https://img.shields.io/badge/expo-SDK%2056-000020)](https://docs.expo.dev/)
6
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,165 +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
- ```
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 example project is aligned with Expo SDK 56, React Native 0.85, and React 19.2.
101
-
102
- 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.
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
- type StorageItemConfig,
130
- useStorage,
103
+ createStorageItem,
104
+ storage,
131
105
  } from "react-native-nitro-storage";
132
106
 
133
- type Theme = "system" | "light" | "dark";
134
-
135
- const themeConfig = {
107
+ const themeItem = createStorageItem<"light" | "dark">({
136
108
  key: "theme",
109
+ namespace: "settings",
137
110
  scope: StorageScope.Disk,
138
- defaultValue: "system",
139
- validate: (value): value is Theme =>
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
- export function ThemeButton() {
146
- const [theme, setTheme] = useStorage(themeItem);
114
+ themeItem.set("dark");
147
115
 
148
- return (
149
- <Button
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
- Secure values use the same item API:
120
+ ## Typed Storage Items
158
121
 
159
- ```ts
160
- import {
161
- AccessControl,
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
- import { StorageScope, getBatch } from "react-native-nitro-storage";
184
-
185
- const [theme] = getBatch([themeItem], StorageScope.Disk);
186
- ```
127
+ type Preferences = {
128
+ theme: "system" | "light" | "dark";
129
+ compactMode: boolean;
130
+ };
187
131
 
188
- 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
+ });
189
140
 
190
- ```ts
191
- import { StorageScope, runTransaction } from "react-native-nitro-storage";
141
+ preferencesItem.set((previous) => ({
142
+ ...previous,
143
+ compactMode: !previous.compactMode,
144
+ }));
192
145
 
193
- runTransaction(StorageScope.Disk, (tx) => {
194
- tx.setItem(themeItem, "dark");
195
- tx.setRaw("onboarding:complete", "true");
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
- 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.
200
156
 
201
- ```ts
202
- import { StorageScope, storage } from "react-native-nitro-storage";
203
-
204
- const snapshot = storage.export(StorageScope.Disk);
157
+ ## React Hooks
205
158
 
206
- storage.import(snapshot, StorageScope.Disk);
207
- ```
159
+ ```tsx
160
+ import { Switch } from "react-native";
161
+ import { useSetStorage, useStorage } from "react-native-nitro-storage";
208
162
 
209
- `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);
210
166
 
211
- 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
+ ```
212
175
 
213
- ```ts
214
- 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.
215
178
 
216
- const unsubscribe = storage.subscribeNamespace(
217
- "settings",
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
- console.log(event.key, event.operation);
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 | Best for |
233
- | --------------------- | ----------------------------------------------------------------------------- | ------------------------------------------------ |
234
- | `StorageScope.Memory` | JS memory | Session flags, wizard state, optimistic UI cache |
235
- | `StorageScope.Disk` | UserDefaults on iOS, SharedPreferences on Android, web Disk backend | Preferences, feature flags, non-secret app state |
236
- | `StorageScope.Secure` | iOS Keychain, Android Keystore/EncryptedSharedPreferences, web Secure backend | Tokens, credentials, device-bound secrets |
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
- ## API Snapshot
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
- 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
+ });
297
216
 
298
- ```ts
299
- import {
300
- StorageScope,
301
- createStorageItem,
302
- type StorageItemConfig,
303
- } from "react-native-nitro-storage";
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
- type Preferences = {
306
- theme: "system" | "light" | "dark";
307
- compactMode: boolean;
308
- };
226
+ const auth = createSecureAuthStorage({
227
+ accessToken: { ttlMs: 15 * 60_000 },
228
+ refreshToken: {
229
+ accessControl: AccessControl.AfterFirstUnlockThisDeviceOnly,
230
+ },
231
+ });
309
232
 
310
- function isPreferences(value: unknown): value is Preferences {
311
- if (typeof value !== "object" || value === null) {
312
- return false;
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
- For web backend overrides, import the backend contracts instead of using loose object shapes:
340
-
341
- ```ts
342
- import type {
343
- WebDiskStorageBackend,
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
- | Platform | Status | Notes |
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
- Tested release matrix:
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
- const capabilities = storage.getSecurityCapabilities();
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
- Important boundaries:
256
+ const localeItem = createStorageItem({
257
+ key: "locale",
258
+ namespace: "settings",
259
+ scope: StorageScope.Disk,
260
+ defaultValue: "en-US",
261
+ });
387
262
 
388
- - Disk and Memory scopes are not secret stores.
389
- - Web Secure storage depends on the backend you configure; browser storage cannot provide iOS Keychain or Android Keystore guarantees.
390
- - Hardware-backed secure storage is reported as `unknown` unless the platform can prove it through the native backend.
391
- - 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
+ );
392
267
 
393
- 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
+ );
394
275
 
395
- ## Migration Paths
276
+ removeBatch([themeItem, localeItem], StorageScope.Disk);
277
+ ```
396
278
 
397
- From `react-native-mmkv`, migrate existing keys in place and keep your typed item API as the destination:
279
+ ## Events And Observability
398
280
 
399
281
  ```ts
400
- import { migrateFromMMKV } from "react-native-nitro-storage";
401
-
402
- migrateFromMMKV(mmkv, themeItem, true);
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
- 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
+ });
407
289
 
408
- ```ts
409
- const oldTheme = await AsyncStorage.getItem("theme");
290
+ storage.setMetricsObserver((event) => {
291
+ console.log(event.operation, event.durationMs);
292
+ });
410
293
 
411
- if (oldTheme === "light" || oldTheme === "dark" || oldTheme === "system") {
412
- themeItem.set(oldTheme);
413
- await AsyncStorage.removeItem("theme");
414
- }
294
+ const metrics = storage.getMetricsSnapshot();
295
+ storage.resetMetrics();
296
+ unsubscribe();
415
297
  ```
416
298
 
417
- 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
418
304
 
419
305
  ```ts
420
306
  import {
421
- StorageScope,
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
- if (getRaw("theme") === "auto") {
428
- 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");
429
319
  }
430
320
  });
431
321
 
432
322
  migrateToLatest(StorageScope.Disk);
433
- ```
434
323
 
435
- 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
+ });
436
328
 
437
- ## Choosing a Storage Library
329
+ migrateFromMMKV(mmkvInstance, themeItem);
330
+ ```
438
331
 
439
- | Need | Good fit |
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
- 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
448
335
 
449
- ## 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";
450
342
 
451
- - Use `StorageScope.Secure` only for secrets and tokens.
452
- - Keep secrets out of logs, exports, analytics, and crash reports.
453
- - Test biometric and Keychain/Keystore flows on real iOS and Android devices.
454
- - Configure a web Secure backend intentionally; browser storage does not provide native Keychain or Keystore guarantees.
455
- - Run the full package gate before release:
343
+ const backend = await createIndexedDBBackend({
344
+ dbName: "app-storage",
345
+ storeName: "kv",
346
+ });
456
347
 
457
- ```sh
458
- bun run lint -- --filter=react-native-nitro-storage
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
- Publishing a GitHub Release tagged `v<package.version>` triggers the npm workflow.
476
- 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.
477
390
 
478
391
  ## Development
479
392
 
480
393
  ```sh
481
394
  bun install
482
- bun run lint -- --filter=react-native-nitro-storage
483
- bun run format:check -- --filter=react-native-nitro-storage
484
- bun run typecheck -- --filter=react-native-nitro-storage
485
- bun run test:types -- --filter=react-native-nitro-storage
486
- bun run test -- --filter=react-native-nitro-storage
487
- 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
488
402
  ```
489
403
 
490
- Release checks:
491
-
492
- ```sh
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)