├── .devcontainer └── devcontainer.json ├── .github └── workflows │ └── dart.yml ├── .gitignore ├── AUTHORS.md ├── CHANGELOG.md ├── CONTRIBUTING.md ├── LICENSE ├── README.md ├── graphql.iml ├── img ├── angel3_logo.png ├── angel3_logo.svg ├── angel_graphql.png ├── angel_logo.png └── angel_logo.xcf ├── melos.yaml ├── melos_angel3.iml ├── packages ├── angel_graphql │ ├── AUTHORS.md │ ├── CHANGELOG.md │ ├── LICENSE │ ├── README.md │ ├── analysis_options.yaml │ ├── angel_graphql.iml │ ├── example │ │ ├── main.dart │ │ └── subscription.dart │ ├── lib │ │ ├── angel3_graphql.dart │ │ └── src │ │ │ ├── graphiql.dart │ │ │ ├── graphql_http.dart │ │ │ ├── graphql_ws.dart │ │ │ └── resolvers.dart │ ├── melos_angel3_graphql.iml │ ├── posts.json │ ├── pubspec.lock │ └── pubspec.yaml ├── graphql_data_loader │ ├── AUTHORS.md │ ├── CHANGELOG.md │ ├── LICENSE │ ├── README.md │ ├── analysis_options.yaml │ ├── example │ │ └── main.dart │ ├── lib │ │ └── graphql_data_loader2.dart │ ├── melos_graphql_data_loader2.iml │ ├── pubspec.lock │ ├── pubspec.yaml │ └── test │ │ └── all_test.dart ├── graphql_generator │ ├── AUTHORS.md │ ├── CHANGELOG.md │ ├── LICENSE │ ├── README.md │ ├── analysis_options.yaml │ ├── build.yaml │ ├── example │ │ ├── main.dart │ │ └── main.g.dart │ ├── lib │ │ └── graphql_generator2.dart │ ├── melos_graphql_generator2.iml │ ├── pubspec.lock │ └── pubspec.yaml ├── graphql_parser │ ├── AUTHORS.md │ ├── CHANGELOG.md │ ├── LICENSE │ ├── README.md │ ├── analysis_options.yaml │ ├── example │ │ └── example.dart │ ├── graphql_parser.iml │ ├── lib │ │ ├── graphql_parser2.dart │ │ └── src │ │ │ └── language │ │ │ ├── ast │ │ │ ├── alias.dart │ │ │ ├── argument.dart │ │ │ ├── array_value.dart │ │ │ ├── ast.dart │ │ │ ├── boolean_value.dart │ │ │ ├── default_value.dart │ │ │ ├── definition.dart │ │ │ ├── deprecated_value.dart │ │ │ ├── directive.dart │ │ │ ├── document.dart │ │ │ ├── field.dart │ │ │ ├── field_name.dart │ │ │ ├── fragment_definition.dart │ │ │ ├── fragment_spread.dart │ │ │ ├── inline_fragment.dart │ │ │ ├── input_value.dart │ │ │ ├── list_type.dart │ │ │ ├── misc_value.dart │ │ │ ├── node.dart │ │ │ ├── number_value.dart │ │ │ ├── operation_definition.dart │ │ │ ├── selection.dart │ │ │ ├── selection_set.dart │ │ │ ├── string_value.dart │ │ │ ├── type.dart │ │ │ ├── type_condition.dart │ │ │ ├── type_name.dart │ │ │ ├── variable.dart │ │ │ ├── variable_definition.dart │ │ │ └── variable_definitions.dart │ │ │ ├── language.dart │ │ │ ├── lexer.dart │ │ │ ├── parser.dart │ │ │ ├── syntax_error.dart │ │ │ ├── token.dart │ │ │ └── token_type.dart │ ├── melos_graphql_parser2.iml │ ├── pubspec.lock │ ├── pubspec.yaml │ └── test │ │ ├── argument_test.dart │ │ ├── comment_test.dart │ │ ├── common.dart │ │ ├── directive_test.dart │ │ ├── document_test.dart │ │ ├── field_test.dart │ │ ├── fragment_spread_test.dart │ │ ├── inline_fragment_test.dart │ │ ├── issue23_test.dart │ │ ├── next_name_test.dart │ │ ├── selection_set_test.dart │ │ ├── type_test.dart │ │ ├── value_test.dart │ │ ├── variable_definition_test.dart │ │ └── variable_test.dart ├── graphql_schema │ ├── AUTHORS.md │ ├── CHANGELOG.md │ ├── LICENSE │ ├── README.md │ ├── analysis_options.yaml │ ├── example │ │ └── example.dart │ ├── graphql_schema.iml │ ├── lib │ │ ├── graphql_schema2.dart │ │ └── src │ │ │ ├── argument.dart │ │ │ ├── enum.dart │ │ │ ├── field.dart │ │ │ ├── gen.dart │ │ │ ├── object_type.dart │ │ │ ├── scalar.dart │ │ │ ├── schema.dart │ │ │ ├── type.dart │ │ │ ├── union.dart │ │ │ └── validation_result.dart │ ├── melos_graphql_schema2.iml │ ├── pubspec.lock │ ├── pubspec.yaml │ └── test │ │ ├── common.dart │ │ ├── equality_test.dart │ │ ├── inheritance_test.dart │ │ ├── serialize_test.dart │ │ └── validation_test.dart └── graphql_server │ ├── AUTHORS.md │ ├── CHANGELOG.md │ ├── LICENSE │ ├── README.md │ ├── analysis_options.yaml │ ├── example │ └── main.dart │ ├── graphql_server.iml │ ├── lib │ ├── graphql_server2.dart │ ├── introspection.dart │ ├── mirrors.dart │ ├── src │ │ └── apollo │ │ │ ├── remote_client.dart │ │ │ ├── server.dart │ │ │ └── transport.dart │ └── subscriptions_transport_ws.dart │ ├── melos_graphql_server2.iml │ ├── pubspec.lock │ ├── pubspec.yaml │ └── test │ ├── common.dart │ ├── mirrors_test.dart │ ├── query_test.dart │ └── subscription_test.dart ├── pubspec.lock └── pubspec.yaml /.devcontainer/devcontainer.json: -------------------------------------------------------------------------------- 1 | { 2 | "image": "dart:3.4", 3 | "forwardPorts": [3000,5000], 4 | "features": { 5 | } 6 | } -------------------------------------------------------------------------------- /.github/workflows/dart.yml: -------------------------------------------------------------------------------- 1 | # This workflow uses actions that are not certified by GitHub. 2 | # They are provided by a third-party and are governed by 3 | # separate terms of service, privacy policy, and support 4 | # documentation. 5 | 6 | name: Graphql Dart Server 7 | 8 | on: 9 | push: 10 | branches: [ master ] 11 | pull_request: 12 | branches: [ master ] 13 | 14 | jobs: 15 | build: 16 | runs-on: ubuntu-latest 17 | 18 | steps: 19 | - uses: actions/checkout@v2 20 | 21 | # Note: This workflow uses the latest stable version of the Dart SDK. 22 | # You can specify other versions if desired, see documentation here: 23 | # https://github.com/dart-lang/setup-dart/blob/main/README.md 24 | - uses: dart-lang/setup-dart@v1 25 | with: 26 | sdk: "3.6.1" 27 | 28 | # Graphql Schema 29 | - id: graphql_schema2_upgrade 30 | name: graphql_schema2; Upgrade depedencies 31 | working-directory: packages/graphql_schema 32 | run: dart pub upgrade 33 | 34 | - name: graphql_schema2; Verify formatting 35 | working-directory: packages/graphql_schema 36 | run: dart format --output=none --set-exit-if-changed . 37 | 38 | # Consider passing '--fatal-infos' for slightly stricter analysis. 39 | - name: graphql_schema2; Analyze project source 40 | working-directory: packages/graphql_schema 41 | run: dart analyze 42 | 43 | - name: graphql_schema2; Run tests 44 | working-directory: packages/graphql_schema 45 | run: dart test 46 | 47 | # Graphql Parser 48 | - id: graphql_parser2_upgrade 49 | name: graphql_parser2; Upgrade depedencies 50 | working-directory: packages/graphql_parser 51 | run: dart pub upgrade 52 | 53 | - name: graphql_parser2; Verify formatting 54 | working-directory: packages/graphql_parser 55 | run: dart format --output=none --set-exit-if-changed . 56 | 57 | # Consider passing '--fatal-infos' for slightly stricter analysis. 58 | - name: graphql_parser2; Analyze project source 59 | working-directory: packages/graphql_parser 60 | run: dart analyze 61 | 62 | - name: graphql_parser2; Run tests 63 | working-directory: packages/graphql_parser 64 | run: dart test 65 | 66 | # Graphql Server 67 | - id: graphql_server2_upgrade 68 | name: graphql_server2; Upgrade depedencies 69 | working-directory: packages/graphql_server 70 | run: dart pub upgrade 71 | 72 | - name: graphql_server2; Verify formatting 73 | working-directory: packages/graphql_server 74 | run: dart format --output=none --set-exit-if-changed . 75 | 76 | # Consider passing '--fatal-infos' for slightly stricter analysis. 77 | - name: graphql_server2; Analyze project source 78 | working-directory: packages/graphql_server 79 | run: dart analyze 80 | 81 | - name: graphql_server2; Run tests 82 | working-directory: packages/graphql_server 83 | run: dart test 84 | 85 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Created by .ignore support plugin (hsz.mobi) 2 | ### JetBrains template 3 | # Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm, CLion, Android Studio and WebStorm 4 | # Reference: https://intellij-support.jetbrains.com/hc/en-us/articles/206544839 5 | 6 | # User-specific stuff 7 | 8 | # Gradle 9 | 10 | # CMake 11 | cmake-build-debug/ 12 | cmake-build-release/ 13 | 14 | # Mongo Explorer plugin 15 | # File-based project format 16 | *.iws 17 | 18 | # IntelliJ 19 | .idea 20 | out/ 21 | 22 | # mpeltonen/sbt-idea plugin 23 | .idea_modules/ 24 | 25 | # JIRA plugin 26 | atlassian-ide-plugin.xml 27 | 28 | # Cursive Clojure plugin 29 | .idea/replstate.xml 30 | 31 | # Crashlytics plugin (for Android Studio and IntelliJ) 32 | com_crashlytics_export_strings.xml 33 | crashlytics.properties 34 | crashlytics-build.properties 35 | fabric.properties 36 | 37 | # Editor-based Rest Client 38 | .idea/httpRequests 39 | 40 | .dart_tool 41 | .metals 42 | .packages 43 | 44 | .vscode 45 | !.vscode/settings.json 46 | .DS_Store 47 | -------------------------------------------------------------------------------- /AUTHORS.md: -------------------------------------------------------------------------------- 1 | Primary Authors 2 | =============== 3 | 4 | * __[Thomas Hii](dukefirehawk.apps@gmail.com)__ 5 | 6 | Thomas is the current maintainer of the code base. He has refactored and migrated the 7 | code base to support NNBD. 8 | 9 | * __[Tobe O](thosakwe@gmail.com)__ 10 | 11 | Tobe has written much of the original code prior to NNBD migration. He has moved on and 12 | is no longer involved with the project. 13 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Change Log 2 | 3 | ## 6.0.1 4 | 5 | * Updated repository link 6 | 7 | ## 6.0.0 8 | 9 | * Require dart >= 3.0.x 10 | * Updated to `angel3_framework` 8.x.x 11 | 12 | ## 5.0.0 13 | 14 | * Require dart >= 2.17.x 15 | * Updated to `angel3_framework` 7.x.x 16 | 17 | ## 4.0.0 18 | 19 | * Require dart >= 2.16.x 20 | * Updated to `angel3_framework` 6.x.x 21 | 22 | ## 3.0.0 23 | 24 | * Require dart >= 2.15.x 25 | 26 | ## 2.0.0 27 | 28 | * Migrated to support Dart SDK 2.12.x NNBD 29 | * Works with Angel3 Framework 30 | * Changed package `graphql_parser` to `graphql_parser2` 31 | * Changed package `graphql_schema` to `graphql_schema2` 32 | * Changed package `graphql_server` to `graphql_server2` 33 | * Changed package `graphql_generator` to `graphql_generator2` 34 | * Changed package `data_loader` to `graphql_data_loader2` 35 | * Changed package `angel_graphql` to `angel3_graphql` 36 | 37 | ## 1.0.0 38 | 39 | * Works with Angel Framework 40 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Contributing Graphql Dart 2 | 3 | Any contributions from the community are welcome. 4 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | BSD 3-Clause License 2 | 3 | Copyright (c) 2021, dukefirehawk.com 4 | All rights reserved. 5 | 6 | Redistribution and use in source and binary forms, with or without 7 | modification, are permitted provided that the following conditions are met: 8 | 9 | 1. Redistributions of source code must retain the above copyright notice, this 10 | list of conditions and the following disclaimer. 11 | 12 | 2. Redistributions in binary form must reproduce the above copyright notice, 13 | this list of conditions and the following disclaimer in the documentation 14 | and/or other materials provided with the distribution. 15 | 16 | 3. Neither the name of the copyright holder nor the names of its 17 | contributors may be used to endorse or promote products derived from 18 | this software without specific prior written permission. 19 | 20 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 21 | AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 22 | IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 23 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE 24 | FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 25 | DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 26 | SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 27 | CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, 28 | OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 29 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 30 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # GraphQL for Dart 2 | 3 | [![Logo](./img/angel3_logo.png)](https://github.com/dart-backend/graphql_dart) 4 | 5 | ![Pub Version (including pre-releases)](https://img.shields.io/pub/v/angel3_graphql?include_prereleases) 6 | [![Null Safety](https://img.shields.io/badge/null-safety-brightgreen)](https://dart.dev/null-safety) 7 | [![Gitter](https://img.shields.io/gitter/room/nwjs/nw.js.svg)](https://gitter.im/angel_dart/discussion) 8 | [![License](https://img.shields.io/github/license/dart-backend/graphql_dart)](https://github.com/dart-backend/graphql_dart/LICENSE) 9 | [![melos](https://img.shields.io/badge/maintained%20with-melos-f700ff.svg?style=flat-square)](https://github.com/invertase/melos) 10 | 11 | A complete implementation of the official [GraphQL specification](https://graphql.github.io/graphql-spec/June2018/), in the Dart programming language. 12 | 13 | The goal of this project is to provide to an alternative to REST API's for server-side development in Dart. 14 | 15 | Included is `angel3_graphql`, a plugin which integrates with [Angel3](https://github.com/dart-backend/angel) framework to allow developers to build backend services with GraphQL and virtually any supported database in Dart. 16 | 17 | ## Projects 18 | 19 | This mono repo is split into several sub-projects, each with its own detailed documentation and examples: 20 | 21 | * `graphql_parser2`: A recursive descent parser for the GraphQL language. 22 | * `graphql_schema2`: An implementation of GraphQL's type system. 23 | * `graphql_generator2`: Generates `graphql_schema2` object types from concrete Dart classes. 24 | * `graphql_data_loader2` - A Dart port of [`graphql/dataloader`](https://github.com/graphql/dataloader). 25 | * `graphql_server2`: Base functionality for implementing GraphQL servers in Dart. Has no dependency on any framework except `graphql_parser2` and `graphql_schema2` packages. 26 | * `angel3_graphql` - An implementation of `graphql_server2` in handling GraphQL via HTTP and WebSockets for [Angel3](https://github.com/dart-backend/angel) framework. 27 | 28 | ### Development Setup 29 | 30 | 1. Fork `graphql_dart` repository 31 | 32 | 2. Clone the project to local and create a new branch 33 | 34 | ```bash 35 | git clone https://github.com//graphql_dart.git 36 | git checkout -b feature/ 37 | ``` 38 | 39 | 3. Download and install [Dart 3](https://dart.dev/get-dart) 40 | 41 | 4. Install `melos` 42 | 43 | ```bash 44 | dart pub global activate melos 45 | ``` 46 | 47 | 5. Run `melos exec "dart pub upgrade"` to update all the packages 48 | 49 | 6. Make changes to the packages 50 | 51 | ## Donation & Support 52 | 53 | If you like this project and interested in supporting its development, you can make a donation via [paypal](https://paypal.me/dukefirehawk?country.x=MY&locale.x=en_US) service. 54 | -------------------------------------------------------------------------------- /graphql.iml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /img/angel3_logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dart-backend/graphql_dart/ae417b07d7267c0c64fe1a0160896edbd6f75a90/img/angel3_logo.png -------------------------------------------------------------------------------- /img/angel_graphql.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dart-backend/graphql_dart/ae417b07d7267c0c64fe1a0160896edbd6f75a90/img/angel_graphql.png -------------------------------------------------------------------------------- /img/angel_logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dart-backend/graphql_dart/ae417b07d7267c0c64fe1a0160896edbd6f75a90/img/angel_logo.png -------------------------------------------------------------------------------- /img/angel_logo.xcf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dart-backend/graphql_dart/ae417b07d7267c0c64fe1a0160896edbd6f75a90/img/angel_logo.xcf -------------------------------------------------------------------------------- /melos.yaml: -------------------------------------------------------------------------------- 1 | name: graphql_dart 2 | 3 | packages: 4 | - packages/** 5 | 6 | #scripts: 7 | # analyze: melos exec -- "dart analyze ." -------------------------------------------------------------------------------- /melos_angel3.iml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /packages/angel_graphql/AUTHORS.md: -------------------------------------------------------------------------------- 1 | Primary Authors 2 | =============== 3 | 4 | * __[Thomas Hii](dukefirehawk.apps@gmail.com)__ 5 | 6 | Thomas is the current maintainer of the code base. He has refactored and migrated the 7 | code base to support NNBD. 8 | 9 | * __[Tobe O](thosakwe@gmail.com)__ 10 | 11 | Tobe has written much of the original code prior to NNBD migration. He has moved on and 12 | is no longer involved with the project. 13 | -------------------------------------------------------------------------------- /packages/angel_graphql/CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Change log 2 | 3 | ## 8.3.0 4 | 5 | * Require Dart >= 3.6 6 | * Updated `lints` to 5.0.0 7 | 8 | ## 8.2.0 9 | 10 | * Require Dart >= 3.3 11 | * Updated `lints` to 4.0.0 12 | 13 | ## 8.1.0 14 | 15 | * Updated `lints` to 3.0.0 16 | * Fixed linter warnings 17 | * Updated repository link 18 | 19 | ## 8.0.1 20 | 21 | * Fixed reflector error in the examples 22 | 23 | ## 8.0.0 24 | 25 | * Require dart >= 3.0.x 26 | * Updated `angel3_framework` to 8.x.x 27 | 28 | ## 7.0.0 29 | 30 | * Require dart >= 2.17.x 31 | * Updated `angel3_framework` to 7.x.x 32 | 33 | ## 6.0.1 34 | 35 | * Fixed null warning for logger 36 | 37 | ## 6.0.0 38 | 39 | * Require dart >= 2.16.x 40 | * Updated `angel3_framework` to 6.x.x 41 | 42 | ## 5.0.0 43 | 44 | * Skipped release 45 | 46 | ## 4.0.0 47 | 48 | * Skipped release 49 | 50 | ## 3.0.0 51 | 52 | * Skipped release 53 | 54 | ## 2.1.0 55 | 56 | * Upgraded from `pendantic` to `lints` linter 57 | 58 | ## 2.0.2 59 | 60 | * Fixed NNBD issues 61 | 62 | ## 2.0.1 63 | 64 | * Updated README 65 | 66 | ## 2.0.0 67 | 68 | * Migrated to support Dart SDK 2.12.x NNBD 69 | * Rename `angel_graphql` to `angel3_graphql` 70 | 71 | ## 1.1.0 72 | 73 | * Support the GraphQL multipart spec: 74 | 75 | ## 1.0.0 76 | 77 | * Apply `package:pedantic`. 78 | 79 | ## 1.0.0-rc.0 80 | 81 | * Finish `graphQLWS`. 82 | 83 | ## 1.0.0-beta.1 84 | 85 | * Add `graphQLWS` handler, and support subscriptions. 86 | 87 | ## 1.0.0-beta 88 | 89 | * Angel RC updates. 90 | 91 | ## 1.0.0-alpha 92 | 93 | * First official release. 94 | -------------------------------------------------------------------------------- /packages/angel_graphql/LICENSE: -------------------------------------------------------------------------------- 1 | BSD 3-Clause License 2 | 3 | Copyright (c) 2021, dukefirehawk.com 4 | All rights reserved. 5 | 6 | Redistribution and use in source and binary forms, with or without 7 | modification, are permitted provided that the following conditions are met: 8 | 9 | 1. Redistributions of source code must retain the above copyright notice, this 10 | list of conditions and the following disclaimer. 11 | 12 | 2. Redistributions in binary form must reproduce the above copyright notice, 13 | this list of conditions and the following disclaimer in the documentation 14 | and/or other materials provided with the distribution. 15 | 16 | 3. Neither the name of the copyright holder nor the names of its 17 | contributors may be used to endorse or promote products derived from 18 | this software without specific prior written permission. 19 | 20 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 21 | AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 22 | IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 23 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE 24 | FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 25 | DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 26 | SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 27 | CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, 28 | OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 29 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 30 | -------------------------------------------------------------------------------- /packages/angel_graphql/analysis_options.yaml: -------------------------------------------------------------------------------- 1 | include: package:lints/recommended.yaml 2 | 3 | linter: 4 | rules: 5 | constant_identifier_names: false 6 | non_constant_identifier_names: false -------------------------------------------------------------------------------- /packages/angel_graphql/angel_graphql.iml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | -------------------------------------------------------------------------------- /packages/angel_graphql/example/main.dart: -------------------------------------------------------------------------------- 1 | // ignore_for_file: deprecated_member_use 2 | import 'package:angel3_container/mirrors.dart'; 3 | import 'package:angel3_framework/angel3_framework.dart'; 4 | import 'package:angel3_framework/http.dart'; 5 | import 'package:angel3_graphql/angel3_graphql.dart'; 6 | import 'package:angel3_serialize/angel3_serialize.dart'; 7 | import 'package:graphql_schema2/graphql_schema2.dart'; 8 | import 'package:graphql_server2/graphql_server2.dart'; 9 | import 'package:graphql_server2/mirrors.dart'; 10 | import 'package:logging/logging.dart'; 11 | 12 | void main() async { 13 | var logger = Logger('angel3_graphql'); 14 | var app = Angel( 15 | reflector: MirrorsReflector(), 16 | logger: logger 17 | ..onRecord.listen((rec) { 18 | print(rec); 19 | if (rec.error != null) print(rec.error); 20 | if (rec.stackTrace != null) print(rec.stackTrace); 21 | })); 22 | var http = AngelHttp(app); 23 | 24 | var todoService = app.use('api/todos', MapService()); 25 | 26 | var queryType = objectType( 27 | 'Query', 28 | description: 'A simple API that manages your to-do list.', 29 | fields: [ 30 | field( 31 | 'todos', 32 | listOf(convertDartType(Todo)!.nonNullable()), 33 | resolve: resolveViaServiceIndex(todoService), 34 | ), 35 | field( 36 | 'todo', 37 | convertDartType(Todo)!, 38 | resolve: resolveViaServiceRead(todoService), 39 | inputs: [ 40 | GraphQLFieldInput('id', graphQLId.nonNullable()), 41 | ], 42 | ), 43 | ], 44 | ); 45 | 46 | var mutationType = objectType( 47 | 'Mutation', 48 | description: 'Modify the to-do list.', 49 | fields: [ 50 | field( 51 | 'createTodo', 52 | convertDartType(Todo)!, 53 | inputs: [ 54 | GraphQLFieldInput( 55 | 'data', convertDartType(Todo)!.coerceToInputObject()), 56 | ], 57 | resolve: resolveViaServiceCreate(todoService), 58 | ), 59 | ], 60 | ); 61 | 62 | var schema = graphQLSchema( 63 | queryType: queryType, 64 | mutationType: mutationType, 65 | ); 66 | 67 | app.all('/graphql', graphQLHttp(GraphQL(schema))); 68 | app.get('/graphiql', graphiQL()); 69 | 70 | await todoService 71 | .create({'text': 'Clean your room!', 'completion_status': 'COMPLETE'}); 72 | await todoService.create( 73 | {'text': 'Take out the trash', 'completion_status': 'INCOMPLETE'}); 74 | await todoService.create({ 75 | 'text': 'Become a billionaire at the age of 5', 76 | 'completion_status': 'INCOMPLETE' 77 | }); 78 | 79 | var server = await http.startServer('127.0.0.1', 3000); 80 | var uri = 81 | Uri(scheme: 'http', host: server.address.address, port: server.port); 82 | var graphiqlUri = uri.replace(path: 'graphiql'); 83 | print('Listening at $uri'); 84 | print('Access graphiql at $graphiqlUri'); 85 | } 86 | 87 | @GraphQLDocumentation(description: 'Any object with a .text (String) property.') 88 | abstract class HasText { 89 | String? get text; 90 | } 91 | 92 | @serializable 93 | @GraphQLDocumentation( 94 | description: 'A task that might not be completed yet. **Yay! Markdown!**') 95 | class Todo extends Model implements HasText { 96 | @override 97 | String? text; 98 | 99 | @GraphQLDocumentation(deprecationReason: 'Use `completion_status` instead.') 100 | bool? completed; 101 | 102 | CompletionStatus? completionStatus; 103 | 104 | Todo({this.text, this.completed, this.completionStatus}); 105 | } 106 | 107 | @GraphQLDocumentation(description: 'The completion status of a to-do item.') 108 | enum CompletionStatus { COMPLETE, INCOMPLETE } 109 | -------------------------------------------------------------------------------- /packages/angel_graphql/example/subscription.dart: -------------------------------------------------------------------------------- 1 | // Inspired by: 2 | // https://www.apollographql.com/docs/apollo-server/features/subscriptions/#subscriptions-example 3 | 4 | import 'package:angel3_container/mirrors.dart'; 5 | import 'package:angel3_file_service/angel3_file_service.dart'; 6 | import 'package:angel3_framework/angel3_framework.dart'; 7 | import 'package:angel3_framework/http.dart'; 8 | import 'package:angel3_graphql/angel3_graphql.dart'; 9 | import 'package:file/local.dart'; 10 | import 'package:graphql_schema2/graphql_schema2.dart'; 11 | import 'package:graphql_server2/graphql_server2.dart'; 12 | import 'package:logging/logging.dart'; 13 | 14 | void main() async { 15 | var logger = Logger('angel3_graphql'); 16 | var app = Angel(reflector: MirrorsReflector(), logger: logger); 17 | var http = AngelHttp(app); 18 | app.logger.onRecord.listen((rec) { 19 | print(rec); 20 | if (rec.error != null) print(rec.error); 21 | if (rec.stackTrace != null) print(rec.stackTrace); 22 | }); 23 | 24 | // Create an in-memory service. 25 | var fs = LocalFileSystem(); 26 | var postService = 27 | app.use('/api/posts', JsonFileService(fs.file('posts.json'))); 28 | 29 | // Also get a [Stream] of item creation events. 30 | var postAdded = postService.afterCreated 31 | .asStream() 32 | .map((e) => {'postAdded': e.result}) 33 | .asBroadcastStream(); 34 | 35 | // GraphQL setup. 36 | var postType = objectType('Post', fields: [ 37 | field('author', graphQLString), 38 | field('comment', graphQLString), 39 | ]); 40 | 41 | var schema = graphQLSchema( 42 | // Hooked up to the postService: 43 | // type Query { posts: [Post] } 44 | queryType: objectType( 45 | 'Query', 46 | fields: [ 47 | field( 48 | 'posts', 49 | listOf(postType), 50 | resolve: resolveViaServiceIndex(postService), 51 | ), 52 | ], 53 | ), 54 | 55 | // Hooked up to the postService: 56 | // type Mutation { 57 | // addPost(author: String!, comment: String!): Post 58 | // } 59 | mutationType: objectType( 60 | 'Mutation', 61 | fields: [ 62 | field( 63 | 'addPost', 64 | postType, 65 | inputs: [ 66 | GraphQLFieldInput( 67 | 'data', postType.toInputObject('PostInput').nonNullable()), 68 | ], 69 | resolve: resolveViaServiceCreate(postService), 70 | ), 71 | ], 72 | ), 73 | 74 | // Hooked up to `postAdded`: 75 | // type Subscription { postAdded: Post } 76 | subscriptionType: objectType( 77 | 'Subscription', 78 | fields: [ 79 | field('postAdded', postType, resolve: (_, __) => postAdded), 80 | ], 81 | ), 82 | ); 83 | 84 | // Mount GraphQL routes; we'll support HTTP and WebSockets transports. 85 | app.all('/graphql', graphQLHttp(GraphQL(schema))); 86 | app.get('/subscriptions', 87 | graphQLWS(GraphQL(schema), keepAliveInterval: Duration(seconds: 3))); 88 | app.get('/graphiql', 89 | graphiQL(subscriptionsEndpoint: 'ws://localhost:3000/subscriptions')); 90 | 91 | var server = await http.startServer('127.0.0.1', 3000); 92 | var uri = 93 | Uri(scheme: 'http', host: server.address.address, port: server.port); 94 | var graphiqlUri = uri.replace(path: 'graphiql'); 95 | var postsUri = uri.replace(pathSegments: ['api', 'posts']); 96 | print('Listening at $uri'); 97 | print('Access graphiql at $graphiqlUri'); 98 | print('Access posts service at $postsUri'); 99 | } 100 | -------------------------------------------------------------------------------- /packages/angel_graphql/lib/angel3_graphql.dart: -------------------------------------------------------------------------------- 1 | import 'package:angel3_framework/angel3_framework.dart'; 2 | import 'package:graphql_schema2/graphql_schema2.dart'; 3 | export 'src/graphiql.dart'; 4 | export 'src/graphql_http.dart'; 5 | export 'src/graphql_ws.dart'; 6 | export 'src/resolvers.dart'; 7 | 8 | /// The canonical [GraphQLUploadType] instance. 9 | final GraphQLUploadType graphQLUpload = GraphQLUploadType(); 10 | 11 | /// A [GraphQLScalarType] that is used to read uploaded files from 12 | /// `multipart/form-data` requests. 13 | class GraphQLUploadType extends GraphQLScalarType { 14 | @override 15 | String get name => 'Upload'; 16 | 17 | @override 18 | String get description => 19 | 'Represents a file that has been uploaded to the server.'; 20 | 21 | @override 22 | GraphQLType coerceToInputObject() => this; 23 | 24 | @override 25 | UploadedFile deserialize(UploadedFile serialized) => serialized; 26 | 27 | @override 28 | UploadedFile serialize(UploadedFile value) => value; 29 | 30 | @override 31 | ValidationResult validate(String key, UploadedFile input) { 32 | //if (input is! UploadedFile) { 33 | // return _Vr(false, errors: ['Expected "$key" to be a boolean.']); 34 | //} 35 | return _Vr(true, value: input, errors: []); 36 | } 37 | } 38 | 39 | // TODO: Really need to make the validation result constructors *public* 40 | class _Vr implements ValidationResult { 41 | @override 42 | final bool successful; 43 | @override 44 | final List errors; 45 | @override 46 | final T? value; 47 | 48 | _Vr(this.successful, {required this.errors, this.value}); 49 | } 50 | -------------------------------------------------------------------------------- /packages/angel_graphql/lib/src/graphiql.dart: -------------------------------------------------------------------------------- 1 | import 'package:angel3_framework/angel3_framework.dart'; 2 | import 'package:http_parser/http_parser.dart'; 3 | 4 | /// Returns a simple [RequestHandler] that renders the GraphiQL visual interface for GraphQL. 5 | /// 6 | /// By default, the interface expects your backend to be mounted at `/graphql`; this is configurable 7 | /// via [graphQLEndpoint]. 8 | RequestHandler graphiQL( 9 | {String graphQLEndpoint = '/graphql', String? subscriptionsEndpoint}) { 10 | return (req, res) { 11 | res 12 | ..contentType = MediaType('text', 'html') 13 | ..write(renderGraphiql( 14 | graphqlEndpoint: graphQLEndpoint, 15 | subscriptionsEndpoint: subscriptionsEndpoint)) 16 | ..close(); 17 | }; 18 | } 19 | 20 | String renderGraphiql( 21 | {String graphqlEndpoint = '/graphql', String? subscriptionsEndpoint}) { 22 | var subscriptionsScripts = '', 23 | subscriptionsFetcher = '', 24 | fetcherName = 'graphQLFetcher'; 25 | 26 | if (subscriptionsEndpoint != null) { 27 | fetcherName = 'subscriptionsFetcher'; 28 | subscriptionsScripts = ''' 29 | 30 | 31 | '''; 32 | subscriptionsFetcher = ''' 33 | let subscriptionsClient = window.SubscriptionsTransportWs.SubscriptionClient('$subscriptionsEndpoint', { 34 | reconnect: true 35 | }); 36 | let $fetcherName = window.GraphiQLSubscriptionsFetcher.graphQLFetcher(subscriptionsClient, graphQLFetcher); 37 | '''; 38 | } 39 | 40 | return ''' 41 | 42 | 43 | 44 | 45 | Angel GraphQL 46 | 47 | 56 | 57 | 58 |
59 | 60 | 61 | 62 | 63 | $subscriptionsScripts 64 | 85 | 86 | 87 | ''' 88 | .trim(); 89 | } 90 | -------------------------------------------------------------------------------- /packages/angel_graphql/lib/src/graphql_ws.dart: -------------------------------------------------------------------------------- 1 | import 'dart:async'; 2 | import 'dart:io'; 3 | import 'package:angel3_framework/angel3_framework.dart'; 4 | import 'package:angel3_framework/http.dart'; 5 | import 'package:graphql_schema2/graphql_schema2.dart'; 6 | import 'package:graphql_server2/graphql_server2.dart'; 7 | import 'package:graphql_server2/subscriptions_transport_ws.dart' as stw; 8 | import 'package:web_socket_channel/io.dart'; 9 | 10 | /// A [RequestHandler] that serves a spec-compliant GraphQL backend, over WebSockets. 11 | /// This endpoint only supports WebSockets, and can be used to deliver subscription events. 12 | /// 13 | /// `graphQLWS` uses the Apollo WebSocket protocol, for the sake of compatibility with 14 | /// existing tooling. 15 | /// 16 | /// See: 17 | /// * https://github.com/apollographql/subscriptions-transport-ws 18 | RequestHandler graphQLWS(GraphQL graphQL, {Duration? keepAliveInterval}) { 19 | return (req, res) async { 20 | if (req is HttpRequestContext) { 21 | if (WebSocketTransformer.isUpgradeRequest(req.rawRequest!)) { 22 | await res.detach(); 23 | var socket = await WebSocketTransformer.upgrade(req.rawRequest!, 24 | protocolSelector: (protocols) { 25 | if (protocols.contains('graphql-ws')) { 26 | return 'graphql-ws'; 27 | } else { 28 | throw AngelHttpException.badRequest( 29 | message: 'Only the "graphql-ws" protocol is allowed.'); 30 | } 31 | }); 32 | var channel = IOWebSocketChannel(socket); 33 | var client = stw.RemoteClient(channel.cast()); 34 | var server = 35 | _GraphQLWSServer(client, graphQL, req, res, keepAliveInterval); 36 | await server.done; 37 | } else { 38 | throw AngelHttpException.badRequest( 39 | message: 'The `graphQLWS` endpoint only accepts WebSockets.'); 40 | } 41 | } else { 42 | throw AngelHttpException.badRequest( 43 | message: 'The `graphQLWS` endpoint only accepts HTTP/1.1 requests.'); 44 | } 45 | }; 46 | } 47 | 48 | class _GraphQLWSServer extends stw.Server { 49 | final GraphQL graphQL; 50 | final RequestContext req; 51 | final ResponseContext res; 52 | 53 | _GraphQLWSServer(super.client, this.graphQL, this.req, this.res, 54 | Duration? keepAliveInterval) 55 | : super(keepAliveInterval: keepAliveInterval); 56 | 57 | @override 58 | bool onConnect(stw.RemoteClient client, [Map? connectionParams]) => true; 59 | 60 | @override 61 | Future onOperation(String? id, String query, 62 | [Map? variables, String? operationName]) async { 63 | try { 64 | var globalVariables = { 65 | '__requestctx': req, 66 | '__responsectx': res, 67 | }; 68 | var data = await graphQL.parseAndExecute( 69 | query, 70 | operationName: operationName, 71 | sourceUrl: 'input', 72 | globalVariables: globalVariables, 73 | variableValues: variables!, 74 | ); 75 | return stw.GraphQLResult(data); 76 | } on GraphQLException catch (e) { 77 | return stw.GraphQLResult(null, errors: e.errors); 78 | } 79 | } 80 | } 81 | -------------------------------------------------------------------------------- /packages/angel_graphql/melos_angel3_graphql.iml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | -------------------------------------------------------------------------------- /packages/angel_graphql/posts.json: -------------------------------------------------------------------------------- 1 | [] -------------------------------------------------------------------------------- /packages/angel_graphql/pubspec.yaml: -------------------------------------------------------------------------------- 1 | name: angel3_graphql 2 | version: 8.2.0 3 | description: The fastest and easiest way to get a GraphQL backend in Dart, using Angel3. 4 | homepage: https://angel3-framework.web.app/ 5 | repository: https://github.com/dart-backend/graphql_dart/tree/master/packages/angel_graphql 6 | environment: 7 | sdk: '>=3.6.0 <4.0.0' 8 | dependencies: 9 | angel3_file_service: ^8.0.0 10 | angel3_framework: ^8.4.0 11 | angel3_container: ^8.0.0 12 | angel3_websocket: ^8.1.0 13 | angel3_validate: ^8.0.0 14 | graphql_parser2: ^6.0.0 15 | graphql_schema2: ^6.0.0 16 | graphql_server2: ^6.0.0 17 | http_parser: ^4.0.0 18 | web_socket_channel: ^3.0.0 19 | collection: ^1.17.0 20 | dev_dependencies: 21 | angel3_serialize: ^8.0.0 22 | file: ^7.0.0 23 | logging: ^1.2.0 24 | lints: ^5.0.0 25 | # dependency_overrides: 26 | # graphql_server2: 27 | # path: ../graphql_server 28 | # graphql_parser2: 29 | # path: ../graphql_parser 30 | # graphql_schema2: 31 | # path: ../graphql_schema 32 | -------------------------------------------------------------------------------- /packages/graphql_data_loader/AUTHORS.md: -------------------------------------------------------------------------------- 1 | Primary Authors 2 | =============== 3 | 4 | * __[Thomas Hii](dukefirehawk.apps@gmail.com)__ 5 | 6 | Thomas is the current maintainer of the code base. He has refactored and migrated the 7 | code base to support NNBD. 8 | 9 | * __[Tobe O](thosakwe@gmail.com)__ 10 | 11 | Tobe has written much of the original code prior to NNBD migration. He has moved on and 12 | is no longer involved with the project. 13 | -------------------------------------------------------------------------------- /packages/graphql_data_loader/CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Change Log 2 | 3 | ## 6.3.0 4 | 5 | * Require Dart >= 3.6 6 | * Updated `lints` to 5.0.0 7 | 8 | ## 6.2.0 9 | 10 | * Require Dart >= 3.3 11 | * Updated `lints` to 4.0.0 12 | 13 | ## 6.1.0 14 | 15 | * Updated `lints` to 3.0.0 16 | * Fixed linter warnings 17 | * Updated repository link 18 | 19 | ## 6.0.0 20 | 21 | * Require dart >= 3.0.x 22 | 23 | ## 5.0.0 24 | 25 | * Require dart >= 2.17.x 26 | 27 | ## 4.0.0 28 | 29 | * Require dart >= 2.16.x 30 | 31 | ## 3.0.0 32 | 33 | * Updated to SDK 2.15.x 34 | 35 | ## 2.1.0 36 | 37 | * Upgraded from `pendantic` to `lints` linter 38 | 39 | ## 2.0.0 40 | 41 | * Migrated to support Dart SDK 2.12.x NNBD 42 | * Rename `data_loader` to `graphql_data_loader2` 43 | 44 | ## 1.0.0 45 | 46 | * Initial version. 47 | -------------------------------------------------------------------------------- /packages/graphql_data_loader/LICENSE: -------------------------------------------------------------------------------- 1 | BSD 3-Clause License 2 | 3 | Copyright (c) 2021, dukefirehawk.com 4 | All rights reserved. 5 | 6 | Redistribution and use in source and binary forms, with or without 7 | modification, are permitted provided that the following conditions are met: 8 | 9 | 1. Redistributions of source code must retain the above copyright notice, this 10 | list of conditions and the following disclaimer. 11 | 12 | 2. Redistributions in binary form must reproduce the above copyright notice, 13 | this list of conditions and the following disclaimer in the documentation 14 | and/or other materials provided with the distribution. 15 | 16 | 3. Neither the name of the copyright holder nor the names of its 17 | contributors may be used to endorse or promote products derived from 18 | this software without specific prior written permission. 19 | 20 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 21 | AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 22 | IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 23 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE 24 | FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 25 | DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 26 | SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 27 | CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, 28 | OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 29 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 30 | -------------------------------------------------------------------------------- /packages/graphql_data_loader/README.md: -------------------------------------------------------------------------------- 1 | # GraphQL Data Loader 2 2 | 3 | ![Pub Version (including pre-releases)](https://img.shields.io/pub/v/graphql_data_loader2?include_prereleases) 4 | [![Null Safety](https://img.shields.io/badge/null-safety-brightgreen)](https://dart.dev/null-safety) 5 | [![Gitter](https://img.shields.io/gitter/room/nwjs/nw.js.svg)](https://gitter.im/angel_dart/discussion) 6 | [![License](https://img.shields.io/github/license/dart-backend/graphql_dart)](https://github.com/dart-backend/graphql_dart/blob/master/packages/graphql_data_loader/LICENSE) 7 | [![melos](https://img.shields.io/badge/maintained%20with-melos-f700ff.svg?style=flat-square)](https://github.com/invertase/melos) 8 | 9 | Batch and cache database lookups. Works well with GraphQL. Ported from the original JS version: [`Graphql`](https://github.com/graphql/dataloader) 10 | 11 | ## Installation 12 | 13 | In your pubspec.yaml: 14 | 15 | ```yaml 16 | dependencies: 17 | graphql_data_loader2: ^6.0.0 18 | ``` 19 | 20 | ## Usage 21 | 22 | Complete example: [`Source code`](https://github.com/dart-backend/graphql_dart/tree/master/graphql_data_loader/example/main.dart) 23 | 24 | ```dart 25 | var userLoader = DataLoader((key) => myBatchGetUsers(keys)); 26 | var invitedBy = await userLoader.load(1)then(user => userLoader.load(user.invitedByID)) 27 | print('User 1 was invited by $invitedBy')); 28 | ``` 29 | -------------------------------------------------------------------------------- /packages/graphql_data_loader/analysis_options.yaml: -------------------------------------------------------------------------------- 1 | include: package:lints/recommended.yaml -------------------------------------------------------------------------------- /packages/graphql_data_loader/example/main.dart: -------------------------------------------------------------------------------- 1 | import 'dart:async'; 2 | import 'package:graphql_data_loader2/graphql_data_loader2.dart'; 3 | import 'package:graphql_schema2/graphql_schema2.dart'; 4 | 5 | external Future> fetchTodos(Iterable ids); 6 | 7 | void main() async { 8 | // Create a DataLoader. By default, it caches lookups. 9 | var todoLoader = DataLoader(fetchTodos); // DataLoader 10 | 11 | // type Todo { id: Int, text: String, is_complete: Boolean } 12 | var todoType = objectType( 13 | 'Todo', 14 | fields: [ 15 | field('id', graphQLInt), 16 | field('text', graphQLString), 17 | field('is_complete', graphQLBoolean), 18 | ], 19 | ); 20 | 21 | // type Query { todo($id: Int!) Todo } 22 | // ignore: unused_local_variable 23 | var schema = graphQLSchema( 24 | queryType: objectType( 25 | 'Query', 26 | fields: [ 27 | field( 28 | 'todo', 29 | listOf(todoType), 30 | inputs: [GraphQLFieldInput('id', graphQLInt.nonNullable())], 31 | resolve: (_, args) => todoLoader.load(args['id'] as int?), 32 | ), 33 | ], 34 | ), 35 | ); 36 | 37 | // Do something with your schema... 38 | } 39 | 40 | abstract class Todo { 41 | int get id; 42 | String get text; 43 | bool get isComplete; 44 | } 45 | -------------------------------------------------------------------------------- /packages/graphql_data_loader/lib/graphql_data_loader2.dart: -------------------------------------------------------------------------------- 1 | import 'dart:async'; 2 | import 'dart:collection'; 3 | 4 | /// A utility for batching multiple requests together, to improve application performance. 5 | /// 6 | /// Enqueues batches of requests until the next tick, when they are processed in bulk. 7 | /// 8 | /// Port of Facebook's `DataLoader`: 9 | /// https://github.com/graphql/dataloader 10 | class DataLoader { 11 | /// Invoked to fetch a batch of keys simultaneously. 12 | final FutureOr> Function(Iterable) loadMany; 13 | 14 | /// Whether to use a memoization cache to store the results of past lookups. 15 | final bool cache; 16 | 17 | final _cache = {}; 18 | final _queue = Queue<_QueueItem>(); 19 | bool _started = false; 20 | 21 | DataLoader(this.loadMany, {this.cache = true}); 22 | 23 | Future _onTick() async { 24 | if (_queue.isNotEmpty) { 25 | var current = _queue.toList(); 26 | _queue.clear(); 27 | 28 | var loadIds = current.map((i) => i.id).toSet().toList(growable: false); 29 | 30 | var data = await loadMany( 31 | loadIds, 32 | ); 33 | 34 | for (var i = 0; i < loadIds.length; i++) { 35 | var id = loadIds[i]; 36 | var value = data.elementAt(i); 37 | 38 | if (cache) _cache[id] = value; 39 | 40 | current 41 | .where((item) => item.id == id) 42 | .forEach((item) => item.completer.complete(value)); 43 | } 44 | } 45 | 46 | _started = false; 47 | // if (!_closed) scheduleMicrotask(_onTick); 48 | } 49 | 50 | /// Clears the value at [key], if it exists. 51 | void clear(Id key) => _cache.remove(key); 52 | 53 | /// Clears the entire cache. 54 | void clearAll() => _cache.clear(); 55 | 56 | /// Primes the cache with the provided key and value. If the key already exists, no change is made. 57 | /// 58 | /// To forcefully prime the cache, clear the key first with 59 | /// `loader..clear(key)..prime(key, value)`. 60 | void prime(Id key, Data value) => _cache.putIfAbsent(key, () => value); 61 | 62 | /// Closes this [DataLoader], cancelling all pending requests. 63 | void close() { 64 | while (_queue.isNotEmpty) { 65 | _queue.removeFirst().completer.completeError( 66 | StateError('The DataLoader was closed before the item was loaded.')); 67 | } 68 | 69 | _queue.clear(); 70 | } 71 | 72 | /// Returns a [Future] that completes when the next batch of requests completes. 73 | Future load(Id id) { 74 | if (cache && _cache.containsKey(id)) { 75 | return Future.value(_cache[id]); 76 | } else { 77 | var item = _QueueItem(id); 78 | _queue.add(item); 79 | if (!_started) { 80 | _started = true; 81 | scheduleMicrotask(_onTick); 82 | } 83 | return item.completer.future; 84 | } 85 | } 86 | } 87 | 88 | class _QueueItem { 89 | final Id id; 90 | final Completer completer = Completer(); 91 | 92 | _QueueItem(this.id); 93 | } 94 | -------------------------------------------------------------------------------- /packages/graphql_data_loader/melos_graphql_data_loader2.iml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | -------------------------------------------------------------------------------- /packages/graphql_data_loader/pubspec.yaml: -------------------------------------------------------------------------------- 1 | name: graphql_data_loader2 2 | version: 6.3.0 3 | description: Batch and cache database lookups. Works well with GraphQL. Ported from JS. 4 | homepage: https://angel3-framework.web.app/ 5 | repository: https://github.com/dart-backend/graphql_dart/tree/master/packages/graphql_data_loader 6 | environment: 7 | sdk: '>=3.6.0 <4.0.0' 8 | dev_dependencies: 9 | graphql_schema2: ^6.0.0 10 | lints: ^5.0.0 11 | test: ^1.24.0 12 | # dependency_overrides: 13 | # graphql_schema2: 14 | # path: ../graphql_schema 15 | -------------------------------------------------------------------------------- /packages/graphql_data_loader/test/all_test.dart: -------------------------------------------------------------------------------- 1 | import 'dart:async'; 2 | 3 | import 'package:graphql_data_loader2/graphql_data_loader2.dart'; 4 | import 'package:test/test.dart'; 5 | 6 | void main() { 7 | var numbers = List.generate(10, (i) => i.toStringAsFixed(2)); 8 | var numberLoader = DataLoader((ids) { 9 | print('ID batch: $ids'); 10 | return ids.map((i) => numbers[i]); 11 | }); 12 | 13 | test('batch', () async { 14 | var zero = numberLoader.load(0); 15 | var one = numberLoader.load(1); 16 | var two = numberLoader.load(2); 17 | var batch = await Future.wait([zero, one, two]); 18 | print('Fetched result: $batch'); 19 | expect(batch, ['0.00', '1.00', '2.00']); 20 | }); 21 | 22 | test('dedupe', () async { 23 | var loader = DataLoader>>((ids) { 24 | return ids.map( 25 | (i) => {i: ids.toList()}, 26 | ); 27 | }); 28 | 29 | var zero = loader.load(0); 30 | var one = loader.load(1); 31 | var two = loader.load(2); 32 | var anotherZero = loader.load(0); 33 | var batch = await Future.wait([zero, one, two, anotherZero]); 34 | 35 | expect( 36 | batch, 37 | [ 38 | { 39 | 0: [0, 1, 2] 40 | }, 41 | { 42 | 1: [0, 1, 2] 43 | }, 44 | { 45 | 2: [0, 1, 2] 46 | }, 47 | { 48 | 0: [0, 1, 2] 49 | }, 50 | ], 51 | ); 52 | }); 53 | 54 | group('cache', () { 55 | late DataLoader uniqueLoader; 56 | late DataLoader noCache; 57 | 58 | setUp(() { 59 | uniqueLoader = DataLoader((ids) async { 60 | var numbers = await numberLoader.loadMany(ids); 61 | return numbers.map((s) => _Unique(s)); 62 | }); 63 | noCache = DataLoader(uniqueLoader.loadMany, cache: false); 64 | }); 65 | 66 | tearDown(() { 67 | uniqueLoader.close(); 68 | noCache.close(); 69 | }); 70 | 71 | test('only lookup once', () async { 72 | var a = await uniqueLoader.load(3); 73 | var b = await uniqueLoader.load(3); 74 | expect(a, b); 75 | }); 76 | 77 | test('can be disabled', () async { 78 | var a = await noCache.load(3); 79 | var b = await noCache.load(3); 80 | expect(a, isNot(b)); 81 | }); 82 | 83 | test('clear', () async { 84 | var a = await uniqueLoader.load(3); 85 | uniqueLoader.clear(3); 86 | var b = await uniqueLoader.load(3); 87 | expect(a, isNot(b)); 88 | }); 89 | 90 | test('clearAll', () async { 91 | var a = await uniqueLoader.load(3); 92 | uniqueLoader.clearAll(); 93 | var b = await uniqueLoader.load(3); 94 | expect(a, isNot(b)); 95 | }); 96 | 97 | test('prime', () async { 98 | uniqueLoader.prime(3, _Unique('hey')); 99 | var a = await uniqueLoader.load(3); 100 | expect(a.value, 'hey'); 101 | }); 102 | }); 103 | } 104 | 105 | class _Unique { 106 | final String value; 107 | 108 | _Unique(this.value); 109 | } 110 | -------------------------------------------------------------------------------- /packages/graphql_generator/AUTHORS.md: -------------------------------------------------------------------------------- 1 | Primary Authors 2 | =============== 3 | 4 | * __[Thomas Hii](dukefirehawk.apps@gmail.com)__ 5 | 6 | Thomas is the current maintainer of the code base. He has refactored and migrated the 7 | code base to support NNBD. 8 | 9 | * __[Tobe O](thosakwe@gmail.com)__ 10 | 11 | Tobe has written much of the original code prior to NNBD migration. He has moved on and 12 | is no longer involved with the project. 13 | -------------------------------------------------------------------------------- /packages/graphql_generator/CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Change Log 2 | 3 | ## 6.3.0 4 | 5 | * Require Dart >= 3.6 6 | * Updated `lints` to 5.0.0 7 | * Upgraded to `analyzer` 7.0.0 8 | 9 | ## 6.2.1 10 | 11 | * Upgraded to `analyzer` 6.5.x 12 | 13 | ## 6.2.0 14 | 15 | * Require Dart >= 3.3 16 | * Updated `lints` to 4.0.0 17 | 18 | ## 6.1.0 19 | 20 | * Upgraded to `analyzer` 6.3.x 21 | * Updated `lints` to 3.0.0 22 | * Fixed linter warnings 23 | * Updated repository link 24 | 25 | ## 6.0.0 26 | 27 | * Require dart >= 3.0.x 28 | 29 | ## 5.1.0 30 | 31 | * Require dart >= 2.18.x 32 | * Upgraded to `analyzer` 5.x.x 33 | * Replaced deprecated `element2` with `element` 34 | 35 | ## 5.0.0 36 | 37 | * Require dart >= 2.17.x 38 | 39 | ## 4.1.0 40 | 41 | * Updated to `analyzer` 4.x.x 42 | 43 | ## 4.0.0 44 | 45 | * Require dart >= 2.16.x 46 | * Updated `angel3_model` to 6.x.x 47 | * Updated `angel3_serialize_generator` to 6.x.x 48 | 49 | ## 3.0.0 50 | 51 | * Updated to min SDK 2.15.x 52 | 53 | ## 2.2.0 54 | 55 | * Upgraded from `pendantic` to `lints` linter 56 | 57 | ## 2.1.1 58 | 59 | * Updated `build.xml` to use the correct packages 60 | 61 | ## 2.1.0 62 | 63 | * Updated `build.xml` to use `angel3` packages 64 | * Upgraded to support major `analyzer` 2.0.0 release 65 | 66 | ## 2.0.0 67 | 68 | * Migrated to support Dart SDK 2.12.x NNBD 69 | * Rename `graphql_generator` to `graphql_generator2` 70 | 71 | ## 1.0.0+1 72 | 73 | * Replace `snakeCase` with `camelCase`. 74 | 75 | ## 1.0.0 76 | 77 | * Apply `package:pedantic`. 78 | 79 | ## 1.0.0-rc.1 80 | 81 | * Add `CHANGELOG.md`, `example/main.dart`. 82 | * Add documentation to `README.md`. 83 | -------------------------------------------------------------------------------- /packages/graphql_generator/LICENSE: -------------------------------------------------------------------------------- 1 | BSD 3-Clause License 2 | 3 | Copyright (c) 2021, dukefirehawk.com 4 | All rights reserved. 5 | 6 | Redistribution and use in source and binary forms, with or without 7 | modification, are permitted provided that the following conditions are met: 8 | 9 | 1. Redistributions of source code must retain the above copyright notice, this 10 | list of conditions and the following disclaimer. 11 | 12 | 2. Redistributions in binary form must reproduce the above copyright notice, 13 | this list of conditions and the following disclaimer in the documentation 14 | and/or other materials provided with the distribution. 15 | 16 | 3. Neither the name of the copyright holder nor the names of its 17 | contributors may be used to endorse or promote products derived from 18 | this software without specific prior written permission. 19 | 20 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 21 | AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 22 | IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 23 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE 24 | FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 25 | DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 26 | SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 27 | CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, 28 | OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 29 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 30 | -------------------------------------------------------------------------------- /packages/graphql_generator/README.md: -------------------------------------------------------------------------------- 1 | # Graphql Generator 2 2 | 3 | ![Pub Version (including pre-releases)](https://img.shields.io/pub/v/graphql_generator2?include_prereleases) 4 | [![Null Safety](https://img.shields.io/badge/null-safety-brightgreen)](https://dart.dev/null-safety) 5 | [![Gitter](https://img.shields.io/gitter/room/nwjs/nw.js.svg)](https://gitter.im/angel_dart/discussion) 6 | [![License](https://img.shields.io/github/license/dart-backend/graphql_dart)](https://github.com/dart-backend/graphql_dart/blob/master/packages/graphql_generator/LICENSE) 7 | [![melos](https://img.shields.io/badge/maintained%20with-melos-f700ff.svg?style=flat-square)](https://github.com/invertase/melos) 8 | 9 | Generates `package:graphql_schema2` schemas for annotated class. 10 | 11 | Replaces `convertDartType` from `package:graphql_server2`. 12 | 13 | ## Usage 14 | 15 | Usage is very simple. You just need a `@graphQLClass` or `@GraphQLClass()` annotation on any class you want to generate an object type for. 16 | 17 | Individual fields can have a `@GraphQLDocumentation()` annotation, to provide information like descriptions, deprecation reasons, etc. 18 | 19 | ```dart 20 | @graphQLClass 21 | @GraphQLDocumentation(description: 'Todo object type') 22 | class Todo { 23 | String text; 24 | 25 | /// Whether this item is complete 26 | bool isComplete; 27 | } 28 | 29 | void main() { 30 | print(todoGraphQLType.fields.map((f) => f.name)); 31 | } 32 | ``` 33 | 34 | The following is generated (as of April 18th, 2019): 35 | 36 | ```dart 37 | // GENERATED CODE - DO NOT MODIFY BY HAND 38 | 39 | part of 'main.dart'; 40 | 41 | // ************************************************************************** 42 | // _GraphQLGenerator 43 | // ************************************************************************** 44 | 45 | /// Auto-generated from [Todo]. 46 | final GraphQLObjectType todoGraphQLType = objectType('Todo', 47 | isInterface: false, 48 | description: 'Todo object type', 49 | interfaces: [], 50 | fields: [ 51 | field('text', graphQLString), 52 | field('isComplete', graphQLBoolean, description: 'Whether this item is complete') 53 | ]); 54 | ``` 55 | -------------------------------------------------------------------------------- /packages/graphql_generator/analysis_options.yaml: -------------------------------------------------------------------------------- 1 | include: package:lints/recommended.yaml -------------------------------------------------------------------------------- /packages/graphql_generator/build.yaml: -------------------------------------------------------------------------------- 1 | builders: 2 | graphql: 3 | import: "package:graphql_generator2/graphql_generator2.dart" 4 | builder_factories: 5 | - graphQLBuilder 6 | auto_apply: root_package 7 | build_to: cache 8 | build_extensions: 9 | .dart: 10 | - graphql_generator2.g.part 11 | required_inputs: 12 | - angel3_serialize.g.part 13 | applies_builders: 14 | - source_gen|combining_builder -------------------------------------------------------------------------------- /packages/graphql_generator/example/main.dart: -------------------------------------------------------------------------------- 1 | import 'package:graphql_schema2/graphql_schema2.dart'; 2 | part 'main.g.dart'; 3 | 4 | @graphQLClass 5 | class TodoItem { 6 | String? text; 7 | 8 | @GraphQLDocumentation(description: 'Whether this item is complete.') 9 | bool? isComplete; 10 | } 11 | 12 | void main() { 13 | print(todoItemGraphQLType.fields.map((f) => f.name)); 14 | } 15 | -------------------------------------------------------------------------------- /packages/graphql_generator/example/main.g.dart: -------------------------------------------------------------------------------- 1 | // GENERATED CODE - DO NOT MODIFY BY HAND 2 | 3 | part of 'main.dart'; 4 | 5 | // ************************************************************************** 6 | // _GraphQLGenerator 7 | // ************************************************************************** 8 | 9 | /// Auto-generated from [TodoItem]. 10 | final GraphQLObjectType todoItemGraphQLType = objectType( 11 | 'TodoItem', 12 | isInterface: false, 13 | interfaces: [], 14 | fields: [ 15 | field( 16 | 'text', 17 | graphQLString, 18 | ), 19 | field( 20 | 'isComplete', 21 | graphQLBoolean, 22 | ), 23 | ], 24 | ); 25 | -------------------------------------------------------------------------------- /packages/graphql_generator/melos_graphql_generator2.iml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | -------------------------------------------------------------------------------- /packages/graphql_generator/pubspec.yaml: -------------------------------------------------------------------------------- 1 | name: graphql_generator2 2 | version: 6.3.0 3 | description: Generates GraphQL schemas from Dart classes, for use with pkg:graphql_server2. 4 | homepage: https://angel3-framework.web.app/ 5 | repository: https://github.com/dart-backend/graphql_dart/tree/master/packages/graphql_generator 6 | environment: 7 | sdk: '>=3.6.0 <4.0.0' 8 | dependencies: 9 | analyzer: ^7.0.0 10 | angel3_model: ^8.0.0 11 | angel3_serialize_generator: ^8.2.0 12 | graphql_schema2: ^6.2.0 13 | build: ^2.4.0 14 | build_config: ^1.1.0 15 | code_builder: ^4.5.0 16 | recase: ^4.1.0 17 | source_gen: ^2.0.0 18 | collection: ^1.17.0 19 | dev_dependencies: 20 | build_runner: ^2.4.0 21 | lints: ^5.0.0 22 | # dependency_overrides: 23 | # graphql_schema2: 24 | # path: ../graphql_schema 25 | 26 | 27 | -------------------------------------------------------------------------------- /packages/graphql_parser/AUTHORS.md: -------------------------------------------------------------------------------- 1 | Primary Authors 2 | =============== 3 | 4 | * __[Thomas Hii](dukefirehawk.apps@gmail.com)__ 5 | 6 | Thomas is the current maintainer of the code base. He has refactored and migrated the 7 | code base to support NNBD. 8 | 9 | * __[Tobe O](thosakwe@gmail.com)__ 10 | 11 | Tobe has written much of the original code prior to NNBD migration. He has moved on and 12 | is no longer involved with the project. 13 | -------------------------------------------------------------------------------- /packages/graphql_parser/CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Change Log 2 | 3 | ## 6.3.0 4 | 5 | * Require Dart >= 3.6 6 | * Updated `lints` to 5.0.0 7 | 8 | ## 6.2.0 9 | 10 | * Require Dart >= 3.3 11 | * Updated `lints` to 4.0.0 12 | 13 | ## 6.1.0 14 | 15 | * Updated `lints` to 3.0.0 16 | * Fixed linter warnings 17 | * Updated repository link 18 | 19 | ## 6.0.0 20 | 21 | * Require dart >= 3.0.x 22 | 23 | ## 5.0.0 24 | 25 | * Require dart >= 2.17.x 26 | 27 | ## 4.0.0 28 | 29 | * Require dart >= 2.16.x 30 | 31 | ## 3.0.0 32 | 33 | * Implemented directives 34 | * Updated to SDK 2.15.x 35 | 36 | ## 2.1.0 37 | 38 | * Upgraded from `pendantic` to `lints` linter 39 | 40 | ## 2.0.2 41 | 42 | * Fixed NNBD issues 43 | 44 | ## 2.0.1 45 | 46 | * Updated array_value.dart 47 | * Updated string_value.dart 48 | 49 | ## 2.0.0 50 | 51 | * Migrated to support Dart SDK 2.12.x NNBD 52 | * Rename `graphql_parser` to `graphql_parser2` 53 | 54 | ## 1.2.0 55 | 56 | * Combine `ValueContext` and `VariableContext` into a single `InputValueContext` supertype. 57 | * Add `T computeValue(Map variables);` 58 | * Resolve [##23](https://github.com/angel-dart/graphql/issues/23). 59 | * Deprecate old `ValueOrVariable` class, and parser/AST methods related to it. 60 | 61 | ## 1.1.4 62 | 63 | * Fix broken int variable parsing - 64 | 65 | ## 1.1.3 66 | 67 | * Add `Parser.nextName`, and remove all formerly-reserved words from the lexer. 68 | Resolves [##19](https://github.com/angel-dart/graphql/issues/19). 69 | 70 | ## 1.1.2 71 | 72 | * Parse the `subscription` keyword. 73 | 74 | ## 1.1.1 75 | 76 | * Pubspec updates for Dart 2. 77 | 78 | ## 1.1.0 79 | 80 | * Removed `GraphQLVisitor`. 81 | * Enable parsing operations without an explicit 82 | name. 83 | * Parse `null`. 84 | * Completely ignore commas. 85 | * Ignore Unicode BOM, as per the spec. 86 | * Parse object values. 87 | * Parse enum values. 88 | -------------------------------------------------------------------------------- /packages/graphql_parser/LICENSE: -------------------------------------------------------------------------------- 1 | BSD 3-Clause License 2 | 3 | Copyright (c) 2021, dukefirehawk.com 4 | All rights reserved. 5 | 6 | Redistribution and use in source and binary forms, with or without 7 | modification, are permitted provided that the following conditions are met: 8 | 9 | 1. Redistributions of source code must retain the above copyright notice, this 10 | list of conditions and the following disclaimer. 11 | 12 | 2. Redistributions in binary form must reproduce the above copyright notice, 13 | this list of conditions and the following disclaimer in the documentation 14 | and/or other materials provided with the distribution. 15 | 16 | 3. Neither the name of the copyright holder nor the names of its 17 | contributors may be used to endorse or promote products derived from 18 | this software without specific prior written permission. 19 | 20 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 21 | AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 22 | IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 23 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE 24 | FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 25 | DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 26 | SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 27 | CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, 28 | OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 29 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 30 | -------------------------------------------------------------------------------- /packages/graphql_parser/README.md: -------------------------------------------------------------------------------- 1 | # Graphql Parser 2 2 | 3 | ![Pub Version (including pre-releases)](https://img.shields.io/pub/v/graphql_parser2?include_prereleases) 4 | [![Null Safety](https://img.shields.io/badge/null-safety-brightgreen)](https://dart.dev/null-safety) 5 | [![Gitter](https://img.shields.io/gitter/room/nwjs/nw.js.svg)](https://gitter.im/angel_dart/discussion) 6 | [![License](https://img.shields.io/github/license/dart-backend/graphql_dart)](https://github.com/dart-backend/graphql_dart/blob/master/packages/graphql_parser/LICENSE) 7 | [![melos](https://img.shields.io/badge/maintained%20with-melos-f700ff.svg?style=flat-square)](https://github.com/invertase/melos) 8 | 9 | Parses GraphQL queries and schemas. 10 | 11 | *This library is merely a parser/visitor*. Any sort of actual GraphQL API functionality must be implemented by you, 12 | or by a third-party package. 13 | 14 | [Angel3 Framework](https://pub.dev/packages/angel3_framework) users should consider 15 | [`package:angel3_graphql`](https://pub.dev/packages/angel3_graphql) 16 | as a dead-simple way to add GraphQL functionality to their servers. 17 | 18 | ## Installation 19 | 20 | Add `graphql_parser2` as a dependency in your `pubspec.yaml` file: 21 | 22 | ```yaml 23 | dependencies: 24 | graphql_parser2: ^6.0.0 25 | ``` 26 | 27 | ## Usage 28 | 29 | The AST featured in this library was originally directly based off this ANTLR4 grammar created by Joseph T. McBride: 30 | 31 | 32 | It has since been updated to reflect upon the grammar in the official GraphQL 33 | specification ([June 2018](https://facebook.github.io/graphql/June2018/)). 34 | 35 | ```dart 36 | import 'package:graphql_parser2/graphql_parser2.dart'; 37 | 38 | doSomething(String text) { 39 | var tokens = scan(text); 40 | var parser = Parser(tokens); 41 | 42 | if (parser.errors.isNotEmpty) { 43 | // Handle errors... 44 | } 45 | 46 | // Parse the GraphQL document using recursive descent 47 | var doc = parser.parseDocument(); 48 | 49 | // Do something with the parsed GraphQL document... 50 | } 51 | ``` 52 | -------------------------------------------------------------------------------- /packages/graphql_parser/analysis_options.yaml: -------------------------------------------------------------------------------- 1 | include: package:lints/recommended.yaml 2 | 3 | linter: 4 | rules: 5 | constant_identifier_names: false 6 | non_constant_identifier_names: false -------------------------------------------------------------------------------- /packages/graphql_parser/example/example.dart: -------------------------------------------------------------------------------- 1 | import 'package:graphql_parser2/graphql_parser2.dart'; 2 | 3 | final String text = ''' 4 | { 5 | project(name: "GraphQL") { 6 | tagline 7 | } 8 | } 9 | ''' 10 | .trim(); 11 | 12 | void main() { 13 | var tokens = scan(text); 14 | var parser = Parser(tokens); 15 | var doc = parser.parseDocument(); 16 | 17 | var operation = doc.definitions.first as OperationDefinitionContext; 18 | 19 | var projectField = operation.selectionSet.selections.first.field!; 20 | print(projectField.fieldName.name); // project 21 | print(projectField.arguments.first.name); // name 22 | print(projectField.arguments.first.value); // GraphQL 23 | 24 | var taglineField = projectField.selectionSet!.selections.first.field!; 25 | print(taglineField.fieldName.name); // tagline 26 | } 27 | -------------------------------------------------------------------------------- /packages/graphql_parser/graphql_parser.iml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | -------------------------------------------------------------------------------- /packages/graphql_parser/lib/graphql_parser2.dart: -------------------------------------------------------------------------------- 1 | export 'src/language/ast/ast.dart'; 2 | export 'src/language/language.dart'; 3 | -------------------------------------------------------------------------------- /packages/graphql_parser/lib/src/language/ast/alias.dart: -------------------------------------------------------------------------------- 1 | import 'package:source_span/source_span.dart'; 2 | import '../token.dart'; 3 | import 'node.dart'; 4 | 5 | /// An alternate name for a field within a [SelectionSet]. 6 | class AliasContext extends Node { 7 | /// The source tokens. 8 | final Token nameToken1, colonToken, nameToken2; 9 | 10 | AliasContext(this.nameToken1, this.colonToken, this.nameToken2); 11 | 12 | // Use [nameToken1] instead. 13 | //@deprecated 14 | //Token? get NAME1 => nameToken1; 15 | 16 | // Use [colonToken] instead. 17 | //@deprecated 18 | //Token? get COLON => colonToken; 19 | 20 | // Use [nameToken2] instead. 21 | //@deprecated 22 | //Token? get NAME2 => nameToken2; 23 | 24 | /// The aliased name of the value. 25 | String get alias => nameToken1.text; 26 | 27 | /// The actual name of the value. 28 | String get name => nameToken2.text; 29 | 30 | @override 31 | FileSpan get span => 32 | nameToken1.span!.expand(colonToken.span!).expand(nameToken2.span!); 33 | } 34 | -------------------------------------------------------------------------------- /packages/graphql_parser/lib/src/language/ast/argument.dart: -------------------------------------------------------------------------------- 1 | import 'package:source_span/source_span.dart'; 2 | import '../token.dart'; 3 | import 'node.dart'; 4 | import 'input_value.dart'; 5 | 6 | /// An argument passed to a [FieldContext]. 7 | class ArgumentContext extends Node { 8 | /// The source tokens. 9 | final Token nameToken, colonToken; 10 | 11 | /// The value of the argument. 12 | final InputValueContext value; 13 | 14 | ArgumentContext(this.nameToken, this.colonToken, this.value); 15 | 16 | // Use [value] instead. 17 | //@deprecated 18 | //InputValueContext get valueOrVariable => value; 19 | 20 | // Use [nameToken] instead. 21 | //@deprecated 22 | //Token? get NAME => nameToken; 23 | 24 | // Use [colonToken] instead. 25 | //@deprecated 26 | //Token? get COLON => colonToken; 27 | 28 | /// The name of the argument, as a [String]. 29 | String get name => nameToken.text; 30 | 31 | @override 32 | FileSpan get span => 33 | nameToken.span!.expand(colonToken.span!).expand(value.span!); 34 | } 35 | -------------------------------------------------------------------------------- /packages/graphql_parser/lib/src/language/ast/array_value.dart: -------------------------------------------------------------------------------- 1 | import 'package:source_span/source_span.dart'; 2 | import '../token.dart'; 3 | import 'input_value.dart'; 4 | 5 | /// A GraphQL list value literal. 6 | class ListValueContext extends InputValueContext { 7 | /// The source tokens. 8 | final Token lBracketToken, rBracketToken; 9 | 10 | /// The child values. 11 | final List values = []; 12 | 13 | ListValueContext(this.lBracketToken, this.rBracketToken); 14 | 15 | /// Use [lBracketToken] instead. 16 | @Deprecated('Use [lBracketToken]') 17 | Token get LBRACKET => lBracketToken; 18 | 19 | /// Use [rBracketToken] instead. 20 | @Deprecated('Use [rBracketToken]') 21 | Token get RBRACKET => rBracketToken; 22 | 23 | @override 24 | FileSpan get span { 25 | var out = values.fold( 26 | lBracketToken.span, (o, v) => o!.expand(v.span!))!; 27 | return out.expand(rBracketToken.span!); 28 | } 29 | 30 | @override 31 | List computeValue(Map variables) { 32 | return values.map((v) => v.computeValue(variables)).toList(); 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /packages/graphql_parser/lib/src/language/ast/ast.dart: -------------------------------------------------------------------------------- 1 | library; 2 | 3 | export 'alias.dart'; 4 | export 'array_value.dart'; 5 | export 'argument.dart'; 6 | export 'boolean_value.dart'; 7 | export 'default_value.dart'; 8 | export 'definition.dart'; 9 | export 'deprecated_value.dart'; 10 | export 'directive.dart'; 11 | export 'document.dart'; 12 | export 'field.dart'; 13 | export 'field_name.dart'; 14 | export 'fragment_definition.dart'; 15 | export 'fragment_spread.dart'; 16 | export 'inline_fragment.dart'; 17 | export 'input_value.dart'; 18 | export 'list_type.dart'; 19 | export 'misc_value.dart'; 20 | export 'node.dart'; 21 | export 'number_value.dart'; 22 | export 'operation_definition.dart'; 23 | export 'selection.dart'; 24 | export 'selection_set.dart'; 25 | export 'string_value.dart'; 26 | export 'type.dart'; 27 | export 'type_condition.dart'; 28 | export 'type_name.dart'; 29 | export 'variable.dart'; 30 | export 'variable_definition.dart'; 31 | export 'variable_definitions.dart'; 32 | -------------------------------------------------------------------------------- /packages/graphql_parser/lib/src/language/ast/boolean_value.dart: -------------------------------------------------------------------------------- 1 | import 'package:source_span/source_span.dart'; 2 | import 'input_value.dart'; 3 | import '../token.dart'; 4 | 5 | /// A GraphQL boolean value literal. 6 | class BooleanValueContext extends InputValueContext { 7 | bool localValueCache = false; 8 | 9 | /// The source token. 10 | final Token booleanToken; 11 | 12 | BooleanValueContext(this.booleanToken) { 13 | assert(booleanToken.text == 'true' || booleanToken.text == 'false'); 14 | } 15 | 16 | /// The [bool] value of this literal. 17 | bool get booleanValue => localValueCache = booleanToken.text == 'true'; 18 | 19 | /// Use [booleanToken] instead. 20 | @Deprecated("Use [boolenToken]") 21 | Token get BOOLEAN => booleanToken; 22 | 23 | @override 24 | FileSpan? get span => booleanToken.span; 25 | 26 | @override 27 | bool computeValue(Map variables) => booleanValue; 28 | } 29 | -------------------------------------------------------------------------------- /packages/graphql_parser/lib/src/language/ast/default_value.dart: -------------------------------------------------------------------------------- 1 | import 'package:source_span/source_span.dart'; 2 | import '../token.dart'; 3 | import 'input_value.dart'; 4 | import 'node.dart'; 5 | 6 | /// The default value to be passed to an [ArgumentContext]. 7 | class DefaultValueContext extends Node { 8 | /// The source token. 9 | final Token equalsToken; 10 | 11 | /// The default value for the argument. 12 | final InputValueContext value; 13 | 14 | DefaultValueContext(this.equalsToken, this.value); 15 | 16 | // Use [equalsToken] instead. 17 | //@deprecated 18 | //Token? get EQUALS => equalsToken; 19 | 20 | @override 21 | FileSpan get span => equalsToken.span!.expand(value.span!); 22 | } 23 | -------------------------------------------------------------------------------- /packages/graphql_parser/lib/src/language/ast/definition.dart: -------------------------------------------------------------------------------- 1 | import 'node.dart'; 2 | 3 | /// The base class for top-level GraphQL definitions. 4 | abstract class DefinitionContext extends Node {} 5 | 6 | /// An executable definition. 7 | abstract class ExecutableDefinitionContext extends DefinitionContext {} 8 | 9 | /// An ad-hoc type system declared in GraphQL. 10 | abstract class TypeSystemDefinitionContext extends DefinitionContext {} 11 | 12 | /// An extension to an existing ad-hoc type system. 13 | abstract class TypeSystemExtensionContext extends DefinitionContext {} 14 | -------------------------------------------------------------------------------- /packages/graphql_parser/lib/src/language/ast/deprecated_value.dart: -------------------------------------------------------------------------------- 1 | import 'input_value.dart'; 2 | 3 | /// Use [ConstantContext] instead. This class remains solely for backwards compatibility. 4 | @Deprecated("Use [ConstantContext]") 5 | abstract class ValueContext extends InputValueContext { 6 | /// Return a constant value. 7 | T get value; 8 | 9 | @override 10 | T computeValue(Map variables) => value; 11 | } 12 | -------------------------------------------------------------------------------- /packages/graphql_parser/lib/src/language/ast/directive.dart: -------------------------------------------------------------------------------- 1 | import 'package:source_span/source_span.dart'; 2 | import '../token.dart'; 3 | import 'argument.dart'; 4 | import 'input_value.dart'; 5 | import 'node.dart'; 6 | 7 | /// A GraphQL directive, which may or may not have runtime semantics. 8 | class DirectiveContext extends Node { 9 | /// The source tokens. 10 | final Token? arrobaToken, nameToken, colonToken, lParenToken, rParenToken; 11 | 12 | /// The argument being passed as the directive. 13 | final ArgumentContext? argument; 14 | 15 | /// The (optional) value being passed with the directive. 16 | final InputValueContext? value; 17 | 18 | DirectiveContext(this.arrobaToken, this.nameToken, this.colonToken, 19 | this.lParenToken, this.rParenToken, this.argument, this.value) { 20 | assert(nameToken != null); 21 | } 22 | 23 | // Use [value] instead. 24 | //@deprecated 25 | //InputValueContext? get valueOrVariable => value; 26 | 27 | // Use [arrobaToken] instead. 28 | //@deprecated 29 | //Token? get ARROBA => arrobaToken; 30 | 31 | // Use [nameToken] instead. 32 | //@deprecated 33 | //Token? get NAME => nameToken; 34 | 35 | // Use [colonToken] instead. 36 | //@deprecated 37 | //Token? get COLON => colonToken; 38 | 39 | // Use [lParenToken] instead. 40 | //@deprecated 41 | //Token? get LPAREN => lParenToken; 42 | 43 | // Use [rParenToken] instead. 44 | //@deprecated 45 | //Token? get RPAREN => rParenToken; 46 | 47 | @override 48 | FileSpan get span { 49 | var out = arrobaToken!.span!.expand(nameToken!.span!); 50 | 51 | if (colonToken != null) { 52 | out = out.expand(colonToken!.span!).expand(value!.span!); 53 | } else if (lParenToken != null) { 54 | out = out 55 | .expand(lParenToken!.span!) 56 | .expand(argument!.span) 57 | .expand(rParenToken!.span!); 58 | } 59 | 60 | return out; 61 | } 62 | } 63 | -------------------------------------------------------------------------------- /packages/graphql_parser/lib/src/language/ast/document.dart: -------------------------------------------------------------------------------- 1 | import 'package:source_span/source_span.dart'; 2 | import 'definition.dart'; 3 | import 'node.dart'; 4 | 5 | /// A GraphQL document. 6 | class DocumentContext extends Node { 7 | /// The top-level definitions in the document. 8 | final List definitions = []; 9 | 10 | @override 11 | FileSpan? get span { 12 | if (definitions.isEmpty) { 13 | return null; 14 | } 15 | return definitions.map((d) => d.span).reduce((a, b) { 16 | if (a == null) { 17 | return b; 18 | } 19 | 20 | if (b == null) { 21 | return a; 22 | } 23 | return a.expand(b); 24 | }); 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /packages/graphql_parser/lib/src/language/ast/field.dart: -------------------------------------------------------------------------------- 1 | import 'package:source_span/source_span.dart'; 2 | import 'argument.dart'; 3 | import 'field_name.dart'; 4 | import 'node.dart'; 5 | import 'selection_set.dart'; 6 | 7 | /// A field in a GraphQL [SelectionSet]. 8 | class FieldContext extends Node with Directives { 9 | /// The name of this field. 10 | final FieldNameContext fieldName; 11 | 12 | /// Any arguments this field expects. 13 | final List arguments = []; 14 | 15 | /// The list of selections to resolve on an object. 16 | final SelectionSetContext? selectionSet; 17 | 18 | FieldContext(this.fieldName, [this.selectionSet]); 19 | 20 | @override 21 | FileSpan? get span { 22 | if (selectionSet != null) { 23 | var otherSpan = selectionSet?.span; 24 | if (otherSpan == null) { 25 | return fieldName.span; 26 | } 27 | return fieldName.span?.expand(otherSpan); 28 | } else if (directives.isNotEmpty) { 29 | return directives.fold( 30 | fieldName.span, (out, d) => out?.expand(d.span)); 31 | } 32 | if (arguments.isNotEmpty) { 33 | return arguments.fold( 34 | fieldName.span, (out, a) => out?.expand(a.span)); 35 | } else { 36 | return fieldName.span; 37 | } 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /packages/graphql_parser/lib/src/language/ast/field_name.dart: -------------------------------------------------------------------------------- 1 | import 'package:source_span/source_span.dart'; 2 | import '../token.dart'; 3 | import 'alias.dart'; 4 | import 'node.dart'; 5 | 6 | /// The name of a GraphQL [FieldContext], which may or may not be [alias]ed. 7 | class FieldNameContext extends Node { 8 | /// The source token. 9 | final Token? nameToken; 10 | 11 | /// An (optional) alias for the field. 12 | final AliasContext? alias; 13 | 14 | FieldNameContext(this.nameToken, [this.alias]) { 15 | assert(nameToken != null || alias != null); 16 | } 17 | 18 | /// Use [nameToken] instead. 19 | @Deprecated("Use [nameToken]") 20 | Token? get NAME => nameToken; 21 | 22 | /// The [String] value of the [nameToken], if any. 23 | String? get name => nameToken?.text; 24 | 25 | @override 26 | FileSpan? get span => alias?.span ?? nameToken?.span; 27 | } 28 | -------------------------------------------------------------------------------- /packages/graphql_parser/lib/src/language/ast/fragment_definition.dart: -------------------------------------------------------------------------------- 1 | import '../token.dart'; 2 | import 'definition.dart'; 3 | import 'directive.dart'; 4 | import 'package:source_span/source_span.dart'; 5 | import 'selection_set.dart'; 6 | import 'type_condition.dart'; 7 | 8 | /// A GraphQL query fragment definition. 9 | class FragmentDefinitionContext extends ExecutableDefinitionContext { 10 | /// The source tokens. 11 | final Token? fragmentToken, nameToken, onToken; 12 | 13 | /// The type to which this fragment applies. 14 | final TypeConditionContext typeCondition; 15 | 16 | /// Any directives on the fragment. 17 | final List directives = []; 18 | 19 | /// The selections to apply when the [typeCondition] is met. 20 | final SelectionSetContext selectionSet; 21 | 22 | /// The [String] value of the [nameToken]. 23 | String? get name => nameToken!.text; 24 | 25 | FragmentDefinitionContext(this.fragmentToken, this.nameToken, this.onToken, 26 | this.typeCondition, this.selectionSet); 27 | 28 | /// Use [fragmentToken] instead. 29 | @Deprecated("Use [fragmentToken]") 30 | Token? get FRAGMENT => fragmentToken; 31 | 32 | /// Use [nameToken] instead. 33 | @Deprecated("Use [nameToken]") 34 | Token? get NAME => nameToken; 35 | 36 | /// Use [onToken] instead. 37 | @Deprecated("Use [onToken]") 38 | Token? get ON => onToken; 39 | 40 | @override 41 | FileSpan? get span { 42 | var out = fragmentToken?.span; 43 | var nameSpan = nameToken?.span; 44 | var onSpan = onToken?.span; 45 | var conditionSpan = typeCondition.span; 46 | 47 | if (out == null) { 48 | return selectionSet.span; 49 | } 50 | if (nameSpan != null) { 51 | out = out.expand(nameSpan); 52 | } 53 | 54 | if (onSpan != null) { 55 | out = out.expand(onSpan); 56 | } 57 | 58 | if (conditionSpan != null) { 59 | out = out.expand(conditionSpan); 60 | } 61 | 62 | //var out = fragmentToken?.span 63 | // .expand(nameToken?.span!) 64 | // .expand(onToken?.span!) 65 | // .expand(typeCondition.span!); 66 | out = directives.fold(out, (o, d) => o.expand(d.span)); 67 | if (selectionSet.span != null) { 68 | return out.expand(selectionSet.span!); 69 | } else { 70 | return out; 71 | } 72 | } 73 | } 74 | -------------------------------------------------------------------------------- /packages/graphql_parser/lib/src/language/ast/fragment_spread.dart: -------------------------------------------------------------------------------- 1 | import '../token.dart'; 2 | import 'directive.dart'; 3 | import 'node.dart'; 4 | import 'package:source_span/source_span.dart'; 5 | 6 | /// A GraphQL fragment spread. 7 | class FragmentSpreadContext extends Node { 8 | /// The source tokens. 9 | final Token ellipsisToken; 10 | final Token nameToken; 11 | 12 | /// Any directives affixed to this fragment spread. 13 | final List directives = []; 14 | 15 | FragmentSpreadContext(this.ellipsisToken, this.nameToken); 16 | 17 | /// The [String] value of the [nameToken]. 18 | String? get name => nameToken.text; 19 | 20 | /// Use [ellipsisToken] instead. 21 | //@deprecated 22 | //Token? get ELLIPSIS => ellipsisToken; 23 | 24 | /// Use [nameToken] instead. 25 | //@deprecated 26 | //Token? get NAME => nameToken; 27 | 28 | @override 29 | FileSpan? get span { 30 | var out = ellipsisToken.span?.expand(nameToken.span!); 31 | if (directives.isEmpty || out == null) { 32 | return out; 33 | } 34 | return directives.fold(out, (o, d) => o.expand(d.span)); 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /packages/graphql_parser/lib/src/language/ast/inline_fragment.dart: -------------------------------------------------------------------------------- 1 | import '../token.dart'; 2 | import 'directive.dart'; 3 | import 'node.dart'; 4 | import 'package:source_span/source_span.dart'; 5 | import 'selection_set.dart'; 6 | import 'type_condition.dart'; 7 | 8 | /// An inline fragment, which typically appears in a [SelectionSetContext]. 9 | class InlineFragmentContext extends Node { 10 | /// The source tokens. 11 | final Token ellipsisToken, onToken; 12 | 13 | /// The type which this fragment matches. 14 | final TypeConditionContext typeCondition; 15 | 16 | /// Any directives affixed to this inline fragment. 17 | final List directives = []; 18 | 19 | /// The selections applied when the [typeCondition] is met. 20 | final SelectionSetContext selectionSet; 21 | 22 | InlineFragmentContext( 23 | this.ellipsisToken, this.onToken, this.typeCondition, this.selectionSet); 24 | 25 | @override 26 | FileSpan get span { 27 | var out = 28 | ellipsisToken.span!.expand(onToken.span!).expand(typeCondition.span!); 29 | out = directives.fold(out, (o, d) => o.expand(d.span)); 30 | 31 | return out.expand(selectionSet.span!); 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /packages/graphql_parser/lib/src/language/ast/input_value.dart: -------------------------------------------------------------------------------- 1 | import 'node.dart'; 2 | 3 | /// Represents a value in GraphQL. 4 | abstract class InputValueContext extends Node { 5 | /// Computes the value, relative to some set of [variables]. 6 | T computeValue(Map variables); 7 | } 8 | -------------------------------------------------------------------------------- /packages/graphql_parser/lib/src/language/ast/list_type.dart: -------------------------------------------------------------------------------- 1 | import '../token.dart'; 2 | import 'node.dart'; 3 | import 'package:source_span/source_span.dart'; 4 | import 'type.dart'; 5 | 6 | /// Represents a type that holds a list of another type. 7 | class ListTypeContext extends Node { 8 | /// The source tokens. 9 | final Token lBracketToken, rBracketToken; 10 | 11 | /// The inner type. 12 | final TypeContext innerType; 13 | 14 | ListTypeContext(this.lBracketToken, this.innerType, this.rBracketToken); 15 | 16 | @override 17 | FileSpan get span => 18 | lBracketToken.span!.expand(innerType.span).expand(rBracketToken.span!); 19 | } 20 | -------------------------------------------------------------------------------- /packages/graphql_parser/lib/src/language/ast/misc_value.dart: -------------------------------------------------------------------------------- 1 | import 'package:source_span/source_span.dart'; 2 | import '../token.dart'; 3 | import 'input_value.dart'; 4 | import 'node.dart'; 5 | 6 | /// A GraphQL `null` literal. 7 | class NullValueContext extends InputValueContext { 8 | /// The source token. 9 | final Token nullToken; 10 | 11 | NullValueContext(this.nullToken); 12 | 13 | // Use [nullToken] instead. 14 | //@deprecated 15 | //Token get NULL => nullToken; 16 | 17 | @override 18 | FileSpan? get span => nullToken.span; 19 | 20 | @override 21 | Null computeValue(Map variables) => null; 22 | } 23 | 24 | /// A GraphQL enumeration literal. 25 | class EnumValueContext extends InputValueContext { 26 | /// The source token. 27 | final Token nameToken; 28 | 29 | EnumValueContext(this.nameToken); 30 | 31 | @override 32 | FileSpan? get span => nameToken.span; 33 | 34 | @override 35 | String computeValue(Map variables) => nameToken.span!.text; 36 | } 37 | 38 | /// A GraphQL object literal. 39 | class ObjectValueContext extends InputValueContext> { 40 | /// The source tokens. 41 | final Token lBraceToken, rBraceToken; 42 | 43 | /// The fields in the object. 44 | final List fields; 45 | 46 | ObjectValueContext(this.lBraceToken, this.fields, this.rBraceToken); 47 | 48 | /// Use [lBraceToken] instead. 49 | Token get LBRACE => lBraceToken; 50 | 51 | @override 52 | FileSpan get span { 53 | var left = lBraceToken.span; 54 | 55 | for (var field in fields) { 56 | left = left!.expand(field.span); 57 | } 58 | 59 | return left!.expand(rBraceToken.span!); 60 | } 61 | 62 | @override 63 | Map computeValue(Map variables) { 64 | if (fields.isEmpty) { 65 | return {}; 66 | } else { 67 | return fields.fold>({}, 68 | (map, field) { 69 | return map 70 | ..[field.nameToken.text] = field.value.computeValue(variables); 71 | }); 72 | } 73 | } 74 | } 75 | 76 | /// A field within an [ObjectValueContext]. 77 | class ObjectFieldContext extends Node { 78 | /// The source tokens. 79 | final Token nameToken, colonToken; 80 | 81 | /// The associated value. 82 | final InputValueContext value; 83 | 84 | ObjectFieldContext(this.nameToken, this.colonToken, this.value); 85 | 86 | @override 87 | FileSpan get span => 88 | nameToken.span!.expand(colonToken.span!).expand(value.span!); 89 | } 90 | -------------------------------------------------------------------------------- /packages/graphql_parser/lib/src/language/ast/node.dart: -------------------------------------------------------------------------------- 1 | import 'package:source_span/source_span.dart'; 2 | 3 | import '../../../graphql_parser2.dart'; 4 | 5 | abstract class Node { 6 | FileSpan? get span; 7 | } 8 | 9 | mixin Directives { 10 | /// Any directives affixed to this field. 11 | final List directives = []; 12 | } 13 | -------------------------------------------------------------------------------- /packages/graphql_parser/lib/src/language/ast/number_value.dart: -------------------------------------------------------------------------------- 1 | import 'dart:math' as math; 2 | import 'package:source_span/source_span.dart'; 3 | import '../token.dart'; 4 | import 'input_value.dart'; 5 | 6 | /// A GraphQL number literal. 7 | class NumberValueContext extends InputValueContext { 8 | /// The source token. 9 | final Token numberToken; 10 | 11 | NumberValueContext(this.numberToken); 12 | 13 | /// The [num] value of the [numberToken]. 14 | num get numberValue { 15 | var text = numberToken.text; 16 | if (!text.contains('E') && !text.contains('e')) { 17 | return num.parse(text); 18 | } else { 19 | var split = text.split(text.contains('E') ? 'E' : 'e'); 20 | var base = num.parse(split[0]); 21 | var exp = num.parse(split[1]); 22 | return base * math.pow(10, exp); 23 | } 24 | } 25 | 26 | // Use [numberToken] instead. 27 | // @deprecated 28 | // Token get NUMBER => numberToken; 29 | 30 | @override 31 | FileSpan? get span => numberToken.span; 32 | 33 | @override 34 | num computeValue(Map variables) => numberValue; 35 | } 36 | -------------------------------------------------------------------------------- /packages/graphql_parser/lib/src/language/ast/operation_definition.dart: -------------------------------------------------------------------------------- 1 | import 'package:source_span/source_span.dart'; 2 | import '../token.dart'; 3 | import 'definition.dart'; 4 | import 'directive.dart'; 5 | import 'selection_set.dart'; 6 | import 'variable_definitions.dart'; 7 | 8 | /// An executable GraphQL operation definition. 9 | class OperationDefinitionContext extends ExecutableDefinitionContext { 10 | /// The source tokens. 11 | final Token? typeToken, nameToken; 12 | 13 | /// The variables defined in the operation. 14 | final VariableDefinitionsContext? variableDefinitions; 15 | 16 | /// Any directives affixed to this operation. 17 | final List directives = []; 18 | 19 | /// The selections to be applied to an object resolved in this operation. 20 | final SelectionSetContext selectionSet; 21 | 22 | /// Whether this operation is a `mutation`. 23 | bool get isMutation => typeToken?.text == 'mutation'; 24 | 25 | /// Whether this operation is a `subscription`. 26 | bool get isSubscription => typeToken?.text == 'subscription'; 27 | 28 | /// Whether this operation is a `query`. 29 | bool get isQuery => typeToken?.text == 'query' || typeToken == null; 30 | 31 | /// The [String] value of the [nameToken]. 32 | String? get name => nameToken?.text; 33 | 34 | OperationDefinitionContext(this.typeToken, this.nameToken, 35 | this.variableDefinitions, this.selectionSet) { 36 | assert(typeToken == null || 37 | typeToken!.text == 'query' || 38 | typeToken!.text == 'mutation' || 39 | typeToken!.text == 'subscription'); 40 | } 41 | 42 | @override 43 | FileSpan? get span { 44 | if (typeToken == null) return selectionSet.span; 45 | var out = nameToken == null 46 | ? typeToken!.span 47 | : typeToken!.span!.expand(nameToken!.span!); 48 | out = directives.fold(out, (o, d) => o!.expand(d.span)); 49 | return out!.expand(selectionSet.span!); 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /packages/graphql_parser/lib/src/language/ast/selection.dart: -------------------------------------------------------------------------------- 1 | import 'field.dart'; 2 | import 'fragment_spread.dart'; 3 | import 'inline_fragment.dart'; 4 | import 'node.dart'; 5 | import 'package:source_span/source_span.dart'; 6 | 7 | class SelectionContext extends Node { 8 | final FieldContext? field; 9 | final FragmentSpreadContext? fragmentSpread; 10 | final InlineFragmentContext? inlineFragment; 11 | 12 | SelectionContext(this.field, [this.fragmentSpread, this.inlineFragment]) { 13 | assert(field != null || fragmentSpread != null || inlineFragment != null); 14 | } 15 | 16 | @override 17 | FileSpan? get span => 18 | field?.span ?? fragmentSpread?.span ?? inlineFragment?.span; 19 | } 20 | -------------------------------------------------------------------------------- /packages/graphql_parser/lib/src/language/ast/selection_set.dart: -------------------------------------------------------------------------------- 1 | import 'package:source_span/source_span.dart'; 2 | 3 | import '../token.dart'; 4 | import 'node.dart'; 5 | import 'selection.dart'; 6 | 7 | /// A set of GraphQL selections - fields, fragments, or inline fragments. 8 | class SelectionSetContext extends Node { 9 | /// The source tokens. 10 | final Token? lBraceToken, rBraceToken; 11 | 12 | /// The selections to be applied. 13 | List selections = []; 14 | 15 | SelectionSetContext(this.lBraceToken, this.rBraceToken); 16 | 17 | /// A synthetic [SelectionSetContext] produced from a set of [selections]. 18 | factory SelectionSetContext.merged(List selections) = 19 | _MergedSelectionSetContext; 20 | 21 | @override 22 | FileSpan? get span { 23 | var out = selections.fold( 24 | lBraceToken!.span, (out, s) => out!.expand(s.span!))!; 25 | return out.expand(rBraceToken!.span!); 26 | } 27 | } 28 | 29 | class _MergedSelectionSetContext extends SelectionSetContext { 30 | //@override 31 | //final List selections; 32 | 33 | _MergedSelectionSetContext(List selections) 34 | : super(null, null) { 35 | super.selections = selections; 36 | } 37 | 38 | @override 39 | FileSpan? get span => 40 | selections.map((s) => s.span).reduce((a, b) => a!.expand(b!)); 41 | } 42 | -------------------------------------------------------------------------------- /packages/graphql_parser/lib/src/language/ast/string_value.dart: -------------------------------------------------------------------------------- 1 | import 'package:charcode/charcode.dart'; 2 | import 'package:source_span/source_span.dart'; 3 | 4 | import '../syntax_error.dart'; 5 | import '../token.dart'; 6 | import 'input_value.dart'; 7 | 8 | /// A GraphQL string value literal. 9 | class StringValueContext extends InputValueContext { 10 | /// The source token. 11 | final Token stringToken; 12 | 13 | /// Whether this is a block string. 14 | final bool isBlockString; 15 | 16 | StringValueContext(this.stringToken, {this.isBlockString = false}); 17 | 18 | @override 19 | FileSpan? get span => stringToken.span; 20 | 21 | /// The [String] value of the [stringToken]. 22 | String get stringValue { 23 | String text; 24 | 25 | if (!isBlockString) { 26 | text = stringToken.text.substring(1, stringToken.text.length - 1); 27 | } else { 28 | text = stringToken.text.substring(3, stringToken.text.length - 3).trim(); 29 | } 30 | 31 | var codeUnits = text.codeUnits; 32 | var buf = StringBuffer(); 33 | 34 | for (var i = 0; i < codeUnits.length; i++) { 35 | var ch = codeUnits[i]; 36 | 37 | if (ch == $backslash) { 38 | if (i < codeUnits.length - 5 && codeUnits[i + 1] == $u) { 39 | var c1 = codeUnits[i += 2], 40 | c2 = codeUnits[++i], 41 | c3 = codeUnits[++i], 42 | c4 = codeUnits[++i]; 43 | var hexString = String.fromCharCodes([c1, c2, c3, c4]); 44 | var hexNumber = int.parse(hexString, radix: 16); 45 | buf.write(String.fromCharCode(hexNumber)); 46 | continue; 47 | } 48 | 49 | if (i < codeUnits.length - 1) { 50 | var next = codeUnits[++i]; 51 | 52 | switch (next) { 53 | case $b: 54 | buf.write('\b'); 55 | break; 56 | case $f: 57 | buf.write('\f'); 58 | break; 59 | case $n: 60 | buf.writeCharCode($lf); 61 | break; 62 | case $r: 63 | buf.writeCharCode($cr); 64 | break; 65 | case $t: 66 | buf.writeCharCode($tab); 67 | break; 68 | default: 69 | buf.writeCharCode(next); 70 | } 71 | } else { 72 | throw SyntaxError('Unexpected "\\" in string literal.', span); 73 | } 74 | } else { 75 | buf.writeCharCode(ch); 76 | } 77 | } 78 | 79 | return buf.toString(); 80 | } 81 | 82 | @override 83 | String computeValue(Map variables) => stringValue; 84 | } 85 | -------------------------------------------------------------------------------- /packages/graphql_parser/lib/src/language/ast/type.dart: -------------------------------------------------------------------------------- 1 | import 'package:source_span/source_span.dart'; 2 | import '../token.dart'; 3 | import 'list_type.dart'; 4 | import 'node.dart'; 5 | import 'type_name.dart'; 6 | 7 | /// A GraphQL type node. 8 | class TypeContext extends Node { 9 | /// A source token, present in a nullable type literal. 10 | final Token? exclamationToken; 11 | 12 | /// The name of the referenced type. 13 | final TypeNameContext? typeName; 14 | 15 | /// A list type that is being referenced. 16 | final ListTypeContext? listType; 17 | 18 | /// Whether the type is nullable. 19 | bool get isNullable => exclamationToken == null; 20 | 21 | TypeContext(this.typeName, this.listType, [this.exclamationToken]) { 22 | assert(typeName != null || listType != null); 23 | } 24 | 25 | @override 26 | FileSpan get span { 27 | var out = typeName?.span ?? listType!.span; 28 | return exclamationToken != null ? out.expand(exclamationToken!.span!) : out; 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /packages/graphql_parser/lib/src/language/ast/type_condition.dart: -------------------------------------------------------------------------------- 1 | import 'node.dart'; 2 | import 'package:source_span/source_span.dart'; 3 | import 'type_name.dart'; 4 | 5 | class TypeConditionContext extends Node { 6 | final TypeNameContext typeName; 7 | 8 | TypeConditionContext(this.typeName); 9 | 10 | @override 11 | FileSpan? get span => typeName.span; 12 | } 13 | -------------------------------------------------------------------------------- /packages/graphql_parser/lib/src/language/ast/type_name.dart: -------------------------------------------------------------------------------- 1 | import 'node.dart'; 2 | import 'package:source_span/source_span.dart'; 3 | import '../token.dart'; 4 | 5 | /// The name of a GraphQL type. 6 | class TypeNameContext extends Node { 7 | /// The source token. 8 | final Token? nameToken; 9 | 10 | TypeNameContext(this.nameToken); 11 | 12 | /// The [String] value of the [nameToken]. 13 | String? get name => nameToken!.text; 14 | 15 | @override 16 | FileSpan? get span => nameToken!.span; 17 | } 18 | -------------------------------------------------------------------------------- /packages/graphql_parser/lib/src/language/ast/variable.dart: -------------------------------------------------------------------------------- 1 | import 'package:source_span/source_span.dart'; 2 | import '../token.dart'; 3 | import 'input_value.dart'; 4 | 5 | /// A variable reference in GraphQL. 6 | class VariableContext extends InputValueContext { 7 | /// The source tokens. 8 | final Token dollarToken, nameToken; 9 | 10 | VariableContext(this.dollarToken, this.nameToken); 11 | 12 | /// The [String] value of the [nameToken]. 13 | String get name => nameToken.text; 14 | 15 | // Use [dollarToken] instead. 16 | //@deprecated 17 | //Token get DOLLAR => dollarToken; 18 | 19 | // Use [nameToken] instead. 20 | //@deprecated 21 | //Token get NAME => nameToken; 22 | 23 | @override 24 | FileSpan get span => dollarToken.span!.expand(nameToken.span!); 25 | 26 | @override 27 | Object? computeValue(Map variables) => variables[name]; 28 | } 29 | -------------------------------------------------------------------------------- /packages/graphql_parser/lib/src/language/ast/variable_definition.dart: -------------------------------------------------------------------------------- 1 | import '../../../graphql_parser2.dart'; 2 | import 'package:source_span/source_span.dart'; 3 | 4 | /// A single variable definition. 5 | class VariableDefinitionContext extends Node with Directives { 6 | /// The source token. 7 | final Token? colonToken; 8 | 9 | /// The declared variable. 10 | final VariableContext variable; 11 | 12 | /// The type of the variable. 13 | final TypeContext type; 14 | 15 | /// The default value of the variable. 16 | final DefaultValueContext? defaultValue; 17 | 18 | VariableDefinitionContext(this.variable, this.colonToken, this.type, 19 | [this.defaultValue]); 20 | 21 | /// Use [colonToken] instead. 22 | @Deprecated('Use [colonToken] instead.') 23 | Token? get COLON => colonToken; 24 | 25 | @override 26 | FileSpan get span => variable.span.expand(defaultValue?.span ?? type.span); 27 | } 28 | -------------------------------------------------------------------------------- /packages/graphql_parser/lib/src/language/ast/variable_definitions.dart: -------------------------------------------------------------------------------- 1 | import '../token.dart'; 2 | import 'node.dart'; 3 | import 'package:source_span/source_span.dart'; 4 | import 'variable_definition.dart'; 5 | 6 | /// A set of variable definitions in a GraphQL operation. 7 | class VariableDefinitionsContext extends Node { 8 | /// The source tokens. 9 | final Token? lParenToken, rParenToken; 10 | 11 | /// The variables defined in this node. 12 | final List variableDefinitions = []; 13 | 14 | VariableDefinitionsContext(this.lParenToken, this.rParenToken); 15 | 16 | @override 17 | FileSpan get span { 18 | var out = variableDefinitions.fold( 19 | lParenToken!.span, (o, v) => o!.expand(v.span))!; 20 | return out.expand(rParenToken!.span!); 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /packages/graphql_parser/lib/src/language/language.dart: -------------------------------------------------------------------------------- 1 | library; 2 | 3 | export 'lexer.dart'; 4 | export 'parser.dart'; 5 | export 'syntax_error.dart'; 6 | export 'token.dart'; 7 | export 'token_type.dart'; 8 | -------------------------------------------------------------------------------- /packages/graphql_parser/lib/src/language/lexer.dart: -------------------------------------------------------------------------------- 1 | import 'package:string_scanner/string_scanner.dart'; 2 | 3 | import 'syntax_error.dart'; 4 | import 'token.dart'; 5 | import 'token_type.dart'; 6 | 7 | final RegExp _comment = RegExp(r'#[^\n]*'); 8 | final RegExp _whitespace = RegExp('[ \t\n\r]+'); 9 | // final RegExp _boolean = RegExp(r'true|false'); 10 | final RegExp _number = RegExp(r'-?[0-9]+(\.[0-9]+)?(E|e(\+|-)?[0-9]+)?'); 11 | final RegExp _string = RegExp( 12 | r'"((\\(["\\/bfnrt]|(u[0-9a-fA-F][0-9a-fA-F][0-9a-fA-F][0-9a-fA-F])))|([^"\\]))*"'); 13 | final RegExp _blockString = RegExp(r'"""(([^"])|(\\"""))*"""'); 14 | final RegExp _name = RegExp(r'[_A-Za-z][_0-9A-Za-z]*'); 15 | 16 | final Map _patterns = { 17 | '@': TokenType.ARROBA, 18 | ':': TokenType.COLON, 19 | ',': TokenType.COMMA, 20 | r'$': TokenType.DOLLAR, 21 | '...': TokenType.ELLIPSIS, 22 | '=': TokenType.EQUALS, 23 | '!': TokenType.EXCLAMATION, 24 | '{': TokenType.LBRACE, 25 | '}': TokenType.RBRACE, 26 | '[': TokenType.LBRACKET, 27 | ']': TokenType.RBRACKET, 28 | '(': TokenType.LPAREN, 29 | ')': TokenType.RPAREN, 30 | // 'fragment': TokenType.FRAGMENT, 31 | // 'mutation': TokenType.MUTATION, 32 | // 'subscription': TokenType.SUBSCRIPTION, 33 | // 'on': TokenType.ON, 34 | // 'query': TokenType.QUERY, 35 | // 'null': TokenType.NULL, 36 | // _boolean: TokenType.BOOLEAN, 37 | _number: TokenType.NUMBER, 38 | _string: TokenType.STRING, 39 | _blockString: TokenType.BLOCK_STRING, 40 | _name: TokenType.NAME 41 | }; 42 | 43 | List scan(String text, {sourceUrl}) { 44 | var out = []; 45 | var scanner = SpanScanner(text, sourceUrl: sourceUrl); 46 | 47 | while (!scanner.isDone) { 48 | var potential = []; 49 | 50 | if (scanner.scan(_comment) || 51 | scanner.scan(_whitespace) || 52 | scanner.scan(',') || 53 | scanner.scan('\ufeff')) { 54 | continue; 55 | } 56 | 57 | for (var pattern in _patterns.keys) { 58 | if (scanner.matches(pattern)) { 59 | var pat = _patterns[pattern]; 60 | var scan = scanner.lastMatch?[0]; 61 | if (pat != null && scan != null) { 62 | potential.add(Token(pat, scan, scanner.lastSpan)); 63 | } 64 | } 65 | } 66 | 67 | if (potential.isEmpty) { 68 | var ch = String.fromCharCode(scanner.readChar()); 69 | throw SyntaxError("Unexpected token '$ch'.", scanner.emptySpan); 70 | } else { 71 | // Choose longest token 72 | potential.sort((a, b) => b.text.length.compareTo(a.text.length)); 73 | var chosen = potential.first; 74 | out.add(chosen); 75 | scanner.scan(chosen.text); 76 | } 77 | } 78 | 79 | return out; 80 | } 81 | -------------------------------------------------------------------------------- /packages/graphql_parser/lib/src/language/syntax_error.dart: -------------------------------------------------------------------------------- 1 | import 'package:source_span/source_span.dart'; 2 | 3 | class SyntaxError implements Exception { 4 | final String message; 5 | final FileSpan? span; 6 | 7 | SyntaxError(this.message, this.span); 8 | 9 | @override 10 | String toString() => 11 | 'Syntax error at ${span?.start.toolString}: $message\n${span?.highlight()}'; 12 | } 13 | -------------------------------------------------------------------------------- /packages/graphql_parser/lib/src/language/token.dart: -------------------------------------------------------------------------------- 1 | import 'package:source_span/source_span.dart'; 2 | import 'token_type.dart'; 3 | 4 | class Token { 5 | final TokenType type; 6 | final String text; 7 | FileSpan? span; 8 | 9 | Token(this.type, this.text, [this.span]); 10 | 11 | @override 12 | String toString() { 13 | if (span == null) { 14 | return "'$text' -> $type"; 15 | } else { 16 | return "(${span!.start.line}:${span?.start.column}) '$text' -> $type"; 17 | } 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /packages/graphql_parser/lib/src/language/token_type.dart: -------------------------------------------------------------------------------- 1 | enum TokenType { 2 | ARROBA, 3 | COLON, 4 | COMMA, 5 | DOLLAR, 6 | ELLIPSIS, 7 | EQUALS, 8 | EXCLAMATION, 9 | LBRACE, 10 | RBRACE, 11 | LBRACKET, 12 | RBRACKET, 13 | LPAREN, 14 | RPAREN, 15 | 16 | // Note: these are *not* reserved names. 17 | // FRAGMENT, 18 | // MUTATION, 19 | // SUBSCRIPTION, 20 | // ON, 21 | // QUERY, 22 | // NULL 23 | // BOOLEAN, 24 | 25 | NUMBER, 26 | STRING, 27 | BLOCK_STRING, 28 | 29 | NAME, 30 | } 31 | -------------------------------------------------------------------------------- /packages/graphql_parser/melos_graphql_parser2.iml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | -------------------------------------------------------------------------------- /packages/graphql_parser/pubspec.yaml: -------------------------------------------------------------------------------- 1 | name: graphql_parser2 2 | version: 6.3.0 3 | description: Parses GraphQL queries and schemas. Also includes classes for the GraphQL AST. 4 | homepage: https://angel3-framework.web.app/ 5 | repository: https://github.com/dart-backend/graphql_dart/tree/master/packages/graphql_parser 6 | environment: 7 | sdk: '>=3.6.0 <4.0.0' 8 | dependencies: 9 | charcode: ^1.3.1 10 | source_span: ^1.10.0 11 | string_scanner: ^1.2.0 12 | dev_dependencies: 13 | matcher: ^0.12.10 14 | lints: ^5.0.0 15 | test: ^1.24.0 16 | -------------------------------------------------------------------------------- /packages/graphql_parser/test/argument_test.dart: -------------------------------------------------------------------------------- 1 | import 'package:graphql_parser2/graphql_parser2.dart'; 2 | import 'package:test/test.dart'; 3 | 4 | import 'common.dart'; 5 | 6 | void main() { 7 | test('argument', () { 8 | expect('foo: 2', isArgument('foo', 2)); 9 | expect(r'foo: $bar', isArgument('foo', 'bar')); 10 | }); 11 | 12 | test('exception', () { 13 | var isSyntaxError = predicate((dynamic x) { 14 | var parser = parse(x.toString())..parseArgument(); 15 | return parser.errors.isNotEmpty; 16 | }, 'fails to parse argument'); 17 | 18 | var isSyntaxErrorOnArguments = predicate((dynamic x) { 19 | var parser = parse(x.toString())..parseArguments(); 20 | return parser.errors.isNotEmpty; 21 | }, 'fails to parse arguments'); 22 | 23 | expect('foo', isSyntaxError); 24 | expect('foo:', isSyntaxError); 25 | expect(r'(foo: $bar', isSyntaxErrorOnArguments); 26 | }); 27 | } 28 | 29 | ArgumentContext? parseArgument(String text) => parse(text).parseArgument(); 30 | 31 | List? parseArgumentList(String text) => 32 | parse(text).parseArguments(); 33 | 34 | Matcher isArgument(String name, value) => _IsArgument(name, value); 35 | 36 | Matcher isArgumentList(List arguments) => _IsArgumentList(arguments); 37 | 38 | class _IsArgument extends Matcher { 39 | final String name; 40 | final dynamic value; 41 | 42 | _IsArgument(this.name, this.value); 43 | 44 | @override 45 | Description describe(Description description) { 46 | return description.add('is an argument named "$name" with value $value'); 47 | } 48 | 49 | @override 50 | bool matches(item, Map matchState) { 51 | var arg = item is ArgumentContext ? item : parseArgument(item.toString()); 52 | if (arg == null) return false; 53 | print(arg.span.highlight()); 54 | 55 | var v = arg.value; 56 | return equals(name).matches(arg.name, matchState) && 57 | ((v is VariableContext && equals(value).matches(v.name, matchState)) || 58 | equals(value).matches(arg.value.computeValue({}), matchState)); 59 | } 60 | } 61 | 62 | class _IsArgumentList extends Matcher { 63 | final List arguments; 64 | 65 | _IsArgumentList(this.arguments); 66 | 67 | @override 68 | Description describe(Description description) { 69 | return description.add('is list of ${arguments.length} argument(s)'); 70 | } 71 | 72 | @override 73 | bool matches(item, Map matchState) { 74 | var args = item is List 75 | ? item 76 | : parse(item.toString()).parseArguments()!; 77 | 78 | if (args.length != arguments.length) return false; 79 | 80 | for (var i = 0; i < args.length; i++) { 81 | if (!arguments[i].matches(args[i], matchState)) return false; 82 | } 83 | 84 | return true; 85 | } 86 | } 87 | -------------------------------------------------------------------------------- /packages/graphql_parser/test/comment_test.dart: -------------------------------------------------------------------------------- 1 | import 'package:graphql_parser2/graphql_parser2.dart'; 2 | import 'package:test/test.dart'; 3 | 4 | void main() { 5 | test('heeds comments', () { 6 | var tokens = scan(''' 7 | # Hello 8 | { 9 | # Goodbye 10 | } 11 | # Bonjour 12 | '''); 13 | 14 | expect(tokens, hasLength(2)); 15 | }); 16 | } 17 | -------------------------------------------------------------------------------- /packages/graphql_parser/test/common.dart: -------------------------------------------------------------------------------- 1 | import 'package:graphql_parser2/graphql_parser2.dart'; 2 | 3 | Parser parse(String text) => Parser(scan(text)); 4 | -------------------------------------------------------------------------------- /packages/graphql_parser/test/directive_test.dart: -------------------------------------------------------------------------------- 1 | import 'package:graphql_parser2/graphql_parser2.dart'; 2 | import 'package:test/test.dart'; 3 | import 'argument_test.dart'; 4 | import 'common.dart'; 5 | 6 | void main() { 7 | test('name only', () { 8 | expect('@foo', isDirective('foo')); 9 | }); 10 | 11 | test('with value or variable', () { 12 | expect('@foo: 2', isDirective('foo', valueOrVariable: equals(2))); 13 | expect(r'@foo: $bar', isDirective('foo', valueOrVariable: equals('bar'))); 14 | }); 15 | 16 | test('with argument', () { 17 | expect('@foo (bar: 2)', isDirective('foo', argument: isArgument('bar', 2))); 18 | expect(r'@foo (bar: $baz)', 19 | isDirective('foo', argument: isArgument('bar', r'baz'))); 20 | }); 21 | 22 | test('exceptions', () { 23 | var isSyntaxError = predicate((dynamic x) { 24 | var parser = parse(x.toString())..parseDirective(); 25 | return parser.errors.isNotEmpty; 26 | }, 'fails to parse directive'); 27 | 28 | expect('@', isSyntaxError); 29 | expect('@foo:', isSyntaxError); 30 | expect('@foo (', isSyntaxError); 31 | expect('@foo (bar: 2', isSyntaxError); 32 | expect('@foo (', isSyntaxError); 33 | }); 34 | } 35 | 36 | DirectiveContext? parseDirective(String text) => parse(text).parseDirective(); 37 | 38 | Matcher isDirective(String name, 39 | {Matcher? valueOrVariable, Matcher? argument}) => 40 | _IsDirective(name, valueOrVariable: valueOrVariable, argument: argument); 41 | 42 | Matcher isDirectiveList(List directives) => 43 | _IsDirectiveList(directives); 44 | 45 | class _IsDirective extends Matcher { 46 | final String name; 47 | final Matcher? valueOrVariable, argument; 48 | 49 | _IsDirective(this.name, {this.valueOrVariable, this.argument}); 50 | 51 | @override 52 | Description describe(Description description) { 53 | var desc = description.add('is a directive with name "$name"'); 54 | 55 | if (valueOrVariable != null) { 56 | return valueOrVariable!.describe(desc.add(' and ')); 57 | } else if (argument != null) { 58 | return argument!.describe(desc.add(' and ')); 59 | } else { 60 | return desc; 61 | } 62 | } 63 | 64 | @override 65 | bool matches(item, Map matchState) { 66 | var directive = 67 | item is DirectiveContext ? item : parseDirective(item.toString()); 68 | if (directive == null) return false; 69 | if (valueOrVariable != null) { 70 | if (directive.value == null) { 71 | return false; 72 | } else { 73 | var v = directive.value; 74 | if (v is VariableContext) { 75 | return valueOrVariable!.matches(v.name, matchState); 76 | } else { 77 | return valueOrVariable! 78 | .matches(directive.value!.computeValue({}), matchState); 79 | } 80 | } 81 | } else if (argument != null) { 82 | if (directive.argument == null) { 83 | return false; 84 | } else { 85 | return argument!.matches(directive.argument, matchState); 86 | } 87 | } else { 88 | return true; 89 | } 90 | } 91 | } 92 | 93 | class _IsDirectiveList extends Matcher { 94 | final List directives; 95 | 96 | _IsDirectiveList(this.directives); 97 | 98 | @override 99 | Description describe(Description description) { 100 | return description.add('is list of ${directives.length} directive(s)'); 101 | } 102 | 103 | @override 104 | bool matches(item, Map matchState) { 105 | var args = item is List 106 | ? item 107 | : parse(item.toString()).parseDirectives(); 108 | 109 | if (args.length != directives.length) return false; 110 | 111 | for (var i = 0; i < args.length; i++) { 112 | if (!directives[i].matches(args[i], matchState)) return false; 113 | } 114 | 115 | return true; 116 | } 117 | } 118 | -------------------------------------------------------------------------------- /packages/graphql_parser/test/document_test.dart: -------------------------------------------------------------------------------- 1 | import 'package:graphql_parser2/graphql_parser2.dart'; 2 | import 'package:test/test.dart'; 3 | 4 | import 'common.dart'; 5 | import 'directive_test.dart'; 6 | import 'field_test.dart'; 7 | import 'selection_set_test.dart'; 8 | import 'type_test.dart'; 9 | import 'value_test.dart'; 10 | import 'variable_definition_test.dart'; 11 | 12 | void main() { 13 | test('fragment', () { 14 | var fragment = parse(''' 15 | fragment PostInfo on Post { 16 | description 17 | comments { 18 | id 19 | } 20 | } 21 | ''').parseFragmentDefinition()!; 22 | 23 | expect(fragment, isNotNull); 24 | expect(fragment.name, 'PostInfo'); 25 | expect(fragment.typeCondition.typeName.name, 'Post'); 26 | expect( 27 | fragment.selectionSet, 28 | isSelectionSet([ 29 | isField(fieldName: isFieldName('description')), 30 | isField( 31 | fieldName: isFieldName('comments'), 32 | selectionSet: 33 | isSelectionSet([isField(fieldName: isFieldName('id'))])), 34 | ])); 35 | }); 36 | 37 | test('fragment exceptions', () { 38 | var throwsSyntaxError = predicate((dynamic x) { 39 | var parser = parse(x.toString())..parseFragmentDefinition(); 40 | return parser.errors.isNotEmpty; 41 | }, 'fails to parse fragment definition'); 42 | 43 | expect('fragment', throwsSyntaxError); 44 | expect('fragment foo', throwsSyntaxError); 45 | expect('fragment foo on', throwsSyntaxError); 46 | expect('fragment foo on bar', throwsSyntaxError); 47 | }); 48 | 49 | group('operation', () { 50 | test('with selection set', () { 51 | var op = parse('{foo, bar: baz}').parseOperationDefinition()!; 52 | expect(op.variableDefinitions, isNull); 53 | expect(op.isQuery, isTrue); 54 | expect(op.isMutation, isFalse); 55 | expect(op.name, isNull); 56 | expect( 57 | op.selectionSet, 58 | isSelectionSet([ 59 | isField(fieldName: isFieldName('foo')), 60 | isField(fieldName: isFieldName('bar', alias: 'baz')) 61 | ])); 62 | }); 63 | 64 | test('mutation', () { 65 | var op = parse('mutation {foo, bar: baz}').parseOperationDefinition()!; 66 | expect(op.variableDefinitions, isNull); 67 | expect(op.isQuery, isFalse); 68 | expect(op.isMutation, isTrue); 69 | expect(op.name, isNull); 70 | expect( 71 | op.selectionSet, 72 | isSelectionSet([ 73 | isField(fieldName: isFieldName('foo')), 74 | isField(fieldName: isFieldName('bar', alias: 'baz')) 75 | ])); 76 | }); 77 | 78 | test('with operation type', () { 79 | var doc = 80 | parse(r'query foo ($one: [int] = 2) @foo @bar: 2 {foo, bar: baz}') 81 | .parseDocument(); 82 | print(doc.span!.highlight()); 83 | expect(doc.definitions, hasLength(1)); 84 | expect(doc.definitions.first is OperationDefinitionContext, isTrue); 85 | var op = doc.definitions.first as OperationDefinitionContext; 86 | expect(op.isMutation, isFalse); 87 | expect(op.isQuery, isTrue); 88 | 89 | expect(op.variableDefinitions!.variableDefinitions, hasLength(1)); 90 | expect( 91 | op.variableDefinitions!.variableDefinitions.first, 92 | isVariableDefinition('one', 93 | type: isListType(isType('int'), isNullable: true), 94 | defaultValue: isValue(2))); 95 | 96 | expect(op.directives, hasLength(2)); 97 | expect(op.directives[0], isDirective('foo')); 98 | expect(op.directives[1], isDirective('bar', valueOrVariable: equals(2))); 99 | 100 | expect(op.selectionSet, isNotNull); 101 | expect( 102 | op.selectionSet, 103 | isSelectionSet([ 104 | isField(fieldName: isFieldName('foo')), 105 | isField(fieldName: isFieldName('bar', alias: 'baz')) 106 | ])); 107 | }); 108 | 109 | test('exceptions', () { 110 | var throwsSyntaxError = predicate((dynamic x) { 111 | var parser = parse(x.toString())..parseOperationDefinition(); 112 | return parser.errors.isNotEmpty; 113 | }, 'fails to parse operation definition'); 114 | 115 | expect('query', throwsSyntaxError); 116 | expect('query foo()', throwsSyntaxError); 117 | }); 118 | }); 119 | } 120 | -------------------------------------------------------------------------------- /packages/graphql_parser/test/field_test.dart: -------------------------------------------------------------------------------- 1 | import 'package:graphql_parser2/graphql_parser2.dart'; 2 | import 'package:test/test.dart'; 3 | 4 | import 'argument_test.dart'; 5 | import 'common.dart'; 6 | import 'directive_test.dart'; 7 | import 'fragment_spread_test.dart'; 8 | import 'selection_set_test.dart'; 9 | import 'value_test.dart'; 10 | 11 | void main() { 12 | group('field name', () { 13 | test('plain field name', () { 14 | expect('foo', isFieldName('foo')); 15 | }); 16 | test('alias', () { 17 | expect('foo: bar', isFieldName('foo', alias: 'bar')); 18 | }); 19 | test('exceptions', () { 20 | var throwsSyntaxError = predicate((dynamic x) { 21 | var parser = parse(x.toString())..parseFieldName(); 22 | return parser.errors.isNotEmpty; 23 | }, 'fails to parse field name'); 24 | 25 | expect('foo:', throwsSyntaxError); 26 | }); 27 | }); 28 | 29 | test('arguments', () { 30 | expect('()', isArgumentList([])); 31 | expect(r'(a: 2)', isArgumentList([isArgument('a', 2)])); 32 | expect(r'(a: 2, b: $c)', 33 | isArgumentList([isArgument('a', 2), isArgument('b', 'c')])); 34 | }); 35 | 36 | group('field tests', () { 37 | test('plain field name', () { 38 | expect('foo', isField(fieldName: isFieldName('foo'))); 39 | }); 40 | 41 | test('aliased field name', () { 42 | expect('foo: bar', isField(fieldName: isFieldName('foo', alias: 'bar'))); 43 | }); 44 | 45 | test('with arguments', () { 46 | expect( 47 | r'foo (a: 2, b: $c)', 48 | isField( 49 | fieldName: isFieldName('foo'), 50 | arguments: 51 | isArgumentList([isArgument('a', 2), isArgument('b', 'c')]))); 52 | }); 53 | 54 | test('with directives', () { 55 | expect( 56 | 'foo: bar (a: 2) @bar @baz: 2 @quux (one: 1)', 57 | isField( 58 | fieldName: isFieldName('foo', alias: 'bar'), 59 | arguments: isArgumentList([isArgument('a', 2)]), 60 | directives: isDirectiveList([ 61 | isDirective('bar'), 62 | isDirective('baz', valueOrVariable: isValue(2)), 63 | isDirective('quux', argument: isArgument('one', 1)) 64 | ]))); 65 | }); 66 | 67 | test('with selection set', () { 68 | expect( 69 | 'foo: bar {baz, ...quux}', 70 | isField( 71 | fieldName: isFieldName('foo', alias: 'bar'), 72 | selectionSet: isSelectionSet([ 73 | isField(fieldName: isFieldName('baz')), 74 | isFragmentSpread('quux') 75 | ]))); 76 | }); 77 | }); 78 | } 79 | 80 | FieldContext? parseField(String text) => parse(text).parseField(); 81 | 82 | FieldNameContext? parseFieldName(String text) => parse(text).parseFieldName(); 83 | 84 | Matcher isField( 85 | {Matcher? fieldName, 86 | Matcher? arguments, 87 | Matcher? directives, 88 | Matcher? selectionSet}) => 89 | _IsField(fieldName, arguments, directives, selectionSet); 90 | 91 | Matcher isFieldName(String name, {String? alias}) => _IsFieldName(name, alias); 92 | 93 | class _IsField extends Matcher { 94 | final Matcher? fieldName, arguments, directives, selectionSet; 95 | 96 | _IsField(this.fieldName, this.arguments, this.directives, this.selectionSet); 97 | 98 | @override 99 | Description describe(Description description) { 100 | // Too lazy to make a real description... 101 | return description.add('is field'); 102 | } 103 | 104 | @override 105 | bool matches(item, Map matchState) { 106 | var field = item is FieldContext ? item : parseField(item.toString()); 107 | if (field == null) return false; 108 | if (fieldName != null && !fieldName!.matches(field.fieldName, matchState)) { 109 | return false; 110 | } 111 | if (arguments != null && !arguments!.matches(field.arguments, matchState)) { 112 | return false; 113 | } 114 | return true; 115 | } 116 | } 117 | 118 | class _IsFieldName extends Matcher { 119 | final String? name, realName; 120 | 121 | _IsFieldName(this.name, this.realName); 122 | 123 | @override 124 | Description describe(Description description) { 125 | if (realName != null) { 126 | return description 127 | .add('is field with name "$name" and alias "$realName"'); 128 | } 129 | return description.add('is field with name "$name"'); 130 | } 131 | 132 | @override 133 | bool matches(item, Map matchState) { 134 | var fieldName = 135 | item is FieldNameContext ? item : parseFieldName(item.toString()); 136 | if (realName != null) { 137 | return fieldName!.alias?.alias == name && 138 | fieldName.alias?.name == realName; 139 | } else { 140 | return fieldName!.name == name; 141 | } 142 | } 143 | } 144 | -------------------------------------------------------------------------------- /packages/graphql_parser/test/fragment_spread_test.dart: -------------------------------------------------------------------------------- 1 | import 'package:graphql_parser2/graphql_parser2.dart'; 2 | import 'package:test/test.dart'; 3 | import 'common.dart'; 4 | import 'argument_test.dart'; 5 | import 'directive_test.dart'; 6 | 7 | void main() { 8 | test('name only', () { 9 | expect(['...foo', '... foo'], everyElement(isFragmentSpread('foo'))); 10 | }); 11 | 12 | test('with directives', () { 13 | expect( 14 | '... foo @bar @baz: 2 @quux(one: 1)', 15 | isFragmentSpread('foo', 16 | directives: isDirectiveList([ 17 | isDirective('bar'), 18 | isDirective('baz', valueOrVariable: equals(2)), 19 | isDirective('quux', argument: isArgument('one', 1)) 20 | ]))); 21 | }); 22 | } 23 | 24 | FragmentSpreadContext? parseFragmentSpread(String text) => 25 | parse(text).parseFragmentSpread(); 26 | 27 | Matcher isFragmentSpread(String name, {Matcher? directives}) => 28 | _IsFragmentSpread(name, directives); 29 | 30 | class _IsFragmentSpread extends Matcher { 31 | final String name; 32 | final Matcher? directives; 33 | 34 | _IsFragmentSpread(this.name, this.directives); 35 | 36 | @override 37 | Description describe(Description description) { 38 | if (directives != null) { 39 | return directives!.describe( 40 | description.add('is a fragment spread named "$name" that also ')); 41 | } 42 | return description.add('is a fragment spread named "$name"'); 43 | } 44 | 45 | @override 46 | bool matches(item, Map matchState) { 47 | var spread = item is FragmentSpreadContext 48 | ? item 49 | : parseFragmentSpread(item.toString()); 50 | if (spread == null) return false; 51 | if (spread.name != name) return false; 52 | if (directives != null) { 53 | return directives!.matches(spread.directives, matchState); 54 | } else { 55 | return true; 56 | } 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /packages/graphql_parser/test/inline_fragment_test.dart: -------------------------------------------------------------------------------- 1 | import 'package:graphql_parser2/graphql_parser2.dart'; 2 | import 'package:test/test.dart'; 3 | import 'common.dart'; 4 | import 'argument_test.dart'; 5 | import 'directive_test.dart'; 6 | import 'field_test.dart'; 7 | import 'fragment_spread_test.dart'; 8 | import 'selection_set_test.dart'; 9 | 10 | void main() { 11 | test('no directives', () { 12 | expect( 13 | '... on foo {bar, baz: quux}', 14 | isInlineFragment('foo', 15 | selectionSet: isSelectionSet([ 16 | isField(fieldName: isFieldName('bar')), 17 | isField(fieldName: isFieldName('baz', alias: 'quux')) 18 | ]))); 19 | }); 20 | 21 | test('with directives', () { 22 | expect( 23 | '... on foo @bar @baz: 2 @quux(one: 1) {... bar}', 24 | isInlineFragment('foo', 25 | directives: isDirectiveList([ 26 | isDirective('bar'), 27 | isDirective('baz', valueOrVariable: equals(2)), 28 | isDirective('quux', argument: isArgument('one', 1)) 29 | ]), 30 | selectionSet: isSelectionSet([isFragmentSpread('bar')]))); 31 | }); 32 | 33 | test('exceptions', () { 34 | var throwsSyntaxError = predicate((dynamic x) { 35 | var parser = parse(x.toString())..parseInlineFragment(); 36 | return parser.errors.isNotEmpty; 37 | }, 'fails to parse inline fragment'); 38 | expect('... on foo', throwsSyntaxError); 39 | expect('... on foo @bar', throwsSyntaxError); 40 | expect('... on', throwsSyntaxError); 41 | expect('...', throwsSyntaxError); 42 | }); 43 | } 44 | 45 | InlineFragmentContext? parseInlineFragment(String text) => 46 | parse(text).parseInlineFragment(); 47 | 48 | Matcher isInlineFragment(String name, 49 | {Matcher? directives, Matcher? selectionSet}) => 50 | _IsInlineFragment(name, directives, selectionSet); 51 | 52 | class _IsInlineFragment extends Matcher { 53 | final String name; 54 | final Matcher? directives, selectionSet; 55 | 56 | _IsInlineFragment(this.name, this.directives, this.selectionSet); 57 | 58 | @override 59 | Description describe(Description description) { 60 | return description.add('is an inline fragment named "$name"'); 61 | } 62 | 63 | @override 64 | bool matches(item, Map matchState) { 65 | var fragment = item is InlineFragmentContext 66 | ? item 67 | : parseInlineFragment(item.toString()); 68 | if (fragment == null) return false; 69 | if (fragment.typeCondition.typeName.name != name) return false; 70 | if (directives != null && 71 | !directives!.matches(fragment.directives, matchState)) { 72 | return false; 73 | } 74 | if (selectionSet != null && 75 | !selectionSet!.matches(fragment.selectionSet, matchState)) { 76 | return false; 77 | } 78 | return true; 79 | } 80 | } 81 | -------------------------------------------------------------------------------- /packages/graphql_parser/test/issue23_test.dart: -------------------------------------------------------------------------------- 1 | import 'package:graphql_parser2/graphql_parser2.dart'; 2 | import 'package:test/test.dart'; 3 | 4 | /// This is an *extremely* verbose test, but basically it 5 | /// parses both documents, and makes sure that $memberId has 6 | /// a valid value. 7 | /// 8 | /// Resolves https://github.com/angel-dart/graphql/issues/23. 9 | void main() { 10 | void testStr(String name, String text) { 11 | test('name', () { 12 | final tokens = scan(text); 13 | final parser = Parser(tokens); 14 | 15 | if (parser.errors.isNotEmpty) { 16 | print(parser.errors.toString()); 17 | } 18 | expect(parser.errors, isEmpty); 19 | 20 | // Parse the GraphQL document using recursive descent 21 | final doc = parser.parseDocument(); 22 | 23 | expect(doc.definitions, isNotNull); 24 | expect(doc.definitions, isNotEmpty); 25 | 26 | // Sanity check 27 | var queryDef = doc.definitions[0] as OperationDefinitionContext; 28 | expect(queryDef.isQuery, true); 29 | expect(queryDef.name, 'customerMemberAttributes'); 30 | expect(queryDef.variableDefinitions!.variableDefinitions, hasLength(1)); 31 | var memberIdDef = queryDef.variableDefinitions!.variableDefinitions[0]; 32 | expect(memberIdDef.variable.name, 'memberId'); 33 | 34 | // Find $memberId 35 | var customerByCustomerId = queryDef.selectionSet.selections[0]; 36 | var customerMemberAttributesByCustomerId = 37 | customerByCustomerId.field!.selectionSet!.selections[0]; 38 | var nodes0 = customerMemberAttributesByCustomerId 39 | .field!.selectionSet!.selections[0]; 40 | var customerMemberAttributeId = nodes0.field!.selectionSet!.selections[0]; 41 | expect(customerMemberAttributeId.field!.fieldName.name, 42 | 'customerMemberAttributeId'); 43 | var memberAttr = nodes0.field!.selectionSet!.selections[1]; 44 | expect(memberAttr.field!.fieldName.name, 45 | 'memberAttributesByCustomerMemberAttributeId'); 46 | expect(memberAttr.field!.arguments, hasLength(1)); 47 | var condition = memberAttr.field!.arguments[0]; 48 | expect(condition.name, 'condition'); 49 | expect(condition.value, TypeMatcher()); 50 | var conditionValue = condition.value as ObjectValueContext; 51 | var memberId = conditionValue.fields 52 | .singleWhere((f) => f.nameToken.text == 'memberId'); 53 | expect(memberId.value, TypeMatcher()); 54 | print('Found \$memberId: Instance of $T'); 55 | }); 56 | } 57 | 58 | testStr('member id as var', memberIdAsVar); 59 | testStr('member id as constant', memberIdAsConstant); 60 | } 61 | 62 | final String memberIdAsVar = r''' 63 | query customerMemberAttributes($memberId: Int!){ 64 | customerByCustomerId(customerId: 7) { 65 | customerMemberAttributesByCustomerId { 66 | nodes { 67 | customerMemberAttributeId 68 | memberAttributesByCustomerMemberAttributeId(condition: {memberId: $memberId}) { 69 | nodes { 70 | memberAttributeId 71 | } 72 | } 73 | } 74 | } 75 | } 76 | } 77 | '''; 78 | 79 | final String memberIdAsConstant = r''' 80 | query customerMemberAttributes($memberId: Int!){ 81 | customerByCustomerId(customerId: 7) { 82 | customerMemberAttributesByCustomerId { 83 | nodes { 84 | customerMemberAttributeId 85 | memberAttributesByCustomerMemberAttributeId(condition: {memberId: 7}) { 86 | nodes { 87 | memberAttributeId 88 | } 89 | } 90 | } 91 | } 92 | } 93 | } 94 | '''; 95 | -------------------------------------------------------------------------------- /packages/graphql_parser/test/next_name_test.dart: -------------------------------------------------------------------------------- 1 | import 'package:test/test.dart'; 2 | import 'common.dart'; 3 | 4 | var githubSrc = r''' 5 | query searchRepos($queryString: String!, $repositoryOrder: RepositoryOrder, $first: Int!) { 6 | search(type: REPOSITORY, query: $queryString, first: $first) { 7 | ...SearchResultItemConnection 8 | } 9 | } 10 | '''; 11 | 12 | void main() { 13 | test('can parse formerly-reserved words', () { 14 | var def = parse(githubSrc).parseOperationDefinition()!; 15 | expect(def.isQuery, isTrue); 16 | expect(def.variableDefinitions!.variableDefinitions, hasLength(3)); 17 | 18 | var searchField = def.selectionSet.selections[0].field!; 19 | expect(searchField.fieldName.name, 'search'); 20 | 21 | var argNames = searchField.arguments.map((a) => a.name).toList(); 22 | expect(argNames, ['type', 'query', 'first']); 23 | }); 24 | } 25 | -------------------------------------------------------------------------------- /packages/graphql_parser/test/selection_set_test.dart: -------------------------------------------------------------------------------- 1 | import 'package:graphql_parser2/graphql_parser2.dart'; 2 | import 'package:test/test.dart'; 3 | import 'common.dart'; 4 | import 'field_test.dart'; 5 | import 'fragment_spread_test.dart'; 6 | import 'inline_fragment_test.dart'; 7 | 8 | void main() { 9 | test('empty', () { 10 | expect('{}', isSelectionSet([])); 11 | }); 12 | 13 | test('with commas', () { 14 | expect( 15 | '{foo, bar: baz}', 16 | isSelectionSet([ 17 | isField(fieldName: isFieldName('foo')), 18 | isField(fieldName: isFieldName('bar', alias: 'baz')) 19 | ])); 20 | }); 21 | 22 | test('no commas', () { 23 | expect( 24 | ''' 25 | { 26 | foo 27 | bar: baz ...quux 28 | ... on foo {bar, baz} 29 | }''' 30 | .split('\n') 31 | .map((s) => s.trim()) 32 | .join(' '), 33 | isSelectionSet([ 34 | isField(fieldName: isFieldName('foo')), 35 | isField(fieldName: isFieldName('bar', alias: 'baz')), 36 | isFragmentSpread('quux'), 37 | isInlineFragment('foo', 38 | selectionSet: isSelectionSet([ 39 | isField(fieldName: isFieldName('bar')), 40 | isField(fieldName: isFieldName('baz')), 41 | ])) 42 | ])); 43 | }); 44 | 45 | test('exceptions', () { 46 | var throwsSyntaxError = predicate((dynamic x) { 47 | var parser = parse(x.toString())..parseSelectionSet(); 48 | return parser.errors.isNotEmpty; 49 | }, 'fails to parse selection set'); 50 | 51 | expect('{foo,bar,baz', throwsSyntaxError); 52 | }); 53 | } 54 | 55 | SelectionSetContext? parseSelectionSet(String text) => 56 | parse(text).parseSelectionSet(); 57 | 58 | Matcher isSelectionSet(List selections) => _IsSelectionSet(selections); 59 | 60 | class _IsSelectionSet extends Matcher { 61 | final List selections; 62 | 63 | _IsSelectionSet(this.selections); 64 | 65 | @override 66 | Description describe(Description description) { 67 | return description 68 | .add('is selection set with ${selections.length} selection(s)'); 69 | } 70 | 71 | @override 72 | bool matches(item, Map matchState) { 73 | var set = 74 | item is SelectionSetContext ? item : parseSelectionSet(item.toString()); 75 | 76 | // if (set != null) { 77 | // print('Item: $set has ${set.selections.length} selection(s):'); 78 | // for (var s in set.selections) { 79 | // print(' * $s (${s.span.text})'); 80 | // } 81 | // } 82 | 83 | if (set == null) return false; 84 | if (set.selections.length != selections.length) return false; 85 | 86 | for (var i = 0; i < set.selections.length; i++) { 87 | var sel = set.selections[i]; 88 | if (!selections[i].matches( 89 | sel.field ?? sel.fragmentSpread ?? sel.inlineFragment, matchState)) { 90 | return false; 91 | } 92 | } 93 | 94 | return true; 95 | } 96 | } 97 | -------------------------------------------------------------------------------- /packages/graphql_parser/test/type_test.dart: -------------------------------------------------------------------------------- 1 | import 'package:graphql_parser2/graphql_parser2.dart'; 2 | import 'package:test/test.dart'; 3 | import 'common.dart'; 4 | 5 | void main() { 6 | test('nullable', () { 7 | expect('foo', isType('foo', isNullable: true)); 8 | }); 9 | 10 | test('non-nullable', () { 11 | expect('foo!', isType('foo', isNullable: false)); 12 | }); 13 | 14 | group('list type', () { 15 | group('nullable list type', () { 16 | test('with nullable', () { 17 | expect('[foo]', isListType(isType('foo', isNullable: true))); 18 | }); 19 | 20 | test('with non-nullable', () { 21 | expect('[foo!]', isListType(isType('foo', isNullable: false))); 22 | }); 23 | }); 24 | 25 | group('non-nullable list type', () { 26 | test('with nullable', () { 27 | expect('[foo]!', 28 | isListType(isType('foo', isNullable: true), isNullable: false)); 29 | }); 30 | 31 | test('with non-nullable', () { 32 | expect('[foo!]!', 33 | isListType(isType('foo', isNullable: false), isNullable: false)); 34 | }); 35 | }); 36 | 37 | test('exceptions', () { 38 | var throwsSyntaxError = predicate((dynamic x) { 39 | var parser = parse(x.toString())..parseType(); 40 | return parser.errors.isNotEmpty; 41 | }, 'fails to parse type'); 42 | 43 | expect('[foo', throwsSyntaxError); 44 | expect('[', throwsSyntaxError); 45 | }); 46 | }); 47 | } 48 | 49 | TypeContext? parseType(String text) => parse(text).parseType(); 50 | 51 | Matcher isListType(Matcher innerType, {bool? isNullable}) => 52 | _IsListType(innerType, isNullable: isNullable != false); 53 | 54 | Matcher isType(String name, {bool? isNullable}) => 55 | _IsType(name, nonNull: isNullable != true); 56 | 57 | class _IsListType extends Matcher { 58 | final Matcher innerType; 59 | final bool? isNullable; 60 | 61 | _IsListType(this.innerType, {this.isNullable}); 62 | 63 | @override 64 | Description describe(Description description) { 65 | var tok = isNullable != false ? 'nullable' : 'non-nullable'; 66 | var desc = description.add('is $tok list type with an inner type that '); 67 | return innerType.describe(desc); 68 | } 69 | 70 | @override 71 | bool matches(item, Map matchState) { 72 | var type = item is TypeContext ? item : parseType(item.toString())!; 73 | if (type.listType == null) return false; 74 | if (type.isNullable != (isNullable != false)) return false; 75 | return innerType.matches(type.listType!.innerType, matchState); 76 | } 77 | } 78 | 79 | class _IsType extends Matcher { 80 | final String name; 81 | final bool? nonNull; 82 | 83 | _IsType(this.name, {this.nonNull}); 84 | 85 | @override 86 | Description describe(Description description) { 87 | if (nonNull == true) { 88 | return description.add('is non-null type named "$name"'); 89 | } else { 90 | return description.add('is nullable type named "$name"'); 91 | } 92 | } 93 | 94 | @override 95 | bool matches(item, Map matchState) { 96 | var type = item is TypeContext ? item : parseType(item.toString())!; 97 | if (type.typeName == null) return false; 98 | var result = type.typeName!.name == name; 99 | return result && type.isNullable == !(nonNull == true); 100 | } 101 | } 102 | -------------------------------------------------------------------------------- /packages/graphql_parser/test/value_test.dart: -------------------------------------------------------------------------------- 1 | import 'dart:math' as math; 2 | 3 | import 'package:graphql_parser2/graphql_parser2.dart'; 4 | import 'package:test/test.dart'; 5 | 6 | import 'common.dart'; 7 | 8 | void main() { 9 | test('boolean', () { 10 | expect('true', isValue(true)); 11 | expect('false', isValue(false)); 12 | }); 13 | 14 | test('number', () { 15 | expect('1', isValue(1)); 16 | expect('1.0', isValue(1.0)); 17 | expect('-1', isValue(-1)); 18 | expect('-1.0', isValue(-1.0)); 19 | expect('6.26e-34', isValue(6.26 * math.pow(10, -34))); 20 | expect('-6.26e-34', isValue(-6.26 * math.pow(10, -34))); 21 | expect('-6.26e34', isValue(-6.26 * math.pow(10, 34))); 22 | }); 23 | 24 | test('array', () { 25 | expect('[]', isValue([])); 26 | expect('[1,2]', isValue([1, 2])); 27 | expect('[1,2, 3]', isValue([1, 2, 3])); 28 | expect('["a"]', isValue(['a'])); 29 | }); 30 | 31 | test('string', () { 32 | expect('""', isValue('')); 33 | expect('"a"', isValue('a')); 34 | expect('"abc"', isValue('abc')); 35 | expect('"\\""', isValue('"')); 36 | expect('"\\b"', isValue('\b')); 37 | expect('"\\f"', isValue('\f')); 38 | expect('"\\n"', isValue('\n')); 39 | expect('"\\r"', isValue('\r')); 40 | expect('"\\t"', isValue('\t')); 41 | expect('"\\u0123"', isValue('\u0123')); 42 | expect('"\\u0123\\u4567"', isValue('\u0123\u4567')); 43 | }); 44 | 45 | test('block string', () { 46 | expect('""""""', isValue('')); 47 | expect('"""abc"""', isValue('abc')); 48 | expect('"""\n\n\nabc\n\n\n"""', isValue('abc')); 49 | }); 50 | 51 | test('object', () { 52 | expect('{}', isValue({})); 53 | expect('{a: 2}', isValue({'a': 2})); 54 | expect('{a: 2, b: "c"}', isValue({'a': 2, 'b': 'c'})); 55 | }); 56 | 57 | test('null', () { 58 | expect('null', isValue(null)); 59 | }); 60 | 61 | test('enum', () { 62 | expect('FOO_BAR', isValue('FOO_BAR')); 63 | }); 64 | 65 | test('exceptions', () { 66 | var throwsSyntaxError = predicate((dynamic x) { 67 | var parser = parse(x.toString())..parseInputValue(); 68 | return parser.errors.isNotEmpty; 69 | }, 'fails to parse value'); 70 | 71 | expect('[1', throwsSyntaxError); 72 | }); 73 | } 74 | 75 | InputValueContext? parseValue(String text) => parse(text).parseInputValue(); 76 | 77 | Matcher isValue(value) => _IsValue(value); 78 | 79 | class _IsValue extends Matcher { 80 | final dynamic value; 81 | 82 | _IsValue(this.value); 83 | 84 | @override 85 | Description describe(Description description) => 86 | description.add('equals $value when parsed as a GraphQL value'); 87 | 88 | @override 89 | bool matches(item, Map matchState) { 90 | var v = item is InputValueContext ? item : parseValue(item.toString())!; 91 | return equals(value).matches(v.computeValue({}), matchState); 92 | } 93 | } 94 | -------------------------------------------------------------------------------- /packages/graphql_parser/test/variable_definition_test.dart: -------------------------------------------------------------------------------- 1 | import 'package:graphql_parser2/graphql_parser2.dart'; 2 | import 'package:test/test.dart'; 3 | 4 | import 'common.dart'; 5 | import 'type_test.dart'; 6 | import 'value_test.dart'; 7 | 8 | void main() { 9 | test('no default value', () { 10 | expect(r'$foo: bar', 11 | isVariableDefinition('foo', type: isType('bar', isNullable: true))); 12 | }); 13 | 14 | test('default value', () { 15 | expect( 16 | r'$foo: int! = 2', 17 | isVariableDefinition('foo', 18 | type: isType('int', isNullable: false), defaultValue: isValue(2))); 19 | }); 20 | 21 | test('exceptions', () { 22 | var throwsSyntaxError = predicate((dynamic x) { 23 | var parser = parse(x.toString())..parseVariableDefinition(); 24 | return parser.errors.isNotEmpty; 25 | }, 'fails to parse variable definition'); 26 | 27 | var throwsSyntaxErrorOnDefinitions = predicate((dynamic x) { 28 | var parser = parse(x.toString())..parseVariableDefinitions(); 29 | return parser.errors.isNotEmpty; 30 | }, 'fails to parse variable definitions'); 31 | 32 | expect(r'$foo', throwsSyntaxError); 33 | expect(r'$foo:', throwsSyntaxError); 34 | expect(r'$foo: int =', throwsSyntaxError); 35 | 36 | expect(r'($foo: int = 2', throwsSyntaxErrorOnDefinitions); 37 | }); 38 | } 39 | 40 | VariableDefinitionContext? parseVariableDefinition(String text) => 41 | parse(text).parseVariableDefinition(); 42 | 43 | Matcher isVariableDefinition(String name, 44 | {Matcher? type, Matcher? defaultValue}) => 45 | _IsVariableDefinition(name, type, defaultValue); 46 | 47 | class _IsVariableDefinition extends Matcher { 48 | final String name; 49 | final Matcher? type, defaultValue; 50 | 51 | _IsVariableDefinition(this.name, this.type, this.defaultValue); 52 | 53 | @override 54 | Description describe(Description description) { 55 | var desc = description.add('is variable definition with name "$name"'); 56 | 57 | if (type != null) { 58 | desc = type!.describe(desc.add(' with type that ')); 59 | } 60 | 61 | if (defaultValue != null) { 62 | desc = type!.describe(desc.add(' with default value that ')); 63 | } 64 | 65 | return desc; 66 | } 67 | 68 | @override 69 | bool matches(item, Map matchState) { 70 | var def = item is VariableDefinitionContext 71 | ? item 72 | : parseVariableDefinition(item.toString()); 73 | if (def == null) return false; 74 | if (def.variable.name != name) return false; 75 | bool result = true; 76 | 77 | if (type != null) { 78 | result == result && type!.matches(def.type, matchState); 79 | } 80 | 81 | if (defaultValue != null) { 82 | result = 83 | result && defaultValue!.matches(def.defaultValue!.value, matchState); 84 | } 85 | 86 | return result; 87 | } 88 | } 89 | -------------------------------------------------------------------------------- /packages/graphql_parser/test/variable_test.dart: -------------------------------------------------------------------------------- 1 | import 'package:test/test.dart'; 2 | 3 | import 'common.dart'; 4 | 5 | void main() { 6 | test('variables', () { 7 | expect(r'$a', isVariable('a')); 8 | expect(r'$abc', isVariable('abc')); 9 | expect(r'$abc123', isVariable('abc123')); 10 | expect(r'$_', isVariable('_')); 11 | expect(r'$___', isVariable('___')); 12 | expect(r'$_123', isVariable('_123')); 13 | }); 14 | 15 | test('exceptions', () { 16 | var throwsSyntaxError = predicate((dynamic x) { 17 | var parser = parse(x.toString())..parseVariable(); 18 | return parser.errors.isNotEmpty; 19 | }, 'fails to parse variable'); 20 | 21 | expect(r'$', throwsSyntaxError); 22 | }); 23 | } 24 | 25 | Matcher isVariable(String name) => _IsVariable(name); 26 | 27 | class _IsVariable extends Matcher { 28 | final String name; 29 | 30 | _IsVariable(this.name); 31 | 32 | @override 33 | Description describe(Description description) { 34 | return description.add('parses as a variable named "$name"'); 35 | } 36 | 37 | @override 38 | bool matches(item, Map matchState) { 39 | var p = parse(item.toString()); 40 | var v = p.parseVariable(); 41 | return equals(name).matches(v?.name, matchState); 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /packages/graphql_schema/AUTHORS.md: -------------------------------------------------------------------------------- 1 | Primary Authors 2 | =============== 3 | 4 | * __[Thomas Hii](dukefirehawk.apps@gmail.com)__ 5 | 6 | Thomas is the current maintainer of the code base. He has refactored and migrated the 7 | code base to support NNBD. 8 | 9 | * __[Tobe O](thosakwe@gmail.com)__ 10 | 11 | Tobe has written much of the original code prior to NNBD migration. He has moved on and 12 | is no longer involved with the project. 13 | -------------------------------------------------------------------------------- /packages/graphql_schema/CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Change Log 2 | 3 | ## 6.3.0 4 | 5 | * Require Dart >= 3.6 6 | * Updated `lints` to 5.0.0 7 | 8 | ## 6.2.0 9 | 10 | * Require Dart >= 3.3 11 | * Updated `lints` to 4.0.0 12 | 13 | ## 6.1.0 14 | 15 | * Updated `lints` to 3.0.0 16 | * Fixed linter warnings 17 | * Updated repository link 18 | 19 | ## 6.0.0 20 | 21 | * Require dart >= 3.0.x 22 | 23 | ## 5.0.0 24 | 25 | * Require dart >= 2.17.x 26 | 27 | ## 4.0.0 28 | 29 | * Require dart >= 2.16.x 30 | 31 | ## 3.0.0 32 | 33 | * Implemented directives 34 | * Added polymorphic type names (basically type alias for polymorphism) 35 | * Updated to SDK 2.15.x 36 | 37 | ## 2.1.1 38 | 39 | * Fixed bug in enums 40 | 41 | ## 2.1.0 42 | 43 | * Upgraded from `pendantic` to `lints` linter 44 | 45 | ## 2.0.0 46 | 47 | * Migrated to support Dart SDK 2.12.x NNBD 48 | * Rename `graphql_schema` to `graphql_schema2` 49 | 50 | ## 1.0.4 51 | 52 | * Add `convert` method to types, to avoid some annoying generics bugs. 53 | 54 | ## 1.0.3 55 | 56 | * `enumTypeFromStrings` now returns `GraphQLEnumType`. 57 | 58 | ## 1.0.2 59 | 60 | * Added `GraphQLClass()`. 61 | * Added `typeName`. 62 | 63 | ## 1.0.1 64 | 65 | * Dart 2 updates. 66 | -------------------------------------------------------------------------------- /packages/graphql_schema/LICENSE: -------------------------------------------------------------------------------- 1 | BSD 3-Clause License 2 | 3 | Copyright (c) 2021, dukefirehawk.com 4 | All rights reserved. 5 | 6 | Redistribution and use in source and binary forms, with or without 7 | modification, are permitted provided that the following conditions are met: 8 | 9 | 1. Redistributions of source code must retain the above copyright notice, this 10 | list of conditions and the following disclaimer. 11 | 12 | 2. Redistributions in binary form must reproduce the above copyright notice, 13 | this list of conditions and the following disclaimer in the documentation 14 | and/or other materials provided with the distribution. 15 | 16 | 3. Neither the name of the copyright holder nor the names of its 17 | contributors may be used to endorse or promote products derived from 18 | this software without specific prior written permission. 19 | 20 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 21 | AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 22 | IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 23 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE 24 | FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 25 | DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 26 | SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 27 | CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, 28 | OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 29 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 30 | -------------------------------------------------------------------------------- /packages/graphql_schema/README.md: -------------------------------------------------------------------------------- 1 | # GraphQL Schema 2 2 | 3 | ![Pub Version (including pre-releases)](https://img.shields.io/pub/v/graphql_schema2?include_prereleases) 4 | [![Null Safety](https://img.shields.io/badge/null-safety-brightgreen)](https://dart.dev/null-safety) 5 | [![Gitter](https://img.shields.io/gitter/room/nwjs/nw.js.svg)](https://gitter.im/angel_dart/discussion) 6 | [![License](https://img.shields.io/github/license/dart-backend/graphql_dart)](https://github.com/dart-backend/graphql_dart/blob/master/packages/graphql_schema/LICENSE) 7 | [![melos](https://img.shields.io/badge/maintained%20with-melos-f700ff.svg?style=flat-square)](https://github.com/invertase/melos) 8 | 9 | An implementation of GraphQL's type system in Dart. Supports any platform where Dart runs. The decisions made in the design of this library were done to make the experience as similar to the JavaScript reference implementation as possible, and to also correctly implement the official specification. 10 | 11 | Contains functionality to build *all* GraphQL types: 12 | 13 | * `String` 14 | * `Int` 15 | * `Float` 16 | * `Boolean` 17 | * `GraphQLObjectType` 18 | * `GraphQLUnionType` 19 | * `GraphQLEnumType` 20 | * `GraphQLInputObjectType` 21 | * `Date` - ISO-8601 Date string, deserializes to a Dart `DateTime` object 22 | 23 | Of course, for a full description of GraphQL's type system, see the official [GraphQL Specification](https://spec.graphql.org/). Mostly analogous to [graphql-js](https://graphql.org/graphql-js/type/); many names are verbatim. 24 | 25 | ## Usage 26 | 27 | It's easy to define a schema with the [helper functions](#helpers): 28 | 29 | ```dart 30 | final GraphQLSchema todoSchema = GraphQLSchema( 31 | query: objectType('Todo', [ 32 | field('text', graphQLString.nonNullable()), 33 | field('created_at', graphQLDate) 34 | ])); 35 | ``` 36 | 37 | All GraphQL types are generic, in order to leverage Dart's strong typing support. 38 | 39 | ## Serialization 40 | 41 | GraphQL types can `serialize` and `deserialize` input data. The exact implementation of this depends on the type. 42 | 43 | ```dart 44 | var iso8601String = graphQLDate.serialize(DateTime.now()); 45 | var date = graphQLDate.deserialize(iso8601String); 46 | print(date.millisecondsSinceEpoch); 47 | ``` 48 | 49 | ## Validation 50 | 51 | GraphQL types can `validate` input data. 52 | 53 | ```dart 54 | var validation = myType.validate('@root', {...}); 55 | 56 | if (validation.successful) { 57 | doSomething(validation.value); 58 | } else { 59 | print(validation.errors); 60 | } 61 | ``` 62 | 63 | ## Helpers 64 | 65 | * `graphQLSchema` - Create a `GraphQLSchema` 66 | * `objectType` - Create a `GraphQLObjectType` with fields 67 | * `field` - Create a `GraphQLField` with a type/argument/resolver 68 | * `listOf` - Create a `GraphQLListType` with the provided `innerType` 69 | * `inputObjectType` - Creates a `GraphQLInputObjectType` 70 | * `inputField` - Creates a field for a `GraphQLInputObjectType` 71 | 72 | ## Types 73 | 74 | All of the GraphQL scalar types are built in, as well as a `Date` type: 75 | 76 | * `graphQLString` 77 | * `graphQLId` 78 | * `graphQLBoolean` 79 | * `graphQLInt` 80 | * `graphQLFloat` 81 | * `graphQLDate` 82 | 83 | ## Non-Nullable Types 84 | 85 | You can easily make a type non-nullable by calling its `nonNullable` method. 86 | 87 | ## List Types 88 | 89 | Support for list types is also included. Use the `listType` helper for convenience. 90 | 91 | ```dart 92 | /// A non-nullable list of non-nullable integers 93 | listOf(graphQLInt.nonNullable()).nonNullable(); 94 | ``` 95 | 96 | ### Input values and parameters 97 | 98 | Take the following GraphQL query: 99 | 100 | ```graphql 101 | { 102 | anime { 103 | characters(title: "Hunter x Hunter") { 104 | name 105 | age 106 | } 107 | } 108 | } 109 | ``` 110 | 111 | And subsequently, its schema: 112 | 113 | ```graphql 114 | type AnimeQuery { 115 | characters($title: String!): [Character!] 116 | } 117 | 118 | type Character { 119 | name: String 120 | age: Int 121 | } 122 | ``` 123 | 124 | The field `characters` accepts a parameter, `title`. To reproduce this in `package:graphql_schema2`, use `GraphQLFieldInput`: 125 | 126 | ```dart 127 | final GraphQLObjectType queryType = objectType('AnimeQuery', fields: [ 128 | field('characters', 129 | listOf(characterType.nonNullable()), 130 | inputs: [ 131 | new GraphQLFieldInput('title', graphQLString.nonNullable()) 132 | ] 133 | ), 134 | ]); 135 | 136 | final GraphQLObjectType characterType = objectType('Character', fields: [ 137 | field('name', graphQLString), 138 | field('age', graphQLInt), 139 | ]); 140 | ``` 141 | 142 | In the majority of cases where you use GraphQL, you will be delegate the actual fetching of data to a database object, or some asynchronous resolver function. 143 | 144 | `package:graphql_schema2` includes this functionality in the `resolve` property, which is passed a context object and a `Map` of arguments. 145 | 146 | A hypothetical example of the above might be: 147 | 148 | ```dart 149 | var field = field( 150 | 'characters', 151 | graphQLString, 152 | resolve: (_, args) async { 153 | return await myDatabase.findCharacters(args['title']); 154 | }, 155 | ); 156 | ``` 157 | -------------------------------------------------------------------------------- /packages/graphql_schema/analysis_options.yaml: -------------------------------------------------------------------------------- 1 | include: package:lints/recommended.yaml -------------------------------------------------------------------------------- /packages/graphql_schema/example/example.dart: -------------------------------------------------------------------------------- 1 | import 'package:graphql_schema2/graphql_schema2.dart'; 2 | 3 | final GraphQLSchema todoSchema = GraphQLSchema( 4 | queryType: objectType('Todo', fields: [ 5 | field( 6 | 'text', 7 | graphQLString.nonNullable(), 8 | resolve: resolveToNull, 9 | ), 10 | field( 11 | 'created_at', 12 | graphQLDate, 13 | resolve: resolveToNull, 14 | ), 15 | ]), 16 | ); 17 | 18 | void main() { 19 | // Validation 20 | var validation = todoSchema.queryType!.validate( 21 | '@root', 22 | { 23 | 'foo': 'bar', 24 | 'text': null, 25 | 'created_at': 24, 26 | }, 27 | ); 28 | 29 | if (validation.successful) { 30 | print('This is valid data!!!'); 31 | } else { 32 | print('Invalid data.'); 33 | for (var s in validation.errors) { 34 | print(' * $s'); 35 | } 36 | } 37 | 38 | // Serialization 39 | print(todoSchema.queryType!.serialize({ 40 | 'text': 'Clean your room!', 41 | 'created_at': DateTime.now().subtract(Duration(days: 10)) 42 | })); 43 | } 44 | -------------------------------------------------------------------------------- /packages/graphql_schema/graphql_schema.iml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | -------------------------------------------------------------------------------- /packages/graphql_schema/lib/graphql_schema2.dart: -------------------------------------------------------------------------------- 1 | export 'src/schema.dart'; 2 | -------------------------------------------------------------------------------- /packages/graphql_schema/lib/src/argument.dart: -------------------------------------------------------------------------------- 1 | part of 'schema.dart'; 2 | 3 | /// An input to a GraphQL field. This is analogous to a function parameter in Dart. 4 | class GraphQLFieldInput { 5 | /// The name of this field. 6 | final String name; 7 | 8 | /// The type that input values must conform to. 9 | final GraphQLType type; 10 | 11 | /// An optional default value for this field. 12 | final Value? defaultValue; 13 | 14 | /// An optional description for this field. 15 | /// 16 | /// This is useful when documenting your API for consumers like GraphiQL. 17 | final String? description; 18 | 19 | /// If [defaultValue] is `null`, and `null` is a valid value for this parameter, set this to `true`. 20 | final bool defaultsToNull; 21 | 22 | static bool _isInputTypeOrScalar(GraphQLType type) { 23 | if (type is GraphQLInputObjectType) { 24 | return true; 25 | } else if (type is GraphQLUnionType) { 26 | return type.possibleTypes.every(_isInputTypeOrScalar); 27 | } else if (type is GraphQLObjectType) { 28 | return false; 29 | } else if (type is GraphQLNonNullableType) { 30 | return _isInputTypeOrScalar(type.ofType); 31 | } else if (type is GraphQLListType) { 32 | return _isInputTypeOrScalar(type.ofType); 33 | } else { 34 | return true; 35 | } 36 | } 37 | 38 | GraphQLFieldInput(this.name, this.type, 39 | {this.defaultValue, this.defaultsToNull = false, this.description}) { 40 | assert(_isInputTypeOrScalar(type), 41 | 'All inputs to a GraphQL field must either be scalar types, or explicitly marked as INPUT_OBJECT. Call `GraphQLObjectType.asInputObject()` on any object types you are passing as inputs to a field.'); 42 | } 43 | 44 | @override 45 | bool operator ==(other) => 46 | other is GraphQLFieldInput && 47 | other.name == name && 48 | other.type == type && 49 | other.defaultValue == other.defaultValue && 50 | other.defaultsToNull == defaultsToNull && 51 | other.description == description; 52 | 53 | @override 54 | int get hashCode => hash4(name, type, description, defaultValue); 55 | } 56 | -------------------------------------------------------------------------------- /packages/graphql_schema/lib/src/enum.dart: -------------------------------------------------------------------------------- 1 | part of 'schema.dart'; 2 | 3 | /// Shorthand for building a [GraphQLEnumType]. 4 | GraphQLEnumType enumType(String name, Map values, 5 | {String? description}) { 6 | final len = values.keys.length; 7 | 8 | return GraphQLEnumType( 9 | name, 10 | [ 11 | for (var i = 0; i < len; i++) 12 | GraphQLEnumValue( 13 | values.keys.elementAt(i), values.values.elementAt(i)), 14 | ], 15 | description: description); 16 | } 17 | 18 | /// Shorthand for building a [GraphQLEnumType] where all the possible values 19 | /// are mapped to Dart strings. 20 | GraphQLEnumType enumTypeFromStrings(String name, List values, 21 | {String? description}) { 22 | return GraphQLEnumType( 23 | name, values.map((s) => GraphQLEnumValue(s, s)).toList(), 24 | description: description); 25 | } 26 | 27 | /// A [GraphQLType] with only a predetermined number of possible values. 28 | /// 29 | /// Though these are serialized as strings, they carry special meaning with a type system. 30 | class GraphQLEnumType extends GraphQLScalarType 31 | with _NonNullableMixin { 32 | /// The name of this enum type. 33 | @override 34 | final String name; 35 | 36 | /// The defined set of possible values for this type. 37 | /// 38 | /// No other values will be accepted than the ones you define. 39 | final List> values; 40 | 41 | /// A description of this enum type, for tools like GraphiQL. 42 | @override 43 | final String? description; 44 | 45 | GraphQLEnumType(this.name, this.values, {this.description}); 46 | 47 | @override 48 | String serialize(Value value) { 49 | //if (value == null) return null; 50 | return values.firstWhere((v) => v.value == value).name; 51 | } 52 | 53 | @override 54 | String? convert(value) => serialize(value); 55 | 56 | @override 57 | Value deserialize(String serialized) { 58 | return values.firstWhere((v) => v.name == serialized).value; 59 | } 60 | 61 | @override 62 | ValidationResult validate(String key, String input) { 63 | if (!values.any((v) => v.name == input)) { 64 | //if (input == null) { 65 | // return new ValidationResult._failure( 66 | // ['The enum "$name" does not accept null values.']); 67 | //} 68 | 69 | return ValidationResult._failure( 70 | ['"$input" is not a valid value for the enum "$name".']); 71 | } 72 | 73 | return ValidationResult._ok(input); 74 | } 75 | 76 | @override 77 | bool operator ==(other) => 78 | other is GraphQLEnumType && 79 | other.name == name && 80 | other.description == description && 81 | const ListEquality().equals(other.values, values); 82 | 83 | @override 84 | int get hashCode => hash3(name, description, values); 85 | 86 | @override 87 | GraphQLType coerceToInputObject() => this; 88 | } 89 | 90 | /// A known value of a [GraphQLEnumType]. 91 | /// 92 | /// In practice, you might not directly call this constructor very often. 93 | class GraphQLEnumValue { 94 | /// The name of this value. 95 | final String name; 96 | 97 | /// The Dart value associated with enum values bearing the given [name]. 98 | final Value value; 99 | 100 | /// An optional description of this value; useful for tools like GraphiQL. 101 | final String? description; 102 | 103 | /// The reason, if any, that this value was deprecated, if it indeed is deprecated. 104 | final String? deprecationReason; 105 | 106 | GraphQLEnumValue(this.name, this.value, 107 | {this.description, this.deprecationReason}); 108 | 109 | /// Returns `true` if this value has a [deprecationReason]. 110 | bool get isDeprecated => deprecationReason != null; 111 | 112 | @override 113 | bool operator ==(other) => 114 | other is GraphQLEnumValue && 115 | other.name == name && 116 | other.value == value && 117 | other.description == description && 118 | other.deprecationReason == deprecationReason; 119 | 120 | @override 121 | int get hashCode => hash4(name, value, description, deprecationReason); 122 | } 123 | -------------------------------------------------------------------------------- /packages/graphql_schema/lib/src/field.dart: -------------------------------------------------------------------------------- 1 | part of 'schema.dart'; 2 | 3 | /// Typedef for a function that resolves the value of a [GraphQLObjectField], whether asynchronously or not. 4 | typedef GraphQLFieldResolver = FutureOr Function( 5 | Serialized serialized, Map argumentValues); 6 | 7 | /// A field on a [GraphQLObjectType]. 8 | /// 9 | /// It can have input values and additional documentation, and explicitly declares it shape 10 | /// within the schema. 11 | class GraphQLObjectField { 12 | /// The list of input values this field accepts, if any. 13 | final List inputs = []; 14 | 15 | /// The name of this field in serialized input. 16 | final String name; 17 | 18 | /// A function used to evaluate the value of this field, with respect to an arbitrary Dart value. 19 | final GraphQLFieldResolver? resolve; 20 | 21 | /// The [GraphQLType] associated with values that this field's [resolve] callback returns. 22 | final GraphQLType type; 23 | 24 | /// An optional description of this field; useful for tools like GraphiQL. 25 | final String? description; 26 | 27 | /// The reason that this field, if it is deprecated, was deprecated. 28 | final String? deprecationReason; 29 | 30 | GraphQLObjectField(this.name, this.type, 31 | {Iterable arguments = const [], 32 | required this.resolve, 33 | this.deprecationReason, 34 | this.description}) { 35 | // assert(type != null, 'GraphQL fields must specify a `type`.'); 36 | // assert( 37 | // resolve != null, 'GraphQL fields must specify a `resolve` callback.'); 38 | // this.inputs.addAll(arguments ?? []); 39 | inputs.addAll(arguments); 40 | } 41 | 42 | /// Returns `true` if this field has a [deprecationReason]. 43 | bool get isDeprecated => deprecationReason?.isNotEmpty == true; 44 | 45 | FutureOr serialize(Value value) { 46 | return type.serialize(value); 47 | } 48 | 49 | FutureOr? deserialize(Serialized serialized, 50 | [Map argumentValues = const {}]) { 51 | if (resolve != null) return resolve!(serialized, argumentValues); 52 | return type.deserialize(serialized); 53 | } 54 | 55 | @override 56 | bool operator ==(other) => 57 | other is GraphQLObjectField && 58 | other.name == name && 59 | other.deprecationReason == deprecationReason && 60 | other.type == type && 61 | other.resolve == resolve && 62 | const ListEquality().equals(other.inputs, inputs); 63 | 64 | @override 65 | int get hashCode => hash4(name, deprecationReason, type, resolve); 66 | } 67 | -------------------------------------------------------------------------------- /packages/graphql_schema/lib/src/gen.dart: -------------------------------------------------------------------------------- 1 | part of 'schema.dart'; 2 | 3 | /// Shorthand for generating a [GraphQLObjectType]. 4 | GraphQLObjectType objectType(String name, 5 | {String? description, 6 | bool isInterface = false, 7 | Iterable fields = const [], 8 | Iterable interfaces = const [], 9 | Iterable subs = const [], 10 | String? polymorphicName}) { 11 | var obj = GraphQLObjectType(name, description, 12 | isInterface: isInterface, polymorphicName: polymorphicName) 13 | ..fields.addAll(fields); 14 | 15 | if (interfaces.isNotEmpty == true) { 16 | for (var i in interfaces) { 17 | obj.inheritFrom(i); 18 | } 19 | } 20 | 21 | if (subs.isNotEmpty) { 22 | for (final sub in subs) { 23 | sub.inheritFrom(obj); 24 | } 25 | } 26 | 27 | return obj; 28 | } 29 | 30 | /// Shorthand for generating a [GraphQLObjectField]. 31 | GraphQLObjectField field( 32 | String name, GraphQLType type, 33 | {Iterable> inputs = const [], 34 | GraphQLFieldResolver? resolve, 35 | String? deprecationReason, 36 | String? description}) { 37 | return GraphQLObjectField(name, type, 38 | arguments: inputs, 39 | resolve: resolve, 40 | description: description, 41 | deprecationReason: deprecationReason); 42 | } 43 | 44 | /// Shorthand for generating a [GraphQLInputObjectType]. 45 | GraphQLInputObjectType inputObjectType(String name, 46 | {String? description, 47 | Iterable inputFields = const []}) { 48 | return GraphQLInputObjectType(name, 49 | description: description, inputFields: inputFields); 50 | } 51 | 52 | /// Shorthand for generating a [GraphQLInputObjectField]. 53 | GraphQLInputObjectField inputField( 54 | String name, GraphQLType type, 55 | {String? description, T? defaultValue}) { 56 | return GraphQLInputObjectField(name, type, 57 | description: description, defaultValue: defaultValue); 58 | } 59 | -------------------------------------------------------------------------------- /packages/graphql_schema/lib/src/union.dart: -------------------------------------------------------------------------------- 1 | part of 'schema.dart'; 2 | 3 | /// A special [GraphQLType] that indicates that an input value may be valid against one or more [possibleTypes]. 4 | /// 5 | /// All provided types must be [GraphQLObjectType]s. 6 | class GraphQLUnionType 7 | extends GraphQLType, Map> 8 | with _NonNullableMixin, Map> { 9 | /// The name of this type. 10 | @override 11 | final String name; 12 | 13 | /// A list of all types that conform to this union. 14 | final List possibleTypes = []; 15 | 16 | GraphQLUnionType( 17 | this.name, 18 | Iterable, Map>> 19 | possibleTypes) { 20 | assert(possibleTypes.every((t) => t is GraphQLObjectType), 21 | 'The member types of a Union type must all be Object base types; Scalar, Interface and Union types must not be member types of a Union. Similarly, wrapping types must not be member types of a Union.'); 22 | assert(possibleTypes.isNotEmpty, 23 | 'A Union type must define one or more member types.'); 24 | 25 | for (var t in possibleTypes.toSet()) { 26 | this.possibleTypes.add(t as GraphQLObjectType); 27 | } 28 | } 29 | 30 | @override 31 | String get description => possibleTypes.map((t) => t.name).join(' | '); 32 | 33 | @override 34 | GraphQLType, Map> 35 | coerceToInputObject() { 36 | return GraphQLUnionType( 37 | '${name}Input', possibleTypes.map((t) => t.coerceToInputObject())); 38 | } 39 | 40 | @override 41 | Map serialize(Map value) { 42 | for (var type in possibleTypes) { 43 | try { 44 | if (type.validate('@root', value).successful) { 45 | return type.serialize(value); 46 | } 47 | } catch (_) {} 48 | } 49 | 50 | throw ArgumentError(); 51 | } 52 | 53 | @override 54 | Map deserialize(Map serialized) { 55 | for (var type in possibleTypes) { 56 | try { 57 | return type.deserialize(serialized); 58 | } catch (_) {} 59 | } 60 | 61 | throw ArgumentError(); 62 | } 63 | 64 | @override 65 | ValidationResult> validate( 66 | String key, Map input) { 67 | var errors = []; 68 | 69 | for (var type in possibleTypes) { 70 | var result = type.validate(key, input); 71 | 72 | if (result.successful) { 73 | return result; 74 | } else { 75 | errors.addAll(result.errors); 76 | } 77 | } 78 | 79 | return ValidationResult>._failure(errors); 80 | } 81 | 82 | @override 83 | bool operator ==(other) => 84 | other is GraphQLUnionType && 85 | other.name == name && 86 | other.description == description && 87 | const ListEquality() 88 | .equals(other.possibleTypes, possibleTypes); 89 | 90 | @override 91 | int get hashCode => hash3(name, description, possibleTypes); 92 | } 93 | -------------------------------------------------------------------------------- /packages/graphql_schema/lib/src/validation_result.dart: -------------------------------------------------------------------------------- 1 | part of 'schema.dart'; 2 | 3 | /// Represents the result of asserting an input [value] against a [GraphQLType]. 4 | 5 | class ValidationResult { 6 | /// `true` if there were no errors during validation. 7 | final bool successful; 8 | 9 | /// The input value passed to whatever caller invoked validation. 10 | final Value? value; 11 | 12 | /// A list of errors that caused validation to fail. 13 | final List errors; 14 | 15 | //ValidationResult._(this.successful, this.value, this.errors); 16 | 17 | ValidationResult._ok(this.value) 18 | : errors = [], 19 | successful = true; 20 | 21 | ValidationResult._failure(this.errors) 22 | : value = null, 23 | successful = false; 24 | 25 | // ValidationResult _asFailure() { 26 | // return new ValidationResult._(false, value, errors); 27 | // } 28 | } 29 | -------------------------------------------------------------------------------- /packages/graphql_schema/melos_graphql_schema2.iml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | -------------------------------------------------------------------------------- /packages/graphql_schema/pubspec.yaml: -------------------------------------------------------------------------------- 1 | name: graphql_schema2 2 | version: 6.3.0 3 | description: An implementation of GraphQL's type system in Dart. Basis of graphql_server2. 4 | homepage: https://angel3-framework.web.app/ 5 | repository: https://github.com/dart-backend/graphql_dart/tree/master/packages/graphql_schema 6 | environment: 7 | sdk: '>=3.6.0 <4.0.0' 8 | dependencies: 9 | collection: ^1.17.0 10 | meta: ^1.9.0 11 | source_span: ^1.10.0 12 | quiver: ^3.2.0 13 | dev_dependencies: 14 | test: ^1.24.0 15 | lints: ^5.0.0 16 | -------------------------------------------------------------------------------- /packages/graphql_schema/test/common.dart: -------------------------------------------------------------------------------- 1 | import 'package:graphql_schema2/graphql_schema2.dart'; 2 | 3 | final GraphQLObjectType pokemonType = objectType('Pokemon', fields: [ 4 | field('species', graphQLString), 5 | field('catch_date', graphQLDate) 6 | ]); 7 | 8 | final GraphQLObjectType trainerType = 9 | objectType('Trainer', fields: [field('name', graphQLString)]); 10 | 11 | final GraphQLObjectType pokemonRegionType = objectType('PokemonRegion', 12 | fields: [ 13 | field('trainer', trainerType), 14 | field('pokemon_species', listOf(pokemonType)) 15 | ]); 16 | -------------------------------------------------------------------------------- /packages/graphql_schema/test/equality_test.dart: -------------------------------------------------------------------------------- 1 | import 'package:graphql_schema2/graphql_schema2.dart'; 2 | import 'package:test/test.dart'; 3 | 4 | /// Note: this doesn't test for scalar types, which are final, and therefore use built-in equality. 5 | void main() { 6 | group('equality', () { 7 | test('enums', () { 8 | expect(enumTypeFromStrings('A', ['B', 'C']), 9 | enumTypeFromStrings('A', ['B', 'C'])); 10 | expect(enumTypeFromStrings('A', ['B', 'C']), 11 | isNot(enumTypeFromStrings('B', ['B', 'C']))); 12 | }); 13 | 14 | test('objects', () { 15 | expect( 16 | objectType('B', fields: [ 17 | field('b', graphQLString.nonNullable()), 18 | ]), 19 | objectType('B', fields: [ 20 | field('b', graphQLString.nonNullable()), 21 | ]), 22 | ); 23 | 24 | expect( 25 | objectType('B', fields: [ 26 | field('b', graphQLString.nonNullable()), 27 | ]), 28 | isNot(objectType('BD', fields: [ 29 | field('b', graphQLString.nonNullable()), 30 | ])), 31 | ); 32 | 33 | expect( 34 | objectType('B', fields: [ 35 | field('b', graphQLString.nonNullable()), 36 | ]), 37 | isNot(objectType('B', fields: [ 38 | field('ba', graphQLString.nonNullable()), 39 | ])), 40 | ); 41 | 42 | expect( 43 | objectType('B', fields: [ 44 | field('b', graphQLString.nonNullable()), 45 | ]), 46 | isNot(objectType('B', fields: [ 47 | field('a', graphQLFloat.nonNullable()), 48 | ])), 49 | ); 50 | }); 51 | 52 | test('input type', () {}); 53 | 54 | test('union type', () { 55 | expect( 56 | GraphQLUnionType('A', [ 57 | objectType('B', fields: [ 58 | field('b', graphQLString.nonNullable()), 59 | ]), 60 | objectType('C', fields: [ 61 | field('c', graphQLString.nonNullable()), 62 | ]), 63 | ]), 64 | GraphQLUnionType('A', [ 65 | objectType('B', fields: [ 66 | field('b', graphQLString.nonNullable()), 67 | ]), 68 | objectType('C', fields: [ 69 | field('c', graphQLString.nonNullable()), 70 | ]), 71 | ]), 72 | ); 73 | 74 | expect( 75 | GraphQLUnionType('A', [ 76 | objectType('B', fields: [ 77 | field('b', graphQLString.nonNullable()), 78 | ]), 79 | objectType('C', fields: [ 80 | field('c', graphQLString.nonNullable()), 81 | ]), 82 | ]), 83 | isNot(GraphQLUnionType('AA', [ 84 | objectType('B', fields: [ 85 | field('b', graphQLString.nonNullable()), 86 | ]), 87 | objectType('C', fields: [ 88 | field('c', graphQLString.nonNullable()), 89 | ]), 90 | ])), 91 | ); 92 | 93 | expect( 94 | GraphQLUnionType('A', [ 95 | objectType('BB', fields: [ 96 | field('b', graphQLString.nonNullable()), 97 | ]), 98 | objectType('C', fields: [ 99 | field('c', graphQLString.nonNullable()), 100 | ]), 101 | ]), 102 | isNot(GraphQLUnionType('AA', [ 103 | objectType('BDD', fields: [ 104 | field('b', graphQLString.nonNullable()), 105 | ]), 106 | objectType('C', fields: [ 107 | field('c', graphQLString.nonNullable()), 108 | ]), 109 | ])), 110 | ); 111 | }); 112 | }); 113 | } 114 | -------------------------------------------------------------------------------- /packages/graphql_schema/test/inheritance_test.dart: -------------------------------------------------------------------------------- 1 | import 'package:graphql_schema2/graphql_schema2.dart'; 2 | import 'package:test/test.dart'; 3 | 4 | void main() { 5 | group('interface', () { 6 | var a = objectType( 7 | 'A', 8 | isInterface: true, 9 | fields: [ 10 | field('text', graphQLString.nonNullable()), 11 | ], 12 | ); 13 | 14 | var b = objectType( 15 | 'B', 16 | isInterface: true, 17 | interfaces: [a], 18 | fields: [ 19 | field('text', graphQLString.nonNullable()), 20 | ], 21 | ); 22 | 23 | var c = objectType( 24 | 'C', 25 | isInterface: true, 26 | interfaces: [b], 27 | fields: [ 28 | field('text', graphQLString.nonNullable()), 29 | ], 30 | ); 31 | 32 | test('child implements parent', () { 33 | expect(b.isImplementationOf(a), true); 34 | expect(c.isImplementationOf(b), true); 35 | }); 36 | 37 | test('parent does not implement child', () { 38 | expect(a.isImplementationOf(b), false); 39 | }); 40 | 41 | test('child interfaces contains parent', () { 42 | expect(b.interfaces, contains(a)); 43 | expect(c.interfaces, contains(b)); 44 | }); 45 | 46 | test('parent possibleTypes contains child', () { 47 | expect(a.possibleTypes, contains(b)); 48 | expect(b.possibleTypes, contains(c)); 49 | }); 50 | 51 | test('grandchild implements grandparent', () { 52 | expect(c.isImplementationOf(a), true); 53 | }); 54 | 55 | test('grandparent does not implement grandchild', () { 56 | expect(a.isImplementationOf(c), false); 57 | }); 58 | 59 | test('grandchild interfaces contains grandparent', () { 60 | expect(c.interfaces, contains(a)); 61 | }); 62 | 63 | test('grandparent possibleTypes contains grandchild', () { 64 | expect(a.possibleTypes, contains(c)); 65 | }); 66 | }); 67 | } 68 | -------------------------------------------------------------------------------- /packages/graphql_schema/test/serialize_test.dart: -------------------------------------------------------------------------------- 1 | import 'package:graphql_schema2/graphql_schema2.dart'; 2 | import 'package:test/test.dart'; 3 | 4 | import 'common.dart'; 5 | 6 | void main() { 7 | test('int', () { 8 | expect(graphQLInt.serialize(23), 23); 9 | }); 10 | 11 | test('float', () { 12 | expect(graphQLFloat.serialize(23.0), 23.0); 13 | }); 14 | 15 | test('bool', () { 16 | expect(graphQLBoolean.serialize(true), true); 17 | }); 18 | 19 | test('string', () { 20 | expect(graphQLString.serialize('a'), 'a'); 21 | }); 22 | 23 | test('enum', () { 24 | var response = enumTypeFromStrings('Response', ['YES', 'NO']); 25 | expect(response.serialize('YES'), 'YES'); 26 | }); 27 | 28 | test('enum only serializes correct values', () { 29 | var response = enumTypeFromStrings('Response', ['YES', 'NO']); 30 | expect(() => response.serialize('MAYBE'), throwsStateError); 31 | }); 32 | 33 | test('date', () { 34 | var now = DateTime.now(); 35 | expect(graphQLDate.serialize(now), now.toIso8601String()); 36 | }); 37 | 38 | test('list', () { 39 | expect(listOf(graphQLString).serialize(['foo', 'bar']), ['foo', 'bar']); 40 | 41 | var today = DateTime.now(); 42 | var tomorrow = today.add(Duration(days: 1)); 43 | expect(listOf(graphQLDate).serialize([today, tomorrow]), 44 | [today.toIso8601String(), tomorrow.toIso8601String()]); 45 | }); 46 | 47 | group('input object', () { 48 | var type = inputObjectType( 49 | 'Foo', 50 | inputFields: [ 51 | inputField('bar', graphQLString.nonNullable()), 52 | inputField('baz', graphQLFloat.nonNullable()), 53 | ], 54 | ); 55 | 56 | test('serializes valid input', () { 57 | expect( 58 | type.serialize({'bar': 'a', 'baz': 2.0}), {'bar': 'a', 'baz': 2.0}); 59 | }); 60 | }); 61 | 62 | test('object', () { 63 | var catchDate = DateTime.now(); 64 | 65 | var pikachu = {'species': 'Pikachu', 'catch_date': catchDate}; 66 | 67 | expect(pokemonType.serialize(pikachu), 68 | {'species': 'Pikachu', 'catch_date': catchDate.toIso8601String()}); 69 | }); 70 | 71 | test('union type lets any of its types serialize', () { 72 | var typeType = enumTypeFromStrings('Type', [ 73 | 'FIRE', 74 | 'WATER', 75 | 'GRASS', 76 | ]); 77 | 78 | var pokemonType = objectType('Pokémon', fields: [ 79 | field( 80 | 'name', 81 | graphQLString.nonNullable(), 82 | ), 83 | field( 84 | 'type', 85 | typeType, 86 | ), 87 | ]); 88 | 89 | var digimonType = objectType( 90 | 'Digimon', 91 | fields: [ 92 | field('size', graphQLFloat.nonNullable()), 93 | ], 94 | ); 95 | 96 | var u = GraphQLUnionType('Monster', [pokemonType, digimonType]); 97 | 98 | expect(u.serialize({'size': 10.0}), {'size': 10.0}); 99 | expect(u.serialize({'name': 'Charmander', 'type': 'FIRE'}), 100 | {'name': 'Charmander', 'type': 'FIRE'}); 101 | }); 102 | 103 | test('nested object', () { 104 | var pikachuDate = DateTime.now(), 105 | charizardDate = pikachuDate.subtract(Duration(days: 10)); 106 | 107 | var pikachu = {'species': 'Pikachu', 'catch_date': pikachuDate}; 108 | var charizard = {'species': 'Charizard', 'catch_date': charizardDate}; 109 | 110 | var trainer = {'name': 'Tobe O'}; 111 | 112 | var region = pokemonRegionType.serialize({ 113 | 'trainer': trainer, 114 | 'pokemon_species': [pikachu, charizard] 115 | }); 116 | print(region); 117 | 118 | expect(region, { 119 | 'trainer': trainer, 120 | 'pokemon_species': [ 121 | {'species': 'Pikachu', 'catch_date': pikachuDate.toIso8601String()}, 122 | {'species': 'Charizard', 'catch_date': charizardDate.toIso8601String()} 123 | ] 124 | }); 125 | 126 | expect( 127 | () => pokemonRegionType.serialize({ 128 | 'trainer': trainer, 129 | 'DIGIMON_species': [pikachu, charizard] 130 | }), 131 | throwsUnsupportedError); 132 | }); 133 | } 134 | -------------------------------------------------------------------------------- /packages/graphql_schema/test/validation_test.dart: -------------------------------------------------------------------------------- 1 | import 'package:graphql_schema2/graphql_schema2.dart'; 2 | import 'package:test/test.dart'; 3 | 4 | void main() { 5 | var typeType = enumTypeFromStrings('Type', [ 6 | 'FIRE', 7 | 'WATER', 8 | 'GRASS', 9 | ]); 10 | 11 | var pokemonType = objectType('Pokémon', fields: [ 12 | field( 13 | 'name', 14 | graphQLString.nonNullable(), 15 | ), 16 | field( 17 | 'type', 18 | typeType, 19 | ), 20 | ]); 21 | 22 | var isValidPokemon = predicate( 23 | (dynamic x) => 24 | pokemonType.validate('@root', x as Map).successful, 25 | 'is a valid Pokémon'); 26 | 27 | var throwsATypeError = throwsA( 28 | predicate((dynamic x) => x is TypeError, 'is a type or cast error')); 29 | 30 | test('object accepts valid input', () { 31 | expect({'name': 'Charmander', 'type': 'FIRE'}, isValidPokemon); 32 | }); 33 | 34 | test('mismatched scalar type', () { 35 | expect(() => pokemonType.validate('@root', {'name': 24}), throwsATypeError); 36 | }); 37 | 38 | test('empty passed for non-nullable', () { 39 | expect({}, isNot(isValidPokemon)); 40 | }); 41 | 42 | /* TODO: Required fixing 43 | test('null passed for non-nullable', () { 44 | expect({'name': null}, isNot(isValidPokemon)); 45 | }); 46 | */ 47 | 48 | test('rejects extraneous fields', () { 49 | expect({'name': 'Vulpix', 'foo': 'bar'}, isNot(isValidPokemon)); 50 | }); 51 | 52 | test('enum accepts valid value', () { 53 | expect(typeType.validate('@root', 'FIRE').successful, true); 54 | }); 55 | 56 | test('enum rejects invalid value', () { 57 | expect(typeType.validate('@root', 'POISON').successful, false); 58 | }); 59 | 60 | group('union type', () { 61 | var digimonType = objectType( 62 | 'Digimon', 63 | fields: [ 64 | field('size', graphQLFloat.nonNullable()), 65 | ], 66 | ); 67 | 68 | var u = GraphQLUnionType('Monster', [pokemonType, digimonType]); 69 | 70 | test('any of its types returns valid', () { 71 | expect(u.validate('@root', {'size': 32.0}).successful, true); 72 | expect( 73 | u.validate( 74 | '@root', {'name': 'Charmander', 'type': 'FIRE'}).successful, 75 | true); 76 | }); 77 | }); 78 | 79 | group('input object', () { 80 | var type = inputObjectType( 81 | 'Foo', 82 | inputFields: [ 83 | inputField('bar', graphQLString.nonNullable()), 84 | inputField('baz', graphQLFloat.nonNullable()), 85 | ], 86 | ); 87 | 88 | test('accept valid input', () { 89 | expect(type.validate('@root', {'bar': 'a', 'baz': 2.0}).value, 90 | {'bar': 'a', 'baz': 2.0}); 91 | }); 92 | 93 | test('error on missing non-null fields', () { 94 | expect(type.validate('@root', {'bar': 'a'}).successful, false); 95 | }); 96 | 97 | test('error on unrecognized fields', () { 98 | expect( 99 | type.validate( 100 | '@root', {'bar': 'a', 'baz': 2.0, 'franken': 'stein'}).successful, 101 | false); 102 | }); 103 | }); 104 | } 105 | -------------------------------------------------------------------------------- /packages/graphql_server/AUTHORS.md: -------------------------------------------------------------------------------- 1 | Primary Authors 2 | =============== 3 | 4 | * __[Thomas Hii](dukefirehawk.apps@gmail.com)__ 5 | 6 | Thomas is the current maintainer of the code base. He has refactored and migrated the 7 | code base to support NNBD. 8 | 9 | * __[Tobe O](thosakwe@gmail.com)__ 10 | 11 | Tobe has written much of the original code prior to NNBD migration. He has moved on and 12 | is no longer involved with the project. 13 | -------------------------------------------------------------------------------- /packages/graphql_server/CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Change Log 2 | 3 | ## 6.3.0 4 | 5 | * Require Dart >= 3.6 6 | * Updated `lints` to 5.0.0 7 | 8 | ## 6.2.0 9 | 10 | * Require Dart >= 3.3 11 | * Updated `lints` to 4.0.0 12 | 13 | ## 6.1.0 14 | 15 | * Updated `lints` to 3.0.0 16 | * Fixed linter warnings 17 | * Updated repository link 18 | 19 | ## 6.0.0 20 | 21 | * Require dart >= 3.0.x 22 | 23 | ## 5.0.0 24 | 25 | * Require dart >= 2.17.x 26 | 27 | ## 4.0.0 28 | 29 | * Require dart >= 2.16.x 30 | * Updated `angel3_serialise` to 6.x.x 31 | 32 | ## 3.0.0 33 | 34 | * Fixed enum conversion 35 | * Implemented directives 36 | * Implemented jsonpath directive, example: 37 | 38 | ```graphql 39 | // you add the directive at the variable definition: 40 | mutation myQuery($createdId: Int! @jsonpath(path: "$.C0.create.id")) { 41 | // this mutation will create some object and return the new id 42 | C0: SomeNamespace { 43 | create(name: "Some object") { 44 | id 45 | } 46 | } 47 | // this mutation uses the generated $createdId from the json path directive 48 | C1: OtherNamespace { 49 | update(id: 123, relationshipId: $createdId) { 50 | id 51 | } 52 | } 53 | } 54 | // you can optionally declare the variable 55 | { 56 | createdId: 0 // if the jsonpath directive can't resolve a value, 57 | // it will use this (0) instead 58 | } 59 | ``` 60 | 61 | * Added polymorphic names: you can add an alias to a type to use with `on` fragments. This is nice when you have a `__typename` that must be unique like `MyNestedType123` but you would like to use a better name when on fragments: `...on MyType` 62 | * Updated to SDK 2.15.x 63 | 64 | ## 2.1.1 65 | 66 | * Fixed bug in enums 67 | 68 | ## 2.1.0 69 | 70 | * Upgraded from `pendantic` to `lints` linter 71 | 72 | ## 2.0.1 73 | 74 | * Fixed NNBD issues 75 | 76 | ## 2.0.0 77 | 78 | * Migrated to support Dart SDK 2.12.x NNBD 79 | * Rename `graphql_server` to `graphql_server2` 80 | 81 | ## 1.1.0 82 | 83 | * Updates for `package:graphql_parser@1.2.0`. 84 | * Now that variables are `InputValueContext` descendants, handle them the 85 | same way as other values in `coerceArgumentValues`. TLDR - Removed 86 | now-obsolete, variable-specific logic in `coerceArgumentValues`. 87 | * Pass `argumentName`, not `fieldName`, to type validations. 88 | 89 | ## 1.0.3 90 | 91 | * Make field resolution asynchronous. 92 | * Make introspection cycle-safe. 93 | * Thanks @deep-guarav and @micimize! 94 | 95 | ## 1.0.2 96 | 97 | * 98 | 99 | ## 1.0.1 100 | 101 | * Fix a bug where `globalVariables` were not being properly passed 102 | to field resolvers. 103 | 104 | ## 1.0.0 105 | 106 | * Finish testing. 107 | * Add `package:pedantic` fixes. 108 | 109 | ## 1.0.0-rc.0 110 | 111 | * Get the Apollo support working with the latest version of `subscriptions-transport-ws`. 112 | 113 | ## 1.0.0-beta.4 114 | 115 | For some reason, Pub was not including `subscriptions_transport_ws.dart`. 116 | 117 | ## 1.0.0-beta.3 118 | 119 | * Introspection on subscription types (if any). 120 | 121 | ## 1.0.0-beta.2 122 | 123 | * Fix bug where field aliases would not be resolved. 124 | 125 | ## 1.0.0-beta.1 126 | 127 | * Add (currently untested) subscription support. 128 | 129 | ## 1.0.0-beta 130 | 131 | * First release. 132 | -------------------------------------------------------------------------------- /packages/graphql_server/LICENSE: -------------------------------------------------------------------------------- 1 | BSD 3-Clause License 2 | 3 | Copyright (c) 2021, dukefirehawk.com 4 | All rights reserved. 5 | 6 | Redistribution and use in source and binary forms, with or without 7 | modification, are permitted provided that the following conditions are met: 8 | 9 | 1. Redistributions of source code must retain the above copyright notice, this 10 | list of conditions and the following disclaimer. 11 | 12 | 2. Redistributions in binary form must reproduce the above copyright notice, 13 | this list of conditions and the following disclaimer in the documentation 14 | and/or other materials provided with the distribution. 15 | 16 | 3. Neither the name of the copyright holder nor the names of its 17 | contributors may be used to endorse or promote products derived from 18 | this software without specific prior written permission. 19 | 20 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 21 | AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 22 | IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 23 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE 24 | FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 25 | DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 26 | SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 27 | CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, 28 | OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 29 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 30 | -------------------------------------------------------------------------------- /packages/graphql_server/README.md: -------------------------------------------------------------------------------- 1 | # Graphql Server 2 2 | 3 | ![Pub Version (including pre-releases)](https://img.shields.io/pub/v/graphql_server2?include_prereleases) 4 | [![Null Safety](https://img.shields.io/badge/null-safety-brightgreen)](https://dart.dev/null-safety) 5 | [![Gitter](https://img.shields.io/gitter/room/nwjs/nw.js.svg)](https://gitter.im/angel_dart/discussion) 6 | [![License](https://img.shields.io/github/license/dart-backend/graphql_dart)](https://github.com/dart-backend/graphql_dart/blob/master/packages/graphql_server/LICENSE) 7 | 8 | Base package for implementing GraphQL servers. You might prefer [`package:angel3_graphql`](https://pub.dev/packages/angel3_graphql), the fastest way to implement GraphQL backends in Dart. 9 | 10 | `package:graphql_server2` does not require any specific framework, and thus can be used in any Dart project. 11 | 12 | ## Ad-hoc Usage 13 | 14 | The actual querying functionality is handled by the `GraphQL` class, which takes a schema (from `package:graphql_schema2`). In most cases, you'll want to call `parseAndExecute` on some string of GraphQL text. It returns either a `Stream` or `Map`, and can potentially throw a `GraphQLException` (which is JSON-serializable): 15 | 16 | ```dart 17 | try { 18 | var data = await graphQL.parseExecute(responseText); 19 | 20 | if (data is Stream) { 21 | // Handle a subscription somehow... 22 | } else { 23 | response.send({'data': data}); 24 | } 25 | } on GraphQLException catch(e) { 26 | response.send(e.toJson()); 27 | } 28 | ``` 29 | 30 | Consult the API reference for more: [`API Document`](https://pub.dev/documentation/graphql_server2/latest/graphql_server2/GraphQL/parseAndExecute.html) 31 | 32 | If you're looking for functionality like `graphQLHttp` in `graphql-js`, that is not included in this package, because it is typically specific to the framework/platform you are using. The `graphQLHttp` implementation in `package:angel3_graphql` is a good example: [`graphQLHttp source code`](https://github.com/dart-backend/graphql_dart/tree/master/angel_graphql/lib/src/graphql_http.dart) 33 | 34 | ## Subscriptions 35 | 36 | GraphQL queries involving `subscription` operations can return a `Stream`. Ultimately, the transport for relaying subscription events to clients is not specified in the GraphQL spec, so it's up to you. 37 | 38 | Note that in a schema like this: 39 | 40 | ```graphql 41 | type TodoSubscription { 42 | onTodo: TodoAdded! 43 | } 44 | 45 | type TodoAdded { 46 | id: ID! 47 | text: String! 48 | isComplete: Bool 49 | } 50 | ``` 51 | 52 | Your Dart schema's resolver for `onTodo` should be a `Map` *containing an `onTodo` key*: 53 | 54 | ```dart 55 | field( 56 | 'onTodo', 57 | todoAddedType, 58 | resolve: (_, __) { 59 | return someStreamOfTodos() 60 | .map((todo) => {'onTodo': todo}); 61 | }, 62 | ); 63 | ``` 64 | 65 | For the purposes of reusing existing tooling (i.e. JS clients, etc.), `package:graphql_server2` rolls with an implementation of Apollo's 66 | `subscriptions-transport-ws` spec. 67 | 68 | **NOTE: At this point, Apollo's spec is extremely out-of-sync with the protocol their client actually expects.** 69 | **See the following issue to track this:** 70 | **** 71 | 72 | The implementation is built on `package:stream_channel`, and therefore can be used on any two-way transport, whether it is WebSockets, TCP sockets, Isolates, or otherwise. 73 | 74 | Users of this package are expected to extend the `Server` abstract class. `Server` will handle the transport and communication, but again, ultimately, emitting subscription events is up to your implementation. 75 | 76 | Here's a snippet from `graphQLWS` in `package:angel3_graphql`. It runs within the context of one single request: 77 | 78 | ```dart 79 | var channel = IOWebSocketChannel(socket); 80 | var client = stw.RemoteClient(channel.cast()); 81 | var server = 82 | _GraphQLWSServer(client, graphQL, req, res, keepAliveInterval); 83 | await server.done; 84 | ``` 85 | 86 | See `graphQLWS` in `package:angel3_graphql` for a good example: [`graphQLWS source code`]( 87 | https://github.com/dart-backend/graphql_dart/tree/master/angel_graphql/lib/src/graphql_ws.dart) 88 | 89 | ## Introspection 90 | 91 | Introspection of a GraphQL schema allows clients to query the schema itself, and get information about the response the server expects. The `GraphQL` class handles this automatically, so you don't have to write any code for it. 92 | 93 | However, you can call the `reflectSchema` method to manually reflect a schema: [`API Document`](https://pub.dev/documentation/graphql_server2/latest/introspection/reflectSchema.html) 94 | 95 | ## Mirrors Usage 96 | 97 | By default, `dart:mirrors` is not required, but it can be optionally used. 98 | 99 | The `mirrorsFieldResolver` can resolve fields from concrete objects, instead of you first having to serialize them: [`API Document`](https://pub.dev/documentation/graphql_server2/latest/graphql_server2.mirrorsmirrorsFieldResolver.html) 100 | 101 | You can also use `convertDartType` to convert a concrete Dart type into a `GraphQLType`. However, the ideal choice is `package:graphql_generator2`. 102 | 103 | * [`API Document`](https://pub.dev/documentation/graphql_server2/latest/graphql_server2/mirrors/convertDartType.html) 104 | * [`package:graphql_generator2`](https://pub.dev/packages/graphql_generator2) 105 | -------------------------------------------------------------------------------- /packages/graphql_server/analysis_options.yaml: -------------------------------------------------------------------------------- 1 | include: package:lints/recommended.yaml -------------------------------------------------------------------------------- /packages/graphql_server/example/main.dart: -------------------------------------------------------------------------------- 1 | import 'package:graphql_schema2/graphql_schema2.dart'; 2 | import 'package:graphql_server2/graphql_server2.dart'; 3 | import 'package:test/test.dart'; 4 | 5 | void main() { 6 | test('single element', () async { 7 | var todoType = objectType('todo', fields: [ 8 | field( 9 | 'text', 10 | graphQLString, 11 | resolve: (obj, args) => obj.text, 12 | ), 13 | field( 14 | 'completed', 15 | graphQLBoolean, 16 | resolve: (obj, args) => obj.completed, 17 | ), 18 | ]); 19 | 20 | var schema = graphQLSchema( 21 | queryType: objectType('api', fields: [ 22 | field( 23 | 'todos', 24 | listOf(todoType), 25 | resolve: (_, __) => [ 26 | Todo( 27 | text: 'Clean your room!', 28 | completed: false, 29 | ) 30 | ], 31 | ), 32 | ]), 33 | ); 34 | 35 | var graphql = GraphQL(schema); 36 | var result = await graphql.parseAndExecute('{ todos { text } }'); 37 | 38 | print(result); 39 | expect(result, { 40 | 'todos': [ 41 | {'text': 'Clean your room!'} 42 | ] 43 | }); 44 | }); 45 | } 46 | 47 | class Todo { 48 | final String? text; 49 | final bool? completed; 50 | 51 | Todo({this.text, this.completed}); 52 | } 53 | -------------------------------------------------------------------------------- /packages/graphql_server/graphql_server.iml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | -------------------------------------------------------------------------------- /packages/graphql_server/lib/src/apollo/remote_client.dart: -------------------------------------------------------------------------------- 1 | import 'dart:async'; 2 | import 'package:stream_channel/stream_channel.dart'; 3 | import 'transport.dart'; 4 | 5 | class RemoteClient extends StreamChannelMixin { 6 | final StreamChannel channel; 7 | final StreamChannelController _ctrl = 8 | StreamChannelController(); 9 | 10 | RemoteClient.withoutJson(this.channel) { 11 | _ctrl.local.stream 12 | .map((m) => m.toJson()) 13 | .cast() 14 | .forEach(channel.sink.add); 15 | channel.stream.listen((m) { 16 | _ctrl.local.sink.add(OperationMessage.fromJson(m)); 17 | }); 18 | } 19 | 20 | RemoteClient(StreamChannel channel) 21 | : this.withoutJson(jsonDocument.bind(channel).cast()); 22 | @override 23 | StreamSink get sink => _ctrl.foreign.sink; 24 | 25 | @override 26 | Stream get stream => _ctrl.foreign.stream; 27 | 28 | void close() { 29 | channel.sink.close(); 30 | _ctrl.local.sink.close(); 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /packages/graphql_server/lib/src/apollo/server.dart: -------------------------------------------------------------------------------- 1 | import 'dart:async'; 2 | import 'remote_client.dart'; 3 | import 'transport.dart'; 4 | 5 | abstract class Server { 6 | final RemoteClient client; 7 | final Duration? keepAliveInterval; 8 | final Completer _done = Completer(); 9 | StreamSubscription? _sub; 10 | bool _init = false; 11 | Timer? _timer; 12 | 13 | Future get done => _done.future; 14 | 15 | Server(this.client, {this.keepAliveInterval}) { 16 | _sub = client.stream.listen( 17 | (msg) async { 18 | if ((msg.type == OperationMessage.gqlConnectionInit) && !_init) { 19 | try { 20 | Map? connectionParams; 21 | if (msg.payload is Map) { 22 | connectionParams = msg.payload as Map?; 23 | } else if (msg.payload != null) { 24 | throw FormatException( 25 | '${msg.type} payload must be a map (object).'); 26 | } 27 | 28 | var connect = await onConnect(client, connectionParams); 29 | if (!connect) throw false; 30 | _init = true; 31 | client.sink 32 | .add(OperationMessage(OperationMessage.gqlConnectionAck)); 33 | 34 | if (keepAliveInterval != null) { 35 | client.sink.add( 36 | OperationMessage(OperationMessage.gqlConnectionKeepAlive)); 37 | _timer ??= Timer.periodic(keepAliveInterval!, (timer) { 38 | client.sink.add(OperationMessage( 39 | OperationMessage.gqlConnectionKeepAlive)); 40 | }); 41 | } 42 | } catch (e) { 43 | if (e == false) { 44 | _reportError('The connection was rejected.'); 45 | } else { 46 | _reportError(e.toString()); 47 | } 48 | } 49 | } else if (_init) { 50 | if (msg.type == OperationMessage.gqlStart) { 51 | if (msg.id == null) { 52 | throw FormatException('${msg.type} id is required.'); 53 | } 54 | if (msg.payload == null) { 55 | throw FormatException('${msg.type} payload is required.'); 56 | } else if (msg.payload is! Map) { 57 | throw FormatException( 58 | '${msg.type} payload must be a map (object).'); 59 | } 60 | var payload = msg.payload as Map; 61 | var query = payload['query']; 62 | var variables = payload['variables']; 63 | var operationName = payload['operationName']; 64 | if (query == null || query is! String) { 65 | throw FormatException( 66 | '${msg.type} payload must contain a string named "query".'); 67 | } 68 | if (variables != null && variables is! Map) { 69 | throw FormatException( 70 | '${msg.type} payload\'s "variables" field must be a map (object).'); 71 | } 72 | if (operationName != null && operationName is! String) { 73 | throw FormatException( 74 | '${msg.type} payload\'s "operationName" field must be a string.'); 75 | } 76 | var result = await onOperation( 77 | msg.id, 78 | query, 79 | (variables as Map?)?.cast(), 80 | operationName as String?); 81 | var data = result.data; 82 | 83 | if (result.errors.isNotEmpty) { 84 | client.sink.add(OperationMessage(OperationMessage.gqlData, 85 | id: msg.id, payload: {'errors': result.errors.toList()})); 86 | } else { 87 | if (data is Map && 88 | data.keys.length == 1 && 89 | data.containsKey('data')) { 90 | data = data['data']; 91 | } 92 | 93 | if (data is Stream) { 94 | await for (var event in data) { 95 | if (event is Map && 96 | event.keys.length == 1 && 97 | event.containsKey('data')) { 98 | event = event['data']; 99 | } 100 | client.sink.add(OperationMessage(OperationMessage.gqlData, 101 | id: msg.id, payload: {'data': event})); 102 | } 103 | } else { 104 | client.sink.add(OperationMessage(OperationMessage.gqlData, 105 | id: msg.id, payload: {'data': data})); 106 | } 107 | } 108 | 109 | // c.complete(); 110 | client.sink.add( 111 | OperationMessage(OperationMessage.gqlComplete, id: msg.id)); 112 | } else if (msg.type == OperationMessage.gqlConnectionTerminate) { 113 | await _sub?.cancel(); 114 | } 115 | } 116 | }, 117 | onError: _done.completeError, 118 | onDone: () { 119 | _done.complete(); 120 | _timer?.cancel(); 121 | }); 122 | } 123 | 124 | void _reportError(String message) { 125 | client.sink.add(OperationMessage(OperationMessage.gqlConnectionError, 126 | payload: {'message': message})); 127 | } 128 | 129 | FutureOr onConnect(RemoteClient client, [Map? connectionParams]); 130 | 131 | FutureOr onOperation(String? id, String query, 132 | [Map? variables, String? operationName]); 133 | } 134 | -------------------------------------------------------------------------------- /packages/graphql_server/lib/src/apollo/transport.dart: -------------------------------------------------------------------------------- 1 | import 'package:graphql_schema2/graphql_schema2.dart'; 2 | 3 | /// A basic message in the Apollo WebSocket protocol. 4 | class OperationMessage { 5 | static const String gqlConnectionInit = 'connection_init', 6 | gqlConnectionAck = 'connection_ack', 7 | gqlConnectionKeepAlive = 'ka', 8 | gqlConnectionError = 'connection_error', 9 | gqlStart = 'start', 10 | gqlStop = 'stop', 11 | gqlConnectionTerminate = 'connection_terminate', 12 | gqlData = 'data', 13 | gqlError = 'error', 14 | gqlComplete = 'complete'; 15 | static const String legacyGqlConnectionInit = 'connection_init', 16 | legacyGqlConnectionAck = 'connection_ack', 17 | legacyGqlConnectionKeepAlive = 'ka', 18 | legacyGqlConnectionError = 'connection_error', 19 | legacyGqlStart = 'start', 20 | legacyGqlStop = 'stop', 21 | legacyGqlConnectionTerminate = 'connection_terminate', 22 | legacyGqlData = 'data', 23 | legacyGqlError = 'error', 24 | legacyGqlComplete = 'complete'; 25 | 26 | // static const String gqlConnectionInit = 'GQL_CONNECTION_INIT', 27 | // gqlConnectionAck = 'GQL_CONNECTION_ACK', 28 | // gqlConnectionKeepAlive = 'GQL_CONNECTION_KEEP_ALIVE', 29 | // gqlConnectionError = 'GQL_CONNECTION_ERROR', 30 | // gqlStart = 'GQL_START', 31 | // gqlStop = 'GQL_STOP', 32 | // gqlConnectionTerminate = 'GQL_CONNECTION_TERMINATE', 33 | // gqlData = 'GQL_DATA', 34 | // gqlError = 'GQL_ERROR', 35 | // gqlComplete = 'GQL_COMPLETE'; 36 | final dynamic payload; 37 | final String? id; 38 | final String type; 39 | 40 | OperationMessage(this.type, {this.payload, this.id}); 41 | 42 | factory OperationMessage.fromJson(Map map) { 43 | var type = map['type']; 44 | var payload = map['payload']; 45 | var id = map['id']; 46 | 47 | if (type == null) { 48 | throw ArgumentError.notNull('type'); 49 | } else if (type is! String) { 50 | throw ArgumentError.value(type, 'type', 'must be a string'); 51 | } else if (id is num) { 52 | id = id.toString(); 53 | } else if (id != null && id is! String) { 54 | throw ArgumentError.value(id, 'id', 'must be a string or number'); 55 | } 56 | 57 | // TODO: This is technically a violation of the spec. 58 | // https://github.com/apollographql/subscriptions-transport-ws/issues/551 59 | if (map.containsKey('query') || 60 | map.containsKey('operationName') || 61 | map.containsKey('variables')) { 62 | payload = Map.from(map); 63 | } 64 | return OperationMessage(type, id: id as String?, payload: payload); 65 | } 66 | 67 | Map toJson() { 68 | var out = {'type': type}; 69 | if (id != null) out['id'] = id; 70 | if (payload != null) out['payload'] = payload; 71 | return out; 72 | } 73 | } 74 | 75 | class GraphQLResult { 76 | final dynamic data; 77 | final Iterable errors; 78 | 79 | GraphQLResult(this.data, {this.errors = const []}); 80 | } 81 | -------------------------------------------------------------------------------- /packages/graphql_server/lib/subscriptions_transport_ws.dart: -------------------------------------------------------------------------------- 1 | /// An implementation of Apollo's `subscriptions-transport-ws` in Dart. 2 | /// 3 | /// See: 4 | /// https://github.com/apollographql/subscriptions-transport-ws/blob/master/PROTOCOL.md 5 | library; 6 | 7 | export 'src/apollo/remote_client.dart'; 8 | export 'src/apollo/server.dart'; 9 | export 'src/apollo/transport.dart'; 10 | -------------------------------------------------------------------------------- /packages/graphql_server/melos_graphql_server2.iml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | -------------------------------------------------------------------------------- /packages/graphql_server/pubspec.yaml: -------------------------------------------------------------------------------- 1 | name: graphql_server2 2 | version: 6.3.0 3 | description: Base package for implementing GraphQL servers. You might prefer `package:angel3_graphql`, the fastest way to implement GraphQL backends in Dart. 4 | homepage: https://angel3-framework.web.app/ 5 | repository: https://github.com/dart-backend/graphql_dart/tree/master/packages/graphql_server 6 | 7 | environment: 8 | sdk: '>=3.6.0 <4.0.0' 9 | 10 | dependencies: 11 | angel3_serialize: ^8.0.0 12 | graphql_schema2: ^6.0.0 13 | graphql_parser2: ^6.0.0 14 | collection: ^1.17.0 15 | meta: ^1.9.0 16 | recase: ^4.1.0 17 | stream_channel: ^2.1.0 18 | tuple: ^2.0.0 19 | 20 | dev_dependencies: 21 | lints: ^5.0.0 22 | test: ^1.24.0 23 | 24 | # dependency_overrides: 25 | # graphql_parser2: 26 | # path: ../graphql_parser 27 | # graphql_schema2: 28 | # path: ../graphql_schema 29 | -------------------------------------------------------------------------------- /packages/graphql_server/test/common.dart: -------------------------------------------------------------------------------- 1 | import 'package:graphql_schema2/graphql_schema2.dart'; 2 | import 'package:test/test.dart'; 3 | 4 | final Matcher throwsAGraphQLException = throwsA( 5 | predicate((dynamic x) => x is GraphQLException, 'is a GraphQL exception')); 6 | -------------------------------------------------------------------------------- /packages/graphql_server/test/mirrors_test.dart: -------------------------------------------------------------------------------- 1 | import 'package:graphql_schema2/graphql_schema2.dart'; 2 | import 'package:graphql_server2/mirrors.dart'; 3 | import 'package:test/test.dart'; 4 | 5 | void main() { 6 | group('convertDartType', () { 7 | group('on enum', () { 8 | // ignore: deprecated_member_use_from_same_package 9 | var type = convertDartType(RomanceLanguage); 10 | var asEnumType = type as GraphQLEnumType; 11 | 12 | test('produces enum type', () { 13 | expect(type, isNotNull); 14 | }); 15 | 16 | test('rejects invalid value', () { 17 | expect(asEnumType.validate('@root', 'GERMAN').successful, false); 18 | }); 19 | 20 | test('accepts valid value', () { 21 | expect(asEnumType.validate('@root', 'spanish').successful, true); 22 | }); 23 | 24 | test('deserializes to concrete value', () { 25 | expect(asEnumType.deserialize('italian'), RomanceLanguage.italian); 26 | }); 27 | 28 | test('serializes to concrete value', () { 29 | expect(asEnumType.serialize(RomanceLanguage.france), 'france'); 30 | }); 31 | 32 | /* TODO: Required fixing 33 | test('can serialize null', () { 34 | expect(asEnumType.serialize(null), null); 35 | }); 36 | */ 37 | 38 | test('fails to serialize invalid value', () { 39 | expect(() => asEnumType.serialize(34), throwsStateError); 40 | }); 41 | 42 | test('fails to deserialize invalid value', () { 43 | expect(() => asEnumType.deserialize('JAPANESE'), throwsStateError); 44 | }); 45 | }); 46 | }); 47 | } 48 | 49 | @graphQLClass 50 | enum RomanceLanguage { 51 | spanish, 52 | france, 53 | italian, 54 | } 55 | -------------------------------------------------------------------------------- /packages/graphql_server/test/query_test.dart: -------------------------------------------------------------------------------- 1 | import 'package:graphql_schema2/graphql_schema2.dart'; 2 | import 'package:graphql_server2/graphql_server2.dart'; 3 | import 'package:test/test.dart'; 4 | 5 | void main() { 6 | test('single element', () async { 7 | var todoType = objectType('todo', fields: [ 8 | field( 9 | 'text', 10 | graphQLString, 11 | resolve: (obj, args) => obj.text, 12 | ), 13 | field( 14 | 'completed', 15 | graphQLBoolean, 16 | resolve: (obj, args) => obj.completed, 17 | ), 18 | ]); 19 | 20 | var schema = graphQLSchema( 21 | queryType: objectType('api', fields: [ 22 | field( 23 | 'todos', 24 | listOf(todoType), 25 | resolve: (_, __) => [ 26 | Todo( 27 | text: 'Clean your room!', 28 | completed: false, 29 | ) 30 | ], 31 | ), 32 | ]), 33 | ); 34 | 35 | var graphql = GraphQL(schema); 36 | var result = await graphql.parseAndExecute('{ todos { text } }'); 37 | 38 | print(result); 39 | expect(result, { 40 | 'todos': [ 41 | {'text': 'Clean your room!'} 42 | ] 43 | }); 44 | }); 45 | } 46 | 47 | class Todo { 48 | final String? text; 49 | final bool? completed; 50 | 51 | Todo({this.text, this.completed}); 52 | } 53 | -------------------------------------------------------------------------------- /packages/graphql_server/test/subscription_test.dart: -------------------------------------------------------------------------------- 1 | import 'dart:async'; 2 | import 'package:graphql_schema2/graphql_schema2.dart'; 3 | import 'package:graphql_server2/graphql_server2.dart'; 4 | import 'package:test/test.dart'; 5 | 6 | void main() { 7 | var episodes = [ 8 | {'name': 'The Phantom Menace'}, 9 | {'name': 'Attack of the Clones'}, 10 | {'name': 'Attack of the Clones'} 11 | ]; 12 | var episodesAsData = episodes.map((ep) { 13 | return { 14 | 'data': {'prequels': ep} 15 | }; 16 | }); 17 | 18 | Stream> resolveEpisodes(_, __) => 19 | Stream.fromIterable(episodes) 20 | .map((ep) => {'prequels': ep, 'not_selected': 1337}); 21 | 22 | var episodeType = objectType('Episode', fields: [ 23 | field('name', graphQLString.nonNullable()), 24 | field('not_selected', graphQLInt), 25 | ]); 26 | 27 | var schema = graphQLSchema( 28 | queryType: objectType('TestQuery', fields: [ 29 | field('episodes', graphQLInt, resolve: (_, __) => episodes), 30 | ]), 31 | subscriptionType: objectType('TestSubscription', fields: [ 32 | field('prequels', episodeType, resolve: resolveEpisodes), 33 | ]), 34 | ); 35 | 36 | var graphQL = GraphQL(schema); 37 | 38 | test('subscribe with selection', () async { 39 | var stream = await graphQL.parseAndExecute(''' 40 | subscription { 41 | prequels { 42 | name 43 | } 44 | } 45 | ''') as Stream>; 46 | 47 | var asList = await stream.toList(); 48 | print(asList); 49 | expect(asList, episodesAsData); 50 | }); 51 | } 52 | -------------------------------------------------------------------------------- /pubspec.yaml: -------------------------------------------------------------------------------- 1 | name: graphql_dart 2 | 3 | environment: 4 | sdk: '>=3.6.0 <4.0.0' 5 | 6 | dependencies: 7 | melos: ^3.4.0 8 | --------------------------------------------------------------------------------