NEEGHAN
NEEGHAN•4mo ago

`useQuery` returning type `never`

I have a monorepo with PNPM workspaces and TS project references and in one package @ns/convex I have all the Convex code and in another @nx/nextjs-app I develop my Next.js client. Everything works well, except that TypeScript infers useQuery's return type to never.
// in packages/convex/src/chat.ts
export const getMessages = query({
handler: async (ctx) => {
const messages = await ctx.db.query("chat_messages").collect();
return messages;
},
});
// in packages/convex/src/chat.ts
export const getMessages = query({
handler: async (ctx) => {
const messages = await ctx.db.query("chat_messages").collect();
return messages;
},
});
// in packages/nextjs-app/src/components/Chat.tsx
export const Chat = () => {
const messages = useQuery(api.chat.getMessages);
// ^
// TypeScript infers this to `never[] | undefined`
...
};
// in packages/nextjs-app/src/components/Chat.tsx
export const Chat = () => {
const messages = useQuery(api.chat.getMessages);
// ^
// TypeScript infers this to `never[] | undefined`
...
};
Has anyone else encountered this? Got any idea how to solve it?
9 Replies
Convex Bot
Convex Bot•4mo 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!
NEEGHAN
NEEGHANOP•4mo ago
Same happens if I explicitly type messages or handler:
// in packages/convex/src/chat.ts
export const getMessages = query({
handler: async (ctx): Promise<Doc<"chat_messages">[]> => {
const messages = await ctx.db.query("chat_messages").collect();
return messages;
},
});
// in packages/convex/src/chat.ts
export const getMessages = query({
handler: async (ctx): Promise<Doc<"chat_messages">[]> => {
const messages = await ctx.db.query("chat_messages").collect();
return messages;
},
});
// in packages/convex/src/chat.ts
export const getMessages = query({
handler: async (ctx) => {
const messages: Doc<"chat_messages">[] = await ctx.db.query("chat_messages").collect();
return messages;
},
});
// in packages/convex/src/chat.ts
export const getMessages = query({
handler: async (ctx) => {
const messages: Doc<"chat_messages">[] = await ctx.db.query("chat_messages").collect();
return messages;
},
});
But if the type doesn't use anything that comes from the data model (schema), then it weirdly enough works:
// in packages/convex/src/chat.ts
export const getMessages = query({
handler: async (ctx) => {
const messages = await ctx.db.query("chat_messages").collect();
// has to be cast to unknown first since here it still manages to infer the type correctly
return messages as unknown as {customType: string}[];
},
});
// in packages/convex/src/chat.ts
export const getMessages = query({
handler: async (ctx) => {
const messages = await ctx.db.query("chat_messages").collect();
// has to be cast to unknown first since here it still manages to infer the type correctly
return messages as unknown as {customType: string}[];
},
});
// in packages/nextjs-app/src/components/Chat.tsx
export const Chat = () => {
const messages = useQuery(api.chat.getMessages);
// ^
// TypeScript infers this to `{customType: string}[] | undefined`
...
};
// in packages/nextjs-app/src/components/Chat.tsx
export const Chat = () => {
const messages = useQuery(api.chat.getMessages);
// ^
// TypeScript infers this to `{customType: string}[] | undefined`
...
};
FYI, if I import dataModel.d.ts in the Next.js app, the Doc type also resolves to never:
{
"name": "@ns/convex",
...
"exports": {
...
"./dataModel": "./src/_generated/dataModel.d.ts"
}
}
{
"name": "@ns/convex",
...
"exports": {
...
"./dataModel": "./src/_generated/dataModel.d.ts"
}
}
// in packages/nextjs-app/src/components/Chat.tsx
import {type Doc} from "@ns/convex/dataModel";
// ^
// TypeScript resolves this as
// type Doc<TableName extends TableNames> = {
// chat_messages: never;
// }[TableName]["document"]
// in packages/nextjs-app/src/components/Chat.tsx
import {type Doc} from "@ns/convex/dataModel";
// ^
// TypeScript resolves this as
// type Doc<TableName extends TableNames> = {
// chat_messages: never;
// }[TableName]["document"]
For context, here's the schema.ts:
import {defineSchema, defineTable} from "convex/server";
import {type Infer, v} from "convex/values";

export const roleValidator = v.union(v.literal("user"), v.literal("assistant"));

export type ChatMessageRole = Infer<typeof roleValidator>;

export default defineSchema({
chat_messages: defineTable({
user: v.string(),
role: roleValidator,
content: v.optional(v.string()),
stream: v.optional(v.string()),
}).index("by_user", ["user"]),
});
import {defineSchema, defineTable} from "convex/server";
import {type Infer, v} from "convex/values";

export const roleValidator = v.union(v.literal("user"), v.literal("assistant"));

export type ChatMessageRole = Infer<typeof roleValidator>;

export default defineSchema({
chat_messages: defineTable({
user: v.string(),
role: roleValidator,
content: v.optional(v.string()),
stream: v.optional(v.string()),
}).index("by_user", ["user"]),
});
erquhart
erquhart•4mo ago
That's really odd. I'd start with the basics - restart typescript language server, nuke node_modules and reinstall. If it persists, I'd wonder how the generated api.js and api.d.ts files look. What happens when you hover the query itself, does the return type look right there?
NEEGHAN
NEEGHANOP•4mo ago
Yeah, I tried those things as well (nuking node_modules, restarting TS language server) but it didn't help 😅
NEEGHAN
NEEGHANOP•4mo ago
Here's api.js and api.d.ts
No description
NEEGHAN
NEEGHANOP•4mo ago
It shows the correct type when hovering over the query itself
No description
NEEGHAN
NEEGHANOP•4mo ago
Another interesting thing is that when running tsc --build for the Next.js app project, I get a type error
const messages = useQuery(api.chat.getMessages);
// ^
// error TS2339: Property 'chat' does not exist on type '{}'.
const messages = useQuery(api.chat.getMessages);
// ^
// error TS2339: Property 'chat' does not exist on type '{}'.
which doesn't show up in the editor. So there's inconsistency between tsc and editor integration as well. (FYI, I have set up "typescript.tsdk" to point to node_modules TS SDK.) So at compile-time, TypeScript seems to think api is just an empty object?? Update: it started working once I disabled TS project references for the convex project (@nx/convex which contained Convex code and was being imported from by @ns/nextjs-app which contained Next.js app code). So I set "composite", "declarationMap" and "emitDeclarationOnly" to false in the project's tsconfigs and dereferenced the project everywhere else (removed from "references" of root tsconfig and the Next.js app's tsconfig).
erquhart
erquhart•4mo ago
Thanks for following up with a solution - any drawbacks?
NEEGHAN
NEEGHANOP•4mo ago
No noticeable drawbacks

Did you find this page helpful?