Basically, I want to provide a wrapper function for all the RPC calls I am making. This is so that I can log specific information about each RPC call without the use of a middleware. I want to be able to get the parameter of the method which is called by doing rpc[serviceName][method]
using TypeScript.
This is my current implementation where the params
is not specific enough:
async rpcWrapper<Service extends keyof IRpc>(
serviceName: Service,
method: keyof IRpc[Service],
params: Object,
) {
return rpc[serviceName][method]({ ...params });
}
I have also tried to do this but have gotten an error:
async rpcWrapper<Service extends keyof IRpc, Method extends keyof IRpc[Service]>(
serviceName: Service,
method: Method,
params: Parameters<Method>, // Type 'Method' does not satisfy the constraint '(...args: any) => any'.
) {
return rpc[serviceName][method]({ ...params });
}
IRPC interface
interface IRpc {
ExampleService: ExampleService;
ExampleService2: ExampleService2;
ExampleService3: ExampleService3;
}
type of an ExampleService
export declare class ExampleService {
public Login(req: LoginReq): Promise<LoginResp>;
public Login(ctx: ClientInvokeOptions, req: LoginReq): Promise<LoginResp>;
public Logout(req: LogoutReq): Promise<CommonResp>;
public Logout(ctx: ClientInvokeOptions, req: LogoutReq): Promise<CommonResp>;
}
export interface LoginReq {
username: string;
email: string;
}
What I want
rpcWrapper("ExampleService", "Login", { })
// Autocomplete tells me that I can fill in username and email
The correct type inference is sometimes hard to accomplish.
In your function the Method
type is just the key of one method of a service and you can't access the parameters of a PropertyKey
.
E.g. Parameters<"Login">
async rpcWrapper<Service extends keyof IRpc, Method extends keyof IRpc[Service]>(
serviceName: Service,
method: Method,
// Method is just the method key of your function
params: Parameters<Method>, // Type 'Method' does not satisfy the constraint '(...args: any) => any'.
) {
return rpc[serviceName][method]({ ...params });
}
To get the right Method of your service you have to access this method like this IRPc[Service][Method] //<- but this will result in unknown
Therefore you have to check somehow your IRPc[Service][Method]
is a valid Function. You can do that by writing a utility type that checks if the provided generic extends any Function type CastFn<T> = T extends AnyFn ? T : never
Now you can access the parameters of your method like this
Parameters<CastFn<IRPc[Service][Method]>>
.
interface IRpc {
ExampleService: ExampleService;
ExampleService2: ExampleService2;
}
interface LoginResp {
message: string
}
export declare class ExampleService {
public Login(input: { req: LoginReq, ctx?: ClientInvokeOptions }): Promise<LoginResp>;
public Logout(input: { req: LogoutReq, ctx?: ClientInvokeOptions }): Promise<CommonResp>;
}
export declare class ExampleService2 {
public Study(req: StudyReq): Promise<LoginResp>;
}
export interface StudyReq {
canStudy: boolean;
}
export interface LoginReq {
username: string;
email: string;
}
interface LogoutReq {
username: string;
}
interface CommonResp { }
interface ClientInvokeOptions { }
declare const rpc: IRpc;
type AnyFn = (...args: any[]) => any
type CastFn<T> = T extends AnyFn ? T : never
function rpcWrapper<
ServiceKey extends keyof IRpc,
MethodKey extends keyof IRpc[ServiceKey],
ClassMethod extends AnyFn = CastFn<IRpc[ServiceKey][MethodKey]>
>(
serviceName: ServiceKey,
method: MethodKey,
...params: Parameters<ClassMethod>
) {
return (rpc[serviceName][method] as ClassMethod)(...params);
}
type X = Parameters<IRpc["ExampleService"]["Login"]>
// Can put anything
rpcWrapper("ExampleService", "Login", { a: "a" }) // invalid
// I should be restricted to this
rpcWrapper("ExampleService", "Login", { req: { username: "haha", email: "[email protected]" } }) //valid```