Skip to content

Commit 193bc22

Browse files
committed
add new publisher
Corresponds to graphql/graphql-js#3786
1 parent db5742c commit 193bc22

File tree

1 file changed

+154
-98
lines changed

1 file changed

+154
-98
lines changed

spec/Section 6 -- Execution.md

Lines changed: 154 additions & 98 deletions
Original file line numberDiff line numberDiff line change
@@ -361,32 +361,81 @@ The Async Payload Records may be published lazily as requested, with the
361361
internal state of the unpublished stream held by a Publisher Record unique to
362362
the request.
363363

364-
- {subsequentPayloads}: the set of Async Payload Records for this response that
365-
have not yet been published.
364+
- {pending}: the set of Async Payload Records for this response that have not
365+
yet been completed.
366+
- {waiting}: the set of Async Payload Records for this response that have been
367+
completed, but are waiting for a parent to complete.
368+
- {waitingByParent}: an unordered map of uncompleted parent Async Payload
369+
Records to sets of completed child Async Payload Records.
370+
- {pushed}: a weakly held set of Async Payload Records for this response that
371+
have been completed, but are waiting for a parent to complete.
372+
- {current}: the set of Async Payload Records for this response that may be
373+
yielded on the next request.
374+
- {signal}: An asynchronous signal that can be awaited and triggered.
366375

367376
## Create Publisher
368377

369378
CreatePublisher():
370379

371380
- Let {publisherRecord} be a publisher record.
372-
- Initialize {subsequentPayloads} on {publisherRecord} to an empty set.
381+
- Initialize {pending} on {publisherRecord} to an empty set.
382+
- Initialize {waiting} on {publisherRecord} to an empty set.
383+
- Initialize {waitingByParent} on {publisherRecord} to an empty unordered map.
384+
- Initialize {pushed} on {publisherRecord} to an empty set.
385+
- Initialize {current} on {publisherRecord} to an empty set.
386+
- Initialize {signal}.
373387
- Return {publisherRecord}.
374388

375389
## Has Subsequent Payloads
376390

377391
HasSubsequentPayloads(publisherRecord):
378392

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}.
393+
- Let {pending}, {waiting}, and {current} be the corresponding entries on
394+
{publisherRecord}.
395+
- If {pending}, {waiting}, or {current} is not empty, return {true}.
382396
- Return {false}.
383397

384398
## Add Payload
385399

386400
AddPayload(payload, publisherRecord):
387401

388-
- Let {subsequentPayloads} be the corresponding entry on {publisherRecord}.
389-
- Add {payload} to {subsequentPayloads}.
402+
- Let {pending} be the corresponding entry on {publisherRecord}.
403+
- Add {payload} to {pending}.
404+
405+
## Complete Payload
406+
407+
CompletePayload(payload, publisherRecord):
408+
409+
- Let {pending} be the corresponding entry on {publisherRecord}.
410+
- If {payload} is not within {pending}, return.
411+
- Remove {payload} from {pending}.
412+
- Let {parentRecord} be the corresponding entry on {payload}.
413+
- If {parentRecord} is not defined:
414+
- Call PushPayload(payload, publisherRecord).
415+
- Let {signal} be the corresponding entry on {publisherRecord}.
416+
- Trigger {signal}.
417+
- Otherwise:
418+
- Let {waiting} and {waitingByChildren} be the corresponding entries on
419+
{publisherRecord}.
420+
- Add {payload} to {waiting}.
421+
- Let {children} be the set in {waitingByParent} for {parentRecord}; if no
422+
such set exists, create it as an empty set.
423+
- Append {payload} to {children}.
424+
425+
## Push Payload
426+
427+
PushPayload(payload, publisherRecord):
428+
429+
- Let {pushed}, {current}, and {waitingByParent} be the corresponding entries on
430+
{publisherRecord}.
431+
- Add {payload} to {pushed} and {current}.
432+
- Let {children} be the set in {waitingByParent} for {parentRecord}.
433+
- If {children} is not defined, return.
434+
- Let {waiting} be the corresponding entry on {publisherRecord}.
435+
- For each {child} in {children}:
436+
- Call {PushPayload(payload, publisherRecord)}.
437+
- Remove {child} from {waiting}.
438+
- Remove the set in {waitingByParent} for {parentRecord}.
390439

391440
## Yield Subsequent Payloads
392441

@@ -396,15 +445,13 @@ payloads should be processed.
396445

397446
YieldSubsequentPayloads(initialResponse, publisherRecord):
398447

