├── .DS_Store ├── .eslintignore ├── .eslintrc.json ├── .gitignore ├── LICENSE.txt ├── README.md ├── __tests__ └── package.json ├── appspec.yml ├── dist ├── 09b15ca8b7c8c9c0e312689c43705fc9.png ├── 196fe2c644b80ada4fd0a665f0206307.png ├── 1f2da45411728b5788423767bcf13981.png ├── 29bee30226fd116d1e01b7f43c79e93c.png ├── 2ab3fc407e5d2d5c1486b43b1a1aa2df.png ├── 2b2ccd24da1e291a4d7b966cd5aaae43.png ├── 619ac5ec97620f0e274878d51e4e47c6.png ├── 69ccd4eabb21b1ad8eac50dab5903a05.png ├── 6feb0ea439b5cb6ee74291fa3e2e45d2.png ├── 83fe72be1352299b2d3df5d657664954.png ├── a39f6a67fbcf0e2868f9603dfbfe500e.png ├── aad6c641a2d7c79758da48abda119985.png ├── ade232f25687acaeb8b1530bf46bd3b2.png ├── af108db04a54d784da9bf75244a6daf5.png ├── bundle.js ├── bundle.js.LICENSE.txt ├── d15af6ac3714df84fd5293a896b05972.png ├── f503499b382dcc2e9c767d0d1965cea7.png ├── fc6b9451733c620ceb86a49ca3953d65.png └── index.html ├── npm-package ├── Helpers │ └── cleanQuery.js ├── lightql.ts ├── package-lock.json └── package.json ├── package-lock.json ├── package.json ├── scripts ├── install_dependencies └── start_server ├── src ├── .DS_Store ├── assets │ ├── LightQL.png │ ├── black-logo.png │ ├── cassidy.png │ ├── cyrus.png │ ├── docs-example.png │ ├── drew.png │ ├── graphql-logo.png │ ├── jest-js-icon.png │ ├── local-forage.png │ ├── lower-left-lines.png │ ├── nobg-LightQL.png │ ├── npm-vector.png │ ├── pierce.png │ ├── rhea.png │ ├── typescript-logo.png │ └── upper-right-lines.png ├── client │ ├── exports.d.ts │ ├── index.html │ ├── index.tsx │ └── webpage │ │ ├── components │ │ ├── AboutUs.tsx │ │ ├── App.tsx │ │ ├── Docs.tsx │ │ ├── Homepage.tsx │ │ ├── aboutUsComponents │ │ │ └── SingleTile.tsx │ │ └── homepageComponents │ │ │ ├── Demo.js │ │ │ ├── Descriptions.tsx │ │ │ ├── Hero.tsx │ │ │ ├── OneBox.tsx │ │ │ └── ThreeBox.tsx │ │ └── styling │ │ ├── aboutUs.scss │ │ ├── boxes.scss │ │ ├── demo.scss │ │ ├── descriptions.scss │ │ ├── docs.scss │ │ ├── hero.scss │ │ └── sitewide.scss └── simulation │ ├── graphQLSchemas.ts │ ├── models.ts │ ├── server.ts │ ├── simpleTable.sql │ └── user_data.sql ├── tsconfig.json ├── tslint.json └── webpack.config.js /.DS_Store: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/oslabs-beta/LightQL/83dff8889140f841a9bfdae409f25b2db56e76d5/.DS_Store -------------------------------------------------------------------------------- /.eslintignore: -------------------------------------------------------------------------------- 1 | src/models -------------------------------------------------------------------------------- /.eslintrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "parser": "babel-eslint", 3 | "parserOptions": { 4 | "sourceType": "module" 5 | } 6 | } -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /node_modules/ 2 | .env 3 | -------------------------------------------------------------------------------- /LICENSE.txt: -------------------------------------------------------------------------------- 1 | 2 | 3 | MIT License 4 | 5 | Copyright (c) 2022 LightQL 6 | 7 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: 8 | 9 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 10 | 11 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 12 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 |
5 |
6 | 7 | Logo 8 | 9 | 10 |

LightQL

11 | 12 |

13 | Welcome to LightQL. A lightspeed, lightweight client-side cache for GraphQL. 14 |
15 | Explore the docs » 16 |
17 |
18 | View Demo 19 |

20 |
21 | 22 | 23 | 24 | Table of Contents 25 |
    26 |
  1. Overview
  2. 27 |
  3. Built With
  4. 28 |
  5. Prerequisites
  6. 29 |
  7. Demo
  8. 30 |
  9. Installation
  10. 31 |
  11. How LightQL works
  12. 32 |
  13. Roadmap
  14. 33 |
  15. Contributing
  16. 34 |
  17. License
  18. 35 |
  19. Contact
  20. 36 |
  21. Authors
  22. 37 |
38 | 39 | 40 | 41 | ## Overview 42 | 43 | LightQL is an easy-to-use super fast and lightweight Javascript library providing a client-side caching solution for GraphQL. Use LightQL for extremely low latency retrieval of persistent cache data. 44 | 45 |

(back to top)

46 | 47 | ## Built With 48 | 49 | - GraphQL 50 | - Typescript 51 | - Node/Express 52 | - React 53 | - Chart.js 54 | - Jest 55 | - Supertest 56 | - AWS RDS 57 | - Webpack 58 | 59 |

(back to top)

60 | 61 | 62 | 63 | ## Prerequisites 64 | 65 | - All developers will have to integrate GraphQL into their functionality. This means setting up your GraphQL schemas and resolvers as well as your database. 66 | - Our package is intended to work in tandem with a full-stack application where the frontend makes query requests to the backend, so please set this up as well before using our package. 67 | 68 | ## Demo 69 | 70 | - Head over to the home page of our website ([lightql.com](https://www.lightql.com/)) 71 | - Using our demo is a simple 2-step process. 72 | - First, press the “Run the demo” button to see the resulting GraphQL query 73 | result. If you divert your attention to the metrics box down below you 74 | will see an uncached run time populate. This statistic represents the 75 | time it took to make a GraphQL fetch and receive the response data. You 76 | can also see the spike in time upwards on the graph provided on the 77 | right. 78 | - If you press the run query button again you will notice that your 79 | cached run time metric will now render. The graph on the right will also 80 | dive down as the response time has significantly decreased. The uncached run time should never change after this, as we are now retrieving your data from the cache every instance forward showing our super lightning speed of retrieval! 81 | 82 | \*A small disclaimer: It should be noted that your first query will have a significantly higher runtime than the other first queries because it is establishing a connection. 83 | 84 | ## Installation 85 | 86 | 1. If this is your first time using LightQL, run the following command in your terminal: 87 | 2. ```sh 88 | npm install lightql-cache 89 | ``` 90 | 3. In your frontend app’s file (e.g. your filename.js file), you want to import our LightQL module to handle GraphQL requests using the ES6 module format. This can also be done in your React (.jsx), Typescript (.ts and .tsx), and similar file formats. 91 | ```js 92 | import { LRUCache, DoublyLinkedList, DLLNode } from ‘lightql-cache’; 93 | ``` 94 | 4. Next, create an instance of a cache using the de-structured LRUCache object, passing in a capacity as the first argument. The capacity must be an integer and greater than zero. You must also pass in a valid GraphQL endpoint as a string as the second argument. We have set a capacity of 3 in our example below: 95 | ```js 96 | const cache = new LRUCache(3, 'http://localhost:3000/graphql'); 97 | ``` 98 | 5. Now, to make your first query to the cache, you create a GraphQL formatted query string based on your requirements, for example: 99 | ```js 100 | const graphqlQueryStr = ` { 101 | user { 102 | user_name, 103 | song_name, 104 | movie_name 105 | } 106 | }`; 107 | ``` 108 | 6. Next, we invoke the get function associated with the named variable you have for the LRUCache, and pass in your query string and associate variables if necessary. The get function always returns a promise, therefore it is best to generate an async function that leverages the await syntax to resolve the data returned from the cache. 109 | ```js 110 | const callLightQL = async () => { 111 | const cacheGet = await cache.get(graphqlQueryStr, variables); 112 | }; 113 | ``` 114 | 7. Now, you are properly set up and can use the data as you wish! 115 | 8. A quick example: imagine you had a React app that included Chart.js functionality that you wanted to display on a specific page. You could import LightQL cache to effectively retrieve your data from your database, and then store it in your client-side LightQL caching solution. Then, every time you wanted to display the correct chart.js data, you could grab the correct information from your LightQL cache with extremely low latency time. Example code below: 116 | 117 | Logo 118 | 119 |

(back to top)

120 | 121 | 122 | 123 | ## How LightQL works 124 | 125 | LightQL caches responses to GraphQL formatted queries as key-value object representations of the graph’s nodes, making it possible to satisfy GraphQL queries from a cached data store. 126 | 127 | When your application needs data, it checks the cache first to determine whether the data is available. If the data is available (a cache hit), the cached data is returned, and the response is issued to the user. If the data isn’t available (a cache miss), the database is queried for the data. The cache is then populated with the data that is retrieved from the database, and the data is returned to the user. The benefit of this strategy is that the cache contains only data that the application actually requests, keeping the cache size cost-effective. Further, this increases cache hits, reduces network calls, and significantly reduces the overall runtime and latency. 128 | 129 | LightQL’s LRUCache function creates an instance of the LightQL cache. A capacity and GraphQL endpoint are the two necessary arguments to pass in to this function. The LRUCache consists of a HashMap and Doubly-Linked List to store GraphQL query responses. The combination of these two data structures offer best-case scenario time complexity (O(1)) for insertion, deletion, and lookup. 130 | 131 | ```js 132 | function LRUCache(capacity, graphqlEndpoint) { 133 | this.capacity = Math.floor(capacity); 134 | this.map = new Map(); 135 | this.dll = new DoublyLinkedList(); 136 | this.graphqlEndpoint = graphqlEndpoint; 137 | } 138 | ``` 139 | 140 | LightQL leverages localForage and IndexedDB to persist cached data between sessions. localForage is a fast and simple storage library for Javascript. localForage leverages asynchronous storage through IndexedDB, a JS-based object-oriented database that runs in your browser, with a simple, localStorage-like API. Whenever you run a query through LightQL, the capacity, HashMap, Doubly-Linked List, and graphqlEndpoint are loaded into memory if available through the localForage setItem method: 141 | 142 | ```js 143 | localforage.setItem(); 144 | ``` 145 | 146 | Additionally, before returning data to users, LightQL writes our cache data structures and graphqlEndpoint to our persistent memory in IndexedDB through the localForage getItem method: 147 | 148 | ```js 149 | localforage.getItem(); 150 | ``` 151 | 152 | Developers can entrust LightQL to handle their GraphQL caching needs simply and effectively, so they can focus on working what matters most. 153 | 154 |

(back to top)

155 | 156 | 157 | 158 | ## Roadmap 159 | 160 | Upcoming planned features: 161 | 162 | - Caching of query mutations 163 | - Cache pruning/invalidation strategy 164 | - Partial retrieval 165 | 166 |

(back to top)

167 | 168 | 169 | 170 | ## Contributing 171 | 172 | Here at LightQL we created our open-source project with the intention to further expand upon and improve this tool for years to come. 173 | 174 | That's where the community comes in! If you have an idea that might make LightQL better we always encourage contributions. Simply follow the steps below to submit the changes you would make. 175 | 176 | - Fork LightQL 177 | - Pull down our dev branch with command (`git pull origin dev`) 178 | - Create your own Feature Branch with the command (`git checkout -b `) 179 | - Add your changes with the command (`git add .`) 180 | - Stage and commit your changes with the command (`git commit -m ""`) 181 | - Merge your branch with the dev branch locally with the command (`git merge dev`) 182 | - Resolve any merge conflicts 183 | - Push up your branch with the command (`git push origin `) 184 | - Open a pull request 185 | - Don't forget to star this repo! We look forward to your contributions! 186 | 187 |

