Skip to content

Commit

Permalink
feat: client.fetchMore utility for leveraging the fetch more logic re…
Browse files Browse the repository at this point in the history
…sults without using ObservableQuery
  • Loading branch information
micimize committed May 21, 2020
1 parent 84cba43 commit 814ccb3
Show file tree
Hide file tree
Showing 12 changed files with 314 additions and 234 deletions.
6 changes: 3 additions & 3 deletions packages/graphql/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ First, depend on this package:

```yaml
dependencies:
graphql: ^3.0.0
graphql: ^4.0.0-rc1
```
And then import it inside your dart code:
Expand All @@ -45,13 +45,13 @@ dev_dependencies:
To connect to a GraphQL Server, we first need to create a `GraphQLClient`. A `GraphQLClient` requires both a `cache` and a `link` to be initialized.

In our example below, we will be using the Github Public API. In our example below, we are going to use `HttpLink` which we will concatenate with `AuthLink` so as to attach our github access token. For the cache, we are going to use `GraphQLCache`.
In our example below, we will be using the Github Public API. we are going to use `HttpLink` which we will concatenate with `AuthLink` so as to attach our github access token. For the cache, we are going to use `GraphQLCache`.

```dart
// ...
final HttpLink _httpLink = HttpLink(
uri: 'https://api.github.com/graphql',
'https://api.github.com/graphql',
);
final AuthLink _authLink = AuthLink(
Expand Down
7 changes: 6 additions & 1 deletion packages/graphql/example/bin/main.dart
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,11 @@ ArgResults argResults;

// client - create a graphql client
GraphQLClient client() {
/// `graphql/client.dart` leverages the [gql_link][1] interface,
/// re-exporting `HttpLink`, `WebsocketLink`, `ErrorLink`, and `DedupeLink`,
/// in addition to the links we define ourselves (`AuthLink`)
///
/// [1]: https://pub.dev/packages/gql_link
final Link _link = HttpLink(
'https://api.github.com/graphql',
defaultHeaders: {
Expand All @@ -37,7 +42,7 @@ void query() async {

final QueryOptions options = QueryOptions(
document: gql(readRepositories),
variables: <String, dynamic>{
variables: {
'nRepositories': nRepositories,
},
);
Expand Down
2 changes: 2 additions & 0 deletions packages/graphql/lib/client.dart
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,9 @@ library graphql;
export 'package:graphql/src/cache/cache.dart';
export 'package:graphql/src/core/query_manager.dart';
export 'package:graphql/src/core/query_options.dart';
export 'package:graphql/src/core/fetch_more.dart' show FetchMoreOptions;
export 'package:graphql/src/core/query_result.dart';
export 'package:graphql/src/core/policies.dart';
export 'package:graphql/src/exceptions.dart';
export 'package:graphql/src/graphql_client.dart';

Expand Down
98 changes: 98 additions & 0 deletions packages/graphql/lib/src/core/fetch_more.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,98 @@
import 'dart:async';

import 'package:gql/ast.dart';
import 'package:graphql/client.dart';
import 'package:meta/meta.dart';

import 'package:graphql/src/core/query_manager.dart';
import 'package:graphql/src/core/query_options.dart';
import 'package:graphql/src/core/query_result.dart';
import 'package:graphql/src/core/policies.dart';

/// options for fetchmore operations
class FetchMoreOptions {
FetchMoreOptions({
@required this.document,
this.variables = const <String, dynamic>{},
@required this.updateQuery,
}) : assert(updateQuery != null);

DocumentNode document;

final Map<String, dynamic> variables;

/// Strategy for merging the fetchMore result data
/// with the result data already in the cache
UpdateQuery updateQuery;
}

/// Fetch more results and then merge them with [previousResult]
/// according to [FetchMoreOptions.updateQuery]
///
/// Will add results if [ObservableQuery.queryId] is supplied,
/// and broadcast any cache changes
///
/// This is the **Internal Implementation**,
/// used by [ObservableQuery] and [GraphQLCLient.fetchMore]
Future<QueryResult> fetchMoreImplementation(
FetchMoreOptions fetchMoreOptions, {
@required QueryOptions originalOptions,
@required QueryManager queryManager,
@required QueryResult previousResult,
String queryId,
}) async {
// fetch more and udpate
assert(fetchMoreOptions.updateQuery != null);

final document = (fetchMoreOptions.document ?? originalOptions.document);

assert(
document != null,
'Either fetchMoreOptions.document '
'or the previous QueryOptions must be supplied!',
);

final combinedOptions = QueryOptions(
fetchPolicy: FetchPolicy.noCache,
errorPolicy: originalOptions.errorPolicy,
document: document,
variables: {
...originalOptions.variables,
...fetchMoreOptions.variables,
},
);

QueryResult fetchMoreResult = await queryManager.query(combinedOptions);

try {
// combine the query with the new query, using the function provided by the user
fetchMoreResult.data = fetchMoreOptions.updateQuery(
previousResult.data,
fetchMoreResult.data,
);
assert(fetchMoreResult.data != null, 'updateQuery result cannot be null');
// will add to a stream with `queryId` and rebroadcast if appropriate
queryManager.addQueryResult(
originalOptions.asRequest,
queryId,
fetchMoreResult,
writeToCache: originalOptions.fetchPolicy != FetchPolicy.noCache,
);
} catch (error) {
if (fetchMoreResult.hasException) {
// because the updateQuery failure might have been because of these errors,
// we just add them to the old errors
previousResult.exception = coalesceErrors(
exception: previousResult.exception,
graphqlErrors: fetchMoreResult.exception.graphqlErrors,
linkException: fetchMoreResult.exception.linkException,
);
return previousResult;
} else {
// TODO merge results OperationException
rethrow;
}
}

return fetchMoreResult;
}
86 changes: 22 additions & 64 deletions packages/graphql/lib/src/core/observable_query.dart
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
import 'dart:async';

import 'package:graphql/src/exceptions.dart';
import 'package:meta/meta.dart';

import 'package:graphql/src/core/query_manager.dart';
import 'package:graphql/src/core/query_options.dart';
import 'package:graphql/src/core/fetch_more.dart';
import 'package:graphql/src/core/query_result.dart';
import 'package:graphql/src/core/policies.dart';
import 'package:graphql/src/scheduler/scheduler.dart';

typedef OnData = void Function(QueryResult result);
Expand All @@ -22,6 +22,11 @@ enum QueryLifecycle {
CLOSED
}

/// An Observable/Stream-based API returned from `watchQuery` for use in reactive programming
///
/// Modelled closely after [Apollo's ObservableQuery][apollo_oq]
///
/// [apollo_oq]: https://www.apollographql.com/docs/react/v3.0-beta/api/core/ObservableQuery/
class ObservableQuery {
ObservableQuery({
@required this.queryManager,
Expand All @@ -39,6 +44,7 @@ class ObservableQuery {
// set to true when eagerly fetched to prevent back-to-back queries
bool _latestWasEagerlyFetched = false;

/// The identity of this query within the [QueryManager]
final String queryId;
final QueryManager queryManager;

Expand Down Expand Up @@ -136,67 +142,21 @@ class ObservableQuery {
return allResults;
}

/// fetch more results and then merge them according to the updateQuery method.
/// the results will then be added to to stream for the widget to re-build
void fetchMore(FetchMoreOptions fetchMoreOptions) async {
// fetch more and udpate
/// fetch more results and then merge them with the [latestResult]
/// according to [FetchMoreOptions.updateQuery].
/// The results will then be added to to stream for the widget to re-build
Future<QueryResult> fetchMore(FetchMoreOptions fetchMoreOptions) async {
assert(fetchMoreOptions.updateQuery != null);

final combinedOptions = QueryOptions(
fetchPolicy: FetchPolicy.noCache,
errorPolicy: options.errorPolicy,
document: fetchMoreOptions.document ?? options.document,
context: options.context,
variables: {
...options.variables,
...fetchMoreOptions.variables,
},
);

// stream old results with a loading indicator
addResult(QueryResult.loading(data: latestResult.data));

QueryResult fetchMoreResult = await queryManager.query(combinedOptions);

final request = options.asRequest;

try {
// combine the query with the new query, using the function provided by the user
fetchMoreResult.data = fetchMoreOptions.updateQuery(
latestResult.data,
fetchMoreResult.data,
);
assert(fetchMoreResult.data != null, 'updateQuery result cannot be null');

// stream the new results and rebuild
queryManager.addQueryResult(
request,
queryId,
fetchMoreResult,
writeToCache: true,
);
} catch (error) {
if (fetchMoreResult.hasException) {
// because the updateQuery failure might have been because of these errors,
// we just add them to the old errors
latestResult.exception = coalesceErrors(
exception: latestResult.exception,
graphqlErrors: fetchMoreResult.exception.graphqlErrors,
linkException: fetchMoreResult.exception.linkException,
);

queryManager.addQueryResult(
request,
queryId,
latestResult,
writeToCache: true,
);
return;
} else {
// TODO merge results OperationException
rethrow;
}
}
return fetchMoreImplementation(
fetchMoreOptions,
originalOptions: options,
queryManager: queryManager,
previousResult: latestResult,
queryId: queryId,
);
}

/// add a result to the stream,
Expand All @@ -213,7 +173,7 @@ class ObservableQuery {
result.source ??= latestResult.source;
}

if (lifecycle == QueryLifecycle.PENDING && result.optimistic != true) {
if (lifecycle == QueryLifecycle.PENDING && !result.isOptimistic) {
lifecycle = QueryLifecycle.COMPLETED;
}

Expand All @@ -232,14 +192,12 @@ class ObservableQuery {
StreamSubscription<QueryResult> subscription;

subscription = stream.listen((QueryResult result) async {
if (!result.loading) {
if (!result.isLoading) {
for (final callback in callbacks) {
await callback(result);
}

queryManager.rebroadcastQueries();

if (!result.optimistic) {
if (!result.isOptimistic) {
await subscription.cancel();
_onDataSubscriptions.remove(subscription);

Expand Down
Loading

0 comments on commit 814ccb3

Please sign in to comment.