조건

설치

npm install @sinclair/typebox

Code

import { Static, TSchema, Type } from "@sinclair/typebox";
import * as Util from "util";

// ========================================================================
//  Helper
// ========================================================================
class ControllerHelper {
    public static InjectRequiredFlags(obj: TSchema): void {
        if(obj["properties"] === undefined) return;
        const reqs = obj["required"];
        reqs?.forEach((prop: any) => {
            obj["properties"][prop]["required"] = true;
        })
    }
}

// ========================================================================
//  Types
// ========================================================================
type HttpRequestMethod = "get" | "post" | "put" | "patch" | "delete";
type ControllerOptions = {
    requireAuth: boolean;
}

type MixBuilder<
    TQuery = any, 
    TParam = any,
    TBody  = any, 
    TMethods extends string = ""
> = Omit<ControllerBuilder<TQuery, TParam, TBody, TMethods>, TMethods>;

// ========================================================================
//  Dto Examples
// ========================================================================
type UserFindQueryDto = Static<typeof UserFindQueryDto>;
const UserFindQueryDto = Type.Object({
    id: Type.Integer(),
    name: Type.Optional(Type.String())
});

type UserFindParamDto = Static<typeof UserFindParamDto>;
const UserFindParamDto = Type.Object({
    id: Type.Integer(),
    nick: Type.Optional(Type.String())
});

// ========================================================================
//  Class
// ========================================================================
class ControllerBuilder <
    TQuery = any, 
    TParam = any, 
    TBody  = any,
    Tx extends string = ""
> {
    private url: string;
    private method: HttpRequestMethod;
    private summaryDesc: string;
    private detailDesc: string;
    private options: ControllerOptions = {} as any;

    public query: TQuery;
    public param: TParam;
    public body : TBody;

    public static Begin() {
        return new ControllerBuilder();
    }

    public Auth() : MixBuilder<TQuery, TParam, TBody, Tx | "Auth">
    {
        this.options.requireAuth = true;
        return this as any;
    }

    public URL(url: string) : MixBuilder<TQuery, TParam, TBody, Tx | "URL">
    {
        this.url = url;
        return this as any;
    }

    public Method(method: HttpRequestMethod) : MixBuilder<TQuery, TParam, TBody, Tx | "Method">
    {
        this.method = method;
        return this as any;
    }

    public Query<TQuery extends TSchema>(query: TQuery)
        : MixBuilder<Static<TQuery>, TParam, TBody, Tx | "Query">
    {
        ControllerHelper.InjectRequiredFlags(query);
        (this as any).query = query;
        return (this as any);
    }

    public Param<TParam extends TSchema>(param: TParam)
        : MixBuilder<TQuery, Static<TParam>, TBody, Tx | "Param">
    {
        ControllerHelper.InjectRequiredFlags(param);
        (this as any).param = param;
        return (this as any);
    }

    public Body<TBody extends TSchema>(body: TBody)
        : MixBuilder<TQuery, TParam, Static<TBody>, Tx | "Body">
    {
        ControllerHelper.InjectRequiredFlags(body);
        (this as any).body = body;
        return (this as any);
    }

		// TODO: 컨트롤러 구현 바디 작성
    public Test(q: (a: {
        query: TQuery,
        param: TParam,
    }) => void) {
        return this; // TODO: 제거
    }
}

// ========================================================================
//  Usage
// ========================================================================
const userFind = 
    ControllerBuilder.Begin()
        .Auth()
        .Method("get")
        .URL("/")
        .Query(UserFindQueryDto)
        .Param(UserFindParamDto)
        .Test(
            (obj) => {
                obj.query.id;
                obj.param.nick;
            }
        )
;

// console.log(userFind);
console.log(Util.inspect(userFind, true, null, true))
userFind.query.id;