react-native-nitro-storage 0.8.0 → 0.10.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 (78) hide show
  1. package/CHANGELOG.md +107 -25
  2. package/README.md +140 -27
  3. package/android/src/main/cpp/AndroidStorageAdapterCpp.cpp +5 -2
  4. package/android/src/main/java/com/nitrostorage/AndroidStorageAdapter.kt +240 -34
  5. package/cpp/bindings/HybridStorage.cpp +109 -293
  6. package/cpp/bindings/HybridStorage.hpp +17 -10
  7. package/docs/api-reference.md +75 -19
  8. package/docs/benchmarks.md +6 -1
  9. package/docs/keychain-lifecycle-testing.md +36 -0
  10. package/docs/recipes.md +1 -2
  11. package/docs/secure-storage.md +45 -9
  12. package/ios/IOSStorageAdapterCpp.mm +391 -175
  13. package/lib/commonjs/capabilities.js +3 -1
  14. package/lib/commonjs/capabilities.js.map +1 -1
  15. package/lib/commonjs/core/durability.js +110 -43
  16. package/lib/commonjs/core/durability.js.map +1 -1
  17. package/lib/commonjs/index.js +15 -5
  18. package/lib/commonjs/index.js.map +1 -1
  19. package/lib/commonjs/index.web.js +219 -97
  20. package/lib/commonjs/index.web.js.map +1 -1
  21. package/lib/commonjs/internal.js +3 -11
  22. package/lib/commonjs/internal.js.map +1 -1
  23. package/lib/commonjs/shared.js +62 -2
  24. package/lib/commonjs/shared.js.map +1 -1
  25. package/lib/commonjs/storage-core.js +859 -145
  26. package/lib/commonjs/storage-core.js.map +1 -1
  27. package/lib/commonjs/storage-runtime.js +5 -1
  28. package/lib/commonjs/storage-runtime.js.map +1 -1
  29. package/lib/commonjs/testing.js +13 -0
  30. package/lib/commonjs/testing.js.map +1 -1
  31. package/lib/module/capabilities.js +2 -1
  32. package/lib/module/capabilities.js.map +1 -1
  33. package/lib/module/core/durability.js +110 -43
  34. package/lib/module/core/durability.js.map +1 -1
  35. package/lib/module/index.js +12 -8
  36. package/lib/module/index.js.map +1 -1
  37. package/lib/module/index.web.js +215 -99
  38. package/lib/module/index.web.js.map +1 -1
  39. package/lib/module/internal.js +2 -9
  40. package/lib/module/internal.js.map +1 -1
  41. package/lib/module/shared.js +60 -2
  42. package/lib/module/shared.js.map +1 -1
  43. package/lib/module/storage-core.js +860 -146
  44. package/lib/module/storage-core.js.map +1 -1
  45. package/lib/module/storage-runtime.js +4 -1
  46. package/lib/module/storage-runtime.js.map +1 -1
  47. package/lib/module/testing.js +8 -1
  48. package/lib/module/testing.js.map +1 -1
  49. package/lib/typescript/capabilities.d.ts +2 -1
  50. package/lib/typescript/capabilities.d.ts.map +1 -1
  51. package/lib/typescript/core/durability.d.ts +7 -2
  52. package/lib/typescript/core/durability.d.ts.map +1 -1
  53. package/lib/typescript/index.d.ts +2 -2
  54. package/lib/typescript/index.d.ts.map +1 -1
  55. package/lib/typescript/index.web.d.ts +3 -29
  56. package/lib/typescript/index.web.d.ts.map +1 -1
  57. package/lib/typescript/internal.d.ts +0 -2
  58. package/lib/typescript/internal.d.ts.map +1 -1
  59. package/lib/typescript/shared.d.ts +19 -0
  60. package/lib/typescript/shared.d.ts.map +1 -1
  61. package/lib/typescript/storage-core.d.ts +12 -3
  62. package/lib/typescript/storage-core.d.ts.map +1 -1
  63. package/lib/typescript/storage-runtime.d.ts +2 -1
  64. package/lib/typescript/storage-runtime.d.ts.map +1 -1
  65. package/lib/typescript/testing.d.ts +1 -1
  66. package/lib/typescript/testing.d.ts.map +1 -1
  67. package/nitrogen/generated/shared/c++/HybridStorageSpec.hpp +1 -1
  68. package/package.json +3 -3
  69. package/react-native-nitro-storage.podspec +3 -1
  70. package/src/capabilities.ts +3 -1
  71. package/src/core/durability.ts +139 -39
  72. package/src/index.ts +20 -10
  73. package/src/index.web.ts +328 -166
  74. package/src/internal.ts +0 -14
  75. package/src/shared.ts +85 -0
  76. package/src/storage-core.ts +1165 -199
  77. package/src/storage-runtime.ts +6 -0
  78. package/src/testing.ts +8 -1
