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’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add a way to delegate to a non-root field #543

Closed
gviligvili opened this issue Dec 18, 2017 · 10 comments
Closed

Add a way to delegate to a non-root field #543

gviligvili opened this issue Dec 18, 2017 · 10 comments
Labels
feature New addition or enhancement to existing solutions schema stitching

Comments

@gviligvili
Copy link

ver : 2.2.1
some micro service has this queries :

    meetingQueries: {
           getMeetingsByDates(startDate, endDate): [Meeting]
           ......
    }

when I try to stitch him, in the resolver I can't use "getMeetingsByDate" because his not defined in the microservice root queries.

I get:

"Cannot find subschema for root field "query.getMeetingsByDates" ".

so I tried instead of :

mergeInfo.delegate(
`query`,
`getMettingsByDates`
.........)

to do :

mergeInfo.delegate(
`query`,
`meetingQueries.getMettingsByDates`
.........)

yet still I can't find a way to reach a subquery, and couldn't find any documentation on it.

@adampash
Copy link

@stubailo did you ever find a solution to this question? I have the same need but can't find any documentation.

@mrpspring
Copy link

Having this same issue. Was anyone able to figure this out?

@IAlexandr
Copy link

I have the same issue, I can be wrong, but if you know how resolve this field, you can resolve inside the merging resolver.

