react-native-nitro-storage 0.1.3 → 0.3.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 (48) hide show
  1. package/README.md +320 -391
  2. package/android/src/main/cpp/AndroidStorageAdapterCpp.cpp +101 -0
  3. package/android/src/main/cpp/AndroidStorageAdapterCpp.hpp +6 -41
  4. package/android/src/main/java/com/nitrostorage/AndroidStorageAdapter.kt +125 -37
  5. package/app.plugin.js +9 -7
  6. package/cpp/bindings/HybridStorage.cpp +214 -19
  7. package/cpp/bindings/HybridStorage.hpp +1 -0
  8. package/cpp/core/NativeStorageAdapter.hpp +7 -0
  9. package/ios/IOSStorageAdapterCpp.hpp +6 -0
  10. package/ios/IOSStorageAdapterCpp.mm +90 -7
  11. package/lib/commonjs/index.js +537 -66
  12. package/lib/commonjs/index.js.map +1 -1
  13. package/lib/commonjs/index.web.js +558 -130
  14. package/lib/commonjs/index.web.js.map +1 -1
  15. package/lib/commonjs/internal.js +102 -0
  16. package/lib/commonjs/internal.js.map +1 -0
  17. package/lib/module/index.js +528 -67
  18. package/lib/module/index.js.map +1 -1
  19. package/lib/module/index.web.js +536 -122
  20. package/lib/module/index.web.js.map +1 -1
  21. package/lib/module/internal.js +92 -0
  22. package/lib/module/internal.js.map +1 -0
  23. package/lib/typescript/index.d.ts +42 -6
  24. package/lib/typescript/index.d.ts.map +1 -1
  25. package/lib/typescript/index.web.d.ts +45 -12
  26. package/lib/typescript/index.web.d.ts.map +1 -1
  27. package/lib/typescript/internal.d.ts +19 -0
  28. package/lib/typescript/internal.d.ts.map +1 -0
  29. package/lib/typescript/migration.d.ts +2 -3
  30. package/lib/typescript/migration.d.ts.map +1 -1
  31. package/nitrogen/generated/android/NitroStorage+autolinking.cmake +1 -1
  32. package/nitrogen/generated/android/NitroStorage+autolinking.gradle +1 -1
  33. package/nitrogen/generated/android/NitroStorageOnLoad.cpp +1 -1
  34. package/nitrogen/generated/android/NitroStorageOnLoad.hpp +1 -1
  35. package/nitrogen/generated/android/kotlin/com/margelo/nitro/com/nitrostorage/NitroStorageOnLoad.kt +1 -1
  36. package/nitrogen/generated/ios/NitroStorage+autolinking.rb +1 -1
  37. package/nitrogen/generated/ios/NitroStorage-Swift-Cxx-Bridge.cpp +1 -1
  38. package/nitrogen/generated/ios/NitroStorage-Swift-Cxx-Bridge.hpp +1 -1
  39. package/nitrogen/generated/ios/NitroStorage-Swift-Cxx-Umbrella.hpp +1 -1
  40. package/nitrogen/generated/ios/NitroStorageAutolinking.mm +1 -1
  41. package/nitrogen/generated/ios/NitroStorageAutolinking.swift +5 -1
  42. package/nitrogen/generated/shared/c++/HybridStorageSpec.cpp +1 -1
  43. package/nitrogen/generated/shared/c++/HybridStorageSpec.hpp +1 -1
  44. package/package.json +19 -8
  45. package/src/index.ts +734 -74
  46. package/src/index.web.ts +732 -128
  47. package/src/internal.ts +134 -0
  48. package/src/migration.ts +2 -2
@@ -11,14 +11,30 @@ Object.defineProperty(exports, "StorageScope", {
11
11
  });
12
12
  exports.createStorageItem = createStorageItem;
13
13
  exports.getBatch = getBatch;
14
+ Object.defineProperty(exports, "migrateFromMMKV", {
15
+ enumerable: true,
16
+ get: function () {
17
+ return _migration.migrateFromMMKV;
18
+ }
19
+ });
20
+ exports.migrateToLatest = migrateToLatest;
21
+ exports.registerMigration = registerMigration;
14
22
  exports.removeBatch = removeBatch;
