react-native-nitro-storage 0.5.0 → 0.5.2
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/README.md +42 -8
- package/docs/api-reference.md +95 -31
- package/docs/batch-transactions-migrations.md +23 -9
- package/docs/benchmarks.md +1 -1
- package/docs/recipes.md +30 -9
- package/docs/secure-storage.md +20 -1
- package/lib/commonjs/index.js +214 -13
- package/lib/commonjs/index.js.map +1 -1
- package/lib/commonjs/index.web.js +166 -11
- package/lib/commonjs/index.web.js.map +1 -1
- package/lib/commonjs/indexeddb-backend.js +7 -0
- package/lib/commonjs/indexeddb-backend.js.map +1 -1
- package/lib/commonjs/storage-events.js +117 -0
- package/lib/commonjs/storage-events.js.map +1 -0
- package/lib/module/index.js +214 -13
- package/lib/module/index.js.map +1 -1
- package/lib/module/index.web.js +166 -11
- package/lib/module/index.web.js.map +1 -1
- package/lib/module/indexeddb-backend.js +7 -0
- package/lib/module/indexeddb-backend.js.map +1 -1
- package/lib/module/storage-events.js +112 -0
- package/lib/module/storage-events.js.map +1 -0
- package/lib/typescript/index.d.ts +14 -0
- package/lib/typescript/index.d.ts.map +1 -1
- package/lib/typescript/index.web.d.ts +14 -0
- package/lib/typescript/index.web.d.ts.map +1 -1
- package/lib/typescript/indexeddb-backend.d.ts.map +1 -1
- package/lib/typescript/storage-events.d.ts +37 -0
- package/lib/typescript/storage-events.d.ts.map +1 -0
- package/package.json +5 -4
- package/src/index.ts +534 -13
- package/src/index.web.ts +459 -22
- package/src/indexeddb-backend.ts +8 -0
- package/src/storage-events.ts +184 -0
package/src/index.ts
CHANGED
|
@@ -26,6 +26,15 @@ import {
|
|
|
26
26
|
type StorageCapabilities,
|
|
27
27
|
type StorageErrorCode,
|
|
28
28
|
} from "./storage-runtime";
|
|
29
|
+
import {
|
|
30
|
+
StorageEventRegistry,
|
|
31
|
+
type StorageBatchChangeEvent,
|
|
32
|
+
type StorageChangeEvent,
|
|
33
|
+
type StorageChangeOperation,
|
|
34
|
+
type StorageChangeSource,
|
|
35
|
+
type StorageEventListener,
|
|
36
|
+
type StorageKeyChangeEvent,
|
|
37
|
+
} from "./storage-events";
|
|
29
38
|
|
|
30
39
|
export { StorageScope, AccessControl, BiometricLevel } from "./Storage.types";
|
|
31
40
|
export type { Storage } from "./Storage.nitro";
|
|
@@ -37,6 +46,14 @@ export {
|
|
|
37
46
|
type StorageCapabilities,
|
|
38
47
|
type StorageErrorCode,
|
|
39
48
|
} from "./storage-runtime";
|
|
49
|
+
export type {
|
|
50
|
+
StorageBatchChangeEvent,
|
|
51
|
+
StorageChangeEvent,
|
|
52
|
+
StorageChangeOperation,
|
|
53
|
+
StorageChangeSource,
|
|
54
|
+
StorageEventListener,
|
|
55
|
+
StorageKeyChangeEvent,
|
|
56
|
+
} from "./storage-events";
|
|
40
57
|
export type {
|
|
41
58
|
WebStorageBackend,
|
|
42
59
|
WebStorageChangeEvent,
|
|
@@ -65,6 +82,14 @@ export type StorageMetricSummary = {
|
|
|
65
82
|
avgDurationMs: number;
|
|
66
83
|
maxDurationMs: number;
|
|
67
84
|
};
|
|
85
|
+
export type StorageSelectorListener<TSelected> = (
|
|
86
|
+
value: TSelected,
|
|
87
|
+
previousValue: TSelected,
|
|
88
|
+
) => void;
|
|
89
|
+
export type StorageSelectorSubscribeOptions<TSelected> = {
|
|
90
|
+
isEqual?: (previousValue: TSelected, nextValue: TSelected) => boolean;
|
|
91
|
+
fireImmediately?: boolean;
|
|
92
|
+
};
|
|
68
93
|
export type MigrationContext = {
|
|
69
94
|
scope: StorageScope;
|
|
70
95
|
getRaw: (key: string) => string | undefined;
|
|
@@ -161,11 +186,17 @@ let diskWritesAsync = false;
|
|
|
161
186
|
const pendingSecureWrites = new Map<string, PendingSecureWrite>();
|
|
162
187
|
let secureFlushScheduled = false;
|
|
163
188
|
let secureDefaultAccessControl: AccessControl = AccessControl.WhenUnlocked;
|
|
189
|
+
const suppressedNativeEvents = new Map<NonMemoryScope, Map<string, number>>([
|
|
190
|
+
[StorageScope.Disk, new Map()],
|
|
191
|
+
[StorageScope.Secure, new Map()],
|
|
192
|
+
]);
|
|
164
193
|
let metricsObserver: StorageMetricsObserver | undefined;
|
|
194
|
+
let eventObserver: StorageEventListener | undefined;
|
|
165
195
|
const metricsCounters = new Map<
|
|
166
196
|
string,
|
|
167
197
|
{ count: number; totalDurationMs: number; maxDurationMs: number }
|
|
168
198
|
>();
|
|
199
|
+
const storageEvents = new StorageEventRegistry();
|
|
169
200
|
const nativeSecureBackend = "platform-secure-storage";
|
|
170
201
|
|
|
171
202
|
function recordMetric(
|
|
@@ -245,6 +276,28 @@ function clearScopeRawCache(scope: NonMemoryScope): void {
|
|
|
245
276
|
getScopeRawCache(scope).clear();
|
|
246
277
|
}
|
|
247
278
|
|
|
279
|
+
function suppressNativeEvent(scope: NonMemoryScope, key: string): void {
|
|
280
|
+
const suppressedEvents = suppressedNativeEvents.get(scope)!;
|
|
281
|
+
suppressedEvents.set(key, (suppressedEvents.get(key) ?? 0) + 1);
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
function consumeSuppressedNativeEvent(
|
|
285
|
+
scope: NonMemoryScope,
|
|
286
|
+
key: string,
|
|
287
|
+
): boolean {
|
|
288
|
+
const suppressedEvents = suppressedNativeEvents.get(scope)!;
|
|
289
|
+
const count = suppressedEvents.get(key);
|
|
290
|
+
if (count === undefined) {
|
|
291
|
+
return false;
|
|
292
|
+
}
|
|
293
|
+
if (count <= 1) {
|
|
294
|
+
suppressedEvents.delete(key);
|
|
295
|
+
} else {
|
|
296
|
+
suppressedEvents.set(key, count - 1);
|
|
297
|
+
}
|
|
298
|
+
return true;
|
|
299
|
+
}
|
|
300
|
+
|
|
248
301
|
function notifyKeyListeners(registry: KeyListenerRegistry, key: string): void {
|
|
249
302
|
const listeners = registry.get(key);
|
|
250
303
|
if (listeners) {
|
|
@@ -286,6 +339,99 @@ function addKeyListener(
|
|
|
286
339
|
};
|
|
287
340
|
}
|
|
288
341
|
|
|
342
|
+
function getEventRawValue(
|
|
343
|
+
scope: StorageScope,
|
|
344
|
+
key: string,
|
|
345
|
+
): string | undefined {
|
|
346
|
+
if (scope === StorageScope.Memory) {
|
|
347
|
+
const value = memoryStore.get(key);
|
|
348
|
+
return typeof value === "string" ? value : undefined;
|
|
349
|
+
}
|
|
350
|
+
|
|
351
|
+
return getRawValue(key, scope);
|
|
352
|
+
}
|
|
353
|
+
|
|
354
|
+
function createKeyChange(
|
|
355
|
+
scope: StorageScope,
|
|
356
|
+
key: string,
|
|
357
|
+
oldValue: string | undefined,
|
|
358
|
+
newValue: string | undefined,
|
|
359
|
+
operation: StorageChangeOperation,
|
|
360
|
+
source: StorageChangeSource,
|
|
361
|
+
): StorageKeyChangeEvent {
|
|
362
|
+
return {
|
|
363
|
+
type: "key",
|
|
364
|
+
scope,
|
|
365
|
+
key,
|
|
366
|
+
oldValue,
|
|
367
|
+
newValue,
|
|
368
|
+
operation,
|
|
369
|
+
source,
|
|
370
|
+
};
|
|
371
|
+
}
|
|
372
|
+
|
|
373
|
+
function hasStorageChangeObservers(scope: StorageScope): boolean {
|
|
374
|
+
return storageEvents.hasListeners(scope) || eventObserver !== undefined;
|
|
375
|
+
}
|
|
376
|
+
|
|
377
|
+
function emitKeyChange(
|
|
378
|
+
scope: StorageScope,
|
|
379
|
+
key: string,
|
|
380
|
+
oldValue: string | undefined,
|
|
381
|
+
newValue: string | undefined,
|
|
382
|
+
operation: StorageChangeOperation,
|
|
383
|
+
source: StorageChangeSource,
|
|
384
|
+
): void {
|
|
385
|
+
if (
|
|
386
|
+
source === "native" &&
|
|
387
|
+
operation !== "external" &&
|
|
388
|
+
scope !== StorageScope.Memory &&
|
|
389
|
+
scopedUnsubscribers.has(scope)
|
|
390
|
+
) {
|
|
391
|
+
suppressNativeEvent(scope, key);
|
|
392
|
+
}
|
|
393
|
+
const event = createKeyChange(
|
|
394
|
+
scope,
|
|
395
|
+
key,
|
|
396
|
+
oldValue,
|
|
397
|
+
newValue,
|
|
398
|
+
operation,
|
|
399
|
+
source,
|
|
400
|
+
);
|
|
401
|
+
storageEvents.emitKey(event);
|
|
402
|
+
eventObserver?.(event);
|
|
403
|
+
}
|
|
404
|
+
|
|
405
|
+
function emitBatchChange(
|
|
406
|
+
scope: StorageScope,
|
|
407
|
+
operation: StorageChangeOperation,
|
|
408
|
+
source: StorageChangeSource,
|
|
409
|
+
changes: StorageKeyChangeEvent[],
|
|
410
|
+
): void {
|
|
411
|
+
if (changes.length === 0) {
|
|
412
|
+
return;
|
|
413
|
+
}
|
|
414
|
+
|
|
415
|
+
if (
|
|
416
|
+
source === "native" &&
|
|
417
|
+
operation !== "external" &&
|
|
418
|
+
scope !== StorageScope.Memory &&
|
|
419
|
+
scopedUnsubscribers.has(scope)
|
|
420
|
+
) {
|
|
421
|
+
changes.forEach((change) => suppressNativeEvent(scope, change.key));
|
|
422
|
+
}
|
|
423
|
+
|
|
424
|
+
const event: StorageBatchChangeEvent = {
|
|
425
|
+
type: "batch",
|
|
426
|
+
scope,
|
|
427
|
+
operation,
|
|
428
|
+
source,
|
|
429
|
+
changes,
|
|
430
|
+
};
|
|
431
|
+
storageEvents.emitBatch(event);
|
|
432
|
+
eventObserver?.(event);
|
|
433
|
+
}
|
|
434
|
+
|
|
289
435
|
function readPendingSecureWrite(key: string): string | undefined {
|
|
290
436
|
return pendingSecureWrites.get(key)?.value;
|
|
291
437
|
}
|
|
@@ -438,15 +584,27 @@ function ensureNativeScopeSubscription(scope: NonMemoryScope): void {
|
|
|
438
584
|
return;
|
|
439
585
|
}
|
|
440
586
|
|
|
587
|
+
const oldValue = readCachedRawValue(scope, key);
|
|
441
588
|
cacheRawValue(scope, key, value);
|
|
442
589
|
notifyKeyListeners(getScopedListeners(scope), key);
|
|
590
|
+
if (consumeSuppressedNativeEvent(scope, key)) {
|
|
591
|
+
return;
|
|
592
|
+
}
|
|
593
|
+
emitKeyChange(scope, key, oldValue, value, "external", "native");
|
|
443
594
|
});
|
|
444
|
-
scopedUnsubscribers.set(
|
|
595
|
+
scopedUnsubscribers.set(
|
|
596
|
+
scope,
|
|
597
|
+
typeof unsubscribe === "function" ? unsubscribe : () => {},
|
|
598
|
+
);
|
|
445
599
|
}
|
|
446
600
|
|
|
447
601
|
function maybeCleanupNativeScopeSubscription(scope: NonMemoryScope): void {
|
|
448
602
|
const listeners = getScopedListeners(scope);
|
|
449
|
-
if (
|
|
603
|
+
if (
|
|
604
|
+
listeners.size > 0 ||
|
|
605
|
+
storageEvents.hasListeners(scope) ||
|
|
606
|
+
eventObserver !== undefined
|
|
607
|
+
) {
|
|
450
608
|
return;
|
|
451
609
|
}
|
|
452
610
|
|
|
@@ -479,9 +637,12 @@ function getRawValue(key: string, scope: StorageScope): string | undefined {
|
|
|
479
637
|
|
|
480
638
|
function setRawValue(key: string, value: string, scope: StorageScope): void {
|
|
481
639
|
assertValidScope(scope);
|
|
640
|
+
const oldValue =
|
|
641
|
+
scope === StorageScope.Memory ? getEventRawValue(scope, key) : undefined;
|
|
482
642
|
if (scope === StorageScope.Memory) {
|
|
483
643
|
memoryStore.set(key, value);
|
|
484
644
|
notifyKeyListeners(memoryListeners, key);
|
|
645
|
+
emitKeyChange(scope, key, oldValue, value, "set", "memory");
|
|
485
646
|
return;
|
|
486
647
|
}
|
|
487
648
|
|
|
@@ -489,6 +650,7 @@ function setRawValue(key: string, value: string, scope: StorageScope): void {
|
|
|
489
650
|
cacheRawValue(scope, key, value);
|
|
490
651
|
if (diskWritesAsync) {
|
|
491
652
|
scheduleDiskWrite(key, value);
|
|
653
|
+
emitKeyChange(scope, key, oldValue, value, "set", "native");
|
|
492
654
|
return;
|
|
493
655
|
}
|
|
494
656
|
|
|
@@ -504,13 +666,16 @@ function setRawValue(key: string, value: string, scope: StorageScope): void {
|
|
|
504
666
|
|
|
505
667
|
getStorageModule().set(key, value, scope);
|
|
506
668
|
cacheRawValue(scope, key, value);
|
|
669
|
+
emitKeyChange(scope, key, oldValue, value, "set", "native");
|
|
507
670
|
}
|
|
508
671
|
|
|
509
672
|
function removeRawValue(key: string, scope: StorageScope): void {
|
|
510
673
|
assertValidScope(scope);
|
|
674
|
+
const oldValue = getEventRawValue(scope, key);
|
|
511
675
|
if (scope === StorageScope.Memory) {
|
|
512
676
|
memoryStore.delete(key);
|
|
513
677
|
notifyKeyListeners(memoryListeners, key);
|
|
678
|
+
emitKeyChange(scope, key, oldValue, undefined, "remove", "memory");
|
|
514
679
|
return;
|
|
515
680
|
}
|
|
516
681
|
|
|
@@ -518,6 +683,7 @@ function removeRawValue(key: string, scope: StorageScope): void {
|
|
|
518
683
|
cacheRawValue(scope, key, undefined);
|
|
519
684
|
if (diskWritesAsync) {
|
|
520
685
|
scheduleDiskWrite(key, undefined);
|
|
686
|
+
emitKeyChange(scope, key, oldValue, undefined, "remove", "native");
|
|
521
687
|
return;
|
|
522
688
|
}
|
|
523
689
|
|
|
@@ -532,6 +698,7 @@ function removeRawValue(key: string, scope: StorageScope): void {
|
|
|
532
698
|
|
|
533
699
|
getStorageModule().remove(key, scope);
|
|
534
700
|
cacheRawValue(scope, key, undefined);
|
|
701
|
+
emitKeyChange(scope, key, oldValue, undefined, "remove", "native");
|
|
535
702
|
}
|
|
536
703
|
|
|
537
704
|
function readMigrationVersion(scope: StorageScope): number {
|
|
@@ -549,11 +716,97 @@ function writeMigrationVersion(scope: StorageScope, version: number): void {
|
|
|
549
716
|
}
|
|
550
717
|
|
|
551
718
|
export const storage = {
|
|
719
|
+
subscribe: (
|
|
720
|
+
scope: StorageScope,
|
|
721
|
+
listener: StorageEventListener,
|
|
722
|
+
): (() => void) => {
|
|
723
|
+
assertValidScope(scope);
|
|
724
|
+
if (scope !== StorageScope.Memory) {
|
|
725
|
+
ensureNativeScopeSubscription(scope);
|
|
726
|
+
const unsubscribe = storageEvents.subscribe(scope, listener);
|
|
727
|
+
return () => {
|
|
728
|
+
unsubscribe();
|
|
729
|
+
maybeCleanupNativeScopeSubscription(scope);
|
|
730
|
+
};
|
|
731
|
+
}
|
|
732
|
+
return storageEvents.subscribe(scope, listener);
|
|
733
|
+
},
|
|
734
|
+
subscribeKey: (
|
|
735
|
+
scope: StorageScope,
|
|
736
|
+
key: string,
|
|
737
|
+
listener: StorageEventListener,
|
|
738
|
+
): (() => void) => {
|
|
739
|
+
assertValidScope(scope);
|
|
740
|
+
if (scope !== StorageScope.Memory) {
|
|
741
|
+
ensureNativeScopeSubscription(scope);
|
|
742
|
+
const unsubscribe = storageEvents.subscribeKey(scope, key, listener);
|
|
743
|
+
return () => {
|
|
744
|
+
unsubscribe();
|
|
745
|
+
maybeCleanupNativeScopeSubscription(scope);
|
|
746
|
+
};
|
|
747
|
+
}
|
|
748
|
+
return storageEvents.subscribeKey(scope, key, listener);
|
|
749
|
+
},
|
|
750
|
+
subscribePrefix: (
|
|
751
|
+
scope: StorageScope,
|
|
752
|
+
prefix: string,
|
|
753
|
+
listener: StorageEventListener,
|
|
754
|
+
): (() => void) => {
|
|
755
|
+
assertValidScope(scope);
|
|
756
|
+
if (scope !== StorageScope.Memory) {
|
|
757
|
+
ensureNativeScopeSubscription(scope);
|
|
758
|
+
const unsubscribe = storageEvents.subscribePrefix(
|
|
759
|
+
scope,
|
|
760
|
+
prefix,
|
|
761
|
+
listener,
|
|
762
|
+
);
|
|
763
|
+
return () => {
|
|
764
|
+
unsubscribe();
|
|
765
|
+
maybeCleanupNativeScopeSubscription(scope);
|
|
766
|
+
};
|
|
767
|
+
}
|
|
768
|
+
return storageEvents.subscribePrefix(scope, prefix, listener);
|
|
769
|
+
},
|
|
770
|
+
subscribeNamespace: (
|
|
771
|
+
namespace: string,
|
|
772
|
+
scope: StorageScope,
|
|
773
|
+
listener: StorageEventListener,
|
|
774
|
+
): (() => void) => {
|
|
775
|
+
return storage.subscribePrefix(scope, prefixKey(namespace, ""), listener);
|
|
776
|
+
},
|
|
777
|
+
setEventObserver: (observer?: StorageEventListener) => {
|
|
778
|
+
eventObserver = observer;
|
|
779
|
+
if (observer) {
|
|
780
|
+
ensureNativeScopeSubscription(StorageScope.Disk);
|
|
781
|
+
ensureNativeScopeSubscription(StorageScope.Secure);
|
|
782
|
+
return;
|
|
783
|
+
}
|
|
784
|
+
maybeCleanupNativeScopeSubscription(StorageScope.Disk);
|
|
785
|
+
maybeCleanupNativeScopeSubscription(StorageScope.Secure);
|
|
786
|
+
},
|
|
552
787
|
clear: (scope: StorageScope) => {
|
|
553
788
|
measureOperation("storage:clear", scope, () => {
|
|
789
|
+
const previousValues = hasStorageChangeObservers(scope)
|
|
790
|
+
? storage.getAll(scope)
|
|
791
|
+
: {};
|
|
554
792
|
if (scope === StorageScope.Memory) {
|
|
555
793
|
memoryStore.clear();
|
|
556
794
|
notifyAllListeners(memoryListeners);
|
|
795
|
+
emitBatchChange(
|
|
796
|
+
scope,
|
|
797
|
+
"clear",
|
|
798
|
+
"memory",
|
|
799
|
+
Object.keys(previousValues).map((key) =>
|
|
800
|
+
createKeyChange(
|
|
801
|
+
scope,
|
|
802
|
+
key,
|
|
803
|
+
previousValues[key],
|
|
804
|
+
undefined,
|
|
805
|
+
"clear",
|
|
806
|
+
"memory",
|
|
807
|
+
),
|
|
808
|
+
),
|
|
809
|
+
);
|
|
557
810
|
return;
|
|
558
811
|
}
|
|
559
812
|
|
|
@@ -569,6 +822,21 @@ export const storage = {
|
|
|
569
822
|
|
|
570
823
|
clearScopeRawCache(scope);
|
|
571
824
|
getStorageModule().clear(scope);
|
|
825
|
+
emitBatchChange(
|
|
826
|
+
scope,
|
|
827
|
+
"clear",
|
|
828
|
+
"native",
|
|
829
|
+
Object.keys(previousValues).map((key) =>
|
|
830
|
+
createKeyChange(
|
|
831
|
+
scope,
|
|
832
|
+
key,
|
|
833
|
+
previousValues[key],
|
|
834
|
+
undefined,
|
|
835
|
+
"clear",
|
|
836
|
+
"native",
|
|
837
|
+
),
|
|
838
|
+
),
|
|
839
|
+
);
|
|
572
840
|
});
|
|
573
841
|
},
|
|
574
842
|
clearAll: () => {
|
|
@@ -587,16 +855,44 @@ export const storage = {
|
|
|
587
855
|
measureOperation("storage:clearNamespace", scope, () => {
|
|
588
856
|
assertValidScope(scope);
|
|
589
857
|
if (scope === StorageScope.Memory) {
|
|
590
|
-
|
|
591
|
-
|
|
592
|
-
|
|
593
|
-
|
|
858
|
+
const affectedKeys = Array.from(memoryStore.keys()).filter((key) =>
|
|
859
|
+
isNamespaced(key, namespace),
|
|
860
|
+
);
|
|
861
|
+
const previousValues = affectedKeys.map((key) => ({
|
|
862
|
+
key,
|
|
863
|
+
value: getEventRawValue(scope, key),
|
|
864
|
+
}));
|
|
865
|
+
|
|
866
|
+
if (affectedKeys.length === 0) {
|
|
867
|
+
return;
|
|
594
868
|
}
|
|
595
|
-
|
|
869
|
+
|
|
870
|
+
affectedKeys.forEach((key) => {
|
|
871
|
+
memoryStore.delete(key);
|
|
872
|
+
});
|
|
873
|
+
affectedKeys.forEach((key) => notifyKeyListeners(memoryListeners, key));
|
|
874
|
+
emitBatchChange(
|
|
875
|
+
scope,
|
|
876
|
+
"clearNamespace",
|
|
877
|
+
"memory",
|
|
878
|
+
previousValues.map(({ key, value }) =>
|
|
879
|
+
createKeyChange(
|
|
880
|
+
scope,
|
|
881
|
+
key,
|
|
882
|
+
value,
|
|
883
|
+
undefined,
|
|
884
|
+
"clearNamespace",
|
|
885
|
+
"memory",
|
|
886
|
+
),
|
|
887
|
+
),
|
|
888
|
+
);
|
|
596
889
|
return;
|
|
597
890
|
}
|
|
598
891
|
|
|
599
892
|
const keyPrefix = prefixKey(namespace, "");
|
|
893
|
+
const previousValues = hasStorageChangeObservers(scope)
|
|
894
|
+
? storage.getByPrefix(keyPrefix, scope)
|
|
895
|
+
: {};
|
|
600
896
|
if (scope === StorageScope.Disk) {
|
|
601
897
|
flushDiskWrites();
|
|
602
898
|
}
|
|
@@ -611,6 +907,21 @@ export const storage = {
|
|
|
611
907
|
}
|
|
612
908
|
}
|
|
613
909
|
getStorageModule().removeByPrefix(keyPrefix, scope);
|
|
910
|
+
emitBatchChange(
|
|
911
|
+
scope,
|
|
912
|
+
"clearNamespace",
|
|
913
|
+
"native",
|
|
914
|
+
Object.keys(previousValues).map((key) =>
|
|
915
|
+
createKeyChange(
|
|
916
|
+
scope,
|
|
917
|
+
key,
|
|
918
|
+
previousValues[key],
|
|
919
|
+
undefined,
|
|
920
|
+
"clearNamespace",
|
|
921
|
+
"native",
|
|
922
|
+
),
|
|
923
|
+
),
|
|
924
|
+
);
|
|
614
925
|
});
|
|
615
926
|
},
|
|
616
927
|
clearBiometric: () => {
|
|
@@ -662,7 +973,7 @@ export const storage = {
|
|
|
662
973
|
if (scope === StorageScope.Secure) {
|
|
663
974
|
flushSecureWrites();
|
|
664
975
|
}
|
|
665
|
-
return getStorageModule().getKeysByPrefix(prefix, scope);
|
|
976
|
+
return getStorageModule().getKeysByPrefix(prefix, scope) ?? [];
|
|
666
977
|
});
|
|
667
978
|
},
|
|
668
979
|
getByPrefix: (
|
|
@@ -692,7 +1003,7 @@ export const storage = {
|
|
|
692
1003
|
if (scope === StorageScope.Secure) {
|
|
693
1004
|
flushSecureWrites();
|
|
694
1005
|
}
|
|
695
|
-
const values = getStorageModule().getBatch(keys, scope);
|
|
1006
|
+
const values = getStorageModule().getBatch(keys, scope) ?? [];
|
|
696
1007
|
keys.forEach((key, idx) => {
|
|
697
1008
|
const value = decodeNativeBatchValue(values[idx]);
|
|
698
1009
|
if (value !== undefined) {
|
|
@@ -707,9 +1018,10 @@ export const storage = {
|
|
|
707
1018
|
assertValidScope(scope);
|
|
708
1019
|
const result: Record<string, string> = {};
|
|
709
1020
|
if (scope === StorageScope.Memory) {
|
|
710
|
-
memoryStore.
|
|
1021
|
+
for (const key of memoryStore.keys()) {
|
|
1022
|
+
const value = memoryStore.get(key);
|
|
711
1023
|
if (typeof value === "string") result[key] = value;
|
|
712
|
-
}
|
|
1024
|
+
}
|
|
713
1025
|
return result;
|
|
714
1026
|
}
|
|
715
1027
|
if (scope === StorageScope.Disk) {
|
|
@@ -718,9 +1030,9 @@ export const storage = {
|
|
|
718
1030
|
if (scope === StorageScope.Secure) {
|
|
719
1031
|
flushSecureWrites();
|
|
720
1032
|
}
|
|
721
|
-
const keys = getStorageModule().getAllKeys(scope);
|
|
1033
|
+
const keys = getStorageModule().getAllKeys(scope) ?? [];
|
|
722
1034
|
if (keys.length === 0) return result;
|
|
723
|
-
const values = getStorageModule().getBatch(keys, scope);
|
|
1035
|
+
const values = getStorageModule().getBatch(keys, scope) ?? [];
|
|
724
1036
|
keys.forEach((key, idx) => {
|
|
725
1037
|
const val = decodeNativeBatchValue(values[idx]);
|
|
726
1038
|
if (val !== undefined) result[key] = val;
|
|
@@ -728,6 +1040,11 @@ export const storage = {
|
|
|
728
1040
|
return result;
|
|
729
1041
|
});
|
|
730
1042
|
},
|
|
1043
|
+
export: (scope: StorageScope): Record<string, string> => {
|
|
1044
|
+
return measureOperation("storage:export", scope, () =>
|
|
1045
|
+
storage.getAll(scope),
|
|
1046
|
+
);
|
|
1047
|
+
},
|
|
731
1048
|
size: (scope: StorageScope): number => {
|
|
732
1049
|
return measureOperation("storage:size", scope, () => {
|
|
733
1050
|
assertValidScope(scope);
|
|
@@ -901,12 +1218,23 @@ export const storage = {
|
|
|
901
1218
|
assertValidScope(scope);
|
|
902
1219
|
if (keys.length === 0) return;
|
|
903
1220
|
const values = keys.map((k) => data[k]!);
|
|
1221
|
+
const changes = keys.map((key, index) =>
|
|
1222
|
+
createKeyChange(
|
|
1223
|
+
scope,
|
|
1224
|
+
key,
|
|
1225
|
+
getEventRawValue(scope, key),
|
|
1226
|
+
values[index],
|
|
1227
|
+
"import",
|
|
1228
|
+
scope === StorageScope.Memory ? "memory" : "native",
|
|
1229
|
+
),
|
|
1230
|
+
);
|
|
904
1231
|
|
|
905
1232
|
if (scope === StorageScope.Memory) {
|
|
906
1233
|
keys.forEach((key, index) => {
|
|
907
1234
|
memoryStore.set(key, values[index]);
|
|
908
1235
|
});
|
|
909
1236
|
keys.forEach((key) => notifyKeyListeners(memoryListeners, key));
|
|
1237
|
+
emitBatchChange(scope, "import", "memory", changes);
|
|
910
1238
|
return;
|
|
911
1239
|
}
|
|
912
1240
|
|
|
@@ -917,6 +1245,7 @@ export const storage = {
|
|
|
917
1245
|
|
|
918
1246
|
getStorageModule().setBatch(keys, values, scope);
|
|
919
1247
|
keys.forEach((key, index) => cacheRawValue(scope, key, values[index]));
|
|
1248
|
+
emitBatchChange(scope, "import", "native", changes);
|
|
920
1249
|
},
|
|
921
1250
|
keys.length,
|
|
922
1251
|
);
|
|
@@ -979,6 +1308,11 @@ export interface StorageItem<T> {
|
|
|
979
1308
|
delete: () => void;
|
|
980
1309
|
has: () => boolean;
|
|
981
1310
|
subscribe: (callback: () => void) => () => void;
|
|
1311
|
+
subscribeSelector: <TSelected>(
|
|
1312
|
+
selector: (value: T) => TSelected,
|
|
1313
|
+
listener: StorageSelectorListener<TSelected>,
|
|
1314
|
+
options?: StorageSelectorSubscribeOptions<TSelected>,
|
|
1315
|
+
) => () => void;
|
|
982
1316
|
serialize: (value: T) => string;
|
|
983
1317
|
deserialize: (value: string) => T;
|
|
984
1318
|
scope: StorageScope;
|
|
@@ -1147,12 +1481,21 @@ export function createStorageItem<T = undefined>(
|
|
|
1147
1481
|
};
|
|
1148
1482
|
|
|
1149
1483
|
const writeStoredRaw = (rawValue: string): void => {
|
|
1484
|
+
const oldValue = undefined;
|
|
1150
1485
|
if (isBiometric) {
|
|
1151
1486
|
getStorageModule().setSecureBiometricWithLevel(
|
|
1152
1487
|
storageKey,
|
|
1153
1488
|
rawValue,
|
|
1154
1489
|
resolvedBiometricLevel,
|
|
1155
1490
|
);
|
|
1491
|
+
emitKeyChange(
|
|
1492
|
+
config.scope,
|
|
1493
|
+
storageKey,
|
|
1494
|
+
oldValue,
|
|
1495
|
+
rawValue,
|
|
1496
|
+
"set",
|
|
1497
|
+
"native",
|
|
1498
|
+
);
|
|
1156
1499
|
return;
|
|
1157
1500
|
}
|
|
1158
1501
|
|
|
@@ -1161,6 +1504,14 @@ export function createStorageItem<T = undefined>(
|
|
|
1161
1504
|
if (nonMemoryScope === StorageScope.Disk) {
|
|
1162
1505
|
if (coalesceDiskWrites || diskWritesAsync) {
|
|
1163
1506
|
scheduleDiskWrite(storageKey, rawValue);
|
|
1507
|
+
emitKeyChange(
|
|
1508
|
+
config.scope,
|
|
1509
|
+
storageKey,
|
|
1510
|
+
oldValue,
|
|
1511
|
+
rawValue,
|
|
1512
|
+
"set",
|
|
1513
|
+
"native",
|
|
1514
|
+
);
|
|
1164
1515
|
return;
|
|
1165
1516
|
}
|
|
1166
1517
|
|
|
@@ -1173,6 +1524,14 @@ export function createStorageItem<T = undefined>(
|
|
|
1173
1524
|
rawValue,
|
|
1174
1525
|
secureAccessControl ?? secureDefaultAccessControl,
|
|
1175
1526
|
);
|
|
1527
|
+
emitKeyChange(
|
|
1528
|
+
config.scope,
|
|
1529
|
+
storageKey,
|
|
1530
|
+
oldValue,
|
|
1531
|
+
rawValue,
|
|
1532
|
+
"set",
|
|
1533
|
+
"native",
|
|
1534
|
+
);
|
|
1176
1535
|
return;
|
|
1177
1536
|
}
|
|
1178
1537
|
|
|
@@ -1184,11 +1543,28 @@ export function createStorageItem<T = undefined>(
|
|
|
1184
1543
|
}
|
|
1185
1544
|
|
|
1186
1545
|
getStorageModule().set(storageKey, rawValue, config.scope);
|
|
1546
|
+
emitKeyChange(
|
|
1547
|
+
config.scope,
|
|
1548
|
+
storageKey,
|
|
1549
|
+
oldValue,
|
|
1550
|
+
rawValue,
|
|
1551
|
+
"set",
|
|
1552
|
+
"native",
|
|
1553
|
+
);
|
|
1187
1554
|
};
|
|
1188
1555
|
|
|
1189
1556
|
const removeStoredRaw = (): void => {
|
|
1557
|
+
const oldValue = getEventRawValue(config.scope, storageKey);
|
|
1190
1558
|
if (isBiometric) {
|
|
1191
1559
|
getStorageModule().deleteSecureBiometric(storageKey);
|
|
1560
|
+
emitKeyChange(
|
|
1561
|
+
config.scope,
|
|
1562
|
+
storageKey,
|
|
1563
|
+
oldValue,
|
|
1564
|
+
undefined,
|
|
1565
|
+
"remove",
|
|
1566
|
+
"native",
|
|
1567
|
+
);
|
|
1192
1568
|
return;
|
|
1193
1569
|
}
|
|
1194
1570
|
|
|
@@ -1197,6 +1573,14 @@ export function createStorageItem<T = undefined>(
|
|
|
1197
1573
|
if (nonMemoryScope === StorageScope.Disk) {
|
|
1198
1574
|
if (coalesceDiskWrites || diskWritesAsync) {
|
|
1199
1575
|
scheduleDiskWrite(storageKey, undefined);
|
|
1576
|
+
emitKeyChange(
|
|
1577
|
+
config.scope,
|
|
1578
|
+
storageKey,
|
|
1579
|
+
oldValue,
|
|
1580
|
+
undefined,
|
|
1581
|
+
"remove",
|
|
1582
|
+
"native",
|
|
1583
|
+
);
|
|
1200
1584
|
return;
|
|
1201
1585
|
}
|
|
1202
1586
|
|
|
@@ -1209,6 +1593,14 @@ export function createStorageItem<T = undefined>(
|
|
|
1209
1593
|
undefined,
|
|
1210
1594
|
secureAccessControl ?? secureDefaultAccessControl,
|
|
1211
1595
|
);
|
|
1596
|
+
emitKeyChange(
|
|
1597
|
+
config.scope,
|
|
1598
|
+
storageKey,
|
|
1599
|
+
oldValue,
|
|
1600
|
+
undefined,
|
|
1601
|
+
"remove",
|
|
1602
|
+
"native",
|
|
1603
|
+
);
|
|
1212
1604
|
return;
|
|
1213
1605
|
}
|
|
1214
1606
|
|
|
@@ -1217,15 +1609,32 @@ export function createStorageItem<T = undefined>(
|
|
|
1217
1609
|
}
|
|
1218
1610
|
|
|
1219
1611
|
getStorageModule().remove(storageKey, config.scope);
|
|
1612
|
+
emitKeyChange(
|
|
1613
|
+
config.scope,
|
|
1614
|
+
storageKey,
|
|
1615
|
+
oldValue,
|
|
1616
|
+
undefined,
|
|
1617
|
+
"remove",
|
|
1618
|
+
"native",
|
|
1619
|
+
);
|
|
1220
1620
|
};
|
|
1221
1621
|
|
|
1222
1622
|
const writeValueWithoutValidation = (value: T): void => {
|
|
1223
1623
|
if (isMemory) {
|
|
1624
|
+
const oldValue = getEventRawValue(config.scope, storageKey);
|
|
1224
1625
|
if (memoryExpiration) {
|
|
1225
1626
|
memoryExpiration.set(storageKey, Date.now() + (expirationTtlMs ?? 0));
|
|
1226
1627
|
}
|
|
1227
1628
|
memoryStore.set(storageKey, value);
|
|
1228
1629
|
notifyKeyListeners(memoryListeners, storageKey);
|
|
1630
|
+
emitKeyChange(
|
|
1631
|
+
config.scope,
|
|
1632
|
+
storageKey,
|
|
1633
|
+
oldValue,
|
|
1634
|
+
typeof value === "string" ? value : undefined,
|
|
1635
|
+
"set",
|
|
1636
|
+
"memory",
|
|
1637
|
+
);
|
|
1229
1638
|
return;
|
|
1230
1639
|
}
|
|
1231
1640
|
|
|
@@ -1397,11 +1806,20 @@ export function createStorageItem<T = undefined>(
|
|
|
1397
1806
|
invalidateParsedCache();
|
|
1398
1807
|
|
|
1399
1808
|
if (isMemory) {
|
|
1809
|
+
const oldValue = getEventRawValue(config.scope, storageKey);
|
|
1400
1810
|
if (memoryExpiration) {
|
|
1401
1811
|
memoryExpiration.delete(storageKey);
|
|
1402
1812
|
}
|
|
1403
1813
|
memoryStore.delete(storageKey);
|
|
1404
1814
|
notifyKeyListeners(memoryListeners, storageKey);
|
|
1815
|
+
emitKeyChange(
|
|
1816
|
+
config.scope,
|
|
1817
|
+
storageKey,
|
|
1818
|
+
oldValue,
|
|
1819
|
+
undefined,
|
|
1820
|
+
"remove",
|
|
1821
|
+
"memory",
|
|
1822
|
+
);
|
|
1405
1823
|
return;
|
|
1406
1824
|
}
|
|
1407
1825
|
|
|
@@ -1443,6 +1861,30 @@ export function createStorageItem<T = undefined>(
|
|
|
1443
1861
|
};
|
|
1444
1862
|
};
|
|
1445
1863
|
|
|
1864
|
+
const subscribeSelector = <TSelected>(
|
|
1865
|
+
selector: (value: T) => TSelected,
|
|
1866
|
+
listener: StorageSelectorListener<TSelected>,
|
|
1867
|
+
options: StorageSelectorSubscribeOptions<TSelected> = {},
|
|
1868
|
+
): (() => void) => {
|
|
1869
|
+
const isEqual = options.isEqual ?? Object.is;
|
|
1870
|
+
let currentValue = selector(getInternal());
|
|
1871
|
+
|
|
1872
|
+
if (options.fireImmediately === true) {
|
|
1873
|
+
listener(currentValue, currentValue);
|
|
1874
|
+
}
|
|
1875
|
+
|
|
1876
|
+
return subscribe(() => {
|
|
1877
|
+
const nextValue = selector(getInternal());
|
|
1878
|
+
if (isEqual(currentValue, nextValue)) {
|
|
1879
|
+
return;
|
|
1880
|
+
}
|
|
1881
|
+
|
|
1882
|
+
const previousValue = currentValue;
|
|
1883
|
+
currentValue = nextValue;
|
|
1884
|
+
listener(nextValue, previousValue);
|
|
1885
|
+
});
|
|
1886
|
+
};
|
|
1887
|
+
|
|
1446
1888
|
const storageItem: StorageItemInternal<T> = {
|
|
1447
1889
|
get,
|
|
1448
1890
|
getWithVersion,
|
|
@@ -1451,6 +1893,7 @@ export function createStorageItem<T = undefined>(
|
|
|
1451
1893
|
delete: deleteItem,
|
|
1452
1894
|
has: hasItem,
|
|
1453
1895
|
subscribe,
|
|
1896
|
+
subscribeSelector,
|
|
1454
1897
|
serialize,
|
|
1455
1898
|
deserialize,
|
|
1456
1899
|
_triggerListeners: () => {
|
|
@@ -1607,6 +2050,17 @@ export function setBatch<T>(
|
|
|
1607
2050
|
return;
|
|
1608
2051
|
}
|
|
1609
2052
|
|
|
2053
|
+
const changes = items.map(({ item, value }) =>
|
|
2054
|
+
createKeyChange(
|
|
2055
|
+
scope,
|
|
2056
|
+
item.key,
|
|
2057
|
+
getEventRawValue(scope, item.key),
|
|
2058
|
+
typeof value === "string" ? value : undefined,
|
|
2059
|
+
"setBatch",
|
|
2060
|
+
"memory",
|
|
2061
|
+
),
|
|
2062
|
+
);
|
|
2063
|
+
|
|
1610
2064
|
// Atomic write: update all values in memoryStore, invalidate caches, then batch-notify
|
|
1611
2065
|
items.forEach(({ item, value }) => {
|
|
1612
2066
|
memoryStore.set(item.key, value);
|
|
@@ -1615,6 +2069,7 @@ export function setBatch<T>(
|
|
|
1615
2069
|
items.forEach(({ item }) =>
|
|
1616
2070
|
notifyKeyListeners(memoryListeners, item.key),
|
|
1617
2071
|
);
|
|
2072
|
+
emitBatchChange(scope, "setBatch", "memory", changes);
|
|
1618
2073
|
return;
|
|
1619
2074
|
}
|
|
1620
2075
|
|
|
@@ -1634,6 +2089,10 @@ export function setBatch<T>(
|
|
|
1634
2089
|
|
|
1635
2090
|
flushSecureWrites();
|
|
1636
2091
|
const storageModule = getStorageModule();
|
|
2092
|
+
const keys = secureEntries.map(({ item }) => item.key);
|
|
2093
|
+
const oldValues = hasStorageChangeObservers(scope)
|
|
2094
|
+
? (storageModule.getBatch(keys, scope) ?? [])
|
|
2095
|
+
: [];
|
|
1637
2096
|
const groupedByAccessControl = new Map<
|
|
1638
2097
|
number,
|
|
1639
2098
|
{ keys: string[]; values: string[] }
|
|
@@ -1658,6 +2117,21 @@ export function setBatch<T>(
|
|
|
1658
2117
|
cacheRawValue(scope, key, group.values[index]),
|
|
1659
2118
|
);
|
|
1660
2119
|
});
|
|
2120
|
+
emitBatchChange(
|
|
2121
|
+
scope,
|
|
2122
|
+
"setBatch",
|
|
2123
|
+
"native",
|
|
2124
|
+
secureEntries.map(({ item, value }, index) =>
|
|
2125
|
+
createKeyChange(
|
|
2126
|
+
scope,
|
|
2127
|
+
item.key,
|
|
2128
|
+
oldValues[index],
|
|
2129
|
+
item.serialize(value),
|
|
2130
|
+
"setBatch",
|
|
2131
|
+
"native",
|
|
2132
|
+
),
|
|
2133
|
+
),
|
|
2134
|
+
);
|
|
1661
2135
|
return;
|
|
1662
2136
|
}
|
|
1663
2137
|
|
|
@@ -1673,9 +2147,27 @@ export function setBatch<T>(
|
|
|
1673
2147
|
|
|
1674
2148
|
const keys = items.map((entry) => entry.item.key);
|
|
1675
2149
|
const values = items.map((entry) => entry.item.serialize(entry.value));
|
|
2150
|
+
const oldValues = hasStorageChangeObservers(scope)
|
|
2151
|
+
? (getStorageModule().getBatch(keys, scope) ?? [])
|
|
2152
|
+
: [];
|
|
1676
2153
|
|
|
1677
2154
|
getStorageModule().setBatch(keys, values, scope);
|
|
1678
2155
|
keys.forEach((key, index) => cacheRawValue(scope, key, values[index]));
|
|
2156
|
+
emitBatchChange(
|
|
2157
|
+
scope,
|
|
2158
|
+
"setBatch",
|
|
2159
|
+
"native",
|
|
2160
|
+
keys.map((key, index) =>
|
|
2161
|
+
createKeyChange(
|
|
2162
|
+
scope,
|
|
2163
|
+
key,
|
|
2164
|
+
oldValues[index],
|
|
2165
|
+
values[index],
|
|
2166
|
+
"setBatch",
|
|
2167
|
+
"native",
|
|
2168
|
+
),
|
|
2169
|
+
),
|
|
2170
|
+
);
|
|
1679
2171
|
},
|
|
1680
2172
|
items.length,
|
|
1681
2173
|
);
|
|
@@ -1692,7 +2184,18 @@ export function removeBatch(
|
|
|
1692
2184
|
assertBatchScope(items, scope);
|
|
1693
2185
|
|
|
1694
2186
|
if (scope === StorageScope.Memory) {
|
|
2187
|
+
const changes = items.map((item) =>
|
|
2188
|
+
createKeyChange(
|
|
2189
|
+
scope,
|
|
2190
|
+
item.key,
|
|
2191
|
+
getEventRawValue(scope, item.key),
|
|
2192
|
+
undefined,
|
|
2193
|
+
"removeBatch",
|
|
2194
|
+
"memory",
|
|
2195
|
+
),
|
|
2196
|
+
);
|
|
1695
2197
|
items.forEach((item) => item.delete());
|
|
2198
|
+
emitBatchChange(scope, "removeBatch", "memory", changes);
|
|
1696
2199
|
return;
|
|
1697
2200
|
}
|
|
1698
2201
|
|
|
@@ -1703,8 +2206,26 @@ export function removeBatch(
|
|
|
1703
2206
|
if (scope === StorageScope.Secure) {
|
|
1704
2207
|
flushSecureWrites();
|
|
1705
2208
|
}
|
|
2209
|
+
const oldValues = hasStorageChangeObservers(scope)
|
|
2210
|
+
? (getStorageModule().getBatch(keys, scope) ?? [])
|
|
2211
|
+
: [];
|
|
1706
2212
|
getStorageModule().removeBatch(keys, scope);
|
|
1707
2213
|
keys.forEach((key) => cacheRawValue(scope, key, undefined));
|
|
2214
|
+
emitBatchChange(
|
|
2215
|
+
scope,
|
|
2216
|
+
"removeBatch",
|
|
2217
|
+
"native",
|
|
2218
|
+
keys.map((key, index) =>
|
|
2219
|
+
createKeyChange(
|
|
2220
|
+
scope,
|
|
2221
|
+
key,
|
|
2222
|
+
oldValues[index],
|
|
2223
|
+
undefined,
|
|
2224
|
+
"removeBatch",
|
|
2225
|
+
"native",
|
|
2226
|
+
),
|
|
2227
|
+
),
|
|
2228
|
+
);
|
|
1708
2229
|
},
|
|
1709
2230
|
items.length,
|
|
1710
2231
|
);
|