prisma-nestjs-graphql icon indicating copy to clipboard operation
prisma-nestjs-graphql copied to clipboard

GroupBy Argument Type required parameter follows an optional parameter

Open lodeli-lulu opened this issue 10 months ago • 8 comments

Hey,

the required property by follows the optional parameters where and orderBy, which results in an error on the generated typescript definition file from NestJs.

Example to reproduce:

// Prisma Schema
model User {
  id        Int        @id @default(autoincrement())
  email     String     @unique
  name      String?
  blogPosts BlogPost[]

  @@map("user")
}

model BlogPost {
  id        Int      @id @default(autoincrement())
  createdAt DateTime @default(now()) @map(name: "created_at")
  updatedAt DateTime @updatedAt @map(name: "updated_at")
  title     String
  content   String?
  published Boolean  @default(false)
  viewCount Int      @default(0) @map(name: "view_count")
  author    User?    @relation(fields: [authorId], references: [id])
  /// @HideField()
  authorId  Int?     @map(name: "author_id")

  @@map("blog_post")
}
// NestJs Query
@Query(() => [UserGroupBy])
  private async groupByUser(
    @Args() groupByArgs: UserGroupByArgs,
  ): Promise<UserGroupBy[]> {
    return this._user.groupBy(groupByArgs) as Promise<UserGroupBy[]>;
  }

You will get following error in generated typescript definitions file:

error TS1016: A required parameter cannot follow an optional parameter.

abstract groupByUser(where?: Nullable<UserWhereInput>, orderBy?: Nullable<UserOrderByWithAggregationInput[]>, by: UserScalarFieldEnum[], having?: Nullable<UserScalarWhereWithAggregatesInput>, take?: Nullable<number>, skip?: Nullable<number>, _count?: Nullable<UserCountAggregateInput>, _avg?: Nullable<UserAvgAggregateInput>, _sum?: Nullable<UserSumAggregateInput>, _min?: Nullable<UserMinAggregateInput>, _max?: Nullable<UserMaxAggregateInput>): UserGroupBy | Promise<UserGroupBy>;

My versions:

  • NestJs version: 11
  • Prisma version: 6
  • prisma-nestjs-graphql: 21.0.2

lodeli-lulu avatar Jan 22 '25 12:01 lodeli-lulu

Some generated graphql classes (input/output) may not be compatible with prisma types. You must adapt it.

unlight avatar Jan 23 '25 04:01 unlight

It's not the problem, that some generated graphql classes are not compatible with prisma types.

The fields of the generated @ArgType for the groupBy method have an incorrect order. The required fields should be defined before the optional fields.

Current generated class:

@ArgsType()
export class UserGroupByArgs {
  @Field(() => UserWhereInput, { nullable: true })
  @Type(() => UserWhereInput)
  where?: InstanceType<typeof UserWhereInput>;
  @Field(() => [UserOrderByWithAggregationInput], { nullable: true })
  orderBy?: Array<UserOrderByWithAggregationInput>;
  @Field(() => [UserScalarFieldEnum], { nullable: false }) // <-- Required field after optional fields
  by!: Array<`${UserScalarFieldEnum}`>;
  @Field(() => UserScalarWhereWithAggregatesInput, { nullable: true })
  having?: InstanceType<typeof UserScalarWhereWithAggregatesInput>;
  @Field(() => Int, { nullable: true })
  take?: number;
  @Field(() => Int, { nullable: true })
  skip?: number;
  @Field(() => UserCountAggregateInput, { nullable: true })
  _count?: InstanceType<typeof UserCountAggregateInput>;
  @Field(() => UserAvgAggregateInput, { nullable: true })
  _avg?: InstanceType<typeof UserAvgAggregateInput>;
  @Field(() => UserSumAggregateInput, { nullable: true })
  _sum?: InstanceType<typeof UserSumAggregateInput>;
  @Field(() => UserMinAggregateInput, { nullable: true })
  _min?: InstanceType<typeof UserMinAggregateInput>;
  @Field(() => UserMaxAggregateInput, { nullable: true })
  _max?: InstanceType<typeof UserMaxAggregateInput>;
}

