Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We鈥檒l occasionally send you account related emails.

Already on GitHub? Sign in to your account

Use the same context object in the entire pipeline #3051

Merged
merged 4 commits into from Oct 13, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
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');
ardatan marked this conversation as resolved.
Show resolved Hide resolved
});
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 @@
{
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 @@
}

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,15 +542,17 @@
{
params,
request,
batched: true,
},
serverContext,
),

Check failure on line 548 in packages/graphql-yoga/src/server.ts

View workflow job for this annotation

GitHub Actions / release-candidate / snapshot

Argument of type '{ params: GraphQLParams<Record<string, any>, Record<string, any>>; request: Request; }' is not assignable to parameter of type '{ params: GraphQLParams<Record<string, any>, Record<string, any>>; request: Request; batched: boolean; }'.
),
)
: this.getResultForParams(
{
params: requestParserResult,
request,
batched: false,
},
serverContext,
))) as ResultProcessorInput;
Expand Down