@lidofinance/rpc 0.58.0 → 0.59.0

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 CHANGED
@@ -42,3 +42,25 @@ export const cachedFetchRpcFactory = (timeout: number): CachedFetchRPC => {
42
42
  }
43
43
  }
44
44
  ```
45
+
46
+ ### iterateUrls fallback callback
47
+
48
+ `iterateUrls(urls, callback, onError?)` iterates provider URLs until one succeeds. The optional `onError` handler fires when an attempt fails — either the callback threw, or it returned a non-ok `Response`. The second argument carries context so you can log structured fallback transitions.
49
+
50
+ ```ts
51
+ import { iterateUrls } from '@lidofinance/rpc'
52
+
53
+ await iterateUrls(
54
+ providers,
55
+ (url) => fetch(url),
56
+ (error, { failedUrl, nextUrl, attempt, response }) => {
57
+ if (nextUrl) {
58
+ logger.warn(`RPC ${failedUrl} failed (attempt ${attempt}), switching to ${nextUrl}`)
59
+ } else {
60
+ logger.error(`All RPC providers failed, last one was ${failedUrl}`)
61
+ }
62
+ },
63
+ )
64
+ ```
65
+
66
+ Handlers with a single-argument signature (`(error) => ...`) keep working — the extra context argument is simply ignored.
package/dist/index.cjs CHANGED
@@ -46,15 +46,31 @@ class $f5c96785547d6405$export$544290ca59c30f2b extends Error {
46
46
  const $f5c96785547d6405$export$874e79e4fbe19379 = async (urls, callback, onError)=>{
47
47
  let lastResponse;
48
48
  let lastError;
49
- for (const url of urls)try {
50
- const response = await callback(url);
51
- lastResponse = response;
52
- // We want to return first succeeded response
53
- if (typeof Response !== "undefined" && response instanceof Response && !response.ok) continue;
54
- return response;
55
- } catch (error) {
56
- onError?.(error);
57
- lastError = error;
49
+ for(let i = 0; i < urls.length; i++){
50
+ const url = urls[i];
51
+ const nextUrl = urls[i + 1];
52
+ try {
53
+ const response = await callback(url);
54
+ lastResponse = response;
55
+ // We want to return first succeeded response
56
+ if (typeof Response !== "undefined" && response instanceof Response && !response.ok) {
57
+ if (onError) onError(new Error(`Non-ok response: ${response.status}`), {
58
+ failedUrl: url,
59
+ nextUrl: nextUrl,
60
+ attempt: i + 1,
61
+ response: response
62
+ });
63
+ continue;
64
+ }
65
+ return response;
66
+ } catch (error) {
67
+ if (onError) onError(error, {
68
+ failedUrl: url,
69
+ nextUrl: nextUrl,
70
+ attempt: i + 1
71
+ });
72
+ lastError = error;
73
+ }
58
74
  }
59
75
  // If there are no succeeded responses, return last not thrown
60
76
  if (lastResponse != null) return lastResponse;
@@ -1 +1 @@
1
- {"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;ACoBO,MAAM,4CAAqB,CAAC,KAAK,OACtC,MAAM,KAAK;QACT,GAAG,IAAI;QACP,QAAQ;QACR,SAAS;YACP,GAAG,KAAK,OAAO;YACf,gBAAgB;QAClB;QACA,MAAM,KAAK,UAAU,KAAK;IAC5B;;;;;;;AC7BK,MAAM,kDAAgC;IAC3C,YAAY,OAAgB,CAAE;QAC5B,KAAK,CAAC,WAAW;IACnB;AACF;AAEO,MAAM,4CAAc,OACzB,MACA,UACA;IAEA,IAAI;IACJ,IAAI;IAEJ,KAAK,MAAM,OAAO,KAChB,IAAI;QACF,MAAM,WAAW,MAAM,SAAS;QAChC,eAAe;QAEf,6CAA6C;QAC7C,IAAI,OAAO,aAAa,eAAe,oBAAoB,YAAY,CAAC,SAAS,IAC/E;QAGF,OAAO;IACT,EAAE,OAAO,OAAO;QACd,UAAU;QACV,YAAY;IACd;IAGF,8DAA8D;IAC9D,IAAI,gBAAgB,MAClB,OAAO;IAET,qDAAqD;IACrD,IAAI,aAAa,MACf,MAAM;IAER,+BAA+B;IAC/B,MAAM,IAAI;AACZ;;","sources":["packages/core/rpc/src/index.ts","packages/core/rpc/src/fetchRpc.ts","packages/core/rpc/src/iterateUrls.ts"],"sourcesContent":["import 'isomorphic-fetch'\n\nexport * from './fetchRpc'\nexport * from './iterateUrls'\n","export type FetchRpcInitBody = {\n jsonrpc: '1.0' | '2.0' | string\n method: string\n params?: unknown\n id?: string | number | null\n}\n\n// eslint-disable-next-line no-undef\nexport type FetchRpcInit = Omit<RequestInit, 'body' | 'method'> & {\n method?: 'POST'\n body: FetchRpcInitBody | FetchRpcInitBody[]\n}\n\n// Need Extension type so we can extend fetchRPC with metrics tracking or caching\nexport type FetchRpc<Extension extends Record<string | number, unknown> | void = void> = (\n url: string,\n init: FetchRpcInit,\n extension: Extension,\n) => Promise<Response>\n\nexport const fetchRpc: FetchRpc = (url, init) =>\n fetch(url, {\n ...init,\n method: 'POST',\n headers: {\n ...init.headers,\n 'Content-Type': 'application/json',\n },\n body: JSON.stringify(init.body),\n })\n","export class ExhaustedIterationError extends Error {\n constructor(message?: string) {\n super(message ?? 'Iteration ended without success')\n }\n}\n\nexport const iterateUrls = async <T>(\n urls: [string, ...string[]],\n callback: (url: string) => T | Promise<T>,\n onError?: (error: unknown) => unknown,\n): Promise<T> => {\n let lastResponse: T | undefined\n let lastError: unknown | undefined\n\n for (const url of urls) {\n try {\n const response = await callback(url)\n lastResponse = response\n\n // We want to return first succeeded response\n if (typeof Response !== 'undefined' && response instanceof Response && !response.ok) {\n continue\n }\n\n return response\n } catch (error) {\n onError?.(error)\n lastError = error\n }\n }\n\n // If there are no succeeded responses, return last not thrown\n if (lastResponse != null) {\n return lastResponse\n }\n // If there are no responses at all, throw last error\n if (lastError != null) {\n throw lastError\n }\n // This should not be reachable\n throw new ExhaustedIterationError()\n}\n"],"names":[],"version":3,"file":"index.cjs.map"}
1
+ {"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;ACoBO,MAAM,4CAAqB,CAAC,KAAK,OACtC,MAAM,KAAK;QACT,GAAG,IAAI;QACP,QAAQ;QACR,SAAS;YACP,GAAG,KAAK,OAAO;YACf,gBAAgB;QAClB;QACA,MAAM,KAAK,UAAU,KAAK;IAC5B;;;;;;;AC7BK,MAAM,kDAAgC;IAC3C,YAAY,OAAgB,CAAE;QAC5B,KAAK,CAAC,WAAW;IACnB;AACF;AASO,MAAM,4CAAc,OACzB,MACA,UACA;IAEA,IAAI;IACJ,IAAI;IAEJ,IAAK,IAAI,IAAI,GAAG,IAAI,KAAK,QAAQ,IAAK;QACpC,MAAM,MAAM,IAAI,CAAC,EAAE;QACnB,MAAM,UAAU,IAAI,CAAC,IAAI,EAAE;QAC3B,IAAI;YACF,MAAM,WAAW,MAAM,SAAS;YAChC,eAAe;YAEf,6CAA6C;YAC7C,IAAI,OAAO,aAAa,eAAe,oBAAoB,YAAY,CAAC,SAAS,IAAI;gBACnF,IAAI,SACF,QAAQ,IAAI,MAAM,CAAC,iBAAiB,EAAE,SAAS,OAAO,CAAC,GAAG;oBACxD,WAAW;6BACX;oBACA,SAAS,IAAI;8BACb;gBACF;gBAEF;YACF;YAEA,OAAO;QACT,EAAE,OAAO,OAAO;YACd,IAAI,SACF,QAAQ,OAAO;gBAAE,WAAW;yBAAK;gBAAS,SAAS,IAAI;YAAE;YAE3D,YAAY;QACd;IACF;IAEA,8DAA8D;IAC9D,IAAI,gBAAgB,MAClB,OAAO;IAET,qDAAqD;IACrD,IAAI,aAAa,MACf,MAAM;IAER,+BAA+B;IAC/B,MAAM,IAAI;AACZ;;","sources":["packages/core/rpc/src/index.ts","packages/core/rpc/src/fetchRpc.ts","packages/core/rpc/src/iterateUrls.ts"],"sourcesContent":["import 'isomorphic-fetch'\n\nexport * from './fetchRpc'\nexport * from './iterateUrls'\n","export type FetchRpcInitBody = {\n jsonrpc: '1.0' | '2.0' | string\n method: string\n params?: unknown\n id?: string | number | null\n}\n\n// eslint-disable-next-line no-undef\nexport type FetchRpcInit = Omit<RequestInit, 'body' | 'method'> & {\n method?: 'POST'\n body: FetchRpcInitBody | FetchRpcInitBody[]\n}\n\n// Need Extension type so we can extend fetchRPC with metrics tracking or caching\nexport type FetchRpc<Extension extends Record<string | number, unknown> | void = void> = (\n url: string,\n init: FetchRpcInit,\n extension: Extension,\n) => Promise<Response>\n\nexport const fetchRpc: FetchRpc = (url, init) =>\n fetch(url, {\n ...init,\n method: 'POST',\n headers: {\n ...init.headers,\n 'Content-Type': 'application/json',\n },\n body: JSON.stringify(init.body),\n })\n","export class ExhaustedIterationError extends Error {\n constructor(message?: string) {\n super(message ?? 'Iteration ended without success')\n }\n}\n\nexport type IterateUrlsErrorContext = {\n failedUrl: string\n nextUrl: string | undefined\n attempt: number\n response?: Response\n}\n\nexport const iterateUrls = async <T>(\n urls: [string, ...string[]],\n callback: (url: string) => T | Promise<T>,\n onError?: (error: unknown, context: IterateUrlsErrorContext) => unknown,\n): Promise<T> => {\n let lastResponse: T | undefined\n let lastError: unknown | undefined\n\n for (let i = 0; i < urls.length; i++) {\n const url = urls[i]\n const nextUrl = urls[i + 1]\n try {\n const response = await callback(url)\n lastResponse = response\n\n // We want to return first succeeded response\n if (typeof Response !== 'undefined' && response instanceof Response && !response.ok) {\n if (onError) {\n onError(new Error(`Non-ok response: ${response.status}`), {\n failedUrl: url,\n nextUrl,\n attempt: i + 1,\n response,\n })\n }\n continue\n }\n\n return response\n } catch (error) {\n if (onError) {\n onError(error, { failedUrl: url, nextUrl, attempt: i + 1 })\n }\n lastError = error\n }\n }\n\n // If there are no succeeded responses, return last not thrown\n if (lastResponse != null) {\n return lastResponse\n }\n // If there are no responses at all, throw last error\n if (lastError != null) {\n throw lastError\n }\n // This should not be reachable\n throw new ExhaustedIterationError()\n}\n"],"names":[],"version":3,"file":"index.cjs.map"}
package/dist/index.d.ts CHANGED
@@ -13,6 +13,12 @@ export const fetchRpc: FetchRpc;
13
13
  export class ExhaustedIterationError extends Error {
14
14
  constructor(message?: string);
15
15
  }
16
- export const iterateUrls: <T>(urls: [string, ...string[]], callback: (url: string) => T | Promise<T>, onError?: ((error: unknown) => unknown) | undefined) => Promise<T>;
16
+ export type IterateUrlsErrorContext = {
17
+ failedUrl: string;
18
+ nextUrl: string | undefined;
19
+ attempt: number;
20
+ response?: Response;
21
+ };
22
+ export const iterateUrls: <T>(urls: [string, ...string[]], callback: (url: string) => T | Promise<T>, onError?: ((error: unknown, context: IterateUrlsErrorContext) => unknown) | undefined) => Promise<T>;
17
23
 
18
24
  //# sourceMappingURL=index.d.ts.map
@@ -1 +1 @@
1
- {"mappings":"AAAA,+BAA+B;IAC7B,OAAO,EAAE,KAAK,GAAG,KAAK,GAAG,MAAM,CAAA;IAC/B,MAAM,EAAE,MAAM,CAAA;IACd,MAAM,CAAC,EAAE,OAAO,CAAA;IAChB,EAAE,CAAC,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI,CAAA;CAC5B,CAAA;AAGD,2BAA2B,IAAI,CAAC,WAAW,EAAE,MAAM,GAAG,QAAQ,CAAC,GAAG;IAChE,MAAM,CAAC,EAAE,MAAM,CAAA;IACf,IAAI,EAAE,gBAAgB,GAAG,gBAAgB,EAAE,CAAA;CAC5C,CAAA;AAGD,qBAAqB,SAAS,SAAS,MAAM,CAAC,MAAM,GAAG,MAAM,EAAE,OAAO,CAAC,GAAG,IAAI,GAAG,IAAI,IAAI,CACvF,GAAG,EAAE,MAAM,EACX,IAAI,EAAE,YAAY,EAClB,SAAS,EAAE,SAAS,KACjB,OAAO,CAAC,QAAQ,CAAC,CAAA;AAEtB,OAAO,MAAM,UAAU,QASnB,CAAA;AC7BJ,oCAAqC,SAAQ,KAAK;gBACpC,OAAO,CAAC,EAAE,MAAM;CAG7B;AAED,OAAO,MAAM,uBACL,CAAC,MAAM,EAAE,GAAG,MAAM,EAAE,CAAC,kBACX,MAAM,wCACJ,OAAO,KAAK,OAAO,4BAgCtC,CAAA","sources":["packages/core/rpc/src/src/fetchRpc.ts","packages/core/rpc/src/src/iterateUrls.ts","packages/core/rpc/src/src/index.ts","packages/core/rpc/src/index.ts"],"sourcesContent":[null,null,null,"import 'isomorphic-fetch'\n\nexport * from './fetchRpc'\nexport * from './iterateUrls'\n"],"names":[],"version":3,"file":"index.d.ts.map"}
1
+ {"mappings":"AAAA,+BAA+B;IAC7B,OAAO,EAAE,KAAK,GAAG,KAAK,GAAG,MAAM,CAAA;IAC/B,MAAM,EAAE,MAAM,CAAA;IACd,MAAM,CAAC,EAAE,OAAO,CAAA;IAChB,EAAE,CAAC,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI,CAAA;CAC5B,CAAA;AAGD,2BAA2B,IAAI,CAAC,WAAW,EAAE,MAAM,GAAG,QAAQ,CAAC,GAAG;IAChE,MAAM,CAAC,EAAE,MAAM,CAAA;IACf,IAAI,EAAE,gBAAgB,GAAG,gBAAgB,EAAE,CAAA;CAC5C,CAAA;AAGD,qBAAqB,SAAS,SAAS,MAAM,CAAC,MAAM,GAAG,MAAM,EAAE,OAAO,CAAC,GAAG,IAAI,GAAG,IAAI,IAAI,CACvF,GAAG,EAAE,MAAM,EACX,IAAI,EAAE,YAAY,EAClB,SAAS,EAAE,SAAS,KACjB,OAAO,CAAC,QAAQ,CAAC,CAAA;AAEtB,OAAO,MAAM,UAAU,QASnB,CAAA;AC7BJ,oCAAqC,SAAQ,KAAK;gBACpC,OAAO,CAAC,EAAE,MAAM;CAG7B;AAED,sCAAsC;IACpC,SAAS,EAAE,MAAM,CAAA;IACjB,OAAO,EAAE,MAAM,GAAG,SAAS,CAAA;IAC3B,OAAO,EAAE,MAAM,CAAA;IACf,QAAQ,CAAC,EAAE,QAAQ,CAAA;CACpB,CAAA;AAED,OAAO,MAAM,uBACL,CAAC,MAAM,EAAE,GAAG,MAAM,EAAE,CAAC,kBACX,MAAM,wCACJ,OAAO,WAAW,uBAAuB,KAAK,OAAO,4BA4CxE,CAAA","sources":["packages/core/rpc/src/src/fetchRpc.ts","packages/core/rpc/src/src/iterateUrls.ts","packages/core/rpc/src/src/index.ts","packages/core/rpc/src/index.ts"],"sourcesContent":[null,null,null,"import 'isomorphic-fetch'\n\nexport * from './fetchRpc'\nexport * from './iterateUrls'\n"],"names":[],"version":3,"file":"index.d.ts.map"}
package/dist/index.mjs CHANGED
@@ -30,15 +30,31 @@ class $83a6df06ccf0d0e9$export$544290ca59c30f2b extends Error {
30
30
  const $83a6df06ccf0d0e9$export$874e79e4fbe19379 = async (urls, callback, onError)=>{
31
31
  let lastResponse;
32
32
  let lastError;
33
- for (const url of urls)try {
34
- const response = await callback(url);
35
- lastResponse = response;
36
- // We want to return first succeeded response
37
- if (typeof Response !== "undefined" && response instanceof Response && !response.ok) continue;
38
- return response;
39
- } catch (error) {
40
- onError?.(error);
41
- lastError = error;
33
+ for(let i = 0; i < urls.length; i++){
34
+ const url = urls[i];
35
+ const nextUrl = urls[i + 1];
36
+ try {
37
+ const response = await callback(url);
38
+ lastResponse = response;
39
+ // We want to return first succeeded response
40
+ if (typeof Response !== "undefined" && response instanceof Response && !response.ok) {
41
+ if (onError) onError(new Error(`Non-ok response: ${response.status}`), {
42
+ failedUrl: url,
43
+ nextUrl: nextUrl,
44
+ attempt: i + 1,
45
+ response: response
46
+ });
47
+ continue;
48
+ }
49
+ return response;
50
+ } catch (error) {
51
+ if (onError) onError(error, {
52
+ failedUrl: url,
53
+ nextUrl: nextUrl,
54
+ attempt: i + 1
55
+ });
56
+ lastError = error;
57
+ }
42
58
  }
43
59
  // If there are no succeeded responses, return last not thrown
44
60
  if (lastResponse != null) return lastResponse;
@@ -1 +1 @@
1
- {"mappings":";;;;;;;;;ACoBO,MAAM,4CAAqB,CAAC,KAAK,OACtC,MAAM,KAAK;QACT,GAAG,IAAI;QACP,QAAQ;QACR,SAAS;YACP,GAAG,KAAK,OAAO;YACf,gBAAgB;QAClB;QACA,MAAM,KAAK,UAAU,KAAK;IAC5B;;;;;;;AC7BK,MAAM,kDAAgC;IAC3C,YAAY,OAAgB,CAAE;QAC5B,KAAK,CAAC,WAAW;IACnB;AACF;AAEO,MAAM,4CAAc,OACzB,MACA,UACA;IAEA,IAAI;IACJ,IAAI;IAEJ,KAAK,MAAM,OAAO,KAChB,IAAI;QACF,MAAM,WAAW,MAAM,SAAS;QAChC,eAAe;QAEf,6CAA6C;QAC7C,IAAI,OAAO,aAAa,eAAe,oBAAoB,YAAY,CAAC,SAAS,IAC/E;QAGF,OAAO;IACT,EAAE,OAAO,OAAO;QACd,UAAU;QACV,YAAY;IACd;IAGF,8DAA8D;IAC9D,IAAI,gBAAgB,MAClB,OAAO;IAET,qDAAqD;IACrD,IAAI,aAAa,MACf,MAAM;IAER,+BAA+B;IAC/B,MAAM,IAAI;AACZ;;","sources":["packages/core/rpc/src/index.ts","packages/core/rpc/src/fetchRpc.ts","packages/core/rpc/src/iterateUrls.ts"],"sourcesContent":["import 'isomorphic-fetch'\n\nexport * from './fetchRpc'\nexport * from './iterateUrls'\n","export type FetchRpcInitBody = {\n jsonrpc: '1.0' | '2.0' | string\n method: string\n params?: unknown\n id?: string | number | null\n}\n\n// eslint-disable-next-line no-undef\nexport type FetchRpcInit = Omit<RequestInit, 'body' | 'method'> & {\n method?: 'POST'\n body: FetchRpcInitBody | FetchRpcInitBody[]\n}\n\n// Need Extension type so we can extend fetchRPC with metrics tracking or caching\nexport type FetchRpc<Extension extends Record<string | number, unknown> | void = void> = (\n url: string,\n init: FetchRpcInit,\n extension: Extension,\n) => Promise<Response>\n\nexport const fetchRpc: FetchRpc = (url, init) =>\n fetch(url, {\n ...init,\n method: 'POST',\n headers: {\n ...init.headers,\n 'Content-Type': 'application/json',\n },\n body: JSON.stringify(init.body),\n })\n","export class ExhaustedIterationError extends Error {\n constructor(message?: string) {\n super(message ?? 'Iteration ended without success')\n }\n}\n\nexport const iterateUrls = async <T>(\n urls: [string, ...string[]],\n callback: (url: string) => T | Promise<T>,\n onError?: (error: unknown) => unknown,\n): Promise<T> => {\n let lastResponse: T | undefined\n let lastError: unknown | undefined\n\n for (const url of urls) {\n try {\n const response = await callback(url)\n lastResponse = response\n\n // We want to return first succeeded response\n if (typeof Response !== 'undefined' && response instanceof Response && !response.ok) {\n continue\n }\n\n return response\n } catch (error) {\n onError?.(error)\n lastError = error\n }\n }\n\n // If there are no succeeded responses, return last not thrown\n if (lastResponse != null) {\n return lastResponse\n }\n // If there are no responses at all, throw last error\n if (lastError != null) {\n throw lastError\n }\n // This should not be reachable\n throw new ExhaustedIterationError()\n}\n"],"names":[],"version":3,"file":"index.mjs.map"}
1
+ {"mappings":";;;;;;;;;ACoBO,MAAM,4CAAqB,CAAC,KAAK,OACtC,MAAM,KAAK;QACT,GAAG,IAAI;QACP,QAAQ;QACR,SAAS;YACP,GAAG,KAAK,OAAO;YACf,gBAAgB;QAClB;QACA,MAAM,KAAK,UAAU,KAAK;IAC5B;;;;;;;AC7BK,MAAM,kDAAgC;IAC3C,YAAY,OAAgB,CAAE;QAC5B,KAAK,CAAC,WAAW;IACnB;AACF;AASO,MAAM,4CAAc,OACzB,MACA,UACA;IAEA,IAAI;IACJ,IAAI;IAEJ,IAAK,IAAI,IAAI,GAAG,IAAI,KAAK,QAAQ,IAAK;QACpC,MAAM,MAAM,IAAI,CAAC,EAAE;QACnB,MAAM,UAAU,IAAI,CAAC,IAAI,EAAE;QAC3B,IAAI;YACF,MAAM,WAAW,MAAM,SAAS;YAChC,eAAe;YAEf,6CAA6C;YAC7C,IAAI,OAAO,aAAa,eAAe,oBAAoB,YAAY,CAAC,SAAS,IAAI;gBACnF,IAAI,SACF,QAAQ,IAAI,MAAM,CAAC,iBAAiB,EAAE,SAAS,OAAO,CAAC,GAAG;oBACxD,WAAW;6BACX;oBACA,SAAS,IAAI;8BACb;gBACF;gBAEF;YACF;YAEA,OAAO;QACT,EAAE,OAAO,OAAO;YACd,IAAI,SACF,QAAQ,OAAO;gBAAE,WAAW;yBAAK;gBAAS,SAAS,IAAI;YAAE;YAE3D,YAAY;QACd;IACF;IAEA,8DAA8D;IAC9D,IAAI,gBAAgB,MAClB,OAAO;IAET,qDAAqD;IACrD,IAAI,aAAa,MACf,MAAM;IAER,+BAA+B;IAC/B,MAAM,IAAI;AACZ;;","sources":["packages/core/rpc/src/index.ts","packages/core/rpc/src/fetchRpc.ts","packages/core/rpc/src/iterateUrls.ts"],"sourcesContent":["import 'isomorphic-fetch'\n\nexport * from './fetchRpc'\nexport * from './iterateUrls'\n","export type FetchRpcInitBody = {\n jsonrpc: '1.0' | '2.0' | string\n method: string\n params?: unknown\n id?: string | number | null\n}\n\n// eslint-disable-next-line no-undef\nexport type FetchRpcInit = Omit<RequestInit, 'body' | 'method'> & {\n method?: 'POST'\n body: FetchRpcInitBody | FetchRpcInitBody[]\n}\n\n// Need Extension type so we can extend fetchRPC with metrics tracking or caching\nexport type FetchRpc<Extension extends Record<string | number, unknown> | void = void> = (\n url: string,\n init: FetchRpcInit,\n extension: Extension,\n) => Promise<Response>\n\nexport const fetchRpc: FetchRpc = (url, init) =>\n fetch(url, {\n ...init,\n method: 'POST',\n headers: {\n ...init.headers,\n 'Content-Type': 'application/json',\n },\n body: JSON.stringify(init.body),\n })\n","export class ExhaustedIterationError extends Error {\n constructor(message?: string) {\n super(message ?? 'Iteration ended without success')\n }\n}\n\nexport type IterateUrlsErrorContext = {\n failedUrl: string\n nextUrl: string | undefined\n attempt: number\n response?: Response\n}\n\nexport const iterateUrls = async <T>(\n urls: [string, ...string[]],\n callback: (url: string) => T | Promise<T>,\n onError?: (error: unknown, context: IterateUrlsErrorContext) => unknown,\n): Promise<T> => {\n let lastResponse: T | undefined\n let lastError: unknown | undefined\n\n for (let i = 0; i < urls.length; i++) {\n const url = urls[i]\n const nextUrl = urls[i + 1]\n try {\n const response = await callback(url)\n lastResponse = response\n\n // We want to return first succeeded response\n if (typeof Response !== 'undefined' && response instanceof Response && !response.ok) {\n if (onError) {\n onError(new Error(`Non-ok response: ${response.status}`), {\n failedUrl: url,\n nextUrl,\n attempt: i + 1,\n response,\n })\n }\n continue\n }\n\n return response\n } catch (error) {\n if (onError) {\n onError(error, { failedUrl: url, nextUrl, attempt: i + 1 })\n }\n lastError = error\n }\n }\n\n // If there are no succeeded responses, return last not thrown\n if (lastResponse != null) {\n return lastResponse\n }\n // If there are no responses at all, throw last error\n if (lastError != null) {\n throw lastError\n }\n // This should not be reachable\n throw new ExhaustedIterationError()\n}\n"],"names":[],"version":3,"file":"index.mjs.map"}
package/package.json CHANGED
@@ -7,7 +7,7 @@
7
7
  "url": "https://github.com/lidofinance/warehouse/issues"
8
8
  },
9
9
  "license": "MIT",
10
- "version": "0.58.0",
10
+ "version": "0.59.0",
11
11
  "files": [
12
12
  "dist"
13
13
  ],
@@ -33,7 +33,7 @@
33
33
  "test": "jest"
34
34
  },
35
35
  "devDependencies": {
36
- "@lidofinance/config-prettier": "~0.58.0",
36
+ "@lidofinance/config-prettier": "~0.59.0",
37
37
  "@types/jest": "^29.2.4",
38
38
  "jest": "^29.3.1",
39
39
  "ts-jest": "^29.0.3",