react-native-nitro-storage 0.9.0 → 0.10.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (77) hide show
  1. package/CHANGELOG.md +101 -29
  2. package/README.md +83 -26
  3. package/android/build.gradle +3 -1
  4. package/android/src/main/cpp/AndroidStorageAdapterCpp.cpp +5 -2
  5. package/android/src/main/java/com/nitrostorage/AndroidStorageAdapter.kt +240 -34
  6. package/cpp/bindings/HybridStorage.cpp +106 -283
  7. package/cpp/bindings/HybridStorage.hpp +16 -9
  8. package/docs/api-reference.md +75 -19
  9. package/docs/benchmarks.md +6 -1
  10. package/docs/keychain-lifecycle-testing.md +36 -0
  11. package/docs/recipes.md +1 -2
  12. package/docs/secure-storage.md +45 -9
  13. package/ios/IOSStorageAdapterCpp.mm +391 -175
  14. package/lib/commonjs/capabilities.js +3 -1
  15. package/lib/commonjs/capabilities.js.map +1 -1
  16. package/lib/commonjs/core/durability.js +111 -45
  17. package/lib/commonjs/core/durability.js.map +1 -1
  18. package/lib/commonjs/index.js +15 -5
  19. package/lib/commonjs/index.js.map +1 -1
  20. package/lib/commonjs/index.web.js +219 -97
  21. package/lib/commonjs/index.web.js.map +1 -1
  22. package/lib/commonjs/internal.js +3 -11
  23. package/lib/commonjs/internal.js.map +1 -1
  24. package/lib/commonjs/shared.js +62 -2
  25. package/lib/commonjs/shared.js.map +1 -1
  26. package/lib/commonjs/storage-core.js +883 -163
  27. package/lib/commonjs/storage-core.js.map +1 -1
  28. package/lib/commonjs/storage-runtime.js +5 -1
  29. package/lib/commonjs/storage-runtime.js.map +1 -1
  30. package/lib/commonjs/testing.js +13 -0
  31. package/lib/commonjs/testing.js.map +1 -1
  32. package/lib/module/capabilities.js +2 -1
  33. package/lib/module/capabilities.js.map +1 -1
  34. package/lib/module/core/durability.js +111 -45
  35. package/lib/module/core/durability.js.map +1 -1
  36. package/lib/module/index.js +12 -8
  37. package/lib/module/index.js.map +1 -1
  38. package/lib/module/index.web.js +215 -99
  39. package/lib/module/index.web.js.map +1 -1
  40. package/lib/module/internal.js +2 -9
  41. package/lib/module/internal.js.map +1 -1
  42. package/lib/module/shared.js +60 -2
  43. package/lib/module/shared.js.map +1 -1
  44. package/lib/module/storage-core.js +884 -164
  45. package/lib/module/storage-core.js.map +1 -1
  46. package/lib/module/storage-runtime.js +4 -1
  47. package/lib/module/storage-runtime.js.map +1 -1
  48. package/lib/module/testing.js +8 -1
  49. package/lib/module/testing.js.map +1 -1
  50. package/lib/typescript/capabilities.d.ts +2 -1
  51. package/lib/typescript/capabilities.d.ts.map +1 -1
  52. package/lib/typescript/core/durability.d.ts +8 -4
  53. package/lib/typescript/core/durability.d.ts.map +1 -1
  54. package/lib/typescript/index.d.ts +2 -2
  55. package/lib/typescript/index.d.ts.map +1 -1
  56. package/lib/typescript/index.web.d.ts +3 -29
  57. package/lib/typescript/index.web.d.ts.map +1 -1
  58. package/lib/typescript/internal.d.ts +0 -2
  59. package/lib/typescript/internal.d.ts.map +1 -1
  60. package/lib/typescript/shared.d.ts +19 -0
  61. package/lib/typescript/shared.d.ts.map +1 -1
  62. package/lib/typescript/storage-core.d.ts +15 -6
  63. package/lib/typescript/storage-core.d.ts.map +1 -1
  64. package/lib/typescript/storage-runtime.d.ts +2 -1
  65. package/lib/typescript/storage-runtime.d.ts.map +1 -1
  66. package/lib/typescript/testing.d.ts +1 -1
  67. package/lib/typescript/testing.d.ts.map +1 -1
  68. package/package.json +2 -2
  69. package/src/capabilities.ts +3 -1
  70. package/src/core/durability.ts +140 -43
  71. package/src/index.ts +20 -10
  72. package/src/index.web.ts +328 -166
  73. package/src/internal.ts +0 -14
  74. package/src/shared.ts +85 -0
  75. package/src/storage-core.ts +1198 -232
  76. package/src/storage-runtime.ts +6 -0
  77. package/src/testing.ts +8 -1
