UNPKG

@zhanghongping/json-sage-workflow-cli

Version:

An intelligent JSON processing workflow system with improved error handling and configuration

259 lines (233 loc) 7.28 kB
import { WorkflowContext } from '../core/WorkflowContext'; import { ValidationError } from '../core/errors/ValidationError'; import { ConfigurationError } from '../core/errors/ConfigurationError'; /** * Utility class providing common functionality for workflow nodes */ export class NodeUtils { /** * Validates node input data against specified criteria * @param input - Input data to validate * @param schema - Validation schema (optional) * @param context - Workflow context * @throws {ValidationError} If validation fails */ static async validateInput( input: any, schema: any, context: WorkflowContext ): Promise<void> { if (!input) { throw new ValidationError('Input data is required'); } if (schema) { const validationResult = await this.validateSchema(input, schema); if (!validationResult.valid) { throw new ValidationError( 'Input validation failed', validationResult.errors ); } } } /** * Validates node configuration * @param config - Node configuration * @param requiredFields - List of required configuration fields * @throws {ConfigurationError} If configuration is invalid */ static validateConfig( config: any, requiredFields: string[] = [] ): void { if (!config) { throw new ConfigurationError('Configuration is required'); } for (const field of requiredFields) { if (!(field in config)) { throw new ConfigurationError(`Missing required configuration field: ${field}`); } } } /** * Parses and validates data type * @param value - Value to parse * @param expectedType - Expected type * @returns Parsed value * @throws {ValidationError} If type is invalid */ static parseType(value: any, expectedType: string): any { switch (expectedType.toLowerCase()) { case 'string': return String(value); case 'number': const num = Number(value); if (isNaN(num)) { throw new ValidationError(`Invalid number: ${value}`); } return num; case 'boolean': if (typeof value === 'string') { return value.toLowerCase() === 'true'; } return Boolean(value); case 'object': if (typeof value === 'string') { try { return JSON.parse(value); } catch (e) { throw new ValidationError(`Invalid JSON object: ${value}`); } } return value; case 'array': if (Array.isArray(value)) { return value; } if (typeof value === 'string') { try { const parsed = JSON.parse(value); if (!Array.isArray(parsed)) { throw new Error(); } return parsed; } catch (e) { throw new ValidationError(`Invalid array: ${value}`); } } throw new ValidationError(`Invalid array: ${value}`); default: throw new ValidationError(`Unsupported type: ${expectedType}`); } } /** * Validates data against JSON Schema * @param data - Data to validate * @param schema - JSON Schema * @returns Validation result */ private static async validateSchema( data: any, schema: any ): Promise<{ valid: boolean; errors?: any[] }> { // Implementation of JSON Schema validation // You might want to use a library like Ajv here return { valid: true }; } } /** * Utility functions for working with asynchronous operations */ export class AsyncUtils { /** * Executes an async operation with retry logic * @param operation - Async operation to execute * @param maxRetries - Maximum number of retries * @param delay - Delay between retries in milliseconds * @returns Operation result */ static async withRetry<T>( operation: () => Promise<T>, maxRetries: number = 3, delay: number = 1000 ): Promise<T> { let lastError: Error; for (let attempt = 1; attempt <= maxRetries; attempt++) { try { return await operation(); } catch (error) { lastError = error as Error; if (attempt < maxRetries) { await new Promise(resolve => setTimeout(resolve, delay * attempt)); } } } throw lastError; } /** * Executes an async operation with timeout * @param operation - Async operation to execute * @param timeout - Timeout in milliseconds * @returns Operation result */ static async withTimeout<T>( operation: () => Promise<T>, timeout: number ): Promise<T> { const timeoutPromise = new Promise<never>((_, reject) => { setTimeout(() => reject(new Error(`Operation timed out after ${timeout}ms`)), timeout); }); return Promise.race([operation(), timeoutPromise]); } } /** * Utility functions for dependency management */ export class DependencyUtils { /** * Detects circular dependencies in a graph * @param graph - Dependency graph * @returns Array of circular dependencies if found */ static detectCircularDependencies( graph: Record<string, string[]> ): string[][] { const visited = new Set<string>(); const recursionStack = new Set<string>(); const cycles: string[][] = []; const dfs = (node: string, path: string[] = []): void => { visited.add(node); recursionStack.add(node); path.push(node); const dependencies = graph[node] || []; for (const dependency of dependencies) { if (!visited.has(dependency)) { dfs(dependency, [...path]); } else if (recursionStack.has(dependency)) { const cycleStart = path.indexOf(dependency); cycles.push(path.slice(cycleStart)); } } recursionStack.delete(node); path.pop(); }; for (const node of Object.keys(graph)) { if (!visited.has(node)) { dfs(node); } } return cycles; } /** * Sorts nodes topologically * @param graph - Dependency graph * @returns Sorted node list * @throws {Error} If circular dependency is detected */ static topologicalSort( graph: Record<string, string[]> ): string[] { const visited = new Set<string>(); const sorted: string[] = []; const visit = (node: string, ancestors: Set<string>): void => { if (ancestors.has(node)) { throw new Error(`Circular dependency detected: ${Array.from(ancestors).join(' -> ')} -> ${node}`); } if (visited.has(node)) return; ancestors.add(node); const dependencies = graph[node] || []; for (const dependency of dependencies) { visit(dependency, new Set(ancestors)); } ancestors.delete(node); visited.add(node); sorted.unshift(node); }; for (const node of Object.keys(graph)) { if (!visited.has(node)) { visit(node, new Set()); } } return sorted; } }