├── .eslintrc ├── .gitignore ├── LICENSE ├── README.md ├── build └── babelRelayPlugin.js ├── data ├── schema.js └── schema.json ├── index.js ├── js ├── app.js ├── components │ ├── App.js │ ├── Todo.js │ ├── TodoApp.js │ ├── TodoList.js │ └── TodoTextInput.js ├── mutations │ ├── AddTodoMutation.js │ ├── ChangeTodoStatusMutation.js │ ├── ChangeTodoTextMutation.js │ └── DeleteTodoMutation.js └── routes │ └── AppRoute.js ├── package.json ├── public ├── app.js └── index.html ├── scripts └── updateSchema.js ├── server.js └── webpack.config.js /.eslintrc: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "airbnb/base", 3 | "plugins": [ 4 | "react" 5 | ], 6 | "ecmaFeatures": { 7 | "blockBindings": true, 8 | "forOf": true, 9 | "jsx": true 10 | }, 11 | "env": { 12 | "node": true 13 | }, 14 | "globals": { 15 | "before": false, 16 | "after": false, 17 | "beforeEach": false, 18 | "afterEach": false, 19 | "describe": false, 20 | "it": false 21 | }, 22 | "parser": "babel-eslint", 23 | "rules": { 24 | "no-shadow": 0, 25 | "no-param-reassign": 0, 26 | "id-length": [2, {"exceptions": ["_", "i", "e"]}], 27 | "comma-dangle": [1, "never"] 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | node_modules 3 | npm-debug.log 4 | data/schema.graphql 5 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2015 RisingStack 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 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | Graffiti Relay TodoMVC application 2 | --- 3 | 4 | [Running version](https://graffiti-todo.herokuapp.com/) 5 | 6 | This is an example application using [graffiti-mongoose](https://github.com/RisingStack/graffiti-mongoose). 7 | You only need to specify your Mongoose schemas, and graffiti will handle the rest. 8 | It will generate the GraphQL schema supporting queries and mutations compatible with [Relay](https://github.com/facebook/relay). 9 | -------------------------------------------------------------------------------- /build/babelRelayPlugin.js: -------------------------------------------------------------------------------- 1 | var getBabelRelayPlugin = require('babel-relay-plugin'); 2 | var schema = require('../data/schema.json'); 3 | 4 | module.exports = getBabelRelayPlugin(schema.data); 5 | -------------------------------------------------------------------------------- /data/schema.js: -------------------------------------------------------------------------------- 1 | import mongoose from 'mongoose'; 2 | 3 | const TodoSchema = new mongoose.Schema({ 4 | text: { 5 | type: String 6 | }, 7 | complete: { 8 | type: Boolean 9 | } 10 | }); 11 | 12 | const Todo = mongoose.model('Todo', TodoSchema); 13 | 14 | export default [Todo]; 15 | -------------------------------------------------------------------------------- /data/schema.json: -------------------------------------------------------------------------------- 1 | { 2 | "data": { 3 | "__schema": { 4 | "queryType": { 5 | "name": "RootQuery" 6 | }, 7 | "mutationType": { 8 | "name": "RootMutation" 9 | }, 10 | "subscriptionType": null, 11 | "types": [ 12 | { 13 | "kind": "OBJECT", 14 | "name": "RootQuery", 15 | "description": null, 16 | "fields": [ 17 | { 18 | "name": "viewer", 19 | "description": null, 20 | "args": [], 21 | "type": { 22 | "kind": "OBJECT", 23 | "name": "Viewer", 24 | "ofType": null 25 | }, 26 | "isDeprecated": false, 27 | "deprecationReason": null 28 | }, 29 | { 30 | "name": "node", 31 | "description": "Fetches an object given its ID", 32 | "args": [ 33 | { 34 | "name": "id", 35 | "description": "The ID of an object", 36 | "type": { 37 | "kind": "NON_NULL", 38 | "name": null, 39 | "ofType": { 40 | "kind": "SCALAR", 41 | "name": "ID", 42 | "ofType": null 43 | } 44 | }, 45 | "defaultValue": null 46 | } 47 | ], 48 | "type": { 49 | "kind": "INTERFACE", 50 | "name": "Node", 51 | "ofType": null 52 | }, 53 | "isDeprecated": false, 54 | "deprecationReason": null 55 | }, 56 | { 57 | "name": "todo", 58 | "description": null, 59 | "args": [ 60 | { 61 | "name": "id", 62 | "description": null, 63 | "type": { 64 | "kind": "NON_NULL", 65 | "name": null, 66 | "ofType": { 67 | "kind": "SCALAR", 68 | "name": "ID", 69 | "ofType": null 70 | } 71 | }, 72 | "defaultValue": null 73 | } 74 | ], 75 | "type": { 76 | "kind": "OBJECT", 77 | "name": "Todo", 78 | "ofType": null 79 | }, 80 | "isDeprecated": false, 81 | "deprecationReason": null 82 | }, 83 | { 84 | "name": "todos", 85 | "description": null, 86 | "args": [ 87 | { 88 | "name": "id", 89 | "description": "The ID of a Todo", 90 | "type": { 91 | "kind": "LIST", 92 | "name": null, 93 | "ofType": { 94 | "kind": "SCALAR", 95 | "name": "ID", 96 | "ofType": null 97 | } 98 | }, 99 | "defaultValue": null 100 | }, 101 | { 102 | "name": "ids", 103 | "description": "The ID of a Todo", 104 | "type": { 105 | "kind": "LIST", 106 | "name": null, 107 | "ofType": { 108 | "kind": "SCALAR", 109 | "name": "ID", 110 | "ofType": null 111 | } 112 | }, 113 | "defaultValue": null 114 | }, 115 | { 116 | "name": "orderBy", 117 | "description": null, 118 | "type": { 119 | "kind": "ENUM", 120 | "name": "orderByTodo", 121 | "ofType": null 122 | }, 123 | "defaultValue": null 124 | }, 125 | { 126 | "name": "text", 127 | "description": null, 128 | "type": { 129 | "kind": "SCALAR", 130 | "name": "String", 131 | "ofType": null 132 | }, 133 | "defaultValue": null 134 | }, 135 | { 136 | "name": "complete", 137 | "description": null, 138 | "type": { 139 | "kind": "SCALAR", 140 | "name": "Boolean", 141 | "ofType": null 142 | }, 143 | "defaultValue": null 144 | }, 145 | { 146 | "name": "_id", 147 | "description": null, 148 | "type": { 149 | "kind": "SCALAR", 150 | "name": "ID", 151 | "ofType": null 152 | }, 153 | "defaultValue": null 154 | } 155 | ], 156 | "type": { 157 | "kind": "LIST", 158 | "name": null, 159 | "ofType": { 160 | "kind": "OBJECT", 161 | "name": "Todo", 162 | "ofType": null 163 | } 164 | }, 165 | "isDeprecated": false, 166 | "deprecationReason": null 167 | } 168 | ], 169 | "inputFields": null, 170 | "interfaces": [], 171 | "enumValues": null, 172 | "possibleTypes": null 173 | }, 174 | { 175 | "kind": "OBJECT", 176 | "name": "Viewer", 177 | "description": null, 178 | "fields": [ 179 | { 180 | "name": "id", 181 | "description": "The ID of an object", 182 | "args": [], 183 | "type": { 184 | "kind": "NON_NULL", 185 | "name": null, 186 | "ofType": { 187 | "kind": "SCALAR", 188 | "name": "ID", 189 | "ofType": null 190 | } 191 | }, 192 | "isDeprecated": false, 193 | "deprecationReason": null 194 | }, 195 | { 196 | "name": "todos", 197 | "description": null, 198 | "args": [ 199 | { 200 | "name": "after", 201 | "description": null, 202 | "type": { 203 | "kind": "SCALAR", 204 | "name": "String", 205 | "ofType": null 206 | }, 207 | "defaultValue": null 208 | }, 209 | { 210 | "name": "first", 211 | "description": null, 212 | "type": { 213 | "kind": "SCALAR", 214 | "name": "Int", 215 | "ofType": null 216 | }, 217 | "defaultValue": null 218 | }, 219 | { 220 | "name": "before", 221 | "description": null, 222 | "type": { 223 | "kind": "SCALAR", 224 | "name": "String", 225 | "ofType": null 226 | }, 227 | "defaultValue": null 228 | }, 229 | { 230 | "name": "last", 231 | "description": null, 232 | "type": { 233 | "kind": "SCALAR", 234 | "name": "Int", 235 | "ofType": null 236 | }, 237 | "defaultValue": null 238 | }, 239 | { 240 | "name": "orderBy", 241 | "description": null, 242 | "type": { 243 | "kind": "ENUM", 244 | "name": "orderByTodo", 245 | "ofType": null 246 | }, 247 | "defaultValue": null 248 | }, 249 | { 250 | "name": "text", 251 | "description": null, 252 | "type": { 253 | "kind": "SCALAR", 254 | "name": "String", 255 | "ofType": null 256 | }, 257 | "defaultValue": null 258 | }, 259 | { 260 | "name": "complete", 261 | "description": null, 262 | "type": { 263 | "kind": "SCALAR", 264 | "name": "Boolean", 265 | "ofType": null 266 | }, 267 | "defaultValue": null 268 | }, 269 | { 270 | "name": "_id", 271 | "description": null, 272 | "type": { 273 | "kind": "SCALAR", 274 | "name": "ID", 275 | "ofType": null 276 | }, 277 | "defaultValue": null 278 | } 279 | ], 280 | "type": { 281 | "kind": "OBJECT", 282 | "name": "TodoConnection", 283 | "ofType": null 284 | }, 285 | "isDeprecated": false, 286 | "deprecationReason": null 287 | }, 288 | { 289 | "name": "todo", 290 | "description": null, 291 | "args": [ 292 | { 293 | "name": "id", 294 | "description": null, 295 | "type": { 296 | "kind": "NON_NULL", 297 | "name": null, 298 | "ofType": { 299 | "kind": "SCALAR", 300 | "name": "ID", 301 | "ofType": null 302 | } 303 | }, 304 | "defaultValue": null 305 | } 306 | ], 307 | "type": { 308 | "kind": "OBJECT", 309 | "name": "Todo", 310 | "ofType": null 311 | }, 312 | "isDeprecated": false, 313 | "deprecationReason": null 314 | } 315 | ], 316 | "inputFields": null, 317 | "interfaces": [ 318 | { 319 | "kind": "INTERFACE", 320 | "name": "Node", 321 | "ofType": null 322 | } 323 | ], 324 | "enumValues": null, 325 | "possibleTypes": null 326 | }, 327 | { 328 | "kind": "INTERFACE", 329 | "name": "Node", 330 | "description": "An object with an ID", 331 | "fields": [ 332 | { 333 | "name": "id", 334 | "description": "The id of the object.", 335 | "args": [], 336 | "type": { 337 | "kind": "NON_NULL", 338 | "name": null, 339 | "ofType": { 340 | "kind": "SCALAR", 341 | "name": "ID", 342 | "ofType": null 343 | } 344 | }, 345 | "isDeprecated": false, 346 | "deprecationReason": null 347 | } 348 | ], 349 | "inputFields": null, 350 | "interfaces": null, 351 | "enumValues": null, 352 | "possibleTypes": [ 353 | { 354 | "kind": "OBJECT", 355 | "name": "Viewer", 356 | "ofType": null 357 | }, 358 | { 359 | "kind": "OBJECT", 360 | "name": "Todo", 361 | "ofType": null 362 | } 363 | ] 364 | }, 365 | { 366 | "kind": "OBJECT", 367 | "name": "Todo", 368 | "description": null, 369 | "fields": [ 370 | { 371 | "name": "text", 372 | "description": null, 373 | "args": [], 374 | "type": { 375 | "kind": "SCALAR", 376 | "name": "String", 377 | "ofType": null 378 | }, 379 | "isDeprecated": false, 380 | "deprecationReason": null 381 | }, 382 | { 383 | "name": "complete", 384 | "description": null, 385 | "args": [], 386 | "type": { 387 | "kind": "SCALAR", 388 | "name": "Boolean", 389 | "ofType": null 390 | }, 391 | "isDeprecated": false, 392 | "deprecationReason": null 393 | }, 394 | { 395 | "name": "_id", 396 | "description": null, 397 | "args": [], 398 | "type": { 399 | "kind": "SCALAR", 400 | "name": "ID", 401 | "ofType": null 402 | }, 403 | "isDeprecated": false, 404 | "deprecationReason": null 405 | }, 406 | { 407 | "name": "id", 408 | "description": "The ID of an object", 409 | "args": [], 410 | "type": { 411 | "kind": "NON_NULL", 412 | "name": null, 413 | "ofType": { 414 | "kind": "SCALAR", 415 | "name": "ID", 416 | "ofType": null 417 | } 418 | }, 419 | "isDeprecated": false, 420 | "deprecationReason": null 421 | } 422 | ], 423 | "inputFields": null, 424 | "interfaces": [ 425 | { 426 | "kind": "INTERFACE", 427 | "name": "Node", 428 | "ofType": null 429 | } 430 | ], 431 | "enumValues": null, 432 | "possibleTypes": null 433 | }, 434 | { 435 | "kind": "SCALAR", 436 | "name": "String", 437 | "description": "The `String` scalar type represents textual data, represented as UTF-8 character sequences. The String type is most often used by GraphQL to represent free-form human-readable text.", 438 | "fields": null, 439 | "inputFields": null, 440 | "interfaces": null, 441 | "enumValues": null, 442 | "possibleTypes": null 443 | }, 444 | { 445 | "kind": "SCALAR", 446 | "name": "Boolean", 447 | "description": "The `Boolean` scalar type represents `true` or `false`.", 448 | "fields": null, 449 | "inputFields": null, 450 | "interfaces": null, 451 | "enumValues": null, 452 | "possibleTypes": null 453 | }, 454 | { 455 | "kind": "SCALAR", 456 | "name": "ID", 457 | "description": "The `ID` scalar type represents a unique identifier, often used to refetch an object or as key for a cache. The ID type appears in a JSON response as a String; however, it is not intended to be human-readable. When expected as an input type, any string (such as `\"4\"`) or integer (such as `4`) input value will be accepted as an ID.", 458 | "fields": null, 459 | "inputFields": null, 460 | "interfaces": null, 461 | "enumValues": null, 462 | "possibleTypes": null 463 | }, 464 | { 465 | "kind": "SCALAR", 466 | "name": "Int", 467 | "description": "The `Int` scalar type represents non-fractional signed whole numeric values. Int can represent values between -(2^53 - 1) and 2^53 - 1 since represented in JSON as double-precision floating point numbers specifiedby [IEEE 754](http://en.wikipedia.org/wiki/IEEE_floating_point).", 468 | "fields": null, 469 | "inputFields": null, 470 | "interfaces": null, 471 | "enumValues": null, 472 | "possibleTypes": null 473 | }, 474 | { 475 | "kind": "ENUM", 476 | "name": "orderByTodo", 477 | "description": null, 478 | "fields": null, 479 | "inputFields": null, 480 | "interfaces": null, 481 | "enumValues": [ 482 | { 483 | "name": "TEXT_ASC", 484 | "description": null, 485 | "isDeprecated": false, 486 | "deprecationReason": null 487 | }, 488 | { 489 | "name": "TEXT_DESC", 490 | "description": null, 491 | "isDeprecated": false, 492 | "deprecationReason": null 493 | }, 494 | { 495 | "name": "COMPLETE_ASC", 496 | "description": null, 497 | "isDeprecated": false, 498 | "deprecationReason": null 499 | }, 500 | { 501 | "name": "COMPLETE_DESC", 502 | "description": null, 503 | "isDeprecated": false, 504 | "deprecationReason": null 505 | }, 506 | { 507 | "name": "_ID_ASC", 508 | "description": null, 509 | "isDeprecated": false, 510 | "deprecationReason": null 511 | }, 512 | { 513 | "name": "_ID_DESC", 514 | "description": null, 515 | "isDeprecated": false, 516 | "deprecationReason": null 517 | } 518 | ], 519 | "possibleTypes": null 520 | }, 521 | { 522 | "kind": "OBJECT", 523 | "name": "TodoConnection", 524 | "description": "A connection to a list of items.", 525 | "fields": [ 526 | { 527 | "name": "pageInfo", 528 | "description": "Information to aid in pagination.", 529 | "args": [], 530 | "type": { 531 | "kind": "NON_NULL", 532 | "name": null, 533 | "ofType": { 534 | "kind": "OBJECT", 535 | "name": "PageInfo", 536 | "ofType": null 537 | } 538 | }, 539 | "isDeprecated": false, 540 | "deprecationReason": null 541 | }, 542 | { 543 | "name": "edges", 544 | "description": "Information to aid in pagination.", 545 | "args": [], 546 | "type": { 547 | "kind": "LIST", 548 | "name": null, 549 | "ofType": { 550 | "kind": "OBJECT", 551 | "name": "TodoEdge", 552 | "ofType": null 553 | } 554 | }, 555 | "isDeprecated": false, 556 | "deprecationReason": null 557 | }, 558 | { 559 | "name": "count", 560 | "description": null, 561 | "args": [], 562 | "type": { 563 | "kind": "SCALAR", 564 | "name": "Float", 565 | "ofType": null 566 | }, 567 | "isDeprecated": false, 568 | "deprecationReason": null 569 | } 570 | ], 571 | "inputFields": null, 572 | "interfaces": [], 573 | "enumValues": null, 574 | "possibleTypes": null 575 | }, 576 | { 577 | "kind": "OBJECT", 578 | "name": "PageInfo", 579 | "description": "Information about pagination in a connection.", 580 | "fields": [ 581 | { 582 | "name": "hasNextPage", 583 | "description": "When paginating forwards, are there more items?", 584 | "args": [], 585 | "type": { 586 | "kind": "NON_NULL", 587 | "name": null, 588 | "ofType": { 589 | "kind": "SCALAR", 590 | "name": "Boolean", 591 | "ofType": null 592 | } 593 | }, 594 | "isDeprecated": false, 595 | "deprecationReason": null 596 | }, 597 | { 598 | "name": "hasPreviousPage", 599 | "description": "When paginating backwards, are there more items?", 600 | "args": [], 601 | "type": { 602 | "kind": "NON_NULL", 603 | "name": null, 604 | "ofType": { 605 | "kind": "SCALAR", 606 | "name": "Boolean", 607 | "ofType": null 608 | } 609 | }, 610 | "isDeprecated": false, 611 | "deprecationReason": null 612 | }, 613 | { 614 | "name": "startCursor", 615 | "description": "When paginating backwards, the cursor to continue.", 616 | "args": [], 617 | "type": { 618 | "kind": "SCALAR", 619 | "name": "String", 620 | "ofType": null 621 | }, 622 | "isDeprecated": false, 623 | "deprecationReason": null 624 | }, 625 | { 626 | "name": "endCursor", 627 | "description": "When paginating forwards, the cursor to continue.", 628 | "args": [], 629 | "type": { 630 | "kind": "SCALAR", 631 | "name": "String", 632 | "ofType": null 633 | }, 634 | "isDeprecated": false, 635 | "deprecationReason": null 636 | } 637 | ], 638 | "inputFields": null, 639 | "interfaces": [], 640 | "enumValues": null, 641 | "possibleTypes": null 642 | }, 643 | { 644 | "kind": "OBJECT", 645 | "name": "TodoEdge", 646 | "description": "An edge in a connection.", 647 | "fields": [ 648 | { 649 | "name": "node", 650 | "description": "The item at the end of the edge", 651 | "args": [], 652 | "type": { 653 | "kind": "OBJECT", 654 | "name": "Todo", 655 | "ofType": null 656 | }, 657 | "isDeprecated": false, 658 | "deprecationReason": null 659 | }, 660 | { 661 | "name": "cursor", 662 | "description": "A cursor for use in pagination", 663 | "args": [], 664 | "type": { 665 | "kind": "NON_NULL", 666 | "name": null, 667 | "ofType": { 668 | "kind": "SCALAR", 669 | "name": "String", 670 | "ofType": null 671 | } 672 | }, 673 | "isDeprecated": false, 674 | "deprecationReason": null 675 | } 676 | ], 677 | "inputFields": null, 678 | "interfaces": [], 679 | "enumValues": null, 680 | "possibleTypes": null 681 | }, 682 | { 683 | "kind": "SCALAR", 684 | "name": "Float", 685 | "description": "The `Float` scalar type represents signed double-precision fractional values as specified by [IEEE 754](http://en.wikipedia.org/wiki/IEEE_floating_point). ", 686 | "fields": null, 687 | "inputFields": null, 688 | "interfaces": null, 689 | "enumValues": null, 690 | "possibleTypes": null 691 | }, 692 | { 693 | "kind": "OBJECT", 694 | "name": "RootMutation", 695 | "description": null, 696 | "fields": [ 697 | { 698 | "name": "addTodo", 699 | "description": null, 700 | "args": [ 701 | { 702 | "name": "input", 703 | "description": null, 704 | "type": { 705 | "kind": "NON_NULL", 706 | "name": null, 707 | "ofType": { 708 | "kind": "INPUT_OBJECT", 709 | "name": "addTodoInput", 710 | "ofType": null 711 | } 712 | }, 713 | "defaultValue": null 714 | } 715 | ], 716 | "type": { 717 | "kind": "OBJECT", 718 | "name": "addTodoPayload", 719 | "ofType": null 720 | }, 721 | "isDeprecated": false, 722 | "deprecationReason": null 723 | }, 724 | { 725 | "name": "updateTodo", 726 | "description": null, 727 | "args": [ 728 | { 729 | "name": "input", 730 | "description": null, 731 | "type": { 732 | "kind": "NON_NULL", 733 | "name": null, 734 | "ofType": { 735 | "kind": "INPUT_OBJECT", 736 | "name": "updateTodoInput", 737 | "ofType": null 738 | } 739 | }, 740 | "defaultValue": null 741 | } 742 | ], 743 | "type": { 744 | "kind": "OBJECT", 745 | "name": "updateTodoPayload", 746 | "ofType": null 747 | }, 748 | "isDeprecated": false, 749 | "deprecationReason": null 750 | }, 751 | { 752 | "name": "deleteTodo", 753 | "description": null, 754 | "args": [ 755 | { 756 | "name": "input", 757 | "description": null, 758 | "type": { 759 | "kind": "NON_NULL", 760 | "name": null, 761 | "ofType": { 762 | "kind": "INPUT_OBJECT", 763 | "name": "deleteTodoInput", 764 | "ofType": null 765 | } 766 | }, 767 | "defaultValue": null 768 | } 769 | ], 770 | "type": { 771 | "kind": "OBJECT", 772 | "name": "deleteTodoPayload", 773 | "ofType": null 774 | }, 775 | "isDeprecated": false, 776 | "deprecationReason": null 777 | } 778 | ], 779 | "inputFields": null, 780 | "interfaces": [], 781 | "enumValues": null, 782 | "possibleTypes": null 783 | }, 784 | { 785 | "kind": "INPUT_OBJECT", 786 | "name": "addTodoInput", 787 | "description": null, 788 | "fields": null, 789 | "inputFields": [ 790 | { 791 | "name": "text", 792 | "description": null, 793 | "type": { 794 | "kind": "SCALAR", 795 | "name": "String", 796 | "ofType": null 797 | }, 798 | "defaultValue": null 799 | }, 800 | { 801 | "name": "complete", 802 | "description": null, 803 | "type": { 804 | "kind": "SCALAR", 805 | "name": "Boolean", 806 | "ofType": null 807 | }, 808 | "defaultValue": null 809 | }, 810 | { 811 | "name": "clientMutationId", 812 | "description": null, 813 | "type": { 814 | "kind": "NON_NULL", 815 | "name": null, 816 | "ofType": { 817 | "kind": "SCALAR", 818 | "name": "String", 819 | "ofType": null 820 | } 821 | }, 822 | "defaultValue": null 823 | } 824 | ], 825 | "interfaces": null, 826 | "enumValues": null, 827 | "possibleTypes": null 828 | }, 829 | { 830 | "kind": "OBJECT", 831 | "name": "addTodoPayload", 832 | "description": null, 833 | "fields": [ 834 | { 835 | "name": "viewer", 836 | "description": null, 837 | "args": [], 838 | "type": { 839 | "kind": "OBJECT", 840 | "name": "Viewer", 841 | "ofType": null 842 | }, 843 | "isDeprecated": false, 844 | "deprecationReason": null 845 | }, 846 | { 847 | "name": "changedTodoEdge", 848 | "description": null, 849 | "args": [], 850 | "type": { 851 | "kind": "OBJECT", 852 | "name": "changedTodoEdge", 853 | "ofType": null 854 | }, 855 | "isDeprecated": false, 856 | "deprecationReason": null 857 | }, 858 | { 859 | "name": "clientMutationId", 860 | "description": null, 861 | "args": [], 862 | "type": { 863 | "kind": "NON_NULL", 864 | "name": null, 865 | "ofType": { 866 | "kind": "SCALAR", 867 | "name": "String", 868 | "ofType": null 869 | } 870 | }, 871 | "isDeprecated": false, 872 | "deprecationReason": null 873 | } 874 | ], 875 | "inputFields": null, 876 | "interfaces": [], 877 | "enumValues": null, 878 | "possibleTypes": null 879 | }, 880 | { 881 | "kind": "OBJECT", 882 | "name": "changedTodoEdge", 883 | "description": "An edge in a connection.", 884 | "fields": [ 885 | { 886 | "name": "node", 887 | "description": "The item at the end of the edge", 888 | "args": [], 889 | "type": { 890 | "kind": "OBJECT", 891 | "name": "changedTodoNode", 892 | "ofType": null 893 | }, 894 | "isDeprecated": false, 895 | "deprecationReason": null 896 | }, 897 | { 898 | "name": "cursor", 899 | "description": "A cursor for use in pagination", 900 | "args": [], 901 | "type": { 902 | "kind": "NON_NULL", 903 | "name": null, 904 | "ofType": { 905 | "kind": "SCALAR", 906 | "name": "String", 907 | "ofType": null 908 | } 909 | }, 910 | "isDeprecated": false, 911 | "deprecationReason": null 912 | } 913 | ], 914 | "inputFields": null, 915 | "interfaces": [], 916 | "enumValues": null, 917 | "possibleTypes": null 918 | }, 919 | { 920 | "kind": "OBJECT", 921 | "name": "changedTodoNode", 922 | "description": null, 923 | "fields": [ 924 | { 925 | "name": "text", 926 | "description": null, 927 | "args": [], 928 | "type": { 929 | "kind": "SCALAR", 930 | "name": "String", 931 | "ofType": null 932 | }, 933 | "isDeprecated": false, 934 | "deprecationReason": null 935 | }, 936 | { 937 | "name": "complete", 938 | "description": null, 939 | "args": [], 940 | "type": { 941 | "kind": "SCALAR", 942 | "name": "Boolean", 943 | "ofType": null 944 | }, 945 | "isDeprecated": false, 946 | "deprecationReason": null 947 | }, 948 | { 949 | "name": "_id", 950 | "description": null, 951 | "args": [], 952 | "type": { 953 | "kind": "SCALAR", 954 | "name": "ID", 955 | "ofType": null 956 | }, 957 | "isDeprecated": false, 958 | "deprecationReason": null 959 | }, 960 | { 961 | "name": "id", 962 | "description": "The ID of an object", 963 | "args": [], 964 | "type": { 965 | "kind": "NON_NULL", 966 | "name": null, 967 | "ofType": { 968 | "kind": "SCALAR", 969 | "name": "ID", 970 | "ofType": null 971 | } 972 | }, 973 | "isDeprecated": false, 974 | "deprecationReason": null 975 | } 976 | ], 977 | "inputFields": null, 978 | "interfaces": [], 979 | "enumValues": null, 980 | "possibleTypes": null 981 | }, 982 | { 983 | "kind": "INPUT_OBJECT", 984 | "name": "updateTodoInput", 985 | "description": null, 986 | "fields": null, 987 | "inputFields": [ 988 | { 989 | "name": "text", 990 | "description": null, 991 | "type": { 992 | "kind": "SCALAR", 993 | "name": "String", 994 | "ofType": null 995 | }, 996 | "defaultValue": null 997 | }, 998 | { 999 | "name": "complete", 1000 | "description": null, 1001 | "type": { 1002 | "kind": "SCALAR", 1003 | "name": "Boolean", 1004 | "ofType": null 1005 | }, 1006 | "defaultValue": null 1007 | }, 1008 | { 1009 | "name": "id", 1010 | "description": null, 1011 | "type": { 1012 | "kind": "NON_NULL", 1013 | "name": null, 1014 | "ofType": { 1015 | "kind": "SCALAR", 1016 | "name": "ID", 1017 | "ofType": null 1018 | } 1019 | }, 1020 | "defaultValue": null 1021 | }, 1022 | { 1023 | "name": "clientMutationId", 1024 | "description": null, 1025 | "type": { 1026 | "kind": "NON_NULL", 1027 | "name": null, 1028 | "ofType": { 1029 | "kind": "SCALAR", 1030 | "name": "String", 1031 | "ofType": null 1032 | } 1033 | }, 1034 | "defaultValue": null 1035 | } 1036 | ], 1037 | "interfaces": null, 1038 | "enumValues": null, 1039 | "possibleTypes": null 1040 | }, 1041 | { 1042 | "kind": "OBJECT", 1043 | "name": "updateTodoPayload", 1044 | "description": null, 1045 | "fields": [ 1046 | { 1047 | "name": "changedTodo", 1048 | "description": null, 1049 | "args": [], 1050 | "type": { 1051 | "kind": "OBJECT", 1052 | "name": "Todo", 1053 | "ofType": null 1054 | }, 1055 | "isDeprecated": false, 1056 | "deprecationReason": null 1057 | }, 1058 | { 1059 | "name": "clientMutationId", 1060 | "description": null, 1061 | "args": [], 1062 | "type": { 1063 | "kind": "NON_NULL", 1064 | "name": null, 1065 | "ofType": { 1066 | "kind": "SCALAR", 1067 | "name": "String", 1068 | "ofType": null 1069 | } 1070 | }, 1071 | "isDeprecated": false, 1072 | "deprecationReason": null 1073 | } 1074 | ], 1075 | "inputFields": null, 1076 | "interfaces": [], 1077 | "enumValues": null, 1078 | "possibleTypes": null 1079 | }, 1080 | { 1081 | "kind": "INPUT_OBJECT", 1082 | "name": "deleteTodoInput", 1083 | "description": null, 1084 | "fields": null, 1085 | "inputFields": [ 1086 | { 1087 | "name": "id", 1088 | "description": null, 1089 | "type": { 1090 | "kind": "NON_NULL", 1091 | "name": null, 1092 | "ofType": { 1093 | "kind": "SCALAR", 1094 | "name": "ID", 1095 | "ofType": null 1096 | } 1097 | }, 1098 | "defaultValue": null 1099 | }, 1100 | { 1101 | "name": "clientMutationId", 1102 | "description": null, 1103 | "type": { 1104 | "kind": "NON_NULL", 1105 | "name": null, 1106 | "ofType": { 1107 | "kind": "SCALAR", 1108 | "name": "String", 1109 | "ofType": null 1110 | } 1111 | }, 1112 | "defaultValue": null 1113 | } 1114 | ], 1115 | "interfaces": null, 1116 | "enumValues": null, 1117 | "possibleTypes": null 1118 | }, 1119 | { 1120 | "kind": "OBJECT", 1121 | "name": "deleteTodoPayload", 1122 | "description": null, 1123 | "fields": [ 1124 | { 1125 | "name": "viewer", 1126 | "description": null, 1127 | "args": [], 1128 | "type": { 1129 | "kind": "OBJECT", 1130 | "name": "Viewer", 1131 | "ofType": null 1132 | }, 1133 | "isDeprecated": false, 1134 | "deprecationReason": null 1135 | }, 1136 | { 1137 | "name": "ok", 1138 | "description": null, 1139 | "args": [], 1140 | "type": { 1141 | "kind": "SCALAR", 1142 | "name": "Boolean", 1143 | "ofType": null 1144 | }, 1145 | "isDeprecated": false, 1146 | "deprecationReason": null 1147 | }, 1148 | { 1149 | "name": "id", 1150 | "description": null, 1151 | "args": [], 1152 | "type": { 1153 | "kind": "NON_NULL", 1154 | "name": null, 1155 | "ofType": { 1156 | "kind": "SCALAR", 1157 | "name": "ID", 1158 | "ofType": null 1159 | } 1160 | }, 1161 | "isDeprecated": false, 1162 | "deprecationReason": null 1163 | }, 1164 | { 1165 | "name": "clientMutationId", 1166 | "description": null, 1167 | "args": [], 1168 | "type": { 1169 | "kind": "NON_NULL", 1170 | "name": null, 1171 | "ofType": { 1172 | "kind": "SCALAR", 1173 | "name": "String", 1174 | "ofType": null 1175 | } 1176 | }, 1177 | "isDeprecated": false, 1178 | "deprecationReason": null 1179 | } 1180 | ], 1181 | "inputFields": null, 1182 | "interfaces": [], 1183 | "enumValues": null, 1184 | "possibleTypes": null 1185 | }, 1186 | { 1187 | "kind": "OBJECT", 1188 | "name": "__Schema", 1189 | "description": "A GraphQL Schema defines the capabilities of a GraphQL server. It exposes all available types and directives on the server, as well as the entry points for query, mutation, and subscription operations.", 1190 | "fields": [ 1191 | { 1192 | "name": "types", 1193 | "description": "A list of all types supported by this server.", 1194 | "args": [], 1195 | "type": { 1196 | "kind": "NON_NULL", 1197 | "name": null, 1198 | "ofType": { 1199 | "kind": "LIST", 1200 | "name": null, 1201 | "ofType": { 1202 | "kind": "NON_NULL", 1203 | "name": null, 1204 | "ofType": { 1205 | "kind": "OBJECT", 1206 | "name": "__Type" 1207 | } 1208 | } 1209 | } 1210 | }, 1211 | "isDeprecated": false, 1212 | "deprecationReason": null 1213 | }, 1214 | { 1215 | "name": "queryType", 1216 | "description": "The type that query operations will be rooted at.", 1217 | "args": [], 1218 | "type": { 1219 | "kind": "NON_NULL", 1220 | "name": null, 1221 | "ofType": { 1222 | "kind": "OBJECT", 1223 | "name": "__Type", 1224 | "ofType": null 1225 | } 1226 | }, 1227 | "isDeprecated": false, 1228 | "deprecationReason": null 1229 | }, 1230 | { 1231 | "name": "mutationType", 1232 | "description": "If this server supports mutation, the type that mutation operations will be rooted at.", 1233 | "args": [], 1234 | "type": { 1235 | "kind": "OBJECT", 1236 | "name": "__Type", 1237 | "ofType": null 1238 | }, 1239 | "isDeprecated": false, 1240 | "deprecationReason": null 1241 | }, 1242 | { 1243 | "name": "subscriptionType", 1244 | "description": "If this server support subscription, the type that subscription operations will be rooted at.", 1245 | "args": [], 1246 | "type": { 1247 | "kind": "OBJECT", 1248 | "name": "__Type", 1249 | "ofType": null 1250 | }, 1251 | "isDeprecated": false, 1252 | "deprecationReason": null 1253 | }, 1254 | { 1255 | "name": "directives", 1256 | "description": "A list of all directives supported by this server.", 1257 | "args": [], 1258 | "type": { 1259 | "kind": "NON_NULL", 1260 | "name": null, 1261 | "ofType": { 1262 | "kind": "LIST", 1263 | "name": null, 1264 | "ofType": { 1265 | "kind": "NON_NULL", 1266 | "name": null, 1267 | "ofType": { 1268 | "kind": "OBJECT", 1269 | "name": "__Directive" 1270 | } 1271 | } 1272 | } 1273 | }, 1274 | "isDeprecated": false, 1275 | "deprecationReason": null 1276 | } 1277 | ], 1278 | "inputFields": null, 1279 | "interfaces": [], 1280 | "enumValues": null, 1281 | "possibleTypes": null 1282 | }, 1283 | { 1284 | "kind": "OBJECT", 1285 | "name": "__Type", 1286 | "description": "The fundamental unit of any GraphQL Schema is the type. There are many kinds of types in GraphQL as represented by the `__TypeKind` enum.\n\nDepending on the kind of a type, certain fields describe information about that type. Scalar types provide no information beyond a name and description, while Enum types provide their values. Object and Interface types provide the fields they describe. Abstract types, Union and Interface, provide the Object types possible at runtime. List and NonNull types compose other types.", 1287 | "fields": [ 1288 | { 1289 | "name": "kind", 1290 | "description": null, 1291 | "args": [], 1292 | "type": { 1293 | "kind": "NON_NULL", 1294 | "name": null, 1295 | "ofType": { 1296 | "kind": "ENUM", 1297 | "name": "__TypeKind", 1298 | "ofType": null 1299 | } 1300 | }, 1301 | "isDeprecated": false, 1302 | "deprecationReason": null 1303 | }, 1304 | { 1305 | "name": "name", 1306 | "description": null, 1307 | "args": [], 1308 | "type": { 1309 | "kind": "SCALAR", 1310 | "name": "String", 1311 | "ofType": null 1312 | }, 1313 | "isDeprecated": false, 1314 | "deprecationReason": null 1315 | }, 1316 | { 1317 | "name": "description", 1318 | "description": null, 1319 | "args": [], 1320 | "type": { 1321 | "kind": "SCALAR", 1322 | "name": "String", 1323 | "ofType": null 1324 | }, 1325 | "isDeprecated": false, 1326 | "deprecationReason": null 1327 | }, 1328 | { 1329 | "name": "fields", 1330 | "description": null, 1331 | "args": [ 1332 | { 1333 | "name": "includeDeprecated", 1334 | "description": null, 1335 | "type": { 1336 | "kind": "SCALAR", 1337 | "name": "Boolean", 1338 | "ofType": null 1339 | }, 1340 | "defaultValue": "false" 1341 | } 1342 | ], 1343 | "type": { 1344 | "kind": "LIST", 1345 | "name": null, 1346 | "ofType": { 1347 | "kind": "NON_NULL", 1348 | "name": null, 1349 | "ofType": { 1350 | "kind": "OBJECT", 1351 | "name": "__Field", 1352 | "ofType": null 1353 | } 1354 | } 1355 | }, 1356 | "isDeprecated": false, 1357 | "deprecationReason": null 1358 | }, 1359 | { 1360 | "name": "interfaces", 1361 | "description": null, 1362 | "args": [], 1363 | "type": { 1364 | "kind": "LIST", 1365 | "name": null, 1366 | "ofType": { 1367 | "kind": "NON_NULL", 1368 | "name": null, 1369 | "ofType": { 1370 | "kind": "OBJECT", 1371 | "name": "__Type", 1372 | "ofType": null 1373 | } 1374 | } 1375 | }, 1376 | "isDeprecated": false, 1377 | "deprecationReason": null 1378 | }, 1379 | { 1380 | "name": "possibleTypes", 1381 | "description": null, 1382 | "args": [], 1383 | "type": { 1384 | "kind": "LIST", 1385 | "name": null, 1386 | "ofType": { 1387 | "kind": "NON_NULL", 1388 | "name": null, 1389 | "ofType": { 1390 | "kind": "OBJECT", 1391 | "name": "__Type", 1392 | "ofType": null 1393 | } 1394 | } 1395 | }, 1396 | "isDeprecated": false, 1397 | "deprecationReason": null 1398 | }, 1399 | { 1400 | "name": "enumValues", 1401 | "description": null, 1402 | "args": [ 1403 | { 1404 | "name": "includeDeprecated", 1405 | "description": null, 1406 | "type": { 1407 | "kind": "SCALAR", 1408 | "name": "Boolean", 1409 | "ofType": null 1410 | }, 1411 | "defaultValue": "false" 1412 | } 1413 | ], 1414 | "type": { 1415 | "kind": "LIST", 1416 | "name": null, 1417 | "ofType": { 1418 | "kind": "NON_NULL", 1419 | "name": null, 1420 | "ofType": { 1421 | "kind": "OBJECT", 1422 | "name": "__EnumValue", 1423 | "ofType": null 1424 | } 1425 | } 1426 | }, 1427 | "isDeprecated": false, 1428 | "deprecationReason": null 1429 | }, 1430 | { 1431 | "name": "inputFields", 1432 | "description": null, 1433 | "args": [], 1434 | "type": { 1435 | "kind": "LIST", 1436 | "name": null, 1437 | "ofType": { 1438 | "kind": "NON_NULL", 1439 | "name": null, 1440 | "ofType": { 1441 | "kind": "OBJECT", 1442 | "name": "__InputValue", 1443 | "ofType": null 1444 | } 1445 | } 1446 | }, 1447 | "isDeprecated": false, 1448 | "deprecationReason": null 1449 | }, 1450 | { 1451 | "name": "ofType", 1452 | "description": null, 1453 | "args": [], 1454 | "type": { 1455 | "kind": "OBJECT", 1456 | "name": "__Type", 1457 | "ofType": null 1458 | }, 1459 | "isDeprecated": false, 1460 | "deprecationReason": null 1461 | } 1462 | ], 1463 | "inputFields": null, 1464 | "interfaces": [], 1465 | "enumValues": null, 1466 | "possibleTypes": null 1467 | }, 1468 | { 1469 | "kind": "ENUM", 1470 | "name": "__TypeKind", 1471 | "description": "An enum describing what kind of type a given `__Type` is.", 1472 | "fields": null, 1473 | "inputFields": null, 1474 | "interfaces": null, 1475 | "enumValues": [ 1476 | { 1477 | "name": "SCALAR", 1478 | "description": "Indicates this type is a scalar.", 1479 | "isDeprecated": false, 1480 | "deprecationReason": null 1481 | }, 1482 | { 1483 | "name": "OBJECT", 1484 | "description": "Indicates this type is an object. `fields` and `interfaces` are valid fields.", 1485 | "isDeprecated": false, 1486 | "deprecationReason": null 1487 | }, 1488 | { 1489 | "name": "INTERFACE", 1490 | "description": "Indicates this type is an interface. `fields` and `possibleTypes` are valid fields.", 1491 | "isDeprecated": false, 1492 | "deprecationReason": null 1493 | }, 1494 | { 1495 | "name": "UNION", 1496 | "description": "Indicates this type is a union. `possibleTypes` is a valid field.", 1497 | "isDeprecated": false, 1498 | "deprecationReason": null 1499 | }, 1500 | { 1501 | "name": "ENUM", 1502 | "description": "Indicates this type is an enum. `enumValues` is a valid field.", 1503 | "isDeprecated": false, 1504 | "deprecationReason": null 1505 | }, 1506 | { 1507 | "name": "INPUT_OBJECT", 1508 | "description": "Indicates this type is an input object. `inputFields` is a valid field.", 1509 | "isDeprecated": false, 1510 | "deprecationReason": null 1511 | }, 1512 | { 1513 | "name": "LIST", 1514 | "description": "Indicates this type is a list. `ofType` is a valid field.", 1515 | "isDeprecated": false, 1516 | "deprecationReason": null 1517 | }, 1518 | { 1519 | "name": "NON_NULL", 1520 | "description": "Indicates this type is a non-null. `ofType` is a valid field.", 1521 | "isDeprecated": false, 1522 | "deprecationReason": null 1523 | } 1524 | ], 1525 | "possibleTypes": null 1526 | }, 1527 | { 1528 | "kind": "OBJECT", 1529 | "name": "__Field", 1530 | "description": "Object and Interface types are described by a list of Fields, each of which has a name, potentially a list of arguments, and a return type.", 1531 | "fields": [ 1532 | { 1533 | "name": "name", 1534 | "description": null, 1535 | "args": [], 1536 | "type": { 1537 | "kind": "NON_NULL", 1538 | "name": null, 1539 | "ofType": { 1540 | "kind": "SCALAR", 1541 | "name": "String", 1542 | "ofType": null 1543 | } 1544 | }, 1545 | "isDeprecated": false, 1546 | "deprecationReason": null 1547 | }, 1548 | { 1549 | "name": "description", 1550 | "description": null, 1551 | "args": [], 1552 | "type": { 1553 | "kind": "SCALAR", 1554 | "name": "String", 1555 | "ofType": null 1556 | }, 1557 | "isDeprecated": false, 1558 | "deprecationReason": null 1559 | }, 1560 | { 1561 | "name": "args", 1562 | "description": null, 1563 | "args": [], 1564 | "type": { 1565 | "kind": "NON_NULL", 1566 | "name": null, 1567 | "ofType": { 1568 | "kind": "LIST", 1569 | "name": null, 1570 | "ofType": { 1571 | "kind": "NON_NULL", 1572 | "name": null, 1573 | "ofType": { 1574 | "kind": "OBJECT", 1575 | "name": "__InputValue" 1576 | } 1577 | } 1578 | } 1579 | }, 1580 | "isDeprecated": false, 1581 | "deprecationReason": null 1582 | }, 1583 | { 1584 | "name": "type", 1585 | "description": null, 1586 | "args": [], 1587 | "type": { 1588 | "kind": "NON_NULL", 1589 | "name": null, 1590 | "ofType": { 1591 | "kind": "OBJECT", 1592 | "name": "__Type", 1593 | "ofType": null 1594 | } 1595 | }, 1596 | "isDeprecated": false, 1597 | "deprecationReason": null 1598 | }, 1599 | { 1600 | "name": "isDeprecated", 1601 | "description": null, 1602 | "args": [], 1603 | "type": { 1604 | "kind": "NON_NULL", 1605 | "name": null, 1606 | "ofType": { 1607 | "kind": "SCALAR", 1608 | "name": "Boolean", 1609 | "ofType": null 1610 | } 1611 | }, 1612 | "isDeprecated": false, 1613 | "deprecationReason": null 1614 | }, 1615 | { 1616 | "name": "deprecationReason", 1617 | "description": null, 1618 | "args": [], 1619 | "type": { 1620 | "kind": "SCALAR", 1621 | "name": "String", 1622 | "ofType": null 1623 | }, 1624 | "isDeprecated": false, 1625 | "deprecationReason": null 1626 | } 1627 | ], 1628 | "inputFields": null, 1629 | "interfaces": [], 1630 | "enumValues": null, 1631 | "possibleTypes": null 1632 | }, 1633 | { 1634 | "kind": "OBJECT", 1635 | "name": "__InputValue", 1636 | "description": "Arguments provided to Fields or Directives and the input fields of an InputObject are represented as Input Values which describe their type and optionally a default value.", 1637 | "fields": [ 1638 | { 1639 | "name": "name", 1640 | "description": null, 1641 | "args": [], 1642 | "type": { 1643 | "kind": "NON_NULL", 1644 | "name": null, 1645 | "ofType": { 1646 | "kind": "SCALAR", 1647 | "name": "String", 1648 | "ofType": null 1649 | } 1650 | }, 1651 | "isDeprecated": false, 1652 | "deprecationReason": null 1653 | }, 1654 | { 1655 | "name": "description", 1656 | "description": null, 1657 | "args": [], 1658 | "type": { 1659 | "kind": "SCALAR", 1660 | "name": "String", 1661 | "ofType": null 1662 | }, 1663 | "isDeprecated": false, 1664 | "deprecationReason": null 1665 | }, 1666 | { 1667 | "name": "type", 1668 | "description": null, 1669 | "args": [], 1670 | "type": { 1671 | "kind": "NON_NULL", 1672 | "name": null, 1673 | "ofType": { 1674 | "kind": "OBJECT", 1675 | "name": "__Type", 1676 | "ofType": null 1677 | } 1678 | }, 1679 | "isDeprecated": false, 1680 | "deprecationReason": null 1681 | }, 1682 | { 1683 | "name": "defaultValue", 1684 | "description": "A GraphQL-formatted string representing the default value for this input value.", 1685 | "args": [], 1686 | "type": { 1687 | "kind": "SCALAR", 1688 | "name": "String", 1689 | "ofType": null 1690 | }, 1691 | "isDeprecated": false, 1692 | "deprecationReason": null 1693 | } 1694 | ], 1695 | "inputFields": null, 1696 | "interfaces": [], 1697 | "enumValues": null, 1698 | "possibleTypes": null 1699 | }, 1700 | { 1701 | "kind": "OBJECT", 1702 | "name": "__EnumValue", 1703 | "description": "One possible value for a given Enum. Enum values are unique values, not a placeholder for a string or numeric value. However an Enum value is returned in a JSON response as a string.", 1704 | "fields": [ 1705 | { 1706 | "name": "name", 1707 | "description": null, 1708 | "args": [], 1709 | "type": { 1710 | "kind": "NON_NULL", 1711 | "name": null, 1712 | "ofType": { 1713 | "kind": "SCALAR", 1714 | "name": "String", 1715 | "ofType": null 1716 | } 1717 | }, 1718 | "isDeprecated": false, 1719 | "deprecationReason": null 1720 | }, 1721 | { 1722 | "name": "description", 1723 | "description": null, 1724 | "args": [], 1725 | "type": { 1726 | "kind": "SCALAR", 1727 | "name": "String", 1728 | "ofType": null 1729 | }, 1730 | "isDeprecated": false, 1731 | "deprecationReason": null 1732 | }, 1733 | { 1734 | "name": "isDeprecated", 1735 | "description": null, 1736 | "args": [], 1737 | "type": { 1738 | "kind": "NON_NULL", 1739 | "name": null, 1740 | "ofType": { 1741 | "kind": "SCALAR", 1742 | "name": "Boolean", 1743 | "ofType": null 1744 | } 1745 | }, 1746 | "isDeprecated": false, 1747 | "deprecationReason": null 1748 | }, 1749 | { 1750 | "name": "deprecationReason", 1751 | "description": null, 1752 | "args": [], 1753 | "type": { 1754 | "kind": "SCALAR", 1755 | "name": "String", 1756 | "ofType": null 1757 | }, 1758 | "isDeprecated": false, 1759 | "deprecationReason": null 1760 | } 1761 | ], 1762 | "inputFields": null, 1763 | "interfaces": [], 1764 | "enumValues": null, 1765 | "possibleTypes": null 1766 | }, 1767 | { 1768 | "kind": "OBJECT", 1769 | "name": "__Directive", 1770 | "description": "A Directive provides a way to describe alternate runtime execution and type validation behavior in a GraphQL document.\n\nIn some cases, you need to provide options to alter GraphQL’s execution behavior in ways field arguments will not suffice, such as conditionally including or skipping a field. Directives provide this by describing additional information to the executor.", 1771 | "fields": [ 1772 | { 1773 | "name": "name", 1774 | "description": null, 1775 | "args": [], 1776 | "type": { 1777 | "kind": "NON_NULL", 1778 | "name": null, 1779 | "ofType": { 1780 | "kind": "SCALAR", 1781 | "name": "String", 1782 | "ofType": null 1783 | } 1784 | }, 1785 | "isDeprecated": false, 1786 | "deprecationReason": null 1787 | }, 1788 | { 1789 | "name": "description", 1790 | "description": null, 1791 | "args": [], 1792 | "type": { 1793 | "kind": "SCALAR", 1794 | "name": "String", 1795 | "ofType": null 1796 | }, 1797 | "isDeprecated": false, 1798 | "deprecationReason": null 1799 | }, 1800 | { 1801 | "name": "args", 1802 | "description": null, 1803 | "args": [], 1804 | "type": { 1805 | "kind": "NON_NULL", 1806 | "name": null, 1807 | "ofType": { 1808 | "kind": "LIST", 1809 | "name": null, 1810 | "ofType": { 1811 | "kind": "NON_NULL", 1812 | "name": null, 1813 | "ofType": { 1814 | "kind": "OBJECT", 1815 | "name": "__InputValue" 1816 | } 1817 | } 1818 | } 1819 | }, 1820 | "isDeprecated": false, 1821 | "deprecationReason": null 1822 | }, 1823 | { 1824 | "name": "onOperation", 1825 | "description": null, 1826 | "args": [], 1827 | "type": { 1828 | "kind": "NON_NULL", 1829 | "name": null, 1830 | "ofType": { 1831 | "kind": "SCALAR", 1832 | "name": "Boolean", 1833 | "ofType": null 1834 | } 1835 | }, 1836 | "isDeprecated": false, 1837 | "deprecationReason": null 1838 | }, 1839 | { 1840 | "name": "onFragment", 1841 | "description": null, 1842 | "args": [], 1843 | "type": { 1844 | "kind": "NON_NULL", 1845 | "name": null, 1846 | "ofType": { 1847 | "kind": "SCALAR", 1848 | "name": "Boolean", 1849 | "ofType": null 1850 | } 1851 | }, 1852 | "isDeprecated": false, 1853 | "deprecationReason": null 1854 | }, 1855 | { 1856 | "name": "onField", 1857 | "description": null, 1858 | "args": [], 1859 | "type": { 1860 | "kind": "NON_NULL", 1861 | "name": null, 1862 | "ofType": { 1863 | "kind": "SCALAR", 1864 | "name": "Boolean", 1865 | "ofType": null 1866 | } 1867 | }, 1868 | "isDeprecated": false, 1869 | "deprecationReason": null 1870 | } 1871 | ], 1872 | "inputFields": null, 1873 | "interfaces": [], 1874 | "enumValues": null, 1875 | "possibleTypes": null 1876 | } 1877 | ], 1878 | "directives": [ 1879 | { 1880 | "name": "include", 1881 | "description": "Directs the executor to include this field or fragment only when the `if` argument is true.", 1882 | "args": [ 1883 | { 1884 | "name": "if", 1885 | "description": "Included when true.", 1886 | "type": { 1887 | "kind": "NON_NULL", 1888 | "name": null, 1889 | "ofType": { 1890 | "kind": "SCALAR", 1891 | "name": "Boolean", 1892 | "ofType": null 1893 | } 1894 | }, 1895 | "defaultValue": null 1896 | } 1897 | ], 1898 | "onOperation": false, 1899 | "onFragment": true, 1900 | "onField": true 1901 | }, 1902 | { 1903 | "name": "skip", 1904 | "description": "Directs the executor to skip this field or fragment when the `if` argument is true.", 1905 | "args": [ 1906 | { 1907 | "name": "if", 1908 | "description": "Skipped when true.", 1909 | "type": { 1910 | "kind": "NON_NULL", 1911 | "name": null, 1912 | "ofType": { 1913 | "kind": "SCALAR", 1914 | "name": "Boolean", 1915 | "ofType": null 1916 | } 1917 | }, 1918 | "defaultValue": null 1919 | } 1920 | ], 1921 | "onOperation": false, 1922 | "onFragment": true, 1923 | "onField": true 1924 | } 1925 | ] 1926 | } 1927 | } 1928 | } -------------------------------------------------------------------------------- /index.js: -------------------------------------------------------------------------------- 1 | require('babel-register'); 2 | require('babel-polyfill'); 3 | require('./server'); 4 | -------------------------------------------------------------------------------- /js/app.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; // eslint-disable-line 2 | import ReactDOM from 'react-dom'; 3 | import App from './components/App'; 4 | 5 | ReactDOM.render( 6 | , 7 | document.getElementById('root') 8 | ); 9 | -------------------------------------------------------------------------------- /js/components/App.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import Relay from 'react-relay'; 3 | 4 | import TodoApp from './TodoApp'; 5 | import AppRoute from '../routes/AppRoute'; 6 | 7 | export default class App extends React.Component { 8 | render() { 9 | return ( 10 | 13 | ); 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /js/components/Todo.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import Relay from 'react-relay'; 3 | import classNames from 'classnames'; 4 | 5 | import ChangeTodoStatusMutation from '../mutations/ChangeTodoStatusMutation'; 6 | import ChangeTodoTextMutation from '../mutations/ChangeTodoTextMutation'; 7 | import DeleteTodoMutation from '../mutations/DeleteTodoMutation'; 8 | 9 | import TodoTextInput from './TodoTextInput'; 10 | 11 | class Todo extends React.Component { 12 | state = { 13 | isEditing: false 14 | } 15 | 16 | handleCompleteToggle = () => { 17 | Relay.Store.update( 18 | new ChangeTodoStatusMutation({ 19 | id: this.props.todo.id, 20 | complete: !this.props.todo.complete 21 | }), 22 | ); 23 | } 24 | 25 | handleLabelDoubleClick = () => { 26 | this.setState({ 27 | isEditing: true 28 | }); 29 | } 30 | 31 | handleDestroyClick = () => { 32 | Relay.Store.update( 33 | new DeleteTodoMutation({ 34 | id: this.props.todo.id, 35 | viewer: this.props.viewer 36 | }), 37 | ); 38 | } 39 | 40 | handleInputSave = (text) => { 41 | Relay.Store.update( 42 | new ChangeTodoTextMutation({ 43 | id: this.props.todo.id, 44 | text: text 45 | }), 46 | ); 47 | this.setState({ 48 | isEditing: false 49 | }); 50 | } 51 | 52 | handleInputCancel = () => { 53 | this.setState({ 54 | isEditing: false 55 | }); 56 | } 57 | 58 | handleInputDelete = () => { 59 | this.setState({ 60 | isEditing: false 61 | }); 62 | } 63 | 64 | makeInput() { 65 | if (this.state.isEditing) { 66 | return ( 67 | 73 | ); 74 | } 75 | return null; 76 | } 77 | 78 | render() { 79 | return ( 80 |
  • 84 |
    85 | 89 | 92 |
    95 | {this.makeInput()} 96 |
  • 97 | ); 98 | } 99 | } 100 | 101 | export default Relay.createContainer(Todo, { 102 | fragments: { 103 | viewer: () => Relay.QL` 104 | fragment on Viewer { 105 | ${DeleteTodoMutation.getFragment('viewer')} 106 | } 107 | `, 108 | todo: () => Relay.QL` 109 | fragment on Todo { 110 | id 111 | text 112 | complete 113 | } 114 | ` 115 | } 116 | }); 117 | -------------------------------------------------------------------------------- /js/components/TodoApp.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import Relay from 'react-relay'; 3 | import classNames from 'classnames'; 4 | 5 | import AddTodoMutation from '../mutations/AddTodoMutation'; 6 | import DeleteTodoMutation from '../mutations/DeleteTodoMutation'; 7 | 8 | import TodoTextInput from './TodoTextInput'; 9 | import TodoList from './TodoList'; 10 | 11 | import 'todomvc-app-css/index.css'; 12 | import 'todomvc-common/base.css'; 13 | 14 | class TodoApp extends React.Component { 15 | state = { 16 | selectedFilter: 'all' 17 | }; 18 | 19 | handleTextInputSave = (text) => { 20 | Relay.Store.update( 21 | new AddTodoMutation({ text, viewer: this.props.viewer }) 22 | ); 23 | } 24 | 25 | handleFilterChange = (selectedFilter) => { 26 | this.setState({ selectedFilter }); 27 | } 28 | 29 | handleClearCompleted = () => { 30 | const viewer = this.props.viewer; 31 | viewer.todos.edges 32 | .filter((edge) => edge.node.complete) 33 | .forEach((edge) => Relay.Store.update( 34 | new DeleteTodoMutation({ viewer, id: edge.node.id }) 35 | )); 36 | } 37 | 38 | makeHeader() { 39 | return ( 40 |
    41 |

    Todos

    42 | 47 |
    48 | ); 49 | } 50 | 51 | makeList() { 52 | const todos = this.props.viewer.todos.edges; 53 | if (todos.length > 0) { 54 | const undone = todos.filter((edge) => !edge.node.complete).length; 55 | const total = todos.length; 56 | const filters = ['all', 'active', 'completed'].map((filter) => { 57 | const selected = filter === this.state.selectedFilter; 58 | return ( 59 |
  • 60 | 63 | {filter} 64 | 65 |
  • 66 | ); 67 | }); 68 | 69 | let clearButton; 70 | if (total - undone > 0) { 71 | clearButton = ( 72 | 76 | ); 77 | } 78 | 79 | return ( 80 |
    81 | 82 | {undone} / {total} items left 83 | 84 |
      85 | {filters} 86 |
    87 | {clearButton} 88 |
    89 | ); 90 | } 91 | } 92 | 93 | render() { 94 | const viewer = this.props.viewer; 95 | const todos = viewer.todos; 96 | return ( 97 |
    98 |
    99 | {this.makeHeader()} 100 | 103 | {this.makeList()} 104 |
    105 |
    106 |

    Double-click to edit a todo

    107 |

    108 | Created by the RisingStack team 109 |

    110 |

    111 | The source code can be found on GitHub 112 |

    113 |

    114 | Part of TodoMVC 115 |

    116 |
    117 |
    118 | ); 119 | } 120 | } 121 | 122 | export default Relay.createContainer(TodoApp, { 123 | prepareVariables() { 124 | return { 125 | limit: 100 126 | }; 127 | }, 128 | 129 | fragments: { 130 | viewer: () => Relay.QL` 131 | fragment on Viewer { 132 | __typename 133 | todos(first: $limit) { 134 | edges { 135 | node { 136 | id 137 | complete 138 | } 139 | } 140 | ${TodoList.getFragment('todos')} 141 | } 142 | ${TodoList.getFragment('viewer')} 143 | ${AddTodoMutation.getFragment('viewer')} 144 | } 145 | ` 146 | } 147 | }); 148 | -------------------------------------------------------------------------------- /js/components/TodoList.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import Relay from 'react-relay'; 3 | 4 | import ChangeTodoStatusMutation from '../mutations/ChangeTodoStatusMutation'; 5 | 6 | import Todo from './Todo'; 7 | 8 | class TodoList extends React.Component { 9 | getFilteredTodos() { 10 | const edges = this.props.todos.edges; 11 | if (this.props.filter === 'active') { 12 | return edges.filter((todo) => !todo.node.complete); 13 | } else if (this.props.filter === 'completed') { 14 | return edges.filter((todo) => todo.node.complete); 15 | } 16 | return edges; 17 | } 18 | 19 | handleToggleAllChange = () => { 20 | const todoCount = this.props.todos.count; 21 | const edges = this.props.todos.edges; 22 | const done = edges.filter((edge) => edge.node.complete); 23 | const setTo = todoCount === done ? false : true; 24 | 25 | edges 26 | .filter((edge) => edge.node.complete !== setTo) 27 | .forEach((edge) => Relay.Store.update( 28 | new ChangeTodoStatusMutation({ 29 | id: edge.node.id, 30 | complete: setTo 31 | }) 32 | )); 33 | } 34 | 35 | makeTodo = (edge) => { 36 | return ( 37 | 40 | ); 41 | } 42 | 43 | render() { 44 | const todoCount = this.props.todos.count; 45 | const edges = this.props.todos.edges; 46 | const done = edges.filter((edge) => edge.node.complete); 47 | const todos = this.getFilteredTodos(); 48 | const todoList = todos.map(this.makeTodo); 49 | return ( 50 |
    51 | 55 |
      56 | {todoList} 57 |
    58 |
    59 | ); 60 | } 61 | } 62 | 63 | export default Relay.createContainer(TodoList, { 64 | fragments: { 65 | viewer: () => Relay.QL` 66 | fragment on Viewer { 67 | ${Todo.getFragment('viewer')} 68 | } 69 | `, 70 | todos: () => Relay.QL` 71 | fragment on TodoConnection { 72 | count 73 | edges { 74 | node { 75 | id 76 | complete 77 | ${Todo.getFragment('todo')} 78 | } 79 | } 80 | } 81 | ` 82 | } 83 | }); 84 | -------------------------------------------------------------------------------- /js/components/TodoTextInput.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import ReactDom from 'react-dom'; 3 | 4 | const ENTER_KEY_CODE = 13; 5 | const ESC_KEY_CODE = 27; 6 | 7 | export default class TodoTextInput extends React.Component { 8 | static defaultProps = { 9 | commitOnBlur: false 10 | } 11 | 12 | state = { 13 | isEditing: false, 14 | text: this.props.initialValue || '' 15 | } 16 | 17 | componentDidMount() { 18 | ReactDom.findDOMNode(this).focus(); 19 | } 20 | 21 | handleBlur = () => { 22 | if (this.props.saveOnBlur) { 23 | this.save(); 24 | } 25 | } 26 | 27 | handleChange = (e) => { 28 | this.setState({ text: e.target.value }); 29 | } 30 | 31 | handleKeyDown = (e) => { 32 | if (this.props.onCancel && e.keyCode === ESC_KEY_CODE) { 33 | this.props.onCancel(); 34 | } else if (e.keyCode === ENTER_KEY_CODE) { 35 | this.save(); 36 | } 37 | } 38 | 39 | save() { 40 | const text = this.state.text.trim(); 41 | if (this.props.onDelete && text === '') { 42 | this.props.onDelete(); 43 | } else if (this.props.onCancel && text === this.props.initialValue) { 44 | this.props.onCancel(); 45 | } else if (text !== '') { 46 | this.props.onSave(text); 47 | this.setState({ text: '' }); 48 | } 49 | } 50 | 51 | render() { 52 | return ( 53 | 59 | ); 60 | } 61 | } 62 | -------------------------------------------------------------------------------- /js/mutations/AddTodoMutation.js: -------------------------------------------------------------------------------- 1 | import Relay from 'react-relay'; 2 | 3 | export default class AddTodoMutation extends Relay.Mutation { 4 | static fragments = { 5 | viewer: () => Relay.QL`fragment on Viewer { 6 | id 7 | todos { 8 | count 9 | } 10 | }` 11 | }; 12 | 13 | getMutation() { 14 | return Relay.QL`mutation{addTodo}`; 15 | } 16 | 17 | getVariables() { 18 | return { 19 | text: this.props.text, 20 | complete: false 21 | }; 22 | } 23 | 24 | getFatQuery() { 25 | return Relay.QL` 26 | fragment on addTodoPayload { 27 | changedTodoEdge 28 | viewer { 29 | todos { 30 | count 31 | } 32 | } 33 | } 34 | `; 35 | } 36 | 37 | getConfigs() { 38 | return [{ 39 | type: 'RANGE_ADD', 40 | parentID: this.props.viewer.id, 41 | connectionName: 'todos', 42 | edgeName: 'changedTodoEdge', 43 | rangeBehaviors: { 44 | '': 'prepend' 45 | } 46 | }]; 47 | } 48 | 49 | getOptimisticResponse() { 50 | return { 51 | changedTodoEdge: { 52 | node: { 53 | text: this.props.text, 54 | complete: false 55 | } 56 | }, 57 | viewer: { 58 | id: this.props.viewer.id, 59 | todos: { 60 | count: this.props.viewer.todos.count + 1 61 | } 62 | } 63 | }; 64 | } 65 | } 66 | -------------------------------------------------------------------------------- /js/mutations/ChangeTodoStatusMutation.js: -------------------------------------------------------------------------------- 1 | import Relay from 'react-relay'; 2 | 3 | export default class ChangeTodoStatusMutation extends Relay.Mutation { 4 | getMutation() { 5 | return Relay.QL`mutation{updateTodo}`; 6 | } 7 | 8 | getVariables() { 9 | return { 10 | id: this.props.id, 11 | complete: this.props.complete 12 | }; 13 | } 14 | 15 | getFatQuery() { 16 | return Relay.QL` 17 | fragment on updateTodoPayload { 18 | changedTodo { 19 | complete 20 | } 21 | } 22 | `; 23 | } 24 | 25 | getConfigs() { 26 | return [{ 27 | type: 'FIELDS_CHANGE', 28 | fieldIDs: { 29 | changedTodo: this.props.id 30 | } 31 | }]; 32 | } 33 | 34 | getOptimisticResponse() { 35 | return { 36 | changedTodo: { 37 | id: this.props.id, 38 | complete: this.props.complete 39 | } 40 | }; 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /js/mutations/ChangeTodoTextMutation.js: -------------------------------------------------------------------------------- 1 | import Relay from 'react-relay'; 2 | 3 | export default class ChangeTodoTextMutation extends Relay.Mutation { 4 | getMutation() { 5 | return Relay.QL`mutation{updateTodo}`; 6 | } 7 | 8 | getVariables() { 9 | return { 10 | id: this.props.id, 11 | text: this.props.text 12 | }; 13 | } 14 | 15 | getFatQuery() { 16 | return Relay.QL` 17 | fragment on updateTodoPayload { 18 | changedTodo { 19 | text 20 | } 21 | } 22 | `; 23 | } 24 | 25 | getConfigs() { 26 | return [{ 27 | type: 'FIELDS_CHANGE', 28 | fieldIDs: { 29 | changedTodo: this.props.id 30 | } 31 | }]; 32 | } 33 | 34 | getOptimisticResponse() { 35 | return { 36 | changedTodo: { 37 | id: this.props.id, 38 | text: this.props.text 39 | } 40 | }; 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /js/mutations/DeleteTodoMutation.js: -------------------------------------------------------------------------------- 1 | import Relay from 'react-relay'; 2 | 3 | export default class AddTodoMutation extends Relay.Mutation { 4 | static fragments = { 5 | viewer: () => Relay.QL`fragment on Viewer { 6 | id 7 | todos { 8 | count 9 | } 10 | }` 11 | }; 12 | 13 | getMutation() { 14 | return Relay.QL`mutation{deleteTodo}`; 15 | } 16 | 17 | getVariables() { 18 | return { 19 | id: this.props.id 20 | }; 21 | } 22 | 23 | getFatQuery() { 24 | return Relay.QL` 25 | fragment on deleteTodoPayload { 26 | id 27 | viewer { 28 | id 29 | todos { 30 | count 31 | } 32 | } 33 | } 34 | `; 35 | } 36 | 37 | getConfigs() { 38 | return [{ 39 | type: 'NODE_DELETE', 40 | parentName: 'viewer', 41 | parentID: this.props.viewer.id, 42 | connectionName: 'todos', 43 | deletedIDFieldName: 'id' 44 | }]; 45 | } 46 | 47 | getOptimisticResponse() { 48 | return { 49 | id: this.props.id, 50 | viewer: { 51 | id: this.props.viewer.id, 52 | todos: { 53 | count: this.props.viewer.todos.count - 1 54 | } 55 | } 56 | }; 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /js/routes/AppRoute.js: -------------------------------------------------------------------------------- 1 | import Relay from 'react-relay'; 2 | 3 | export default class extends Relay.Route { 4 | static queries = { 5 | viewer: () => Relay.QL`query{viewer}` 6 | }; 7 | static routeName = 'AppRoute'; 8 | } 9 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@risingstack/graffiti-todo", 3 | "version": "1.0.0", 4 | "description": "Example TodoMVC Relay application using graffiti-mongoose", 5 | "author": "RisingStack", 6 | "contributors": [ 7 | "Andras Toth (http://andrastoth.com/)" 8 | ], 9 | "repository": { 10 | "type": "git", 11 | "url": "git@github.com:RisingStack/graffiti-todo.git" 12 | }, 13 | "bugs": { 14 | "url": "https://github.com/RisingStack/graffiti-todo/issues" 15 | }, 16 | "homepage": "https://github.com/RisingStack/graffiti-todo#readme", 17 | "license": "MIT", 18 | "keywords": [ 19 | "React", 20 | "Relay", 21 | "GraphQL", 22 | "graffiti", 23 | "mongoose", 24 | "ORM", 25 | "TODOMVC", 26 | "TODO" 27 | ], 28 | "main": "index.js", 29 | "scripts": { 30 | "start": "node .", 31 | "build": "npm run update-schema && webpack", 32 | "update-schema": "babel-node ./scripts/updateSchema.js", 33 | "prepush": "npm run build && git commit --amend --no-edit", 34 | "eslint": "eslint js" 35 | }, 36 | "dependencies": { 37 | "@risingstack/graffiti": "^2.1.1", 38 | "@risingstack/graffiti-mongoose": "^4.3.3", 39 | "babel": "6.3.13", 40 | "babel-loader": "6.2.0", 41 | "babel-polyfill": "^6.3.14", 42 | "babel-preset-es2015": "^6.3.13", 43 | "babel-preset-react": "^6.3.13", 44 | "babel-preset-stage-0": "^6.3.13", 45 | "babel-register": "^6.3.13", 46 | "babel-relay-plugin": "0.6.0", 47 | "bad-words": "1.3.1", 48 | "classnames": "2.2.1", 49 | "graphql": "0.4.14", 50 | "hapi": "11.1.2", 51 | "inert": "3.2.0", 52 | "mongoose": "4.3.0", 53 | "react": "0.14.3", 54 | "react-dom": "0.14.3", 55 | "react-relay": "0.6.0", 56 | "todomvc-app-css": "2.0.3", 57 | "todomvc-common": "1.0.2" 58 | }, 59 | "devDependencies": { 60 | "babel-cli": "^6.3.17", 61 | "babel-eslint": "5.0.0-beta6", 62 | "css-loader": "0.23.0", 63 | "eslint": "1.10.3", 64 | "eslint-config-airbnb": "2.0.0", 65 | "eslint-plugin-react": "3.11.3", 66 | "pre-push": "0.1.1", 67 | "style-loader": "0.13.0", 68 | "webpack": "1.12.9" 69 | }, 70 | "pre-push": [ 71 | "prepush" 72 | ], 73 | "babel": { 74 | "presets": [ 75 | "react", 76 | "es2015", 77 | "stage-0" 78 | ] 79 | } 80 | } 81 | -------------------------------------------------------------------------------- /public/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | Relay TODO • Graffiti Mongoose 7 | 8 | 9 |
    10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /scripts/updateSchema.js: -------------------------------------------------------------------------------- 1 | import fs from 'fs'; 2 | import path from 'path'; 3 | import {getSchema} from '@risingstack/graffiti-mongoose'; 4 | import {graphql} from 'graphql'; 5 | import {introspectionQuery, printSchema} from 'graphql/utilities'; 6 | import mongooseSchema from '../data/schema'; 7 | 8 | const schema = getSchema(mongooseSchema); 9 | // Save JSON of full schema introspection for Babel Relay Plugin to use 10 | (async () => { 11 | const result = await (graphql(schema, introspectionQuery)); 12 | if (result.errors) { 13 | console.error( // eslint-disable-line no-console 14 | 'ERROR introspecting schema: ', 15 | JSON.stringify(result.errors, null, 2) 16 | ); 17 | } else { 18 | fs.writeFileSync( 19 | path.join(__dirname, '../data/schema.json'), 20 | JSON.stringify(result, null, 2) 21 | ); 22 | } 23 | })(); 24 | 25 | // Save user readable type system shorthand of schema 26 | fs.writeFileSync( 27 | path.join(__dirname, '../data/schema.graphql'), 28 | printSchema(schema) 29 | ); 30 | -------------------------------------------------------------------------------- /server.js: -------------------------------------------------------------------------------- 1 | import path from 'path'; 2 | import Hapi from 'hapi'; 3 | import Inert from 'inert'; 4 | import { hapi } from '@risingstack/graffiti'; 5 | import { getSchema } from '@risingstack/graffiti-mongoose'; 6 | import mongoose from 'mongoose'; 7 | import mongooseSchema from './data/schema'; 8 | import Filter from 'bad-words'; 9 | 10 | const PORT = process.env.PORT || 8080; 11 | const MONGO_URI = process.env.MONGO_URI || process.env.MONGOLAB_URI || 'mongodb://localhost/graphql'; 12 | 13 | mongoose.connect(MONGO_URI); 14 | 15 | const server = new Hapi.Server(); 16 | server.connection({ port: PORT }); 17 | 18 | const filter = new Filter(); 19 | const hooks = { 20 | mutation: { 21 | pre: (next, todo, ...rest) => { 22 | if (todo.text) { 23 | todo.text = filter.clean(todo.text); 24 | } 25 | 26 | next(todo, ...rest); 27 | } 28 | } 29 | }; 30 | server.register([Inert, { 31 | register: hapi, 32 | options: { 33 | schema: getSchema(mongooseSchema, { hooks }) 34 | } 35 | }], (err) => { 36 | if (err) { 37 | throw new Error('Failed to load a plugin: ' + err); 38 | } 39 | }); 40 | 41 | // serve static files 42 | server.route({ 43 | method: 'GET', 44 | path: '/{param*}', 45 | handler: { 46 | directory: { 47 | path: path.join(__dirname, 'public'), 48 | redirectToSlash: true, 49 | index: true 50 | } 51 | } 52 | }); 53 | 54 | server.start(() => { 55 | console.log('Server running at:', server.info.uri); // eslint-disable-line 56 | }); 57 | -------------------------------------------------------------------------------- /webpack.config.js: -------------------------------------------------------------------------------- 1 | var path = require('path'); 2 | 3 | module.exports = { 4 | entry: path.resolve(__dirname, 'js/app.js'), 5 | output: { 6 | path: path.resolve(__dirname, 'public'), 7 | filename: 'app.js' 8 | }, 9 | module: { 10 | loaders: [ 11 | { 12 | test: /\.js$/, 13 | exclude: /node_modules/, 14 | loader: 'babel', 15 | query: { 16 | presets: ['react', 'es2015', 'stage-0'], 17 | plugins: ['./build/babelRelayPlugin'] 18 | } 19 | }, 20 | { 21 | test: /\.css$/, 22 | loader: 'style!css' 23 | } 24 | ] 25 | } 26 | }; 27 | --------------------------------------------------------------------------------