Skip to content

Commit d1aed10

Browse files
refactor: reorganize storage logic and improve performance
- Moved utility functions and constants to a new internal module for better organization. - Simplified storage operations by consolidating common logic into reusable functions. - Enhanced caching mechanisms for raw values to reduce redundant storage access. - Improved batch processing for storage operations, allowing for more efficient data handling. - Updated type definitions and added new features for better type safety and clarity. - Refactored listener management to streamline event handling across different storage scopes. - Adjusted migration handling to ensure compatibility with new storage structures. - Updated task dependencies in turbo.json to include code generation step before build.
1 parent 34869d6 commit d1aed10

32 files changed

Lines changed: 3351 additions & 2241 deletions

.github/workflows/ci.yml

Lines changed: 7 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -13,15 +13,15 @@ jobs:
1313

1414
steps:
1515
- name: Checkout code
16-
uses: actions/checkout@v4
16+
uses: actions/checkout@v6
1717

1818
- name: Setup Bun
1919
uses: oven-sh/setup-bun@v2
2020
with:
21-
bun-version: latest
21+
bun-version: 1.3.9
2222

2323
- name: Cache dependencies
24-
uses: actions/cache@v4
24+
uses: actions/cache@v5
2525
with:
2626
path: |
2727
~/.bun/install/cache
@@ -41,9 +41,10 @@ jobs:
4141
run: bun run test
4242

4343
- name: Run JavaScript tests with coverage
44-
run: |
45-
cd packages/react-native-nitro-storage
46-
bun run test:coverage
44+
run: bun run test:coverage
45+
46+
- name: Run benchmark regression checks
47+
run: bun run benchmark
4748

4849
- name: Setup CMake (for C++ tests)
4950
run: |

CHANGELOG.md

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,41 @@ All notable changes to this project are documented in this file.
44

55
The format follows Keep a Changelog and the project adheres to SemVer.
66

7+
## 0.3.0 - 2026-02-15
8+
9+
### Added
10+
- Add `useStorageSelector(item, selector, isEqual?)` to reduce rerenders from unrelated object updates.
11+
- Add opt-in `coalesceSecureWrites` and per-item `readCache` controls in `createStorageItem` config.
12+
- Add benchmark regression gate (`benchmark` task/script) and wire it into CI and publish checks.
13+
14+
### Changed
15+
- Switch default serialization to a primitive fast path for primitives while preserving JSON compatibility for objects and legacy values.
16+
- Replace broad listener fan-out with key-indexed registries and automatic pruning for memory/native/web paths.
17+
- Rework Turbo task graph so `build` depends on `codegen`, `test`/`typecheck` run from source, and `codegen` can be cached.
18+
19+
### Fixed
20+
- Route native batch calls through true adapter-level batch APIs (HybridStorage + iOS/Android adapters) instead of per-key loops.
21+
- Add read-through cache invalidation on scoped/key change events and native/web clear paths.
22+
23+
## 0.2.1 - 2026-02-15
24+
25+
### Added
26+
- Add explicit package `exports` for ESM/CJS/react-native/web resolution.
27+
28+
### Fixed
29+
- Preserve validation and TTL semantics in batch APIs by falling back to per-item paths when needed.
30+
- Preserve item-level semantics in transaction `setItem`/`removeItem` by using item methods directly.
31+
- Decode native batch missing values correctly to avoid empty-string ambiguity on iOS/Android C++ bindings.
32+
- Avoid duplicate observer updates on native/web `setBatch` paths.
33+
- Scope iOS disk storage to a dedicated UserDefaults suite and avoid clearing unrelated app defaults.
34+
- Use a package-specific Android master-key alias for encrypted storage initialization and recovery.
35+
- Expo config plugin now preserves existing `NSFaceIDUsageDescription` values.
36+
- Expo config plugin makes Android biometric permissions opt-in.
37+
38+
### Changed
39+
- Raise `react` peer dependency floor to `>=18.2.0`.
40+
- Update CI workflow action versions and Bun runtime pin to latest stable releases.
41+
742
## 0.2.0 - 2026-02-15
843

944
### Added

README.md

Lines changed: 41 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ Synchronous storage for React Native with a unified API for memory, disk, and se
66

77
- `react-native >= 0.75.0`
88
- `react-native-nitro-modules >= 0.33.9`
9-
- `react`
9+
- `react >= 18.2.0`
1010

1111
## Installation
1212

@@ -25,11 +25,24 @@ bunx expo install react-native-nitro-storage react-native-nitro-modules
2525
```json
2626
{
2727
"expo": {
28-
"plugins": ["react-native-nitro-storage"]
28+
"plugins": [
29+
[
30+
"react-native-nitro-storage",
31+
{
32+
"faceIDPermission": "Allow $(PRODUCT_NAME) to use Face ID for secure authentication",
33+
"addBiometricPermissions": false
34+
}
35+
]
36+
]
2937
}
3038
}
3139
```
3240

41+
Notes:
42+
43+
- If `faceIDPermission` is omitted, the plugin sets a default only when `NSFaceIDUsageDescription` is missing.
44+
- Android biometric permissions are opt-in via `addBiometricPermissions: true`.
45+
3346
Then:
3447

