diff --git a/packages/graphql/lib/src/core/_base_options.dart b/packages/graphql/lib/src/core/_base_options.dart index fa3fcb62..de2eb0cc 100644 --- a/packages/graphql/lib/src/core/_base_options.dart +++ b/packages/graphql/lib/src/core/_base_options.dart @@ -23,6 +23,7 @@ abstract class BaseOptions { ErrorPolicy? errorPolicy, CacheRereadPolicy? cacheRereadPolicy, this.optimisticResult, + this.queryRequestTimeout, }) : policies = Policies( fetch: fetchPolicy, error: errorPolicy, @@ -60,6 +61,9 @@ abstract class BaseOptions { final ResultParserFn parserFn; + /// Override default query timeout + final Duration? queryRequestTimeout; + // TODO consider inverting this relationship /// Resolve these options into a request Request get asRequest => Request( @@ -80,6 +84,7 @@ abstract class BaseOptions { policies, context, parserFn, + queryRequestTimeout, ]; OperationType get type { diff --git a/packages/graphql/lib/src/core/mutation_options.dart b/packages/graphql/lib/src/core/mutation_options.dart index e69d49dd..01824771 100644 --- a/packages/graphql/lib/src/core/mutation_options.dart +++ b/packages/graphql/lib/src/core/mutation_options.dart @@ -30,6 +30,7 @@ class MutationOptions extends BaseOptions { this.update, this.onError, ResultParserFn? parserFn, + Duration? queryRequestTimeout, }) : super( fetchPolicy: fetchPolicy, errorPolicy: errorPolicy, @@ -40,6 +41,7 @@ class MutationOptions extends BaseOptions { context: context, optimisticResult: optimisticResult, parserFn: parserFn, + queryRequestTimeout: queryRequestTimeout, ); final OnMutationCompleted? onCompleted; @@ -68,6 +70,7 @@ class MutationOptions extends BaseOptions { update: update, onError: onError, parserFn: parserFn, + queryRequestTimeout: queryRequestTimeout, ); WatchQueryOptions asWatchQueryOptions() => @@ -81,6 +84,7 @@ class MutationOptions extends BaseOptions { fetchResults: false, context: context, parserFn: parserFn, + queryRequestTimeout: queryRequestTimeout, ); } diff --git a/packages/graphql/lib/src/core/query_manager.dart b/packages/graphql/lib/src/core/query_manager.dart index 375707f0..263d4cfc 100644 --- a/packages/graphql/lib/src/core/query_manager.dart +++ b/packages/graphql/lib/src/core/query_manager.dart @@ -53,7 +53,7 @@ class QueryManager { final bool alwaysRebroadcast; /// The timeout for resolving a query - final Duration requestTimeout; + final Duration? requestTimeout; QueryScheduler? scheduler; static final _oneOffOpId = '0'; @@ -260,7 +260,16 @@ class QueryManager { try { // execute the request through the provided link(s) - response = await link.request(request).timeout(this.requestTimeout).first; + Stream responseStream = link.request(request); + + // Resolve the request timeout by first checking the options of this specific request, + // then the manager level requestTimeout. + // Only apply the timeout if it is non-null. + final timeout = options.queryRequestTimeout ?? this.requestTimeout; + if (timeout case final Duration timeout) { + responseStream = responseStream.timeout(timeout); + } + response = await responseStream.first; queryResult = mapFetchResultToQueryResult( response, diff --git a/packages/graphql/lib/src/core/query_options.dart b/packages/graphql/lib/src/core/query_options.dart index 7a4a43b2..f9634b3d 100644 --- a/packages/graphql/lib/src/core/query_options.dart +++ b/packages/graphql/lib/src/core/query_options.dart @@ -27,6 +27,7 @@ class QueryOptions extends BaseOptions { this.pollInterval, Context? context, ResultParserFn? parserFn, + Duration? queryRequestTimeout, this.onComplete, this.onError, }) : super( @@ -39,6 +40,7 @@ class QueryOptions extends BaseOptions { context: context, optimisticResult: optimisticResult, parserFn: parserFn, + queryRequestTimeout: queryRequestTimeout, ); final OnQueryComplete? onComplete; @@ -68,6 +70,7 @@ class QueryOptions extends BaseOptions { Duration? pollInterval, Context? context, ResultParserFn? parserFn, + Duration? queryRequestTimeout, OnQueryComplete? onComplete, OnQueryError? onError, }) => @@ -82,6 +85,7 @@ class QueryOptions extends BaseOptions { pollInterval: pollInterval ?? this.pollInterval, context: context ?? this.context, parserFn: parserFn ?? this.parserFn, + queryRequestTimeout: queryRequestTimeout ?? this.queryRequestTimeout, onComplete: onComplete ?? this.onComplete, onError: onError ?? this.onError, ); @@ -95,6 +99,7 @@ class QueryOptions extends BaseOptions { fetchPolicy: FetchPolicy.noCache, errorPolicy: errorPolicy, parserFn: parserFn, + queryRequestTimeout: queryRequestTimeout, context: context, variables: { ...variables, @@ -115,6 +120,7 @@ class QueryOptions extends BaseOptions { context: context, optimisticResult: optimisticResult, parserFn: parserFn, + queryRequestTimeout: queryRequestTimeout, ); QueryOptions copyWithPolicies(Policies policies) => QueryOptions( @@ -128,6 +134,7 @@ class QueryOptions extends BaseOptions { pollInterval: pollInterval, context: context, parserFn: parserFn, + queryRequestTimeout: queryRequestTimeout, ); } @@ -144,6 +151,7 @@ class SubscriptionOptions Object? optimisticResult, Context? context, ResultParserFn? parserFn, + Duration? queryRequestTimeout, }) : super( fetchPolicy: fetchPolicy, errorPolicy: errorPolicy, @@ -154,6 +162,7 @@ class SubscriptionOptions context: context, optimisticResult: optimisticResult, parserFn: parserFn, + queryRequestTimeout: queryRequestTimeout, ); SubscriptionOptions copyWithPolicies(Policies policies) => SubscriptionOptions( @@ -166,6 +175,7 @@ class SubscriptionOptions optimisticResult: optimisticResult, context: context, parserFn: parserFn, + queryRequestTimeout: queryRequestTimeout, ); } @@ -185,6 +195,7 @@ class WatchQueryOptions extends QueryOptions { bool? eagerlyFetchResults, Context? context, ResultParserFn? parserFn, + Duration? queryRequestTimeout, }) : eagerlyFetchResults = eagerlyFetchResults ?? fetchResults, super( document: document, @@ -197,6 +208,7 @@ class WatchQueryOptions extends QueryOptions { context: context, optimisticResult: optimisticResult, parserFn: parserFn, + queryRequestTimeout: queryRequestTimeout, ); /// Whether or not to fetch results every time a new listener is added. @@ -237,6 +249,7 @@ class WatchQueryOptions extends QueryOptions { bool? eagerlyFetchResults, Context? context, ResultParserFn? parserFn, + Duration? queryRequestTimeout, }) => WatchQueryOptions( document: document ?? this.document, @@ -253,6 +266,7 @@ class WatchQueryOptions extends QueryOptions { carryForwardDataOnException ?? this.carryForwardDataOnException, context: context ?? this.context, parserFn: parserFn ?? this.parserFn, + queryRequestTimeout: queryRequestTimeout ?? this.queryRequestTimeout, ); WatchQueryOptions copyWithFetchPolicy( @@ -272,6 +286,7 @@ class WatchQueryOptions extends QueryOptions { carryForwardDataOnException: carryForwardDataOnException, context: context, parserFn: parserFn, + queryRequestTimeout: queryRequestTimeout, ); WatchQueryOptions copyWithPolicies( Policies policies, @@ -290,6 +305,7 @@ class WatchQueryOptions extends QueryOptions { carryForwardDataOnException: carryForwardDataOnException, context: context, parserFn: parserFn, + queryRequestTimeout: queryRequestTimeout, ); WatchQueryOptions copyWithPollInterval(Duration? pollInterval) => @@ -307,6 +323,7 @@ class WatchQueryOptions extends QueryOptions { carryForwardDataOnException: carryForwardDataOnException, context: context, parserFn: parserFn, + queryRequestTimeout: queryRequestTimeout, ); WatchQueryOptions copyWithVariables( @@ -325,6 +342,7 @@ class WatchQueryOptions extends QueryOptions { carryForwardDataOnException: carryForwardDataOnException, context: context, parserFn: parserFn, + queryRequestTimeout: queryRequestTimeout, ); WatchQueryOptions copyWithOptimisticResult( @@ -343,6 +361,7 @@ class WatchQueryOptions extends QueryOptions { carryForwardDataOnException: carryForwardDataOnException, context: context, parserFn: parserFn, + queryRequestTimeout: queryRequestTimeout, ); } @@ -358,6 +377,7 @@ class FetchMoreOptions { this.document, this.variables = const {}, required this.updateQuery, + Duration? queryRequestTimeout, }); /// Automatically merge the results of [updateQuery] into `previousResultData`. @@ -369,11 +389,13 @@ class FetchMoreOptions { DocumentNode? document, Map variables = const {}, required UpdateQuery updateQuery, + Duration? queryRequestTimeout, }) => FetchMoreOptions( document: document, variables: variables, updateQuery: partialUpdater(updateQuery), + queryRequestTimeout: queryRequestTimeout, ); final DocumentNode? document; diff --git a/packages/graphql/lib/src/graphql_client.dart b/packages/graphql/lib/src/graphql_client.dart index 6d7a1edf..136489c8 100644 --- a/packages/graphql/lib/src/graphql_client.dart +++ b/packages/graphql/lib/src/graphql_client.dart @@ -28,7 +28,7 @@ class GraphQLClient implements GraphQLDataProxy { bool alwaysRebroadcast = false, DeepEqualsFn? deepEquals, bool deduplicatePollers = false, - Duration queryRequestTimeout = const Duration(seconds: 5), + Duration? queryRequestTimeout = const Duration(seconds: 5), }) : defaultPolicies = defaultPolicies ?? DefaultPolicies(), queryManager = QueryManager( link: link,