| 1 | // Alchemy modifications are licensed under Apache-2.0. |
| 2 | // This file includes third-party code; see /THIRD_PARTY_LICENSES.md. |
| 3 | import { ms } from "itty-time"; |
| 4 | import type { ResolvedStepConfig, StepState } from "../context.ts"; |
| 5 | import type { WorkflowSleepDuration } from "cloudflare:workers"; |
| 6 | |
| 7 | export class DelayFunctionError extends Error { |
| 8 | 	constructor(reason: string) { |
| 9 | 		super(reason); |
| 10 | 		this.name = "DelayFunctionError"; |
| 11 | 	} |
| 12 | } |
| 13 | |
| 14 | export function calcRetryDuration( |
| 15 | 	config: ResolvedStepConfig, |
| 16 | 	stepState: StepState, |
| 17 | 	delayValue: unknown |
| 18 | ): number { |
| 19 | 	const { attemptedCount: attemptCount } = stepState; |
| 20 | 	const { retries } = config; |
| 21 | |
| 22 | 	let base: number; |
| 23 | 	try { |
| 24 | 		base = ms(delayValue as WorkflowSleepDuration); |
| 25 | 	} catch { |
| 26 | 		throw new DelayFunctionError( |
| 27 | 			'returned an invalid delay value (expected a number of ms or a duration string like "30 seconds")' |
| 28 | 		); |
| 29 | 	} |
| 30 | 	if (!Number.isFinite(base) || base < 0) { |
| 31 | 		throw new DelayFunctionError( |
| 32 | 			'returned an invalid delay value (expected a number of ms or a duration string like "30 seconds")' |
| 33 | 		); |
| 34 | 	} |
| 35 | |
| 36 | 	switch (retries.backoff) { |
| 37 | 		case "exponential": { |
| 38 | 			return base * Math.pow(2, attemptCount - 1); |
| 39 | 		} |
| 40 | 		case "linear": { |
| 41 | 			return base * attemptCount; |
| 42 | 		} |
| 43 | 		case "constant": |
| 44 | 		default: { |
| 45 | 			return base; |
| 46 | 		} |
| 47 | 	} |
| 48 | } |