├── js ├── routes │ └── AppHomeRoute.js ├── queries │ └── ViewerQueries.js ├── components │ ├── App.js │ ├── TodoTextInput.js │ ├── TodoListFooter.js │ ├── TodoApp.js │ ├── TodoList.js │ └── Todo.js ├── mutations │ ├── RenameTodoMutation.js │ ├── AddTodoMutation.js │ ├── ChangeTodoStatusMutation.js │ ├── RemoveTodoMutation.js │ ├── MarkAllTodosMutation.js │ └── RemoveCompletedTodosMutation.js └── app.js ├── .gitignore ├── graphql.go ├── public ├── index.html ├── base.css ├── learn.json └── index.css ├── package.json ├── server.js ├── scripts └── updateSchema.go ├── LICENSE ├── README.md └── data ├── database.go ├── schema.go └── schema.json /js/routes/AppHomeRoute.js: -------------------------------------------------------------------------------- 1 | import Relay from 'react-relay'; 2 | 3 | export default class extends Relay.Route { 4 | static queries = { 5 | viewer: () => Relay.QL` 6 | query { 7 | viewer 8 | } 9 | `, 10 | }; 11 | static routeName = 'AppHomeRoute'; 12 | } 13 | -------------------------------------------------------------------------------- /js/queries/ViewerQueries.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2013-2015, Facebook, Inc. 3 | * All rights reserved. 4 | * 5 | * This source code is licensed under the BSD-style license found in the 6 | * LICENSE file in the root directory of this source tree. An additional grant 7 | * of patent rights can be found in the PATENTS file in the same directory. 8 | */ 9 | 10 | import Relay from 'react-relay'; 11 | 12 | export default { 13 | viewer: () => Relay.QL`query { viewer }`, 14 | }; 15 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | logs 3 | *.log 4 | 5 | # Runtime data 6 | pids 7 | *.pid 8 | *.seed 9 | 10 | # Directory for instrumented libs generated by jscoverage/JSCover 11 | lib-cov 12 | 13 | # Coverage directory used by tools like istanbul 14 | coverage 15 | 16 | # Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files) 17 | .grunt 18 | 19 | # node-waf configuration 20 | .lock-wscript 21 | 22 | # Compiled binary addons (http://nodejs.org/api/addons.html) 23 | build/Release 24 | 25 | # Dependency directory 26 | # https://www.npmjs.org/doc/misc/npm-faq.html#should-i-check-my-node_modules-folder-into-git 27 | node_modules 28 | -------------------------------------------------------------------------------- /graphql.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "github.com/graphql-go/handler" 5 | "github.com/sogko/todomvc-relay-go/data" 6 | "log" 7 | "net/http" 8 | ) 9 | 10 | func main() { 11 | 12 | // simplest relay-compliant graphql server HTTP handler 13 | h := handler.New(&handler.Config{ 14 | Schema: &data.Schema, 15 | Pretty: true, 16 | }) 17 | 18 | // create graphql endpoint 19 | http.Handle("/graphql", h) 20 | 21 | // serve! 22 | port := ":8080" 23 | log.Printf(`GraphQL server starting up on http://localhost%v`, port) 24 | err := http.ListenAndServe(port, nil) 25 | if err != nil { 26 | log.Fatalf("ListenAndServe failed, %v", err) 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /public/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | Relay • TodoMVC 7 | 8 | 9 | 10 | 11 |
12 | 17 | 18 | 19 | 20 | 21 | -------------------------------------------------------------------------------- /js/components/App.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import Relay from 'react-relay'; 3 | 4 | class App extends React.Component { 5 | render() { 6 | return ( 7 |
8 |

Widget list

9 | 14 |
15 | ); 16 | } 17 | } 18 | 19 | export default Relay.createContainer(App, { 20 | fragments: { 21 | viewer: () => Relay.QL` 22 | fragment on User { 23 | widgets(first: 10) { 24 | edges { 25 | node { 26 | id, 27 | name, 28 | }, 29 | }, 30 | }, 31 | } 32 | `, 33 | }, 34 | }); 35 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "todomvc-relay-go", 3 | "private": true, 4 | "description": "React/Relay TodoMVC app, driven by a Golang GraphQL backend", 5 | "repository": "github.com/sogko/todomvc-relay-go", 6 | "version": "0.1.0", 7 | "scripts": { 8 | "start": "go run graphql.go & babel-node ./server.js", 9 | "update-schema": "cd ./scripts && go run updateSchema.go" 10 | }, 11 | "dependencies": { 12 | "babel": "5.8.21", 13 | "babel-loader": "5.3.2", 14 | "babel-relay-plugin": "0.2.3", 15 | "classnames": "^2.1.3", 16 | "express": "^4.13.1", 17 | "history": "^1.11.1", 18 | "react": "^0.14.0-rc", 19 | "react-dom": "^0.14.0-rc", 20 | "react-relay": "^0.3.1", 21 | "react-router": "^1.0.0-rc1", 22 | "react-router-relay": "^0.6.1", 23 | "todomvc-common": "^1.0.2", 24 | "webpack": "^1.10.5", 25 | "webpack-dev-server": "^1.10.1" 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /server.js: -------------------------------------------------------------------------------- 1 | import express from 'express'; 2 | import path from 'path'; 3 | import webpack from 'webpack'; 4 | import WebpackDevServer from 'webpack-dev-server'; 5 | 6 | const APP_PORT = 3000; 7 | const GRAPHQL_PORT = 8080; 8 | 9 | // Serve the Relay app 10 | var compiler = webpack({ 11 | entry: path.resolve(__dirname, 'js', 'app.js'), 12 | module: { 13 | loaders: [ 14 | { 15 | exclude: /node_modules/, 16 | loader: 'babel', 17 | query: {stage: 0, plugins: ['./build/babelRelayPlugin']}, 18 | test: /\.js$/ 19 | } 20 | ] 21 | }, 22 | output: {filename: 'app.js', path: '/'} 23 | }); 24 | var app = new WebpackDevServer(compiler, { 25 | contentBase: '/public/', 26 | proxy: {'/graphql': `http://localhost:${GRAPHQL_PORT}`}, 27 | publicPath: '/js/', 28 | stats: {colors: true} 29 | }); 30 | // Serve static resources 31 | app.use('/', express.static(path.resolve(__dirname, 'public'))); 32 | app.listen(APP_PORT, () => { 33 | console.log(`App is now running on http://localhost:${APP_PORT}`); 34 | }); 35 | -------------------------------------------------------------------------------- /scripts/updateSchema.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "encoding/json" 5 | "github.com/graphql-go/graphql" 6 | "github.com/graphql-go/graphql/testutil" 7 | "github.com/sogko/todomvc-relay-go/data" 8 | "io/ioutil" 9 | "log" 10 | "os" 11 | ) 12 | 13 | func main() { 14 | // Save JSON of full schema introspection for Babel Relay Plugin to use 15 | result := graphql.Graphql(graphql.Params{ 16 | Schema: data.Schema, 17 | RequestString: testutil.IntrospectionQuery, 18 | }) 19 | if result.HasErrors() { 20 | log.Fatalf("ERROR introspecting schema: %v", result.Errors) 21 | return 22 | } else { 23 | b, err := json.MarshalIndent(result, "", " ") 24 | if err != nil { 25 | log.Fatalf("ERROR: %v", err) 26 | } 27 | err = ioutil.WriteFile("../data/schema.json", b, os.ModePerm) 28 | if err != nil { 29 | log.Fatalf("ERROR: %v", err) 30 | } 31 | 32 | } 33 | // TODO: Save user readable type system shorthand of schema 34 | // pending implementation of printSchema 35 | /* 36 | fs.writeFileSync( 37 | path.join(__dirname, '../data/schema.graphql'), 38 | printSchema(Schema) 39 | ); 40 | */ 41 | } 42 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2015 Hafiz Ismail 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | 23 | -------------------------------------------------------------------------------- /js/mutations/RenameTodoMutation.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2013-2015, Facebook, Inc. 3 | * All rights reserved. 4 | * 5 | * This source code is licensed under the BSD-style license found in the 6 | * LICENSE file in the root directory of this source tree. An additional grant 7 | * of patent rights can be found in the PATENTS file in the same directory. 8 | */ 9 | 10 | import Relay from 'react-relay'; 11 | 12 | export default class RenameTodoMutation extends Relay.Mutation { 13 | static fragments = { 14 | todo: () => Relay.QL` 15 | fragment on Todo { 16 | id, 17 | } 18 | `, 19 | }; 20 | getMutation() { 21 | return Relay.QL`mutation{renameTodo}`; 22 | } 23 | getFatQuery() { 24 | return Relay.QL` 25 | fragment on RenameTodoPayload { 26 | todo { 27 | text, 28 | } 29 | } 30 | `; 31 | } 32 | getConfigs() { 33 | return [{ 34 | type: 'FIELDS_CHANGE', 35 | fieldIDs: { 36 | todo: this.props.todo.id, 37 | }, 38 | }]; 39 | } 40 | getVariables() { 41 | return { 42 | id: this.props.todo.id, 43 | text: this.props.text, 44 | }; 45 | } 46 | getOptimisticResponse() { 47 | return { 48 | todo: { 49 | id: this.props.todo.id, 50 | text: this.props.text, 51 | }, 52 | }; 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /js/app.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2013-2015, Facebook, Inc. 3 | * All rights reserved. 4 | * 5 | * This source code is licensed under the BSD-style license found in the 6 | * LICENSE file in the root directory of this source tree. An additional grant 7 | * of patent rights can be found in the PATENTS file in the same directory. 8 | */ 9 | 10 | import 'babel/polyfill'; 11 | import 'todomvc-common'; 12 | import createHashHistory from 'history/lib/createHashHistory'; 13 | import {IndexRoute, Route, Router} from 'react-router'; 14 | import React from 'react'; 15 | import ReactDOM from 'react-dom'; 16 | import ReactRouterRelay from 'react-router-relay'; 17 | import TodoApp from './components/TodoApp'; 18 | import TodoList from './components/TodoList'; 19 | import ViewerQueries from './queries/ViewerQueries'; 20 | 21 | ReactDOM.render( 22 | 26 | 29 | 34 | 38 | 39 | , 40 | document.getElementById('root') 41 | ); 42 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # todomvc-relay-go 2 | Port of the [React/Relay TodoMVC app](https://github.com/facebook/relay/tree/master/examples/todo), driven by a Golang GraphQL backend 3 | 4 | ## Parts and pieces 5 | - [golang-relay-starter-kit](https://github.com/sogko/golang-relay-starter-kit) 6 | - [graphql-go](https://github.com/chris-ramon/graphql-go) 7 | - [graphql-go-handler](https://github.com/sogko/graphql-go-handler) 8 | - [graphql-relay-go](https://github.com/sogko/graphql-relay-go) 9 | 10 | ### Notes: 11 | This is based on alpha version of `graphql-go` and `graphql-relay-go`. 12 | Be sure to watch both repositories for latest changes. 13 | 14 | ## Installation 15 | 16 | 1. Install dependencies for NodeJS app server 17 | ``` 18 | npm install 19 | ``` 20 | 2. Install dependencies for Golang GraphQL server 21 | ``` 22 | go get -v ./... 23 | ``` 24 | 25 | ## Running 26 | 27 | Start a local server: 28 | 29 | ``` 30 | npm start 31 | ``` 32 | 33 | The above command will run both the NodeJS app server and Golang GraphQL server concurrently. 34 | 35 | - Golang GraphQL server will be running at http://localhost:8080/graphql 36 | - NodeJS app server will be running at http://localhost:3000 37 | 38 | ## Developing 39 | 40 | Any changes you make to files in the `js/` directory will cause the server to 41 | automatically rebuild the app and refresh your browser. 42 | 43 | If at any time you make changes to `data/schema.go`, stop the server, 44 | regenerate `data/schema.json`, and restart the server: 45 | 46 | ``` 47 | npm run update-schema 48 | npm start 49 | ``` 50 | -------------------------------------------------------------------------------- /js/mutations/AddTodoMutation.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2013-2015, Facebook, Inc. 3 | * All rights reserved. 4 | * 5 | * This source code is licensed under the BSD-style license found in the 6 | * LICENSE file in the root directory of this source tree. An additional grant 7 | * of patent rights can be found in the PATENTS file in the same directory. 8 | */ 9 | 10 | import Relay from 'react-relay'; 11 | 12 | export default class AddTodoMutation extends Relay.Mutation { 13 | static fragments = { 14 | viewer: () => Relay.QL` 15 | fragment on User { 16 | id, 17 | totalCount, 18 | } 19 | `, 20 | }; 21 | getMutation() { 22 | return Relay.QL`mutation{addTodo}`; 23 | } 24 | getFatQuery() { 25 | return Relay.QL` 26 | fragment on AddTodoPayload { 27 | todoEdge, 28 | viewer { 29 | todos, 30 | totalCount, 31 | }, 32 | } 33 | `; 34 | } 35 | getConfigs() { 36 | return [{ 37 | type: 'RANGE_ADD', 38 | parentName: 'viewer', 39 | parentID: this.props.viewer.id, 40 | connectionName: 'todos', 41 | edgeName: 'todoEdge', 42 | rangeBehaviors: { 43 | '': 'append', 44 | 'status(any)': 'append', 45 | 'status(active)': 'append', 46 | 'status(completed)': null, 47 | }, 48 | }]; 49 | } 50 | getVariables() { 51 | return { 52 | text: this.props.text, 53 | }; 54 | } 55 | getOptimisticResponse() { 56 | return { 57 | // FIXME: totalCount gets updated optimistically, but this edge does not 58 | // get added until the server responds 59 | todoEdge: { 60 | node: { 61 | complete: false, 62 | text: this.props.text, 63 | }, 64 | }, 65 | viewer: { 66 | id: this.props.viewer.id, 67 | totalCount: this.props.viewer.totalCount + 1, 68 | }, 69 | }; 70 | } 71 | } 72 | -------------------------------------------------------------------------------- /js/mutations/ChangeTodoStatusMutation.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2013-2015, Facebook, Inc. 3 | * All rights reserved. 4 | * 5 | * This source code is licensed under the BSD-style license found in the 6 | * LICENSE file in the root directory of this source tree. An additional grant 7 | * of patent rights can be found in the PATENTS file in the same directory. 8 | */ 9 | 10 | import Relay from 'react-relay'; 11 | 12 | export default class ChangeTodoStatusMutation extends Relay.Mutation { 13 | static fragments = { 14 | todo: () => Relay.QL` 15 | fragment on Todo { 16 | id, 17 | } 18 | `, 19 | // TODO: Mark completedCount optional 20 | viewer: () => Relay.QL` 21 | fragment on User { 22 | id, 23 | completedCount, 24 | } 25 | `, 26 | }; 27 | getMutation() { 28 | return Relay.QL`mutation{changeTodoStatus}`; 29 | } 30 | getFatQuery() { 31 | return Relay.QL` 32 | fragment on ChangeTodoStatusPayload { 33 | todo { 34 | complete, 35 | }, 36 | viewer { 37 | completedCount, 38 | todos, 39 | }, 40 | } 41 | `; 42 | } 43 | getConfigs() { 44 | return [{ 45 | type: 'FIELDS_CHANGE', 46 | fieldIDs: { 47 | todo: this.props.todo.id, 48 | viewer: this.props.viewer.id, 49 | }, 50 | }]; 51 | } 52 | getVariables() { 53 | return { 54 | complete: this.props.complete, 55 | id: this.props.todo.id, 56 | }; 57 | } 58 | getOptimisticResponse() { 59 | var viewerPayload = {id: this.props.viewer.id}; 60 | if (this.props.viewer.completedCount != null) { 61 | viewerPayload.completedCount = this.props.complete ? 62 | this.props.viewer.completedCount + 1 : 63 | this.props.viewer.completedCount - 1; 64 | } 65 | return { 66 | todo: { 67 | complete: this.props.complete, 68 | id: this.props.todo.id, 69 | }, 70 | viewer: viewerPayload, 71 | }; 72 | } 73 | } 74 | -------------------------------------------------------------------------------- /js/mutations/RemoveTodoMutation.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2013-2015, Facebook, Inc. 3 | * All rights reserved. 4 | * 5 | * This source code is licensed under the BSD-style license found in the 6 | * LICENSE file in the root directory of this source tree. An additional grant 7 | * of patent rights can be found in the PATENTS file in the same directory. 8 | */ 9 | 10 | import Relay from 'react-relay'; 11 | 12 | export default class RemoveTodoMutation extends Relay.Mutation { 13 | static fragments = { 14 | // TODO: Mark complete as optional 15 | todo: () => Relay.QL` 16 | fragment on Todo { 17 | complete, 18 | id, 19 | } 20 | `, 21 | // TODO: Mark completedCount and totalCount as optional 22 | viewer: () => Relay.QL` 23 | fragment on User { 24 | completedCount, 25 | id, 26 | totalCount, 27 | } 28 | `, 29 | }; 30 | getMutation() { 31 | return Relay.QL`mutation{removeTodo}`; 32 | } 33 | getFatQuery() { 34 | return Relay.QL` 35 | fragment on RemoveTodoPayload { 36 | deletedTodoId, 37 | viewer { 38 | completedCount, 39 | totalCount, 40 | }, 41 | } 42 | `; 43 | } 44 | getConfigs() { 45 | return [{ 46 | type: 'NODE_DELETE', 47 | parentName: 'viewer', 48 | parentID: this.props.viewer.id, 49 | connectionName: 'todos', 50 | deletedIDFieldName: 'deletedTodoId', 51 | }]; 52 | } 53 | getVariables() { 54 | return { 55 | id: this.props.todo.id, 56 | }; 57 | } 58 | getOptimisticResponse() { 59 | var viewerPayload = {id: this.props.viewer.id}; 60 | if (this.props.viewer.completedCount != null) { 61 | viewerPayload.completedCount = this.props.todo.complete === true ? 62 | this.props.viewer.completedCount - 1 : 63 | this.props.viewer.completedCount; 64 | } 65 | if (this.props.viewer.totalCount != null) { 66 | viewerPayload.totalCount = this.props.viewer.totalCount - 1; 67 | } 68 | return { 69 | deletedTodoId: this.props.todo.id, 70 | viewer: viewerPayload, 71 | }; 72 | } 73 | } 74 | -------------------------------------------------------------------------------- /js/mutations/MarkAllTodosMutation.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2013-2015, Facebook, Inc. 3 | * All rights reserved. 4 | * 5 | * This source code is licensed under the BSD-style license found in the 6 | * LICENSE file in the root directory of this source tree. An additional grant 7 | * of patent rights can be found in the PATENTS file in the same directory. 8 | */ 9 | 10 | import Relay from 'react-relay'; 11 | 12 | export default class MarkAllTodosMutation extends Relay.Mutation { 13 | static fragments = { 14 | // TODO: Mark edges and totalCount optional 15 | todos: () => Relay.QL` 16 | fragment on TodoConnection { 17 | edges { 18 | node { 19 | complete, 20 | id, 21 | }, 22 | }, 23 | } 24 | `, 25 | viewer: () => Relay.QL` 26 | fragment on User { 27 | id, 28 | totalCount, 29 | } 30 | `, 31 | }; 32 | getMutation() { 33 | return Relay.QL`mutation{markAllTodos}`; 34 | } 35 | getFatQuery() { 36 | return Relay.QL` 37 | fragment on MarkAllTodosPayload { 38 | viewer { 39 | completedCount, 40 | todos, 41 | }, 42 | } 43 | `; 44 | } 45 | getConfigs() { 46 | return [{ 47 | type: 'FIELDS_CHANGE', 48 | fieldIDs: { 49 | viewer: this.props.viewer.id, 50 | }, 51 | }]; 52 | } 53 | getVariables() { 54 | return { 55 | complete: this.props.complete, 56 | }; 57 | } 58 | getOptimisticResponse() { 59 | var viewerPayload = {id: this.props.viewer.id}; 60 | if (this.props.todos && this.props.todos.edges) { 61 | viewerPayload.todos = { 62 | edges: this.props.todos.edges 63 | .filter(edge => edge.node.complete !== this.props.complete) 64 | .map(edge => ({ 65 | node: { 66 | complete: this.props.complete, 67 | id: edge.node.id, 68 | }, 69 | })) 70 | }; 71 | } 72 | if (this.props.viewer.totalCount != null) { 73 | viewerPayload.completedCount = this.props.complete ? 74 | this.props.viewer.totalCount : 75 | 0; 76 | } 77 | return { 78 | viewer: viewerPayload, 79 | }; 80 | } 81 | } 82 | -------------------------------------------------------------------------------- /js/mutations/RemoveCompletedTodosMutation.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2013-2015, Facebook, Inc. 3 | * All rights reserved. 4 | * 5 | * This source code is licensed under the BSD-style license found in the 6 | * LICENSE file in the root directory of this source tree. An additional grant 7 | * of patent rights can be found in the PATENTS file in the same directory. 8 | */ 9 | 10 | import Relay from 'react-relay'; 11 | 12 | export default class RemoveCompletedTodosMutation extends Relay.Mutation { 13 | static fragments = { 14 | // TODO: Make completedCount, edges, and totalCount optional 15 | todos: () => Relay.QL` 16 | fragment on TodoConnection { 17 | edges { 18 | node { 19 | complete, 20 | id, 21 | }, 22 | }, 23 | } 24 | `, 25 | viewer: () => Relay.QL` 26 | fragment on User { 27 | completedCount, 28 | id, 29 | totalCount, 30 | } 31 | `, 32 | }; 33 | getMutation() { 34 | return Relay.QL`mutation{removeCompletedTodos}`; 35 | } 36 | getFatQuery() { 37 | return Relay.QL` 38 | fragment on RemoveCompletedTodosPayload { 39 | deletedTodoIds, 40 | viewer { 41 | completedCount, 42 | totalCount, 43 | }, 44 | } 45 | `; 46 | } 47 | getConfigs() { 48 | return [{ 49 | type: 'NODE_DELETE', 50 | parentName: 'viewer', 51 | parentID: this.props.viewer.id, 52 | connectionName: 'todos', 53 | deletedIDFieldName: 'deletedTodoIds', 54 | }]; 55 | } 56 | getVariables() { 57 | return {}; 58 | } 59 | getOptimisticResponse() { 60 | var deletedTodoIds; 61 | var newTotalCount; 62 | if (this.props.todos && this.props.todos.edges) { 63 | deletedTodoIds = this.props.todos.edges 64 | .filter(edge => edge.node.complete) 65 | .map(edge => edge.node.id); 66 | } 67 | var {completedCount, totalCount} = this.props.viewer; 68 | if (completedCount != null && totalCount != null) { 69 | newTotalCount = totalCount - completedCount; 70 | } 71 | return { 72 | deletedTodoIds, 73 | viewer: { 74 | completedCount: 0, 75 | id: this.props.viewer.id, 76 | totalCount: newTotalCount, 77 | }, 78 | }; 79 | } 80 | } 81 | -------------------------------------------------------------------------------- /js/components/TodoTextInput.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2013-2015, Facebook, Inc. 3 | * All rights reserved. 4 | * 5 | * This source code is licensed under the BSD-style license found in the 6 | * LICENSE file in the root directory of this source tree. An additional grant 7 | * of patent rights can be found in the PATENTS file in the same directory. 8 | */ 9 | 10 | import React from 'react'; 11 | import ReactDOM from 'react-dom'; 12 | 13 | var {PropTypes} = React; 14 | 15 | var ENTER_KEY_CODE = 13; 16 | var ESC_KEY_CODE = 27; 17 | 18 | export default class TodoTextInput extends React.Component { 19 | static defaultProps = { 20 | commitOnBlur: false, 21 | } 22 | static propTypes = { 23 | className: PropTypes.string, 24 | commitOnBlur: PropTypes.bool.isRequired, 25 | initialValue: PropTypes.string, 26 | onCancel: PropTypes.func, 27 | onDelete: PropTypes.func, 28 | onSave: PropTypes.func.isRequired, 29 | placeholder: PropTypes.string, 30 | } 31 | state = { 32 | isEditing: false, 33 | text: this.props.initialValue || '', 34 | }; 35 | componentDidMount() { 36 | ReactDOM.findDOMNode(this).focus(); 37 | } 38 | _commitChanges = () => { 39 | var newText = this.state.text.trim(); 40 | if (this.props.onDelete && newText === '') { 41 | this.props.onDelete(); 42 | } else if (this.props.onCancel && newText === this.props.initialValue) { 43 | this.props.onCancel(); 44 | } else if (newText !== '') { 45 | this.props.onSave(newText); 46 | this.setState({text: ''}); 47 | } 48 | } 49 | _handleBlur = () => { 50 | if (this.props.commitOnBlur) { 51 | this._commitChanges(); 52 | } 53 | } 54 | _handleChange = (e) => { 55 | this.setState({text: e.target.value}); 56 | } 57 | _handleKeyDown = (e) => { 58 | if (this.props.onCancel && e.keyCode === ESC_KEY_CODE) { 59 | this.props.onCancel(); 60 | } else if (e.keyCode === ENTER_KEY_CODE) { 61 | this._commitChanges(); 62 | } 63 | } 64 | render() { 65 | return ( 66 | 74 | ); 75 | } 76 | } 77 | -------------------------------------------------------------------------------- /js/components/TodoListFooter.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2013-2015, Facebook, Inc. 3 | * All rights reserved. 4 | * 5 | * This source code is licensed under the BSD-style license found in the 6 | * LICENSE file in the root directory of this source tree. An additional grant 7 | * of patent rights can be found in the PATENTS file in the same directory. 8 | */ 9 | 10 | // IndexLink isn't exported in main package on React Router 1.0.0-rc1. 11 | import IndexLink from 'react-router/lib/IndexLink'; 12 | import Link from 'react-router/lib/Link'; 13 | import RemoveCompletedTodosMutation from '../mutations/RemoveCompletedTodosMutation'; 14 | 15 | import React from 'react'; 16 | import Relay from 'react-relay'; 17 | 18 | class TodoListFooter extends React.Component { 19 | _handleRemoveCompletedTodosClick = () => { 20 | Relay.Store.update( 21 | new RemoveCompletedTodosMutation({ 22 | todos: this.props.viewer.todos, 23 | viewer: this.props.viewer, 24 | }) 25 | ); 26 | } 27 | render() { 28 | var numTodos = this.props.viewer.totalCount; 29 | var numCompletedTodos = this.props.viewer.completedCount; 30 | return ( 31 | 54 | ); 55 | } 56 | } 57 | 58 | export default Relay.createContainer(TodoListFooter, { 59 | prepareVariables() { 60 | return { 61 | limit: Number.MAX_SAFE_INTEGER || 9007199254740991, 62 | }; 63 | }, 64 | 65 | fragments: { 66 | viewer: () => Relay.QL` 67 | fragment on User { 68 | completedCount, 69 | todos(status: "completed", first: $limit) { 70 | ${RemoveCompletedTodosMutation.getFragment('todos')}, 71 | }, 72 | totalCount, 73 | ${RemoveCompletedTodosMutation.getFragment('viewer')}, 74 | } 75 | `, 76 | }, 77 | }); 78 | -------------------------------------------------------------------------------- /public/base.css: -------------------------------------------------------------------------------- 1 | hr { 2 | margin: 20px 0; 3 | border: 0; 4 | border-top: 1px dashed #c5c5c5; 5 | border-bottom: 1px dashed #f7f7f7; 6 | } 7 | 8 | .learn a { 9 | font-weight: normal; 10 | text-decoration: none; 11 | color: #b83f45; 12 | } 13 | 14 | .learn a:hover { 15 | text-decoration: underline; 16 | color: #787e7e; 17 | } 18 | 19 | .learn h3, 20 | .learn h4, 21 | .learn h5 { 22 | margin: 10px 0; 23 | font-weight: 500; 24 | line-height: 1.2; 25 | color: #000; 26 | } 27 | 28 | .learn h3 { 29 | font-size: 24px; 30 | } 31 | 32 | .learn h4 { 33 | font-size: 18px; 34 | } 35 | 36 | .learn h5 { 37 | margin-bottom: 0; 38 | font-size: 14px; 39 | } 40 | 41 | .learn ul { 42 | padding: 0; 43 | margin: 0 0 30px 25px; 44 | } 45 | 46 | .learn li { 47 | line-height: 20px; 48 | } 49 | 50 | .learn p { 51 | font-size: 15px; 52 | font-weight: 300; 53 | line-height: 1.3; 54 | margin-top: 0; 55 | margin-bottom: 0; 56 | } 57 | 58 | #issue-count { 59 | display: none; 60 | } 61 | 62 | .quote { 63 | border: none; 64 | margin: 20px 0 60px 0; 65 | } 66 | 67 | .quote p { 68 | font-style: italic; 69 | } 70 | 71 | .quote p:before { 72 | content: '“'; 73 | font-size: 50px; 74 | opacity: .15; 75 | position: absolute; 76 | top: -20px; 77 | left: 3px; 78 | } 79 | 80 | .quote p:after { 81 | content: '”'; 82 | font-size: 50px; 83 | opacity: .15; 84 | position: absolute; 85 | bottom: -42px; 86 | right: 3px; 87 | } 88 | 89 | .quote footer { 90 | position: absolute; 91 | bottom: -40px; 92 | right: 0; 93 | } 94 | 95 | .quote footer img { 96 | border-radius: 3px; 97 | } 98 | 99 | .quote footer a { 100 | margin-left: 5px; 101 | vertical-align: middle; 102 | } 103 | 104 | .speech-bubble { 105 | position: relative; 106 | padding: 10px; 107 | background: rgba(0, 0, 0, .04); 108 | border-radius: 5px; 109 | } 110 | 111 | .speech-bubble:after { 112 | content: ''; 113 | position: absolute; 114 | top: 100%; 115 | right: 30px; 116 | border: 13px solid transparent; 117 | border-top-color: rgba(0, 0, 0, .04); 118 | } 119 | 120 | .learn-bar > .learn { 121 | position: absolute; 122 | width: 272px; 123 | top: 8px; 124 | left: -300px; 125 | padding: 10px; 126 | border-radius: 5px; 127 | background-color: rgba(255, 255, 255, .6); 128 | transition-property: left; 129 | transition-duration: 500ms; 130 | } 131 | 132 | @media (min-width: 899px) { 133 | .learn-bar { 134 | width: auto; 135 | padding-left: 300px; 136 | } 137 | 138 | .learn-bar > .learn { 139 | left: 8px; 140 | } 141 | } 142 | -------------------------------------------------------------------------------- /js/components/TodoApp.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2013-2015, Facebook, Inc. 3 | * All rights reserved. 4 | * 5 | * This source code is licensed under the BSD-style license found in the 6 | * LICENSE file in the root directory of this source tree. An additional grant 7 | * of patent rights can be found in the PATENTS file in the same directory. 8 | */ 9 | 10 | import AddTodoMutation from '../mutations/AddTodoMutation'; 11 | import TodoListFooter from './TodoListFooter'; 12 | import TodoTextInput from './TodoTextInput'; 13 | 14 | import React from 'react'; 15 | import Relay from 'react-relay'; 16 | 17 | class TodoApp extends React.Component { 18 | _handleTextInputSave = (text) => { 19 | Relay.Store.update( 20 | new AddTodoMutation({text, viewer: this.props.viewer}) 21 | ); 22 | } 23 | render() { 24 | var hasTodos = this.props.viewer.totalCount > 0; 25 | return ( 26 |
27 |
28 |
29 |

30 | todos 31 |

32 | 38 |
39 | 40 | {this.props.children} 41 | 42 | {hasTodos && 43 | 47 | } 48 |
49 | 67 |
68 | ); 69 | } 70 | } 71 | 72 | export default Relay.createContainer(TodoApp, { 73 | fragments: { 74 | viewer: () => Relay.QL` 75 | fragment on User { 76 | totalCount, 77 | ${AddTodoMutation.getFragment('viewer')}, 78 | ${TodoListFooter.getFragment('viewer')}, 79 | } 80 | `, 81 | }, 82 | }); 83 | -------------------------------------------------------------------------------- /js/components/TodoList.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2013-2015, Facebook, Inc. 3 | * All rights reserved. 4 | * 5 | * This source code is licensed under the BSD-style license found in the 6 | * LICENSE file in the root directory of this source tree. An additional grant 7 | * of patent rights can be found in the PATENTS file in the same directory. 8 | */ 9 | 10 | import MarkAllTodosMutation from '../mutations/MarkAllTodosMutation'; 11 | import Todo from './Todo'; 12 | 13 | import React from 'react'; 14 | import Relay from 'react-relay'; 15 | 16 | class TodoList extends React.Component { 17 | _handleMarkAllChange = (e) => { 18 | var complete = e.target.checked; 19 | Relay.Store.update( 20 | new MarkAllTodosMutation({ 21 | complete, 22 | todos: this.props.viewer.todos, 23 | viewer: this.props.viewer, 24 | }) 25 | ); 26 | } 27 | renderTodos() { 28 | return this.props.viewer.todos.edges.map(edge => 29 | 34 | ); 35 | } 36 | render() { 37 | var numTodos = this.props.viewer.totalCount; 38 | var numCompletedTodos = this.props.viewer.completedCount; 39 | return ( 40 |
41 | 47 | 50 | 53 |
54 | ); 55 | } 56 | } 57 | 58 | export default Relay.createContainer(TodoList, { 59 | initialVariables: { 60 | status: null, 61 | }, 62 | 63 | prepareVariables({status}) { 64 | var nextStatus; 65 | if (status === 'active' || status === 'completed') { 66 | nextStatus = status; 67 | } else { 68 | // This matches the Backbone example, which displays all todos on an 69 | // invalid route. 70 | nextStatus = 'any'; 71 | } 72 | return { 73 | status: nextStatus, 74 | limit: Number.MAX_SAFE_INTEGER || 9007199254740991, 75 | }; 76 | }, 77 | 78 | fragments: { 79 | viewer: () => Relay.QL` 80 | fragment on User { 81 | completedCount, 82 | todos(status: $status, first: $limit) { 83 | edges { 84 | node { 85 | id, 86 | ${Todo.getFragment('todo')}, 87 | }, 88 | }, 89 | ${MarkAllTodosMutation.getFragment('todos')}, 90 | }, 91 | totalCount, 92 | ${MarkAllTodosMutation.getFragment('viewer')}, 93 | ${Todo.getFragment('viewer')}, 94 | } 95 | `, 96 | }, 97 | }); 98 | -------------------------------------------------------------------------------- /public/learn.json: -------------------------------------------------------------------------------- 1 | { 2 | "relay": { 3 | "name": "todomvc-relay-go", 4 | "description": "An example implementation of TodoMVC with React + Relay, powered by Golang GraphQL backend", 5 | "homepage": "sogko.github.io/todomvc-relay-go/", 6 | "examples": [{ 7 | "name": "relay + graphql-go + graphql-relay-go", 8 | "url": "", 9 | "source_url": "https://github.com/sogko/todomvc-relay-go", 10 | "type": "backend" 11 | }], 12 | "link_groups": [{ 13 | "heading": "Official Resources", 14 | "links": [{ 15 | "name": "Documentation", 16 | "url": "https://facebook.github.io/relay/docs/getting-started.html" 17 | }, { 18 | "name": "API Reference", 19 | "url": "https://facebook.github.io/relay/docs/api-reference-relay.html" 20 | }, { 21 | "name": "Relay on GitHub", 22 | "url": "https://github.com/facebook/relay" 23 | }] 24 | }, { 25 | "heading": "Golang + Relay Starter Kit", 26 | "links": [{ 27 | "name": "golang-relay-starter-kit", 28 | "url": "https://github.com/sogko/golang-relay-starter-kit" 29 | }] 30 | }, { 31 | "heading": "Golang GraphQL libraries", 32 | "links": [{ 33 | "name": "graphql-go", 34 | "url": "https://github.com/chris-ramon/graphql-go" 35 | }, { 36 | "name": "graphql-go-handler", 37 | "url": "https://github.com/sogko/graphql-go-handler" 38 | }, { 39 | "name": "graphql-relay-go", 40 | "url": "https://github.com/sogko/graphql-relay-go" 41 | }] 42 | }, { 43 | "heading": "Community", 44 | "links": [{ 45 | "name": "Relay on StackOverflow", 46 | "url": "https://stackoverflow.com/questions/tagged/relayjs" 47 | }] 48 | }] 49 | }, 50 | "templates": { 51 | "todomvc": "

<%= name %>

<% if (typeof examples !== 'undefined') { %> <% examples.forEach(function (example) { %>
<%= example.name %>
<% if (!location.href.match(example.url + '/')) { %> \" href=\"<%= example.url %>\">Demo, <% } if (example.type === 'backend') { %>\"><% } else { %>\"><% } %>Source <% }); %> <% } %>

<%= description %>

<% if (typeof link_groups !== 'undefined') { %>
<% link_groups.forEach(function (link_group) { %>

<%= link_group.heading %>

<% }); %> <% } %> " 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /data/database.go: -------------------------------------------------------------------------------- 1 | package data 2 | 3 | import "fmt" 4 | 5 | // Mock authenticated ID 6 | const ViewerId = "me" 7 | 8 | // Model structs 9 | type Todo struct { 10 | ID string `json:"id"` 11 | Text string `json:"text"` 12 | Complete bool `json:"complete"` 13 | } 14 | 15 | type User struct { 16 | ID string `json:"id"` 17 | } 18 | 19 | // Mock data 20 | var viewer = &User{ViewerId} 21 | var usersById = map[string]*User{ 22 | ViewerId: viewer, 23 | } 24 | var todosById = map[string]*Todo{} 25 | var todoIdsByUser = map[string][]string{ 26 | ViewerId: []string{}, 27 | } 28 | var nextTodoId = 0 29 | 30 | // Data methods 31 | 32 | func AddTodo(text string, complete bool) string { 33 | todo := &Todo{ 34 | ID: fmt.Sprintf("%v", nextTodoId), 35 | Text: text, 36 | Complete: complete, 37 | } 38 | nextTodoId = nextTodoId + 1 39 | 40 | todosById[todo.ID] = todo 41 | todoIdsByUser[ViewerId] = append(todoIdsByUser[ViewerId], todo.ID) 42 | 43 | return todo.ID 44 | } 45 | 46 | func GetTodo(id string) *Todo { 47 | if todo, ok := todosById[id]; ok { 48 | return todo 49 | } 50 | return nil 51 | } 52 | 53 | func GetTodos(status string) []*Todo { 54 | todos := []*Todo{} 55 | for _, todoId := range todoIdsByUser[ViewerId] { 56 | if todo := GetTodo(todoId); todo != nil { 57 | 58 | switch status { 59 | case "completed": 60 | if todo.Complete { 61 | todos = append(todos, todo) 62 | } 63 | case "incomplete": 64 | if !todo.Complete { 65 | todos = append(todos, todo) 66 | } 67 | case "any": 68 | fallthrough 69 | default: 70 | todos = append(todos, todo) 71 | } 72 | } 73 | } 74 | return todos 75 | } 76 | 77 | func GetUser(id string) *User { 78 | if user, ok := usersById[id]; ok { 79 | return user 80 | } 81 | return nil 82 | } 83 | 84 | func GetViewer() *User { 85 | return GetUser(ViewerId) 86 | } 87 | 88 | func ChangeTodoStatus(id string, complete bool) { 89 | todo := GetTodo(id) 90 | if todo == nil { 91 | return 92 | } 93 | todo.Complete = complete 94 | } 95 | 96 | func MarkAllTodos(complete bool) []string { 97 | changedTodoIds := []string{} 98 | for _, todo := range GetTodos("any") { 99 | if todo.Complete != complete { 100 | todo.Complete = complete 101 | changedTodoIds = append(changedTodoIds, todo.ID) 102 | } 103 | } 104 | return changedTodoIds 105 | } 106 | 107 | func RemoveTodo(id string) { 108 | 109 | updatedTodoIdsForUser := []string{} 110 | for _, todoId := range todoIdsByUser[ViewerId] { 111 | if todoId != id { 112 | updatedTodoIdsForUser = append(updatedTodoIdsForUser, todoId) 113 | } 114 | } 115 | todoIdsByUser[ViewerId] = updatedTodoIdsForUser 116 | delete(todosById, id) 117 | 118 | } 119 | 120 | func RemoveCompletedTodos() []string { 121 | todosIdRemoved := []string{} 122 | for _, completedTodo := range GetTodos("completed") { 123 | RemoveTodo(completedTodo.ID) 124 | todosIdRemoved = append(todosIdRemoved, completedTodo.ID) 125 | } 126 | return todosIdRemoved 127 | } 128 | 129 | func RenameTodo(id string, text string) { 130 | todo := GetTodo(id) 131 | if todo != nil { 132 | todo.Text = text 133 | } 134 | } 135 | 136 | func TodosToSliceInterface(todos []*Todo) []interface{} { 137 | todosIFace := []interface{}{} 138 | for _, todo := range todos { 139 | todosIFace = append(todosIFace, todo) 140 | } 141 | return todosIFace 142 | } 143 | -------------------------------------------------------------------------------- /js/components/Todo.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2013-2015, Facebook, Inc. 3 | * All rights reserved. 4 | * 5 | * This source code is licensed under the BSD-style license found in the 6 | * LICENSE file in the root directory of this source tree. An additional grant 7 | * of patent rights can be found in the PATENTS file in the same directory. 8 | */ 9 | 10 | import ChangeTodoStatusMutation from '../mutations/ChangeTodoStatusMutation'; 11 | import RemoveTodoMutation from '../mutations/RemoveTodoMutation'; 12 | import RenameTodoMutation from '../mutations/RenameTodoMutation'; 13 | import TodoTextInput from './TodoTextInput'; 14 | 15 | import React from 'react'; 16 | import Relay from 'react-relay'; 17 | import classnames from 'classnames'; 18 | 19 | class Todo extends React.Component { 20 | state = { 21 | isEditing: false, 22 | }; 23 | _handleCompleteChange = (e) => { 24 | var complete = e.target.checked; 25 | Relay.Store.update( 26 | new ChangeTodoStatusMutation({ 27 | complete, 28 | todo: this.props.todo, 29 | viewer: this.props.viewer, 30 | }) 31 | ); 32 | } 33 | _handleDestroyClick = () => { 34 | this._removeTodo(); 35 | } 36 | _handleLabelDoubleClick = () => { 37 | this._setEditMode(true); 38 | } 39 | _handleTextInputCancel = () => { 40 | this._setEditMode(false); 41 | } 42 | _handleTextInputDelete = () => { 43 | this._setEditMode(false); 44 | this._removeTodo(); 45 | } 46 | _handleTextInputSave = (text) => { 47 | this._setEditMode(false); 48 | Relay.Store.update( 49 | new RenameTodoMutation({todo: this.props.todo, text}) 50 | ); 51 | } 52 | _removeTodo() { 53 | Relay.Store.update( 54 | new RemoveTodoMutation({todo: this.props.todo, viewer: this.props.viewer}) 55 | ); 56 | } 57 | _setEditMode = (shouldEdit) => { 58 | this.setState({isEditing: shouldEdit}); 59 | } 60 | renderTextInput() { 61 | return ( 62 | 70 | ); 71 | } 72 | render() { 73 | return ( 74 |
  • 79 |
    80 | 86 | 89 |
    94 | {this.state.isEditing && this.renderTextInput()} 95 |
  • 96 | ); 97 | } 98 | } 99 | 100 | export default Relay.createContainer(Todo, { 101 | fragments: { 102 | todo: () => Relay.QL` 103 | fragment on Todo { 104 | complete, 105 | id, 106 | text, 107 | ${ChangeTodoStatusMutation.getFragment('todo')}, 108 | ${RemoveTodoMutation.getFragment('todo')}, 109 | ${RenameTodoMutation.getFragment('todo')}, 110 | } 111 | `, 112 | viewer: () => Relay.QL` 113 | fragment on User { 114 | ${ChangeTodoStatusMutation.getFragment('viewer')}, 115 | ${RemoveTodoMutation.getFragment('viewer')}, 116 | } 117 | `, 118 | }, 119 | }); 120 | -------------------------------------------------------------------------------- /public/index.css: -------------------------------------------------------------------------------- 1 | html, 2 | body { 3 | margin: 0; 4 | padding: 0; 5 | } 6 | 7 | button { 8 | margin: 0; 9 | padding: 0; 10 | border: 0; 11 | background: none; 12 | font-size: 100%; 13 | vertical-align: baseline; 14 | font-family: inherit; 15 | font-weight: inherit; 16 | color: inherit; 17 | -webkit-appearance: none; 18 | appearance: none; 19 | -webkit-font-smoothing: antialiased; 20 | -moz-font-smoothing: antialiased; 21 | font-smoothing: antialiased; 22 | } 23 | 24 | body { 25 | font: 14px 'Helvetica Neue', Helvetica, Arial, sans-serif; 26 | line-height: 1.4em; 27 | background: #f5f5f5; 28 | color: #4d4d4d; 29 | min-width: 230px; 30 | max-width: 550px; 31 | margin: 0 auto; 32 | -webkit-font-smoothing: antialiased; 33 | -moz-font-smoothing: antialiased; 34 | font-smoothing: antialiased; 35 | font-weight: 300; 36 | } 37 | 38 | button, 39 | input[type="checkbox"] { 40 | outline: none; 41 | } 42 | 43 | .hidden { 44 | display: none; 45 | } 46 | 47 | .todoapp { 48 | background: #fff; 49 | margin: 130px 0 40px 0; 50 | position: relative; 51 | box-shadow: 0 2px 4px 0 rgba(0, 0, 0, 0.2), 52 | 0 25px 50px 0 rgba(0, 0, 0, 0.1); 53 | } 54 | 55 | .todoapp input::-webkit-input-placeholder { 56 | font-style: italic; 57 | font-weight: 300; 58 | color: #e6e6e6; 59 | } 60 | 61 | .todoapp input::-moz-placeholder { 62 | font-style: italic; 63 | font-weight: 300; 64 | color: #e6e6e6; 65 | } 66 | 67 | .todoapp input::input-placeholder { 68 | font-style: italic; 69 | font-weight: 300; 70 | color: #e6e6e6; 71 | } 72 | 73 | .todoapp h1 { 74 | position: absolute; 75 | top: -155px; 76 | width: 100%; 77 | font-size: 100px; 78 | font-weight: 100; 79 | text-align: center; 80 | color: rgba(175, 47, 47, 0.15); 81 | -webkit-text-rendering: optimizeLegibility; 82 | -moz-text-rendering: optimizeLegibility; 83 | text-rendering: optimizeLegibility; 84 | } 85 | 86 | .new-todo, 87 | .edit { 88 | position: relative; 89 | margin: 0; 90 | width: 100%; 91 | font-size: 24px; 92 | font-family: inherit; 93 | font-weight: inherit; 94 | line-height: 1.4em; 95 | border: 0; 96 | outline: none; 97 | color: inherit; 98 | padding: 6px; 99 | border: 1px solid #999; 100 | box-shadow: inset 0 -1px 5px 0 rgba(0, 0, 0, 0.2); 101 | box-sizing: border-box; 102 | -webkit-font-smoothing: antialiased; 103 | -moz-font-smoothing: antialiased; 104 | font-smoothing: antialiased; 105 | } 106 | 107 | .new-todo { 108 | padding: 16px 16px 16px 60px; 109 | border: none; 110 | background: rgba(0, 0, 0, 0.003); 111 | box-shadow: inset 0 -2px 1px rgba(0,0,0,0.03); 112 | } 113 | 114 | .main { 115 | position: relative; 116 | z-index: 2; 117 | border-top: 1px solid #e6e6e6; 118 | } 119 | 120 | label[for='toggle-all'] { 121 | display: none; 122 | } 123 | 124 | .toggle-all { 125 | position: absolute; 126 | top: -55px; 127 | left: -12px; 128 | width: 60px; 129 | height: 34px; 130 | text-align: center; 131 | border: none; /* Mobile Safari */ 132 | } 133 | 134 | .toggle-all:before { 135 | content: '❯'; 136 | font-size: 22px; 137 | color: #e6e6e6; 138 | padding: 10px 27px 10px 27px; 139 | } 140 | 141 | .toggle-all:checked:before { 142 | color: #737373; 143 | } 144 | 145 | .todo-list { 146 | margin: 0; 147 | padding: 0; 148 | list-style: none; 149 | } 150 | 151 | .todo-list li { 152 | position: relative; 153 | font-size: 24px; 154 | border-bottom: 1px solid #ededed; 155 | } 156 | 157 | .todo-list li:last-child { 158 | border-bottom: none; 159 | } 160 | 161 | .todo-list li.editing { 162 | border-bottom: none; 163 | padding: 0; 164 | } 165 | 166 | .todo-list li.editing .edit { 167 | display: block; 168 | width: 506px; 169 | padding: 13px 17px 12px 17px; 170 | margin: 0 0 0 43px; 171 | } 172 | 173 | .todo-list li.editing .view { 174 | display: none; 175 | } 176 | 177 | .todo-list li .toggle { 178 | text-align: center; 179 | width: 40px; 180 | /* auto, since non-WebKit browsers doesn't support input styling */ 181 | height: auto; 182 | position: absolute; 183 | top: 0; 184 | bottom: 0; 185 | margin: auto 0; 186 | border: none; /* Mobile Safari */ 187 | -webkit-appearance: none; 188 | appearance: none; 189 | } 190 | 191 | .todo-list li .toggle:after { 192 | content: url('data:image/svg+xml;utf8,'); 193 | } 194 | 195 | .todo-list li .toggle:checked:after { 196 | content: url('data:image/svg+xml;utf8,'); 197 | } 198 | 199 | .todo-list li label { 200 | white-space: pre; 201 | word-break: break-word; 202 | padding: 15px 60px 15px 15px; 203 | margin-left: 45px; 204 | display: block; 205 | line-height: 1.2; 206 | transition: color 0.4s; 207 | } 208 | 209 | .todo-list li.completed label { 210 | color: #d9d9d9; 211 | text-decoration: line-through; 212 | } 213 | 214 | .todo-list li .destroy { 215 | display: none; 216 | position: absolute; 217 | top: 0; 218 | right: 10px; 219 | bottom: 0; 220 | width: 40px; 221 | height: 40px; 222 | margin: auto 0; 223 | font-size: 30px; 224 | color: #cc9a9a; 225 | margin-bottom: 11px; 226 | transition: color 0.2s ease-out; 227 | } 228 | 229 | .todo-list li .destroy:hover { 230 | color: #af5b5e; 231 | } 232 | 233 | .todo-list li .destroy:after { 234 | content: '×'; 235 | } 236 | 237 | .todo-list li:hover .destroy { 238 | display: block; 239 | } 240 | 241 | .todo-list li .edit { 242 | display: none; 243 | } 244 | 245 | .todo-list li.editing:last-child { 246 | margin-bottom: -1px; 247 | } 248 | 249 | .footer { 250 | color: #777; 251 | padding: 10px 15px; 252 | height: 20px; 253 | text-align: center; 254 | border-top: 1px solid #e6e6e6; 255 | } 256 | 257 | .footer:before { 258 | content: ''; 259 | position: absolute; 260 | right: 0; 261 | bottom: 0; 262 | left: 0; 263 | height: 50px; 264 | overflow: hidden; 265 | box-shadow: 0 1px 1px rgba(0, 0, 0, 0.2), 266 | 0 8px 0 -3px #f6f6f6, 267 | 0 9px 1px -3px rgba(0, 0, 0, 0.2), 268 | 0 16px 0 -6px #f6f6f6, 269 | 0 17px 2px -6px rgba(0, 0, 0, 0.2); 270 | } 271 | 272 | .todo-count { 273 | float: left; 274 | text-align: left; 275 | } 276 | 277 | .todo-count strong { 278 | font-weight: 300; 279 | } 280 | 281 | .filters { 282 | margin: 0; 283 | padding: 0; 284 | list-style: none; 285 | position: absolute; 286 | right: 0; 287 | left: 0; 288 | } 289 | 290 | .filters li { 291 | display: inline; 292 | } 293 | 294 | .filters li a { 295 | color: inherit; 296 | margin: 3px; 297 | padding: 3px 7px; 298 | text-decoration: none; 299 | border: 1px solid transparent; 300 | border-radius: 3px; 301 | } 302 | 303 | .filters li a.selected, 304 | .filters li a:hover { 305 | border-color: rgba(175, 47, 47, 0.1); 306 | } 307 | 308 | .filters li a.selected { 309 | border-color: rgba(175, 47, 47, 0.2); 310 | } 311 | 312 | .clear-completed, 313 | html .clear-completed:active { 314 | float: right; 315 | position: relative; 316 | line-height: 20px; 317 | text-decoration: none; 318 | cursor: pointer; 319 | position: relative; 320 | } 321 | 322 | .clear-completed:hover { 323 | text-decoration: underline; 324 | } 325 | 326 | .info { 327 | margin: 65px auto 0; 328 | color: #bfbfbf; 329 | font-size: 10px; 330 | text-shadow: 0 1px 0 rgba(255, 255, 255, 0.5); 331 | text-align: center; 332 | } 333 | 334 | .info p { 335 | line-height: 1; 336 | } 337 | 338 | .info a { 339 | color: inherit; 340 | text-decoration: none; 341 | font-weight: 400; 342 | } 343 | 344 | .info a:hover { 345 | text-decoration: underline; 346 | } 347 | 348 | /* 349 | Hack to remove background from Mobile Safari. 350 | Can't use it globally since it destroys checkboxes in Firefox 351 | */ 352 | @media screen and (-webkit-min-device-pixel-ratio:0) { 353 | .toggle-all, 354 | .todo-list li .toggle { 355 | background: none; 356 | } 357 | 358 | .todo-list li .toggle { 359 | height: 40px; 360 | } 361 | 362 | .toggle-all { 363 | -webkit-transform: rotate(90deg); 364 | transform: rotate(90deg); 365 | -webkit-appearance: none; 366 | appearance: none; 367 | } 368 | } 369 | 370 | @media (max-width: 430px) { 371 | .footer { 372 | height: 50px; 373 | } 374 | 375 | .filters { 376 | bottom: 10px; 377 | } 378 | } 379 | -------------------------------------------------------------------------------- /data/schema.go: -------------------------------------------------------------------------------- 1 | package data 2 | 3 | import ( 4 | "github.com/graphql-go/graphql" 5 | "github.com/graphql-go/relay" 6 | ) 7 | 8 | var todoType *graphql.Object 9 | var userType *graphql.Object 10 | 11 | var nodeDefinitions *relay.NodeDefinitions 12 | var todosConnection *relay.GraphQLConnectionDefinitions 13 | 14 | var Schema graphql.Schema 15 | 16 | func init() { 17 | 18 | nodeDefinitions = relay.NewNodeDefinitions(relay.NodeDefinitionsConfig{ 19 | IDFetcher: func(id string, info graphql.ResolveInfo) interface{} { 20 | resolvedID := relay.FromGlobalID(id) 21 | if resolvedID.Type == "Todo" { 22 | return GetTodo(resolvedID.ID) 23 | } 24 | if resolvedID.Type == "User" { 25 | return GetUser(resolvedID.ID) 26 | } 27 | return nil 28 | }, 29 | TypeResolve: func(value interface{}, info graphql.ResolveInfo) *graphql.Object { 30 | switch value.(type) { 31 | case *Todo: 32 | return todoType 33 | case *User: 34 | return userType 35 | default: 36 | return userType 37 | } 38 | }, 39 | }) 40 | 41 | todoType = graphql.NewObject(graphql.ObjectConfig{ 42 | Name: "Todo", 43 | Fields: graphql.Fields{ 44 | "id": relay.GlobalIDField("Todo", nil), 45 | "text": &graphql.Field{ 46 | Type: graphql.String, 47 | }, 48 | "complete": &graphql.Field{ 49 | Type: graphql.Boolean, 50 | }, 51 | }, 52 | Interfaces: []*graphql.Interface{nodeDefinitions.NodeInterface}, 53 | }) 54 | 55 | todosConnection = relay.ConnectionDefinitions(relay.ConnectionConfig{ 56 | Name: "Todo", 57 | NodeType: todoType, 58 | }) 59 | 60 | userType = graphql.NewObject(graphql.ObjectConfig{ 61 | Name: "User", 62 | Fields: graphql.Fields{ 63 | "id": relay.GlobalIDField("User", nil), 64 | "todos": &graphql.Field{ 65 | Type: todosConnection.ConnectionType, 66 | Args: relay.NewConnectionArgs(graphql.FieldConfigArgument{ 67 | "status": &graphql.ArgumentConfig{ 68 | Type: graphql.String, 69 | DefaultValue: "any", 70 | }, 71 | }), 72 | Resolve: func(p graphql.ResolveParams) interface{} { 73 | status, _ := p.Args["status"].(string) 74 | args := relay.NewConnectionArguments(p.Args) 75 | todos := TodosToSliceInterface(GetTodos(status)) 76 | return relay.ConnectionFromArray(todos, args) 77 | }, 78 | }, 79 | "totalCount": &graphql.Field{ 80 | Type: graphql.Int, 81 | Resolve: func(p graphql.ResolveParams) interface{} { 82 | return len(GetTodos("any")) 83 | }, 84 | }, 85 | "completedCount": &graphql.Field{ 86 | Type: graphql.Int, 87 | Resolve: func(p graphql.ResolveParams) interface{} { 88 | return len(GetTodos("completed")) 89 | }, 90 | }, 91 | }, 92 | Interfaces: []*graphql.Interface{nodeDefinitions.NodeInterface}, 93 | }) 94 | 95 | rootType := graphql.NewObject(graphql.ObjectConfig{ 96 | Name: "Root", 97 | Fields: graphql.Fields{ 98 | "viewer": &graphql.Field{ 99 | Type: userType, 100 | Resolve: func(p graphql.ResolveParams) interface{} { 101 | return GetViewer() 102 | }, 103 | }, 104 | "node": nodeDefinitions.NodeField, 105 | }, 106 | }) 107 | 108 | addTodoMutation := relay.MutationWithClientMutationID(relay.MutationConfig{ 109 | Name: "AddTodo", 110 | InputFields: graphql.InputObjectConfigFieldMap{ 111 | "text": &graphql.InputObjectFieldConfig{ 112 | Type: graphql.NewNonNull(graphql.String), 113 | }, 114 | }, 115 | OutputFields: graphql.Fields{ 116 | "todoEdge": &graphql.Field{ 117 | Type: todosConnection.EdgeType, 118 | Resolve: func(p graphql.ResolveParams) interface{} { 119 | payload, _ := p.Source.(map[string]interface{}) 120 | todoId, _ := payload["todoId"].(string) 121 | todo := GetTodo(todoId) 122 | return relay.EdgeType{ 123 | Node: todo, 124 | Cursor: relay.CursorForObjectInConnection(TodosToSliceInterface(GetTodos("any")), todo), 125 | } 126 | }, 127 | }, 128 | "viewer": &graphql.Field{ 129 | Type: userType, 130 | Resolve: func(p graphql.ResolveParams) interface{} { 131 | return GetViewer() 132 | }, 133 | }, 134 | }, 135 | MutateAndGetPayload: func(inputMap map[string]interface{}, info graphql.ResolveInfo) map[string]interface{} { 136 | text, _ := inputMap["text"].(string) 137 | todoId := AddTodo(text, false) 138 | return map[string]interface{}{ 139 | "todoId": todoId, 140 | } 141 | }, 142 | }) 143 | 144 | changeTodoStatusMutation := relay.MutationWithClientMutationID(relay.MutationConfig{ 145 | Name: "ChangeTodoStatus", 146 | InputFields: graphql.InputObjectConfigFieldMap{ 147 | "id": &graphql.InputObjectFieldConfig{ 148 | Type: graphql.NewNonNull(graphql.ID), 149 | }, 150 | "complete": &graphql.InputObjectFieldConfig{ 151 | Type: graphql.NewNonNull(graphql.Boolean), 152 | }, 153 | }, 154 | OutputFields: graphql.Fields{ 155 | "todo": &graphql.Field{ 156 | Type: todoType, 157 | Resolve: func(p graphql.ResolveParams) interface{} { 158 | payload, _ := p.Source.(map[string]interface{}) 159 | todoId, _ := payload["todoId"].(string) 160 | todo := GetTodo(todoId) 161 | return todo 162 | }, 163 | }, 164 | "viewer": &graphql.Field{ 165 | Type: userType, 166 | Resolve: func(p graphql.ResolveParams) interface{} { 167 | return GetViewer() 168 | }, 169 | }, 170 | }, 171 | MutateAndGetPayload: func(inputMap map[string]interface{}, info graphql.ResolveInfo) map[string]interface{} { 172 | id, _ := inputMap["id"].(string) 173 | complete, _ := inputMap["complete"].(bool) 174 | resolvedId := relay.FromGlobalID(id) 175 | ChangeTodoStatus(resolvedId.ID, complete) 176 | return map[string]interface{}{ 177 | "todoId": resolvedId.ID, 178 | } 179 | }, 180 | }) 181 | 182 | markAllTodosMutation := relay.MutationWithClientMutationID(relay.MutationConfig{ 183 | Name: "MarkAllTodos", 184 | InputFields: graphql.InputObjectConfigFieldMap{ 185 | "complete": &graphql.InputObjectFieldConfig{ 186 | Type: graphql.NewNonNull(graphql.Boolean), 187 | }, 188 | }, 189 | OutputFields: graphql.Fields{ 190 | "changedTodos": &graphql.Field{ 191 | Type: graphql.NewList(todoType), 192 | Resolve: func(p graphql.ResolveParams) interface{} { 193 | payload, _ := p.Source.(map[string]interface{}) 194 | todoIds, _ := payload["todoIds"].([]string) 195 | todos := []*Todo{} 196 | for _, todoId := range todoIds { 197 | todo := GetTodo(todoId) 198 | if todo != nil { 199 | todos = append(todos, todo) 200 | } 201 | } 202 | return todos 203 | }, 204 | }, 205 | "viewer": &graphql.Field{ 206 | Type: userType, 207 | Resolve: func(p graphql.ResolveParams) interface{} { 208 | return GetViewer() 209 | }, 210 | }, 211 | }, 212 | MutateAndGetPayload: func(inputMap map[string]interface{}, info graphql.ResolveInfo) map[string]interface{} { 213 | complete, _ := inputMap["complete"].(bool) 214 | todoIds := MarkAllTodos(complete) 215 | return map[string]interface{}{ 216 | "todoIds": todoIds, 217 | } 218 | }, 219 | }) 220 | 221 | removeCompletedTodosMutation := relay.MutationWithClientMutationID(relay.MutationConfig{ 222 | Name: "RemoveCompletedTodos", 223 | OutputFields: graphql.Fields{ 224 | "deletedTodoIds": &graphql.Field{ 225 | Type: graphql.NewList(graphql.String), 226 | Resolve: func(p graphql.ResolveParams) interface{} { 227 | payload, _ := p.Source.(map[string]interface{}) 228 | return payload["todoIds"] 229 | }, 230 | }, 231 | "viewer": &graphql.Field{ 232 | Type: userType, 233 | Resolve: func(p graphql.ResolveParams) interface{} { 234 | return GetViewer() 235 | }, 236 | }, 237 | }, 238 | MutateAndGetPayload: func(inputMap map[string]interface{}, info graphql.ResolveInfo) map[string]interface{} { 239 | todoIds := RemoveCompletedTodos() 240 | return map[string]interface{}{ 241 | "todoIds": todoIds, 242 | } 243 | }, 244 | }) 245 | 246 | removeTodoMutation := relay.MutationWithClientMutationID(relay.MutationConfig{ 247 | Name: "RemoveTodo", 248 | InputFields: graphql.InputObjectConfigFieldMap{ 249 | "id": &graphql.InputObjectFieldConfig{ 250 | Type: graphql.NewNonNull(graphql.ID), 251 | }, 252 | }, 253 | OutputFields: graphql.Fields{ 254 | "deletedTodoId": &graphql.Field{ 255 | Type: graphql.ID, 256 | Resolve: func(p graphql.ResolveParams) interface{} { 257 | payload, _ := p.Source.(map[string]interface{}) 258 | return payload["todoId"] 259 | }, 260 | }, 261 | "viewer": &graphql.Field{ 262 | Type: userType, 263 | Resolve: func(p graphql.ResolveParams) interface{} { 264 | return GetViewer() 265 | }, 266 | }, 267 | }, 268 | MutateAndGetPayload: func(inputMap map[string]interface{}, info graphql.ResolveInfo) map[string]interface{} { 269 | id, _ := inputMap["id"].(string) 270 | resolvedId := relay.FromGlobalID(id) 271 | RemoveTodo(resolvedId.ID) 272 | return map[string]interface{}{ 273 | "todoId": resolvedId.ID, 274 | } 275 | }, 276 | }) 277 | renameTodoMutation := relay.MutationWithClientMutationID(relay.MutationConfig{ 278 | Name: "RenameTodo", 279 | InputFields: graphql.InputObjectConfigFieldMap{ 280 | "id": &graphql.InputObjectFieldConfig{ 281 | Type: graphql.NewNonNull(graphql.ID), 282 | }, 283 | "text": &graphql.InputObjectFieldConfig{ 284 | Type: graphql.NewNonNull(graphql.String), 285 | }, 286 | }, 287 | OutputFields: graphql.Fields{ 288 | "todo": &graphql.Field{ 289 | Type: todoType, 290 | Resolve: func(p graphql.ResolveParams) interface{} { 291 | payload, _ := p.Source.(map[string]interface{}) 292 | todoId, _ := payload["todoId"].(string) 293 | return GetTodo(todoId) 294 | }, 295 | }, 296 | "viewer": &graphql.Field{ 297 | Type: userType, 298 | Resolve: func(p graphql.ResolveParams) interface{} { 299 | return GetViewer() 300 | }, 301 | }, 302 | }, 303 | MutateAndGetPayload: func(inputMap map[string]interface{}, info graphql.ResolveInfo) map[string]interface{} { 304 | id, _ := inputMap["id"].(string) 305 | resolvedId := relay.FromGlobalID(id) 306 | text, _ := inputMap["text"].(string) 307 | RenameTodo(resolvedId.ID, text) 308 | return map[string]interface{}{ 309 | "todoId": resolvedId.ID, 310 | } 311 | }, 312 | }) 313 | mutationType := graphql.NewObject(graphql.ObjectConfig{ 314 | Name: "Mutation", 315 | Fields: graphql.Fields{ 316 | "addTodo": addTodoMutation, 317 | "changeTodoStatus": changeTodoStatusMutation, 318 | "markAllTodos": markAllTodosMutation, 319 | "removeCompletedTodos": removeCompletedTodosMutation, 320 | "removeTodo": removeTodoMutation, 321 | "renameTodo": renameTodoMutation, 322 | }, 323 | }) 324 | 325 | var err error 326 | Schema, err = graphql.NewSchema(graphql.SchemaConfig{ 327 | Query: rootType, 328 | Mutation: mutationType, 329 | }) 330 | if err != nil { 331 | panic(err) 332 | } 333 | } 334 | -------------------------------------------------------------------------------- /data/schema.json: -------------------------------------------------------------------------------- 1 | { 2 | "data": { 3 | "__schema": { 4 | "directives": [ 5 | { 6 | "args": [ 7 | { 8 | "defaultValue": null, 9 | "description": "Included when true.", 10 | "name": "if", 11 | "type": { 12 | "kind": "NON_NULL", 13 | "name": null, 14 | "ofType": { 15 | "kind": "SCALAR", 16 | "name": "Boolean", 17 | "ofType": null 18 | } 19 | } 20 | } 21 | ], 22 | "description": "Directs the executor to include this field or fragment only when the `if` argument is true.", 23 | "name": "include", 24 | "onField": true, 25 | "onFragment": true, 26 | "onOperation": false 27 | }, 28 | { 29 | "args": [ 30 | { 31 | "defaultValue": null, 32 | "description": "Skipped when true.", 33 | "name": "if", 34 | "type": { 35 | "kind": "NON_NULL", 36 | "name": null, 37 | "ofType": { 38 | "kind": "SCALAR", 39 | "name": "Boolean", 40 | "ofType": null 41 | } 42 | } 43 | } 44 | ], 45 | "description": "Directs the executor to skip this field or fragment when the `if` argument is true.", 46 | "name": "skip", 47 | "onField": true, 48 | "onFragment": true, 49 | "onOperation": false 50 | } 51 | ], 52 | "mutationType": { 53 | "name": "Mutation" 54 | }, 55 | "queryType": { 56 | "name": "Root" 57 | }, 58 | "types": [ 59 | { 60 | "description": null, 61 | "enumValues": null, 62 | "fields": [ 63 | { 64 | "args": [ 65 | { 66 | "defaultValue": null, 67 | "description": "The ID of an object", 68 | "name": "id", 69 | "type": { 70 | "kind": "NON_NULL", 71 | "name": null, 72 | "ofType": { 73 | "kind": "SCALAR", 74 | "name": "ID", 75 | "ofType": null 76 | } 77 | } 78 | } 79 | ], 80 | "deprecationReason": null, 81 | "description": "Fetches an object given its ID", 82 | "isDeprecated": false, 83 | "name": "node", 84 | "type": { 85 | "kind": "INTERFACE", 86 | "name": "Node", 87 | "ofType": null 88 | } 89 | }, 90 | { 91 | "args": [], 92 | "deprecationReason": null, 93 | "description": null, 94 | "isDeprecated": false, 95 | "name": "viewer", 96 | "type": { 97 | "kind": "OBJECT", 98 | "name": "User", 99 | "ofType": null 100 | } 101 | } 102 | ], 103 | "inputFields": null, 104 | "interfaces": [], 105 | "kind": "OBJECT", 106 | "name": "Root", 107 | "possibleTypes": null 108 | }, 109 | { 110 | "description": null, 111 | "enumValues": null, 112 | "fields": [ 113 | { 114 | "args": [], 115 | "deprecationReason": null, 116 | "description": null, 117 | "isDeprecated": false, 118 | "name": "totalCount", 119 | "type": { 120 | "kind": "SCALAR", 121 | "name": "Int", 122 | "ofType": null 123 | } 124 | }, 125 | { 126 | "args": [], 127 | "deprecationReason": null, 128 | "description": null, 129 | "isDeprecated": false, 130 | "name": "completedCount", 131 | "type": { 132 | "kind": "SCALAR", 133 | "name": "Int", 134 | "ofType": null 135 | } 136 | }, 137 | { 138 | "args": [], 139 | "deprecationReason": null, 140 | "description": "The ID of an object", 141 | "isDeprecated": false, 142 | "name": "id", 143 | "type": { 144 | "kind": "NON_NULL", 145 | "name": null, 146 | "ofType": { 147 | "kind": "SCALAR", 148 | "name": "ID", 149 | "ofType": null 150 | } 151 | } 152 | }, 153 | { 154 | "args": [ 155 | { 156 | "defaultValue": null, 157 | "description": null, 158 | "name": "first", 159 | "type": { 160 | "kind": "SCALAR", 161 | "name": "Int", 162 | "ofType": null 163 | } 164 | }, 165 | { 166 | "defaultValue": null, 167 | "description": null, 168 | "name": "last", 169 | "type": { 170 | "kind": "SCALAR", 171 | "name": "Int", 172 | "ofType": null 173 | } 174 | }, 175 | { 176 | "defaultValue": "\"any\"", 177 | "description": null, 178 | "name": "status", 179 | "type": { 180 | "kind": "SCALAR", 181 | "name": "String", 182 | "ofType": null 183 | } 184 | }, 185 | { 186 | "defaultValue": null, 187 | "description": null, 188 | "name": "before", 189 | "type": { 190 | "kind": "SCALAR", 191 | "name": "String", 192 | "ofType": null 193 | } 194 | }, 195 | { 196 | "defaultValue": null, 197 | "description": null, 198 | "name": "after", 199 | "type": { 200 | "kind": "SCALAR", 201 | "name": "String", 202 | "ofType": null 203 | } 204 | } 205 | ], 206 | "deprecationReason": null, 207 | "description": null, 208 | "isDeprecated": false, 209 | "name": "todos", 210 | "type": { 211 | "kind": "OBJECT", 212 | "name": "TodoConnection", 213 | "ofType": null 214 | } 215 | } 216 | ], 217 | "inputFields": null, 218 | "interfaces": [ 219 | { 220 | "kind": "INTERFACE", 221 | "name": "Node", 222 | "ofType": null 223 | } 224 | ], 225 | "kind": "OBJECT", 226 | "name": "User", 227 | "possibleTypes": null 228 | }, 229 | { 230 | "description": null, 231 | "enumValues": null, 232 | "fields": [ 233 | { 234 | "args": [], 235 | "deprecationReason": null, 236 | "description": null, 237 | "isDeprecated": false, 238 | "name": "clientMutationId", 239 | "type": { 240 | "kind": "NON_NULL", 241 | "name": null, 242 | "ofType": { 243 | "kind": "SCALAR", 244 | "name": "String", 245 | "ofType": null 246 | } 247 | } 248 | }, 249 | { 250 | "args": [], 251 | "deprecationReason": null, 252 | "description": null, 253 | "isDeprecated": false, 254 | "name": "todo", 255 | "type": { 256 | "kind": "OBJECT", 257 | "name": "Todo", 258 | "ofType": null 259 | } 260 | }, 261 | { 262 | "args": [], 263 | "deprecationReason": null, 264 | "description": null, 265 | "isDeprecated": false, 266 | "name": "viewer", 267 | "type": { 268 | "kind": "OBJECT", 269 | "name": "User", 270 | "ofType": null 271 | } 272 | } 273 | ], 274 | "inputFields": null, 275 | "interfaces": [], 276 | "kind": "OBJECT", 277 | "name": "RenameTodoPayload", 278 | "possibleTypes": null 279 | }, 280 | { 281 | "description": null, 282 | "enumValues": null, 283 | "fields": null, 284 | "inputFields": [ 285 | { 286 | "defaultValue": null, 287 | "description": null, 288 | "name": "id", 289 | "type": { 290 | "kind": "NON_NULL", 291 | "name": null, 292 | "ofType": { 293 | "kind": "SCALAR", 294 | "name": "ID", 295 | "ofType": null 296 | } 297 | } 298 | }, 299 | { 300 | "defaultValue": null, 301 | "description": null, 302 | "name": "complete", 303 | "type": { 304 | "kind": "NON_NULL", 305 | "name": null, 306 | "ofType": { 307 | "kind": "SCALAR", 308 | "name": "Boolean", 309 | "ofType": null 310 | } 311 | } 312 | }, 313 | { 314 | "defaultValue": null, 315 | "description": null, 316 | "name": "clientMutationId", 317 | "type": { 318 | "kind": "NON_NULL", 319 | "name": null, 320 | "ofType": { 321 | "kind": "SCALAR", 322 | "name": "String", 323 | "ofType": null 324 | } 325 | } 326 | } 327 | ], 328 | "interfaces": null, 329 | "kind": "INPUT_OBJECT", 330 | "name": "ChangeTodoStatusInput", 331 | "possibleTypes": null 332 | }, 333 | { 334 | "description": null, 335 | "enumValues": null, 336 | "fields": [ 337 | { 338 | "args": [], 339 | "deprecationReason": null, 340 | "description": null, 341 | "isDeprecated": false, 342 | "name": "description", 343 | "type": { 344 | "kind": "SCALAR", 345 | "name": "String", 346 | "ofType": null 347 | } 348 | }, 349 | { 350 | "args": [], 351 | "deprecationReason": null, 352 | "description": null, 353 | "isDeprecated": false, 354 | "name": "type", 355 | "type": { 356 | "kind": "NON_NULL", 357 | "name": null, 358 | "ofType": { 359 | "kind": "OBJECT", 360 | "name": "__Type", 361 | "ofType": null 362 | } 363 | } 364 | }, 365 | { 366 | "args": [], 367 | "deprecationReason": null, 368 | "description": null, 369 | "isDeprecated": false, 370 | "name": "defaultValue", 371 | "type": { 372 | "kind": "SCALAR", 373 | "name": "String", 374 | "ofType": null 375 | } 376 | }, 377 | { 378 | "args": [], 379 | "deprecationReason": null, 380 | "description": null, 381 | "isDeprecated": false, 382 | "name": "name", 383 | "type": { 384 | "kind": "NON_NULL", 385 | "name": null, 386 | "ofType": { 387 | "kind": "SCALAR", 388 | "name": "String", 389 | "ofType": null 390 | } 391 | } 392 | } 393 | ], 394 | "inputFields": null, 395 | "interfaces": [], 396 | "kind": "OBJECT", 397 | "name": "__InputValue", 398 | "possibleTypes": null 399 | }, 400 | { 401 | "description": null, 402 | "enumValues": null, 403 | "fields": null, 404 | "inputFields": [ 405 | { 406 | "defaultValue": null, 407 | "description": null, 408 | "name": "clientMutationId", 409 | "type": { 410 | "kind": "NON_NULL", 411 | "name": null, 412 | "ofType": { 413 | "kind": "SCALAR", 414 | "name": "String", 415 | "ofType": null 416 | } 417 | } 418 | } 419 | ], 420 | "interfaces": null, 421 | "kind": "INPUT_OBJECT", 422 | "name": "RemoveCompletedTodosInput", 423 | "possibleTypes": null 424 | }, 425 | { 426 | "description": null, 427 | "enumValues": null, 428 | "fields": null, 429 | "inputFields": [ 430 | { 431 | "defaultValue": null, 432 | "description": null, 433 | "name": "id", 434 | "type": { 435 | "kind": "NON_NULL", 436 | "name": null, 437 | "ofType": { 438 | "kind": "SCALAR", 439 | "name": "ID", 440 | "ofType": null 441 | } 442 | } 443 | }, 444 | { 445 | "defaultValue": null, 446 | "description": null, 447 | "name": "clientMutationId", 448 | "type": { 449 | "kind": "NON_NULL", 450 | "name": null, 451 | "ofType": { 452 | "kind": "SCALAR", 453 | "name": "String", 454 | "ofType": null 455 | } 456 | } 457 | } 458 | ], 459 | "interfaces": null, 460 | "kind": "INPUT_OBJECT", 461 | "name": "RemoveTodoInput", 462 | "possibleTypes": null 463 | }, 464 | { 465 | "description": "Information about pagination in a connection.", 466 | "enumValues": null, 467 | "fields": [ 468 | { 469 | "args": [], 470 | "deprecationReason": null, 471 | "description": "When paginating forwards, the cursor to continue.", 472 | "isDeprecated": false, 473 | "name": "endCursor", 474 | "type": { 475 | "kind": "SCALAR", 476 | "name": "String", 477 | "ofType": null 478 | } 479 | }, 480 | { 481 | "args": [], 482 | "deprecationReason": null, 483 | "description": "When paginating forwards, are there more items?", 484 | "isDeprecated": false, 485 | "name": "hasNextPage", 486 | "type": { 487 | "kind": "NON_NULL", 488 | "name": null, 489 | "ofType": { 490 | "kind": "SCALAR", 491 | "name": "Boolean", 492 | "ofType": null 493 | } 494 | } 495 | }, 496 | { 497 | "args": [], 498 | "deprecationReason": null, 499 | "description": "When paginating backwards, are there more items?", 500 | "isDeprecated": false, 501 | "name": "hasPreviousPage", 502 | "type": { 503 | "kind": "NON_NULL", 504 | "name": null, 505 | "ofType": { 506 | "kind": "SCALAR", 507 | "name": "Boolean", 508 | "ofType": null 509 | } 510 | } 511 | }, 512 | { 513 | "args": [], 514 | "deprecationReason": null, 515 | "description": "When paginating backwards, the cursor to continue.", 516 | "isDeprecated": false, 517 | "name": "startCursor", 518 | "type": { 519 | "kind": "SCALAR", 520 | "name": "String", 521 | "ofType": null 522 | } 523 | } 524 | ], 525 | "inputFields": null, 526 | "interfaces": [], 527 | "kind": "OBJECT", 528 | "name": "PageInfo", 529 | "possibleTypes": null 530 | }, 531 | { 532 | "description": null, 533 | "enumValues": null, 534 | "fields": [ 535 | { 536 | "args": [], 537 | "deprecationReason": null, 538 | "description": null, 539 | "isDeprecated": false, 540 | "name": "name", 541 | "type": { 542 | "kind": "NON_NULL", 543 | "name": null, 544 | "ofType": { 545 | "kind": "SCALAR", 546 | "name": "String", 547 | "ofType": null 548 | } 549 | } 550 | }, 551 | { 552 | "args": [], 553 | "deprecationReason": null, 554 | "description": null, 555 | "isDeprecated": false, 556 | "name": "description", 557 | "type": { 558 | "kind": "SCALAR", 559 | "name": "String", 560 | "ofType": null 561 | } 562 | }, 563 | { 564 | "args": [], 565 | "deprecationReason": null, 566 | "description": null, 567 | "isDeprecated": false, 568 | "name": "args", 569 | "type": { 570 | "kind": "NON_NULL", 571 | "name": null, 572 | "ofType": { 573 | "kind": "LIST", 574 | "name": null, 575 | "ofType": { 576 | "kind": "NON_NULL", 577 | "name": null, 578 | "ofType": { 579 | "kind": "OBJECT", 580 | "name": "__InputValue" 581 | } 582 | } 583 | } 584 | } 585 | }, 586 | { 587 | "args": [], 588 | "deprecationReason": null, 589 | "description": null, 590 | "isDeprecated": false, 591 | "name": "type", 592 | "type": { 593 | "kind": "NON_NULL", 594 | "name": null, 595 | "ofType": { 596 | "kind": "OBJECT", 597 | "name": "__Type", 598 | "ofType": null 599 | } 600 | } 601 | }, 602 | { 603 | "args": [], 604 | "deprecationReason": null, 605 | "description": null, 606 | "isDeprecated": false, 607 | "name": "isDeprecated", 608 | "type": { 609 | "kind": "NON_NULL", 610 | "name": null, 611 | "ofType": { 612 | "kind": "SCALAR", 613 | "name": "Boolean", 614 | "ofType": null 615 | } 616 | } 617 | }, 618 | { 619 | "args": [], 620 | "deprecationReason": null, 621 | "description": null, 622 | "isDeprecated": false, 623 | "name": "deprecationReason", 624 | "type": { 625 | "kind": "SCALAR", 626 | "name": "String", 627 | "ofType": null 628 | } 629 | } 630 | ], 631 | "inputFields": null, 632 | "interfaces": [], 633 | "kind": "OBJECT", 634 | "name": "__Field", 635 | "possibleTypes": null 636 | }, 637 | { 638 | "description": null, 639 | "enumValues": null, 640 | "fields": null, 641 | "inputFields": [ 642 | { 643 | "defaultValue": null, 644 | "description": null, 645 | "name": "text", 646 | "type": { 647 | "kind": "NON_NULL", 648 | "name": null, 649 | "ofType": { 650 | "kind": "SCALAR", 651 | "name": "String", 652 | "ofType": null 653 | } 654 | } 655 | }, 656 | { 657 | "defaultValue": null, 658 | "description": null, 659 | "name": "clientMutationId", 660 | "type": { 661 | "kind": "NON_NULL", 662 | "name": null, 663 | "ofType": { 664 | "kind": "SCALAR", 665 | "name": "String", 666 | "ofType": null 667 | } 668 | } 669 | } 670 | ], 671 | "interfaces": null, 672 | "kind": "INPUT_OBJECT", 673 | "name": "AddTodoInput", 674 | "possibleTypes": null 675 | }, 676 | { 677 | "description": null, 678 | "enumValues": null, 679 | "fields": [ 680 | { 681 | "args": [], 682 | "deprecationReason": null, 683 | "description": null, 684 | "isDeprecated": false, 685 | "name": "clientMutationId", 686 | "type": { 687 | "kind": "NON_NULL", 688 | "name": null, 689 | "ofType": { 690 | "kind": "SCALAR", 691 | "name": "String", 692 | "ofType": null 693 | } 694 | } 695 | }, 696 | { 697 | "args": [], 698 | "deprecationReason": null, 699 | "description": null, 700 | "isDeprecated": false, 701 | "name": "todo", 702 | "type": { 703 | "kind": "OBJECT", 704 | "name": "Todo", 705 | "ofType": null 706 | } 707 | }, 708 | { 709 | "args": [], 710 | "deprecationReason": null, 711 | "description": null, 712 | "isDeprecated": false, 713 | "name": "viewer", 714 | "type": { 715 | "kind": "OBJECT", 716 | "name": "User", 717 | "ofType": null 718 | } 719 | } 720 | ], 721 | "inputFields": null, 722 | "interfaces": [], 723 | "kind": "OBJECT", 724 | "name": "ChangeTodoStatusPayload", 725 | "possibleTypes": null 726 | }, 727 | { 728 | "description": "An enum describing what kind of type a given __Type is", 729 | "enumValues": [ 730 | { 731 | "deprecationReason": null, 732 | "description": "Indicates this type is an enum. `enumValues` is a valid field.", 733 | "isDeprecated": false, 734 | "name": "ENUM" 735 | }, 736 | { 737 | "deprecationReason": null, 738 | "description": "Indicates this type is an input object. `inputFields` is a valid field.", 739 | "isDeprecated": false, 740 | "name": "INPUT_OBJECT" 741 | }, 742 | { 743 | "deprecationReason": null, 744 | "description": "Indicates this type is a list. `ofType` is a valid field.", 745 | "isDeprecated": false, 746 | "name": "LIST" 747 | }, 748 | { 749 | "deprecationReason": null, 750 | "description": "Indicates this type is a non-null. `ofType` is a valid field.", 751 | "isDeprecated": false, 752 | "name": "NON_NULL" 753 | }, 754 | { 755 | "deprecationReason": null, 756 | "description": "Indicates this type is a scalar.", 757 | "isDeprecated": false, 758 | "name": "SCALAR" 759 | }, 760 | { 761 | "deprecationReason": null, 762 | "description": "Indicates this type is an object. `fields` and `interfaces` are valid fields.", 763 | "isDeprecated": false, 764 | "name": "OBJECT" 765 | }, 766 | { 767 | "deprecationReason": null, 768 | "description": "Indicates this type is an interface. `fields` and `possibleTypes` are valid fields.", 769 | "isDeprecated": false, 770 | "name": "INTERFACE" 771 | }, 772 | { 773 | "deprecationReason": null, 774 | "description": "Indicates this type is a union. `possibleTypes` is a valid field.", 775 | "isDeprecated": false, 776 | "name": "UNION" 777 | } 778 | ], 779 | "fields": null, 780 | "inputFields": null, 781 | "interfaces": null, 782 | "kind": "ENUM", 783 | "name": "__TypeKind", 784 | "possibleTypes": null 785 | }, 786 | { 787 | "description": null, 788 | "enumValues": null, 789 | "fields": [ 790 | { 791 | "args": [], 792 | "deprecationReason": null, 793 | "description": "The ID of an object", 794 | "isDeprecated": false, 795 | "name": "id", 796 | "type": { 797 | "kind": "NON_NULL", 798 | "name": null, 799 | "ofType": { 800 | "kind": "SCALAR", 801 | "name": "ID", 802 | "ofType": null 803 | } 804 | } 805 | }, 806 | { 807 | "args": [], 808 | "deprecationReason": null, 809 | "description": null, 810 | "isDeprecated": false, 811 | "name": "text", 812 | "type": { 813 | "kind": "SCALAR", 814 | "name": "String", 815 | "ofType": null 816 | } 817 | }, 818 | { 819 | "args": [], 820 | "deprecationReason": null, 821 | "description": null, 822 | "isDeprecated": false, 823 | "name": "complete", 824 | "type": { 825 | "kind": "SCALAR", 826 | "name": "Boolean", 827 | "ofType": null 828 | } 829 | } 830 | ], 831 | "inputFields": null, 832 | "interfaces": [ 833 | { 834 | "kind": "INTERFACE", 835 | "name": "Node", 836 | "ofType": null 837 | } 838 | ], 839 | "kind": "OBJECT", 840 | "name": "Todo", 841 | "possibleTypes": null 842 | }, 843 | { 844 | "description": null, 845 | "enumValues": null, 846 | "fields": null, 847 | "inputFields": null, 848 | "interfaces": null, 849 | "kind": "SCALAR", 850 | "name": "Int", 851 | "possibleTypes": null 852 | }, 853 | { 854 | "description": null, 855 | "enumValues": null, 856 | "fields": [ 857 | { 858 | "args": [ 859 | { 860 | "defaultValue": null, 861 | "description": null, 862 | "name": "input", 863 | "type": { 864 | "kind": "NON_NULL", 865 | "name": null, 866 | "ofType": { 867 | "kind": "INPUT_OBJECT", 868 | "name": "MarkAllTodosInput", 869 | "ofType": null 870 | } 871 | } 872 | } 873 | ], 874 | "deprecationReason": null, 875 | "description": null, 876 | "isDeprecated": false, 877 | "name": "markAllTodos", 878 | "type": { 879 | "kind": "OBJECT", 880 | "name": "MarkAllTodosPayload", 881 | "ofType": null 882 | } 883 | }, 884 | { 885 | "args": [ 886 | { 887 | "defaultValue": null, 888 | "description": null, 889 | "name": "input", 890 | "type": { 891 | "kind": "NON_NULL", 892 | "name": null, 893 | "ofType": { 894 | "kind": "INPUT_OBJECT", 895 | "name": "RemoveCompletedTodosInput", 896 | "ofType": null 897 | } 898 | } 899 | } 900 | ], 901 | "deprecationReason": null, 902 | "description": null, 903 | "isDeprecated": false, 904 | "name": "removeCompletedTodos", 905 | "type": { 906 | "kind": "OBJECT", 907 | "name": "RemoveCompletedTodosPayload", 908 | "ofType": null 909 | } 910 | }, 911 | { 912 | "args": [ 913 | { 914 | "defaultValue": null, 915 | "description": null, 916 | "name": "input", 917 | "type": { 918 | "kind": "NON_NULL", 919 | "name": null, 920 | "ofType": { 921 | "kind": "INPUT_OBJECT", 922 | "name": "RemoveTodoInput", 923 | "ofType": null 924 | } 925 | } 926 | } 927 | ], 928 | "deprecationReason": null, 929 | "description": null, 930 | "isDeprecated": false, 931 | "name": "removeTodo", 932 | "type": { 933 | "kind": "OBJECT", 934 | "name": "RemoveTodoPayload", 935 | "ofType": null 936 | } 937 | }, 938 | { 939 | "args": [ 940 | { 941 | "defaultValue": null, 942 | "description": null, 943 | "name": "input", 944 | "type": { 945 | "kind": "NON_NULL", 946 | "name": null, 947 | "ofType": { 948 | "kind": "INPUT_OBJECT", 949 | "name": "RenameTodoInput", 950 | "ofType": null 951 | } 952 | } 953 | } 954 | ], 955 | "deprecationReason": null, 956 | "description": null, 957 | "isDeprecated": false, 958 | "name": "renameTodo", 959 | "type": { 960 | "kind": "OBJECT", 961 | "name": "RenameTodoPayload", 962 | "ofType": null 963 | } 964 | }, 965 | { 966 | "args": [ 967 | { 968 | "defaultValue": null, 969 | "description": null, 970 | "name": "input", 971 | "type": { 972 | "kind": "NON_NULL", 973 | "name": null, 974 | "ofType": { 975 | "kind": "INPUT_OBJECT", 976 | "name": "AddTodoInput", 977 | "ofType": null 978 | } 979 | } 980 | } 981 | ], 982 | "deprecationReason": null, 983 | "description": null, 984 | "isDeprecated": false, 985 | "name": "addTodo", 986 | "type": { 987 | "kind": "OBJECT", 988 | "name": "AddTodoPayload", 989 | "ofType": null 990 | } 991 | }, 992 | { 993 | "args": [ 994 | { 995 | "defaultValue": null, 996 | "description": null, 997 | "name": "input", 998 | "type": { 999 | "kind": "NON_NULL", 1000 | "name": null, 1001 | "ofType": { 1002 | "kind": "INPUT_OBJECT", 1003 | "name": "ChangeTodoStatusInput", 1004 | "ofType": null 1005 | } 1006 | } 1007 | } 1008 | ], 1009 | "deprecationReason": null, 1010 | "description": null, 1011 | "isDeprecated": false, 1012 | "name": "changeTodoStatus", 1013 | "type": { 1014 | "kind": "OBJECT", 1015 | "name": "ChangeTodoStatusPayload", 1016 | "ofType": null 1017 | } 1018 | } 1019 | ], 1020 | "inputFields": null, 1021 | "interfaces": [], 1022 | "kind": "OBJECT", 1023 | "name": "Mutation", 1024 | "possibleTypes": null 1025 | }, 1026 | { 1027 | "description": null, 1028 | "enumValues": null, 1029 | "fields": [ 1030 | { 1031 | "args": [], 1032 | "deprecationReason": null, 1033 | "description": null, 1034 | "isDeprecated": false, 1035 | "name": "deletedTodoIds", 1036 | "type": { 1037 | "kind": "LIST", 1038 | "name": null, 1039 | "ofType": { 1040 | "kind": "SCALAR", 1041 | "name": "String", 1042 | "ofType": null 1043 | } 1044 | } 1045 | }, 1046 | { 1047 | "args": [], 1048 | "deprecationReason": null, 1049 | "description": null, 1050 | "isDeprecated": false, 1051 | "name": "viewer", 1052 | "type": { 1053 | "kind": "OBJECT", 1054 | "name": "User", 1055 | "ofType": null 1056 | } 1057 | }, 1058 | { 1059 | "args": [], 1060 | "deprecationReason": null, 1061 | "description": null, 1062 | "isDeprecated": false, 1063 | "name": "clientMutationId", 1064 | "type": { 1065 | "kind": "NON_NULL", 1066 | "name": null, 1067 | "ofType": { 1068 | "kind": "SCALAR", 1069 | "name": "String", 1070 | "ofType": null 1071 | } 1072 | } 1073 | } 1074 | ], 1075 | "inputFields": null, 1076 | "interfaces": [], 1077 | "kind": "OBJECT", 1078 | "name": "RemoveCompletedTodosPayload", 1079 | "possibleTypes": null 1080 | }, 1081 | { 1082 | "description": null, 1083 | "enumValues": null, 1084 | "fields": null, 1085 | "inputFields": [ 1086 | { 1087 | "defaultValue": null, 1088 | "description": null, 1089 | "name": "complete", 1090 | "type": { 1091 | "kind": "NON_NULL", 1092 | "name": null, 1093 | "ofType": { 1094 | "kind": "SCALAR", 1095 | "name": "Boolean", 1096 | "ofType": null 1097 | } 1098 | } 1099 | }, 1100 | { 1101 | "defaultValue": null, 1102 | "description": null, 1103 | "name": "clientMutationId", 1104 | "type": { 1105 | "kind": "NON_NULL", 1106 | "name": null, 1107 | "ofType": { 1108 | "kind": "SCALAR", 1109 | "name": "String", 1110 | "ofType": null 1111 | } 1112 | } 1113 | } 1114 | ], 1115 | "interfaces": null, 1116 | "kind": "INPUT_OBJECT", 1117 | "name": "MarkAllTodosInput", 1118 | "possibleTypes": null 1119 | }, 1120 | { 1121 | "description": null, 1122 | "enumValues": null, 1123 | "fields": [ 1124 | { 1125 | "args": [], 1126 | "deprecationReason": null, 1127 | "description": null, 1128 | "isDeprecated": false, 1129 | "name": "kind", 1130 | "type": { 1131 | "kind": "NON_NULL", 1132 | "name": null, 1133 | "ofType": { 1134 | "kind": "ENUM", 1135 | "name": "__TypeKind", 1136 | "ofType": null 1137 | } 1138 | } 1139 | }, 1140 | { 1141 | "args": [ 1142 | { 1143 | "defaultValue": "false", 1144 | "description": null, 1145 | "name": "includeDeprecated", 1146 | "type": { 1147 | "kind": "SCALAR", 1148 | "name": "Boolean", 1149 | "ofType": null 1150 | } 1151 | } 1152 | ], 1153 | "deprecationReason": null, 1154 | "description": null, 1155 | "isDeprecated": false, 1156 | "name": "enumValues", 1157 | "type": { 1158 | "kind": "LIST", 1159 | "name": null, 1160 | "ofType": { 1161 | "kind": "NON_NULL", 1162 | "name": null, 1163 | "ofType": { 1164 | "kind": "OBJECT", 1165 | "name": "__EnumValue", 1166 | "ofType": null 1167 | } 1168 | } 1169 | } 1170 | }, 1171 | { 1172 | "args": [], 1173 | "deprecationReason": null, 1174 | "description": null, 1175 | "isDeprecated": false, 1176 | "name": "description", 1177 | "type": { 1178 | "kind": "SCALAR", 1179 | "name": "String", 1180 | "ofType": null 1181 | } 1182 | }, 1183 | { 1184 | "args": [], 1185 | "deprecationReason": null, 1186 | "description": null, 1187 | "isDeprecated": false, 1188 | "name": "ofType", 1189 | "type": { 1190 | "kind": "OBJECT", 1191 | "name": "__Type", 1192 | "ofType": null 1193 | } 1194 | }, 1195 | { 1196 | "args": [], 1197 | "deprecationReason": null, 1198 | "description": null, 1199 | "isDeprecated": false, 1200 | "name": "interfaces", 1201 | "type": { 1202 | "kind": "LIST", 1203 | "name": null, 1204 | "ofType": { 1205 | "kind": "NON_NULL", 1206 | "name": null, 1207 | "ofType": { 1208 | "kind": "OBJECT", 1209 | "name": "__Type", 1210 | "ofType": null 1211 | } 1212 | } 1213 | } 1214 | }, 1215 | { 1216 | "args": [], 1217 | "deprecationReason": null, 1218 | "description": null, 1219 | "isDeprecated": false, 1220 | "name": "name", 1221 | "type": { 1222 | "kind": "SCALAR", 1223 | "name": "String", 1224 | "ofType": null 1225 | } 1226 | }, 1227 | { 1228 | "args": [], 1229 | "deprecationReason": null, 1230 | "description": null, 1231 | "isDeprecated": false, 1232 | "name": "possibleTypes", 1233 | "type": { 1234 | "kind": "LIST", 1235 | "name": null, 1236 | "ofType": { 1237 | "kind": "NON_NULL", 1238 | "name": null, 1239 | "ofType": { 1240 | "kind": "OBJECT", 1241 | "name": "__Type", 1242 | "ofType": null 1243 | } 1244 | } 1245 | } 1246 | }, 1247 | { 1248 | "args": [], 1249 | "deprecationReason": null, 1250 | "description": null, 1251 | "isDeprecated": false, 1252 | "name": "inputFields", 1253 | "type": { 1254 | "kind": "LIST", 1255 | "name": null, 1256 | "ofType": { 1257 | "kind": "NON_NULL", 1258 | "name": null, 1259 | "ofType": { 1260 | "kind": "OBJECT", 1261 | "name": "__InputValue", 1262 | "ofType": null 1263 | } 1264 | } 1265 | } 1266 | }, 1267 | { 1268 | "args": [ 1269 | { 1270 | "defaultValue": "false", 1271 | "description": null, 1272 | "name": "includeDeprecated", 1273 | "type": { 1274 | "kind": "SCALAR", 1275 | "name": "Boolean", 1276 | "ofType": null 1277 | } 1278 | } 1279 | ], 1280 | "deprecationReason": null, 1281 | "description": null, 1282 | "isDeprecated": false, 1283 | "name": "fields", 1284 | "type": { 1285 | "kind": "LIST", 1286 | "name": null, 1287 | "ofType": { 1288 | "kind": "NON_NULL", 1289 | "name": null, 1290 | "ofType": { 1291 | "kind": "OBJECT", 1292 | "name": "__Field", 1293 | "ofType": null 1294 | } 1295 | } 1296 | } 1297 | } 1298 | ], 1299 | "inputFields": null, 1300 | "interfaces": [], 1301 | "kind": "OBJECT", 1302 | "name": "__Type", 1303 | "possibleTypes": null 1304 | }, 1305 | { 1306 | "description": "A GraphQL Schema defines the capabilities of a GraphQL\nserver. It exposes all available types and directives on\nthe server, as well as the entry points for query and\nmutation operations.", 1307 | "enumValues": null, 1308 | "fields": [ 1309 | { 1310 | "args": [], 1311 | "deprecationReason": null, 1312 | "description": "A list of all directives supported by this server.", 1313 | "isDeprecated": false, 1314 | "name": "directives", 1315 | "type": { 1316 | "kind": "NON_NULL", 1317 | "name": null, 1318 | "ofType": { 1319 | "kind": "LIST", 1320 | "name": null, 1321 | "ofType": { 1322 | "kind": "NON_NULL", 1323 | "name": null, 1324 | "ofType": { 1325 | "kind": "OBJECT", 1326 | "name": "__Directive" 1327 | } 1328 | } 1329 | } 1330 | } 1331 | }, 1332 | { 1333 | "args": [], 1334 | "deprecationReason": null, 1335 | "description": "A list of all types supported by this server.", 1336 | "isDeprecated": false, 1337 | "name": "types", 1338 | "type": { 1339 | "kind": "NON_NULL", 1340 | "name": null, 1341 | "ofType": { 1342 | "kind": "LIST", 1343 | "name": null, 1344 | "ofType": { 1345 | "kind": "NON_NULL", 1346 | "name": null, 1347 | "ofType": { 1348 | "kind": "OBJECT", 1349 | "name": "__Type" 1350 | } 1351 | } 1352 | } 1353 | } 1354 | }, 1355 | { 1356 | "args": [], 1357 | "deprecationReason": null, 1358 | "description": "The type that query operations will be rooted at.", 1359 | "isDeprecated": false, 1360 | "name": "queryType", 1361 | "type": { 1362 | "kind": "NON_NULL", 1363 | "name": null, 1364 | "ofType": { 1365 | "kind": "OBJECT", 1366 | "name": "__Type", 1367 | "ofType": null 1368 | } 1369 | } 1370 | }, 1371 | { 1372 | "args": [], 1373 | "deprecationReason": null, 1374 | "description": "If this server supports mutation, the type that mutation operations will be rooted at.", 1375 | "isDeprecated": false, 1376 | "name": "mutationType", 1377 | "type": { 1378 | "kind": "OBJECT", 1379 | "name": "__Type", 1380 | "ofType": null 1381 | } 1382 | } 1383 | ], 1384 | "inputFields": null, 1385 | "interfaces": [], 1386 | "kind": "OBJECT", 1387 | "name": "__Schema", 1388 | "possibleTypes": null 1389 | }, 1390 | { 1391 | "description": null, 1392 | "enumValues": null, 1393 | "fields": null, 1394 | "inputFields": null, 1395 | "interfaces": null, 1396 | "kind": "SCALAR", 1397 | "name": "Boolean", 1398 | "possibleTypes": null 1399 | }, 1400 | { 1401 | "description": "A connection to a list of items.", 1402 | "enumValues": null, 1403 | "fields": [ 1404 | { 1405 | "args": [], 1406 | "deprecationReason": null, 1407 | "description": "Information to aid in pagination.", 1408 | "isDeprecated": false, 1409 | "name": "pageInfo", 1410 | "type": { 1411 | "kind": "NON_NULL", 1412 | "name": null, 1413 | "ofType": { 1414 | "kind": "OBJECT", 1415 | "name": "PageInfo", 1416 | "ofType": null 1417 | } 1418 | } 1419 | }, 1420 | { 1421 | "args": [], 1422 | "deprecationReason": null, 1423 | "description": "Information to aid in pagination.", 1424 | "isDeprecated": false, 1425 | "name": "edges", 1426 | "type": { 1427 | "kind": "LIST", 1428 | "name": null, 1429 | "ofType": { 1430 | "kind": "OBJECT", 1431 | "name": "TodoEdge", 1432 | "ofType": null 1433 | } 1434 | } 1435 | } 1436 | ], 1437 | "inputFields": null, 1438 | "interfaces": [], 1439 | "kind": "OBJECT", 1440 | "name": "TodoConnection", 1441 | "possibleTypes": null 1442 | }, 1443 | { 1444 | "description": "An edge in a connection", 1445 | "enumValues": null, 1446 | "fields": [ 1447 | { 1448 | "args": [], 1449 | "deprecationReason": null, 1450 | "description": "The item at the end of the edge", 1451 | "isDeprecated": false, 1452 | "name": "node", 1453 | "type": { 1454 | "kind": "OBJECT", 1455 | "name": "Todo", 1456 | "ofType": null 1457 | } 1458 | }, 1459 | { 1460 | "args": [], 1461 | "deprecationReason": null, 1462 | "description": " cursor for use in pagination", 1463 | "isDeprecated": false, 1464 | "name": "cursor", 1465 | "type": { 1466 | "kind": "NON_NULL", 1467 | "name": null, 1468 | "ofType": { 1469 | "kind": "SCALAR", 1470 | "name": "String", 1471 | "ofType": null 1472 | } 1473 | } 1474 | } 1475 | ], 1476 | "inputFields": null, 1477 | "interfaces": [], 1478 | "kind": "OBJECT", 1479 | "name": "TodoEdge", 1480 | "possibleTypes": null 1481 | }, 1482 | { 1483 | "description": null, 1484 | "enumValues": null, 1485 | "fields": [ 1486 | { 1487 | "args": [], 1488 | "deprecationReason": null, 1489 | "description": null, 1490 | "isDeprecated": false, 1491 | "name": "deletedTodoId", 1492 | "type": { 1493 | "kind": "SCALAR", 1494 | "name": "ID", 1495 | "ofType": null 1496 | } 1497 | }, 1498 | { 1499 | "args": [], 1500 | "deprecationReason": null, 1501 | "description": null, 1502 | "isDeprecated": false, 1503 | "name": "viewer", 1504 | "type": { 1505 | "kind": "OBJECT", 1506 | "name": "User", 1507 | "ofType": null 1508 | } 1509 | }, 1510 | { 1511 | "args": [], 1512 | "deprecationReason": null, 1513 | "description": null, 1514 | "isDeprecated": false, 1515 | "name": "clientMutationId", 1516 | "type": { 1517 | "kind": "NON_NULL", 1518 | "name": null, 1519 | "ofType": { 1520 | "kind": "SCALAR", 1521 | "name": "String", 1522 | "ofType": null 1523 | } 1524 | } 1525 | } 1526 | ], 1527 | "inputFields": null, 1528 | "interfaces": [], 1529 | "kind": "OBJECT", 1530 | "name": "RemoveTodoPayload", 1531 | "possibleTypes": null 1532 | }, 1533 | { 1534 | "description": null, 1535 | "enumValues": null, 1536 | "fields": [ 1537 | { 1538 | "args": [], 1539 | "deprecationReason": null, 1540 | "description": null, 1541 | "isDeprecated": false, 1542 | "name": "clientMutationId", 1543 | "type": { 1544 | "kind": "NON_NULL", 1545 | "name": null, 1546 | "ofType": { 1547 | "kind": "SCALAR", 1548 | "name": "String", 1549 | "ofType": null 1550 | } 1551 | } 1552 | }, 1553 | { 1554 | "args": [], 1555 | "deprecationReason": null, 1556 | "description": null, 1557 | "isDeprecated": false, 1558 | "name": "changedTodos", 1559 | "type": { 1560 | "kind": "LIST", 1561 | "name": null, 1562 | "ofType": { 1563 | "kind": "OBJECT", 1564 | "name": "Todo", 1565 | "ofType": null 1566 | } 1567 | } 1568 | }, 1569 | { 1570 | "args": [], 1571 | "deprecationReason": null, 1572 | "description": null, 1573 | "isDeprecated": false, 1574 | "name": "viewer", 1575 | "type": { 1576 | "kind": "OBJECT", 1577 | "name": "User", 1578 | "ofType": null 1579 | } 1580 | } 1581 | ], 1582 | "inputFields": null, 1583 | "interfaces": [], 1584 | "kind": "OBJECT", 1585 | "name": "MarkAllTodosPayload", 1586 | "possibleTypes": null 1587 | }, 1588 | { 1589 | "description": "An object with an ID", 1590 | "enumValues": null, 1591 | "fields": [ 1592 | { 1593 | "args": [], 1594 | "deprecationReason": null, 1595 | "description": "The id of the object", 1596 | "isDeprecated": false, 1597 | "name": "id", 1598 | "type": { 1599 | "kind": "NON_NULL", 1600 | "name": null, 1601 | "ofType": { 1602 | "kind": "SCALAR", 1603 | "name": "ID", 1604 | "ofType": null 1605 | } 1606 | } 1607 | } 1608 | ], 1609 | "inputFields": null, 1610 | "interfaces": null, 1611 | "kind": "INTERFACE", 1612 | "name": "Node", 1613 | "possibleTypes": [ 1614 | { 1615 | "kind": "OBJECT", 1616 | "name": "Todo", 1617 | "ofType": null 1618 | }, 1619 | { 1620 | "kind": "OBJECT", 1621 | "name": "User", 1622 | "ofType": null 1623 | } 1624 | ] 1625 | }, 1626 | { 1627 | "description": null, 1628 | "enumValues": null, 1629 | "fields": null, 1630 | "inputFields": null, 1631 | "interfaces": null, 1632 | "kind": "SCALAR", 1633 | "name": "String", 1634 | "possibleTypes": null 1635 | }, 1636 | { 1637 | "description": null, 1638 | "enumValues": null, 1639 | "fields": null, 1640 | "inputFields": [ 1641 | { 1642 | "defaultValue": null, 1643 | "description": null, 1644 | "name": "clientMutationId", 1645 | "type": { 1646 | "kind": "NON_NULL", 1647 | "name": null, 1648 | "ofType": { 1649 | "kind": "SCALAR", 1650 | "name": "String", 1651 | "ofType": null 1652 | } 1653 | } 1654 | }, 1655 | { 1656 | "defaultValue": null, 1657 | "description": null, 1658 | "name": "id", 1659 | "type": { 1660 | "kind": "NON_NULL", 1661 | "name": null, 1662 | "ofType": { 1663 | "kind": "SCALAR", 1664 | "name": "ID", 1665 | "ofType": null 1666 | } 1667 | } 1668 | }, 1669 | { 1670 | "defaultValue": null, 1671 | "description": null, 1672 | "name": "text", 1673 | "type": { 1674 | "kind": "NON_NULL", 1675 | "name": null, 1676 | "ofType": { 1677 | "kind": "SCALAR", 1678 | "name": "String", 1679 | "ofType": null 1680 | } 1681 | } 1682 | } 1683 | ], 1684 | "interfaces": null, 1685 | "kind": "INPUT_OBJECT", 1686 | "name": "RenameTodoInput", 1687 | "possibleTypes": null 1688 | }, 1689 | { 1690 | "description": null, 1691 | "enumValues": null, 1692 | "fields": [ 1693 | { 1694 | "args": [], 1695 | "deprecationReason": null, 1696 | "description": null, 1697 | "isDeprecated": false, 1698 | "name": "viewer", 1699 | "type": { 1700 | "kind": "OBJECT", 1701 | "name": "User", 1702 | "ofType": null 1703 | } 1704 | }, 1705 | { 1706 | "args": [], 1707 | "deprecationReason": null, 1708 | "description": null, 1709 | "isDeprecated": false, 1710 | "name": "clientMutationId", 1711 | "type": { 1712 | "kind": "NON_NULL", 1713 | "name": null, 1714 | "ofType": { 1715 | "kind": "SCALAR", 1716 | "name": "String", 1717 | "ofType": null 1718 | } 1719 | } 1720 | }, 1721 | { 1722 | "args": [], 1723 | "deprecationReason": null, 1724 | "description": null, 1725 | "isDeprecated": false, 1726 | "name": "todoEdge", 1727 | "type": { 1728 | "kind": "OBJECT", 1729 | "name": "TodoEdge", 1730 | "ofType": null 1731 | } 1732 | } 1733 | ], 1734 | "inputFields": null, 1735 | "interfaces": [], 1736 | "kind": "OBJECT", 1737 | "name": "AddTodoPayload", 1738 | "possibleTypes": null 1739 | }, 1740 | { 1741 | "description": null, 1742 | "enumValues": null, 1743 | "fields": null, 1744 | "inputFields": null, 1745 | "interfaces": null, 1746 | "kind": "SCALAR", 1747 | "name": "ID", 1748 | "possibleTypes": null 1749 | }, 1750 | { 1751 | "description": null, 1752 | "enumValues": null, 1753 | "fields": [ 1754 | { 1755 | "args": [], 1756 | "deprecationReason": null, 1757 | "description": null, 1758 | "isDeprecated": false, 1759 | "name": "name", 1760 | "type": { 1761 | "kind": "NON_NULL", 1762 | "name": null, 1763 | "ofType": { 1764 | "kind": "SCALAR", 1765 | "name": "String", 1766 | "ofType": null 1767 | } 1768 | } 1769 | }, 1770 | { 1771 | "args": [], 1772 | "deprecationReason": null, 1773 | "description": null, 1774 | "isDeprecated": false, 1775 | "name": "description", 1776 | "type": { 1777 | "kind": "SCALAR", 1778 | "name": "String", 1779 | "ofType": null 1780 | } 1781 | }, 1782 | { 1783 | "args": [], 1784 | "deprecationReason": null, 1785 | "description": null, 1786 | "isDeprecated": false, 1787 | "name": "isDeprecated", 1788 | "type": { 1789 | "kind": "NON_NULL", 1790 | "name": null, 1791 | "ofType": { 1792 | "kind": "SCALAR", 1793 | "name": "Boolean", 1794 | "ofType": null 1795 | } 1796 | } 1797 | }, 1798 | { 1799 | "args": [], 1800 | "deprecationReason": null, 1801 | "description": null, 1802 | "isDeprecated": false, 1803 | "name": "deprecationReason", 1804 | "type": { 1805 | "kind": "SCALAR", 1806 | "name": "String", 1807 | "ofType": null 1808 | } 1809 | } 1810 | ], 1811 | "inputFields": null, 1812 | "interfaces": [], 1813 | "kind": "OBJECT", 1814 | "name": "__EnumValue", 1815 | "possibleTypes": null 1816 | }, 1817 | { 1818 | "description": null, 1819 | "enumValues": null, 1820 | "fields": [ 1821 | { 1822 | "args": [], 1823 | "deprecationReason": null, 1824 | "description": null, 1825 | "isDeprecated": false, 1826 | "name": "name", 1827 | "type": { 1828 | "kind": "NON_NULL", 1829 | "name": null, 1830 | "ofType": { 1831 | "kind": "SCALAR", 1832 | "name": "String", 1833 | "ofType": null 1834 | } 1835 | } 1836 | }, 1837 | { 1838 | "args": [], 1839 | "deprecationReason": null, 1840 | "description": null, 1841 | "isDeprecated": false, 1842 | "name": "description", 1843 | "type": { 1844 | "kind": "SCALAR", 1845 | "name": "String", 1846 | "ofType": null 1847 | } 1848 | }, 1849 | { 1850 | "args": [], 1851 | "deprecationReason": null, 1852 | "description": null, 1853 | "isDeprecated": false, 1854 | "name": "args", 1855 | "type": { 1856 | "kind": "NON_NULL", 1857 | "name": null, 1858 | "ofType": { 1859 | "kind": "LIST", 1860 | "name": null, 1861 | "ofType": { 1862 | "kind": "NON_NULL", 1863 | "name": null, 1864 | "ofType": { 1865 | "kind": "OBJECT", 1866 | "name": "__InputValue" 1867 | } 1868 | } 1869 | } 1870 | } 1871 | }, 1872 | { 1873 | "args": [], 1874 | "deprecationReason": null, 1875 | "description": null, 1876 | "isDeprecated": false, 1877 | "name": "onOperation", 1878 | "type": { 1879 | "kind": "NON_NULL", 1880 | "name": null, 1881 | "ofType": { 1882 | "kind": "SCALAR", 1883 | "name": "Boolean", 1884 | "ofType": null 1885 | } 1886 | } 1887 | }, 1888 | { 1889 | "args": [], 1890 | "deprecationReason": null, 1891 | "description": null, 1892 | "isDeprecated": false, 1893 | "name": "onFragment", 1894 | "type": { 1895 | "kind": "NON_NULL", 1896 | "name": null, 1897 | "ofType": { 1898 | "kind": "SCALAR", 1899 | "name": "Boolean", 1900 | "ofType": null 1901 | } 1902 | } 1903 | }, 1904 | { 1905 | "args": [], 1906 | "deprecationReason": null, 1907 | "description": null, 1908 | "isDeprecated": false, 1909 | "name": "onField", 1910 | "type": { 1911 | "kind": "NON_NULL", 1912 | "name": null, 1913 | "ofType": { 1914 | "kind": "SCALAR", 1915 | "name": "Boolean", 1916 | "ofType": null 1917 | } 1918 | } 1919 | } 1920 | ], 1921 | "inputFields": null, 1922 | "interfaces": [], 1923 | "kind": "OBJECT", 1924 | "name": "__Directive", 1925 | "possibleTypes": null 1926 | } 1927 | ] 1928 | } 1929 | } 1930 | } --------------------------------------------------------------------------------