package/CHANGELOG.md CHANGED
@@ -2,9 +2,91 @@
2
2
 
3
3
  All notable changes to this project are documented in this file.
4
4
 
5
- The format follows Keep a Changelog and the project adheres to SemVer.
5
+ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
6
+ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
7
+ Breaking changes are always listed first in each release section.
6
8
 
7
- ## 0.8.0 - 2026-08-12
9
+ ## [0.10.0] - 2026-08-25
10
+
11
+ ### Breaking changes
12
+
13
+ - None when upgrading from `0.9.x`. Direct upgrades from `0.8.x` or earlier
14
+ still require Nitro Modules `0.37.x` and a native rebuild as described in
15
+ the `0.9.0` entry. This release restores the previous set-item type and
16
+ secure-write defaults for existing consumers.
17
+
18
+ ### Added
19
+
20
+ - Added `isStorageError(error, code)` to select recovery behavior from an exact
21
+ stable storage error code on native and web.
22
+
23
+ ### Changed
24
+
25
+ - Restored `SetStorageItem.get()` and its `item` property to the original
26
+ `Record<string, true>` compatibility shape. Added `getTyped()` for new code
27
+ that wants `Partial<Record<TMember, true>>` without forcing a type migration.
28
+ - Restored synchronous Android Secure writes by default. Asynchronous
29
+ `apply()` writes remain available through the explicit
30
+ `storage.setSecureWritesAsync(true)` opt-in and can be drained with
31
+ `storage.flushSecureWrites()`.
32
+ - Enabled raw read-cache lookups now reuse cached missing values in single and
33
+ batch reads without per-item fallback calls.
34
+
35
+ ### Deprecated
36
+
37
+ - Deprecated `isKeychainLockedError()`. It remains backward compatible but
38
+ groups `keychain_locked`, `authentication_required`, and `key_invalidated`;
39
+ use `isStorageError()` when deciding whether to retry, authenticate, or
40
+ rebuild a credential.
41
+
42
+ ### Documentation
43
+
44
+ - Documented secure-storage recovery semantics and warned against cached
45
+ fallback for authentication tokens unless stale credentials are an explicit
46
+ application policy.
47
+ - Clarified isolated web benchmark limits and corrected capability/API examples.
48
+
49
+ ## [0.9.0] - 2026-08-20
50
+
51
+ ### Breaking changes
52
+
53
+ - `react-native-nitro-modules` now has a peer range of `>=0.37.0 <0.38.0`.
54
+ Upgrade Nitro Modules and rebuild the native app before using Nitro Storage
55
+ 0.9.0; the previous 0.36.x range is not supported.
56
+ - `SetStorageItem.get()` now returns `Partial<Record<TMember, true>>`. Use
57
+ `has()` for membership checks or handle an indexed value as `true | undefined`
58
+ instead of assuming every member exists.
59
+ - Android Secure writes now default to asynchronous `apply()`. Call
60
+ `storage.setSecureWritesAsync(false)` when existing code depends on
61
+ synchronous `commit()` durability, or call `storage.flushSecureWrites()` at
62
+ deterministic persistence boundaries.
63
+
64
+ ### Changed
65
+
66
+ - Regenerated the shipped Nitro bindings with Nitro Modules and Nitrogen 0.37.0
67
+ while preserving synchronous JSI storage behavior across native platforms.
68
+ - Native batch reads now preserve missing entries as `undefined`, matching the
69
+ TypeScript contract and allowing stored values that match the old internal
70
+ sentinel string.
71
+ - Secure write flushes now retain failed and unattempted last-write-wins entries
72
+ for retry instead of silently dropping them.
73
+ - iOS legacy Disk migration is conservative and retryable. A valid registry is
74
+ copied into the suite domain and each `standardUserDefaults` source is
75
+ removed only after target persistence is verified; malformed registries,
76
+ fallback or same-domain stores, conflicts, and failed persistence leave the
77
+ source and registry available for recovery.
78
+
79
+ ### Fixed
80
+
81
+ - `storage.clearBiometric()` flushes pending Secure writes, clears the JS raw
82
+ cache before listener notification, uses a durable Android biometric clear,
83
+ surfaces failures, and emits the same Secure-scope `clear` event as a Secure
84
+ clear after success.
85
+ - Android biometric corruption is checked and recovered at preference-store
86
+ initialization instead of probing the full encrypted store on each
87
+ existence or deletion hot path.
88
+
89
+ ## [0.8.0] - 2026-08-12
8
90
 