(back to top)

188 | 189 | 190 | 191 | ## License 192 | 193 | Distributed under the MIT License. See `LICENSE.txt` for more information. 194 | 195 |

(back to top)

196 | 197 | 198 | 199 | ## Contact 200 | 201 | Visit https://lightql.com/aboutus to reach out to the team! 202 | 203 |

(back to top)

204 | 205 | 206 | 207 | ## Authors 208 | 209 | - Cassidy Johnson 210 | - Cyrus Yari 211 | - Drew Tucker 212 | - Pierce Heska 213 | - Rhea Wu 214 | 215 |

(back to top)

216 | -------------------------------------------------------------------------------- /__tests__/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "tests", 3 | "version": "1.0.0", 4 | "description": "", 5 | "main": "index.js", 6 | "scripts": { 7 | "test": "echo \"Error: no test specified\" && exit 1" 8 | }, 9 | "keywords": [], 10 | "author": "", 11 | "license": "ISC" 12 | } 13 | -------------------------------------------------------------------------------- /appspec.yml: -------------------------------------------------------------------------------- 1 | version: 0.0 2 | os: macos 3 | files: 4 | - source: /src/client/index.html 5 | destination: /var/www/html/ 6 | hooks: 7 | BeforeInstall: 8 | - location: scripts/install_dependencies 9 | timeout: 300 10 | runas: root 11 | - location: scripts/start_server 12 | timeout: 300 13 | runas: root -------------------------------------------------------------------------------- /dist/09b15ca8b7c8c9c0e312689c43705fc9.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/oslabs-beta/LightQL/83dff8889140f841a9bfdae409f25b2db56e76d5/dist/09b15ca8b7c8c9c0e312689c43705fc9.png -------------------------------------------------------------------------------- /dist/196fe2c644b80ada4fd0a665f0206307.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/oslabs-beta/LightQL/83dff8889140f841a9bfdae409f25b2db56e76d5/dist/196fe2c644b80ada4fd0a665f0206307.png -------------------------------------------------------------------------------- /dist/1f2da45411728b5788423767bcf13981.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/oslabs-beta/LightQL/83dff8889140f841a9bfdae409f25b2db56e76d5/dist/1f2da45411728b5788423767bcf13981.png -------------------------------------------------------------------------------- /dist/29bee30226fd116d1e01b7f43c79e93c.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/oslabs-beta/LightQL/83dff8889140f841a9bfdae409f25b2db56e76d5/dist/29bee30226fd116d1e01b7f43c79e93c.png -------------------------------------------------------------------------------- /dist/2ab3fc407e5d2d5c1486b43b1a1aa2df.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/oslabs-beta/LightQL/83dff8889140f841a9bfdae409f25b2db56e76d5/dist/2ab3fc407e5d2d5c1486b43b1a1aa2df.png -------------------------------------------------------------------------------- /dist/2b2ccd24da1e291a4d7b966cd5aaae43.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/oslabs-beta/LightQL/83dff8889140f841a9bfdae409f25b2db56e76d5/dist/2b2ccd24da1e291a4d7b966cd5aaae43.png -------------------------------------------------------------------------------- /dist/619ac5ec97620f0e274878d51e4e47c6.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/oslabs-beta/LightQL/83dff8889140f841a9bfdae409f25b2db56e76d5/dist/619ac5ec97620f0e274878d51e4e47c6.png -------------------------------------------------------------------------------- /dist/69ccd4eabb21b1ad8eac50dab5903a05.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/oslabs-beta/LightQL/83dff8889140f841a9bfdae409f25b2db56e76d5/dist/69ccd4eabb21b1ad8eac50dab5903a05.png -------------------------------------------------------------------------------- /dist/6feb0ea439b5cb6ee74291fa3e2e45d2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/oslabs-beta/LightQL/83dff8889140f841a9bfdae409f25b2db56e76d5/dist/6feb0ea439b5cb6ee74291fa3e2e45d2.png -------------------------------------------------------------------------------- /dist/83fe72be1352299b2d3df5d657664954.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/oslabs-beta/LightQL/83dff8889140f841a9bfdae409f25b2db56e76d5/dist/83fe72be1352299b2d3df5d657664954.png -------------------------------------------------------------------------------- /dist/a39f6a67fbcf0e2868f9603dfbfe500e.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/oslabs-beta/LightQL/83dff8889140f841a9bfdae409f25b2db56e76d5/dist/a39f6a67fbcf0e2868f9603dfbfe500e.png -------------------------------------------------------------------------------- /dist/aad6c641a2d7c79758da48abda119985.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/oslabs-beta/LightQL/83dff8889140f841a9bfdae409f25b2db56e76d5/dist/aad6c641a2d7c79758da48abda119985.png -------------------------------------------------------------------------------- /dist/ade232f25687acaeb8b1530bf46bd3b2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/oslabs-beta/LightQL/83dff8889140f841a9bfdae409f25b2db56e76d5/dist/ade232f25687acaeb8b1530bf46bd3b2.png -------------------------------------------------------------------------------- /dist/af108db04a54d784da9bf75244a6daf5.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/oslabs-beta/LightQL/83dff8889140f841a9bfdae409f25b2db56e76d5/dist/af108db04a54d784da9bf75244a6daf5.png -------------------------------------------------------------------------------- /dist/bundle.js.LICENSE.txt: -------------------------------------------------------------------------------- 1 | /*! 2 | localForage -- Offline Storage, Improved 3 | Version 1.10.0 4 | https://localforage.github.io/localForage 5 | (c) 2013-2017 Mozilla, Apache License 2.0 6 | */ 7 | 8 | /*! 9 | * @kurkle/color v0.2.1 10 | * https://github.com/kurkle/color#readme 11 | * (c) 2022 Jukka Kurkela 12 | * Released under the MIT License 13 | */ 14 | 15 | /*! 16 | * Chart.js v4.0.1 17 | * https://www.chartjs.org 18 | * (c) 2022 Chart.js Contributors 19 | * Released under the MIT License 20 | */ 21 | 22 | /*! regenerator-runtime -- Copyright (c) 2014-present, Facebook, Inc. -- license (MIT): https://github.com/facebook/regenerator/blob/main/LICENSE */ 23 | 24 | /** 25 | * @license React 26 | * react-dom.production.min.js 27 | * 28 | * Copyright (c) Facebook, Inc. and its affiliates. 29 | * 30 | * This source code is licensed under the MIT license found in the 31 | * LICENSE file in the root directory of this source tree. 32 | */ 33 | 34 | /** 35 | * @license React 36 | * react.production.min.js 37 | * 38 | * Copyright (c) Facebook, Inc. and its affiliates. 39 | * 40 | * This source code is licensed under the MIT license found in the 41 | * LICENSE file in the root directory of this source tree. 42 | */ 43 | 44 | /** 45 | * @license React 46 | * scheduler.production.min.js 47 | * 48 | * Copyright (c) Facebook, Inc. and its affiliates. 49 | * 50 | * This source code is licensed under the MIT license found in the 51 | * LICENSE file in the root directory of this source tree. 52 | */ 53 | 54 | /** 55 | * @remix-run/router v1.0.4 56 | * 57 | * Copyright (c) Remix Software Inc. 58 | * 59 | * This source code is licensed under the MIT license found in the 60 | * LICENSE.md file in the root directory of this source tree. 61 | * 62 | * @license MIT 63 | */ 64 | 65 | /** 66 | * React Router DOM v6.4.4 67 | * 68 | * Copyright (c) Remix Software Inc. 69 | * 70 | * This source code is licensed under the MIT license found in the 71 | * LICENSE.md file in the root directory of this source tree. 72 | * 73 | * @license MIT 74 | */ 75 | 76 | /** 77 | * React Router v6.4.4 78 | * 79 | * Copyright (c) Remix Software Inc. 80 | * 81 | * This source code is licensed under the MIT license found in the 82 | * LICENSE.md file in the root directory of this source tree. 83 | * 84 | * @license MIT 85 | */ 86 | -------------------------------------------------------------------------------- /dist/d15af6ac3714df84fd5293a896b05972.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/oslabs-beta/LightQL/83dff8889140f841a9bfdae409f25b2db56e76d5/dist/d15af6ac3714df84fd5293a896b05972.png -------------------------------------------------------------------------------- /dist/f503499b382dcc2e9c767d0d1965cea7.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/oslabs-beta/LightQL/83dff8889140f841a9bfdae409f25b2db56e76d5/dist/f503499b382dcc2e9c767d0d1965cea7.png -------------------------------------------------------------------------------- /dist/fc6b9451733c620ceb86a49ca3953d65.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/oslabs-beta/LightQL/83dff8889140f841a9bfdae409f25b2db56e76d5/dist/fc6b9451733c620ceb86a49ca3953d65.png -------------------------------------------------------------------------------- /dist/index.html: -------------------------------------------------------------------------------- 1 | LightQL
-------------------------------------------------------------------------------- /npm-package/Helpers/cleanQuery.js: -------------------------------------------------------------------------------- 1 | //Custom function to clean GraphQL Queries 2 | const cleanQuery = (query) => { 3 | let queryStr = JSON.stringify(query); 4 | let resultStr = queryStr.replace(/\s+/g, ' ').trim(); 5 | resultStr = resultStr.replace(/\s+/g, ' ').trim(); 6 | resultStr = resultStr.replace(/\\n/g, ' '); 7 | resultStr = resultStr.replace('/', ''); 8 | resultStr = resultStr.replace(/\\/g, ''); 9 | resultStr = resultStr.match(/\(.*\)/)[0]; 10 | return resultStr; 11 | } 12 | 13 | module.exports = cleanQuery; -------------------------------------------------------------------------------- /npm-package/lightql.ts: -------------------------------------------------------------------------------- 1 | /*localForage is a fast and simple storage library for Javascript. localForage leverages asynchronous storage thorugh IndexedDB, a JS-based object-oriented database that runs in your browser, with a simple, localStorage-like API. LightQL leverages localForage and IndexedDB to persist cached data between sessions. Whenever you run a query through LightQL, the capacity, HashMap, Doubly-Linked List, and graphqlEndpoint are loaded into memory if available through the localForage method: localforage.setItem(). Additionally, before returning data to users, LighQL writes our cache data stuctures and graphqlEndpoint to our persistent memory in IndexedDB through the localForage method: localforage.getItem(). 2 | */ 3 | const localforage = require("localforage"); 4 | 5 | //LRUCache function creates an instance of the LightQL cache. A capacity and graphQL endpoint are necessary arguments to pass in. 6 | function LRUCache(capacity, graphqlEndpoint) { 7 | this.capacity = Math.floor(capacity); 8 | this.map = new Map(); 9 | this.dll = new DoublyLinkedList(); 10 | this.graphqlEndpoint = graphqlEndpoint; 11 | } 12 | 13 | // Edge case handler to check if hashmap and dll are the same size: 14 | LRUCache.prototype.equalSize = function() { 15 | return this.map.size === this.dll.currCapacity; 16 | }; 17 | 18 | 19 | //This function retrieves relevant data structures (this.map, this.dll, etc) stored in the IndexedDB browser storage and maps them with the corresponding structures in our LighQL cache solution using localForage.getItem() 20 | LRUCache.prototype.getIDBCache = async function () { 21 | await localforage.getItem('LightQL', (err, value) => { 22 | if(err) { 23 | return false; 24 | } else { 25 | if(!value) { 26 | return false; 27 | } 28 | else { 29 | this.capacity = value.capacity; 30 | this.graphqlEndpoint = value.graphqlEndpoint; 31 | //When data structures (this.dll, this.map) are saved to IndexedDB the underlying methods that allow these structures to function are not saved in IndexDB. As a result, a new instance of each structure must be created and updated with the data from IndexedDB 32 | this.map = new Map(); 33 | value.map.forEach((val, query) => { 34 | this.map.set(query, val); 35 | }) 36 | this.dll = new DoublyLinkedList(); 37 | let currNode = value.dll.head; 38 | while(currNode){ 39 | this.dll.add(currNode); 40 | currNode = currNode.next; 41 | } 42 | return true; 43 | } 44 | } 45 | }) 46 | } 47 | 48 | 49 | //A function to save the current LighQL cache data structures to IndexedDB using localforage.setItem() 50 | LRUCache.prototype.saveIDBCache = async function (){ 51 | let data = { 52 | capacity : this.capacity, 53 | map : this.map, 54 | dll : this.dll, 55 | graphqlEndpoint: this.graphqlEndpoint 56 | }; 57 | await localforage.setItem('LightQL', data, (err, value) => { 58 | if (err) { 59 | return false; 60 | } else { 61 | return true; 62 | } 63 | }); 64 | } 65 | 66 | //A function that allows the user to request data for a specific graphQL query. It implements our Cache's LRU eviction policy and Lazy-Loading caching pattern 67 | LRUCache.prototype.get = async function(query, variables) { 68 | //Writes LightQL Cache to IndexedDB 69 | await this.saveIDBCache(); 70 | //If our Cache is not empty, pull relevant data structures stored in IndexedDB into our LightQL cache 71 | await this.getIDBCache(); 72 | 73 | //Determines whether this.map and this.DLL have the same number of nodes and throws an error if not 74 | if (this.equalSize() === false) { 75 | throw new Error('Hashmap and linked list are out of sync and no longer have the same number of nodes'); 76 | } 77 | //Checks if the grapQL endpoint is valid and throws an error if not 78 | if(!this.graphqlEndpoint){ 79 | throw new Error('Graphql Endpoint Argument is invalid or missing') 80 | } 81 | //Checks capacity constraints : capacity must be an integer and greater than 0. 82 | if(this.capacity <= 0 || !this.capacity || typeof this.capacity !== 'number') { 83 | throw new Error('Capacity is invalid') 84 | } 85 | 86 | /* LAZY LOADING IMPLEMENTATION 87 | Lazy loading is a caching strategy that loads data into the cache only when necessary to improve performance and save system resources. 88 | When making a specific query, the application will hit the cache first; if the data for the query is present, the cache returns the data to your application. If the data is missing, then the cache will be responsible for pulling the data from the main database/data store. The returned data will then be written to the cache, and can quickly be retrieved the next time it's requested. 89 | */ 90 | if (this.map.has(query)) { 91 | let currNode = this.map.get(query); 92 | this.dll.remove(currNode); 93 | this.dll.add(currNode); 94 | this.saveIDBCache(); 95 | return currNode.value; 96 | } else { 97 | return new Promise ((resolve, reject) =>{ 98 | fetch(this.graphqlEndpoint, { 99 | method: 'POST', 100 | headers: {'Content-type' : 'application/json', 101 | 'Accept' : 'application/json', 102 | }, 103 | body: JSON.stringify({ 104 | query: query, 105 | variables : {variables} 106 | }) 107 | }) 108 | .then((res) => res.json()) 109 | .then((data) => { 110 | const actualData = data.data; 111 | this.put(query, actualData); 112 | this.saveIDBCache(); 113 | resolve(actualData); 114 | }) 115 | .catch((err) => console.log(`Error in data fetch: ` + err)); 116 | }); 117 | } 118 | }; 119 | 120 | 121 | //A function that stores new queries and associated data into our LRU Cache. It maintains consistency between our underlying HashMap structure and Doubly-Linked List 122 | LRUCache.prototype.put = function (query, value) { 123 | if (this.equalSize() === false) { 124 | return console.log('Check hashmap and linked list'); 125 | } 126 | if (this.map.has(query)) { 127 | //Get the current node from the list and save in a temp variable: currNode 128 | let currNode = this.map.get(query); 129 | //Delete the node from the Doubly-Linked list 130 | this.dll.remove(currNode); 131 | //Delete the node from the HashMap 132 | this.map.delete(query); 133 | //Update the currNode's value to reflect fresh data returned from the query 134 | currNode.value = value; 135 | //Add the node back to the DLL 136 | this.dll.add(currNode); 137 | //Add the node to the HashMap 138 | this.map.set(query, currNode); 139 | return; 140 | } else { 141 | // Case if the cache doesn't contain the requested Query 142 | // Case if the cache is at capacity: LRU Eviction is implemented here! 143 | if (this.map.size === this.capacity) { 144 | //Find the node at the tail of the DLL 145 | let tempTail = this.dll.tail; 146 | //Save the query from the tail DLL Node 147 | let tailToDeletequery = tempTail.query; 148 | //Delete the tail node from the DLL 149 | this.dll.remove(tempTail); 150 | //Remove the tail node from the HashMap 151 | this.map.delete(tailToDeletequery); 152 | //now we have free space in the HashMap and the DLL to add the new node 153 | } 154 | //Create a new node with the value and query passed in 155 | const newNode = new DLLNode(query, value); 156 | //Add the node to the DLL 157 | this.dll.add(newNode); 158 | //Add the node to the HashMap 159 | this.map.set(query, newNode); 160 | return; 161 | } 162 | }; 163 | 164 | //Function to create a Doubly Linked-List node 165 | const DLLNode = function (query, value) { 166 | this.query = query; 167 | this.value = value; 168 | this.next = null; 169 | this.prev = null; 170 | }; 171 | 172 | //Function that creates a Doubly Linked-List 173 | const DoublyLinkedList = function () { 174 | this.head = null; 175 | this.tail = null; 176 | this.currCapacity = 0; 177 | }; 178 | 179 | //Function for adding nodes to the Doubly Linked-List 180 | DoublyLinkedList.prototype.add = function (node) { 181 | if (!this.head && !this.tail) { 182 | this.head = node; 183 | this.tail = node; 184 | } else { 185 | node.next = this.head; 186 | this.head.prev = node; 187 | this.head = node; 188 | } 189 | this.currCapacity += 1; 190 | return; 191 | }; 192 | 193 | //Function for removing nodes from the Doubly Linked-List 194 | DoublyLinkedList.prototype.remove = function (nodeToRemove) { 195 | let curr = this.head; 196 | while (curr) { 197 | if ((curr.value === nodeToRemove.value)) { 198 | if (this.head.value === curr.value) { 199 | if (this.tail.value === this.head.value) { 200 | this.head = this.tail = null; 201 | this.currCapacity--; 202 | return; 203 | } 204 | this.head = curr.next; 205 | this.head.prev = null; 206 | this.currCapacity--; 207 | return; 208 | } 209 | if (this.tail.value === curr.value) { 210 | this.tail = curr.prev; 211 | this.tail.next = null; 212 | this.currCapacity--; 213 | return; 214 | } 215 | curr.prev.next = curr.next; 216 | curr.next.prev = curr.prev; 217 | this.currCapacity--; 218 | return; 219 | } 220 | curr = curr.next; 221 | } 222 | }; 223 | 224 | module.exports = { LRUCache, DoublyLinkedList, DLLNode}; 225 | 226 | -------------------------------------------------------------------------------- /npm-package/package-lock.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "lightql-cache", 3 | "version": "1.2.3", 4 | "lockfileVersion": 3, 5 | "requires": true, 6 | "packages": { 7 | "": { 8 | "name": "lightql-cache", 9 | "version": "1.2.3", 10 | "license": "ISC", 11 | "dependencies": { 12 | "localforage": "^1.10.0" 13 | } 14 | }, 15 | "node_modules/immediate": { 16 | "version": "3.0.6", 17 | "resolved": "https://registry.npmjs.org/immediate/-/immediate-3.0.6.tgz", 18 | "integrity": "sha512-XXOFtyqDjNDAQxVfYxuF7g9Il/IbWmmlQg2MYKOH8ExIT1qg6xc4zyS3HaEEATgs1btfzxq15ciUiY7gjSXRGQ==" 19 | }, 20 | "node_modules/lie": { 21 | "version": "3.1.1", 22 | "resolved": "https://registry.npmjs.org/lie/-/lie-3.1.1.tgz", 23 | "integrity": "sha512-RiNhHysUjhrDQntfYSfY4MU24coXXdEOgw9WGcKHNeEwffDYbF//u87M1EWaMGzuFoSbqW0C9C6lEEhDOAswfw==", 24 | "dependencies": { 25 | "immediate": "~3.0.5" 26 | } 27 | }, 28 | "node_modules/localforage": { 29 | "version": "1.10.0", 30 | "resolved": "https://registry.npmjs.org/localforage/-/localforage-1.10.0.tgz", 31 | "integrity": "sha512-14/H1aX7hzBBmmh7sGPd+AOMkkIrHM3Z1PAyGgZigA1H1p5O5ANnMyWzvpAETtG68/dC4pC0ncy3+PPGzXZHPg==", 32 | "dependencies": { 33 | "lie": "3.1.1" 34 | } 35 | } 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /npm-package/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "lightql-cache", 3 | "version": "1.2.3", 4 | "description": "A lightweight, ultra-fast client-side cache for GraphQL.", 5 | "main": "lightql.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/LightQL.git" 12 | }, 13 | "keywords": [ 14 | "GraphQL", 15 | "cache", 16 | "client-side", 17 | "lightweight" 18 | ], 19 | "author": "LightQL Team - Cyrus Yari, Rhea Wu, Pierce Heska, Drew Tucker, and Cassidy Johnson cassidyiscoding@gmail.com", 20 | "license": "ISC", 21 | "bugs": { 22 | "url": "https://github.com/oslabs-beta/LightQL/issues" 23 | }, 24 | "homepage": "https://github.com/oslabs-beta/LightQL#readme", 25 | "dependencies": { 26 | "localforage": "^1.10.0" 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "lightql", 3 | "version": "1.0.0", 4 | "description": "A lightweight, ultra-fast client-side cache for GraphQL.", 5 | "main": "./src/client/index.tsx", 6 | "scripts": { 7 | "start": "npx ts-node ./src/simulation/server.ts", 8 | "dev": "webpack-dev-server --mode development --open --hot & nodemon --watch './**/*.ts' --exec 'ts-node' ./src/simulation/server.ts", 9 | "build": "webpack --mode production" 10 | }, 11 | "repository": { 12 | "type": "git", 13 | "url": "git+https://github.com/oslabs-beta/LightQL.git" 14 | }, 15 | "keywords": [ 16 | "GraphQL", 17 | "cache", 18 | "client-side", 19 | "lightweight" 20 | ], 21 | "author": "LightQL Team - Cyrus Yari, Rhea Wu, Pierce Heska, Drew Tucker, and Cassidy Johnson cassidyiscoding@gmail.com", 22 | "license": "ISC", 23 | "bugs": { 24 | "url": "https://github.com/oslabs-beta/LightQL/issues" 25 | }, 26 | "homepage": "https://github.com/oslabs-beta/LightQL#readme", 27 | "dependencies": { 28 | "@cubejs-client/core": "^0.31.15", 29 | "chart.js": "^4.0.1", 30 | "concurrently": "^7.5.0", 31 | "cors": "^2.8.5", 32 | "dotenv": "^16.0.3", 33 | "express": "^4.18.2", 34 | "express-graphql": "^0.12.0", 35 | "framer-motion": "^7.6.7", 36 | "graphql": "^15.8.0", 37 | "localforage": "^1.10.0", 38 | "node-fetch": "^3.3.0", 39 | "pg": "^8.8.0", 40 | "react": "^18.2.0", 41 | "react-chartjs-2": "^5.0.1", 42 | "react-dom": "^18.2.0", 43 | "react-favicon": "^1.0.1", 44 | "react-intersection-observer": "^9.4.1", 45 | "react-router": "^6.4.3", 46 | "react-router-dom": "^6.4.3", 47 | "react-scroll": "^1.8.8", 48 | "schema-utils": "^4.0.0", 49 | "source-map-loader": "^4.0.1", 50 | "ts-loader": "^9.4.2", 51 | "webpack": "^5.75.0", 52 | "webpack-cli": "^4.10.0" 53 | }, 54 | "devDependencies": { 55 | "@babel/core": "^7.20.2", 56 | "@babel/preset-env": "^7.20.2", 57 | "@babel/preset-react": "^7.18.6", 58 | "@types/express": "^4.17.15", 59 | "@types/jest": "^29.2.5", 60 | "@types/node": "^18.11.18", 61 | "@types/react": "^18.0.28", 62 | "@types/react-dom": "^18.0.11", 63 | "@typescript-eslint/parser": "^5.48.1", 64 | "babel-loader": "^9.1.0", 65 | "css-loader": "^6.7.2", 66 | "express": "^4.18.2", 67 | "file-loader": "^6.2.0", 68 | "html-webpack-plugin": "^5.5.0", 69 | "nodemon": "^2.0.20", 70 | "sass": "^1.56.1", 71 | "sass-loader": "^13.2.0", 72 | "style-loader": "^3.3.1", 73 | "ts-node": "^10.9.1", 74 | "ts-node-dev": "^2.0.0", 75 | "tslint": "^6.1.3", 76 | "typescript": "^4.9.4", 77 | "webpack": "^5.75.0", 78 | "webpack-cli": "^4.10.0", 79 | "webpack-dev-server": "^4.11.1" 80 | } 81 | } 82 | -------------------------------------------------------------------------------- /scripts/install_dependencies: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | npm install -------------------------------------------------------------------------------- /scripts/start_server: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | npm start -------------------------------------------------------------------------------- /src/.DS_Store: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/oslabs-beta/LightQL/83dff8889140f841a9bfdae409f25b2db56e76d5/src/.DS_Store -------------------------------------------------------------------------------- /src/assets/LightQL.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/oslabs-beta/LightQL/83dff8889140f841a9bfdae409f25b2db56e76d5/src/assets/LightQL.png -------------------------------------------------------------------------------- /src/assets/black-logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/oslabs-beta/LightQL/83dff8889140f841a9bfdae409f25b2db56e76d5/src/assets/black-logo.png -------------------------------------------------------------------------------- /src/assets/cassidy.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/oslabs-beta/LightQL/83dff8889140f841a9bfdae409f25b2db56e76d5/src/assets/cassidy.png -------------------------------------------------------------------------------- /src/assets/cyrus.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/oslabs-beta/LightQL/83dff8889140f841a9bfdae409f25b2db56e76d5/src/assets/cyrus.png -------------------------------------------------------------------------------- /src/assets/docs-example.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/oslabs-beta/LightQL/83dff8889140f841a9bfdae409f25b2db56e76d5/src/assets/docs-example.png -------------------------------------------------------------------------------- /src/assets/drew.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/oslabs-beta/LightQL/83dff8889140f841a9bfdae409f25b2db56e76d5/src/assets/drew.png -------------------------------------------------------------------------------- /src/assets/graphql-logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/oslabs-beta/LightQL/83dff8889140f841a9bfdae409f25b2db56e76d5/src/assets/graphql-logo.png -------------------------------------------------------------------------------- /src/assets/jest-js-icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/oslabs-beta/LightQL/83dff8889140f841a9bfdae409f25b2db56e76d5/src/assets/jest-js-icon.png -------------------------------------------------------------------------------- /src/assets/local-forage.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/oslabs-beta/LightQL/83dff8889140f841a9bfdae409f25b2db56e76d5/src/assets/local-forage.png -------------------------------------------------------------------------------- /src/assets/lower-left-lines.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/oslabs-beta/LightQL/83dff8889140f841a9bfdae409f25b2db56e76d5/src/assets/lower-left-lines.png -------------------------------------------------------------------------------- /src/assets/nobg-LightQL.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/oslabs-beta/LightQL/83dff8889140f841a9bfdae409f25b2db56e76d5/src/assets/nobg-LightQL.png -------------------------------------------------------------------------------- /src/assets/npm-vector.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/oslabs-beta/LightQL/83dff8889140f841a9bfdae409f25b2db56e76d5/src/assets/npm-vector.png -------------------------------------------------------------------------------- /src/assets/pierce.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/oslabs-beta/LightQL/83dff8889140f841a9bfdae409f25b2db56e76d5/src/assets/pierce.png -------------------------------------------------------------------------------- /src/assets/rhea.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/oslabs-beta/LightQL/83dff8889140f841a9bfdae409f25b2db56e76d5/src/assets/rhea.png -------------------------------------------------------------------------------- /src/assets/typescript-logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/oslabs-beta/LightQL/83dff8889140f841a9bfdae409f25b2db56e76d5/src/assets/typescript-logo.png -------------------------------------------------------------------------------- /src/assets/upper-right-lines.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/oslabs-beta/LightQL/83dff8889140f841a9bfdae409f25b2db56e76d5/src/assets/upper-right-lines.png -------------------------------------------------------------------------------- /src/client/exports.d.ts: -------------------------------------------------------------------------------- 1 | declare module "*.png" { 2 | const value: any; 3 | export = value; 4 | } -------------------------------------------------------------------------------- /src/client/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | LightQL 9 | 10 | 11 | 12 | 13 | 15 | 16 | 17 | 18 | 19 | 20 | 21 |
22 | 23 | -------------------------------------------------------------------------------- /src/client/index.tsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import ReactDOM from "react-dom/client"; 3 | import Favicon from 'react-favicon'; 4 | // import { createBrowserRouter, RouterProvider } from "react-router-dom"; 5 | import { BrowserRouter as Router, Routes, Route } from "react-router-dom"; 6 | import App from './webpage/components/App'; 7 | import Homepage from './webpage/components/Homepage'; 8 | import Docs from './webpage/components/Docs'; 9 | import AboutUs from './webpage/components/AboutUs'; 10 | import Demo from './webpage/components/homepageComponents/Demo' 11 | 12 | const root = ReactDOM.createRoot( 13 | document.getElementById("root")! 14 | ); 15 | 16 | const router = ( 17 | 18 | 19 | }> 20 | } /> 21 | } /> 22 | } /> 23 | } /> 24 | 25 | 26 | 27 | ); 28 | 29 | root.render ( 30 |
31 | 32 | {router} 33 |
34 | ); 35 | 36 | // changes made during ts transition: 37 | // split up BrowserRouter import into Router, routes, and route from react-router-dom 38 | // changed createBrowserRouter to BrowserRouter 39 | // added '!' to root var to TS knows the value will never be null or undefined 40 | // replaced previous tsx with new {router} inside render statement -------------------------------------------------------------------------------- /src/client/webpage/components/AboutUs.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import SingleTile from './aboutUsComponents/SingleTile'; 3 | import '../styling/aboutUs.scss'; 4 | 5 | const cassidy = require('../../../assets/cassidy.png') 6 | const drew = require('../../../assets/drew.png'); 7 | const pierce = require('../../../assets/pierce.png'); 8 | const cyrus = require('../../../assets/cyrus.png'); 9 | const rhea = require('../../../assets/rhea.png'); 10 | 11 | const AboutUs: React.FC = () => { 12 | 13 | return ( 14 |
15 |

