diff --git a/src/__tests__/processors/defaultProcessor.test.ts b/src/__tests__/processors/defaultProcessor.test.ts new file mode 100644 index 0000000..c168e87 --- /dev/null +++ b/src/__tests__/processors/defaultProcessor.test.ts @@ -0,0 +1,307 @@ +import defaultProcessor, { + DefaultProcessorResult, +} from "../../services/processors/defaultProcessor"; +import { + ExecutionContext, + ExecutionPlan, +} from "../../services/executionEngine"; +import { ProcessorRegistry } from "../../services/processors/ProcessorRegistry"; + +// Mock the ProcessorRegistry +jest.mock("../../services/processors/ProcessorRegistry", () => ({ + ProcessorRegistry: { + getNodeTypeStatus: jest.fn(), + getSimilarTypes: jest.fn(), + }, +})); + +describe("DefaultProcessor", () => { + let mockContext: ExecutionContext; + let mockPlan: ExecutionPlan; + + beforeEach(() => { + mockContext = { + nodeId: "test-node-1", + nodeType: "unsupportedType", + config: {}, + inputs: new Map([["input1", "test data"]]), + }; + + mockPlan = { + id: "test-plan-1", + name: "Test Plan", + nodes: [], + edges: [], + status: "running", + createdAt: new Date(), + updatedAt: new Date(), + }; + + // Reset mocks + jest.clearAllMocks(); + }); + + describe("Unsupported node types", () => { + beforeEach(() => { + (ProcessorRegistry.getNodeTypeStatus as jest.Mock).mockReturnValue({ + supported: false, + deprecated: false, + experimental: false, + status: "unsupported", + }); + (ProcessorRegistry.getSimilarTypes as jest.Mock).mockReturnValue([ + "dataInput", + "dataOutput", + ]); + }); + + it("should process unsupported node types with warnings", async () => { + const result = await defaultProcessor(mockContext, mockPlan); + + expect(result).toMatchObject({ + input: "test data", + output: "test data", + type: "default", + success: true, + warning: + "Node type 'unsupportedType' is not supported. Using fallback processor.", + diagnostics: { + nodeType: "unsupportedType", + status: "unsupported", + similarTypes: ["dataInput", "dataOutput"], + suggestion: + "Consider using one of these supported types: dataInput, dataOutput", + }, + metadata: { + executionTime: expect.any(Number), + timestamp: expect.any(Date), + fallbackUsed: true, + }, + }); + }); + + it("should log warning to console", async () => { + const consoleSpy = jest.spyOn(console, "warn").mockImplementation(); + + await defaultProcessor(mockContext, mockPlan); + + expect(consoleSpy).toHaveBeenCalledWith( + "[DefaultProcessor] Node type 'unsupportedType' is not supported. Using fallback processor.", + expect.objectContaining({ + nodeType: "unsupportedType", + nodeId: "test-node-1", + executionId: "test-plan-1", + suggestion: + "Consider using one of these supported types: dataInput, dataOutput", + similarTypes: ["dataInput", "dataOutput"], + }) + ); + + consoleSpy.mockRestore(); + }); + + it("should handle unsupported types with no similar types", async () => { + (ProcessorRegistry.getSimilarTypes as jest.Mock).mockReturnValue([]); + + const result = await defaultProcessor(mockContext, mockPlan); + + expect(result.diagnostics?.suggestion).toBe( + "No similar node types found. Check the documentation for supported types." + ); + }); + }); + + describe("Deprecated node types", () => { + beforeEach(() => { + (ProcessorRegistry.getNodeTypeStatus as jest.Mock).mockReturnValue({ + supported: true, + deprecated: true, + experimental: false, + status: "deprecated", + }); + (ProcessorRegistry.getSimilarTypes as jest.Mock).mockReturnValue([ + "llmTask", + ]); + }); + + it("should process deprecated node types with warnings", async () => { + const result = await defaultProcessor(mockContext, mockPlan); + + expect(result).toMatchObject({ + warning: + "Node type 'unsupportedType' is deprecated and may be removed in future versions.", + diagnostics: { + status: "deprecated", + suggestion: "Consider migrating to: llmTask", + }, + }); + }); + }); + + describe("Experimental node types", () => { + beforeEach(() => { + (ProcessorRegistry.getNodeTypeStatus as jest.Mock).mockReturnValue({ + supported: true, + deprecated: false, + experimental: true, + status: "experimental", + }); + (ProcessorRegistry.getSimilarTypes as jest.Mock).mockReturnValue([]); + }); + + it("should process experimental node types with warnings", async () => { + const result = await defaultProcessor(mockContext, mockPlan); + + expect(result).toMatchObject({ + warning: + "Node type 'unsupportedType' is experimental and may have limited functionality.", + diagnostics: { + status: "experimental", + suggestion: "Use with caution. API may change in future versions.", + }, + }); + }); + }); + + describe("Supported node types", () => { + beforeEach(() => { + (ProcessorRegistry.getNodeTypeStatus as jest.Mock).mockReturnValue({ + supported: true, + deprecated: false, + experimental: false, + status: "supported", + }); + (ProcessorRegistry.getSimilarTypes as jest.Mock).mockReturnValue([]); + }); + + it("should process supported node types without warnings", async () => { + const result = await defaultProcessor(mockContext, mockPlan); + + expect(result).toMatchObject({ + input: "test data", + output: "test data", + type: "default", + success: true, + warning: undefined, + diagnostics: { + nodeType: "unsupportedType", + status: "supported", + similarTypes: [], + suggestion: undefined, + }, + }); + }); + }); + + describe("Error handling", () => { + it("should handle processing errors gracefully", async () => { + // Mock an error in the processor + const errorContext = { + ...mockContext, + inputs: new Map([["input1", null]]), // This might cause an error + }; + + (ProcessorRegistry.getNodeTypeStatus as jest.Mock).mockReturnValue({ + supported: false, + deprecated: false, + experimental: false, + status: "unsupported", + }); + + const consoleSpy = jest.spyOn(console, "error").mockImplementation(); + + await expect(defaultProcessor(errorContext, mockPlan)).rejects.toThrow( + "Default processing failed for node type 'unsupportedType'" + ); + + expect(consoleSpy).toHaveBeenCalledWith( + "[DefaultProcessor] Processing failed for node type 'unsupportedType':", + expect.objectContaining({ + nodeType: "unsupportedType", + nodeId: "test-node-1", + executionId: "test-plan-1", + }) + ); + + consoleSpy.mockRestore(); + }); + }); + + describe("Input handling", () => { + it("should handle empty inputs", async () => { + const emptyContext = { + ...mockContext, + inputs: new Map(), + }; + + (ProcessorRegistry.getNodeTypeStatus as jest.Mock).mockReturnValue({ + supported: false, + deprecated: false, + experimental: false, + status: "unsupported", + }); + (ProcessorRegistry.getSimilarTypes as jest.Mock).mockReturnValue([]); + + const result = await defaultProcessor(emptyContext, mockPlan); + + expect(result).toMatchObject({ + input: "", + output: "", + success: true, + }); + }); + + it("should handle multiple inputs by taking the last one", async () => { + const multiInputContext = { + ...mockContext, + inputs: new Map([ + ["input1", "first data"], + ["input2", "second data"], + ["input3", "third data"], + ]), + }; + + (ProcessorRegistry.getNodeTypeStatus as jest.Mock).mockReturnValue({ + supported: false, + deprecated: false, + experimental: false, + status: "unsupported", + }); + (ProcessorRegistry.getSimilarTypes as jest.Mock).mockReturnValue([]); + + const result = await defaultProcessor(multiInputContext, mockPlan); + + expect(result).toMatchObject({ + input: "third data", + output: "third data", + }); + }); + }); + + describe("Metadata", () => { + it("should include execution metadata", async () => { + (ProcessorRegistry.getNodeTypeStatus as jest.Mock).mockReturnValue({ + supported: false, + deprecated: false, + experimental: false, + status: "unsupported", + }); + (ProcessorRegistry.getSimilarTypes as jest.Mock).mockReturnValue([]); + + const startTime = Date.now(); + const result = await defaultProcessor(mockContext, mockPlan); + const endTime = Date.now(); + + expect(result.metadata).toMatchObject({ + executionTime: expect.any(Number), + timestamp: expect.any(Date), + fallbackUsed: true, + }); + + expect(result.metadata!.executionTime).toBeGreaterThanOrEqual(0); + expect(result.metadata!.executionTime).toBeLessThanOrEqual( + endTime - startTime + 10 + ); // Allow some margin + }); + }); +}); diff --git a/src/__tests__/utils/nodeTypeDiagnostics.test.ts b/src/__tests__/utils/nodeTypeDiagnostics.test.ts new file mode 100644 index 0000000..e95a7ba --- /dev/null +++ b/src/__tests__/utils/nodeTypeDiagnostics.test.ts @@ -0,0 +1,254 @@ +import { + generateNodeTypeDiagnostics, + getNodeTypeSummary, + suggestAlternatives, + canExecuteWithoutFallback, +} from "../../utils/nodeTypeDiagnostics"; +import { WorkflowStructure } from "../../types"; +import { ProcessorRegistry } from "../../services/processors/ProcessorRegistry"; + +// Mock the ProcessorRegistry +jest.mock("../../services/processors/ProcessorRegistry", () => ({ + ProcessorRegistry: { + getNodeTypeStatus: jest.fn(), + getSimilarTypes: jest.fn(), + }, +})); + +describe("NodeTypeDiagnostics", () => { + let mockWorkflow: WorkflowStructure; + + beforeEach(() => { + mockWorkflow = { + nodes: [ + { id: "node-1", type: "dataInput", label: "Input", config: {} }, + { + id: "node-2", + type: "unsupportedType", + label: "Unsupported", + config: {}, + }, + { + id: "node-3", + type: "deprecatedType", + label: "Deprecated", + config: {}, + }, + { + id: "node-4", + type: "experimentalType", + label: "Experimental", + config: {}, + }, + ], + edges: [], + complexity: "medium", + estimatedExecutionTime: 1000, + }; + + jest.clearAllMocks(); + }); + + describe("generateNodeTypeDiagnostics", () => { + it("should generate comprehensive diagnostics for all node types", () => { + // Mock different statuses for different node types + (ProcessorRegistry.getNodeTypeStatus as jest.Mock) + .mockReturnValueOnce({ status: "supported" }) + .mockReturnValueOnce({ status: "unsupported" }) + .mockReturnValueOnce({ status: "deprecated" }) + .mockReturnValueOnce({ status: "experimental" }); + + (ProcessorRegistry.getSimilarTypes as jest.Mock) + .mockReturnValueOnce([]) + .mockReturnValueOnce(["dataInput", "dataOutput"]) + .mockReturnValueOnce(["llmTask"]) + .mockReturnValueOnce([]); + + const report = generateNodeTypeDiagnostics(mockWorkflow); + + expect(report).toMatchObject({ + totalNodes: 4, + supportedNodes: 1, + deprecatedNodes: 1, + experimentalNodes: 1, + unsupportedNodes: 1, + diagnostics: expect.arrayContaining([ + expect.objectContaining({ + nodeType: "dataInput", + status: "supported", + severity: "info", + }), + expect.objectContaining({ + nodeType: "unsupportedType", + status: "unsupported", + severity: "error", + warning: "Node type 'unsupportedType' is not supported", + }), + expect.objectContaining({ + nodeType: "deprecatedType", + status: "deprecated", + severity: "warning", + warning: "Node type 'deprecatedType' is deprecated", + }), + expect.objectContaining({ + nodeType: "experimentalType", + status: "experimental", + severity: "warning", + warning: "Node type 'experimentalType' is experimental", + }), + ]), + recommendations: expect.arrayContaining([ + "Replace 1 unsupported node type(s) with supported alternatives", + "Migrate 1 deprecated node type(s) to current versions", + "Review 1 experimental node type(s) for stability", + ]), + }); + }); + + it("should handle workflows with only supported nodes", () => { + const supportedWorkflow = { + ...mockWorkflow, + nodes: [ + { id: "node-1", type: "dataInput", label: "Input", config: {} }, + { id: "node-2", type: "llmTask", label: "LLM", config: {} }, + ], + }; + + (ProcessorRegistry.getNodeTypeStatus as jest.Mock).mockReturnValue({ + status: "supported", + }); + (ProcessorRegistry.getSimilarTypes as jest.Mock).mockReturnValue([]); + + const report = generateNodeTypeDiagnostics(supportedWorkflow); + + expect(report).toMatchObject({ + totalNodes: 2, + supportedNodes: 2, + deprecatedNodes: 0, + experimentalNodes: 0, + unsupportedNodes: 0, + recommendations: ["All node types are supported and up-to-date"], + }); + }); + }); + + describe("getNodeTypeSummary", () => { + it("should provide a concise summary of issues", () => { + (ProcessorRegistry.getNodeTypeStatus as jest.Mock) + .mockReturnValueOnce({ status: "supported" }) + .mockReturnValueOnce({ status: "unsupported" }) + .mockReturnValueOnce({ status: "deprecated" }) + .mockReturnValueOnce({ status: "experimental" }); + + (ProcessorRegistry.getSimilarTypes as jest.Mock).mockReturnValue([]); + + const summary = getNodeTypeSummary(mockWorkflow); + + expect(summary).toMatchObject({ + hasIssues: true, + issueCount: 1, + warningCount: 2, + summary: + "1 error(s) - unsupported node types, 2 warning(s) - deprecated/experimental types", + }); + }); + + it("should indicate no issues for supported workflows", () => { + const supportedWorkflow = { + ...mockWorkflow, + nodes: [ + { id: "node-1", type: "dataInput", label: "Input", config: {} }, + ], + }; + + (ProcessorRegistry.getNodeTypeStatus as jest.Mock).mockReturnValue({ + status: "supported", + }); + (ProcessorRegistry.getSimilarTypes as jest.Mock).mockReturnValue([]); + + const summary = getNodeTypeSummary(supportedWorkflow); + + expect(summary).toMatchObject({ + hasIssues: false, + issueCount: 0, + warningCount: 0, + summary: "All node types are supported", + }); + }); + }); + + describe("suggestAlternatives", () => { + it("should suggest alternatives for unsupported types", () => { + (ProcessorRegistry.getSimilarTypes as jest.Mock).mockReturnValue([ + "dataInput", + "dataOutput", + ]); + (ProcessorRegistry.getNodeTypeStatus as jest.Mock).mockReturnValue({ + status: "unsupported", + }); + + const result = suggestAlternatives("unsupportedType"); + + expect(result).toMatchObject({ + alternatives: ["dataInput", "dataOutput"], + reason: "This node type is not supported", + }); + }); + + it("should suggest alternatives for deprecated types", () => { + (ProcessorRegistry.getSimilarTypes as jest.Mock).mockReturnValue([ + "llmTask", + ]); + (ProcessorRegistry.getNodeTypeStatus as jest.Mock).mockReturnValue({ + status: "deprecated", + }); + + const result = suggestAlternatives("deprecatedType"); + + expect(result).toMatchObject({ + alternatives: ["llmTask"], + reason: "This node type is deprecated", + }); + }); + }); + + describe("canExecuteWithoutFallback", () => { + it("should identify workflows that need fallback processing", () => { + (ProcessorRegistry.getNodeTypeStatus as jest.Mock) + .mockReturnValueOnce({ status: "supported" }) + .mockReturnValueOnce({ status: "unsupported" }) + .mockReturnValueOnce({ status: "deprecated" }) + .mockReturnValueOnce({ status: "experimental" }); + + const result = canExecuteWithoutFallback(mockWorkflow); + + expect(result).toMatchObject({ + canExecute: false, + fallbackNodes: ["Unsupported"], + reason: "1 node(s) will use fallback processing", + }); + }); + + it("should identify workflows that can execute without fallback", () => { + const supportedWorkflow = { + ...mockWorkflow, + nodes: [ + { id: "node-1", type: "dataInput", label: "Input", config: {} }, + { id: "node-2", type: "llmTask", label: "LLM", config: {} }, + ], + }; + + (ProcessorRegistry.getNodeTypeStatus as jest.Mock).mockReturnValue({ + status: "supported", + }); + + const result = canExecuteWithoutFallback(supportedWorkflow); + + expect(result).toMatchObject({ + canExecute: true, + fallbackNodes: [], + reason: "All nodes have dedicated processors", + }); + }); + }); +}); diff --git a/src/services/executionEngine.ts b/src/services/executionEngine.ts index a9d8d8c..1c495e4 100644 --- a/src/services/executionEngine.ts +++ b/src/services/executionEngine.ts @@ -831,6 +831,11 @@ export class ExecutionEngine { case "llmTask": return (await import("./processors/llmProcessor")).default; case "databaseQuery": + case "databaseInsert": + case "databaseUpdate": + case "databaseDelete": + case "databaseAggregate": + case "databaseTransaction": return (await import("./processors/databaseProcessor")).default; case "structuredOutput": return (await import("./processors/structuredOutputProcessor")).default; @@ -839,7 +844,9 @@ export class ExecutionEngine { case "similaritySearch": return (await import("./processors/similarityProcessor")).default; default: - console.log(`Using default processor for unknown type: ${nodeType}`); + console.warn( + `Using default processor for unknown/unsupported type: ${nodeType}` + ); return (await import("./processors/defaultProcessor")).default; } } diff --git a/src/services/processors/ProcessorRegistry.ts b/src/services/processors/ProcessorRegistry.ts new file mode 100644 index 0000000..2239b11 --- /dev/null +++ b/src/services/processors/ProcessorRegistry.ts @@ -0,0 +1,174 @@ +/** + * Registry for tracking supported node processors + */ +export class ProcessorRegistry { + private static supportedTypes: Set = new Set([ + "dataInput", + "dataOutput", + "webScraping", + "llmTask", + "databaseQuery", + "databaseInsert", + "databaseUpdate", + "databaseDelete", + "databaseAggregate", + "databaseTransaction", + "structuredOutput", + "embeddingGenerator", + "similaritySearch", + ]); + + private static deprecatedTypes: Set = new Set([ + // Add deprecated types here as they become obsolete + ]); + + private static experimentalTypes: Set = new Set([ + // Add experimental types here + ]); + + /** + * Check if a node type is supported + */ + static isSupported(nodeType: string): boolean { + return this.supportedTypes.has(nodeType); + } + + /** + * Check if a node type is deprecated + */ + static isDeprecated(nodeType: string): boolean { + return this.deprecatedTypes.has(nodeType); + } + + /** + * Check if a node type is experimental + */ + static isExperimental(nodeType: string): boolean { + return this.experimentalTypes.has(nodeType); + } + + /** + * Get all supported node types + */ + static getSupportedTypes(): string[] { + return Array.from(this.supportedTypes); + } + + /** + * Get all deprecated node types + */ + static getDeprecatedTypes(): string[] { + return Array.from(this.deprecatedTypes); + } + + /** + * Get all experimental node types + */ + static getExperimentalTypes(): string[] { + return Array.from(this.experimentalTypes); + } + + /** + * Register a new supported node type + */ + static registerType( + nodeType: string, + options?: { + experimental?: boolean; + deprecated?: boolean; + } + ): void { + this.supportedTypes.add(nodeType); + + if (options?.experimental) { + this.experimentalTypes.add(nodeType); + } + + if (options?.deprecated) { + this.deprecatedTypes.add(nodeType); + } + } + + /** + * Mark a node type as deprecated + */ + static markAsDeprecated(nodeType: string): void { + this.deprecatedTypes.add(nodeType); + } + + /** + * Mark a node type as experimental + */ + static markAsExperimental(nodeType: string): void { + this.experimentalTypes.add(nodeType); + } + + /** + * Get node type status information + */ + static getNodeTypeStatus(nodeType: string): { + supported: boolean; + deprecated: boolean; + experimental: boolean; + status: "supported" | "deprecated" | "experimental" | "unsupported"; + } { + const supported = this.isSupported(nodeType); + const deprecated = this.isDeprecated(nodeType); + const experimental = this.isExperimental(nodeType); + + let status: "supported" | "deprecated" | "experimental" | "unsupported"; + if (!supported) { + status = "unsupported"; + } else if (deprecated) { + status = "deprecated"; + } else if (experimental) { + status = "experimental"; + } else { + status = "supported"; + } + + return { + supported, + deprecated, + experimental, + status, + }; + } + + /** + * Get suggestions for similar node types + */ + static getSimilarTypes(nodeType: string): string[] { + const supportedTypes = this.getSupportedTypes(); + const similarTypes: string[] = []; + + // Simple similarity matching based on common patterns + const patterns = [ + { pattern: /data/i, types: ["dataInput", "dataOutput"] }, + { pattern: /web|scrap|extract/i, types: ["webScraping"] }, + { pattern: /llm|ai|gpt|openai/i, types: ["llmTask"] }, + { + pattern: /db|database|sql/i, + types: [ + "databaseQuery", + "databaseInsert", + "databaseUpdate", + "databaseDelete", + ], + }, + { + pattern: /embed|vector/i, + types: ["embeddingGenerator", "similaritySearch"], + }, + { pattern: /struct|schema|json/i, types: ["structuredOutput"] }, + ]; + + patterns.forEach(({ pattern, types }) => { + if (pattern.test(nodeType)) { + similarTypes.push(...types.filter((type) => this.isSupported(type))); + } + }); + + return [...new Set(similarTypes)]; // Remove duplicates + } +} diff --git a/src/services/processors/README.md b/src/services/processors/README.md new file mode 100644 index 0000000..a29ae7e --- /dev/null +++ b/src/services/processors/README.md @@ -0,0 +1,274 @@ +# Processor System with Enhanced Diagnostics + +This directory contains the enhanced processor system with comprehensive diagnostics for unsupported node types, improved error handling, and better developer experience. + +## Overview + +The processor system has been enhanced to provide: + +- **Warning logging** for unsupported node types +- **Comprehensive diagnostics** with suggestions for alternatives +- **Workflow validation** that flags unsupported types +- **Processor registry** for tracking supported/deprecated/experimental types +- **No-op result status** for fallback processing + +## Key Components + +### 1. ProcessorRegistry + +Central registry for tracking node type support status. + +```typescript +import { ProcessorRegistry } from "./ProcessorRegistry"; + +// Check if a node type is supported +const isSupported = ProcessorRegistry.isSupported("llmTask"); + +// Get node type status +const status = ProcessorRegistry.getNodeTypeStatus("deprecatedType"); +// Returns: { supported: true, deprecated: true, experimental: false, status: 'deprecated' } + +// Get similar types for suggestions +const alternatives = ProcessorRegistry.getSimilarTypes("unsupportedType"); +// Returns: ['dataInput', 'dataOutput'] +``` + +### 2. Enhanced DefaultProcessor + +The default processor now provides comprehensive diagnostics and warnings. + +```typescript +import defaultProcessor from "./defaultProcessor"; + +const result = await defaultProcessor(context, plan); + +// Result includes: +// - warning: Warning message for unsupported/deprecated types +// - diagnostics: Detailed status and suggestions +// - metadata: Execution timing and fallback usage info +``` + +**Example Result:** + +```typescript +{ + input: "test data", + output: "test data", // Pass-through behavior + type: "default", + success: true, + warning: "Node type 'unsupportedType' is not supported. Using fallback processor.", + diagnostics: { + nodeType: "unsupportedType", + status: "unsupported", + similarTypes: ["dataInput", "dataOutput"], + suggestion: "Consider using one of these supported types: dataInput, dataOutput" + }, + metadata: { + executionTime: 15, + timestamp: new Date(), + fallbackUsed: true + } +} +``` + +### 3. Workflow Validation + +Enhanced workflow validator now checks for unsupported node types. + +```typescript +import { validateWorkflowStructure } from "../utils/workflowValidator"; + +const validation = validateWorkflowStructure(workflow, input); + +// Validation now includes node type checks: +// - Issues for unsupported types +// - Warnings for deprecated types +// - Suggestions for alternatives +``` + +### 4. Node Type Diagnostics + +Utility functions for comprehensive node type analysis. + +```typescript +import { + generateNodeTypeDiagnostics, + getNodeTypeSummary, +} from "../utils/nodeTypeDiagnostics"; + +// Generate comprehensive diagnostics +const report = generateNodeTypeDiagnostics(workflow); +// Returns: { totalNodes, supportedNodes, deprecatedNodes, unsupportedNodes, diagnostics, recommendations } + +// Get quick summary +const summary = getNodeTypeSummary(workflow); +// Returns: { hasIssues, issueCount, warningCount, summary } +``` + +## Node Type Status Levels + +### Supported ✅ + +- Fully supported node types +- Have dedicated processors +- No warnings or issues + +### Deprecated ⚠️ + +- Still supported but marked for removal +- Generates warnings in validation +- Suggests migration alternatives + +### Experimental 🧪 + +- New or experimental features +- May have limited functionality +- API may change in future versions + +### Unsupported ❌ + +- Not supported by the system +- Uses fallback processor +- Generates errors in validation + +## Usage Examples + +### 1. Basic Processor Usage + +```typescript +// The execution engine automatically handles unsupported types +const result = await executionEngine.executeWorkflow(workflow); + +// Check if any nodes used fallback processing +const fallbackNodes = result.nodes.filter( + (node) => node.result?.metadata?.fallbackUsed === true +); +``` + +### 2. Workflow Validation + +```typescript +import { validateWorkflowStructure } from "../utils/workflowValidator"; + +const workflow = { + nodes: [ + { id: "node-1", type: "dataInput", label: "Input", config: {} }, + { id: "node-2", type: "unsupportedType", label: "Unsupported", config: {} }, + { id: "node-3", type: "deprecatedType", label: "Deprecated", config: {} }, + ], + edges: [], + complexity: "medium", +}; + +const validation = validateWorkflowStructure(workflow, "test input"); + +console.log(validation.issues); +// [ +// "Node 2 (Unsupported) uses unsupported node type: unsupportedType", +// "Node 3 (Deprecated) uses deprecated node type: deprecatedType" +// ] + +console.log(validation.suggestions); +// [ +// "Consider replacing 'unsupportedType' with one of these supported types: dataInput, dataOutput", +// "Consider migrating 'deprecatedType' to: llmTask" +// ] +``` + +### 3. Node Type Diagnostics + +```typescript +import { generateNodeTypeDiagnostics } from "../utils/nodeTypeDiagnostics"; + +const report = generateNodeTypeDiagnostics(workflow); + +console.log(`Total nodes: ${report.totalNodes}`); +console.log(`Supported: ${report.supportedNodes}`); +console.log(`Unsupported: ${report.unsupportedNodes}`); +console.log(`Deprecated: ${report.deprecatedNodes}`); +console.log(`Experimental: ${report.experimentalNodes}`); + +// Review diagnostics for each node +report.diagnostics.forEach((diagnostic) => { + if (diagnostic.severity === "error") { + console.error(`❌ ${diagnostic.nodeType}: ${diagnostic.warning}`); + console.log(` Suggestion: ${diagnostic.suggestion}`); + } else if (diagnostic.severity === "warning") { + console.warn(`⚠️ ${diagnostic.nodeType}: ${diagnostic.warning}`); + console.log(` Suggestion: ${diagnostic.suggestion}`); + } +}); +``` + +### 4. Adding New Node Types + +```typescript +import { ProcessorRegistry } from "./ProcessorRegistry"; + +// Register a new supported type +ProcessorRegistry.registerType("newNodeType"); + +// Register an experimental type +ProcessorRegistry.registerType("experimentalType", { experimental: true }); + +// Mark a type as deprecated +ProcessorRegistry.markAsDeprecated("oldNodeType"); +``` + +## Console Output + +The enhanced system provides detailed console output for debugging: + +``` +[DefaultProcessor] Node type 'unsupportedType' is not supported. Using fallback processor. { + nodeType: 'unsupportedType', + nodeId: 'node-2', + executionId: 'plan-123', + suggestion: 'Consider using one of these supported types: dataInput, dataOutput', + similarTypes: ['dataInput', 'dataOutput'] +} +``` + +## Testing + +Comprehensive tests are included for all components: + +```bash +# Run processor tests +npm test src/__tests__/processors/defaultProcessor.test.ts + +# Run diagnostics tests +npm test src/__tests__/utils/nodeTypeDiagnostics.test.ts +``` + +## Migration Guide + +### For Existing Workflows + +1. **Check validation results** for any unsupported node types +2. **Review warnings** for deprecated types +3. **Update node types** based on suggestions +4. **Test workflows** to ensure they still work as expected + +### For New Workflows + +1. **Use supported node types** from the registry +2. **Validate workflows** before execution +3. **Handle fallback cases** in your application logic +4. **Monitor console output** for warnings + +## Best Practices + +1. **Always validate workflows** before execution +2. **Handle fallback results** gracefully in your UI +3. **Provide user feedback** for unsupported types +4. **Keep node types up-to-date** to avoid deprecation warnings +5. **Test with mixed node types** to ensure compatibility + +## Future Enhancements + +- **Auto-migration tools** for deprecated types +- **Visual indicators** in the UI for node type status +- **Performance metrics** for fallback processing +- **Custom processor registration** at runtime +- **Node type compatibility checking** between connected nodes diff --git a/src/services/processors/defaultProcessor.ts b/src/services/processors/defaultProcessor.ts index 859891f..b662467 100644 --- a/src/services/processors/defaultProcessor.ts +++ b/src/services/processors/defaultProcessor.ts @@ -1,27 +1,121 @@ import { ExecutionContext, ExecutionPlan } from "../executionEngine"; +import { ProcessorRegistry } from "./ProcessorRegistry"; + +export interface DefaultProcessorResult { + input: any; + output: any; + type: "default"; + success: boolean; + warning?: string; + diagnostics?: { + nodeType: string; + status: "unsupported" | "deprecated" | "experimental" | "supported"; + similarTypes: string[]; + suggestion?: string; + }; + metadata?: { + executionTime: number; + timestamp: Date; + fallbackUsed: boolean; + }; +} export default async function defaultProcessor( context: ExecutionContext, plan: ExecutionPlan -): Promise { - const { inputs } = context; +): Promise { + const { inputs, nodeType } = context; + const startTime = Date.now(); try { // Get input data from previous nodes - const inputData = Array.from(inputs.values()).pop() || ""; + let inputData = ""; + if (inputs.size > 0) { + const values = Array.from(inputs.values()); + inputData = values.length > 0 ? values[values.length - 1] || "" : ""; + } - // For unknown node types, just pass through the data + // Check node type status + const nodeStatus = ProcessorRegistry.getNodeTypeStatus(nodeType); + const similarTypes = ProcessorRegistry.getSimilarTypes(nodeType); + + // Generate warning message based on status + let warning: string | undefined; + let suggestion: string | undefined; + + if (nodeStatus.status === "unsupported") { + warning = `Node type '${nodeType}' is not supported. Using fallback processor.`; + suggestion = + similarTypes.length > 0 + ? `Consider using one of these supported types: ${similarTypes.join( + ", " + )}` + : "No similar node types found. Check the documentation for supported types."; + } else if (nodeStatus.status === "deprecated") { + warning = `Node type '${nodeType}' is deprecated and may be removed in future versions.`; + suggestion = + similarTypes.length > 0 + ? `Consider migrating to: ${similarTypes.join(", ")}` + : "Check the documentation for migration guidance."; + } else if (nodeStatus.status === "experimental") { + warning = `Node type '${nodeType}' is experimental and may have limited functionality.`; + suggestion = "Use with caution. API may change in future versions."; + } + + // Log warning to console for debugging + if (warning) { + console.warn(`[DefaultProcessor] ${warning}`, { + nodeType, + nodeId: context.nodeId, + executionId: plan.id, + suggestion, + similarTypes, + }); + } + + // Create diagnostics object + const diagnostics = { + nodeType, + status: nodeStatus.status, + similarTypes, + suggestion, + }; + + const executionTime = Date.now() - startTime; + + // Return result with comprehensive diagnostics return { input: inputData, - output: inputData, + output: inputData, // Pass-through behavior type: "default", success: true, + warning, + diagnostics, + metadata: { + executionTime, + timestamp: new Date(), + fallbackUsed: true, + }, }; } catch (error) { + const executionTime = Date.now() - startTime; + + // Enhanced error reporting + const errorMessage = + error instanceof Error ? error.message : "Unknown error"; + console.error( + `[DefaultProcessor] Processing failed for node type '${nodeType}':`, + { + error: errorMessage, + nodeId: context.nodeId, + executionId: plan.id, + nodeType, + executionTime, + } + ); + throw new Error( - `Default processing failed: ${ - error instanceof Error ? error.message : "Unknown error" - }` + `Default processing failed for node type '${nodeType}': ${errorMessage}` ); } } diff --git a/src/utils/nodeTypeDiagnostics.ts b/src/utils/nodeTypeDiagnostics.ts new file mode 100644 index 0000000..5353602 --- /dev/null +++ b/src/utils/nodeTypeDiagnostics.ts @@ -0,0 +1,218 @@ +import { ProcessorRegistry } from "../services/processors/ProcessorRegistry"; +import { WorkflowStructure } from "../types"; + +export interface NodeTypeDiagnostic { + nodeType: string; + status: "supported" | "deprecated" | "experimental" | "unsupported"; + warning?: string; + suggestion?: string; + similarTypes: string[]; + severity: "error" | "warning" | "info"; +} + +export interface WorkflowNodeTypeReport { + totalNodes: number; + supportedNodes: number; + deprecatedNodes: number; + experimentalNodes: number; + unsupportedNodes: number; + diagnostics: NodeTypeDiagnostic[]; + recommendations: string[]; +} + +/** + * Generate comprehensive node type diagnostics for a workflow + */ +export function generateNodeTypeDiagnostics( + workflow: WorkflowStructure +): WorkflowNodeTypeReport { + const diagnostics: NodeTypeDiagnostic[] = []; + const recommendations: string[] = []; + + let supportedNodes = 0; + let deprecatedNodes = 0; + let experimentalNodes = 0; + let unsupportedNodes = 0; + + // Analyze each node + workflow.nodes.forEach((node, index) => { + const nodeType = node.type; + const nodeStatus = ProcessorRegistry.getNodeTypeStatus(nodeType); + const similarTypes = ProcessorRegistry.getSimilarTypes(nodeType); + + let warning: string | undefined; + let suggestion: string | undefined; + let severity: "error" | "warning" | "info"; + + switch (nodeStatus.status) { + case "unsupported": + unsupportedNodes++; + warning = `Node type '${nodeType}' is not supported`; + suggestion = + similarTypes.length > 0 + ? `Consider using: ${similarTypes.join(", ")}` + : "Check documentation for supported types"; + severity = "error"; + break; + + case "deprecated": + deprecatedNodes++; + warning = `Node type '${nodeType}' is deprecated`; + suggestion = + similarTypes.length > 0 + ? `Migrate to: ${similarTypes.join(", ")}` + : "Check migration documentation"; + severity = "warning"; + break; + + case "experimental": + experimentalNodes++; + warning = `Node type '${nodeType}' is experimental`; + suggestion = "Use with caution - API may change"; + severity = "warning"; + break; + + case "supported": + supportedNodes++; + severity = "info"; + break; + } + + diagnostics.push({ + nodeType, + status: nodeStatus.status, + warning, + suggestion, + similarTypes, + severity, + }); + }); + + // Generate recommendations + if (unsupportedNodes > 0) { + recommendations.push( + `Replace ${unsupportedNodes} unsupported node type(s) with supported alternatives` + ); + } + + if (deprecatedNodes > 0) { + recommendations.push( + `Migrate ${deprecatedNodes} deprecated node type(s) to current versions` + ); + } + + if (experimentalNodes > 0) { + recommendations.push( + `Review ${experimentalNodes} experimental node type(s) for stability` + ); + } + + if (supportedNodes === workflow.nodes.length) { + recommendations.push("All node types are supported and up-to-date"); + } + + return { + totalNodes: workflow.nodes.length, + supportedNodes, + deprecatedNodes, + experimentalNodes, + unsupportedNodes, + diagnostics, + recommendations, + }; +} + +/** + * Get a summary of node type issues in a workflow + */ +export function getNodeTypeSummary(workflow: WorkflowStructure): { + hasIssues: boolean; + issueCount: number; + warningCount: number; + summary: string; +} { + const report = generateNodeTypeDiagnostics(workflow); + + const errorCount = report.diagnostics.filter( + (d) => d.severity === "error" + ).length; + const warningCount = report.diagnostics.filter( + (d) => d.severity === "warning" + ).length; + + let summary = ""; + if (errorCount > 0) { + summary += `${errorCount} error(s) - unsupported node types`; + } + if (warningCount > 0) { + summary += + (summary ? ", " : "") + + `${warningCount} warning(s) - deprecated/experimental types`; + } + if (errorCount === 0 && warningCount === 0) { + summary = "All node types are supported"; + } + + return { + hasIssues: errorCount > 0 || warningCount > 0, + issueCount: errorCount, + warningCount, + summary, + }; +} + +/** + * Suggest alternative node types for a given node type + */ +export function suggestAlternatives(nodeType: string): { + alternatives: string[]; + reason: string; +} { + const similarTypes = ProcessorRegistry.getSimilarTypes(nodeType); + const nodeStatus = ProcessorRegistry.getNodeTypeStatus(nodeType); + + let reason = ""; + switch (nodeStatus.status) { + case "unsupported": + reason = "This node type is not supported"; + break; + case "deprecated": + reason = "This node type is deprecated"; + break; + case "experimental": + reason = "This node type is experimental"; + break; + default: + reason = "Similar functionality available"; + } + + return { + alternatives: similarTypes, + reason, + }; +} + +/** + * Check if a workflow can be executed without fallback processors + */ +export function canExecuteWithoutFallback(workflow: WorkflowStructure): { + canExecute: boolean; + fallbackNodes: string[]; + reason: string; +} { + const unsupportedNodes = workflow.nodes.filter( + (node) => + ProcessorRegistry.getNodeTypeStatus(node.type).status === "unsupported" + ); + + const fallbackNodes = unsupportedNodes.map((node) => node.label || node.type); + + return { + canExecute: unsupportedNodes.length === 0, + fallbackNodes, + reason: + unsupportedNodes.length > 0 + ? `${unsupportedNodes.length} node(s) will use fallback processing` + : "All nodes have dedicated processors", + }; +} diff --git a/src/utils/workflowValidator.ts b/src/utils/workflowValidator.ts index f9d09b5..fcd4b8d 100644 --- a/src/utils/workflowValidator.ts +++ b/src/utils/workflowValidator.ts @@ -4,6 +4,7 @@ import { MixedValidationResult, WorkflowNodeStructure, } from "../types"; +import { ProcessorRegistry } from "../services/processors/ProcessorRegistry"; /** * Validate generated workflow structure @@ -76,6 +77,11 @@ export function validateWorkflowStructure( issues.push(...labelValidation.issues); suggestions.push(...labelValidation.suggestions); + // Check for unsupported node types + const nodeTypeValidation = validateNodeTypes(workflow); + issues.push(...nodeTypeValidation.issues); + suggestions.push(...nodeTypeValidation.suggestions); + return { isValid: issues.length === 0, issues, @@ -706,3 +712,98 @@ function workflowAddressesRequirement( return true; } + +/** + * Validate node types in the workflow + */ +function validateNodeTypes(workflow: WorkflowStructure): { + issues: string[]; + suggestions: string[]; +} { + const issues: string[] = []; + const suggestions: string[] = []; + + workflow.nodes.forEach((node, index) => { + const nodeType = node.type; + const nodeStatus = ProcessorRegistry.getNodeTypeStatus(nodeType); + const similarTypes = ProcessorRegistry.getSimilarTypes(nodeType); + + switch (nodeStatus.status) { + case "unsupported": + issues.push( + `Node ${index + 1} (${ + node.label || nodeType + }) uses unsupported node type: ${nodeType}` + ); + if (similarTypes.length > 0) { + suggestions.push( + `Consider replacing '${nodeType}' with one of these supported types: ${similarTypes.join( + ", " + )}` + ); + } else { + suggestions.push( + `Node type '${nodeType}' is not supported. Check the documentation for available node types.` + ); + } + break; + + case "deprecated": + issues.push( + `Node ${index + 1} (${ + node.label || nodeType + }) uses deprecated node type: ${nodeType}` + ); + if (similarTypes.length > 0) { + suggestions.push( + `Consider migrating '${nodeType}' to: ${similarTypes.join(", ")}` + ); + } else { + suggestions.push( + `Node type '${nodeType}' is deprecated. Check the documentation for migration guidance.` + ); + } + break; + + case "experimental": + // Experimental types are warnings, not errors + suggestions.push( + `Node ${index + 1} (${ + node.label || nodeType + }) uses experimental node type: ${nodeType}. Use with caution.` + ); + break; + + case "supported": + // No issues for supported types + break; + } + }); + + // Check for mixed deprecated and supported types + const deprecatedNodes = workflow.nodes.filter( + (node) => + ProcessorRegistry.getNodeTypeStatus(node.type).status === "deprecated" + ); + const unsupportedNodes = workflow.nodes.filter( + (node) => + ProcessorRegistry.getNodeTypeStatus(node.type).status === "unsupported" + ); + + if ( + deprecatedNodes.length > 0 && + workflow.nodes.length > deprecatedNodes.length + ) { + suggestions.push( + `Workflow contains ${deprecatedNodes.length} deprecated node type(s). Consider updating to supported alternatives.` + ); + } + + if (unsupportedNodes.length > 0) { + issues.push( + `Workflow contains ${unsupportedNodes.length} unsupported node type(s). These will use fallback processing.` + ); + } + + return { issues, suggestions }; +}