react-native-nitro-storage 0.7.0 → 0.8.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 +32 -0
- package/README.md +20 -9
- package/SECURITY.md +2 -2
- package/docs/api-reference.md +11 -1
- package/docs/batch-transactions-migrations.md +8 -2
- package/docs/benchmarks.md +19 -15
- package/docs/web-backends.md +18 -1
- package/ios/IOSStorageAdapterCpp.mm +162 -17
- package/lib/commonjs/capabilities.js +26 -0
- package/lib/commonjs/capabilities.js.map +1 -0
- package/lib/commonjs/core/durability.js +137 -0
- package/lib/commonjs/core/durability.js.map +1 -0
- package/lib/commonjs/core/metrics.js +91 -0
- package/lib/commonjs/core/metrics.js.map +1 -0
- package/lib/commonjs/index.js +47 -22
- package/lib/commonjs/index.js.map +1 -1
- package/lib/commonjs/index.web.js +118 -66
- package/lib/commonjs/index.web.js.map +1 -1
- package/lib/commonjs/indexeddb-backend.js +73 -23
- package/lib/commonjs/indexeddb-backend.js.map +1 -1
- package/lib/commonjs/internal.js +27 -4
- package/lib/commonjs/internal.js.map +1 -1
- package/lib/commonjs/shared.js +2 -2
- package/lib/commonjs/shared.js.map +1 -1
- package/lib/commonjs/storage-core.js +217 -233
- package/lib/commonjs/storage-core.js.map +1 -1
- package/lib/commonjs/storage-events.js.map +1 -1
- package/lib/commonjs/storage-platform.js +6 -0
- package/lib/commonjs/storage-platform.js.map +1 -0
- package/lib/commonjs/storage-runtime.js +3 -21
- package/lib/commonjs/storage-runtime.js.map +1 -1
- package/lib/commonjs/testing.js +19 -4
- package/lib/commonjs/testing.js.map +1 -1
- package/lib/commonjs/web-backend-contract.js +27 -0
- package/lib/commonjs/web-backend-contract.js.map +1 -0
- package/lib/module/capabilities.js +21 -0
- package/lib/module/capabilities.js.map +1 -0
- package/lib/module/core/durability.js +133 -0
- package/lib/module/core/durability.js.map +1 -0
- package/lib/module/core/metrics.js +87 -0
- package/lib/module/core/metrics.js.map +1 -0
- package/lib/module/index.js +48 -23
- package/lib/module/index.js.map +1 -1
- package/lib/module/index.web.js +103 -64
- package/lib/module/index.web.js.map +1 -1
- package/lib/module/indexeddb-backend.js +73 -23
- package/lib/module/indexeddb-backend.js.map +1 -1
- package/lib/module/internal.js +24 -3
- package/lib/module/internal.js.map +1 -1
- package/lib/module/shared.js +2 -2
- package/lib/module/shared.js.map +1 -1
- package/lib/module/storage-core.js +218 -234
- package/lib/module/storage-core.js.map +1 -1
- package/lib/module/storage-events.js.map +1 -1
- package/lib/module/storage-platform.js +4 -0
- package/lib/module/storage-platform.js.map +1 -0
- package/lib/module/storage-runtime.js +3 -21
- package/lib/module/storage-runtime.js.map +1 -1
- package/lib/module/testing.js +5 -3
- package/lib/module/testing.js.map +1 -1
- package/lib/module/web-backend-contract.js +22 -0
- package/lib/module/web-backend-contract.js.map +1 -0
- package/lib/typescript/capabilities.d.ts +11 -0
- package/lib/typescript/capabilities.d.ts.map +1 -0
- package/lib/typescript/core/durability.d.ts +27 -0
- package/lib/typescript/core/durability.d.ts.map +1 -0
- package/lib/typescript/core/metrics.d.ts +12 -0
- package/lib/typescript/core/metrics.d.ts.map +1 -0
- package/lib/typescript/index.d.ts +5 -2
- package/lib/typescript/index.d.ts.map +1 -1
- package/lib/typescript/index.web.d.ts +8 -4
- package/lib/typescript/index.web.d.ts.map +1 -1
- package/lib/typescript/indexeddb-backend.d.ts.map +1 -1
- package/lib/typescript/internal.d.ts +3 -0
- package/lib/typescript/internal.d.ts.map +1 -1
- package/lib/typescript/shared.d.ts +2 -1
- package/lib/typescript/shared.d.ts.map +1 -1
- package/lib/typescript/storage-core.d.ts +3 -3
- package/lib/typescript/storage-core.d.ts.map +1 -1
- package/lib/typescript/storage-events.d.ts +2 -2
- package/lib/typescript/storage-events.d.ts.map +1 -1
- package/lib/typescript/storage-platform.d.ts +12 -0
- package/lib/typescript/storage-platform.d.ts.map +1 -0
- package/lib/typescript/storage-runtime.d.ts.map +1 -1
- package/lib/typescript/testing.d.ts +5 -1
- package/lib/typescript/testing.d.ts.map +1 -1
- package/lib/typescript/web-backend-contract.d.ts +10 -0
- package/lib/typescript/web-backend-contract.d.ts.map +1 -0
- package/lib/typescript/web-storage-backend.d.ts +1 -1
- package/lib/typescript/web-storage-backend.d.ts.map +1 -1
- package/package.json +3 -2
- package/src/capabilities.ts +36 -0
- package/src/core/durability.ts +170 -0
- package/src/core/metrics.ts +133 -0
- package/src/index.ts +66 -40
- package/src/index.web.ts +158 -86
- package/src/indexeddb-backend.ts +91 -20
- package/src/internal.ts +50 -4
- package/src/shared.ts +7 -6
- package/src/storage-core.ts +317 -285
- package/src/storage-events.ts +2 -1
- package/src/storage-platform.ts +16 -0
- package/src/storage-runtime.ts +13 -47
- package/src/testing.ts +14 -7
- package/src/web-backend-contract.ts +34 -0
- package/src/web-storage-backend.ts +1 -1
package/src/storage-core.ts
CHANGED
|
@@ -1,3 +1,17 @@
|
|
|
1
|
+
import { createDurabilityCoordinator } from "./core/durability";
|
|
2
|
+
import { createMetricsRegistry } from "./core/metrics";
|
|
3
|
+
import {
|
|
4
|
+
MIGRATION_VERSION_KEY,
|
|
5
|
+
type StoredEnvelope,
|
|
6
|
+
isStoredEnvelope,
|
|
7
|
+
assertBatchScope,
|
|
8
|
+
assertValidScope,
|
|
9
|
+
toVersionToken,
|
|
10
|
+
prefixKey,
|
|
11
|
+
isNamespaced,
|
|
12
|
+
escapeCollidingRawValue,
|
|
13
|
+
unescapeCollidingRawValue,
|
|
14
|
+
} from "./internal";
|
|
1
15
|
import {
|
|
2
16
|
assertAccessControlLevel,
|
|
3
17
|
assertBiometricLevel,
|
|
@@ -10,17 +24,12 @@ import {
|
|
|
10
24
|
isUpdater,
|
|
11
25
|
notifyAllListeners,
|
|
12
26
|
notifyKeyListeners,
|
|
13
|
-
now,
|
|
14
27
|
redactSecureKeyChange,
|
|
15
|
-
runMicrotask,
|
|
16
28
|
typedKeys,
|
|
17
29
|
type ExpirationConfig,
|
|
18
30
|
type KeyListenerRegistry,
|
|
19
31
|
type Migration,
|
|
20
|
-
type MigrationContext,
|
|
21
32
|
type NonMemoryScope,
|
|
22
|
-
type PendingDiskWrite,
|
|
23
|
-
type PendingSecureWrite,
|
|
24
33
|
type RollbackRecord,
|
|
25
34
|
type SecureAuthStorageConfig,
|
|
26
35
|
type StorageEventObserverOptions,
|
|
@@ -33,18 +42,6 @@ import {
|
|
|
33
42
|
type Validator,
|
|
34
43
|
type VersionedValue,
|
|
35
44
|
} from "./shared";
|
|
36
|
-
import { StorageScope, AccessControl, BiometricLevel } from "./Storage.types";
|
|
37
|
-
import {
|
|
38
|
-
MIGRATION_VERSION_KEY,
|
|
39
|
-
type StoredEnvelope,
|
|
40
|
-
isStoredEnvelope,
|
|
41
|
-
assertBatchScope,
|
|
42
|
-
assertValidScope,
|
|
43
|
-
toVersionToken,
|
|
44
|
-
prefixKey,
|
|
45
|
-
isNamespaced,
|
|
46
|
-
} from "./internal";
|
|
47
|
-
import type { SecureStorageMetadata } from "./storage-runtime";
|
|
48
45
|
import {
|
|
49
46
|
StorageEventRegistry,
|
|
50
47
|
type StorageBatchChangeEvent,
|
|
@@ -55,6 +52,8 @@ import {
|
|
|
55
52
|
type StorageKeyChangeEvent,
|
|
56
53
|
} from "./storage-events";
|
|
57
54
|
import type { StorageSetter } from "./storage-hooks";
|
|
55
|
+
import type { SecureStorageMetadata } from "./storage-runtime";
|
|
56
|
+
import { StorageScope, AccessControl, BiometricLevel } from "./Storage.types";
|
|
58
57
|
|
|
59
58
|
export type TransactionContext = {
|
|
60
59
|
scope: StorageScope;
|
|
@@ -122,6 +121,7 @@ export type StorageItem<T> = {
|
|
|
122
121
|
type StorageItemInternal<T> = StorageItem<T> & {
|
|
123
122
|
_triggerListeners: () => void;
|
|
124
123
|
_invalidateParsedCacheOnly: () => void;
|
|
124
|
+
_deleteMemoryEntry: () => void;
|
|
125
125
|
_hasValidation: boolean;
|
|
126
126
|
_hasExpiration: boolean;
|
|
127
127
|
_readCacheEnabled: boolean;
|
|
@@ -229,7 +229,6 @@ export type StorageCoreAdapter = {
|
|
|
229
229
|
backend: StorageCoreBackend;
|
|
230
230
|
changeSource: StorageChangeSource;
|
|
231
231
|
applyAccessControlOnSecureRawWrite: boolean;
|
|
232
|
-
flushDiskWritesOnImport: boolean;
|
|
233
232
|
ensureScopeSubscription(scope: NonMemoryScope): void;
|
|
234
233
|
maybeCleanupScopeSubscription(scope: NonMemoryScope): void;
|
|
235
234
|
onWillEmitChanges(
|
|
@@ -299,84 +298,45 @@ export function createStorageCore(
|
|
|
299
298
|
const registeredKeyCounts = new Map<string, number>();
|
|
300
299
|
const memoryStore = new Map<string, unknown>();
|
|
301
300
|
const memoryListeners: KeyListenerRegistry = new Map();
|
|
302
|
-
const scopedListeners
|
|
303
|
-
[StorageScope.Disk
|
|
304
|
-
[StorageScope.Secure
|
|
305
|
-
|
|
306
|
-
const scopedRawCache
|
|
301
|
+
const scopedListeners: Record<NonMemoryScope, KeyListenerRegistry> = {
|
|
302
|
+
[StorageScope.Disk]: new Map(),
|
|
303
|
+
[StorageScope.Secure]: new Map(),
|
|
304
|
+
};
|
|
305
|
+
const scopedRawCache: Record<
|
|
307
306
|
NonMemoryScope,
|
|
308
307
|
Map<string, string | undefined>
|
|
309
|
-
>
|
|
310
|
-
[StorageScope.Disk
|
|
311
|
-
[StorageScope.Secure
|
|
312
|
-
|
|
313
|
-
const pendingDiskWrites = new Map<string, PendingDiskWrite>();
|
|
314
|
-
let diskFlushScheduled = false;
|
|
315
|
-
let diskWritesAsync = false;
|
|
316
|
-
const pendingSecureWrites = new Map<string, PendingSecureWrite>();
|
|
317
|
-
let secureFlushScheduled = false;
|
|
308
|
+
> = {
|
|
309
|
+
[StorageScope.Disk]: new Map(),
|
|
310
|
+
[StorageScope.Secure]: new Map(),
|
|
311
|
+
};
|
|
318
312
|
let secureDefaultAccessControl: AccessControl = AccessControl.WhenUnlocked;
|
|
319
|
-
let metricsObserver: StorageMetricsObserver | undefined;
|
|
320
313
|
let eventObserver: StorageEventListener | undefined;
|
|
321
314
|
let eventObserverRedactSecureValues = true;
|
|
322
|
-
const metricsCounters = new Map<
|
|
323
|
-
string,
|
|
324
|
-
{ count: number; totalDurationMs: number; maxDurationMs: number }
|
|
325
|
-
>();
|
|
326
315
|
const storageEvents = new StorageEventRegistry();
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
existing.count += 1;
|
|
343
|
-
existing.totalDurationMs += durationMs;
|
|
344
|
-
existing.maxDurationMs = Math.max(existing.maxDurationMs, durationMs);
|
|
345
|
-
}
|
|
346
|
-
|
|
347
|
-
metricsObserver?.({
|
|
348
|
-
operation,
|
|
349
|
-
scope,
|
|
350
|
-
durationMs,
|
|
351
|
-
keysCount,
|
|
352
|
-
});
|
|
353
|
-
}
|
|
354
|
-
|
|
355
|
-
function measureOperation<T>(
|
|
356
|
-
operation: string,
|
|
357
|
-
scope: StorageScope,
|
|
358
|
-
fn: () => T,
|
|
359
|
-
keysCount = 1,
|
|
360
|
-
): T {
|
|
361
|
-
if (!metricsObserver) {
|
|
362
|
-
return fn();
|
|
363
|
-
}
|
|
364
|
-
const start = now();
|
|
365
|
-
try {
|
|
366
|
-
return fn();
|
|
367
|
-
} finally {
|
|
368
|
-
recordMetric(operation, scope, now() - start, keysCount);
|
|
369
|
-
}
|
|
370
|
-
}
|
|
316
|
+
const metrics = createMetricsRegistry();
|
|
317
|
+
const durability = createDurabilityCoordinator({
|
|
318
|
+
backend: {
|
|
319
|
+
setBatch: (keys, values, scope) => {
|
|
320
|
+
adapter.backend.setBatch(keys, values, scope);
|
|
321
|
+
},
|
|
322
|
+
removeBatch: (keys, scope) => {
|
|
323
|
+
adapter.backend.removeBatch(keys, scope);
|
|
324
|
+
},
|
|
325
|
+
setSecureAccessControl: (level) => {
|
|
326
|
+
adapter.backend.setSecureAccessControl(level);
|
|
327
|
+
},
|
|
328
|
+
},
|
|
329
|
+
resolveSecureDefaultAccessControl: () => secureDefaultAccessControl,
|
|
330
|
+
});
|
|
371
331
|
|
|
372
332
|
function getScopedListeners(scope: NonMemoryScope): KeyListenerRegistry {
|
|
373
|
-
return scopedListeners
|
|
333
|
+
return scopedListeners[scope];
|
|
374
334
|
}
|
|
375
335
|
|
|
376
336
|
function getScopeRawCache(
|
|
377
337
|
scope: NonMemoryScope,
|
|
378
338
|
): Map<string, string | undefined> {
|
|
379
|
-
return scopedRawCache
|
|
339
|
+
return scopedRawCache[scope];
|
|
380
340
|
}
|
|
381
341
|
|
|
382
342
|
function cacheRawValue(
|
|
@@ -428,7 +388,9 @@ export function createStorageCore(
|
|
|
428
388
|
): string | undefined {
|
|
429
389
|
if (scope === StorageScope.Memory) {
|
|
430
390
|
const value = memoryStore.get(key);
|
|
431
|
-
return typeof value === "string"
|
|
391
|
+
return typeof value === "string"
|
|
392
|
+
? unescapeCollidingRawValue(value)
|
|
393
|
+
: undefined;
|
|
432
394
|
}
|
|
433
395
|
|
|
434
396
|
return getRawValue(key, scope);
|
|
@@ -513,110 +475,58 @@ export function createStorageCore(
|
|
|
513
475
|
eventObserver?.(eventForGlobalObserver(event));
|
|
514
476
|
}
|
|
515
477
|
|
|
478
|
+
function measureOperation<T>(
|
|
479
|
+
operation: string,
|
|
480
|
+
scope: StorageScope,
|
|
481
|
+
fn: () => T,
|
|
482
|
+
keysCount = 1,
|
|
483
|
+
): T {
|
|
484
|
+
return metrics.measure(operation, scope, fn, keysCount);
|
|
485
|
+
}
|
|
486
|
+
|
|
487
|
+
function recordMetric(
|
|
488
|
+
operation: string,
|
|
489
|
+
scope: StorageScope,
|
|
490
|
+
durationMs: number,
|
|
491
|
+
keysCount = 1,
|
|
492
|
+
): void {
|
|
493
|
+
metrics.record(operation, scope, durationMs, keysCount);
|
|
494
|
+
}
|
|
495
|
+
|
|
516
496
|
function readPendingSecureWrite(key: string): string | undefined {
|
|
517
|
-
return
|
|
497
|
+
return durability.readPendingSecureWrite(key);
|
|
518
498
|
}
|
|
519
499
|
|
|
520
500
|
function readPendingDiskWrite(key: string): string | undefined {
|
|
521
|
-
return
|
|
501
|
+
return durability.readPendingDiskWrite(key);
|
|
522
502
|
}
|
|
523
503
|
|
|
524
504
|
function hasPendingDiskWrite(key: string): boolean {
|
|
525
|
-
return
|
|
505
|
+
return durability.hasPendingDiskWrite(key);
|
|
526
506
|
}
|
|
527
507
|
|
|
528
508
|
function hasPendingSecureWrite(key: string): boolean {
|
|
529
|
-
return
|
|
509
|
+
return durability.hasPendingSecureWrite(key);
|
|
530
510
|
}
|
|
531
511
|
|
|
532
512
|
function clearPendingDiskWrite(key: string): void {
|
|
533
|
-
|
|
513
|
+
durability.clearPendingDiskWrite(key);
|
|
534
514
|
}
|
|
535
515
|
|
|
536
516
|
function clearPendingSecureWrite(key: string): void {
|
|
537
|
-
|
|
517
|
+
durability.clearPendingSecureWrite(key);
|
|
538
518
|
}
|
|
539
519
|
|
|
540
520
|
function flushDiskWrites(): void {
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
if (pendingDiskWrites.size === 0) {
|
|
544
|
-
return;
|
|
545
|
-
}
|
|
546
|
-
|
|
547
|
-
const writes = Array.from(pendingDiskWrites.values());
|
|
548
|
-
pendingDiskWrites.clear();
|
|
549
|
-
|
|
550
|
-
const keysToSet: string[] = [];
|
|
551
|
-
const valuesToSet: string[] = [];
|
|
552
|
-
const keysToRemove: string[] = [];
|
|
553
|
-
|
|
554
|
-
writes.forEach(({ key, value }) => {
|
|
555
|
-
if (value === undefined) {
|
|
556
|
-
keysToRemove.push(key);
|
|
557
|
-
return;
|
|
558
|
-
}
|
|
559
|
-
|
|
560
|
-
keysToSet.push(key);
|
|
561
|
-
valuesToSet.push(value);
|
|
562
|
-
});
|
|
563
|
-
|
|
564
|
-
if (keysToSet.length > 0) {
|
|
565
|
-
adapter.backend.setBatch(keysToSet, valuesToSet, StorageScope.Disk);
|
|
566
|
-
}
|
|
567
|
-
if (keysToRemove.length > 0) {
|
|
568
|
-
adapter.backend.removeBatch(keysToRemove, StorageScope.Disk);
|
|
569
|
-
}
|
|
521
|
+
durability.flushDiskWrites();
|
|
570
522
|
}
|
|
571
523
|
|
|
572
524
|
function flushSecureWrites(): void {
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
if (pendingSecureWrites.size === 0) {
|
|
576
|
-
return;
|
|
577
|
-
}
|
|
578
|
-
|
|
579
|
-
const writes = Array.from(pendingSecureWrites.values());
|
|
580
|
-
pendingSecureWrites.clear();
|
|
581
|
-
|
|
582
|
-
const groupedSetWrites = new Map<
|
|
583
|
-
AccessControl,
|
|
584
|
-
{ keys: string[]; values: string[] }
|
|
585
|
-
>();
|
|
586
|
-
const keysToRemove: string[] = [];
|
|
587
|
-
|
|
588
|
-
writes.forEach(({ key, value, accessControl }) => {
|
|
589
|
-
if (value === undefined) {
|
|
590
|
-
keysToRemove.push(key);
|
|
591
|
-
} else {
|
|
592
|
-
const resolvedAccessControl =
|
|
593
|
-
accessControl ?? secureDefaultAccessControl;
|
|
594
|
-
const existingGroup = groupedSetWrites.get(resolvedAccessControl);
|
|
595
|
-
const group = existingGroup ?? { keys: [], values: [] };
|
|
596
|
-
group.keys.push(key);
|
|
597
|
-
group.values.push(value);
|
|
598
|
-
if (!existingGroup) {
|
|
599
|
-
groupedSetWrites.set(resolvedAccessControl, group);
|
|
600
|
-
}
|
|
601
|
-
}
|
|
602
|
-
});
|
|
603
|
-
|
|
604
|
-
groupedSetWrites.forEach((group, accessControl) => {
|
|
605
|
-
adapter.backend.setSecureAccessControl(accessControl);
|
|
606
|
-
adapter.backend.setBatch(group.keys, group.values, StorageScope.Secure);
|
|
607
|
-
});
|
|
608
|
-
if (keysToRemove.length > 0) {
|
|
609
|
-
adapter.backend.removeBatch(keysToRemove, StorageScope.Secure);
|
|
610
|
-
}
|
|
525
|
+
durability.flushSecureWrites();
|
|
611
526
|
}
|
|
612
527
|
|
|
613
528
|
function scheduleDiskWrite(key: string, value: string | undefined): void {
|
|
614
|
-
|
|
615
|
-
if (diskFlushScheduled) {
|
|
616
|
-
return;
|
|
617
|
-
}
|
|
618
|
-
diskFlushScheduled = true;
|
|
619
|
-
runMicrotask(flushDiskWrites);
|
|
529
|
+
durability.scheduleDiskWrite(key, value);
|
|
620
530
|
}
|
|
621
531
|
|
|
622
532
|
function scheduleSecureWrite(
|
|
@@ -624,20 +534,21 @@ export function createStorageCore(
|
|
|
624
534
|
value: string | undefined,
|
|
625
535
|
accessControl?: AccessControl,
|
|
626
536
|
): void {
|
|
627
|
-
|
|
628
|
-
if (accessControl !== undefined) {
|
|
629
|
-
pendingWrite.accessControl = accessControl;
|
|
630
|
-
}
|
|
631
|
-
pendingSecureWrites.set(key, pendingWrite);
|
|
632
|
-
if (secureFlushScheduled) {
|
|
633
|
-
return;
|
|
634
|
-
}
|
|
635
|
-
secureFlushScheduled = true;
|
|
636
|
-
runMicrotask(flushSecureWrites);
|
|
537
|
+
durability.scheduleSecureWrite(key, value, accessControl);
|
|
637
538
|
}
|
|
638
539
|
|
|
639
|
-
function
|
|
640
|
-
|
|
540
|
+
function setDiskWritesAsyncMode(enabled: boolean): void {
|
|
541
|
+
durability.setDiskWritesAsync(enabled);
|
|
542
|
+
}
|
|
543
|
+
|
|
544
|
+
function isDiskWritesAsync(): boolean {
|
|
545
|
+
return durability.isDiskWritesAsync();
|
|
546
|
+
}
|
|
547
|
+
|
|
548
|
+
function getStoredRawValue(
|
|
549
|
+
key: string,
|
|
550
|
+
scope: StorageScope,
|
|
551
|
+
): string | undefined {
|
|
641
552
|
if (scope === StorageScope.Memory) {
|
|
642
553
|
const value = memoryStore.get(key);
|
|
643
554
|
return typeof value === "string" ? value : undefined;
|
|
@@ -654,21 +565,49 @@ export function createStorageCore(
|
|
|
654
565
|
return adapter.backend.get(key, scope);
|
|
655
566
|
}
|
|
656
567
|
|
|
568
|
+
function getRawValue(key: string, scope: StorageScope): string | undefined {
|
|
569
|
+
assertValidScope(scope);
|
|
570
|
+
if (scope === StorageScope.Memory) {
|
|
571
|
+
const value = memoryStore.get(key);
|
|
572
|
+
return typeof value === "string"
|
|
573
|
+
? unescapeCollidingRawValue(value)
|
|
574
|
+
: undefined;
|
|
575
|
+
}
|
|
576
|
+
|
|
577
|
+
if (scope === StorageScope.Disk && hasPendingDiskWrite(key)) {
|
|
578
|
+
const pending = readPendingDiskWrite(key);
|
|
579
|
+
return pending === undefined
|
|
580
|
+
? undefined
|
|
581
|
+
: unescapeCollidingRawValue(pending);
|
|
582
|
+
}
|
|
583
|
+
|
|
584
|
+
if (scope === StorageScope.Secure && hasPendingSecureWrite(key)) {
|
|
585
|
+
const pending = readPendingSecureWrite(key);
|
|
586
|
+
return pending === undefined
|
|
587
|
+
? undefined
|
|
588
|
+
: unescapeCollidingRawValue(pending);
|
|
589
|
+
}
|
|
590
|
+
|
|
591
|
+
const raw = adapter.backend.get(key, scope);
|
|
592
|
+
return raw === undefined ? undefined : unescapeCollidingRawValue(raw);
|
|
593
|
+
}
|
|
594
|
+
|
|
657
595
|
function setRawValue(key: string, value: string, scope: StorageScope): void {
|
|
658
596
|
assertValidScope(scope);
|
|
597
|
+
const storedValue = escapeCollidingRawValue(value);
|
|
659
598
|
const oldValue =
|
|
660
599
|
scope === StorageScope.Memory ? getEventRawValue(scope, key) : undefined;
|
|
661
600
|
if (scope === StorageScope.Memory) {
|
|
662
|
-
memoryStore.set(key,
|
|
601
|
+
memoryStore.set(key, storedValue);
|
|
663
602
|
notifyKeyListeners(memoryListeners, key);
|
|
664
603
|
emitKeyChange(scope, key, oldValue, value, "set", "memory");
|
|
665
604
|
return;
|
|
666
605
|
}
|
|
667
606
|
|
|
668
607
|
if (scope === StorageScope.Disk) {
|
|
669
|
-
cacheRawValue(scope, key,
|
|
670
|
-
if (
|
|
671
|
-
scheduleDiskWrite(key,
|
|
608
|
+
cacheRawValue(scope, key, storedValue);
|
|
609
|
+
if (isDiskWritesAsync()) {
|
|
610
|
+
scheduleDiskWrite(key, storedValue);
|
|
672
611
|
emitKeyChange(scope, key, oldValue, value, "set", adapter.changeSource);
|
|
673
612
|
return;
|
|
674
613
|
}
|
|
@@ -685,8 +624,8 @@ export function createStorageCore(
|
|
|
685
624
|
}
|
|
686
625
|
}
|
|
687
626
|
|
|
688
|
-
adapter.backend.set(key,
|
|
689
|
-
cacheRawValue(scope, key,
|
|
627
|
+
adapter.backend.set(key, storedValue, scope);
|
|
628
|
+
cacheRawValue(scope, key, storedValue);
|
|
690
629
|
emitKeyChange(scope, key, oldValue, value, "set", adapter.changeSource);
|
|
691
630
|
}
|
|
692
631
|
|
|
@@ -702,7 +641,7 @@ export function createStorageCore(
|
|
|
702
641
|
|
|
703
642
|
if (scope === StorageScope.Disk) {
|
|
704
643
|
cacheRawValue(scope, key, undefined);
|
|
705
|
-
if (
|
|
644
|
+
if (isDiskWritesAsync()) {
|
|
706
645
|
scheduleDiskWrite(key, undefined);
|
|
707
646
|
emitKeyChange(
|
|
708
647
|
scope,
|
|
@@ -746,10 +685,6 @@ export function createStorageCore(
|
|
|
746
685
|
return Number.isFinite(parsed) && parsed > 0 ? parsed : 0;
|
|
747
686
|
}
|
|
748
687
|
|
|
749
|
-
function writeMigrationVersion(scope: StorageScope, version: number): void {
|
|
750
|
-
setRawValue(MIGRATION_VERSION_KEY, String(version), scope);
|
|
751
|
-
}
|
|
752
|
-
|
|
753
688
|
const internals: StorageCoreInternals = {
|
|
754
689
|
getScopedListeners,
|
|
755
690
|
cacheRawValue,
|
|
@@ -758,10 +693,10 @@ export function createStorageCore(
|
|
|
758
693
|
clearPendingDiskWrite,
|
|
759
694
|
clearPendingSecureWrite,
|
|
760
695
|
clearAllPendingDiskWrites: () => {
|
|
761
|
-
|
|
696
|
+
durability.clearAllPendingDiskWrites();
|
|
762
697
|
},
|
|
763
698
|
clearAllPendingSecureWrites: () => {
|
|
764
|
-
|
|
699
|
+
durability.clearAllPendingSecureWrites();
|
|
765
700
|
},
|
|
766
701
|
flushDiskWrites,
|
|
767
702
|
flushSecureWrites,
|
|
@@ -801,7 +736,9 @@ export function createStorageCore(
|
|
|
801
736
|
? removeKeys.map((key) => getEventRawValue(scope, key))
|
|
802
737
|
: [];
|
|
803
738
|
removeKeys.forEach((key) => memoryStore.delete(key));
|
|
804
|
-
removeKeys.forEach((key) =>
|
|
739
|
+
removeKeys.forEach((key) => {
|
|
740
|
+
notifyKeyListeners(memoryListeners, key);
|
|
741
|
+
});
|
|
805
742
|
emitBatchChange(
|
|
806
743
|
scope,
|
|
807
744
|
"clear",
|
|
@@ -834,10 +771,18 @@ export function createStorageCore(
|
|
|
834
771
|
return;
|
|
835
772
|
}
|
|
836
773
|
const previousValues = shouldReadPreviousEventValues(scope)
|
|
837
|
-
? adapter.backend
|
|
774
|
+
? adapter.backend
|
|
775
|
+
.getBatch(removeKeys, scope)
|
|
776
|
+
.map((value) =>
|
|
777
|
+
value === undefined
|
|
778
|
+
? undefined
|
|
779
|
+
: unescapeCollidingRawValue(value),
|
|
780
|
+
)
|
|
838
781
|
: [];
|
|
839
782
|
adapter.backend.removeBatch(removeKeys, scope);
|
|
840
|
-
removeKeys.forEach((key) =>
|
|
783
|
+
removeKeys.forEach((key) => {
|
|
784
|
+
cacheRawValue(scope, key, undefined);
|
|
785
|
+
});
|
|
841
786
|
emitBatchChange(
|
|
842
787
|
scope,
|
|
843
788
|
"clear",
|
|
@@ -961,12 +906,12 @@ export function createStorageCore(
|
|
|
961
906
|
|
|
962
907
|
if (scope === StorageScope.Disk) {
|
|
963
908
|
flushDiskWrites();
|
|
964
|
-
|
|
909
|
+
durability.clearAllPendingDiskWrites();
|
|
965
910
|
}
|
|
966
911
|
|
|
967
912
|
if (scope === StorageScope.Secure) {
|
|
968
913
|
flushSecureWrites();
|
|
969
|
-
|
|
914
|
+
durability.clearAllPendingSecureWrites();
|
|
970
915
|
}
|
|
971
916
|
|
|
972
917
|
clearScopeRawCache(scope);
|
|
@@ -1100,9 +1045,9 @@ export function createStorageCore(
|
|
|
1100
1045
|
affectedKeys.forEach((key) => {
|
|
1101
1046
|
memoryStore.delete(key);
|
|
1102
1047
|
});
|
|
1103
|
-
affectedKeys.forEach((key) =>
|
|
1104
|
-
notifyKeyListeners(memoryListeners, key)
|
|
1105
|
-
);
|
|
1048
|
+
affectedKeys.forEach((key) => {
|
|
1049
|
+
notifyKeyListeners(memoryListeners, key);
|
|
1050
|
+
});
|
|
1106
1051
|
emitBatchChange(
|
|
1107
1052
|
scope,
|
|
1108
1053
|
"clearNamespace",
|
|
@@ -1239,7 +1184,7 @@ export function createStorageCore(
|
|
|
1239
1184
|
keys.forEach((key, idx) => {
|
|
1240
1185
|
const value = values[idx];
|
|
1241
1186
|
if (value !== undefined) {
|
|
1242
|
-
result[key] = value;
|
|
1187
|
+
result[key] = unescapeCollidingRawValue(value);
|
|
1243
1188
|
}
|
|
1244
1189
|
});
|
|
1245
1190
|
return result;
|
|
@@ -1252,7 +1197,8 @@ export function createStorageCore(
|
|
|
1252
1197
|
if (scope === StorageScope.Memory) {
|
|
1253
1198
|
for (const key of memoryStore.keys()) {
|
|
1254
1199
|
const value = memoryStore.get(key);
|
|
1255
|
-
if (typeof value === "string")
|
|
1200
|
+
if (typeof value === "string")
|
|
1201
|
+
result[key] = unescapeCollidingRawValue(value);
|
|
1256
1202
|
}
|
|
1257
1203
|
return result;
|
|
1258
1204
|
}
|
|
@@ -1267,7 +1213,7 @@ export function createStorageCore(
|
|
|
1267
1213
|
const values = adapter.backend.getBatch(keys, scope);
|
|
1268
1214
|
keys.forEach((key, idx) => {
|
|
1269
1215
|
const val = values[idx];
|
|
1270
|
-
if (val !== undefined) result[key] = val;
|
|
1216
|
+
if (val !== undefined) result[key] = unescapeCollidingRawValue(val);
|
|
1271
1217
|
});
|
|
1272
1218
|
return result;
|
|
1273
1219
|
});
|
|
@@ -1312,7 +1258,7 @@ export function createStorageCore(
|
|
|
1312
1258
|
},
|
|
1313
1259
|
setDiskWritesAsync: (enabled: boolean) => {
|
|
1314
1260
|
measureOperation("storage:setDiskWritesAsync", StorageScope.Disk, () => {
|
|
1315
|
-
|
|
1261
|
+
setDiskWritesAsyncMode(enabled);
|
|
1316
1262
|
if (!enabled) {
|
|
1317
1263
|
flushDiskWrites();
|
|
1318
1264
|
}
|
|
@@ -1329,23 +1275,16 @@ export function createStorageCore(
|
|
|
1329
1275
|
});
|
|
1330
1276
|
},
|
|
1331
1277
|
setMetricsObserver: (observer?: StorageMetricsObserver) => {
|
|
1332
|
-
|
|
1278
|
+
metrics.setObserver(observer);
|
|
1333
1279
|
},
|
|
1334
1280
|
getMetricsSnapshot: (): Record<string, StorageMetricSummary> => {
|
|
1335
|
-
|
|
1336
|
-
|
|
1337
|
-
|
|
1338
|
-
|
|
1339
|
-
totalDurationMs: value.totalDurationMs,
|
|
1340
|
-
avgDurationMs:
|
|
1341
|
-
value.count === 0 ? 0 : value.totalDurationMs / value.count,
|
|
1342
|
-
maxDurationMs: value.maxDurationMs,
|
|
1343
|
-
};
|
|
1344
|
-
});
|
|
1345
|
-
return snapshot;
|
|
1281
|
+
return metrics.getSnapshot();
|
|
1282
|
+
},
|
|
1283
|
+
getScopedMetricsSnapshot: (): Record<string, StorageMetricSummary> => {
|
|
1284
|
+
return metrics.getScopedSnapshot();
|
|
1346
1285
|
},
|
|
1347
1286
|
resetMetrics: () => {
|
|
1348
|
-
|
|
1287
|
+
metrics.reset();
|
|
1349
1288
|
},
|
|
1350
1289
|
getSecureMetadata: (key: string): SecureStorageMetadata => {
|
|
1351
1290
|
return measureOperation(
|
|
@@ -1410,7 +1349,8 @@ export function createStorageCore(
|
|
|
1410
1349
|
() => {
|
|
1411
1350
|
assertValidScope(scope);
|
|
1412
1351
|
if (keys.length === 0) return;
|
|
1413
|
-
const values = keys.map((k) => data[k]
|
|
1352
|
+
const values = keys.map((k) => data[k] as string);
|
|
1353
|
+
const storedValues = values.map(escapeCollidingRawValue);
|
|
1414
1354
|
const changes = keys.map((key, index) =>
|
|
1415
1355
|
createKeyChange(
|
|
1416
1356
|
scope,
|
|
@@ -1424,9 +1364,11 @@ export function createStorageCore(
|
|
|
1424
1364
|
|
|
1425
1365
|
if (scope === StorageScope.Memory) {
|
|
1426
1366
|
keys.forEach((key, index) => {
|
|
1427
|
-
memoryStore.set(key,
|
|
1367
|
+
memoryStore.set(key, storedValues[index]);
|
|
1368
|
+
});
|
|
1369
|
+
keys.forEach((key) => {
|
|
1370
|
+
notifyKeyListeners(memoryListeners, key);
|
|
1428
1371
|
});
|
|
1429
|
-
keys.forEach((key) => notifyKeyListeners(memoryListeners, key));
|
|
1430
1372
|
emitBatchChange(scope, "import", "memory", changes);
|
|
1431
1373
|
return;
|
|
1432
1374
|
}
|
|
@@ -1435,14 +1377,14 @@ export function createStorageCore(
|
|
|
1435
1377
|
flushSecureWrites();
|
|
1436
1378
|
adapter.backend.setSecureAccessControl(secureDefaultAccessControl);
|
|
1437
1379
|
}
|
|
1438
|
-
if (scope === StorageScope.Disk
|
|
1380
|
+
if (scope === StorageScope.Disk) {
|
|
1439
1381
|
flushDiskWrites();
|
|
1440
1382
|
}
|
|
1441
1383
|
|
|
1442
|
-
adapter.backend.setBatch(keys,
|
|
1443
|
-
keys.forEach((key, index) =>
|
|
1444
|
-
cacheRawValue(scope, key,
|
|
1445
|
-
);
|
|
1384
|
+
adapter.backend.setBatch(keys, storedValues, scope);
|
|
1385
|
+
keys.forEach((key, index) => {
|
|
1386
|
+
cacheRawValue(scope, key, storedValues[index]);
|
|
1387
|
+
});
|
|
1446
1388
|
emitBatchChange(scope, "import", adapter.changeSource, changes);
|
|
1447
1389
|
},
|
|
1448
1390
|
keys.length,
|
|
@@ -1522,6 +1464,15 @@ export function createStorageCore(
|
|
|
1522
1464
|
lastExpiresAt = undefined;
|
|
1523
1465
|
};
|
|
1524
1466
|
|
|
1467
|
+
const resolveNonMemoryScope = (): NonMemoryScope => {
|
|
1468
|
+
if (nonMemoryScope === null) {
|
|
1469
|
+
throw new Error(
|
|
1470
|
+
"NitroStorage: this operation requires Disk or Secure scope.",
|
|
1471
|
+
);
|
|
1472
|
+
}
|
|
1473
|
+
return nonMemoryScope;
|
|
1474
|
+
};
|
|
1475
|
+
|
|
1525
1476
|
const ensureSubscription = () => {
|
|
1526
1477
|
if (unsubscribe) {
|
|
1527
1478
|
return;
|
|
@@ -1529,7 +1480,9 @@ export function createStorageCore(
|
|
|
1529
1480
|
|
|
1530
1481
|
const listener = () => {
|
|
1531
1482
|
invalidateParsedCache();
|
|
1532
|
-
listeners.forEach((callback) =>
|
|
1483
|
+
listeners.forEach((callback) => {
|
|
1484
|
+
callback();
|
|
1485
|
+
});
|
|
1533
1486
|
};
|
|
1534
1487
|
|
|
1535
1488
|
if (isMemory) {
|
|
@@ -1537,9 +1490,9 @@ export function createStorageCore(
|
|
|
1537
1490
|
return;
|
|
1538
1491
|
}
|
|
1539
1492
|
|
|
1540
|
-
adapter.ensureScopeSubscription(
|
|
1493
|
+
adapter.ensureScopeSubscription(resolveNonMemoryScope());
|
|
1541
1494
|
unsubscribe = addKeyListener(
|
|
1542
|
-
getScopedListeners(
|
|
1495
|
+
getScopedListeners(resolveNonMemoryScope()),
|
|
1543
1496
|
storageKey,
|
|
1544
1497
|
listener,
|
|
1545
1498
|
);
|
|
@@ -1557,7 +1510,9 @@ export function createStorageCore(
|
|
|
1557
1510
|
emitKeyChange(
|
|
1558
1511
|
config.scope,
|
|
1559
1512
|
storageKey,
|
|
1560
|
-
typeof expiredRaw === "string"
|
|
1513
|
+
typeof expiredRaw === "string"
|
|
1514
|
+
? unescapeCollidingRawValue(expiredRaw)
|
|
1515
|
+
: undefined,
|
|
1561
1516
|
undefined,
|
|
1562
1517
|
"expire",
|
|
1563
1518
|
"memory",
|
|
@@ -1566,27 +1521,30 @@ export function createStorageCore(
|
|
|
1566
1521
|
return undefined;
|
|
1567
1522
|
}
|
|
1568
1523
|
}
|
|
1569
|
-
|
|
1524
|
+
const memoryStored = memoryStore.get(storageKey);
|
|
1525
|
+
return typeof memoryStored === "string"
|
|
1526
|
+
? unescapeCollidingRawValue(memoryStored)
|
|
1527
|
+
: memoryStored;
|
|
1570
1528
|
}
|
|
1571
1529
|
|
|
1572
1530
|
migrateRenamesIfNeeded();
|
|
1573
1531
|
|
|
1574
1532
|
if (nonMemoryScope === StorageScope.Disk) {
|
|
1575
|
-
const pending =
|
|
1533
|
+
const pending = durability.readPendingDiskWrite(storageKey);
|
|
1576
1534
|
if (pending !== undefined) {
|
|
1577
|
-
return pending
|
|
1535
|
+
return pending;
|
|
1578
1536
|
}
|
|
1579
1537
|
}
|
|
1580
1538
|
|
|
1581
1539
|
if (nonMemoryScope === StorageScope.Secure && !isBiometric) {
|
|
1582
|
-
const pending =
|
|
1540
|
+
const pending = durability.readPendingSecureWrite(storageKey);
|
|
1583
1541
|
if (pending !== undefined) {
|
|
1584
|
-
return pending
|
|
1542
|
+
return pending;
|
|
1585
1543
|
}
|
|
1586
1544
|
}
|
|
1587
1545
|
|
|
1588
1546
|
if (readCache) {
|
|
1589
|
-
const cache = getScopeRawCache(
|
|
1547
|
+
const cache = getScopeRawCache(resolveNonMemoryScope());
|
|
1590
1548
|
const cached = cache.get(storageKey);
|
|
1591
1549
|
if (cached !== undefined || cache.has(storageKey)) {
|
|
1592
1550
|
return cached;
|
|
@@ -1602,7 +1560,7 @@ export function createStorageCore(
|
|
|
1602
1560
|
const raw = readBackendRaw(() =>
|
|
1603
1561
|
adapter.backend.get(storageKey, config.scope),
|
|
1604
1562
|
);
|
|
1605
|
-
cacheRawValue(
|
|
1563
|
+
cacheRawValue(resolveNonMemoryScope(), storageKey, raw);
|
|
1606
1564
|
return raw;
|
|
1607
1565
|
};
|
|
1608
1566
|
|
|
@@ -1614,7 +1572,9 @@ export function createStorageCore(
|
|
|
1614
1572
|
} catch (error) {
|
|
1615
1573
|
onReadError?.(error);
|
|
1616
1574
|
if (fallbackToCacheOnReadError) {
|
|
1617
|
-
const cached = getScopeRawCache(
|
|
1575
|
+
const cached = getScopeRawCache(resolveNonMemoryScope()).get(
|
|
1576
|
+
storageKey,
|
|
1577
|
+
);
|
|
1618
1578
|
if (cached !== undefined) {
|
|
1619
1579
|
return cached;
|
|
1620
1580
|
}
|
|
@@ -1643,10 +1603,10 @@ export function createStorageCore(
|
|
|
1643
1603
|
return;
|
|
1644
1604
|
}
|
|
1645
1605
|
|
|
1646
|
-
cacheRawValue(
|
|
1606
|
+
cacheRawValue(resolveNonMemoryScope(), storageKey, rawValue);
|
|
1647
1607
|
|
|
1648
1608
|
if (nonMemoryScope === StorageScope.Disk) {
|
|
1649
|
-
if (coalesceDiskWrites ||
|
|
1609
|
+
if (coalesceDiskWrites || isDiskWritesAsync()) {
|
|
1650
1610
|
scheduleDiskWrite(storageKey, rawValue);
|
|
1651
1611
|
emitKeyChange(
|
|
1652
1612
|
config.scope,
|
|
@@ -1767,10 +1727,10 @@ export function createStorageCore(
|
|
|
1767
1727
|
return;
|
|
1768
1728
|
}
|
|
1769
1729
|
|
|
1770
|
-
cacheRawValue(
|
|
1730
|
+
cacheRawValue(resolveNonMemoryScope(), storageKey, undefined);
|
|
1771
1731
|
|
|
1772
1732
|
if (nonMemoryScope === StorageScope.Disk) {
|
|
1773
|
-
if (coalesceDiskWrites ||
|
|
1733
|
+
if (coalesceDiskWrites || isDiskWritesAsync()) {
|
|
1774
1734
|
scheduleDiskWrite(storageKey, undefined);
|
|
1775
1735
|
emitKeyChange(
|
|
1776
1736
|
config.scope,
|
|
@@ -1824,7 +1784,9 @@ export function createStorageCore(
|
|
|
1824
1784
|
if (memoryExpiration) {
|
|
1825
1785
|
memoryExpiration.set(storageKey, Date.now() + (expirationTtlMs ?? 0));
|
|
1826
1786
|
}
|
|
1827
|
-
|
|
1787
|
+
const storedValue =
|
|
1788
|
+
typeof value === "string" ? escapeCollidingRawValue(value) : value;
|
|
1789
|
+
memoryStore.set(storageKey, storedValue);
|
|
1828
1790
|
notifyKeyListeners(memoryListeners, storageKey);
|
|
1829
1791
|
emitKeyChange(
|
|
1830
1792
|
config.scope,
|
|
@@ -1895,7 +1857,9 @@ export function createStorageCore(
|
|
|
1895
1857
|
onExpired?.(storageKey);
|
|
1896
1858
|
lastValue = ensureValidatedValue(defaultValue, false);
|
|
1897
1859
|
hasLastValue = true;
|
|
1898
|
-
listeners.forEach((cb) =>
|
|
1860
|
+
listeners.forEach((cb) => {
|
|
1861
|
+
cb();
|
|
1862
|
+
});
|
|
1899
1863
|
return lastValue;
|
|
1900
1864
|
}
|
|
1901
1865
|
}
|
|
@@ -1937,7 +1901,9 @@ export function createStorageCore(
|
|
|
1937
1901
|
onExpired?.(storageKey);
|
|
1938
1902
|
lastValue = ensureValidatedValue(defaultValue, false);
|
|
1939
1903
|
hasLastValue = true;
|
|
1940
|
-
listeners.forEach((cb) =>
|
|
1904
|
+
listeners.forEach((cb) => {
|
|
1905
|
+
cb();
|
|
1906
|
+
});
|
|
1941
1907
|
return lastValue;
|
|
1942
1908
|
}
|
|
1943
1909
|
|
|
@@ -1958,7 +1924,12 @@ export function createStorageCore(
|
|
|
1958
1924
|
|
|
1959
1925
|
const getCurrentVersion = (): StorageVersion => {
|
|
1960
1926
|
const raw = readStoredRaw();
|
|
1961
|
-
|
|
1927
|
+
if (raw === undefined) {
|
|
1928
|
+
return toVersionToken(undefined);
|
|
1929
|
+
}
|
|
1930
|
+
return toVersionToken(
|
|
1931
|
+
typeof raw === "string" ? unescapeCollidingRawValue(raw) : raw,
|
|
1932
|
+
);
|
|
1962
1933
|
};
|
|
1963
1934
|
|
|
1964
1935
|
const get = (): T =>
|
|
@@ -2052,15 +2023,13 @@ export function createStorageCore(
|
|
|
2052
2023
|
if (isMemory) return memoryStore.has(storageKey);
|
|
2053
2024
|
if (isBiometric) return adapter.backend.hasSecureBiometric(storageKey);
|
|
2054
2025
|
if (nonMemoryScope === StorageScope.Disk) {
|
|
2055
|
-
|
|
2056
|
-
|
|
2057
|
-
return pending.value !== undefined;
|
|
2026
|
+
if (durability.hasPendingDiskWrite(storageKey)) {
|
|
2027
|
+
return durability.readPendingDiskWrite(storageKey) !== undefined;
|
|
2058
2028
|
}
|
|
2059
2029
|
}
|
|
2060
2030
|
if (nonMemoryScope === StorageScope.Secure) {
|
|
2061
|
-
|
|
2062
|
-
|
|
2063
|
-
return pending.value !== undefined;
|
|
2031
|
+
if (durability.hasPendingSecureWrite(storageKey)) {
|
|
2032
|
+
return durability.readPendingSecureWrite(storageKey) !== undefined;
|
|
2064
2033
|
}
|
|
2065
2034
|
}
|
|
2066
2035
|
return adapter.backend.has(storageKey, config.scope);
|
|
@@ -2074,7 +2043,7 @@ export function createStorageCore(
|
|
|
2074
2043
|
if (listeners.size === 0 && unsubscribe) {
|
|
2075
2044
|
unsubscribe();
|
|
2076
2045
|
if (!isMemory) {
|
|
2077
|
-
adapter.maybeCleanupScopeSubscription(
|
|
2046
|
+
adapter.maybeCleanupScopeSubscription(resolveNonMemoryScope());
|
|
2078
2047
|
}
|
|
2079
2048
|
unsubscribe = null;
|
|
2080
2049
|
}
|
|
@@ -2121,11 +2090,20 @@ export function createStorageCore(
|
|
|
2121
2090
|
deserialize,
|
|
2122
2091
|
_triggerListeners: () => {
|
|
2123
2092
|
invalidateParsedCache();
|
|
2124
|
-
listeners.forEach((listener) =>
|
|
2093
|
+
listeners.forEach((listener) => {
|
|
2094
|
+
listener();
|
|
2095
|
+
});
|
|
2125
2096
|
},
|
|
2126
2097
|
_invalidateParsedCacheOnly: () => {
|
|
2127
2098
|
invalidateParsedCache();
|
|
2128
2099
|
},
|
|
2100
|
+
_deleteMemoryEntry: () => {
|
|
2101
|
+
if (memoryExpiration) {
|
|
2102
|
+
memoryExpiration.delete(storageKey);
|
|
2103
|
+
}
|
|
2104
|
+
memoryStore.delete(storageKey);
|
|
2105
|
+
invalidateParsedCache();
|
|
2106
|
+
},
|
|
2129
2107
|
_hasValidation: validate !== undefined,
|
|
2130
2108
|
_hasExpiration: expiration !== undefined,
|
|
2131
2109
|
_readCacheEnabled: readCache,
|
|
@@ -2187,17 +2165,17 @@ export function createStorageCore(
|
|
|
2187
2165
|
|
|
2188
2166
|
items.forEach((item, index) => {
|
|
2189
2167
|
if (scope === StorageScope.Disk) {
|
|
2190
|
-
const pending =
|
|
2168
|
+
const pending = durability.readPendingDiskWrite(item.key);
|
|
2191
2169
|
if (pending !== undefined) {
|
|
2192
|
-
rawValues[index] = pending
|
|
2170
|
+
rawValues[index] = pending;
|
|
2193
2171
|
return;
|
|
2194
2172
|
}
|
|
2195
2173
|
}
|
|
2196
2174
|
|
|
2197
2175
|
if (scope === StorageScope.Secure) {
|
|
2198
|
-
const pending =
|
|
2176
|
+
const pending = durability.readPendingSecureWrite(item.key);
|
|
2199
2177
|
if (pending !== undefined) {
|
|
2200
|
-
rawValues[index] = pending
|
|
2178
|
+
rawValues[index] = pending;
|
|
2201
2179
|
return;
|
|
2202
2180
|
}
|
|
2203
2181
|
}
|
|
@@ -2266,7 +2244,9 @@ export function createStorageCore(
|
|
|
2266
2244
|
|
|
2267
2245
|
if (needsIndividualSets) {
|
|
2268
2246
|
// Fall back to individual sets to preserve validation and TTL semantics
|
|
2269
|
-
items.forEach(({ item, value }) =>
|
|
2247
|
+
items.forEach(({ item, value }) => {
|
|
2248
|
+
item.set(value);
|
|
2249
|
+
});
|
|
2270
2250
|
return;
|
|
2271
2251
|
}
|
|
2272
2252
|
|
|
@@ -2288,9 +2268,9 @@ export function createStorageCore(
|
|
|
2288
2268
|
item as StorageItem<unknown>,
|
|
2289
2269
|
)._invalidateParsedCacheOnly();
|
|
2290
2270
|
});
|
|
2291
|
-
items.forEach(({ item }) =>
|
|
2292
|
-
notifyKeyListeners(memoryListeners, item.key)
|
|
2293
|
-
);
|
|
2271
|
+
items.forEach(({ item }) => {
|
|
2272
|
+
notifyKeyListeners(memoryListeners, item.key);
|
|
2273
|
+
});
|
|
2294
2274
|
emitBatchChange(scope, "setBatch", "memory", changes);
|
|
2295
2275
|
return;
|
|
2296
2276
|
}
|
|
@@ -2305,14 +2285,22 @@ export function createStorageCore(
|
|
|
2305
2285
|
canUseSecureRawBatchPath(internal),
|
|
2306
2286
|
);
|
|
2307
2287
|
if (!canUseSecureBatchPath) {
|
|
2308
|
-
items.forEach(({ item, value }) =>
|
|
2288
|
+
items.forEach(({ item, value }) => {
|
|
2289
|
+
item.set(value);
|
|
2290
|
+
});
|
|
2309
2291
|
return;
|
|
2310
2292
|
}
|
|
2311
2293
|
|
|
2312
2294
|
flushSecureWrites();
|
|
2313
2295
|
const keys = secureEntries.map(({ item }) => item.key);
|
|
2314
2296
|
const oldValues = shouldReadPreviousEventValues(scope)
|
|
2315
|
-
? adapter.backend
|
|
2297
|
+
? adapter.backend
|
|
2298
|
+
.getBatch(keys, scope)
|
|
2299
|
+
.map((value) =>
|
|
2300
|
+
value === undefined
|
|
2301
|
+
? undefined
|
|
2302
|
+
: unescapeCollidingRawValue(value),
|
|
2303
|
+
)
|
|
2316
2304
|
: [];
|
|
2317
2305
|
const groupedByAccessControl = new Map<
|
|
2318
2306
|
number,
|
|
@@ -2334,9 +2322,9 @@ export function createStorageCore(
|
|
|
2334
2322
|
groupedByAccessControl.forEach((group, accessControl) => {
|
|
2335
2323
|
adapter.backend.setSecureAccessControl(accessControl);
|
|
2336
2324
|
adapter.backend.setBatch(group.keys, group.values, scope);
|
|
2337
|
-
group.keys.forEach((key, index) =>
|
|
2338
|
-
cacheRawValue(scope, key, group.values[index])
|
|
2339
|
-
);
|
|
2325
|
+
group.keys.forEach((key, index) => {
|
|
2326
|
+
cacheRawValue(scope, key, group.values[index]);
|
|
2327
|
+
});
|
|
2340
2328
|
});
|
|
2341
2329
|
emitBatchChange(
|
|
2342
2330
|
scope,
|
|
@@ -2362,18 +2350,28 @@ export function createStorageCore(
|
|
|
2362
2350
|
canUseRawBatchPath(asInternal(item)),
|
|
2363
2351
|
);
|
|
2364
2352
|
if (!useRawBatchPath) {
|
|
2365
|
-
items.forEach(({ item, value }) =>
|
|
2353
|
+
items.forEach(({ item, value }) => {
|
|
2354
|
+
item.set(value);
|
|
2355
|
+
});
|
|
2366
2356
|
return;
|
|
2367
2357
|
}
|
|
2368
2358
|
|
|
2369
2359
|
const keys = items.map((entry) => entry.item.key);
|
|
2370
2360
|
const values = items.map((entry) => entry.item.serialize(entry.value));
|
|
2371
2361
|
const oldValues = shouldReadPreviousEventValues(scope)
|
|
2372
|
-
? adapter.backend
|
|
2362
|
+
? adapter.backend
|
|
2363
|
+
.getBatch(keys, scope)
|
|
2364
|
+
.map((value) =>
|
|
2365
|
+
value === undefined
|
|
2366
|
+
? undefined
|
|
2367
|
+
: unescapeCollidingRawValue(value),
|
|
2368
|
+
)
|
|
2373
2369
|
: [];
|
|
2374
2370
|
|
|
2375
2371
|
adapter.backend.setBatch(keys, values, scope);
|
|
2376
|
-
keys.forEach((key, index) =>
|
|
2372
|
+
keys.forEach((key, index) => {
|
|
2373
|
+
cacheRawValue(scope, key, values[index]);
|
|
2374
|
+
});
|
|
2377
2375
|
emitBatchChange(
|
|
2378
2376
|
scope,
|
|
2379
2377
|
"setBatch",
|
|
@@ -2415,7 +2413,12 @@ export function createStorageCore(
|
|
|
2415
2413
|
"memory",
|
|
2416
2414
|
),
|
|
2417
2415
|
);
|
|
2418
|
-
items.forEach((item) =>
|
|
2416
|
+
items.forEach((item) => {
|
|
2417
|
+
asInternal(item as StorageItem<unknown>)._deleteMemoryEntry();
|
|
2418
|
+
});
|
|
2419
|
+
items.forEach((item) => {
|
|
2420
|
+
notifyKeyListeners(memoryListeners, item.key);
|
|
2421
|
+
});
|
|
2419
2422
|
emitBatchChange(scope, "removeBatch", "memory", changes);
|
|
2420
2423
|
return;
|
|
2421
2424
|
}
|
|
@@ -2428,10 +2431,18 @@ export function createStorageCore(
|
|
|
2428
2431
|
flushSecureWrites();
|
|
2429
2432
|
}
|
|
2430
2433
|
const oldValues = shouldReadPreviousEventValues(scope)
|
|
2431
|
-
? adapter.backend
|
|
2434
|
+
? adapter.backend
|
|
2435
|
+
.getBatch(keys, scope)
|
|
2436
|
+
.map((value) =>
|
|
2437
|
+
value === undefined
|
|
2438
|
+
? undefined
|
|
2439
|
+
: unescapeCollidingRawValue(value),
|
|
2440
|
+
)
|
|
2432
2441
|
: [];
|
|
2433
2442
|
adapter.backend.removeBatch(keys, scope);
|
|
2434
|
-
keys.forEach((key) =>
|
|
2443
|
+
keys.forEach((key) => {
|
|
2444
|
+
cacheRawValue(scope, key, undefined);
|
|
2445
|
+
});
|
|
2435
2446
|
emitBatchChange(
|
|
2436
2447
|
scope,
|
|
2437
2448
|
"removeBatch",
|
|
@@ -2473,26 +2484,19 @@ export function createStorageCore(
|
|
|
2473
2484
|
.sort((a, b) => a - b);
|
|
2474
2485
|
|
|
2475
2486
|
let appliedVersion = currentVersion;
|
|
2476
|
-
const context: MigrationContext = {
|
|
2477
|
-
scope,
|
|
2478
|
-
getRaw: (key) => getRawValue(key, scope),
|
|
2479
|
-
setRaw: (key, value) => setRawValue(key, value, scope),
|
|
2480
|
-
removeRaw: (key) => removeRawValue(key, scope),
|
|
2481
|
-
};
|
|
2482
2487
|
|
|
2483
2488
|
versions.forEach((version) => {
|
|
2484
2489
|
const migration = registeredMigrations.get(version);
|
|
2485
2490
|
if (!migration) {
|
|
2486
2491
|
return;
|
|
2487
2492
|
}
|
|
2488
|
-
|
|
2493
|
+
runTransaction(scope, (tx) => {
|
|
2494
|
+
migration(tx);
|
|
2495
|
+
tx.setRaw(MIGRATION_VERSION_KEY, String(version));
|
|
2496
|
+
});
|
|
2489
2497
|
appliedVersion = version;
|
|
2490
2498
|
});
|
|
2491
2499
|
|
|
2492
|
-
if (appliedVersion !== currentVersion) {
|
|
2493
|
-
writeMigrationVersion(scope, appliedVersion);
|
|
2494
|
-
}
|
|
2495
|
-
|
|
2496
2500
|
return appliedVersion;
|
|
2497
2501
|
});
|
|
2498
2502
|
}
|
|
@@ -2542,7 +2546,7 @@ export function createStorageCore(
|
|
|
2542
2546
|
}
|
|
2543
2547
|
rollback.set(key, {
|
|
2544
2548
|
kind: "raw",
|
|
2545
|
-
value:
|
|
2549
|
+
value: getStoredRawValue(key, scope),
|
|
2546
2550
|
...(scope === StorageScope.Secure &&
|
|
2547
2551
|
internal?._secureAccessControl !== undefined
|
|
2548
2552
|
? { accessControl: internal._secureAccessControl }
|
|
@@ -2582,6 +2586,12 @@ export function createStorageCore(
|
|
|
2582
2586
|
return transaction(tx);
|
|
2583
2587
|
} catch (error) {
|
|
2584
2588
|
const rollbackEntries = Array.from(rollback.entries()).reverse();
|
|
2589
|
+
const rollbackSource =
|
|
2590
|
+
scope === StorageScope.Memory ? "memory" : adapter.changeSource;
|
|
2591
|
+
const preRollbackValues =
|
|
2592
|
+
rollbackEntries.length > 0
|
|
2593
|
+
? rollbackEntries.map(([key]) => getEventRawValue(scope, key))
|
|
2594
|
+
: [];
|
|
2585
2595
|
if (scope === StorageScope.Memory) {
|
|
2586
2596
|
rollbackEntries.forEach(([key, record]) => {
|
|
2587
2597
|
if (record.value === NOT_SET) {
|
|
@@ -2640,15 +2650,35 @@ export function createStorageCore(
|
|
|
2640
2650
|
adapter.backend.setSecureAccessControl(accessControl);
|
|
2641
2651
|
}
|
|
2642
2652
|
adapter.backend.setBatch(group.keys, group.values, scope);
|
|
2643
|
-
group.keys.forEach((key, index) =>
|
|
2644
|
-
cacheRawValue(scope, key, group.values[index])
|
|
2645
|
-
);
|
|
2653
|
+
group.keys.forEach((key, index) => {
|
|
2654
|
+
cacheRawValue(scope, key, group.values[index]);
|
|
2655
|
+
});
|
|
2646
2656
|
});
|
|
2647
2657
|
if (keysToRemove.length > 0) {
|
|
2648
2658
|
adapter.backend.removeBatch(keysToRemove, scope);
|
|
2649
|
-
keysToRemove.forEach((key) =>
|
|
2659
|
+
keysToRemove.forEach((key) => {
|
|
2660
|
+
cacheRawValue(scope, key, undefined);
|
|
2661
|
+
});
|
|
2650
2662
|
}
|
|
2651
2663
|
}
|
|
2664
|
+
|
|
2665
|
+
if (rollbackEntries.length > 0) {
|
|
2666
|
+
emitBatchChange(
|
|
2667
|
+
scope,
|
|
2668
|
+
"rollback",
|
|
2669
|
+
rollbackSource,
|
|
2670
|
+
rollbackEntries.map(([key], index) =>
|
|
2671
|
+
createKeyChange(
|
|
2672
|
+
scope,
|
|
2673
|
+
key,
|
|
2674
|
+
preRollbackValues[index],
|
|
2675
|
+
getEventRawValue(scope, key),
|
|
2676
|
+
"rollback",
|
|
2677
|
+
rollbackSource,
|
|
2678
|
+
),
|
|
2679
|
+
),
|
|
2680
|
+
);
|
|
2681
|
+
}
|
|
2652
2682
|
throw error;
|
|
2653
2683
|
}
|
|
2654
2684
|
});
|
|
@@ -2771,7 +2801,9 @@ export function createStorageCore(
|
|
|
2771
2801
|
toggle,
|
|
2772
2802
|
values: () => Object.keys(item.get()) as TMember[],
|
|
2773
2803
|
size: () => Object.keys(item.get()).length,
|
|
2774
|
-
clear: () =>
|
|
2804
|
+
clear: () => {
|
|
2805
|
+
item.set({});
|
|
2806
|
+
},
|
|
2775
2807
|
reset: item.reset,
|
|
2776
2808
|
subscribe: item.subscribe,
|
|
2777
2809
|
scope: item.scope,
|