Dan6erbondD
Convex Community12mo ago
2 replies
Dan6erbond

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:
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()),
        }),
      })
    ),
  }),
});


The error I get from TypeScript is:
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)


Can someone assist in this on how I can best JOIN the creator?
Was this page helpful?