Skip to content

Commit

Permalink
feat: uniquely identify batched queries (#849)
Browse files Browse the repository at this point in the history
  • Loading branch information
drakhart committed Aug 29, 2022
1 parent 27e9df4 commit 911a5ef
Show file tree
Hide file tree
Showing 6 changed files with 93 additions and 47 deletions.
3 changes: 3 additions & 0 deletions index.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,9 @@ export interface PubSub {
export interface MercuriusContext {
app: FastifyInstance;
reply: FastifyReply;
operationsCount?: number;
operationId?: number;
__currentQuery: string;
/**
* __Caution__: Only available if `subscriptions` are enabled
*/
Expand Down
24 changes: 17 additions & 7 deletions lib/routes.js
Original file line number Diff line number Diff line change
Expand Up @@ -352,13 +352,23 @@ module.exports = async function (app, opts) {

if (allowBatchedQueries && Array.isArray(request.body)) {
// Batched query
return Promise.all(request.body.map(r =>
execute(r, request, reply)
.catch(e => {
const { response } = errorFormatter(e, request[kRequestContext])
return response
})
))
const operationsCount = request.body.length

Object.assign(request[kRequestContext], { operationsCount })

return Promise.all(request.body.map(async (r, operationId) => {
// Create individual reqs for multiple operations, otherwise reference the original req
const operationReq = operationsCount > 1 ? Object.create(request) : request

Object.assign(operationReq[kRequestContext], { operationId })

try {
return await execute(r, operationReq, reply)
} catch (e) {
const { response } = errorFormatter(e, request[kRequestContext])
return response
}
}))
} else {
// Regular query
return execute(request.body, request, reply)
Expand Down
42 changes: 42 additions & 0 deletions test/batched.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

const { test } = require('tap')
const Fastify = require('fastify')
const sinon = require('sinon')
const GQL = require('..')

test('POST regular query', async (t) => {
Expand Down Expand Up @@ -346,3 +347,44 @@ test('POST batched query with a resolver which succeeds and a resolver which thr

t.same(JSON.parse(res.body), [{ data: { add: 3 } }, { data: null, errors: [{ message: 'Internal Server Error' }] }])
})

test('POST batched query has an individual context for each operation', async (t) => {
const app = Fastify()

const contextSpy = sinon.spy()

const schema = `
type Query {
test: String
}
`

const resolvers = {
test: (_, ctx) => contextSpy(ctx.operationId, ctx.operationsCount, ctx.__currentQuery)
}

app.register(GQL, {
schema,
resolvers,
allowBatchedQueries: true
})

await app.inject({
method: 'POST',
url: '/graphql',
body: [
{
operationName: 'TestQuery',
query: 'query TestQuery { test }'
},
{
operationName: 'DoubleQuery',
query: 'query DoubleQuery { test }'
}
]
})

sinon.assert.calledTwice(contextSpy)
sinon.assert.calledWith(contextSpy, 0, 2, sinon.match(/TestQuery/))
sinon.assert.calledWith(contextSpy, 1, 2, sinon.match(/DoubleQuery/))
})
4 changes: 1 addition & 3 deletions test/options.js
Original file line number Diff line number Diff line change
Expand Up @@ -30,8 +30,6 @@ const resolvers = {
}

test('call compileQuery with correct options if compilerOptions specified', async t => {
t.plan(1)

const app = Fastify()
t.teardown(() => app.close())

Expand Down Expand Up @@ -82,5 +80,5 @@ test('call compileQuery with correct options if compilerOptions specified', asyn
body: JSON.stringify({ query })
})

t.ok(compileQueryStub.calledOnceWith(sinon.match.any, sinon.match.any, sinon.match.any, { customJSONSerializer: true }))
sinon.assert.calledOnceWithExactly(compileQueryStub, sinon.match.any, sinon.match.any, sinon.match.any, { customJSONSerializer: true })
})
58 changes: 22 additions & 36 deletions test/subscription-connection.js
Original file line number Diff line number Diff line change
Expand Up @@ -637,24 +637,17 @@ test('subscription connection handles query when fullWsTransport: true', async (
})
)

t.ok(
send.withArgs(
JSON.stringify({
type: 'next',
id: 1,
payload: {}
})
).calledOnce
)
t.ok(
send.withArgs(
JSON.stringify({
type: 'complete',
id: 1,
payload: null
})
).calledOnce
)
sinon.assert.calledTwice(send)
sinon.assert.calledWith(send, JSON.stringify({
type: 'next',
id: 1,
payload: {}
}))
sinon.assert.calledWith(send, JSON.stringify({
type: 'complete',
id: 1,
payload: null
}))
})

test('subscription connection handles mutation when fullWsTransport: true', async (t) => {
Expand Down Expand Up @@ -688,24 +681,17 @@ test('subscription connection handles mutation when fullWsTransport: true', asyn
})
)

t.ok(
send.withArgs(
JSON.stringify({
type: 'next',
id: 1,
payload: {}
})
).calledOnce
)
t.ok(
send.withArgs(
JSON.stringify({
type: 'complete',
id: 1,
payload: null
})
).calledOnce
)
sinon.assert.calledTwice(send)
sinon.assert.calledWith(send, JSON.stringify({
type: 'next',
id: 1,
payload: {}
}))
sinon.assert.calledWith(send, JSON.stringify({
type: 'complete',
id: 1,
payload: null
}))
})

test('subscription data is released right after it ends', async (t) => {
Expand Down
9 changes: 8 additions & 1 deletion test/types/index.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { expectAssignable, expectError } from 'tsd'
import { expectAssignable, expectError, expectType } from 'tsd'
/* eslint-disable no-unused-expressions */
import { EventEmitter } from 'events'
// eslint-disable-next-line no-unused-vars
Expand Down Expand Up @@ -780,3 +780,10 @@ expectError(() => {
expectError(() => {
return mercurius.defaultErrorFormatter({}, undefined)
})

// Context contains correct information about (batched) query identity
app.graphql.addHook('onResolution', async function (_execution, context) {
expectType<number | undefined>(context.operationId)
expectType<number | undefined>(context.operationsCount)
expectType<string>(context.__currentQuery)
})

0 comments on commit 911a5ef

Please sign in to comment.