11import { DocumentNode , GraphQLResolveInfo } from 'graphql' ;
22import { ValueOrPromise } from 'value-or-promise' ;
33import {
4+ AsyncExecutor ,
45 createGraphQLError ,
56 DisposableAsyncExecutor ,
67 DisposableExecutor ,
@@ -9,6 +10,7 @@ import {
910 ExecutionResult ,
1011 Executor ,
1112 getOperationASTFromRequest ,
13+ SyncExecutor ,
1214} from '@graphql-tools/utils' ;
1315import { fetch as defaultFetch } from '@whatwg-node/fetch' ;
1416import { createFormDataFromVariables } from './createFormDataFromVariables.js' ;
@@ -85,34 +87,89 @@ export interface HTTPExecutorOptions {
8587 * Print function for DocumentNode
8688 */
8789 print ?: ( doc : DocumentNode ) => string ;
90+ /**
91+ * Enable [Explicit Resource Management](https://www.typescriptlang.org/docs/handbook/release-notes/typescript-5-2.html#using-declarations-and-explicit-resource-management)
92+ * @default false
93+ */
94+ disposable ?: boolean ;
8895}
8996
9097export type HeadersConfig = Record < string , string > ;
9198
9299export function buildHTTPExecutor (
93- options ?: Omit < HTTPExecutorOptions , 'fetch' > & { fetch : SyncFetchFn } ,
100+ options ?: Omit < HTTPExecutorOptions , 'fetch' | 'disposable' > & {
101+ fetch : SyncFetchFn ;
102+ disposable : true ;
103+ } ,
94104) : DisposableSyncExecutor < any , HTTPExecutorOptions > ;
95105
106+ export function buildHTTPExecutor (
107+ options ?: Omit < HTTPExecutorOptions , 'fetch' | 'disposable' > & {
108+ fetch : SyncFetchFn ;
109+ disposable : false ;
110+ } ,
111+ ) : SyncExecutor < any , HTTPExecutorOptions > ;
112+
113+ export function buildHTTPExecutor (
114+ options ?: Omit < HTTPExecutorOptions , 'fetch' > & { fetch : SyncFetchFn } ,
115+ ) : SyncExecutor < any , HTTPExecutorOptions > ;
116+
117+ export function buildHTTPExecutor (
118+ options ?: Omit < HTTPExecutorOptions , 'fetch' | 'disposable' > & {
119+ fetch : AsyncFetchFn ;
120+ disposable : true ;
121+ } ,
122+ ) : DisposableAsyncExecutor < any , HTTPExecutorOptions > ;
123+
124+ export function buildHTTPExecutor (
125+ options ?: Omit < HTTPExecutorOptions , 'fetch' | 'disposable' > & {
126+ fetch : AsyncFetchFn ;
127+ disposable : false ;
128+ } ,
129+ ) : AsyncExecutor < any , HTTPExecutorOptions > ;
130+
96131export function buildHTTPExecutor (
97132 options ?: Omit < HTTPExecutorOptions , 'fetch' > & { fetch : AsyncFetchFn } ,
133+ ) : AsyncExecutor < any , HTTPExecutorOptions > ;
134+
135+ export function buildHTTPExecutor (
136+ options ?: Omit < HTTPExecutorOptions , 'fetch' | 'disposable' > & {
137+ fetch : RegularFetchFn ;
138+ disposable : true ;
139+ } ,
98140) : DisposableAsyncExecutor < any , HTTPExecutorOptions > ;
99141
142+ export function buildHTTPExecutor (
143+ options ?: Omit < HTTPExecutorOptions , 'fetch' | 'disposable' > & {
144+ fetch : RegularFetchFn ;
145+ disposable : false ;
146+ } ,
147+ ) : AsyncExecutor < any , HTTPExecutorOptions > ;
148+
100149export function buildHTTPExecutor (
101150 options ?: Omit < HTTPExecutorOptions , 'fetch' > & { fetch : RegularFetchFn } ,
151+ ) : AsyncExecutor < any , HTTPExecutorOptions > ;
152+
153+ export function buildHTTPExecutor (
154+ options ?: Omit < HTTPExecutorOptions , 'fetch' | 'disposable' > & { disposable : true } ,
102155) : DisposableAsyncExecutor < any , HTTPExecutorOptions > ;
103156
157+ export function buildHTTPExecutor (
158+ options ?: Omit < HTTPExecutorOptions , 'fetch' | 'disposable' > & { disposable : false } ,
159+ ) : AsyncExecutor < any , HTTPExecutorOptions > ;
160+
104161export function buildHTTPExecutor (
105162 options ?: Omit < HTTPExecutorOptions , 'fetch' > ,
106- ) : DisposableAsyncExecutor < any , HTTPExecutorOptions > ;
163+ ) : AsyncExecutor < any , HTTPExecutorOptions > ;
107164
108165export function buildHTTPExecutor (
109166 options ?: HTTPExecutorOptions ,
110- ) : Executor < any , HTTPExecutorOptions > {
167+ ) : DisposableExecutor < any , HTTPExecutorOptions > | Executor < any , HTTPExecutorOptions > {
111168 const printFn = options ?. print ?? defaultPrintFn ;
112- const disposeCtrl = new AbortController ( ) ;
113- const executor = ( request : ExecutionRequest < any , any , any , HTTPExecutorOptions > ) => {
114- if ( disposeCtrl . signal . aborted ) {
115- throw new Error ( 'Executor was disposed. Aborting execution' ) ;
169+ let disposeCtrl : AbortController | undefined ;
170+ const baseExecutor = ( request : ExecutionRequest < any , any , any , HTTPExecutorOptions > ) => {
171+ if ( disposeCtrl ? .signal . aborted ) {
172+ return createResultForAbort ( disposeCtrl . signal ) ;
116173 }
117174 const fetchFn = request . extensions ?. fetch ?? options ?. fetch ?? defaultFetch ;
118175 let method = request . extensions ?. method || options ?. method ;
@@ -153,17 +210,17 @@ export function buildHTTPExecutor(
153210
154211 const query = printFn ( request . document ) ;
155212
156- let signal = disposeCtrl . signal ;
213+ let signal = disposeCtrl ? .signal ;
157214 let clearTimeoutFn : VoidFunction = ( ) => { } ;
158215 if ( options ?. timeout ) {
159216 const timeoutCtrl = new AbortController ( ) ;
160217 signal = timeoutCtrl . signal ;
161- disposeCtrl . signal . addEventListener ( 'abort' , clearTimeoutFn ) ;
218+ disposeCtrl ? .signal . addEventListener ( 'abort' , clearTimeoutFn ) ;
162219 const timeoutId = setTimeout ( ( ) => {
163220 if ( ! timeoutCtrl . signal . aborted ) {
164221 timeoutCtrl . abort ( 'timeout' ) ;
165222 }
166- disposeCtrl . signal . removeEventListener ( 'abort' , clearTimeoutFn ) ;
223+ disposeCtrl ? .signal . removeEventListener ( 'abort' , clearTimeoutFn ) ;
167224 } , options . timeout ) ;
168225 clearTimeoutFn = ( ) => {
169226 clearTimeout ( timeoutId ) ;
@@ -349,20 +406,17 @@ export function buildHTTPExecutor(
349406 ] ,
350407 } ;
351408 } else if ( e . name === 'AbortError' && signal ?. reason ) {
352- return {
353- errors : [
354- createGraphQLError ( 'The operation was aborted. reason: ' + signal . reason , {
355- extensions : {
356- requestBody : {
357- query,
358- operationName : request . operationName ,
359- } ,
360- responseDetails : responseDetailsForError ,
361- } ,
362- originalError : e ,
363- } ) ,
364- ] ,
365- } ;
409+ return createResultForAbort (
410+ signal ,
411+ {
412+ requestBody : {
413+ query,
414+ operationName : request . operationName ,
415+ } ,
416+ responseDetails : responseDetailsForError ,
417+ } ,
418+ e ,
419+ ) ;
366420 } else if ( e . message ) {
367421 return {
368422 errors : [
@@ -398,11 +452,16 @@ export function buildHTTPExecutor(
398452 . resolve ( ) ;
399453 } ;
400454
455+ let executor : Executor = baseExecutor ;
456+
401457 if ( options ?. retry != null ) {
402- return function retryExecutor ( request : ExecutionRequest ) {
458+ executor = function retryExecutor ( request : ExecutionRequest ) {
403459 let result : ExecutionResult < any > | undefined ;
404460 let attempt = 0 ;
405461 function retryAttempt ( ) : Promise < ExecutionResult < any > > | ExecutionResult < any > {
462+ if ( disposeCtrl ?. signal . aborted ) {
463+ return createResultForAbort ( disposeCtrl . signal ) ;
464+ }
406465 attempt ++ ;
407466 if ( attempt > options ! . retry ! ) {
408467 if ( result != null ) {
@@ -412,7 +471,7 @@ export function buildHTTPExecutor(
412471 errors : [ createGraphQLError ( 'No response returned from fetch' ) ] ,
413472 } ;
414473 }
415- return new ValueOrPromise ( ( ) => executor ( request ) )
474+ return new ValueOrPromise ( ( ) => baseExecutor ( request ) )
416475 . then ( res => {
417476 result = res ;
418477 if ( result ?. errors ?. length ) {
@@ -426,17 +485,50 @@ export function buildHTTPExecutor(
426485 } ;
427486 }
428487
429- const disposableExecutor : DisposableExecutor = executor ;
488+ if ( ! options ?. disposable ) {
489+ disposeCtrl = undefined ;
490+ return executor ;
491+ }
430492
431- disposableExecutor [ Symbol . dispose ] = ( ) => {
432- return disposeCtrl . abort ( new Error ( 'Executor was disposed. Aborting execution' ) ) ;
433- } ;
493+ disposeCtrl = new AbortController ( ) ;
494+
495+ Object . defineProperties ( executor , {
496+ [ Symbol . dispose ] : {
497+ get ( ) {
498+ return function dispose ( ) {
499+ return disposeCtrl ! . abort ( createAbortErrorReason ( ) ) ;
500+ } ;
501+ } ,
502+ } ,
503+ [ Symbol . asyncDispose ] : {
504+ get ( ) {
505+ return function asyncDispose ( ) {
506+ return disposeCtrl ! . abort ( createAbortErrorReason ( ) ) ;
507+ } ;
508+ } ,
509+ } ,
510+ } ) ;
511+
512+ return executor ;
513+ }
434514
435- disposableExecutor [ Symbol . asyncDispose ] = ( ) => {
436- return disposeCtrl . abort ( new Error ( 'Executor was disposed. Aborting execution' ) ) ;
437- } ;
515+ function createAbortErrorReason ( ) {
516+ return new Error ( 'Executor was disposed.' ) ;
517+ }
438518
439- return disposableExecutor ;
519+ function createResultForAbort (
520+ signal : AbortSignal ,
521+ extensions ?: Record < string , any > ,
522+ originalError ?: Error ,
523+ ) {
524+ return {
525+ errors : [
526+ createGraphQLError ( 'The operation was aborted. reason: ' + signal . reason , {
527+ extensions,
528+ originalError,
529+ } ) ,
530+ ] ,
531+ } ;
440532}
441533
442534export { isLiveQueryOperationDefinitionNode } ;
0 commit comments