import type { LoggerFriendly, LoggerFriendlyOptions } from "#Source/log/index.ts" import { Logger } from "#Source/log/index.ts" import type { Standard } from "#Source/validation/index.ts" import type { ApiHandler } from "./api-handler.ts" import type { ApiSchema, AnyApiSchema } from "./api-schema.ts" export interface ApiOptions< Path extends string, Method extends "get" | "post", InputQuerySchema extends Standard.Schema, InputBodySchema extends Standard.Schema, OutputErrorSchema extends Standard.Schema, OutputSuccessSchema extends Standard.Schema, > extends LoggerFriendlyOptions { apiSchema: ApiSchema< Path, Method, InputQuerySchema, InputBodySchema, OutputErrorSchema, OutputSuccessSchema > apiHandler: ApiHandler< Path, Method, InputQuerySchema, InputBodySchema, OutputErrorSchema, OutputSuccessSchema > } /** * @description Api 应该严格遵循 {@link ApiSchema} 的描述进行实现。 * * 当一个请求被 {@link ApiServer} 按照路径(Path)和方法(Method)匹配到一个接口后, * 便会开始按照 {@link Api} 的逻辑对请求进行处理。 * * Api 的整个处理流程如下: * * 请求 -> 构造上下文 -> 处理器(Api Runner) -> 返回结果(Result) * * 其中: * - 构造上下文包括: * - 从处理原始请求并从中提取输入数据,主要来自 query 和 body。 * - 校验输入数据,确保输入数据符合 {@link ApiSchema} 的描述。 * - 处理器是 Api 的核心逻辑,负责对请求进行处理,可以包含副作用,比如查询数据库、调用其他服务等。 * - 一般情况下,处理器不应该直接返回数据给前端,而应该通过 {@link ErrorResult} 或 {@link SuccessResult} * 定义自己想要返回什么,然后 {@link ApiServer} 会负责执行返回逻辑,将结果返回给前端,这样做既可以使 Api 的 * 核心逻辑更纯粹,也可以保证所有的接口始终给前端返回一致的数据结构。 */ export class Api< Path extends string, Method extends "get" | "post", InputQuerySchema extends Standard.Schema, InputBodySchema extends Standard.Schema, OutputErrorSchema extends Standard.Schema, OutputSuccessSchema extends Standard.Schema, > implements LoggerFriendly { readonly logger: Logger private readonly apiSchema: ApiSchema< Path, Method, InputQuerySchema, InputBodySchema, OutputErrorSchema, OutputSuccessSchema > private readonly apiHandler: ApiHandler< Path, Method, InputQuerySchema, InputBodySchema, OutputErrorSchema, OutputSuccessSchema > constructor( options: ApiOptions< Path, Method, InputQuerySchema, InputBodySchema, OutputErrorSchema, OutputSuccessSchema >, ) { this.logger = Logger.fromOptions(options).setDefaultName("Api") this.apiSchema = options.apiSchema this.apiHandler = options.apiHandler } getApiSchema(): ApiSchema< Path, Method, InputQuerySchema, InputBodySchema, OutputErrorSchema, OutputSuccessSchema > { return this.apiSchema } getApiHandler(): ApiHandler< Path, Method, InputQuerySchema, InputBodySchema, OutputErrorSchema, OutputSuccessSchema > { return this.apiHandler } } export const createApi = < Path extends string, Method extends "get" | "post", InputQuerySchema extends Standard.Schema, InputBodySchema extends Standard.Schema, OutputErrorSchema extends Standard.Schema, OutputSuccessSchema extends Standard.Schema, >( options: ApiOptions< Path, Method, InputQuerySchema, InputBodySchema, OutputErrorSchema, OutputSuccessSchema >, ): Api => { return new Api(options) } // oxlint-disable-next-line typescript/no-explicit-any export type AnyApi = Api export type ApiFromApiSchema = Target extends ApiSchema< infer Path, infer Method, infer InputQuerySchema, infer InputBodySchema, infer OutputErrorSchema, infer OutputSuccessSchema > ? Api : never