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

feat: @defer support #898

Open
wants to merge 20 commits into
base: next
Choose a base branch
from
Open

Conversation

igrlk
Copy link
Contributor

@igrlk igrlk commented Oct 25, 2022

Hi everyone ✋ As some of you may have noticed, apollo and graphql-js added support for @defer directive, so it's time to do the same in Mercurius!

🤔 What is @defer and why do we need that?

@defer is a directive that allows you to receive query data incrementally. There is a bunch of information about it but a decent source is Apollo Docs @defer.

⌛ Current state of things

📦 What this PR does

It uses the new GraphQL incremental api (if it's available! otherwise we use old api for backwards-compatibility) to execute queries.
The incremental api itself does all the magic for us and returns either the classic response (body.data.myField) or the incremental one with stream (see more in the api type definitions)

Client should send us the following header accept: multipart/mixed; deferSpec=20220824 in order to get a stream response back. Otherwise, the error is thrown that doesn't allow client to get the data.

The PR also moves to a new signature of GraphQLError since the old one is deprecated and its support is removed in graphql@17

Additionally, it bumps the graphql-js to 17.0.0-alpha.2 (latest available right now). Once PR has all the tests passed and feedback incorporated, it's going to be cut off to a separate release branch, instead of getting merged to master directly because of graphql@17 which is still in alpha

🧰 Local development / testing

If you wish to see how it's going to look like in a real app, I've put together an Apollo Client/Server + Mercurius setup that allows you to run a query with @defer against either:

  • This Mercurius branch, to see what we've got
  • Apollo Server, to see how it works there

⚠️ Things that aren't yet done

:octocat: Please take into account that all the things I mentioned in the PR are my suggestions based on what I've read and heard. I am open to the ideas you might have, especially because it's my first PR to this repository.

I want to give a huge shoutout to the Mercurius team for their amazing hard work maintaining this project and to Apollo team who were the first in @defer implementation, and whose examples I've used to implement the changes in this repo.

Cheers ✌️

Copy link
Collaborator

@mcollina mcollina left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is a amazing on multiple level! Deferred resolver is massive new feature coming in graphql v17!

(Given that this PR is also made possible by Readable.from() and node stream iterator support... I'm even more in awe. I championed and implemented those features).

I'm a bit concerned how we could land this and let people experiment about it for now.


It's better if we put @defer under an option. This will allow to throw if jit is enabled or it's missing graphql-jit.

package.json Outdated Show resolved Hide resolved
test/directives.js Outdated Show resolved Hide resolved
'multipart/mixed; deferSpec=12345'
]

for (const accept of wrongAcceptValues) {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

move this for loop outside of the test definition

test/directives.js Outdated Show resolved Hide resolved
content-type: application/json; charset=utf-8\r
\r
{"hasNext":false,"incremental":[{"path":["allProducts",0,"delivery"],"data":{"estimatedDelivery":"25.01.2000","fastestDelivery":"25.01.2000"}}]}\r
-----\r
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can you also add a test using undici.fetch()? It should work and support multipart responses.

index.js Show resolved Hide resolved
index.js Show resolved Hide resolved
test/directives.js Outdated Show resolved Hide resolved
index.js Outdated Show resolved Hide resolved
index.js Show resolved Hide resolved
index.js Outdated
) {
// The client ran an operation that would yield multiple parts, but didn't
// specify `accept: multipart/mixed`. We return an error.
throw new Error(
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

test/defer.js Outdated
directive @defer(
if: Boolean! = true
label: String
) on FRAGMENT_SPREAD | INLINE_FRAGMENT
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Shouldn't this directive added automatically by mercurius?

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I would do this behind a defer: true option, set to false by default.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hey @mcollina I've added opts.defer, but I'm not quite sure if we really need that, since we decided to cut this off to a new release branch where we assume @defer will be available to everyone.

I can remove opts.defer and just leave its directive definition so that we always have this functionality with graphql@17.

What do you think?

@mcollina
Copy link
Collaborator

How could I ship this today and keep the current dependency at graphql@16?

@igrlk
Copy link
Contributor Author

igrlk commented Oct 26, 2022

How could I ship this today and keep the current dependency at graphql@16?

I didn't update any gql-related dependencies in package.json and the whole change is not breaking, since the new functionality only works if graphql@17 is installed.

So I think it's safe to ship this once I incorporate the feedback and CI is green. Appreciate if you can help with the things you mentioned - otherwise I'll do this later today 🙂

package.json Outdated
@@ -64,6 +64,7 @@
"graphql": "^16.0.0",
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think we are not testing this behavior in CI, right? We should. The peerDependency might not be correct.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I could try adding CI tests that install graphql@17 if you can point me to the right direction - how and where you add them?

Also, what do you mean by peerDependency not correct? It didn't change in my PR, right?

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

How would an end user use defer? As you can see we depend upon graphql.

Copy link
Contributor Author

@igrlk igrlk Oct 26, 2022

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I see. So the Apollo Server, as a reference, didn't update graphql version and they will only do that when graphql@17 is released. Until that, it's the change that is implemented and tested on CI, but not available to the end users since it's still in alpha

That being said, the idea of mine was to do the same here - start supporting it but not allow users to use it until graphql@17 is officially released

In the future, when graphql@17 is live, we will update the mercurius dependency and everyone will be able to use defer

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm happy to cut a next branch for you, so we can land this with the alpha version of graphql-js.

wdyt?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

sounds good to me! so i finish the feedback first and then you create a next release branch?

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

+1

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I incorporated the feedback and going to see what's going on there with JIT compiler when I have some time

test/defer.js Outdated
t.teardown(async () => {
await reader.releaseLock()
app.close()
process.exit()
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not sure if I wasn't iterating through the stream correctly, but the test never stops - added process.exit() to make it passing.
@mcollina can you please help here, maybe you see a problem in my code?

@igrlk
Copy link
Contributor Author

igrlk commented Oct 27, 2022

@mcollina I believe you saw my issue in graphql-jit - looking forward to hearing back from them re graphql 17 support. Should we keep this PR open until they give us more details?

@mcollina
Copy link
Collaborator

It's likely better to wait until we hear back, I might be wrong but there is no urgency to ship given graphl@17 is still in alpha.

@glasser
Copy link

glasser commented Oct 28, 2022

Apollo-Server changes are not yet in main branch, but in the next major version branch

Just to clarify: we have released support for defer/stream in Apollo Server 4 (which is now the current major version), but we are considering it experimental for now for various reasons including that it requires unreleased graphql-js.

Exciting to see Mercurius implementing it as well! Happy to see you're also requiring the client to opt in with deferSpec so that the whole ecosystem can cleanly adjust to any final changes that may show up before the GraphQL spec receives the final version.

@mcollina mcollina changed the base branch from master to next November 2, 2022 11:07
@mcollina mcollina changed the base branch from next to master November 2, 2022 11:07
@mcollina mcollina changed the base branch from master to next November 2, 2022 11:10
Copy link
Collaborator

@mcollina mcollina left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I've rebased this on top of the next branch.
We can land as soon as the tests pass

test/defer.js Outdated
const mercurius = require('../index')
const { canUseIncrementalExecution } = require('../lib/util')

if (canUseIncrementalExecution) {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This if should not be needed anymore.

@@ -187,6 +189,20 @@ const plugin = fp(async function (app, opts) {
})
}

if (opts.defer) {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Could you throw if jit is enabled with defer?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I've added an error throwing when both jit and defer are enabled & updated the docs to have defer option there and in typescript definitions

lib/util.js Outdated

// istanbul ignore next
function executeGraphql (args) {
if (canUseIncrementalExecution) {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This should happen only if defer is enabled.

lib/util.js Outdated
@@ -23,7 +26,26 @@ function hasExtensionDirective (node) {
}
}

const canUseIncrementalExecution = !!require('graphql/execution').experimentalExecuteIncrementally
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

not needed, this is always true now.

Igloczek and others added 3 commits November 2, 2022 12:11
* Add space between merged SDLs to fix merging errors

* Add unit tests for schema merging
* feat: add types for object in graphiql configuration

* feat: add test to types
@igrlk
Copy link
Contributor Author

igrlk commented Nov 2, 2022

I've rebased this on top of the next branch.
We can land as soon as the tests pass

Thanks! I've made more changes that you requested.

I'm going to check if we can get the response from graphql-jit team or I can maybe make the necessary changes in their project, since we're blocked by that.

@igrlk
Copy link
Contributor Author

igrlk commented Nov 14, 2022

I didn't get any response from graphql-jit so decided to make a PR to migrate it to graphql@17 - hopefully someone will respond on it.
zalando-incubator/graphql-jit#185

@BorisTB
Copy link

BorisTB commented Aug 10, 2023

Hi! What needs to be done here to merge this? I’d really like to use this feature so I’ll gladly help.

@mcollina
Copy link
Collaborator

I think a public release of GraphQL v17?

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

Successfully merging this pull request may close these issues.

6 participants