Review job retry classification

src/retry-job.tsTypeScript
@@ -0,0 +1,42 @@
1+export class HttpError extends Error {
2+ constructor(readonly status: number, message: string) {
3+ super(message);
4+ this.name = "HttpError";
5+ }
6+}
7+
8+export interface RetryJobDependencies {
9+ deliver(): Promise<void>;
10+ sleep(milliseconds: number): Promise<void>;
11+ random(): number;
12+ markFailed(error: unknown): Promise<void>;
13+}
14+
15+export interface RetryPolicy {
16+ maxAttempts: number;
17+ baseDelayMs: number;
18+}
19+
20+export async function runRetryableJob(
21+ policy: RetryPolicy,
22+ dependencies: RetryJobDependencies,
23+): Promise<void> {
24+ for (let attempt = 0; attempt <= policy.maxAttempts; attempt += 1) {
25+ try {
26+ await dependencies.deliver();
27+ return;
28+ } catch (error) {
29+ if (error instanceof HttpError && error.status >= 400 && error.status < 500) {
30+ await dependencies.markFailed(error);
31+ return;
32+ }
33+
34+ if (attempt === policy.maxAttempts) {
35+ await dependencies.markFailed(error);
36+ return;
37+ }
38+
39+ await dependencies.sleep(policy.baseDelayMs * 2 ** attempt);
40+ }
41+ }
42+}