@graphql-tools/executor 2.0.0-alpha-20240620175016-1a4246713c9c2dcf5e3debfccfa47e0efd992528 → 2.0.0-alpha-20240702150657-b50b1dda80c0083a63896cdbfd42efdcdf4c0b98

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -14,7 +14,7 @@ const invariant_js_1 = require("./invariant.js");
14
14
  *
15
15
  * @internal
16
16
  */
17
- function collectFields(schema, fragments, variableValues, runtimeType, operation) {
17
+ function collectFields(schema, fragments, variableValues, runtimeType, selectionSet, errorWithIncrementalSubscription) {
18
18
  const groupedFieldSet = new AccumulatorMap_js_1.AccumulatorMap();
19
19
  const newDeferUsages = [];
20
20
  const context = {
@@ -22,10 +22,10 @@ function collectFields(schema, fragments, variableValues, runtimeType, operation
22
22
  fragments,
23
23
  variableValues,
24
24
  runtimeType,
25
- operation,
25
+ errorWithIncrementalSubscription,
26
26
  visitedFragmentNames: new Set(),
27
27
  };
28
- collectFieldsImpl(context, operation.selectionSet, groupedFieldSet, newDeferUsages);
28
+ collectFieldsImpl(context, selectionSet, groupedFieldSet, newDeferUsages);
29
29
  return { groupedFieldSet, newDeferUsages };
30
30
  }
31
31
  exports.collectFields = collectFields;
@@ -39,13 +39,13 @@ exports.collectFields = collectFields;
39
39
  *
40
40
  * @internal
41
41
  */
42
- function collectSubfields(schema, fragments, variableValues, operation, returnType, fieldGroup) {
42
+ function collectSubfields(schema, fragments, variableValues, errorWithIncrementalSubscription, returnType, fieldGroup) {
43
43
  const context = {
44
44
  schema,
45
45
  fragments,
46
46
  variableValues,
47
47
  runtimeType: returnType,
48
- operation,
48
+ errorWithIncrementalSubscription,
49
49
  visitedFragmentNames: new Set(),
50
50
  };
51
51
  const subGroupedFieldSet = new AccumulatorMap_js_1.AccumulatorMap();
@@ -63,7 +63,7 @@ function collectSubfields(schema, fragments, variableValues, operation, returnTy
63
63
  }
64
64
  exports.collectSubfields = collectSubfields;
65
65
  function collectFieldsImpl(context, selectionSet, groupedFieldSet, newDeferUsages, deferUsage) {
66
- const { schema, fragments, variableValues, runtimeType, operation, visitedFragmentNames } = context;
66
+ const { schema, fragments, variableValues, runtimeType, errorWithIncrementalSubscription, visitedFragmentNames, } = context;
67
67
  for (const selection of selectionSet.selections) {
68
68
  switch (selection.kind) {
69
69
  case graphql_1.Kind.FIELD: {
@@ -81,7 +81,7 @@ function collectFieldsImpl(context, selectionSet, groupedFieldSet, newDeferUsage
81
81
  !doesFragmentConditionMatch(schema, selection, runtimeType)) {
82
82
  continue;
83
83
  }
84
- const newDeferUsage = getDeferUsage(operation, variableValues, selection, deferUsage);
84
+ const newDeferUsage = getDeferUsage(errorWithIncrementalSubscription, variableValues, selection, deferUsage);
85
85
  if (!newDeferUsage) {
86
86
  collectFieldsImpl(context, selection.selectionSet, groupedFieldSet, newDeferUsages, deferUsage);
87
87
  }
@@ -93,7 +93,7 @@ function collectFieldsImpl(context, selectionSet, groupedFieldSet, newDeferUsage
93
93
  }
94
94
  case graphql_1.Kind.FRAGMENT_SPREAD: {
95
95
  const fragName = selection.name.value;
96
- const newDeferUsage = getDeferUsage(operation, variableValues, selection, deferUsage);
96
+ const newDeferUsage = getDeferUsage(errorWithIncrementalSubscription, variableValues, selection, deferUsage);
97
97
  if (!newDeferUsage &&
98
98
  (visitedFragmentNames.has(fragName) || !shouldIncludeNode(variableValues, selection))) {
99
99
  continue;
@@ -120,7 +120,7 @@ function collectFieldsImpl(context, selectionSet, groupedFieldSet, newDeferUsage
120
120
  * deferred based on the experimental flag, defer directive present and
121
121
  * not disabled by the "if" argument.
122
122
  */
123
- function getDeferUsage(operation, variableValues, node, parentDeferUsage) {
123
+ function getDeferUsage(errorWithIncrementalSubscription, variableValues, node, parentDeferUsage) {
124
124
  const defer = (0, graphql_1.getDirectiveValues)(utils_1.GraphQLDeferDirective, node, variableValues);
125
125
  if (!defer) {
126
126
  return;
@@ -128,7 +128,7 @@ function getDeferUsage(operation, variableValues, node, parentDeferUsage) {
128
128
  if (defer['if'] === false) {
129
129
  return;
130
130
  }
131
- (0, invariant_js_1.invariant)(operation.operation !== 'subscription', '`@defer` directive not supported on subscription operations. Disable `@defer` by setting the `if` argument to `false`.');
131
+ (0, invariant_js_1.invariant)(!errorWithIncrementalSubscription, '`@defer` directive not supported on subscription operations. Disable `@defer` by setting the `if` argument to `false`.');
132
132
  return {
133
133
  label: typeof defer['label'] === 'string' ? defer['label'] : undefined,
134
134
  parentDeferUsage,
@@ -7,6 +7,7 @@ const BoxedPromiseOrValue_js_1 = require("./BoxedPromiseOrValue.js");
7
7
  const buildFieldPlan_js_1 = require("./buildFieldPlan.js");
8
8
  const coerceError_js_1 = require("./coerceError.js");
9
9
  const collectFields_js_1 = require("./collectFields.js");
10
+ const flattenAsyncIterable_js_1 = require("./flattenAsyncIterable.js");
10
11
  const IncrementalPublisher_js_1 = require("./IncrementalPublisher.js");
11
12
  const invariant_js_1 = require("./invariant.js");
12
13
  const promiseForObject_js_1 = require("./promiseForObject.js");
@@ -17,7 +18,7 @@ const values_js_1 = require("./values.js");
17
18
  * type. Memoizing ensures the subfields are not repeatedly calculated, which
18
19
  * saves overhead when resolving lists of values.
19
20
  */
20
- const collectSubfields = (0, utils_1.memoize3)((exeContext, returnType, fieldGroup) => (0, collectFields_js_1.collectSubfields)(exeContext.schema, exeContext.fragments, exeContext.variableValues, exeContext.operation, returnType, fieldGroup));
21
+ const collectSubfields = (0, utils_1.memoize3)((exeContext, returnType, fieldGroup) => (0, collectFields_js_1.collectSubfields)(exeContext.schema, exeContext.fragments, exeContext.variableValues, exeContext.errorWithIncrementalSubscription, returnType, fieldGroup));
21
22
  /**
22
23
  * Implements the "Executing requests" section of the GraphQL specification,
23
24
  * including `@defer` and `@stream` as proposed in
@@ -149,7 +150,7 @@ exports.getFragmentsFromDocument = (0, utils_1.memoize1)(function getFragmentsFr
149
150
  * @internal
150
151
  */
151
152
  function buildExecutionContext(args) {
152
- const { schema, document, rootValue, contextValue, variableValues: rawVariableValues, operationName, fieldResolver, typeResolver, subscribeFieldResolver, enableEarlyExecution, signal, } = args;
153
+ const { schema, document, rootValue, contextValue, variableValues: rawVariableValues, operationName, fieldResolver, typeResolver, subscribeFieldResolver, enableEarlyExecution, errorWithIncrementalSubscription, signal, } = args;
153
154
  // If the schema used for execution is invalid, throw an error.
154
155
  (0, graphql_1.assertValidSchema)(schema);
155
156
  const fragments = (0, exports.getFragmentsFromDocument)(document);
@@ -199,6 +200,7 @@ function buildExecutionContext(args) {
199
200
  typeResolver: typeResolver ?? exports.defaultTypeResolver,
200
201
  subscribeFieldResolver: subscribeFieldResolver ?? exports.defaultFieldResolver,
201
202
  enableEarlyExecution: enableEarlyExecution !== false,
203
+ errorWithIncrementalSubscription: operation.operation === 'subscription' && errorWithIncrementalSubscription !== false,
202
204
  signal,
203
205
  errors: undefined,
204
206
  cancellableStreams: undefined,
@@ -221,14 +223,14 @@ function executeOperation(exeContext) {
221
223
  throw exeContext.signal.reason;
222
224
  }
223
225
  try {
224
- const { operation, schema, fragments, variableValues, rootValue } = exeContext;
226
+ const { operation, schema, fragments, variableValues, rootValue, errorWithIncrementalSubscription, } = exeContext;
225
227
  const rootType = (0, utils_1.getDefinedRootType)(schema, operation.operation, [operation]);
226
228
  if (rootType == null) {
227
229
  (0, utils_1.createGraphQLError)(`Schema is not configured to execute ${operation.operation} operation.`, {
228
230
  nodes: operation,
229
231
  });
230
232
  }
231
- const collectedFields = (0, collectFields_js_1.collectFields)(schema, fragments, variableValues, rootType, operation);
233
+ const collectedFields = (0, collectFields_js_1.collectFields)(schema, fragments, variableValues, rootType, operation.selectionSet, errorWithIncrementalSubscription);
232
234
  let groupedFieldSet = collectedFields.groupedFieldSet;
233
235
  const newDeferUsages = collectedFields.newDeferUsages;
234
236
  let data;
@@ -537,7 +539,7 @@ function getStreamUsage(exeContext, fieldGroup, path) {
537
539
  }
538
540
  (0, invariant_js_1.invariant)(typeof stream['initialCount'] === 'number', 'initialCount must be a number');
539
541
  (0, invariant_js_1.invariant)(stream['initialCount'] >= 0, 'initialCount must be a positive integer');
540
- (0, invariant_js_1.invariant)(exeContext.operation.operation !== 'subscription', '`@stream` directive not supported on subscription operations. Disable `@stream` by setting the `if` argument to `false`.');
542
+ (0, invariant_js_1.invariant)(!exeContext.errorWithIncrementalSubscription, '`@stream` directive not supported on subscription operations. Disable `@stream` by setting the `if` argument to `false`.');
541
543
  const streamedFieldGroup = fieldGroup.map(fieldDetails => ({
542
544
  node: fieldDetails.node,
543
545
  deferUsage: undefined,
@@ -908,39 +910,6 @@ const defaultFieldResolver = function (source, args, contextValue, info) {
908
910
  }
909
911
  };
910
912
  exports.defaultFieldResolver = defaultFieldResolver;
911
- /**
912
- * Implements the "Subscribe" algorithm described in the GraphQL specification,
913
- * including `@defer` and `@stream` as proposed in
914
- * https://github.com/graphql/graphql-spec/pull/742
915
- *
916
- * Returns a Promise which resolves to either an AsyncIterator (if successful)
917
- * or an ExecutionResult (error). The promise will be rejected if the schema or
918
- * other arguments to this function are invalid, or if the resolved event stream
919
- * is not an async iterable.
920
- *
921
- * If the client-provided arguments to this function do not result in a
922
- * compliant subscription, a GraphQL Response (ExecutionResult) with descriptive
923
- * errors and no data will be returned.
924
- *
925
- * If the source stream could not be created due to faulty subscription resolver
926
- * logic or underlying systems, the promise will resolve to a single
927
- * ExecutionResult containing `errors` and no `data`.
928
- *
929
- * If the operation succeeded, the promise resolves to an AsyncIterator, which
930
- * yields a stream of result representing the response stream.
931
- *
932
- * Each result may be an ExecutionResult with no `hasNext` (if executing the
933
- * event did not use `@defer` or `@stream`), or an
934
- * `InitialIncrementalExecutionResult` or `SubsequentIncrementalExecutionResult`
935
- * (if executing the event used `@defer` or `@stream`). In the case of
936
- * incremental execution results, each event produces a single
937
- * `InitialIncrementalExecutionResult` followed by one or more
938
- * `SubsequentIncrementalExecutionResult`s; all but the last have `hasNext: true`,
939
- * and the last has `hasNext: false`. There is no interleaving between results
940
- * generated from the same original event.
941
- *
942
- * Accepts an object with named arguments.
943
- */
944
913
  function subscribe(args) {
945
914
  // If a valid execution context cannot be created due to incorrect arguments,
946
915
  // a "Response" with only errors is returned.
@@ -1004,6 +973,14 @@ function flattenIncrementalResults(incrementalResults) {
1004
973
  };
1005
974
  }
1006
975
  exports.flattenIncrementalResults = flattenIncrementalResults;
976
+ async function* ensureAsyncIterable(someExecutionResult) {
977
+ if ('initialResult' in someExecutionResult) {
978
+ yield* flattenIncrementalResults(someExecutionResult);
979
+ }
980
+ else {
981
+ yield someExecutionResult;
982
+ }
983
+ }
1007
984
  function mapSourceToResponse(exeContext, resultOrStream) {
1008
985
  if (!(0, utils_1.isAsyncIterable)(resultOrStream)) {
1009
986
  return resultOrStream;
@@ -1014,13 +991,13 @@ function mapSourceToResponse(exeContext, resultOrStream) {
1014
991
  // the GraphQL specification. The `execute` function provides the
1015
992
  // "ExecuteSubscriptionEvent" algorithm, as it is nearly identical to the
1016
993
  // "ExecuteQuery" algorithm, for which `execute` is also used.
1017
- return (0, utils_1.mapAsyncIterator)(resultOrStream[Symbol.asyncIterator](), (payload) => executeOperation(buildPerEventExecutionContext(exeContext, payload)), (error) => {
994
+ return (0, flattenAsyncIterable_js_1.flattenAsyncIterable)((0, utils_1.mapAsyncIterator)(resultOrStream[Symbol.asyncIterator](), async (payload) => ensureAsyncIterable(await executeOperation(buildPerEventExecutionContext(exeContext, payload))), (error) => {
1018
995
  const wrappedError = (0, utils_1.createGraphQLError)(error.message, {
1019
996
  originalError: error,
1020
997
  nodes: [exeContext.operation],
1021
998
  });
1022
999
  throw wrappedError;
1023
- });
1000
+ }));
1024
1001
  }
1025
1002
  function createSourceEventStreamImpl(exeContext) {
1026
1003
  try {
@@ -1035,14 +1012,14 @@ function createSourceEventStreamImpl(exeContext) {
1035
1012
  }
1036
1013
  }
1037
1014
  function executeSubscription(exeContext) {
1038
- const { schema, fragments, operation, variableValues, rootValue } = exeContext;
1015
+ const { schema, fragments, operation, variableValues, rootValue, errorWithIncrementalSubscription, } = exeContext;
1039
1016
  const rootType = schema.getSubscriptionType();
1040
1017
  if (rootType == null) {
1041
1018
  throw (0, utils_1.createGraphQLError)('Schema is not configured to execute subscription operation.', {
1042
1019
  nodes: operation,
1043
1020
  });
1044
1021
  }
1045
- const { groupedFieldSet } = (0, collectFields_js_1.collectFields)(schema, fragments, variableValues, rootType, operation);
1022
+ const { groupedFieldSet } = (0, collectFields_js_1.collectFields)(schema, fragments, variableValues, rootType, operation.selectionSet, errorWithIncrementalSubscription);
1046
1023
  const firstRootField = [...groupedFieldSet.entries()][0];
1047
1024
  const [responseName, fieldGroup] = firstRootField;
1048
1025
  const fieldName = fieldGroup[0].node.name.value;
@@ -0,0 +1,89 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.flattenAsyncIterable = void 0;
4
+ /**
5
+ * Given an AsyncIterable of AsyncIterables, flatten all yielded results into a
6
+ * single AsyncIterable.
7
+ */
8
+ function flattenAsyncIterable(iterable) {
9
+ // You might think this whole function could be replaced with
10
+ //
11
+ // async function* flattenAsyncIterable(iterable) {
12
+ // for await (const subIterator of iterable) {
13
+ // yield* subIterator;
14
+ // }
15
+ // }
16
+ //
17
+ // but calling `.return()` on the iterator it returns won't interrupt the `for await`.
18
+ const topIterator = iterable[Symbol.asyncIterator]();
19
+ let currentNestedIterator;
20
+ let waitForCurrentNestedIterator;
21
+ let done = false;
22
+ async function next() {
23
+ if (done) {
24
+ return { value: undefined, done: true };
25
+ }
26
+ try {
27
+ if (!currentNestedIterator) {
28
+ // Somebody else is getting it already.
29
+ if (waitForCurrentNestedIterator) {
30
+ await waitForCurrentNestedIterator;
31
+ return await next();
32
+ }
33
+ // Nobody else is getting it. We should!
34
+ let resolve;
35
+ waitForCurrentNestedIterator = new Promise(r => {
36
+ resolve = r;
37
+ });
38
+ const topIteratorResult = await topIterator.next();
39
+ if (topIteratorResult.done) {
40
+ // Given that done only ever transitions from false to true,
41
+ // require-atomic-updates is being unnecessarily cautious.
42
+ done = true;
43
+ return await next();
44
+ }
45
+ // eslint is making a reasonable point here, but we've explicitly protected
46
+ // ourself from the race condition by ensuring that only the single call
47
+ // that assigns to waitForCurrentNestedIterator is allowed to assign to
48
+ // currentNestedIterator or waitForCurrentNestedIterator.
49
+ currentNestedIterator = topIteratorResult.value[Symbol.asyncIterator]();
50
+ waitForCurrentNestedIterator = undefined;
51
+ resolve();
52
+ return await next();
53
+ }
54
+ const rememberCurrentNestedIterator = currentNestedIterator;
55
+ const nestedIteratorResult = await currentNestedIterator.next();
56
+ if (!nestedIteratorResult.done) {
57
+ return nestedIteratorResult;
58
+ }
59
+ // The nested iterator is done. If it's still the current one, make it not
60
+ // current. (If it's not the current one, somebody else has made us move on.)
61
+ if (currentNestedIterator === rememberCurrentNestedIterator) {
62
+ currentNestedIterator = undefined;
63
+ }
64
+ return await next();
65
+ }
66
+ catch (err) {
67
+ done = true;
68
+ throw err;
69
+ }
70
+ }
71
+ return {
72
+ next,
73
+ async return() {
74
+ done = true;
75
+ await Promise.all([currentNestedIterator?.return?.(), topIterator.return?.()]);
76
+ return { value: undefined, done: true };
77
+ },
78
+ async throw(error) {
79
+ done = true;
80
+ await Promise.all([currentNestedIterator?.throw?.(error), topIterator.throw?.(error)]);
81
+ /* c8 ignore next */
82
+ throw error;
83
+ },
84
+ [Symbol.asyncIterator]() {
85
+ return this;
86
+ },
87
+ };
88
+ }
89
+ exports.flattenAsyncIterable = flattenAsyncIterable;
@@ -11,7 +11,7 @@ import { invariant } from './invariant.js';
11
11
  *
12
12
  * @internal
13
13
  */
14
- export function collectFields(schema, fragments, variableValues, runtimeType, operation) {
14
+ export function collectFields(schema, fragments, variableValues, runtimeType, selectionSet, errorWithIncrementalSubscription) {
15
15
  const groupedFieldSet = new AccumulatorMap();
16
16
  const newDeferUsages = [];
17
17
  const context = {
@@ -19,10 +19,10 @@ export function collectFields(schema, fragments, variableValues, runtimeType, op
19
19
  fragments,
20
20
  variableValues,
21
21
  runtimeType,
22
- operation,
22
+ errorWithIncrementalSubscription,
23
23
  visitedFragmentNames: new Set(),
24
24
  };
25
- collectFieldsImpl(context, operation.selectionSet, groupedFieldSet, newDeferUsages);
25
+ collectFieldsImpl(context, selectionSet, groupedFieldSet, newDeferUsages);
26
26
  return { groupedFieldSet, newDeferUsages };
27
27
  }
28
28
  /**
@@ -35,13 +35,13 @@ export function collectFields(schema, fragments, variableValues, runtimeType, op
35
35
  *
36
36
  * @internal
37
37
  */
38
- export function collectSubfields(schema, fragments, variableValues, operation, returnType, fieldGroup) {
38
+ export function collectSubfields(schema, fragments, variableValues, errorWithIncrementalSubscription, returnType, fieldGroup) {
39
39
  const context = {
40
40
  schema,
41
41
  fragments,
42
42
  variableValues,
43
43
  runtimeType: returnType,
44
- operation,
44
+ errorWithIncrementalSubscription,
45
45
  visitedFragmentNames: new Set(),
46
46
  };
47
47
  const subGroupedFieldSet = new AccumulatorMap();
@@ -58,7 +58,7 @@ export function collectSubfields(schema, fragments, variableValues, operation, r
58
58
  };
59
59
  }
60
60
  function collectFieldsImpl(context, selectionSet, groupedFieldSet, newDeferUsages, deferUsage) {
61
- const { schema, fragments, variableValues, runtimeType, operation, visitedFragmentNames } = context;
61
+ const { schema, fragments, variableValues, runtimeType, errorWithIncrementalSubscription, visitedFragmentNames, } = context;
62
62
  for (const selection of selectionSet.selections) {
63
63
  switch (selection.kind) {
64
64
  case Kind.FIELD: {
@@ -76,7 +76,7 @@ function collectFieldsImpl(context, selectionSet, groupedFieldSet, newDeferUsage
76
76
  !doesFragmentConditionMatch(schema, selection, runtimeType)) {
77
77
  continue;
78
78
  }
79
- const newDeferUsage = getDeferUsage(operation, variableValues, selection, deferUsage);
79
+ const newDeferUsage = getDeferUsage(errorWithIncrementalSubscription, variableValues, selection, deferUsage);
80
80
  if (!newDeferUsage) {
81
81
  collectFieldsImpl(context, selection.selectionSet, groupedFieldSet, newDeferUsages, deferUsage);
82
82
  }
@@ -88,7 +88,7 @@ function collectFieldsImpl(context, selectionSet, groupedFieldSet, newDeferUsage
88
88
  }
89
89
  case Kind.FRAGMENT_SPREAD: {
90
90
  const fragName = selection.name.value;
91
- const newDeferUsage = getDeferUsage(operation, variableValues, selection, deferUsage);
91
+ const newDeferUsage = getDeferUsage(errorWithIncrementalSubscription, variableValues, selection, deferUsage);
92
92
  if (!newDeferUsage &&
93
93
  (visitedFragmentNames.has(fragName) || !shouldIncludeNode(variableValues, selection))) {
94
94
  continue;
@@ -115,7 +115,7 @@ function collectFieldsImpl(context, selectionSet, groupedFieldSet, newDeferUsage
115
115
  * deferred based on the experimental flag, defer directive present and
116
116
  * not disabled by the "if" argument.
117
117
  */
118
- function getDeferUsage(operation, variableValues, node, parentDeferUsage) {
118
+ function getDeferUsage(errorWithIncrementalSubscription, variableValues, node, parentDeferUsage) {
119
119
  const defer = getDirectiveValues(GraphQLDeferDirective, node, variableValues);
120
120
  if (!defer) {
121
121
  return;
@@ -123,7 +123,7 @@ function getDeferUsage(operation, variableValues, node, parentDeferUsage) {
123
123
  if (defer['if'] === false) {
124
124
  return;
125
125
  }
126
- invariant(operation.operation !== 'subscription', '`@defer` directive not supported on subscription operations. Disable `@defer` by setting the `if` argument to `false`.');
126
+ invariant(!errorWithIncrementalSubscription, '`@defer` directive not supported on subscription operations. Disable `@defer` by setting the `if` argument to `false`.');
127
127
  return {
128
128
  label: typeof defer['label'] === 'string' ? defer['label'] : undefined,
129
129
  parentDeferUsage,
@@ -4,6 +4,7 @@ import { BoxedPromiseOrValue } from './BoxedPromiseOrValue.js';
4
4
  import { buildFieldPlan } from './buildFieldPlan.js';
5
5
  import { coerceError } from './coerceError.js';
6
6
  import { collectSubfields as _collectSubfields, collectFields, } from './collectFields.js';
7
+ import { flattenAsyncIterable } from './flattenAsyncIterable.js';
7
8
  import { buildIncrementalResponse } from './IncrementalPublisher.js';
8
9
  import { invariant } from './invariant.js';
9
10
  import { promiseForObject } from './promiseForObject.js';
@@ -14,7 +15,7 @@ import { getVariableValues } from './values.js';
14
15
  * type. Memoizing ensures the subfields are not repeatedly calculated, which
15
16
  * saves overhead when resolving lists of values.
16
17
  */
17
- const collectSubfields = memoize3((exeContext, returnType, fieldGroup) => _collectSubfields(exeContext.schema, exeContext.fragments, exeContext.variableValues, exeContext.operation, returnType, fieldGroup));
18
+ const collectSubfields = memoize3((exeContext, returnType, fieldGroup) => _collectSubfields(exeContext.schema, exeContext.fragments, exeContext.variableValues, exeContext.errorWithIncrementalSubscription, returnType, fieldGroup));
18
19
  /**
19
20
  * Implements the "Executing requests" section of the GraphQL specification,
20
21
  * including `@defer` and `@stream` as proposed in
@@ -143,7 +144,7 @@ export const getFragmentsFromDocument = memoize1(function getFragmentsFromDocume
143
144
  * @internal
144
145
  */
145
146
  export function buildExecutionContext(args) {
146
- const { schema, document, rootValue, contextValue, variableValues: rawVariableValues, operationName, fieldResolver, typeResolver, subscribeFieldResolver, enableEarlyExecution, signal, } = args;
147
+ const { schema, document, rootValue, contextValue, variableValues: rawVariableValues, operationName, fieldResolver, typeResolver, subscribeFieldResolver, enableEarlyExecution, errorWithIncrementalSubscription, signal, } = args;
147
148
  // If the schema used for execution is invalid, throw an error.
148
149
  assertValidSchema(schema);
149
150
  const fragments = getFragmentsFromDocument(document);
@@ -193,6 +194,7 @@ export function buildExecutionContext(args) {
193
194
  typeResolver: typeResolver ?? defaultTypeResolver,
194
195
  subscribeFieldResolver: subscribeFieldResolver ?? defaultFieldResolver,
195
196
  enableEarlyExecution: enableEarlyExecution !== false,
197
+ errorWithIncrementalSubscription: operation.operation === 'subscription' && errorWithIncrementalSubscription !== false,
196
198
  signal,
197
199
  errors: undefined,
198
200
  cancellableStreams: undefined,
@@ -214,14 +216,14 @@ function executeOperation(exeContext) {
214
216
  throw exeContext.signal.reason;
215
217
  }
216
218
  try {
217
- const { operation, schema, fragments, variableValues, rootValue } = exeContext;
219
+ const { operation, schema, fragments, variableValues, rootValue, errorWithIncrementalSubscription, } = exeContext;
218
220
  const rootType = getDefinedRootType(schema, operation.operation, [operation]);
219
221
  if (rootType == null) {
220
222
  createGraphQLError(`Schema is not configured to execute ${operation.operation} operation.`, {
221
223
  nodes: operation,
222
224
  });
223
225
  }
224
- const collectedFields = collectFields(schema, fragments, variableValues, rootType, operation);
226
+ const collectedFields = collectFields(schema, fragments, variableValues, rootType, operation.selectionSet, errorWithIncrementalSubscription);
225
227
  let groupedFieldSet = collectedFields.groupedFieldSet;
226
228
  const newDeferUsages = collectedFields.newDeferUsages;
227
229
  let data;
@@ -529,7 +531,7 @@ function getStreamUsage(exeContext, fieldGroup, path) {
529
531
  }
530
532
  invariant(typeof stream['initialCount'] === 'number', 'initialCount must be a number');
531
533
  invariant(stream['initialCount'] >= 0, 'initialCount must be a positive integer');
532
- invariant(exeContext.operation.operation !== 'subscription', '`@stream` directive not supported on subscription operations. Disable `@stream` by setting the `if` argument to `false`.');
534
+ invariant(!exeContext.errorWithIncrementalSubscription, '`@stream` directive not supported on subscription operations. Disable `@stream` by setting the `if` argument to `false`.');
533
535
  const streamedFieldGroup = fieldGroup.map(fieldDetails => ({
534
536
  node: fieldDetails.node,
535
537
  deferUsage: undefined,
@@ -898,39 +900,6 @@ export const defaultFieldResolver = function (source, args, contextValue, info)
898
900
  return property;
899
901
  }
900
902
  };
901
- /**
902
- * Implements the "Subscribe" algorithm described in the GraphQL specification,
903
- * including `@defer` and `@stream` as proposed in
904
- * https://github.com/graphql/graphql-spec/pull/742
905
- *
906
- * Returns a Promise which resolves to either an AsyncIterator (if successful)
907
- * or an ExecutionResult (error). The promise will be rejected if the schema or
908
- * other arguments to this function are invalid, or if the resolved event stream
909
- * is not an async iterable.
910
- *
911
- * If the client-provided arguments to this function do not result in a
912
- * compliant subscription, a GraphQL Response (ExecutionResult) with descriptive
913
- * errors and no data will be returned.
914
- *
915
- * If the source stream could not be created due to faulty subscription resolver
916
- * logic or underlying systems, the promise will resolve to a single
917
- * ExecutionResult containing `errors` and no `data`.
918
- *
919
- * If the operation succeeded, the promise resolves to an AsyncIterator, which
920
- * yields a stream of result representing the response stream.
921
- *
922
- * Each result may be an ExecutionResult with no `hasNext` (if executing the
923
- * event did not use `@defer` or `@stream`), or an
924
- * `InitialIncrementalExecutionResult` or `SubsequentIncrementalExecutionResult`
925
- * (if executing the event used `@defer` or `@stream`). In the case of
926
- * incremental execution results, each event produces a single
927
- * `InitialIncrementalExecutionResult` followed by one or more
928
- * `SubsequentIncrementalExecutionResult`s; all but the last have `hasNext: true`,
929
- * and the last has `hasNext: false`. There is no interleaving between results
930
- * generated from the same original event.
931
- *
932
- * Accepts an object with named arguments.
933
- */
934
903
  export function subscribe(args) {
935
904
  // If a valid execution context cannot be created due to incorrect arguments,
936
905
  // a "Response" with only errors is returned.
@@ -992,6 +961,14 @@ export function flattenIncrementalResults(incrementalResults) {
992
961
  },
993
962
  };
994
963
  }
964
+ async function* ensureAsyncIterable(someExecutionResult) {
965
+ if ('initialResult' in someExecutionResult) {
966
+ yield* flattenIncrementalResults(someExecutionResult);
967
+ }
968
+ else {
969
+ yield someExecutionResult;
970
+ }
971
+ }
995
972
  function mapSourceToResponse(exeContext, resultOrStream) {
996
973
  if (!isAsyncIterable(resultOrStream)) {
997
974
  return resultOrStream;
@@ -1002,13 +979,13 @@ function mapSourceToResponse(exeContext, resultOrStream) {
1002
979
  // the GraphQL specification. The `execute` function provides the
1003
980
  // "ExecuteSubscriptionEvent" algorithm, as it is nearly identical to the
1004
981
  // "ExecuteQuery" algorithm, for which `execute` is also used.
1005
- return mapAsyncIterator(resultOrStream[Symbol.asyncIterator](), (payload) => executeOperation(buildPerEventExecutionContext(exeContext, payload)), (error) => {
982
+ return flattenAsyncIterable(mapAsyncIterator(resultOrStream[Symbol.asyncIterator](), async (payload) => ensureAsyncIterable(await executeOperation(buildPerEventExecutionContext(exeContext, payload))), (error) => {
1006
983
  const wrappedError = createGraphQLError(error.message, {
1007
984
  originalError: error,
1008
985
  nodes: [exeContext.operation],
1009
986
  });
1010
987
  throw wrappedError;
1011
- });
988
+ }));
1012
989
  }
1013
990
  function createSourceEventStreamImpl(exeContext) {
1014
991
  try {
@@ -1023,14 +1000,14 @@ function createSourceEventStreamImpl(exeContext) {
1023
1000
  }
1024
1001
  }
1025
1002
  function executeSubscription(exeContext) {
1026
- const { schema, fragments, operation, variableValues, rootValue } = exeContext;
1003
+ const { schema, fragments, operation, variableValues, rootValue, errorWithIncrementalSubscription, } = exeContext;
1027
1004
  const rootType = schema.getSubscriptionType();
1028
1005
  if (rootType == null) {
1029
1006
  throw createGraphQLError('Schema is not configured to execute subscription operation.', {
1030
1007
  nodes: operation,
1031
1008
  });
1032
1009
  }
1033
- const { groupedFieldSet } = collectFields(schema, fragments, variableValues, rootType, operation);
1010
+ const { groupedFieldSet } = collectFields(schema, fragments, variableValues, rootType, operation.selectionSet, errorWithIncrementalSubscription);
1034
1011
  const firstRootField = [...groupedFieldSet.entries()][0];
1035
1012
  const [responseName, fieldGroup] = firstRootField;
1036
1013
  const fieldName = fieldGroup[0].node.name.value;
@@ -0,0 +1,85 @@
1
+ /**
2
+ * Given an AsyncIterable of AsyncIterables, flatten all yielded results into a
3
+ * single AsyncIterable.
4
+ */
5
+ export function flattenAsyncIterable(iterable) {
6
+ // You might think this whole function could be replaced with
7
+ //
8
+ // async function* flattenAsyncIterable(iterable) {
9
+ // for await (const subIterator of iterable) {
10
+ // yield* subIterator;
11
+ // }
12
+ // }
13
+ //
14
+ // but calling `.return()` on the iterator it returns won't interrupt the `for await`.
15
+ const topIterator = iterable[Symbol.asyncIterator]();
16
+ let currentNestedIterator;
17
+ let waitForCurrentNestedIterator;
18
+ let done = false;
19
+ async function next() {
20
+ if (done) {
21
+ return { value: undefined, done: true };
22
+ }
23
+ try {
24
+ if (!currentNestedIterator) {
25
+ // Somebody else is getting it already.
26
+ if (waitForCurrentNestedIterator) {
27
+ await waitForCurrentNestedIterator;
28
+ return await next();
29
+ }
30
+ // Nobody else is getting it. We should!
31
+ let resolve;
32
+ waitForCurrentNestedIterator = new Promise(r => {
33
+ resolve = r;
34
+ });
35
+ const topIteratorResult = await topIterator.next();
36
+ if (topIteratorResult.done) {
37
+ // Given that done only ever transitions from false to true,
38
+ // require-atomic-updates is being unnecessarily cautious.
39
+ done = true;
40
+ return await next();
41
+ }
42
+ // eslint is making a reasonable point here, but we've explicitly protected
43
+ // ourself from the race condition by ensuring that only the single call
44
+ // that assigns to waitForCurrentNestedIterator is allowed to assign to
45
+ // currentNestedIterator or waitForCurrentNestedIterator.
46
+ currentNestedIterator = topIteratorResult.value[Symbol.asyncIterator]();
47
+ waitForCurrentNestedIterator = undefined;
48
+ resolve();
49
+ return await next();
50
+ }
51
+ const rememberCurrentNestedIterator = currentNestedIterator;
52
+ const nestedIteratorResult = await currentNestedIterator.next();
53
+ if (!nestedIteratorResult.done) {
54
+ return nestedIteratorResult;
55
+ }
56
+ // The nested iterator is done. If it's still the current one, make it not
57
+ // current. (If it's not the current one, somebody else has made us move on.)
58
+ if (currentNestedIterator === rememberCurrentNestedIterator) {
59
+ currentNestedIterator = undefined;
60
+ }
61
+ return await next();
62
+ }
63
+ catch (err) {
64
+ done = true;
65
+ throw err;
66
+ }
67
+ }
68
+ return {
69
+ next,
70
+ async return() {
71
+ done = true;
72
+ await Promise.all([currentNestedIterator?.return?.(), topIterator.return?.()]);
73
+ return { value: undefined, done: true };
74
+ },
75
+ async throw(error) {
76
+ done = true;
77
+ await Promise.all([currentNestedIterator?.throw?.(error), topIterator.throw?.(error)]);
78
+ /* c8 ignore next */
79
+ throw error;
80
+ },
81
+ [Symbol.asyncIterator]() {
82
+ return this;
83
+ },
84
+ };
85
+ }
package/package.json CHANGED
@@ -1,12 +1,12 @@
1
1
  {
2
2
  "name": "@graphql-tools/executor",
3
- "version": "2.0.0-alpha-20240620175016-1a4246713c9c2dcf5e3debfccfa47e0efd992528",
3
+ "version": "2.0.0-alpha-20240702150657-b50b1dda80c0083a63896cdbfd42efdcdf4c0b98",
4
4
  "sideEffects": false,
5
5
  "peerDependencies": {
6
6
  "graphql": "^14.0.0 || ^15.0.0 || ^16.0.0 || ^17.0.0"
7
7
  },
8
8
  "dependencies": {
9
- "@graphql-tools/utils": "10.3.0-alpha-20240620175016-1a4246713c9c2dcf5e3debfccfa47e0efd992528",
9
+ "@graphql-tools/utils": "10.3.0-alpha-20240702150657-b50b1dda80c0083a63896cdbfd42efdcdf4c0b98",
10
10
  "@graphql-typed-document-node/core": "3.2.0",
11
11
  "@repeaterjs/repeater": "^3.0.4",
12
12
  "tslib": "^2.4.0",
@@ -1,4 +1,4 @@
1
- import type { FieldNode, FragmentDefinitionNode, GraphQLObjectType, GraphQLSchema, OperationDefinitionNode } from 'graphql';
1
+ import type { FieldNode, FragmentDefinitionNode, GraphQLObjectType, GraphQLSchema, SelectionSetNode } from 'graphql';
2
2
  export interface DeferUsage {
3
3
  label: string | undefined;
4
4
  parentDeferUsage: DeferUsage | undefined;
@@ -18,7 +18,7 @@ export type GroupedFieldSet = ReadonlyMap<string, FieldGroup>;
18
18
  *
19
19
  * @internal
20
20
  */
21
- export declare function collectFields<TVariables = any>(schema: GraphQLSchema, fragments: Record<string, FragmentDefinitionNode>, variableValues: TVariables, runtimeType: GraphQLObjectType, operation: OperationDefinitionNode): {
21
+ export declare function collectFields<TVariables = any>(schema: GraphQLSchema, fragments: Record<string, FragmentDefinitionNode>, variableValues: TVariables, runtimeType: GraphQLObjectType, selectionSet: SelectionSetNode, errorWithIncrementalSubscription: boolean): {
22
22
  groupedFieldSet: GroupedFieldSet;
23
23
  newDeferUsages: ReadonlyArray<DeferUsage>;
24
24
  };
@@ -34,7 +34,7 @@ export declare function collectFields<TVariables = any>(schema: GraphQLSchema, f
34
34
  */
35
35
  export declare function collectSubfields(schema: GraphQLSchema, fragments: Record<string, FragmentDefinitionNode>, variableValues: {
36
36
  [variable: string]: unknown;
37
- }, operation: OperationDefinitionNode, returnType: GraphQLObjectType, fieldGroup: FieldGroup): {
37
+ }, errorWithIncrementalSubscription: boolean, returnType: GraphQLObjectType, fieldGroup: FieldGroup): {
38
38
  groupedFieldSet: GroupedFieldSet;
39
39
  newDeferUsages: ReadonlyArray<DeferUsage>;
40
40
  };
@@ -1,4 +1,4 @@
1
- import type { FieldNode, FragmentDefinitionNode, GraphQLObjectType, GraphQLSchema, OperationDefinitionNode } from 'graphql';
1
+ import type { FieldNode, FragmentDefinitionNode, GraphQLObjectType, GraphQLSchema, SelectionSetNode } from 'graphql';
2
2
  export interface DeferUsage {
3
3
  label: string | undefined;
4
4
  parentDeferUsage: DeferUsage | undefined;
@@ -18,7 +18,7 @@ export type GroupedFieldSet = ReadonlyMap<string, FieldGroup>;
18
18
  *
19
19
  * @internal
20
20
  */
21
- export declare function collectFields<TVariables = any>(schema: GraphQLSchema, fragments: Record<string, FragmentDefinitionNode>, variableValues: TVariables, runtimeType: GraphQLObjectType, operation: OperationDefinitionNode): {
21
+ export declare function collectFields<TVariables = any>(schema: GraphQLSchema, fragments: Record<string, FragmentDefinitionNode>, variableValues: TVariables, runtimeType: GraphQLObjectType, selectionSet: SelectionSetNode, errorWithIncrementalSubscription: boolean): {
22
22
  groupedFieldSet: GroupedFieldSet;
23
23
  newDeferUsages: ReadonlyArray<DeferUsage>;
24
24
  };
@@ -34,7 +34,7 @@ export declare function collectFields<TVariables = any>(schema: GraphQLSchema, f
34
34
  */
35
35
  export declare function collectSubfields(schema: GraphQLSchema, fragments: Record<string, FragmentDefinitionNode>, variableValues: {
36
36
  [variable: string]: unknown;
37
- }, operation: OperationDefinitionNode, returnType: GraphQLObjectType, fieldGroup: FieldGroup): {
37
+ }, errorWithIncrementalSubscription: boolean, returnType: GraphQLObjectType, fieldGroup: FieldGroup): {
38
38
  groupedFieldSet: GroupedFieldSet;
39
39
  newDeferUsages: ReadonlyArray<DeferUsage>;
40
40
  };
@@ -38,6 +38,7 @@ export interface ExecutionContext<TVariables = any, TContext = any> {
38
38
  typeResolver: GraphQLTypeResolver<any, TContext>;
39
39
  subscribeFieldResolver: GraphQLFieldResolver<any, TContext>;
40
40
  enableEarlyExecution: boolean;
41
+ errorWithIncrementalSubscription: boolean;
41
42
  signal: AbortSignal | undefined;
42
43
  errors: Map<Path | undefined, GraphQLError> | undefined;
43
44
  cancellableStreams: Set<CancellableStreamRecord> | undefined;
@@ -54,6 +55,7 @@ export interface ExecutionArgs<TData = any, TVariables = any, TContext = any> {
54
55
  typeResolver?: Maybe<GraphQLTypeResolver<any, TContext>>;
55
56
  subscribeFieldResolver?: Maybe<GraphQLFieldResolver<any, TContext>>;
56
57
  enableEarlyExecution?: Maybe<boolean>;
58
+ errorWithIncrementalSubscription?: Maybe<boolean>;
57
59
  signal?: AbortSignal;
58
60
  }
59
61
  /**
@@ -149,6 +151,9 @@ export declare const defaultFieldResolver: GraphQLFieldResolver<unknown, unknown
149
151
  *
150
152
  * Accepts an object with named arguments.
151
153
  */
154
+ export declare function subscribe<TData = any, TVariables = any, TContext = any>(args: ExecutionArgs<TData, TVariables, TContext> & {
155
+ errorWithIncrementalSubscription: true | undefined | null;
156
+ }): MaybePromise<AsyncGenerator<SingularExecutionResult<TData>, void, void> | SingularExecutionResult<TData>>;
152
157
  export declare function subscribe<TData = any, TVariables = any, TContext = any>(args: ExecutionArgs<TData, TVariables, TContext>): MaybePromise<AsyncGenerator<SingularExecutionResult<TData> | InitialIncrementalExecutionResult<TData> | SubsequentIncrementalExecutionResult<TData>, void, void> | SingularExecutionResult<TData>>;
153
158
  export declare function flattenIncrementalResults<TData>(incrementalResults: IncrementalExecutionResults<TData>): AsyncGenerator<SubsequentIncrementalExecutionResult<TData, Record<string, unknown>>, void, void>;
154
159
  /**
@@ -38,6 +38,7 @@ export interface ExecutionContext<TVariables = any, TContext = any> {
38
38
  typeResolver: GraphQLTypeResolver<any, TContext>;
39
39
  subscribeFieldResolver: GraphQLFieldResolver<any, TContext>;
40
40
  enableEarlyExecution: boolean;
41
+ errorWithIncrementalSubscription: boolean;
41
42
  signal: AbortSignal | undefined;
42
43
  errors: Map<Path | undefined, GraphQLError> | undefined;
43
44
  cancellableStreams: Set<CancellableStreamRecord> | undefined;
@@ -54,6 +55,7 @@ export interface ExecutionArgs<TData = any, TVariables = any, TContext = any> {
54
55
  typeResolver?: Maybe<GraphQLTypeResolver<any, TContext>>;
55
56
  subscribeFieldResolver?: Maybe<GraphQLFieldResolver<any, TContext>>;
56
57
  enableEarlyExecution?: Maybe<boolean>;
58
+ errorWithIncrementalSubscription?: Maybe<boolean>;
57
59
  signal?: AbortSignal;
58
60
  }
59
61
  /**
@@ -149,6 +151,9 @@ export declare const defaultFieldResolver: GraphQLFieldResolver<unknown, unknown
149
151
  *
150
152
  * Accepts an object with named arguments.
151
153
  */
154
+ export declare function subscribe<TData = any, TVariables = any, TContext = any>(args: ExecutionArgs<TData, TVariables, TContext> & {
155
+ errorWithIncrementalSubscription: true | undefined | null;
156
+ }): MaybePromise<AsyncGenerator<SingularExecutionResult<TData>, void, void> | SingularExecutionResult<TData>>;
152
157
  export declare function subscribe<TData = any, TVariables = any, TContext = any>(args: ExecutionArgs<TData, TVariables, TContext>): MaybePromise<AsyncGenerator<SingularExecutionResult<TData> | InitialIncrementalExecutionResult<TData> | SubsequentIncrementalExecutionResult<TData>, void, void> | SingularExecutionResult<TData>>;
153
158
  export declare function flattenIncrementalResults<TData>(incrementalResults: IncrementalExecutionResults<TData>): AsyncGenerator<SubsequentIncrementalExecutionResult<TData, Record<string, unknown>>, void, void>;
154
159
  /**
@@ -0,0 +1,7 @@
1
+ type AsyncIterableOrGenerator<T> = AsyncGenerator<T, void, void> | AsyncIterable<T>;
2
+ /**
3
+ * Given an AsyncIterable of AsyncIterables, flatten all yielded results into a
4
+ * single AsyncIterable.
5
+ */
6
+ export declare function flattenAsyncIterable<T>(iterable: AsyncIterableOrGenerator<AsyncIterableOrGenerator<T>>): AsyncGenerator<T, void, void>;
7
+ export {};
@@ -0,0 +1,7 @@
1
+ type AsyncIterableOrGenerator<T> = AsyncGenerator<T, void, void> | AsyncIterable<T>;
2
+ /**
3
+ * Given an AsyncIterable of AsyncIterables, flatten all yielded results into a
4
+ * single AsyncIterable.
5
+ */
6
+ export declare function flattenAsyncIterable<T>(iterable: AsyncIterableOrGenerator<AsyncIterableOrGenerator<T>>): AsyncGenerator<T, void, void>;
7
+ export {};