Skip to content

Commit 428e3c5

Browse files
Merge pull request #9 from JoaoPauloCMarra/v0.3.2
v0.3.2
2 parents a7c6347 + 6a3fd42 commit 428e3c5

40 files changed

Lines changed: 2529 additions & 713 deletions

.github/workflows/ci.yml

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,9 @@ jobs:
3737
- name: Run type checking
3838
run: bun run typecheck
3939

40+
- name: Run type-level API tests
41+
run: bun run test:types
42+
4043
- name: Run JavaScript tests
4144
run: bun run test
4245

@@ -71,6 +74,4 @@ jobs:
7174
- name: Test package packaging
7275
run: |
7376
cd packages/react-native-nitro-storage
74-
# Test that npm pack works (dry run)
75-
npm pack --dry-run > /dev/null 2>&1 || exit 1
76-
echo "✅ Package can be packed for publishing"
77+
bun run check:pack

CHANGELOG.md

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,32 @@ 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.2 - 2026-02-22
8+
9+
### Added
10+
11+
- Add `storage.setSecureWritesAsync(enabled)` to toggle Android secure writes between synchronous `commit()` and asynchronous `apply()`.
12+
- Add `storage.flushSecureWrites()` for deterministic flush control of coalesced secure writes.
13+
- Add native `removeByPrefix(prefix, scope)` plumbing and route namespace clears through the native/web prefix path.
14+
- Add dedicated C++ binding tests for `HybridStorage` behavior (`cpp/bindings/HybridStorageTest.cpp`), wired into `test:cpp`.
15+
- Add type-level public API tests (`test:types`) and package content guard checks (`check:pack`).
16+
17+
### Changed
18+
19+
- Skip unnecessary read path on direct `item.set(value)` writes (still reads for updater functions).
20+
- Reuse TTL envelope parse results while entries remain unexpired to avoid repeated JSON parse/deserialization work.
21+
- Group secure raw batch writes by per-item access control so secure batch paths stay fast even with mixed access-control settings.
22+
- Optimize C++ batch listener dispatch by copying scoped listeners once per batch operation.
23+
- Avoid duplicate secure biometric clearing calls by relying on secure clear paths that already include biometric cleanup.
24+
- Optimize web secure/disk key bookkeeping with an indexed key cache (faster `size`, `getAllKeys`, and namespace clears without repeated `localStorage` scans).
25+
- Improve iOS secure key union performance by deduplicating with an `unordered_set`.
26+
- Extract shared React hooks into `src/storage-hooks.ts` to reduce native/web entrypoint duplication.
27+
- Expand benchmark coverage to include Disk and Secure scope throughput checks and tighten regression thresholds.
28+
- Refresh the Expo example app UI with a cleaner shared design system and add an Android secure write mode demo control.
29+
- Configure Expo iOS example builds to use React Native source builds under New Architecture and silence expected deprecated RN host warnings in the Android template wrapper.
30+
- Extend CI with Android/iOS example build jobs under New Architecture.
31+
- 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.
32+
733
## 0.3.1 - 2026-02-16
834

935
### Added

README.md