package/CHANGELOG.md CHANGED
@@ -2,9 +2,63 @@
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.9.0 - 2026-08-20
9
+ ## [0.10.1] - 2026-09-09
10
+
11
+ ### Breaking changes
12
+
13
+ - None.
14
+
15
+ ### Fixed
16
+
17
+ - Apply the Kotlin Android plugin only when the Gradle Kotlin extension is
18
+ absent, so AGP 9 consumers that already ship built-in Kotlin can configure
19
+ the library.
20
+
21
+ ## [0.10.0] - 2026-08-25
22
+
23
+ ### Breaking changes
24
+
25
+ - None when upgrading from `0.9.x`. Direct upgrades from `0.8.x` or earlier
26
+ still require Nitro Modules `0.37.x` and a native rebuild as described in
27
+ the `0.9.0` entry. This release restores the previous set-item type and
28
+ secure-write defaults for existing consumers.
29
+
30
+ ### Added
31
+
32
+ - Added `isStorageError(error, code)` to select recovery behavior from an exact
33
+ stable storage error code on native and web.
34
+
35
+ ### Changed
36
+
37
+ - Restored `SetStorageItem.get()` and its `item` property to the original
38
+ `Record<string, true>` compatibility shape. Added `getTyped()` for new code
39
+ that wants `Partial<Record<TMember, true>>` without forcing a type migration.
40
+ - Restored synchronous Android Secure writes by default. Asynchronous
41
+ `apply()` writes remain available through the explicit
42
+ `storage.setSecureWritesAsync(true)` opt-in and can be drained with
43
+ `storage.flushSecureWrites()`.
44
+ - Enabled raw read-cache lookups now reuse cached missing values in single and
45
+ batch reads without per-item fallback calls.
46
+
47
+ ### Deprecated
48
+
49
+ - Deprecated `isKeychainLockedError()`. It remains backward compatible but
50
+ groups `keychain_locked`, `authentication_required`, and `key_invalidated`;
51
+ use `isStorageError()` when deciding whether to retry, authenticate, or
52
+ rebuild a credential.
53
+
54
+ ### Documentation
55
+
56
+ - Documented secure-storage recovery semantics and warned against cached
57
+ fallback for authentication tokens unless stale credentials are an explicit
58
+ application policy.
59
+ - Clarified isolated web benchmark limits and corrected capability/API examples.
60
+
61
+ ## [0.9.0] - 2026-08-20
8
62
 
9
63
  ### Breaking changes
10
64
 
@@ -14,6 +68,10 @@ The format follows Keep a Changelog and the project adheres to SemVer.
14
68
  - `SetStorageItem.get()` now returns `Partial<Record<TMember, true>>`. Use
15
69
  `has()` for membership checks or handle an indexed value as `true | undefined`
16
70
  instead of assuming every member exists.
71
+ - Android Secure writes now default to asynchronous `apply()`. Call
72
+ `storage.setSecureWritesAsync(false)` when existing code depends on
73
+ synchronous `commit()` durability, or call `storage.flushSecureWrites()` at
74
+ deterministic persistence boundaries.
17
75
 
18
76
  ### Changed
19
77
 
@@ -22,11 +80,25 @@ The format follows Keep a Changelog and the project adheres to SemVer.
22
80
  - Native batch reads now preserve missing entries as `undefined`, matching the
23
81
  TypeScript contract and allowing stored values that match the old internal
24
82
  sentinel string.
25
- - The standalone package development and type baseline is now React Native
26
- 0.87.0. The Expo SDK 57 example remains on its supported React Native 0.86.2
27
- baseline.
83
+ - Secure write flushes now retain failed and unattempted last-write-wins entries
84
+ for retry instead of silently dropping them.
85
+ - iOS legacy Disk migration is conservative and retryable. A valid registry is
86
+ copied into the suite domain and each `standardUserDefaults` source is
87
+ removed only after target persistence is verified; malformed registries,
88
+ fallback or same-domain stores, conflicts, and failed persistence leave the
89
+ source and registry available for recovery.
90
+
91
+ ### Fixed
92
+
93
+ - `storage.clearBiometric()` flushes pending Secure writes, clears the JS raw
94
+ cache before listener notification, uses a durable Android biometric clear,
95
+ surfaces failures, and emits the same Secure-scope `clear` event as a Secure
96
+ clear after success.
97
+ - Android biometric corruption is checked and recovered at preference-store
98
+ initialization instead of probing the full encrypted store on each
99
+ existence or deletion hot path.
28
100
 
