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

552 lines 20.1 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.DynamicMiddlewareLoader = void 0; exports.createDynamicLoader = createDynamicLoader; const tslib_1 = require("tslib"); const crypto = tslib_1.__importStar(require("crypto")); const events_1 = require("events"); const fs = tslib_1.__importStar(require("fs/promises")); const path = tslib_1.__importStar(require("path")); const vm = tslib_1.__importStar(require("vm")); class DynamicMiddlewareLoader extends events_1.EventEmitter { constructor(config = {}) { super(); this.middlewares = new Map(); this.versions = new Map(); this.config = { middlewarePath: config.middlewarePath || './middleware', configPath: config.configPath || './middleware.json', watchEnabled: config.watchEnabled ?? true, watchInterval: config.watchInterval || 5000, hotReload: config.hotReload ?? true, validation: { enabled: config.validation?.enabled ?? true, timeout: config.validation?.timeout || 5000, testRequest: config.validation?.testRequest || { method: 'GET', url: '/health' } }, versioning: { enabled: config.versioning?.enabled ?? true, maxVersions: config.versioning?.maxVersions || 5, autoRollback: config.versioning?.autoRollback ?? true }, security: { sandbox: config.security?.sandbox ?? true, allowedModules: config.security?.allowedModules || [ 'express', 'body-parser', 'cors', 'helmet', 'compression', 'morgan', 'winston', 'joi', 'zod', 'axios' ], checksum: config.security?.checksum ?? true } }; } async initializeAsync(app) { this.app = app; await this.loadManifest(); await this.loadAllMiddlewares(); if (this.config.watchEnabled) { this.startWatching(); } this.emit('initialized', { middlewareCount: this.middlewares.size, watchEnabled: this.config.watchEnabled }); } async loadManifest() { try { const content = await fs.readFile(this.config.configPath, 'utf-8'); this.manifest = JSON.parse(content); this.emit('manifest:loaded', this.manifest); } catch (error) { this.emit('manifest:error', error); this.manifest = { version: '1.0.0', middlewares: [] }; } } async loadAllMiddlewares() { if (!this.manifest) return; const loadOrder = this.manifest.loadOrder || this.manifest.middlewares .sort((a, b) => (a.priority || 100) - (b.priority || 100)) .map(m => m.id); for (const middlewareId of loadOrder) { const definition = this.manifest.middlewares.find(m => m.id === middlewareId); if (definition && definition.enabled) { await this.loadMiddleware(definition); } } } async loadMiddleware(definition) { try { this.emit('middleware:loading', { id: definition.id, name: definition.name }); this.validateDefinition(definition); const handler = await this.loadMiddlewareHandler(definition); if (this.config.validation.enabled) { await this.validateMiddlewareAsync(handler, definition); } const loaded = { id: definition.id, definition, handler, loadedAt: new Date(), version: definition.version || '1.0.0', checksum: await this.calculateChecksumAsync(definition.path), instances: 0, errors: 0 }; if (this.config.versioning.enabled) { this.storeVersion(loaded); } if (this.app) { this.applyMiddleware(loaded); } const existing = this.middlewares.get(definition.id); this.middlewares.set(definition.id, loaded); if (existing) { this.emit('middleware:updated', { id: definition.id, previousVersion: existing.version, newVersion: loaded.version }); } else { this.emit('middleware:loaded', { id: definition.id, name: definition.name, version: loaded.version }); } return { success: true, middleware: loaded }; } catch (error) { this.emit('middleware:error', { id: definition.id, error: error.message }); if (this.config.versioning.autoRollback) { const rollbackVersion = await this.rollbackAsync(definition.id); return { success: false, error, rollbackVersion }; } return { success: false, error }; } } async loadMiddlewareHandler(definition) { const middlewarePath = path.resolve(this.config.middlewarePath, definition.path); if (this.config.security.sandbox) { return this.loadInSandbox(middlewarePath, definition); } else { delete require.cache[middlewarePath]; const module = require(middlewarePath); return typeof module === 'function' ? module : module.default || module.middleware; } } async loadInSandbox(middlewarePath, definition) { const code = await fs.readFile(middlewarePath, 'utf-8'); const sandbox = { module: { exports: {} }, exports: {}, require: this.createSafeRequire(), console, process: { env: process.env, version: process.version }, _dirname: path.dirname(middlewarePath), _filename: middlewarePath, setTimeout, setInterval, clearTimeout, clearInterval, Buffer, config: definition.config || {} }; const script = new vm.Script(code, { filename: middlewarePath, lineOffset: 0, columnOffset: 0 }); const context = vm.createContext(sandbox); script.runInContext(context); const exported = sandbox.module.exports || sandbox.exports; const handler = typeof exported === 'function' ? exported : exported.default || exported.middleware; if (typeof handler !== 'function') { throw new Error(`Middleware ${definition.id} does not export a valid handler`); } return handler; } createSafeRequire() { return (moduleName) => { if (!this.config.security.allowedModules?.includes(moduleName)) { throw new Error(`Module '${moduleName}' is not allowed in sandbox`); } return require(moduleName); }; } validateDefinition(definition) { if (!definition.id) { throw new Error('Middleware ID is required'); } if (!definition.name) { throw new Error('Middleware name is required'); } if (!definition.path) { throw new Error('Middleware path is required'); } if (definition.dependencies) { for (const dep of definition.dependencies) { if (!this.middlewares.has(dep)) { throw new Error(`Dependency '${dep}' not loaded`); } } } } async validateMiddlewareAsync(handler, definition) { return new Promise((resolve, reject) => { const timeout = setTimeout(() => { reject(new Error('Middleware validation timeout')); }, this.config.validation.timeout); const req = this.createMockRequest(this.config.validation.testRequest); const res = this.createMockResponse(); let nextCalled = false; const next = (error) => { clearTimeout(timeout); nextCalled = true; if (error) { reject(error); } else { resolve(); } }; try { const result = handler(req, res, next); if (result && typeof result.then === 'function') { result .then(() => { if (!nextCalled) { clearTimeout(timeout); resolve(); } }) .catch(error => { clearTimeout(timeout); reject(error); }); } } catch (error) { clearTimeout(timeout); reject(error); } }); } applyMiddleware(loaded) { if (!this.app) return; const definition = loaded.definition; const wrappedHandler = this.wrapHandler(loaded); if (definition.routes && definition.routes.length > 0) { for (const route of definition.routes) { if (definition.methods && definition.methods.length > 0) { for (const method of definition.methods) { this.app[method.toLowerCase()](route, wrappedHandler); } } else { this.app.use(route, wrappedHandler); } } } else { this.app.use(wrappedHandler); } } wrapHandler(loaded) { return (req, res, next) => { loaded.instances++; try { const result = loaded.handler(req, res, (error) => { if (error) { loaded.errors++; loaded.lastError = error; this.emit('middleware:execution:error', { id: loaded.id, error: error.message }); } next(error); }); if (result && typeof result.catch === 'function') { result.catch((error) => { loaded.errors++; loaded.lastError = error; this.emit('middleware:execution:error', { id: loaded.id, error: error.message }); next(error); }); } } catch (error) { loaded.errors++; loaded.lastError = error; this.emit('middleware:execution:error', { id: loaded.id, error: error.message }); next(error); } }; } async calculateChecksumAsync(filePath) { if (!this.config.security.checksum) { return ''; } const fullPath = path.resolve(this.config.middlewarePath, filePath); const content = await fs.readFile(fullPath); return crypto.createHash('sha256').update(content).digest('hex'); } storeVersion(loaded) { const versions = this.versions.get(loaded.id) || new Map(); versions.set(loaded.version, loaded); if (versions.size > this.config.versioning.maxVersions) { const sortedVersions = Array.from(versions.entries()) .sort((a, b) => b[1].loadedAt.getTime() - a[1].loadedAt.getTime()); const toKeep = sortedVersions.slice(0, this.config.versioning.maxVersions); versions.clear(); for (const [version, middleware] of toKeep) { versions.set(version, middleware); } } this.versions.set(loaded.id, versions); } async rollbackAsync(middlewareId, targetVersion) { const versions = this.versions.get(middlewareId); if (!versions || versions.size === 0) { return null; } let versionToLoad; if (targetVersion) { versionToLoad = versions.get(targetVersion); } else { const sortedVersions = Array.from(versions.values()) .sort((a, b) => b.loadedAt.getTime() - a.loadedAt.getTime()); if (sortedVersions.length > 1) { versionToLoad = sortedVersions[1]; } } if (!versionToLoad) { return null; } this.middlewares.set(middlewareId, versionToLoad); this.emit('middleware:rollback', { id: middlewareId, version: versionToLoad.version }); return versionToLoad.version; } startWatching() { this.watcher = setInterval(async () => { await this.checkForChangesAsync(); }, this.config.watchInterval); fs.watch(this.config.configPath, async (eventType) => { if (eventType === 'change') { this.emit('manifest:changed'); await this.reloadManifestAsync(); } }).on('error', error => { this.emit('watch:error', error); }); } async checkForChangesAsync() { if (!this.manifest) return; for (const definition of this.manifest.middlewares) { if (!definition.enabled) continue; const loaded = this.middlewares.get(definition.id); if (!loaded) { await this.loadMiddleware(definition); continue; } const currentChecksum = await this.calculateChecksumAsync(definition.path); if (currentChecksum !== loaded.checksum) { this.emit('middleware:changed', { id: definition.id, previousChecksum: loaded.checksum, newChecksum: currentChecksum }); if (this.config.hotReload) { await this.reloadMiddlewareAsync(definition.id); } } } } async reloadManifestAsync() { const previousManifest = this.manifest; try { await this.loadManifest(); const changes = this.findManifestChanges(previousManifest, this.manifest); for (const change of changes) { switch (change.type) { case 'added': await this.loadMiddleware(change.definition); break; case 'removed': await this.unloadMiddlewareAsync(change.id); break; case 'updated': await this.reloadMiddlewareAsync(change.id); break; case 'disabled': await this.disableMiddlewareAsync(change.id); break; case 'enabled': await this.enableMiddlewareAsync(change.id); break; } } } catch (error) { this.emit('manifest:reload:error', error); this.manifest = previousManifest; } } findManifestChanges(oldManifest, newManifest) { const changes = []; if (!oldManifest || !newManifest) return changes; const oldMap = new Map(oldManifest.middlewares.map(m => [m.id, m])); const newMap = new Map(newManifest.middlewares.map(m => [m.id, m])); for (const [id, newDef] of newMap) { const oldDef = oldMap.get(id); if (!oldDef) { changes.push({ type: 'added', id, definition: newDef }); } else if (JSON.stringify(oldDef) !== JSON.stringify(newDef)) { if (oldDef.enabled && !newDef.enabled) { changes.push({ type: 'disabled', id }); } else if (!oldDef.enabled && newDef.enabled) { changes.push({ type: 'enabled', id, definition: newDef }); } else { changes.push({ type: 'updated', id, definition: newDef }); } } } for (const [id, oldDef] of oldMap) { if (!newMap.has(id)) { changes.push({ type: 'removed', id }); } } return changes; } async reloadMiddlewareAsync(middlewareId) { const definition = this.manifest?.middlewares.find(m => m.id === middlewareId); if (!definition) { return { success: false, error: new Error(`Middleware ${middlewareId} not found in manifest`) }; } return this.loadMiddleware(definition); } async unloadMiddlewareAsync(middlewareId) { const loaded = this.middlewares.get(middlewareId); if (!loaded) { return false; } this.middlewares.delete(middlewareId); this.emit('middleware:unloaded', { id: middlewareId, version: loaded.version }); return true; } async disableMiddlewareAsync(middlewareId) { const loaded = this.middlewares.get(middlewareId); if (!loaded) { return false; } loaded.definition.enabled = false; this.emit('middleware:disabled', { id: middlewareId }); return true; } async enableMiddlewareAsync(middlewareId) { const definition = this.manifest?.middlewares.find(m => m.id === middlewareId); if (!definition) { return false; } definition.enabled = true; const result = await this.loadMiddleware(definition); return result.success; } getMiddlewares() { return new Map(this.middlewares); } getMiddleware(middlewareId) { return this.middlewares.get(middlewareId); } getVersions(middlewareId) { return this.versions.get(middlewareId); } getManifest() { return this.manifest; } createMockRequest(config) { return { method: config.method || 'GET', url: config.url || '/', headers: config.headers || {}, params: config.params || {}, query: config.query || {}, body: config.body || {} }; } createMockResponse() { const res = { statusCode: 200, headers: {} }; res.status = (code) => { res.statusCode = code; return res; }; res.json = (data) => { res.body = data; return res; }; res.send = (data) => { res.body = data; return res; }; res.setHeader = (name, value) => { res.headers[name] = value; return res; }; return res; } stop() { if (this.watcher) { clearInterval(this.watcher); } this.removeAllListeners(); } } exports.DynamicMiddlewareLoader = DynamicMiddlewareLoader; function createDynamicLoader(config) { return new DynamicMiddlewareLoader(config); } //# sourceMappingURL=dynamic-loader.js.map