feat: Update options
MODIFY
packages/graphql/lib/src/core/_base_options.dart
packages/graphql/lib/src/core/_base_options.dart
-import 'package:graphql/src/core/_data_class.dart';
-
+import 'package:collection/collection.dart';
import 'package:gql/ast.dart';
import 'package:graphql/client.dart';
import 'package:graphql/src/core/result_parser.dart';
+import 'package:meta/meta.dart';
+
+TParsed unprovidedParserFn<TParsed>(_d) => throw UnimplementedError(
+ "Please provide a parser function to support result parsing.",
+ );
/// TODO refactor into [Request] container
/// Base options.
-abstract class BaseOptions<TParsed> extends MutableDataClass {
+@immutable
+abstract class BaseOptions<TParsed> {
BaseOptions({
required this.document,
this.variables = const {},
@@ -24,28 +29,25 @@ abstract class BaseOptions<TParsed> extends MutableDataClass {
cacheReread: cacheRereadPolicy,
),
context = context ?? Context(),
- parserFn = parserFn ??
- ((d) => throw UnimplementedError(
- "Please provide a parser function to support result parsing.",
- ));
+ parserFn = parserFn ?? unprovidedParserFn;
/// Document containing at least one [OperationDefinitionNode]
- DocumentNode document;
+ final DocumentNode document;
/// Name of the executable definition
///
/// Must be specified if [document] contains more than one [OperationDefinitionNode]
- String? operationName;
+ final String? operationName;
/// A map going from variable name to variable value, where the variables are used
/// within the GraphQL query.
- Map<String, dynamic> variables;
+ final Map<String, dynamic> variables;
/// An optimistic result to eagerly add to the operation stream
- Object? optimisticResult;
+ final Object? optimisticResult;
/// Specifies the [Policies] to be used during execution.
- Policies policies;
+ final Policies policies;
FetchPolicy? get fetchPolicy => policies.fetch;
@@ -54,9 +56,9 @@ abstract class BaseOptions<TParsed> extends MutableDataClass {
CacheRereadPolicy? get cacheRereadPolicy => policies.cacheReread;
/// Context to be passed to link execution chain.
- Context context;
+ final Context context;
- ResultParserFn<TParsed> parserFn;
+ final ResultParserFn<TParsed> parserFn;
// TODO consider inverting this relationship
/// Resolve these options into a request
@@ -69,7 +71,7 @@ abstract class BaseOptions<TParsed> extends MutableDataClass {
context: context,
);
- @override
+ @protected
List<Object?> get properties => [
document,
operationName,
@@ -77,6 +79,7 @@ abstract class BaseOptions<TParsed> extends MutableDataClass {
optimisticResult,
policies,
context,
+ parserFn,
];
OperationType get type {
@@ -95,4 +98,21 @@ abstract class BaseOptions<TParsed> extends MutableDataClass {
bool get isQuery => type == OperationType.query;
bool get isMutation => type == OperationType.mutation;
bool get isSubscription => type == OperationType.subscription;
+
+ /// [properties] based deep equality check
+ operator ==(Object other) =>
+ identical(this, other) ||
+ (other is BaseOptions &&
+ runtimeType == other.runtimeType &&
+ const ListEquality<Object?>(
+ DeepCollectionEquality(),
+ ).equals(
+ other.properties,
+ properties,
+ ));
+
+ @override
+ int get hashCode => const ListEquality<Object?>(
+ DeepCollectionEquality(),
+ ).hash(properties);
}

DELETE
packages/graphql/lib/src/core/_data_class.dart
-import 'package:meta/meta.dart';
-import "package:collection/collection.dart";
-
-/// Helper for making mutable data classes with
-/// a [properties] based [equal] helper
-///
-/// NOTE: I (@micimize) settled on this helper instead of truly immutable classes
-/// because I didn't want to deal with the issue of `copyWith(field: null)`,
-/// but also didn't want to commit to adding a true dataclass generator
-/// like `freezed` or `built_value` yet. I consider this a stopgap,
-/// and think we should eventually have a truly immutable API
-abstract class MutableDataClass {
- const MutableDataClass();
-
- /// identifying properties for the inheriting class
- @protected
- List<Object?> get properties;
-
- /// [properties] based deep equality check
- bool equal(MutableDataClass other) =>
- identical(this, other) ||
- (runtimeType == other.runtimeType &&
- const ListEquality<Object?>(
- DeepCollectionEquality(),
- ).equals(
- other.properties,
- properties,
- ));
-}

MODIFY
packages/graphql/lib/src/core/mutation_options.dart
packages/graphql/lib/src/core/mutation_options.dart
-// ignore_for_file: deprecated_member_use_from_same_package
import 'dart:async';
-import 'package:graphql/src/cache/cache.dart';
+import 'package:graphql/client.dart';
import 'package:graphql/src/core/_base_options.dart';
-import 'package:graphql/src/core/observable_query.dart';
import 'package:gql/ast.dart';
-import 'package:gql_exec/gql_exec.dart';
import 'package:graphql/src/core/result_parser.dart';
-import 'package:graphql/src/exceptions.dart';
-import 'package:graphql/src/core/query_result.dart';
import 'package:graphql/src/utilities/helpers.dart';
-import 'package:graphql/src/core/policies.dart';
+import 'package:meta/meta.dart';
typedef OnMutationCompleted = FutureOr<void> Function(dynamic data);
typedef OnMutationUpdate = FutureOr<void> Function(
@@ -20,6 +15,7 @@ typedef OnMutationUpdate = FutureOr<void> Function(
);
typedef OnError = FutureOr<void> Function(OperationException? error);
+@immutable
class MutationOptions<TParsed> extends BaseOptions<TParsed> {
MutationOptions({
required DocumentNode document,
@@ -51,8 +47,41 @@ class MutationOptions<TParsed> extends BaseOptions<TParsed> {
final OnError? onError;
@override
- List<Object?> get properties =>
- [...super.properties, onCompleted, update, onError];
+ List<Object?> get properties => [
+ ...super.properties,
+ onCompleted,
+ update,
+ onError,
+ ];
+
+ MutationOptions<TParsed> copyWithPolicies(Policies policies) =>
+ MutationOptions(
+ document: document,
+ operationName: operationName,
+ variables: variables,
+ fetchPolicy: policies.fetch,
+ errorPolicy: policies.error,
+ cacheRereadPolicy: policies.cacheReread,
+ context: context,
+ optimisticResult: optimisticResult,
+ onCompleted: onCompleted,
+ update: update,
+ onError: onError,
+ parserFn: parserFn,
+ );
+
+ WatchQueryOptions<TParsed> asWatchQueryOptions() =>
+ WatchQueryOptions<TParsed>(
+ document: document,
+ operationName: operationName,
+ variables: variables,
+ fetchPolicy: fetchPolicy,
+ errorPolicy: errorPolicy,
+ cacheRereadPolicy: cacheRereadPolicy,
+ fetchResults: false,
+ context: context,
+ parserFn: parserFn,
+ );
}
/// Handles execution of mutation `update`, `onCompleted`, and `onError` callbacks

MODIFY
packages/graphql/lib/src/core/observable_query.dart
packages/graphql/lib/src/core/observable_query.dart
@@ -330,7 +330,7 @@ class ObservableQuery<TParsed> {
scheduler!.stopPollingQuery(queryId);
}
- options.pollInterval = pollInterval;
+ options = options.copyWithPollInterval(pollInterval);
lifecycle = QueryLifecycle.polling;
scheduler!.startPollingQuery(options, queryId);
}
@@ -338,13 +338,18 @@ class ObservableQuery<TParsed> {
void stopPolling() {
if (isCurrentlyPolling) {
scheduler!.stopPollingQuery(queryId);
- options.pollInterval = null;
+ options = options.copyWithPollInterval(null);
lifecycle = QueryLifecycle.pollingStopped;
}
}
- set variables(Map<String, dynamic> variables) =>
- options.variables = variables;
+ set variables(Map<String, dynamic> variables) {
+ options = options.copyWithVariables(variables);
+ }
+
+ set optimisticResult(Object? optimisticResult) {
+ options = options.copyWithOptimisticResult(optimisticResult);
+ }
/// [onData] callbacks have het to be run
///

MODIFY
packages/graphql/lib/src/core/query_manager.dart
packages/graphql/lib/src/core/query_manager.dart
@@ -343,12 +343,10 @@ class QueryManager {
/// Refetch the [ObservableQuery] referenced by [queryId],
/// overriding any present non-network-only [FetchPolicy].
Future<QueryResult<TParsed>?> refetchQuery<TParsed>(String queryId) {
- final WatchQueryOptions<TParsed> options =
- queries[queryId]!.options.copy() as WatchQueryOptions<TParsed>;
+ WatchQueryOptions<TParsed> options =
+ queries[queryId]!.options as WatchQueryOptions<TParsed>;
if (!willAlwaysExecuteOnNetwork(options.fetchPolicy)) {
- options.policies = options.policies.copyWith(
- fetch: FetchPolicy.networkOnly,
- );
+ options = options.copyWithFetchPolicy(FetchPolicy.networkOnly);
}
// create a new request to execute

MODIFY
packages/graphql/lib/src/core/query_options.dart
packages/graphql/lib/src/core/query_options.dart
-// ignore_for_file: deprecated_member_use_from_same_package
import 'package:graphql/src/core/_base_options.dart';
import 'package:graphql/src/core/result_parser.dart';
import 'package:graphql/src/utilities/helpers.dart';
@@ -6,8 +5,10 @@ import 'package:graphql/src/utilities/helpers.dart';
import 'package:gql/ast.dart';
import 'package:graphql/client.dart';
+import 'package:meta/meta.dart';
/// Query options.
+@immutable
class QueryOptions<TParsed> extends BaseOptions<TParsed> {
QueryOptions({
required DocumentNode document,
@@ -33,10 +34,13 @@ class QueryOptions<TParsed> extends BaseOptions<TParsed> {
);
/// The time interval on which this query should be re-fetched from the server.
- Duration? pollInterval;
+ final Duration? pollInterval;
@override
- List<Object?> get properties => [...super.properties, pollInterval];
+ List<Object?> get properties => [
+ ...super.properties,
+ pollInterval,
+ ];
WatchQueryOptions<TParsed> asWatchQueryOptions({bool fetchResults = true}) =>
WatchQueryOptions(
@@ -52,8 +56,22 @@ class QueryOptions<TParsed> extends BaseOptions<TParsed> {
optimisticResult: optimisticResult,
parserFn: this.parserFn,
);
+
+ QueryOptions<TParsed> copyWithPolicies(Policies policies) => QueryOptions(
+ document: document,
+ operationName: operationName,
+ variables: variables,
+ fetchPolicy: policies.fetch,
+ errorPolicy: policies.error,
+ cacheRereadPolicy: policies.cacheReread,
+ optimisticResult: optimisticResult,
+ pollInterval: pollInterval,
+ context: context,
+ parserFn: parserFn,
+ );
}
+@immutable
class SubscriptionOptions<TParsed> extends BaseOptions<TParsed> {
SubscriptionOptions({
required DocumentNode document,
@@ -76,11 +94,21 @@ class SubscriptionOptions<TParsed> extends BaseOptions<TParsed> {
optimisticResult: optimisticResult,
parserFn: parserFn,
);
-
- /// An optimistic first result to eagerly add to the subscription stream
- Object? optimisticResult;
+ SubscriptionOptions<TParsed> copyWithPolicies(Policies policies) =>
+ SubscriptionOptions(
+ document: document,
+ operationName: operationName,
+ variables: variables,
+ fetchPolicy: policies.fetch,
+ errorPolicy: policies.error,
+ cacheRereadPolicy: policies.cacheReread,
+ optimisticResult: optimisticResult,
+ context: context,
+ parserFn: parserFn,
+ );
}
+@immutable
class WatchQueryOptions<TParsed> extends QueryOptions<TParsed> {
WatchQueryOptions({
required DocumentNode document,
@@ -111,21 +139,99 @@ class WatchQueryOptions<TParsed> extends QueryOptions<TParsed> {
);
/// Whether or not to fetch results
- bool fetchResults;
+ final bool fetchResults;
/// Whether to [fetchResults] immediately on instantiation.
/// Defaults to [fetchResults].
- bool eagerlyFetchResults;
+ final bool eagerlyFetchResults;
/// carry forward previous data in the result of errors and no data.
/// defaults to `true`.
- bool carryForwardDataOnException;
+ final bool carryForwardDataOnException;
@override
- List<Object?> get properties =>
- [...super.properties, fetchResults, eagerlyFetchResults];
+ List<Object?> get properties => [
+ ...super.properties,
+ fetchResults,
+ eagerlyFetchResults,
+ carryForwardDataOnException,
+ ];
+
+ WatchQueryOptions<TParsed> copyWithFetchPolicy(
+ FetchPolicy? fetchPolicy,
+ ) =>
+ WatchQueryOptions<TParsed>(
+ document: document,
+ operationName: operationName,
+ variables: variables,
+ fetchPolicy: fetchPolicy,
+ errorPolicy: errorPolicy,
+ cacheRereadPolicy: cacheRereadPolicy,
+ optimisticResult: optimisticResult,
+ pollInterval: pollInterval,
+ fetchResults: fetchResults,
+ eagerlyFetchResults: eagerlyFetchResults,
+ carryForwardDataOnException: carryForwardDataOnException,
+ context: context,
+ parserFn: parserFn,
+ );
+ WatchQueryOptions<TParsed> copyWithPolicies(
+ Policies policies,
+ ) =>
+ WatchQueryOptions<TParsed>(
+ document: document,
+ operationName: operationName,
+ variables: variables,
+ fetchPolicy: policies.fetch,
+ errorPolicy: policies.error,
+ cacheRereadPolicy: policies.cacheReread,
+ optimisticResult: optimisticResult,
+ pollInterval: pollInterval,
+ fetchResults: fetchResults,
+ eagerlyFetchResults: eagerlyFetchResults,
+ carryForwardDataOnException: carryForwardDataOnException,
+ context: context,
+ parserFn: parserFn,
+ );
+
+ WatchQueryOptions<TParsed> copyWithPollInterval(Duration? pollInterval) =>
+ WatchQueryOptions<TParsed>(
+ document: document,
+ operationName: operationName,
+ variables: variables,
+ fetchPolicy: fetchPolicy,
+ errorPolicy: errorPolicy,
+ cacheRereadPolicy: cacheRereadPolicy,
+ optimisticResult: optimisticResult,
+ pollInterval: pollInterval,
+ fetchResults: fetchResults,
+ eagerlyFetchResults: eagerlyFetchResults,
+ carryForwardDataOnException: carryForwardDataOnException,
+ context: context,
+ parserFn: parserFn,
+ );
+
+ WatchQueryOptions<TParsed> copyWithVariables(
+ Map<String, dynamic> variables) =>
+ WatchQueryOptions<TParsed>(
+ document: document,
+ operationName: operationName,
+ variables: variables,
+ fetchPolicy: fetchPolicy,
+ errorPolicy: errorPolicy,
+ cacheRereadPolicy: cacheRereadPolicy,
+ optimisticResult: optimisticResult,
+ pollInterval: pollInterval,
+ fetchResults: fetchResults,
+ eagerlyFetchResults: eagerlyFetchResults,
+ carryForwardDataOnException: carryForwardDataOnException,
+ context: context,
+ parserFn: parserFn,
+ );
- WatchQueryOptions<TParsed> copy() => WatchQueryOptions<TParsed>(
+ WatchQueryOptions<TParsed> copyWithOptimisticResult(
+ Object? optimisticResult) =>
+ WatchQueryOptions<TParsed>(
document: document,
operationName: operationName,
variables: variables,
@@ -148,6 +254,7 @@ class WatchQueryOptions<TParsed> extends QueryOptions<TParsed> {
/// it is easy to make mistakes in writing [updateQuery].
///
/// To mitigate this, [FetchMoreOptions.partial] has been provided.
+@immutable
class FetchMoreOptions {
FetchMoreOptions({
this.document,
@@ -171,13 +278,13 @@ class FetchMoreOptions {
updateQuery: partialUpdater(updateQuery),
);
- DocumentNode? document;
+ final DocumentNode? document;
- Map<String, dynamic> variables;
+ final Map<String, dynamic> variables;
/// Strategy for merging the fetchMore result data
/// with the result data already in the cache
- UpdateQuery updateQuery;
+ final UpdateQuery updateQuery;
/// Wrap an [UpdateQuery] in a [deeplyMergeLeft] of the `previousResultData`.
static UpdateQuery partialUpdater(UpdateQuery update) =>

MODIFY
packages/graphql/lib/src/graphql_client.dart
packages/graphql/lib/src/graphql_client.dart
@@ -89,9 +89,8 @@ class GraphQLClient implements GraphQLDataProxy {
/// {@end-tool}
ObservableQuery<TParsed> watchQuery<TParsed>(
WatchQueryOptions<TParsed> options) {
- options.policies =
- defaultPolicies.watchQuery.withOverrides(options.policies);
- return queryManager.watchQuery(options);
+ final policies = defaultPolicies.watchQuery.withOverrides(options.policies);
+ return queryManager.watchQuery(options.copyWithPolicies(policies));
}
/// [watchMutation] is the same as [watchQuery], but with a different [defaultPolicies] that are more appropriate for mutations.
@@ -101,9 +100,9 @@ class GraphQLClient implements GraphQLDataProxy {
/// For more details, see https://github.com/zino-app/graphql-flutter/issues/774
ObservableQuery<TParsed> watchMutation<TParsed>(
WatchQueryOptions<TParsed> options) {
- options.policies =
+ final policies =
defaultPolicies.watchMutation.withOverrides(options.policies);
- return queryManager.watchQuery(options);
+ return queryManager.watchQuery(options.copyWithPolicies(policies));
}
/// This resolves a single query according to the [QueryOptions] specified and
@@ -149,16 +148,16 @@ class GraphQLClient implements GraphQLDataProxy {
Future<QueryResult<TParsed>> query<TParsed>(
QueryOptions<TParsed> options,
) async {
- options.policies = defaultPolicies.query.withOverrides(options.policies);
- return await queryManager.query(options);
+ final policies = defaultPolicies.query.withOverrides(options.policies);
+ return await queryManager.query(options.copyWithPolicies(policies));
}
/// This resolves a single mutation according to the [MutationOptions] specified and
/// returns a [Future] which resolves with the [QueryResult] or throws an [Exception].
Future<QueryResult<TParsed>> mutate<TParsed>(
MutationOptions<TParsed> options) async {
- options.policies = defaultPolicies.mutate.withOverrides(options.policies);
- return await queryManager.mutate(options);
+ final policies = defaultPolicies.mutate.withOverrides(options.policies);
+ return await queryManager.mutate(options.copyWithPolicies(policies));
}
/// This subscribes to a GraphQL subscription according to the options specified and returns a
@@ -199,10 +198,10 @@ class GraphQLClient implements GraphQLDataProxy {
/// {@end-tool}
Stream<QueryResult<TParsed>> subscribe<TParsed>(
SubscriptionOptions<TParsed> options) {
- options.policies = defaultPolicies.subscribe.withOverrides(
+ final policies = defaultPolicies.subscribe.withOverrides(
options.policies,
);
- return queryManager.subscribe(options);
+ return queryManager.subscribe(options.copyWithPolicies(policies));
}
/// Fetch more results and then merge them with the given [previousResult]

MODIFY
packages/graphql/test/query_options_test.dart
packages/graphql/test/query_options_test.dart
@@ -5,6 +5,29 @@ import 'package:test/test.dart';
void main() {
group('query options', () {
+ group('equality', () {
+ test('compares content', () {
+ final fn = (_d) => null;
+ final t1 = QueryOptions(
+ variables: {'foo': 'bar'},
+ document: parseString('query { bar }'),
+ parserFn: fn,
+ );
+ final t2 = QueryOptions(
+ variables: {'foo': 'bar'},
+ document: parseString('query { bar }'),
+ parserFn: fn,
+ );
+ final t3 = QueryOptions(
+ document: parseString('query { barz }'),
+ parserFn: fn,
+ );
+ expect(t1, equals(t2));
+ expect(t1.hashCode, equals(t2.hashCode));
+ expect(t1, isNot(equals(t3)));
+ expect(t1.hashCode, isNot(equals(t3.hashCode)));
+ });
+ });
group('type getters', () {
test('on QueryOptions', () {
final options = QueryOptions(

feat: Update options