Skip to content

Commit db5742c

Browse files
committed
extract publisher from execution algorithm
parallels graphql/graphql-js#3784
1 parent 82ca7fe commit db5742c

File tree

1 file changed

+77
-42
lines changed

1 file changed

+77
-42
lines changed

spec/Section 6 -- Execution.md

Lines changed: 77 additions & 42 deletions
Original file line numberDiff line numberDiff line change
@@ -132,22 +132,22 @@ An initial value may be provided when executing a query operation.
132132

133133
ExecuteQuery(query, schema, variableValues, initialValue):
134134

135-
- Let {subsequentPayloads} be an empty list.
135+
- Let {publisherRecord} be the result of {CreatePublisher()}.
136136
- Let {queryType} be the root Query type in {schema}.
137137
- Assert: {queryType} is an Object type.
138138
- Let {selectionSet} be the top level Selection Set in {query}.
139139
- Let {data} be the result of running {ExecuteSelectionSet(selectionSet,
140-
queryType, initialValue, variableValues, subsequentPayloads)} _normally_
140+
queryType, initialValue, variableValues, publisherRecord)} _normally_
141141
(allowing parallelization).
142142
- Let {errors} be the list of all _field error_ raised while executing the
143143
selection set.
144-
- If {subsequentPayloads} is empty:
144+
- If {HasSubsequentPayloads(publisherRecord)} is {false}:
145145
- Return an unordered map containing {data} and {errors}.
146-
- If {subsequentPayloads} is not empty:
146+
- Otherwise:
147147
- Let {initialResponse} be an unordered map containing {data}, {errors}, and
148148
an entry named {hasNext} with the value {true}.
149149
- Let {iterator} be the result of running
150-
{YieldSubsequentPayloads(initialResponse, subsequentPayloads)}.
150+
{YieldSubsequentPayloads(initialResponse, publisherRecord)}.
151151
- For each {payload} yielded by {iterator}:
152152
- If a termination signal is received:
153153
- Send a termination signal to {iterator}.
@@ -167,21 +167,21 @@ mutations ensures against race conditions during these side-effects.
167167

168168
ExecuteMutation(mutation, schema, variableValues, initialValue):
169169

170-
- Let {subsequentPayloads} be an empty list.
170+
- Let {publisherRecord} be the result of {CreatePublisher()}.
171171
- Let {mutationType} be the root Mutation type in {schema}.
172172
- Assert: {mutationType} is an Object type.
173173
- Let {selectionSet} be the top level Selection Set in {mutation}.
174174
- Let {data} be the result of running {ExecuteSelectionSet(selectionSet,
175-
mutationType, initialValue, variableValues, subsequentPayloads)} _serially_.
175+
mutationType, initialValue, variableValues, publisherRecord)} _serially_.
176176
- Let {errors} be the list of all _field error_ raised while executing the
177177
selection set.
178-
- If {subsequentPayloads} is empty:
178+
- If {HasSubsequentPayloads(publisherRecord)} is {false}:
179179
- Return an unordered map containing {data} and {errors}.
180-
- If {subsequentPayloads} is not empty:
180+
- Otherwise:
181181
- Let {initialResponse} be an unordered map containing {data}, {errors}, and
182182
an entry named {hasNext} with the value {true}.
183183
- Let {iterator} be the result of running
184-
{YieldSubsequentPayloads(initialResponse, subsequentPayloads)}.
184+
{YieldSubsequentPayloads(initialResponse, publisherRecord)}.
185185
- For each {payload} yielded by {iterator}:
186186
- If a termination signal is received:
187187
- Send a termination signal to {iterator}.
@@ -353,14 +353,50 @@ Unsubscribe(responseStream):
353353

354354
- Cancel {responseStream}
355355

