├── .gitignore
├── README.md
├── assets
├── no-server.png
└── server.png
└── examples
├── graphql-wrapper-rest
├── .eslintrc.json
├── .gitignore
├── README.md
├── package-lock.json
├── package.json
├── public
│ ├── favicon.ico
│ ├── index.html
│ └── manifest.json
├── src
│ ├── App.css
│ ├── App.js
│ ├── App.test.js
│ ├── AppRouter.js
│ ├── components
│ │ └── QueryExample.js
│ ├── graphql
│ │ ├── resolvers.js
│ │ └── typeDefs.js
│ ├── index.css
│ ├── index.js
│ ├── logo.svg
│ └── registerServiceWorker.js
└── yarn.lock
└── schema-stitching
├── .eslintrc.json
├── .gitignore
├── README.md
├── package-lock.json
├── package.json
├── public
├── favicon.ico
├── index.html
└── manifest.json
├── src
├── App.css
├── App.js
├── App.test.js
├── AppRouter.js
├── components
│ └── QueryExample.js
├── graphql
│ ├── linkTypeDefs.js
│ ├── resolvers.js
│ ├── schemaStitchingResolvers.js
│ └── typeDefs.js
├── index.css
├── index.js
├── logo.svg
└── registerServiceWorker.js
└── yarn.lock
/.gitignore:
--------------------------------------------------------------------------------
1 | *node_modules
2 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # GraphQL without a server
2 |
3 | ## TL;DR
4 |
5 | - You do not need a GraphQL server to use the GraphQL APIs on the client. ([Example](https://github.com/hasura/client-side-graphql/tree/master/examples/graphql-wrapper-rest))
6 | - You do not need a separate GraphQL server to stitch the schemas of two existing GraphQL APIs. ([Example](https://github.com/hasura/client-side-graphql/tree/master/examples/schema-stitching))
7 |
8 | ## Introduction
9 |
10 | GraphQL is essentially a syntax that describes how to ask for data. A GraphQL API is something that accepts queries in GraphQL syntax and resolves them to whatever is asked by a query. Traditionally, this are written on a server and exposed on a single endpoint. However, since the growth of [Apollo Client](https://www.apollographql.com/client/), and the tools around it, this GraphQL wrapper can be written on the client as well.
11 |
12 | ## Analogy
13 |
14 | 
15 |
16 | 
17 |
18 | ## Examples
19 |
20 | ### Simple GraphQL schema
21 |
22 | This example makes a simple hello-world GraphQL schema.
23 |
24 | ```js
25 | import { makeExecutableSchema } from 'graphql-tools';
26 | import { SchemaLink } from 'apollo-link-schema';
27 | import { InMemoryCache } from 'apollo-cache-inmemory';
28 | import ApolloClient from 'apollo-client';
29 |
30 | const typeDefs = `
31 | type Hello {
32 | message: String
33 | }
34 | `;
35 |
36 | const resolvers = {
37 | Query: {
38 | message: (root, args, context, info) => "hello-world"
39 | }
40 | };
41 |
42 | const schema = makeExecutableSchema({
43 | typeDefs,
44 | resolvers
45 | });
46 |
47 | const client = new ApolloClient({
48 | link: new SchemaLink({ schema }),
49 | cache: new InMemoryCache()
50 | })
51 |
52 | // You can use this client in your app and it will work like any other GraphQL server
53 |
54 | ```
55 |
56 | Check out [this example](https://github.com/hasura/client-side-graphql/tree/master/examples/graphql-wrapper-rest) where we have written a GraphQL wrapper over the [Meta weather REST API](https://www.metaweather.com/).
57 |
58 | ### Schema Stitching
59 |
60 | This is an example of stitching two remote GraphQL schemas.
61 |
62 | ```js
63 | import { makeRemoteExecutableSchema, introspectSchema, mergeSchemas } from 'graphql-tools';
64 | import { SchemaLink } from 'apollo-link-schema';
65 | import ApolloClient from 'apollo-client';
66 |
67 | const uri1 = 'https://server1.com/graphql';
68 | const uri2 = 'https://server2.com/graphql';
69 |
70 | const getRemoteExecutableSchema = async (uri) => {
71 | const httpLink = new HttpLink({ uri });
72 | const remoteSchema = await introspectSchema(httpLink);
73 | return makeRemoteExecutableSchema({ schema: remoteSchema, link: httpLink });
74 | }
75 |
76 | const executableSchema1 = await getRemoteExecutableSchema(uri1);
77 | const executableSchema2 = await getRemoteExecutableSchema(uri2);
78 |
79 | const newSchema = mergeSchemas({
80 | schemas: [
81 | executableSchema1,
82 | executableSchema2
83 | ]
84 | });
85 |
86 | const client = new ApolloClient({
87 | link: new SchemaLink({ schema: newSchema }),
88 | cache: new InMemoryCache()
89 | });
90 |
91 | // You can use this client in your app and it will work like any other GraphQL server
92 | ```
93 |
94 | You can also have custom resolvers if you want to link your schemas in some way. You just have to add them to the resolvers field in the `mergeSchemas` function.
95 |
96 | ```js
97 |
98 | const resolvers = {
99 | Query: {
100 | ...
101 | },
102 | Mutation: {
103 | ...
104 | }
105 | }
106 |
107 | const newSchema = mergeSchemas({
108 | schemas: [
109 | executableSchema1,
110 | executableSchema2
111 | ],
112 | resolvers: resolvers
113 | });
114 | ```
115 |
116 | Check out [this example](https://github.com/hasura/client-side-graphql/tree/master/examples/schema-stitching) where we stitch a remote GraphQL schema with a local GraphQL schema.
117 |
--------------------------------------------------------------------------------
/assets/no-server.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/hasura/client-side-graphql/8608faf48bfe89b0bc8b6ab5dcc0e6e8142fc64c/assets/no-server.png
--------------------------------------------------------------------------------
/assets/server.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/hasura/client-side-graphql/8608faf48bfe89b0bc8b6ab5dcc0e6e8142fc64c/assets/server.png
--------------------------------------------------------------------------------
/examples/graphql-wrapper-rest/.eslintrc.json:
--------------------------------------------------------------------------------
1 | {
2 | "env": {
3 | "browser": true,
4 | "es6": true
5 | },
6 | "extends": "eslint:recommended",
7 | "parserOptions": {
8 | "ecmaFeatures": {
9 | "jsx": true
10 | },
11 | "ecmaVersion": 2018,
12 | "sourceType": "module"
13 | },
14 | "plugins": [
15 | "react"
16 | ],
17 | "rules": {
18 | "indent": [
19 | "error",
20 | 4
21 | ],
22 | "linebreak-style": [
23 | "error",
24 | "unix"
25 | ],
26 | "quotes": [
27 | "error",
28 | "single"
29 | ],
30 | "semi": [
31 | "error",
32 | "always"
33 | ]
34 | }
35 | }
--------------------------------------------------------------------------------
/examples/graphql-wrapper-rest/.gitignore:
--------------------------------------------------------------------------------
1 | # See https://help.github.com/ignore-files/ for more about ignoring files.
2 |
3 | # dependencies
4 | /node_modules
5 |
6 | # testing
7 | /coverage
8 |
9 | # production
10 | /build
11 |
12 | # misc
13 | .DS_Store
14 | .env.local
15 | .env.development.local
16 | .env.test.local
17 | .env.production.local
18 |
19 | npm-debug.log*
20 | yarn-debug.log*
21 | yarn-error.log*
22 |
--------------------------------------------------------------------------------
/examples/graphql-wrapper-rest/README.md:
--------------------------------------------------------------------------------
1 | To run this example project, please run:
2 |
3 | ```bash
4 | $ npm install
5 | $ npm start
6 | ```
7 |
8 | Check [this file](https://github.com/hasura/client-side-graphql/blob/master/examples/graphql-wrapper-rest/src/App.js) to see how the GraphQL to REST mapping is done.
9 |
--------------------------------------------------------------------------------
/examples/graphql-wrapper-rest/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "graphql-wrapper-rest",
3 | "version": "0.1.0",
4 | "private": true,
5 | "dependencies": {
6 | "apollo-boost": "^0.1.10",
7 | "apollo-link-schema": "^1.1.0",
8 | "graphql": "^0.13.2",
9 | "graphql-tools": "^3.0.5",
10 | "react": "^16.4.1",
11 | "react-apollo": "^2.1.9",
12 | "react-dom": "^16.4.1",
13 | "react-router-dom": "^4.3.1",
14 | "react-scripts": "^1.1.1"
15 | },
16 | "scripts": {
17 | "start": "react-scripts start",
18 | "build": "react-scripts build",
19 | "test": "react-scripts test --env=jsdom",
20 | "eject": "react-scripts eject"
21 | },
22 | "devDependencies": {
23 | "eslint-plugin-graphql": "^1.5.0",
24 | "eslint": "^5.1.0",
25 | "eslint-plugin-react": "^7.10.0"
26 | }
27 | }
28 |
--------------------------------------------------------------------------------
/examples/graphql-wrapper-rest/public/favicon.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/hasura/client-side-graphql/8608faf48bfe89b0bc8b6ab5dcc0e6e8142fc64c/examples/graphql-wrapper-rest/public/favicon.ico
--------------------------------------------------------------------------------
/examples/graphql-wrapper-rest/public/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
11 |
12 |
13 |
22 | React App
23 |
24 |
25 |
28 |
29 |
39 |
40 |
41 |
--------------------------------------------------------------------------------
/examples/graphql-wrapper-rest/public/manifest.json:
--------------------------------------------------------------------------------
1 | {
2 | "short_name": "React App",
3 | "name": "Create React App Sample",
4 | "icons": [
5 | {
6 | "src": "favicon.ico",
7 | "sizes": "64x64 32x32 24x24 16x16",
8 | "type": "image/x-icon"
9 | }
10 | ],
11 | "start_url": "./index.html",
12 | "display": "standalone",
13 | "theme_color": "#000000",
14 | "background_color": "#ffffff"
15 | }
16 |
--------------------------------------------------------------------------------
/examples/graphql-wrapper-rest/src/App.css:
--------------------------------------------------------------------------------
1 | .App {
2 | text-align: center;
3 | }
4 |
5 | .Row {
6 | display: flex;
7 | flex-direction: row;
8 | }
9 |
--------------------------------------------------------------------------------
/examples/graphql-wrapper-rest/src/App.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { ApolloProvider } from 'react-apollo';
3 | import ApolloClient from 'apollo-client';
4 | import { SchemaLink } from 'apollo-link-schema';
5 | import { InMemoryCache } from 'apollo-cache-inmemory';
6 | import { makeExecutableSchema } from 'graphql-tools';
7 | import AppRouter from './AppRouter';
8 | import typeDefs from './graphql/typeDefs';
9 | import resolvers from './graphql/resolvers';
10 |
11 |
12 | const initApollo = () => {
13 | const schema = makeExecutableSchema({
14 | typeDefs,
15 | resolvers
16 | });
17 |
18 | const client = new ApolloClient({
19 | link: new SchemaLink({ schema }),
20 | cache: new InMemoryCache({
21 | addTypename: false
22 | })
23 | });
24 |
25 | return client;
26 | };
27 |
28 | class App extends React.Component {
29 | state = {
30 | client: null,
31 | }
32 |
33 | componentWillMount() {
34 | const client = initApollo();
35 | this.setState({ client });
36 | }
37 |
38 | render() {
39 | if (!this.state.client) {
40 | return "Loading ...";
41 | }
42 | return (
43 |
44 |
45 |
46 | );
47 | }
48 | }
49 |
50 | export default App;
51 |
--------------------------------------------------------------------------------
/examples/graphql-wrapper-rest/src/App.test.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import ReactDOM from 'react-dom';
3 | import App from './App';
4 |
5 | it('renders without crashing', () => {
6 | const div = document.createElement('div');
7 | ReactDOM.render(, div);
8 | ReactDOM.unmountComponentAtNode(div);
9 | });
10 |
--------------------------------------------------------------------------------
/examples/graphql-wrapper-rest/src/AppRouter.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import './App.css';
3 | import QueryExample from './components/QueryExample';
4 |
5 | import {
6 | BrowserRouter as Router,
7 | Route,
8 | Link
9 | } from 'react-router-dom';
10 |
11 | const Home = () => (
12 |
13 |
Home
14 |
15 | );
16 |
17 | const AppRouter = (props) => {
18 | return (
19 |
20 |
21 |
22 | - Home
23 | - Example
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 | );
33 | }
34 |
35 | export default AppRouter;
36 |
37 |
38 |
39 |
40 |
41 |
42 |
43 |
44 |
45 |
46 |
47 |
48 |
49 |
50 |
51 |
52 |
53 |
--------------------------------------------------------------------------------
/examples/graphql-wrapper-rest/src/components/QueryExample.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { Query } from 'react-apollo';
3 | import gql from 'graphql-tag';
4 |
5 | const queryText = gql`
6 | query ($cityName: String!){
7 | cityWeather (city_name: $cityName) {
8 | temp
9 | max_temp
10 | min_temp
11 | }
12 | }
13 | `;
14 |
15 | class QueryComponent extends React.Component {
16 |
17 | render() {
18 | if (!this.props.cityName) {
19 | return "Please type a city to get its temperature";
20 | }
21 | return (
22 |
26 | {({ data, loading, error }) => {
27 | if (loading) {
28 | return ( Loading
);
29 | }
30 | if (error) {
31 | return ( {JSON.stringify(error)}
);
32 | }
33 | if (!data.cityWeather) {
34 | return ("No info found for this city");
35 | }
36 | const { temp, max_temp, min_temp } = data.cityWeather;
37 | return (
38 |
39 |
Temp: {temp}
40 | Max temp: {max_temp}
41 | Min temp: {min_temp}
42 |
43 | );
44 | }}
45 |
46 | )
47 | }
48 | }
49 |
50 | class CityTemp extends React.Component {
51 |
52 | constructor (props) {
53 | super(props);
54 | this.state = {
55 | text: ''
56 | }
57 | }
58 |
59 | handleTextChange = (text) => {
60 | this.setState({ ...this.state, text })
61 | }
62 |
63 | setFinalValue = () => {
64 | this.setState({ finalText: this.state.text});
65 | }
66 |
67 | render() {
68 | return (
69 |
70 |
71 |
72 |
73 |
74 | this.handleTextChange(e.target.value)}
78 | placeholder="Enter a city name"
79 | />
80 |
86 |
87 |
88 | )
89 | }
90 | }
91 |
92 | export default CityTemp;
93 |
--------------------------------------------------------------------------------
/examples/graphql-wrapper-rest/src/graphql/resolvers.js:
--------------------------------------------------------------------------------
1 | const METAWEATHER_API_URL = "https://www.metaweather.com/api/location/";
2 |
3 | const getWeather = (data) => {
4 | return fetch(METAWEATHER_API_URL + data.woeid)
5 | .then(response => response.json())
6 | };
7 |
8 | // get woeid (where on earth id) using city name
9 | const getWoeid = (place) => {
10 | return fetch(`${METAWEATHER_API_URL}search/?query=${place}`)
11 | .then(response => response.json())
12 | .then(jsonResponse => jsonResponse[0])
13 | };
14 |
15 | // resolvers -> get where on earth id -> get consolidated_weather data and return
16 | const resolvers = {
17 | Query: {
18 | cityWeather: (root, args, context, info) => {
19 | return getWoeid(args.city_name).then( (response) => {
20 | if (!response) {
21 | return null;
22 | }
23 | return getWeather(response).then( (weather) => {
24 | if (!weather) {
25 | return null;
26 | }
27 | let consolidated_weather = weather.consolidated_weather;
28 | // check for args applicable_date to apply filter
29 | consolidated_weather = args.applicable_date ? consolidated_weather.find(item => item.applicable_date === args.applicable_date) : consolidated_weather[0];
30 | const respObj = {'temp': consolidated_weather.the_temp.toString(), 'min_temp': consolidated_weather.min_temp.toString(), 'max_temp': consolidated_weather.max_temp.toString(), 'city_name': weather.title, 'applicable_date': consolidated_weather.applicable_date};
31 | return respObj;
32 | });
33 | });
34 | }
35 | },
36 | };
37 |
38 | export default resolvers;
39 |
--------------------------------------------------------------------------------
/examples/graphql-wrapper-rest/src/graphql/typeDefs.js:
--------------------------------------------------------------------------------
1 | const typeDefs = `
2 | type CityWeather {
3 | temp: String
4 | min_temp: String
5 | max_temp: String
6 | city_name: String!
7 | applicable_date: String!
8 | }
9 |
10 | type Query {
11 | cityWeather(city_name: String! applicable_date: String): CityWeather
12 | }
13 | `;
14 |
15 | export default typeDefs;
16 |
--------------------------------------------------------------------------------
/examples/graphql-wrapper-rest/src/index.css:
--------------------------------------------------------------------------------
1 | body {
2 | margin: 0;
3 | padding: 0;
4 | font-family: sans-serif;
5 | }
6 |
--------------------------------------------------------------------------------
/examples/graphql-wrapper-rest/src/index.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import ReactDOM from 'react-dom';
3 | import App from './App';
4 |
5 | ReactDOM.render(
6 | ,
7 | document.getElementById('root')
8 | );
9 |
--------------------------------------------------------------------------------
/examples/graphql-wrapper-rest/src/logo.svg:
--------------------------------------------------------------------------------
1 |
8 |
--------------------------------------------------------------------------------
/examples/graphql-wrapper-rest/src/registerServiceWorker.js:
--------------------------------------------------------------------------------
1 | // In production, we register a service worker to serve assets from local cache.
2 |
3 | // This lets the app load faster on subsequent visits in production, and gives
4 | // it offline capabilities. However, it also means that developers (and users)
5 | // will only see deployed updates on the "N+1" visit to a page, since previously
6 | // cached resources are updated in the background.
7 |
8 | // To learn more about the benefits of this model, read https://goo.gl/KwvDNy.
9 | // This link also includes instructions on opting out of this behavior.
10 |
11 | const isLocalhost = Boolean(
12 | window.location.hostname === 'localhost' ||
13 | // [::1] is the IPv6 localhost address.
14 | window.location.hostname === '[::1]' ||
15 | // 127.0.0.1/8 is considered localhost for IPv4.
16 | window.location.hostname.match(
17 | /^127(?:\.(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)){3}$/
18 | )
19 | );
20 |
21 | export default function register() {
22 | if (process.env.NODE_ENV === 'production' && 'serviceWorker' in navigator) {
23 | // The URL constructor is available in all browsers that support SW.
24 | const publicUrl = new URL(process.env.PUBLIC_URL, window.location);
25 | if (publicUrl.origin !== window.location.origin) {
26 | // Our service worker won't work if PUBLIC_URL is on a different origin
27 | // from what our page is served on. This might happen if a CDN is used to
28 | // serve assets; see https://github.com/facebookincubator/create-react-app/issues/2374
29 | return;
30 | }
31 |
32 | window.addEventListener('load', () => {
33 | const swUrl = `${process.env.PUBLIC_URL}/service-worker.js`;
34 |
35 | if (isLocalhost) {
36 | // This is running on localhost. Lets check if a service worker still exists or not.
37 | checkValidServiceWorker(swUrl);
38 |
39 | // Add some additional logging to localhost, pointing developers to the
40 | // service worker/PWA documentation.
41 | navigator.serviceWorker.ready.then(() => {
42 | console.log(
43 | 'This web app is being served cache-first by a service ' +
44 | 'worker. To learn more, visit https://goo.gl/SC7cgQ'
45 | );
46 | });
47 | } else {
48 | // Is not local host. Just register service worker
49 | registerValidSW(swUrl);
50 | }
51 | });
52 | }
53 | }
54 |
55 | function registerValidSW(swUrl) {
56 | navigator.serviceWorker
57 | .register(swUrl)
58 | .then(registration => {
59 | registration.onupdatefound = () => {
60 | const installingWorker = registration.installing;
61 | installingWorker.onstatechange = () => {
62 | if (installingWorker.state === 'installed') {
63 | if (navigator.serviceWorker.controller) {
64 | // At this point, the old content will have been purged and
65 | // the fresh content will have been added to the cache.
66 | // It's the perfect time to display a "New content is
67 | // available; please refresh." message in your web app.
68 | console.log('New content is available; please refresh.');
69 | } else {
70 | // At this point, everything has been precached.
71 | // It's the perfect time to display a
72 | // "Content is cached for offline use." message.
73 | console.log('Content is cached for offline use.');
74 | }
75 | }
76 | };
77 | };
78 | })
79 | .catch(error => {
80 | console.error('Error during service worker registration:', error);
81 | });
82 | }
83 |
84 | function checkValidServiceWorker(swUrl) {
85 | // Check if the service worker can be found. If it can't reload the page.
86 | fetch(swUrl)
87 | .then(response => {
88 | // Ensure service worker exists, and that we really are getting a JS file.
89 | if (
90 | response.status === 404 ||
91 | response.headers.get('content-type').indexOf('javascript') === -1
92 | ) {
93 | // No service worker found. Probably a different app. Reload the page.
94 | navigator.serviceWorker.ready.then(registration => {
95 | registration.unregister().then(() => {
96 | window.location.reload();
97 | });
98 | });
99 | } else {
100 | // Service worker found. Proceed as normal.
101 | registerValidSW(swUrl);
102 | }
103 | })
104 | .catch(() => {
105 | console.log(
106 | 'No internet connection found. App is running in offline mode.'
107 | );
108 | });
109 | }
110 |
111 | export function unregister() {
112 | if ('serviceWorker' in navigator) {
113 | navigator.serviceWorker.ready.then(registration => {
114 | registration.unregister();
115 | });
116 | }
117 | }
118 |
--------------------------------------------------------------------------------
/examples/schema-stitching/.eslintrc.json:
--------------------------------------------------------------------------------
1 | {
2 | "env": {
3 | "browser": true,
4 | "es6": true
5 | },
6 | "extends": "eslint:recommended",
7 | "parserOptions": {
8 | "ecmaFeatures": {
9 | "jsx": true
10 | },
11 | "ecmaVersion": 2018,
12 | "sourceType": "module"
13 | },
14 | "plugins": [
15 | "react"
16 | ],
17 | "rules": {
18 | "accessor-pairs": "error",
19 | "array-bracket-newline": "error",
20 | "array-bracket-spacing": "error",
21 | "array-callback-return": "error",
22 | "array-element-newline": "error",
23 | "arrow-body-style": "error",
24 | "arrow-parens": [
25 | "error",
26 | "as-needed"
27 | ],
28 | "arrow-spacing": [
29 | "error",
30 | {
31 | "after": true,
32 | "before": true
33 | }
34 | ],
35 | "block-scoped-var": "error",
36 | "block-spacing": "error",
37 | "brace-style": [
38 | "error",
39 | "1tbs"
40 | ],
41 | "callback-return": "error",
42 | "camelcase": "error",
43 | "capitalized-comments": "off",
44 | "class-methods-use-this": "off",
45 | "comma-dangle": "error",
46 | "comma-spacing": [
47 | "error",
48 | {
49 | "after": true,
50 | "before": false
51 | }
52 | ],
53 | "comma-style": [
54 | "error",
55 | "last"
56 | ],
57 | "complexity": "error",
58 | "computed-property-spacing": "error",
59 | "consistent-return": "error",
60 | "consistent-this": "error",
61 | "curly": "error",
62 | "default-case": "error",
63 | "dot-location": [
64 | "error",
65 | "property"
66 | ],
67 | "dot-notation": [
68 | "error",
69 | {
70 | "allowKeywords": true
71 | }
72 | ],
73 | "eol-last": "error",
74 | "eqeqeq": "error",
75 | "func-call-spacing": "error",
76 | "func-name-matching": "error",
77 | "func-names": "error",
78 | "func-style": [
79 | "error",
80 | "declaration"
81 | ],
82 | "function-paren-newline": "off",
83 | "generator-star-spacing": "error",
84 | "global-require": "error",
85 | "guard-for-in": "error",
86 | "handle-callback-err": "error",
87 | "id-blacklist": "error",
88 | "id-length": "error",
89 | "id-match": "error",
90 | "implicit-arrow-linebreak": [
91 | "error",
92 | "beside"
93 | ],
94 | "indent": "off",
95 | "indent-legacy": "off",
96 | "init-declarations": "error",
97 | "jsx-quotes": [
98 | "error",
99 | "prefer-double"
100 | ],
101 | "key-spacing": "error",
102 | "keyword-spacing": [
103 | "error",
104 | {
105 | "after": true,
106 | "before": true
107 | }
108 | ],
109 | "line-comment-position": "error",
110 | "linebreak-style": [
111 | "error",
112 | "unix"
113 | ],
114 | "lines-around-comment": "error",
115 | "lines-around-directive": "error",
116 | "lines-between-class-members": "error",
117 | "max-classes-per-file": "error",
118 | "max-depth": "error",
119 | "max-len": "off",
120 | "max-lines": "error",
121 | "max-lines-per-function": "error",
122 | "max-nested-callbacks": "error",
123 | "max-params": "error",
124 | "max-statements": "error",
125 | "max-statements-per-line": "error",
126 | "multiline-comment-style": [
127 | "error",
128 | "separate-lines"
129 | ],
130 | "multiline-ternary": "error",
131 | "new-cap": "error",
132 | "new-parens": "error",
133 | "newline-after-var": "off",
134 | "newline-before-return": "error",
135 | "newline-per-chained-call": "error",
136 | "no-alert": "error",
137 | "no-array-constructor": "error",
138 | "no-await-in-loop": "error",
139 | "no-bitwise": "error",
140 | "no-buffer-constructor": "error",
141 | "no-caller": "error",
142 | "no-catch-shadow": "error",
143 | "no-confusing-arrow": "error",
144 | "no-continue": "error",
145 | "no-div-regex": "error",
146 | "no-duplicate-imports": "error",
147 | "no-else-return": "error",
148 | "no-empty-function": "error",
149 | "no-eq-null": "error",
150 | "no-eval": "error",
151 | "no-extend-native": "error",
152 | "no-extra-bind": "error",
153 | "no-extra-label": "error",
154 | "no-extra-parens": "error",
155 | "no-floating-decimal": "error",
156 | "no-implicit-coercion": "error",
157 | "no-implicit-globals": "error",
158 | "no-implied-eval": "error",
159 | "no-inline-comments": "error",
160 | "no-invalid-this": "error",
161 | "no-iterator": "error",
162 | "no-label-var": "error",
163 | "no-labels": "error",
164 | "no-lone-blocks": "error",
165 | "no-lonely-if": "error",
166 | "no-loop-func": "error",
167 | "no-magic-numbers": "off",
168 | "no-mixed-operators": "error",
169 | "no-mixed-requires": "error",
170 | "no-multi-assign": "error",
171 | "no-multi-spaces": "error",
172 | "no-multi-str": "error",
173 | "no-multiple-empty-lines": "error",
174 | "no-native-reassign": "error",
175 | "no-negated-condition": "error",
176 | "no-negated-in-lhs": "error",
177 | "no-nested-ternary": "error",
178 | "no-new": "error",
179 | "no-new-func": "error",
180 | "no-new-object": "error",
181 | "no-new-require": "error",
182 | "no-new-wrappers": "error",
183 | "no-octal-escape": "error",
184 | "no-param-reassign": "error",
185 | "no-path-concat": "error",
186 | "no-plusplus": "error",
187 | "no-process-env": "off",
188 | "no-process-exit": "error",
189 | "no-proto": "error",
190 | "no-prototype-builtins": "error",
191 | "no-restricted-globals": "error",
192 | "no-restricted-imports": "error",
193 | "no-restricted-modules": "error",
194 | "no-restricted-properties": "error",
195 | "no-restricted-syntax": "error",
196 | "no-return-assign": "error",
197 | "no-return-await": "error",
198 | "no-script-url": "error",
199 | "no-self-compare": "error",
200 | "no-sequences": "error",
201 | "no-shadow": "error",
202 | "no-shadow-restricted-names": "error",
203 | "no-spaced-func": "error",
204 | "no-sync": "error",
205 | "no-tabs": "error",
206 | "no-template-curly-in-string": "error",
207 | "no-ternary": "error",
208 | "no-throw-literal": "error",
209 | "no-trailing-spaces": "error",
210 | "no-undef-init": "error",
211 | "no-undefined": "error",
212 | "no-underscore-dangle": "error",
213 | "no-unmodified-loop-condition": "error",
214 | "no-unneeded-ternary": "error",
215 | "no-unused-expressions": "error",
216 | "no-use-before-define": "off",
217 | "no-useless-call": "error",
218 | "no-useless-computed-key": "error",
219 | "no-useless-concat": "error",
220 | "no-useless-constructor": "error",
221 | "no-useless-rename": "error",
222 | "no-useless-return": "error",
223 | "no-var": "error",
224 | "no-void": "error",
225 | "no-warning-comments": "error",
226 | "no-whitespace-before-property": "error",
227 | "no-with": "error",
228 | "nonblock-statement-body-position": "error",
229 | "object-curly-newline": "error",
230 | "object-curly-spacing": [
231 | "error",
232 | "always"
233 | ],
234 | "object-property-newline": "error",
235 | "object-shorthand": "error",
236 | "one-var": "error",
237 | "one-var-declaration-per-line": "error",
238 | "operator-assignment": "error",
239 | "operator-linebreak": "error",
240 | "padded-blocks": "off",
241 | "padding-line-between-statements": "error",
242 | "prefer-arrow-callback": "error",
243 | "prefer-const": "error",
244 | "prefer-destructuring": "error",
245 | "prefer-numeric-literals": "error",
246 | "prefer-object-spread": "error",
247 | "prefer-promise-reject-errors": "error",
248 | "prefer-reflect": "error",
249 | "prefer-rest-params": "error",
250 | "prefer-spread": "error",
251 | "prefer-template": "error",
252 | "quote-props": "off",
253 | "quotes": [
254 | "error",
255 | "single"
256 | ],
257 | "radix": "error",
258 | "require-await": "error",
259 | "require-jsdoc": "off",
260 | "rest-spread-spacing": "error",
261 | "semi": "error",
262 | "semi-spacing": "error",
263 | "semi-style": [
264 | "error",
265 | "last"
266 | ],
267 | "sort-imports": "off",
268 | "sort-keys": [
269 | "error",
270 | "desc"
271 | ],
272 | "sort-vars": "error",
273 | "space-before-blocks": "error",
274 | "space-before-function-paren": "off",
275 | "space-in-parens": [
276 | "error",
277 | "never"
278 | ],
279 | "space-infix-ops": "error",
280 | "space-unary-ops": "error",
281 | "spaced-comment": [
282 | "error",
283 | "always"
284 | ],
285 | "strict": "error",
286 | "switch-colon-spacing": "error",
287 | "symbol-description": "error",
288 | "template-curly-spacing": [
289 | "error",
290 | "never"
291 | ],
292 | "template-tag-spacing": "error",
293 | "unicode-bom": [
294 | "error",
295 | "never"
296 | ],
297 | "valid-jsdoc": "error",
298 | "vars-on-top": "error",
299 | "wrap-iife": "error",
300 | "wrap-regex": "error",
301 | "yield-star-spacing": "error",
302 | "yoda": [
303 | "error",
304 | "never"
305 | ]
306 | }
307 | }
--------------------------------------------------------------------------------
/examples/schema-stitching/.gitignore:
--------------------------------------------------------------------------------
1 | # See https://help.github.com/ignore-files/ for more about ignoring files.
2 |
3 | # dependencies
4 | /node_modules
5 |
6 | # testing
7 | /coverage
8 |
9 | # production
10 | /build
11 |
12 | # misc
13 | .DS_Store
14 | .env.local
15 | .env.development.local
16 | .env.test.local
17 | .env.production.local
18 |
19 | npm-debug.log*
20 | yarn-debug.log*
21 | yarn-error.log*
22 |
--------------------------------------------------------------------------------
/examples/schema-stitching/README.md:
--------------------------------------------------------------------------------
1 | To run this example project, please run:
2 |
3 | ```bash
4 | $ npm install
5 | $ npm start
6 | ```
7 |
8 | Check [this file](https://github.com/hasura/client-side-graphql/blob/master/examples/schema-stitching/src/App.js) to see how schema stitching is being done.
9 |
--------------------------------------------------------------------------------
/examples/schema-stitching/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "react-graphql-boilerplate",
3 | "version": "0.1.0",
4 | "private": true,
5 | "dependencies": {
6 | "apollo-boost": "^0.1.10",
7 | "apollo-link-schema": "^1.1.0",
8 | "graphql": "^0.13.2",
9 | "graphql-tools": "^3.0.5",
10 | "react": "^16.4.1",
11 | "react-apollo": "^2.1.9",
12 | "react-dom": "^16.4.1",
13 | "react-router-dom": "^4.3.1",
14 | "react-scripts": "^1.1.1"
15 | },
16 | "scripts": {
17 | "start": "react-scripts start",
18 | "build": "react-scripts build",
19 | "test": "react-scripts test --env=jsdom",
20 | "eject": "react-scripts eject"
21 | },
22 | "devDependencies": {
23 | "eslint-plugin-graphql": "^1.5.0",
24 | "eslint": "^5.1.0",
25 | "eslint-plugin-react": "^7.10.0"
26 | }
27 | }
28 |
--------------------------------------------------------------------------------
/examples/schema-stitching/public/favicon.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/hasura/client-side-graphql/8608faf48bfe89b0bc8b6ab5dcc0e6e8142fc64c/examples/schema-stitching/public/favicon.ico
--------------------------------------------------------------------------------
/examples/schema-stitching/public/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
11 |
12 |
13 |
22 | React App
23 |
24 |
25 |
28 |
29 |
39 |
40 |
41 |
--------------------------------------------------------------------------------
/examples/schema-stitching/public/manifest.json:
--------------------------------------------------------------------------------
1 | {
2 | "short_name": "React App",
3 | "name": "Create React App Sample",
4 | "icons": [
5 | {
6 | "src": "favicon.ico",
7 | "sizes": "64x64 32x32 24x24 16x16",
8 | "type": "image/x-icon"
9 | }
10 | ],
11 | "start_url": "./index.html",
12 | "display": "standalone",
13 | "theme_color": "#000000",
14 | "background_color": "#ffffff"
15 | }
16 |
--------------------------------------------------------------------------------
/examples/schema-stitching/src/App.css:
--------------------------------------------------------------------------------
1 | .App {
2 | text-align: center;
3 | }
4 |
5 | .App-logo {
6 | animation: App-logo-spin infinite 20s linear;
7 | height: 80px;
8 | }
9 |
10 | .App-header {
11 | background-color: #222;
12 | height: 150px;
13 | padding: 20px;
14 | color: white;
15 | }
16 |
17 | .App-title {
18 | font-size: 1.5em;
19 | }
20 |
21 | .App-intro {
22 | font-size: large;
23 | }
24 |
25 | @keyframes App-logo-spin {
26 | from { transform: rotate(0deg); }
27 | to { transform: rotate(360deg); }
28 | }
29 |
--------------------------------------------------------------------------------
/examples/schema-stitching/src/App.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { ApolloProvider } from 'react-apollo';
3 | import AppRouter from './AppRouter';
4 | import ApolloClient from 'apollo-client';
5 | import { SchemaLink } from 'apollo-link-schema';
6 | import { HttpLink } from 'apollo-link-http';
7 | import { InMemoryCache } from 'apollo-cache-inmemory';
8 | import {
9 | makeExecutableSchema,
10 | makeRemoteExecutableSchema,
11 | introspectSchema,
12 | mergeSchemas
13 | } from 'graphql-tools';
14 | import weatherTypeDefs from './graphql/typeDefs';
15 | import weatherResolvers from './graphql/resolvers';
16 | import personWeatherTypeExtensions from './graphql/linkTypeDefs';
17 | import schemaStitchingResolvers from './graphql/schemaStitchingResolvers';
18 |
19 | const personGraphQLUri = 'https://bazookaand.herokuapp.com/v1alpha1/graphql';
20 |
21 | const initApollo = async () => {
22 | const link = new HttpLink({uri: personGraphQLUri});
23 | const personSchema = await introspectSchema(link);
24 | const executablePersonSchema = makeRemoteExecutableSchema({
25 | schema: personSchema,
26 | link
27 | });
28 | const executableWeatherSchema = makeExecutableSchema({
29 | typeDefs: weatherTypeDefs,
30 | resolvers: weatherResolvers
31 | });
32 |
33 | const newSchema = mergeSchemas({
34 | schemas: [
35 | executableWeatherSchema,
36 | executablePersonSchema,
37 | personWeatherTypeExtensions
38 | ],
39 | resolvers: schemaStitchingResolvers({
40 | weather: executableWeatherSchema,
41 | person: executablePersonSchema
42 | })
43 | });
44 |
45 | const client = new ApolloClient({
46 | link: new SchemaLink({ schema: newSchema }),
47 | cache: new InMemoryCache({
48 | addTypename: false
49 | }),
50 | connectToDevTools: true
51 | });
52 |
53 | return client;
54 | };
55 |
56 | class App extends React.Component {
57 | state = {
58 | client: null,
59 | }
60 |
61 | async componentWillMount() {
62 | const client = await initApollo();
63 | this.setState({ client });
64 | }
65 |
66 | render() {
67 | if (!this.state.client) {
68 | return "Loading ...";
69 | }
70 | return (
71 |
72 |
73 |
74 | );
75 | }
76 | }
77 |
78 | export default App;
79 |
--------------------------------------------------------------------------------
/examples/schema-stitching/src/App.test.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import ReactDOM from 'react-dom';
3 | import App from './App';
4 |
5 | it('renders without crashing', () => {
6 | const div = document.createElement('div');
7 | ReactDOM.render(, div);
8 | ReactDOM.unmountComponentAtNode(div);
9 | });
10 |
--------------------------------------------------------------------------------
/examples/schema-stitching/src/AppRouter.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import './App.css';
3 | import QueryExample from './components/QueryExample.js';
4 |
5 | import {
6 | BrowserRouter as Router,
7 | Route,
8 | Link
9 | } from 'react-router-dom';
10 |
11 | const Home = () => (
12 |
13 |
Hello world
14 |
15 | );
16 |
17 | const AppRouter = (props) => {
18 | console.log(props);
19 | return (
20 |
21 |
22 |
23 | - Home
24 | - Example
25 |
26 |
27 |
28 |
}/>
29 |
30 |
31 |
32 | );
33 | }
34 |
35 | export default AppRouter;
36 |
--------------------------------------------------------------------------------
/examples/schema-stitching/src/components/QueryExample.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { Query } from 'react-apollo';
3 | import gql from 'graphql-tag';
4 |
5 | const queryText = gql`
6 | query ($personName: String!){
7 | person (where: { name: { _eq: $personName }}) {
8 | name
9 | age
10 | city
11 | city_weather {
12 | temp
13 | max_temp
14 | min_temp
15 | }
16 | }
17 | }
18 | `;
19 |
20 | class QueryComponent extends React.Component {
21 |
22 | render() {
23 | if (!this.props.personName) {
24 | return "Type an person's name";
25 | }
26 | return (
27 |
31 | {({ data, loading, error }) => {
32 | if (loading) {
33 | return ( Loading
);
34 | }
35 | if (error) {
36 | return ( {JSON.stringify(error)}
);
37 | }
38 | if (data.person.length === 0) {
39 | return "Name not found in the database. Try something else";
40 | }
41 | const { name, city, age, city_weather } = data.person[0] ;
42 | const renderWeather = () => {
43 | if (city_weather) {
44 | return (
45 |
46 |
City Temp: {city_weather.temp}
47 | City Max temp: {city_weather.max_temp}
48 | City Min temp: {city_weather.min_temp}
49 |
50 | );
51 | }
52 | return City Weather: No information available
53 | }
54 | return (
55 |
56 |
Name: {name}
57 | Age: {age}
58 | City: {city}
59 | {renderWeather()}
60 |
61 | );
62 | }}
63 |
64 | )
65 | }
66 | }
67 |
68 | class CityTemp extends React.Component {
69 |
70 | constructor (props) {
71 | super(props);
72 | this.state = {
73 | text: ''
74 | }
75 | }
76 |
77 | handleTextChange = (text) => {
78 | this.setState({ ...this.state, text })
79 | }
80 |
81 | setFinalValue = () => {
82 | this.setState({ finalText: this.state.text});
83 | }
84 |
85 | render() {
86 | return (
87 |
88 |
89 |
90 |
91 |
92 | this.handleTextChange(e.target.value)}
96 | placeholder="Name"
97 | />
98 |
104 |
105 |
106 | )
107 | }
108 | }
109 |
110 | export default CityTemp;
111 |
--------------------------------------------------------------------------------
/examples/schema-stitching/src/graphql/linkTypeDefs.js:
--------------------------------------------------------------------------------
1 | const linkTypeDefs = `
2 | extend type person {
3 | city_weather: CityWeather,
4 | }
5 | `;
6 |
7 | export default linkTypeDefs;
--------------------------------------------------------------------------------
/examples/schema-stitching/src/graphql/resolvers.js:
--------------------------------------------------------------------------------
1 | import fetch from 'node-fetch';
2 |
3 | const METAWEATHER_API_URL = "https://www.metaweather.com/api/location/";
4 |
5 | const getWeather = (data) => {
6 | return fetch(METAWEATHER_API_URL + data.woeid)
7 | .then(response => response.json())
8 | };
9 |
10 | // get woeid (where on earth id) using city name
11 | const getWoeid = (place) => {
12 | return fetch(`${METAWEATHER_API_URL}search/?query=${place}`)
13 | .then(response => response.json())
14 | .then(jsonResponse => jsonResponse[0])
15 | };
16 |
17 | // resolvers -> get where on earth id -> get consolidated_weather data and return
18 | const resolvers = {
19 | Query: {
20 | cityWeather: (root, args, context, info) => {
21 | return getWoeid(args.city_name).then( (response) => {
22 | if (!response) {
23 | return null;
24 | }
25 | return getWeather(response).then( (weather) => {
26 | if (!weather) {
27 | return null;
28 | }
29 | let consolidated_weather = weather.consolidated_weather;
30 | // check for args applicable_date to apply filter
31 | consolidated_weather = args.applicable_date ? consolidated_weather.find(item => item.applicable_date === args.applicable_date) : consolidated_weather[0];
32 | const respObj = {'temp': consolidated_weather.the_temp.toString(), 'min_temp': consolidated_weather.min_temp.toString(), 'max_temp': consolidated_weather.max_temp.toString(), 'city_name': weather.title, 'applicable_date': consolidated_weather.applicable_date};
33 | return respObj;
34 | });
35 | });
36 | }
37 | },
38 | };
39 |
40 | export default resolvers;
41 |
--------------------------------------------------------------------------------
/examples/schema-stitching/src/graphql/schemaStitchingResolvers.js:
--------------------------------------------------------------------------------
1 | const schemaStitchingResolvers = (executableSchemas) => ({
2 | person: {
3 | city_weather : {
4 | resolve(parent, args, context, info) {
5 | return info.mergeInfo.delegateToSchema({
6 | schema: executableSchemas.weather,
7 | operation: 'query',
8 | fieldName: 'cityWeather',
9 | args: {
10 | city_name: parent.city,
11 | },
12 | context,
13 | info,
14 | });
15 | },
16 | },
17 | },
18 | });
19 |
20 | export default schemaStitchingResolvers;
--------------------------------------------------------------------------------
/examples/schema-stitching/src/graphql/typeDefs.js:
--------------------------------------------------------------------------------
1 | const typeDefs = `
2 | type CityWeather {
3 | temp: String
4 | min_temp: String
5 | max_temp: String
6 | city_name: String!
7 | applicable_date: String!
8 | }
9 |
10 | type Query {
11 | cityWeather(city_name: String! applicable_date: String): CityWeather
12 | }
13 | `;
14 |
15 | export default typeDefs;
16 |
--------------------------------------------------------------------------------
/examples/schema-stitching/src/index.css:
--------------------------------------------------------------------------------
1 | body {
2 | margin: 0;
3 | padding: 0;
4 | font-family: sans-serif;
5 | }
6 |
--------------------------------------------------------------------------------
/examples/schema-stitching/src/index.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import ReactDOM from 'react-dom';
3 | import App from './App';
4 |
5 | ReactDOM.render(
6 | ,
7 | document.getElementById('root')
8 | );
9 |
--------------------------------------------------------------------------------
/examples/schema-stitching/src/logo.svg:
--------------------------------------------------------------------------------
1 |
8 |
--------------------------------------------------------------------------------
/examples/schema-stitching/src/registerServiceWorker.js:
--------------------------------------------------------------------------------
1 | // In production, we register a service worker to serve assets from local cache.
2 |
3 | // This lets the app load faster on subsequent visits in production, and gives
4 | // it offline capabilities. However, it also means that developers (and users)
5 | // will only see deployed updates on the "N+1" visit to a page, since previously
6 | // cached resources are updated in the background.
7 |
8 | // To learn more about the benefits of this model, read https://goo.gl/KwvDNy.
9 | // This link also includes instructions on opting out of this behavior.
10 |
11 | const isLocalhost = Boolean(
12 | window.location.hostname === 'localhost' ||
13 | // [::1] is the IPv6 localhost address.
14 | window.location.hostname === '[::1]' ||
15 | // 127.0.0.1/8 is considered localhost for IPv4.
16 | window.location.hostname.match(
17 | /^127(?:\.(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)){3}$/
18 | )
19 | );
20 |
21 | export default function register() {
22 | if (process.env.NODE_ENV === 'production' && 'serviceWorker' in navigator) {
23 | // The URL constructor is available in all browsers that support SW.
24 | const publicUrl = new URL(process.env.PUBLIC_URL, window.location);
25 | if (publicUrl.origin !== window.location.origin) {
26 | // Our service worker won't work if PUBLIC_URL is on a different origin
27 | // from what our page is served on. This might happen if a CDN is used to
28 | // serve assets; see https://github.com/facebookincubator/create-react-app/issues/2374
29 | return;
30 | }
31 |
32 | window.addEventListener('load', () => {
33 | const swUrl = `${process.env.PUBLIC_URL}/service-worker.js`;
34 |
35 | if (isLocalhost) {
36 | // This is running on localhost. Lets check if a service worker still exists or not.
37 | checkValidServiceWorker(swUrl);
38 |
39 | // Add some additional logging to localhost, pointing developers to the
40 | // service worker/PWA documentation.
41 | navigator.serviceWorker.ready.then(() => {
42 | console.log(
43 | 'This web app is being served cache-first by a service ' +
44 | 'worker. To learn more, visit https://goo.gl/SC7cgQ'
45 | );
46 | });
47 | } else {
48 | // Is not local host. Just register service worker
49 | registerValidSW(swUrl);
50 | }
51 | });
52 | }
53 | }
54 |
55 | function registerValidSW(swUrl) {
56 | navigator.serviceWorker
57 | .register(swUrl)
58 | .then(registration => {
59 | registration.onupdatefound = () => {
60 | const installingWorker = registration.installing;
61 | installingWorker.onstatechange = () => {
62 | if (installingWorker.state === 'installed') {
63 | if (navigator.serviceWorker.controller) {
64 | // At this point, the old content will have been purged and
65 | // the fresh content will have been added to the cache.
66 | // It's the perfect time to display a "New content is
67 | // available; please refresh." message in your web app.
68 | console.log('New content is available; please refresh.');
69 | } else {
70 | // At this point, everything has been precached.
71 | // It's the perfect time to display a
72 | // "Content is cached for offline use." message.
73 | console.log('Content is cached for offline use.');
74 | }
75 | }
76 | };
77 | };
78 | })
79 | .catch(error => {
80 | console.error('Error during service worker registration:', error);
81 | });
82 | }
83 |
84 | function checkValidServiceWorker(swUrl) {
85 | // Check if the service worker can be found. If it can't reload the page.
86 | fetch(swUrl)
87 | .then(response => {
88 | // Ensure service worker exists, and that we really are getting a JS file.
89 | if (
90 | response.status === 404 ||
91 | response.headers.get('content-type').indexOf('javascript') === -1
92 | ) {
93 | // No service worker found. Probably a different app. Reload the page.
94 | navigator.serviceWorker.ready.then(registration => {
95 | registration.unregister().then(() => {
96 | window.location.reload();
97 | });
98 | });
99 | } else {
100 | // Service worker found. Proceed as normal.
101 | registerValidSW(swUrl);
102 | }
103 | })
104 | .catch(() => {
105 | console.log(
106 | 'No internet connection found. App is running in offline mode.'
107 | );
108 | });
109 | }
110 |
111 | export function unregister() {
112 | if ('serviceWorker' in navigator) {
113 | navigator.serviceWorker.ready.then(registration => {
114 | registration.unregister();
115 | });
116 | }
117 | }
118 |
--------------------------------------------------------------------------------