11import { DocumentNode , GraphQLResolveInfo } from 'graphql' ;
22import { ValueOrPromise } from 'value-or-promise' ;
33import {
4- AsyncExecutor ,
54 createGraphQLError ,
5+ DisposableAsyncExecutor ,
6+ DisposableExecutor ,
7+ DisposableSyncExecutor ,
68 ExecutionRequest ,
79 ExecutionResult ,
810 Executor ,
911 getOperationASTFromRequest ,
10- SyncExecutor ,
1112} from '@graphql-tools/utils' ;
1213import { fetch as defaultFetch } from '@whatwg-node/fetch' ;
1314import { createFormDataFromVariables } from './createFormDataFromVariables.js' ;
@@ -90,27 +91,31 @@ export type HeadersConfig = Record<string, string>;
9091
9192export function buildHTTPExecutor (
9293 options ?: Omit < HTTPExecutorOptions , 'fetch' > & { fetch : SyncFetchFn } ,
93- ) : SyncExecutor < any , HTTPExecutorOptions > ;
94+ ) : DisposableSyncExecutor < any , HTTPExecutorOptions > ;
9495
9596export function buildHTTPExecutor (
9697 options ?: Omit < HTTPExecutorOptions , 'fetch' > & { fetch : AsyncFetchFn } ,
97- ) : AsyncExecutor < any , HTTPExecutorOptions > ;
98+ ) : DisposableAsyncExecutor < any , HTTPExecutorOptions > ;
9899
99100export function buildHTTPExecutor (
100101 options ?: Omit < HTTPExecutorOptions , 'fetch' > & { fetch : RegularFetchFn } ,
101- ) : AsyncExecutor < any , HTTPExecutorOptions > ;
102+ ) : DisposableAsyncExecutor < any , HTTPExecutorOptions > ;
102103
103104export function buildHTTPExecutor (
104105 options ?: Omit < HTTPExecutorOptions , 'fetch' > ,
105- ) : AsyncExecutor < any , HTTPExecutorOptions > ;
106+ ) : DisposableAsyncExecutor < any , HTTPExecutorOptions > ;
106107
107108export function buildHTTPExecutor (
108109 options ?: HTTPExecutorOptions ,
109110) : Executor < any , HTTPExecutorOptions > {
110111 const printFn = options ?. print ?? defaultPrintFn ;
112+ const controller = new AbortController ( ) ;
111113 const executor = ( request : ExecutionRequest < any , any , any , HTTPExecutorOptions > ) => {
114+ if ( controller . signal . aborted ) {
115+ throw new Error ( 'Executor was disposed. Aborting execution' ) ;
116+ }
112117 const fetchFn = request . extensions ?. fetch ?? options ?. fetch ?? defaultFetch ;
113- let controller : AbortController | undefined ;
118+ let signal = controller . signal ;
114119 let method = request . extensions ?. method || options ?. method ;
115120
116121 const operationAst = getOperationASTFromRequest ( request ) ;
@@ -149,14 +154,10 @@ export function buildHTTPExecutor(
149154
150155 const query = printFn ( request . document ) ;
151156
152- let timeoutId : any ;
153157 if ( options ?. timeout ) {
154- controller = new AbortController ( ) ;
155- timeoutId = setTimeout ( ( ) => {
156- if ( ! controller ?. signal . aborted ) {
157- controller ?. abort ( 'timeout' ) ;
158- }
159- } , options . timeout ) ;
158+ // eslint-disable-next-line @typescript-eslint/ban-ts-comment
159+ // @ts -ignore AbortSignal.any is not yet in the DOM types
160+ signal = AbortSignal . any ( [ signal , AbortSignal . timeout ( options . timeout ) ] ) ;
160161 }
161162
162163 const responseDetailsForError : {
@@ -177,7 +178,7 @@ export function buildHTTPExecutor(
177178 const fetchOptions : RequestInit = {
178179 method : 'GET' ,
179180 headers,
180- signal : controller ?. signal ,
181+ signal,
181182 } ;
182183 if ( options ?. credentials != null ) {
183184 fetchOptions . credentials = options . credentials ;
@@ -207,7 +208,7 @@ export function buildHTTPExecutor(
207208 method : 'POST' ,
208209 body,
209210 headers,
210- signal : controller ?. signal ,
211+ signal,
211212 } ;
212213 if ( options ?. credentials != null ) {
213214 fetchOptions . credentials = options . credentials ;
@@ -220,9 +221,6 @@ export function buildHTTPExecutor(
220221 . then ( ( fetchResult : Response ) : any => {
221222 responseDetailsForError . status = fetchResult . status ;
222223 responseDetailsForError . statusText = fetchResult . statusText ;
223- if ( timeoutId != null ) {
224- clearTimeout ( timeoutId ) ;
225- }
226224
227225 // Retry should respect HTTP Errors
228226 if ( options ?. retry != null && ! fetchResult . status . toString ( ) . startsWith ( '2' ) ) {
@@ -231,9 +229,9 @@ export function buildHTTPExecutor(
231229
232230 const contentType = fetchResult . headers . get ( 'content-type' ) ;
233231 if ( contentType ?. includes ( 'text/event-stream' ) ) {
234- return handleEventStreamResponse ( fetchResult , controller ) ;
232+ return handleEventStreamResponse ( fetchResult ) ;
235233 } else if ( contentType ?. includes ( 'multipart/mixed' ) ) {
236- return handleMultipartMixedResponse ( fetchResult , controller ) ;
234+ return handleMultipartMixedResponse ( fetchResult ) ;
237235 }
238236
239237 return fetchResult . text ( ) ;
@@ -317,10 +315,10 @@ export function buildHTTPExecutor(
317315 } ) ,
318316 ] ,
319317 } ;
320- } else if ( e . name === 'AbortError' && controller ?. signal ?. reason ) {
318+ } else if ( e . name === 'AbortError' && signal ?. reason ) {
321319 return {
322320 errors : [
323- createGraphQLError ( 'The operation was aborted. reason: ' + controller . signal . reason , {
321+ createGraphQLError ( 'The operation was aborted. reason: ' + signal . reason , {
324322 extensions : {
325323 requestBody : {
326324 query,
@@ -395,7 +393,17 @@ export function buildHTTPExecutor(
395393 } ;
396394 }
397395
398- return executor ;
396+ const disposableExecutor : DisposableExecutor = executor ;
397+
398+ disposableExecutor [ Symbol . dispose ] = ( ) => {
399+ return controller . abort ( new Error ( 'Executor was disposed. Aborting execution' ) ) ;
400+ } ;
401+
402+ disposableExecutor [ Symbol . asyncDispose ] = ( ) => {
403+ return controller . abort ( new Error ( 'Executor was disposed. Aborting execution' ) ) ;
404+ } ;
405+
406+ return disposableExecutor ;
399407}
400408
401409export { isLiveQueryOperationDefinitionNode } ;
0 commit comments