@hiveio/dhive 1.3.2 → 1.3.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/dist/dhive.d.ts +142 -2
- package/dist/dhive.js +27 -18
- package/dist/dhive.js.gz +0 -0
- package/dist/dhive.js.map +1 -1
- package/lib/client.d.ts +11 -0
- package/lib/client.js +33 -4
- package/lib/health-tracker.d.ts +100 -0
- package/lib/health-tracker.js +167 -0
- package/lib/index.d.ts +2 -0
- package/lib/index.js +2 -0
- package/lib/utils.d.ts +26 -2
- package/lib/utils.js +150 -43
- package/lib/version.js +1 -1
- package/package.json +1 -1
package/lib/client.d.ts
CHANGED
|
@@ -40,6 +40,7 @@ import { HivemindAPI } from './helpers/hivemind';
|
|
|
40
40
|
import { AccountByKeyAPI } from './helpers/key';
|
|
41
41
|
import { RCAPI } from './helpers/rc';
|
|
42
42
|
import { TransactionStatusAPI } from './helpers/transaction';
|
|
43
|
+
import { NodeHealthTracker, HealthTrackerOptions } from './health-tracker';
|
|
43
44
|
/**
|
|
44
45
|
* Library version.
|
|
45
46
|
*/
|
|
@@ -102,6 +103,11 @@ export interface ClientOptions {
|
|
|
102
103
|
* Deprecated - don't use
|
|
103
104
|
*/
|
|
104
105
|
rebrandedApi?: boolean;
|
|
106
|
+
/**
|
|
107
|
+
* Options for the node health tracker.
|
|
108
|
+
* Controls cooldown periods, stale block thresholds, etc.
|
|
109
|
+
*/
|
|
110
|
+
healthTrackerOptions?: HealthTrackerOptions;
|
|
105
111
|
}
|
|
106
112
|
/**
|
|
107
113
|
* RPC Client
|
|
@@ -146,6 +152,11 @@ export declare class Client {
|
|
|
146
152
|
* Transaction status API helper.
|
|
147
153
|
*/
|
|
148
154
|
readonly transaction: TransactionStatusAPI;
|
|
155
|
+
/**
|
|
156
|
+
* Node health tracker for smart failover.
|
|
157
|
+
* Tracks per-node, per-API health and head block freshness.
|
|
158
|
+
*/
|
|
159
|
+
readonly healthTracker: NodeHealthTracker;
|
|
149
160
|
/**
|
|
150
161
|
* Chain ID for current network.
|
|
151
162
|
*/
|
package/lib/client.js
CHANGED
|
@@ -53,6 +53,7 @@ const hivemind_1 = require("./helpers/hivemind");
|
|
|
53
53
|
const key_1 = require("./helpers/key");
|
|
54
54
|
const rc_1 = require("./helpers/rc");
|
|
55
55
|
const transaction_1 = require("./helpers/transaction");
|
|
56
|
+
const health_tracker_1 = require("./health-tracker");
|
|
56
57
|
const utils_1 = require("./utils");
|
|
57
58
|
/**
|
|
58
59
|
* Library version.
|
|
@@ -94,6 +95,7 @@ class Client {
|
|
|
94
95
|
this.backoff = options.backoff || defaultBackoff;
|
|
95
96
|
this.failoverThreshold = options.failoverThreshold || 3;
|
|
96
97
|
this.consoleOnFailover = options.consoleOnFailover || false;
|
|
98
|
+
this.healthTracker = new health_tracker_1.NodeHealthTracker(options.healthTrackerOptions);
|
|
97
99
|
this.database = new database_1.DatabaseAPI(this);
|
|
98
100
|
this.broadcast = new broadcast_1.BroadcastAPI(this);
|
|
99
101
|
this.blockchain = new blockchain_1.Blockchain(this);
|
|
@@ -125,6 +127,8 @@ class Client {
|
|
|
125
127
|
*/
|
|
126
128
|
call(api, method, params = []) {
|
|
127
129
|
return __awaiter(this, void 0, void 0, function* () {
|
|
130
|
+
const isBroadcast = api === 'network_broadcast_api' ||
|
|
131
|
+
method.startsWith('broadcast_transaction');
|
|
128
132
|
const request = {
|
|
129
133
|
id: 0,
|
|
130
134
|
jsonrpc: '2.0',
|
|
@@ -159,18 +163,32 @@ class Client {
|
|
|
159
163
|
opts.agent = this.options.agent;
|
|
160
164
|
}
|
|
161
165
|
let fetchTimeout;
|
|
162
|
-
if (
|
|
163
|
-
!method.startsWith('broadcast_transaction')) {
|
|
166
|
+
if (!isBroadcast) {
|
|
164
167
|
// bit of a hack to work around some nodes high error rates
|
|
165
168
|
// only effective in node.js (until timeout spec lands in browsers)
|
|
166
169
|
fetchTimeout = (tries) => (tries + 1) * 500;
|
|
167
170
|
}
|
|
168
|
-
const { response, currentAddress } = yield utils_1.retryingFetch(this.currentAddress, this.address, opts, this.timeout, this.failoverThreshold, this.consoleOnFailover, this.backoff, fetchTimeout
|
|
171
|
+
const { response, currentAddress } = yield utils_1.retryingFetch(this.currentAddress, this.address, opts, this.timeout, this.failoverThreshold, this.consoleOnFailover, this.backoff, fetchTimeout, {
|
|
172
|
+
healthTracker: this.healthTracker,
|
|
173
|
+
api,
|
|
174
|
+
isBroadcast,
|
|
175
|
+
consoleOnFailover: this.consoleOnFailover,
|
|
176
|
+
});
|
|
169
177
|
// After failover, change the currently active address
|
|
170
178
|
if (currentAddress !== this.currentAddress) {
|
|
171
179
|
this.currentAddress = currentAddress;
|
|
172
180
|
}
|
|
173
|
-
//
|
|
181
|
+
// Passively track head block from get_dynamic_global_properties responses.
|
|
182
|
+
// This costs nothing — we just inspect data we already fetched.
|
|
183
|
+
if (response.result &&
|
|
184
|
+
method === 'get_dynamic_global_properties' &&
|
|
185
|
+
response.result.head_block_number) {
|
|
186
|
+
this.healthTracker.updateHeadBlock(currentAddress, response.result.head_block_number);
|
|
187
|
+
}
|
|
188
|
+
// Handle RPC-level errors.
|
|
189
|
+
// Unlike network errors, these mean the node responded but returned an error.
|
|
190
|
+
// We record it as an API-specific failure so the health tracker can
|
|
191
|
+
// deprioritize this node for this API in future calls.
|
|
174
192
|
if (response.error) {
|
|
175
193
|
const formatValue = (value) => {
|
|
176
194
|
switch (typeof value) {
|
|
@@ -200,6 +218,17 @@ class Client {
|
|
|
200
218
|
message += ' ' + unformattedData.join(' ');
|
|
201
219
|
}
|
|
202
220
|
}
|
|
221
|
+
// Track RPC errors that indicate node/plugin issues (not user errors).
|
|
222
|
+
// JSON-RPC error codes (response.error.code):
|
|
223
|
+
// -32601 = Method not found (plugin not enabled on this node)
|
|
224
|
+
// -32603 = Internal error (node issue)
|
|
225
|
+
// -32003 = Hive assertion error (user error — bad params, invalid account)
|
|
226
|
+
// Only API/plugin errors should be tracked, and only as API-specific failures
|
|
227
|
+
// (not global node failures) since other APIs on this node may work fine.
|
|
228
|
+
const rpcCode = response.error.code;
|
|
229
|
+
if (rpcCode === -32601 || rpcCode === -32603) {
|
|
230
|
+
this.healthTracker.recordApiFailure(currentAddress, api);
|
|
231
|
+
}
|
|
203
232
|
throw new verror_1.VError({ info: data, name: 'RPCError' }, message);
|
|
204
233
|
}
|
|
205
234
|
assert.equal(response.id, request.id, 'got invalid response id');
|
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @file Node health tracking for smart failover.
|
|
3
|
+
* @license BSD-3-Clause-No-Military-License
|
|
4
|
+
*
|
|
5
|
+
* Tracks per-node, per-API health to enable intelligent failover decisions.
|
|
6
|
+
* Nodes that fail for specific APIs are deprioritized for those APIs while
|
|
7
|
+
* remaining available for others. Stale nodes (behind on head block) are
|
|
8
|
+
* also deprioritized.
|
|
9
|
+
*/
|
|
10
|
+
export interface HealthTrackerOptions {
|
|
11
|
+
/**
|
|
12
|
+
* How long (ms) to deprioritize a node after consecutive failures.
|
|
13
|
+
* Default: 30 seconds.
|
|
14
|
+
*/
|
|
15
|
+
nodeCooldownMs?: number;
|
|
16
|
+
/**
|
|
17
|
+
* How long (ms) to deprioritize a node for a specific API after failures.
|
|
18
|
+
* Default: 60 seconds.
|
|
19
|
+
*/
|
|
20
|
+
apiCooldownMs?: number;
|
|
21
|
+
/**
|
|
22
|
+
* Number of consecutive failures before a node enters cooldown.
|
|
23
|
+
* Default: 3.
|
|
24
|
+
*/
|
|
25
|
+
maxFailuresBeforeCooldown?: number;
|
|
26
|
+
/**
|
|
27
|
+
* Number of API-specific failures before deprioritizing for that API.
|
|
28
|
+
* Default: 2.
|
|
29
|
+
*/
|
|
30
|
+
maxApiFailuresBeforeCooldown?: number;
|
|
31
|
+
/**
|
|
32
|
+
* How many blocks behind the best known head block a node can be
|
|
33
|
+
* before being considered stale. Default: 30.
|
|
34
|
+
*/
|
|
35
|
+
staleBlockThreshold?: number;
|
|
36
|
+
/**
|
|
37
|
+
* How long (ms) head block data remains valid for staleness checks.
|
|
38
|
+
* Default: 2 minutes.
|
|
39
|
+
*/
|
|
40
|
+
headBlockTtlMs?: number;
|
|
41
|
+
}
|
|
42
|
+
export declare class NodeHealthTracker {
|
|
43
|
+
private health;
|
|
44
|
+
private bestKnownHeadBlock;
|
|
45
|
+
private bestKnownHeadBlockTime;
|
|
46
|
+
private readonly nodeCooldownMs;
|
|
47
|
+
private readonly apiCooldownMs;
|
|
48
|
+
private readonly maxFailuresBeforeCooldown;
|
|
49
|
+
private readonly maxApiFailuresBeforeCooldown;
|
|
50
|
+
private readonly staleBlockThreshold;
|
|
51
|
+
private readonly headBlockTtlMs;
|
|
52
|
+
constructor(options?: HealthTrackerOptions);
|
|
53
|
+
private getOrCreate;
|
|
54
|
+
/**
|
|
55
|
+
* Record a successful call to a node for a specific API.
|
|
56
|
+
* Clears consecutive failure counter and API-specific failures for this API.
|
|
57
|
+
*/
|
|
58
|
+
recordSuccess(node: string, api: string): void;
|
|
59
|
+
/**
|
|
60
|
+
* Record a network-level failure (timeout, connection refused, HTTP error).
|
|
61
|
+
* Increments both the global consecutive failure counter and the API-specific counter.
|
|
62
|
+
*/
|
|
63
|
+
recordFailure(node: string, api: string): void;
|
|
64
|
+
/**
|
|
65
|
+
* Record an API/plugin-specific failure (e.g. "method not found", "plugin not enabled").
|
|
66
|
+
* Only increments the per-API counter, NOT the global consecutive failure counter.
|
|
67
|
+
* This prevents a node with a disabled plugin from being penalized for all APIs.
|
|
68
|
+
*/
|
|
69
|
+
recordApiFailure(node: string, api: string): void;
|
|
70
|
+
private incrementApiFailure;
|
|
71
|
+
/**
|
|
72
|
+
* Update head block number for a node.
|
|
73
|
+
* Called passively when get_dynamic_global_properties responses are observed.
|
|
74
|
+
*/
|
|
75
|
+
updateHeadBlock(node: string, headBlock: number): void;
|
|
76
|
+
/**
|
|
77
|
+
* Check if a node is considered healthy for a given API.
|
|
78
|
+
*/
|
|
79
|
+
isNodeHealthy(node: string, api?: string): boolean;
|
|
80
|
+
/**
|
|
81
|
+
* Return nodes ordered by health for a specific API call.
|
|
82
|
+
* Healthy nodes come first (preserving original order), then unhealthy nodes as fallback.
|
|
83
|
+
*/
|
|
84
|
+
getOrderedNodes(allNodes: string[], api?: string): string[];
|
|
85
|
+
/**
|
|
86
|
+
* Reset all health tracking data.
|
|
87
|
+
*/
|
|
88
|
+
reset(): void;
|
|
89
|
+
/**
|
|
90
|
+
* Get a snapshot of current health state for diagnostics.
|
|
91
|
+
*/
|
|
92
|
+
getHealthSnapshot(): Map<string, {
|
|
93
|
+
consecutiveFailures: number;
|
|
94
|
+
headBlock: number;
|
|
95
|
+
apiFailures: Record<string, {
|
|
96
|
+
count: number;
|
|
97
|
+
}>;
|
|
98
|
+
healthy: boolean;
|
|
99
|
+
}>;
|
|
100
|
+
}
|
|
@@ -0,0 +1,167 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* @file Node health tracking for smart failover.
|
|
4
|
+
* @license BSD-3-Clause-No-Military-License
|
|
5
|
+
*
|
|
6
|
+
* Tracks per-node, per-API health to enable intelligent failover decisions.
|
|
7
|
+
* Nodes that fail for specific APIs are deprioritized for those APIs while
|
|
8
|
+
* remaining available for others. Stale nodes (behind on head block) are
|
|
9
|
+
* also deprioritized.
|
|
10
|
+
*/
|
|
11
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
12
|
+
class NodeHealthTracker {
|
|
13
|
+
constructor(options = {}) {
|
|
14
|
+
var _a, _b, _c, _d, _e, _f;
|
|
15
|
+
this.health = new Map();
|
|
16
|
+
this.bestKnownHeadBlock = 0;
|
|
17
|
+
this.bestKnownHeadBlockTime = 0;
|
|
18
|
+
this.nodeCooldownMs = (_a = options.nodeCooldownMs) !== null && _a !== void 0 ? _a : 30000;
|
|
19
|
+
this.apiCooldownMs = (_b = options.apiCooldownMs) !== null && _b !== void 0 ? _b : 60000;
|
|
20
|
+
this.maxFailuresBeforeCooldown = (_c = options.maxFailuresBeforeCooldown) !== null && _c !== void 0 ? _c : 3;
|
|
21
|
+
this.maxApiFailuresBeforeCooldown = (_d = options.maxApiFailuresBeforeCooldown) !== null && _d !== void 0 ? _d : 2;
|
|
22
|
+
this.staleBlockThreshold = (_e = options.staleBlockThreshold) !== null && _e !== void 0 ? _e : 30;
|
|
23
|
+
this.headBlockTtlMs = (_f = options.headBlockTtlMs) !== null && _f !== void 0 ? _f : 120000;
|
|
24
|
+
}
|
|
25
|
+
getOrCreate(node) {
|
|
26
|
+
let state = this.health.get(node);
|
|
27
|
+
if (!state) {
|
|
28
|
+
state = {
|
|
29
|
+
apiFailures: new Map(),
|
|
30
|
+
consecutiveFailures: 0,
|
|
31
|
+
lastFailure: 0,
|
|
32
|
+
headBlock: 0,
|
|
33
|
+
headBlockUpdatedAt: 0,
|
|
34
|
+
};
|
|
35
|
+
this.health.set(node, state);
|
|
36
|
+
}
|
|
37
|
+
return state;
|
|
38
|
+
}
|
|
39
|
+
/**
|
|
40
|
+
* Record a successful call to a node for a specific API.
|
|
41
|
+
* Clears consecutive failure counter and API-specific failures for this API.
|
|
42
|
+
*/
|
|
43
|
+
recordSuccess(node, api) {
|
|
44
|
+
const state = this.getOrCreate(node);
|
|
45
|
+
state.consecutiveFailures = 0;
|
|
46
|
+
state.apiFailures.delete(api);
|
|
47
|
+
}
|
|
48
|
+
/**
|
|
49
|
+
* Record a network-level failure (timeout, connection refused, HTTP error).
|
|
50
|
+
* Increments both the global consecutive failure counter and the API-specific counter.
|
|
51
|
+
*/
|
|
52
|
+
recordFailure(node, api) {
|
|
53
|
+
const state = this.getOrCreate(node);
|
|
54
|
+
state.consecutiveFailures++;
|
|
55
|
+
state.lastFailure = Date.now();
|
|
56
|
+
this.incrementApiFailure(state, api);
|
|
57
|
+
}
|
|
58
|
+
/**
|
|
59
|
+
* Record an API/plugin-specific failure (e.g. "method not found", "plugin not enabled").
|
|
60
|
+
* Only increments the per-API counter, NOT the global consecutive failure counter.
|
|
61
|
+
* This prevents a node with a disabled plugin from being penalized for all APIs.
|
|
62
|
+
*/
|
|
63
|
+
recordApiFailure(node, api) {
|
|
64
|
+
const state = this.getOrCreate(node);
|
|
65
|
+
this.incrementApiFailure(state, api);
|
|
66
|
+
}
|
|
67
|
+
incrementApiFailure(state, api) {
|
|
68
|
+
const apiState = state.apiFailures.get(api) || { count: 0, lastFailure: 0 };
|
|
69
|
+
apiState.count++;
|
|
70
|
+
apiState.lastFailure = Date.now();
|
|
71
|
+
state.apiFailures.set(api, apiState);
|
|
72
|
+
}
|
|
73
|
+
/**
|
|
74
|
+
* Update head block number for a node.
|
|
75
|
+
* Called passively when get_dynamic_global_properties responses are observed.
|
|
76
|
+
*/
|
|
77
|
+
updateHeadBlock(node, headBlock) {
|
|
78
|
+
if (!headBlock || headBlock <= 0)
|
|
79
|
+
return;
|
|
80
|
+
const state = this.getOrCreate(node);
|
|
81
|
+
state.headBlock = headBlock;
|
|
82
|
+
state.headBlockUpdatedAt = Date.now();
|
|
83
|
+
if (headBlock > this.bestKnownHeadBlock) {
|
|
84
|
+
this.bestKnownHeadBlock = headBlock;
|
|
85
|
+
this.bestKnownHeadBlockTime = Date.now();
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
/**
|
|
89
|
+
* Check if a node is considered healthy for a given API.
|
|
90
|
+
*/
|
|
91
|
+
isNodeHealthy(node, api) {
|
|
92
|
+
const state = this.health.get(node);
|
|
93
|
+
if (!state)
|
|
94
|
+
return true; // Unknown nodes are assumed healthy
|
|
95
|
+
const now = Date.now();
|
|
96
|
+
// Check overall node health (consecutive failures)
|
|
97
|
+
if (state.consecutiveFailures >= this.maxFailuresBeforeCooldown) {
|
|
98
|
+
if (now - state.lastFailure < this.nodeCooldownMs) {
|
|
99
|
+
return false;
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
// Check API-specific health
|
|
103
|
+
if (api) {
|
|
104
|
+
const apiState = state.apiFailures.get(api);
|
|
105
|
+
if (apiState && apiState.count >= this.maxApiFailuresBeforeCooldown) {
|
|
106
|
+
if (now - apiState.lastFailure < this.apiCooldownMs) {
|
|
107
|
+
return false;
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
// Check head block staleness
|
|
112
|
+
if (state.headBlock > 0 &&
|
|
113
|
+
this.bestKnownHeadBlock > 0 &&
|
|
114
|
+
now - state.headBlockUpdatedAt < this.headBlockTtlMs &&
|
|
115
|
+
now - this.bestKnownHeadBlockTime < this.headBlockTtlMs) {
|
|
116
|
+
if (this.bestKnownHeadBlock - state.headBlock > this.staleBlockThreshold) {
|
|
117
|
+
return false;
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
return true;
|
|
121
|
+
}
|
|
122
|
+
/**
|
|
123
|
+
* Return nodes ordered by health for a specific API call.
|
|
124
|
+
* Healthy nodes come first (preserving original order), then unhealthy nodes as fallback.
|
|
125
|
+
*/
|
|
126
|
+
getOrderedNodes(allNodes, api) {
|
|
127
|
+
const healthy = [];
|
|
128
|
+
const unhealthy = [];
|
|
129
|
+
for (const node of allNodes) {
|
|
130
|
+
if (this.isNodeHealthy(node, api)) {
|
|
131
|
+
healthy.push(node);
|
|
132
|
+
}
|
|
133
|
+
else {
|
|
134
|
+
unhealthy.push(node);
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
return [...healthy, ...unhealthy];
|
|
138
|
+
}
|
|
139
|
+
/**
|
|
140
|
+
* Reset all health tracking data.
|
|
141
|
+
*/
|
|
142
|
+
reset() {
|
|
143
|
+
this.health.clear();
|
|
144
|
+
this.bestKnownHeadBlock = 0;
|
|
145
|
+
this.bestKnownHeadBlockTime = 0;
|
|
146
|
+
}
|
|
147
|
+
/**
|
|
148
|
+
* Get a snapshot of current health state for diagnostics.
|
|
149
|
+
*/
|
|
150
|
+
getHealthSnapshot() {
|
|
151
|
+
const snapshot = new Map();
|
|
152
|
+
for (const [node, state] of this.health) {
|
|
153
|
+
const apiFailures = {};
|
|
154
|
+
for (const [api, failure] of state.apiFailures) {
|
|
155
|
+
apiFailures[api] = { count: failure.count };
|
|
156
|
+
}
|
|
157
|
+
snapshot.set(node, {
|
|
158
|
+
consecutiveFailures: state.consecutiveFailures,
|
|
159
|
+
headBlock: state.headBlock,
|
|
160
|
+
apiFailures,
|
|
161
|
+
healthy: this.isNodeHealthy(node),
|
|
162
|
+
});
|
|
163
|
+
}
|
|
164
|
+
return snapshot;
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
exports.NodeHealthTracker = NodeHealthTracker;
|
package/lib/index.d.ts
CHANGED
|
@@ -34,6 +34,8 @@
|
|
|
34
34
|
*/
|
|
35
35
|
import * as utils from './utils';
|
|
36
36
|
export { utils };
|
|
37
|
+
export { NodeHealthTracker } from './health-tracker';
|
|
38
|
+
export type { HealthTrackerOptions } from './health-tracker';
|
|
37
39
|
export * from './helpers/blockchain';
|
|
38
40
|
export * from './helpers/database';
|
|
39
41
|
export * from './helpers/rc';
|
package/lib/index.js
CHANGED
|
@@ -39,6 +39,8 @@ function __export(m) {
|
|
|
39
39
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
40
40
|
const utils = require("./utils");
|
|
41
41
|
exports.utils = utils;
|
|
42
|
+
var health_tracker_1 = require("./health-tracker");
|
|
43
|
+
exports.NodeHealthTracker = health_tracker_1.NodeHealthTracker;
|
|
42
44
|
__export(require("./helpers/blockchain"));
|
|
43
45
|
__export(require("./helpers/database"));
|
|
44
46
|
__export(require("./helpers/rc"));
|
package/lib/utils.d.ts
CHANGED
|
@@ -34,6 +34,20 @@
|
|
|
34
34
|
*/
|
|
35
35
|
/// <reference types="node" />
|
|
36
36
|
import { EventEmitter } from 'events';
|
|
37
|
+
import { NodeHealthTracker } from './health-tracker';
|
|
38
|
+
/**
|
|
39
|
+
* Context for smart retry/failover decisions.
|
|
40
|
+
*/
|
|
41
|
+
export interface RetryContext {
|
|
42
|
+
/** Health tracker instance for per-node, per-API tracking */
|
|
43
|
+
healthTracker?: NodeHealthTracker;
|
|
44
|
+
/** The API being called (e.g. "bridge", "condenser_api", "database_api") */
|
|
45
|
+
api?: string;
|
|
46
|
+
/** Whether this is a broadcast operation — never retry after request may have been received */
|
|
47
|
+
isBroadcast?: boolean;
|
|
48
|
+
/** Whether to log failover events to console */
|
|
49
|
+
consoleOnFailover?: boolean;
|
|
50
|
+
}
|
|
37
51
|
/**
|
|
38
52
|
* Return a promise that will resove when a specific event is emitted.
|
|
39
53
|
*/
|
|
@@ -51,9 +65,19 @@ export declare function iteratorStream<T>(iterator: AsyncIterableIterator<T>): N
|
|
|
51
65
|
*/
|
|
52
66
|
export declare function copy<T>(object: T): T;
|
|
53
67
|
/**
|
|
54
|
-
*
|
|
68
|
+
* Smart fetch with immediate failover and per-node health tracking.
|
|
69
|
+
*
|
|
70
|
+
* For read operations:
|
|
71
|
+
* - On failure, immediately try the next healthy node (no backoff within a round)
|
|
72
|
+
* - After trying all nodes once (one round), apply backoff before the next round
|
|
73
|
+
* - Stop after failoverThreshold rounds
|
|
74
|
+
*
|
|
75
|
+
* For broadcast operations:
|
|
76
|
+
* - Only retry on pre-connection errors (ECONNREFUSED, ENOTFOUND, etc.)
|
|
77
|
+
* where we know the request never reached the server
|
|
78
|
+
* - NEVER retry after timeout or response errors to prevent double-broadcasting
|
|
55
79
|
*/
|
|
56
|
-
export declare function retryingFetch(currentAddress: string, allAddresses: string | string[], opts: any, timeout: number, failoverThreshold: number, consoleOnFailover: boolean, backoff: (tries: number) => number, fetchTimeout?: (tries: number) => number): Promise<{
|
|
80
|
+
export declare function retryingFetch(currentAddress: string, allAddresses: string | string[], opts: any, timeout: number, failoverThreshold: number, consoleOnFailover: boolean, backoff: (tries: number) => number, fetchTimeout?: (tries: number) => number, retryContext?: RetryContext): Promise<{
|
|
57
81
|
response: any;
|
|
58
82
|
currentAddress: string;
|
|
59
83
|
}>;
|