import type { RouteDefinition, RouteModule } from '../routes/types.ts'; import { ApplicationError } from '../errors/index.ts'; import { ApplicationRouteStream } from './route-stream.ts'; import type { BaseStream } from './stream/base-stream.ts'; /** Canonical request produced by every transport adapter. */ export type ApplicationRequest = { /** Exact application route name. */ path: string; /** Transport-decoded application payload. */ body?: unknown; /** Optional correlation ID supplied by a multiplexed transport. */ requestId?: string; }; /** Exact-match application routing shared by every wire transport. */ export class ApplicationRouter { /** @param routes - Validated route table keyed by exact path. */ private constructor(private readonly routes: ReadonlyMap) {} /** * Load and validate the complete route table before accepting traffic. * * @param routeModules - Route modules whose handlers will be registered. * @returns A ready-to-dispatch router instance. */ static async create(routeModules: RouteModule[]): Promise { const routes = new Map(); // Collect routes from every module and reject duplicates at startup. for (const routeModule of routeModules) { for (const route of await routeModule.getRoutes()) { ApplicationRouter.assertValidPath(route.url); if (routes.has(route.url)) { throw new Error(`Duplicate application route: ${route.url}`); } routes.set(route.url, route); } } return new ApplicationRouter(routes); } /** * Execute one route using a request-scoped facade over the connection. * * @param request - Transport-normalized application request. * @param connection - Shared connection stream for this transport session. */ async dispatch(request: ApplicationRequest, connection: BaseStream): Promise { const route = this.routes.get(request.path); if (!route) { throw new ApplicationError(404, `No route found for ${request.path}`); } const stream = new ApplicationRouteStream(connection, request.body, request.requestId); await route.handler(stream); } /** * Enforce the deliberately small exact-path routing grammar at startup. * * @param path - Candidate route path to validate. */ private static assertValidPath(path: string): void { if (!path.startsWith('/') || path.length === 1 || path.includes(':') || path.includes('?') || path.includes('#')) { throw new Error(`Invalid application route "${path}": routes must be exact paths beginning with /`); } } }