incremental delivery without branching
At a glance
- Identifier: #1023
- Stage: RFCX: Closed 2023-05-21T13:16:57Z
- Champion: @yaacovCR
- PR: incremental delivery without branching
- Related:
- #742 (Spec edits for @defer/@stream)
Timeline
- Spec PR created on 2023-03-24 by yaacovCR
- Commit pushed: CollectFields does not require path or asyncRecord (#11) on 2023-01-16 by @yaacovCR
- Commit pushed: replace server with service on 2023-01-15 by @robrichard
- Commit pushed: fix typo on 2022-12-05 by @robrichard
- Commit pushed: fix parentRecord argument in ExecuteStreamField (#7) on 2022-11-29 by @yaacovCR
- Commit pushed: spec edits for incremental delivery without branching on 2022-11-27 by @yaacovCR
- Commit pushed: clarify label is not required on 2022-11-23 by @robrichard
- 2 commits pushed on 2022-11-22:
- Commit pushed: Add error handling for stream iterators (#5) on 2022-11-21 by @yaacovCR
- Commit pushed: fix typos (#6) on 2022-11-18 by @yaacovCR
- Commit pushed: remove ResolveFIeldGenerator (#4) on 2022-11-16 by @yaacovCR
- Commit pushed: small fixes (#3) on 2022-11-07 by @yaacovCR
- Commit pushed: update on hasNext on 2022-11-01 by @robrichard
- Commit pushed: link to note on should on 2022-10-12 by @robrichard
- 3 commits pushed on 2022-09-09:
- 11 commits pushed on 2022-09-08:
- Add error boundary behavior by @robrichard
- defer/stream response => payload by @robrichard
- event stream => response stream by @robrichard
- link to path section by @robrichard
- use case no dash by @robrichard
- remove "or null" by @robrichard
- add detailed incremental example by @robrichard
- update label validation rule by @robrichard
- clarify hasNext on incremental example by @robrichard
- clarify canceling of subsequent payloads by @robrichard
- Add examples for non-null cases by @robrichard
- Commit pushed: clarify null behavior of if on 2022-08-24 by @robrichard
- 2 commits pushed on 2022-08-23:
- 7 commits pushed on 2022-08-18:
- Introduce @defer and @stream. by @robrichard
- Raise a field error if initialCount is less than zero by @robrichard
- wait for parent async record to ensure correct order of payloads by @robrichard
- spec updates to reflect latest discussions by @robrichard
- Note about mutation execution order by @robrichard
- minor change for uniqueness by @robrichard
- fix typos by @robrichard
- Commit pushed: Align deferred fragment field collection with reference implementation on 2022-08-03 by @robrichard
- 3 commits pushed on 2022-06-09:
- 2 commits pushed on 2022-03-23:
- Commit pushed: Clarification on labels on 2022-03-08 by @robrichard
- Commit pushed: add validation “Defer And Stream Directive Labels Are Unique” on 2022-03-07 by @robrichard
- 2 commits pushed on 2022-02-07:
- Commit pushed: deferDirective and visitedFragments on 2022-02-02 by @robrichard
- Commit pushed: fix typo on 2022-01-21 by @robrichard
- Commit pushed: add isCompletedIterator to AsyncPayloadRecord to track completed iter… on 2021-12-30 by @robrichard
- 2 commits pushed on 2021-12-20:
- 2 commits pushed on 2021-12-06:
- Commit pushed: fix typo on 2021-11-26 by @robrichard
- Commit pushed: allow extensions only subsequent payloads on 2021-11-25 by @robrichard
- Commit pushed: clarify negative values of initialCount on 2021-11-20 by @robrichard
- Commit pushed: clarification on defer/stream requirement on 2021-11-19 by @robrichard
- Commit pushed: Update Section 3 -- Type System.md on 2021-05-15 by @robrichard
- 3 commits pushed on 2021-02-17:
These spec edits should correspond to the working implementation at https://github.com/graphql/graphql-js/pull/3862 demonstrating incremental delivery without branching.
[The diff to main might be helpful, but this is built on top of the amazing https://github.com/graphql/graphql-spec/pull/742 and so the diff from that branch could be more useful.]
Other efforts are in the works to avoid branching, specifically #1018 and #1020. As far as I can tell, the main distinction of the approach taken by this PR is that the implementation and spec changes show how one can start executing deferred fragments semi-immediately (i.e. after deferring in an implementation-specific way), rather than waiting for the entire initial result to be emitted. This is not required -- one could still be compliant with the spec by deferring all the way until the initial result completes! In fact, how one defers is not per se observable and so the spec cannot mandate much about it with great normative force. But -- and I think this is important -- this PR and the implementation PR provide an algorithm/implementation/spec changes that give servers the flexibility to do what they think is right in that regard, and that might be desirable.
As of this moment, I am fairly confident in the implementation PR over at
graphql-js
, and the spec PR should generally correspond, demonstrating:
- the
Field Group
,Defer Usage
, andStream Usage
record types that contain the complex information derived from the operation during field collection- the new
Publisher
construct, which keeps track of when to release payloads- the change from a single parent
Async Payload
record to potentially multiple parents- the deferMap, which maps
Defer Usage
records to individualDeferred Fragment
Async Payload
records- the tracking mechanism by which
Deferred Fragment
records are notified as complete, namely theAddPendingDeferredField
andReportDeferredValue
algorithmsDeduplication and Payload Format
The implementation and these spec edits do not currently included deduplication, and the payload format is the same as the deferred fragment, but that can be easily changed as per below.
TLDR: it can be easily changed.
Adding deduplication
The algorithm includes events and handlers corresponding to:
- the unique completion of each fields
- the successful completion of each overall deferred fragment at a given path.
On events of type 1, fields are forwarded to each deferred fragment, and on events of type 2, the fragment including potentially duplicate values is published. We can easily change the algorithm to do something different in response to each event, without the use of
WeakMap
or any sort of long-lived cache.For example:
- On events of type 1, we could immediately publish the field values
- On events of type 2, we could publish a completion notice for the fragment
Or, potentially:
- On events of type 1, we could add the field to a list of values ready to be sent -- once the first payload completes.
- On events of type 2, we could send a completion notice for the fragment and release any pending field values associated with the fragment.
Or, we could do something more complicated:
We could modify the field group collection algorithm to defined "building blocks" or subset of the deferred fragments that will combine in a predictable way no matter the order of deferred fragment completion.
- On events of type 1A, field completion, we could notify the "building block" that a field is ready.
- On events of type 1B, "building block" completion, we could notify the deferred fragment that a building block is ready
- On events of type 2, all "building blocks" for a field are ready, we could send a completion notice for the fragment and release any pending "building blocks" associated with the fragment.
** huge thanks to @urigo and @dotansimha of the guild for sponsoring my open-source work on this. **