@graphql-tools/executor 0.0.1 → 0.0.2-alpha-20221029152711-14f4f7a7
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.
- package/cjs/directives/defer.js +23 -0
- package/cjs/directives/index.js +5 -0
- package/cjs/directives/stream.js +28 -0
- package/cjs/execution/AccumulatorMap.js +21 -0
- package/cjs/execution/collectFields.js +126 -0
- package/cjs/execution/execute.js +656 -109
- package/cjs/execution/flattenAsyncIterable.js +91 -0
- package/cjs/execution/invariant.js +9 -0
- package/cjs/execution/promiseForObject.js +21 -0
- package/cjs/index.js +1 -0
- package/esm/directives/defer.js +20 -0
- package/esm/directives/index.js +2 -0
- package/esm/directives/stream.js +25 -0
- package/esm/execution/AccumulatorMap.js +17 -0
- package/esm/execution/collectFields.js +122 -0
- package/esm/execution/execute.js +653 -108
- package/esm/execution/flattenAsyncIterable.js +87 -0
- package/esm/execution/invariant.js +5 -0
- package/esm/execution/promiseForObject.js +17 -0
- package/esm/index.js +1 -0
- package/package.json +2 -2
- package/typings/directives/defer.d.cts +5 -0
- package/typings/directives/defer.d.ts +5 -0
- package/typings/directives/index.d.cts +2 -0
- package/typings/directives/index.d.ts +2 -0
- package/typings/directives/stream.d.cts +5 -0
- package/typings/directives/stream.d.ts +5 -0
- package/typings/execution/AccumulatorMap.d.cts +7 -0
- package/typings/execution/AccumulatorMap.d.ts +7 -0
- package/typings/execution/collectFields.d.cts +32 -0
- package/typings/execution/collectFields.d.ts +32 -0
- package/typings/execution/execute.d.cts +167 -22
- package/typings/execution/execute.d.ts +167 -22
- package/typings/execution/flattenAsyncIterable.d.cts +7 -0
- package/typings/execution/flattenAsyncIterable.d.ts +7 -0
- package/typings/execution/invariant.d.cts +1 -0
- package/typings/execution/invariant.d.ts +1 -0
- package/typings/execution/promiseForObject.d.cts +12 -0
- package/typings/execution/promiseForObject.d.ts +12 -0
- package/typings/index.d.cts +1 -0
- package/typings/index.d.ts +1 -0
- package/cjs/execution/subscribe.js +0 -158
- package/esm/execution/subscribe.js +0 -153
- package/typings/execution/subscribe.d.cts +0 -59
- package/typings/execution/subscribe.d.ts +0 -59
package/esm/execution/execute.js
CHANGED
|
@@ -1,6 +1,18 @@
|
|
|
1
|
-
import { locatedError, Kind, isAbstractType, isLeafType, isListType, isNonNullType, isObjectType, assertValidSchema, SchemaMetaFieldDef, TypeMetaFieldDef, TypeNameMetaFieldDef, } from 'graphql';
|
|
2
|
-
import {
|
|
1
|
+
import { locatedError, Kind, isAbstractType, isLeafType, isListType, isNonNullType, isObjectType, assertValidSchema, getDirectiveValues, SchemaMetaFieldDef, TypeMetaFieldDef, TypeNameMetaFieldDef, } from 'graphql';
|
|
2
|
+
import { createGraphQLError, inspect, isAsyncIterable, isIterableObject, isObjectLike, isPromise, pathToArray, addPath, getArgumentValues, promiseReduce, memoize3, getDefinedRootType, mapAsyncIterator, } from '@graphql-tools/utils';
|
|
3
3
|
import { getVariableValues } from './values.js';
|
|
4
|
+
import { promiseForObject } from './promiseForObject.js';
|
|
5
|
+
import { flattenAsyncIterable } from './flattenAsyncIterable.js';
|
|
6
|
+
import { collectFields, collectSubfields as _collectSubfields } from './collectFields.js';
|
|
7
|
+
import { GraphQLStreamDirective } from '../directives/index.js';
|
|
8
|
+
import { invariant } from './invariant.js';
|
|
9
|
+
/**
|
|
10
|
+
* A memoized collection of relevant subfields with regard to the return
|
|
11
|
+
* type. Memoizing ensures the subfields are not repeatedly calculated, which
|
|
12
|
+
* saves overhead when resolving lists of values.
|
|
13
|
+
*/
|
|
14
|
+
const collectSubfields = memoize3((exeContext, returnType, fieldNodes) => _collectSubfields(exeContext.schema, exeContext.fragments, exeContext.variableValues, returnType, fieldNodes));
|
|
15
|
+
const UNEXPECTED_MULTIPLE_PAYLOADS = 'Executing this GraphQL operation would unexpectedly produce multiple payloads (due to @defer or @stream directive)';
|
|
4
16
|
/**
|
|
5
17
|
* Implements the "Executing requests" section of the GraphQL specification.
|
|
6
18
|
*
|
|
@@ -10,8 +22,43 @@ import { getVariableValues } from './values.js';
|
|
|
10
22
|
*
|
|
11
23
|
* If the arguments to this function do not result in a legal execution context,
|
|
12
24
|
* a GraphQLError will be thrown immediately explaining the invalid input.
|
|
25
|
+
*
|
|
26
|
+
* This function does not support incremental delivery (`@defer` and `@stream`).
|
|
27
|
+
* If an operation which would defer or stream data is executed with this
|
|
28
|
+
* function, it will throw or resolve to an object containing an error instead.
|
|
29
|
+
* Use `experimentalExecuteIncrementally` if you want to support incremental
|
|
30
|
+
* delivery.
|
|
13
31
|
*/
|
|
14
32
|
export function execute(args) {
|
|
33
|
+
const result = experimentalExecuteIncrementally(args);
|
|
34
|
+
if (!isPromise(result)) {
|
|
35
|
+
if ('initialResult' in result) {
|
|
36
|
+
throw new Error(UNEXPECTED_MULTIPLE_PAYLOADS);
|
|
37
|
+
}
|
|
38
|
+
return result;
|
|
39
|
+
}
|
|
40
|
+
return result.then(incrementalResult => {
|
|
41
|
+
if ('initialResult' in incrementalResult) {
|
|
42
|
+
return {
|
|
43
|
+
errors: [createGraphQLError(UNEXPECTED_MULTIPLE_PAYLOADS)],
|
|
44
|
+
};
|
|
45
|
+
}
|
|
46
|
+
return incrementalResult;
|
|
47
|
+
});
|
|
48
|
+
}
|
|
49
|
+
/**
|
|
50
|
+
* Implements the "Executing requests" section of the GraphQL specification,
|
|
51
|
+
* including `@defer` and `@stream` as proposed in
|
|
52
|
+
* https://github.com/graphql/graphql-spec/pull/742
|
|
53
|
+
*
|
|
54
|
+
* This function returns a Promise of an ExperimentalIncrementalExecutionResults
|
|
55
|
+
* object. This object either consists of a single ExecutionResult, or an
|
|
56
|
+
* object containing an `initialResult` and a stream of `subsequentResults`.
|
|
57
|
+
*
|
|
58
|
+
* If the arguments to this function do not result in a legal execution context,
|
|
59
|
+
* a GraphQLError will be thrown immediately explaining the invalid input.
|
|
60
|
+
*/
|
|
61
|
+
export function experimentalExecuteIncrementally(args) {
|
|
15
62
|
// If a valid execution context cannot be created due to incorrect arguments,
|
|
16
63
|
// a "Response" with only errors is returned.
|
|
17
64
|
const exeContext = buildExecutionContext(args);
|
|
@@ -36,12 +83,34 @@ function executeImpl(exeContext) {
|
|
|
36
83
|
try {
|
|
37
84
|
const result = executeOperation(exeContext);
|
|
38
85
|
if (isPromise(result)) {
|
|
39
|
-
return result.then(data =>
|
|
86
|
+
return result.then(data => {
|
|
87
|
+
const initialResult = buildResponse(data, exeContext.errors);
|
|
88
|
+
if (exeContext.subsequentPayloads.size > 0) {
|
|
89
|
+
return {
|
|
90
|
+
initialResult: {
|
|
91
|
+
...initialResult,
|
|
92
|
+
hasNext: true,
|
|
93
|
+
},
|
|
94
|
+
subsequentResults: yieldSubsequentPayloads(exeContext),
|
|
95
|
+
};
|
|
96
|
+
}
|
|
97
|
+
return initialResult;
|
|
98
|
+
}, error => {
|
|
40
99
|
exeContext.errors.push(error);
|
|
41
100
|
return buildResponse(null, exeContext.errors);
|
|
42
101
|
});
|
|
43
102
|
}
|
|
44
|
-
|
|
103
|
+
const initialResult = buildResponse(result, exeContext.errors);
|
|
104
|
+
if (exeContext.subsequentPayloads.size > 0) {
|
|
105
|
+
return {
|
|
106
|
+
initialResult: {
|
|
107
|
+
...initialResult,
|
|
108
|
+
hasNext: true,
|
|
109
|
+
},
|
|
110
|
+
subsequentResults: yieldSubsequentPayloads(exeContext),
|
|
111
|
+
};
|
|
112
|
+
}
|
|
113
|
+
return initialResult;
|
|
45
114
|
}
|
|
46
115
|
catch (error) {
|
|
47
116
|
exeContext.errors.push(error);
|
|
@@ -54,9 +123,9 @@ function executeImpl(exeContext) {
|
|
|
54
123
|
* that all field resolvers are also synchronous.
|
|
55
124
|
*/
|
|
56
125
|
export function executeSync(args) {
|
|
57
|
-
const result =
|
|
126
|
+
const result = experimentalExecuteIncrementally(args);
|
|
58
127
|
// Assert that the execution was synchronous.
|
|
59
|
-
if (isPromise(result)) {
|
|
128
|
+
if (isPromise(result) || 'initialResult' in result) {
|
|
60
129
|
throw new Error('GraphQL execution failed to complete synchronously.');
|
|
61
130
|
}
|
|
62
131
|
return result;
|
|
@@ -142,6 +211,7 @@ export function buildExecutionContext(args) {
|
|
|
142
211
|
fieldResolver: fieldResolver !== null && fieldResolver !== void 0 ? fieldResolver : defaultFieldResolver,
|
|
143
212
|
typeResolver: typeResolver !== null && typeResolver !== void 0 ? typeResolver : defaultTypeResolver,
|
|
144
213
|
subscribeFieldResolver: subscribeFieldResolver !== null && subscribeFieldResolver !== void 0 ? subscribeFieldResolver : defaultFieldResolver,
|
|
214
|
+
subsequentPayloads: new Set(),
|
|
145
215
|
errors: [],
|
|
146
216
|
};
|
|
147
217
|
}
|
|
@@ -149,6 +219,7 @@ function buildPerEventExecutionContext(exeContext, payload) {
|
|
|
149
219
|
return {
|
|
150
220
|
...exeContext,
|
|
151
221
|
rootValue: payload,
|
|
222
|
+
subsequentPayloads: new Set(),
|
|
152
223
|
errors: [],
|
|
153
224
|
};
|
|
154
225
|
}
|
|
@@ -158,26 +229,32 @@ function buildPerEventExecutionContext(exeContext, payload) {
|
|
|
158
229
|
function executeOperation(exeContext) {
|
|
159
230
|
const { operation, schema, fragments, variableValues, rootValue } = exeContext;
|
|
160
231
|
const rootType = getDefinedRootType(schema, operation.operation, [operation]);
|
|
161
|
-
|
|
232
|
+
if (rootType == null) {
|
|
233
|
+
createGraphQLError(`Schema is not configured to execute ${operation.operation} operation.`, {
|
|
234
|
+
nodes: operation,
|
|
235
|
+
});
|
|
236
|
+
}
|
|
237
|
+
const { fields: rootFields, patches } = collectFields(schema, fragments, variableValues, rootType, operation.selectionSet);
|
|
162
238
|
const path = undefined;
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
239
|
+
let result;
|
|
240
|
+
if (operation.operation === 'mutation') {
|
|
241
|
+
result = executeFieldsSerially(exeContext, rootType, rootValue, path, rootFields);
|
|
242
|
+
}
|
|
243
|
+
else {
|
|
244
|
+
result = executeFields(exeContext, rootType, rootValue, path, rootFields);
|
|
245
|
+
}
|
|
246
|
+
for (const patch of patches) {
|
|
247
|
+
const { label, fields: patchFields } = patch;
|
|
248
|
+
executeDeferredFragment(exeContext, rootType, rootValue, patchFields, label, path);
|
|
249
|
+
}
|
|
250
|
+
return result;
|
|
174
251
|
}
|
|
175
252
|
/**
|
|
176
253
|
* Implements the "Executing selection sets" section of the spec
|
|
177
254
|
* for fields that must be executed serially.
|
|
178
255
|
*/
|
|
179
256
|
function executeFieldsSerially(exeContext, parentType, sourceValue, path, fields) {
|
|
180
|
-
return promiseReduce(fields
|
|
257
|
+
return promiseReduce(fields, (results, [responseName, fieldNodes]) => {
|
|
181
258
|
const fieldPath = addPath(path, responseName, parentType.name);
|
|
182
259
|
const result = executeField(exeContext, parentType, sourceValue, fieldNodes, fieldPath);
|
|
183
260
|
if (result === undefined) {
|
|
@@ -197,19 +274,30 @@ function executeFieldsSerially(exeContext, parentType, sourceValue, path, fields
|
|
|
197
274
|
* Implements the "Executing selection sets" section of the spec
|
|
198
275
|
* for fields that may be executed in parallel.
|
|
199
276
|
*/
|
|
200
|
-
function executeFields(exeContext, parentType, sourceValue, path, fields) {
|
|
277
|
+
function executeFields(exeContext, parentType, sourceValue, path, fields, asyncPayloadRecord) {
|
|
201
278
|
const results = Object.create(null);
|
|
202
279
|
let containsPromise = false;
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
280
|
+
try {
|
|
281
|
+
for (const [responseName, fieldNodes] of fields) {
|
|
282
|
+
const fieldPath = addPath(path, responseName, parentType.name);
|
|
283
|
+
const result = executeField(exeContext, parentType, sourceValue, fieldNodes, fieldPath, asyncPayloadRecord);
|
|
284
|
+
if (result !== undefined) {
|
|
285
|
+
results[responseName] = result;
|
|
286
|
+
if (isPromise(result)) {
|
|
287
|
+
containsPromise = true;
|
|
288
|
+
}
|
|
210
289
|
}
|
|
211
290
|
}
|
|
212
291
|
}
|
|
292
|
+
catch (error) {
|
|
293
|
+
if (containsPromise) {
|
|
294
|
+
// Ensure that any promises returned by other fields are handled, as they may also reject.
|
|
295
|
+
return promiseForObject(results).finally(() => {
|
|
296
|
+
throw error;
|
|
297
|
+
});
|
|
298
|
+
}
|
|
299
|
+
throw error;
|
|
300
|
+
}
|
|
213
301
|
// If there are no promises, we can just return the object
|
|
214
302
|
if (!containsPromise) {
|
|
215
303
|
return results;
|
|
@@ -217,13 +305,7 @@ function executeFields(exeContext, parentType, sourceValue, path, fields) {
|
|
|
217
305
|
// Otherwise, results is a map from field name to the result of resolving that
|
|
218
306
|
// field, which is possibly a promise. Return a promise that will return this
|
|
219
307
|
// same map, but with any promises replaced with the values they resolved to.
|
|
220
|
-
return
|
|
221
|
-
const resolvedObject = Object.create(null);
|
|
222
|
-
for (const [i, key] of Object.keys(results).entries()) {
|
|
223
|
-
resolvedObject[key] = resolvedValues[i];
|
|
224
|
-
}
|
|
225
|
-
return resolvedObject;
|
|
226
|
-
});
|
|
308
|
+
return promiseForObject(results);
|
|
227
309
|
}
|
|
228
310
|
/**
|
|
229
311
|
* Implements the "Executing fields" section of the spec
|
|
@@ -231,14 +313,15 @@ function executeFields(exeContext, parentType, sourceValue, path, fields) {
|
|
|
231
313
|
* calling its resolve function, then calls completeValue to complete promises,
|
|
232
314
|
* serialize scalars, or execute the sub-selection-set for objects.
|
|
233
315
|
*/
|
|
234
|
-
function executeField(exeContext, parentType, source, fieldNodes, path) {
|
|
235
|
-
var _a;
|
|
316
|
+
function executeField(exeContext, parentType, source, fieldNodes, path, asyncPayloadRecord) {
|
|
317
|
+
var _a, _b;
|
|
318
|
+
const errors = (_a = asyncPayloadRecord === null || asyncPayloadRecord === void 0 ? void 0 : asyncPayloadRecord.errors) !== null && _a !== void 0 ? _a : exeContext.errors;
|
|
236
319
|
const fieldDef = getFieldDef(exeContext.schema, parentType, fieldNodes[0]);
|
|
237
320
|
if (!fieldDef) {
|
|
238
321
|
return;
|
|
239
322
|
}
|
|
240
323
|
const returnType = fieldDef.type;
|
|
241
|
-
const resolveFn = (
|
|
324
|
+
const resolveFn = (_b = fieldDef.resolve) !== null && _b !== void 0 ? _b : exeContext.fieldResolver;
|
|
242
325
|
const info = buildResolveInfo(exeContext, fieldDef, fieldNodes, parentType, path);
|
|
243
326
|
// Get the resolve function, regardless of if its result is normal or abrupt (error).
|
|
244
327
|
try {
|
|
@@ -253,24 +336,28 @@ function executeField(exeContext, parentType, source, fieldNodes, path) {
|
|
|
253
336
|
const result = resolveFn(source, args, contextValue, info);
|
|
254
337
|
let completed;
|
|
255
338
|
if (isPromise(result)) {
|
|
256
|
-
completed = result.then(resolved => completeValue(exeContext, returnType, fieldNodes, info, path, resolved));
|
|
339
|
+
completed = result.then(resolved => completeValue(exeContext, returnType, fieldNodes, info, path, resolved, asyncPayloadRecord));
|
|
257
340
|
}
|
|
258
341
|
else {
|
|
259
|
-
completed = completeValue(exeContext, returnType, fieldNodes, info, path, result);
|
|
342
|
+
completed = completeValue(exeContext, returnType, fieldNodes, info, path, result, asyncPayloadRecord);
|
|
260
343
|
}
|
|
261
344
|
if (isPromise(completed)) {
|
|
262
345
|
// Note: we don't rely on a `catch` method, but we do expect "thenable"
|
|
263
346
|
// to take a second callback for the error case.
|
|
264
347
|
return completed.then(undefined, rawError => {
|
|
265
348
|
const error = locatedError(rawError, fieldNodes, pathToArray(path));
|
|
266
|
-
|
|
349
|
+
const handledError = handleFieldError(error, returnType, errors);
|
|
350
|
+
filterSubsequentPayloads(exeContext, path, asyncPayloadRecord);
|
|
351
|
+
return handledError;
|
|
267
352
|
});
|
|
268
353
|
}
|
|
269
354
|
return completed;
|
|
270
355
|
}
|
|
271
356
|
catch (rawError) {
|
|
272
357
|
const error = locatedError(rawError, fieldNodes, pathToArray(path));
|
|
273
|
-
|
|
358
|
+
const handledError = handleFieldError(error, returnType, errors);
|
|
359
|
+
filterSubsequentPayloads(exeContext, path, asyncPayloadRecord);
|
|
360
|
+
return handledError;
|
|
274
361
|
}
|
|
275
362
|
}
|
|
276
363
|
/**
|
|
@@ -293,7 +380,7 @@ export function buildResolveInfo(exeContext, fieldDef, fieldNodes, parentType, p
|
|
|
293
380
|
variableValues: exeContext.variableValues,
|
|
294
381
|
};
|
|
295
382
|
}
|
|
296
|
-
function handleFieldError(error, returnType,
|
|
383
|
+
function handleFieldError(error, returnType, errors) {
|
|
297
384
|
// If the field type is non-nullable, then it is resolved without any
|
|
298
385
|
// protection from errors, however it still properly locates the error.
|
|
299
386
|
if (isNonNullType(returnType)) {
|
|
@@ -301,7 +388,7 @@ function handleFieldError(error, returnType, exeContext) {
|
|
|
301
388
|
}
|
|
302
389
|
// Otherwise, error protection is applied, logging the error and resolving
|
|
303
390
|
// a null value for this field if one is encountered.
|
|
304
|
-
|
|
391
|
+
errors.push(error);
|
|
305
392
|
return null;
|
|
306
393
|
}
|
|
307
394
|
/**
|
|
@@ -325,7 +412,7 @@ function handleFieldError(error, returnType, exeContext) {
|
|
|
325
412
|
* Otherwise, the field type expects a sub-selection set, and will complete the
|
|
326
413
|
* value by executing all sub-selections.
|
|
327
414
|
*/
|
|
328
|
-
function completeValue(exeContext, returnType, fieldNodes, info, path, result) {
|
|
415
|
+
function completeValue(exeContext, returnType, fieldNodes, info, path, result, asyncPayloadRecord) {
|
|
329
416
|
// If result is an Error, throw a located error.
|
|
330
417
|
if (result instanceof Error) {
|
|
331
418
|
throw result;
|
|
@@ -333,7 +420,7 @@ function completeValue(exeContext, returnType, fieldNodes, info, path, result) {
|
|
|
333
420
|
// If field type is NonNull, complete for inner type, and throw field error
|
|
334
421
|
// if result is null.
|
|
335
422
|
if (isNonNullType(returnType)) {
|
|
336
|
-
const completed = completeValue(exeContext, returnType.ofType, fieldNodes, info, path, result);
|
|
423
|
+
const completed = completeValue(exeContext, returnType.ofType, fieldNodes, info, path, result, asyncPayloadRecord);
|
|
337
424
|
if (completed === null) {
|
|
338
425
|
throw new Error(`Cannot return null for non-nullable field ${info.parentType.name}.${info.fieldName}.`);
|
|
339
426
|
}
|
|
@@ -345,7 +432,7 @@ function completeValue(exeContext, returnType, fieldNodes, info, path, result) {
|
|
|
345
432
|
}
|
|
346
433
|
// If field type is List, complete each item in the list with the inner type
|
|
347
434
|
if (isListType(returnType)) {
|
|
348
|
-
return completeListValue(exeContext, returnType, fieldNodes, info, path, result);
|
|
435
|
+
return completeListValue(exeContext, returnType, fieldNodes, info, path, result, asyncPayloadRecord);
|
|
349
436
|
}
|
|
350
437
|
// If field type is a leaf type, Scalar or Enum, serialize to a valid value,
|
|
351
438
|
// returning null if serialization is not possible.
|
|
@@ -355,51 +442,74 @@ function completeValue(exeContext, returnType, fieldNodes, info, path, result) {
|
|
|
355
442
|
// If field type is an abstract type, Interface or Union, determine the
|
|
356
443
|
// runtime Object type and complete for that type.
|
|
357
444
|
if (isAbstractType(returnType)) {
|
|
358
|
-
return completeAbstractValue(exeContext, returnType, fieldNodes, info, path, result);
|
|
445
|
+
return completeAbstractValue(exeContext, returnType, fieldNodes, info, path, result, asyncPayloadRecord);
|
|
359
446
|
}
|
|
360
447
|
// If field type is Object, execute and complete all sub-selections.
|
|
361
448
|
if (isObjectType(returnType)) {
|
|
362
|
-
return completeObjectValue(exeContext, returnType, fieldNodes, info, path, result);
|
|
449
|
+
return completeObjectValue(exeContext, returnType, fieldNodes, info, path, result, asyncPayloadRecord);
|
|
363
450
|
}
|
|
364
451
|
/* c8 ignore next 6 */
|
|
365
452
|
// Not reachable, all possible output types have been considered.
|
|
366
453
|
console.assert(false, 'Cannot complete value of unexpected output type: ' + inspect(returnType));
|
|
367
454
|
}
|
|
455
|
+
/**
|
|
456
|
+
* Returns an object containing the `@stream` arguments if a field should be
|
|
457
|
+
* streamed based on the experimental flag, stream directive present and
|
|
458
|
+
* not disabled by the "if" argument.
|
|
459
|
+
*/
|
|
460
|
+
function getStreamValues(exeContext, fieldNodes, path) {
|
|
461
|
+
// do not stream inner lists of multi-dimensional lists
|
|
462
|
+
if (typeof path.key === 'number') {
|
|
463
|
+
return;
|
|
464
|
+
}
|
|
465
|
+
// validation only allows equivalent streams on multiple fields, so it is
|
|
466
|
+
// safe to only check the first fieldNode for the stream directive
|
|
467
|
+
const stream = getDirectiveValues(GraphQLStreamDirective, fieldNodes[0], exeContext.variableValues);
|
|
468
|
+
if (!stream) {
|
|
469
|
+
return;
|
|
470
|
+
}
|
|
471
|
+
if (stream['if'] === false) {
|
|
472
|
+
return;
|
|
473
|
+
}
|
|
474
|
+
invariant(typeof stream['initialCount'] === 'number', 'initialCount must be a number');
|
|
475
|
+
invariant(stream['initialCount'] >= 0, 'initialCount must be a positive integer');
|
|
476
|
+
return {
|
|
477
|
+
initialCount: stream['initialCount'],
|
|
478
|
+
label: typeof stream['label'] === 'string' ? stream['label'] : undefined,
|
|
479
|
+
};
|
|
480
|
+
}
|
|
368
481
|
/**
|
|
369
482
|
* Complete a async iterator value by completing the result and calling
|
|
370
483
|
* recursively until all the results are completed.
|
|
371
484
|
*/
|
|
372
|
-
async function completeAsyncIteratorValue(exeContext, itemType, fieldNodes, info, path, iterator) {
|
|
485
|
+
async function completeAsyncIteratorValue(exeContext, itemType, fieldNodes, info, path, iterator, asyncPayloadRecord) {
|
|
486
|
+
var _a;
|
|
487
|
+
const errors = (_a = asyncPayloadRecord === null || asyncPayloadRecord === void 0 ? void 0 : asyncPayloadRecord.errors) !== null && _a !== void 0 ? _a : exeContext.errors;
|
|
488
|
+
const stream = getStreamValues(exeContext, fieldNodes, path);
|
|
373
489
|
let containsPromise = false;
|
|
374
490
|
const completedResults = [];
|
|
375
491
|
let index = 0;
|
|
376
492
|
while (true) {
|
|
377
|
-
|
|
493
|
+
if (stream && typeof stream.initialCount === 'number' && index >= stream.initialCount) {
|
|
494
|
+
executeStreamIterator(index, iterator, exeContext, fieldNodes, info, itemType, path, stream.label, asyncPayloadRecord);
|
|
495
|
+
break;
|
|
496
|
+
}
|
|
497
|
+
const itemPath = addPath(path, index, undefined);
|
|
498
|
+
let iteration;
|
|
378
499
|
try {
|
|
379
|
-
|
|
380
|
-
if (done) {
|
|
500
|
+
iteration = await iterator.next();
|
|
501
|
+
if (iteration.done) {
|
|
381
502
|
break;
|
|
382
503
|
}
|
|
383
|
-
try {
|
|
384
|
-
// TODO can the error checking logic be consolidated with completeListValue?
|
|
385
|
-
const completedItem = completeValue(exeContext, itemType, fieldNodes, info, fieldPath, value);
|
|
386
|
-
if (isPromise(completedItem)) {
|
|
387
|
-
containsPromise = true;
|
|
388
|
-
}
|
|
389
|
-
completedResults.push(completedItem);
|
|
390
|
-
}
|
|
391
|
-
catch (rawError) {
|
|
392
|
-
completedResults.push(null);
|
|
393
|
-
const error = locatedError(rawError, fieldNodes, pathToArray(fieldPath));
|
|
394
|
-
handleFieldError(error, itemType, exeContext);
|
|
395
|
-
}
|
|
396
504
|
}
|
|
397
505
|
catch (rawError) {
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
handleFieldError(error, itemType, exeContext);
|
|
506
|
+
const error = locatedError(rawError, fieldNodes, pathToArray(itemPath));
|
|
507
|
+
completedResults.push(handleFieldError(error, itemType, errors));
|
|
401
508
|
break;
|
|
402
509
|
}
|
|
510
|
+
if (completeListItemValue(iteration.value, completedResults, errors, exeContext, itemType, fieldNodes, info, itemPath, asyncPayloadRecord)) {
|
|
511
|
+
containsPromise = true;
|
|
512
|
+
}
|
|
403
513
|
index += 1;
|
|
404
514
|
}
|
|
405
515
|
return containsPromise ? Promise.all(completedResults) : completedResults;
|
|
@@ -408,48 +518,75 @@ async function completeAsyncIteratorValue(exeContext, itemType, fieldNodes, info
|
|
|
408
518
|
* Complete a list value by completing each item in the list with the
|
|
409
519
|
* inner type
|
|
410
520
|
*/
|
|
411
|
-
function completeListValue(exeContext, returnType, fieldNodes, info, path, result) {
|
|
521
|
+
function completeListValue(exeContext, returnType, fieldNodes, info, path, result, asyncPayloadRecord) {
|
|
522
|
+
var _a;
|
|
412
523
|
const itemType = returnType.ofType;
|
|
524
|
+
const errors = (_a = asyncPayloadRecord === null || asyncPayloadRecord === void 0 ? void 0 : asyncPayloadRecord.errors) !== null && _a !== void 0 ? _a : exeContext.errors;
|
|
413
525
|
if (isAsyncIterable(result)) {
|
|
414
526
|
const iterator = result[Symbol.asyncIterator]();
|
|
415
|
-
return completeAsyncIteratorValue(exeContext, itemType, fieldNodes, info, path, iterator);
|
|
527
|
+
return completeAsyncIteratorValue(exeContext, itemType, fieldNodes, info, path, iterator, asyncPayloadRecord);
|
|
416
528
|
}
|
|
417
529
|
if (!isIterableObject(result)) {
|
|
418
530
|
throw createGraphQLError(`Expected Iterable, but did not find one for field "${info.parentType.name}.${info.fieldName}".`);
|
|
419
531
|
}
|
|
532
|
+
const stream = getStreamValues(exeContext, fieldNodes, path);
|
|
420
533
|
// This is specified as a simple map, however we're optimizing the path
|
|
421
534
|
// where the list contains no Promises by avoiding creating another Promise.
|
|
422
535
|
let containsPromise = false;
|
|
423
|
-
|
|
536
|
+
let previousAsyncPayloadRecord = asyncPayloadRecord;
|
|
537
|
+
const completedResults = [];
|
|
538
|
+
let index = 0;
|
|
539
|
+
for (const item of result) {
|
|
424
540
|
// No need to modify the info object containing the path,
|
|
425
541
|
// since from here on it is not ever accessed by resolver functions.
|
|
426
542
|
const itemPath = addPath(path, index, undefined);
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
}
|
|
432
|
-
else {
|
|
433
|
-
completedItem = completeValue(exeContext, itemType, fieldNodes, info, itemPath, item);
|
|
434
|
-
}
|
|
435
|
-
if (isPromise(completedItem)) {
|
|
436
|
-
containsPromise = true;
|
|
437
|
-
// Note: we don't rely on a `catch` method, but we do expect "thenable"
|
|
438
|
-
// to take a second callback for the error case.
|
|
439
|
-
return completedItem.then(undefined, rawError => {
|
|
440
|
-
const error = locatedError(rawError, fieldNodes, pathToArray(itemPath));
|
|
441
|
-
return handleFieldError(error, itemType, exeContext);
|
|
442
|
-
});
|
|
443
|
-
}
|
|
444
|
-
return completedItem;
|
|
543
|
+
if (stream && typeof stream.initialCount === 'number' && index >= stream.initialCount) {
|
|
544
|
+
previousAsyncPayloadRecord = executeStreamField(path, itemPath, item, exeContext, fieldNodes, info, itemType, stream.label, previousAsyncPayloadRecord);
|
|
545
|
+
index++;
|
|
546
|
+
continue;
|
|
445
547
|
}
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
return handleFieldError(error, itemType, exeContext);
|
|
548
|
+
if (completeListItemValue(item, completedResults, errors, exeContext, itemType, fieldNodes, info, itemPath, asyncPayloadRecord)) {
|
|
549
|
+
containsPromise = true;
|
|
449
550
|
}
|
|
450
|
-
|
|
551
|
+
index++;
|
|
552
|
+
}
|
|
451
553
|
return containsPromise ? Promise.all(completedResults) : completedResults;
|
|
452
554
|
}
|
|
555
|
+
/**
|
|
556
|
+
* Complete a list item value by adding it to the completed results.
|
|
557
|
+
*
|
|
558
|
+
* Returns true if the value is a Promise.
|
|
559
|
+
*/
|
|
560
|
+
function completeListItemValue(item, completedResults, errors, exeContext, itemType, fieldNodes, info, itemPath, asyncPayloadRecord) {
|
|
561
|
+
try {
|
|
562
|
+
let completedItem;
|
|
563
|
+
if (isPromise(item)) {
|
|
564
|
+
completedItem = item.then(resolved => completeValue(exeContext, itemType, fieldNodes, info, itemPath, resolved, asyncPayloadRecord));
|
|
565
|
+
}
|
|
566
|
+
else {
|
|
567
|
+
completedItem = completeValue(exeContext, itemType, fieldNodes, info, itemPath, item, asyncPayloadRecord);
|
|
568
|
+
}
|
|
569
|
+
if (isPromise(completedItem)) {
|
|
570
|
+
// Note: we don't rely on a `catch` method, but we do expect "thenable"
|
|
571
|
+
// to take a second callback for the error case.
|
|
572
|
+
completedResults.push(completedItem.then(undefined, rawError => {
|
|
573
|
+
const error = locatedError(rawError, fieldNodes, pathToArray(itemPath));
|
|
574
|
+
const handledError = handleFieldError(error, itemType, errors);
|
|
575
|
+
filterSubsequentPayloads(exeContext, itemPath, asyncPayloadRecord);
|
|
576
|
+
return handledError;
|
|
577
|
+
}));
|
|
578
|
+
return true;
|
|
579
|
+
}
|
|
580
|
+
completedResults.push(completedItem);
|
|
581
|
+
}
|
|
582
|
+
catch (rawError) {
|
|
583
|
+
const error = locatedError(rawError, fieldNodes, pathToArray(itemPath));
|
|
584
|
+
const handledError = handleFieldError(error, itemType, errors);
|
|
585
|
+
filterSubsequentPayloads(exeContext, itemPath, asyncPayloadRecord);
|
|
586
|
+
completedResults.push(handledError);
|
|
587
|
+
}
|
|
588
|
+
return false;
|
|
589
|
+
}
|
|
453
590
|
/**
|
|
454
591
|
* Complete a Scalar or Enum by serializing to a valid value, returning
|
|
455
592
|
* null if serialization is not possible.
|
|
@@ -466,15 +603,15 @@ function completeLeafValue(returnType, result) {
|
|
|
466
603
|
* Complete a value of an abstract type by determining the runtime object type
|
|
467
604
|
* of that value, then complete the value for that type.
|
|
468
605
|
*/
|
|
469
|
-
function completeAbstractValue(exeContext, returnType, fieldNodes, info, path, result) {
|
|
606
|
+
function completeAbstractValue(exeContext, returnType, fieldNodes, info, path, result, asyncPayloadRecord) {
|
|
470
607
|
var _a;
|
|
471
608
|
const resolveTypeFn = (_a = returnType.resolveType) !== null && _a !== void 0 ? _a : exeContext.typeResolver;
|
|
472
609
|
const contextValue = exeContext.contextValue;
|
|
473
610
|
const runtimeType = resolveTypeFn(result, contextValue, info, returnType);
|
|
474
611
|
if (isPromise(runtimeType)) {
|
|
475
|
-
return runtimeType.then(resolvedRuntimeType => completeObjectValue(exeContext, ensureValidRuntimeType(resolvedRuntimeType, exeContext, returnType, fieldNodes, info, result), fieldNodes, info, path, result));
|
|
612
|
+
return runtimeType.then(resolvedRuntimeType => completeObjectValue(exeContext, ensureValidRuntimeType(resolvedRuntimeType, exeContext, returnType, fieldNodes, info, result), fieldNodes, info, path, result, asyncPayloadRecord));
|
|
476
613
|
}
|
|
477
|
-
return completeObjectValue(exeContext, ensureValidRuntimeType(runtimeType, exeContext, returnType, fieldNodes, info, result), fieldNodes, info, path, result);
|
|
614
|
+
return completeObjectValue(exeContext, ensureValidRuntimeType(runtimeType, exeContext, returnType, fieldNodes, info, result), fieldNodes, info, path, result, asyncPayloadRecord);
|
|
478
615
|
}
|
|
479
616
|
function ensureValidRuntimeType(runtimeTypeName, exeContext, returnType, fieldNodes, info, result) {
|
|
480
617
|
if (runtimeTypeName == null) {
|
|
@@ -504,9 +641,7 @@ function ensureValidRuntimeType(runtimeTypeName, exeContext, returnType, fieldNo
|
|
|
504
641
|
/**
|
|
505
642
|
* Complete an Object value by executing all sub-selections.
|
|
506
643
|
*/
|
|
507
|
-
function completeObjectValue(exeContext, returnType, fieldNodes, info, path, result) {
|
|
508
|
-
// Collect sub-fields to execute to complete this value.
|
|
509
|
-
const subFieldNodes = collectSubFields(exeContext.schema, exeContext.fragments, exeContext.variableValues, returnType, fieldNodes);
|
|
644
|
+
function completeObjectValue(exeContext, returnType, fieldNodes, info, path, result, asyncPayloadRecord) {
|
|
510
645
|
// If there is an isTypeOf predicate function, call it with the
|
|
511
646
|
// current result. If isTypeOf returns false, then raise an error rather
|
|
512
647
|
// than continuing execution.
|
|
@@ -517,20 +652,30 @@ function completeObjectValue(exeContext, returnType, fieldNodes, info, path, res
|
|
|
517
652
|
if (!resolvedIsTypeOf) {
|
|
518
653
|
throw invalidReturnTypeError(returnType, result, fieldNodes);
|
|
519
654
|
}
|
|
520
|
-
return
|
|
655
|
+
return collectAndExecuteSubfields(exeContext, returnType, fieldNodes, path, result, asyncPayloadRecord);
|
|
521
656
|
});
|
|
522
657
|
}
|
|
523
658
|
if (!isTypeOf) {
|
|
524
659
|
throw invalidReturnTypeError(returnType, result, fieldNodes);
|
|
525
660
|
}
|
|
526
661
|
}
|
|
527
|
-
return
|
|
662
|
+
return collectAndExecuteSubfields(exeContext, returnType, fieldNodes, path, result, asyncPayloadRecord);
|
|
528
663
|
}
|
|
529
664
|
function invalidReturnTypeError(returnType, result, fieldNodes) {
|
|
530
665
|
return createGraphQLError(`Expected value of type "${returnType.name}" but got: ${inspect(result)}.`, {
|
|
531
666
|
nodes: fieldNodes,
|
|
532
667
|
});
|
|
533
668
|
}
|
|
669
|
+
function collectAndExecuteSubfields(exeContext, returnType, fieldNodes, path, result, asyncPayloadRecord) {
|
|
670
|
+
// Collect sub-fields to execute to complete this value.
|
|
671
|
+
const { fields: subFieldNodes, patches: subPatches } = collectSubfields(exeContext, returnType, fieldNodes);
|
|
672
|
+
const subFields = executeFields(exeContext, returnType, result, path, subFieldNodes, asyncPayloadRecord);
|
|
673
|
+
for (const subPatch of subPatches) {
|
|
674
|
+
const { label, fields: subPatchFieldNodes } = subPatch;
|
|
675
|
+
executeDeferredFragment(exeContext, returnType, result, subPatchFieldNodes, label, path, asyncPayloadRecord);
|
|
676
|
+
}
|
|
677
|
+
return subFields;
|
|
678
|
+
}
|
|
534
679
|
/**
|
|
535
680
|
* If a resolveType function is not given, then a default resolve behavior is
|
|
536
681
|
* used which attempts two strategies:
|
|
@@ -596,19 +741,77 @@ export const defaultFieldResolver = function (source, args, contextValue, info)
|
|
|
596
741
|
* is not an async iterable.
|
|
597
742
|
*
|
|
598
743
|
* If the client-provided arguments to this function do not result in a
|
|
599
|
-
* compliant subscription, a GraphQL Response (ExecutionResult) with
|
|
600
|
-
*
|
|
744
|
+
* compliant subscription, a GraphQL Response (ExecutionResult) with descriptive
|
|
745
|
+
* errors and no data will be returned.
|
|
601
746
|
*
|
|
602
|
-
* If the source stream could not be created due to faulty subscription
|
|
603
|
-
*
|
|
747
|
+
* If the source stream could not be created due to faulty subscription resolver
|
|
748
|
+
* logic or underlying systems, the promise will resolve to a single
|
|
604
749
|
* ExecutionResult containing `errors` and no `data`.
|
|
605
750
|
*
|
|
606
751
|
* If the operation succeeded, the promise resolves to an AsyncIterator, which
|
|
607
752
|
* yields a stream of ExecutionResults representing the response stream.
|
|
608
753
|
*
|
|
609
|
-
*
|
|
754
|
+
* This function does not support incremental delivery (`@defer` and `@stream`).
|
|
755
|
+
* If an operation which would defer or stream data is executed with this
|
|
756
|
+
* function, each `InitialIncrementalExecutionResult` and
|
|
757
|
+
* `SubsequentIncrementalExecutionResult` in the result stream will be replaced
|
|
758
|
+
* with an `ExecutionResult` with a single error stating that defer/stream is
|
|
759
|
+
* not supported. Use `experimentalSubscribeIncrementally` if you want to
|
|
760
|
+
* support incremental delivery.
|
|
761
|
+
*
|
|
762
|
+
* Accepts an object with named arguments.
|
|
610
763
|
*/
|
|
611
764
|
export function subscribe(args) {
|
|
765
|
+
const maybePromise = experimentalSubscribeIncrementally(args);
|
|
766
|
+
if (isPromise(maybePromise)) {
|
|
767
|
+
return maybePromise.then(resultOrIterable => isAsyncIterable(resultOrIterable)
|
|
768
|
+
? mapAsyncIterator(resultOrIterable, ensureSingleExecutionResult)
|
|
769
|
+
: resultOrIterable);
|
|
770
|
+
}
|
|
771
|
+
return isAsyncIterable(maybePromise) ? mapAsyncIterator(maybePromise, ensureSingleExecutionResult) : maybePromise;
|
|
772
|
+
}
|
|
773
|
+
function ensureSingleExecutionResult(result) {
|
|
774
|
+
if ('hasNext' in result) {
|
|
775
|
+
return {
|
|
776
|
+
errors: [createGraphQLError(UNEXPECTED_MULTIPLE_PAYLOADS)],
|
|
777
|
+
};
|
|
778
|
+
}
|
|
779
|
+
return result;
|
|
780
|
+
}
|
|
781
|
+
/**
|
|
782
|
+
* Implements the "Subscribe" algorithm described in the GraphQL specification,
|
|
783
|
+
* including `@defer` and `@stream` as proposed in
|
|
784
|
+
* https://github.com/graphql/graphql-spec/pull/742
|
|
785
|
+
*
|
|
786
|
+
* Returns a Promise which resolves to either an AsyncIterator (if successful)
|
|
787
|
+
* or an ExecutionResult (error). The promise will be rejected if the schema or
|
|
788
|
+
* other arguments to this function are invalid, or if the resolved event stream
|
|
789
|
+
* is not an async iterable.
|
|
790
|
+
*
|
|
791
|
+
* If the client-provided arguments to this function do not result in a
|
|
792
|
+
* compliant subscription, a GraphQL Response (ExecutionResult) with descriptive
|
|
793
|
+
* errors and no data will be returned.
|
|
794
|
+
*
|
|
795
|
+
* If the source stream could not be created due to faulty subscription resolver
|
|
796
|
+
* logic or underlying systems, the promise will resolve to a single
|
|
797
|
+
* ExecutionResult containing `errors` and no `data`.
|
|
798
|
+
*
|
|
799
|
+
* If the operation succeeded, the promise resolves to an AsyncIterator, which
|
|
800
|
+
* yields a stream of result representing the response stream.
|
|
801
|
+
*
|
|
802
|
+
* Each result may be an ExecutionResult with no `hasNext` (if executing the
|
|
803
|
+
* event did not use `@defer` or `@stream`), or an
|
|
804
|
+
* `InitialIncrementalExecutionResult` or `SubsequentIncrementalExecutionResult`
|
|
805
|
+
* (if executing the event used `@defer` or `@stream`). In the case of
|
|
806
|
+
* incremental execution results, each event produces a single
|
|
807
|
+
* `InitialIncrementalExecutionResult` followed by one or more
|
|
808
|
+
* `SubsequentIncrementalExecutionResult`s; all but the last have `hasNext: true`,
|
|
809
|
+
* and the last has `hasNext: false`. There is no interleaving between results
|
|
810
|
+
* generated from the same original event.
|
|
811
|
+
*
|
|
812
|
+
* Accepts an object with named arguments.
|
|
813
|
+
*/
|
|
814
|
+
export function experimentalSubscribeIncrementally(args) {
|
|
612
815
|
// If a valid execution context cannot be created due to incorrect arguments,
|
|
613
816
|
// a "Response" with only errors is returned.
|
|
614
817
|
const exeContext = buildExecutionContext(args);
|
|
@@ -622,6 +825,15 @@ export function subscribe(args) {
|
|
|
622
825
|
}
|
|
623
826
|
return mapSourceToResponse(exeContext, resultOrStream);
|
|
624
827
|
}
|
|
828
|
+
async function* ensureAsyncIterable(someExecutionResult) {
|
|
829
|
+
if ('initialResult' in someExecutionResult) {
|
|
830
|
+
yield someExecutionResult.initialResult;
|
|
831
|
+
yield* someExecutionResult.subsequentResults;
|
|
832
|
+
}
|
|
833
|
+
else {
|
|
834
|
+
yield someExecutionResult;
|
|
835
|
+
}
|
|
836
|
+
}
|
|
625
837
|
function mapSourceToResponse(exeContext, resultOrStream) {
|
|
626
838
|
if (!isAsyncIterable(resultOrStream)) {
|
|
627
839
|
return resultOrStream;
|
|
@@ -632,7 +844,7 @@ function mapSourceToResponse(exeContext, resultOrStream) {
|
|
|
632
844
|
// the GraphQL specification. The `execute` function provides the
|
|
633
845
|
// "ExecuteSubscriptionEvent" algorithm, as it is nearly identical to the
|
|
634
846
|
// "ExecuteQuery" algorithm, for which `execute` is also used.
|
|
635
|
-
return mapAsyncIterator(resultOrStream[Symbol.asyncIterator](), (payload) => executeImpl(buildPerEventExecutionContext(exeContext, payload)));
|
|
847
|
+
return flattenAsyncIterable(mapAsyncIterator(resultOrStream[Symbol.asyncIterator](), async (payload) => ensureAsyncIterable(await executeImpl(buildPerEventExecutionContext(exeContext, payload)))));
|
|
636
848
|
}
|
|
637
849
|
/**
|
|
638
850
|
* Implements the "CreateSourceEventStream" algorithm described in the
|
|
@@ -691,7 +903,7 @@ function executeSubscription(exeContext) {
|
|
|
691
903
|
if (rootType == null) {
|
|
692
904
|
throw createGraphQLError('Schema is not configured to execute subscription operation.', { nodes: operation });
|
|
693
905
|
}
|
|
694
|
-
const rootFields = collectFields(schema, fragments, variableValues, rootType, operation.selectionSet);
|
|
906
|
+
const { fields: rootFields } = collectFields(schema, fragments, variableValues, rootType, operation.selectionSet);
|
|
695
907
|
const [responseName, fieldNodes] = [...rootFields.entries()][0];
|
|
696
908
|
const fieldName = fieldNodes[0].name.value;
|
|
697
909
|
const fieldDef = getFieldDef(schema, rootType, fieldNodes[0]);
|
|
@@ -735,6 +947,339 @@ function assertEventStream(result) {
|
|
|
735
947
|
}
|
|
736
948
|
return result;
|
|
737
949
|
}
|
|
950
|
+
function executeDeferredFragment(exeContext, parentType, sourceValue, fields, label, path, parentContext) {
|
|
951
|
+
const asyncPayloadRecord = new DeferredFragmentRecord({
|
|
952
|
+
label,
|
|
953
|
+
path,
|
|
954
|
+
parentContext,
|
|
955
|
+
exeContext,
|
|
956
|
+
});
|
|
957
|
+
let promiseOrData;
|
|
958
|
+
try {
|
|
959
|
+
promiseOrData = executeFields(exeContext, parentType, sourceValue, path, fields, asyncPayloadRecord);
|
|
960
|
+
if (isPromise(promiseOrData)) {
|
|
961
|
+
promiseOrData = promiseOrData.then(null, e => {
|
|
962
|
+
asyncPayloadRecord.errors.push(e);
|
|
963
|
+
return null;
|
|
964
|
+
});
|
|
965
|
+
}
|
|
966
|
+
}
|
|
967
|
+
catch (e) {
|
|
968
|
+
asyncPayloadRecord.errors.push(e);
|
|
969
|
+
promiseOrData = null;
|
|
970
|
+
}
|
|
971
|
+
asyncPayloadRecord.addData(promiseOrData);
|
|
972
|
+
}
|
|
973
|
+
function executeStreamField(path, itemPath, item, exeContext, fieldNodes, info, itemType, label, parentContext) {
|
|
974
|
+
const asyncPayloadRecord = new StreamRecord({
|
|
975
|
+
label,
|
|
976
|
+
path: itemPath,
|
|
977
|
+
parentContext,
|
|
978
|
+
exeContext,
|
|
979
|
+
});
|
|
980
|
+
let completedItem;
|
|
981
|
+
try {
|
|
982
|
+
try {
|
|
983
|
+
if (isPromise(item)) {
|
|
984
|
+
completedItem = item.then(resolved => completeValue(exeContext, itemType, fieldNodes, info, itemPath, resolved, asyncPayloadRecord));
|
|
985
|
+
}
|
|
986
|
+
else {
|
|
987
|
+
completedItem = completeValue(exeContext, itemType, fieldNodes, info, itemPath, item, asyncPayloadRecord);
|
|
988
|
+
}
|
|
989
|
+
if (isPromise(completedItem)) {
|
|
990
|
+
// Note: we don't rely on a `catch` method, but we do expect "thenable"
|
|
991
|
+
// to take a second callback for the error case.
|
|
992
|
+
completedItem = completedItem.then(undefined, rawError => {
|
|
993
|
+
const error = locatedError(rawError, fieldNodes, pathToArray(itemPath));
|
|
994
|
+
const handledError = handleFieldError(error, itemType, asyncPayloadRecord.errors);
|
|
995
|
+
filterSubsequentPayloads(exeContext, itemPath, asyncPayloadRecord);
|
|
996
|
+
return handledError;
|
|
997
|
+
});
|
|
998
|
+
}
|
|
999
|
+
}
|
|
1000
|
+
catch (rawError) {
|
|
1001
|
+
const error = locatedError(rawError, fieldNodes, pathToArray(itemPath));
|
|
1002
|
+
completedItem = handleFieldError(error, itemType, asyncPayloadRecord.errors);
|
|
1003
|
+
filterSubsequentPayloads(exeContext, itemPath, asyncPayloadRecord);
|
|
1004
|
+
}
|
|
1005
|
+
}
|
|
1006
|
+
catch (error) {
|
|
1007
|
+
asyncPayloadRecord.errors.push(error);
|
|
1008
|
+
filterSubsequentPayloads(exeContext, path, asyncPayloadRecord);
|
|
1009
|
+
asyncPayloadRecord.addItems(null);
|
|
1010
|
+
return asyncPayloadRecord;
|
|
1011
|
+
}
|
|
1012
|
+
let completedItems;
|
|
1013
|
+
if (isPromise(completedItem)) {
|
|
1014
|
+
completedItems = completedItem.then(value => [value], error => {
|
|
1015
|
+
asyncPayloadRecord.errors.push(error);
|
|
1016
|
+
filterSubsequentPayloads(exeContext, path, asyncPayloadRecord);
|
|
1017
|
+
return null;
|
|
1018
|
+
});
|
|
1019
|
+
}
|
|
1020
|
+
else {
|
|
1021
|
+
completedItems = [completedItem];
|
|
1022
|
+
}
|
|
1023
|
+
asyncPayloadRecord.addItems(completedItems);
|
|
1024
|
+
return asyncPayloadRecord;
|
|
1025
|
+
}
|
|
1026
|
+
async function executeStreamIteratorItem(iterator, exeContext, fieldNodes, info, itemType, asyncPayloadRecord, itemPath) {
|
|
1027
|
+
let item;
|
|
1028
|
+
try {
|
|
1029
|
+
const { value, done } = await iterator.next();
|
|
1030
|
+
if (done) {
|
|
1031
|
+
asyncPayloadRecord.setIsCompletedIterator();
|
|
1032
|
+
return { done, value: undefined };
|
|
1033
|
+
}
|
|
1034
|
+
item = value;
|
|
1035
|
+
}
|
|
1036
|
+
catch (rawError) {
|
|
1037
|
+
const error = locatedError(rawError, fieldNodes, pathToArray(itemPath));
|
|
1038
|
+
const value = handleFieldError(error, itemType, asyncPayloadRecord.errors);
|
|
1039
|
+
// don't continue if iterator throws
|
|
1040
|
+
return { done: true, value };
|
|
1041
|
+
}
|
|
1042
|
+
let completedItem;
|
|
1043
|
+
try {
|
|
1044
|
+
completedItem = completeValue(exeContext, itemType, fieldNodes, info, itemPath, item, asyncPayloadRecord);
|
|
1045
|
+
if (isPromise(completedItem)) {
|
|
1046
|
+
completedItem = completedItem.then(undefined, rawError => {
|
|
1047
|
+
const error = locatedError(rawError, fieldNodes, pathToArray(itemPath));
|
|
1048
|
+
const handledError = handleFieldError(error, itemType, asyncPayloadRecord.errors);
|
|
1049
|
+
filterSubsequentPayloads(exeContext, itemPath, asyncPayloadRecord);
|
|
1050
|
+
return handledError;
|
|
1051
|
+
});
|
|
1052
|
+
}
|
|
1053
|
+
return { done: false, value: completedItem };
|
|
1054
|
+
}
|
|
1055
|
+
catch (rawError) {
|
|
1056
|
+
const error = locatedError(rawError, fieldNodes, pathToArray(itemPath));
|
|
1057
|
+
const value = handleFieldError(error, itemType, asyncPayloadRecord.errors);
|
|
1058
|
+
filterSubsequentPayloads(exeContext, itemPath, asyncPayloadRecord);
|
|
1059
|
+
return { done: false, value };
|
|
1060
|
+
}
|
|
1061
|
+
}
|
|
1062
|
+
async function executeStreamIterator(initialIndex, iterator, exeContext, fieldNodes, info, itemType, path, label, parentContext) {
|
|
1063
|
+
let index = initialIndex;
|
|
1064
|
+
let previousAsyncPayloadRecord = parentContext !== null && parentContext !== void 0 ? parentContext : undefined;
|
|
1065
|
+
while (true) {
|
|
1066
|
+
const itemPath = addPath(path, index, undefined);
|
|
1067
|
+
const asyncPayloadRecord = new StreamRecord({
|
|
1068
|
+
label,
|
|
1069
|
+
path: itemPath,
|
|
1070
|
+
parentContext: previousAsyncPayloadRecord,
|
|
1071
|
+
iterator,
|
|
1072
|
+
exeContext,
|
|
1073
|
+
});
|
|
1074
|
+
let iteration;
|
|
1075
|
+
try {
|
|
1076
|
+
iteration = await executeStreamIteratorItem(iterator, exeContext, fieldNodes, info, itemType, asyncPayloadRecord, itemPath);
|
|
1077
|
+
}
|
|
1078
|
+
catch (error) {
|
|
1079
|
+
asyncPayloadRecord.errors.push(error);
|
|
1080
|
+
filterSubsequentPayloads(exeContext, path, asyncPayloadRecord);
|
|
1081
|
+
asyncPayloadRecord.addItems(null);
|
|
1082
|
+
// entire stream has errored and bubbled upwards
|
|
1083
|
+
if (iterator === null || iterator === void 0 ? void 0 : iterator.return) {
|
|
1084
|
+
iterator.return().catch(() => {
|
|
1085
|
+
// ignore errors
|
|
1086
|
+
});
|
|
1087
|
+
}
|
|
1088
|
+
return;
|
|
1089
|
+
}
|
|
1090
|
+
const { done, value: completedItem } = iteration;
|
|
1091
|
+
let completedItems;
|
|
1092
|
+
if (isPromise(completedItem)) {
|
|
1093
|
+
completedItems = completedItem.then(value => [value], error => {
|
|
1094
|
+
asyncPayloadRecord.errors.push(error);
|
|
1095
|
+
filterSubsequentPayloads(exeContext, path, asyncPayloadRecord);
|
|
1096
|
+
return null;
|
|
1097
|
+
});
|
|
1098
|
+
}
|
|
1099
|
+
else {
|
|
1100
|
+
completedItems = [completedItem];
|
|
1101
|
+
}
|
|
1102
|
+
asyncPayloadRecord.addItems(completedItems);
|
|
1103
|
+
if (done) {
|
|
1104
|
+
break;
|
|
1105
|
+
}
|
|
1106
|
+
previousAsyncPayloadRecord = asyncPayloadRecord;
|
|
1107
|
+
index++;
|
|
1108
|
+
}
|
|
1109
|
+
}
|
|
1110
|
+
function filterSubsequentPayloads(exeContext, nullPath, currentAsyncRecord) {
|
|
1111
|
+
const nullPathArray = pathToArray(nullPath);
|
|
1112
|
+
exeContext.subsequentPayloads.forEach(asyncRecord => {
|
|
1113
|
+
var _a;
|
|
1114
|
+
if (asyncRecord === currentAsyncRecord) {
|
|
1115
|
+
// don't remove payload from where error originates
|
|
1116
|
+
return;
|
|
1117
|
+
}
|
|
1118
|
+
for (let i = 0; i < nullPathArray.length; i++) {
|
|
1119
|
+
if (asyncRecord.path[i] !== nullPathArray[i]) {
|
|
1120
|
+
// asyncRecord points to a path unaffected by this payload
|
|
1121
|
+
return;
|
|
1122
|
+
}
|
|
1123
|
+
}
|
|
1124
|
+
// asyncRecord path points to nulled error field
|
|
1125
|
+
if (isStreamPayload(asyncRecord) && ((_a = asyncRecord.iterator) === null || _a === void 0 ? void 0 : _a.return)) {
|
|
1126
|
+
asyncRecord.iterator.return().catch(() => {
|
|
1127
|
+
// ignore error
|
|
1128
|
+
});
|
|
1129
|
+
}
|
|
1130
|
+
exeContext.subsequentPayloads.delete(asyncRecord);
|
|
1131
|
+
});
|
|
1132
|
+
}
|
|
1133
|
+
function getCompletedIncrementalResults(exeContext) {
|
|
1134
|
+
const incrementalResults = [];
|
|
1135
|
+
for (const asyncPayloadRecord of exeContext.subsequentPayloads) {
|
|
1136
|
+
const incrementalResult = {};
|
|
1137
|
+
if (!asyncPayloadRecord.isCompleted) {
|
|
1138
|
+
continue;
|
|
1139
|
+
}
|
|
1140
|
+
exeContext.subsequentPayloads.delete(asyncPayloadRecord);
|
|
1141
|
+
if (isStreamPayload(asyncPayloadRecord)) {
|
|
1142
|
+
const items = asyncPayloadRecord.items;
|
|
1143
|
+
if (asyncPayloadRecord.isCompletedIterator) {
|
|
1144
|
+
// async iterable resolver just finished but there may be pending payloads
|
|
1145
|
+
continue;
|
|
1146
|
+
}
|
|
1147
|
+
incrementalResult.items = items;
|
|
1148
|
+
}
|
|
1149
|
+
else {
|
|
1150
|
+
const data = asyncPayloadRecord.data;
|
|
1151
|
+
incrementalResult.data = data !== null && data !== void 0 ? data : null;
|
|
1152
|
+
}
|
|
1153
|
+
incrementalResult.path = asyncPayloadRecord.path;
|
|
1154
|
+
if (asyncPayloadRecord.label) {
|
|
1155
|
+
incrementalResult.label = asyncPayloadRecord.label;
|
|
1156
|
+
}
|
|
1157
|
+
if (asyncPayloadRecord.errors.length > 0) {
|
|
1158
|
+
incrementalResult.errors = asyncPayloadRecord.errors;
|
|
1159
|
+
}
|
|
1160
|
+
incrementalResults.push(incrementalResult);
|
|
1161
|
+
}
|
|
1162
|
+
return incrementalResults;
|
|
1163
|
+
}
|
|
1164
|
+
function yieldSubsequentPayloads(exeContext) {
|
|
1165
|
+
let isDone = false;
|
|
1166
|
+
async function next() {
|
|
1167
|
+
if (isDone) {
|
|
1168
|
+
return { value: undefined, done: true };
|
|
1169
|
+
}
|
|
1170
|
+
await Promise.race(Array.from(exeContext.subsequentPayloads).map(p => p.promise));
|
|
1171
|
+
if (isDone) {
|
|
1172
|
+
// a different call to next has exhausted all payloads
|
|
1173
|
+
return { value: undefined, done: true };
|
|
1174
|
+
}
|
|
1175
|
+
const incremental = getCompletedIncrementalResults(exeContext);
|
|
1176
|
+
const hasNext = exeContext.subsequentPayloads.size > 0;
|
|
1177
|
+
if (!incremental.length && hasNext) {
|
|
1178
|
+
return next();
|
|
1179
|
+
}
|
|
1180
|
+
if (!hasNext) {
|
|
1181
|
+
isDone = true;
|
|
1182
|
+
}
|
|
1183
|
+
return {
|
|
1184
|
+
value: incremental.length ? { incremental, hasNext } : { hasNext },
|
|
1185
|
+
done: false,
|
|
1186
|
+
};
|
|
1187
|
+
}
|
|
1188
|
+
function returnStreamIterators() {
|
|
1189
|
+
const promises = [];
|
|
1190
|
+
exeContext.subsequentPayloads.forEach(asyncPayloadRecord => {
|
|
1191
|
+
var _a;
|
|
1192
|
+
if (isStreamPayload(asyncPayloadRecord) && ((_a = asyncPayloadRecord.iterator) === null || _a === void 0 ? void 0 : _a.return)) {
|
|
1193
|
+
promises.push(asyncPayloadRecord.iterator.return());
|
|
1194
|
+
}
|
|
1195
|
+
});
|
|
1196
|
+
return Promise.all(promises);
|
|
1197
|
+
}
|
|
1198
|
+
return {
|
|
1199
|
+
[Symbol.asyncIterator]() {
|
|
1200
|
+
return this;
|
|
1201
|
+
},
|
|
1202
|
+
next,
|
|
1203
|
+
async return() {
|
|
1204
|
+
await returnStreamIterators();
|
|
1205
|
+
isDone = true;
|
|
1206
|
+
return { value: undefined, done: true };
|
|
1207
|
+
},
|
|
1208
|
+
async throw(error) {
|
|
1209
|
+
await returnStreamIterators();
|
|
1210
|
+
isDone = true;
|
|
1211
|
+
return Promise.reject(error);
|
|
1212
|
+
},
|
|
1213
|
+
};
|
|
1214
|
+
}
|
|
1215
|
+
class DeferredFragmentRecord {
|
|
1216
|
+
constructor(opts) {
|
|
1217
|
+
this.type = 'defer';
|
|
1218
|
+
this.label = opts.label;
|
|
1219
|
+
this.path = pathToArray(opts.path);
|
|
1220
|
+
this.parentContext = opts.parentContext;
|
|
1221
|
+
this.errors = [];
|
|
1222
|
+
this._exeContext = opts.exeContext;
|
|
1223
|
+
this._exeContext.subsequentPayloads.add(this);
|
|
1224
|
+
this.isCompleted = false;
|
|
1225
|
+
this.data = null;
|
|
1226
|
+
this.promise = new Promise(resolve => {
|
|
1227
|
+
this._resolve = MaybePromise => {
|
|
1228
|
+
resolve(MaybePromise);
|
|
1229
|
+
};
|
|
1230
|
+
}).then(data => {
|
|
1231
|
+
this.data = data;
|
|
1232
|
+
this.isCompleted = true;
|
|
1233
|
+
});
|
|
1234
|
+
}
|
|
1235
|
+
addData(data) {
|
|
1236
|
+
var _a, _b, _c;
|
|
1237
|
+
const parentData = (_a = this.parentContext) === null || _a === void 0 ? void 0 : _a.promise;
|
|
1238
|
+
if (parentData) {
|
|
1239
|
+
(_b = this._resolve) === null || _b === void 0 ? void 0 : _b.call(this, parentData.then(() => data));
|
|
1240
|
+
return;
|
|
1241
|
+
}
|
|
1242
|
+
(_c = this._resolve) === null || _c === void 0 ? void 0 : _c.call(this, data);
|
|
1243
|
+
}
|
|
1244
|
+
}
|
|
1245
|
+
class StreamRecord {
|
|
1246
|
+
constructor(opts) {
|
|
1247
|
+
this.type = 'stream';
|
|
1248
|
+
this.items = null;
|
|
1249
|
+
this.label = opts.label;
|
|
1250
|
+
this.path = pathToArray(opts.path);
|
|
1251
|
+
this.parentContext = opts.parentContext;
|
|
1252
|
+
this.iterator = opts.iterator;
|
|
1253
|
+
this.errors = [];
|
|
1254
|
+
this._exeContext = opts.exeContext;
|
|
1255
|
+
this._exeContext.subsequentPayloads.add(this);
|
|
1256
|
+
this.isCompleted = false;
|
|
1257
|
+
this.items = null;
|
|
1258
|
+
this.promise = new Promise(resolve => {
|
|
1259
|
+
this._resolve = MaybePromise => {
|
|
1260
|
+
resolve(MaybePromise);
|
|
1261
|
+
};
|
|
1262
|
+
}).then(items => {
|
|
1263
|
+
this.items = items;
|
|
1264
|
+
this.isCompleted = true;
|
|
1265
|
+
});
|
|
1266
|
+
}
|
|
1267
|
+
addItems(items) {
|
|
1268
|
+
var _a, _b, _c;
|
|
1269
|
+
const parentData = (_a = this.parentContext) === null || _a === void 0 ? void 0 : _a.promise;
|
|
1270
|
+
if (parentData) {
|
|
1271
|
+
(_b = this._resolve) === null || _b === void 0 ? void 0 : _b.call(this, parentData.then(() => items));
|
|
1272
|
+
return;
|
|
1273
|
+
}
|
|
1274
|
+
(_c = this._resolve) === null || _c === void 0 ? void 0 : _c.call(this, items);
|
|
1275
|
+
}
|
|
1276
|
+
setIsCompletedIterator() {
|
|
1277
|
+
this.isCompletedIterator = true;
|
|
1278
|
+
}
|
|
1279
|
+
}
|
|
1280
|
+
function isStreamPayload(asyncPayload) {
|
|
1281
|
+
return asyncPayload.type === 'stream';
|
|
1282
|
+
}
|
|
738
1283
|
/**
|
|
739
1284
|
* This method looks up the field on the given type definition.
|
|
740
1285
|
* It has special casing for the three introspection fields,
|