29
- ## 0.8.0 - 2026-08-12
101
+ ## [0.8.0] - 2026-08-12
30
102
 
31
103
  ### Breaking changes
32
104
 
@@ -58,7 +130,7 @@ The format follows Keep a Changelog and the project adheres to SemVer.
58
130
  - The IndexedDB backend reports affected keys when `flush()` fails and starts a best-effort flush on `pagehide` and hidden visibility changes.
59
131
  - `setIfVersion()` is documented as optimistic (no backend-level atomicity); CAS guarantees are covered by race tests.
60
132
 
61
- ## 0.7.0 - 2026-07-30
133
+ ## [0.7.0] - 2026-07-30
62
134
 
63
135
  ### Changes
64
136
 
@@ -69,7 +141,7 @@ The format follows Keep a Changelog and the project adheres to SemVer.
69
141
  - Enforce Android biometric policy levels with distinct Keystore keys, propagate locked or invalidated biometric failures, and keep secure preference files excluded from backup.
70
142
  - Preflight biometric store access before aggregate secure mutations and surface native commit or corruption-recovery failures.
71
143
 
72
- ## 0.6.0 - 2026-06-15
144
+ ## [0.6.0] - 2026-06-15
73
145
 
74
146
  ### Added
75
147
 
@@ -97,7 +169,7 @@ type changes can affect advanced consumers:
97
169
  - `StorageChangeOperation` gained the `"expire"` and `"clearGroup"` members. Exhaustive `switch` statements over a change event's `operation` need cases for the new members.
