mirror of
https://github.com/Nikhil-Doye/workflow-builder.git
synced 2026-07-22 02:01:56 +02:00
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:
@@ -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",
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
@@ -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}`
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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",
|
||||
};
|
||||
}
|
||||
@@ -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 };
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user