whysocket
whysocket4w ago

How can I avoid explicitly defining type in TypeScript for a internal call query handler?

I'm loving Convex <3, and I'd like to simplify my code. I have a query handler where I define a specific response type, but I want TypeScript to automatically infer the return type, instead of explicitly specifying something like GetMeResponse | null. Here’s an example of my current implementation:
type GetMeResponse = {
_id: Id<"users">;
name: string;
email: string;
role: string;
isTutor: boolean;
isStudent: boolean;
stripeCustomerId?: string;
imageUrl?: string;
};

export const getMe = query({
handler: async (ctx): Promise<GetMeResponse | null> => {
const userId = await getAuthUserId(ctx);
if (!userId) return null;

const user = await ctx.db.get(userId);

if (!user) {
throw new Error("User not found");
}

if (!user.name) {
throw new Error("User name not found");
}

if (!user.email) {
throw new Error("User email not found");
}

if (!user.role) {
throw new Error("User role not found");
}

return {
_id: userId,
name: user.name,
email: user.email,
role: user.role,
isTutor: user.role === ROLES.TUTOR,
isStudent: user.role === ROLES.STUDENT,
stripeCustomerId: user.stripeCustomerId,
imageUrl: user.image,
};
},
});

export const getMeOrThrow = query({
handler: async (ctx): Promise<GetMeResponse> => {
const user: GetMeResponse | null = await ctx.runQuery(api.queries.users.getMe);

if (!user) {
throw new Error("User not found");
}

return user;
},
});
type GetMeResponse = {
_id: Id<"users">;
name: string;
email: string;
role: string;
isTutor: boolean;
isStudent: boolean;
stripeCustomerId?: string;
imageUrl?: string;
};

export const getMe = query({
handler: async (ctx): Promise<GetMeResponse | null> => {
const userId = await getAuthUserId(ctx);
if (!userId) return null;

const user = await ctx.db.get(userId);

if (!user) {
throw new Error("User not found");
}

if (!user.name) {
throw new Error("User name not found");
}

if (!user.email) {
throw new Error("User email not found");
}

if (!user.role) {
throw new Error("User role not found");
}

return {
_id: userId,
name: user.name,
email: user.email,
role: user.role,
isTutor: user.role === ROLES.TUTOR,
isStudent: user.role === ROLES.STUDENT,
stripeCustomerId: user.stripeCustomerId,
imageUrl: user.image,
};
},
});

export const getMeOrThrow = query({
handler: async (ctx): Promise<GetMeResponse> => {
const user: GetMeResponse | null = await ctx.runQuery(api.queries.users.getMe);

if (!user) {
throw new Error("User not found");
}

return user;
},
});
Question: How can I avoid explicitly defining GetMeResponse | null and let TypeScript infer the types automatically for these Convex query handlers?
4 Replies
Convex Bot
Convex Bot4w 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!
erquhart
erquhart4w ago
the getMe query doesn't require a return type, but getMeOrThrow does because it introduces circular inference. There's a doc about it here: https://docs.convex.dev/functions/actions#dealing-with-circular-type-inference Hmm actually you should be able to drop the return types for both
lee
lee4w ago
you're also using an unnecessary ctx.runQuery. we recommend helper functions https://docs.convex.dev/production/best-practices/#use-helper-functions-to-write-shared-code
Best Practices | Convex Developer Hub
Here's a collection of our recommendations on how best to use Convex to build
whysocket
whysocketOP4w ago
Thanks @erquhart and @lee

Did you find this page helpful?