9
91
  ### Breaking changes
10
92
 
@@ -36,7 +118,7 @@ The format follows Keep a Changelog and the project adheres to SemVer.
36
118
  - The IndexedDB backend reports affected keys when `flush()` fails and starts a best-effort flush on `pagehide` and hidden visibility changes.
37
119
  - `setIfVersion()` is documented as optimistic (no backend-level atomicity); CAS guarantees are covered by race tests.
38
120
 
39
- ## 0.7.0 - 2026-07-30
121
+ ## [0.7.0] - 2026-07-30
40
122
 
41
123
  ### Changes
42
124
 
@@ -47,7 +129,7 @@ The format follows Keep a Changelog and the project adheres to SemVer.
47
129
  - Enforce Android biometric policy levels with distinct Keystore keys, propagate locked or invalidated biometric failures, and keep secure preference files excluded from backup.
48
130
  - Preflight biometric store access before aggregate secure mutations and surface native commit or corruption-recovery failures.
49
131
 
50
- ## 0.6.0 - 2026-06-15
132
+ ## [0.6.0] - 2026-06-15
51
133
 
52
134
  ### Added
53
135
 
@@ -75,7 +157,7 @@ type changes can affect advanced consumers:
75
157
  - `StorageChangeOperation` gained the `"expire"` and `"clearGroup"` members. Exhaustive `switch` statements over a change event's `operation` need cases for the new members.