Meet the Team

16 |
17 | 28 | 39 | 50 | 61 | 72 |
73 |
74 | ) 75 | } 76 | 77 | export default AboutUs; -------------------------------------------------------------------------------- /src/client/webpage/components/App.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { Link, Route, Routes } from 'react-router-dom'; 3 | import { motion } from 'framer-motion'; 4 | import '../styling/sitewide.scss'; 5 | import Homepage from './Homepage'; 6 | import Docs from './Docs'; 7 | import AboutUs from './AboutUs'; 8 | import Demo from './homepageComponents/Demo'; 9 | 10 | const logo = require('../../../assets/nobg-LightQL.png') 11 | const blackLogo = require('../../../assets/black-logo.png') 12 | const npmLogo = require('../../../assets/npm-vector.png') 13 | 14 | const App: React.FC = () => { 15 | return ( 16 | <> 17 | 92 | 93 | 97 | } 98 | /> 99 | 103 | } 104 | /> 105 | 109 | } 110 | /> 111 | 115 | } 116 | /> 117 | 118 | 122 | 123 | ) 124 | }; 125 | 126 | export default App; 127 | 128 | // changes made during ts transition 129 | // added React.FC type -------------------------------------------------------------------------------- /src/client/webpage/components/Docs.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { useState } from 'react'; 3 | import '../styling/docs.scss'; 4 | import docsExample from '../../../assets/docs-example.png'; 5 | 6 | const Docs: React.FC = () => { 7 | const [buttonTextA, setButtonTextA] = useState('Copy'); 8 | const [buttonTextB, setButtonTextB] = useState('Copy'); 9 | const [buttonTextC, setButtonTextC] = useState('Copy'); 10 | const [buttonTextD, setButtonTextD] = useState('Copy'); 11 | 12 | const graphqlQueryStr = ` 13 | \nconst graphqlQueryStr = 14 | \n\t{ 15 | \n\t\tuser { 16 | \n\t\t\tuser_name, 17 | \n\t\t\tsong_name, 18 | \n\t\t\tmovie_name 19 | \n\t\t} 20 | \n\t};`; 21 | 22 | const callTheCache = `const callLightQL = async () => { 23 | \n\tconst cacheGet = await cache.get(graphqlQueryStr, variables); 24 | \n}\n`; 25 | 26 | const importStr = `import { LRUCache, DoublyLinkedList, DLLNode } from 'lightql-cache';`; 27 | 28 | return ( 29 | <> 30 |
31 |
32 |
33 |
34 |

