Repository: mcollina/fastify-gql
Pull request #210 (2020-07-11 09:07:38)

Use onReady for schema validation


Patch:
diff --git a/index.d.ts b/index.d.ts
index 622e298..b85c1dc 100644
--- a/index.d.ts
+++ b/index.d.ts
@@ -1,228 +1,264 @@
-import { FastifyError, FastifyReply, FastifyRequest, FastifyInstance, RawServerBase, RawRequestDefaultExpression, RawReplyDefaultExpression, RegisterOptions  } from "fastify";
-import { DocumentNode, ExecutionResult, GraphQLSchema, Source, GraphQLResolveInfo, GraphQLIsTypeOfFn, GraphQLTypeResolver, GraphQLScalarType, ValidationRule  } from 'graphql';
+import {
+  FastifyError,
+  FastifyReply,
+  FastifyRequest,
+  FastifyInstance,
+  RawServerBase,
+  RawRequestDefaultExpression,
+  RawReplyDefaultExpression,
+  FastifyPlugin,
+} from "fastify";
+import {
+  DocumentNode,
+  ExecutionResult,
+  GraphQLSchema,
+  Source,
+  GraphQLResolveInfo,
+  GraphQLIsTypeOfFn,
+  GraphQLTypeResolver,
+  GraphQLScalarType,
+  ValidationRule,
+} from "graphql";
 
