My aim is to create a function which has two parameters. First one is union value of type field in Action and second parameter should force developer to use specific object in Action.
For example, assume that we have this function function dispatch(actionType, payload){}
dispatch('SIGN_OUT', { type: 'SIGN_OUT', userName: '' });
or
dispatch('SIGN_IN_FAILURE', { type: 'SIGN_IN_FAILURE', error: '' });
type Action =
| { type: 'INIT' }
| { type: 'SYNC' }
| { type: 'SIGN_UP', userName: string, password: string, attributeList: Array<any> }
| { type: 'SIGN_IN', userName: string, pasword: string }
| { type: 'SIGN_IN_SUCCESS', accessToken: string }
| { type: 'SIGN_IN_FAILURE', error: string }
| { type: 'SIGN_OUT', userName: string }
| { type: 'FORGOT_PASSWORD', userName: string }
| { type: 'FORGOT_PASSWORD_SUCCESS', verificationCode: string }
| { type: 'CHANGE_PASSWORD', userName: string, oldPassword: string, newPassword: string }
| { type: 'CONFIRM_REGISTRATION', userName: string, confirmationCode: string }
| { type: 'RESEND_CONFIRMATION_CODE', userName: string }
| { type: 'DELETE_USER', userName: string }
| { type: 'GET_SESSION', userName: string }
| { type: 'GET_SESSION_SUCCESS', session: string };
type TodoAction =
| { type: 'INIT' }
| { type: 'ADD_TODO', text: string }
| { type: 'REMOVE_TODO', id: string }
| { type: 'SET_COMPLETED', id: string };
Wrong usage
Notice that payload parameter in line 32 is Action type, so I can create any object in Action type.

type DispatchType = (actionType: Action['type'], payload: Extract<Action, { type: Action['type'] }>) => void;
const dispatch: DispatchType = (actionType, payload) => { throw new Error('Not implemented') };
dispatch('CHANGE_PASSWORD', { type: 'DELETE_USER', userName: '' });
Still Wrong usage
Notice that when I write comma character in line 39, intellisense popup show me that I can use { type: 'CHANGE_PASSWORD', ....}, this is expected thing.

But I could use { type: 'DELETE_USER', ....} , so it is not expected behaviour, VSCode should error normally.

type DispatchType = <K extends Action['type']>(actionType: K, payload: Extract<Action, { type: K }>) => void;
const dispatch: DispatchType = (actionType, payload) => { throw new Error('Not implemented') };
dispatch('CHANGE_PASSWORD', { type: 'DELETE_USER', userName: '' });
Good usage
type DispatchType = <K extends Action['type'], J extends Extract<Action, { type: K }>>(actionType: K, payload: J) => void;
const dispatch: DispatchType = (actionType, payload) => { };
dispatch('SIGN_IN', { type: 'SIGN_IN', userName: '', pasword: '' });
dispatch('CONFIRM_REGISTRATION', { type: 'CONFIRM_REGISTRATION', userName: '', confirmationCode: '' });
Good usage
Notice that I just add a new generic type T extends Action and it is used in K extends T['type'] so instead of using K extends Action['type'], K extends T['type'] is used.

type DispatchType = <T extends Action, K extends T['type']>(actionType: K, payload: Extract<T, { type: K }>) => void;
const dispatch: DispatchType = (actionType, payload) => { throw new Error('Not implemented') };
dispatch('DELETE_USER', { type: 'DELETE_USER', userName: '' });
dispatch('CONFIRM_REGISTRATION', { type: 'CONFIRM_REGISTRATION', userName: '', confirmationCode: '' });
Better usage
type DispatchType = <T extends Action, K extends T['type'], J extends Extract<T, { type: K }>>(actionType: K, payload: J) => void;
const dispatch: DispatchType = (actionType, payload) => { };
dispatch('SIGN_IN', { type: 'SIGN_IN', userName: '', pasword: '' });
dispatch('GET_SESSION_SUCCESS', { type: 'GET_SESSION_SUCCESS', session: '' });
Ideal usage
Notice that I moved T extends { type: any }> in DispactType<T extends { type: any }> so that I could use dispatch for Action type and TodoAction type.
type DispatchType<T extends { type: any }> = <K extends T['type'], J extends Extract<T, { type: K }>>(actionType: K, payload: J) => void;
const dispatch: DispatchType<Action> = (actionType, payload) => { };
dispatch('SIGN_IN', { type: 'SIGN_IN', userName: '', pasword: '' });
const dispatch_: DispatchType<TodoAction> = (actionType, payload) => { };
dispatch_('ADD_TODO', { type: 'ADD_TODO', text: '' });
dispatch_('SET_COMPLETED', { type: 'SET_COMPLETED', id: '' });
Almost Best usage
Notice that payload doesn't require type field anymore.
type DispatchType<T extends { type: any }> = <K extends T['type'], J extends Omit<Extract<T, { type: K }>, 'type'>>(actionType: K, payload: J) => void;
const dispatch: DispatchType<Action> = (actionType, payload) => { throw new Error('Not implemented') };
dispatch('INIT', {});
dispatch('SIGN_IN', { userName: '', pasword: '' });
dispatch('CHANGE_PASSWORD', { userName: '', oldPassword: '', newPassword: '' });
Maybe better than best usage
type DispatchType<T extends { type: any }> = <K extends T['type'], J extends Omit<Extract<T, { type: K }>, 'type'>>(actionType: K, payload: J) => void;
const dispatch: DispatchType<Action> = (actionType, payload) => { throw new Error('Not implemented') };
dispatch('INIT', {});
dispatch('SIGN_IN', { userName: '', pasword: '' });
dispatch('CHANGE_PASSWORD', { userName: '', oldPassword: '', newPassword: '' });