UNPKG

mcp-quiz-server

Version:

🧠 AI-Powered Quiz Management via Model Context Protocol (MCP) - Create, manage, and take quizzes directly from VS Code, Claude, and other AI agents.

315 lines (314 loc) 10.9 kB
"use strict"; /** * @fileoverview Command Bus Interface - Application Layer * @version 1.0.0 * @since 2025-07-30 * @lastUpdated 2025-07-30 * @module ICommandBus * @description Command Bus interface for CQRS implementation in Clean Architecture. * Routes commands to appropriate handlers with security context. * @contributors Claude Code Agent * @dependencies Command, CommandResult, SecurityContext * @requirements SECURITY_001 (Transport Layer Integration), REQ-ARCH-001 * @testCoverage Unit and integration tests for command routing and security */ Object.defineProperty(exports, "__esModule", { value: true }); exports.CommandValidationError = exports.DuplicateHandlerError = exports.CommandHandlerNotFoundError = exports.CommandBusError = exports.CommandBus = exports.BaseCommandHandler = void 0; /** * Base Command Handler Implementation * * @description Abstract base class providing common command handler functionality */ class BaseCommandHandler { /** * Default validation implementation */ async validate(command, context) { const errors = []; const warnings = []; // Basic validation if (!command.commandId) { errors.push('Command ID is required'); } if (!command.commandType) { errors.push('Command type is required'); } if (!context.user) { errors.push('User context is required'); } if (!context.isAuthenticated) { errors.push('User must be authenticated'); } // Command-specific validation const commandValidation = this.validateCommandData(command); errors.push(...commandValidation.errors); warnings.push(...commandValidation.warnings); return { isValid: errors.length === 0, errors, warnings, }; } /** * Check if this handler can process the command */ canHandle(command) { return command.commandType === this.getCommandType(); } /** * Command-specific data validation (override in derived classes) */ validateCommandData(command) { return { errors: [], warnings: [] }; } /** * Execute command with error handling and logging */ async executeWithLogging(command, context, execution) { const startTime = Date.now(); try { console.log(`🔄 Executing command: ${command.commandType} (${command.commandId})`); const result = await execution(); const executionTime = Date.now() - startTime; console.log(`✅ Command completed: ${command.commandType} in ${executionTime}ms`); return { success: true, commandId: command.commandId, data: result, executionTime, timestamp: new Date(), }; } catch (error) { const executionTime = Date.now() - startTime; console.error(`❌ Command failed: ${command.commandType}`, error); return { success: false, commandId: command.commandId, error: { code: error instanceof Error ? error.constructor.name : 'UNKNOWN_ERROR', message: error instanceof Error ? error.message : 'Unknown error occurred', stack: error instanceof Error ? error.stack : undefined, }, executionTime, timestamp: new Date(), }; } } } exports.BaseCommandHandler = BaseCommandHandler; /** * Command Bus Implementation * * @description command bus with security validation and monitoring */ class CommandBus { constructor() { this.handlers = new Map(); this.statistics = { totalCommands: 0, successfulCommands: 0, failedCommands: 0, averageExecutionTime: 0, registeredHandlers: 0, commandTypes: [], recentActivity: [], }; this.executionTimes = []; } /** * Execute command with full security validation */ async execute(command, context) { const startTime = Date.now(); try { // Validate command const validation = await this.validateCommand(command, context); if (!validation.isValid) { return this.createValidationFailureResult(command, validation); } // Get handler const handler = this.handlers.get(command.commandType); if (!handler) { throw new CommandHandlerNotFoundError(`No handler registered for command type: ${command.commandType}`); } // Execute command const result = await handler.handle(command, context); // Update statistics this.updateStatistics(command, context, result, Date.now() - startTime); return result; } catch (error) { const executionTime = Date.now() - startTime; const result = { success: false, commandId: command.commandId, error: { code: error instanceof Error ? error.constructor.name : 'UNKNOWN_ERROR', message: error instanceof Error ? error.message : 'Command execution failed', }, executionTime, timestamp: new Date(), }; this.updateStatistics(command, context, result, executionTime); return result; } } /** * Register command handler */ registerHandler(commandType, handler) { if (this.handlers.has(commandType)) { throw new DuplicateHandlerError(`Handler already registered for command type: ${commandType}`); } this.handlers.set(commandType, handler); this.statistics.registeredHandlers++; this.statistics.commandTypes = Array.from(this.handlers.keys()).sort(); console.log(`📝 Command handler registered: ${commandType}`); } /** * Check if handler exists */ hasHandler(commandType) { return this.handlers.has(commandType); } /** * Get registered command types */ getRegisteredCommandTypes() { return Array.from(this.handlers.keys()).sort(); } /** * Validate command before execution */ async validateCommand(command, context) { const errors = []; const warnings = []; // Basic command validation if (!command || typeof command !== 'object') { errors.push('Command must be a valid object'); return { isValid: false, errors, warnings }; } if (!command.commandId) { errors.push('Command ID is required'); } if (!command.commandType) { errors.push('Command type is required'); } // Security context validation if (!context) { errors.push('Security context is required'); return { isValid: false, errors, warnings }; } if (!context.isAuthenticated) { errors.push('User must be authenticated'); } if (!context.user) { errors.push('User information is required'); } // Handler validation const handler = this.handlers.get(command.commandType); if (!handler) { errors.push(`No handler registered for command type: ${command.commandType}`); } else { // Delegate to handler validation const handlerValidation = await handler.validate(command, context); errors.push(...handlerValidation.errors); warnings.push(...handlerValidation.warnings); } return { isValid: errors.length === 0, errors, warnings, }; } /** * Get command execution statistics */ getStatistics() { return { ...this.statistics }; } /** * Create validation failure result */ createValidationFailureResult(command, validation) { return { success: false, commandId: command.commandId, error: { code: 'VALIDATION_ERROR', message: 'Command validation failed', validationErrors: validation.errors, }, executionTime: 0, timestamp: new Date(), warnings: validation.warnings, }; } /** * Update execution statistics */ updateStatistics(command, context, result, executionTime) { this.statistics.totalCommands++; if (result.success) { this.statistics.successfulCommands++; } else { this.statistics.failedCommands++; } // Update execution time statistics this.executionTimes.push(executionTime); if (this.executionTimes.length > 1000) { this.executionTimes = this.executionTimes.slice(-1000); // Keep last 1000 } this.statistics.averageExecutionTime = this.executionTimes.reduce((sum, time) => sum + time, 0) / this.executionTimes.length; // Add to recent activity const activity = { commandId: command.commandId, commandType: command.commandType, userId: context.user.id, executionTime, success: result.success, timestamp: new Date(), }; this.statistics.recentActivity.unshift(activity); if (this.statistics.recentActivity.length > 100) { this.statistics.recentActivity = this.statistics.recentActivity.slice(0, 100); } } } exports.CommandBus = CommandBus; /** * Command Bus Error Types */ class CommandBusError extends Error { constructor(message) { super(message); this.name = 'CommandBusError'; } } exports.CommandBusError = CommandBusError; class CommandHandlerNotFoundError extends CommandBusError { constructor(message) { super(message); this.name = 'CommandHandlerNotFoundError'; } } exports.CommandHandlerNotFoundError = CommandHandlerNotFoundError; class DuplicateHandlerError extends CommandBusError { constructor(message) { super(message); this.name = 'DuplicateHandlerError'; } } exports.DuplicateHandlerError = DuplicateHandlerError; class CommandValidationError extends CommandBusError { constructor(message, validationErrors) { super(message); this.validationErrors = validationErrors; this.name = 'CommandValidationError'; } } exports.CommandValidationError = CommandValidationError;