@unrdf/knowledge-engine 5.0.1

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 (60) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +84 -0
  3. package/package.json +64 -0
  4. package/src/browser-shims.mjs +343 -0
  5. package/src/browser.mjs +910 -0
  6. package/src/canonicalize.mjs +414 -0
  7. package/src/condition-cache.mjs +109 -0
  8. package/src/condition-evaluator.mjs +722 -0
  9. package/src/dark-matter-core.mjs +742 -0
  10. package/src/define-hook.mjs +213 -0
  11. package/src/effect-sandbox-browser.mjs +283 -0
  12. package/src/effect-sandbox-worker.mjs +170 -0
  13. package/src/effect-sandbox.mjs +517 -0
  14. package/src/engines/index.mjs +11 -0
  15. package/src/engines/rdf-engine.mjs +299 -0
  16. package/src/file-resolver.mjs +387 -0
  17. package/src/hook-executor-batching.mjs +277 -0
  18. package/src/hook-executor.mjs +870 -0
  19. package/src/hook-management.mjs +150 -0
  20. package/src/index.mjs +93 -0
  21. package/src/ken-parliment.mjs +119 -0
  22. package/src/ken.mjs +149 -0
  23. package/src/knowledge-engine/builtin-rules.mjs +190 -0
  24. package/src/knowledge-engine/inference-engine.mjs +418 -0
  25. package/src/knowledge-engine/knowledge-engine.mjs +317 -0
  26. package/src/knowledge-engine/pattern-dsl.mjs +142 -0
  27. package/src/knowledge-engine/pattern-matcher.mjs +215 -0
  28. package/src/knowledge-engine/rules.mjs +184 -0
  29. package/src/knowledge-engine.mjs +319 -0
  30. package/src/knowledge-hook-engine.mjs +360 -0
  31. package/src/knowledge-hook-manager.mjs +469 -0
  32. package/src/knowledge-substrate-core.mjs +927 -0
  33. package/src/lite.mjs +222 -0
  34. package/src/lockchain-writer-browser.mjs +414 -0
  35. package/src/lockchain-writer.mjs +602 -0
  36. package/src/monitoring/andon-signals.mjs +775 -0
  37. package/src/observability.mjs +531 -0
  38. package/src/parse.mjs +290 -0
  39. package/src/performance-optimizer.mjs +678 -0
  40. package/src/policy-pack.mjs +572 -0
  41. package/src/query-cache.mjs +116 -0
  42. package/src/query-optimizer.mjs +1051 -0
  43. package/src/query.mjs +306 -0
  44. package/src/reason.mjs +350 -0
  45. package/src/resolution-layer.mjs +506 -0
  46. package/src/schemas.mjs +1063 -0
  47. package/src/security/error-sanitizer.mjs +257 -0
  48. package/src/security/path-validator.mjs +194 -0
  49. package/src/security/sandbox-restrictions.mjs +331 -0
  50. package/src/security-validator.mjs +389 -0
  51. package/src/store-cache.mjs +137 -0
  52. package/src/telemetry.mjs +167 -0
  53. package/src/transaction.mjs +810 -0
  54. package/src/utils/adaptive-monitor.mjs +746 -0
  55. package/src/utils/circuit-breaker.mjs +513 -0
  56. package/src/utils/edge-case-handler.mjs +503 -0
  57. package/src/utils/memory-manager.mjs +498 -0
  58. package/src/utils/ring-buffer.mjs +282 -0
  59. package/src/validate.mjs +319 -0
  60. package/src/validators/index.mjs +338 -0
