@devilsdev/rag-pipeline-utils 2.2.0 → 2.3.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.
Files changed (46) hide show
  1. package/README.md +323 -722
  2. package/contracts/contract-schema.json +189 -0
  3. package/contracts/reranker-contract.json +69 -0
  4. package/dist/index.cjs +19 -0
  5. package/dist/index.mjs +15 -1
  6. package/package.json +4 -2
  7. package/src/cli/commands/wizard.js +627 -0
  8. package/src/cli/input-sanitization-middleware.js +356 -0
  9. package/src/cli/templates/development.template.json +64 -0
  10. package/src/cli/templates/minimal.template.json +33 -0
  11. package/src/cli/templates/production.template.json +104 -0
  12. package/src/cli/templates/testing.template.json +45 -0
  13. package/src/config/connection-pool-config.json +259 -0
  14. package/src/config/memory-optimization.js +414 -0
  15. package/src/config/redaction-patterns.json +150 -0
  16. package/src/config/sanitization-rules.json +323 -0
  17. package/src/core/performance/streaming-safeguards.js +10 -1
  18. package/src/core/plugin-registry.js +275 -0
  19. package/src/dag/core/error-context.js +391 -0
  20. package/src/dag/core/execution-scheduler.js +350 -0
  21. package/src/dag/core/topology-validation.js +415 -0
  22. package/src/dag/dag-engine.js +26 -424
  23. package/src/dev/README.md +126 -0
  24. package/src/dev/dev-server.js +356 -0
  25. package/src/dev/examples/hot-reload-usage.js +355 -0
  26. package/src/dev/hot-reload.js +807 -0
  27. package/src/enterprise/sso-integration.js +184 -15
  28. package/src/index.js +19 -0
  29. package/src/observability/event-logger.js +62 -2
  30. package/src/plugins/optimized-openai-embedder.js +169 -0
  31. package/src/reranker/llm-reranker.js +30 -4
  32. package/src/security/jwt-config.js +431 -0
  33. package/src/security/jwt-validator.js +701 -0
  34. package/src/utils/api-client-pool-integration.js +436 -0
  35. package/src/utils/batch-processor.js +710 -0
  36. package/src/utils/connection-pool.js +605 -0
  37. package/src/utils/contract-validator.js +460 -0
  38. package/src/utils/error-formatter.js +603 -0
  39. package/src/utils/examples/error-formatter-usage.js +351 -0
  40. package/src/utils/input-sanitizer.js +967 -0
  41. package/src/utils/memory-monitor.js +800 -0
  42. package/src/utils/rate-limiter.js +244 -0
  43. package/src/utils/secure-logger-config.js +352 -0
  44. package/src/utils/secure-logger.js +576 -0
  45. package/src/utils/secure-logging.js +338 -0
  46. package/src/utils/structured-logger.js +32 -5
