UNPKG

yml-mvc-router

Version:

A configurable, Express-compatible routing module that maps routes from YAML to controllers following the MVC pattern

137 lines (116 loc) 4.18 kB
const path = require('path'); const fs = require('fs'); /** * Resolves controllers and middlewares from filesystem */ class Resolver { constructor(config) { this.config = config; this.controllerCache = new Map(); this.middlewareCache = new Map(); } /** * Resolve controller by name * @param {string} controllerName - Controller name (e.g., "UserController") * @returns {Object} Controller module exports */ resolveController(controllerName) { if (this.controllerCache.has(controllerName)) { return this.controllerCache.get(controllerName); } // Use custom resolver if provided if (this.config.resolveController) { const controller = this.config.resolveController(controllerName); this.controllerCache.set(controllerName, controller); return controller; } // Default resolution: look for file in controllers directory const controllerPath = this.getControllerPath(controllerName); if (!fs.existsSync(controllerPath)) { throw new Error(`Controller file not found: ${controllerPath}`); } try { // Clear require cache in development for hot reload if (this.config.watch) { delete require.cache[require.resolve(controllerPath)]; } const controller = require(controllerPath); this.controllerCache.set(controllerName, controller); return controller; } catch (error) { throw new Error(`Failed to load controller ${controllerName}: ${error.message}`); } } /** * Get full path to controller file * @param {string} controllerName - Controller name * @returns {string} Full file path */ getControllerPath(controllerName) { // Try each allowed extension for (const ext of this.config.controllerExt) { const filePath = path.join(this.config.controllers, `${controllerName}${ext}`); if (fs.existsSync(filePath)) { return filePath; } } // Default to .js if not found return path.join(this.config.controllers, `${controllerName}.js`); } /** * Resolve middleware by name or configuration object * @param {string|Object} middlewareRef - Middleware name or config object * @returns {Function} Middleware function */ resolveMiddleware(middlewareRef) { let name, options = {}; if (typeof middlewareRef === 'string') { name = middlewareRef; } else if (typeof middlewareRef === 'object' && middlewareRef.name) { name = middlewareRef.name; options = middlewareRef.options || {}; } else { throw new Error('Invalid middleware reference'); } const cacheKey = `${name}:${JSON.stringify(options)}`; if (this.middlewareCache.has(cacheKey)) { return this.middlewareCache.get(cacheKey); } const middlewarePath = path.join(this.config.middlewares, `${name}.js`); if (!fs.existsSync(middlewarePath)) { throw new Error(`Middleware file not found: ${middlewarePath}`); } try { // Clear require cache in development if (this.config.watch) { delete require.cache[require.resolve(middlewarePath)]; } const middlewareModule = require(middlewarePath); let middleware; if (typeof middlewareModule === 'function') { // If module exports a function, call it with options middleware = middlewareModule(options); } else if (middlewareModule.handler && typeof middlewareModule.handler === 'function') { // If module exports { handler, ... } middleware = middlewareModule.handler(options); } else { throw new Error(`Invalid middleware export in ${name}.js`); } if (typeof middleware !== 'function') { throw new Error(`Middleware ${name} must export or return a function`); } this.middlewareCache.set(cacheKey, middleware); return middleware; } catch (error) { throw new Error(`Failed to load middleware ${name}: ${error.message}`); } } /** * Clear caches (useful for hot reload) */ clearCache() { this.controllerCache.clear(); this.middlewareCache.clear(); } } module.exports = Resolver;