samchon / tgrid

TypeScript RPC (Remote Procedure Call) for WebSocket and Worker protocols
https://tgrid.com/
MIT License
146 stars 19 forks source link

[RFC] Parametric types should be compatible with primitive #22

Closed samchon closed 5 years ago

samchon commented 5 years ago

TGrid has adopted JSON structure on network communication.

Therefore, when calling a remote function, parametric objects always be primitive. Prototype would be JS primitive Object and all methods would be removed. I think such domain characteristic should be reflected to the Driver<Controller> through the meta programming.

Although a Controller has defined its functions to have non-primitive parameters, Driver should make the parameter type to be compatible with primitive. As you can see from the below example code, functions defined in the Feature class have objects as their parameters; Report and Member.

When you wrap the Feature class into the Driver type, those functions' parameters should be changed to compatible with their primitive object types. Methods Feature.insertMember() and Feature.publish() have object typed parameters. Those parameters should be compatible with their primitive type in the Driver<Feature>.

/* ----------------------------------------------------------------
    ORIGINAL TYPES
---------------------------------------------------------------- */
declare class Feature
{
    public count(): Number;
    public getReport(): Report;
    public getMembers(): Member[];

    public insertMember(obj: Member): void;
    public findMember(key: number | string): Member | null;

    public publish(x: Member, y: Report, pred: Number): Report;
}

declare class Report implements IReport
{
    public code: string;
    public title: string;
    public content: string;

    public archive(): void;
    public publish(): void;
}

declare class Member implements IJsonable<IMember>
{
    private id_: number;
    private name_: string;

    public login(password: string): void;
    public update(name: string): void;
    public toJSON(): IMember
}

/* ----------------------------------------------------------------
    PRIMITIVE TYPES
---------------------------------------------------------------- */
type Driver<Feature> = 
{
    public count(): Promise<number>;
    public getReport(): Promise<IReport>;
    public getMembers(): Promise<IMember[]>;

    public insertMember(obj: Member | IMember | IJsonable<IMember>): Promise<void>;
    public findMember(key: number | string): Promise<Member | null>;

    public publish
        (
            x: Member | IMember | IJsonable<IMember>, 
            y: Report | IReport | IJsonable<IReport>, 
            pred: number
        ): Promise<IReport>;
};

interface IReport
{
    code: string;
    title: string;
    content: string;
}

interface IMember
{
    id: number;
    name: string;
}
samchon commented 5 years ago

I succeeded to implement it, however, it seems like an over-spec. So I'm considering whether to adapt the parametric conversion or not. Until the decision, I'll adapt both of them for a while. The option of parametric conversion would be added to the second generic parameter of the Driver type.

class Communicator<Provider>
{
    public getDriver<Controller extends object, UseParametric extends boolean = false>()
        : Driver<Controller, UseParametric>;
}

When you configure the second generic parameter UseParametric to be false, parametric conversion would not be used. The parameter types of the remote functions would keep their origin form.

type Driver<Feature, false> = 
{
    public count(): Promise<number>;
    public getReport(): Promise<IReport>;
    public getMembers(): Promise<IMember[]>;

    public insertMember(obj: Member): Promise<void>;
    public findMember(key: number | string): Promise<Member | null>;

    public publish(x: Member, y: Report, pred: number): Promise<IReport>;
};

Otherwise you configure the UseParameric to be true, parametric conversion would be used.

type Driver<Feature, true> = 
{
    public count(): Promise<number>;
    public getReport(): Promise<IReport>;
    public getMembers(): Promise<IMember[]>;

    public insertMember(obj: Member | IMember | IJsonable<IMember>): Promise<void>;
    public findMember(key: number | string | IJsonable<number|string>): Promise<Member | null>;

    public publish
        (
            x: Member | IMember | IJsonable<IMember>, 
            y: Report | IReport | IJsonable<IReport>, 
            pred: number | IJsonable<number>
        ): Promise<IReport>;
};