Let's say I have a union type that I am using with a reducer-like pattern for API calls that looks something like this:
type Action = {
request: {
action: "create user",
payload: { name: string, age: number },
},
response: { ok: true, message: "user created" },
} | {
request: {
action: "delete user",
payload: { id: number },
},
response: { ok: true, message: "user deleted" },
};
When I choose a particular action, I want to be able to predict what the corresponding response will be with type safety. For example, if I were to make a function like this, I want to enforce that I am indeed returning the proper corresponding response for every request. I am not having a trouble with type safety on the input types. I am having trouble linking the output to the corresponding sub-type related to the input type.
function doAction<T extends Action>(request: T["request"]): T["response"] {
if (request.action === "create user") {
const userName = payload.user.name; // Here we're good for type safety
return {
ok: true,
message: "user deleted", // This should error, but it doesn't
};
} else {
const { id } = request.payload; // Again, we're good here for type safety
return {
ok: true,
message: "user deleted", // Ok, but missing strong type safety here
};
}
}
How can I indicate that I want the ["response"] part of the object type chosen in the ["request"], and not just any Action["response"]?
I would also like to be able to link these two corresponding halves of the object when making middleware functions like this, for example:
function<T extends Action>(req: Request<T["request"]>, res: Response<T["response"]>) {
if (req.body.action === "create user") {
res.send({ ok: true, message: "user deleted" }); // this should not be allowed
}
...