react-native-nitro-storage 0.5.0 → 0.5.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.
package/README.md CHANGED
@@ -3,7 +3,7 @@
3
3
  [![npm](https://img.shields.io/npm/v/react-native-nitro-storage)](https://www.npmjs.com/package/react-native-nitro-storage)
4
4
  [![MIT license](https://img.shields.io/badge/license-MIT-blue.svg)](LICENSE)
5
5
  [![React Native](https://img.shields.io/badge/react--native-%3E%3D0.75-61dafb)](https://reactnative.dev/)
6
- [![Nitro Modules](https://img.shields.io/badge/nitro--modules-%3E%3D0.35.4-black)](https://nitro.margelo.com/)
6
+ [![Nitro Modules](https://img.shields.io/badge/nitro--modules-%3E%3D0.35.5-black)](https://nitro.margelo.com/)
7
7
 
8
8
  One storage layer for render-time state, persisted app state, and native secrets.
9
9
 
@@ -42,6 +42,7 @@ Use it when you want one storage API for React Native and web, with fast synchro
42
42
  | Move existing MMKV data | `migrateFromMMKV` |
43
43
  | Persist storage on web | `setWebDiskStorageBackend` or `createIndexedDBBackend` |
44
44
  | Inspect secure backend state | `getSecurityCapabilities`, `getSecureMetadata`, metadata APIs |
45
+ | Connect external state/debug code | `subscribeNamespace`, `subscribePrefix`, `setEventObserver` |
45
46
 
46
47
  ## Use It When
47
48
 
@@ -56,7 +57,7 @@ Use a database or server-state cache instead when you need relational queries, c
56
57
  - Three scopes: in-memory session state, persisted disk state, and platform secure storage.
57
58
  - Secure storage backed by iOS Keychain and Android Keystore/EncryptedSharedPreferences.
58
59
  - React hooks without providers: `useStorage`, `useStorageSelector`, and `useSetStorage`.
59
- - Batch reads/writes, namespace cleanup, raw import/export, transactions, and migrations.
60
+ - Batch reads/writes, namespace cleanup, raw import/export, event subscriptions, transactions, and migrations.
60
61
  - Web parity with configurable Disk/Secure backends and an IndexedDB backend.
61
62
  - MMKV migration helper for moving existing keys without rewriting app code first.
62
63
 
@@ -188,6 +189,37 @@ runTransaction(StorageScope.Disk, (tx) => {
188
189
  });
189
190
  ```
190
191
 
192
+ Create a raw snapshot for backups or test fixtures, then restore it later:
193
+
194
+ ```ts
195
+ import { StorageScope, storage } from "react-native-nitro-storage";
196
+
197
+ const snapshot = storage.export(StorageScope.Disk);
198
+
199
+ storage.import(snapshot, StorageScope.Disk);
200
+ ```
201
+
202
+ `storage.export(StorageScope.Secure)` returns raw secret values. Do not log Secure exports or include them in diagnostics, analytics, crash reports, or support bundles.
203
+
204
+ Subscribe to storage changes outside React:
205
+
206
+ ```ts
207
+ import { StorageScope, storage } from "react-native-nitro-storage";
208
+
209
+ const unsubscribe = storage.subscribeNamespace(
210
+ "settings",
211
+ StorageScope.Disk,
212
+ (event) => {
213
+ if (event.type === "batch") {
214
+ console.log("settings changed", event.changes.length);
215
+ return;
216
+ }
217
+
218
+ console.log(event.key, event.operation);
219
+ },
220
+ );
221
+ ```
222
+
191
223
  ## Storage Scopes
192
224
 
193
225
  | Scope | Backing store | Best for |
@@ -243,7 +275,7 @@ import {
243
275
  The main building blocks are:
244
276
 
245
277
  - `createStorageItem<T>(config)` for typed values.
246
- - `storage` for raw reads, namespace cleanup, secure metadata, metrics, and runtime capability checks.
278
+ - `storage` for raw reads, namespace cleanup, events, secure metadata, metrics, and runtime capability checks.
247
279
  - `getBatch`, `setBatch`, and `removeBatch` for multi-key work.
248
280
  - `runTransaction` for synchronous rollback on failure.
249
281
  - `registerMigration` and `migrateToLatest` for versioned local data migrations.
@@ -265,7 +297,7 @@ Peer dependencies:
265
297
 
266
298
  - `react >=18.2.0`
267
299
  - `react-native >=0.75.0`
268
- - `react-native-nitro-modules >=0.35.4`
300
+ - `react-native-nitro-modules >=0.35.5`
269
301
 
270
302
  ## Security Model
271
303
 
@@ -355,9 +387,11 @@ bun run format:check -- --filter=react-native-nitro-storage
355
387
  bun run typecheck -- --filter=react-native-nitro-storage
356
388
  bun run test:types -- --filter=react-native-nitro-storage
357
389
  bun run test -- --filter=react-native-nitro-storage
390
+ bun run test:coverage -- --filter=react-native-nitro-storage
358
391
  bun run test:cpp -- --filter=react-native-nitro-storage
392
+ bun run test:cpp:coverage -- --filter=react-native-nitro-storage
359
393
  bun run --cwd packages/react-native-nitro-storage check:pack
360
- npm publish --dry-run
394
+ bun run publish-package:dry -- --yes --with-coverage
361
395
  ```
362
396
 
363
397
  ## Development
@@ -378,7 +412,7 @@ Release checks:
378
412
  bun run build -- --filter=react-native-nitro-storage
379
413
  bun run benchmark -- --filter=react-native-nitro-storage
380
414
  bun run --cwd packages/react-native-nitro-storage check:pack
381
- npm publish --dry-run
415
+ bun run publish-package:dry -- --yes
382
416
  ```
383
417
 
384
418
  ## License
@@ -44,9 +44,20 @@ const item = createStorageItem<T>({
44
44
  | `delete()` | Remove the key. |
45
45
  | `has()` | Check whether the key exists. |
46
46
  | `subscribe(callback)` | Subscribe to item changes. Returns an unsubscribe function. |
47
+ | `subscribeSelector(...)` | Subscribe to a selected value with an equality check. |
47
48
  | `serialize(value)` | Serialize a value with the item encoder. |
48
49
  | `deserialize(value)` | Deserialize a raw string with the item decoder. |
49
50
 
51
+ ```ts
52
+ const unsubscribe = profileItem.subscribeSelector(
53
+ (profile) => profile.name,
54
+ (name, previousName) => {
55
+ console.log("Profile name changed", { name, previousName });
56
+ },
57
+ { fireImmediately: true },
58
+ );
59
+ ```
60
+
50
61
  ## React Hooks
51
62
 
52
63
  ```ts
@@ -61,37 +72,84 @@ See [react-hooks.md](react-hooks.md).
61
72
 
62
73
  `storage` exposes raw and cross-item utilities:
63
74
 
64
- | Method | Purpose |
65
- | ---------------------------------- | ------------------------------------------------------------- |
66
- | `clear(scope)` | Clear one scope. |
67
- | `clearAll()` | Clear Memory, Disk, and Secure scopes. |
68
- | `clearNamespace(namespace, scope)` | Remove keys under `namespace:`. |
69
- | `clearBiometric()` | Clear biometric Secure entries. |
70
- | `has(key, scope)` | Check for a raw key. |
71
- | `getAllKeys(scope)` | List raw keys. |
72
- | `getKeysByPrefix(prefix, scope)` | List raw keys with a prefix. |
73
- | `getByPrefix(prefix, scope)` | Read raw string values by prefix. |
74
- | `getAll(scope)` | Read all raw string values in a scope. |
75
- | `size(scope)` | Return approximate scope entry count. |
76
- | `setAccessControl(accessControl)` | Set the default Secure access control level. |
77
- | `setSecureWritesAsync(enabled)` | Toggle Android secure writes between sync and async modes. |
78
- | `setDiskWritesAsync(enabled)` | Toggle coalesced Disk write behavior. |
79
- | `flushDiskWrites()` | Flush pending Disk writes. |
80
- | `flushSecureWrites()` | Flush pending Secure writes. |
81
- | `setKeychainAccessGroup(group)` | Configure iOS Keychain access group. |
82
- | `setMetricsObserver(observer)` | Receive operation timing events. |
83
- | `getMetricsSnapshot()` | Read aggregated metrics. |
84
- | `resetMetrics()` | Clear metrics counters. |
85
- | `getCapabilities()` | Read runtime storage capabilities. |
86
- | `getSecurityCapabilities()` | Read secure backend capability metadata. |
87
- | `getSecureMetadata(key)` | Read secure metadata for one key without returning its value. |
88
- | `getAllSecureMetadata()` | Read secure metadata for all secure keys without values. |
89
- | `getString(key, scope)` | Read a raw string. |
90
- | `setString(key, value, scope)` | Write a raw string. |
91
- | `deleteString(key, scope)` | Remove a raw key. |
92
- | `import(data, scope)` | Bulk import raw strings. |
93
-
94
- Raw string APIs bypass item serialization and validation. Prefer `StorageItem<T>` unless you are migrating, importing, or writing a custom integration.
75
+ | Method | Purpose |
76
+ | ------------------------------------------------ | ------------------------------------------------------------- |
77
+ | `clear(scope)` | Clear one scope. |
78
+ | `clearAll()` | Clear Memory, Disk, and Secure scopes. |
79
+ | `clearNamespace(namespace, scope)` | Remove keys under `namespace:`. |
80
+ | `subscribe(scope, listener)` | Subscribe to raw scope-level change events. |
81
+ | `subscribeKey(scope, key, listener)` | Subscribe to raw events for one key. |
82
+ | `subscribePrefix(scope, prefix, listener)` | Subscribe to raw events for matching key prefixes. |
83
+ | `subscribeNamespace(namespace, scope, listener)` | Subscribe to raw events for `namespace:` keys. |
84
+ | `setEventObserver(observer)` | Receive all change events for devtools or logging. |
85
+ | `clearBiometric()` | Clear biometric Secure entries. |
86
+ | `has(key, scope)` | Check for a raw key. |
87
+ | `getAllKeys(scope)` | List raw keys. |
88
+ | `getKeysByPrefix(prefix, scope)` | List raw keys with a prefix. |
89
+ | `getByPrefix(prefix, scope)` | Read raw string values by prefix. |
90
+ | `getAll(scope)` | Read all raw string values in a scope. |
91
+ | `size(scope)` | Return approximate scope entry count. |
92
+ | `setAccessControl(accessControl)` | Set the default Secure access control level. |
93
+ | `setSecureWritesAsync(enabled)` | Toggle Android secure writes between sync and async modes. |
94
+ | `setDiskWritesAsync(enabled)` | Toggle coalesced Disk write behavior. |
95
+ | `flushDiskWrites()` | Flush pending Disk writes. |
96
+ | `flushSecureWrites()` | Flush pending Secure writes. |
97
+ | `setKeychainAccessGroup(group)` | Configure iOS Keychain access group. |
98
+ | `setMetricsObserver(observer)` | Receive operation timing events. |
99
+ | `getMetricsSnapshot()` | Read aggregated metrics. |
100
+ | `resetMetrics()` | Clear metrics counters. |
101
+ | `getCapabilities()` | Read runtime storage capabilities. |
102
+ | `getSecurityCapabilities()` | Read secure backend capability metadata. |
103
+ | `getSecureMetadata(key)` | Read secure metadata for one key without returning its value. |
104
+ | `getAllSecureMetadata()` | Read secure metadata for all secure keys without values. |
105
+ | `getString(key, scope)` | Read a raw string. |
106
+ | `setString(key, value, scope)` | Write a raw string. |
107
+ | `deleteString(key, scope)` | Remove a raw key. |
108
+ | `export(scope)` | Snapshot raw strings from one scope. |
109
+ | `import(data, scope)` | Bulk import raw strings. |
110
+
111
+ Raw string APIs bypass item serialization and validation. Prefer `StorageItem<T>` unless you are migrating, exporting/importing, or writing a custom integration.
112
+
113
+ ```ts
114
+ const diskSnapshot = storage.export(StorageScope.Disk);
115
+ storage.import(diskSnapshot, StorageScope.Disk);
116
+ ```
117
+
118
+ Secure exports contain raw secret values. Do not log `storage.export(StorageScope.Secure)` output or attach it to diagnostics, analytics, crash reports, or support bundles.
119
+
120
+ ## Event Subscriptions
121
+
122
+ Use raw subscriptions when integrating Nitro Storage with state managers, sync engines, debug tooling, or non-React code.
123
+
124
+ ```ts
125
+ const unsubscribe = storage.subscribeNamespace(
126
+ "auth",
127
+ StorageScope.Secure,
128
+ (event) => {
129
+ if (event.type === "batch") {
130
+ console.log(
131
+ "Auth keys changed",
132
+ event.changes.map((change) => change.key),
133
+ );
134
+ return;
135
+ }
136
+
137
+ console.log("Auth key changed", event.key, event.operation);
138
+ },
139
+ );
140
+ ```
141
+
142
+ For whole-app debug tooling, install one observer:
143
+
144
+ ```ts
145
+ storage.setEventObserver((event) => {
146
+ if (event.scope !== StorageScope.Secure) {
147
+ console.log(event);
148
+ }
149
+ });
150
+ ```
151
+
152
+ Local batch APIs emit one `type: "batch"` envelope to scope and prefix/namespace listeners. Key subscribers receive the matching per-key change so direct key integrations do not need to unpack batch envelopes. Secure events can include raw secret values; do not log Secure event payloads in production.
95
153
 
96
154
  ## Batch Operations
97
155
 
@@ -201,6 +259,12 @@ Common public types:
201
259
  - `StorageMetricsEvent`
202
260
  - `StorageMetricsObserver`
203
261
  - `StorageMetricSummary`
262
+ - `StorageChangeEvent`
263
+ - `StorageKeyChangeEvent`
264
+ - `StorageBatchChangeEvent`
265
+ - `StorageChangeOperation`
266
+ - `StorageChangeSource`
267
+ - `StorageEventListener`
204
268
  - `MigrationContext`
205
269
  - `Migration`
206
270
  - `TransactionContext`
@@ -44,6 +44,22 @@ setBatch(
44
44
 
45
45
  Memory-scope batch writes are two-phase: all values are written first, then listeners are notified. Items with validation or TTL fall back to per-item writes so those rules still run.
46
46
 
47
+ Scope and prefix subscriptions receive one batch event for raw batch writes and removes:
48
+
49
+ ```ts
50
+ const unsubscribe = storage.subscribePrefix(
51
+ StorageScope.Disk,
52
+ "settings:",
53
+ (event) => {
54
+ if (event.type === "batch") {
55
+ event.changes.forEach((change) => {
56
+ console.log(change.key, change.operation);
57
+ });
58
+ }
59
+ },
60
+ );
61
+ ```
62
+
47
63
  ## Batch Removes
48
64
 
49
65
  ```ts
@@ -52,24 +68,22 @@ import { removeBatch, StorageScope } from "react-native-nitro-storage";
52
68
  removeBatch([themeItem, localeItem], StorageScope.Disk);
53
69
  ```
54
70
 
55
- ## Raw Import
71
+ ## Raw Import and Export
56
72
 
57
- `storage.import(data, scope)` writes raw strings. It does not serialize values.
73
+ `storage.export(scope)` returns raw strings, and `storage.import(data, scope)` writes raw strings. These APIs do not serialize values.
58
74
 
59
75
  ```ts
60
76
  import { storage, StorageScope } from "react-native-nitro-storage";
61
77
 
62
- storage.import(
63
- {
64
- "flags:newOnboarding": "true",
65
- "flags:paywall": "control",
66
- },
67
- StorageScope.Disk,
68
- );
78
+ const snapshot = storage.export(StorageScope.Disk);
79
+
80
+ storage.import(snapshot, StorageScope.Disk);
69
81
  ```
70
82
 
71
83
  For Memory scope, import is atomic: all keys are written before listeners fire. For Disk and Secure, import delegates to native or web batch paths.
72
84
 
85
+ Secure exports contain raw secret values. Do not log them or include them in diagnostics, analytics, crash reports, or support bundles.
86
+
73
87
  ## Transactions
74
88
 
75
89
  Use `runTransaction(scope, callback)` when several raw or item writes should roll back together if the callback throws.
package/docs/recipes.md CHANGED
@@ -205,19 +205,15 @@ tokenItem.set("token");
205
205
  storage.flushSecureWrites();
206
206
  ```
207
207
 
208
- ## Raw Import
208
+ ## Raw Import and Export
209
209
 
210
210
  ```ts
211
- storage.import(
212
- {
213
- "settings:theme": "dark",
214
- "settings:locale": "en-US",
215
- },
216
- StorageScope.Disk,
217
- );
211
+ const snapshot = storage.export(StorageScope.Disk);
212
+
213
+ storage.import(snapshot, StorageScope.Disk);
218
214
  ```
219
215
 
220
- Raw import writes strings exactly as provided. It does not run item serializers.
216
+ Raw export/import reads and writes strings exactly as stored. It does not run item serializers. Secure exports contain raw secret values, so do not log them or attach them to diagnostics.
221
217
 
222
218
  ## Snapshot and Cleanup
223
219
 
@@ -259,6 +255,31 @@ const snapshot = storage.getMetricsSnapshot();
259
255
  storage.resetMetrics();
260
256
  ```
261
257
 
258
+ ## Event Logging
259
+
260
+ ```ts
261
+ const unsubscribe = storage.subscribePrefix(
262
+ StorageScope.Disk,
263
+ "settings:",
264
+ (event) => {
265
+ if (event.type === "batch") {
266
+ console.log("settings changed", event.changes.length);
267
+ return;
268
+ }
269
+
270
+ console.log(event.key, event.operation);
271
+ },
272
+ );
273
+
274
+ storage.setEventObserver((event) => {
275
+ if (event.scope !== StorageScope.Secure) {
276
+ console.log(event.type, event.operation);
277
+ }
278
+ });
279
+ ```
280
+
281
+ Use `subscribePrefix()` or `subscribeNamespace()` for targeted integrations. Use `setEventObserver()` for devtools-style logging. Secure events can include raw secret values, so filter them out of logs.
282
+
262
283
  ## Capability Checks
263
284
 
264
285
  ```ts
@@ -114,6 +114,25 @@ const allKeys = storage.getAllSecureMetadata();
114
114
 
115
115
  `getSecureMetadata()` and `getAllSecureMetadata()` never return stored secret values. They report key existence, storage kind, backend name, access-control metadata, and whether a metadata path accidentally exposed a value.
116
116
 
117
+ ## Secure Export Warning
118
+
119
+ `storage.export(StorageScope.Secure)` returns raw secret values so it can round-trip with `storage.import(data, StorageScope.Secure)`.
120
+
121
+ ```ts
122
+ import { storage, StorageScope } from "react-native-nitro-storage";
123
+
124
+ const secureSnapshot = storage.export(StorageScope.Secure);
125
+ storage.import(secureSnapshot, StorageScope.Secure);
126
+ ```
127
+
128
+ Only keep Secure exports in memory for the shortest possible workflow. Do not log them or include them in diagnostics, analytics, crash reports, or support bundles.
129
+
130
+ ## Secure Event Warning
131
+
132
+ Secure scope event subscriptions and `storage.setEventObserver()` can receive raw secret values in `oldValue`, `newValue`, or batch `changes`.
133
+
134
+ Use Secure events for in-memory coordination only. Do not log Secure event payloads or send them to analytics, crash reporting, support bundles, or devtools sessions that persist outside the device.
135
+
117
136
  ## Locked Keychain Errors
118
137
 
119
138
  ```ts