I'm following the Apollo Docs tutorial to build an Apollo Server (Express) with TypeScript and I'm also using GraphQL Code Generator to generate the necessary typings based on my GraphQL schema.
This is my current codegen.json configuration:
{
"schema": "./lib/schema/index.graphql",
"generates": {
"./dist/typings/graphql/schema.d.ts": {
"plugins": [
"typescript",
"typescript-resolvers"
],
"config": {
"typesPrefix": "GQL",
"skipTypename": true,
"noSchemaStitching": true,
"useIndexSignature": true
}
}
}
}
This is my current GraphQL schema based on the tutorial (it's not complete, I haven't finished the whole thing yet and I've trimmed a few things to make the example smaller):
type Query {
launch(id: ID!): Launch
}
type Launch {
id: ID!
site: String
mission: Mission
}
enum PatchSize {
SMALL
LARGE
}
type Mission {
name: String
missionPatch(mission: String, size: PatchSize): String
}
Which generates the following TypeScript typings:
import { GraphQLResolveInfo } from 'graphql';
export type Maybe<T> = T | null;
export type RequireFields<T, K extends keyof T> = { [X in Exclude<keyof T, K>]?: T[X] } & { [P in K]-?: NonNullable<T[P]> };
/** All built-in and custom scalars, mapped to their actual values */
export type Scalars = {
ID: string,
String: string,
Boolean: boolean,
Int: number,
Float: number,
};
export type GQLLaunch = {
id: Scalars['ID'],
site?: Maybe<Scalars['String']>,
mission?: Maybe<GQLMission>,
};
export type GQLMission = {
name?: Maybe<Scalars['String']>,
missionPatch?: Maybe<Scalars['String']>,
};
export type GQLMissionMissionPatchArgs = {
mission?: Maybe<Scalars['String']>,
size?: Maybe<GQLPatchSize>
};
export enum GQLPatchSize {
Small = 'SMALL',
Large = 'LARGE'
}
export type GQLQuery = {
launch?: Maybe<GQLLaunch>,
};
export type GQLQueryLaunchArgs = {
id: Scalars['ID']
};
export type WithIndex<TObject> = TObject & Record<string, any>;
export type ResolversObject<TObject> = WithIndex<TObject>;
export type ResolverTypeWrapper<T> = Promise<T> | T;
export type ResolverFn<TResult, TParent, TContext, TArgs> = (
parent: TParent,
args: TArgs,
context: TContext,
info: GraphQLResolveInfo
) => Promise<TResult> | TResult;
export type Resolver<TResult, TParent = {}, TContext = {}, TArgs = {}> = ResolverFn<TResult, TParent, TContext, TArgs>;
export type SubscriptionSubscribeFn<TResult, TParent, TContext, TArgs> = (
parent: TParent,
args: TArgs,
context: TContext,
info: GraphQLResolveInfo
) => AsyncIterator<TResult> | Promise<AsyncIterator<TResult>>;
export type SubscriptionResolveFn<TResult, TParent, TContext, TArgs> = (
parent: TParent,
args: TArgs,
context: TContext,
info: GraphQLResolveInfo
) => TResult | Promise<TResult>;
export interface SubscriptionSubscriberObject<TResult, TKey extends string, TParent, TContext, TArgs> {
subscribe: SubscriptionSubscribeFn<{ [key in TKey]: TResult }, TParent, TContext, TArgs>;
resolve?: SubscriptionResolveFn<TResult, { [key in TKey]: TResult }, TContext, TArgs>;
}
export interface SubscriptionResolverObject<TResult, TParent, TContext, TArgs> {
subscribe: SubscriptionSubscribeFn<any, TParent, TContext, TArgs>;
resolve: SubscriptionResolveFn<TResult, any, TContext, TArgs>;
}
export type SubscriptionObject<TResult, TKey extends string, TParent, TContext, TArgs> =
| SubscriptionSubscriberObject<TResult, TKey, TParent, TContext, TArgs>
| SubscriptionResolverObject<TResult, TParent, TContext, TArgs>;
export type SubscriptionResolver<TResult, TKey extends string, TParent = {}, TContext = {}, TArgs = {}> =
| ((...args: any[]) => SubscriptionObject<TResult, TKey, TParent, TContext, TArgs>)
| SubscriptionObject<TResult, TKey, TParent, TContext, TArgs>;
export type TypeResolveFn<TTypes, TParent = {}, TContext = {}> = (
parent: TParent,
context: TContext,
info: GraphQLResolveInfo
) => Maybe<TTypes>;
export type NextResolverFn<T> = () => Promise<T>;
export type DirectiveResolverFn<TResult = {}, TParent = {}, TContext = {}, TArgs = {}> = (
next: NextResolverFn<TResult>,
parent: TParent,
args: TArgs,
context: TContext,
info: GraphQLResolveInfo
) => TResult | Promise<TResult>;
/** Mapping between all available schema types and the resolvers types */
export type GQLResolversTypes = ResolversObject<{
Query: ResolverTypeWrapper<{}>,
ID: ResolverTypeWrapper<Scalars['ID']>,
Launch: ResolverTypeWrapper<GQLLaunch>,
String: ResolverTypeWrapper<Scalars['String']>,
Mission: ResolverTypeWrapper<GQLMission>,
PatchSize: GQLPatchSize,
Boolean: ResolverTypeWrapper<Scalars['Boolean']>,
}>;
/** Mapping between all available schema types and the resolvers parents */
export type GQLResolversParentTypes = ResolversObject<{
Query: {},
ID: Scalars['ID'],
Launch: GQLLaunch,
String: Scalars['String'],
Mission: GQLMission,
PatchSize: GQLPatchSize,
Boolean: Scalars['Boolean'],
}>;
export type GQLLaunchResolvers<ContextType = any, ParentType extends GQLResolversParentTypes['Launch'] = GQLResolversParentTypes['Launch']> = ResolversObject<{
id?: Resolver<GQLResolversTypes['ID'], ParentType, ContextType>,
site?: Resolver<Maybe<GQLResolversTypes['String']>, ParentType, ContextType>,
mission?: Resolver<Maybe<GQLResolversTypes['Mission']>, ParentType, ContextType>,
}>;
export type GQLMissionResolvers<ContextType = any, ParentType extends GQLResolversParentTypes['Mission'] = GQLResolversParentTypes['Mission']> = ResolversObject<{
name?: Resolver<Maybe<GQLResolversTypes['String']>, ParentType, ContextType>,
missionPatch?: Resolver<Maybe<GQLResolversTypes['String']>, ParentType, ContextType, GQLMissionMissionPatchArgs>,
}>;
export type GQLQueryResolvers<ContextType = any, ParentType extends GQLResolversParentTypes['Query'] = GQLResolversParentTypes['Query']> = ResolversObject<{
launch?: Resolver<Maybe<GQLResolversTypes['Launch']>, ParentType, ContextType, RequireFields<GQLQueryLaunchArgs, 'id'>>,
}>;
export type GQLResolvers<ContextType = any> = ResolversObject<{
Launch?: GQLLaunchResolvers<ContextType>,
Mission?: GQLMissionResolvers<ContextType>,
Query?: GQLQueryResolvers<ContextType>,
}>;
This is my resolvers.ts file:
import { GQLPatchSize } from '@typings/graphql/schema';
import { GQLResolvers } from '@typings/graphql/schema';
const resolvers: GQLResolvers = {
Query: {
launch: (_, args, { dataSources }) => {
return dataSources.launchesAPI.getLaunchById(args);
},
},
Mission: {
missionPatch: (mission, { size } = { size: GQLPatchSize.Large }) => {
return size === 'SMALL' ? mission.missionPatchSmall : mission.missionPatchLarge;
},
},
};
export { resolvers };
And to finish, my launches.ts file with the LaunchesAPI class:
import { GQLLaunch } from '@typings/graphql/schema';
import { GQLQueryLaunchArgs } from '@typings/graphql/schema';
import { RESTDataSource } from 'apollo-datasource-rest';
const SPACEX_API_ENDPOINT = 'https://api.spacexdata.com/v3/';
class LaunchesAPI extends RESTDataSource {
constructor() {
super();
this.baseURL = SPACEX_API_ENDPOINT;
}
async getLaunchById({ id }: GQLQueryLaunchArgs) {
const response = await this.get('launches', { flight_number: id });
return this.launchReducer(response[0]);
}
launchReducer(launch: any): GQLLaunch {
return {
id: String(launch.flight_number) || '0',
site: launch.launch_site && launch.launch_site.site_name,
mission: {
name: launch.mission_name,
missionPatchSmall: launch.links.mission_patch_small,
missionPatchLarge: launch.links.mission_patch,
},
};
}
}
export { LaunchesAPI };
Now, because I'm typing the result of launchReducer() with GQLLaunch, the mission property type is GQLMission and this type only has two properties, name and missionPatch. It does not have missionPatchSmall or missionPatchLarge and thus I get this error:
Type '{ name: any; missionPatchSmall: any; missionPatchLarge: any; }' is not assignable to type 'GQLMission'. Object literal may only specify known properties, and 'missionPatchSmall' does not exist in type 'GQLMission'. ts(2339)
A similar error exists in the resolvers.ts file when it tries to read mission.missionPatchSmall or mission.missionPatchLarge as they don't exist in the mission object of type GQLMission:
Property 'missionPatchSmall' does not exist on type 'GQLMission'. ts(2339)
I'm not sure how to handle this, suggestions?