98
170
  - `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.
99
171
 
100
- ## 0.5.9 - 2026-06-11
172
+ ## [0.5.9] - 2026-06-11
101
173
 
102
174
  ### Fixed
103
175
 
@@ -108,7 +180,7 @@ type changes can affect advanced consumers:
108
180
 
109
181
  - Included `CHANGELOG.md` in the packed package docs.
110
182
 
111
- ## 0.5.8 - 2026-06-11
183
+ ## [0.5.8] - 2026-06-11
112
184
 
113
185
  ### Changed
114
186
 
@@ -119,7 +191,7 @@ type changes can affect advanced consumers:
119
191
 
120
192
  - Regenerate Nitrogen output and package build artifacts before pack-content audits so clean release and CI environments validate the actual published tarball.
121
193
 
122
- ## 0.5.7 - 2026-06-10
194
+ ## [0.5.7] - 2026-06-10
123
195
 
124
196
  ### Added
125
197
 
@@ -137,7 +209,7 @@ type changes can affect advanced consumers:
137
209
  - Keep native and web public TypeScript entrypoints aligned so IDEs infer storage setters and batch tuple results consistently across React Native and web imports.
138
210
  - Keep the README, issue template, package metadata, and release notes aligned with the current `0.5.7` package surface.
139
211
 
140
- ## 0.5.6 - 2026-05-22
212
+ ## [0.5.6] - 2026-05-22
141
213
 
142
214
  ### Added
143
215
 
@@ -159,7 +231,7 @@ type changes can affect advanced consumers:
159
231
  - Remove package-owned Android native log spam for expected unavailable biometric storage paths.
160
232
  - Modernize Android Gradle assignment syntax to avoid package-owned Gradle warnings.
161
233
 
162
- ## 0.5.4 - 2026-05-13
234
+ ## [0.5.4] - 2026-05-13
163
235
 
164
236
  ### Fixed
165
237
 
@@ -170,7 +242,7 @@ type changes can affect advanced consumers:
170
242
  - Publish GitHub Releases to npm through a Trusted Publishing/OIDC workflow.
171
243
  - Resolve the package build's TypeScript binary lookup warning during release checks.
172
244
 
173
- ## 0.5.2 - 2026-04-27
245
+ ## [0.5.2] - 2026-04-27
174
246
 
175
247
  ### Fixed
176
248
 
@@ -178,7 +250,7 @@ type changes can affect advanced consumers:
178
250
  - Stabilize the release benchmark gate by sampling each benchmark three times while keeping the same regression thresholds.
179
251
  - Correct package content check commands in the release documentation for current Bun.
180
252
 
181
- ## 0.5.1 - 2026-04-24
253
+ ## [0.5.1] - 2026-04-24
182
254
 
183
255
  ### Added
184
256
 
@@ -196,7 +268,7 @@ type changes can affect advanced consumers:
196
268
  - Document raw import/export workflows and warn that Secure exports expose secret values.
197
269
  - Refactor the publish script to validate release docs, report check timings, support coverage gates, and avoid redundant pack dry-runs.
198
270
 
199
- ## 0.5.0 - 2026-04-18
271
+ ## [0.5.0] - 2026-04-18
200
272
 
201
273
  ### Added
202
274
 
@@ -213,7 +285,7 @@ type changes can affect advanced consumers:
213
285
  - 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.
214
286
  - Harden publish dry-runs, package docs syncing, and npm pack content validation.
215
287
 
216
- ## 0.4.5 - 2026-04-14
288
+ ## [0.4.5] - 2026-04-14
217
289
 
218
290
  ### Added
219
291
 
@@ -258,7 +330,7 @@ type changes can affect advanced consumers:
258
330
  - Fix web `import()` for Secure scope skipping `flushSecureWrites()` and `setSecureAccessControl()` before writing.
259
331
  - Expand ProGuard/R8 keep rules with explicit method-signature patterns so JNI-callable methods survive aggressive R8 shrinking in release builds.
260
332
 
261
- ## 0.4.1 - 2026-03-04
333
+ ## [0.4.1] - 2026-03-04
262
334
 
263
335
  ### Added
264
336
 
@@ -277,7 +349,7 @@ type changes can affect advanced consumers:
277
349
  - Bump to **React 19.2.0** and **React Native 0.83.2** across workspace and example.
278
350
  - Add `--provenance` flag to `npm publish` for npm supply-chain attestation.
279
351
 
280
- ## 0.4.0 - 2026-02-25
352
+ ## [0.4.0] - 2026-02-25
281
353
 
282
354
  ### Added
283
355
 
@@ -297,7 +369,7 @@ type changes can affect advanced consumers:
297
369
  - Keep iOS secure keychain cache/index behavior aligned with new prefix query and biometric-level paths.
298
370
  - Expand README/API docs to cover the new public API surface with concrete TypeScript use-case snippets.
299
371
 
300
- ## 0.3.2 - 2026-02-22
372
+ ## [0.3.2] - 2026-02-22
301
373
 
302
374
  ### Added
303
375
 
@@ -320,7 +392,7 @@ type changes can affect advanced consumers:
320
392
  - Expand benchmark coverage to include Disk and Secure scope throughput checks and tighten regression thresholds.
321
393
  - 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.
322
394
 
323
- ## 0.3.1 - 2026-02-16
395
+ ## [0.3.1] - 2026-02-16
324
396
 
325
397
  ### Changed
326
398
 
@@ -337,7 +409,7 @@ type changes can affect advanced consumers:
337
409
  - Fix global `storage.setAccessControl(...)` handling so non-item raw secure writes keep the configured level instead of being forced back to default.
338
410
  - Fix Android secure key enumeration to return deduplicated key sets when secure and biometric stores share key names.
339
411
 
340
- ## 0.3.0 - 2026-02-15
412
+ ## [0.3.0] - 2026-02-15
341
413
 
342
414
  ### Added
343
415
 
@@ -354,7 +426,7 @@ type changes can affect advanced consumers:
354
426
  - Route native batch calls through true adapter-level batch APIs (HybridStorage + iOS/Android adapters) instead of per-key loops.
355
427
  - Add read-through cache invalidation on scoped/key change events and native/web clear paths.
356
428
 
357
- ## 0.2.1 - 2026-02-15
429
+ ## [0.2.1] - 2026-02-15
358
430
 
359
431
  ### Added
360
432
 
@@ -375,7 +447,7 @@ type changes can affect advanced consumers:
375
447
 
376
448
  - Raise `react` peer dependency floor to `>=18.2.0`.
377
449
 
378
- ## 0.2.0 - 2026-02-15
450
+ ## [0.2.0] - 2026-02-15
379
451
 
380
452
  ### Added
381
453
 
@@ -398,7 +470,7 @@ type changes can affect advanced consumers:
398
470
  - Expand README with complete API behavior/throws documentation.
399
471
  - Strengthen native and web test coverage for validation, TTL, migrations, and transactions.
400
472
 
401
- ## 0.1.4 - 2026-02-09
473
+ ## [0.1.4] - 2026-02-09
402
474
 
403
475
  ### Added
404
476
 
@@ -412,13 +484,13 @@ type changes can affect advanced consumers:
412
484
 
413
485
  - Bump react-native-nitro-modules to the latest version and raise the peer dependency floor.
414
486
 
415
- ## 0.1.3 - 2026-01-22
487
+ ## [0.1.3] - 2026-01-22
416
488
 
417
489
  ### Fixed
418
490
 
419
491
  - Prevent ProGuard from stripping the JNI class in release builds.
420
492
 
421
- ## 0.1.2 - 2026-01-07
493
+ ## [0.1.2] - 2026-01-07
422
494
 
423
495
  ### Added
424
496
 
@@ -429,7 +501,7 @@ type changes can affect advanced consumers:
429
501
 
430
502
  - Point types to the correct path and simplify bob targets.
431
503
 
432
- ## 0.1.1 - 2025-12-15
504
+ ## [0.1.1] - 2025-12-15
433
505
 
434
506
  ### Added
435
507
 
@@ -442,7 +514,7 @@ type changes can affect advanced consumers:
442
514
  - Update README screenshots.
443
515
  - Add tests for memory item deletion and MMKV migration, and simplify the README.
444
516
 
445
- ## 0.1.0 - 2025-12-15
517
+ ## [0.1.0] - 2025-12-15
446
518
 
447
519
  ### Added
448
520
 
package/README.md CHANGED
@@ -4,8 +4,8 @@
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-0.87.0-61dafb)](https://reactnative.dev/docs/0.87/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/)
7
+ [![React Native](https://img.shields.io/badge/react--native-0.86.3-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.3%29-000020)](https://docs.expo.dev/versions/v57.0.0/)
9
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
 
@@ -24,6 +24,7 @@ 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)
29
30
  - [Auth Tokens](#auth-tokens)
@@ -52,6 +53,8 @@ pagination, conflict resolution, or remote synchronization.
52
53
  bun add react-native-nitro-storage react-native-nitro-modules
53
54
  ```
