unlight / prisma-nestjs-graphql

Generate object types, inputs, args, etc. from prisma schema file for usage with @nestjs/graphql module

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

Prisma 5 released - breaks generated types

jasonmacdonald opened this issue · comments

It looks like the changes introduced in V5 of Prisma break the generated files from this. Lots of * is not assignable to type * type errors. I guess it's because of the changes to carry shortcuts, but it might be something else. I've tried searching through, but I got in a bit of looping trying to find the exact reason.

https://www.prisma.io/docs/guides/upgrade-guides/upgrading-versions/upgrading-to-prisma-5#removal-of-array-shortcuts

It looks like they are now wrapping certain types in a Prisma.AtLeast<{...classProps}, "prop1" | "prop1"> function which makes certain properties required if they are, I assume, @id, @@id or @unique in the schema.

EDIT: Narrowed it down to any WhereUniqueInput class. they are all wrapped in this condition now.

I'm trying to fix it, note sure if that's enough but maybe doing something like that in input-type.ts will fix the issue:

    const isWhereUniqueInput = inputType.name.endsWith('WhereUniqueInput');
    const modelField = model?.fields.find(f => f.name === name);
    const isCustomsApplicable = typeName === modelField?.type;
    const isId = modelField?.isId; // Extract if it's an id
    const isUnique = modelField?.isUnique; // Extract if it's unique
    const hasDefaultValue = modelField?.hasDefaultValue; // Extract if it has default value
    // Create a new nullable condition based on the values
    const isNullable = !((isId && hasDefaultValue) || (isId && isWhereUniqueInput) || isUnique || isRequired);
    const propertyType = castArray(
      propertySettings?.name ||
        getPropertyType({
          location,
          type: typeName,
        }),
    );
    const property = propertyStructure({
      name,
      isNullable,
      propertyType,
      isList,
    });

Is it the correct way to go @unlight ?

@unlight I make a PR, hope it's the correct way to fix it

@jasonmacdonald @magrinj @Siumauricio
Please check version 19 (use next dist-tag) with prisma 5

npm i prisma-nestjs-graphql@next

Hi @unlight, thanks a lot, I will take a look today :)
Didn't had time to fix tests issues yesterday, but it seems you fix them

Can confirm that v19.0.0 works as expected.

@unlight Unfortunately, I'm still getting errors in any WHere class with an AND etc., as they are self-references and, therefore, not wrapped with the Prisma.AtLeast. So we need to also add it to those.

export declare class SomeClassWhereUniqueInput {
    AND?: Array<SomeClassWhereUniqueInput>;
    OR?: Array<SomeClassWhereUniqueInput>;
    NOT?: Array<SomeClassWhereUniqueInput>;
}

I think the issue is that we are wrapping the where: Prisma.AtLeast<SomeCLasseUniqueInput, 'id'>; instead of the actual type like Prisma does.

 export type SomeClassWhereUniqueInput = Prisma.AtLeast<{
    id?: string
    AND?: SomeClassWhereUniqueInput | SomeClassWhereUniqueInput[]
    OR?: SomeClassWhereUniqueInput[]
    NOT?: SomeClassWhereUniqueInput | SomeClassWhereUniqueInput[]
    ...
}

@jasonmacdonald
Please provide example schema, how can I reproduce it.

@jasonmacdonald Curious if you ended up finding a workaround here? We are seeing the same issue in our project as well

@jasonmacdonald Curious if you ended up finding a workaround here? We are seeing the same issue in our project as well

We held off upgrading due to some other reasons, but this issue still exists for us as well. I haven't had a chance to dig deeper though.

I have same problem. Installed prisma-nestjs-graphql@next version.

findOne(userWhereUniqueInput: UserWhereUniqueInput) {
    return this.prisma.user.findUnique({
        where: userWhereUniqueInput,
    }) as Promise<User>;
}

Here is type error:

