@naman_deep_singh/communication-core 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +345 -0
- package/dist/cjs/abstract/BaseCircuitBreaker.js +253 -0
- package/dist/cjs/abstract/BaseClient.js +298 -0
- package/dist/cjs/abstract/BaseCompressionManager.js +377 -0
- package/dist/cjs/abstract/BaseConnectionPool.js +543 -0
- package/dist/cjs/abstract/BaseInterceptor.js +235 -0
- package/dist/cjs/abstract/BaseLoadBalancer.js +269 -0
- package/dist/cjs/abstract/BaseProtocol.js +269 -0
- package/dist/cjs/abstract/BaseRetryStrategy.js +255 -0
- package/dist/cjs/abstract/BaseSerializer.js +341 -0
- package/dist/cjs/abstract/BaseServiceDiscoverer.js +254 -0
- package/dist/cjs/abstract/BaseTimeoutManager.js +295 -0
- package/dist/cjs/abstract/index.js +25 -0
- package/dist/cjs/errors/CircuitBreakerError.js +16 -0
- package/dist/cjs/errors/CommunicationError.js +15 -0
- package/dist/cjs/errors/ConnectionError.js +15 -0
- package/dist/cjs/errors/DiscoveryError.js +15 -0
- package/dist/cjs/errors/LoadBalancerError.js +15 -0
- package/dist/cjs/errors/ProtocolError.js +15 -0
- package/dist/cjs/errors/RetryError.js +16 -0
- package/dist/cjs/errors/SerializationError.js +15 -0
- package/dist/cjs/errors/ServiceUnavailableError.js +15 -0
- package/dist/cjs/errors/TimeoutError.js +16 -0
- package/dist/cjs/errors/communicationErrorCodes.js +35 -0
- package/dist/cjs/errors/index.js +31 -0
- package/dist/cjs/index.js +38 -0
- package/dist/cjs/interfaces/CircuitBreaker.interface.js +6 -0
- package/dist/cjs/interfaces/Client.interface.js +6 -0
- package/dist/cjs/interfaces/Compression.interface.js +6 -0
- package/dist/cjs/interfaces/ConnectionPool.interface.js +6 -0
- package/dist/cjs/interfaces/Interceptor.interface.js +6 -0
- package/dist/cjs/interfaces/LoadBalancer.interface.js +2 -0
- package/dist/cjs/interfaces/Protocol.interface.js +6 -0
- package/dist/cjs/interfaces/RetryStrategy.interface.js +6 -0
- package/dist/cjs/interfaces/Serializer.interface.js +2 -0
- package/dist/cjs/interfaces/ServiceDiscovery.interface.js +6 -0
- package/dist/cjs/interfaces/Timeout.interface.js +6 -0
- package/dist/cjs/interfaces/index.js +6 -0
- package/dist/cjs/types/config.js +6 -0
- package/dist/cjs/types/events.js +6 -0
- package/dist/cjs/types/index.js +6 -0
- package/dist/cjs/types/request.js +6 -0
- package/dist/cjs/types/response.js +6 -0
- package/dist/cjs/types/service.js +6 -0
- package/dist/cjs/utils.js +200 -0
- package/dist/esm/abstract/BaseCircuitBreaker.js +249 -0
- package/dist/esm/abstract/BaseClient.js +294 -0
- package/dist/esm/abstract/BaseCompressionManager.js +373 -0
- package/dist/esm/abstract/BaseConnectionPool.js +539 -0
- package/dist/esm/abstract/BaseInterceptor.js +231 -0
- package/dist/esm/abstract/BaseLoadBalancer.js +265 -0
- package/dist/esm/abstract/BaseProtocol.js +265 -0
- package/dist/esm/abstract/BaseRetryStrategy.js +251 -0
- package/dist/esm/abstract/BaseSerializer.js +337 -0
- package/dist/esm/abstract/BaseServiceDiscoverer.js +250 -0
- package/dist/esm/abstract/BaseTimeoutManager.js +291 -0
- package/dist/esm/abstract/index.js +11 -0
- package/dist/esm/errors/CircuitBreakerError.js +12 -0
- package/dist/esm/errors/CommunicationError.js +11 -0
- package/dist/esm/errors/ConnectionError.js +11 -0
- package/dist/esm/errors/DiscoveryError.js +11 -0
- package/dist/esm/errors/LoadBalancerError.js +11 -0
- package/dist/esm/errors/ProtocolError.js +11 -0
- package/dist/esm/errors/RetryError.js +12 -0
- package/dist/esm/errors/SerializationError.js +11 -0
- package/dist/esm/errors/ServiceUnavailableError.js +11 -0
- package/dist/esm/errors/TimeoutError.js +12 -0
- package/dist/esm/errors/communicationErrorCodes.js +32 -0
- package/dist/esm/errors/index.js +17 -0
- package/dist/esm/index.js +18 -0
- package/dist/esm/interfaces/CircuitBreaker.interface.js +5 -0
- package/dist/esm/interfaces/Client.interface.js +5 -0
- package/dist/esm/interfaces/Compression.interface.js +5 -0
- package/dist/esm/interfaces/ConnectionPool.interface.js +5 -0
- package/dist/esm/interfaces/Interceptor.interface.js +5 -0
- package/dist/esm/interfaces/LoadBalancer.interface.js +1 -0
- package/dist/esm/interfaces/Protocol.interface.js +5 -0
- package/dist/esm/interfaces/RetryStrategy.interface.js +5 -0
- package/dist/esm/interfaces/Serializer.interface.js +1 -0
- package/dist/esm/interfaces/ServiceDiscovery.interface.js +5 -0
- package/dist/esm/interfaces/Timeout.interface.js +5 -0
- package/dist/esm/interfaces/index.js +5 -0
- package/dist/esm/types/config.js +5 -0
- package/dist/esm/types/events.js +5 -0
- package/dist/esm/types/index.js +5 -0
- package/dist/esm/types/request.js +5 -0
- package/dist/esm/types/response.js +5 -0
- package/dist/esm/types/service.js +5 -0
- package/dist/esm/utils.js +193 -0
- package/dist/types/abstract/BaseCircuitBreaker.d.ts +167 -0
- package/dist/types/abstract/BaseClient.d.ts +197 -0
- package/dist/types/abstract/BaseCompressionManager.d.ts +180 -0
- package/dist/types/abstract/BaseConnectionPool.d.ts +210 -0
- package/dist/types/abstract/BaseInterceptor.d.ts +150 -0
- package/dist/types/abstract/BaseLoadBalancer.d.ts +167 -0
- package/dist/types/abstract/BaseProtocol.d.ts +163 -0
- package/dist/types/abstract/BaseRetryStrategy.d.ts +130 -0
- package/dist/types/abstract/BaseSerializer.d.ts +181 -0
- package/dist/types/abstract/BaseServiceDiscoverer.d.ts +161 -0
- package/dist/types/abstract/BaseTimeoutManager.d.ts +145 -0
- package/dist/types/abstract/index.d.ts +11 -0
- package/dist/types/errors/CircuitBreakerError.d.ts +8 -0
- package/dist/types/errors/CommunicationError.d.ts +10 -0
- package/dist/types/errors/ConnectionError.d.ts +9 -0
- package/dist/types/errors/DiscoveryError.d.ts +9 -0
- package/dist/types/errors/LoadBalancerError.d.ts +9 -0
- package/dist/types/errors/ProtocolError.d.ts +9 -0
- package/dist/types/errors/RetryError.d.ts +11 -0
- package/dist/types/errors/SerializationError.d.ts +9 -0
- package/dist/types/errors/ServiceUnavailableError.d.ts +12 -0
- package/dist/types/errors/TimeoutError.d.ts +11 -0
- package/dist/types/errors/communicationErrorCodes.d.ts +27 -0
- package/dist/types/errors/index.d.ts +11 -0
- package/dist/types/index.d.ts +13 -0
- package/dist/types/interfaces/CircuitBreaker.interface.d.ts +150 -0
- package/dist/types/interfaces/Client.interface.d.ts +153 -0
- package/dist/types/interfaces/Compression.interface.d.ts +190 -0
- package/dist/types/interfaces/ConnectionPool.interface.d.ts +191 -0
- package/dist/types/interfaces/Interceptor.interface.d.ts +220 -0
- package/dist/types/interfaces/LoadBalancer.interface.d.ts +153 -0
- package/dist/types/interfaces/Protocol.interface.d.ts +117 -0
- package/dist/types/interfaces/RetryStrategy.interface.d.ts +160 -0
- package/dist/types/interfaces/Serializer.interface.d.ts +176 -0
- package/dist/types/interfaces/ServiceDiscovery.interface.d.ts +189 -0
- package/dist/types/interfaces/Timeout.interface.d.ts +135 -0
- package/dist/types/interfaces/index.d.ts +15 -0
- package/dist/types/types/config.d.ts +540 -0
- package/dist/types/types/events.d.ts +204 -0
- package/dist/types/types/index.d.ts +9 -0
- package/dist/types/types/request.d.ts +143 -0
- package/dist/types/types/response.d.ts +155 -0
- package/dist/types/types/service.d.ts +279 -0
- package/dist/types/utils.d.ts +179 -0
- package/package.json +88 -0
|
@@ -0,0 +1,265 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Abstract base protocol implementation
|
|
3
|
+
* @packageDocumentation
|
|
4
|
+
*/
|
|
5
|
+
import { CommunicationError } from '../errors/CommunicationError.js';
|
|
6
|
+
/**
|
|
7
|
+
* Abstract base protocol implementation
|
|
8
|
+
* Provides common functionality for all protocol implementations
|
|
9
|
+
*/
|
|
10
|
+
export class BaseProtocol {
|
|
11
|
+
/**
|
|
12
|
+
* Create a new base protocol instance
|
|
13
|
+
* @param name Protocol name
|
|
14
|
+
* @param config Protocol configuration
|
|
15
|
+
*/
|
|
16
|
+
constructor(name, config) {
|
|
17
|
+
/** Protocol interceptors */
|
|
18
|
+
this.interceptors = [];
|
|
19
|
+
/** Protocol metrics */
|
|
20
|
+
this.metrics = {};
|
|
21
|
+
/** Whether protocol is connected (for stateful protocols) */
|
|
22
|
+
this.connected = false;
|
|
23
|
+
/** Protocol start time */
|
|
24
|
+
this.startTime = Date.now();
|
|
25
|
+
/** Total requests sent */
|
|
26
|
+
this.totalRequests = 0;
|
|
27
|
+
/** Total successful requests */
|
|
28
|
+
this.successfulRequests = 0;
|
|
29
|
+
/** Total failed requests */
|
|
30
|
+
this.failedRequests = 0;
|
|
31
|
+
this.name = name;
|
|
32
|
+
this.config = { ...config };
|
|
33
|
+
this.version = config.options?.version || '1.0.0';
|
|
34
|
+
}
|
|
35
|
+
/**
|
|
36
|
+
* Configure the protocol with new settings
|
|
37
|
+
* @param config Configuration object
|
|
38
|
+
*/
|
|
39
|
+
configure(config) {
|
|
40
|
+
this.config = { ...this.config, ...config };
|
|
41
|
+
this.onConfigure(config);
|
|
42
|
+
}
|
|
43
|
+
/**
|
|
44
|
+
* Hook for protocol configuration changes
|
|
45
|
+
* @param newConfig New configuration
|
|
46
|
+
*/
|
|
47
|
+
onConfigure(newConfig) {
|
|
48
|
+
// Can be overridden by subclasses
|
|
49
|
+
}
|
|
50
|
+
/**
|
|
51
|
+
* Connect to the service (for stateful protocols)
|
|
52
|
+
* @returns Promise that resolves when connected
|
|
53
|
+
*/
|
|
54
|
+
async connect() {
|
|
55
|
+
this.connected = true;
|
|
56
|
+
await this.onConnect?.();
|
|
57
|
+
}
|
|
58
|
+
/**
|
|
59
|
+
* Hook for connection logic
|
|
60
|
+
*/
|
|
61
|
+
async onConnect() {
|
|
62
|
+
// Can be overridden by subclasses
|
|
63
|
+
}
|
|
64
|
+
/**
|
|
65
|
+
* Disconnect from the service (for stateful protocols)
|
|
66
|
+
* @returns Promise that resolves when disconnected
|
|
67
|
+
*/
|
|
68
|
+
async disconnect() {
|
|
69
|
+
this.connected = false;
|
|
70
|
+
await this.onDisconnect?.();
|
|
71
|
+
}
|
|
72
|
+
/**
|
|
73
|
+
* Hook for disconnection logic
|
|
74
|
+
*/
|
|
75
|
+
async onDisconnect() {
|
|
76
|
+
// Can be overridden by subclasses
|
|
77
|
+
}
|
|
78
|
+
/**
|
|
79
|
+
* Check if the protocol is currently connected
|
|
80
|
+
* @returns True if connected, false otherwise
|
|
81
|
+
*/
|
|
82
|
+
isConnected() {
|
|
83
|
+
return this.connected;
|
|
84
|
+
}
|
|
85
|
+
/**
|
|
86
|
+
* Add an interceptor to the protocol
|
|
87
|
+
* @param interceptor Interceptor to add
|
|
88
|
+
*/
|
|
89
|
+
addInterceptor(interceptor) {
|
|
90
|
+
this.interceptors.push(interceptor);
|
|
91
|
+
interceptor.initialize?.({ protocol: this.name });
|
|
92
|
+
this.onInterceptorAdded(interceptor);
|
|
93
|
+
}
|
|
94
|
+
/**
|
|
95
|
+
* Hook for interceptor addition
|
|
96
|
+
* @param interceptor Added interceptor
|
|
97
|
+
*/
|
|
98
|
+
onInterceptorAdded(interceptor) {
|
|
99
|
+
// Can be overridden by subclasses
|
|
100
|
+
}
|
|
101
|
+
/**
|
|
102
|
+
* Remove an interceptor from the protocol
|
|
103
|
+
* @param interceptorId Interceptor identifier
|
|
104
|
+
*/
|
|
105
|
+
removeInterceptor(interceptorId) {
|
|
106
|
+
const index = this.interceptors.findIndex(i => i.name === interceptorId);
|
|
107
|
+
if (index !== -1) {
|
|
108
|
+
const interceptor = this.interceptors[index];
|
|
109
|
+
interceptor.cleanup?.();
|
|
110
|
+
this.interceptors.splice(index, 1);
|
|
111
|
+
this.onInterceptorRemoved(interceptorId);
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
/**
|
|
115
|
+
* Hook for interceptor removal
|
|
116
|
+
* @param interceptorId Removed interceptor ID
|
|
117
|
+
*/
|
|
118
|
+
onInterceptorRemoved(interceptorId) {
|
|
119
|
+
// Can be overridden by subclasses
|
|
120
|
+
}
|
|
121
|
+
/**
|
|
122
|
+
* Get all interceptors
|
|
123
|
+
*/
|
|
124
|
+
getInterceptors() {
|
|
125
|
+
return [...this.interceptors];
|
|
126
|
+
}
|
|
127
|
+
/**
|
|
128
|
+
* Execute request interceptors
|
|
129
|
+
* @param request Original request
|
|
130
|
+
* @param context Request context
|
|
131
|
+
* @returns Modified request
|
|
132
|
+
*/
|
|
133
|
+
async executeRequestInterceptors(request, context) {
|
|
134
|
+
let modifiedRequest = request;
|
|
135
|
+
for (const interceptor of this.interceptors) {
|
|
136
|
+
if (interceptor.enabled && interceptor.onRequest) {
|
|
137
|
+
const result = await interceptor.onRequest(modifiedRequest, context);
|
|
138
|
+
if (result !== undefined) {
|
|
139
|
+
modifiedRequest = result;
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
return modifiedRequest;
|
|
144
|
+
}
|
|
145
|
+
/**
|
|
146
|
+
* Execute response interceptors
|
|
147
|
+
* @param response Original response
|
|
148
|
+
* @param context Request context
|
|
149
|
+
* @returns Modified response
|
|
150
|
+
*/
|
|
151
|
+
async executeResponseInterceptors(response, context) {
|
|
152
|
+
let modifiedResponse = response;
|
|
153
|
+
for (const interceptor of this.interceptors) {
|
|
154
|
+
if (interceptor.enabled && interceptor.onResponse) {
|
|
155
|
+
const result = await interceptor.onResponse(modifiedResponse, context);
|
|
156
|
+
if (result !== undefined) {
|
|
157
|
+
modifiedResponse = result;
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
return modifiedResponse;
|
|
162
|
+
}
|
|
163
|
+
/**
|
|
164
|
+
* Execute error interceptors
|
|
165
|
+
* @param error Original error
|
|
166
|
+
* @param context Request context
|
|
167
|
+
* @returns Modified error or response
|
|
168
|
+
*/
|
|
169
|
+
async executeErrorInterceptors(error, context) {
|
|
170
|
+
let result = error;
|
|
171
|
+
for (const interceptor of this.interceptors) {
|
|
172
|
+
if (interceptor.enabled && interceptor.onError) {
|
|
173
|
+
const interceptorResult = await interceptor.onError(result instanceof CommunicationError ? result : error, context);
|
|
174
|
+
if (interceptorResult !== undefined) {
|
|
175
|
+
result = interceptorResult;
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
return result;
|
|
180
|
+
}
|
|
181
|
+
/**
|
|
182
|
+
* Update request metrics
|
|
183
|
+
* @param success Whether request was successful
|
|
184
|
+
* @param duration Request duration in milliseconds
|
|
185
|
+
*/
|
|
186
|
+
updateMetrics(success, duration) {
|
|
187
|
+
this.totalRequests++;
|
|
188
|
+
if (success) {
|
|
189
|
+
this.successfulRequests++;
|
|
190
|
+
}
|
|
191
|
+
else {
|
|
192
|
+
this.failedRequests++;
|
|
193
|
+
}
|
|
194
|
+
this.metrics = {
|
|
195
|
+
...this.metrics,
|
|
196
|
+
totalRequests: this.totalRequests,
|
|
197
|
+
successfulRequests: this.successfulRequests,
|
|
198
|
+
failedRequests: this.failedRequests,
|
|
199
|
+
successRate: this.totalRequests > 0 ? this.successfulRequests / this.totalRequests : 0,
|
|
200
|
+
averageResponseTime: this.calculateAverageResponseTime(duration),
|
|
201
|
+
uptime: Date.now() - this.startTime,
|
|
202
|
+
};
|
|
203
|
+
}
|
|
204
|
+
/**
|
|
205
|
+
* Calculate average response time
|
|
206
|
+
* @param newDuration New request duration
|
|
207
|
+
* @returns Average response time
|
|
208
|
+
*/
|
|
209
|
+
calculateAverageResponseTime(newDuration) {
|
|
210
|
+
const currentAverage = this.metrics.averageResponseTime || 0;
|
|
211
|
+
const totalDuration = currentAverage * (this.totalRequests - 1) + newDuration;
|
|
212
|
+
return totalDuration / this.totalRequests;
|
|
213
|
+
}
|
|
214
|
+
/**
|
|
215
|
+
* Get protocol metrics
|
|
216
|
+
*/
|
|
217
|
+
getMetrics() {
|
|
218
|
+
return { ...this.metrics };
|
|
219
|
+
}
|
|
220
|
+
/**
|
|
221
|
+
* Reset protocol metrics
|
|
222
|
+
*/
|
|
223
|
+
resetMetrics() {
|
|
224
|
+
this.totalRequests = 0;
|
|
225
|
+
this.successfulRequests = 0;
|
|
226
|
+
this.failedRequests = 0;
|
|
227
|
+
this.metrics = {
|
|
228
|
+
uptime: Date.now() - this.startTime,
|
|
229
|
+
};
|
|
230
|
+
}
|
|
231
|
+
/**
|
|
232
|
+
* Health check for the protocol
|
|
233
|
+
* @returns Promise resolving to health status
|
|
234
|
+
*/
|
|
235
|
+
async healthCheck() {
|
|
236
|
+
const healthy = this.connected !== false;
|
|
237
|
+
return {
|
|
238
|
+
healthy,
|
|
239
|
+
message: healthy ? 'Protocol is healthy' : 'Protocol is not connected',
|
|
240
|
+
details: {
|
|
241
|
+
name: this.name,
|
|
242
|
+
version: this.version,
|
|
243
|
+
connected: this.connected,
|
|
244
|
+
totalRequests: this.totalRequests,
|
|
245
|
+
successRate: this.metrics.successRate,
|
|
246
|
+
uptime: this.metrics.uptime,
|
|
247
|
+
},
|
|
248
|
+
};
|
|
249
|
+
}
|
|
250
|
+
/**
|
|
251
|
+
* Create request context
|
|
252
|
+
* @param request Request object
|
|
253
|
+
* @param attempt Attempt number
|
|
254
|
+
* @returns Request context
|
|
255
|
+
*/
|
|
256
|
+
createRequestContext(request, attempt = 1) {
|
|
257
|
+
return {
|
|
258
|
+
protocol: this.name,
|
|
259
|
+
requestId: request.id || `req_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`,
|
|
260
|
+
attempt,
|
|
261
|
+
startTime: Date.now(),
|
|
262
|
+
isRetry: attempt > 1,
|
|
263
|
+
};
|
|
264
|
+
}
|
|
265
|
+
}
|
|
@@ -0,0 +1,251 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Abstract base retry strategy implementation
|
|
3
|
+
* @packageDocumentation
|
|
4
|
+
*/
|
|
5
|
+
/**
|
|
6
|
+
* Abstract base retry strategy implementation
|
|
7
|
+
* Provides common functionality for all retry strategy implementations
|
|
8
|
+
*/
|
|
9
|
+
export class BaseRetryStrategy {
|
|
10
|
+
/**
|
|
11
|
+
* Create a new base retry strategy instance
|
|
12
|
+
* @param name Strategy name
|
|
13
|
+
* @param config Strategy configuration
|
|
14
|
+
*/
|
|
15
|
+
constructor(name, config) {
|
|
16
|
+
/** Total retry attempts made */
|
|
17
|
+
this.totalAttempts = 0;
|
|
18
|
+
/** Successful retries */
|
|
19
|
+
this.successfulRetries = 0;
|
|
20
|
+
/** Failed retries */
|
|
21
|
+
this.failedRetries = 0;
|
|
22
|
+
/** Retry statistics */
|
|
23
|
+
this.stats = {
|
|
24
|
+
totalExecutions: 0,
|
|
25
|
+
totalAttempts: 0,
|
|
26
|
+
successfulRetries: 0,
|
|
27
|
+
failedRetries: 0,
|
|
28
|
+
successRate: 0,
|
|
29
|
+
averageAttempts: 0,
|
|
30
|
+
averageDelay: 0,
|
|
31
|
+
totalTimeSpent: 0,
|
|
32
|
+
};
|
|
33
|
+
this.name = name;
|
|
34
|
+
this.config = { ...config };
|
|
35
|
+
this.initialize();
|
|
36
|
+
}
|
|
37
|
+
/**
|
|
38
|
+
* Initialize retry strategy
|
|
39
|
+
*/
|
|
40
|
+
initialize() {
|
|
41
|
+
// Can be overridden by subclasses
|
|
42
|
+
}
|
|
43
|
+
/**
|
|
44
|
+
* Determine if a retry should be attempted
|
|
45
|
+
* @param error Error that occurred
|
|
46
|
+
* @param context Current retry context
|
|
47
|
+
* @returns Retry decision
|
|
48
|
+
*/
|
|
49
|
+
shouldRetry(error, context) {
|
|
50
|
+
// Check max attempts
|
|
51
|
+
if (context.attempt >= context.maxAttempts) {
|
|
52
|
+
return {
|
|
53
|
+
shouldRetry: false,
|
|
54
|
+
reason: 'Max attempts reached',
|
|
55
|
+
};
|
|
56
|
+
}
|
|
57
|
+
// Check retry on status codes (for HTTP errors)
|
|
58
|
+
if (error.statusCode && this.config.retryOnStatus?.includes(error.statusCode)) {
|
|
59
|
+
return {
|
|
60
|
+
shouldRetry: true,
|
|
61
|
+
delay: this.calculateDelay(context.attempt, context),
|
|
62
|
+
reason: `Status code ${error.statusCode} is retryable`,
|
|
63
|
+
};
|
|
64
|
+
}
|
|
65
|
+
// Check retry on error types
|
|
66
|
+
if (this.config.retryOnErrors?.includes(error.code)) {
|
|
67
|
+
return {
|
|
68
|
+
shouldRetry: true,
|
|
69
|
+
delay: this.calculateDelay(context.attempt, context),
|
|
70
|
+
reason: `Error code ${error.code} is retryable`,
|
|
71
|
+
};
|
|
72
|
+
}
|
|
73
|
+
// Check if error is retryable based on type
|
|
74
|
+
if (this.isRetryableError(error)) {
|
|
75
|
+
return {
|
|
76
|
+
shouldRetry: true,
|
|
77
|
+
delay: this.calculateDelay(context.attempt, context),
|
|
78
|
+
reason: 'Error is retryable',
|
|
79
|
+
};
|
|
80
|
+
}
|
|
81
|
+
return {
|
|
82
|
+
shouldRetry: false,
|
|
83
|
+
reason: 'Error is not retryable',
|
|
84
|
+
};
|
|
85
|
+
}
|
|
86
|
+
/**
|
|
87
|
+
* Check if an error is retryable
|
|
88
|
+
* @param error Error to check
|
|
89
|
+
* @returns True if error is retryable
|
|
90
|
+
*/
|
|
91
|
+
isRetryableError(error) {
|
|
92
|
+
// Default implementation: retry on network errors, timeouts, and 5xx errors
|
|
93
|
+
const networkErrors = ['ECONNREFUSED', 'ECONNRESET', 'ETIMEDOUT', 'ENOTFOUND'];
|
|
94
|
+
const timeoutErrors = ['TIMEOUT', 'TIMEOUT_EXCEEDED'];
|
|
95
|
+
return (networkErrors.some(code => error.code?.includes(code)) ||
|
|
96
|
+
timeoutErrors.some(code => error.code?.includes(code)) ||
|
|
97
|
+
(error?.statusCode >= 500 && error?.statusCode < 600));
|
|
98
|
+
}
|
|
99
|
+
/**
|
|
100
|
+
* Calculate delay for next retry attempt
|
|
101
|
+
* @param attempt Current attempt number
|
|
102
|
+
* @param context Current retry context
|
|
103
|
+
* @returns Delay in milliseconds
|
|
104
|
+
*/
|
|
105
|
+
calculateDelay(attempt, context) {
|
|
106
|
+
const backoff = this.config?.backoff || {};
|
|
107
|
+
const strategy = this.config.backoffStrategy || 'exponential';
|
|
108
|
+
switch (strategy) {
|
|
109
|
+
case 'fixed':
|
|
110
|
+
return backoff.initialDelay || 1000;
|
|
111
|
+
case 'exponential':
|
|
112
|
+
const baseDelay = backoff.initialDelay || 100;
|
|
113
|
+
const multiplier = backoff.multiplier || 2;
|
|
114
|
+
return Math.min(baseDelay * Math.pow(multiplier, attempt - 1), backoff.maxDelay || 30000);
|
|
115
|
+
case 'linear':
|
|
116
|
+
const linearDelay = backoff.initialDelay || 100;
|
|
117
|
+
const increment = backoff.multiplier || 1000;
|
|
118
|
+
return Math.min(linearDelay + (increment * (attempt - 1)), backoff.maxDelay || 30000);
|
|
119
|
+
case 'jitter':
|
|
120
|
+
const jitterDelay = backoff.initialDelay || 100;
|
|
121
|
+
const jitterMultiplier = backoff.multiplier || 2;
|
|
122
|
+
const base = jitterDelay * Math.pow(jitterMultiplier, attempt - 1);
|
|
123
|
+
const jitter = backoff.jitter || 0.3;
|
|
124
|
+
const jitterValue = base * jitter * Math.random();
|
|
125
|
+
return Math.min(base + jitterValue, backoff.maxDelay || 30000);
|
|
126
|
+
case 'fibonacci':
|
|
127
|
+
const fibDelay = backoff.initialDelay || 100;
|
|
128
|
+
let a = 0, b = 1;
|
|
129
|
+
for (let i = 0; i < attempt; i++) {
|
|
130
|
+
[a, b] = [b, a + b];
|
|
131
|
+
}
|
|
132
|
+
return Math.min(fibDelay * a, backoff.maxDelay || 30000);
|
|
133
|
+
default:
|
|
134
|
+
// Custom strategy or fallback
|
|
135
|
+
if (backoff.custom) {
|
|
136
|
+
return backoff.custom(attempt);
|
|
137
|
+
}
|
|
138
|
+
return backoff.initialDelay || 1000;
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
/**
|
|
142
|
+
* Create retry context
|
|
143
|
+
* @param initialContext Initial context values
|
|
144
|
+
* @returns Complete retry context
|
|
145
|
+
*/
|
|
146
|
+
createRetryContext(initialContext) {
|
|
147
|
+
const defaultContext = {
|
|
148
|
+
attempt: 1,
|
|
149
|
+
maxAttempts: this.config.maxAttempts || 3,
|
|
150
|
+
lastError: undefined,
|
|
151
|
+
startTime: Date.now(),
|
|
152
|
+
elapsedTime: 0,
|
|
153
|
+
data: new Map(),
|
|
154
|
+
isRetry: false,
|
|
155
|
+
};
|
|
156
|
+
return {
|
|
157
|
+
...defaultContext,
|
|
158
|
+
...initialContext,
|
|
159
|
+
};
|
|
160
|
+
}
|
|
161
|
+
/**
|
|
162
|
+
* Update retry context after attempt
|
|
163
|
+
* @param context Current context
|
|
164
|
+
* @param error Error from attempt (if any)
|
|
165
|
+
* @returns Updated context
|
|
166
|
+
*/
|
|
167
|
+
updateRetryContext(context, error) {
|
|
168
|
+
return {
|
|
169
|
+
...context,
|
|
170
|
+
attempt: context.attempt + 1,
|
|
171
|
+
lastError: error,
|
|
172
|
+
elapsedTime: Date.now() - context.startTime,
|
|
173
|
+
isRetry: context.attempt > 1,
|
|
174
|
+
};
|
|
175
|
+
}
|
|
176
|
+
/**
|
|
177
|
+
* Update retry statistics
|
|
178
|
+
* @param success Whether retry was successful
|
|
179
|
+
* @param attempts Number of attempts
|
|
180
|
+
* @param totalDelay Total delay time
|
|
181
|
+
*/
|
|
182
|
+
updateStats(success, attempts, totalDelay) {
|
|
183
|
+
this.totalAttempts += attempts;
|
|
184
|
+
this.stats.totalExecutions++;
|
|
185
|
+
this.stats.totalAttempts += attempts;
|
|
186
|
+
this.stats.totalTimeSpent += totalDelay;
|
|
187
|
+
if (success) {
|
|
188
|
+
this.successfulRetries++;
|
|
189
|
+
this.stats.successfulRetries++;
|
|
190
|
+
}
|
|
191
|
+
else {
|
|
192
|
+
this.failedRetries++;
|
|
193
|
+
this.stats.failedRetries++;
|
|
194
|
+
}
|
|
195
|
+
// Update calculated stats
|
|
196
|
+
const totalExecutions = this.stats.totalExecutions;
|
|
197
|
+
const totalAttempts = this.stats.totalAttempts;
|
|
198
|
+
this.stats.successRate = totalExecutions > 0 ?
|
|
199
|
+
this.stats.successfulRetries / totalExecutions : 0;
|
|
200
|
+
this.stats.averageAttempts = totalExecutions > 0 ?
|
|
201
|
+
totalAttempts / totalExecutions : 0;
|
|
202
|
+
this.stats.averageDelay = totalAttempts > 0 ?
|
|
203
|
+
this.stats.totalTimeSpent / totalAttempts : 0;
|
|
204
|
+
}
|
|
205
|
+
/**
|
|
206
|
+
* Update retry strategy configuration
|
|
207
|
+
* @param config New configuration
|
|
208
|
+
*/
|
|
209
|
+
updateConfig(config) {
|
|
210
|
+
this.config = { ...this.config, ...config };
|
|
211
|
+
}
|
|
212
|
+
/**
|
|
213
|
+
* Get retry strategy statistics
|
|
214
|
+
*/
|
|
215
|
+
getStats() {
|
|
216
|
+
return { ...this.stats };
|
|
217
|
+
}
|
|
218
|
+
/**
|
|
219
|
+
* Reset retry strategy statistics
|
|
220
|
+
*/
|
|
221
|
+
resetStats() {
|
|
222
|
+
this.totalAttempts = 0;
|
|
223
|
+
this.successfulRetries = 0;
|
|
224
|
+
this.failedRetries = 0;
|
|
225
|
+
this.stats = {
|
|
226
|
+
totalExecutions: 0,
|
|
227
|
+
totalAttempts: 0,
|
|
228
|
+
successfulRetries: 0,
|
|
229
|
+
failedRetries: 0,
|
|
230
|
+
successRate: 0,
|
|
231
|
+
averageAttempts: 0,
|
|
232
|
+
averageDelay: 0,
|
|
233
|
+
totalTimeSpent: 0,
|
|
234
|
+
};
|
|
235
|
+
}
|
|
236
|
+
/**
|
|
237
|
+
* Health check for the retry strategy
|
|
238
|
+
*/
|
|
239
|
+
healthCheck() {
|
|
240
|
+
const healthy = true; // Retry strategy is always healthy
|
|
241
|
+
return {
|
|
242
|
+
healthy,
|
|
243
|
+
message: 'Retry strategy is operational',
|
|
244
|
+
details: {
|
|
245
|
+
name: this.name,
|
|
246
|
+
config: this.config,
|
|
247
|
+
statistics: this.getStats(),
|
|
248
|
+
},
|
|
249
|
+
};
|
|
250
|
+
}
|
|
251
|
+
}
|