TypeScript not Inferring Query `handler` Type Correctly
Hey everyone, I'm trying to "JOIN" some data in my query in order to include the creator directly in my response. However, it seems like TypeScript isn't able to properly infer the handler's return type even with excessive type hints:
The error I get from TypeScript is:
Can someone assist in this on how I can best JOIN the creator?
export const find = query({
args: {
paginationOpts: paginationOptsValidator,
filter: v.optional(
v.object({
createdById: v.optional(v.id("users")),
search: v.optional(v.string()),
})
),
},
handler: async (ctx, args) => {
let query = ctx.db.query("protests");
if (args.filter?.search) {
return await query
.withSearchIndex("fts", (q) => q.search("fts", args.filter!.search!))
.paginate(args.paginationOpts);
}
if (args.filter?.createdById) {
query = query.filter((q) =>
q.eq(q.field("createdById"), args.filter?.createdById)
);
}
const { page, ...res } = await query.paginate(args.paginationOpts);
const users: (Doc<"users"> & {
profileImageUrl?: string;
})[] = await Promise.all(
page
.reduce(
(ids, protest) =>
ids.indexOf(protest.createdById) !== -1
? ids
: [...ids, protest.createdById],
[] as Id<"users">[]
)
.map(
(id) =>
ctx.runQuery(api.users.findById, { id }) as Promise<
Doc<"users"> & {
profileImageUrl?: string;
}
>
)
);
return {
...res,
page: page.map(
(protest) =>
({
...protest,
createdBy: users.find((u) => u?._id === protest.createdById),
}) as Doc<"protests"> & {
createdBy: Doc<"users"> & {
profileImageUrl?: string;
};
}
),
};
},
returns: v.object({
isDone: v.boolean(),
continueCursor: v.string(),
splitCursor: v.optional(v.union(v.string(), v.null())),
pageStatus: v.optional(
v.union(
v.literal("SplitRecommended"),
v.literal("SplitRequired"),
v.null()
)
),
page: v.array(
v.object({
_id: v.id("protests"),
_creationTime: v.number(),
name: v.string(),
startDate: v.number(),
endDate: v.optional(v.number()),
createdById: v.id("users"),
mainLocationId: v.optional(v.id("locations")),
missionStatement: v.string(),
fts: v.string(),
createdBy: v.object({
name: v.optional(v.string()),
image: v.optional(v.string()),
email: v.optional(v.string()),
emailVerificationTime: v.optional(v.number()),
phone: v.optional(v.string()),
phoneVerificationTime: v.optional(v.number()),
isAnonymous: v.optional(v.boolean()),
profileImageId: v.optional(v.id("_storage")),
profileImageUrl: v.optional(v.string()),
}),
})
),
}),
});
export const find = query({
args: {
paginationOpts: paginationOptsValidator,
filter: v.optional(
v.object({
createdById: v.optional(v.id("users")),
search: v.optional(v.string()),
})
),
},
handler: async (ctx, args) => {
let query = ctx.db.query("protests");
if (args.filter?.search) {
return await query
.withSearchIndex("fts", (q) => q.search("fts", args.filter!.search!))
.paginate(args.paginationOpts);
}
if (args.filter?.createdById) {
query = query.filter((q) =>
q.eq(q.field("createdById"), args.filter?.createdById)
);
}
const { page, ...res } = await query.paginate(args.paginationOpts);
const users: (Doc<"users"> & {
profileImageUrl?: string;
})[] = await Promise.all(
page
.reduce(
(ids, protest) =>
ids.indexOf(protest.createdById) !== -1
? ids
: [...ids, protest.createdById],
[] as Id<"users">[]
)
.map(
(id) =>
ctx.runQuery(api.users.findById, { id }) as Promise<
Doc<"users"> & {
profileImageUrl?: string;
}
>
)
);
return {
...res,
page: page.map(
(protest) =>
({
...protest,
createdBy: users.find((u) => u?._id === protest.createdById),
}) as Doc<"protests"> & {
createdBy: Doc<"users"> & {
profileImageUrl?: string;
};
}
),
};
},
returns: v.object({
isDone: v.boolean(),
continueCursor: v.string(),
splitCursor: v.optional(v.union(v.string(), v.null())),
pageStatus: v.optional(
v.union(
v.literal("SplitRecommended"),
v.literal("SplitRequired"),
v.null()
)
),
page: v.array(
v.object({
_id: v.id("protests"),
_creationTime: v.number(),
name: v.string(),
startDate: v.number(),
endDate: v.optional(v.number()),
createdById: v.id("users"),
mainLocationId: v.optional(v.id("locations")),
missionStatement: v.string(),
fts: v.string(),
createdBy: v.object({
name: v.optional(v.string()),
image: v.optional(v.string()),
email: v.optional(v.string()),
emailVerificationTime: v.optional(v.number()),
phone: v.optional(v.string()),
phoneVerificationTime: v.optional(v.number()),
isAnonymous: v.optional(v.boolean()),
profileImageId: v.optional(v.id("_storage")),
profileImageUrl: v.optional(v.string()),
}),
})
),
}),
});
Type 'Promise<PaginationResult<...>>' is not assignable to type 'ValidatorTypeToReturnType<{ splitCursor?: string | null | undefined; pageStatus?: "SplitRecommended" | "SplitRequired" | null | undefined; isDone: boolean; continueCursor: string; page: { endDate?: number | undefined; ... 8 more ...; createdBy: { ...; }; }[]; }>'.
...
Property 'createdBy' is missing in type '{ _id: Id<"protests">; _creationTime: number; endDate?: number | undefined; mainLocationId?: Id<"locations"> | undefined; name: string; startDate: number; createdById: Id<"users">; missionStatement: string; fts: string; }' but required in type '{ endDate?: number | undefined; mainLocationId?: Id<"locations"> | undefined; name: string; _creationTime: number; startDate: number; createdById: Id<"users">; missionStatement: string; fts: string; _id: Id<...>; createdBy: { ...; }; }'.ts(2719)
Type 'Promise<PaginationResult<...>>' is not assignable to type 'ValidatorTypeToReturnType<{ splitCursor?: string | null | undefined; pageStatus?: "SplitRecommended" | "SplitRequired" | null | undefined; isDone: boolean; continueCursor: string; page: { endDate?: number | undefined; ... 8 more ...; createdBy: { ...; }; }[]; }>'.
...
Property 'createdBy' is missing in type '{ _id: Id<"protests">; _creationTime: number; endDate?: number | undefined; mainLocationId?: Id<"locations"> | undefined; name: string; startDate: number; createdById: Id<"users">; missionStatement: string; fts: string; }' but required in type '{ endDate?: number | undefined; mainLocationId?: Id<"locations"> | undefined; name: string; _creationTime: number; startDate: number; createdById: Id<"users">; missionStatement: string; fts: string; _id: Id<...>; createdBy: { ...; }; }'.ts(2719)
2 Replies
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!
At what stage does the inference fail, can you find the first place where the type is too general?