-declare namespace fastifyGQL {
+declare interface FastifyGQLPlugin {
+  /**
+   * Replace existing schema
+   * @param schema graphql schema
+   */
+  replaceSchema(schema: GraphQLSchema): void;
+  /**
+   * Extend existing schema
+   * @param schema graphql schema
+   */
+  extendSchema(schema: string | Source | DocumentNode): void;
+  /**
+   * Define additional resolvers
+   * @param resolvers object with resolver functions
+   */
+  defineResolvers(resolvers: IResolvers): void;
+  /**
+   * Define data loaders
+   * @param loaders object with data loader functions
+   */
+  defineLoaders(loaders: {
+    [key: string]: {
+      [key: string]: (
+        queries: Array<{
+          obj: any;
+          params: any;
+        }>,
+        context: {
+          reply: FastifyReply;
+        }
+      ) => any;
+    };
+  }): void;
+  /**
+   * Managed GraphQL schema object for doing custom execution with. Will reflect changes made via `extendSchema`, `defineResolvers`, etc.
+   */
+  schema: GraphQLSchema;
+}
 
-  export interface Plugin {
-    /**
-     * Replace existing schema
-     * @param schema graphql schema
-     */
-    replaceSchema(schema: GraphQLSchema): void;
-    /**
-     * Extend existing schema
-     * @param schema graphql schema
-     */
-    extendSchema(schema: string | Source | DocumentNode): void;
-    /**
-     * Define additional resolvers
-     * @param resolvers object with resolver functions
-     */
-    defineResolvers(resolvers: IResolvers): void;
-    /**
-     * Define data loaders
-     * @param loaders object with data loader functions
-     */
-    defineLoaders(loaders: {
-      [key: string]: {
-        [key: string]: (
-          queries: Array<{
-            obj: any,
-            params: any
-          }>,
-          context: {
-            reply: FastifyReply
-          }) => any
-      }
-    }): void;
-    /**
-     * Managed GraphQL schema object for doing custom execution with. Will reflect changes made via `extendSchema`, `defineResolvers`, etc.
-     */
-    schema: GraphQLSchema;
-  }
+interface QueryRequest {
+  operationName?: string;
+  query: string;
+  variables?: object;
+  extensions?: object;
+}
 
-  export interface QueryRequest {
-      operationName?: string
-      query: string
-      variables?: object
-      extensions?: object
-  }
+export interface FastifyGQLOptions {
+  /**
+   * The GraphQL schema. String schema will be parsed
+   */
+  schema: GraphQLSchema | string;
+  /**
+   * Object with resolver functions
+   */
+  resolvers: IResolvers;
+  /**
+   * Object with data loader functions
+   */
+  loaders?: {
+    [key: string]: {
+      [key: string]: (
+        queries: Array<{
+          obj: any;
+          params: any;
+        }>,
+        context: {
+          reply: FastifyReply;
+        }
+      ) => any;
+    };
+  };
+  /**
+   * Serve GraphiQL on /graphiql if true or 'graphiql', or GraphQL IDE on /playground if 'playground' and if routes is true
+   */
+  graphiql?: boolean | string;
+  ide?: boolean | string;
+  /**
+   * The minimum number of execution a query needs to be executed before being jit'ed.
+   * @default true
+   */
+  jit?: number;
+  /**
+   * A graphql endpoint is exposed at /graphql when true
+   * @default true
+   */
+  routes?: boolean;
+  /**
+   * An endpoint for graphql if routes is true
+   * @default '/graphql'
+   */
+  path?: string;
+  /**
+   * Change the route prefix of the graphql endpoint if set
+   */
+  prefix?: string;
+  /**
+   * Add the empty Mutation definition if schema is not defined
+   * @default false
+   */
+  defineMutation?: boolean;
+  /**
+   * Change the default error handler (Default: true).
+   * If a custom error handler is defined, it should return the standardized response format according to [GraphQL spec](https://graphql.org/learn/serving-over-http/#response).
+   * @default true
+   */
+  errorHandler?:
+    | boolean
+    | ((
+        error: FastifyError,
+        request: FastifyRequest,
+        reply: FastifyReply
+      ) => ExecutionResult);
+  /**
+   * The maximum depth allowed for a single query.
+   */
+  queryDepth?: number;
+  context?: (request: FastifyRequest, reply: FastifyReply) => Promise<any>;
+  /**
+   * Optional additional validation rules.
+   * Queries must satisfy these rules in addition to those defined by the GraphQL specification.
+   */
+  validationRules?: ValidationRules;
+  /**
+   * Enable subscription support when options are provided. [`emitter`](https://github.com/mcollina/mqemitter) property is required when subscriptions is an object. (Default false)
+   */
+  subscription?:
+    | boolean
+    | {
+        emitter?: object;
+        verifyClient?: (
+          info: object,
+          next: (result: boolean) => void
+        ) => void;
+      };
+  /**
+   * Enable federation metadata support so the service can be deployed behind an Apollo Gateway
+   */
+  federationMetadata?: boolean;
+  /**
+   * A list of GraphQL services to be combined into the gateway schema
+   */
+  gateway?: {
+    services: Array<{
+      name: string;
+      url: string;
+    }>;
+  };
+  /**
+   * Persisted queries, overrides persistedQueryProvider.
+   */
+  persistedQueries?: object;
+  /**
+   * Only allow persisted queries. Required persistedQueries, overrides persistedQueryProvider.
+   */
+  onlyPersisted?: boolean;
+  /**
+   * Settings for enabling persisted queries.
+   */
+  persistedQueryProvider?: fastifyGQL.PeristedQueryProvider;
 
-  export interface PeristedQueryProvider {
-      /**
-       *  Return true if a given request matches the desired persisted query format.
-       */
-      isPersistedQuery: (r: QueryRequest) => boolean
-      /**
-       *  Return the hash from a given request, or falsy if this request format is not supported.
-       */
-      getHash: (r: QueryRequest) => string
-      /**
-       *  Return the query for a given hash.
-       */
-      getQueryFromHash: (hash: string) => Promise<string>
-      /**
-       *  Return the hash for a given query string. Do not provide if you want to skip saving new queries.
-       */
-      getHashForQuery?: (query: string) => string
-      /**
-       *  Save a query, given its hash.
-       */
-      saveQuery?: (hash: string, query: string) => Promise<void>
-      /**
-       * An error message to return when getQueryFromHash returns a falsy result. Defaults to 'Bad Request'.
-       */
-      notFoundError?: string
-      /**
-       * An error message to return when a query matches isPersistedQuery, but fasly from getHash. Defaults to 'Bad Request'.
-       */
-      notSupportedError?: string
-  }
+  /**
+   * Enable support for batched queries (POST requests only).
+   * Batched query support allows clients to send an array of queries and
+   * receive an array of responses within a single request.
+   */
+  allowBatchedQueries?: boolean;
+}
 
-  export interface Options {
-    /**
-     * The GraphQL schema. String schema will be parsed
-     */
-    schema: GraphQLSchema | string,
-    /**
-     * Object with resolver functions
-     */
-    resolvers: IResolvers,
-    /**
-     * Object with data loader functions
-     */
-    loaders?: {
-      [key: string]: {
-        [key: string]: (
-          queries: Array<{
-            obj: any,
-            params: any
-          }>,
-          context: {
-            reply: FastifyReply
-          }) => any
-      }
-    },
-    /**
-     * Serve GraphiQL on /graphiql if true or 'graphiql', or GraphQL IDE on /playground if 'playground' and if routes is true
-     */
-		graphiql?: boolean | string,
-		ide?: boolean | string,
-    /**
-     * The minimum number of execution a query needs to be executed before being jit'ed.
-     * @default true
-     */
-    jit?: number,
-    /**
-     * A graphql endpoint is exposed at /graphql when true
-     * @default true
-     */
-    routes?: boolean,
-    /**
-     * An endpoint for graphql if routes is true
-     * @default '/graphql'
-     */
-    path?: string,
-    /**
-     * Change the route prefix of the graphql endpoint if set
-     */
-    prefix?: string,
-    /**
-     * Add the empty Mutation definition if schema is not defined
-     * @default false
-     */
-    defineMutation?: boolean,
-    /**
-     * Change the default error handler (Default: true).
-     * If a custom error handler is defined, it should return the standardized response format according to [GraphQL spec](https://graphql.org/learn/serving-over-http/#response).
-     * @default true
-     */
-    errorHandler?: boolean | ((
-      error: FastifyError,
-      request: FastifyRequest,
-      reply: FastifyReply
-    ) => ExecutionResult),
-    /**
-     * The maximum depth allowed for a single query.
-     */
-    queryDepth?: number,
-    context?: (request: FastifyRequest, reply: FastifyReply) => Promise<any>,
-    /**
-     * Optional additional validation rules.
-     * Queries must satisfy these rules in addition to those defined by the GraphQL specification.
-     */
-    validationRules?: ValidationRules,
+
+declare function fastifyGQL 
+  (
+    instance: FastifyInstance,
+    opts: FastifyGQLOptions
+  ): void;
+
+
+declare namespace fastifyGQL {
+  interface PeristedQueryProvider {
     /**
-     * Enable subscription support when options are provided. [`emitter`](https://github.com/mcollina/mqemitter) property is required when subscriptions is an object. (Default false)
+     *  Return true if a given request matches the desired persisted query format.
      */
-    subscription?: boolean | {
-      emitter?: object,
-      verifyClient?: (
-        info: object,
-        next: (result: boolean) => void
-      ) => void
-    },
+    isPersistedQuery: (r: QueryRequest) => boolean;
     /**
-     * Enable federation metadata support so the service can be deployed behind an Apollo Gateway
+     *  Return the hash from a given request, or falsy if this request format is not supported.
      */
-    federationMetadata?: boolean
+    getHash: (r: QueryRequest) => string;
     /**
-     * A list of GraphQL services to be combined into the gateway schema
+     *  Return the query for a given hash.
      */
-    gateway?: {
-      services: Array<{
-        name: string
-        url: string
-      }>
-    }
+    getQueryFromHash: (hash: string) => Promise<string>;
     /**
-     * Persisted queries, overrides persistedQueryProvider.
+     *  Return the hash for a given query string. Do not provide if you want to skip saving new queries.
      */
-    persistedQueries?: object
+    getHashForQuery?: (query: string) => string;
     /**
-     * Only allow persisted queries. Required persistedQueries, overrides persistedQueryProvider.
+     *  Save a query, given its hash.
      */
-    onlyPersisted?: boolean
+    saveQuery?: (hash: string, query: string) => Promise<void>;
     /**
-     * Settings for enabling persisted queries.
+     * An error message to return when getQueryFromHash returns a falsy result. Defaults to 'Bad Request'.
      */
-    persistedQueryProvider?: PeristedQueryProvider
-    
+    notFoundError?: string;
     /**
-     * Enable support for batched queries (POST requests only).
-     * Batched query support allows clients to send an array of queries and
-     * receive an array of responses within a single request.
+     * An error message to return when a query matches isPersistedQuery, but fasly from getHash. Defaults to 'Bad Request'.
      */
-    allowBatchedQueries?: boolean
+    notSupportedError?: string;
   }
 
+ 
+
   /**
    * Extended errors for adding additional information in error responses
    */
-  export class ErrorWithProps extends Error {
-    constructor (message: string, extensions?: object)
+  class ErrorWithProps extends Error {
+    constructor(message: string, extensions?: object);
     /**
      * Custom additional properties of this error
      */
-    extensions?: object
+    extensions?: object;
   }
 
   /**
    * Default options for persisted queries.
    */
-  export const persistedQueryDefaults: { 
-    prepared: (persistedQueries: object) => PeristedQueryProvider
-    preparedOnly: (persistedQueries: object) => PeristedQueryProvider
-    automatic: (maxSize?: number) => PeristedQueryProvider
-   };
+  const persistedQueryDefaults: {
+    prepared: (persistedQueries: object) => PeristedQueryProvider;
+    preparedOnly: (persistedQueries: object) => PeristedQueryProvider;
+    automatic: (maxSize?: number) => PeristedQueryProvider;
+  };
 }
 
+export default fastifyGQL;
+
 declare module "fastify" {
   interface FastifyInstance {
     /**
      * GraphQL plugin
      */
-    graphql: fastifyGQL.Plugin;
+    graphql: FastifyGQLPlugin;
   }
 
   interface FastifyReply {
@@ -241,19 +277,20 @@ declare module "fastify" {
   }
 }
 
-declare function fastifyGQL(
-  instance: FastifyInstance<RawServerBase, RawRequestDefaultExpression<RawServerBase>, RawReplyDefaultExpression<RawServerBase>>,
-  opts: fastifyGQL.Options): void
-
-
-export = fastifyGQL;
-
 interface IResolvers<TSource = any, TContext = any> {
-  [key: string]: (() => any) | IResolverObject<TSource, TContext> | IResolverOptions<TSource, TContext> | GraphQLScalarType | IEnumResolver;
+  [key: string]:
+    | (() => any)
+    | IResolverObject<TSource, TContext>
+    | IResolverOptions<TSource, TContext>
+    | GraphQLScalarType
+    | IEnumResolver;
 }
 
 type IResolverObject<TSource = any, TContext = any, TArgs = any> = {
-  [key: string]: IFieldResolver<TSource, TContext, TArgs> | IResolverOptions<TSource, TContext> | IResolverObject<TSource, TContext>;
+  [key: string]:
+    | IFieldResolver<TSource, TContext, TArgs>
+    | IResolverOptions<TSource, TContext>
+    | IResolverObject<TSource, TContext>;
 };
 
 interface IResolverOptions<TSource = any, TContext = any, TArgs = any> {
@@ -268,20 +305,32 @@ type IEnumResolver = {
   [key: string]: string | number;
 };
 
-type IFieldResolver<TSource, TContext, TArgs = Record<string, any>> = (source: TSource, args: TArgs, context: TContext, info: GraphQLResolveInfo & {
-  mergeInfo: MergeInfo;
-}) => any;
+type IFieldResolver<TSource, TContext, TArgs = Record<string, any>> = (
+  source: TSource,
+  args: TArgs,
+  context: TContext,
+  info: GraphQLResolveInfo & {
+    mergeInfo: MergeInfo;
+  }
+) => any;
 
 type MergeInfo = {
-  delegate: (type: 'query' | 'mutation' | 'subscription', fieldName: string, args: {
+  delegate: (
+    type: "query" | "mutation" | "subscription",
+    fieldName: string,
+    args: {
       [key: string]: any;
-  }, context: {
+    },
+    context: {
       [key: string]: any;
-  }, info: GraphQLResolveInfo, transforms?: Array<Transform>) => any;
+    },
+    info: GraphQLResolveInfo,
+    transforms?: Array<Transform>
+  ) => any;
   delegateToSchema<TContext>(options: IDelegateToSchemaOptions<TContext>): any;
   fragments: Array<{
-      field: string;
-      fragment: string;
+    field: string;
+    fragment: string;
   }>;
 };
 
@@ -291,14 +340,16 @@ type Transform = {
   transformResult?: (result: Result) => Result;
 };
 
-interface IDelegateToSchemaOptions<TContext = {
-  [key: string]: any;
-}> {
+interface IDelegateToSchemaOptions<
+  TContext = {
+    [key: string]: any;
+  }
+> {
   schema: GraphQLSchema;
   operation: Operation;
   fieldName: string;
   args?: {
-      [key: string]: any;
+    [key: string]: any;
   };
   context: TContext;
   info: IGraphQLToolsResolveInfo;
@@ -306,7 +357,7 @@ interface IDelegateToSchemaOptions<TContext = {
   skipValidation?: boolean;
 }
 
-type Operation = 'query' | 'mutation' | 'subscription';
+type Operation = "query" | "mutation" | "subscription";
 
 type Result = ExecutionResult & {
   extensions?: Record<string, any>;
@@ -322,4 +373,10 @@ type Request = {
   extensions?: Record<string, any>;
 };
 
-type ValidationRules = ValidationRule[] | ((params: { source: string, variables?: Record<string, any>, operationName?: string }) => ValidationRule[])
+type ValidationRules =
+  | ValidationRule[]
+  | ((params: {
+      source: string;
+      variables?: Record<string, any>;
+      operationName?: string;
+    }) => ValidationRule[]);
diff --git a/index.js b/index.js
index 236b0ae..fc7d5cf 100644
--- a/index.js
+++ b/index.js
@@ -154,7 +154,7 @@ const plugin = fp(async function (app, opts) {
 
   fastifyGraphQl.schema = schema
 
-  app.ready(async function () {
+  app.addHook('onReady', async function () {
     const schemaValidationErrors = validateSchema(fastifyGraphQl.schema)
     if (schemaValidationErrors.length > 0) {
       const err = new Error('schema issues')
diff --git a/lib/routes.js b/lib/routes.js
index af8d987..167c242 100644
--- a/lib/routes.js
+++ b/lib/routes.js
@@ -101,7 +101,15 @@ async function defaultErrorHandler (err, request, reply) {
   if (err.data) {
     reply.code(200)
   } else {
-    reply.code(err.statusCode || 500)
+    /* istanbul ignore if */
+    if (!err.statusCode) {
+      // The errors that lands here should have the statusCode set.
+      // If they don't, we have a bug to fix.
+      reply.log.trace({ err }, 'error without status code, report this as a bug')
+      reply.code(500)
+    } else {
+      reply.code(err.statusCode)
+    }
   }
 
   if (err.errors) {
@@ -274,13 +282,8 @@ module.exports = async function (app, opts) {
     app.route(getOptions)
   }
 
-  app.addContentTypeParser('application/graphql', function (req, done) {
-    req.setEncoding('utf8')
-    var data = ''
-    req.on('data', chunk => { data += chunk })
-    req.on('end', _ => {
-      done(null, { query: data })
-    })
+  app.addContentTypeParser('application/graphql', { parseAs: 'string' }, function (req, payload, done) {
+    done(null, { query: payload })
   })
 
   app.post(graphqlPath, {
diff --git a/package.json b/package.json
index b328c8f..7f9f1a6 100644
--- a/package.json
+++ b/package.json
@@ -24,11 +24,11 @@
   },
   "homepage": "https://github.com/mcollina/fastify-gql#readme",
   "devDependencies": {
-    "@types/node": "^14.0.1",
-    "@typescript-eslint/eslint-plugin": "^3.0.0",
-    "@typescript-eslint/parser": "^3.0.0",
+    "@types/node": "^14.0.23",
+    "@typescript-eslint/eslint-plugin": "^3.6.1",
+    "@typescript-eslint/parser": "^3.6.1",
     "autocannon": "^5.0.0",
-    "fastify": "^3.0.0",
+    "fastify": "^3.0.2",
     "graphql-tools": "^6.0.8",
     "pre-commit": "^1.2.2",
     "proxyquire": "^2.1.3",
@@ -39,9 +39,9 @@
   },
   "dependencies": {
     "end-of-stream": "^1.4.4",
-    "fastify-plugin": "^2.0.0",
+    "fastify-plugin": "^2.0.1",
     "fastify-static": "^3.0.0",
-    "fastify-websocket": "^2.0.0",
+    "fastify-websocket": "^2.0.5",
     "graphql": "^15.0.0",
     "graphql-jit": "^0.4.0",
     "http-errors": "^1.7.3",


Test patch:
diff --git a/test/app-decorator.js b/test/app-decorator.js
index 6f1cdc6..b6cf57e 100644
--- a/test/app-decorator.js
+++ b/test/app-decorator.js
@@ -1016,3 +1016,34 @@ test('extended Schema is an object', async t => {
 
   await app.ready()
 })
+
+test('Error in schema', async (t) => {
+  const schema = `
+    interface Event {
+      Id: Int!
+    }
+    type CustomEvent implements Event {
+      # Id needs to be specified here
+      Name: String!
+    }
+    type Query {
+      listEvent: [Event]
+    }
+  `
+
+  const resolvers = {
+    listEvent: async () => []
+  }
+
+  const app = Fastify()
+
+  try {
+    app.register(GQL, {
+      schema,
+      resolvers
+    })
+    await app.ready()
+  } catch (error) {
+    t.equal(error.name, 'Error')
+  }
+})
diff --git a/test/loaders.js b/test/loaders.js
index b175117..42c0caf 100644
--- a/test/loaders.js
+++ b/test/loaders.js
@@ -368,7 +368,8 @@ test('options cache is type = number', async t => {
   const app = Fastify()
 
   app.register(GQL, {
-    cache: 256
+    cache: 256,
+    schema
   })
 
   await app.ready()
@@ -378,7 +379,8 @@ test('options cache is boolean', async t => {
   const app = Fastify()
 
   app.register(GQL, {
-    cache: true
+    cache: true,
+    schema
   })
 
   try {
diff --git a/test/routes.js b/test/routes.js
index 14c6b9f..749d0c4 100644
--- a/test/routes.js
+++ b/test/routes.js
@@ -518,50 +518,6 @@ test('POST return 400 on error', async (t) => {
   t.matchSnapshot(JSON.stringify(JSON.parse(res.body), null, 2))
 })
 
-test('POST return 500 on error without statusCode', async (t) => {
-  const app = Fastify()
-  const schema = `
-    interface Event {
-      Id: Int!
-    }
-    type CustomEvent implements Event {
-      # Id needs to be specified here
-      Name: String!
-    }
-    type Query {
-      listEvent: [Event]
-    }
-  `
-
-  const resolvers = {
-    listEvent: async () => []
-  }
-
-  // node v14 requires that this GQL schema error is caught - error doesn't propagate when node <= v12
-  try {
-    app.register(GQL, {
-      schema,
-      resolvers
-    })
-    await app.ready()
-  } catch (error) {
-    t.equal(error.name, 'GraphQLError')
-  }
-
-  const query = '{ listEvent { id } }'
-
-  const res = await app.inject({
-    method: 'POST',
-    url: '/graphql',
-    body: {
-      query
-    }
-  })
-
-  t.equal(res.statusCode, 500) // Internal error
-  t.matchSnapshot(JSON.stringify(JSON.parse(res.body), null, 2))
-})
-
 test('mutation with POST', async (t) => {
   const app = Fastify()
   const schema = `
diff --git a/test/types/index.ts b/test/types/index.ts
index 420913c..8e7f1e5 100644
--- a/test/types/index.ts
+++ b/test/types/index.ts
@@ -1,5 +1,6 @@
 import Fastify from 'fastify'
-import * as GQL from '../..'
+// eslint-disable-next-line no-unused-vars
+import fastifyGQL, { FastifyGQLOptions } from '../..'
 // eslint-disable-next-line no-unused-vars
 import { ValidationContext, ValidationRule } from 'graphql'
 
@@ -39,7 +40,7 @@ const resolvers = {
   }
 }
 
-app.register(GQL, {
+app.register(fastifyGQL, {
   schema: schema,
   resolvers,
   loaders: {},
@@ -91,7 +92,7 @@ app.register(async function (app) {
   `)
   app.graphql.defineResolvers({
     Query: {
-      willThrow: async () => { throw new GQL.ErrorWithProps('Extended Error', { code: 'EXTENDED_ERROR', reason: 'some reason', other: 32 }) }
+      willThrow: async () => { throw new fastifyGQL.ErrorWithProps('Extended Error', { code: 'EXTENDED_ERROR', reason: 'some reason', other: 32 }) }
     }
   })
 })
@@ -103,10 +104,10 @@ app.get('/', async function (req, reply) {
 
 app.listen(3000)
 
-function makeGraphqlServer (options: GQL.Options) {
+function makeGraphqlServer (options: FastifyGQLOptions) {
   const app = Fastify()
 
-  app.register(GQL, options)
+  app.register(fastifyGQL, options)
 
   return app
 }
@@ -122,4 +123,4 @@ const customValidationRule: ValidationRule = (_context: ValidationContext) => {
 makeGraphqlServer({ schema, resolvers })
 makeGraphqlServer({ schema, resolvers, validationRules: [] })
 makeGraphqlServer({ schema, resolvers, validationRules: [customValidationRule] })
-makeGraphqlServer({ schema, resolvers, validationRules: ({ variables, operationName, source }) => [customValidationRule] })
+makeGraphqlServer({ schema, resolvers, validationRules: ({ variables, operationName, source }: { source: string, variables?: Record<string, any>, operationName?: string }) => [customValidationRule] })
diff --git a/test/types/tsconfig.json b/tsconfig.json
similarity index 69%
rename from test/types/tsconfig.json
rename to tsconfig.json
index cc771bc..a7254c2 100644
--- a/test/types/tsconfig.json
+++ b/tsconfig.json
@@ -6,8 +6,8 @@
     "strict": true,
     "esModuleInterop": true
   },
-  "exclude": ["node_modules", "**/node_modules/*"],
+  "exclude": ["node_modules"],
   "files": [
-    "./index.ts"
+    "./test/types/index.ts"
   ]
 }
\ No newline at end of file