@@ -361,32 +361,81 @@ The Async Payload Records may be published lazily as requested, with the
361
361
internal state of the unpublished stream held by a Publisher Record unique to
362
362
the request.
363
363
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.
366
375
367
376
## Create Publisher
368
377
369
378
CreatePublisher():
370
379
371
380
- 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}.
373
387
- Return {publisherRecord}.
374
388
375
389
## Has Subsequent Payloads
376
390
377
391
HasSubsequentPayloads(publisherRecord):
378
392
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}.
382
396
- Return {false}.
383
397
384
398
## Add Payload
385
399
386
400
AddPayload(payload, publisherRecord):
387
401
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}.
390
439
391
440
## Yield Subsequent Payloads
392
441
@@ -396,15 +445,13 @@ payloads should be processed.
396
445
397
446
YieldSubsequentPayloads(initialResponse, publisherRecord):
398
447
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}.
402
449
- 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 }.
405
452
- If {isCompletedIterator} on {record} is {true}:
406
453
- 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 }.
408
455
- Append {payload} to {initialIncremental}.
409
456
- If {initialIncremental} is not empty:
410
457
- Add an entry to {initialResponse} named ` incremental ` containing the value
@@ -416,17 +463,17 @@ YieldSubsequentPayloads(initialResponse, publisherRecord):
416
463
- If {record} contains {iterator}:
417
464
- Send a termination signal to {iterator}.
418
465
- 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}.
421
469
- Let {subsequentResponse} be an unordered map with an entry {incremental}
422
470
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}:
426
473
- Remove {record} from {subsequentPayloads}.
427
474
- If {isCompletedIterator} on {record} is {true}:
428
475
- 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 }.
430
477
- Append {payload} to the {incremental} entry on {subsequentResponse}.
431
478
- If {subsequentPayloads} is empty:
432
479
- Add an entry to {subsequentResponse} named ` hasNext ` with the value
@@ -505,20 +552,34 @@ outside of {ExecuteDeferredFragment} or {ExecuteStreamField}.
505
552
506
553
FilterSubsequentPayloads(publisherRecord, nullPath, currentAsyncRecord):
507
554
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}.
522
583
523
584
For example, assume the field ` alwaysThrows ` is a ` Non-Null ` type that always
524
585
raises a field error:
@@ -797,51 +858,49 @@ DoesFragmentTypeApply(objectType, fragmentType):
797
858
An Async Payload Record is either a Deferred Fragment Record or a Stream Record.
798
859
All Async Payload Records are structures containing:
799
860
861
+ - {parentRecord}: The generating parent Async Payload Record, not defined if
862
+ this Async Payload Record is spawned by the initial result.
800
863
- {label}: value derived from the corresponding ` @defer ` or ` @stream ` directive.
801
864
- {path}: a list of field names and indices from root to the location of the
802
865
corresponding ` @defer ` or ` @stream ` directive.
803
866
- {iterator}: The underlying iterator if created from a ` @stream ` directive.
804
867
- {isCompletedIterator}: a boolean indicating the payload record was generated
805
868
from an iterator that has completed.
806
869
- {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.
809
871
810
872
#### Execute Deferred Fragment
811
873
812
874
ExecuteDeferredFragment(label, objectType, objectValue, groupedFieldSet, path,
813
875
variableValues, parentRecord, publisherRecord):
814
876
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}.
844
879
- 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)}.
845
904
846
905
## Executing Fields
847
906
@@ -968,43 +1027,40 @@ yielded items satisfies `initialCount` specified on the `@stream` directive.
968
1027
ExecuteStreamField(label, iterator, index, fields, innerType, path,
969
1028
streamRecord, variableValues, publisherRecord):
970
1029
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)}.
973
1033
- Initialize {errors} on {streamRecord} to an empty list.
974
1034
- 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:
983
1053
- Add an entry to {payload} named ` items ` with the value {null}.
984
1054
- 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)}.
1008
1064
1009
1065
CompleteValue(fieldType, fields, result, variableValues, path, publisherRecord,
1010
1066
asyncRecord):
0 commit comments