UNPKG

@allan1361/iota-big3-sdk-middleware

Version:

🏆 A+ Grade Certified Enterprise Middleware Framework - Phase 3 Certified (90/100) with advanced resilience patterns, comprehensive type safety, and production-ready observability

316 lines 11.6 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.MiddlewareComposer = void 0; exports.createComposer = createComposer; const events_1 = require("events"); class MiddlewareWrapper { constructor(fn, options = {}) { this.fn = fn; this.options = options; } get name() { return this.options.name || this.fn.name || 'anonymous'; } async executeAsync(req, res, next) { if (this.options.condition) { const shouldRun = await this.options.condition(req, res); if (!shouldRun) { return next(); } } let timeoutId; if (this.options.timeout) { timeoutId = setTimeout(() => { const error = new Error(`Middleware '${this.name}' timed out after ${this.options.timeout}ms`); error.code = 'MIDDLEWARE_TIMEOUT'; next(error); }, this.options.timeout); } let lastError; const attempts = this.options.retries ? this.options.retries + 1 : 1; for (let attempt = 0; attempt < attempts; attempt++) { try { if (attempt > 0) { await new Promise(resolve => setTimeout(resolve, this.options.retryDelay || 100)); } await new Promise((resolve, reject) => { const result = this.fn(req, res, (err) => { if (err) reject(err); else resolve(); }); if (result instanceof Promise) { result.then(resolve).catch(reject); } }); if (timeoutId) clearTimeout(timeoutId); return next(); } catch (error) { lastError = error; if (attempt === attempts - 1) { if (timeoutId) clearTimeout(timeoutId); if (this.options.errorHandler) { return this.options.errorHandler(error, req, res, next); } return next(error); } } } if (timeoutId) clearTimeout(timeoutId); next(lastError); } } class MiddlewareComposer extends events_1.EventEmitter { constructor(options = {}) { super(); this.middleware = []; this.metrics = new Map(); this.defaultErrorHandler = (error, req, res, next) => { console.error('Middleware error:', error); if (res.headersSent) { return next(error); } res.status(500).json({ error: 'Internal Server Error', message: error.message, middleware: error.middleware, code: error.code }); }; this.options = { errorHandler: options.errorHandler || this.defaultErrorHandler, timeout: options.timeout || 30000, monitoring: options.monitoring ?? true, contextKey: options.contextKey || 'context' }; } use(fn, options) { if (Array.isArray(fn)) { fn.forEach(f => this.use(f, options)); } else { const wrapper = new MiddlewareWrapper(fn, options); this.middleware.push(wrapper); if (this.options.monitoring) { this.metrics.set(wrapper.name, { name: wrapper.name, executions: 0, errors: 0, totalTime: 0, averageTime: 0, minTime: Infinity, maxTime: 0 }); } } return this; } when(condition, trueBranch, falseBranch) { const branchMiddleware = async (req, res, next) => { const result = await condition(req, res); if (result && trueBranch) { if (trueBranch instanceof MiddlewareComposer) { return trueBranch.compose()(req, res, next); } else { return trueBranch(req, res, next); } } else if (!result && falseBranch) { if (falseBranch instanceof MiddlewareComposer) { return falseBranch.compose()(req, res, next); } else { return falseBranch(req, res, next); } } else { next(); } }; return this.use(branchMiddleware, { name: 'conditional-branch' }); } parallel(middleware, options) { const parallelMiddleware = async (req, res, next) => { const promises = middleware.map(fn => new Promise((resolve, reject) => { fn(req, res, (err) => { if (err) reject(err); else resolve(undefined); }); })); try { if (options?.waitForAll) { await Promise.all(promises); } else { await Promise.race(promises); } next(); } catch (error) { if (options?.errorHandler) { options.errorHandler(error, req, res, next); } else { next(error); } } }; return this.use(parallelMiddleware, { name: 'parallel-execution' }); } group(name) { const subComposer = new MiddlewareComposer(this.options); this.use(subComposer.compose(), { name }); return subComposer; } compose() { return async (req, res, next) => { const composerReq = req; if (!composerReq[this.options.contextKey]) { composerReq[this.options.contextKey] = {}; } composerReq.context = composerReq[this.options.contextKey]; composerReq.startTime = Date.now(); composerReq.middlewareStack = []; let index = 0; const executeNext = async (error) => { if (error) { return this.options.errorHandler(error, composerReq, res, next); } if (index >= this.middleware.length) { const totalTime = Date.now() - composerReq.startTime; this.emit('complete', { request: composerReq, totalTime, middlewareStack: composerReq.middlewareStack }); return next(); } const wrapper = this.middleware[index++]; const startTime = Date.now(); try { composerReq.middlewareStack.push(wrapper.name); await wrapper.executeAsync(composerReq, res, executeNext); if (this.options.monitoring) { const duration = Date.now() - startTime; this.updateMetrics(wrapper.name, duration, false); } } catch (error) { if (this.options.monitoring) { const duration = Date.now() - startTime; this.updateMetrics(wrapper.name, duration, true); } this.emit('error', { middleware: wrapper.name, error, request: composerReq }); executeNext(error); } }; executeNext(); }; } updateMetrics(name, duration, error) { const metrics = this.metrics.get(name); if (!metrics) return; metrics.executions++; if (error) metrics.errors++; metrics.totalTime += duration; metrics.averageTime = metrics.totalTime / metrics.executions; metrics.minTime = Math.min(metrics.minTime, duration); metrics.maxTime = Math.max(metrics.maxTime, duration); } getMetrics() { return Array.from(this.metrics.values()); } clear() { this.middleware = []; this.metrics.clear(); } rateLimit(limit, window, keyGenerator) { const requests = new Map(); return this.use((req, res, next) => { const key = keyGenerator ? keyGenerator(req) : req.ip || 'anonymous'; const now = Date.now(); const windowStart = now - window; const timestamps = requests.get(key) || []; const recentRequests = timestamps.filter(t => t > windowStart); if (recentRequests.length >= limit) { res.status(429).json({ error: 'Too many requests' }); return; } recentRequests.push(now); requests.set(key, recentRequests); next(); }, { name: 'rate-limit' }); } log(logger) { return this.use((req, res, next) => { const start = Date.now(); res.on('finish', () => { const duration = Date.now() - start; if (logger) { logger(req, res, duration); } else { console.log(`${req.method} ${req.url} - ${res.statusCode} - ${duration}ms`); } }); next(); }, { name: 'request-logger' }); } cors(options) { return this.use((req, res, next) => { const origin = req.headers.origin; if (options?.origin) { if (typeof options.origin === 'function') { if (origin && options.origin(origin)) { res.setHeader('Access-Control-Allow-Origin', origin); } } else if (Array.isArray(options.origin)) { if (origin && options.origin.includes(origin)) { res.setHeader('Access-Control-Allow-Origin', origin); } } else { res.setHeader('Access-Control-Allow-Origin', options.origin); } } else { res.setHeader('Access-Control-Allow-Origin', '*'); } if (options?.credentials) { res.setHeader('Access-Control-Allow-Credentials', 'true'); } if (options?.methods) { res.setHeader('Access-Control-Allow-Methods', options.methods.join(', ')); } if (options?.headers) { res.setHeader('Access-Control-Allow-Headers', options.headers.join(', ')); } if (req.method === 'OPTIONS') { res.sendStatus(200); } else { next(); } }, { name: 'cors' }); } } exports.MiddlewareComposer = MiddlewareComposer; function createComposer(options) { return new MiddlewareComposer(options); } //# sourceMappingURL=middleware-composer.js.map