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/src/internal.ts CHANGED
@@ -1,8 +1,6 @@
1
1
  import { StorageScope } from "./Storage.types";
2
2
 
3
3
  export const MIGRATION_VERSION_KEY = "__nitro_storage_migration_version__";
4
- export const NATIVE_BATCH_MISSING_SENTINEL =
5
- "__nitro_storage_batch_missing__::v1";
6
4
  const PRIMITIVE_FAST_PATH_PREFIX = "__nitro_storage_primitive__:";
7
5
  const PRIM_NULL = "__nitro_storage_primitive__:l";
8
6
  const PRIM_UNDEFINED = "__nitro_storage_primitive__:u";
@@ -26,7 +24,6 @@ const RESERVED_RAW_TOKENS = new Set<string>([
26
24
  PRIM_INFINITY,
27
25
  PRIM_NEG_INFINITY,
28
26
  PRIM_NAN,
29
- NATIVE_BATCH_MISSING_SENTINEL,
30
27
  ]);
31
28
 
32
29
  export function escapeCollidingRawValue(value: string): string {
@@ -98,16 +95,6 @@ export function assertBatchScope(
98
95
  );
99
96
  }
100
97
 
101
- export function decodeNativeBatchValue(
102
- value: string | undefined,
103
- ): string | undefined {
104
- if (value === undefined || value === NATIVE_BATCH_MISSING_SENTINEL) {
105
- return undefined;
106
- }
107
-
108
- return value;
109
- }
110
-
111
98
  export function prefixKey(namespace: string | undefined, key: string): string {
112
99
  if (!namespace) return key;
113
100
  return `${namespace}${NAMESPACE_SEPARATOR}${key}`;
@@ -126,7 +113,6 @@ export function serializeWithPrimitiveFastPath<T>(value: T): string {
126
113
  case "string": {
127
114
  const stringValue = value as string;
128
115
  if (
129
- stringValue === NATIVE_BATCH_MISSING_SENTINEL ||
130
116
  stringValue.startsWith(PRIMITIVE_FAST_PATH_PREFIX) ||
131
117
  stringValue.startsWith(ESCAPE_PREFIX)
132
118
  ) {
package/src/shared.ts CHANGED
@@ -75,6 +75,7 @@ export type KeyListenerRegistry = Map<string, Set<() => void>>;
75
75
  export type RawBatchPathItem = {
76
76
  _hasValidation?: boolean;
77
77
  _hasExpiration?: boolean;
78
+ _hasRenameFrom?: boolean;
78
79
  _isBiometric?: boolean;
79
80
  _biometricLevel?: BiometricLevel;
80
81
  _secureAccessControl?: AccessControl;
@@ -84,6 +85,7 @@ export type RollbackRecord =
84
85
  | {
85
86
  kind: "memory";
86
87
  value: unknown;
88
+ expiresAt?: number;
87
89
  }
88
90
  | {
89
91
  kind: "raw";
@@ -96,6 +98,81 @@ export type RollbackRecord =
96
98
  level: BiometricLevel;
97
99
  };
98
100
 
101
+ export type StorageCompositeError = Error & {
102
+ readonly cause: unknown;
103
+ readonly errors: readonly unknown[];
104
+ readonly code: "storage_compensation_failed";
105
+ };
106
+
107
+ export type StorageCompensationError = StorageCompositeError;
108
+
109
+ export function createStorageCompositeError(
110
+ operation: string,
111
+ primary: unknown,
112
+ contexts: readonly { label: string; error: unknown }[],
113
+ ): StorageCompositeError {
114
+ const primaryMessage =
115
+ primary instanceof Error ? primary.message : String(primary);
116
+ const contextMessage = contexts
117
+ .map(({ label, error }) => {
118
+ const message = error instanceof Error ? error.message : String(error);
119
+ return `${label}: ${message}`;
120
+ })
121
+ .join("; ");
122
+ const composite = new Error(
123
+ `[nitro-error:storage_compensation_failed] NitroStorage: ${operation} failed; primary error: ${primaryMessage}${
124
+ contextMessage.length > 0 ? `; ${contextMessage}` : ""
125
+ }`,
126
+ ) as StorageCompositeError;
127
+ Object.defineProperties(composite, {
128
+ cause: {
129
+ configurable: true,
130
+ enumerable: false,
131
+ value: primary,
132
+ writable: false,
133
+ },
134
+ errors: {
135
+ configurable: true,
136
+ enumerable: false,
137
+ value: Object.freeze([primary, ...contexts.map(({ error }) => error)]),
138
+ writable: false,
139
+ },
140
+ code: {
141
+ configurable: false,
142
+ enumerable: false,
143
+ value: "storage_compensation_failed",
144
+ writable: false,
145
+ },
146
+ });
147
+ return composite;
148
+ }
149
+
150
+ export function normalizeStorageError(error: unknown): unknown {
151
+ if (!(error instanceof Error)) {
152
+ return error;
153
+ }
154
+
155
+ const code = getStorageErrorCode(error);
156
+ if (
157
+ code !== "storage_compensation_failed" ||
158
+ ("cause" in error && "errors" in error && "code" in error)
159
+ ) {
160
+ return error;
161
+ }
162
+
163
+ const operationMatch = error.message.match(
164
+ /NitroStorage: ([^;]+?) failed(?:;|$)/,
165
+ );
166
+ const countMatch = error.message.match(/rollback_error_count=(\d+)/);
167
+ const rollbackErrorCount = countMatch ? Number(countMatch[1]) : 0;
168
+ const operation = operationMatch?.[1] ?? "native storage compensation";
169
+ const contexts = Array.from({ length: rollbackErrorCount }, (_, index) => ({
170
+ label: `native rollback ${index + 1}`,
171
+ error: new Error("Native storage rollback failed"),
172
+ }));
173
+ return createStorageCompositeError(operation, error, contexts);
174
+ }
175
+
99
176
  export function isUpdater<T>(
100
177
  valueOrFn: T | ((prev: T) => T),
101
178
  ): valueOrFn is (prev: T) => T {
@@ -133,12 +210,14 @@ export type NonMemoryScope = StorageScope.Disk | StorageScope.Secure;
133
210
  export type PendingDiskWrite = {
134
211
  key: string;
135
212
  value: string | undefined;
213
+ generation: number;
136
214
  };
137
215
 
138
216
  export type PendingSecureWrite = {
139
217
  key: string;
140
218
  value: string | undefined;
141
219
  accessControl?: AccessControl;
220
+ generation: number;
142
221
  };
143
222
 
144
223
  export const runMicrotask =
@@ -214,6 +293,7 @@ export function canUseRawBatchPath(item: RawBatchPathItem): boolean {
214
293
  return (
215
294
  item._hasExpiration === false &&
216
295
  item._hasValidation === false &&
296
+ item._hasRenameFrom !== true &&
217
297
  item._isBiometric !== true &&
218
298
  item._secureAccessControl === undefined
219
299
  );
@@ -223,6 +303,7 @@ export function canUseSecureRawBatchPath(item: RawBatchPathItem): boolean {
223
303
  return (
224
304
  item._hasExpiration === false &&
225
305
  item._hasValidation === false &&
306
+ item._hasRenameFrom !== true &&
226
307
  item._isBiometric !== true
227
308
  );
228
309
  }
@@ -246,6 +327,10 @@ export type SecureAuthStorageConfig<K extends string = string> = Record<
246
327
  }
247
328
  >;
248
329
 
330
+ /**
331
+ * @deprecated Use isStorageError(error, code) to distinguish temporary lock,
332
+ * authentication, and invalidated-key recovery paths.
333
+ */
249
334
  export function isKeychainLockedError(err: unknown): boolean {
250
335
  return isLockedStorageErrorCode(getStorageErrorCode(err));
251
336
  }