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.
- package/CHANGELOG.md +384 -0
- package/README.md +178 -10
- package/android/src/main/AndroidManifest.xml +9 -0
- package/android/src/main/java/com/nitrostorage/NitroStorageInitializer.kt +38 -0
- package/app.plugin.js +3 -47
- package/cpp/bindings/HybridStorage.cpp +25 -9
- package/cpp/bindings/HybridStorage.hpp +5 -0
- package/lib/commonjs/index.js +19 -2
- package/lib/commonjs/index.js.map +1 -1
- package/lib/commonjs/index.web.js +19 -2
- package/lib/commonjs/index.web.js.map +1 -1
- package/lib/commonjs/shared.js.map +1 -1
- package/lib/commonjs/storage-core.js +306 -10
- package/lib/commonjs/storage-core.js.map +1 -1
- package/lib/commonjs/storage-events.js.map +1 -1
- package/lib/commonjs/storage-hooks.js +16 -1
- package/lib/commonjs/storage-hooks.js.map +1 -1
- package/lib/commonjs/testing.js +267 -0
- package/lib/commonjs/testing.js.map +1 -0
- package/lib/module/index.js +5 -1
- package/lib/module/index.js.map +1 -1
- package/lib/module/index.web.js +5 -1
- package/lib/module/index.web.js.map +1 -1
- package/lib/module/shared.js.map +1 -1
- package/lib/module/storage-core.js +307 -11
- package/lib/module/storage-core.js.map +1 -1
- package/lib/module/storage-events.js.map +1 -1
- package/lib/module/storage-hooks.js +15 -2
- package/lib/module/storage-hooks.js.map +1 -1
- package/lib/module/testing.js +188 -0
- package/lib/module/testing.js.map +1 -0
- package/lib/typescript/index.d.ts +22 -4
- package/lib/typescript/index.d.ts.map +1 -1
- package/lib/typescript/index.web.d.ts +22 -4
- package/lib/typescript/index.web.d.ts.map +1 -1
- package/lib/typescript/shared.d.ts +1 -0
- package/lib/typescript/shared.d.ts.map +1 -1
- package/lib/typescript/storage-core.d.ts +51 -2
- package/lib/typescript/storage-core.d.ts.map +1 -1
- package/lib/typescript/storage-events.d.ts +1 -1
- package/lib/typescript/storage-events.d.ts.map +1 -1
- package/lib/typescript/storage-hooks.d.ts +18 -1
- package/lib/typescript/storage-hooks.d.ts.map +1 -1
- package/lib/typescript/testing.d.ts +148 -0
- package/lib/typescript/testing.d.ts.map +1 -0
- package/package.json +8 -1
- package/src/index.ts +16 -2
- package/src/index.web.ts +16 -2
- package/src/shared.ts +1 -0
- package/src/storage-core.ts +446 -11
- package/src/storage-events.ts +2 -0
- package/src/storage-hooks.ts +42 -3
- 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
|
-
|
|
96
|
-
|
|
97
|
-
|
|
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
|
|
155
|
-
|
|
156
|
-
|
|
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
|
-
|
|
327
|
-
|
|
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
|
-
|
|
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>
|