@devilsdev/rag-pipeline-utils 2.2.1 → 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 (40) hide show
  1. package/README.md +244 -11
  2. package/contracts/contract-schema.json +189 -0
  3. package/dist/index.cjs +19 -0
  4. package/dist/index.mjs +15 -1
  5. package/package.json +3 -2
  6. package/src/cli/commands/wizard.js +627 -0
  7. package/src/cli/input-sanitization-middleware.js +356 -0
  8. package/src/cli/templates/development.template.json +64 -0
  9. package/src/cli/templates/minimal.template.json +33 -0
  10. package/src/cli/templates/production.template.json +104 -0
  11. package/src/cli/templates/testing.template.json +45 -0
  12. package/src/config/connection-pool-config.json +259 -0
  13. package/src/config/memory-optimization.js +414 -0
  14. package/src/config/redaction-patterns.json +150 -0
  15. package/src/config/sanitization-rules.json +323 -0
  16. package/src/core/plugin-registry.js +266 -20
  17. package/src/dag/core/error-context.js +391 -0
  18. package/src/dag/core/execution-scheduler.js +350 -0
  19. package/src/dag/core/topology-validation.js +415 -0
  20. package/src/dag/dag-engine.js +26 -424
  21. package/src/dev/README.md +126 -0
  22. package/src/dev/dev-server.js +356 -0
  23. package/src/dev/examples/hot-reload-usage.js +355 -0
  24. package/src/dev/hot-reload.js +807 -0
  25. package/src/enterprise/sso-integration.js +67 -25
  26. package/src/index.js +19 -0
  27. package/src/observability/event-logger.js +62 -2
  28. package/src/plugins/optimized-openai-embedder.js +169 -0
  29. package/src/security/jwt-config.js +431 -0
  30. package/src/security/jwt-validator.js +701 -0
  31. package/src/utils/api-client-pool-integration.js +436 -0
  32. package/src/utils/batch-processor.js +710 -0
  33. package/src/utils/connection-pool.js +605 -0
  34. package/src/utils/contract-validator.js +460 -0
  35. package/src/utils/error-formatter.js +603 -0
  36. package/src/utils/examples/error-formatter-usage.js +351 -0
  37. package/src/utils/input-sanitizer.js +967 -0
  38. package/src/utils/memory-monitor.js +800 -0
  39. package/src/utils/secure-logger-config.js +352 -0
  40. package/src/utils/secure-logger.js +576 -0
