Add 'packages/graphql/' from commit '33e2f86ba73d559197b6270df036256104726aca'
git-subtree-dir: packages/graphql git-subtree-mainline:ac29392d7f
git-subtree-split:33e2f86ba7
This commit is contained in:
commit
4e69153e3e
185 changed files with 10757 additions and 0 deletions
56
packages/graphql/.gitignore
vendored
Normal file
56
packages/graphql/.gitignore
vendored
Normal file
|
@ -0,0 +1,56 @@
|
|||
# Created by .ignore support plugin (hsz.mobi)
|
||||
### JetBrains template
|
||||
# Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm, CLion, Android Studio and WebStorm
|
||||
# Reference: https://intellij-support.jetbrains.com/hc/en-us/articles/206544839
|
||||
|
||||
# User-specific stuff
|
||||
.idea/**/workspace.xml
|
||||
.idea/**/tasks.xml
|
||||
.idea/**/dictionaries
|
||||
.idea/**/shelf
|
||||
|
||||
# Sensitive or high-churn files
|
||||
.idea/**/dataSources/
|
||||
.idea/**/dataSources.ids
|
||||
.idea/**/dataSources.local.xml
|
||||
.idea/**/sqlDataSources.xml
|
||||
.idea/**/dynamic.xml
|
||||
.idea/**/uiDesigner.xml
|
||||
.idea/**/dbnavigator.xml
|
||||
|
||||
# Gradle
|
||||
.idea/**/gradle.xml
|
||||
.idea/**/libraries
|
||||
|
||||
# CMake
|
||||
cmake-build-debug/
|
||||
cmake-build-release/
|
||||
|
||||
# Mongo Explorer plugin
|
||||
.idea/**/mongoSettings.xml
|
||||
|
||||
# File-based project format
|
||||
*.iws
|
||||
|
||||
# IntelliJ
|
||||
out/
|
||||
|
||||
# mpeltonen/sbt-idea plugin
|
||||
.idea_modules/
|
||||
|
||||
# JIRA plugin
|
||||
atlassian-ide-plugin.xml
|
||||
|
||||
# Cursive Clojure plugin
|
||||
.idea/replstate.xml
|
||||
|
||||
# Crashlytics plugin (for Android Studio and IntelliJ)
|
||||
com_crashlytics_export_strings.xml
|
||||
crashlytics.properties
|
||||
crashlytics-build.properties
|
||||
fabric.properties
|
||||
|
||||
# Editor-based Rest Client
|
||||
.idea/httpRequests
|
||||
|
||||
.dart_tool
|
14
packages/graphql/.idea/graphql_parser.iml
Normal file
14
packages/graphql/.idea/graphql_parser.iml
Normal file
|
@ -0,0 +1,14 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<module type="WEB_MODULE" version="4">
|
||||
<component name="NewModuleRootManager">
|
||||
<content url="file://$MODULE_DIR$">
|
||||
<excludeFolder url="file://$MODULE_DIR$/.pub" />
|
||||
<excludeFolder url="file://$MODULE_DIR$/.tmp" />
|
||||
<excludeFolder url="file://$MODULE_DIR$/build" />
|
||||
<excludeFolder url="file://$MODULE_DIR$/temp" />
|
||||
<excludeFolder url="file://$MODULE_DIR$/tmp" />
|
||||
</content>
|
||||
<orderEntry type="inheritedJdk" />
|
||||
<orderEntry type="sourceFolder" forTests="false" />
|
||||
</component>
|
||||
</module>
|
13
packages/graphql/.idea/modules.xml
Normal file
13
packages/graphql/.idea/modules.xml
Normal file
|
@ -0,0 +1,13 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project version="4">
|
||||
<component name="ProjectModuleManager">
|
||||
<modules>
|
||||
<module fileurl="file://$PROJECT_DIR$/angel_graphql/angel_graphql.iml" filepath="$PROJECT_DIR$/angel_graphql/angel_graphql.iml" />
|
||||
<module fileurl="file://$PROJECT_DIR$/example_star_wars/example_star_wars.iml" filepath="$PROJECT_DIR$/example_star_wars/example_star_wars.iml" />
|
||||
<module fileurl="file://$PROJECT_DIR$/graphql.iml" filepath="$PROJECT_DIR$/graphql.iml" />
|
||||
<module fileurl="file://$PROJECT_DIR$/graphql_parser/graphql_parser.iml" filepath="$PROJECT_DIR$/graphql_parser/graphql_parser.iml" />
|
||||
<module fileurl="file://$PROJECT_DIR$/graphql_schema/graphql_schema.iml" filepath="$PROJECT_DIR$/graphql_schema/graphql_schema.iml" />
|
||||
<module fileurl="file://$PROJECT_DIR$/graphql_server/graphql_server.iml" filepath="$PROJECT_DIR$/graphql_server/graphql_server.iml" />
|
||||
</modules>
|
||||
</component>
|
||||
</project>
|
7
packages/graphql/.idea/runConfigurations/main_dart.xml
Normal file
7
packages/graphql/.idea/runConfigurations/main_dart.xml
Normal file
|
@ -0,0 +1,7 @@
|
|||
<component name="ProjectRunConfigurationManager">
|
||||
<configuration default="false" name="main.dart" type="DartCommandLineRunConfigurationType" factoryName="Dart Command Line Application" singleton="true" nameIsGenerated="true">
|
||||
<option name="filePath" value="$PROJECT_DIR$/angel_graphql/example/main.dart" />
|
||||
<option name="workingDirectory" value="$PROJECT_DIR$/angel_graphql" />
|
||||
<method />
|
||||
</configuration>
|
||||
</component>
|
|
@ -0,0 +1,9 @@
|
|||
<component name="ProjectRunConfigurationManager">
|
||||
<configuration default="false" name="objects in equality_test.dart" type="DartTestRunConfigurationType" factoryName="Dart Test" singleton="true" nameIsGenerated="true">
|
||||
<option name="filePath" value="$PROJECT_DIR$/graphql_schema/test/equality_test.dart" />
|
||||
<option name="scope" value="GROUP_OR_TEST_BY_NAME" />
|
||||
<option name="testName" value="objects" />
|
||||
<option name="testRunnerOptions" value="-j4" />
|
||||
<method />
|
||||
</configuration>
|
||||
</component>
|
8
packages/graphql/.idea/runConfigurations/server_dart.xml
Normal file
8
packages/graphql/.idea/runConfigurations/server_dart.xml
Normal file
|
@ -0,0 +1,8 @@
|
|||
<component name="ProjectRunConfigurationManager">
|
||||
<configuration default="false" name="server.dart" type="DartCommandLineRunConfigurationType" factoryName="Dart Command Line Application" singleton="true" nameIsGenerated="true">
|
||||
<option name="VMOptions" value="--observe" />
|
||||
<option name="filePath" value="$PROJECT_DIR$/example_star_wars/bin/server.dart" />
|
||||
<option name="workingDirectory" value="$PROJECT_DIR$/example_star_wars" />
|
||||
<method />
|
||||
</configuration>
|
||||
</component>
|
|
@ -0,0 +1,6 @@
|
|||
<component name="ProjectRunConfigurationManager">
|
||||
<configuration default="false" name="tests in comment_test.dart" type="DartTestRunConfigurationType" factoryName="Dart Test" singleton="true" nameIsGenerated="true">
|
||||
<option name="filePath" value="$PROJECT_DIR$/graphql_parser/test/comment_test.dart" />
|
||||
<method />
|
||||
</configuration>
|
||||
</component>
|
|
@ -0,0 +1,8 @@
|
|||
<component name="ProjectRunConfigurationManager">
|
||||
<configuration default="false" name="tests in graphql_parser" type="DartTestRunConfigurationType" factoryName="Dart Test" singleton="true" nameIsGenerated="true">
|
||||
<option name="filePath" value="$PROJECT_DIR$/graphql_parser" />
|
||||
<option name="scope" value="FOLDER" />
|
||||
<option name="testRunnerOptions" value="-j 4" />
|
||||
<method />
|
||||
</configuration>
|
||||
</component>
|
|
@ -0,0 +1,8 @@
|
|||
<component name="ProjectRunConfigurationManager">
|
||||
<configuration default="false" name="tests in graphql_schema" type="DartTestRunConfigurationType" factoryName="Dart Test" singleton="true" nameIsGenerated="true">
|
||||
<option name="filePath" value="$PROJECT_DIR$/graphql_schema" />
|
||||
<option name="scope" value="FOLDER" />
|
||||
<option name="testRunnerOptions" value="-j4" />
|
||||
<method />
|
||||
</configuration>
|
||||
</component>
|
|
@ -0,0 +1,7 @@
|
|||
<component name="ProjectRunConfigurationManager">
|
||||
<configuration default="false" name="tests in mirrors_test.dart" type="DartTestRunConfigurationType" factoryName="Dart Test" singleton="true" nameIsGenerated="true">
|
||||
<option name="filePath" value="$PROJECT_DIR$/graphql_server/test/mirrors_test.dart" />
|
||||
<option name="testRunnerOptions" value="-j4" />
|
||||
<method />
|
||||
</configuration>
|
||||
</component>
|
|
@ -0,0 +1,7 @@
|
|||
<component name="ProjectRunConfigurationManager">
|
||||
<configuration default="false" name="tests in query_test.dart" type="DartTestRunConfigurationType" factoryName="Dart Test" singleton="true" nameIsGenerated="true">
|
||||
<option name="filePath" value="$PROJECT_DIR$/graphql_server/test/query_test.dart" />
|
||||
<option name="testRunnerOptions" value="-j4" />
|
||||
<method />
|
||||
</configuration>
|
||||
</component>
|
|
@ -0,0 +1,7 @@
|
|||
<component name="ProjectRunConfigurationManager">
|
||||
<configuration default="false" name="tests in validation_test.dart" type="DartTestRunConfigurationType" factoryName="Dart Test" singleton="true" nameIsGenerated="true">
|
||||
<option name="filePath" value="$PROJECT_DIR$/graphql_schema/test/validation_test.dart" />
|
||||
<option name="testRunnerOptions" value="-j4" />
|
||||
<method />
|
||||
</configuration>
|
||||
</component>
|
|
@ -0,0 +1,7 @@
|
|||
<component name="ProjectRunConfigurationManager">
|
||||
<configuration default="false" name="tests in value_test.dart" type="DartTestRunConfigurationType" factoryName="Dart Test" singleton="true" nameIsGenerated="true">
|
||||
<option name="filePath" value="$PROJECT_DIR$/graphql_parser/test/value_test.dart" />
|
||||
<option name="testRunnerOptions" value="-j4" />
|
||||
<method />
|
||||
</configuration>
|
||||
</component>
|
6
packages/graphql/.idea/vcs.xml
Normal file
6
packages/graphql/.idea/vcs.xml
Normal file
|
@ -0,0 +1,6 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project version="4">
|
||||
<component name="VcsDirectoryMappings">
|
||||
<mapping directory="$PROJECT_DIR$" vcs="Git" />
|
||||
</component>
|
||||
</project>
|
6
packages/graphql/.travis.yml
Normal file
6
packages/graphql/.travis.yml
Normal file
|
@ -0,0 +1,6 @@
|
|||
language: dart
|
||||
dart:
|
||||
- dev
|
||||
- stable
|
||||
script:
|
||||
- bash -ex travis.sh
|
21
packages/graphql/LICENSE
Normal file
21
packages/graphql/LICENSE
Normal file
|
@ -0,0 +1,21 @@
|
|||
MIT License
|
||||
|
||||
Copyright (c) 2017 The Angel Framework
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
39
packages/graphql/README.md
Normal file
39
packages/graphql/README.md
Normal file
|
@ -0,0 +1,39 @@
|
|||
![Logo](https://github.com/angel-dart/graphql/raw/master/img/angel_logo.png)
|
||||
|
||||
<div style="text-align: center">
|
||||
<hr>
|
||||
<a href="https://pub.dartlang.org/packages/angel_graphql" rel="nofollow"><img src="https://img.shields.io/pub/v/angel_graphql.svg" alt="Pub" data-canonical-src="https://img.shields.io/pub/v/angel_graphql.svg" style="max-width:100%;"></a>
|
||||
<a href="https://travis-ci.org/angel-dart/graphql" rel="nofollow"><img src="https://travis-ci.org/angel-dart/graphql.svg" alt="Pub" data-canonical-src="https://img.shields.io/pub/v/angel_graphql.svg" style="max-width:100%;"></a>
|
||||
</div>
|
||||
|
||||
|
||||
A complete implementation of the official
|
||||
[GraphQL specification](https://graphql.github.io/graphql-spec/June2018/),
|
||||
in the Dart programming language.
|
||||
|
||||
The goal of this project is to provide to server-side
|
||||
users of Dart an alternative to REST API's.
|
||||
|
||||
Included is also
|
||||
`package:angel_graphql`, which, when combined with the
|
||||
[Angel](https://github.com/angel-dart) framework, allows
|
||||
server-side Dart users to build backends with GraphQL and
|
||||
virtually any database imaginable.
|
||||
|
||||
## Tutorial Demo (click to watch)
|
||||
[![Youtube thumbnail](video.png)](https://youtu.be/5x6S4kDODa8)
|
||||
|
||||
## Projects
|
||||
This mono repo is split into several sub-projects,
|
||||
each with its own detailed documentation and examples:
|
||||
* `angel_graphql` - Support for handling GraphQL via HTTP and
|
||||
WebSockets in the [Angel](https://angel-dart.dev) framework. Also serves as the `package:graphql_server` reference implementation.
|
||||
* `data_loader` - A Dart port of [`graphql/data_loader`](https://github.com/graphql/dataloader).
|
||||
* `example_star_wars`: An example GraphQL API built using
|
||||
`package:angel_graphql`.
|
||||
* `graphql_generator`: Generates `package:graphql_schema` object types from concrete Dart classes.
|
||||
* `graphql_parser`: A recursive descent parser for the GraphQL language.
|
||||
* `graphql_schema`: An implementation of GraphQL's type system. This, combined with `package:graphql_parser`,
|
||||
powers `package:graphql_server`.
|
||||
* `graphql_server`: Base functionality for implementing GraphQL servers in Dart. Has no dependency on any
|
||||
framework.
|
94
packages/graphql/angel_graphql/.gitignore
vendored
Normal file
94
packages/graphql/angel_graphql/.gitignore
vendored
Normal file
|
@ -0,0 +1,94 @@
|
|||
# See https://www.dartlang.org/tools/private-files.html
|
||||
|
||||
# Files and directories created by pub
|
||||
.buildlog
|
||||
.packages
|
||||
.project
|
||||
.pub/
|
||||
.scripts-bin/
|
||||
build/
|
||||
**/packages/
|
||||
posts.json
|
||||
|
||||
# Files created by dart2js
|
||||
# (Most Dart developers will use pub build to compile Dart, use/modify these
|
||||
# rules if you intend to use dart2js directly
|
||||
# Convention is to use extension '.dart.js' for Dart compiled to Javascript to
|
||||
# differentiate from explicit Javascript files)
|
||||
*.dart.js
|
||||
*.part.js
|
||||
*.js.deps
|
||||
*.js.map
|
||||
*.info.json
|
||||
|
||||
# Directory created by dartdoc
|
||||
doc/api/
|
||||
|
||||
# Don't commit pubspec lock file
|
||||
# (Library packages only! Remove pattern if developing an application package)
|
||||
pubspec.lock
|
||||
### Dart template
|
||||
# See https://www.dartlang.org/tools/private-files.html
|
||||
|
||||
# Files and directories created by pub
|
||||
|
||||
# SDK 1.20 and later (no longer creates packages directories)
|
||||
|
||||
# Older SDK versions
|
||||
# (Include if the minimum SDK version specified in pubsepc.yaml is earlier than 1.20)
|
||||
|
||||
|
||||
# Files created by dart2js
|
||||
# (Most Dart developers will use pub build to compile Dart, use/modify these
|
||||
# rules if you intend to use dart2js directly
|
||||
# Convention is to use extension '.dart.js' for Dart compiled to Javascript to
|
||||
# differentiate from explicit Javascript files)
|
||||
|
||||
# Directory created by dartdoc
|
||||
|
||||
# Don't commit pubspec lock file
|
||||
# (Library packages only! Remove pattern if developing an application package)
|
||||
### JetBrains template
|
||||
# Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm, CLion, Android Studio and Webstorm
|
||||
# Reference: https://intellij-support.jetbrains.com/hc/en-us/articles/206544839
|
||||
|
||||
# User-specific stuff:
|
||||
../.idea/workspace.xml
|
||||
.idea/**/tasks.xml
|
||||
.idea/dictionaries
|
||||
|
||||
# Sensitive or high-churn files:
|
||||
.idea/**/dataSources/
|
||||
.idea/**/dataSources.ids
|
||||
.idea/**/dataSources.xml
|
||||
.idea/**/dataSources.local.xml
|
||||
.idea/**/sqlDataSources.xml
|
||||
.idea/**/dynamic.xml
|
||||
.idea/**/uiDesigner.xml
|
||||
|
||||
# Gradle:
|
||||
.idea/**/gradle.xml
|
||||
.idea/**/libraries
|
||||
|
||||
# Mongo Explorer plugin:
|
||||
.idea/**/mongoSettings.xml
|
||||
|
||||
## File-based project format:
|
||||
*.iws
|
||||
|
||||
## Plugin-specific files:
|
||||
|
||||
# IntelliJ
|
||||
/out/
|
||||
|
||||
# mpeltonen/sbt-idea plugin
|
||||
.idea_modules/
|
||||
|
||||
# JIRA plugin
|
||||
atlassian-ide-plugin.xml
|
||||
|
||||
# Crashlytics plugin (for Android Studio and IntelliJ)
|
||||
com_crashlytics_export_strings.xml
|
||||
crashlytics.properties
|
||||
crashlytics-build.properties
|
||||
fabric.properties
|
17
packages/graphql/angel_graphql/CHANGELOG.md
Normal file
17
packages/graphql/angel_graphql/CHANGELOG.md
Normal file
|
@ -0,0 +1,17 @@
|
|||
# 1.1.0
|
||||
* Support the GraphQL multipart spec: https://github.com/jaydenseric/graphql-multipart-request-spec
|
||||
|
||||
# 1.0.0
|
||||
* Apply `package:pedantic`.
|
||||
|
||||
# 1.0.0-rc.0
|
||||
* Finish `graphQLWS`.
|
||||
|
||||
# 1.0.0-beta.1
|
||||
* Add `graphQLWS` handler, and support subscriptions.
|
||||
|
||||
# 1.0.0-beta
|
||||
* Angel RC updates.
|
||||
|
||||
# 1.0.0-alpha
|
||||
* First official release.
|
21
packages/graphql/angel_graphql/LICENSE
Normal file
21
packages/graphql/angel_graphql/LICENSE
Normal file
|
@ -0,0 +1,21 @@
|
|||
MIT License
|
||||
|
||||
Copyright (c) 2017 The Angel Framework
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
297
packages/graphql/angel_graphql/README.md
Normal file
297
packages/graphql/angel_graphql/README.md
Normal file
|
@ -0,0 +1,297 @@
|
|||
![Logo](https://github.com/angel-dart/graphql/raw/master/img/angel_logo.png)
|
||||
|
||||
<div style="text-align: center">
|
||||
<hr>
|
||||
<a href="https://pub.dartlang.org/packages/angel_graphql" rel="nofollow"><img src="https://img.shields.io/pub/v/angel_graphql.svg" alt="Pub" data-canonical-src="https://img.shields.io/pub/v/angel_graphql.svg" style="max-width:100%;"></a>
|
||||
<a href="https://travis-ci.org/angel-dart/graphql" rel="nofollow"><img src="https://travis-ci.org/angel-dart/graphql.svg" alt="Pub" data-canonical-src="https://img.shields.io/pub/v/angel_graphql.svg" style="max-width:100%;"></a>
|
||||
</div>
|
||||
|
||||
* [Installation](#installation)
|
||||
* [Usage](#usage)
|
||||
* [Subscriptions](#subscriptions)
|
||||
* [Integration with Angel `Service`s](#using-services)
|
||||
* [Documenting API's](#documentation)
|
||||
* [Deprecated - Mirrors Usage](#mirrors)
|
||||
|
||||
A complete implementation of the official
|
||||
[GraphQL specification](http://facebook.github.io/graphql/October2016/#sec-Language) - these
|
||||
are the [Angel framework](https://angel-dart.github.io)-specific
|
||||
bindings.
|
||||
|
||||
The goal of this project is to provide to server-side
|
||||
users of Dart an alternative to REST API's. `package:angel_graphql`, which, when combined with the allows
|
||||
server-side Dart users to build backends with GraphQL and
|
||||
virtually any database imaginable.
|
||||
|
||||
## Installation
|
||||
To install `package:angel_graphql`, add the following to your
|
||||
`pubspec.yaml`:
|
||||
|
||||
```yaml
|
||||
dependencies:
|
||||
angel_framework: ^2.0.0-alpha
|
||||
angel_graphql: ^1.0.0-alpha
|
||||
```
|
||||
|
||||
## Usage
|
||||
Using this package is very similar to GraphQL.js - you define
|
||||
a schema, and then mount `graphQLHttp` in your router to start
|
||||
serving. This implementation supports GraphQL features like
|
||||
introspection, so you can play around with `graphiql` as well!
|
||||
|
||||
Firstly, define your schema. A GraphQL schema contains an
|
||||
*object type* that defines all querying operations that can be
|
||||
applied to the backend.
|
||||
|
||||
A GraphQL schema may also have a *mutation* object type,
|
||||
which defines operations that change the backend's state, and
|
||||
optionally a *subscription* type, which defines real-time
|
||||
interactions (coming soon!).
|
||||
|
||||
You can use the `convertDartType` helper to wrap your existing
|
||||
`Model`/PODO classes, and make GraphQL aware of them without duplicated
|
||||
effort.
|
||||
|
||||
```dart
|
||||
import 'package:angel_framework/angel_framework.dart';
|
||||
import 'package:angel_graphql/angel_graphql.dart';
|
||||
import 'package:graphql_schema/graphql_schema.dart';
|
||||
import 'package:graphql_server/graphql_server.dart';
|
||||
import 'package:graphql_server/mirrors.dart';
|
||||
|
||||
Future configureServer(Angel app) async {
|
||||
var queryType = objectType(
|
||||
'Query',
|
||||
description: 'A simple API that manages your to-do list.',
|
||||
fields: [
|
||||
field(
|
||||
'todos',
|
||||
listOf(convertDartType(Todo).nonNullable()),
|
||||
resolve: resolveViaServiceIndex(todoService),
|
||||
),
|
||||
field(
|
||||
'todo',
|
||||
convertDartType(Todo),
|
||||
resolve: resolveViaServiceRead(todoService),
|
||||
inputs: [
|
||||
GraphQLFieldInput('id', graphQLId.nonNullable()),
|
||||
],
|
||||
),
|
||||
],
|
||||
);
|
||||
|
||||
var mutationType = objectType(
|
||||
'Mutation',
|
||||
description: 'Modify the to-do list.',
|
||||
fields: [
|
||||
field(
|
||||
'create',
|
||||
graphQLString,
|
||||
),
|
||||
],
|
||||
);
|
||||
|
||||
var schema = graphQLSchema(
|
||||
queryType: queryType,
|
||||
mutationType: mutationType,
|
||||
);
|
||||
}
|
||||
```
|
||||
|
||||
After you've created your `GraphQLSchema`, you just need to
|
||||
wrap in a call to `graphQLHttp`, a request handler that responds
|
||||
to GraphQL.
|
||||
|
||||
In *development*, it's also highly recommended to mount the
|
||||
`graphiQL` handler, which serves GraphQL's official visual
|
||||
interface, for easy querying and feedback.
|
||||
|
||||
```dart
|
||||
app.all('/graphql', graphQLHttp(GraphQL(schema)));
|
||||
app.get('/graphiql', graphiQL());
|
||||
```
|
||||
|
||||
All that's left now is just to start the server!
|
||||
|
||||
```dart
|
||||
var server = await http.startServer('127.0.0.1', 3000);
|
||||
var uri =
|
||||
Uri(scheme: 'http', host: server.address.address, port: server.port);
|
||||
var graphiqlUri = uri.replace(path: 'graphiql');
|
||||
print('Listening at $uri');
|
||||
print('Access graphiql at $graphiqlUri');
|
||||
```
|
||||
|
||||
Visit your `/graphiql` endpoint, and you'll see the `graphiql`
|
||||
UI, ready-to-go!
|
||||
|
||||
![Graphiql screenshot](https://github.com/angel-dart/graphql/raw/master/img/angel_graphql.png)
|
||||
|
||||
Now you're ready to build a GraphQL API!
|
||||
|
||||
## Subscriptions
|
||||
Example:
|
||||
https://github.com/angel-dart/graphql/blob/master/angel_graphql/example/subscription.dart
|
||||
|
||||
In GraphQL, as of the June 2018 spec, clients can subscribe to streams of events
|
||||
from the server. In your schema, all you need to do is return a `Stream` from a `resolve`
|
||||
callback, rather than a plain object:
|
||||
|
||||
```dart
|
||||
var postAdded = postService.afterCreated
|
||||
.asStream()
|
||||
.map((e) => {'postAdded': e.result})
|
||||
.asBroadcastStream();
|
||||
|
||||
var schema = graphQLSchema(
|
||||
// ...
|
||||
subscriptionType: objectType(
|
||||
'Subscription',
|
||||
fields: [
|
||||
field('postAdded', postType, resolve: (_, __) => postAdded),
|
||||
],
|
||||
),
|
||||
);
|
||||
```
|
||||
|
||||
By default, `graphQLHttp` has no support for subscriptions, because regular
|
||||
HTTP requests are stateless, and are not ideal for continuous data pushing.
|
||||
You can add your own handler:
|
||||
|
||||
```dart
|
||||
graphQLHttp(graphQL, onSubscription: (req, res, stream) {
|
||||
// Do something with the stream here. It's up to you.
|
||||
});
|
||||
```
|
||||
|
||||
There is, however, `graphQLWS`, which implements Apollo's
|
||||
`subscriptions-transport-ws` protocol:
|
||||
|
||||
```dart
|
||||
app.get('/subscriptions', graphQLWS(GraphQL(schema)));
|
||||
```
|
||||
|
||||
You can then use existing JavaScript clients to handle subscriptions.
|
||||
|
||||
The `graphiQL` handler also supports using subscriptions. In the following snippet, the
|
||||
necessary scripts will be added to the rendered page, so that the `subscriptions-transport-ws`
|
||||
client can be used by GraphiQL:
|
||||
|
||||
```dart
|
||||
app.get('/graphiql',
|
||||
graphiQL(subscriptionsEndpoint: 'ws://localhost:3000/subscriptions'));
|
||||
```
|
||||
|
||||
**NOTE: Apollo's spec for the aforementioned protocol is very far outdated, and completely inaccurate,**
|
||||
**See this issue for more:**
|
||||
**https://github.com/apollographql/subscriptions-transport-ws/issues/551**
|
||||
|
||||
## Using Services
|
||||
What would Angel be without services? For those unfamiliar - in Angel,
|
||||
`Service` is a base class that implements CRUD functionality, and serves
|
||||
as the database interface within an Angel application. They are well-suited
|
||||
for NoSQL or other databases without a schema (they can be used with
|
||||
SQL, but that's not their primary focus).
|
||||
|
||||
`package:angel_graphql` has functionality to resolve fields by interacting with
|
||||
services.
|
||||
|
||||
Consider our previous example, and note the calls to
|
||||
`resolveViaServiceIndex` and `resolveViaServiceRead`:
|
||||
|
||||
```dart
|
||||
var queryType = objectType(
|
||||
'Query',
|
||||
description: 'A simple API that manages your to-do list.',
|
||||
fields: [
|
||||
field(
|
||||
'todos',
|
||||
listOf(convertDartType(Todo).nonNullable()),
|
||||
resolve: resolveViaServiceIndex(todoService),
|
||||
),
|
||||
field(
|
||||
'todo',
|
||||
convertDartType(Todo),
|
||||
resolve: resolveViaServiceRead(todoService),
|
||||
inputs: [
|
||||
GraphQLFieldInput('id', graphQLId.nonNullable()),
|
||||
],
|
||||
),
|
||||
],
|
||||
);
|
||||
```
|
||||
|
||||
In all, there are:
|
||||
* `resolveViaServiceIndex`
|
||||
* `resolveViaServiceFindOne`
|
||||
* `resolveViaServiceRead`
|
||||
* `resolveViaServiceCreate`
|
||||
* `resolveViaServiceModify`
|
||||
* `resolveViaServiceUpdate`
|
||||
* `resolveViaServiceRemove`
|
||||
|
||||
As one might imagine, using these convenience helpers makes
|
||||
it much quicker to implement CRUD functionality in a GraphQL
|
||||
API.
|
||||
|
||||
## Documentation
|
||||
Using `package:graphql_generator`, you can generate GraphQL schemas for concrete Dart
|
||||
types:
|
||||
|
||||
```dart
|
||||
configureServer(Angel app) async {
|
||||
var schema = graphQLSchema(
|
||||
queryType: objectType('Query', fields: [
|
||||
field('todos', listOf(todoGraphQLType), resolve: (_, __) => ...)
|
||||
]);
|
||||
);
|
||||
}
|
||||
|
||||
@graphQLClass
|
||||
class Todo {
|
||||
String text;
|
||||
|
||||
@GraphQLDocumentation(description: 'Whether this item is complete.')
|
||||
bool isComplete;
|
||||
}
|
||||
```
|
||||
|
||||
For more documentation, see:
|
||||
https://pub.dartlang.org/packages/graphql_generator
|
||||
|
||||
## Mirrors
|
||||
**NOTE: Mirrors support is deprecated, and will not be updated further.**
|
||||
|
||||
The `convertDartType` function can automatically read the documentation
|
||||
from a type like the following:
|
||||
|
||||
```dart
|
||||
@GraphQLDocumentation(description: 'Any object with a .text (String) property.')
|
||||
abstract class HasText {
|
||||
String get text;
|
||||
}
|
||||
|
||||
@serializable
|
||||
@GraphQLDocumentation(
|
||||
description: 'A task that might not be completed yet. **Yay! Markdown!**')
|
||||
class Todo extends Model implements HasText {
|
||||
String text;
|
||||
|
||||
@GraphQLDocumentation(deprecationReason: 'Use `completion_status` instead.')
|
||||
bool completed;
|
||||
|
||||
CompletionStatus completionStatus;
|
||||
|
||||
Todo({this.text, this.completed, this.completionStatus});
|
||||
}
|
||||
|
||||
@GraphQLDocumentation(description: 'The completion status of a to-do item.')
|
||||
enum CompletionStatus { COMPLETE, INCOMPLETE }
|
||||
```
|
||||
|
||||
You can also manually provide documentation for
|
||||
parameters and endpoints, via a `description` parameter on almost
|
||||
all related functions.
|
||||
|
||||
See [`package:graphql_schema`](https://github.com/angel-dart/graphql/tree/master/graphql_schema)
|
||||
for more documentation.
|
4
packages/graphql/angel_graphql/analysis_options.yaml
Normal file
4
packages/graphql/angel_graphql/analysis_options.yaml
Normal file
|
@ -0,0 +1,4 @@
|
|||
include: package:pedantic/analysis_options.yaml
|
||||
analyzer:
|
||||
strong-mode:
|
||||
implicit-casts: false
|
15
packages/graphql/angel_graphql/angel_graphql.iml
Normal file
15
packages/graphql/angel_graphql/angel_graphql.iml
Normal file
|
@ -0,0 +1,15 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<module type="WEB_MODULE" version="4">
|
||||
<component name="NewModuleRootManager" inherit-compiler-output="true">
|
||||
<exclude-output />
|
||||
<content url="file://$MODULE_DIR$">
|
||||
<excludeFolder url="file://$MODULE_DIR$/.dart_tool" />
|
||||
<excludeFolder url="file://$MODULE_DIR$/.pub" />
|
||||
<excludeFolder url="file://$MODULE_DIR$/build" />
|
||||
</content>
|
||||
<orderEntry type="inheritedJdk" />
|
||||
<orderEntry type="sourceFolder" forTests="false" />
|
||||
<orderEntry type="library" name="Dart SDK" level="project" />
|
||||
<orderEntry type="library" name="Dart Packages" level="project" />
|
||||
</component>
|
||||
</module>
|
105
packages/graphql/angel_graphql/example/main.dart
Normal file
105
packages/graphql/angel_graphql/example/main.dart
Normal file
|
@ -0,0 +1,105 @@
|
|||
// ignore_for_file: deprecated_member_use
|
||||
import 'package:angel_framework/angel_framework.dart';
|
||||
import 'package:angel_framework/http.dart';
|
||||
import 'package:angel_graphql/angel_graphql.dart';
|
||||
import 'package:angel_serialize/angel_serialize.dart';
|
||||
import 'package:graphql_schema/graphql_schema.dart';
|
||||
import 'package:graphql_server/graphql_server.dart';
|
||||
import 'package:graphql_server/mirrors.dart';
|
||||
import 'package:logging/logging.dart';
|
||||
|
||||
main() async {
|
||||
var logger = Logger('angel_graphql');
|
||||
var app = Angel(
|
||||
logger: logger
|
||||
..onRecord.listen((rec) {
|
||||
print(rec);
|
||||
if (rec.error != null) print(rec.error);
|
||||
if (rec.stackTrace != null) print(rec.stackTrace);
|
||||
}));
|
||||
var http = AngelHttp(app);
|
||||
|
||||
var todoService = app.use('api/todos', MapService());
|
||||
|
||||
var queryType = objectType(
|
||||
'Query',
|
||||
description: 'A simple API that manages your to-do list.',
|
||||
fields: [
|
||||
field(
|
||||
'todos',
|
||||
listOf(convertDartType(Todo).nonNullable()),
|
||||
resolve: resolveViaServiceIndex(todoService),
|
||||
),
|
||||
field(
|
||||
'todo',
|
||||
convertDartType(Todo),
|
||||
resolve: resolveViaServiceRead(todoService),
|
||||
inputs: [
|
||||
GraphQLFieldInput('id', graphQLId.nonNullable()),
|
||||
],
|
||||
),
|
||||
],
|
||||
);
|
||||
|
||||
var mutationType = objectType(
|
||||
'Mutation',
|
||||
description: 'Modify the to-do list.',
|
||||
fields: [
|
||||
field(
|
||||
'createTodo',
|
||||
convertDartType(Todo),
|
||||
inputs: [
|
||||
GraphQLFieldInput(
|
||||
'data', convertDartType(Todo).coerceToInputObject()),
|
||||
],
|
||||
resolve: resolveViaServiceCreate(todoService),
|
||||
),
|
||||
],
|
||||
);
|
||||
|
||||
var schema = graphQLSchema(
|
||||
queryType: queryType,
|
||||
mutationType: mutationType,
|
||||
);
|
||||
|
||||
app.all('/graphql', graphQLHttp(GraphQL(schema)));
|
||||
app.get('/graphiql', graphiQL());
|
||||
|
||||
await todoService
|
||||
.create({'text': 'Clean your room!', 'completion_status': 'COMPLETE'});
|
||||
await todoService.create(
|
||||
{'text': 'Take out the trash', 'completion_status': 'INCOMPLETE'});
|
||||
await todoService.create({
|
||||
'text': 'Become a billionaire at the age of 5',
|
||||
'completion_status': 'INCOMPLETE'
|
||||
});
|
||||
|
||||
var server = await http.startServer('127.0.0.1', 3000);
|
||||
var uri =
|
||||
Uri(scheme: 'http', host: server.address.address, port: server.port);
|
||||
var graphiqlUri = uri.replace(path: 'graphiql');
|
||||
print('Listening at $uri');
|
||||
print('Access graphiql at $graphiqlUri');
|
||||
}
|
||||
|
||||
@GraphQLDocumentation(description: 'Any object with a .text (String) property.')
|
||||
abstract class HasText {
|
||||
String get text;
|
||||
}
|
||||
|
||||
@serializable
|
||||
@GraphQLDocumentation(
|
||||
description: 'A task that might not be completed yet. **Yay! Markdown!**')
|
||||
class Todo extends Model implements HasText {
|
||||
String text;
|
||||
|
||||
@GraphQLDocumentation(deprecationReason: 'Use `completion_status` instead.')
|
||||
bool completed;
|
||||
|
||||
CompletionStatus completionStatus;
|
||||
|
||||
Todo({this.text, this.completed, this.completionStatus});
|
||||
}
|
||||
|
||||
@GraphQLDocumentation(description: 'The completion status of a to-do item.')
|
||||
enum CompletionStatus { COMPLETE, INCOMPLETE }
|
98
packages/graphql/angel_graphql/example/subscription.dart
Normal file
98
packages/graphql/angel_graphql/example/subscription.dart
Normal file
|
@ -0,0 +1,98 @@
|
|||
// Inspired by:
|
||||
// https://www.apollographql.com/docs/apollo-server/features/subscriptions/#subscriptions-example
|
||||
|
||||
import 'package:angel_file_service/angel_file_service.dart';
|
||||
import 'package:angel_framework/angel_framework.dart';
|
||||
import 'package:angel_framework/http.dart';
|
||||
import 'package:angel_graphql/angel_graphql.dart';
|
||||
import 'package:file/local.dart';
|
||||
import 'package:graphql_schema/graphql_schema.dart';
|
||||
import 'package:graphql_server/graphql_server.dart';
|
||||
import 'package:logging/logging.dart';
|
||||
|
||||
main() async {
|
||||
var logger = Logger('angel_graphql');
|
||||
var app = Angel(logger: logger);
|
||||
var http = AngelHttp(app);
|
||||
app.logger.onRecord.listen((rec) {
|
||||
print(rec);
|
||||
if (rec.error != null) print(rec.error);
|
||||
if (rec.stackTrace != null) print(rec.stackTrace);
|
||||
});
|
||||
|
||||
// Create an in-memory service.
|
||||
var fs = LocalFileSystem();
|
||||
var postService =
|
||||
app.use('/api/posts', JsonFileService(fs.file('posts.json')));
|
||||
|
||||
// Also get a [Stream] of item creation events.
|
||||
var postAdded = postService.afterCreated
|
||||
.asStream()
|
||||
.map((e) => {'postAdded': e.result})
|
||||
.asBroadcastStream();
|
||||
|
||||
// GraphQL setup.
|
||||
var postType = objectType('Post', fields: [
|
||||
field('author', graphQLString),
|
||||
field('comment', graphQLString),
|
||||
]);
|
||||
|
||||
var schema = graphQLSchema(
|
||||
// Hooked up to the postService:
|
||||
// type Query { posts: [Post] }
|
||||
queryType: objectType(
|
||||
'Query',
|
||||
fields: [
|
||||
field(
|
||||
'posts',
|
||||
listOf(postType),
|
||||
resolve: resolveViaServiceIndex(postService),
|
||||
),
|
||||
],
|
||||
),
|
||||
|
||||
// Hooked up to the postService:
|
||||
// type Mutation {
|
||||
// addPost(author: String!, comment: String!): Post
|
||||
// }
|
||||
mutationType: objectType(
|
||||
'Mutation',
|
||||
fields: [
|
||||
field(
|
||||
'addPost',
|
||||
postType,
|
||||
inputs: [
|
||||
GraphQLFieldInput(
|
||||
'data', postType.toInputObject('PostInput').nonNullable()),
|
||||
],
|
||||
resolve: resolveViaServiceCreate(postService),
|
||||
),
|
||||
],
|
||||
),
|
||||
|
||||
// Hooked up to `postAdded`:
|
||||
// type Subscription { postAdded: Post }
|
||||
subscriptionType: objectType(
|
||||
'Subscription',
|
||||
fields: [
|
||||
field('postAdded', postType, resolve: (_, __) => postAdded),
|
||||
],
|
||||
),
|
||||
);
|
||||
|
||||
// Mount GraphQL routes; we'll support HTTP and WebSockets transports.
|
||||
app.all('/graphql', graphQLHttp(GraphQL(schema)));
|
||||
app.get('/subscriptions',
|
||||
graphQLWS(GraphQL(schema), keepAliveInterval: Duration(seconds: 3)));
|
||||
app.get('/graphiql',
|
||||
graphiQL(subscriptionsEndpoint: 'ws://localhost:3000/subscriptions'));
|
||||
|
||||
var server = await http.startServer('127.0.0.1', 3000);
|
||||
var uri =
|
||||
Uri(scheme: 'http', host: server.address.address, port: server.port);
|
||||
var graphiqlUri = uri.replace(path: 'graphiql');
|
||||
var postsUri = uri.replace(pathSegments: ['api', 'posts']);
|
||||
print('Listening at $uri');
|
||||
print('Access graphiql at $graphiqlUri');
|
||||
print('Access posts service at $postsUri');
|
||||
}
|
46
packages/graphql/angel_graphql/lib/angel_graphql.dart
Normal file
46
packages/graphql/angel_graphql/lib/angel_graphql.dart
Normal file
|
@ -0,0 +1,46 @@
|
|||
import 'package:angel_framework/angel_framework.dart';
|
||||
import 'package:graphql_schema/graphql_schema.dart';
|
||||
export 'src/graphiql.dart';
|
||||
export 'src/graphql_http.dart';
|
||||
export 'src/graphql_ws.dart';
|
||||
export 'src/resolvers.dart';
|
||||
|
||||
/// The canonical [GraphQLUploadType] instance.
|
||||
final GraphQLUploadType graphQLUpload = GraphQLUploadType();
|
||||
|
||||
/// A [GraphQLScalarType] that is used to read uploaded files from
|
||||
/// `multipart/form-data` requests.
|
||||
class GraphQLUploadType extends GraphQLScalarType<UploadedFile, UploadedFile> {
|
||||
@override
|
||||
String get name => 'Upload';
|
||||
|
||||
@override
|
||||
String get description =>
|
||||
'Represents a file that has been uploaded to the server.';
|
||||
|
||||
@override
|
||||
GraphQLType<UploadedFile, UploadedFile> coerceToInputObject() => this;
|
||||
|
||||
@override
|
||||
UploadedFile deserialize(UploadedFile serialized) => serialized;
|
||||
|
||||
@override
|
||||
UploadedFile serialize(UploadedFile value) => value;
|
||||
|
||||
@override
|
||||
ValidationResult<UploadedFile> validate(String key, UploadedFile input) {
|
||||
if (input != null && input is! UploadedFile) {
|
||||
return _Vr(false, errors: ['Expected "$key" to be a boolean.']);
|
||||
}
|
||||
return _Vr(true, value: input);
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: Really need to make the validation result constructors *public*
|
||||
class _Vr<T> implements ValidationResult<T> {
|
||||
final bool successful;
|
||||
final List<String> errors;
|
||||
final T value;
|
||||
|
||||
_Vr(this.successful, {this.errors, this.value});
|
||||
}
|
89
packages/graphql/angel_graphql/lib/src/graphiql.dart
Normal file
89
packages/graphql/angel_graphql/lib/src/graphiql.dart
Normal file
|
@ -0,0 +1,89 @@
|
|||
import 'package:angel_framework/angel_framework.dart';
|
||||
import 'package:http_parser/http_parser.dart';
|
||||
|
||||
/// Returns a simple [RequestHandler] that renders the GraphiQL visual interface for GraphQL.
|
||||
///
|
||||
/// By default, the interface expects your backend to be mounted at `/graphql`; this is configurable
|
||||
/// via [graphQLEndpoint].
|
||||
RequestHandler graphiQL(
|
||||
{String graphQLEndpoint = '/graphql', String subscriptionsEndpoint}) {
|
||||
return (req, res) {
|
||||
res
|
||||
..contentType = MediaType('text', 'html')
|
||||
..write(renderGraphiql(
|
||||
graphqlEndpoint: graphQLEndpoint,
|
||||
subscriptionsEndpoint: subscriptionsEndpoint))
|
||||
..close();
|
||||
};
|
||||
}
|
||||
|
||||
String renderGraphiql(
|
||||
{String graphqlEndpoint = '/graphql', String subscriptionsEndpoint}) {
|
||||
var subscriptionsScripts = '',
|
||||
subscriptionsFetcher = '',
|
||||
fetcherName = 'graphQLFetcher';
|
||||
|
||||
if (subscriptionsEndpoint != null) {
|
||||
fetcherName = 'subscriptionsFetcher';
|
||||
subscriptionsScripts = '''
|
||||
<script src="//unpkg.com/subscriptions-transport-ws@0.8.3/browser/client.js"></script>
|
||||
<script src="//unpkg.com/graphiql-subscriptions-fetcher@0.0.2/browser/client.js"></script>
|
||||
''';
|
||||
subscriptionsFetcher = '''
|
||||
let subscriptionsClient = window.SubscriptionsTransportWs.SubscriptionClient('$subscriptionsEndpoint', {
|
||||
reconnect: true
|
||||
});
|
||||
let $fetcherName = window.GraphiQLSubscriptionsFetcher.graphQLFetcher(subscriptionsClient, graphQLFetcher);
|
||||
''';
|
||||
}
|
||||
|
||||
return '''
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<title>Angel GraphQL</title>
|
||||
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/graphiql/0.11.11/graphiql.min.css">
|
||||
<style>
|
||||
html, body {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
}
|
||||
html, body, #app {
|
||||
height: 100%;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div id="app"></div>
|
||||
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.2.0/umd/react.production.min.js"></script>
|
||||
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.2.0/umd/react-dom.production.min.js"></script>
|
||||
<script src="https://cdnjs.cloudflare.com/ajax/libs/fetch/2.0.3/fetch.min.js"></script>
|
||||
<script src="https://cdnjs.cloudflare.com/ajax/libs/graphiql/0.11.11/graphiql.js"></script>
|
||||
$subscriptionsScripts
|
||||
<script>
|
||||
window.onload = function() {
|
||||
function graphQLFetcher(graphQLParams) {
|
||||
return fetch('$graphqlEndpoint', {
|
||||
method: 'post',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify(graphQLParams)
|
||||
}).then(function(response) {
|
||||
return response.json();
|
||||
});
|
||||
}
|
||||
$subscriptionsFetcher
|
||||
ReactDOM.render(
|
||||
React.createElement(
|
||||
GraphiQL,
|
||||
{fetcher: $fetcherName}
|
||||
),
|
||||
document.getElementById('app')
|
||||
);
|
||||
};
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
'''
|
||||
.trim();
|
||||
}
|
182
packages/graphql/angel_graphql/lib/src/graphql_http.dart
Normal file
182
packages/graphql/angel_graphql/lib/src/graphql_http.dart
Normal file
|
@ -0,0 +1,182 @@
|
|||
import 'dart:async';
|
||||
import 'dart:convert';
|
||||
import 'dart:io';
|
||||
import 'package:angel_framework/angel_framework.dart';
|
||||
import 'package:angel_validate/server.dart';
|
||||
import 'package:graphql_parser/graphql_parser.dart';
|
||||
import 'package:graphql_schema/graphql_schema.dart';
|
||||
import 'package:graphql_server/graphql_server.dart';
|
||||
|
||||
final ContentType graphQlContentType = ContentType('application', 'graphql');
|
||||
|
||||
final Validator graphQlPostBody = Validator({
|
||||
'query*': isNonEmptyString,
|
||||
'operation_name': isNonEmptyString,
|
||||
'variables': predicate((v) => v == null || v is String || v is Map),
|
||||
});
|
||||
|
||||
final RegExp _num = RegExp(r'^[0-9]+$');
|
||||
|
||||
/// A [RequestHandler] that serves a spec-compliant GraphQL backend.
|
||||
///
|
||||
/// Follows the guidelines listed here:
|
||||
/// https://graphql.org/learn/serving-over-http/
|
||||
RequestHandler graphQLHttp(GraphQL graphQL,
|
||||
{Function(RequestContext, ResponseContext, Stream<Map<String, dynamic>>)
|
||||
onSubscription}) {
|
||||
return (req, res) async {
|
||||
var globalVariables = <String, dynamic>{
|
||||
'__requestctx': req,
|
||||
'__responsectx': res,
|
||||
};
|
||||
|
||||
sendGraphQLResponse(result) async {
|
||||
if (result is Stream<Map<String, dynamic>>) {
|
||||
if (onSubscription == null) {
|
||||
throw StateError(
|
||||
'The GraphQL backend returned a Stream, but no `onSubscription` callback was provided.');
|
||||
} else {
|
||||
return await onSubscription(req, res, result);
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
'data': result,
|
||||
};
|
||||
}
|
||||
|
||||
executeMap(Map map) async {
|
||||
var body = await req.parseBody().then((_) => req.bodyAsMap);
|
||||
var text = body['query'] as String;
|
||||
var operationName = body['operation_name'] as String;
|
||||
var variables = body['variables'];
|
||||
|
||||
if (variables is String) {
|
||||
variables = json.decode(variables as String);
|
||||
}
|
||||
|
||||
return await sendGraphQLResponse(await graphQL.parseAndExecute(
|
||||
text,
|
||||
sourceUrl: 'input',
|
||||
operationName: operationName,
|
||||
variableValues: foldToStringDynamic(variables as Map),
|
||||
globalVariables: globalVariables,
|
||||
));
|
||||
}
|
||||
|
||||
try {
|
||||
if (req.method == 'GET') {
|
||||
if (await validateQuery(graphQlPostBody)(req, res) as bool) {
|
||||
return await executeMap(req.queryParameters);
|
||||
}
|
||||
} else if (req.method == 'POST') {
|
||||
if (req.headers.contentType?.mimeType == graphQlContentType.mimeType) {
|
||||
var text = await req.body.transform(utf8.decoder).join();
|
||||
return sendGraphQLResponse(await graphQL.parseAndExecute(
|
||||
text,
|
||||
sourceUrl: 'input',
|
||||
globalVariables: globalVariables,
|
||||
));
|
||||
} else if (req.headers.contentType?.mimeType == 'application/json') {
|
||||
if (await validate(graphQlPostBody)(req, res) as bool) {
|
||||
return await executeMap(req.bodyAsMap);
|
||||
}
|
||||
} else if (req.headers.contentType?.mimeType == 'multipart/form-data') {
|
||||
var fields = await req.parseBody().then((_) => req.bodyAsMap);
|
||||
var operations = fields['operations'] as String;
|
||||
if (operations == null) {
|
||||
throw AngelHttpException.badRequest(
|
||||
message: 'Missing "operations" field.');
|
||||
}
|
||||
var map = fields.containsKey('map')
|
||||
? json.decode(fields['map'] as String)
|
||||
: null;
|
||||
if (map is! Map) {
|
||||
throw AngelHttpException.badRequest(
|
||||
message: '"map" field must decode to a JSON object.');
|
||||
}
|
||||
var variables = Map<String, dynamic>.from(globalVariables);
|
||||
for (var entry in (map as Map).entries) {
|
||||
var file = req.uploadedFiles
|
||||
.firstWhere((f) => f.name == entry.key, orElse: () => null);
|
||||
if (file == null) {
|
||||
throw AngelHttpException.badRequest(
|
||||
message:
|
||||
'"map" contained key "${entry.key}", but no uploaded file '
|
||||
'has that name.');
|
||||
}
|
||||
if (entry.value is! List) {
|
||||
throw AngelHttpException.badRequest(
|
||||
message:
|
||||
'The value for "${entry.key}" in the "map" field was not a JSON array.');
|
||||
}
|
||||
var objectPaths = entry.value as List;
|
||||
for (var objectPath in objectPaths) {
|
||||
var subPaths = (objectPath as String).split('.');
|
||||
if (subPaths[0] == 'variables') {
|
||||
Object current = variables;
|
||||
for (int i = 1; i < subPaths.length; i++) {
|
||||
var name = subPaths[i];
|
||||
var parent = subPaths.take(i).join('.');
|
||||
if (_num.hasMatch(name)) {
|
||||
if (current is! List) {
|
||||
throw AngelHttpException.badRequest(
|
||||
message:
|
||||
'Object "$parent" is not a JSON array, but the '
|
||||
'"map" field contained a mapping to $parent.$name.');
|
||||
}
|
||||
(current as List)[int.parse(name)] = file;
|
||||
} else {
|
||||
if (current is! Map) {
|
||||
throw AngelHttpException.badRequest(
|
||||
message:
|
||||
'Object "$parent" is not a JSON object, but the '
|
||||
'"map" field contained a mapping to $parent.$name.');
|
||||
}
|
||||
(current as Map)[name] = file;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
throw AngelHttpException.badRequest(
|
||||
message:
|
||||
'All array values in the "map" field must begin with "variables.".');
|
||||
}
|
||||
}
|
||||
}
|
||||
return await sendGraphQLResponse(await graphQL.parseAndExecute(
|
||||
operations,
|
||||
sourceUrl: 'input',
|
||||
globalVariables: variables,
|
||||
));
|
||||
} else {
|
||||
throw AngelHttpException.badRequest();
|
||||
}
|
||||
} else {
|
||||
throw AngelHttpException.badRequest();
|
||||
}
|
||||
} on ValidationException catch (e) {
|
||||
var errors = <GraphQLExceptionError>[GraphQLExceptionError(e.message)];
|
||||
|
||||
errors.addAll(e.errors.map((ee) => GraphQLExceptionError(ee)).toList());
|
||||
return GraphQLException(errors).toJson();
|
||||
} on AngelHttpException catch (e) {
|
||||
var errors = <GraphQLExceptionError>[GraphQLExceptionError(e.message)];
|
||||
|
||||
errors.addAll(e.errors.map((ee) => GraphQLExceptionError(ee)).toList());
|
||||
return GraphQLException(errors).toJson();
|
||||
} on SyntaxError catch (e) {
|
||||
return GraphQLException.fromSourceSpan(e.message, e.span);
|
||||
} on GraphQLException catch (e) {
|
||||
return e.toJson();
|
||||
} catch (e, st) {
|
||||
if (req.app?.logger != null) {
|
||||
req.app.logger.severe(
|
||||
'An error occurred while processing GraphQL query at ${req.uri}.',
|
||||
e,
|
||||
st);
|
||||
}
|
||||
|
||||
return GraphQLException.fromMessage(e.toString()).toJson();
|
||||
}
|
||||
};
|
||||
}
|
80
packages/graphql/angel_graphql/lib/src/graphql_ws.dart
Normal file
80
packages/graphql/angel_graphql/lib/src/graphql_ws.dart
Normal file
|
@ -0,0 +1,80 @@
|
|||
import 'dart:async';
|
||||
import 'dart:io';
|
||||
import 'package:angel_framework/angel_framework.dart';
|
||||
import 'package:angel_framework/http.dart';
|
||||
import 'package:graphql_schema/graphql_schema.dart';
|
||||
import 'package:graphql_server/graphql_server.dart';
|
||||
import 'package:graphql_server/subscriptions_transport_ws.dart' as stw;
|
||||
import 'package:web_socket_channel/io.dart';
|
||||
|
||||
/// A [RequestHandler] that serves a spec-compliant GraphQL backend, over WebSockets.
|
||||
/// This endpoint only supports WebSockets, and can be used to deliver subscription events.
|
||||
///
|
||||
/// `graphQLWS` uses the Apollo WebSocket protocol, for the sake of compatibility with
|
||||
/// existing tooling.
|
||||
///
|
||||
/// See:
|
||||
/// * https://github.com/apollographql/subscriptions-transport-ws
|
||||
RequestHandler graphQLWS(GraphQL graphQL, {Duration keepAliveInterval}) {
|
||||
return (req, res) async {
|
||||
if (req is HttpRequestContext) {
|
||||
if (WebSocketTransformer.isUpgradeRequest(req.rawRequest)) {
|
||||
await res.detach();
|
||||
var socket = await WebSocketTransformer.upgrade(req.rawRequest,
|
||||
protocolSelector: (protocols) {
|
||||
if (protocols.contains('graphql-ws')) {
|
||||
return 'graphql-ws';
|
||||
} else {
|
||||
throw AngelHttpException.badRequest(
|
||||
message: 'Only the "graphql-ws" protocol is allowed.');
|
||||
}
|
||||
});
|
||||
var channel = IOWebSocketChannel(socket);
|
||||
var client = stw.RemoteClient(channel.cast<String>());
|
||||
var server =
|
||||
_GraphQLWSServer(client, graphQL, req, res, keepAliveInterval);
|
||||
await server.done;
|
||||
} else {
|
||||
throw AngelHttpException.badRequest(
|
||||
message: 'The `graphQLWS` endpoint only accepts WebSockets.');
|
||||
}
|
||||
} else {
|
||||
throw AngelHttpException.badRequest(
|
||||
message: 'The `graphQLWS` endpoint only accepts HTTP/1.1 requests.');
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
class _GraphQLWSServer extends stw.Server {
|
||||
final GraphQL graphQL;
|
||||
final RequestContext req;
|
||||
final ResponseContext res;
|
||||
|
||||
_GraphQLWSServer(stw.RemoteClient client, this.graphQL, this.req, this.res,
|
||||
Duration keepAliveInterval)
|
||||
: super(client, keepAliveInterval: keepAliveInterval);
|
||||
|
||||
@override
|
||||
bool onConnect(stw.RemoteClient client, [Map connectionParams]) => true;
|
||||
|
||||
@override
|
||||
Future<stw.GraphQLResult> onOperation(String id, String query,
|
||||
[Map<String, dynamic> variables, String operationName]) async {
|
||||
try {
|
||||
var globalVariables = <String, dynamic>{
|
||||
'__requestctx': req,
|
||||
'__responsectx': res,
|
||||
};
|
||||
var data = await graphQL.parseAndExecute(
|
||||
query,
|
||||
operationName: operationName,
|
||||
sourceUrl: 'input',
|
||||
globalVariables: globalVariables,
|
||||
variableValues: variables,
|
||||
);
|
||||
return stw.GraphQLResult(data);
|
||||
} on GraphQLException catch (e) {
|
||||
return stw.GraphQLResult(null, errors: e.errors);
|
||||
}
|
||||
}
|
||||
}
|
143
packages/graphql/angel_graphql/lib/src/resolvers.dart
Normal file
143
packages/graphql/angel_graphql/lib/src/resolvers.dart
Normal file
|
@ -0,0 +1,143 @@
|
|||
import 'package:angel_framework/angel_framework.dart';
|
||||
import 'package:graphql_schema/graphql_schema.dart';
|
||||
|
||||
Map<String, dynamic> _fetchRequestInfo(Map<String, dynamic> arguments) {
|
||||
return <String, dynamic>{
|
||||
'__requestctx': arguments.remove('__requestctx'),
|
||||
'__responsectx': arguments.remove('__responsectx'),
|
||||
};
|
||||
}
|
||||
|
||||
Map<String, dynamic> _getQuery(Map<String, dynamic> arguments) {
|
||||
var f = Map<String, dynamic>.from(arguments)..remove('id')..remove('data');
|
||||
return f.isEmpty ? null : f;
|
||||
}
|
||||
|
||||
/// A GraphQL resolver that `index`es an Angel service.
|
||||
///
|
||||
/// The arguments passed to the resolver will be forwarded to the service, and the
|
||||
/// service will receive [Providers.graphql].
|
||||
GraphQLFieldResolver<List<Value>, Serialized>
|
||||
resolveViaServiceIndex<Value, Serialized>(Service<dynamic, Value> service) {
|
||||
return (_, arguments) async {
|
||||
var _requestInfo = _fetchRequestInfo(arguments);
|
||||
var params = {'query': _getQuery(arguments), 'provider': Providers.graphQL}
|
||||
..addAll(_requestInfo);
|
||||
|
||||
return await service.index(params);
|
||||
};
|
||||
}
|
||||
|
||||
/// A GraphQL resolver that calls `findOne` on an Angel service.
|
||||
///
|
||||
/// The arguments passed to the resolver will be forwarded to the service, and the
|
||||
/// service will receive [Providers.graphql].
|
||||
GraphQLFieldResolver<Value, Serialized>
|
||||
resolveViaServiceFindOne<Value, Serialized>(
|
||||
Service<dynamic, Value> service) {
|
||||
return (_, arguments) async {
|
||||
var _requestInfo = _fetchRequestInfo(arguments);
|
||||
var params = {'query': _getQuery(arguments), 'provider': Providers.graphQL}
|
||||
..addAll(_requestInfo);
|
||||
return await service.findOne(params);
|
||||
};
|
||||
}
|
||||
|
||||
/// A GraphQL resolver that `read`s a single value from an Angel service.
|
||||
///
|
||||
/// This resolver should be used on a field with at least the following inputs:
|
||||
/// * `id`: a [graphQLId] or [graphQLString]
|
||||
///
|
||||
/// The arguments passed to the resolver will be forwarded to the service, and the
|
||||
/// service will receive [Providers.graphql].
|
||||
GraphQLFieldResolver<Value, Serialized>
|
||||
resolveViaServiceRead<Value, Serialized>(Service<dynamic, Value> service,
|
||||
{String idField = 'id'}) {
|
||||
return (_, arguments) async {
|
||||
var _requestInfo = _fetchRequestInfo(arguments);
|
||||
var params = {'query': _getQuery(arguments), 'provider': Providers.graphQL}
|
||||
..addAll(_requestInfo);
|
||||
var id = arguments.remove(idField);
|
||||
return await service.read(id, params);
|
||||
};
|
||||
}
|
||||
|
||||
/// A GraphQL resolver that `creates` a single value in an Angel service.
|
||||
///
|
||||
/// This resolver should be used on a field with at least the following input:
|
||||
/// * `data`: a [GraphQLObjectType] corresponding to the format of `data` to be passed to `create`
|
||||
///
|
||||
/// The arguments passed to the resolver will be forwarded to the service, and the
|
||||
/// service will receive [Providers.graphql].
|
||||
GraphQLFieldResolver<Value, Serialized>
|
||||
resolveViaServiceCreate<Value, Serialized>(
|
||||
Service<dynamic, Value> service) {
|
||||
return (_, arguments) async {
|
||||
var _requestInfo = _fetchRequestInfo(arguments);
|
||||
var params = {'query': _getQuery(arguments), 'provider': Providers.graphQL}
|
||||
..addAll(_requestInfo);
|
||||
return await service.create(arguments['data'] as Value, params);
|
||||
};
|
||||
}
|
||||
|
||||
/// A GraphQL resolver that `modifies` a single value from an Angel service.
|
||||
///
|
||||
/// This resolver should be used on a field with at least the following inputs:
|
||||
/// * `id`: a [graphQLId] or [graphQLString]
|
||||
/// * `data`: a [GraphQLObjectType] corresponding to the format of `data` to be passed to `modify`
|
||||
///
|
||||
/// The arguments passed to the resolver will be forwarded to the service, and the
|
||||
/// service will receive [Providers.graphql].
|
||||
GraphQLFieldResolver<Value, Serialized>
|
||||
resolveViaServiceModify<Value, Serialized>(Service<dynamic, Value> service,
|
||||
{String idField = 'id'}) {
|
||||
return (_, arguments) async {
|
||||
var _requestInfo = _fetchRequestInfo(arguments);
|
||||
var params = {'query': _getQuery(arguments), 'provider': Providers.graphQL}
|
||||
..addAll(_requestInfo);
|
||||
var id = arguments.remove(idField);
|
||||
return await service.modify(id, arguments['data'] as Value, params);
|
||||
};
|
||||
}
|
||||
|
||||
/// A GraphQL resolver that `update`s a single value from an Angel service.
|
||||
///
|
||||
/// This resolver should be used on a field with at least the following inputs:
|
||||
/// * `id`: a [graphQLId] or [graphQLString]
|
||||
/// * `data`: a [GraphQLObjectType] corresponding to the format of `data` to be passed to `update`
|
||||
///
|
||||
/// The arguments passed to the resolver will be forwarded to the service, and the
|
||||
/// service will receive [Providers.graphql].
|
||||
///
|
||||
/// Keep in mind that `update` **overwrites** existing contents.
|
||||
/// To avoid this, use [resolveViaServiceModify] instead.
|
||||
GraphQLFieldResolver<Value, Serialized>
|
||||
resolveViaServiceUpdate<Value, Serialized>(Service<dynamic, Value> service,
|
||||
{String idField = 'id'}) {
|
||||
return (_, arguments) async {
|
||||
var _requestInfo = _fetchRequestInfo(arguments);
|
||||
var params = {'query': _getQuery(arguments), 'provider': Providers.graphQL}
|
||||
..addAll(_requestInfo);
|
||||
var id = arguments.remove(idField);
|
||||
return await service.update(id, arguments['data'] as Value, params);
|
||||
};
|
||||
}
|
||||
|
||||
/// A GraphQL resolver that `remove`s a single value from an Angel service.
|
||||
///
|
||||
/// This resolver should be used on a field with at least the following inputs:
|
||||
/// * `id`: a [graphQLId] or [graphQLString]
|
||||
///
|
||||
/// The arguments passed to the resolver will be forwarded to the service, and the
|
||||
/// service will receive [Providers.graphql].
|
||||
GraphQLFieldResolver<Value, Serialized>
|
||||
resolveViaServiceRemove<Value, Serialized>(Service<dynamic, Value> service,
|
||||
{String idField = 'id'}) {
|
||||
return (_, arguments) async {
|
||||
var _requestInfo = _fetchRequestInfo(arguments);
|
||||
var params = {'query': _getQuery(arguments), 'provider': Providers.graphQL}
|
||||
..addAll(_requestInfo);
|
||||
var id = arguments.remove(idField);
|
||||
return await service.remove(id, params);
|
||||
};
|
||||
}
|
0
packages/graphql/angel_graphql/mono_pkg.yaml
Normal file
0
packages/graphql/angel_graphql/mono_pkg.yaml
Normal file
22
packages/graphql/angel_graphql/pubspec.yaml
Normal file
22
packages/graphql/angel_graphql/pubspec.yaml
Normal file
|
@ -0,0 +1,22 @@
|
|||
name: angel_graphql
|
||||
version: 1.1.0
|
||||
description: The fastest + easiest way to get a GraphQL backend in Dart, using Angel.
|
||||
homepage: https://github.com/angel-dart/graphql
|
||||
author: Tobe O <thosakwe@gmail.com>
|
||||
environment:
|
||||
sdk: ">=2.0.0-dev <3.0.0"
|
||||
dependencies:
|
||||
angel_file_service: ^2.0.0
|
||||
angel_framework: ^2.0.0
|
||||
angel_websocket: ^2.0.0
|
||||
angel_validate: ^2.0.0
|
||||
graphql_parser: ^1.0.0
|
||||
graphql_schema: ^1.0.0
|
||||
graphql_server: ^1.0.0
|
||||
http_parser: ^3.0.0
|
||||
web_socket_channel: ^1.0.0
|
||||
dev_dependencies:
|
||||
angel_serialize: ^2.0.0
|
||||
file: ^5.0.0
|
||||
logging: ^0.11.0
|
||||
pedantic: ^1.0.0
|
4
packages/graphql/data_loader/.gitignore
vendored
Normal file
4
packages/graphql/data_loader/.gitignore
vendored
Normal file
|
@ -0,0 +1,4 @@
|
|||
.packages
|
||||
pubspec.lock
|
||||
.dart_tool
|
||||
doc/api
|
2
packages/graphql/data_loader/CHANGELOG.md
Normal file
2
packages/graphql/data_loader/CHANGELOG.md
Normal file
|
@ -0,0 +1,2 @@
|
|||
# 1.0.0
|
||||
* Initial version.
|
21
packages/graphql/data_loader/LICENSE
Normal file
21
packages/graphql/data_loader/LICENSE
Normal file
|
@ -0,0 +1,21 @@
|
|||
MIT License
|
||||
|
||||
Copyright (c) 2017 The Angel Framework
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
26
packages/graphql/data_loader/README.md
Normal file
26
packages/graphql/data_loader/README.md
Normal file
|
@ -0,0 +1,26 @@
|
|||
# data_loader
|
||||
[![Pub](https://img.shields.io/pub/v/data_loader.svg)](https://pub.dartlang.org/packages/data_loader)
|
||||
[![build status](https://travis-ci.org/angel-dart/graphql.svg)](https://travis-ci.org/angel-dart/graphql)
|
||||
|
||||
|
||||
Batch and cache database lookups. Works well with GraphQL.
|
||||
Ported from the original JS version:
|
||||
https://github.com/graphql/dataloader
|
||||
|
||||
## Installation
|
||||
In your pubspec.yaml:
|
||||
|
||||
```yaml
|
||||
dependencies:
|
||||
data_loader: ^1.0.0
|
||||
```
|
||||
|
||||
## Usage
|
||||
Complete example:
|
||||
https://github.com/angel-dart/graphql/blob/master/data_loader/example/main.dart
|
||||
|
||||
```dart
|
||||
var userLoader = new DataLoader((key) => myBatchGetUsers(keys));
|
||||
var invitedBy = await userLoader.load(1)then(user => userLoader.load(user.invitedByID))
|
||||
print('User 1 was invited by $invitedBy'));
|
||||
```
|
4
packages/graphql/data_loader/analysis_options.yaml
Normal file
4
packages/graphql/data_loader/analysis_options.yaml
Normal file
|
@ -0,0 +1,4 @@
|
|||
include: package:pedantic/analysis_options.yaml
|
||||
analyzer:
|
||||
strong-mode:
|
||||
implicit-casts: false
|
44
packages/graphql/data_loader/example/main.dart
Normal file
44
packages/graphql/data_loader/example/main.dart
Normal file
|
@ -0,0 +1,44 @@
|
|||
import 'dart:async';
|
||||
import 'package:data_loader/data_loader.dart';
|
||||
import 'package:graphql_schema/graphql_schema.dart';
|
||||
|
||||
external Future<List<Todo>> fetchTodos(Iterable<int> ids);
|
||||
|
||||
main() async {
|
||||
// Create a DataLoader. By default, it caches lookups.
|
||||
var todoLoader = DataLoader(fetchTodos); // DataLoader<int, Todo>
|
||||
|
||||
// type Todo { id: Int, text: String, is_complete: Boolean }
|
||||
var todoType = objectType(
|
||||
'Todo',
|
||||
fields: [
|
||||
field('id', graphQLInt),
|
||||
field('text', graphQLString),
|
||||
field('is_complete', graphQLBoolean),
|
||||
],
|
||||
);
|
||||
|
||||
// type Query { todo($id: Int!) Todo }
|
||||
// ignore: unused_local_variable
|
||||
var schema = graphQLSchema(
|
||||
queryType: objectType(
|
||||
'Query',
|
||||
fields: [
|
||||
field(
|
||||
'todo',
|
||||
listOf(todoType),
|
||||
inputs: [GraphQLFieldInput('id', graphQLInt.nonNullable())],
|
||||
resolve: (_, args) => todoLoader.load(args['id'] as int),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
|
||||
// Do something with your schema...
|
||||
}
|
||||
|
||||
abstract class Todo {
|
||||
int get id;
|
||||
String get text;
|
||||
bool get isComplete;
|
||||
}
|
94
packages/graphql/data_loader/lib/data_loader.dart
Normal file
94
packages/graphql/data_loader/lib/data_loader.dart
Normal file
|
@ -0,0 +1,94 @@
|
|||
import 'dart:async';
|
||||
import 'dart:collection';
|
||||
|
||||
/// A utility for batching multiple requests together, to improve application performance.
|
||||
///
|
||||
/// Enqueues batches of requests until the next tick, when they are processed in bulk.
|
||||
///
|
||||
/// Port of Facebook's `DataLoader`:
|
||||
/// https://github.com/graphql/dataloader
|
||||
class DataLoader<Id, Data> {
|
||||
/// Invoked to fetch a batch of keys simultaneously.
|
||||
final FutureOr<Iterable<Data>> Function(Iterable<Id>) loadMany;
|
||||
|
||||
/// Whether to use a memoization cache to store the results of past lookups.
|
||||
final bool cache;
|
||||
|
||||
var _cache = <Id, Data>{};
|
||||
var _queue = Queue<_QueueItem<Id, Data>>();
|
||||
bool _started = false;
|
||||
|
||||
DataLoader(this.loadMany, {this.cache = true});
|
||||
|
||||
Future<void> _onTick() async {
|
||||
if (_queue.isNotEmpty) {
|
||||
var current = _queue.toList();
|
||||
_queue.clear();
|
||||
|
||||
List<Id> loadIds =
|
||||
current.map((i) => i.id).toSet().toList(growable: false);
|
||||
|
||||
var data = await loadMany(
|
||||
loadIds,
|
||||
);
|
||||
|
||||
for (int i = 0; i < loadIds.length; i++) {
|
||||
var id = loadIds[i];
|
||||
var value = data.elementAt(i);
|
||||
|
||||
if (cache) _cache[id] = value;
|
||||
|
||||
current
|
||||
.where((item) => item.id == id)
|
||||
.forEach((item) => item.completer.complete(value));
|
||||
}
|
||||
}
|
||||
|
||||
_started = false;
|
||||
// if (!_closed) scheduleMicrotask(_onTick);
|
||||
}
|
||||
|
||||
/// Clears the value at [key], if it exists.
|
||||
void clear(Id key) => _cache.remove(key);
|
||||
|
||||
/// Clears the entire cache.
|
||||
void clearAll() => _cache.clear();
|
||||
|
||||
/// Primes the cache with the provided key and value. If the key already exists, no change is made.
|
||||
///
|
||||
/// To forcefully prime the cache, clear the key first with
|
||||
/// `loader..clear(key)..prime(key, value)`.
|
||||
void prime(Id key, Data value) => _cache.putIfAbsent(key, () => value);
|
||||
|
||||
/// Closes this [DataLoader], cancelling all pending requests.
|
||||
void close() {
|
||||
while (_queue.isNotEmpty) {
|
||||
_queue.removeFirst().completer.completeError(
|
||||
StateError('The DataLoader was closed before the item was loaded.'));
|
||||
}
|
||||
|
||||
_queue.clear();
|
||||
}
|
||||
|
||||
/// Returns a [Future] that completes when the next batch of requests completes.
|
||||
Future<Data> load(Id id) {
|
||||
if (cache && _cache.containsKey(id)) {
|
||||
return Future<Data>.value(_cache[id]);
|
||||
} else {
|
||||
var item = _QueueItem<Id, Data>(id);
|
||||
_queue.add(item);
|
||||
if (!_started) {
|
||||
_started = true;
|
||||
scheduleMicrotask(_onTick);
|
||||
}
|
||||
return item.completer.future;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class _QueueItem<Id, Data> {
|
||||
final Id id;
|
||||
final Completer<Data> completer = Completer();
|
||||
|
||||
_QueueItem(this.id);
|
||||
}
|
0
packages/graphql/data_loader/mono_pkg.yaml
Normal file
0
packages/graphql/data_loader/mono_pkg.yaml
Normal file
11
packages/graphql/data_loader/pubspec.yaml
Normal file
11
packages/graphql/data_loader/pubspec.yaml
Normal file
|
@ -0,0 +1,11 @@
|
|||
name: data_loader
|
||||
version: 1.0.0
|
||||
author: Tobe O <thosakwe@gmail.com>
|
||||
description: Batch and cache database lookups. Works well with GraphQL. Ported from JS.
|
||||
homepage: https://github.com/angel-dart/graphql
|
||||
environment:
|
||||
sdk: ">=2.0.0-dev <3.0.0"
|
||||
dev_dependencies:
|
||||
graphql_schema: ^1.0.0
|
||||
pedantic: ^1.0.0
|
||||
test: ">=0.12.0 <2.0.0"
|
100
packages/graphql/data_loader/test/all_test.dart
Normal file
100
packages/graphql/data_loader/test/all_test.dart
Normal file
|
@ -0,0 +1,100 @@
|
|||
import 'dart:async';
|
||||
|
||||
import 'package:data_loader/data_loader.dart';
|
||||
import 'package:test/test.dart';
|
||||
|
||||
void main() {
|
||||
var numbers = List.generate(10, (i) => i.toStringAsFixed(2));
|
||||
var numberLoader = DataLoader<int, String>((ids) {
|
||||
print('ID batch: $ids');
|
||||
return ids.map((i) => numbers[i]);
|
||||
});
|
||||
|
||||
test('batch', () async {
|
||||
var zero = numberLoader.load(0);
|
||||
var one = numberLoader.load(1);
|
||||
var two = numberLoader.load(2);
|
||||
var batch = await Future.wait([zero, one, two]);
|
||||
print('Fetched result: $batch');
|
||||
expect(batch, ['0.00', '1.00', '2.00']);
|
||||
});
|
||||
|
||||
test('dedupe', () async {
|
||||
var loader = DataLoader<int, Map<int, List<int>>>((ids) {
|
||||
return ids.map(
|
||||
(i) => {i: ids.toList()},
|
||||
);
|
||||
});
|
||||
|
||||
var zero = loader.load(0);
|
||||
var one = loader.load(1);
|
||||
var two = loader.load(2);
|
||||
var anotherZero = loader.load(0);
|
||||
var batch = await Future.wait([zero, one, two, anotherZero]);
|
||||
|
||||
expect(
|
||||
batch,
|
||||
[
|
||||
{ 0: [0, 1, 2]},
|
||||
{ 1: [0, 1, 2]},
|
||||
{ 2: [0, 1, 2]},
|
||||
{ 0: [0, 1, 2]},
|
||||
],
|
||||
);
|
||||
});
|
||||
|
||||
group('cache', () {
|
||||
DataLoader<int, _Unique> uniqueLoader, noCache;
|
||||
|
||||
setUp(() {
|
||||
uniqueLoader = DataLoader<int, _Unique>((ids) async {
|
||||
var numbers = await numberLoader.loadMany(ids);
|
||||
return numbers.map((s) => _Unique(s));
|
||||
});
|
||||
noCache = DataLoader(uniqueLoader.loadMany, cache: false);
|
||||
});
|
||||
|
||||
tearDown(() {
|
||||
uniqueLoader.close();
|
||||
noCache.close();
|
||||
});
|
||||
|
||||
test('only lookup once', () async {
|
||||
var a = await uniqueLoader.load(3);
|
||||
var b = await uniqueLoader.load(3);
|
||||
expect(a, b);
|
||||
});
|
||||
|
||||
test('can be disabled', () async {
|
||||
var a = await noCache.load(3);
|
||||
var b = await noCache.load(3);
|
||||
expect(a, isNot(b));
|
||||
});
|
||||
|
||||
test('clear', () async {
|
||||
var a = await uniqueLoader.load(3);
|
||||
uniqueLoader.clear(3);
|
||||
var b = await uniqueLoader.load(3);
|
||||
expect(a, isNot(b));
|
||||
});
|
||||
|
||||
test('clearAll', () async {
|
||||
var a = await uniqueLoader.load(3);
|
||||
uniqueLoader.clearAll();
|
||||
var b = await uniqueLoader.load(3);
|
||||
expect(a, isNot(b));
|
||||
});
|
||||
|
||||
test('prime', () async {
|
||||
uniqueLoader.prime(3, _Unique('hey'));
|
||||
var a = await uniqueLoader.load(3);
|
||||
expect(a.value, 'hey');
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
class _Unique {
|
||||
final String value;
|
||||
|
||||
_Unique(this.value);
|
||||
}
|
93
packages/graphql/example_star_wars/.gitignore
vendored
Normal file
93
packages/graphql/example_star_wars/.gitignore
vendored
Normal file
|
@ -0,0 +1,93 @@
|
|||
# See https://www.dartlang.org/tools/private-files.html
|
||||
|
||||
# Files and directories created by pub
|
||||
.buildlog
|
||||
.packages
|
||||
.project
|
||||
.pub/
|
||||
.scripts-bin/
|
||||
build/
|
||||
**/packages/
|
||||
|
||||
# Files created by dart2js
|
||||
# (Most Dart developers will use pub build to compile Dart, use/modify these
|
||||
# rules if you intend to use dart2js directly
|
||||
# Convention is to use extension '.dart.js' for Dart compiled to Javascript to
|
||||
# differentiate from explicit Javascript files)
|
||||
*.dart.js
|
||||
*.part.js
|
||||
*.js.deps
|
||||
*.js.map
|
||||
*.info.json
|
||||
|
||||
# Directory created by dartdoc
|
||||
doc/api/
|
||||
|
||||
# Don't commit pubspec lock file
|
||||
# (Library packages only! Remove pattern if developing an application package)
|
||||
pubspec.lock
|
||||
### Dart template
|
||||
# See https://www.dartlang.org/tools/private-files.html
|
||||
|
||||
# Files and directories created by pub
|
||||
|
||||
# SDK 1.20 and later (no longer creates packages directories)
|
||||
|
||||
# Older SDK versions
|
||||
# (Include if the minimum SDK version specified in pubsepc.yaml is earlier than 1.20)
|
||||
|
||||
|
||||
# Files created by dart2js
|
||||
# (Most Dart developers will use pub build to compile Dart, use/modify these
|
||||
# rules if you intend to use dart2js directly
|
||||
# Convention is to use extension '.dart.js' for Dart compiled to Javascript to
|
||||
# differentiate from explicit Javascript files)
|
||||
|
||||
# Directory created by dartdoc
|
||||
|
||||
# Don't commit pubspec lock file
|
||||
# (Library packages only! Remove pattern if developing an application package)
|
||||
### JetBrains template
|
||||
# Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm, CLion, Android Studio and Webstorm
|
||||
# Reference: https://intellij-support.jetbrains.com/hc/en-us/articles/206544839
|
||||
|
||||
# User-specific stuff:
|
||||
../.idea/workspace.xml
|
||||
.idea/**/tasks.xml
|
||||
.idea/dictionaries
|
||||
|
||||
# Sensitive or high-churn files:
|
||||
.idea/**/dataSources/
|
||||
.idea/**/dataSources.ids
|
||||
.idea/**/dataSources.xml
|
||||
.idea/**/dataSources.local.xml
|
||||
.idea/**/sqlDataSources.xml
|
||||
.idea/**/dynamic.xml
|
||||
.idea/**/uiDesigner.xml
|
||||
|
||||
# Gradle:
|
||||
.idea/**/gradle.xml
|
||||
.idea/**/libraries
|
||||
|
||||
# Mongo Explorer plugin:
|
||||
.idea/**/mongoSettings.xml
|
||||
|
||||
## File-based project format:
|
||||
*.iws
|
||||
|
||||
## Plugin-specific files:
|
||||
|
||||
# IntelliJ
|
||||
/out/
|
||||
|
||||
# mpeltonen/sbt-idea plugin
|
||||
.idea_modules/
|
||||
|
||||
# JIRA plugin
|
||||
atlassian-ide-plugin.xml
|
||||
|
||||
# Crashlytics plugin (for Android Studio and IntelliJ)
|
||||
com_crashlytics_export_strings.xml
|
||||
crashlytics.properties
|
||||
crashlytics-build.properties
|
||||
fabric.properties
|
21
packages/graphql/example_star_wars/LICENSE
Normal file
21
packages/graphql/example_star_wars/LICENSE
Normal file
|
@ -0,0 +1,21 @@
|
|||
MIT License
|
||||
|
||||
Copyright (c) 2017 The Angel Framework
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
3
packages/graphql/example_star_wars/analysis_options.yaml
Normal file
3
packages/graphql/example_star_wars/analysis_options.yaml
Normal file
|
@ -0,0 +1,3 @@
|
|||
analyzer:
|
||||
strong-mode:
|
||||
implicit-casts: false
|
28
packages/graphql/example_star_wars/bin/server.dart
Normal file
28
packages/graphql/example_star_wars/bin/server.dart
Normal file
|
@ -0,0 +1,28 @@
|
|||
import 'dart:async';
|
||||
import 'dart:io';
|
||||
|
||||
import 'package:angel_framework/angel_framework.dart';
|
||||
import 'package:angel_hot/angel_hot.dart';
|
||||
import 'package:logging/logging.dart';
|
||||
import 'package:star_wars/src/pretty_logging.dart' as star_wars;
|
||||
import 'package:star_wars/star_wars.dart' as star_wars;
|
||||
|
||||
main() async {
|
||||
Future<Angel> createServer() async {
|
||||
hierarchicalLoggingEnabled = true;
|
||||
var logger = Logger.detached('star_wars')
|
||||
..onRecord.listen(star_wars.prettyLog);
|
||||
var app = Angel(logger: logger);
|
||||
await app.configure(star_wars.configureServer);
|
||||
return app;
|
||||
}
|
||||
|
||||
var hot = HotReloader(createServer, [Directory('lib')]);
|
||||
|
||||
var server = await hot.startServer('127.0.0.1', 3000);
|
||||
var serverUrl =
|
||||
Uri(scheme: 'http', host: server.address.address, port: server.port);
|
||||
var graphiQLUrl = serverUrl.replace(path: '/graphiql');
|
||||
print('Listening at $serverUrl');
|
||||
print('GraphiQL endpoint: $graphiQLUrl');
|
||||
}
|
14
packages/graphql/example_star_wars/example_star_wars.iml
Normal file
14
packages/graphql/example_star_wars/example_star_wars.iml
Normal file
|
@ -0,0 +1,14 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<module type="WEB_MODULE" version="4">
|
||||
<component name="NewModuleRootManager" inherit-compiler-output="true">
|
||||
<exclude-output />
|
||||
<content url="file://$MODULE_DIR$">
|
||||
<excludeFolder url="file://$MODULE_DIR$/.dart_tool" />
|
||||
<excludeFolder url="file://$MODULE_DIR$/.pub" />
|
||||
<excludeFolder url="file://$MODULE_DIR$/build" />
|
||||
</content>
|
||||
<orderEntry type="sourceFolder" forTests="false" />
|
||||
<orderEntry type="library" name="Dart SDK" level="project" />
|
||||
<orderEntry type="library" name="Dart Packages" level="project" />
|
||||
</component>
|
||||
</module>
|
|
@ -0,0 +1,12 @@
|
|||
import 'package:graphql_schema/graphql_schema.dart';
|
||||
import 'episode.dart';
|
||||
part 'character.g.dart';
|
||||
|
||||
@graphQLClass
|
||||
abstract class Character {
|
||||
String get id;
|
||||
|
||||
String get name;
|
||||
|
||||
// List<Episode> get appearsIn;
|
||||
}
|
|
@ -0,0 +1,13 @@
|
|||
// GENERATED CODE - DO NOT MODIFY BY HAND
|
||||
|
||||
part of 'character.dart';
|
||||
|
||||
// **************************************************************************
|
||||
// _GraphQLGenerator
|
||||
// **************************************************************************
|
||||
|
||||
/// Auto-generated from [Character].
|
||||
final GraphQLObjectType characterGraphQLType = objectType('Character',
|
||||
isInterface: true,
|
||||
interfaces: [],
|
||||
fields: [field('id', graphQLString), field('name', graphQLString)]);
|
23
packages/graphql/example_star_wars/lib/src/models/droid.dart
Normal file
23
packages/graphql/example_star_wars/lib/src/models/droid.dart
Normal file
|
@ -0,0 +1,23 @@
|
|||
import 'package:angel_model/angel_model.dart';
|
||||
import 'package:angel_serialize/angel_serialize.dart';
|
||||
import 'package:collection/collection.dart';
|
||||
import 'package:graphql_schema/graphql_schema.dart';
|
||||
import 'character.dart';
|
||||
import 'episode.dart';
|
||||
part 'droid.g.dart';
|
||||
|
||||
@serializable
|
||||
@graphQLClass
|
||||
@GraphQLDocumentation(description: 'Beep! Boop!')
|
||||
abstract class _Droid extends Model implements Character {
|
||||
String get id;
|
||||
|
||||
String get name;
|
||||
|
||||
@GraphQLDocumentation(
|
||||
description: 'The list of episodes this droid appears in.')
|
||||
List<Episode> get appearsIn;
|
||||
|
||||
/// Doc comments automatically become GraphQL descriptions.
|
||||
List<Character> get friends;
|
||||
}
|
164
packages/graphql/example_star_wars/lib/src/models/droid.g.dart
Normal file
164
packages/graphql/example_star_wars/lib/src/models/droid.g.dart
Normal file
|
@ -0,0 +1,164 @@
|
|||
// GENERATED CODE - DO NOT MODIFY BY HAND
|
||||
|
||||
part of 'droid.dart';
|
||||
|
||||
// **************************************************************************
|
||||
// JsonModelGenerator
|
||||
// **************************************************************************
|
||||
|
||||
@generatedSerializable
|
||||
class Droid extends _Droid {
|
||||
Droid(
|
||||
{this.id,
|
||||
this.name,
|
||||
List<Episode> appearsIn,
|
||||
List<Character> friends,
|
||||
this.createdAt,
|
||||
this.updatedAt})
|
||||
: this.appearsIn = new List.unmodifiable(appearsIn ?? []),
|
||||
this.friends = new List.unmodifiable(friends ?? []);
|
||||
|
||||
@override
|
||||
final String id;
|
||||
|
||||
@override
|
||||
final String name;
|
||||
|
||||
@override
|
||||
final List<Episode> appearsIn;
|
||||
|
||||
@override
|
||||
final List<Character> friends;
|
||||
|
||||
@override
|
||||
final DateTime createdAt;
|
||||
|
||||
@override
|
||||
final DateTime updatedAt;
|
||||
|
||||
Droid copyWith(
|
||||
{String id,
|
||||
String name,
|
||||
List<Episode> appearsIn,
|
||||
List<Character> friends,
|
||||
DateTime createdAt,
|
||||
DateTime updatedAt}) {
|
||||
return new Droid(
|
||||
id: id ?? this.id,
|
||||
name: name ?? this.name,
|
||||
appearsIn: appearsIn ?? this.appearsIn,
|
||||
friends: friends ?? this.friends,
|
||||
createdAt: createdAt ?? this.createdAt,
|
||||
updatedAt: updatedAt ?? this.updatedAt);
|
||||
}
|
||||
|
||||
bool operator ==(other) {
|
||||
return other is _Droid &&
|
||||
other.id == id &&
|
||||
other.name == name &&
|
||||
const ListEquality<Episode>(const DefaultEquality<Episode>())
|
||||
.equals(other.appearsIn, appearsIn) &&
|
||||
const ListEquality<Character>(const DefaultEquality<Character>())
|
||||
.equals(other.friends, friends) &&
|
||||
other.createdAt == createdAt &&
|
||||
other.updatedAt == updatedAt;
|
||||
}
|
||||
|
||||
@override
|
||||
int get hashCode {
|
||||
return hashObjects([id, name, appearsIn, friends, createdAt, updatedAt]);
|
||||
}
|
||||
|
||||
Map<String, dynamic> toJson() {
|
||||
return DroidSerializer.toMap(this);
|
||||
}
|
||||
}
|
||||
|
||||
// **************************************************************************
|
||||
// SerializerGenerator
|
||||
// **************************************************************************
|
||||
|
||||
abstract class DroidSerializer {
|
||||
static Droid fromMap(Map map) {
|
||||
return new Droid(
|
||||
id: map['id'] as String,
|
||||
name: map['name'] as String,
|
||||
appearsIn: map['appears_in'] is Iterable
|
||||
? (map['appears_in'] as Iterable).cast<Episode>().toList()
|
||||
: null,
|
||||
friends: map['friends'] is Iterable
|
||||
? (map['friends'] as Iterable).cast<Character>().toList()
|
||||
: null,
|
||||
createdAt: map['created_at'] != null
|
||||
? (map['created_at'] is DateTime
|
||||
? (map['created_at'] as DateTime)
|
||||
: DateTime.parse(map['created_at'].toString()))
|
||||
: null,
|
||||
updatedAt: map['updated_at'] != null
|
||||
? (map['updated_at'] is DateTime
|
||||
? (map['updated_at'] as DateTime)
|
||||
: DateTime.parse(map['updated_at'].toString()))
|
||||
: null);
|
||||
}
|
||||
|
||||
static Map<String, dynamic> toMap(_Droid model) {
|
||||
if (model == null) {
|
||||
return null;
|
||||
}
|
||||
return {
|
||||
'id': model.id,
|
||||
'name': model.name,
|
||||
'appears_in': model.appearsIn,
|
||||
'friends': model.friends,
|
||||
'created_at': model.createdAt?.toIso8601String(),
|
||||
'updated_at': model.updatedAt?.toIso8601String()
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
abstract class DroidFields {
|
||||
static const List<String> allFields = <String>[
|
||||
id,
|
||||
name,
|
||||
appearsIn,
|
||||
friends,
|
||||
createdAt,
|
||||
updatedAt
|
||||
];
|
||||
|
||||
static const String id = 'id';
|
||||
|
||||
static const String name = 'name';
|
||||
|
||||
static const String appearsIn = 'appears_in';
|
||||
|
||||
static const String friends = 'friends';
|
||||
|
||||
static const String createdAt = 'created_at';
|
||||
|
||||
static const String updatedAt = 'updated_at';
|
||||
}
|
||||
|
||||
// **************************************************************************
|
||||
// _GraphQLGenerator
|
||||
// **************************************************************************
|
||||
|
||||
/// Auto-generated from [Droid].
|
||||
final GraphQLObjectType droidGraphQLType = objectType('Droid',
|
||||
isInterface: false,
|
||||
description: 'Beep! Boop!',
|
||||
interfaces: [
|
||||
characterGraphQLType
|
||||
],
|
||||
fields: [
|
||||
field('id', graphQLString),
|
||||
field('name', graphQLString),
|
||||
field('appears_in', listOf(episodeGraphQLType),
|
||||
description: 'The list of episodes this droid appears in.'),
|
||||
field('friends', listOf(characterGraphQLType),
|
||||
description:
|
||||
'Doc comments automatically become GraphQL descriptions.'),
|
||||
field('created_at', graphQLDate),
|
||||
field('updated_at', graphQLDate),
|
||||
field('idAsInt', graphQLInt)
|
||||
]);
|
|
@ -0,0 +1,11 @@
|
|||
import 'package:graphql_schema/graphql_schema.dart';
|
||||
part 'episode.g.dart';
|
||||
|
||||
@GraphQLDocumentation(
|
||||
description: 'The episodes of the Star Wars original trilogy.')
|
||||
@graphQLClass
|
||||
enum Episode {
|
||||
NEWHOPE,
|
||||
EMPIRE,
|
||||
JEDI,
|
||||
}
|
|
@ -0,0 +1,12 @@
|
|||
// GENERATED CODE - DO NOT MODIFY BY HAND
|
||||
|
||||
part of 'episode.dart';
|
||||
|
||||
// **************************************************************************
|
||||
// _GraphQLGenerator
|
||||
// **************************************************************************
|
||||
|
||||
/// Auto-generated from [Episode].
|
||||
final GraphQLEnumType<String> episodeGraphQLType = enumTypeFromStrings(
|
||||
'Episode', const ['NEWHOPE', 'EMPIRE', 'JEDI'],
|
||||
description: 'The episodes of the Star Wars original trilogy.');
|
35
packages/graphql/example_star_wars/lib/src/models/human.dart
Normal file
35
packages/graphql/example_star_wars/lib/src/models/human.dart
Normal file
|
@ -0,0 +1,35 @@
|
|||
import 'package:angel_model/angel_model.dart';
|
||||
import 'package:angel_serialize/angel_serialize.dart';
|
||||
import 'package:collection/collection.dart';
|
||||
import 'package:graphql_schema/graphql_schema.dart';
|
||||
import 'character.dart';
|
||||
import 'episode.dart';
|
||||
part 'human.g.dart';
|
||||
|
||||
@serializable
|
||||
@graphQLClass
|
||||
abstract class _Human extends Model implements Character {
|
||||
// @GraphQLDocumentation(description: "This human's name, of course.")
|
||||
// String name;
|
||||
// List<Character> friends;
|
||||
// List<Episode> appearsIn;
|
||||
// List<Starship> starships;
|
||||
// int totalCredits;
|
||||
|
||||
String get id;
|
||||
|
||||
String get name;
|
||||
|
||||
List<Episode> get appearsIn;
|
||||
|
||||
List<Character> get friends;
|
||||
|
||||
int get totalCredits;
|
||||
|
||||
// Human(
|
||||
// {this.name,
|
||||
// this.friends,
|
||||
// this.appearsIn,
|
||||
// this.starships,
|
||||
// this.totalCredits});
|
||||
}
|
172
packages/graphql/example_star_wars/lib/src/models/human.g.dart
Normal file
172
packages/graphql/example_star_wars/lib/src/models/human.g.dart
Normal file
|
@ -0,0 +1,172 @@
|
|||
// GENERATED CODE - DO NOT MODIFY BY HAND
|
||||
|
||||
part of 'human.dart';
|
||||
|
||||
// **************************************************************************
|
||||
// JsonModelGenerator
|
||||
// **************************************************************************
|
||||
|
||||
@generatedSerializable
|
||||
class Human extends _Human {
|
||||
Human(
|
||||
{this.id,
|
||||
this.name,
|
||||
List<Episode> appearsIn,
|
||||
List<Character> friends,
|
||||
this.totalCredits,
|
||||
this.createdAt,
|
||||
this.updatedAt})
|
||||
: this.appearsIn = new List.unmodifiable(appearsIn ?? []),
|
||||
this.friends = new List.unmodifiable(friends ?? []);
|
||||
|
||||
@override
|
||||
final String id;
|
||||
|
||||
@override
|
||||
final String name;
|
||||
|
||||
@override
|
||||
final List<Episode> appearsIn;
|
||||
|
||||
@override
|
||||
final List<Character> friends;
|
||||
|
||||
@override
|
||||
final int totalCredits;
|
||||
|
||||
@override
|
||||
final DateTime createdAt;
|
||||
|
||||
@override
|
||||
final DateTime updatedAt;
|
||||
|
||||
Human copyWith(
|
||||
{String id,
|
||||
String name,
|
||||
List<Episode> appearsIn,
|
||||
List<Character> friends,
|
||||
int totalCredits,
|
||||
DateTime createdAt,
|
||||
DateTime updatedAt}) {
|
||||
return new Human(
|
||||
id: id ?? this.id,
|
||||
name: name ?? this.name,
|
||||
appearsIn: appearsIn ?? this.appearsIn,
|
||||
friends: friends ?? this.friends,
|
||||
totalCredits: totalCredits ?? this.totalCredits,
|
||||
createdAt: createdAt ?? this.createdAt,
|
||||
updatedAt: updatedAt ?? this.updatedAt);
|
||||
}
|
||||
|
||||
bool operator ==(other) {
|
||||
return other is _Human &&
|
||||
other.id == id &&
|
||||
other.name == name &&
|
||||
const ListEquality<Episode>(const DefaultEquality<Episode>())
|
||||
.equals(other.appearsIn, appearsIn) &&
|
||||
const ListEquality<Character>(const DefaultEquality<Character>())
|
||||
.equals(other.friends, friends) &&
|
||||
other.totalCredits == totalCredits &&
|
||||
other.createdAt == createdAt &&
|
||||
other.updatedAt == updatedAt;
|
||||
}
|
||||
|
||||
@override
|
||||
int get hashCode {
|
||||
return hashObjects(
|
||||
[id, name, appearsIn, friends, totalCredits, createdAt, updatedAt]);
|
||||
}
|
||||
|
||||
Map<String, dynamic> toJson() {
|
||||
return HumanSerializer.toMap(this);
|
||||
}
|
||||
}
|
||||
|
||||
// **************************************************************************
|
||||
// SerializerGenerator
|
||||
// **************************************************************************
|
||||
|
||||
abstract class HumanSerializer {
|
||||
static Human fromMap(Map map) {
|
||||
return new Human(
|
||||
id: map['id'] as String,
|
||||
name: map['name'] as String,
|
||||
appearsIn: map['appears_in'] is Iterable
|
||||
? (map['appears_in'] as Iterable).cast<Episode>().toList()
|
||||
: null,
|
||||
friends: map['friends'] is Iterable
|
||||
? (map['friends'] as Iterable).cast<Character>().toList()
|
||||
: null,
|
||||
totalCredits: map['total_credits'] as int,
|
||||
createdAt: map['created_at'] != null
|
||||
? (map['created_at'] is DateTime
|
||||
? (map['created_at'] as DateTime)
|
||||
: DateTime.parse(map['created_at'].toString()))
|
||||
: null,
|
||||
updatedAt: map['updated_at'] != null
|
||||
? (map['updated_at'] is DateTime
|
||||
? (map['updated_at'] as DateTime)
|
||||
: DateTime.parse(map['updated_at'].toString()))
|
||||
: null);
|
||||
}
|
||||
|
||||
static Map<String, dynamic> toMap(_Human model) {
|
||||
if (model == null) {
|
||||
return null;
|
||||
}
|
||||
return {
|
||||
'id': model.id,
|
||||
'name': model.name,
|
||||
'appears_in': model.appearsIn,
|
||||
'friends': model.friends,
|
||||
'total_credits': model.totalCredits,
|
||||
'created_at': model.createdAt?.toIso8601String(),
|
||||
'updated_at': model.updatedAt?.toIso8601String()
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
abstract class HumanFields {
|
||||
static const List<String> allFields = <String>[
|
||||
id,
|
||||
name,
|
||||
appearsIn,
|
||||
friends,
|
||||
totalCredits,
|
||||
createdAt,
|
||||
updatedAt
|
||||
];
|
||||
|
||||
static const String id = 'id';
|
||||
|
||||
static const String name = 'name';
|
||||
|
||||
static const String appearsIn = 'appears_in';
|
||||
|
||||
static const String friends = 'friends';
|
||||
|
||||
static const String totalCredits = 'total_credits';
|
||||
|
||||
static const String createdAt = 'created_at';
|
||||
|
||||
static const String updatedAt = 'updated_at';
|
||||
}
|
||||
|
||||
// **************************************************************************
|
||||
// _GraphQLGenerator
|
||||
// **************************************************************************
|
||||
|
||||
/// Auto-generated from [Human].
|
||||
final GraphQLObjectType humanGraphQLType =
|
||||
objectType('Human', isInterface: false, interfaces: [
|
||||
characterGraphQLType
|
||||
], fields: [
|
||||
field('id', graphQLString),
|
||||
field('name', graphQLString),
|
||||
field('appears_in', listOf(episodeGraphQLType)),
|
||||
field('friends', listOf(characterGraphQLType)),
|
||||
field('total_credits', graphQLInt),
|
||||
field('created_at', graphQLDate),
|
||||
field('updated_at', graphQLDate),
|
||||
field('idAsInt', graphQLInt)
|
||||
]);
|
|
@ -0,0 +1,5 @@
|
|||
export 'character.dart';
|
||||
export 'droid.dart';
|
||||
export 'episode.dart';
|
||||
export 'human.dart';
|
||||
export 'starship.dart';
|
|
@ -0,0 +1,11 @@
|
|||
import 'package:angel_model/angel_model.dart';
|
||||
import 'package:angel_serialize/angel_serialize.dart';
|
||||
import 'package:graphql_schema/graphql_schema.dart';
|
||||
part 'starship.g.dart';
|
||||
|
||||
@serializable
|
||||
@graphQLClass
|
||||
abstract class _Starship extends Model {
|
||||
String get name;
|
||||
int get length;
|
||||
}
|
|
@ -0,0 +1,130 @@
|
|||
// GENERATED CODE - DO NOT MODIFY BY HAND
|
||||
|
||||
part of 'starship.dart';
|
||||
|
||||
// **************************************************************************
|
||||
// JsonModelGenerator
|
||||
// **************************************************************************
|
||||
|
||||
@generatedSerializable
|
||||
class Starship extends _Starship {
|
||||
Starship({this.id, this.name, this.length, this.createdAt, this.updatedAt});
|
||||
|
||||
@override
|
||||
final String id;
|
||||
|
||||
@override
|
||||
final String name;
|
||||
|
||||
@override
|
||||
final int length;
|
||||
|
||||
@override
|
||||
final DateTime createdAt;
|
||||
|
||||
@override
|
||||
final DateTime updatedAt;
|
||||
|
||||
Starship copyWith(
|
||||
{String id,
|
||||
String name,
|
||||
int length,
|
||||
DateTime createdAt,
|
||||
DateTime updatedAt}) {
|
||||
return new Starship(
|
||||
id: id ?? this.id,
|
||||
name: name ?? this.name,
|
||||
length: length ?? this.length,
|
||||
createdAt: createdAt ?? this.createdAt,
|
||||
updatedAt: updatedAt ?? this.updatedAt);
|
||||
}
|
||||
|
||||
bool operator ==(other) {
|
||||
return other is _Starship &&
|
||||
other.id == id &&
|
||||
other.name == name &&
|
||||
other.length == length &&
|
||||
other.createdAt == createdAt &&
|
||||
other.updatedAt == updatedAt;
|
||||
}
|
||||
|
||||
@override
|
||||
int get hashCode {
|
||||
return hashObjects([id, name, length, createdAt, updatedAt]);
|
||||
}
|
||||
|
||||
Map<String, dynamic> toJson() {
|
||||
return StarshipSerializer.toMap(this);
|
||||
}
|
||||
}
|
||||
|
||||
// **************************************************************************
|
||||
// SerializerGenerator
|
||||
// **************************************************************************
|
||||
|
||||
abstract class StarshipSerializer {
|
||||
static Starship fromMap(Map map) {
|
||||
return new Starship(
|
||||
id: map['id'] as String,
|
||||
name: map['name'] as String,
|
||||
length: map['length'] as int,
|
||||
createdAt: map['created_at'] != null
|
||||
? (map['created_at'] is DateTime
|
||||
? (map['created_at'] as DateTime)
|
||||
: DateTime.parse(map['created_at'].toString()))
|
||||
: null,
|
||||
updatedAt: map['updated_at'] != null
|
||||
? (map['updated_at'] is DateTime
|
||||
? (map['updated_at'] as DateTime)
|
||||
: DateTime.parse(map['updated_at'].toString()))
|
||||
: null);
|
||||
}
|
||||
|
||||
static Map<String, dynamic> toMap(_Starship model) {
|
||||
if (model == null) {
|
||||
return null;
|
||||
}
|
||||
return {
|
||||
'id': model.id,
|
||||
'name': model.name,
|
||||
'length': model.length,
|
||||
'created_at': model.createdAt?.toIso8601String(),
|
||||
'updated_at': model.updatedAt?.toIso8601String()
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
abstract class StarshipFields {
|
||||
static const List<String> allFields = <String>[
|
||||
id,
|
||||
name,
|
||||
length,
|
||||
createdAt,
|
||||
updatedAt
|
||||
];
|
||||
|
||||
static const String id = 'id';
|
||||
|
||||
static const String name = 'name';
|
||||
|
||||
static const String length = 'length';
|
||||
|
||||
static const String createdAt = 'created_at';
|
||||
|
||||
static const String updatedAt = 'updated_at';
|
||||
}
|
||||
|
||||
// **************************************************************************
|
||||
// _GraphQLGenerator
|
||||
// **************************************************************************
|
||||
|
||||
/// Auto-generated from [Starship].
|
||||
final GraphQLObjectType starshipGraphQLType =
|
||||
objectType('Starship', isInterface: false, interfaces: [], fields: [
|
||||
field('id', graphQLString),
|
||||
field('name', graphQLString),
|
||||
field('length', graphQLInt),
|
||||
field('created_at', graphQLDate),
|
||||
field('updated_at', graphQLDate),
|
||||
field('idAsInt', graphQLInt)
|
||||
]);
|
|
@ -0,0 +1,35 @@
|
|||
import 'package:angel_http_exception/angel_http_exception.dart';
|
||||
import 'package:logging/logging.dart';
|
||||
import 'package:io/ansi.dart';
|
||||
|
||||
/// Prints the contents of a [LogRecord] with pretty colors.
|
||||
void prettyLog(LogRecord record) {
|
||||
var code = chooseLogColor(record.level);
|
||||
|
||||
if (record.error == null) print(code.wrap(record.toString()));
|
||||
|
||||
if (record.error != null) {
|
||||
var err = record.error;
|
||||
if (err is AngelHttpException && err.statusCode != 500) return;
|
||||
print(code.wrap(record.toString() + '\n'));
|
||||
print(code.wrap(err.toString()));
|
||||
|
||||
if (record.stackTrace != null) {
|
||||
print(code.wrap(record.stackTrace.toString()));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Chooses a color based on the logger [level].
|
||||
AnsiCode chooseLogColor(Level level) {
|
||||
if (level == Level.SHOUT)
|
||||
return backgroundRed;
|
||||
else if (level == Level.SEVERE)
|
||||
return red;
|
||||
else if (level == Level.WARNING)
|
||||
return yellow;
|
||||
else if (level == Level.INFO)
|
||||
return cyan;
|
||||
else if (level == Level.FINER || level == Level.FINEST) return lightGray;
|
||||
return resetAll;
|
||||
}
|
154
packages/graphql/example_star_wars/lib/star_wars.dart
Normal file
154
packages/graphql/example_star_wars/lib/star_wars.dart
Normal file
|
@ -0,0 +1,154 @@
|
|||
import 'dart:async';
|
||||
import 'dart:math';
|
||||
import 'package:angel_framework/angel_framework.dart';
|
||||
import 'package:angel_graphql/angel_graphql.dart';
|
||||
import 'package:graphql_schema/graphql_schema.dart';
|
||||
import 'package:graphql_server/graphql_server.dart';
|
||||
import 'package:graphql_server/mirrors.dart';
|
||||
import 'src/models/models.dart';
|
||||
|
||||
Future configureServer(Angel app) async {
|
||||
// Create standard Angel services. Note that these will also *automatically* be
|
||||
// exposed via a REST API as well.
|
||||
var droidService = app.use('/api/droids', MapService());
|
||||
var humansService = app.use('/api/humans', MapService());
|
||||
var starshipService = app.use('/api/starships', MapService());
|
||||
var rnd = Random();
|
||||
|
||||
// Create the GraphQL schema.
|
||||
// `package:graphql_generator` has generated schemas for some of our
|
||||
// classes.
|
||||
|
||||
// A Hero can be either a Droid or Human; create a union type that represents this.
|
||||
var heroType = GraphQLUnionType('Hero', [droidGraphQLType, humanGraphQLType]);
|
||||
|
||||
// Create the query type.
|
||||
//
|
||||
// Use the `resolveViaServiceIndex` helper to load data directly from an
|
||||
// Angel service.
|
||||
var queryType = objectType(
|
||||
'StarWarsQuery',
|
||||
description: 'A long time ago, in a galaxy far, far away...',
|
||||
fields: [
|
||||
field(
|
||||
'droids',
|
||||
listOf(droidGraphQLType.nonNullable()),
|
||||
description: 'All droids in the known galaxy.',
|
||||
resolve: resolveViaServiceIndex(droidService),
|
||||
),
|
||||
field(
|
||||
'humans',
|
||||
listOf(humanGraphQLType.nonNullable()),
|
||||
description: 'All humans in the known galaxy.',
|
||||
resolve: resolveViaServiceIndex(humansService),
|
||||
),
|
||||
field(
|
||||
'starships',
|
||||
listOf(starshipGraphQLType.nonNullable()),
|
||||
description: 'All starships in the known galaxy.',
|
||||
resolve: resolveViaServiceIndex(starshipService),
|
||||
),
|
||||
field(
|
||||
'hero',
|
||||
heroType,
|
||||
description:
|
||||
'Finds a random hero within the known galaxy, whether a Droid or Human.',
|
||||
inputs: [
|
||||
GraphQLFieldInput('ep', episodeGraphQLType),
|
||||
],
|
||||
resolve: randomHeroResolver(droidService, humansService, rnd),
|
||||
),
|
||||
],
|
||||
);
|
||||
|
||||
// Convert our object types to input objects, so that they can be passed to
|
||||
// mutations.
|
||||
var humanChangesType = humanGraphQLType.toInputObject('HumanChanges');
|
||||
|
||||
// Create the mutation type.
|
||||
var mutationType = objectType(
|
||||
'StarWarsMutation',
|
||||
fields: [
|
||||
// We'll use the `modify_human` mutation to modify a human in the database.
|
||||
field(
|
||||
'modify_human',
|
||||
humanGraphQLType.nonNullable(),
|
||||
description: 'Modifies a human in the database.',
|
||||
inputs: [
|
||||
GraphQLFieldInput('id', graphQLId.nonNullable()),
|
||||
GraphQLFieldInput('data', humanChangesType.nonNullable()),
|
||||
],
|
||||
resolve: resolveViaServiceModify(humansService),
|
||||
),
|
||||
],
|
||||
);
|
||||
|
||||
// Finally, create the schema.
|
||||
var schema = graphQLSchema(
|
||||
queryType: queryType,
|
||||
mutationType: mutationType,
|
||||
);
|
||||
|
||||
// Next, create a GraphQL object, which will be passed to `graphQLHttp`, and
|
||||
// used to mount a spec-compliant GraphQL endpoint on the server.
|
||||
//
|
||||
// The `mirrorsFieldResolver` is unnecessary in this case, because we are using
|
||||
// `Map`s only, but if our services returned concrete Dart objects, we'd need
|
||||
// this to allow GraphQL to read field values.
|
||||
var graphQL = GraphQL(schema, defaultFieldResolver: mirrorsFieldResolver);
|
||||
|
||||
// Mount the GraphQL endpoint.
|
||||
app.all('/graphql', graphQLHttp(graphQL));
|
||||
|
||||
// In development, we'll want to mount GraphiQL, for easy management of the database.
|
||||
if (!app.isProduction) {
|
||||
app.get('/graphiql', graphiQL());
|
||||
}
|
||||
|
||||
// Seed the database.
|
||||
var leia = await humansService.create({
|
||||
'name': 'Leia Organa',
|
||||
'appears_in': ['NEWHOPE', 'EMPIRE', 'JEDI'],
|
||||
'total_credits': 520,
|
||||
});
|
||||
|
||||
var lando = await humansService.create({
|
||||
'name': 'Lando Calrissian',
|
||||
'appears_in': ['EMPIRE', 'JEDI'],
|
||||
'total_credits': 525430,
|
||||
});
|
||||
|
||||
var hanSolo = await humansService.create({
|
||||
'name': 'Han Solo',
|
||||
'appears_in': ['NEWHOPE', 'EMPIRE', 'JEDI'],
|
||||
'total_credits': 23,
|
||||
'friends': [leia, lando],
|
||||
});
|
||||
|
||||
// Luke, of course.
|
||||
await humansService.create({
|
||||
'name': 'Luke Skywalker',
|
||||
'appears_in': ['NEWHOPE', 'EMPIRE', 'JEDI'],
|
||||
'total_credits': 682,
|
||||
'friends': [leia, hanSolo, lando],
|
||||
});
|
||||
}
|
||||
|
||||
GraphQLFieldResolver randomHeroResolver(
|
||||
Service droidService, Service humansService, Random rnd) {
|
||||
return (_, args) async {
|
||||
var allHeroes = [];
|
||||
var allDroids = await droidService.index();
|
||||
var allHumans = await humansService.index();
|
||||
allHeroes..addAll(allDroids)..addAll(allHumans);
|
||||
|
||||
// Ignore the annoying cast here, hopefully Dart 2 fixes cases like this
|
||||
allHeroes = allHeroes
|
||||
.where((m) =>
|
||||
!args.containsKey('ep') ||
|
||||
(m['appears_in'].contains(args['ep']) as bool))
|
||||
.toList();
|
||||
|
||||
return allHeroes.isEmpty ? null : allHeroes[rnd.nextInt(allHeroes.length)];
|
||||
};
|
||||
}
|
0
packages/graphql/example_star_wars/mono_pkg.yaml
Normal file
0
packages/graphql/example_star_wars/mono_pkg.yaml
Normal file
21
packages/graphql/example_star_wars/pubspec.yaml
Normal file
21
packages/graphql/example_star_wars/pubspec.yaml
Normal file
|
@ -0,0 +1,21 @@
|
|||
name: star_wars
|
||||
publish_to: none
|
||||
dependencies:
|
||||
#angel_file_service: ^1.0.0
|
||||
angel_graphql:
|
||||
path: ../angel_graphql
|
||||
angel_hot: ^2.0.0-alpha
|
||||
angel_serialize: ^2.0.0
|
||||
io: ^0.3.2
|
||||
dev_dependencies:
|
||||
angel_serialize_generator: ^2.0.0
|
||||
build_runner: ^1.0.0
|
||||
graphql_generator:
|
||||
path: ../graphql_generator
|
||||
dependency_overrides:
|
||||
graphql_parser:
|
||||
path: ../graphql_parser
|
||||
graphql_schema:
|
||||
path: ../graphql_schema
|
||||
graphql_server:
|
||||
path: ../graphql_server
|
11
packages/graphql/graphql.iml
Normal file
11
packages/graphql/graphql.iml
Normal file
|
@ -0,0 +1,11 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<module type="WEB_MODULE" version="4">
|
||||
<component name="NewModuleRootManager" inherit-compiler-output="true">
|
||||
<exclude-output />
|
||||
<content url="file://$MODULE_DIR$" />
|
||||
<orderEntry type="inheritedJdk" />
|
||||
<orderEntry type="sourceFolder" forTests="false" />
|
||||
<orderEntry type="library" name="Dart SDK" level="project" />
|
||||
<orderEntry type="library" name="Dart Packages" level="project" />
|
||||
</component>
|
||||
</module>
|
93
packages/graphql/graphql_generator/.gitignore
vendored
Normal file
93
packages/graphql/graphql_generator/.gitignore
vendored
Normal file
|
@ -0,0 +1,93 @@
|
|||
# See https://www.dartlang.org/tools/private-files.html
|
||||
|
||||
# Files and directories created by pub
|
||||
.buildlog
|
||||
.packages
|
||||
.project
|
||||
.pub/
|
||||
.scripts-bin/
|
||||
build/
|
||||
**/packages/
|
||||
|
||||
# Files created by dart2js
|
||||
# (Most Dart developers will use pub build to compile Dart, use/modify these
|
||||
# rules if you intend to use dart2js directly
|
||||
# Convention is to use extension '.dart.js' for Dart compiled to Javascript to
|
||||
# differentiate from explicit Javascript files)
|
||||
*.dart.js
|
||||
*.part.js
|
||||
*.js.deps
|
||||
*.js.map
|
||||
*.info.json
|
||||
|
||||
# Directory created by dartdoc
|
||||
doc/api/
|
||||
|
||||
# Don't commit pubspec lock file
|
||||
# (Library packages only! Remove pattern if developing an application package)
|
||||
pubspec.lock
|
||||
### Dart template
|
||||
# See https://www.dartlang.org/tools/private-files.html
|
||||
|
||||
# Files and directories created by pub
|
||||
|
||||
# SDK 1.20 and later (no longer creates packages directories)
|
||||
|
||||
# Older SDK versions
|
||||
# (Include if the minimum SDK version specified in pubsepc.yaml is earlier than 1.20)
|
||||
|
||||
|
||||
# Files created by dart2js
|
||||
# (Most Dart developers will use pub build to compile Dart, use/modify these
|
||||
# rules if you intend to use dart2js directly
|
||||
# Convention is to use extension '.dart.js' for Dart compiled to Javascript to
|
||||
# differentiate from explicit Javascript files)
|
||||
|
||||
# Directory created by dartdoc
|
||||
|
||||
# Don't commit pubspec lock file
|
||||
# (Library packages only! Remove pattern if developing an application package)
|
||||
### JetBrains template
|
||||
# Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm, CLion, Android Studio and Webstorm
|
||||
# Reference: https://intellij-support.jetbrains.com/hc/en-us/articles/206544839
|
||||
|
||||
# User-specific stuff:
|
||||
../.idea/workspace.xml
|
||||
.idea/**/tasks.xml
|
||||
.idea/dictionaries
|
||||
|
||||
# Sensitive or high-churn files:
|
||||
.idea/**/dataSources/
|
||||
.idea/**/dataSources.ids
|
||||
.idea/**/dataSources.xml
|
||||
.idea/**/dataSources.local.xml
|
||||
.idea/**/sqlDataSources.xml
|
||||
.idea/**/dynamic.xml
|
||||
.idea/**/uiDesigner.xml
|
||||
|
||||
# Gradle:
|
||||
.idea/**/gradle.xml
|
||||
.idea/**/libraries
|
||||
|
||||
# Mongo Explorer plugin:
|
||||
.idea/**/mongoSettings.xml
|
||||
|
||||
## File-based project format:
|
||||
*.iws
|
||||
|
||||
## Plugin-specific files:
|
||||
|
||||
# IntelliJ
|
||||
/out/
|
||||
|
||||
# mpeltonen/sbt-idea plugin
|
||||
.idea_modules/
|
||||
|
||||
# JIRA plugin
|
||||
atlassian-ide-plugin.xml
|
||||
|
||||
# Crashlytics plugin (for Android Studio and IntelliJ)
|
||||
com_crashlytics_export_strings.xml
|
||||
crashlytics.properties
|
||||
crashlytics-build.properties
|
||||
fabric.properties
|
9
packages/graphql/graphql_generator/CHANGELOG.md
Normal file
9
packages/graphql/graphql_generator/CHANGELOG.md
Normal file
|
@ -0,0 +1,9 @@
|
|||
# 1.0.0+1
|
||||
* Replace `snakeCase` with `camelCase`.
|
||||
|
||||
# 1.0.0
|
||||
* Apply `package:pedantic`.
|
||||
|
||||
# 1.0.0-rc.1
|
||||
* Add `CHANGELOG.md`, `example/main.dart`.
|
||||
* Add documentation to `README.md`.
|
21
packages/graphql/graphql_generator/LICENSE
Normal file
21
packages/graphql/graphql_generator/LICENSE
Normal file
|
@ -0,0 +1,21 @@
|
|||
MIT License
|
||||
|
||||
Copyright (c) 2017 The Angel Framework
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
50
packages/graphql/graphql_generator/README.md
Normal file
50
packages/graphql/graphql_generator/README.md
Normal file
|
@ -0,0 +1,50 @@
|
|||
# graphql_generator
|
||||
[![Pub](https://img.shields.io/pub/v/graphql_generator.svg)](https://pub.dartlang.org/packages/graphql_generator)
|
||||
[![build status](https://travis-ci.org/angel-dart/graphql.svg)](https://travis-ci.org/angel-dart/graphql)
|
||||
|
||||
Generates `package:graphql_schema` schemas for
|
||||
annotated class.
|
||||
|
||||
Replaces `convertDartType` from `package:graphql_server`.
|
||||
|
||||
## Usage
|
||||
Usage is very simple. You just need a `@graphQLClass` or `@GraphQLClass()` annotation
|
||||
on any class you want to generate an object type for.
|
||||
|
||||
Individual fields can have a `@GraphQLDocumentation()` annotation, to provide information
|
||||
like descriptions, deprecation reasons, etc.
|
||||
|
||||
```dart
|
||||
@graphQLClass
|
||||
class Todo {
|
||||
String text;
|
||||
|
||||
@GraphQLDocumentation(description: 'Whether this item is complete.')
|
||||
bool isComplete;
|
||||
}
|
||||
|
||||
void main() {
|
||||
print(todoGraphQLType.fields.map((f) => f.name));
|
||||
}
|
||||
```
|
||||
|
||||
The following is generated (as of April 18th, 2019):
|
||||
|
||||
```dart
|
||||
// GENERATED CODE - DO NOT MODIFY BY HAND
|
||||
|
||||
part of 'main.dart';
|
||||
|
||||
// **************************************************************************
|
||||
// _GraphQLGenerator
|
||||
// **************************************************************************
|
||||
|
||||
/// Auto-generated from [Todo].
|
||||
final GraphQLObjectType todoGraphQLType = objectType('Todo',
|
||||
isInterface: false,
|
||||
interfaces: [],
|
||||
fields: [
|
||||
field('text', graphQLString),
|
||||
field('isComplete', graphQLBoolean)
|
||||
]);
|
||||
```
|
4
packages/graphql/graphql_generator/analysis_options.yaml
Normal file
4
packages/graphql/graphql_generator/analysis_options.yaml
Normal file
|
@ -0,0 +1,4 @@
|
|||
include: package:pedantic/analysis_options.yaml
|
||||
analyzer:
|
||||
strong-mode:
|
||||
implicit-casts: false
|
14
packages/graphql/graphql_generator/build.yaml
Normal file
14
packages/graphql/graphql_generator/build.yaml
Normal file
|
@ -0,0 +1,14 @@
|
|||
builders:
|
||||
graphql:
|
||||
import: "package:graphql_generator/graphql_generator.dart"
|
||||
builder_factories:
|
||||
- graphQLBuilder
|
||||
auto_apply: root_package
|
||||
build_to: cache
|
||||
build_extensions:
|
||||
.dart:
|
||||
- graphql_generator.g.part
|
||||
required_inputs:
|
||||
- angel_serialize.g.part
|
||||
applies_builders:
|
||||
- source_gen|combining_builder
|
14
packages/graphql/graphql_generator/example/main.dart
Normal file
14
packages/graphql/graphql_generator/example/main.dart
Normal file
|
@ -0,0 +1,14 @@
|
|||
import 'package:graphql_schema/graphql_schema.dart';
|
||||
part 'main.g.dart';
|
||||
|
||||
@graphQLClass
|
||||
class TodoItem {
|
||||
String text;
|
||||
|
||||
@GraphQLDocumentation(description: 'Whether this item is complete.')
|
||||
bool isComplete;
|
||||
}
|
||||
|
||||
void main() {
|
||||
print(todoItemGraphQLType.fields.map((f) => f.name));
|
||||
}
|
16
packages/graphql/graphql_generator/example/main.g.dart
Normal file
16
packages/graphql/graphql_generator/example/main.g.dart
Normal file
|
@ -0,0 +1,16 @@
|
|||
// GENERATED CODE - DO NOT MODIFY BY HAND
|
||||
|
||||
part of 'main.dart';
|
||||
|
||||
// **************************************************************************
|
||||
// _GraphQLGenerator
|
||||
// **************************************************************************
|
||||
|
||||
/// Auto-generated from [TodoItem].
|
||||
final GraphQLObjectType todoItemGraphQLType = objectType('TodoItem',
|
||||
isInterface: false,
|
||||
interfaces: [],
|
||||
fields: [
|
||||
field('text', graphQLString),
|
||||
field('isComplete', graphQLBoolean)
|
||||
]);
|
233
packages/graphql/graphql_generator/lib/graphql_generator.dart
Normal file
233
packages/graphql/graphql_generator/lib/graphql_generator.dart
Normal file
|
@ -0,0 +1,233 @@
|
|||
import 'dart:async';
|
||||
import 'dart:mirrors';
|
||||
import 'package:analyzer/dart/element/element.dart';
|
||||
import 'package:analyzer/dart/element/type.dart';
|
||||
import 'package:angel_model/angel_model.dart';
|
||||
import 'package:angel_serialize_generator/build_context.dart';
|
||||
import 'package:angel_serialize_generator/context.dart';
|
||||
import 'package:build/build.dart';
|
||||
import 'package:code_builder/code_builder.dart';
|
||||
import 'package:graphql_schema/graphql_schema.dart';
|
||||
import 'package:recase/recase.dart';
|
||||
import 'package:source_gen/source_gen.dart';
|
||||
|
||||
/// Generates GraphQL schemas, statically.
|
||||
Builder graphQLBuilder(_) {
|
||||
return SharedPartBuilder([_GraphQLGenerator()], 'graphql_generator');
|
||||
}
|
||||
|
||||
var _docComment = RegExp(r'^/// ', multiLine: true);
|
||||
var _graphQLDoc = TypeChecker.fromRuntime(GraphQLDocumentation);
|
||||
var _graphQLClassTypeChecker = TypeChecker.fromRuntime(GraphQLClass);
|
||||
|
||||
class _GraphQLGenerator extends GeneratorForAnnotation<GraphQLClass> {
|
||||
@override
|
||||
Future<String> generateForAnnotatedElement(
|
||||
Element element, ConstantReader annotation, BuildStep buildStep) async {
|
||||
if (element is ClassElement) {
|
||||
var ctx = element.isEnum
|
||||
? null
|
||||
: await buildContext(
|
||||
element,
|
||||
annotation,
|
||||
buildStep,
|
||||
buildStep.resolver,
|
||||
serializableTypeChecker.hasAnnotationOf(element));
|
||||
var lib = buildSchemaLibrary(element, ctx, annotation);
|
||||
return lib.accept(DartEmitter()).toString();
|
||||
} else {
|
||||
throw UnsupportedError('@GraphQLClass() is only supported on classes.');
|
||||
}
|
||||
}
|
||||
|
||||
bool isInterface(ClassElement clazz) {
|
||||
return clazz.isAbstract && !serializableTypeChecker.hasAnnotationOf(clazz);
|
||||
}
|
||||
|
||||
bool _isGraphQLClass(InterfaceType clazz) {
|
||||
var search = clazz;
|
||||
|
||||
while (search != null) {
|
||||
if (_graphQLClassTypeChecker.hasAnnotationOf(search.element)) return true;
|
||||
search = search.superclass;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
Expression _inferType(String className, String name, DartType type) {
|
||||
// Firstly, check if it's a GraphQL class.
|
||||
if (type is InterfaceType && _isGraphQLClass(type)) {
|
||||
var c = type;
|
||||
var name = serializableTypeChecker.hasAnnotationOf(c.element) &&
|
||||
c.name.startsWith('_')
|
||||
? c.name.substring(1)
|
||||
: c.name;
|
||||
var rc = ReCase(name);
|
||||
return refer('${rc.camelCase}GraphQLType');
|
||||
}
|
||||
|
||||
// Next, check if this is the "id" field of a `Model`.
|
||||
if (TypeChecker.fromRuntime(Model).isAssignableFromType(type) &&
|
||||
name == 'id') {
|
||||
return refer('graphQLId');
|
||||
}
|
||||
|
||||
var primitive = {
|
||||
String: 'graphQLString',
|
||||
int: 'graphQLInt',
|
||||
double: 'graphQLFloat',
|
||||
bool: 'graphQLBoolean',
|
||||
DateTime: 'graphQLDate'
|
||||
};
|
||||
|
||||
// Check to see if it's a primitive type.
|
||||
for (var entry in primitive.entries) {
|
||||
if (TypeChecker.fromRuntime(entry.key).isAssignableFromType(type)) {
|
||||
return refer(entry.value);
|
||||
}
|
||||
}
|
||||
|
||||
// Next, check to see if it's a List.
|
||||
if (type is InterfaceType &&
|
||||
type.typeArguments.isNotEmpty &&
|
||||
TypeChecker.fromRuntime(Iterable).isAssignableFromType(type)) {
|
||||
var arg = type.typeArguments[0];
|
||||
var inner = _inferType(className, name, arg);
|
||||
return refer('listOf').call([inner]);
|
||||
}
|
||||
|
||||
// Nothing else is allowed.
|
||||
throw 'Cannot infer the GraphQL type for field $className.$name (type=$type).';
|
||||
}
|
||||
|
||||
void _applyDescription(
|
||||
Map<String, Expression> named, Element element, String docComment) {
|
||||
String docString = docComment;
|
||||
|
||||
if (docString == null && _graphQLDoc.hasAnnotationOf(element)) {
|
||||
var ann = _graphQLDoc.firstAnnotationOf(element);
|
||||
var cr = ConstantReader(ann);
|
||||
docString = cr.peek('description')?.stringValue;
|
||||
}
|
||||
|
||||
if (docString != null) {
|
||||
named['description'] = literalString(
|
||||
docString.replaceAll(_docComment, '').replaceAll('\n', '\\n'));
|
||||
}
|
||||
}
|
||||
|
||||
Library buildSchemaLibrary(
|
||||
ClassElement clazz, BuildContext ctx, ConstantReader ann) {
|
||||
return Library((b) {
|
||||
// Generate a top-level xGraphQLType object
|
||||
|
||||
if (clazz.isEnum) {
|
||||
b.body.add(Field((b) {
|
||||
// enumTypeFromStrings(String name, List<String> values, {String description}
|
||||
var args = <Expression>[literalString(clazz.name)];
|
||||
var values =
|
||||
clazz.fields.where((f) => f.isEnumConstant).map((f) => f.name);
|
||||
var named = <String, Expression>{};
|
||||
_applyDescription(named, clazz, clazz.documentationComment);
|
||||
args.add(literalConstList(values.map(literalString).toList()));
|
||||
|
||||
b
|
||||
..name = ReCase(clazz.name).camelCase + 'GraphQLType'
|
||||
..docs.add('/// Auto-generated from [${clazz.name}].')
|
||||
..type = TypeReference((b) => b
|
||||
..symbol = 'GraphQLEnumType'
|
||||
..types.add(refer('String')))
|
||||
..modifier = FieldModifier.final$
|
||||
..assignment = refer('enumTypeFromStrings').call(args, named).code;
|
||||
}));
|
||||
} else {
|
||||
b.body.add(Field((b) {
|
||||
var args = <Expression>[literalString(ctx.modelClassName)];
|
||||
var named = <String, Expression>{
|
||||
'isInterface': literalBool(isInterface(clazz))
|
||||
};
|
||||
|
||||
// Add documentation
|
||||
_applyDescription(named, clazz, clazz.documentationComment);
|
||||
|
||||
// Add interfaces
|
||||
var interfaces = clazz.interfaces.where(_isGraphQLClass).map((c) {
|
||||
var name = serializableTypeChecker.hasAnnotationOf(c.element) &&
|
||||
c.name.startsWith('_')
|
||||
? c.name.substring(1)
|
||||
: c.name;
|
||||
var rc = ReCase(name);
|
||||
return refer('${rc.camelCase}GraphQLType');
|
||||
});
|
||||
named['interfaces'] = literalList(interfaces);
|
||||
|
||||
// Add fields
|
||||
var ctxFields = ctx.fields.toList();
|
||||
|
||||
// Also incorporate parent fields.
|
||||
var search = clazz.type;
|
||||
while (search != null &&
|
||||
!TypeChecker.fromRuntime(Object).isExactlyType(search)) {
|
||||
for (var field in search.element.fields) {
|
||||
if (!ctxFields.any((f) => f.name == field.name)) {
|
||||
ctxFields.add(field);
|
||||
}
|
||||
}
|
||||
|
||||
search = search.superclass;
|
||||
}
|
||||
|
||||
var fields = <Expression>[];
|
||||
for (var field in ctxFields) {
|
||||
var named = <String, Expression>{};
|
||||
var originalField = clazz.fields
|
||||
.firstWhere((f) => f.name == field.name, orElse: () => null);
|
||||
|
||||
// Check if it is deprecated.
|
||||
var depEl = originalField?.getter ?? originalField ?? field;
|
||||
var depAnn =
|
||||
TypeChecker.fromRuntime(Deprecated).firstAnnotationOf(depEl);
|
||||
if (depAnn != null) {
|
||||
var dep = ConstantReader(depAnn);
|
||||
var reason = dep.peek('messages')?.stringValue ??
|
||||
dep.peek('expires')?.stringValue ??
|
||||
'Expires: ${deprecated.message}.';
|
||||
named['deprecationReason'] = literalString(reason);
|
||||
}
|
||||
|
||||
// Description finder...
|
||||
_applyDescription(
|
||||
named,
|
||||
originalField?.getter ?? originalField ?? field,
|
||||
originalField?.getter?.documentationComment ??
|
||||
originalField?.documentationComment);
|
||||
|
||||
// Pick the type.
|
||||
var doc = _graphQLDoc.firstAnnotationOf(depEl);
|
||||
Expression type;
|
||||
if (doc != null) {
|
||||
var cr = ConstantReader(doc);
|
||||
var typeName = cr.peek('typeName')?.symbolValue;
|
||||
if (typeName != null)
|
||||
type = refer(MirrorSystem.getName(typeName));
|
||||
}
|
||||
|
||||
fields.add(refer('field').call([
|
||||
literalString(ctx.resolveFieldName(field.name)),
|
||||
type ??= _inferType(clazz.name, field.name, field.type)
|
||||
], named));
|
||||
}
|
||||
named['fields'] = literalList(fields);
|
||||
|
||||
b
|
||||
..name = ctx.modelClassNameRecase.camelCase + 'GraphQLType'
|
||||
..docs.add('/// Auto-generated from [${ctx.modelClassName}].')
|
||||
..type = refer('GraphQLObjectType')
|
||||
..modifier = FieldModifier.final$
|
||||
..assignment = refer('objectType').call(args, named).code;
|
||||
}));
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
0
packages/graphql/graphql_generator/mono_pkg.yaml
Normal file
0
packages/graphql/graphql_generator/mono_pkg.yaml
Normal file
20
packages/graphql/graphql_generator/pubspec.yaml
Normal file
20
packages/graphql/graphql_generator/pubspec.yaml
Normal file
|
@ -0,0 +1,20 @@
|
|||
name: graphql_generator
|
||||
version: 1.0.0+1
|
||||
description: Generates GraphQL schemas from Dart classes, for use with pkg:graphql_server.
|
||||
author: Tobe O <thosakwe@gmail.com>
|
||||
homepage: https://github.com/angel-dart/graphql
|
||||
environment:
|
||||
sdk: ">=2.0.0 <3.0.0"
|
||||
dependencies:
|
||||
analyzer: ">=0.27.1 <2.0.0"
|
||||
angel_model: ^1.0.0
|
||||
angel_serialize_generator: ^2.0.0
|
||||
build: ^1.0.0
|
||||
build_config: ^0.3.0
|
||||
code_builder: ^3.0.0
|
||||
graphql_schema: ^1.0.2
|
||||
recase: ^2.0.0
|
||||
source_gen: ^0.9.4
|
||||
dev_dependencies:
|
||||
build_runner: ^1.0.0
|
||||
pedantic: ^1.0.0
|
95
packages/graphql/graphql_parser/.gitignore
vendored
Normal file
95
packages/graphql/graphql_parser/.gitignore
vendored
Normal file
|
@ -0,0 +1,95 @@
|
|||
# See https://www.dartlang.org/tools/private-files.html
|
||||
|
||||
# Files and directories created by pub
|
||||
.buildlog
|
||||
.packages
|
||||
.project
|
||||
.pub/
|
||||
.scripts-bin/
|
||||
build/
|
||||
**/packages/
|
||||
|
||||
# Files created by dart2js
|
||||
# (Most Dart developers will use pub build to compile Dart, use/modify these
|
||||
# rules if you intend to use dart2js directly
|
||||
# Convention is to use extension '.dart.js' for Dart compiled to Javascript to
|
||||
# differentiate from explicit Javascript files)
|
||||
*.dart.js
|
||||
*.part.js
|
||||
*.js.deps
|
||||
*.js.map
|
||||
*.info.json
|
||||
|
||||
# Directory created by dartdoc
|
||||
doc/api/
|
||||
|
||||
# Don't commit pubspec lock file
|
||||
# (Library packages only! Remove pattern if developing an application package)
|
||||
pubspec.lock
|
||||
### Dart template
|
||||
# See https://www.dartlang.org/tools/private-files.html
|
||||
|
||||
# Files and directories created by pub
|
||||
|
||||
# SDK 1.20 and later (no longer creates packages directories)
|
||||
|
||||
# Older SDK versions
|
||||
# (Include if the minimum SDK version specified in pubsepc.yaml is earlier than 1.20)
|
||||
|
||||
|
||||
# Files created by dart2js
|
||||
# (Most Dart developers will use pub build to compile Dart, use/modify these
|
||||
# rules if you intend to use dart2js directly
|
||||
# Convention is to use extension '.dart.js' for Dart compiled to Javascript to
|
||||
# differentiate from explicit Javascript files)
|
||||
|
||||
# Directory created by dartdoc
|
||||
|
||||
# Don't commit pubspec lock file
|
||||
# (Library packages only! Remove pattern if developing an application package)
|
||||
### JetBrains template
|
||||
# Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm, CLion, Android Studio and Webstorm
|
||||
# Reference: https://intellij-support.jetbrains.com/hc/en-us/articles/206544839
|
||||
|
||||
# User-specific stuff:
|
||||
../.idea/workspace.xml
|
||||
.idea/**/tasks.xml
|
||||
.idea/dictionaries
|
||||
|
||||
# Sensitive or high-churn files:
|
||||
.idea/**/dataSources/
|
||||
.idea/**/dataSources.ids
|
||||
.idea/**/dataSources.xml
|
||||
.idea/**/dataSources.local.xml
|
||||
.idea/**/sqlDataSources.xml
|
||||
.idea/**/dynamic.xml
|
||||
.idea/**/uiDesigner.xml
|
||||
|
||||
# Gradle:
|
||||
.idea/**/gradle.xml
|
||||
.idea/**/libraries
|
||||
|
||||
# Mongo Explorer plugin:
|
||||
.idea/**/mongoSettings.xml
|
||||
|
||||
## File-based project format:
|
||||
*.iws
|
||||
|
||||
## Plugin-specific files:
|
||||
|
||||
# IntelliJ
|
||||
/out/
|
||||
|
||||
# mpeltonen/sbt-idea plugin
|
||||
.idea_modules/
|
||||
|
||||
# JIRA plugin
|
||||
atlassian-ide-plugin.xml
|
||||
|
||||
# Crashlytics plugin (for Android Studio and IntelliJ)
|
||||
com_crashlytics_export_strings.xml
|
||||
crashlytics.properties
|
||||
crashlytics-build.properties
|
||||
fabric.properties
|
||||
|
||||
.dart_tool
|
28
packages/graphql/graphql_parser/CHANGELOG.md
Normal file
28
packages/graphql/graphql_parser/CHANGELOG.md
Normal file
|
@ -0,0 +1,28 @@
|
|||
# 1.2.0
|
||||
* Combine `ValueContext` and `VariableContext` into a single `InputValueContext` supertype.
|
||||
* Add `T computeValue(Map<String, dynamic> variables);`
|
||||
* Resolve [#23](https://github.com/angel-dart/graphql/issues/23).
|
||||
* Deprecate old `ValueOrVariable` class, and parser/AST methods related to it.
|
||||
|
||||
# 1.1.4
|
||||
* Fix broken int variable parsing - https://github.com/angel-dart/graphql/pull/32
|
||||
|
||||
# 1.1.3
|
||||
* Add `Parser.nextName`, and remove all formerly-reserved words from the lexer.
|
||||
Resolves [#19](https://github.com/angel-dart/graphql/issues/19).
|
||||
|
||||
# 1.1.2
|
||||
* Parse the `subscription` keyword.
|
||||
|
||||
# 1.1.1
|
||||
* Pubspec updates for Dart 2.
|
||||
|
||||
# 1.1.0
|
||||
* Removed `GraphQLVisitor`.
|
||||
* Enable parsing operations without an explicit
|
||||
name.
|
||||
* Parse `null`.
|
||||
* Completely ignore commas.
|
||||
* Ignore Unicode BOM, as per the spec.
|
||||
* Parse object values.
|
||||
* Parse enum values.
|
21
packages/graphql/graphql_parser/LICENSE
Normal file
21
packages/graphql/graphql_parser/LICENSE
Normal file
|
@ -0,0 +1,21 @@
|
|||
MIT License
|
||||
|
||||
Copyright (c) 2017 The Angel Framework
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
47
packages/graphql/graphql_parser/README.md
Normal file
47
packages/graphql/graphql_parser/README.md
Normal file
|
@ -0,0 +1,47 @@
|
|||
# graphql_parser
|
||||
[![Pub](https://img.shields.io/pub/v/graphql_parser.svg)](https://pub.dartlang.org/packages/graphql_parser)
|
||||
[![build status](https://travis-ci.org/angel-dart/graphql.svg)](https://travis-ci.org/angel-dart/graphql)
|
||||
|
||||
Parses GraphQL queries and schemas.
|
||||
|
||||
*This library is merely a parser/visitor*. Any sort of actual GraphQL API functionality must be implemented by you,
|
||||
or by a third-party package.
|
||||
|
||||
[Angel framework](https://angel-dart.github.io)
|
||||
users should consider
|
||||
[`package:angel_graphql`](https://pub.dartlang.org/packages/angel_graphql)
|
||||
as a dead-simple way to add GraphQL functionality to their servers.
|
||||
|
||||
# Installation
|
||||
Add `graphql_parser` as a dependency in your `pubspec.yaml` file:
|
||||
|
||||
```yaml
|
||||
dependencies:
|
||||
graphql_parser: ^1.0.0
|
||||
```
|
||||
|
||||
# Usage
|
||||
The AST featured in this library was originally directly based off this ANTLR4 grammar created by Joseph T. McBride:
|
||||
https://github.com/antlr/grammars-v4/blob/master/graphql/GraphQL.g4
|
||||
|
||||
It has since been updated to reflect upon the grammar in the official GraphQL
|
||||
specification (
|
||||
[June 2018](https://facebook.github.io/graphql/June2018/)).
|
||||
|
||||
```dart
|
||||
import 'package:graphql_parser/graphql_parser.dart';
|
||||
|
||||
doSomething(String text) {
|
||||
var tokens = scan(text);
|
||||
var parser = Parser(tokens);
|
||||
|
||||
if (parser.errors.isNotEmpty) {
|
||||
// Handle errors...
|
||||
}
|
||||
|
||||
// Parse the GraphQL document using recursive descent
|
||||
var doc = parser.parseDocument();
|
||||
|
||||
// Do something with the parsed GraphQL document...
|
||||
}
|
||||
```
|
4
packages/graphql/graphql_parser/analysis_options.yaml
Normal file
4
packages/graphql/graphql_parser/analysis_options.yaml
Normal file
|
@ -0,0 +1,4 @@
|
|||
include: package:pedantic/analysis_options.yaml
|
||||
analyzer:
|
||||
strong-mode:
|
||||
implicit-casts: false
|
26
packages/graphql/graphql_parser/example/example.dart
Normal file
26
packages/graphql/graphql_parser/example/example.dart
Normal file
|
@ -0,0 +1,26 @@
|
|||
import 'package:graphql_parser/graphql_parser.dart';
|
||||
|
||||
final String text = '''
|
||||
{
|
||||
project(name: "GraphQL") {
|
||||
tagline
|
||||
}
|
||||
}
|
||||
'''
|
||||
.trim();
|
||||
|
||||
main() {
|
||||
var tokens = scan(text);
|
||||
var parser = Parser(tokens);
|
||||
var doc = parser.parseDocument();
|
||||
|
||||
var operation = doc.definitions.first as OperationDefinitionContext;
|
||||
|
||||
var projectField = operation.selectionSet.selections.first.field;
|
||||
print(projectField.fieldName.name); // project
|
||||
print(projectField.arguments.first.name); // name
|
||||
print(projectField.arguments.first.value); // GraphQL
|
||||
|
||||
var taglineField = projectField.selectionSet.selections.first.field;
|
||||
print(taglineField.fieldName.name); // tagline
|
||||
}
|
15
packages/graphql/graphql_parser/graphql_parser.iml
Normal file
15
packages/graphql/graphql_parser/graphql_parser.iml
Normal file
|
@ -0,0 +1,15 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<module type="JAVA_MODULE" version="4">
|
||||
<component name="NewModuleRootManager" inherit-compiler-output="true">
|
||||
<exclude-output />
|
||||
<content url="file://$MODULE_DIR$">
|
||||
<excludeFolder url="file://$MODULE_DIR$/.dart_tool" />
|
||||
<excludeFolder url="file://$MODULE_DIR$/.pub" />
|
||||
<excludeFolder url="file://$MODULE_DIR$/build" />
|
||||
</content>
|
||||
<orderEntry type="inheritedJdk" />
|
||||
<orderEntry type="sourceFolder" forTests="false" />
|
||||
<orderEntry type="library" name="Dart SDK" level="project" />
|
||||
<orderEntry type="library" name="Dart Packages" level="project" />
|
||||
</component>
|
||||
</module>
|
2
packages/graphql/graphql_parser/lib/graphql_parser.dart
Normal file
2
packages/graphql/graphql_parser/lib/graphql_parser.dart
Normal file
|
@ -0,0 +1,2 @@
|
|||
export 'src/language/ast/ast.dart';
|
||||
export 'src/language/language.dart';
|
|
@ -0,0 +1,33 @@
|
|||
import 'package:source_span/source_span.dart';
|
||||
import '../token.dart';
|
||||
import 'node.dart';
|
||||
|
||||
/// An alternate name for a field within a [SelectionSet].
|
||||
class AliasContext extends Node {
|
||||
/// The source tokens.
|
||||
final Token nameToken1, colonToken, nameToken2;
|
||||
|
||||
AliasContext(this.nameToken1, this.colonToken, this.nameToken2);
|
||||
|
||||
/// Use [nameToken1] instead.
|
||||
@deprecated
|
||||
Token get NAME1 => nameToken1;
|
||||
|
||||
/// Use [colonToken] instead.
|
||||
@deprecated
|
||||
Token get COLON => colonToken;
|
||||
|
||||
/// Use [nameToken2] instead.
|
||||
@deprecated
|
||||
Token get NAME2 => nameToken2;
|
||||
|
||||
/// The aliased name of the value.
|
||||
String get alias => nameToken1.text;
|
||||
|
||||
/// The actual name of the value.
|
||||
String get name => nameToken2.text;
|
||||
|
||||
@override
|
||||
FileSpan get span =>
|
||||
nameToken1.span.expand(colonToken.span).expand(nameToken2.span);
|
||||
}
|
|
@ -0,0 +1,34 @@
|
|||
import 'package:source_span/source_span.dart';
|
||||
import '../token.dart';
|
||||
import 'node.dart';
|
||||
import 'input_value.dart';
|
||||
|
||||
/// An argument passed to a [FieldContext].
|
||||
class ArgumentContext extends Node {
|
||||
/// The source tokens.
|
||||
final Token nameToken, colonToken;
|
||||
|
||||
/// The value of the argument.
|
||||
final InputValueContext value;
|
||||
|
||||
ArgumentContext(this.nameToken, this.colonToken, this.value);
|
||||
|
||||
/// Use [value] instead.
|
||||
@deprecated
|
||||
InputValueContext get valueOrVariable => value;
|
||||
|
||||
/// Use [nameToken] instead.
|
||||
@deprecated
|
||||
Token get NAME => nameToken;
|
||||
|
||||
/// Use [colonToken] instead.
|
||||
@deprecated
|
||||
Token get COLON => colonToken;
|
||||
|
||||
/// The name of the argument, as a [String].
|
||||
String get name => nameToken.text;
|
||||
|
||||
@override
|
||||
FileSpan get span =>
|
||||
nameToken.span.expand(colonToken.span).expand(value.span);
|
||||
}
|
|
@ -0,0 +1,34 @@
|
|||
import 'package:source_span/source_span.dart';
|
||||
import '../token.dart';
|
||||
import 'input_value.dart';
|
||||
|
||||
/// A GraphQL list value literal.
|
||||
class ListValueContext extends InputValueContext {
|
||||
/// The source tokens.
|
||||
final Token lBracketToken, rBracketToken;
|
||||
|
||||
/// The child values.
|
||||
final List<InputValueContext> values = [];
|
||||
|
||||
ListValueContext(this.lBracketToken, this.rBracketToken);
|
||||
|
||||
/// Use [lBracketToken] instead.
|
||||
@deprecated
|
||||
Token get LBRACKET => lBracketToken;
|
||||
|
||||
/// Use [rBracketToken] instead.
|
||||
@deprecated
|
||||
Token get RBRACKET => rBracketToken;
|
||||
|
||||
@override
|
||||
FileSpan get span {
|
||||
var out =
|
||||
values.fold<FileSpan>(lBracketToken.span, (o, v) => o.expand(v.span));
|
||||
return out.expand(rBracketToken.span);
|
||||
}
|
||||
|
||||
@override
|
||||
computeValue(Map<String, dynamic> variables) {
|
||||
return values.map((v) => v.computeValue(variables)).toList();
|
||||
}
|
||||
}
|
|
@ -0,0 +1,31 @@
|
|||
library graphql_parser.language.ast;
|
||||
|
||||
export 'alias.dart';
|
||||
export 'array_value.dart';
|
||||
export 'argument.dart';
|
||||
export 'boolean_value.dart';
|
||||
export 'default_value.dart';
|
||||
export 'definition.dart';
|
||||
export 'deprecated_value.dart';
|
||||
export 'directive.dart';
|
||||
export 'document.dart';
|
||||
export 'field.dart';
|
||||
export 'field_name.dart';
|
||||
export 'fragment_definition.dart';
|
||||
export 'fragment_spread.dart';
|
||||
export 'inline_fragment.dart';
|
||||
export 'input_value.dart';
|
||||
export 'list_type.dart';
|
||||
export 'misc_value.dart';
|
||||
export 'node.dart';
|
||||
export 'number_value.dart';
|
||||
export 'operation_definition.dart';
|
||||
export 'selection.dart';
|
||||
export 'selection_set.dart';
|
||||
export 'string_value.dart';
|
||||
export 'type.dart';
|
||||
export 'type_condition.dart';
|
||||
export 'type_name.dart';
|
||||
export 'variable.dart';
|
||||
export 'variable_definition.dart';
|
||||
export 'variable_definitions.dart';
|
|
@ -0,0 +1,28 @@
|
|||
import 'package:source_span/source_span.dart';
|
||||
import 'input_value.dart';
|
||||
import '../token.dart';
|
||||
|
||||
/// A GraphQL boolean value literal.
|
||||
class BooleanValueContext extends InputValueContext<bool> {
|
||||
bool _valueCache;
|
||||
|
||||
/// The source token.
|
||||
final Token booleanToken;
|
||||
|
||||
BooleanValueContext(this.booleanToken) {
|
||||
assert(booleanToken?.text == 'true' || booleanToken?.text == 'false');
|
||||
}
|
||||
|
||||
/// The [bool] value of this literal.
|
||||
bool get booleanValue => _valueCache ??= booleanToken.text == 'true';
|
||||
|
||||
/// Use [booleanToken] instead.
|
||||
@deprecated
|
||||
Token get BOOLEAN => booleanToken;
|
||||
|
||||
@override
|
||||
FileSpan get span => booleanToken.span;
|
||||
|
||||
@override
|
||||
bool computeValue(Map<String, dynamic> variables) => booleanValue;
|
||||
}
|
|
@ -0,0 +1,22 @@
|
|||
import 'package:source_span/source_span.dart';
|
||||
import '../token.dart';
|
||||
import 'input_value.dart';
|
||||
import 'node.dart';
|
||||
|
||||
/// The default value to be passed to an [ArgumentContext].
|
||||
class DefaultValueContext extends Node {
|
||||
/// The source token.
|
||||
final Token equalsToken;
|
||||
|
||||
/// The default value for the argument.
|
||||
final InputValueContext value;
|
||||
|
||||
DefaultValueContext(this.equalsToken, this.value);
|
||||
|
||||
/// Use [equalsToken] instead.
|
||||
@deprecated
|
||||
Token get EQUALS => equalsToken;
|
||||
|
||||
@override
|
||||
FileSpan get span => equalsToken.span.expand(value.span);
|
||||
}
|
|
@ -0,0 +1,13 @@
|
|||
import 'node.dart';
|
||||
|
||||
/// The base class for top-level GraphQL definitions.
|
||||
abstract class DefinitionContext extends Node {}
|
||||
|
||||
/// An executable definition.
|
||||
abstract class ExecutableDefinitionContext extends DefinitionContext {}
|
||||
|
||||
/// An ad-hoc type system declared in GraphQL.
|
||||
abstract class TypeSystemDefinitionContext extends DefinitionContext {}
|
||||
|
||||
/// An extension to an existing ad-hoc type system.
|
||||
abstract class TypeSystemExtensionContext extends DefinitionContext {}
|
|
@ -0,0 +1,11 @@
|
|||
import 'input_value.dart';
|
||||
|
||||
/// Use [ConstantContext] instead. This class remains solely for backwards compatibility.
|
||||
@deprecated
|
||||
abstract class ValueContext<T> extends InputValueContext<T> {
|
||||
/// Return a constant value.
|
||||
T get value;
|
||||
|
||||
@override
|
||||
T computeValue(Map<String, dynamic> variables) => value;
|
||||
}
|
|
@ -0,0 +1,62 @@
|
|||
import 'package:source_span/source_span.dart';
|
||||
import '../token.dart';
|
||||
import 'argument.dart';
|
||||
import 'input_value.dart';
|
||||
import 'node.dart';
|
||||
|
||||
/// A GraphQL directive, which may or may not have runtime semantics.
|
||||
class DirectiveContext extends Node {
|
||||
/// The source tokens.
|
||||
final Token arrobaToken, nameToken, colonToken, lParenToken, rParenToken;
|
||||
|
||||
/// The argument being passed as the directive.
|
||||
final ArgumentContext argument;
|
||||
|
||||
/// The (optional) value being passed with the directive.
|
||||
final InputValueContext value;
|
||||
|
||||
DirectiveContext(this.arrobaToken, this.nameToken, this.colonToken,
|
||||
this.lParenToken, this.rParenToken, this.argument, this.value) {
|
||||
assert(nameToken != null);
|
||||
}
|
||||
|
||||
/// Use [value] instead.
|
||||
@deprecated
|
||||
InputValueContext get valueOrVariable => value;
|
||||
|
||||
/// Use [arrobaToken] instead.
|
||||
@deprecated
|
||||
Token get ARROBA => arrobaToken;
|
||||
|
||||
/// Use [nameToken] instead.
|
||||
@deprecated
|
||||
Token get NAME => nameToken;
|
||||
|
||||
/// Use [colonToken] instead.
|
||||
@deprecated
|
||||
Token get COLON => colonToken;
|
||||
|
||||
/// Use [lParenToken] instead.
|
||||
@deprecated
|
||||
Token get LPAREN => lParenToken;
|
||||
|
||||
/// Use [rParenToken] instead.
|
||||
@deprecated
|
||||
Token get RPAREN => rParenToken;
|
||||
|
||||
@override
|
||||
FileSpan get span {
|
||||
var out = arrobaToken.span.expand(nameToken.span);
|
||||
|
||||
if (colonToken != null) {
|
||||
out = out.expand(colonToken.span).expand(value.span);
|
||||
} else if (lParenToken != null) {
|
||||
out = out
|
||||
.expand(lParenToken.span)
|
||||
.expand(argument.span)
|
||||
.expand(rParenToken.span);
|
||||
}
|
||||
|
||||
return out;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,17 @@
|
|||
import 'package:source_span/source_span.dart';
|
||||
import 'definition.dart';
|
||||
import 'node.dart';
|
||||
|
||||
/// A GraphQL document.
|
||||
class DocumentContext extends Node {
|
||||
/// The top-level definitions in the document.
|
||||
final List<DefinitionContext> definitions = [];
|
||||
|
||||
@override
|
||||
FileSpan get span {
|
||||
if (definitions.isEmpty) return null;
|
||||
return definitions
|
||||
.map<FileSpan>((d) => d.span)
|
||||
.reduce((a, b) => a.expand(b));
|
||||
}
|
||||
}
|
|
@ -0,0 +1,39 @@
|
|||
import 'package:source_span/source_span.dart';
|
||||
import 'argument.dart';
|
||||
import 'directive.dart';
|
||||
import 'field_name.dart';
|
||||
import 'node.dart';
|
||||
import 'selection_set.dart';
|
||||
|
||||
/// A field in a GraphQL [SelectionSet].
|
||||
class FieldContext extends Node {
|
||||
/// The name of this field.
|
||||
final FieldNameContext fieldName;
|
||||
|
||||
/// Any arguments this field expects.
|
||||
final List<ArgumentContext> arguments = [];
|
||||
|
||||
/// Any directives affixed to this field.
|
||||
final List<DirectiveContext> directives = [];
|
||||
|
||||
/// The list of selections to resolve on an object.
|
||||
final SelectionSetContext selectionSet;
|
||||
|
||||
FieldContext(this.fieldName, [this.selectionSet]);
|
||||
|
||||
@override
|
||||
FileSpan get span {
|
||||
if (selectionSet != null) {
|
||||
return fieldName.span.expand(selectionSet.span);
|
||||
} else if (directives.isNotEmpty) {
|
||||
return directives.fold<FileSpan>(
|
||||
fieldName.span, (out, d) => out.expand(d.span));
|
||||
}
|
||||
if (arguments.isNotEmpty) {
|
||||
return arguments.fold<FileSpan>(
|
||||
fieldName.span, (out, a) => out.expand(a.span));
|
||||
} else {
|
||||
return fieldName.span;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,27 @@
|
|||
import 'package:source_span/source_span.dart';
|
||||
import '../token.dart';
|
||||
import 'alias.dart';
|
||||
import 'node.dart';
|
||||
|
||||
/// The name of a GraphQL [FieldContext], which may or may not be [alias]ed.
|
||||
class FieldNameContext extends Node {
|
||||
/// The source token.
|
||||
final Token nameToken;
|
||||
|
||||
/// An (optional) alias for the field.
|
||||
final AliasContext alias;
|
||||
|
||||
FieldNameContext(this.nameToken, [this.alias]) {
|
||||
assert(nameToken != null || alias != null);
|
||||
}
|
||||
|
||||
/// Use [nameToken] instead.
|
||||
@deprecated
|
||||
Token get NAME => nameToken;
|
||||
|
||||
/// The [String] value of the [nameToken], if any.
|
||||
String get name => nameToken?.text;
|
||||
|
||||
@override
|
||||
FileSpan get span => alias?.span ?? nameToken.span;
|
||||
}
|
|
@ -0,0 +1,49 @@
|
|||
import '../token.dart';
|
||||
import 'definition.dart';
|
||||
import 'directive.dart';
|
||||
import 'package:source_span/source_span.dart';
|
||||
import 'selection_set.dart';
|
||||
import 'type_condition.dart';
|
||||
|
||||
/// A GraphQL query fragment definition.
|
||||
class FragmentDefinitionContext extends ExecutableDefinitionContext {
|
||||
/// The source tokens.
|
||||
final Token fragmentToken, nameToken, onToken;
|
||||
|
||||
/// The type to which this fragment applies.
|
||||
final TypeConditionContext typeCondition;
|
||||
|
||||
/// Any directives on the fragment.
|
||||
final List<DirectiveContext> directives = [];
|
||||
|
||||
/// The selections to apply when the [typeCondition] is met.
|
||||
final SelectionSetContext selectionSet;
|
||||
|
||||
/// The [String] value of the [nameToken].
|
||||
String get name => nameToken.text;
|
||||
|
||||
FragmentDefinitionContext(this.fragmentToken, this.nameToken, this.onToken,
|
||||
this.typeCondition, this.selectionSet);
|
||||
|
||||
/// Use [fragmentToken] instead.
|
||||
@deprecated
|
||||
Token get FRAGMENT => fragmentToken;
|
||||
|
||||
/// Use [nameToken] instead.
|
||||
@deprecated
|
||||
Token get NAME => nameToken;
|
||||
|
||||
/// Use [onToken] instead.
|
||||
@deprecated
|
||||
Token get ON => onToken;
|
||||
|
||||
@override
|
||||
FileSpan get span {
|
||||
var out = fragmentToken.span
|
||||
.expand(nameToken.span)
|
||||
.expand(onToken.span)
|
||||
.expand(typeCondition.span);
|
||||
out = directives.fold<FileSpan>(out, (o, d) => o.expand(d.span));
|
||||
return out.expand(selectionSet.span);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,33 @@
|
|||
import '../token.dart';
|
||||
import 'directive.dart';
|
||||
import 'node.dart';
|
||||
import 'package:source_span/source_span.dart';
|
||||
|
||||
/// A GraphQL fragment spread.
|
||||
class FragmentSpreadContext extends Node {
|
||||
/// The source tokens.
|
||||
final Token ellipsisToken, nameToken;
|
||||
|
||||
/// Any directives affixed to this fragment spread.
|
||||
final List<DirectiveContext> directives = [];
|
||||
|
||||
FragmentSpreadContext(this.ellipsisToken, this.nameToken);
|
||||
|
||||
/// The [String] value of the [nameToken].
|
||||
String get name => nameToken.text;
|
||||
|
||||
/// Use [ellipsisToken] instead.
|
||||
@deprecated
|
||||
Token get ELLIPSIS => ellipsisToken;
|
||||
|
||||
/// Use [nameToken] instead.
|
||||
@deprecated
|
||||
Token get NAME => nameToken;
|
||||
|
||||
@override
|
||||
FileSpan get span {
|
||||
var out = ellipsisToken.span.expand(nameToken.span);
|
||||
if (directives.isEmpty) return out;
|
||||
return directives.fold<FileSpan>(out, (o, d) => o.expand(d.span));
|
||||
}
|
||||
}
|
|
@ -0,0 +1,40 @@
|
|||
import '../token.dart';
|
||||
import 'directive.dart';
|
||||
import 'node.dart';
|
||||
import 'package:source_span/source_span.dart';
|
||||
import 'selection_set.dart';
|
||||
import 'type_condition.dart';
|
||||
|
||||
/// An inline fragment, which typically appears in a [SelectionSetContext].
|
||||
class InlineFragmentContext extends Node {
|
||||
/// The source tokens.
|
||||
final Token ellipsisToken, onToken;
|
||||
|
||||
/// The type which this fragment matches.
|
||||
final TypeConditionContext typeCondition;
|
||||
|
||||
/// Any directives affixed to this inline fragment.
|
||||
final List<DirectiveContext> directives = [];
|
||||
|
||||
/// The selections applied when the [typeCondition] is met.
|
||||
final SelectionSetContext selectionSet;
|
||||
|
||||
InlineFragmentContext(
|
||||
this.ellipsisToken, this.onToken, this.typeCondition, this.selectionSet);
|
||||
|
||||
/// Use [ellipsisToken] instead.
|
||||
@deprecated
|
||||
Token get ELLIPSIS => ellipsisToken;
|
||||
|
||||
/// Use [onToken] instead.
|
||||
@deprecated
|
||||
Token get ON => onToken;
|
||||
|
||||
@override
|
||||
FileSpan get span {
|
||||
var out =
|
||||
ellipsisToken.span.expand(onToken.span).expand(typeCondition.span);
|
||||
out = directives.fold<FileSpan>(out, (o, d) => o.expand(d.span));
|
||||
return out.expand(selectionSet.span);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,7 @@
|
|||
import 'node.dart';
|
||||
|
||||
/// Represents a value in GraphQL.
|
||||
abstract class InputValueContext<T> extends Node {
|
||||
/// Computes the value, relative to some set of [variables].
|
||||
T computeValue(Map<String, dynamic> variables);
|
||||
}
|
|
@ -0,0 +1,31 @@
|
|||
import '../token.dart';
|
||||
import 'node.dart';
|
||||
import 'package:source_span/source_span.dart';
|
||||
import 'type.dart';
|
||||
|
||||
/// Represents a type that holds a list of another type.
|
||||
class ListTypeContext extends Node {
|
||||
/// The source tokens.
|
||||
final Token lBracketToken, rBracketToken;
|
||||
|
||||
/// The inner type.
|
||||
final TypeContext innerType;
|
||||
|
||||
ListTypeContext(this.lBracketToken, this.innerType, this.rBracketToken);
|
||||
|
||||
/// Use [innerType] instead.
|
||||
@deprecated
|
||||
TypeContext get type => innerType;
|
||||
|
||||
/// Use [lBracketToken] instead.
|
||||
@deprecated
|
||||
Token get LBRACKET => lBracketToken;
|
||||
|
||||
/// Use [rBracketToken] instead.
|
||||
@deprecated
|
||||
Token get RBRACKET => rBracketToken;
|
||||
|
||||
@override
|
||||
FileSpan get span =>
|
||||
lBracketToken.span.expand(innerType.span).expand(rBracketToken.span);
|
||||
}
|
|
@ -0,0 +1,105 @@
|
|||
import 'package:source_span/source_span.dart';
|
||||
import '../token.dart';
|
||||
import 'input_value.dart';
|
||||
import 'node.dart';
|
||||
|
||||
/// A GraphQL `null` literal.
|
||||
class NullValueContext extends InputValueContext<Null> {
|
||||
/// The source token.
|
||||
final Token nullToken;
|
||||
|
||||
NullValueContext(this.nullToken);
|
||||
|
||||
/// Use [nullToken] instead.
|
||||
@deprecated
|
||||
Token get NULL => nullToken;
|
||||
|
||||
@override
|
||||
FileSpan get span => nullToken.span;
|
||||
|
||||
@override
|
||||
Null computeValue(Map<String, dynamic> variables) => null;
|
||||
}
|
||||
|
||||
/// A GraphQL enumeration literal.
|
||||
class EnumValueContext extends InputValueContext<String> {
|
||||
/// The source token.
|
||||
final Token nameToken;
|
||||
|
||||
EnumValueContext(this.nameToken);
|
||||
|
||||
/// Use [nameToken] instead.
|
||||
@deprecated
|
||||
Token get NAME => nameToken;
|
||||
|
||||
@override
|
||||
FileSpan get span => nameToken.span;
|
||||
|
||||
@override
|
||||
String computeValue(Map<String, dynamic> variables) => nameToken.span.text;
|
||||
}
|
||||
|
||||
/// A GraphQL object literal.
|
||||
class ObjectValueContext extends InputValueContext<Map<String, dynamic>> {
|
||||
/// The source tokens.
|
||||
final Token lBraceToken, rBraceToken;
|
||||
|
||||
/// The fields in the object.
|
||||
final List<ObjectFieldContext> fields;
|
||||
|
||||
ObjectValueContext(this.lBraceToken, this.fields, this.rBraceToken);
|
||||
|
||||
/// Use [lBraceToken] instead.
|
||||
Token get LBRACE => lBraceToken;
|
||||
|
||||
/// Use [rBraceToken] instead.
|
||||
@deprecated
|
||||
Token get RBRACE => rBraceToken;
|
||||
|
||||
@override
|
||||
FileSpan get span {
|
||||
var left = lBraceToken.span;
|
||||
|
||||
for (var field in fields) {
|
||||
left = left.expand(field.span);
|
||||
}
|
||||
|
||||
return left.expand(rBraceToken.span);
|
||||
}
|
||||
|
||||
@override
|
||||
Map<String, dynamic> computeValue(Map<String, dynamic> variables) {
|
||||
if (fields.isEmpty) {
|
||||
return <String, dynamic>{};
|
||||
} else {
|
||||
return fields.fold<Map<String, dynamic>>(<String, dynamic>{},
|
||||
(map, field) {
|
||||
return map
|
||||
..[field.nameToken.text] = field.value.computeValue(variables);
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// A field within an [ObjectValueContext].
|
||||
class ObjectFieldContext extends Node {
|
||||
/// The source tokens.
|
||||
final Token nameToken, colonToken;
|
||||
|
||||
/// The associated value.
|
||||
final InputValueContext value;
|
||||
|
||||
ObjectFieldContext(this.nameToken, this.colonToken, this.value);
|
||||
|
||||
/// Use [nameToken] instead.
|
||||
@deprecated
|
||||
Token get NAME => nameToken;
|
||||
|
||||
/// Use [colonToken] instead.
|
||||
@deprecated
|
||||
Token get COLON => colonToken;
|
||||
|
||||
@override
|
||||
FileSpan get span =>
|
||||
nameToken.span.expand(colonToken.span).expand(value.span);
|
||||
}
|
Some files were not shown because too many files have changed in this diff Show more
Loading…
Reference in a new issue