Class as it should be generated:

@ArgsType()
export class UserGroupByArgs {
  @Field(() => [UserScalarFieldEnum], { nullable: false }) // <-- Required field at first
  by!: Array<`${UserScalarFieldEnum}`>;
  @Field(() => UserWhereInput, { nullable: true })
  @Type(() => UserWhereInput)
  where?: InstanceType<typeof UserWhereInput>;
  @Field(() => [UserOrderByWithAggregationInput], { nullable: true })
  orderBy?: Array<UserOrderByWithAggregationInput>;
  @Field(() => UserScalarWhereWithAggregatesInput, { nullable: true })
  having?: InstanceType<typeof UserScalarWhereWithAggregatesInput>;
  @Field(() => Int, { nullable: true })
  take?: number;
  @Field(() => Int, { nullable: true })
  skip?: number;
  @Field(() => UserCountAggregateInput, { nullable: true })
  _count?: InstanceType<typeof UserCountAggregateInput>;
  @Field(() => UserAvgAggregateInput, { nullable: true })
  _avg?: InstanceType<typeof UserAvgAggregateInput>;
  @Field(() => UserSumAggregateInput, { nullable: true })
  _sum?: InstanceType<typeof UserSumAggregateInput>;
  @Field(() => UserMinAggregateInput, { nullable: true })
  _min?: InstanceType<typeof UserMinAggregateInput>;
  @Field(() => UserMaxAggregateInput, { nullable: true })
  _max?: InstanceType<typeof UserMaxAggregateInput>;
}

lodeli-lulu avatar Jan 24 '25 09:01 lodeli-lulu

Weird. 😕

unlight avatar Jan 24 '25 20:01 unlight

Like I suspected, order of fields in class doesnt matter.

Image

Some types may not be compatible, in your case with schema above generated UserGroupByArgs is compatible with prisma user.groupBy argument.

import { Prisma } from '@prisma/client';

  @Query(() => [UserGroupBy])
  async groupByUser(
    @Args() groupByArgs: UserGroupByArgs,
  ): Promise<UserGroupBy[]> {
    const userGroupByArgs: Prisma.UserGroupByArgs = groupByArgs; // no errors 

    return prisma.user.groupBy(userGroupByArgs);
  }


unlight avatar Jan 25 '25 11:01 unlight

Tested on graphql query

query {
  groupByUser(
    orderBy: [{ name: asc }]
    by: [id, name]
  ) 
  {
    id
    name
  }
}
userGroupByArgs = {
  orderBy: [ { name: 'asc' } ],
  by: [ 'id', 'name' ]
}

unlight avatar Jan 25 '25 11:01 unlight

I created a minimal reproduction project. You can find it here.

I noticed that I used that the definitions property to configure the GraphQLModule, but it is not necessary in code-first approach. The error error TS1016: A required parameter cannot follow an optional parameter. occurred in this generated definitions file.

However, I get still an error on the groupBy function:

@Query(() => [UserGroupBy])
  async groupByUser(
    @Args() groupByArgs: UserGroupByArgs,
  ): Promise<UserGroupBy[]> {
    const userGroupByArgs: Prisma.UserGroupByArgs = groupByArgs;

    // TODO: Does not work without @ts-ignore
    return this._prisma.user.groupBy(userGroupByArgs);
  }

You can search for TODO in my example project.

lodeli-lulu avatar Jan 27 '25 13:01 lodeli-lulu

Looks like you are facing this issue https://github.com/prisma/prisma/issues/17297

unlight avatar Jan 27 '25 13:01 unlight

I see different error:

Type of property 'AND' circularly references itself in mapped type '{ [K in keyof { AND?: BlogPostScalarWhereWithAggregatesInput | BlogPostScalarWhereWithAggregatesInput[]; ... 9 more ...; authorId?: number | IntNullableWithAggregatesFilter<...>; }]: Or<...> extends 1 ? { ...; }[K] extends infer TK ? GetHavingFields<...> : never : {} extends FieldPaths<...> ? never : K; }'

unlight avatar Jan 27 '25 18:01 unlight