import type { IRouteConfig, IRouteAction, IRouteTarget } from './models/route-types.js'; import { logger } from '../../core/utils/logger.js'; /** * Preprocesses routes before sending them to Rust. * * Strips non-serializable fields (functions, callbacks) and classifies * routes that must be handled by TypeScript (socket-handler, dynamic host/port). */ export class RoutePreprocessor { /** * Map of route name/id → original route config (with JS functions preserved). * Used by the socket handler server to look up the original handler. */ private originalRoutes = new Map(); /** * Preprocess routes for the Rust binary. * * - Routes with `socketHandler` callbacks are marked as socket-handler type * (Rust will relay these back to TS) * - Routes with dynamic `host`/`port` functions are converted to socket-handler * type (Rust relays, TS resolves the function) * - Non-serializable fields are stripped * - Original routes are preserved in the local map for handler lookup */ public preprocessForRust(routes: IRouteConfig[]): IRouteConfig[] { this.originalRoutes.clear(); return routes.map((route, index) => this.preprocessRoute(route, index)); } /** * Get the original route config (with JS functions) by route name or id. */ public getOriginalRoute(routeKey: string): IRouteConfig | undefined { return this.originalRoutes.get(routeKey); } /** * Get all original routes that have socket handlers or dynamic functions. */ public getHandlerRoutes(): Map { return new Map(this.originalRoutes); } private preprocessRoute(route: IRouteConfig, index: number): IRouteConfig { const routeKey = route.name || route.id || `route_${index}`; // Check if this route needs TS-side handling const needsTsHandling = this.routeNeedsTsHandling(route); if (needsTsHandling) { // Store the original route for handler lookup this.originalRoutes.set(routeKey, route); } // Create a clean copy for Rust const cleanRoute: IRouteConfig = { ...route, action: this.cleanAction(route.action, routeKey, needsTsHandling), }; // Ensure we have a name for handler lookup if (!cleanRoute.name && !cleanRoute.id) { cleanRoute.name = routeKey; } return cleanRoute; } private routeNeedsTsHandling(route: IRouteConfig): boolean { // Socket handler routes always need TS if (route.action.type === 'socket-handler' && route.action.socketHandler) { return true; } // Routes with dynamic host/port functions need TS if (route.action.targets) { for (const target of route.action.targets) { if (typeof target.host === 'function' || typeof target.port === 'function') { return true; } } } return false; } private cleanAction(action: IRouteAction, routeKey: string, needsTsHandling: boolean): IRouteAction { const cleanAction: IRouteAction = { ...action }; if (needsTsHandling) { // Convert to socket-handler type for Rust (Rust will relay back to TS) cleanAction.type = 'socket-handler'; // Remove the JS handler (not serializable) delete (cleanAction as any).socketHandler; } // Clean targets - replace functions with static values if (cleanAction.targets) { cleanAction.targets = cleanAction.targets.map(t => this.cleanTarget(t)); } return cleanAction; } private cleanTarget(target: IRouteTarget): IRouteTarget { const clean: IRouteTarget = { ...target }; // Replace function host with placeholder if (typeof clean.host === 'function') { clean.host = 'localhost'; } // Replace function port with placeholder if (typeof clean.port === 'function') { clean.port = 0; } return clean; } }