react-native-nitro-storage 0.5.6 → 0.5.8

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