hayes / pothos

Pothos GraphQL is library for creating GraphQL schemas in typescript using a strongly typed code first approach
https://pothos-graphql.dev
ISC License
2.35k stars 163 forks source link

PrismaUtils `prismaCreate` does not respect `Prisma.XxxxInput` interface #1135

Open cyrilchapon opened 10 months ago

cyrilchapon commented 10 months ago

Hi !

Running through the documentation, I applied the chapter "Creating input for mutations" and found a strange behavior :

export const UserSelfCreateInput: InputObjectRef<Prisma.UserCreateInput> =
  builder.prismaCreate('User', {
    name: 'UserCreateInput',
    fields: () => ({
      // Empty
    }),
  });

This perfectly TS compile, while Prisma.UserCreateInput is like this :

type Prisma.UserCreateInput = {
    id?: string | undefined;
    contactEmail: string;
    name: string;
}

The consequence is in the resolver, when using it like so :

  t.prismaField({
    type: User,
    // used here
    args: { input: t.arg({ type: UserSelfCreateInput, required: true }) },

    resolve: async (query, parent, { input: payload }, context) => {
      // `payload` resolves to `Prisma.UserCreateInput`

      const createdUser = await database.user.create({
        // So compiler is happy here
        data: payload,
      });

      return createdUser;
    },
  }),

And the generate GraphQL is like so :

input UserCreateInput

(empty input)

Which lead to runtime errors; because the GraphQL schema is not consistent to what is passed to the resolver.


I observed the exact same behavior with any kind of more complex configuration, example :

export const UserSelfCreateInput: InputObjectRef<Prisma.UserCreateInput> =
  builder.prismaCreate('User', {
    name: 'UserCreateInput',
    fields: (t) => ({
      contactEmail: t.string({
        required: true,
        validate: { schema: z.string().email() },
      }),
      name: t.string({
        // This shouldn't be accepted as it's not nullable in the model
        required: false
      }),
    }),
  });

My observation is that prismaCreate.fields is basically just not producing something type-checkable with a type like so :

const variable: InputObjectRef<Prisma.UserCreateInput>
hayes commented 10 months ago

This used to be stricter, but some changes prisma made, made maintaining it very painful.

You could probably define your type as:

export const UserSelfCreateInput: InputObjectRef<Partial<Prisma.UserCreateInput>> =
  builder.prismaCreate('User', {
    name: 'UserCreateInput',
    fields: () => ({
      // Empty
    }),
  });

For a lot of create inputs, you want to mix user provided inputs with things provided in the resolver, it is rare that the user is providing all fields in their input. This does lead to some lack of type-saftey.

I am not against having the types be more accurate here, but getting this to work reliably with prisma's input types without compromising other parts of the developer experience is tricky and there are higher priority features I am working on right now, so I probably won't be able to improve this until after v4 releases