Add comprehensive processor system with enhanced diagnostics and validation

- Introduced a ProcessorRegistry for managing supported, deprecated, and experimental node types.
- Enhanced defaultProcessor to provide detailed diagnostics, warnings, and execution metadata for unsupported and deprecated node types.
- Implemented comprehensive tests for defaultProcessor and node type diagnostics utilities, ensuring robust functionality and error handling.
- Developed utilities for generating node type diagnostics and summaries, improving workflow validation and user feedback.
- Updated workflowValidator to check for unsupported and deprecated node types, providing suggestions for alternatives.
- Enhanced documentation to reflect new features and usage examples for the processor system.
This commit is contained in:
Nikhil-Doye
2025-10-20 17:59:26 -04:00
parent 7e3c204119
commit 7dd679c6e8
8 changed files with 1438 additions and 9 deletions
@@ -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
});
});
});
@@ -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",
});
});
});
});
+8 -1
View File
@@ -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;
}
}
@@ -0,0 +1,174 @@
/**
* Registry for tracking supported node processors
*/
export class ProcessorRegistry {
private static supportedTypes: Set<string> = new Set([
"dataInput",
"dataOutput",
"webScraping",
"llmTask",
"databaseQuery",
"databaseInsert",
"databaseUpdate",
"databaseDelete",
"databaseAggregate",
"databaseTransaction",
"structuredOutput",
"embeddingGenerator",
"similaritySearch",
]);
private static deprecatedTypes: Set<string> = new Set([
// Add deprecated types here as they become obsolete
]);
private static experimentalTypes: Set<string> = 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
}
}
+274
View File
@@ -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
+102 -8
View File
@@ -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<any> {
const { inputs } = context;
): Promise<DefaultProcessorResult> {
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}`
);
}
}
+218
View File
@@ -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",
};
}
+101
View File
@@ -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 };
}