Skip to content

Commit c6c60ae

Browse files
Neelansh-nsCopilot
authored andcommitted
feat(graphql): add asyncDeepEquals to the graphqlClient
fix: add .ignore() to fire and forget Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
1 parent 4d97e1b commit c6c60ae

File tree

3 files changed

+54
-27
lines changed

3 files changed

+54
-27
lines changed

packages/graphql/lib/src/core/observable_query.dart

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -90,7 +90,7 @@ class ObservableQuery<TParsed> {
9090
/// same as [_onDataCallbacks], but not removed after invocation
9191
Set<OnData<TParsed>> _notRemovableOnDataCallbacks = Set();
9292

93-
/// call [queryManager.maybeRebroadcastQueries] after all other [_onDataCallbacks]
93+
/// call [queryManager.maybeRebroadcastQueriesAsync] after all other [_onDataCallbacks]
9494
///
9595
/// Automatically appended as an [OnData]
9696
FutureOr<void> _maybeRebroadcast(QueryResult? result) {
@@ -101,10 +101,10 @@ class ObservableQuery<TParsed> {
101101
// data. It's valid GQL to have data _and_ exception. If options.carryForwardDataOnException
102102
// are true, this condition may never get hit.
103103
// If there are onDataCallbacks, it's possible they modify cache and are
104-
// depending on maybeRebroadcastQueries being called.
104+
// depending on maybeRebroadcastQueriesAsync being called.
105105
return false;
106106
}
107-
return queryManager.maybeRebroadcastQueries(exclude: this);
107+
return queryManager.maybeRebroadcastQueriesAsync(exclude: this);
108108
}
109109

110110
/// The most recently seen result from this operation's stream

packages/graphql/lib/src/core/query_manager.dart

Lines changed: 45 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -21,19 +21,29 @@ import 'package:graphql/src/scheduler/scheduler.dart';
2121
import 'package:graphql/src/core/_query_write_handling.dart';
2222

2323
typedef DeepEqualsFn = bool Function(dynamic a, dynamic b);
24+
typedef AsyncDeepEqualsFn = Future<bool> Function(dynamic a, dynamic b);
2425

25-
/// The equality function used for comparing cached and new data.
26+
/// The equality function used for comparing cached and new data
27+
/// in synchronous contexts like operator overrides or custom logic.
2628
///
2729
/// You can alternatively provide [optimizedDeepEquals] for a faster
28-
/// equality check. Or provide your own via [GqlClient] constructor.
30+
/// equality check, or provide your own via the [GqlClient] constructor.
2931
DeepEqualsFn gqlDeepEquals = const DeepCollectionEquality().equals;
3032

33+
/// The async equality function used for comparing cached and new data
34+
/// during asynchronous operations like rebroadcast checks.
35+
///
36+
/// Can be provided via the constructor for custom or isolate-based comparison logic.
37+
AsyncDeepEqualsFn gqlAsyncDeepEquals =
38+
(dynamic a, dynamic b) async => gqlDeepEquals(a, b);
39+
3140
class QueryManager {
3241
QueryManager({
3342
required this.link,
3443
required this.cache,
3544
this.alwaysRebroadcast = false,
3645
DeepEqualsFn? deepEquals,
46+
AsyncDeepEqualsFn? asyncDeepEquals,
3747
bool deduplicatePollers = false,
3848
this.requestTimeout = const Duration(seconds: 5),
3949
}) {
@@ -44,12 +54,15 @@ class QueryManager {
4454
if (deepEquals != null) {
4555
gqlDeepEquals = deepEquals;
4656
}
57+
if (asyncDeepEquals != null) {
58+
gqlAsyncDeepEquals = asyncDeepEquals;
59+
}
4760
}
4861

4962
final Link link;
5063
final GraphQLCache cache;
5164

52-
/// Whether to skip deep equality checks in [maybeRebroadcastQueries]
65+
/// Whether to skip deep equality checks in [maybeRebroadcastQueriesAsync]
5366
final bool alwaysRebroadcast;
5467

5568
/// The timeout for resolving a query
@@ -154,7 +167,7 @@ class QueryManager {
154167
)),
155168
))
156169
.map((QueryResult<TParsed> queryResult) {
157-
maybeRebroadcastQueries();
170+
maybeRebroadcastQueriesAsync();
158171
return queryResult;
159172
});
160173
} catch (ex, trace) {
@@ -170,19 +183,19 @@ class QueryManager {
170183

171184
Future<QueryResult<TParsed>> query<TParsed>(
172185
QueryOptions<TParsed> options) async {
173-
final results = fetchQueryAsMultiSourceResult(_oneOffOpId, options);
186+
final results = await fetchQueryAsMultiSourceResult(_oneOffOpId, options);
174187
final eagerResult = results.eagerResult;
175188
final networkResult = results.networkResult;
176189
if (options.fetchPolicy != FetchPolicy.cacheAndNetwork ||
177190
eagerResult.isLoading) {
178191
final result = networkResult ?? eagerResult;
179192
await result;
180-
maybeRebroadcastQueries();
193+
maybeRebroadcastQueriesAsync();
181194
return result;
182195
}
183-
maybeRebroadcastQueries();
196+
maybeRebroadcastQueriesAsync();
184197
if (networkResult is Future<QueryResult<TParsed>>) {
185-
networkResult.then((value) => maybeRebroadcastQueries());
198+
networkResult.then((value) => maybeRebroadcastQueriesAsync());
186199
}
187200
return eagerResult;
188201
}
@@ -205,7 +218,7 @@ class QueryManager {
205218
}
206219

207220
/// wait until callbacks complete to rebroadcast
208-
maybeRebroadcastQueries();
221+
maybeRebroadcastQueriesAsync();
209222

210223
return result;
211224
}
@@ -423,11 +436,12 @@ class QueryManager {
423436
@experimental
424437
Future<List<QueryResult<Object?>?>> refetchSafeQueries() async {
425438
rebroadcastLocked = true;
439+
final queriesSnapshot = List.of(queries.values);
426440
final results = await Future.wait(
427-
queries.values.where((q) => q.isRefetchSafe).map((q) => q.refetch()),
441+
queriesSnapshot.where((q) => q.isRefetchSafe).map((q) => q.refetch()),
428442
);
429443
rebroadcastLocked = false;
430-
maybeRebroadcastQueries();
444+
maybeRebroadcastQueriesAsync();
431445
return results;
432446
}
433447

@@ -444,7 +458,7 @@ class QueryManager {
444458

445459
/// Add a result to the [ObservableQuery] specified by `queryId`, if it exists.
446460
///
447-
/// Will [maybeRebroadcastQueries] from [ObservableQuery.addResult] if the [cache] has flagged the need to.
461+
/// Will [maybeRebroadcastQueriesAsync] from [ObservableQuery.addResult] if the [cache] has flagged the need to.
448462
///
449463
/// Queries are registered via [setQuery] and [watchQuery]
450464
void addQueryResult<TParsed>(
@@ -504,10 +518,10 @@ class QueryManager {
504518
/// **Note on internal implementation details**:
505519
/// There is sometimes confusion on when this is called, but rebroadcasts are requested
506520
/// from every [addQueryResult] where `result.isNotLoading` as an [OnData] callback from [ObservableQuery].
507-
bool maybeRebroadcastQueries({
521+
Future<bool> maybeRebroadcastQueriesAsync({
508522
ObservableQuery<Object?>? exclude,
509523
bool force = false,
510-
}) {
524+
}) async {
511525
if (rebroadcastLocked && !force) {
512526
return false;
513527
}
@@ -522,7 +536,11 @@ class QueryManager {
522536
// to [readQuery] for it once.
523537
final Map<Request, QueryResult<Object?>> diffQueryResultCache = {};
524538
final Map<Request, bool> ignoreQueryResults = {};
525-
for (final query in queries.values) {
539+
540+
final List<ObservableQuery<Object?>> queriesSnapshot =
541+
List.of(queries.values);
542+
543+
for (final query in queriesSnapshot) {
526544
final Request request = query.options.asRequest;
527545
final cachedQueryResult = diffQueryResultCache[request];
528546
if (query == exclude || !query.isRebroadcastSafe) {
@@ -545,7 +563,7 @@ class QueryManager {
545563
query.options.asRequest,
546564
optimistic: query.options.policies.mergeOptimisticData,
547565
);
548-
if (_cachedDataHasChangedFor(query, cachedData)) {
566+
if (await _cachedDataHasChangedForAsync(query, cachedData)) {
549567
// The data has changed
550568
final queryResult = QueryResult(
551569
data: cachedData,
@@ -567,14 +585,19 @@ class QueryManager {
567585
return true;
568586
}
569587

570-
bool _cachedDataHasChangedFor(
588+
Future<bool> _cachedDataHasChangedForAsync(
571589
ObservableQuery<Object?> query,
572590
Map<String, dynamic>? cachedData,
573-
) =>
574-
cachedData != null &&
575-
query.latestResult != null &&
576-
(alwaysRebroadcast ||
577-
!gqlDeepEquals(query.latestResult!.data, cachedData));
591+
) async {
592+
if (cachedData == null || query.latestResult == null) return false;
593+
if (alwaysRebroadcast) return true;
594+
595+
final isEqual = await gqlAsyncDeepEquals(
596+
query.latestResult!.data,
597+
cachedData,
598+
);
599+
return !isEqual;
600+
}
578601

579602
void setQuery(ObservableQuery<Object?> observableQuery) {
580603
queries[observableQuery.queryId] = observableQuery;

packages/graphql/lib/src/graphql_client.dart

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@ class GraphQLClient implements GraphQLDataProxy {
2727
DefaultPolicies? defaultPolicies,
2828
bool alwaysRebroadcast = false,
2929
DeepEqualsFn? deepEquals,
30+
AsyncDeepEqualsFn? asyncDeepEquals,
3031
bool deduplicatePollers = false,
3132
Duration? queryRequestTimeout = const Duration(seconds: 5),
3233
}) : defaultPolicies = defaultPolicies ?? DefaultPolicies(),
@@ -35,6 +36,7 @@ class GraphQLClient implements GraphQLDataProxy {
3536
cache: cache,
3637
alwaysRebroadcast: alwaysRebroadcast,
3738
deepEquals: deepEquals,
39+
asyncDeepEquals: asyncDeepEquals,
3840
deduplicatePollers: deduplicatePollers,
3941
requestTimeout: queryRequestTimeout,
4042
);
@@ -57,6 +59,7 @@ class GraphQLClient implements GraphQLDataProxy {
5759
DefaultPolicies? defaultPolicies,
5860
bool? alwaysRebroadcast,
5961
DeepEqualsFn? deepEquals,
62+
AsyncDeepEqualsFn? asyncDeepEquals,
6063
bool deduplicatePollers = false,
6164
Duration? queryRequestTimeout,
6265
}) {
@@ -66,6 +69,7 @@ class GraphQLClient implements GraphQLDataProxy {
6669
defaultPolicies: defaultPolicies ?? this.defaultPolicies,
6770
alwaysRebroadcast: alwaysRebroadcast ?? queryManager.alwaysRebroadcast,
6871
deepEquals: deepEquals,
72+
asyncDeepEquals: asyncDeepEquals,
6973
deduplicatePollers: deduplicatePollers,
7074
queryRequestTimeout: queryRequestTimeout ?? queryManager.requestTimeout,
7175
);
@@ -269,7 +273,7 @@ class GraphQLClient implements GraphQLDataProxy {
269273
/// pass through to [cache.writeQuery] and then rebroadcast any changes.
270274
void writeQuery(request, {required data, broadcast = true}) {
271275
cache.writeQuery(request, data: data, broadcast: broadcast);
272-
queryManager.maybeRebroadcastQueries();
276+
queryManager.maybeRebroadcastQueriesAsync().ignore();
273277
}
274278

275279
/// pass through to [cache.writeFragment] and then rebroadcast any changes.
@@ -283,7 +287,7 @@ class GraphQLClient implements GraphQLDataProxy {
283287
broadcast: broadcast,
284288
data: data,
285289
);
286-
queryManager.maybeRebroadcastQueries();
290+
queryManager.maybeRebroadcastQueriesAsync();
287291
}
288292

289293
/// Resets the contents of the store with [cache.store.reset()]

0 commit comments

Comments
 (0)