import { Controller } from "./controller.js"; import { query } from "./query.js"; import type { ControllerRoute, ControllerRouteParams } from "./route-params.js"; // public @query export abstract class QueryController extends Controller { // Phantom marker carrying the route this controller is declared for (supplied via TRoute). // It lets `@route` verify the decorator argument and lets `QueryEndpoint` derive the route. // `declare` means it is type-only — nothing is emitted. declare public readonly __route?: TRoute; public abstract override execute(...params: Array): Promise; } // public // Extracts the response body type (TResBody) from a concrete QueryController subclass, // given either the class (constructor) or an instance type. export type QueryControllerResponseBody = T extends abstract new (...args: Array) => infer TInstance ? TInstance extends QueryController ? TResBody : never : T extends QueryController ? TResBody : never; /** * Endpoint contract for a query (read) operation, derived entirely from the concrete * `QueryController` that serves it: the route (from the controller's declared route), its resolved * params, and the response body. A single generic is all a client passes (e.g. to `RpcClient.query`), * and there is no separate route argument that could drift from the controller. * * @example * type GetTodo = QueryEndpoint; * // route: ; params: { id: number }; res: */ export type QueryEndpoint< TController extends QueryController | (abstract new (...args: Array) => QueryController) > = { readonly route: ControllerRoute; readonly params: ControllerRouteParams>; readonly res: QueryControllerResponseBody; };