Type 'UserWhereUniqueInput' is not assignable to type '{ id: string; email: string; } & { id?: string | undefined; email?: string | undefined; AND?: UserWhereInput | UserWhereInput[] | undefined; OR?: UserWhereInput[] | undefined; NOT?: UserWhereInput | ... 1 more ... | undefined; password?: string | ... 1 more ... | undefined; createdAt?: string | ... 3 more ... | unde...'.
Type  UserWhereUniqueInput  is not assignable to type  { id: string; email: string; } 
Types of property  id  are incompatible.
Type  string | undefined  is not assignable to type  string 
Type  undefined  is not assignable to type  string 
index.d.ts(1471, 5): The expected type comes from property  where  which is declared here on type

Prisma model

model User {
  /// @HideField({ match: 'User*@(Create|Update)*Input' })
  id        String    @id @default(cuid())
  email     String    @unique @db.VarChar(255)
  password  String
  /// @HideField({ output: false, input: true })
  createdAt DateTime? @default(now())
  /// @HideField({ output: false, input: true })
  updatedAt DateTime? @updatedAt
}

Here is generate prisma UserWhereUniqueInput

export type UserWhereUniqueInput = Prisma.AtLeast<{
    id?: string
    email?: string
    AND?: UserWhereInput | UserWhereInput[]
    OR?: UserWhereInput[]
    NOT?: UserWhereInput | UserWhereInput[]
    password?: StringFilter<"User"> | string
    createdAt?: DateTimeNullableFilter<"User"> | Date | string | null
    updatedAt?: DateTimeNullableFilter<"User"> | Date | string | null
}, "id" | "email">

And here prisma-nestjs-graphql UserWhereUniqueInput

import { Field } from '@nestjs/graphql';
import { InputType } from '@nestjs/graphql';
import { UserWhereInput } from './user-where.input';
import { StringFilter } from '../prisma/string-filter.input';
import { DateTimeNullableFilter } from '../prisma/date-time-nullable-filter.input';
import { HideField } from '@nestjs/graphql';

@InputType()
export class UserWhereUniqueInput {

    @Field(() => String, {nullable:true})
    id?: string;

    @Field(() => String, {nullable:true})
    email?: string;

    @Field(() => [UserWhereInput], {nullable:true})
    AND?: Array<UserWhereInput>;

    @Field(() => [UserWhereInput], {nullable:true})
    OR?: Array<UserWhereInput>;

    @Field(() => [UserWhereInput], {nullable:true})
    NOT?: Array<UserWhereInput>;

    @Field(() => StringFilter, {nullable:true})
    password?: StringFilter;

    @HideField()
    createdAt?: DateTimeNullableFilter;

    @HideField()
    updatedAt?: DateTimeNullableFilter;
}

any idea?

@bigfree Thank you for example.

