react-native-onyx 3.0.95 → 3.0.97
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/dist/Onyx.d.ts +1 -1
- package/dist/Onyx.js +42 -11
- package/dist/OnyxUtils.d.ts +6 -0
- package/dist/OnyxUtils.js +24 -0
- package/dist/storage/InstanceSync/index.web.d.ts +17 -11
- package/dist/storage/InstanceSync/index.web.js +110 -10
- package/dist/storage/__mocks__/index.d.ts +1 -0
- package/dist/storage/errors.d.ts +4 -0
- package/dist/storage/errors.js +4 -0
- package/dist/storage/index.js +3 -3
- package/dist/storage/providers/SQLiteProvider.js +11 -6
- package/dist/storage/providers/classifySQLiteError.js +3 -0
- package/dist/storage/providers/types.d.ts +5 -4
- package/package.json +1 -1
package/dist/Onyx.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import * as Logger from './Logger';
|
|
2
|
-
import type { CollectionKeyBase, ConnectOptions, InitOptions, OnyxKey, OnyxMergeCollectionInput,
|
|
2
|
+
import type { CollectionKeyBase, ConnectOptions, InitOptions, OnyxKey, OnyxMergeCollectionInput, OnyxMergeInput, OnyxMultiSetInput, OnyxSetCollectionInput, OnyxSetInput, OnyxUpdate, SetOptions } from './types';
|
|
3
3
|
import type { Connection } from './OnyxConnectionManager';
|
|
4
4
|
/** Initialize the store with actions and listening for storage events */
|
|
5
5
|
declare function init({ keys, initialKeyStates, evictableKeys, shouldSyncMultipleInstances, enableDevTools, skippableCollectionMemberIDs, ramOnlyKeys, snapshotMergeKeys, }: InitOptions): void;
|
package/dist/Onyx.js
CHANGED
|
@@ -55,18 +55,49 @@ function init({ keys = {}, initialKeyStates = {}, evictableKeys = [], shouldSync
|
|
|
55
55
|
OnyxUtils_1.default.setSnapshotMergeKeys(new Set(snapshotMergeKeys));
|
|
56
56
|
OnyxKeys_1.default.setRamOnlyKeys(new Set(ramOnlyKeys));
|
|
57
57
|
if (shouldSyncMultipleInstances) {
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
58
|
+
// Cross-tab sync (InstanceSync) hands us the full batch of key/value pairs that changed together in
|
|
59
|
+
// a single write. We process it synchronously, grouping collection members so each affected
|
|
60
|
+
// collection is notified once (mirroring the local mergeCollection batching) instead of
|
|
61
|
+
// re-delivering the whole collection per member.
|
|
62
|
+
(_a = storage_1.default.keepInstancesSync) === null || _a === void 0 ? void 0 : _a.call(storage_1.default, (pairs) => {
|
|
63
|
+
const individual = [];
|
|
64
|
+
const collectionBatches = new Map();
|
|
65
|
+
for (const [key, value] of pairs) {
|
|
66
|
+
// RAM-only keys should never sync from storage as they may have stale persisted data
|
|
67
|
+
// from before the key was migrated to RAM-only.
|
|
68
|
+
if (OnyxKeys_1.default.isRamOnlyKey(key)) {
|
|
69
|
+
continue;
|
|
70
|
+
}
|
|
71
|
+
const collectionKey = OnyxKeys_1.default.getCollectionKey(key);
|
|
72
|
+
const isCollectionMember = !!collectionKey && OnyxKeys_1.default.isCollectionMemberKey(collectionKey, key);
|
|
73
|
+
// Capture the previous cached value BEFORE cache.set() so keysChanged() can diff old vs new per member.
|
|
74
|
+
const previousValue = isCollectionMember ? OnyxCache_1.default.get(key) : undefined;
|
|
75
|
+
OnyxCache_1.default.set(key, value);
|
|
76
|
+
if (isCollectionMember && collectionKey) {
|
|
77
|
+
let batch = collectionBatches.get(collectionKey);
|
|
78
|
+
if (!batch) {
|
|
79
|
+
batch = { partial: {}, previous: {} };
|
|
80
|
+
collectionBatches.set(collectionKey, batch);
|
|
81
|
+
}
|
|
82
|
+
batch.partial[key] = value;
|
|
83
|
+
// Keep the earliest previous value in case the same member appears twice in one batch.
|
|
84
|
+
if (!(key in batch.previous)) {
|
|
85
|
+
batch.previous[key] = previousValue;
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
else {
|
|
89
|
+
individual.push([key, value]);
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
// Non-collection keys: notify individually, matching keyChanged() semantics for exact keys.
|
|
93
|
+
for (const [key, value] of individual) {
|
|
94
|
+
OnyxUtils_1.default.keyChanged(key, value);
|
|
95
|
+
}
|
|
96
|
+
// One keysChanged() per collection notifies the collection-root subscriber once and lets
|
|
97
|
+
// keysChanged() decide which individual member subscribers actually changed.
|
|
98
|
+
for (const [collectionKey, { partial, previous }] of collectionBatches) {
|
|
99
|
+
OnyxUtils_1.default.keysChanged(collectionKey, partial, previous);
|
|
63
100
|
}
|
|
64
|
-
OnyxCache_1.default.set(key, value);
|
|
65
|
-
// Check if this is a collection member key to prevent duplicate callbacks
|
|
66
|
-
// When a collection is updated, individual members sync separately to other tabs
|
|
67
|
-
// Setting isProcessingCollectionUpdate=true prevents triggering collection callbacks for each individual update
|
|
68
|
-
const isKeyCollectionMember = OnyxKeys_1.default.isCollectionMember(key);
|
|
69
|
-
OnyxUtils_1.default.keyChanged(key, value, undefined, isKeyCollectionMember);
|
|
70
101
|
});
|
|
71
102
|
}
|
|
72
103
|
OnyxUtils_1.default.initStoreValues(keys, initialKeyStates, evictableKeys);
|
package/dist/OnyxUtils.d.ts
CHANGED
|
@@ -12,6 +12,8 @@ declare const METHOD: {
|
|
|
12
12
|
readonly MULTI_SET: "multiset";
|
|
13
13
|
readonly CLEAR: "clear";
|
|
14
14
|
};
|
|
15
|
+
/** Test-only: clears the disk-pressure log throttle so each test observes its own alert. */
|
|
16
|
+
declare function resetDiskPressureLogThrottle(): void;
|
|
15
17
|
type OnyxMethod = ValueOf<typeof METHOD>;
|
|
16
18
|
declare function getSnapshotKey(): OnyxKey | null;
|
|
17
19
|
/**
|
|
@@ -143,6 +145,9 @@ declare function reportStorageQuota(error?: Error): Promise<void>;
|
|
|
143
145
|
* - CAPACITY: evicts the least recently accessed evictable key and retries, under a session-level
|
|
144
146
|
* circuit breaker (see lib/StorageCircuitBreaker.ts) that halts the loop once eviction stops making
|
|
145
147
|
* progress or failures storm — the per-operation budget alone cannot stop a session-wide storm.
|
|
148
|
+
* - DISK_PRESSURE: the device disk itself is full (or the database files are unreadable), so neither
|
|
149
|
+
* retries nor in-DB eviction can free space — the write is dropped (cache stays authoritative) with
|
|
150
|
+
* a single throttled alert + quota snapshot per burst.
|
|
146
151
|
* - UNKNOWN: the provider couldn't classify it — log the full error shape (name + message +
|
|
147
152
|
* provider) once so it's visible, then bounded retry without eviction.
|
|
148
153
|
*/
|
|
@@ -291,6 +296,7 @@ declare const OnyxUtils: {
|
|
|
291
296
|
getCollectionDataAndSendAsObject: typeof getCollectionDataAndSendAsObject;
|
|
292
297
|
remove: typeof remove;
|
|
293
298
|
reportStorageQuota: typeof reportStorageQuota;
|
|
299
|
+
resetDiskPressureLogThrottle: typeof resetDiskPressureLogThrottle;
|
|
294
300
|
retryOperation: typeof retryOperation;
|
|
295
301
|
broadcastUpdate: typeof broadcastUpdate;
|
|
296
302
|
hasPendingMergeForKey: typeof hasPendingMergeForKey;
|
package/dist/OnyxUtils.js
CHANGED
|
@@ -60,6 +60,14 @@ const METHOD = {
|
|
|
60
60
|
};
|
|
61
61
|
// Max number of retries for failed storage operations
|
|
62
62
|
const MAX_STORAGE_OPERATION_RETRY_ATTEMPTS = 5;
|
|
63
|
+
/** Minimum interval between disk-pressure alerts. One disk-pressure burst fails every queued operation
|
|
64
|
+
* with the identical error, so per-operation logging would amplify the very storm it reports. */
|
|
65
|
+
const DISK_PRESSURE_LOG_INTERVAL_MS = 60000;
|
|
66
|
+
let lastDiskPressureLogTime = 0;
|
|
67
|
+
/** Test-only: clears the disk-pressure log throttle so each test observes its own alert. */
|
|
68
|
+
function resetDiskPressureLogThrottle() {
|
|
69
|
+
lastDiskPressureLogTime = 0;
|
|
70
|
+
}
|
|
63
71
|
// Key/value store of Onyx key and arrays of values to merge
|
|
64
72
|
let mergeQueue = {};
|
|
65
73
|
let mergeQueuePromise = {};
|
|
@@ -659,6 +667,9 @@ function reportStorageQuota(error) {
|
|
|
659
667
|
* - CAPACITY: evicts the least recently accessed evictable key and retries, under a session-level
|
|
660
668
|
* circuit breaker (see lib/StorageCircuitBreaker.ts) that halts the loop once eviction stops making
|
|
661
669
|
* progress or failures storm — the per-operation budget alone cannot stop a session-wide storm.
|
|
670
|
+
* - DISK_PRESSURE: the device disk itself is full (or the database files are unreadable), so neither
|
|
671
|
+
* retries nor in-DB eviction can free space — the write is dropped (cache stays authoritative) with
|
|
672
|
+
* a single throttled alert + quota snapshot per burst.
|
|
662
673
|
* - UNKNOWN: the provider couldn't classify it — log the full error shape (name + message +
|
|
663
674
|
* provider) once so it's visible, then bounded retry without eviction.
|
|
664
675
|
*/
|
|
@@ -674,6 +685,18 @@ function retryOperation(error, onyxMethod, defaultParams, retryAttempt, inFlight
|
|
|
674
685
|
StorageCircuitBreaker_1.default.recordProbeFailure();
|
|
675
686
|
return Promise.resolve();
|
|
676
687
|
}
|
|
688
|
+
// DISK_PRESSURE: the device disk is full, so neither retries nor eviction can succeed until the OS
|
|
689
|
+
// frees space. Drop the write (cache stays authoritative) and log one alert + quota snapshot per
|
|
690
|
+
// interval — the snapshot's free-disk bytes let telemetry confirm (or rule out) disk pressure.
|
|
691
|
+
if (errorClass === errors_1.StorageErrorClass.DISK_PRESSURE) {
|
|
692
|
+
const now = Date.now();
|
|
693
|
+
if (now - lastDiskPressureLogTime < DISK_PRESSURE_LOG_INTERVAL_MS) {
|
|
694
|
+
return Promise.resolve();
|
|
695
|
+
}
|
|
696
|
+
lastDiskPressureLogTime = now;
|
|
697
|
+
Logger.logAlert(`Disk-pressure storage error; skipping retries. provider: ${storage_1.default.getStorageProvider().name}. message: ${error === null || error === void 0 ? void 0 : error.message}. onyxMethod: ${onyxMethod.name}.`);
|
|
698
|
+
return reportStorageQuota(error);
|
|
699
|
+
}
|
|
677
700
|
Logger.logInfo(`Failed to save to storage. Error: ${error}. class: ${errorClass}. onyxMethod: ${onyxMethod.name}. retryAttempt: ${currentRetryAttempt}/${MAX_STORAGE_OPERATION_RETRY_ATTEMPTS}`);
|
|
678
701
|
if (errorClass === errors_1.StorageErrorClass.INVALID_DATA) {
|
|
679
702
|
Logger.logAlert(`Attempted to set invalid data set in Onyx. Please ensure all data is serializable. Error: ${error}`);
|
|
@@ -1510,6 +1533,7 @@ const OnyxUtils = {
|
|
|
1510
1533
|
getCollectionDataAndSendAsObject,
|
|
1511
1534
|
remove,
|
|
1512
1535
|
reportStorageQuota,
|
|
1536
|
+
resetDiskPressureLogThrottle,
|
|
1513
1537
|
retryOperation,
|
|
1514
1538
|
broadcastUpdate,
|
|
1515
1539
|
hasPendingMergeForKey,
|
|
@@ -1,23 +1,29 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* The InstancesSync object provides data-changed events like the ones that exist
|
|
3
|
-
* when using LocalStorage APIs in the browser. These events are great because multiple tabs can listen for when
|
|
4
|
-
* data changes and then stay up-to-date with everything happening in Onyx.
|
|
5
|
-
*/
|
|
6
1
|
import type { OnyxKey } from '../../types';
|
|
7
|
-
import type { StorageKeyList,
|
|
2
|
+
import type { StorageKeyList, OnStorageKeysChanged } from '../providers/types';
|
|
8
3
|
import type StorageProvider from '../providers/types';
|
|
9
4
|
/**
|
|
10
|
-
* Raise
|
|
11
|
-
*
|
|
5
|
+
* Raise cross-tab event(s) for a batch of changed keys. Sending keys together (instead of one event per
|
|
6
|
+
* key) preserves the write's batch boundary across tabs, so the receiving tab notifies collection
|
|
7
|
+
* subscribers once for the whole batch — matching the local mergeCollection behavior — instead of
|
|
8
|
+
* re-delivering the whole collection once per member (O(N^2), which can crash the tab). Large batches are
|
|
9
|
+
* chunked so no single payload approaches the localStorage quota.
|
|
12
10
|
*/
|
|
13
|
-
declare function raiseStorageSyncEvent(onyxKey: OnyxKey): void;
|
|
14
11
|
declare function raiseStorageSyncManyKeysEvent(onyxKeys: StorageKeyList): void;
|
|
12
|
+
/**
|
|
13
|
+
* Raise an event through `localStorage` to let other tabs know a single key changed.
|
|
14
|
+
*
|
|
15
|
+
* This intentionally emits the raw key (the legacy, pre-batching format) rather than a JSON array, so a
|
|
16
|
+
* tab still running the previous bundle during a deploy keeps receiving single-key updates (a new message,
|
|
17
|
+
* a pin, a rename, etc.). Only multi-key writes use the batched JSON-array format; the receiver here
|
|
18
|
+
* understands both. The mixed-version gap is therefore limited to bulk collection writes, which resolve on reload.
|
|
19
|
+
*/
|
|
20
|
+
declare function raiseStorageSyncEvent(onyxKey: OnyxKey): void;
|
|
15
21
|
declare const InstanceSync: {
|
|
16
22
|
shouldBeUsed: boolean;
|
|
17
23
|
/**
|
|
18
|
-
* @param {Function}
|
|
24
|
+
* @param {Function} onStorageKeysChanged Storage synchronization mechanism keeping all opened tabs in sync
|
|
19
25
|
*/
|
|
20
|
-
init: (
|
|
26
|
+
init: (onStorageKeysChanged: OnStorageKeysChanged, store: StorageProvider<unknown>) => void;
|
|
21
27
|
setItem: typeof raiseStorageSyncEvent;
|
|
22
28
|
removeItem: typeof raiseStorageSyncEvent;
|
|
23
29
|
removeItems: typeof raiseStorageSyncManyKeysEvent;
|
|
@@ -1,30 +1,130 @@
|
|
|
1
1
|
"use strict";
|
|
2
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
+
if (k2 === undefined) k2 = k;
|
|
4
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
+
}
|
|
8
|
+
Object.defineProperty(o, k2, desc);
|
|
9
|
+
}) : (function(o, m, k, k2) {
|
|
10
|
+
if (k2 === undefined) k2 = k;
|
|
11
|
+
o[k2] = m[k];
|
|
12
|
+
}));
|
|
13
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
14
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
15
|
+
}) : function(o, v) {
|
|
16
|
+
o["default"] = v;
|
|
17
|
+
});
|
|
18
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
19
|
+
var ownKeys = function(o) {
|
|
20
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
21
|
+
var ar = [];
|
|
22
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
23
|
+
return ar;
|
|
24
|
+
};
|
|
25
|
+
return ownKeys(o);
|
|
26
|
+
};
|
|
27
|
+
return function (mod) {
|
|
28
|
+
if (mod && mod.__esModule) return mod;
|
|
29
|
+
var result = {};
|
|
30
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
31
|
+
__setModuleDefault(result, mod);
|
|
32
|
+
return result;
|
|
33
|
+
};
|
|
34
|
+
})();
|
|
2
35
|
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
36
|
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
37
|
};
|
|
5
38
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
39
|
+
/**
|
|
40
|
+
* The InstancesSync object provides data-changed events like the ones that exist
|
|
41
|
+
* when using LocalStorage APIs in the browser. These events are great because multiple tabs can listen for when
|
|
42
|
+
* data changes and then stay up-to-date with everything happening in Onyx.
|
|
43
|
+
*/
|
|
44
|
+
const Logger = __importStar(require("../../Logger"));
|
|
6
45
|
const NoopProvider_1 = __importDefault(require("../providers/NoopProvider"));
|
|
7
46
|
const SYNC_ONYX = 'SYNC_ONYX';
|
|
47
|
+
// localStorage stores values as UTF-16 (~2 bytes/char). The per-origin quota isn't fixed by the spec —
|
|
48
|
+
// it's user-agent dependent and commonly ~5MB — so we keep each SYNC_ONYX payload conservatively small
|
|
49
|
+
// (and pair it with a try/catch in emitSyncEvent). This way a large key batch (e.g. Onyx.clear() on a
|
|
50
|
+
// heavy account, or a bulk import) is split across several events instead of throwing QuotaExceededError.
|
|
51
|
+
// See https://developer.mozilla.org/en-US/docs/Web/API/Web_Storage_API/Using_the_Web_Storage_API
|
|
52
|
+
const MAX_SYNC_PAYLOAD_LENGTH = 1000000;
|
|
8
53
|
/**
|
|
9
|
-
*
|
|
10
|
-
*
|
|
54
|
+
* Parses the SYNC_ONYX storage event value.
|
|
55
|
+
* The payload is a JSON array of the changed keys (a batch). It falls back to treating the raw
|
|
56
|
+
* value as a single key for backwards compatibility with the previous one-key-per-event format.
|
|
11
57
|
*/
|
|
12
|
-
function
|
|
13
|
-
|
|
14
|
-
|
|
58
|
+
function parseSyncOnyxStorageEventValue(value) {
|
|
59
|
+
let onyxKeys;
|
|
60
|
+
try {
|
|
61
|
+
const parsed = JSON.parse(value);
|
|
62
|
+
onyxKeys = Array.isArray(parsed) ? parsed : [value];
|
|
63
|
+
}
|
|
64
|
+
catch (_a) {
|
|
65
|
+
onyxKeys = [value];
|
|
66
|
+
}
|
|
67
|
+
return onyxKeys;
|
|
15
68
|
}
|
|
69
|
+
/**
|
|
70
|
+
* Emit a single SYNC_ONYX storage event. Wrapped so a failed cross-tab signal
|
|
71
|
+
* degrades gracefully — other tabs simply miss this update until their next organic sync/reload — instead
|
|
72
|
+
* of throwing an uncaught rejection in the writing tab.
|
|
73
|
+
*/
|
|
74
|
+
function emitSyncEvent(value) {
|
|
75
|
+
try {
|
|
76
|
+
global.localStorage.setItem(SYNC_ONYX, value);
|
|
77
|
+
global.localStorage.removeItem(SYNC_ONYX);
|
|
78
|
+
}
|
|
79
|
+
catch (error) {
|
|
80
|
+
Logger.logAlert(`[InstanceSync] Failed to raise storage sync event: ${error}`);
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
/**
|
|
84
|
+
* Raise cross-tab event(s) for a batch of changed keys. Sending keys together (instead of one event per
|
|
85
|
+
* key) preserves the write's batch boundary across tabs, so the receiving tab notifies collection
|
|
86
|
+
* subscribers once for the whole batch — matching the local mergeCollection behavior — instead of
|
|
87
|
+
* re-delivering the whole collection once per member (O(N^2), which can crash the tab). Large batches are
|
|
88
|
+
* chunked so no single payload approaches the localStorage quota.
|
|
89
|
+
*/
|
|
16
90
|
function raiseStorageSyncManyKeysEvent(onyxKeys) {
|
|
91
|
+
if (onyxKeys.length === 0) {
|
|
92
|
+
return;
|
|
93
|
+
}
|
|
94
|
+
let chunk = [];
|
|
95
|
+
let chunkLength = 2; // accounts for the surrounding `[]`
|
|
17
96
|
for (const onyxKey of onyxKeys) {
|
|
18
|
-
|
|
97
|
+
const keyLength = onyxKey.length + 3; // quotes + comma separator
|
|
98
|
+
if (chunk.length > 0 && chunkLength + keyLength > MAX_SYNC_PAYLOAD_LENGTH) {
|
|
99
|
+
emitSyncEvent(JSON.stringify(chunk));
|
|
100
|
+
chunk = [];
|
|
101
|
+
chunkLength = 2;
|
|
102
|
+
}
|
|
103
|
+
chunk.push(onyxKey);
|
|
104
|
+
chunkLength += keyLength;
|
|
105
|
+
}
|
|
106
|
+
if (chunk.length > 0) {
|
|
107
|
+
emitSyncEvent(JSON.stringify(chunk));
|
|
19
108
|
}
|
|
20
109
|
}
|
|
110
|
+
/**
|
|
111
|
+
* Raise an event through `localStorage` to let other tabs know a single key changed.
|
|
112
|
+
*
|
|
113
|
+
* This intentionally emits the raw key (the legacy, pre-batching format) rather than a JSON array, so a
|
|
114
|
+
* tab still running the previous bundle during a deploy keeps receiving single-key updates (a new message,
|
|
115
|
+
* a pin, a rename, etc.). Only multi-key writes use the batched JSON-array format; the receiver here
|
|
116
|
+
* understands both. The mixed-version gap is therefore limited to bulk collection writes, which resolve on reload.
|
|
117
|
+
*/
|
|
118
|
+
function raiseStorageSyncEvent(onyxKey) {
|
|
119
|
+
emitSyncEvent(onyxKey);
|
|
120
|
+
}
|
|
21
121
|
let storage = NoopProvider_1.default;
|
|
22
122
|
const InstanceSync = {
|
|
23
123
|
shouldBeUsed: true,
|
|
24
124
|
/**
|
|
25
|
-
* @param {Function}
|
|
125
|
+
* @param {Function} onStorageKeysChanged Storage synchronization mechanism keeping all opened tabs in sync
|
|
26
126
|
*/
|
|
27
|
-
init: (
|
|
127
|
+
init: (onStorageKeysChanged, store) => {
|
|
28
128
|
storage = store;
|
|
29
129
|
// This listener will only be triggered by events coming from other tabs
|
|
30
130
|
global.addEventListener('storage', (event) => {
|
|
@@ -32,8 +132,8 @@ const InstanceSync = {
|
|
|
32
132
|
if (event.key !== SYNC_ONYX || !event.newValue) {
|
|
33
133
|
return;
|
|
34
134
|
}
|
|
35
|
-
const
|
|
36
|
-
storage.
|
|
135
|
+
const onyxKeys = parseSyncOnyxStorageEventValue(event.newValue);
|
|
136
|
+
storage.multiGet(onyxKeys).then((pairs) => onStorageKeysChanged(pairs));
|
|
37
137
|
});
|
|
38
138
|
},
|
|
39
139
|
setItem: raiseStorageSyncEvent,
|
|
@@ -3,6 +3,7 @@ declare const StorageMock: {
|
|
|
3
3
|
classifyError: jest.Mock<import("type-fest").ValueOf<{
|
|
4
4
|
readonly TRANSIENT: "transient";
|
|
5
5
|
readonly CAPACITY: "capacity";
|
|
6
|
+
readonly DISK_PRESSURE: "diskPressure";
|
|
6
7
|
readonly INVALID_DATA: "invalidData";
|
|
7
8
|
readonly FATAL: "fatal";
|
|
8
9
|
readonly UNKNOWN: "unknown";
|
package/dist/storage/errors.d.ts
CHANGED
|
@@ -15,6 +15,10 @@ declare const StorageErrorClass: {
|
|
|
15
15
|
readonly TRANSIENT: "transient";
|
|
16
16
|
/** Quota exceeded / disk full. Owner: operation layer — evict and retry. */
|
|
17
17
|
readonly CAPACITY: "capacity";
|
|
18
|
+
/** Filesystem-level failure around the database files (device disk full, or the files cannot be
|
|
19
|
+
* created/read). Owner: operation layer — skip retries and eviction (neither can free OS-level
|
|
20
|
+
* space) and log one throttled alert + quota snapshot per burst. */
|
|
21
|
+
readonly DISK_PRESSURE: "diskPressure";
|
|
18
22
|
/** Non-serializable payload. Never retriable — the same data will always fail. */
|
|
19
23
|
readonly INVALID_DATA: "invalidData";
|
|
20
24
|
/** Backing-store corruption. Owner: connection layer — budgeted heal, then give up. */
|
package/dist/storage/errors.js
CHANGED
|
@@ -19,6 +19,10 @@ const StorageErrorClass = {
|
|
|
19
19
|
TRANSIENT: 'transient',
|
|
20
20
|
/** Quota exceeded / disk full. Owner: operation layer — evict and retry. */
|
|
21
21
|
CAPACITY: 'capacity',
|
|
22
|
+
/** Filesystem-level failure around the database files (device disk full, or the files cannot be
|
|
23
|
+
* created/read). Owner: operation layer — skip retries and eviction (neither can free OS-level
|
|
24
|
+
* space) and log one throttled alert + quota snapshot per burst. */
|
|
25
|
+
DISK_PRESSURE: 'diskPressure',
|
|
22
26
|
/** Non-serializable payload. Never retriable — the same data will always fail. */
|
|
23
27
|
INVALID_DATA: 'invalidData',
|
|
24
28
|
/** Backing-store corruption. Owner: connection layer — budgeted heal, then give up. */
|
package/dist/storage/index.js
CHANGED
|
@@ -181,14 +181,14 @@ const storage = {
|
|
|
181
181
|
*/
|
|
182
182
|
getDatabaseSize: () => tryOrDegradePerformance(() => provider.getDatabaseSize()),
|
|
183
183
|
/**
|
|
184
|
-
* @param
|
|
184
|
+
* @param onStorageKeysChanged - Storage synchronization mechanism keeping all opened tabs in sync (web only)
|
|
185
185
|
*/
|
|
186
|
-
keepInstancesSync(
|
|
186
|
+
keepInstancesSync(onStorageKeysChanged) {
|
|
187
187
|
// If InstanceSync shouldn't be used, it means we're on a native platform and we don't need to keep instances in sync
|
|
188
188
|
if (!InstanceSync_1.default.shouldBeUsed)
|
|
189
189
|
return;
|
|
190
190
|
shouldKeepInstancesSync = true;
|
|
191
|
-
InstanceSync_1.default.init(
|
|
191
|
+
InstanceSync_1.default.init(onStorageKeysChanged, this);
|
|
192
192
|
},
|
|
193
193
|
};
|
|
194
194
|
exports.default = storage;
|
|
@@ -244,15 +244,20 @@ const provider = {
|
|
|
244
244
|
if (!provider.store) {
|
|
245
245
|
throw new Error('Store is not initialized!');
|
|
246
246
|
}
|
|
247
|
-
|
|
247
|
+
// The PRAGMAs need the SQLite connection; getFreeDiskStorage() is filesystem-level. Degrade
|
|
248
|
+
// bytesUsed to -1 instead of failing, so the free-disk bytes always get logged.
|
|
249
|
+
const bytesUsedPromise = Promise.all([provider.store.executeAsync('PRAGMA page_size;'), provider.store.executeAsync('PRAGMA page_count;')])
|
|
250
|
+
.then(([pageSizeResult, pageCountResult]) => {
|
|
248
251
|
var _a, _b, _c, _d, _e, _f;
|
|
249
252
|
const pageSize = (_c = (_b = (_a = pageSizeResult.rows) === null || _a === void 0 ? void 0 : _a.item(0)) === null || _b === void 0 ? void 0 : _b.page_size) !== null && _c !== void 0 ? _c : 0;
|
|
250
253
|
const pageCount = (_f = (_e = (_d = pageCountResult.rows) === null || _d === void 0 ? void 0 : _d.item(0)) === null || _e === void 0 ? void 0 : _e.page_count) !== null && _f !== void 0 ? _f : 0;
|
|
251
|
-
return
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
254
|
+
return pageSize * pageCount;
|
|
255
|
+
})
|
|
256
|
+
.catch(() => -1);
|
|
257
|
+
return Promise.all([bytesUsedPromise, (0, react_native_device_info_1.getFreeDiskStorage)()]).then(([bytesUsed, bytesRemaining]) => ({
|
|
258
|
+
bytesUsed,
|
|
259
|
+
bytesRemaining,
|
|
260
|
+
}));
|
|
256
261
|
},
|
|
257
262
|
};
|
|
258
263
|
exports.default = provider;
|
|
@@ -16,6 +16,9 @@ function classifySQLiteError(error) {
|
|
|
16
16
|
if (message.includes('database or disk is full')) {
|
|
17
17
|
return errors_1.StorageErrorClass.CAPACITY;
|
|
18
18
|
}
|
|
19
|
+
if (message.includes('disk i/o error') || message.includes('unable to open database file')) {
|
|
20
|
+
return errors_1.StorageErrorClass.DISK_PRESSURE;
|
|
21
|
+
}
|
|
19
22
|
return errors_1.StorageErrorClass.UNKNOWN;
|
|
20
23
|
}
|
|
21
24
|
exports.default = classifySQLiteError;
|
|
@@ -9,7 +9,8 @@ type DatabaseSize = {
|
|
|
9
9
|
bytesRemaining: number;
|
|
10
10
|
usageDetails?: Record<string, number>;
|
|
11
11
|
};
|
|
12
|
-
|
|
12
|
+
/** Called with the full batch of key/value pairs that changed together in a single cross-tab sync event. */
|
|
13
|
+
type OnStorageKeysChanged = (pairs: StorageKeyValuePair[]) => void;
|
|
13
14
|
type StorageProvider<TStore> = {
|
|
14
15
|
store: TStore;
|
|
15
16
|
/**
|
|
@@ -77,9 +78,9 @@ type StorageProvider<TStore> = {
|
|
|
77
78
|
*/
|
|
78
79
|
classifyError: (error: unknown) => ValueOf<typeof StorageErrorClass>;
|
|
79
80
|
/**
|
|
80
|
-
* @param
|
|
81
|
+
* @param onStorageKeysChanged Storage synchronization mechanism keeping all opened tabs in sync
|
|
81
82
|
*/
|
|
82
|
-
keepInstancesSync?: (
|
|
83
|
+
keepInstancesSync?: (onStorageKeysChanged: OnStorageKeysChanged) => void;
|
|
83
84
|
};
|
|
84
85
|
export default StorageProvider;
|
|
85
|
-
export type { StorageKeyList, StorageKeyValuePair,
|
|
86
|
+
export type { StorageKeyList, StorageKeyValuePair, OnStorageKeysChanged };
|