When using the Prisma client together with graphqlgen in TS scrict mode, the generated typings for optional fields from the Prisma datamodel and the graphqlgen resolver arguments don't match. Example:
datamodel.prisma
type User {
id: ID! @unique
email: String! @unique
name: String
posts: [Post!]!
}
type Post {
id: ID! @unique
createdAt: DateTime!
updatedAt: DateTime!
published: Boolean! @default(value: "false")
title: String!
content: String
author: User!
}
For example, content on Post is optional here. Now consider this GraphQL schema:
scalar DateTime
type Query {
feed: [Post!]!
filterPosts(searchString: String): [Post!]!
post(id: ID!): Post
}
type Mutation {
signupUser(email: String!, name: String): User!
createDraft(title: String!, content: String, authorEmail: String!): Post!
deletePost(id: ID!): Post
publish(id: ID!): Post
}
type Post {
id: ID!
createdAt: DateTime!
updatedAt: DateTime!
published: Boolean!
title: String!
content: String
author: User!
}
type User {
id: ID!
email: String!
name: String
posts: [Post!]!
}
The content field is optional in the createDraft mutation. Now, the generated resolver args from graphqlgen for the input to createDraft look like this:
export interface ArgsCreateDraft {
title: string
content: string | null
}
On the other hand, the generated PostCreateInput type for the createPost operation in the Prisma client API looks as follows:
export interface PostCreateInput {
published?: Boolean;
title: String;
content?: String;
author: UserCreateOneWithoutPostsInput;
}
content here is declared as an _optional_ instead of string | null. As fas as I understand, the optional is interpreted as string | undefined. Now, when implementing the createDraft resolver like so:
createDraft: (parent, { title, content, authorEmail }, ctx) => {
return ctx.prisma.createPost({
title,
content,
author: {
connect: {
email: authorEmail,
},
},
})
},
TypeScript throws this error in strict mode:
src/resolvers/Mutation.ts:15:7 - error TS2322: Type 'string | null' is not assignable to type 'string | undefined'.
Type 'null' is not assignable to type 'string | undefined'.
15 content,
~~~~~~~
src/generated/prisma-client/index.ts:282:3
282 content?: String;
~~~~~~~
The expected type comes from property 'content' which is declared here on type 'PostCreateInput'
src/resolvers/Query.ts:14:29 - error TS2345: Argument of type '{ where: { OR: ({ title_contains: string | null; } | { content_contains: string | null; })[]; }; }' is not assignable to parameter of type '{ where?: PostWhereInput | undefined; orderBy?: "id_ASC" | "id_DESC" | "createdAt_ASC" | "createdAt_DESC" |"updatedAt_ASC" | "updatedAt_DESC" | "published_ASC" | "published_DESC" | ... 4 more ... | undefined; ... 4 more ...; last?: number | undefined; }'.
Types of property 'where' are incompatible.
Type '{ OR: ({ title_contains: string | null; } | { content_contains: string | null; })[]; }' is not assignable to type 'PostWhereInput'.
Types of property 'OR' are incompatible.
Type '({ title_contains: string | null; } | { content_contains: string | null; })[]' is not assignable to type 'PostWhereInput | PostWhereInput[] | undefined'.
Type '({ title_contains: string | null; } | { content_contains: string | null; })[]' is not assignable to type 'PostWhereInput[]'.
Type '{ title_contains: string | null; } | { content_contains: string | null; }' is not assignable to type 'PostWhereInput'.
Type '{ title_contains: string | null; }' is not assignable to type 'PostWhereInput'.
Types of property 'title_contains' are incompatible.
Type 'string | null' is not assignable to type 'string | undefined'.
Type 'null' is not assignable to type 'string | undefined'.
14 return ctx.prisma.posts({
~
15 where: {
~~~~~~~~~~~~~~
...
24 },
~~~~~~~~
25 })
~~~~~
As suggested by @Weakky here, we need to In order to fix this, we need to both fix prisma-client and graphqlgen to type all nullable input fields as field?: type | null.
I just found out that this problem also applies to the Flow client. This is the Flow error that's thrown:
Error โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ src/resolvers/Mutation.js:12:34
Cannot call ctx.prisma.createPost with object literal bound to data because:
โข null [1] is incompatible with string [2] in property content.
โข object literal [3] is incompatible with undefined [4] in property author.connect.
src/resolvers/Mutation.js
9โ })
10โ },
11โ createDraft: (parent, { title, content, authorEmail }, ctx, info) => {
12โ return ctx.prisma.createPost({
13โ title,
14โ content,
15โ author: {
[3] 16โ connect: { email: authorEmail },
17โ },
18โ })
19โ },
20โ deletePost: (parent, { id }, ctx, info) => {
21โ return ctx.prisma.deletePost({
src/generated/graphqlgen.js
[1] 249โ content: string | null;
src/generated/prisma-client/index.js
[2] 276โ content?: String;
:
[4] 287โ connect?: UserWhereUniqueInput;
? on object keysAs fas as I understand, the optional is interpreted as string | undefined
There is a difference between e.g. a: undefined | 1 and a?: 1
$ ts-node -v
ts-node v7.0.1
node v10.11.0
typescript v3.2.2
$ ts-node -O '{"strict":true}'
type Args = { a: undefined | 1, b?: 2 }
let args: Args
args = {}
// error TS2741: Property 'a' is missing in type '{}' but required in type 'Args'
args = { a: undefined }
// ok
let { a, b } = args
console.log(a, b)
// undefined undefined
As you can see only the a: undefined | 1 variant guarantees that a key is present. But from a consumer's point of view, it does not matter.
null vs undefined in graphqlConsider this schema:
type Message {
content: String!
}
type Query {
messages(foo: Boolean, bar: Boolean = null): [Message!]!
}
In the following queries, observe the resolver args values:
query implicit {
messages {
content
}
}
{ bar: null }
md5-6a5276bfd64315b70cfb08b5cfde62bd
```ts
{ foo: null, bar: null }
md5-dd63091d2ca1ed2a5436735ced38f605
GQL TS
a: String a?: null | string
^ ^
| | 2. user may pass explicit null
| 1. user may pass nothing
a: String = null a: null | string
^
| 1. user may pass explicit null
| 2. user may pass nothing, thus gql null default
One thing I would suggest tweaking about the proposed fix is that graphql gen should be able to output either case stated above: a?: null | string _or_ a: null | string. As long as nullable prisma client args have type a?: null | string the types will work.
Excellent work. Hope this gets fixed soon.
I have exactly the same problem.
Graphql-code-generator has an avoidOptionals setting for this.
https://graphql-code-generator.com/docs/plugins/typescript-common
Maybe something similar could be the solution here?
@breytex there is already a clean solution identified though please let us know if you identified any flaws!
@jasonkuhrt do you mean this PR? https://github.com/prisma/graphqlgen/pull/329
I just found it.
Unfortunately I cant test wether this PR solves my problem or not, because npm install prisma/graphqlgen#1f8b347 gives me "Missing package name". prisma/graphqlgen doesn't have a name specified in the package.json. Any other way I can test this without monkey patching?
Thanks :)
prisma/graphqlgen is a yarn workspaces monorepo so I don't think your install approach there will work. I don't actually have much experience with yarn workspaces / learna / monorepos in general (was ramping up tonight with some reading! ๐
) so can't give you a confident one-liner right now.
I think something like this could work:
Anyway, hopefully the issue will be resolved soon. Also better project ci/cd is something that is on the roadmap I think https://github.com/prisma/graphqlgen/issues/87
Related to https://github.com/prisma/prisma/issues/3774#issuecomment-450287958
Will this issue be solved soon? If i understand, for the the moment we have to add a // @ts-ignore comment before the problematic lines?
Also looking forward for this fix. Any updates?
This issue has been automatically marked as stale because it has not had recent activity. It will be closed in 10 days if no further activity occurs. Thank you for your contributions.
I am going to close this in favour of #3774 as it has a more recent discussion about this and it covers more generic use case.
@steida Yes, you can use those. I am personally disabling strictNullChecks in my tsconfig while the typings get fixed
Most helpful comment
Effect of
?on object keysThere is a difference between e.g.
a: undefined | 1anda?: 1As you can see only the
a: undefined | 1variant guarantees thatakey is present. But from a consumer's point of view, it does not matter.nullvsundefinedin graphqlConsider this schema:
In the following queries, observe the resolver
argsvalues:Reconciling graphqlgen and prism
One thing I would suggest tweaking about the proposed fix is that graphql gen should be able to output either case stated above:
a?: null | string_or_a: null | string. As long as nullable prisma client args have typea?: null | stringthe types will work.