Dan6erbond
CCConvex Community
•Created by Dan6erbond on 2/10/2025 in #support-community
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)
3 replies