399-
- Let {subsequentPayloads} be the corresponding entry on {publisherRecord}.
400-
- Let {initialRecords} be any items in {subsequentPayloads} with a completed
401-
{dataExecution}.
448+
- Let {current} be the corresponding entry on {publisherRecord}.
402449
- Initialize {initialIncremental} to an empty list.
403-
- For each {record} in {initialRecords}:
404-
- Remove {record} from {subsequentPayloads}.
450+
- For each {record} in {current}:
451+
- Remove {record} from {current}.
405452
- If {isCompletedIterator} on {record} is {true}:
406453
- Continue to the next record in {records}.
407-
- Let {payload} be the completed result returned by {dataExecution}.
454+
- Let {payload} be the corresponding entry on {record}.
408455
- Append {payload} to {initialIncremental}.
409456
- If {initialIncremental} is not empty:
410457
- Add an entry to {initialResponse} named `incremental` containing the value
@@ -416,17 +463,17 @@ YieldSubsequentPayloads(initialResponse, publisherRecord):
416463
- If {record} contains {iterator}:
417464
- Send a termination signal to {iterator}.
418465
- Return.
419-
- Wait for at least one record in {subsequentPayloads} to have a completed
420-
{dataExecution}.
466+
- Let {signal} be the corresponding entry on {publisherRecord}.
467+
- Wait for {signal} to be triggered.
468+
- Reinitialize {signal} on {publisherRecord}.
421469
- Let {subsequentResponse} be an unordered map with an entry {incremental}
422470
initialized to an empty list.
423-
- Let {records} be the items in {subsequentPayloads} with a completed
424-
{dataExecution}.
425-
- For each {record} in {records}:
471+
- Let {current} be the corresponding entry on {publisherRecord}.
472+
- For each {record} in {current}:
426473
- Remove {record} from {subsequentPayloads}.
427474
- If {isCompletedIterator} on {record} is {true}:
428475
- Continue to the next record in {records}.
429-
- Let {payload} be the completed result returned by {dataExecution}.
476+
- Let {payload} be the corresponding entry on {record}.
430477
- Append {payload} to the {incremental} entry on {subsequentResponse}.
431478
- If {subsequentPayloads} is empty:
432479
- Add an entry to {subsequentResponse} named `hasNext` with the value
@@ -505,20 +552,34 @@ outside of {ExecuteDeferredFragment} or {ExecuteStreamField}.
505552

506553
FilterSubsequentPayloads(publisherRecord, nullPath, currentAsyncRecord):
507554

508-
- Let {subsequentPayloads} be the corresponding entry on {publisherRecord}.
509-
- For each {asyncRecord} in {subsequentPayloads}:
510-
- If {asyncRecord} is the same record as {currentAsyncRecord}:
511-
- Continue to the next record in {subsequentPayloads}.
512-
- Initialize {index} to zero.
513-
- While {index} is less then the length of {nullPath}:
514-
- Initialize {nullPathItem} to the element at {index} in {nullPath}.
515-
- Initialize {asyncRecordPathItem} to the element at {index} in the {path}
516-
of {asyncRecord}.
517-
- If {nullPathItem} is not equivalent to {asyncRecordPathItem}:
518-
- Continue to the next record in {subsequentPayloads}.
519-
- Increment {index} by one.
520-
- Remove {asyncRecord} from {subsequentPayloads}. Optionally, cancel any
521-
incomplete work in the execution of {asyncRecord}.
555+
- Let {pending}, {current}, {waiting}, and {waitingByParent} be the
556+
corresponding entries on {publisherRecord}.
557+
- For each {asyncRecord} in {pending} and {current}:
558+
- If {ShouldKeepPayload(asyncRecord, nullPath, currentAsyncRecord)} is {true}:
559+
- Continue to the next record in {set}.
560+
- Remove {asyncRecord} from {set}. Optionally, cancel any incomplete work in
561+
the execution of {asyncRecord}.
562+
- For each {asyncRecord} in {waiting}:
563+
- If {ShouldKeepPayload(asyncRecord, nullPath, currentAsyncRecord)} is {true}:
564+
- Continue to the next record in {waiting}.
565+
- Remove {asyncRecord} from {waiting}. Optionally, cancel any incomplete work
566+
in the execution of {asyncRecord}.
567+
- Let {parentRecord} be the corresponding entry on {asyncRecord}.
568+
- Let {children} be the set in {waitingByParent} for {parentRecord}.
569+
- Remove {asyncRecord} from {children}.
570+
571+
ShouldKeepPayload(asyncRecord, nullPath, currentAsyncRecord):
572+
573+
- If {asyncRecord} is the same record as {currentAsyncRecord}:
574+
- Return {true}.
575+
- Initialize {index} to zero.
576+
- While {index} is less then the length of {nullPath}:
577+
- Initialize {nullPathItem} to the element at {index} in {nullPath}.
578+
- Initialize {asyncRecordPathItem} to the element at {index} in the {path} of
579+
{asyncRecord}.
580+
- If {nullPathItem} is not equivalent to {asyncRecordPathItem}:
581+
- Return {true}.
582+
- Increment {index} by one. Return {false}.
522583

