@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
JavaScript
"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