Giter Club home page Giter Club logo

gql's Introduction

gql-dart/gql

MIT License PRs Welcome Watch on GitHub Star on GitHub Watch on GitHub Discord

This is an effort to advance the Dart GraphQL ecosystem.

It consists of multiple packages and libraries centered around GraphQL AST.

Packages

Core

The core of this project is the GraphQL parser which parses GraphQL strings into an AST. The parsed AST can then be transformed, visited and printed back to GraphQL string.

Pub Package Library Description
version package:gql ast.dart GraphQL AST implementation with Visitor pattern and AST transformer
version package:gql language.dart GraphQL source parser and printer. Recognizes both operations and SDL
version package:gql document.dart Document and schema validation

Code generation

GraphQL language enables code generation to speed up development and move document processing to build-time.

We provide code builders to generate Dart source code and file builders which easily plug into standard Dart code generation pipeline.

Ideas for future work:

  • Typed resolvers for field resolution on the client or server
Pub Package Library Description
version package:gql_code_builder Various builders
version package:gql_build File builders

Client

To enable development of GraphQL clients, we provide type definitions used to execute GraphQL operations. They include AST-based operation, request with per operation context, and response. These types are used by the Links.

Link is the current client implementation. Link is GraphQL AST-aware request/response middleware. They can be used to build a pipeline to deduplicate requests, conditionally transform requests and responses (including the context), and eventually send the requests to the server.

Ideas for future work:

  • websocket link for subscriptions
  • batched request HTTP link
  • retry link to handle network issues transparently
  • link for client-side request resolution
Pub Package Library Description
version package:gql_exec gql_exec.dart Implementation of types for GraphQL requests and responses
version package:gql_link link.dart Base for modular GraphQL execution interface
version package:gql_http_link gql_http_link.dart Link to execute GraphQL requests via HTTP
version package:gql_dedupe_link gql_dedupe_link.dart De-duplicating Link to avoid execution of identical requests
version package:gql_transform_link gql_transform_link.dart GQL Link to transform Requests and Responses. May be used to update context, document, variables, data, errors, etc.
version package:gql_websocket_link gql_websocket_link.dart GQL Link for subscriptions using websocket.
version package:gql_dio_link gql_dio_link.dart Similar to gql_http_link, A GQL Terminating Link to execute requests via Dio using JSON.

Other

Pub Package Library Description
version package:gql_pedantic Lint rules used by all gql-dart/gql packages

Examples

Pub Package Library Description
version package:gql_example_cli Example usage in a CLI environment
version package:gql_example_flutter Example usage in a Flutter environment

Users

Project Description
artemis Build dart types from GraphQL schemas and queries (using Introspection Query).
graphql A stand-alone GraphQL client for Dart, bringing all the features from a modern GraphQL client to one easy to use package.
graphql_flutter A GraphQL client for Flutter, bringing all the features from a modern GraphQL client to one easy to use package.
normalize Normalization and denormalization of GraphQL responses in Dart
ferry GraphQL Client for Dart
Your project? Open a PR to add it to this readme!

Contributing

The goal of this project is to expand Dart GraphQL ecosystem and to build a community around this vendor-neutral implementation.

Community contributions are welcome.

multipack

This repo uses multipack. To activate it run the following command.

pub global activate multipack

multipack provides a simple way of running commands in multiple packages at once. It builds a directed graph of packages to run commands in topological order.

Link all local packages by running

multipack pubspec override

Get all packages by running

multipack pub get

Clean up the pubspec file before publishing

multipack pubspec clean

See more usage examples in .github/workflows/dart.yml.

Features and bugs

Please file feature requests and bugs at the GitHub.

gql's People

Contributors

2shrestha22 avatar agent3bood avatar britannio avatar caffeineflo avatar catapop84 avatar comigor avatar dehypnosis avatar grohden avatar huanggood-star avatar jamonkko avatar janosroden avatar jonas-jonas avatar juancastillo0 avatar julianscheel avatar kenspy avatar klavs avatar knaeckekami avatar lilatee avatar liyuqian avatar micimize avatar osaxma avatar philipsoeberg avatar rgplvr avatar rodolfosilva avatar samuelstroschein avatar smkhalsa avatar tarekkma avatar ueman avatar valentinvignal avatar vasilich6107 avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

gql's Issues

Blank comment lines break parser

