I have this TS code:
type MyFunctions = {
play(): void
pause(args: { reset: boolean }): void
stop(args: { restart: boolean }): void
}
type AuthStruct = {
[K in keyof MyFunctions]: {
requiresAuth: boolean
}
}
const auth = {
play: {
requiresAuth: true
},
pause: {
requiresAuth: true
},
stop: {
requiresAuth: false
}
} as const satisfies AuthStruct
type Auth = typeof auth
type FunctionParameters<K extends keyof MyFunctions> = Parameters<
MyFunctions[K]
>[0] extends undefined
? never
: Parameters<MyFunctions[K]>[0]
type Data<K extends keyof MyFunctions> = (FunctionParameters<K> extends never
? {}
: FunctionParameters<K>
) & (
Auth[K] extends { requiresAuth: true }
? { token: string }
: {}
)
function runFunction<F extends keyof MyFunctions>(
func: F,
data: Data<F>
) {
if(auth[func].requiresAuth) {
data.token // Should work
}
}
I would suppose that after running the conditional check if(auth[func].requiresAuth)
the compiler would understand that data
has a requiresAuth
property. But unfortunately it doesn't work. Furthermore, I can't even force the compiler to accept that data
has the token
property by doing something like: data.token!
. How can I solve the issue?
I was able to get it working with this code using typeguard and some black magic (without using Any):
type HasToken = {token: string}
const isAuth = <F extends keyof MyFunctions> (func: AuthStruct[F], _: Data<F> | HasToken): _ is HasToken => {
return func.requiresAuth;
}
function runFunction<F extends keyof MyFunctions>(
func: F,
data: Data<F>
) {
if(isAuth(auth[func], data)) {
data.token // Should work
}
}
Probably it can be improved so that you don't need to pass the func and use a bit ugly unused variable, but hey it works.
Edit: after seeing another answer I think this might be better:
const isAuth = <F extends keyof MyFunctions> (data: Data<F> | HasToken): data is HasToken => {
return 'token' in data;
}
// inside of function
if(auth[func].requiresAuth && isAuth(data)) {
data.token // Should work
}
}
or even like this, preserving maximum of your original logic
const isAuth = <F extends keyof MyFunctions> (func: AuthStruct[F], data: Data<F> | HasToken): data is HasToken => {
return func.requiresAuth && 'token' in data;
}
// inside of function
if(isAuth(auth[func], data)) {
data.token // Should work
}