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

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,93 +1,63 @@
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
+ Synchronous storage for React Native with a unified API for memory, disk, and secure data.
4
4
 
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)
5
+ ## Requirements
8
6
 
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.
7
+ - `react-native >= 0.75.0`
8
+ - `react-native-nitro-modules >= 0.33.9`
9
+ - `react >= 18.2.0`
10
10
 
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>
15
-
16
- <p align="center">
17
- <em>Real-world performance: 1,000 operations in milliseconds</em>
18
- </p>
19
-
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**.
39
-
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
- ---
45
-
46
- ## πŸ“¦ Installation
11
+ ## Installation
47
12
 
48
13
  ```bash
49
- 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
14
  bun add react-native-nitro-storage react-native-nitro-modules
54
15
  ```
55
16
 
56
- ### For Expo Projects
17
+ ### Expo
57
18
 
58
19
  ```bash
59
- npx expo install react-native-nitro-storage react-native-nitro-modules
20
+ bunx expo install react-native-nitro-storage react-native-nitro-modules
60
21
  ```
61
22
 
62
- Add the plugin to your `app.json` or `app.config.js`:
23
+ `app.json`:
63
24
 
64
25
  ```json
65
26
  {
66
27
  "expo": {
67
- "plugins": ["react-native-nitro-storage"]
28
+ "plugins": [
29
+ [
30
+ "react-native-nitro-storage",
31
+ {
32
+ "faceIDPermission": "Allow $(PRODUCT_NAME) to use Face ID for secure authentication",
33
+ "addBiometricPermissions": false
34
+ }
35
+ ]
36
+ ]
68
37
  }
69
38
  }
70
39
  ```
71
40
 
72
- Then run:
41
+ Notes:
42
+
43
+ - If `faceIDPermission` is omitted, the plugin sets a default only when `NSFaceIDUsageDescription` is missing.
44
+ - Android biometric permissions are opt-in via `addBiometricPermissions: true`.
45
+
46
+ Then:
73
47
 
74
48
  ```bash
75
- npx expo prebuild
49
+ bunx expo prebuild
76
50
  ```
77
51
 
78
- The config plugin automatically handles Android initialization. No manual setup required!
79
-
80
- ### For Bare React Native Projects
52
+ ### Bare React Native
81
53
 
82
- **iOS:**
54
+ iOS:
83
55
 
84
56
  ```bash
85
57
  cd ios && pod install
86
58
  ```
87
59
 
88
- **Android:**
89
-
90
- Add initialization to your `MainApplication.kt` (or `.java`):
60
+ Android (`MainApplication.kt`):
91
61
 
92
62
  ```kotlin
93
63
  import com.nitrostorage.AndroidStorageAdapter
@@ -100,435 +70,394 @@ class MainApplication : Application() {
100
70
  }
101
71
  ```
102
72
 
103
- ---
104
-
105
- ## πŸš€ Quick Start
73
+ ## Quick Start
106
74
 
107
- ```typescript
108
- import {
109
- createStorageItem,
110
- useStorage,
111
- StorageScope,
112
- } from "react-native-nitro-storage";
75
+ ```ts
76
+ import { createStorageItem, StorageScope, useStorage } from "react-native-nitro-storage";
113
77
 
