I'm currently trying to figure out how to create a typed function that will call the following object based on its key:
const commandOrQuery = {
CREATE_USER_WITH_PASSWORD: CreateUserCommandHandler,
GET_USERS: GetUsersQueryHandler,
};
In which commandOrQuery
is of the shape: Record<string, Function>
I'd like to create a dispatch
function that will:
export const dispatch = ({type, args}) => commandOrQuery[type](args)
Where:
type
is the key of the commandOrQuery
object.
args
are the arguments of the commandOrquery[type]
function.
And dispatch
returns the type of the object.
Meaning that if I do the following:
const result = await dispatch({
type: "GET_USERS",
args : {
// tenantId is exclusive to "GET_USERS"
tenantId: string;
}
});
// result will have the following shape {name: string, id: string}[] which
// is exclusive to "GET_USERS" and GetUsersQueryHandler
I've come very close by defining the types separately, like this:
export type Mediator =
| {
type: "CREATE_USER_WITH_PASSWORD";
arg: CreateUserCommand | typeof CreateUserCommandHandler;
}
| {
type: "GET_USERS";
arg: GetUsersQuery | typeof GetUsersQueryHandler;
}
| {
type: "GET_USER_PROFILE";
arg: GetUserProfile | typeof GetUserProfileQueryHandler;
};
And defining the dispatch like this:
export const dispatch = ({type, args}: Mediator) => commandOrQuery[type](args)
But I'm currently lacking the return type. I'd like TypeScript to infer the ReturnType automatically after I provide the type
in the argument.
Is this even possible?
I've been researching for a couple of hours now: TypeScript conditional return value type? https://stackoverflow.com/search?q=key+function+argument+return+typescript Typescript Key-Value relation preserving Object.entries type
Edit x1: Here's the Code Sandbox: https://codesandbox.io/s/prod-http-50m9zk?file=/src/mediator.ts
Edit x2:
Let's say we have the following structure:
//file: bounded_contexts/CreateUserWithPasswordCommand.ts
export type CreateUserCommand = {
email: string;
password: string;
};
export async function CreateUserCommandHandler(cmd: CreateUserCommand) {
console.log("Creating User");
return true;
}
// file: bounded_contexts/GetUserProfileQuery.ts
export type GetUserProfileQuery = {
userId: string;
};
export async function GetUserProfileQueryHandler(query: GetUserProfileQuery) {
console.log("Searching in db the userId", query.userId);
return {
firstName: "Carlos",
lastName: "Johnson",
dateOfBirth: new Date()
};
}
// file: bounded_contexts/GetUsersQuery.ts
export type GetUsersQuery = {};
export async function GetUsersQueryHandler(q: GetUsersQuery) {
return {
users: [
{
id: "id-1",
name: "Julian Perez"
},
{
id: "id-2",
name: "Adam Smith"
}
]
};
}
// file: index.ts
import { dispatch } from "./mediator";
(async () => {
// Result should be boolean
const result = await dispatch({
type: "CREATE_USER_WITH_PASSWORD",
arg: {
email: "theemail@mail.com",
password: "the password"
}
});
// result2 should be { users: {name: string; id: string }[]
const result2 = await dispatch({
type: "GET_USERS",
arg: {}
});
// resul3 should be { firstName: string; lastName: string; dateOfBirth: Date}
const result3 = await dispatch({
type: "GET_USER_PROFILE",
arg: {
userId: "the user Id"
}
});
})();
// file: mediator.ts
import {
CreateUserCommandHandler,
CreateUserCommand
} from "./bounded_contexts/CreateUserWithPasswordCommand";
import {
GetUsersQueryHandler,
GetUsersQuery
} from "./bounded_contexts/GetUsersQuery";
import {
GetUserProfileQueryHandler,
GetUserProfileQuery
} from "./bounded_contexts/GetUserProfileQuery";
const commandsOrQueries = {
CREATE_USER_WITH_PASSWORD: CreateUserCommandHandler,
GET_USERS: GetUsersQueryHandler,
GET_USER_PROFILE: GetUserProfileQueryHandler
};
type Mediator =
| {
type: "CREATE_USER_WITH_PASSWORD";
arg: CreateUserCommand | typeof CreateUserCommandHandler;
}
| {
type: "GET_USERS";
arg: GetUsersQuery | typeof GetUsersQueryHandler;
}
| {
type: "GET_USER_PROFILE";
arg: GetUserProfileQuery | typeof GetUserProfileQueryHandler;
};
export function dispatch({ arg, type }: Mediator) {
return commandsOrQueries[type](arg as any);
}