Skip to content

Commit

Permalink
fix(server): Consistently set rootValue and contextValue, if not …
Browse files Browse the repository at this point in the history
…overridden (enisdenjo#49)
  • Loading branch information
benjie authored Nov 4, 2020
1 parent bff92a8 commit 7aa3bcd
Show file tree
Hide file tree
Showing 4 changed files with 69 additions and 21 deletions.
15 changes: 8 additions & 7 deletions docs/interfaces/_server_.serveroptions.md
Original file line number Diff line number Diff line change
Expand Up @@ -204,10 +204,11 @@ If you return `ExecutionArgs` from the callback,
it will be used instead of trying to build one
internally. In this case, you are responsible
for providing a ready set of arguments which will
be directly plugged in the operation execution. Beware,
the `context` server option is an exception. Only if you
dont provide a context alongside the returned value
here, the `context` server option will be used instead.
be directly plugged in the operation execution.

Omitting the fields `contextValue` or `rootValue`
from the returned value will have the provided server
options fill in the gaps.

To report GraphQL errors simply return an array
of them from the callback, they will be reported
Expand All @@ -233,9 +234,9 @@ The GraphQL root fields or resolvers to go
alongside the schema. Learn more about them
here: https://graphql.org/learn/execution/#root-fields-resolvers.

If you return from the `onSubscribe` callback, the
root field value will NOT be injected. You should add it
in the returned `ExecutionArgs` from the callback.
If you return from `onSubscribe`, and the returned value is
missing the `rootValue` field, the relevant operation root
will be used instead.

___

Expand Down
2 changes: 1 addition & 1 deletion docs/modules/_server_.md
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@

### GraphQLExecutionContextValue

Ƭ **GraphQLExecutionContextValue**: object \| symbol \| number \| string \| boolean \| null
Ƭ **GraphQLExecutionContextValue**: object \| symbol \| number \| string \| boolean \| undefined \| null

A concrete GraphQL execution context value type.

Expand Down
25 changes: 13 additions & 12 deletions src/server.ts
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,7 @@ export type GraphQLExecutionContextValue =
| number
| string
| boolean
| undefined
| null;

export interface ServerOptions {
Expand Down Expand Up @@ -96,9 +97,9 @@ export interface ServerOptions {
* alongside the schema. Learn more about them
* here: https://graphql.org/learn/execution/#root-fields-resolvers.
*
* If you return from the `onSubscribe` callback, the
* root field value will NOT be injected. You should add it
* in the returned `ExecutionArgs` from the callback.
* If you return from `onSubscribe`, and the returned value is
* missing the `rootValue` field, the relevant operation root
* will be used instead.
*/
roots?: {
[operation in OperationTypeNode]?: Record<
Expand Down Expand Up @@ -177,10 +178,11 @@ export interface ServerOptions {
* it will be used instead of trying to build one
* internally. In this case, you are responsible
* for providing a ready set of arguments which will
* be directly plugged in the operation execution. Beware,
* the `context` server option is an exception. Only if you
* dont provide a context alongside the returned value
* here, the `context` server option will be used instead.
* be directly plugged in the operation execution.
*
* Omitting the fields `contextValue` or `rootValue`
* from the returned value will have the provided server
* options fill in the gaps.
*
* To report GraphQL errors simply return an array
* of them from the callback, they will be reported
Expand Down Expand Up @@ -590,14 +592,13 @@ export function createServer(
]);
}

// if onsubscribe didnt return anything, inject roots
if (!maybeExecArgsOrErrors) {
// if `onSubscribe` didnt specify a rootValue, inject one
if (!('rootValue' in execArgs)) {
execArgs.rootValue = roots?.[operationAST.operation];
}

// inject the context, if provided, before the operation.
// but, only if the `onSubscribe` didnt provide one already
if (context !== undefined && !execArgs.contextValue) {
// if `onSubscribe` didn't specify a context, inject one
if (!('contextValue' in execArgs)) {
execArgs.contextValue =
typeof context === 'function'
? context(ctx, message, execArgs)
Expand Down
48 changes: 47 additions & 1 deletion src/tests/server.ts
Original file line number Diff line number Diff line change
Expand Up @@ -364,6 +364,51 @@ it('should pass the `onSubscribe` exec args to the `context` option and use it',
);
});

it('should use the root from the `roots` option if the `onSubscribe` doesnt provide one', async (done) => {
const rootValue = {};
const execArgs = {
// no rootValue here
schema,
document: parse(`query { getValue }`),
};

const { url } = await startTServer({
roots: {
query: rootValue,
},
onSubscribe: () => {
return execArgs;
},
execute: (args) => {
expect(args).toBe(execArgs); // from `onSubscribe`
expect(args.rootValue).toBe(rootValue); // injected by `roots`
done();
return execute(args);
},
subscribe,
});

const client = await createTClient(url);
client.ws.send(
stringifyMessage<MessageType.ConnectionInit>({
type: MessageType.ConnectionInit,
}),
);
await client.waitForMessage(({ data }) => {
expect(parseMessage(data).type).toBe(MessageType.ConnectionAck);
});

client.ws.send(
stringifyMessage<MessageType.Subscribe>({
id: '1',
type: MessageType.Subscribe,
payload: {
query: `{ getValue }`,
},
}),
);
});

it('should prefer the `onSubscribe` context value even if `context` option is set', async (done) => {
const context = 'not-me';
const execArgs = {
Expand Down Expand Up @@ -790,6 +835,7 @@ describe('Subscribe', () => {
schema,
operationName: 'Nope',
document: parse(`query Nope { getValue }`),
rootValue: null,
};
const { url } = await startTServer({
schema: undefined,
Expand All @@ -798,7 +844,7 @@ describe('Subscribe', () => {
},
execute: (args) => {
expect(args.schema).toBe(nopeArgs.schema); // schema from nopeArgs
expect(args.rootValue).toBeUndefined(); // nopeArgs didnt provide any root value
expect(args.rootValue).toBeNull(); // nopeArgs provided rootValue: null, so don't overwrite
expect(args.operationName).toBe('Nope');
expect(args.variableValues).toBeUndefined(); // nopeArgs didnt provide variables
return execute(args);
Expand Down

0 comments on commit 7aa3bcd

Please sign in to comment.