Skip to content

Commit

Permalink
Add support for resolveObject to query execution
Browse files Browse the repository at this point in the history
  • Loading branch information
martijnwalraven committed Nov 9, 2018
1 parent 3413c59 commit bb67584
Show file tree
Hide file tree
Showing 3 changed files with 63 additions and 8 deletions.
14 changes: 11 additions & 3 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

3 changes: 2 additions & 1 deletion package.json
Expand Up @@ -34,6 +34,7 @@
"node": ">=6"
},
"dependencies": {
"@apollographql/apollo-tools": "file:../apollo-tooling/packages/apollo-tools",
"apollo-cache-control": "file:packages/apollo-cache-control",
"apollo-datasource": "file:packages/apollo-datasource",
"apollo-datasource-rest": "file:packages/apollo-datasource-rest",
Expand Down Expand Up @@ -94,7 +95,7 @@
"express": "4.16.4",
"fibers": "3.0.0",
"form-data": "2.3.3",
"graphql": "14.0.2",
"graphql": "file:../graphql-js/dist",
"graphql-subscriptions": "1.0.0",
"graphql-tag": "2.10.0",
"graphql-tools": "4.0.3",
Expand Down
54 changes: 50 additions & 4 deletions packages/graphql-extensions/src/index.ts
Expand Up @@ -8,6 +8,8 @@ import {
GraphQLResolveInfo,
ExecutionArgs,
DocumentNode,
ResponsePath,
FieldNode,
} from 'graphql';

import { Request } from 'apollo-server-env';
Expand All @@ -19,6 +21,8 @@ import {
} from 'apollo-server-core/dist/requestPipelineAPI';
export { GraphQLResponse };

import { GraphQLObjectResolver } from '@apollographql/apollo-tools';

export type EndHandler = (...errors: Array<Error>) => void;
// A StartHandlerInvoker is a function that, given a specific GraphQLExtension,
// finds a specific StartHandler on that extension and calls it with appropriate
Expand Down Expand Up @@ -199,6 +203,15 @@ function wrapField(field: GraphQLField<any, any>): void {
const fieldResolver = field.resolve;

field.resolve = (source, args, context, info) => {
// This is a bit of a hack, but since `ResponsePath` is a linked list,
// a new object gets created every time a path segment is added.
// So we can use that to share our `whenObjectResolved` promise across
// all field resolvers for the same object.
const parentPath = info.path.prev as ResponsePath & {
__fields: Record<string, ReadonlyArray<FieldNode>>;
__whenObjectResolved: Promise<any>;
};

const extensionStack = context && context._extensionStack;
const handler =
(extensionStack &&
Expand All @@ -207,12 +220,45 @@ function wrapField(field: GraphQLField<any, any>): void {
/* do nothing */
});

// If no resolver has been defined for a field, use the default field resolver
// (which matches the behavior of graphql-js when there is no explicit resolve function defined).
const resolveObject: GraphQLObjectResolver<
any,
any
> = (info.parentType as any).resolveObject;

let whenObjectResolved: Promise<any> | undefined;

if (parentPath && resolveObject) {
whenObjectResolved = parentPath.__whenObjectResolved;
if (!whenObjectResolved) {
whenObjectResolved = (async () => {
return resolveObject(source, context, info);
})();
parentPath.__whenObjectResolved = whenObjectResolved;
}
}

try {
const result = (fieldResolver ||
// If no resolver has been defined for a field, use either the configured
// field resolver or the default field resolver
// (which matches the behavior of graphql-js when there is no explicit
// resolve function defined).
// XXX: Can't this be pulled up to the top of `wrapField` and only
// assigned once? It seems `extensionStack.fieldResolver` isn't set
// anywhere?
const actualFieldResolver =
fieldResolver ||
(extensionStack && extensionStack.fieldResolver) ||
defaultFieldResolver)(source, args, context, info);
defaultFieldResolver;

let result: any;
if (whenObjectResolved) {
result = whenObjectResolved.then((resolvedObject: any) => {
return actualFieldResolver(resolvedObject, args, context, info);
});
} else {
result = actualFieldResolver(source, args, context, info);
}

// Call the stack's handlers either immediately (if result is not a
// Promise) or once the Promise is done. Then return that same
// maybe-Promise value.
Expand Down

0 comments on commit bb67584

Please sign in to comment.