356+
## Publisher Record
357+
358+
If an operation contains `@defer` or `@stream` directives, execution may also
359+
result in an Async Payload Record stream in addition to the initial response.
360+
The Async Payload Records may be published lazily as requested, with the
361+
internal state of the unpublished stream held by a Publisher Record unique to
362+
the request.
363+
364+
- {subsequentPayloads}: the set of Async Payload Records for this response that
365+
have not yet been published.
366+
367+
## Create Publisher
368+
369+
CreatePublisher():
370+
371+
- Let {publisherRecord} be a publisher record.
372+
- Initialize {subsequentPayloads} on {publisherRecord} to an empty set.
373+
- Return {publisherRecord}.
374+
375+
## Has Subsequent Payloads
376+
377+
HasSubsequentPayloads(publisherRecord):
378+
379+
- Let {subsequentPayloads} be the corresponding entry on {publisherRecord}.
380+
- Let {size} be the number of payloads within {subsequentPayloads}.
381+
- If {size} is greater than zero, return {true}.
382+
- Return {false}.
383+
384+
## Add Payload
385+
386+
AddPayload(payload, publisherRecord):
387+
388+
- Let {subsequentPayloads} be the corresponding entry on {publisherRecord}.
389+
- Add {payload} to {subsequentPayloads}.
390+
356391
## Yield Subsequent Payloads
357392

358393
If an operation contains subsequent payload records resulting from `@stream` or
359394
`@defer` directives, the {YieldSubsequentPayloads} algorithm defines how the
360395
payloads should be processed.
361396

362-
YieldSubsequentPayloads(initialResponse, subsequentPayloads):
397+
YieldSubsequentPayloads(initialResponse, publisherRecord):
363398

399+
- Let {subsequentPayloads} be the corresponding entry on {publisherRecord}.
364400
- Let {initialRecords} be any items in {subsequentPayloads} with a completed
365401
{dataExecution}.
366402
- Initialize {initialIncremental} to an empty list.
@@ -411,10 +447,9 @@ represented field in the grouped field set produces an entry into a response
411447
map.
412448

413449
ExecuteSelectionSet(selectionSet, objectType, objectValue, variableValues, path,
414-
subsequentPayloads, asyncRecord):
450+
publisherRecord, asyncRecord):
415451

416452
- If {path} is not provided, initialize it to an empty list.
417-
- If {subsequentPayloads} is not provided, initialize it to the empty set.
418453
- Let {groupedFieldSet} and {deferredGroupedFieldsList} be the result of
419454
{CollectFields(objectType, selectionSet, variableValues, path, asyncRecord)}.
420455
- Initialize {resultMap} to an empty ordered map.
@@ -425,12 +460,11 @@ subsequentPayloads, asyncRecord):
425460
{objectType}.
426461
- If {fieldType} is defined:
427462
- Let {responseValue} be {ExecuteField(objectType, objectValue, fieldType,
428-
fields, variableValues, path, subsequentPayloads, asyncRecord)}.
463+
fields, variableValues, path, publisherRecord, asyncRecord)}.
429464
- Set {responseValue} as the value for {responseKey} in {resultMap}.
430465
- For each {deferredGroupFieldSet} and {label} in {deferredGroupedFieldsList}
431466
- Call {ExecuteDeferredFragment(label, objectType, objectValue,
432-
deferredGroupFieldSet, path, variableValues, asyncRecord,
433-
subsequentPayloads)}
467+
deferredGroupFieldSet, path, variableValues, asyncRecord, publisherRecord)}
434468
- Return {resultMap}.
435469

436470
Note: {resultMap} is ordered by which fields appear first in the operation. This
@@ -445,32 +479,33 @@ either resolving to {null} if allowed or further propagated to a parent field.
445479
If this occurs, any sibling fields which have not yet executed or have not yet
446480
yielded a value may be cancelled to avoid unnecessary work.
447481

