├── .gitignore ├── relayPlugin.js ├── src ├── frontend │ ├── components │ │ ├── About.js │ │ ├── User.js │ │ ├── Application.js │ │ ├── Logout.js │ │ ├── Dashboard.js │ │ ├── App.js │ │ └── Login.js │ ├── containers │ │ ├── User.js │ │ └── Application.js │ ├── index.template.html │ ├── roots │ │ ├── UserRoot.js │ │ └── ExampleRoot.js │ ├── index2.js │ ├── index.js │ └── helpers │ │ └── auth.js ├── server │ ├── server.js │ └── data │ │ ├── schema.js │ │ └── schema.json └── public │ └── ql │ ├── index.html │ └── graphiql.css ├── .eslintrc ├── README.md ├── CHANGELOG.md ├── webpack.config.js ├── LICENSE ├── package.json └── gulpfile.babel.js /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | build 3 | *.sublime-project 4 | *.sublime-workspace 5 | -------------------------------------------------------------------------------- /relayPlugin.js: -------------------------------------------------------------------------------- 1 | var getbabelRelayPlugin = require('babel-relay-plugin'); 2 | var schema = require('./src/server/data/schema.json'); 3 | 4 | module.exports = getbabelRelayPlugin(schema.data); -------------------------------------------------------------------------------- /src/frontend/components/About.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | 3 | export default React.createClass({ 4 | displayName: 'About', 5 | 6 | render() { 7 | return ( 8 |

About

9 | ); 10 | }, 11 | }); 12 | 13 | -------------------------------------------------------------------------------- /src/frontend/components/User.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | 3 | export default class Application extends React.Component { 4 | render() { 5 | return ( 6 |
7 | email:{ this.props.user.email }, 8 | id:{ this.props.user.id } 9 |
10 | ); 11 | } 12 | } -------------------------------------------------------------------------------- /src/frontend/containers/User.js: -------------------------------------------------------------------------------- 1 | import User from '../components/User'; 2 | import Relay from 'react-relay'; 3 | 4 | export default Relay.createContainer(User, { 5 | fragments: { 6 | user: () => Relay.QL` 7 | fragment on User { 8 | email, 9 | id 10 | } 11 | ` 12 | } 13 | }); -------------------------------------------------------------------------------- /src/frontend/index.template.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | {%=o.htmlWebpackPlugin.options.title || 'Skele' %} 5 | 6 | 7 | 8 | 9 |
10 | 11 | -------------------------------------------------------------------------------- /src/frontend/components/Application.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | 3 | export default class Application extends React.Component { 4 | render() { 5 | return ( 6 |
7 | text:{ this.props.example.text }, 8 | version:{ this.props.example.version }, 9 | id:{ this.props.example.id } 10 |
11 | ); 12 | } 13 | } -------------------------------------------------------------------------------- /src/frontend/containers/Application.js: -------------------------------------------------------------------------------- 1 | import Application from '../components/Application'; 2 | import Relay from 'react-relay'; 3 | 4 | export default Relay.createContainer(Application, { 5 | fragments: { 6 | example: () => Relay.QL` 7 | fragment on Example { 8 | text, 9 | version, 10 | id 11 | } 12 | ` 13 | } 14 | }); -------------------------------------------------------------------------------- /src/frontend/roots/UserRoot.js: -------------------------------------------------------------------------------- 1 | import Relay from 'react-relay'; 2 | 3 | export default class extends Relay.Route { 4 | static path = '/'; 5 | static queries = { 6 | user: (Component) => Relay.QL` 7 | query { 8 | user { 9 | ${Component.getFragment('user')} 10 | } 11 | } 12 | ` 13 | }; 14 | static routeName = 'UserRoute'; 15 | } -------------------------------------------------------------------------------- /src/frontend/roots/ExampleRoot.js: -------------------------------------------------------------------------------- 1 | import Relay from 'react-relay'; 2 | 3 | export default class extends Relay.Route { 4 | static path = '/'; 5 | static queries = { 6 | example: (Component) => Relay.QL` 7 | query { 8 | example { 9 | ${Component.getFragment('example')} 10 | } 11 | } 12 | ` 13 | }; 14 | static routeName = 'ExampleRoute'; 15 | } -------------------------------------------------------------------------------- /src/frontend/components/Logout.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | 3 | import auth from '../helpers/auth'; 4 | 5 | export default React.createClass({ 6 | displayName: 'Logout', 7 | 8 | propTypes: { 9 | }, 10 | 11 | componentDidMount() { 12 | auth.logout(); 13 | }, 14 | 15 | render() { 16 | return ( 17 |

You are now logged out

18 | ); 19 | }, 20 | }); 21 | -------------------------------------------------------------------------------- /src/frontend/components/Dashboard.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | 3 | import auth from '../helpers/auth'; 4 | 5 | export default React.createClass({ 6 | displayName: 'Dashboard', 7 | 8 | propTypes: { 9 | }, 10 | 11 | render() { 12 | const token = auth.getToken(); 13 | 14 | return ( 15 |
16 |

Dashboard

17 |

You made it!

18 |

{token}

19 |
20 | ); 21 | }, 22 | }); 23 | -------------------------------------------------------------------------------- /src/frontend/index2.js: -------------------------------------------------------------------------------- 1 | /*global document*/ 2 | import React from 'react'; 3 | import ReactDOM from 'react-dom'; 4 | import Relay from 'react-relay'; 5 | 6 | // purposefully calling Relay 'routes' roots (as in Query Root) 7 | import UserRoot from './roots/UserRoot'; 8 | import Application from './containers/User'; 9 | 10 | class Root extends React.Component { 11 | render() { 12 | return ( 13 | 16 | ); 17 | } 18 | } 19 | 20 | ReactDOM.render( 21 | , 22 | document.getElementById('container') 23 | ); -------------------------------------------------------------------------------- /src/server/server.js: -------------------------------------------------------------------------------- 1 | import express from 'express'; 2 | import { Schema } from './data/schema'; 3 | import graphQLHTTP from 'express-graphql'; 4 | import { printSchema } from 'graphql/utilities/schemaPrinter'; 5 | import path from 'path'; 6 | 7 | const app = express(); 8 | app.use(express.static(path.resolve(__dirname, 'public'))); 9 | app.use('/schema',function(req,res,next) { 10 | res.set('Content-Type', 'text/plain'); 11 | res.send(printSchema(Schema)); 12 | }); 13 | app.use('/', graphQLHTTP({ schema: Schema, pretty: true })); 14 | app.listen(8080, (err) => { 15 | if (err) { 16 | return console.error(err); 17 | } 18 | console.log('GraphQL Server is now running on localhost:8080'); 19 | }); -------------------------------------------------------------------------------- /.eslintrc: -------------------------------------------------------------------------------- 1 | { 2 | "parser": "babel-eslint", 3 | "rules": { 4 | "quotes": [ 5 | 2, 6 | "single" 7 | ], 8 | "linebreak-style": [ 9 | 2, 10 | "unix" 11 | ], 12 | "no-unused-vars": [2, { 13 | "vars": "all", 14 | "args": "after-used", 15 | "argsIgnorePattern": "^_", 16 | "varsIgnorePattern": "^_" 17 | }], 18 | "semi": [ 19 | 2, 20 | "always" 21 | ], 22 | "no-console" : 0 23 | }, 24 | "env": { 25 | "es6": true, 26 | "node": true 27 | }, 28 | "ecmaFeatures": { 29 | "classes": true, 30 | "modules": true, 31 | "jsx": true 32 | }, 33 | "plugins": [ 34 | "react" 35 | ], 36 | "extends": "airbnb" 37 | } 38 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # graphql-relay-authentication 2 | This project is a simple application to use as a starting point for an authentication based React/Relay application. 3 | 4 | This application has the following characteristics: 5 | 6 | - front end built with react and relay 7 | - back end will be a GraphQL server with no actual persistent layer (everything in memory) 8 | - application will have a login and register pages 9 | - a protected page which will be available only to authenticated user 10 | 11 | # Credits 12 | 13 | [fortruce/relay-skeleton](https://github.com/fortruce/relay-skeleton): skeleton for a React, Relay, GraphQL project. I expect 14 | to change the structure of the project over time but this skeleton is a very valuable starting point. 15 | 16 | [Keep a CHANGELOG](http://keepachangelog.com/): very interesting initiative by [Olivier Lacan](http://olivierlacan.com/) to formalize 17 | the way you should write your CHANGELOG.md file. Worth giving it a try. -------------------------------------------------------------------------------- /src/frontend/index.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { render } from 'react-dom'; 3 | import { Router, Route } from 'react-router'; 4 | 5 | // componets for the application 6 | import App from './components/App'; 7 | import Login from './components/Login'; 8 | import Logout from './components/Logout'; 9 | import Dashboard from './components/Dashboard'; 10 | import About from './components/About'; 11 | 12 | import auth from './helpers/auth'; 13 | 14 | function requireAuth(nextState, replaceState) { 15 | if (!auth.loggedIn()) { 16 | replaceState({ nextPathname: nextState.location.pathname }, '/login'); 17 | } 18 | } 19 | 20 | render(( 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | ), document.getElementById('container')); 30 | 31 | -------------------------------------------------------------------------------- /src/frontend/components/App.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { Link } from 'react-router'; 3 | 4 | import auth from '../helpers/auth'; 5 | 6 | export default React.createClass({ 7 | displayName: 'App', 8 | 9 | propTypes: { 10 | children: React.PropTypes.element, 11 | }, 12 | 13 | getInitialState() { 14 | return { 15 | loggedIn: auth.loggedIn(), 16 | }; 17 | }, 18 | 19 | componentWillMount() { 20 | auth.onChange = this.updateAuth; 21 | auth.login(); 22 | }, 23 | 24 | updateAuth(loggedIn) { 25 | this.setState({ 26 | loggedIn: loggedIn, 27 | }); 28 | }, 29 | 30 | render() { 31 | return ( 32 |
33 |
    34 |
  • 35 | {this.state.loggedIn ? ( 36 | Log out 37 | ) : ( 38 | Sign in 39 | )} 40 |
  • 41 |
  • About
  • 42 |
  • Dashboard (authenticated)
  • 43 |