LightQL

35 |

36 | LightQL is an open-source developer tool that leverages the 37 | pinpoint accuracy of GraphQL's queries and implements caching to 38 | improve your website's query efficiency. 39 |

40 |
41 | 42 |
43 |

Prerequisites:

44 |
    45 |
  • GraphQL schemas setup with your database.
  • 46 |
  • 47 | Fullstack Application where frontend makes query request to 48 | backend. 49 |
  • 50 |
51 |
52 |
53 | 54 |
55 |

Getting Started

56 |

57 | If this is your first time using LightQL, run the following 58 | command in your terminal: 59 |

60 |
61 |
npm install lightql-cache
62 | 72 |
73 | 74 |

75 | In your frontend app’s file (e.g. your ***filename.js*** file), 76 | you want to import our LightQL module to handle GraphQL requests 77 | using the ES6 module format. This can also be done in your React 78 | (.jsx), Typescript (.ts and .tsx), and similar file formats. 79 |

80 | 81 |
82 |
{importStr}
83 | 93 |
94 | 95 |

96 | Next, create an instance of a cache using the de-structured 97 | LRUCache object, passing in a capacity as the first argument. 98 | The capacity must be an integer and greater than zero. You must 99 | also pass in a valid GraphQL endpoint as a string as the second 100 | argument. We have set a capacity of 3 in our example below: 101 |

102 | 103 |
104 |
105 |                   const cache = new LRUCache(3,
106 |                   'http://localhost:3000/graphql');
107 |                 
108 | 118 |
119 | 120 |

121 | Now, to make your first query to the cache, you create a GraphQL 122 | formatted query string based on your requirements, for example: 123 |

124 |
125 |
126 |                   {graphqlQueryStr}
127 |                 
128 |
129 | 130 |

131 | Next, we invoke the get function associated with the named 132 | variable you have for the LRUCache, and pass in your query 133 | string and associate variables if necessary. The get function 134 | always returns a promise, therefore it is best to generate an 135 | async function that leverages the await syntax to resolve the 136 | data returned from the cache. 137 |

138 | 139 |
140 |
{callTheCache}
141 | 153 |
154 | 155 |