114
- // Create a storage atom
115
- const counterAtom = createStorageItem({
78
+ const counterItem = createStorageItem({
116
79
  key: "counter",
117
80
  scope: StorageScope.Memory,
118
81
  defaultValue: 0,
119
82
  });
120
83
 
121
- // Use in React
122
- function Counter() {
123
- const [count, setCount] = useStorage(counterAtom);
84
+ export function Counter() {
85
+ const [count, setCount] = useStorage(counterItem);
124
86
 
125
87
  return (
126
- <View>
127
- <Text>{count}</Text>
128
- <Button title="+" onPress={() => setCount(count + 1)} />
129
- </View>
88
+ <Button
89
+ title={`Count: ${count}`}
90
+ onPress={() => setCount((prev) => prev + 1)}
91
+ />
130
92
  );
131
93
  }
132
-
133
- // Use outside React
134
- counterAtom.set(42);
135
- const value = counterAtom.get(); // 42, instantly
136
94
  ```
137
95
 
138
- ---
139
-
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
- });
155
-
156
- // Store a React Component
157
- const modalAtom = createStorageItem({
158
- key: "active-modal",
159
- scope: StorageScope.Memory,
160
- defaultValue: <View />,
161
- });
96
+ ## What Is Exported
97
+
98
+ - `StorageScope`
99
+ - `storage`
100
+ - `createStorageItem`
101
+ - `useStorage`
102
+ - `useStorageSelector`
103
+ - `useSetStorage`
104
+ - `getBatch`
105
+ - `setBatch`
106
+ - `removeBatch`
107
+ - `registerMigration`
108
+ - `migrateToLatest`
109
+ - `runTransaction`
110
+ - `migrateFromMMKV`
111
+
112
+ Exported types:
113
+
114
+ - `Storage`
115
+ - `StorageItemConfig<T>`
116
+ - `StorageItem<T>`
117
+ - `StorageBatchSetItem<T>`
118
+ - `Validator<T>`
119
+ - `ExpirationConfig`
120
+ - `MigrationContext`
121
+ - `Migration`
122
+ - `TransactionContext`
123
+ - `MMKVLike`
124
+
125
+ ## API Reference
126
+
127
+ ### `StorageScope`
128
+
129
+ ```ts
130
+ enum StorageScope {
131
+ Memory = 0,
132
+ Disk = 1,
133
+ Secure = 2,
134
+ }
162
135
  ```
163
136
 
164
- **Performance:** < 0.001ms per operation (Zero JSI overhead)
165
-
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
- });
137
+ ### `Storage` (low-level native/web adapter type)
138
+
139
+ ```ts
140
+ type Storage = {
141
+ set(key: string, value: string, scope: number): void;
142
+ get(key: string, scope: number): string | undefined;
143
+ remove(key: string, scope: number): void;
144
+ clear(scope: number): void;
145
+ setBatch(keys: string[], values: string[], scope: number): void;
146
+ getBatch(keys: string[], scope: number): (string | undefined)[];
147
+ removeBatch(keys: string[], scope: number): void;
148
+ addOnChange(
149
+ scope: number,
150
+ callback: (key: string, value: string | undefined) => void
151
+ ): () => void;
152
+ };
178
153
  ```
179
154
 
180
- **Performance:** ~1-2ms per operation
181
- **Storage:** UserDefaults (iOS), SharedPreferences (Android)
182
-
183
- ### **Secure Storage**
184
-
185
- _Replaces: Expo Secure Store, react-native-keychain_
186
-
187
- Encrypted storage for sensitive data like auth tokens.
188
-
189
- ```typescript
190
- const tokenAtom = createStorageItem<string | undefined>({
191
- key: "auth-token",
192
- scope: StorageScope.Secure,
193
- });
155
+ Notes:
156
+
157
+ - Exported for typing/integration use cases.
158
+ - Most app code should use `createStorageItem` + hooks instead of this low-level API.
159
+
160
+ ### `StorageItemConfig<T>`
161
+
162
+ ```ts
163
+ type StorageItemConfig<T> = {
164
+ key: string;
165
+ scope: StorageScope;
166
+ defaultValue?: T;
167
+ serialize?: (value: T) => string;
168
+ deserialize?: (value: string) => T;
169
+ validate?: Validator<T>;
170
+ onValidationError?: (invalidValue: unknown) => T;
171
+ expiration?: ExpirationConfig;
172
+ readCache?: boolean;
173
+ coalesceSecureWrites?: boolean;
174
+ };
194
175
  ```
195
176
 
196
- **Performance:** ~2-5ms per operation
197
- **Encryption:** Keychain (iOS), EncryptedSharedPreferences with AES256-GCM (Android)
177
+ ### `StorageItem<T>`
198
178
 
199
- ---
179
+ ```ts
180
+ type StorageItem<T> = {
181
+ get: () => T;
182
+ set: (value: T | ((prev: T) => T)) => void;
183
+ delete: () => void;
184
+ subscribe: (callback: () => void) => () => void;
185
+ serialize: (value: T) => string;
186
+ deserialize: (value: string) => T;
187
+ _triggerListeners: () => void;
188
+ scope: StorageScope;
189
+ key: string;
190
+ };
191
+ ```
200
192
 
201
- ## 🎯 Advanced Usage
193
+ ### `createStorageItem<T>(config)`
202
194
 
203
- ### TypeScript Best Practices
195
+ ```ts
196
+ function createStorageItem<T = undefined>(config: StorageItemConfig<T>): StorageItem<T>
197
+ ```
204
198
 
205
- **Nullable Types:**
206
- Use explicit generics for nullable values. `defaultValue` is optional and defaults to `undefined`.
199
+ Notes:
207
200
 
208
- ```typescript
209
- // βœ… Clean - explicit generic
210
- const userAtom = createStorageItem<User | null>({
211
- key: "current-user",
212
- scope: StorageScope.Memory,
213
- defaultValue: null,
214
- });
201
+ - `Memory` stores values directly.
202
+ - `Disk` and `Secure` store serialized values.
203
+ - Default serialization uses a primitive fast path for strings/numbers/booleans/null/undefined and JSON for objects/arrays.
204
+ - If `expiration` is enabled, values are wrapped internally and expired lazily on read.
205
+ - `readCache` is opt-in for `Disk`/`Secure` and can be enabled per item.
206
+ - `coalesceSecureWrites` is opt-in and batches same-tick secure writes per key.
207
+ - If `validate` fails on a stored value, fallback is:
208
+ 1. `onValidationError(invalidValue)` if provided
209
+ 2. `defaultValue` otherwise
210
+ - Fallback values are written back only when the source was stored data and the resolved fallback also passes `validate`.
215
211
 
216
- // βœ… Optional value - no defaultValue needed
217
- const tokenAtom = createStorageItem<string | undefined>({
218
- key: "auth-token",
219
- scope: StorageScope.Secure,
220
- });
212
+ Throws:
221
213
 
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
- });
228
- ```
214
+ - `Error("expiration.ttlMs must be greater than 0.")` when `expiration.ttlMs <= 0`.
229
215
 
230
- **Non-nullable Types:**
231
- For required values, just specify the default.
216
+ ### `useStorage(item)`
232
217
 
233
- ```typescript
234
- const counterAtom = createStorageItem({
235
- key: "counter",
236
- scope: StorageScope.Memory,
237
- defaultValue: 0, // Type inferred as number
238
- });
218
+ ```ts
219
+ function useStorage<T>(
220
+ item: StorageItem<T>
221
+ ): [T, (value: T | ((prev: T) => T)) => void]
239
222
  ```
240
223
 
241
- ### Custom Serialization
224
+ ### `useStorageSelector(item, selector, isEqual?)`
242
225
 
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
- });
226
+ ```ts
227
+ function useStorageSelector<T, TSelected>(
228
+ item: StorageItem<T>,
229
+ selector: (value: T) => TSelected,
230
+ isEqual?: (prev: TSelected, next: TSelected) => boolean
231
+ ): [TSelected, (value: T | ((prev: T) => T)) => void]
251
232
  ```
252
233
 
253
- ### Complex Objects
254
-
255
- ```typescript
256
- interface User {
257
- id: string;
258
- name: string;
259
- preferences: {
260
- theme: "light" | "dark";
261
- language: string;
262
- };
263
- }
234
+ Use this to subscribe to a derived slice of a storage value and avoid rerenders when that slice does not change.
264
235
 
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
- });
236
+ ### `useSetStorage(item)`
274
237
 
275
- // TypeScript knows the exact shape
276
- const user = userAtom.get();
277
- console.log(user.preferences.theme); // βœ… Type-safe
238
+ ```ts
239
+ function useSetStorage<T>(
240
+ item: StorageItem<T>
241
+ ): (value: T | ((prev: T) => T)) => void
278
242
  ```
279
243
 
280
- ### Direct Access (Outside React)
281
-
282
- Perfect for API interceptors, middleware, or anywhere you need storage without React.
244
+ ### `storage`
283
245
 
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
- });
293
-
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);
246
+ ```ts
247
+ const storage: {
248
+ clear: (scope: StorageScope) => void;
249
+ clearAll: () => void;
300
250
  };
301
251
  ```
