├── .gitattributes ├── .gitignore ├── LICENSE ├── README.md ├── demo ├── .eslintignore ├── .eslintrc ├── EmberQL.ts ├── client │ ├── App.tsx │ ├── components │ │ ├── Contributing.tsx │ │ ├── ContributingStyles.css │ │ ├── DocsContainer.tsx │ │ ├── DocsStyles.css │ │ ├── EmberQL.tsx │ │ ├── EmberQLStyles.css │ │ ├── Feature.tsx │ │ ├── Features.tsx │ │ ├── FeaturesStyles.css │ │ ├── LandingContainer.tsx │ │ ├── NavStyles.css │ │ ├── Navbar.tsx │ │ ├── Team.tsx │ │ ├── TeamMember.tsx │ │ ├── TeamStyles.css │ │ ├── WhyReason.tsx │ │ ├── WhyStyles.css │ │ ├── WhyWeExist.tsx │ │ ├── assets │ │ │ ├── BloatedA.png │ │ │ ├── buttons.png │ │ │ ├── cristian.png │ │ │ ├── favicon.ico │ │ │ ├── graphql.png │ │ │ ├── heart.png │ │ │ ├── icon.png │ │ │ ├── manju.png │ │ │ ├── mike.png │ │ │ ├── ram.png │ │ │ ├── redis.png │ │ │ ├── shield.png │ │ │ └── tyler.png │ │ └── demo-components │ │ │ ├── DemoContainer.tsx │ │ │ ├── GraphContainer.tsx │ │ │ └── QueryContainer.tsx │ ├── index.tsx │ └── styles.css ├── d.ts ├── index.html ├── package-lock.json ├── package.json ├── server │ ├── models │ │ └── db.js │ ├── schema │ │ └── schema.ts │ └── server.ts ├── tsconfig.json └── webpack.config.js └── src ├── EmberQL.js ├── EmberQL.ts ├── README.md ├── package-lock.json └── package.json /.gitattributes: -------------------------------------------------------------------------------- 1 | *.js linguist-vendored=true 2 | *.ts linguist-vendored=false 3 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | logs 3 | *.log 4 | npm-debug.log* 5 | yarn-debug.log* 6 | yarn-error.log* 7 | lerna-debug.log* 8 | 9 | # Diagnostic reports (https://nodejs.org/api/report.html) 10 | report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json 11 | 12 | # Runtime data 13 | pids 14 | *.pid 15 | *.seed 16 | *.pid.lock 17 | 18 | # Directory for instrumented libs generated by jscoverage/JSCover 19 | lib-cov 20 | 21 | # Coverage directory used by tools like istanbul 22 | coverage 23 | *.lcov 24 | 25 | # nyc test coverage 26 | .nyc_output 27 | 28 | # Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files) 29 | .grunt 30 | 31 | # Bower dependency directory (https://bower.io/) 32 | bower_components 33 | 34 | # node-waf configuration 35 | .lock-wscript 36 | 37 | # Compiled binary addons (https://nodejs.org/api/addons.html) 38 | build/Release 39 | 40 | # Dependency directories 41 | node_modules/ 42 | jspm_packages/ 43 | compiledTS/ 44 | build/ 45 | 46 | # TypeScript v1 declaration files 47 | typings/ 48 | 49 | # TypeScript cache 50 | *.tsbuildinfo 51 | 52 | # Optional npm cache directory 53 | .npm 54 | 55 | # Optional eslint cache 56 | .eslintcache 57 | 58 | # Microbundle cache 59 | .rpt2_cache/ 60 | .rts2_cache_cjs/ 61 | .rts2_cache_es/ 62 | .rts2_cache_umd/ 63 | 64 | # Optional REPL history 65 | .node_repl_history 66 | 67 | # Output of 'npm pack' 68 | *.tgz 69 | 70 | # Yarn Integrity file 71 | .yarn-integrity 72 | 73 | # dotenv environment variables file 74 | .env 75 | .env.test 76 | 77 | # parcel-bundler cache (https://parceljs.org/) 78 | .cache 79 | 80 | # Next.js build output 81 | .next 82 | 83 | # Nuxt.js build / generate output 84 | .nuxt 85 | dist 86 | 87 | # Gatsby files 88 | .cache/ 89 | # Comment in the public line in if your project uses Gatsby and *not* Next.js 90 | # https://nextjs.org/blog/next-9-1#public-directory-support 91 | # public 92 | 93 | # vuepress build output 94 | .vuepress/dist 95 | 96 | # Serverless directories 97 | .serverless/ 98 | 99 | # FuseBox cache 100 | .fusebox/ 101 | 102 | # DynamoDB Local files 103 | .dynamodb/ 104 | 105 | # TernJS port file 106 | .tern-port 107 | 108 | # Zip files 109 | *.zip -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2021 OSLabs Beta 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # EmberQL 2 | 3 |

4 | 5 | [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://github.com/oslabs-beta/EmberQL/blob/dev/LICENSE) 6 | 7 | ## What is EmberQL? 8 | 9 | EmberQL is an intuitive, lightweight Node module that facilitates caching data from GraphQL queries, and implements a dynamic data persistence system that monitors the status of the primary database and modifies cache invalidation accordingly. 10 | 11 | ## Features 12 | 13 | ### Server-side caching with Redis to decrease query times 14 | 15 | Decrease the time it takes for your users to fetch data from your database by up to one hundred fold. Research shows that even a second of latency will drastically increase bounce rates on your application. Additionally, depending on the specifications or hosting of your database, too many simultaneous queries can cause timeouts to occur. Using EmberQL, there is no need to gamble with forcing your users to make redundant queries to your database. 16 | 17 | ### Dynamic cache invalidation 18 | 19 | EmberQL incorporates a smart "heartbeat" feature that will monitor your database in real time and halt cache invalidation when it detects downtime. This is done by periodically increasing the time to live of cached data, and as soon as the database comes back online cached items will revert to being evicted normally. The heartbeat will communicate relevant information about the cache and database to the developer in the server console. 20 | 21 | ### Data persistence system utilizing **RDB** (Redis Database) and **AOF** (Append Only File) 22 | 23 | In the event of your database going down, the most relevant information users are querying will be available in the in-memory database and thus available to users. With EmberQL, there is no need for your clients to notice when your database isn't running. You can rest assured that your application will have fault tolerance after installing the module. 24 | 25 | ## Installation & Prerequisites 26 | 27 | Install the EmberQL module into your Node.js application by running the command npm install emberql. Your application must have GraphQL and as a dependency, and you will need to define your schema so that EmberQL can make use of it. You will also need Redis as a dependency to access the Redis functions (createClient, connect, on, etc.) and you will need to either run a Redis server on your machine locally or utilize AWS Elasticache to run a Redis server. 28 | 29 | ## Implementation 30 | 31 | After installing, the module can be easily configured by making a few small additions to your server file. 32 | 33 | The EmberQL class will take your GraphQL schema and your Redis cache instance as arguments: 34 | 35 | ``` 36 | const Ember = new EmberQL(schema, redisCache); 37 | ``` 38 | 39 | Any request sent to '/graphql' should be routed through the handleQuery middleware: 40 | 41 | ``` 42 | app.use('/graphql', Ember.handleQuery, (req, res) => { 43 | res.status(202).json(res.locals.data); 44 | }); 45 | ``` 46 | 47 | To clear the Redis cache, send a request to the '/clearCache' endpoint and route it through the EmberQL clearCache method: 48 | 49 | ``` 50 | app.use('/clearCache', Ember.clearCache, (req, res) => { 51 | res.sendStatus(202); 52 | }); 53 | ``` 54 | 55 | To set up the heartbeat, simply save the heartbeat property of the new EmberQL instance you just declared to a new variable. Then use the setInterval method and an interval of your choice to assign the frequency you would like the heartbeat to check your database: 56 | 57 | ``` 58 | const EmberHeartbeat = Ember.heartbeat; 59 | 60 | setInterval(() => { 61 | EmberHeartbeat(); 62 | }, 3000); 63 | ``` 64 | 65 | ## Features in Production 66 | 67 | Data normalization for Redis caching is currently in our development pipeline. The prototype utilizes a recursive function to parse the GraphQL AST and transform queries into key value pairs leveraging hashing to optimize memory. 68 | 69 | ## EmberQL Engineering Team 70 | 71 | [Cristian De Los Rios](https://github.com/Cristian-DeLosRios) | 72 | [Manjunath Ajjappa Pattanashetty](https://github.com/manjunathap85) | 73 | [Mike Masatsugu](https://github.com/mikemasatsugu) | 74 | [Ram Marimuthu](https://github.com/rammarimuthu) | 75 | [Tyler Pohn](https://github.com/tylerpohn) 76 | -------------------------------------------------------------------------------- /demo/.eslintignore: -------------------------------------------------------------------------------- 1 | db.js 2 | webpack.config.js 3 | .eslintrc 4 | -------------------------------------------------------------------------------- /demo/.eslintrc: -------------------------------------------------------------------------------- 1 | { 2 | "env": { 3 | "browser": true, 4 | "es6": true 5 | }, 6 | "rules": { 7 | "no-console": "warn", 8 | "import/order": "off", 9 | "import/first": "off", 10 | "import/extensions": "off", 11 | "react/prop-types": "off", 12 | "linebreak-style": "off", 13 | "indentation-style": "off", 14 | "spaced-comment": "off", 15 | "no-use-before-define": "warn", 16 | "@typescript-eslint/no-var-requires": 0, 17 | "@typescript-eslint/no-unused-vars": "error", 18 | "@typescript-eslint/indent": ["warn", 2], 19 | "@typescript-eslint/no-useless-constructor": "error", 20 | "@typescript-eslint/no-unsafe-return": "warn" 21 | }, 22 | "extends": [ 23 | "plugin:@typescript-eslint/recommended", 24 | "plugin:@typescript-eslint/eslint-recommended", 25 | "plugin:react-hooks/recommended", 26 | "airbnb-typescript" 27 | ], 28 | "parser": "@typescript-eslint/parser", 29 | "root": true, 30 | "plugins": [ 31 | "react", 32 | "@typescript-eslint", 33 | "eslint-plugin-react", 34 | "eslint-plugin-react-hooks", 35 | "eslint-plugin-import" 36 | ], 37 | "parserOptions": { 38 | "ecmaVersion": 11, 39 | 40 | "ecmaFeatures": { 41 | "modules": true, 42 | "jsx": true 43 | }, 44 | "project": "./demo/tsconfig.json", 45 | "sourceType": "module" 46 | }, 47 | "settings": { 48 | "import/extensions": [".js", "jsx", ".ts", ".tsx"], 49 | "import/parsers": { 50 | "@typescript-eslint/parser": [".ts", ".tsx"] 51 | }, 52 | "import/resolver": { 53 | "node": { 54 | "extensions": [".js", ".jsx", ".ts", ".tsx"] 55 | } 56 | } 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /demo/EmberQL.ts: -------------------------------------------------------------------------------- 1 | import { graphql, GraphQLSchema } from 'graphql'; 2 | import * as express from 'express'; 3 | const redis = require('redis'); 4 | const fetch = require('node-fetch'); 5 | 6 | class EmberQL { 7 | redisClient: any; 8 | graphQLQuery: string; 9 | schema: GraphQLSchema; 10 | redisCache: any; 11 | 12 | constructor(schema: GraphQLSchema, redisCache: any) { 13 | this.handleQuery = this.handleQuery.bind(this); 14 | this.clearCache = this.clearCache.bind(this); 15 | this.heartbeat = this.heartbeat.bind(this); 16 | this.increaseTTL = this.increaseTTL.bind(this); 17 | 18 | this.graphQLQuery = ''; 19 | this.schema = schema; 20 | this.redisCache = redisCache; 21 | } 22 | 23 | async handleQuery( 24 | req: express.Request, 25 | res: express.Response, 26 | next: express.NextFunction 27 | ) { 28 | this.graphQLQuery = req.body.query; 29 | 30 | if (await this.redisCache.exists(this.graphQLQuery)) { 31 | const response = await this.redisCache.get(this.graphQLQuery); 32 | res.locals.data = JSON.parse(response); 33 | return next(); 34 | } else { 35 | const results = await graphql({ 36 | schema: this.schema, 37 | source: this.graphQLQuery, 38 | }); 39 | this.redisCache.set(this.graphQLQuery, JSON.stringify(results)); 40 | res.locals.data = results; 41 | return next(); 42 | } 43 | } 44 | 45 | heartbeat() { 46 | console.log('enter heartbeat'); 47 | fetch('http://localhost:3000/heartbeat', { 48 | method: 'POST', 49 | headers: { 50 | 'content-type': 'application/json;charset=UTF-8', 51 | }, 52 | body: JSON.stringify({ 53 | query: '{book(id:1){id}}', 54 | }), 55 | }) 56 | .then((data: any) => data.json()) 57 | .then((data: any) => { 58 | const date = new Date(); 59 | // console.log('data:', data); 60 | if (data.errors) { 61 | for (const error of data.errors) { 62 | // console.log(error.message.slice(0, 20)); 63 | if (error.message.slice(0, 20) === 'connect ECONNREFUSED') { 64 | console.log( 65 | `Heartbeat FAILED, Cache invalidation halted. ${date}` 66 | ); 67 | this.increaseTTL(); 68 | } 69 | } 70 | } else { 71 | console.log(`Heartbeat OK, ${date}`); 72 | } 73 | }) 74 | .catch((err: any) => { 75 | console.log('entered catch'); 76 | if (err.code === 'ECONNREFUSED') this.increaseTTL(); 77 | }); 78 | } 79 | 80 | // heartbeat() { 81 | // console.log('enter heartbeat'); 82 | // graphql(this.schema, '{books{title}}') 83 | // // .then((data: any) => data.json()) 84 | // .then((data: any) => console.log(data)) 85 | // .catch((err: any) => { 86 | // console.log('entered catch'); 87 | // console.log(err); 88 | // if (err.code === 'ECONNREFUSED') this.increaseTTL(); 89 | // }); 90 | // } 91 | 92 | async increaseTTL() { 93 | console.log('TTL increased on all KEYS'); 94 | const keysArr = await this.redisCache.keys('*'); 95 | for (const key of keysArr) { 96 | const currentTTL = await this.redisCache.ttl(key); 97 | // console.log('before', currentTTL); 98 | // ttl time is in seconds 99 | await this.redisCache.EXPIRE(key, currentTTL + 60); 100 | 101 | console.log(await this.redisCache.ttl(key)); 102 | } 103 | } 104 | 105 | clearCache( 106 | req: express.Request, 107 | res: express.Response, 108 | next: express.NextFunction 109 | ) { 110 | this.redisCache.flushAll(); 111 | next(); 112 | } 113 | } 114 | 115 | export default EmberQL; 116 | -------------------------------------------------------------------------------- /demo/client/App.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { Routes, Route } from 'react-router-dom'; 3 | import './styles.css'; 4 | import DemoContainer from './components/demo-components/DemoContainer'; 5 | import LandingContainer from './components/LandingContainer'; 6 | import DocsContainer from './components/DocsContainer'; 7 | import Team from './components/Team'; 8 | import Navbar from './components/Navbar'; 9 | 10 | function App() { 11 | return ( 12 |
13 | 14 | 15 | } /> 16 | } /> 17 | } /> 18 | } /> 19 | } /> 20 | 21 |
22 | ); 23 | } 24 | 25 | export default App; 26 | -------------------------------------------------------------------------------- /demo/client/components/Contributing.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import './ContributingStyles.css'; 3 | 4 | const description = ` 5 | Thank you for considering contributing to EmberQL's codebase! 6 | If you'd like to help us maintain and update EmberQL, please understand that all of your contributions will fall under EmberQL's MIT license.\n \n 7 | 8 | Feel free to fork our repo and create a branch for your intended feature. 9 | If you've added a feature that needs to be reflected in the README or in our documentation, please be sure to add that as well, along with any tests that ensure your feature is working properly.\n \n 10 | 11 | Also, please make sure your code is error-free using our linter, to ensure consistency across our contributions.\n \n 12 | 13 | After all is finished and pushed to your branch, issue that pull request to the main repository! 14 | `; 15 | 16 | function Contributing() { 17 | return ( 18 |
19 |

CONTRIBUTING

20 |

{description}

21 |
22 | ); 23 | } 24 | 25 | export default Contributing; 26 | -------------------------------------------------------------------------------- /demo/client/components/ContributingStyles.css: -------------------------------------------------------------------------------- 1 | #section-title { 2 | font-size: 6vw; 3 | min-block-size: 40px; 4 | font-family: 'Red Hat Display', sans-serif; 5 | font-weight: 400; 6 | } 7 | .contributing-container { 8 | background-color: #082032; 9 | 10 | margin: 0; 11 | padding: 10% 10%; 12 | display: flex; 13 | flex-direction: column; 14 | align-items: center; 15 | justify-content: space-evenly; 16 | } 17 | 18 | .contributing-description { 19 | max-width: 600px; 20 | text-align: center; 21 | white-space: pre-wrap; 22 | } 23 | @media (min-width: 1100px) { 24 | #section-title { 25 | font-size: 5vw; 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /demo/client/components/DocsContainer.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { render } from 'react-dom'; 3 | import './DocsStyles.css'; 4 | 5 | function DocsContainer() { 6 | return ( 7 |
8 |

Docs

9 | 10 | Explore Our Github 11 | 12 |
13 |

14 | 15 | License: MIT 19 | 20 |

21 |

What is EmberQL?

22 |
23 |
24 |

25 | EmberQL is an intuitive, lightweight Node module that facilitates 26 | caching data from GraphQL queries, and implements a dynamic data 27 | persistence system that monitors the status of the primary database 28 | and modifies cache invalidation accordingly. 29 |

30 |
31 |

Features

32 |
33 |
34 |

35 | Server-side caching with Redis to decrease query times 36 |

37 |

38 | {' '} 39 | Decrease the time it takes for your users to fetch data from your 40 | database by up to one hundred fold. Research shows that even a second 41 | of latency will drastically increase bounce rates on your application. 42 | Additionally, depending on the specifications or hosting of your 43 | database, too many simultaneous queries can cause timeouts to occur. 44 | Using EmberQL, there is no need to gamble with forcing your users to 45 | make redundant queries to your database. 46 |

47 |
48 |

Dynamic cache invalidation

49 |

50 | {' '} 51 | EmberQL incorporates a smart "heartbeat" feature that will monitor 52 | your database in real time and halt cache invalidation when it detects 53 | downtime. This is done by periodically increasing the time to live of 54 | cached data, and as soon as the database comes back online cached 55 | items will revert to being evicted normally. The heartbeat will 56 | communicate relevant information about the cache and database to the 57 | developer in the server console.{' '} 58 |

59 |
60 |

61 | Data persistence system utilizing RDB (Redis 62 | Database) and AOF (Append Only File) 63 |

64 |

65 | {' '} 66 | In the event of your database going down, the most relevant 67 | information users are querying will be available in the in-memory 68 | database and thus available to users. With EmberQL, there is no need 69 | for your clients to notice when your database isn't running. You can 70 | rest assured that your application will have fault tolerance after 71 | installing the module.{' '} 72 |

73 |
74 |

75 | Installation & Prerequisites 76 |

77 |
78 |
79 |

80 | Install the EmberQL module into your Node.js application by running 81 | the command npm install emberql. Your application must have GraphQL 82 | and as a dependency, and you will need to define your schema so that 83 | EmberQL can make use of it. You will also need Redis as a dependency 84 | to access the Redis functions (createClient, connect, on, etc.) and 85 | you will need to either run a Redis server on your machine locally or 86 | utilize AWS Elasticache to run a Redis server.{' '} 87 |