I've found that a "blank comment" (lines with only # followed by line terminator) before any important line break gql parser.

For instance, any of the following breaks:

#
schema {
#
  query: Query
}

type Query {
  object: Object!
#
}

# The next line will be only `#`
#
type Object {
  id: String
}

As per spec "Comments" section, it seems they are valid.

Here's a file reproducing this error: schema.graphql

Typed graphql document visitor

It would make some use cases easier to implement if there was a type-aware visitor provided. Maybe something that visits AST nodes that have a type attribute?

I'm basically trying to implement something like what I talk about in comigor/artemis#39, where a base schema_types.dart file gets generated with just the subset of schema types referenced by the collected operations and fragments.

[gql_link] exceptions

I am working on adding exceptions to WebSocketLink, the plan is

  1. WebSocketLinkParserException Exception occurring when parsing fails or parsed response is missing both data and errors.

  2. WebSocketLinkServerException Exception occurring when network fails.

Right now the error "parsed response is missing both data and errors." is handled as ServerException

I want to refactor this as ResponseFormatException.

Any suggestions, should I continue as planned?

Implement schema validation

This is partially implemented, but still lacks majority of the rules.

Unlike graphql-js, there is no validation context given when validating a document. A schema validation rule should be self-sufficient and based only on the given AST document.
See https://github.com/gql-dart/gql/tree/master/gql/lib/src/validation.

  • LoneSchemaDefinition (ref)
  • UniqueOperationTypes (ref)
  • UniqueTypeNames (ref; issue)
  • UniqueEnumValueNames (ref)
  • UniqueFieldDefinitionNames (ref)
  • UniqueDirectiveNames (ref)
  • KnownTypeNames (ref; issue)
  • KnownDirectives (ref; issue)
  • UniqueDirectivesPerLocation (ref; issue)
  • PossibleTypeExtensions (ref; issue)
  • KnownArgumentNamesOnDirectives (ref; issue)
  • UniqueArgumentNames (ref; issue)
  • UniqueInputFieldNames (ref; issue)
  • ProvidedRequiredArgumentsOnDirectives (ref; issue)

websocket inactivityTimeout

Was wondering why gql_websocket_link removed the inactivityTimeout option?

      if (config.inactivityTimeout != null) {
        _keepAliveSubscription = _messagesOfType<ConnectionKeepAlive>().timeout(
          config.inactivityTimeout,
          onTimeout: (EventSink<ConnectionKeepAlive> event) {
            print(
                "Haven't received keep alive message for ${config.inactivityTimeout.inSeconds} seconds. Disconnecting..");
            event.close();
            _socket.close(WebSocketStatus.goingAway);
            _connectionStateController.value =
                SocketConnectionState.NOT_CONNECTED;
          },
        ).listen(null);
      }

Make Operation Context serializable

I mentioned the issue of serializing Context in #110, but I figured I'd open a separate issue to discuss this further.

As far as I can tell, some way of serializing data passed into Context is necessary to enable first-class support for offline-first use cases.

For example, let's say we have a mutation that requires passing the userId as an argument. We might create a ContextEntry such as the following:

class UserContextEntry extends ContextEntry {
  final String userId;

  UserContextEntry(this.userId);

  @override
  List<Object> get fieldsForEquality => [userId];
}

If the app is offline, a GraphQL client will need to persist this data along with the query in order to properly execute the mutation when the app comes back online.

We could potentially work around this using code generators without adjusting the existing Context implementation. For example, we could create an aggregate builder that searches source code for implementations of ContextEntry, then builds a serializer that maps type names to types so that ContextEntrys can be deserialized later. This would, of course, require each ContextEntry to be serializable, meaning that our UserContextEntry above would probably need to be extended to something like this:

class UserContextEntry extends ContextEntry implements SerializableContextEntry {
  final String userId;

  UserContextEntry(this.userId);

  @override
  List<Object> get fieldsForEquality => [userId];

  Map<String, dynamic> toJson() => {"userId": userId};

  UserContextEntry fromJson(Map<String, dynamic> json) =>
      UserContextEntry(json["userId"]);
}

This is starting to be a lot of boilerplate just to pass a userId to a mutation.

@klavs @micimize I'd appreciate any insight you may have on this.

[gql_build] multiple queries in one file?

Hi,

I was trying out gql_build and ran into an issue:

