react-native-onyx 3.0.101 → 3.0.103
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/CircuitBreaker/AbstractCircuitBreaker.d.ts +8 -8
- package/dist/CircuitBreaker/AbstractCircuitBreaker.js +13 -13
- package/dist/CircuitBreaker/types.d.ts +3 -3
- package/dist/CircuitBreaker/types.js +2 -2
- package/dist/OnyxUtils.js +16 -1
- package/dist/StorageCircuitBreaker.js +1 -1
- package/package.json +1 -1
|
@@ -4,7 +4,7 @@ import type { CircuitBreakerOptions, CircuitBreakerState } from './types';
|
|
|
4
4
|
*
|
|
5
5
|
* - **closed**: requests are allowed; failures are counted.
|
|
6
6
|
* - **open**: requests are rejected until {@link resetTimeoutMs} elapses.
|
|
7
|
-
* - **
|
|
7
|
+
* - **halfOpen**: the recovery-probe state. After the open timeout, the breaker admits exactly ONE
|
|
8
8
|
* probe request: success means the dependency recovered, so the circuit closes. Failure means it's
|
|
9
9
|
* still down, so the circuit reopens. This single-request probe prevents a "thundering herd" where
|
|
10
10
|
* every caller fails loudly when the service hasn't recovered yet.
|
|
@@ -51,23 +51,23 @@ declare abstract class AbstractCircuitBreaker {
|
|
|
51
51
|
/**
|
|
52
52
|
* Whether a request may proceed.
|
|
53
53
|
*
|
|
54
|
-
* Returns `false` while open. In
|
|
54
|
+
* Returns `false` while open. In halfOpen, the FIRST caller is admitted as the recovery probe and
|
|
55
55
|
* `isProbeInFlight` is latched so every subsequent caller is rejected until that probe resolves
|
|
56
56
|
* (via {@link recordSuccess} → close, or {@link recordFailure} → reopen). That single-probe gate is
|
|
57
|
-
* the whole point of
|
|
57
|
+
* the whole point of halfOpen: it tests recovery with one request instead of letting a herd of
|
|
58
58
|
* waiting callers stampede a dependency that may still be down.
|
|
59
59
|
*/
|
|
60
60
|
isAllowed(): boolean;
|
|
61
61
|
/**
|
|
62
|
-
* Record a failed request. May open the circuit from closed or
|
|
62
|
+
* Record a failed request. May open the circuit from closed or halfOpen.
|
|
63
63
|
* @returns `true` when the circuit is open after recording (the request must not proceed).
|
|
64
64
|
*/
|
|
65
65
|
recordFailure(): boolean;
|
|
66
|
-
/** Record a successful request. Closes the circuit from
|
|
66
|
+
/** Record a successful request. Closes the circuit from halfOpen and clears failure counts. */
|
|
67
67
|
recordSuccess(): void;
|
|
68
68
|
/**
|
|
69
69
|
* The current state WITHOUT advancing recovery — a pure query, safe to call without side effects.
|
|
70
|
-
* The open→
|
|
70
|
+
* The open→halfOpen transition is applied only at the admission point ({@link isAllowed}); by the
|
|
71
71
|
* time a caller queries state after being admitted, that transition has already happened.
|
|
72
72
|
*/
|
|
73
73
|
peekState(): CircuitBreakerState;
|
|
@@ -81,9 +81,9 @@ declare abstract class AbstractCircuitBreaker {
|
|
|
81
81
|
private trip;
|
|
82
82
|
private close;
|
|
83
83
|
/**
|
|
84
|
-
* Lazily advance open →
|
|
84
|
+
* Lazily advance open → halfOpen once the reset timeout has elapsed. This is checked on read
|
|
85
85
|
* (via {@link getCurrentState}) rather than on a timer, so there's nothing to schedule or clean up:
|
|
86
|
-
* the transition simply becomes visible to the next caller after the window. Entering
|
|
86
|
+
* the transition simply becomes visible to the next caller after the window. Entering halfOpen
|
|
87
87
|
* clears `isProbeInFlight` so the next admitted request becomes the recovery probe.
|
|
88
88
|
*/
|
|
89
89
|
private maybeRecover;
|
|
@@ -10,7 +10,7 @@ const types_1 = require("./types");
|
|
|
10
10
|
*
|
|
11
11
|
* - **closed**: requests are allowed; failures are counted.
|
|
12
12
|
* - **open**: requests are rejected until {@link resetTimeoutMs} elapses.
|
|
13
|
-
* - **
|
|
13
|
+
* - **halfOpen**: the recovery-probe state. After the open timeout, the breaker admits exactly ONE
|
|
14
14
|
* probe request: success means the dependency recovered, so the circuit closes. Failure means it's
|
|
15
15
|
* still down, so the circuit reopens. This single-request probe prevents a "thundering herd" where
|
|
16
16
|
* every caller fails loudly when the service hasn't recovered yet.
|
|
@@ -53,10 +53,10 @@ class AbstractCircuitBreaker {
|
|
|
53
53
|
/**
|
|
54
54
|
* Whether a request may proceed.
|
|
55
55
|
*
|
|
56
|
-
* Returns `false` while open. In
|
|
56
|
+
* Returns `false` while open. In halfOpen, the FIRST caller is admitted as the recovery probe and
|
|
57
57
|
* `isProbeInFlight` is latched so every subsequent caller is rejected until that probe resolves
|
|
58
58
|
* (via {@link recordSuccess} → close, or {@link recordFailure} → reopen). That single-probe gate is
|
|
59
|
-
* the whole point of
|
|
59
|
+
* the whole point of halfOpen: it tests recovery with one request instead of letting a herd of
|
|
60
60
|
* waiting callers stampede a dependency that may still be down.
|
|
61
61
|
*/
|
|
62
62
|
isAllowed() {
|
|
@@ -64,7 +64,7 @@ class AbstractCircuitBreaker {
|
|
|
64
64
|
if (currentState === 'open') {
|
|
65
65
|
return false;
|
|
66
66
|
}
|
|
67
|
-
if (currentState === '
|
|
67
|
+
if (currentState === 'halfOpen') {
|
|
68
68
|
if (this.isProbeInFlight) {
|
|
69
69
|
return false;
|
|
70
70
|
}
|
|
@@ -73,14 +73,14 @@ class AbstractCircuitBreaker {
|
|
|
73
73
|
return true;
|
|
74
74
|
}
|
|
75
75
|
/**
|
|
76
|
-
* Record a failed request. May open the circuit from closed or
|
|
76
|
+
* Record a failed request. May open the circuit from closed or halfOpen.
|
|
77
77
|
* @returns `true` when the circuit is open after recording (the request must not proceed).
|
|
78
78
|
*/
|
|
79
79
|
recordFailure() {
|
|
80
80
|
if (this.machine.state === 'open') {
|
|
81
81
|
return true;
|
|
82
82
|
}
|
|
83
|
-
if (this.machine.state === '
|
|
83
|
+
if (this.machine.state === 'halfOpen') {
|
|
84
84
|
this.trip();
|
|
85
85
|
return true;
|
|
86
86
|
}
|
|
@@ -91,9 +91,9 @@ class AbstractCircuitBreaker {
|
|
|
91
91
|
}
|
|
92
92
|
return false;
|
|
93
93
|
}
|
|
94
|
-
/** Record a successful request. Closes the circuit from
|
|
94
|
+
/** Record a successful request. Closes the circuit from halfOpen and clears failure counts. */
|
|
95
95
|
recordSuccess() {
|
|
96
|
-
if (this.machine.state === '
|
|
96
|
+
if (this.machine.state === 'halfOpen') {
|
|
97
97
|
this.close();
|
|
98
98
|
return;
|
|
99
99
|
}
|
|
@@ -103,7 +103,7 @@ class AbstractCircuitBreaker {
|
|
|
103
103
|
}
|
|
104
104
|
/**
|
|
105
105
|
* The current state WITHOUT advancing recovery — a pure query, safe to call without side effects.
|
|
106
|
-
* The open→
|
|
106
|
+
* The open→halfOpen transition is applied only at the admission point ({@link isAllowed}); by the
|
|
107
107
|
* time a caller queries state after being admitted, that transition has already happened.
|
|
108
108
|
*/
|
|
109
109
|
peekState() {
|
|
@@ -137,7 +137,7 @@ class AbstractCircuitBreaker {
|
|
|
137
137
|
}
|
|
138
138
|
close() {
|
|
139
139
|
var _a;
|
|
140
|
-
// close() only ever runs from
|
|
140
|
+
// close() only ever runs from halfOpen (see recordSuccess), and halfOpen → closed is the one
|
|
141
141
|
// legal closing transition — so go through transition() to keep the illegal open → closed jump
|
|
142
142
|
// an error rather than silently constructing a fresh closed machine.
|
|
143
143
|
this.machine = this.machine.transition('closed');
|
|
@@ -147,9 +147,9 @@ class AbstractCircuitBreaker {
|
|
|
147
147
|
(_a = this.onClose) === null || _a === void 0 ? void 0 : _a.call(this);
|
|
148
148
|
}
|
|
149
149
|
/**
|
|
150
|
-
* Lazily advance open →
|
|
150
|
+
* Lazily advance open → halfOpen once the reset timeout has elapsed. This is checked on read
|
|
151
151
|
* (via {@link getCurrentState}) rather than on a timer, so there's nothing to schedule or clean up:
|
|
152
|
-
* the transition simply becomes visible to the next caller after the window. Entering
|
|
152
|
+
* the transition simply becomes visible to the next caller after the window. Entering halfOpen
|
|
153
153
|
* clears `isProbeInFlight` so the next admitted request becomes the recovery probe.
|
|
154
154
|
*/
|
|
155
155
|
maybeRecover() {
|
|
@@ -159,7 +159,7 @@ class AbstractCircuitBreaker {
|
|
|
159
159
|
if (Date.now() - this.openedAt < this.resetTimeoutMs) {
|
|
160
160
|
return;
|
|
161
161
|
}
|
|
162
|
-
this.machine = this.machine.transition('
|
|
162
|
+
this.machine = this.machine.transition('halfOpen');
|
|
163
163
|
this.isProbeInFlight = false;
|
|
164
164
|
}
|
|
165
165
|
}
|
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
* - **open**: tripped; requests are rejected outright so a known-bad dependency isn't hammered.
|
|
6
6
|
* - **half-open**: a trial state entered after the open timeout — see {@link CIRCUIT_BREAKER_TRANSITIONS}.
|
|
7
7
|
*/
|
|
8
|
-
type CircuitBreakerState = 'closed' | 'open' | '
|
|
8
|
+
type CircuitBreakerState = 'closed' | 'open' | 'halfOpen';
|
|
9
9
|
/**
|
|
10
10
|
* Legal state transitions. The flow is closed → open → half-open → (closed | open).
|
|
11
11
|
*
|
|
@@ -23,8 +23,8 @@ type CircuitBreakerState = 'closed' | 'open' | 'half-open';
|
|
|
23
23
|
*/
|
|
24
24
|
declare const CIRCUIT_BREAKER_TRANSITIONS: {
|
|
25
25
|
readonly closed: readonly ["open"];
|
|
26
|
-
readonly open: readonly ["
|
|
27
|
-
readonly
|
|
26
|
+
readonly open: readonly ["halfOpen"];
|
|
27
|
+
readonly halfOpen: readonly ["closed", "open"];
|
|
28
28
|
};
|
|
29
29
|
type CircuitBreakerOptions = {
|
|
30
30
|
/** Time in milliseconds the circuit stays open before moving to half-open. */
|
|
@@ -18,7 +18,7 @@ exports.CIRCUIT_BREAKER_TRANSITIONS = void 0;
|
|
|
18
18
|
*/
|
|
19
19
|
const CIRCUIT_BREAKER_TRANSITIONS = {
|
|
20
20
|
closed: ['open'],
|
|
21
|
-
open: ['
|
|
22
|
-
|
|
21
|
+
open: ['halfOpen'],
|
|
22
|
+
halfOpen: ['closed', 'open'],
|
|
23
23
|
};
|
|
24
24
|
exports.CIRCUIT_BREAKER_TRANSITIONS = CIRCUIT_BREAKER_TRANSITIONS;
|
package/dist/OnyxUtils.js
CHANGED
|
@@ -1020,13 +1020,28 @@ function updateSnapshots(data, mergeFn) {
|
|
|
1020
1020
|
return [];
|
|
1021
1021
|
const promises = [];
|
|
1022
1022
|
const snapshotCollection = getCachedCollection(snapshotCollectionKey);
|
|
1023
|
+
// Multiset entries are keyless but update every key of their payload, so expand them into
|
|
1024
|
+
// per-key entries to keep cached snapshots in sync with the real Onyx data.
|
|
1025
|
+
const flattenedData = data.flatMap((entry) => {
|
|
1026
|
+
if (entry.onyxMethod === METHOD.MULTI_SET && typeof entry.key !== 'string' && entry.value && typeof entry.value === 'object' && !Array.isArray(entry.value)) {
|
|
1027
|
+
return Object.entries(entry.value).map(([key, value]) => ({ onyxMethod: METHOD.SET, key, value }));
|
|
1028
|
+
}
|
|
1029
|
+
return entry;
|
|
1030
|
+
});
|
|
1023
1031
|
for (const [snapshotEntryKey, snapshotEntryValue] of Object.entries(snapshotCollection)) {
|
|
1024
1032
|
// Snapshots may not be present in cache. We don't know how to update them so we skip.
|
|
1025
1033
|
if (!snapshotEntryValue) {
|
|
1026
1034
|
continue;
|
|
1027
1035
|
}
|
|
1028
1036
|
let updatedData = {};
|
|
1029
|
-
for (const { key, value } of
|
|
1037
|
+
for (const { key, value, onyxMethod } of flattenedData) {
|
|
1038
|
+
if (typeof key !== 'string') {
|
|
1039
|
+
// clear entries legitimately carry no key, and malformed multiset payloads are already logged by update() itself
|
|
1040
|
+
if (onyxMethod !== METHOD.CLEAR && onyxMethod !== METHOD.MULTI_SET) {
|
|
1041
|
+
Logger.logHmmm(`Invalid ${typeof key} key (method: ${onyxMethod}, key: ${String(key).slice(0, 50)}) provided in Onyx update. Skipping snapshot update for this entry.`);
|
|
1042
|
+
}
|
|
1043
|
+
continue;
|
|
1044
|
+
}
|
|
1030
1045
|
// snapshots are normal keys so we want to skip update if they are written to Onyx
|
|
1031
1046
|
if (OnyxKeys_1.default.isCollectionMemberKey(snapshotCollectionKey, key)) {
|
|
1032
1047
|
continue;
|
|
@@ -97,7 +97,7 @@ class StorageCircuitBreaker extends AbstractCircuitBreaker_1.default {
|
|
|
97
97
|
// failure that triggered it must not re-trip the circuit. Let it proceed; the probe's outcome
|
|
98
98
|
// is the verdict — recordWriteSuccess (retry landed) closes, recordProbeFailure (retry failed,
|
|
99
99
|
// re-entering retryOperation) reopens.
|
|
100
|
-
if (this.peekState() === '
|
|
100
|
+
if (this.peekState() === 'halfOpen') {
|
|
101
101
|
return false;
|
|
102
102
|
}
|
|
103
103
|
return this.recordFailure();
|