djbalin
djbalin•3w ago

Narrowing function return type

Hi, consider the toy example below: passing true as an argument causes the function to return a number while passing false causes it to return a boolean. The inferred return type is however always number | boolean regardless of what argument is passed. I guess this is just a quirk with TS not narrowing function signature based on arguments passed. Is it possible to achieve this however in an elegant way? 🙂
export const returnNumOrBool = authQuery({
args: {
returnNumber: v.boolean(),
},
handler: async (ctx, { returnNumber }) => {
if (returnNumber === true) {
return 1
} else {
return false
}
},
})
export const returnNumOrBool = authQuery({
args: {
returnNumber: v.boolean(),
},
handler: async (ctx, { returnNumber }) => {
if (returnNumber === true) {
return 1
} else {
return false
}
},
})
6 Replies
Convex Bot
Convex Bot•3w ago
Thanks for posting in <#1088161997662724167>. Reminder: If you have a Convex Pro account, use the Convex Dashboard to file support tickets. - Provide context: What are you trying to achieve, what is the end-user interaction, what are you seeing? (full error message, command output, etc.) - Use search.convex.dev to search Docs, Stack, and Discord all at once. - Additionally, you can post your questions in the Convex Community's <#1228095053885476985> channel to receive a response from AI. - Avoid tagging staff unless specifically instructed. Thank you!
sshader
sshader•3w ago
Yeah taking out all the convex parts, I'm not sure how to get TS to do this sort of type narrowing for you. Playing around a little bit, I could only get this behavior by defining an overload. Let's see if discord will let me send this as a pretty link
TS Playground - An online editor for exploring TypeScript and JavaS...
The Playground lets you write TypeScript or JavaScript online in a safe and sharable way.
RJ
RJ•3w ago
You can also use generics, but I like Sarah's approach (overloads) better. However, both approaches aren't really type-safe in the sense that they don't enforce the type-level logic that you'd expect/want (playground) Regarding Convex specifically though (and honestly also in normal TypeScript code), I would recommend just splitting this into two different functions. It may not even end up being more code when you take into account the fancy TS type-level stuff you would need to do to get it to work with one, and it's safer and easier to understand
djbalin
djbalinOP•3w ago
Thanks both of you! Haha yeah seems like quite a bit of hoop-jumping and arm-twisting has to be done to achieve this, and that you'll probably end up with even less peace of mind!! I think we'll end up writing two dedicated queries to get the intellisense and type safety in the frontend that we expect. If anyone's curious as to why I was interested in this, it was for basically this logic: getUnreadNotifications(userId, onlyReturnCount)
ballingt
ballingt•3w ago
Two Convex queries sounds useful to me, remember you can write helpers functions so there's no code duplication The generated types https://docs.convex.dev/client/open-api for Convex functions can't include overloads or type parameters, in some languages these would not be expressible. The goal is that a Convex Query should be an API endpoint that can be expressed in any language; even though the TypeScript integration is good, you're defining an API and it's nice for that to return a consistent type.
lee
lee•3w ago
I'm curious if you can do export const returnNumOrBool = authQuery(...) as RegisteredQuery<"public", { returnNumber: true }, number> | RegisteredQuery<"public", { returnNumber: false }, boolean>

Did you find this page helpful?