23
+ exports.runTransaction = runTransaction;
15
24
  exports.setBatch = setBatch;
16
25
  exports.storage = void 0;
17
26
  exports.useSetStorage = useSetStorage;
18
27
  exports.useStorage = useStorage;
28
+ exports.useStorageSelector = useStorageSelector;
19
29
  var _react = require("react");
20
30
  var _reactNativeNitroModules = require("react-native-nitro-modules");
21
31
  var _Storage = require("./Storage.types");
32
+ var _internal = require("./internal");
33
+ var _migration = require("./migration");
34
+ const registeredMigrations = new Map();
35
+ const runMicrotask = typeof queueMicrotask === "function" ? queueMicrotask : task => {
36
+ Promise.resolve().then(task);
37
+ };
22
38
  let _storageModule = null;
23
39
  function getStorageModule() {
24
40
  if (!_storageModule) {
@@ -27,18 +43,202 @@ function getStorageModule() {
27
43
  return _storageModule;
28
44
  }
29
45
  const memoryStore = new Map();
30
- const memoryListeners = new Set();
31
- function notifyMemoryListeners(key, value) {
32
- memoryListeners.forEach(listener => listener(key, value));
46
+ const memoryListeners = new Map();
47
+ const scopedListeners = new Map([[_Storage.StorageScope.Disk, new Map()], [_Storage.StorageScope.Secure, new Map()]]);
48
+ const scopedUnsubscribers = new Map();
49
+ const scopedRawCache = new Map([[_Storage.StorageScope.Disk, new Map()], [_Storage.StorageScope.Secure, new Map()]]);
50
+ const pendingSecureWrites = new Map();
51
+ let secureFlushScheduled = false;
52
+ function getScopedListeners(scope) {
53
+ return scopedListeners.get(scope);
54
+ }
55
+ function getScopeRawCache(scope) {
56
+ return scopedRawCache.get(scope);
57
+ }
58
+ function cacheRawValue(scope, key, value) {
59
+ getScopeRawCache(scope).set(key, value);
60
+ }
61
+ function readCachedRawValue(scope, key) {
62
+ return getScopeRawCache(scope).get(key);
63
+ }
64
+ function hasCachedRawValue(scope, key) {
65
+ return getScopeRawCache(scope).has(key);
66
+ }
67
+ function clearScopeRawCache(scope) {
68
+ getScopeRawCache(scope).clear();
69
+ }
70
+ function notifyKeyListeners(registry, key) {
71
+ registry.get(key)?.forEach(listener => listener());
72
+ }
73
+ function notifyAllListeners(registry) {
74
+ registry.forEach(listeners => {
75
+ listeners.forEach(listener => listener());
76
+ });
77
+ }
78
+ function addKeyListener(registry, key, listener) {
79
+ let listeners = registry.get(key);
80
+ if (!listeners) {
81
+ listeners = new Set();
82
+ registry.set(key, listeners);
83
+ }
84
+ listeners.add(listener);
85
+ return () => {
86
+ const scopedListeners = registry.get(key);
87
+ if (!scopedListeners) {
88
+ return;
89
+ }
90
+ scopedListeners.delete(listener);
91
+ if (scopedListeners.size === 0) {
92
+ registry.delete(key);
93
+ }
94
+ };
95
+ }
96
+ function readPendingSecureWrite(key) {
97
+ return pendingSecureWrites.get(key)?.value;
98
+ }
99
+ function hasPendingSecureWrite(key) {
100
+ return pendingSecureWrites.has(key);
101
+ }
102
+ function clearPendingSecureWrite(key) {
103
+ pendingSecureWrites.delete(key);
104
+ }
105
+ function flushSecureWrites() {
106
+ secureFlushScheduled = false;
107
+ if (pendingSecureWrites.size === 0) {
108
+ return;
109
+ }
110
+ const writes = Array.from(pendingSecureWrites.values());
111
+ pendingSecureWrites.clear();
112
+ const keysToSet = [];
113
+ const valuesToSet = [];
114
+ const keysToRemove = [];
115
+ writes.forEach(({
116
+ key,
117
+ value
118
+ }) => {
119
+ if (value === undefined) {
120
+ keysToRemove.push(key);
121
+ } else {
122
+ keysToSet.push(key);
123
+ valuesToSet.push(value);
124
+ }
125
+ });
126
+ const storageModule = getStorageModule();
127
+ if (keysToSet.length > 0) {
128
+ storageModule.setBatch(keysToSet, valuesToSet, _Storage.StorageScope.Secure);
129
+ }
130
+ if (keysToRemove.length > 0) {
131
+ storageModule.removeBatch(keysToRemove, _Storage.StorageScope.Secure);
132
+ }
133
+ }
134
+ function scheduleSecureWrite(key, value) {
135
+ pendingSecureWrites.set(key, {
136
+ key,
137
+ value
138
+ });
139
+ if (secureFlushScheduled) {
140
+ return;
141
+ }
142
+ secureFlushScheduled = true;
143
+ runMicrotask(flushSecureWrites);
144
+ }
145
+ function ensureNativeScopeSubscription(scope) {
146
+ if (scopedUnsubscribers.has(scope)) {
147
+ return;
148
+ }
149
+ const unsubscribe = getStorageModule().addOnChange(scope, (key, value) => {
150
+ if (scope === _Storage.StorageScope.Secure) {
151
+ if (key === "") {
152
+ pendingSecureWrites.clear();
153
+ } else {
154
+ clearPendingSecureWrite(key);
155
+ }
156
+ }
157
+ if (key === "") {
158
+ clearScopeRawCache(scope);
159
+ notifyAllListeners(getScopedListeners(scope));
160
+ return;
161
+ }
162
+ cacheRawValue(scope, key, value);
163
+ notifyKeyListeners(getScopedListeners(scope), key);
164
+ });
165
+ scopedUnsubscribers.set(scope, unsubscribe);
166
+ }
167
+ function maybeCleanupNativeScopeSubscription(scope) {
168
+ const listeners = getScopedListeners(scope);
169
+ if (listeners.size > 0) {
170
+ return;
171
+ }
172
+ const unsubscribe = scopedUnsubscribers.get(scope);
173
+ if (!unsubscribe) {
174
+ return;
175
+ }
176
+ unsubscribe();
177
+ scopedUnsubscribers.delete(scope);
178
+ }
179
+ function getRawValue(key, scope) {
180
+ (0, _internal.assertValidScope)(scope);
181
+ if (scope === _Storage.StorageScope.Memory) {
182
+ const value = memoryStore.get(key);
183
+ return typeof value === "string" ? value : undefined;
184
+ }
185
+ if (scope === _Storage.StorageScope.Secure && hasPendingSecureWrite(key)) {
186
+ return readPendingSecureWrite(key);
187
+ }
188
+ return getStorageModule().get(key, scope);
189
+ }
190
+ function setRawValue(key, value, scope) {
191
+ (0, _internal.assertValidScope)(scope);
192
+ if (scope === _Storage.StorageScope.Memory) {
193
+ memoryStore.set(key, value);
194
+ notifyKeyListeners(memoryListeners, key);
195
+ return;
196
+ }
197
+ if (scope === _Storage.StorageScope.Secure) {
198
+ flushSecureWrites();
199
+ clearPendingSecureWrite(key);
200
+ }
201
+ getStorageModule().set(key, value, scope);
202
+ cacheRawValue(scope, key, value);
203
+ }
204
+ function removeRawValue(key, scope) {
205
+ (0, _internal.assertValidScope)(scope);
206
+ if (scope === _Storage.StorageScope.Memory) {
207
+ memoryStore.delete(key);
208
+ notifyKeyListeners(memoryListeners, key);
209
+ return;
210
+ }
211
+ if (scope === _Storage.StorageScope.Secure) {
212
+ flushSecureWrites();
213
+ clearPendingSecureWrite(key);
214
+ }
215
+ getStorageModule().remove(key, scope);
216
+ cacheRawValue(scope, key, undefined);
217
+ }
218
+ function readMigrationVersion(scope) {
219
+ const raw = getRawValue(_internal.MIGRATION_VERSION_KEY, scope);
220
+ if (raw === undefined) {
221
+ return 0;
222
+ }
223
+ const parsed = Number.parseInt(raw, 10);
224
+ return Number.isFinite(parsed) && parsed > 0 ? parsed : 0;
225
+ }
226
+ function writeMigrationVersion(scope, version) {
227
+ setRawValue(_internal.MIGRATION_VERSION_KEY, String(version), scope);
33
228
  }
34
229
  const storage = exports.storage = {
35
230
  clear: scope => {
36
231
  if (scope === _Storage.StorageScope.Memory) {
37
232
  memoryStore.clear();
38
- notifyMemoryListeners("", undefined);
39
- } else {
40
- getStorageModule().clear(scope);
233
+ notifyAllListeners(memoryListeners);
234
+ return;
41
235
  }
236
+ if (scope === _Storage.StorageScope.Secure) {
237
+ flushSecureWrites();
238
+ pendingSecureWrites.clear();
239
+ }
240
+ clearScopeRawCache(scope);
241
+ getStorageModule().clear(scope);
42
242
  },
43
243
  clearAll: () => {
44
244
  storage.clear(_Storage.StorageScope.Memory);
@@ -46,83 +246,201 @@ const storage = exports.storage = {
46
246
  storage.clear(_Storage.StorageScope.Secure);
47
247
  }
48
248
  };
249
+ function canUseRawBatchPath(item) {
250
+ return item._hasExpiration === false && item._hasValidation === false;
251
+ }
49
252
  function defaultSerialize(value) {
50
- return JSON.stringify(value);
253
+ return (0, _internal.serializeWithPrimitiveFastPath)(value);
51
254
  }
52
255
  function defaultDeserialize(value) {
53
- return JSON.parse(value);
256
+ return (0, _internal.deserializeWithPrimitiveFastPath)(value);
54
257
  }
55
258
  function createStorageItem(config) {
56
259
  const serialize = config.serialize ?? defaultSerialize;
57
260
  const deserialize = config.deserialize ?? defaultDeserialize;
58
261
  const isMemory = config.scope === _Storage.StorageScope.Memory;
262
+ const validate = config.validate;
263
+ const onValidationError = config.onValidationError;
264
+ const expiration = config.expiration;
265
+ const expirationTtlMs = expiration?.ttlMs;
266
+ const memoryExpiration = expiration && isMemory ? new Map() : null;
267
+ const readCache = !isMemory && config.readCache === true;
268
+ const coalesceSecureWrites = config.scope === _Storage.StorageScope.Secure && config.coalesceSecureWrites === true;
269
+ const nonMemoryScope = config.scope === _Storage.StorageScope.Disk ? _Storage.StorageScope.Disk : config.scope === _Storage.StorageScope.Secure ? _Storage.StorageScope.Secure : null;
270
+ if (expiration && expiration.ttlMs <= 0) {
271
+ throw new Error("expiration.ttlMs must be greater than 0.");
272
+ }
59
273
  const listeners = new Set();
60
274
  let unsubscribe = null;
275
+ let lastRaw = undefined;
276
+ let lastValue;
277
+ let hasLastValue = false;
278
+ const invalidateParsedCache = () => {
279
+ lastRaw = undefined;
280
+ lastValue = undefined;
281
+ hasLastValue = false;
282
+ };
61
283
  const ensureSubscription = () => {
62
- if (!unsubscribe) {
63
- if (isMemory) {
64
- const listener = key => {
65
- if (key === "" || key === config.key) {
66
- lastRaw = undefined;
67
- lastValue = undefined;
68
- listeners.forEach(l => l());
69
- }
70
- };
71
- memoryListeners.add(listener);
72
- unsubscribe = () => memoryListeners.delete(listener);
73
- } else {
74
- unsubscribe = getStorageModule().addOnChange(config.scope, key => {
75
- if (key === "" || key === config.key) {
76
- lastRaw = undefined;
77
- lastValue = undefined;
78
- listeners.forEach(listener => listener());
79
- }
80
- });
284
+ if (unsubscribe) {
285
+ return;
286
+ }
287
+ const listener = () => {
288
+ invalidateParsedCache();
289
+ listeners.forEach(callback => callback());
290
+ };
291
+ if (isMemory) {
292
+ unsubscribe = addKeyListener(memoryListeners, config.key, listener);
293
+ return;
294
+ }
295
+ ensureNativeScopeSubscription(nonMemoryScope);
296
+ unsubscribe = addKeyListener(getScopedListeners(nonMemoryScope), config.key, listener);
297
+ };
298
+ const readStoredRaw = () => {
299
+ if (isMemory) {
300
+ if (memoryExpiration) {
301
+ const expiresAt = memoryExpiration.get(config.key);
302
+ if (expiresAt !== undefined && expiresAt <= Date.now()) {
303
+ memoryExpiration.delete(config.key);
304
+ memoryStore.delete(config.key);
305
+ notifyKeyListeners(memoryListeners, config.key);
306
+ return undefined;
307
+ }
308
+ }
309
+ return memoryStore.get(config.key);
310
+ }
311
+ if (nonMemoryScope === _Storage.StorageScope.Secure && hasPendingSecureWrite(config.key)) {
312
+ return readPendingSecureWrite(config.key);
313
+ }
314
+ if (readCache) {
315
+ if (hasCachedRawValue(nonMemoryScope, config.key)) {
316
+ return readCachedRawValue(nonMemoryScope, config.key);
81
317
  }
82
318
  }
319
+ const raw = getStorageModule().get(config.key, config.scope);
320
+ cacheRawValue(nonMemoryScope, config.key, raw);
321
+ return raw;
83
322
  };
84
- let lastRaw;
85
- let lastValue;
86
- const get = () => {
87
- let raw;
323
+ const writeStoredRaw = rawValue => {
324
+ cacheRawValue(nonMemoryScope, config.key, rawValue);
325
+ if (coalesceSecureWrites) {
326
+ scheduleSecureWrite(config.key, rawValue);
327
+ return;
328
+ }
329
+ if (nonMemoryScope === _Storage.StorageScope.Secure) {
330
+ clearPendingSecureWrite(config.key);
331
+ }
332
+ getStorageModule().set(config.key, rawValue, config.scope);
333
+ };
334
+ const removeStoredRaw = () => {
335
+ cacheRawValue(nonMemoryScope, config.key, undefined);
336
+ if (coalesceSecureWrites) {
337
+ scheduleSecureWrite(config.key, undefined);
338
+ return;
339
+ }
340
+ if (nonMemoryScope === _Storage.StorageScope.Secure) {
341
+ clearPendingSecureWrite(config.key);
342
+ }
343
+ getStorageModule().remove(config.key, config.scope);
344
+ };
345
+ const writeValueWithoutValidation = value => {
88
346
  if (isMemory) {
89
- raw = memoryStore.get(config.key);
90
- } else {
91
- raw = getStorageModule().get(config.key, config.scope);
347
+ if (memoryExpiration) {
348
+ memoryExpiration.set(config.key, Date.now() + (expirationTtlMs ?? 0));
349
+ }
350
+ memoryStore.set(config.key, value);
351
+ notifyKeyListeners(memoryListeners, config.key);
352
+ return;
353
+ }
354
+ const serialized = serialize(value);
355
+ if (expiration) {
356
+ const envelope = {
357
+ __nitroStorageEnvelope: true,
358
+ expiresAt: Date.now() + expiration.ttlMs,
359
+ payload: serialized
360
+ };
361
+ writeStoredRaw(JSON.stringify(envelope));
362
+ return;
363
+ }
364
+ writeStoredRaw(serialized);
365
+ };
366
+ const resolveInvalidValue = invalidValue => {
367
+ if (onValidationError) {
368
+ return onValidationError(invalidValue);
92
369
  }
93
- if (raw === lastRaw && lastValue !== undefined) {
370
+ return config.defaultValue;
371
+ };
372
+ const ensureValidatedValue = (candidate, hadStoredValue) => {
373
+ if (!validate || validate(candidate)) {
374
+ return candidate;
375
+ }
376
+ const resolved = resolveInvalidValue(candidate);
377
+ if (validate && !validate(resolved)) {
378
+ return config.defaultValue;
379
+ }
380
+ if (hadStoredValue) {
381
+ writeValueWithoutValidation(resolved);
382
+ }
383
+ return resolved;
384
+ };
385
+ const get = () => {
386
+ const raw = readStoredRaw();
387
+ const canUseCachedValue = !expiration && !memoryExpiration;
388
+ if (canUseCachedValue && raw === lastRaw && hasLastValue) {
94
389
  return lastValue;
95
390
  }
96
391
  lastRaw = raw;
97
392
  if (raw === undefined) {
98
- lastValue = config.defaultValue;
99
- } else {
100
- if (isMemory) {
101
- lastValue = raw;
102
- } else {
103
- lastValue = deserialize(raw);
393
+ lastValue = ensureValidatedValue(config.defaultValue, false);
394
+ hasLastValue = true;
395
+ return lastValue;
396
+ }
397
+ if (isMemory) {
398
+ lastValue = ensureValidatedValue(raw, true);
399
+ hasLastValue = true;
400
+ return lastValue;
401
+ }
402
+ let deserializableRaw = raw;
403
+ if (expiration) {
404
+ try {
405
+ const parsed = JSON.parse(raw);
406
+ if ((0, _internal.isStoredEnvelope)(parsed)) {
407
+ if (parsed.expiresAt <= Date.now()) {
408
+ removeStoredRaw();
409
+ invalidateParsedCache();
410
+ lastValue = ensureValidatedValue(config.defaultValue, false);
411
+ hasLastValue = true;
412
+ return lastValue;
413
+ }
414
+ deserializableRaw = parsed.payload;
415
+ }
416
+ } catch {
417
+ // Keep backward compatibility with legacy raw values.
104
418
  }
105
419
  }
420
+ lastValue = ensureValidatedValue(deserialize(deserializableRaw), true);
421
+ hasLastValue = true;
106
422
  return lastValue;
107
423
  };
108
424
  const set = valueOrFn => {
109
425
  const currentValue = get();
110
426
  const newValue = typeof valueOrFn === "function" ? valueOrFn(currentValue) : valueOrFn;
111
- if (isMemory) {
112
- memoryStore.set(config.key, newValue);
113
- notifyMemoryListeners(config.key, newValue);
114
- } else {
115
- const serialized = serialize(newValue);
116
- getStorageModule().set(config.key, serialized, config.scope);
427
+ invalidateParsedCache();
428
+ if (validate && !validate(newValue)) {
429
+ throw new Error(`Validation failed for key "${config.key}" in scope "${_Storage.StorageScope[config.scope]}".`);
117
430
  }
431
+ writeValueWithoutValidation(newValue);
118
432
  };
119
433
  const deleteItem = () => {
434
+ invalidateParsedCache();
120
435
  if (isMemory) {
436
+ if (memoryExpiration) {
437
+ memoryExpiration.delete(config.key);
438
+ }
121
439
  memoryStore.delete(config.key);
122
- notifyMemoryListeners(config.key, undefined);
123
- } else {
124
- getStorageModule().remove(config.key, config.scope);
440
+ notifyKeyListeners(memoryListeners, config.key);
441
+ return;
125
442
  }
443
+ removeStoredRaw();
126
444
  };
127
445
  const subscribe = callback => {
128
446
  ensureSubscription();
@@ -131,11 +449,14 @@ function createStorageItem(config) {
131
449
  listeners.delete(callback);
132
450
  if (listeners.size === 0 && unsubscribe) {
133
451
  unsubscribe();
452
+ if (!isMemory) {
453
+ maybeCleanupNativeScopeSubscription(nonMemoryScope);
454
+ }
134
455
  unsubscribe = null;
135
456
  }
136
457
  };
137
458
  };
138
- return {
459
+ const storageItem = {
139
460
  get,
140
461
  set,
141
462
  delete: deleteItem,
@@ -143,29 +464,83 @@ function createStorageItem(config) {
143
464
  serialize,
144
465
  deserialize,
145
466
  _triggerListeners: () => {
146
- lastRaw = undefined;
147
- lastValue = undefined;
148
- listeners.forEach(l => l());
467
+ invalidateParsedCache();
468
+ listeners.forEach(listener => listener());
149
469
  },
470
+ _hasValidation: validate !== undefined,
471
+ _hasExpiration: expiration !== undefined,
472
+ _readCacheEnabled: readCache,
150
473
  scope: config.scope,
151
474
  key: config.key
152
475
  };
476
+ return storageItem;
153
477
  }
154
478
  function useStorage(item) {
155
479
  const value = (0, _react.useSyncExternalStore)(item.subscribe, item.get, item.get);
156
480
  return [value, item.set];
157
481
  }
482
+ function useStorageSelector(item, selector, isEqual = Object.is) {
483
+ const selectedRef = (0, _react.useRef)({
484
+ hasValue: false
485
+ });
486
+ const getSelectedSnapshot = () => {
487
+ const nextSelected = selector(item.get());
488
+ const current = selectedRef.current;
489
+ if (current.hasValue && isEqual(current.value, nextSelected)) {
490
+ return current.value;
491
+ }
492
+ selectedRef.current = {
493
+ hasValue: true,
494
+ value: nextSelected
495
+ };
496
+ return nextSelected;
497
+ };
498
+ const selectedValue = (0, _react.useSyncExternalStore)(item.subscribe, getSelectedSnapshot, getSelectedSnapshot);
499
+ return [selectedValue, item.set];
500
+ }
158
501
  function useSetStorage(item) {
159
502
  return item.set;
160
503
  }
161
504
  function getBatch(items, scope) {
505
+ (0, _internal.assertBatchScope)(items, scope);
162
506
  if (scope === _Storage.StorageScope.Memory) {
163
507
  return items.map(item => item.get());
164
508
  }
165
- const keys = items.map(item => item.key);
166
- const rawValues = getStorageModule().getBatch(keys, scope);
167
- return items.map((item, idx) => {
168
- const raw = rawValues[idx];
509
+ const useRawBatchPath = items.every(item => canUseRawBatchPath(item));
510
+ if (!useRawBatchPath) {
511
+ return items.map(item => item.get());
512
+ }
513
+ const useBatchCache = items.every(item => item._readCacheEnabled === true);
514
+ const rawValues = new Array(items.length);
515
+ const keysToFetch = [];
516
+ const keyIndexes = [];
517
+ items.forEach((item, index) => {
518
+ if (scope === _Storage.StorageScope.Secure) {
519
+ if (hasPendingSecureWrite(item.key)) {
520
+ rawValues[index] = readPendingSecureWrite(item.key);
521
+ return;
522
+ }
523
+ }
524
+ if (useBatchCache) {
525
+ if (hasCachedRawValue(scope, item.key)) {
526
+ rawValues[index] = readCachedRawValue(scope, item.key);
527
+ return;
528
+ }
529
+ }
530
+ keysToFetch.push(item.key);
531
+ keyIndexes.push(index);
532
+ });
533
+ if (keysToFetch.length > 0) {
534
+ const fetchedValues = getStorageModule().getBatch(keysToFetch, scope).map(value => (0, _internal.decodeNativeBatchValue)(value));
535
+ fetchedValues.forEach((value, index) => {
536
+ const key = keysToFetch[index];
537
+ const targetIndex = keyIndexes[index];
538
+ rawValues[targetIndex] = value;
539
+ cacheRawValue(scope, key, value);
540
+ });
541
+ }
542
+ return items.map((item, index) => {
543
+ const raw = rawValues[index];
169
544
  if (raw === undefined) {
170
545
  return item.get();
171
546
  }
@@ -173,6 +548,7 @@ function getBatch(items, scope) {
173
548
  });
174
549
  }
175
550
  function setBatch(items, scope) {
551
+ (0, _internal.assertBatchScope)(items.map(batchEntry => batchEntry.item), scope);
176
552
  if (scope === _Storage.StorageScope.Memory) {
177
553
  items.forEach(({
178
554
  item,
@@ -180,22 +556,117 @@ function setBatch(items, scope) {
180
556
  }) => item.set(value));
181
557
  return;
182
558
  }
183
- const keys = items.map(i => i.item.key);
184
- const values = items.map(i => i.item.serialize(i.value));
185
- getStorageModule().setBatch(keys, values, scope);
186
- items.forEach(({
559
+ const useRawBatchPath = items.every(({
187
560
  item
188
- }) => {
189
- item._triggerListeners();
190
- });
561
+ }) => canUseRawBatchPath(item));
562
+ if (!useRawBatchPath) {
563
+ items.forEach(({
564
+ item,
565
+ value
566
+ }) => item.set(value));
567
+ return;
568
+ }
569
+ const keys = items.map(entry => entry.item.key);
570
+ const values = items.map(entry => entry.item.serialize(entry.value));
571
+ if (scope === _Storage.StorageScope.Secure) {
572
+ flushSecureWrites();
573
+ }
574
+ getStorageModule().setBatch(keys, values, scope);
575
+ keys.forEach((key, index) => cacheRawValue(scope, key, values[index]));
191
576
  }
192
577
  function removeBatch(items, scope) {
578
+ (0, _internal.assertBatchScope)(items, scope);
193
579
  if (scope === _Storage.StorageScope.Memory) {
194
580
  items.forEach(item => item.delete());
195
581
  return;
196
582
  }
197
583
  const keys = items.map(item => item.key);
584
+ if (scope === _Storage.StorageScope.Secure) {
585
+ flushSecureWrites();
586
+ }
198
587
  getStorageModule().removeBatch(keys, scope);
199
- items.forEach(item => item.delete());
588
+ keys.forEach(key => cacheRawValue(scope, key, undefined));
589
+ }
590
+ function registerMigration(version, migration) {
591
+ if (!Number.isInteger(version) || version <= 0) {
592
+ throw new Error("Migration version must be a positive integer.");
593
+ }
594
+ if (registeredMigrations.has(version)) {
595
+ throw new Error(`Migration version ${version} is already registered.`);
596
+ }
597
+ registeredMigrations.set(version, migration);
598
+ }
599
+ function migrateToLatest(scope = _Storage.StorageScope.Disk) {
600
+ (0, _internal.assertValidScope)(scope);
601
+ const currentVersion = readMigrationVersion(scope);
602
+ const versions = Array.from(registeredMigrations.keys()).filter(version => version > currentVersion).sort((a, b) => a - b);
603
+ let appliedVersion = currentVersion;
604
+ const context = {
605
+ scope,
606
+ getRaw: key => getRawValue(key, scope),
607
+ setRaw: (key, value) => setRawValue(key, value, scope),
608
+ removeRaw: key => removeRawValue(key, scope)
609
+ };
610
+ versions.forEach(version => {
611
+ const migration = registeredMigrations.get(version);
612
+ if (!migration) {
613
+ return;
614
+ }
615
+ migration(context);
616
+ writeMigrationVersion(scope, version);
617
+ appliedVersion = version;
618
+ });
619
+ return appliedVersion;
620
+ }
621
+ function runTransaction(scope, transaction) {
622
+ (0, _internal.assertValidScope)(scope);
623
+ if (scope === _Storage.StorageScope.Secure) {
624
+ flushSecureWrites();
625
+ }
626
+ const rollback = new Map();
627
+ const rememberRollback = key => {
628
+ if (rollback.has(key)) {
629
+ return;
630
+ }
631
+ rollback.set(key, getRawValue(key, scope));
632
+ };
633
+ const tx = {
634
+ scope,
635
+ getRaw: key => getRawValue(key, scope),
636
+ setRaw: (key, value) => {
637
+ rememberRollback(key);
638
+ setRawValue(key, value, scope);
639
+ },
640
+ removeRaw: key => {
641
+ rememberRollback(key);
642
+ removeRawValue(key, scope);
643
+ },
644
+ getItem: item => {
645
+ (0, _internal.assertBatchScope)([item], scope);
646
+ return item.get();
647
+ },
648
+ setItem: (item, value) => {
649
+ (0, _internal.assertBatchScope)([item], scope);
650
+ rememberRollback(item.key);
651
+ item.set(value);
652
+ },
653
+ removeItem: item => {
654
+ (0, _internal.assertBatchScope)([item], scope);
655
+ rememberRollback(item.key);
656
+ item.delete();
657
+ }
658
+ };
659
+ try {
660
+ return transaction(tx);
661
+ } catch (error) {
662
+ Array.from(rollback.entries()).reverse().forEach(([key, previousValue]) => {
663
+ if (previousValue === undefined) {
664
+ removeRawValue(key, scope);
665
+ } else {
666
+ setRawValue(key, previousValue, scope);
667
+ }
668
+ });
669
+ throw error;
670
+ }
200
671
  }
201
672
  //# sourceMappingURL=index.js.map