MichalLytek / typegraphql-prisma

Prisma generator to emit TypeGraphQL types and CRUD resolvers from your Prisma schema
https://prisma.typegraphql.com
MIT License
891 stars 113 forks source link

`_count` returning null from custom resolvers #365

Open art049 opened 1 year ago

art049 commented 1 year ago

Describe the Bug When accessed through custom resolvers, the _count field is always null.

To Reproduce Considering the following schema(I'm using mongo):

model List {
  id    String     @id @default(auto()) @map("_id") @db.ObjectId
  items ListItem[]
}

model ListItem {
  id     String @id @default(auto()) @map("_id") @db.ObjectId
  list   List   @relation(fields: [listId], references: [id])
  listId String @db.ObjectId
}

And this sample custom resolver:

import { List } from "@generated/type-graphql";
import { Ctx, Query, Resolver } from "type-graphql";
import { Context } from "..";

@Resolver()
export class MyListResolver {
  @Query(() => List)
  async mylist(@Ctx() { prisma }: Context): Promise<List> {
    return await prisma.list.findFirstOrThrow();
  }
}

With the following data: image

This query:

query Mylist {
  mylist {
    id
    _count {
      items
    }
  }
}

Returns a null count:

{
  "data": {
    "mylist": {
      "id": "6408705c0cd690ec08e5b690",
      "_count": null
    }
  }
}

Expected Behavior The query should return the actual count so:

{
  "data": {
    "mylist": {
      "id": "6408705c0cd690ec08e5b690",
      "_count": {
        "items": 2
      }
    }
  }
}

Environment (please complete the following information):

Additional Context I'm using MongoDB with prisma.

MichalLytek commented 1 year ago

This feature is only available for the generated resolvers.

It's barely possible to make it working automagically for custom resolvers under the hood.

Check out the body of the generated resolvers and implement it in your custom resolvers:

async findFirstCategory(@Ctx() ctx: any, @Info() info: GraphQLResolveInfo, @Args(_returns => FindFirstCategoryArgs) args: FindFirstCategoryArgs): Promise<Category | null> {
  const { _count } = transformInfoIntoPrismaArgs(info);
  return getPrismaFromContext(ctx).category.findFirst({
    ...args,
    ...(_count && transformCountFieldIntoSelectRelationsCount(_count)),
  });
}

You need to first read and parse the graphql resolve info to get into about requested fields in query. And then build a proper prisma args for fetching count for relations.

art049 commented 1 year ago

Okay, I thought this was an unexpected behavior. Wouldn't it be possible to add this through the Query decorator? Btw, I'd be up working on this if you think it's doable

MichalLytek commented 1 year ago

It's not possible to achieve automagically via the @Query decorator.

The crucial part here is that we can't move this logic of transformInfoIntoPrismaArgs and transformCountFieldIntoSelectRelationsCount into relation resolvers (virtual _count filed working with all queries, even custom) because Prisma relations count is coupled with the main, root db query: https://www.prisma.io/docs/concepts/components/prisma-client/aggregation-grouping-summarizing#count-relations

So that's why the crud resolver logic is reading gql query and apply _count part of the prisma query there. If we moved them to the relation resolvers, we would be missing args from the graphql query, so all the filtration would be not taken into account.

Automagic decorator also won't automagically put the _count into your prisma.xyz.find() call. All you could do is to create custom param decorator that contains all the logic, so you just get _count variable to put directly in your query.

MichalLytek commented 1 year ago

OK now I see, we can make this working tho less optimal from query perspective 😉

@Resolver()
class UserResolver {
  @Query(() => [MainUser])
  async users(@Ctx() { prismaClient }: Context) {
    return prismaClient.user.findMany();
  }
}

@Resolver(() => MainUser)
class UserRelationsResolver {
  @FieldResolver(() => MainUserCount)
  async _count(
    @Root() root: MainUser,
    @Ctx() { prismaClient }: Context,
    @Info() info: GraphQLResolveInfo,
  ) {
    const _count = transformInfoIntoPrismaArgs(info);
    const data: any = await prismaClient.user.findUniqueOrThrow({
      where: { id: root.id },
      ...(_count && transformCountFieldIntoSelectRelationsCount(_count)),
    });
    return data._count;
  }
}
art049 commented 1 year ago

Nice! Thanks.