Support @defer and @stream in the e2e test suite - #5409
Draft
captbaritone wants to merge 1 commit into
Draft
captbaritone wants to merge 1 commit into
captbaritone wants to merge 1 commit into
Conversation
captbaritone
force-pushed
the
e2e-incremental-delivery
branch
from
September 2, 2026 21:09
d74e3f5 to
c496d26
Compare
Incremental delivery did not work in the markdown e2e fixtures, and both
reasons it did not work were silent rather than loud.
The Relay compiler does not define `@defer`/`@stream`; it reads them off
the schema, and it has to be the *server* schema. Grats has no reason to
emit them -- they are executor-level, not part of anyone's resolvers --
so `runFixture` now appends the two definitions to the generated
`schema.graphql` between the Grats and relay-compiler steps. Supplying
them through `schemaExtensions` instead does compile, but
`skip_client_directives` then strips the directive out of the printed
query text as a client-only directive, so the server is never asked to
defer. The field comes back inline while the generated artifact still
holds a `Defer` node waiting for a patch, and the fixture passes with
its deferred field silently empty.
graphql-js ignored the directive regardless: `collectFields` checks
`schema._enableDeferStream` and returns before looking at it. The flag
only takes effect through the `GraphQLSchema` constructor and does not
round-trip through `toConfig()`, so `GratsNetwork` re-wraps the Grats
schema to turn it on.
`GratsNetwork` also drops graphql-js's trailing `{hasNext: false}`
payload, which carries neither `data` nor `errors` and so is not a
`GraphQLResponse`. Relay reported it as "No data returned for operation"
and logged a warning. Nothing is lost by dropping it: Relay learns the
request finished when the observable completes. `@defer` rarely hits
this because graphql-js folds `hasNext: false` into the final patch,
but `@stream` emits the trailing payload every time.
Three fixtures. `defer/basic` covers a deferred fragment rendering once
its patch is normalized. `defer/suspense-fallback` asserts on the
pending state, which needs a resolver held open by the client rather
than a bare `async` resolver -- otherwise the fallback settles a
microtask later and the assertion is a race. `stream/list-field` keeps
the other half of the schema flag honest and is what caught the
trailing-payload bug.
captbaritone
force-pushed
the
e2e-incremental-delivery
branch
from
September 2, 2026 21:12
c496d26 to
767a804
Compare
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Adds
@defer/@streamsupport to the markdown-driven e2e suite, plus three fixtures covering it.Incremental delivery did not work in these fixtures, and neither reason it did not work was loud.
The compiler stripped
@deferfrom the query textrelay-compiler does not define
@defer/@stream— it reads them off the schema, and it has to be the server schema. Grats has no reason to emit them (they are executor-level, not part of anyone's resolvers), sorunFixturenow appends the definitions to the generatedschema.graphqlbetween the Grats and relay-compiler steps.Supplying them through
schemaExtensionsinstead does compile, which is the trap:skip_client_directivesthen strips the directive out of the printed query text as a client-only directive, so the server is never asked to defer. The field comes back inline while the generated artifact still holds aDefernode waiting for a patch that never arrives, so the field reads as missing. The fixture passes with its deferred content silently empty.graphql-js ignored the directive regardless
collectFieldschecksschema._enableDeferStreamand returns before it ever looks at@defer. The flag only takes effect through theGraphQLSchemaconstructor and does not round-trip throughtoConfig(), soGratsNetworkre-wraps the Grats schema to turn it on.Trailing payload
GratsNetworkalso drops graphql-js's closing{hasNext: false}payload, which carries neitherdatanorerrorsand so is not aGraphQLResponse. Relay reported it asNo data returned for operationand logged a warning. Nothing is lost: Relay learns the request finished when the observable completes.@deferrarely hits this — graphql-js foldshasNext: falseinto the final patch when it knows that patch is last — but@streamemits the trailing payload every time.Fixtures
defer/basicdefer/suspense-fallbackstream/list-fieldFixtures need no per-fixture setup now — spread with
@deferand give the field anasyncresolver.defer/suspense-fallbackuses a technique worth flagging in review: anasyncresolver settles a microtask later, so asserting on the pending state would be a race. Insteadserver.tsexports a plain non-schemareleaseBio()that a button calls, since fixture server and client share a module realm. That makeswait "Loading bio..."deterministic. Documented in the writing guide.@streamis included because the graphql-js flag enables both; declaring only@deferwould leave half of it enabled and untested. The stream fixture is what caught the trailing-payload bug.Test plan
yarn test:e2e— the three new fixtures pass, and the rest of the suite is unchanged by the harness edits (verified by stashing them and re-running).Four fixtures (
errors/nested-catch-field-error{,-flag-enabled},pagination/root-catch-{load-next,refetch}) fail in my working tree, but they fail identically without these changes — unrelated in-progress local work, not caused by this PR and not touched by it.