react-native-nitro-storage 0.1.4 β†’ 0.3.1

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.
Files changed (48) hide show
  1. package/README.md +432 -345
  2. package/android/src/main/cpp/AndroidStorageAdapterCpp.cpp +191 -3
  3. package/android/src/main/cpp/AndroidStorageAdapterCpp.hpp +21 -41
  4. package/android/src/main/java/com/nitrostorage/AndroidStorageAdapter.kt +181 -29
  5. package/android/src/main/java/com/nitrostorage/NitroStoragePackage.kt +2 -2
  6. package/app.plugin.js +9 -7
  7. package/cpp/bindings/HybridStorage.cpp +239 -10
  8. package/cpp/bindings/HybridStorage.hpp +10 -0
  9. package/cpp/core/NativeStorageAdapter.hpp +22 -0
  10. package/ios/IOSStorageAdapterCpp.hpp +25 -0
  11. package/ios/IOSStorageAdapterCpp.mm +315 -33
  12. package/lib/commonjs/Storage.types.js +23 -1
  13. package/lib/commonjs/Storage.types.js.map +1 -1
  14. package/lib/commonjs/index.js +680 -68
  15. package/lib/commonjs/index.js.map +1 -1
  16. package/lib/commonjs/index.web.js +801 -133
  17. package/lib/commonjs/index.web.js.map +1 -1
  18. package/lib/commonjs/internal.js +112 -0
  19. package/lib/commonjs/internal.js.map +1 -0
  20. package/lib/module/Storage.types.js +22 -0
  21. package/lib/module/Storage.types.js.map +1 -1
  22. package/lib/module/index.js +660 -71
  23. package/lib/module/index.js.map +1 -1
  24. package/lib/module/index.web.js +766 -125
  25. package/lib/module/index.web.js.map +1 -1
  26. package/lib/module/internal.js +100 -0
  27. package/lib/module/internal.js.map +1 -0
  28. package/lib/typescript/Storage.nitro.d.ts +10 -0
  29. package/lib/typescript/Storage.nitro.d.ts.map +1 -1
  30. package/lib/typescript/Storage.types.d.ts +20 -0
  31. package/lib/typescript/Storage.types.d.ts.map +1 -1
  32. package/lib/typescript/index.d.ts +68 -9
  33. package/lib/typescript/index.d.ts.map +1 -1
  34. package/lib/typescript/index.web.d.ts +79 -13
  35. package/lib/typescript/index.web.d.ts.map +1 -1
  36. package/lib/typescript/internal.d.ts +21 -0
  37. package/lib/typescript/internal.d.ts.map +1 -0
  38. package/lib/typescript/migration.d.ts +2 -3
  39. package/lib/typescript/migration.d.ts.map +1 -1
  40. package/nitrogen/generated/shared/c++/HybridStorageSpec.cpp +10 -0
  41. package/nitrogen/generated/shared/c++/HybridStorageSpec.hpp +10 -0
  42. package/package.json +22 -8
  43. package/src/Storage.nitro.ts +11 -2
  44. package/src/Storage.types.ts +22 -0
  45. package/src/index.ts +943 -84
  46. package/src/index.web.ts +1082 -137
  47. package/src/internal.ts +144 -0
  48. package/src/migration.ts +3 -3
package/README.md CHANGED
@@ -1,83 +1,77 @@
1
- # react-native-nitro-storage πŸ—„οΈ
1
+ # react-native-nitro-storage
2
2
 
