├── 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 |
10 | {this.props.viewer.widgets.edges.map(edge =>
11 | - {edge.node.name} (ID: {edge.node.id})
12 | )}
13 |
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 |
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": "
<%= 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 |
93 |
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 | }
--------------------------------------------------------------------------------