react-native-nitro-storage 0.4.1 → 0.4.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +39 -0
- package/android/build.gradle +0 -12
- package/android/consumer-rules.pro +26 -4
- package/android/src/main/cpp/AndroidStorageAdapterCpp.cpp +7 -10
- package/android/src/main/cpp/AndroidStorageAdapterCpp.hpp +0 -4
- package/android/src/main/java/com/nitrostorage/AndroidStorageAdapter.kt +172 -77
- package/cpp/bindings/HybridStorage.cpp +120 -69
- package/cpp/bindings/HybridStorage.hpp +4 -0
- package/ios/IOSStorageAdapterCpp.hpp +2 -1
- package/ios/IOSStorageAdapterCpp.mm +264 -49
- package/lib/commonjs/index.js +73 -21
- package/lib/commonjs/index.js.map +1 -1
- package/lib/commonjs/index.web.js +120 -42
- package/lib/commonjs/index.web.js.map +1 -1
- package/lib/commonjs/internal.js +51 -23
- package/lib/commonjs/internal.js.map +1 -1
- package/lib/module/index.js +72 -21
- package/lib/module/index.js.map +1 -1
- package/lib/module/index.web.js +119 -42
- package/lib/module/index.web.js.map +1 -1
- package/lib/module/internal.js +51 -23
- package/lib/module/internal.js.map +1 -1
- package/lib/typescript/index.d.ts +4 -0
- package/lib/typescript/index.d.ts.map +1 -1
- package/lib/typescript/index.web.d.ts +5 -1
- package/lib/typescript/index.web.d.ts.map +1 -1
- package/lib/typescript/internal.d.ts.map +1 -1
- package/package.json +1 -1
- package/src/index.ts +83 -28
- package/src/index.web.ts +135 -50
- package/src/internal.ts +51 -23
package/src/index.web.ts
CHANGED
|
@@ -163,10 +163,12 @@ const webScopeKeyIndex = new Map<NonMemoryScope, Set<string>>([
|
|
|
163
163
|
const hydratedWebScopeKeyIndex = new Set<NonMemoryScope>();
|
|
164
164
|
const pendingSecureWrites = new Map<string, PendingSecureWrite>();
|
|
165
165
|
let secureFlushScheduled = false;
|
|
166
|
+
let secureDefaultAccessControl: AccessControl = AccessControl.WhenUnlocked;
|
|
166
167
|
const SECURE_WEB_PREFIX = "__secure_";
|
|
167
168
|
const BIOMETRIC_WEB_PREFIX = "__bio_";
|
|
168
169
|
let hasWarnedAboutWebBiometricFallback = false;
|
|
169
170
|
let hasWebStorageEventSubscription = false;
|
|
171
|
+
let webStorageSubscriberCount = 0;
|
|
170
172
|
let metricsObserver: StorageMetricsObserver | undefined;
|
|
171
173
|
const metricsCounters = new Map<
|
|
172
174
|
string,
|
|
@@ -200,6 +202,9 @@ function measureOperation<T>(
|
|
|
200
202
|
fn: () => T,
|
|
201
203
|
keysCount = 1,
|
|
202
204
|
): T {
|
|
205
|
+
if (!metricsObserver) {
|
|
206
|
+
return fn();
|
|
207
|
+
}
|
|
203
208
|
const start = Date.now();
|
|
204
209
|
try {
|
|
205
210
|
return fn();
|
|
@@ -232,6 +237,9 @@ function createLocalStorageWebSecureBackend(): WebSecureStorageBackend {
|
|
|
232
237
|
let webSecureStorageBackend: WebSecureStorageBackend | undefined =
|
|
233
238
|
createLocalStorageWebSecureBackend();
|
|
234
239
|
|
|
240
|
+
let cachedSecureBrowserStorage: BrowserStorageLike | undefined;
|
|
241
|
+
let cachedSecureBackendRef: WebSecureStorageBackend | undefined;
|
|
242
|
+
|
|
235
243
|
function getBrowserStorage(scope: number): BrowserStorageLike | undefined {
|
|
236
244
|
if (scope === StorageScope.Disk) {
|
|
237
245
|
return globalThis.localStorage;
|
|
@@ -240,16 +248,24 @@ function getBrowserStorage(scope: number): BrowserStorageLike | undefined {
|
|
|
240
248
|
if (!webSecureStorageBackend) {
|
|
241
249
|
return undefined;
|
|
242
250
|
}
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
251
|
+
if (
|
|
252
|
+
cachedSecureBackendRef === webSecureStorageBackend &&
|
|
253
|
+
cachedSecureBrowserStorage
|
|
254
|
+
) {
|
|
255
|
+
return cachedSecureBrowserStorage;
|
|
256
|
+
}
|
|
257
|
+
cachedSecureBackendRef = webSecureStorageBackend;
|
|
258
|
+
cachedSecureBrowserStorage = {
|
|
259
|
+
setItem: (key, value) => webSecureStorageBackend!.setItem(key, value),
|
|
260
|
+
getItem: (key) => webSecureStorageBackend!.getItem(key) ?? null,
|
|
261
|
+
removeItem: (key) => webSecureStorageBackend!.removeItem(key),
|
|
262
|
+
clear: () => webSecureStorageBackend!.clear(),
|
|
263
|
+
key: (index) => webSecureStorageBackend!.getAllKeys()[index] ?? null,
|
|
249
264
|
get length() {
|
|
250
|
-
return webSecureStorageBackend
|
|
265
|
+
return webSecureStorageBackend!.getAllKeys().length;
|
|
251
266
|
},
|
|
252
267
|
};
|
|
268
|
+
return cachedSecureBrowserStorage;
|
|
253
269
|
}
|
|
254
270
|
return undefined;
|
|
255
271
|
}
|
|
@@ -370,17 +386,27 @@ function handleWebStorageEvent(event: StorageEvent): void {
|
|
|
370
386
|
}
|
|
371
387
|
|
|
372
388
|
function ensureWebStorageEventSubscription(): void {
|
|
373
|
-
|
|
374
|
-
|
|
389
|
+
webStorageSubscriberCount += 1;
|
|
390
|
+
if (
|
|
391
|
+
webStorageSubscriberCount === 1 &&
|
|
392
|
+
typeof window !== "undefined" &&
|
|
393
|
+
typeof window.addEventListener === "function"
|
|
394
|
+
) {
|
|
395
|
+
window.addEventListener("storage", handleWebStorageEvent);
|
|
396
|
+
hasWebStorageEventSubscription = true;
|
|
375
397
|
}
|
|
398
|
+
}
|
|
399
|
+
|
|
400
|
+
function maybeCleanupWebStorageSubscription(): void {
|
|
401
|
+
webStorageSubscriberCount = Math.max(0, webStorageSubscriberCount - 1);
|
|
376
402
|
if (
|
|
377
|
-
|
|
378
|
-
|
|
403
|
+
webStorageSubscriberCount === 0 &&
|
|
404
|
+
hasWebStorageEventSubscription &&
|
|
405
|
+
typeof window !== "undefined"
|
|
379
406
|
) {
|
|
380
|
-
|
|
407
|
+
window.removeEventListener("storage", handleWebStorageEvent);
|
|
408
|
+
hasWebStorageEventSubscription = false;
|
|
381
409
|
}
|
|
382
|
-
window.addEventListener("storage", handleWebStorageEvent);
|
|
383
|
-
hasWebStorageEventSubscription = true;
|
|
384
410
|
}
|
|
385
411
|
|
|
386
412
|
function getScopedListeners(scope: NonMemoryScope): KeyListenerRegistry {
|
|
@@ -417,13 +443,20 @@ function clearScopeRawCache(scope: NonMemoryScope): void {
|
|
|
417
443
|
}
|
|
418
444
|
|
|
419
445
|
function notifyKeyListeners(registry: KeyListenerRegistry, key: string): void {
|
|
420
|
-
registry.get(key)
|
|
446
|
+
const listeners = registry.get(key);
|
|
447
|
+
if (listeners) {
|
|
448
|
+
for (const listener of listeners) {
|
|
449
|
+
listener();
|
|
450
|
+
}
|
|
451
|
+
}
|
|
421
452
|
}
|
|
422
453
|
|
|
423
454
|
function notifyAllListeners(registry: KeyListenerRegistry): void {
|
|
424
|
-
registry.
|
|
425
|
-
|
|
426
|
-
|
|
455
|
+
for (const listeners of registry.values()) {
|
|
456
|
+
for (const listener of listeners) {
|
|
457
|
+
listener();
|
|
458
|
+
}
|
|
459
|
+
}
|
|
427
460
|
}
|
|
428
461
|
|
|
429
462
|
function addKeyListener(
|
|
@@ -482,7 +515,7 @@ function flushSecureWrites(): void {
|
|
|
482
515
|
if (value === undefined) {
|
|
483
516
|
keysToRemove.push(key);
|
|
484
517
|
} else {
|
|
485
|
-
const resolvedAccessControl = accessControl ??
|
|
518
|
+
const resolvedAccessControl = accessControl ?? secureDefaultAccessControl;
|
|
486
519
|
const existingGroup = groupedSetWrites.get(resolvedAccessControl);
|
|
487
520
|
const group = existingGroup ?? { keys: [], values: [] };
|
|
488
521
|
group.keys.push(key);
|
|
@@ -882,7 +915,12 @@ export const storage = {
|
|
|
882
915
|
if (scope === StorageScope.Secure) {
|
|
883
916
|
flushSecureWrites();
|
|
884
917
|
}
|
|
885
|
-
|
|
918
|
+
const scopeCache = getScopeRawCache(scope);
|
|
919
|
+
for (const key of scopeCache.keys()) {
|
|
920
|
+
if (isNamespaced(key, namespace)) {
|
|
921
|
+
scopeCache.delete(key);
|
|
922
|
+
}
|
|
923
|
+
}
|
|
886
924
|
WebStorage.removeByPrefix(keyPrefix, scope);
|
|
887
925
|
});
|
|
888
926
|
},
|
|
@@ -958,9 +996,13 @@ export const storage = {
|
|
|
958
996
|
return result;
|
|
959
997
|
}
|
|
960
998
|
const keys = WebStorage.getAllKeys(scope);
|
|
961
|
-
keys.
|
|
962
|
-
|
|
963
|
-
|
|
999
|
+
if (keys.length === 0) return {};
|
|
1000
|
+
const values = WebStorage.getBatch(keys, scope);
|
|
1001
|
+
keys.forEach((key, index) => {
|
|
1002
|
+
const val = values[index];
|
|
1003
|
+
if (val !== undefined && val !== null) {
|
|
1004
|
+
result[key] = val;
|
|
1005
|
+
}
|
|
964
1006
|
});
|
|
965
1007
|
return result;
|
|
966
1008
|
});
|
|
@@ -972,7 +1014,8 @@ export const storage = {
|
|
|
972
1014
|
return WebStorage.size(scope);
|
|
973
1015
|
});
|
|
974
1016
|
},
|
|
975
|
-
setAccessControl: (
|
|
1017
|
+
setAccessControl: (level: AccessControl) => {
|
|
1018
|
+
secureDefaultAccessControl = level;
|
|
976
1019
|
recordMetric("storage:setAccessControl", StorageScope.Secure, 0);
|
|
977
1020
|
},
|
|
978
1021
|
setSecureWritesAsync: (_enabled: boolean) => {
|
|
@@ -1005,13 +1048,28 @@ export const storage = {
|
|
|
1005
1048
|
resetMetrics: () => {
|
|
1006
1049
|
metricsCounters.clear();
|
|
1007
1050
|
},
|
|
1051
|
+
getString: (key: string, scope: StorageScope): string | undefined => {
|
|
1052
|
+
return measureOperation("storage:getString", scope, () => {
|
|
1053
|
+
return getRawValue(key, scope);
|
|
1054
|
+
});
|
|
1055
|
+
},
|
|
1056
|
+
setString: (key: string, value: string, scope: StorageScope): void => {
|
|
1057
|
+
measureOperation("storage:setString", scope, () => {
|
|
1058
|
+
setRawValue(key, value, scope);
|
|
1059
|
+
});
|
|
1060
|
+
},
|
|
1061
|
+
deleteString: (key: string, scope: StorageScope): void => {
|
|
1062
|
+
measureOperation("storage:deleteString", scope, () => {
|
|
1063
|
+
removeRawValue(key, scope);
|
|
1064
|
+
});
|
|
1065
|
+
},
|
|
1008
1066
|
import: (data: Record<string, string>, scope: StorageScope): void => {
|
|
1067
|
+
const keys = Object.keys(data);
|
|
1009
1068
|
measureOperation(
|
|
1010
1069
|
"storage:import",
|
|
1011
1070
|
scope,
|
|
1012
1071
|
() => {
|
|
1013
1072
|
assertValidScope(scope);
|
|
1014
|
-
const keys = Object.keys(data);
|
|
1015
1073
|
if (keys.length === 0) return;
|
|
1016
1074
|
const values = keys.map((k) => data[k]!);
|
|
1017
1075
|
|
|
@@ -1023,9 +1081,15 @@ export const storage = {
|
|
|
1023
1081
|
return;
|
|
1024
1082
|
}
|
|
1025
1083
|
|
|
1084
|
+
if (scope === StorageScope.Secure) {
|
|
1085
|
+
flushSecureWrites();
|
|
1086
|
+
WebStorage.setSecureAccessControl(secureDefaultAccessControl);
|
|
1087
|
+
}
|
|
1088
|
+
|
|
1026
1089
|
WebStorage.setBatch(keys, values, scope);
|
|
1090
|
+
keys.forEach((key, index) => cacheRawValue(scope, key, values[index]));
|
|
1027
1091
|
},
|
|
1028
|
-
|
|
1092
|
+
keys.length,
|
|
1029
1093
|
);
|
|
1030
1094
|
},
|
|
1031
1095
|
};
|
|
@@ -1034,6 +1098,8 @@ export function setWebSecureStorageBackend(
|
|
|
1034
1098
|
backend?: WebSecureStorageBackend,
|
|
1035
1099
|
): void {
|
|
1036
1100
|
webSecureStorageBackend = backend ?? createLocalStorageWebSecureBackend();
|
|
1101
|
+
cachedSecureBrowserStorage = undefined;
|
|
1102
|
+
cachedSecureBackendRef = undefined;
|
|
1037
1103
|
hydratedWebScopeKeyIndex.delete(StorageScope.Secure);
|
|
1038
1104
|
clearScopeRawCache(StorageScope.Secure);
|
|
1039
1105
|
}
|
|
@@ -1207,17 +1273,18 @@ export function createStorageItem<T = undefined>(
|
|
|
1207
1273
|
return memoryStore.get(storageKey);
|
|
1208
1274
|
}
|
|
1209
1275
|
|
|
1210
|
-
if (
|
|
1211
|
-
|
|
1212
|
-
|
|
1213
|
-
|
|
1214
|
-
|
|
1215
|
-
return readPendingSecureWrite(storageKey);
|
|
1276
|
+
if (nonMemoryScope === StorageScope.Secure && !isBiometric) {
|
|
1277
|
+
const pending = pendingSecureWrites.get(storageKey);
|
|
1278
|
+
if (pending !== undefined) {
|
|
1279
|
+
return pending.value;
|
|
1280
|
+
}
|
|
1216
1281
|
}
|
|
1217
1282
|
|
|
1218
1283
|
if (readCache) {
|
|
1219
|
-
|
|
1220
|
-
|
|
1284
|
+
const cache = getScopeRawCache(nonMemoryScope!);
|
|
1285
|
+
const cached = cache.get(storageKey);
|
|
1286
|
+
if (cached !== undefined || cache.has(storageKey)) {
|
|
1287
|
+
return cached;
|
|
1221
1288
|
}
|
|
1222
1289
|
}
|
|
1223
1290
|
|
|
@@ -1246,7 +1313,7 @@ export function createStorageItem<T = undefined>(
|
|
|
1246
1313
|
scheduleSecureWrite(
|
|
1247
1314
|
storageKey,
|
|
1248
1315
|
rawValue,
|
|
1249
|
-
secureAccessControl ??
|
|
1316
|
+
secureAccessControl ?? secureDefaultAccessControl,
|
|
1250
1317
|
);
|
|
1251
1318
|
return;
|
|
1252
1319
|
}
|
|
@@ -1270,7 +1337,7 @@ export function createStorageItem<T = undefined>(
|
|
|
1270
1337
|
scheduleSecureWrite(
|
|
1271
1338
|
storageKey,
|
|
1272
1339
|
undefined,
|
|
1273
|
-
secureAccessControl ??
|
|
1340
|
+
secureAccessControl ?? secureDefaultAccessControl,
|
|
1274
1341
|
);
|
|
1275
1342
|
return;
|
|
1276
1343
|
}
|
|
@@ -1431,14 +1498,13 @@ export function createStorageItem<T = undefined>(
|
|
|
1431
1498
|
? valueOrFn(getInternal())
|
|
1432
1499
|
: valueOrFn;
|
|
1433
1500
|
|
|
1434
|
-
invalidateParsedCache();
|
|
1435
|
-
|
|
1436
1501
|
if (validate && !validate(newValue)) {
|
|
1437
1502
|
throw new Error(
|
|
1438
1503
|
`Validation failed for key "${storageKey}" in scope "${StorageScope[config.scope]}".`,
|
|
1439
1504
|
);
|
|
1440
1505
|
}
|
|
1441
1506
|
|
|
1507
|
+
invalidateParsedCache();
|
|
1442
1508
|
writeValueWithoutValidation(newValue);
|
|
1443
1509
|
});
|
|
1444
1510
|
};
|
|
@@ -1488,6 +1554,9 @@ export function createStorageItem<T = undefined>(
|
|
|
1488
1554
|
if (listeners.size === 0 && unsubscribe) {
|
|
1489
1555
|
unsubscribe();
|
|
1490
1556
|
unsubscribe = null;
|
|
1557
|
+
if (!isMemory) {
|
|
1558
|
+
maybeCleanupWebStorageSubscription();
|
|
1559
|
+
}
|
|
1491
1560
|
}
|
|
1492
1561
|
};
|
|
1493
1562
|
};
|
|
@@ -1574,15 +1643,18 @@ export function getBatch(
|
|
|
1574
1643
|
|
|
1575
1644
|
items.forEach((item, index) => {
|
|
1576
1645
|
if (scope === StorageScope.Secure) {
|
|
1577
|
-
|
|
1578
|
-
|
|
1646
|
+
const pending = pendingSecureWrites.get(item.key);
|
|
1647
|
+
if (pending !== undefined) {
|
|
1648
|
+
rawValues[index] = pending.value;
|
|
1579
1649
|
return;
|
|
1580
1650
|
}
|
|
1581
1651
|
}
|
|
1582
1652
|
|
|
1583
1653
|
if (item._readCacheEnabled === true) {
|
|
1584
|
-
|
|
1585
|
-
|
|
1654
|
+
const cache = getScopeRawCache(scope);
|
|
1655
|
+
const cached = cache.get(item.key);
|
|
1656
|
+
if (cached !== undefined || cache.has(item.key)) {
|
|
1657
|
+
rawValues[index] = cached;
|
|
1586
1658
|
return;
|
|
1587
1659
|
}
|
|
1588
1660
|
}
|
|
@@ -1674,7 +1746,7 @@ export function setBatch<T>(
|
|
|
1674
1746
|
|
|
1675
1747
|
secureEntries.forEach(({ item, value, internal }) => {
|
|
1676
1748
|
const accessControl =
|
|
1677
|
-
internal._secureAccessControl ??
|
|
1749
|
+
internal._secureAccessControl ?? secureDefaultAccessControl;
|
|
1678
1750
|
const existingGroup = groupedByAccessControl.get(accessControl);
|
|
1679
1751
|
const group = existingGroup ?? { keys: [], values: [] };
|
|
1680
1752
|
group.keys.push(item.key);
|
|
@@ -1773,10 +1845,13 @@ export function migrateToLatest(
|
|
|
1773
1845
|
return;
|
|
1774
1846
|
}
|
|
1775
1847
|
migration(context);
|
|
1776
|
-
writeMigrationVersion(scope, version);
|
|
1777
1848
|
appliedVersion = version;
|
|
1778
1849
|
});
|
|
1779
1850
|
|
|
1851
|
+
if (appliedVersion !== currentVersion) {
|
|
1852
|
+
writeMigrationVersion(scope, appliedVersion);
|
|
1853
|
+
}
|
|
1854
|
+
|
|
1780
1855
|
return appliedVersion;
|
|
1781
1856
|
});
|
|
1782
1857
|
}
|
|
@@ -1791,13 +1866,18 @@ export function runTransaction<T>(
|
|
|
1791
1866
|
flushSecureWrites();
|
|
1792
1867
|
}
|
|
1793
1868
|
|
|
1794
|
-
const
|
|
1869
|
+
const NOT_SET = Symbol();
|
|
1870
|
+
const rollback = new Map<string, unknown>();
|
|
1795
1871
|
|
|
1796
1872
|
const rememberRollback = (key: string) => {
|
|
1797
1873
|
if (rollback.has(key)) {
|
|
1798
1874
|
return;
|
|
1799
1875
|
}
|
|
1800
|
-
|
|
1876
|
+
if (scope === StorageScope.Memory) {
|
|
1877
|
+
rollback.set(key, memoryStore.has(key) ? memoryStore.get(key) : NOT_SET);
|
|
1878
|
+
} else {
|
|
1879
|
+
rollback.set(key, getRawValue(key, scope));
|
|
1880
|
+
}
|
|
1801
1881
|
};
|
|
1802
1882
|
|
|
1803
1883
|
const tx: TransactionContext = {
|
|
@@ -1833,11 +1913,12 @@ export function runTransaction<T>(
|
|
|
1833
1913
|
const rollbackEntries = Array.from(rollback.entries()).reverse();
|
|
1834
1914
|
if (scope === StorageScope.Memory) {
|
|
1835
1915
|
rollbackEntries.forEach(([key, previousValue]) => {
|
|
1836
|
-
if (previousValue ===
|
|
1837
|
-
|
|
1916
|
+
if (previousValue === NOT_SET) {
|
|
1917
|
+
memoryStore.delete(key);
|
|
1838
1918
|
} else {
|
|
1839
|
-
|
|
1919
|
+
memoryStore.set(key, previousValue);
|
|
1840
1920
|
}
|
|
1921
|
+
notifyKeyListeners(memoryListeners, key);
|
|
1841
1922
|
});
|
|
1842
1923
|
} else {
|
|
1843
1924
|
const keysToSet: string[] = [];
|
|
@@ -1849,7 +1930,7 @@ export function runTransaction<T>(
|
|
|
1849
1930
|
keysToRemove.push(key);
|
|
1850
1931
|
} else {
|
|
1851
1932
|
keysToSet.push(key);
|
|
1852
|
-
valuesToSet.push(previousValue);
|
|
1933
|
+
valuesToSet.push(previousValue as string);
|
|
1853
1934
|
}
|
|
1854
1935
|
});
|
|
1855
1936
|
|
|
@@ -1915,3 +1996,7 @@ export function createSecureAuthStorage<K extends string>(
|
|
|
1915
1996
|
|
|
1916
1997
|
return result as Record<K, StorageItem<string>>;
|
|
1917
1998
|
}
|
|
1999
|
+
|
|
2000
|
+
export function isKeychainLockedError(_err: unknown): boolean {
|
|
2001
|
+
return false;
|
|
2002
|
+
}
|
package/src/internal.ts
CHANGED
|
@@ -4,6 +4,15 @@ export const MIGRATION_VERSION_KEY = "__nitro_storage_migration_version__";
|
|
|
4
4
|
export const NATIVE_BATCH_MISSING_SENTINEL =
|
|
5
5
|
"__nitro_storage_batch_missing__::v1";
|
|
6
6
|
const PRIMITIVE_FAST_PATH_PREFIX = "__nitro_storage_primitive__:";
|
|
7
|
+
const PRIM_NULL = "__nitro_storage_primitive__:l";
|
|
8
|
+
const PRIM_UNDEFINED = "__nitro_storage_primitive__:u";
|
|
9
|
+
const PRIM_TRUE = "__nitro_storage_primitive__:b:1";
|
|
10
|
+
const PRIM_FALSE = "__nitro_storage_primitive__:b:0";
|
|
11
|
+
const PRIM_STRING_PREFIX = "__nitro_storage_primitive__:s:";
|
|
12
|
+
const PRIM_NUMBER_PREFIX = "__nitro_storage_primitive__:n:";
|
|
13
|
+
const PRIM_INFINITY = "__nitro_storage_primitive__:n:Infinity";
|
|
14
|
+
const PRIM_NEG_INFINITY = "__nitro_storage_primitive__:n:-Infinity";
|
|
15
|
+
const PRIM_NAN = "__nitro_storage_primitive__:n:NaN";
|
|
7
16
|
const NAMESPACE_SEPARATOR = ":";
|
|
8
17
|
const VERSION_TOKEN_PREFIX = "__nitro_storage_version__:";
|
|
9
18
|
|
|
@@ -80,21 +89,30 @@ export function isNamespaced(key: string, namespace: string): boolean {
|
|
|
80
89
|
|
|
81
90
|
export function serializeWithPrimitiveFastPath<T>(value: T): string {
|
|
82
91
|
if (value === null) {
|
|
83
|
-
return
|
|
92
|
+
return PRIM_NULL;
|
|
84
93
|
}
|
|
85
94
|
|
|
86
95
|
switch (typeof value) {
|
|
87
96
|
case "string":
|
|
88
|
-
return
|
|
97
|
+
return PRIM_STRING_PREFIX + (value as string);
|
|
89
98
|
case "number":
|
|
90
99
|
if (Number.isFinite(value)) {
|
|
91
|
-
return
|
|
100
|
+
return PRIM_NUMBER_PREFIX + String(value);
|
|
101
|
+
}
|
|
102
|
+
if (Number.isNaN(value as number)) {
|
|
103
|
+
return PRIM_NAN;
|
|
104
|
+
}
|
|
105
|
+
if (value === Infinity) {
|
|
106
|
+
return PRIM_INFINITY;
|
|
107
|
+
}
|
|
108
|
+
if (value === -Infinity) {
|
|
109
|
+
return PRIM_NEG_INFINITY;
|
|
92
110
|
}
|
|
93
111
|
break;
|
|
94
112
|
case "boolean":
|
|
95
|
-
return
|
|
113
|
+
return value ? PRIM_TRUE : PRIM_FALSE;
|
|
96
114
|
case "undefined":
|
|
97
|
-
return
|
|
115
|
+
return PRIM_UNDEFINED;
|
|
98
116
|
default:
|
|
99
117
|
break;
|
|
100
118
|
}
|
|
@@ -108,31 +126,41 @@ export function serializeWithPrimitiveFastPath<T>(value: T): string {
|
|
|
108
126
|
return serialized;
|
|
109
127
|
}
|
|
110
128
|
|
|
129
|
+
// charCode constants for fast tag dispatch
|
|
130
|
+
const CHAR_U = 117; // 'u'
|
|
131
|
+
const CHAR_L = 108; // 'l'
|
|
132
|
+
const CHAR_S = 115; // 's'
|
|
133
|
+
const CHAR_B = 98; // 'b'
|
|
134
|
+
const CHAR_N = 110; // 'n'
|
|
135
|
+
|
|
111
136
|
export function deserializeWithPrimitiveFastPath<T>(value: string): T {
|
|
112
137
|
if (value.startsWith(PRIMITIVE_FAST_PATH_PREFIX)) {
|
|
113
|
-
const
|
|
114
|
-
|
|
138
|
+
const prefixLen = PRIMITIVE_FAST_PATH_PREFIX.length;
|
|
139
|
+
const tagChar = value.charCodeAt(prefixLen);
|
|
140
|
+
|
|
141
|
+
if (tagChar === CHAR_U) {
|
|
115
142
|
return undefined as T;
|
|
116
143
|
}
|
|
117
|
-
if (
|
|
144
|
+
if (tagChar === CHAR_L) {
|
|
118
145
|
return null as T;
|
|
119
146
|
}
|
|
120
147
|
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
if (
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
148
|
+
// Tagged values have format: prefix + tag + ':' + payload
|
|
149
|
+
const payload = value.slice(prefixLen + 2);
|
|
150
|
+
|
|
151
|
+
if (tagChar === CHAR_S) {
|
|
152
|
+
return payload as T;
|
|
153
|
+
}
|
|
154
|
+
if (tagChar === CHAR_B) {
|
|
155
|
+
return (payload === "1") as T;
|
|
156
|
+
}
|
|
157
|
+
if (tagChar === CHAR_N) {
|
|
158
|
+
if (payload === "NaN") return NaN as T;
|
|
159
|
+
if (payload === "Infinity") return Infinity as T;
|
|
160
|
+
if (payload === "-Infinity") return -Infinity as T;
|
|
161
|
+
const parsed = Number(payload);
|
|
162
|
+
if (Number.isFinite(parsed)) {
|
|
163
|
+
return parsed as T;
|
|
136
164
|
}
|
|
137
165
|
}
|
|
138
166
|
}
|