UNPKG

@gati-framework/runtime

Version:

Gati runtime execution engine for running handler-based applications

104 lines 3.68 kB
/** * @module runtime/hot-loader * @description Hot loading system for handlers */ import { watch } from 'chokidar'; import { resolve, relative } from 'path'; import { existsSync } from 'fs'; export class HotLoader { app; srcDir; watcher; handlerCache = new Map(); constructor(app, options) { this.app = app; this.srcDir = options.srcDir; if (options.enabled) { this.startWatching(); } } startWatching() { const handlersDir = resolve(this.srcDir, 'handlers'); if (!existsSync(handlersDir)) return; this.watcher = watch(`${handlersDir}/**/*.{ts,js}`, { ignoreInitial: false, persistent: true }); this.watcher.on('add', (filePath) => this.loadHandler(filePath)); this.watcher.on('change', (filePath) => this.reloadHandler(filePath)); this.watcher.on('unlink', (filePath) => this.unloadHandler(filePath)); } async loadHandler(filePath) { try { const relativePath = relative(this.srcDir, filePath); const route = this.pathToRoute(relativePath); // Clear module cache for hot reload delete require.cache[require.resolve(filePath)]; const module = await import(`file://${filePath}?t=${Date.now()}`); const method = module.METHOD || 'GET'; const customRoute = module.ROUTE; const handler = this.findHandler(module); if (handler) { const finalRoute = customRoute ? this.buildFullRoute(relativePath, customRoute) : route; // Register/update route this.app.registerRoute(method, finalRoute, handler); this.handlerCache.set(filePath, { method, route: finalRoute, handler }); console.log(`🔥 Loaded ${method} ${finalRoute}`); } } catch (error) { console.error(`❌ Failed to load handler ${filePath}:`, error); } } async reloadHandler(filePath) { console.log(`🔄 Reloading ${relative(this.srcDir, filePath)}`); await this.loadHandler(filePath); } unloadHandler(filePath) { const cached = this.handlerCache.get(filePath); if (cached) { this.app.unregisterRoute(cached.method, cached.route); this.handlerCache.delete(filePath); console.log(`🗑️ Unloaded ${cached.method} ${cached.route}`); } } findHandler(module) { for (const [key, value] of Object.entries(module)) { if (key.toLowerCase().includes('handler') && typeof value === 'function') { return value; } } return null; } pathToRoute(relativePath) { let route = relativePath .replace(/\\/g, '/') .replace(/^handlers\//, '') .replace(/\.ts$/, '') .replace(/\.js$/, '') .replace(/\/index$/, ''); route = route.replace(/\[([^\]]+)\]/g, ':$1'); if (!route.startsWith('/')) { route = '/' + route; } return route === '/' ? '/' : route; } buildFullRoute(relativePath, customRoute) { const parentPath = relativePath .replace(/\\/g, '/') .replace(/^handlers\//, '') .replace(/\/[^/]*$/, '') .replace(/\/index$/, ''); if (!parentPath) { return customRoute; } return `/${parentPath}${customRoute}`; } stop() { if (this.watcher) { this.watcher.close(); } } } //# sourceMappingURL=hot-loader.js.map