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.
Files changed (61) hide show
  1. package/CHANGELOG.md +47 -0
  2. package/README.md +107 -15
  3. package/SECURITY.md +2 -2
  4. package/android/build.gradle +5 -0
  5. package/android/src/main/cpp/AndroidStorageAdapterCpp.cpp +12 -13
  6. package/android/src/main/cpp/JniSize.hpp +21 -0
  7. package/android/src/main/java/com/nitrostorage/AndroidStorageAdapter.kt +94 -7
  8. package/android/src/main/java/com/nitrostorage/DiskSqliteStore.kt +147 -34
  9. package/cpp/bindings/HybridStorage.cpp +12 -4
  10. package/cpp/core/SqliteDiskStore.cpp +172 -26
  11. package/cpp/core/SqliteDiskStore.hpp +10 -1
  12. package/docs/api-reference.md +60 -54
  13. package/docs/batch-transactions-migrations.md +7 -0
  14. package/docs/native-libraries.md +28 -0
  15. package/docs/qa/agent-device-replay.md +70 -0
  16. package/docs/secure-storage.md +12 -6
  17. package/ios/IOSStorageAdapterCpp.mm +89 -22
  18. package/lib/commonjs/Storage.types.js +4 -2
  19. package/lib/commonjs/Storage.types.js.map +1 -1
  20. package/lib/commonjs/core/durability.js +30 -2
  21. package/lib/commonjs/core/durability.js.map +1 -1
  22. package/lib/commonjs/index.js.map +1 -1
  23. package/lib/commonjs/index.web.js.map +1 -1
  24. package/lib/commonjs/shared.js.map +1 -1
  25. package/lib/commonjs/storage-core.js +63 -9
  26. package/lib/commonjs/storage-core.js.map +1 -1
  27. package/lib/commonjs/testing.js +1 -0
  28. package/lib/commonjs/testing.js.map +1 -1
  29. package/lib/module/Storage.types.js +4 -2
  30. package/lib/module/Storage.types.js.map +1 -1
  31. package/lib/module/core/durability.js +30 -2
  32. package/lib/module/core/durability.js.map +1 -1
  33. package/lib/module/index.js.map +1 -1
  34. package/lib/module/index.web.js.map +1 -1
  35. package/lib/module/shared.js.map +1 -1
  36. package/lib/module/storage-core.js +63 -9
  37. package/lib/module/storage-core.js.map +1 -1
  38. package/lib/module/testing.js +1 -0
  39. package/lib/module/testing.js.map +1 -1
  40. package/lib/typescript/Storage.types.d.ts +4 -2
  41. package/lib/typescript/Storage.types.d.ts.map +1 -1
  42. package/lib/typescript/core/durability.d.ts +7 -1
  43. package/lib/typescript/core/durability.d.ts.map +1 -1
  44. package/lib/typescript/index.d.ts +4 -2
  45. package/lib/typescript/index.d.ts.map +1 -1
  46. package/lib/typescript/index.web.d.ts +4 -2
  47. package/lib/typescript/index.web.d.ts.map +1 -1
  48. package/lib/typescript/shared.d.ts +8 -1
  49. package/lib/typescript/shared.d.ts.map +1 -1
  50. package/lib/typescript/storage-core.d.ts +6 -3
  51. package/lib/typescript/storage-core.d.ts.map +1 -1
  52. package/lib/typescript/testing.d.ts +7 -4
  53. package/lib/typescript/testing.d.ts.map +1 -1
  54. package/package.json +2 -1
  55. package/src/Storage.types.ts +4 -2
  56. package/src/core/durability.ts +46 -2
  57. package/src/index.ts +4 -0
  58. package/src/index.web.ts +4 -0
  59. package/src/shared.ts +17 -1
  60. package/src/storage-core.ts +87 -13
  61. package/src/testing.ts +5 -0
@@ -1,4 +1,7 @@
1
- import { createDurabilityCoordinator } from "./core/durability";
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
- const previousValues = shouldReadPreviousEventValues(scope)
1028
- ? storage.getAll(scope)
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
- flushDiskWrites();
1054
- durability.clearAllPendingDiskWrites();
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(version: number, migration: Migration): void {
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) => getRawValue(key, scope),
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
- return transaction(tx);
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();