I defined a schema.graphql file and added a queries.graphql file, where I added multiple queries (say, Query1, Query2, Query3) with different required variables.

This does not seem to work.
In the queries.ast.gql.dart file, there is a line

      const document = _i1.DocumentNode(definitions: [Query1, Query2, Query3 ...]);

If I then execute

    graphqlClient
        .responseStream(Query1(
            buildVars: (vars) => vars..query1Var = "test"
      )

All Queries (Query1, Query2, Query3) will be executed, but of course, this query fails as the variables for the latter 2 queries are not set.
If I split all the queries in separate files, they work fine.

Is having multiple queries in one .graphql file supported?
If yes, this should be fixed.
If not, this should probably be an error during the build_runner step.

[gql_build] Support shared fragments

As far as I can tell, there is no way to have multiple queries use shared fragments
i.e.

fragment someFragment on C {
fields
}

query A {
someField
...someFragment
}

query B {
someField
...someFragment
}

A normal use of fragments is to extract shared pieces of queries.
This is pretty important in dart as types are not structural like other languages such as Typescript.
That means if I want to render a shared widget using the query data, I cannot mix and match data from query A and query B even if the types are structurally the same.

Should every exception thrown from the Link be of type LinkException?

I'm building my own link that utilizes dio for making HTTP requests instead of http library. dio have multiple types of errors should I remap them to a exceptions that extend LinkException?

for example, should I do the following or rethrow expectations other than the ones in the gql_link library?

} on dio.DioError catch (e) {
  if (e.type != dio.DioErrorType.RESPONSE) {
    final res = e.response;
    if (res.data is Map<String, dynamic>) {
      final parsedResponse =
          parser.parseResponse(res.data as Map<String, dynamic>);
      throw DioLinkServerException(
          response: res, parsedResponse: parsedResponse);
    }
  }else if (e.type == dio.DioErrorType.CONNECT_TIMEOUT){
    throw DioLinkTimeoutException(originalException:e);
  }
  else if(...){
    ...
    throw DioLinkXXXException(originalException:e);
  }
}catch (e){
  throw DioLinkUnkownException(originalException:e);
}

And as I can see the ferry client only catches LinkException.
https://github.com/gql-dart/ferry/blob/master/lib/src/client/client.dart#L171

[gql_build] duplicate ast imports

I'm running into an issue with the new import functionality. If I have a query that uses multiple fragments which themselves use a common fragment, the ast_builder will include multiple instances of the import for that common fragment in its List of definitions.

@klavs I assume we can simply check for uniqueness here, but let me know your thoughts.

Update to gql_link: ^0.3.0 and gql_http_link: ^0.2.9

Hi!

Try get new updates for gql_link: ^0.3.0 and gql_http_link: ^0.2.9, and get in

Because gql_http_link >=0.2.9 depends on gql_link ^0.3.0 and lifext depends on gql_link ^0.2.3, gql_http_link >=0.2.9 is forbidden.

So, because lifext depends on gql_http_link ^0.2.9, version solving failed.
pub get failed (1; So, because lifext depends on gql_http_link ^0.2.9, version solving failed.)

If update only gql_link: ^0.3.0

Because ferry 0.3.0 depends on gql_link ^0.2.2 and no versions of ferry match >0.3.0 <0.4.0, ferry ^0.3.0 requires gql_link ^0.2.2.

If update only gql_http_link: ^0.2.9

Because gql_http_link >=0.2.9 depends on gql_link ^0.3.0 and lifext depends on gql_link ^0.2.3, gql_http_link >=0.2.9 is forbidden.

[gql_build] [BUG] Can not have types with 'value' as property

When the Graphql type has a property with name value it will generate schema.gql.dart file with duplicated property names. This happens mostly when using Hasura and its feature Enum which you have a Type with value and comment properties. Here is an example of such issue:

class role_select_column {
  const role_select_column(this.value);

  final String value;

  static const role_select_column comment = role_select_column('comment');

  static const role_select_column value = role_select_column('value');

  @override
  int get hashCode => value.hashCode;
  @override
  bool operator ==(Object o) => o is role_select_column && o.value == value;
}

I'm not sure what would be the correct approach to fix this issue, my best guess is to rename String value property to something else when it detects that there is a property with same name.

[gql_build] Add immutability features

I just wanted to start a discussion about the possibility of building data as built_values rather than storing the Json object and using getters.