448-
Additionally, async payload records in {subsequentPayloads} must be filtered if
449-
their path points to a location that has resolved to {null} due to propagation
450-
of a field error. This is described in
451-
[Filter Subsequent Payloads](#sec-Filter-Subsequent-Payloads). These async
452-
payload records must be removed from {subsequentPayloads} and their result must
453-
not be sent to clients. If these async records have not yet executed or have not
454-
yet yielded a value they may also be cancelled to avoid unnecessary work.
482+
Additionally, unpublished Async Payload Records must be filtered if their path
483+
points to a location that has resolved to {null} due to propagation of a field
484+
error. This is described in
485+
[Filter Subsequent Payloads](#sec-Filter-Subsequent-Payloads). The result of
486+
these async payload records must not be sent to clients. If these async records
487+
have not yet executed or have not yet yielded a value they may also be cancelled
488+
to avoid unnecessary work.
455489

456490
Note: See [Handling Field Errors](#sec-Handling-Field-Errors) for more about
457491
this behavior.
458492

459493
### Filter Subsequent Payloads
460494

461-
When a field error is raised, there may be async payload records in
462-
{subsequentPayloads} with a path that points to a location that has been removed
463-
or set to null due to null propagation. These async payload records must be
464-
removed from subsequent payloads and their results must not be sent to clients.
495+
When a field error is raised, there may be unpublished async payload records
496+
with a path that points to a location that has been removed or set to null due
497+
to null propagation. The results of these async payload records must not be sent
498+
to clients.
465499

466500
In {FilterSubsequentPayloads}, {nullPath} is the path which has resolved to null
467501
after propagation as a result of a field error. {currentAsyncRecord} is the
468502
async payload record where the field error was raised. {currentAsyncRecord} will
469503
not be set for field errors that were raised during the initial execution
470504
outside of {ExecuteDeferredFragment} or {ExecuteStreamField}.
471505

472-
FilterSubsequentPayloads(subsequentPayloads, nullPath, currentAsyncRecord):
506+
FilterSubsequentPayloads(publisherRecord, nullPath, currentAsyncRecord):
473507

508+
- Let {subsequentPayloads} be the corresponding entry on {publisherRecord}.
474509
- For each {asyncRecord} in {subsequentPayloads}:
475510
- If {asyncRecord} is the same record as {currentAsyncRecord}:
476511
- Continue to the next record in {subsequentPayloads}.
@@ -775,7 +810,7 @@ All Async Payload Records are structures containing:
775810
#### Execute Deferred Fragment
776811

777812
ExecuteDeferredFragment(label, objectType, objectValue, groupedFieldSet, path,
778-
variableValues, parentRecord, subsequentPayloads):
813+
variableValues, parentRecord, publisherRecord):
779814

780815
- Let {deferRecord} be an async payload record created from {label} and {path}.
781816
- Initialize {errors} on {deferRecord} to an empty list.
@@ -789,7 +824,7 @@ variableValues, parentRecord, subsequentPayloads):
789824
{objectType}.
790825
- If {fieldType} is defined:
791826
- Let {responseValue} be {ExecuteField(objectType, objectValue, fieldType,
792-
fields, variableValues, path, subsequentPayloads, asyncRecord)}.
827+
fields, variableValues, path, publisherRecord, asyncRecord)}.
793828
- Set {responseValue} as the value for {responseKey} in {resultMap}.
794829
- Append any encountered field errors to {errors}.
795830
- If {parentRecord} is defined:
@@ -806,7 +841,7 @@ variableValues, parentRecord, subsequentPayloads):
806841
- Add an entry to {payload} named `path` with the value {path}.
807842
- Return {payload}.
808843
- Set {dataExecution} on {deferredFragmentRecord}.
809-
- Append {deferRecord} to {subsequentPayloads}.
844+
- Call {AddPayload(deferRecord, publisherRecord)}.
810845

811846
## Executing Fields
812847

@@ -817,7 +852,7 @@ finally completes that value either by recursively executing another selection
817852
set or coercing a scalar value.
818853

819854
ExecuteField(objectType, objectValue, fieldType, fields, variableValues, path,
820-
subsequentPayloads, asyncRecord):
855+
publisherRecord, asyncRecord):
821856

822857
- Let {field} be the first entry in {fields}.
823858
- Let {fieldName} be the field name of {field}.
@@ -827,7 +862,7 @@ subsequentPayloads, asyncRecord):
827862
- Let {resolvedValue} be {ResolveFieldValue(objectType, objectValue, fieldName,
828863
argumentValues)}.
829864
- Let {result} be the result of calling {CompleteValue(fieldType, fields,
830-
resolvedValue, variableValues, path, subsequentPayloads, asyncRecord)}.
865+
resolvedValue, variableValues, path, publisherRecord, asyncRecord)}.
831866
- Return {result}.
832867

