From b6ec1a1fbc6acff339ef2a4ae0e829f24f77ba0c Mon Sep 17 00:00:00 2001 From: Pelle Wessman Date: Fri, 6 Sep 2024 14:19:38 +0200 Subject: [PATCH] feat: support pre-parsed queries (eg. graphql-tag) (#1108) --- index.d.ts | 4 ++-- index.js | 19 +++++++++++-------- package.json | 2 ++ test/app-decorator.js | 31 +++++++++++++++++++++++++++++++ 4 files changed, 46 insertions(+), 10 deletions(-) diff --git a/index.d.ts b/index.d.ts index a64fc2f8..13396202 100644 --- a/index.d.ts +++ b/index.d.ts @@ -196,7 +196,7 @@ declare namespace mercurius { TData extends Record = Record, TVariables extends Record = Record >( - source: string, + source: string | DocumentNode, context?: Record, variables?: TVariables, operationName?: string @@ -737,7 +737,7 @@ declare module 'fastify' { TData extends Record = Record, TVariables extends Record = Record >( - source: string, + source: string | DocumentNode, context?: Record, variables?: TVariables, operationName?: string diff --git a/index.js b/index.js index 4523237d..c9303c91 100644 --- a/index.js +++ b/index.js @@ -1,7 +1,6 @@ 'use strict' const fp = require('fastify-plugin') -const LRU = require('tiny-lru').lru const routes = require('./lib/routes') const { compileQuery, isCompiledQuery } = require('graphql-jit') const { Factory } = require('single-user-cache') @@ -46,7 +45,7 @@ const { onExtendSchemaHandler } = require('./lib/handlers') -function buildCache (opts) { +async function buildCache (opts) { if (Object.prototype.hasOwnProperty.call(opts, 'cache')) { const isBoolean = typeof opts.cache === 'boolean' const isNumber = typeof opts.cache === 'number' @@ -55,20 +54,22 @@ function buildCache (opts) { // no cache return null } else if (isNumber) { + const QuickLRU = (await import('quick-lru')).default // cache size as specified - return LRU(opts.cache) + return new QuickLRU({ maxSize: opts.cache }) } else if (!isBoolean && !isNumber) { throw new MER_ERR_INVALID_OPTS('Cache type is not supported') } } + const QuickLRU = (await import('quick-lru')).default // default cache, 1024 entries - return LRU(1024) + return new QuickLRU({ maxSize: 1024 }) } const mercurius = fp(async function (app, opts) { - const lru = buildCache(opts) - const lruErrors = buildCache(opts) + const lru = await buildCache(opts) + const lruErrors = await buildCache(opts) if (lru && opts.validationRules && typeof opts.validationRules === 'function') { throw new MER_ERR_INVALID_OPTS('Using a function for the validationRules is incompatible with query caching') @@ -413,7 +414,7 @@ const mercurius = fp(async function (app, opts) { const reply = context.reply // Trigger preParsing hook - if (context.preParsing !== null) { + if (context.preParsing !== null && typeof source === 'string') { await preParsingHandler({ schema: fastifyGraphQl.schema, source, context }) } @@ -433,7 +434,9 @@ const mercurius = fp(async function (app, opts) { } try { - document = parse(source, gqlParseOpts) + document = typeof source === 'string' + ? parse(source, gqlParseOpts) + : structuredClone(source) } catch (syntaxError) { try { // Do not try to JSON.parse maxToken exceeded validation errors diff --git a/package.json b/package.json index 00171a9b..772c64ad 100644 --- a/package.json +++ b/package.json @@ -41,6 +41,7 @@ "eslint": "^9.9.1", "fastify": "^4.17.0", "graphql": "^16.0.0", + "graphql-tag": "^2.12.6", "graphql-ws": "^5.11.2", "neostandard": "^0.11.4", "pre-commit": "^1.2.2", @@ -61,6 +62,7 @@ "graphql-jit": "0.8.4", "mqemitter": "^5.0.0", "p-map": "^4.0.0", + "quick-lru": "^7.0.0", "readable-stream": "^4.0.0", "safe-stable-stringify": "^2.3.0", "secure-json-parse": "^2.7.0", diff --git a/test/app-decorator.js b/test/app-decorator.js index 13c7bafa..8e9e197b 100644 --- a/test/app-decorator.js +++ b/test/app-decorator.js @@ -4,6 +4,7 @@ const { test } = require('tap') const Fastify = require('fastify') const GQL = require('..') const { GraphQLError } = require('graphql') +const gql = require('graphql-tag') const { GraphQLScalarType, @@ -1439,3 +1440,33 @@ test('defineResolvers should support __resolveReference', async (t) => { // needed so that graphql is defined await app.ready() }) + +test('graphql-tag', async (t) => { + const app = Fastify() + const schema = ` + type Query { + add(x: Int, y: Int): Int + } + ` + + const resolvers = { + add: async ({ x, y }) => x + y + } + + app.register(GQL, { + schema, + resolvers + }) + + // needed so that graphql is defined + await app.ready() + + const query = gql`{ add(x: 2, y: 2) }` + const res = await app.graphql(query) + + t.same(res, { + data: { + add: 4 + } + }) +})