I've thoroughly commented code below. I feel like it's much easier to understand the problem like this instead of trying to explain it just with the worlds.
abstract class BaseEvent<REQUEST_PAYLOAD, RESPONSE_PAYLOAD> {
constructor(public requestPayload: REQUEST_PAYLOAD) {}
// this method was only created for the sake of showing response1 type example
public return_RESPONSE_PAYLOAD_type(): RESPONSE_PAYLOAD {
return null;
}
}
type GetUserInfoRequest = { userId: number };
type GetUserInfoResponse = { username: string; age: number };
class GetUserInfoEvent extends BaseEvent<
GetUserInfoRequest,
GetUserInfoResponse
> {}
const emit = async <
REQUEST_PAYLOAD,
RESPONSE_PAYLOAD,
EVENT extends BaseEvent<REQUEST_PAYLOAD, RESPONSE_PAYLOAD>
>(
event: EVENT
): Promise<RESPONSE_PAYLOAD> => {
// some stuff will be done there - for the sake of example it was removed
return null;
// return event.return_RESPONSE_PAYLOAD_type(); // doesn't work aswell
};
const main = async () => {
const event = new GetUserInfoEvent({ userId: 666 });
const response1 = event.return_RESPONSE_PAYLOAD_type(); // type === { username: string; age: number; }
const response2 = await emit(event); // type === {} but I want it to be { username: string; age: number; }
// response2.username <-- error
// response2.age <-- error
// I want emit function to return current RESPONSE_PAYLOAD type instead of an empty object. I don't want to manually cast returned types to GetUserInfoResponse because I want to achieve 100% type safety.
};
main();
I should probably not that it was tested with typescript@3.2.4
.
Use can use a conditional type to extract the type argument from EVENT
. Type parameters are not inferred one based on another usually, and you end up with the narrowest possible type (in this case {}
)
abstract class BaseEvent<REQUEST_PAYLOAD, RESPONSE_PAYLOAD> {
constructor(public requestPayload: REQUEST_PAYLOAD) { }
// this method was only created for the sake of showing response1 type example
public return_RESPONSE_PAYLOAD_type(): RESPONSE_PAYLOAD {
return null;
}
}
type GetUserInfoRequest = { userId: number };
type GetUserInfoResponse = { username: string; age: number };
class GetUserInfoEvent extends BaseEvent<
GetUserInfoRequest,
GetUserInfoResponse
> { }
// Conditional type to extract the response payload type:
type RESPONSE_PAYLOAD<T extends BaseEvent<any, any>> = T extends BaseEvent<any, infer U> ? U : never;
const emit = async <
EVENT extends BaseEvent<any, any>
>(
event: EVENT
): Promise<RESPONSE_PAYLOAD<EVENT>> => {
// some stuff will be done there - for the sake of example it was removed
return null;
// return event.return_RESPONSE_PAYLOAD_type(); // doesn't work aswell
};
const main = async () => {
const event = new GetUserInfoEvent({ userId: 666 });
const response1 = event.return_RESPONSE_PAYLOAD_type(); // type === { username: string; age: number; }
const response2 = await emit(event); // is now GetUserInfoResponse
response2.username //<-- ok
response2.age //<-- ok
};