mercurius-js / mercurius-typescript

TypeScript usage examples and "mercurius-codegen" for Mercurius

Home Page:https://mercurius.dev

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

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

  1. Query/Mutation key of the resolver as standalone modules (Scalars seem to work fine...)
  2. 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 :)