;
16 | };
17 |
18 | export type IRootValueType = {
19 | messages: IMessageType[];
20 | };
21 |
22 | export type RootValueType = IRootValueType;
23 |
24 | export type IGetRootValueType = () => RootValueType;
25 |
26 | export type IContextType = {
27 | eventEmitter: EventEmitter;
28 | mutateState: (producer: (root: IRootValueType) => void) => void;
29 | addMessage: (message: IMessageType) => void;
30 | };
31 |
--------------------------------------------------------------------------------
/packages/frontend/src/chat-message.tsx:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import { createFragmentContainer } from "react-relay";
3 | import graphql from "babel-plugin-relay/macro";
4 | import { chatMessage_message } from "./__generated__/chatMessage_message.graphql";
5 |
6 | const ChatMessage: React.FC<{ message: chatMessage_message }> = React.memo(
7 | ({ message }) => {
8 | return (
9 |
10 |
11 | {message.authorName}
12 |
13 | {message.rawContent}
14 |
15 | );
16 | }
17 | );
18 |
19 | export default createFragmentContainer(ChatMessage, {
20 | message: graphql`
21 | fragment chatMessage_message on Message {
22 | id
23 | authorName
24 | rawContent
25 | createdAt
26 | }
27 | `,
28 | });
29 |
--------------------------------------------------------------------------------
/packages/backend/schema-loader.js:
--------------------------------------------------------------------------------
1 | "use strict";
2 |
3 | const fs = require("fs");
4 | const { buildSchema } = require("graphql");
5 | const { mergeTypes } = require("merge-graphql-schemas");
6 |
7 | // TODO: use AST parser instead
8 | const REGEX = () => /export const typeDefs = \/\* GraphQL \*\/ `([^`]*)`/s;
9 |
10 | /**
11 | * Custom schema loader for modules located under src/graphql/modules
12 | * Each file must have a typeDefs export.
13 | */
14 | module.exports = (filePaths) => {
15 | if (Array.isArray(filePaths) === false) {
16 | filePaths = [filePaths];
17 | }
18 | const schemaParts = [];
19 | for (const filePath of filePaths) {
20 | const contents = fs.readFileSync(filePath, "utf-8");
21 | const result = REGEX().exec(contents);
22 | if (!result) {
23 | throw new Error(`Invalid '${filePath}'. exports no schema definition.`);
24 | }
25 | const [, schema] = result;
26 | schemaParts.push(schema);
27 | }
28 | return buildSchema(mergeTypes(schemaParts));
29 | };
30 |
--------------------------------------------------------------------------------
/packages/backend/src/graphql/modules/live.ts:
--------------------------------------------------------------------------------
1 | import { subscribeToLiveData } from "graphql-live-subscriptions";
2 | import GraphQLJSON from "graphql-type-json";
3 | import { SubscriptionResolvers } from "../__generated__/graphql-types";
4 | import { RootValueType, IContextType, IGetRootValueType } from "../../@types";
5 |
6 | export const typeDefs = /* GraphQL */ `
7 | scalar JSON
8 |
9 | type RFC6902Operation {
10 | op: String!
11 | path: String!
12 | from: String
13 | value: JSON
14 | }
15 |
16 | type LiveSubscription {
17 | query: Query
18 | patch: [RFC6902Operation!]
19 | }
20 |
21 | type Subscription {
22 | live: LiveSubscription
23 | }
24 |
25 | type Mutation
26 | type Query
27 | `;
28 |
29 | const live: SubscriptionResolvers["live"] = {
30 | subscribe: subscribeToLiveData({
31 | initialState: (root) => root,
32 | eventEmitter: (root, args, context) => context.eventEmitter,
33 | sourceRoots: {},
34 | }),
35 | resolve: (root: RootValueType) => root,
36 | };
37 |
38 | export const resolvers = {
39 | JSON: GraphQLJSON,
40 | Subscription: { live },
41 | };
42 |
--------------------------------------------------------------------------------
/packages/frontend/src/chat.tsx:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import { createFragmentContainer } from "react-relay";
3 | import graphql from "babel-plugin-relay/macro";
4 | import { chat_app } from "./__generated__/chat_app.graphql";
5 | import ChatMessage from "./chat-message";
6 |
7 | const ChatWindow: React.FC<{ app: chat_app }> = ({ app }) => {
8 | const ref = React.useRef(null);
9 | const [follow, setFollow] = React.useState(true);
10 | React.useEffect(() => {
11 | if (!ref.current) return;
12 | if (follow === true) {
13 | ref.current.scrollTop = ref.current.scrollHeight;
14 | }
15 | }, [app.messages, follow]);
16 | return (
17 | {
21 | if (!ref.current) return;
22 | const target: HTMLElement = ref.current;
23 | if (target.scrollTop !== target.scrollHeight - target.clientHeight) {
24 | setFollow(false);
25 | } else {
26 | setFollow(true);
27 | }
28 | }}
29 | >
30 | {app.messages.map((message) => (
31 |
32 | ))}
33 |
34 | );
35 | };
36 |
37 | export default createFragmentContainer(ChatWindow, {
38 | app: graphql`
39 | fragment chat_app on Query {
40 | messages {
41 | id
42 | ...chatMessage_message
43 | }
44 | }
45 | `,
46 | });
47 |
--------------------------------------------------------------------------------
/packages/backend/src/graphql-live-subscriptions.d.ts:
--------------------------------------------------------------------------------
1 | declare module "graphql-live-subscriptions" {
2 | import { EventEmitter } from "events";
3 | import { GraphQLObjectType, GraphQLType } from "graphql";
4 |
5 | type ILiveSubscriptionTypeDefOptions = {
6 | type?: string;
7 | queryType?: string;
8 | subscriptionName?: string;
9 | };
10 | declare function liveSubscriptionTypeDef(
11 | options: ILiveSubscriptionTypeDefOptions
12 | ): GraphQLObjectType;
13 |
14 | type ISubscribeToLiveDataOptions = {
15 | initialState: (source: TRoot, args: any, context: IContextType) => any;
16 | eventEmitter: (
17 | source: TRoot,
18 | args: any,
19 | context: IContextType
20 | ) => EventEmitter;
21 | sourceRoots: {
22 | [typeName: string]: string[];
23 | };
24 | };
25 | declare function subscribeToLiveData(
26 | options: ISubscribeToLiveDataOptions
27 | ): AsyncIterator;
28 |
29 | declare var GraphQLLiveData: (opts: {
30 | name: string;
31 | type: GraphQLType;
32 | resumption?: boolean;
33 | }) => GraphQLObjectType;
34 |
35 | export { liveSubscriptionTypeDef, subscribeToLiveData, GraphQLLiveData };
36 | }
37 |
38 | // declare module "graphql-type-json/RFC6902Operation" {
39 | // import { GraphQLObjectType } from "graphql";
40 |
41 | // declare var RFC6902Operation: GraphQLObjectType;
42 | // export default RFC6902Operation;
43 | // }
44 |
--------------------------------------------------------------------------------
/Dockerfile:
--------------------------------------------------------------------------------
1 | FROM node:12-slim as frontend-builder
2 |
3 | WORKDIR /usr/context
4 |
5 | COPY packages/frontend/package.json .
6 | COPY packages/frontend/yarn.lock .
7 | RUN yarn install --frozen-lockfile
8 |
9 | COPY packages/frontend/tsconfig.json .
10 | COPY packages/frontend/src/ ./src/
11 | COPY packages/frontend/public/ ./public/
12 |
13 | RUN yarn build
14 |
15 | FROM node:12-slim as backend-builder
16 |
17 | WORKDIR /usr/context
18 |
19 | COPY packages/backend/package.json .
20 | COPY packages/backend/yarn.lock .
21 | RUN yarn install --frozen-lockfile
22 |
23 | COPY packages/backend/tsconfig.json .
24 | COPY packages/backend/src/ ./src/
25 |
26 | RUN yarn build
27 |
28 | FROM node:12-slim as backend-dependency-builder
29 |
30 | WORKDIR /usr/context
31 | COPY packages/backend/package.json .
32 | COPY packages/backend/yarn.lock .
33 | COPY ./packages/backend/patches ./patches
34 | RUN yarn install --frozen-lockfile
35 | RUN npm prune --production
36 | COPY ./.yarnclean_ ./.yarnclean
37 | RUN yarn autoclean --force
38 | RUN find . -type d -empty -delete
39 |
40 | FROM m03geek/alpine-node:milli-12 as application
41 |
42 | WORKDIR /usr/app
43 |
44 | ARG NODE_ENV="production"
45 | ENV NODE_ENV="production"
46 | COPY --from=backend-builder /usr/context/package.json ./
47 | COPY --from=backend-dependency-builder /usr/context/node_modules/ ./node_modules/
48 |
49 | COPY --from=backend-builder /usr/context/build/ ./lib
50 | COPY --from=frontend-builder /usr/context/build ./public/
51 |
52 | CMD ["node", "lib/index.js"]
53 |
--------------------------------------------------------------------------------
/packages/frontend/src/__generated__/chat_app.graphql.ts:
--------------------------------------------------------------------------------
1 | /* tslint:disable */
2 | /* eslint-disable */
3 |
4 | import { ReaderFragment } from "relay-runtime";
5 | import { FragmentRefs } from "relay-runtime";
6 | export type chat_app = {
7 | readonly messages: ReadonlyArray<{
8 | readonly id: string;
9 | readonly " $fragmentRefs": FragmentRefs<"chatMessage_message">;
10 | }>;
11 | readonly " $refType": "chat_app";
12 | };
13 | export type chat_app$data = chat_app;
14 | export type chat_app$key = {
15 | readonly " $data"?: chat_app$data;
16 | readonly " $fragmentRefs": FragmentRefs<"chat_app">;
17 | };
18 |
19 |
20 |
21 | const node: ReaderFragment = {
22 | "kind": "Fragment",
23 | "name": "chat_app",
24 | "type": "Query",
25 | "metadata": null,
26 | "argumentDefinitions": [],
27 | "selections": [
28 | {
29 | "kind": "LinkedField",
30 | "alias": null,
31 | "name": "messages",
32 | "storageKey": null,
33 | "args": null,
34 | "concreteType": "Message",
35 | "plural": true,
36 | "selections": [
37 | {
38 | "kind": "ScalarField",
39 | "alias": null,
40 | "name": "id",
41 | "args": null,
42 | "storageKey": null
43 | },
44 | {
45 | "kind": "FragmentSpread",
46 | "name": "chatMessage_message",
47 | "args": null
48 | }
49 | ]
50 | }
51 | ]
52 | };
53 | (node as any).hash = '64d7e086c03c981baa98a88cc55d1fb7';
54 | export default node;
55 |
--------------------------------------------------------------------------------
/packages/backend/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "@graphql-live-chat/backend",
3 | "version": "0.0.0",
4 | "description": "> TODO: description",
5 | "author": "n1ru4l ",
6 | "homepage": "",
7 | "license": "MIT",
8 | "files": [
9 | "lib"
10 | ],
11 | "publishConfig": {
12 | "registry": "https://registry.yarnpkg.com"
13 | },
14 | "scripts": {
15 | "test": "echo \"Error: run tests from root\" && exit 1",
16 | "start": "ts-node-dev src/index.ts",
17 | "postinstall": "yarn patch-package",
18 | "build": "yarn tsc"
19 | },
20 | "dependencies": {
21 | "express": "4.17.1",
22 | "faker": "4.1.0",
23 | "graphql": "14.6.0",
24 | "graphql-live-subscriptions": "1.4.2",
25 | "graphql-subscriptions": "1.1.0",
26 | "graphql-tools": "4.0.7",
27 | "graphql-type-json": "0.3.1",
28 | "immer": "8.0.1",
29 | "subscriptions-transport-ws": "0.9.16",
30 | "uuid": "7.0.2"
31 | },
32 | "devDependencies": {
33 | "@graphql-codegen/cli": "1.13.1",
34 | "@graphql-codegen/typescript": "1.13.1",
35 | "@graphql-codegen/typescript-resolvers": "1.13.1",
36 | "@graphql-toolkit/schema-merging": "0.9.10",
37 | "@types/express": "4.17.3",
38 | "@types/faker": "4.1.11",
39 | "@types/graphql-type-json": "0.3.2",
40 | "@types/node": "12.12.31",
41 | "@types/uuid": "7.0.2",
42 | "@types/ws": "7.2.3",
43 | "merge-graphql-schemas": "1.7.6",
44 | "patch-package": "6.2.1",
45 | "ts-node-dev": "1.0.0-pre.44",
46 | "typescript": "3.8.3"
47 | }
48 | }
49 |
--------------------------------------------------------------------------------
/packages/backend/src/graphql/modules/message.ts:
--------------------------------------------------------------------------------
1 | import {
2 | MessageResolvers,
3 | QueryResolvers,
4 | MutationResolvers,
5 | } from "../__generated__/graphql-types";
6 | import { v4 as uuidV4 } from "uuid";
7 |
8 | export const typeDefs = /* GraphQL */ `
9 | type Message {
10 | id: ID!
11 | authorName: String!
12 | rawContent: String!
13 | createdAt: String!
14 | }
15 |
16 | input MessageAddInput {
17 | authorName: String!
18 | rawContent: String!
19 | }
20 |
21 | type Subscription
22 |
23 | type Query {
24 | messages: [Message!]!
25 | }
26 |
27 | type Mutation {
28 | messageAdd(input: MessageAddInput!): Boolean
29 | }
30 | `;
31 |
32 | export type IMessageType = {
33 | id: string;
34 | authorName: string;
35 | rawContent: string;
36 | createdAt: Date;
37 | };
38 |
39 | const Message: MessageResolvers = {
40 | id: (message) => message.id,
41 | authorName: (message) => message.authorName,
42 | rawContent: (message) => message.rawContent,
43 | createdAt: (message) => String(message.createdAt),
44 | };
45 |
46 | const messageAdd: MutationResolvers["messageAdd"] = (_, args, context) => {
47 | context.addMessage({
48 | id: uuidV4(),
49 | authorName: args.input.authorName,
50 | rawContent: args.input.rawContent,
51 | createdAt: new Date(),
52 | });
53 | return null;
54 | };
55 |
56 | const Mutation = { messageAdd };
57 |
58 | const messages: QueryResolvers["messages"] = (root) => root.messages;
59 |
60 | const Query = {
61 | messages,
62 | };
63 |
64 | export const resolvers = {
65 | Message,
66 | Mutation,
67 | Query,
68 | };
69 |
--------------------------------------------------------------------------------
/packages/frontend/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "@graphql-live-chat/frontend",
3 | "version": "0.1.0",
4 | "private": true,
5 | "scripts": {
6 | "start": "react-scripts start",
7 | "build": "react-scripts build",
8 | "test": "react-scripts test",
9 | "eject": "react-scripts eject",
10 | "relay": "relay-compiler --src ./src --schema ./schema.graphql --language typescript --artifactDirectory ./src/__generated__"
11 | },
12 | "eslintConfig": {
13 | "extends": "react-app"
14 | },
15 | "browserslist": {
16 | "production": [
17 | ">0.2%",
18 | "not dead",
19 | "not op_mini all"
20 | ],
21 | "development": [
22 | "last 1 chrome version",
23 | "last 1 firefox version",
24 | "last 1 safari version"
25 | ]
26 | },
27 | "devDependencies": {
28 | "@testing-library/jest-dom": "4.2.4",
29 | "@testing-library/react": "9.3.2",
30 | "@testing-library/user-event": "7.1.2",
31 | "@types/jest": "24.0.0",
32 | "@types/node": "12.0.0",
33 | "@types/react": "16.9.0",
34 | "@types/react-dom": "16.9.0",
35 | "@types/react-relay": "7.0.3",
36 | "@types/relay-runtime": "8.0.7",
37 | "babel-plugin-relay": "9.0.0",
38 | "graphql": "14.6.0",
39 | "http-proxy-middleware": "1.0.3",
40 | "react": "16.13.1",
41 | "react-dom": "16.13.1",
42 | "react-relay": "9.0.0",
43 | "react-scripts": "3.4.1",
44 | "relay-compiler": "^9.0.0",
45 | "relay-compiler-language-typescript": "12.0.0",
46 | "relay-config": "^9.0.0",
47 | "relay-runtime": "9.0.0",
48 | "subscriptions-transport-ws": "0.9.16",
49 | "typescript": "3.7.2"
50 | }
51 | }
52 |
--------------------------------------------------------------------------------
/packages/frontend/src/__generated__/chatMessage_message.graphql.ts:
--------------------------------------------------------------------------------
1 | /* tslint:disable */
2 | /* eslint-disable */
3 |
4 | import { ReaderFragment } from "relay-runtime";
5 | import { FragmentRefs } from "relay-runtime";
6 | export type chatMessage_message = {
7 | readonly id: string;
8 | readonly authorName: string;
9 | readonly rawContent: string;
10 | readonly createdAt: string;
11 | readonly " $refType": "chatMessage_message";
12 | };
13 | export type chatMessage_message$data = chatMessage_message;
14 | export type chatMessage_message$key = {
15 | readonly " $data"?: chatMessage_message$data;
16 | readonly " $fragmentRefs": FragmentRefs<"chatMessage_message">;
17 | };
18 |
19 |
20 |
21 | const node: ReaderFragment = {
22 | "kind": "Fragment",
23 | "name": "chatMessage_message",
24 | "type": "Message",
25 | "metadata": null,
26 | "argumentDefinitions": [],
27 | "selections": [
28 | {
29 | "kind": "ScalarField",
30 | "alias": null,
31 | "name": "id",
32 | "args": null,
33 | "storageKey": null
34 | },
35 | {
36 | "kind": "ScalarField",
37 | "alias": null,
38 | "name": "authorName",
39 | "args": null,
40 | "storageKey": null
41 | },
42 | {
43 | "kind": "ScalarField",
44 | "alias": null,
45 | "name": "rawContent",
46 | "args": null,
47 | "storageKey": null
48 | },
49 | {
50 | "kind": "ScalarField",
51 | "alias": null,
52 | "name": "createdAt",
53 | "args": null,
54 | "storageKey": null
55 | }
56 | ]
57 | };
58 | (node as any).hash = '961fc1422ef53556ea46829957905b07';
59 | export default node;
60 |
--------------------------------------------------------------------------------
/packages/frontend/public/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
12 |
13 |
17 |
18 |
27 | React App
28 |
29 |
30 |
31 |
32 |
42 |
43 |
44 |
--------------------------------------------------------------------------------
/packages/frontend/src/__generated__/messageAddMutation.graphql.ts:
--------------------------------------------------------------------------------
1 | /* tslint:disable */
2 | /* eslint-disable */
3 | /* @relayHash fda5a148c1642f758ac96a49b33af268 */
4 |
5 | import { ConcreteRequest } from "relay-runtime";
6 | export type MessageAddInput = {
7 | authorName: string;
8 | rawContent: string;
9 | };
10 | export type messageAddMutationVariables = {
11 | input: MessageAddInput;
12 | };
13 | export type messageAddMutationResponse = {
14 | readonly messageAdd: boolean | null;
15 | };
16 | export type messageAddMutation = {
17 | readonly response: messageAddMutationResponse;
18 | readonly variables: messageAddMutationVariables;
19 | };
20 |
21 |
22 |
23 | /*
24 | mutation messageAddMutation(
25 | $input: MessageAddInput!
26 | ) {
27 | messageAdd(input: $input)
28 | }
29 | */
30 |
31 | const node: ConcreteRequest = (function(){
32 | var v0 = [
33 | {
34 | "kind": "LocalArgument",
35 | "name": "input",
36 | "type": "MessageAddInput!",
37 | "defaultValue": null
38 | }
39 | ],
40 | v1 = [
41 | {
42 | "kind": "ScalarField",
43 | "alias": null,
44 | "name": "messageAdd",
45 | "args": [
46 | {
47 | "kind": "Variable",
48 | "name": "input",
49 | "variableName": "input"
50 | }
51 | ],
52 | "storageKey": null
53 | }
54 | ];
55 | return {
56 | "kind": "Request",
57 | "fragment": {
58 | "kind": "Fragment",
59 | "name": "messageAddMutation",
60 | "type": "RootMutationType",
61 | "metadata": null,
62 | "argumentDefinitions": (v0/*: any*/),
63 | "selections": (v1/*: any*/)
64 | },
65 | "operation": {
66 | "kind": "Operation",
67 | "name": "messageAddMutation",
68 | "argumentDefinitions": (v0/*: any*/),
69 | "selections": (v1/*: any*/)
70 | },
71 | "params": {
72 | "operationKind": "mutation",
73 | "name": "messageAddMutation",
74 | "id": null,
75 | "text": "mutation messageAddMutation(\n $input: MessageAddInput!\n) {\n messageAdd(input: $input)\n}\n",
76 | "metadata": {}
77 | }
78 | };
79 | })();
80 | (node as any).hash = '07c75ee8e12b1d2e1d8c858e2fe16bb4';
81 | export default node;
82 |
--------------------------------------------------------------------------------
/packages/backend/patches/subscriptions-transport-ws+0.9.16.patch:
--------------------------------------------------------------------------------
1 | diff --git a/node_modules/subscriptions-transport-ws/dist/server.d.ts b/node_modules/subscriptions-transport-ws/dist/server.d.ts
2 | index 8ffafe2..e6efca2 100644
3 | --- a/node_modules/subscriptions-transport-ws/dist/server.d.ts
4 | +++ b/node_modules/subscriptions-transport-ws/dist/server.d.ts
5 | @@ -44,7 +44,7 @@ export declare type SubscribeFunction = (schema: GraphQLSchema, document: Docume
6 | [key: string]: any;
7 | }, operationName?: string, fieldResolver?: GraphQLFieldResolver, subscribeFieldResolver?: GraphQLFieldResolver) => AsyncIterator | Promise | ExecutionResult>;
8 | export interface ServerOptions {
9 | - rootValue?: any;
10 | + rootValue?: any | (() => any);
11 | schema?: GraphQLSchema;
12 | execute?: ExecuteFunction;
13 | subscribe?: SubscribeFunction;
14 | diff --git a/node_modules/subscriptions-transport-ws/dist/server.js b/node_modules/subscriptions-transport-ws/dist/server.js
15 | index 730c585..e3e0115 100644
16 | --- a/node_modules/subscriptions-transport-ws/dist/server.js
17 | +++ b/node_modules/subscriptions-transport-ws/dist/server.js
18 | @@ -196,7 +196,8 @@ var SubscriptionServer = (function () {
19 | if (_this.subscribe && is_subscriptions_1.isASubscriptionOperation(document, params.operationName)) {
20 | executor = _this.subscribe;
21 | }
22 | - executionPromise = Promise.resolve(executor(params.schema, document, _this.rootValue, params.context, params.variables, params.operationName));
23 | + var rootValue = typeof _this.rootValue === "function" ? _this.rootValue() : _this.rootValue
24 | + executionPromise = Promise.resolve(executor(params.schema, document, rootValue, params.context, params.variables, params.operationName));
25 | }
26 | return executionPromise.then(function (executionResult) { return ({
27 | executionIterable: iterall_1.isAsyncIterable(executionResult) ?
28 |
--------------------------------------------------------------------------------
/packages/frontend/README.md:
--------------------------------------------------------------------------------
1 | This project was bootstrapped with [Create React App](https://github.com/facebook/create-react-app).
2 |
3 | ## Available Scripts
4 |
5 | In the project directory, you can run:
6 |
7 | ### `yarn start`
8 |
9 | Runs the app in the development mode.
10 | Open [http://localhost:3000](http://localhost:3000) to view it in the browser.
11 |
12 | The page will reload if you make edits.
13 | You will also see any lint errors in the console.
14 |
15 | ### `yarn test`
16 |
17 | Launches the test runner in the interactive watch mode.
18 | See the section about [running tests](https://facebook.github.io/create-react-app/docs/running-tests) for more information.
19 |
20 | ### `yarn build`
21 |
22 | Builds the app for production to the `build` folder.
23 | It correctly bundles React in production mode and optimizes the build for the best performance.
24 |
25 | The build is minified and the filenames include the hashes.
26 | Your app is ready to be deployed!
27 |
28 | See the section about [deployment](https://facebook.github.io/create-react-app/docs/deployment) for more information.
29 |
30 | ### `yarn eject`
31 |
32 | **Note: this is a one-way operation. Once you `eject`, you can’t go back!**
33 |
34 | If you aren’t satisfied with the build tool and configuration choices, you can `eject` at any time. This command will remove the single build dependency from your project.
35 |
36 | Instead, it will copy all the configuration files and the transitive dependencies (webpack, Babel, ESLint, etc) right into your project so you have full control over them. All of the commands except `eject` will still work, but they will point to the copied scripts so you can tweak them. At this point you’re on your own.
37 |
38 | You don’t have to ever use `eject`. The curated feature set is suitable for small and middle deployments, and you shouldn’t feel obligated to use this feature. However we understand that this tool wouldn’t be useful if you couldn’t customize it when you are ready for it.
39 |
40 | ## Learn More
41 |
42 | You can learn more in the [Create React App documentation](https://facebook.github.io/create-react-app/docs/getting-started).
43 |
44 | To learn React, check out the [React documentation](https://reactjs.org/).
45 |
--------------------------------------------------------------------------------
/packages/frontend/src/relay-environment.ts:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import {
3 | Environment,
4 | Network,
5 | RecordSource,
6 | Store,
7 | FetchFunction,
8 | SubscribeFunction,
9 | Observable,
10 | GraphQLResponse,
11 | } from "relay-runtime";
12 | import { SubscriptionClient } from "subscriptions-transport-ws";
13 | import { RelayModernEnvironment } from "relay-runtime/lib/store/RelayModernEnvironment";
14 |
15 | const fetchQuery = (client: SubscriptionClient): FetchFunction => (
16 | operation,
17 | variables
18 | ) => {
19 | if (!operation.text) throw new Error("Missing document.");
20 | const { text: query } = operation;
21 |
22 | return new Promise((resolve, reject) => {
23 | const subscription = client
24 | .request({
25 | query,
26 | variables,
27 | })
28 | .subscribe({
29 | next: (value) => {
30 | resolve(value as GraphQLResponse);
31 | subscription.unsubscribe();
32 | },
33 | error: reject,
34 | });
35 | });
36 | };
37 |
38 | const getWebSocketProtocol = (location: Location) =>
39 | location.protocol === "http:" ? "ws" : "wss";
40 |
41 | const getWebSocketUrl = (location: Location) =>
42 | // prettier-ignore
43 | `${getWebSocketProtocol(location)}://${location.host}/graphql`;
44 |
45 | // @source https://github.com/facebook/relay/issues/2967#issuecomment-567355735
46 | const setupSubscription = (
47 | subscriptionClient: SubscriptionClient
48 | ): SubscribeFunction => (request, variables) => {
49 | if (!request.text) throw new Error("Missing document.");
50 | const { text: query } = request;
51 |
52 | return Observable.create((sink) => {
53 | const c = subscriptionClient
54 | .request({ query, variables })
55 | .subscribe(sink as any);
56 | return c as any;
57 | });
58 | };
59 |
60 | export const createEnvironment = () => {
61 | const client = new SubscriptionClient(getWebSocketUrl(window.location), {
62 | reconnect: true,
63 | reconnectionAttempts: 100000,
64 | });
65 |
66 | return new Environment({
67 | network: Network.create(fetchQuery(client), setupSubscription(client)),
68 | store: new Store(new RecordSource()),
69 | });
70 | };
71 |
72 | export const EnvironmentContext = React.createContext(
73 | null
74 | );
75 |
76 | export const useEnvironment = (): RelayModernEnvironment => {
77 | const environment = React.useContext(EnvironmentContext);
78 | if (!environment) throw new Error("Missing Environment");
79 | return environment;
80 | };
81 |
--------------------------------------------------------------------------------
/packages/frontend/src/jsonpatch.ts:
--------------------------------------------------------------------------------
1 | import { RecordSourceSelectorProxy } from "relay-runtime";
2 |
3 | type IOperation = {
4 | op: string;
5 | path: string;
6 | from: string;
7 | value: any;
8 | };
9 |
10 | export const applyPatch = (store: RecordSourceSelectorProxy, patch: any) => {
11 | const operations: IOperation[] = [];
12 | for (const operationRecordProxy of patch) {
13 | const operation = {
14 | op: operationRecordProxy.getValue("op"),
15 | path: operationRecordProxy.getValue("path"),
16 | from: operationRecordProxy.getValue("from"),
17 | value: operationRecordProxy.getValue("value"),
18 | };
19 |
20 | operations.push(operation);
21 | }
22 | for (const operation of operations) {
23 | applyOperation(store, operation);
24 | }
25 | };
26 |
27 | export const applyOperation = (
28 | store: RecordSourceSelectorProxy,
29 | operation: IOperation
30 | ) => {
31 | if (operation.op === "replace") {
32 | // Currently only supports paths of array/element/property
33 | const path = operation.path.split("/").filter((item) => item !== "");
34 |
35 | const list: any = store.getRoot().getLinkedRecords(path[0]);
36 |
37 | if (list && list[path[1]]) list[path[1]].setValue(operation.value, path[2]);
38 | } else if (operation.op === "remove") {
39 | // Currently only supports paths of array/element/property
40 | const path = operation.path.split("/").filter((item) => item !== "");
41 | const list: any = store.getRoot().getLinkedRecords(path[0]);
42 | if (list && list[path[1]]) {
43 | const dataID = list[path[1]].getDataID();
44 | if (dataID) store.delete(dataID);
45 | }
46 | } else if (operation.op === "add") {
47 | // Currently only supports paths of array/element/property
48 | const path = operation.path.split("/").filter((item) => item !== "");
49 | const list = store.getRoot().getLinkedRecords(path[0]);
50 | if (list) {
51 | if (store.get(operation.value.id)) {
52 | // in case the websocket connection is lost and re-established the entry could already exist inside the cache
53 | return;
54 | }
55 | const newRecord = store.create(operation.value.id /* dataID */, "Jedi");
56 | for (const key in operation.value) {
57 | // issue https://github.com/facebook/relay/issues/2441
58 | if (
59 | // @ts-ignore
60 | typeof operation.value[key] !== "array" &&
61 | typeof operation.value[key] !== "object" &&
62 | operation.value[key] !== null
63 | ) {
64 | newRecord.setValue(operation.value[key], key);
65 | }
66 | }
67 |
68 | const newRecords = [...list, newRecord].filter((item) => item);
69 | store.getRoot().setLinkedRecords(newRecords, path[0]); //
70 | }
71 | }
72 | };
73 |
--------------------------------------------------------------------------------
/packages/backend/src/index.ts:
--------------------------------------------------------------------------------
1 | import * as gql from "graphql";
2 | import { EventEmitter } from "events";
3 | import { SubscriptionServer } from "subscriptions-transport-ws";
4 | import { produce } from "immer";
5 | import express = require("express");
6 | import { IRootValueType, IContextType } from "./@types";
7 | import { schema } from "./graphql/schema";
8 | import { registerFakeUsers } from "./register-fake-users";
9 | import { IMessageType } from "./graphql/modules/message";
10 |
11 | const app = express();
12 |
13 | const HOST = process.env.HOST || "0.0.0.0";
14 | const PORT = parseInt(process.env.port || "3001", 10);
15 |
16 | app.use(express.static("public"));
17 | app.all("/", (_, response) => {
18 | response.redirect("/");
19 | });
20 |
21 | const httpServer = app.listen(PORT, HOST, () => {
22 | console.log(`Listening on http://${HOST}:${PORT}.`);
23 | });
24 |
25 | let rootValue: IRootValueType = {
26 | messages: [],
27 | };
28 |
29 | const context: IContextType = {
30 | eventEmitter: new EventEmitter(),
31 | mutateState: (producer) => {
32 | rootValue = produce(rootValue, producer);
33 | context.eventEmitter.emit("update", { nextState: rootValue });
34 | },
35 | addMessage: (message: IMessageType) => {
36 | context.mutateState((root) => {
37 | if (root.messages.length > 100) {
38 | root.messages.splice(0, 1);
39 | }
40 | root.messages.push(message);
41 | });
42 | },
43 | };
44 |
45 | if (process.env.NODE_ENV === "development") {
46 | registerFakeUsers({ context });
47 | }
48 |
49 | const subscriptionServer = new SubscriptionServer(
50 | {
51 | execute: gql.execute,
52 | subscribe: gql.subscribe,
53 | schema: schema,
54 | rootValue: () => rootValue,
55 | onConnect: () => context,
56 | },
57 | {
58 | server: httpServer,
59 | path: "/graphql",
60 | }
61 | );
62 |
63 | const shutdownHandler = (() => {
64 | let isInvoked = false;
65 | return () => {
66 | if (isInvoked === true) return;
67 | isInvoked = true;
68 |
69 | subscriptionServer.close();
70 |
71 | httpServer.close((err) => {
72 | if (err) {
73 | console.error(err);
74 | process.exitCode = 1;
75 | }
76 | });
77 | };
78 | })();
79 |
80 | const errorExitHandler = (() => {
81 | let isInvoked = false;
82 | return () => {
83 | if (isInvoked === true) return;
84 | isInvoked = true;
85 |
86 | process.exitCode = 1;
87 | shutdownHandler();
88 |
89 | setTimeout(() => {
90 | process.exit(1);
91 | }, 5000).unref();
92 | };
93 | })();
94 |
95 | process.on("uncaughtException", () => {
96 | errorExitHandler();
97 | });
98 |
99 | process.on("unhandledRejection", () => {
100 | errorExitHandler();
101 | });
102 |
103 | process.on("SIGINT", shutdownHandler);
104 |
--------------------------------------------------------------------------------
/packages/frontend/src/__generated__/appQuery.graphql.ts:
--------------------------------------------------------------------------------
1 | /* tslint:disable */
2 | /* eslint-disable */
3 | /* @relayHash 22a6e1a1ee42b78d55a4e269b7c4880d */
4 |
5 | import { ConcreteRequest } from "relay-runtime";
6 | import { FragmentRefs } from "relay-runtime";
7 | export type appQueryVariables = {};
8 | export type appQueryResponse = {
9 | readonly " $fragmentRefs": FragmentRefs<"chat_app">;
10 | };
11 | export type appQuery = {
12 | readonly response: appQueryResponse;
13 | readonly variables: appQueryVariables;
14 | };
15 |
16 |
17 |
18 | /*
19 | query appQuery {
20 | ...chat_app
21 | }
22 |
23 | fragment chatMessage_message on Message {
24 | id
25 | authorName
26 | rawContent
27 | createdAt
28 | }
29 |
30 | fragment chat_app on Query {
31 | messages {
32 | id
33 | ...chatMessage_message
34 | }
35 | }
36 | */
37 |
38 | const node: ConcreteRequest = {
39 | "kind": "Request",
40 | "fragment": {
41 | "kind": "Fragment",
42 | "name": "appQuery",
43 | "type": "Query",
44 | "metadata": null,
45 | "argumentDefinitions": [],
46 | "selections": [
47 | {
48 | "kind": "FragmentSpread",
49 | "name": "chat_app",
50 | "args": null
51 | }
52 | ]
53 | },
54 | "operation": {
55 | "kind": "Operation",
56 | "name": "appQuery",
57 | "argumentDefinitions": [],
58 | "selections": [
59 | {
60 | "kind": "LinkedField",
61 | "alias": null,
62 | "name": "messages",
63 | "storageKey": null,
64 | "args": null,
65 | "concreteType": "Message",
66 | "plural": true,
67 | "selections": [
68 | {
69 | "kind": "ScalarField",
70 | "alias": null,
71 | "name": "id",
72 | "args": null,
73 | "storageKey": null
74 | },
75 | {
76 | "kind": "ScalarField",
77 | "alias": null,
78 | "name": "authorName",
79 | "args": null,
80 | "storageKey": null
81 | },
82 | {
83 | "kind": "ScalarField",
84 | "alias": null,
85 | "name": "rawContent",
86 | "args": null,
87 | "storageKey": null
88 | },
89 | {
90 | "kind": "ScalarField",
91 | "alias": null,
92 | "name": "createdAt",
93 | "args": null,
94 | "storageKey": null
95 | }
96 | ]
97 | }
98 | ]
99 | },
100 | "params": {
101 | "operationKind": "query",
102 | "name": "appQuery",
103 | "id": null,
104 | "text": "query appQuery {\n ...chat_app\n}\n\nfragment chatMessage_message on Message {\n id\n authorName\n rawContent\n createdAt\n}\n\nfragment chat_app on Query {\n messages {\n id\n ...chatMessage_message\n }\n}\n",
105 | "metadata": {}
106 | }
107 | };
108 | (node as any).hash = 'ee5ac318cee3817f2b2011324b7a22eb';
109 | export default node;
110 |
--------------------------------------------------------------------------------
/packages/backend/patches/graphql-live-subscriptions+1.4.2.patch:
--------------------------------------------------------------------------------
1 | diff --git a/node_modules/graphql-live-subscriptions/dist/queryExecutors/reactiveTree/ReactiveTree.js b/node_modules/graphql-live-subscriptions/dist/queryExecutors/reactiveTree/ReactiveTree.js
2 | index 2b154c4..c51776c 100644
3 | --- a/node_modules/graphql-live-subscriptions/dist/queryExecutors/reactiveTree/ReactiveTree.js
4 | +++ b/node_modules/graphql-live-subscriptions/dist/queryExecutors/reactiveTree/ReactiveTree.js
5 | @@ -5,7 +5,7 @@ Object.defineProperty(exports, "__esModule", {
6 | });
7 | exports.default = exports.createReactiveTreeInner = void 0;
8 |
9 | -var _execute = require("graphql/execution/execute");
10 | +var _path = require("graphql/jsutils/Path");
11 |
12 | var _collectSubFields = _interopRequireDefault(require("../util/collectSubFields"));
13 |
14 | @@ -67,8 +67,8 @@ const ReactiveTree = ({
15 | });
16 | const queryFieldDef = liveDataType.getFields().query;
17 | let graphqlPath;
18 | - graphqlPath = (0, _execute.addPath)(undefined, subscriptionName);
19 | - graphqlPath = (0, _execute.addPath)(graphqlPath, 'query');
20 | + graphqlPath = (0, _path.addPath)(undefined, subscriptionName);
21 | + graphqlPath = (0, _path.addPath)(graphqlPath, 'query');
22 | const sourceRootConfig = {
23 | // all ReactiveNodes that are source roots in the current query in the order
24 | // that they are initially resolved which is the same order they will
25 | diff --git a/node_modules/graphql-live-subscriptions/dist/queryExecutors/reactiveTree/updateChildNodes.js b/node_modules/graphql-live-subscriptions/dist/queryExecutors/reactiveTree/updateChildNodes.js
26 | index 7421408..f32df51 100644
27 | --- a/node_modules/graphql-live-subscriptions/dist/queryExecutors/reactiveTree/updateChildNodes.js
28 | +++ b/node_modules/graphql-live-subscriptions/dist/queryExecutors/reactiveTree/updateChildNodes.js
29 | @@ -7,8 +7,10 @@ exports.default = void 0;
30 |
31 | var _graphql = require("graphql");
32 |
33 | +var _path = require("graphql/jsutils/Path");
34 | var _execute = require("graphql/execution/execute");
35 |
36 | +
37 | var _collectSubFields = _interopRequireDefault(require("../util/collectSubFields"));
38 |
39 | var _ReactiveNode = require("./ReactiveNode");
40 | @@ -57,7 +59,7 @@ const updateChildNodes = reactiveNode => {
41 |
42 | Object.entries(fields).forEach(([childResponseName, childFieldNodes]) => {
43 | const childFieldDef = (0, _execute.getFieldDef)(schema, type, childFieldNodes[0].name.value);
44 | - const childPath = (0, _execute.addPath)(graphqlPath, childResponseName);
45 | + const childPath = (0, _path.addPath)(graphqlPath, childResponseName);
46 | const childReactiveNode = (0, _ReactiveNode.createNode)({
47 | exeContext,
48 | parentType: type,
49 | diff --git a/node_modules/graphql-live-subscriptions/dist/queryExecutors/reactiveTree/updateListChildNodes.js b/node_modules/graphql-live-subscriptions/dist/queryExecutors/reactiveTree/updateListChildNodes.js
50 | index 524f01f..b2675da 100644
51 | --- a/node_modules/graphql-live-subscriptions/dist/queryExecutors/reactiveTree/updateListChildNodes.js
52 | +++ b/node_modules/graphql-live-subscriptions/dist/queryExecutors/reactiveTree/updateListChildNodes.js
53 | @@ -7,7 +7,7 @@ exports.default = exports.ADD = exports.REMOVE = void 0;
54 |
55 | var _graphql = require("graphql");
56 |
57 | -var _execute = require("graphql/execution/execute");
58 | +var _path = require("graphql/jsutils/Path");
59 |
60 | var _listDiff = _interopRequireDefault(require("@d1plo1d/list-diff2"));
61 |
62 | @@ -63,7 +63,7 @@ const updateListChildNodes = reactiveNode => {
63 | parentType: reactiveNode.type,
64 | type: reactiveNode.type.ofType,
65 | fieldNodes,
66 | - graphqlPath: (0, _execute.addPath)(graphqlPath, move.index),
67 | + graphqlPath: (0, _path.addPath)(graphqlPath, move.index),
68 | sourceRootConfig
69 | }); // add the child at it's index
70 |
71 | diff --git a/node_modules/graphql-live-subscriptions/dist/subscribeToLiveData.js b/node_modules/graphql-live-subscriptions/dist/subscribeToLiveData.js
72 | index aa8dd1f..5e6ac63 100644
73 | --- a/node_modules/graphql-live-subscriptions/dist/subscribeToLiveData.js
74 | +++ b/node_modules/graphql-live-subscriptions/dist/subscribeToLiveData.js
75 | @@ -135,8 +135,8 @@ const subscribeToLiveData = ({
76 |
77 | connectionPubSub.unsubscribe = subID => {
78 | originalUnsubscribe(subID);
79 | - eventEmitter.removeEventListener('update', onUpdate);
80 | - eventEmitter.removeEventListener('patch', onPatch);
81 | + eventEmitter.removeListener('update', onUpdate);
82 | + eventEmitter.removeListener('patch', onPatch);
83 | };
84 |
85 | setImmediate(async () => {
86 | diff --git a/node_modules/graphql-live-subscriptions/src/queryExecutors/reactiveTree/ReactiveTree.js b/node_modules/graphql-live-subscriptions/src/queryExecutors/reactiveTree/ReactiveTree.js
87 | index f549654..0ad4d3e 100644
88 | --- a/node_modules/graphql-live-subscriptions/src/queryExecutors/reactiveTree/ReactiveTree.js
89 | +++ b/node_modules/graphql-live-subscriptions/src/queryExecutors/reactiveTree/ReactiveTree.js
90 | @@ -1,8 +1,8 @@
91 | -import { addPath } from 'graphql/execution/execute'
92 | +import { addPath } from 'graphql/jsutils/Path'
93 | import collectSubFields from '../util/collectSubFields'
94 |
95 | import * as ReactiveNode from './ReactiveNode'
96 | -
97 | +console.log("aaaa",addPath)
98 | export const createReactiveTreeInner = (opts) => {
99 | const {
100 | exeContext,
101 |
--------------------------------------------------------------------------------
/packages/frontend/src/app.tsx:
--------------------------------------------------------------------------------
1 | import React, { useCallback } from "react";
2 | import graphql from "babel-plugin-relay/macro";
3 | import Chat from "./chat";
4 | import { QueryRenderer, requestSubscription } from "react-relay";
5 | import { appSubscription } from "./__generated__/appSubscription.graphql";
6 | import { appQuery } from "./__generated__/appQuery.graphql";
7 | import { applyPatch } from "./jsonpatch";
8 | import { useMessageAddMutation } from "./message-add-mutation";
9 | import { useEnvironment } from "./relay-environment";
10 |
11 | const AppSubscription = graphql`
12 | subscription appSubscription {
13 | live {
14 | query {
15 | ...chat_app
16 | }
17 | patch {
18 | op
19 | path
20 | from
21 | value
22 | }
23 | }
24 | }
25 | `;
26 |
27 | const AppQuery = graphql`
28 | query appQuery {
29 | ...chat_app
30 | }
31 | `;
32 |
33 | const Container: React.FC<{}> = ({ children }) => (
34 |
42 | {children}
43 |
44 | );
45 |
46 | const useLocalStorageState = (
47 | identifier: string
48 | ): [string, React.Dispatch>] => {
49 | const [state, setState] = React.useState(
50 | () => window.localStorage.getItem(identifier) || "anon"
51 | );
52 | React.useEffect(() => {
53 | setState((state) => window.localStorage.getItem(identifier) || state);
54 | }, [identifier]);
55 |
56 | React.useEffect(() => {
57 | const eventListener = (event: StorageEvent) => {
58 | if (
59 | !event.storageArea ||
60 | event.storageArea !== localStorage ||
61 | event.key !== identifier ||
62 | !event.newValue
63 | ) {
64 | return;
65 | }
66 | setState(event.newValue);
67 | };
68 | window.addEventListener("storage", eventListener, false);
69 | return () => window.removeEventListener("storage", eventListener);
70 | }, [identifier]);
71 |
72 | const prevState = React.useRef(state);
73 | React.useEffect(() => {
74 | if (prevState.current !== state) {
75 | window.localStorage.setItem(identifier, state);
76 | }
77 | }, [identifier, state]);
78 |
79 | return [state, setState];
80 | };
81 |
82 | const ChatInput: React.FC<{}> = () => {
83 | const messageAdd = useMessageAddMutation();
84 | const authorNameInputRef = React.useRef(null);
85 | const [userName, setUserName] = useLocalStorageState("chat.userName");
86 |
87 | const onKeyDown = React.useCallback(
88 | (ev: React.KeyboardEvent) => {
89 | if (!authorNameInputRef.current) return;
90 | if (ev.key !== "Enter") return;
91 | messageAdd({
92 | authorName: userName,
93 | rawContent: authorNameInputRef.current.value,
94 | });
95 | authorNameInputRef.current.value = "";
96 | },
97 | [userName]
98 | );
99 |
100 | const onChangeUserName = React.useCallback(
101 | (ev: React.ChangeEvent) => {
102 | setUserName(ev.target.value || "anon");
103 | },
104 | [setUserName]
105 | );
106 |
107 | const onSubmit = useCallback(
108 | (ev: React.FormEvent) => ev.preventDefault(),
109 | []
110 | );
111 |
112 | return (
113 |
147 | );
148 | };
149 |
150 | export const App: React.FC<{}> = () => {
151 | const environment = useEnvironment();
152 | React.useEffect(() => {
153 | requestSubscription(environment, {
154 | subscription: AppSubscription,
155 | variables: {},
156 | updater: (store) => {
157 | const rootField = store.getRootField("live");
158 | if (!rootField) return;
159 | const patch = rootField.getLinkedRecords("patch");
160 | if (patch) applyPatch(store, patch as any);
161 | },
162 | });
163 | }, [environment]);
164 |
165 | return (
166 |
167 | query={AppQuery}
168 | environment={environment}
169 | variables={{}}
170 | render={({ error, props }) => {
171 | if (error) {
172 | return Error!
;
173 | }
174 | if (!props) {
175 | return Loading...
;
176 | }
177 | return (
178 |
179 |
180 |
181 |
182 | );
183 | }}
184 | />
185 | );
186 | };
187 |
--------------------------------------------------------------------------------
/packages/frontend/src/__generated__/appSubscription.graphql.ts:
--------------------------------------------------------------------------------
1 | /* tslint:disable */
2 | /* eslint-disable */
3 | /* @relayHash d5b221445c6fa0ba811a4f14629058b9 */
4 |
5 | import { ConcreteRequest } from "relay-runtime";
6 | import { FragmentRefs } from "relay-runtime";
7 | export type appSubscriptionVariables = {};
8 | export type appSubscriptionResponse = {
9 | readonly live: {
10 | readonly query: {
11 | readonly " $fragmentRefs": FragmentRefs<"chat_app">;
12 | } | null;
13 | readonly patch: ReadonlyArray<{
14 | readonly op: string;
15 | readonly path: string;
16 | readonly from: string | null;
17 | readonly value: unknown | null;
18 | }> | null;
19 | } | null;
20 | };
21 | export type appSubscription = {
22 | readonly response: appSubscriptionResponse;
23 | readonly variables: appSubscriptionVariables;
24 | };
25 |
26 |
27 |
28 | /*
29 | subscription appSubscription {
30 | live {
31 | query {
32 | ...chat_app
33 | }
34 | patch {
35 | op
36 | path
37 | from
38 | value
39 | }
40 | }
41 | }
42 |
43 | fragment chatMessage_message on Message {
44 | id
45 | authorName
46 | rawContent
47 | createdAt
48 | }
49 |
50 | fragment chat_app on Query {
51 | messages {
52 | id
53 | ...chatMessage_message
54 | }
55 | }
56 | */
57 |
58 | const node: ConcreteRequest = (function(){
59 | var v0 = {
60 | "kind": "LinkedField",
61 | "alias": null,
62 | "name": "patch",
63 | "storageKey": null,
64 | "args": null,
65 | "concreteType": "RFC6902Operation",
66 | "plural": true,
67 | "selections": [
68 | {
69 | "kind": "ScalarField",
70 | "alias": null,
71 | "name": "op",
72 | "args": null,
73 | "storageKey": null
74 | },
75 | {
76 | "kind": "ScalarField",
77 | "alias": null,
78 | "name": "path",
79 | "args": null,
80 | "storageKey": null
81 | },
82 | {
83 | "kind": "ScalarField",
84 | "alias": null,
85 | "name": "from",
86 | "args": null,
87 | "storageKey": null
88 | },
89 | {
90 | "kind": "ScalarField",
91 | "alias": null,
92 | "name": "value",
93 | "args": null,
94 | "storageKey": null
95 | }
96 | ]
97 | };
98 | return {
99 | "kind": "Request",
100 | "fragment": {
101 | "kind": "Fragment",
102 | "name": "appSubscription",
103 | "type": "Subscription",
104 | "metadata": null,
105 | "argumentDefinitions": [],
106 | "selections": [
107 | {
108 | "kind": "LinkedField",
109 | "alias": null,
110 | "name": "live",
111 | "storageKey": null,
112 | "args": null,
113 | "concreteType": "LiveSubscription",
114 | "plural": false,
115 | "selections": [
116 | {
117 | "kind": "LinkedField",
118 | "alias": null,
119 | "name": "query",
120 | "storageKey": null,
121 | "args": null,
122 | "concreteType": "Query",
123 | "plural": false,
124 | "selections": [
125 | {
126 | "kind": "FragmentSpread",
127 | "name": "chat_app",
128 | "args": null
129 | }
130 | ]
131 | },
132 | (v0/*: any*/)
133 | ]
134 | }
135 | ]
136 | },
137 | "operation": {
138 | "kind": "Operation",
139 | "name": "appSubscription",
140 | "argumentDefinitions": [],
141 | "selections": [
142 | {
143 | "kind": "LinkedField",
144 | "alias": null,
145 | "name": "live",
146 | "storageKey": null,
147 | "args": null,
148 | "concreteType": "LiveSubscription",
149 | "plural": false,
150 | "selections": [
151 | {
152 | "kind": "LinkedField",
153 | "alias": null,
154 | "name": "query",
155 | "storageKey": null,
156 | "args": null,
157 | "concreteType": "Query",
158 | "plural": false,
159 | "selections": [
160 | {
161 | "kind": "LinkedField",
162 | "alias": null,
163 | "name": "messages",
164 | "storageKey": null,
165 | "args": null,
166 | "concreteType": "Message",
167 | "plural": true,
168 | "selections": [
169 | {
170 | "kind": "ScalarField",
171 | "alias": null,
172 | "name": "id",
173 | "args": null,
174 | "storageKey": null
175 | },
176 | {
177 | "kind": "ScalarField",
178 | "alias": null,
179 | "name": "authorName",
180 | "args": null,
181 | "storageKey": null
182 | },
183 | {
184 | "kind": "ScalarField",
185 | "alias": null,
186 | "name": "rawContent",
187 | "args": null,
188 | "storageKey": null
189 | },
190 | {
191 | "kind": "ScalarField",
192 | "alias": null,
193 | "name": "createdAt",
194 | "args": null,
195 | "storageKey": null
196 | }
197 | ]
198 | }
199 | ]
200 | },
201 | (v0/*: any*/)
202 | ]
203 | }
204 | ]
205 | },
206 | "params": {
207 | "operationKind": "subscription",
208 | "name": "appSubscription",
209 | "id": null,
210 | "text": "subscription appSubscription {\n live {\n query {\n ...chat_app\n }\n patch {\n op\n path\n from\n value\n }\n }\n}\n\nfragment chatMessage_message on Message {\n id\n authorName\n rawContent\n createdAt\n}\n\nfragment chat_app on Query {\n messages {\n id\n ...chatMessage_message\n }\n}\n",
211 | "metadata": {}
212 | }
213 | };
214 | })();
215 | (node as any).hash = 'b700ddbdde79149f6a52c03f3c25de94';
216 | export default node;
217 |
--------------------------------------------------------------------------------
/packages/backend/src/graphql/__generated__/graphql-types.ts:
--------------------------------------------------------------------------------
1 | import { GraphQLResolveInfo, GraphQLScalarType, GraphQLScalarTypeConfig } from 'graphql';
2 | import { IMessageType } from '../modules/message';
3 | import { RootValueType, IContextType } from '../../@types';
4 | export type Maybe = T | null;
5 | export type RequireFields = { [X in Exclude]?: T[X] } & { [P in K]-?: NonNullable };
6 | /** All built-in and custom scalars, mapped to their actual values */
7 | export type Scalars = {
8 | ID: string;
9 | String: string;
10 | Boolean: boolean;
11 | Int: number;
12 | Float: number;
13 | JSON: unknown;
14 | };
15 |
16 |
17 | export type LiveSubscription = {
18 | readonly __typename: 'LiveSubscription';
19 | readonly query: Maybe;
20 | readonly patch: Maybe>;
21 | };
22 |
23 | export type Message = {
24 | readonly __typename: 'Message';
25 | readonly id: Scalars['ID'];
26 | readonly authorName: Scalars['String'];
27 | readonly rawContent: Scalars['String'];
28 | readonly createdAt: Scalars['String'];
29 | };
30 |
31 | export type MessageAddInput = {
32 | readonly authorName: Scalars['String'];
33 | readonly rawContent: Scalars['String'];
34 | };
35 |
36 | export type Mutation = {
37 | readonly __typename: 'Mutation';
38 | readonly messageAdd: Maybe;
39 | };
40 |
41 |
42 | export type MutationMessageAddArgs = {
43 | input: MessageAddInput;
44 | };
45 |
46 | export type Query = {
47 | readonly __typename: 'Query';
48 | readonly messages: ReadonlyArray;
49 | };
50 |
51 | export type Rfc6902Operation = {
52 | readonly __typename: 'RFC6902Operation';
53 | readonly op: Scalars['String'];
54 | readonly path: Scalars['String'];
55 | readonly from: Maybe;
56 | readonly value: Maybe;
57 | };
58 |
59 | export type Subscription = {
60 | readonly __typename: 'Subscription';
61 | readonly live: Maybe;
62 | };
63 |
64 |
65 |
66 | export type ResolverTypeWrapper = Promise | T;
67 |
68 | export type Resolver = ResolverFn;
69 |
70 | export type ResolverFn = (
71 | parent: TParent,
72 | args: TArgs,
73 | context: TContext,
74 | info: GraphQLResolveInfo
75 | ) => Promise | TResult;
76 |
77 | export type SubscriptionSubscribeFn = (
78 | parent: TParent,
79 | args: TArgs,
80 | context: TContext,
81 | info: GraphQLResolveInfo
82 | ) => AsyncIterator | Promise>;
83 |
84 | export type SubscriptionResolveFn = (
85 | parent: TParent,
86 | args: TArgs,
87 | context: TContext,
88 | info: GraphQLResolveInfo
89 | ) => TResult | Promise;
90 |
91 | export interface SubscriptionSubscriberObject {
92 | subscribe: SubscriptionSubscribeFn<{ [key in TKey]: TResult }, TParent, TContext, TArgs>;
93 | resolve?: SubscriptionResolveFn;
94 | }
95 |
96 | export interface SubscriptionResolverObject {
97 | subscribe: SubscriptionSubscribeFn;
98 | resolve: SubscriptionResolveFn;
99 | }
100 |
101 | export type SubscriptionObject =
102 | | SubscriptionSubscriberObject
103 | | SubscriptionResolverObject;
104 |
105 | export type SubscriptionResolver =
106 | | ((...args: any[]) => SubscriptionObject)
107 | | SubscriptionObject;
108 |
109 | export type TypeResolveFn = (
110 | parent: TParent,
111 | context: TContext,
112 | info: GraphQLResolveInfo
113 | ) => Maybe | Promise>;
114 |
115 | export type isTypeOfResolverFn = (obj: T, info: GraphQLResolveInfo) => boolean | Promise;
116 |
117 | export type NextResolverFn = () => Promise;
118 |
119 | export type DirectiveResolverFn = (
120 | next: NextResolverFn,
121 | parent: TParent,
122 | args: TArgs,
123 | context: TContext,
124 | info: GraphQLResolveInfo
125 | ) => TResult | Promise;
126 |
127 | /** Mapping between all available schema types and the resolvers types */
128 | export type ResolversTypes = {
129 | Query: ResolverTypeWrapper,
130 | Message: ResolverTypeWrapper,
131 | ID: ResolverTypeWrapper,
132 | String: ResolverTypeWrapper,
133 | Mutation: ResolverTypeWrapper,
134 | MessageAddInput: MessageAddInput,
135 | Boolean: ResolverTypeWrapper,
136 | Subscription: ResolverTypeWrapper,
137 | LiveSubscription: ResolverTypeWrapper,
138 | RFC6902Operation: ResolverTypeWrapper,
139 | JSON: ResolverTypeWrapper,
140 | };
141 |
142 | /** Mapping between all available schema types and the resolvers parents */
143 | export type ResolversParentTypes = {
144 | Query: RootValueType,
145 | Message: IMessageType,
146 | ID: Scalars['ID'],
147 | String: Scalars['String'],
148 | Mutation: RootValueType,
149 | MessageAddInput: MessageAddInput,
150 | Boolean: Scalars['Boolean'],
151 | Subscription: RootValueType,
152 | LiveSubscription: RootValueType,
153 | RFC6902Operation: Rfc6902Operation,
154 | JSON: Scalars['JSON'],
155 | };
156 |
157 | export interface JsonScalarConfig extends GraphQLScalarTypeConfig {
158 | name: 'JSON'
159 | }
160 |
161 | export type LiveSubscriptionResolvers = {
162 | query: Resolver, ParentType, ContextType>,
163 | patch: Resolver>, ParentType, ContextType>,
164 | __isTypeOf?: isTypeOfResolverFn,
165 | };
166 |
167 | export type MessageResolvers = {
168 | id: Resolver,
169 | authorName: Resolver,
170 | rawContent: Resolver,
171 | createdAt: Resolver,
172 | __isTypeOf?: isTypeOfResolverFn,
173 | };
174 |
175 | export type MutationResolvers = {
176 | messageAdd: Resolver, ParentType, ContextType, RequireFields>,
177 | };
178 |
179 | export type QueryResolvers = {
180 | messages: Resolver, ParentType, ContextType>,
181 | };
182 |
183 | export type Rfc6902OperationResolvers = {
184 | op: Resolver,
185 | path: Resolver,
186 | from: Resolver, ParentType, ContextType>,
187 | value: Resolver, ParentType, ContextType>,
188 | __isTypeOf?: isTypeOfResolverFn,
189 | };
190 |
191 | export type SubscriptionResolvers = {
192 | live: SubscriptionResolver, "live", ParentType, ContextType>,
193 | };
194 |
195 | export type Resolvers = {
196 | JSON: GraphQLScalarType,
197 | LiveSubscription: LiveSubscriptionResolvers,
198 | Message: MessageResolvers,
199 | Mutation: MutationResolvers,
200 | Query: QueryResolvers,
201 | RFC6902Operation: Rfc6902OperationResolvers,
202 | Subscription: SubscriptionResolvers,
203 | };
204 |
205 |
206 | /**
207 | * @deprecated
208 | * Use "Resolvers" root object instead. If you wish to get "IResolvers", add "typesPrefix: I" to your config.
209 | */
210 | export type IResolvers = Resolvers;
211 |
--------------------------------------------------------------------------------