react-native-nitro-storage 0.6.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 +43 -0
- package/README.md +36 -12
- package/SECURITY.md +2 -2
- package/android/src/main/java/com/nitrostorage/AndroidStorageAdapter.kt +225 -76
- package/app.plugin.js +2 -0
- package/cpp/bindings/HybridStorage.cpp +4 -12
- package/docs/api-reference.md +11 -1
- package/docs/batch-transactions-migrations.md +8 -2
- package/docs/benchmarks.md +19 -15
- package/docs/secure-storage.md +4 -0
- 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-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-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 +11 -3
- package/lib/typescript/index.d.ts.map +1 -1
- package/lib/typescript/index.web.d.ts +14 -5
- 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 +14 -4
- 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 +17 -3
- 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 +6 -6
- package/src/capabilities.ts +36 -0
- package/src/core/durability.ts +170 -0
- package/src/core/metrics.ts +133 -0
- package/src/index.ts +67 -42
- package/src/index.web.ts +159 -88
- package/src/indexeddb-backend.ts +92 -22
- package/src/internal.ts +51 -5
- package/src/shared.ts +7 -6
- package/src/storage-core.ts +336 -287
- package/src/storage-events.ts +3 -3
- 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;
|
|
@@ -158,6 +158,19 @@ export type StorageBatchSetItem<T> = {
|
|
|
158
158
|
value: T;
|
|
159
159
|
};
|
|
160
160
|
|
|
161
|
+
type StorageBatchSetCandidate = {
|
|
162
|
+
get: () => unknown;
|
|
163
|
+
};
|
|
164
|
+
|
|
165
|
+
type StorageBatchSetEntries<
|
|
166
|
+
TItems extends readonly StorageBatchSetCandidate[],
|
|
167
|
+
> = {
|
|
168
|
+
[Index in keyof TItems]: {
|
|
169
|
+
item: TItems[Index] & StorageItem<ReturnType<TItems[Index]["get"]>>;
|
|
170
|
+
value: ReturnType<TItems[Index]["get"]>;
|
|
171
|
+
};
|
|
172
|
+
};
|
|
173
|
+
|
|
161
174
|
export type StorageKeyRef = string | { readonly key: string };
|
|
162
175
|
|
|
163
176
|
export type StorageClearOptions = {
|
|
@@ -216,7 +229,6 @@ export type StorageCoreAdapter = {
|
|
|
216
229
|
backend: StorageCoreBackend;
|
|
217
230
|
changeSource: StorageChangeSource;
|
|
218
231
|
applyAccessControlOnSecureRawWrite: boolean;
|
|
219
|
-
flushDiskWritesOnImport: boolean;
|
|
220
232
|
ensureScopeSubscription(scope: NonMemoryScope): void;
|
|
221
233
|
maybeCleanupScopeSubscription(scope: NonMemoryScope): void;
|
|
222
234
|
onWillEmitChanges(
|
|
@@ -286,84 +298,45 @@ export function createStorageCore(
|
|
|
286
298
|
const registeredKeyCounts = new Map<string, number>();
|
|
287
299
|
const memoryStore = new Map<string, unknown>();
|
|
288
300
|
const memoryListeners: KeyListenerRegistry = new Map();
|
|
289
|
-
const scopedListeners
|
|
290
|
-
[StorageScope.Disk
|
|
291
|
-
[StorageScope.Secure
|
|
292
|
-
|
|
293
|
-
const scopedRawCache
|
|
301
|
+
const scopedListeners: Record<NonMemoryScope, KeyListenerRegistry> = {
|
|
302
|
+
[StorageScope.Disk]: new Map(),
|
|
303
|
+
[StorageScope.Secure]: new Map(),
|
|
304
|
+
};
|
|
305
|
+
const scopedRawCache: Record<
|
|
294
306
|
NonMemoryScope,
|
|
295
307
|
Map<string, string | undefined>
|
|
296
|
-
>
|
|
297
|
-
[StorageScope.Disk
|
|
298
|
-
[StorageScope.Secure
|
|
299
|
-
|
|
300
|
-
const pendingDiskWrites = new Map<string, PendingDiskWrite>();
|
|
301
|
-
let diskFlushScheduled = false;
|
|
302
|
-
let diskWritesAsync = false;
|
|
303
|
-
const pendingSecureWrites = new Map<string, PendingSecureWrite>();
|
|
304
|
-
let secureFlushScheduled = false;
|
|
308
|
+
> = {
|
|
309
|
+
[StorageScope.Disk]: new Map(),
|
|
310
|
+
[StorageScope.Secure]: new Map(),
|
|
311
|
+
};
|
|
305
312
|
let secureDefaultAccessControl: AccessControl = AccessControl.WhenUnlocked;
|
|
306
|
-
let metricsObserver: StorageMetricsObserver | undefined;
|
|
307
313
|
let eventObserver: StorageEventListener | undefined;
|
|
308
314
|
let eventObserverRedactSecureValues = true;
|
|
309
|
-
const metricsCounters = new Map<
|
|
310
|
-
string,
|
|
311
|
-
{ count: number; totalDurationMs: number; maxDurationMs: number }
|
|
312
|
-
>();
|
|
313
315
|
const storageEvents = new StorageEventRegistry();
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
existing.count += 1;
|
|
330
|
-
existing.totalDurationMs += durationMs;
|
|
331
|
-
existing.maxDurationMs = Math.max(existing.maxDurationMs, durationMs);
|
|
332
|
-
}
|
|
333
|
-
|
|
334
|
-
metricsObserver?.({
|
|
335
|
-
operation,
|
|
336
|
-
scope,
|
|
337
|
-
durationMs,
|
|
338
|
-
keysCount,
|
|
339
|
-
});
|
|
340
|
-
}
|
|
341
|
-
|
|
342
|
-
function measureOperation<T>(
|
|
343
|
-
operation: string,
|
|
344
|
-
scope: StorageScope,
|
|
345
|
-
fn: () => T,
|
|
346
|
-
keysCount = 1,
|
|
347
|
-
): T {
|
|
348
|
-
if (!metricsObserver) {
|
|
349
|
-
return fn();
|
|
350
|
-
}
|
|
351
|
-
const start = now();
|
|
352
|
-
try {
|
|
353
|
-
return fn();
|
|
354
|
-
} finally {
|
|
355
|
-
recordMetric(operation, scope, now() - start, keysCount);
|
|
356
|
-
}
|
|
357
|
-
}
|
|
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
|
+
});
|
|
358
331
|
|
|
359
332
|
function getScopedListeners(scope: NonMemoryScope): KeyListenerRegistry {
|
|
360
|
-
return scopedListeners
|
|
333
|
+
return scopedListeners[scope];
|
|
361
334
|
}
|
|
362
335
|
|
|
363
336
|
function getScopeRawCache(
|
|
364
337
|
scope: NonMemoryScope,
|
|
365
338
|
): Map<string, string | undefined> {
|
|
366
|
-
return scopedRawCache
|
|
339
|
+
return scopedRawCache[scope];
|
|
367
340
|
}
|
|
368
341
|
|
|
369
342
|
function cacheRawValue(
|
|
@@ -415,7 +388,9 @@ export function createStorageCore(
|
|
|
415
388
|
): string | undefined {
|
|
416
389
|
if (scope === StorageScope.Memory) {
|
|
417
390
|
const value = memoryStore.get(key);
|
|
418
|
-
return typeof value === "string"
|
|
391
|
+
return typeof value === "string"
|
|
392
|
+
? unescapeCollidingRawValue(value)
|
|
393
|
+
: undefined;
|
|
419
394
|
}
|
|
420
395
|
|
|
421
396
|
return getRawValue(key, scope);
|
|
@@ -500,110 +475,58 @@ export function createStorageCore(
|
|
|
500
475
|
eventObserver?.(eventForGlobalObserver(event));
|
|
501
476
|
}
|
|
502
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
|
+
|
|
503
496
|
function readPendingSecureWrite(key: string): string | undefined {
|
|
504
|
-
return
|
|
497
|
+
return durability.readPendingSecureWrite(key);
|
|
505
498
|
}
|
|
506
499
|
|
|
507
500
|
function readPendingDiskWrite(key: string): string | undefined {
|
|
508
|
-
return
|
|
501
|
+
return durability.readPendingDiskWrite(key);
|
|
509
502
|
}
|
|
510
503
|
|
|
511
504
|
function hasPendingDiskWrite(key: string): boolean {
|
|
512
|
-
return
|
|
505
|
+
return durability.hasPendingDiskWrite(key);
|
|
513
506
|
}
|
|
514
507
|
|
|
515
508
|
function hasPendingSecureWrite(key: string): boolean {
|
|
516
|
-
return
|
|
509
|
+
return durability.hasPendingSecureWrite(key);
|
|
517
510
|
}
|
|
518
511
|
|
|
519
512
|
function clearPendingDiskWrite(key: string): void {
|
|
520
|
-
|
|
513
|
+
durability.clearPendingDiskWrite(key);
|
|
521
514
|
}
|
|
522
515
|
|
|
523
516
|
function clearPendingSecureWrite(key: string): void {
|
|
524
|
-
|
|
517
|
+
durability.clearPendingSecureWrite(key);
|
|
525
518
|
}
|
|
526
519
|
|
|
527
520
|
function flushDiskWrites(): void {
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
if (pendingDiskWrites.size === 0) {
|
|
531
|
-
return;
|
|
532
|
-
}
|
|
533
|
-
|
|
534
|
-
const writes = Array.from(pendingDiskWrites.values());
|
|
535
|
-
pendingDiskWrites.clear();
|
|
536
|
-
|
|
537
|
-
const keysToSet: string[] = [];
|
|
538
|
-
const valuesToSet: string[] = [];
|
|
539
|
-
const keysToRemove: string[] = [];
|
|
540
|
-
|
|
541
|
-
writes.forEach(({ key, value }) => {
|
|
542
|
-
if (value === undefined) {
|
|
543
|
-
keysToRemove.push(key);
|
|
544
|
-
return;
|
|
545
|
-
}
|
|
546
|
-
|
|
547
|
-
keysToSet.push(key);
|
|
548
|
-
valuesToSet.push(value);
|
|
549
|
-
});
|
|
550
|
-
|
|
551
|
-
if (keysToSet.length > 0) {
|
|
552
|
-
adapter.backend.setBatch(keysToSet, valuesToSet, StorageScope.Disk);
|
|
553
|
-
}
|
|
554
|
-
if (keysToRemove.length > 0) {
|
|
555
|
-
adapter.backend.removeBatch(keysToRemove, StorageScope.Disk);
|
|
556
|
-
}
|
|
521
|
+
durability.flushDiskWrites();
|
|
557
522
|
}
|
|
558
523
|
|
|
559
524
|
function flushSecureWrites(): void {
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
if (pendingSecureWrites.size === 0) {
|
|
563
|
-
return;
|
|
564
|
-
}
|
|
565
|
-
|
|
566
|
-
const writes = Array.from(pendingSecureWrites.values());
|
|
567
|
-
pendingSecureWrites.clear();
|
|
568
|
-
|
|
569
|
-
const groupedSetWrites = new Map<
|
|
570
|
-
AccessControl,
|
|
571
|
-
{ keys: string[]; values: string[] }
|
|
572
|
-
>();
|
|
573
|
-
const keysToRemove: string[] = [];
|
|
574
|
-
|
|
575
|
-
writes.forEach(({ key, value, accessControl }) => {
|
|
576
|
-
if (value === undefined) {
|
|
577
|
-
keysToRemove.push(key);
|
|
578
|
-
} else {
|
|
579
|
-
const resolvedAccessControl =
|
|
580
|
-
accessControl ?? secureDefaultAccessControl;
|
|
581
|
-
const existingGroup = groupedSetWrites.get(resolvedAccessControl);
|
|
582
|
-
const group = existingGroup ?? { keys: [], values: [] };
|
|
583
|
-
group.keys.push(key);
|
|
584
|
-
group.values.push(value);
|
|
585
|
-
if (!existingGroup) {
|
|
586
|
-
groupedSetWrites.set(resolvedAccessControl, group);
|
|
587
|
-
}
|
|
588
|
-
}
|
|
589
|
-
});
|
|
590
|
-
|
|
591
|
-
groupedSetWrites.forEach((group, accessControl) => {
|
|
592
|
-
adapter.backend.setSecureAccessControl(accessControl);
|
|
593
|
-
adapter.backend.setBatch(group.keys, group.values, StorageScope.Secure);
|
|
594
|
-
});
|
|
595
|
-
if (keysToRemove.length > 0) {
|
|
596
|
-
adapter.backend.removeBatch(keysToRemove, StorageScope.Secure);
|
|
597
|
-
}
|
|
525
|
+
durability.flushSecureWrites();
|
|
598
526
|
}
|
|
599
527
|
|
|
600
528
|
function scheduleDiskWrite(key: string, value: string | undefined): void {
|
|
601
|
-
|
|
602
|
-
if (diskFlushScheduled) {
|
|
603
|
-
return;
|
|
604
|
-
}
|
|
605
|
-
diskFlushScheduled = true;
|
|
606
|
-
runMicrotask(flushDiskWrites);
|
|
529
|
+
durability.scheduleDiskWrite(key, value);
|
|
607
530
|
}
|
|
608
531
|
|
|
609
532
|
function scheduleSecureWrite(
|
|
@@ -611,20 +534,21 @@ export function createStorageCore(
|
|
|
611
534
|
value: string | undefined,
|
|
612
535
|
accessControl?: AccessControl,
|
|
613
536
|
): void {
|
|
614
|
-
|
|
615
|
-
if (accessControl !== undefined) {
|
|
616
|
-
pendingWrite.accessControl = accessControl;
|
|
617
|
-
}
|
|
618
|
-
pendingSecureWrites.set(key, pendingWrite);
|
|
619
|
-
if (secureFlushScheduled) {
|
|
620
|
-
return;
|
|
621
|
-
}
|
|
622
|
-
secureFlushScheduled = true;
|
|
623
|
-
runMicrotask(flushSecureWrites);
|
|
537
|
+
durability.scheduleSecureWrite(key, value, accessControl);
|
|
624
538
|
}
|
|
625
539
|
|
|
626
|
-
function
|
|
627
|
-
|
|
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 {
|
|
628
552
|
if (scope === StorageScope.Memory) {
|
|
629
553
|
const value = memoryStore.get(key);
|
|
630
554
|
return typeof value === "string" ? value : undefined;
|
|
@@ -641,21 +565,49 @@ export function createStorageCore(
|
|
|
641
565
|
return adapter.backend.get(key, scope);
|
|
642
566
|
}
|
|
643
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
|
+
|
|
644
595
|
function setRawValue(key: string, value: string, scope: StorageScope): void {
|
|
645
596
|
assertValidScope(scope);
|
|
597
|
+
const storedValue = escapeCollidingRawValue(value);
|
|
646
598
|
const oldValue =
|
|
647
599
|
scope === StorageScope.Memory ? getEventRawValue(scope, key) : undefined;
|
|
648
600
|
if (scope === StorageScope.Memory) {
|
|
649
|
-
memoryStore.set(key,
|
|
601
|
+
memoryStore.set(key, storedValue);
|
|
650
602
|
notifyKeyListeners(memoryListeners, key);
|
|
651
603
|
emitKeyChange(scope, key, oldValue, value, "set", "memory");
|
|
652
604
|
return;
|
|
653
605
|
}
|
|
654
606
|
|
|
655
607
|
if (scope === StorageScope.Disk) {
|
|
656
|
-
cacheRawValue(scope, key,
|
|
657
|
-
if (
|
|
658
|
-
scheduleDiskWrite(key,
|
|
608
|
+
cacheRawValue(scope, key, storedValue);
|
|
609
|
+
if (isDiskWritesAsync()) {
|
|
610
|
+
scheduleDiskWrite(key, storedValue);
|
|
659
611
|
emitKeyChange(scope, key, oldValue, value, "set", adapter.changeSource);
|
|
660
612
|
return;
|
|
661
613
|
}
|
|
@@ -672,8 +624,8 @@ export function createStorageCore(
|
|
|
672
624
|
}
|
|
673
625
|
}
|
|
674
626
|
|
|
675
|
-
adapter.backend.set(key,
|
|
676
|
-
cacheRawValue(scope, key,
|
|
627
|
+
adapter.backend.set(key, storedValue, scope);
|
|
628
|
+
cacheRawValue(scope, key, storedValue);
|
|
677
629
|
emitKeyChange(scope, key, oldValue, value, "set", adapter.changeSource);
|
|
678
630
|
}
|
|
679
631
|
|
|
@@ -689,7 +641,7 @@ export function createStorageCore(
|
|
|
689
641
|
|
|
690
642
|
if (scope === StorageScope.Disk) {
|
|
691
643
|
cacheRawValue(scope, key, undefined);
|
|
692
|
-
if (
|
|
644
|
+
if (isDiskWritesAsync()) {
|
|
693
645
|
scheduleDiskWrite(key, undefined);
|
|
694
646
|
emitKeyChange(
|
|
695
647
|
scope,
|
|
@@ -733,10 +685,6 @@ export function createStorageCore(
|
|
|
733
685
|
return Number.isFinite(parsed) && parsed > 0 ? parsed : 0;
|
|
734
686
|
}
|
|
735
687
|
|
|
736
|
-
function writeMigrationVersion(scope: StorageScope, version: number): void {
|
|
737
|
-
setRawValue(MIGRATION_VERSION_KEY, String(version), scope);
|
|
738
|
-
}
|
|
739
|
-
|
|
740
688
|
const internals: StorageCoreInternals = {
|
|
741
689
|
getScopedListeners,
|
|
742
690
|
cacheRawValue,
|
|
@@ -745,10 +693,10 @@ export function createStorageCore(
|
|
|
745
693
|
clearPendingDiskWrite,
|
|
746
694
|
clearPendingSecureWrite,
|
|
747
695
|
clearAllPendingDiskWrites: () => {
|
|
748
|
-
|
|
696
|
+
durability.clearAllPendingDiskWrites();
|
|
749
697
|
},
|
|
750
698
|
clearAllPendingSecureWrites: () => {
|
|
751
|
-
|
|
699
|
+
durability.clearAllPendingSecureWrites();
|
|
752
700
|
},
|
|
753
701
|
flushDiskWrites,
|
|
754
702
|
flushSecureWrites,
|
|
@@ -788,7 +736,9 @@ export function createStorageCore(
|
|
|
788
736
|
? removeKeys.map((key) => getEventRawValue(scope, key))
|
|
789
737
|
: [];
|
|
790
738
|
removeKeys.forEach((key) => memoryStore.delete(key));
|
|
791
|
-
removeKeys.forEach((key) =>
|
|
739
|
+
removeKeys.forEach((key) => {
|
|
740
|
+
notifyKeyListeners(memoryListeners, key);
|
|
741
|
+
});
|
|
792
742
|
emitBatchChange(
|
|
793
743
|
scope,
|
|
794
744
|
"clear",
|
|
@@ -821,10 +771,18 @@ export function createStorageCore(
|
|
|
821
771
|
return;
|
|
822
772
|
}
|
|
823
773
|
const previousValues = shouldReadPreviousEventValues(scope)
|
|
824
|
-
? adapter.backend
|
|
774
|
+
? adapter.backend
|
|
775
|
+
.getBatch(removeKeys, scope)
|
|
776
|
+
.map((value) =>
|
|
777
|
+
value === undefined
|
|
778
|
+
? undefined
|
|
779
|
+
: unescapeCollidingRawValue(value),
|
|
780
|
+
)
|
|
825
781
|
: [];
|
|
826
782
|
adapter.backend.removeBatch(removeKeys, scope);
|
|
827
|
-
removeKeys.forEach((key) =>
|
|
783
|
+
removeKeys.forEach((key) => {
|
|
784
|
+
cacheRawValue(scope, key, undefined);
|
|
785
|
+
});
|
|
828
786
|
emitBatchChange(
|
|
829
787
|
scope,
|
|
830
788
|
"clear",
|
|
@@ -948,12 +906,12 @@ export function createStorageCore(
|
|
|
948
906
|
|
|
949
907
|
if (scope === StorageScope.Disk) {
|
|
950
908
|
flushDiskWrites();
|
|
951
|
-
|
|
909
|
+
durability.clearAllPendingDiskWrites();
|
|
952
910
|
}
|
|
953
911
|
|
|
954
912
|
if (scope === StorageScope.Secure) {
|
|
955
913
|
flushSecureWrites();
|
|
956
|
-
|
|
914
|
+
durability.clearAllPendingSecureWrites();
|
|
957
915
|
}
|
|
958
916
|
|
|
959
917
|
clearScopeRawCache(scope);
|
|
@@ -1050,7 +1008,7 @@ export function createStorageCore(
|
|
|
1050
1008
|
}
|
|
1051
1009
|
const separatorIndex = registryKey.indexOf(":");
|
|
1052
1010
|
duplicates.push({
|
|
1053
|
-
scope: Number(registryKey.slice(0, separatorIndex))
|
|
1011
|
+
scope: Number(registryKey.slice(0, separatorIndex)),
|
|
1054
1012
|
key: registryKey.slice(separatorIndex + 1),
|
|
1055
1013
|
count,
|
|
1056
1014
|
});
|
|
@@ -1062,7 +1020,7 @@ export function createStorageCore(
|
|
|
1062
1020
|
registeredKeyCounts.forEach((_count, registryKey) => {
|
|
1063
1021
|
const separatorIndex = registryKey.indexOf(":");
|
|
1064
1022
|
result.push({
|
|
1065
|
-
scope: Number(registryKey.slice(0, separatorIndex))
|
|
1023
|
+
scope: Number(registryKey.slice(0, separatorIndex)),
|
|
1066
1024
|
key: registryKey.slice(separatorIndex + 1),
|
|
1067
1025
|
});
|
|
1068
1026
|
});
|
|
@@ -1087,9 +1045,9 @@ export function createStorageCore(
|
|
|
1087
1045
|
affectedKeys.forEach((key) => {
|
|
1088
1046
|
memoryStore.delete(key);
|
|
1089
1047
|
});
|
|
1090
|
-
affectedKeys.forEach((key) =>
|
|
1091
|
-
notifyKeyListeners(memoryListeners, key)
|
|
1092
|
-
);
|
|
1048
|
+
affectedKeys.forEach((key) => {
|
|
1049
|
+
notifyKeyListeners(memoryListeners, key);
|
|
1050
|
+
});
|
|
1093
1051
|
emitBatchChange(
|
|
1094
1052
|
scope,
|
|
1095
1053
|
"clearNamespace",
|
|
@@ -1226,7 +1184,7 @@ export function createStorageCore(
|
|
|
1226
1184
|
keys.forEach((key, idx) => {
|
|
1227
1185
|
const value = values[idx];
|
|
1228
1186
|
if (value !== undefined) {
|
|
1229
|
-
result[key] = value;
|
|
1187
|
+
result[key] = unescapeCollidingRawValue(value);
|
|
1230
1188
|
}
|
|
1231
1189
|
});
|
|
1232
1190
|
return result;
|
|
@@ -1239,7 +1197,8 @@ export function createStorageCore(
|
|
|
1239
1197
|
if (scope === StorageScope.Memory) {
|
|
1240
1198
|
for (const key of memoryStore.keys()) {
|
|
1241
1199
|
const value = memoryStore.get(key);
|
|
1242
|
-
if (typeof value === "string")
|
|
1200
|
+
if (typeof value === "string")
|
|
1201
|
+
result[key] = unescapeCollidingRawValue(value);
|
|
1243
1202
|
}
|
|
1244
1203
|
return result;
|
|
1245
1204
|
}
|
|
@@ -1254,7 +1213,7 @@ export function createStorageCore(
|
|
|
1254
1213
|
const values = adapter.backend.getBatch(keys, scope);
|
|
1255
1214
|
keys.forEach((key, idx) => {
|
|
1256
1215
|
const val = values[idx];
|
|
1257
|
-
if (val !== undefined) result[key] = val;
|
|
1216
|
+
if (val !== undefined) result[key] = unescapeCollidingRawValue(val);
|
|
1258
1217
|
});
|
|
1259
1218
|
return result;
|
|
1260
1219
|
});
|
|
@@ -1299,7 +1258,7 @@ export function createStorageCore(
|
|
|
1299
1258
|
},
|
|
1300
1259
|
setDiskWritesAsync: (enabled: boolean) => {
|
|
1301
1260
|
measureOperation("storage:setDiskWritesAsync", StorageScope.Disk, () => {
|
|
1302
|
-
|
|
1261
|
+
setDiskWritesAsyncMode(enabled);
|
|
1303
1262
|
if (!enabled) {
|
|
1304
1263
|
flushDiskWrites();
|
|
1305
1264
|
}
|
|
@@ -1316,23 +1275,16 @@ export function createStorageCore(
|
|
|
1316
1275
|
});
|
|
1317
1276
|
},
|
|
1318
1277
|
setMetricsObserver: (observer?: StorageMetricsObserver) => {
|
|
1319
|
-
|
|
1278
|
+
metrics.setObserver(observer);
|
|
1320
1279
|
},
|
|
1321
1280
|
getMetricsSnapshot: (): Record<string, StorageMetricSummary> => {
|
|
1322
|
-
|
|
1323
|
-
|
|
1324
|
-
|
|
1325
|
-
|
|
1326
|
-
totalDurationMs: value.totalDurationMs,
|
|
1327
|
-
avgDurationMs:
|
|
1328
|
-
value.count === 0 ? 0 : value.totalDurationMs / value.count,
|
|
1329
|
-
maxDurationMs: value.maxDurationMs,
|
|
1330
|
-
};
|
|
1331
|
-
});
|
|
1332
|
-
return snapshot;
|
|
1281
|
+
return metrics.getSnapshot();
|
|
1282
|
+
},
|
|
1283
|
+
getScopedMetricsSnapshot: (): Record<string, StorageMetricSummary> => {
|
|
1284
|
+
return metrics.getScopedSnapshot();
|
|
1333
1285
|
},
|
|
1334
1286
|
resetMetrics: () => {
|
|
1335
|
-
|
|
1287
|
+
metrics.reset();
|
|
1336
1288
|
},
|
|
1337
1289
|
getSecureMetadata: (key: string): SecureStorageMetadata => {
|
|
1338
1290
|
return measureOperation(
|
|
@@ -1397,7 +1349,8 @@ export function createStorageCore(
|
|
|
1397
1349
|
() => {
|
|
1398
1350
|
assertValidScope(scope);
|
|
1399
1351
|
if (keys.length === 0) return;
|
|
1400
|
-
const values = keys.map((k) => data[k]
|
|
1352
|
+
const values = keys.map((k) => data[k] as string);
|
|
1353
|
+
const storedValues = values.map(escapeCollidingRawValue);
|
|
1401
1354
|
const changes = keys.map((key, index) =>
|
|
1402
1355
|
createKeyChange(
|
|
1403
1356
|
scope,
|
|
@@ -1411,9 +1364,11 @@ export function createStorageCore(
|
|
|
1411
1364
|
|
|
1412
1365
|
if (scope === StorageScope.Memory) {
|
|
1413
1366
|
keys.forEach((key, index) => {
|
|
1414
|
-
memoryStore.set(key,
|
|
1367
|
+
memoryStore.set(key, storedValues[index]);
|
|
1368
|
+
});
|
|
1369
|
+
keys.forEach((key) => {
|
|
1370
|
+
notifyKeyListeners(memoryListeners, key);
|
|
1415
1371
|
});
|
|
1416
|
-
keys.forEach((key) => notifyKeyListeners(memoryListeners, key));
|
|
1417
1372
|
emitBatchChange(scope, "import", "memory", changes);
|
|
1418
1373
|
return;
|
|
1419
1374
|
}
|
|
@@ -1422,14 +1377,14 @@ export function createStorageCore(
|
|
|
1422
1377
|
flushSecureWrites();
|
|
1423
1378
|
adapter.backend.setSecureAccessControl(secureDefaultAccessControl);
|
|
1424
1379
|
}
|
|
1425
|
-
if (scope === StorageScope.Disk
|
|
1380
|
+
if (scope === StorageScope.Disk) {
|
|
1426
1381
|
flushDiskWrites();
|
|
1427
1382
|
}
|
|
1428
1383
|
|
|
1429
|
-
adapter.backend.setBatch(keys,
|
|
1430
|
-
keys.forEach((key, index) =>
|
|
1431
|
-
cacheRawValue(scope, key,
|
|
1432
|
-
);
|
|
1384
|
+
adapter.backend.setBatch(keys, storedValues, scope);
|
|
1385
|
+
keys.forEach((key, index) => {
|
|
1386
|
+
cacheRawValue(scope, key, storedValues[index]);
|
|
1387
|
+
});
|
|
1433
1388
|
emitBatchChange(scope, "import", adapter.changeSource, changes);
|
|
1434
1389
|
},
|
|
1435
1390
|
keys.length,
|
|
@@ -1509,6 +1464,15 @@ export function createStorageCore(
|
|
|
1509
1464
|
lastExpiresAt = undefined;
|
|
1510
1465
|
};
|
|
1511
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
|
+
|
|
1512
1476
|
const ensureSubscription = () => {
|
|
1513
1477
|
if (unsubscribe) {
|
|
1514
1478
|
return;
|
|
@@ -1516,7 +1480,9 @@ export function createStorageCore(
|
|
|
1516
1480
|
|
|
1517
1481
|
const listener = () => {
|
|
1518
1482
|
invalidateParsedCache();
|
|
1519
|
-
listeners.forEach((callback) =>
|
|
1483
|
+
listeners.forEach((callback) => {
|
|
1484
|
+
callback();
|
|
1485
|
+
});
|
|
1520
1486
|
};
|
|
1521
1487
|
|
|
1522
1488
|
if (isMemory) {
|
|
@@ -1524,9 +1490,9 @@ export function createStorageCore(
|
|
|
1524
1490
|
return;
|
|
1525
1491
|
}
|
|
1526
1492
|
|
|
1527
|
-
adapter.ensureScopeSubscription(
|
|
1493
|
+
adapter.ensureScopeSubscription(resolveNonMemoryScope());
|
|
1528
1494
|
unsubscribe = addKeyListener(
|
|
1529
|
-
getScopedListeners(
|
|
1495
|
+
getScopedListeners(resolveNonMemoryScope()),
|
|
1530
1496
|
storageKey,
|
|
1531
1497
|
listener,
|
|
1532
1498
|
);
|
|
@@ -1544,7 +1510,9 @@ export function createStorageCore(
|
|
|
1544
1510
|
emitKeyChange(
|
|
1545
1511
|
config.scope,
|
|
1546
1512
|
storageKey,
|
|
1547
|
-
typeof expiredRaw === "string"
|
|
1513
|
+
typeof expiredRaw === "string"
|
|
1514
|
+
? unescapeCollidingRawValue(expiredRaw)
|
|
1515
|
+
: undefined,
|
|
1548
1516
|
undefined,
|
|
1549
1517
|
"expire",
|
|
1550
1518
|
"memory",
|
|
@@ -1553,27 +1521,30 @@ export function createStorageCore(
|
|
|
1553
1521
|
return undefined;
|
|
1554
1522
|
}
|
|
1555
1523
|
}
|
|
1556
|
-
|
|
1524
|
+
const memoryStored = memoryStore.get(storageKey);
|
|
1525
|
+
return typeof memoryStored === "string"
|
|
1526
|
+
? unescapeCollidingRawValue(memoryStored)
|
|
1527
|
+
: memoryStored;
|
|
1557
1528
|
}
|
|
1558
1529
|
|
|
1559
1530
|
migrateRenamesIfNeeded();
|
|
1560
1531
|
|
|
1561
1532
|
if (nonMemoryScope === StorageScope.Disk) {
|
|
1562
|
-
const pending =
|
|
1533
|
+
const pending = durability.readPendingDiskWrite(storageKey);
|
|
1563
1534
|
if (pending !== undefined) {
|
|
1564
|
-
return pending
|
|
1535
|
+
return pending;
|
|
1565
1536
|
}
|
|
1566
1537
|
}
|
|
1567
1538
|
|
|
1568
1539
|
if (nonMemoryScope === StorageScope.Secure && !isBiometric) {
|
|
1569
|
-
const pending =
|
|
1540
|
+
const pending = durability.readPendingSecureWrite(storageKey);
|
|
1570
1541
|
if (pending !== undefined) {
|
|
1571
|
-
return pending
|
|
1542
|
+
return pending;
|
|
1572
1543
|
}
|
|
1573
1544
|
}
|
|
1574
1545
|
|
|
1575
1546
|
if (readCache) {
|
|
1576
|
-
const cache = getScopeRawCache(
|
|
1547
|
+
const cache = getScopeRawCache(resolveNonMemoryScope());
|
|
1577
1548
|
const cached = cache.get(storageKey);
|
|
1578
1549
|
if (cached !== undefined || cache.has(storageKey)) {
|
|
1579
1550
|
return cached;
|
|
@@ -1589,7 +1560,7 @@ export function createStorageCore(
|
|
|
1589
1560
|
const raw = readBackendRaw(() =>
|
|
1590
1561
|
adapter.backend.get(storageKey, config.scope),
|
|
1591
1562
|
);
|
|
1592
|
-
cacheRawValue(
|
|
1563
|
+
cacheRawValue(resolveNonMemoryScope(), storageKey, raw);
|
|
1593
1564
|
return raw;
|
|
1594
1565
|
};
|
|
1595
1566
|
|
|
@@ -1601,7 +1572,9 @@ export function createStorageCore(
|
|
|
1601
1572
|
} catch (error) {
|
|
1602
1573
|
onReadError?.(error);
|
|
1603
1574
|
if (fallbackToCacheOnReadError) {
|
|
1604
|
-
const cached = getScopeRawCache(
|
|
1575
|
+
const cached = getScopeRawCache(resolveNonMemoryScope()).get(
|
|
1576
|
+
storageKey,
|
|
1577
|
+
);
|
|
1605
1578
|
if (cached !== undefined) {
|
|
1606
1579
|
return cached;
|
|
1607
1580
|
}
|
|
@@ -1630,10 +1603,10 @@ export function createStorageCore(
|
|
|
1630
1603
|
return;
|
|
1631
1604
|
}
|
|
1632
1605
|
|
|
1633
|
-
cacheRawValue(
|
|
1606
|
+
cacheRawValue(resolveNonMemoryScope(), storageKey, rawValue);
|
|
1634
1607
|
|
|
1635
1608
|
if (nonMemoryScope === StorageScope.Disk) {
|
|
1636
|
-
if (coalesceDiskWrites ||
|
|
1609
|
+
if (coalesceDiskWrites || isDiskWritesAsync()) {
|
|
1637
1610
|
scheduleDiskWrite(storageKey, rawValue);
|
|
1638
1611
|
emitKeyChange(
|
|
1639
1612
|
config.scope,
|
|
@@ -1754,10 +1727,10 @@ export function createStorageCore(
|
|
|
1754
1727
|
return;
|
|
1755
1728
|
}
|
|
1756
1729
|
|
|
1757
|
-
cacheRawValue(
|
|
1730
|
+
cacheRawValue(resolveNonMemoryScope(), storageKey, undefined);
|
|
1758
1731
|
|
|
1759
1732
|
if (nonMemoryScope === StorageScope.Disk) {
|
|
1760
|
-
if (coalesceDiskWrites ||
|
|
1733
|
+
if (coalesceDiskWrites || isDiskWritesAsync()) {
|
|
1761
1734
|
scheduleDiskWrite(storageKey, undefined);
|
|
1762
1735
|
emitKeyChange(
|
|
1763
1736
|
config.scope,
|
|
@@ -1811,7 +1784,9 @@ export function createStorageCore(
|
|
|
1811
1784
|
if (memoryExpiration) {
|
|
1812
1785
|
memoryExpiration.set(storageKey, Date.now() + (expirationTtlMs ?? 0));
|
|
1813
1786
|
}
|
|
1814
|
-
|
|
1787
|
+
const storedValue =
|
|
1788
|
+
typeof value === "string" ? escapeCollidingRawValue(value) : value;
|
|
1789
|
+
memoryStore.set(storageKey, storedValue);
|
|
1815
1790
|
notifyKeyListeners(memoryListeners, storageKey);
|
|
1816
1791
|
emitKeyChange(
|
|
1817
1792
|
config.scope,
|
|
@@ -1882,7 +1857,9 @@ export function createStorageCore(
|
|
|
1882
1857
|
onExpired?.(storageKey);
|
|
1883
1858
|
lastValue = ensureValidatedValue(defaultValue, false);
|
|
1884
1859
|
hasLastValue = true;
|
|
1885
|
-
listeners.forEach((cb) =>
|
|
1860
|
+
listeners.forEach((cb) => {
|
|
1861
|
+
cb();
|
|
1862
|
+
});
|
|
1886
1863
|
return lastValue;
|
|
1887
1864
|
}
|
|
1888
1865
|
}
|
|
@@ -1924,7 +1901,9 @@ export function createStorageCore(
|
|
|
1924
1901
|
onExpired?.(storageKey);
|
|
1925
1902
|
lastValue = ensureValidatedValue(defaultValue, false);
|
|
1926
1903
|
hasLastValue = true;
|
|
1927
|
-
listeners.forEach((cb) =>
|
|
1904
|
+
listeners.forEach((cb) => {
|
|
1905
|
+
cb();
|
|
1906
|
+
});
|
|
1928
1907
|
return lastValue;
|
|
1929
1908
|
}
|
|
1930
1909
|
|
|
@@ -1945,7 +1924,12 @@ export function createStorageCore(
|
|
|
1945
1924
|
|
|
1946
1925
|
const getCurrentVersion = (): StorageVersion => {
|
|
1947
1926
|
const raw = readStoredRaw();
|
|
1948
|
-
|
|
1927
|
+
if (raw === undefined) {
|
|
1928
|
+
return toVersionToken(undefined);
|
|
1929
|
+
}
|
|
1930
|
+
return toVersionToken(
|
|
1931
|
+
typeof raw === "string" ? unescapeCollidingRawValue(raw) : raw,
|
|
1932
|
+
);
|
|
1949
1933
|
};
|
|
1950
1934
|
|
|
1951
1935
|
const get = (): T =>
|
|
@@ -2039,15 +2023,13 @@ export function createStorageCore(
|
|
|
2039
2023
|
if (isMemory) return memoryStore.has(storageKey);
|
|
2040
2024
|
if (isBiometric) return adapter.backend.hasSecureBiometric(storageKey);
|
|
2041
2025
|
if (nonMemoryScope === StorageScope.Disk) {
|
|
2042
|
-
|
|
2043
|
-
|
|
2044
|
-
return pending.value !== undefined;
|
|
2026
|
+
if (durability.hasPendingDiskWrite(storageKey)) {
|
|
2027
|
+
return durability.readPendingDiskWrite(storageKey) !== undefined;
|
|
2045
2028
|
}
|
|
2046
2029
|
}
|
|
2047
2030
|
if (nonMemoryScope === StorageScope.Secure) {
|
|
2048
|
-
|
|
2049
|
-
|
|
2050
|
-
return pending.value !== undefined;
|
|
2031
|
+
if (durability.hasPendingSecureWrite(storageKey)) {
|
|
2032
|
+
return durability.readPendingSecureWrite(storageKey) !== undefined;
|
|
2051
2033
|
}
|
|
2052
2034
|
}
|
|
2053
2035
|
return adapter.backend.has(storageKey, config.scope);
|
|
@@ -2061,7 +2043,7 @@ export function createStorageCore(
|
|
|
2061
2043
|
if (listeners.size === 0 && unsubscribe) {
|
|
2062
2044
|
unsubscribe();
|
|
2063
2045
|
if (!isMemory) {
|
|
2064
|
-
adapter.maybeCleanupScopeSubscription(
|
|
2046
|
+
adapter.maybeCleanupScopeSubscription(resolveNonMemoryScope());
|
|
2065
2047
|
}
|
|
2066
2048
|
unsubscribe = null;
|
|
2067
2049
|
}
|
|
@@ -2108,11 +2090,20 @@ export function createStorageCore(
|
|
|
2108
2090
|
deserialize,
|
|
2109
2091
|
_triggerListeners: () => {
|
|
2110
2092
|
invalidateParsedCache();
|
|
2111
|
-
listeners.forEach((listener) =>
|
|
2093
|
+
listeners.forEach((listener) => {
|
|
2094
|
+
listener();
|
|
2095
|
+
});
|
|
2112
2096
|
},
|
|
2113
2097
|
_invalidateParsedCacheOnly: () => {
|
|
2114
2098
|
invalidateParsedCache();
|
|
2115
2099
|
},
|
|
2100
|
+
_deleteMemoryEntry: () => {
|
|
2101
|
+
if (memoryExpiration) {
|
|
2102
|
+
memoryExpiration.delete(storageKey);
|
|
2103
|
+
}
|
|
2104
|
+
memoryStore.delete(storageKey);
|
|
2105
|
+
invalidateParsedCache();
|
|
2106
|
+
},
|
|
2116
2107
|
_hasValidation: validate !== undefined,
|
|
2117
2108
|
_hasExpiration: expiration !== undefined,
|
|
2118
2109
|
_readCacheEnabled: readCache,
|
|
@@ -2174,17 +2165,17 @@ export function createStorageCore(
|
|
|
2174
2165
|
|
|
2175
2166
|
items.forEach((item, index) => {
|
|
2176
2167
|
if (scope === StorageScope.Disk) {
|
|
2177
|
-
const pending =
|
|
2168
|
+
const pending = durability.readPendingDiskWrite(item.key);
|
|
2178
2169
|
if (pending !== undefined) {
|
|
2179
|
-
rawValues[index] = pending
|
|
2170
|
+
rawValues[index] = pending;
|
|
2180
2171
|
return;
|
|
2181
2172
|
}
|
|
2182
2173
|
}
|
|
2183
2174
|
|
|
2184
2175
|
if (scope === StorageScope.Secure) {
|
|
2185
|
-
const pending =
|
|
2176
|
+
const pending = durability.readPendingSecureWrite(item.key);
|
|
2186
2177
|
if (pending !== undefined) {
|
|
2187
|
-
rawValues[index] = pending
|
|
2178
|
+
rawValues[index] = pending;
|
|
2188
2179
|
return;
|
|
2189
2180
|
}
|
|
2190
2181
|
}
|
|
@@ -2227,6 +2218,10 @@ export function createStorageCore(
|
|
|
2227
2218
|
) as BatchValues<TItems>;
|
|
2228
2219
|
}
|
|
2229
2220
|
|
|
2221
|
+
function setBatch<const TItems extends readonly StorageBatchSetCandidate[]>(
|
|
2222
|
+
items: StorageBatchSetEntries<TItems>,
|
|
2223
|
+
scope: StorageScope,
|
|
2224
|
+
): void;
|
|
2230
2225
|
function setBatch<T>(
|
|
2231
2226
|
items: readonly StorageBatchSetItem<T>[],
|
|
2232
2227
|
scope: StorageScope,
|
|
@@ -2249,7 +2244,9 @@ export function createStorageCore(
|
|
|
2249
2244
|
|
|
2250
2245
|
if (needsIndividualSets) {
|
|
2251
2246
|
// Fall back to individual sets to preserve validation and TTL semantics
|
|
2252
|
-
items.forEach(({ item, value }) =>
|
|
2247
|
+
items.forEach(({ item, value }) => {
|
|
2248
|
+
item.set(value);
|
|
2249
|
+
});
|
|
2253
2250
|
return;
|
|
2254
2251
|
}
|
|
2255
2252
|
|
|
@@ -2271,9 +2268,9 @@ export function createStorageCore(
|
|
|
2271
2268
|
item as StorageItem<unknown>,
|
|
2272
2269
|
)._invalidateParsedCacheOnly();
|
|
2273
2270
|
});
|
|
2274
|
-
items.forEach(({ item }) =>
|
|
2275
|
-
notifyKeyListeners(memoryListeners, item.key)
|
|
2276
|
-
);
|
|
2271
|
+
items.forEach(({ item }) => {
|
|
2272
|
+
notifyKeyListeners(memoryListeners, item.key);
|
|
2273
|
+
});
|
|
2277
2274
|
emitBatchChange(scope, "setBatch", "memory", changes);
|
|
2278
2275
|
return;
|
|
2279
2276
|
}
|
|
@@ -2288,14 +2285,22 @@ export function createStorageCore(
|
|
|
2288
2285
|
canUseSecureRawBatchPath(internal),
|
|
2289
2286
|
);
|
|
2290
2287
|
if (!canUseSecureBatchPath) {
|
|
2291
|
-
items.forEach(({ item, value }) =>
|
|
2288
|
+
items.forEach(({ item, value }) => {
|
|
2289
|
+
item.set(value);
|
|
2290
|
+
});
|
|
2292
2291
|
return;
|
|
2293
2292
|
}
|
|
2294
2293
|
|
|
2295
2294
|
flushSecureWrites();
|
|
2296
2295
|
const keys = secureEntries.map(({ item }) => item.key);
|
|
2297
2296
|
const oldValues = shouldReadPreviousEventValues(scope)
|
|
2298
|
-
? adapter.backend
|
|
2297
|
+
? adapter.backend
|
|
2298
|
+
.getBatch(keys, scope)
|
|
2299
|
+
.map((value) =>
|
|
2300
|
+
value === undefined
|
|
2301
|
+
? undefined
|
|
2302
|
+
: unescapeCollidingRawValue(value),
|
|
2303
|
+
)
|
|
2299
2304
|
: [];
|
|
2300
2305
|
const groupedByAccessControl = new Map<
|
|
2301
2306
|
number,
|
|
@@ -2317,9 +2322,9 @@ export function createStorageCore(
|
|
|
2317
2322
|
groupedByAccessControl.forEach((group, accessControl) => {
|
|
2318
2323
|
adapter.backend.setSecureAccessControl(accessControl);
|
|
2319
2324
|
adapter.backend.setBatch(group.keys, group.values, scope);
|
|
2320
|
-
group.keys.forEach((key, index) =>
|
|
2321
|
-
cacheRawValue(scope, key, group.values[index])
|
|
2322
|
-
);
|
|
2325
|
+
group.keys.forEach((key, index) => {
|
|
2326
|
+
cacheRawValue(scope, key, group.values[index]);
|
|
2327
|
+
});
|
|
2323
2328
|
});
|
|
2324
2329
|
emitBatchChange(
|
|
2325
2330
|
scope,
|
|
@@ -2345,18 +2350,28 @@ export function createStorageCore(
|
|
|
2345
2350
|
canUseRawBatchPath(asInternal(item)),
|
|
2346
2351
|
);
|
|
2347
2352
|
if (!useRawBatchPath) {
|
|
2348
|
-
items.forEach(({ item, value }) =>
|
|
2353
|
+
items.forEach(({ item, value }) => {
|
|
2354
|
+
item.set(value);
|
|
2355
|
+
});
|
|
2349
2356
|
return;
|
|
2350
2357
|
}
|
|
2351
2358
|
|
|
2352
2359
|
const keys = items.map((entry) => entry.item.key);
|
|
2353
2360
|
const values = items.map((entry) => entry.item.serialize(entry.value));
|
|
2354
2361
|
const oldValues = shouldReadPreviousEventValues(scope)
|
|
2355
|
-
? adapter.backend
|
|
2362
|
+
? adapter.backend
|
|
2363
|
+
.getBatch(keys, scope)
|
|
2364
|
+
.map((value) =>
|
|
2365
|
+
value === undefined
|
|
2366
|
+
? undefined
|
|
2367
|
+
: unescapeCollidingRawValue(value),
|
|
2368
|
+
)
|
|
2356
2369
|
: [];
|
|
2357
2370
|
|
|
2358
2371
|
adapter.backend.setBatch(keys, values, scope);
|
|
2359
|
-
keys.forEach((key, index) =>
|
|
2372
|
+
keys.forEach((key, index) => {
|
|
2373
|
+
cacheRawValue(scope, key, values[index]);
|
|
2374
|
+
});
|
|
2360
2375
|
emitBatchChange(
|
|
2361
2376
|
scope,
|
|
2362
2377
|
"setBatch",
|
|
@@ -2398,7 +2413,12 @@ export function createStorageCore(
|
|
|
2398
2413
|
"memory",
|
|
2399
2414
|
),
|
|
2400
2415
|
);
|
|
2401
|
-
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
|
+
});
|
|
2402
2422
|
emitBatchChange(scope, "removeBatch", "memory", changes);
|
|
2403
2423
|
return;
|
|
2404
2424
|
}
|
|
@@ -2411,10 +2431,18 @@ export function createStorageCore(
|
|
|
2411
2431
|
flushSecureWrites();
|
|
2412
2432
|
}
|
|
2413
2433
|
const oldValues = shouldReadPreviousEventValues(scope)
|
|
2414
|
-
? adapter.backend
|
|
2434
|
+
? adapter.backend
|
|
2435
|
+
.getBatch(keys, scope)
|
|
2436
|
+
.map((value) =>
|
|
2437
|
+
value === undefined
|
|
2438
|
+
? undefined
|
|
2439
|
+
: unescapeCollidingRawValue(value),
|
|
2440
|
+
)
|
|
2415
2441
|
: [];
|
|
2416
2442
|
adapter.backend.removeBatch(keys, scope);
|
|
2417
|
-
keys.forEach((key) =>
|
|
2443
|
+
keys.forEach((key) => {
|
|
2444
|
+
cacheRawValue(scope, key, undefined);
|
|
2445
|
+
});
|
|
2418
2446
|
emitBatchChange(
|
|
2419
2447
|
scope,
|
|
2420
2448
|
"removeBatch",
|
|
@@ -2456,26 +2484,19 @@ export function createStorageCore(
|
|
|
2456
2484
|
.sort((a, b) => a - b);
|
|
2457
2485
|
|
|
2458
2486
|
let appliedVersion = currentVersion;
|
|
2459
|
-
const context: MigrationContext = {
|
|
2460
|
-
scope,
|
|
2461
|
-
getRaw: (key) => getRawValue(key, scope),
|
|
2462
|
-
setRaw: (key, value) => setRawValue(key, value, scope),
|
|
2463
|
-
removeRaw: (key) => removeRawValue(key, scope),
|
|
2464
|
-
};
|
|
2465
2487
|
|
|
2466
2488
|
versions.forEach((version) => {
|
|
2467
2489
|
const migration = registeredMigrations.get(version);
|
|
2468
2490
|
if (!migration) {
|
|
2469
2491
|
return;
|
|
2470
2492
|
}
|
|
2471
|
-
|
|
2493
|
+
runTransaction(scope, (tx) => {
|
|
2494
|
+
migration(tx);
|
|
2495
|
+
tx.setRaw(MIGRATION_VERSION_KEY, String(version));
|
|
2496
|
+
});
|
|
2472
2497
|
appliedVersion = version;
|
|
2473
2498
|
});
|
|
2474
2499
|
|
|
2475
|
-
if (appliedVersion !== currentVersion) {
|
|
2476
|
-
writeMigrationVersion(scope, appliedVersion);
|
|
2477
|
-
}
|
|
2478
|
-
|
|
2479
2500
|
return appliedVersion;
|
|
2480
2501
|
});
|
|
2481
2502
|
}
|
|
@@ -2525,7 +2546,7 @@ export function createStorageCore(
|
|
|
2525
2546
|
}
|
|
2526
2547
|
rollback.set(key, {
|
|
2527
2548
|
kind: "raw",
|
|
2528
|
-
value:
|
|
2549
|
+
value: getStoredRawValue(key, scope),
|
|
2529
2550
|
...(scope === StorageScope.Secure &&
|
|
2530
2551
|
internal?._secureAccessControl !== undefined
|
|
2531
2552
|
? { accessControl: internal._secureAccessControl }
|
|
@@ -2565,6 +2586,12 @@ export function createStorageCore(
|
|
|
2565
2586
|
return transaction(tx);
|
|
2566
2587
|
} catch (error) {
|
|
2567
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
|
+
: [];
|
|
2568
2595
|
if (scope === StorageScope.Memory) {
|
|
2569
2596
|
rollbackEntries.forEach(([key, record]) => {
|
|
2570
2597
|
if (record.value === NOT_SET) {
|
|
@@ -2623,15 +2650,35 @@ export function createStorageCore(
|
|
|
2623
2650
|
adapter.backend.setSecureAccessControl(accessControl);
|
|
2624
2651
|
}
|
|
2625
2652
|
adapter.backend.setBatch(group.keys, group.values, scope);
|
|
2626
|
-
group.keys.forEach((key, index) =>
|
|
2627
|
-
cacheRawValue(scope, key, group.values[index])
|
|
2628
|
-
);
|
|
2653
|
+
group.keys.forEach((key, index) => {
|
|
2654
|
+
cacheRawValue(scope, key, group.values[index]);
|
|
2655
|
+
});
|
|
2629
2656
|
});
|
|
2630
2657
|
if (keysToRemove.length > 0) {
|
|
2631
2658
|
adapter.backend.removeBatch(keysToRemove, scope);
|
|
2632
|
-
keysToRemove.forEach((key) =>
|
|
2659
|
+
keysToRemove.forEach((key) => {
|
|
2660
|
+
cacheRawValue(scope, key, undefined);
|
|
2661
|
+
});
|
|
2633
2662
|
}
|
|
2634
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
|
+
}
|
|
2635
2682
|
throw error;
|
|
2636
2683
|
}
|
|
2637
2684
|
});
|
|
@@ -2754,7 +2801,9 @@ export function createStorageCore(
|
|
|
2754
2801
|
toggle,
|
|
2755
2802
|
values: () => Object.keys(item.get()) as TMember[],
|
|
2756
2803
|
size: () => Object.keys(item.get()).length,
|
|
2757
|
-
clear: () =>
|
|
2804
|
+
clear: () => {
|
|
2805
|
+
item.set({});
|
|
2806
|
+
},
|
|
2758
2807
|
reset: item.reset,
|
|
2759
2808
|
subscribe: item.subscribe,
|
|
2760
2809
|
scope: item.scope,
|