44 | {this.props.children} 45 |
46 | ); 47 | }, 48 | }); 49 | -------------------------------------------------------------------------------- /src/frontend/helpers/auth.js: -------------------------------------------------------------------------------- 1 | function pretendRequest(email, pass, cb) { 2 | setTimeout(() => { 3 | if (email === 'joe@example.com' && pass === 'password1') { 4 | cb({ 5 | authenticated: true, 6 | token: Math.random().toString(36).substring(7), 7 | }); 8 | } else { 9 | cb({ authenticated: false }); 10 | } 11 | }, 0); 12 | } 13 | 14 | export default { 15 | login(email, pass, _cb) { 16 | const cb = arguments[arguments.length - 1]; 17 | if (localStorage.token) { 18 | if (cb) { 19 | cb(true); 20 | } 21 | this.onChange(true); 22 | return; 23 | } 24 | pretendRequest(email, pass, (res) => { 25 | if (res.authenticated) { 26 | localStorage.token = res.token; 27 | if (cb) { 28 | cb(true); 29 | } 30 | this.onChange(true); 31 | } else { 32 | if (cb) { 33 | cb(false); 34 | } 35 | this.onChange(false); 36 | } 37 | }); 38 | }, 39 | 40 | getToken() { 41 | return localStorage.token; 42 | }, 43 | 44 | logout(cb) { 45 | delete localStorage.token; 46 | if (cb) { 47 | cb(); 48 | } 49 | this.onChange(false); 50 | }, 51 | 52 | loggedIn() { 53 | return !!localStorage.token; 54 | }, 55 | 56 | onChange() {}, 57 | }; 58 | 59 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Change Log 2 | All notable changes to this project will be documented in this file. 3 | 4 | ## [Unreleased][unreleased] 5 | ### Added 6 | - add eslint support 7 | - add [Airbnb javascript coding](https://github.com/airbnb/javascript) style using [eslint](https://github.com/airbnb/javascript/tree/master/packages/eslint-config-airbnb) 8 | - use the `auth` sample code from [Recat Royer example](https://github.com/rackt/react-router/tree/master/examples/auth-flow) as a staring point for our sample 9 | 10 | ## 0.0.1 - 2015-10-05 11 | ### Added 12 | - Add the `/schema` middleware to get the GraphQL schema 13 | - Add the GraphiQL web interface in the `ql` subdirectory of the server 14 | - Add gulp tasks to clean and to prepare the build directory 15 | 16 | ### Changed 17 | - Remove webpack to generate the server code and replace with babel to generate es5 code 18 | - Rename gulp tasks to follow a naming convention "system:function" 19 | 20 | ## Initial import in master - 2015-10-04 21 | ### Added 22 | - Skeleton project using the [fortruce/relay-skeleton](https://github.com/fortruce/relay-skeleton) project 23 | - README.md file to describe the project and prepare a credit page 24 | - prepare CHANGELOG.md file following the format described [here](http://keepachangelog.com/) 25 | - .gitignore file for this file organization 26 | 27 | [unreleased]: https://github.com/pcarion/graphql-relay-authentication/compare/rel/0.0.1...develop -------------------------------------------------------------------------------- /webpack.config.js: -------------------------------------------------------------------------------- 1 | /* 2 | TODO: 3 | * add a production flag that disables debug/sourcemaps and minifies 4 | */ 5 | 6 | var webpack = require('webpack'); 7 | var path = require('path'); 8 | var assign = require('object-assign'); 9 | var HtmlWebpackPlugin = require('html-webpack-plugin'); 10 | 11 | var defaultConfig = { 12 | devtool: 'sourcemap' 13 | }; 14 | 15 | var frontendConfig = assign({}, defaultConfig, { 16 | entry: [ 17 | 'webpack-dev-server/client?http://localhost:3000', 18 | 'webpack/hot/only-dev-server', 19 | './src/frontend/index.js' 20 | ], 21 | 22 | output: { 23 | filename: 'bundle.js', 24 | path: path.join(__dirname, 'build', 'public') 25 | }, 26 | 27 | plugins: [ 28 | new webpack.HotModuleReplacementPlugin(), 29 | new webpack.NoErrorsPlugin(), 30 | new HtmlWebpackPlugin({ 31 | title: 'Skele', 32 | filename: 'index.html', 33 | template: 'src/frontend/index.template.html', 34 | inject: true 35 | }) 36 | ], 37 | 38 | module: { 39 | loaders: [ 40 | { 41 | test: /\.js$/, 42 | include: path.join(__dirname, 'src', 'frontend'), 43 | loaders: ['react-hot', 'babel?stage=0&plugins[]=' + path.join(__dirname, 'relayPlugin')] 44 | }, 45 | { 46 | test: /\.scss$/, 47 | include: path.join(__dirname, 'src', 'frontend', 'scss'), 48 | loaders: ['style', 'css', 'sass'] 49 | } 50 | ] 51 | } 52 | }); 53 | 54 | module.exports = [frontendConfig ]; -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2015, Pierre Carion 2 | All rights reserved. 3 | 4 | Redistribution and use in source and binary forms, with or without 5 | modification, are permitted provided that the following conditions are met: 6 | 7 | * Redistributions of source code must retain the above copyright notice, this 8 | list of conditions and the following disclaimer. 9 | 10 | * Redistributions in binary form must reproduce the above copyright notice, 11 | this list of conditions and the following disclaimer in the documentation 12 | and/or other materials provided with the distribution. 13 | 14 | * Neither the name of graphql-relay-authentication nor the names of its 15 | contributors may be used to endorse or promote products derived from 16 | this software without specific prior written permission. 17 | 18 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 19 | AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 20 | IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 21 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE 22 | FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 23 | DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 24 | SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 25 | CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, 26 | OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 27 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 28 | 29 | -------------------------------------------------------------------------------- /src/frontend/components/Login.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import {findDOMNode} from 'react-dom'; 3 | import { History } from 'react-router'; 4 | 5 | import auth from '../helpers/auth'; 6 | 7 | export default React.createClass({ 8 | displayName: 'Login', 9 | 10 | propTypes: { 11 | location: React.PropTypes.object.isRequired, 12 | }, 13 | 14 | mixins: [History], 15 | 16 | getInitialState() { 17 | return { 18 | error: false, 19 | }; 20 | }, 21 | 22 | handleSubmit(event) { 23 | event.preventDefault(); 24 | 25 | const {location} = this.props; 26 | const email = findDOMNode(this.refs.email).value; 27 | const pass = findDOMNode(this.refs.pass).value; 28 | 29 | auth.login(email, pass, (loggedIn) => { 30 | if (!loggedIn) { 31 | return this.setState({ 32 | error: true, 33 | }); 34 | } 35 | 36 | if (location.state && location.state.nextPathname) { 37 | this.history.replaceState(null, location.state.nextPathname); 38 | } else { 39 | this.history.replaceState(null, '/about'); 40 | } 41 | }); 42 | }, 43 | 44 | render() { 45 | return ( 46 |
47 | 48 | (hint: password1)
49 | 50 | {this.state.error && ( 51 |

Bad login information

52 | )} 53 |
54 | ); 55 | }, 56 | }); 57 | 58 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "relay-skeleton", 3 | "version": "0.0.1", 4 | "description": "A minimal React, Relay, GraphQL skeleton.", 5 | "main": "gulpfile.babel.js", 6 | "scripts": { 7 | "start": "gulp" 8 | }, 9 | "dependencies": { 10 | "express-graphql": "^0.3.0", 11 | "graphql": "^0.4.2", 12 | "graphql-relay": "^0.3.0", 13 | "history": "^1.12.4", 14 | "node-uuid": "^1.4.3", 15 | "react": "^0.14.0-beta3", 16 | "react-dom": "^0.14.0-beta3", 17 | "react-relay": "^0.1.1", 18 | "react-router": "^1.0.0-beta3" 19 | }, 20 | "devDependencies": { 21 | "babel": "^5.8.21", 22 | "babel-core": "^5.8.20", 23 | "babel-eslint": "^4.1.3", 24 | "babel-loader": "^5.3.2", 25 | "babel-relay-plugin": "^0.1.2", 26 | "css-loader": "^0.15.6", 27 | "del": "^2.0.2", 28 | "eslint": "^1.6.0", 29 | "eslint-config-airbnb": "^0.1.0", 30 | "eslint-plugin-react": "^3.5.1", 31 | "express": "^4.13.3", 32 | "gulp": "^3.9.0", 33 | "gulp-babel": "^5.2.1", 34 | "gulp-concat": "^2.6.0", 35 | "gulp-sourcemaps": "^1.6.0", 36 | "html-webpack-plugin": "^1.6.0", 37 | "nodemon": "^1.4.0", 38 | "object-assign": "^3.0.0", 39 | "react-hot-loader": "^1.2.8", 40 | "sass-loader": "^2.0.0", 41 | "source-map-support": "^0.3.2", 42 | "style-loader": "^0.12.3", 43 | "webpack": "^1.10.5", 44 | "webpack-dev-server": "^1.10.1" 45 | }, 46 | "repository": { 47 | "type": "git", 48 | "url": "https://github.com/fortruce/relay-skeleton.git" 49 | }, 50 | "keywords": [ 51 | "react", 52 | "relay", 53 | "skeleton", 54 | "webpack" 55 | ], 56 | "author": "fortruce", 57 | "license": "ISC", 58 | "bugs": { 59 | "url": "https://github.com/fortruce/relay-skeleton/issues" 60 | }, 61 | "homepage": "https://github.com/fortruce/relay-skeleton" 62 | } 63 | -------------------------------------------------------------------------------- /src/server/data/schema.js: -------------------------------------------------------------------------------- 1 | import { 2 | GraphQLObjectType, 3 | GraphQLInt, 4 | GraphQLString, 5 | GraphQLSchema 6 | } from 'graphql'; 7 | 8 | import { 9 | fromGlobalId, 10 | globalIdField, 11 | nodeDefinitions 12 | } from 'graphql-relay'; 13 | 14 | import uuid from 'node-uuid'; 15 | 16 | class User { 17 | constructor(email, password) { 18 | this._id = uuid.v4(); 19 | this._email = email; 20 | this._password = password; 21 | } 22 | 23 | get id() { 24 | return this._id; 25 | } 26 | 27 | get email() { 28 | return this._email; 29 | } 30 | } 31 | 32 | class Example { 33 | constructor(text, version) { 34 | this._id = uuid.v4(); 35 | this._text = text; 36 | this._version = version; 37 | } 38 | 39 | get id() { 40 | return this._id; 41 | } 42 | 43 | get text() { 44 | return this._text; 45 | } 46 | 47 | get version() { 48 | return this._version; 49 | } 50 | } 51 | 52 | 53 | const example = new Example('Hello World!',3); 54 | const user = new User('pcarion@gmail.com', 'xyz123'); 55 | 56 | // let usersDirectory = new Map(); 57 | 58 | // function getUser(id) { 59 | // return usersDirectory.get(id); 60 | // } 61 | 62 | // function createUser(email, password) { 63 | // return new User(email, password); 64 | 65 | // } 66 | 67 | /** 68 | * The first argument defines the way to resolve an ID to its object. 69 | * The second argument defines the way to resolve a node object to its GraphQL type. 70 | */ 71 | var { nodeInterface, nodeField } = nodeDefinitions( 72 | (globalId) => { 73 | let { _id, type } = fromGlobalId(globalId); 74 | if (type === 'Example') { 75 | return example; 76 | } else if (type === 'User') { 77 | return user; 78 | } 79 | return null; 80 | }, 81 | (obj) => { 82 | if (obj instanceof User) { 83 | return userType; 84 | } else if (obj instanceof Example) { 85 | return exampleType; 86 | } 87 | return null; 88 | } 89 | ); 90 | 91 | var userType = new GraphQLObjectType({ 92 | name: 'User', 93 | fields: () => ({ 94 | id: globalIdField('User'), 95 | email: { 96 | type: GraphQLString, 97 | description: 'email of the user' 98 | }, 99 | password: { 100 | type: GraphQLString, 101 | description: 'encoded password' 102 | } 103 | }), 104 | interfaces: [ nodeInterface ] 105 | }); 106 | 107 | var exampleType = new GraphQLObjectType({ 108 | name: 'Example', 109 | fields: () => ({ 110 | id: globalIdField('Example'), 111 | text: { 112 | type: GraphQLString, 113 | description: 'Hello World' 114 | }, 115 | version: { 116 | type: GraphQLInt, 117 | description: 'version' 118 | } 119 | }), 120 | interfaces: [ nodeInterface ] 121 | }); 122 | 123 | var queryType = new GraphQLObjectType({ 124 | name: 'Query', 125 | fields: () => ({ 126 | node: nodeField, 127 | example: { 128 | type: exampleType, 129 | resolve: () => example 130 | }, 131 | user: { 132 | type: userType, 133 | resolve: () => user 134 | } 135 | }) 136 | }); 137 | 138 | export var Schema = new GraphQLSchema({ 139 | query: queryType 140 | }); -------------------------------------------------------------------------------- /src/public/ql/index.html: -------------------------------------------------------------------------------- 1 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | Loading... 19 | 95 | 96 | 97 | -------------------------------------------------------------------------------- /gulpfile.babel.js: -------------------------------------------------------------------------------- 1 | import gulp from 'gulp'; 2 | import sourcemaps from 'gulp-sourcemaps'; 3 | import del from 'del'; 4 | import babel from 'gulp-babel'; 5 | import concat from 'gulp-concat'; 6 | 7 | import webpack from 'webpack'; 8 | import WebpackDevServer from 'webpack-dev-server'; 9 | import nodemon from 'nodemon'; 10 | import path from 'path'; 11 | import { Schema } from './src/server/data/schema'; 12 | import { introspectionQuery } from 'graphql/utilities'; 13 | import { graphql } from 'graphql'; 14 | import fs from 'fs'; 15 | 16 | import configs from './webpack.config'; 17 | const [ frontendConfig ] = configs; 18 | 19 | let compiler; 20 | 21 | // trigger a manual recompilation of webpack(frontendConfig); 22 | function recompile() { 23 | if (!compiler) 24 | return null; 25 | return new Promise((resolve, reject) => { 26 | compiler.run((err, stats) => { 27 | if (err) 28 | reject(err); 29 | console.log('[webpackDevServer]: recompiled'); 30 | resolve(); 31 | }); 32 | }); 33 | } 34 | 35 | // run the webpack dev server 36 | // must generate the schema.json first as compiler relies on it for babel-relay-plugin 37 | gulp.task('frontend:webpack', ['schema:generate'], () => { 38 | compiler = webpack(frontendConfig); 39 | let server = new WebpackDevServer(compiler, { 40 | contentBase: path.join(__dirname, 'build', 'public'), 41 | hot: true, 42 | noInfo: true, 43 | stats: { colors: true }, 44 | historyApiFallback: true, 45 | proxy: { 46 | '/graphql': 'http://localhost:8080' 47 | } 48 | }); 49 | server.listen(3000, 'localhost', (err, result) => { 50 | if (err) 51 | return console.error(err); 52 | console.log('[webpackDevServer]: listening on localhost:3000'); 53 | }); 54 | }); 55 | 56 | // Regenerate the graphql schema and recompile the frontend code that relies on schema.json 57 | gulp.task('schema:generate', () => { 58 | return graphql(Schema, introspectionQuery) 59 | .then(result => { 60 | if (result.errors) 61 | return console.error('[schema]: ERROR --', JSON.stringify(result.errors, null, 2)); 62 | fs.writeFileSync( 63 | path.join(__dirname, './src/server/data/schema.json'), 64 | JSON.stringify(result, null, 2) 65 | ); 66 | return compiler ? recompile() : null; 67 | }); 68 | }); 69 | 70 | // generate the server using babel 71 | gulp.task('server:babel', function () { 72 | return gulp.src("src/server/**/*.js") 73 | .pipe(sourcemaps.init()) 74 | .pipe(babel()) 75 | .pipe(sourcemaps.write(".")) 76 | .pipe(gulp.dest("build")); 77 | }); 78 | 79 | // recompile the schema whenever .js files in data are updated 80 | gulp.task('schema:watch', () => { 81 | gulp.watch(path.join(__dirname, './src/server/data', '**/*.js'), ['schema:generate','server:babel']); 82 | }); 83 | 84 | // copy the public directory to the build folder 85 | gulp.task('public:copy', function () { 86 | return gulp 87 | .src('src/public/**/*.*', {base: './src'}) 88 | .pipe(gulp.dest('build')); 89 | }) 90 | 91 | gulp.task('public:clean', function () { 92 | return del([ 93 | 'build' 94 | ]); 95 | }); 96 | 97 | gulp.task('server:start', ['server:babel', 'public:copy', 'schema:watch'], () => { 98 | nodemon({ 99 | execMap: { 100 | js: 'node' 101 | }, 102 | script: path.join(__dirname, 'build', 'server.js'), 103 | watch: ['build/'], 104 | ext: 'js' 105 | }).on('restart', () => { 106 | console.log('[nodemon]: restart'); 107 | }); 108 | }); 109 | 110 | gulp.task('frontend', ['frontend:webpack']); 111 | gulp.task('server', ['server:start']); 112 | 113 | gulp.task('default', ['frontend', 'server']); 114 | 115 | -------------------------------------------------------------------------------- /src/public/ql/graphiql.css: -------------------------------------------------------------------------------- 1 | html, body { 2 | height: 100%; 3 | margin: 0; 4 | overflow: hidden; 5 | width: 100%; 6 | } 7 | 8 | #graphiql-container { 9 | color: #141823; 10 | width: 100%; 11 | display: -webkit-flex; 12 | display: flex; 13 | -webkit-flex-direction: row; 14 | flex-direction: row; 15 | height: 100%; 16 | font-family: helvetica, arial, sans-serif; 17 | font-size: 14px; 18 | } 19 | 20 | #graphiql-container .editorWrap { 21 | display: -webkit-flex; 22 | display: flex; 23 | -webkit-flex-direction: column; 24 | flex-direction: column; 25 | -webkit-flex: 1; 26 | flex: 1; 27 | } 28 | 29 | #graphiql-container .title { 30 | font-size: 18px; 31 | } 32 | 33 | #graphiql-container .title em { 34 | font-family: georgia; 35 | font-size: 19px; 36 | } 37 | 38 | #graphiql-container .topBarWrap { 39 | display: -webkit-flex; 40 | display: flex; 41 | -webkit-flex-direction: row; 42 | flex-direction: row; 43 | } 44 | 45 | #graphiql-container .topBar { 46 | background: -webkit-linear-gradient(#f7f7f7, #e2e2e2); 47 | background: linear-gradient(#f7f7f7, #e2e2e2); 48 | border-bottom: solid 1px #d0d0d0; 49 | cursor: default; 50 | height: 34px; 51 | padding: 7px 14px 6px; 52 | -webkit-user-select: none; 53 | user-select: none; 54 | display: -webkit-flex; 55 | display: flex; 56 | -webkit-flex-direction: row; 57 | flex-direction: row; 58 | -webkit-flex: 1; 59 | flex: 1; 60 | -webkit-align-items: center; 61 | align-items: center; 62 | } 63 | 64 | #graphiql-container .docExplorerShow { 65 | background: -webkit-linear-gradient(#f7f7f7, #e2e2e2); 66 | background: linear-gradient(#f7f7f7, #e2e2e2); 67 | border: none; 68 | border-bottom: solid 1px #d0d0d0; 69 | border-left: solid 1px rgba(0, 0, 0, 0.2); 70 | color: #3B5998; 71 | cursor: pointer; 72 | font-size: 14px; 73 | outline: 0; 74 | padding: 2px 20px 0 18px; 75 | } 76 | 77 | #graphiql-container .docExplorerShow:before { 78 | border-left: 2px solid #3B5998; 79 | border-top: 2px solid #3B5998; 80 | content: ''; 81 | display: inline-block; 82 | height: 9px; 83 | margin: 0 3px -1px 0; 84 | position: relative; 85 | width: 9px; 86 | -webkit-transform: rotate(-45deg); 87 | transform: rotate(-45deg); 88 | } 89 | 90 | #graphiql-container .editorBar { 91 | display: -webkit-flex; 92 | display: flex; 93 | -webkit-flex-direction: row; 94 | flex-direction: row; 95 | -webkit-flex: 1; 96 | flex: 1; 97 | } 98 | 99 | #graphiql-container .queryWrap { 100 | display: -webkit-flex; 101 | display: flex; 102 | -webkit-flex-direction: column; 103 | flex-direction: column; 104 | -webkit-flex: 1; 105 | flex: 1; 106 | } 107 | 108 | #graphiql-container .resultWrap { 109 | display: -webkit-flex; 110 | display: flex; 111 | -webkit-flex-direction: column; 112 | flex-direction: column; 113 | -webkit-flex: 1; 114 | flex: 1; 115 | border-left: solid 1px #e0e0e0; 116 | } 117 | 118 | #graphiql-container .docExplorerWrap { 119 | box-shadow: 0 0 8px rgba(0, 0, 0, 0.15); 120 | z-index: 3; 121 | position: relative; 122 | background: white; 123 | } 124 | 125 | #graphiql-container .docExplorerResizer { 126 | cursor: col-resize; 127 | height: 100%; 128 | left: -5px; 129 | position: absolute; 130 | top: 0; 131 | width: 10px; 132 | z-index: 10; 133 | } 134 | 135 | #graphiql-container .docExplorerHide { 136 | cursor: pointer; 137 | font-size: 18px; 138 | margin: -7px -8px -6px 0; 139 | padding: 18px 16px 15px 12px; 140 | } 141 | 142 | #graphiql-container .query-editor { 143 | -webkit-flex: 1; 144 | flex: 1; 145 | position: relative; 146 | } 147 | 148 | #graphiql-container .variable-editor { 149 | height: 30px; 150 | display: -webkit-flex; 151 | display: flex; 152 | -webkit-flex-direction: column; 153 | flex-direction: column; 154 | position: relative; 155 | } 156 | 157 | #graphiql-container .variable-editor-title { 158 | background: #eeeeee; 159 | border-bottom: solid 1px #d6d6d6; 160 | border-top: solid 1px #e0e0e0; 161 | color: #777; 162 | font-variant: small-caps; 163 | font-weight: bold; 164 | letter-spacing: 1px; 165 | line-height: 14px; 166 | padding: 6px 0 8px 43px; 167 | text-transform: lowercase; 168 | -webkit-user-select: none; 169 | user-select: none; 170 | } 171 | 172 | #graphiql-container .codemirrorWrap { 173 | -webkit-flex: 1; 174 | flex: 1; 175 | position: relative; 176 | } 177 | 178 | #graphiql-container .result-window { 179 | -webkit-flex: 1; 180 | flex: 1; 181 | position: relative; 182 | } 183 | 184 | #graphiql-container .footer { 185 | background: #f6f7f8; 186 | border-left: solid 1px #e0e0e0; 187 | border-top: solid 1px #e0e0e0; 188 | margin-left: 12px; 189 | position: relative; 190 | } 191 | 192 | #graphiql-container .footer:before { 193 | background: #eeeeee; 194 | bottom: 0; 195 | content: " "; 196 | left: -13px; 197 | position: absolute; 198 | top: -1px; 199 | width: 12px; 200 | } 201 | 202 | #graphiql-container .result-window .CodeMirror { 203 | background: #f6f7f8; 204 | } 205 | 206 | #graphiql-container .result-window .CodeMirror-gutters { 207 | background-color: #eeeeee; 208 | border-color: #e0e0e0; 209 | cursor: col-resize; 210 | } 211 | 212 | #graphiql-container .result-window .CodeMirror-foldgutter, 213 | #graphiql-container .result-window .CodeMirror-foldgutter-open:after, 214 | #graphiql-container .result-window .CodeMirror-foldgutter-folded:after { 215 | padding-left: 3px; 216 | } 217 | 218 | #graphiql-container .execute-button { 219 | background: -webkit-linear-gradient(#fdfdfd, #d2d3d6); 220 | background: linear-gradient(#fdfdfd, #d2d3d6); 221 | border: solid 1px rgba(0,0,0,0.25); 222 | border-radius: 17px; 223 | box-shadow: 0 1px 0 #fff; 224 | cursor: pointer; 225 | fill: #444; 226 | height: 34px; 227 | margin: 0 14px 0 28px; 228 | padding: 0; 229 | width: 34px; 230 | } 231 | 232 | #graphiql-container .execute-button:active { 233 | background: -webkit-linear-gradient(#e6e6e6, #c0c0c0); 234 | background: linear-gradient(#e6e6e6, #c0c0c0); 235 | box-shadow: 236 | 0 1px 0 #fff, 237 | inset 0 0 2px rgba(0, 0, 0, 0.3), 238 | inset 0 0 6px rgba(0, 0, 0, 0.2); 239 | } 240 | 241 | #graphiql-container .execute-button:focus { 242 | outline: 0; 243 | } 244 | 245 | #graphiql-container .CodeMirror-scroll { 246 | -webkit-overflow-scrolling: touch; 247 | } 248 | 249 | #graphiql-container .CodeMirror { 250 | position: absolute; 251 | top: 0; 252 | left: 0; 253 | height: 100%; 254 | width: 100%; 255 | font-size: 13px; 256 | font-family: 'Consolas', 'Inconsolata', 'Droid Sans Mono', 'Monaco', monospace; 257 | color: #141823; 258 | } 259 | 260 | #graphiql-container .CodeMirror-lines { 261 | padding: 20px 0; 262 | } 263 | 264 | .CodeMirror-hint-information .content { 265 | -webkit-box-orient: vertical; 266 | color: #141823; 267 | display: -webkit-box; 268 | font-family: helvetica, arial, sans-serif; 269 | font-size: 13px; 270 | -webkit-line-clamp: 3; 271 | line-height: 16px; 272 | max-height: 48px; 273 | overflow: hidden; 274 | text-overflow: -o-ellipsis-lastline; 275 | } 276 | 277 | .CodeMirror-hint-information .content p:first-child { 278 | margin-top: 0; 279 | } 280 | 281 | .CodeMirror-hint-information .content p:last-child { 282 | margin-bottom: 0; 283 | } 284 | 285 | .CodeMirror-hint-information .infoType { 286 | color: #30a; 287 | margin-right: 0.5em; 288 | display: inline; 289 | cursor: pointer; 290 | } 291 | 292 | .autoInsertedLeaf.cm-property { 293 | padding: 2px 4px 1px; 294 | margin: -2px -4px -1px; 295 | border-radius: 2px; 296 | border-bottom: solid 2px rgba(255, 255, 255, 0); 297 | -webkit-animation-duration: 6s; 298 | -moz-animation-duration: 6s; 299 | animation-duration: 6s; 300 | -webkit-animation-name: insertionFade; 301 | -moz-animation-name: insertionFade; 302 | animation-name: insertionFade; 303 | } 304 | 305 | @-moz-keyframes insertionFade { 306 | from, to { 307 | background: rgba(255, 255, 255, 0); 308 | border-color: rgba(255, 255, 255, 0); 309 | } 310 | 311 | 15%, 85% { 312 | background: #fbffc9; 313 | border-color: #f0f3c0; 314 | } 315 | } 316 | 317 | @-webkit-keyframes insertionFade { 318 | from, to { 319 | background: rgba(255, 255, 255, 0); 320 | border-color: rgba(255, 255, 255, 0); 321 | } 322 | 323 | 15%, 85% { 324 | background: #fbffc9; 325 | border-color: #f0f3c0; 326 | } 327 | } 328 | 329 | @keyframes insertionFade { 330 | from, to { 331 | background: rgba(255, 255, 255, 0); 332 | border-color: rgba(255, 255, 255, 0); 333 | } 334 | 335 | 15%, 85% { 336 | background: #fbffc9; 337 | border-color: #f0f3c0; 338 | } 339 | } 340 | 341 | div.CodeMirror-lint-tooltip { 342 | background-color: white; 343 | color: #141823; 344 | border: 0; 345 | border-radius: 2px; 346 | -webkit-box-shadow: 0 1px 3px rgba(0, 0, 0, 0.45); 347 | -moz-box-shadow: 0 1px 3px rgba(0, 0, 0, 0.45); 348 | box-shadow: 0 1px 3px rgba(0, 0, 0, 0.45); 349 | font-family: helvetica, arial, sans-serif; 350 | font-size: 13px; 351 | line-height: 16px; 352 | padding: 6px 10px; 353 | opacity: 0; 354 | transition: opacity 0.15s; 355 | -moz-transition: opacity 0.15s; 356 | -webkit-transition: opacity 0.15s; 357 | -o-transition: opacity 0.15s; 358 | -ms-transition: opacity 0.15s; 359 | } 360 | 361 | div.CodeMirror-lint-message-error, div.CodeMirror-lint-message-warning { 362 | padding-left: 23px; 363 | } 364 | 365 | /* COLORS */ 366 | 367 | #graphiql-container .CodeMirror-foldmarker { 368 | border-radius: 4px; 369 | background: #08f; 370 | background: -webkit-linear-gradient(#43A8FF, #0F83E8); 371 | background: linear-gradient(#43A8FF, #0F83E8); 372 | 373 | color: white; 374 | -webkit-box-shadow: 0 1px 1px rgba(0, 0, 0, 0.2), inset 0 0 0 1px rgba(0, 0, 0, 0.1); 375 | -moz-box-shadow: 0 1px 1px rgba(0, 0, 0, 0.2), inset 0 0 0 1px rgba(0, 0, 0, 0.1); 376 | box-shadow: 0 1px 1px rgba(0, 0, 0, 0.2), inset 0 0 0 1px rgba(0, 0, 0, 0.1); 377 | font-family: arial; 378 | line-height: 0; 379 | padding: 0px 4px 1px; 380 | font-size: 12px; 381 | margin: 0 3px; 382 | text-shadow: 0 -1px rgba(0, 0, 0, 0.1); 383 | } 384 | 385 | #graphiql-container div.CodeMirror span.CodeMirror-matchingbracket { 386 | color: #555; 387 | text-decoration: underline; 388 | } 389 | 390 | #graphiql-container div.CodeMirror span.CodeMirror-nonmatchingbracket { 391 | color: #f00; 392 | } 393 | 394 | /* Comment */ 395 | .cm-comment { 396 | color: #999; 397 | } 398 | 399 | /* Punctuation */ 400 | .cm-punctuation { 401 | color: #555; 402 | } 403 | 404 | /* Keyword */ 405 | .cm-keyword { 406 | color: #B11A04; 407 | } 408 | 409 | /* OperationName, FragmentName */ 410 | .cm-def { 411 | color: #D2054E; 412 | } 413 | 414 | /* FieldName */ 415 | .cm-property { 416 | color: #1F61A0; 417 | } 418 | 419 | /* FieldAlias */ 420 | .cm-qualifier { 421 | color: #1C92A9; 422 | } 423 | 424 | /* ArgumentName and ObjectFieldName */ 425 | .cm-attribute { 426 | color: #8B2BB9; 427 | } 428 | 429 | /* Number */ 430 | .cm-number { 431 | color: #2882F9; 432 | } 433 | 434 | /* String */ 435 | .cm-string { 436 | color: #D64292; 437 | } 438 | 439 | /* Boolean */ 440 | .cm-builtin { 441 | color: #D47509; 442 | } 443 | 444 | /* EnumValue */ 445 | .cm-string-2 { 446 | color: #0B7FC7; 447 | } 448 | 449 | /* Variable */ 450 | .cm-variable { 451 | color: #397D13; 452 | } 453 | 454 | /* Directive */ 455 | .cm-meta { 456 | color: #B33086; 457 | } 458 | 459 | /* Type */ 460 | .cm-atom { 461 | color: #CA9800; 462 | } 463 | /* BASICS */ 464 | 465 | .CodeMirror { 466 | /* Set height, width, borders, and global font properties here */ 467 | font-family: monospace; 468 | height: 300px; 469 | color: black; 470 | } 471 | 472 | /* PADDING */ 473 | 474 | .CodeMirror-lines { 475 | padding: 4px 0; /* Vertical padding around content */ 476 | } 477 | .CodeMirror pre { 478 | padding: 0 4px; /* Horizontal padding of content */ 479 | } 480 | 481 | .CodeMirror-scrollbar-filler, .CodeMirror-gutter-filler { 482 | background-color: white; /* The little square between H and V scrollbars */ 483 | } 484 | 485 | /* GUTTER */ 486 | 487 | .CodeMirror-gutters { 488 | border-right: 1px solid #ddd; 489 | background-color: #f7f7f7; 490 | white-space: nowrap; 491 | } 492 | .CodeMirror-linenumbers {} 493 | .CodeMirror-linenumber { 494 | padding: 0 3px 0 5px; 495 | min-width: 20px; 496 | text-align: right; 497 | color: #999; 498 | white-space: nowrap; 499 | } 500 | 501 | .CodeMirror-guttermarker { color: black; } 502 | .CodeMirror-guttermarker-subtle { color: #999; } 503 | 504 | /* CURSOR */ 505 | 506 | .CodeMirror div.CodeMirror-cursor { 507 | border-left: 1px solid black; 508 | } 509 | /* Shown when moving in bi-directional text */ 510 | .CodeMirror div.CodeMirror-secondarycursor { 511 | border-left: 1px solid silver; 512 | } 513 | .CodeMirror.cm-fat-cursor div.CodeMirror-cursor { 514 | width: auto; 515 | border: 0; 516 | background: #7e7; 517 | } 518 | .CodeMirror.cm-fat-cursor div.CodeMirror-cursors { 519 | z-index: 1; 520 | } 521 | 522 | .cm-animate-fat-cursor { 523 | width: auto; 524 | border: 0; 525 | -webkit-animation: blink 1.06s steps(1) infinite; 526 | -moz-animation: blink 1.06s steps(1) infinite; 527 | animation: blink 1.06s steps(1) infinite; 528 | } 529 | @-moz-keyframes blink { 530 | 0% { background: #7e7; } 531 | 50% { background: none; } 532 | 100% { background: #7e7; } 533 | } 534 | @-webkit-keyframes blink { 535 | 0% { background: #7e7; } 536 | 50% { background: none; } 537 | 100% { background: #7e7; } 538 | } 539 | @keyframes blink { 540 | 0% { background: #7e7; } 541 | 50% { background: none; } 542 | 100% { background: #7e7; } 543 | } 544 | 545 | /* Can style cursor different in overwrite (non-insert) mode */ 546 | div.CodeMirror-overwrite div.CodeMirror-cursor {} 547 | 548 | .cm-tab { display: inline-block; text-decoration: inherit; } 549 | 550 | .CodeMirror-ruler { 551 | border-left: 1px solid #ccc; 552 | position: absolute; 553 | } 554 | 555 | /* DEFAULT THEME */ 556 | 557 | .cm-s-default .cm-keyword {color: #708;} 558 | .cm-s-default .cm-atom {color: #219;} 559 | .cm-s-default .cm-number {color: #164;} 560 | .cm-s-default .cm-def {color: #00f;} 561 | .cm-s-default .cm-variable, 562 | .cm-s-default .cm-punctuation, 563 | .cm-s-default .cm-property, 564 | .cm-s-default .cm-operator {} 565 | .cm-s-default .cm-variable-2 {color: #05a;} 566 | .cm-s-default .cm-variable-3 {color: #085;} 567 | .cm-s-default .cm-comment {color: #a50;} 568 | .cm-s-default .cm-string {color: #a11;} 569 | .cm-s-default .cm-string-2 {color: #f50;} 570 | .cm-s-default .cm-meta {color: #555;} 571 | .cm-s-default .cm-qualifier {color: #555;} 572 | .cm-s-default .cm-builtin {color: #30a;} 573 | .cm-s-default .cm-bracket {color: #997;} 574 | .cm-s-default .cm-tag {color: #170;} 575 | .cm-s-default .cm-attribute {color: #00c;} 576 | .cm-s-default .cm-header {color: blue;} 577 | .cm-s-default .cm-quote {color: #090;} 578 | .cm-s-default .cm-hr {color: #999;} 579 | .cm-s-default .cm-link {color: #00c;} 580 | 581 | .cm-negative {color: #d44;} 582 | .cm-positive {color: #292;} 583 | .cm-header, .cm-strong {font-weight: bold;} 584 | .cm-em {font-style: italic;} 585 | .cm-link {text-decoration: underline;} 586 | .cm-strikethrough {text-decoration: line-through;} 587 | 588 | .cm-s-default .cm-error {color: #f00;} 589 | .cm-invalidchar {color: #f00;} 590 | 591 | .CodeMirror-composing { border-bottom: 2px solid; } 592 | 593 | /* Default styles for common addons */ 594 | 595 | div.CodeMirror span.CodeMirror-matchingbracket {color: #0f0;} 596 | div.CodeMirror span.CodeMirror-nonmatchingbracket {color: #f22;} 597 | .CodeMirror-matchingtag { background: rgba(255, 150, 0, .3); } 598 | .CodeMirror-activeline-background {background: #e8f2ff;} 599 | 600 | /* STOP */ 601 | 602 | /* The rest of this file contains styles related to the mechanics of 603 | the editor. You probably shouldn't touch them. */ 604 | 605 | .CodeMirror { 606 | position: relative; 607 | overflow: hidden; 608 | background: white; 609 | } 610 | 611 | .CodeMirror-scroll { 612 | overflow: scroll !important; /* Things will break if this is overridden */ 613 | /* 30px is the magic margin used to hide the element's real scrollbars */ 614 | /* See overflow: hidden in .CodeMirror */ 615 | margin-bottom: -30px; margin-right: -30px; 616 | padding-bottom: 30px; 617 | height: 100%; 618 | outline: none; /* Prevent dragging from highlighting the element */ 619 | position: relative; 620 | } 621 | .CodeMirror-sizer { 622 | position: relative; 623 | border-right: 30px solid transparent; 624 | } 625 | 626 | /* The fake, visible scrollbars. Used to force redraw during scrolling 627 | before actuall scrolling happens, thus preventing shaking and 628 | flickering artifacts. */ 629 | .CodeMirror-vscrollbar, .CodeMirror-hscrollbar, .CodeMirror-scrollbar-filler, .CodeMirror-gutter-filler { 630 | position: absolute; 631 | z-index: 6; 632 | display: none; 633 | } 634 | .CodeMirror-vscrollbar { 635 | right: 0; top: 0; 636 | overflow-x: hidden; 637 | overflow-y: scroll; 638 | } 639 | .CodeMirror-hscrollbar { 640 | bottom: 0; left: 0; 641 | overflow-y: hidden; 642 | overflow-x: scroll; 643 | } 644 | .CodeMirror-scrollbar-filler { 645 | right: 0; bottom: 0; 646 | } 647 | .CodeMirror-gutter-filler { 648 | left: 0; bottom: 0; 649 | } 650 | 651 | .CodeMirror-gutters { 652 | position: absolute; left: 0; top: 0; 653 | z-index: 3; 654 | } 655 | .CodeMirror-gutter { 656 | white-space: normal; 657 | height: 100%; 658 | display: inline-block; 659 | margin-bottom: -30px; 660 | /* Hack to make IE7 behave */ 661 | *zoom:1; 662 | *display:inline; 663 | } 664 | .CodeMirror-gutter-wrapper { 665 | position: absolute; 666 | z-index: 4; 667 | height: 100%; 668 | } 669 | .CodeMirror-gutter-elt { 670 | position: absolute; 671 | cursor: default; 672 | z-index: 4; 673 | } 674 | .CodeMirror-gutter-wrapper { 675 | -webkit-user-select: none; 676 | -moz-user-select: none; 677 | user-select: none; 678 | } 679 | 680 | .CodeMirror-lines { 681 | cursor: text; 682 | min-height: 1px; /* prevents collapsing before first draw */ 683 | } 684 | .CodeMirror pre { 685 | /* Reset some styles that the rest of the page might have set */ 686 | -moz-border-radius: 0; -webkit-border-radius: 0; border-radius: 0; 687 | border-width: 0; 688 | background: transparent; 689 | font-family: inherit; 690 | font-size: inherit; 691 | margin: 0; 692 | white-space: pre; 693 | word-wrap: normal; 694 | line-height: inherit; 695 | color: inherit; 696 | z-index: 2; 697 | position: relative; 698 | overflow: visible; 699 | -webkit-tap-highlight-color: transparent; 700 | } 701 | .CodeMirror-wrap pre { 702 | word-wrap: break-word; 703 | white-space: pre-wrap; 704 | word-break: normal; 705 | } 706 | 707 | .CodeMirror-linebackground { 708 | position: absolute; 709 | left: 0; right: 0; top: 0; bottom: 0; 710 | z-index: 0; 711 | } 712 | 713 | .CodeMirror-linewidget { 714 | position: relative; 715 | z-index: 2; 716 | overflow: auto; 717 | } 718 | 719 | .CodeMirror-widget {} 720 | 721 | .CodeMirror-code { 722 | outline: none; 723 | } 724 | 725 | /* Force content-box sizing for the elements where we expect it */ 726 | .CodeMirror-scroll, 727 | .CodeMirror-sizer, 728 | .CodeMirror-gutter, 729 | .CodeMirror-gutters, 730 | .CodeMirror-linenumber { 731 | -moz-box-sizing: content-box; 732 | box-sizing: content-box; 733 | } 734 | 735 | .CodeMirror-measure { 736 | position: absolute; 737 | width: 100%; 738 | height: 0; 739 | overflow: hidden; 740 | visibility: hidden; 741 | } 742 | .CodeMirror-measure pre { position: static; } 743 | 744 | .CodeMirror div.CodeMirror-cursor { 745 | position: absolute; 746 | border-right: none; 747 | width: 0; 748 | } 749 | 750 | div.CodeMirror-cursors { 751 | visibility: hidden; 752 | position: relative; 753 | z-index: 3; 754 | } 755 | .CodeMirror-focused div.CodeMirror-cursors { 756 | visibility: visible; 757 | } 758 | 759 | .CodeMirror-selected { background: #d9d9d9; } 760 | .CodeMirror-focused .CodeMirror-selected { background: #d7d4f0; } 761 | .CodeMirror-crosshair { cursor: crosshair; } 762 | .CodeMirror ::selection { background: #d7d4f0; } 763 | .CodeMirror ::-moz-selection { background: #d7d4f0; } 764 | 765 | .cm-searching { 766 | background: #ffa; 767 | background: rgba(255, 255, 0, .4); 768 | } 769 | 770 | /* IE7 hack to prevent it from returning funny offsetTops on the spans */ 771 | .CodeMirror span { *vertical-align: text-bottom; } 772 | 773 | /* Used to force a border model for a node */ 774 | .cm-force-border { padding-right: .1px; } 775 | 776 | @media print { 777 | /* Hide the cursor when printing */ 778 | .CodeMirror div.CodeMirror-cursors { 779 | visibility: hidden; 780 | } 781 | } 782 | 783 | /* See issue #2901 */ 784 | .cm-tab-wrap-hack:after { content: ''; } 785 | 786 | /* Help users use markselection to safely style text background */ 787 | span.CodeMirror-selectedtext { background: none; } 788 | #graphiql-container .doc-explorer { 789 | background: white; 790 | } 791 | 792 | #graphiql-container .doc-explorer-title-bar { 793 | cursor: default; 794 | display: -webkit-flex; 795 | display: flex; 796 | height: 34px; 797 | line-height: 14px; 798 | padding: 8px 8px 5px; 799 | position: relative; 800 | -webkit-user-select: none; 801 | user-select: none; 802 | } 803 | 804 | #graphiql-container .doc-explorer-title { 805 | padding: 10px 0 10px 10px; 806 | font-weight: bold; 807 | text-align: center; 808 | text-overflow: ellipsis; 809 | white-space: nowrap; 810 | overflow-x: hidden; 811 | -webkit-flex: 1; 812 | flex: 1; 813 | } 814 | 815 | #graphiql-container .doc-explorer-back { 816 | color: #3B5998; 817 | cursor: pointer; 818 | margin: -7px 0 -6px -8px; 819 | overflow-x: hidden; 820 | padding: 17px 12px 16px 16px; 821 | text-overflow: ellipsis; 822 | white-space: nowrap; 823 | } 824 | 825 | #graphiql-container .doc-explorer-back:before { 826 | border-left: 2px solid #3B5998; 827 | border-top: 2px solid #3B5998; 828 | content: ''; 829 | display: inline-block; 830 | height: 9px; 831 | margin: 0 3px -1px 0; 832 | position: relative; 833 | width: 9px; 834 | -webkit-transform: rotate(-45deg); 835 | transform: rotate(-45deg); 836 | } 837 | 838 | #graphiql-container .doc-explorer-rhs { 839 | position: relative; 840 | } 841 | 842 | #graphiql-container .doc-explorer-contents { 843 | position: relative; 844 | height: 100%; 845 | background-color: #ffffff; 846 | border-top: 1px solid #d6d6d6; 847 | padding: 20px 15px; 848 | overflow-y: auto; 849 | min-width: 300px; 850 | } 851 | 852 | #graphiql-container .doc-type-description p:first-child , 853 | #graphiql-container .doc-type-description blockquote:first-child { 854 | margin-top: 0; 855 | } 856 | 857 | #graphiql-container .doc-explorer-contents a { 858 | cursor: pointer; 859 | text-decoration: none; 860 | } 861 | 862 | #graphiql-container .doc-explorer-contents a:hover { 863 | text-decoration: underline; 864 | } 865 | 866 | #graphiql-container .doc-value-description { 867 | padding: 4px 0 8px 12px; 868 | } 869 | 870 | #graphiql-container .doc-category { 871 | margin: 20px 0; 872 | } 873 | 874 | #graphiql-container .doc-category-title { 875 | border-bottom: 1px solid #e0e0e0; 876 | color: #777; 877 | cursor: default; 878 | font-size: 14px; 879 | font-variant: small-caps; 880 | font-weight: bold; 881 | letter-spacing: 1px; 882 | margin-bottom: 10px; 883 | padding: 10px 0; 884 | -webkit-user-select: none; 885 | user-select: none; 886 | } 887 | 888 | #graphiql-container .doc-category-item { 889 | margin: 12px 0; 890 | color: #555; 891 | } 892 | 893 | #graphiql-container .keyword { 894 | color: #B11A04; 895 | } 896 | 897 | #graphiql-container .type-name { 898 | color: #CA9800; 899 | } 900 | 901 | #graphiql-container .field-name { 902 | color: #1F61A0; 903 | } 904 | 905 | #graphiql-container .value-name { 906 | color: #0B7FC7; 907 | } 908 | 909 | #graphiql-container .arg-name { 910 | color: #8B2BB9; 911 | } 912 | 913 | #graphiql-container .arg:after { 914 | content: ', '; 915 | } 916 | 917 | #graphiql-container .arg:last-child:after { 918 | content: ''; 919 | } 920 | .CodeMirror-foldmarker { 921 | color: blue; 922 | text-shadow: #b9f 1px 1px 2px, #b9f -1px -1px 2px, #b9f 1px -1px 2px, #b9f -1px 1px 2px; 923 | font-family: arial; 924 | line-height: .3; 925 | cursor: pointer; 926 | } 927 | .CodeMirror-foldgutter { 928 | width: .7em; 929 | } 930 | .CodeMirror-foldgutter-open, 931 | .CodeMirror-foldgutter-folded { 932 | cursor: pointer; 933 | } 934 | .CodeMirror-foldgutter-open:after { 935 | content: "\25BE"; 936 | } 937 | .CodeMirror-foldgutter-folded:after { 938 | content: "\25B8"; 939 | } 940 | /* The lint marker gutter */ 941 | .CodeMirror-lint-markers { 942 | width: 16px; 943 | } 944 | 945 | .CodeMirror-lint-tooltip { 946 | background-color: infobackground; 947 | border: 1px solid black; 948 | border-radius: 4px 4px 4px 4px; 949 | color: infotext; 950 | font-family: monospace; 951 | font-size: 10pt; 952 | overflow: hidden; 953 | padding: 2px 5px; 954 | position: fixed; 955 | white-space: pre; 956 | white-space: pre-wrap; 957 | z-index: 100; 958 | max-width: 600px; 959 | opacity: 0; 960 | transition: opacity .4s; 961 | -moz-transition: opacity .4s; 962 | -webkit-transition: opacity .4s; 963 | -o-transition: opacity .4s; 964 | -ms-transition: opacity .4s; 965 | } 966 | 967 | .CodeMirror-lint-mark-error, .CodeMirror-lint-mark-warning { 968 | background-position: left bottom; 969 | background-repeat: repeat-x; 970 | } 971 | 972 | .CodeMirror-lint-mark-error { 973 | background-image: 974 | url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAQAAAADCAYAAAC09K7GAAAAAXNSR0IArs4c6QAAAAZiS0dEAP8A/wD/oL2nkwAAAAlwSFlzAAALEwAACxMBAJqcGAAAAAd0SU1FB9sJDw4cOCW1/KIAAAAZdEVYdENvbW1lbnQAQ3JlYXRlZCB3aXRoIEdJTVBXgQ4XAAAAHElEQVQI12NggIL/DAz/GdA5/xkY/qPKMDAwAADLZwf5rvm+LQAAAABJRU5ErkJggg==") 975 | ; 976 | } 977 | 978 | .CodeMirror-lint-mark-warning { 979 | background-image: url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAQAAAADCAYAAAC09K7GAAAAAXNSR0IArs4c6QAAAAZiS0dEAP8A/wD/oL2nkwAAAAlwSFlzAAALEwAACxMBAJqcGAAAAAd0SU1FB9sJFhQXEbhTg7YAAAAZdEVYdENvbW1lbnQAQ3JlYXRlZCB3aXRoIEdJTVBXgQ4XAAAAMklEQVQI12NkgIIvJ3QXMjAwdDN+OaEbysDA4MPAwNDNwMCwiOHLCd1zX07o6kBVGQEAKBANtobskNMAAAAASUVORK5CYII="); 980 | } 981 | 982 | .CodeMirror-lint-marker-error, .CodeMirror-lint-marker-warning { 983 | background-position: center center; 984 | background-repeat: no-repeat; 985 | cursor: pointer; 986 | display: inline-block; 987 | height: 16px; 988 | width: 16px; 989 | vertical-align: middle; 990 | position: relative; 991 | } 992 | 993 | .CodeMirror-lint-message-error, .CodeMirror-lint-message-warning { 994 | padding-left: 18px; 995 | background-position: top left; 996 | background-repeat: no-repeat; 997 | } 998 | 999 | .CodeMirror-lint-marker-error, .CodeMirror-lint-message-error { 1000 | background-image: url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAMAAAAoLQ9TAAAAHlBMVEW7AAC7AACxAAC7AAC7AAAAAAC4AAC5AAD///+7AAAUdclpAAAABnRSTlMXnORSiwCK0ZKSAAAATUlEQVR42mWPOQ7AQAgDuQLx/z8csYRmPRIFIwRGnosRrpamvkKi0FTIiMASR3hhKW+hAN6/tIWhu9PDWiTGNEkTtIOucA5Oyr9ckPgAWm0GPBog6v4AAAAASUVORK5CYII="); 1001 | } 1002 | 1003 | .CodeMirror-lint-marker-warning, .CodeMirror-lint-message-warning { 1004 | background-image: url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAMAAAAoLQ9TAAAANlBMVEX/uwDvrwD/uwD/uwD/uwD/uwD/uwD/uwD/uwD6twD/uwAAAADurwD2tQD7uAD+ugAAAAD/uwDhmeTRAAAADHRSTlMJ8mN1EYcbmiixgACm7WbuAAAAVklEQVR42n3PUQqAIBBFUU1LLc3u/jdbOJoW1P08DA9Gba8+YWJ6gNJoNYIBzAA2chBth5kLmG9YUoG0NHAUwFXwO9LuBQL1giCQb8gC9Oro2vp5rncCIY8L8uEx5ZkAAAAASUVORK5CYII="); 1005 | } 1006 | 1007 | .CodeMirror-lint-marker-multiple { 1008 | background-image: url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAcAAAAHCAMAAADzjKfhAAAACVBMVEUAAAAAAAC/v7914kyHAAAAAXRSTlMAQObYZgAAACNJREFUeNo1ioEJAAAIwmz/H90iFFSGJgFMe3gaLZ0od+9/AQZ0ADosbYraAAAAAElFTkSuQmCC"); 1009 | background-repeat: no-repeat; 1010 | background-position: right bottom; 1011 | width: 100%; height: 100%; 1012 | } 1013 | .CodeMirror-hints { 1014 | background: white; 1015 | -webkit-box-shadow: 0 1px 3px rgba(0, 0, 0, 0.45); 1016 | -moz-box-shadow: 0 1px 3px rgba(0, 0, 0, 0.45); 1017 | box-shadow: 0 1px 3px rgba(0, 0, 0, 0.45); 1018 | font-family: 'Consolas', 'Inconsolata', 'Droid Sans Mono', 'Monaco', monospace; 1019 | font-size: 13px; 1020 | list-style: none; 1021 | margin: 0; 1022 | margin-left: -6px; 1023 | max-height: 14.5em; 1024 | overflow-y: auto; 1025 | overflow: hidden; 1026 | padding: 0; 1027 | position: absolute; 1028 | z-index: 10; 1029 | } 1030 | 1031 | .CodeMirror-hints-wrapper { 1032 | background: white; 1033 | -webkit-box-shadow: 0 1px 3px rgba(0, 0, 0, 0.45); 1034 | -moz-box-shadow: 0 1px 3px rgba(0, 0, 0, 0.45); 1035 | box-shadow: 0 1px 3px rgba(0, 0, 0, 0.45); 1036 | margin-left: -6px; 1037 | position: absolute; 1038 | z-index: 10; 1039 | } 1040 | 1041 | .CodeMirror-hints-wrapper .CodeMirror-hints { 1042 | -webkit-box-shadow: none; 1043 | -moz-box-shadow: none; 1044 | box-shadow: none; 1045 | position: relative; 1046 | margin-left: 0; 1047 | z-index: 0; 1048 | } 1049 | 1050 | .CodeMirror-hint { 1051 | border-top: solid 1px #f7f7f7; 1052 | color: #141823; 1053 | cursor: pointer; 1054 | margin: 0; 1055 | max-width: 300px; 1056 | overflow: hidden; 1057 | padding: 2px 6px; 1058 | white-space: pre; 1059 | } 1060 | 1061 | li.CodeMirror-hint-active { 1062 | background-color: #08f; 1063 | border-top-color: white; 1064 | color: white; 1065 | } 1066 | 1067 | .CodeMirror-hint-information { 1068 | border-top: solid 1px #c0c0c0; 1069 | max-width: 300px; 1070 | padding: 4px 6px; 1071 | position: relative; 1072 | z-index: 1; 1073 | } 1074 | 1075 | .CodeMirror-hint-information:first-child { 1076 | border-bottom: solid 1px #c0c0c0; 1077 | border-top: none; 1078 | margin-bottom: -1px; 1079 | } 1080 | -------------------------------------------------------------------------------- /src/server/data/schema.json: -------------------------------------------------------------------------------- 1 | { 2 | "data": { 3 | "__schema": { 4 | "queryType": { 5 | "name": "Query" 6 | }, 7 | "mutationType": null, 8 | "types": [ 9 | { 10 | "kind": "OBJECT", 11 | "name": "Query", 12 | "description": null, 13 | "fields": [ 14 | { 15 | "name": "node", 16 | "description": "Fetches an object given its ID", 17 | "args": [ 18 | { 19 | "name": "id", 20 | "description": "The ID of an object", 21 | "type": { 22 | "kind": "NON_NULL", 23 | "name": null, 24 | "ofType": { 25 | "kind": "SCALAR", 26 | "name": "ID", 27 | "ofType": null 28 | } 29 | }, 30 | "defaultValue": null 31 | } 32 | ], 33 | "type": { 34 | "kind": "INTERFACE", 35 | "name": "Node", 36 | "ofType": null 37 | }, 38 | "isDeprecated": false, 39 | "deprecationReason": null 40 | }, 41 | { 42 | "name": "example", 43 | "description": null, 44 | "args": [], 45 | "type": { 46 | "kind": "OBJECT", 47 | "name": "Example", 48 | "ofType": null 49 | }, 50 | "isDeprecated": false, 51 | "deprecationReason": null 52 | }, 53 | { 54 | "name": "user", 55 | "description": null, 56 | "args": [], 57 | "type": { 58 | "kind": "OBJECT", 59 | "name": "User", 60 | "ofType": null 61 | }, 62 | "isDeprecated": false, 63 | "deprecationReason": null 64 | } 65 | ], 66 | "inputFields": null, 67 | "interfaces": [], 68 | "enumValues": null, 69 | "possibleTypes": null 70 | }, 71 | { 72 | "kind": "SCALAR", 73 | "name": "ID", 74 | "description": "The `ID` scalar type represents a unique identifier, often used to refetch an object or as key for a cache. The ID type appears in a JSON response as a String; however, it is not intended to be human-readable. When expected as an input type, any string (such as `\"4\"`) or integer (such as `4`) input value will be accepted as an ID.", 75 | "fields": null, 76 | "inputFields": null, 77 | "interfaces": null, 78 | "enumValues": null, 79 | "possibleTypes": null 80 | }, 81 | { 82 | "kind": "INTERFACE", 83 | "name": "Node", 84 | "description": "An object with an ID", 85 | "fields": [ 86 | { 87 | "name": "id", 88 | "description": "The id of the object.", 89 | "args": [], 90 | "type": { 91 | "kind": "NON_NULL", 92 | "name": null, 93 | "ofType": { 94 | "kind": "SCALAR", 95 | "name": "ID", 96 | "ofType": null 97 | } 98 | }, 99 | "isDeprecated": false, 100 | "deprecationReason": null 101 | } 102 | ], 103 | "inputFields": null, 104 | "interfaces": null, 105 | "enumValues": null, 106 | "possibleTypes": [ 107 | { 108 | "kind": "OBJECT", 109 | "name": "User", 110 | "ofType": null 111 | }, 112 | { 113 | "kind": "OBJECT", 114 | "name": "Example", 115 | "ofType": null 116 | } 117 | ] 118 | }, 119 | { 120 | "kind": "OBJECT", 121 | "name": "User", 122 | "description": null, 123 | "fields": [ 124 | { 125 | "name": "id", 126 | "description": "The ID of an object", 127 | "args": [], 128 | "type": { 129 | "kind": "NON_NULL", 130 | "name": null, 131 | "ofType": { 132 | "kind": "SCALAR", 133 | "name": "ID", 134 | "ofType": null 135 | } 136 | }, 137 | "isDeprecated": false, 138 | "deprecationReason": null 139 | }, 140 | { 141 | "name": "email", 142 | "description": "email of the user", 143 | "args": [], 144 | "type": { 145 | "kind": "SCALAR", 146 | "name": "String", 147 | "ofType": null 148 | }, 149 | "isDeprecated": false, 150 | "deprecationReason": null 151 | }, 152 | { 153 | "name": "password", 154 | "description": "encoded password", 155 | "args": [], 156 | "type": { 157 | "kind": "SCALAR", 158 | "name": "String", 159 | "ofType": null 160 | }, 161 | "isDeprecated": false, 162 | "deprecationReason": null 163 | } 164 | ], 165 | "inputFields": null, 166 | "interfaces": [ 167 | { 168 | "kind": "INTERFACE", 169 | "name": "Node", 170 | "ofType": null 171 | } 172 | ], 173 | "enumValues": null, 174 | "possibleTypes": null 175 | }, 176 | { 177 | "kind": "SCALAR", 178 | "name": "String", 179 | "description": "The `String` scalar type represents textual data, represented as UTF-8 character sequences. The String type is most often used by GraphQL to represent free-form human-readable text.", 180 | "fields": null, 181 | "inputFields": null, 182 | "interfaces": null, 183 | "enumValues": null, 184 | "possibleTypes": null 185 | }, 186 | { 187 | "kind": "OBJECT", 188 | "name": "Example", 189 | "description": null, 190 | "fields": [ 191 | { 192 | "name": "id", 193 | "description": "The ID of an object", 194 | "args": [], 195 | "type": { 196 | "kind": "NON_NULL", 197 | "name": null, 198 | "ofType": { 199 | "kind": "SCALAR", 200 | "name": "ID", 201 | "ofType": null 202 | } 203 | }, 204 | "isDeprecated": false, 205 | "deprecationReason": null 206 | }, 207 | { 208 | "name": "text", 209 | "description": "Hello World", 210 | "args": [], 211 | "type": { 212 | "kind": "SCALAR", 213 | "name": "String", 214 | "ofType": null 215 | }, 216 | "isDeprecated": false, 217 | "deprecationReason": null 218 | }, 219 | { 220 | "name": "version", 221 | "description": "version", 222 | "args": [], 223 | "type": { 224 | "kind": "SCALAR", 225 | "name": "Int", 226 | "ofType": null 227 | }, 228 | "isDeprecated": false, 229 | "deprecationReason": null 230 | } 231 | ], 232 | "inputFields": null, 233 | "interfaces": [ 234 | { 235 | "kind": "INTERFACE", 236 | "name": "Node", 237 | "ofType": null 238 | } 239 | ], 240 | "enumValues": null, 241 | "possibleTypes": null 242 | }, 243 | { 244 | "kind": "SCALAR", 245 | "name": "Int", 246 | "description": "The `Int` scalar type represents non-fractional signed whole numeric values. Int can represent values between -(2^53 - 1) and 2^53 - 1 since represented in JSON as double-precision floating point numbers specifiedby [IEEE 754](http://en.wikipedia.org/wiki/IEEE_floating_point).", 247 | "fields": null, 248 | "inputFields": null, 249 | "interfaces": null, 250 | "enumValues": null, 251 | "possibleTypes": null 252 | }, 253 | { 254 | "kind": "OBJECT", 255 | "name": "__Schema", 256 | "description": "A GraphQL Schema defines the capabilities of a GraphQL server. It exposes all available types and directives on the server, as well as the entry points for query and mutation operations.", 257 | "fields": [ 258 | { 259 | "name": "types", 260 | "description": "A list of all types supported by this server.", 261 | "args": [], 262 | "type": { 263 | "kind": "NON_NULL", 264 | "name": null, 265 | "ofType": { 266 | "kind": "LIST", 267 | "name": null, 268 | "ofType": { 269 | "kind": "NON_NULL", 270 | "name": null, 271 | "ofType": { 272 | "kind": "OBJECT", 273 | "name": "__Type" 274 | } 275 | } 276 | } 277 | }, 278 | "isDeprecated": false, 279 | "deprecationReason": null 280 | }, 281 | { 282 | "name": "queryType", 283 | "description": "The type that query operations will be rooted at.", 284 | "args": [], 285 | "type": { 286 | "kind": "NON_NULL", 287 | "name": null, 288 | "ofType": { 289 | "kind": "OBJECT", 290 | "name": "__Type", 291 | "ofType": null 292 | } 293 | }, 294 | "isDeprecated": false, 295 | "deprecationReason": null 296 | }, 297 | { 298 | "name": "mutationType", 299 | "description": "If this server supports mutation, the type that mutation operations will be rooted at.", 300 | "args": [], 301 | "type": { 302 | "kind": "OBJECT", 303 | "name": "__Type", 304 | "ofType": null 305 | }, 306 | "isDeprecated": false, 307 | "deprecationReason": null 308 | }, 309 | { 310 | "name": "directives", 311 | "description": "A list of all directives supported by this server.", 312 | "args": [], 313 | "type": { 314 | "kind": "NON_NULL", 315 | "name": null, 316 | "ofType": { 317 | "kind": "LIST", 318 | "name": null, 319 | "ofType": { 320 | "kind": "NON_NULL", 321 | "name": null, 322 | "ofType": { 323 | "kind": "OBJECT", 324 | "name": "__Directive" 325 | } 326 | } 327 | } 328 | }, 329 | "isDeprecated": false, 330 | "deprecationReason": null 331 | } 332 | ], 333 | "inputFields": null, 334 | "interfaces": [], 335 | "enumValues": null, 336 | "possibleTypes": null 337 | }, 338 | { 339 | "kind": "OBJECT", 340 | "name": "__Type", 341 | "description": "The fundamental unit of any GraphQL Schema is the type. There are many kinds of types in GraphQL as represented by the `__TypeKind` enum.\n\nDepending on the kind of a type, certain fields describe information about that type. Scalar types provide no information beyond a name and description, while Enum types provide their values. Object and Interface types provide the fields they describe. Abstract types, Union and Interface, provide the Object types possible at runtime. List and NonNull types compose other types.", 342 | "fields": [ 343 | { 344 | "name": "kind", 345 | "description": null, 346 | "args": [], 347 | "type": { 348 | "kind": "NON_NULL", 349 | "name": null, 350 | "ofType": { 351 | "kind": "ENUM", 352 | "name": "__TypeKind", 353 | "ofType": null 354 | } 355 | }, 356 | "isDeprecated": false, 357 | "deprecationReason": null 358 | }, 359 | { 360 | "name": "name", 361 | "description": null, 362 | "args": [], 363 | "type": { 364 | "kind": "SCALAR", 365 | "name": "String", 366 | "ofType": null 367 | }, 368 | "isDeprecated": false, 369 | "deprecationReason": null 370 | }, 371 | { 372 | "name": "description", 373 | "description": null, 374 | "args": [], 375 | "type": { 376 | "kind": "SCALAR", 377 | "name": "String", 378 | "ofType": null 379 | }, 380 | "isDeprecated": false, 381 | "deprecationReason": null 382 | }, 383 | { 384 | "name": "fields", 385 | "description": null, 386 | "args": [ 387 | { 388 | "name": "includeDeprecated", 389 | "description": null, 390 | "type": { 391 | "kind": "SCALAR", 392 | "name": "Boolean", 393 | "ofType": null 394 | }, 395 | "defaultValue": "false" 396 | } 397 | ], 398 | "type": { 399 | "kind": "LIST", 400 | "name": null, 401 | "ofType": { 402 | "kind": "NON_NULL", 403 | "name": null, 404 | "ofType": { 405 | "kind": "OBJECT", 406 | "name": "__Field", 407 | "ofType": null 408 | } 409 | } 410 | }, 411 | "isDeprecated": false, 412 | "deprecationReason": null 413 | }, 414 | { 415 | "name": "interfaces", 416 | "description": null, 417 | "args": [], 418 | "type": { 419 | "kind": "LIST", 420 | "name": null, 421 | "ofType": { 422 | "kind": "NON_NULL", 423 | "name": null, 424 | "ofType": { 425 | "kind": "OBJECT", 426 | "name": "__Type", 427 | "ofType": null 428 | } 429 | } 430 | }, 431 | "isDeprecated": false, 432 | "deprecationReason": null 433 | }, 434 | { 435 | "name": "possibleTypes", 436 | "description": null, 437 | "args": [], 438 | "type": { 439 | "kind": "LIST", 440 | "name": null, 441 | "ofType": { 442 | "kind": "NON_NULL", 443 | "name": null, 444 | "ofType": { 445 | "kind": "OBJECT", 446 | "name": "__Type", 447 | "ofType": null 448 | } 449 | } 450 | }, 451 | "isDeprecated": false, 452 | "deprecationReason": null 453 | }, 454 | { 455 | "name": "enumValues", 456 | "description": null, 457 | "args": [ 458 | { 459 | "name": "includeDeprecated", 460 | "description": null, 461 | "type": { 462 | "kind": "SCALAR", 463 | "name": "Boolean", 464 | "ofType": null 465 | }, 466 | "defaultValue": "false" 467 | } 468 | ], 469 | "type": { 470 | "kind": "LIST", 471 | "name": null, 472 | "ofType": { 473 | "kind": "NON_NULL", 474 | "name": null, 475 | "ofType": { 476 | "kind": "OBJECT", 477 | "name": "__EnumValue", 478 | "ofType": null 479 | } 480 | } 481 | }, 482 | "isDeprecated": false, 483 | "deprecationReason": null 484 | }, 485 | { 486 | "name": "inputFields", 487 | "description": null, 488 | "args": [], 489 | "type": { 490 | "kind": "LIST", 491 | "name": null, 492 | "ofType": { 493 | "kind": "NON_NULL", 494 | "name": null, 495 | "ofType": { 496 | "kind": "OBJECT", 497 | "name": "__InputValue", 498 | "ofType": null 499 | } 500 | } 501 | }, 502 | "isDeprecated": false, 503 | "deprecationReason": null 504 | }, 505 | { 506 | "name": "ofType", 507 | "description": null, 508 | "args": [], 509 | "type": { 510 | "kind": "OBJECT", 511 | "name": "__Type", 512 | "ofType": null 513 | }, 514 | "isDeprecated": false, 515 | "deprecationReason": null 516 | } 517 | ], 518 | "inputFields": null, 519 | "interfaces": [], 520 | "enumValues": null, 521 | "possibleTypes": null 522 | }, 523 | { 524 | "kind": "ENUM", 525 | "name": "__TypeKind", 526 | "description": "An enum describing what kind of type a given `__Type` is.", 527 | "fields": null, 528 | "inputFields": null, 529 | "interfaces": null, 530 | "enumValues": [ 531 | { 532 | "name": "SCALAR", 533 | "description": "Indicates this type is a scalar.", 534 | "isDeprecated": false, 535 | "deprecationReason": null 536 | }, 537 | { 538 | "name": "OBJECT", 539 | "description": "Indicates this type is an object. `fields` and `interfaces` are valid fields.", 540 | "isDeprecated": false, 541 | "deprecationReason": null 542 | }, 543 | { 544 | "name": "INTERFACE", 545 | "description": "Indicates this type is an interface. `fields` and `possibleTypes` are valid fields.", 546 | "isDeprecated": false, 547 | "deprecationReason": null 548 | }, 549 | { 550 | "name": "UNION", 551 | "description": "Indicates this type is a union. `possibleTypes` is a valid field.", 552 | "isDeprecated": false, 553 | "deprecationReason": null 554 | }, 555 | { 556 | "name": "ENUM", 557 | "description": "Indicates this type is an enum. `enumValues` is a valid field.", 558 | "isDeprecated": false, 559 | "deprecationReason": null 560 | }, 561 | { 562 | "name": "INPUT_OBJECT", 563 | "description": "Indicates this type is an input object. `inputFields` is a valid field.", 564 | "isDeprecated": false, 565 | "deprecationReason": null 566 | }, 567 | { 568 | "name": "LIST", 569 | "description": "Indicates this type is a list. `ofType` is a valid field.", 570 | "isDeprecated": false, 571 | "deprecationReason": null 572 | }, 573 | { 574 | "name": "NON_NULL", 575 | "description": "Indicates this type is a non-null. `ofType` is a valid field.", 576 | "isDeprecated": false, 577 | "deprecationReason": null 578 | } 579 | ], 580 | "possibleTypes": null 581 | }, 582 | { 583 | "kind": "SCALAR", 584 | "name": "Boolean", 585 | "description": "The `Boolean` scalar type represents `true` or `false`.", 586 | "fields": null, 587 | "inputFields": null, 588 | "interfaces": null, 589 | "enumValues": null, 590 | "possibleTypes": null 591 | }, 592 | { 593 | "kind": "OBJECT", 594 | "name": "__Field", 595 | "description": "Object and Interface types are described by a list of Fields, each of which has a name, potentially a list of arguments, and a return type.", 596 | "fields": [ 597 | { 598 | "name": "name", 599 | "description": null, 600 | "args": [], 601 | "type": { 602 | "kind": "NON_NULL", 603 | "name": null, 604 | "ofType": { 605 | "kind": "SCALAR", 606 | "name": "String", 607 | "ofType": null 608 | } 609 | }, 610 | "isDeprecated": false, 611 | "deprecationReason": null 612 | }, 613 | { 614 | "name": "description", 615 | "description": null, 616 | "args": [], 617 | "type": { 618 | "kind": "SCALAR", 619 | "name": "String", 620 | "ofType": null 621 | }, 622 | "isDeprecated": false, 623 | "deprecationReason": null 624 | }, 625 | { 626 | "name": "args", 627 | "description": null, 628 | "args": [], 629 | "type": { 630 | "kind": "NON_NULL", 631 | "name": null, 632 | "ofType": { 633 | "kind": "LIST", 634 | "name": null, 635 | "ofType": { 636 | "kind": "NON_NULL", 637 | "name": null, 638 | "ofType": { 639 | "kind": "OBJECT", 640 | "name": "__InputValue" 641 | } 642 | } 643 | } 644 | }, 645 | "isDeprecated": false, 646 | "deprecationReason": null 647 | }, 648 | { 649 | "name": "type", 650 | "description": null, 651 | "args": [], 652 | "type": { 653 | "kind": "NON_NULL", 654 | "name": null, 655 | "ofType": { 656 | "kind": "OBJECT", 657 | "name": "__Type", 658 | "ofType": null 659 | } 660 | }, 661 | "isDeprecated": false, 662 | "deprecationReason": null 663 | }, 664 | { 665 | "name": "isDeprecated", 666 | "description": null, 667 | "args": [], 668 | "type": { 669 | "kind": "NON_NULL", 670 | "name": null, 671 | "ofType": { 672 | "kind": "SCALAR", 673 | "name": "Boolean", 674 | "ofType": null 675 | } 676 | }, 677 | "isDeprecated": false, 678 | "deprecationReason": null 679 | }, 680 | { 681 | "name": "deprecationReason", 682 | "description": null, 683 | "args": [], 684 | "type": { 685 | "kind": "SCALAR", 686 | "name": "String", 687 | "ofType": null 688 | }, 689 | "isDeprecated": false, 690 | "deprecationReason": null 691 | } 692 | ], 693 | "inputFields": null, 694 | "interfaces": [], 695 | "enumValues": null, 696 | "possibleTypes": null 697 | }, 698 | { 699 | "kind": "OBJECT", 700 | "name": "__InputValue", 701 | "description": "Arguments provided to Fields or Directives and the input fields of an InputObject are represented as Input Values which describe their type and optionally a default value.", 702 | "fields": [ 703 | { 704 | "name": "name", 705 | "description": null, 706 | "args": [], 707 | "type": { 708 | "kind": "NON_NULL", 709 | "name": null, 710 | "ofType": { 711 | "kind": "SCALAR", 712 | "name": "String", 713 | "ofType": null 714 | } 715 | }, 716 | "isDeprecated": false, 717 | "deprecationReason": null 718 | }, 719 | { 720 | "name": "description", 721 | "description": null, 722 | "args": [], 723 | "type": { 724 | "kind": "SCALAR", 725 | "name": "String", 726 | "ofType": null 727 | }, 728 | "isDeprecated": false, 729 | "deprecationReason": null 730 | }, 731 | { 732 | "name": "type", 733 | "description": null, 734 | "args": [], 735 | "type": { 736 | "kind": "NON_NULL", 737 | "name": null, 738 | "ofType": { 739 | "kind": "OBJECT", 740 | "name": "__Type", 741 | "ofType": null 742 | } 743 | }, 744 | "isDeprecated": false, 745 | "deprecationReason": null 746 | }, 747 | { 748 | "name": "defaultValue", 749 | "description": "A GraphQL-formatted string representing the default value for this input value.", 750 | "args": [], 751 | "type": { 752 | "kind": "SCALAR", 753 | "name": "String", 754 | "ofType": null 755 | }, 756 | "isDeprecated": false, 757 | "deprecationReason": null 758 | } 759 | ], 760 | "inputFields": null, 761 | "interfaces": [], 762 | "enumValues": null, 763 | "possibleTypes": null 764 | }, 765 | { 766 | "kind": "OBJECT", 767 | "name": "__EnumValue", 768 | "description": "One possible value for a given Enum. Enum values are unique values, not a placeholder for a string or numeric value. However an Enum value is returned in a JSON response as a string.", 769 | "fields": [ 770 | { 771 | "name": "name", 772 | "description": null, 773 | "args": [], 774 | "type": { 775 | "kind": "NON_NULL", 776 | "name": null, 777 | "ofType": { 778 | "kind": "SCALAR", 779 | "name": "String", 780 | "ofType": null 781 | } 782 | }, 783 | "isDeprecated": false, 784 | "deprecationReason": null 785 | }, 786 | { 787 | "name": "description", 788 | "description": null, 789 | "args": [], 790 | "type": { 791 | "kind": "SCALAR", 792 | "name": "String", 793 | "ofType": null 794 | }, 795 | "isDeprecated": false, 796 | "deprecationReason": null 797 | }, 798 | { 799 | "name": "isDeprecated", 800 | "description": null, 801 | "args": [], 802 | "type": { 803 | "kind": "NON_NULL", 804 | "name": null, 805 | "ofType": { 806 | "kind": "SCALAR", 807 | "name": "Boolean", 808 | "ofType": null 809 | } 810 | }, 811 | "isDeprecated": false, 812 | "deprecationReason": null 813 | }, 814 | { 815 | "name": "deprecationReason", 816 | "description": null, 817 | "args": [], 818 | "type": { 819 | "kind": "SCALAR", 820 | "name": "String", 821 | "ofType": null 822 | }, 823 | "isDeprecated": false, 824 | "deprecationReason": null 825 | } 826 | ], 827 | "inputFields": null, 828 | "interfaces": [], 829 | "enumValues": null, 830 | "possibleTypes": null 831 | }, 832 | { 833 | "kind": "OBJECT", 834 | "name": "__Directive", 835 | "description": "A Directives provides a way to describe alternate runtime execution and type validation behavior in a GraphQL document.\n\nIn some cases, you need to provide options to alter GraphQL’s execution behavior in ways field arguments will not suffice, such as conditionally including or skipping a field. Directives provide this by describing additional information to the executor.", 836 | "fields": [ 837 | { 838 | "name": "name", 839 | "description": null, 840 | "args": [], 841 | "type": { 842 | "kind": "NON_NULL", 843 | "name": null, 844 | "ofType": { 845 | "kind": "SCALAR", 846 | "name": "String", 847 | "ofType": null 848 | } 849 | }, 850 | "isDeprecated": false, 851 | "deprecationReason": null 852 | }, 853 | { 854 | "name": "description", 855 | "description": null, 856 | "args": [], 857 | "type": { 858 | "kind": "SCALAR", 859 | "name": "String", 860 | "ofType": null 861 | }, 862 | "isDeprecated": false, 863 | "deprecationReason": null 864 | }, 865 | { 866 | "name": "args", 867 | "description": null, 868 | "args": [], 869 | "type": { 870 | "kind": "NON_NULL", 871 | "name": null, 872 | "ofType": { 873 | "kind": "LIST", 874 | "name": null, 875 | "ofType": { 876 | "kind": "NON_NULL", 877 | "name": null, 878 | "ofType": { 879 | "kind": "OBJECT", 880 | "name": "__InputValue" 881 | } 882 | } 883 | } 884 | }, 885 | "isDeprecated": false, 886 | "deprecationReason": null 887 | }, 888 | { 889 | "name": "onOperation", 890 | "description": null, 891 | "args": [], 892 | "type": { 893 | "kind": "NON_NULL", 894 | "name": null, 895 | "ofType": { 896 | "kind": "SCALAR", 897 | "name": "Boolean", 898 | "ofType": null 899 | } 900 | }, 901 | "isDeprecated": false, 902 | "deprecationReason": null 903 | }, 904 | { 905 | "name": "onFragment", 906 | "description": null, 907 | "args": [], 908 | "type": { 909 | "kind": "NON_NULL", 910 | "name": null, 911 | "ofType": { 912 | "kind": "SCALAR", 913 | "name": "Boolean", 914 | "ofType": null 915 | } 916 | }, 917 | "isDeprecated": false, 918 | "deprecationReason": null 919 | }, 920 | { 921 | "name": "onField", 922 | "description": null, 923 | "args": [], 924 | "type": { 925 | "kind": "NON_NULL", 926 | "name": null, 927 | "ofType": { 928 | "kind": "SCALAR", 929 | "name": "Boolean", 930 | "ofType": null 931 | } 932 | }, 933 | "isDeprecated": false, 934 | "deprecationReason": null 935 | } 936 | ], 937 | "inputFields": null, 938 | "interfaces": [], 939 | "enumValues": null, 940 | "possibleTypes": null 941 | } 942 | ], 943 | "directives": [ 944 | { 945 | "name": "include", 946 | "description": "Directs the executor to include this field or fragment only when the `if` argument is true.", 947 | "args": [ 948 | { 949 | "name": "if", 950 | "description": "Included when true.", 951 | "type": { 952 | "kind": "NON_NULL", 953 | "name": null, 954 | "ofType": { 955 | "kind": "SCALAR", 956 | "name": "Boolean", 957 | "ofType": null 958 | } 959 | }, 960 | "defaultValue": null 961 | } 962 | ], 963 | "onOperation": false, 964 | "onFragment": true, 965 | "onField": true 966 | }, 967 | { 968 | "name": "skip", 969 | "description": "Directs the executor to skip this field or fragment when the `if` argument is true.", 970 | "args": [ 971 | { 972 | "name": "if", 973 | "description": "Skipped when true.", 974 | "type": { 975 | "kind": "NON_NULL", 976 | "name": null, 977 | "ofType": { 978 | "kind": "SCALAR", 979 | "name": "Boolean", 980 | "ofType": null 981 | } 982 | }, 983 | "defaultValue": null 984 | } 985 | ], 986 | "onOperation": false, 987 | "onFragment": true, 988 | "onField": true 989 | } 990 | ] 991 | } 992 | } 993 | } --------------------------------------------------------------------------------