302
252
 
303
- ### Manual Subscriptions
253
+ Behavior:
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
+ - `clear(scope)` clears all keys in a single scope.
256
+ - `clearAll()` clears `Memory`, `Disk`, and `Secure`.
313
257
 
314
258
  ### Batch Operations
315
259
 
316
- Read or write multiple items at once. This is highly optimized on the native side for minimum overhead.
317
-
318
- ```typescript
319
- import { getBatch, setBatch, removeBatch } from "react-native-nitro-storage";
260
+ ```ts
261
+ type StorageBatchSetItem<T> = {
262
+ item: StorageItem<T>;
263
+ value: T;
264
+ };
320
265
 
321
- // Write multiple items
322
- setBatch(
323
- [
324
- { item: item1, value: "v1" },
325
- { item: item2, value: "v2" },
326
- ],
327
- StorageScope.Disk
328
- );
266
+ function getBatch(
267
+ items: readonly Pick<StorageItem<unknown>, "key" | "scope" | "get" | "deserialize">[],
268
+ scope: StorageScope
269
+ ): unknown[];
329
270
 
330
- // Read multiple items
331
- const [v1, v2] = getBatch([item1, item2], StorageScope.Disk);
271
+ function setBatch<T>(
272
+ items: readonly StorageBatchSetItem<T>[],
273
+ scope: StorageScope
274
+ ): void;
332
275
 