Lines changed: 199 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,24 @@ Synchronous Memory, Disk, and Secure storage in one unified API — powered by [
1919
- **MMKV migration** — drop-in `migrateFromMMKV` for painless migration from MMKV
2020
- **Cross-platform** — iOS, Android, and web (`localStorage` fallback)
2121

22+
## Feature Coverage
23+
24+
Every feature in this package is documented with at least one runnable example in this README:
25+
26+
- Core item API (`createStorageItem`, `get/set/delete/has/subscribe`) — see Quick Start and Low-level subscription use case
27+
- Hooks (`useStorage`, `useStorageSelector`, `useSetStorage`) — see Quick Start and Persisted User Preferences
28+
- Scopes (`Memory`, `Disk`, `Secure`) — see Storage Scopes and multiple use cases
29+
- Namespaces — see Multi-Tenant / Namespaced Storage
30+
- TTL expiration + callbacks — see OTP / Temporary Codes
31+
- Validation + recovery — see Feature Flags with Validation
32+
- Biometric + access control — see Biometric-protected Secrets
33+
- Global storage utilities (`clear*`, `has`, `getAll*`, `size`, secure write settings) — see Global utility examples and Storage Snapshots and Cleanup
34+
- Batch APIs (`getBatch`, `setBatch`, `removeBatch`) — see Batch Operations and Bulk Bootstrap with Batch APIs
35+
- Transactions — see Transactions and Atomic Balance Transfer
36+
- Migrations (`registerMigration`, `migrateToLatest`) — see Migrations
37+
- MMKV migration (`migrateFromMMKV`) — see MMKV Migration and Migrating From MMKV
38+
- Auth storage factory (`createSecureAuthStorage`) — see Auth Token Management
39+
2240
## Requirements
2341

2442
| Dependency | Version |
@@ -183,6 +201,17 @@ function createStorageItem<T = undefined>(
183201
| `scope` | `StorageScope` | The item's scope |
184202
| `key` | `string` | The resolved key (includes namespace prefix) |
185203

204+
**Non-React subscription example:**
205+
206+
```ts
207+
const unsubscribe = sessionItem.subscribe(() => {
208+
console.log("session changed:", sessionItem.get());
209+
});
210+
211+
sessionItem.set("next-session");
212+
unsubscribe();
213+
```
214+
186215
---
187216

188217
### React Hooks
@@ -231,10 +260,49 @@ import { storage, StorageScope } from "react-native-nitro-storage";
231260
| `storage.getAll(scope)` | Get all key-value pairs as `Record<string, string>` |
232261
| `storage.size(scope)` | Number of stored keys |
233262
| `storage.setAccessControl(level)` | Set default secure access control for subsequent secure writes (native only) |
263+
| `storage.setSecureWritesAsync(enabled)` | Toggle async secure writes on Android (`false` by default) |
264+
| `storage.flushSecureWrites()` | Force flush of queued secure writes when coalescing is enabled |
234265
| `storage.setKeychainAccessGroup(group)` | Set keychain access group for app sharing (native only) |
235266

236267
> `storage.getAll(StorageScope.Secure)` returns regular secure entries. Biometric-protected values are not included in this snapshot API.
237268
269+
#### Global utility examples
270+
271+
```ts
272+
import { AccessControl, storage, StorageScope } from "react-native-nitro-storage";
273+
274+
storage.has("session", StorageScope.Disk);
275+
storage.getAllKeys(StorageScope.Disk);
276+
storage.getAll(StorageScope.Disk);
277+
storage.size(StorageScope.Disk);
278+
279+
storage.clearNamespace("user-42", StorageScope.Disk);
280+
storage.clearBiometric();
281+
282+
storage.setAccessControl(AccessControl.WhenUnlockedThisDeviceOnly);
283+
storage.setKeychainAccessGroup("group.com.example.shared");
284+
285+
storage.clear(StorageScope.Memory);
286+
storage.clearAll();
287+
```
288+
289+
#### Android secure write mode
290+
291+
`storage.setSecureWritesAsync(true)` switches secure writes from synchronous `commit()` to asynchronous `apply()` on Android.
292+
Use this for non-critical secure writes when lower latency matters more than immediate durability.
293+
294+
Call `storage.flushSecureWrites()` when you need deterministic persistence boundaries (for example before namespace clears, process handoff, or strict test assertions).
295+
296+
```ts
297+
import { storage } from "react-native-nitro-storage";
298+
299+
storage.setSecureWritesAsync(true);
300+
301+
// ...multiple secure writes happen (including coalesced item writes)
302+
303+
storage.flushSecureWrites(); // deterministic durability boundary
304+
```
305+
238306
---
239307

240308
### `createSecureAuthStorage<K>(config, options?)`
@@ -398,11 +466,11 @@ enum BiometricLevel {
398466
### Persisted User Preferences
399467

400468
```ts
401-
interface UserPreferences {
469+
type UserPreferences = {
402470
theme: "light" | "dark" | "system";
403471
language: string;
404472
notifications: boolean;
405-
}
473+
};
406474

407475
const prefsItem = createStorageItem<UserPreferences>({
408476
key: "prefs",
@@ -446,21 +514,29 @@ storage.clearNamespace("myapp-auth", StorageScope.Secure);
446514
### Feature Flags with Validation
447515

448516
```ts
449-
interface FeatureFlags {
517+
type FeatureFlags = {
450518
darkMode: boolean;
451519
betaFeature: boolean;
452520
maxUploadMb: number;
453-
}
521+
};
522+
523+
const isRecord = (value: unknown): value is Record<string, unknown> =>
524+
typeof value === "object" && value !== null;
525+
526+
const isFeatureFlags = (value: unknown): value is FeatureFlags => {
527+
if (!isRecord(value)) return false;
528+
return (
529+
typeof value.darkMode === "boolean" &&
530+
typeof value.betaFeature === "boolean" &&
531+
typeof value.maxUploadMb === "number"
532+
);
533+
};
454534

455535
const flagsItem = createStorageItem<FeatureFlags>({
456536
key: "feature-flags",
457537
scope: StorageScope.Disk,
458538
defaultValue: { darkMode: false, betaFeature: false, maxUploadMb: 10 },
459-
validate: (v): v is FeatureFlags =>
460-
typeof v === "object" &&
461-
v !== null &&
462-
typeof (v as any).darkMode === "boolean" &&
463-
typeof (v as any).maxUploadMb === "number",
539+
validate: isFeatureFlags,
464540
onValidationError: () => ({
465541
darkMode: false,
466542
betaFeature: false,
@@ -471,6 +547,24 @@ const flagsItem = createStorageItem<FeatureFlags>({
471547
});
472548
```
473549

550+
### Biometric-protected Secrets
551+
552+
```ts
553+
import { AccessControl, createStorageItem, StorageScope } from "react-native-nitro-storage";
554+
555+
const paymentPin = createStorageItem<string>({
556+
key: "payment-pin",
557+
scope: StorageScope.Secure,
558+
defaultValue: "",
559+
biometric: true,
560+
accessControl: AccessControl.WhenPasscodeSetThisDeviceOnly,
561+
});
562+
563+
paymentPin.set("4829");
564+
const pin = paymentPin.get();
565+
paymentPin.delete();
566+
```
567+
474568
### Multi-Tenant / Namespaced Storage
475569

476570
```ts
@@ -515,6 +609,40 @@ otpItem.set("482917");
515609
const code = otpItem.get();
516610
```
517611

612+
### Bulk Bootstrap with Batch APIs
613+
614+
```ts
615+
import {
616+
createStorageItem,
617+
getBatch,
618+
removeBatch,
619+
setBatch,
620+
StorageScope,
621+
} from "react-native-nitro-storage";
622+
623+
const firstName = createStorageItem({
624+
key: "first-name",
625+
scope: StorageScope.Disk,
626+
defaultValue: "",
627+
});
628+
const lastName = createStorageItem({
629+
key: "last-name",
630+
scope: StorageScope.Disk,
631+
defaultValue: "",
632+
});
633+
634+
setBatch(
635+
[
636+
{ item: firstName, value: "Ada" },
637+
{ item: lastName, value: "Lovelace" },
638+
],
639+
StorageScope.Disk,
640+
);
641+
642+
const [first, last] = getBatch([firstName, lastName], StorageScope.Disk);
643+
removeBatch([firstName, lastName], StorageScope.Disk);
644+
```
645+
518646
### Atomic Balance Transfer
519647

520648
```ts
@@ -555,6 +683,60 @@ const compactItem = createStorageItem<{ id: number; active: boolean }>({
555683
});
556684
```
557685

686+
### Coalesced Secure Writes with Deterministic Flush
687+
688+
```ts
689+
import { createStorageItem, storage, StorageScope } from "react-native-nitro-storage";
690+
691+
const sessionToken = createStorageItem<string>({
692+
key: "session-token",
693+
scope: StorageScope.Secure,
694+
defaultValue: "",
695+
coalesceSecureWrites: true,
696+
});
697+
698+
sessionToken.set("token-v1");
699+
sessionToken.set("token-v2");
700+
701+
// force pending secure writes to native persistence
702+
storage.flushSecureWrites();
703+
```
704+
705+
### Storage Snapshots and Cleanup
706+
707+
```ts
708+
import { storage, StorageScope } from "react-native-nitro-storage";
709+
710+
const diskKeys = storage.getAllKeys(StorageScope.Disk);
711+
const diskValues = storage.getAll(StorageScope.Disk);
712+
const secureCount = storage.size(StorageScope.Secure);
713+
714+
if (storage.has("legacy-flag", StorageScope.Disk)) {
715+
storage.clearNamespace("legacy", StorageScope.Disk);
716+
}
717+
718+
storage.clearBiometric();
719+
```
720+
721+
### Low-level Subscription (outside React)
722+
723+
```ts
724+
import { createStorageItem, StorageScope } from "react-native-nitro-storage";
725+
726+
const notificationsItem = createStorageItem<boolean>({
727+
key: "notifications-enabled",
728+
scope: StorageScope.Disk,
729+
defaultValue: true,
730+
});
731+
732+
const unsubscribe = notificationsItem.subscribe(() => {
733+
console.log("notifications changed:", notificationsItem.get());
734+
});
735+
736+
notificationsItem.set(false);
737+
unsubscribe();
738+
```
739+
558740
### Migrating From MMKV
559741

560742
```ts
@@ -600,7 +782,11 @@ From repository root:
600782

601783
```bash
602784
bun run test -- --filter=react-native-nitro-storage
785+
bun run lint -- --filter=react-native-nitro-storage
786+
bun run format:check -- --filter=react-native-nitro-storage
603787
bun run typecheck -- --filter=react-native-nitro-storage
788+
bun run test:types -- --filter=react-native-nitro-storage
789+
bun run test:cpp -- --filter=react-native-nitro-storage
604790
bun run build -- --filter=react-native-nitro-storage
605791
```
606792

@@ -612,7 +798,10 @@ bun run test:coverage # run tests with coverage
612798
bun run lint # eslint (expo-magic rules)
613799
bun run format:check # prettier check
614800
bun run typecheck # tsc --noEmit
615-
bun run build # tsup build
801+
bun run test:types # public type-level API tests
802+
bun run test:cpp # C++ binding/core tests
803+
bun run check:pack # npm pack content guard
804+
bun run build # bob build
616805
bun run benchmark # performance benchmarks
617806
```
618807

apps/example/app.json

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,14 @@
1717
},
1818
"plugins": [
1919
"expo-router",
20-
"expo-build-properties",
20+
[
21+
"expo-build-properties",
22+
{
23+
"ios": {
24+
"buildReactNativeFromSource": true
25+
}
26+
}
27+
],
2128
"react-native-nitro-storage"
2229
],
2330
"experiments": {

0 commit comments

Comments
 (0)