1+ import { inspect } from '../jsutils/inspect.js' ;
2+ import { isAsyncIterable } from '../jsutils/isAsyncIterable.js' ;
13import { isObjectLike } from '../jsutils/isObjectLike.js' ;
24import { isPromise } from '../jsutils/isPromise.js' ;
35import type { Maybe } from '../jsutils/Maybe.js' ;
46import type { ObjMap } from '../jsutils/ObjMap.js' ;
7+ import { addPath , pathToArray } from '../jsutils/Path.js' ;
58import type { PromiseOrValue } from '../jsutils/PromiseOrValue.js' ;
69
710import { GraphQLError } from '../error/GraphQLError.js' ;
11+ import { locatedError } from '../error/locatedError.js' ;
812
913import type {
1014 DocumentNode ,
15+ FieldNode ,
1116 FragmentDefinitionNode ,
1217 OperationDefinitionNode ,
1318} from '../language/ast.js' ;
@@ -21,15 +26,15 @@ import type {
2126import { assertValidSchema } from '../type/index.js' ;
2227import type { GraphQLSchema } from '../type/schema.js' ;
2328
24- import type { FragmentDetails } from './collectFields.js' ;
29+ import { cancellablePromise } from './cancellablePromise.js' ;
30+ import type { FieldDetailsList , FragmentDetails } from './collectFields.js' ;
31+ import { collectFields } from './collectFields.js' ;
2532import type { ExecutionResult , ValidatedExecutionArgs } from './Executor.js' ;
26- import {
27- createSourceEventStreamImpl ,
28- executeQueryOrMutationOrSubscriptionEvent ,
29- mapSourceToResponse ,
30- } from './Executor.js' ;
33+ import { executeQueryOrMutationOrSubscriptionEvent } from './Executor.js' ;
3134import { getVariableSignature } from './getVariableSignature.js' ;
32- import { getVariableValues } from './values.js' ;
35+ import { mapAsyncIterable } from './mapAsyncIterable.js' ;
36+ import { ResolveInfo } from './ResolveInfo.js' ;
37+ import { getArgumentValues , getVariableValues } from './values.js' ;
3338
3439/**
3540 * Implements the "Executing requests" section of the GraphQL specification.
@@ -373,3 +378,173 @@ export const defaultFieldResolver: GraphQLFieldResolver<unknown, unknown> =
373378 return property ;
374379 }
375380 } ;
381+
382+ function mapSourceToResponse (
383+ validatedExecutionArgs : ValidatedExecutionArgs ,
384+ resultOrStream : ExecutionResult | AsyncIterable < unknown > ,
385+ ) : AsyncGenerator < ExecutionResult , void , void > | ExecutionResult {
386+ if ( ! isAsyncIterable ( resultOrStream ) ) {
387+ return resultOrStream ;
388+ }
389+
390+ // For each payload yielded from a subscription, map it over the normal
391+ // GraphQL `execute` function, with `payload` as the rootValue.
392+ // This implements the "MapSourceToResponseEvent" algorithm described in
393+ // the GraphQL specification..
394+ function mapFn ( payload : unknown ) : PromiseOrValue < ExecutionResult > {
395+ const perEventExecutionArgs : ValidatedExecutionArgs = {
396+ ...validatedExecutionArgs ,
397+ rootValue : payload ,
398+ } ;
399+ return validatedExecutionArgs . perEventExecutor ( perEventExecutionArgs ) ;
400+ }
401+
402+ const externalAbortSignal = validatedExecutionArgs . externalAbortSignal ;
403+ if ( externalAbortSignal ) {
404+ const generator = mapAsyncIterable ( resultOrStream , mapFn ) ;
405+ return {
406+ ...generator ,
407+ next : ( ) => cancellablePromise ( generator . next ( ) , externalAbortSignal ) ,
408+ } ;
409+ }
410+ return mapAsyncIterable ( resultOrStream , mapFn ) ;
411+ }
412+
413+ function createSourceEventStreamImpl (
414+ validatedExecutionArgs : ValidatedExecutionArgs ,
415+ ) : PromiseOrValue < AsyncIterable < unknown > | ExecutionResult > {
416+ try {
417+ const eventStream = executeSubscription ( validatedExecutionArgs ) ;
418+ if ( isPromise ( eventStream ) ) {
419+ return eventStream . then ( undefined , ( error : unknown ) => ( {
420+ errors : [ error as GraphQLError ] ,
421+ } ) ) ;
422+ }
423+
424+ return eventStream ;
425+ } catch ( error ) {
426+ return { errors : [ error ] } ;
427+ }
428+ }
429+
430+ function executeSubscription (
431+ validatedExecutionArgs : ValidatedExecutionArgs ,
432+ ) : PromiseOrValue < AsyncIterable < unknown > > {
433+ const {
434+ schema,
435+ fragments,
436+ rootValue,
437+ contextValue,
438+ operation,
439+ variableValues,
440+ hideSuggestions,
441+ externalAbortSignal,
442+ } = validatedExecutionArgs ;
443+
444+ const rootType = schema . getSubscriptionType ( ) ;
445+ if ( rootType == null ) {
446+ throw new GraphQLError (
447+ 'Schema is not configured to execute subscription operation.' ,
448+ { nodes : operation } ,
449+ ) ;
450+ }
451+
452+ const { groupedFieldSet } = collectFields (
453+ schema ,
454+ fragments ,
455+ variableValues ,
456+ rootType ,
457+ operation . selectionSet ,
458+ hideSuggestions ,
459+ ) ;
460+
461+ const firstRootField = groupedFieldSet . entries ( ) . next ( ) . value as [
462+ string ,
463+ FieldDetailsList ,
464+ ] ;
465+ const [ responseName , fieldDetailsList ] = firstRootField ;
466+ const firstFieldDetails = fieldDetailsList [ 0 ] ;
467+ const firstNode = firstFieldDetails . node ;
468+ const fieldName = firstNode . name . value ;
469+ const fieldDef = schema . getField ( rootType , fieldName ) ;
470+
471+ if ( ! fieldDef ) {
472+ throw new GraphQLError (
473+ `The subscription field "${ fieldName } " is not defined.` ,
474+ { nodes : toNodes ( fieldDetailsList ) } ,
475+ ) ;
476+ }
477+
478+ const path = addPath ( undefined , responseName , rootType . name ) ;
479+ const info = new ResolveInfo (
480+ validatedExecutionArgs ,
481+ fieldDef ,
482+ fieldDetailsList ,
483+ rootType ,
484+ path ,
485+ ( ) => ( { abortSignal : externalAbortSignal } ) ,
486+ ) ;
487+
488+ try {
489+ // Implements the "ResolveFieldEventStream" algorithm from GraphQL specification.
490+ // It differs from "ResolveFieldValue" due to providing a different `resolveFn`.
491+
492+ // Build a JS object of arguments from the field.arguments AST, using the
493+ // variables scope to fulfill any variable references.
494+ const args = getArgumentValues (
495+ fieldDef ,
496+ firstNode ,
497+ variableValues ,
498+ firstFieldDetails . fragmentVariableValues ,
499+ hideSuggestions ,
500+ ) ;
501+
502+ // Call the `subscribe()` resolver or the default resolver to produce an
503+ // AsyncIterable yielding raw payloads.
504+ const resolveFn =
505+ fieldDef . subscribe ?? validatedExecutionArgs . subscribeFieldResolver ;
506+
507+ // The resolve function's optional third argument is a context value that
508+ // is provided to every resolve function within an execution. It is commonly
509+ // used to represent an authenticated user, or request-specific caches.
510+ const result = resolveFn ( rootValue , args , contextValue , info ) ;
511+
512+ if ( isPromise ( result ) ) {
513+ const promise = externalAbortSignal
514+ ? cancellablePromise ( result , externalAbortSignal )
515+ : result ;
516+ return promise
517+ . then ( assertEventStream )
518+ . then ( undefined , ( error : unknown ) => {
519+ throw locatedError (
520+ error ,
521+ toNodes ( fieldDetailsList ) ,
522+ pathToArray ( path ) ,
523+ ) ;
524+ } ) ;
525+ }
526+ return assertEventStream ( result ) ;
527+ } catch ( error ) {
528+ throw locatedError ( error , toNodes ( fieldDetailsList ) , pathToArray ( path ) ) ;
529+ }
530+ }
531+
532+ function assertEventStream ( result : unknown ) : AsyncIterable < unknown > {
533+ if ( result instanceof Error ) {
534+ throw result ;
535+ }
536+
537+ // Assert field returned an event stream, otherwise yield an error.
538+ if ( ! isAsyncIterable ( result ) ) {
539+ throw new GraphQLError (
540+ 'Subscription field must return Async Iterable. ' +
541+ `Received: ${ inspect ( result ) } .` ,
542+ ) ;
543+ }
544+
545+ return result ;
546+ }
547+
548+ function toNodes ( fieldDetailsList : FieldDetailsList ) : ReadonlyArray < FieldNode > {
549+ return fieldDetailsList . map ( ( fieldDetails ) => fieldDetails . node ) ;
550+ }
0 commit comments