3
- > **The fastest, most complete storage solution for React Native.** Replace Zustand, MMKV, AsyncStorage, and Expo Secure Store with one lightning-fast, type-safe library.
3
+ The fastest, most complete storage solution for React Native.
4
+ Synchronous Memory, Disk, and Secure storage in one unified API β€” powered by [Nitro Modules](https://github.com/mrousavy/nitro) and JSI.
4
5
 
5
- [![npm version](https://img.shields.io/npm/v/react-native-nitro-storage?style=flat-square)](https://www.npmjs.com/package/react-native-nitro-storage)
6
- [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg?style=flat-square)](https://opensource.org/licenses/MIT)
7
- [![Nitro Modules](https://img.shields.io/badge/Powered%20by-Nitro%20Modules-blueviolet?style=flat-square)](https://nitro.margelo.com)
6
+ ## Highlights
8
7
 
9
- **react-native-nitro-storage** unifies **Memory** (global state), **Disk** (persistence), and **Secure** (keychain) storage into a single, blazing-fast C++ library built with [Nitro Modules](https://nitro.margelo.com). All operations are **100% synchronous** via JSIβ€”no promises, no bridge, no lag.
8
+ - **Three storage scopes** β€” in-memory, persistent disk, and hardware-encrypted secure storage
9
+ - **Synchronous reads & writes** β€” no `async/await`, no bridge, zero serialization overhead for primitives
10
+ - **React hooks** β€” `useStorage`, `useStorageSelector`, `useSetStorage` with automatic re-renders
11
+ - **Type-safe** β€” full TypeScript generics, custom serializers, schema validation with fallback
12
+ - **Namespaces** β€” isolate keys by feature, user, or tenant with automatic prefixing
13
+ - **TTL expiration** β€” time-based auto-expiry with optional `onExpired` callback
14
+ - **Biometric storage** β€” hardware-backed biometric protection on iOS & Android
15
+ - **Auth storage factory** β€” `createSecureAuthStorage` for multi-token auth flows
16
+ - **Batch operations** β€” atomic multi-key get/set/remove via native batch APIs
17
+ - **Transactions** β€” grouped writes with automatic rollback on error
18
+ - **Migrations** β€” versioned data migrations with `registerMigration` / `migrateToLatest`
19
+ - **MMKV migration** β€” drop-in `migrateFromMMKV` for painless migration from MMKV
20
+ - **Cross-platform** β€” iOS, Android, and web (`localStorage` fallback)
10
21
 
11
- <p align="center">
12
- <img src="./readme/ios.png" alt="iOS Benchmark" height="640" />
13
- <img src="./readme/android.png" alt="Android Benchmark" height="640" />
14
- </p>
22
+ ## Requirements
15
23
 
16
- <p align="center">
17
- <em>Real-world performance: 1,000 operations in milliseconds</em>
18
- </p>
24
+ | Dependency | Version |
25
+ | ---------------------------- | ----------- |
26
+ | `react-native` | `>= 0.75.0` |
27
+ | `react-native-nitro-modules` | `>= 0.33.9` |
28
+ | `react` | `>= 18.2.0` |
19
29
 
20
- ---
21
-
22
- ## ⚑ Why Nitro Storage?
23
-
24
- ### **One Library, Three Storage Types**
25
-
26
- Stop juggling multiple packages. Get memory state, disk persistence, and secure storage in one unified API.
27
-
28
- ### **Truly Synchronous**
29
-
30
- Every operationβ€”read, write, deleteβ€”executes instantly. No `await`, no `.then()`, no bridge overhead.
31
-
32
- ### **Jotai-Style Atoms**
33
-
34
- Familiar, elegant API with `createStorageItem` and `useStorage`. Works inside and outside React components.
35
-
36
- ### **Production-Ready**
37
-
38
- Thread-safe C++ core, comprehensive test coverage, and battle-tested on iOS, Android, and **Web**.
30
+ ## Installation
39
31
 
40
- ### **Web Support**
41
-
42
- Fully functional on the web via `localStorage` and `sessionStorage`. All hooks and storage atoms are fully reactive across all platforms.
43
-
44
- ---
32
+ ```bash
33
+ bun add react-native-nitro-storage react-native-nitro-modules
34
+ ```
45
35
 
46
- ## πŸ“¦ Installation
36
+ or:
47
37
 
48
38
  ```bash
49
39
  npm install react-native-nitro-storage react-native-nitro-modules
50
- # or
51
- yarn add react-native-nitro-storage react-native-nitro-modules
52
- # or
53
- bun add react-native-nitro-storage react-native-nitro-modules
54
40
  ```
55
41
 
56
- ### For Expo Projects
42
+ ### Expo
57
43
 
58
44
  ```bash
59
- npx expo install react-native-nitro-storage react-native-nitro-modules
45
+ bunx expo install react-native-nitro-storage react-native-nitro-modules
60
46
  ```
61
47
 
62
- Add the plugin to your `app.json` or `app.config.js`:
48
+ Add the config plugin to `app.json`:
63
49
 
64
50
  ```json
65
51
  {
66
52
  "expo": {
67
- "plugins": ["react-native-nitro-storage"]
53
+ "plugins": [
54
+ [
55
+ "react-native-nitro-storage",
56
+ {
57
+ "faceIDPermission": "Allow $(PRODUCT_NAME) to use Face ID for secure authentication",
58
+ "addBiometricPermissions": false
59
+ }
60
+ ]
61
+ ]
68
62
  }
69
63
  }
70
64
  ```
71
65
 
66
+ > `faceIDPermission` sets `NSFaceIDUsageDescription` only when missing. Android biometric permissions are opt-in via `addBiometricPermissions: true`.
67
+
72
68
  Then run:
73
69
 
74
70
  ```bash
75
- npx expo prebuild
71
+ bunx expo prebuild
76
72
  ```
77
73
 
78
- The config plugin automatically handles Android initialization. No manual setup required!
79
-
80
- ### For Bare React Native Projects
74
+ ### Bare React Native
81
75
 
82
76
  **iOS:**
83
77
 
@@ -85,9 +79,7 @@ The config plugin automatically handles Android initialization. No manual setup
85
79
  cd ios && pod install
86
80
  ```
87
81
 
88
- **Android:**
89
-
90
- Add initialization to your `MainApplication.kt` (or `.java`):
82
+ **Android** β€” initialize the native adapter in `MainApplication.kt`:
91
83
 
92
84
  ```kotlin
93
85
  import com.nitrostorage.AndroidStorageAdapter
@@ -102,433 +94,528 @@ class MainApplication : Application() {
102
94
 
103
95
  ---
104
96
 
105
- ## πŸš€ Quick Start
97
+ ## Quick Start
106
98
 
107
- ```typescript
108
- import {
109
- createStorageItem,
110
- useStorage,
111
- StorageScope,
112
- } from "react-native-nitro-storage";
99
+ ```ts
100
+ import { createStorageItem, StorageScope, useStorage } from "react-native-nitro-storage";
113
101
 
114
- // Create a storage atom
115
- const counterAtom = createStorageItem({
102
+ // define a storage item outside of components
103
+ const counterItem = createStorageItem({
116
104
  key: "counter",
117
105
  scope: StorageScope.Memory,
118
106
  defaultValue: 0,
119
107
  });
120
108
 
121
- // Use in React
122
- function Counter() {
123
- const [count, setCount] = useStorage(counterAtom);
109
+ export function Counter() {
110
+ const [count, setCount] = useStorage(counterItem);
124
111
 
125
112
  return (
126
- <View>
127
- <Text>{count}</Text>
128
- <Button title="+" onPress={() => setCount(count + 1)} />
129
- </View>
113
+ <Button
114
+ title={`Count: ${count}`}
115
+ onPress={() => setCount((prev) => prev + 1)}
116
+ />
130
117
  );
131
118
  }
132
-
133
- // Use outside React
134
- counterAtom.set(42);
135
- const value = counterAtom.get(); // 42, instantly
136
119
  ```
137
120
 
138
121
  ---
139
122
 
140
- ## πŸ’Ύ Storage Scopes
141
-
142
- ### **Memory Storage**
143
-
144
- _Replaces: Zustand, Jotai, Redux_
145
-
146
- Fast, in-memory state. **Now Pure JS** - can store complex objects, functions, and React nodes!
147
-
148
- ```typescript
149
- // Store a function
150
- const callbackAtom = createStorageItem({
151
- key: "on-click",
152
- scope: StorageScope.Memory,
153
- defaultValue: () => console.log("Clicked!"),
154
- });
123
+ ## Storage Scopes
155
124
 
156
- // Store a React Component
157
- const modalAtom = createStorageItem({
158
- key: "active-modal",
159
- scope: StorageScope.Memory,
160
- defaultValue: <View />,
161
- });
162
- ```
125
+ | Scope | Backend (iOS) | Backend (Android) | Backend (Web) | Persisted |
126
+ | -------- | ------------------------ | -------------------------- | ------------------------------------------------ | --------- |
127
+ | `Memory` | In-process JS Map | In-process JS Map | In-process JS Map | No |
128
+ | `Disk` | UserDefaults (app suite) | SharedPreferences | `localStorage` | Yes |
129
+ | `Secure` | Keychain (AES-256 GCM) | EncryptedSharedPreferences | `localStorage` (`__secure_` + `__bio_` prefixes) | Yes |
163
130
 
164
- **Performance:** < 0.001ms per operation (Zero JSI overhead)
131
+ ```ts
132
+ import { StorageScope } from "react-native-nitro-storage";
165
133
 
166
- ### **Disk Storage**
167
-
168
- _Replaces: MMKV, AsyncStorage_
169
-
170
- Persisted to disk. Survives app restarts.
171
-
172
- ```typescript
173
- const settingsAtom = createStorageItem({
174
- key: "app-settings",
175
- scope: StorageScope.Disk,
176
- defaultValue: { theme: "dark", notifications: true },
177
- });
134
+ StorageScope.Memory; // 0 β€” ephemeral, fastest
135
+ StorageScope.Disk; // 1 β€” persistent, fast
136
+ StorageScope.Secure; // 2 β€” encrypted, slightly slower
178
137
  ```
179
138
 
180
- **Performance:** ~1-2ms per operation
181
- **Storage:** UserDefaults (iOS), SharedPreferences (Android)
139
+ ---
182
140
 
183
- ### **Secure Storage**
141
+ ## API Reference
184
142
 
185
- _Replaces: Expo Secure Store, react-native-keychain_
143
+ ### `createStorageItem<T>(config)`
186
144
 
187
- Encrypted storage for sensitive data like auth tokens.
145
+ The core factory. Creates a reactive storage item that can be used standalone or with hooks.
188
146
 
189
- ```typescript
190
- const tokenAtom = createStorageItem<string | undefined>({
191
- key: "auth-token",
192
- scope: StorageScope.Secure,
193
- });
147
+ ```ts
148
+ function createStorageItem<T = undefined>(
149
+ config: StorageItemConfig<T>,
150
+ ): StorageItem<T>;
194
151
  ```
195
152
 
196
- **Performance:** ~2-5ms per operation
197
- **Encryption:** Keychain (iOS), EncryptedSharedPreferences with AES256-GCM (Android)
153
+ **Config options:**
154
+
155
+ | Property | Type | Default | Description |
156
+ | ---------------------- | -------------------------------- | -------------- | -------------------------------------------------------------- |
157
+ | `key` | `string` | _required_ | Storage key identifier |
158
+ | `scope` | `StorageScope` | _required_ | Where to store the data |
159
+ | `defaultValue` | `T` | `undefined` | Value returned when no data exists |
160
+ | `serialize` | `(value: T) => string` | JSON fast path | Custom serialization |
161
+ | `deserialize` | `(value: string) => T` | JSON fast path | Custom deserialization |
162
+ | `validate` | `(value: unknown) => value is T` | β€” | Type guard run on every read |
163
+ | `onValidationError` | `(invalidValue: unknown) => T` | β€” | Recovery function when validation fails |
164
+ | `expiration` | `{ ttlMs: number }` | β€” | Time-to-live in milliseconds |
165
+ | `onExpired` | `(key: string) => void` | β€” | Callback fired when a TTL value expires on read |
166
+ | `readCache` | `boolean` | `false` | Cache deserialized values in JS (avoids repeated native reads) |
167
+ | `coalesceSecureWrites` | `boolean` | `false` | Batch same-tick Secure writes per key |
168
+ | `namespace` | `string` | β€” | Prefix key as `namespace:key` for isolation |
169
+ | `biometric` | `boolean` | `false` | Require biometric auth (Secure scope only) |
170
+ | `accessControl` | `AccessControl` | β€” | Keychain access control level (native only) |
171
+
172
+ **Returned `StorageItem<T>`:**
173
+
174
+ | Method / Property | Type | Description |
175
+ | ----------------- | ---------------------------------------- | -------------------------------------------------- |
176
+ | `get()` | `() => T` | Read current value (synchronous) |
177
+ | `set(value)` | `(value: T \| ((prev: T) => T)) => void` | Write a value or updater function |
178
+ | `delete()` | `() => void` | Remove the stored value (resets to `defaultValue`) |
179
+ | `has()` | `() => boolean` | Check if a value exists in storage |
180
+ | `subscribe(cb)` | `(cb: () => void) => () => void` | Listen for changes, returns unsubscribe |
181
+ | `serialize` | `(v: T) => string` | The item's serializer |
182
+ | `deserialize` | `(v: string) => T` | The item's deserializer |
183
+ | `scope` | `StorageScope` | The item's scope |
184
+ | `key` | `string` | The resolved key (includes namespace prefix) |
198
185
 
199
186
  ---
200
187
 
201
- ## 🎯 Advanced Usage
188
+ ### React Hooks
202
189
 
203
- ### TypeScript Best Practices
190
+ #### `useStorage(item)`
204
191
 
205
- **Nullable Types:**
206
- Use explicit generics for nullable values. `defaultValue` is optional and defaults to `undefined`.
192
+ Full reactive binding. Re-renders when the value changes.
207
193
 
208
- ```typescript
209
- // βœ… Clean - explicit generic
210
- const userAtom = createStorageItem<User | null>({
211
- key: "current-user",
212
- scope: StorageScope.Memory,
213
- defaultValue: null,
214
- });
215
-
216
- // βœ… Optional value - no defaultValue needed
217
- const tokenAtom = createStorageItem<string | undefined>({
218
- key: "auth-token",
219
- scope: StorageScope.Secure,
220
- });
221
-
222
- // ❌ Avoid - type assertion
223
- const userAtom = createStorageItem({
224
- key: "current-user",
225
- scope: StorageScope.Memory,
226
- defaultValue: null as User | null, // Not necessary
227
- });
194
+ ```ts
195
+ const [value, setValue] = useStorage(item);
228
196
  ```
229
197
 
230
- **Non-nullable Types:**
231
- For required values, just specify the default.
198
+ #### `useStorageSelector(item, selector, isEqual?)`
232
199
 
233
- ```typescript
234
- const counterAtom = createStorageItem({
235
- key: "counter",
236
- scope: StorageScope.Memory,
237
- defaultValue: 0, // Type inferred as number
238
- });
200
+ Subscribe to a derived slice. Only re-renders when the selected value changes.
201
+
202
+ ```ts
203
+ const [theme, setSettings] = useStorageSelector(settingsItem, (s) => s.theme);
239
204
  ```
240
205
 
241
- ### Custom Serialization
206
+ #### `useSetStorage(item)`
242
207
 
243
- ```typescript
244
- const dateAtom = createStorageItem({
245
- key: "last-login",
246
- scope: StorageScope.Disk,
247
- defaultValue: new Date(),
248
- serialize: (date) => date.toISOString(),
249
- deserialize: (str) => new Date(str),
250
- });
208
+ Write-only hook. Useful when a component needs to update a value but doesn't depend on it.
209
+
210
+ ```ts
211
+ const setToken = useSetStorage(tokenItem);
212
+ setToken("new-token");
251
213
  ```
252
214
 
253
- ### Complex Objects
215
+ ---
254
216
 
255
- ```typescript
256
- interface User {
257
- id: string;
258
- name: string;
259
- preferences: {
260
- theme: "light" | "dark";
261
- language: string;
262
- };
263
- }
217
+ ### `storage` β€” Global Utilities
264
218
 
265
- const userAtom = createStorageItem<User>({
266
- key: "user",
267
- scope: StorageScope.Disk,
268
- defaultValue: {
269
- id: "",
270
- name: "",
271
- preferences: { theme: "dark", language: "en" },
272
- },
273
- });
274
-
275
- // TypeScript knows the exact shape
276
- const user = userAtom.get();
277
- console.log(user.preferences.theme); // βœ… Type-safe
219
+ ```ts
220
+ import { storage, StorageScope } from "react-native-nitro-storage";
278
221
  ```
279
222
 
280
- ### Direct Access (Outside React)
223
+ | Method | Description |
224
+ | --------------------------------------- | ---------------------------------------------------------------------------- |
225
+ | `storage.clear(scope)` | Clear all keys in a scope (`Secure` also clears biometric entries) |
226
+ | `storage.clearAll()` | Clear Memory + Disk + Secure |
227
+ | `storage.clearNamespace(ns, scope)` | Remove only keys matching a namespace |
228
+ | `storage.clearBiometric()` | Remove all biometric-prefixed keys |
229
+ | `storage.has(key, scope)` | Check if a key exists |
230
+ | `storage.getAllKeys(scope)` | Get all key names |
231
+ | `storage.getAll(scope)` | Get all key-value pairs as `Record<string, string>` |
232
+ | `storage.size(scope)` | Number of stored keys |
233
+ | `storage.setAccessControl(level)` | Set default secure access control for subsequent secure writes (native only) |
234
+ | `storage.setKeychainAccessGroup(group)` | Set keychain access group for app sharing (native only) |
235
+
236
+ > `storage.getAll(StorageScope.Secure)` returns regular secure entries. Biometric-protected values are not included in this snapshot API.
237
+
238
+ ---
281
239
 
282
- Perfect for API interceptors, middleware, or anywhere you need storage without React.
240
+ ### `createSecureAuthStorage<K>(config, options?)`
283
241
 
284
- ```typescript
285
- // In an API interceptor
286
- axios.interceptors.request.use((config) => {
287
- const token = tokenAtom.get();
288
- if (token) {
289
- config.headers.Authorization = `Bearer ${token}`;
290
- }
291
- return config;
292
- });
242
+ One-liner factory for authentication flows. Creates multiple `StorageItem<string>` entries in Secure scope.
293
243
 
294
- // In a Redux middleware
295
- const authMiddleware = (store) => (next) => (action) => {
296
- if (action.type === "AUTH_SUCCESS") {
297
- tokenAtom.set(action.payload.token);
298
- }
299
- return next(action);
300
- };
244
+ ```ts
245
+ function createSecureAuthStorage<K extends string>(
246
+ config: SecureAuthStorageConfig<K>,
247
+ options?: { namespace?: string },
248
+ ): Record<K, StorageItem<string>>;
301
249
  ```
302
250
 
303
- ### Manual Subscriptions
251
+ - Default namespace: `"auth"`
252
+ - Each key is a separate `StorageItem<string>` with `StorageScope.Secure`
253
+ - Supports per-key TTL, biometric, and access control
304
254
 
305
- ```typescript
306
- const unsubscribe = counterAtom.subscribe(() => {
307
- console.log("Counter changed:", counterAtom.get());
308
- });
309
-
310
- // Clean up
311
- unsubscribe();
312
- ```
255
+ ---
313
256
 
314
257
  ### Batch Operations
315
258
 
316
- Read or write multiple items at once. This is highly optimized on the native side for minimum overhead.
259
+ Atomic multi-key operations. Uses native batch APIs for best performance.
317
260
 
318
- ```typescript
261
+ ```ts
319
262
  import { getBatch, setBatch, removeBatch } from "react-native-nitro-storage";
320
263
 
321
- // Write multiple items
264
+ // Read multiple items at once
265
+ const [a, b, c] = getBatch([itemA, itemB, itemC], StorageScope.Disk);
266
+
267
+ // Write multiple items atomically
322
268
  setBatch(
323
269
  [
324
- { item: item1, value: "v1" },
325
- { item: item2, value: "v2" },
270
+ { item: itemA, value: "hello" },
271
+ { item: itemB, value: "world" },
326
272
  ],
327
- StorageScope.Disk
273
+ StorageScope.Disk,
328
274
  );
329
275
 
330
- // Read multiple items
331
- const [v1, v2] = getBatch([item1, item2], StorageScope.Disk);
332
-
333
276
  // Remove multiple items
334
- removeBatch([item1, item2], StorageScope.Disk);
277
+ removeBatch([itemA, itemB], StorageScope.Disk);
335
278
  ```
336
279
 
337
- ### Functional Updates
280
+ > All items in a batch must share the same scope. Items with `validate` or `expiration` automatically use per-item paths to preserve semantics.
338
281
 
339
- Update state based on the previous value, just like `useState`.
282
+ ---
340
283
 
341
- ```typescript
342
- // Increment counter
343
- counterAtom.set((prev) => prev + 1);
344
- ```
284
+ ### Transactions
345
285
 
346
- ### Optimized Writes
286
+ Grouped writes with automatic rollback on error.
347
287
 
348
- Use `useSetStorage` to set values without subscribing to updates (avoids re-renders).
288
+ ```ts
289
+ import { runTransaction, StorageScope } from "react-native-nitro-storage";
349
290
 
350
- ```typescript
351
- import { useSetStorage } from "react-native-nitro-storage";
291
+ runTransaction(StorageScope.Disk, (tx) => {
292
+ const balance = tx.getItem(balanceItem);
293
+ tx.setItem(balanceItem, balance - 50);
294
+ tx.setItem(logItem, `Deducted 50 at ${new Date().toISOString()}`);
352
295
 
353
- function IncrementButton() {
354
- const setCount = useSetStorage(counterAtom);
355
- return <Button onPress={() => setCount((c) => c + 1)} title="+" />;
356
- }
296
+ if (balance - 50 < 0) throw new Error("Insufficient funds");
297
+ // if this throws, both writes are rolled back
298
+ });
357
299
  ```
358
300
 
359
- ### Clearing Data
301
+ **TransactionContext methods:**
360
302
 
361
- Clear entire storage scopes at once.
303
+ | Method | Description |
304
+ | ---------------------- | --------------------------- |
305
+ | `getItem(item)` | Read a StorageItem's value |
306
+ | `setItem(item, value)` | Write a StorageItem's value |
307
+ | `removeItem(item)` | Delete a StorageItem |
308
+ | `getRaw(key)` | Read raw string by key |
309
+ | `setRaw(key, value)` | Write raw string by key |
310
+ | `removeRaw(key)` | Delete raw key |
362
311
 
363
- ```typescript
364
- import { storage } from "react-native-nitro-storage";
312
+ ---
365
313
 
366
- // Clear all storage (all scopes)
367
- storage.clearAll();
314
+ ### Migrations
368
315
 
369
- // Clear specific scope
370
- storage.clear(StorageScope.Memory);
371
- storage.clear(StorageScope.Disk);
372
- storage.clear(StorageScope.Secure);
373
- ```
316
+ Versioned, sequential data migrations.
374
317
 
375
- ### Migration from MMKV
318
+ ```ts
319
+ import {
320
+ registerMigration,
321
+ migrateToLatest,
322
+ StorageScope,
323
+ } from "react-native-nitro-storage";
376
324
 
377
- Easily migrate data from `react-native-mmkv` to Nitro Storage.
325
+ registerMigration(1, ({ setRaw }) => {
326
+ setRaw("onboarding-complete", "false");
327
+ });
378
328
 
379
- ```typescript
380
- import { migrateFromMMKV } from "react-native-nitro-storage/src/migration";
329
+ registerMigration(2, ({ getRaw, setRaw, removeRaw }) => {
330
+ const raw = getRaw("legacy-key");
331
+ if (raw) {
332
+ setRaw("new-key", raw);
333
+ removeRaw("legacy-key");
334
+ }
335
+ });
381
336
 
382
- // Migrate 'user-settings' and delete from MMKV
383
- migrateFromMMKV(mmkvInstance, settingsAtom, true);
337
+ // apply all pending migrations (runs once per scope)
338
+ migrateToLatest(StorageScope.Disk);
384
339
  ```
385
340
 
386
- ---
341
+ - Versions must be positive integers, registered in any order, applied ascending
342
+ - Version state is tracked per scope via `__nitro_storage_migration_version__`
343
+ - Duplicate versions throw at registration time
387
344
 
388
- ## πŸ“Š Performance Benchmarks
345
+ ---
389
346
 
390
- All operations are **100% synchronous** via JSIβ€”no promises, no bridge, no lag.
347
+ ### MMKV Migration
391
348
 
392
- **Performance Metrics (1,000 operations per storage type):**
349
+ Drop-in helper for migrating from `react-native-mmkv`.
393
350
 
394
- | Storage Type | Write | Read | Avg/op |
395
- | ------------ | ----- | ----- | ------------ |
396
- | Memory | 0.5ms | 0.3ms | **0.0008ms** |
397
- | Disk | 45ms | 38ms | **0.083ms** |
398
- | Secure | 120ms | 95ms | **0.215ms** |
351
+ ```ts
352
+ import { migrateFromMMKV } from "react-native-nitro-storage";
353
+ import { MMKV } from "react-native-mmkv";
399
354
 
400
- Run the benchmark yourself:
355
+ const mmkv = new MMKV();
401
356
 
402
- ```bash
403
- cd apps/example
404
- npm run ios # or npm run android
405
- # Navigate to the "Benchmark" tab
357
+ const migrated = migrateFromMMKV(mmkv, myStorageItem, true);
358
+ // true β†’ value found and copied, original deleted from MMKV
359
+ // false β†’ no matching key in MMKV
406
360
  ```
407
361
 
408
- ---
362
+ - Read priority: `getString` β†’ `getNumber` β†’ `getBoolean`
363
+ - Uses `item.set()` so validation still applies
364
+ - Only deletes from MMKV when migration succeeds
409
365
 
410
- ## 🎯 API Reference
366
+ ---
411
367
 
412
- ### `createStorageItem<T>(config)`
368
+ ### Enums
413
369
 
414
- Creates a storage atom.
370
+ #### `AccessControl`
415
371
 
416
- **Parameters:**
372
+ Controls keychain item access requirements (iOS Keychain / Android Keystore). No-op on web.
417
373
 
418
- - `key: string` - Unique identifier
419
- - `scope: StorageScope` - Memory, Disk, or Secure
420
- - `defaultValue: T` - Default value
421
- - `serialize?: (value: T) => string` - Custom serializer (default: JSON.stringify)
422
- - `deserialize?: (value: string) => T` - Custom deserializer (default: JSON.parse)
374
+ ```ts
375
+ enum AccessControl {
376
+ WhenUnlocked = 0,
377
+ AfterFirstUnlock = 1,
378
+ WhenPasscodeSetThisDeviceOnly = 2,
379
+ WhenUnlockedThisDeviceOnly = 3,
380
+ AfterFirstUnlockThisDeviceOnly = 4,
381
+ }
382
+ ```
423
383
 
424
- **Returns:** `StorageItem<T>`
384
+ #### `BiometricLevel`
425
385
 
426
- ### `StorageItem<T>`
386
+ ```ts
387
+ enum BiometricLevel {
388
+ None = 0,
389
+ BiometryOrPasscode = 1,
390
+ BiometryOnly = 2,
391
+ }
392
+ ```
427
393
 
428
- **Methods:**
394
+ ---
429
395
 
430
- - `get(): T` - Get current value (synchronous)
431
- - `set(value: T | ((prev: T) => T)): void` - Set new value (synchronous)
432
- - `delete(): void` - Remove value (synchronous)
433
- - `subscribe(callback: () => void): () => void` - Subscribe to changes
396
+ ## Use Cases
434
397
 
435
- ### `useStorage<T>(item: StorageItem<T>)`
398
+ ### Persisted User Preferences
436
399
 
437
- React hook using `useSyncExternalStore` for automatic re-renders.
400
+ ```ts
401
+ interface UserPreferences {
402
+ theme: "light" | "dark" | "system";
403
+ language: string;
404
+ notifications: boolean;
405
+ }
438
406
 
439
- **Returns:** `[value: T, setValue: (value: T | ((prev: T) => T)) => void]`
407
+ const prefsItem = createStorageItem<UserPreferences>({
408
+ key: "prefs",
409
+ scope: StorageScope.Disk,
410
+ defaultValue: { theme: "system", language: "en", notifications: true },
411
+ });
440
412
 
441
- ### `useSetStorage<T>(item: StorageItem<T>)`
413
+ // in a component β€” only re-renders when theme changes
414
+ const [theme, setPrefs] = useStorageSelector(prefsItem, (p) => p.theme);
415
+ ```
442
416
 
443
- Returns the setter function only. Does not subscribe to updates.
417
+ ### Auth Token Management
444
418
 
445
- **Returns:** `(value: T | ((prev: T) => T)) => void`
419
+ ```ts
420
+ const auth = createSecureAuthStorage(
421
+ {
422
+ accessToken: { ttlMs: 15 * 60_000, biometric: true },
423
+ refreshToken: { ttlMs: 7 * 24 * 60 * 60_000 },
424
+ idToken: {},
425
+ },
426
+ { namespace: "myapp-auth" },
427
+ );
446
428
 
447
- ### `storage` Object
429
+ // after login
430
+ auth.accessToken.set(response.accessToken);
431
+ auth.refreshToken.set(response.refreshToken);
432
+ auth.idToken.set(response.idToken);
433
+
434
+ // check if token exists and hasn't expired
435
+ if (auth.accessToken.has()) {
436
+ const token = auth.accessToken.get();
437
+ // use token
438
+ } else {
439
+ // refresh or re-login
440
+ }
448
441
 
449
- - `clearAll()`: Clears all storage across all scopes.
450
- - `clear(scope)`: Clears a specific storage scope.
442
+ // logout
443
+ storage.clearNamespace("myapp-auth", StorageScope.Secure);
444
+ ```
451
445
 
452
- ### Batch Operations
446
+ ### Feature Flags with Validation
453
447
 
454
- - `getBatch(items, scope)`: Returns an array of values for the given items.
455
- - `setBatch(items, scope)`: Sets multiple values at once.
456
- - `removeBatch(items, scope)`: Removes multiple items at once.
448
+ ```ts
449
+ interface FeatureFlags {
450
+ darkMode: boolean;
451
+ betaFeature: boolean;
452
+ maxUploadMb: number;
453
+ }
457
454
 
458
- ---
455
+ const flagsItem = createStorageItem<FeatureFlags>({
456
+ key: "feature-flags",
457
+ scope: StorageScope.Disk,
458
+ defaultValue: { darkMode: false, betaFeature: false, maxUploadMb: 10 },
459
+ validate: (v): v is FeatureFlags =>
460
+ typeof v === "object" &&
461
+ v !== null &&
462
+ typeof (v as any).darkMode === "boolean" &&
463
+ typeof (v as any).maxUploadMb === "number",
464
+ onValidationError: () => ({
465
+ darkMode: false,
466
+ betaFeature: false,
467
+ maxUploadMb: 10,
468
+ }),
469
+ expiration: { ttlMs: 60 * 60_000 }, // refresh from server every hour
470
+ onExpired: () => fetchAndStoreFlags(),
471
+ });
472
+ ```
459
473
 
460
- ## πŸ” Security
474
+ ### Multi-Tenant / Namespaced Storage
475
+
476
+ ```ts
477
+ function createUserStorage(userId: string) {
478
+ return {
479
+ cart: createStorageItem<string[]>({
480
+ key: "cart",
481
+ scope: StorageScope.Disk,
482
+ defaultValue: [],
483
+ namespace: `user-${userId}`,
484
+ }),
485
+ draft: createStorageItem<string>({
486
+ key: "draft",
487
+ scope: StorageScope.Disk,
488
+ defaultValue: "",
489
+ namespace: `user-${userId}`,
490
+ }),
491
+ };
492
+ }
461
493
 
462
- ### iOS
494
+ // clear all data for a specific user
495
+ storage.clearNamespace("user-123", StorageScope.Disk);
496
+ ```
463
497
 
464
- - **Disk:** `UserDefaults.standard`
465
- - **Secure:** Keychain with `kSecAttrAccessibleWhenUnlocked`
498
+ ### OTP / Temporary Codes
466
499
 
467
- ### Android
500
+ ```ts
501
+ const otpItem = createStorageItem<string>({
502
+ key: "otp-code",
503
+ scope: StorageScope.Secure,
504
+ defaultValue: "",
505
+ expiration: { ttlMs: 5 * 60_000 }, // 5 minutes
506
+ onExpired: (key) => {
507
+ console.log(`${key} expired β€” prompt user to request a new code`);
508
+ },
509
+ });
468
510
 
469
- - **Disk:** `SharedPreferences`
470
- - **Secure:** `EncryptedSharedPreferences` with AES256-GCM encryption
511
+ // store the code
512
+ otpItem.set("482917");
471
513
 
472
- All secure operations use platform-native encryption. No data is stored in plain text.
514
+ // later β€” returns "" if expired
515
+ const code = otpItem.get();
516
+ ```
473
517
 
474
- ---
518
+ ### Atomic Balance Transfer
475
519
 
476
- ## πŸ§ͺ Testing
520
+ ```ts
521
+ const fromBalance = createStorageItem({
522
+ key: "from",
523
+ scope: StorageScope.Disk,
524
+ defaultValue: 100,
525
+ });
526
+ const toBalance = createStorageItem({
527
+ key: "to",
528
+ scope: StorageScope.Disk,
529
+ defaultValue: 0,
530
+ });
477
531
 
478
- Comprehensive test coverage for both TypeScript and C++:
532
+ function transfer(amount: number) {
533
+ runTransaction(StorageScope.Disk, (tx) => {
534
+ const from = tx.getItem(fromBalance);
535
+ if (from < amount) throw new Error("Insufficient funds");
479
536
 
480
- ```bash
481
- # TypeScript/Jest tests
482
- npm test
537
+ tx.setItem(fromBalance, from - amount);
538
+ tx.setItem(toBalance, tx.getItem(toBalance) + amount);
539
+ });
540
+ }
541
+ ```
483
542
 
484
- # Type checking
485
- npm run typecheck
543
+ ### Custom Binary Codec
486
544
 
487
- # Build verification
488
- npm run build
545
+ ```ts
546
+ const compactItem = createStorageItem<{ id: number; active: boolean }>({
547
+ key: "compact",
548
+ scope: StorageScope.Disk,
549
+ defaultValue: { id: 0, active: false },
550
+ serialize: (v) => `${v.id}|${v.active ? "1" : "0"}`,
551
+ deserialize: (v) => {
552
+ const [id, flag] = v.split("|");
553
+ return { id: Number(id), active: flag === "1" };
554
+ },
555
+ });
489
556
  ```
490
557
 
491
- **Test Coverage:**
558
+ ### Migrating From MMKV
492
559
 
493
- - βœ… All storage scopes (Memory, Disk, Secure)
494
- - βœ… Custom serialization
495
- - βœ… Complex objects
496
- - βœ… Subscription/unsubscription
497
- - βœ… Memory leak prevention
498
- - βœ… Thread safety (C++)
560
+ ```ts
561
+ import { MMKV } from "react-native-mmkv";
499
562
 
500
- ---
563
+ const mmkv = new MMKV();
501
564
 
502
- ## πŸ—οΈ Architecture
565
+ const usernameItem = createStorageItem({
566
+ key: "username",
567
+ scope: StorageScope.Disk,
568
+ defaultValue: "",
569
+ });
503
570
 
504
- Built on [Nitro Modules](https://nitro.margelo.com) for maximum performance:
571
+ // run once at app startup
572
+ migrateFromMMKV(mmkv, usernameItem, true); // true = delete from MMKV after
573
+ ```
574
+
575
+ ---
505
576
 
506
- - **C++ Core:** Thread-safe storage implementation with mutex protection
507
- - **JSI Bridge:** Zero-copy, synchronous JavaScript ↔ C++ communication
508
- - **Platform Adapters:** Native iOS (Objective-C++) and Android (Kotlin + JNI) implementations
509
- - **React Integration:** `useSyncExternalStore` for optimal React 18+ compatibility
577
+ ## Exported Types
578
+
579
+ ```ts
580
+ import type {
581
+ Storage,
582
+ StorageItemConfig,
583
+ StorageItem,
584
+ StorageBatchSetItem,
585
+ Validator,
586
+ ExpirationConfig,
587
+ MigrationContext,
588
+ Migration,
589
+ TransactionContext,
590
+ MMKVLike,
591
+ SecureAuthStorageConfig,
592
+ } from "react-native-nitro-storage";
593
+ ```
510
594
 
511
595
  ---
512
596
 
513
- ## πŸ†š Comparison
597
+ ## Dev Commands
514
598
 
515
- | Feature | Nitro Storage | MMKV | AsyncStorage | Zustand | Expo Secure Store |
516
- | ---------------- | ------------- | ---- | ------------ | ------- | ----------------- |
517
- | Synchronous | βœ… | βœ… | ❌ | βœ… | ❌ |
518
- | Memory State | βœ… | ❌ | ❌ | βœ… | ❌ |
519
- | Disk Persistence | βœ… | βœ… | βœ… | ❌ | ❌ |
520
- | Secure Storage | βœ… | ❌ | ❌ | ❌ | βœ… |
521
- | Type-Safe | βœ… | ⚠️ | ⚠️ | βœ… | ⚠️ |
522
- | Unified API | βœ… | ❌ | ❌ | ❌ | ❌ |
523
- | React Hooks | βœ… | ❌ | ❌ | βœ… | ❌ |
524
- | Web Support | βœ… | ❌ | βœ… | βœ… | ❌ |
599
+ From repository root:
525
600
 
526
- ---
601
+ ```bash
602
+ bun run test -- --filter=react-native-nitro-storage
603
+ bun run typecheck -- --filter=react-native-nitro-storage
604
+ bun run build -- --filter=react-native-nitro-storage
605
+ ```
527
606
 
528
- ## πŸ“„ License
607
+ Inside `packages/react-native-nitro-storage`:
529
608
 
530
- MIT
609
+ ```bash
610
+ bun run test # run tests
611
+ bun run test:coverage # run tests with coverage
612
+ bun run lint # eslint (expo-magic rules)
613
+ bun run format:check # prettier check
614
+ bun run typecheck # tsc --noEmit
615
+ bun run build # tsup build
616
+ bun run benchmark # performance benchmarks
617
+ ```
531
618
 
532
- ---
619
+ ## License
533
620
 
534
- **Keywords:** react-native storage, react-native state management, react-native keychain, react-native secure storage, react-native mmkv alternative, react-native async storage, synchronous storage react native, jsi storage, nitro modules, react native persistence
621
+ MIT