Skip to content

Add @defer on fragment spreads and inline fragments - #615

Merged
xperiandri merged 4 commits into
devfrom
incremental-delivery-fragment-defer
Sep 21, 2026
Merged

xperiandri merged 4 commits into
devfrom
incremental-delivery-fragment-defer

Conversation

@xperiandri

Copy link
Copy Markdown
Collaborator

Summary

Adds @defer on fragment spreads and inline fragments, the last piece of the incremental delivery specification v0.2 missing after #609, on top of the incremental-delivery-v02 branch.

  • A deferred fragment is planned as the new ResolveDeferredFragment kind: its fields stand among the fields of the object containing them, are resolved together against that object once the object's own fields were delivered, and go out as one payload announced at the object's path with the fragment's label.
  • The engine reports fragments through three new events, DeferredFragmentPending, DeferredFragmentResult and DeferredFragmentCompleted; the graphql-transport-ws translator delivers them as pending/incremental/completed entries with the fragment's fields as the data object map.
  • Spec behaviours covered by tests: a field also selected directly on the object is executed with it and left out of the fragment; a fragment spread twice at the same place is delivered once; two labeled fragments at the same path get distinct ids; a fragment at the operation root and a fragment on an abstract type (... on C @defer) work; an error propagating up to the fragment completes it with the errors and no data; a fragment left without fields delivers nothing.
  • Fixes validation of an inline fragment without a type condition (... { … }), which threw ValueOption.Value instead of applying to the parent type.

Review scope

Please review only fragment-level @defer: Planning.fs, the fragment parts of Execution.fs, IncrementalDelivery.fs and the new tests. The lock-free middleware, directive arguments and validation rules are reviewed in #609.

Dependency chain: #609 → this PR.

Verification

  • FSharp.Data.GraphQL.Tests: 751 passed, 5 skipped (pre-existing), 0 failed.
  • FSharp.Data.GraphQL.IntegrationTests WebSocketTests (real socket through WebApplicationFactory): 3 passed.
  • dotnet build FSharp.Data.GraphQL.slnx succeeds.

🤖 Generated with Claude Code

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Copilot review overview

🟡 Changes recommended

Fragment condition handling, overlapping selections, spread ordering, and deferred root argument coercion have correctness issues.

Get a fresh assessment by requesting another Copilot review.

Review effort: Balanced
Findings: 4 Medium severity · 1 Low severity

Open (5)
What changed in this PR

Adds fragment-level @defer planning, execution events, transport translation, validation fixes, and coverage.

Changes:

  • Introduces deferred-fragment execution plans and events.
  • Translates fragment events into incremental WebSocket payloads.
  • Adds validation fixes, tests, and documentation.
File Description
RELEASE_NOTES.md Documents fragment defer support.
docs/​type-system.md Describes fragment delivery behavior.
src/​FSharp.Data.GraphQL.Shared/​TypeSystem.fs Adds the deferred-fragment plan kind.
src/​FSharp.Data.GraphQL.Shared/​Validation.fs Fixes typeless inline-fragment validation.
src/​FSharp.Data.GraphQL.Server/​Execution.fs Executes deferred fragments.
src/​FSharp.Data.GraphQL.Server/​IO.fs Adds fragment delivery events.
src/​FSharp.Data.GraphQL.Server/​Linq.fs Tracks deferred-fragment fields.
src/​FSharp.Data.GraphQL.Server/​Planning.fs Plans and groups deferred fragments.
src/​FSharp.Data.GraphQL.Server.Middleware/​MiddlewareDefinitions.fs Includes fragment fields in query weight.
src/​FSharp.Data.GraphQL.Server.AspNetCore/​GraphQLRequestHandler.fs Logs fragment events.
src/​FSharp.Data.GraphQL.Server.AspNetCore/​IncrementalDelivery.fs Translates fragment events into payloads.
src/​FSharp.Data.GraphQL.Server.AspNetCore/​SubscriptionPayloads.fs Absorbs fragment announcements.
tests/​FSharp.Data.GraphQL.Tests/​DeferredTests.fs Covers fragment execution behavior.
tests/​FSharp.Data.GraphQL.Tests/​Helpers.fs Filters fragment lifecycle events.
tests/​FSharp.Data.GraphQL.Tests/​AspNetCore/​IncrementalDeliveryTests.fs Tests fragment payload translation.
tests/​FSharp.Data.GraphQL.Tests/​AspNetCore/​IncrementalDeliveryEndToEndTests.fs Tests end-to-end delivery.

💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.

