@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
JavaScript
;
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();
};
}