76
158
  - `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.
77
159
 
78
- ## 0.5.9 - 2026-06-11
160
+ ## [0.5.9] - 2026-06-11
79
161
 
80
162
  ### Fixed
81
163
 
@@ -86,7 +168,7 @@ type changes can affect advanced consumers:
86
168
 
87
169
  - Included `CHANGELOG.md` in the packed package docs.
88
170
 
89
- ## 0.5.8 - 2026-06-11
171
+ ## [0.5.8] - 2026-06-11
90
172
 
91
173
  ### Changed
92
174
 
@@ -97,7 +179,7 @@ type changes can affect advanced consumers:
97
179
 
98
180
  - Regenerate Nitrogen output and package build artifacts before pack-content audits so clean release and CI environments validate the actual published tarball.
99
181
 
100
- ## 0.5.7 - 2026-06-10
182
+ ## [0.5.7] - 2026-06-10
101
183
 
102
184
  ### Added
103
185
 
@@ -115,7 +197,7 @@ type changes can affect advanced consumers:
115
197
  - Keep native and web public TypeScript entrypoints aligned so IDEs infer storage setters and batch tuple results consistently across React Native and web imports.
116
198
  - Keep the README, issue template, package metadata, and release notes aligned with the current `0.5.7` package surface.
117
199
 
118
- ## 0.5.6 - 2026-05-22
200
+ ## [0.5.6] - 2026-05-22
119
201
 
120
202
  ### Added
121
203
 
@@ -137,7 +219,7 @@ type changes can affect advanced consumers:
137
219
  - Remove package-owned Android native log spam for expected unavailable biometric storage paths.
138
220
  - Modernize Android Gradle assignment syntax to avoid package-owned Gradle warnings.
139
221
 
140
- ## 0.5.4 - 2026-05-13
222
+ ## [0.5.4] - 2026-05-13
141
223
 
142
224
  ### Fixed
143
225
 
@@ -148,7 +230,7 @@ type changes can affect advanced consumers:
148
230
  - Publish GitHub Releases to npm through a Trusted Publishing/OIDC workflow.
149
231
  - Resolve the package build's TypeScript binary lookup warning during release checks.
150
232
 
151
- ## 0.5.2 - 2026-04-27
233
+ ## [0.5.2] - 2026-04-27
152
234
 
153
235
  ### Fixed
154
236
 
@@ -156,7 +238,7 @@ type changes can affect advanced consumers:
156
238
  - Stabilize the release benchmark gate by sampling each benchmark three times while keeping the same regression thresholds.
157
239
  - Correct package content check commands in the release documentation for current Bun.
158
240
 
159
- ## 0.5.1 - 2026-04-24
241
+ ## [0.5.1] - 2026-04-24
160
242
 
161
243
  ### Added
162
244
 
@@ -174,7 +256,7 @@ type changes can affect advanced consumers:
174
256
  - Document raw import/export workflows and warn that Secure exports expose secret values.
175
257
  - Refactor the publish script to validate release docs, report check timings, support coverage gates, and avoid redundant pack dry-runs.
176
258
 
177
- ## 0.5.0 - 2026-04-18
259
+ ## [0.5.0] - 2026-04-18
178
260
 
179
261
  ### Added
180
262
 
@@ -191,7 +273,7 @@ type changes can affect advanced consumers:
191
273
  - 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.
192
274
  - Harden publish dry-runs, package docs syncing, and npm pack content validation.
193
275
 
194
- ## 0.4.5 - 2026-04-14
276
+ ## [0.4.5] - 2026-04-14
195
277
 
196
278
  ### Added
197
279
 
@@ -236,7 +318,7 @@ type changes can affect advanced consumers:
236
318
  - Fix web `import()` for Secure scope skipping `flushSecureWrites()` and `setSecureAccessControl()` before writing.
237
319
  - Expand ProGuard/R8 keep rules with explicit method-signature patterns so JNI-callable methods survive aggressive R8 shrinking in release builds.
238
320
 
239
- ## 0.4.1 - 2026-03-04
321
+ ## [0.4.1] - 2026-03-04
240
322
 
241
323
  ### Added
242
324
 
@@ -255,7 +337,7 @@ type changes can affect advanced consumers:
255
337
  - Bump to **React 19.2.0** and **React Native 0.83.2** across workspace and example.
256
338
  - Add `--provenance` flag to `npm publish` for npm supply-chain attestation.
257
339
 
258
- ## 0.4.0 - 2026-02-25
340
+ ## [0.4.0] - 2026-02-25
259
341
 
260
342
  ### Added
261
343
 
@@ -275,7 +357,7 @@ type changes can affect advanced consumers:
275
357
  - Keep iOS secure keychain cache/index behavior aligned with new prefix query and biometric-level paths.
276
358
  - Expand README/API docs to cover the new public API surface with concrete TypeScript use-case snippets.
277
359
 
278
- ## 0.3.2 - 2026-02-22
360
+ ## [0.3.2] - 2026-02-22
279
361
 
280
362
  ### Added
281
363
 
@@ -298,7 +380,7 @@ type changes can affect advanced consumers:
298
380
  - Expand benchmark coverage to include Disk and Secure scope throughput checks and tighten regression thresholds.
299
381
  - 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.
300
382
 
301
- ## 0.3.1 - 2026-02-16
383
+ ## [0.3.1] - 2026-02-16
302
384
 
303
385
  ### Changed
304
386
 
@@ -315,7 +397,7 @@ type changes can affect advanced consumers:
315
397
  - Fix global `storage.setAccessControl(...)` handling so non-item raw secure writes keep the configured level instead of being forced back to default.
316
398
  - Fix Android secure key enumeration to return deduplicated key sets when secure and biometric stores share key names.
317
399
 
318
- ## 0.3.0 - 2026-02-15
400
+ ## [0.3.0] - 2026-02-15
319
401
 
320
402
  ### Added
321
403
 
@@ -332,7 +414,7 @@ type changes can affect advanced consumers:
332
414
  - Route native batch calls through true adapter-level batch APIs (HybridStorage + iOS/Android adapters) instead of per-key loops.
333
415
  - Add read-through cache invalidation on scoped/key change events and native/web clear paths.
334
416
 
335
- ## 0.2.1 - 2026-02-15
417
+ ## [0.2.1] - 2026-02-15
336
418
 
337
419
  ### Added
338
420
 
@@ -353,7 +435,7 @@ type changes can affect advanced consumers:
353
435
 
354
436
  - Raise `react` peer dependency floor to `>=18.2.0`.
355
437
 
356
- ## 0.2.0 - 2026-02-15
438
+ ## [0.2.0] - 2026-02-15
357
439
 
358
440
  ### Added
359
441
 
@@ -376,7 +458,7 @@ type changes can affect advanced consumers:
376
458
  - Expand README with complete API behavior/throws documentation.
377
459
  - Strengthen native and web test coverage for validation, TTL, migrations, and transactions.
378
460
 
379
- ## 0.1.4 - 2026-02-09
461
+ ## [0.1.4] - 2026-02-09
380
462
 
381
463
  ### Added
382
464
 
@@ -390,13 +472,13 @@ type changes can affect advanced consumers:
390
472
 
391
473
  - Bump react-native-nitro-modules to the latest version and raise the peer dependency floor.
392
474
 
393
- ## 0.1.3 - 2026-01-22
475
+ ## [0.1.3] - 2026-01-22
394
476
 
395
477
  ### Fixed
396
478
 
397
479
  - Prevent ProGuard from stripping the JNI class in release builds.
398
480
 
399
- ## 0.1.2 - 2026-01-07
481
+ ## [0.1.2] - 2026-01-07
400
482
 
401
483
  ### Added
402
484
 
@@ -407,7 +489,7 @@ type changes can affect advanced consumers:
407
489
 
408
490
  - Point types to the correct path and simplify bob targets.
409
491
 
410
- ## 0.1.1 - 2025-12-15
492
+ ## [0.1.1] - 2025-12-15
411
493
 
412
494
  ### Added
413
495
 
@@ -420,7 +502,7 @@ type changes can affect advanced consumers:
420
502
  - Update README screenshots.
421
503
  - Add tests for memory item deletion and MMKV migration, and simplify the README.
422
504
 
423
- ## 0.1.0 - 2025-12-15
505
+ ## [0.1.0] - 2025-12-15
424
506
 
425
507
  ### Added
426
508
 
package/README.md CHANGED
@@ -4,9 +4,9 @@
4
4
  [![npm downloads](https://img.shields.io/npm/dm/react-native-nitro-storage?color=22c55e&label=downloads)](https://www.npmjs.com/package/react-native-nitro-storage)
5
5
  [![CI](https://github.com/JoaoPauloCMarra/react-native-nitro-storage/actions/workflows/ci.yml/badge.svg)](https://github.com/JoaoPauloCMarra/react-native-nitro-storage/actions/workflows/ci.yml)
6
6
  [![license](https://img.shields.io/npm/l/react-native-nitro-storage?color=007ec6)](https://github.com/JoaoPauloCMarra/react-native-nitro-storage/blob/main/LICENSE)
7
- [![React Native](https://img.shields.io/badge/react--native-%3E%3D0.75-61dafb)](https://reactnative.dev/docs/0.86/getting-started-without-a-framework)
8
- [![Expo](https://img.shields.io/badge/expo-SDK%2057-000020)](https://docs.expo.dev/versions/v57.0.0/)
9
- [![Nitro Modules](https://img.shields.io/badge/nitro--modules-%3E%3D0.36.5%20%3C0.37.0-black)](https://nitro.margelo.com/)
7
+ [![React Native](https://img.shields.io/badge/react--native-0.86.2-61dafb)](https://reactnative.dev/docs/0.86/getting-started-without-a-framework)
8
+ [![Expo](https://img.shields.io/badge/expo-SDK%2057%20%28RN%200.86.2%29-000020)](https://docs.expo.dev/versions/v57.0.0/)
9
+ [![Nitro Modules](https://img.shields.io/badge/nitro--modules-%3E%3D0.37.0%20%3C0.38.0-black)](https://nitro.margelo.com/)
10
10
  [![TypeScript](https://img.shields.io/badge/typescript-6.0-3178c6)](https://www.typescriptlang.org/)
11
11
 
12
12
  Synchronous Memory, Disk, and Secure storage for React Native, Expo development
@@ -24,8 +24,10 @@ pagination, conflict resolution, or remote synchronization.
24
24
  ## Contents
25
25
 
26
26
  - [Install](#install)
27
+ - [Requirements and compatibility](#requirements-and-compatibility)
27
28
  - [Expo Config](#expo-config)
28
29
  - [Quick Start](#quick-start)
30
+ - [Auth Tokens](#auth-tokens)
29
31
  - [Typed Storage Items](#typed-storage-items)
30
32
  - [Item Ergonomics](#item-ergonomics)
31
33
  - [Set Items](#set-items)
@@ -51,18 +53,41 @@ pagination, conflict resolution, or remote synchronization.
51
53
  bun add react-native-nitro-storage react-native-nitro-modules
52
54
  ```
