To achieve this, utilize a mapped type along with a nested conditional type to test if the key being mapped matches a specific template literal type (to identify the need for the user
parameter):
export const Events = {
user_updated: "user:updated",
empty: "empty",
};
type ServerToClientEvents = {
[Key in keyof typeof Events]: Key extends `user_${string}` ? (user: User) => void : () => void;
};
const goodExample: ServerToClientEvents = {
user_updated(user: User) {},
empty() {},
};
const badExample: ServerToClientEvents = {
//^^^^^^^^^^^−−−−− Property 'empty' is missing in type '{ user_updated(user: User): void; }' but required in type 'ServerToClientEvents'.ts(2741)
user_updated(user: User) {},
};
Playground link
In my interpretation, the prefix user_
indicates that the function should accept a User
object based on the provided code snippet, but feel free to adjust as needed.
Based on the defined Events
constant, the mapped type outlines this structure:
{
user_updated: (user: User) => void;
empty: () => void;
}
https://i.sstatic.net/JVMxY.png
For better readability, consider separating out the conditional type:
type ServerFunctionType<Key extends string> =
Key extends `user_${string}`
? (user: User) => void
: () => void;
type ServerToClientEvents = {
[Key in keyof typeof Events]: ServerFunctionType<Key>;
};
Playground link