UNPKG

@legend-ssr/router

Version:

High-performance radix tree router for Legend framework

211 lines (172 loc) 6.4 kB
import { LegendApplication } from '@legend-ssr/common'; import { RadixTree, RouteHandler, RouteMatch } from './radix-tree'; export interface LegendRouterOptions { caseSensitive?: boolean; strict?: boolean; mergeParams?: boolean; } // Updated interface to match Legend application's route structure export interface ControllerRoute { controller: any; controllerPath: string; method: string; path: string; handler: Function; fullPath: string; } export class LegendRouter { private trees: Map<string, RadixTree> = new Map(); private options: LegendRouterOptions; private registeredRoutes: ControllerRoute[] = []; constructor(options: LegendRouterOptions = {}) { this.options = { caseSensitive: false, strict: false, mergeParams: true, ...options, }; } /** * Build router from Legend application */ static fromLegendApplication(app: LegendApplication, options?: LegendRouterOptions): LegendRouter { const router = new LegendRouter(options); console.log('[LegendRouter] Building router from Legend application...', app); // Get all controller routes from Legend application without stripping basePath const routes: ControllerRoute[] = app.getControllerRoutes(); console.log('[LegendRouter] Raw routes from Legend app:', routes); console.log('[LegendRouter] Number of routes found:' , routes.length); if (routes.length === 0) { console.warn('[LegendRouter] WARNING: No routes found in Legend application!'); console.log('[LegendRouter] Checking controllers directly...'); // Debug: Check if controllers are registered const controllers: any[] = app.getAllControllers(); console.log('[LegendRouter] Controllers found:', controllers.length); controllers.forEach((controller: any, index: number) => { console.log(`[LegendRouter] Controller ${index}:`, controller.constructor.name); }); } // Store routes for reference router.registeredRoutes = routes; // Add each route to the appropriate method tree routes.forEach((route: ControllerRoute, index: number) => { console.log(`[LegendRouter] Registering route ${index + 1}/${routes.length}:`, { method: route.method, fullPath: route.fullPath, controllerPath: route.controllerPath }); router.addRoute(route.method, route.fullPath, { method: route.method, handler: route.handler, controller: route.controller, controllerPath: route.controllerPath, fullPath: route.fullPath, originalRoute: route }); console.log(`[LegendRouter] Registering route: method=${route.method}, fullPath=${route.fullPath}`); }); console.log('[LegendRouter] Router built successfully with', routes.length, 'routes'); console.log('[LegendRouter] Route trees created for methods:', Array.from(router.trees.keys())); console.log(`[LegendRouter] Route trees after registration:`, Array.from(router.trees.keys())); return router; } /** * Add a route to the router */ addRoute(method: string, path: string, handler: RouteHandler): void { const normalizedMethod = method.toUpperCase(); if (!this.trees.has(normalizedMethod)) { this.trees.set(normalizedMethod, new RadixTree()); console.log(`[LegendRouter] Created new tree for method: ${normalizedMethod}`); } const tree = this.trees.get(normalizedMethod)!; const normalizedPath = this.normalizePath(path); console.log(`[LegendRouter] Adding route: ${normalizedMethod} ${normalizedPath}`); tree.insert(normalizedPath, handler); } /** * Find a matching route */ match(method: string, path: string): RouteMatch | null { const normalizedMethod = method.toUpperCase(); const tree = this.trees.get(normalizedMethod); console.log(`[LegendRouter] Looking for route: ${normalizedMethod} ${path}`); console.log(`[LegendRouter] Available trees:`, Array.from(this.trees.keys())); if (!tree) { console.log(`[LegendRouter] No tree found for method: ${normalizedMethod}`); return null; } const normalizedPath = this.normalizePath(path); console.log(`[LegendRouter] Normalized path: ${normalizedPath}`); const result = tree.search(normalizedPath); if (result) { console.log(`[LegendRouter] Route matched:`, { method: normalizedMethod, path: normalizedPath, params: result.params }); } else { console.log(`[LegendRouter] No match found for: ${normalizedMethod} ${normalizedPath}`); } return result; } /** * Get all registered routes */ getAllRoutes(): Array<{ method: string; routes: RouteHandler[] }> { const allRoutes: Array<{ method: string; routes: RouteHandler[] }> = []; for (const [method, tree] of this.trees) { allRoutes.push({ method, routes: tree.getAllRoutes(), }); } return allRoutes; } /** * Get original controller routes */ getControllerRoutes(): ControllerRoute[] { return this.registeredRoutes; } /** * Get route statistics */ getStats(): { totalRoutes: number; methodCounts: Record<string, number>; methods: string[]; routes: Array<{ method: string; path: string; controller: string }>; } { let totalRoutes = 0; const methodCounts: Record<string, number> = {}; for (const [method, tree] of this.trees) { const routes = tree.getAllRoutes(); methodCounts[method] = routes.length; totalRoutes += routes.length; } // Create detailed route list const routes = this.registeredRoutes.map(route => ({ method: route.method, path: route.fullPath, controller: route.controller?.constructor?.name || 'Unknown' })); return { totalRoutes, methodCounts, methods: Array.from(this.trees.keys()), routes }; } private normalizePath(path: string): string { // Remove query parameters const cleanPath = path.split('?')[0]; // Handle case sensitivity const normalizedPath = this.options.caseSensitive ? cleanPath : cleanPath.toLowerCase(); // Handle trailing slash if (!this.options.strict && normalizedPath.length > 1 && normalizedPath.endsWith('/')) { return normalizedPath.slice(0, -1); } return normalizedPath || '/'; } }