@devilsdev/rag-pipeline-utils 2.3.1 → 2.4.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 (160) hide show
  1. package/README.md +94 -480
  2. package/contracts/loader-contract.json +11 -6
  3. package/contracts/reranker-contract.json +18 -26
  4. package/dist/index.cjs +21190 -47
  5. package/dist/index.d.ts +298 -68
  6. package/dist/index.mjs +9 -76
  7. package/package.json +37 -11
  8. package/src/agentic/agentic-pipeline.js +235 -0
  9. package/src/agentic/iterative-retriever.js +213 -0
  10. package/src/agentic/query-planner.js +208 -0
  11. package/src/agentic/self-critique.js +102 -0
  12. package/src/ai/adaptive-retrieval.js +0 -3
  13. package/src/ai/federated-learning.js +0 -3
  14. package/src/ai/federation.js +1 -6
  15. package/src/ai/index.js +9 -20
  16. package/src/ai/model-training.js +0 -3
  17. package/src/ai/multimodal-processing.js +0 -3
  18. package/src/ai/multimodal.js +1 -6
  19. package/src/ai/orchestrator.js +1 -6
  20. package/src/ai/retrieval-engine.js +1 -6
  21. package/src/chunking/chunking-engine.js +142 -0
  22. package/src/chunking/strategies/fixed-size.js +54 -0
  23. package/src/chunking/strategies/recursive.js +127 -0
  24. package/src/chunking/strategies/semantic.js +128 -0
  25. package/src/chunking/strategies/sentence.js +32 -0
  26. package/src/chunking/strategies/structure-aware.js +138 -0
  27. package/src/citation/citation-tracker.js +161 -0
  28. package/src/citation/hallucination-detector.js +74 -0
  29. package/src/citation/source-mapper.js +95 -0
  30. package/src/cli/commands/ai-ml/adaptive-commands.js +178 -0
  31. package/src/cli/commands/ai-ml/federated-commands.js +207 -0
  32. package/src/cli/commands/ai-ml/index.js +17 -0
  33. package/src/cli/commands/ai-ml/multimodal-commands.js +187 -0
  34. package/src/cli/commands/ai-ml/shared.js +51 -0
  35. package/src/cli/commands/ai-ml/training-commands.js +228 -0
  36. package/src/cli/commands/ai-ml.js +176 -757
  37. package/src/cli/commands/plugin-hub/certification-commands.js +221 -0
  38. package/src/cli/commands/plugin-hub/event-handlers.js +64 -0
  39. package/src/cli/commands/plugin-hub/hub-commands.js +290 -0
  40. package/src/cli/commands/plugin-hub/review-commands.js +78 -0
  41. package/src/cli/commands/plugin-hub.js +106 -782
  42. package/src/cli/diagnostics/config-checker.js +81 -0
  43. package/src/cli/diagnostics/dependency-checker.js +69 -0
  44. package/src/cli/diagnostics/environment-checker.js +45 -0
  45. package/src/cli/diagnostics/performance-checker.js +51 -0
  46. package/src/cli/diagnostics/plugin-checker.js +106 -0
  47. package/src/cli/diagnostics/security-checker.js +61 -0
  48. package/src/cli/doctor-command.js +70 -369
  49. package/src/cli/enhanced-cli-commands.js +162 -706
  50. package/src/cli/handlers/config-handler.js +126 -0
  51. package/src/cli/handlers/diagnostics-handler.js +113 -0
  52. package/src/cli/handlers/ingest-handler.js +42 -0
  53. package/src/cli/handlers/init-handler.js +51 -0
  54. package/src/cli/handlers/query-handler.js +45 -0
  55. package/src/cli/handlers/run-handler.js +181 -0
  56. package/src/cli/interactive-wizard.js +99 -723
  57. package/src/cli/shared/cli-utils.js +89 -0
  58. package/src/cli/wizard/configuration.js +148 -0
  59. package/src/cli/wizard/observability.js +79 -0
  60. package/src/cli/wizard/performance.js +76 -0
  61. package/src/cli/wizard/plugin-selection.js +297 -0
  62. package/src/cli/wizard/preview-save.js +172 -0
  63. package/src/cli/wizard/project-setup.js +66 -0
  64. package/src/config/memory-optimization.js +3 -3
  65. package/src/connectors/anthropic-connector.js +179 -0
  66. package/src/connectors/base-connector.js +94 -0
  67. package/src/connectors/cohere-connector.js +143 -0
  68. package/src/connectors/local-embedder.js +171 -0
  69. package/src/connectors/memory-retriever.js +150 -0
  70. package/src/connectors/ollama-connector.js +205 -0
  71. package/src/connectors/openai-connector.js +173 -0
  72. package/src/core/create-pipeline.js +21 -1
  73. package/src/core/observability/tracing.js +2 -1
  74. package/src/core/pipeline-factory.js +0 -1
  75. package/src/core/plugin-registry.js +14 -14
  76. package/src/cost/cost-calculator.js +146 -0
  77. package/src/cost/cost-tracker.js +260 -0
  78. package/src/cost/token-budget.js +245 -0
  79. package/src/dag/core/execution-scheduler.js +2 -1
  80. package/src/dag/core/topology-validation.js +0 -3
  81. package/src/dag/dag-engine.js +3 -5
  82. package/src/dag/dag-node.js +0 -1
  83. package/src/dag/enhanced-dag-engine.js +171 -99
  84. package/src/dashboard/dashboard-generator.js +642 -0
  85. package/src/dashboard/metrics-aggregator.js +409 -0
  86. package/src/debug/execution-tracer.js +210 -0
  87. package/src/debug/trace-inspector.js +281 -0
  88. package/src/dev/dev-server.js +5 -5
  89. package/src/dev/hot-reload.js +3 -2
  90. package/src/dx/integration-templates.js +0 -3
  91. package/src/ecosystem/plugin-analytics-dashboard.js +2 -2
  92. package/src/ecosystem/plugin-hub.js +2 -2
  93. package/src/enterprise/audit-logging.js +2 -2
  94. package/src/enterprise/sso-integration.js +52 -32
  95. package/src/evaluate/context-metrics.js +127 -0
  96. package/src/evaluate/evaluator.js +4 -2
  97. package/src/evaluate/faithfulness.js +117 -0
  98. package/src/evaluate/groundedness.js +60 -0
  99. package/src/evaluate/pipeline-evaluator.js +156 -0
  100. package/src/evaluate/relevance.js +129 -0
  101. package/src/evaluate/scoring.js +27 -0
  102. package/src/graph/entity-extractor.js +183 -0
  103. package/src/graph/graph-index.js +113 -0
  104. package/src/graph/graph-retriever.js +244 -0
  105. package/src/graph/knowledge-graph.js +256 -0
  106. package/src/guardrails/guardrails-pipeline.js +136 -0
  107. package/src/guardrails/post-generation-guard.js +170 -0
  108. package/src/guardrails/pre-retrieval-guard.js +161 -0
  109. package/src/guardrails/retrieval-guard.js +126 -0
  110. package/src/index.js +255 -9
  111. package/src/loader/html-loader.js +4 -15
  112. package/src/loader/markdown-loader.js +4 -15
  113. package/src/mcp/mcp-server.js +199 -0
  114. package/src/mcp/mcp-tool-builder.js +121 -0
  115. package/src/observability/audit-logger.js +0 -2
  116. package/src/observability/event-logger.js +2 -1
  117. package/src/observability/security-monitor.js +0 -2
  118. package/src/observability/telemetry.js +0 -2
  119. package/src/reranker/cascade-reranker.js +79 -0
  120. package/src/reranker/embedding-reranker.js +124 -0
  121. package/src/reranker/llm-reranker.js +6 -3
  122. package/src/reranker/scoring-reranker.js +123 -0
  123. package/src/retrieval/bm25-search.js +157 -0
  124. package/src/retrieval/hybrid-retriever.js +111 -0
  125. package/src/retrieval/rank-fusion.js +62 -0
  126. package/src/security/jwt-config.js +4 -3
  127. package/src/security/jwt-validator.js +2 -1
  128. package/src/security/plugin-sandbox.js +0 -3
  129. package/src/security/plugin-signature-verifier.js +4 -6
  130. package/src/security/validator.js +0 -2
  131. package/src/streaming/embedding-stream.js +197 -0
  132. package/src/streaming/sse-adapter.js +168 -0
  133. package/src/streaming/stream-router.js +210 -0
  134. package/src/streaming/streaming-embedder.js +231 -0
  135. package/src/streaming/websocket-adapter.js +171 -0
  136. package/src/templates/scaffold.js +147 -0
  137. package/src/templates/template-registry.js +272 -0
  138. package/src/utils/contract-validator.js +2 -1
  139. package/src/utils/input-sanitizer.js +80 -99
  140. package/src/utils/performance-monitor.js +0 -3
  141. package/src/utils/plugin-scaffolder.js +9 -16
  142. package/src/utils/rate-limiter.js +0 -1
  143. package/src/utils/retry-policy.js +0 -3
  144. package/src/utils/safe-expression-evaluator.js +0 -3
  145. package/src/utils/secure-logger-config.js +2 -4
  146. package/src/utils/secure-logging.js +0 -3
  147. package/src/utils/structured-logger.js +0 -3
  148. package/src/ai/index-fixed.js +0 -509
  149. package/src/ai/module-template.js +0 -37
  150. package/src/ci/workflow-analyzer.js +0 -71
  151. package/src/cli/plugin-marketplace-commands.js +0 -869
  152. package/src/dev/README.md +0 -126
  153. package/src/dev/examples/hot-reload-usage.js +0 -355
  154. package/src/mocks/openai-embedder.js +0 -119
  155. package/src/mocks/openai-llm.js +0 -77
  156. package/src/mocks/pdf-loader.js +0 -21
  157. package/src/mocks/pinecone-retriever.js +0 -106
  158. package/src/scripts/script-utilities.js +0 -36
  159. package/src/utils/ci/diagnostic-reporter.js +0 -57
  160. package/src/utils/examples/error-formatter-usage.js +0 -351
