react-native-nitro-storage 0.1.3 β 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 +320 -391
- package/android/src/main/cpp/AndroidStorageAdapterCpp.cpp +101 -0
- package/android/src/main/cpp/AndroidStorageAdapterCpp.hpp +6 -41
- package/android/src/main/java/com/nitrostorage/AndroidStorageAdapter.kt +125 -37
- package/app.plugin.js +9 -7
- package/cpp/bindings/HybridStorage.cpp +214 -19
- package/cpp/bindings/HybridStorage.hpp +1 -0
- package/cpp/core/NativeStorageAdapter.hpp +7 -0
- package/ios/IOSStorageAdapterCpp.hpp +6 -0
- package/ios/IOSStorageAdapterCpp.mm +90 -7
- package/lib/commonjs/index.js +537 -66
- package/lib/commonjs/index.js.map +1 -1
- package/lib/commonjs/index.web.js +558 -130
- package/lib/commonjs/index.web.js.map +1 -1
- package/lib/commonjs/internal.js +102 -0
- package/lib/commonjs/internal.js.map +1 -0
- package/lib/module/index.js +528 -67
- package/lib/module/index.js.map +1 -1
- package/lib/module/index.web.js +536 -122
- package/lib/module/index.web.js.map +1 -1
- package/lib/module/internal.js +92 -0
- package/lib/module/internal.js.map +1 -0
- package/lib/typescript/index.d.ts +42 -6
- package/lib/typescript/index.d.ts.map +1 -1
- package/lib/typescript/index.web.d.ts +45 -12
- package/lib/typescript/index.web.d.ts.map +1 -1
- package/lib/typescript/internal.d.ts +19 -0
- package/lib/typescript/internal.d.ts.map +1 -0
- package/lib/typescript/migration.d.ts +2 -3
- package/lib/typescript/migration.d.ts.map +1 -1
- package/nitrogen/generated/android/NitroStorage+autolinking.cmake +1 -1
- package/nitrogen/generated/android/NitroStorage+autolinking.gradle +1 -1
- package/nitrogen/generated/android/NitroStorageOnLoad.cpp +1 -1
- package/nitrogen/generated/android/NitroStorageOnLoad.hpp +1 -1
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/com/nitrostorage/NitroStorageOnLoad.kt +1 -1
- package/nitrogen/generated/ios/NitroStorage+autolinking.rb +1 -1
- package/nitrogen/generated/ios/NitroStorage-Swift-Cxx-Bridge.cpp +1 -1
- package/nitrogen/generated/ios/NitroStorage-Swift-Cxx-Bridge.hpp +1 -1
- package/nitrogen/generated/ios/NitroStorage-Swift-Cxx-Umbrella.hpp +1 -1
- package/nitrogen/generated/ios/NitroStorageAutolinking.mm +1 -1
- package/nitrogen/generated/ios/NitroStorageAutolinking.swift +5 -1
- package/nitrogen/generated/shared/c++/HybridStorageSpec.cpp +1 -1
- package/nitrogen/generated/shared/c++/HybridStorageSpec.hpp +1 -1
- package/package.json +19 -8
- package/src/index.ts +734 -74
- package/src/index.web.ts +732 -128
- package/src/internal.ts +134 -0
- package/src/migration.ts +2 -2
package/README.md
CHANGED
|
@@ -1,93 +1,63 @@
|
|
|
1
|
-
# react-native-nitro-storage
|
|
1
|
+
# react-native-nitro-storage
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
Synchronous storage for React Native with a unified API for memory, disk, and secure data.
|
|
4
4
|
|
|
5
|
-
|
|
6
|
-
[](https://opensource.org/licenses/MIT)
|
|
7
|
-
[](https://nitro.margelo.com)
|
|
5
|
+
## Requirements
|
|
8
6
|
|
|
9
|
-
|
|
7
|
+
- `react-native >= 0.75.0`
|
|
8
|
+
- `react-native-nitro-modules >= 0.33.9`
|
|
9
|
+
- `react >= 18.2.0`
|
|
10
10
|
|
|
11
|
-
|
|
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
|
-
###
|
|
17
|
+
### Expo
|
|
57
18
|
|
|
58
19
|
```bash
|
|
59
|
-
|
|
20
|
+
bunx expo install react-native-nitro-storage react-native-nitro-modules
|
|
60
21
|
```
|
|
61
22
|
|
|
62
|
-
|
|
23
|
+
`app.json`:
|
|
63
24
|
|
|
64
25
|
```json
|
|
65
26
|
{
|
|
66
27
|
"expo": {
|
|
67
|
-
"plugins": [
|
|
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
|
-
|
|
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
|
-
|
|
49
|
+
bunx expo prebuild
|
|
76
50
|
```
|
|
77
51
|
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
### For Bare React Native Projects
|
|
52
|
+
### Bare React Native
|
|
81
53
|
|
|
82
|
-
|
|
54
|
+
iOS:
|
|
83
55
|
|
|
84
56
|
```bash
|
|
85
57
|
cd ios && pod install
|
|
86
58
|
```
|
|
87
59
|
|
|
88
|
-
|
|
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
|
-
```
|
|
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
|
-
|
|
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
|
-
|
|
122
|
-
|
|
123
|
-
const [count, setCount] = useStorage(counterAtom);
|
|
84
|
+
export function Counter() {
|
|
85
|
+
const [count, setCount] = useStorage(counterItem);
|
|
124
86
|
|
|
125
87
|
return (
|
|
126
|
-
<
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
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
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
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
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
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
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
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
|
-
|
|
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
|
-
|
|
193
|
+
### `createStorageItem<T>(config)`
|
|
202
194
|
|
|
203
|
-
|
|
195
|
+
```ts
|
|
196
|
+
function createStorageItem<T = undefined>(config: StorageItemConfig<T>): StorageItem<T>
|
|
197
|
+
```
|
|
204
198
|
|
|
205
|
-
|
|
206
|
-
Use explicit generics for nullable values. `defaultValue` is optional and defaults to `undefined`.
|
|
199
|
+
Notes:
|
|
207
200
|
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
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
|
-
|
|
217
|
-
const tokenAtom = createStorageItem<string | undefined>({
|
|
218
|
-
key: "auth-token",
|
|
219
|
-
scope: StorageScope.Secure,
|
|
220
|
-
});
|
|
212
|
+
Throws:
|
|
221
213
|
|
|
222
|
-
|
|
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
|
-
|
|
231
|
-
For required values, just specify the default.
|
|
216
|
+
### `useStorage(item)`
|
|
232
217
|
|
|
233
|
-
```
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
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
|
-
###
|
|
224
|
+
### `useStorageSelector(item, selector, isEqual?)`
|
|
242
225
|
|
|
243
|
-
```
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
276
|
-
|
|
277
|
-
|
|
238
|
+
```ts
|
|
239
|
+
function useSetStorage<T>(
|
|
240
|
+
item: StorageItem<T>
|
|
241
|
+
): (value: T | ((prev: T) => T)) => void
|
|
278
242
|
```
|
|
279
243
|
|
|
280
|
-
###
|
|
281
|
-
|
|
282
|
-
Perfect for API interceptors, middleware, or anywhere you need storage without React.
|
|
244
|
+
### `storage`
|
|
283
245
|
|
|
284
|
-
```
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
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
|
-
|
|
253
|
+
Behavior:
|
|
304
254
|
|
|
305
|
-
|
|
306
|
-
|
|
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
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
260
|
+
```ts
|
|
261
|
+
type StorageBatchSetItem<T> = {
|
|
262
|
+
item: StorageItem<T>;
|
|
263
|
+
value: T;
|
|
264
|
+
};
|
|
320
265
|
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
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
|
-
|
|
331
|
-
|
|
271
|
+
function setBatch<T>(
|
|
272
|
+
items: readonly StorageBatchSetItem<T>[],
|
|
273
|
+
scope: StorageScope
|
|
274
|
+
): void;
|
|
332
275
|
|
|
333
|
-
|
|
334
|
-
|
|
276
|
+
function removeBatch(
|
|
277
|
+
items: readonly Pick<StorageItem<unknown>, "key" | "scope" | "delete">[],
|
|
278
|
+
scope: StorageScope
|
|
279
|
+
): void;
|
|
335
280
|
```
|
|
336
281
|
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
Update state based on the previous value, just like `useState`.
|
|
282
|
+
Rules:
|
|
340
283
|
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
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
|
-
###
|
|
289
|
+
### Migrations
|
|
347
290
|
|
|
348
|
-
|
|
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
|
-
|
|
351
|
-
import { useSetStorage } from "react-native-nitro-storage";
|
|
299
|
+
type Migration = (context: MigrationContext) => void;
|
|
352
300
|
|
|
353
|
-
function
|
|
354
|
-
|
|
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
|
-
|
|
360
|
-
|
|
361
|
-
Clear entire storage scopes at once.
|
|
305
|
+
Behavior:
|
|
362
306
|
|
|
363
|
-
|
|
364
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
318
|
+
### Transactions
|
|
378
319
|
|
|
379
|
-
```
|
|
380
|
-
|
|
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
|
-
|
|
383
|
-
|
|
331
|
+
function runTransaction<T>(
|
|
332
|
+
scope: StorageScope,
|
|
333
|
+
transaction: (context: TransactionContext) => T
|
|
334
|
+
): T;
|
|
384
335
|
```
|
|
385
336
|
|
|
386
|
-
|
|
337
|
+
Behavior:
|
|
387
338
|
|
|
388
|
-
|
|
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
|
-
|
|
343
|
+
Throws:
|
|
391
344
|
|
|
392
|
-
|
|
345
|
+
- Throws on invalid scope.
|
|
346
|
+
- Rethrows any error thrown by the transaction callback after rollback.
|
|
393
347
|
|
|
394
|
-
|
|
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
|
-
|
|
350
|
+
```ts
|
|
351
|
+
type Validator<T> = (value: unknown) => value is T;
|
|
401
352
|
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
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
|
-
|
|
436
|
-
|
|
437
|
-
|
|
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
|
-
|
|
370
|
+
function migrateFromMMKV<T>(
|
|
371
|
+
mmkv: MMKVLike,
|
|
372
|
+
item: StorageItem<T>,
|
|
373
|
+
deleteFromMMKV?: boolean
|
|
374
|
+
): boolean;
|
|
375
|
+
```
|
|
440
376
|
|
|
441
|
-
|
|
377
|
+
Behavior:
|
|
442
378
|
|
|
443
|
-
Returns
|
|
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
|
-
|
|
384
|
+
## Examples
|
|
446
385
|
|
|
447
|
-
###
|
|
386
|
+
### Schema Validation + Fallback
|
|
448
387
|
|
|
449
|
-
|
|
450
|
-
|
|
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
|
-
###
|
|
398
|
+
### TTL
|
|
453
399
|
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
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
|
-
|
|
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
|
-
###
|
|
417
|
+
### Versioned Migrations
|
|
463
418
|
|
|
464
|
-
|
|
465
|
-
|
|
419
|
+
```ts
|
|
420
|
+
registerMigration(1, ({ setRaw }) => {
|
|
421
|
+
setRaw("seed", JSON.stringify({ ready: true }));
|
|
422
|
+
});
|
|
466
423
|
|
|
467
|
-
|
|
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
|
-
|
|
470
|
-
|
|
431
|
+
migrateToLatest(StorageScope.Disk);
|
|
432
|
+
```
|
|
471
433
|
|
|
472
|
-
|
|
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
|
-
##
|
|
440
|
+
## Dev Commands
|
|
477
441
|
|
|
478
|
-
|
|
442
|
+
From repo root:
|
|
479
443
|
|
|
480
444
|
```bash
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
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
|
-
|
|
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
|
-
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
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
|
-
##
|
|
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
|