Skip to content

Conversation

carldunham
Copy link

@carldunham carldunham commented Feb 18, 2025

Pretty rough. Still need to clean up tests and do real-world testing.

@stream should be an easy add-on. Will need to consider things like holding the concurrency token, etc.

defer close(ch)
go func() {
buf := &bytes.Buffer{}
if _, err := r.ResolveGraphQLResponse(ctx, response.DeferredResponse, nil, buf); err != nil {
Copy link
Member

Choose a reason for hiding this comment

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

This assumes that there's just one single deferred Response.

executor chan func()
}

func (r *Resolver) ResolveGraphQLIncrementalResponse(ctx *Context, response *GraphQLIncrementalResponse, writer io.Writer) error {
Copy link
Member

Choose a reason for hiding this comment

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

I'd create an interface as writer, e.g. IncrementalResponseWriter with 3 funcs, write, flush, complete. This way, you don't have to think about boundaries or transports here. Just write a JSON and flush it. Let the implementer of the writer adapt the transport, e.g. by adding a boundary after flush.

}

if streaming && operationKind != ast.OperationTypeQuery {
v.Walker.StopWithExternalErr(operationreport.ErrDeferStreamNotAllowedOutsideQuery())
Copy link
Member

Choose a reason for hiding this comment

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

What's wrong with having defer on Mutations?


func (v *Visitor) LeaveFragmentSpread(ref int) {
v.debugOnLeaveNode(ast.NodeKindFragmentSpread, ref)
v.inDeferredFragment = false
Copy link
Member

Choose a reason for hiding this comment

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

If you walk into a deferred fragment spread, then inside another fragment spread, then out of the nested spread, you're still in the parent spread but your logic now says we're out because we left the nested one.

One solution we use for such a problem is to create a map of fragment spread ref's that are deferred.
When you enter, you add it, when you leave, you verify if you left a deferred one or not.

The problem now is that when you leave a fragment spread, you need to know if this was already deferred by a parent. So this map could contain the ref of the fragment spread as the key, and a struct with information if we're inside a deferred parent. Then we can properly set if we're within a deferred fragment.

There's just a question of how valuable is the information that we're inside a deferred fragment when they can be nested. Maybe we need to also know in which deferred fragment we are, but I haven't digged enough into the code.

Copy link
Author

Choose a reason for hiding this comment

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

Yeah, at this point I'm reworking this as a stack. It doesn't look like we need anything that isn't available when we leave, but I haven't addressed label or if yet.

Copy link
Member

Choose a reason for hiding this comment

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

You don't have to worry about "if". We can do the same thing like we're doing with include and skip, we "normalize" defer out when if evaluated to false.

for _, deferred := range response.DeferredResponses {
g.Go(func() error {
buf := &bytes.Buffer{}
if _, err := r.ResolveGraphQLResponse(ctx, deferred.ImmediateResponse, nil, buf); err != nil {
Copy link
Member

Choose a reason for hiding this comment

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

I'd rewrite this. If you look a bit into the code, you'll see that we create a "tools" struct, which contains a "resolvable" which contains "data". We can keep this tools throughout all steps and do the following flow:

  1. resolve the initial data
  2. print and flush the initial response
  3. iterate over the incremental response parts
  4. for each part, use the same tools to "add" more data to it
  5. once the data was loaded with the Loader, we render it into the writer and flush
  6. repeat for all incremental parts until all data is loaded and resolved

The "missing" link is that we need to split the "resolve" part into incremental resolve objects, always keeping a reference path to where in the "data" object this incremental part should render from.
In addition, each incremental part needs to contain information on how to render the incremental meta information.

@carldunham
Copy link
Author

Closing in favor of #1108

@carldunham carldunham closed this Mar 12, 2025
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.

2 participants