react-native-onyx 3.0.113 → 3.0.115
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/OnyxUtils.d.ts +2 -0
- package/dist/OnyxUtils.js +8 -0
- package/dist/storage/__mocks__/index.d.ts +1 -0
- package/dist/storage/errors.d.ts +8 -2
- package/dist/storage/errors.js +8 -2
- package/dist/storage/index.js +22 -5
- package/dist/storage/providers/IDBKeyValProvider/classifyError.js +5 -0
- package/dist/storage/providers/IDBKeyValProvider/createStore.js +6 -0
- package/dist/storage/providers/IDBKeyValProvider/index.js +4 -0
- package/dist/storage/providers/IDBKeyValProvider/isIndexedDBAvailable.d.ts +9 -0
- package/dist/storage/providers/IDBKeyValProvider/isIndexedDBAvailable.js +14 -0
- package/package.json +1 -1
package/dist/OnyxUtils.d.ts
CHANGED
|
@@ -153,6 +153,8 @@ declare function reportStorageQuota(error?: Error): Promise<void>;
|
|
|
153
153
|
* - DISK_PRESSURE: the device disk itself is full (or the database files are unreadable), so neither
|
|
154
154
|
* retries nor in-DB eviction can free space — the write is dropped (cache stays authoritative) with
|
|
155
155
|
* a single throttled alert + quota snapshot per burst.
|
|
156
|
+
* - UNAVAILABLE: the storage engine does not exist in this environment, so the storage layer has
|
|
157
|
+
* already degraded to the in-memory provider. No retry.
|
|
156
158
|
* - UNKNOWN: the provider couldn't classify it — log the full error shape (name + message +
|
|
157
159
|
* provider) once so it's visible, then bounded retry without eviction.
|
|
158
160
|
*/
|
package/dist/OnyxUtils.js
CHANGED
|
@@ -668,6 +668,8 @@ function reportStorageQuota(error) {
|
|
|
668
668
|
* - DISK_PRESSURE: the device disk itself is full (or the database files are unreadable), so neither
|
|
669
669
|
* retries nor in-DB eviction can free space — the write is dropped (cache stays authoritative) with
|
|
670
670
|
* a single throttled alert + quota snapshot per burst.
|
|
671
|
+
* - UNAVAILABLE: the storage engine does not exist in this environment, so the storage layer has
|
|
672
|
+
* already degraded to the in-memory provider. No retry.
|
|
671
673
|
* - UNKNOWN: the provider couldn't classify it — log the full error shape (name + message +
|
|
672
674
|
* provider) once so it's visible, then bounded retry without eviction.
|
|
673
675
|
*/
|
|
@@ -704,6 +706,12 @@ function retryOperation(error, onyxMethod, defaultParams, retryAttempt, inFlight
|
|
|
704
706
|
Logger.logInfo(`Storage operation skipped retry; ${errorClass} errors are handled by the connection layer. Error: ${error}. onyxMethod: ${onyxMethod.name}.`);
|
|
705
707
|
return Promise.resolve();
|
|
706
708
|
}
|
|
709
|
+
// UNAVAILABLE: there is no storage engine in this environment. The storage layer has already swapped
|
|
710
|
+
// in the in-memory provider, so the write's data is not lost.
|
|
711
|
+
if (errorClass === errors_1.StorageErrorClass.UNAVAILABLE) {
|
|
712
|
+
Logger.logInfo(`Storage operation skipped retry; the storage engine is unavailable and the storage layer has degraded to memory-only. Error: ${error}. onyxMethod: ${onyxMethod.name}.`);
|
|
713
|
+
return Promise.resolve();
|
|
714
|
+
}
|
|
707
715
|
if (nextRetryAttempt > MAX_STORAGE_OPERATION_RETRY_ATTEMPTS) {
|
|
708
716
|
Logger.logAlert(`Storage operation failed after ${MAX_STORAGE_OPERATION_RETRY_ATTEMPTS} retries. Error: ${error}. onyxMethod: ${onyxMethod.name}.`);
|
|
709
717
|
return Promise.resolve();
|
|
@@ -6,6 +6,7 @@ declare const StorageMock: {
|
|
|
6
6
|
readonly DISK_PRESSURE: "diskPressure";
|
|
7
7
|
readonly INVALID_DATA: "invalidData";
|
|
8
8
|
readonly FATAL: "fatal";
|
|
9
|
+
readonly UNAVAILABLE: "unavailable";
|
|
9
10
|
readonly UNKNOWN: "unknown";
|
|
10
11
|
}>, [error: unknown], any>;
|
|
11
12
|
getStorageProvider: jest.Mock<import("../providers/types").default<{
|
package/dist/storage/errors.d.ts
CHANGED
|
@@ -4,8 +4,9 @@
|
|
|
4
4
|
* provider's `classifyError`). This module deliberately holds NO string matchers: it is the common
|
|
5
5
|
* taxonomy the two reacting layers agree on, while the per-engine knowledge lives with the engine.
|
|
6
6
|
*
|
|
7
|
-
* - the connection layer (`createStore`) recovers TRANSIENT and FATAL errors by reopening the DB,
|
|
8
|
-
* - the operation layer (`OnyxUtils.retryOperation`) recovers CAPACITY by eviction and retries UNKNOWN
|
|
7
|
+
* - the connection layer (`createStore`) recovers TRANSIENT and FATAL errors by reopening the DB,
|
|
8
|
+
* - the operation layer (`OnyxUtils.retryOperation`) recovers CAPACITY by eviction and retries UNKNOWN, and
|
|
9
|
+
* - the storage layer (`lib/storage/index.ts`) reacts to UNAVAILABLE by degrading to memory-only.
|
|
9
10
|
*
|
|
10
11
|
* This module has no Onyx dependencies (and no engine dependencies) so it can live in the storage
|
|
11
12
|
* layer, and be imported by every provider, without creating an import cycle.
|
|
@@ -23,6 +24,11 @@ declare const StorageErrorClass: {
|
|
|
23
24
|
readonly INVALID_DATA: "invalidData";
|
|
24
25
|
/** Backing-store corruption. Owner: connection layer — budgeted heal, then give up. */
|
|
25
26
|
readonly FATAL: "fatal";
|
|
27
|
+
/** The storage engine itself does not exist in this environment (for example `indexedDB` is an
|
|
28
|
+
* undeclared global in Chrome for iOS private tabs and in Lockdown Mode). Never retriable — the
|
|
29
|
+
* engine cannot appear mid-session. Owner: the storage layer — degrade
|
|
30
|
+
* to the in-memory provider. */
|
|
31
|
+
readonly UNAVAILABLE: "unavailable";
|
|
26
32
|
/** Unmatched by the active provider. Owner: operation layer — bounded retry, and log the shape so
|
|
27
33
|
* recurring cases can be promoted into one of the classes above. */
|
|
28
34
|
readonly UNKNOWN: "unknown";
|
package/dist/storage/errors.js
CHANGED
|
@@ -8,8 +8,9 @@ exports.getErrorParts = getErrorParts;
|
|
|
8
8
|
* provider's `classifyError`). This module deliberately holds NO string matchers: it is the common
|
|
9
9
|
* taxonomy the two reacting layers agree on, while the per-engine knowledge lives with the engine.
|
|
10
10
|
*
|
|
11
|
-
* - the connection layer (`createStore`) recovers TRANSIENT and FATAL errors by reopening the DB,
|
|
12
|
-
* - the operation layer (`OnyxUtils.retryOperation`) recovers CAPACITY by eviction and retries UNKNOWN
|
|
11
|
+
* - the connection layer (`createStore`) recovers TRANSIENT and FATAL errors by reopening the DB,
|
|
12
|
+
* - the operation layer (`OnyxUtils.retryOperation`) recovers CAPACITY by eviction and retries UNKNOWN, and
|
|
13
|
+
* - the storage layer (`lib/storage/index.ts`) reacts to UNAVAILABLE by degrading to memory-only.
|
|
13
14
|
*
|
|
14
15
|
* This module has no Onyx dependencies (and no engine dependencies) so it can live in the storage
|
|
15
16
|
* layer, and be imported by every provider, without creating an import cycle.
|
|
@@ -27,6 +28,11 @@ const StorageErrorClass = {
|
|
|
27
28
|
INVALID_DATA: 'invalidData',
|
|
28
29
|
/** Backing-store corruption. Owner: connection layer — budgeted heal, then give up. */
|
|
29
30
|
FATAL: 'fatal',
|
|
31
|
+
/** The storage engine itself does not exist in this environment (for example `indexedDB` is an
|
|
32
|
+
* undeclared global in Chrome for iOS private tabs and in Lockdown Mode). Never retriable — the
|
|
33
|
+
* engine cannot appear mid-session. Owner: the storage layer — degrade
|
|
34
|
+
* to the in-memory provider. */
|
|
35
|
+
UNAVAILABLE: 'unavailable',
|
|
30
36
|
/** Unmatched by the active provider. Owner: operation layer — bounded retry, and log the shape so
|
|
31
37
|
* recurring cases can be promoted into one of the classes above. */
|
|
32
38
|
UNKNOWN: 'unknown',
|
package/dist/storage/index.js
CHANGED
|
@@ -40,7 +40,13 @@ const Logger = __importStar(require("../Logger"));
|
|
|
40
40
|
const platforms_1 = __importDefault(require("./platforms"));
|
|
41
41
|
const InstanceSync_1 = __importDefault(require("./InstanceSync"));
|
|
42
42
|
const MemoryOnlyProvider_1 = __importDefault(require("./providers/MemoryOnlyProvider"));
|
|
43
|
+
const errors_1 = require("./errors");
|
|
43
44
|
let provider = platforms_1.default;
|
|
45
|
+
/**
|
|
46
|
+
* Held separately from `provider` so `degradePerformance` swapping it for `MemoryOnlyProvider`
|
|
47
|
+
* doesn't erase the class of the error that caused the swap.
|
|
48
|
+
*/
|
|
49
|
+
const classifyStorageError = provider.classifyError;
|
|
44
50
|
let shouldKeepInstancesSync = false;
|
|
45
51
|
let finishInitalization;
|
|
46
52
|
const initPromise = new Promise((resolve) => {
|
|
@@ -61,6 +67,13 @@ function degradePerformance(error) {
|
|
|
61
67
|
console.error(error);
|
|
62
68
|
provider = MemoryOnlyProvider_1.default;
|
|
63
69
|
}
|
|
70
|
+
/**
|
|
71
|
+
* Whether an error means the storage engine itself is unusable, which is what justifies dropping it.
|
|
72
|
+
*/
|
|
73
|
+
function shouldDegradeOn(error) {
|
|
74
|
+
// catch the error if DB connection can not be established/DB can not be created
|
|
75
|
+
return error instanceof Error && (error.message.includes('IDBKeyVal store could not be created') || classifyStorageError(error) === errors_1.StorageErrorClass.UNAVAILABLE);
|
|
76
|
+
}
|
|
64
77
|
/**
|
|
65
78
|
* Runs a piece of code and degrades performance if certain errors are thrown
|
|
66
79
|
*/
|
|
@@ -69,8 +82,7 @@ function tryOrDegradePerformance(fn, waitForInitialization = true) {
|
|
|
69
82
|
return initialization
|
|
70
83
|
.then(() => fn())
|
|
71
84
|
.catch((error) => {
|
|
72
|
-
|
|
73
|
-
if (error instanceof Error && error.message.includes('IDBKeyVal store could not be created')) {
|
|
85
|
+
if (shouldDegradeOn(error)) {
|
|
74
86
|
degradePerformance(error);
|
|
75
87
|
}
|
|
76
88
|
return Promise.reject(error);
|
|
@@ -84,17 +96,22 @@ const storage = {
|
|
|
84
96
|
return provider;
|
|
85
97
|
},
|
|
86
98
|
/**
|
|
87
|
-
* Classifies a write error using the
|
|
99
|
+
* Classifies a write error using the platform provider's own classifier. Synchronous and pure —
|
|
88
100
|
* never wrapped in tryOrDegradePerformance.
|
|
89
101
|
*/
|
|
90
|
-
classifyError: (error) =>
|
|
102
|
+
classifyError: (error) => classifyStorageError(error),
|
|
91
103
|
/**
|
|
92
104
|
* Initializes all providers in the list of storage providers
|
|
93
105
|
* and enables fallback providers if necessary
|
|
106
|
+
*
|
|
107
|
+
* The rejection is consumed here: an unconsumed one would fire the app's global unhandled-rejection handler.
|
|
94
108
|
*/
|
|
95
109
|
init() {
|
|
96
|
-
tryOrDegradePerformance(provider.init, false).
|
|
110
|
+
tryOrDegradePerformance(provider.init, false).then(finishInitalization, (error) => {
|
|
97
111
|
finishInitalization();
|
|
112
|
+
if (!shouldDegradeOn(error)) {
|
|
113
|
+
Logger.logAlert(`Storage initialization failed. Original error: ${error instanceof Error ? error.message : String(error)}`);
|
|
114
|
+
}
|
|
98
115
|
});
|
|
99
116
|
},
|
|
100
117
|
/**
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
const errors_1 = require("../../errors");
|
|
4
|
+
const isIndexedDBAvailable_1 = require("./isIndexedDBAvailable");
|
|
4
5
|
/**
|
|
5
6
|
* Classifies an IndexedDB write failure into the shared storage taxonomy (lib/storage/errors.ts).
|
|
6
7
|
* Matching is done on the lowercased error name and message. This is the IndexedDB engine's own
|
|
@@ -8,6 +9,10 @@ const errors_1 = require("../../errors");
|
|
|
8
9
|
*/
|
|
9
10
|
function classifyIDBError(error) {
|
|
10
11
|
const { name, message } = (0, errors_1.getErrorParts)(error);
|
|
12
|
+
// The engine is absent, not broken e.g. private tabs and Lockdown Mode on WebKit.
|
|
13
|
+
if (message.includes("can't find variable: indexeddb") || message.includes('indexeddb is not defined') || message.includes(isIndexedDBAvailable_1.INDEXED_DB_UNAVAILABLE_MESSAGE.toLowerCase())) {
|
|
14
|
+
return errors_1.StorageErrorClass.UNAVAILABLE;
|
|
15
|
+
}
|
|
11
16
|
// Non-serializable data passed to IDBObjectStore.put — retrying is futile.
|
|
12
17
|
if (message.includes("failed to execute 'put' on 'idbobjectstore'")) {
|
|
13
18
|
return errors_1.StorageErrorClass.INVALID_DATA;
|
|
@@ -40,6 +40,7 @@ const IDB = __importStar(require("idb-keyval"));
|
|
|
40
40
|
const Logger = __importStar(require("../../../Logger"));
|
|
41
41
|
const errors_1 = require("../../errors");
|
|
42
42
|
const classifyError_1 = __importDefault(require("./classifyError"));
|
|
43
|
+
const isIndexedDBAvailable_1 = __importStar(require("./isIndexedDBAvailable"));
|
|
43
44
|
const HEAL_ATTEMPTS_MAX = 3;
|
|
44
45
|
// This is a copy of the createStore function from idb-keyval, we need a custom implementation
|
|
45
46
|
// because we need to create the database manually in order to ensure that the store exists before we use it.
|
|
@@ -88,6 +89,9 @@ function createStore(dbName, storeName) {
|
|
|
88
89
|
const getDB = () => {
|
|
89
90
|
if (dbp)
|
|
90
91
|
return dbp;
|
|
92
|
+
if (!(0, isIndexedDBAvailable_1.default)()) {
|
|
93
|
+
return Promise.reject(new Error(isIndexedDBAvailable_1.INDEXED_DB_UNAVAILABLE_MESSAGE));
|
|
94
|
+
}
|
|
91
95
|
const request = indexedDB.open(dbName);
|
|
92
96
|
request.onupgradeneeded = () => request.result.createObjectStore(storeName);
|
|
93
97
|
return cacheOpenPromise(IDB.promisifyRequest(request));
|
|
@@ -181,6 +185,8 @@ function createStore(dbName, storeName) {
|
|
|
181
185
|
// Mirrors Dexie's PR1398_maxLoop pattern: https://github.com/dexie/Dexie.js/blob/master/src/functions/temp-transaction.ts
|
|
182
186
|
// - CAPACITY / UNKNOWN are NOT the connection layer's responsibility — propagate to the operation
|
|
183
187
|
// layer (OnyxUtils.retryOperation) without retrying here, to avoid compounding retries.
|
|
188
|
+
// - UNAVAILABLE (no IndexedDB engine at all) is nobody's to recover — propagate so the storage
|
|
189
|
+
// layer can degrade to memory-only once.
|
|
184
190
|
// Note: concurrent store() calls share the heal budget. Under overlapping failures each caller
|
|
185
191
|
// decrements independently, so the budget may drain faster than one-per-incident. This is
|
|
186
192
|
// acceptable — same as Dexie's approach — and the budget resets on any success.
|
|
@@ -40,6 +40,7 @@ const IDB = __importStar(require("idb-keyval"));
|
|
|
40
40
|
const utils_1 = __importDefault(require("../../../utils"));
|
|
41
41
|
const createStore_1 = __importDefault(require("./createStore"));
|
|
42
42
|
const classifyError_1 = __importDefault(require("./classifyError"));
|
|
43
|
+
const isIndexedDBAvailable_1 = __importStar(require("./isIndexedDBAvailable"));
|
|
43
44
|
const DB_NAME = 'OnyxDB';
|
|
44
45
|
const STORE_NAME = 'keyvaluepairs';
|
|
45
46
|
/**
|
|
@@ -69,6 +70,9 @@ const provider = {
|
|
|
69
70
|
* Initializes the storage provider
|
|
70
71
|
*/
|
|
71
72
|
init() {
|
|
73
|
+
if (!(0, isIndexedDBAvailable_1.default)()) {
|
|
74
|
+
throw new Error(`IDBKeyVal store could not be created: ${isIndexedDBAvailable_1.INDEXED_DB_UNAVAILABLE_MESSAGE}`);
|
|
75
|
+
}
|
|
72
76
|
const newIdbKeyValStore = (0, createStore_1.default)(DB_NAME, STORE_NAME);
|
|
73
77
|
if (newIdbKeyValStore == null) {
|
|
74
78
|
throw Error('IDBKeyVal store could not be created');
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
/** Matched by `classifyIDBError` so a guarded failure classifies as UNAVAILABLE like an unguarded one. */
|
|
2
|
+
declare const INDEXED_DB_UNAVAILABLE_MESSAGE = "indexedDB is not available in this environment";
|
|
3
|
+
/**
|
|
4
|
+
* `typeof` instead of `indexedDB === undefined`: on WebKit the global is undeclared in private tabs and
|
|
5
|
+
* Lockdown Mode, so reading it throws a `ReferenceError` instead of evaluating to `undefined`.
|
|
6
|
+
*/
|
|
7
|
+
declare function isIndexedDBAvailable(): boolean;
|
|
8
|
+
export default isIndexedDBAvailable;
|
|
9
|
+
export { INDEXED_DB_UNAVAILABLE_MESSAGE };
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.INDEXED_DB_UNAVAILABLE_MESSAGE = void 0;
|
|
4
|
+
/** Matched by `classifyIDBError` so a guarded failure classifies as UNAVAILABLE like an unguarded one. */
|
|
5
|
+
const INDEXED_DB_UNAVAILABLE_MESSAGE = 'indexedDB is not available in this environment';
|
|
6
|
+
exports.INDEXED_DB_UNAVAILABLE_MESSAGE = INDEXED_DB_UNAVAILABLE_MESSAGE;
|
|
7
|
+
/**
|
|
8
|
+
* `typeof` instead of `indexedDB === undefined`: on WebKit the global is undeclared in private tabs and
|
|
9
|
+
* Lockdown Mode, so reading it throws a `ReferenceError` instead of evaluating to `undefined`.
|
|
10
|
+
*/
|
|
11
|
+
function isIndexedDBAvailable() {
|
|
12
|
+
return typeof indexedDB !== 'undefined' && indexedDB !== null;
|
|
13
|
+
}
|
|
14
|
+
exports.default = isIndexedDBAvailable;
|