@rpckit/fallback 0.9.99
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 +107 -0
- package/dist/electrum-cash/fallback.d.ts +8 -0
- package/dist/electrum-cash/fallback.d.ts.map +1 -0
- package/dist/electrum-cash/fallback.js +40 -0
- package/dist/electrum-cash/fallback.js.map +1 -0
- package/dist/electrum-cash/index.d.ts +2 -0
- package/dist/electrum-cash/index.d.ts.map +1 -0
- package/dist/electrum-cash/index.js +2 -0
- package/dist/electrum-cash/index.js.map +1 -0
- package/dist/fallback.d.ts +6 -0
- package/dist/fallback.d.ts.map +1 -0
- package/dist/fallback.js +208 -0
- package/dist/fallback.js.map +1 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +2 -0
- package/dist/index.js.map +1 -0
- package/package.json +49 -0
package/README.md
ADDED
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
# @rpckit/fallback
|
|
2
|
+
|
|
3
|
+
Fallback meta-transport for rpckit. Provides automatic failover across multiple transports with optional health-based ranking.
|
|
4
|
+
|
|
5
|
+
## Installation
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
npm install @rpckit/core @rpckit/fallback
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
## Usage
|
|
12
|
+
|
|
13
|
+
```typescript
|
|
14
|
+
import { fallback } from '@rpckit/fallback'
|
|
15
|
+
import { webSocket } from '@rpckit/websocket'
|
|
16
|
+
import { http } from '@rpckit/http'
|
|
17
|
+
|
|
18
|
+
const transport = fallback([
|
|
19
|
+
webSocket('wss://primary.example.com'),
|
|
20
|
+
http('https://backup.example.com'),
|
|
21
|
+
])
|
|
22
|
+
|
|
23
|
+
await transport.connect()
|
|
24
|
+
|
|
25
|
+
// Automatically tries next transport on failure
|
|
26
|
+
const result = await transport.request('my.method', 'param')
|
|
27
|
+
|
|
28
|
+
await transport.close()
|
|
29
|
+
```
|
|
30
|
+
|
|
31
|
+
## Options
|
|
32
|
+
|
|
33
|
+
- `shouldThrow` - `(error: Error) => boolean | undefined` to control fallback behavior per error. Return `true` to throw immediately, `false` to continue to next transport, or `undefined` to use default logic. By default, deterministic JSON-RPC errors (parse error, invalid request, invalid params) stop the fallback chain.
|
|
34
|
+
- `rank` - `true` or `{ interval, sampleCount, timeout, weights, ping }` for health ranking
|
|
35
|
+
- `eagerConnect` - Connect to all transports in parallel (fastest transport is prioritized)
|
|
36
|
+
|
|
37
|
+
## Health Ranking
|
|
38
|
+
|
|
39
|
+
When `rank` is enabled, transports are periodically pinged and ranked by latency and stability. Requests are routed to the best-performing transport first.
|
|
40
|
+
|
|
41
|
+
**Note:** Without a `ping` function, ranking has no way to probe transport health and will not collect samples — effectively making it a no-op. Provide a `ping` function appropriate for your protocol.
|
|
42
|
+
|
|
43
|
+
```typescript
|
|
44
|
+
const transport = fallback(transports, {
|
|
45
|
+
rank: {
|
|
46
|
+
interval: 4000, // ms between health checks (default: 4000)
|
|
47
|
+
timeout: 1000, // ping timeout in ms (default: 1000)
|
|
48
|
+
sampleCount: 10, // number of samples to average (default: 10)
|
|
49
|
+
weights: {
|
|
50
|
+
latency: 0.3, // weight for latency score (default: 0.3)
|
|
51
|
+
stability: 0.7, // weight for stability score (default: 0.7)
|
|
52
|
+
},
|
|
53
|
+
ping: (t) => t.request('health.check'), // provide a ping function for your protocol
|
|
54
|
+
},
|
|
55
|
+
})
|
|
56
|
+
```
|
|
57
|
+
|
|
58
|
+
### Accessing Scores
|
|
59
|
+
|
|
60
|
+
```typescript
|
|
61
|
+
// Get current scores
|
|
62
|
+
console.log(transport.scores)
|
|
63
|
+
// [{ transport, score, latency, stability }, ...]
|
|
64
|
+
|
|
65
|
+
// Get ranked transport list
|
|
66
|
+
console.log(transport.transports)
|
|
67
|
+
|
|
68
|
+
// Subscribe to score updates
|
|
69
|
+
const unsub = transport.onScores((scores) => {
|
|
70
|
+
console.log('Scores updated:', scores)
|
|
71
|
+
})
|
|
72
|
+
// Later: unsub()
|
|
73
|
+
```
|
|
74
|
+
|
|
75
|
+
## Electrum Cash Variant
|
|
76
|
+
|
|
77
|
+
The `@rpckit/fallback/electrum-cash` subpath provides a variant with `server.ping` as the default ping function for health ranking and a protocol-aware `shouldThrow` that retries on transient server errors (internal error, OOM, warmup, syncing) while stopping on deterministic errors (invalid params, bad address, etc.):
|
|
78
|
+
|
|
79
|
+
```typescript
|
|
80
|
+
import { fallback } from '@rpckit/fallback/electrum-cash'
|
|
81
|
+
import { webSocket } from '@rpckit/websocket/electrum-cash'
|
|
82
|
+
|
|
83
|
+
const transport = fallback([
|
|
84
|
+
webSocket('wss://server1.example.com:50004'),
|
|
85
|
+
webSocket('wss://server2.example.com:50004'),
|
|
86
|
+
], { rank: true }) // Uses server.ping for health checks
|
|
87
|
+
```
|
|
88
|
+
|
|
89
|
+
The `shouldThrow` function is also exported for use in custom configurations:
|
|
90
|
+
|
|
91
|
+
```typescript
|
|
92
|
+
import { shouldThrow } from '@rpckit/fallback/electrum-cash'
|
|
93
|
+
```
|
|
94
|
+
|
|
95
|
+
## Response Observation
|
|
96
|
+
|
|
97
|
+
Monitor all requests and responses across transports:
|
|
98
|
+
|
|
99
|
+
```typescript
|
|
100
|
+
const unsub = transport.onResponse(({ method, params, transport, response, error, status }) => {
|
|
101
|
+
console.log(`${method} via ${transport.url}: ${status}`)
|
|
102
|
+
if (status === 'error') {
|
|
103
|
+
console.error('Failed:', error)
|
|
104
|
+
}
|
|
105
|
+
})
|
|
106
|
+
// Later: unsub()
|
|
107
|
+
```
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import type { FallbackTransport, FallbackTransportOptions } from '@rpckit/core';
|
|
2
|
+
/** Default shouldThrow for Electrum Cash / BCHN: throws for all deterministic
|
|
3
|
+
* RPC errors (invalid params, bad address, verify rejected, etc.) and falls
|
|
4
|
+
* through on transient server-health issues (internal error, OOM, warmup). */
|
|
5
|
+
export declare function shouldThrow(error: Error): boolean | undefined;
|
|
6
|
+
export declare function fallback<T>(transports: [T], options?: FallbackTransportOptions): T;
|
|
7
|
+
export declare function fallback<T>(transports: [T, T, ...T[]], options?: FallbackTransportOptions): FallbackTransport;
|
|
8
|
+
//# sourceMappingURL=fallback.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"fallback.d.ts","sourceRoot":"","sources":["../../src/electrum-cash/fallback.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAGV,iBAAiB,EACjB,wBAAwB,EAIzB,MAAM,cAAc,CAAA;AAarB;;+EAE+E;AAC/E,wBAAgB,WAAW,CAAC,KAAK,EAAE,KAAK,GAAG,OAAO,GAAG,SAAS,CAQ7D;AAGD,wBAAgB,QAAQ,CAAC,CAAC,EACxB,UAAU,EAAE,CAAC,CAAC,CAAC,EACf,OAAO,CAAC,EAAE,wBAAwB,GACjC,CAAC,CAAA;AAGJ,wBAAgB,QAAQ,CAAC,CAAC,EACxB,UAAU,EAAE,CAAC,CAAC,EAAE,CAAC,EAAE,GAAG,CAAC,EAAE,CAAC,EAC1B,OAAO,CAAC,EAAE,wBAAwB,GACjC,iBAAiB,CAAA"}
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
import { fallback as baseFallback } from '../fallback.js';
|
|
2
|
+
/** Transient server-specific errors worth retrying on another server. */
|
|
3
|
+
const TRANSIENT_CODES = new Set([
|
|
4
|
+
-32603, // RPC_INTERNAL_ERROR — server corruption/bug
|
|
5
|
+
-7, // RPC_OUT_OF_MEMORY
|
|
6
|
+
-20, // RPC_DATABASE_ERROR
|
|
7
|
+
-28, // RPC_IN_WARMUP — server still starting
|
|
8
|
+
-9, // RPC_CLIENT_NOT_CONNECTED — server's node disconnected
|
|
9
|
+
-10, // RPC_CLIENT_IN_INITIAL_DOWNLOAD — server still syncing
|
|
10
|
+
]);
|
|
11
|
+
/** Default shouldThrow for Electrum Cash / BCHN: throws for all deterministic
|
|
12
|
+
* RPC errors (invalid params, bad address, verify rejected, etc.) and falls
|
|
13
|
+
* through on transient server-health issues (internal error, OOM, warmup). */
|
|
14
|
+
export function shouldThrow(error) {
|
|
15
|
+
if ('code' in error &&
|
|
16
|
+
typeof error.code === 'number') {
|
|
17
|
+
return !TRANSIENT_CODES.has(error.code);
|
|
18
|
+
}
|
|
19
|
+
return false;
|
|
20
|
+
}
|
|
21
|
+
// Implementation
|
|
22
|
+
export function fallback(transports, options = {}) {
|
|
23
|
+
// biome-ignore lint/suspicious/noExplicitAny: overload forwarding requires type erasure
|
|
24
|
+
return baseFallback(transports, resolveOptions(options));
|
|
25
|
+
}
|
|
26
|
+
function resolveOptions(options) {
|
|
27
|
+
const resolved = { ...options };
|
|
28
|
+
if (!resolved.shouldThrow) {
|
|
29
|
+
resolved.shouldThrow = shouldThrow;
|
|
30
|
+
}
|
|
31
|
+
if (resolved.rank) {
|
|
32
|
+
const rankOpts = typeof resolved.rank === 'object' ? { ...resolved.rank } : {};
|
|
33
|
+
if (!rankOpts.ping) {
|
|
34
|
+
rankOpts.ping = (t) => t.request('server.ping');
|
|
35
|
+
}
|
|
36
|
+
resolved.rank = rankOpts;
|
|
37
|
+
}
|
|
38
|
+
return resolved;
|
|
39
|
+
}
|
|
40
|
+
//# sourceMappingURL=fallback.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"fallback.js","sourceRoot":"","sources":["../../src/electrum-cash/fallback.ts"],"names":[],"mappings":"AASA,OAAO,EAAE,QAAQ,IAAI,YAAY,EAAE,MAAM,gBAAgB,CAAA;AAEzD,yEAAyE;AACzE,MAAM,eAAe,GAAG,IAAI,GAAG,CAAC;IAC9B,CAAC,KAAK,EAAE,6CAA6C;IACrD,CAAC,CAAC,EAAE,oBAAoB;IACxB,CAAC,EAAE,EAAE,qBAAqB;IAC1B,CAAC,EAAE,EAAE,wCAAwC;IAC7C,CAAC,CAAC,EAAE,wDAAwD;IAC5D,CAAC,EAAE,EAAE,wDAAwD;CAC9D,CAAC,CAAA;AAEF;;+EAE+E;AAC/E,MAAM,UAAU,WAAW,CAAC,KAAY;IACtC,IACE,MAAM,IAAI,KAAK;QACf,OAAQ,KAA2B,CAAC,IAAI,KAAK,QAAQ,EACrD,CAAC;QACD,OAAO,CAAC,eAAe,CAAC,GAAG,CAAE,KAA0B,CAAC,IAAI,CAAC,CAAA;IAC/D,CAAC;IACD,OAAO,KAAK,CAAA;AACd,CAAC;AAcD,iBAAiB;AACjB,MAAM,UAAU,QAAQ,CACtB,UAA0B,EAC1B,UAAuC,EAAE;IAEzC,wFAAwF;IACxF,OAAQ,YAAoB,CAAC,UAAU,EAAE,cAAc,CAAC,OAAO,CAAC,CAAC,CAAA;AACnE,CAAC;AAED,SAAS,cAAc,CACrB,OAAoC;IAEpC,MAAM,QAAQ,GAAgC,EAAE,GAAG,OAAO,EAAE,CAAA;IAE5D,IAAI,CAAC,QAAQ,CAAC,WAAW,EAAE,CAAC;QAC1B,QAAQ,CAAC,WAAW,GAAG,WAAW,CAAA;IACpC,CAAC;IAED,IAAI,QAAQ,CAAC,IAAI,EAAE,CAAC;QAClB,MAAM,QAAQ,GACZ,OAAO,QAAQ,CAAC,IAAI,KAAK,QAAQ,CAAC,CAAC,CAAC,EAAE,GAAG,QAAQ,CAAC,IAAI,EAAE,CAAC,CAAC,CAAC,EAAE,CAAA;QAE/D,IAAI,CAAC,QAAQ,CAAC,IAAI,EAAE,CAAC;YACnB,QAAQ,CAAC,IAAI,GAAG,CAAC,CAAe,EAAE,EAAE,CAClC,CAAC,CAAC,OAAO,CAAC,aAAwC,CAAC,CAAA;QACvD,CAAC;QAED,QAAQ,CAAC,IAAI,GAAG,QAAQ,CAAA;IAC1B,CAAC;IAED,OAAO,QAAQ,CAAA;AACjB,CAAC"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/electrum-cash/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,QAAQ,EAAE,WAAW,EAAE,MAAM,eAAe,CAAA"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/electrum-cash/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,QAAQ,EAAE,WAAW,EAAE,MAAM,eAAe,CAAA"}
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
import type { FallbackTransport, FallbackTransportOptions } from '@rpckit/core';
|
|
2
|
+
export declare function fallback<T>(transports: [T], options?: FallbackTransportOptions): T;
|
|
3
|
+
export declare function fallback<T>(transports: [T, T, ...T[]], options?: FallbackTransportOptions): FallbackTransport;
|
|
4
|
+
/** Default shouldThrow: stops fallback for deterministic JSON-RPC errors. */
|
|
5
|
+
export declare function shouldThrow(error: Error): boolean | undefined;
|
|
6
|
+
//# sourceMappingURL=fallback.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"fallback.d.ts","sourceRoot":"","sources":["../src/fallback.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAEV,iBAAiB,EACjB,wBAAwB,EAMzB,MAAM,cAAc,CAAA;AAGrB,wBAAgB,QAAQ,CAAC,CAAC,EACxB,UAAU,EAAE,CAAC,CAAC,CAAC,EACf,OAAO,CAAC,EAAE,wBAAwB,GACjC,CAAC,CAAA;AAGJ,wBAAgB,QAAQ,CAAC,CAAC,EACxB,UAAU,EAAE,CAAC,CAAC,EAAE,CAAC,EAAE,GAAG,CAAC,EAAE,CAAC,EAC1B,OAAO,CAAC,EAAE,wBAAwB,GACjC,iBAAiB,CAAA;AA4OpB,6EAA6E;AAC7E,wBAAgB,WAAW,CAAC,KAAK,EAAE,KAAK,GAAG,OAAO,GAAG,SAAS,CAc7D"}
|
package/dist/fallback.js
ADDED
|
@@ -0,0 +1,208 @@
|
|
|
1
|
+
// Implementation
|
|
2
|
+
export function fallback(transports, options = {}) {
|
|
3
|
+
// Single transport: no need to wrap
|
|
4
|
+
if (transports.length === 1) {
|
|
5
|
+
return transports[0];
|
|
6
|
+
}
|
|
7
|
+
let ranked = [...transports];
|
|
8
|
+
const { shouldThrow: shouldThrow_ = shouldThrow } = options;
|
|
9
|
+
let rankingTimer = null;
|
|
10
|
+
let rankingStopped = false;
|
|
11
|
+
let currentScores = transports.map((t) => ({
|
|
12
|
+
transport: t,
|
|
13
|
+
score: 0,
|
|
14
|
+
latency: 0,
|
|
15
|
+
stability: 0,
|
|
16
|
+
}));
|
|
17
|
+
const scoreListeners = new Set();
|
|
18
|
+
const responseListeners = new Set();
|
|
19
|
+
if (options.rank) {
|
|
20
|
+
const rankOpts = typeof options.rank === 'object' ? options.rank : {};
|
|
21
|
+
startRanking(transports, rankOpts);
|
|
22
|
+
}
|
|
23
|
+
function startRanking(transports, opts) {
|
|
24
|
+
if (!opts.ping)
|
|
25
|
+
return;
|
|
26
|
+
const ping = opts.ping;
|
|
27
|
+
const interval = opts.interval ?? 4000;
|
|
28
|
+
const sampleCount = opts.sampleCount ?? 10;
|
|
29
|
+
const timeout = opts.timeout ?? 1000;
|
|
30
|
+
const latencyWeight = opts.weights?.latency ?? 0.3;
|
|
31
|
+
const stabilityWeight = opts.weights?.stability ?? 0.7;
|
|
32
|
+
const samples = [];
|
|
33
|
+
async function sample() {
|
|
34
|
+
if (rankingStopped)
|
|
35
|
+
return;
|
|
36
|
+
const results = await Promise.all(transports.map(async (transport) => {
|
|
37
|
+
const start = performance.now();
|
|
38
|
+
try {
|
|
39
|
+
await Promise.race([
|
|
40
|
+
ping(transport),
|
|
41
|
+
new Promise((_, reject) => setTimeout(() => reject(new Error('ping timeout')), timeout)),
|
|
42
|
+
]);
|
|
43
|
+
return { latency: performance.now() - start, success: 1 };
|
|
44
|
+
}
|
|
45
|
+
catch {
|
|
46
|
+
return { latency: timeout, success: 0 };
|
|
47
|
+
}
|
|
48
|
+
}));
|
|
49
|
+
samples.push(results);
|
|
50
|
+
if (samples.length > sampleCount)
|
|
51
|
+
samples.shift();
|
|
52
|
+
const maxLatency = Math.max(...samples.flatMap((s) => s.map((r) => r.latency)));
|
|
53
|
+
const scores = transports.map((transport, i) => {
|
|
54
|
+
const latencies = samples.map((s) => s[i].latency);
|
|
55
|
+
const meanLatency = latencies.reduce((a, b) => a + b, 0) / latencies.length;
|
|
56
|
+
const latencyScore = maxLatency > 0 ? 1 - meanLatency / maxLatency : 0;
|
|
57
|
+
const successes = samples.map((s) => s[i].success);
|
|
58
|
+
const stabilityScore = successes.reduce((a, b) => a + b, 0) / successes.length;
|
|
59
|
+
const score = stabilityScore === 0
|
|
60
|
+
? 0
|
|
61
|
+
: latencyWeight * latencyScore + stabilityWeight * stabilityScore;
|
|
62
|
+
return {
|
|
63
|
+
transport,
|
|
64
|
+
score,
|
|
65
|
+
latency: meanLatency,
|
|
66
|
+
stability: stabilityScore,
|
|
67
|
+
};
|
|
68
|
+
});
|
|
69
|
+
scores.sort((a, b) => b.score - a.score);
|
|
70
|
+
ranked = scores.map((s) => s.transport);
|
|
71
|
+
currentScores = scores;
|
|
72
|
+
for (const listener of scoreListeners) {
|
|
73
|
+
listener(scores);
|
|
74
|
+
}
|
|
75
|
+
if (!rankingStopped) {
|
|
76
|
+
rankingTimer = setTimeout(sample, interval);
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
sample();
|
|
80
|
+
}
|
|
81
|
+
return {
|
|
82
|
+
url: transports.map((t) => t.url).join(','),
|
|
83
|
+
async connect() {
|
|
84
|
+
if (options.eagerConnect) {
|
|
85
|
+
const connectResults = ranked.map((t, i) => t.connect().then(() => i));
|
|
86
|
+
const fastestIndex = await Promise.any(connectResults);
|
|
87
|
+
// Move the fastest-connecting transport to front priority
|
|
88
|
+
if (fastestIndex > 0) {
|
|
89
|
+
const [fastest] = ranked.splice(fastestIndex, 1);
|
|
90
|
+
ranked.unshift(fastest);
|
|
91
|
+
}
|
|
92
|
+
// Let remaining connections settle in background
|
|
93
|
+
Promise.allSettled(connectResults);
|
|
94
|
+
}
|
|
95
|
+
else {
|
|
96
|
+
await Promise.all(ranked.map((t) => t.connect()));
|
|
97
|
+
}
|
|
98
|
+
},
|
|
99
|
+
get transports() {
|
|
100
|
+
return ranked;
|
|
101
|
+
},
|
|
102
|
+
get scores() {
|
|
103
|
+
return currentScores;
|
|
104
|
+
},
|
|
105
|
+
onScores(listener) {
|
|
106
|
+
scoreListeners.add(listener);
|
|
107
|
+
return () => {
|
|
108
|
+
scoreListeners.delete(listener);
|
|
109
|
+
};
|
|
110
|
+
},
|
|
111
|
+
onResponse(listener) {
|
|
112
|
+
responseListeners.add(listener);
|
|
113
|
+
return () => {
|
|
114
|
+
responseListeners.delete(listener);
|
|
115
|
+
};
|
|
116
|
+
},
|
|
117
|
+
async request(method, ...params) {
|
|
118
|
+
let lastError;
|
|
119
|
+
for (const transport of ranked) {
|
|
120
|
+
try {
|
|
121
|
+
// biome-ignore lint/suspicious/noExplicitAny: meta-transport delegates with erased types
|
|
122
|
+
const response = await transport.request(method, ...params);
|
|
123
|
+
for (const listener of responseListeners) {
|
|
124
|
+
listener({
|
|
125
|
+
method,
|
|
126
|
+
params,
|
|
127
|
+
transport,
|
|
128
|
+
response,
|
|
129
|
+
status: 'success',
|
|
130
|
+
});
|
|
131
|
+
}
|
|
132
|
+
return response;
|
|
133
|
+
}
|
|
134
|
+
catch (error) {
|
|
135
|
+
lastError = error;
|
|
136
|
+
for (const listener of responseListeners) {
|
|
137
|
+
listener({
|
|
138
|
+
method,
|
|
139
|
+
params,
|
|
140
|
+
transport,
|
|
141
|
+
error,
|
|
142
|
+
status: 'error',
|
|
143
|
+
});
|
|
144
|
+
}
|
|
145
|
+
if (shouldThrow_(error))
|
|
146
|
+
throw error;
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
throw lastError;
|
|
150
|
+
},
|
|
151
|
+
async subscribe(method, ...args) {
|
|
152
|
+
const onData = args.pop();
|
|
153
|
+
const params = args;
|
|
154
|
+
let lastError;
|
|
155
|
+
for (const transport of ranked) {
|
|
156
|
+
try {
|
|
157
|
+
// biome-ignore lint/suspicious/noExplicitAny: meta-transport delegates with erased types
|
|
158
|
+
const result = await transport.subscribe(method, ...params, onData);
|
|
159
|
+
for (const listener of responseListeners) {
|
|
160
|
+
listener({
|
|
161
|
+
method,
|
|
162
|
+
params,
|
|
163
|
+
transport,
|
|
164
|
+
response: result,
|
|
165
|
+
status: 'success',
|
|
166
|
+
});
|
|
167
|
+
}
|
|
168
|
+
return result;
|
|
169
|
+
}
|
|
170
|
+
catch (error) {
|
|
171
|
+
lastError = error;
|
|
172
|
+
for (const listener of responseListeners) {
|
|
173
|
+
listener({
|
|
174
|
+
method,
|
|
175
|
+
params,
|
|
176
|
+
transport,
|
|
177
|
+
error,
|
|
178
|
+
status: 'error',
|
|
179
|
+
});
|
|
180
|
+
}
|
|
181
|
+
}
|
|
182
|
+
}
|
|
183
|
+
throw lastError;
|
|
184
|
+
},
|
|
185
|
+
async close() {
|
|
186
|
+
rankingStopped = true;
|
|
187
|
+
if (rankingTimer)
|
|
188
|
+
clearTimeout(rankingTimer);
|
|
189
|
+
scoreListeners.clear();
|
|
190
|
+
responseListeners.clear();
|
|
191
|
+
await Promise.all(transports.map((t) => t.close()));
|
|
192
|
+
},
|
|
193
|
+
};
|
|
194
|
+
}
|
|
195
|
+
/** Default shouldThrow: stops fallback for deterministic JSON-RPC errors. */
|
|
196
|
+
export function shouldThrow(error) {
|
|
197
|
+
if ('code' in error &&
|
|
198
|
+
typeof error.code === 'number') {
|
|
199
|
+
const code = error.code;
|
|
200
|
+
if (code === -32700 || // Parse error
|
|
201
|
+
code === -32600 || // Invalid Request
|
|
202
|
+
code === -32602 // Invalid params
|
|
203
|
+
)
|
|
204
|
+
return true;
|
|
205
|
+
}
|
|
206
|
+
return false;
|
|
207
|
+
}
|
|
208
|
+
//# sourceMappingURL=fallback.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"fallback.js","sourceRoot":"","sources":["../src/fallback.ts"],"names":[],"mappings":"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"}
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,QAAQ,EAAE,WAAW,EAAE,MAAM,eAAe,CAAA"}
|
package/dist/index.js
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,QAAQ,EAAE,WAAW,EAAE,MAAM,eAAe,CAAA"}
|
package/package.json
ADDED
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@rpckit/fallback",
|
|
3
|
+
"version": "0.9.99",
|
|
4
|
+
"description": "Fallback meta-transport for rpckit with automatic failover and health ranking",
|
|
5
|
+
"author": "mainnet_pat",
|
|
6
|
+
"license": "MIT",
|
|
7
|
+
"type": "module",
|
|
8
|
+
"sideEffects": false,
|
|
9
|
+
"main": "dist/index.js",
|
|
10
|
+
"types": "dist/index.d.ts",
|
|
11
|
+
"exports": {
|
|
12
|
+
".": {
|
|
13
|
+
"types": "./dist/index.d.ts",
|
|
14
|
+
"default": "./dist/index.js"
|
|
15
|
+
},
|
|
16
|
+
"./electrum-cash": {
|
|
17
|
+
"types": "./dist/electrum-cash/index.d.ts",
|
|
18
|
+
"default": "./dist/electrum-cash/index.js"
|
|
19
|
+
}
|
|
20
|
+
},
|
|
21
|
+
"files": [
|
|
22
|
+
"dist"
|
|
23
|
+
],
|
|
24
|
+
"keywords": [
|
|
25
|
+
"json-rpc",
|
|
26
|
+
"rpc",
|
|
27
|
+
"fallback",
|
|
28
|
+
"failover",
|
|
29
|
+
"typescript"
|
|
30
|
+
],
|
|
31
|
+
"repository": {
|
|
32
|
+
"type": "git",
|
|
33
|
+
"url": "git+https://github.com/mainnet-pat/rpckit.git",
|
|
34
|
+
"directory": "packages/fallback"
|
|
35
|
+
},
|
|
36
|
+
"homepage": "https://rpckit.dev",
|
|
37
|
+
"bugs": {
|
|
38
|
+
"url": "https://github.com/mainnet-pat/rpckit/issues"
|
|
39
|
+
},
|
|
40
|
+
"scripts": {
|
|
41
|
+
"build": "tsc"
|
|
42
|
+
},
|
|
43
|
+
"dependencies": {
|
|
44
|
+
"@rpckit/core": "*"
|
|
45
|
+
},
|
|
46
|
+
"devDependencies": {
|
|
47
|
+
"typescript": "^5.7.0"
|
|
48
|
+
}
|
|
49
|
+
}
|