/packages/*/dist',
22 | ],
23 | clearMocks: true,
24 | };
25 |
--------------------------------------------------------------------------------
/.github/workflows/workflow.yml:
--------------------------------------------------------------------------------
1 | name: Main workflow
2 | on: [push]
3 | jobs:
4 | build:
5 | name: Install, Test, Snapshot, e2e and Deploy
6 | runs-on: ubuntu-latest
7 | steps:
8 | - uses: actions/checkout@master
9 |
10 | - name: Install
11 | uses: ./workflows/action-cypress/
12 | with:
13 | args: install
14 |
15 | - name: Test
16 | uses: ./workflows/action-cypress/
17 | with:
18 | args: ci
19 |
20 | - name: Snapshot UI
21 | uses: ./workflows/action-cypress/
22 | env:
23 | PERCY_TOKEN: ${{ secrets.PERCY_TOKEN }}
24 | with:
25 | args: snapshot-ui
26 |
27 | - name: End to End
28 | uses: ./workflows/action-cypress/
29 | with:
30 | args: e2e
31 |
32 | - name: Deploy
33 | uses: ./workflows/action-cypress/
34 | env:
35 | NOW_TOKEN: ${{ secrets.NOW_TOKEN }}
36 | with:
37 | args: deploy
38 |
--------------------------------------------------------------------------------
/src/components/Header.css:
--------------------------------------------------------------------------------
1 | .header {
2 | position: sticky;
3 | left: 0;
4 | top: 0;
5 | width: 100%;
6 | height: var(--navbarHeight);
7 | padding: 0;
8 | background: var(--navbar);
9 | box-shadow: 0 0 5px rgba(0, 0, 0, 0.5);
10 | z-index: 50;
11 | }
12 |
13 | .header h1 {
14 | float: left;
15 | margin: 0;
16 | padding: 0 15px;
17 | font-size: 24px;
18 | line-height: var(--navbarHeight);
19 | font-weight: 400;
20 | color: var(--navbarTextColor);
21 | }
22 |
23 | .header nav {
24 | float: right;
25 | }
26 |
27 | .header nav a {
28 | display: inline-block;
29 | line-height: var(--navbarHeight);
30 | padding: 0 15px;
31 | min-width: 50px;
32 | text-align: center;
33 | background: rgba(255, 255, 255, 0);
34 | text-decoration: none;
35 | color: var(--navbarTextColor);
36 | will-change: background-color;
37 | }
38 |
39 | .header nav a:hover,
40 | .header nav a:active {
41 | background: rgba(0, 0, 0, 0.1);
42 | }
43 |
44 | .header nav a.active {
45 | background: rgba(0, 0, 0, 0.2);
46 | }
47 |
--------------------------------------------------------------------------------
/src/utils/fetchMocks.tsx:
--------------------------------------------------------------------------------
1 | import { GlobalWithFetchMock } from 'jest-fetch-mock';
2 | import subredditFixture from './fixtures/subredditFixture';
3 |
4 | const customGlobal: GlobalWithFetchMock = global as GlobalWithFetchMock;
5 |
6 | type Subreddit = typeof subredditFixture;
7 |
8 | export function mockFetchSubredditOnce({
9 | delay = 0,
10 | subreddit = subredditFixture,
11 | }: { delay?: number; subreddit?: Subreddit | null } = {}) {
12 | customGlobal.fetch.mockResponseOnce(
13 | () =>
14 | new Promise(resolve =>
15 | setTimeout(
16 | () =>
17 | resolve({
18 | body: JSON.stringify({
19 | data: {
20 | subreddit,
21 | },
22 | }),
23 | }),
24 | delay,
25 | ),
26 | ),
27 | );
28 |
29 | return subreddit;
30 | }
31 |
32 | export function mockFetchErrorResponseOnce(message = 'fake error message') {
33 | customGlobal.fetch.mockRejectOnce(new Error(message));
34 |
35 | return message;
36 | }
37 |
--------------------------------------------------------------------------------
/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "compileOnSave": false,
3 | "compilerOptions": {
4 | "target": "esnext",
5 | "module": "esnext",
6 | "jsx": "preserve",
7 | "moduleResolution": "node",
8 | "allowSyntheticDefaultImports": true,
9 | "noUnusedLocals": true,
10 | "noUnusedParameters": true,
11 | "removeComments": false,
12 | "preserveConstEnums": true,
13 | "sourceMap": true,
14 | "skipLibCheck": true,
15 | "typeRoots": ["./node_modules/@types"],
16 | "lib": ["dom", "es2015", "es2016", "es2017.object"],
17 | "strictNullChecks": true,
18 | "rootDir": "./",
19 | "baseUrl": "./",
20 | "allowJs": true,
21 | "strict": false,
22 | "forceConsistentCasingInFileNames": true,
23 | "esModuleInterop": true,
24 | "resolveJsonModule": true,
25 | "isolatedModules": true,
26 | "noEmit": true
27 | },
28 | "include": ["**/*.ts", "**/*.tsx"],
29 | "exclude": ["node_modules"],
30 | "awesomeTypescriptLoaderOptions": {
31 | "useBabel": true,
32 | "babelCore": "@babel/core"
33 | }
34 | }
35 |
--------------------------------------------------------------------------------
/bsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "reason-graphql-demo",
3 | "sources": [
4 | {
5 | "dir": "src",
6 | "subdirs": true
7 | }
8 | ],
9 | "bs-dependencies": [
10 | "reason-react",
11 | "re-classnames",
12 | "@dblechoc/bs-next",
13 | "@dblechoc/bs-apollo"
14 | ],
15 | "reason": { "react-jsx": 3 },
16 | "package-specs": {
17 | "module": "es6",
18 | "in-source": true
19 | },
20 | "suffix": ".bs.js",
21 | "bsc-flags": ["-bs-super-errors"],
22 | "ppx-flags": ["@baransu/graphql_ppx_re/ppx6"],
23 | "refmt": 3,
24 | "warnings": {
25 | "number": "+A-48-102",
26 | "error": "+A-3-44-102"
27 | },
28 | "gentypeconfig": {
29 | "language": "typescript",
30 | "module": "es6",
31 | "importPath": "relative",
32 | "shims": {
33 | "Js": "Js",
34 | "React": "ReactShim",
35 | "ReactEvent": "ReactEvent",
36 | "ReasonPervasives": "ReasonPervasives",
37 | "ReasonReact": "ReactShim",
38 | "Next": "Next"
39 | },
40 | "debug": {
41 | "all": false,
42 | "basic": false
43 | },
44 | "exportInterfaces": false
45 | }
46 | }
47 |
--------------------------------------------------------------------------------
/src/components/Subreddit.re:
--------------------------------------------------------------------------------
1 | let ste = ReasonReact.string;
2 |
3 | type post = {
4 | id: string,
5 | title: string,
6 | };
7 |
8 | type subreddit = {posts: array(post)};
9 |
10 | module SubredditQuery = [%graphql
11 | {|
12 | query GetSubreddit($name: String!) {
13 | subreddit(name: $name) {
14 | posts {
15 | id
16 | title
17 | }
18 | }
19 | }
20 | |}
21 | ];
22 |
23 | [@react.component]
24 | let make = (~name) => {
25 | let query = SubredditQuery.make(~name, ());
26 | let result = ApolloHooks.useQuery(~query);
27 |
28 | switch (result) {
29 | | ApolloHooks.Loading => {ste("Loading")}
30 | | ApolloHooks.Error(message) => {ste(message)}
31 | | ApolloHooks.Data(response) =>
32 | switch (response##subreddit) {
33 | | Some(subreddit) =>
34 |
35 | {subreddit##posts
36 | |> Array.map(post => - {post##title |> ste}
)
37 | |> ReasonReact.array}
38 |
39 | | _ => {"No stories found" |> ste}
40 | }
41 | };
42 | };
43 |
44 | [@genType "Subreddit"]
45 | let default = make;
46 |
--------------------------------------------------------------------------------
/src/components/Header.bs.js:
--------------------------------------------------------------------------------
1 | // Generated by BUCKLESCRIPT, PLEASE EDIT WITH CARE
2 |
3 | import * as Cn from "re-classnames/src/Cn.bs.js";
4 | import * as React from "react";
5 | import * as ActiveLink from "./ActiveLink.bs.js";
6 | import * as HeaderCss from "./Header.css";
7 |
8 | var css = HeaderCss;
9 |
10 | function Header(Props) {
11 | var className = Props.className;
12 | var className$1 = Cn.make(/* :: */[
13 | css.header,
14 | /* :: */[
15 | Cn.unpack(className),
16 | /* [] */0
17 | ]
18 | ]);
19 | return React.createElement("header", {
20 | className: className$1
21 | }, React.createElement("h1", undefined, "Reddit"), React.createElement("nav", undefined, React.createElement(ActiveLink.make, {
22 | href: "/",
23 | activeClassName: css.active,
24 | children: "Home"
25 | })));
26 | }
27 |
28 | var make = Header;
29 |
30 | var $$default = Header;
31 |
32 | export {
33 | css ,
34 | make ,
35 | $$default ,
36 | $$default as default,
37 |
38 | }
39 | /* css Not a pure module */
40 |
--------------------------------------------------------------------------------
/src/components/ActiveLink.gen.tsx:
--------------------------------------------------------------------------------
1 | /* TypeScript file generated by genType. */
2 | /* eslint-disable import/first */
3 |
4 |
5 | import * as React from 'react';
6 |
7 | // tslint:disable-next-line:no-var-requires
8 | const ActiveLinkBS = require('./ActiveLink.bs');
9 |
10 | import {Router_t as Next_Router_t} from '../../src/shims/Next.shim';
11 |
12 | // tslint:disable-next-line:interface-over-type-literal
13 | export type Props = {
14 | readonly activeClassName: string;
15 | readonly children: React.ReactNode;
16 | readonly href: string;
17 | readonly router?: (null | undefined | Next_Router_t)
18 | };
19 |
20 | export const $$default: React.ComponentType<{
21 | readonly activeClassName: string;
22 | readonly children: React.ReactNode;
23 | readonly href: string;
24 | readonly router?: (null | undefined | Next_Router_t)
25 | }> = function ActiveLink(Arg1: any) {
26 | const $props = {activeClassName:Arg1.activeClassName, children:Arg1.children, href:Arg1.href, router:(Arg1.router == null ? undefined : (Arg1.router == null ? undefined : Arg1.router))};
27 | const result = React.createElement(ActiveLinkBS.default, $props);
28 | return result
29 | };
30 |
31 | export default $$default;
32 |
--------------------------------------------------------------------------------
/src/pages/api/graphql.ts:
--------------------------------------------------------------------------------
1 | import { ApolloServer, gql } from 'apollo-server-micro';
2 | import fetch from 'isomorphic-fetch';
3 |
4 | export type PostData = {
5 | data: Post;
6 | };
7 |
8 | export type Post = {
9 | id: string;
10 | title: string;
11 | author: string;
12 | ups: number;
13 | };
14 |
15 | export type Subreddit = {
16 | children: PostData[];
17 | };
18 |
19 | export type SubredditData = {
20 | data: Subreddit;
21 | };
22 |
23 | const typeDefs = gql`
24 | type Query {
25 | subreddit(name: String!): Subreddit
26 | }
27 |
28 | type Subreddit {
29 | posts: [Post!]!
30 | }
31 |
32 | type Post {
33 | id: String!
34 | title: String!
35 | author: String!
36 | """
37 | The upvotes a post has received
38 | """
39 | ups: Int!
40 | }
41 | `;
42 |
43 | const resolvers = {
44 | Query: {
45 | subreddit: async (_: any, { name }) => {
46 | const response: SubredditData = await fetch(
47 | `https://www.reddit.com/r/${name}.json`,
48 | ).then(r => r.json());
49 | return response && response.data;
50 | },
51 | },
52 | Subreddit: {
53 | posts: (subreddit: Subreddit) =>
54 | subreddit ? subreddit.children.map(child => child.data) : [],
55 | },
56 | };
57 |
58 | const server = new ApolloServer({
59 | typeDefs,
60 | resolvers,
61 | introspection: true,
62 | playground: true,
63 | });
64 |
65 | export const config = {
66 | api: {
67 | bodyParser: false,
68 | },
69 | };
70 |
71 | export default server.createHandler({ path: '/api/graphql' });
72 |
--------------------------------------------------------------------------------
/src/components/ActiveLink.bs.js:
--------------------------------------------------------------------------------
1 | // Generated by BUCKLESCRIPT, PLEASE EDIT WITH CARE
2 |
3 | import * as Cn from "re-classnames/src/Cn.bs.js";
4 | import * as React from "react";
5 | import * as Belt_Option from "bs-platform/lib/es6/belt_Option.js";
6 | import * as Caml_option from "bs-platform/lib/es6/caml_option.js";
7 | import * as Router from "next/router";
8 |
9 | function ActiveLink(Props) {
10 | var href = Props.href;
11 | var activeClassName = Props.activeClassName;
12 | var match = Props.router;
13 | var router = match !== undefined ? Caml_option.valFromOption(match) : Caml_option.nullable_to_opt(Router.useRouter());
14 | var children = Props.children;
15 | var handleClick = function ($$event) {
16 | $$event.preventDefault();
17 | Belt_Option.map(router, (function (router) {
18 | return router.push(href);
19 | }));
20 | return /* () */0;
21 | };
22 | var className = Cn.make(/* :: */[
23 | Cn.ifTrue(activeClassName, Belt_Option.getWithDefault(Belt_Option.map(router, (function (router) {
24 | return router.pathname;
25 | })), "/") === href),
26 | /* [] */0
27 | ]);
28 | return React.createElement("a", {
29 | className: className,
30 | href: href,
31 | onClick: handleClick
32 | }, children);
33 | }
34 |
35 | var make = ActiveLink;
36 |
37 | var $$default = ActiveLink;
38 |
39 | export {
40 | make ,
41 | $$default ,
42 | $$default as default,
43 |
44 | }
45 | /* react Not a pure module */
46 |
--------------------------------------------------------------------------------
/packages/e2e/cypress/plugins/index.js:
--------------------------------------------------------------------------------
1 | const ora = require('ora');
2 | const Promise = require('bluebird');
3 | const { GraphQLClient } = require('graphql-request');
4 |
5 | const queries = {
6 | subredditQuery: `
7 | query GetSubreddit($name: String!) {
8 | subreddit(name: $name) {
9 | posts {
10 | id
11 | title
12 | }
13 | }
14 | }
15 | `,
16 | };
17 |
18 | const makeGraphRequest = (apiUrl, operation, variables, user) => {
19 | const opts = {};
20 |
21 | if (user && user.JWT) {
22 | opts.headers = {
23 | Authorization: `Bearer ${user.JWT}`,
24 | };
25 | }
26 |
27 | const client = new GraphQLClient(apiUrl, opts);
28 | return client.request(operation, variables);
29 | };
30 |
31 | const apiUrl = 'http://localhost:3000/api/graphql';
32 |
33 | const getSubredditAsync = name => {
34 | return new Promise((resolve, reject) => {
35 | return makeGraphRequest(apiUrl, queries.subredditQuery, {
36 | name,
37 | })
38 | .then(({ subreddit }) => {
39 | if (subreddit) {
40 | resolve(subreddit);
41 | } else {
42 | reject(new Error('Could not load subreddit'));
43 | }
44 | })
45 | .catch(error => {
46 | reject(error);
47 | });
48 | });
49 | };
50 |
51 | module.exports = (on, config) => {
52 | on('task', {
53 | getSubreddit(name) {
54 | const spinner = ora('Looking for subreddit').start();
55 | return getSubredditAsync(name)
56 | .tap(() => {
57 | spinner.succeed('Found subreddit');
58 | })
59 | .tapCatch(err => {
60 | spinner.fail(err.message);
61 | });
62 | },
63 | });
64 | };
65 |
--------------------------------------------------------------------------------
/src/components/__tests__/ActiveLink.test.tsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { render, fireEvent } from '../../utils/testUtils';
3 | import ActiveLink from '../ActiveLink.gen';
4 |
5 | describe('ActiveLink', () => {
6 | it('renders an active link', () => {
7 | const linkText = 'some text here';
8 | const activeClassName = 'test-active';
9 |
10 | const { getByText, container } = render(
11 |
12 | {linkText}
13 | ,
14 | );
15 |
16 | expect(getByText(linkText)).toBeTruthy();
17 | expect(container.firstChild).toHaveClass(activeClassName);
18 | });
19 |
20 | it('renders an inactive link', () => {
21 | const linkText = 'some text here';
22 | const activeClassName = 'test-active';
23 |
24 | const { getByText, container } = render(
25 |
26 | {linkText}
27 | ,
28 | );
29 |
30 | expect(getByText(linkText)).toBeTruthy();
31 | expect(container.firstChild).not.toHaveClass(activeClassName);
32 | });
33 |
34 | it('can click on a link', () => {
35 | const linkText = 'some text here';
36 | const href = '/';
37 | const router: any = { pathname: href };
38 | router.push = jest.fn();
39 |
40 | const { getByText } = render(
41 |
42 | {linkText}
43 | ,
44 | );
45 |
46 | const link = getByText(linkText);
47 | expect(link).toBeTruthy();
48 |
49 | fireEvent.click(link);
50 | expect(router.push).toHaveBeenCalledWith(href);
51 | });
52 | });
53 |
--------------------------------------------------------------------------------
/workflows/action-cypress/Dockerfile:
--------------------------------------------------------------------------------
1 | FROM node:10
2 |
3 | LABEL repository="https://github.com/sync/reason-graphql-demo"
4 | LABEL homepage="http://github.com/sync"
5 | LABEL maintainer="sync@github.com>"
6 |
7 | LABEL com.github.actions.name="GitHub Action for cypress"
8 | LABEL com.github.actions.description="Wraps the yarn CLI to enable common yarn commands with extra stuff added for cypress."
9 | LABEL com.github.actions.icon="package"
10 | LABEL com.github.actions.color="brown"
11 |
12 | # "fake" dbus address to prevent errors
13 | # https://github.com/SeleniumHQ/docker-selenium/issues/87
14 | ENV DBUS_SESSION_BUS_ADDRESS=/dev/null
15 |
16 | # For Cypress
17 | ENV CI=1
18 |
19 | # https://github.com/GoogleChrome/puppeteer/blob/9de34499ef06386451c01b2662369c224502ebe7/docs/troubleshooting.md#running-puppeteer-in-docker
20 | RUN apt-get update && apt-get install -y wget --no-install-recommends \
21 | && wget -q -O - https://dl-ssl.google.com/linux/linux_signing_key.pub | apt-key add - \
22 | && sh -c 'echo "deb [arch=amd64] http://dl.google.com/linux/chrome/deb/ stable main" >> /etc/apt/sources.list.d/google.list' \
23 | && apt-get update \
24 | && apt-get -y install procps git less openssh-client python-dev python-pip \
25 | && apt-get -y install libgtk2.0-0 libnotify-dev libgconf-2-4 libnss3 libxss1 libasound2 xvfb \
26 | && apt-get -y install curl groff jq zip libpng-dev \
27 | && apt-get install -y dbus-x11 google-chrome-unstable \
28 | --no-install-recommends
29 |
30 | RUN npm install -g yarn
31 | RUN npm install -g --unsafe-perm now
32 |
33 | # It's a good idea to use dumb-init to help prevent zombie chrome processes.
34 | ADD https://github.com/Yelp/dumb-init/releases/download/v1.2.0/dumb-init_1.2.0_amd64 /usr/local/bin/dumb-init
35 | RUN chmod +x /usr/local/bin/dumb-init
36 |
37 | COPY "entrypoint.sh" "/entrypoint.sh"
38 | ENTRYPOINT ["dumb-init", "--", "/entrypoint.sh"]
39 | CMD ["help"]
40 |
--------------------------------------------------------------------------------
/src/__tests__/pages/index.test.tsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { GlobalWithFetchMock } from 'jest-fetch-mock';
3 | import mockConsole from 'jest-mock-console';
4 | import { render, waitForElement } from '../../utils/testUtils';
5 | import { Index } from '../../pages/index.gen';
6 | import {
7 | mockFetchSubredditOnce,
8 | mockFetchErrorResponseOnce,
9 | } from '../../utils/fetchMocks';
10 |
11 | const customGlobal: GlobalWithFetchMock = global as GlobalWithFetchMock;
12 |
13 | describe('Index', () => {
14 | beforeEach(() => {
15 | // eslint-disable-next-line global-require
16 | customGlobal.fetch = require('jest-fetch-mock');
17 | customGlobal.fetchMock = customGlobal.fetch;
18 |
19 | window.scrollTo = jest.fn();
20 | });
21 |
22 | afterEach(() => {
23 | customGlobal.fetch.resetMocks();
24 | });
25 |
26 | it('renders stories given some posts', async () => {
27 | const subreddit = mockFetchSubredditOnce()!;
28 |
29 | const { getByText } = render();
30 |
31 | // first post
32 | await waitForElement(() => getByText(subreddit.posts[0].title));
33 |
34 | // last post
35 | expect(
36 | getByText(subreddit.posts[subreddit.posts.length - 1].title),
37 | ).toBeTruthy();
38 | });
39 |
40 | it('renders no stories given no posts', async () => {
41 | mockFetchSubredditOnce({ subreddit: null });
42 |
43 | const { getByText } = render();
44 |
45 | await waitForElement(() => getByText('No stories found'));
46 | });
47 |
48 | it('renders the provided error', async () => {
49 | const restoreConsole = mockConsole();
50 |
51 | const message = mockFetchErrorResponseOnce();
52 |
53 | const { getByText } = render();
54 |
55 | await waitForElement(() => getByText(`Network error: ${message}`));
56 |
57 | // eslint-disable-next-line no-console
58 | expect(console.log).toHaveBeenCalledWith(
59 | `[Network error]: Error: ${message}`,
60 | );
61 | restoreConsole();
62 | });
63 | });
64 |
--------------------------------------------------------------------------------
/src/shims/ReactEvent.shim.ts:
--------------------------------------------------------------------------------
1 | // tslint:disable-next-line:max-classes-per-file
2 | export abstract class Animation_t {
3 | protected opaque: unknown;
4 | }
5 |
6 | // tslint:disable-next-line:max-classes-per-file
7 | export abstract class Clipboard_t {
8 | protected opaque: unknown;
9 | }
10 |
11 | // tslint:disable-next-line:max-classes-per-file
12 | export abstract class Composition_t {
13 | protected opaque: unknown;
14 | }
15 |
16 | // tslint:disable-next-line:max-classes-per-file
17 | export abstract class Focus_t {
18 | protected opaque: unknown;
19 | }
20 |
21 | // tslint:disable-next-line:max-classes-per-file
22 | export abstract class Form_t {
23 | protected opaque: unknown;
24 | }
25 |
26 | // tslint:disable-next-line:max-classes-per-file
27 | export abstract class Keyboard_t {
28 | protected opaque: unknown;
29 | }
30 |
31 | // tslint:disable-next-line:max-classes-per-file
32 | export abstract class Image_t {
33 | protected opaque: unknown;
34 | }
35 |
36 | // tslint:disable-next-line:max-classes-per-file
37 | export abstract class Media_t {
38 | protected opaque: unknown;
39 | }
40 |
41 | // tslint:disable-next-line:max-classes-per-file
42 | export abstract class Mouse_t {
43 | protected opaque: unknown;
44 | }
45 |
46 | // tslint:disable-next-line:max-classes-per-file
47 | export abstract class Selection_t {
48 | protected opaque: unknown;
49 | }
50 |
51 | // tslint:disable-next-line:max-classes-per-file
52 | export abstract class Synthetic_t {
53 | protected opaque: unknown;
54 | }
55 |
56 | // tslint:disable-next-line:max-classes-per-file
57 | export abstract class Touch_t {
58 | protected opaque: unknown;
59 | }
60 |
61 | // tslint:disable-next-line:max-classes-per-file
62 | export abstract class Transition_t {
63 | protected opaque: unknown;
64 | }
65 |
66 | // tslint:disable-next-line:max-classes-per-file
67 | export abstract class UI_t {
68 | protected opaque: unknown;
69 | }
70 |
71 | // tslint:disable-next-line:max-classes-per-file
72 | export abstract class Wheel_t {
73 | protected opaque: unknown;
74 | }
75 |
--------------------------------------------------------------------------------
/.eslintrc.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | parser: '@typescript-eslint/parser',
3 | parserOptions: {
4 | ecmaVersion: 2018,
5 | sourceType: 'module',
6 | },
7 | extends: [
8 | 'eslint:recommended',
9 | 'plugin:@typescript-eslint/recommended',
10 | 'prettier',
11 | 'prettier/react',
12 | 'prettier/@typescript-eslint',
13 | 'plugin:jest/recommended',
14 | 'plugin:cypress/recommended',
15 | ],
16 | plugins: ['jest', '@typescript-eslint', 'cypress'],
17 | env: {
18 | browser: true,
19 | node: true,
20 | es6: true,
21 | },
22 | settings: {
23 | react: {
24 | version: 'detect',
25 | },
26 | },
27 | rules: {
28 | 'jest/expect-expect': 0,
29 | },
30 | overrides: [
31 | {
32 | files: ['**/*.ts', '**/*.tsx', '**/*.js', '**/*.jsx'],
33 | rules: {
34 | '@typescript-eslint/no-undef': 'off',
35 | '@typescript-eslint/no-unused-vars': 'off',
36 | '@typescript-eslint/spaced-comment': 'off',
37 | '@typescript-eslint/no-restricted-globals': 'off',
38 | '@typescript-eslint/explicit-member-accessibility': 'off',
39 | '@typescript-eslint/explicit-function-return-type': 'off',
40 | '@typescript-eslint/camelcase': 'off',
41 | '@typescript-eslint/no-var-requires': 'off',
42 | '@typescript-eslint/class-name-casing': 'off',
43 | '@typescript-eslint/no-explicit-any': 'off',
44 | '@typescript-eslint/prefer-interface': 'off',
45 | '@typescript-eslint/no-non-null-assertion': 'off',
46 | },
47 | },
48 | {
49 | files: ['**/__tests__/**', '**/__mocks__/**'],
50 | globals: {
51 | mockData: true,
52 | },
53 | env: {
54 | jest: true,
55 | },
56 | },
57 | ],
58 | globals: {
59 | fetch: true,
60 | __DEV__: true,
61 | window: true,
62 | FormData: true,
63 | XMLHttpRequest: true,
64 | requestAnimationFrame: true,
65 | cancelAnimationFrame: true,
66 | page: true,
67 | browser: true,
68 | 'cypress/globals': true,
69 | },
70 | };
71 |
--------------------------------------------------------------------------------
/next.config.js:
--------------------------------------------------------------------------------
1 | const withTM = require('next-transpile-modules');
2 | const withOffline = require('next-offline');
3 | const withCSS = require('@zeit/next-css');
4 |
5 | const isDev = process.env.NODE_ENV !== 'production';
6 | const isProd = process.env.NODE_ENV === 'production';
7 | const disableServerless = Boolean(process.env.DISABLE_SERVERLESS);
8 |
9 | const baseTarget = disableServerless ? {} : { target: 'serverless' };
10 |
11 | const config = {
12 | env: {
13 | isDev,
14 | isProd,
15 | },
16 | ...baseTarget,
17 | crossOrigin: 'anonymous',
18 | webpack: config => {
19 | const rules = config.module.rules;
20 |
21 | // don't even ask my why
22 | config.node = {
23 | fs: 'empty',
24 | };
25 |
26 | // some react native library need this
27 | rules.push({
28 | test: /\.(gif|jpe?g|png|svg)$/,
29 | use: {
30 | loader: 'url-loader',
31 | options: {
32 | name: '[name].[ext]',
33 | },
34 | },
35 | });
36 | // .mjs before .js (fixing failing now.sh deploy)
37 | config.resolve.extensions = [
38 | '.wasm',
39 | '.mjs',
40 | '.web.js',
41 | '.web.jsx',
42 | '.ts',
43 | '.tsx',
44 | '.js',
45 | '.jsx',
46 | '.json',
47 | '.bs.js',
48 | '.gen.tsx',
49 | ];
50 |
51 | return config;
52 | },
53 | dontAutoRegisterSw: true,
54 | workboxOpts: {
55 | swDest: 'static/service-worker.js',
56 | runtimeCaching: [
57 | {
58 | urlPattern: /^https?.*/,
59 | handler: 'NetworkFirst',
60 | options: {
61 | cacheName: 'https-calls',
62 | networkTimeoutSeconds: 15,
63 | expiration: {
64 | maxEntries: 150,
65 | maxAgeSeconds: 30 * 24 * 60 * 60, // 1 month
66 | },
67 | cacheableResponse: {
68 | statuses: [0, 200],
69 | },
70 | },
71 | },
72 | ],
73 | },
74 | pageExtensions: ['jsx', 'js', 'web.js', 'web.jsx', 'ts', 'tsx', 'bs.js'],
75 | transpileModules: ['bs-platform', 're-classnames', 'bs-next', 'bs-apollo'],
76 | cssModules: true,
77 | };
78 |
79 | module.exports = withOffline(withCSS(withTM(config)));
80 |
--------------------------------------------------------------------------------
/src/helpers/initApollo.tsx:
--------------------------------------------------------------------------------
1 | import { Hermes } from 'apollo-cache-hermes';
2 | import { ApolloClient } from 'apollo-client';
3 | import { ApolloLink } from 'apollo-link';
4 | import { onError } from 'apollo-link-error';
5 | import { createHttpLink } from 'apollo-link-http';
6 | import fetch from 'isomorphic-fetch';
7 |
8 | let apolloClient: ApolloClient<{}> | null = null;
9 |
10 | export function createApolloClient(
11 | baseUrl: string,
12 | initialState: object | null,
13 | ) {
14 | const isBrowser = typeof window !== 'undefined';
15 | const httpLink = createHttpLink({
16 | uri: `${baseUrl}/api/graphql`,
17 | credentials: 'same-origin',
18 | // Use fetch() polyfill on the server
19 | fetch: isBrowser ? undefined : fetch,
20 | });
21 |
22 | const errorLink = onError(({ graphQLErrors, networkError }) => {
23 | if (graphQLErrors) {
24 | graphQLErrors.map(({ message, locations, path }) =>
25 | // eslint-disable-next-line no-console
26 | console.log(
27 | `[GraphQL error]: Message: ${message}, Location: ${locations}, Path: ${path}`,
28 | ),
29 | );
30 | }
31 |
32 | if (networkError) {
33 | // eslint-disable-next-line no-console
34 | console.log(`[Network error]: ${networkError}`);
35 | }
36 | });
37 |
38 | const transports = [errorLink, httpLink];
39 | const allLink: any = ApolloLink.from(transports);
40 |
41 | // Check out https://github.com/zeit/next.js/pull/4611 if you want to use the AWSAppSyncClient
42 | return new ApolloClient({
43 | connectToDevTools: isBrowser,
44 | ssrMode: !isBrowser, // Disables forceFetch on the server (so queries are only run once)
45 | link: allLink,
46 | cache: new Hermes({
47 | resolverRedirects: {
48 | Query: {
49 | node: ({ id }) => id,
50 | },
51 | },
52 | addTypename: true,
53 | freeze: true,
54 | }).restore(initialState || {}),
55 | });
56 | }
57 |
58 | export default function initApolloClient(
59 | baseUrl: string,
60 | initialState: object | null,
61 | ) {
62 | // Make sure to create a new client for every server-side request so that data
63 | // isn't shared between connections (which would be bad)
64 | if (typeof window === 'undefined') {
65 | return createApolloClient(baseUrl, initialState);
66 | }
67 |
68 | // Reuse client on the client-side
69 | if (!apolloClient) {
70 | apolloClient = createApolloClient(baseUrl, initialState);
71 | }
72 |
73 | return apolloClient;
74 | }
75 |
--------------------------------------------------------------------------------
/src/helpers/withApollo.tsx:
--------------------------------------------------------------------------------
1 | import React, { useMemo } from 'react';
2 | import Head from 'next/head';
3 | import { ApolloProvider } from '@apollo/react-hooks';
4 | import initApolloClient from './initApollo';
5 |
6 | function getBaseUrl(req: any) {
7 | const protocol = req.headers['x-forwarded-proto'] || 'http';
8 | const host = req.headers['x-forwarded-host'] || req.headers.host;
9 | return `${protocol}://${host}`;
10 | }
11 |
12 | export default function withApollo(PageComponent: any, { ssr = true } = {}) {
13 | const WithApollo = ({ apolloClient, apolloState, ...pageProps }) => {
14 | const client = useMemo(
15 | () => apolloClient || initApolloClient('', apolloState),
16 | [],
17 | );
18 |
19 | return (
20 |
21 |
22 |
23 | );
24 | };
25 |
26 | // Set the correct displayName in development
27 | if (process.env.NODE_ENV !== 'production') {
28 | const displayName =
29 | PageComponent.displayName || PageComponent.name || 'Component';
30 |
31 | if (displayName === 'App') {
32 | console.warn('This withApollo HOC only works with PageComponents.');
33 | }
34 |
35 | WithApollo.displayName = `withApollo(${displayName})`;
36 | }
37 |
38 | // Allow Next.js to remove getInitialProps from the browser build
39 | if (typeof window === 'undefined') {
40 | if (ssr) {
41 | WithApollo.getInitialProps = async (ctx: any) => {
42 | const { AppTree, req } = ctx;
43 |
44 | let pageProps = {};
45 | if (PageComponent.getInitialProps) {
46 | pageProps = await PageComponent.getInitialProps(ctx);
47 | }
48 |
49 | const baseUrl = getBaseUrl(req);
50 |
51 | // Run all GraphQL queries in the component tree
52 | // and extract the resulting data
53 | const apolloClient = initApolloClient(baseUrl, {});
54 |
55 | try {
56 | // Run all GraphQL queries
57 | await require('@apollo/react-ssr').getDataFromTree(
58 | ,
64 | );
65 | } catch (error) {
66 | // Prevent Apollo Client GraphQL errors from crashing SSR.
67 | // Handle them in components via the data.error prop:
68 | // https://www.apollographql.com/docs/react/api/react-apollo.html#graphql-query-data-error
69 | console.error('Error while running `getDataFromTree`', error);
70 | }
71 |
72 | // getDataFromTree does not call componentWillUnmount
73 | // head side effect therefore need to be cleared manually
74 | Head.rewind();
75 |
76 | // Extract query data from the Apollo store
77 | const apolloState = apolloClient.cache.extract();
78 |
79 | return {
80 | ...pageProps,
81 | apolloState,
82 | };
83 | };
84 | }
85 | }
86 |
87 | return WithApollo;
88 | }
89 |
--------------------------------------------------------------------------------
/src/pages/_document.tsx:
--------------------------------------------------------------------------------
1 | import Document, { Head, Main, NextScript } from 'next/document';
2 | import React from 'react';
3 |
4 | const serviceWorkerRegistration = `
5 | document.addEventListener('DOMContentLoaded', event => {
6 | if ('serviceWorker' in navigator) {
7 | window.addEventListener('load', () => {
8 | navigator.serviceWorker.register('/service-worker.js', { scope: "/" }).then(registration => {
9 | console.log('SW registered: ', registration)
10 | }).catch(registrationError => {
11 | console.log('SW registration failed: ', registrationError)
12 | })
13 | })
14 | }
15 | })
16 | `;
17 |
18 | export default class MyDocument extends Document {
19 | static getInitialProps({ renderPage }) {
20 | const page = renderPage();
21 |
22 | const styles = [
23 | ,
56 | ];
57 |
58 | return { ...page, styles: React.Children.toArray(styles) };
59 | }
60 |
61 | render() {
62 | return (
63 |
64 |
65 |
66 |
70 |
71 |
72 |
73 |
74 |
75 |
76 | {process.env.isProd && (
77 | <>
78 |
83 |
89 |
95 |
96 | >
97 | )}
98 |
99 |
100 |
101 |
102 |
103 |
104 | {process.env.isProd && (
105 |
109 | )}
110 |
111 |
112 | );
113 | }
114 | }
115 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | ## reason-graphql-demo
2 |
3 | Originated from: https://github.com/peggyrayzis/redux-to-graphql
4 |
5 | [View the application](https://reason-graphql-demo.now.sh/)
6 | [Go to a specific reddit](https://reason-graphql-demo.now.sh/subreddit/vim)
7 |
8 | Ultra high performance progressive web application built with React + Reason (with hooks) and Next.js.
9 |
10 | [](https://github.com/ebidel/lighthouse-badge)
11 | [](https://github.com/ebidel/lighthouse-badge)
12 | [](https://github.com/ebidel/lighthouse-badge)
13 | [](https://github.com/ebidel/lighthouse-badge)
14 | [](https://github.com/ebidel/lighthouse-badge)
15 |
16 | 
17 | [](https://percy.io/Dblechoc/reason-graphql-demo)
18 |
19 | ## Features
20 |
21 | - Progressive web app
22 | - offline
23 | - install prompts on supported platforms
24 | - Server side rendering
25 | - Next.js 9 (canary)
26 | - Webpack 4.x
27 | - Babel 7.x
28 | - Now.sh 2.x
29 | - Reason React (latest release with hooks)
30 | - Yarn (monorepo with workspaces)
31 |
32 | ## Things to know
33 |
34 | - A production build is deployed from a merge to master
35 | - A staging build is deployed from a PR against master
36 |
37 | ## Setting the project up locally
38 |
39 | First of all make sure you are using node `10.15.3` (any node 10.x would also do) and latest yarn, you can always have a look at the `engines` section of the `package.json`. Why node 8.10. We are using Now.sh to make the app available online and underneath it's using AWS lambda and you have to use Node 8.
40 |
41 | ```sh
42 | $ yarn (install)
43 | $ yarn dev
44 | ```
45 |
46 | After doing this, you'll have a server with hot-reloading running at [http://localhost:3000](http://localhost:3000) and a graphql server running at [http://localhost:3000/api/graphql](http://localhost:3000/api/graphql).
47 |
48 | ## When changing the graphql server schema
49 |
50 | ```sh
51 | $ yarn dev
52 | $ yarn update-graphql-schema
53 | ```
54 |
55 | ## Setting up your editor for reasonml
56 |
57 | Go install this plugin from the vscode market: [here](https://marketplace.visualstudio.com/items?itemName=jaredly.reason-vscode). The plugin is called `reason-vscode` from Jared Forsyth. For more editors go see this [guide](https://reasonml.github.io/docs/en/editor-plugins).
58 |
59 | ## Run tests and friends
60 |
61 | We don't want to use snapshots, we use also use [react-testing-library](https://github.com/testing-library/react-testing-library) to avoid having to use enzyme and to enforce best test practices.
62 |
63 | ```sh
64 | $ yarn format
65 | $ yarn typecheck
66 | $ yarn lint
67 | $ yarn test
68 | ```
69 |
70 | or
71 |
72 | ```sh
73 | $ yarn ci
74 | ```
75 |
76 | ## Troubleshooting
77 |
78 | If you have any issue while running this sample app, open an issue or often just running `yarn clean && yarn build:reason` will help resolve issues.
79 |
80 | ## End to end tests
81 |
82 | We use cypress. Please check `e2e` for more details.
83 | If you wan to add a new test use the following command and wait for cypress to open
84 |
85 | ```
86 | yarn e2e-build
87 | yarn start
88 | yarn workspace @dblechoc/e2e cypress open
89 | ```
90 |
91 | ## Storybook
92 |
93 | This is where we list all our components (comes with hot reloading)
94 |
95 | ```sh
96 | $ yarn storybook
97 | ```
98 |
99 | After doing this, you'll have a showcase page running at [http://localhost:6006](http://localhost:6006)
100 |
101 | ## CI
102 |
103 | We are using [Github Actions](https://help.github.com/en/articles/about-github-actions).
104 |
105 | ## Useful Now.sh commands
106 |
107 | ```sh
108 |
109 | # force a deploy
110 | $ yarn now
111 |
112 | # check all running instances
113 | $ yarn now ls
114 |
115 | # check logs for a given instance
116 | $ yarn now logs reason-graphql-demo.now.sh --all
117 | ```
118 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "reason-graphql-demo",
3 | "private": true,
4 | "version": "1.0.0",
5 | "engines": {
6 | "node": "10.x"
7 | },
8 | "workspaces": {
9 | "packages": [
10 | "packages/*"
11 | ]
12 | },
13 | "scripts": {
14 | "clean": "bsb -clean-world",
15 | "dev": "run-p -c dev:*",
16 | "dev:reason": "bsb -make-world -w",
17 | "dev:now": "next dev",
18 | "build": "yarn clean && yarn build:reason && yarn build:next",
19 | "build:reason": "bsb -make-world",
20 | "build:next": "next build",
21 | "start": "DISABLE_SERVERLESS=true NODE_ENV=production next start",
22 | "now-build": "yarn build",
23 | "format": "prettier --write \"**/*.{js,jsx,ts,tsx,json,css,md,html,graphql}\"",
24 | "lint": "run-p -c lint:*",
25 | "lint:css": "stylelint '**/*.css'",
26 | "lint:ts": "eslint '**/*.js{,x}' '**/*.ts{,x}'",
27 | "typecheck": "tsc --noEmit",
28 | "test": "NODE_ENV=test jest",
29 | "test-watch": "NODE_ENV=test jest -o --watch",
30 | "ci": "yarn typecheck && yarn lint && yarn test",
31 | "storybook": "run-p -c storybook:*",
32 | "storybook:reason": "yarn dev:reason",
33 | "storybook:start": "start-storybook -p 6006",
34 | "build-storybook": "yarn build:reason && build-storybook",
35 | "snapshot-ui": "build-storybook && percy-storybook --widths=320,1280",
36 | "e2e-build": "DISABLE_SERVERLESS=true yarn build",
37 | "e2e-run": "yarn --cwd packages/e2e e2e",
38 | "e2e": "yarn e2e-build && start-server-and-test start 3000 e2e-run",
39 | "deploy": "scripts/deploy-ci.sh",
40 | "deploy:production": "now --token $NOW_TOKEN --target production",
41 | "deploy:staging": "now --token $NOW_TOKEN --target staging",
42 | "update-graphql-schema": "yarn send-introspection-query http://localhost:3000/api/graphql"
43 | },
44 | "license": "ISC",
45 | "dependencies": {
46 | "@apollo/react-common": "3.1.3",
47 | "@apollo/react-hooks": "3.1.3",
48 | "@apollo/react-ssr": "3.1.3",
49 | "@dblechoc/bs-apollo": "2.5.0",
50 | "@dblechoc/bs-next": "0.22.0",
51 | "apollo-cache-hermes": "0.8.10",
52 | "apollo-client": "2.6.8",
53 | "apollo-link": "1.2.13",
54 | "apollo-link-error": "1.1.12",
55 | "apollo-link-http": "1.5.16",
56 | "apollo-server-micro": "2.9.15",
57 | "apollo-utilities": "1.3.3",
58 | "clsx": "1.0.4",
59 | "graphql": "14.5.8",
60 | "graphql-tag": "2.10.1",
61 | "isomorphic-fetch": "2.2.1",
62 | "next": "9.1.7",
63 | "prop-types": "15.7.2",
64 | "re-classnames": "4.1.0",
65 | "react": "16.12.0",
66 | "react-dom": "16.12.0",
67 | "reason-react": "0.7.0"
68 | },
69 | "devDependencies": {
70 | "@babel/core": "7.7.7",
71 | "@babel/plugin-proposal-class-properties": "7.7.4",
72 | "@baransu/graphql_ppx_re": "0.4.9",
73 | "@percy-io/percy-storybook": "2.1.0",
74 | "@storybook/addon-actions": "5.2.8",
75 | "@storybook/addon-centered": "5.2.8",
76 | "@storybook/addon-links": "5.2.8",
77 | "@storybook/addon-viewport": "5.2.8",
78 | "@storybook/addons": "5.2.8",
79 | "@storybook/react": "5.2.8",
80 | "@testing-library/jest-dom": "4.2.4",
81 | "@testing-library/react": "9.4.0",
82 | "@types/isomorphic-fetch": "0.0.35",
83 | "@types/jest": "24.0.25",
84 | "@types/react": "16.9.17",
85 | "@typescript-eslint/eslint-plugin": "2.15.0",
86 | "@typescript-eslint/parser": "2.15.0",
87 | "@zeit/next-css": "1.0.1",
88 | "autoprefixer": "9.7.3",
89 | "awesome-typescript-loader": "5.2.1",
90 | "babel-loader": "8.0.6",
91 | "bs-platform": "7.0.1",
92 | "eslint": "6.8.0",
93 | "eslint-config-prettier": "6.9.0",
94 | "eslint-plugin-cypress": "2.8.1",
95 | "eslint-plugin-jest": "23.3.0",
96 | "eslint-plugin-react": "7.17.0",
97 | "gentype": "3.9.1",
98 | "husky": "4.0.1",
99 | "identity-obj-proxy": "3.0.0",
100 | "jest": "24.9.0",
101 | "jest-canvas-mock": "2.2.0",
102 | "jest-fetch-mock": "3.0.1",
103 | "jest-mock-console": "1.0.0",
104 | "lint-staged": "9.5.0",
105 | "next-offline": "4.0.6",
106 | "next-transpile-modules": "2.3.1",
107 | "now": "16.7.2",
108 | "npm-run-all": "4.1.5",
109 | "prettier": "1.19.1",
110 | "start-server-and-test": "1.10.6",
111 | "stylelint": "12.0.1",
112 | "stylelint-config-recommended": "3.0.0",
113 | "stylelint-config-standard": "19.0.0",
114 | "typescript": "3.7.4",
115 | "webpack": "4.41.5"
116 | },
117 | "prettier": {
118 | "singleQuote": true,
119 | "trailingComma": "all",
120 | "bracketSpacing": true
121 | },
122 | "lint-staged": {
123 | "*.{ts,tsx,js,jsx,json,css,md,html}": [
124 | "yarn format",
125 | "git add"
126 | ]
127 | },
128 | "husky": {
129 | "hooks": {
130 | "pre-commit": "lint-staged",
131 | "pre-push": "yarn lint",
132 | "post-commit": "git update-index -g"
133 | }
134 | }
135 | }
136 |
--------------------------------------------------------------------------------
/src/components/Subreddit.bs.js:
--------------------------------------------------------------------------------
1 | // Generated by BUCKLESCRIPT, PLEASE EDIT WITH CARE
2 |
3 | import * as $$Array from "bs-platform/lib/es6/array.js";
4 | import * as React from "react";
5 | import * as Js_exn from "bs-platform/lib/es6/js_exn.js";
6 | import * as Js_dict from "bs-platform/lib/es6/js_dict.js";
7 | import * as Js_json from "bs-platform/lib/es6/js_json.js";
8 | import * as Caml_obj from "bs-platform/lib/es6/caml_obj.js";
9 | import * as Js_option from "bs-platform/lib/es6/js_option.js";
10 | import * as ApolloHooks from "@dblechoc/bs-apollo/src/ApolloHooks.bs.js";
11 | import * as Caml_option from "bs-platform/lib/es6/caml_option.js";
12 |
13 | function ste(prim) {
14 | return prim;
15 | }
16 |
17 | var ppx_printed_query = "query GetSubreddit($name: String!) {\nsubreddit(name: $name) {\nposts {\nid \ntitle \n}\n\n}\n\n}\n";
18 |
19 | function parse(value) {
20 | var value$1 = Js_option.getExn(Js_json.decodeObject(value));
21 | var match = Js_dict.get(value$1, "subreddit");
22 | var tmp;
23 | if (match !== undefined) {
24 | var value$2 = Caml_option.valFromOption(match);
25 | var match$1 = Js_json.decodeNull(value$2);
26 | if (match$1 !== undefined) {
27 | tmp = undefined;
28 | } else {
29 | var value$3 = Js_option.getExn(Js_json.decodeObject(value$2));
30 | var match$2 = Js_dict.get(value$3, "posts");
31 | tmp = {
32 | posts: match$2 !== undefined ? Js_option.getExn(Js_json.decodeArray(Caml_option.valFromOption(match$2))).map((function (value) {
33 | var value$1 = Js_option.getExn(Js_json.decodeObject(value));
34 | var match = Js_dict.get(value$1, "id");
35 | var tmp;
36 | if (match !== undefined) {
37 | var value$2 = Caml_option.valFromOption(match);
38 | var match$1 = Js_json.decodeString(value$2);
39 | tmp = match$1 !== undefined ? match$1 : Js_exn.raiseError("graphql_ppx: Expected string, got " + JSON.stringify(value$2));
40 | } else {
41 | tmp = Js_exn.raiseError("graphql_ppx: Field id on type Post is missing");
42 | }
43 | var match$2 = Js_dict.get(value$1, "title");
44 | var tmp$1;
45 | if (match$2 !== undefined) {
46 | var value$3 = Caml_option.valFromOption(match$2);
47 | var match$3 = Js_json.decodeString(value$3);
48 | tmp$1 = match$3 !== undefined ? match$3 : Js_exn.raiseError("graphql_ppx: Expected string, got " + JSON.stringify(value$3));
49 | } else {
50 | tmp$1 = Js_exn.raiseError("graphql_ppx: Field title on type Post is missing");
51 | }
52 | return {
53 | id: tmp,
54 | title: tmp$1
55 | };
56 | })) : Js_exn.raiseError("graphql_ppx: Field posts on type Subreddit is missing")
57 | };
58 | }
59 | } else {
60 | tmp = undefined;
61 | }
62 | return {
63 | subreddit: tmp
64 | };
65 | }
66 |
67 | function make(name, param) {
68 | return {
69 | query: ppx_printed_query,
70 | variables: Js_dict.fromArray(/* array */[/* tuple */[
71 | "name",
72 | name
73 | ]].filter((function (param) {
74 | return Caml_obj.caml_notequal(param[1], null);
75 | }))),
76 | parse: parse
77 | };
78 | }
79 |
80 | function makeWithVariables(variables) {
81 | var name = variables.name;
82 | return {
83 | query: ppx_printed_query,
84 | variables: Js_dict.fromArray(/* array */[/* tuple */[
85 | "name",
86 | name
87 | ]].filter((function (param) {
88 | return Caml_obj.caml_notequal(param[1], null);
89 | }))),
90 | parse: parse
91 | };
92 | }
93 |
94 | function ret_type(f) {
95 | return { };
96 | }
97 |
98 | var MT_Ret = { };
99 |
100 | var SubredditQuery = {
101 | ppx_printed_query: ppx_printed_query,
102 | query: ppx_printed_query,
103 | parse: parse,
104 | make: make,
105 | makeWithVariables: makeWithVariables,
106 | ret_type: ret_type,
107 | MT_Ret: MT_Ret
108 | };
109 |
110 | function Subreddit(Props) {
111 | var name = Props.name;
112 | var query = make(name, /* () */0);
113 | var result = ApolloHooks.useQuery(query);
114 | if (typeof result === "number") {
115 | return React.createElement("div", undefined, "Loading");
116 | } else if (result.tag) {
117 | var match = result[0].subreddit;
118 | if (match !== undefined) {
119 | return React.createElement("ul", undefined, $$Array.map((function (post) {
120 | return React.createElement("li", {
121 | key: post.id
122 | }, post.title);
123 | }), Caml_option.valFromOption(match).posts));
124 | } else {
125 | return React.createElement("div", undefined, "No stories found");
126 | }
127 | } else {
128 | return React.createElement("div", undefined, result[0]);
129 | }
130 | }
131 |
132 | var make$1 = Subreddit;
133 |
134 | var $$default = Subreddit;
135 |
136 | export {
137 | ste ,
138 | SubredditQuery ,
139 | make$1 as make,
140 | $$default ,
141 | $$default as default,
142 |
143 | }
144 | /* react Not a pure module */
145 |
--------------------------------------------------------------------------------
/graphql_schema.json:
--------------------------------------------------------------------------------
1 | {
2 | "data": {
3 | "__schema": {
4 | "queryType": {
5 | "name": "Query"
6 | },
7 | "mutationType": null,
8 | "subscriptionType": null,
9 | "types": [
10 | {
11 | "kind": "OBJECT",
12 | "name": "Query",
13 | "description": "",
14 | "fields": [
15 | {
16 | "name": "subreddit",
17 | "description": "",
18 | "args": [
19 | {
20 | "name": "name",
21 | "description": "",
22 | "type": {
23 | "kind": "NON_NULL",
24 | "name": null,
25 | "ofType": {
26 | "kind": "SCALAR",
27 | "name": "String",
28 | "ofType": null
29 | }
30 | },
31 | "defaultValue": null
32 | }
33 | ],
34 | "type": {
35 | "kind": "OBJECT",
36 | "name": "Subreddit",
37 | "ofType": null
38 | },
39 | "isDeprecated": false,
40 | "deprecationReason": null
41 | }
42 | ],
43 | "inputFields": null,
44 | "interfaces": [],
45 | "enumValues": null,
46 | "possibleTypes": null
47 | },
48 | {
49 | "kind": "SCALAR",
50 | "name": "String",
51 | "description": "The `String` scalar type represents textual data, represented as UTF-8 character sequences. The String type is most often used by GraphQL to represent free-form human-readable text.",
52 | "fields": null,
53 | "inputFields": null,
54 | "interfaces": null,
55 | "enumValues": null,
56 | "possibleTypes": null
57 | },
58 | {
59 | "kind": "OBJECT",
60 | "name": "Subreddit",
61 | "description": "",
62 | "fields": [
63 | {
64 | "name": "posts",
65 | "description": "",
66 | "args": [],
67 | "type": {
68 | "kind": "NON_NULL",
69 | "name": null,
70 | "ofType": {
71 | "kind": "LIST",
72 | "name": null,
73 | "ofType": {
74 | "kind": "NON_NULL",
75 | "name": null,
76 | "ofType": {
77 | "kind": "OBJECT",
78 | "name": "Post",
79 | "ofType": null
80 | }
81 | }
82 | }
83 | },
84 | "isDeprecated": false,
85 | "deprecationReason": null
86 | }
87 | ],
88 | "inputFields": null,
89 | "interfaces": [],
90 | "enumValues": null,
91 | "possibleTypes": null
92 | },
93 | {
94 | "kind": "OBJECT",
95 | "name": "Post",
96 | "description": "",
97 | "fields": [
98 | {
99 | "name": "id",
100 | "description": "",
101 | "args": [],
102 | "type": {
103 | "kind": "NON_NULL",
104 | "name": null,
105 | "ofType": {
106 | "kind": "SCALAR",
107 | "name": "String",
108 | "ofType": null
109 | }
110 | },
111 | "isDeprecated": false,
112 | "deprecationReason": null
113 | },
114 | {
115 | "name": "title",
116 | "description": "",
117 | "args": [],
118 | "type": {
119 | "kind": "NON_NULL",
120 | "name": null,
121 | "ofType": {
122 | "kind": "SCALAR",
123 | "name": "String",
124 | "ofType": null
125 | }
126 | },
127 | "isDeprecated": false,
128 | "deprecationReason": null
129 | },
130 | {
131 | "name": "author",
132 | "description": "",
133 | "args": [],
134 | "type": {
135 | "kind": "NON_NULL",
136 | "name": null,
137 | "ofType": {
138 | "kind": "SCALAR",
139 | "name": "String",
140 | "ofType": null
141 | }
142 | },
143 | "isDeprecated": false,
144 | "deprecationReason": null
145 | },
146 | {
147 | "name": "ups",
148 | "description": "The upvotes a post has received",
149 | "args": [],
150 | "type": {
151 | "kind": "NON_NULL",
152 | "name": null,
153 | "ofType": {
154 | "kind": "SCALAR",
155 | "name": "Int",
156 | "ofType": null
157 | }
158 | },
159 | "isDeprecated": false,
160 | "deprecationReason": null
161 | }
162 | ],
163 | "inputFields": null,
164 | "interfaces": [],
165 | "enumValues": null,
166 | "possibleTypes": null
167 | },
168 | {
169 | "kind": "SCALAR",
170 | "name": "Int",
171 | "description": "The `Int` scalar type represents non-fractional signed whole numeric values. Int can represent values between -(2^31) and 2^31 - 1. ",
172 | "fields": null,
173 | "inputFields": null,
174 | "interfaces": null,
175 | "enumValues": null,
176 | "possibleTypes": null
177 | },
178 | {
179 | "kind": "OBJECT",
180 | "name": "__Schema",
181 | "description": "A GraphQL Schema defines the capabilities of a GraphQL server. It exposes all available types and directives on the server, as well as the entry points for query, mutation, and subscription operations.",
182 | "fields": [
183 | {
184 | "name": "types",
185 | "description": "A list of all types supported by this server.",
186 | "args": [],
187 | "type": {
188 | "kind": "NON_NULL",
189 | "name": null,
190 | "ofType": {
191 | "kind": "LIST",
192 | "name": null,
193 | "ofType": {
194 | "kind": "NON_NULL",
195 | "name": null,
196 | "ofType": {
197 | "kind": "OBJECT",
198 | "name": "__Type",
199 | "ofType": null
200 | }
201 | }
202 | }
203 | },
204 | "isDeprecated": false,
205 | "deprecationReason": null
206 | },
207 | {
208 | "name": "queryType",
209 | "description": "The type that query operations will be rooted at.",
210 | "args": [],
211 | "type": {
212 | "kind": "NON_NULL",
213 | "name": null,
214 | "ofType": {
215 | "kind": "OBJECT",
216 | "name": "__Type",
217 | "ofType": null
218 | }
219 | },
220 | "isDeprecated": false,
221 | "deprecationReason": null
222 | },
223 | {
224 | "name": "mutationType",
225 | "description": "If this server supports mutation, the type that mutation operations will be rooted at.",
226 | "args": [],
227 | "type": {
228 | "kind": "OBJECT",
229 | "name": "__Type",
230 | "ofType": null
231 | },
232 | "isDeprecated": false,
233 | "deprecationReason": null
234 | },
235 | {
236 | "name": "subscriptionType",
237 | "description": "If this server support subscription, the type that subscription operations will be rooted at.",
238 | "args": [],
239 | "type": {
240 | "kind": "OBJECT",
241 | "name": "__Type",
242 | "ofType": null
243 | },
244 | "isDeprecated": false,
245 | "deprecationReason": null
246 | },
247 | {
248 | "name": "directives",
249 | "description": "A list of all directives supported by this server.",
250 | "args": [],
251 | "type": {
252 | "kind": "NON_NULL",
253 | "name": null,
254 | "ofType": {
255 | "kind": "LIST",
256 | "name": null,
257 | "ofType": {
258 | "kind": "NON_NULL",
259 | "name": null,
260 | "ofType": {
261 | "kind": "OBJECT",
262 | "name": "__Directive",
263 | "ofType": null
264 | }
265 | }
266 | }
267 | },
268 | "isDeprecated": false,
269 | "deprecationReason": null
270 | }
271 | ],
272 | "inputFields": null,
273 | "interfaces": [],
274 | "enumValues": null,
275 | "possibleTypes": null
276 | },
277 | {
278 | "kind": "OBJECT",
279 | "name": "__Type",
280 | "description": "The fundamental unit of any GraphQL Schema is the type. There are many kinds of types in GraphQL as represented by the `__TypeKind` enum.\n\nDepending on the kind of a type, certain fields describe information about that type. Scalar types provide no information beyond a name and description, while Enum types provide their values. Object and Interface types provide the fields they describe. Abstract types, Union and Interface, provide the Object types possible at runtime. List and NonNull types compose other types.",
281 | "fields": [
282 | {
283 | "name": "kind",
284 | "description": null,
285 | "args": [],
286 | "type": {
287 | "kind": "NON_NULL",
288 | "name": null,
289 | "ofType": {
290 | "kind": "ENUM",
291 | "name": "__TypeKind",
292 | "ofType": null
293 | }
294 | },
295 | "isDeprecated": false,
296 | "deprecationReason": null
297 | },
298 | {
299 | "name": "name",
300 | "description": null,
301 | "args": [],
302 | "type": {
303 | "kind": "SCALAR",
304 | "name": "String",
305 | "ofType": null
306 | },
307 | "isDeprecated": false,
308 | "deprecationReason": null
309 | },
310 | {
311 | "name": "description",
312 | "description": null,
313 | "args": [],
314 | "type": {
315 | "kind": "SCALAR",
316 | "name": "String",
317 | "ofType": null
318 | },
319 | "isDeprecated": false,
320 | "deprecationReason": null
321 | },
322 | {
323 | "name": "fields",
324 | "description": null,
325 | "args": [
326 | {
327 | "name": "includeDeprecated",
328 | "description": null,
329 | "type": {
330 | "kind": "SCALAR",
331 | "name": "Boolean",
332 | "ofType": null
333 | },
334 | "defaultValue": "false"
335 | }
336 | ],
337 | "type": {
338 | "kind": "LIST",
339 | "name": null,
340 | "ofType": {
341 | "kind": "NON_NULL",
342 | "name": null,
343 | "ofType": {
344 | "kind": "OBJECT",
345 | "name": "__Field",
346 | "ofType": null
347 | }
348 | }
349 | },
350 | "isDeprecated": false,
351 | "deprecationReason": null
352 | },
353 | {
354 | "name": "interfaces",
355 | "description": null,
356 | "args": [],
357 | "type": {
358 | "kind": "LIST",
359 | "name": null,
360 | "ofType": {
361 | "kind": "NON_NULL",
362 | "name": null,
363 | "ofType": {
364 | "kind": "OBJECT",
365 | "name": "__Type",
366 | "ofType": null
367 | }
368 | }
369 | },
370 | "isDeprecated": false,
371 | "deprecationReason": null
372 | },
373 | {
374 | "name": "possibleTypes",
375 | "description": null,
376 | "args": [],
377 | "type": {
378 | "kind": "LIST",
379 | "name": null,
380 | "ofType": {
381 | "kind": "NON_NULL",
382 | "name": null,
383 | "ofType": {
384 | "kind": "OBJECT",
385 | "name": "__Type",
386 | "ofType": null
387 | }
388 | }
389 | },
390 | "isDeprecated": false,
391 | "deprecationReason": null
392 | },
393 | {
394 | "name": "enumValues",
395 | "description": null,
396 | "args": [
397 | {
398 | "name": "includeDeprecated",
399 | "description": null,
400 | "type": {
401 | "kind": "SCALAR",
402 | "name": "Boolean",
403 | "ofType": null
404 | },
405 | "defaultValue": "false"
406 | }
407 | ],
408 | "type": {
409 | "kind": "LIST",
410 | "name": null,
411 | "ofType": {
412 | "kind": "NON_NULL",
413 | "name": null,
414 | "ofType": {
415 | "kind": "OBJECT",
416 | "name": "__EnumValue",
417 | "ofType": null
418 | }
419 | }
420 | },
421 | "isDeprecated": false,
422 | "deprecationReason": null
423 | },
424 | {
425 | "name": "inputFields",
426 | "description": null,
427 | "args": [],
428 | "type": {
429 | "kind": "LIST",
430 | "name": null,
431 | "ofType": {
432 | "kind": "NON_NULL",
433 | "name": null,
434 | "ofType": {
435 | "kind": "OBJECT",
436 | "name": "__InputValue",
437 | "ofType": null
438 | }
439 | }
440 | },
441 | "isDeprecated": false,
442 | "deprecationReason": null
443 | },
444 | {
445 | "name": "ofType",
446 | "description": null,
447 | "args": [],
448 | "type": {
449 | "kind": "OBJECT",
450 | "name": "__Type",
451 | "ofType": null
452 | },
453 | "isDeprecated": false,
454 | "deprecationReason": null
455 | }
456 | ],
457 | "inputFields": null,
458 | "interfaces": [],
459 | "enumValues": null,
460 | "possibleTypes": null
461 | },
462 | {
463 | "kind": "ENUM",
464 | "name": "__TypeKind",
465 | "description": "An enum describing what kind of type a given `__Type` is.",
466 | "fields": null,
467 | "inputFields": null,
468 | "interfaces": null,
469 | "enumValues": [
470 | {
471 | "name": "SCALAR",
472 | "description": "Indicates this type is a scalar.",
473 | "isDeprecated": false,
474 | "deprecationReason": null
475 | },
476 | {
477 | "name": "OBJECT",
478 | "description": "Indicates this type is an object. `fields` and `interfaces` are valid fields.",
479 | "isDeprecated": false,
480 | "deprecationReason": null
481 | },
482 | {
483 | "name": "INTERFACE",
484 | "description": "Indicates this type is an interface. `fields` and `possibleTypes` are valid fields.",
485 | "isDeprecated": false,
486 | "deprecationReason": null
487 | },
488 | {
489 | "name": "UNION",
490 | "description": "Indicates this type is a union. `possibleTypes` is a valid field.",
491 | "isDeprecated": false,
492 | "deprecationReason": null
493 | },
494 | {
495 | "name": "ENUM",
496 | "description": "Indicates this type is an enum. `enumValues` is a valid field.",
497 | "isDeprecated": false,
498 | "deprecationReason": null
499 | },
500 | {
501 | "name": "INPUT_OBJECT",
502 | "description": "Indicates this type is an input object. `inputFields` is a valid field.",
503 | "isDeprecated": false,
504 | "deprecationReason": null
505 | },
506 | {
507 | "name": "LIST",
508 | "description": "Indicates this type is a list. `ofType` is a valid field.",
509 | "isDeprecated": false,
510 | "deprecationReason": null
511 | },
512 | {
513 | "name": "NON_NULL",
514 | "description": "Indicates this type is a non-null. `ofType` is a valid field.",
515 | "isDeprecated": false,
516 | "deprecationReason": null
517 | }
518 | ],
519 | "possibleTypes": null
520 | },
521 | {
522 | "kind": "SCALAR",
523 | "name": "Boolean",
524 | "description": "The `Boolean` scalar type represents `true` or `false`.",
525 | "fields": null,
526 | "inputFields": null,
527 | "interfaces": null,
528 | "enumValues": null,
529 | "possibleTypes": null
530 | },
531 | {
532 | "kind": "OBJECT",
533 | "name": "__Field",
534 | "description": "Object and Interface types are described by a list of Fields, each of which has a name, potentially a list of arguments, and a return type.",
535 | "fields": [
536 | {
537 | "name": "name",
538 | "description": null,
539 | "args": [],
540 | "type": {
541 | "kind": "NON_NULL",
542 | "name": null,
543 | "ofType": {
544 | "kind": "SCALAR",
545 | "name": "String",
546 | "ofType": null
547 | }
548 | },
549 | "isDeprecated": false,
550 | "deprecationReason": null
551 | },
552 | {
553 | "name": "description",
554 | "description": null,
555 | "args": [],
556 | "type": {
557 | "kind": "SCALAR",
558 | "name": "String",
559 | "ofType": null
560 | },
561 | "isDeprecated": false,
562 | "deprecationReason": null
563 | },
564 | {
565 | "name": "args",
566 | "description": null,
567 | "args": [],
568 | "type": {
569 | "kind": "NON_NULL",
570 | "name": null,
571 | "ofType": {
572 | "kind": "LIST",
573 | "name": null,
574 | "ofType": {
575 | "kind": "NON_NULL",
576 | "name": null,
577 | "ofType": {
578 | "kind": "OBJECT",
579 | "name": "__InputValue",
580 | "ofType": null
581 | }
582 | }
583 | }
584 | },
585 | "isDeprecated": false,
586 | "deprecationReason": null
587 | },
588 | {
589 | "name": "type",
590 | "description": null,
591 | "args": [],
592 | "type": {
593 | "kind": "NON_NULL",
594 | "name": null,
595 | "ofType": {
596 | "kind": "OBJECT",
597 | "name": "__Type",
598 | "ofType": null
599 | }
600 | },
601 | "isDeprecated": false,
602 | "deprecationReason": null
603 | },
604 | {
605 | "name": "isDeprecated",
606 | "description": null,
607 | "args": [],
608 | "type": {
609 | "kind": "NON_NULL",
610 | "name": null,
611 | "ofType": {
612 | "kind": "SCALAR",
613 | "name": "Boolean",
614 | "ofType": null
615 | }
616 | },
617 | "isDeprecated": false,
618 | "deprecationReason": null
619 | },
620 | {
621 | "name": "deprecationReason",
622 | "description": null,
623 | "args": [],
624 | "type": {
625 | "kind": "SCALAR",
626 | "name": "String",
627 | "ofType": null
628 | },
629 | "isDeprecated": false,
630 | "deprecationReason": null
631 | }
632 | ],
633 | "inputFields": null,
634 | "interfaces": [],
635 | "enumValues": null,
636 | "possibleTypes": null
637 | },
638 | {
639 | "kind": "OBJECT",
640 | "name": "__InputValue",
641 | "description": "Arguments provided to Fields or Directives and the input fields of an InputObject are represented as Input Values which describe their type and optionally a default value.",
642 | "fields": [
643 | {
644 | "name": "name",
645 | "description": null,
646 | "args": [],
647 | "type": {
648 | "kind": "NON_NULL",
649 | "name": null,
650 | "ofType": {
651 | "kind": "SCALAR",
652 | "name": "String",
653 | "ofType": null
654 | }
655 | },
656 | "isDeprecated": false,
657 | "deprecationReason": null
658 | },
659 | {
660 | "name": "description",
661 | "description": null,
662 | "args": [],
663 | "type": {
664 | "kind": "SCALAR",
665 | "name": "String",
666 | "ofType": null
667 | },
668 | "isDeprecated": false,
669 | "deprecationReason": null
670 | },
671 | {
672 | "name": "type",
673 | "description": null,
674 | "args": [],
675 | "type": {
676 | "kind": "NON_NULL",
677 | "name": null,
678 | "ofType": {
679 | "kind": "OBJECT",
680 | "name": "__Type",
681 | "ofType": null
682 | }
683 | },
684 | "isDeprecated": false,
685 | "deprecationReason": null
686 | },
687 | {
688 | "name": "defaultValue",
689 | "description": "A GraphQL-formatted string representing the default value for this input value.",
690 | "args": [],
691 | "type": {
692 | "kind": "SCALAR",
693 | "name": "String",
694 | "ofType": null
695 | },
696 | "isDeprecated": false,
697 | "deprecationReason": null
698 | }
699 | ],
700 | "inputFields": null,
701 | "interfaces": [],
702 | "enumValues": null,
703 | "possibleTypes": null
704 | },
705 | {
706 | "kind": "OBJECT",
707 | "name": "__EnumValue",
708 | "description": "One possible value for a given Enum. Enum values are unique values, not a placeholder for a string or numeric value. However an Enum value is returned in a JSON response as a string.",
709 | "fields": [
710 | {
711 | "name": "name",
712 | "description": null,
713 | "args": [],
714 | "type": {
715 | "kind": "NON_NULL",
716 | "name": null,
717 | "ofType": {
718 | "kind": "SCALAR",
719 | "name": "String",
720 | "ofType": null
721 | }
722 | },
723 | "isDeprecated": false,
724 | "deprecationReason": null
725 | },
726 | {
727 | "name": "description",
728 | "description": null,
729 | "args": [],
730 | "type": {
731 | "kind": "SCALAR",
732 | "name": "String",
733 | "ofType": null
734 | },
735 | "isDeprecated": false,
736 | "deprecationReason": null
737 | },
738 | {
739 | "name": "isDeprecated",
740 | "description": null,
741 | "args": [],
742 | "type": {
743 | "kind": "NON_NULL",
744 | "name": null,
745 | "ofType": {
746 | "kind": "SCALAR",
747 | "name": "Boolean",
748 | "ofType": null
749 | }
750 | },
751 | "isDeprecated": false,
752 | "deprecationReason": null
753 | },
754 | {
755 | "name": "deprecationReason",
756 | "description": null,
757 | "args": [],
758 | "type": {
759 | "kind": "SCALAR",
760 | "name": "String",
761 | "ofType": null
762 | },
763 | "isDeprecated": false,
764 | "deprecationReason": null
765 | }
766 | ],
767 | "inputFields": null,
768 | "interfaces": [],
769 | "enumValues": null,
770 | "possibleTypes": null
771 | },
772 | {
773 | "kind": "OBJECT",
774 | "name": "__Directive",
775 | "description": "A Directive provides a way to describe alternate runtime execution and type validation behavior in a GraphQL document.\n\nIn some cases, you need to provide options to alter GraphQL's execution behavior in ways field arguments will not suffice, such as conditionally including or skipping a field. Directives provide this by describing additional information to the executor.",
776 | "fields": [
777 | {
778 | "name": "name",
779 | "description": null,
780 | "args": [],
781 | "type": {
782 | "kind": "NON_NULL",
783 | "name": null,
784 | "ofType": {
785 | "kind": "SCALAR",
786 | "name": "String",
787 | "ofType": null
788 | }
789 | },
790 | "isDeprecated": false,
791 | "deprecationReason": null
792 | },
793 | {
794 | "name": "description",
795 | "description": null,
796 | "args": [],
797 | "type": {
798 | "kind": "SCALAR",
799 | "name": "String",
800 | "ofType": null
801 | },
802 | "isDeprecated": false,
803 | "deprecationReason": null
804 | },
805 | {
806 | "name": "locations",
807 | "description": null,
808 | "args": [],
809 | "type": {
810 | "kind": "NON_NULL",
811 | "name": null,
812 | "ofType": {
813 | "kind": "LIST",
814 | "name": null,
815 | "ofType": {
816 | "kind": "NON_NULL",
817 | "name": null,
818 | "ofType": {
819 | "kind": "ENUM",
820 | "name": "__DirectiveLocation",
821 | "ofType": null
822 | }
823 | }
824 | }
825 | },
826 | "isDeprecated": false,
827 | "deprecationReason": null
828 | },
829 | {
830 | "name": "args",
831 | "description": null,
832 | "args": [],
833 | "type": {
834 | "kind": "NON_NULL",
835 | "name": null,
836 | "ofType": {
837 | "kind": "LIST",
838 | "name": null,
839 | "ofType": {
840 | "kind": "NON_NULL",
841 | "name": null,
842 | "ofType": {
843 | "kind": "OBJECT",
844 | "name": "__InputValue",
845 | "ofType": null
846 | }
847 | }
848 | }
849 | },
850 | "isDeprecated": false,
851 | "deprecationReason": null
852 | }
853 | ],
854 | "inputFields": null,
855 | "interfaces": [],
856 | "enumValues": null,
857 | "possibleTypes": null
858 | },
859 | {
860 | "kind": "ENUM",
861 | "name": "__DirectiveLocation",
862 | "description": "A Directive can be adjacent to many parts of the GraphQL language, a __DirectiveLocation describes one such possible adjacencies.",
863 | "fields": null,
864 | "inputFields": null,
865 | "interfaces": null,
866 | "enumValues": [
867 | {
868 | "name": "QUERY",
869 | "description": "Location adjacent to a query operation.",
870 | "isDeprecated": false,
871 | "deprecationReason": null
872 | },
873 | {
874 | "name": "MUTATION",
875 | "description": "Location adjacent to a mutation operation.",
876 | "isDeprecated": false,
877 | "deprecationReason": null
878 | },
879 | {
880 | "name": "SUBSCRIPTION",
881 | "description": "Location adjacent to a subscription operation.",
882 | "isDeprecated": false,
883 | "deprecationReason": null
884 | },
885 | {
886 | "name": "FIELD",
887 | "description": "Location adjacent to a field.",
888 | "isDeprecated": false,
889 | "deprecationReason": null
890 | },
891 | {
892 | "name": "FRAGMENT_DEFINITION",
893 | "description": "Location adjacent to a fragment definition.",
894 | "isDeprecated": false,
895 | "deprecationReason": null
896 | },
897 | {
898 | "name": "FRAGMENT_SPREAD",
899 | "description": "Location adjacent to a fragment spread.",
900 | "isDeprecated": false,
901 | "deprecationReason": null
902 | },
903 | {
904 | "name": "INLINE_FRAGMENT",
905 | "description": "Location adjacent to an inline fragment.",
906 | "isDeprecated": false,
907 | "deprecationReason": null
908 | },
909 | {
910 | "name": "VARIABLE_DEFINITION",
911 | "description": "Location adjacent to a variable definition.",
912 | "isDeprecated": false,
913 | "deprecationReason": null
914 | },
915 | {
916 | "name": "SCHEMA",
917 | "description": "Location adjacent to a schema definition.",
918 | "isDeprecated": false,
919 | "deprecationReason": null
920 | },
921 | {
922 | "name": "SCALAR",
923 | "description": "Location adjacent to a scalar definition.",
924 | "isDeprecated": false,
925 | "deprecationReason": null
926 | },
927 | {
928 | "name": "OBJECT",
929 | "description": "Location adjacent to an object type definition.",
930 | "isDeprecated": false,
931 | "deprecationReason": null
932 | },
933 | {
934 | "name": "FIELD_DEFINITION",
935 | "description": "Location adjacent to a field definition.",
936 | "isDeprecated": false,
937 | "deprecationReason": null
938 | },
939 | {
940 | "name": "ARGUMENT_DEFINITION",
941 | "description": "Location adjacent to an argument definition.",
942 | "isDeprecated": false,
943 | "deprecationReason": null
944 | },
945 | {
946 | "name": "INTERFACE",
947 | "description": "Location adjacent to an interface definition.",
948 | "isDeprecated": false,
949 | "deprecationReason": null
950 | },
951 | {
952 | "name": "UNION",
953 | "description": "Location adjacent to a union definition.",
954 | "isDeprecated": false,
955 | "deprecationReason": null
956 | },
957 | {
958 | "name": "ENUM",
959 | "description": "Location adjacent to an enum definition.",
960 | "isDeprecated": false,
961 | "deprecationReason": null
962 | },
963 | {
964 | "name": "ENUM_VALUE",
965 | "description": "Location adjacent to an enum value definition.",
966 | "isDeprecated": false,
967 | "deprecationReason": null
968 | },
969 | {
970 | "name": "INPUT_OBJECT",
971 | "description": "Location adjacent to an input object type definition.",
972 | "isDeprecated": false,
973 | "deprecationReason": null
974 | },
975 | {
976 | "name": "INPUT_FIELD_DEFINITION",
977 | "description": "Location adjacent to an input object field definition.",
978 | "isDeprecated": false,
979 | "deprecationReason": null
980 | }
981 | ],
982 | "possibleTypes": null
983 | },
984 | {
985 | "kind": "ENUM",
986 | "name": "CacheControlScope",
987 | "description": "",
988 | "fields": null,
989 | "inputFields": null,
990 | "interfaces": null,
991 | "enumValues": [
992 | {
993 | "name": "PUBLIC",
994 | "description": "",
995 | "isDeprecated": false,
996 | "deprecationReason": null
997 | },
998 | {
999 | "name": "PRIVATE",
1000 | "description": "",
1001 | "isDeprecated": false,
1002 | "deprecationReason": null
1003 | }
1004 | ],
1005 | "possibleTypes": null
1006 | },
1007 | {
1008 | "kind": "SCALAR",
1009 | "name": "Upload",
1010 | "description": "The `Upload` scalar type represents a file upload.",
1011 | "fields": null,
1012 | "inputFields": null,
1013 | "interfaces": null,
1014 | "enumValues": null,
1015 | "possibleTypes": null
1016 | }
1017 | ],
1018 | "directives": [
1019 | {
1020 | "name": "cacheControl",
1021 | "description": "",
1022 | "locations": ["FIELD_DEFINITION", "OBJECT", "INTERFACE"],
1023 | "args": [
1024 | {
1025 | "name": "maxAge",
1026 | "description": "",
1027 | "type": {
1028 | "kind": "SCALAR",
1029 | "name": "Int",
1030 | "ofType": null
1031 | },
1032 | "defaultValue": null
1033 | },
1034 | {
1035 | "name": "scope",
1036 | "description": "",
1037 | "type": {
1038 | "kind": "ENUM",
1039 | "name": "CacheControlScope",
1040 | "ofType": null
1041 | },
1042 | "defaultValue": null
1043 | }
1044 | ]
1045 | },
1046 | {
1047 | "name": "skip",
1048 | "description": "Directs the executor to skip this field or fragment when the `if` argument is true.",
1049 | "locations": ["FIELD", "FRAGMENT_SPREAD", "INLINE_FRAGMENT"],
1050 | "args": [
1051 | {
1052 | "name": "if",
1053 | "description": "Skipped when true.",
1054 | "type": {
1055 | "kind": "NON_NULL",
1056 | "name": null,
1057 | "ofType": {
1058 | "kind": "SCALAR",
1059 | "name": "Boolean",
1060 | "ofType": null
1061 | }
1062 | },
1063 | "defaultValue": null
1064 | }
1065 | ]
1066 | },
1067 | {
1068 | "name": "include",
1069 | "description": "Directs the executor to include this field or fragment only when the `if` argument is true.",
1070 | "locations": ["FIELD", "FRAGMENT_SPREAD", "INLINE_FRAGMENT"],
1071 | "args": [
1072 | {
1073 | "name": "if",
1074 | "description": "Included when true.",
1075 | "type": {
1076 | "kind": "NON_NULL",
1077 | "name": null,
1078 | "ofType": {
1079 | "kind": "SCALAR",
1080 | "name": "Boolean",
1081 | "ofType": null
1082 | }
1083 | },
1084 | "defaultValue": null
1085 | }
1086 | ]
1087 | },
1088 | {
1089 | "name": "deprecated",
1090 | "description": "Marks an element of a GraphQL schema as no longer supported.",
1091 | "locations": ["FIELD_DEFINITION", "ENUM_VALUE"],
1092 | "args": [
1093 | {
1094 | "name": "reason",
1095 | "description": "Explains why this element was deprecated, usually also including a suggestion for how to access supported similar data. Formatted using the Markdown syntax (as specified by [CommonMark](https://commonmark.org/).",
1096 | "type": {
1097 | "kind": "SCALAR",
1098 | "name": "String",
1099 | "ofType": null
1100 | },
1101 | "defaultValue": "\"No longer supported\""
1102 | }
1103 | ]
1104 | }
1105 | ]
1106 | }
1107 | }
1108 | }
1109 |
--------------------------------------------------------------------------------