import { assertMethodDecorator } from "../common/decorators.js"; import type { AsyncMethod } from "../common/types.js"; import { resolveCallable, sleep, } from "../common/utils.js"; const DEFAULT_DELAY = 1000; export type OnRetry = (error: any, retriesCount: number) => void; export type RetryInput = number | number[] | RetryInputConfig; export type RetryInputConfig = { delaysArray?: number[]; retries?: number; delay?: number; onRetry?: OnRetry | keyof This; }; export function getRetriesArray(input: RetryInput): number[] { if (Array.isArray(input)) { return input; } if (typeof input === "number" && Number.isInteger(input)) { return Array.from({ length: input }, () => { return DEFAULT_DELAY; }); } if (typeof input === "object" && input !== null) { const { retries, delaysArray, delay } = input; if (retries !== undefined && delaysArray !== undefined) { throw new Error("You can not provide both retries and delaysArray"); } if (delaysArray !== undefined) { return delaysArray; } return Array.from({ length: retries ?? 0 }, () => { return delay ?? DEFAULT_DELAY; }); } throw new Error("invalid input"); } function getOnRetry(input: RetryInput, context: This): OnRetry | undefined { if (typeof input === "object" && !Array.isArray(input) && input !== null) { const { onRetry } = input; if (onRetry !== undefined) { return resolveCallable(context, onRetry) as OnRetry; } } return undefined; } async function execRetry( thisArg: This, originalMethod: AsyncMethod, args: Args, retriesArray: number[], callsMadeSoFar: number, onRetry?: OnRetry, ): Promise { try { return await originalMethod.apply(thisArg, args); } catch (error) { if (callsMadeSoFar < retriesArray.length) { onRetry?.(error, callsMadeSoFar); await sleep(retriesArray[callsMadeSoFar] ?? DEFAULT_DELAY); return execRetry(thisArg, originalMethod, args, retriesArray, callsMadeSoFar + 1, onRetry); } throw error; } } export function createRetryMethod( originalMethod: AsyncMethod, retriesArray: number[], input: RetryInput, ): AsyncMethod { return function(this: This, ...args: Args): Promise { const onRetry = getOnRetry(input, this); return execRetry(this, originalMethod, args, retriesArray, 0, onRetry); }; } export function retry(input: RetryInput) { const retriesArray = getRetriesArray(input); return function( value: AsyncMethod, context: ClassMethodDecoratorContext>, ): AsyncMethod { assertMethodDecorator("retry", value, context); return createRetryMethod(value, retriesArray, input); }; }