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
JavaScript
;
/**
* @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;