3548
```bash
@@ -86,6 +99,7 @@ export function Counter() {
8699
- `storage`
87100
- `createStorageItem`
88101
- `useStorage`
102+
- `useStorageSelector`
89103
- `useSetStorage`
90104
- `getBatch`
91105
- `setBatch`
@@ -155,6 +169,8 @@ type StorageItemConfig<T> = {
155169
validate?: Validator<T>;
156170
onValidationError?: (invalidValue: unknown) => T;
157171
expiration?: ExpirationConfig;
172+
readCache?: boolean;
173+
coalesceSecureWrites?: boolean;
158174
};
159175
```
160176

@@ -184,7 +200,10 @@ Notes:
184200

185201
- `Memory` stores values directly.
186202
- `Disk` and `Secure` store serialized values.
203+
- Default serialization uses a primitive fast path for strings/numbers/booleans/null/undefined and JSON for objects/arrays.
187204
- If `expiration` is enabled, values are wrapped internally and expired lazily on read.
205+
- `readCache` is opt-in for `Disk`/`Secure` and can be enabled per item.
206+
- `coalesceSecureWrites` is opt-in and batches same-tick secure writes per key.
188207
- If `validate` fails on a stored value, fallback is:
189208
1. `onValidationError(invalidValue)` if provided
190209
2. `defaultValue` otherwise
@@ -202,6 +221,18 @@ function useStorage<T>(
202221
): [T, (value: T | ((prev: T) => T)) => void]
203222
```
204223

224+
### `useStorageSelector(item, selector, isEqual?)`
225+
226+
```ts
227+
function useStorageSelector<T, TSelected>(
228+
item: StorageItem<T>,
229+
selector: (value: T) => TSelected,
230+
isEqual?: (prev: TSelected, next: TSelected) => boolean
231+
): [TSelected, (value: T | ((prev: T) => T)) => void]
232+
```
233+
234+
Use this to subscribe to a derived slice of a storage value and avoid rerenders when that slice does not change.
235+
205236
### `useSetStorage(item)`
206237

207238
```ts
@@ -243,14 +274,15 @@ function setBatch<T>(
243274
): void;
244275
245276
function removeBatch(
246-
items: readonly Pick<StorageItem<unknown>, "key" | "scope" | "delete" | "_triggerListeners">[],
277+
items: readonly Pick<StorageItem<unknown>, "key" | "scope" | "delete">[],
247278
scope: StorageScope
248279
): void;
249280
```
250281

251282
Rules:
252283

253284
- All items must match the batch `scope`.
285+
- Items using `validate` or `expiration` automatically run via per-item `get()`/`set()` paths to preserve validation and TTL behavior.
254286
- Mixed-scope calls throw:
255287
- `Batch scope mismatch for "<key>": expected <Scope>, received <Scope>.`
256288

@@ -292,11 +324,8 @@ type TransactionContext = {
292324
setRaw: (key: string, value: string) => void;
293325
removeRaw: (key: string) => void;
294326
getItem: <T>(item: Pick<StorageItem<T>, "scope" | "key" | "get">) => T;
295-
setItem: <T>(
296-
item: Pick<StorageItem<T>, "scope" | "key" | "serialize">,
297-
value: T
298-
) => void;
299-
removeItem: (item: Pick<StorageItem<unknown>, "scope" | "key">) => void;
327+
setItem: <T>(item: Pick<StorageItem<T>, "scope" | "key" | "set">, value: T) => void;
328+
removeItem: (item: Pick<StorageItem<unknown>, "scope" | "key" | "delete">) => void;
300329
};
301330
302331
function runTransaction<T>(
@@ -309,6 +338,7 @@ Behavior:
309338

310339
- On exception, it rolls back keys modified in that transaction.
311340
- Rollback is best-effort within process lifetime.
341+
- `setItem`/`removeItem` prefer item methods when available, so validation/TTL/cache semantics stay consistent.
312342

313343
Throws:
314344

@@ -404,7 +434,7 @@ migrateToLatest(StorageScope.Disk);
404434
## Scope Semantics
405435

406436
- `Memory`: in-memory only, not persisted.
407-
- `Disk`: UserDefaults (iOS), SharedPreferences (Android), `localStorage` (web).
437+
- `Disk`: App-scoped UserDefaults suite (iOS), SharedPreferences (Android), `localStorage` (web).
408438
- `Secure`: Keychain (iOS), EncryptedSharedPreferences (Android), `sessionStorage` fallback (web).
409439

410440
## Dev Commands
@@ -415,6 +445,7 @@ From repo root:
415445
bun run test -- --filter=react-native-nitro-storage
416446
bun run typecheck -- --filter=react-native-nitro-storage
417447
bun run build -- --filter=react-native-nitro-storage
448+
bun run benchmark
418449
```
419450

420451
Inside package:
@@ -424,6 +455,7 @@ bun run test
424455
bun run test:coverage
425456
bun run typecheck
426457
bun run build
458+
bun run benchmark
427459
```
428460

429461
## License

0 commit comments

Comments
 (0)