Skip to content

Commit

Permalink
Merge branch 'main' into connectivity-plus-upgrade
Browse files Browse the repository at this point in the history
  • Loading branch information
Avinash-dhiraj authored Apr 13, 2024
2 parents 878c5af + 385c331 commit dee9b94
Show file tree
Hide file tree
Showing 20 changed files with 625 additions and 81 deletions.
13 changes: 11 additions & 2 deletions .github/PULL_REQUEST_TEMPLATE.md
Original file line number Diff line number Diff line change
@@ -1,4 +1,13 @@
Describe the purpose of the pull request.
There are some simple steps to get your PR merged, that are the following:

- Describe your PR and why a maintainer should merge it;
- Put the same description inside the commit body otherwise if we change github hosting you application will be based on a instable source code;
- Write the commit header in the way that it is following these simple rules [1]
- Make sure that your PR will pass the CI
- Wait for an review :smile: that will not happen if one of the previous step is missing.

[1](https://github.com/zino-hofmann/graphql-flutter/blob/main/docs/dev/MAINTAINERS.md#commit-style)

<!--
### Breaking changes
Expand All @@ -14,4 +23,4 @@ Describe the purpose of the pull request.
- Added ... .
- Updated ... .
-->
-->
2 changes: 1 addition & 1 deletion examples/starwars/pubspec.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ description: An example graphql_flutter application utilizing graphql_starwars_t
publish_to: none

environment:
sdk: ">=2.13.0 <=3.0.0"
sdk: ">=2.13.0 <4.0.0"

dependencies:
flutter:
Expand Down
2 changes: 1 addition & 1 deletion examples/starwars/server/pubspec.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ name: starwars_server
publish_to: none

environment:
sdk: ">=2.10.0 <3.0.0"
sdk: ">=2.12.0 <4.0.0"

dependencies:
graphql_starwars_test_server: ^0.1.0
Expand Down
34 changes: 34 additions & 0 deletions packages/graphql/CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,37 @@
# v5.2.0-beta.7

## Fixed
- bump uuid dependency to ^4.0.0 ([commit](https://github.com/zino-hofmann/graphql-flutter/commit/4fa6dd61c7fb9aad806df70a318cfd1086e35e68)). @francescoberardi 23-11-2023


# v5.2.0-beta.6

## Fixed
- Send SubscriptionComplete message when using graphqlTra… ([commit](https://github.com/zino-hofmann/graphql-flutter/commit/6e73d62ba2b8a58a35b3b18e372003462a73e192)). @ 30-08-2023

## Added
- Send custom payload in PingMessage ([commit](https://github.com/zino-hofmann/graphql-flutter/commit/19d5c86b98e889b333996da43126f9404a9a4556)). @Rochak69 31-08-2023
- added WebSocket token refresh and autoReconnect toggling ([commit](https://github.com/zino-hofmann/graphql-flutter/commit/e1c6d5404be2ff54f916bceab6bb52a04bae5d01)). @vytautas-pranskunas- 24-07-2023


# v5.2.0-beta.5

## Fixed
- bumps http to v1 ([commit](https://github.com/zino-hofmann/graphql-flutter/commit/396c3b3f6986b6d3174e548982a93188b49ee5bc)). @moisessantos 06-07-2023


# v5.2.0-beta.4

## Added
- Cache parsed data ([commit](https://github.com/zino-hofmann/graphql-flutter/commit/aa81251f71f7a5f566eae4a9575eb6547050c2d9)). @budde377 03-06-2023


# v5.2.0-beta.3

## Added
- bump sdk version upper bound to <4.0.0 ([commit](https://github.com/zino-hofmann/graphql-flutter/commit/8bb9ba355e53dccf5e291b1f05171459bf8867ed)). @ndelanou 17-05-2023


# v5.2.0-beta.2

## Added
Expand Down
112 changes: 97 additions & 15 deletions packages/graphql/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -128,7 +128,7 @@ which requires a few changes to the above:
> **NB**: This is different in `graphql_flutter`, which provides `await initHiveForFlutter()` for initialization in `main`
```dart
GraphQL getClient() async {
GraphQLClient getClient() async {
...
/// initialize Hive and wrap the default box in a HiveStore
final store = await HiveStore.open(path: 'my/cache/path');
Expand Down Expand Up @@ -327,6 +327,87 @@ subscription = client.subscribe(
subscription.listen(reactToAddedReview)
```

#### Adding headers (including auth) to WebSocket

In order to add auth header or any other header to websocket connection use `initialPayload` property

```dart
initialPayload: () {
var headers = <String, String>{};
headers.putIfAbsent(HttpHeaders.authorizationHeader, () => token);
return headers;
},
```

#### Refreshing headers (including auth)

In order to refresh auth header you need to setup `onConnectionLost` function

```dart
onConnectionLost: (int? code, String? reason) async {
if (code == 4001) {
await authTokenService.issueToken(refresh: true);
return Duration.zero;
}
return null;
}
```

Where `code` and `reason` are values returned from the server on connection close. There is no such code like 401 in WebSockets so you can use your custom and server code could look similar:

```typescript
subscriptions: {
'graphql-ws': {
onConnect: async (context: any) => {
const { connectionParams } = context;

if (!connectionParams) {
throw new Error('Connection params are missing');
}

const authToken = connectionParams.authorization;

if (authToken) {
const isValid await authService.isTokenValid(authToken);

if (!isValid) {
context.extra.socket.close(4001, 'Unauthorized');
}

return;
}
},
},
},
```

`onConnectionLost` function returns `Duration` which is basically `delayBetweenReconnectionAttempts` for current reconnect attempt. If duration is `null` then default `delayBetweenReconnectionAttempts` will be used. Otherwise returned value. For example upon expired auth token there is not much sense to wait after token is refreshed.

#### Handling connection manually

`toggleConnection` stream was introduced to allow connect or disconnect manually.

```dart
var toggleConnection = PublishSubject<ToggleConnectionState>;
SocketClientConfig(
toggleConnection: toggleConnection,
),
```

later from your code call

```dart
toggleConnection.add(ToggleConnectionState.disconnect);
//OR
toggleConnection.add(ToggleConnectionState.connect);
```

When `disconnect` event is called `autoReconnect` stops. When `connect` is called `autoReconnect` resumes.
this is useful when for some reason you want to stop reconnection. For example when user logouts from the system and reconnection would cause auth error from server causing infinite loop.

#### Customizing WebSocket Connections

`WebSocketLink` now has an experimental `connect` parameter that can be
Expand Down Expand Up @@ -427,15 +508,15 @@ class _Connection {
```

2- if you need to update your socket just cancel your subscription and resubscribe again using usual way
2- if you need to update your socket just cancel your subscription and resubscribe again using usual way
and if the token changed it will be reconnect with the new token otherwise it will use the same client



### `client.watchQuery` and `ObservableQuery`

[`client.watchQuery`](https://pub.dev/documentation/graphql/latest/graphql/GraphQLClient/watchQuery.html)
can be used to execute both queries and mutations, then reactively listen to changes to the underlying data in the cache.
can be used to execute both queries and mutations, then reactively listen to changes to the underlying data in the cache.

```dart
final observableQuery = client.watchQuery(
Expand Down Expand Up @@ -506,7 +587,7 @@ To disable cache normalization entirely, you could pass `(data) => null`.
If you only cared about `nodeId`, you could pass `(data) => data['nodeId']`.

Here's a more detailed example where the system involved contains versioned entities you don't want to clobber:
```dart
```dart
String customDataIdFromObject(Map<String, Object> data) {
final typeName = data['__typename'];
final entityId = data['entityId'];
Expand Down Expand Up @@ -589,17 +670,17 @@ query {

```

if you're not providing the possible type map and introspecting the typename, the cache can't be updated.
if you're not providing the possible type map and introspecting the typename, the cache can't be updated.

## Direct Cache Access API

The [`GraphQLCache`](https://pub.dev/documentation/graphql/latest/graphql/GraphQLCache-class.html)
leverages [`normalize`] to give us a fairly apollo-ish [direct cache access] API, which is also available on `GraphQLClient`.
This means we can do [local state management] in a similar fashion as well.

The cache access methods are available on any cache proxy, which includes the `GraphQLCache` the `OptimisticProxy` passed to `update` in the `graphql_flutter` `Mutation` widget, and the `client` itself.
The cache access methods are available on any cache proxy, which includes the `GraphQLCache` the `OptimisticProxy` passed to `update` in the `graphql_flutter` `Mutation` widget, and the `client` itself.
> **NB** counter-intuitively, you likely never want to use use direct cache access methods directly on the `cache`,
> as they will not be rebroadcast automatically.
> as they will not be rebroadcast automatically.
> **Prefer `client.writeQuery` and `client.writeFragment` to those on the `client.cache` for automatic rebroadcasting**
In addition to this overview, a complete and well-commented rundown of can be found in the
Expand Down Expand Up @@ -641,10 +722,10 @@ final data = client.readQuery(queryRequest);
client.writeQuery(queryRequest, data);
```

The cache access methods are available on any cache proxy, which includes the `GraphQLCache` the `OptimisticProxy` passed to `update` in the `graphql_flutter` `Mutation` widget, and the `client` itself.
> **NB** counter-intuitively, you likely never want to use use direct cache access methods on the cache
The cache access methods are available on any cache proxy, which includes the `GraphQLCache` the `OptimisticProxy` passed to `update` in the `graphql_flutter` `Mutation` widget, and the `client` itself.
> **NB** counter-intuitively, you likely never want to use use direct cache access methods on the cache
cache.readQuery(queryRequest);
client.readQuery(queryRequest); //
client.readQuery(queryRequest); //

### `FragmentRequest`, `readFragment`, and `writeFragment`
`FragmentRequest` has almost the same api as `Request`, but is provided directly from `graphql` for consistency.
Expand Down Expand Up @@ -710,7 +791,7 @@ client.query(QueryOptions(
errorPolicy: ErrorPolicy.ignore,
// ignore cache data.
cacheRereadPolicy: CacheRereadPolicy.ignore,
// ...
// ...
));
```
Defaults can also be overridden via `defaultPolices` on the client itself:
Expand All @@ -724,11 +805,11 @@ GraphQLClient(
CacheRereadPolicy.mergeOptimistic,
),
),
// ...
// ...
)
```

**[`FetchPolicy`](https://pub.dev/documentation/graphql/latest/graphql/FetchPolicy-class.html):** determines where the client may return a result from, and whether that result will be saved to the cache.
**[`FetchPolicy`](https://pub.dev/documentation/graphql/latest/graphql/FetchPolicy-class.html):** determines where the client may return a result from, and whether that result will be saved to the cache.
Possible options:

- cacheFirst: return result from cache. Only fetch from network if cached result is not available.
Expand All @@ -737,7 +818,7 @@ Possible options:
- noCache: return result from network, fail if network call doesn't succeed, don't save to cache.
- networkOnly: return result from network, fail if network call doesn't succeed, save to cache.

**[`ErrorPolicy`](https://pub.dev/documentation/graphql/latest/graphql/ErrorPolicy-class.html):** determines the level of events for errors in the execution result.
**[`ErrorPolicy`](https://pub.dev/documentation/graphql/latest/graphql/ErrorPolicy-class.html):** determines the level of events for errors in the execution result.
Possible options:

- none (default): Any GraphQL Errors are treated the same as network errors and any data is ignored from the response.
Expand Down Expand Up @@ -869,7 +950,7 @@ API key, IAM, and Federated provider authorization could be accomplished through

This package does not support code-generation out of the box, but [graphql_codegen](https://pub.dev/packages/graphql_codegen) does!

This package extensions on the client which takes away the struggle of serialization and gives you confidence through type-safety.
This package extensions on the client which takes away the struggle of serialization and gives you confidence through type-safety.
It is also more performant than parsing GraphQL queries at runtime.

For example, by creating the `.graphql` file
Expand Down Expand Up @@ -966,3 +1047,4 @@ HttpLink httpLink = HttpLink('https://api.url/graphql', defaultHeaders: {
[local state management]: https://www.apollographql.com/docs/tutorial/local-state/#update-local-data
[`typepolicies`]: https://www.apollographql.com/docs/react/caching/cache-configuration/#the-typepolicy-type
[direct cache access]: https://www.apollographql.com/docs/react/caching/cache-interaction/

2 changes: 1 addition & 1 deletion packages/graphql/changelog.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"package_name": "graphql",
"version": "v5.2.0-beta.2",
"version": "v5.2.0-beta.7",
"api": {
"name": "github",
"repository": "zino-hofmann/graphql-flutter",
Expand Down
2 changes: 1 addition & 1 deletion packages/graphql/example/pubspec.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ version: 1.0.0+1
publish_to: none

environment:
sdk: '>=2.12.0 <3.0.0'
sdk: '>=2.12.0 <4.0.0'

dependencies:
args:
Expand Down
4 changes: 2 additions & 2 deletions packages/graphql/lib/src/core/fetch_more.dart
Original file line number Diff line number Diff line change
Expand Up @@ -34,11 +34,11 @@ Future<QueryResult<TParsed>> fetchMoreImplementation<TParsed>(
final data = fetchMoreOptions.updateQuery(
previousResult.data,
fetchMoreResult.data,
)!;
);

fetchMoreResult.data = data;

if (originalOptions.fetchPolicy != FetchPolicy.noCache) {
if (originalOptions.fetchPolicy != FetchPolicy.noCache && data != null) {
queryManager.attemptCacheWriteFromClient(
request,
data,
Expand Down
24 changes: 20 additions & 4 deletions packages/graphql/lib/src/core/query_result.dart
Original file line number Diff line number Diff line change
Expand Up @@ -43,12 +43,14 @@ final _eagerSources = {
class QueryResult<TParsed extends Object?> {
@protected
QueryResult.internal({
this.data,
Map<String, dynamic>? data,
this.exception,
this.context = const Context(),
required this.parserFn,
required this.source,
}) : timestamp = DateTime.now();
}) : timestamp = DateTime.now() {
_data = data;
}

factory QueryResult({
required BaseOptions<TParsed> options,
Expand Down Expand Up @@ -99,7 +101,17 @@ class QueryResult<TParsed extends Object?> {
QueryResultSource? source;

/// Response data
Map<String, dynamic>? data;
Map<String, dynamic>? get data {
return _data;
}

set data(Map<String, dynamic>? data) {
_data = data;
_cachedParsedData = null;
}

Map<String, dynamic>? _data;
TParsed? _cachedParsedData;

/// Response context. Defaults to an empty `Context()`
Context context;
Expand Down Expand Up @@ -137,11 +149,15 @@ class QueryResult<TParsed extends Object?> {
TParsed? get parsedData {
final data = this.data;
final parserFn = this.parserFn;
final cachedParsedData = _cachedParsedData;

if (data == null) {
return null;
}
return parserFn(data);
if (cachedParsedData != null) {
return cachedParsedData;
}
return _cachedParsedData = parserFn(data);
}

@override
Expand Down
Loading

0 comments on commit dee9b94

Please sign in to comment.