In an effort to teach myself Apollo Server I was implementing the schema described here. CodeGen generated what look like very sensible definitions for the types.
export type Book = {
__typename?: 'Book';
author: Author;
title: Scalars['String'];
};
export type Library = {
__typename?: 'Library';
books?: Maybe<Array<Book>>;
branch: Scalars['String'];
};
The problem was the (Javascript) given in the example, equally sensibly, gave data for books and libraries as
{
branch: "riverside",
}
and
{
title: "The Awakening",
author: "Kate Chopin",
branch: "riverside",
}
That is, in the Javascript, each child had the ID of its parent, but in the GraphQL, each parent had an array of its actual children.
When I applied the Resolvers
type, of course, the compiler complained. Where is the child array? it asked plaintively.
You are supposed to figure that out at run-time, if you are even asked. Stupid computer.
Both Apollo and GraphQL CodeGen are very professional products. They would not have omitted something this basic. What am I missing?
(Incidentally, I think, but am really not sure, that this question springs from the same issue.)
I got the answer, surprisingly enough, from a YouTube video — and the very end of the video (starting at 5:38), almost a throwaway.
In the codegen.yml
, you have to explain to the generator what the types are of the default objects that the resolvers will return. In the example, I added to types to the Typescript:
export type LibraryModel = typeof libraries[number];
export type BookModel = typeof books[number];
And then to the codegen.yml
told the generator to use them:
config:
mappers:
Library: ../service/libraries#LibraryModel
Book: ../service/libraries#BookModel
(Note: the path is relative to the location of the generated file.)
This is great in that I intend to use an ORM that generates types from the database, and the names of the scalars mostly match up, so this will save me a lot of work.
It is less good in that it breaks a lot of type-safety. Any given field might be given by the default resolver or it might be given by its own resolver, but it is given by neither, the type system does not catch it and it’s a runtime error. Boo!
Edit: two years on and I needed this answer (which I had forgotten all about), and I wanted to add something. If you don’t have a pre-existing type with the right fields, you might be better off defining one like this:
type ScalarBook = Omit<Book, 'author'>;
type ScalarLibrary = Omit<Library, 'books'>;
But really, the codegen people should fix this.