0

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:

  1. How can I associate the "Play" action to a request/response to use it at type level?
  2. Why I cannot differentiate between Request/Response even if those types are appropriately tagged (_tag field)?
  3. How can I constraint the action to be the same action in request/response in RoundTrip type?

1 Answer 1

1

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 {}.

Playground

Sign up to request clarification or add additional context in comments.

3 Comments

Thanks! That's the answer to the question number 2, any clue on the others?
Wait, it fixes also the problem number 3, only number 1 remains, any suggestions?
Never mind, you are right on all accounts, by using RequestD<any, {}> and ResponseD<any, {}> in ActionOf I get the correct answer. Thanks!

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.