333
- // Remove multiple items
334
- removeBatch([item1, item2], StorageScope.Disk);
276
+ function removeBatch(
277
+ items: readonly Pick<StorageItem<unknown>, "key" | "scope" | "delete">[],
278
+ scope: StorageScope
279
+ ): void;
335
280
  ```
336
281
 
337
- ### Functional Updates
338
-
339
- Update state based on the previous value, just like `useState`.
282
+ Rules:
340
283
 
341
- ```typescript
342
- // Increment counter
343
- counterAtom.set((prev) => prev + 1);
344
- ```
284
+ - All items must match the batch `scope`.
285
+ - Items using `validate` or `expiration` automatically run via per-item `get()`/`set()` paths to preserve validation and TTL behavior.
286
+ - Mixed-scope calls throw:
287
+ - `Batch scope mismatch for "<key>": expected <Scope>, received <Scope>.`
345
288
 
346
- ### Optimized Writes
289
+ ### Migrations
347
290
 
348
- Use `useSetStorage` to set values without subscribing to updates (avoids re-renders).
291
+ ```ts
292
+ type MigrationContext = {
293
+ scope: StorageScope;
294
+ getRaw: (key: string) => string | undefined;
295
+ setRaw: (key: string, value: string) => void;
296
+ removeRaw: (key: string) => void;
297
+ };
349
298
 
350
- ```typescript
351
- import { useSetStorage } from "react-native-nitro-storage";
299
+ type Migration = (context: MigrationContext) => void;
352
300
 
353
- function IncrementButton() {
354
- const setCount = useSetStorage(counterAtom);
355
- return <Button onPress={() => setCount((c) => c + 1)} title="+" />;
356
- }
301
+ function registerMigration(version: number, migration: Migration): void;
302
+ function migrateToLatest(scope?: StorageScope): number;
357
303
  ```
358
304
 
359
- ### Clearing Data
360
-
361
- Clear entire storage scopes at once.
305
+ Behavior:
362
306
 
363
- ```typescript
364
- import { storage } from "react-native-nitro-storage";
307
+ - Versions must be positive integers.
308
+ - Duplicate versions throw.
309
+ - Migration version is tracked per scope using key `__nitro_storage_migration_version__`.
310
+ - `migrateToLatest` applies pending migrations in ascending version order and returns applied/latest version.
365
311
 
366
- // Clear all storage (all scopes)
367
- storage.clearAll();
368
-
369
- // Clear specific scope
370
- storage.clear(StorageScope.Memory);
371
- storage.clear(StorageScope.Disk);
372
- storage.clear(StorageScope.Secure);
373
- ```
312
+ Throws:
374
313
 
375
- ### Migration from MMKV
314
+ - `registerMigration`: throws when version is not a positive integer.
315
+ - `registerMigration`: throws when version is already registered.
316
+ - `migrateToLatest`: throws on invalid scope.
376
317
 
377
- Easily migrate data from `react-native-mmkv` to Nitro Storage.
318
+ ### Transactions
378
319
 
