├── .babelrc ├── .eslintrc ├── .gitignore ├── .idea ├── .name ├── encodings.xml ├── graphql-blog-schema.iml ├── jsLibraryMappings.xml ├── libraries │ └── graphql_blog_schema_node_modules.xml ├── misc.xml ├── modules.xml └── vcs.xml ├── LICENSE ├── README.md ├── index.html ├── package.json ├── server.js ├── src ├── App.js ├── CashayBook.js ├── Comment.js ├── CommentContainer.js ├── CreateComment.js ├── LatestPostComments.js ├── LeastRecentPosts.js ├── SinglePost.js ├── data │ ├── authors.js │ ├── comments.js │ ├── groups.js │ └── posts.js ├── getCashaySchema.js ├── graphiqlSchema.json ├── index.js ├── recentPosts.js └── schema.js ├── static └── graphiql.css └── webpack.config.js /.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "presets": ["react", "es2015", "stage-0"], 3 | "plugins": [ 4 | ["transform-decorators-legacy"], 5 | ["add-module-exports"] 6 | ] 7 | } 8 | -------------------------------------------------------------------------------- /.eslintrc: -------------------------------------------------------------------------------- 1 | { 2 | "ecmaFeatures": { 3 | "jsx": true, 4 | "modules": true 5 | }, 6 | "env": { 7 | "browser": true, 8 | "node": true 9 | }, 10 | "parser": "babel-eslint", 11 | "rules": { 12 | "quotes": [2, "single"], 13 | "strict": [2, "never"], 14 | "react/jsx-uses-react": 2, 15 | "react/jsx-uses-vars": 2, 16 | "react/react-in-jsx-scope": 2 17 | }, 18 | "plugins": [ 19 | "react" 20 | ] 21 | } 22 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | npm-debug.log 3 | .DS_Store 4 | dist 5 | -------------------------------------------------------------------------------- /.idea/.name: -------------------------------------------------------------------------------- 1 | graphql-blog-schema -------------------------------------------------------------------------------- /.idea/encodings.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /.idea/graphql-blog-schema.iml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /.idea/jsLibraryMappings.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | -------------------------------------------------------------------------------- /.idea/libraries/graphql_blog_schema_node_modules.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 9 | 10 | 11 | 12 | 13 | 14 | -------------------------------------------------------------------------------- /.idea/misc.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | -------------------------------------------------------------------------------- /.idea/modules.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /.idea/vcs.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2014 Dan Abramov 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 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # cashay-playground 2 | really boring & uninteresting & definitely not click worthy 3 | 4 | ## Installation 5 | - `git clone https://github.com/mattkrick/cashay-playground.git` 6 | - `npm i && npm start` 7 | - `npm run updateSchema` (optional, run this whenever you change the schema) 8 | 9 | ## Things to do in the playground 10 | - Server calls are delayed by 1 second to see all the optimistic goodness 11 | - Notice that checking loading status is really, really easy 12 | - Click "get 2 more" and notice that the button goes away after you've got em all without a 2nd request to the server. 13 | - Click "show comments" on the first post to see 2 comments load 14 | - Add a comment & notice that you can add it ***anywhere in the array*** (in this case, it's sorted by karma & starts you out at 0) 15 | - Add a post & notice that the optimistic update is immediate & the response from the server overwrites your optimistic update 16 | - Adding a post updates the array _and_ the post count. 17 | - Crack open the Chrome Redux Devtools and take a look at your sexy, sexy store 18 | 19 | ## Things to notice in the code 20 | - I never wrote a single mutation on the client (no `variableDefinitions`, no response or fat query, nothing). 21 | - For most mutations, you don't even need to include a `components` options, it'll just grab all the queries with the corresponding handlers 22 | - Your mutation handler can do ANYTHING. seriously, ANYTHING 23 | -------------------------------------------------------------------------------- /index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | LearnGraphQL Sandbox 4 | 5 | 6 | 7 |
8 |
9 | 10 | 11 | 12 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "react-hot-boilerplate", 3 | "version": "1.0.0", 4 | "description": "Boilerplate for ReactJS project with hot code reloading", 5 | "scripts": { 6 | "start": "node server.js", 7 | "lint": "eslint src", 8 | "updateSchema": "cashay-schema src/schema.js src/clientSchema.json" 9 | }, 10 | "repository": { 11 | "type": "git", 12 | "url": "https://github.com/gaearon/react-hot-boilerplate.git" 13 | }, 14 | "keywords": [ 15 | "react", 16 | "reactjs", 17 | "boilerplate", 18 | "hot", 19 | "reload", 20 | "hmr", 21 | "live", 22 | "edit", 23 | "webpack" 24 | ], 25 | "author": "Dan Abramov (http://github.com/gaearon)", 26 | "license": "MIT", 27 | "bugs": { 28 | "url": "https://github.com/gaearon/react-hot-boilerplate/issues" 29 | }, 30 | "homepage": "https://github.com/gaearon/react-hot-boilerplate", 31 | "devDependencies": { 32 | "eslint-plugin-react": "^5.1.1", 33 | "json-loader": "^0.5.4", 34 | "webpack-hot-middleware": "^2.10.0", 35 | "react-transform-catch-errors": "1.0.2", 36 | "react-transform-hmr": "^1.0.4", 37 | "redbox-react": "1.2.6", 38 | "babel-cli": "6.9.0", 39 | "babel-core": "6.9.1", 40 | "babel-loader": "6.2.4", 41 | "babel-plugin-add-module-exports": "0.2.1", 42 | "babel-plugin-react-transform": "2.0.2", 43 | "babel-plugin-transform-decorators-legacy": "1.3.4", 44 | "babel-preset-es2015": "6.9.0", 45 | "babel-preset-react": "6.5.0", 46 | "babel-preset-stage-0": "6.5.0", 47 | "babel-register": "6.9.0" 48 | }, 49 | "dependencies": { 50 | "babel-polyfill": "^6.9.1", 51 | "body-parser": "^1.15.1", 52 | "cashay": "^0.8.0", 53 | "express": "4.x.x", 54 | "graphiql": "^0.7.1", 55 | "graphql": "^0.6.0", 56 | "react": "^15.1.0", 57 | "react-dom": "^15.1.0", 58 | "react-hot-loader": "^1.3.0", 59 | "react-redux": "^4.4.5", 60 | "redux": "^3.5.2", 61 | "webpack": "^1.13.1", 62 | "webpack-dev-server": "^1.14.1" 63 | } 64 | } 65 | -------------------------------------------------------------------------------- /server.js: -------------------------------------------------------------------------------- 1 | require('babel-polyfill'); 2 | require('babel-register'); 3 | var webpack = require('webpack') 4 | var config = require('./webpack.config') 5 | var express = require('express'); 6 | var app = new express(); 7 | var port = 3000 8 | var graphql = require('graphql').graphql 9 | var Schema = require('./src/schema'); 10 | var bodyParser = require('body-parser'); 11 | const compiler = webpack(config); 12 | const delay = () => new Promise(resolve => setTimeout(resolve, 1000)); 13 | 14 | app.use(bodyParser.json()); 15 | app.use(require('webpack-dev-middleware')(compiler, { 16 | noInfo: true, 17 | publicPath: config.output.publicPath 18 | })); 19 | app.use(require('webpack-hot-middleware')(compiler)); 20 | app.use('/static', express.static('static')); 21 | app.use('/graphql', function(req, res) { 22 | // const {query, variables} = req.body; 23 | const query = req.body.query; 24 | const variables = req.body.variables; 25 | const authToken = req.user || {}; 26 | const context = {authToken}; 27 | graphql(Schema, query, null, context, variables) 28 | .then(result => { 29 | if (result.errors) { 30 | console.log(query) 31 | console.log('DEBUG GraphQL Error:', result.errors); 32 | 33 | } 34 | return result; 35 | }) 36 | .then(result => { 37 | delay().then(() => res.send(result)) 38 | }) 39 | 40 | }); 41 | app.get('*', function(req, res) { 42 | res.sendFile(__dirname + '/index.html') 43 | }); 44 | 45 | app.listen(port, function(error) { 46 | if (error) { 47 | console.error(error) 48 | } else { 49 | console.info("==> Listening on port %s. Open up http://localhost:%s/ in your browser.", port, port) 50 | } 51 | }) 52 | -------------------------------------------------------------------------------- /src/App.js: -------------------------------------------------------------------------------- 1 | import React, { Component } from 'react'; 2 | import GraphiQL from 'graphiql'; 3 | import fetch from 'isomorphic-fetch'; 4 | import gqlSchema from './schema.js'; 5 | import { graphql } from 'graphql'; 6 | 7 | 8 | GraphiQL.Logo = class Logo extends Component { 9 | render() { 10 | let style = { 11 | fontWeight: 800, 12 | fontSize: 16, 13 | color: "#252525" 14 | }; 15 | 16 | return ( 17 | Learn GraphQL Sandbox 18 | ); 19 | } 20 | } 21 | 22 | export default class App extends Component { 23 | fetchData({query, variables}) { 24 | let queryVariables = {}; 25 | try { 26 | queryVariables = JSON.parse(variables); 27 | } catch (ex) { 28 | } 29 | return graphql(gqlSchema, query, null, queryVariables); 30 | } 31 | 32 | render() { 33 | return ( 34 | 35 | ); 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /src/CashayBook.js: -------------------------------------------------------------------------------- 1 | import React, {Component} from 'react'; 2 | import RecentPosts from './RecentPosts'; 3 | import LeastRecentPosts from './LeastRecentPosts'; 4 | import LatestPostComments from './LatestPostComments'; 5 | import {cashay} from 'cashay'; 6 | import {connect} from 'react-redux'; 7 | 8 | const queryPostCount = ` 9 | query { 10 | postCount: getPostCount 11 | }`; 12 | 13 | const mutationHandlers = { 14 | createPost(optimisticVariables, docFromServer, currentResponse, getEntities, invalidate) { 15 | if (optimisticVariables) { 16 | return currentResponse.postCount++; 17 | } 18 | return docFromServer.createPost.postCount; 19 | }, 20 | removePostById(optimisticVariables, docFromServer, currentResponse) { 21 | if (optimisticVariables) { 22 | return currentResponse.postCount--; 23 | } 24 | currentResponse.postCount = docFromServer.removePostById.postCount; 25 | return currentResponse; 26 | } 27 | }; 28 | const cashayOptions = { 29 | component: 'CashayBook', 30 | mutationHandlers 31 | }; 32 | const mapStateToProps = () => ({cashay: cashay.query(queryPostCount, cashayOptions)}); 33 | 34 | @connect(mapStateToProps) 35 | export default class CashayBook extends Component { 36 | render() { 37 | const {postCount} = this.props.cashay.data; 38 | const {isComplete} = this.props.cashay; 39 | return ( 40 |
41 |
42 |

Welcome to the Cashay book!

43 |

{isComplete ? `We currently have ${postCount} posts!` : "LOADING AHH I CANT WAIT IM SO EXCITED"}

44 | 45 | 46 |
47 |
48 | ); 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /src/Comment.js: -------------------------------------------------------------------------------- 1 | import React, {Component} from 'react'; 2 | 3 | export default class Comment extends Component { 4 | render() { 5 | const {comment} = this.props; 6 | return ( 7 |
8 |
{comment.karma} - {comment.content}
9 |
10 | ) 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /src/CommentContainer.js: -------------------------------------------------------------------------------- 1 | import React, {Component} from 'react'; 2 | import {connect} from 'react-redux'; 3 | import {cashay} from 'cashay'; 4 | import Comment from './Comment'; 5 | import CreateComment from './CreateComment' 6 | // import uuid from 'node-uuid'; 7 | 8 | // commentCount: getCommentCount(postId: $postId), 9 | const queryCommentsForPostId = ` 10 | query($postId: String!) { 11 | comments: getCommentsByPostId(postId: $postId) { 12 | _id, 13 | content, 14 | karma, 15 | createdAt, 16 | postId 17 | 18 | } 19 | }`; 20 | 21 | // mutation: ` 22 | // mutation($postId: String!, $content: String!, $_id: String!) { 23 | // newComment: createComment(postId: $postId, content: $content, _id: $_id) { 24 | // _id, 25 | // content, 26 | // karma, 27 | // createdAt, 28 | // postId 29 | // } 30 | // }` 31 | 32 | const mutationHandlers = { 33 | createComment(optimisticVariables, docFromServer, currentResponse, state, invalidate) { 34 | if (optimisticVariables) { 35 | const {content, postId, _id} = optimisticVariables; 36 | const newComment = { 37 | _id, 38 | content: content + ' OPTMISTICAL!', 39 | postId, 40 | createdAt: Date.now(), 41 | karma: 0 42 | }; 43 | const placeBefore = currentResponse.comments.findIndex(comment => comment.karma < newComment.karma); 44 | const spliceLocation = placeBefore !== -1 ? placeBefore : currentResponse.comments.length; 45 | currentResponse.comments.splice(spliceLocation, 0, newComment); 46 | return currentResponse; 47 | } 48 | const optimisticDocIdx = currentResponse.comments.findIndex(comment => comment._id === docFromServer.createComment._id); 49 | currentResponse.comments[optimisticDocIdx] = docFromServer.createComment; 50 | return currentResponse; 51 | } 52 | }; 53 | 54 | const mapStateToProps = (state, props) => { 55 | const {postId} = props; 56 | return { 57 | cashay: cashay.query(queryCommentsForPostId, { 58 | variables: {postId}, 59 | component: `comments`, 60 | key: postId, 61 | mutationHandlers 62 | }) 63 | } 64 | }; 65 | 66 | @connect(mapStateToProps) 67 | export default class CommentContainer extends Component { 68 | render() { 69 | const {comments} = this.props.cashay.data; 70 | const {postId} = this.props; 71 | return ( 72 |
73 |
{comments.map(comment => )}
74 | 75 |
76 | ) 77 | } 78 | } 79 | -------------------------------------------------------------------------------- /src/CreateComment.js: -------------------------------------------------------------------------------- 1 | import React, {Component} from 'react'; 2 | import {cashay} from 'cashay'; 3 | import uuid from 'node-uuid'; 4 | 5 | export default class CreateComment extends Component { 6 | constructor(props) { 7 | super(props); 8 | this.state = {content: ''}; 9 | } 10 | 11 | render() { 12 | return ( 13 |
14 | New Comment: 15 | 16 | 17 |
18 | ) 19 | } 20 | 21 | handleChange = event => { 22 | this.setState({content: event.target.value.substr(0, 140)}); 23 | } 24 | 25 | handleSubmit = () => { 26 | const variables = { 27 | content: this.state.content, 28 | postId: this.props.postId, 29 | _id: uuid.v4() 30 | }; 31 | this.setState({content: ''}); 32 | cashay.mutate('createComment', {variables, components: {comments: variables.postId}}) 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /src/LatestPostComments.js: -------------------------------------------------------------------------------- 1 | import React, {Component} from 'react'; 2 | import {cashay} from 'cashay'; 3 | import {connect} from 'react-redux'; 4 | import Comment from './Comment'; 5 | const queryPostCount = ` 6 | query { 7 | latestPostId: getLatestPostId 8 | latestPostComments: getCommentsByPostId(postId: $postId) { 9 | _id, 10 | content, 11 | karma 12 | } 13 | }`; 14 | 15 | const cashayOptions = { 16 | component: 'LatestPostComments', 17 | variables: { 18 | postId: (response, cashayDataState) => response.latestPostId 19 | } 20 | }; 21 | const mapStateToProps = (state, props) => { 22 | return { 23 | cashayResponse: cashay.query(queryPostCount, cashayOptions) 24 | 25 | } 26 | }; 27 | 28 | @connect(mapStateToProps) 29 | export default class LatestPostComments extends Component { 30 | render() { 31 | const {isComplete} = this.props.cashayResponse; 32 | const {latestPostId, latestPostComments} = this.props.cashayResponse.data; 33 | return ( 34 |
35 |
36 |
37 |
38 |

LATEST POST COMMENTS

39 |

(Example of a multi-part query)

40 |
41 |
Latest post ID: {latestPostId}
42 |

Comments for post

43 |
{latestPostComments.map(comment => )}
44 |
45 | ) 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /src/LeastRecentPosts.js: -------------------------------------------------------------------------------- 1 | import React, {Component} from 'react'; 2 | import {connect} from 'react-redux'; 3 | import {cashay} from 'cashay'; 4 | import SinglePost from './SinglePost'; 5 | import uuid from 'node-uuid'; 6 | 7 | const queryRecentPosts = ` 8 | query($count: Int) { 9 | leastRecentPosts: getRecentPosts(last: $count) { 10 | _id, 11 | title, 12 | cursor, 13 | spanishTitle: title(language:"spanish"), 14 | reverseSpanishTitle: title(language:"spanish", inReverse:true) 15 | } 16 | }`; 17 | 18 | const customMutations = { 19 | removePostById: ` 20 | mutation($postId: String!) { 21 | removePostById(postId: $postId) { 22 | removedPostId 23 | } 24 | }` 25 | }; 26 | 27 | const mutationHandlers = { 28 | createPost(optimisticVariables, docFromServer, currentResponse, state, invalidate) { 29 | if (optimisticVariables) { 30 | const {newPost} = optimisticVariables; 31 | const newOptimisticPost = Object.assign({}, newPost, { 32 | title: `${newPost.title} OPTIMISTICAL!` 33 | }); 34 | currentResponse.leastRecentPosts.push(newOptimisticPost); 35 | return currentResponse; 36 | } 37 | const optimisticDocIdx = currentResponse.leastRecentPosts.findIndex(post => post._id === docFromServer.createPost.post._id); 38 | currentResponse.leastRecentPosts[optimisticDocIdx] = docFromServer.createPost.post; 39 | return currentResponse; 40 | }, 41 | removePostById(optimisticVariables, docFromServer, currentResponse) { 42 | // example of not using optimistic updates 43 | const idRemoved = docFromServer && docFromServer.removePostById.removedPostId; 44 | if (idRemoved) { 45 | const idx = currentResponse.leastRecentPosts.findIndex(post => post._id === idRemoved); 46 | currentResponse.leastRecentPosts.splice(idx, 1); 47 | return currentResponse; 48 | } 49 | } 50 | }; 51 | 52 | const mapStateToProps = (state, props) => { 53 | return { 54 | cashay: cashay.query(queryRecentPosts, { 55 | variables: {count: 2}, 56 | mutationHandlers, 57 | customMutations, 58 | component: 'LeastRecentPosts' 59 | }) 60 | } 61 | }; 62 | 63 | @connect(mapStateToProps) 64 | export default class LeastRecentPosts extends Component { 65 | render() { 66 | const {leastRecentPosts} = this.props.cashay.data; 67 | return ( 68 |
69 |

LEAST RECENT POSTS

70 | 71 |
{leastRecentPosts.map(post => )}
72 | {leastRecentPosts.BOF ? null : 73 | 74 | } 75 |
76 | ); 77 | } 78 | 79 | createPost = () => { 80 | const variables = { 81 | newPost: { 82 | _id: uuid.v4(), 83 | title: 'Woo another post!' 84 | } 85 | }; 86 | cashay.mutate('createPost', {variables}) 87 | }; 88 | 89 | get2More = () => { 90 | const {setVariables} = this.props.cashay; 91 | setVariables(currentVariables => { 92 | return { 93 | count: currentVariables.count + 2 94 | } 95 | }) 96 | } 97 | } 98 | -------------------------------------------------------------------------------- /src/SinglePost.js: -------------------------------------------------------------------------------- 1 | import React, {Component} from 'react'; 2 | import CommentContainer from './CommentContainer'; 3 | import {cashay} from 'cashay'; 4 | 5 | export default class SinglePost extends Component { 6 | constructor(props) { 7 | super(props); 8 | this.state = {showComments: false} 9 | } 10 | render() { 11 | const {post} = this.props; 12 | const {showComments} = this.state; 13 | return ( 14 |
15 |
16 | {post.title} 17 | Try to Delete Me 18 |
19 | {showComments ? : null} 20 |
21 | {showComments ? 'Hide Comments' : 'Show Comments'} 22 |
23 |
24 |
25 | ) 26 | } 27 | deletePost = () => { 28 | const variables = { 29 | postId: this.props.post._id 30 | }; 31 | cashay.mutate('removePostById', {variables}); 32 | }; 33 | 34 | toggleComments = () => { 35 | this.setState({showComments: !this.state.showComments}) 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /src/data/authors.js: -------------------------------------------------------------------------------- 1 | export default [ 2 | { 3 | _id: 'a123', 4 | name: 'Matt K', 5 | twitterHandle: 's@__mattk' 6 | }, 7 | { 8 | _id: 'a124', 9 | name: 'Joe J', 10 | twitterHandle: '@__joej' 11 | }, 12 | { 13 | _id: 'a125', 14 | name: 'Rob R', 15 | twitterHandle: '@__robr' 16 | } 17 | ] 18 | -------------------------------------------------------------------------------- /src/data/comments.js: -------------------------------------------------------------------------------- 1 | export default [ 2 | { 3 | _id: 'c123', 4 | cursor: 'c123', 5 | content: 'This is great!', 6 | author: 'a125', 7 | postId: 'p126', 8 | karma: 5, 9 | createdAt: 1463074881798 10 | }, 11 | { 12 | _id: 'c124', 13 | cursor: 'c124', 14 | content: 'Do you want to make $67,000/hr working from home? This single mom can do it!', 15 | author: 'a124', 16 | postId: 'p126', 17 | karma: -100, 18 | createdAt: 1463078881798 19 | } 20 | ] 21 | -------------------------------------------------------------------------------- /src/data/groups.js: -------------------------------------------------------------------------------- 1 | export default [ 2 | { 3 | name: 'executive team', 4 | _id: 'g123', 5 | ownerId: 'a123', 6 | members: ['a123', 'a124'] 7 | }, 8 | { 9 | name: 'all employees', 10 | _id: 'g124', 11 | ownerId: 'a123', 12 | members: ['a125', 'g123'] 13 | } 14 | ] 15 | -------------------------------------------------------------------------------- /src/data/posts.js: -------------------------------------------------------------------------------- 1 | export default [ 2 | { 3 | _id: 'p123', 4 | author: 'a123', 5 | content: 'Cashay is neat', 6 | title: 'What is cashay?', 7 | title_ES: 'What is cashay? EN ESPANOL!', 8 | category: 'hot stuff', 9 | karma: 2, 10 | createdAt: 1411111111111, 11 | cursor: '1411111111111' + 'chikachikow' 12 | }, 13 | { 14 | _id: 'p124', 15 | author: 'a123', 16 | content: 'Cashay uses the redux store to hold all of its normalized data and variables', 17 | title: 'Does cashay work with redux?', 18 | title_ES: 'Does cashay work with redux? EN ESPANOL!', 19 | category: 'hot stuff', 20 | karma: 2, 21 | createdAt: 1422222222222, 22 | cursor: '1422222222222' + 'chikachikow' 23 | }, 24 | { 25 | _id: 'p125', 26 | author: 'a124', 27 | content: 'Benchmarks are usually written by people who have a bias. You should write your own (but yes)', 28 | title: 'Is cashay smaller or faster than relay?', 29 | title_ES: 'Is cashay smaller or faster than relay? EN ESPANOL!', 30 | category: 'ice cold', 31 | karma: 2, 32 | createdAt: 1433333333333, 33 | cursor: '1433333333333' + 'chikachikow' 34 | }, 35 | { 36 | _id: 'p126', 37 | author: 'a124', 38 | content: 'Denormalized data is cached in the cashay singleton', 39 | title: 'How does cashay store denormalized data?', 40 | title_ES: 'How does cashay store denormalized data? EN ESPANOL!', 41 | category: 'hot stuff', 42 | karma: 2, 43 | createdAt: 1444444444444, 44 | cursor: '1444444444444' + 'chikachikow' 45 | } 46 | ] 47 | -------------------------------------------------------------------------------- /src/getCashaySchema.js: -------------------------------------------------------------------------------- 1 | require('babel-register'); 2 | require('babel-polyfill'); 3 | const {transformSchema} = require('cashay'); 4 | const graphql = require('graphql').graphql; 5 | const rootSchema = require('./schema'); 6 | module.exports = transformSchema(rootSchema, graphql); 7 | -------------------------------------------------------------------------------- /src/graphiqlSchema.json: -------------------------------------------------------------------------------- 1 | { 2 | "queryType": { 3 | "name": "BlogSchema" 4 | }, 5 | "mutationType": { 6 | "name": "BlogMutations" 7 | }, 8 | "subscriptionType": null, 9 | "types": [ 10 | { 11 | "kind": "OBJECT", 12 | "name": "BlogSchema", 13 | "fields": [ 14 | { 15 | "name": "getPostCount", 16 | "args": [], 17 | "type": { 18 | "kind": "NON_NULL", 19 | "name": null, 20 | "ofType": { 21 | "kind": "SCALAR", 22 | "name": "Int", 23 | "ofType": null 24 | } 25 | } 26 | }, 27 | { 28 | "name": "getLatestPost", 29 | "args": [], 30 | "type": { 31 | "kind": "OBJECT", 32 | "name": "PostType", 33 | "ofType": null 34 | } 35 | }, 36 | { 37 | "name": "getRecentPosts", 38 | "args": [ 39 | { 40 | "name": "beforeCursor", 41 | "type": { 42 | "kind": "SCALAR", 43 | "name": "String", 44 | "ofType": null 45 | }, 46 | "defaultValue": null 47 | }, 48 | { 49 | "name": "afterCursor", 50 | "type": { 51 | "kind": "SCALAR", 52 | "name": "String", 53 | "ofType": null 54 | }, 55 | "defaultValue": null 56 | }, 57 | { 58 | "name": "first", 59 | "type": { 60 | "kind": "SCALAR", 61 | "name": "Int", 62 | "ofType": null 63 | }, 64 | "defaultValue": null 65 | }, 66 | { 67 | "name": "last", 68 | "type": { 69 | "kind": "SCALAR", 70 | "name": "Int", 71 | "ofType": null 72 | }, 73 | "defaultValue": null 74 | } 75 | ], 76 | "type": { 77 | "kind": "LIST", 78 | "name": null, 79 | "ofType": { 80 | "kind": "OBJECT", 81 | "name": "PostType", 82 | "ofType": null 83 | } 84 | } 85 | }, 86 | { 87 | "name": "getPostById", 88 | "args": [ 89 | { 90 | "name": "_id", 91 | "type": { 92 | "kind": "NON_NULL", 93 | "name": null, 94 | "ofType": { 95 | "kind": "SCALAR", 96 | "name": "String", 97 | "ofType": null 98 | } 99 | }, 100 | "defaultValue": null 101 | } 102 | ], 103 | "type": { 104 | "kind": "OBJECT", 105 | "name": "PostType", 106 | "ofType": null 107 | } 108 | }, 109 | { 110 | "name": "getGroup", 111 | "args": [ 112 | { 113 | "name": "_id", 114 | "type": { 115 | "kind": "NON_NULL", 116 | "name": null, 117 | "ofType": { 118 | "kind": "SCALAR", 119 | "name": "String", 120 | "ofType": null 121 | } 122 | }, 123 | "defaultValue": null 124 | } 125 | ], 126 | "type": { 127 | "kind": "OBJECT", 128 | "name": "Group", 129 | "ofType": null 130 | } 131 | }, 132 | { 133 | "name": "getCommentsByPostId", 134 | "args": [ 135 | { 136 | "name": "postId", 137 | "type": { 138 | "kind": "NON_NULL", 139 | "name": null, 140 | "ofType": { 141 | "kind": "SCALAR", 142 | "name": "String", 143 | "ofType": null 144 | } 145 | }, 146 | "defaultValue": null 147 | } 148 | ], 149 | "type": { 150 | "kind": "LIST", 151 | "name": null, 152 | "ofType": { 153 | "kind": "OBJECT", 154 | "name": "CommentType", 155 | "ofType": null 156 | } 157 | } 158 | } 159 | ], 160 | "inputFields": null, 161 | "interfaces": [], 162 | "enumValues": null, 163 | "possibleTypes": null 164 | }, 165 | { 166 | "kind": "SCALAR", 167 | "name": "Int", 168 | "fields": null, 169 | "inputFields": null, 170 | "interfaces": null, 171 | "enumValues": null, 172 | "possibleTypes": null 173 | }, 174 | { 175 | "kind": "OBJECT", 176 | "name": "PostType", 177 | "fields": [ 178 | { 179 | "name": "_id", 180 | "args": [], 181 | "type": { 182 | "kind": "SCALAR", 183 | "name": "String", 184 | "ofType": null 185 | } 186 | }, 187 | { 188 | "name": "title", 189 | "args": [ 190 | { 191 | "name": "language", 192 | "type": { 193 | "kind": "SCALAR", 194 | "name": "String", 195 | "ofType": null 196 | }, 197 | "defaultValue": null 198 | }, 199 | { 200 | "name": "inReverse", 201 | "type": { 202 | "kind": "SCALAR", 203 | "name": "Boolean", 204 | "ofType": null 205 | }, 206 | "defaultValue": null 207 | } 208 | ], 209 | "type": { 210 | "kind": "SCALAR", 211 | "name": "String", 212 | "ofType": null 213 | } 214 | }, 215 | { 216 | "name": "category", 217 | "args": [], 218 | "type": { 219 | "kind": "ENUM", 220 | "name": "CategoryType", 221 | "ofType": null 222 | } 223 | }, 224 | { 225 | "name": "content", 226 | "args": [], 227 | "type": { 228 | "kind": "SCALAR", 229 | "name": "String", 230 | "ofType": null 231 | } 232 | }, 233 | { 234 | "name": "createdAt", 235 | "args": [ 236 | { 237 | "name": "dateOptions", 238 | "type": { 239 | "kind": "INPUT_OBJECT", 240 | "name": "DateOptions", 241 | "ofType": null 242 | }, 243 | "defaultValue": null 244 | } 245 | ], 246 | "type": { 247 | "kind": "SCALAR", 248 | "name": "Int", 249 | "ofType": null 250 | } 251 | }, 252 | { 253 | "name": "comments", 254 | "args": [ 255 | { 256 | "name": "beforeCursor", 257 | "type": { 258 | "kind": "SCALAR", 259 | "name": "String", 260 | "ofType": null 261 | }, 262 | "defaultValue": null 263 | }, 264 | { 265 | "name": "afterCursor", 266 | "type": { 267 | "kind": "SCALAR", 268 | "name": "String", 269 | "ofType": null 270 | }, 271 | "defaultValue": null 272 | }, 273 | { 274 | "name": "first", 275 | "type": { 276 | "kind": "SCALAR", 277 | "name": "Int", 278 | "ofType": null 279 | }, 280 | "defaultValue": null 281 | }, 282 | { 283 | "name": "last", 284 | "type": { 285 | "kind": "SCALAR", 286 | "name": "Int", 287 | "ofType": null 288 | }, 289 | "defaultValue": null 290 | } 291 | ], 292 | "type": { 293 | "kind": "LIST", 294 | "name": null, 295 | "ofType": { 296 | "kind": "OBJECT", 297 | "name": "CommentType", 298 | "ofType": null 299 | } 300 | } 301 | }, 302 | { 303 | "name": "author", 304 | "args": [], 305 | "type": { 306 | "kind": "OBJECT", 307 | "name": "AuthorType", 308 | "ofType": null 309 | } 310 | }, 311 | { 312 | "name": "cursor", 313 | "args": [], 314 | "type": { 315 | "kind": "SCALAR", 316 | "name": "String", 317 | "ofType": null 318 | } 319 | } 320 | ], 321 | "inputFields": null, 322 | "interfaces": [ 323 | { 324 | "kind": "INTERFACE", 325 | "name": "HasAuthorType", 326 | "ofType": null 327 | } 328 | ], 329 | "enumValues": null, 330 | "possibleTypes": null 331 | }, 332 | { 333 | "kind": "INTERFACE", 334 | "name": "HasAuthorType", 335 | "fields": [ 336 | { 337 | "name": "author", 338 | "args": [], 339 | "type": { 340 | "kind": "OBJECT", 341 | "name": "AuthorType", 342 | "ofType": null 343 | } 344 | } 345 | ], 346 | "inputFields": null, 347 | "interfaces": null, 348 | "enumValues": null, 349 | "possibleTypes": [ 350 | { 351 | "kind": "OBJECT", 352 | "name": "PostType", 353 | "ofType": null 354 | }, 355 | { 356 | "kind": "OBJECT", 357 | "name": "CommentType", 358 | "ofType": null 359 | } 360 | ] 361 | }, 362 | { 363 | "kind": "OBJECT", 364 | "name": "AuthorType", 365 | "fields": [ 366 | { 367 | "name": "_id", 368 | "args": [], 369 | "type": { 370 | "kind": "SCALAR", 371 | "name": "String", 372 | "ofType": null 373 | } 374 | }, 375 | { 376 | "name": "name", 377 | "args": [], 378 | "type": { 379 | "kind": "SCALAR", 380 | "name": "String", 381 | "ofType": null 382 | } 383 | }, 384 | { 385 | "name": "twitterHandle", 386 | "args": [], 387 | "type": { 388 | "kind": "SCALAR", 389 | "name": "String", 390 | "ofType": null 391 | } 392 | } 393 | ], 394 | "inputFields": null, 395 | "interfaces": [], 396 | "enumValues": null, 397 | "possibleTypes": null 398 | }, 399 | { 400 | "kind": "SCALAR", 401 | "name": "String", 402 | "fields": null, 403 | "inputFields": null, 404 | "interfaces": null, 405 | "enumValues": null, 406 | "possibleTypes": null 407 | }, 408 | { 409 | "kind": "SCALAR", 410 | "name": "Boolean", 411 | "fields": null, 412 | "inputFields": null, 413 | "interfaces": null, 414 | "enumValues": null, 415 | "possibleTypes": null 416 | }, 417 | { 418 | "kind": "ENUM", 419 | "name": "CategoryType", 420 | "fields": null, 421 | "inputFields": null, 422 | "interfaces": null, 423 | "enumValues": [ 424 | { 425 | "name": "HOT_STUFF" 426 | }, 427 | { 428 | "name": "ICE_COLD" 429 | } 430 | ], 431 | "possibleTypes": null 432 | }, 433 | { 434 | "kind": "INPUT_OBJECT", 435 | "name": "DateOptions", 436 | "fields": null, 437 | "inputFields": [ 438 | { 439 | "name": "day", 440 | "type": { 441 | "kind": "SCALAR", 442 | "name": "Boolean", 443 | "ofType": null 444 | }, 445 | "defaultValue": null 446 | }, 447 | { 448 | "name": "month", 449 | "type": { 450 | "kind": "SCALAR", 451 | "name": "Boolean", 452 | "ofType": null 453 | }, 454 | "defaultValue": null 455 | }, 456 | { 457 | "name": "year", 458 | "type": { 459 | "kind": "SCALAR", 460 | "name": "Boolean", 461 | "ofType": null 462 | }, 463 | "defaultValue": null 464 | } 465 | ], 466 | "interfaces": null, 467 | "enumValues": null, 468 | "possibleTypes": null 469 | }, 470 | { 471 | "kind": "OBJECT", 472 | "name": "CommentType", 473 | "fields": [ 474 | { 475 | "name": "_id", 476 | "args": [], 477 | "type": { 478 | "kind": "SCALAR", 479 | "name": "String", 480 | "ofType": null 481 | } 482 | }, 483 | { 484 | "name": "content", 485 | "args": [], 486 | "type": { 487 | "kind": "SCALAR", 488 | "name": "String", 489 | "ofType": null 490 | } 491 | }, 492 | { 493 | "name": "author", 494 | "args": [], 495 | "type": { 496 | "kind": "OBJECT", 497 | "name": "AuthorType", 498 | "ofType": null 499 | } 500 | }, 501 | { 502 | "name": "createdAt", 503 | "args": [], 504 | "type": { 505 | "kind": "SCALAR", 506 | "name": "Int", 507 | "ofType": null 508 | } 509 | }, 510 | { 511 | "name": "cursor", 512 | "args": [], 513 | "type": { 514 | "kind": "SCALAR", 515 | "name": "String", 516 | "ofType": null 517 | } 518 | } 519 | ], 520 | "inputFields": null, 521 | "interfaces": [ 522 | { 523 | "kind": "INTERFACE", 524 | "name": "HasAuthorType", 525 | "ofType": null 526 | } 527 | ], 528 | "enumValues": null, 529 | "possibleTypes": null 530 | }, 531 | { 532 | "kind": "OBJECT", 533 | "name": "Group", 534 | "fields": [ 535 | { 536 | "name": "_id", 537 | "args": [], 538 | "type": { 539 | "kind": "SCALAR", 540 | "name": "String", 541 | "ofType": null 542 | } 543 | }, 544 | { 545 | "name": "owner", 546 | "args": [], 547 | "type": { 548 | "kind": "UNION", 549 | "name": "Member", 550 | "ofType": null 551 | } 552 | }, 553 | { 554 | "name": "members", 555 | "args": [], 556 | "type": { 557 | "kind": "LIST", 558 | "name": null, 559 | "ofType": { 560 | "kind": "UNION", 561 | "name": "Member", 562 | "ofType": null 563 | } 564 | } 565 | } 566 | ], 567 | "inputFields": null, 568 | "interfaces": [], 569 | "enumValues": null, 570 | "possibleTypes": null 571 | }, 572 | { 573 | "kind": "UNION", 574 | "name": "Member", 575 | "fields": null, 576 | "inputFields": null, 577 | "interfaces": null, 578 | "enumValues": null, 579 | "possibleTypes": [ 580 | { 581 | "kind": "OBJECT", 582 | "name": "Group", 583 | "ofType": null 584 | }, 585 | { 586 | "kind": "OBJECT", 587 | "name": "AuthorType", 588 | "ofType": null 589 | } 590 | ] 591 | }, 592 | { 593 | "kind": "OBJECT", 594 | "name": "BlogMutations", 595 | "fields": [ 596 | { 597 | "name": "createPost", 598 | "args": [ 599 | { 600 | "name": "newPost", 601 | "type": { 602 | "kind": "NON_NULL", 603 | "name": null, 604 | "ofType": { 605 | "kind": "INPUT_OBJECT", 606 | "name": "NewPost", 607 | "ofType": null 608 | } 609 | }, 610 | "defaultValue": null 611 | }, 612 | { 613 | "name": "author", 614 | "type": { 615 | "kind": "SCALAR", 616 | "name": "String", 617 | "ofType": null 618 | }, 619 | "defaultValue": null 620 | } 621 | ], 622 | "type": { 623 | "kind": "OBJECT", 624 | "name": "CreatePostMutationPayload", 625 | "ofType": null 626 | } 627 | }, 628 | { 629 | "name": "createComment", 630 | "args": [ 631 | { 632 | "name": "_id", 633 | "type": { 634 | "kind": "NON_NULL", 635 | "name": null, 636 | "ofType": { 637 | "kind": "SCALAR", 638 | "name": "String", 639 | "ofType": null 640 | } 641 | }, 642 | "defaultValue": null 643 | }, 644 | { 645 | "name": "postId", 646 | "type": { 647 | "kind": "NON_NULL", 648 | "name": null, 649 | "ofType": { 650 | "kind": "SCALAR", 651 | "name": "String", 652 | "ofType": null 653 | } 654 | }, 655 | "defaultValue": null 656 | }, 657 | { 658 | "name": "content", 659 | "type": { 660 | "kind": "NON_NULL", 661 | "name": null, 662 | "ofType": { 663 | "kind": "SCALAR", 664 | "name": "String", 665 | "ofType": null 666 | } 667 | }, 668 | "defaultValue": null 669 | } 670 | ], 671 | "type": { 672 | "kind": "OBJECT", 673 | "name": "CommentType", 674 | "ofType": null 675 | } 676 | }, 677 | { 678 | "name": "createMembers", 679 | "args": [ 680 | { 681 | "name": "members", 682 | "type": { 683 | "kind": "NON_NULL", 684 | "name": null, 685 | "ofType": { 686 | "kind": "LIST", 687 | "name": null, 688 | "ofType": { 689 | "kind": "NON_NULL", 690 | "name": null, 691 | "ofType": { 692 | "kind": "INPUT_OBJECT", 693 | "name": "NewMember" 694 | } 695 | } 696 | } 697 | }, 698 | "defaultValue": null 699 | } 700 | ], 701 | "type": { 702 | "kind": "LIST", 703 | "name": null, 704 | "ofType": { 705 | "kind": "UNION", 706 | "name": "Member", 707 | "ofType": null 708 | } 709 | } 710 | } 711 | ], 712 | "inputFields": null, 713 | "interfaces": [], 714 | "enumValues": null, 715 | "possibleTypes": null 716 | }, 717 | { 718 | "kind": "INPUT_OBJECT", 719 | "name": "NewPost", 720 | "fields": null, 721 | "inputFields": [ 722 | { 723 | "name": "_id", 724 | "type": { 725 | "kind": "NON_NULL", 726 | "name": null, 727 | "ofType": { 728 | "kind": "SCALAR", 729 | "name": "String", 730 | "ofType": null 731 | } 732 | }, 733 | "defaultValue": null 734 | }, 735 | { 736 | "name": "content", 737 | "type": { 738 | "kind": "SCALAR", 739 | "name": "String", 740 | "ofType": null 741 | }, 742 | "defaultValue": null 743 | }, 744 | { 745 | "name": "title", 746 | "type": { 747 | "kind": "SCALAR", 748 | "name": "String", 749 | "ofType": null 750 | }, 751 | "defaultValue": null 752 | }, 753 | { 754 | "name": "category", 755 | "type": { 756 | "kind": "SCALAR", 757 | "name": "String", 758 | "ofType": null 759 | }, 760 | "defaultValue": null 761 | } 762 | ], 763 | "interfaces": null, 764 | "enumValues": null, 765 | "possibleTypes": null 766 | }, 767 | { 768 | "kind": "OBJECT", 769 | "name": "CreatePostMutationPayload", 770 | "fields": [ 771 | { 772 | "name": "post", 773 | "args": [], 774 | "type": { 775 | "kind": "OBJECT", 776 | "name": "PostType", 777 | "ofType": null 778 | } 779 | }, 780 | { 781 | "name": "postCount", 782 | "args": [], 783 | "type": { 784 | "kind": "SCALAR", 785 | "name": "Int", 786 | "ofType": null 787 | } 788 | } 789 | ], 790 | "inputFields": null, 791 | "interfaces": [], 792 | "enumValues": null, 793 | "possibleTypes": null 794 | }, 795 | { 796 | "kind": "INPUT_OBJECT", 797 | "name": "NewMember", 798 | "fields": null, 799 | "inputFields": [ 800 | { 801 | "name": "_id", 802 | "type": { 803 | "kind": "NON_NULL", 804 | "name": null, 805 | "ofType": { 806 | "kind": "SCALAR", 807 | "name": "String", 808 | "ofType": null 809 | } 810 | }, 811 | "defaultValue": null 812 | }, 813 | { 814 | "name": "name", 815 | "type": { 816 | "kind": "SCALAR", 817 | "name": "String", 818 | "ofType": null 819 | }, 820 | "defaultValue": null 821 | }, 822 | { 823 | "name": "ownerId", 824 | "type": { 825 | "kind": "SCALAR", 826 | "name": "String", 827 | "ofType": null 828 | }, 829 | "defaultValue": null 830 | }, 831 | { 832 | "name": "members", 833 | "type": { 834 | "kind": "LIST", 835 | "name": null, 836 | "ofType": { 837 | "kind": "SCALAR", 838 | "name": "String", 839 | "ofType": null 840 | } 841 | }, 842 | "defaultValue": null 843 | }, 844 | { 845 | "name": "twitterHandle", 846 | "type": { 847 | "kind": "SCALAR", 848 | "name": "String", 849 | "ofType": null 850 | }, 851 | "defaultValue": null 852 | } 853 | ], 854 | "interfaces": null, 855 | "enumValues": null, 856 | "possibleTypes": null 857 | }, 858 | { 859 | "kind": "OBJECT", 860 | "name": "__Schema", 861 | "fields": [ 862 | { 863 | "name": "types", 864 | "args": [], 865 | "type": { 866 | "kind": "NON_NULL", 867 | "name": null, 868 | "ofType": { 869 | "kind": "LIST", 870 | "name": null, 871 | "ofType": { 872 | "kind": "NON_NULL", 873 | "name": null, 874 | "ofType": { 875 | "kind": "OBJECT", 876 | "name": "__Type" 877 | } 878 | } 879 | } 880 | } 881 | }, 882 | { 883 | "name": "queryType", 884 | "args": [], 885 | "type": { 886 | "kind": "NON_NULL", 887 | "name": null, 888 | "ofType": { 889 | "kind": "OBJECT", 890 | "name": "__Type", 891 | "ofType": null 892 | } 893 | } 894 | }, 895 | { 896 | "name": "mutationType", 897 | "args": [], 898 | "type": { 899 | "kind": "OBJECT", 900 | "name": "__Type", 901 | "ofType": null 902 | } 903 | }, 904 | { 905 | "name": "subscriptionType", 906 | "args": [], 907 | "type": { 908 | "kind": "OBJECT", 909 | "name": "__Type", 910 | "ofType": null 911 | } 912 | }, 913 | { 914 | "name": "directives", 915 | "args": [], 916 | "type": { 917 | "kind": "NON_NULL", 918 | "name": null, 919 | "ofType": { 920 | "kind": "LIST", 921 | "name": null, 922 | "ofType": { 923 | "kind": "NON_NULL", 924 | "name": null, 925 | "ofType": { 926 | "kind": "OBJECT", 927 | "name": "__Directive" 928 | } 929 | } 930 | } 931 | } 932 | } 933 | ], 934 | "inputFields": null, 935 | "interfaces": [], 936 | "enumValues": null, 937 | "possibleTypes": null 938 | }, 939 | { 940 | "kind": "OBJECT", 941 | "name": "__Type", 942 | "fields": [ 943 | { 944 | "name": "kind", 945 | "args": [], 946 | "type": { 947 | "kind": "NON_NULL", 948 | "name": null, 949 | "ofType": { 950 | "kind": "ENUM", 951 | "name": "__TypeKind", 952 | "ofType": null 953 | } 954 | } 955 | }, 956 | { 957 | "name": "name", 958 | "args": [], 959 | "type": { 960 | "kind": "SCALAR", 961 | "name": "String", 962 | "ofType": null 963 | } 964 | }, 965 | { 966 | "name": "description", 967 | "args": [], 968 | "type": { 969 | "kind": "SCALAR", 970 | "name": "String", 971 | "ofType": null 972 | } 973 | }, 974 | { 975 | "name": "fields", 976 | "args": [ 977 | { 978 | "name": "includeDeprecated", 979 | "type": { 980 | "kind": "SCALAR", 981 | "name": "Boolean", 982 | "ofType": null 983 | }, 984 | "defaultValue": "false" 985 | } 986 | ], 987 | "type": { 988 | "kind": "LIST", 989 | "name": null, 990 | "ofType": { 991 | "kind": "NON_NULL", 992 | "name": null, 993 | "ofType": { 994 | "kind": "OBJECT", 995 | "name": "__Field", 996 | "ofType": null 997 | } 998 | } 999 | } 1000 | }, 1001 | { 1002 | "name": "interfaces", 1003 | "args": [], 1004 | "type": { 1005 | "kind": "LIST", 1006 | "name": null, 1007 | "ofType": { 1008 | "kind": "NON_NULL", 1009 | "name": null, 1010 | "ofType": { 1011 | "kind": "OBJECT", 1012 | "name": "__Type", 1013 | "ofType": null 1014 | } 1015 | } 1016 | } 1017 | }, 1018 | { 1019 | "name": "possibleTypes", 1020 | "args": [], 1021 | "type": { 1022 | "kind": "LIST", 1023 | "name": null, 1024 | "ofType": { 1025 | "kind": "NON_NULL", 1026 | "name": null, 1027 | "ofType": { 1028 | "kind": "OBJECT", 1029 | "name": "__Type", 1030 | "ofType": null 1031 | } 1032 | } 1033 | } 1034 | }, 1035 | { 1036 | "name": "enumValues", 1037 | "args": [ 1038 | { 1039 | "name": "includeDeprecated", 1040 | "type": { 1041 | "kind": "SCALAR", 1042 | "name": "Boolean", 1043 | "ofType": null 1044 | }, 1045 | "defaultValue": "false" 1046 | } 1047 | ], 1048 | "type": { 1049 | "kind": "LIST", 1050 | "name": null, 1051 | "ofType": { 1052 | "kind": "NON_NULL", 1053 | "name": null, 1054 | "ofType": { 1055 | "kind": "OBJECT", 1056 | "name": "__EnumValue", 1057 | "ofType": null 1058 | } 1059 | } 1060 | } 1061 | }, 1062 | { 1063 | "name": "inputFields", 1064 | "args": [], 1065 | "type": { 1066 | "kind": "LIST", 1067 | "name": null, 1068 | "ofType": { 1069 | "kind": "NON_NULL", 1070 | "name": null, 1071 | "ofType": { 1072 | "kind": "OBJECT", 1073 | "name": "__InputValue", 1074 | "ofType": null 1075 | } 1076 | } 1077 | } 1078 | }, 1079 | { 1080 | "name": "ofType", 1081 | "args": [], 1082 | "type": { 1083 | "kind": "OBJECT", 1084 | "name": "__Type", 1085 | "ofType": null 1086 | } 1087 | } 1088 | ], 1089 | "inputFields": null, 1090 | "interfaces": [], 1091 | "enumValues": null, 1092 | "possibleTypes": null 1093 | }, 1094 | { 1095 | "kind": "ENUM", 1096 | "name": "__TypeKind", 1097 | "fields": null, 1098 | "inputFields": null, 1099 | "interfaces": null, 1100 | "enumValues": [ 1101 | { 1102 | "name": "SCALAR" 1103 | }, 1104 | { 1105 | "name": "OBJECT" 1106 | }, 1107 | { 1108 | "name": "INTERFACE" 1109 | }, 1110 | { 1111 | "name": "UNION" 1112 | }, 1113 | { 1114 | "name": "ENUM" 1115 | }, 1116 | { 1117 | "name": "INPUT_OBJECT" 1118 | }, 1119 | { 1120 | "name": "LIST" 1121 | }, 1122 | { 1123 | "name": "NON_NULL" 1124 | } 1125 | ], 1126 | "possibleTypes": null 1127 | }, 1128 | { 1129 | "kind": "OBJECT", 1130 | "name": "__Field", 1131 | "fields": [ 1132 | { 1133 | "name": "name", 1134 | "args": [], 1135 | "type": { 1136 | "kind": "NON_NULL", 1137 | "name": null, 1138 | "ofType": { 1139 | "kind": "SCALAR", 1140 | "name": "String", 1141 | "ofType": null 1142 | } 1143 | } 1144 | }, 1145 | { 1146 | "name": "description", 1147 | "args": [], 1148 | "type": { 1149 | "kind": "SCALAR", 1150 | "name": "String", 1151 | "ofType": null 1152 | } 1153 | }, 1154 | { 1155 | "name": "args", 1156 | "args": [], 1157 | "type": { 1158 | "kind": "NON_NULL", 1159 | "name": null, 1160 | "ofType": { 1161 | "kind": "LIST", 1162 | "name": null, 1163 | "ofType": { 1164 | "kind": "NON_NULL", 1165 | "name": null, 1166 | "ofType": { 1167 | "kind": "OBJECT", 1168 | "name": "__InputValue" 1169 | } 1170 | } 1171 | } 1172 | } 1173 | }, 1174 | { 1175 | "name": "type", 1176 | "args": [], 1177 | "type": { 1178 | "kind": "NON_NULL", 1179 | "name": null, 1180 | "ofType": { 1181 | "kind": "OBJECT", 1182 | "name": "__Type", 1183 | "ofType": null 1184 | } 1185 | } 1186 | }, 1187 | { 1188 | "name": "isDeprecated", 1189 | "args": [], 1190 | "type": { 1191 | "kind": "NON_NULL", 1192 | "name": null, 1193 | "ofType": { 1194 | "kind": "SCALAR", 1195 | "name": "Boolean", 1196 | "ofType": null 1197 | } 1198 | } 1199 | }, 1200 | { 1201 | "name": "deprecationReason", 1202 | "args": [], 1203 | "type": { 1204 | "kind": "SCALAR", 1205 | "name": "String", 1206 | "ofType": null 1207 | } 1208 | } 1209 | ], 1210 | "inputFields": null, 1211 | "interfaces": [], 1212 | "enumValues": null, 1213 | "possibleTypes": null 1214 | }, 1215 | { 1216 | "kind": "OBJECT", 1217 | "name": "__InputValue", 1218 | "fields": [ 1219 | { 1220 | "name": "name", 1221 | "args": [], 1222 | "type": { 1223 | "kind": "NON_NULL", 1224 | "name": null, 1225 | "ofType": { 1226 | "kind": "SCALAR", 1227 | "name": "String", 1228 | "ofType": null 1229 | } 1230 | } 1231 | }, 1232 | { 1233 | "name": "description", 1234 | "args": [], 1235 | "type": { 1236 | "kind": "SCALAR", 1237 | "name": "String", 1238 | "ofType": null 1239 | } 1240 | }, 1241 | { 1242 | "name": "type", 1243 | "args": [], 1244 | "type": { 1245 | "kind": "NON_NULL", 1246 | "name": null, 1247 | "ofType": { 1248 | "kind": "OBJECT", 1249 | "name": "__Type", 1250 | "ofType": null 1251 | } 1252 | } 1253 | }, 1254 | { 1255 | "name": "defaultValue", 1256 | "args": [], 1257 | "type": { 1258 | "kind": "SCALAR", 1259 | "name": "String", 1260 | "ofType": null 1261 | } 1262 | } 1263 | ], 1264 | "inputFields": null, 1265 | "interfaces": [], 1266 | "enumValues": null, 1267 | "possibleTypes": null 1268 | }, 1269 | { 1270 | "kind": "OBJECT", 1271 | "name": "__EnumValue", 1272 | "fields": [ 1273 | { 1274 | "name": "name", 1275 | "args": [], 1276 | "type": { 1277 | "kind": "NON_NULL", 1278 | "name": null, 1279 | "ofType": { 1280 | "kind": "SCALAR", 1281 | "name": "String", 1282 | "ofType": null 1283 | } 1284 | } 1285 | }, 1286 | { 1287 | "name": "description", 1288 | "args": [], 1289 | "type": { 1290 | "kind": "SCALAR", 1291 | "name": "String", 1292 | "ofType": null 1293 | } 1294 | }, 1295 | { 1296 | "name": "isDeprecated", 1297 | "args": [], 1298 | "type": { 1299 | "kind": "NON_NULL", 1300 | "name": null, 1301 | "ofType": { 1302 | "kind": "SCALAR", 1303 | "name": "Boolean", 1304 | "ofType": null 1305 | } 1306 | } 1307 | }, 1308 | { 1309 | "name": "deprecationReason", 1310 | "args": [], 1311 | "type": { 1312 | "kind": "SCALAR", 1313 | "name": "String", 1314 | "ofType": null 1315 | } 1316 | } 1317 | ], 1318 | "inputFields": null, 1319 | "interfaces": [], 1320 | "enumValues": null, 1321 | "possibleTypes": null 1322 | }, 1323 | { 1324 | "kind": "OBJECT", 1325 | "name": "__Directive", 1326 | "fields": [ 1327 | { 1328 | "name": "name", 1329 | "args": [], 1330 | "type": { 1331 | "kind": "NON_NULL", 1332 | "name": null, 1333 | "ofType": { 1334 | "kind": "SCALAR", 1335 | "name": "String", 1336 | "ofType": null 1337 | } 1338 | } 1339 | }, 1340 | { 1341 | "name": "description", 1342 | "args": [], 1343 | "type": { 1344 | "kind": "SCALAR", 1345 | "name": "String", 1346 | "ofType": null 1347 | } 1348 | }, 1349 | { 1350 | "name": "locations", 1351 | "args": [], 1352 | "type": { 1353 | "kind": "NON_NULL", 1354 | "name": null, 1355 | "ofType": { 1356 | "kind": "LIST", 1357 | "name": null, 1358 | "ofType": { 1359 | "kind": "NON_NULL", 1360 | "name": null, 1361 | "ofType": { 1362 | "kind": "ENUM", 1363 | "name": "__DirectiveLocation" 1364 | } 1365 | } 1366 | } 1367 | } 1368 | }, 1369 | { 1370 | "name": "args", 1371 | "args": [], 1372 | "type": { 1373 | "kind": "NON_NULL", 1374 | "name": null, 1375 | "ofType": { 1376 | "kind": "LIST", 1377 | "name": null, 1378 | "ofType": { 1379 | "kind": "NON_NULL", 1380 | "name": null, 1381 | "ofType": { 1382 | "kind": "OBJECT", 1383 | "name": "__InputValue" 1384 | } 1385 | } 1386 | } 1387 | } 1388 | } 1389 | ], 1390 | "inputFields": null, 1391 | "interfaces": [], 1392 | "enumValues": null, 1393 | "possibleTypes": null 1394 | }, 1395 | { 1396 | "kind": "ENUM", 1397 | "name": "__DirectiveLocation", 1398 | "fields": null, 1399 | "inputFields": null, 1400 | "interfaces": null, 1401 | "enumValues": [ 1402 | { 1403 | "name": "QUERY" 1404 | }, 1405 | { 1406 | "name": "MUTATION" 1407 | }, 1408 | { 1409 | "name": "SUBSCRIPTION" 1410 | }, 1411 | { 1412 | "name": "FIELD" 1413 | }, 1414 | { 1415 | "name": "FRAGMENT_DEFINITION" 1416 | }, 1417 | { 1418 | "name": "FRAGMENT_SPREAD" 1419 | }, 1420 | { 1421 | "name": "INLINE_FRAGMENT" 1422 | }, 1423 | { 1424 | "name": "SCHEMA" 1425 | }, 1426 | { 1427 | "name": "SCALAR" 1428 | }, 1429 | { 1430 | "name": "OBJECT" 1431 | }, 1432 | { 1433 | "name": "FIELD_DEFINITION" 1434 | }, 1435 | { 1436 | "name": "ARGUMENT_DEFINITION" 1437 | }, 1438 | { 1439 | "name": "INTERFACE" 1440 | }, 1441 | { 1442 | "name": "UNION" 1443 | }, 1444 | { 1445 | "name": "ENUM" 1446 | }, 1447 | { 1448 | "name": "ENUM_VALUE" 1449 | }, 1450 | { 1451 | "name": "INPUT_OBJECT" 1452 | }, 1453 | { 1454 | "name": "INPUT_FIELD_DEFINITION" 1455 | } 1456 | ], 1457 | "possibleTypes": null 1458 | } 1459 | ], 1460 | "directives": [ 1461 | { 1462 | "name": "include", 1463 | "args": [ 1464 | { 1465 | "name": "if", 1466 | "type": { 1467 | "kind": "NON_NULL", 1468 | "name": null, 1469 | "ofType": { 1470 | "kind": "SCALAR", 1471 | "name": "Boolean", 1472 | "ofType": null 1473 | } 1474 | }, 1475 | "defaultValue": null 1476 | } 1477 | ], 1478 | "onOperation": false, 1479 | "onFragment": true, 1480 | "onField": true 1481 | }, 1482 | { 1483 | "name": "skip", 1484 | "args": [ 1485 | { 1486 | "name": "if", 1487 | "type": { 1488 | "kind": "NON_NULL", 1489 | "name": null, 1490 | "ofType": { 1491 | "kind": "SCALAR", 1492 | "name": "Boolean", 1493 | "ofType": null 1494 | } 1495 | }, 1496 | "defaultValue": null 1497 | } 1498 | ], 1499 | "onOperation": false, 1500 | "onFragment": true, 1501 | "onField": true 1502 | }, 1503 | { 1504 | "name": "deprecated", 1505 | "args": [ 1506 | { 1507 | "name": "reason", 1508 | "type": { 1509 | "kind": "SCALAR", 1510 | "name": "String", 1511 | "ofType": null 1512 | }, 1513 | "defaultValue": "\"No longer supported\"" 1514 | } 1515 | ], 1516 | "onOperation": false, 1517 | "onFragment": false, 1518 | "onField": false 1519 | } 1520 | ] 1521 | } -------------------------------------------------------------------------------- /src/index.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import {render} from 'react-dom'; 3 | import App from './App'; 4 | import CashayBook from './CashayBook'; 5 | import {createStore, compose, combineReducers} from 'redux' 6 | import {Provider} from 'react-redux'; 7 | import {cashay, cashayReducer, HTTPTransport} from 'cashay'; 8 | import gqlSchema from './schema.js'; 9 | const clientSchema = require('cashay!./getCashaySchema.js'); 10 | import {graphql} from 'graphql'; 11 | 12 | const rootReducer = combineReducers({ 13 | cashay: cashayReducer 14 | }); 15 | 16 | const transport = new HTTPTransport('/graphql'); 17 | 18 | const devtoolsExt = global.devToolsExtension && global.devToolsExtension(); 19 | const store = createStore(rootReducer, {}, compose( 20 | devtoolsExt || (f => f) 21 | )); 22 | 23 | cashay.create({ 24 | store, 25 | schema: clientSchema, 26 | idFieldName: '_id', 27 | paginationWords: {before: 'beforeCursor', after: 'afterCursor'}, 28 | transport 29 | }); 30 | 31 | render( 32 | 33 | 34 | 35 | , document.getElementById('root') 36 | ); 37 | 38 | // use this for graphiql 39 | // render(, document.getElementById('root')); 40 | -------------------------------------------------------------------------------- /src/recentPosts.js: -------------------------------------------------------------------------------- 1 | import React, {Component} from 'react'; 2 | import {connect} from 'react-redux'; 3 | import {cashay} from 'cashay'; 4 | import SinglePost from './SinglePost'; 5 | import uuid from 'node-uuid'; 6 | 7 | const queryRecentPosts = ` 8 | query($count: Int) { 9 | recentPosts: getRecentPosts(first: $count) { 10 | _id, 11 | title, 12 | cursor, 13 | spanishTitle: title(language:"spanish"), 14 | reverseSpanishTitle: title(language:"spanish", inReverse:true) 15 | } 16 | }`; 17 | 18 | const customMutations = { 19 | removePostById: ` 20 | mutation($postId: String!) { 21 | removePostById(postId: $postId) { 22 | removedPostId 23 | } 24 | }` 25 | }; 26 | 27 | const mutationHandlers = { 28 | createPost(optimisticVariables, docFromServer, currentResponse, getEntities, invalidate) { 29 | if (optimisticVariables) { 30 | const {newPost} = optimisticVariables; 31 | const newOptimisticPost = Object.assign({}, newPost, { 32 | title: `${newPost.title} OPTIMISTICAL!` 33 | }); 34 | currentResponse.recentPosts.unshift(newOptimisticPost); 35 | return currentResponse; 36 | } 37 | // const foo = getEntities('PostType'); 38 | const optimisticDocIdx = currentResponse.recentPosts.findIndex(post => post._id === docFromServer.createPost.post._id); 39 | currentResponse.recentPosts[optimisticDocIdx] = docFromServer.createPost.post; 40 | return currentResponse; 41 | }, 42 | removePostById(optimisticVariables, docFromServer, currentResponse) { 43 | // example of not using optimistic updates 44 | const idRemoved = docFromServer && docFromServer.removePostById.removedPostId; 45 | if (idRemoved) { 46 | const idx = currentResponse.recentPosts.findIndex(post => post._id === idRemoved); 47 | currentResponse.recentPosts.splice(idx, 1); 48 | return currentResponse; 49 | } 50 | } 51 | }; 52 | 53 | const mapStateToProps = (state, props) => { 54 | return { 55 | cashay: cashay.query(queryRecentPosts, {variables: {count: 2}, mutationHandlers, customMutations, component: 'RecentPosts'}) 56 | } 57 | }; 58 | 59 | @connect(mapStateToProps) 60 | export default class RecentPosts extends Component { 61 | render() { 62 | const {recentPosts} = this.props.cashay.data; 63 | return ( 64 |
65 |

RECENT POSTS

66 | 67 |
{recentPosts.map(post => )}
68 | {recentPosts.EOF ? null : 69 | 70 | } 71 |
72 | ); 73 | } 74 | 75 | createPost = () => { 76 | const variables = { 77 | newPost: { 78 | _id: uuid.v4(), 79 | title: 'Woo another post!' 80 | } 81 | }; 82 | cashay.mutate('createPost', {variables}); 83 | }; 84 | 85 | get2More = () => { 86 | const {setVariables} = this.props.cashay; 87 | setVariables(currentVariables => { 88 | return { 89 | count: currentVariables.count + 2 90 | } 91 | }) 92 | } 93 | } 94 | -------------------------------------------------------------------------------- /src/schema.js: -------------------------------------------------------------------------------- 1 | import PostDB from './data/posts'; 2 | import AuthorDB from './data/authors'; 3 | import GroupDB from './data/groups'; 4 | import CommentDB from './data/comments'; 5 | 6 | import { 7 | GraphQLList, 8 | GraphQLObjectType, 9 | GraphQLSchema, 10 | GraphQLString, 11 | GraphQLInt, 12 | GraphQLEnumType, 13 | GraphQLNonNull, 14 | GraphQLInterfaceType, 15 | GraphQLUnionType, 16 | GraphQLInputObjectType, 17 | GraphQLBoolean, 18 | GraphQLFloat 19 | } from 'graphql'; 20 | 21 | const handlePaginationArgs = ({beforeCursor, afterCursor, first, last}, objs) => { 22 | let arrayPartial; 23 | if (first) { 24 | const docsToSend = first + 1; 25 | const startingIdx = objs.findIndex(obj => obj.cursor === afterCursor) + 1; 26 | arrayPartial = objs.slice(startingIdx, startingIdx + docsToSend); 27 | } else if (last) { 28 | const docsToSend = last + 1; 29 | let endingIdx = objs.findIndex(obj => obj.cursor === beforeCursor); 30 | endingIdx = endingIdx === -1 ? objs.length : endingIdx; 31 | const start = Math.max(0, endingIdx - docsToSend); 32 | arrayPartial = objs.slice(start, endingIdx); 33 | } else { 34 | arrayPartial = objs; 35 | } 36 | return arrayPartial; 37 | }; 38 | 39 | const CategoryType = new GraphQLEnumType({ 40 | name: "CategoryType", 41 | description: "A CategoryType of the blog", 42 | values: { 43 | HOT_STUFF: {value: "hot stuff"}, 44 | ICE_COLD: {value: "ice cold"} 45 | } 46 | }); 47 | 48 | const AuthorType = new GraphQLObjectType({ 49 | name: "AuthorType", 50 | description: "Represent the type of an author of a blog post or a comment", 51 | fields: () => ({ 52 | _id: {type: GraphQLString}, 53 | name: {type: GraphQLString}, 54 | twitterHandle: {type: GraphQLString} 55 | }) 56 | }); 57 | 58 | const HasAuthorType = new GraphQLInterfaceType({ 59 | name: "HasAuthorType", 60 | description: "This type has an author", 61 | fields: () => ({ 62 | author: {type: AuthorType} 63 | }), 64 | resolveType: (obj) => { 65 | if (obj.title) { 66 | return PostType; 67 | } else if (obj.replies) { 68 | return CommentType; 69 | } 70 | } 71 | }); 72 | 73 | const CommentType = new GraphQLObjectType({ 74 | name: "CommentType", 75 | interfaces: [HasAuthorType], 76 | description: "Represent the type of a comment", 77 | fields: () => ({ 78 | _id: {type: GraphQLString}, 79 | content: {type: GraphQLString}, 80 | author: { 81 | type: AuthorType, 82 | resolve: function({author}) { 83 | return AuthorDB.find(doc => doc.author === author); 84 | } 85 | }, 86 | createdAt: {type: GraphQLFloat}, 87 | cursor: {type: GraphQLString}, 88 | karma: {type: GraphQLInt}, 89 | postId: {type: GraphQLString} 90 | }) 91 | }); 92 | 93 | const GroupType = new GraphQLObjectType({ 94 | name: "Group", 95 | description: "A group with an owner and members", 96 | args: { 97 | groupId: {type: GraphQLString} 98 | }, 99 | fields: () => ({ 100 | _id: {type: GraphQLString}, 101 | owner: { 102 | type: MemberType, 103 | resolve(source) { 104 | let author; 105 | author = AuthorDB.find(doc => doc._id === source.ownerId); 106 | if (!author) { 107 | author = GroupDB.find(doc => doc._id === source.ownerId); 108 | } 109 | return author; 110 | } 111 | }, 112 | members: { 113 | type: new GraphQLList(MemberType), 114 | resolve(source) { 115 | return source.members.map(member => { 116 | let author; 117 | author = AuthorDB.find(doc => doc._id === member); 118 | if (!author) { 119 | author = GroupDB.find(doc => doc._id === member); 120 | } 121 | return author; 122 | }); 123 | } 124 | } 125 | }) 126 | }); 127 | 128 | const MemberType = new GraphQLUnionType({ 129 | name: "Member", 130 | resolveType(obj) { 131 | if (obj.hasOwnProperty('ownerId')) { 132 | return GroupType 133 | } else { 134 | return AuthorType; 135 | } 136 | }, 137 | types: [GroupType, AuthorType] 138 | }); 139 | 140 | 141 | const PostType = new GraphQLObjectType({ 142 | name: "PostType", 143 | interfaces: [HasAuthorType], 144 | description: "Represent the type of a blog post", 145 | fields: () => ({ 146 | _id: {type: GraphQLString}, 147 | title: { 148 | type: GraphQLString, 149 | args: { 150 | language: {type: GraphQLString, description: "Language of the title"}, 151 | inReverse: {type: GraphQLBoolean, description: 'give the title in reverse'} 152 | }, 153 | resolve(source, args) { 154 | if (args.language === 'spanish') { 155 | if (args.inReverse) { 156 | return source.title_ES.split('').reverse().join(''); 157 | } 158 | return source.title_ES; 159 | } 160 | if (args.inReverse) { 161 | return source.title.split('').reverse().join(''); 162 | } 163 | return source.title; 164 | } 165 | }, 166 | category: {type: CategoryType}, 167 | content: {type: GraphQLString}, 168 | createdAt: { 169 | type: GraphQLInt, 170 | args: { 171 | dateOptions: {type: DateOptionsType, description: "example of a subfield with an input obj"} 172 | }, 173 | resolve(source) { 174 | return source.createdAt 175 | } 176 | }, 177 | comments: { 178 | type: new GraphQLList(CommentType), 179 | args: { 180 | beforeCursor: {type: GraphQLString, description: 'the cursor coming from the back'}, 181 | afterCursor: {type: GraphQLString, description: 'the cursor coming from the front'}, 182 | first: {type: GraphQLInt, description: "Limit the comments from the front"}, 183 | last: {type: GraphQLInt, description: "Limit the comments from the back"} 184 | }, 185 | resolve: function(post, args) { 186 | return handlePaginationArgs(args, CommentDB) 187 | } 188 | }, 189 | author: { 190 | type: AuthorType, 191 | resolve: function({author}) { 192 | return AuthorDB.find(doc => doc._id === author); 193 | } 194 | }, 195 | cursor: {type: GraphQLString} 196 | }) 197 | }); 198 | 199 | const CreatePostMutationPayload = new GraphQLObjectType({ 200 | name: "CreatePostMutationPayload", 201 | description: "Payload for creating a post", 202 | fields: () => ({ 203 | post: {type: PostType}, 204 | postCount: {type: GraphQLInt} 205 | }) 206 | }); 207 | 208 | const RemovePostMutationPayload = new GraphQLObjectType({ 209 | name: "RemovePostMutationPayload", 210 | description: "Payload for removing a post", 211 | fields: () => ({ 212 | removedPostId: {type: GraphQLString}, 213 | postCount: {type: GraphQLInt} 214 | }) 215 | }); 216 | 217 | const NewPost = new GraphQLInputObjectType({ 218 | name: "NewPost", 219 | description: "input object for a new post", 220 | fields: () => ({ 221 | _id: {type: new GraphQLNonNull(GraphQLString)}, 222 | content: {type: GraphQLString}, 223 | title: {type: GraphQLString}, 224 | category: {type: GraphQLString} 225 | }) 226 | }); 227 | 228 | const DateOptionsType = new GraphQLInputObjectType({ 229 | name: "DateOptions", 230 | description: "formatting options for the date", 231 | fields: () => ({ 232 | day: {type: GraphQLBoolean}, 233 | month: {type: GraphQLBoolean}, 234 | year: {type: GraphQLBoolean} 235 | }) 236 | }); 237 | 238 | const NewMember = new GraphQLInputObjectType({ 239 | name: "NewMember", 240 | description: "input object for a new member", 241 | fields: () => ({ 242 | _id: {type: new GraphQLNonNull(GraphQLString)}, 243 | name: {type: GraphQLString}, 244 | ownerId: {type: GraphQLString}, 245 | members: {type: new GraphQLList(GraphQLString)}, 246 | twitterHandle: {type: GraphQLString} 247 | }) 248 | }); 249 | 250 | const Query = new GraphQLObjectType({ 251 | name: 'BlogSchema', 252 | description: "Root of the Blog Schema", 253 | fields: () => ({ 254 | getPostCount: { 255 | type: new GraphQLNonNull(GraphQLInt), 256 | description: "the number of posts currently in the db", 257 | resolve() { 258 | return PostDB.length; 259 | } 260 | }, 261 | getLatestPostId: { 262 | type: GraphQLString, 263 | description: "Latest post in the blog", 264 | resolve() { 265 | const sortedPosts = PostDB.sort((a, b) => b.createdAt - a.createdAt); 266 | return sortedPosts[0]._id; 267 | } 268 | }, 269 | getRecentPosts: { 270 | type: new GraphQLList(PostType), 271 | description: "Recent posts in the blog", 272 | args: { 273 | beforeCursor: {type: GraphQLString, description: 'the cursor coming from the back'}, 274 | afterCursor: {type: GraphQLString, description: 'the cursor coming from the front'}, 275 | first: {type: GraphQLInt, description: "Limit the comments from the front"}, 276 | last: {type: GraphQLInt, description: "Limit the comments from the back"} 277 | }, 278 | resolve(source, args, ref) { 279 | const sortedPosts = PostDB.sort((a, b) => b.createdAt - a.createdAt); 280 | 281 | return handlePaginationArgs(args, sortedPosts); 282 | } 283 | }, 284 | getPostById: { 285 | type: PostType, 286 | description: "PostType by _id", 287 | args: { 288 | _id: {type: new GraphQLNonNull(GraphQLString)} 289 | }, 290 | resolve: function(source, {_id}) { 291 | return PostDB.find(doc => doc._id === _id); 292 | } 293 | }, 294 | getGroup: { 295 | type: GroupType, 296 | args: { 297 | _id: {type: new GraphQLNonNull(GraphQLString)} 298 | }, 299 | resolve(source, {_id}) { 300 | return GroupDB.find(doc => doc._id === _id); 301 | } 302 | }, 303 | getCommentsByPostId: { 304 | type: new GraphQLList(CommentType), 305 | description: "Comments for a specific post", 306 | args: { 307 | postId: {type: new GraphQLNonNull(GraphQLString)} 308 | }, 309 | resolve: function(source, {postId}) { 310 | return CommentDB.filter(doc => doc.postId === postId); 311 | } 312 | }, 313 | }) 314 | }); 315 | 316 | const Mutation = new GraphQLObjectType({ 317 | name: "BlogMutations", 318 | fields: () => ({ 319 | createPost: { 320 | type: CreatePostMutationPayload, 321 | description: "Create a post", 322 | args: { 323 | newPost: {type: new GraphQLNonNull(NewPost)}, 324 | // this is wrong to break out the author, but useful for testing different arg types 325 | author: {type: GraphQLString} 326 | }, 327 | resolve(source, {newPost, author}) { 328 | const now = Date.now(); 329 | const post = Object.assign({}, newPost, { 330 | karma: 0, 331 | createdAt: now, 332 | title_ES: `${newPost.title} EN ESPANOL!`, 333 | cursor: `${now}chikachikow`, 334 | author 335 | }); 336 | PostDB.push(post); 337 | return { 338 | post, 339 | postCount: PostDB.length 340 | } 341 | } 342 | }, 343 | removePostById: { 344 | type: RemovePostMutationPayload, 345 | description: 'Remove a post', 346 | args: { 347 | postId: {type: new GraphQLNonNull(GraphQLString)} 348 | }, 349 | resolve(source, {postId}) { 350 | const removedPostIdx= PostDB.findIndex(doc => doc.postId === postId); 351 | let didRemove = false; 352 | if (removedPostIdx !== -1) { 353 | PostDB.splice(removedPostIdx,1); 354 | didRemove = true; 355 | } 356 | return { 357 | removedPostId: didRemove ? postId : null, 358 | postCount: Object.keys(PostDB).length 359 | }; 360 | } 361 | }, 362 | updatePost: { 363 | type: PostType, 364 | description: 'update a post', 365 | args: { 366 | post: {type: NewPost} 367 | }, 368 | resolve(source, {post}) { 369 | const storedPost = PostDB.find(doc => doc._id === post._id); 370 | if (storedPost) { 371 | const updatedKeys = Object.keys(post); 372 | updatedKeys.forEach(key => { 373 | const value = post[key]; 374 | if (value === null) { 375 | delete storedPost[key]; 376 | } else { 377 | storedPost[key] = value 378 | } 379 | }) 380 | } 381 | return storedPost; 382 | } 383 | }, 384 | createComment: { 385 | type: CommentType, 386 | description: "Comment on a post", 387 | args: { 388 | _id: {type: new GraphQLNonNull(GraphQLString)}, 389 | postId: {type: new GraphQLNonNull(GraphQLString)}, 390 | content: {type: new GraphQLNonNull(GraphQLString)} 391 | }, 392 | resolve(source, {content, postId, _id}) { 393 | const newPost = { 394 | _id, 395 | content, 396 | postId, 397 | karma: 0, 398 | author: 'a125', 399 | createdAt: Date.now() 400 | }; 401 | CommentDB.push(newPost); 402 | return newPost; 403 | } 404 | }, 405 | createMembers: { 406 | type: new GraphQLList(MemberType), 407 | description: "Create multiple members", 408 | args: { 409 | members: {type: new GraphQLNonNull(new GraphQLList(new GraphQLNonNull(NewMember)))} 410 | }, 411 | resolve(source, {members}) { 412 | return members; 413 | } 414 | } 415 | }) 416 | }); 417 | 418 | export default new GraphQLSchema({ 419 | query: Query, 420 | mutation: Mutation 421 | }); 422 | -------------------------------------------------------------------------------- /static/graphiql.css: -------------------------------------------------------------------------------- 1 | .postContainer { 2 | display: flex; 3 | flex-direction: row; 4 | } 5 | 6 | .recentPosts { 7 | margin: 16px; 8 | } 9 | 10 | .latestAndById { 11 | margin: 16px; 12 | } 13 | 14 | .showComments { 15 | font-size: .6em; 16 | text-align: right; 17 | color: blue; 18 | cursor: pointer; 19 | } 20 | 21 | .deleteMe { 22 | font-size: .6em; 23 | text-align: right; 24 | color: darkred; 25 | cursor: pointer; 26 | } 27 | 28 | .commentText { 29 | font-size: .8em; 30 | padding: 16px; 31 | } 32 | 33 | .newComment { 34 | font-size: .8em; 35 | padding-left: 16px; 36 | } 37 | 38 | html, body { 39 | height: 100%; 40 | margin: 0; 41 | overflow: hidden; 42 | width: 100%; 43 | font-family: system, -apple-system, 'San Francisco', '.SFNSDisplay-Regular', 'Segoe UI', Segoe, 'Segoe WP', 'Helvetica Neue', helvetica, 'Lucida Grande', arial, sans-serif; 44 | } 45 | 46 | 47 | 48 | #graphiql-container { 49 | color: #141823; 50 | width: 100%; 51 | display: -webkit-flex; 52 | display: flex; 53 | -webkit-flex-direction: row; 54 | flex-direction: row; 55 | height: 100%; 56 | font-size: 14px; 57 | } 58 | 59 | #graphiql-container .editorWrap { 60 | display: -webkit-flex; 61 | display: flex; 62 | -webkit-flex-direction: column; 63 | flex-direction: column; 64 | -webkit-flex: 1; 65 | flex: 1; 66 | } 67 | 68 | #graphiql-container .title { 69 | font-size: 18px; 70 | } 71 | 72 | #graphiql-container .title em { 73 | font-family: georgia; 74 | font-size: 19px; 75 | } 76 | 77 | #graphiql-container .topBarWrap { 78 | display: -webkit-flex; 79 | display: flex; 80 | -webkit-flex-direction: row; 81 | flex-direction: row; 82 | } 83 | 84 | #graphiql-container .topBar { 85 | background: -webkit-linear-gradient(#f7f7f7, #e2e2e2); 86 | background: linear-gradient(#f7f7f7, #e2e2e2); 87 | border-bottom: solid 1px #d0d0d0; 88 | cursor: default; 89 | height: 34px; 90 | padding: 7px 14px 6px; 91 | -webkit-user-select: none; 92 | user-select: none; 93 | display: -webkit-flex; 94 | display: flex; 95 | -webkit-flex-direction: row; 96 | flex-direction: row; 97 | -webkit-flex: 1; 98 | flex: 1; 99 | -webkit-align-items: center; 100 | align-items: center; 101 | } 102 | 103 | #graphiql-container .docExplorerShow { 104 | background: -webkit-linear-gradient(#f7f7f7, #e2e2e2); 105 | background: linear-gradient(#f7f7f7, #e2e2e2); 106 | border: none; 107 | border-bottom: solid 1px #d0d0d0; 108 | border-left: solid 1px rgba(0, 0, 0, 0.2); 109 | color: #3B5998; 110 | cursor: pointer; 111 | font-size: 14px; 112 | outline: 0; 113 | padding: 2px 20px 0 18px; 114 | } 115 | 116 | #graphiql-container .docExplorerShow:before { 117 | border-left: 2px solid #3B5998; 118 | border-top: 2px solid #3B5998; 119 | content: ''; 120 | display: inline-block; 121 | height: 9px; 122 | margin: 0 3px -1px 0; 123 | position: relative; 124 | width: 9px; 125 | -webkit-transform: rotate(-45deg); 126 | transform: rotate(-45deg); 127 | } 128 | 129 | #graphiql-container .editorBar { 130 | display: -webkit-flex; 131 | display: flex; 132 | -webkit-flex-direction: row; 133 | flex-direction: row; 134 | -webkit-flex: 1; 135 | flex: 1; 136 | } 137 | 138 | #graphiql-container .queryWrap { 139 | display: -webkit-flex; 140 | display: flex; 141 | -webkit-flex-direction: column; 142 | flex-direction: column; 143 | -webkit-flex: 1; 144 | flex: 1; 145 | } 146 | 147 | #graphiql-container .resultWrap { 148 | display: -webkit-flex; 149 | display: flex; 150 | -webkit-flex-direction: column; 151 | flex-direction: column; 152 | -webkit-flex: 1; 153 | flex: 1; 154 | border-left: solid 1px #e0e0e0; 155 | } 156 | 157 | #graphiql-container .docExplorerWrap { 158 | box-shadow: 0 0 8px rgba(0, 0, 0, 0.15); 159 | z-index: 3; 160 | position: relative; 161 | background: white; 162 | } 163 | 164 | #graphiql-container .docExplorerResizer { 165 | cursor: col-resize; 166 | height: 100%; 167 | left: -5px; 168 | position: absolute; 169 | top: 0; 170 | width: 10px; 171 | z-index: 10; 172 | } 173 | 174 | #graphiql-container .docExplorerHide { 175 | cursor: pointer; 176 | font-size: 18px; 177 | margin: -7px -8px -6px 0; 178 | padding: 18px 16px 15px 12px; 179 | } 180 | 181 | #graphiql-container .query-editor { 182 | -webkit-flex: 1; 183 | flex: 1; 184 | position: relative; 185 | } 186 | 187 | #graphiql-container .variable-editor { 188 | height: 30px; 189 | display: -webkit-flex; 190 | display: flex; 191 | -webkit-flex-direction: column; 192 | flex-direction: column; 193 | position: relative; 194 | } 195 | 196 | #graphiql-container .variable-editor-title { 197 | background: #eeeeee; 198 | border-bottom: solid 1px #d6d6d6; 199 | border-top: solid 1px #e0e0e0; 200 | color: #777; 201 | font-variant: small-caps; 202 | font-weight: bold; 203 | letter-spacing: 1px; 204 | line-height: 14px; 205 | padding: 6px 0 8px 43px; 206 | text-transform: lowercase; 207 | -webkit-user-select: none; 208 | user-select: none; 209 | } 210 | 211 | #graphiql-container .codemirrorWrap { 212 | -webkit-flex: 1; 213 | flex: 1; 214 | position: relative; 215 | } 216 | 217 | #graphiql-container .result-window { 218 | -webkit-flex: 1; 219 | flex: 1; 220 | position: relative; 221 | } 222 | 223 | #graphiql-container .footer { 224 | background: #f6f7f8; 225 | border-left: solid 1px #e0e0e0; 226 | border-top: solid 1px #e0e0e0; 227 | margin-left: 12px; 228 | position: relative; 229 | } 230 | 231 | #graphiql-container .footer:before { 232 | background: #eeeeee; 233 | bottom: 0; 234 | content: " "; 235 | left: -13px; 236 | position: absolute; 237 | top: -1px; 238 | width: 12px; 239 | } 240 | 241 | #graphiql-container .result-window .CodeMirror { 242 | background: #f6f7f8; 243 | } 244 | 245 | #graphiql-container .result-window .CodeMirror-gutters { 246 | background-color: #eeeeee; 247 | border-color: #e0e0e0; 248 | cursor: col-resize; 249 | } 250 | 251 | #graphiql-container .result-window .CodeMirror-foldgutter, 252 | #graphiql-container .result-window .CodeMirror-foldgutter-open:after, 253 | #graphiql-container .result-window .CodeMirror-foldgutter-folded:after { 254 | padding-left: 3px; 255 | } 256 | 257 | #graphiql-container .execute-button { 258 | background: -webkit-linear-gradient(#fdfdfd, #d2d3d6); 259 | background: linear-gradient(#fdfdfd, #d2d3d6); 260 | border: solid 1px rgba(0,0,0,0.25); 261 | border-radius: 17px; 262 | box-shadow: 0 1px 0 #fff; 263 | cursor: pointer; 264 | fill: #444; 265 | height: 34px; 266 | margin: 0 14px 0 28px; 267 | padding: 0; 268 | width: 34px; 269 | } 270 | 271 | #graphiql-container .execute-button:active { 272 | background: -webkit-linear-gradient(#e6e6e6, #c0c0c0); 273 | background: linear-gradient(#e6e6e6, #c0c0c0); 274 | box-shadow: 275 | 0 1px 0 #fff, 276 | inset 0 0 2px rgba(0, 0, 0, 0.3), 277 | inset 0 0 6px rgba(0, 0, 0, 0.2); 278 | } 279 | 280 | #graphiql-container .execute-button:focus { 281 | outline: 0; 282 | } 283 | 284 | #graphiql-container .CodeMirror-scroll { 285 | -webkit-overflow-scrolling: touch; 286 | } 287 | 288 | #graphiql-container .CodeMirror { 289 | position: absolute; 290 | top: 0; 291 | left: 0; 292 | height: 100%; 293 | width: 100%; 294 | font-size: 13px; 295 | font-family: 'Consolas', 'Inconsolata', 'Droid Sans Mono', 'Monaco', monospace; 296 | color: #141823; 297 | } 298 | 299 | #graphiql-container .CodeMirror-lines { 300 | padding: 20px 0; 301 | } 302 | 303 | .CodeMirror-hint-information .content { 304 | -webkit-box-orient: vertical; 305 | color: #141823; 306 | display: -webkit-box; 307 | font-family: system, -apple-system, 'San Francisco', '.SFNSDisplay-Regular', 'Segoe UI', Segoe, 'Segoe WP', 'Helvetica Neue', helvetica, 'Lucida Grande', arial, sans-serif; 308 | font-size: 13px; 309 | -webkit-line-clamp: 3; 310 | line-height: 16px; 311 | max-height: 48px; 312 | overflow: hidden; 313 | text-overflow: -o-ellipsis-lastline; 314 | } 315 | 316 | .CodeMirror-hint-information .content p:first-child { 317 | margin-top: 0; 318 | } 319 | 320 | .CodeMirror-hint-information .content p:last-child { 321 | margin-bottom: 0; 322 | } 323 | 324 | .CodeMirror-hint-information .infoType { 325 | color: #30a; 326 | margin-right: 0.5em; 327 | display: inline; 328 | cursor: pointer; 329 | } 330 | 331 | .autoInsertedLeaf.cm-property { 332 | padding: 2px 4px 1px; 333 | margin: -2px -4px -1px; 334 | border-radius: 2px; 335 | border-bottom: solid 2px rgba(255, 255, 255, 0); 336 | -webkit-animation-duration: 6s; 337 | -moz-animation-duration: 6s; 338 | animation-duration: 6s; 339 | -webkit-animation-name: insertionFade; 340 | -moz-animation-name: insertionFade; 341 | animation-name: insertionFade; 342 | } 343 | 344 | @-moz-keyframes insertionFade { 345 | from, to { 346 | background: rgba(255, 255, 255, 0); 347 | border-color: rgba(255, 255, 255, 0); 348 | } 349 | 350 | 15%, 85% { 351 | background: #fbffc9; 352 | border-color: #f0f3c0; 353 | } 354 | } 355 | 356 | @-webkit-keyframes insertionFade { 357 | from, to { 358 | background: rgba(255, 255, 255, 0); 359 | border-color: rgba(255, 255, 255, 0); 360 | } 361 | 362 | 15%, 85% { 363 | background: #fbffc9; 364 | border-color: #f0f3c0; 365 | } 366 | } 367 | 368 | @keyframes insertionFade { 369 | from, to { 370 | background: rgba(255, 255, 255, 0); 371 | border-color: rgba(255, 255, 255, 0); 372 | } 373 | 374 | 15%, 85% { 375 | background: #fbffc9; 376 | border-color: #f0f3c0; 377 | } 378 | } 379 | 380 | div.CodeMirror-lint-tooltip { 381 | background-color: white; 382 | color: #141823; 383 | border: 0; 384 | border-radius: 2px; 385 | -webkit-box-shadow: 0 1px 3px rgba(0, 0, 0, 0.45); 386 | -moz-box-shadow: 0 1px 3px rgba(0, 0, 0, 0.45); 387 | box-shadow: 0 1px 3px rgba(0, 0, 0, 0.45); 388 | font-family: system, -apple-system, 'San Francisco', '.SFNSDisplay-Regular', 'Segoe UI', Segoe, 'Segoe WP', 'Helvetica Neue', helvetica, 'Lucida Grande', arial, sans-serif; 389 | font-size: 13px; 390 | line-height: 16px; 391 | padding: 6px 10px; 392 | opacity: 0; 393 | transition: opacity 0.15s; 394 | -moz-transition: opacity 0.15s; 395 | -webkit-transition: opacity 0.15s; 396 | -o-transition: opacity 0.15s; 397 | -ms-transition: opacity 0.15s; 398 | } 399 | 400 | div.CodeMirror-lint-message-error, div.CodeMirror-lint-message-warning { 401 | padding-left: 23px; 402 | } 403 | 404 | /* COLORS */ 405 | 406 | #graphiql-container .CodeMirror-foldmarker { 407 | border-radius: 4px; 408 | background: #08f; 409 | background: -webkit-linear-gradient(#43A8FF, #0F83E8); 410 | background: linear-gradient(#43A8FF, #0F83E8); 411 | 412 | color: white; 413 | -webkit-box-shadow: 0 1px 1px rgba(0, 0, 0, 0.2), inset 0 0 0 1px rgba(0, 0, 0, 0.1); 414 | -moz-box-shadow: 0 1px 1px rgba(0, 0, 0, 0.2), inset 0 0 0 1px rgba(0, 0, 0, 0.1); 415 | box-shadow: 0 1px 1px rgba(0, 0, 0, 0.2), inset 0 0 0 1px rgba(0, 0, 0, 0.1); 416 | font-family: arial; 417 | line-height: 0; 418 | padding: 0px 4px 1px; 419 | font-size: 12px; 420 | margin: 0 3px; 421 | text-shadow: 0 -1px rgba(0, 0, 0, 0.1); 422 | } 423 | 424 | #graphiql-container div.CodeMirror span.CodeMirror-matchingbracket { 425 | color: #555; 426 | text-decoration: underline; 427 | } 428 | 429 | #graphiql-container div.CodeMirror span.CodeMirror-nonmatchingbracket { 430 | color: #f00; 431 | } 432 | 433 | /* Comment */ 434 | .cm-comment { 435 | color: #999; 436 | } 437 | 438 | /* Punctuation */ 439 | .cm-punctuation { 440 | color: #555; 441 | } 442 | 443 | /* Keyword */ 444 | .cm-keyword { 445 | color: #B11A04; 446 | } 447 | 448 | /* OperationName, FragmentName */ 449 | .cm-def { 450 | color: #D2054E; 451 | } 452 | 453 | /* FieldName */ 454 | .cm-property { 455 | color: #1F61A0; 456 | } 457 | 458 | /* FieldAlias */ 459 | .cm-qualifier { 460 | color: #1C92A9; 461 | } 462 | 463 | /* ArgumentName and ObjectFieldName */ 464 | .cm-attribute { 465 | color: #8B2BB9; 466 | } 467 | 468 | /* Number */ 469 | .cm-number { 470 | color: #2882F9; 471 | } 472 | 473 | /* String */ 474 | .cm-string { 475 | color: #D64292; 476 | } 477 | 478 | /* Boolean */ 479 | .cm-builtin { 480 | color: #D47509; 481 | } 482 | 483 | /* EnumValue */ 484 | .cm-string-2 { 485 | color: #0B7FC7; 486 | } 487 | 488 | /* Variable */ 489 | .cm-variable { 490 | color: #397D13; 491 | } 492 | 493 | /* Directive */ 494 | .cm-meta { 495 | color: #B33086; 496 | } 497 | 498 | /* Type */ 499 | .cm-atom { 500 | color: #CA9800; 501 | } 502 | /* BASICS */ 503 | 504 | .CodeMirror { 505 | /* Set height, width, borders, and global font properties here */ 506 | font-family: monospace; 507 | height: 300px; 508 | color: black; 509 | } 510 | 511 | /* PADDING */ 512 | 513 | .CodeMirror-lines { 514 | padding: 4px 0; /* Vertical padding around content */ 515 | } 516 | .CodeMirror pre { 517 | padding: 0 4px; /* Horizontal padding of content */ 518 | } 519 | 520 | .CodeMirror-scrollbar-filler, .CodeMirror-gutter-filler { 521 | background-color: white; /* The little square between H and V scrollbars */ 522 | } 523 | 524 | /* GUTTER */ 525 | 526 | .CodeMirror-gutters { 527 | border-right: 1px solid #ddd; 528 | background-color: #f7f7f7; 529 | white-space: nowrap; 530 | } 531 | .CodeMirror-linenumbers {} 532 | .CodeMirror-linenumber { 533 | padding: 0 3px 0 5px; 534 | min-width: 20px; 535 | text-align: right; 536 | color: #999; 537 | white-space: nowrap; 538 | } 539 | 540 | .CodeMirror-guttermarker { color: black; } 541 | .CodeMirror-guttermarker-subtle { color: #999; } 542 | 543 | /* CURSOR */ 544 | 545 | .CodeMirror div.CodeMirror-cursor { 546 | border-left: 1px solid black; 547 | } 548 | /* Shown when moving in bi-directional text */ 549 | .CodeMirror div.CodeMirror-secondarycursor { 550 | border-left: 1px solid silver; 551 | } 552 | .CodeMirror.cm-fat-cursor div.CodeMirror-cursor { 553 | width: auto; 554 | border: 0; 555 | background: #7e7; 556 | } 557 | .CodeMirror.cm-fat-cursor div.CodeMirror-cursors { 558 | z-index: 1; 559 | } 560 | 561 | .cm-animate-fat-cursor { 562 | width: auto; 563 | border: 0; 564 | -webkit-animation: blink 1.06s steps(1) infinite; 565 | -moz-animation: blink 1.06s steps(1) infinite; 566 | animation: blink 1.06s steps(1) infinite; 567 | } 568 | @-moz-keyframes blink { 569 | 0% { background: #7e7; } 570 | 50% { background: none; } 571 | 100% { background: #7e7; } 572 | } 573 | @-webkit-keyframes blink { 574 | 0% { background: #7e7; } 575 | 50% { background: none; } 576 | 100% { background: #7e7; } 577 | } 578 | @keyframes blink { 579 | 0% { background: #7e7; } 580 | 50% { background: none; } 581 | 100% { background: #7e7; } 582 | } 583 | 584 | /* Can style cursor different in overwrite (non-insert) mode */ 585 | div.CodeMirror-overwrite div.CodeMirror-cursor {} 586 | 587 | .cm-tab { display: inline-block; text-decoration: inherit; } 588 | 589 | .CodeMirror-ruler { 590 | border-left: 1px solid #ccc; 591 | position: absolute; 592 | } 593 | 594 | /* DEFAULT THEME */ 595 | 596 | .cm-s-default .cm-keyword {color: #708;} 597 | .cm-s-default .cm-atom {color: #219;} 598 | .cm-s-default .cm-number {color: #164;} 599 | .cm-s-default .cm-def {color: #00f;} 600 | .cm-s-default .cm-variable, 601 | .cm-s-default .cm-punctuation, 602 | .cm-s-default .cm-property, 603 | .cm-s-default .cm-operator {} 604 | .cm-s-default .cm-variable-2 {color: #05a;} 605 | .cm-s-default .cm-variable-3 {color: #085;} 606 | .cm-s-default .cm-comment {color: #a50;} 607 | .cm-s-default .cm-string {color: #a11;} 608 | .cm-s-default .cm-string-2 {color: #f50;} 609 | .cm-s-default .cm-meta {color: #555;} 610 | .cm-s-default .cm-qualifier {color: #555;} 611 | .cm-s-default .cm-builtin {color: #30a;} 612 | .cm-s-default .cm-bracket {color: #997;} 613 | .cm-s-default .cm-tag {color: #170;} 614 | .cm-s-default .cm-attribute {color: #00c;} 615 | .cm-s-default .cm-header {color: blue;} 616 | .cm-s-default .cm-quote {color: #090;} 617 | .cm-s-default .cm-hr {color: #999;} 618 | .cm-s-default .cm-link {color: #00c;} 619 | 620 | .cm-negative {color: #d44;} 621 | .cm-positive {color: #292;} 622 | .cm-header, .cm-strong {font-weight: bold;} 623 | .cm-em {font-style: italic;} 624 | .cm-link {text-decoration: underline;} 625 | .cm-strikethrough {text-decoration: line-through;} 626 | 627 | .cm-s-default .cm-error {color: #f00;} 628 | .cm-invalidchar {color: #f00;} 629 | 630 | .CodeMirror-composing { border-bottom: 2px solid; } 631 | 632 | /* Default styles for common addons */ 633 | 634 | div.CodeMirror span.CodeMirror-matchingbracket {color: #0f0;} 635 | div.CodeMirror span.CodeMirror-nonmatchingbracket {color: #f22;} 636 | .CodeMirror-matchingtag { background: rgba(255, 150, 0, .3); } 637 | .CodeMirror-activeline-background {background: #e8f2ff;} 638 | 639 | /* STOP */ 640 | 641 | /* The rest of this file contains styles related to the mechanics of 642 | the editor. You probably shouldn't touch them. */ 643 | 644 | .CodeMirror { 645 | position: relative; 646 | overflow: hidden; 647 | background: white; 648 | } 649 | 650 | .CodeMirror-scroll { 651 | overflow: scroll !important; /* Things will break if this is overridden */ 652 | /* 30px is the magic margin used to hide the element's real scrollbars */ 653 | /* See overflow: hidden in .CodeMirror */ 654 | margin-bottom: -30px; margin-right: -30px; 655 | padding-bottom: 30px; 656 | height: 100%; 657 | outline: none; /* Prevent dragging from highlighting the element */ 658 | position: relative; 659 | } 660 | .CodeMirror-sizer { 661 | position: relative; 662 | border-right: 30px solid transparent; 663 | } 664 | 665 | /* The fake, visible scrollbars. Used to force redraw during scrolling 666 | before actuall scrolling happens, thus preventing shaking and 667 | flickering artifacts. */ 668 | .CodeMirror-vscrollbar, .CodeMirror-hscrollbar, .CodeMirror-scrollbar-filler, .CodeMirror-gutter-filler { 669 | position: absolute; 670 | z-index: 6; 671 | display: none; 672 | } 673 | .CodeMirror-vscrollbar { 674 | right: 0; top: 0; 675 | overflow-x: hidden; 676 | overflow-y: scroll; 677 | } 678 | .CodeMirror-hscrollbar { 679 | bottom: 0; left: 0; 680 | overflow-y: hidden; 681 | overflow-x: scroll; 682 | } 683 | .CodeMirror-scrollbar-filler { 684 | right: 0; bottom: 0; 685 | } 686 | .CodeMirror-gutter-filler { 687 | left: 0; bottom: 0; 688 | } 689 | 690 | .CodeMirror-gutters { 691 | position: absolute; left: 0; top: 0; 692 | z-index: 3; 693 | } 694 | .CodeMirror-gutter { 695 | white-space: normal; 696 | height: 100%; 697 | display: inline-block; 698 | margin-bottom: -30px; 699 | /* Hack to make IE7 behave */ 700 | *zoom:1; 701 | *display:inline; 702 | } 703 | .CodeMirror-gutter-wrapper { 704 | position: absolute; 705 | z-index: 4; 706 | height: 100%; 707 | } 708 | .CodeMirror-gutter-elt { 709 | position: absolute; 710 | cursor: default; 711 | z-index: 4; 712 | } 713 | .CodeMirror-gutter-wrapper { 714 | -webkit-user-select: none; 715 | -moz-user-select: none; 716 | user-select: none; 717 | } 718 | 719 | .CodeMirror-lines { 720 | cursor: text; 721 | min-height: 1px; /* prevents collapsing before first draw */ 722 | } 723 | .CodeMirror pre { 724 | /* Reset some styles that the rest of the page might have set */ 725 | -moz-border-radius: 0; -webkit-border-radius: 0; border-radius: 0; 726 | border-width: 0; 727 | background: transparent; 728 | font-family: inherit; 729 | font-size: inherit; 730 | margin: 0; 731 | white-space: pre; 732 | word-wrap: normal; 733 | line-height: inherit; 734 | color: inherit; 735 | z-index: 2; 736 | position: relative; 737 | overflow: visible; 738 | -webkit-tap-highlight-color: transparent; 739 | } 740 | .CodeMirror-wrap pre { 741 | word-wrap: break-word; 742 | white-space: pre-wrap; 743 | word-break: normal; 744 | } 745 | 746 | .CodeMirror-linebackground { 747 | position: absolute; 748 | left: 0; right: 0; top: 0; bottom: 0; 749 | z-index: 0; 750 | } 751 | 752 | .CodeMirror-linewidget { 753 | position: relative; 754 | z-index: 2; 755 | overflow: auto; 756 | } 757 | 758 | .CodeMirror-widget {} 759 | 760 | .CodeMirror-code { 761 | outline: none; 762 | } 763 | 764 | /* Force content-box sizing for the elements where we expect it */ 765 | .CodeMirror-scroll, 766 | .CodeMirror-sizer, 767 | .CodeMirror-gutter, 768 | .CodeMirror-gutters, 769 | .CodeMirror-linenumber { 770 | -moz-box-sizing: content-box; 771 | box-sizing: content-box; 772 | } 773 | 774 | .CodeMirror-measure { 775 | position: absolute; 776 | width: 100%; 777 | height: 0; 778 | overflow: hidden; 779 | visibility: hidden; 780 | } 781 | .CodeMirror-measure pre { position: static; } 782 | 783 | .CodeMirror div.CodeMirror-cursor { 784 | position: absolute; 785 | border-right: none; 786 | width: 0; 787 | } 788 | 789 | div.CodeMirror-cursors { 790 | visibility: hidden; 791 | position: relative; 792 | z-index: 3; 793 | } 794 | .CodeMirror-focused div.CodeMirror-cursors { 795 | visibility: visible; 796 | } 797 | 798 | .CodeMirror-selected { background: #d9d9d9; } 799 | .CodeMirror-focused .CodeMirror-selected { background: #d7d4f0; } 800 | .CodeMirror-crosshair { cursor: crosshair; } 801 | .CodeMirror ::selection { background: #d7d4f0; } 802 | .CodeMirror ::-moz-selection { background: #d7d4f0; } 803 | 804 | .cm-searching { 805 | background: #ffa; 806 | background: rgba(255, 255, 0, .4); 807 | } 808 | 809 | /* IE7 hack to prevent it from returning funny offsetTops on the spans */ 810 | .CodeMirror span { *vertical-align: text-bottom; } 811 | 812 | /* Used to force a border model for a node */ 813 | .cm-force-border { padding-right: .1px; } 814 | 815 | @media print { 816 | /* Hide the cursor when printing */ 817 | .CodeMirror div.CodeMirror-cursors { 818 | visibility: hidden; 819 | } 820 | } 821 | 822 | /* See issue #2901 */ 823 | .cm-tab-wrap-hack:after { content: ''; } 824 | 825 | /* Help users use markselection to safely style text background */ 826 | span.CodeMirror-selectedtext { background: none; } 827 | #graphiql-container .doc-explorer { 828 | background: white; 829 | } 830 | 831 | #graphiql-container .doc-explorer-title-bar { 832 | cursor: default; 833 | display: -webkit-flex; 834 | display: flex; 835 | height: 34px; 836 | line-height: 14px; 837 | padding: 8px 8px 5px; 838 | position: relative; 839 | -webkit-user-select: none; 840 | user-select: none; 841 | } 842 | 843 | #graphiql-container .doc-explorer-title { 844 | padding: 10px 0 10px 10px; 845 | font-weight: bold; 846 | text-align: center; 847 | text-overflow: ellipsis; 848 | white-space: nowrap; 849 | overflow-x: hidden; 850 | -webkit-flex: 1; 851 | flex: 1; 852 | } 853 | 854 | #graphiql-container .doc-explorer-back { 855 | color: #3B5998; 856 | cursor: pointer; 857 | margin: -7px 0 -6px -8px; 858 | overflow-x: hidden; 859 | padding: 17px 12px 16px 16px; 860 | text-overflow: ellipsis; 861 | white-space: nowrap; 862 | } 863 | 864 | #graphiql-container .doc-explorer-back:before { 865 | border-left: 2px solid #3B5998; 866 | border-top: 2px solid #3B5998; 867 | content: ''; 868 | display: inline-block; 869 | height: 9px; 870 | margin: 0 3px -1px 0; 871 | position: relative; 872 | width: 9px; 873 | -webkit-transform: rotate(-45deg); 874 | transform: rotate(-45deg); 875 | } 876 | 877 | #graphiql-container .doc-explorer-rhs { 878 | position: relative; 879 | } 880 | 881 | #graphiql-container .doc-explorer-contents { 882 | background-color: #ffffff; 883 | border-top: 1px solid #d6d6d6; 884 | bottom: 0; 885 | left: 0; 886 | min-width: 300px; 887 | overflow-y: auto; 888 | padding: 20px 15px; 889 | position: absolute; 890 | right: 0; 891 | top: 47px; 892 | } 893 | 894 | #graphiql-container .doc-type-description p:first-child , 895 | #graphiql-container .doc-type-description blockquote:first-child { 896 | margin-top: 0; 897 | } 898 | 899 | #graphiql-container .doc-explorer-contents a { 900 | cursor: pointer; 901 | text-decoration: none; 902 | } 903 | 904 | #graphiql-container .doc-explorer-contents a:hover { 905 | text-decoration: underline; 906 | } 907 | 908 | #graphiql-container .doc-value-description { 909 | padding: 4px 0 8px 12px; 910 | } 911 | 912 | #graphiql-container .doc-category { 913 | margin: 20px 0; 914 | } 915 | 916 | #graphiql-container .doc-category-title { 917 | border-bottom: 1px solid #e0e0e0; 918 | color: #777; 919 | cursor: default; 920 | font-size: 14px; 921 | font-variant: small-caps; 922 | font-weight: bold; 923 | letter-spacing: 1px; 924 | margin: 0 -15px 10px 0; 925 | padding: 10px 0; 926 | -webkit-user-select: none; 927 | user-select: none; 928 | } 929 | 930 | #graphiql-container .doc-category-item { 931 | margin: 12px 0; 932 | color: #555; 933 | } 934 | 935 | #graphiql-container .keyword { 936 | color: #B11A04; 937 | } 938 | 939 | #graphiql-container .type-name { 940 | color: #CA9800; 941 | } 942 | 943 | #graphiql-container .field-name { 944 | color: #1F61A0; 945 | } 946 | 947 | #graphiql-container .value-name { 948 | color: #0B7FC7; 949 | } 950 | 951 | #graphiql-container .arg-name { 952 | color: #8B2BB9; 953 | } 954 | 955 | #graphiql-container .arg:after { 956 | content: ', '; 957 | } 958 | 959 | #graphiql-container .arg:last-child:after { 960 | content: ''; 961 | } 962 | .CodeMirror-foldmarker { 963 | color: blue; 964 | text-shadow: #b9f 1px 1px 2px, #b9f -1px -1px 2px, #b9f 1px -1px 2px, #b9f -1px 1px 2px; 965 | font-family: arial; 966 | line-height: .3; 967 | cursor: pointer; 968 | } 969 | .CodeMirror-foldgutter { 970 | width: .7em; 971 | } 972 | .CodeMirror-foldgutter-open, 973 | .CodeMirror-foldgutter-folded { 974 | cursor: pointer; 975 | } 976 | .CodeMirror-foldgutter-open:after { 977 | content: "\25BE"; 978 | } 979 | .CodeMirror-foldgutter-folded:after { 980 | content: "\25B8"; 981 | } 982 | /* The lint marker gutter */ 983 | .CodeMirror-lint-markers { 984 | width: 16px; 985 | } 986 | 987 | .CodeMirror-lint-tooltip { 988 | background-color: infobackground; 989 | border: 1px solid black; 990 | border-radius: 4px 4px 4px 4px; 991 | color: infotext; 992 | font-family: monospace; 993 | font-size: 10pt; 994 | overflow: hidden; 995 | padding: 2px 5px; 996 | position: fixed; 997 | white-space: pre; 998 | white-space: pre-wrap; 999 | z-index: 100; 1000 | max-width: 600px; 1001 | opacity: 0; 1002 | transition: opacity .4s; 1003 | -moz-transition: opacity .4s; 1004 | -webkit-transition: opacity .4s; 1005 | -o-transition: opacity .4s; 1006 | -ms-transition: opacity .4s; 1007 | } 1008 | 1009 | .CodeMirror-lint-mark-error, .CodeMirror-lint-mark-warning { 1010 | background-position: left bottom; 1011 | background-repeat: repeat-x; 1012 | } 1013 | 1014 | .CodeMirror-lint-mark-error { 1015 | background-image: 1016 | url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAQAAAADCAYAAAC09K7GAAAAAXNSR0IArs4c6QAAAAZiS0dEAP8A/wD/oL2nkwAAAAlwSFlzAAALEwAACxMBAJqcGAAAAAd0SU1FB9sJDw4cOCW1/KIAAAAZdEVYdENvbW1lbnQAQ3JlYXRlZCB3aXRoIEdJTVBXgQ4XAAAAHElEQVQI12NggIL/DAz/GdA5/xkY/qPKMDAwAADLZwf5rvm+LQAAAABJRU5ErkJggg==") 1017 | ; 1018 | } 1019 | 1020 | .CodeMirror-lint-mark-warning { 1021 | background-image: url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAQAAAADCAYAAAC09K7GAAAAAXNSR0IArs4c6QAAAAZiS0dEAP8A/wD/oL2nkwAAAAlwSFlzAAALEwAACxMBAJqcGAAAAAd0SU1FB9sJFhQXEbhTg7YAAAAZdEVYdENvbW1lbnQAQ3JlYXRlZCB3aXRoIEdJTVBXgQ4XAAAAMklEQVQI12NkgIIvJ3QXMjAwdDN+OaEbysDA4MPAwNDNwMCwiOHLCd1zX07o6kBVGQEAKBANtobskNMAAAAASUVORK5CYII="); 1022 | } 1023 | 1024 | .CodeMirror-lint-marker-error, .CodeMirror-lint-marker-warning { 1025 | background-position: center center; 1026 | background-repeat: no-repeat; 1027 | cursor: pointer; 1028 | display: inline-block; 1029 | height: 16px; 1030 | width: 16px; 1031 | vertical-align: middle; 1032 | position: relative; 1033 | } 1034 | 1035 | .CodeMirror-lint-message-error, .CodeMirror-lint-message-warning { 1036 | padding-left: 18px; 1037 | background-position: top left; 1038 | background-repeat: no-repeat; 1039 | } 1040 | 1041 | .CodeMirror-lint-marker-error, .CodeMirror-lint-message-error { 1042 | background-image: url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAMAAAAoLQ9TAAAAHlBMVEW7AAC7AACxAAC7AAC7AAAAAAC4AAC5AAD///+7AAAUdclpAAAABnRSTlMXnORSiwCK0ZKSAAAATUlEQVR42mWPOQ7AQAgDuQLx/z8csYRmPRIFIwRGnosRrpamvkKi0FTIiMASR3hhKW+hAN6/tIWhu9PDWiTGNEkTtIOucA5Oyr9ckPgAWm0GPBog6v4AAAAASUVORK5CYII="); 1043 | } 1044 | 1045 | .CodeMirror-lint-marker-warning, .CodeMirror-lint-message-warning { 1046 | background-image: url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAMAAAAoLQ9TAAAANlBMVEX/uwDvrwD/uwD/uwD/uwD/uwD/uwD/uwD/uwD6twD/uwAAAADurwD2tQD7uAD+ugAAAAD/uwDhmeTRAAAADHRSTlMJ8mN1EYcbmiixgACm7WbuAAAAVklEQVR42n3PUQqAIBBFUU1LLc3u/jdbOJoW1P08DA9Gba8+YWJ6gNJoNYIBzAA2chBth5kLmG9YUoG0NHAUwFXwO9LuBQL1giCQb8gC9Oro2vp5rncCIY8L8uEx5ZkAAAAASUVORK5CYII="); 1047 | } 1048 | 1049 | .CodeMirror-lint-marker-multiple { 1050 | background-image: url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAcAAAAHCAMAAADzjKfhAAAACVBMVEUAAAAAAAC/v7914kyHAAAAAXRSTlMAQObYZgAAACNJREFUeNo1ioEJAAAIwmz/H90iFFSGJgFMe3gaLZ0od+9/AQZ0ADosbYraAAAAAElFTkSuQmCC"); 1051 | background-repeat: no-repeat; 1052 | background-position: right bottom; 1053 | width: 100%; height: 100%; 1054 | } 1055 | .CodeMirror-hints { 1056 | background: white; 1057 | -webkit-box-shadow: 0 1px 3px rgba(0, 0, 0, 0.45); 1058 | -moz-box-shadow: 0 1px 3px rgba(0, 0, 0, 0.45); 1059 | box-shadow: 0 1px 3px rgba(0, 0, 0, 0.45); 1060 | font-family: 'Consolas', 'Inconsolata', 'Droid Sans Mono', 'Monaco', monospace; 1061 | font-size: 13px; 1062 | list-style: none; 1063 | margin: 0; 1064 | margin-left: -6px; 1065 | max-height: 14.5em; 1066 | overflow-y: auto; 1067 | overflow: hidden; 1068 | padding: 0; 1069 | position: absolute; 1070 | z-index: 10; 1071 | } 1072 | 1073 | .CodeMirror-hints-wrapper { 1074 | background: white; 1075 | -webkit-box-shadow: 0 1px 3px rgba(0, 0, 0, 0.45); 1076 | -moz-box-shadow: 0 1px 3px rgba(0, 0, 0, 0.45); 1077 | box-shadow: 0 1px 3px rgba(0, 0, 0, 0.45); 1078 | margin-left: -6px; 1079 | position: absolute; 1080 | z-index: 10; 1081 | } 1082 | 1083 | .CodeMirror-hints-wrapper .CodeMirror-hints { 1084 | -webkit-box-shadow: none; 1085 | -moz-box-shadow: none; 1086 | box-shadow: none; 1087 | position: relative; 1088 | margin-left: 0; 1089 | z-index: 0; 1090 | } 1091 | 1092 | .CodeMirror-hint { 1093 | border-top: solid 1px #f7f7f7; 1094 | color: #141823; 1095 | cursor: pointer; 1096 | margin: 0; 1097 | max-width: 300px; 1098 | overflow: hidden; 1099 | padding: 2px 6px; 1100 | white-space: pre; 1101 | } 1102 | 1103 | li.CodeMirror-hint-active { 1104 | background-color: #08f; 1105 | border-top-color: white; 1106 | color: white; 1107 | } 1108 | 1109 | .CodeMirror-hint-information { 1110 | border-top: solid 1px #c0c0c0; 1111 | max-width: 300px; 1112 | padding: 4px 6px; 1113 | position: relative; 1114 | z-index: 1; 1115 | } 1116 | 1117 | .CodeMirror-hint-information:first-child { 1118 | border-bottom: solid 1px #c0c0c0; 1119 | border-top: none; 1120 | margin-bottom: -1px; 1121 | } 1122 | -------------------------------------------------------------------------------- /webpack.config.js: -------------------------------------------------------------------------------- 1 | 2 | const path = require('path'); 3 | const webpack = require('webpack') 4 | module.exports = { 5 | //devtool: 'source-maps', // use this when using the chrome devtools debugger 6 | devtool: 'eval', 7 | context: path.join(__dirname, "src"), 8 | entry: { 9 | app: ['babel-polyfill','./index.js', 'webpack-hot-middleware/client'] 10 | }, 11 | output: { 12 | filename: 'bundle.js', 13 | path: path.join(__dirname, 'build'), 14 | publicPath: '/static/' 15 | }, 16 | plugins: [ 17 | new webpack.HotModuleReplacementPlugin(), 18 | new webpack.NoErrorsPlugin(), 19 | ], 20 | resolve: { 21 | extensions: ['', '.js'], 22 | root: path.join(__dirname, 'src') 23 | }, 24 | module: { 25 | loaders: [ 26 | {test: /\.json$/, loader: 'json-loader'}, 27 | {test: /\.txt$/, loader: 'raw-loader'}, 28 | {test: /\.(png|jpg|jpeg|gif|svg|woff|woff2)$/, loader: 'url-loader?limit=10000'}, 29 | {test: /\.(eot|ttf|wav|mp3)$/, loader: 'file-loader'}, 30 | { 31 | test: /\.js$/, 32 | loader: 'babel', 33 | include: [path.join(__dirname, 'src')] 34 | } 35 | ] 36 | } 37 | }; 38 | --------------------------------------------------------------------------------