Splitting up resolver code and retain typing
n8jadams opened this issue · comments
I'm trying to migrate a project from fastify-gql
to mercurius, and we had our resolver code split into a few files. Also, one nuance is that I want to build the graphql types file first, and then use them in my code, outside of what's done automatically.
Here is a codesandbox with a pared down version of my codebase
I recommend downloading that codesandbox as a zip, extracting it, installing it (with yarn) and then running
$ yarn start
I get errors like this:
src/resolvers.ts:6:14 - error TS2322: Type '{ Mutation: { setEntity: (_: any, { entity }: { entity: any; }, { user, reply }: { user: any; reply: any; }) => Promise<Entity>; }; Entity?: EntityResolvers<MercuriusContext, Entity> | undefined; Query?: QueryResolvers<...> | undefined; Date: GraphQLScalarType; }' is not assignable to type 'IResolvers<any, MercuriusContext>'.
The types of 'Mutation.setEntity' are incompatible between these types.
Type '(_: any, { entity }: { entity: any; }, { user, reply }: { user: any; reply: any; }) => Promise<Entity>' is not assignable to type 'Resolver<ResolverTypeWrapper<Entity>[], {}, MercuriusContext, RequireFields<MutationsetEntityArgs, never>> | undefined'.
Type '(_: any, { entity }: { entity: any; }, { user, reply }: { user: any; reply: any; }) => Promise<Entity>' is not assignable to type 'ResolverFn<ResolverTypeWrapper<Entity>[], {}, MercuriusContext, RequireFields<MutationsetEntityArgs, never>>'.
Types of parameters '__1' and 'args' are incompatible.
Type 'RequireFields<MutationsetEntityArgs, never>' is not assignable to type '{ entity: any; }'.
Property 'entity' is optional in type 'RequireFields<MutationsetEntityArgs, never>' but required in type '{ entity: any; }'.
Can you help me get the typing correct? Or show me how to type
- Query/Mutation key of the resolver as standalone modules (Scalars seem to work fine...)
- Both async and sync resolver functions underneath the Query/Mutation keys as modules. (In the codesandbox, the
setEntity
for example). Please include the function argument types, and return type.
Thanks!
EDIT: Here is my fully working codesandbox, for anybody reading after the fact: https://codesandbox.io/s/serene-mendeleev-rkhb9
Mostly to answer my own question:
import { IResolvers } from 'mercurius'
import { MutationResolvers, Entity } from './generated-gql-types'
const queries: IResolvers = {
Query: {
Entity: async (_, { entityId }, { user, reply }) => {
// Entity code
}
}
};
const setEntity: MutationResolvers["setEntity"] = async function setEntity(
_,
{ entity },
{ user, reply }
): Promise<Entity> {
// setEntity code
};
const mutations: IResolvers = {
Mutation: {
setEntity
}
};
export const resolvers: IResolvers = {
...queries,
...mutations
};
https://codesandbox.io/s/great-stonebraker-s9898?file=/src/build.ts
Here I fixed the majority of the types issues I found on the codesandbox 👍
Thank you for the help so far! Sorry I'm having a heck of a time with this all.
I've got another issue that is sort of related to this same issue, mostly around context
graphql-context-type.ts
import { FastifyRequest } from 'fastify'
interface AuthenticatedRequest extends FastifyRequest {
user: { id: string }
body: any
params: any
}
export async function buildContext(request: AuthenticatedRequest, reply) {
return {
user: request.user,
reply
}
}
type PromiseType<T> = T extends PromiseLike<infer U> ? U : T
export interface GraphQLContextType extends PromiseType<ReturnType<typeof buildContext>> {}
declare module 'mercurius' {
/*
Duplicate identifier 'MercuriusContext'.ts(2300)
index.d.ts(27, 18): 'MercuriusContext' was also declared here.
*/
type MercuriusContext = GraphQLContextType
}
In my project, Typescript really doesn't like that duplicate identifier, and then I think because of that, when I define a resolver like this:
query-resolvers.ts
import { IResolvers } from 'mercurius'
import { GraphQLContextType } from './graphql-context-type'
export const queries: IResolvers = {
Query: {
Blog: async (
_,
{ id },
{ user, reply }: GraphQLContextType
): Promise<Blog> => {
// blog code
},
},
}
gives me this error:
Type '(_: {}, { expertId }: RequireFields<QueryexpertArgs, never>, { user, reply }: GraphQLContextType) => Promise<Expert>' is not assignable to type 'Resolver<ResolverTypeWrapper<Expert>, {}, MercuriusContext, RequireFields<QueryexpertArgs, never>>'.
Type '(_: {}, { expertId }: RequireFields<QueryexpertArgs, never>, { user, reply }: GraphQLContextType) => Promise<Expert>' is not assignable to type 'ResolverFn<ResolverTypeWrapper<Expert>, {}, MercuriusContext, RequireFields<QueryexpertArgs, never>>'.
Types of parameters '__2' and 'context' are incompatible.
Property 'user' is missing in type 'MercuriusContext' but required in type 'GraphQLContextType'. ts(2322)
graphql-context-type.ts(6, 3): 'user' is declared here.
generated-graphql-types.ts(1345, 2): The expected type comes from property 'Blog' which is declared here on type 'QueryResolvers<MercuriusContext, {}>'
Any suggestions?
As already shown here for extending the Mercurius context: https://github.com/mercurius-js/mercurius-typescript/blob/master/examples/manual/src/index.ts
and Fastify type module augmention an example is here: https://www.fastify.io/docs/latest/TypeScript/#request
the syntax would be something like this:
import { FastifyRequest, FastifyReply } from 'fastify'
declare module "fastify" {
interface FastifyRequest {
user: { id: string }
}
}
export async function buildContext(request: FastifyRequest, reply: FastifyReply) {
return {
user: request.user,
}
}
type PromiseType<T> = T extends PromiseLike<infer U> ? U : T
declare module 'mercurius' {
interface MercuriusContext
extends PromiseType<ReturnType<typeof buildContext>> {}
}
Turns out my problem was that my linter was changing this:
declare module 'mercurius' {
interface MercuriusContext extends GraphQLContextType {}
}
to this:
declare module 'mercurius' {
type MercuriusContext = GraphQLContextType
}
It needs to be the interface. Thanks :)