88 |
89 |

Implementation

90 |
91 |
92 |

93 | After installing, the module can be easily configured by making a few 94 | small additions to your server file.{' '} 95 |

96 |

97 | The EmberQL class will take your GraphQL schema and your Redis cache 98 | instance as arguments: 99 |

100 |
101 |           
102 | 103 | const Ember ={' '} 104 | new EmberQL(schema, 105 | redisCache);{'\n'} 106 | 107 |
108 |
109 |

110 | Any request sent to '/graphql' should be routed through the 111 | handleQuery middleware: 112 |

113 |
114 |           
115 | 116 | app.use('/graphql', 117 | Ember.handleQuery,{' '} 118 | 119 | (req, res) => 120 | {' '} 121 | {'{'} 122 | {'\n'} 123 | {' '}res.status(202 124 | ).json(res.locals.data);{'\n'} 125 | {'}'});{'\n'} 126 | 127 |
128 |
129 |

130 | To clear the Redis cache, send a request to the '/clearCache' endpoint 131 | and route it through the EmberQL clearCache method: 132 |

133 |
134 |           
135 | 136 | app.use('/clearCache', 137 | Ember.clearCache,{' '} 138 | 139 | (req, res) => 140 | {' '} 141 | {'{'} 142 | {'\n'} 143 | {' '}res.sendStatus(202); 144 | {'\n'} 145 | {'}'});{'\n'} 146 | 147 |
148 |
149 |

150 | To set up the heartbeat, simply save the heartbeat property of the new 151 | EmberQL instance you just declared to a new variable. Then use the 152 | setInterval method and an interval of your choice to assign the 153 | frequency you would like the heartbeat to check your database: 154 |

155 |
156 |           
157 | 158 | const EmberHeartbeat = Ember.heartbeat;{'\n'} 159 | {'\n'}setInterval( 160 | 161 | () => 162 | {' '} 163 | {'{'} 164 | {'\n'} 165 | {' '}EmberHeartbeat();{'\n'} 166 | {'}'}, 3000);{'\n'} 167 | 168 |
169 |
170 |

Features in Production

171 |
172 |
173 |

174 | Data normalization for Redis caching is currently in our development 175 | pipeline. The prototype utilizes a recursive function to parse the 176 | GraphQL AST and transform queries into key value pairs leveraging 177 | hashing to optimize memory.{' '} 178 |

179 |
180 |

EmberQL Engineering Team

181 |
182 |
183 |

184 | 185 | Cristian De Los Rios 186 | {' '} 187 | |{' '} 188 | 189 | Manjunath Ajjappa Pattanashetty 190 | {' '} 191 | |{' '} 192 | 193 | Mike Masatsugu 194 | {' '} 195 | |{' '} 196 | 197 | Ram Marimuthu 198 | {' '} 199 | |{' '} 200 | 201 | Tyler Pohn 202 | 203 |

204 |
205 |
206 | ); 207 | } 208 | 209 | export default DocsContainer; 210 | -------------------------------------------------------------------------------- /demo/client/components/DocsStyles.css: -------------------------------------------------------------------------------- 1 | /* Background #082032 */ 2 | /* Blue-Gray Accent #2C394B */ 3 | /* Gray #334756 */ 4 | /* Ember #FF4C29 */ 5 | .link { 6 | font-size: large; 7 | font-family: 'Red Hat Display', sans-serif; 8 | color: #ff4c29; 9 | margin-bottom: 10px; 10 | } 11 | 12 | .github { 13 | display: flex; 14 | align-items: center; 15 | flex-direction: column; 16 | } 17 | 18 | .markdown-container { 19 | margin: 50px; 20 | background-color: #082032; 21 | padding: 50px; 22 | width: 95%; 23 | } 24 | .code { 25 | display: flex; 26 | background-color: #334756; 27 | margin: 25px; 28 | padding: 25px; 29 | overflow: auto; 30 | flex-wrap: wrap; 31 | } 32 | 33 | .git-link { 34 | color: #ff4c29; 35 | } 36 | 37 | @media (max-width: 900px) { 38 | .code { 39 | display: flex; 40 | margin-left: 0px; 41 | margin-right: 0px; 42 | padding-left: 0px; 43 | padding-right: 0px; 44 | flex-wrap: wrap; 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /demo/client/components/EmberQL.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { Link } from 'react-router-dom'; 3 | import './EmberQLStyles.css'; 4 | import icon from './assets/icon.png'; 5 | 6 | function EmberQL() { 7 | return ( 8 |
9 | 10 |
11 | EmberQL is an npm module made for applications that utilize GraphQL. 12 | When incorporating caching for GraphQL, devs are often pigeon-holed into 13 | using massive modules like Apollo, adding huge overhead to an otherwise 14 | small application. Under/overfetching is a large complaint in the 15 | RestfulAPI developer community. We address this as well. 16 |
17 |
18 | 25 |
26 |
27 | 28 | 31 | 32 | 33 | 34 | 37 | 38 |
39 |
40 | ); 41 | } 42 | 43 | export default EmberQL; 44 | -------------------------------------------------------------------------------- /demo/client/components/EmberQLStyles.css: -------------------------------------------------------------------------------- 1 | #EmberQL { 2 | color: #082032; 3 | align-items: center; 4 | display: flex; 5 | flex-direction: column; 6 | background-color: #082032; 7 | padding: 10vh 10vw; 8 | } 9 | #icon { 10 | width: 450px; 11 | height: 450px; 12 | } 13 | #Title { 14 | color: whitesmoke; 15 | font-family: 'Red Hat Display', sans-serif; 16 | font-size: 8vw; 17 | font-weight: 600; 18 | } 19 | #Paragraph { 20 | width: 50vw; 21 | color: rgb(196, 196, 196); 22 | font-style: italic; 23 | padding-bottom: 4vw; 24 | font-family: 'Red Hat Display', sans-serif; 25 | font-size: 14px; 26 | } 27 | #Bullets { 28 | color: whitesmoke; 29 | font-weight: bold; 30 | font-family: 'Red Hat Display', sans-serif; 31 | font-size: 16px; 32 | } 33 | button { 34 | width: 8em; 35 | height: 3em; 36 | border-radius: 8px; 37 | color: whitesmoke; 38 | font-weight: bolder; 39 | font-family: 'Red Hat Display', sans-serif; 40 | margin-top: 10%; 41 | margin-bottom: 10%; 42 | } 43 | 44 | .button { 45 | margin: 20px; 46 | } 47 | #button1 { 48 | background-color: #ff4c29; 49 | } 50 | #button2 { 51 | border: 2px solid whitesmoke; 52 | background-color: #082032; 53 | color: whitesmoke; 54 | } 55 | @media (max-width: 900px) { 56 | #icon { 57 | width: 300px; 58 | height: 300px; 59 | } 60 | } 61 | @media (min-width: 1100px) { 62 | #icon { 63 | width: 550px; 64 | height: 550px; 65 | } 66 | } 67 | -------------------------------------------------------------------------------- /demo/client/components/Feature.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | 3 | interface FeatureProps { 4 | img: any; 5 | description: string; 6 | } 7 | 8 | const Feature = function ({ img, description }: FeatureProps) { 9 | return ( 10 |
11 | 12 |

{description}

13 |
14 | ); 15 | }; 16 | 17 | export default Feature; 18 | -------------------------------------------------------------------------------- /demo/client/components/Features.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import Feature from './Feature'; 3 | import './FeaturesStyles.css'; 4 | import redis from './assets/redis.png'; 5 | import heart from './assets/heart.png'; 6 | import buttons from './assets/buttons.png'; 7 | 8 | function Features() { 9 | const featureArray = [ 10 | { 11 | img: redis, 12 | description: `Server-side caching leveraging Redis to store query responses in memory, drastically decreasing query times and reducing queries made to the database`, 13 | }, 14 | { 15 | img: heart, 16 | description: `Primary database heartbeat detection - server status monitoring with in-terminal reporting with customizable checking intervals`, 17 | }, 18 | { 19 | img: buttons, 20 | description: `Dynamic cache invalidation, using our database monitoring to determine and extend the lifetime of keys and value stored in memory based on the primary database status`, 21 | }, 22 | ]; 23 | 24 | return ( 25 |
26 |

FEATURES

27 |
28 | {featureArray.map((el, i) => ( 29 | 34 | ))} 35 |
36 |
37 | ); 38 | } 39 | 40 | export default Features; 41 | -------------------------------------------------------------------------------- /demo/client/components/FeaturesStyles.css: -------------------------------------------------------------------------------- 1 | .features { 2 | background-color: #2c394b; 3 | display: flex; 4 | flex-direction: column; 5 | align-items: center; 6 | padding: 15vh 10vw; 7 | } 8 | 9 | .feature-container { 10 | display: flex; 11 | flex-direction: row; 12 | align-items: center; 13 | } 14 | 15 | .feature-description { 16 | margin: 5vh 1vw; 17 | } 18 | 19 | .single-feature { 20 | display: flex; 21 | flex-direction: column; 22 | align-items: center; 23 | text-align: center; 24 | font-family: 'Red Hat Display', sans-serif; 25 | padding: 5vh 3vw; 26 | } 27 | 28 | .feature-img { 29 | border-radius: 50%; 30 | box-shadow: 5px 7px 14px black; 31 | height: 300px; 32 | width: 300px; 33 | /* max-width: 100%; */ 34 | /* width: auto; 35 | height: auto; */ 36 | } 37 | 38 | @media (max-width: 900px) { 39 | .feature-container { 40 | flex-direction: column; 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /demo/client/components/LandingContainer.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import EmberQL from './EmberQL'; 3 | import Navbar from './Navbar'; 4 | import Features from './Features'; 5 | import WhyWeExist from './WhyWeExist'; 6 | import Contributing from './Contributing'; 7 | 8 | function LandingContainer() { 9 | return ( 10 |
11 | 12 | 13 | 14 | 15 |
16 | ); 17 | } 18 | 19 | export default LandingContainer; 20 | -------------------------------------------------------------------------------- /demo/client/components/NavStyles.css: -------------------------------------------------------------------------------- 1 | nav { 2 | background-color: #082032; 3 | padding: 0; 4 | margin: 0; 5 | padding: 1vw; 6 | display: flex; 7 | flex-direction: row; 8 | align-items: center; 9 | justify-content: space-evenly; 10 | border: 0.3vw solid #ff4c29; 11 | /* position: fixed; */ 12 | height: 7vh; 13 | width: 99vw; 14 | } 15 | .nav-el { 16 | color: white; 17 | font-family: 'Red Hat Display', sans-serif; 18 | font-weight: 800; 19 | display: flex; 20 | font-size: 2vw; 21 | } 22 | @media (max-width: 900px) { 23 | .nav-el { 24 | font-size: large; 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /demo/client/components/Navbar.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { Link } from 'react-router-dom'; 3 | import './NavStyles.css'; 4 | 5 | function Navbar() { 6 | return ( 7 | 21 | ); 22 | } 23 | 24 | export default Navbar; 25 | -------------------------------------------------------------------------------- /demo/client/components/Team.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import TeamMember from './TeamMember'; 3 | import './TeamStyles.css'; 4 | import cristian from './assets/cristian.png'; 5 | import mike from './assets/mike.png'; 6 | import manju from './assets/manju.png'; 7 | import tyler from './assets/tyler.png'; 8 | import ram from './assets/ram.png'; 9 | 10 | function Team() { 11 | const teamArray = [ 12 | { 13 | img: cristian, 14 | description: `Christian De Los Rios`, 15 | github: 'https://github.com/Cristian-DeLosRios', 16 | linkedin: 'https://www.linkedin.com/in/cristian-de-los-rios-600875b2/', 17 | }, 18 | { 19 | img: mike, 20 | description: `Mike Masatsugu`, 21 | github: 'https://github.com/mikemasatsugu', 22 | linkedin: 'https://www.linkedin.com/in/michael-masatsugu/', 23 | }, 24 | { 25 | img: manju, 26 | description: `Manjunath Pattanashetty`, 27 | github: 'https://github.com/manjunathap85', 28 | linkedin: 'https://www.linkedin.com/in/manjunath-pattanashetty-711b6911/', 29 | }, 30 | { 31 | img: ram, 32 | description: `Ram Marimuthu`, 33 | github: 'https://github.com/rammarimuthu', 34 | linkedin: 'https://www.linkedin.com/in/ram-marimuthu/', 35 | }, 36 | { 37 | img: tyler, 38 | description: `Tyler Pohn`, 39 | github: 'https://github.com/tylerpohn', 40 | linkedin: 'https://www.linkedin.com/in/tylerpohn/', 41 | }, 42 | ]; 43 | return ( 44 |
45 |

Meet The Team

46 | 47 |
48 | 55 | 62 | 69 |
70 |
71 | 78 | 85 |
86 |
87 | ); 88 | } 89 | 90 | export default Team; 91 | { 92 | /*
93 | {teamArray.map((el, i) => ( 94 | 99 | ))} 100 |
*/ 101 | } 102 | -------------------------------------------------------------------------------- /demo/client/components/TeamMember.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import './TeamStyles.css'; 3 | interface TeamMemberProps { 4 | img: any; 5 | description: string; 6 | github: string; 7 | linkedin: string; 8 | } 9 | 10 | const TeamMember = function ({ 11 | img, 12 | description, 13 | github, 14 | linkedin, 15 | }: TeamMemberProps) { 16 | return ( 17 |
18 | 19 |

{description}

20 | 21 | Github 22 | 23 | 24 | Linkedin 25 | 26 |
27 | ); 28 | }; 29 | 30 | export default TeamMember; 31 | -------------------------------------------------------------------------------- /demo/client/components/TeamStyles.css: -------------------------------------------------------------------------------- 1 | #section-title-team { 2 | font-size: 7vw; 3 | font-family: 'Red Hat Display', sans-serif; 4 | font-weight: 500; 5 | } 6 | 7 | .team-container { 8 | /* background-color: #082032; 9 | width: 99vw; 10 | height: 99vh; 11 | margin: 0; 12 | display: flex; 13 | flex-direction: column; 14 | align-items: center; 15 | justify-content: space-evenly; 16 | margin-top: 25%; 17 | font-family: 'Red Hat Display', sans-serif; */ 18 | background-color: #082032; 19 | display: flex; 20 | flex-direction: column; 21 | align-items: center; 22 | margin-bottom: 50px; 23 | } 24 | 25 | .team-1 { 26 | display: flex; 27 | align-items: center; 28 | justify-content: space-evenly; 29 | } 30 | 31 | .team-2 { 32 | display: flex; 33 | align-items: center; 34 | justify-content: space-between; 35 | } 36 | 37 | .single-member { 38 | display: flex; 39 | flex-direction: column; 40 | align-items: center; 41 | justify-content: space-around; 42 | text-align: center; 43 | margin: 4vw; 44 | } 45 | 46 | .member-img { 47 | border-radius: 50%; 48 | margin-bottom: 10px; 49 | box-shadow: 5px 7px 14px black; 50 | height: 300px; 51 | width: 300px; 52 | } 53 | 54 | .member-description { 55 | font-size: large; 56 | } 57 | .team-link { 58 | font-size: medium; 59 | font-family: 'Red Hat Display', sans-serif; 60 | color: #ff4c29; 61 | text-decoration: none; 62 | } 63 | /* img { 64 | height: 300px; 65 | width: 300px; 66 | } */ 67 | @media (max-width: 1075px) { 68 | .member-description { 69 | font-size: large; 70 | } 71 | .team-1 { 72 | flex-direction: column; 73 | } 74 | .team-2 { 75 | flex-direction: column; 76 | } 77 | } 78 | -------------------------------------------------------------------------------- /demo/client/components/WhyReason.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | 3 | interface ReasonProps { 4 | img: any; 5 | description: string; 6 | style: React.CSSProperties; 7 | } 8 | 9 | const WhyReason = function ({ img, description, style }: ReasonProps) { 10 | return ( 11 |
12 | 13 |

{description}

