forked from AubaidFarrukh/smart-retry
-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathretryManager.ts
More file actions
133 lines (111 loc) · 3.16 KB
/
Copy pathretryManager.ts
File metadata and controls
133 lines (111 loc) · 3.16 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
/** @format */
import { RetryConfig, RetryResult, RetryableFunction, FailedRequest } from './types';
import { FileStore } from './fileStore';
import {
generateId,
calculateDelay,
defaultShouldRetry,
sleep,
getErrorMessage,
getStatusCode,
} from './utils';
export class RetryManager {
private config: Required<RetryConfig>;
private store: FileStore;
constructor(config: RetryConfig = {}, storePath?: string) {
this.config = {
maxRetries: config.maxRetries ?? 3,
delay: config.delay ?? 2000,
backoff: config.backoff ?? 'exponential',
shouldRetry: config.shouldRetry ?? defaultShouldRetry,
onRetry: config.onRetry ?? (() => {}),
};
this.store = new FileStore(storePath);
}
async execute<T>(fn: RetryableFunction<T>): Promise<RetryResult<T>> {
const startTime = Date.now();
let lastError: any;
let attempts = 0;
while (attempts < this.config.maxRetries) {
attempts++;
try {
const data = await fn();
const totalDuration = Date.now() - startTime;
return {
success: true,
data,
attempts,
totalDuration,
};
} catch (error) {
lastError = error;
if (!this.config.shouldRetry(error)) {
break;
}
if (attempts < this.config.maxRetries) {
const delay = calculateDelay(this.config.delay, attempts, this.config.backoff);
this.config.onRetry(attempts, error);
await sleep(delay);
}
}
}
const totalDuration = Date.now() - startTime;
await this.logFailure(fn, lastError, attempts, totalDuration);
return {
success: false,
error: lastError,
attempts,
totalDuration,
};
}
private async logFailure(
fn: RetryableFunction<any>,
error: any,
attempts: number,
totalDuration: number
): Promise<void> {
const failedRequest: FailedRequest = {
id: generateId(),
url: this.extractUrl(fn, error),
method: this.extractMethod(error),
headers: error.config?.headers,
body: error.config?.data,
error: getErrorMessage(error),
statusCode: getStatusCode(error),
attempts,
totalDuration,
timestamp: new Date().toISOString(),
};
await this.store.save(failedRequest);
}
private extractUrl(fn: RetryableFunction<any>, error: any): string {
if (error.config?.url) {
return error.config.url;
}
if (error.request?.path) {
return error.request.path;
}
return 'unknown';
}
private extractMethod(error: any): string {
return error.config?.method?.toUpperCase() || 'GET';
}
async getFailedRequests(): Promise<FailedRequest[]> {
return this.store.loadAll();
}
async getFailedRequest(id: string): Promise<FailedRequest | undefined> {
return this.store.findById(id);
}
async clearFailedRequests(): Promise<void> {
await this.store.clear();
}
async removeFailedRequest(id: string): Promise<boolean> {
return this.store.remove(id);
}
async getFailedRequestCount(): Promise<number> {
return this.store.count();
}
getLogFilePath(): string {
return this.store.getFilePath();
}
}