156 | Now, you are properly set up and can use the data as you wish! 157 |

158 | 159 |

160 | A quick example: imagine you had a React app that included 161 | Chart.js functionality that you wanted to display on a specific 162 | page. You could import LightQL cache to effectively retrieve 163 | your data from your database, and then store it in your 164 | client-side LightQL caching solution. Then, every time you 165 | wanted to display the correct chart.js data, you could grab the 166 | correct information from your LightQL cache with extremely low 167 | latency time. Example code below: 168 |

169 | 170 | 171 | 172 |
173 | 174 |
175 |

Technology Stack

176 |
    177 |
  • GraphQL
  • 178 |
  • Typescript
  • 179 |
  • Node/Express
  • 180 |
  • AWS RDS
  • 181 |
  • React
  • 182 |
  • Chart.js
  • 183 |
  • Jest/Supertest
  • 184 |
  • Webpack
  • 185 |
186 |
187 |
188 |
189 |
190 |
191 | 192 | ); 193 | }; 194 | 195 | export default Docs; 196 | 197 | // changes made during ts transition 198 | // added React.FC type -------------------------------------------------------------------------------- /src/client/webpage/components/Homepage.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import Hero from './homepageComponents/Hero'; 3 | import ThreeBox from './homepageComponents/ThreeBox'; 4 | import Descriptions from './homepageComponents/Descriptions'; 5 | import Demo from './homepageComponents/Demo'; 6 | import '../styling/sitewide.scss'; 7 | 8 | const Homepage: React.FC = () => { 9 | return ( 10 |
11 | 12 | 13 | 14 | 15 |
16 | ) 17 | } 18 | 19 | export default Homepage; 20 | 21 | // changes made during ts transition 22 | // added React.FC type -------------------------------------------------------------------------------- /src/client/webpage/components/aboutUsComponents/SingleTile.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import '../../styling/aboutUs.scss'; 3 | import { motion } from 'framer-motion'; 4 | 5 | interface Props { 6 | emailIdName: string; 7 | name: string; 8 | email: string; 9 | emailAria: string; 10 | GHAria: string; 11 | LIAria: string; 12 | headshot: {default: string}; 13 | githubLink: string; 14 | linkedInLink: string; 15 | } 16 | 17 | const SingleTile: React.FC = ({emailIdName, name, email, emailAria, GHAria, LIAria, headshot, githubLink, linkedInLink}) => { 18 | 19 | let emailTo = `mailto: ${email}`; 20 | 21 | return ( 22 | 33 | 34 |
35 |

{name}

36 | 37 |
38 | Email me! 50 | 60 | 61 | 62 | 72 | 73 | 74 |
75 |
76 | 77 |
78 | ) 79 | 80 | } 81 | 82 | export default SingleTile; 83 | 84 | // changes made during ts transition 85 | // added props interface and React.FC type -------------------------------------------------------------------------------- /src/client/webpage/components/homepageComponents/Demo.js: -------------------------------------------------------------------------------- 1 | import React, { useState, useLayoutEffect } from 'react'; 2 | import { LRUCache } from '../../../../../npm-package/lightql.ts'; 3 | import 'chart.js/auto'; 4 | import { Chart } from 'react-chartjs-2'; 5 | import { motion } from 'framer-motion'; 6 | import { Chart as ChartJS, LineController, LineElement, PointElement, LinearScale, Title } from 'chart.js'; 7 | import '../../styling/demo.scss'; 8 | 9 | ChartJS.register(LineController, LineElement, PointElement, LinearScale, Title); 10 | 11 | 12 | 13 | const Demo = () => { 14 | 15 | const [pulledData, setPulledData] = useState('Waiting for user to submit GraphQL query'); 16 | const [timeArr, setTimeArr] = useState([]); 17 | const [chartData, setChartData] = useState({ 18 | datasets: [] 19 | }); 20 | const [uncachedTime, setUncachedTime] = useState('') 21 | const [currentTime, setCurrentTime] = useState(''); 22 | 23 | const queryStr = `{ 24 | user { 25 | user_name, 26 | song_name, 27 | movie_name 28 | } 29 | }` 30 | 31 | 32 | useLayoutEffect(() => { 33 | const labels = []; 34 | for (let i = 0; i < timeArr.length; i++) { 35 | if (i === 0) { 36 | labels.push('uncached data') 37 | } else { 38 | labels.push('cached data') 39 | } 40 | } 41 | 42 | setChartData({ 43 | labels: labels, 44 | datasets: [{ 45 | label: 'Query Run Time', 46 | data: timeArr, 47 | fill: false, 48 | borderColor: '#11b5e4', 49 | tension: 0.1 50 | }] 51 | }); 52 | }, [timeArr]); 53 | 54 | 55 | const cache = new LRUCache(3, 'https://lightql-i8h6.onrender.com/graphql'); 56 | 57 | const callLightQL = async () => { 58 | let start, end; 59 | if (pulledData === 'Waiting for user to submit GraphQL query') { 60 | start = performance.now(); 61 | const cacheGet = await cache.get(queryStr); 62 | console.log('cacheGet:', cacheGet.user); 63 | end = performance.now(); 64 | setPulledData(JSON.stringify(cacheGet.user, null, 2)); 65 | console.log(`Execution time before: ${(end - start).toFixed(2)} ms`); 66 | setTimeArr((timeArr) => [...timeArr, (end - start).toFixed(2)]); 67 | setUncachedTime(`${(end - start).toFixed(2)}` + ' ms') 68 | } else { 69 | start = performance.now(); 70 | cache.get(queryStr); 71 | end = performance.now(); 72 | console.log(`Execution time after: ${(end - start)} ms`); 73 | setTimeArr((timeArr) => [...timeArr, (end - start).toFixed(2)]); 74 | setCurrentTime(`${(end - start).toFixed(2)}` + ' ms'); 75 | } 76 | return; 77 | } 78 | 79 | return ( 80 |
81 |

Watch it work!

82 | { 86 | callLightQL(); 87 | }} 88 | whileHover={{ scale: 1.05 }} 89 | whileTap={{ scale: 0.9 }} 90 | transition={{ 91 | type: "spring", 92 | stiffness: 400, 93 | damping: 17 94 | }} 95 | >Run the demo 96 | 97 |
98 |
99 |

Query:

100 |
{queryStr}
101 |
102 |
103 |

Query Result:

104 |
105 | 						{pulledData}
106 | 					
107 |
108 |
109 |
110 |
111 | 120 |
121 |
122 |

Run Time Statistics

123 |
Uncached Run Time:

{uncachedTime}

124 |
Cached Run Time:

{currentTime}

125 |
126 |
127 | 128 |
129 | ) 130 | } 131 | 132 | export default Demo; -------------------------------------------------------------------------------- /src/client/webpage/components/homepageComponents/Descriptions.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import '../../styling/descriptions.scss'; 3 | 4 | const jestLogo = require('../../../../assets/jest-js-icon.png'); 5 | const typescriptLogo = require('../../../../assets/typescript-logo.png'); 6 | const graphqlLogo = require('../../../../assets/graphql-logo.png'); 7 | const localForageLogo = require('../../../../assets/local-forage.png'); 8 | 9 | const Descriptions: React.FC = () => { 10 | return ( 11 |
12 |
13 |
14 |

15 | Problem With Existing Solutions 16 |

17 |

18 | Bloated NPM installs with 100+ dependencies and packages, pulling in 19 | 35MB+ of JS generates a bundle size that could lead to poor app 20 | performance in bandwith constrained apps. There is also a lack of 21 | support for other frameworks outside of React. 22 |

23 |
24 |
25 |

26 | Who Is This For? 27 |

28 |

29 | You want extremely low latency, client-side caching with persistent 30 | data storage. You want a high cache hit-rate from a lazy loading 31 | implementation to improve performance and save system resources. You 32 | want to set up ALL OF THIS in seconds. 33 |

34 |
35 |
36 |
37 |
38 |

39 | Tech Stack 40 |

41 |

42 | In order to provide the developer with very efficient caching for 43 | their GraphQL queries, LightQL implements a modern tech stack. Cache 44 | your queries with us to leverage these technologies and improve your 45 | application performance! 46 |

47 |
48 |
49 | 54 |

55 | Typescript 56 |

57 |
58 |
59 | 64 |

65 | GraphQL 66 |

67 |
68 |
69 | 70 |

71 | Jest 72 |

73 |
74 |
75 | 80 |

81 | Local Forage 82 |

83 |
84 |
85 |
86 |
87 | ); 88 | }; 89 | 90 | export default Descriptions; 91 | 92 | // changes made during ts transition 93 | // added React.FC type 94 | -------------------------------------------------------------------------------- /src/client/webpage/components/homepageComponents/Hero.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import '../../styling/hero.scss'; 3 | import { motion } from 'framer-motion'; 4 | import { Link } from 'react-router-dom'; 5 | 6 | const leftLines = require('../../../../assets/lower-left-lines.png'); 7 | const rightLines = require('../../../../assets/upper-right-lines.png'); 8 | const logo = require('../../../../assets/nobg-LightQL.png'); 9 | 10 | const Hero: React.FC = () => { 11 | return ( 12 |
13 |
14 | 22 |
23 | 34 |

35 | This is LightQL. 36 |

37 |

38 | A lightspeed, lightweight client-side cache for GraphQL. 39 |

40 |
41 |
42 |

43 | $ 44 |

45 | 48 | 51 | navigator.clipboard.writeText('npm install lightql-cache') 52 | } 53 | style={{ color: '#323949' }} 54 | > 55 | 56 | 57 |
58 | 59 | 62 | 63 |
64 | 65 |

Contact us

66 | 67 |
68 | 76 |
77 |
78 | ); 79 | }; 80 | 81 | export default Hero; 82 | 83 | // changes made during ts transition 84 | // added React.FC type 85 | -------------------------------------------------------------------------------- /src/client/webpage/components/homepageComponents/OneBox.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import '../../styling/boxes.scss'; 3 | 4 | interface Props { 5 | icon: React.ReactNode; 6 | title: string; 7 | text: string; 8 | } 9 | 10 | const OneBox: React.FC = ({ icon, title, text }) => { 11 | 12 | return ( 13 |
14 | {icon} 15 |

{title}

16 |

{text}

