@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,298 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* Abstract base client implementation
|
|
4
|
+
* @packageDocumentation
|
|
5
|
+
*/
|
|
6
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
7
|
+
exports.BaseClient = void 0;
|
|
8
|
+
const CommunicationError_js_1 = require("../errors/CommunicationError.js");
|
|
9
|
+
const communicationErrorCodes_js_1 = require("../errors/communicationErrorCodes.js");
|
|
10
|
+
/**
|
|
11
|
+
* Abstract base client implementation
|
|
12
|
+
* Provides common functionality for all client implementations
|
|
13
|
+
*/
|
|
14
|
+
class BaseClient {
|
|
15
|
+
/**
|
|
16
|
+
* Create a new base client instance
|
|
17
|
+
* @param serviceName Service name to communicate with
|
|
18
|
+
* @param config Client configuration
|
|
19
|
+
* @param protocol Protocol instance
|
|
20
|
+
*/
|
|
21
|
+
constructor(serviceName, config, protocol) {
|
|
22
|
+
/** Client interceptors */
|
|
23
|
+
this.interceptors = [];
|
|
24
|
+
/** Cached service instances */
|
|
25
|
+
this.cachedInstances = [];
|
|
26
|
+
/** Client metrics */
|
|
27
|
+
this.metrics = {};
|
|
28
|
+
/** Client start time */
|
|
29
|
+
this.startTime = Date.now();
|
|
30
|
+
/** Total calls made */
|
|
31
|
+
this.totalCalls = 0;
|
|
32
|
+
/** Successful calls */
|
|
33
|
+
this.successfulCalls = 0;
|
|
34
|
+
/** Failed calls */
|
|
35
|
+
this.failedCalls = 0;
|
|
36
|
+
this.serviceName = serviceName;
|
|
37
|
+
this.config = { ...config };
|
|
38
|
+
this.protocol = protocol;
|
|
39
|
+
this.name = config.custom?.name || `client_${serviceName}_${Date.now()}`;
|
|
40
|
+
// Initialize components
|
|
41
|
+
this.initializeComponents();
|
|
42
|
+
}
|
|
43
|
+
/**
|
|
44
|
+
* Initialize client components
|
|
45
|
+
*/
|
|
46
|
+
initializeComponents() {
|
|
47
|
+
// Can be overridden by subclasses to initialize
|
|
48
|
+
// serviceDiscoverer, loadBalancer, circuitBreaker, retryStrategy
|
|
49
|
+
}
|
|
50
|
+
/**
|
|
51
|
+
* Get service discovery instance
|
|
52
|
+
*/
|
|
53
|
+
getServiceDiscoverer() {
|
|
54
|
+
return this.serviceDiscoverer;
|
|
55
|
+
}
|
|
56
|
+
/**
|
|
57
|
+
* Get load balancer instance
|
|
58
|
+
*/
|
|
59
|
+
getLoadBalancer() {
|
|
60
|
+
return this.loadBalancer;
|
|
61
|
+
}
|
|
62
|
+
/**
|
|
63
|
+
* Get circuit breaker instance
|
|
64
|
+
*/
|
|
65
|
+
getCircuitBreaker() {
|
|
66
|
+
return this.circuitBreaker;
|
|
67
|
+
}
|
|
68
|
+
/**
|
|
69
|
+
* Get retry strategy instance
|
|
70
|
+
*/
|
|
71
|
+
getRetryStrategy() {
|
|
72
|
+
return this.retryStrategy;
|
|
73
|
+
}
|
|
74
|
+
/**
|
|
75
|
+
* Add an interceptor to the client
|
|
76
|
+
* @param interceptor IInterceptor to add
|
|
77
|
+
*/
|
|
78
|
+
addInterceptor(interceptor) {
|
|
79
|
+
this.interceptors.push(interceptor);
|
|
80
|
+
interceptor.initialize?.({ client: this.name, service: this.serviceName });
|
|
81
|
+
this.onInterceptorAdded(interceptor);
|
|
82
|
+
}
|
|
83
|
+
/**
|
|
84
|
+
* Hook for interceptor addition
|
|
85
|
+
* @param interceptor Added interceptor
|
|
86
|
+
*/
|
|
87
|
+
onInterceptorAdded(interceptor) {
|
|
88
|
+
// Can be overridden by subclasses
|
|
89
|
+
}
|
|
90
|
+
/**
|
|
91
|
+
* Remove an interceptor from the client
|
|
92
|
+
* @param interceptorId IInterceptor identifier
|
|
93
|
+
*/
|
|
94
|
+
removeInterceptor(interceptorId) {
|
|
95
|
+
const index = this.interceptors.findIndex(i => i.name === interceptorId);
|
|
96
|
+
if (index !== -1) {
|
|
97
|
+
const interceptor = this.interceptors[index];
|
|
98
|
+
interceptor.cleanup?.();
|
|
99
|
+
this.interceptors.splice(index, 1);
|
|
100
|
+
this.onInterceptorRemoved(interceptorId);
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
/**
|
|
104
|
+
* Hook for interceptor removal
|
|
105
|
+
* @param interceptorId Removed interceptor ID
|
|
106
|
+
*/
|
|
107
|
+
onInterceptorRemoved(interceptorId) {
|
|
108
|
+
// Can be overridden by subclasses
|
|
109
|
+
}
|
|
110
|
+
/**
|
|
111
|
+
* Get all interceptors
|
|
112
|
+
*/
|
|
113
|
+
getInterceptors() {
|
|
114
|
+
return [...this.interceptors];
|
|
115
|
+
}
|
|
116
|
+
/**
|
|
117
|
+
* Clear all interceptors
|
|
118
|
+
*/
|
|
119
|
+
clearInterceptors() {
|
|
120
|
+
for (const interceptor of this.interceptors) {
|
|
121
|
+
interceptor.cleanup?.();
|
|
122
|
+
}
|
|
123
|
+
this.interceptors = [];
|
|
124
|
+
this.onInterceptorsCleared();
|
|
125
|
+
}
|
|
126
|
+
/**
|
|
127
|
+
* Hook for interceptors cleared
|
|
128
|
+
*/
|
|
129
|
+
onInterceptorsCleared() {
|
|
130
|
+
// Can be overridden by subclasses
|
|
131
|
+
}
|
|
132
|
+
/**
|
|
133
|
+
* Discover service instances
|
|
134
|
+
* @returns Promise resolving to service instances
|
|
135
|
+
*/
|
|
136
|
+
async discoverService() {
|
|
137
|
+
if (!this.serviceDiscoverer) {
|
|
138
|
+
throw new CommunicationError_js_1.CommunicationError(communicationErrorCodes_js_1.COMMUNICATION_ERROR_CODES.DISCOVERY_ERROR, 503, {
|
|
139
|
+
message: 'Service discovery not configured',
|
|
140
|
+
service: this.serviceName,
|
|
141
|
+
});
|
|
142
|
+
}
|
|
143
|
+
const result = await this.serviceDiscoverer.resolve(this.serviceName);
|
|
144
|
+
this.cachedInstances = result.instances;
|
|
145
|
+
this.lastCacheRefresh = Date.now();
|
|
146
|
+
return this.cachedInstances;
|
|
147
|
+
}
|
|
148
|
+
/**
|
|
149
|
+
* Get current service instances (cached)
|
|
150
|
+
*/
|
|
151
|
+
getServiceInstances() {
|
|
152
|
+
return [...this.cachedInstances];
|
|
153
|
+
}
|
|
154
|
+
/**
|
|
155
|
+
* Refresh service instances cache
|
|
156
|
+
*/
|
|
157
|
+
async refreshServiceInstances() {
|
|
158
|
+
await this.discoverService();
|
|
159
|
+
}
|
|
160
|
+
/**
|
|
161
|
+
* Select a service instance using load balancer
|
|
162
|
+
* @param instances Available instances
|
|
163
|
+
* @returns Selected instance
|
|
164
|
+
*/
|
|
165
|
+
selectInstance(instances) {
|
|
166
|
+
if (instances.length === 0) {
|
|
167
|
+
throw new CommunicationError_js_1.CommunicationError(communicationErrorCodes_js_1.COMMUNICATION_ERROR_CODES.NO_AVAILABLE_INSTANCES, 503, {
|
|
168
|
+
service: this.serviceName,
|
|
169
|
+
});
|
|
170
|
+
}
|
|
171
|
+
if (instances.length === 1) {
|
|
172
|
+
return instances[0];
|
|
173
|
+
}
|
|
174
|
+
if (this.loadBalancer) {
|
|
175
|
+
return this.loadBalancer.select(instances, {
|
|
176
|
+
service: this.serviceName,
|
|
177
|
+
client: this.name,
|
|
178
|
+
});
|
|
179
|
+
}
|
|
180
|
+
// Default: round-robin selection
|
|
181
|
+
const index = this.totalCalls % instances.length;
|
|
182
|
+
return instances[index];
|
|
183
|
+
}
|
|
184
|
+
/**
|
|
185
|
+
* Update client metrics
|
|
186
|
+
* @param success Whether call was successful
|
|
187
|
+
* @param duration Call duration in milliseconds
|
|
188
|
+
*/
|
|
189
|
+
updateMetrics(success, duration) {
|
|
190
|
+
this.totalCalls++;
|
|
191
|
+
if (success) {
|
|
192
|
+
this.successfulCalls++;
|
|
193
|
+
}
|
|
194
|
+
else {
|
|
195
|
+
this.failedCalls++;
|
|
196
|
+
}
|
|
197
|
+
this.metrics = {
|
|
198
|
+
...this.metrics,
|
|
199
|
+
totalCalls: this.totalCalls,
|
|
200
|
+
successfulCalls: this.successfulCalls,
|
|
201
|
+
failedCalls: this.failedCalls,
|
|
202
|
+
successRate: this.totalCalls > 0 ? this.successfulCalls / this.totalCalls : 0,
|
|
203
|
+
averageResponseTime: this.calculateAverageResponseTime(duration),
|
|
204
|
+
uptime: Date.now() - this.startTime,
|
|
205
|
+
cachedInstances: this.cachedInstances.length,
|
|
206
|
+
lastCacheRefresh: this.lastCacheRefresh,
|
|
207
|
+
};
|
|
208
|
+
}
|
|
209
|
+
/**
|
|
210
|
+
* Calculate average response time
|
|
211
|
+
* @param newDuration New call duration
|
|
212
|
+
* @returns Average response time
|
|
213
|
+
*/
|
|
214
|
+
calculateAverageResponseTime(newDuration) {
|
|
215
|
+
const currentAverage = this.metrics.averageResponseTime || 0;
|
|
216
|
+
const totalDuration = currentAverage * (this.totalCalls - 1) + newDuration;
|
|
217
|
+
return totalDuration / this.totalCalls;
|
|
218
|
+
}
|
|
219
|
+
/**
|
|
220
|
+
* Health check for the client
|
|
221
|
+
* @returns Promise resolving to health status
|
|
222
|
+
*/
|
|
223
|
+
async healthCheck() {
|
|
224
|
+
const checks = [];
|
|
225
|
+
// Check protocol health
|
|
226
|
+
const protocolHealth = await this.protocol.healthCheck?.() || { healthy: false };
|
|
227
|
+
checks.push({
|
|
228
|
+
component: 'protocol',
|
|
229
|
+
healthy: protocolHealth.healthy,
|
|
230
|
+
message: protocolHealth.message,
|
|
231
|
+
});
|
|
232
|
+
// Check service discovery health if configured
|
|
233
|
+
if (this.serviceDiscoverer) {
|
|
234
|
+
const discoveryHealth = await this.serviceDiscoverer.healthCheckSelf();
|
|
235
|
+
checks.push({
|
|
236
|
+
component: 'service-discovery',
|
|
237
|
+
healthy: discoveryHealth.healthy,
|
|
238
|
+
message: discoveryHealth.message,
|
|
239
|
+
});
|
|
240
|
+
}
|
|
241
|
+
// Check if we have service instances
|
|
242
|
+
const hasInstances = this.cachedInstances.length > 0;
|
|
243
|
+
checks.push({
|
|
244
|
+
component: 'service-instances',
|
|
245
|
+
healthy: hasInstances,
|
|
246
|
+
message: hasInstances ? `Has ${this.cachedInstances.length} instances` : 'No instances available',
|
|
247
|
+
});
|
|
248
|
+
const allHealthy = checks.every(check => check.healthy);
|
|
249
|
+
return {
|
|
250
|
+
healthy: allHealthy,
|
|
251
|
+
message: allHealthy ? 'Client is healthy' : 'Client has issues',
|
|
252
|
+
details: {
|
|
253
|
+
name: this.name,
|
|
254
|
+
service: this.serviceName,
|
|
255
|
+
checks,
|
|
256
|
+
metrics: this.metrics,
|
|
257
|
+
},
|
|
258
|
+
};
|
|
259
|
+
}
|
|
260
|
+
/**
|
|
261
|
+
* Get client metrics
|
|
262
|
+
*/
|
|
263
|
+
getMetrics() {
|
|
264
|
+
return { ...this.metrics };
|
|
265
|
+
}
|
|
266
|
+
/**
|
|
267
|
+
* Reset client metrics
|
|
268
|
+
*/
|
|
269
|
+
resetMetrics() {
|
|
270
|
+
this.totalCalls = 0;
|
|
271
|
+
this.successfulCalls = 0;
|
|
272
|
+
this.failedCalls = 0;
|
|
273
|
+
this.metrics = {
|
|
274
|
+
uptime: Date.now() - this.startTime,
|
|
275
|
+
};
|
|
276
|
+
}
|
|
277
|
+
/**
|
|
278
|
+
* Close/cleanup client resources
|
|
279
|
+
*/
|
|
280
|
+
async close() {
|
|
281
|
+
// Clear interceptors
|
|
282
|
+
this.clearInterceptors();
|
|
283
|
+
// Close protocol
|
|
284
|
+
await this.protocol.disconnect?.();
|
|
285
|
+
// Close service discovery if configured
|
|
286
|
+
if (this.serviceDiscoverer) {
|
|
287
|
+
await this.serviceDiscoverer.close();
|
|
288
|
+
}
|
|
289
|
+
this.onClose();
|
|
290
|
+
}
|
|
291
|
+
/**
|
|
292
|
+
* Hook for client close
|
|
293
|
+
*/
|
|
294
|
+
onClose() {
|
|
295
|
+
// Can be overridden by subclasses
|
|
296
|
+
}
|
|
297
|
+
}
|
|
298
|
+
exports.BaseClient = BaseClient;
|
|
@@ -0,0 +1,377 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* Abstract base compression manager implementation
|
|
4
|
+
* @packageDocumentation
|
|
5
|
+
*/
|
|
6
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
7
|
+
exports.BaseCompressionManager = void 0;
|
|
8
|
+
const communicationErrorCodes_js_1 = require("../errors/communicationErrorCodes.js");
|
|
9
|
+
const SerializationError_js_1 = require("../errors/SerializationError.js");
|
|
10
|
+
/**
|
|
11
|
+
* Abstract base compression manager implementation
|
|
12
|
+
* Provides common functionality for compression management
|
|
13
|
+
*/
|
|
14
|
+
class BaseCompressionManager {
|
|
15
|
+
/**
|
|
16
|
+
* Create a new base compression manager instance
|
|
17
|
+
* @param name Compression manager name
|
|
18
|
+
* @param config Compression configuration
|
|
19
|
+
*/
|
|
20
|
+
constructor(name, config) {
|
|
21
|
+
/** Supported algorithms */
|
|
22
|
+
this.supportedAlgorithms = ['none'];
|
|
23
|
+
/** Compression statistics */
|
|
24
|
+
this.stats = {
|
|
25
|
+
totalCompressions: 0,
|
|
26
|
+
totalDecompressions: 0,
|
|
27
|
+
compressionErrors: 0,
|
|
28
|
+
decompressionErrors: 0,
|
|
29
|
+
totalCompressionTime: 0,
|
|
30
|
+
totalDecompressionTime: 0,
|
|
31
|
+
totalOriginalSize: 0,
|
|
32
|
+
totalCompressedSize: 0,
|
|
33
|
+
totalDecompressedSize: 0,
|
|
34
|
+
averageCompressionTime: 0,
|
|
35
|
+
averageDecompressionTime: 0,
|
|
36
|
+
averageCompressionRatio: 0,
|
|
37
|
+
algorithmUsage: {},
|
|
38
|
+
};
|
|
39
|
+
this.name = name;
|
|
40
|
+
this.config = { ...config };
|
|
41
|
+
this.initialize();
|
|
42
|
+
}
|
|
43
|
+
/**
|
|
44
|
+
* Initialize compression manager
|
|
45
|
+
*/
|
|
46
|
+
initialize() {
|
|
47
|
+
// Detect supported algorithms
|
|
48
|
+
this.supportedAlgorithms.push(...this.detectSupportedAlgorithms());
|
|
49
|
+
}
|
|
50
|
+
/**
|
|
51
|
+
* Compress data
|
|
52
|
+
* @param data Data to compress
|
|
53
|
+
* @param options Compression options
|
|
54
|
+
* @returns Promise resolving to compression result
|
|
55
|
+
* @throws {CommunicationError} If compression fails
|
|
56
|
+
*/
|
|
57
|
+
async compress(data, options) {
|
|
58
|
+
const result = await this.compressWithMetrics(data, options);
|
|
59
|
+
return result;
|
|
60
|
+
}
|
|
61
|
+
/**
|
|
62
|
+
* Compress data with detailed metrics
|
|
63
|
+
* @param data Data to compress
|
|
64
|
+
* @param options Compression options
|
|
65
|
+
* @returns Promise resolving to compression result with metrics
|
|
66
|
+
*/
|
|
67
|
+
async compressWithMetrics(data, options) {
|
|
68
|
+
const startTime = Date.now();
|
|
69
|
+
const algorithm = options?.algorithm || this.config.algorithm || 'gzip';
|
|
70
|
+
const level = options?.level || this.config.level || 6;
|
|
71
|
+
// Convert string to buffer if needed
|
|
72
|
+
const inputBuffer = typeof data === 'string'
|
|
73
|
+
? Buffer.from(data, 'utf8')
|
|
74
|
+
: Buffer.from(data);
|
|
75
|
+
const originalSize = inputBuffer.length;
|
|
76
|
+
// Check if we should compress
|
|
77
|
+
if (!this.shouldCompress(originalSize)) {
|
|
78
|
+
return {
|
|
79
|
+
data: inputBuffer,
|
|
80
|
+
originalSize,
|
|
81
|
+
compressedSize: originalSize,
|
|
82
|
+
compressionRatio: 1,
|
|
83
|
+
algorithm: 'none',
|
|
84
|
+
level: 0,
|
|
85
|
+
compressionTime: Date.now() - startTime,
|
|
86
|
+
};
|
|
87
|
+
}
|
|
88
|
+
// Check algorithm support
|
|
89
|
+
if (!this.isAlgorithmSupported(algorithm)) {
|
|
90
|
+
throw new SerializationError_js_1.SerializationError(communicationErrorCodes_js_1.COMMUNICATION_ERROR_CODES.SERIALIZATION_ERROR, {
|
|
91
|
+
message: `Compression algorithm not supported: ${algorithm}`,
|
|
92
|
+
algorithm,
|
|
93
|
+
supportedAlgorithms: this.supportedAlgorithms,
|
|
94
|
+
});
|
|
95
|
+
}
|
|
96
|
+
try {
|
|
97
|
+
const compressedData = await this.compressData(inputBuffer, algorithm, level);
|
|
98
|
+
const compressionTime = Date.now() - startTime;
|
|
99
|
+
const compressedSize = compressedData.length;
|
|
100
|
+
const compressionRatio = compressedSize / originalSize;
|
|
101
|
+
// Update statistics
|
|
102
|
+
this.updateCompressionStats(originalSize, compressedSize, compressionTime, algorithm);
|
|
103
|
+
return {
|
|
104
|
+
data: compressedData,
|
|
105
|
+
originalSize,
|
|
106
|
+
compressedSize,
|
|
107
|
+
compressionRatio,
|
|
108
|
+
algorithm,
|
|
109
|
+
level,
|
|
110
|
+
compressionTime,
|
|
111
|
+
};
|
|
112
|
+
}
|
|
113
|
+
catch (error) {
|
|
114
|
+
this.stats.compressionErrors++;
|
|
115
|
+
throw new SerializationError_js_1.SerializationError(communicationErrorCodes_js_1.COMMUNICATION_ERROR_CODES.SERIALIZATION_ERROR, {
|
|
116
|
+
message: `Compression failed: ${error instanceof Error ? error.message : String(error)}`,
|
|
117
|
+
algorithm,
|
|
118
|
+
level,
|
|
119
|
+
originalSize,
|
|
120
|
+
}, error instanceof Error ? error : undefined);
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
/**
|
|
124
|
+
* Decompress data
|
|
125
|
+
* @param data Data to decompress
|
|
126
|
+
* @param options Decompression options
|
|
127
|
+
* @returns Promise resolving to decompression result
|
|
128
|
+
* @throws {CommunicationError} If decompression fails
|
|
129
|
+
*/
|
|
130
|
+
async decompress(data, options) {
|
|
131
|
+
const startTime = Date.now();
|
|
132
|
+
const algorithm = options?.algorithm || this.detectAlgorithm(data) || 'gzip';
|
|
133
|
+
// Check algorithm support
|
|
134
|
+
if (!this.isAlgorithmSupported(algorithm)) {
|
|
135
|
+
throw new SerializationError_js_1.SerializationError(communicationErrorCodes_js_1.COMMUNICATION_ERROR_CODES.DESERIALIZATION_ERROR, {
|
|
136
|
+
message: `Decompression algorithm not supported: ${algorithm}`,
|
|
137
|
+
algorithm,
|
|
138
|
+
supportedAlgorithms: this.supportedAlgorithms,
|
|
139
|
+
});
|
|
140
|
+
}
|
|
141
|
+
try {
|
|
142
|
+
const decompressedData = await this.decompressData(Buffer.from(data), algorithm);
|
|
143
|
+
const decompressionTime = Date.now() - startTime;
|
|
144
|
+
const originalSize = data.length;
|
|
145
|
+
const decompressedSize = decompressedData.length;
|
|
146
|
+
// Update statistics
|
|
147
|
+
this.updateDecompressionStats(originalSize, decompressedSize, decompressionTime, algorithm);
|
|
148
|
+
return {
|
|
149
|
+
data: decompressedData,
|
|
150
|
+
originalSize,
|
|
151
|
+
decompressedSize,
|
|
152
|
+
decompressionTime,
|
|
153
|
+
algorithm,
|
|
154
|
+
};
|
|
155
|
+
}
|
|
156
|
+
catch (error) {
|
|
157
|
+
this.stats.decompressionErrors++;
|
|
158
|
+
throw new SerializationError_js_1.SerializationError(communicationErrorCodes_js_1.COMMUNICATION_ERROR_CODES.DESERIALIZATION_ERROR, {
|
|
159
|
+
message: `Decompression failed: ${error instanceof Error ? error.message : String(error)}`,
|
|
160
|
+
algorithm,
|
|
161
|
+
originalSize: data.length,
|
|
162
|
+
}, error instanceof Error ? error : undefined);
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
/**
|
|
166
|
+
* Detect compression algorithm from data
|
|
167
|
+
* @param data Compressed data
|
|
168
|
+
* @returns Detected algorithm or undefined
|
|
169
|
+
*/
|
|
170
|
+
detectAlgorithm(data) {
|
|
171
|
+
const buffer = Buffer.from(data);
|
|
172
|
+
// Check for gzip magic number: 0x1F 0x8B
|
|
173
|
+
if (buffer.length >= 2 && buffer[0] === 0x1F && buffer[1] === 0x8B) {
|
|
174
|
+
return 'gzip';
|
|
175
|
+
}
|
|
176
|
+
// Check for deflate magic number: 0x78 (zlib header)
|
|
177
|
+
if (buffer.length >= 1 && (buffer[0] === 0x78 || buffer[0] === 0x58)) {
|
|
178
|
+
return 'deflate';
|
|
179
|
+
}
|
|
180
|
+
// Check for brotli magic number: 0xCE 0x2B 0x2F
|
|
181
|
+
if (buffer.length >= 3 && buffer[0] === 0xCE && buffer[1] === 0x2B && buffer[2] === 0x2F) {
|
|
182
|
+
return 'brotli';
|
|
183
|
+
}
|
|
184
|
+
return undefined;
|
|
185
|
+
}
|
|
186
|
+
/**
|
|
187
|
+
* Check if algorithm is supported
|
|
188
|
+
* @param algorithm Algorithm to check
|
|
189
|
+
* @returns True if algorithm is supported
|
|
190
|
+
*/
|
|
191
|
+
isAlgorithmSupported(algorithm) {
|
|
192
|
+
return this.supportedAlgorithms.includes(algorithm);
|
|
193
|
+
}
|
|
194
|
+
/**
|
|
195
|
+
* Get content encoding header for algorithm
|
|
196
|
+
* @param algorithm Compression algorithm
|
|
197
|
+
* @returns Content-Encoding header value
|
|
198
|
+
*/
|
|
199
|
+
getContentEncoding(algorithm) {
|
|
200
|
+
switch (algorithm) {
|
|
201
|
+
case 'gzip':
|
|
202
|
+
return 'gzip';
|
|
203
|
+
case 'deflate':
|
|
204
|
+
return 'deflate';
|
|
205
|
+
case 'brotli':
|
|
206
|
+
return 'br';
|
|
207
|
+
case 'none':
|
|
208
|
+
return 'identity';
|
|
209
|
+
default:
|
|
210
|
+
return algorithm;
|
|
211
|
+
}
|
|
212
|
+
}
|
|
213
|
+
/**
|
|
214
|
+
* Get algorithm from content encoding header
|
|
215
|
+
* @param contentEncoding Content-Encoding header value
|
|
216
|
+
* @returns Compression algorithm or undefined
|
|
217
|
+
*/
|
|
218
|
+
getAlgorithmFromEncoding(contentEncoding) {
|
|
219
|
+
const normalized = contentEncoding.toLowerCase().trim();
|
|
220
|
+
switch (normalized) {
|
|
221
|
+
case 'gzip':
|
|
222
|
+
case 'x-gzip':
|
|
223
|
+
return 'gzip';
|
|
224
|
+
case 'deflate':
|
|
225
|
+
return 'deflate';
|
|
226
|
+
case 'br':
|
|
227
|
+
return 'brotli';
|
|
228
|
+
case 'identity':
|
|
229
|
+
case 'none':
|
|
230
|
+
return 'none';
|
|
231
|
+
default:
|
|
232
|
+
return undefined;
|
|
233
|
+
}
|
|
234
|
+
}
|
|
235
|
+
/**
|
|
236
|
+
* Should compress based on configuration
|
|
237
|
+
* @param dataSize Data size in bytes
|
|
238
|
+
* @param contentType Content type
|
|
239
|
+
* @returns True if should compress
|
|
240
|
+
*/
|
|
241
|
+
shouldCompress(dataSize, contentType) {
|
|
242
|
+
if (!this.config.enabled) {
|
|
243
|
+
return false;
|
|
244
|
+
}
|
|
245
|
+
// Check minimum size
|
|
246
|
+
if (this.config.minSize && dataSize < this.config.minSize) {
|
|
247
|
+
return false;
|
|
248
|
+
}
|
|
249
|
+
// Check maximum size
|
|
250
|
+
if (this.config.maxSize && dataSize > this.config.maxSize) {
|
|
251
|
+
return false;
|
|
252
|
+
}
|
|
253
|
+
// Check content types
|
|
254
|
+
if (this.config.contentTypes && contentType) {
|
|
255
|
+
const normalizedContentType = contentType.toLowerCase().split(';')[0].trim();
|
|
256
|
+
const shouldCompress = this.config.contentTypes.some(ct => normalizedContentType.includes(ct.toLowerCase()));
|
|
257
|
+
if (!shouldCompress) {
|
|
258
|
+
return false;
|
|
259
|
+
}
|
|
260
|
+
}
|
|
261
|
+
return true;
|
|
262
|
+
}
|
|
263
|
+
/**
|
|
264
|
+
* Create compression stream
|
|
265
|
+
* @param algorithm Compression algorithm
|
|
266
|
+
* @param options Compression options
|
|
267
|
+
* @returns Transform stream for compression
|
|
268
|
+
*/
|
|
269
|
+
createCompressionStream(algorithm, options) {
|
|
270
|
+
throw new Error('createCompressionStream not implemented');
|
|
271
|
+
}
|
|
272
|
+
/**
|
|
273
|
+
* Create decompression stream
|
|
274
|
+
* @param algorithm Compression algorithm
|
|
275
|
+
* @returns Transform stream for decompression
|
|
276
|
+
*/
|
|
277
|
+
createDecompressionStream(algorithm) {
|
|
278
|
+
throw new Error('createDecompressionStream not implemented');
|
|
279
|
+
}
|
|
280
|
+
/**
|
|
281
|
+
* Update compression statistics
|
|
282
|
+
* @param originalSize Original size in bytes
|
|
283
|
+
* @param compressedSize Compressed size in bytes
|
|
284
|
+
* @param compressionTime Compression time in milliseconds
|
|
285
|
+
* @param algorithm Compression algorithm used
|
|
286
|
+
*/
|
|
287
|
+
updateCompressionStats(originalSize, compressedSize, compressionTime, algorithm) {
|
|
288
|
+
this.stats.totalCompressions++;
|
|
289
|
+
this.stats.totalCompressionTime += compressionTime;
|
|
290
|
+
this.stats.totalOriginalSize += originalSize;
|
|
291
|
+
this.stats.totalCompressedSize += compressedSize;
|
|
292
|
+
// Update algorithm usage
|
|
293
|
+
this.stats.algorithmUsage[algorithm] = (this.stats.algorithmUsage[algorithm] || 0) + 1;
|
|
294
|
+
// Update averages
|
|
295
|
+
this.stats.averageCompressionTime = this.stats.totalCompressionTime / this.stats.totalCompressions;
|
|
296
|
+
this.stats.averageCompressionRatio = this.stats.totalCompressedSize / this.stats.totalOriginalSize;
|
|
297
|
+
}
|
|
298
|
+
/**
|
|
299
|
+
* Update decompression statistics
|
|
300
|
+
* @param originalSize Original compressed size in bytes
|
|
301
|
+
* @param decompressedSize Decompressed size in bytes
|
|
302
|
+
* @param decompressionTime Decompression time in milliseconds
|
|
303
|
+
* @param algorithm Decompression algorithm used
|
|
304
|
+
*/
|
|
305
|
+
updateDecompressionStats(originalSize, decompressedSize, decompressionTime, algorithm) {
|
|
306
|
+
this.stats.totalDecompressions++;
|
|
307
|
+
this.stats.totalDecompressionTime += decompressionTime;
|
|
308
|
+
this.stats.totalDecompressedSize += decompressedSize;
|
|
309
|
+
// Update algorithm usage
|
|
310
|
+
this.stats.algorithmUsage[algorithm] = (this.stats.algorithmUsage[algorithm] || 0) + 1;
|
|
311
|
+
// Update averages
|
|
312
|
+
this.stats.averageDecompressionTime = this.stats.totalDecompressionTime / this.stats.totalDecompressions;
|
|
313
|
+
}
|
|
314
|
+
/**
|
|
315
|
+
* Update compression configuration
|
|
316
|
+
* @param config New configuration
|
|
317
|
+
*/
|
|
318
|
+
updateConfig(config) {
|
|
319
|
+
this.config = { ...this.config, ...config };
|
|
320
|
+
}
|
|
321
|
+
/**
|
|
322
|
+
* Get compression statistics
|
|
323
|
+
*/
|
|
324
|
+
getStats() {
|
|
325
|
+
const bytesSaved = this.stats.totalOriginalSize - this.stats.totalCompressedSize;
|
|
326
|
+
return {
|
|
327
|
+
totalCompressions: this.stats.totalCompressions,
|
|
328
|
+
totalDecompressions: this.stats.totalDecompressions,
|
|
329
|
+
compressionErrors: this.stats.compressionErrors,
|
|
330
|
+
decompressionErrors: this.stats.decompressionErrors,
|
|
331
|
+
averageCompressionTime: this.stats.averageCompressionTime,
|
|
332
|
+
averageDecompressionTime: this.stats.averageDecompressionTime,
|
|
333
|
+
averageCompressionRatio: this.stats.averageCompressionRatio,
|
|
334
|
+
algorithmUsage: { ...this.stats.algorithmUsage },
|
|
335
|
+
totalBytesCompressed: this.stats.totalOriginalSize,
|
|
336
|
+
totalBytesDecompressed: this.stats.totalDecompressedSize,
|
|
337
|
+
bytesSaved: Math.max(0, bytesSaved),
|
|
338
|
+
};
|
|
339
|
+
}
|
|
340
|
+
/**
|
|
341
|
+
* Reset compression statistics
|
|
342
|
+
*/
|
|
343
|
+
resetStats() {
|
|
344
|
+
this.stats = {
|
|
345
|
+
totalCompressions: 0,
|
|
346
|
+
totalDecompressions: 0,
|
|
347
|
+
compressionErrors: 0,
|
|
348
|
+
decompressionErrors: 0,
|
|
349
|
+
totalCompressionTime: 0,
|
|
350
|
+
totalDecompressionTime: 0,
|
|
351
|
+
totalOriginalSize: 0,
|
|
352
|
+
totalCompressedSize: 0,
|
|
353
|
+
totalDecompressedSize: 0,
|
|
354
|
+
averageCompressionTime: 0,
|
|
355
|
+
averageDecompressionTime: 0,
|
|
356
|
+
averageCompressionRatio: 0,
|
|
357
|
+
algorithmUsage: {},
|
|
358
|
+
};
|
|
359
|
+
}
|
|
360
|
+
/**
|
|
361
|
+
* Health check for compression manager
|
|
362
|
+
*/
|
|
363
|
+
healthCheck() {
|
|
364
|
+
const healthy = this.supportedAlgorithms.length > 0;
|
|
365
|
+
return {
|
|
366
|
+
healthy,
|
|
367
|
+
message: healthy ? 'Compression manager is operational' : 'No compression algorithms supported',
|
|
368
|
+
details: {
|
|
369
|
+
name: this.name,
|
|
370
|
+
supportedAlgorithms: this.supportedAlgorithms,
|
|
371
|
+
config: this.config,
|
|
372
|
+
statistics: this.getStats(),
|
|
373
|
+
},
|
|
374
|
+
};
|
|
375
|
+
}
|
|
376
|
+
}
|
|
377
|
+
exports.BaseCompressionManager = BaseCompressionManager;
|