react-native-nitro-storage 0.12.0 → 0.14.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 +47 -0
- package/README.md +107 -15
- package/SECURITY.md +2 -2
- package/android/build.gradle +5 -0
- package/android/src/main/cpp/AndroidStorageAdapterCpp.cpp +12 -13
- package/android/src/main/cpp/JniSize.hpp +21 -0
- package/android/src/main/java/com/nitrostorage/AndroidStorageAdapter.kt +94 -7
- package/android/src/main/java/com/nitrostorage/DiskSqliteStore.kt +147 -34
- package/cpp/bindings/HybridStorage.cpp +12 -4
- package/cpp/core/SqliteDiskStore.cpp +172 -26
- package/cpp/core/SqliteDiskStore.hpp +10 -1
- package/docs/api-reference.md +60 -54
- package/docs/batch-transactions-migrations.md +7 -0
- package/docs/native-libraries.md +28 -0
- package/docs/qa/agent-device-replay.md +70 -0
- package/docs/secure-storage.md +12 -6
- package/ios/IOSStorageAdapterCpp.mm +89 -22
- package/lib/commonjs/Storage.types.js +4 -2
- package/lib/commonjs/Storage.types.js.map +1 -1
- package/lib/commonjs/core/durability.js +30 -2
- package/lib/commonjs/core/durability.js.map +1 -1
- package/lib/commonjs/index.js.map +1 -1
- package/lib/commonjs/index.web.js.map +1 -1
- package/lib/commonjs/shared.js.map +1 -1
- package/lib/commonjs/storage-core.js +63 -9
- package/lib/commonjs/storage-core.js.map +1 -1
- package/lib/commonjs/testing.js +1 -0
- package/lib/commonjs/testing.js.map +1 -1
- package/lib/module/Storage.types.js +4 -2
- package/lib/module/Storage.types.js.map +1 -1
- package/lib/module/core/durability.js +30 -2
- package/lib/module/core/durability.js.map +1 -1
- package/lib/module/index.js.map +1 -1
- package/lib/module/index.web.js.map +1 -1
- package/lib/module/shared.js.map +1 -1
- package/lib/module/storage-core.js +63 -9
- package/lib/module/storage-core.js.map +1 -1
- package/lib/module/testing.js +1 -0
- package/lib/module/testing.js.map +1 -1
- package/lib/typescript/Storage.types.d.ts +4 -2
- package/lib/typescript/Storage.types.d.ts.map +1 -1
- package/lib/typescript/core/durability.d.ts +7 -1
- package/lib/typescript/core/durability.d.ts.map +1 -1
- package/lib/typescript/index.d.ts +4 -2
- package/lib/typescript/index.d.ts.map +1 -1
- package/lib/typescript/index.web.d.ts +4 -2
- package/lib/typescript/index.web.d.ts.map +1 -1
- package/lib/typescript/shared.d.ts +8 -1
- package/lib/typescript/shared.d.ts.map +1 -1
- package/lib/typescript/storage-core.d.ts +6 -3
- package/lib/typescript/storage-core.d.ts.map +1 -1
- package/lib/typescript/testing.d.ts +7 -4
- package/lib/typescript/testing.d.ts.map +1 -1
- package/package.json +2 -1
- package/src/Storage.types.ts +4 -2
- package/src/core/durability.ts +46 -2
- package/src/index.ts +4 -0
- package/src/index.web.ts +4 -0
- package/src/shared.ts +17 -1
- package/src/storage-core.ts +87 -13
- package/src/testing.ts +5 -0
package/src/storage-core.ts
CHANGED
|
@@ -1,4 +1,7 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import {
|
|
2
|
+
createDurabilityCoordinator,
|
|
3
|
+
type StorageScheduledFlushErrorObserver,
|
|
4
|
+
} from "./core/durability";
|
|
2
5
|
import { createMetricsRegistry } from "./core/metrics";
|
|
3
6
|
import {
|
|
4
7
|
MIGRATION_VERSION_KEY,
|
|
@@ -44,6 +47,7 @@ import {
|
|
|
44
47
|
type StorageMetricsObserver,
|
|
45
48
|
type StorageSelectorListener,
|
|
46
49
|
type StorageSelectorSubscribeOptions,
|
|
50
|
+
type SynchronousCallbackResult,
|
|
47
51
|
type StorageVersion,
|
|
48
52
|
type Validator,
|
|
49
53
|
type VersionedValue,
|
|
@@ -76,6 +80,28 @@ export type TransactionContext = {
|
|
|
76
80
|
) => void;
|
|
77
81
|
};
|
|
78
82
|
|
|
83
|
+
function assertSynchronousCallbackResult(
|
|
84
|
+
value: unknown,
|
|
85
|
+
callbackKind: "Transaction" | "Migration",
|
|
86
|
+
): void {
|
|
87
|
+
if (
|
|
88
|
+
value === null ||
|
|
89
|
+
(typeof value !== "object" && typeof value !== "function")
|
|
90
|
+
) {
|
|
91
|
+
return;
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
const then = (value as { then?: unknown }).then;
|
|
95
|
+
if (typeof then !== "function") {
|
|
96
|
+
return;
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
void Promise.resolve(value).catch(() => undefined);
|
|
100
|
+
throw new TypeError(
|
|
101
|
+
`${callbackKind} callbacks must be synchronous and cannot return a Promise or thenable.`,
|
|
102
|
+
);
|
|
103
|
+
}
|
|
104
|
+
|
|
79
105
|
export type StorageItemConfig<T> = {
|
|
80
106
|
key: string;
|
|
81
107
|
scope: StorageScope;
|
|
@@ -318,7 +344,7 @@ function asInternal<T>(item: StorageItem<T>): StorageItemInternal<T> {
|
|
|
318
344
|
export function createStorageCore(
|
|
319
345
|
buildAdapter: (internals: StorageCoreInternals) => StorageCoreAdapter,
|
|
320
346
|
) {
|
|
321
|
-
const registeredMigrations = new Map<number, Migration
|
|
347
|
+
const registeredMigrations = new Map<number, Migration<unknown>>();
|
|
322
348
|
const itemGroups = new Map<string, Set<StorageItemInternal<unknown>>>();
|
|
323
349
|
const registeredKeyCounts = new Map<string, number>();
|
|
324
350
|
const memoryStore = new Map<string, unknown>();
|
|
@@ -574,8 +600,9 @@ export function createStorageCore(
|
|
|
574
600
|
operation: StorageChangeOperation,
|
|
575
601
|
source: StorageChangeSource,
|
|
576
602
|
changes: StorageKeyChangeEvent[],
|
|
603
|
+
emitWhenEmpty = false,
|
|
577
604
|
): void {
|
|
578
|
-
if (changes.length === 0) {
|
|
605
|
+
if (changes.length === 0 && !emitWhenEmpty) {
|
|
579
606
|
return;
|
|
580
607
|
}
|
|
581
608
|
|
|
@@ -1024,9 +1051,18 @@ export function createStorageCore(
|
|
|
1024
1051
|
return;
|
|
1025
1052
|
}
|
|
1026
1053
|
measureOperation("storage:clear", scope, () => {
|
|
1027
|
-
|
|
1028
|
-
|
|
1029
|
-
|
|
1054
|
+
let previousValues: Record<string, string> = {};
|
|
1055
|
+
let previousValuesUnreadable = false;
|
|
1056
|
+
if (shouldReadPreviousEventValues(scope)) {
|
|
1057
|
+
try {
|
|
1058
|
+
previousValues = storage.getAll(scope);
|
|
1059
|
+
} catch (error) {
|
|
1060
|
+
if (scope !== StorageScope.Disk) {
|
|
1061
|
+
throw error;
|
|
1062
|
+
}
|
|
1063
|
+
previousValuesUnreadable = true;
|
|
1064
|
+
}
|
|
1065
|
+
}
|
|
1030
1066
|
if (scope === StorageScope.Memory) {
|
|
1031
1067
|
memoryStore.clear();
|
|
1032
1068
|
memoryExpirationDeadlines.clear();
|
|
@@ -1049,9 +1085,15 @@ export function createStorageCore(
|
|
|
1049
1085
|
return;
|
|
1050
1086
|
}
|
|
1051
1087
|
|
|
1088
|
+
let diskFlushFailed = false;
|
|
1052
1089
|
if (scope === StorageScope.Disk) {
|
|
1053
|
-
|
|
1054
|
-
|
|
1090
|
+
try {
|
|
1091
|
+
flushDiskWrites();
|
|
1092
|
+
durability.clearAllPendingDiskWrites();
|
|
1093
|
+
} catch {
|
|
1094
|
+
diskFlushFailed = true;
|
|
1095
|
+
previousValuesUnreadable = shouldReadPreviousEventValues(scope);
|
|
1096
|
+
}
|
|
1055
1097
|
}
|
|
1056
1098
|
|
|
1057
1099
|
if (scope === StorageScope.Secure) {
|
|
@@ -1061,6 +1103,9 @@ export function createStorageCore(
|
|
|
1061
1103
|
|
|
1062
1104
|
clearScopeRawCache(scope);
|
|
1063
1105
|
adapter.backend.clear(scope);
|
|
1106
|
+
if (diskFlushFailed) {
|
|
1107
|
+
durability.clearAllPendingDiskWrites();
|
|
1108
|
+
}
|
|
1064
1109
|
emitBatchChange(
|
|
1065
1110
|
scope,
|
|
1066
1111
|
"clear",
|
|
@@ -1075,6 +1120,7 @@ export function createStorageCore(
|
|
|
1075
1120
|
adapter.changeSource,
|
|
1076
1121
|
),
|
|
1077
1122
|
),
|
|
1123
|
+
previousValuesUnreadable,
|
|
1078
1124
|
);
|
|
1079
1125
|
});
|
|
1080
1126
|
},
|
|
@@ -1465,6 +1511,12 @@ export function createStorageCore(
|
|
|
1465
1511
|
flushSecureWrites();
|
|
1466
1512
|
});
|
|
1467
1513
|
},
|
|
1514
|
+
/** Observes scheduled flush failures; explicit flush calls still throw. */
|
|
1515
|
+
setScheduledFlushErrorObserver: (
|
|
1516
|
+
observer?: StorageScheduledFlushErrorObserver,
|
|
1517
|
+
): void => {
|
|
1518
|
+
durability.setScheduledFlushErrorObserver(observer);
|
|
1519
|
+
},
|
|
1468
1520
|
setMetricsObserver: (observer?: StorageMetricsObserver) => {
|
|
1469
1521
|
metrics.setObserver(observer);
|
|
1470
1522
|
},
|
|
@@ -3300,7 +3352,10 @@ export function createStorageCore(
|
|
|
3300
3352
|
);
|
|
3301
3353
|
}
|
|
3302
3354
|
|
|
3303
|
-
function registerMigration(
|
|
3355
|
+
function registerMigration<TResult>(
|
|
3356
|
+
version: number,
|
|
3357
|
+
migration: Migration<TResult>,
|
|
3358
|
+
): void {
|
|
3304
3359
|
if (!Number.isInteger(version) || version <= 0) {
|
|
3305
3360
|
throw new Error("Migration version must be a positive integer.");
|
|
3306
3361
|
}
|
|
@@ -3328,7 +3383,8 @@ export function createStorageCore(
|
|
|
3328
3383
|
return;
|
|
3329
3384
|
}
|
|
3330
3385
|
runTransaction(scope, (tx) => {
|
|
3331
|
-
migration(tx);
|
|
3386
|
+
const migrationResult: unknown = migration(tx);
|
|
3387
|
+
assertSynchronousCallbackResult(migrationResult, "Migration");
|
|
3332
3388
|
tx.setRaw(MIGRATION_VERSION_KEY, String(version));
|
|
3333
3389
|
});
|
|
3334
3390
|
appliedVersion = version;
|
|
@@ -3340,7 +3396,7 @@ export function createStorageCore(
|
|
|
3340
3396
|
|
|
3341
3397
|
function runTransaction<T>(
|
|
3342
3398
|
scope: StorageScope,
|
|
3343
|
-
transaction: (context: TransactionContext) => T
|
|
3399
|
+
transaction: (context: TransactionContext) => SynchronousCallbackResult<T>,
|
|
3344
3400
|
): T {
|
|
3345
3401
|
return measureOperation("transaction:run", scope, () => {
|
|
3346
3402
|
assertValidScope(scope);
|
|
@@ -3359,6 +3415,12 @@ export function createStorageCore(
|
|
|
3359
3415
|
};
|
|
3360
3416
|
const rollback = new Map<string, TransactionRollbackEntry>();
|
|
3361
3417
|
const itemRenameStates = new Map<StorageItemInternal<unknown>, boolean>();
|
|
3418
|
+
let transactionOpen = true;
|
|
3419
|
+
const assertTransactionOpen = (): void => {
|
|
3420
|
+
if (!transactionOpen) {
|
|
3421
|
+
throw new TypeError("Transaction context is closed.");
|
|
3422
|
+
}
|
|
3423
|
+
};
|
|
3362
3424
|
|
|
3363
3425
|
const rememberRollback = (
|
|
3364
3426
|
key: string,
|
|
@@ -3466,26 +3528,34 @@ export function createStorageCore(
|
|
|
3466
3528
|
|
|
3467
3529
|
const tx: TransactionContext = {
|
|
3468
3530
|
scope,
|
|
3469
|
-
getRaw: (key) =>
|
|
3531
|
+
getRaw: (key) => {
|
|
3532
|
+
assertTransactionOpen();
|
|
3533
|
+
return getRawValue(key, scope);
|
|
3534
|
+
},
|
|
3470
3535
|
setRaw: (key, value) => {
|
|
3536
|
+
assertTransactionOpen();
|
|
3471
3537
|
rememberRollback(key);
|
|
3472
3538
|
setRawValue(key, value, scope);
|
|
3473
3539
|
},
|
|
3474
3540
|
removeRaw: (key) => {
|
|
3541
|
+
assertTransactionOpen();
|
|
3475
3542
|
rememberRollback(key, undefined, scope === StorageScope.Secure);
|
|
3476
3543
|
removeRawValue(key, scope);
|
|
3477
3544
|
},
|
|
3478
3545
|
getItem: (item) => {
|
|
3546
|
+
assertTransactionOpen();
|
|
3479
3547
|
assertBatchScope([item], scope);
|
|
3480
3548
|
rememberItemRollback(item);
|
|
3481
3549
|
return item.get();
|
|
3482
3550
|
},
|
|
3483
3551
|
setItem: (item, value) => {
|
|
3552
|
+
assertTransactionOpen();
|
|
3484
3553
|
assertBatchScope([item], scope);
|
|
3485
3554
|
rememberItemRollback(item);
|
|
3486
3555
|
item.set(value);
|
|
3487
3556
|
},
|
|
3488
3557
|
removeItem: (item) => {
|
|
3558
|
+
assertTransactionOpen();
|
|
3489
3559
|
assertBatchScope([item], scope);
|
|
3490
3560
|
rememberItemRollback(item);
|
|
3491
3561
|
item.delete();
|
|
@@ -3493,8 +3563,12 @@ export function createStorageCore(
|
|
|
3493
3563
|
};
|
|
3494
3564
|
|
|
3495
3565
|
try {
|
|
3496
|
-
|
|
3566
|
+
const result = transaction(tx);
|
|
3567
|
+
assertSynchronousCallbackResult(result, "Transaction");
|
|
3568
|
+
transactionOpen = false;
|
|
3569
|
+
return result;
|
|
3497
3570
|
} catch (error) {
|
|
3571
|
+
transactionOpen = false;
|
|
3498
3572
|
itemRenameStates.forEach((migrated, item) => {
|
|
3499
3573
|
item._setRenameMigrationState(migrated);
|
|
3500
3574
|
});
|
package/src/testing.ts
CHANGED
|
@@ -23,6 +23,10 @@ import type {
|
|
|
23
23
|
|
|
24
24
|
export { StorageScope, AccessControl, BiometricLevel } from "./Storage.types";
|
|
25
25
|
export { isKeychainLockedError } from "./shared";
|
|
26
|
+
export type {
|
|
27
|
+
StorageScheduledFlushError,
|
|
28
|
+
StorageScheduledFlushErrorObserver,
|
|
29
|
+
} from "./core/durability";
|
|
26
30
|
export { migrateFromMMKV } from "./migration";
|
|
27
31
|
export { getStorageErrorCode, isStorageError } from "./storage-runtime";
|
|
28
32
|
export {
|
|
@@ -279,6 +283,7 @@ function buildTestingModule() {
|
|
|
279
283
|
|
|
280
284
|
const reset = (): void => {
|
|
281
285
|
storage.setEventObserver(undefined);
|
|
286
|
+
storage.setScheduledFlushErrorObserver(undefined);
|
|
282
287
|
storage.setMetricsObserver(undefined);
|
|
283
288
|
storage.resetMetrics();
|
|
284
289
|
storage.clearAll();
|