@nosana/node 1.1.35 → 1.1.36-rc

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@nosana/node",
3
- "version": "1.1.35",
3
+ "version": "1.1.36-rc",
4
4
  "description": "",
5
5
  "bin": {
6
6
  "nosana-node": "./dist/src/index.js"
@@ -1,6 +1,7 @@
1
1
  import createClient from 'openapi-fetch';
2
2
  import { getSDK } from '../../sdk/index.js';
3
3
  import { configs } from '../../configs/configs.js';
4
+ import { retryFetch } from '../retryFetch.js';
4
5
  import { maintenanceMiddleware } from '../maintenanceMiddleware.js';
5
6
  let instance = undefined;
6
7
  export const hostManagerClientSelector = () => {
@@ -9,6 +10,7 @@ export const hostManagerClientSelector = () => {
9
10
  const address = sdk.solana.provider.wallet.publicKey.toString();
10
11
  instance = createClient({
11
12
  baseUrl: configs().hostManagerBaseUrl,
13
+ fetch: retryFetch,
12
14
  headers: {
13
15
  'Content-Type': 'application/json',
14
16
  },
@@ -0,0 +1,7 @@
1
+ /**
2
+ * Custom fetch for openapi-fetch clients that retries network-level failures
3
+ * (errors thrown by fetch itself: DNS, connection resets, timeouts) with
4
+ * exponential backoff. HTTP error responses are returned untouched — those
5
+ * are handled by the callers and the maintenance middleware.
6
+ */
7
+ export declare const retryFetch: (input: Request) => Promise<Response>;
@@ -0,0 +1,29 @@
1
+ import chalk from 'chalk';
2
+ import { sleep } from '../utils/utils.js';
3
+ import { HostManagerUnreachableError, describeNetworkError, } from '../errors/HostManagerUnreachableError.js';
4
+ const MAX_ATTEMPTS = 6;
5
+ const BASE_DELAY_S = 1;
6
+ /**
7
+ * Custom fetch for openapi-fetch clients that retries network-level failures
8
+ * (errors thrown by fetch itself: DNS, connection resets, timeouts) with
9
+ * exponential backoff. HTTP error responses are returned untouched — those
10
+ * are handled by the callers and the maintenance middleware.
11
+ */
12
+ export const retryFetch = async (input) => {
13
+ let lastError;
14
+ for (let attempt = 1; attempt <= MAX_ATTEMPTS; attempt++) {
15
+ try {
16
+ // Fetch a clone so the body is still readable on the next attempt.
17
+ return await fetch(input.clone());
18
+ }
19
+ catch (error) {
20
+ lastError = error;
21
+ if (attempt < MAX_ATTEMPTS) {
22
+ const delayS = BASE_DELAY_S * 2 ** (attempt - 1);
23
+ console.warn(chalk.yellow(`Request to the host manager failed (${describeNetworkError(error)}), retrying in ${delayS}s (attempt ${attempt}/${MAX_ATTEMPTS})`));
24
+ await sleep(delayS);
25
+ }
26
+ }
27
+ }
28
+ throw new HostManagerUnreachableError(MAX_ATTEMPTS, lastError);
29
+ };
@@ -0,0 +1,10 @@
1
+ export declare class HostManagerUnreachableError extends Error {
2
+ readonly cause: unknown;
3
+ constructor(attempts: number, cause: unknown);
4
+ }
5
+ /**
6
+ * Undici reports every network failure as `TypeError: fetch failed` and hides
7
+ * the actual reason (ECONNRESET, EAI_AGAIN, ...) in the error's cause chain,
8
+ * so surface it in the message.
9
+ */
10
+ export declare function describeNetworkError(error: unknown): string;
@@ -0,0 +1,30 @@
1
+ export class HostManagerUnreachableError extends Error {
2
+ constructor(attempts, cause) {
3
+ super(`Could not reach the host manager after ${attempts} attempts: ${describeNetworkError(cause)}. Check your internet connection and firewall, then restart the node.`);
4
+ this.name = 'HostManagerUnreachableError';
5
+ this.cause = cause;
6
+ }
7
+ }
8
+ /**
9
+ * Undici reports every network failure as `TypeError: fetch failed` and hides
10
+ * the actual reason (ECONNRESET, EAI_AGAIN, ...) in the error's cause chain,
11
+ * so surface it in the message.
12
+ */
13
+ export function describeNetworkError(error) {
14
+ if (!(error instanceof Error))
15
+ return String(error);
16
+ const cause = error.cause;
17
+ if (cause instanceof AggregateError && cause.errors.length > 0) {
18
+ return `${error.message} (${cause.errors.map(describeSingle).join('; ')})`;
19
+ }
20
+ if (cause instanceof Error) {
21
+ return `${error.message} (${describeSingle(cause)})`;
22
+ }
23
+ return describeSingle(error);
24
+ }
25
+ function describeSingle(error) {
26
+ const code = error.code;
27
+ return code && !error.message.includes(code)
28
+ ? `${code}: ${error.message}`
29
+ : error.message;
30
+ }
@@ -57,7 +57,7 @@ export class Benchmark {
57
57
  const flowResults = this.repository.getFlow(this.benchmarkId);
58
58
  if (!flowResults)
59
59
  throw new Error(`Cannot find results for flow with id ${this.benchmarkId}`);
60
- this.repository.deleteflow(this.benchmarkId);
61
60
  await HostManager.submitBenchmarkResults(this.benchmarkId, flowResults.state);
61
+ this.repository.deleteflow(this.benchmarkId);
62
62
  }
63
63
  }
@@ -1,12 +1,12 @@
1
1
  {
2
2
  "name": "@nosana/node",
3
- "version": "1.1.35",
3
+ "version": "1.1.36-rc",
4
4
  "lockfileVersion": 3,
5
5
  "requires": true,
6
6
  "packages": {
7
7
  "": {
8
8
  "name": "@nosana/node",
9
- "version": "1.1.35",
9
+ "version": "1.1.36-rc",
10
10
  "license": "ISC",
11
11
  "dependencies": {
12
12
  "@coral-xyz/anchor": "^0.28.1-beta.1",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@nosana/node",
3
- "version": "1.1.35",
3
+ "version": "1.1.36-rc",
4
4
  "description": "",
5
5
  "bin": {
6
6
  "nosana-node": "./dist/src/index.js"