833868
### Coercing Field Arguments
@@ -931,7 +966,7 @@ yielded items satisfies `initialCount` specified on the `@stream` directive.
931966
#### Execute Stream Field
932967

933968
ExecuteStreamField(label, iterator, index, fields, innerType, path,
934-
streamRecord, variableValues, subsequentPayloads):
969+
streamRecord, variableValues, publisherRecord):
935970

936971
- Let {streamRecord} be an async payload record created from {label}, {path},
937972
and {iterator}.
@@ -949,11 +984,11 @@ streamRecord, variableValues, subsequentPayloads):
949984
- Otherwise:
950985
- Let {item} be the item retrieved from {iterator}.
951986
- Let {data} be the result of calling {CompleteValue(innerType, fields,
952-
item, variableValues, itemPath, subsequentPayloads, parentRecord)}.
987+
item, variableValues, itemPath, publisherRecord, parentRecord)}.
953988
- Append any encountered field errors to {errors}.
954989
- Increment {index}.
955990
- Call {ExecuteStreamField(label, iterator, index, fields, innerType, path,
956-
streamRecord, variableValues, subsequentPayloads)}.
991+
streamRecord, variableValues, publisherRecord)}.
957992
- If a field error was raised, causing a {null} to be propagated to {data},
958993
and {innerType} is a Non-Nullable type:
959994
- Add an entry to {payload} named `items` with the value {null}.
@@ -969,10 +1004,10 @@ streamRecord, variableValues, subsequentPayloads):
9691004
- Wait for the result of {dataExecution} on {parentRecord}.
9701005
- Return {payload}.
9711006
- Set {dataExecution} on {streamRecord}.
972-
- Append {streamRecord} to {subsequentPayloads}.
1007+
- Call {AddPayload(streamRecord, publisherRecord)}.
9731008

974-
CompleteValue(fieldType, fields, result, variableValues, path,
975-
subsequentPayloads, asyncRecord):
1009+
CompleteValue(fieldType, fields, result, variableValues, path, publisherRecord,
1010+
asyncRecord):
9761011

9771012
- If the {fieldType} is a Non-Null type:
9781013
- Let {innerType} be the inner type of {fieldType}.
@@ -1004,15 +1039,15 @@ subsequentPayloads, asyncRecord):
10041039
- If {streamDirective} is defined and {index} is greater than or equal to
10051040
{initialCount}:
10061041
- Call {ExecuteStreamField(label, iterator, index, fields, innerType,
1007-
path, asyncRecord, subsequentPayloads)}.
1042+
path, asyncRecord, publisherRecord)}.
10081043
- Return {items}.
10091044
- Otherwise:
10101045
- Wait for the next item from {result} via the {iterator}.
10111046
- If an item is not retrieved because of an error, raise a _field error_.
10121047
- Let {resultItem} be the item retrieved from {result}.
10131048
- Let {itemPath} be {path} with {index} appended.
10141049
- Let {resolvedItem} be the result of calling {CompleteValue(innerType,
1015-
fields, resultItem, variableValues, itemPath, subsequentPayloads,
1050+
fields, resultItem, variableValues, itemPath, publisherRecord,
10161051
asyncRecord)}.
10171052
- Append {resolvedItem} to {items}.
10181053
- Increment {index}.
@@ -1026,7 +1061,7 @@ subsequentPayloads, asyncRecord):
10261061
- Let {objectType} be {ResolveAbstractType(fieldType, result)}.
10271062
- Let {subSelectionSet} be the result of calling {MergeSelectionSets(fields)}.
10281063
- Return the result of evaluating {ExecuteSelectionSet(subSelectionSet,
1029-
objectType, result, variableValues, path, subsequentPayloads, asyncRecord)}
1064+
objectType, result, variableValues, path, publisherRecord, asyncRecord)}
10301065
_normally_ (allowing for parallelization).
10311066

10321067
**Coercing Results**

0 commit comments

Comments
 (0)