@juspay/neurolink
Version:
Universal AI Development Platform with working MCP integration, multi-provider support, voice (TTS/STT/realtime), and professional CLI. 58+ external MCP servers discoverable, multimodal file processing, RAG pipelines. Build, test, and deploy AI applicatio
172 lines (171 loc) • 5.56 kB
JavaScript
/**
* Retry Utilities
*
* Provides retry logic with exponential backoff for resilient async operations.
*/
import { delay } from "./delay.js";
/**
* Default retry configuration.
*/
export const DEFAULT_RETRY_OPTIONS = {
maxAttempts: 3,
initialDelay: 1000,
maxDelay: 30000,
backoffMultiplier: 2,
};
/**
* Calculate exponential backoff delay with optional jitter.
*
* Uses the formula: min(baseDelay * 2^(attempt-1) + jitter, maxDelay)
*
* @param attempt - Current attempt number (1-based)
* @param baseDelayMs - Base delay in milliseconds
* @param maxDelayMs - Maximum delay cap in milliseconds
* @param addJitter - Whether to add random jitter (default: true)
* @returns Calculated delay in milliseconds
*
* @example
* ```typescript
* // Without jitter
* calculateBackoff(1, 1000, 30000, false); // 1000ms
* calculateBackoff(2, 1000, 30000, false); // 2000ms
* calculateBackoff(3, 1000, 30000, false); // 4000ms
*
* // With jitter (adds up to 10% random delay)
* calculateBackoff(3, 1000, 30000, true); // ~4000-4400ms
* ```
*/
export function calculateBackoff(attempt, baseDelayMs, maxDelayMs, addJitter = true) {
const exponentialDelay = baseDelayMs * 2 ** (attempt - 1);
if (addJitter) {
// Add up to 10% jitter to prevent thundering herd
const jitter = Math.random() * 0.1 * exponentialDelay;
return Math.min(exponentialDelay + jitter, maxDelayMs);
}
return Math.min(exponentialDelay, maxDelayMs);
}
/**
* Error thrown when all retry attempts are exhausted.
*/
export class RetryExhaustedError extends Error {
attempts;
lastError;
/**
* Creates a new RetryExhaustedError.
*
* @param message - Error message
* @param attempts - Total number of attempts made
* @param lastError - The last error that caused the final failure
*/
constructor(message, attempts, lastError) {
super(message);
this.attempts = attempts;
this.lastError = lastError;
this.name = "RetryExhaustedError";
if (Error.captureStackTrace) {
Error.captureStackTrace(this, RetryExhaustedError);
}
}
}
/**
* Retry an async function with exponential backoff.
*
* Executes the provided function and retries on failure according to
* the specified options. Uses exponential backoff between retries.
*
* @param fn - Async function to execute
* @param options - Retry configuration (merged with defaults)
* @returns Promise that resolves with the function result
* @throws The last error if all retries are exhausted
*
* @example
* ```typescript
* // Basic usage with defaults
* const data = await retry(() => fetchFromAPI());
* ```
*
* @example
* ```typescript
* // With custom options
* const data = await retry(
* () => fetchFromAPI(),
* {
* maxRetries: 5,
* baseDelayMs: 500,
* maxDelayMs: 10000,
* onRetry: (err, attempt, delay) => {
* console.log(`Retry ${attempt} after ${delay}ms: ${err.message}`);
* },
* shouldRetry: (err) => {
* // Only retry on network errors
* return err.name === 'NetworkError';
* }
* }
* );
* ```
*/
export async function retry(fn, options = {}) {
const config = {
...DEFAULT_RETRY_OPTIONS,
...options,
};
const { maxAttempts, initialDelay, maxDelay, backoffMultiplier, retryCondition = () => true, onRetry, } = config;
let lastError = new Error("Retry failed");
let currentDelay = initialDelay;
// Total attempts = initial attempt + retries
const totalAttempts = maxAttempts + 1;
for (let attempt = 1; attempt <= totalAttempts; attempt++) {
try {
return await fn();
}
catch (error) {
const err = error instanceof Error ? error : new Error(String(error));
lastError = err;
// Check if we've exhausted all retries
if (attempt >= totalAttempts) {
throw new RetryExhaustedError(`All ${totalAttempts} retry attempts exhausted`, totalAttempts, err);
}
// Check if we should retry this error
if (!retryCondition(err)) {
throw err;
}
// Calculate delay with exponential backoff (capped at maxDelay)
const delayMs = Math.min(currentDelay, maxDelay);
// Notify about retry
if (onRetry) {
onRetry(attempt, err);
}
// Wait before next attempt
await delay(delayMs);
// Increase delay for next iteration
currentDelay = currentDelay * backoffMultiplier;
}
}
// This should never be reached, but TypeScript needs it
throw new RetryExhaustedError(`All ${totalAttempts} retry attempts exhausted`, totalAttempts, lastError || new Error("Unknown retry failure"));
}
/**
* Create a retry wrapper with pre-configured options.
*
* Useful for creating reusable retry strategies.
*
* @param defaultOptions - Default retry options for the wrapper
* @returns A retry function with the specified defaults
*
* @example
* ```typescript
* const apiRetry = createRetry({
* maxRetries: 5,
* baseDelayMs: 100,
* onRetry: (err, attempt) => logger.warn(`API retry ${attempt}`)
* });
*
* // Use the configured retry
* const data = await apiRetry(() => fetchFromAPI());
* ```
*/
export function createRetry(defaultOptions) {
return (fn, overrideOptions) => {
return retry(fn, { ...defaultOptions, ...overrideOptions });
};
}