@@ -0,0 +1,245 @@
1
+ 'use strict';
2
+
3
+ const { EventEmitter } = require('events');
4
+
5
+ /**
6
+ * Default budget configuration.
7
+ * @type {{maxTokens: number, maxCost: number, softLimitPercent: number, trackingWindow: number}}
8
+ */
9
+ const DEFAULT_CONFIG = {
10
+ maxTokens: Infinity,
11
+ maxCost: Infinity,
12
+ softLimitPercent: 0.8, // warn at 80% of limit
13
+ trackingWindow: 0, // 0 = no windowing, else ms for sliding window
14
+ };
15
+
16
+ /**
17
+ * Configurable token and cost budgets with hard and soft limits.
18
+ *
19
+ * Emits:
20
+ * - `usage` — after every {@link record} call with the recorded entry.
21
+ * - `warning` — when usage crosses the soft limit threshold.
22
+ * - `exceeded` — when usage crosses a hard limit.
23
+ *
24
+ * @example
25
+ * const budget = new TokenBudget({ maxTokens: 100000, maxCost: 5.0 });
26
+ * budget.on('warning', (msg) => console.warn(msg));
27
+ * const status = budget.check(2000, 0.10);
28
+ * if (status.allowed) budget.record(2000, 0.10);
29
+ */
30
+ class TokenBudget extends EventEmitter {
31
+ /**
32
+ * @param {object} [options]
33
+ * @param {number} [options.maxTokens=Infinity] - Hard token limit.
34
+ * @param {number} [options.maxCost=Infinity] - Hard cost limit (USD).
35
+ * @param {number} [options.softLimitPercent=0.8] - Fraction (0-1) at which soft-limit warnings fire.
36
+ * @param {number} [options.trackingWindow=0] - Sliding window in ms (0 disables).
37
+ */
38
+ constructor(options = {}) {
39
+ super();
40
+ this.config = { ...DEFAULT_CONFIG, ...options };
41
+ this.usage = { tokens: 0, cost: 0, operations: 0, history: [] };
42
+ }
43
+
44
+ /**
45
+ * Prune history entries outside the sliding window.
46
+ * @private
47
+ */
48
+ _pruneWindow() {
49
+ if (this.config.trackingWindow <= 0) return;
50
+
51
+ const cutoff = Date.now() - this.config.trackingWindow;
52
+ this.usage.history = this.usage.history.filter(
53
+ (h) => h.timestamp >= cutoff,
54
+ );
55
+
56
+ // Recompute totals from remaining history
57
+ this.usage.tokens = this.usage.history.reduce(
58
+ (sum, h) => sum + h.tokens,
59
+ 0,
60
+ );
61
+ this.usage.cost = this.usage.history.reduce((sum, h) => sum + h.cost, 0);
62
+ this.usage.operations = this.usage.history.length;
63
+ }
64
+
65
+ /**
66
+ * Check whether a prospective usage fits within the budget.
67
+ *
68
+ * @param {number} [tokens=0] - Tokens about to be consumed.
69
+ * @param {number} [cost=0] - Cost about to be incurred.
70
+ * @returns {{allowed: boolean, reason?: string, remaining: {tokens: number, cost: number},
71
+ * warnings: string[], usage: {tokens: number, cost: number, operations: number}}}
72
+ */
73
+ check(tokens = 0, cost = 0) {
74
+ this._pruneWindow();
75
+
76
+ const projectedTokens = this.usage.tokens + tokens;
77
+ const projectedCost = this.usage.cost + cost;
78
+ const warnings = [];
79
+
80
+ // Hard-limit checks
81
+ if (projectedTokens > this.config.maxTokens) {
82
+ return {
83
+ allowed: false,
84
+ reason: `Token limit exceeded: ${projectedTokens} > ${this.config.maxTokens}`,
85
+ remaining: {
86
+ tokens: Math.max(0, this.config.maxTokens - this.usage.tokens),
87
+ cost: Math.max(0, this.config.maxCost - this.usage.cost),
88
+ },
89
+ warnings,
90
+ usage: {
91
+ tokens: this.usage.tokens,
92
+ cost: this.usage.cost,
93
+ operations: this.usage.operations,
94
+ },
95
+ };
96
+ }
97
+
98
+ if (projectedCost > this.config.maxCost) {
99
+ return {
100
+ allowed: false,
101
+ reason: `Cost limit exceeded: ${projectedCost.toFixed(4)} > ${this.config.maxCost}`,
102
+ remaining: {
103
+ tokens: Math.max(0, this.config.maxTokens - this.usage.tokens),
104
+ cost: Math.max(0, this.config.maxCost - this.usage.cost),
105
+ },
106
+ warnings,
107
+ usage: {
108
+ tokens: this.usage.tokens,
109
+ cost: this.usage.cost,
110
+ operations: this.usage.operations,
111
+ },
112
+ };
113
+ }
114
+
115
+ // Soft-limit checks
116
+ const softTokens = this.config.maxTokens * this.config.softLimitPercent;
117
+ const softCost = this.config.maxCost * this.config.softLimitPercent;
118
+
119
+ if (isFinite(this.config.maxTokens) && projectedTokens > softTokens) {
120
+ warnings.push(
121
+ `Approaching token limit: ${projectedTokens} / ${this.config.maxTokens} (${((projectedTokens / this.config.maxTokens) * 100).toFixed(1)}%)`,
122
+ );
123
+ }
124
+
125
+ if (isFinite(this.config.maxCost) && projectedCost > softCost) {
126
+ warnings.push(
127
+ `Approaching cost limit: $${projectedCost.toFixed(4)} / $${this.config.maxCost} (${((projectedCost / this.config.maxCost) * 100).toFixed(1)}%)`,
128
+ );
129
+ }
130
+
131
+ return {
132
+ allowed: true,
133
+ remaining: {
134
+ tokens: isFinite(this.config.maxTokens)
135
+ ? this.config.maxTokens - projectedTokens
136
+ : Infinity,
137
+ cost: isFinite(this.config.maxCost)
138
+ ? this.config.maxCost - projectedCost
139
+ : Infinity,
140
+ },
141
+ warnings,
142
+ usage: {
143
+ tokens: this.usage.tokens,
144
+ cost: this.usage.cost,
145
+ operations: this.usage.operations,
146
+ },
147
+ };
148
+ }
149
+
150
+ /**
151
+ * Record actual token and cost usage.
152
+ *
153
+ * @param {number} tokens - Tokens consumed.
154
+ * @param {number} [cost=0] - Cost incurred (USD).
155
+ */
156
+ record(tokens, cost = 0) {
157
+ const entry = { tokens, cost, timestamp: Date.now() };
158
+ this.usage.history.push(entry);
159
+ this.usage.tokens += tokens;
160
+ this.usage.cost += cost;
161
+ this.usage.operations += 1;
162
+
163
+ this.emit('usage', entry);
164
+
165
+ // Soft-limit warnings
166
+ if (isFinite(this.config.maxTokens)) {
167
+ const softTokens = this.config.maxTokens * this.config.softLimitPercent;
168
+ if (
169
+ this.usage.tokens > softTokens &&
170
+ this.usage.tokens <= this.config.maxTokens
171
+ ) {
172
+ this.emit(
173
+ 'warning',
174
+ `Token usage at ${((this.usage.tokens / this.config.maxTokens) * 100).toFixed(1)}% of limit`,
175
+ );
176
+ }
177
+ }
178
+
179
+ if (isFinite(this.config.maxCost)) {
180
+ const softCost = this.config.maxCost * this.config.softLimitPercent;
181
+ if (
182
+ this.usage.cost > softCost &&
183
+ this.usage.cost <= this.config.maxCost
184
+ ) {
185
+ this.emit(
186
+ 'warning',
187
+ `Cost usage at ${((this.usage.cost / this.config.maxCost) * 100).toFixed(1)}% of limit`,
188
+ );
189
+ }
190
+ }
191
+
192
+ // Hard-limit exceeded
193
+ if (this.usage.tokens > this.config.maxTokens) {
194
+ this.emit(
195
+ 'exceeded',
196
+ `Token limit exceeded: ${this.usage.tokens} > ${this.config.maxTokens}`,
197
+ );
198
+ }
199
+
200
+ if (this.usage.cost > this.config.maxCost) {
201
+ this.emit(
202
+ 'exceeded',
203
+ `Cost limit exceeded: ${this.usage.cost.toFixed(4)} > ${this.config.maxCost}`,
204
+ );
205
+ }
206
+ }
207
+
208
+ /**
209
+ * Reset all usage counters to zero.
210
+ */
211
+ reset() {
212
+ this.usage = { tokens: 0, cost: 0, operations: 0, history: [] };
213
+ }
214
+
215
+ /**
216
+ * Return current usage summary.
217
+ *
218
+ * @returns {{tokens: number, cost: number, operations: number,
219
+ * limits: {maxTokens: number, maxCost: number},
220
+ * remaining: {tokens: number, cost: number}}}
221
+ */
222
+ getUsage() {
223
+ this._pruneWindow();
224
+
225
+ return {
226
+ tokens: this.usage.tokens,
227
+ cost: this.usage.cost,
228
+ operations: this.usage.operations,
229
+ limits: {
230
+ maxTokens: this.config.maxTokens,
231
+ maxCost: this.config.maxCost,
232
+ },
233
+ remaining: {
234
+ tokens: isFinite(this.config.maxTokens)
235
+ ? Math.max(0, this.config.maxTokens - this.usage.tokens)
236
+ : Infinity,
237
+ cost: isFinite(this.config.maxCost)
238
+ ? Math.max(0, this.config.maxCost - this.usage.cost)
239
+ : Infinity,
240
+ },
241
+ };
242
+ }
243
+ }
244
+
245
+ module.exports = { TokenBudget, DEFAULT_CONFIG };
@@ -14,6 +14,7 @@
14
14
  'use strict';
