import { generateUuidV4 } from "#Source/identifier/index.ts" import type { ObjectPrettify } from "#Source/type/index.ts" import { Ark } from "#Source/validation/index.ts" import type { ApiCore, ApiCoreStream } from "../api/api-core.ts" import type { ApiHandlerContext } from "../api/api-handler.ts" import type { SuccessResultOptions, ErrorResultOptions } from "../api/api-result.ts" import { SuccessResult, ErrorResult } from "../api/api-result.ts" export interface JsonSuccessResultOptions extends SuccessResultOptions { data: Data } export class JsonSuccessResult extends SuccessResult { declare protected options: JsonSuccessResultOptions constructor(options: JsonSuccessResultOptions) { super(options) this.logger.setDefaultName("JsonSuccessResult") } async handle(apiCore: ApiCore): Promise { const { data } = this.options this.logger.log(`send, ${JSON.stringify(data, null, 2)}`) await apiCore.json(data) this.logger.log(`end`) } } export interface CommonSuccessJson { status: "success" data: Data } export interface CommonJsonSuccessResultOptions extends Omit< JsonSuccessResultOptions>, "data" > { data: Data } export class CommonJsonSuccessResult extends JsonSuccessResult> { declare protected options: JsonSuccessResultOptions> constructor(options: CommonJsonSuccessResultOptions) { super({ ...options, data: { status: "success", data: options.data, }, }) this.logger.setDefaultName("CommonJsonSuccessResult") } } export const commonJsonSuccessResult = ( options: CommonJsonSuccessResultOptions, ): CommonJsonSuccessResult => { return new CommonJsonSuccessResult(options) } export type CommonJsonSuccessResultSchema = Ark.Type<{ status: "success" data: Data }> export type GetDataSchemaFromCommonJsonSuccessResultSchema = Schema extends CommonJsonSuccessResultSchema ? Ark.Type : never export type GetDataFromCommonJsonSuccessResultSchema = Schema extends CommonJsonSuccessResultSchema ? Data : never export const commonJsonSuccessResultSchema = >( dataSchema: Ark.Type, ): CommonJsonSuccessResultSchema => { // arktype's instantiated object schema is compatible here but // not assignable through its generic facade const newResultSchema = Ark.type({ status: "'success'", data: dataSchema, }) as CommonJsonSuccessResultSchema return newResultSchema } export type StreamWrite = (chunk: Buffer | string, encoding: BufferEncoding) => void | Promise export type StreamEnd = () => void | Promise export interface StreamSuccessResultOptions extends SuccessResultOptions { writeHandler: (write: StreamWrite) => Promise } export class StreamSuccessResult extends SuccessResult { declare protected options: StreamSuccessResultOptions protected streamId: string constructor(options: StreamSuccessResultOptions) { super(options) this.logger.setDefaultName("StreamSuccessResult") this.streamId = generateUuidV4() } async handle(apiCore: ApiCore): Promise { const apiCoreStream = await apiCore.stream() const write = this.wrapWrite(apiCoreStream) await this.options.writeHandler(write) const end = this.wrapEnd(apiCoreStream) await end() } /** * @description 把 write 方法包装一下,以达到注入日志等目的。 */ protected wrapWrite(apiCoreStream: ApiCoreStream): StreamWrite { return async (chunk, encoding): Promise => { this.logger.log(`send, id: ${this.streamId}, data: ${String(chunk)}`) await apiCoreStream.write(chunk, encoding) } } /** * @description 把 end 方法包装一下,以达到注入日志等目的。 */ protected wrapEnd(apiCoreStream: ApiCoreStream): StreamEnd { const end = async (): Promise => { this.logger.log(`end, id: ${this.streamId}`) await apiCoreStream.end() } return end } } export type CustomHandler = (apiCore: ApiCore) => Promise export interface CustomSuccessResultOptions extends SuccessResultOptions { customHandler: CustomHandler } export class CustomSuccessResult extends SuccessResult { declare protected options: CustomSuccessResultOptions constructor(options: CustomSuccessResultOptions) { super(options) this.logger.setDefaultName("CustomSuccessResult") } async handle(apiCore: ApiCore): Promise { this.logger.log(`send`) await this.options.customHandler(apiCore) this.logger.log(`end`) } } // ====================== export interface JsonErrorResultOptions extends ErrorResultOptions { data: Data } export class JsonErrorResult extends ErrorResult { declare protected options: JsonErrorResultOptions constructor(options: JsonErrorResultOptions) { super(options) this.logger.setDefaultName("JsonErrorResult") } async handle(apiCore: ApiCore): Promise { const { data } = this.options this.logger.log(`send, ${JSON.stringify(data, null, 2)}`) await apiCore.json(data) this.logger.log(`end`) } } export interface CommonErrorJsonData { reason: Reason reasonDetail?: string | undefined } export interface CommonErrorJson { status: "error" data: CommonErrorJsonData } export interface CommonJsonErrorResultOptions extends Omit< JsonErrorResultOptions>, "data" > { data: CommonErrorJsonData } export class CommonJsonErrorResult extends JsonErrorResult< CommonErrorJson > { declare protected options: JsonErrorResultOptions> constructor(options: CommonJsonErrorResultOptions) { super({ ...options, data: { status: "error", data: options.data, }, }) this.logger.setDefaultName("CommonJsonErrorResult") } } export const commonJsonErrorResult = ( options: CommonJsonErrorResultOptions, ): CommonJsonErrorResult => { return new CommonJsonErrorResult(options) } export type CommonJsonErrorResultSchema< Data extends { reason: string reasonDetail?: string | undefined }, > = Ark.Type<{ status: "error" data: ObjectPrettify< Data & { reasonDetail?: string | undefined } > }> export type GetDataSchemaFromCommonJsonErrorResultSchema = Schema extends CommonJsonErrorResultSchema ? Ark.Type : never export type GetDataFromCommonJsonErrorResultSchema = Schema extends CommonJsonErrorResultSchema ? Data : never export type GetReasonFromCommonJsonErrorResultSchema = Schema extends CommonJsonErrorResultSchema ? Data extends { reason: infer Reason } ? Reason : never : never export const commonJsonErrorResultSchema = < Data extends { reason: string reasonDetail?: string | undefined }, >( dataSchema: Ark.Type, ): CommonJsonErrorResultSchema => { const newResultSchema = Ark.type({ status: "'error'", data: Ark.type.and( dataSchema as Ark.Type<{ reason: string reasonDetail?: string | undefined }>, { reasonDetail: Ark.type.string, }, ), }) as CommonJsonErrorResultSchema return newResultSchema } // ====================== export type ContextWithResultHandler< Path extends string, Method extends "get" | "post", InputQuerySchema extends Ark.Type, InputBodySchema extends Ark.Type, OutputErrorSchema extends Ark.Type, OutputSuccessSchema extends Ark.Type, > = ObjectPrettify< ApiHandlerContext< Path, Method, InputQuerySchema, InputBodySchema, OutputErrorSchema, OutputSuccessSchema > & { commonJsonErrorResult: ( options: ObjectPrettify< Omit< CommonJsonErrorResultOptions>, "data" > & CommonJsonErrorResultOptions< GetReasonFromCommonJsonErrorResultSchema >["data"] >, ) => CommonJsonErrorResult> commonJsonSuccessResult: ( options: ObjectPrettify< Omit< CommonJsonSuccessResultOptions< GetDataFromCommonJsonSuccessResultSchema >, "data" > & CommonJsonSuccessResultOptions< GetDataFromCommonJsonSuccessResultSchema >["data"] >, ) => CommonJsonSuccessResult> } > export const withResultHandler = < Path extends string, Method extends "get" | "post", InputQuerySchema extends Ark.Type, InputBodySchema extends Ark.Type, OutputErrorSchema extends Ark.Type, OutputSuccessSchema extends Ark.Type, >( context: ApiHandlerContext< Path, Method, InputQuerySchema, InputBodySchema, OutputErrorSchema, OutputSuccessSchema >, ): ContextWithResultHandler< Path, Method, InputQuerySchema, InputBodySchema, OutputErrorSchema, OutputSuccessSchema > => { return { ...context, commonJsonErrorResult: (options) => { const { logger, ...rest } = options return commonJsonErrorResult({ logger: logger ?? context.logger.derive(), data: rest, }) }, commonJsonSuccessResult: (options) => { const { logger, ...rest } = options return commonJsonSuccessResult({ logger: logger ?? context.logger.derive(), data: rest, }) as CommonJsonSuccessResult> }, } }