I find the immutability and serializability of built_value to be compelling, although it would add some complexity.

Thoughts?

What packages can be used to build graphql server?

I am interested in building graphql server (not using angel framework and its packages).
In this project, what packages can be used to build graphql server?
If you give me a rough idea, it will really be appreciated.

[RFC] - Use generic types for data & variables in Request & Response

Currently, the Request.variables and Response.dataobjects must be json (i.e. Map<String, dynamic>).

Therefore, in order to implement typed requests and responses (such as those used in ferry), we must re-implement the Request and Response classes.

I suggest we consider the following changes:

  1. use generic types for data and variables in Request, Response
  2. make Request and Response abstract
  3. implement a default concrete implementation of Request and Response that uses json for data and vars.

For example, Request might look like this:

abstract class Request<TVars> {
  final Operation operation;

  final TVars variables;

  const Request({
    this.operation,
    this.variables,
  });
}

We could then implement a DefaultRequest that uses json:

class DefaultRequest extends Request<Map<String, dynamic>> {
  @override
  final Operation operation;

  @override
  final Map<String, dynamic> variables;

  final Context context;

  const DefaultRequest({
    @required this.operation,
    this.variables = const <String, dynamic>{},
    this.context = const Context(),
  })  : assert(operation != null),
        assert(context != null);

  DefaultRequest withContextEntry<T extends ContextEntry>(T entry) =>
      DefaultRequest(
        operation: operation,
        variables: variables,
        context: context.withEntry<T>(entry),
      );

  DefaultRequest updateContextEntry<T extends ContextEntry>(
    ContextUpdater<T> update,
  ) =>
      DefaultRequest(
        operation: operation,
        variables: variables,
        context: context.updateEntry<T>(update),
      );

  List<Object> _getChildren() => [
        operation,
        variables,
        context,
      ];

  @override
  bool operator ==(Object o) =>
      identical(this, o) ||
      (o is DefaultRequest &&
          const ListEquality<Object>(
            DeepCollectionEquality(),
          ).equals(
            o._getChildren(),
            _getChildren(),
          ));

  @override
  int get hashCode => const ListEquality<Object>(
        DeepCollectionEquality(),
      ).hash(
        _getChildren(),
      );
}

Of course, we'd also need to make changes to gql_link and any other libraries that depend on gql_exec.

[gql_build] Overhaul import logic

As discussed here, in order to support building fragment data next to the fragment definitions in source, we need to rethink the import implementation.

In addition, I'd like to suggest that we follow the graphql-import style for imports...

Import specific types: # import A from 'schema.graphql'
Import multiple types: # import A, B, C from 'schema.graphql'
Import all types: # import * from 'schema.graphql'
Import root fields: # import Query.* from 'schema.graphql'
Import root fields and all types: # import Query.*, Mutation.*, * from 'schema.graphql'
Relative paths: # import Post from "../database/schema.graphql"

[gql_http_link] Consider adding more context options

  • should/can other links add headers?
  • should more http config options (like method) just be context? That way, useGETForQueries could eventually be a standalone a context update operation

For reference, I think the entire graphql/client.dart HttpConfig can be overridden through context, which is roughly:

class HttpQueryOptions {
  bool includeQuery;
  bool includeExtensions;
}


class HttpConfig {
  HttpQueryOptions http;
  Map<String, dynamic> options;
  Map<String, dynamic> credentials;
  Map<String, String> headers;
}

[gql_build] Build to cache

I've been experimenting with the builders, and I think we should build most of the files to the cache rather than to source. We have 10 builders now, and the number of generated files is overwhelming.

Do any builders other than req_builder and data_builder really need to be built to source?

[gql_build] ensure all serializer return types (all types?) are available in generated file, or allow import config

@smkhalsa Issue created from our DIscord discussion

A custom serializer's return type that is imported from another file is not shown in serializers.gql.dart causing a build error.

Only happens with generated BuiltLists? I have other custom scalars set-up the same way but addBuilderFactory functions are not generated for them so this problem doesn't occur...

Also, the addBuilderFactory function is generated multiple times...

Note: build_runner build is still successful

Error

lib/graphql/schema/serializers.gql.g.dart:182:59: Error: Getter not found: 'MyModel'.
          const FullType(BuiltList, const [const FullType(MyModel)]),
                                                          ^^^^^^^^^^^^^^
