├── .gitignore
├── .meteor
├── .gitignore
├── release
├── platforms
├── packages
├── .id
├── .finished-upgraders
└── versions
├── client
├── main.html
└── main.js
├── README.md
├── .babelrc
├── package.json
├── server
└── main.js
└── imports
├── app.js
└── schema.js
/.gitignore:
--------------------------------------------------------------------------------
1 | node_modules/
--------------------------------------------------------------------------------
/.meteor/.gitignore:
--------------------------------------------------------------------------------
1 | local
2 |
--------------------------------------------------------------------------------
/.meteor/release:
--------------------------------------------------------------------------------
1 | METEOR@1.6
2 |
--------------------------------------------------------------------------------
/.meteor/platforms:
--------------------------------------------------------------------------------
1 | server
2 | browser
3 |
--------------------------------------------------------------------------------
/client/main.html:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/.meteor/packages:
--------------------------------------------------------------------------------
1 | static-html
2 | standard-minifier-js
3 | es5-shim
4 | ecmascript
5 | server-render
6 | meteor
7 | webapp
8 | dynamic-import
9 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | ## Sample App
2 |
3 | ### Installation
4 | - install [meteor](https://www.meteor.com/install)
5 | - `npm install`
6 | - `npm start`
7 |
8 |
--------------------------------------------------------------------------------
/.babelrc:
--------------------------------------------------------------------------------
1 | {
2 | "presets": [
3 | "react"
4 | ],
5 | "plugins": [
6 | "transform-react-require",
7 | "transform-inline-environment-variables",
8 | "transform-dead-code-elimination",
9 | "emotion/babel"
10 | ]
11 | }
12 |
--------------------------------------------------------------------------------
/.meteor/.id:
--------------------------------------------------------------------------------
1 | # This file contains a token that is unique to your project.
2 | # Check it into your repository along with the rest of this directory.
3 | # It can be used for purposes such as:
4 | # - ensuring you don't accidentally deploy one app on top of another
5 | # - providing package authors with aggregated statistics
6 |
7 | 9bnsqv1ia2dh11nxikou
8 |
--------------------------------------------------------------------------------
/.meteor/.finished-upgraders:
--------------------------------------------------------------------------------
1 | # This file contains information which helps Meteor properly upgrade your
2 | # app when you run 'meteor update'. You should check it into version control
3 | # with your project.
4 |
5 | notices-for-0.9.0
6 | notices-for-0.9.1
7 | 0.9.4-platform-file
8 | notices-for-facebook-graph-api-2
9 | 1.2.0-standard-minifiers-package
10 | 1.2.0-meteor-platform-split
11 | 1.2.0-cordova-changes
12 | 1.2.0-breaking-changes
13 | 1.3.0-split-minifiers-package
14 | 1.4.0-remove-old-dev-bundle-link
15 | 1.4.1-add-shell-server-package
16 | 1.4.3-split-account-service-packages
17 | 1.5-add-dynamic-import-package
18 |
--------------------------------------------------------------------------------
/client/main.js:
--------------------------------------------------------------------------------
1 | import { render } from "react-dom";
2 | import { onPageLoad } from "meteor/server-render";
3 | import { hydrate } from "emotion";
4 | import { BrowserRouter } from "react-router-dom";
5 |
6 | import { ApolloProvider } from "react-apollo";
7 | import { ApolloClient } from "apollo-client";
8 | import { createHttpLink } from "apollo-link-http";
9 | import { InMemoryCache } from "apollo-cache-inmemory";
10 |
11 | import { App } from "/imports/app";
12 |
13 | export const start = () => {
14 | hydrate(window.__CSS__);
15 |
16 | const client = new ApolloClient({
17 | link: createHttpLink({ uri: "/graphql" }),
18 | cache: new InMemoryCache().restore(window.__APOLLO_STATE__),
19 | });
20 |
21 | const WrappedApp = (
22 |
23 |
24 |
25 |
26 |
27 | );
28 |
29 | render(WrappedApp, document.getElementById("app"));
30 | };
31 |
32 | onPageLoad(start);
33 |
--------------------------------------------------------------------------------
/.meteor/versions:
--------------------------------------------------------------------------------
1 | babel-compiler@6.24.7
2 | babel-runtime@1.1.1
3 | base64@1.0.10
4 | blaze-tools@1.0.10
5 | boilerplate-generator@1.3.0
6 | caching-compiler@1.1.9
7 | caching-html-compiler@1.1.2
8 | callback-hook@1.0.10
9 | check@1.2.5
10 | ddp@1.4.0
11 | ddp-client@2.2.0
12 | ddp-common@1.3.0
13 | ddp-server@2.1.1
14 | deps@1.0.12
15 | diff-sequence@1.0.7
16 | dynamic-import@0.2.1
17 | ecmascript@0.9.0
18 | ecmascript-runtime@0.5.0
19 | ecmascript-runtime-client@0.5.0
20 | ecmascript-runtime-server@0.5.0
21 | ejson@1.1.0
22 | es5-shim@4.6.15
23 | geojson-utils@1.0.10
24 | html-tools@1.0.11
25 | htmljs@1.0.11
26 | id-map@1.0.9
27 | logging@1.1.19
28 | meteor@1.8.0
29 | minifier-js@2.2.0
30 | minimongo@1.4.1
31 | modules@0.11.0
32 | modules-runtime@0.9.0
33 | mongo-id@1.0.6
34 | ordered-dict@1.0.9
35 | promise@0.10.0
36 | random@1.0.10
37 | retry@1.0.9
38 | routepolicy@1.0.12
39 | server-render@0.2.0
40 | spacebars-compiler@1.1.2
41 | standard-minifier-js@2.2.0
42 | static-html@1.2.2
43 | templating-tools@1.1.2
44 | tracker@1.1.3
45 | underscore@1.0.10
46 | webapp@1.4.0
47 | webapp-hashing@1.0.9
48 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "ssr",
3 | "private": true,
4 | "scripts": {
5 | "start": "meteor run",
6 | "flow": "flow",
7 | "visualize": "meteor --production --extra-packages bundle-visualizer",
8 | "lint-staged": "lint-staged",
9 | "lint-fix": "prettier --trailing-comma all --write \"{client,server,imports}/**/*.js*\""
10 | },
11 | "lint-staged": {
12 | "*.ts*": ["prettier --trailing-comma all --write", "git add"],
13 | "*.js*": ["prettier --trailing-comma all --write", "git add"]
14 | },
15 | "pre-commit": "lint-staged",
16 | "dependencies": {
17 | "apollo-cache-inmemory": "^1.1.0",
18 | "apollo-client": "^2.0.2",
19 | "apollo-link": "^1.0.0",
20 | "apollo-link-http": "^1.1.0",
21 | "apollo-server-express": "^1.1.0",
22 | "babel-runtime": "^6.20.0",
23 | "body-parser": "^1.17.2",
24 | "core-js": "^2.5.0",
25 | "emotion": "^7.0.13",
26 | "express": "^4.15.4",
27 | "graphql": "^0.10.5",
28 | "graphql-tag": "^2.4.2",
29 | "graphql-tools": "^1.1.0",
30 | "meteor-node-stubs": "~0.2.4",
31 | "optics-agent": "^1.1.6",
32 | "react": "^15.6.1",
33 | "react-apollo": "^2.0.1",
34 | "react-dom": "^15.6.1",
35 | "react-helmet": "^5.1.3",
36 | "react-router": "^4.1.2",
37 | "react-router-dom": "^4.1.2",
38 | "theming": "^1.1.0"
39 | },
40 | "devDependencies": {
41 | "babel-plugin-transform-dead-code-elimination": "^2.2.2",
42 | "babel-plugin-transform-inline-environment-variables": "^0.1.1",
43 | "babel-plugin-transform-react-require": "^1.0.1",
44 | "babel-preset-react": "6.24.1",
45 | "lint-staged": "^4.0.3",
46 | "pre-commit": "^1.2.2",
47 | "prettier": "^1.5.3"
48 | }
49 | }
50 |
--------------------------------------------------------------------------------
/server/main.js:
--------------------------------------------------------------------------------
1 | import { renderToString } from "react-dom/server";
2 | import { onPageLoad } from "meteor/server-render";
3 | import { getDataFromTree, ApolloProvider } from "react-apollo";
4 | import { ApolloClient } from "apollo-client";
5 | import { ApolloLink, Observable } from "apollo-link";
6 | import { InMemoryCache } from "apollo-cache-inmemory";
7 | import { Helmet } from "react-helmet";
8 | import { extractCritical } from "emotion/server";
9 | import { StaticRouter } from "react-router";
10 |
11 | import { WebApp } from "meteor/webapp";
12 | import { graphqlExpress, graphiqlExpress } from "apollo-server-express";
13 | import bodyParser from "body-parser";
14 | import express from "express";
15 | import { graphql } from "graphql";
16 | import { print } from "graphql/language/printer";
17 |
18 | import { schema } from "/imports/schema";
19 | import { App } from "/imports/app";
20 |
21 | export const render = async sink => {
22 | const client = new ApolloClient({
23 | // simple local interface to query graphql directly
24 | link: new ApolloLink(({ query, variables, operationName }) => {
25 | return new Observable(obs => {
26 | graphql(schema, print(query), {}, {}, variables, operationName)
27 | .then(result => {
28 | obs.next(result);
29 | obs.complete();
30 | })
31 | .catch(obs.error.bind(obs));
32 | });
33 | }),
34 | cache: new InMemoryCache(),
35 | ssrMode: true,
36 | });
37 |
38 | const context = {};
39 | const WrappedApp = (
40 |
41 |
42 |
43 |
44 |
45 | );
46 |
47 | // load all data from local server;
48 | await getDataFromTree(WrappedApp);
49 |
50 | const { html, ids, css } = extractCritical(renderToString(WrappedApp));
51 | const meta = Helmet.renderStatic();
52 |
53 | sink.appendToHead(meta.title.toString());
54 | sink.appendToHead(``);
55 |
56 | sink.renderIntoElementById("app", html);
57 |
58 | sink.appendToBody(`
59 |
63 | `);
64 | };
65 |
66 | // hanlde SSR
67 | onPageLoad(render);
68 |
69 | // expose graphql endpoint
70 | // setup GraphQL endpoint and GraphiQL
71 | const server = express();
72 |
73 | server.use(
74 | "/graphql",
75 | bodyParser.json(),
76 | graphqlExpress(req => ({
77 | schema,
78 | })),
79 | );
80 | server.use("/graphiql", graphiqlExpress({ endpointURL: "/graphql" }));
81 |
82 | WebApp.connectHandlers.use(server);
83 |
--------------------------------------------------------------------------------
/imports/app.js:
--------------------------------------------------------------------------------
1 | import { graphql } from "react-apollo";
2 | import { Helmet } from "react-helmet";
3 | import gql from "graphql-tag";
4 | import styled from "emotion/react";
5 | import { css } from "emotion";
6 | import { Link, Route, Switch } from "react-router-dom";
7 |
8 | const Card = styled(Link)`
9 | width: 200px;
10 | height: 200px;
11 | border: 15px solid white;
12 | background-size: cover;
13 | background-position: center center;
14 | margin: auto;
15 | background-image: ${props => `url('${props.image}')`};
16 | `;
17 |
18 | const background = css`
19 | display: flex;
20 | align-items: center;
21 | justify-content: center;
22 | background-size: cover;
23 | background-position: center center;
24 | position: fixed;
25 | top: 0;
26 | bottom: 0;
27 | left: 0;
28 | right: 0;
29 | `;
30 |
31 | const Background = styled("div")`
32 | composes: ${background};
33 | background-color: darksalmon;
34 | `;
35 |
36 | const FullScreen = styled("div")`
37 | composes: ${background};
38 | background-color: transparent;
39 | `;
40 |
41 | const BlurryBackground = styled("div")`
42 | composes: ${background};
43 | background-image: ${props => `url('${props.image}')`};
44 | -webkit-filter: blur(.25px);
45 | filter: blur(.25px);
46 | `;
47 |
48 | const MOVIE_QUERY = gql`
49 | query GetMovies {
50 | movies {
51 | id
52 | name
53 | image
54 | episode
55 | }
56 | }
57 | `;
58 |
59 | const withMovies = graphql(MOVIE_QUERY, {
60 | props: ({ data }) => ({ ...data }),
61 | });
62 |
63 | export const Movies = withMovies(({ loading, movies, error }) => {
64 | if (loading) return Loading
;
65 | if (error) return ERROR
;
66 | return (
67 |
68 |
69 |
70 | {movies.map(x => x.name).join(" || ")}
71 |
72 |
73 |
74 | {movies.map(movie =>
75 | ,
76 | )}
77 |
78 |
79 | );
80 | });
81 |
82 | const HERO_QUERY = gql`
83 | query GetEpisode($episode: Episode!) {
84 | character: hero(episode: $episode) {
85 | id
86 | name
87 | image
88 | friends {
89 | id
90 | name
91 | image
92 | }
93 | }
94 | }
95 | `;
96 |
97 | export const Character = ({ loading, character, error, networkStatus }) => {
98 | if (!character) return null;
99 | return (
100 |
101 |
102 |
103 | {character.friends.map(friend =>
104 | ,
109 | )}
110 |
111 |
112 | );
113 | };
114 |
115 | const withHero = graphql(HERO_QUERY, {
116 | options: ({ match }) => ({
117 | variables: {
118 | episode: match.params.episode,
119 | },
120 | }),
121 | props: ({ data }) => ({ ...data }),
122 | });
123 |
124 | export const Hero = withHero(Character);
125 |
126 | export const CHARACTER_QUERY = gql`
127 | query GetCharacter($id: ID!) {
128 | character(id: $id) {
129 | id
130 | name
131 | image
132 | friends {
133 | id
134 | name
135 | image
136 | }
137 | }
138 | }
139 | `;
140 |
141 | const withCharacter = graphql(CHARACTER_QUERY, {
142 | options: ({ match }) => ({ variables: { id: match.params.id } }),
143 | props: ({ data }) => ({ ...data }),
144 | });
145 |
146 | export const CharacterWithData = withCharacter(Character);
147 |
148 | export const App = () =>
149 |
150 |
151 |
152 |
153 |
154 |
155 |
;
156 |
--------------------------------------------------------------------------------
/imports/schema.js:
--------------------------------------------------------------------------------
1 | // This is the Star Wars schema used in all of the interactive GraphiQL
2 | // examples on GraphQL.org. License reproduced at the bottom.
3 |
4 | /**
5 | * Copyright (c) 2015, Facebook, Inc.
6 | * All rights reserved.
7 | *
8 | * This source code is licensed under the license found in the
9 | * LICENSE file in the root directory of this source tree.
10 | */
11 |
12 | import { makeExecutableSchema } from "graphql-tools";
13 |
14 | const schemaString = `
15 | schema {
16 | query: Query
17 | mutation: Mutation
18 | }
19 | # The query type, represents all of the entry points into our object graph
20 | type Query {
21 | hero(episode: Episode): Character
22 | reviews(episode: Episode!): [Review]
23 | search(text: String): [SearchResult]
24 | character(id: ID!): Character
25 | droid(id: ID!): Droid
26 | human(id: ID!): Human
27 | starship(id: ID!): Starship
28 | movies: [Movie]
29 | }
30 | # The mutation type, represents all updates we can make to our data
31 | type Mutation {
32 | createReview(episode: Episode, review: ReviewInput!): Review
33 | }
34 | # The episodes in the Star Wars trilogy
35 | enum Episode {
36 | # Star Wars Episode IV: A New Hope, released in 1977.
37 | NEWHOPE
38 | # Star Wars Episode V: The Empire Strikes Back, released in 1980.
39 | EMPIRE
40 | # Star Wars Episode VI: Return of the Jedi, released in 1983.
41 | JEDI
42 | }
43 | # A character from the Star Wars universe
44 | interface Character {
45 | # The ID of the character
46 | id: ID!
47 | # The name of the character
48 | name: String!
49 | # The friends of the character, or an empty list if they have none
50 | friends: [Character]
51 | # The friends of the character exposed as a connection with edges
52 | friendsConnection(first: Int, after: ID): FriendsConnection!
53 | # The movies this character appears in
54 | appearsIn: [Episode]!
55 | # the image of the character
56 | image: String!
57 | }
58 | # Units of height
59 | enum LengthUnit {
60 | # The standard unit around the world
61 | METER
62 | # Primarily used in the United States
63 | FOOT
64 | }
65 | # A humanoid creature from the Star Wars universe
66 | type Human implements Character {
67 | # The ID of the human
68 | id: ID!
69 | # What this human calls themselves
70 | name: String!
71 | # Height in the preferred unit, default is meters
72 | height(unit: LengthUnit = METER): Float
73 | # Mass in kilograms, or null if unknown
74 | mass: Float
75 | # This human's friends, or an empty list if they have none
76 | friends: [Character]
77 | # The friends of the human exposed as a connection with edges
78 | friendsConnection(first: Int, after: ID): FriendsConnection!
79 | # The movies this human appears in
80 | appearsIn: [Episode]!
81 | # A list of starships this person has piloted, or an empty list if none
82 | starships: [Starship]
83 | # the image of the character
84 | image: String!
85 | }
86 | # An autonomous mechanical character in the Star Wars universe
87 | type Droid implements Character {
88 | # The ID of the droid
89 | id: ID!
90 | # What others call this droid
91 | name: String!
92 | # This droid's friends, or an empty list if they have none
93 | friends: [Character]
94 | # The friends of the droid exposed as a connection with edges
95 | friendsConnection(first: Int, after: ID): FriendsConnection!
96 | # The movies this droid appears in
97 | appearsIn: [Episode]!
98 | # This droid's primary function
99 | primaryFunction: String
100 | # the image of the character
101 | image: String!
102 | }
103 | # A connection object for a character's friends
104 | type FriendsConnection {
105 | # The total number of friends
106 | totalCount: Int
107 | # The edges for each of the character's friends.
108 | edges: [FriendsEdge]
109 | # A list of the friends, as a convenience when edges are not needed.
110 | friends: [Character]
111 | # Information for paginating this connection
112 | pageInfo: PageInfo!
113 | }
114 | # An edge object for a character's friends
115 | type FriendsEdge {
116 | # A cursor used for pagination
117 | cursor: ID!
118 | # The character represented by this friendship edge
119 | node: Character
120 | }
121 | # Information for paginating this connection
122 | type PageInfo {
123 | startCursor: ID
124 | endCursor: ID
125 | hasNextPage: Boolean!
126 | }
127 | # Represents a review for a movie
128 | type Review {
129 | # The number of stars this review gave, 1-5
130 | stars: Int!
131 | # Comment about the movie
132 | commentary: String
133 | }
134 | # The input object sent when someone is creating a new review
135 | input ReviewInput {
136 | # 0-5 stars
137 | stars: Int!
138 | # Comment about the movie, optional
139 | commentary: String
140 | }
141 | type Starship {
142 | # The ID of the starship
143 | id: ID!
144 | # The name of the starship
145 | name: String!
146 | # Length of the starship, along the longest axis
147 | length(unit: LengthUnit = METER): Float
148 | }
149 | union SearchResult = Human | Droid | Starship
150 | type Movie {
151 | # the Id of the movie
152 | id: ID!
153 | # the name of the movie
154 | name: String!
155 | # the episode
156 | episode: Episode!
157 | # the poster of the movie
158 | image: String!
159 | }
160 | `;
161 |
162 | /**
163 | * This defines a basic set of data for our Star Wars Schema.
164 | *
165 | * This data is hard coded for the sake of the demo, but you could imagine
166 | * fetching this data from a backend service rather than from hardcoded
167 | * JSON objects in a more complex demo.
168 | */
169 |
170 | const humans = [
171 | {
172 | id: "1000",
173 | name: "Luke Skywalker",
174 | friends: ["1002", "1003", "2000", "2001"],
175 | appearsIn: ["NEWHOPE", "EMPIRE", "JEDI"],
176 | height: 1.72,
177 | mass: 77,
178 | starships: ["3001", "3003"],
179 | image:
180 | "https://mir-s3-cdn-cf.behance.net/project_modules/max_1200/9ae95a32222089.567434549f642.jpg",
181 | },
182 | {
183 | id: "1001",
184 | name: "Darth Vader",
185 | friends: ["1004"],
186 | appearsIn: ["NEWHOPE", "EMPIRE", "JEDI"],
187 | height: 2.02,
188 | mass: 136,
189 | starships: ["3002"],
190 | image:
191 | "https://mir-s3-cdn-cf.behance.net/project_modules/disp/4cccd816984159.562b4037c502c.jpg",
192 | },
193 | {
194 | id: "1002",
195 | name: "Han Solo",
196 | friends: ["1000", "1003", "2001"],
197 | appearsIn: ["NEWHOPE", "EMPIRE", "JEDI"],
198 | height: 1.8,
199 | mass: 80,
200 | starships: ["3000", "3003"],
201 | image:
202 | "https://mir-s3-cdn-cf.behance.net/project_modules/disp/8f612a19912933.562e25a31c908.jpg",
203 | },
204 | {
205 | id: "1003",
206 | name: "Leia Organa",
207 | friends: ["1000", "1002", "2000", "2001"],
208 | appearsIn: ["NEWHOPE", "EMPIRE", "JEDI"],
209 | height: 1.5,
210 | mass: 49,
211 | starships: [],
212 | image:
213 | "https://mir-cdn.behance.net/v1/rendition/project_modules/fs/6ea87455699513.598f548ce49c3.jpg",
214 | },
215 | ];
216 |
217 | const humanData = {};
218 | humans.forEach(ship => {
219 | humanData[ship.id] = ship;
220 | });
221 |
222 | const droids = [
223 | {
224 | id: "2000",
225 | name: "C-3PO",
226 | friends: ["1000", "1002", "1003", "2001"],
227 | appearsIn: ["NEWHOPE", "EMPIRE", "JEDI"],
228 | primaryFunction: "Protocol",
229 | image:
230 | "https://mir-s3-cdn-cf.behance.net/project_modules/fs/e91a0e32833045.5695736631a8d.jpg",
231 | },
232 | {
233 | id: "2001",
234 | name: "R2-D2",
235 | friends: ["1000", "1002", "1003"],
236 | appearsIn: ["NEWHOPE", "EMPIRE", "JEDI"],
237 | primaryFunction: "Astromech",
238 | image:
239 | "https://mir-s3-cdn-cf.behance.net/project_modules/fs/bb1dde49635909.58ba18a073115.jpg",
240 | },
241 | ];
242 |
243 | const droidData = {};
244 | droids.forEach(ship => {
245 | droidData[ship.id] = ship;
246 | });
247 |
248 | const starships = [
249 | {
250 | id: "3000",
251 | name: "Millenium Falcon",
252 | length: 34.37,
253 | },
254 | {
255 | id: "3001",
256 | name: "X-Wing",
257 | length: 12.5,
258 | },
259 | {
260 | id: "3002",
261 | name: "TIE Advanced x1",
262 | length: 9.2,
263 | },
264 | {
265 | id: "3003",
266 | name: "Imperial shuttle",
267 | length: 20,
268 | },
269 | ];
270 |
271 | const starshipData = {};
272 | starships.forEach(ship => {
273 | starshipData[ship.id] = ship;
274 | });
275 |
276 | const movies = [
277 | {
278 | id: "4",
279 | name: "A New Hope",
280 | episode: "NEWHOPE",
281 | image:
282 | "https://mir-s3-cdn-cf.behance.net/project_modules/max_1200/fd9cef50812813.58da69a85361d.jpg",
283 | },
284 | {
285 | id: "5",
286 | name: "The Empire Strikes Back",
287 | episode: "EMPIRE",
288 | image:
289 | "https://mir-s3-cdn-cf.behance.net/project_modules/max_1200/555e9b50812813.58da69a853ac3.jpg",
290 | },
291 | {
292 | id: "6",
293 | name: "Return of the Jedi",
294 | episode: "JEDI",
295 | image:
296 | "https://mir-s3-cdn-cf.behance.net/project_modules/max_1200/f0e07750812813.58da69a854034.jpg",
297 | },
298 | ];
299 |
300 | const movieData = {};
301 | movies.forEach(movie => {
302 | movieData[movie.id] = movie;
303 | });
304 |
305 | /**
306 | * Helper function to get a character by ID.
307 | */
308 | function getCharacter(id) {
309 | // Returning a promise just to illustrate GraphQL.js's support.
310 | return Promise.resolve(humanData[id] || droidData[id]);
311 | }
312 |
313 | /**
314 | * Allows us to fetch the undisputed hero of the Star Wars trilogy, R2-D2.
315 | */
316 | function getHero(episode) {
317 | if (episode === "EMPIRE") {
318 | // Luke is the hero of Episode V.
319 | return humanData["1000"];
320 | }
321 | // Artoo is the hero otherwise.
322 | return droidData["2001"];
323 | }
324 |
325 | /**
326 | * Allows us to query for the human with the given id.
327 | */
328 | function getHuman(id) {
329 | return humanData[id];
330 | }
331 |
332 | /**
333 | * Allows us to query for the droid with the given id.
334 | */
335 | function getDroid(id) {
336 | return droidData[id];
337 | }
338 |
339 | function getStarship(id) {
340 | return starshipData[id];
341 | }
342 |
343 | function getMovie(id) {
344 | return movieData[id];
345 | }
346 |
347 | function toCursor(str) {
348 | return Buffer("cursor" + str).toString("base64");
349 | }
350 |
351 | function fromCursor(str) {
352 | return Buffer.from(str, "base64").toString().slice(6);
353 | }
354 |
355 | const resolvers = {
356 | Query: {
357 | hero: (root, { episode }) => getHero(episode),
358 | character: (root, { id }) => getCharacter(id),
359 | human: (root, { id }) => getHuman(id),
360 | droid: (root, { id }) => getDroid(id),
361 | starship: (root, { id }) => getStarship(id),
362 | movies: () => movies,
363 | reviews: () => null,
364 | search: (root, { text }) => {
365 | const re = new RegExp(text, "i");
366 |
367 | const allData = [...humans, ...droids, ...starships];
368 |
369 | return allData.filter(obj => re.test(obj.name));
370 | },
371 | },
372 | Mutation: {
373 | createReview: (root, { episode, review }) => review,
374 | },
375 | Character: {
376 | __resolveType(data, context, info) {
377 | if (humanData[data.id]) {
378 | return info.schema.getType("Human");
379 | }
380 | if (droidData[data.id]) {
381 | return info.schema.getType("Droid");
382 | }
383 | return null;
384 | },
385 | },
386 | Human: {
387 | height: ({ height }, { unit }) => {
388 | if (unit === "FOOT") {
389 | return height * 3.28084;
390 | }
391 |
392 | return height;
393 | },
394 | friends: ({ friends }) => friends.map(getCharacter),
395 | friendsConnection: ({ friends }, { first, after }) => {
396 | first = first || friends.length;
397 | after = after ? parseInt(fromCursor(after), 10) : 0;
398 | const edges = friends
399 | .map((friend, i) => ({
400 | cursor: toCursor(i + 1),
401 | node: getCharacter(friend),
402 | }))
403 | .slice(after, first + after);
404 | const slicedFriends = edges.map(({ node }) => node);
405 | return {
406 | edges,
407 | friends: slicedFriends,
408 | pageInfo: {
409 | startCursor: edges.length > 0 ? edges[0].cursor : null,
410 | hasNextPage: first + after < friends.length,
411 | endCursor: edges.length > 0 ? edges[edges.length - 1].cursor : null,
412 | },
413 | totalCount: friends.length,
414 | };
415 | },
416 | starships: ({ starships }) => starships.map(getStarship),
417 | image: ({ image }) => image,
418 | appearsIn: ({ appearsIn }) => appearsIn,
419 | },
420 | Droid: {
421 | friends: ({ friends }) => friends.map(getCharacter),
422 | friendsConnection: ({ friends }, { first, after }) => {
423 | first = first || friends.length;
424 | after = after ? parseInt(fromCursor(after), 10) : 0;
425 | const edges = friends
426 | .map((friend, i) => ({
427 | cursor: toCursor(i + 1),
428 | node: getCharacter(friend),
429 | }))
430 | .slice(after, first + after);
431 | const slicedFriends = edges.map(({ node }) => node);
432 | return {
433 | edges,
434 | friends: slicedFriends,
435 | pageInfo: {
436 | startCursor: edges.length > 0 ? edges[0].cursor : null,
437 | hasNextPage: first + after < friends.length,
438 | endCursor: edges.length > 0 ? edges[edges.length - 1].cursor : null,
439 | },
440 | totalCount: friends.length,
441 | };
442 | },
443 | appearsIn: ({ appearsIn }) => appearsIn,
444 | image: ({ image }) => image,
445 | },
446 | FriendsConnection: {
447 | edges: ({ edges }) => edges,
448 | friends: ({ friends }) => friends,
449 | pageInfo: ({ pageInfo }) => pageInfo,
450 | totalCount: ({ totalCount }) => totalCount,
451 | },
452 | FriendsEdge: {
453 | node: ({ node }) => node,
454 | cursor: ({ cursor }) => cursor,
455 | },
456 | Starship: {
457 | length: ({ length }, { unit }) => {
458 | if (unit === "FOOT") {
459 | return length * 3.28084;
460 | }
461 |
462 | return length;
463 | },
464 | },
465 | SearchResult: {
466 | __resolveType(data, context, info) {
467 | if (humanData[data.id]) {
468 | return info.schema.getType("Human");
469 | }
470 | if (droidData[data.id]) {
471 | return info.schema.getType("Droid");
472 | }
473 | if (starshipData[data.id]) {
474 | return info.schema.getType("Starship");
475 | }
476 | return null;
477 | },
478 | },
479 | };
480 |
481 | /**
482 | * Finally, we construct our schema (whose starting query type is the query
483 | * type we defined above) and export it.
484 | */
485 | export const schema = makeExecutableSchema({
486 | typeDefs: [schemaString],
487 | resolvers,
488 | });
489 |
490 | /*
491 | License from https://github.com/graphql/graphql.github.io/blob/source/LICENSE
492 |
493 | LICENSE AGREEMENT For graphql.org software
494 |
495 | Facebook, Inc. (“Facebook”) owns all right, title and interest, including all
496 | intellectual property and other proprietary rights, in and to the graphql.org
497 | software. Subject to your compliance with these terms, you are hereby granted a
498 | non-exclusive, worldwide, royalty-free copyright license to (1) use and copy the
499 | graphql.org software; and (2) reproduce and distribute the graphql.org software
500 | as part of your own software (“Your Software”). Facebook reserves all rights not
501 | expressly granted to you in this license agreement.
502 |
503 | THE SOFTWARE AND DOCUMENTATION, IF ANY, ARE PROVIDED "AS IS" AND ANY EXPRESS OR
504 | IMPLIED WARRANTIES (INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
505 | MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE) ARE DISCLAIMED. IN NO
506 | EVENT SHALL FACEBOOK OR ITS AFFILIATES, OFFICES, DIRECTORS OR EMPLOYEES BE
507 | LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
508 | CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE
509 | GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
510 | HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
511 | LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF
512 | THE USE OF THE SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
513 |
514 | You will include in Your Software (e.g., in the file(s), documentation or other
515 | materials accompanying your software): (1) the disclaimer set forth above; (2)
516 | this sentence; and (3) the following copyright notice:
517 |
518 | Copyright (c) 2015, Facebook, Inc. All rights reserved.
519 | */
520 |
--------------------------------------------------------------------------------