Repository: mcollina/fastify-gql
Pull request #192 (2020-07-03 00:52:08)

Expose schema
`fastify-gql` currently keeps the schema it executes queries against private. If you want to execute GraphQL queries against your `fastify-gql` managed schema in another way, like in tests, from background jobs, or using your own protocol bindings of some sort, this is unfortunate! I think it makes sense to expose it so developers can access it and do whatever they need to with it. In the normal course of writing GraphQL apps this shouldn't be necessary, but it doesn't seem like `fastify-gql` needs to reserve the schema as an implementation detail since the type is already in use outside `fastify-gql`'s API boundaries.

This is currently on top of #190 to get the tests to pass, I'll rebase once that's merged for a clean master merge! 


Patch:
diff --git a/README.md b/README.md
index 9bc9be8..8e4b5cf 100644
--- a/README.md
+++ b/README.md
@@ -901,7 +901,10 @@ async function run () {
 run()
 ```
 
-<a name="defineLoaders"></a>
+#### app.graphql.schema
+
+Provides access to the built `GraphQLSchema` object that `fastify-gql` will use to execute queries. This property will reflect any updates made by `extendSchema` or `replaceSchema` as well.
+
 #### app.graphql.defineLoaders(loaders)
 
 A loader is an utility to avoid the 1 + N query problem of GraphQL.
diff --git a/index.d.ts b/index.d.ts
index 25a053f..6dcf30b 100644
--- a/index.d.ts
+++ b/index.d.ts
@@ -37,6 +37,10 @@ declare namespace fastifyGQL {
           }) => any
       }
     }): void;
+    /**
+     * Managed GraphQL schema object for doing custom execution with. Will reflect changes made via `extendSchema`, `defineResolvers`, etc.
+     */
+    schema: GraphQLSchema;
   }
 
   export interface Options<
diff --git a/index.js b/index.js
index 5156635..18ded13 100644
--- a/index.js
+++ b/index.js
@@ -130,8 +130,10 @@ const plugin = fp(async function (app, opts) {
     })
   }
 
+  fastifyGraphQl.schema = schema
+
   app.ready(async function () {
-    const schemaValidationErrors = validateSchema(schema)
+    const schemaValidationErrors = validateSchema(fastifyGraphQl.schema)
     if (schemaValidationErrors.length > 0) {
       const err = new Error('schema issues')
       err.errors = schemaValidationErrors
@@ -152,7 +154,7 @@ const plugin = fp(async function (app, opts) {
       onlyPersisted: onlyPersisted,
       persistedQueries: opts.persistedQueries,
       allowBatchedQueries: opts.allowBatchedQueries,
-      schema,
+      schema: fastifyGraphQl.schema,
       subscriber,
       verifyClient,
       lruGatewayResolvers,
@@ -177,7 +179,7 @@ const plugin = fp(async function (app, opts) {
       throw new Error('Must provide valid Document AST')
     }
 
-    schema = s
+    fastifyGraphQl.schema = s
 
     lru.clear()
     lruErrors.clear()
@@ -194,7 +196,7 @@ const plugin = fp(async function (app, opts) {
       throw new Error('Must provide valid Document AST')
     }
 
-    schema = extendSchema(schema, s)
+    fastifyGraphQl.schema = extendSchema(fastifyGraphQl.schema, s)
   }
 
   fastifyGraphQl.defineResolvers = function (resolvers) {
@@ -203,7 +205,7 @@ const plugin = fp(async function (app, opts) {
     }
 
     for (const name of Object.keys(resolvers)) {
-      const type = schema.getType(name)
+      const type = fastifyGraphQl.schema.getType(name)
 
       if (typeof resolvers[name] === 'function') {
         root[name] = resolvers[name]
@@ -319,7 +321,7 @@ const plugin = fp(async function (app, opts) {
       }
 
       // Validate
-      const validationErrors = validate(schema, document, validationRules)
+      const validationErrors = validate(fastifyGraphQl.schema, document, validationRules)
 
       if (validationErrors.length > 0) {
         if (lruErrors) {
@@ -359,7 +361,7 @@ const plugin = fp(async function (app, opts) {
 
     // minJit is 0 by default
     if (cached && cached.count++ === minJit) {
-      cached.jit = compileQuery(schema, document, operationName)
+      cached.jit = compileQuery(fastifyGraphQl.schema, document, operationName)
     }
 
     if (cached && cached.jit !== null) {
@@ -383,7 +385,7 @@ const plugin = fp(async function (app, opts) {
 
     // Validate variables
     if (variables !== undefined) {
-      const executionContext = buildExecutionContext(schema, document, root, context, variables, operationName)
+      const executionContext = buildExecutionContext(fastifyGraphQl.schema, document, root, context, variables, operationName)
       if (Array.isArray(executionContext)) {
         const err = new BadRequest()
         err.errors = executionContext
@@ -392,7 +394,7 @@ const plugin = fp(async function (app, opts) {
     }
 
     const execution = await execute(
-      schema,
+      fastifyGraphQl.schema,
       document,
       root,
       context,


Test patch:
diff --git a/test/app-decorator.js b/test/app-decorator.js
index 37b521d..c930ef9 100644
--- a/test/app-decorator.js
+++ b/test/app-decorator.js
@@ -314,6 +314,35 @@ test('extendSchema and defineResolvers for query', async (t) => {
   })
 })
 
+test('extendSchema changes reflected in schema access', async (t) => {
+  const app = Fastify()
+  const schema = `
+    extend type Query {
+      add(x: Int, y: Int): Int
+    }
+  `
+
+  const resolvers = {
+    add: async ({ x, y }) => x + y
+  }
+
+  app.register(GQL)
+
+  let beforeSchema
+  app.register(async function (app) {
+    beforeSchema = app.graphql.schema
+
+    app.graphql.extendSchema(schema)
+    app.graphql.defineResolvers(resolvers)
+  })
+
+  // needed so that graphql is defined
+  await app.ready()
+
+  t.notEqual(beforeSchema, app.graphql.schema)
+  t.end()
+})
+
 test('extendSchema and defineResolvers with mutation definition', async (t) => {
   const app = Fastify()
   const schema = `