523584
For example, assume the field `alwaysThrows` is a `Non-Null` type that always
524585
raises a field error:
@@ -797,51 +858,49 @@ DoesFragmentTypeApply(objectType, fragmentType):
797858
An Async Payload Record is either a Deferred Fragment Record or a Stream Record.
798859
All Async Payload Records are structures containing:
799860

861+
- {parentRecord}: The generating parent Async Payload Record, not defined if
862+
this Async Payload Record is spawned by the initial result.
800863
- {label}: value derived from the corresponding `@defer` or `@stream` directive.
801864
- {path}: a list of field names and indices from root to the location of the
802865
corresponding `@defer` or `@stream` directive.
803866
- {iterator}: The underlying iterator if created from a `@stream` directive.
804867
- {isCompletedIterator}: a boolean indicating the payload record was generated
805868
from an iterator that has completed.
806869
- {errors}: a list of field errors encountered during execution.
807-
- {dataExecution}: A result that can notify when the corresponding execution has
808-
completed.
870+
- {payload}: An unordered map containing the formatted payload.
809871

810872
#### Execute Deferred Fragment
811873

812874
ExecuteDeferredFragment(label, objectType, objectValue, groupedFieldSet, path,
813875
variableValues, parentRecord, publisherRecord):
814876

815-
- Let {deferRecord} be an async payload record created from {label} and {path}.
816-
- Initialize {errors} on {deferRecord} to an empty list.
817-
- Let {dataExecution} be the asynchronous future value of:
818-
- Let {payload} be an unordered map.
819-
- Initialize {resultMap} to an empty ordered map.
820-
- For each {groupedFieldSet} as {responseKey} and {fields}:
821-
- Let {fieldName} be the name of the first entry in {fields}. Note: This
822-
value is unaffected if an alias is used.
823-
- Let {fieldType} be the return type defined for the field {fieldName} of
824-
{objectType}.
825-
- If {fieldType} is defined:
826-
- Let {responseValue} be {ExecuteField(objectType, objectValue, fieldType,
827-
fields, variableValues, path, publisherRecord, asyncRecord)}.
828-
- Set {responseValue} as the value for {responseKey} in {resultMap}.
829-
- Append any encountered field errors to {errors}.
830-
- If {parentRecord} is defined:
831-
- Wait for the result of {dataExecution} on {parentRecord}.
832-
- If {errors} is not empty:
833-
- Add an entry to {payload} named `errors` with the value {errors}.
834-
- If a field error was raised, causing a {null} to be propagated to
835-
{responseValue}:
836-
- Add an entry to {payload} named `data` with the value {null}.
837-
- Otherwise:
838-
- Add an entry to {payload} named `data` with the value {resultMap}.
839-
- If {label} is defined:
840-
- Add an entry to {payload} named `label` with the value {label}.
841-
- Add an entry to {payload} named `path` with the value {path}.
842-
- Return {payload}.
843-
- Set {dataExecution} on {deferredFragmentRecord}.
877+
- Let {deferRecord} be an async payload record created from {parentRecord},
878+
{label}, and {path}.
844879
- Call {AddPayload(deferRecord, publisherRecord)}.
880+
- Initialize {errors} on {deferRecord} to an empty list.
881+
- Initialize {resultMap} to an empty ordered map.
882+
- For each {groupedFieldSet} as {responseKey} and {fields}:
883+
- Let {fieldName} be the name of the first entry in {fields}. Note: This value
884+
is unaffected if an alias is used.
885+
- Let {fieldType} be the return type defined for the field {fieldName} of
886+
{objectType}.
887+
- If {fieldType} is defined:
888+
- Let {responseValue} be {ExecuteField(objectType, objectValue, fieldType,
889+
fields, variableValues, path, publisherRecord, asyncRecord)}.
890+
- Set {responseValue} as the value for {responseKey} in {resultMap}.
891+
- Append any encountered field errors to {errors}.
892+
- If {errors} is not empty:
893+
- Add an entry to {payload} named `errors` with the value {errors}.
894+
- If a field error was raised, causing a {null} to be propagated to
895+
{responseValue}:
896+
- Add an entry to {payload} named `data` with the value {null}.
897+
- Otherwise:
898+
- Add an entry to {payload} named `data` with the value {resultMap}.
899+
- If {label} is defined:
900+
- Add an entry to {payload} named `label` with the value {label}.
901+
- Add an entry to {payload} named `path` with the value {path}.
902+
- Set {payload} on {deferRecord}.
903+
- Call {CompletePayload(payload, publisherRecord)}.
845904

