Skip to content
This repository has been archived by the owner on Apr 15, 2020. It is now read-only.

Nesting entire root query #24

Closed
terion-name opened this issue Oct 1, 2019 · 7 comments
Closed

Nesting entire root query #24

terion-name opened this issue Oct 1, 2019 · 7 comments

Comments

@terion-name
Copy link

Hello. I need to transform schema in such manner:

Query { Foo: Something }

to

Query { Prefix: PrefixQuery }
PrefixQuery { Foo: PrefixSomething }

In other words to

  1. Prefix all types (with Prefix in example)
  2. Wrap/nest entire query in one object (QueryPrefixQuery)

Prefixing is easily done with RenameTypes transform, but I can't figure out how to properly make nesting (that is in fact prefixing root types and creating a new root type with single field).

Any suggestions on how to do this with existing tooling or how to make a transformer for this?

@yaacovCR
Copy link
Owner

yaacovCR commented Oct 2, 2019

Does this help you?

ardatan#1183

@yaacovCR
Copy link
Owner

yaacovCR commented Oct 2, 2019

From what I recall, I think I had in mind that you first extend with new wrapped fields and then filter out original unwrapped fields using filterSchema

@yaacovCR
Copy link
Owner

yaacovCR commented Oct 2, 2019

Actually, that "wraps" by turning a field into a new type with its own fields wherever it appears, so, for example, you can move street, city, zip fields on some type to new Address subtype, but you have to hard code which fields you are moving to the subtype.

That transform works wherever the type appears. You are talking about just wrapping root fields, so you may be able to get away with WrapQuery or the improved TransformQuery, but you would have to hard code somewhere the PrefixQuery definition.

You could probably use these transforms as inspiration to automate that part. Would be happy to accept PR.

@terion-name
Copy link
Author

Hm. Interesting.

Several moments:

  1. I can't get what WrapQuery does and how it works. Docs are... not)
  2. Is there a way in some transform to rename root types (Query, Mutation, Subscription)? It would be the most easy and obvious flow: rename root types and extend schema with new root types of these types
  3. In your examples what is createMergedResolver?

@yaacovCR
Copy link
Owner

yaacovCR commented Oct 3, 2019

Preface: Intro to Transforms

In general, I find "transforms" a confusing subject. Just to make sure we are on the same page. Some transforms (type A?) implement only request and response middleware (the transformRequest/transformResponse methods) that can be tacked onto any schema delegation call, and they can be used to delegate to a conflicting schema.

Other transforms (type B?) implement a transformSchema method. When used with the exported graphql-tools transformSchema function, they can be used to change the underlying schema. What the exported transformSchema function does is create an outer schema that delegates to the passed in schema (an "inner" schema). The outer schema initially has the same types as the inner/original schema. Each of the passed in transforms' transformSchema methods is called on that original schema to yield a new modified outer schema, with the inner/original schema unchanged.

[As a side note, I found it confusing that the individual transforms' transformSchema methods are called the same thing as the exported transformSchema function, and so I gave that exported function an alias, 'wrapSchema' which I believe is more descriptive.]

As mentioned, type B transforms shadow the original root fields and just delegate to the original schema, automatically including all of the passed transforms' request/response middleware. This extra complexity buys you the ability to stand up a graphql server with the new modified schema using the same tools (express-graphql/Apollo Server) that expect a regular schema.

The WrapQuery transform is an original Apollo transform that is decidedly type A. Its docs are here. Because it does not modify the underling target schema, (i.e., is not a type B), you have to handle generation of the outer schema on your own, presumably by handcoding.

WrapQuery is useful for people working directly with the underlying delegateToSchema function.](ardatan#901). My transformQuery is just an improved version that has better support for errors and fragments.

For comparison, RenameRootFields and RenameTypes are original Apollo type B transforms that gives you a new schema with different root fields.

Your Questions.

  1. WrapQuery is a type A Transform.

WrapType started off with just wrapping, and then, as linked above, modification of the existing selection set was added. This was before my TransformObjectFields transform and ExtendSchema transform allowed for easier ways of renaming fields.

  1. New WrapType Transform! v6.6.0 of my fork, just released, gives you a type B transform that could be what you are looking for.

WrapType is type B, and so you can use it with mergeSchemas/try to extend it/stand up a server with it. Check out an initial use case in the tests with namespacing the root Query type.

Be careful when namespacing mutations, I am not sure, but it may in this implementation break the non-concurrent guarantee. I'm not sure at all what would happen when namespacing subscriptions with this transform, but please report back if you can. I think it technically breaks the spec to namespace subscriptions, as that means sub fields would be returning async iterators, but maybe it doesn't work at all, so the spec is ok. :)

  1. createMergedResolver is a helper function for use with my ExtendSchema transform.

ExtendSchema is type B. It lets you add arbitrary fields and types to an existing schema to provide custom functionality, similar to mergeSchemas, but with just one schema. Schema modification with the ExtendSchema is unidirectional, you are always modifying the function of the original/inner schema (and so that avoids the need to add a required fragment map = info.mergeInfo a la mergeSchemas).

The advantage of using the ExtendSchema rather than mergeSchemas with just one schema is that ExtendSchemas gives you the ability to specify functions for how to modify the request to the inner schema rather than just hardcoding a fragment.

With ExtendSchema, you pass a fieldNodeTransformerMap to the transform to specify how to modify the fieldNodes in the request and then you define your own resolvers to do what you want.

In the examples in my tests, what I set up is wrapping/extracting fields, and so basically I am not adding extra functionality to the schema, just moving fields/data around. The fieldNodeTransformerMap uses helper functions for wrapping and extracting selections and the resolvers use helper functions for reversing those changes so the right data returned by the inner schema goes to the right place in the outer schema.

That last part is where createMergedResolver comes in: it creates a resolver that handles a merge with path mapping between the outer and inner schema.

To recap:

  • If you already have your outer schema, but just need to delegate to a new schema, use WrapQuery or TransformQuery.
  • If you want to import types from elsewhere, you need to use type B transforms such as RenameRootFields, RenameTypes, RenameObjectFields, ExtendSchema, or WrapType.
  • If you want to just do renaming, it is probably easier to use the broken out RenameRootFields/RenameTypes/RenameObjectFields transformers.
  • If you want to add/subtract new types and map the resultant fields however many layers there are, I would suggest using ExtendSchema. The drawback is that you have to handcode at this point the new types that you want to add as well as the relationships between them. Helper functions have been provided, but is still could be automated further.
  • If you want to just wrap an existing type's fields with a new field and type, you can now use WrapType.

Finally...

Hopefully, better documentation will be forthcoming. Your issue/request has been helpful toward providing some initial docs!

@yaacovCR
Copy link
Owner

yaacovCR commented Oct 4, 2019

@terion-name, see also:
ardatan#439, ardatan#961, and
https://stackoverflow.com/questions/46759547/how-to-wrap-a-remote-schema-in-another-query-field

@terion-name
Copy link
Author

@yaacovCR thank you very much! very helpful. I've tried to understand all of this, but there is so small amount of information on the topic. Now many thing are a lot clearer, thank you!

Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
None yet
Projects
None yet
Development

No branches or pull requests

2 participants