@@ -0,0 +1,605 @@
1
+ 'use strict';
2
+
3
+ /**
4
+ * Connection Pool Manager
5
+ *
6
+ * High-performance HTTP/HTTPS connection pooling for external API calls.
7
+ * Provides connection reuse, keep-alive, and intelligent pool management
8
+ * to reduce latency and improve throughput.
9
+ *
10
+ * Features:
11
+ * - Connection reuse with keep-alive
12
+ * - Configurable pool size limits
13
+ * - Timeout management
14
+ * - Pool utilization metrics
15
+ * - Graceful degradation
16
+ * - Per-host connection limits
17
+ *
18
+ * @module utils/connection-pool
19
+ * @since 2.3.0
20
+ */
21
+
22
+ const http = require('http');
23
+ const https = require('https');
24
+ const { EventEmitter } = require('events');
25
+
26
+ /**
27
+ * Connection Pool Configuration
28
+ */
29
+ const DEFAULT_CONFIG = {
30
+ // Pool size limits
31
+ maxSockets: 50, // Maximum total sockets
32
+ maxFreeSockets: 10, // Maximum idle sockets
33
+ maxSocketsPerHost: 10, // Maximum sockets per host
34
+
35
+ // Timeout settings (milliseconds)
36
+ timeout: 30000, // Socket timeout (30s)
37
+ freeSocketTimeout: 15000, // Idle socket timeout (15s)
38
+ keepAliveTimeout: 60000, // Keep-alive timeout (60s)
39
+
40
+ // Keep-alive settings
41
+ keepAlive: true,
42
+ keepAliveMsecs: 1000,
43
+
44
+ // Scheduling algorithm
45
+ scheduling: 'lifo', // 'lifo' or 'fifo'
46
+
47
+ // Monitoring
48
+ trackMetrics: true,
49
+ metricsInterval: 60000, // Metrics collection interval (1 minute)
50
+ };
51
+
52
+ /**
53
+ * Connection Pool Manager Class
54
+ */
55
+ class ConnectionPoolManager extends EventEmitter {
56
+ constructor(options = {}) {
57
+ super();
58
+
59
+ this.config = {
60
+ ...DEFAULT_CONFIG,
61
+ ...options,
62
+ };
63
+
64
+ // HTTP and HTTPS agents
65
+ this.httpAgent = null;
66
+ this.httpsAgent = null;
67
+
68
+ // Pool state
69
+ this.pools = new Map(); // host -> pool state
70
+ this.activeConnections = 0;
71
+ this.totalConnections = 0;
72
+
73
+ // Metrics
74
+ this.metrics = {
75
+ totalRequests: 0,
76
+ cachedConnections: 0,
77
+ newConnections: 0,
78
+ connectionReuse: 0,
79
+ timeouts: 0,
80
+ errors: 0,
81
+ avgResponseTime: 0,
82
+ totalResponseTime: 0,
83
+ poolExhausted: 0,
84
+ connectionsCreated: 0,
85
+ connectionsDestroyed: 0,
86
+ currentPoolSize: 0,
87
+ peakPoolSize: 0,
88
+ };
89
+
90
+ // Performance tracking
91
+ this.requestTimings = [];
92
+ this.maxTimingsSamples = 1000;
93
+
94
+ // Initialize agents
95
+ this._initializeAgents();
96
+
97
+ // Start metrics collection
98
+ if (this.config.trackMetrics) {
99
+ this._startMetricsCollection();
100
+ }
101
+ }
102
+
103
+ /**
104
+ * Initialize HTTP and HTTPS agents with connection pooling
105
+ * @private
106
+ */
107
+ _initializeAgents() {
108
+ // HTTP Agent configuration
109
+ const httpConfig = {
110
+ keepAlive: this.config.keepAlive,
111
+ keepAliveMsecs: this.config.keepAliveMsecs,
112
+ maxSockets: this.config.maxSockets,
113
+ maxFreeSockets: this.config.maxFreeSockets,
114
+ timeout: this.config.timeout,
115
+ freeSocketTimeout: this.config.freeSocketTimeout,
116
+ scheduling: this.config.scheduling,
117
+ };
118
+
119
+ // HTTPS Agent configuration (inherits from HTTP)
120
+ const httpsConfig = {
121
+ ...httpConfig,
122
+ // Additional HTTPS-specific settings
123
+ rejectUnauthorized: true,
124
+ };
125
+
126
+ // Create agents
127
+ this.httpAgent = new http.Agent(httpConfig);
128
+ this.httpsAgent = new https.Agent(httpsConfig);
129
+
130
+ // Instrument agents for metrics
131
+ this._instrumentAgent(this.httpAgent, 'http');
132
+ this._instrumentAgent(this.httpsAgent, 'https');
133
+
134
+ this.emit('agents_initialized', {
135
+ http: this.httpAgent,
136
+ https: this.httpsAgent,
137
+ });
138
+ }
139
+
140
+ /**
141
+ * Instrument agent for metrics collection
142
+ * @private
143
+ */
144
+ _instrumentAgent(agent, protocol) {
145
+ // Track socket creation
146
+ const originalCreateConnection = agent.createConnection;
147
+ agent.createConnection = (...args) => {
148
+ this.metrics.connectionsCreated++;
149
+ this.metrics.newConnections++;
150
+ this.activeConnections++;
151
+ this.totalConnections++;
152
+
153
+ // Update peak pool size
154
+ if (this.activeConnections > this.metrics.peakPoolSize) {
155
+ this.metrics.peakPoolSize = this.activeConnections;
156
+ }
157
+
158
+ this.emit('connection_created', {
159
+ protocol,
160
+ active: this.activeConnections,
161
+ total: this.totalConnections,
162
+ });
163
+
164
+ return originalCreateConnection.apply(agent, args);
165
+ };
166
+
167
+ // Track socket lifecycle
168
+ agent.on('free', (socket, options) => {
169
+ this.activeConnections--;
170
+ this.metrics.cachedConnections++;
171
+
172
+ this.emit('connection_freed', {
173
+ protocol,
174
+ host: options.host,
175
+ port: options.port,
176
+ active: this.activeConnections,
177
+ });
178
+ });
179
+
180
+ // Track socket removal
181
+ const originalRemoveSocket = agent.removeSocket;
182
+ agent.removeSocket = function (socket, options) {
183
+ this.metrics.connectionsDestroyed++;
184
+ this.activeConnections = Math.max(0, this.activeConnections - 1);
185
+
186
+ this.emit('connection_removed', {
187
+ protocol,
188
+ host: options?.host,
189
+ active: this.activeConnections,
190
+ });
191
+
192
+ return originalRemoveSocket.call(agent, socket, options);
193
+ }.bind(this);
194
+ }
195
+
196
+ /**
197
+ * Get HTTP agent
198
+ *
199
+ * @returns {http.Agent} HTTP agent with connection pooling
200
+ */
201
+ getHttpAgent() {
202
+ return this.httpAgent;
203
+ }
204
+
205
+ /**
206
+ * Get HTTPS agent
207
+ *
208
+ * @returns {https.Agent} HTTPS agent with connection pooling
209
+ */
210
+ getHttpsAgent() {
211
+ return this.httpsAgent;
212
+ }
213
+
214
+ /**
215
+ * Get agent for URL
216
+ *
217
+ * @param {string} url - URL to get agent for
218
+ * @returns {http.Agent|https.Agent} Appropriate agent
219
+ */
220
+ getAgentForUrl(url) {
221
+ const protocol = url.startsWith('https') ? 'https' : 'http';
222
+ return protocol === 'https' ? this.httpsAgent : this.httpAgent;
223
+ }
224
+
225
+ /**
226
+ * Track request timing
227
+ *
228
+ * @param {number} duration - Request duration in milliseconds
229
+ */
230
+ trackRequest(duration) {
231
+ this.metrics.totalRequests++;
232
+ this.metrics.totalResponseTime += duration;
233
+ this.metrics.avgResponseTime =
234
+ this.metrics.totalResponseTime / this.metrics.totalRequests;
235
+
236
+ // Store timing sample (with circular buffer)
237
+ this.requestTimings.push(duration);
238
+ if (this.requestTimings.length > this.maxTimingsSamples) {
239
+ this.requestTimings.shift();
240
+ }
241
+
242
+ // Check for connection reuse
243
+ const reuseRate =
244
+ this.metrics.cachedConnections / Math.max(1, this.metrics.totalRequests);
245
+ if (reuseRate > 0) {
246
+ this.metrics.connectionReuse = reuseRate * 100; // Percentage
247
+ }
248
+
249
+ this.emit('request_completed', {
250
+ duration,
251
+ avgResponseTime: this.metrics.avgResponseTime,
252
+ connectionReuse: this.metrics.connectionReuse,
253
+ });
254
+ }
255
+
256
+ /**
257
+ * Track request error
258
+ *
259
+ * @param {Error} error - Request error
260
+ * @param {string} type - Error type (timeout, network, etc.)
261
+ */
262
+ trackError(error, type = 'error') {
263
+ this.metrics.errors++;
264
+
265
+ if (type === 'timeout') {
266
+ this.metrics.timeouts++;
267
+ }
268
+
269
+ this.emit('request_error', {
270
+ error: error.message,
271
+ type,
272
+ totalErrors: this.metrics.errors,
273
+ });
274
+ }
275
+
276
+ /**
277
+ * Track pool exhaustion
278
+ */
279
+ trackPoolExhaustion() {
280
+ this.metrics.poolExhausted++;
281
+
282
+ this.emit('pool_exhausted', {
283
+ maxSockets: this.config.maxSockets,
284
+ active: this.activeConnections,
285
+ exhaustedCount: this.metrics.poolExhausted,
286
+ });
287
+ }
288
+
289
+ /**
290
+ * Get current pool metrics
291
+ *
292
+ * @returns {Object} Current metrics
293
+ */
294
+ getMetrics() {
295
+ // Calculate percentiles from timing samples
296
+ const sortedTimings = [...this.requestTimings].sort((a, b) => a - b);
297
+ const p50 = sortedTimings[Math.floor(sortedTimings.length * 0.5)] || 0;
298
+ const p95 = sortedTimings[Math.floor(sortedTimings.length * 0.95)] || 0;
299
+ const p99 = sortedTimings[Math.floor(sortedTimings.length * 0.99)] || 0;
300
+
301
+ // Get agent statistics
302
+ const httpSockets = this._getAgentStats(this.httpAgent);
303
+ const httpsSockets = this._getAgentStats(this.httpsAgent);
304
+
305
+ return {
306
+ ...this.metrics,
307
+ currentPoolSize: this.activeConnections,
308
+ poolUtilization: (this.activeConnections / this.config.maxSockets) * 100,
309
+ connectionReuseRate: this.metrics.connectionReuse,
310
+
311
+ // Response time percentiles
312
+ responseTime: {
313
+ avg: this.metrics.avgResponseTime,
314
+ p50,
315
+ p95,
316
+ p99,
317
+ },
318
+
319
+ // Agent-specific stats
320
+ agents: {
321
+ http: httpSockets,
322
+ https: httpsSockets,
323
+ },
324
+
325
+ // Pool configuration
326
+ config: {
327
+ maxSockets: this.config.maxSockets,
328
+ maxFreeSockets: this.config.maxFreeSockets,
329
+ timeout: this.config.timeout,
330
+ },
331
+ };
332
+ }
333
+
334
+ /**
335
+ * Get agent statistics
336
+ * @private
337
+ */
338
+ _getAgentStats(agent) {
339
+ const sockets = agent.sockets;
340
+ const freeSockets = agent.freeSockets;
341
+ const requests = agent.requests;
342
+
343
+ let totalSockets = 0;
344
+ let totalFreeSockets = 0;
345
+ let totalRequests = 0;
346
+
347
+ // Count sockets per host
348
+ for (const [, socketArray] of Object.entries(sockets)) {
349
+ totalSockets += socketArray.length;
350
+ }
351
+
352
+ // Count free sockets per host
353
+ for (const [, socketArray] of Object.entries(freeSockets)) {
354
+ totalFreeSockets += socketArray.length;
355
+ }
356
+
357
+ // Count pending requests per host
358
+ for (const [, requestArray] of Object.entries(requests)) {
359
+ totalRequests += requestArray.length;
360
+ }
361
+
362
+ return {
363
+ active: totalSockets,
364
+ idle: totalFreeSockets,
365
+ pending: totalRequests,
366
+ total: totalSockets + totalFreeSockets,
367
+ };
368
+ }
369
+
370
+ /**
371
+ * Get pool status
372
+ *
373
+ * @returns {Object} Pool status information
374
+ */
375
+ getStatus() {
376
+ const metrics = this.getMetrics();
377
+
378
+ return {
379
+ healthy: metrics.poolUtilization < 90,
380
+ utilization: metrics.poolUtilization,
381
+ activeConnections: this.activeConnections,
382
+ maxConnections: this.config.maxSockets,
383
+ connectionReuse: metrics.connectionReuseRate,
384
+ avgResponseTime: metrics.avgResponseTime,
385
+ errors: metrics.errors,
386
+ timeouts: metrics.timeouts,
387
+ };
388
+ }
389
+
390
+ /**
391
+ * Reset metrics
392
+ */
393
+ resetMetrics() {
394
+ const currentPoolSize = this.activeConnections;
395
+ const peakPoolSize = this.metrics.peakPoolSize;
396
+
397
+ this.metrics = {
398
+ totalRequests: 0,
399
+ cachedConnections: 0,
400
+ newConnections: 0,
401
+ connectionReuse: 0,
402
+ timeouts: 0,
403
+ errors: 0,
404
+ avgResponseTime: 0,
405
+ totalResponseTime: 0,
406
+ poolExhausted: 0,
407
+ connectionsCreated: 0,
408
+ connectionsDestroyed: 0,
409
+ currentPoolSize,
410
+ peakPoolSize,
411
+ };
412
+
413
+ this.requestTimings = [];
414
+
415
+ this.emit('metrics_reset');
416
+ }
417
+
418
+ /**
419
+ * Start periodic metrics collection
420
+ * @private
421
+ */
422
+ _startMetricsCollection() {
423
+ this.metricsInterval = setInterval(() => {
424
+ const metrics = this.getMetrics();
425
+
426
+ this.emit('metrics_collected', metrics);
427
+
428
+ // Warn if pool utilization is high
429
+ if (metrics.poolUtilization > 80) {
430
+ this.emit('high_utilization', {
431
+ utilization: metrics.poolUtilization,
432
+ active: this.activeConnections,
433
+ max: this.config.maxSockets,
434
+ });
435
+ }
436
+
437
+ // Warn if error rate is high
438
+ const errorRate =
439
+ (metrics.errors / Math.max(1, metrics.totalRequests)) * 100;
440
+ if (errorRate > 5) {
441
+ this.emit('high_error_rate', {
442
+ errorRate,
443
+ errors: metrics.errors,
444
+ requests: metrics.totalRequests,
445
+ });
446
+ }
447
+ }, this.config.metricsInterval);
448
+
449
+ // Don't block process exit
450
+ if (this.metricsInterval.unref) {
451
+ this.metricsInterval.unref();
452
+ }
453
+ }
454
+
455
+ /**
456
+ * Destroy all connections and cleanup
457
+ */
458
+ destroy() {
459
+ // Stop metrics collection
460
+ if (this.metricsInterval) {
461
+ clearInterval(this.metricsInterval);
462
+ this.metricsInterval = null;
463
+ }
464
+
465
+ // Destroy agents
466
+ if (this.httpAgent) {
467
+ this.httpAgent.destroy();
468
+ this.httpAgent = null;
469
+ }
470
+
471
+ if (this.httpsAgent) {
472
+ this.httpsAgent.destroy();
473
+ this.httpsAgent = null;
474
+ }
475
+
476
+ // Clear state
477
+ this.pools.clear();
478
+ this.activeConnections = 0;
479
+ this.requestTimings = [];
480
+
481
+ this.emit('destroyed');
482
+ this.removeAllListeners();
483
+ }
484
+
485
+ /**
486
+ * Get connection pool report
487
+ *
488
+ * @returns {string} Human-readable pool report
489
+ */
490
+ getReport() {
491
+ const metrics = this.getMetrics();
492
+ const status = this.getStatus();
493
+
494
+ return `
495
+ Connection Pool Report
496
+ ======================
497
+ Status: ${status.healthy ? 'Healthy ✓' : 'Warning ⚠'}
498
+ Pool Utilization: ${status.utilization.toFixed(1)}%
499
+ Active Connections: ${status.activeConnections} / ${status.maxConnections}
500
+ Connection Reuse Rate: ${status.connectionReuse.toFixed(1)}%
501
+
502
+ Performance Metrics
503
+ -------------------
504
+ Total Requests: ${metrics.totalRequests}
505
+ Avg Response Time: ${metrics.avgResponseTime.toFixed(2)}ms
506
+ P50: ${metrics.responseTime.p50.toFixed(2)}ms
507
+ P95: ${metrics.responseTime.p95.toFixed(2)}ms
508
+ P99: ${metrics.responseTime.p99.toFixed(2)}ms
509
+
510
+ Connection Stats
511
+ ----------------
512
+ New Connections: ${metrics.newConnections}
513
+ Cached Connections: ${metrics.cachedConnections}
514
+ Connections Created: ${metrics.connectionsCreated}
515
+ Connections Destroyed: ${metrics.connectionsDestroyed}
516
+ Peak Pool Size: ${metrics.peakPoolSize}
517
+
518
+ Errors
519
+ ------
520
+ Total Errors: ${metrics.errors}
521
+ Timeouts: ${metrics.timeouts}
522
+ Pool Exhausted: ${metrics.poolExhausted}
523
+
524
+ Agent Stats
525
+ -----------
526
+ HTTP: Active: ${metrics.agents.http.active}, Idle: ${metrics.agents.http.idle}, Pending: ${metrics.agents.http.pending}
527
+ HTTPS: Active: ${metrics.agents.https.active}, Idle: ${metrics.agents.https.idle}, Pending: ${metrics.agents.https.pending}
528
+ `.trim();
529
+ }
530
+ }
531
+
532
+ /**
533
+ * Global connection pool instance
534
+ */
535
+ let globalPool = null;
536
+
537
+ /**
538
+ * Get or create global connection pool
539
+ *
540
+ * @param {Object} options - Pool configuration options
541
+ * @returns {ConnectionPoolManager} Global pool instance
542
+ */
543
+ function getGlobalPool(options = {}) {
544
+ if (!globalPool) {
545
+ globalPool = new ConnectionPoolManager(options);
546
+ }
547
+ return globalPool;
548
+ }
549
+
550
+ /**
551
+ * Reset global connection pool
552
+ */
553
+ function resetGlobalPool() {
554
+ if (globalPool) {
555
+ globalPool.destroy();
556
+ globalPool = null;
557
+ }
558
+ }
559
+
560
+ /**
561
+ * Create fetch wrapper with connection pooling
562
+ *
563
+ * @param {ConnectionPoolManager} pool - Connection pool manager
564
+ * @returns {Function} Fetch function with pooling
565
+ */
566
+ function createPooledFetch(pool) {
567
+ return async function pooledFetch(url, options = {}) {
568
+ const agent = pool.getAgentForUrl(url);
569
+ const startTime = Date.now();
570
+
571
+ try {
572
+ // Add agent to fetch options
573
+ const fetchOptions = {
574
+ ...options,
575
+ agent,
576
+ };
577
+
578
+ // Make request (assuming fetch or node-fetch is available)
579
+ const response = await fetch(url, fetchOptions);
580
+
581
+ // Track successful request
582
+ const duration = Date.now() - startTime;
583
+ pool.trackRequest(duration);
584
+
585
+ return response;
586
+ } catch (error) {
587
+ // Track error
588
+ const errorType =
589
+ error.code === 'ETIMEDOUT' || error.code === 'ESOCKETTIMEDOUT'
590
+ ? 'timeout'
591
+ : 'error';
592
+ pool.trackError(error, errorType);
593
+
594
+ throw error;
595
+ }
596
+ };
597
+ }
598
+
599
+ module.exports = {
600
+ ConnectionPoolManager,
601
+ getGlobalPool,
602
+ resetGlobalPool,
603
+ createPooledFetch,
604
+ DEFAULT_CONFIG,
605
+ };