Added Define.TaskSeqField with streaming of IAsyncEnumerable results - #598
Open
xperiandri wants to merge 3 commits into
Open
xperiandri wants to merge 3 commits into
xperiandri wants to merge 3 commits into
Conversation
Test Results 3 files 3 suites 11m 50s ⏱️ Results for commit b0d54ed. ♻️ This comment has been updated with latest results. |
A field resolved from `IAsyncEnumerable<'T>` is enumerated into a list without directives, delivered as a whole with `@defer`, and streamed item by item with `@stream`. Streaming pulls the sequence lazily, cancels the enumeration when the subscriber disposes, and reports an enumeration error as a deferred error after the items already produced, so buffered items and sibling deferred streams are not lost. `StreamBatching` groups streamed items into batches of a fixed size or of a size computed from the sequence. The `preferredBatchSize` argument of `@stream` takes precedence. Azure `AsyncPageable<T>` does not expose its page size, so tests cover both a plain pageable and one that keeps the page size hint. The `graphql-transport-ws` middleware now sends deferred and streamed payloads immediately with `path` and `hasNext`, followed by a final `hasNext: false` payload, instead of after a fixed 5 second delay. It no longer casts payloads to a dictionary and no longer drops initial payload errors. `SubscriptionExecutionResult.Data` became `obj Skippable` and the record got `Path` and `HasNext`. `FSharp.Data.GraphQL.Shared` references `Microsoft.Bcl.AsyncInterfaces` for `netstandard2.0`. The Star Wars sample got a `Human.friendsStream` field. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
xperiandri
force-pushed
the
task-seq-field
branch
from
September 14, 2026 23:32
9dda1df to
764cc07
Compare
There was a problem hiding this comment.
🟡 Changes recommended
Stream failures can overtake prior items, batching callbacks run for non-streamed queries, and item resolution concurrency is unbounded.
Get a fresh assessment by requesting another Copilot review.
Pull request overview
Adds native IAsyncEnumerable field resolution, streaming/batching support, and incremental WebSocket delivery.
Changes:
- Introduces
Define.TaskSeqFieldand configurable stream batching. - Adds async-enumerable execution, cancellation, and error handling.
- Updates WebSocket payloads, documentation, samples, and tests.
File summaries
| File | Description |
|---|---|
TaskSeqFieldTests.fs |
Tests async sequence fields and streaming. |
Helpers.fs |
Adds a suspending async-enumerable test helper. |
ObservableExtensionsTests.fs |
Tests async-enumerable observables. |
FSharp.Data.GraphQL.Tests.fsproj |
Adds test dependencies and source file. |
SerializationTests.fs |
Tests incremental payload serialization. |
WebSockets.fs |
Expands WebSocket execution payloads. |
TypeSystem.fs |
Adds async-sequence resolver and batching types. |
SchemaDefinitionsExtensions.fs |
Rejects unsupported resolver middleware. |
SchemaDefinitions.fs |
Adds TaskSeqField overloads. |
FSharp.Data.GraphQL.Shared.fsproj |
Adds async-interface compatibility dependency. |
ObservableExtensions.fs |
Adds async-enumerable observable adapters. |
Execution.fs |
Executes and streams async sequence items. |
ErrorMessages.fs |
Updates enumerable type error text. |
GraphQLWebsocketMiddleware.fs |
Sends incremental payloads immediately. |
star-wars-api.fsproj |
Adds TaskSeq dependency. |
Schema.fs |
Demonstrates streamed friends. |
RELEASE_NOTES.md |
Documents features and breaking changes. |
Packages.props |
Centrally versions new dependencies. |
docs/type-system.md |
Documents asynchronous sequence fields. |
Review details
- Files reviewed: 19/19 changed files
- Comments generated: 3
- Review effort level: Balanced
💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.
Comment on lines
+366
to
+369
| | System.Reactive.NotificationKind.OnError -> Observable.singleton (StreamFailure notification.Exception) | ||
| | _ -> Observable.empty) | ||
| // Each item is emitted as soon as its own fields are resolved | ||
| |> Observable.mergeInner |
Comment on lines
+368
to
+370
| // Each item is emitted as soon as its own fields are resolved | ||
| |> Observable.mergeInner | ||
| |> buffer |
Comment on lines
+2439
to
+2441
| match applyBatching policy boxedSource with | ||
| | ValueSome size -> AsyncEnumerableFieldValue<'U> (source, preferredBatchSize = size) |> box | ||
| | ValueNone -> AsyncEnumerableFieldValue<'U> (source) |> box |
`BufferedStreamOptions.Interval` and `BufferedStreamOptions.PreferredBatchSize` are now `int voption`, so the batch size computed for a `Define.TaskSeqField` sequence is used without converting between `option` and `voption`. The `@stream` planning and buffering code and the stream event filtering follow. `Define.Input` still takes the default value of a `Nullable IntType` argument as `int option`. The optional callbacks of the `TestObserver` and `SuspendingAsyncEnumerable` test helpers are struct optional parameters. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
The tests checking that disposing a subscription stops the enumeration blocked the test thread with `Thread.Sleep` and `ManualResetEventSlim.Wait`. They now return `Task`, await `TaskCompletionSource` signals through the new `waitForTask` helper, which fails the test with a message on timeout, and wait with `Task.Delay`. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
xperiandri
force-pushed
the
task-seq-field
branch
from
September 15, 2026 00:06
d0a9fb8 to
b0d54ed
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.
Summary
Adds native support for list fields resolved from
IAsyncEnumerable<'T>, such astaskSeq { }, C# async iterators or Azure SDKAsyncPageable<T>, and makes@defer/@streamresults usable overgraphql-transport-ws.Define.TaskSeqField@deferNullable (ListOf …)overloads, which are included)@streamseqtoday: nullable field →null+ field error, non-nullable → error propagates;@streamit is delivered asDeferredErrorsfor the field after the items already produced, so buffered items and sibling deferred streams are not lost.@streamon ordinary lists keeps its behavior.Batching of streamed items
StreamBatching<'Item>is set on the field:StreamBatching.Fixed sizeStreamBatching.FromSource (IAsyncEnumerable<'Item> -> int voption)computes the size from the resolved sequence instance.The
preferredBatchSizeargument of@streamtakes precedence over the field-level policy.Note: Azure
AsyncPageable<T>does not expose a page size (it is only a hint passed toAsPages), soFromSourcecan batch by pages only when the application keeps the hint, for example in a subclass ofAsyncPageable<T>. Tests cover both a plainAsyncPageable<T>and one that keeps the hint.WebSocket (
graphql-transport-ws) deliverypathandhasNext; the initial payload hashasNext: true, and a final{"hasNext": false}payload precedescomplete.Dictionary<string, obj>, which failed for streamed items and scalars.Example with the Star Wars sample (
Human.friendsStreamwas added to demonstrate@stream):Breaking changes
SubscriptionExecutionResult.Datais nowobj Skippable, and the record has newPathandHasNextfields. Use theCreate,CreateErrors,CreateInitial,CreateIncrementalandCreateCompletedfactory members.BufferedStreamOptions.IntervalandBufferedStreamOptions.PreferredBatchSizeare nowint voption. Construct the options withValueSome/ValueNone, for exampleSchemaConfig.DefaultWithBufferedStream { Interval = ValueSome 2000; PreferredBatchSize = ValueNone }.Dependencies
FSharp.Data.GraphQL.SharedreferencesMicrosoft.Bcl.AsyncInterfacesfornetstandard2.0only.FSharp.Control.TaskSeqandAzure.Coreexplicitly.SDK
devnow pins SDK10.0.303instead of10.0.401. The F# compiler in SDK10.0.4xx(verified on10.0.400and10.0.401) compiles resumable state machines incorrectly in Debug, so ataskSeq { }that awaits returns no further items: dotnet/fsharp#20466, fixed inmainby dotnet/fsharp#20469 but not shipped yet (also fsprojects/FSharp.Control.TaskSeq#473).10.0.303is the latest SDK without the regression. The--always-inline+workaround andOptimize=truealone did not help on10.0.401.The code in this PR still builds with SDK
10.0.4xx. Passing avoptionto a struct optional parameter through?name = valueis supported only by the10.0.4xxcompiler, so that call is written as a match.Known limitations
taskSeq { }block usinglet!oryield!must live in a separate function called from the resolver. This is documented.WithResolveMiddlewareis not supported forTaskSeqFieldand throwsNotSupportedException.10.0.4xxin Debug hit the compiler regression above in their owntaskSeq { }blocks. Tests that need a real await use a hand-written suspendingIAsyncEnumerable, so they pass on any SDK.Testing
TaskSeqFieldTests: draining,@defer,@streamordering and early delivery, query/fixed/source batching and precedence, enumeration errors (nullable, non-nullable, streamed), cancellation,nullsequence, AzureAsyncPageable<T>.ObservableExtensionsTestsforofAsyncEnumerableandwithCompletionMarker;SerializationTestsfor the new WebSocket payload shape.10.0.303: unit tests 623 passed, 0 failed, 5 skipped (already skipped ondev); Release builds of all library projects succeed with warnings as errors; the Star Wars sample builds.FSharp.Data.GraphQL.Sharedalso builds in Release on SDK10.0.401.🤖 Generated with Claude Code