graphql-code-generator: Generated typescript recursive query types are wrong
Describe the bug
Because codegen relies heavily on Intersection types, typescript does not correctly check query fields which are optional, and allows nulls to go unchecked in code.
An example query:
type Query {
categories(id: ID!): [Category!]!
}
type Category {
id: ID!
name: String!
children: [Category!]
}
query GetCategories {
categories(id: 1) {
id
name
children {
id
name
children {
id
name
}
}
}
}
To Reproduce
Use the code sandbox:
https://codesandbox.io/s/graphql-codegen-issue-template-563vy
yarn generate will generate the query types for the nested query on Category
test.ts shows how these types provide no protection for null fields in typescript.
We generate some query response data which is a valid structure for the GetCategoriesQuery and then use that data in a function which uses the Category type as if it’s children are not null (should throw an error)
yarn build will show how no type errors are generated from the types.
yarn test will show how a runtime error exists when the .length property is queried on the children field.
Expected result
GetCategoriesQuery should not be compatible with Category as children is not expected to be a null type in Category.
Environment:
- OS: MacOS
@graphql-codegen/...@1.7.0:- NodeJS: 10
Additional context
Typescript has issues with intersections being tracked https://github.com/microsoft/TypeScript/issues/33243. Using them for generating these types seems to be the issue here, as manually generating the type without intersections correctly picks up the errors.
About this issue
- Original URL
- State: closed
- Created 5 years ago
- Comments: 26 (2 by maintainers)
@freshollie Maybe I’m missing something, not sure.
In your use case, you are using:
Which means that
childrenfield could benull, but the array values must be set. If you wish to have a nullable type, you need to usechildren: [Category!]!.With your schema, it seems to work for me in TS Playground:
And if I’m changing it to force a value there and make the field as non-nullable, it allow me to access it directly:
You shouldn’t use
Categorytype (generated bytypescript) directly - it’s a base type that contains all fields of the type, and then queries types can justPickfrom it. GraphQL let you choose the fields and it means that always there is a base type and a selection set based on that type.If you wish to have a dedicated type for
Categoryof your query, use GraphQL fragments and then you’ll get a type per each fragment type and it will represent the actual representation ofCategorybased on your fragment selection set.@freshollie Thanks, closing for now. The current default is using
Pickbut I think maybe we can change it in a future version, we just need to make sure preResolveTypes is stable and has no issues.@dotansimha @n1ru4l any conclusions on this? To summaries:
preResolveTypes: truefixes our issue, so I am fine to close.Other people may encounter this and
preResolveTypes: trueis not enabled by default, so documenting this behaviour might be the solution?