54
55
 
56
+ ## Requirements and compatibility
57
+
55
58
  Peer dependencies:
56
59
 
57
60
  | Package | Version |
@@ -62,24 +65,26 @@ Peer dependencies:
62
65
 
63
66
  Nitro peer requirement: `react-native-nitro-modules >=0.37.0 <0.38.0`.
64
67
 
65
- The standalone package gate uses React Native `0.87.0` and the Strict
66
- TypeScript API. The Expo example uses Expo SDK `57.0.15`, React Native
67
- `0.86.2`, React `19.2.3`, and Nitro Modules `0.37.0`, which is the React Native
68
+ The package gate uses React Native `0.86.3` 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.21`, React Native
72
+ `0.86.3`, React `19.2.3`, and Nitro Modules `0.37.1`, which is the React Native
68
73
  version supported by that Expo SDK. Do not override Expo's React Native version.
69
74
 
70
- When upgrading from 0.8.x, upgrade Nitro Modules to the 0.37.x range before
71
- installing this package, then rebuild the native app so the generated Nitro
72
- bindings and native runtime use the same major-minor version:
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:
73
78
 
74
79
  ```sh
75
- bun add react-native-nitro-modules@0.37.0 react-native-nitro-storage@0.9.0
80
+ bun add react-native-nitro-modules@0.37.1 react-native-nitro-storage@0.10.1
76
81
  bunx expo prebuild
77
82
  ```
78
83
 
79
- `SetStorageItem.get()` now returns a partial membership map because a member can
80
- be absent. Use `set.has(member)` for membership checks, or handle an indexed
81
- value as `true | undefined`; code that assigned the result to
82
- `Record<string, true>` must update its type.
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>>`.
83
88
 
84
89
  Nitro Storage requires an Expo development build or a bare React Native app;
85
90
  Expo Go and native Windows, macOS, and tvOS targets are not supported.
@@ -171,7 +176,7 @@ const auth = createSecureAuthStorage(
171
176
  accessToken: { renameFrom: "authToken" },
172
177
  refreshToken: { renameFrom: "refreshToken" },
173
178
  },
174
- { namespace: "auth", fallbackToCacheOnReadError: true },
179
+ { namespace: "auth" },
175
180
  );
176
181
 
177
182
  auth.accessToken.set("access-token");
@@ -183,6 +188,11 @@ Keep `getString` facades only when the app owns a storage architecture
183
188
  boundary. `createSecureAuthStorage` already namespaces keys, notifies