17 |
18 | ) 19 | } 20 | 21 | export default OneBox; 22 | 23 | // changes made during ts transition 24 | // added props interface and React.FC type -------------------------------------------------------------------------------- /src/client/webpage/components/homepageComponents/ThreeBox.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import '../../styling/boxes.scss'; 3 | import OneBox from './OneBox'; 4 | 5 | const ThreeBox: React.FC = () => { 6 | 7 | const ElectricBoltIcon = 8 | const ThumbUpAltOutlinedIcon = 9 | const ArchiveOutlinedIcon = 10 | 11 | const easyTitle = 'Easy to use'; 12 | const fastTitle = 'Ultra-fast'; 13 | const mutationTitle = 'Persistent cache'; 14 | 15 | const easyText = 16 | 'Set-up in seconds, without additional costs or processes, and instantly implement the caching in your web app file with just a few of lines of code.'; 17 | const fastText = 18 | 'Extremely low latency speeds (as shown in our demo below) from our high cache hit-rate due to lazy loading implementation and O(1) look-up.'; 19 | const mutationText = 20 | 'Persistently cache data offline between sessions using localForage and IndexedDB - improving performance and saving backend resources.'; 21 | 22 | return ( 23 |
24 | 25 | 26 | 31 |
32 | ); 33 | }; 34 | 35 | export default ThreeBox; 36 | 37 | // changes made during ts transition 38 | // added React.FC type 39 | -------------------------------------------------------------------------------- /src/client/webpage/styling/aboutUs.scss: -------------------------------------------------------------------------------- 1 | @import './sitewide.scss'; 2 | 3 | #about-us-page-layout { 4 | display: flex; 5 | flex-direction: column; 6 | align-items: center; 7 | } 8 | 9 | #about-us-title { 10 | text-align: center; 11 | font-family: $title-font; 12 | color: $black; 13 | font-size: 3rem; 14 | } 15 | 16 | #about-us-layout { 17 | margin: 0; 18 | display: inline-grid; 19 | grid-template-columns: 1fr 1fr 1fr 1fr 1fr; 20 | grid-auto-flow: row dense; 21 | place-items: center; 22 | height: 73vh; 23 | margin: 0 40px 0 40px; 24 | } 25 | 26 | @media (max-width: 1350px) { 27 | #about-us-layout { 28 | display: grid; 29 | grid-template-columns: 1fr 1fr 1fr; 30 | height: auto; 31 | } 32 | 33 | } 34 | 35 | @media (max-width: 900px) { 36 | #about-us-layout { 37 | display: grid; 38 | grid-template-columns: 1fr 1fr; 39 | height: auto; 40 | } 41 | } 42 | 43 | @media (max-width: 600px) { 44 | #about-us-layout { 45 | display: grid; 46 | grid-template-columns: 1fr; 47 | height: auto; 48 | } 49 | } 50 | 51 | #singleTile { 52 | background-color: rgba($black, 0.05); 53 | height: 389px; 54 | width: 235px; 55 | margin: 20px; 56 | border-radius: 20px; 57 | display: flex; 58 | flex-direction: column; 59 | box-shadow: rgba(60, 64, 67, 0.3) 0px 1px 2px 0px, rgba(60, 64, 67, 0.15) 0px 1px 3px 1px; 60 | } 61 | 62 | #singleTile:hover { 63 | box-shadow: rgba(0, 0, 0, 0.19) 0px 10px 20px, rgba(0, 0, 0, 0.23) 0px 6px 6px; 64 | } 65 | 66 | #headshots { 67 | width: 100%; 68 | min-height: 260px; 69 | border-radius: 20px 20px 0 0; 70 | } 71 | 72 | #bottom-of-tile { 73 | margin: 12px; 74 | height: 100%; 75 | display: flex; 76 | flex-direction: column; 77 | align-items: center; 78 | justify-content: space-evenly; 79 | } 80 | 81 | #name { 82 | color: $black; 83 | font-family: $title-font; 84 | margin: 0; 85 | } 86 | 87 | #contact-links { 88 | display: flex; 89 | flex-direction: column; 90 | } 91 | 92 | .email-button { 93 | display: flex; 94 | justify-content: center; 95 | border: 0.5px solid $black; 96 | border-radius: 8px; 97 | width: 100px; 98 | background-color: $white; 99 | padding: 3%; 100 | margin: 2% 0 2% 0; 101 | font-family: $body-font; 102 | font-size: 0.9rem; 103 | text-decoration: none; 104 | color: $black; 105 | } 106 | 107 | #contact-icons { 108 | display: flex; 109 | width: 100%; 110 | align-items: center; 111 | justify-content: space-evenly; 112 | } -------------------------------------------------------------------------------- /src/client/webpage/styling/boxes.scss: -------------------------------------------------------------------------------- 1 | @import './sitewide.scss'; 2 | 3 | #three-box-layout { 4 | height: 45vh; 5 | display: flex; 6 | justify-content: space-evenly; 7 | margin: 0 10vw 0 10vw; 8 | } 9 | 10 | #one-box-layout { 11 | height: 80%; 12 | width: 25%; 13 | display: flex; 14 | flex-direction: column; 15 | align-items: center; 16 | row-gap: 5%; 17 | text-align: center; 18 | padding-top: 30px; 19 | } 20 | 21 | #box-title { 22 | font-family: $title-font; 23 | user-select: none; 24 | 25 | } 26 | 27 | #box-paragraph { 28 | font-family: $body-font; 29 | user-select: none; 30 | margin: 0; 31 | } 32 | 33 | .text-color { 34 | color: $black; 35 | } 36 | 37 | @media screen and (max-width: 830px) { 38 | #three-box-layout { 39 | height: auto; 40 | display: flex; 41 | flex-direction: column; 42 | justify-content: center; 43 | align-items: center; 44 | width: 100%; 45 | padding: 18% 0 12% 0; 46 | margin: 0; 47 | } 48 | 49 | #one-box-layout { 50 | display: flex; 51 | flex-direction: column; 52 | align-items: center; 53 | text-align: center; 54 | gap: 10px; 55 | height: auto; 56 | padding: 10% 5%; 57 | width: 70%; 58 | border-radius: 20px; 59 | } 60 | 61 | #box-paragraph { 62 | width: 95%; 63 | } 64 | } -------------------------------------------------------------------------------- /src/client/webpage/styling/demo.scss: -------------------------------------------------------------------------------- 1 | @import './sitewide.scss'; 2 | 3 | h1 { 4 | margin: 0 5 | } 6 | 7 | #demo-body { 8 | display: grid; 9 | grid-template-rows: 7% 7% auto; 10 | place-items: center; 11 | height: fit-content; 12 | padding: 5% 0 5% 0; 13 | width: 100%; 14 | margin-top: auto; 15 | margin-bottom: auto; 16 | } 17 | 18 | .title-font { 19 | font-family: $title-font; 20 | font-weight: 700; 21 | color: $black; 22 | user-select: none; 23 | 24 | } 25 | 26 | .body-metric-font { 27 | font-family: $body-font; 28 | color: $black; 29 | font-size: 0.95rem; 30 | display: flex; 31 | margin-bottom: 0; 32 | margin-top: 25px; 33 | font-weight: 400; 34 | } 35 | 36 | #demo-btn { 37 | display: flex; 38 | justify-content: center; 39 | align-items: center; 40 | border: 0.5px solid $black; 41 | border-radius: 20px; 42 | padding: 4px 8px 4px 8px; 43 | background-color: $white; 44 | font-family: $body-font; 45 | font-size: 0.9rem; 46 | box-shadow: 2px 0px 5px rgba($black, 0.5); 47 | text-decoration: none; 48 | color: $black; 49 | } 50 | 51 | #demo-btn:hover { 52 | box-shadow: 5px 3px 5px rgba($black, 0.5); 53 | } 54 | 55 | .query-string { 56 | font-family: $code-font; 57 | line-height: 1.5rem; 58 | tab-size: 20px; 59 | } 60 | 61 | #result-boxes { 62 | display: flex; 63 | flex-direction: row; 64 | justify-content: center; 65 | align-items: flex-start; 66 | align-self: flex-start; 67 | height: 80%; 68 | width: 100%; 69 | line-height: 2rem; 70 | } 71 | 72 | .result-box-titles { 73 | position: sticky; 74 | top: 0; 75 | background-color: $black; 76 | width: 100%; 77 | margin: 0; 78 | padding: 20px 0 20px 0; 79 | } 80 | 81 | .data-box { 82 | background-color: $black; 83 | color: $white; 84 | height: 40vh; 85 | width: 50%; 86 | margin: 0; 87 | margin-top: 20px; 88 | border-radius: 8px; 89 | padding: 20px; 90 | padding-top: 0; 91 | font-family: $code-font; 92 | font-size: 0.85rem; 93 | } 94 | 95 | #query-result::-webkit-scrollbar { 96 | height: 90%; 97 | background-color: transparent; 98 | width: 10px; 99 | } 100 | 101 | #query-result::-webkit-scrollbar-track { 102 | border-radius: 20px; 103 | } 104 | 105 | #query-result::-webkit-scrollbar-thumb { 106 | background-color: lighten($black, 40); 107 | border-radius: 20px; 108 | width: 10px; 109 | } 110 | 111 | #query-result { 112 | overflow-y: overlay; 113 | scroll-margin: 20px; 114 | } 115 | 116 | #metrics-container { 117 | display: flex; 118 | width: 100vw; 119 | justify-content: center; 120 | align-items: center; 121 | } 122 | 123 | .metrics { 124 | height: 200px; 125 | margin: 0 20px 10px 20px; 126 | } 127 | 128 | #chart-container { 129 | color: #11b5e4; 130 | border-width: 20px, 131 | } 132 | 133 | #time-box { 134 | width: 300px; 135 | height: 90%; 136 | background-color: lighten($black, 70); 137 | padding: 30px; 138 | border-radius: 8px; 139 | } 140 | 141 | .time-stamp { 142 | margin: 0; 143 | margin-left: 10px; 144 | font-weight: 700;; 145 | } 146 | 147 | @media screen and (min-width: 900px) { 148 | #demo-body { 149 | display: grid; 150 | grid-template-rows: 7% 7% auto; 151 | place-items: center; 152 | height: auto; 153 | width: 100%; 154 | margin-top: auto; 155 | margin-bottom: auto; 156 | } 157 | .data-box { 158 | width: 400px; 159 | } 160 | } 161 | 162 | @media screen and (min-height: 700px) { 163 | .data-box { 164 | height: 250px; 165 | } 166 | } 167 | 168 | @media screen and (max-width: 849px) { 169 | #demo-body { 170 | height: auto; 171 | display: flex; 172 | flex-direction: column; 173 | align-items: center; 174 | justify-content: center; 175 | width: 100%; 176 | margin-top: auto; 177 | margin-bottom: auto; 178 | } 179 | 180 | #result-boxes { 181 | display: flex; 182 | flex-direction: column; 183 | justify-content: center; 184 | align-items: flex-start; 185 | align-self: center; 186 | width: 90%; 187 | line-height: 2rem; 188 | } 189 | 190 | #demo-btn { 191 | margin-top: 4% 192 | } 193 | 194 | .data-box { 195 | height: 40vh; 196 | width: 90%; 197 | margin-top: 20px; 198 | } 199 | 200 | #metrics-container { 201 | display: flex; 202 | flex-direction: column; 203 | width: 100vw; 204 | justify-content: center; 205 | align-items: center; 206 | } 207 | 208 | #chart-container { 209 | margin-top: 4vh; 210 | } 211 | } -------------------------------------------------------------------------------- /src/client/webpage/styling/descriptions.scss: -------------------------------------------------------------------------------- 1 | $white: #ffffff; 2 | $blue: #11b5e4; 3 | $mauve: #957d95; 4 | $black: #323949; 5 | $title-font: 'B612', sans-serif; 6 | $body-font: 'Noto Serif', serif; 7 | $code-font: 'B612 Mono', monospace; 8 | 9 | #descriptions { 10 | height: 95vh; 11 | display: flex; 12 | align-items: center; 13 | justify-content: center; 14 | } 15 | 16 | #info { 17 | width: 49%; 18 | height: 75%; 19 | display: grid; 20 | grid-template-rows: 50% 50%; 21 | align-items: center; 22 | } 23 | 24 | .desc-titles { 25 | font-family: $title-font; 26 | color: $black; 27 | font-size: 1.8rem; 28 | text-align: center; 29 | user-select: none; 30 | } 31 | 32 | .paragraph-text { 33 | font-family: $body-font; 34 | color: $black; 35 | margin: 25px 25% 25px 25%; 36 | line-height: 1.3rem; 37 | font-size: 0.93rem; 38 | letter-spacing: 0.02em; 39 | text-align: center; 40 | user-select: none; 41 | } 42 | 43 | #separating-line { 44 | height: 90%; 45 | width: 4px; 46 | background-color: rgba($black, 0.4); 47 | border-radius: 100px; 48 | } 49 | 50 | #tech-stack { 51 | width: 49%; 52 | height: 75%; 53 | display: flex; 54 | flex-direction: column; 55 | align-items: center; 56 | justify-content: center; 57 | } 58 | 59 | #tech-stack-desc { 60 | margin: 25px 15% 25px 15%; 61 | text-align: center; 62 | user-select: none; 63 | } 64 | 65 | #icon-section { 66 | display: grid; 67 | width: 45%; 68 | height: 60%; 69 | margin-top: 5%; 70 | grid-template-columns: 50% 50%; 71 | grid-template-rows: 50% 50%; 72 | justify-items: center; 73 | } 74 | 75 | .logo-section { 76 | display: flex; 77 | flex-direction: column; 78 | align-items: center; 79 | } 80 | 81 | .tech-stack-logos { 82 | width: auto; 83 | height: 90px; 84 | } 85 | 86 | .icon-text { 87 | font-family: $title-font; 88 | color: $black; 89 | } 90 | 91 | @media screen and (max-width: 830px) { 92 | #descriptions { 93 | height: auto; 94 | margin: 50px 0 50px 0; 95 | display: flex; 96 | flex-direction: column; 97 | background-color: rgba($black, 0.05); 98 | border-radius: 20px; 99 | margin: 0 10%; 100 | padding-top: 8%; 101 | } 102 | 103 | .desc-titles { 104 | font-size: 1.5rem; 105 | } 106 | 107 | #info { 108 | width: 80%; 109 | display: grid; 110 | grid-template-rows: 50% 50%; 111 | align-items: center; 112 | } 113 | 114 | .paragraph-text { 115 | margin: 15px 0 40px 0; 116 | } 117 | 118 | #tech-stack { 119 | width: 90%; 120 | height: 100%; 121 | padding: 30px 0 30px 0; 122 | display: flex; 123 | flex-direction: column; 124 | } 125 | #tech-stack-desc { 126 | margin: 15px 0 40px 0; 127 | width: 79%; 128 | } 129 | #icon-section { 130 | display: grid; 131 | width: 80%; 132 | margin-top: 0; 133 | grid-template-columns: 50% 50%; 134 | grid-template-rows: 50% 50%; 135 | grid-row-gap: 5%; 136 | } 137 | 138 | #separating-line { 139 | height: 5px; 140 | width: 80%; 141 | background-color: rgba($black, 0.4); 142 | border-radius: 100px; 143 | } 144 | } -------------------------------------------------------------------------------- /src/client/webpage/styling/docs.scss: -------------------------------------------------------------------------------- 1 | @import './sitewide.scss'; 2 | 3 | #main { 4 | margin-left: 8vw; 5 | margin-right: 18vw; 6 | margin-top: 3vh; 7 | margin-bottom: 5vh; 8 | user-select: none; 9 | } 10 | 11 | .image { 12 | height: auto; 13 | width: 80vw; 14 | } 15 | 16 | .section-titles { 17 | font-family: $title-font; 18 | color: $black; 19 | } 20 | 21 | .section-paragraphs { 22 | font-family: $body-font; 23 | color: $black; 24 | letter-spacing: 0.02rem; 25 | line-height: 1.5rem; 26 | } 27 | 28 | .code-box { 29 | background-color: $black; 30 | font-family: $code-font; 31 | border-radius: 15px; 32 | display: grid; 33 | grid-template-columns: auto 100px; 34 | align-items: center; 35 | } 36 | 37 | .blue-code-text { 38 | color: $white; 39 | font-size: 1.1rem; 40 | font-family: $code-font; 41 | grid-column: 1 / 2; 42 | margin-left: 25px; 43 | } 44 | 45 | .copy-button { 46 | grid-column: 2 / 3; 47 | border-radius: 8px; 48 | font-size: 1.05rem; 49 | padding: 5px 0; 50 | width: 60%; 51 | min-width: 60px; 52 | justify-self: center; 53 | background-color: $white; 54 | color: darken($blue, 5%); 55 | border: 1px solid $blue; 56 | cursor: pointer; 57 | font-family: $body-font; 58 | } 59 | 60 | .copy-button:hover { 61 | background-color: $blue; 62 | color: $white; 63 | border: 1px solid $white; 64 | } 65 | 66 | .divider { 67 | border-top: 3px solid; 68 | color: $light-grey; 69 | margin: 50px 0; 70 | } 71 | 72 | #big-codestring { 73 | line-height: 1rem; 74 | margin-top: 0; 75 | margin-bottom: 40px; 76 | } 77 | 78 | @media screen and (max-width: 830px) { 79 | #main { 80 | margin-right: 10vw; 81 | } 82 | 83 | #text-box { 84 | display: flex; 85 | flex-direction: column; 86 | width: 100vw; 87 | } 88 | 89 | .image { 90 | height: auto; 91 | width: 80vw; 92 | } 93 | 94 | .code-box { 95 | margin: 25px 0; 96 | padding: 0 10px; 97 | font-size: 20px; 98 | border-radius: 15px; 99 | display: grid; 100 | grid-template-columns: 90% 10%; 101 | align-items: center; 102 | } 103 | 104 | .blue-code-text { 105 | font-size: 0.8rem; 106 | overflow-x: auto; 107 | grid-column: 1 / 2; 108 | margin-right: 0; 109 | margin-left: 0; 110 | } 111 | 112 | .copy-button { 113 | grid-column: 2 / 3; 114 | display: flex; 115 | align-items: center; 116 | justify-content: center; 117 | border-radius: 8px; 118 | font-size: 15px; 119 | height: 31px; 120 | width: 70%; 121 | justify-self: center; 122 | background-color: $white; 123 | color: darken($blue, 5%); 124 | border: 1px solid $blue; 125 | cursor: pointer; 126 | font-family: $code-font; 127 | } 128 | } -------------------------------------------------------------------------------- /src/client/webpage/styling/hero.scss: -------------------------------------------------------------------------------- 1 | @import './sitewide.scss'; 2 | 3 | .text { 4 | color: $black 5 | } 6 | 7 | #hero { 8 | height: 90vh; 9 | display: grid; 10 | grid-template-columns: 18vw 64vw 18vw; 11 | } 12 | 13 | #hero-left { 14 | grid-column: 1 / 2; 15 | height: 90vh; 16 | display: flex; 17 | align-items: flex-end; 18 | 19 | } 20 | 21 | #hero-right { 22 | height: 90vh; 23 | display: flex; 24 | justify-content: flex-end; 25 | } 26 | 27 | #main-info-section { 28 | grid-column: 2 / 3; 29 | display: flex; 30 | flex-direction: column; 31 | align-items: center; 32 | padding-top: 8vh 33 | } 34 | 35 | #logo { 36 | height: 250px; 37 | } 38 | 39 | #welcome-text { 40 | font-family: $title-font; 41 | color: $black; 42 | font-size: 5rem; 43 | text-align: center; 44 | margin: 0; 45 | user-select: none; 46 | } 47 | 48 | #description-text { 49 | font-family: $body-font; 50 | font-size: 1.6rem; 51 | width: 370px; 52 | text-align: center; 53 | user-select: none; 54 | } 55 | 56 | #clipboard-docs { 57 | display: flex; 58 | width: 100%; 59 | justify-content: center; 60 | height: 6vh; 61 | gap: 2%; 62 | } 63 | 64 | #copy-npm { 65 | background-color: $light-grey; 66 | border-radius: 15px; 67 | padding: 10px; 68 | display: flex; 69 | align-items: center; 70 | justify-content: space-around; 71 | gap: 5%; 72 | width: 42%; 73 | max-width: 360px; 74 | min-width: 329px; 75 | } 76 | 77 | #link-group { 78 | width: 100%; 79 | display: flex; 80 | gap: 2%; 81 | } 82 | 83 | #dollar-sign { 84 | color: rgba($black, .7); 85 | } 86 | 87 | #npm-link { 88 | color: $black; 89 | 90 | } 91 | 92 | .npm-text { 93 | font-family: $code-font; 94 | } 95 | 96 | #clipboard-icon { 97 | cursor: pointer; 98 | } 99 | 100 | .button-text { 101 | font-family: $body-font; 102 | margin: 0; 103 | } 104 | 105 | #docs-button { 106 | background-color: $black; 107 | border: none; 108 | border-radius: 15px; 109 | color: $white; 110 | font-size: 1.2rem; 111 | width: 100%; 112 | height: 100%; 113 | cursor: pointer; 114 | min-width: 150px; 115 | } 116 | 117 | #demo-link { 118 | font-family: $body-font; 119 | color: $black; 120 | font-style: italic; 121 | text-decoration: none; 122 | } 123 | 124 | #demo-link:visited { 125 | color: $black; 126 | text-decoration: none; 127 | } 128 | 129 | #demo-link:focus { 130 | color: $black; 131 | text-decoration: none; 132 | } 133 | 134 | .hero-imgs { 135 | height: 80%; 136 | } 137 | 138 | @media screen and (max-width: 849px) { 139 | #clipboard-docs { 140 | flex-direction: column; 141 | display: flex; 142 | width: 100%; 143 | justify-content: center; 144 | align-items: center; 145 | gap: 10%; 146 | height: auto; 147 | } 148 | 149 | #npm-link { 150 | margin: 0; 151 | } 152 | 153 | #docs-button { 154 | width: 25vw; 155 | min-width: 130px; 156 | font-size: 1rem; 157 | padding: 5px; 158 | } 159 | 160 | #demo-link { 161 | display: none; 162 | } 163 | 164 | .hero-imgs { 165 | height: 80%; 166 | width: 40vw; 167 | } 168 | 169 | #lower-left { 170 | width: 30vw; 171 | } 172 | } -------------------------------------------------------------------------------- /src/client/webpage/styling/sitewide.scss: -------------------------------------------------------------------------------- 1 | $white: #ffffff; 2 | $blue: #11b5e4; 3 | $light-grey: rgb(234 235 236); 4 | $mauve: #957d95; 5 | $black: #323949; 6 | $title-font: 'B612', sans-serif; 7 | $body-font: 'Noto Serif', serif; 8 | $code-font: 'B612 Mono', monospace; 9 | $default-padding: 3%; 10 | $default-border: 0.5px solid $black; 11 | $default-border-radius: 8px; 12 | $box-shadow-light: rgba(60, 64, 67, 0.3) 0px 1px 2px 0px, rgba(60, 64, 67, 0.15); 13 | 14 | html { 15 | height: 100vh; 16 | width: 100vw; 17 | overflow-x: hidden; 18 | overflow-y: overlay; 19 | } 20 | 21 | body { 22 | height: 100vh; 23 | width: 100vw; 24 | margin: 0; 25 | display: flex; 26 | flex-direction: column; 27 | overflow-x: hidden; 28 | overflow-y: overlay; 29 | } 30 | 31 | ::-webkit-scrollbar { 32 | background-color: transparent; 33 | width: 10px; 34 | margin: 20px; 35 | } 36 | 37 | ::-webkit-scrollbar-track { 38 | border-radius: 20px; 39 | } 40 | 41 | ::-webkit-scrollbar-thumb { 42 | background-color: rgba($black, .35); 43 | border-radius: 20px; 44 | width: 10px; 45 | } 46 | 47 | #navbar { 48 | height: 10vh; 49 | display: grid; 50 | grid-template-columns: 30% auto 15%; 51 | background-color: rgba(255, 255, 255, 0.9); 52 | position: sticky; 53 | top: 0px; 54 | z-index: 1; 55 | } 56 | 57 | #left-nav { 58 | grid-column: 1 / 2; 59 | padding-left: 40px; 60 | display: flex; 61 | justify-content: space-around; 62 | align-items: center; 63 | } 64 | 65 | .nav-btns { 66 | font-family: $title-font; 67 | color: $black; 68 | background-color: transparent; 69 | font-size: 1.05rem; 70 | border: none; 71 | cursor: pointer; 72 | } 73 | 74 | #navbar-logo { 75 | height: 7vh; 76 | } 77 | 78 | #right-nav { 79 | grid-column: 3 / 4; 80 | display: flex; 81 | justify-content: flex-end; 82 | align-items: center; 83 | gap: 10px; 84 | } 85 | 86 | .top-right-icons { 87 | margin: 0 2vw 0 0; 88 | } 89 | 90 | #npm-logo { 91 | width: 21px; 92 | height: 21px; 93 | } 94 | 95 | #footer { 96 | height: 10vh; 97 | display: flex; 98 | align-items: center; 99 | background-color: rgba($black, .1); 100 | padding-left: 3vw; 101 | } 102 | 103 | #black-logo { 104 | height: 65%; 105 | padding-right: 1vw; 106 | } 107 | 108 | #footer-text { 109 | font-family: $body-font; 110 | color: $black; 111 | } 112 | 113 | @media screen and (max-width: 830px) { 114 | #navbar { 115 | grid-template-columns: 65% auto 25%; 116 | } 117 | 118 | #left-nav { 119 | padding-left: 20px; 120 | } 121 | 122 | #right-nav { 123 | justify-content: center; 124 | gap: 15px; 125 | } 126 | } -------------------------------------------------------------------------------- /src/simulation/graphQLSchemas.ts: -------------------------------------------------------------------------------- 1 | import db from './models'; 2 | 3 | const { 4 | GraphQLSchema, 5 | GraphQLObjectType, 6 | GraphQLString, 7 | GraphQLList, 8 | GraphQLInt, 9 | GraphQLNonNull 10 | } = require('graphql'); 11 | 12 | interface User { 13 | song_name: string; 14 | movie_name: string; 15 | user_id: number; 16 | user_name: string; 17 | } 18 | 19 | const User1 = new GraphQLObjectType({ 20 | name: 'User1', 21 | description: "Usertype for front end", 22 | fields: () => ({ 23 | song_name: { type: GraphQLNonNull(GraphQLString) }, 24 | movie_name: { type: GraphQLNonNull(GraphQLString) }, 25 | user_id: { type: GraphQLNonNull(GraphQLInt) }, 26 | user_name: { type: GraphQLNonNull(GraphQLString) } 27 | }) 28 | }); 29 | 30 | const RootQueryType = new GraphQLObjectType({ 31 | name : 'Query', 32 | description: 'Root query', 33 | type : 'Query', 34 | fields: () => ({ 35 | user: { 36 | type: new GraphQLList(User1), 37 | resolve: async (parentValue: string, args: object): Promise => { 38 | const query = `SELECT * FROM user_info1`; 39 | const data = await db.query(query); 40 | return data.rows; 41 | }, 42 | }, 43 | }), 44 | }); 45 | 46 | const schema = new GraphQLSchema({ 47 | query: RootQueryType, 48 | type: User1 49 | }); 50 | 51 | export default schema; 52 | 53 | // changes made in ts transition: 54 | // added User interface to describe the data returned by the resolve function on line 37 55 | // added type notations to func params and return type of resolve function 56 | // changed module.exports to export statement on line 52 -------------------------------------------------------------------------------- /src/simulation/models.ts: -------------------------------------------------------------------------------- 1 | import { Pool, QueryResult } from 'pg'; 2 | import dotenv from 'dotenv'; 3 | dotenv.config(); 4 | 5 | const PG_URI = 'postgres://hggnmyxh:yvicl-nyG6v9XTeNAEr5mJzaoCbfIn98@peanut.db.elephantsql.com/hggnmyxh'; 6 | const uri = process.env.PG_URI || PG_URI; 7 | 8 | const pool = new Pool({ 9 | connectionString: uri, 10 | }); 11 | 12 | const query = async (text: string, params?: any[]): Promise> => { 13 | console.log('executed query', text); 14 | return await pool.query(text, params); 15 | }; 16 | 17 | export default { 18 | query, 19 | }; 20 | 21 | 22 | // changes made in ts transition: 23 | // Use import syntax to import pg and dotenv. 24 | // Add type annotations for the query function parameters and return type. 25 | // Use the async/await syntax for the query function. 26 | // Export the query function as the default export. -------------------------------------------------------------------------------- /src/simulation/server.ts: -------------------------------------------------------------------------------- 1 | import path from 'path'; 2 | import express, { Request, Response, NextFunction, response } from 'express'; 3 | import type { ErrorRequestHandler } from 'express'; 4 | import { graphqlHTTP } from 'express-graphql'; 5 | import schema from './graphQLSchemas'; 6 | 7 | const app = express(); 8 | const cors = require('cors'); 9 | const PORT = 3000; 10 | 11 | app.use(cors()); 12 | app.use(express.static(path.resolve(__dirname, '../../dist'))); 13 | app.use(express.json()); 14 | app.use(express.urlencoded({ extended: true })); 15 | 16 | app.get('/', (req: Request, res: Response) => { 17 | return res.status(200).sendFile(path.join(__dirname, '../client/index.html')); 18 | }); 19 | 20 | app.use('/graphql', graphqlHTTP({ 21 | schema: schema, 22 | graphiql: true, 23 | })); 24 | 25 | interface ErrObject { 26 | log: string; 27 | status: number; 28 | message: { err: string }; 29 | }; 30 | 31 | // unknown route handler: 32 | app.use((req: Request, res: Response) => res.status(404).send('Cannot get route')); 33 | 34 | // global error handler: 35 | app.use((err: ErrObject, req: Request, res: Response, next: NextFunction) => { 36 | const defaultErr = { 37 | log: 'Express error handler caught unknown middleware error', 38 | status: 500, 39 | message: { err: 'An error occurred' }, 40 | }; 41 | const errorObj = Object.assign({}, defaultErr, err); 42 | console.log(errorObj.log); 43 | return res.status(errorObj.status).json(errorObj.message); 44 | }); 45 | 46 | 47 | //have the server running up on the 3000 48 | app.listen(PORT, () => console.log(`Express GraphQL server now running on localhost:${PORT}/graphql`)); 49 | 50 | // changes made during ts transition 51 | // expressGraphQL is imported from graphqlHTTP now 52 | // replaced require statements with import statements 53 | // unnecessary types and files are removed 54 | // type annotations added for functional parametes and variables when necessary -------------------------------------------------------------------------------- /src/simulation/simpleTable.sql: -------------------------------------------------------------------------------- 1 | --Favorite Music Data 2 | 3 | --User Table 4 | CREATE TABLE user_info ( 5 | user_id serial NOT NULL, --serial definition 6 | user_name varchar (40) NOT NULL 7 | 8 | CONSTRAINT "user_info_pk" PRIMARY KEY ("user_id")) 9 | WITH ( 10 | OIDS = FALSE 11 | ); 12 | 13 | --Fav Songs : connects with user ids 14 | 15 | CREATE TABLE fav_song ( 16 | fav_song_id serial NOT NULL, 17 | song_name varchar (40) NOT NULL, 18 | user_id integer REFERENCES user_info (user_id), 19 | CONSTRAINT "fav_song_pk" PRIMARY KEY ("fav_song_id") 20 | ) 21 | WITH ( 22 | OIDS = FALSE 23 | ); 24 | 25 | 26 | --Fav Artists : connects with user ids 27 | 28 | CREATE TABLE fav_movie ( 29 | fav_movie_id serial NOT NULL, 30 | movie_name varchar (40) NOT NULL, 31 | user_id INTEGER REFERENCES user_info (user_id), 32 | CONSTRAINT "fav_movie_pk" PRIMARY KEY ("fav_movie_id") 33 | ); 34 | 35 | /* 36 | const user = { 37 | id: 101, 38 | email: 'jack@dev.com', 39 | personalInfo: { 40 | name: 'Jack', 41 | address: { 42 | line1: 'westwish st', 43 | line2: 'washmasher', 44 | city: 'wallas', 45 | state: 'WX' 46 | } 47 | } 48 | } 49 | */ 50 | 51 | 52 | --ALTER TABLE STATEMENTS TO LINK TABLE IDS 53 | --ALTER TABLE _table_name ADD CONSTRAINT "primary_key_name" FOREIGN KEY ("user_id") REFERENCES table_name(key) 54 | 55 | --COMMAND LINE FOR RUNNING IN TERMINAL 56 | --psql -d postgres://hggnmyxh:yvicl-nyG6v9XTeNAEr5mJzaoCbfIn98@peanut.db.elephantsql.com/hggnmyxh -f simpleTable.sql -------------------------------------------------------------------------------- /src/simulation/user_data.sql: -------------------------------------------------------------------------------- 1 | CREATE TABLE user_info1 ( 2 | user_id serial NOT NULL, --serial definition 3 | user_name varchar (40) NOT NULL, 4 | song_name varchar (40) NOT NULL, 5 | movie_name varchar (40) NOT NULL, 6 | CONSTRAINT "user_info_pk1" PRIMARY KEY ("user_id")) 7 | WITH ( 8 | OIDS = FALSE 9 | ); 10 | 11 | 12 | --psql -d postgres://hggnmyxh:yvicl-nyG6v9XTeNAEr5mJzaoCbfIn98@peanut.db.elephantsql.com/hggnmyxh -f user_data.sql -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "module": "commonjs", 4 | "esModuleInterop": true, 5 | "resolveJsonModule": true, 6 | "experimentalDecorators": true, 7 | "target": "es6", 8 | // "noImplicitAny": true, 9 | "moduleResolution": "node", 10 | "sourceMap": true, 11 | "outDir": "dist", 12 | "jsx": "react", 13 | "baseUrl": ".", 14 | 15 | "paths": { 16 | "*": [ 17 | "node_modules/*" 18 | ] 19 | } 20 | }, 21 | 22 | "include": [ 23 | "src/**/*", 24 | "dist/**/*", 25 | "npm-package/**/*" 26 | ] 27 | } -------------------------------------------------------------------------------- /tslint.json: -------------------------------------------------------------------------------- 1 | { 2 | "defaultSeverity": "error", 3 | "extends": [ 4 | "tslint:recommended" 5 | ], 6 | "jsRules": {}, 7 | "rules": { 8 | "trailing-comma": [ false ] 9 | }, 10 | "rulesDirectory": [] 11 | } 12 | -------------------------------------------------------------------------------- /webpack.config.js: -------------------------------------------------------------------------------- 1 | const path = require('path'); 2 | const HTMLWebpackPlugin = require('html-webpack-plugin'); 3 | 4 | module.exports = { 5 | entry: './src/client/index.tsx', 6 | 7 | output: { 8 | path: path.join(__dirname, '/dist'), 9 | publicPath: '/', 10 | filename: 'bundle.js' 11 | }, 12 | 13 | devtool: 'inline-source-map', 14 | resolve: { 15 | extensions: ['.tsx', '.ts', '.js'], 16 | }, 17 | 18 | devServer: { 19 | historyApiFallback: true, 20 | hot: true, 21 | proxy: { 22 | '/api/**': { 23 | target: 'http://localhost:3000', 24 | secure: false, 25 | }, 26 | }, 27 | compress: true, 28 | port: 8080, 29 | static: { 30 | directory: path.resolve(__dirname, 'dist'), 31 | publicPath: '/', 32 | }, 33 | }, 34 | 35 | plugins: [ 36 | new HTMLWebpackPlugin({ 37 | template: './src/client/index.html' 38 | }) 39 | ], 40 | module: { 41 | rules: [ 42 | { 43 | test: /\.tsx?$/, 44 | use: 'ts-loader', 45 | exclude: /node_modules/, 46 | }, 47 | { 48 | test: /\.js$/, 49 | exclude: /node_modules/, 50 | use: { 51 | loader: 'babel-loader', 52 | options: { 53 | presets: ['@babel/preset-env', '@babel/preset-react'] 54 | } 55 | } 56 | }, 57 | { 58 | test: /\.s[ac]ss$/i, 59 | use: ['style-loader', 'css-loader', 'sass-loader'], 60 | }, 61 | { 62 | test: /\.(png|jpe?g|gif)$/i, 63 | use: [ 64 | { 65 | loader: 'file-loader' 66 | } 67 | ] 68 | } 69 | ] 70 | } 71 | } --------------------------------------------------------------------------------