14 |
15 | ); 16 | }; 17 | 18 | export default WhyReason; 19 | -------------------------------------------------------------------------------- /demo/client/components/WhyStyles.css: -------------------------------------------------------------------------------- 1 | .why { 2 | display: flex; 3 | align-items: center; 4 | flex-direction: column; 5 | justify-content: center; 6 | padding: 10vh 10vw; 7 | } 8 | 9 | .reason-container { 10 | display: flex; 11 | flex-direction: column; 12 | margin: 10vh 0; 13 | } 14 | .single-reason { 15 | align-items: center; 16 | display: flex; 17 | flex-direction: row; 18 | margin-left: 4vw; 19 | margin-right: 4vw; 20 | } 21 | .reason-img { 22 | border-radius: 50%; 23 | box-shadow: 5px 7px 14px black; 24 | display: flex; 25 | height: 300px; 26 | width: 300px; 27 | } 28 | .reason-description { 29 | font-family: 'Red Hat Display', sans-serif; 30 | margin-left: 4vw; 31 | margin-right: 4vw; 32 | display: flex; 33 | } 34 | 35 | @media (max-width: 900px) { 36 | .single-reason { 37 | flex-direction: column !important; 38 | } 39 | .reason-img { 40 | margin-top: 40px; 41 | height: 300px; 42 | width: 300px; 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /demo/client/components/WhyWeExist.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import WhyReason from './WhyReason'; 3 | import './WhyStyles.css'; 4 | import BloatedA from './assets/BloatedA.png'; 5 | import graphql from './assets/graphql.png'; 6 | import shield from './assets/shield.png'; 7 | 8 | function WhyWeExist() { 9 | const reasonArray = [ 10 | { 11 | img: graphql, 12 | description: `GraphQL Lacks Caching. This means users of GraphQL applications will 13 | experience latencies of 1000+ ms for every query to the database.`, 14 | }, 15 | { 16 | img: BloatedA, 17 | description: `Apollo is bloated. Installing Apollo will add over half a million 18 | files to your node_modules folder. This hinders performance and increases application overhead.`, 19 | }, 20 | { 21 | img: shield, 22 | description: `Data Safety. With standard GraphQL implementations, there is no way to identify 23 | database downtime in a timely manner. EmberQL implements a heartbeat feature that monitors the database 24 | and halts cache invalidation when downtime occurs. Users can continue to access the most important data 25 | even before the database is up again.`, 26 | }, 27 | ]; 28 | 29 | return ( 30 |
31 |

WHY WE EXIST

32 |
33 | {reasonArray.map((el, i) => ( 34 | 44 | ))} 45 | {/* {reasonArray.map((el, i) => ( 46 | 51 | ) 52 | )} */} 53 |
54 |
55 | ); 56 | } 57 | 58 | export default WhyWeExist; 59 | -------------------------------------------------------------------------------- /demo/client/components/assets/BloatedA.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/oslabs-beta/EmberQL/38e2f0eeca781efa70a607dc27284ac2b105142f/demo/client/components/assets/BloatedA.png -------------------------------------------------------------------------------- /demo/client/components/assets/buttons.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/oslabs-beta/EmberQL/38e2f0eeca781efa70a607dc27284ac2b105142f/demo/client/components/assets/buttons.png -------------------------------------------------------------------------------- /demo/client/components/assets/cristian.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/oslabs-beta/EmberQL/38e2f0eeca781efa70a607dc27284ac2b105142f/demo/client/components/assets/cristian.png -------------------------------------------------------------------------------- /demo/client/components/assets/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/oslabs-beta/EmberQL/38e2f0eeca781efa70a607dc27284ac2b105142f/demo/client/components/assets/favicon.ico -------------------------------------------------------------------------------- /demo/client/components/assets/graphql.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/oslabs-beta/EmberQL/38e2f0eeca781efa70a607dc27284ac2b105142f/demo/client/components/assets/graphql.png -------------------------------------------------------------------------------- /demo/client/components/assets/heart.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/oslabs-beta/EmberQL/38e2f0eeca781efa70a607dc27284ac2b105142f/demo/client/components/assets/heart.png -------------------------------------------------------------------------------- /demo/client/components/assets/icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/oslabs-beta/EmberQL/38e2f0eeca781efa70a607dc27284ac2b105142f/demo/client/components/assets/icon.png -------------------------------------------------------------------------------- /demo/client/components/assets/manju.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/oslabs-beta/EmberQL/38e2f0eeca781efa70a607dc27284ac2b105142f/demo/client/components/assets/manju.png -------------------------------------------------------------------------------- /demo/client/components/assets/mike.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/oslabs-beta/EmberQL/38e2f0eeca781efa70a607dc27284ac2b105142f/demo/client/components/assets/mike.png -------------------------------------------------------------------------------- /demo/client/components/assets/ram.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/oslabs-beta/EmberQL/38e2f0eeca781efa70a607dc27284ac2b105142f/demo/client/components/assets/ram.png -------------------------------------------------------------------------------- /demo/client/components/assets/redis.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/oslabs-beta/EmberQL/38e2f0eeca781efa70a607dc27284ac2b105142f/demo/client/components/assets/redis.png -------------------------------------------------------------------------------- /demo/client/components/assets/shield.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/oslabs-beta/EmberQL/38e2f0eeca781efa70a607dc27284ac2b105142f/demo/client/components/assets/shield.png -------------------------------------------------------------------------------- /demo/client/components/assets/tyler.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/oslabs-beta/EmberQL/38e2f0eeca781efa70a607dc27284ac2b105142f/demo/client/components/assets/tyler.png -------------------------------------------------------------------------------- /demo/client/components/demo-components/DemoContainer.tsx: -------------------------------------------------------------------------------- 1 | import React, { useState } from 'react'; 2 | import QueryContainer from './QueryContainer'; 3 | import GraphContainer from './GraphContainer'; 4 | 5 | function DemoContainer() { 6 | const [timesArray, setTimesArray] = useState([]); 7 | 8 | return ( 9 |
10 | 11 | 12 |
13 | ); 14 | } 15 | 16 | 17 | export default DemoContainer; 18 | 19 | -------------------------------------------------------------------------------- /demo/client/components/demo-components/GraphContainer.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { 3 | Chart as ChartJS, 4 | CategoryScale, 5 | LinearScale, 6 | PointElement, 7 | LineElement, 8 | Title, 9 | Tooltip, 10 | Legend, 11 | ChartOptions, 12 | } from 'chart.js'; 13 | 14 | ChartJS.register( 15 | CategoryScale, 16 | LinearScale, 17 | PointElement, 18 | LineElement, 19 | Title, 20 | Tooltip, 21 | Legend, 22 | 23 | ); 24 | import { Line } from 'react-chartjs-2'; 25 | 26 | interface GraphContainerProps { 27 | timesArray: number[]; 28 | } 29 | 30 | function GraphContainer( { timesArray } : GraphContainerProps) { 31 | const data = { 32 | labels: timesArray.map((el, i) => `Query ${i + 1}`), 33 | datasets: [ 34 | { 35 | label: 'Query', 36 | data: timesArray, 37 | borderColor: '#FF4C29', 38 | backgroundColor: 'rgba(255, 99, 132, 0.5)', 39 | }, 40 | ], 41 | }; 42 | 43 | // use ChartOptions to change the default font size to 20 44 | const options : any = { 45 | legend: { 46 | labels: { 47 | font: { 48 | size: 24, 49 | }, 50 | }, 51 | }, 52 | scales: { 53 | x: { 54 | ticks: { 55 | font: { 56 | size: 24, 57 | }, 58 | }, 59 | }, 60 | y: { 61 | ticks: { 62 | font: { 63 | size: 24, 64 | }, 65 | }, 66 | }, 67 | }, 68 | }; 69 | 70 | return ( 71 |
72 | 73 |
74 | ); 75 | } 76 | 77 | export default GraphContainer; 78 | -------------------------------------------------------------------------------- /demo/client/components/demo-components/QueryContainer.tsx: -------------------------------------------------------------------------------- 1 | /* eslint-disable no-console */ 2 | import React, { useState, useEffect } from 'react'; 3 | 4 | // import schema from '../../server/schema/schema'; 5 | // const schema = require('../server/schema/schema'); 6 | 7 | interface QueryContainerProps { 8 | setTimesArray: React.Dispatch>; 9 | timesArray: number[]; 10 | } 11 | 12 | const QueryContainer = function ({ 13 | setTimesArray, 14 | timesArray, 15 | }: QueryContainerProps) { 16 | const [query, setQuery] = useState(''); 17 | const [selectedQuery, setSelectedQuery] = useState('selection1'); 18 | const [incomingData, setIncomingData] = useState(''); 19 | 20 | const clearGraph = () => { 21 | setTimesArray([]); 22 | setIncomingData(''); 23 | }; 24 | 25 | const clearCache = () => { 26 | fetch('/clearcache', { 27 | method: 'GET', 28 | }); 29 | setIncomingData(''); 30 | }; 31 | 32 | const sampleQuery1 = `query { 33 | books { 34 | title 35 | authors{\n\tname\n\tcountry\n } 36 | genre{\n\tname\n } 37 | } 38 | }`; 39 | 40 | const sampleQuery2 = `query { 41 | authors { 42 | name 43 | } 44 | }`; 45 | 46 | const sampleQuery3 = `query { 47 | books { 48 | authors{\n\tname\n } 49 | } 50 | }`; 51 | 52 | useEffect(() => { 53 | const inputField = document.getElementById( 54 | 'query-input', 55 | ) as HTMLSelectElement; 56 | 57 | if (selectedQuery === 'selection1') { 58 | inputField.value = sampleQuery1; 59 | setQuery(sampleQuery1); 60 | } 61 | if (selectedQuery === 'selection2') { 62 | inputField.value = sampleQuery2; 63 | setQuery(sampleQuery2); 64 | } 65 | if (selectedQuery === 'selection3') { 66 | inputField.value = sampleQuery3; 67 | setQuery(sampleQuery3); 68 | } 69 | }, [sampleQuery1, sampleQuery2, sampleQuery3, selectedQuery]); 70 | 71 | const submitQuery = async () => { 72 | const startTime = Date.now(); 73 | await fetch('/graphql', { 74 | method: 'POST', 75 | headers: { 76 | 'content-type': 'application/json;charset=UTF-8', 77 | }, 78 | body: JSON.stringify({ 79 | query: query, 80 | }), 81 | }) 82 | .then((res) => res.json()) 83 | .then((res) => { 84 | setTimesArray([...timesArray, Date.now() - startTime]); 85 | setIncomingData(`${JSON.stringify(res.data, null, 2)}`); 86 | }); 87 | }; 88 | 89 | return ( 90 |
91 | 102 | 105 |
106 | 114 | 122 |
123 |
124 |

Query:

125 | 131 |
132 |

Data:

133 | 140 |
141 | ); 142 | }; 143 | 144 | export default QueryContainer; 145 | -------------------------------------------------------------------------------- /demo/client/index.tsx: -------------------------------------------------------------------------------- 1 | //thread of execution starts here 2 | import React from 'react'; 3 | import ReactDOM from 'react-dom'; 4 | import { BrowserRouter } from 'react-router-dom'; 5 | import App from './App'; 6 | import './styles.css'; 7 | 8 | const root = document.createElement('div'); 9 | root.id = 'root'; 10 | document.body.appendChild(root); 11 | // render('Hi I am a string'); 12 | ReactDOM.render( 13 | 14 | 15 | , 16 | document.getElementById('root') 17 | ); 18 | -------------------------------------------------------------------------------- /demo/client/styles.css: -------------------------------------------------------------------------------- 1 | /* Background #082032 */ 2 | /* Blue-Gray Accent #2C394B */ 3 | /* Gray #334756 */ 4 | /* Ember #FF4C29 */ 5 | * { 6 | box-sizing: border-box; 7 | margin: 0px; 8 | padding: 0px; 9 | } 10 | body { 11 | background-color: #334756; 12 | color: white; 13 | font-family: sans-serif; 14 | } 15 | 16 | .demo-container { 17 | display: flex; 18 | flex-direction: row; 19 | height: 99vh; 20 | width: 97vw; 21 | } 22 | 23 | .section-title { 24 | font-size: 5vw; 25 | font-family: 'Red Hat Display', sans-serif; 26 | font-weight: 400; 27 | } 28 | 29 | .graph-container { 30 | display: flex; 31 | flex-direction: column; 32 | border: 1px black solid; 33 | margin-left: 25px; 34 | margin-right: 25px; 35 | flex: 1; 36 | padding: 20px; 37 | margin-top: 50px; 38 | background-color: #082032; 39 | max-width: 50%; 40 | max-height: 75%; 41 | margin-bottom: 5vh; 42 | } 43 | 44 | .query-container { 45 | display: flex; 46 | flex-direction: column; 47 | border: 1px black solid; 48 | margin-top: 50px; 49 | margin-left: 50px; 50 | margin-right: 50px; 51 | margin-bottom: 50px; 52 | flex: 0.5; 53 | padding: 50px; 54 | background-color: #082032; 55 | align-items: center; 56 | } 57 | 58 | #query-dropdown { 59 | width: 200px; 60 | text-align: center; 61 | font-size: 20px; 62 | } 63 | 64 | #submit-query { 65 | margin: 20px; 66 | width: 300px; 67 | border: none; 68 | background-color: #ff4c29; 69 | color: black; 70 | height: 100px; 71 | font-size: 34px; 72 | } 73 | 74 | .clear-buttons-div { 75 | display: flex; 76 | align-items: center; 77 | justify-content: space-around; 78 | padding: 20px; 79 | } 80 | 81 | .clear-btn { 82 | border: none; 83 | color: white; 84 | /* padding: 15px 32px; */ 85 | text-align: center; 86 | text-decoration: none; 87 | display: inline-block; 88 | font-size: 20px; 89 | margin: 10px; 90 | } 91 | 92 | #clear-graph { 93 | background-color: #2c394b; 94 | border: 1px solid #ff4c29; 95 | } 96 | 97 | #clear-cache { 98 | background-color: #334756; 99 | border: 1px solid #ff4c29; 100 | } 101 | 102 | .text-area { 103 | width: 75%; 104 | height: 300px; 105 | overflow: scroll; 106 | font-size: 18px; 107 | } 108 | 109 | h1 { 110 | color: red; 111 | } 112 | 113 | @media (max-width: 1200px) { 114 | .demo-container { 115 | flex-direction: column; 116 | } 117 | .graph-container { 118 | max-width: 100%; 119 | } 120 | .text-area { 121 | height: 150px; 122 | } 123 | #submit-query { 124 | width: 230px; 125 | } 126 | .query-container { 127 | padding: 20px; 128 | } 129 | .graph-container { 130 | margin-top: 25px; 131 | } 132 | } 133 | @media (max-width: 900px) { 134 | .clear-buttons-div { 135 | flex-direction: column; 136 | } 137 | } 138 | -------------------------------------------------------------------------------- /demo/d.ts: -------------------------------------------------------------------------------- 1 | declare module '*.png' { 2 | const value: any; 3 | export = value; 4 | } 5 | -------------------------------------------------------------------------------- /demo/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 8 | 9 | EmberQL 10 | 11 | 12 | 16 | 17 | 18 |
19 | 20 | 21 | 22 | -------------------------------------------------------------------------------- /demo/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "emberql", 3 | "version": "1.0.0", 4 | "description": "Demo for EmberQL", 5 | "main": "compiledTS/server/server.js", 6 | "scripts": { 7 | "lint": "eslint . --ext .ts", 8 | "start": "NODE_ENV=production node ./compiledTS/server/server", 9 | "build": "tsc && webpack", 10 | "dev": "tsc && NODE_ENV=development nodemon ./server/server.ts & webpack serve --hot", 11 | "test": "echo \"Error: no test specified\" && exit 1" 12 | }, 13 | "repository": { 14 | "type": "git", 15 | "url": "git+https://github.com/oslabs-beta/EmberQL.git" 16 | }, 17 | "keywords": [ 18 | "GraphQL", 19 | "Redis", 20 | "Cache", 21 | "optimize", 22 | "query" 23 | ], 24 | "authors": [ 25 | { 26 | "name": "Cristian De Los Rios", 27 | "url": "https://github.com/Cristian-DeLosRios" 28 | }, 29 | { 30 | "name": "Manjunath Ajjappa Pattanashetty", 31 | "url": "https://github.com/manjunathap85" 32 | }, 33 | { 34 | "name": "Mike Masatsugu", 35 | "url": "https://github.com/mikemasatsugu" 36 | }, 37 | { 38 | "name": "Ram Marimuthu", 39 | "url": "https://github.com/rammarimuthu" 40 | }, 41 | { 42 | "name": "Tyler Pohn", 43 | "url": "https://github.com/tylerpohn" 44 | } 45 | ], 46 | "license": "MIT", 47 | "dependencies": { 48 | "@babel/runtime": "^7.16.3", 49 | "@types/mini-css-extract-plugin": "^2.4.0", 50 | "@types/redis": "^4.0.11", 51 | "acorn": "^8.6.0", 52 | "chart.js": "^3.6.2", 53 | "concurrently": "^6.4.0", 54 | "cors": "^2.8.5", 55 | "dotenv": "^8.2.0", 56 | "eslint-config-airbnb-typescript": "^16.1.0", 57 | "express": "^4.17.1", 58 | "express-graphql": "^0.12.0", 59 | "graphiql": "^1.4.0", 60 | "graphql": "^15.7.2", 61 | "mini-css-extract-plugin": "^2.4.5", 62 | "node-fetch": "^2.6.6", 63 | "node-redis": "^0.1.7", 64 | "pg": "^8.7.1", 65 | "react": "^17.0.2", 66 | "react-chartjs-2": "^4.0.0", 67 | "react-dom": "^17.0.2", 68 | "react-router-dom": "^6.2.1", 69 | "redis": "^4.0.1" 70 | }, 71 | "devDependencies": { 72 | "@babel/core": "^7.16.0", 73 | "@babel/preset-env": "^7.16.4", 74 | "@babel/preset-react": "^7.10.4", 75 | "@babel/preset-typescript": "^7.16.0", 76 | "@types/cors": "^2.8.12", 77 | "@types/express": "^4.17.4", 78 | "@types/jest": "^24.0.13", 79 | "@types/mocha": "^5.2.7", 80 | "@types/node": "^16.11.9", 81 | "@types/react": "^16.7.18", 82 | "@types/react-dom": "^17.0.11", 83 | "@types/react-router-dom": "^4.3.1", 84 | "@types/webpack-env": "^1.13.9", 85 | "@typescript-eslint/eslint-plugin": "^5.4.0", 86 | "@typescript-eslint/parser": "^5.4.0", 87 | "babel-loader": "^8.2.3", 88 | "cross-env": "^7.0.3", 89 | "css-loader": "^4.2.2", 90 | "eslint": "^8.4.1", 91 | "eslint-config-airbnb": "^19.0.2", 92 | "eslint-plugin-import": "^2.25.3", 93 | "eslint-plugin-jsx-a11y": "^6.5.1", 94 | "eslint-plugin-react": "^7.27.1", 95 | "eslint-plugin-react-hooks": "^4.3.0", 96 | "file-loader": "^6.1.0", 97 | "graphiql": "^1.4.0", 98 | "html-webpack-plugin": "^5.5.0", 99 | "jest": "^26.4.2", 100 | "nodemon": "^2.0.4", 101 | "style-loader": "^1.2.1", 102 | "ts-loader": "^9.2.6", 103 | "ts-node": "^10.4.0", 104 | "typescript": "^4.5.2", 105 | "webpack": "^5.64.1", 106 | "webpack-cli": "^4.9.1", 107 | "webpack-dev-middleware": "^5.2.2", 108 | "webpack-dev-server": "^3.11.2", 109 | "webpack-hot-middleware": "^2.25.1" 110 | }, 111 | "bugs": { 112 | "url": "https://github.com/oslabs-beta/EmberQL/issues" 113 | }, 114 | "homepage": "https://github.com/oslabs-beta/EmberQL#readme" 115 | } 116 | -------------------------------------------------------------------------------- /demo/server/models/db.js: -------------------------------------------------------------------------------- 1 | const { Pool } = require('pg'); 2 | 3 | const PG_URI = 4 | 'postgres://mvilptfq:W5-KAiYVVKOgDimT4mY4-vbBzJL7pxXz@castor.db.elephantsql.com/mvilptfq'; 5 | 6 | const pool = new Pool({ 7 | connectionString: PG_URI, 8 | max: 5, 9 | }); 10 | 11 | const db = { 12 | query: function (queryString, params, callback) { 13 | // console.log(`Executed query: ${queryString}`); 14 | return pool.query(queryString, params, callback); 15 | }, 16 | }; 17 | export default db; 18 | /* 19 | https://riptutorial.com/sql/example/4978/library-database 20 | 21 | CREATE TABLE Authors ( 22 | Id INT NOT NULL AUTO_INCREMENT, 23 | Name VARCHAR(70) NOT NULL, 24 | Country VARCHAR(100) NOT NULL, 25 | PRIMARY KEY(Id) 26 | ); 27 | 28 | CREATE TABLE Books ( 29 | Id INT NOT NULL AUTO_INCREMENT, 30 | Title VARCHAR(50) NOT NULL, 31 | PRIMARY KEY(Id) 32 | ); 33 | 34 | CREATE TABLE BooksAuthors ( 35 | AuthorId INT NOT NULL, 36 | BookId INT NOT NULL, 37 | FOREIGN KEY (AuthorId) REFERENCES Authors(Id), 38 | FOREIGN KEY (BookId) REFERENCES Books(Id) 39 | ); 40 | 41 | INSERT INTO Authors 42 | (Name, Country) 43 | VALUES 44 | ('J.D. Salinger', 'USA'), 45 | ('F. Scott. Fitzgerald', 'USA'), 46 | ('Jane Austen', 'UK'), 47 | ('Scott Hanselman', 'USA'), 48 | ('Jason N. Gaylord', 'USA'), 49 | ('Pranav Rastogi', 'India'), 50 | ('Todd Miranda', 'USA'), 51 | ('Christian Wenz', 'USA') 52 | ; 53 | 54 | INSERT INTO Books 55 | (Id, Title) 56 | VALUES 57 | (1, 'The Catcher in the Rye'), 58 | (2, 'Nine Stories'), 59 | (3, 'Franny and Zooey'), 60 | (4, 'The Great Gatsby'), 61 | (5, 'Tender id the Night'), 62 | (6, 'Pride and Prejudice'), 63 | (7, 'Professional ASP.NET 4.5 in C# and VB') 64 | ; 65 | 66 | INSERT INTO BooksAuthors 67 | (BookId, AuthorId) 68 | VALUES 69 | (1, 1), 70 | (2, 1), 71 | (3, 1), 72 | (4, 2), 73 | (5, 2), 74 | (6, 3), 75 | (7, 4), 76 | (7, 5), 77 | (7, 6), 78 | (7, 7), 79 | (7, 8) 80 | ; 81 | */ 82 | -------------------------------------------------------------------------------- /demo/server/schema/schema.ts: -------------------------------------------------------------------------------- 1 | /* eslint-disable no-use-before-define */ 2 | /* eslint-disable @typescript-eslint/no-use-before-define */ 3 | import db from '../models/db.js'; 4 | 5 | import { 6 | GraphQLSchema, 7 | GraphQLObjectType, 8 | GraphQLList, 9 | GraphQLID, 10 | GraphQLString, 11 | GraphQLNonNull, 12 | } from 'graphql'; 13 | 14 | // =========================== // 15 | // ===== TYPE DEFINITIONS ==== // 16 | // =========================== // 17 | 18 | const BookType = new GraphQLObjectType({ 19 | name: 'Book', 20 | fields: () => ({ 21 | id: { type: GraphQLID }, 22 | title: { type: GraphQLString }, 23 | genre: { 24 | type: GenreType, 25 | async resolve(parent) { 26 | const queryString = ` 27 | SELECT * 28 | FROM genres 29 | WHERE id = ${parent.genre_id} 30 | `; 31 | const genre = await db.query(queryString); 32 | return genre.rows[0] as string; 33 | }, 34 | }, 35 | authors: { 36 | type: new GraphQLList(AuthorType), 37 | async resolve(parent) { 38 | const queryString = ` 39 | SELECT authors.* 40 | FROM ((authors 41 | INNER JOIN booksauthors ON booksAuthors.AuthorId = authors.id) 42 | INNER JOIN books ON books.id = booksAuthors.BookId) 43 | WHERE books.id = ${parent.id} 44 | `; 45 | const authors = await db.query(queryString); 46 | return authors.rows as string; 47 | }, 48 | }, 49 | }), 50 | }); 51 | 52 | const AuthorType = new GraphQLObjectType({ 53 | name: 'Author', 54 | fields: () => ({ 55 | id: { type: GraphQLID }, 56 | name: { type: GraphQLString }, 57 | country: { type: GraphQLString }, 58 | }), 59 | }); 60 | 61 | const GenreType = new GraphQLObjectType({ 62 | name: 'Genre', 63 | fields: () => ({ 64 | id: { type: GraphQLID }, 65 | name: { type: GraphQLString }, 66 | }), 67 | }); 68 | 69 | // ================== // 70 | // ===== QUERIES ==== // 71 | // ================== // 72 | 73 | const RootQuery = new GraphQLObjectType({ 74 | name: 'RootQueryType', 75 | fields: { 76 | //GET BOOK BY ID 77 | book: { 78 | type: BookType, 79 | args: { id: { type: GraphQLID } }, 80 | async resolve(parent, args) { 81 | const queryString = ` 82 | SELECT * FROM books 83 | WHERE id = $1 84 | `; 85 | const book = await db.query(queryString, [args.id]); 86 | return book.rows[0] as string; 87 | }, 88 | }, 89 | //GET ALL BOOKS 90 | books: { 91 | type: new GraphQLList(BookType), 92 | async resolve() { 93 | const queryString = ` 94 | SELECT * FROM books 95 | `; 96 | const books = await db.query(queryString); 97 | return books.rows as string; 98 | }, 99 | }, 100 | //GET AUTHOR BY ID 101 | author: { 102 | type: AuthorType, 103 | args: { id: { type: GraphQLID } }, 104 | async resolve(parent, args) { 105 | const queryString = ` 106 | SELECT * 107 | FROM authors 108 | WHERE authors.id = $1 109 | `; 110 | const author = await db.query(queryString, [args.id]); 111 | return author.rows[0] as string; 112 | }, 113 | }, 114 | //GET ALL AUTHORS 115 | authors: { 116 | type: new GraphQLList(AuthorType), 117 | async resolve() { 118 | const queryString = 'SELECT * FROM authors'; 119 | const authors = await db.query(queryString); 120 | return authors.rows as string; 121 | }, 122 | }, 123 | //GET ALL BOOKS BY AUTHOR 124 | booksByAuthor: { 125 | type: new GraphQLList(BookType), 126 | args: { name: { type: GraphQLString } }, 127 | async resolve(parent, args) { 128 | const queryString = ` 129 | SELECT books.* 130 | FROM ((books 131 | INNER JOIN booksAuthors ON books.id = booksAuthors.BookId) 132 | INNER JOIN authors ON booksAuthors.AuthorId = authors.id) 133 | WHERE authors.name = $1 134 | `; 135 | const books = await db.query(queryString, [args.name]); 136 | return books.rows as string; 137 | }, 138 | }, 139 | //GET ALL GENRES 140 | genres: { 141 | type: new GraphQLList(GenreType), 142 | async resolve() { 143 | const queryString = 'SELECT * FROM genres'; 144 | const genres = await db.query(queryString); 145 | return genres.rows as string; 146 | }, 147 | }, 148 | //GET ALL BOOKS BY GENRE 149 | booksByGenre: { 150 | type: new GraphQLList(BookType), 151 | args: { genre: { type: GraphQLString } }, 152 | async resolve(parent, args) { 153 | const queryString = ` 154 | SELECT books.* 155 | FROM books 156 | INNER JOIN genres ON books.genre_id = genres.id 157 | WHERE genres.name = $1 158 | `; 159 | const books = await db.query(queryString, [args.genre]); 160 | return books.rows as string; 161 | }, 162 | }, 163 | }, 164 | }); 165 | 166 | // ================== // 167 | // ===== MUTATIONS ==== // 168 | // ================== // 169 | 170 | const RootMutation = new GraphQLObjectType({ 171 | name: 'RootMutationType', 172 | fields: { 173 | //ADD BOOK 174 | addBook: { 175 | type: BookType, 176 | args: { 177 | title: { type: new GraphQLNonNull(GraphQLString) }, 178 | author: { type: new GraphQLNonNull(GraphQLString) }, 179 | genre: { type: GraphQLString }, 180 | }, 181 | async resolve(parent, args) { 182 | const author = await db.query( 183 | `SELECT id FROM authors WHERE name = '${args.author}'`, 184 | ); 185 | const genre = await db.query( 186 | `SELECT id FROM genres WHERE genres.name = '${args.genre}'`, 187 | ); 188 | 189 | //const authorId: string = author.rows[0].id; 190 | //const genreId: string = genre.rows[0].id; 191 | const authorID = author.rows[0].id; 192 | const genreID = genre.rows[0].id; 193 | const queryString = ` 194 | INSERT INTO books (title, genre_id) 195 | VALUES ($1, $2) 196 | RETURNING *`; 197 | const bookResponse = await db.query(queryString, [args.title, genreID]); 198 | const newBook = bookResponse.rows[0]; 199 | newBook.author = args.author; 200 | newBook.genre = args.genre; 201 | const bookID = newBook.id; 202 | const joinTableQueryString = ` 203 | INSERT INTO booksauthors (bookid, authorid) 204 | VALUES ($1, $2) 205 | `; 206 | await db.query(joinTableQueryString, [bookID, authorID]); 207 | return newBook as string; 208 | }, 209 | }, 210 | //UPDATE BOOK 211 | updateBook: { 212 | type: BookType, 213 | args: { 214 | id: { type: new GraphQLNonNull(GraphQLID) }, 215 | title: { type: GraphQLString }, 216 | author: { type: GraphQLString }, 217 | genre: { type: GraphQLString }, 218 | }, 219 | async resolve(parent, args) { 220 | if (args.author) { 221 | const authorQueryString = ` 222 | UPDATE booksauthors 223 | SET booksauthors.authorID = authors.id 224 | FROM booksauthors 225 | INNER JOIN authors ON booksauthors.authorID = authors.id 226 | WHERE booksauthors.bookID = ${args.id} AND authors.name = '${args.author}' 227 | `; 228 | await db.query(authorQueryString); 229 | } 230 | let bookQueryString = ` 231 | SELECT * FROM books 232 | WHERE books.id = ${args.id} 233 | `; 234 | const toUpdate = []; 235 | if (args.genre) 236 | toUpdate.push( 237 | `genre_id = (SELECT id FROM genres WHERE genres.name = '${args.genre}')`, 238 | ); 239 | if (args.title) toUpdate.push(`title = '${args.title}'`); 240 | if (toUpdate.length > 0) { 241 | bookQueryString = ` 242 | UPDATE books 243 | SET ${toUpdate.join()} 244 | WHERE id = ${args.id} 245 | RETURNING * 246 | `; 247 | } 248 | const updatedBook = await db.query(bookQueryString); 249 | return updatedBook.rows[0] as string; 250 | }, 251 | }, 252 | //DELETE BOOK 253 | deleteBook: { 254 | type: BookType, 255 | args: { 256 | id: { type: new GraphQLNonNull(GraphQLID) }, 257 | }, 258 | async resolve(parent, args) { 259 | const queryString = ` 260 | DELETE FROM books 261 | WHERE id = ${args.id} 262 | RETURNING * 263 | `; 264 | await db.query(`DELETE FROM booksauthors WHERE bookid = ${args.id}`); 265 | const deleted = await db.query(queryString); 266 | return deleted.rows[0] as string; 267 | }, 268 | }, 269 | //ADD AUTHOR 270 | addAuthor: { 271 | type: AuthorType, 272 | args: { 273 | name: { type: new GraphQLNonNull(GraphQLString) }, 274 | country: { type: new GraphQLNonNull(GraphQLString) }, 275 | }, 276 | async resolve(parent, args) { 277 | const queryString = ` 278 | INSERT INTO authors (name, country) 279 | VALUES ($1, $2) 280 | RETURNING * 281 | `; 282 | const newAuthor = await db.query(queryString, [ 283 | args.name, 284 | args.country, 285 | ]); 286 | return newAuthor.rows[0] as string; 287 | }, 288 | }, 289 | //UPDATE AUTHOR 290 | updateAuthor: { 291 | type: AuthorType, 292 | args: { 293 | id: { type: new GraphQLNonNull(GraphQLID) }, 294 | name: { type: GraphQLString }, 295 | country: { type: GraphQLString }, 296 | }, 297 | async resolve(parent, args) { 298 | const toUpdate = []; 299 | if (args.name) toUpdate.push(`name='${args.name}'`); 300 | if (args.country) toUpdate.push(`country='${args.country}'`); 301 | const queryString = ` 302 | UPDATE authors 303 | SET ${toUpdate.join()} 304 | WHERE authors.id = ${args.id} 305 | RETURNING * 306 | `; 307 | const updatedAuthor = await db.query(queryString); 308 | return updatedAuthor.rows[0] as string; 309 | }, 310 | }, 311 | //DELETE AUTHOR 312 | deleteAuthor: { 313 | type: AuthorType, 314 | args: { 315 | name: { type: GraphQLString }, 316 | }, 317 | async resolve(parent, args) { 318 | const queryString = ` 319 | DELETE FROM authors 320 | WHERE name = '${args.name}' 321 | RETURNING * 322 | `; 323 | const deletedAuthor = await db.query(queryString); 324 | return deletedAuthor.rows[0] as string; 325 | }, 326 | }, 327 | //ADD GENRE 328 | addGenre: { 329 | type: GenreType, 330 | args: { 331 | name: { type: new GraphQLNonNull(GraphQLString) }, 332 | }, 333 | async resolve(parent, args) { 334 | const queryString = ` 335 | INSERT INTO genres (name) 336 | VALUES ($1) 337 | RETURNING * 338 | `; 339 | const newGenre = await db.query(queryString, [args.name]); 340 | return newGenre.rows[0] as string; 341 | }, 342 | }, 343 | //DELETE GENRE 344 | deleteGenre: { 345 | type: GenreType, 346 | args: { 347 | name: { type: GraphQLString }, 348 | }, 349 | async resolve(parent, args) { 350 | const queryString = ` 351 | DELETE FROM genres 352 | WHERE name = '${args.name}' 353 | RETURNING * 354 | `; 355 | const deletedGenre = await db.query(queryString); 356 | return deletedGenre.rows[0] as string; 357 | }, 358 | }, 359 | }, 360 | }); 361 | 362 | export default new GraphQLSchema({ 363 | query: RootQuery, 364 | mutation: RootMutation, 365 | types: [BookType, AuthorType, GenreType], 366 | }); 367 | -------------------------------------------------------------------------------- /demo/server/server.ts: -------------------------------------------------------------------------------- 1 | /* eslint-disable no-console */ 2 | import path from 'path'; 3 | import express, { Request, Response } from 'express'; 4 | import { graphqlHTTP } from 'express-graphql'; 5 | import schema from './schema/schema'; 6 | import cors from 'cors'; 7 | const app = express(); 8 | const PORT = process.env.PORT || 3000; 9 | import EmberQL from '../EmberQL'; 10 | app.use(cors()); 11 | 12 | const redis = require('redis'); 13 | const redisCache = redis.createClient({ 14 | url: 'redis://emberqluser:Emberql@123@redis-18883.c270.us-east-1-3.ec2.cloud.redislabs.com:18883', 15 | }); 16 | redisCache.connect(); 17 | redisCache.on('connect', () => { 18 | console.log('Connected to Redis cache'); 19 | }); 20 | 21 | const Ember = new EmberQL(schema, redisCache); 22 | const EmberHeartbeat = Ember.heartbeat; 23 | 24 | app.use(express.json()); 25 | // statically serve everything in the build folder on the route '/build' 26 | console.log( 27 | 'Should print MinifiedUglified build:', 28 | path.resolve(__dirname, '../build') 29 | ); 30 | 31 | //eslint-disable-next-line @typescript-eslint/no-misused-promises 32 | app.use('/graphql', Ember.handleQuery, (req, res) => { 33 | res.status(202).json(res.locals.data); 34 | }); 35 | 36 | app.use('/clearCache', Ember.clearCache, (req, res) => { 37 | res.sendStatus(202); 38 | }); 39 | 40 | app.use('/heartbeat', graphqlHTTP({ schema })); 41 | 42 | //app.use('/build', express.static(path.resolve(__dirname, './build'))); 43 | app.use(express.static('build')); 44 | //app.use('/', express.static(path.resolve(__dirname, './client'))); 45 | // app.use('/', (req: Request, res: Response) => { 46 | // return res.status(200).sendFile(path.join(__dirname, '../client/index.tsx')); 47 | // }); 48 | 49 | // serve index.html on the route '/' 50 | //express.static is replacing the following: (because html gets bundled) 51 | app.get('/*', (req: Request, res: Response) => 52 | res.status(200).sendFile(path.join(__dirname, '../../build/index.html')) 53 | ); 54 | 55 | app.listen(PORT); //listens on port 3000 -> http://localhost:3000/ 56 | console.log(`Listening on port ${PORT}...`); 57 | 58 | // Commenting temporarily during website build (28/12/2021) 59 | // setInterval(() => { 60 | // EmberHeartbeat(); 61 | // }, 3000); 62 | -------------------------------------------------------------------------------- /demo/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | /* Visit https://aka.ms/tsconfig.json to read more about this file */ 4 | 5 | "outDir": "./compiledTS", 6 | "rootDir": "./", 7 | "target": "es2016" /* Set the JavaScript language version for emitted JavaScript and include compatible library declarations. */, 8 | "module": "commonjs" /* Specify what module code is generated. */, 9 | "esModuleInterop": true /* Emit additional JavaScript to ease support for importing CommonJS modules. This enables `allowSyntheticDefaultImports` for type compatibility. */, 10 | "forceConsistentCasingInFileNames": true /* Ensure that casing is correct in imports. */, 11 | "strict": true /* Enable all strict type-checking options. */, 12 | "skipLibCheck": true /* Skip type checking all .d.ts files. */, 13 | "allowSyntheticDefaultImports": true, 14 | "jsx": "react" /* Specify JSX code generation: 'preserve', 'react', or 'react-native'. */, 15 | "allowJs": true /* Allow JavaScript files to be compiled. */, 16 | "moduleResolution": "node" /* Specify module resolution strategy: 'node' (Node.js) or 'classic' (TypeScript pre-1.6). "node" means compile to commonJS*/, 17 | "noImplicitAny": true, 18 | "isolatedModules": false, 19 | //"noEmit": true, 20 | "resolveJsonModule": true, 21 | "removeComments": true //zip emberql.zip -r * .[^.]* 22 | }, 23 | "exclude": ["node_modules", "build", "compiledTS", "__tests__"], 24 | "include": [ 25 | "client/**/*.tsx", 26 | "server/**/*.ts", 27 | "../../src/**/*.ts", 28 | "./d.ts" 29 | ] 30 | } 31 | -------------------------------------------------------------------------------- /demo/webpack.config.js: -------------------------------------------------------------------------------- 1 | const path = require('path'); 2 | const HtmlWebpackPlugin = require('html-webpack-plugin'); 3 | const MiniCssExtractPlugin = require('mini-css-extract-plugin'); 4 | 5 | module.exports = { 6 | mode: process.env.NODE_ENV, 7 | entry: './client/index.tsx', 8 | //devtool: 'source-map', 9 | //mode: "development", 10 | output: { 11 | path: path.resolve(__dirname, 'build'), 12 | filename: 'bundle.js', 13 | //publicPath: '/', 14 | }, 15 | devServer: { 16 | port: 8080, 17 | // publicPath: '/build/', 18 | host: 'localhost', 19 | contentBase: path.resolve(__dirname, '/compiledTS/client'), 20 | 21 | proxy: { 22 | '/**': 'http://localhost:3000/', 23 | }, 24 | hot: true, 25 | historyApiFallback: true, 26 | watchContentBase: true, 27 | watchOptions: { 28 | ignored: /node_modules/, 29 | }, 30 | }, 31 | //entry: path.resolve(__dirname, './client/index.tsx'), 32 | module: { 33 | rules: [ 34 | { 35 | test: /\.(ts|tsx)?$/, 36 | exclude: /node_modules/, 37 | loader: 'ts-loader', 38 | }, 39 | 40 | { 41 | test: /\.(js|jsx)?$/, 42 | exclude: /node_modules/, 43 | use: { 44 | loader: 'babel-loader', 45 | options: { 46 | presets: ['@babel/preset-react', '@babel/preset-env'], 47 | }, 48 | }, 49 | }, 50 | { 51 | test: /\.css$/i, 52 | //exclude: /node_modules/, 53 | include: path.resolve(__dirname, 'client'), 54 | use: ['style-loader', 'css-loader'], 55 | }, 56 | { 57 | test: /.(png|svg|jpg|gif|woff|ico|woff2|eot|ttf|otf)$/, 58 | use: ['file-loader'], 59 | }, 60 | ], 61 | }, 62 | resolve: { 63 | extensions: ['.js', '.ts', '.tsx'], 64 | }, 65 | plugins: [ 66 | new HtmlWebpackPlugin({ 67 | filename: 'index.html', 68 | title: 'EmberQL', 69 | favicon: "./client/components/assets/favicon.ico" 70 | }), 71 | new MiniCssExtractPlugin({ 72 | filename: 'styles.css', 73 | chunkFilename: 'styles.css', 74 | }), 75 | ], 76 | }; 77 | -------------------------------------------------------------------------------- /src/EmberQL.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) { 3 | function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); } 4 | return new (P || (P = Promise))(function (resolve, reject) { 5 | function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } } 6 | function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } } 7 | function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); } 8 | step((generator = generator.apply(thisArg, _arguments || [])).next()); 9 | }); 10 | }; 11 | Object.defineProperty(exports, "__esModule", { value: true }); 12 | const graphql_1 = require("../demo/node_modules/graphql"); 13 | const redis = require('redis'); 14 | const fetch = require('node-fetch'); 15 | class EmberQL { 16 | constructor(schema, redisCache) { 17 | this.handleQuery = this.handleQuery.bind(this); 18 | this.clearCache = this.clearCache.bind(this); 19 | this.heartbeat = this.heartbeat.bind(this); 20 | this.increaseTTL = this.increaseTTL.bind(this); 21 | this.graphQLQuery = ''; 22 | this.schema = schema; 23 | this.redisCache = redisCache; 24 | } 25 | handleQuery(req, res, next) { 26 | return __awaiter(this, void 0, void 0, function* () { 27 | this.graphQLQuery = req.body.query; 28 | if (yield this.redisCache.exists(this.graphQLQuery)) { 29 | const response = yield this.redisCache.get(this.graphQLQuery); 30 | res.locals.data = JSON.parse(response); 31 | return next(); 32 | } 33 | else { 34 | const results = yield (0, graphql_1.graphql)({ 35 | schema: this.schema, 36 | source: this.graphQLQuery, 37 | }); 38 | this.redisCache.set(this.graphQLQuery, JSON.stringify(results)); 39 | res.locals.data = results; 40 | return next(); 41 | } 42 | }); 43 | } 44 | heartbeat() { 45 | console.log('enter heartbeat'); 46 | fetch('http://localhost:3000/heartbeat', { 47 | method: 'POST', 48 | headers: { 49 | 'content-type': 'application/json;charset=UTF-8', 50 | }, 51 | body: JSON.stringify({ 52 | query: '{book(id:1){id}}', 53 | }), 54 | }) 55 | .then((data) => data.json()) 56 | .then((data) => { 57 | const date = new Date(); 58 | // console.log('data:', data); 59 | if (data.errors) { 60 | for (const error of data.errors) { 61 | // console.log(error.message.slice(0, 20)); 62 | if (error.message.slice(0, 20) === 'connect ECONNREFUSED') { 63 | console.log(`Heartbeat FAILED, Cache invalidation halted. ${date}`); 64 | this.increaseTTL(); 65 | } 66 | } 67 | } 68 | else { 69 | console.log(`Heartbeat OK, ${date}`); 70 | } 71 | }) 72 | .catch((err) => { 73 | console.log('entered catch'); 74 | if (err.code === 'ECONNREFUSED') 75 | this.increaseTTL(); 76 | }); 77 | } 78 | // heartbeat() { 79 | // console.log('enter heartbeat'); 80 | // graphql(this.schema, '{books{title}}') 81 | // // .then((data: any) => data.json()) 82 | // .then((data: any) => console.log(data)) 83 | // .catch((err: any) => { 84 | // console.log('entered catch'); 85 | // console.log(err); 86 | // if (err.code === 'ECONNREFUSED') this.increaseTTL(); 87 | // }); 88 | // } 89 | increaseTTL() { 90 | return __awaiter(this, void 0, void 0, function* () { 91 | console.log('TTL increased on all KEYS'); 92 | const keysArr = yield this.redisCache.keys('*'); 93 | for (const key of keysArr) { 94 | const currentTTL = yield this.redisCache.ttl(key); 95 | // console.log('before', currentTTL); 96 | // ttl time is in seconds 97 | yield this.redisCache.EXPIRE(key, currentTTL + 60); 98 | console.log(yield this.redisCache.ttl(key)); 99 | } 100 | }); 101 | } 102 | clearCache(req, res, next) { 103 | this.redisCache.flushAll(); 104 | next(); 105 | } 106 | } 107 | exports.default = EmberQL; 108 | -------------------------------------------------------------------------------- /src/EmberQL.ts: -------------------------------------------------------------------------------- 1 | import { graphql, GraphQLSchema } from '../demo/node_modules/graphql'; 2 | import * as express from 'express'; 3 | const redis = require('redis'); 4 | const fetch = require('node-fetch'); 5 | 6 | class EmberQL { 7 | redisClient: any; 8 | graphQLQuery: string; 9 | schema: GraphQLSchema; 10 | redisCache: any; 11 | 12 | constructor(schema: GraphQLSchema, redisCache: any) { 13 | this.handleQuery = this.handleQuery.bind(this); 14 | this.clearCache = this.clearCache.bind(this); 15 | this.heartbeat = this.heartbeat.bind(this); 16 | this.increaseTTL = this.increaseTTL.bind(this); 17 | 18 | this.graphQLQuery = ''; 19 | this.schema = schema; 20 | this.redisCache = redisCache; 21 | } 22 | 23 | async handleQuery( 24 | req: express.Request, 25 | res: express.Response, 26 | next: express.NextFunction 27 | ) { 28 | this.graphQLQuery = req.body.query; 29 | 30 | if (await this.redisCache.exists(this.graphQLQuery)) { 31 | const response = await this.redisCache.get(this.graphQLQuery); 32 | res.locals.data = JSON.parse(response); 33 | return next(); 34 | } else { 35 | const results = await graphql({ 36 | schema: this.schema, 37 | source: this.graphQLQuery, 38 | }); 39 | this.redisCache.set(this.graphQLQuery, JSON.stringify(results)); 40 | res.locals.data = results; 41 | return next(); 42 | } 43 | } 44 | 45 | heartbeat() { 46 | console.log('enter heartbeat'); 47 | fetch('http://localhost:3000/heartbeat', { 48 | method: 'POST', 49 | headers: { 50 | 'content-type': 'application/json;charset=UTF-8', 51 | }, 52 | body: JSON.stringify({ 53 | query: '{book(id:1){id}}', 54 | }), 55 | }) 56 | .then((data: any) => data.json()) 57 | .then((data: any) => { 58 | const date = new Date() 59 | // console.log('data:', data); 60 | if (data.errors) { 61 | for (const error of data.errors) { 62 | // console.log(error.message.slice(0, 20)); 63 | if (error.message.slice(0, 20) === 'connect ECONNREFUSED') { 64 | console.log(`Heartbeat FAILED, Cache invalidation halted. ${date}`) 65 | this.increaseTTL(); 66 | } 67 | } 68 | } else { 69 | console.log(`Heartbeat OK, ${date}`) 70 | } 71 | }) 72 | .catch((err: any) => { 73 | console.log('entered catch'); 74 | if (err.code === 'ECONNREFUSED') this.increaseTTL(); 75 | }); 76 | } 77 | 78 | // heartbeat() { 79 | // console.log('enter heartbeat'); 80 | // graphql(this.schema, '{books{title}}') 81 | // // .then((data: any) => data.json()) 82 | // .then((data: any) => console.log(data)) 83 | // .catch((err: any) => { 84 | // console.log('entered catch'); 85 | // console.log(err); 86 | // if (err.code === 'ECONNREFUSED') this.increaseTTL(); 87 | // }); 88 | // } 89 | 90 | async increaseTTL() { 91 | console.log('TTL increased on all KEYS'); 92 | const keysArr = await this.redisCache.keys('*'); 93 | for (const key of keysArr) { 94 | const currentTTL = await this.redisCache.ttl(key); 95 | // console.log('before', currentTTL); 96 | // ttl time is in seconds 97 | await this.redisCache.EXPIRE(key, currentTTL + 60); 98 | 99 | console.log(await this.redisCache.ttl(key)); 100 | } 101 | } 102 | 103 | clearCache( 104 | req: express.Request, 105 | res: express.Response, 106 | next: express.NextFunction 107 | ) { 108 | this.redisCache.flushAll(); 109 | next(); 110 | } 111 | } 112 | 113 | export default EmberQL; 114 | -------------------------------------------------------------------------------- /src/README.md: -------------------------------------------------------------------------------- 1 | # EmberQL 2 | 3 |

5 | 6 | [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://github.com/oslabs-beta/EmberQL/blob/dev/LICENSE) 7 | ## What is EmberQL? 8 | 9 | EmberQL is an intuitive, lightweight Node module that facilitates caching data from GraphQL queries, and implements a dynamic data persistence system that monitors the status of the primary database and modifies cache invalidation accordingly. 10 | ## Features 11 | 12 | ### Server-side caching with Redis to decrease query times 13 | Decrease the time it takes for your users to fetch data from your database by up to one hundred fold. Research shows that even a second of latency will drastically increase bounce rates on your application. Additionally, depending on the specifications or hosting of your database, too many simultaneous queries can cause timeouts to occur. Using EmberQL, there is no need to gamble with forcing your users to make redundant queries to your database. 14 | 15 | ### Dynamic cache invalidation 16 | EmberQL incorporates a smart "heartbeat" feature that will monitor your database in real time and halt cache invalidation when it detects downtime. This is done by periodically increasing the time to live of cached data, and as soon as the database comes back online cached items will revert to being evicted normally. The heartbeat will communicate relevant information to the developer in the server console. 17 | 18 | 19 | ### Data persistence system utilizing **RDB** (Redis Database) and **AOF** (Append Only File) 20 | In the event of your database going down, the most relevant information users are querying will be available in the in-memory database and thus available to users. With EmberQL, there is no need for your clients to notice when your database isn't running. You can rest assured that your application will have fault tolerance after installing the module. 21 | 22 | ## Installation & Prerequisites 23 | You can install the EmberQL module into your Node.js application by running the command npm install emberql. Your application must have GraphQL and as a dependency, and you will need to define your schema so that EmberQL can make use of it. You will also need Redis as a dependency to access the Redis functions (createClient, connect, on, etc.) and you will need to either run a Redis server on your machine locally or utilize AWS Elasticache to run a Redis server. 24 | ## Implementation 25 | You 26 | The EmberQL class will take your GraphQL schema and your Redis cache instance as arguments: 27 | ``` 28 | const Ember = new EmberQL(schema, redisCache); 29 | ``` 30 | Any request sent to '/graphql' should be routed through the handleQuery middleware: 31 | ``` 32 | app.use('/graphql', Ember.handleQuery, (req, res) => { 33 | res.status(202).json(res.locals.data); 34 | }); 35 | ``` 36 | To clear the Redis cache, send a request to the '/clearCache' endpoint and route it through the EmberQL clearCache method: 37 | ``` 38 | app.use('/clearCache', Ember.clearCache, (req, res) => { 39 | res.sendStatus(202); 40 | }); 41 | ``` 42 | ## Features in Production 43 | Data normalization for Redis caching is currently in our development pipeline. The prototype utilizes a recursive function to parse the GraphQL AST and transform queries into key value pairs utilizing hashing to optimize memory. 44 | 45 | ## EmberQL Engineering Team 46 | 47 | [Cristian De Los Rios](https://github.com/Cristian-DeLosRios) | 48 | [Manjunath Ajjappa Pattanashetty](https://github.com/manjunathap85) | 49 | [Mike Masatsugu](https://github.com/mikemasatsugu) | 50 | [Ram Marimuthu](https://github.com/rammarimuthu) | 51 | [Tyler Pohn](https://github.com/tylerpohn) 52 | -------------------------------------------------------------------------------- /src/package-lock.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "emberql", 3 | "version": "1.0.0", 4 | "lockfileVersion": 2, 5 | "requires": true, 6 | "packages": { 7 | "": { 8 | "name": "emberql", 9 | "version": "1.0.0", 10 | "license": "ISC", 11 | "dependencies": { 12 | "@types/express": "^4.17.13", 13 | "@types/graphql": "^14.5.0", 14 | "@types/redis": "^4.0.11", 15 | "express": "^4.17.1", 16 | "graphql": "^15.7.2", 17 | "node-fetch": "^2.6.6", 18 | "redis": "^4.0.1" 19 | } 20 | }, 21 | "node_modules/@node-redis/client": { 22 | "version": "1.0.1", 23 | "resolved": "https://registry.npmjs.org/@node-redis/client/-/client-1.0.1.tgz", 24 | "integrity": "sha512-o0I4LdzJXP6QYxRnBPrQ7cIG5tF3SNM/PBnjC3mV6QkzIiGRElzWqSr9a9JCJdcyB1SIA80bhgGhpdTpCQ1Sdw==", 25 | "dependencies": { 26 | "cluster-key-slot": "1.1.0", 27 | "generic-pool": "3.8.2", 28 | "redis-parser": "3.0.0", 29 | "yallist": "4.0.0" 30 | }, 31 | "engines": { 32 | "node": ">=12" 33 | } 34 | }, 35 | "node_modules/@node-redis/json": { 36 | "version": "1.0.1", 37 | "resolved": "https://registry.npmjs.org/@node-redis/json/-/json-1.0.1.tgz", 38 | "integrity": "sha512-2EB96ZN0yUr4mgA9Odme48jX8eF5Ji0jrsRn4rLfEhME7L3rHLdKeUfxJKxbPOxadP6k8+6ViElxPZrKuV2nvQ==", 39 | "peerDependencies": { 40 | "@node-redis/client": "^1.0.0" 41 | } 42 | }, 43 | "node_modules/@node-redis/search": { 44 | "version": "1.0.1", 45 | "resolved": "https://registry.npmjs.org/@node-redis/search/-/search-1.0.1.tgz", 46 | "integrity": "sha512-iA2Gw6gr0X6IfNSjTyme9W1tDlLkwQ1bPApo4s8aVwZ2Ju8Z4COVik0vT6BJPRin79f5xPZgnaec3DIoC2UpHA==", 47 | "peerDependencies": { 48 | "@node-redis/client": "^1.0.0" 49 | } 50 | }, 51 | "node_modules/@node-redis/time-series": { 52 | "version": "1.0.0", 53 | "resolved": "https://registry.npmjs.org/@node-redis/time-series/-/time-series-1.0.0.tgz", 54 | "integrity": "sha512-QcaCIL/DlYJXedSfmjF+IRxKJbBUXBrjA5Gv0IiPlXXFFOkRnbPGKq6hmwBAAWyk1U03wyBHDFKVS3/9GnZV8g==", 55 | "peerDependencies": { 56 | "@node-redis/client": "^1.0.0" 57 | } 58 | }, 59 | "node_modules/@types/body-parser": { 60 | "version": "1.19.2", 61 | "resolved": "https://registry.npmjs.org/@types/body-parser/-/body-parser-1.19.2.tgz", 62 | "integrity": "sha512-ALYone6pm6QmwZoAgeyNksccT9Q4AWZQ6PvfwR37GT6r6FWUPguq6sUmNGSMV2Wr761oQoBxwGGa6DR5o1DC9g==", 63 | "dependencies": { 64 | "@types/connect": "*", 65 | "@types/node": "*" 66 | } 67 | }, 68 | "node_modules/@types/connect": { 69 | "version": "3.4.35", 70 | "resolved": "https://registry.npmjs.org/@types/connect/-/connect-3.4.35.tgz", 71 | "integrity": "sha512-cdeYyv4KWoEgpBISTxWvqYsVy444DOqehiF3fM3ne10AmJ62RSyNkUnxMJXHQWRQQX2eR94m5y1IZyDwBjV9FQ==", 72 | "dependencies": { 73 | "@types/node": "*" 74 | } 75 | }, 76 | "node_modules/@types/express": { 77 | "version": "4.17.13", 78 | "resolved": "https://registry.npmjs.org/@types/express/-/express-4.17.13.tgz", 79 | "integrity": "sha512-6bSZTPaTIACxn48l50SR+axgrqm6qXFIxrdAKaG6PaJk3+zuUr35hBlgT7vOmJcum+OEaIBLtHV/qloEAFITeA==", 80 | "dependencies": { 81 | "@types/body-parser": "*", 82 | "@types/express-serve-static-core": "^4.17.18", 83 | "@types/qs": "*", 84 | "@types/serve-static": "*" 85 | } 86 | }, 87 | "node_modules/@types/express-serve-static-core": { 88 | "version": "4.17.26", 89 | "resolved": "https://registry.npmjs.org/@types/express-serve-static-core/-/express-serve-static-core-4.17.26.tgz", 90 | "integrity": "sha512-zeu3tpouA043RHxW0gzRxwCHchMgftE8GArRsvYT0ByDMbn19olQHx5jLue0LxWY6iYtXb7rXmuVtSkhy9YZvQ==", 91 | "dependencies": { 92 | "@types/node": "*", 93 | "@types/qs": "*", 94 | "@types/range-parser": "*" 95 | } 96 | }, 97 | "node_modules/@types/graphql": { 98 | "version": "14.5.0", 99 | "resolved": "https://registry.npmjs.org/@types/graphql/-/graphql-14.5.0.tgz", 100 | "integrity": "sha512-MOkzsEp1Jk5bXuAsHsUi6BVv0zCO+7/2PTiZMXWDSsMXvNU6w/PLMQT2vHn8hy2i0JqojPz1Sz6rsFjHtsU0lA==", 101 | "deprecated": "This is a stub types definition. graphql provides its own type definitions, so you do not need this installed.", 102 | "dependencies": { 103 | "graphql": "*" 104 | } 105 | }, 106 | "node_modules/@types/mime": { 107 | "version": "1.3.2", 108 | "resolved": "https://registry.npmjs.org/@types/mime/-/mime-1.3.2.tgz", 109 | "integrity": "sha512-YATxVxgRqNH6nHEIsvg6k2Boc1JHI9ZbH5iWFFv/MTkchz3b1ieGDa5T0a9RznNdI0KhVbdbWSN+KWWrQZRxTw==" 110 | }, 111 | "node_modules/@types/node": { 112 | "version": "16.11.12", 113 | "resolved": "https://registry.npmjs.org/@types/node/-/node-16.11.12.tgz", 114 | "integrity": "sha512-+2Iggwg7PxoO5Kyhvsq9VarmPbIelXP070HMImEpbtGCoyWNINQj4wzjbQCXzdHTRXnqufutJb5KAURZANNBAw==" 115 | }, 116 | "node_modules/@types/qs": { 117 | "version": "6.9.7", 118 | "resolved": "https://registry.npmjs.org/@types/qs/-/qs-6.9.7.tgz", 119 | "integrity": "sha512-FGa1F62FT09qcrueBA6qYTrJPVDzah9a+493+o2PCXsesWHIn27G98TsSMs3WPNbZIEj4+VJf6saSFpvD+3Zsw==" 120 | }, 121 | "node_modules/@types/range-parser": { 122 | "version": "1.2.4", 123 | "resolved": "https://registry.npmjs.org/@types/range-parser/-/range-parser-1.2.4.tgz", 124 | "integrity": "sha512-EEhsLsD6UsDM1yFhAvy0Cjr6VwmpMWqFBCb9w07wVugF7w9nfajxLuVmngTIpgS6svCnm6Vaw+MZhoDCKnOfsw==" 125 | }, 126 | "node_modules/@types/redis": { 127 | "version": "4.0.11", 128 | "resolved": "https://registry.npmjs.org/@types/redis/-/redis-4.0.11.tgz", 129 | "integrity": "sha512-bI+gth8La8Wg/QCR1+V1fhrL9+LZUSWfcqpOj2Kc80ZQ4ffbdL173vQd5wovmoV9i071FU9oP2g6etLuEwb6Rg==", 130 | "deprecated": "This is a stub types definition. redis provides its own type definitions, so you do not need this installed.", 131 | "dependencies": { 132 | "redis": "*" 133 | } 134 | }, 135 | "node_modules/@types/serve-static": { 136 | "version": "1.13.10", 137 | "resolved": "https://registry.npmjs.org/@types/serve-static/-/serve-static-1.13.10.tgz", 138 | "integrity": "sha512-nCkHGI4w7ZgAdNkrEu0bv+4xNV/XDqW+DydknebMOQwkpDGx8G+HTlj7R7ABI8i8nKxVw0wtKPi1D+lPOkh4YQ==", 139 | "dependencies": { 140 | "@types/mime": "^1", 141 | "@types/node": "*" 142 | } 143 | }, 144 | "node_modules/accepts": { 145 | "version": "1.3.7", 146 | "resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.7.tgz", 147 | "integrity": "sha512-Il80Qs2WjYlJIBNzNkK6KYqlVMTbZLXgHx2oT0pU/fjRHyEp+PEfEPY0R3WCwAGVOtauxh1hOxNgIf5bv7dQpA==", 148 | "dependencies": { 149 | "mime-types": "~2.1.24", 150 | "negotiator": "0.6.2" 151 | }, 152 | "engines": { 153 | "node": ">= 0.6" 154 | } 155 | }, 156 | "node_modules/array-flatten": { 157 | "version": "1.1.1", 158 | "resolved": "https://registry.npmjs.org/array-flatten/-/array-flatten-1.1.1.tgz", 159 | "integrity": "sha1-ml9pkFGx5wczKPKgCJaLZOopVdI=" 160 | }, 161 | "node_modules/body-parser": { 162 | "version": "1.19.0", 163 | "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.19.0.tgz", 164 | "integrity": "sha512-dhEPs72UPbDnAQJ9ZKMNTP6ptJaionhP5cBb541nXPlW60Jepo9RV/a4fX4XWW9CuFNK22krhrj1+rgzifNCsw==", 165 | "dependencies": { 166 | "bytes": "3.1.0", 167 | "content-type": "~1.0.4", 168 | "debug": "2.6.9", 169 | "depd": "~1.1.2", 170 | "http-errors": "1.7.2", 171 | "iconv-lite": "0.4.24", 172 | "on-finished": "~2.3.0", 173 | "qs": "6.7.0", 174 | "raw-body": "2.4.0", 175 | "type-is": "~1.6.17" 176 | }, 177 | "engines": { 178 | "node": ">= 0.8" 179 | } 180 | }, 181 | "node_modules/bytes": { 182 | "version": "3.1.0", 183 | "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.0.tgz", 184 | "integrity": "sha512-zauLjrfCG+xvoyaqLoV8bLVXXNGC4JqlxFCutSDWA6fJrTo2ZuvLYTqZ7aHBLZSMOopbzwv8f+wZcVzfVTI2Dg==", 185 | "engines": { 186 | "node": ">= 0.8" 187 | } 188 | }, 189 | "node_modules/cluster-key-slot": { 190 | "version": "1.1.0", 191 | "resolved": "https://registry.npmjs.org/cluster-key-slot/-/cluster-key-slot-1.1.0.tgz", 192 | "integrity": "sha512-2Nii8p3RwAPiFwsnZvukotvow2rIHM+yQ6ZcBXGHdniadkYGZYiGmkHJIbZPIV9nfv7m/U1IPMVVcAhoWFeklw==", 193 | "engines": { 194 | "node": ">=0.10.0" 195 | } 196 | }, 197 | "node_modules/content-disposition": { 198 | "version": "0.5.3", 199 | "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-0.5.3.tgz", 200 | "integrity": "sha512-ExO0774ikEObIAEV9kDo50o+79VCUdEB6n6lzKgGwupcVeRlhrj3qGAfwq8G6uBJjkqLrhT0qEYFcWng8z1z0g==", 201 | "dependencies": { 202 | "safe-buffer": "5.1.2" 203 | }, 204 | "engines": { 205 | "node": ">= 0.6" 206 | } 207 | }, 208 | "node_modules/content-type": { 209 | "version": "1.0.4", 210 | "resolved": "https://registry.npmjs.org/content-type/-/content-type-1.0.4.tgz", 211 | "integrity": "sha512-hIP3EEPs8tB9AT1L+NUqtwOAps4mk2Zob89MWXMHjHWg9milF/j4osnnQLXBCBFBk/tvIG/tUc9mOUJiPBhPXA==", 212 | "engines": { 213 | "node": ">= 0.6" 214 | } 215 | }, 216 | "node_modules/cookie": { 217 | "version": "0.4.0", 218 | "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.4.0.tgz", 219 | "integrity": "sha512-+Hp8fLp57wnUSt0tY0tHEXh4voZRDnoIrZPqlo3DPiI4y9lwg/jqx+1Om94/W6ZaPDOUbnjOt/99w66zk+l1Xg==", 220 | "engines": { 221 | "node": ">= 0.6" 222 | } 223 | }, 224 | "node_modules/cookie-signature": { 225 | "version": "1.0.6", 226 | "resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.0.6.tgz", 227 | "integrity": "sha1-4wOogrNCzD7oylE6eZmXNNqzriw=" 228 | }, 229 | "node_modules/debug": { 230 | "version": "2.6.9", 231 | "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", 232 | "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", 233 | "dependencies": { 234 | "ms": "2.0.0" 235 | } 236 | }, 237 | "node_modules/depd": { 238 | "version": "1.1.2", 239 | "resolved": "https://registry.npmjs.org/depd/-/depd-1.1.2.tgz", 240 | "integrity": "sha1-m81S4UwJd2PnSbJ0xDRu0uVgtak=", 241 | "engines": { 242 | "node": ">= 0.6" 243 | } 244 | }, 245 | "node_modules/destroy": { 246 | "version": "1.0.4", 247 | "resolved": "https://registry.npmjs.org/destroy/-/destroy-1.0.4.tgz", 248 | "integrity": "sha1-l4hXRCxEdJ5CBmE+N5RiBYJqvYA=" 249 | }, 250 | "node_modules/ee-first": { 251 | "version": "1.1.1", 252 | "resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz", 253 | "integrity": "sha1-WQxhFWsK4vTwJVcyoViyZrxWsh0=" 254 | }, 255 | "node_modules/encodeurl": { 256 | "version": "1.0.2", 257 | "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-1.0.2.tgz", 258 | "integrity": "sha1-rT/0yG7C0CkyL1oCw6mmBslbP1k=", 259 | "engines": { 260 | "node": ">= 0.8" 261 | } 262 | }, 263 | "node_modules/escape-html": { 264 | "version": "1.0.3", 265 | "resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz", 266 | "integrity": "sha1-Aljq5NPQwJdN4cFpGI7wBR0dGYg=" 267 | }, 268 | "node_modules/etag": { 269 | "version": "1.8.1", 270 | "resolved": "https://registry.npmjs.org/etag/-/etag-1.8.1.tgz", 271 | "integrity": "sha1-Qa4u62XvpiJorr/qg6x9eSmbCIc=", 272 | "engines": { 273 | "node": ">= 0.6" 274 | } 275 | }, 276 | "node_modules/express": { 277 | "version": "4.17.1", 278 | "resolved": "https://registry.npmjs.org/express/-/express-4.17.1.tgz", 279 | "integrity": "sha512-mHJ9O79RqluphRrcw2X/GTh3k9tVv8YcoyY4Kkh4WDMUYKRZUq0h1o0w2rrrxBqM7VoeUVqgb27xlEMXTnYt4g==", 280 | "dependencies": { 281 | "accepts": "~1.3.7", 282 | "array-flatten": "1.1.1", 283 | "body-parser": "1.19.0", 284 | "content-disposition": "0.5.3", 285 | "content-type": "~1.0.4", 286 | "cookie": "0.4.0", 287 | "cookie-signature": "1.0.6", 288 | "debug": "2.6.9", 289 | "depd": "~1.1.2", 290 | "encodeurl": "~1.0.2", 291 | "escape-html": "~1.0.3", 292 | "etag": "~1.8.1", 293 | "finalhandler": "~1.1.2", 294 | "fresh": "0.5.2", 295 | "merge-descriptors": "1.0.1", 296 | "methods": "~1.1.2", 297 | "on-finished": "~2.3.0", 298 | "parseurl": "~1.3.3", 299 | "path-to-regexp": "0.1.7", 300 | "proxy-addr": "~2.0.5", 301 | "qs": "6.7.0", 302 | "range-parser": "~1.2.1", 303 | "safe-buffer": "5.1.2", 304 | "send": "0.17.1", 305 | "serve-static": "1.14.1", 306 | "setprototypeof": "1.1.1", 307 | "statuses": "~1.5.0", 308 | "type-is": "~1.6.18", 309 | "utils-merge": "1.0.1", 310 | "vary": "~1.1.2" 311 | }, 312 | "engines": { 313 | "node": ">= 0.10.0" 314 | } 315 | }, 316 | "node_modules/finalhandler": { 317 | "version": "1.1.2", 318 | "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.1.2.tgz", 319 | "integrity": "sha512-aAWcW57uxVNrQZqFXjITpW3sIUQmHGG3qSb9mUah9MgMC4NeWhNOlNjXEYq3HjRAvL6arUviZGGJsBg6z0zsWA==", 320 | "dependencies": { 321 | "debug": "2.6.9", 322 | "encodeurl": "~1.0.2", 323 | "escape-html": "~1.0.3", 324 | "on-finished": "~2.3.0", 325 | "parseurl": "~1.3.3", 326 | "statuses": "~1.5.0", 327 | "unpipe": "~1.0.0" 328 | }, 329 | "engines": { 330 | "node": ">= 0.8" 331 | } 332 | }, 333 | "node_modules/forwarded": { 334 | "version": "0.2.0", 335 | "resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.2.0.tgz", 336 | "integrity": "sha512-buRG0fpBtRHSTCOASe6hD258tEubFoRLb4ZNA6NxMVHNw2gOcwHo9wyablzMzOA5z9xA9L1KNjk/Nt6MT9aYow==", 337 | "engines": { 338 | "node": ">= 0.6" 339 | } 340 | }, 341 | "node_modules/fresh": { 342 | "version": "0.5.2", 343 | "resolved": "https://registry.npmjs.org/fresh/-/fresh-0.5.2.tgz", 344 | "integrity": "sha1-PYyt2Q2XZWn6g1qx+OSyOhBWBac=", 345 | "engines": { 346 | "node": ">= 0.6" 347 | } 348 | }, 349 | "node_modules/generic-pool": { 350 | "version": "3.8.2", 351 | "resolved": "https://registry.npmjs.org/generic-pool/-/generic-pool-3.8.2.tgz", 352 | "integrity": "sha512-nGToKy6p3PAbYQ7p1UlWl6vSPwfwU6TMSWK7TTu+WUY4ZjyZQGniGGt2oNVvyNSpyZYSB43zMXVLcBm08MTMkg==", 353 | "engines": { 354 | "node": ">= 4" 355 | } 356 | }, 357 | "node_modules/graphql": { 358 | "version": "15.8.0", 359 | "resolved": "https://registry.npmjs.org/graphql/-/graphql-15.8.0.tgz", 360 | "integrity": "sha512-5gghUc24tP9HRznNpV2+FIoq3xKkj5dTQqf4v0CpdPbFVwFkWoxOM+o+2OC9ZSvjEMTjfmG9QT+gcvggTwW1zw==", 361 | "engines": { 362 | "node": ">= 10.x" 363 | } 364 | }, 365 | "node_modules/http-errors": { 366 | "version": "1.7.2", 367 | "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-1.7.2.tgz", 368 | "integrity": "sha512-uUQBt3H/cSIVfch6i1EuPNy/YsRSOUBXTVfZ+yR7Zjez3qjBz6i9+i4zjNaoqcoFVI4lQJ5plg63TvGfRSDCRg==", 369 | "dependencies": { 370 | "depd": "~1.1.2", 371 | "inherits": "2.0.3", 372 | "setprototypeof": "1.1.1", 373 | "statuses": ">= 1.5.0 < 2", 374 | "toidentifier": "1.0.0" 375 | }, 376 | "engines": { 377 | "node": ">= 0.6" 378 | } 379 | }, 380 | "node_modules/iconv-lite": { 381 | "version": "0.4.24", 382 | "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz", 383 | "integrity": "sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==", 384 | "dependencies": { 385 | "safer-buffer": ">= 2.1.2 < 3" 386 | }, 387 | "engines": { 388 | "node": ">=0.10.0" 389 | } 390 | }, 391 | "node_modules/inherits": { 392 | "version": "2.0.3", 393 | "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.3.tgz", 394 | "integrity": "sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4=" 395 | }, 396 | "node_modules/ipaddr.js": { 397 | "version": "1.9.1", 398 | "resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-1.9.1.tgz", 399 | "integrity": "sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g==", 400 | "engines": { 401 | "node": ">= 0.10" 402 | } 403 | }, 404 | "node_modules/media-typer": { 405 | "version": "0.3.0", 406 | "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz", 407 | "integrity": "sha1-hxDXrwqmJvj/+hzgAWhUUmMlV0g=", 408 | "engines": { 409 | "node": ">= 0.6" 410 | } 411 | }, 412 | "node_modules/merge-descriptors": { 413 | "version": "1.0.1", 414 | "resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-1.0.1.tgz", 415 | "integrity": "sha1-sAqqVW3YtEVoFQ7J0blT8/kMu2E=" 416 | }, 417 | "node_modules/methods": { 418 | "version": "1.1.2", 419 | "resolved": "https://registry.npmjs.org/methods/-/methods-1.1.2.tgz", 420 | "integrity": "sha1-VSmk1nZUE07cxSZmVoNbD4Ua/O4=", 421 | "engines": { 422 | "node": ">= 0.6" 423 | } 424 | }, 425 | "node_modules/mime": { 426 | "version": "1.6.0", 427 | "resolved": "https://registry.npmjs.org/mime/-/mime-1.6.0.tgz", 428 | "integrity": "sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg==", 429 | "bin": { 430 | "mime": "cli.js" 431 | }, 432 | "engines": { 433 | "node": ">=4" 434 | } 435 | }, 436 | "node_modules/mime-db": { 437 | "version": "1.51.0", 438 | "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.51.0.tgz", 439 | "integrity": "sha512-5y8A56jg7XVQx2mbv1lu49NR4dokRnhZYTtL+KGfaa27uq4pSTXkwQkFJl4pkRMyNFz/EtYDSkiiEHx3F7UN6g==", 440 | "engines": { 441 | "node": ">= 0.6" 442 | } 443 | }, 444 | "node_modules/mime-types": { 445 | "version": "2.1.34", 446 | "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.34.tgz", 447 | "integrity": "sha512-6cP692WwGIs9XXdOO4++N+7qjqv0rqxxVvJ3VHPh/Sc9mVZcQP+ZGhkKiTvWMQRr2tbHkJP/Yn7Y0npb3ZBs4A==", 448 | "dependencies": { 449 | "mime-db": "1.51.0" 450 | }, 451 | "engines": { 452 | "node": ">= 0.6" 453 | } 454 | }, 455 | "node_modules/ms": { 456 | "version": "2.0.0", 457 | "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", 458 | "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=" 459 | }, 460 | "node_modules/negotiator": { 461 | "version": "0.6.2", 462 | "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.2.tgz", 463 | "integrity": "sha512-hZXc7K2e+PgeI1eDBe/10Ard4ekbfrrqG8Ep+8Jmf4JID2bNg7NvCPOZN+kfF574pFQI7mum2AUqDidoKqcTOw==", 464 | "engines": { 465 | "node": ">= 0.6" 466 | } 467 | }, 468 | "node_modules/node-fetch": { 469 | "version": "2.6.6", 470 | "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.6.6.tgz", 471 | "integrity": "sha512-Z8/6vRlTUChSdIgMa51jxQ4lrw/Jy5SOW10ObaA47/RElsAN2c5Pn8bTgFGWn/ibwzXTE8qwr1Yzx28vsecXEA==", 472 | "dependencies": { 473 | "whatwg-url": "^5.0.0" 474 | }, 475 | "engines": { 476 | "node": "4.x || >=6.0.0" 477 | } 478 | }, 479 | "node_modules/on-finished": { 480 | "version": "2.3.0", 481 | "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.3.0.tgz", 482 | "integrity": "sha1-IPEzZIGwg811M3mSoWlxqi2QaUc=", 483 | "dependencies": { 484 | "ee-first": "1.1.1" 485 | }, 486 | "engines": { 487 | "node": ">= 0.8" 488 | } 489 | }, 490 | "node_modules/parseurl": { 491 | "version": "1.3.3", 492 | "resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.3.tgz", 493 | "integrity": "sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ==", 494 | "engines": { 495 | "node": ">= 0.8" 496 | } 497 | }, 498 | "node_modules/path-to-regexp": { 499 | "version": "0.1.7", 500 | "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.7.tgz", 501 | "integrity": "sha1-32BBeABfUi8V60SQ5yR6G/qmf4w=" 502 | }, 503 | "node_modules/proxy-addr": { 504 | "version": "2.0.7", 505 | "resolved": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-2.0.7.tgz", 506 | "integrity": "sha512-llQsMLSUDUPT44jdrU/O37qlnifitDP+ZwrmmZcoSKyLKvtZxpyV0n2/bD/N4tBAAZ/gJEdZU7KMraoK1+XYAg==", 507 | "dependencies": { 508 | "forwarded": "0.2.0", 509 | "ipaddr.js": "1.9.1" 510 | }, 511 | "engines": { 512 | "node": ">= 0.10" 513 | } 514 | }, 515 | "node_modules/qs": { 516 | "version": "6.7.0", 517 | "resolved": "https://registry.npmjs.org/qs/-/qs-6.7.0.tgz", 518 | "integrity": "sha512-VCdBRNFTX1fyE7Nb6FYoURo/SPe62QCaAyzJvUjwRaIsc+NePBEniHlvxFmmX56+HZphIGtV0XeCirBtpDrTyQ==", 519 | "engines": { 520 | "node": ">=0.6" 521 | } 522 | }, 523 | "node_modules/range-parser": { 524 | "version": "1.2.1", 525 | "resolved": "https://registry.npmjs.org/range-parser/-/range-parser-1.2.1.tgz", 526 | "integrity": "sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg==", 527 | "engines": { 528 | "node": ">= 0.6" 529 | } 530 | }, 531 | "node_modules/raw-body": { 532 | "version": "2.4.0", 533 | "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-2.4.0.tgz", 534 | "integrity": "sha512-4Oz8DUIwdvoa5qMJelxipzi/iJIi40O5cGV1wNYp5hvZP8ZN0T+jiNkL0QepXs+EsQ9XJ8ipEDoiH70ySUJP3Q==", 535 | "dependencies": { 536 | "bytes": "3.1.0", 537 | "http-errors": "1.7.2", 538 | "iconv-lite": "0.4.24", 539 | "unpipe": "1.0.0" 540 | }, 541 | "engines": { 542 | "node": ">= 0.8" 543 | } 544 | }, 545 | "node_modules/redis": { 546 | "version": "4.0.1", 547 | "resolved": "https://registry.npmjs.org/redis/-/redis-4.0.1.tgz", 548 | "integrity": "sha512-qfcq1oz2ci7pNdCfTLLEuKhS8jZ17dFiT1exogOr+jd3EVP/h9qpy7K+VajB4BXA0k8q68KFqR6HrliKV6jt1Q==", 549 | "dependencies": { 550 | "@node-redis/client": "^1.0.1", 551 | "@node-redis/json": "^1.0.1", 552 | "@node-redis/search": "^1.0.1", 553 | "@node-redis/time-series": "^1.0.0" 554 | }, 555 | "engines": { 556 | "npm": ">=7" 557 | } 558 | }, 559 | "node_modules/redis-errors": { 560 | "version": "1.2.0", 561 | "resolved": "https://registry.npmjs.org/redis-errors/-/redis-errors-1.2.0.tgz", 562 | "integrity": "sha1-62LSrbFeTq9GEMBK/hUpOEJQq60=", 563 | "engines": { 564 | "node": ">=4" 565 | } 566 | }, 567 | "node_modules/redis-parser": { 568 | "version": "3.0.0", 569 | "resolved": "https://registry.npmjs.org/redis-parser/-/redis-parser-3.0.0.tgz", 570 | "integrity": "sha1-tm2CjNyv5rS4pCin3vTGvKwxyLQ=", 571 | "dependencies": { 572 | "redis-errors": "^1.0.0" 573 | }, 574 | "engines": { 575 | "node": ">=4" 576 | } 577 | }, 578 | "node_modules/safe-buffer": { 579 | "version": "5.1.2", 580 | "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", 581 | "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==" 582 | }, 583 | "node_modules/safer-buffer": { 584 | "version": "2.1.2", 585 | "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", 586 | "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==" 587 | }, 588 | "node_modules/send": { 589 | "version": "0.17.1", 590 | "resolved": "https://registry.npmjs.org/send/-/send-0.17.1.tgz", 591 | "integrity": "sha512-BsVKsiGcQMFwT8UxypobUKyv7irCNRHk1T0G680vk88yf6LBByGcZJOTJCrTP2xVN6yI+XjPJcNuE3V4fT9sAg==", 592 | "dependencies": { 593 | "debug": "2.6.9", 594 | "depd": "~1.1.2", 595 | "destroy": "~1.0.4", 596 | "encodeurl": "~1.0.2", 597 | "escape-html": "~1.0.3", 598 | "etag": "~1.8.1", 599 | "fresh": "0.5.2", 600 | "http-errors": "~1.7.2", 601 | "mime": "1.6.0", 602 | "ms": "2.1.1", 603 | "on-finished": "~2.3.0", 604 | "range-parser": "~1.2.1", 605 | "statuses": "~1.5.0" 606 | }, 607 | "engines": { 608 | "node": ">= 0.8.0" 609 | } 610 | }, 611 | "node_modules/send/node_modules/ms": { 612 | "version": "2.1.1", 613 | "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.1.tgz", 614 | "integrity": "sha512-tgp+dl5cGk28utYktBsrFqA7HKgrhgPsg6Z/EfhWI4gl1Hwq8B/GmY/0oXZ6nF8hDVesS/FpnYaD/kOWhYQvyg==" 615 | }, 616 | "node_modules/serve-static": { 617 | "version": "1.14.1", 618 | "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-1.14.1.tgz", 619 | "integrity": "sha512-JMrvUwE54emCYWlTI+hGrGv5I8dEwmco/00EvkzIIsR7MqrHonbD9pO2MOfFnpFntl7ecpZs+3mW+XbQZu9QCg==", 620 | "dependencies": { 621 | "encodeurl": "~1.0.2", 622 | "escape-html": "~1.0.3", 623 | "parseurl": "~1.3.3", 624 | "send": "0.17.1" 625 | }, 626 | "engines": { 627 | "node": ">= 0.8.0" 628 | } 629 | }, 630 | "node_modules/setprototypeof": { 631 | "version": "1.1.1", 632 | "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.1.1.tgz", 633 | "integrity": "sha512-JvdAWfbXeIGaZ9cILp38HntZSFSo3mWg6xGcJJsd+d4aRMOqauag1C63dJfDw7OaMYwEbHMOxEZ1lqVRYP2OAw==" 634 | }, 635 | "node_modules/statuses": { 636 | "version": "1.5.0", 637 | "resolved": "https://registry.npmjs.org/statuses/-/statuses-1.5.0.tgz", 638 | "integrity": "sha1-Fhx9rBd2Wf2YEfQ3cfqZOBR4Yow=", 639 | "engines": { 640 | "node": ">= 0.6" 641 | } 642 | }, 643 | "node_modules/toidentifier": { 644 | "version": "1.0.0", 645 | "resolved": "https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.0.tgz", 646 | "integrity": "sha512-yaOH/Pk/VEhBWWTlhI+qXxDFXlejDGcQipMlyxda9nthulaxLZUNcUqFxokp0vcYnvteJln5FNQDRrxj3YcbVw==", 647 | "engines": { 648 | "node": ">=0.6" 649 | } 650 | }, 651 | "node_modules/tr46": { 652 | "version": "0.0.3", 653 | "resolved": "https://registry.npmjs.org/tr46/-/tr46-0.0.3.tgz", 654 | "integrity": "sha1-gYT9NH2snNwYWZLzpmIuFLnZq2o=" 655 | }, 656 | "node_modules/type-is": { 657 | "version": "1.6.18", 658 | "resolved": "https://registry.npmjs.org/type-is/-/type-is-1.6.18.tgz", 659 | "integrity": "sha512-TkRKr9sUTxEH8MdfuCSP7VizJyzRNMjj2J2do2Jr3Kym598JVdEksuzPQCnlFPW4ky9Q+iA+ma9BGm06XQBy8g==", 660 | "dependencies": { 661 | "media-typer": "0.3.0", 662 | "mime-types": "~2.1.24" 663 | }, 664 | "engines": { 665 | "node": ">= 0.6" 666 | } 667 | }, 668 | "node_modules/unpipe": { 669 | "version": "1.0.0", 670 | "resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz", 671 | "integrity": "sha1-sr9O6FFKrmFltIF4KdIbLvSZBOw=", 672 | "engines": { 673 | "node": ">= 0.8" 674 | } 675 | }, 676 | "node_modules/utils-merge": { 677 | "version": "1.0.1", 678 | "resolved": "https://registry.npmjs.org/utils-merge/-/utils-merge-1.0.1.tgz", 679 | "integrity": "sha1-n5VxD1CiZ5R7LMwSR0HBAoQn5xM=", 680 | "engines": { 681 | "node": ">= 0.4.0" 682 | } 683 | }, 684 | "node_modules/vary": { 685 | "version": "1.1.2", 686 | "resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz", 687 | "integrity": "sha1-IpnwLG3tMNSllhsLn3RSShj2NPw=", 688 | "engines": { 689 | "node": ">= 0.8" 690 | } 691 | }, 692 | "node_modules/webidl-conversions": { 693 | "version": "3.0.1", 694 | "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-3.0.1.tgz", 695 | "integrity": "sha1-JFNCdeKnvGvnvIZhHMFq4KVlSHE=" 696 | }, 697 | "node_modules/whatwg-url": { 698 | "version": "5.0.0", 699 | "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-5.0.0.tgz", 700 | "integrity": "sha1-lmRU6HZUYuN2RNNib2dCzotwll0=", 701 | "dependencies": { 702 | "tr46": "~0.0.3", 703 | "webidl-conversions": "^3.0.0" 704 | } 705 | }, 706 | "node_modules/yallist": { 707 | "version": "4.0.0", 708 | "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", 709 | "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==" 710 | } 711 | }, 712 | "dependencies": { 713 | "@node-redis/client": { 714 | "version": "1.0.1", 715 | "resolved": "https://registry.npmjs.org/@node-redis/client/-/client-1.0.1.tgz", 716 | "integrity": "sha512-o0I4LdzJXP6QYxRnBPrQ7cIG5tF3SNM/PBnjC3mV6QkzIiGRElzWqSr9a9JCJdcyB1SIA80bhgGhpdTpCQ1Sdw==", 717 | "requires": { 718 | "cluster-key-slot": "1.1.0", 719 | "generic-pool": "3.8.2", 720 | "redis-parser": "3.0.0", 721 | "yallist": "4.0.0" 722 | } 723 | }, 724 | "@node-redis/json": { 725 | "version": "1.0.1", 726 | "resolved": "https://registry.npmjs.org/@node-redis/json/-/json-1.0.1.tgz", 727 | "integrity": "sha512-2EB96ZN0yUr4mgA9Odme48jX8eF5Ji0jrsRn4rLfEhME7L3rHLdKeUfxJKxbPOxadP6k8+6ViElxPZrKuV2nvQ==", 728 | "requires": {} 729 | }, 730 | "@node-redis/search": { 731 | "version": "1.0.1", 732 | "resolved": "https://registry.npmjs.org/@node-redis/search/-/search-1.0.1.tgz", 733 | "integrity": "sha512-iA2Gw6gr0X6IfNSjTyme9W1tDlLkwQ1bPApo4s8aVwZ2Ju8Z4COVik0vT6BJPRin79f5xPZgnaec3DIoC2UpHA==", 734 | "requires": {} 735 | }, 736 | "@node-redis/time-series": { 737 | "version": "1.0.0", 738 | "resolved": "https://registry.npmjs.org/@node-redis/time-series/-/time-series-1.0.0.tgz", 739 | "integrity": "sha512-QcaCIL/DlYJXedSfmjF+IRxKJbBUXBrjA5Gv0IiPlXXFFOkRnbPGKq6hmwBAAWyk1U03wyBHDFKVS3/9GnZV8g==", 740 | "requires": {} 741 | }, 742 | "@types/body-parser": { 743 | "version": "1.19.2", 744 | "resolved": "https://registry.npmjs.org/@types/body-parser/-/body-parser-1.19.2.tgz", 745 | "integrity": "sha512-ALYone6pm6QmwZoAgeyNksccT9Q4AWZQ6PvfwR37GT6r6FWUPguq6sUmNGSMV2Wr761oQoBxwGGa6DR5o1DC9g==", 746 | "requires": { 747 | "@types/connect": "*", 748 | "@types/node": "*" 749 | } 750 | }, 751 | "@types/connect": { 752 | "version": "3.4.35", 753 | "resolved": "https://registry.npmjs.org/@types/connect/-/connect-3.4.35.tgz", 754 | "integrity": "sha512-cdeYyv4KWoEgpBISTxWvqYsVy444DOqehiF3fM3ne10AmJ62RSyNkUnxMJXHQWRQQX2eR94m5y1IZyDwBjV9FQ==", 755 | "requires": { 756 | "@types/node": "*" 757 | } 758 | }, 759 | "@types/express": { 760 | "version": "4.17.13", 761 | "resolved": "https://registry.npmjs.org/@types/express/-/express-4.17.13.tgz", 762 | "integrity": "sha512-6bSZTPaTIACxn48l50SR+axgrqm6qXFIxrdAKaG6PaJk3+zuUr35hBlgT7vOmJcum+OEaIBLtHV/qloEAFITeA==", 763 | "requires": { 764 | "@types/body-parser": "*", 765 | "@types/express-serve-static-core": "^4.17.18", 766 | "@types/qs": "*", 767 | "@types/serve-static": "*" 768 | } 769 | }, 770 | "@types/express-serve-static-core": { 771 | "version": "4.17.26", 772 | "resolved": "https://registry.npmjs.org/@types/express-serve-static-core/-/express-serve-static-core-4.17.26.tgz", 773 | "integrity": "sha512-zeu3tpouA043RHxW0gzRxwCHchMgftE8GArRsvYT0ByDMbn19olQHx5jLue0LxWY6iYtXb7rXmuVtSkhy9YZvQ==", 774 | "requires": { 775 | "@types/node": "*", 776 | "@types/qs": "*", 777 | "@types/range-parser": "*" 778 | } 779 | }, 780 | "@types/graphql": { 781 | "version": "14.5.0", 782 | "resolved": "https://registry.npmjs.org/@types/graphql/-/graphql-14.5.0.tgz", 783 | "integrity": "sha512-MOkzsEp1Jk5bXuAsHsUi6BVv0zCO+7/2PTiZMXWDSsMXvNU6w/PLMQT2vHn8hy2i0JqojPz1Sz6rsFjHtsU0lA==", 784 | "requires": { 785 | "graphql": "*" 786 | } 787 | }, 788 | "@types/mime": { 789 | "version": "1.3.2", 790 | "resolved": "https://registry.npmjs.org/@types/mime/-/mime-1.3.2.tgz", 791 | "integrity": "sha512-YATxVxgRqNH6nHEIsvg6k2Boc1JHI9ZbH5iWFFv/MTkchz3b1ieGDa5T0a9RznNdI0KhVbdbWSN+KWWrQZRxTw==" 792 | }, 793 | "@types/node": { 794 | "version": "16.11.12", 795 | "resolved": "https://registry.npmjs.org/@types/node/-/node-16.11.12.tgz", 796 | "integrity": "sha512-+2Iggwg7PxoO5Kyhvsq9VarmPbIelXP070HMImEpbtGCoyWNINQj4wzjbQCXzdHTRXnqufutJb5KAURZANNBAw==" 797 | }, 798 | "@types/qs": { 799 | "version": "6.9.7", 800 | "resolved": "https://registry.npmjs.org/@types/qs/-/qs-6.9.7.tgz", 801 | "integrity": "sha512-FGa1F62FT09qcrueBA6qYTrJPVDzah9a+493+o2PCXsesWHIn27G98TsSMs3WPNbZIEj4+VJf6saSFpvD+3Zsw==" 802 | }, 803 | "@types/range-parser": { 804 | "version": "1.2.4", 805 | "resolved": "https://registry.npmjs.org/@types/range-parser/-/range-parser-1.2.4.tgz", 806 | "integrity": "sha512-EEhsLsD6UsDM1yFhAvy0Cjr6VwmpMWqFBCb9w07wVugF7w9nfajxLuVmngTIpgS6svCnm6Vaw+MZhoDCKnOfsw==" 807 | }, 808 | "@types/redis": { 809 | "version": "4.0.11", 810 | "resolved": "https://registry.npmjs.org/@types/redis/-/redis-4.0.11.tgz", 811 | "integrity": "sha512-bI+gth8La8Wg/QCR1+V1fhrL9+LZUSWfcqpOj2Kc80ZQ4ffbdL173vQd5wovmoV9i071FU9oP2g6etLuEwb6Rg==", 812 | "requires": { 813 | "redis": "*" 814 | } 815 | }, 816 | "@types/serve-static": { 817 | "version": "1.13.10", 818 | "resolved": "https://registry.npmjs.org/@types/serve-static/-/serve-static-1.13.10.tgz", 819 | "integrity": "sha512-nCkHGI4w7ZgAdNkrEu0bv+4xNV/XDqW+DydknebMOQwkpDGx8G+HTlj7R7ABI8i8nKxVw0wtKPi1D+lPOkh4YQ==", 820 | "requires": { 821 | "@types/mime": "^1", 822 | "@types/node": "*" 823 | } 824 | }, 825 | "accepts": { 826 | "version": "1.3.7", 827 | "resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.7.tgz", 828 | "integrity": "sha512-Il80Qs2WjYlJIBNzNkK6KYqlVMTbZLXgHx2oT0pU/fjRHyEp+PEfEPY0R3WCwAGVOtauxh1hOxNgIf5bv7dQpA==", 829 | "requires": { 830 | "mime-types": "~2.1.24", 831 | "negotiator": "0.6.2" 832 | } 833 | }, 834 | "array-flatten": { 835 | "version": "1.1.1", 836 | "resolved": "https://registry.npmjs.org/array-flatten/-/array-flatten-1.1.1.tgz", 837 | "integrity": "sha1-ml9pkFGx5wczKPKgCJaLZOopVdI=" 838 | }, 839 | "body-parser": { 840 | "version": "1.19.0", 841 | "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.19.0.tgz", 842 | "integrity": "sha512-dhEPs72UPbDnAQJ9ZKMNTP6ptJaionhP5cBb541nXPlW60Jepo9RV/a4fX4XWW9CuFNK22krhrj1+rgzifNCsw==", 843 | "requires": { 844 | "bytes": "3.1.0", 845 | "content-type": "~1.0.4", 846 | "debug": "2.6.9", 847 | "depd": "~1.1.2", 848 | "http-errors": "1.7.2", 849 | "iconv-lite": "0.4.24", 850 | "on-finished": "~2.3.0", 851 | "qs": "6.7.0", 852 | "raw-body": "2.4.0", 853 | "type-is": "~1.6.17" 854 | } 855 | }, 856 | "bytes": { 857 | "version": "3.1.0", 858 | "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.0.tgz", 859 | "integrity": "sha512-zauLjrfCG+xvoyaqLoV8bLVXXNGC4JqlxFCutSDWA6fJrTo2ZuvLYTqZ7aHBLZSMOopbzwv8f+wZcVzfVTI2Dg==" 860 | }, 861 | "cluster-key-slot": { 862 | "version": "1.1.0", 863 | "resolved": "https://registry.npmjs.org/cluster-key-slot/-/cluster-key-slot-1.1.0.tgz", 864 | "integrity": "sha512-2Nii8p3RwAPiFwsnZvukotvow2rIHM+yQ6ZcBXGHdniadkYGZYiGmkHJIbZPIV9nfv7m/U1IPMVVcAhoWFeklw==" 865 | }, 866 | "content-disposition": { 867 | "version": "0.5.3", 868 | "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-0.5.3.tgz", 869 | "integrity": "sha512-ExO0774ikEObIAEV9kDo50o+79VCUdEB6n6lzKgGwupcVeRlhrj3qGAfwq8G6uBJjkqLrhT0qEYFcWng8z1z0g==", 870 | "requires": { 871 | "safe-buffer": "5.1.2" 872 | } 873 | }, 874 | "content-type": { 875 | "version": "1.0.4", 876 | "resolved": "https://registry.npmjs.org/content-type/-/content-type-1.0.4.tgz", 877 | "integrity": "sha512-hIP3EEPs8tB9AT1L+NUqtwOAps4mk2Zob89MWXMHjHWg9milF/j4osnnQLXBCBFBk/tvIG/tUc9mOUJiPBhPXA==" 878 | }, 879 | "cookie": { 880 | "version": "0.4.0", 881 | "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.4.0.tgz", 882 | "integrity": "sha512-+Hp8fLp57wnUSt0tY0tHEXh4voZRDnoIrZPqlo3DPiI4y9lwg/jqx+1Om94/W6ZaPDOUbnjOt/99w66zk+l1Xg==" 883 | }, 884 | "cookie-signature": { 885 | "version": "1.0.6", 886 | "resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.0.6.tgz", 887 | "integrity": "sha1-4wOogrNCzD7oylE6eZmXNNqzriw=" 888 | }, 889 | "debug": { 890 | "version": "2.6.9", 891 | "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", 892 | "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", 893 | "requires": { 894 | "ms": "2.0.0" 895 | } 896 | }, 897 | "depd": { 898 | "version": "1.1.2", 899 | "resolved": "https://registry.npmjs.org/depd/-/depd-1.1.2.tgz", 900 | "integrity": "sha1-m81S4UwJd2PnSbJ0xDRu0uVgtak=" 901 | }, 902 | "destroy": { 903 | "version": "1.0.4", 904 | "resolved": "https://registry.npmjs.org/destroy/-/destroy-1.0.4.tgz", 905 | "integrity": "sha1-l4hXRCxEdJ5CBmE+N5RiBYJqvYA=" 906 | }, 907 | "ee-first": { 908 | "version": "1.1.1", 909 | "resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz", 910 | "integrity": "sha1-WQxhFWsK4vTwJVcyoViyZrxWsh0=" 911 | }, 912 | "encodeurl": { 913 | "version": "1.0.2", 914 | "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-1.0.2.tgz", 915 | "integrity": "sha1-rT/0yG7C0CkyL1oCw6mmBslbP1k=" 916 | }, 917 | "escape-html": { 918 | "version": "1.0.3", 919 | "resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz", 920 | "integrity": "sha1-Aljq5NPQwJdN4cFpGI7wBR0dGYg=" 921 | }, 922 | "etag": { 923 | "version": "1.8.1", 924 | "resolved": "https://registry.npmjs.org/etag/-/etag-1.8.1.tgz", 925 | "integrity": "sha1-Qa4u62XvpiJorr/qg6x9eSmbCIc=" 926 | }, 927 | "express": { 928 | "version": "4.17.1", 929 | "resolved": "https://registry.npmjs.org/express/-/express-4.17.1.tgz", 930 | "integrity": "sha512-mHJ9O79RqluphRrcw2X/GTh3k9tVv8YcoyY4Kkh4WDMUYKRZUq0h1o0w2rrrxBqM7VoeUVqgb27xlEMXTnYt4g==", 931 | "requires": { 932 | "accepts": "~1.3.7", 933 | "array-flatten": "1.1.1", 934 | "body-parser": "1.19.0", 935 | "content-disposition": "0.5.3", 936 | "content-type": "~1.0.4", 937 | "cookie": "0.4.0", 938 | "cookie-signature": "1.0.6", 939 | "debug": "2.6.9", 940 | "depd": "~1.1.2", 941 | "encodeurl": "~1.0.2", 942 | "escape-html": "~1.0.3", 943 | "etag": "~1.8.1", 944 | "finalhandler": "~1.1.2", 945 | "fresh": "0.5.2", 946 | "merge-descriptors": "1.0.1", 947 | "methods": "~1.1.2", 948 | "on-finished": "~2.3.0", 949 | "parseurl": "~1.3.3", 950 | "path-to-regexp": "0.1.7", 951 | "proxy-addr": "~2.0.5", 952 | "qs": "6.7.0", 953 | "range-parser": "~1.2.1", 954 | "safe-buffer": "5.1.2", 955 | "send": "0.17.1", 956 | "serve-static": "1.14.1", 957 | "setprototypeof": "1.1.1", 958 | "statuses": "~1.5.0", 959 | "type-is": "~1.6.18", 960 | "utils-merge": "1.0.1", 961 | "vary": "~1.1.2" 962 | } 963 | }, 964 | "finalhandler": { 965 | "version": "1.1.2", 966 | "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.1.2.tgz", 967 | "integrity": "sha512-aAWcW57uxVNrQZqFXjITpW3sIUQmHGG3qSb9mUah9MgMC4NeWhNOlNjXEYq3HjRAvL6arUviZGGJsBg6z0zsWA==", 968 | "requires": { 969 | "debug": "2.6.9", 970 | "encodeurl": "~1.0.2", 971 | "escape-html": "~1.0.3", 972 | "on-finished": "~2.3.0", 973 | "parseurl": "~1.3.3", 974 | "statuses": "~1.5.0", 975 | "unpipe": "~1.0.0" 976 | } 977 | }, 978 | "forwarded": { 979 | "version": "0.2.0", 980 | "resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.2.0.tgz", 981 | "integrity": "sha512-buRG0fpBtRHSTCOASe6hD258tEubFoRLb4ZNA6NxMVHNw2gOcwHo9wyablzMzOA5z9xA9L1KNjk/Nt6MT9aYow==" 982 | }, 983 | "fresh": { 984 | "version": "0.5.2", 985 | "resolved": "https://registry.npmjs.org/fresh/-/fresh-0.5.2.tgz", 986 | "integrity": "sha1-PYyt2Q2XZWn6g1qx+OSyOhBWBac=" 987 | }, 988 | "generic-pool": { 989 | "version": "3.8.2", 990 | "resolved": "https://registry.npmjs.org/generic-pool/-/generic-pool-3.8.2.tgz", 991 | "integrity": "sha512-nGToKy6p3PAbYQ7p1UlWl6vSPwfwU6TMSWK7TTu+WUY4ZjyZQGniGGt2oNVvyNSpyZYSB43zMXVLcBm08MTMkg==" 992 | }, 993 | "graphql": { 994 | "version": "15.8.0", 995 | "resolved": "https://registry.npmjs.org/graphql/-/graphql-15.8.0.tgz", 996 | "integrity": "sha512-5gghUc24tP9HRznNpV2+FIoq3xKkj5dTQqf4v0CpdPbFVwFkWoxOM+o+2OC9ZSvjEMTjfmG9QT+gcvggTwW1zw==" 997 | }, 998 | "http-errors": { 999 | "version": "1.7.2", 1000 | "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-1.7.2.tgz", 1001 | "integrity": "sha512-uUQBt3H/cSIVfch6i1EuPNy/YsRSOUBXTVfZ+yR7Zjez3qjBz6i9+i4zjNaoqcoFVI4lQJ5plg63TvGfRSDCRg==", 1002 | "requires": { 1003 | "depd": "~1.1.2", 1004 | "inherits": "2.0.3", 1005 | "setprototypeof": "1.1.1", 1006 | "statuses": ">= 1.5.0 < 2", 1007 | "toidentifier": "1.0.0" 1008 | } 1009 | }, 1010 | "iconv-lite": { 1011 | "version": "0.4.24", 1012 | "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz", 1013 | "integrity": "sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==", 1014 | "requires": { 1015 | "safer-buffer": ">= 2.1.2 < 3" 1016 | } 1017 | }, 1018 | "inherits": { 1019 | "version": "2.0.3", 1020 | "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.3.tgz", 1021 | "integrity": "sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4=" 1022 | }, 1023 | "ipaddr.js": { 1024 | "version": "1.9.1", 1025 | "resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-1.9.1.tgz", 1026 | "integrity": "sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g==" 1027 | }, 1028 | "media-typer": { 1029 | "version": "0.3.0", 1030 | "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz", 1031 | "integrity": "sha1-hxDXrwqmJvj/+hzgAWhUUmMlV0g=" 1032 | }, 1033 | "merge-descriptors": { 1034 | "version": "1.0.1", 1035 | "resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-1.0.1.tgz", 1036 | "integrity": "sha1-sAqqVW3YtEVoFQ7J0blT8/kMu2E=" 1037 | }, 1038 | "methods": { 1039 | "version": "1.1.2", 1040 | "resolved": "https://registry.npmjs.org/methods/-/methods-1.1.2.tgz", 1041 | "integrity": "sha1-VSmk1nZUE07cxSZmVoNbD4Ua/O4=" 1042 | }, 1043 | "mime": { 1044 | "version": "1.6.0", 1045 | "resolved": "https://registry.npmjs.org/mime/-/mime-1.6.0.tgz", 1046 | "integrity": "sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg==" 1047 | }, 1048 | "mime-db": { 1049 | "version": "1.51.0", 1050 | "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.51.0.tgz", 1051 | "integrity": "sha512-5y8A56jg7XVQx2mbv1lu49NR4dokRnhZYTtL+KGfaa27uq4pSTXkwQkFJl4pkRMyNFz/EtYDSkiiEHx3F7UN6g==" 1052 | }, 1053 | "mime-types": { 1054 | "version": "2.1.34", 1055 | "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.34.tgz", 1056 | "integrity": "sha512-6cP692WwGIs9XXdOO4++N+7qjqv0rqxxVvJ3VHPh/Sc9mVZcQP+ZGhkKiTvWMQRr2tbHkJP/Yn7Y0npb3ZBs4A==", 1057 | "requires": { 1058 | "mime-db": "1.51.0" 1059 | } 1060 | }, 1061 | "ms": { 1062 | "version": "2.0.0", 1063 | "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", 1064 | "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=" 1065 | }, 1066 | "negotiator": { 1067 | "version": "0.6.2", 1068 | "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.2.tgz", 1069 | "integrity": "sha512-hZXc7K2e+PgeI1eDBe/10Ard4ekbfrrqG8Ep+8Jmf4JID2bNg7NvCPOZN+kfF574pFQI7mum2AUqDidoKqcTOw==" 1070 | }, 1071 | "node-fetch": { 1072 | "version": "2.6.6", 1073 | "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.6.6.tgz", 1074 | "integrity": "sha512-Z8/6vRlTUChSdIgMa51jxQ4lrw/Jy5SOW10ObaA47/RElsAN2c5Pn8bTgFGWn/ibwzXTE8qwr1Yzx28vsecXEA==", 1075 | "requires": { 1076 | "whatwg-url": "^5.0.0" 1077 | } 1078 | }, 1079 | "on-finished": { 1080 | "version": "2.3.0", 1081 | "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.3.0.tgz", 1082 | "integrity": "sha1-IPEzZIGwg811M3mSoWlxqi2QaUc=", 1083 | "requires": { 1084 | "ee-first": "1.1.1" 1085 | } 1086 | }, 1087 | "parseurl": { 1088 | "version": "1.3.3", 1089 | "resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.3.tgz", 1090 | "integrity": "sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ==" 1091 | }, 1092 | "path-to-regexp": { 1093 | "version": "0.1.7", 1094 | "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.7.tgz", 1095 | "integrity": "sha1-32BBeABfUi8V60SQ5yR6G/qmf4w=" 1096 | }, 1097 | "proxy-addr": { 1098 | "version": "2.0.7", 1099 | "resolved": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-2.0.7.tgz", 1100 | "integrity": "sha512-llQsMLSUDUPT44jdrU/O37qlnifitDP+ZwrmmZcoSKyLKvtZxpyV0n2/bD/N4tBAAZ/gJEdZU7KMraoK1+XYAg==", 1101 | "requires": { 1102 | "forwarded": "0.2.0", 1103 | "ipaddr.js": "1.9.1" 1104 | } 1105 | }, 1106 | "qs": { 1107 | "version": "6.7.0", 1108 | "resolved": "https://registry.npmjs.org/qs/-/qs-6.7.0.tgz", 1109 | "integrity": "sha512-VCdBRNFTX1fyE7Nb6FYoURo/SPe62QCaAyzJvUjwRaIsc+NePBEniHlvxFmmX56+HZphIGtV0XeCirBtpDrTyQ==" 1110 | }, 1111 | "range-parser": { 1112 | "version": "1.2.1", 1113 | "resolved": "https://registry.npmjs.org/range-parser/-/range-parser-1.2.1.tgz", 1114 | "integrity": "sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg==" 1115 | }, 1116 | "raw-body": { 1117 | "version": "2.4.0", 1118 | "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-2.4.0.tgz", 1119 | "integrity": "sha512-4Oz8DUIwdvoa5qMJelxipzi/iJIi40O5cGV1wNYp5hvZP8ZN0T+jiNkL0QepXs+EsQ9XJ8ipEDoiH70ySUJP3Q==", 1120 | "requires": { 1121 | "bytes": "3.1.0", 1122 | "http-errors": "1.7.2", 1123 | "iconv-lite": "0.4.24", 1124 | "unpipe": "1.0.0" 1125 | } 1126 | }, 1127 | "redis": { 1128 | "version": "4.0.1", 1129 | "resolved": "https://registry.npmjs.org/redis/-/redis-4.0.1.tgz", 1130 | "integrity": "sha512-qfcq1oz2ci7pNdCfTLLEuKhS8jZ17dFiT1exogOr+jd3EVP/h9qpy7K+VajB4BXA0k8q68KFqR6HrliKV6jt1Q==", 1131 | "requires": { 1132 | "@node-redis/client": "^1.0.1", 1133 | "@node-redis/json": "^1.0.1", 1134 | "@node-redis/search": "^1.0.1", 1135 | "@node-redis/time-series": "^1.0.0" 1136 | } 1137 | }, 1138 | "redis-errors": { 1139 | "version": "1.2.0", 1140 | "resolved": "https://registry.npmjs.org/redis-errors/-/redis-errors-1.2.0.tgz", 1141 | "integrity": "sha1-62LSrbFeTq9GEMBK/hUpOEJQq60=" 1142 | }, 1143 | "redis-parser": { 1144 | "version": "3.0.0", 1145 | "resolved": "https://registry.npmjs.org/redis-parser/-/redis-parser-3.0.0.tgz", 1146 | "integrity": "sha1-tm2CjNyv5rS4pCin3vTGvKwxyLQ=", 1147 | "requires": { 1148 | "redis-errors": "^1.0.0" 1149 | } 1150 | }, 1151 | "safe-buffer": { 1152 | "version": "5.1.2", 1153 | "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", 1154 | "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==" 1155 | }, 1156 | "safer-buffer": { 1157 | "version": "2.1.2", 1158 | "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", 1159 | "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==" 1160 | }, 1161 | "send": { 1162 | "version": "0.17.1", 1163 | "resolved": "https://registry.npmjs.org/send/-/send-0.17.1.tgz", 1164 | "integrity": "sha512-BsVKsiGcQMFwT8UxypobUKyv7irCNRHk1T0G680vk88yf6LBByGcZJOTJCrTP2xVN6yI+XjPJcNuE3V4fT9sAg==", 1165 | "requires": { 1166 | "debug": "2.6.9", 1167 | "depd": "~1.1.2", 1168 | "destroy": "~1.0.4", 1169 | "encodeurl": "~1.0.2", 1170 | "escape-html": "~1.0.3", 1171 | "etag": "~1.8.1", 1172 | "fresh": "0.5.2", 1173 | "http-errors": "~1.7.2", 1174 | "mime": "1.6.0", 1175 | "ms": "2.1.1", 1176 | "on-finished": "~2.3.0", 1177 | "range-parser": "~1.2.1", 1178 | "statuses": "~1.5.0" 1179 | }, 1180 | "dependencies": { 1181 | "ms": { 1182 | "version": "2.1.1", 1183 | "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.1.tgz", 1184 | "integrity": "sha512-tgp+dl5cGk28utYktBsrFqA7HKgrhgPsg6Z/EfhWI4gl1Hwq8B/GmY/0oXZ6nF8hDVesS/FpnYaD/kOWhYQvyg==" 1185 | } 1186 | } 1187 | }, 1188 | "serve-static": { 1189 | "version": "1.14.1", 1190 | "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-1.14.1.tgz", 1191 | "integrity": "sha512-JMrvUwE54emCYWlTI+hGrGv5I8dEwmco/00EvkzIIsR7MqrHonbD9pO2MOfFnpFntl7ecpZs+3mW+XbQZu9QCg==", 1192 | "requires": { 1193 | "encodeurl": "~1.0.2", 1194 | "escape-html": "~1.0.3", 1195 | "parseurl": "~1.3.3", 1196 | "send": "0.17.1" 1197 | } 1198 | }, 1199 | "setprototypeof": { 1200 | "version": "1.1.1", 1201 | "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.1.1.tgz", 1202 | "integrity": "sha512-JvdAWfbXeIGaZ9cILp38HntZSFSo3mWg6xGcJJsd+d4aRMOqauag1C63dJfDw7OaMYwEbHMOxEZ1lqVRYP2OAw==" 1203 | }, 1204 | "statuses": { 1205 | "version": "1.5.0", 1206 | "resolved": "https://registry.npmjs.org/statuses/-/statuses-1.5.0.tgz", 1207 | "integrity": "sha1-Fhx9rBd2Wf2YEfQ3cfqZOBR4Yow=" 1208 | }, 1209 | "toidentifier": { 1210 | "version": "1.0.0", 1211 | "resolved": "https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.0.tgz", 1212 | "integrity": "sha512-yaOH/Pk/VEhBWWTlhI+qXxDFXlejDGcQipMlyxda9nthulaxLZUNcUqFxokp0vcYnvteJln5FNQDRrxj3YcbVw==" 1213 | }, 1214 | "tr46": { 1215 | "version": "0.0.3", 1216 | "resolved": "https://registry.npmjs.org/tr46/-/tr46-0.0.3.tgz", 1217 | "integrity": "sha1-gYT9NH2snNwYWZLzpmIuFLnZq2o=" 1218 | }, 1219 | "type-is": { 1220 | "version": "1.6.18", 1221 | "resolved": "https://registry.npmjs.org/type-is/-/type-is-1.6.18.tgz", 1222 | "integrity": "sha512-TkRKr9sUTxEH8MdfuCSP7VizJyzRNMjj2J2do2Jr3Kym598JVdEksuzPQCnlFPW4ky9Q+iA+ma9BGm06XQBy8g==", 1223 | "requires": { 1224 | "media-typer": "0.3.0", 1225 | "mime-types": "~2.1.24" 1226 | } 1227 | }, 1228 | "unpipe": { 1229 | "version": "1.0.0", 1230 | "resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz", 1231 | "integrity": "sha1-sr9O6FFKrmFltIF4KdIbLvSZBOw=" 1232 | }, 1233 | "utils-merge": { 1234 | "version": "1.0.1", 1235 | "resolved": "https://registry.npmjs.org/utils-merge/-/utils-merge-1.0.1.tgz", 1236 | "integrity": "sha1-n5VxD1CiZ5R7LMwSR0HBAoQn5xM=" 1237 | }, 1238 | "vary": { 1239 | "version": "1.1.2", 1240 | "resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz", 1241 | "integrity": "sha1-IpnwLG3tMNSllhsLn3RSShj2NPw=" 1242 | }, 1243 | "webidl-conversions": { 1244 | "version": "3.0.1", 1245 | "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-3.0.1.tgz", 1246 | "integrity": "sha1-JFNCdeKnvGvnvIZhHMFq4KVlSHE=" 1247 | }, 1248 | "whatwg-url": { 1249 | "version": "5.0.0", 1250 | "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-5.0.0.tgz", 1251 | "integrity": "sha1-lmRU6HZUYuN2RNNib2dCzotwll0=", 1252 | "requires": { 1253 | "tr46": "~0.0.3", 1254 | "webidl-conversions": "^3.0.0" 1255 | } 1256 | }, 1257 | "yallist": { 1258 | "version": "4.0.0", 1259 | "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", 1260 | "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==" 1261 | } 1262 | } 1263 | } 1264 | -------------------------------------------------------------------------------- /src/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "emberql", 3 | "version": "1.0.4", 4 | "description": "EmberQL is an intuitive, lightweight Node module that facilitates caching data from GraphQL queries, and implements a dynamic data persistence system that monitors the status of the primary database and modifies cache invalidation accordingly.", 5 | "main": "EmberQL.ts", 6 | "scripts": { 7 | "test": "echo \"Error: no test specified\" && exit 1" 8 | }, 9 | "repository": { 10 | "type": "git", 11 | "url": "git+https://github.com/oslabs-beta/EmberQL.git" 12 | }, 13 | "keywords": [ 14 | "GraphQL", 15 | "Cache", 16 | "Caching", 17 | "Redis", 18 | "Optimize", 19 | "Query", 20 | "Ember", 21 | "fault", 22 | "tolerance", 23 | "heartbeat", 24 | "database", 25 | "db" 26 | ], 27 | "author": "Cristian De Los Rios, Manjunath Ajjappa Pattanashetty, Mike Masatsugu, Ram Marimuthu, Tyler Pohn", 28 | "license": "MIT", 29 | "bugs": { 30 | "url": "http://github.com/oslabs-beta/EmberQL/issues" 31 | }, 32 | "homepage": "http://emberql.com/", 33 | "dependencies": { 34 | "@types/express": "^4.17.13", 35 | "@types/graphql": "^14.5.0", 36 | "@types/redis": "^4.0.11", 37 | "express": "^4.17.1", 38 | "graphql": "^15.7.2", 39 | "node-fetch": "^2.6.6", 40 | "redis": "^4.0.1" 41 | }, 42 | "devDependencies": {} 43 | } 44 | --------------------------------------------------------------------------------