846905
## Executing Fields
847906

@@ -968,43 +1027,40 @@ yielded items satisfies `initialCount` specified on the `@stream` directive.
9681027
ExecuteStreamField(label, iterator, index, fields, innerType, path,
9691028
streamRecord, variableValues, publisherRecord):
9701029

971-
- Let {streamRecord} be an async payload record created from {label}, {path},
972-
and {iterator}.
1030+
- Let {streamRecord} be an async payload record created from {parentRecord},
1031+
{label}, {path}, and {iterator}.
1032+
- Call {AddPayload(streamRecord, publisherRecord)}.
9731033
- Initialize {errors} on {streamRecord} to an empty list.
9741034
- Let {itemPath} be {path} with {index} appended.
975-
- Let {dataExecution} be the asynchronous future value of:
976-
- Wait for the next item from {iterator}.
977-
- If an item is not retrieved because {iterator} has completed:
978-
- Set {isCompletedIterator} to {true} on {streamRecord}.
979-
- Return {null}.
980-
- Let {payload} be an unordered map.
981-
- If an item is not retrieved because of an error:
982-
- Append the encountered error to {errors}.
1035+
- Wait for the next item from {iterator}.
1036+
- If an item is not retrieved because {iterator} has completed:
1037+
- Set {isCompletedIterator} to {true} on {streamRecord}.
1038+
- Return {null}.
1039+
- Let {payload} be an unordered map.
1040+
- If an item is not retrieved because of an error:
1041+
- Append the encountered error to {errors}.
1042+
- Add an entry to {payload} named `items` with the value {null}.
1043+
- Otherwise:
1044+
- Let {item} be the item retrieved from {iterator}.
1045+
- Let {data} be the result of calling {CompleteValue(innerType, fields, item,
1046+
variableValues, itemPath, publisherRecord, parentRecord)}.
1047+
- Append any encountered field errors to {errors}.
1048+
- Increment {index}.
1049+
- Call {ExecuteStreamField(label, iterator, index, fields, innerType, path,
1050+
streamRecord, variableValues, publisherRecord)}.
1051+
- If a field error was raised, causing a {null} to be propagated to {data},
1052+
and {innerType} is a Non-Nullable type:
9831053
- Add an entry to {payload} named `items` with the value {null}.
9841054
- Otherwise:
985-
- Let {item} be the item retrieved from {iterator}.
986-
- Let {data} be the result of calling {CompleteValue(innerType, fields,
987-
item, variableValues, itemPath, publisherRecord, parentRecord)}.
988-
- Append any encountered field errors to {errors}.
989-
- Increment {index}.
990-
- Call {ExecuteStreamField(label, iterator, index, fields, innerType, path,
991-
streamRecord, variableValues, publisherRecord)}.
992-
- If a field error was raised, causing a {null} to be propagated to {data},
993-
and {innerType} is a Non-Nullable type:
994-
- Add an entry to {payload} named `items` with the value {null}.
995-
- Otherwise:
996-
- Add an entry to {payload} named `items` with a list containing the value
997-
{data}.
998-
- If {errors} is not empty:
999-
- Add an entry to {payload} named `errors` with the value {errors}.
1000-
- If {label} is defined:
1001-
- Add an entry to {payload} named `label` with the value {label}.
1002-
- Add an entry to {payload} named `path` with the value {itemPath}.
1003-
- If {parentRecord} is defined:
1004-
- Wait for the result of {dataExecution} on {parentRecord}.
1005-
- Return {payload}.
1006-
- Set {dataExecution} on {streamRecord}.
1007-
- Call {AddPayload(streamRecord, publisherRecord)}.
1055+
- Add an entry to {payload} named `items` with a list containing the value
1056+
{data}.
1057+
- If {errors} is not empty:
1058+
- Add an entry to {payload} named `errors` with the value {errors}.
1059+
- If {label} is defined:
1060+
- Add an entry to {payload} named `label` with the value {label}.
1061+
- Add an entry to {payload} named `path` with the value {itemPath}.
1062+
- Set {payload} on {streamRecord}.
1063+
- Call {CompletePayload(streamRecord, publisherRecord)}.
10081064

10091065
CompleteValue(fieldType, fields, result, variableValues, path, publisherRecord,
10101066
asyncRecord):

0 commit comments

Comments
 (0)