Files
devicemanager/ts/helpers/helpers.retry.ts

101 lines
2.7 KiB
TypeScript
Raw Normal View History

2026-01-09 07:14:39 +00:00
import * as plugins from '../plugins.js';
import type { IRetryOptions } from '../interfaces/index.js';
const defaultRetryOptions: Required<IRetryOptions> = {
maxRetries: 5,
baseDelay: 1000,
maxDelay: 16000,
multiplier: 2,
jitter: true,
};
/**
* Calculates the delay for a retry attempt using exponential backoff
*/
function calculateDelay(
attempt: number,
options: Required<IRetryOptions>
): number {
const delay = Math.min(
options.baseDelay * Math.pow(options.multiplier, attempt),
options.maxDelay
);
if (options.jitter) {
// Add random jitter of +/- 25%
const jitterRange = delay * 0.25;
return delay + (Math.random() * jitterRange * 2 - jitterRange);
}
return delay;
}
/**
* Executes a function with retry logic using exponential backoff
*
* @param fn - The async function to execute
* @param options - Retry configuration options
* @returns The result of the function
* @throws The last error if all retries fail
*/
export async function withRetry<T>(
fn: () => Promise<T>,
options?: IRetryOptions
): Promise<T> {
const opts: Required<IRetryOptions> = { ...defaultRetryOptions, ...options };
let lastError: Error | undefined;
for (let attempt = 0; attempt <= opts.maxRetries; attempt++) {
try {
return await fn();
} catch (error) {
lastError = error instanceof Error ? error : new Error(String(error));
if (attempt === opts.maxRetries) {
break;
}
const delay = calculateDelay(attempt, opts);
await plugins.smartdelay.delayFor(delay);
}
}
throw lastError ?? new Error('Retry failed with unknown error');
}
/**
* Creates a retryable version of an async function
*
* @param fn - The async function to wrap
* @param options - Retry configuration options
* @returns A wrapped function that retries on failure
*/
export function createRetryable<TArgs extends unknown[], TResult>(
fn: (...args: TArgs) => Promise<TResult>,
options?: IRetryOptions
): (...args: TArgs) => Promise<TResult> {
return (...args: TArgs) => withRetry(() => fn(...args), options);
}
/**
* Retry decorator for class methods
* Note: Use as a wrapper function since TC39 decorators have different semantics
*/
export function retryMethod<T extends Record<string, unknown>>(
target: T,
methodName: keyof T & string,
options?: IRetryOptions
): void {
const originalMethod = target[methodName];
if (typeof originalMethod !== 'function') {
throw new Error(`${methodName} is not a function`);
}
target[methodName] = createRetryable(
originalMethod.bind(target) as (...args: unknown[]) => Promise<unknown>,
options
) as T[typeof methodName];
}
export { defaultRetryOptions };