mergeSchemas({
    schemas,
    resolvers: mergeInfo => ({
      SomeField1: {
        someField2: {
          fragment: '...',
          resolve(parent, args, { db }, info) {
            return db.SomeField.findOne({ where: { id: parent.id } });
         }
    }
  }

@mrpspring
Copy link

Ah ok that makes sense. Thanks!

@stubailo stubailo changed the title Cannot find subschema for root field. Add a way to delegate to a non-root field Jul 14, 2018
@stubailo stubailo added the feature New addition or enhancement to existing solutions label Jul 14, 2018
@elliottsj
Copy link

I'm facing this issue as well. Would be great to be able to pass a full query + fragment to the delegate method that allows us to query a nested field. Maybe something like this (based on the example https://www.apollographql.com/docs/graphql-tools/schema-stitching.html#basic-example):

Chirp_Chirp: {
  author: {
    fragment: `... on Chirp { authorId }`,
    resolve(chirp, args, context, info, chirpAuthorFragment) {
      return info.mergeInfo.delegateToSchema({
        schema: authorSchema,
        query: gql`
          query User($token: String, $authorId: String) {
            viewer(token: $token) {
              userById(id: $authorId) {
                ...ChirpAuthor
              }
            }
          }
          ${chirpAuthorFragment}
        `,
        variables: {
          token: context.token,
          authorId: chirp.authorId
        },
        context,
        info,
      });
    },
  },
},

@dncrews
Copy link

dncrews commented Nov 19, 2018

You can totally do this already with Transforms. The documentation doesn't really make sense around it:

https://www.apollographql.com/docs/graphql-tools/schema-transforms.html#Modifying-types

But if you look at the tests, it may make a bit more sense:

https://github.com/apollographql/graphql-tools/blob/b85137ba81c9bdc5667bced9cad9b1d226ea83fc/src/test/testTransforms.ts

As far as your specific example, I haven't tried it yet, but it's something like this:

const { WrapQuery } = require('graphql-tools')
const { Kind } = require('graphql')

return info.mergeInfo.delegateToSchema({
  schema: authorSchenam
  operation: 'query',
  fieldName: 'meetingQueries',
  args: { id: nodeId },
  context,
  info,
  transforms: [
    new WrapQuery(
      [ 'meetingQueries' ],
      (subtree) => {
        return {
          selectionSet: {
            thing: Kind.SELECTION_SET,
            selections: [
              {
                kind: Kind.FIELD,
                name: {
                  kind: Kind.NAME,
                  value: 'getMeetingsByDates'
                },
                arguments: [
                  {
                    kind: Kind.ARGUMENT,
                    name: {
                      kind: Kind.NAME,
                      value: startDate
                    }
                    value: {
                      kind: Kind.VARIABLE,
                      name: {
                        kind: Kind.NAME,
                        value: startDate
                      }
                    }
                  },
                  {
                    kind: Kind.ARGUMENT,
                    name: {
                      kind: Kind.NAME,
                      value: endDate
                    }
                    value: {
                      kind: Kind.VARIABLE,
                      name: {
                        kind: Kind.NAME,
                        value: endDate
                      }
                    }
                  }
                ],
                selectionSet: subtree
              }
            ]
          }
        }
      }
    , 
    result => result && result.meetingQueries && result.meetingQueries.getMeetingsByDates)
  ]
})

@dncrews
Copy link

dncrews commented Nov 19, 2018

@gviligvili any chance you'd repost this to stack overflow? It'd be great to get the rep for this. Apparently a bunch of people have been having this particular issue

@elliottsj
Copy link

@dncrews Thank you; this is a huge help.

For others reading, the return value of the (subtree) => { /* ... */ } function is a GraphQL AST node representing the query that's made to the sub-schema authorSchema. Equivalent to what I've highlighted here:

screen shot 2018-11-19 at 2 27 17 pm

https://astexplorer.net/#/gist/078337bd6ad24069f0c9183e22f286d2/d10f9974b78eb16dcdb02f7595469099db84d9ef

In the above example, the id and title fields are the selection set from the original query which we're passing along to the sub-query, i.e. the subtree value.


Here is a more complete example which is similar to what I'm doing in my application, using additional schema transforms too: https://gist.github.com/elliottsj/fa7042c0588ea6df1bc2f02f583d1100

@yaacovCR
Copy link
Collaborator

New TransformQuery transform replaces WrapQuery and ExtractField and should do the job, folded into #1306 .

@lucasconstantino
Copy link
Contributor

This is a rudimentary working version of a nested field transform:

/* eslint-disable @typescript-eslint/no-unsafe-assignment, @typescript-eslint/no-unsafe-return */
import type { Transform, DelegationContext } from '@graphql-tools/delegate'
import type { ExecutionRequest, ExecutionResult } from '@graphql-tools/utils'
import type { DocumentNode, FieldNode } from 'graphql'
import { visit, Kind } from 'graphql'

/**
 * Nested field delegation transform.
 *
 * i.e.:
 * ```
 * await delegateToSchema({
 *   schema: subschema,
 *   operation: 'query',
 *   fieldName: 'some.nested.field',
 *   context,
 *   info,
 *   transforms: [new NestedDelegationTransform()],
 * })
 * ```
 */
class NestedDelegationTransform implements Transform {
  /**
   * Transform document to open nested field (containing `.`).
   */
  transformRequest(request: ExecutionRequest) {
    const document: DocumentNode = visit(request.document, {
      [Kind.FIELD]: {
        enter: (node) => {
          const path = node.name.value.split('.')

          // Skip when not nested field name.
          if (path.length === 1) {
            return node
          }

          const first = path[0]
          const rest = path.slice(1).join('.')

          const child: FieldNode = { ...node, name: { ...node.name, value: rest } }
          const parent: FieldNode = {
            ...node,
            name: { ...node.name, value: first },
            selectionSet: {
              kind: Kind.SELECTION_SET,
              selections: [child],
            },
          }

          return parent
        },
      },
    })

    return { ...request, document }
  }

  /**
   * Transform result data by reconstructing nested field (containing `.`).
   */
  transformResult(result: ExecutionResult, delegation: DelegationContext) {
    // Safeguard for query execution errors.
    if (!result.data) {
      return result
    }

    const path = delegation.fieldName.split('.')

    const value = path.reduce((curr, name) => {
      const nested = curr[name]

      // No result, stop traversing.
      if (typeof nested === 'undefined' || nested === null) return null

      // If is an array, simply get first element (how to handle this?)
      if (Array.isArray(nested)) return nested[0]

      return nested
    }, result.data)

    return { ...result, data: { [delegation.fieldName]: value } }
  }
}

export { NestedDelegationTransform }

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
feature New addition or enhancement to existing solutions schema stitching
Projects
None yet
Development

No branches or pull requests

9 participants