-
Notifications
You must be signed in to change notification settings - Fork 537
Support for experimental @defer & @stream #583
Conversation
looks great, thanks @robrichard! once we get one of the graphql-js proposals merged for 15.0.0 we can revisit the tests here. saw there are two proposal PRs for graphql-js |
2492b68
to
8dc8565
Compare
46b3ff3
to
3505ce2
Compare
src/index.ts
Outdated
if (isAsyncIterable(executeResult)) { | ||
response.setHeader('Content-Type', 'multipart/mixed; boundary="-"'); | ||
sendPartialResponse(pretty, response, formattedResult); | ||
for await (let payload of executeResult) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@robrichard What happens when the request is terminated by the client (i.e. I navigate away from the page or close the browser)? Would it make sense to add an event listener to response for the close
event and call the AsyncIterator's return
method inside?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yes, I think that makes sense. I will implement that.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I added a handler for the close event to call return
on the underlying async iterable.
@IvanGoncharov this required disabling some lint rules (see d883ae7#diff-a2a171449d862fe29692ce031981047d7ab755ae7f84c707aef80701b3ea0c80R419). Do you think this is ok or do you have a better way to implement this?
7c2e919
to
06f9ded
Compare
I got a bit too excited so I created a playground making it easier for people to test this new stuff! https://github.com/n1ru4l/graphql-bleeding-edge-playground Awesome work!!! |
@n1ru4l awesome! it looks great in graphiql as well. we are going to archive playground soon though :( |
ah wait it is graphiql 😂 playground ist just the name of the repo it uses graphiql :D Please check out the GraphiQL fetchers 😇 |
@n1ru4l oh awesome I see, thanks for that! did you see this? graphql/graphiql#1700 (comment) |
@n1ru4l are you interested in helping me create this PR in graphiql monorepo for the new fetcher patterns? Otherwise I might not have time until next weekend :/ |
@robrichard Would it be possible for you to do single commits instead of force-pushing, I am having a hard time following the changes 😅 I also updated the playground to the latest commit. |
I've been force pushing because it's harder to keep rebasing when there are a lot of commits and this PR has been open for a long time. |
src/index.ts
Outdated
@@ -344,16 +372,18 @@ export function graphqlHTTP(options: Options): Middleware { | |||
}); | |||
} | |||
|
|||
// Collect and apply any metadata extensions if a function was provided. | |||
// https://graphql.github.io/graphql-spec/#sec-Response-Format | |||
if (isAsyncIterable(executeResult)) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Since executeFn is typed to return an AsyncIterableIterator
, it would make sense to instead use a isAsyncIterableIterator
function. That way it is also possible to call executeResult.return?.()
in case the request is aborted without adding ts-ignores.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Nevermind, if you use the isAsyncIterable function the type checker already is aware of the actual type (AsyncIterableIterator
), making it easy to call .return()
It would also make sense to adjust the |
src/index.ts
Outdated
// Get first payload from AsyncIterator. http status will reflect status | ||
// of this payload. | ||
const asyncIterator = getAsyncIterator<ExecutionResult>(executeResult); | ||
const { value } = await asyncIterator.next(); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
await
can throw and should be inside try
🔼
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
test for that would be great. You can do that by using custom executeFn
that return an async iterator.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@IvanGoncharov do you think it makes sense for the try/catch
that is currently wrapping await executeFn({...})
to be expanded to also wrap this?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@robrichard Yes, I think it would work 👍
df074c9
to
aa62e24
Compare
result: FormattedExecutionResult | FormattedExecutionPatchResult, | ||
): void { | ||
const json = JSON.stringify(result, null, pretty ? 2 : 0); | ||
const chunk = Buffer.from(json, 'utf8'); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
will this always be utf8?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
this has me wanting to bring up protocolbuffers, messagepack and other binary formats, as well as yaml, however that seems way out of scope for this PR, and should probably begin as an HTTP transport RFC proposal. GraphiQL users have on multiple occasions requested support for these formats, especially for server-to-server transmissions and iot
back to this discussion, though, would it work to use a binary encoding format latin1
with JSON? I've never tried that myself. if someone was returning a binary format like BSON for example, then we should be returning content type of application/bson
, right? Is there a case where someone would be sending application/json
and not using utf8
encoding?
if there are indeed more ways to mix and match charset and content type with JSON than I'm aware of, maybe this should be controlled by the request headers then, and fall back to utf8
as default?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Worth noting that the transport RFC spec doesn't require utf8
necessarily (yet):
https://github.com/graphql/graphql-over-http/blob/master/rfcs/IncrementalDelivery.md
and the reference client implementation expects utf8
:
https://github.com/relay-tools/fetch-multipart-graphql/blob/dfa54d0c649ecbbedac3e9764998f63894367505/src/fetch.js#L26
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
meros also at this time uses UTF8. It's a force of habit. But there might be people out there needing some out of the ordinary encodings.
Maybe it's a cross that bridge when we get there?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
express-graphql already only sends utf8 so I don't think there's any change in behavior here: https://github.com/graphql/express-graphql/blob/master/src/index.ts#L531
As far as the spec, I'm not sure if it's worth specifying what encodings are required. FWIW it seems like JSON requires either UTF-8, UTF-16, or UTF-32 but recommends UTF8 for maximum interoperablity.
Wish github had stacked PR's 😔 robrichard#1 |
b060480
to
d883ae7
Compare
78fdee3
to
e303a2c
Compare
e303a2c
to
ce8429e
Compare
Support for streaming multi-part http responses as implemented by
graphql/graphql-js#2319
https://github.com/relay-tools/fetch-multipart-graphql