Yes, UserWhereUniqueInput and Prisma.AtLeast<UserWhereUniqueInput,...> are incompatible
But GraphQL doesnt have 'oneOf' feature (e.g. https://gatographql.com/guides/augment/oneof-input-object/)

From my POV, generated class is valid, I do not see how it can be fixed in terms of compatibility.
So, I think you should add additional validation logic to your method.

For example:

findOne(userWhereUniqueInput: UserWhereUniqueInput) {
    // Validate that userWhereUniqueInput has id, email other unique key
    const validatedUserWhereUniqueInput = userWhereUniqueInput as Prisma.AtLeast<UserWhereUniqueInput, ...>;
    return this.prisma.user.findUnique({ where: validatedUserWhereUniqueInput });
}
  // Pseudo code
  @UsePipes(new ValidateAtLeastIdOrEmail())
  findOne(userWhereUniqueInput: Prisma.AtLeast<UserWhereUniqueInput, 'id' | 'email'>) {
    $prisma.user.findUnique({ where: userWhereUniqueInput }); // OK
  }
const userWhereUniqueInput: UserWhereUniqueInput = {}; // ERROR

$prisma.user.findUnique({ where: userWhereUniqueInput }); // OK
$prisma.user.findUnique({ where: userWhereUniqueInput as Prisma.AtLeast<UserWhereUniqueInput, 'id' | 'email'> }); // OK

Maybe I may add additional metadata to such classes.

@InputType()
export class UserWhereUniqueInput {
  @Field(() => String, { nullable: true })
  @Reflect.metadata('AtLeast')
  id?: string;

  @Field(() => Scalars.GraphQLEmailAddress, { nullable: true })
  @Reflect.metadata('AtLeast')
  email?: string;
}

Validator can be created
These metadata can be parsed by custom pipe validator, but it will not fix compatibility in typescript.

Another idea - introduce new option in config to trick typescript and add exclamation mark for all unique fields for such WhereUniqueInput classes (unsafe, needs validation and coercing to valid type).

@InputType()
export class UserWhereUniqueInput {
  @Field(() => String, { nullable: true })
  id!: string;

  @Field(() => Scalars.GraphQLEmailAddress, { nullable: true })
  email!: string;
}

Another idea - introduce new option in config to trick typescript and add exclamation mark for all unique fields for such WhereUniqueInput classes (unsafe, needs validation and coercing to valid type).

If helpful, I think we'd choose this solution! Maybe this should be hidden behind some configuration flag since it sacrifices some type safety though?

@unlight Thx for you response. I'll try type assertion, but it's a bit messy in the code. I'm in for a switch in the configuration even if it is a bit unsafe.

@unlight Unfortunately, I'm still getting errors in any WHere class with an AND etc., as they are self-references and, therefore, not wrapped with the Prisma.AtLeast. So we need to also add it to those.

export declare class SomeClassWhereUniqueInput {
    AND?: Array<SomeClassWhereUniqueInput>;
    OR?: Array<SomeClassWhereUniqueInput>;
    NOT?: Array<SomeClassWhereUniqueInput>;
}

I think the issue is that we are wrapping the where: Prisma.AtLeast<SomeCLasseUniqueInput, 'id'>; instead of the actual type like Prisma does.

 export type SomeClassWhereUniqueInput = Prisma.AtLeast<{
    id?: string
    AND?: SomeClassWhereUniqueInput | SomeClassWhereUniqueInput[]
    OR?: SomeClassWhereUniqueInput[]
    NOT?: SomeClassWhereUniqueInput | SomeClassWhereUniqueInput[]
    ...
}

@unlight update on the issue I posted a while back. I finally had time to look deeper into the matter; a custom InputType in our code was the cause. We had created one that was not using AtLeast properly when including a unique input created by this library. It was tricky to hunt down due to the overly verbose nested typescript errors. Once I fixed the aforementioned input, everything worked as expected. Sorry for the false alarm.

@ysuhaas, if you are still getting a similar issue, try checking any custom InputType or ObjectTypes you have.

🎉 This issue has been resolved in version 19.2.0 🎉

The release is available on:

Your semantic-release bot 📦🚀

🎉 This issue has been resolved in version 19.2.0 🎉

The release is available on:

Your semantic-release bot 📦🚀

commented

I got the same problem using last type-graphql and typegraphql-prisma generator

for me to resolve what i did:
when the class got only one @unique field id in his model, change the ? to the exclamation class

@InputType()
export class UserWhereUniqueInput {
  @Field(() => String, { nullable: true })
  id!: string;

  @Field(() => String, { nullable: true })
  fieldoptional?: string;
}

but if there is atleast 2 unique in the model

you will need to change all relations in type-graphql/resolvers/** files generated code :
(here for a model user with unique type are id and email)
where!: UserWhereUniqueInput;
to
where!: Prisma.AtLeast<UserWhereUniqueInput, 'id' | 'email'>;
and also for relation connect
(here with model Country with unique type are id and name)
connect?: CountryWhereUniqueInput | undefined;
to change :
connect?: Prisma.AtLeast<CountryWhereUniqueInput,"id"|"name"> | undefined;

I think this way is the more clean one and should to work for both in runtime and typings.

Seems the last code prisma-nestjs-graphql fix that, but would be great that the generator
https://github.com/MichalLytek/typegraphql-prisma do that by default i think