From 5b3d25351cdd2714a1edb9833ab2c2c7a9316944 Mon Sep 17 00:00:00 2001 From: enisdenjo Date: Fri, 18 Sep 2020 20:30:51 +0200 Subject: [PATCH] feat(server): Define execution/subscription `context` in creation options Closes #13 --- src/server.ts | 10 +++++ src/tests/fixtures/simple.ts | 8 ++++ src/tests/server.ts | 74 +++++++++++++++++++++++++++++++++++- 3 files changed, 91 insertions(+), 1 deletion(-) diff --git a/src/server.ts b/src/server.ts index 22966933..8b3c14d3 100644 --- a/src/server.ts +++ b/src/server.ts @@ -52,6 +52,14 @@ export interface ServerOptions { * from the `onSubscribe` callback. */ schema?: GraphQLSchema; + /** + * A value which is provided to every resolver and holds + * important contextual information like the currently + * logged in user, or access to a database. + * Related operation context value will be injected to the + * `ExecutionArgs` BEFORE the `onSubscribe` callback. + */ + context?: SubscriptionArgs['contextValue']; /** * The GraphQL root fields or resolvers to go * alongside the schema. Learn more about them @@ -213,6 +221,7 @@ export function createServer( ): Server { const { schema, + context, roots, execute, subscribe, @@ -403,6 +412,7 @@ export function createServer( } let execArgsMaybeSchema: Optional = { + contextValue: context, schema, operationName: operation.operationName, document, diff --git a/src/tests/fixtures/simple.ts b/src/tests/fixtures/simple.ts index 6eea9439..36d61108 100644 --- a/src/tests/fixtures/simple.ts +++ b/src/tests/fixtures/simple.ts @@ -33,6 +33,14 @@ export const schema = new GraphQLSchema({ subscription: new GraphQLObjectType({ name: 'Subscription', fields: { + greetings: { + type: new GraphQLNonNull(GraphQLString), + subscribe: async function* () { + for (const hi of ['Hi', 'Bonjour', 'Hola', 'Ciao', 'Zdravo']) { + yield { greetings: hi }; + } + }, + }, becameHappy: { type: personType, args: { diff --git a/src/tests/server.ts b/src/tests/server.ts index 275766af..9764ca19 100644 --- a/src/tests/server.ts +++ b/src/tests/server.ts @@ -1,5 +1,5 @@ import WebSocket from 'ws'; -import { parse, buildSchema } from 'graphql'; +import { parse, buildSchema, execute, subscribe } from 'graphql'; import { GRAPHQL_TRANSPORT_WS_PROTOCOL } from '../protocol'; import { MessageType, parseMessage, stringifyMessage } from '../message'; import { startServer, url, schema, pubsub } from './fixtures/simple'; @@ -998,3 +998,75 @@ it('should use the provided roots as resolvers', async () => { }); expect(nextFn).toBeCalledTimes(3); }); + +it('should pass in the context value from the config', async () => { + const context = {}; + + const executeFn = jest.fn((args) => execute(args)); + const subscribeFn = jest.fn((args) => subscribe(args)); + + await makeServer({ + context, + execute: executeFn, + subscribe: subscribeFn, + }); + + const client = new WebSocket(url, GRAPHQL_TRANSPORT_WS_PROTOCOL); + await new Promise((resolve) => { + client.onopen = () => { + client.send( + stringifyMessage({ + type: MessageType.ConnectionInit, + }), + ); + }; + client.onmessage = ({ data }) => { + const message = parseMessage(data); + if (message.type === MessageType.ConnectionAck) { + resolve(); + } + }; + }); + + await new Promise((resolve) => { + client.send( + stringifyMessage({ + id: '1', + type: MessageType.Subscribe, + payload: { + query: `{ getValue }`, + }, + }), + ); + client.onmessage = ({ data }) => { + const message = parseMessage(data); + if (message.type === MessageType.Next && message.id === '1') { + resolve(); + } + }; + }); + + expect(executeFn).toBeCalled(); + expect(executeFn.mock.calls[0][0].contextValue).toBe(context); + + await new Promise((resolve) => { + client.send( + stringifyMessage({ + id: '2', + type: MessageType.Subscribe, + payload: { + query: `subscription { greetings }`, + }, + }), + ); + client.onmessage = ({ data }) => { + const message = parseMessage(data); + if (message.type === MessageType.Complete && message.id === '2') { + resolve(); + } + }; + }); + + expect(subscribeFn).toBeCalled(); + expect(subscribeFn.mock.calls[0][0].contextValue).toBe(context); +});