lib/graphql/schema/serializers.gql.g.dart:183:33: Error: 'MyModel' isn't a type.
          () => new ListBuilder<MyModel>())
                                ^^^^^^^^^^^^^^

Example

# schema.g.graphql
#...
"""JSON encoded custom scalar type"""
scalar MyModelPlural

type Foo {
  id: String!
  items: [MyModelPlural!]
}
#...
# build.yaml
#...
      ferry_generator|req_builder:
        enabled: true
        options:
          schema: flutter_app|lib/graphql/schema/schema.g.graphql
          type_overrides:
            MyModelsPlural:
              name: MyModel
              import: 'package:flutter_app/models/my_model.dart'
      gql_build|serializer_builder:
        enabled: true
        options:
          schema: flutter_app|lib/graphql/schema/schema.g.graphql
          custom_serializers:
            - import: 'package:flutter_app/graphql/serializers/my_model_serializer.dart'
              name: MyModelSerializer
#...
// my_model_serializer.dart

// ignore: implementation_imports
import 'package:gql_code_builder/src/serializers/json_serializer.dart';

// This import exposes "MyModel" which will be required, but missing, from serializers.gql.dart
import 'package:flutter_app/models/my_model.dart';

class MyModelSerializer extends JsonSerializer<MyModel> {
  @override
  MyModel fromJson(Map<String, dynamic> data) {
    return MyModel.fromJson(data);
  }
  @override
  Map<String, dynamic> toJson(MyModel model) {
    return model.toJson();
  }
}
// serializers.gql.dart

//...
// Correctly exposes "MyModelSerializer" but NOT "MyModel", causing built file to have missing type
import 'package:flutter_app/graphql/serializers/my_model_serializer.dart'
    show MyModelSerializer;

part 'serializers.gql.g.dart';

final SerializersBuilder _serializersBuilder = _$serializers.toBuilder()
  ..add(OtherSerializer())
  ..add(MyModelSerializer())
//...
// serializers.gql.g.dart

//...
      ..addBuilderFactory(
          const FullType(BuiltList, const [const FullType(MyModel)]),
          () => new ListBuilder<MyModel>()))
      ..addBuilderFactory(
          const FullType(BuiltList, const [const FullType(MyModel)]),
          () => new ListBuilder<MyModel>()))
     ..addBuilderFactory(
          const FullType(BuiltList, const [const FullType(MyModel)]),
          () => new ListBuilder<MyModel>()))
//...

Error when parsing SDL schema

Hey @klavs! I'm getting an error while using the language SDL parser (parseString) on some of my examples. It say it can't find the Entity interface while parsing the Area object even though it's declared on the same schema:

Error on line 32, column 28 of lib/graphbrainz.schema.graphql: Unknown definition type 'Entity'
   ╷
32 │ type Area implements Node, Entity {
   │                            ^^^^^^
   ╵
package:gql/src/language/parser.dart 175:5   _Parser._parseDefinition
package:gql/src/language/parser.dart 117:22  _Parser._parseMany
package:gql/src/language/parser.dart 142:22  _Parser._parseDocument
package:gql/src/language/parser.dart 41:27   _Parser.parse
package:gql/src/language/parser.dart 17:17   parse
package:gql/src/language/parser.dart 27:5    parseString

The full schema, for referece: graphbrainz.schema.graphql.

[gql_build] ability to define scalar map in build.yaml

Let's say I have custom scalars in my schema, e.g. Email which actually is String. It's okay that builder generates wrapper class for it. But there are cases when i need that mapping, e.g. with Uint or Time . Builder produces a wrapper class which stores value in String variable, but it's not okay because when I send such variable within query to server, exception occurs. So, the good option to have would be ability to define scalar map in build.yaml like in artemis

Generate types from schema.

Can the schema_builder generate code for all types defined in the schema to be used as QueryRequest instead of making a ${{queryName}} type. Right now a every query has its own return type event if they return the same type.

[gql_link] Rename ServerException to NetworkException

The description for ServerException is:

/// Exception occurring when network fails
/// or parsed response is missing both `data` and `errors`

However, since such an exception could occur on the client and never even reach the server (e.g. if the client is offline), I suggest we rename it to NetworkException.

I recognize this would be a breaking change for a purely semantic reason, but maybe we can include it in the next major version bump.

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.