15
15
 
16
16
  const ErrorContext = require('./error-context.js');
17
+ const { logger } = require('../../utils/logger');
17
18
 
18
19
  /**
19
20
  * ExecutionScheduler class for managing DAG node execution
@@ -251,7 +252,7 @@ class ExecutionScheduler {
251
252
 
252
253
  // Handle non-critical failures with warnings and no result nullification
253
254
  if (isNonCritical || (continueOnError && !requiredIds.has(node.id))) {
254
- console.warn(`Non-critical node failure: ${nodeErr.message}`);
255
+ logger.warn(`Non-critical node failure: ${nodeErr.message}`);
255
256
  errors.set(node.id, nodeErr);
256
257
  // Don't set result for failed optional nodes - return special symbol so they don't appear in results
257
258
  return Symbol('failed-optional-node');
@@ -410,6 +410,3 @@ module.exports = {
410
410
  validateDAG,
411
411
  validateTopology,
412
412
  };
413
-
414
- // Default export for convenience
415
- module.exports.default = module.exports;
@@ -15,6 +15,7 @@ const {
15
15
  } = require('./core/topology-validation.js');
16
16
  const ExecutionScheduler = require('./core/execution-scheduler.js');
17
17
  const ErrorContext = require('./core/error-context.js');
18
+ const { logger } = require('../utils/logger');
18
19
 
19
20
  class DAG {
20
21
  constructor() {
@@ -458,14 +459,12 @@ class DAG {
458
459
  continue; // no error recorded yet
459
460
  }
460
461
  // FINAL failure path - warn EXACT string and keep going
461
- // eslint-disable-next-line no-console
462
- console.warn(`Node ${node.id} failed during resume: ${e.message}`);
462
+ logger.warn(`Node ${node.id} failed during resume: ${e.message}`);
463
463
  keepRetrying = false; // Don't throw, just continue
464
464
  }
465
465
  }
466
466
  } catch (error) {
467
- // eslint-disable-next-line no-console
468
- console.warn(`Node ${node.id} failed during resume: ${error.message}`);
467
+ logger.warn(`Node ${node.id} failed during resume: ${error.message}`);
469
468
  continue;
470
469
  }
471
470
  }
@@ -553,4 +552,3 @@ class DAG {
553
552
 
554
553
  // Export classes
555
554
  module.exports = { DAG, DAGNode };
556
- module.exports.default = module.exports;
@@ -73,4 +73,3 @@ class DAGNode {
73
73
  }
74
74
 
75
75
  module.exports = DAGNode;
76
- module.exports.default = module.exports;
@@ -589,108 +589,36 @@ class EnhancedDAGEngine {
589
589
  // Validate DAG before execution
590
590
  this.validate();
591
591
 
592
- // Initialize execution state
592
+ // Initialize execution state and context
593
593
  const results = new Map();
594
594
  const errors = new Map();
595
595
  const order = this.getTopologicalOrder();
596
-
597
- // Create execution context
598
- const context = {
596
+ const context = this._buildExecutionContext(
599
597
  results,
600
598
  errors,
601
599
  seed,
602
- continueOnError: config.continueOnError,
603
- gracefulDegradation: config.gracefulDegradation,
604
- retryFailedNodes: config.retryFailedNodes,
605
- maxRetries: config.maxRetries,
606
- };
607
-
608
- // Execute nodes with concurrency control
609
- const semaphore = new Semaphore(config.concurrency);
610
- const nodePromises = new Map();
611
-
612
- for (const node of order) {
613
- const promise = semaphore.acquire().then(async (release) => {
614
- try {
615
- // Wait for dependencies to complete
616
- const depPromises = node.inputs
617
- .map((dep) => nodePromises.get(dep.id))
618
- .filter(Boolean);
619
-
620
- await Promise.all(depPromises);
621
-
622
- // Execute the node
623
- const result = await this.executeNode(node, context);
624
-
625
- if (
626
- typeof result !== 'symbol' ||
627
- result.description !== 'failed-optional-node'
628
- ) {
629
- results.set(node.id, result);
630
- }
631
-
632
- return result;
633
- } finally {
634
- release();
635
- }
636
- });
637
-
638
- nodePromises.set(node.id, promise);
639
- }
640
-
641
- // Wait for all nodes to complete
642
- const allResults = await Promise.allSettled(
643
- Array.from(nodePromises.values()),
600
+ config,
644
601
  );
645
602
 
603
+ // Schedule and run all nodes with concurrency control
604
+ await this._scheduleNodes(order, context, config);
605
+
646
606
  const executionTime = Date.now() - startTime;
647
607
 
648
608
  // Update engine metrics
649
- if (this.enableMetrics) {
650
- this.engineMetrics.totalExecutions++;
651
- this.engineMetrics.totalExecutionTime += executionTime;
652
- this.engineMetrics.avgExecutionTime =
653
- this.engineMetrics.totalExecutionTime /
654
- this.engineMetrics.totalExecutions;
655
-
656
- if (errors.size === 0) {
657
- this.engineMetrics.successfulExecutions++;
658
- } else {
659
- this.engineMetrics.failedExecutions++;
660
- }
661
- }
609
+ this._recordExecutionMetrics(executionTime, errors.size === 0);
662
610
 
663
- // Create execution summary
664
- const summary = {
611
+ // Build and store execution summary
612
+ const summary = this._buildExecutionSummary(
665
613
  executionId,
666
- success: errors.size === 0,
667
614
  executionTime,
668
- nodesExecuted: results.size,
669
- totalNodes: this.nodes.size,
670
- errors: Array.from(errors.entries()).map(([id, error]) => ({
671
- nodeId: id,
672
- message: error.message,
673
- code: error.code,
674
- })),
675
- };
676
-
677
- // Store execution history
678
- if (this.enableTracing) {
679
- this.executionHistory.push({
680
- ...summary,
681
- timestamp: new Date().toISOString(),
682
- results: Object.fromEntries(results),
683
- });
684
-
685
- // Keep only last 100 executions
686
- if (this.executionHistory.length > 100) {
687
- this.executionHistory.shift();
688
- }
689
- }
615
+ results,
616
+ errors,
617
+ );
618
+ this._storeExecutionHistory(summary, results);
690
619
 
691
620
  this.logger.info('DAG execution completed', summary);
692
621
 
693
- // Return results
694
622
  return {
695
623
  results: Object.fromEntries(results),
696
624
  errors: Object.fromEntries(errors),
@@ -699,21 +627,168 @@ class EnhancedDAGEngine {
699
627
  };
700
628
  } catch (error) {
701
629
  const executionTime = Date.now() - startTime;
630
+ this._recordExecutionFailure(executionId, error, executionTime);
631
+ throw error;
632
+ }
633
+ });
634
+ }
702
635
 
703
- if (this.enableMetrics) {
704
- this.engineMetrics.totalExecutions++;
705
- this.engineMetrics.failedExecutions++;
636
+ /**
637
+ * Build the execution context passed to each node during DAG execution.
638
+ * @param {Map} results - Map to store completed node results
639
+ * @param {Map} errors - Map to store node execution errors
640
+ * @param {any} seed - Initial seed value for source nodes
641
+ * @param {Object} config - Merged execution configuration
642
+ * @returns {ExecutionContext} Execution context object
643
+ * @private
644
+ */
645
+ _buildExecutionContext(results, errors, seed, config) {
646
+ return {
647
+ results,
648
+ errors,
649
+ seed,
650
+ continueOnError: config.continueOnError,
651
+ gracefulDegradation: config.gracefulDegradation,
652
+ retryFailedNodes: config.retryFailedNodes,
653
+ maxRetries: config.maxRetries,
654
+ };
655
+ }
656
+
657
+ /**
658
+ * Schedule and execute all nodes in topological order with concurrency control.
659
+ * Each node waits for its dependencies before executing.
660
+ * @param {EnhancedDAGNode[]} order - Nodes in topological execution order
661
+ * @param {ExecutionContext} context - Execution context with results and errors maps
662
+ * @param {Object} config - Execution configuration including concurrency limit
663
+ * @returns {Promise<void>} Resolves when all nodes have settled
664
+ * @private
665
+ */
666
+ async _scheduleNodes(order, context, config) {
667
+ const { results } = context;
668
+ const semaphore = new Semaphore(config.concurrency);
669
+ const nodePromises = new Map();
670
+
671
+ for (const node of order) {
672
+ const promise = semaphore.acquire().then(async (release) => {
673
+ try {
674
+ // Wait for dependencies to complete
675
+ const depPromises = node.inputs
676
+ .map((dep) => nodePromises.get(dep.id))
677
+ .filter(Boolean);
678
+
679
+ await Promise.all(depPromises);
680
+
681
+ // Execute the node
682
+ const result = await this.executeNode(node, context);
683
+
684
+ if (
685
+ typeof result !== 'symbol' ||
686
+ result.description !== 'failed-optional-node'
687
+ ) {
688
+ results.set(node.id, result);
689
+ }
690
+
691
+ return result;
692
+ } finally {
693
+ release();
706
694
  }
695
+ });
707
696
 
708
- this.logger.error('DAG execution failed', {
709
- executionId,
710
- error: error.message,
711
- executionTime,
712
- stack: error.stack,
713
- });
697
+ nodePromises.set(node.id, promise);
698
+ }
714
699
 
715
- throw error;
716
- }
700
+ await Promise.allSettled(Array.from(nodePromises.values()));
701
+ }
702
+
703
+ /**
704
+ * Record engine-level metrics after a completed execution.
705
+ * @param {number} executionTime - Total execution time in milliseconds
706
+ * @param {boolean} success - Whether the execution completed without errors
707
+ * @private
708
+ */
709
+ _recordExecutionMetrics(executionTime, success) {
710
+ if (!this.enableMetrics) {
711
+ return;
712
+ }
713
+
714
+ this.engineMetrics.totalExecutions++;
715
+ this.engineMetrics.totalExecutionTime += executionTime;
716
+ this.engineMetrics.avgExecutionTime =
717
+ this.engineMetrics.totalExecutionTime /
718
+ this.engineMetrics.totalExecutions;
719
+
720
+ if (success) {
721
+ this.engineMetrics.successfulExecutions++;
722
+ } else {
723
+ this.engineMetrics.failedExecutions++;
724
+ }
725
+ }
726
+
727
+ /**
728
+ * Build a summary object describing the outcome of a DAG execution.
729
+ * @param {string} executionId - Unique execution identifier
730
+ * @param {number} executionTime - Total execution time in milliseconds
731
+ * @param {Map} results - Map of successful node results
732
+ * @param {Map} errors - Map of node execution errors
733
+ * @returns {Object} Execution summary
734
+ * @private
735
+ */
736
+ _buildExecutionSummary(executionId, executionTime, results, errors) {
737
+ return {
738
+ executionId,
739
+ success: errors.size === 0,
740
+ executionTime,
741
+ nodesExecuted: results.size,
742
+ totalNodes: this.nodes.size,
743
+ errors: Array.from(errors.entries()).map(([id, error]) => ({
744
+ nodeId: id,
745
+ message: error.message,
746
+ code: error.code,
747
+ })),
748
+ };
749
+ }
750
+
751
+ /**
752
+ * Store an execution summary in the tracing history buffer.
753
+ * Keeps only the most recent 100 entries.
754
+ * @param {Object} summary - Execution summary to store
755
+ * @param {Map} results - Map of node results to include in history
756
+ * @private
757
+ */
758
+ _storeExecutionHistory(summary, results) {
759
+ if (!this.enableTracing) {
760
+ return;
761
+ }
762
+
763
+ this.executionHistory.push({
764
+ ...summary,
765
+ timestamp: new Date().toISOString(),
766
+ results: Object.fromEntries(results),
767
+ });
768
+
769
+ if (this.executionHistory.length > 100) {
770
+ this.executionHistory.shift();
771
+ }
772
+ }
773
+
774
+ /**
775
+ * Record metrics and log details when a DAG execution fails with an exception.
776
+ * @param {string} executionId - Unique execution identifier
777
+ * @param {Error} error - The error that caused the failure
778
+ * @param {number} executionTime - Time elapsed before the failure
779
+ * @private
780
+ */
781
+ _recordExecutionFailure(executionId, error, executionTime) {
782
+ if (this.enableMetrics) {
783
+ this.engineMetrics.totalExecutions++;
784
+ this.engineMetrics.failedExecutions++;
785
+ }
786
+
787
+ this.logger.error('DAG execution failed', {
788
+ executionId,
789
+ error: error.message,
790
+ executionTime,
791
+ stack: error.stack,
717
792
  });
718
793
  }
719
794
 
@@ -817,6 +892,3 @@ module.exports = {
817
892
  Semaphore,
818
893
  createEnhancedDAG,
819
894
  };
820
-
821
- // CommonJS/ESM interop
822
- module.exports.default = module.exports;