UNPKG

@re-shell/cli

Version:

Full-stack development platform uniting microservices and microfrontends. Build complete applications with .NET (ASP.NET Core Web API, Minimal API), Java (Spring Boot, Quarkus, Micronaut, Vert.x), Rust (Actix-Web, Warp, Rocket, Axum), Python (FastAPI, Dja

548 lines (547 loc) 21.6 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.builtinMiddleware = exports.MiddlewareChainManager = exports.MiddlewareType = void 0; exports.createMiddlewareChainManager = createMiddlewareChainManager; exports.composeMiddleware = composeMiddleware; const events_1 = require("events"); const error_handler_1 = require("./error-handler"); // Middleware types var MiddlewareType; (function (MiddlewareType) { MiddlewareType["PRE_VALIDATION"] = "pre-validation"; MiddlewareType["VALIDATION"] = "validation"; MiddlewareType["PRE_EXECUTION"] = "pre-execution"; MiddlewareType["POST_EXECUTION"] = "post-execution"; MiddlewareType["ERROR_HANDLER"] = "error-handler"; MiddlewareType["LOGGER"] = "logger"; MiddlewareType["RATE_LIMITER"] = "rate-limiter"; MiddlewareType["CACHE"] = "cache"; MiddlewareType["TRANSFORM"] = "transform"; MiddlewareType["AUTHORIZATION"] = "authorization"; })(MiddlewareType || (exports.MiddlewareType = MiddlewareType = {})); // Middleware chain manager class MiddlewareChainManager extends events_1.EventEmitter { constructor() { super(); this.middlewares = new Map(); this.typeChains = new Map(); this.commandMiddleware = new Map(); this.cache = new Map(); this.rateLimiters = new Map(); this.initializeTypeChains(); } // Initialize middleware type chains initializeTypeChains() { Object.values(MiddlewareType).forEach(type => { this.typeChains.set(type, []); }); } // Register middleware registerMiddleware(pluginName, type, handler, options) { const id = this.generateMiddlewareId(pluginName, type); const registration = { id, pluginName, type, priority: options?.priority || 0, handler, options: options?.options, isActive: true, appliesTo: options?.appliesTo, metadata: options?.metadata }; this.middlewares.set(id, registration); this.updateTypeChain(type); this.emit('middleware-registered', { id, pluginName, type }); return id; } // Unregister middleware unregisterMiddleware(id) { const middleware = this.middlewares.get(id); if (!middleware) { return false; } this.middlewares.delete(id); this.updateTypeChain(middleware.type); // Clean up command-specific registrations this.commandMiddleware.forEach((middlewareIds, commandId) => { const index = middlewareIds.indexOf(id); if (index !== -1) { middlewareIds.splice(index, 1); } }); this.emit('middleware-unregistered', { id }); return true; } // Register middleware for specific command registerCommandMiddleware(commandId, middlewareId) { if (!this.commandMiddleware.has(commandId)) { this.commandMiddleware.set(commandId, []); } const middlewareIds = this.commandMiddleware.get(commandId); if (!middlewareIds.includes(middlewareId)) { middlewareIds.push(middlewareId); } } // Execute middleware chain async executeChain(type, args, options, context) { const startTime = Date.now(); const chain = this.getMiddlewareChain(type, context); let currentArgs = { ...args }; let currentOptions = { ...options }; let skipRemaining = false; this.emit('chain-execution-started', { type, commandId: context.command.name }); try { for (const middleware of chain) { if (skipRemaining) break; const result = await this.executeMiddleware(middleware, currentArgs, currentOptions, context); if (!result.success && !middleware.options?.skipOnError) { throw result.error || new Error('Middleware execution failed'); } if (result.modified?.args) { currentArgs = { ...currentArgs, ...result.modified.args }; } if (result.modified?.options) { currentOptions = { ...currentOptions, ...result.modified.options }; } if (result.skipRemaining) { skipRemaining = true; } } const duration = Date.now() - startTime; this.emit('chain-execution-completed', { type, commandId: context.command.name, duration }); return { success: true, duration, modified: { args: currentArgs, options: currentOptions } }; } catch (error) { const duration = Date.now() - startTime; this.emit('chain-execution-failed', { type, commandId: context.command.name, error, duration }); return { success: false, duration, error: error instanceof Error ? error : new Error(String(error)) }; } } // Execute single middleware async executeMiddleware(middleware, args, options, context) { const startTime = Date.now(); try { // Check cache if enabled if (middleware.options?.cache?.enabled) { const cacheKey = this.getCacheKey(middleware, args, options); const cached = this.getFromCache(cacheKey); if (cached !== undefined) { this.emit('middleware-cache-hit', { id: middleware.id, cacheKey }); return { success: true, duration: Date.now() - startTime, data: cached }; } } // Check rate limit if enabled if (middleware.options?.rateLimit) { const rateLimitKey = this.getRateLimitKey(middleware, context); if (!this.checkRateLimit(middleware, rateLimitKey)) { throw new error_handler_1.ValidationError('Rate limit exceeded'); } } // Create middleware execution context const middlewareContext = { ...context }; let result; const modifiedArgs = args; const modifiedOptions = options; const skipRemaining = false; // Execute middleware with timeout const timeout = middleware.options?.timeout || 30000; const middlewarePromise = new Promise(async (resolve, reject) => { try { await middleware.handler(modifiedArgs, modifiedOptions, middlewareContext, async () => { // Next function - captures modifications resolve(); }); } catch (error) { reject(error); } }); await Promise.race([ middlewarePromise, new Promise((_, reject) => setTimeout(() => reject(new Error('Middleware timeout')), timeout)) ]); // Cache result if enabled if (middleware.options?.cache?.enabled && result !== undefined) { const cacheKey = this.getCacheKey(middleware, args, options); this.setInCache(cacheKey, result, middleware.options.cache.ttl); } const duration = Date.now() - startTime; this.emit('middleware-executed', { id: middleware.id, type: middleware.type, duration }); return { success: true, duration, data: result, modified: { args: modifiedArgs !== args ? modifiedArgs : undefined, options: modifiedOptions !== options ? modifiedOptions : undefined }, skipRemaining }; } catch (error) { const duration = Date.now() - startTime; this.emit('middleware-failed', { id: middleware.id, type: middleware.type, error, duration }); return { success: false, duration, error: error instanceof Error ? error : new Error(String(error)) }; } } // Get middleware chain for type and context getMiddlewareChain(type, context) { const typeChain = this.typeChains.get(type) || []; const commandChain = this.commandMiddleware.get(context.command.name) || []; const allMiddlewareIds = [...new Set([...typeChain, ...commandChain])]; return allMiddlewareIds .map(id => this.middlewares.get(id)) .filter((m) => m !== undefined && m.isActive && (m.type === type || commandChain.includes(m.id)) && this.appliesTo(m, context)) .sort((a, b) => b.priority - a.priority); } // Check if middleware applies to context appliesTo(middleware, context) { if (!middleware.appliesTo) { return true; } const filter = middleware.appliesTo; if (filter.commands && !filter.commands.includes(context.command.name)) { return false; } if (filter.plugins && !filter.plugins.includes(context.plugin.manifest.name)) { return false; } if (filter.categories && context.command.category && !filter.categories.includes(context.command.category)) { return false; } if (filter.patterns) { const matches = filter.patterns.some(pattern => pattern.test(context.command.name)); if (!matches) return false; } if (filter.custom && !filter.custom(context)) { return false; } return true; } // Update type chain after registration/unregistration updateTypeChain(type) { const middlewares = Array.from(this.middlewares.values()) .filter(m => m.type === type && m.isActive) .sort((a, b) => b.priority - a.priority) .map(m => m.id); this.typeChains.set(type, middlewares); } // Generate unique middleware ID generateMiddlewareId(pluginName, type) { return `${pluginName}:${type}:${Date.now()}`; } // Cache management getCacheKey(middleware, args, options) { if (middleware.options?.cache?.key) { return middleware.options.cache.key(args, options); } return `${middleware.id}:${JSON.stringify({ args, options })}`; } getFromCache(key) { const cached = this.cache.get(key); if (cached && cached.expires > Date.now()) { return cached.data; } this.cache.delete(key); return undefined; } setInCache(key, data, ttl) { this.cache.set(key, { data, expires: Date.now() + ttl }); } // Rate limiting getRateLimitKey(middleware, context) { return `${context.plugin.manifest.name}:${context.command.name}`; } checkRateLimit(middleware, key) { if (!middleware.options?.rateLimit) return true; const { maxRequests, windowMs } = middleware.options.rateLimit; const now = Date.now(); const windowStart = now - windowMs; if (!this.rateLimiters.has(middleware.id)) { this.rateLimiters.set(middleware.id, new Map()); } const limiter = this.rateLimiters.get(middleware.id); if (!limiter.has(key)) { limiter.set(key, []); } const requests = limiter.get(key); // Remove old requests outside window const validRequests = requests.filter(timestamp => timestamp > windowStart); if (validRequests.length >= maxRequests) { return false; } validRequests.push(now); limiter.set(key, validRequests); return true; } // Get all middlewares getMiddlewares() { return Array.from(this.middlewares.values()); } // Get middlewares by type getMiddlewaresByType(type) { return Array.from(this.middlewares.values()) .filter(m => m.type === type); } // Get middlewares by plugin getMiddlewaresByPlugin(pluginName) { return Array.from(this.middlewares.values()) .filter(m => m.pluginName === pluginName); } // Clear cache clearCache() { this.cache.clear(); this.emit('cache-cleared'); } // Get statistics getStats() { const stats = { totalMiddlewares: this.middlewares.size, activeMiddlewares: Array.from(this.middlewares.values()).filter(m => m.isActive).length, byType: {}, byPlugin: {}, cacheSize: this.cache.size, rateLimiters: this.rateLimiters.size }; // Count by type Object.values(MiddlewareType).forEach(type => { stats.byType[type] = this.getMiddlewaresByType(type).length; }); // Count by plugin Array.from(this.middlewares.values()).forEach(m => { stats.byPlugin[m.pluginName] = (stats.byPlugin[m.pluginName] || 0) + 1; }); return stats; } } exports.MiddlewareChainManager = MiddlewareChainManager; // Create built-in middleware factories exports.builtinMiddleware = { // Validation middleware validation: (schema) => { return async (args, options, context, next) => { try { // Validate against schema (simplified - would use a real validator) if (schema.args) { Object.entries(schema.args).forEach(([key, rules]) => { const value = args[key]; if (rules.required && value === undefined) { throw new error_handler_1.ValidationError(`Argument '${key}' is required`); } if (rules.type && value !== undefined && typeof value !== rules.type) { throw new error_handler_1.ValidationError(`Argument '${key}' must be of type ${rules.type}`); } }); } if (schema.options) { Object.entries(schema.options).forEach(([key, rules]) => { const value = options[key]; if (rules.required && value === undefined) { throw new error_handler_1.ValidationError(`Option '${key}' is required`); } if (rules.type && value !== undefined && typeof value !== rules.type) { throw new error_handler_1.ValidationError(`Option '${key}' must be of type ${rules.type}`); } }); } await next(); } catch (error) { context.logger.error(`Validation failed: ${error instanceof Error ? error.message : String(error)}`); throw error; } }; }, // Authorization middleware authorization: (requiredPermissions) => { return async (args, options, context, next) => { // Check if plugin has required permissions const pluginPermissions = context.plugin.manifest.reshell?.permissions || []; const hasAllPermissions = requiredPermissions.every(perm => pluginPermissions.includes(perm)); if (!hasAllPermissions) { throw new error_handler_1.ValidationError(`Plugin lacks required permissions: ${requiredPermissions.join(', ')}`); } await next(); }; }, // Rate limiting middleware rateLimit: ({ maxRequests, windowMs }) => { const requests = new Map(); return async (args, options, context, next) => { const key = `${context.plugin.manifest.name}:${context.command.name}`; const now = Date.now(); const windowStart = now - windowMs; if (!requests.has(key)) { requests.set(key, []); } const keyRequests = requests.get(key); const validRequests = keyRequests.filter(timestamp => timestamp > windowStart); if (validRequests.length >= maxRequests) { throw new error_handler_1.ValidationError('Rate limit exceeded'); } validRequests.push(now); requests.set(key, validRequests); await next(); }; }, // Caching middleware cache: ({ ttl, key }) => { const cache = new Map(); return async (args, options, context, next) => { const cacheKey = key ? key(args, options) : JSON.stringify({ args, options }); const cached = cache.get(cacheKey); if (cached && cached.expires > Date.now()) { context.logger.debug('Cache hit'); return cached.data; } let result; const originalNext = next; // Intercept next to capture result await originalNext(); if (result !== undefined) { cache.set(cacheKey, { data: result, expires: Date.now() + ttl }); } return result; }; }, // Logging middleware logger: ({ level = 'info', format } = {}) => { return async (args, options, context, next) => { const startTime = Date.now(); const commandName = context.command.name; const pluginName = context.plugin.manifest.name; context.logger.info(`[${pluginName}:${commandName}] Starting execution`); if (level === 'debug') { context.logger.debug(`Arguments: ${JSON.stringify(args)}`); context.logger.debug(`Options: ${JSON.stringify(options)}`); } try { await next(); const duration = Date.now() - startTime; context.logger.info(`[${pluginName}:${commandName}] Completed in ${duration}ms`); } catch (error) { const duration = Date.now() - startTime; context.logger.error(`[${pluginName}:${commandName}] Failed after ${duration}ms: ${error instanceof Error ? error.message : String(error)}`); throw error; } }; }, // Transform middleware transform: ({ args: argsTransformer, options: optionsTransformer }) => { return async (args, options, context, next) => { let transformedArgs = args; let transformedOptions = options; if (argsTransformer) { transformedArgs = argsTransformer(args); } if (optionsTransformer) { transformedOptions = optionsTransformer(options); } // Update args and options for next middleware Object.assign(args, transformedArgs); Object.assign(options, transformedOptions); await next(); }; }, // Error handler middleware errorHandler: (handler) => { return async (args, options, context, next) => { try { await next(); } catch (error) { handler(error instanceof Error ? error : new Error(String(error)), context); throw error; } }; }, // Timing middleware timing: () => { return async (args, options, context, next) => { const timings = {}; const startTime = Date.now(); // Add timing utility to context const originalContext = { ...context }; context.utils.startTimer = (name) => { timings[name] = Date.now(); }; context.utils.endTimer = (name) => { if (timings[name]) { const duration = Date.now() - timings[name]; context.logger.debug(`${name}: ${duration}ms`); return duration; } return 0; }; try { await next(); const totalDuration = Date.now() - startTime; context.logger.info(`Total execution time: ${totalDuration}ms`); } finally { // Restore original context Object.assign(context, originalContext); } }; } }; // Utility functions function createMiddlewareChainManager() { return new MiddlewareChainManager(); } function composeMiddleware(...middlewares) { return async (args, options, context, next) => { let index = 0; const dispatch = async () => { if (index >= middlewares.length) { return next(); } const middleware = middlewares[index++]; await middleware(args, options, context, dispatch); }; await dispatch(); }; }