53
55
 
56
+ ## Requirements and compatibility
57
+
54
58
  Peer dependencies:
55
59
 
56
60
  | Package | Version |
57
61
  | ---------------------------- | ------------------ |
58
62
  | `react` | `>=18.2.0` |
59
63
  | `react-native` | `>=0.75.0` |
60
- | `react-native-nitro-modules` | `>=0.36.5 <0.37.0` |
64
+ | `react-native-nitro-modules` | `>=0.37.0 <0.38.0` |
65
+
66
+ Nitro peer requirement: `react-native-nitro-modules >=0.37.0 <0.38.0`.
61
67
 
62
- Nitro peer requirement: `react-native-nitro-modules >=0.36.5 <0.37.0`.
68
+ The package gate uses React Native `0.86.2` and the Strict TypeScript API.
69
+ `check:ci` also compiles the public source against React Native `0.87.0`'s
70
+ Strict TypeScript API; this does not change the runtime baseline. The Expo
71
+ example uses Expo SDK `57.0.16`, React Native
72
+ `0.86.2`, React `19.2.3`, and Nitro Modules `0.37.0`, which is the React Native
73
+ version supported by that Expo SDK. Do not override Expo's React Native version.
63
74
 
64
- Validated example baseline: Expo SDK 57, React Native 0.86.2, React 19.2.3,
65
- and Nitro Modules 0.36.5.
75
+ When upgrading from 0.8.x or 0.9.x, upgrade Nitro Modules to the 0.37.x range
76
+ before installing this package, then rebuild the native app so the generated
77
+ Nitro bindings and native runtime use the same major-minor version:
78
+
79
+ ```sh
80
+ bun add react-native-nitro-modules@0.37.0 react-native-nitro-storage@0.10.0
81
+ bunx expo prebuild
82
+ ```
83
+
84
+ `SetStorageItem.get()` retains its original `Record<string, true>` compatibility
85
+ shape. At runtime, absent members are still absent, so use `set.has(member)` for
86
+ membership checks. New code that wants a precise member union can use
87
+ `set.getTyped()`, which returns `Partial<Record<TMember, true>>`.
88
+
89
+ Nitro Storage requires an Expo development build or a bare React Native app;
90
+ Expo Go and native Windows, macOS, and tvOS targets are not supported.
66
91
 
