diff --git a/package-lock.json b/package-lock.json index 4b8d68c963a..c7c2b514d42 100644 --- a/package-lock.json +++ b/package-lock.json @@ -3,6 +3,9 @@ "requires": true, "lockfileVersion": 1, "dependencies": { + "@apollographql/apollo-tools": { + "version": "file:../apollo-tooling/packages/apollo-tools" + }, "@apollographql/apollo-upload-server": { "version": "5.0.3", "resolved": "https://registry.npmjs.org/@apollographql/apollo-upload-server/-/apollo-upload-server-5.0.3.tgz", @@ -6080,12 +6083,17 @@ "dev": true }, "graphql": { - "version": "14.0.2", - "resolved": "https://registry.npmjs.org/graphql/-/graphql-14.0.2.tgz", - "integrity": "sha512-gUC4YYsaiSJT1h40krG3J+USGlwhzNTXSb4IOZljn9ag5Tj+RkoXrWp+Kh7WyE3t1NCfab5kzCuxBIvOMERMXw==", + "version": "file:../graphql-js/dist", "dev": true, "requires": { "iterall": "^1.2.2" + }, + "dependencies": { + "iterall": { + "version": "1.2.2", + "bundled": true, + "dev": true + } } }, "graphql-extensions": { diff --git a/package.json b/package.json index 2f282e9f17b..67e5282c7f4 100644 --- a/package.json +++ b/package.json @@ -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", @@ -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", diff --git a/packages/graphql-extensions/src/index.ts b/packages/graphql-extensions/src/index.ts index 99d82bbb954..70d3d2a1885 100644 --- a/packages/graphql-extensions/src/index.ts +++ b/packages/graphql-extensions/src/index.ts @@ -8,6 +8,8 @@ import { GraphQLResolveInfo, ExecutionArgs, DocumentNode, + ResponsePath, + FieldNode, } from 'graphql'; import { Request } from 'apollo-server-env'; @@ -19,6 +21,8 @@ import { } from 'apollo-server-core/dist/requestPipelineAPI'; export { GraphQLResponse }; +import { GraphQLObjectResolver } from '@apollographql/apollo-tools'; + export type EndHandler = (...errors: Array) => void; // A StartHandlerInvoker is a function that, given a specific GraphQLExtension, // finds a specific StartHandler on that extension and calls it with appropriate @@ -199,6 +203,15 @@ function wrapField(field: GraphQLField): 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>; + __whenObjectResolved: Promise; + }; + const extensionStack = context && context._extensionStack; const handler = (extensionStack && @@ -207,12 +220,45 @@ function wrapField(field: GraphQLField): 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 | 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.