mercurius-js / mercurius-typescript

TypeScript usage examples and "mercurius-codegen" for Mercurius
https://mercurius.dev
MIT License
71 stars 18 forks source link

Split code across different files #276

Open artecoop opened 2 years ago

artecoop commented 2 years ago

Hello Everyone, I have a pretty large project that I'm trying to move to graphql using fastify, mercurius, mercurius-codegen and mercurius-integration-testing

I've split my business logic into modules, so the code is structured like this

src/
├─ modules/
│  ├─ foo/
│  │  ├─ schema.ts
│  │  ├─ resolvers.ts
│  ├─ bar/
│  │  ├─ schema.ts
│  │  ├─ resolvers.ts
...
├─ base.ts
├─ application.ts
├─ server.ts

base.ts contains the root query and mutation, in order to let other schema.ts files to extend

import { gql } from 'mercurius-codegen';

export default gql`
    type Query
    type Mutation
`;

A schema file contains something like this

import { gql } from 'mercurius-codegen';

export default gql`
    extend type Query {
        list: [String!]! 
    }

    extend type Mutation {
        manage(input: String!)
        delete(id: String!)
    }
`;

Here's a resolver:

import { IResolvers } from 'mercurius';

const resolvers: IResolvers = {
    Query: {
        list: async () => await getAll()
    },
    Mutation: {
        manage: async (_root, { input }) => await manage(input),
        delete: async (_root, { id }) => await deleteString(id)
    }
};

export default resolvers;

In order to avoid bloating the server.ts file, I've created an application.ts to import all the schemas and resolvers

import foo from './modules/foo/schema';
import bar from './modules/bar/schema';

export const typeDefs = [foo, bar] ;

import fooResolver from './modules/foo/resolvers';
import barResolver from './modules/bar/resolvers';

export const resolvers = { ...fooResolvers, ...barResolvers };

The problem arises when I need to set up mercurius:

import { resolvers, typeDefs } from './application';

export const app = fastify();

app.register(mercurius, {
    path: '/',
    schema: typeDefs,
    resolvers: resolvers,
    graphiql: process.env.NODE_ENV !== 'production' && 'graphiql'
});

With resolvers merged with the spread operator, all are overwritten by the last one.

Using mergeResolvers from graphql-tools create a different problem

import { mergeResolvers } from '@graphql-tools/merge';

export const resolvers = mergeResolvers([
    fooResolvers,
    barResolvers
]);

give me this error

Argument of type 'IResolvers<any, MercuriusContext>[]' is not assignable to parameter of type 'IResolvers<any, MercuriusContext, Record<string, any>, any> | Maybe<IResolvers<any, MercuriusContext, Record<string, any>, any>>[] | null | undefined'.
  Type 'IResolvers<any, MercuriusContext>[]' is not assignable to type 'Maybe<IResolvers<any, MercuriusContext, Record<string, any>, any>>[]'.
    Type 'IResolvers<any, MercuriusContext>' is not assignable to type 'Maybe<IResolvers<any, MercuriusContext, Record<string, any>, any>>'.
      'string' index signatures are incompatible.
        Type 'GraphQLScalarType<unknown, unknown> | IEnumResolver | (() => any) | IResolverObject<any, MercuriusContext, any> | IResolverOptions<...> | undefined' is not assignable to type 'IUnionTypeResolver | IScalarTypeResolver | IEnumTypeResolver | IInputObjectTypeResolver | ISchemaLevelResolver<...> | IObjectTypeResolver<...> | IInterfaceTypeResolver<...>'.
          Type 'undefined' is not assignable to type 'IUnionTypeResolver | IScalarTypeResolver | IEnumTypeResolver | IInputObjectTypeResolver | ISchemaLevelResolver<...> | IObjectTypeResolver<...> | IInterfaceTypeResolver<...>'.ts(2345)

Any help?

mplibunao commented 2 years ago

What does fooResolver and barResolver look like?

I'm not sure but my understanding from your example is that it looks like this

import { IResolvers } from 'mercurius';

const resolvers: IResolvers = {
    Query: {
        list: async () => await getAll()
    },
    Mutation: {
        manage: async (_root, { input }) => await manage(input),
        delete: async (_root, { id }) => await deleteString(id)
    }
};

export default resolvers;

Then you're merging them like this

import fooResolver from './modules/foo/resolvers';
import barResolver from './modules/bar/resolvers';

export const resolvers = { ...fooResolvers, ...barResolvers };

which causes Query and Mutation objects to keep getting overridden

You probably want to compose it in a different way like

import { fooQueries, fooMutations } from 'modules/foo'
import { barQueries, barMutation } from 'modules/bar'

const resolvers = {
  Query: {...fooQueries, ...barQueries},
  Mutations: {...fooMutations, ...barMutations}
}
artecoop commented 2 years ago

I understand your confusion, because of the naming clash, but when I'm importing, resolvers refers to the file name as stated on the structure of the project. I'm not referencing the const resolvers: IResolvers ... (which isn't exported btw).