67
92
  For Expo development builds:
68
93
 
@@ -129,6 +154,45 @@ const theme = themeItem.get();
129
154
  const raw = storage.getString("settings:theme", StorageScope.Disk);
130
155
  ```
131
156
 
157
+ `storage.getString` / `setString` remain the raw API. Prefer typed items for
158
+ application state.
159
+
160
+ Native storage calls are synchronous JSI operations. Keep values and batches
161
+ small enough for the JavaScript event loop; native and configured web-backend
162
+ failures throw errors. Secure cache fallback is opt-in through
163
+ `fallbackToCacheOnReadError`.
164
+
165
+ ## Auth Tokens
166
+
167
+ Use `createSecureAuthStorage` for access and refresh tokens. `renameFrom`
168
+ copies a legacy key on first read and deletes it, so you do not need a custom
169
+ migration helper.
170
+
171
+ ```ts
172
+ import { createSecureAuthStorage } from "react-native-nitro-storage";
173
+
174
+ const auth = createSecureAuthStorage(
175
+ {
176
+ accessToken: { renameFrom: "authToken" },
177
+ refreshToken: { renameFrom: "refreshToken" },
178
+ },
179
+ { namespace: "auth" },
180
+ );
181
+
182
+ auth.accessToken.set("access-token");
183
+ const current = auth.accessToken.get();
184
+ auth.accessToken.subscribe(() => {});
185
+ ```
186
+
187
+ Keep `getString` facades only when the app owns a storage architecture
188
+ boundary. `createSecureAuthStorage` already namespaces keys, notifies
189
+ subscribers, and migrates legacy keys.
190
+
191
+ Do not enable `fallbackToCacheOnReadError` for access or refresh tokens unless
192
+ the application explicitly accepts stale or revoked credentials. Handle
193
+ temporary secure-storage errors and retry from application lifecycle state
194
+ instead.
195
+
132
196
  ## Typed Storage Items
133
197
 
134
198
  `createStorageItem<T>()` is the recommended API for application code. It keeps
@@ -194,8 +258,11 @@ loginMethod.setOrDelete(maybeMethod); // null/undefined deletes, value sets
194
258
  ## Set Items
195
259
 
196
260
  `createSetItem()` models set-membership state (seen ids, dismissed prompts)
197
- without hand-rolling `Record<string, true>` helpers. Adding an existing member
198
- or deleting an absent one is a no-op, so subscribers do not re-render.
261
+ without hand-rolling membership helpers. Its compatibility `get()` result is a
262
+ `Record<string, true>`; absent members are still not stored at runtime. Use
263
+ `getTyped()` when a precise `Partial<Record<TMember, true>>` result is useful.
264
+ Adding an existing member or deleting an absent one is a no-op, so subscribers
265
+ do not re-render.
199
266
 
200
267
  ```ts
