Skip to content

Commit

Permalink
Use the same context object in the entire pipeline (#3051)
Browse files Browse the repository at this point in the history
* Use the same context object in the entire pipeline

* Go

* Go

* Do not assign request if already defined
  • Loading branch information
ardatan committed Oct 13, 2023
1 parent 17343a1 commit 350bb85
Show file tree
Hide file tree
Showing 5 changed files with 171 additions and 10 deletions.
5 changes: 5 additions & 0 deletions .changeset/slow-files-act.md
@@ -0,0 +1,5 @@
---
'graphql-yoga': patch
---

Use the same context object in the entire pipeline
14 changes: 14 additions & 0 deletions examples/apollo-federation/service/typeDefs.js
@@ -0,0 +1,14 @@
/* eslint-disable */
const { parse } = require('graphql');

module.exports = parse(/* GraphQL */ `
type Query {
me: User
throw: String
}
type User @key(fields: "id") {
id: ID!
username: String
}
`);
5 changes: 1 addition & 4 deletions examples/service-worker/src/index.ts
@@ -1,10 +1,7 @@
import { createSchema, createYoga, Repeater } from 'graphql-yoga';

// We can define GraphQL Route dynamically using env vars.
declare let GRAPHQL_ROUTE: string;

const yoga = createYoga({
graphqlEndpoint: GRAPHQL_ROUTE || '/graphql',
graphqlEndpoint: globalThis.GRAPHQL_ROUTE || '/graphql',
schema: createSchema({
typeDefs: /* GraphQL */ `
type Query {
Expand Down
133 changes: 133 additions & 0 deletions packages/graphql-yoga/__tests__/context.spec.ts
Expand Up @@ -137,4 +137,137 @@ describe('Context', () => {
expect(onContextBuildingFn.mock.lastCall?.[0].context.params).toEqual(params);
expect(onContextBuildingFn.mock.lastCall?.[0].context.request).toBeDefined();
});

it('share the same context object', async () => {
const contextObjects = new Set();
const plugin = {
onContextBuilding: jest.fn(({ context }) => {
contextObjects.add(context);
}),
onEnveloped: jest.fn(({ context }) => {
contextObjects.add(context);
}),
onParse: jest.fn(({ context }) => {
contextObjects.add(context);
}),
onValidate: jest.fn(({ context }) => {
contextObjects.add(context);
}),
onExecute: jest.fn(({ args }) => {
contextObjects.add(args.contextValue);
}),
onRequest: jest.fn(({ serverContext }) => {
contextObjects.add(serverContext);
}),
onRequestParse: jest.fn(({ serverContext }) => {
contextObjects.add(serverContext);
}),
onResponse: jest.fn(({ serverContext }) => {
contextObjects.add(serverContext);
}),
};
const yoga = createYoga({
schema: createSchema({
typeDefs: /* GraphQL */ `
type Query {
hello: String!
}
`,
resolvers: {
Query: {
hello: () => 'world',
},
},
}),
plugins: [plugin],
});
const queryRes = await yoga.fetch('http://yoga/graphql?query={hello}', {
myExtraContext: 'myExtraContext',
});
expect(queryRes.status).toBe(200);
const queryResult = await queryRes.json();
expect(queryResult.data.hello).toBe('world');
expect(contextObjects.size).toBe(1);
for (const hook of Object.keys(plugin) as (keyof typeof plugin)[]) {
expect(plugin[hook]).toHaveBeenCalledTimes(1);
}
const contextObject = contextObjects.values().next().value;
expect(contextObject).toBeDefined();
expect(contextObject.myExtraContext).toBe('myExtraContext');
});
it('share different context objects for batched requests', async () => {
const contextObjects = new Set();
const plugin = {
onContextBuilding: jest.fn(({ context }) => {
contextObjects.add(context);
}),
onEnveloped: jest.fn(({ context }) => {
contextObjects.add(context);
}),
onParse: jest.fn(({ context }) => {
contextObjects.add(context);
}),
onValidate: jest.fn(({ context }) => {
contextObjects.add(context);
}),
onExecute: jest.fn(({ args }) => {
contextObjects.add(args.contextValue);
}),
onRequest: jest.fn(({ serverContext }) => {
contextObjects.add(serverContext);
}),
onRequestParse: jest.fn(({ serverContext }) => {
contextObjects.add(serverContext);
}),
onResponse: jest.fn(({ serverContext }) => {
contextObjects.add(serverContext);
}),
};
const yoga = createYoga({
schema: createSchema({
typeDefs: /* GraphQL */ `
type Query {
hello: String!
}
`,
resolvers: {
Query: {
hello: () => 'world',
},
},
}),
plugins: [plugin],
batching: true,
});
const queryRes = await yoga.fetch(
'http://yoga/graphql',
{
method: 'POST',
body: JSON.stringify([
{ query: '{hello}' },
{ query: '{__typename hello}' },
{ query: '{__typename}' },
]),
headers: {
'Content-Type': 'application/json',
},
},
{
myExtraContext: 'myExtraContext',
},
);
expect(queryRes.status).toBe(200);
const queryResult = await queryRes.json();
expect(queryResult.length).toBe(3);
expect(queryResult[0].data.hello).toBe('world');
expect(queryResult[1].data.__typename).toBe('Query');
expect(queryResult[1].data.hello).toBe('world');
expect(queryResult[2].data.__typename).toBe('Query');
// One for server context, one for each request
expect(contextObjects.size).toBe(4);
for (const contextObject of contextObjects) {
expect(contextObject).toBeDefined();
expect((contextObject as { myExtraContext: string }).myExtraContext).toBe('myExtraContext');
}
});
});
24 changes: 18 additions & 6 deletions packages/graphql-yoga/src/server.ts
Expand Up @@ -419,9 +419,11 @@ export class YogaServer<
{
params,
request,
batched,
}: {
params: GraphQLParams;
request: Request;
batched: boolean;
},
// eslint-disable-next-line @typescript-eslint/ban-types
...args: {} extends TServerContext
Expand All @@ -446,12 +448,20 @@ export class YogaServer<
}

if (result == null) {
const serverContext = args[0];
const initialContext = {
...serverContext,
request,
params,
};
const additionalContext = args[0]?.request
? {
params,
}
: {
request,
params,
};

const initialContext = args[0]
? batched
? Object.assign({}, args[0], additionalContext)
: Object.assign(args[0], additionalContext)
: additionalContext;

const enveloped = this.getEnveloped(initialContext);

Expand Down Expand Up @@ -532,6 +542,7 @@ export class YogaServer<
{
params,
request,
batched: true,
},
serverContext,
),
Expand All @@ -541,6 +552,7 @@ export class YogaServer<
{
params: requestParserResult,
request,
batched: false,
},
serverContext,
))) as ResultProcessorInput;
Expand Down

0 comments on commit 350bb85

Please sign in to comment.