Files
smom-dbis-138/services/relay/src/rpcPool.js

60 lines
1.4 KiB
JavaScript

/**
* Comma-separated RPC URL pool with round-robin and read-call failover.
*/
export function parseRpcPool(...sources) {
const seen = new Set();
const urls = [];
for (const source of sources) {
if (!source || String(source).includes('${')) continue;
for (const part of String(source).split(',')) {
const url = part.trim();
if (!url || seen.has(url)) continue;
seen.add(url);
urls.push(url);
}
}
return urls;
}
export class RpcUrlPool {
constructor(urls, logger) {
this.urls = Array.isArray(urls) ? urls.filter(Boolean) : [];
this.logger = logger;
this._cursor = 0;
}
get size() {
return this.urls.length;
}
primaryUrl() {
if (this.urls.length === 0) return '';
return this.urls[0];
}
nextUrl() {
if (this.urls.length === 0) return '';
const url = this.urls[this._cursor % this.urls.length];
this._cursor += 1;
return url;
}
/** Run async read fn(url) until one succeeds or all fail. */
async withFailover(label, fn) {
if (this.urls.length === 0) {
throw new Error(`${label}: RPC pool is empty`);
}
let lastError;
for (const url of this.urls) {
try {
return await fn(url);
} catch (error) {
lastError = error;
this.logger?.warn?.(`${label} RPC failed (${url}): ${error?.message || error}`);
}
}
throw lastError || new Error(`${label}: all RPC pool URLs failed`);
}
}