js-graphql-intellij-plugin
js-graphql-intellij-plugin copied to clipboard
[Feature Request] Apollo Federation Support
Apollo Federation makes uses of some custom directives:
https://www.apollographql.com/docs/apollo-server/federation/federation-spec/
Version and Environment Details
Operation system: macOS
IDE name and version: IntelliJ IDEA Ultimate 2019.2
Plugin version: 2.2.0
Expected Behaviour
-
The following directives should not produce errors:
directive @external on FIELD_DEFINITION directive @requires(fields: _FieldSet!) on FIELD_DEFINITION directive @provides(fields: _FieldSet!) on FIELD_DEFINITION directive @key(fields: _FieldSet!) on OBJECT | INTERFACE directive @extends on OBJECT | INTERFACE(These directives must NOT be in the GraphQL file itself)
-
An
extend type TypeNameshould be allowed to exist without the base type, as the base type may be defined by another service
Actual Behaviour
- Error:
Unknown directive "XXX" - Error:
The extension 'TypeName' type [@x:x] is missing its base underlying type
Steps to Reproduce / Link to Repo with Reproduction and Instructions
type InternalType @key(fields: "id") {
id: ID!
xyz: String!
externalObject: ExternalType @requires(fields: "xyz") @provides(fields: "foo")
}
# In reality you need EITHER extend OR @extends
# depending on which server this schema is defined in
# (Apollo Server allows extend, graphql-java requires @extends)
extend type ExternalType @key(fields: "id") @extends {
id: ID! @external
foo: String! @external
bar: String! @external
baz: String!
}
Hi Nihal.
Thanks for using the plugin.
Like you mention, these directives are custom, and the plugin only ships with the directives that are described in the GraphQL spec. This is intentional, as new GraphQL frameworks appear all the time. For this reason you have to declare framework directives yourself, and ensure that they're picked up as part of schema discovery. You can look at an example at https://github.com/jimkyndemeyer/graphql-config-examples/tree/master/extend-client-fields-and-directives which shows how to work with the @client Apollo directive.
Extending non-existing types violates the GraphQL Spec https://graphql.github.io/graphql-spec/June2018/#sec-Object-Extensions validation "The named type must already be defined and must be an Object type." As such, graphql-java which this plugin is based on works correctly, so I'm not sure how we would go about adding support for this. Potentially the validation needs to be configurable as it makes sense to enforce it for other use cases.
I noted one interesting comment in your code example:
# (Apollo Server allows extend, graphql-java requires @extends)
If you declared your directives as expected, and used @extends I believe the plugin would work as is.
Best regards, Jim
Thanks for the detailed response! I completely understand that you wouldn't want to add these directives by default.
However, since Apollo Federation has gathered quite a bit of support (including graphql-ruby, graphql-java, graphql-kotlin, gqlgen (go), graphene (python), and graphql-dotnet), I believe it would make sense to integrate this here so that users can easily build Federated schemas.
Regarding the extend: while building an API with graphql-java, this is fine as you have to use @extends, however, Apollo Server users would use extend type for Federation, so it would be nice to have the option to turn off the base type validation in this case. I suppose this would however involve contributing to graphql-java and making it possible to turn off this validation here: SchemaTypeChecker.java#L74
Looking at the examples of how to configure and extend the plugin (https://github.com/jimkyndemeyer/graphql-config-examples) perhaps a boilerplate example for Apollo Federation would help people get started.
If you declared your directives as expected, and used @extends I believe the plugin would work as is.
Perhaps I'm doing something wrong, but declaring a type in service A and then extending it using @extends (which is also supported by Apollo) in service B does result in an error:
Service A
type Foo @key(fields: "id") {
id: ID!
}
Service B
type Foo @extends @key(fields: "id") {
id: ID! @external
}
Directives
directive @key(fields: _FieldSet!) on OBJECT | INTERFACE
directive @extends on OBJECT | INTERFACE
'Foo' type [@66:5] tried to redefine existing 'Foo' type [@13:5]
EDIT:
As a workaround I made some slight changes to my structure and .graphqlconfig. I created a directory remote where I define extended remote types using the @extend directive. The rest of the service uses the extend keyword to add additional fields. In .graphqlconfig I exclude the remote directories using **/remote/**.
@quintstoffers some work has been done recently in this PR https://github.com/jimkyndemeyer/js-graphql-intellij-plugin/pull/374 in 2.6.0, and we plan to continue our work on the schema splitting in different scenarios, but that's a huge amount of work. At this point, a dependency on the underlying graphql-java library is more a problem, than a time-saver.
One of the next steps I believe would be disabling this kind of error 'Foo' type [@66:5] tried to redefine existing 'Foo' type [@13:5] (there are also more possible "something duplicated" errors) and implementing native inspections, which could be configurable and will honor semantics of different frameworks.
For those looking Federation 1 directives can now be toggled on with the new setting: https://github.com/JetBrains/js-graphql-intellij-plugin/pull/499
Federation 2 support and more importantly @link is not yet enabled: https://github.com/JetBrains/js-graphql-intellij-plugin/issues/627
It looks like Federation 2 support is included in plugin version 3.4.0! Make sure to enable the setting in Languages & Frameworks > GraphQL, there's a checkbox called "Federation" (or just search settings for "Federation").
It would be great if the fields attribute in @requires / @provides and @key had syntax highlighting and auto-completion in the editor. E.g. here:
type A {
foo: String
}
type B {
a: A
bar: String @requires(fields:
"""
a {
foo
}
""")
}
fields content should should be navigable / auto-completable like a GraphQL query.