Skip to content

Feature request: Implement Route Management System for Event Handler #4139

Open
@dreamorosi

Description

@dreamorosi

Important

This issue is dependent on #3971 and is on hold until that's merged.

Use case

As part of the Event Handler implementation (#3251), we need a system to store, organize, and manage routes that are registered through the BaseRouter's HTTP method decorators (get(), post(), etc.).

The Route Management System provides the internal infrastructure to:

  1. Store registered routes in an organized, efficient manner
  2. Detect route conflicts and provide helpful warnings to developers
  3. Organize routes for efficient lookup and processing
  4. Maintain route metadata needed for resolution and debugging

This system acts as the storage layer between route registration (BaseRouter) and route matching (separate issue), ensuring routes are properly organized and accessible.

Solution/User Experience

Note

The code snippets below are provided as reference only - they are not exhaustive and final implementation might vary.

Core Route Management Architecture

// Internal Route representation
class Route {
  readonly id: string;                    // Unique route identifier
  readonly method: string;                // 'GET', 'POST', etc.
  readonly path: string;                  // Original path: '/users/:id'
  readonly handler: RouteHandler;         // The function to execute
  readonly registeredAt: Date;            // When route was registered
  
  constructor(method: string, path: string, handler: RouteHandler) {
    this.id = `${method}:${path}`;
    this.method = method.toUpperCase();
    this.path = path;
    this.handler = handler;
    this.registeredAt = new Date();
  }
}

// Route storage and organization
class RouteRegistry {
  private routes: Map<string, Route> = new Map();
  private routesByMethod: Map<string, Route[]> = new Map();
  
  // Add a route to the registry
  addRoute(route: Route): void {
    // Conflict detection
    if (this.routes.has(route.id)) {
      this.handleRouteConflict(route);
    }
    
    // Store route
    this.routes.set(route.id, route);
    
    // Organize by method for efficient lookup
    if (!this.routesByMethod.has(route.method)) {
      this.routesByMethod.set(route.method, []);
    }
    this.routesByMethod.get(route.method)!.push(route);
  }
  
  // Retrieve routes for matching
  getRoutesByMethod(method: string): Route[] {
    return this.routesByMethod.get(method.toUpperCase()) || [];
  }
  
  getAllRoutes(): Route[] {
    return Array.from(this.routes.values());
  }
  
  // Development helpers
  getRouteCount(): number {
    return this.routes.size;
  }
  
  getRouteSummary(): RouteSummary {
    // Provide debugging information
  }
}

Integration with BaseRouter

// BaseRouter uses RouteRegistry for storage
abstract class BaseRouter {
  protected routeRegistry = new RouteRegistry();
  
  get(path: string, handler: RouteHandler): void {
    const route = new Route('GET', path, handler);
    this.routeRegistry.addRoute(route);
  }
  
  post(path: string, handler: RouteHandler): void {
    const route = new Route('POST', path, handler);
    this.routeRegistry.addRoute(route);
  }
  
  // ... other HTTP methods
  
  // Expose routes for matching system (next issue)
  protected getRegisteredRoutes(): Route[] {
    return this.routeRegistry.getAllRoutes();
  }
  
  protected getRoutesByMethod(method: string): Route[] {
    return this.routeRegistry.getRoutesByMethod(method);
  }
}

Route Compilation System

// Convert user-friendly routes to executable patterns
class RouteCompiler {
  private static readonly PARAM_PATTERN = /:(\w+)/g;
  private static readonly SAFE_CHARS = '-._~()\'!*:@,;=+&$';
  private static readonly UNSAFE_CHARS = '%<> \\[\\]{}|^';
  
  static compile(path: string): CompiledRoute {
    // Convert /users/:id/posts/:postId to regex with named groups
    const paramNames: string[] = [];
    
    const regexPattern = path.replace(
      RouteCompiler.PARAM_PATTERN,
      (match, paramName) => {
        paramNames.push(paramName);
        return `(?<${paramName}>[${RouteCompiler.SAFE_CHARS}${RouteCompiler.UNSAFE_CHARS}\\w]+)`;
      }
    );
    
    // Add anchors for exact matching
    const finalPattern = `^${regexPattern}$`;
    
    return {
      originalPath: path,
      regex: new RegExp(finalPattern),
      paramNames,
      isDynamic: paramNames.length > 0
    };
  }
  
  // Validate route patterns for common issues
  static validatePattern(path: string): ValidationResult {
    // Check for invalid characters, malformed parameters, etc.
    const issues: string[] = [];
    
    // Check for malformed parameters
    if (path.includes(':') && !RouteCompiler.PARAM_PATTERN.test(path)) {
      issues.push('Malformed parameter syntax. Use :paramName format.');
    }
    
    // Check for duplicate parameter names
    const params = RouteCompiler.extractParamNames(path);
    const duplicates = params.filter((param, index) => params.indexOf(param) !== index);
    if (duplicates.length > 0) {
      issues.push(`Duplicate parameter names: ${duplicates.join(', ')}`);
    }
    
    return {
      isValid: issues.length === 0,
      issues
    };
  }
  
  private static extractParamNames(path: string): string[] {
    const matches = [...path.matchAll(RouteCompiler.PARAM_PATTERN)];
    return matches.map(match => match[1]);
  }
}

interface CompiledRoute {
  originalPath: string;
  regex: RegExp;
  paramNames: string[];
  isDynamic: boolean;
}

Implementation Details

Scope - In Scope:

  • Route class: Internal representation of registered routes
  • RouteRegistry: Storage and organization of routes
  • Route conflict detection: Basic duplicate detection with warnings
  • Route organization: Efficient storage by HTTP method
  • Development helpers: Route inspection and debugging tools
  • Integration points: Clean interface for route matching system
  • Route compilation (:param → regex)

Scope - Out of Scope (Future Issues):

  • Route pattern matching & execution logic - belongs in Route Matching & Resolution issue
  • Route-specific configuration (CORS, middleware, etc.) - future features

Data Flow:

BaseRouter.get() → Route → RouteRegistry.addRoute() → Storage
RouteRegistry.getRoutesByMethod() → Route Matching System → Resolution

Alternative solutions

N/A

Acknowledgment

Future readers

Please react with 👍 and your use case to help us understand customer demand.

Metadata

Metadata

Assignees

No one assigned

    Labels

    event-handlerThis item relates to the Event Handler Utilityfeature-requestThis item refers to a feature request for an existing or new utilityon-holdThis item is on-hold and will be revisited in the future

    Type

    No type

    Projects

    Status

    On hold

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions