/** * Mandu Contract Handler * Contract 기반 타입 안전 핸들러 정의 * * Elysia 패턴 채택: Contract → Handler 타입 자동 추론 */ import type { z } from "zod"; import type { ContractSchema, ContractMethod, MethodRequestSchema, } from "./schema"; import type { InferResponseSchema } from "./types"; /** * Typed request context for a handler * Contract에서 추론된 타입으로 요청 컨텍스트 제공 */ export interface TypedContext< TQuery = unknown, TBody = unknown, TParams = unknown, THeaders = unknown, > { /** Parsed and validated query parameters */ query: TQuery; /** Parsed and validated request body */ body: TBody; /** Parsed and validated path parameters */ params: TParams; /** Parsed and validated headers */ headers: THeaders; /** Original Request object */ request: Request; /** Route path (e.g., "/users/:id") */ path: string; /** HTTP method */ method: ContractMethod; } /** * Infer context type from method schema */ type InferContextFromMethod = T extends MethodRequestSchema ? TypedContext< T["query"] extends z.ZodTypeAny ? z.infer : undefined, T["body"] extends z.ZodTypeAny ? z.infer : undefined, T["params"] extends z.ZodTypeAny ? z.infer : undefined, T["headers"] extends z.ZodTypeAny ? z.infer : undefined > : TypedContext; /** * Handler function type for a specific method */ export type HandlerFn = ( ctx: TContext ) => TResponse | Promise; /** * Infer response type union from contract response schema */ type InferResponseUnion = { [K in keyof TResponse]: InferResponseSchema; }[keyof TResponse]; /** * Handler definition for all methods in a contract * * @example * ```typescript * const contract = Mandu.contract({ * request: { * GET: { query: z.object({ page: z.number() }) }, * POST: { body: z.object({ name: z.string() }) }, * }, * response: { * 200: z.object({ users: z.array(z.string()) }), * 201: z.object({ user: z.string() }), * }, * }); * * // handlers is typed: { GET: (ctx) => ..., POST: (ctx) => ... } * const handlers = Mandu.handler(contract, { * GET: (ctx) => { * // ctx.query is { page: number } * return { users: [] }; * }, * POST: (ctx) => { * // ctx.body is { name: string } * return { user: ctx.body.name }; * }, * }); * ``` */ export type ContractHandlers = { [M in ContractMethod as M extends keyof T["request"] ? M : never]?: HandlerFn< InferContextFromMethod< T["request"][M] extends MethodRequestSchema ? T["request"][M] : undefined >, InferResponseUnion >; }; /** * Define type-safe handlers for a contract * * @param contract - The contract schema * @param handlers - Handler implementations for each method * @returns Typed handler object * * @example * ```typescript * const handlers = defineHandler(userContract, { * GET: async (ctx) => { * const { page, limit } = ctx.query; // Typed! * const users = await db.users.findMany({ skip: page * limit, take: limit }); * return { data: users }; * }, * POST: async (ctx) => { * const user = await db.users.create({ data: ctx.body }); // Typed! * return { data: user }; * }, * }); * ``` */ export function defineHandler( _contract: T, handlers: ContractHandlers ): ContractHandlers { return handlers; } /** * Handler result with status code * 응답에 상태 코드를 명시적으로 지정 */ export interface HandlerResult { status: number; data: T; headers?: Record; } /** * Create a typed response with status code * * @example * ```typescript * const handler = defineHandler(contract, { * POST: async (ctx) => { * const user = await createUser(ctx.body); * return response(201, { data: user }); * }, * }); * ``` */ export function response( status: number, data: T, headers?: Record ): HandlerResult { return { status, data, headers }; } /** * Type guard for HandlerResult */ export function isHandlerResult(value: unknown): value is HandlerResult { return ( typeof value === "object" && value !== null && "status" in value && "data" in value ); } /** * Extract method-specific handler type from contract * * @example * ```typescript * type GetHandler = ExtractHandler; * // (ctx: { query: { page: number }, ... }) => Promise<{ data: User[] }> * ``` */ export type ExtractHandler< T extends ContractSchema, M extends ContractMethod, > = M extends keyof T["request"] ? HandlerFn< InferContextFromMethod< T["request"][M] extends MethodRequestSchema ? T["request"][M] : undefined >, InferResponseUnion > : never; /** * Utility to create a handler context from raw request * 런타임에서 Request → TypedContext 변환 */ export async function createContext< TQuery = unknown, TBody = unknown, TParams = unknown, THeaders = unknown, >( request: Request, path: string, method: ContractMethod, parsedData: { query?: TQuery; body?: TBody; params?: TParams; headers?: THeaders; } = {} ): Promise> { return { query: parsedData.query as TQuery, body: parsedData.body as TBody, params: parsedData.params as TParams, headers: parsedData.headers as THeaders, request, path, method, }; } /** * Combined contract + handler definition * Contract와 Handler를 한 번에 정의 * * @example * ```typescript * export default Mandu.route({ * contract: { * request: { * GET: { query: z.object({ id: z.string() }) }, * }, * response: { * 200: z.object({ user: UserSchema }), * }, * }, * handler: { * GET: async (ctx) => { * const user = await db.users.findUnique({ where: { id: ctx.query.id } }); * return { user }; * }, * }, * }); * ``` */ export interface RouteDefinition { contract: T; handler: ContractHandlers; } /** * Define a complete route with contract and handler */ export function defineRoute( definition: RouteDefinition ): RouteDefinition { return definition; }