My goal here is to have requests and responses grouped by action, so if I have (as in the code below) two actions: Play
and Stay
, I will have a pair of request/response representing the request/response of Play
action and the same for the Stay
action.
I want to express the constraint that only request and response of the same action can be coupled (see Playground)
type ActionName = "Play" | "Stay";
type RequestD<A extends ActionName, B extends Record<string, unknown>> = {
_tag: "Request";
_action: A;
} & B;
type ResponseD<A extends ActionName, B extends Record<string, unknown>> = {
_tag: "Response";
_action: A;
} & B;
type PlayRequest = RequestD<"Play", { a: number }>;
type PlayResponse = ResponseD<"Play", { b: number }>;
type StayRequest = RequestD<"Stay", { c: number }>;
type StayResponse = ResponseD<"Stay", { d: number }>;
type ActionOf<X> = X extends RequestD<infer A, any>
? A
: X extends ResponseD<infer A, any>
? A
: never;
// Problem:
// X is "Play" | "Stay"`
// Is there a way to get "Play"?
type X = ActionOf<PlayRequest>;
type TagOf<X> = X extends RequestD<any, any>
? "Request"
: X extends ResponseD<any, any>
? "Response"
: never;
// Problem:
// Y1 is "Request" and that's expected
// Y2 is "Request" as well and that's unexpected, how is this possible? How can I fix it?
type Y1 = TagOf<PlayRequest>;
type Y2 = TagOf<PlayResponse>;
type RoundTrip<
A extends ActionName,
Req extends RequestD<A, any>,
Res extends ResponseD<A, any>
> = {
_action: A;
request: Req;
response: Res;
};
type PlayRT = RoundTrip<"Play", PlayRequest, PlayResponse>;
// Problem:
// The following should be wrong because the first request is related to action "Stay", not "Play"
// Is there a way to enforce that?
type WrongRT = RoundTrip<"Play", StayRequest, PlayResponse>;
My questions stated in the code above are:
- How can I associate the "Play" action to a request/response to use it at type level?
- Why I cannot differentiate between Request/Response even if those types are appropriately tagged (
_tag
field)? - How can I constraint the action to be the same action in request/response in
RoundTrip
type?
CodePudding user response:
The issue is actually quite simple, you are using any
as the second parameter in Request
and Response
.
If you take a look at your definition:
type RequestD<A extends ActionName, B extends Record<string, unknown>> = {
_tag: "Request";
_action: A;
} & B; // note that you are extending the entire object.
Then when you then define a generic as such:
type TagOf<X> = X extends RequestD<any, any>
? "Request"
: X extends ResponseD<any, any>
? "Response"
: never;
It will fail because any
includes an object such as { _tag: "Request" }
for a response or { _tag: "Response" }
for a request.
It basically gives typescript nothing to work off of. To fix this you just need to change the any
to something like an empty object {}
.