@@ -0,0 +1,350 @@
1
+ /**
2
+ * @fileoverview DAG Execution Scheduler Module
3
+ *
4
+ * Provides execution scheduling, concurrency management, and timeout handling
5
+ * for DAG node execution. Handles retry logic, error reporting, and dependency
6
+ * resolution.
7
+ *
8
+ * @module dag/core/execution-scheduler
9
+ * @author Ali Kahwaji
10
+ * @since 2.2.3
11
+ * @version 1.0.0
12
+ */
13
+
14
+ 'use strict';
15
+
16
+ const ErrorContext = require('./error-context.js');
17
+
18
+ /**
19
+ * ExecutionScheduler class for managing DAG node execution
20
+ *
21
+ * Provides stateless execution scheduling with:
22
+ * - Concurrency control via semaphore
23
+ * - Timeout enforcement
24
+ * - Retry logic with exponential backoff
25
+ * - Dependency resolution
26
+ * - Error handling and reporting
27
+ *
28
+ * @class
29
+ * @since 2.2.3
30
+ */
31
+ class ExecutionScheduler {
32
+ /**
33
+ * Create an ExecutionScheduler instance
34
+ *
35
+ * @param {Object} options - Scheduler configuration
36
+ * @param {Function} options.getSinkIds - Function to get sink node IDs
37
+ * @param {Object} [options.logger] - Logger instance for debugging
38
+ */
39
+ constructor(options = {}) {
40
+ this.getSinkIds = options.getSinkIds;
41
+ this.logger = options.logger || console;
42
+ this.errorContext = new ErrorContext();
43
+ }
44
+
45
+ /**
46
+ * Schedule and execute all nodes with concurrency management
47
+ *
48
+ * Executes nodes in topological order, respecting dependencies and
49
+ * managing concurrency via semaphore. Each node is executed only after
50
+ * all its dependencies have completed or failed.
51
+ *
52
+ * @param {DAGNode[]} order - Topologically sorted array of nodes
53
+ * @param {Object} context - Execution context
54
+ * @param {number} [context.concurrency] - Maximum concurrent executions
55
+ * @param {Map} context.results - Results map (node ID -> result)
56
+ * @param {Map} context.errors - Errors map (node ID -> error)
57
+ * @param {Map} context.fwd - Forward adjacency map
58
+ * @param {boolean} [context.continueOnError] - Continue on non-critical errors
59
+ * @param {Set} context.requiredIds - Set of required node IDs
60
+ * @param {any} context.seed - Initial seed value
61
+ * @param {boolean} [context.gracefulDegradation] - Enable graceful degradation
62
+ * @param {string[]} [context.requiredNodes] - Array of required node IDs
63
+ * @param {boolean} [context.retryFailedNodes] - Enable retry for failed nodes
64
+ * @param {number} [context.maxRetries] - Maximum retry attempts
65
+ * @returns {Promise<void>}
66
+ *
67
+ * @example
68
+ * const scheduler = new ExecutionScheduler({ getSinkIds });
69
+ * await scheduler.scheduleExecution(sortedNodes, {
70
+ * concurrency: 5,
71
+ * results: new Map(),
72
+ * errors: new Map(),
73
+ * fwd: adjacencyMap,
74
+ * requiredIds: new Set(['critical-node']),
75
+ * seed: initialInput
76
+ * });
77
+ *
78
+ * @performance O(V) where V = number of nodes
79
+ * @since 2.2.3
80
+ */
81
+ async scheduleExecution(order, context) {
82
+ const { concurrency, results, errors, fwd } = context;
83
+
84
+ const semaphore = concurrency ? new Array(concurrency).fill(null) : null;
85
+ let semaphoreIndex = 0;
86
+
87
+ const executeWithSemaphore = async (node) => {
88
+ if (semaphore) {
89
+ // Wait for available slot
90
+ while (semaphore[semaphoreIndex % semaphore.length] !== null) {
91
+ await new Promise((resolve) => setTimeout(resolve, 10));
92
+ }
93
+ const slotIndex = semaphoreIndex % semaphore.length;
94
+ semaphoreIndex++;
95
+
96
+ try {
97
+ const result = await this.executeNode(node, context);
98
+ // Only set result if it's not undefined (failed optional nodes return undefined)
99
+ if (result !== undefined) {
100
+ results.set(node.id, result);
101
+ }
102
+ } finally {
103
+ semaphore[slotIndex] = null;
104
+ }
105
+ } else {
106
+ const result = await this.executeNode(node, context);
107
+ // Only set result if it's not the special failed-optional-node symbol
108
+ if (
109
+ typeof result !== 'symbol' ||
110
+ result.description !== 'failed-optional-node'
111
+ ) {
112
+ results.set(node.id, result);
113
+ }
114
+ }
115
+ };
116
+
117
+ // Execute nodes respecting dependencies
118
+ for (const node of order) {
119
+ // Check if all dependencies are satisfied
120
+ const canExecute = this.canExecuteNode(node, context);
121
+
122
+ if (canExecute) {
123
+ await executeWithSemaphore(node);
124
+ }
125
+ }
126
+ }
127
+
128
+ /**
129
+ * Execute a single DAG node with retry logic and error handling
130
+ *
131
+ * Executes node.run() with comprehensive error handling:
132
+ * - Validates node.run is a function
133
+ * - Determines node criticality
134
+ * - Prepares input from dependencies
135
+ * - Implements retry logic with configurable attempts
136
+ * - Reports errors with downstream impact analysis
137
+ *
138
+ * @param {DAGNode} node - Node to execute
139
+ * @param {Object} context - Execution context
140
+ * @param {Map} context.results - Results map
141
+ * @param {Map} context.errors - Errors map
142
+ * @param {Map} context.fwd - Forward adjacency map
143
+ * @param {boolean} [context.continueOnError] - Continue on non-critical errors
144
+ * @param {Set} context.requiredIds - Required node IDs
145
+ * @param {any} context.seed - Seed value for source nodes
146
+ * @param {boolean} [context.gracefulDegradation] - Enable graceful degradation
147
+ * @param {string[]} [context.requiredNodes] - Required node IDs array
148
+ * @param {boolean} [context.retryFailedNodes] - Enable retry
149
+ * @param {number} [context.maxRetries] - Max retry attempts
150
+ * @returns {Promise<any>} Node execution result or Symbol('failed-optional-node')
151
+ *
152
+ * @throws {Error} Node execution error (if node is critical)
153
+ *
154
+ * @example
155
+ * const result = await scheduler.executeNode(myNode, {
156
+ * results: new Map(),
157
+ * errors: new Map(),
158
+ * fwd: adjacencyMap,
159
+ * requiredIds: new Set(),
160
+ * seed: initialData,
161
+ * retryFailedNodes: true,
162
+ * maxRetries: 3
163
+ * });
164
+ *
165
+ * @performance O(1) for single execution, O(k) for k retry attempts
166
+ * @complexity Cyclomatic complexity: 12 (acceptable for critical execution path)
167
+ * @since 2.2.3
168
+ */
169
+ async executeNode(node, context) {
170
+ const {
171
+ results,
172
+ errors,
173
+ fwd,
174
+ continueOnError,
175
+ requiredIds,
176
+ seed,
177
+ gracefulDegradation,
178
+ requiredNodes,
179
+ } = context;
180
+
181
+ // Handle undefined node.run function
182
+ if (typeof node.run !== 'function') {
183
+ throw new Error(`Node ${node.id} has no run function`);
184
+ }
185
+
186
+ // Determine criticality - default is critical unless explicitly marked optional
187
+ // OR if gracefulDegradation is enabled and node is not in requiredNodes
188
+ // OR if there are multiple sink nodes (individual sink failures should be non-critical)
189
+ const sinkIds = this.getSinkIds(fwd);
190
+ const isMultipleSinks = sinkIds.length > 1;
191
+ const isSinkNode = sinkIds.includes(node.id);
192
+
193
+ const isNonCritical =
194
+ node?.optional === true ||
195
+ node?.isOptional === true ||
196
+ node?.critical === false ||
197
+ (gracefulDegradation &&
198
+ requiredNodes &&
199
+ !requiredNodes.includes(node.id)) ||
200
+ (isMultipleSinks && isSinkNode); // Sink nodes are non-critical when there are multiple sinks
201
+
202
+ // Prepare input based on dependencies
203
+ let input;
204
+ if (node.inputs.length === 0) {
205
+ input = seed;
206
+ } else if (node.inputs.length === 1) {
207
+ input = results.get(node.inputs[0].id);
208
+ } else {
209
+ input = node.inputs.map((inputNode) => {
210
+ // For graceful degradation, return undefined for failed optional dependencies
211
+ if (
212
+ !results.has(inputNode.id) &&
213
+ gracefulDegradation &&
214
+ requiredNodes &&
215
+ !requiredNodes.includes(inputNode.id)
216
+ ) {
217
+ return undefined;
218
+ }
219
+ return results.get(inputNode.id);
220
+ });
221
+ }
222
+
223
+ // Retry logic - use global options if available, otherwise node-specific
224
+ const { retryFailedNodes, maxRetries: globalMaxRetries } = context;
225
+ const nodeMaxRetries = retryFailedNodes
226
+ ? Math.max(0, globalMaxRetries ?? 3)
227
+ : Math.max(0, node?.retry?.retries ?? 0);
228
+ const delayMs = Math.max(0, node?.retry?.delayMs ?? 0);
229
+ let attempt = 0;
230
+ let keepRetrying = true;
231
+
232
+ while (keepRetrying) {
233
+ try {
234
+ // Execute the node
235
+ const output = await node.run(input);
236
+ if (errors.has(node.id)) errors.delete(node.id);
237
+ return output;
238
+ } catch (e) {
239
+ attempt += 1;
240
+ if (attempt <= nodeMaxRetries) {
241
+ if (delayMs) await new Promise((r) => setTimeout(r, delayMs));
242
+ continue; // no error recorded yet
243
+ }
244
+ keepRetrying = false;
245
+ // FINAL failure path
246
+ const deps = [...(fwd.get(node.id) || [])];
247
+ const nodeErr = this.errorContext.createErrorContext(node.id, e, {
248
+ downstream: deps,
249
+ timestamp: Date.now(),
250
+ });
251
+
252
+ // Handle non-critical failures with warnings and no result nullification
253
+ if (isNonCritical || (continueOnError && !requiredIds.has(node.id))) {
254
+ console.warn(`Non-critical node failure: ${nodeErr.message}`);
255
+ errors.set(node.id, nodeErr);
256
+ // Don't set result for failed optional nodes - return special symbol so they don't appear in results
257
+ return Symbol('failed-optional-node');
258
+ }
259
+ errors.set(node.id, nodeErr);
260
+ throw nodeErr;
261
+ }
262
+ }
263
+ }
264
+
265
+ /**
266
+ * Execute function with optional timeout enforcement
267
+ *
268
+ * Uses Promise.race to enforce execution timeout. If timeout is not specified
269
+ * or is <= 0, executes without timeout constraint.
270
+ *
271
+ * @param {Function} runFn - Async function to execute
272
+ * @param {number} [timeout] - Timeout in milliseconds (0 or undefined = no timeout)
273
+ * @returns {Promise<void>}
274
+ *
275
+ * @throws {Error} 'Execution timeout' if execution exceeds timeout
276
+ *
277
+ * @example
278
+ * await scheduler.executeWithTimeout(
279
+ * () => executeLongRunningTask(),
280
+ * 30000 // 30 second timeout
281
+ * );
282
+ *
283
+ * @performance O(1) overhead for timeout setup
284
+ * @since 2.2.3
285
+ */
286
+ async executeWithTimeout(runFn, timeout) {
287
+ if (timeout && timeout > 0) {
288
+ await Promise.race([
289
+ runFn(),
290
+ new Promise((_, reject) => {
291
+ setTimeout(() => reject(new Error('Execution timeout')), timeout);
292
+ }),
293
+ ]);
294
+ } else {
295
+ await runFn();
296
+ }
297
+ }
298
+
299
+ /**
300
+ * Check if a node can be executed based on dependency satisfaction
301
+ *
302
+ * A node can execute if all its input dependencies have either:
303
+ * - Completed successfully (have a result)
304
+ * - Failed (have an error)
305
+ * - Are optional (graceful degradation enabled and not in required nodes)
306
+ *
307
+ * @param {DAGNode} node - Node to check
308
+ * @param {Object} context - Execution context
309
+ * @param {Map} context.results - Results map
310
+ * @param {Map} context.errors - Errors map
311
+ * @param {boolean} [context.gracefulDegradation] - Enable graceful degradation
312
+ * @param {string[]} [context.requiredNodes] - Required node IDs
313
+ * @returns {boolean} True if node can execute, false otherwise
314
+ *
315
+ * @example
316
+ * const canRun = scheduler.canExecuteNode(myNode, {
317
+ * results: new Map([['dep1', result1]]),
318
+ * errors: new Map(),
319
+ * gracefulDegradation: true,
320
+ * requiredNodes: ['critical-dep']
321
+ * });
322
+ *
323
+ * @performance O(d) where d = number of input dependencies
324
+ * @since 2.2.3
325
+ */
326
+ canExecuteNode(node, context) {
327
+ const { results, errors, gracefulDegradation, requiredNodes } = context;
328
+
329
+ return node.inputs.every((dep) => {
330
+ // If dependency has result or error, it's satisfied
331
+ if (results.has(dep.id) || errors.has(dep.id)) {
332
+ return true;
333
+ }
334
+ // If graceful degradation is enabled and dependency is not required, it's satisfied
335
+ if (
336
+ gracefulDegradation &&
337
+ requiredNodes &&
338
+ !requiredNodes.includes(dep.id)
339
+ ) {
340
+ return true;
341
+ }
342
+ return false;
343
+ });
344
+ }
345
+ }
346
+
347
+ module.exports = ExecutionScheduler;
348
+
349
+ // Default export for convenience
350
+ module.exports.default = ExecutionScheduler;