// Type-level parser for route template literals. // // Route templates declared as string literals (e.g. "/api/Todo/{id: number}") already // encode their param names and types. Because the routes are `const` string literals, // `typeof someRoute` is the literal type, so we can parse it at the type level — mirroring // the runtime parser in route-param.ts — and derive a typed params object. // // Token semantics (matching route-param.ts): // {key: type} -> required path param // {$key?: type} -> optional query param (the leading `$` stays part of the key, and the // trailing `?` before `:` marks it optional) // type resolves string/number/boolean to the TS type; anything else falls back to `any`. type RouteTypeMap = { string: string; number: number; boolean: boolean; any: any; }; type ResolveRouteType = T extends keyof RouteTypeMap ? RouteTypeMap[T] : any; type TrimRoute = S extends ` ${infer R}` ? TrimRoute : S extends `${infer L} ` ? TrimRoute : S; // Scan every `{...}` block. Required and optional params are collected in two passes so each // can be expressed with its own mapped type — a single mapped type cannot conditionally make // individual keys optional. Intersecting the two passes yields the correct required + optional shape. type RequiredParams = S extends `${string}{${infer Body}}${infer Rest}` ? Body extends `${string}?:${string}` ? RequiredParams // optional -> handled by OptionalParams : Body extends `${infer K}:${infer T}` ? { [P in TrimRoute]: ResolveRouteType>> } & RequiredParams : RequiredParams : unknown; // identity for `&` type OptionalParams = S extends `${string}{${infer Body}}${infer Rest}` ? Body extends `${infer K}?:${infer T}` ? { [P in TrimRoute]?: ResolveRouteType>> | null } & OptionalParams : OptionalParams : unknown; // identity for `&` type Prettify = { [K in keyof T]: T[K] } & {}; /** * Extracts a typed params object from a route template literal. * * @example * RouteParams<"/api/Todo/{id: number}"> * // -> { id: number } * RouteParams<"/api/Todos?{$search?: string}&{$pageSize?: number}"> * // -> { $search?: string | null; $pageSize?: number | null } */ export type ControllerRouteParams = Prettify & OptionalParams>; type ControllerInstance = T extends abstract new (...args: Array) => infer TInstance ? TInstance : T; /** * Resolves a controller (class or instance type) to the route it is declared for, read from the * phantom `__route` brand that `QueryController`/`CommandController` carry via their `TRoute` type * parameter. Falls back to `string` for controllers that don't declare a specific route. */ export type ControllerRoute = ControllerInstance extends { readonly __route?: infer TRoute extends string; } ? TRoute : string;