Describe the bug
Hi everyone,
This is my first time creating a public issue (and I'm new to TS and Graphql), so please forgive me in advance for any mistake.
I believe that this issue is very similar to https://github.com/dotansimha/graphql-code-generator/issues/1041. It looks like the mapper isn't always mapping types correctly in the current version. I'm creating a new issue as it was requested in https://github.com/dotansimha/graphql-code-generator/issues/1041.
Here is the case that we have seen recently.
type MovieInsights {
movieStarsPerMonth: [MonthlyCounts!]!
movieStarsCumulativePerDay: [MovieCounts!]!
}
type MonthlyCounts {
month: Date!
movieCounts: [MovieCounts!]!
}
type MovieCounts {
# The dynamically resolved Movie type
movie: Movie!
stars: Int!
period: Date!
}
The generated mappers:
Movie: MappedMovie
MonthlyCounts: MonthlyCounts
MovieCounts: Omit<MovieCounts, 'movie'> & {
movie: ResolversTypes['Movie']
}
:point_up: See that the mapper correctly omitted the movie from the mapping for MovieCounts as it has its own resolver. It doesn't do the same for MonthlyCounts that indirectly uses Movie - it does so through the MonthlyCounts. The generated typescript types:
export type MonthlyCounts = {
month: Scalars['Date']
movieCounts: Array<MovieCounts>
}
export type MovieCounts = {
movie: Movie
stars: Scalars['Int']
}
The problem comes when I'm implementing the resolver for movieStarsPerMonth (that uses MonthlyCounts). I keep getting the error: Type 'MappedMovie' is missing the following properties from type 'Movie': boxOffice, worldwideReleases, awards, and 15 more.ts(2322).
The implementation of each query:
// Works fine!
async movieStarsCumulativePerDay(
{ studioId },
_args,
{ services }
){
const starCounts = ... // get from service
return await Promise.all(
starCounts.map(async (dailyCounts) => {
// movieService returns a MappedMovie
const movie = await services.movieService.getMovie(s.movieId)
return {
movie,
period: dailyCounts.period,
stars: dailyCounts.users
}
})
)
}
// This gives me the error shown above: Type 'MappedMovie' is missing the following properties from type 'Movie': boxOffice, worldwideReleases, awards, and 15 more.ts(2322)
async movieStarsPerMonth(
{ studioId },
_args,
{ services }
) {
const yearlyStars = ... //get from a service
const counts = yearlyStars.map(
async (monthCounts) => {
return {
month: monthCounts.month,
movieCounts: await Promise.all(
monthCounts.movies.map(async (s: StarCounts) => {
// movieService returns a MappedMovie
const movie = await services.movieService.getMovie(s.movieId)
return {
movie,
period: s.period,
stars: s.stars
}
})
)
}
}
)
return await Promise.all(counts)
}
Expected behavior
The mappers correctly map the dynamic MappedMovie to Movie and the compiler is happy 💯 .
Environment:
Notably the same error exists after on @1.2.0 ☝️
@mshwery @solon-aguiar I think that I fixed that, maybe you can assist me with it?
I added this test before applying the last fix: https://github.com/dotansimha/graphql-code-generator/blob/master/packages/plugins/typescript/resolvers/tests/mapping.spec.ts#L58-L98 , but maybe it's missing something?
Thank you!
I think the missing piece is one layer of indirection:
type Movie {
id: ID!
title: String!
}
type Book {
id: ID!
author: String!
}
union MovieLike = Movie | Book
type NonInterfaceHasNarrative {
narrative: MovieLike!
movie: Movie!
}
# This one will have the issue – it resolves `movies: Array<NonInterfaceHasNarrative>` which is the fully resolved type from the graphql SDL, instead of applying `movie: ResolverTypes['Movie']`
type LayerOfIndirection {
id: ID!
movies: [NonInterfaceHasNarrative!]!
}
It seems strange that the SDL types themselves don't refer to ResolverTypes['MappedType']. Is there a reason for that?
@mshwery the basic types are generated by typescript and they are the base types, ResolversTypes just refers to it and replaces fields when needed.
I'll take a look soon and try to solve it. Thanks!
@mshwery thank you for the reproduction, and sorry for the delay. I managed to understand the issue, reproduce it and fix it :)
Now for a given schema:
type Movie {
id: ID!
title: String!
}
type Book {
id: ID!
author: String!
}
union MovieLike = Movie | Book
type NonInterfaceHasNarrative {
narrative: MovieLike!
movie: Movie!
}
type LayerOfIndirection {
id: ID!
movies: [NonInterfaceHasNarrative!]!
}
type AnotherLayerOfIndirection {
inner: LayerOfIndirection!
}
you will get:
export type ResolversTypes = {
String: Scalars['String'],
Boolean: Scalars['Boolean'],
Movie: MovieEntity,
ID: Scalars['ID'],
Book: Book,
MovieLike: ResolversTypes['Movie'] | ResolversTypes['Book'],
NonInterfaceHasNarrative: Omit<NonInterfaceHasNarrative, 'narrative' | 'movie'> & { narrative: ResolversTypes['MovieLike'], movie: ResolversTypes['Movie'] },
LayerOfIndirection: Omit<LayerOfIndirection, 'movies'> & { movies: Array<ResolversTypes['NonInterfaceHasNarrative']> },
AnotherLayerOfIndirection: Omit<AnotherLayerOfIndirection, 'inner'> & { inner: ResolversTypes['LayerOfIndirection'] },
};
Fix is in: https://github.com/dotansimha/graphql-code-generator/pull/2022 , can you please test it? it's available as alpha version (1.2.2-alpha-55c83745.27).
Fixed in 1.3.0 🎉
Most helpful comment
@mshwery thank you for the reproduction, and sorry for the delay. I managed to understand the issue, reproduce it and fix it :)
Now for a given schema:
you will get:
Fix is in: https://github.com/dotansimha/graphql-code-generator/pull/2022 , can you please test it? it's available as alpha version (
1.2.2-alpha-55c83745.27).