379
- ```typescript
380
- import { migrateFromMMKV } from "react-native-nitro-storage/src/migration";
320
+ ```ts
321
+ type TransactionContext = {
322
+ scope: StorageScope;
323
+ getRaw: (key: string) => string | undefined;
324
+ setRaw: (key: string, value: string) => void;
325
+ removeRaw: (key: string) => void;
326
+ getItem: <T>(item: Pick<StorageItem<T>, "scope" | "key" | "get">) => T;
327
+ setItem: <T>(item: Pick<StorageItem<T>, "scope" | "key" | "set">, value: T) => void;
328
+ removeItem: (item: Pick<StorageItem<unknown>, "scope" | "key" | "delete">) => void;
329
+ };
381
330
 
382
- // Migrate 'user-settings' and delete from MMKV
383
- migrateFromMMKV(mmkvInstance, settingsAtom, true);
331
+ function runTransaction<T>(
332
+ scope: StorageScope,
333
+ transaction: (context: TransactionContext) => T
334
+ ): T;
384
335
  ```
385
336
 
386
- ---
337
+ Behavior:
387
338
 
388
- ## πŸ“Š Performance Benchmarks
339
+ - On exception, it rolls back keys modified in that transaction.
340
+ - Rollback is best-effort within process lifetime.
341
+ - `setItem`/`removeItem` prefer item methods when available, so validation/TTL/cache semantics stay consistent.
389
342
 
390
- All operations are **100% synchronous** via JSIβ€”no promises, no bridge, no lag.
343
+ Throws:
391
344
 
392
- **Performance Metrics (1,000 operations per storage type):**
345
+ - Throws on invalid scope.
346
+ - Rethrows any error thrown by the transaction callback after rollback.
393
347
 
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** |
348
+ ### Validation and Expiration Types
399
349
 
400
- Run the benchmark yourself:
350
+ ```ts
351
+ type Validator<T> = (value: unknown) => value is T;
401
352
 
402
- ```bash
403
- cd apps/example
404
- npm run ios # or npm run android
405
- # Navigate to the "Benchmark" tab
353
+ type ExpirationConfig = {
354
+ ttlMs: number;
355
+ };
406
356
  ```
407
357
 
408
- ---
409
-
410
- ## 🎯 API Reference
411
-
412
- ### `createStorageItem<T>(config)`
413
-
414
- Creates a storage atom.
415
-
416
- **Parameters:**
417
-
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)
423
-
424
- **Returns:** `StorageItem<T>`
425
-
426
- ### `StorageItem<T>`
427
-
428
- **Methods:**
429
-
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
358
+ ### MMKV Migration
434
359
 
435
- ### `useStorage<T>(item: StorageItem<T>)`
436
-
437
- React hook using `useSyncExternalStore` for automatic re-renders.
360
+ ```ts
361
+ type MMKVLike = {
362
+ getString: (key: string) => string | undefined;
363
+ getNumber: (key: string) => number | undefined;
364
+ getBoolean: (key: string) => boolean | undefined;
365
+ contains: (key: string) => boolean;
366
+ delete: (key: string) => void;
367
+ getAllKeys: () => string[];
368
+ };
438
369
 
439
- **Returns:** `[value: T, setValue: (value: T | ((prev: T) => T)) => void]`
370
+ function migrateFromMMKV<T>(
371
+ mmkv: MMKVLike,
372
+ item: StorageItem<T>,
373
+ deleteFromMMKV?: boolean
374
+ ): boolean;
375
+ ```
440
376
 
441
- ### `useSetStorage<T>(item: StorageItem<T>)`
377
+ Behavior:
442
378
 
443
- Returns the setter function only. Does not subscribe to updates.
379
+ - Returns `true` when a value is found and copied, `false` otherwise.
380
+ - Read priority is: `getString` -> `getNumber` -> `getBoolean`.
381
+ - Uses `item.set(...)`, so schema validation on the target item still applies.
382
+ - If `deleteFromMMKV` is `true`, deletes only when migration succeeds.
444
383
 
445
- **Returns:** `(value: T | ((prev: T) => T)) => void`
384
+ ## Examples
446
385
 
447
- ### `storage` Object
386
+ ### Schema Validation + Fallback
448
387
 