184
189
  subscribers, and migrates legacy keys.
185
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
+
186
196
  ## Typed Storage Items
187
197
 
188
198
  `createStorageItem<T>()` is the recommended API for application code. It keeps
@@ -248,9 +258,11 @@ loginMethod.setOrDelete(maybeMethod); // null/undefined deletes, value sets
248
258
  ## Set Items
249
259
 
250
260
  `createSetItem()` models set-membership state (seen ids, dismissed prompts)
251
- without hand-rolling membership helpers. Its `get()` result is a partial map,
252
- because absent members are not stored. Adding an existing member or deleting an
253
- 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.
254
266
 
255
267
  ```ts
256
268
  import { createSetItem, StorageScope } from "react-native-nitro-storage";
@@ -386,7 +398,7 @@ import {
386
398
  StorageScope,
387
399
  createSecureAuthStorage,
388
400
  createStorageItem,
389
- isKeychainLockedError,
401
+ isStorageError,
390
402
  storage,
391
403
  } from "react-native-nitro-storage";
392
404
 
@@ -417,7 +429,7 @@ const auth = createSecureAuthStorage({
417
429
  try {
418
430
  recoveryCode.get();
419
431
  } catch (error) {
420
- if (isKeychainLockedError(error)) {
432
+ if (isStorageError(error, "keychain_locked")) {
421
433
  storage.getSecurityCapabilities();
422
434
  }
423
435
  }
@@ -429,6 +441,15 @@ exporting secure values unless you are intentionally doing a short-lived
429
441
  in-memory migration. `storage.export(StorageScope.Secure)` throws unless you
430
442
  explicitly opt into `{ includeSecureValues: true }`.
431
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
+
432
453
  On Android 11 and newer, `BiometricLevel.BiometryOnly` and
433
454
  `BiometricLevel.BiometryOrPasscode` use separate Keystore policies. Android 10
434
455
  and older support `BiometryOrPasscode`; `BiometryOnly` throws
@@ -436,9 +457,11 @@ and older support `BiometryOrPasscode`; `BiometryOnly` throws
436
457
  biometric-only distinction. Promoting a value to biometric storage removes the
437
458
  plain secure copy on every platform, so plain reads cannot return a stale
438
459
  value. Secure existence, discovery, and cleanup operations
439
- can also throw when a protected store is locked or its key is invalidated. Catch
440
- those failures and use `isKeychainLockedError()` when authentication-aware retry
441
- 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`.
442
465
 
443
466
  ## Batch Operations
444
467
 
@@ -446,7 +469,9 @@ behavior is appropriate.
446
469
  matching item. `setBatch()` validates every item/value pair independently,
447
470
  including heterogeneous batches.
448
471
  Missing keys use each item's `defaultValue`; the native bridge preserves missing
449
- entries as `undefined` while reading the batch.
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.
450
475
 
451
476
  ```ts
452
477
  import { getBatch, removeBatch, setBatch } from "react-native-nitro-storage";
@@ -569,6 +594,11 @@ setWebDiskStorageBackend(backend);
569
594
  setWebSecureStorageBackend(backend);
570
595
  ```
571
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
+
572
602
  Browser storage cannot provide iOS Keychain or Android Keystore guarantees. Web
573
603
  Secure scope is only as strong as the backend you configure.
574
604
 
@@ -597,6 +627,28 @@ beforeEach(() => {
597
627
  const { storage, memoryItem } = createNitroStorageMock();
598
628
  ```
599
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
+
600
652
  ## Platform Support
601
653
 
602
654
  | Platform | Status |
@@ -650,9 +702,14 @@ bun run example:android
650
702
  bun run example:ios
651
703
  ```
652
704
 
653
- Run native example builds before release when changing plugin, native, Nitro,
654
- secure storage, or packaging files. The package release path also validates
655
- 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.
656
713
 
657
714
  ## License
658
715
 
@@ -15,7 +15,9 @@ def reactNativeArchitectures() {
15
15
  }
16
16
 
17
17
  apply plugin: "com.android.library"
18
- apply plugin: "org.jetbrains.kotlin.android"
18
+ if (project.extensions.findByName("kotlin") == null) {
19
+ apply plugin: "org.jetbrains.kotlin.android"
20
+ }
19
21
  // Apply autolinking script for Nitro
20
22
  apply from: "../nitrogen/generated/android/NitroStorage+autolinking.gradle"
21
23
 
@@ -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
  }