@@ -0,0 +1,746 @@
1
+ /**
2
+ * @file Adaptive monitoring with health-based sampling
3
+ * @module knowledge-engine/utils/adaptive-monitor
4
+ *
5
+ * @description
6
+ * TRIZ #21 - Skipping pattern: Implements adaptive monitoring that adjusts its
7
+ * sampling frequency based on system health. When the system is healthy, monitoring
8
+ * intervals increase (skip unnecessary checks). When issues are detected, intervals
9
+ * decrease rapidly for faster response.
10
+ *
11
+ * Key features:
12
+ * - Exponential backoff when system is healthy (reduce overhead)
13
+ * - Rapid interval decrease when issues detected
14
+ * - Health history tracking for trend analysis
15
+ * - Configurable thresholds and bounds
16
+ * - Event-based notifications
17
+ * - Multiple monitor orchestration
18
+ *
19
+ * @example
20
+ * ```javascript
21
+ * import { AdaptiveMonitor } from 'unrdf/knowledge-engine/utils/adaptive-monitor';
22
+ *
23
+ * const monitor = new AdaptiveMonitor({
24
+ * baseInterval: 60000, // Start at 1 minute
25
+ * minInterval: 1000, // Min 1 second when unhealthy
26
+ * maxInterval: 300000 // Max 5 minutes when stable
27
+ * });
28
+ *
29
+ * monitor.on('health', ({ healthy, details }) => {
30
+ * if (!healthy) console.warn('System unhealthy:', details);
31
+ * });
32
+ *
33
+ * monitor.start(async () => {
34
+ * const memUsage = process.memoryUsage();
35
+ * return memUsage.heapUsed / memUsage.heapTotal < 0.8;
36
+ * });
37
+ * ```
38
+ */
39
+
40
+ import { EventEmitter } from 'node:events';
41
+
42
+ /**
43
+ * Default configuration for AdaptiveMonitor
44
+ * @constant {Object}
45
+ */
46
+ export const DEFAULT_CONFIG = {
47
+ /** Base monitoring interval in ms */
48
+ baseInterval: 60000,
49
+ /** Minimum interval (fastest polling) */
50
+ minInterval: 1000,
51
+ /** Maximum interval (slowest polling) */
52
+ maxInterval: 300000,
53
+ /** Factor to increase interval when healthy */
54
+ healthyBackoffFactor: 1.5,
55
+ /** Factor to decrease interval when unhealthy */
56
+ unhealthyRushFactor: 0.5,
57
+ /** Number of consecutive healthy checks to consider stable */
58
+ stableThreshold: 3,
59
+ /** Number of consecutive unhealthy checks to consider critical */
60
+ criticalThreshold: 3,
61
+ /** Maximum health history entries */
62
+ maxHistorySize: 100,
63
+ /** Enable verbose logging */
64
+ verbose: false,
65
+ };
66
+
67
+ /**
68
+ * Health status enumeration
69
+ * @constant {Object.<string, string>}
70
+ */
71
+ export const HealthStatus = {
72
+ /** System is healthy */
73
+ HEALTHY: 'healthy',
74
+ /** System is unhealthy */
75
+ UNHEALTHY: 'unhealthy',
76
+ /** System health is unknown */
77
+ UNKNOWN: 'unknown',
78
+ /** System is in stable state (multiple healthy checks) */
79
+ STABLE: 'stable',
80
+ /** System is in critical state (multiple unhealthy checks) */
81
+ CRITICAL: 'critical',
82
+ /** System is recovering */
83
+ RECOVERING: 'recovering',
84
+ };
85
+
86
+ /**
87
+ * Adaptive monitor events
88
+ * @constant {Object.<string, string>}
89
+ */
90
+ export const MonitorEvents = {
91
+ /** Emitted on each health check */
92
+ HEALTH: 'health',
93
+ /** Emitted when status changes */
94
+ STATUS_CHANGE: 'statusChange',
95
+ /** Emitted when interval adjusts */
96
+ INTERVAL_CHANGE: 'intervalChange',
97
+ /** Emitted on monitor start */
98
+ START: 'start',
99
+ /** Emitted on monitor stop */
100
+ STOP: 'stop',
101
+ /** Emitted on check error */
102
+ ERROR: 'error',
103
+ /** Emitted when entering critical state */
104
+ CRITICAL: 'critical',
105
+ /** Emitted when entering stable state */
106
+ STABLE: 'stable',
107
+ /** Emitted when recovery begins */
108
+ RECOVERY: 'recovery',
109
+ };
110
+
111
+ /**
112
+ * Health check result type
113
+ * @typedef {Object} HealthCheckResult
114
+ * @property {boolean} healthy - Whether the check passed
115
+ * @property {number} timestamp - Unix timestamp of check
116
+ * @property {number} duration - Check duration in ms
117
+ * @property {Object} [details] - Additional check details
118
+ * @property {Error} [error] - Error if check failed
119
+ */
120
+
121
+ /**
122
+ * Adaptive Monitor class with health-based sampling
123
+ * @extends EventEmitter
124
+ */
125
+ export class AdaptiveMonitor extends EventEmitter {
126
+ /**
127
+ * Create an adaptive monitor
128
+ * @param {Object} [config={}] - Configuration options
129
+ * @param {number} [config.baseInterval=60000] - Base monitoring interval
130
+ * @param {number} [config.minInterval=1000] - Minimum interval
131
+ * @param {number} [config.maxInterval=300000] - Maximum interval
132
+ * @param {number} [config.healthyBackoffFactor=1.5] - Backoff multiplier
133
+ * @param {number} [config.unhealthyRushFactor=0.5] - Rush multiplier
134
+ * @param {number} [config.stableThreshold=3] - Checks for stable status
135
+ * @param {number} [config.criticalThreshold=3] - Checks for critical status
136
+ * @param {number} [config.maxHistorySize=100] - Max history entries
137
+ * @param {boolean} [config.verbose=false] - Enable verbose logging
138
+ */
139
+ constructor(config = {}) {
140
+ super();
141
+
142
+ this.config = { ...DEFAULT_CONFIG, ...config };
143
+
144
+ // Current state
145
+ this.currentInterval = this.config.baseInterval;
146
+ this.running = false;
147
+ this.paused = false;
148
+ this.status = HealthStatus.UNKNOWN;
149
+
150
+ // Health tracking
151
+ this.healthHistory = [];
152
+ this.consecutiveHealthy = 0;
153
+ this.consecutiveUnhealthy = 0;
154
+ this.totalChecks = 0;
155
+ this.totalHealthy = 0;
156
+ this.totalUnhealthy = 0;
157
+
158
+ // Timing
159
+ this.startTime = null;
160
+ this.lastCheckTime = null;
161
+ this.nextCheckTime = null;
162
+
163
+ // Internal
164
+ this._timeoutId = null;
165
+ this._checkFn = null;
166
+
167
+ // Bind methods for stable references
168
+ this._executeCheck = this._executeCheck.bind(this);
169
+ this._scheduleNext = this._scheduleNext.bind(this);
170
+ }
171
+
172
+ /**
173
+ * Start monitoring with the given health check function
174
+ * @param {Function} checkFn - Async function returning boolean or {healthy, details}
175
+ * @returns {AdaptiveMonitor} this for chaining
176
+ */
177
+ start(checkFn) {
178
+ if (this.running) {
179
+ this._log('Monitor already running');
180
+ return this;
181
+ }
182
+
183
+ if (typeof checkFn !== 'function') {
184
+ throw new TypeError('checkFn must be a function');
185
+ }
186
+
187
+ this._checkFn = checkFn;
188
+ this.running = true;
189
+ this.paused = false;
190
+ this.startTime = Date.now();
191
+ this.currentInterval = this.config.baseInterval;
192
+
193
+ this._log(`Starting monitor with ${this.currentInterval}ms interval`);
194
+ this.emit(MonitorEvents.START, {
195
+ interval: this.currentInterval,
196
+ timestamp: this.startTime,
197
+ });
198
+
199
+ // Execute first check immediately
200
+ this._executeCheck();
201
+
202
+ return this;
203
+ }
204
+
205
+ /**
206
+ * Stop monitoring
207
+ * @returns {AdaptiveMonitor} this for chaining
208
+ */
209
+ stop() {
210
+ if (!this.running) {
211
+ return this;
212
+ }
213
+
214
+ this.running = false;
215
+
216
+ if (this._timeoutId) {
217
+ clearTimeout(this._timeoutId);
218
+ this._timeoutId = null;
219
+ }
220
+
221
+ const duration = Date.now() - this.startTime;
222
+ this._log(`Stopping monitor after ${duration}ms`);
223
+
224
+ this.emit(MonitorEvents.STOP, {
225
+ duration,
226
+ totalChecks: this.totalChecks,
227
+ timestamp: Date.now(),
228
+ });
229
+
230
+ return this;
231
+ }
232
+
233
+ /**
234
+ * Pause monitoring (keeps state, stops checks)
235
+ * @returns {AdaptiveMonitor} this for chaining
236
+ */
237
+ pause() {
238
+ if (!this.running || this.paused) {
239
+ return this;
240
+ }
241
+
242
+ this.paused = true;
243
+
244
+ if (this._timeoutId) {
245
+ clearTimeout(this._timeoutId);
246
+ this._timeoutId = null;
247
+ }
248
+
249
+ this._log('Monitor paused');
250
+ return this;
251
+ }
252
+
253
+ /**
254
+ * Resume monitoring after pause
255
+ * @returns {AdaptiveMonitor} this for chaining
256
+ */
257
+ resume() {
258
+ if (!this.running || !this.paused) {
259
+ return this;
260
+ }
261
+
262
+ this.paused = false;
263
+ this._log('Monitor resumed');
264
+ this._executeCheck();
265
+
266
+ return this;
267
+ }
268
+
269
+ /**
270
+ * Force an immediate health check
271
+ * @returns {Promise<HealthCheckResult>} Check result
272
+ */
273
+ async checkNow() {
274
+ return this._executeCheck(true);
275
+ }
276
+
277
+ /**
278
+ * Reset the monitor to initial state
279
+ * @returns {AdaptiveMonitor} this for chaining
280
+ */
281
+ reset() {
282
+ this.stop();
283
+
284
+ this.currentInterval = this.config.baseInterval;
285
+ this.status = HealthStatus.UNKNOWN;
286
+ this.healthHistory = [];
287
+ this.consecutiveHealthy = 0;
288
+ this.consecutiveUnhealthy = 0;
289
+ this.totalChecks = 0;
290
+ this.totalHealthy = 0;
291
+ this.totalUnhealthy = 0;
292
+ this.startTime = null;
293
+ this.lastCheckTime = null;
294
+ this.nextCheckTime = null;
295
+
296
+ this._log('Monitor reset');
297
+ return this;
298
+ }
299
+
300
+ /**
301
+ * Get current monitoring statistics
302
+ * @returns {Object} Monitor statistics
303
+ */
304
+ getStats() {
305
+ const now = Date.now();
306
+ const uptime = this.startTime ? now - this.startTime : 0;
307
+
308
+ return {
309
+ running: this.running,
310
+ paused: this.paused,
311
+ status: this.status,
312
+ currentInterval: this.currentInterval,
313
+ uptime,
314
+ totalChecks: this.totalChecks,
315
+ totalHealthy: this.totalHealthy,
316
+ totalUnhealthy: this.totalUnhealthy,
317
+ healthRate:
318
+ this.totalChecks > 0
319
+ ? ((this.totalHealthy / this.totalChecks) * 100).toFixed(2) + '%'
320
+ : 'N/A',
321
+ consecutiveHealthy: this.consecutiveHealthy,
322
+ consecutiveUnhealthy: this.consecutiveUnhealthy,
323
+ lastCheckTime: this.lastCheckTime,
324
+ nextCheckTime: this.nextCheckTime,
325
+ historySize: this.healthHistory.length,
326
+ };
327
+ }
328
+
329
+ /**
330
+ * Get health history
331
+ * @param {number} [limit=10] - Maximum entries to return
332
+ * @returns {HealthCheckResult[]} Recent health checks
333
+ */
334
+ getHistory(limit = 10) {
335
+ return this.healthHistory.slice(-limit);
336
+ }
337
+
338
+ /**
339
+ * Get health trend (positive = improving, negative = degrading)
340
+ * @param {number} [window=10] - Number of recent checks to analyze
341
+ * @returns {number} Trend value between -1 and 1
342
+ */
343
+ getHealthTrend(window = 10) {
344
+ const recent = this.healthHistory.slice(-window);
345
+
346
+ if (recent.length < 2) {
347
+ return 0;
348
+ }
349
+
350
+ const firstHalf = recent.slice(0, Math.floor(recent.length / 2));
351
+ const secondHalf = recent.slice(Math.floor(recent.length / 2));
352
+
353
+ const firstHealthy = firstHalf.filter(h => h.healthy).length / firstHalf.length;
354
+ const secondHealthy = secondHalf.filter(h => h.healthy).length / secondHalf.length;
355
+
356
+ return secondHealthy - firstHealthy;
357
+ }
358
+
359
+ /**
360
+ * Execute a health check
361
+ * @private
362
+ * @param {boolean} [immediate=false] - If true, don't schedule next
363
+ * @returns {Promise<HealthCheckResult>} Check result
364
+ */
365
+ async _executeCheck(immediate = false) {
366
+ if (!this.running || (this.paused && !immediate)) {
367
+ return null;
368
+ }
369
+
370
+ const startTime = Date.now();
371
+ let result;
372
+
373
+ try {
374
+ const checkResult = await this._checkFn();
375
+
376
+ // Normalize result
377
+ let healthy, details;
378
+ if (typeof checkResult === 'boolean') {
379
+ healthy = checkResult;
380
+ details = {};
381
+ } else if (checkResult && typeof checkResult === 'object') {
382
+ healthy = checkResult.healthy;
383
+ details = checkResult.details || checkResult;
384
+ } else {
385
+ healthy = !!checkResult;
386
+ details = {};
387
+ }
388
+
389
+ result = {
390
+ healthy,
391
+ timestamp: startTime,
392
+ duration: Date.now() - startTime,
393
+ details,
394
+ };
395
+
396
+ this._recordResult(result);
397
+ } catch (error) {
398
+ result = {
399
+ healthy: false,
400
+ timestamp: startTime,
401
+ duration: Date.now() - startTime,
402
+ error,
403
+ };
404
+
405
+ this._recordResult(result);
406
+ this.emit(MonitorEvents.ERROR, { error, timestamp: startTime });
407
+ this._log(`Check error: ${error.message}`);
408
+ }
409
+
410
+ // Schedule next check unless immediate
411
+ if (!immediate && this.running && !this.paused) {
412
+ this._scheduleNext();
413
+ }
414
+
415
+ return result;
416
+ }
417
+
418
+ /**
419
+ * Record a health check result
420
+ * @private
421
+ * @param {HealthCheckResult} result - Check result
422
+ */
423
+ _recordResult(result) {
424
+ this.lastCheckTime = result.timestamp;
425
+ this.totalChecks++;
426
+
427
+ // Add to history (with size limit)
428
+ this.healthHistory.push(result);
429
+ if (this.healthHistory.length > this.config.maxHistorySize) {
430
+ this.healthHistory.shift();
431
+ }
432
+
433
+ // Update counters
434
+ const previousStatus = this.status;
435
+
436
+ if (result.healthy) {
437
+ this.totalHealthy++;
438
+ this.consecutiveHealthy++;
439
+ this.consecutiveUnhealthy = 0;
440
+
441
+ // Check for stable state
442
+ if (this.consecutiveHealthy >= this.config.stableThreshold) {
443
+ if (this.status !== HealthStatus.STABLE) {
444
+ this.status = HealthStatus.STABLE;
445
+ this.emit(MonitorEvents.STABLE, { checks: this.consecutiveHealthy });
446
+ }
447
+ } else if (this.status === HealthStatus.CRITICAL || this.status === HealthStatus.UNHEALTHY) {
448
+ this.status = HealthStatus.RECOVERING;
449
+ this.emit(MonitorEvents.RECOVERY, { timestamp: Date.now() });
450
+ } else {
451
+ this.status = HealthStatus.HEALTHY;
452
+ }
453
+ } else {
454
+ this.totalUnhealthy++;
455
+ this.consecutiveUnhealthy++;
456
+ this.consecutiveHealthy = 0;
457
+
458
+ // Check for critical state
459
+ if (this.consecutiveUnhealthy >= this.config.criticalThreshold) {
460
+ if (this.status !== HealthStatus.CRITICAL) {
461
+ this.status = HealthStatus.CRITICAL;
462
+ this.emit(MonitorEvents.CRITICAL, {
463
+ checks: this.consecutiveUnhealthy,
464
+ details: result.details || result.error,
465
+ });
466
+ }
467
+ } else {
468
+ this.status = HealthStatus.UNHEALTHY;
469
+ }
470
+ }
471
+
472
+ // Emit status change if changed
473
+ if (previousStatus !== this.status) {
474
+ this.emit(MonitorEvents.STATUS_CHANGE, {
475
+ previous: previousStatus,
476
+ current: this.status,
477
+ timestamp: Date.now(),
478
+ });
479
+ }
480
+
481
+ // Emit health event
482
+ this.emit(MonitorEvents.HEALTH, result);
483
+
484
+ // Adjust interval
485
+ this._adjustInterval(result.healthy);
486
+ }
487
+
488
+ /**
489
+ * Adjust the monitoring interval based on health
490
+ * @private
491
+ * @param {boolean} isHealthy - Whether the last check was healthy
492
+ */
493
+ _adjustInterval(isHealthy) {
494
+ const previousInterval = this.currentInterval;
495
+
496
+ if (isHealthy) {
497
+ // Healthy: exponential backoff (increase interval)
498
+ this.currentInterval = Math.min(
499
+ this.currentInterval * this.config.healthyBackoffFactor,
500
+ this.config.maxInterval
501
+ );
502
+ } else {
503
+ // Unhealthy: rush (decrease interval)
504
+ this.currentInterval = Math.max(
505
+ this.currentInterval * this.config.unhealthyRushFactor,
506
+ this.config.minInterval
507
+ );
508
+ }
509
+
510
+ // Round to nearest 100ms for cleaner values
511
+ this.currentInterval = Math.round(this.currentInterval / 100) * 100;
512
+
513
+ // Emit if interval changed significantly (>10%)
514
+ if (Math.abs(this.currentInterval - previousInterval) / previousInterval > 0.1) {
515
+ this._log(`Interval adjusted: ${previousInterval}ms -> ${this.currentInterval}ms`);
516
+ this.emit(MonitorEvents.INTERVAL_CHANGE, {
517
+ previous: previousInterval,
518
+ current: this.currentInterval,
519
+ healthy: isHealthy,
520
+ });
521
+ }
522
+ }
523
+
524
+ /**
525
+ * Schedule the next health check
526
+ * @private
527
+ */
528
+ _scheduleNext() {
529
+ if (this._timeoutId) {
530
+ clearTimeout(this._timeoutId);
531
+ }
532
+
533
+ this.nextCheckTime = Date.now() + this.currentInterval;
534
+ this._timeoutId = setTimeout(this._executeCheck, this.currentInterval);
535
+
536
+ // Don't block process exit
537
+ if (this._timeoutId.unref) {
538
+ this._timeoutId.unref();
539
+ }
540
+ }
541
+
542
+ /**
543
+ * Log a message if verbose mode is enabled
544
+ * @private
545
+ * @param {string} message - Message to log
546
+ */
547
+ _log(message) {
548
+ if (this.config.verbose) {
549
+ console.log(`[AdaptiveMonitor] ${message}`);
550
+ }
551
+ }
552
+ }
553
+
554
+ /**
555
+ * Create an adaptive monitor with default configuration
556
+ * @param {Object} [config] - Configuration overrides
557
+ * @returns {AdaptiveMonitor} New monitor instance
558
+ */
559
+ export function createAdaptiveMonitor(config = {}) {
560
+ return new AdaptiveMonitor(config);
561
+ }
562
+
563
+ /**
564
+ * Create a memory usage monitor
565
+ * @param {number} [threshold=0.8] - Memory usage threshold (0-1)
566
+ * @param {Object} [config] - Additional config
567
+ * @returns {AdaptiveMonitor} Configured monitor
568
+ */
569
+ export function createMemoryMonitor(threshold = 0.8, config = {}) {
570
+ const monitor = new AdaptiveMonitor({
571
+ baseInterval: 30000,
572
+ minInterval: 5000,
573
+ maxInterval: 120000,
574
+ ...config,
575
+ });
576
+
577
+ const checkFn = async () => {
578
+ const usage = process.memoryUsage();
579
+ const heapUsage = usage.heapUsed / usage.heapTotal;
580
+
581
+ return {
582
+ healthy: heapUsage < threshold,
583
+ details: {
584
+ heapUsed: usage.heapUsed,
585
+ heapTotal: usage.heapTotal,
586
+ heapUsage: `${(heapUsage * 100).toFixed(1)}%`,
587
+ external: usage.external,
588
+ rss: usage.rss,
589
+ },
590
+ };
591
+ };
592
+
593
+ return { monitor, start: () => monitor.start(checkFn) };
594
+ }
595
+
596
+ /**
597
+ * Create an event loop monitor
598
+ * @param {number} [lagThreshold=100] - Event loop lag threshold in ms
599
+ * @param {Object} [config] - Additional config
600
+ * @returns {AdaptiveMonitor} Configured monitor
601
+ */
602
+ export function createEventLoopMonitor(lagThreshold = 100, config = {}) {
603
+ const monitor = new AdaptiveMonitor({
604
+ baseInterval: 10000,
605
+ minInterval: 1000,
606
+ maxInterval: 60000,
607
+ ...config,
608
+ });
609
+
610
+ const checkFn = async () => {
611
+ const start = Date.now();
612
+ await new Promise(resolve => setImmediate(resolve));
613
+ const lag = Date.now() - start;
614
+
615
+ return {
616
+ healthy: lag < lagThreshold,
617
+ details: {
618
+ lag,
619
+ lagThreshold,
620
+ },
621
+ };
622
+ };
623
+
624
+ return { monitor, start: () => monitor.start(checkFn) };
625
+ }
626
+
627
+ /**
628
+ * Monitor orchestrator for managing multiple monitors
629
+ */
630
+ export class MonitorOrchestrator extends EventEmitter {
631
+ /**
632
+ * Create a monitor orchestrator
633
+ */
634
+ constructor() {
635
+ super();
636
+ this.monitors = new Map();
637
+ this.aggregatedStatus = HealthStatus.UNKNOWN;
638
+ }
639
+
640
+ /**
641
+ * Add a monitor
642
+ * @param {string} name - Monitor name
643
+ * @param {AdaptiveMonitor} monitor - Monitor instance
644
+ * @returns {MonitorOrchestrator} this for chaining
645
+ */
646
+ add(name, monitor) {
647
+ this.monitors.set(name, monitor);
648
+
649
+ // Forward events
650
+ monitor.on(MonitorEvents.HEALTH, result => {
651
+ this.emit(`${name}:health`, result);
652
+ this._updateAggregatedStatus();
653
+ });
654
+
655
+ monitor.on(MonitorEvents.CRITICAL, data => {
656
+ this.emit(`${name}:critical`, data);
657
+ this.emit('critical', { monitor: name, ...data });
658
+ });
659
+
660
+ return this;
661
+ }
662
+
663
+ /**
664
+ * Remove a monitor
665
+ * @param {string} name - Monitor name
666
+ * @returns {boolean} True if removed
667
+ */
668
+ remove(name) {
669
+ const monitor = this.monitors.get(name);
670
+ if (monitor) {
671
+ monitor.stop();
672
+ this.monitors.delete(name);
673
+ return true;
674
+ }
675
+ return false;
676
+ }
677
+
678
+ /**
679
+ * Start all monitors
680
+ */
681
+ startAll() {
682
+ for (const monitor of this.monitors.values()) {
683
+ if (!monitor.running) {
684
+ // Monitor should have been started with checkFn via .start()
685
+ // This just ensures running state
686
+ }
687
+ }
688
+ }
689
+
690
+ /**
691
+ * Stop all monitors
692
+ */
693
+ stopAll() {
694
+ for (const monitor of this.monitors.values()) {
695
+ monitor.stop();
696
+ }
697
+ }
698
+
699
+ /**
700
+ * Get aggregated statistics
701
+ * @returns {Object} Aggregated stats
702
+ */
703
+ getAggregatedStats() {
704
+ const stats = {};
705
+ for (const [name, monitor] of this.monitors.entries()) {
706
+ stats[name] = monitor.getStats();
707
+ }
708
+ return {
709
+ monitors: stats,
710
+ aggregatedStatus: this.aggregatedStatus,
711
+ monitorCount: this.monitors.size,
712
+ };
713
+ }
714
+
715
+ /**
716
+ * Update aggregated status based on all monitors
717
+ * @private
718
+ */
719
+ _updateAggregatedStatus() {
720
+ const statuses = Array.from(this.monitors.values()).map(m => m.status);
721
+
722
+ if (statuses.includes(HealthStatus.CRITICAL)) {
723
+ this.aggregatedStatus = HealthStatus.CRITICAL;
724
+ } else if (statuses.includes(HealthStatus.UNHEALTHY)) {
725
+ this.aggregatedStatus = HealthStatus.UNHEALTHY;
726
+ } else if (statuses.every(s => s === HealthStatus.STABLE)) {
727
+ this.aggregatedStatus = HealthStatus.STABLE;
728
+ } else if (statuses.includes(HealthStatus.RECOVERING)) {
729
+ this.aggregatedStatus = HealthStatus.RECOVERING;
730
+ } else if (statuses.every(s => s === HealthStatus.HEALTHY || s === HealthStatus.STABLE)) {
731
+ this.aggregatedStatus = HealthStatus.HEALTHY;
732
+ } else {
733
+ this.aggregatedStatus = HealthStatus.UNKNOWN;
734
+ }
735
+ }
736
+ }
737
+
738
+ /**
739
+ * Create a monitor orchestrator
740
+ * @returns {MonitorOrchestrator} New orchestrator
741
+ */
742
+ export function createMonitorOrchestrator() {
743
+ return new MonitorOrchestrator();
744
+ }
745
+
746
+ export default AdaptiveMonitor;