449
- - `clearAll()`: Clears all storage across all scopes.
450
- - `clear(scope)`: Clears a specific storage scope.
388
+ ```ts
389
+ const userIdItem = createStorageItem<number>({
390
+ key: "user-id",
391
+ scope: StorageScope.Disk,
392
+ defaultValue: 0,
393
+ validate: (v): v is number => typeof v === "number" && v > 0,
394
+ onValidationError: () => 1,
395
+ });
396
+ ```
451
397
 
452
- ### Batch Operations
398
+ ### TTL
453
399
 
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.
400
+ ```ts
401
+ const otpItem = createStorageItem<string | undefined>({
402
+ key: "otp",
403
+ scope: StorageScope.Secure,
404
+ expiration: { ttlMs: 60_000 },
405
+ });
406
+ ```
457
407
 
458
- ---
408
+ ### Transaction
459
409
 
460
- ## πŸ” Security
410
+ ```ts
411
+ runTransaction(StorageScope.Disk, (tx) => {
412
+ tx.setRaw("a", JSON.stringify(1));
413
+ tx.setRaw("b", JSON.stringify(2));
414
+ });
415
+ ```
461
416
 
462
- ### iOS
417
+ ### Versioned Migrations
463
418
 
464
- - **Disk:** `UserDefaults.standard`
465
- - **Secure:** Keychain with `kSecAttrAccessibleWhenUnlocked`
419
+ ```ts
420
+ registerMigration(1, ({ setRaw }) => {
421
+ setRaw("seed", JSON.stringify({ ready: true }));
422
+ });
466
423
 
467
- ### Android
424
+ registerMigration(2, ({ getRaw, setRaw }) => {
425
+ const raw = getRaw("seed");
426
+ if (!raw) return;
427
+ const value = JSON.parse(raw) as { ready: boolean };
428
+ setRaw("seed", JSON.stringify({ ...value, migrated: true }));
429
+ });
468
430
 
469
- - **Disk:** `SharedPreferences`
470
- - **Secure:** `EncryptedSharedPreferences` with AES256-GCM encryption
431
+ migrateToLatest(StorageScope.Disk);
432
+ ```
471
433
 
472
- All secure operations use platform-native encryption. No data is stored in plain text.
434
+ ## Scope Semantics
473
435
 
474
- ---
436
+ - `Memory`: in-memory only, not persisted.
437
+ - `Disk`: App-scoped UserDefaults suite (iOS), SharedPreferences (Android), `localStorage` (web).
438
+ - `Secure`: Keychain (iOS), EncryptedSharedPreferences (Android), `sessionStorage` fallback (web).
475
439
 
476
- ## πŸ§ͺ Testing
440
+ ## Dev Commands
477
441
 
478
- Comprehensive test coverage for both TypeScript and C++:
442
+ From repo root:
479
443
 
480
444
  ```bash
481
- # TypeScript/Jest tests
482
- npm test
483
-
484
- # Type checking
485
- npm run typecheck
486
-
487
- # Build verification
488
- npm run build
445
+ bun run test -- --filter=react-native-nitro-storage
446
+ bun run typecheck -- --filter=react-native-nitro-storage
447
+ bun run build -- --filter=react-native-nitro-storage
448
+ bun run benchmark
489
449
  ```
490
450
 
491
- **Test Coverage:**
492
-
493
- - βœ… All storage scopes (Memory, Disk, Secure)
494
- - βœ… Custom serialization
495
- - βœ… Complex objects
496
- - βœ… Subscription/unsubscription
497
- - βœ… Memory leak prevention
498
- - βœ… Thread safety (C++)
499
-
500
- ---
501
-
502
- ## πŸ—οΈ Architecture
503
-
504
- Built on [Nitro Modules](https://nitro.margelo.com) for maximum performance:
451
+ Inside package:
505
452
 
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
510
-
511
- ---
512
-
513
- ## πŸ†š Comparison
514
-
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 | βœ… | ❌ | βœ… | βœ… | ❌ |
525
-
526
- ---
453
+ ```bash
454
+ bun run test
455
+ bun run test:coverage
456
+ bun run typecheck
457
+ bun run build
458
+ bun run benchmark
459
+ ```
527
460
 
528
- ## πŸ“„ License
461
+ ## License
529
462
 
530
463
  MIT
531
-
532
- ---
533
-
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