201
268
  import { createSetItem, StorageScope } from "react-native-nitro-storage";
@@ -331,7 +398,7 @@ import {
331
398
  StorageScope,
332
399
  createSecureAuthStorage,
333
400
  createStorageItem,
334
- isKeychainLockedError,
401
+ isStorageError,
335
402
  storage,
336
403
  } from "react-native-nitro-storage";
337
404
 
@@ -362,7 +429,7 @@ const auth = createSecureAuthStorage({
362
429
  try {
363
430
  recoveryCode.get();
364
431
  } catch (error) {
365
- if (isKeychainLockedError(error)) {
432
+ if (isStorageError(error, "keychain_locked")) {
366
433
  storage.getSecurityCapabilities();
367
434
  }
368
435
  }
@@ -374,6 +441,15 @@ exporting secure values unless you are intentionally doing a short-lived
374
441
  in-memory migration. `storage.export(StorageScope.Secure)` throws unless you
375
442
  explicitly opt into `{ includeSecureValues: true }`.
376
443
 
444
+ Android secure writes default to synchronous `commit()` durability. Call
445
+ `storage.setSecureWritesAsync(true)` only when asynchronous `apply()` writes are
446
+ acceptable. After opting into async writes, call `storage.flushSecureWrites()`
447
+ before a deterministic persistence boundary. A failed secure flush throws and
448
+ keeps failed or unattempted queued writes available for retry.
449
+ `storage.clearBiometric()`
450
+ flushes pending Secure writes before clearing biometric entries and surfaces
451
+ native clear failures.
452
+
377
453
  On Android 11 and newer, `BiometricLevel.BiometryOnly` and
378
454
  `BiometricLevel.BiometryOrPasscode` use separate Keystore policies. Android 10
379
455
  and older support `BiometryOrPasscode`; `BiometryOnly` throws
@@ -381,15 +457,21 @@ and older support `BiometryOrPasscode`; `BiometryOnly` throws
381
457
  biometric-only distinction. Promoting a value to biometric storage removes the
382
458
  plain secure copy on every platform, so plain reads cannot return a stale
383
459
  value. Secure existence, discovery, and cleanup operations
384
- can also throw when a protected store is locked or its key is invalidated. Catch
385
- those failures and use `isKeychainLockedError()` when authentication-aware retry
386
- behavior is appropriate.
460
+ can also throw when a protected store is locked or its key is invalidated. Use
461
+ `isStorageError()` to choose the correct recovery path: retry
462
+ `keychain_locked` after unlock, request user interaction for
463
+ `authentication_required`, and rebuild the affected credential for
464
+ `key_invalidated`.
387
465
 
388
466
  ## Batch Operations
389
467
 
390
468
  `getBatch()` preserves tuple value types, so IDEs infer each result from the
391
469
  matching item. `setBatch()` validates every item/value pair independently,
392
470
  including heterogeneous batches.
471
+ Missing keys use each item's `defaultValue`; the native bridge preserves missing
472
+ entries as `undefined` while reading the batch. With `readCache: true`, item and
473
+ batch reads reuse raw cache entries, including cached missing values, until a
474
+ write, delete, clear, or external change invalidates the entry.
393
475
 
394
476
  ```ts
395
477
  import { getBatch, removeBatch, setBatch } from "react-native-nitro-storage";
@@ -506,15 +588,17 @@ import {
506
588
  } from "react-native-nitro-storage";
507
589
  import { createIndexedDBBackend } from "react-native-nitro-storage/indexeddb-backend";
508
590
 
509
- const backend = await createIndexedDBBackend({
510
- dbName: "app-storage",
511
- storeName: "kv",
512
- });
591
+ const backend = await createIndexedDBBackend("app-storage", "kv");
513
592
 
514
593
  setWebDiskStorageBackend(backend);
515
594
  setWebSecureStorageBackend(backend);
516
595
  ```
517
596
 
597
+ Web reads and mutations stay synchronous against the backend's in-memory
598
+ contract; use `flushWebStorageBackends()` for asynchronous persistence
599
+ boundaries. The native entry keeps the web backend setters, getters, and flush
600
+ function as typed no-ops for cross-platform code.
601
+
518
602
  Browser storage cannot provide iOS Keychain or Android Keystore guarantees. Web
519
603
  Secure scope is only as strong as the backend you configure.
520
604
 
@@ -543,14 +627,38 @@ beforeEach(() => {
543
627
  const { storage, memoryItem } = createNitroStorageMock();
544
628
  ```
545
629
 
630
+ ## API
631
+
632
+ The package exposes named `storage`, `createStorageItem`, the scoped item
633
+ factories, `createSetItem`, batch operations, migration and transaction helpers,
634
+ secure-auth storage, React hooks, and web backend utilities. Values are bound to
635
+ `Memory`, `Disk`, or `Secure` and support typed single-key operations, raw
636
+ inspection, events and observers, cache and write-flush controls, secure
637
+ metadata, transactional migrations with rename/rollback, and configurable web
638
+ backends. The full reference lives in
639
+ [docs/api-reference.md](docs/api-reference.md).
640
+
641
+ ## Error Contract
642
+
643
+ Native and web adapters tag classified failures with stable error codes. Use
644
+ `getStorageErrorCode(error)` or `isStorageError(error, code)` to branch on them:
645
+ `keychain_locked` reports a locked Keychain that a retry can recover after
646
+ authentication, secure-scope write or biometric failures carry their own
647
+ codes, and invalid inputs (bad scope, malformed keys, numeric guard
648
+ violations) are rejected before reaching native storage. Errors never swallow
649
+ the underlying cause silently: the original platform message is preserved on
650
+ the error for diagnostics.
651
+
546
652
  ## Platform Support
547
653
 
548
- | Platform | Status |
549
- | -------- | -------------------------------------------------- |
550
- | iOS | Memory, Disk, and Keychain-backed Secure storage. |
551
- | Android | Memory, Disk, and Keystore-backed Secure storage. |
552
- | Web | Memory plus configurable Disk and Secure backends. |
553
- | Expo | Development builds with the config plugin. |
654
+ | Platform | Status |
655
+ | ---------------------- | -------------------------------------------------- |
656
+ | iOS | Memory, Disk, and Keychain-backed Secure storage. |
657
+ | Android | Memory, Disk, and Keystore-backed Secure storage. |
658
+ | Web | Memory plus configurable Disk and Secure backends. |
659
+ | Expo development build | Supported with the config plugin. |
660
+ | Expo Go | Not supported for Nitro native modules. |
661
+ | Windows, macOS, tvOS | Not supported by this package. |
554
662
 
555
663
  ## Documentation
556
664
 
@@ -594,9 +702,14 @@ bun run example:android
594
702
  bun run example:ios
595
703
  ```
596
704
 
597
- Run native example builds before release when changing plugin, native, Nitro,
598
- secure storage, or packaging files. The package release path also validates
599
- package contents and dry-run publish behavior.
705
+ Run native example builds locally before release when changing plugin, native,
706
+ Nitro, secure storage, or packaging files. GitHub CI does not build the Android
707
+ or iOS example. The package release path also validates package contents and
708
+ dry-run publish behavior.
709
+
710
+ `bun run benchmark` measures only the built web entry with an isolated private
711
+ localStorage implementation; it is not a native Disk or Secure benchmark. See
712
+ [docs/benchmarks.md](docs/benchmarks.md) for sampling and interpretation limits.
600
713
 
601
714
  ## License
602
715
 
@@ -40,8 +40,11 @@ std::vector<std::string> fromJavaStringArray(alias_ref<JavaStringArray> values)
40
40
  result.reserve(size);
41
41
  for (jsize i = 0; i < size; ++i) {
42
42
  auto currentValue = values->getElement(i);
43
- // Preserve null as empty string to maintain index alignment with caller
44
- result.push_back(currentValue ? currentValue->toStdString() : std::string());
43
+ // Null entries are dropped so a missing key can never surface as the
44
+ // empty-string clear sentinel used by change listeners.
45
+ if (currentValue) {
46
+ result.push_back(currentValue->toStdString());
47
+ }
45
48
  }
46
49
  return result;
47
50
  }