react-native-nitro-storage 0.5.8 → 0.6.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.
Files changed (53) hide show
  1. package/CHANGELOG.md +384 -0
  2. package/README.md +178 -10
  3. package/android/src/main/AndroidManifest.xml +9 -0
  4. package/android/src/main/java/com/nitrostorage/NitroStorageInitializer.kt +38 -0
  5. package/app.plugin.js +3 -47
  6. package/cpp/bindings/HybridStorage.cpp +25 -9
  7. package/cpp/bindings/HybridStorage.hpp +5 -0
  8. package/lib/commonjs/index.js +19 -2
  9. package/lib/commonjs/index.js.map +1 -1
  10. package/lib/commonjs/index.web.js +19 -2
  11. package/lib/commonjs/index.web.js.map +1 -1
  12. package/lib/commonjs/shared.js.map +1 -1
  13. package/lib/commonjs/storage-core.js +306 -10
  14. package/lib/commonjs/storage-core.js.map +1 -1
  15. package/lib/commonjs/storage-events.js.map +1 -1
  16. package/lib/commonjs/storage-hooks.js +16 -1
  17. package/lib/commonjs/storage-hooks.js.map +1 -1
  18. package/lib/commonjs/testing.js +267 -0
  19. package/lib/commonjs/testing.js.map +1 -0
  20. package/lib/module/index.js +5 -1
  21. package/lib/module/index.js.map +1 -1
  22. package/lib/module/index.web.js +5 -1
  23. package/lib/module/index.web.js.map +1 -1
  24. package/lib/module/shared.js.map +1 -1
  25. package/lib/module/storage-core.js +307 -11
  26. package/lib/module/storage-core.js.map +1 -1
  27. package/lib/module/storage-events.js.map +1 -1
  28. package/lib/module/storage-hooks.js +15 -2
  29. package/lib/module/storage-hooks.js.map +1 -1
  30. package/lib/module/testing.js +188 -0
  31. package/lib/module/testing.js.map +1 -0
  32. package/lib/typescript/index.d.ts +22 -4
  33. package/lib/typescript/index.d.ts.map +1 -1
  34. package/lib/typescript/index.web.d.ts +22 -4
  35. package/lib/typescript/index.web.d.ts.map +1 -1
  36. package/lib/typescript/shared.d.ts +1 -0
  37. package/lib/typescript/shared.d.ts.map +1 -1
  38. package/lib/typescript/storage-core.d.ts +51 -2
  39. package/lib/typescript/storage-core.d.ts.map +1 -1
  40. package/lib/typescript/storage-events.d.ts +1 -1
  41. package/lib/typescript/storage-events.d.ts.map +1 -1
  42. package/lib/typescript/storage-hooks.d.ts +18 -1
  43. package/lib/typescript/storage-hooks.d.ts.map +1 -1
  44. package/lib/typescript/testing.d.ts +148 -0
  45. package/lib/typescript/testing.d.ts.map +1 -0
  46. package/package.json +8 -1
  47. package/src/index.ts +16 -2
  48. package/src/index.web.ts +16 -2
  49. package/src/shared.ts +1 -0
  50. package/src/storage-core.ts +446 -11
  51. package/src/storage-events.ts +2 -0
  52. package/src/storage-hooks.ts +42 -3
  53. package/src/testing.ts +270 -0