Comment thread src/FSharp.Data.GraphQL.Server/Execution.fs
Comment thread src/FSharp.Data.GraphQL.Server/Planning.fs Outdated
Comment thread src/FSharp.Data.GraphQL.Server/Planning.fs Outdated
Comment thread src/FSharp.Data.GraphQL.Server/Planning.fs Outdated
Comment thread RELEASE_NOTES.md Outdated
Comment thread src/FSharp.Data.GraphQL.Server.AspNetCore/GraphQLRequestHandler.fs Outdated
Comment thread src/FSharp.Data.GraphQL.Server/Planning.fs
Comment thread src/FSharp.Data.GraphQL.Server/Planning.fs Outdated
Comment thread src/FSharp.Data.GraphQL.Server/Planning.fs Outdated
Comment thread src/FSharp.Data.GraphQL.Server/Planning.fs Outdated
Comment thread src/FSharp.Data.GraphQL.Shared/TypeSystem.fs Outdated
@xperiandri
xperiandri force-pushed the incremental-delivery-fragment-defer branch 3 times, most recently from fb49cbd to 77a7531 Compare September 21, 2026 00:44
@xperiandri
xperiandri requested a balanced review from Copilot September 21, 2026 01:13
Base automatically changed from incremental-delivery-v02 to dev September 21, 2026 01:14
xperiandri and others added 3 commits September 21, 2026 03:14
`... { fields }` applies to the parent type; validation used to read the
absent type condition with `ValueOption.Value` and throw, and left the
fragment's fields out of the validation context.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
A deferred fragment is planned as the new `ResolveDeferredFragment` kind:
its fields stand among the fields of the object containing them, are
resolved together against that object once the object's own fields were
delivered, and go out as one payload announced at the object's path with
the fragment's label. The engine reports fragments through the new
`DeferredFragmentPending`, `DeferredFragmentResult` and
`DeferredFragmentCompleted` events, and the `graphql-transport-ws`
translator delivers them as `pending`/`incremental`/`completed` entries
with the fragment's fields as the `data` object map.

A field also selected directly on the object is executed with it and left
out of the fragment; a fragment spread twice at the same place is delivered
once; two labeled fragments at the same path get distinct ids; fragments at
the operation root and on abstract types are supported; an error that
propagates up to the fragment completes it with the errors and no data.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
- `ResolveDeferredFragment` carries the directive's `if` condition, so a
  fragment disabled through a variable is resolved with the object, at
  the root too, instead of always being deferred.
- A fragment selecting under a field the object selects directly adds
  that selection to the direct field instead of losing it.
- A fragment spread directly is resolved with the object whichever of
  its spreads comes first: direct and deferred spreads are tracked
  apart, as graphql-js does.
- The root fields of a deferred root fragment are coerced up front, so
  an argument they reject fails the request before any resolver runs.
- Review suggestions applied: `String.Join` over the path instead of
  materializing it for `Path.Join`, `List.vchoose`, `yield!`, entries
  added to the abstraction map directly, the `StringBuilder` pipeline,
  XML comments on the planning helpers, and the fragment release note
  marked as a breaking change.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
@xperiandri
xperiandri force-pushed the incremental-delivery-fragment-defer branch from 77a7531 to 81c8fde Compare September 21, 2026 01:15

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Copilot review overview

🟡 Changes recommended

Deferred-fragment ordering, conditional inclusion, and mutation-root validation contain unresolved correctness issues.

Get a fresh assessment by requesting another Copilot review.

Review effort: Balanced
Findings: 2 High severity

Open (2)
Resolved since last review (5)

Comment thread src/FSharp.Data.GraphQL.Server/Planning.fs
Comment thread src/FSharp.Data.GraphQL.Server/Planning.fs Outdated
A deferred fragment standing before the field it selects under now adds
its selection to that field too: the object's own fields are collected
first and the fragments' overlapping selections are merged into them
afterwards, so the result does not depend on document order.

The deferred fragment entry carries the includer of its spread or inline
fragment, so `@skip`/`@include` on the fragment exclude it at the root
and inside any object.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Copilot review overview

🟢 Approval recommended

The fragment-level defer implementation is coherent across planning, execution, transport translation, validation, and focused tests, with prior findings addressed.

Review effort: Balanced
Findings: None

Resolved since last review (2)

@github-actions

Copy link
Copy Markdown

Test Results

    9 files      9 suites   15m 8s ⏱️
  877 tests   872 ✅  5 💤 0 ❌
2 631 runs  2 616 ✅ 15 💤 0 ❌

Results for commit 1e1c4e0.

@xperiandri
xperiandri merged commit 401874b into dev Sep 21, 2026
6 checks passed
@xperiandri
xperiandri deleted the incremental-delivery-fragment-defer branch September 21, 2026 01:35
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