Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

compatibility issue in generated code for prisma v5 not assignable to type #445

Open
simonjoom opened this issue Mar 18, 2024 · 7 comments
Labels
bug Something isn't working community Something initiated by the community question Further information is requested

Comments

@simonjoom
Copy link

Describe the Bug
compatibility issue in generated code for prisma v5

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; } ...

Environment (please complete the following information):

  • Node v20.11.1
  • typegraphql-prisma last current version
  • Prisma version 5

[see isssue] (unlight/prisma-nestjs-graphql#177 (comment))
at the end one solution

@MichalLytek
Copy link
Owner

@simonjoom "Fixing" the type only hides the bug. GraphQL runtime does not perform "at least one" check. So you may end up in runtime error where Prisma yells about missing fields for unique.

So I would recommend check in resolver manually and TS should infer the values are checked and you can use spread to set where fields in prisma query properly without type issues.

@MichalLytek MichalLytek added the question Further information is requested label Mar 18, 2024
@simonjoom
Copy link
Author

simonjoom commented Mar 19, 2024

This is aim to resolve the typings issue,.. which is shame to have a typescript generator if we cannot properly use typescript..

, i don't think i will have any runtime error after my change though, read the solution,
i use Prisma.AtLeast for the 'at least one check '
Anyway in code we will never use more than one unique for a whereinput.. i dont see the relevance to check a whereinput with 2 unique... so if a runtime error occur it s only because the implementation is not correct.

this workaround allow to not see a typings issue on something not relevant anyway..

thks

@MichalLytek
Copy link
Owner

This is a workaround, not a solution. With your "fix" you can bypass the typescript type check, as this check takes place only in compile time. In runtime you would then be able to send no data (as graphql definitions alows to do so), your code would have no errors passing those args into prisma client query, but you would end up with error thrown into face 😛

So yeah, while I agree there should be no type issues in generated code, I can't agree with the proposed solution. If you don't care about runtime errors and just want to silence the compiler, just place any all over the code. This is the same quality workaround as proposed.

@simonjoom
Copy link
Author

simonjoom commented Mar 20, 2024

well
I just dont want to use 'any' all over the code... what the point to have typescript else???

maybe to show that the workaround work pretty good for me i share below the code that i do use..

I want to say also that nestjs use the same workaround... i m not the only one.

here below a simplified version of a resolver code i use..
I do get all typings references from the generated typescript, the changes done in the generated code allow all this to work (added some atleast for all reference to a whereuniqueinput and connects like i explained upper).

As you can see the mutation here contains lot of complicated nested upsert ...etc and it s work, the query work in my tests and also in studio apollo.
i didnt have any graphql errors at all... well maybe give me an example that graphql can throw an error?

If i dont apply this workaround then i could not use typescript to check my code is correctly written,..

//the resolver take argument update=true to make some modifications in database listings before to show all the listings/
//resultsArray is just a json object from algolia {client.initIndex("listings")} that i use to retrieve my data to reflect into prisma dtb
import {
    Detail, DetailCreateInput, Listing, ListingUpdateInput, CategoryUpsertWithoutListingInput, ListingWhereUniqueInput, CategoryUpdateOneRequiredWithoutListingNestedInput, CategoryWhereUniqueInput, CategoryCreateOrConnectWithoutListingInput, Sub_CatUpdateOneRequiredWithoutListingNestedInput, FreelancerUpdateManyWithoutListingNestedInput, ListingCreateInput, ListingUpsertWithWhereUniqueWithoutSub_catInput,
    FreelancerCreateOrConnectWithoutListingInput, ResortCreateOrConnectWithoutFreelancerInput, Sub_CatCreateOrConnectWithoutListingInput, CountryCreateOrConnectWithoutUserInput, UserCreateOrConnectWithoutFreelancerInput, LangueCreateOrConnectWithoutFreelancerInput
} from '../prisma/generated/type-graphql';

@Resolver()
export class ListingResolver {
    @Query(_returns => [Listing], { nullable: true })
    async getListings(@Ctx() ctx: ContextValue, @Info() info: GraphQLResolveInfo, @Arg('update', { nullable: true }) update: boolean): Promise<Listing[]> {
        let Listings = [];
if (update) {
            const { hits, nbHits, nbPages } = await client.initIndex("listings").search("", {})
            let resultsArray: any = hits.map(hit => {
                const { _highlightResult, ...el } = hit;
                return el;
            }); 
//below the function getattrobj just format deeply the json correctly 
            resultsArray = resultsArray.map(hit => getattrobj(hit, Object.keys(hit).sort(), false))


            for (const e of resultsArray) { 
                const params = {
                    where: { Slug: e.Slug } as Prisma.AtLeast<ListingWhereUniqueInput, "id" | "Slug">,
                    create: {
                        Slug: e.Slug,
                        price: e.price as number,
                        description: e.description,
                        cover_image: e.cover_image ? e.cover_image : "",
                        category: {
                            connectOrCreate: {
                                where: { name: category.name },
                                create: {
                                    name: category.name,
                                    description: category.description,
                                    cover_image: category.cover_image ? category.cover_image : ""
                                }
                            } as CategoryCreateOrConnectWithoutListingInput
                        },
                        sub_cat: {
                            connectOrCreate: {
                                where: { name: sub_cat.name },
                                create: {
                                    name: sub_cat.name,
                                    type: sub_cat.type,
                                    Slug: sub_cat.Slug
                                },
                            } as Sub_CatCreateOrConnectWithoutListingInput
                        } as Sub_CatUpdateOneRequiredWithoutListingNestedInput

                    } as ListingCreateInput,
                    update: {
                        price: { set: e.price as number },
                        description: { set: e.description },
                        cover_image: { set: e.cover_image ? e.cover_image : "" },
                        category: {
                            upsert: {
                                where: { name: { equals: category.name } },
                                create: {
                                    name: category.name,
                                    description: category.description,
                                    cover_image: category.cover_image
                                },
                                update: {
                                    name: { set: category.name },
                                    description: { set: category.description },
                                    cover_image: { set: category.cover_image ? category.cover_image : "" }
                                }
                            } as CategoryUpsertWithoutListingInput
                        } as CategoryUpdateOneRequiredWithoutListingNestedInput,
                        sub_cat: {
                            connectOrCreate: {
                                where: { name: sub_cat.name },
                                create: {
                                    name: sub_cat.name,
                                    type: sub_cat.type,
                                    Slug: sub_cat.Slug
                                },
                            } as Sub_CatCreateOrConnectWithoutListingInput
                        } as Sub_CatUpdateOneRequiredWithoutListingNestedInput
                    } as ListingUpdateInput
                }

           await ctx.prisma.listing.upsert({
                    ...params
                })
}
}
        return await ctx.prisma.listing.findMany(); //return the listings saved in prisma
    }

it s just a pain in the ass to repatch all the generated code as soon i need to change my schema

@MichalLytek
Copy link
Owner

MichalLytek commented Mar 20, 2024

i didnt have any graphql errors at all... well maybe give me an example that graphql can throw an error?

Sure! 😉

/// @@TypeGraphQL.type(name: "MainUser")
model User {
  id          Int      @id @default(autoincrement())
  email       String   @unique
  age         Int
}
  @Query(returns => MainUser)
  async uniqueMainUser(
    @Ctx() { prismaClient }: Context,
    @Args() args: FindUniqueMainUserArgs,
  ): Promise<Prisma.User> {
    return await prismaClient.user.findUniqueOrThrow(args);
  }
query UniqueArgsFailing {
  uniqueMainUser(where: {age: {gte: 18}}) {
    id
    email
  }
}

Explanation:
The signature of of MainUserWhereUniqueInput in GraphQL definition is that both id and email are optional. However, Prisma requires one of them to be present. So you can send "malformed" GraphQL query parameter, like where age gte 18, which is allowed by GraphQL, but Prisma throws an error.

Why your workaround is just about supressing the error? Because when we put Prisma.AtLeast we just make TS happy. But GraphQL nor TypeGraphQL still does not know about this rule. So the fields are still optional, still you can execute query that will break Prisma, but your IDE does not have red underline and you live happy, thinking all good, while production is broken 😉

A proper solution would be to include a runtime check. GraphQL does not support input unions, there's some work on @oneOf directive. So for now the only option is to do the check manually. Then TS compiler should now that one of those field is not null and allow to pass to Prisma Client.
Much complex but better solution would be to include those checks in generated resolvers, so that they will also work properly, without crashing Prisma.

@simonjoom
Copy link
Author

simonjoom commented Mar 21, 2024

i dont see again where my code can be wrong..

Yes i do understand when you say GraphQL does not support input unions.. then maybe prisma have to remove this "Prisma.atleast " ... if you dont want to follow their signature afterall prisma work with graphql right?

what you just wrote: "Much complex but better solution would be to include those checks in generated resolvers, so that they will also work properly, without crashing Prisma."

it s exactly what i did,
i added Prisma.AtLeast in each generated resolvers (like Userwhereunique ..modelWhereunique ... that i use in code where exist a check on one unique ,
yes typescript is happy and the final code will remain the same.. (the mods dont change the compilation code)
With the mods typescript prisma signature pass with typegraphql-prisma

there is as well one change that your generated code should do... if there is not any unique in the schema (other than id of course) then in any whereUniqueinput

id?:

should not be optional but instead:

id!:

of course to follow prisma typescript happy

@MichalLytek
Copy link
Owner

I sustain my claim and I'm sorry that you don't understand the root issue.
I'm afraid that you need to use the fork/mod with your "workaround" until this issue will be fully solved in a proper way.
It might work for your limited use-case but I've pointed another query that fails no matter we "assert" the type by "fixing" the arg field TS type or by using as any - the result is the same, Prisma throws runtime validation error.

Repository owner locked as too heated and limited conversation to collaborators Mar 21, 2024
@MichalLytek MichalLytek added bug Something isn't working community Something initiated by the community labels Mar 21, 2024
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
bug Something isn't working community Something initiated by the community question Further information is requested
Projects
None yet
Development

No branches or pull requests

2 participants