package/CHANGELOG.md ADDED
@@ -0,0 +1,384 @@
1
+ # Changelog
2
+
3
+ All notable changes to this project are documented in this file.
4
+
5
+ The format follows Keep a Changelog and the project adheres to SemVer.
6
+
7
+ ## 0.6.0 - 2026-06-15
8
+
9
+ ### Added
10
+
11
+ - Object-state ergonomics on `StorageItem<T>`: `item.merge(partial)` for shallow object updates, `item.reset()` to return to the default value, and `item.setOrDelete(value)` which deletes on `null`/`undefined` and sets otherwise.
12
+ - Scoped item factories `memoryItem`, `diskItem`, and `secureItem` so call sites no longer repeat `scope: StorageScope.X`.
13
+ - `createSetItem()` for set-membership state backed by storage, with `add`/`delete`/`has`/`toggle`/`values`/`size`/`clear`/`reset` and no-op-safe writes (no event/render churn when adding an existing member or deleting an absent one).
14
+ - Lifecycle helpers: `storage.clear(scope, { except })` to wipe a scope while preserving listed keys/items, per-item `group` config plus `storage.clearGroup(group)` and `storage.getGroupItems(group)` (which compose for "clear all except a group").
15
+ - Declarative legacy migration: `renameFrom` on items (and per-key on `createSecureAuthStorage`) copies a legacy key to the new key on first read and removes the legacy entry. `createSecureAuthStorage` also accepts `group` and `fallbackToCacheOnReadError`.
16
+ - Secure read resilience: `fallbackToCacheOnReadError` returns the last cached value when a secure read throws a locked-keychain error, plus an `onReadError` hook.
17
+ - Global expiration events: TTL expiry now emits a `"expire"` change event (memory and disk) routed through the event bus, with `storage.subscribeExpired(scope, listener)`.
18
+ - Hook ergonomics: `useStorage` now returns a third, render-stable `actions` element (`set`/`merge`/`reset`/`remove`/`setOrDelete`); new `useStorageValue` (read-only) and `useStorageActions` hooks.
19
+ - Dev introspection: `storage.findDuplicateKeys()` and `storage.getRegisteredKeys()` to audit accidental `(scope, key)` collisions at startup.
20
+ - New `react-native-nitro-storage/testing` entrypoint: a faithful in-memory implementation of the full public surface plus `createNitroStorageMock()` and `resetNitroStorageMock()` for Jest/Storybook without native modules.
21
+
22
+ ### Changed
23
+
24
+ - Faster writes when nothing is subscribed: the native write/notify path now takes a lock-free fast path (per-scope atomic listener counts) and skips locking and copying the listener vector when a scope has no listeners. Applies to both iOS and Android via the shared C++ `HybridStorage`, and is thread-safe (verified under the C++ AddressSanitizer, ThreadSanitizer, and UndefinedBehaviorSanitizer suites).
25
+
26
+ ### Breaking Changes
27
+
28
+ All new APIs are additive — existing code keeps working. These behavior and
29
+ type changes can affect advanced consumers:
30
+
31
+ - TTL expiry now emits a `"expire"` change event instead of `"remove"`. Previously, a disk/secure value expiring on read emitted `operation: "remove"` and an expiring memory value emitted no event at all. If you subscribe to storage events and branch on `operation === "remove"` to detect expiry, also handle `"expire"` (or use the new `storage.subscribeExpired()`).
32
+ - `StorageChangeOperation` gained the `"expire"` and `"clearGroup"` members. Exhaustive `switch` statements over a change event's `operation` need cases for the new members.
33
+ - `useStorage()` now returns a three-element tuple `[value, setter, actions]` (was two). Array destructuring such as `const [value, setStore] = useStorage(item)` is unaffected; only code that annotated the result with an explicit two-element tuple type needs to widen the annotation.
34
+
35
+ ## 0.5.9 - 2026-06-11
36
+
37
+ ### Fixed
38
+
39
+ - Added a package-owned Android manifest initializer so storage setup no longer requires generated `MainApplication` edits in Expo or bare React Native apps.
40
+ - Tied the Expo config plugin run-once metadata to the package version so updated package plugin behavior is reapplied correctly after package upgrades.
41
+
42
+ ### Changed
43
+
44
+ - Included `CHANGELOG.md` in the packed package docs.
45
+
46
+ ## 0.5.8 - 2026-06-11
47
+
48
+ ### Changed
49
+
50
+ - Refactor native and web entrypoints to share the same storage core for item, batch, transaction, migration, metrics, import/export, and event behavior.
51
+ - Strengthen TypeScript checks with stricter compiler options so missing returns, switch fallthrough, and unchecked optional shapes are caught during package validation.
52
+
53
+ ### Fixed
54
+
55
+ - Regenerate Nitrogen output and package build artifacts before pack-content audits so clean release and CI environments validate the actual published tarball.
56
+
57
+ ## 0.5.7 - 2026-06-10
58
+
59
+ ### Added
60
+
61
+ - Add C++ sanitizer release gates for AddressSanitizer, ThreadSanitizer, and UndefinedBehaviorSanitizer so native storage regressions can be isolated before publishing.
62
+ - Add C++ stress coverage for listener unsubscribe behavior, hydrated batch key indexes, and concurrent Memory scope access.
63
+
64
+ ### Changed
65
+
66
+ - Speed up iOS Secure batch operations by reusing the resolved Keychain access group and access-control level across each batch instead of re-reading configuration per key.
67
+ - Refactor iOS Secure set/get/delete helpers so single-item and batch paths share Keychain status handling and cache updates.
68
+ - Strengthen TypeScript inference parity on web by exporting `StorageSetter` and preserving tuple value types from `getBatch()`.
69
+
70
+ ### Fixed
71
+
72
+ - Keep native and web public TypeScript entrypoints aligned so IDEs infer storage setters and batch tuple results consistently across React Native and web imports.
73
+ - Keep the README, issue template, package metadata, and release notes aligned with the current `0.5.7` package surface.
74
+
75
+ ## 0.5.6 - 2026-05-22
76
+
77
+ ### Added
78
+
79
+ - Update the package baseline to Expo SDK 56, React Native 0.85.3, React 19.2.3, TypeScript 6.0.3, and Nitro Modules 0.35.7.
80
+ - Add secure export guardrails: `storage.export(StorageScope.Secure)` now requires an explicit `{ includeSecureValues: true }` opt-in, with `storage.exportSecureUnsafe()` available for short-lived secure migration flows.
81
+ - Add secure event observer redaction options so `storage.setEventObserver()` redacts Secure values by default and requires explicit opt-in for raw Secure event values.
82
+ - Add Expo plugin Android backup rules that exclude Nitro Storage secure preference files from cloud backup and device transfer.
83
+ - Add public web backend contract exports for `WebDiskStorageBackend` and `WebSecureStorageBackend` from the native and web entrypoints.
84
+
85
+ ### Changed
86
+
87
+ - Close replaced web storage backends so IndexedDB-backed `BroadcastChannel` and database handles do not leak after backend swaps.
88
+ - Update README and package docs for the current secure export, event observer, Expo backup, web backend, and TypeScript usage surface.
89
+ - Preserve tuple value types in `getBatch()` so IDEs infer each returned value from its matching `StorageItem`.
90
+
91
+ ### Fixed
92
+
93
+ - Avoid Metro private `metro-config/src/defaults/exclusionList` imports and exclude generated Android `.cxx` directories from Metro and Watchman scans.
94
+ - Remove package-owned Android native log spam for expected unavailable biometric storage paths.
95
+ - Modernize Android Gradle assignment syntax to avoid package-owned Gradle warnings.
96
+
97
+ ## 0.5.4 - 2026-05-13
98
+
99
+ ### Fixed
100
+
101
+ - Align web secure runtime validation with native for access-control and biometric levels.
102
+ - Preserve secure biometric and access-control item semantics during transaction rollback.
103
+ - Keep web raw batch writes from indexing keys whose values were not written.
104
+ - Reject fractional C++ secure access-control and biometric levels before casting them for native adapters.
105
+ - Publish GitHub Releases to npm through a Trusted Publishing/OIDC workflow.
106
+ - Resolve the package build's TypeScript binary lookup warning during release checks.
107
+
108
+ ## 0.5.2 - 2026-04-27
109
+
110
+ ### Fixed
111
+
112
+ - Make `createIndexedDBBackend().flush()` reject queued IndexedDB write failures after surfacing them through `onError`.
113
+ - Stabilize the release benchmark gate by sampling each benchmark three times while keeping the same regression thresholds.
114
+ - Correct package content check commands in the release documentation for current Bun.
115
+
116
+ ## 0.5.1 - 2026-04-24
117
+
118
+ ### Added
119
+
120
+ - Add `storage.export(scope)` for raw string snapshots that can be restored with `storage.import(data, scope)`.
121
+ - Add event subscriptions with `storage.subscribe`, `storage.subscribeKey`, `storage.subscribePrefix`, and `storage.subscribeNamespace`.
122
+ - Add `StorageItem#subscribeSelector()` for selector-based subscriptions with equality checks.
123
+ - Add `storage.setEventObserver()` for devtools and storage event logging integrations.
124
+ - Add enforced JS/TS and C++ coverage gates for the package release path.
125
+
126
+ ### Changed
127
+
128
+ - Improve Memory namespace clear notification fan-out so subscribers under the cleared namespace are notified consistently.
129
+ - Improve web key-index fast paths when the active backend exposes indexed key operations.
130
+ - Emit batch change envelopes for raw import/export-adjacent workflows and batch writes/removes.
131
+ - Document raw import/export workflows and warn that Secure exports expose secret values.
132
+ - Refactor the publish script to validate release docs, report check timings, support coverage gates, and avoid redundant pack dry-runs.
133
+
134
+ ## 0.5.0 - 2026-04-18
135
+
136
+ ### Added
137
+
138
+ - Add secure-storage capability metadata with `storage.getSecurityCapabilities()`.
139
+ - Add metadata-only secure key inspection with `storage.getSecureMetadata(key)` and `storage.getAllSecureMetadata()`.
140
+ - Add public `SecurityCapabilities` and `SecureStorageMetadata` types.
141
+ - Add security policy and focused docs for secure storage, React hooks, and MMKV migration.
142
+
143
+ ### Changed
144
+
145
+ - Refresh README positioning, badges, platform support, security model, storage-library comparison guidance, and benchmark guidance for npm/GitHub discoverability.
146
+ - Tighten README decisioning with an at-a-glance API map, Expo plugin options, bare Android setup, migration paths, and a release checklist.
147
+ - Split detailed usage material into focused docs for API reference, React hooks, secure storage, web backends, batch/transaction/migration workflows, recipes, MMKV migration, and benchmarks.
148
+ - Expand npm package description and keywords around React Native secure storage, biometric storage, Keychain, Android Keystore, Nitro Modules, MMKV migration, Expo SecureStore, Zustand/Jotai, and IndexedDB.
149
+ - Harden publish dry-runs, package docs syncing, and npm pack content validation.
150
+
151
+ ## 0.4.5 - 2026-04-14
152
+
153
+ ### Added
154
+
155
+ - Add configurable web Disk backend hooks: `setWebDiskStorageBackend()`, `getWebDiskStorageBackend()`, and `flushWebStorageBackends()`.
156
+ - Extend the web backend contract with optional batch, sizing, subscription, and flush hooks for higher-performance custom backends.
157
+ - Add IndexedDB backend support for `getMany`, `setMany`, `removeMany`, `size`, `flush`, and `BroadcastChannel`-based cross-tab sync.
158
+ - Expand regression coverage for web backend overrides, backend subscription-driven cache invalidation, backend flush hooks, IndexedDB broadcast sync, and IndexedDB error surfacing.
159
+ - Add Disk write buffering APIs: `coalesceDiskWrites`, `storage.setDiskWritesAsync()`, `storage.flushDiskWrites()`, and `storage.getCapabilities()`.
160
+ - Add structured storage error classification via `getStorageErrorCode()` while keeping `isKeychainLockedError()` as the convenience helper, and tag native bridge errors with stable `[nitro-error:<code>]` markers.
161
+
162
+ ### Changed
163
+
164
+ - Upgrade to **Nitro Modules 0.35.4** and regenerate bindings against the latest stable Nitro 0.35 line.
165
+ - Migrate `nitro.json` to the current schema (`$schema`, `ignorePaths`, `gitAttributesGeneratedFlag`, and `autolinking.all.language = "c++"`).
166
+ - Raise the published `react-native-nitro-modules` requirement to `>= 0.35.4` so package metadata matches the tested Nitro baseline.
167
+ - Refresh root tooling to current patch releases for linting, testing, and workspace orchestration.
168
+ - Switch web operation timing to `performance.now()` when available for tighter metrics on fast paths.
169
+
170
+ ## 0.4.2/0.4.3 - 2026-03-05
171
+
172
+ ### Fixed
173
+
174
+ - Fix crash on Android devices without biometric hardware — all biometric storage paths now catch initialization failures gracefully (non-biometric operations unaffected).
175
+ - Fix Android keystore corruption recovery incorrectly wiping data on a locked keystore — only `AEADBadTagException` now triggers wipe; all other init failures throw without touching stored data.
176
+ - Synchronize `AndroidStorageAdapter.invalidateSecureKeysCache()` under instance lock to close a race between concurrent reads and writes.
177
+ - Synchronize `setSecureBatch`/`deleteSecureBatch` under instance lock to prevent cache rebuild racing a mid-batch write.
178
+ - Propagate `SharedPreferences.commit()` failures out of `applySecureEditor` instead of swallowing them.
179
+ - Fix `IOSStorageAdapterCpp::clearDisk()` using `dictionaryRepresentation` (includes OS-injected keys) — switched to `persistentDomainForName:` scoped strictly to the app suite.
180
+ - Fix `clearSecure()`/`clearSecureBiometric()` clearing the in-memory key cache before confirming `SecItemDelete` succeeded — cache is now only updated after the deletion is confirmed.
181
+ - Fix potential unexpected biometric auth prompt in `getSecure()` — added `kSecUseAuthenticationUI = kSecUseAuthenticationUIFail` consistent with `hasSecure()`.
182
+ - Fix `setKeychainAccessGroup()` race where a concurrent `getAllKeysSecure()` could observe a stale cache between group update and cache invalidation — both are now updated atomically under both mutexes.
183
+ - Fix CFErrorRef leak in `SecAccessControlCreateWithFlags` error path.
184
+ - Fix `setSecureBiometricWithLevel()` incorrectly reporting "value restored" when backup restoration itself threw — now propagates the composite error.
185
+ - Mark `secureKeyCacheHydrated_` as `std::atomic<bool>` to satisfy the C++ memory model.
186
+ - Fix `HybridStorage::addOnChange()` unsubscribe lambda capturing `this` raw pointer — switched to `std::weak_ptr` capture to prevent use-after-free if `HybridStorage` is destroyed before the JS unsubscribe callback fires.
187
+ - Validate access control level in `setSecureAccessControl()` (must be 0–4) and biometric level in `setSecureBiometricWithLevel()` (must be 0–2) — invalid values now throw instead of being silently passed to the native adapter.
188
+ - Fix `clearSecureBiometric()` calling `onScopeClear` which unnecessarily evicted all secure keys from the index — now only marks the index stale for lazy re-hydration.
189
+ - Fix `fromJavaStringArray()` silently dropping null JNI array elements — null entries are now preserved as empty strings to maintain positional alignment.
190
+ - Extend `isKeychainLockedError()` to detect Android `KeyPermanentlyInvalidatedException` and `InvalidKeyException` in addition to existing iOS/Android patterns.
191
+ - Fix web `getAll()` performing O(n) individual reads — switched to `WebStorage.getBatch()`.
192
+ - Fix web `subscribe()` accumulating `window.addEventListener("storage", …)` calls — now reference-counted and removed when the last subscriber unsubscribes.
193
+ - Fix web `import()` for Secure scope skipping `flushSecureWrites()` and `setSecureAccessControl()` before writing.
194
+ - Expand ProGuard/R8 keep rules with explicit method-signature patterns so JNI-callable methods survive aggressive R8 shrinking in release builds.
195
+
196
+ ## 0.4.1 - 2026-03-04
197
+
198
+ ### Added
199
+
200
+ - Add `storage.import(data, scope)` to bulk-load a `Record<string, string>` of raw key/value pairs into any scope in one call. Memory imports are atomic (all keys visible simultaneously before any listener fires).
201
+ - Add `createIndexedDBBackend(dbName?, storeName?)` factory (exported from `react-native-nitro-storage/indexeddb-backend`) that wraps IndexedDB with a write-through in-memory cache, enabling persistent web Secure storage for large payloads without blocking the UI thread.
202
+
203
+ ### Fixed
204
+
205
+ - Fix TTL expiry notification: subscribers registered via `item.subscribe()` are now correctly notified when a value expires on `item.get()` — both on cache-hit expiry and on envelope-parse expiry. Previously the notification was only emitted by the native event bus, which is not triggered in write-through or coalesced paths.
206
+ - Fix `setBatch` Memory atomicity: all values in a Memory-scope batch are now written to the store before any listener is notified, eliminating partial-batch observation windows. Items with `validate` or `expiration` config fall back to per-item sets to preserve those semantics.
207
+
208
+ ### Changed
209
+
210
+ - Upgrade to **Nitro Modules 0.35.0** — regenerate nitrogen specs with the new `registerAllNatives()` JNI entry point, fixing the Kotlin `HybridObject` `jni::global_ref` memory leak (Nitro #1238).
211
+ - Update `cpp-adapter.cpp` to use `registerAllNatives()` instead of the deprecated `initialize(vm)` shim.
212
+ - Bump to **React 19.2.0** and **React Native 0.83.2** across workspace and example.
213
+ - Add `--provenance` flag to `npm publish` for npm supply-chain attestation.
214
+
215
+ ## 0.4.0 - 2026-02-25
216
+
217
+ ### Added
218
+
219
+ - Add prefix query APIs: `storage.getKeysByPrefix(prefix, scope)` and `storage.getByPrefix(prefix, scope)`.
220
+ - Add optimistic concurrency APIs on items: `item.getWithVersion()` and `item.setIfVersion(version, value)`.
221
+ - Add storage metrics APIs: `storage.setMetricsObserver`, `storage.getMetricsSnapshot`, and `storage.resetMetrics`.
222
+ - Add `biometricLevel` item/auth config and native bridge support for `setSecureBiometricWithLevel`.
223
+ - Add configurable web Secure backend hooks: `setWebSecureStorageBackend` and `getWebSecureStorageBackend`.
224
+ - Add native prefix key retrieval plumbing (`getKeysByPrefix`) across Nitro spec, C++ core/bindings, Android, and iOS.
225
+ - Add regression coverage for prefix APIs, versioned APIs, metrics APIs, secure coalescing with access control, cross-tab web updates, and transaction rollback batch behavior.
226
+
227
+ ### Changed
228
+
229
+ - Optimize non-memory transaction rollback paths to use batch native/web writes and removals.
230
+ - Improve batch read semantics by using per-item cache hits and returning each item's default when raw batch data is missing.
231
+ - Improve native/web secure write coalescing by preserving optional access control without violating strict optional typing.
232
+ - Keep iOS secure keychain cache/index behavior aligned with new prefix query and biometric-level paths.
233
+ - Expand README/API docs to cover the new public API surface with concrete TypeScript use-case snippets.
234
+
235
+ ## 0.3.2 - 2026-02-22
236
+
237
+ ### Added
238
+
239
+ - Add `storage.setSecureWritesAsync(enabled)` to toggle Android secure writes between synchronous `commit()` and asynchronous `apply()`.
240
+ - Add `storage.flushSecureWrites()` for deterministic flush control of coalesced secure writes.
241
+ - Add native `removeByPrefix(prefix, scope)` plumbing and route namespace clears through the native/web prefix path.
242
+ - Add dedicated C++ binding tests for `HybridStorage` behavior (`cpp/bindings/HybridStorageTest.cpp`), wired into `test:cpp`.
243
+ - Add type-level public API tests (`test:types`) and package content guard checks (`check:pack`).
244
+
245
+ ### Changed
246
+
247
+ - Skip unnecessary read path on direct `item.set(value)` writes (still reads for updater functions).
248
+ - Reuse TTL envelope parse results while entries remain unexpired to avoid repeated JSON parse/deserialization work.
249
+ - Group secure raw batch writes by per-item access control so secure batch paths stay fast even with mixed access-control settings.
250
+ - Optimize C++ batch listener dispatch by copying scoped listeners once per batch operation.
251
+ - Avoid duplicate secure biometric clearing calls by relying on secure clear paths that already include biometric cleanup.
252
+ - Optimize web secure/disk key bookkeeping with an indexed key cache (faster `size`, `getAllKeys`, and namespace clears without repeated `localStorage` scans).
253
+ - Improve iOS secure key union performance by deduplicating with an `unordered_set`.
254
+ - Extract shared React hooks into `src/storage-hooks.ts` to reduce native/web entrypoint duplication.
255
+ - Expand benchmark coverage to include Disk and Secure scope throughput checks and tighten regression thresholds.
256
+ - Expand README coverage so every public feature has a concrete TypeScript use-case example, including secure write flush, biometric/access-control usage, batch bootstrap, and storage utility workflows.
257
+
258
+ ## 0.3.1 - 2026-02-16
259
+
260
+ ### Changed
261
+
262
+ - Isolate web Secure scope keys under `__secure_` prefix while keeping biometric fallback under `__bio_`.
263
+ - Align `storage.clear(StorageScope.Secure)` with biometric cleanup semantics.
264
+ - Update README installation, enum docs, and quality command docs to match current APIs.
265
+
266
+ ### Fixed
267
+
268
+ - Fix web scope bleed where clearing Disk/Secure could wipe the other secure domain.
269
+ - Fix biometric listener updates by emitting change notifications for biometric set/delete/clear paths.
270
+ - Fix secure namespace cleanup by flushing pending secure writes before namespace removal.
271
+ - Fix secure access-control leakage by applying access control at write time and disabling coalesced raw batch path when access control is configured.
272
+ - Fix global `storage.setAccessControl(...)` handling so non-item raw secure writes keep the configured level instead of being forced back to default.
273
+ - Fix Android secure key enumeration to return deduplicated key sets when secure and biometric stores share key names.
274
+
275
+ ## 0.3.0 - 2026-02-15
276
+
277
+ ### Added
278
+
279
+ - Add `useStorageSelector(item, selector, isEqual?)` to reduce rerenders from unrelated object updates.
280
+ - Add opt-in `coalesceSecureWrites` and per-item `readCache` controls in `createStorageItem` config.
281
+
282
+ ### Changed
283
+
284
+ - Switch default serialization to a primitive fast path for primitives while preserving JSON compatibility for objects and legacy values.
285
+ - Replace broad listener fan-out with key-indexed registries and automatic pruning for memory/native/web paths.
286
+
287
+ ### Fixed
288
+
289
+ - Route native batch calls through true adapter-level batch APIs (HybridStorage + iOS/Android adapters) instead of per-key loops.
290
+ - Add read-through cache invalidation on scoped/key change events and native/web clear paths.
291
+
292
+ ## 0.2.1 - 2026-02-15
293
+
294
+ ### Added
295
+
296
+ - Add explicit package `exports` for ESM/CJS/react-native/web resolution.
297
+
298
+ ### Fixed
299
+
300
+ - Preserve validation and TTL semantics in batch APIs by falling back to per-item paths when needed.
301
+ - Preserve item-level semantics in transaction `setItem`/`removeItem` by using item methods directly.
302
+ - Decode native batch missing values correctly to avoid empty-string ambiguity on iOS/Android C++ bindings.
303
+ - Avoid duplicate observer updates on native/web `setBatch` paths.
304
+ - Scope iOS disk storage to a dedicated UserDefaults suite and avoid clearing unrelated app defaults.
305
+ - Use a package-specific Android master-key alias for encrypted storage initialization and recovery.
306
+ - Expo config plugin now preserves existing `NSFaceIDUsageDescription` values.
307
+ - Expo config plugin makes Android biometric permissions opt-in.
308
+
309
+ ### Changed
310
+
311
+ - Raise `react` peer dependency floor to `>=18.2.0`.
312
+
313
+ ## 0.2.0 - 2026-02-15
314
+
315
+ ### Added
316
+
317
+ - Export `migrateFromMMKV` from the package root entrypoint.
318
+ - Add dedicated web storage tests and include `index.web.ts` in coverage collection.
319
+ - Add `runTransaction(scope, fn)` with rollback on thrown errors.
320
+ - Add versioned migration APIs: `registerMigration` and `migrateToLatest`.
321
+ - Add schema-aware storage options: `validate` and `onValidationError`.
322
+ - Add per-item TTL support via `expiration.ttlMs`.
323
+
324
+ ### Fixed
325
+
326
+ - Validate batch operation scope to prevent mixed-scope usage.
327
+ - Avoid duplicate native remove calls in `removeBatch`.
328
+ - Clear cached item values on `delete()` to prevent stale reads (native and web).
329
+
330
+ ### Changed
331
+
332
+ - Standardize internal package scripts and README contributor commands to Bun/Bunx.
333
+ - Expand README with complete API behavior/throws documentation.
334
+ - Strengthen native and web test coverage for validation, TTL, migrations, and transactions.
335
+
336
+ ## 0.1.4 - 2026-02-09
337
+
338
+ ### Added
339
+
340
+ - Add `clearAll` event.
341
+
342
+ ### Fixed
343
+
344
+ - Fix Android behavior.
345
+
346
+ ### Changed
347
+
348
+ - Bump react-native-nitro-modules to the latest version and raise the peer dependency floor.
349
+
350
+ ## 0.1.3 - 2026-01-22
351
+
352
+ ### Fixed
353
+
354
+ - Prevent ProGuard from stripping the JNI class in release builds.
355
+
356
+ ## 0.1.2 - 2026-01-07
357
+
358
+ ### Added
359
+
360
+ - Finalize batch operations and clean up the implementation.
361
+ - Add missing batch coverage and exclude web from the coverage report.
362
+
363
+ ### Changed
364
+
365
+ - Point types to the correct path and simplify bob targets.
366
+
367
+ ## 0.1.1 - 2025-12-15
368
+
369
+ ### Added
370
+
371
+ - MMKV migration utility.
372
+ - Benchmark UI improvements.
373
+
374
+ ### Changed
375
+
376
+ - Update native build configs.
377
+ - Update README screenshots.
378
+ - Add tests for memory item deletion and MMKV migration, and simplify the README.
379
+
380
+ ## 0.1.0 - 2025-12-15
381
+
382
+ ### Added
383
+
384
+ - Initial public release from the private repository.
package/README.md CHANGED
@@ -27,6 +27,10 @@ pagination, conflict resolution, or remote synchronization.
27
27
  - [Expo Config](#expo-config)
28
28
  - [Quick Start](#quick-start)
29
29
  - [Typed Storage Items](#typed-storage-items)
30
+ - [Item Ergonomics](#item-ergonomics)
31
+ - [Set Items](#set-items)
32
+ - [Groups And Lifecycle](#groups-and-lifecycle)
33
+ - [Legacy Key Migration And Secure Resilience](#legacy-key-migration-and-secure-resilience)
30
34
  - [React Hooks](#react-hooks)
31
35
  - [Storage Scopes](#storage-scopes)
32
36
  - [Secure Storage](#secure-storage)
@@ -34,10 +38,12 @@ pagination, conflict resolution, or remote synchronization.
34
38
  - [Events And Observability](#events-and-observability)
35
39
  - [Migrations And Transactions](#migrations-and-transactions)
36
40
  - [Web Backends](#web-backends)
41
+ - [Testing](#testing)
37
42
  - [Platform Support](#platform-support)
38
43
  - [Documentation](#documentation)
39
44
  - [Troubleshooting](#troubleshooting)
40
45
  - [Development](#development)
46
+ - [License](#license)
41
47
 
42
48
  ## Install
43
49
 
@@ -92,9 +98,11 @@ Add the config plugin before prebuilding native iOS and Android projects:
92
98
  | `addBiometricPermissions` | `false` | Adds Android biometric and fingerprint permissions. |
93
99
  | `configureAndroidBackup` | `true` | Writes Android backup rules that exclude secure storage files. |
94
100
 
95
- The plugin also initializes the Android storage adapter in `MainApplication`.
96
- Set `configureAndroidBackup: false` only when your app maintains equivalent
97
- backup and device-transfer exclusions for Nitro Storage secure files.
101
+ Android adapter initialization is owned by the package through an Android
102
+ manifest initializer, so apps should not edit `MainApplication` to call
103
+ `AndroidStorageAdapter.init(this)`. Set `configureAndroidBackup: false` only
104
+ when your app maintains equivalent backup and device-transfer exclusions for
105
+ Nitro Storage secure files.
98
106
 
99
107
  ## Quick Start
100
108
 
@@ -151,9 +159,108 @@ const didWrite = preferencesItem.setIfVersion(snapshot.version, {
151
159
  });
152
160
  ```
153
161
 
154
- The package exports `StorageItem`, `StorageItemConfig`, `StorageSetter`,
155
- `VersionedValue`, `StorageBatchSetItem`, web backend types, event types, secure
156
- metadata types, and capability types for IDE-safe integrations.
162
+ The package ships its own TypeScript types, so editors and AI tools catch
163
+ mistakes before they reach the runtime. It exports `StorageItem`,
164
+ `StorageItemConfig`, `StorageSetter`, `StorageActions`, `VersionedValue`,
165
+ `StorageBatchSetItem`, `StorageClearOptions`, `StorageKeyRef`, `SetItemConfig`,
166
+ `SetStorageItem`, plus web backend, event, secure-metadata, and capability types.
167
+
168
+ ## Item Ergonomics
169
+
170
+ `merge`, `reset`, and `setOrDelete` cover the most common object-state edits
171
+ without re-reading or hand-writing compare-and-swap loops. Scoped factories
172
+ (`memoryItem`, `diskItem`, `secureItem`) drop the repeated `scope` field.
173
+
174
+ ```ts
175
+ import { diskItem, memoryItem } from "react-native-nitro-storage";
176
+
177
+ const config = diskItem<{ theme: "light" | "dark"; compact: boolean }>({
178
+ key: "config",
179
+ defaultValue: { theme: "light", compact: false },
180
+ });
181
+
182
+ config.merge({ compact: true }); // shallow object update
183
+ config.reset(); // back to the default value
184
+ const loginMethod = memoryItem<string | null>({
185
+ key: "loginMethod",
186
+ defaultValue: null,
187
+ });
188
+ loginMethod.setOrDelete(maybeMethod); // null/undefined deletes, value sets
189
+ ```
190
+
191
+ ## Set Items
192
+
193
+ `createSetItem()` models set-membership state (seen ids, dismissed prompts)
194
+ without hand-rolling `Record<string, true>` helpers. Adding an existing member
195
+ or deleting an absent one is a no-op, so subscribers do not re-render.
196
+
197
+ ```ts
198
+ import { createSetItem, StorageScope } from "react-native-nitro-storage";
199
+
200
+ const dismissedTips = createSetItem({
201
+ key: "dismissedTips",
202
+ scope: StorageScope.Disk,
203
+ });
204
+
205
+ dismissedTips.add("welcome");
206
+ dismissedTips.has("welcome"); // true
207
+ dismissedTips.toggle("welcome"); // false (removed)
208
+ dismissedTips.values(); // string[]
209
+ ```
210
+
211
+ ## Groups And Lifecycle
212
+
213
+ Tag items with a `group` to clear related state in one call, or keep specific
214
+ keys while wiping the rest of a scope. This replaces manual snapshot-and-restore
215
+ logout flows.
216
+
217
+ ```ts
218
+ import { secureItem, storage, StorageScope } from "react-native-nitro-storage";
219
+
220
+ const accessToken = secureItem<string>({
221
+ key: "accessToken",
222
+ defaultValue: "",
223
+ group: "session",
224
+ });
225
+
226
+ // Wipe everything tied to the session.
227
+ storage.clearGroup("session");
228
+
229
+ // Wipe Disk but keep a few opt-in preferences.
230
+ storage.clear(StorageScope.Disk, {
231
+ except: [apiEnvironmentItem, "onboardingComplete"],
232
+ });
233
+ ```
234
+
235
+ ## Legacy Key Migration And Secure Resilience
236
+
237
+ `renameFrom` migrates an old key to a new one on first read and deletes the
238
+ legacy entry. Secure items can fall back to the last cached value when the
239
+ keychain is locked instead of throwing.
240
+
241
+ ```ts
242
+ import {
243
+ secureItem,
244
+ createSecureAuthStorage,
245
+ } from "react-native-nitro-storage";
246
+
247
+ const accessToken = secureItem<string>({
248
+ key: "accessToken",
249
+ namespace: "auth",
250
+ defaultValue: "",
251
+ renameFrom: "authToken", // copied + cleaned up on first read
252
+ fallbackToCacheOnReadError: true,
253
+ onReadError: (error) => reportSecureReadError(error),
254
+ });
255
+
256
+ const auth = createSecureAuthStorage(
257
+ {
258
+ accessToken: { renameFrom: "authToken" },
259
+ refreshToken: { renameFrom: "refreshToken" },
260
+ },
261
+ { namespace: "auth", group: "session", fallbackToCacheOnReadError: true },
262
+ );
263
+ ```
157
264
 
158
265
  ## React Hooks
159
266
 
@@ -186,6 +293,24 @@ const [compactMode] = useStorageSelector(
186
293
  );
187
294
  ```
188
295
 
296
+ `useStorage` also returns a render-stable `actions` object as a third element,
297
+ and `useStorageValue` / `useStorageActions` split read and write concerns.
298
+
299
+ ```tsx
300
+ import {
301
+ useStorage,
302
+ useStorageActions,
303
+ useStorageValue,
304
+ } from "react-native-nitro-storage";
305
+
306
+ const [config, setConfig, actions] = useStorage(configItem);
307
+ actions.merge({ compact: true });
308
+ actions.reset();
309
+
310
+ const theme = useStorageValue(themeItem); // read-only, no setter
311
+ const tokenActions = useStorageActions(tokenItem); // { set, merge, reset, remove, setOrDelete }
312
+ ```
313
+
189
314
  ## Storage Scopes
190
315
 
191
316
  | Scope | Backing store | Use it for |
@@ -301,6 +426,21 @@ Secure event observer values are redacted by default. Pass
301
426
  `{ redactSecureValues: false }` only in trusted debug tooling where raw values
302
427
  are safe to inspect.
303
428
 
429
+ TTL expiry emits a dedicated `"expire"` change event. Use
430
+ `storage.subscribeExpired()` to react to keys that lapse on read.
431
+
432
+ ```ts
433
+ const unsubscribeExpired = storage.subscribeExpired(
434
+ StorageScope.Disk,
435
+ (event) => {
436
+ console.log("expired", event.key);
437
+ },
438
+ );
439
+ ```
440
+
441
+ `storage.findDuplicateKeys()` and `storage.getRegisteredKeys()` help audit
442
+ accidental `(scope, key)` collisions; call them once at startup in development.
443
+
304
444
  ## Migrations And Transactions
305
445
 
306
446
  ```ts
@@ -322,15 +462,16 @@ registerMigration(2, ({ getRaw, setRaw, removeRaw }) => {
322
462
 
323
463
  migrateToLatest(StorageScope.Disk);
324
464
 
325
- runTransaction(() => {
326
- themeItem.set("dark");
327
- localeItem.set("en-US");
465
+ runTransaction(StorageScope.Disk, (tx) => {
466
+ tx.setItem(themeItem, "dark");
467
+ tx.setItem(localeItem, "en-US");
328
468
  });
329
469
 
330
470
  migrateFromMMKV(mmkvInstance, themeItem);
331
471
  ```
332
472
 
333
- Transactions roll back local writes if the callback throws.
473
+ `runTransaction(scope, callback)` rolls back every write made through the `tx`
474
+ context if the callback throws.
334
475
 
335
476
  ## Web Backends
336
477
 
@@ -353,6 +494,31 @@ setWebSecureStorageBackend(backend);
353
494
  Browser storage cannot provide iOS Keychain or Android Keystore guarantees. Web
354
495
  Secure scope is only as strong as the backend you configure.
355
496
 
497
+ ## Testing
498
+
499
+ The `react-native-nitro-storage/testing` entrypoint is a faithful in-memory
500
+ implementation of the full public surface, so unit tests and Storybook run
501
+ without native modules. Mock the package with it, or use it directly.
502
+
503
+ ```ts
504
+ import {
505
+ createNitroStorageMock,
506
+ resetNitroStorageMock,
507
+ } from "react-native-nitro-storage/testing";
508
+
509
+ // Jest: swap the real module for the in-memory implementation.
510
+ jest.mock("react-native-nitro-storage", () =>
511
+ require("react-native-nitro-storage/testing"),
512
+ );
513
+
514
+ beforeEach(() => {
515
+ resetNitroStorageMock();
516
+ });
517
+
518
+ // Or build an isolated instance per test file.
519
+ const { storage, memoryItem } = createNitroStorageMock();
520
+ ```
521
+
356
522
  ## Platform Support
357
523
 
358
524
  | Platform | Status |
@@ -380,6 +546,8 @@ Secure scope is only as strong as the backend you configure.
380
546
 
381
547
  - **Expo Go error:** build a development client; Expo Go cannot load Nitro
382
548
  modules.
549
+ - **Android not initialized:** rebuild the native app after installing or
550
+ upgrading the package so the Android manifest initializer is merged.
383
551
  - **Secure values fail after Android restore:** keep `configureAndroidBackup:
384
552
  true` or provide equivalent backup exclusions.
385
553
  - **Biometric prompt does not appear:** set `biometric: true` on the item and
@@ -0,0 +1,9 @@
1
+ <manifest xmlns:android="http://schemas.android.com/apk/res/android">
2
+ <application>
3
+ <provider
4
+ android:name="com.nitrostorage.NitroStorageInitializer"
5
+ android:authorities="${applicationId}.nitrostorage-initializer"
6
+ android:exported="false"
7
+ android:initOrder="100" />
8
+ </application>
9
+ </manifest>