├── .DS_Store
├── .env
├── .gitignore
├── LICENSE
├── NPM_PartialCache
├── approx_LRU.js
├── helperfunctions
│ ├── checkCacheFunc.js
│ ├── queryNormalizingFuncs.js
│ └── setCacheFunc.js
└── partialQuery.js
├── README.md
├── __tests__
├── demoFuncTests.js
└── supertest.js
├── demo
├── .DS_Store
├── client
│ ├── .DS_Store
│ ├── App.js
│ ├── clientSideCache.js
│ ├── components
│ │ ├── BarChart.js
│ │ ├── Footer.js
│ │ ├── LLNode.js
│ │ ├── LandingContent.js
│ │ ├── LandingContent2.js
│ │ ├── LineChart.js
│ │ ├── NavBar.js
│ │ ├── QueueVisualizer.js
│ │ ├── RemovedLLNode.js
│ │ ├── TeamMemberCard.js
│ │ ├── Testimonials.js
│ │ └── TextQuery.js
│ ├── index.html
│ ├── index.js
│ ├── pages
│ │ ├── About.js
│ │ ├── Demo.js
│ │ ├── Docs.js
│ │ └── Landing.js
│ ├── style.css
│ └── styles
│ │ ├── CachierNavBar(1).png
│ │ ├── Demo.css
│ │ ├── Demo.png
│ │ ├── FastIcon.png
│ │ ├── LLNode.css
│ │ ├── QueueVisualizer.css
│ │ ├── andrew.png
│ │ ├── andy.png
│ │ ├── arrow.png
│ │ ├── cachierlogo.png
│ │ ├── customizableIcon.png
│ │ ├── dhruv.png
│ │ ├── github.png
│ │ ├── graphqlLogo.png
│ │ ├── graphqlgif.gif
│ │ ├── hua.png
│ │ ├── installIcon.png
│ │ ├── jonathan.png
│ │ ├── kaju.png
│ │ ├── linkedin.png
│ │ ├── logo.png
│ │ ├── memoryIcon.png
│ │ ├── partialExampleFetch.png
│ │ ├── roman.png
│ │ ├── spritesheet (1).json
│ │ ├── spritesheet (1).png
│ │ └── stephen.png
├── server
│ ├── .DS_Store
│ ├── DemoFunc.js
│ ├── DemoFunc.ts
│ ├── cacheMoney.js
│ ├── config
│ │ └── db.js
│ ├── models
│ │ ├── Client.js
│ │ └── Project.js
│ ├── schema.js
│ └── server.ts
└── types.ts
├── dist
├── 1eb4c1e9323b71c1279e966e284e1416.gif
├── 29f842ac51c3b17ca07d25a7a09e9735.png
├── 2de6e7ef4ae9042ffd405c8dcbea9aa2.png
├── 2f4208e044c4212969a90d801ef2def8.png
├── 3734ecb3aeb09570f47139362d249be2.png
├── 5039e39a6d6ed8a3d80d43ca1d8c21ee.png
├── 56ea5f568f82e563dfd64ee6a897ef81.png
├── 6b51cea952681842ad6491b49cc56ac2.png
├── 7615be16eed41f806def1ba19d38b46d.png
├── 7664632ce349168853d72e7bb255189b.png
├── 7a6a6c54279a54d1977c9127957a503b.png
├── 852e2634ae6b8384f6fb01d089d92ff6.png
├── 8b89ce01929a419c56256c919e9a61c5.png
├── 8ee3e3d00d4f2f2b77e5cdf5c8d6fafd.png
├── 9a93576d1efc4d5a58034a34d531ec54.png
├── 9e46cba70d2c686f09a050cbce09c62b.png
├── a8a8f596e6871332d41dd5a6117a6427.png
├── a8dc608259608215f26a72fd33e96291.png
├── ac2e1e60fb0e4e4f77e02f9d9121c657.png
├── bundle.js
├── bundle.js.LICENSE.txt
├── c2206b27d7cd6ce4c5fef6b5c60389a6.png
└── index.html
├── package-lock.json
├── package.json
├── postcss.config.js
├── tailwind.config.js
├── tsconfig.json
└── webpack.config.js
/.DS_Store:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/oslabs-beta/Cachier/2e267d1765a9bf5f3d62e7a3c258ea0d3159ff9a/.DS_Store
--------------------------------------------------------------------------------
/.env:
--------------------------------------------------------------------------------
1 | NODE_ENV='production'
2 | PORT = 3000
3 | MONGO_URI = 'mongodb+srv://kajusarkar1:hushmail@cluster0.5n57uxr.mongodb.net/mgmt_db?retryWrites=true&w=majority'
4 |
5 | MONGO_USERNAME = 'kajusarkar@gmail.com'
6 | MONGO_PASSWORD = 'S7khuPkva2gaSESw'
7 |
8 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | node_modules
2 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2022 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 |
--------------------------------------------------------------------------------
/NPM_PartialCache/approx_LRU.js:
--------------------------------------------------------------------------------
1 | function approxLRU(cache, sampleSize) {
2 | const allKeys = Object.keys(cache);
3 | const samplePool = [];
4 |
5 | if (allKeys.length < sampleSize) {
6 | sampleSize = allKeys.length;
7 | }
8 |
9 | for (let i = 0; i < sampleSize; i++) {
10 | const randomNum = Math.floor(Math.random() * allKeys.length);
11 | const randomKey = allKeys[randomNum];
12 | samplePool.push(randomKey);
13 | }
14 | let leastRecentlyUsedKey;
15 | let currLeastRecentDate = Infinity;
16 | for (let i = 0; i < samplePool.length; i++) {
17 | const currKey = samplePool[i];
18 | if (
19 | Array.isArray(cache[currKey]) &&
20 | cache[currKey][cache[currKey].length - 1] < currLeastRecentDate
21 | ) {
22 | currLeastRecentDate = cache[currKey][cache[currKey].length - 1];
23 | leastRecentlyUsedKey = currKey;
24 | } else if (cache[currKey]['__CachierCacheDate'] < currLeastRecentDate) {
25 | currLeastRecentDate = cache[currKey].__CachierCacheDate;
26 | leastRecentlyUsedKey = currKey;
27 | }
28 | }
29 | delete cache[leastRecentlyUsedKey];
30 |
31 | return;
32 | }
33 |
34 | module.exports = approxLRU;
35 |
--------------------------------------------------------------------------------
/NPM_PartialCache/helperfunctions/checkCacheFunc.js:
--------------------------------------------------------------------------------
1 | function checkCache(normalizedQuery, cache) {
2 | const { typesArr, cacheKeysArr, fieldsArr } = normalizedQuery;
3 | const resultData = { data: {} };
4 | const currData = resultData.data;
5 | for (let i = 0; i < fieldsArr.length; i++) {
6 | let currCacheKey = cacheKeysArr[i];
7 | iterateCache(fieldsArr[i], currCacheKey, currData, cache);
8 | depthCount = 0;
9 | }
10 | if (checkMissingData) {
11 | depthCount = 0;
12 | return resultData;
13 | } else {
14 | depthCount = 0;
15 | checkMissingData = true;
16 | return false;
17 | }
18 | }
19 |
20 | let depthCount = 0;
21 | let checkMissingData = true;
22 |
23 | function iterateCache(fieldArr, currCacheKey, currReturnData, currCache) {
24 | const currDepthObj = {};
25 | let currCacheObj = currCache[currCacheKey];
26 | depthCount++;
27 | currReturnData[currCacheKey] = currDepthObj;
28 |
29 | for (let j = 0; j < fieldArr.length; j++) {
30 | if (currCacheObj === undefined) {
31 | checkMissingData = false;
32 | return;
33 | } else {
34 | if (currCacheObj['__CachierCacheDate'] !== undefined) {
35 | currCacheObj['__CachierCacheDate'] = performance.now();
36 | }
37 | if (Array.isArray(currCacheObj)) {
38 | const tempArr = [];
39 | let index = 0;
40 |
41 | for (let i = 0; i < currCacheObj.length - 1; i++) {
42 | tempArr.push(
43 | iterateCache(
44 | fieldArr,
45 | currCacheObj[index],
46 | currReturnData[currCacheKey],
47 | currCache
48 | )
49 | );
50 | if (tempArr[index] === false) {
51 | checkMissingData = false;
52 | return;
53 | }
54 | index++;
55 | }
56 | if (depthCount <= 1) {
57 | currCacheObj[currCacheObj.length - 1] = performance.now();
58 | }
59 | currReturnData[currCacheKey] = tempArr;
60 | } else if (typeof fieldArr[j] === 'string') {
61 | if (currCacheObj[fieldArr[j]] === undefined) {
62 | checkMissingData = false;
63 | return;
64 | }
65 | currDepthObj[fieldArr[j]] = currCacheObj[fieldArr[j]];
66 |
67 | if (currCacheObj.__CachierCacheDate !== undefined) {
68 | currCacheObj['__CachierCacheDate'] = performance.now();
69 | }
70 | } else {
71 | const currNestedFieldName = Object.keys(fieldArr[j])[0];
72 | const innerField = fieldArr[j][currNestedFieldName];
73 | if (currCacheObj[currNestedFieldName] === null) {
74 | currDepthObj[currNestedFieldName] = null;
75 | } else {
76 | const result = iterateCache(
77 | innerField,
78 | currNestedFieldName,
79 | currDepthObj,
80 | currCacheObj
81 | );
82 | if (result === false) {
83 | checkMissingData = false;
84 | return;
85 | }
86 | }
87 | }
88 | }
89 | }
90 | depthCount--;
91 | return currDepthObj;
92 | }
93 |
94 | module.exports = checkCache;
95 |
--------------------------------------------------------------------------------
/NPM_PartialCache/helperfunctions/queryNormalizingFuncs.js:
--------------------------------------------------------------------------------
1 | function queryNormalizer(query, addType = true) {
2 | if (addType) {
3 | query = addTypenameField(query);
4 | }
5 | const outerQueryArr = seperateOuterQueries(query);
6 | const normalizedQueryObj = {};
7 | normalizedQueryObj.typesArr = outerQueryArr.map((query) => checkType(query));
8 | normalizedQueryObj.cacheKeysArr = outerQueryArr.map((query) =>
9 | createKeyForVariableQuery(query)
10 | );
11 | normalizedQueryObj.fieldsArr = outerQueryArr.map((query) =>
12 | seperateInnerQueries(query)
13 | );
14 |
15 | return normalizedQueryObj;
16 | }
17 |
18 | //Adds '__typename' field to every sub query. Helper function to ensure each subquery returns its typename which is crucial for creating unique keys in the cache.
19 | function addTypenameField(query) {
20 | let newQuery = '';
21 | let bracketCount = 2;
22 | let index = 0;
23 | while (bracketCount > 0) {
24 | if (query[index] === '{') bracketCount--;
25 | newQuery += query[index++];
26 | }
27 |
28 | newQuery += ' __typename';
29 |
30 | while (query[index] !== undefined) {
31 | newQuery += query[index];
32 | if (query[index] === '{') newQuery += ' __typename';
33 | index++;
34 | }
35 | return newQuery;
36 | }
37 |
38 | function seperateOuterQueries(query) {
39 | const outerQueryArr = [];
40 | let index = 0;
41 | let mainbracketCount = 1;
42 | while (query[index] !== '{') {
43 | index++;
44 | }
45 | index++;
46 | while (mainbracketCount > 0) {
47 | let currQueryStr = '{';
48 | let currInnerBracketCount = 1;
49 | while (query[index] !== '{') {
50 | if (query[index] === '}') {
51 | return outerQueryArr;
52 | }
53 | currQueryStr += query[index];
54 | index++;
55 | }
56 | currQueryStr += query[index];
57 | index++;
58 | mainbracketCount++;
59 | while (currInnerBracketCount > 0) {
60 | if (query[index] === '{') {
61 | currInnerBracketCount++;
62 | mainbracketCount++;
63 | }
64 | if (query[index] === '}') {
65 | currInnerBracketCount--;
66 | mainbracketCount--;
67 | }
68 | currQueryStr += query[index];
69 | index++;
70 | }
71 | outerQueryArr.push((currQueryStr += '}'));
72 | }
73 | return outerQueryArr;
74 | }
75 |
76 | function checkVariable(query) {
77 | const variables = {};
78 | let key = '';
79 | let value = '';
80 | let index = 0;
81 |
82 | while (query[index] !== '(') {
83 | if (query[index] === '}') return false;
84 | index++;
85 | }
86 | while (query[++index] !== ':') {
87 | key += query[index];
88 | }
89 | while (query[++index] !== ')') {
90 | if (query[index] !== '"' && query[index] !== "'" && query[index] !== ' ') {
91 | value += query[index];
92 | }
93 | }
94 | key = key.toLowerCase().trim();
95 | variables[key] = value.trim();
96 |
97 | return variables;
98 | }
99 |
100 | function checkType(query) {
101 | let index = 0;
102 | let str = '';
103 | while (query[index] !== '{') {
104 | index++;
105 | }
106 | index++;
107 | while (query[index] !== '(' && query[index] !== '{') {
108 | if (query[index] !== ' ' && query.charCodeAt(index) !== 10) {
109 | str += query[index];
110 | }
111 | index++;
112 | }
113 |
114 | return str;
115 | }
116 |
117 | function seperateInnerQueries(query) {
118 | const resultArr = [];
119 | let bracketCount = 2;
120 | let index = 0;
121 | let obj = {};
122 | let tempStr = '';
123 |
124 | while (bracketCount > 0) {
125 | if (query[index++] === '{') bracketCount--;
126 | }
127 | const helper = () => {
128 | while (bracketCount >= 0 && index < query.length) {
129 | if (query[index] === '}') {
130 | bracketCount--;
131 | index++;
132 | }
133 |
134 | while (query[index] === ' ' || query.charCodeAt(index) === 10) {
135 | ++index;
136 | }
137 | while (
138 | query[index] !== ' ' &&
139 | query.charCodeAt(index) !== 10 &&
140 | index < query.length &&
141 | query[index] !== '}' &&
142 | query[index] !== '{'
143 | ) {
144 | tempStr += query[index];
145 | ++index;
146 | }
147 | if (query[index] === '{') {
148 | bracketCount++;
149 | index++;
150 | const key = resultArr[resultArr.length - 1];
151 | resultArr[resultArr.length - 1] = {};
152 | resultArr[resultArr.length - 1][key] = [];
153 | let currArray = resultArr[resultArr.length - 1][key];
154 |
155 | while (bracketCount >= 1) {
156 | let nestStr = '';
157 | if (query[index] === '}') {
158 | bracketCount--;
159 | }
160 | while (
161 | query[index] !== ' ' &&
162 | query.charCodeAt(index) !== 10 &&
163 | index < query.length &&
164 | query[index] !== '}' &&
165 | query[index] !== '{'
166 | ) {
167 | nestStr += query[index];
168 | ++index;
169 | }
170 |
171 | if (query[index + 1] === '{') {
172 | bracketCount++;
173 |
174 | currArray.push({});
175 | currArray[currArray.length - 1][nestStr] = [];
176 | currArray = currArray[currArray.length - 1][nestStr];
177 | nestStr = '';
178 | } else if (nestStr !== '') {
179 | currArray.push(nestStr);
180 | }
181 | index++;
182 | }
183 | // }
184 | }
185 | if (tempStr !== '') {
186 | resultArr.push(tempStr);
187 | tempStr = '';
188 | }
189 | }
190 |
191 | index++;
192 | };
193 | helper();
194 | return resultArr;
195 | }
196 |
197 | function createKeyForVariableQuery(query) {
198 | const variable = Object.keys(checkVariable(query))[0];
199 | const id = checkVariable(query)[variable];
200 | const type = checkType(query);
201 | if (!id) return type;
202 | return `${type}:${id}`;
203 | }
204 |
205 | module.exports = { queryNormalizer, addTypenameField };
206 |
--------------------------------------------------------------------------------
/NPM_PartialCache/helperfunctions/setCacheFunc.js:
--------------------------------------------------------------------------------
1 | let depthCount = 0;
2 |
3 | function cacheNewData(normalizedQuery, data, cache, uniques) {
4 | const { cacheKeysArr, fieldsArr } = normalizedQuery;
5 | const currData = data.data;
6 | for (let i = 0; i < fieldsArr.length; i++) {
7 | let currCacheKey = cacheKeysArr[i];
8 | iterateFieldsArr(
9 | fieldsArr[i],
10 | currCacheKey,
11 | currData,
12 | currCacheKey,
13 | cache,
14 | uniques
15 | );
16 | depthCount = 0;
17 | }
18 | return;
19 | }
20 |
21 | function iterateFieldsArr(
22 | fieldArr,
23 | currCacheKey,
24 | data,
25 | dataKey,
26 | currCache,
27 | uniques
28 | ) {
29 | ++depthCount;
30 | let currDepthObj = {};
31 | currCache[currCacheKey] = currDepthObj;
32 |
33 | if (Array.isArray(data[dataKey])) {
34 | const tempArr = [];
35 | let index = 0;
36 |
37 | data[dataKey].forEach((obj) => {
38 | const unique = uniques[currCacheKey] || 'id';
39 | const objTypeName = `${obj.__typename}`;
40 | const uniqueKey = `${objTypeName.toLowerCase()}:${obj[unique]}`;
41 |
42 | tempArr.push(uniqueKey);
43 |
44 | currCache[uniqueKey] = iterateFieldsArr(
45 | fieldArr,
46 | index,
47 | data[dataKey],
48 | index,
49 | currCache[currCacheKey],
50 | uniques
51 | );
52 | if (depthCount <= 1) {
53 | currCache[uniqueKey]['__CachierCacheDate'] = performance.now();
54 | }
55 | index++;
56 | });
57 | if (depthCount <= 1) {
58 | tempArr.push(performance.now());
59 | }
60 | currCache[currCacheKey] = tempArr;
61 | } else {
62 | for (let j = 0; j < fieldArr.length; j++) {
63 | if (typeof fieldArr[j] === 'string') {
64 | currDepthObj[fieldArr[j]] = data[dataKey][fieldArr[j]];
65 | if (depthCount <= 1) {
66 | currCache[currCacheKey]['__CachierCacheDate'] = performance.now();
67 | }
68 | } else {
69 | if (depthCount <= 1) {
70 | currCache[currCacheKey]['__CachierCacheDate'] = performance.now();
71 | }
72 | const currNestedFieldName = Object.keys(fieldArr[j])[0];
73 | if (data[dataKey][currNestedFieldName] === null) {
74 | currCache[dataKey][currNestedFieldName] = null;
75 | } else {
76 | const innerField = fieldArr[j][currNestedFieldName];
77 | iterateFieldsArr(
78 | innerField,
79 | currNestedFieldName,
80 | data[dataKey],
81 | currNestedFieldName,
82 | currCache[currCacheKey],
83 | uniques
84 | );
85 | }
86 | }
87 | }
88 | }
89 |
90 | depthCount--;
91 | return currDepthObj;
92 | }
93 |
94 | module.exports = cacheNewData;
95 |
--------------------------------------------------------------------------------
/NPM_PartialCache/partialQuery.js:
--------------------------------------------------------------------------------
1 | const fetch = (...args) =>
2 | import('node-fetch').then(({ default: fetch }) => fetch(...args));
3 |
4 | const checkCache = require('./helperfunctions/checkCacheFunc.js');
5 | const {
6 | queryNormalizer,
7 | addTypenameField,
8 | } = require('./helperfunctions/queryNormalizingFuncs.js');
9 | const cacheNewData = require('./helperfunctions/setCacheFunc.js');
10 | const evictionPolicy = require('./approx_LRU');
11 |
12 | function partialQueryCache(
13 | endpoint,
14 | capacity = 100,
15 | sampleSize = 5,
16 | evictionSize = 5
17 | ) {
18 | const cache = {};
19 | return async function helper(req, res, next) {
20 | const { query, uniques } = req.body;
21 | const dataFromCache = checkCache(queryNormalizer(query, false), cache);
22 | if (dataFromCache !== false) {
23 | return res.json(dataFromCache);
24 | } else {
25 | const queryWithTypename = addTypenameField(query);
26 | fetch(endpoint, {
27 | method: 'POST',
28 | headers: { 'Content-type': 'application/json' },
29 | body: JSON.stringify({
30 | query: queryWithTypename,
31 | }),
32 | })
33 | .then((response) => response.json())
34 | .then((data) => {
35 | res.json(data);
36 | cacheNewData(queryNormalizer(query), data, cache, uniques);
37 |
38 | while (Object.keys(cache).length > capacity * 100) {
39 | for (let i = 0; i < evictionSize; i++) {
40 | evictionPolicy(cache, sampleSize);
41 | }
42 | }
43 | return;
44 | });
45 | }
46 | };
47 | }
48 |
49 | module.exports = partialQueryCache;
50 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 |
2 |
3 | [![Contributors][contributors-shield]][contributors-url]
4 | [![Forks][forks-shield]][forks-url]
5 | [![Stargazers][stars-shield]][stars-url]
6 | [![Issues][issues-shield]][issues-url]
7 | [![MIT License][license-shield]][license-url]
8 | [![LinkedIn][linkedin-shield]][linkedin-url]
9 |
10 |
11 |
12 |
13 |
33 |
34 |
35 |
36 | Table of Contents
37 |
38 |
39 | About The Project
40 |
43 |
44 |
45 | Getting Started
46 |
50 |
51 | Usage
52 | Roadmap
53 | Contributing
54 | License
55 | Contact
56 | Acknowledgments
57 |
58 |
59 |
60 |
61 | ## About The Project
62 |
63 | ### Built With
64 |
65 | [![React][React.js]][React-url]
66 | [![Redis][Redis.io]][Redis-url]
67 | [![GraphQL][GraphQL.io]][GraphQL-url]
68 | [![Node/Express][Express.io]][Express-url]
69 | [![TailwindCSS][TailwindCSS.io]][Tailwind-url]
70 |
71 | (back to top )
72 |
73 | Welcome to Cachier, a lightweight GraphQL caching tool that is configured specifically for GraphQL to reduce load times and minimize data fetching.
74 |
75 | GraphQL does not have native HTTP caching as a result of its singular employment of the POST method, forcing the danger of over-fetching by re-running queries and slowing load times. Our team of engineers developed a compact, easy-to-use solution that allows users to cache their queries on the server side and client side!
76 |
77 | #### Cachier currently offers:
78 | - Storage inside session storage for client side caching
79 | - Ability to choose between Redis and a native in memory cache
80 | - Unique key generation for response data to avoid developer having to tag for the cache
81 | - Partial and exact matching for query fields in the developer's GraphQL API
82 | - Highly configurable eviction policies
83 |
84 |
85 | #### We created a highly performant and customizable GraphQL caching library that consists of three main caching functions:
86 | - Cachier Normalized Server-side Cache
87 | - Cachier Direct Server-side Cache
88 | - Cachier Direct Client-side Cache
89 |
90 | We will go over each solution in detail below:
91 |
92 |
93 | ## Getting Started
94 | ## Cachier Normalized Server-side Cache
95 |
96 | Cachier's Normalized Server-side Cache breaks up GraphQL queries into individual sub-queries to be stored in the cache. This provides maximum cache efficency by organizing data in a way that prevents data redundancy and allows for partial retrievals of subset data, thus drastically reducing network requests to the database.
97 |
98 | ## Installation and Import
99 | If this is your first time using Cachier's Normalized Cache, run the following command in your terminal.
100 | ~~~
101 | npm install @cachier/cache-partials
102 | ~~~
103 |
104 | In your server file, require our middleware to handle GraphQL requests using the CommonJS format
105 | ~~~
106 | const Cachier = require('@cachier/cache-partials');
107 | ~~~
108 |
109 | ## Set up your Cachier middleware function
110 | endpoint - the endpoint that the client will make GraphQL queries to if it wants to utilize the cache.
111 | #### graphQLEndpoint
112 | - The graphQLEndpoint parameter is where you will specify your GraphQL APIs endpoint. This allows Cachier to route all queries that are unable to be resolved by the Cachier Cache to your GraphQL API.
113 | #### cacheCapacity
114 | - the cacheCapacity parameter allows you to specify a maximum cache length which allows cachier to know when to evict from the cache. All inputs for Capacity will be multiples of 100. The default parameter for Capacity is 100 (1000 keys in the cache).
115 | #### sampleSize
116 | - the sampleSize parameter allows the developer to configure the number of random keys that will be considered for eviction. The default sampleSize is 5 which we recommend for most applications.
117 | #### evictionSize
118 | - the sampleSize parameter allows the developer to configure the number of evictions what will be made when your cache capacity is reached. The default evictionSize is 5.
119 |
120 | ~~~
121 | app.use(
122 | endpoint,
123 | Cachier(graphQLEndPoint, cacheCapacity, sampleSize, evictionSize);
124 | ~~~
125 |
126 | (back to top )
127 |
128 |
129 | ## Usage
130 |
131 | ~~~
132 | app.use( '/Cachier', Cachier('https://api.spacex.land/graphql', 100, 5, 5) );
133 | ~~~
134 |
135 | To fetch from Cachier's normalized cache you will fetch like you would to your GraphQL API except you will need set the option for uniques in the request body. The uniques object will need to contain a unique identifier for all list items in your query. You will need to include the list name as the key and the unique identifier as a the value. The unique identifier is any piece of data that is queried that is unique to each list item!
136 |
137 | ~~~
138 | fetch('/graphql', {
139 | method: 'POST',
140 | headers:{
141 | 'Content-Type': 'application/json',
142 | Accept: 'application/json',
143 | },
144 | body: JSON.stringify({
145 | query: queryGraphQLString,
146 | uniques: {listKey :uniqueIdentifier},
147 | })
148 | });
149 | ~~~
150 |
151 |
152 | ### How it works
153 |
154 |
155 | Example Fetch to SpaceX GQL API:
156 |
157 | ~~~
158 | fetch('http://localhost:3000/partialCache', {
159 | method: 'POST',
160 | headers: {
161 | 'Content-Type': 'application/json',
162 | Accept: 'application/json',
163 | },
164 | body: JSON.stringify({
165 | query: {
166 | dragons {
167 | id
168 | return_payload_mass {
169 | kg
170 | }
171 | }
172 | }
173 | ,
174 | uniques: { dragons: 'id' },
175 | }),
176 | })
177 |
178 | ~~~
179 |
180 |
181 | The client will fetch to the Cachier Cache endpoint with an object containing the query string and the unique types. The unique types need contain a unique identifier for all array/list items so that Cachier can generate a unique cache key.
182 |
183 | ~~~
184 | {
185 | "typesArr": [
186 | "dragons"
187 | ],
188 | "fieldsArr": [
189 | [
190 | "__typename",
191 | "id",
192 | {
193 | "return_payload_mass": [
194 | "__typename",
195 | "kg"
196 | ]
197 | }
198 | ]
199 | ]
200 | }
201 | ~~~
202 |
203 |
204 | Cachier parses incoming GraphQL queries and seperates them into subqueries stored in a "Cachier" object. The queries are broken up into 2 arrays typesArr, and fieldsArr where their respective indexes connect with one another. FieldsArr will be an array of arrays containing the fields for each cacheKey, if a field is nested it will be stored as a nested object. We will then wait for the return Data and use this "Cachier" query object to sort the data into our cache.
205 |
206 |
207 | Here is data returned from our example query:
208 | ~~~
209 | {
210 | "data": {
211 | "dragons": [
212 | {
213 | "id": "dragon2",
214 | "return_payload_mass": {
215 | "kg": 3000
216 | }
217 | },
218 | {
219 | "id": "dragon1",
220 | "return_payload_mass": {
221 | "kg": 3000
222 | }
223 | }
224 | ]
225 | }
226 | }
227 | ~~~
228 |
229 | After receiving the data back Cachier will utilize the query map stored in the "Cachier" Object to normalize and store the data as individual keys inside the cache. This is how the data will look once normalized and stored in the cache:
230 | ~~~
231 | {
232 | "dragons": [
233 | "dragon:dragon2",
234 | "dragon:dragon1",
235 | 8492.694458007812
236 | ],
237 | "dragon:dragon2": {
238 | "__typename": "Dragon",
239 | "id": "dragon2",
240 | "return_payload_mass": {
241 | "__typename": "Mass",
242 | "kg": 3000
243 | },
244 | "__CachierCacheDate": 8492.681999921799
245 | },
246 | "dragon:dragon1": {
247 | "__typename": "Dragon",
248 | "id": "dragon1",
249 | "return_payload_mass": {
250 | "__typename": "Mass",
251 | "kg": 3000
252 | },
253 | "__CachierCacheDate": 8492.691667079926
254 | }
255 | }
256 | ~~~
257 | As you can see the dragons array now only stores references to keys in the cache and the data from the array is stored as seperate keys unique in the cache. This normalized cache structure eliminiates data redundancy in the cache and allows for partial retrieval of subset data. ("__CachierCacheData" fields and the number at the last index array is to keep track of recency for our eviction policy which we will speak about next).
258 |
259 |
260 | ### Approximated LRU Eviction
261 |
262 | Cachier's Normalized Cache uses a custom Approximated LRU Eviction Policy. This is not a true LRU implementation, but it comes very close in terms of performance. The reason Cachier does not use a true LRU implementation is because it costs more memory. Cachier's LRU policy works by creating a sample (the sample size can be configured by the developer) of randomly selected keys from the cache and evicting the least recently used key from the sample.
263 |
264 | (back to top )
265 |
266 | ## Cachier Direct Server-side Cache
267 | Cachier's Direct Server-side Cache uses a custom LRU-SLFR (Least Recently Used Smallest Latency First Replacement) policy. LRU-SLFR is very similar to LRU except it takes latency into account as well as recency when evicting. Cachier's LRU-SLFR eviction policy utilizes a linked hash map to achieve true LRU and allows O(1) deletion, lookup, and insertion. Cachier takes latency into account as well as recency by creating a group of least recent queries and removes the query with the lowest latency first. This allows for much smarter evictions compared to traditional LRU. The whole group will be evicted first before moving on to the next group. Check out the demo page for a visualization of the eviction policy
268 |
269 | ### How to install and import
270 | If this is your first time using Cachier's Direct Server-side Cache, run the following command in your terminal.
271 |
272 | ~~~
273 | npm install @cachier/server-side
274 | ~~~
275 |
276 | In your server file, require our middleware to handle GraphQL requests using the CommonJS format
277 |
278 | ~~~
279 | const Cachier = require('@cachier/server-side')
280 | ~~~
281 |
282 | ### Set up your Cachier middleware function
283 |
284 | #### Endpoint
285 | - The endpoint that the client will make GraphQL queries to if it wants to utilize the cache.
286 | #### graphQLEndpoint
287 | - The graphQLEndpoint parameter is where you will specify your GraphQL APIs endpoint. This allows Cachier to route all queries that are unable to be resolved by the Cachier Cache to your GraphQL API.
288 | #### capacity
289 | - the cacheCapacity parameter allows you to specify a maximum cache length which allows cachier to know when to evict from the cache.
290 | #### groupSize
291 | - the groupSize parameter allows the developer to configure the number of least recently used keys that will be considered for eviction. The key with the least latency out of the group will be evicted first. The whole group will be evicted first before moving on to the next group.
292 | #### RedisClient
293 | - If you would like to use Redis to store your cache, insert your connected redis client as an arguement. If you leave out this parameter Cachier will default to its native built in cache.
294 |
295 |
296 | ~~~
297 | app.use(
298 | endpoint,
299 | Cachier(graphqlEndpoint, capacity, groupSize, RedisClient(optional));
300 | ~~~
301 |
302 | Example implementation without Redis:
303 |
304 | ~~~
305 | app.use( '/Cachier', Cachier('https://api.spacex.land/graphql', 100, 5) );
306 | ~~~
307 |
308 |
309 | ### If using Redis
310 | First, install the Redis package for Node.js
311 |
312 | `npm install redis`
313 |
314 | Then install redis npm package.
315 | `npm install _______`
316 | 1. Install Redis
317 | - MacOS users: [Redis installation for MacOS](https://redis.io/docs/getting-started/installation/install-redis-on-mac-os/)
318 | - Linux users: [Redis installation for Linux](https://redis.io/docs/getting-started/installation/install-redis-on-linux/)
319 | - Windows users:
320 | 1. Redis is not officially supported on Windows, so you must have a [Windows Subsystem for Linux](https://learn.microsoft.com/en-us/windows/wsl/install).
321 | 2. Once you have WSL, follow [Redis installation for Windows](https://redis.io/docs/getting-started/installation/install-redis-on-windows/)
322 |
323 |
324 | ## Cachier Direct Client-side Cache
325 |
326 | Cachier's Direct Client-Side Cache uses the same underlying mechanisms as Cachier's Direct Server-side cache except it stores the cache in the client browsers session storage. This allows for even faster cached query times than a server side implementation. Cachier's client side cache was built to mimic a traditional fetch request so it is very easy to integrate into new and existing codebases.
327 |
328 | ### Installation and Import
329 |
330 | If this is your first time using Cachier's Direct Client-side Cache, run the following command in your terminal.
331 |
332 | ~~~
333 | npm install @cachier/client-side
334 | ~~~
335 |
336 | In your client file, import the cachier client side function:
337 |
338 | ~~~
339 | import clientSideCache from '@cachier/client-side';
340 | ~~~
341 |
342 | ### Initalize your Cachier client-side cache
343 |
344 | #### capacity
345 | - the cacheCapacity parameter allows you to specify a maximum cache length which allows cachier to know when to evict from the cache.
346 | #### groupSize
347 | - the groupSize parameter allows the developer to configure the number of least recently used keys that will be considered for eviction. The key with the least latency out of the group will be evicted first. The whole group will be evicted first before moving on to the next group.
348 |
349 | ~~~
350 | const cachierFetch = clientSideCache(500, 5);
351 | ~~~
352 |
353 | Operates exactly like fetch():
354 |
355 | ~~~
356 | cachierFetch('/graphql', {
357 | method: 'POST',
358 | headers:{
359 | 'Content-Type': 'application/json',
360 | Accept: 'application/json',
361 | },
362 | body: JSON.stringify({
363 | query: queryGraphQLString,
364 | })
365 | });
366 | ~~~
367 |
368 |
369 | ## Roadmap
370 |
371 | - [ ] Mutation handling
372 | - [ ] Full partial querying
373 | - [ ] Demo with more options
374 | - [ ] Faster text editor
375 |
376 | See the [open issues](https://github.com/oslabs-beta/Cachier/issues) for a full list of proposed features (and known issues).
377 |
378 | (back to top )
379 |
380 |
381 | ## Contributing
382 |
383 | Contributions are what make the open source community such an amazing place to learn, inspire, and create. Any contributions you make are **greatly appreciated**.
384 |
385 | If you have a suggestion that would make this better, please fork the repo and create a pull request. You can also simply open an issue with the tag "enhancement".
386 | Don't forget to give the project a star! Thanks again!
387 |
388 | 1. Fork the Project
389 | 2. Create your Feature Branch (`git checkout -b feature/AmazingFeature`)
390 | 3. Commit your Changes (`git commit -m 'Add some AmazingFeature'`)
391 | 4. Push to the Branch (`git push origin feature/AmazingFeature`)
392 | 5. Open a Pull Request
393 |
394 | (back to top )
395 |
396 | ## Tech stack used
397 | Node - Express - React - Tailwind CSS - ChartJS - Redis - GraphQL - TypeScript - Jest - Supertest - Webpack
398 |
399 | ## Here's how to contribute to our open source library
400 | Our vision for our open-source project is for fellow developers to be able to interate on and improve this tool. This is exactly where you and the community comes in. So, if you have an idea that can make Cachier better, you can make that idea come to life by following the steps below:
401 |
402 | 1. Fork Cachier
403 | 2. Pull down our dev branch with command ```git pull origin dev```
404 | 3. Create your own Feature Branch with the command ```git checkout -b ```
405 | 4. Add your changes with the command ```git add .```
406 | 5. Stage and commit your changes with the command ```git commit -m ""```
407 | 6. Merge your branch with the dev branch locally with the command ```git merge dev```
408 | 7. Resolve any merge conflicts
409 | 8. Push up your branch with the command ```git push origin ```
410 | 9. Open a pull request
411 |
412 | Please star our repo if you've found this useful, we want to be able to help as many of developers as we can!
413 |
414 | ## Contributors
415 | * Andy Zheng || [Github](https://github.com/andy5313) || [Linkedin](https://www.linkedin.com/in/andyzheng5313/)
416 | * Dhruv Thota || [Github](https://github.com/L05Dhruv) || [Linkedin](https://www.linkedin.com/in/dhruv-thota/)
417 | * Jonathan Chen || [Github](https://github.com/jchen0903i) || [Linkedin](https://www.linkedin.com/in/jonathan-chen3/)
418 | * Kaju Sarkar || [Github](https://github.com/kajusarkar) || [Linkedin](https://www.linkedin.com/in/kaju-sarkar-a6329862/)
419 | * Roman Darker || [Github](https://github.com/romanjamesd) || [Linkedin](https://www.linkedin.com/in/roman-darker-707147175/)
420 |
421 | ## Works cited
422 | - LRU based small latency first replacement (SLFR) algorithm for the proxy cache. (2003). Proceedings IEEE/WIC International Conference on Web Intelligence (WI 2003). https://doi.org/10.1109/wi.2003.1241250
423 | - Wang, Y., Yang, J., & Wang, Z. (2020). Dynamically Configuring LRU Replacement Policy in Redis. The International Symposium on Memory Systems. https://doi.org/10.1145/3422575.3422799
424 | - Morales, K., & Lee, B. K. (2012). Fixed Segmented LRU cache replacement scheme with selective caching. 2012 IEEE 31st International Performance Computing and Communications Conference (IPCCC). https://doi.org/10.1109/pccc.2012.6407712
425 |
426 |
427 | ## License
428 | Distributed under the MIT license.
429 |
430 |
431 |
432 | [contributors-shield]: https://img.shields.io/github/contributors/oslabs-beta/Cachier.svg?style=for-the-badge
433 | [contributors-url]: https://github.com/oslabs-beta/Cachier/graphs/contributors
434 | [forks-shield]: https://img.shields.io/github/forks/oslabs-beta/Cachier.svg?style=for-the-badge
435 | [forks-url]: https://github.com/oslabs-beta/Cachier/network/members
436 | [stars-shield]: https://img.shields.io/github/stars/oslabs-beta/Cachier.svg?style=for-the-badge
437 | [stars-url]: https://github.com/oslabs-beta/Cachier/stargazers
438 | [issues-shield]: https://img.shields.io/github/issues/oslabs-beta/Cachier.svg?style=for-the-badge
439 | [issues-url]: https://github.com/oslabs-beta/Cachier/issues
440 | [license-shield]: https://img.shields.io/github/license/oslabs-beta/Cachier.svg?style=for-the-badge
441 | [license-url]: https://github.com/oslabs-beta/Cachier/blob/master/LICENSE.txt
442 | [linkedin-shield]: https://img.shields.io/badge/-LinkedIn-black.svg?style=for-the-badge&logo=linkedin&colorB=555
443 | [linkedin-url]: https://linkedin.com/company/cachier
444 | [product-screenshot]: images/screenshot.png
445 | [React.js]: https://img.shields.io/badge/React-20232A?style=for-the-badge&logo=react&logoColor=61DAFB
446 | [React-url]: https://reactjs.org/
447 | [Redis.io]: https://img.shields.io/badge/Redis-Data%20store-red
448 | [Redis-url]: https://redis.io/
449 | [GraphQL.io]: https://img.shields.io/badge/%20-GraphQL-brightgreen
450 | [GraphQL-url]: https://graphql.org/learn/
451 | [Express.io]: https://img.shields.io/badge/Node-Express-orange
452 | [Express-url]: https://expressjs.com/
453 | [TailwindCSS.io]: https://img.shields.io/badge/%20UI-TailwindCSS%20-blue
454 | [Tailwind-url]: https://v2.tailwindcss.com/docs
455 | [Vue.js]: https://img.shields.io/badge/Vue.js-35495E?style=for-the-badge&logo=vuedotjs&logoColor=4FC08D
456 | [Vue-url]: https://vuejs.org/
457 | [Angular.io]: https://img.shields.io/badge/Angular-DD0031?style=for-the-badge&logo=angular&logoColor=white
458 | [Angular-url]: https://angular.io/
459 |
460 |
--------------------------------------------------------------------------------
/__tests__/demoFuncTests.js:
--------------------------------------------------------------------------------
1 | this.capacity = capacity;
2 | this.groupSize = groupSize;
3 | this.redisClient = redisClient;
4 | this.endpoint = endpoint;
5 |
6 | // tests for CacheMoney function
7 | test("CacheMoney function should throw an error if capacity or groupSize is less than 1 or groupSize exceeds capacity", () => {
8 | expect(() => new CacheMoney(endpoint, 0, 1, redisClient)).toThrowError(
9 | "Capacity and groupSize needs to be a number greater than 1 and groupsize cannot exceed capacity"
10 | );
11 | });
12 |
13 | test("CacheMoney should create a new EvictionQueue", () => {
14 | const cacheMoney = new CacheMoney(endpoint, capacity, groupSize, redisClient);
15 | expect(cacheMoney.queue).toBeInstanceOf(EvictionQueue);
16 | });
17 |
18 | test("CacheMoney should assign capacity, groupSize, redisClient and endpoint correctly", () => {
19 | const cacheMoney = new CacheMoney(endpoint, capacity, groupSize, redisClient);
20 | expect(cacheMoney.capacity).toBe(capacity);
21 | expect(cacheMoney.groupSize).toBe(groupSize);
22 | expect(cacheMoney.redisClient).toBe(redisClient);
23 | expect(cacheMoney.endpoint).toBe(endpoint);
24 | });
25 |
26 | test("CacheMoney should have traverse function", () => {
27 | const cacheMoney = new CacheMoney(endpoint, capacity, groupSize, redisClient);
28 | expect(typeof cacheMoney.traverse).toBe('function');
29 | });
30 |
31 | describe('checkCache', () => {
32 | beforeEach(() => {
33 | const req = {
34 | body: {
35 | query: 'some query',
36 | variables: { some: 'variables' },
37 | }
38 | };
39 | res = jest.fn();
40 | next = jest.fn();
41 | });
42 |
43 | test('should set valueFromCache if redisClient is used', async () => {
44 | const redisClient = {
45 | get: jest.fn().mockReturnValue('someValue')
46 | }
47 | await checkCache(req, res, next);
48 | expect(redisClient.get).toHaveBeenCalled();
49 | expect(valueFromCache).toEqual('someValue');
50 | });
51 |
52 | test('should set valueFromCache if cacheMoneyCache is used', async () => {
53 | await checkCache(req, res, next);
54 | expect(cacheMoneyCache[cacheKey]).toBeDefined();
55 | expect(valueFromCache).toEqual(cacheMoneyCache[cacheKey]);
56 | });
57 | });
58 |
59 |
60 | describe('Testing if cache contains the requested data', () => {
61 | it('should update the recency of the accessed cacheKey and return data from the cache', () => {
62 | const valueFromCache = '{"data": "dummy"}';
63 | const cacheKey = 'cacheKey';
64 | const queue = jest.fn();
65 | const res = {
66 | send: jest.fn(),
67 | };
68 |
69 | queue.updateRecencyOfExistingCache = jest.fn();
70 | const listArray = jest.fn();
71 | const parsedValue = JSON.parse(valueFromCache);
72 | queue.add = jest.fn();
73 | queue.length = 10;
74 | queue.removeSmallestLatencyFromGroup = jest.fn(() => ({
75 | latency: 0,
76 | num: 0,
77 | }));
78 | const redisClient = jest.fn();
79 | redisClient.set = jest.fn();
80 | const cacheMoneyCache = {};
81 | const capacity = 10;
82 | const groupSize = 3;
83 | const currGroupSize = 3;
84 |
85 | if (valueFromCache) {
86 | // update recency of the accessed cacheKey by moving it to the front of the linked list.
87 | queue.updateRecencyOfExistingCache(cacheKey);
88 | // if cache contains the requested data return data from the cache.
89 | const listArray = traverse(queue);
90 |
91 | res.send({
92 | data: parsedValue,
93 | queue: listArray,
94 | currGroupSize,
95 | cached: true,
96 | });
97 |
98 | expect(res.send).toHaveBeenCalledWith({
99 | data: parsedValue,
100 | queue: listArray,
101 | currGroupSize,
102 | cached: true,
103 | });
104 | expect(queue.updateRecencyOfExistingCache).toHaveBeenCalledWith(cacheKey);
105 | }
106 | });
107 | });
108 | //accounts for if the minLatencyNodeInGroup is the tail of the list
109 | if (this.tail === minLatencyNodeInGroup) {
110 | this.tail = this.tail.prev;
111 | this.tail.next = null;
112 | this.length--;
113 | delete this.cache[minLatencyNodeInGroup.key];
114 | return minLatencyNodeInGroup;
115 | }
116 |
117 | //handles the case where minLatencyNodeInGroup is in middle of the list
118 | minLatencyNodeInGroup.prev.next = minLatencyNodeInGroup.next;
119 | minLatencyNodeInGroup.next.prev = minLatencyNodeInGroup.prev;
120 | this.length--;
121 | delete this.cache[minLatencyNodeInGroup.key];
122 | return minLatencyNodeInGroup;
123 |
124 |
125 |
126 | describe("add()", () => {
127 | let evictionQueue;
128 | beforeEach(() => {
129 | evictionQueue = new EvictionQueue();
130 | });
131 |
132 | it("Should add a new node to the head of the list", () => {
133 | const cacheKey = "test";
134 | const latency = 10;
135 | evictionQueue.add(cacheKey, latency);
136 | expect(evictionQueue.head.latency).toBe(10);
137 | expect(evictionQueue.head.key).toBe("test");
138 | });
139 | });
140 |
141 | describe("removeSmallestLatencyFromGroup()", () => {
142 | let evictionQueue;
143 | beforeEach(() => {
144 | evictionQueue = new EvictionQueue();
145 | });
146 |
147 | it("Should return the node with the smallest latency from the group", () => {
148 | evictionQueue.add("test1", 5);
149 | evictionQueue.add("test2", 10);
150 | evictionQueue.add("test3", 8);
151 | const minLatencyNodeInGroup = evictionQueue.removeSmallestLatencyFromGroup(3);
152 | expect(minLatencyNodeInGroup.key).toBe("test1");
153 | expect(minLatencyNodeInGroup.latency).toBe(5);
154 | expect(evictionQueue.length).toBe(2);
155 | });
156 | });
157 | describe("updateRecencyOfExistingCache", () => {
158 | it("should update the recency of an existing cache", () => {
159 | const evictionQueue = new EvictionQueue();
160 | evictionQueue.head = {
161 | key: "head",
162 | prev: null,
163 | next: {
164 | key: "middle",
165 | prev: {
166 | key: "tail",
167 | prev: null,
168 | next: null
169 | },
170 | next: null
171 | }
172 | };
173 | evictionQueue.tail = evictionQueue.head.next.prev;
174 | evictionQueue.cache = {
175 | head: evictionQueue.head,
176 | middle: evictionQueue.head.next,
177 | tail: evictionQueue.tail
178 | };
179 | evictionQueue.length = 3;
180 | evictionQueue.updateRecencyOfExistingCache("middle");
181 | expect(evictionQueue.head.key).toBe("middle");
182 | expect(evictionQueue.head.prev).toBe(null);
183 | expect(evictionQueue.head.next).toEqual({
184 | key: "head",
185 | prev: { key: "middle", prev: null, next: null },
186 | next: {
187 | key: "tail",
188 | prev: { key: "head", prev: null, next: null },
189 | next: null
190 | }
191 | });
192 | expect(evictionQueue.tail.key).toBe("tail");
193 | expect(evictionQueue.tail.next).toBe(null);
194 | expect(evictionQueue.tail.prev).toEqual({
195 | key: "head",
196 | prev: { key: "middle", prev: null, next: null },
197 | next: null
198 | });
199 | expect(evictionQueue.length).toBe(3);
200 | });
201 | });
202 |
--------------------------------------------------------------------------------
/__tests__/supertest.js:
--------------------------------------------------------------------------------
1 | const request = require('supertest');
2 | const app = require('../demo/server/server');
3 | const demoFunc = require('../demo/server/DemoFunc');
4 | const CacheMoney = require('../demo/server/cacheMoney');
5 |
6 | describe('GraphQL endpoint returns an object', () => {
7 | describe('/graphql', () => {
8 | it('is a json object with valid query', async () => {
9 | const response = await request(app)
10 | .post('/graphql')
11 | .send({ query: `{ clients { id name email phone } }` })
12 | .expect('Content-Type', /application\/json/)
13 | .expect(200)
14 | .then((res) => {
15 | expect(typeof res).toBe('object');
16 | expect(JSON.stringify(res)).toBe(JSON.stringify(res));
17 | });
18 | }, 15000);
19 |
20 | it('returns 400 status on invalid query', async () => {
21 | const response = await request(app)
22 | .post('/graphql')
23 | .send({ query: `` })
24 | .expect(400);
25 | });
26 | });
27 | });
28 |
29 | test('Demo func middleware check cache function', () => {
30 | expect(typeof demoFunc('http://localhost:3000/graphql', 4, 2)).toBe(
31 | 'function'
32 | );
33 | });
34 |
35 | test('Cachier caching returns check cache function', () => {
36 | expect(typeof CacheMoney('http://localhost:3000/graphql', 4, 2)).toBe(
37 | 'function'
38 | );
39 | });
40 |
--------------------------------------------------------------------------------
/demo/.DS_Store:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/oslabs-beta/Cachier/2e267d1765a9bf5f3d62e7a3c258ea0d3159ff9a/demo/.DS_Store
--------------------------------------------------------------------------------
/demo/client/.DS_Store:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/oslabs-beta/Cachier/2e267d1765a9bf5f3d62e7a3c258ea0d3159ff9a/demo/client/.DS_Store
--------------------------------------------------------------------------------
/demo/client/App.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { BrowserRouter, Routes, Route } from 'react-router-dom';
3 | import Demo from './pages/Demo';
4 | import Landing from './pages/Landing.js';
5 | import Docs from './pages/Docs';
6 | import About from './pages/About';
7 | import Footer from './components/Footer'
8 | import NavBar from './components/NavBar';
9 |
10 | const App = () => {
11 |
12 | return (
13 |
14 |
15 |
16 | }>
17 | }>
18 | }>
19 | }>
20 |
21 |
22 |
23 | );
24 | };
25 |
26 | export default App;
27 |
--------------------------------------------------------------------------------
/demo/client/clientSideCache.js:
--------------------------------------------------------------------------------
1 | //check if the clientSideCache capacity and groupSize are >1
2 | // if they are < 1 , throw error because that is not possible
3 | // if they are > 1, then set currGrouroupSize to groupSize
4 | //
5 | //check if the clientSideCache capacity and groupSize are >1
6 | // if they are < 1 , throw error because that is not possible
7 | // if they are > 1, then set currGrouroupSize to groupSize
8 | //
9 | export function clientSideCache(capacity, groupSize) {
10 | let currGroupSize = groupSize;
11 |
12 | if (capacity < 1 || groupSize < 1 || groupSize > capacity) {
13 | throw new Error(
14 | 'Capacity and groupSize needs to be a number greater than 1 and groupsize cannot exceed capacity'
15 | );
16 | }
17 |
18 | const queue = new EvictionQueue();
19 |
20 | return async function checkCache(endpoint, options) {
21 | const body = JSON.parse(options.body);
22 | const { query } = body;
23 | let { variables } = body;
24 |
25 | if (!variables) {
26 | variables = {};
27 | }
28 |
29 | const variablesStr = JSON.stringify(variables);
30 | const cacheKey = JSON.stringify(`${query}${variablesStr}`);
31 |
32 | const valueFromCache = sessionStorage.getItem(cacheKey);
33 |
34 | if (valueFromCache) {
35 | queue.updateRecencyOfExistingCache(cacheKey);
36 | return JSON.parse(valueFromCache);
37 | } else {
38 | const start = performance.now();
39 | return fetch(endpoint, {
40 | method: 'POST',
41 | headers: { 'Content-type': 'application/json' },
42 | body: JSON.stringify({
43 | query,
44 | variables,
45 | }),
46 | })
47 | .then((response) => response.json())
48 | .then((data) => {
49 | const end = performance.now();
50 | const latency = end - start;
51 | sessionStorage.setItem(cacheKey, JSON.stringify(data));
52 | queue.add(cacheKey, latency);
53 | if (queue.length > capacity) {
54 | const removedQueryKey =
55 | queue.removeSmallestLatencyFromGroup(currGroupSize).key;
56 |
57 | sessionStorage.removeItem(removedQueryKey);
58 |
59 | currGroupSize -= 1;
60 | if (currGroupSize <= 0) currGroupSize = groupSize;
61 | }
62 | return data;
63 | })
64 | .catch((err) => {
65 | console.log(
66 | `Error:${err} unable to fetch query from specified endpoint`
67 | );
68 | });
69 | }
70 | };
71 | }
72 |
73 | //Node constructor for our doubly linkedlist;
74 | class Node {
75 | constructor(key, latency) {
76 | this.key = key;
77 | this.latency = latency;
78 | this.next = null;
79 | this.prev = null;
80 | }
81 | }
82 |
83 | //Doubly linked list to keep track of our eviction order.
84 | class EvictionQueue {
85 | constructor() {
86 | //keeps track of front of queue.
87 | this.head = null;
88 | //keeps track of back of queue.
89 | this.tail = null;
90 | //keeps track of the number of nodes in the queue.
91 | this.length = 0;
92 | //Keeps all nodes of linkedlist in a hashmap associated with their redis keys to allow O(1) updates to node positions in the queue.
93 | this.cache = {};
94 | }
95 |
96 | //adds a node to the queue.
97 | add(cacheKey, latency) {
98 | //creates a new Node containing the latency of the query and its key in the cache.
99 | const newNode = new Node(cacheKey, latency);
100 | this.cache[cacheKey] = newNode;
101 | //accounts for if the queue is empty.
102 | if (this.head === null && this.tail === null) {
103 | this.head = newNode;
104 | this.tail = newNode;
105 | } else {
106 | //Will add new node to the front of the list by making it the new head.
107 | //makes old head prev point to the newNode.
108 | this.head.prev = newNode;
109 | // makes newNode point to old head and relabels newNode as the new head.
110 | newNode.next = this.head;
111 | this.head = newNode;
112 | }
113 |
114 | //updates length of the EvictionQueue.
115 | this.length += 1;
116 | }
117 |
118 | removeSmallestLatencyFromGroup(groupSize) {
119 | if (this.tail === null) return undefined;
120 |
121 | //keeps track of smallest latency node.
122 | let minLatencyNodeInGroup = this.tail;
123 | let currentNode = this.tail;
124 |
125 | //accounts for edge case if eviction policy capacity is set to 1 and the only node in queue is being removed.
126 | if (this.head === this.tail) {
127 | this.head = null;
128 | this.tail = null;
129 | this.length--;
130 | delete this.cache[currentNode.key];
131 | return currentNode;
132 | }
133 |
134 | //iterates through the least recent group and finds the smallest latency node.
135 | while (groupSize > 0 && currentNode) {
136 | if (currentNode.latency < minLatencyNodeInGroup.latency)
137 | minLatencyNodeInGroup = currentNode;
138 | currentNode = currentNode.prev;
139 | groupSize -= 1;
140 | }
141 |
142 | //accounts for if the minLatencyNodeInGroup is the head of the list
143 | if (this.head === minLatencyNodeInGroup) {
144 | this.head = this.head.next;
145 | this.head.prev = null;
146 | this.length--;
147 | delete this.cache[minLatencyNodeInGroup.key];
148 | return minLatencyNodeInGroup;
149 | }
150 |
151 | //accounts for if the minLatencyNodeInGroup is the tail of the list
152 | if (this.tail === minLatencyNodeInGroup) {
153 | this.tail = this.tail.prev;
154 | this.tail.next = null;
155 | this.length--;
156 | delete this.cache[minLatencyNodeInGroup.key];
157 | return minLatencyNodeInGroup;
158 | }
159 |
160 | //Removes minLatencyNodeInGroup from the Eviction Queue if node is not the tail or head.
161 | const removedNode = minLatencyNodeInGroup;
162 | minLatencyNodeInGroup.prev.next = minLatencyNodeInGroup.next;
163 | minLatencyNodeInGroup.next.prev = minLatencyNodeInGroup.prev;
164 | this.length--;
165 | delete this.cache[removedNode.key];
166 | return removedNode;
167 | }
168 |
169 | //Moves node to front of the linkedList if its cache is accessed again inorder to update recency.
170 | updateRecencyOfExistingCache(cacheKey) {
171 | const node = this.cache[cacheKey];
172 | //if node being updated is already at the head of list, we can just return since it is already in the most recent position.
173 | if (this.head === node) {
174 | return;
175 | }
176 | //remove linkage of the node at the current position.
177 | //accounts for if the tail node is the one getting updated since it wouldnt have a next node in the list.
178 | if (this.tail === node) {
179 | node.prev.next = node.next;
180 | this.tail = node.prev;
181 | } else {
182 | node.next.prev = node.prev;
183 | node.prev.next = node.next;
184 | }
185 |
186 | //move node to the head of the queue inorder to update the recency.
187 | this.head.prev = node;
188 | node.next = this.head;
189 | this.head = node;
190 | this.head.prev = null;
191 | }
192 | }
193 |
--------------------------------------------------------------------------------
/demo/client/components/BarChart.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { Bar } from 'react-chartjs-2';
3 | import { Chart as ChartJS } from "chart.js/auto";
4 |
5 | const BarChart = ({chartData}) => {
6 | const options = {indexAxis: 'y'}
7 |
8 | return (
9 |
10 | )
11 | }
12 |
13 | export default BarChart;
--------------------------------------------------------------------------------
/demo/client/components/Footer.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import logo from '../styles/logo.png';
3 | import { useNavigate } from 'react-router-dom';
4 |
5 | const Footer = () => {
6 | const navigate = useNavigate();
7 | return (
8 |
76 | );
77 | };
78 |
79 | export default Footer;
80 |
--------------------------------------------------------------------------------
/demo/client/components/LLNode.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import '../styles/LLNode.css';
3 |
4 | const LLNode = (props) => {
5 | return (
6 |
7 |
8 |
{`Query ${props.num}`}
9 |
{`${Math.floor(props.latency)}ms`}
10 |
11 |
12 |
17 |
18 |
19 | );
20 | };
21 |
22 | export default LLNode;
23 |
--------------------------------------------------------------------------------
/demo/client/components/LandingContent.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 |
3 | const LandingContent = () => {
4 | return (
5 |
6 |
7 |
11 |
12 |
13 |
17 | in few easy steps
18 |
19 |
23 | Lightweight GraphQL Caching Tool
24 |
25 |
26 |
31 |
36 |
37 |
38 |
39 |
43 |
44 |
45 |
46 |
50 | Ready to use NPM package
51 |
52 |
56 | Cachier is very easy to install and use. Follow our simple
57 | directions to save yourself tons of lines from coding and
58 | start caching your queries!
59 |
60 |
61 |
62 |
67 |
68 |
69 |
70 |
74 |
75 |
76 |
77 |
81 | Top-grade Efficiency
82 |
83 |
87 | {' '}
88 | Our caching methods delivers your queries in constant time -
89 | O(1). Our latency-based customized eviction policy prevents
90 | expensive query calls.
91 |
92 |
93 |
94 |
99 |
100 |
101 |
102 |
106 |
107 |
108 |
109 |
113 | Designed by Developers for Developers
114 |
115 |
119 | Built by a team of engineers that understand the drawbacks
120 | of the GraphQL technical environment.
121 |
122 |
123 |
124 |
129 |
130 |
131 |
132 |
136 |
137 |
138 |
139 |
143 | Built with Modern Technology Stacks
144 |
145 |
149 | Cache your queries with Redis, an in-memory data structure
150 | store, to maximize your application performance!
151 |
152 |
153 |
154 |
155 |
156 |
157 |
158 |
159 | );
160 | };
161 |
162 | export default LandingContent;
163 |
--------------------------------------------------------------------------------
/demo/client/components/LandingContent2.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import graphqlGIF from '../styles/graphqlgif.gif';
3 | import graphqlLogo from '../styles/graphqlLogo.png';
4 | import { useNavigate } from 'react-router-dom';
5 |
6 | const LandingContent2 = () => {
7 | const navigate = useNavigate();
8 |
9 | return (
10 |
11 |
12 |
13 |
14 |
15 |
19 |
20 |
GraphQL
21 |
22 |
23 |
24 | GraphQL is an open-source data query and manipulation language
25 | for APIs, and a runtime for fulfilling queries with existing
26 | data. With GraphQL, you can specify what data you want to
27 | request from your API. You will get back exactly what you
28 | need, nothing more and nothing less. GraphQL queries always
29 | return predictable results.
30 |
31 |
41 |
42 |
Caching
43 |
44 |
45 | The RESTful APIs use the URL as the global unique identifier
46 | which can be leveraged to build a cache. Unlike GraphQL, the
47 | POST HTTP method is used to perform queries and there are no
48 | URL-like endpoints making caching GraphQL queries difficult.
49 |
50 |
51 | Cachier stores queries and the response in in-memory cache,
52 | with this future queries will cause Cachier to check for the
53 | request in the in-memory cache and return the result without
54 | performing the network request.
55 |
56 |
57 |
58 |
59 |
64 |
68 |
69 | Blazing Fast Performance
70 |
71 |
72 |
73 |
78 |
82 |
83 | Server & Client Side Options
84 |
85 |
86 |
87 |
92 |
96 |
97 | Customized Latency-Based Eviction Policy
98 |
99 |
100 |
101 | navigate('/demo')}
103 | type='button'
104 | className='inline-block px-6 py-2.5 bg-blue-600 text-white font-medium text-xs leading-tight uppercase rounded-full shadow-md hover:bg-blue-700 hover:shadow-lg focus:bg-blue-700 focus:shadow-lg focus:outline-none focus:ring-0 active:bg-blue-800 active:shadow-lg transition duration-150 ease-in-out'
105 | >
106 | Run Demo
107 |
108 |
109 |
110 |
111 |
112 |
113 |
114 |
115 |
116 |
117 |
118 |
119 |
120 | );
121 | };
122 |
123 | export default LandingContent2;
124 |
--------------------------------------------------------------------------------
/demo/client/components/LineChart.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { Line } from 'react-chartjs-2';
3 |
4 | const LineChart = ({chartData}) => {
5 |
6 | return (
7 |
8 | )
9 | }
10 |
11 | export default LineChart;
--------------------------------------------------------------------------------
/demo/client/components/NavBar.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import cachierlogo from '../styles/CachierNavBar(1).png';
3 | import { NavLink } from 'react-router-dom';
4 |
5 | const NavBar = () => {
6 | return (
7 |
8 |
9 |
10 | {' '}
11 |
12 |
13 |
14 |
46 |
47 | );
48 | };
49 |
50 | export default NavBar;
51 |
--------------------------------------------------------------------------------
/demo/client/components/QueueVisualizer.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import LLNode from './LLNode';
3 | import '../styles/QueueVisualizer.css';
4 | import RemovedLLNode from './RemovedLLNode';
5 |
6 | const QueueVisualizer = (props) => {
7 | const { queue } = props;
8 |
9 | const traverse = (list) => {
10 | let count = 4 - props.currGroupSize;
11 | const output = [];
12 | let index = 0;
13 | while (index < list.length) {
14 | if (index < count) {
15 | output.push(
16 |
22 | );
23 | } else {
24 | output.push(
25 |
31 | );
32 | }
33 | index++;
34 | }
35 | return output;
36 | };
37 |
38 | return (
39 |
42 |
44 | LRU-SLFR Eviction Policy Visualizer
45 |
46 |
48 |
{traverse(queue)}
49 |
50 |
Last Evicted
51 | {props.removedNode && (
52 |
56 | )}
57 |
58 |
59 |
60 | );
61 | };
62 |
63 | export default QueueVisualizer;
64 |
--------------------------------------------------------------------------------
/demo/client/components/RemovedLLNode.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 |
3 | const RemovedLLNode = (props) => {
4 | return (
5 |
6 |
7 | {props.num !== 0 ?
{`Query ${props.num}`} :
Query }
8 | {props.num !== 0 ? (
9 |
{`${Math.floor(props.latency)}ms`}
10 | ) : (
11 |
null
12 | )}
13 |
14 |
15 | );
16 | };
17 |
18 | export default RemovedLLNode;
19 |
--------------------------------------------------------------------------------
/demo/client/components/TeamMemberCard.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import github from '../styles/github.png';
3 | import linkedin from '../styles/linkedin.png';
4 |
5 | const TeamMemberCard = (props) => {
6 | return (
7 |
8 |
9 |
10 |
11 |
12 |
13 |
{props.info.name}
14 |
15 |
16 |
{
18 | e.preventDefault();
19 | window.location.href = props.info.github;
20 | }}
21 | className='btn btn-active'
22 | >
23 | Github
24 |
25 |
26 |
{
28 | e.preventDefault();
29 | window.location.href = props.info.linkedin;
30 | }}
31 | className='btn'
32 | >
33 | Linkedin
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 | );
42 | };
43 |
44 | export default TeamMemberCard;
45 |
--------------------------------------------------------------------------------
/demo/client/components/Testimonials.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import stephenPic from '../styles/stephen.png'
3 | import huaPic from '../styles/hua.png'
4 | import andrewPic from '../styles/andrew.png'
5 |
6 | const Testimonials = () => {
7 |
8 | return (
9 |
10 |
11 |
12 |
13 | Testimonials
14 |
15 |
16 |
17 |
18 |
30 |
31 |
Hua Liu
32 |
Software Engineer at okNEXT
33 |
"Extremely intuitive and convenient developer caching tool designed by a great team of engineers. Well tested and works great!"
34 |
35 |
36 |
37 |
38 |
39 |
40 |
52 |
53 |
Stephen Fitzsimmons
54 |
Software Engineer at QuiL
55 |
"I'm impressed by the speed and the conciseness of the backend code. Great use of resources to accomplish a vital solution in the GraphQL technical environment."
56 |
57 |
58 |
59 |
60 |
61 |
62 |
74 |
75 |
Andrew Rama
76 |
Software Engineer at Docklight
77 |
"Cachier really is lightweight! Excellent alternative for those looking to cache GraphQL endpoints without having to use Apollo."
78 |
79 |
80 |
81 |
82 |
83 |
84 |
85 |
86 | )
87 | }
88 |
89 | export default Testimonials;
--------------------------------------------------------------------------------
/demo/client/components/TextQuery.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import '../styles/Demo.css';
3 |
4 | const TextQuery = (props) => {
5 |
6 | const { handleUploadAndQuery, loading } = props;
7 | const string = `{ clients { id name email phone } }`;
8 | function handleClick(){
9 | const str = document.getElementsByClassName('textarea textarea-bordered h-24')[0].value;
10 | console.log('text field: ', str);
11 | handleUploadAndQuery();
12 | }
13 |
14 |
15 | return (
16 |
17 |
18 |
19 | Type Query here
20 |
21 |
30 |
31 | Type a GraphQL query as you would in your code. Can handle only queries for now, no mutations.
32 |
33 |
34 |
35 | Run Query
36 |
37 |
38 | );
39 | }
40 |
41 | export default TextQuery;
--------------------------------------------------------------------------------
/demo/client/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | CacheQL
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
--------------------------------------------------------------------------------
/demo/client/index.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import ReactDOM from 'react-dom/client';
3 | import App from './App.js';
4 | import './style.css';
5 |
6 |
7 | const root = ReactDOM.createRoot(document.querySelector('#root'));
8 | root.render(
9 |
12 | );
13 |
--------------------------------------------------------------------------------
/demo/client/pages/About.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import TeamMemberCard from '../components/TeamMemberCard';
3 | import andy from '../styles/andy.png';
4 | import dhruv from '../styles/dhruv.png';
5 | import kaju from '../styles/kaju.png';
6 | import jonathan from '../styles/jonathan.png';
7 | import roman from '../styles/roman.png';
8 |
9 | const About = () => {
10 | const team = [
11 | {
12 | name: 'Dhruv Thota',
13 | img: 'https://www.meme-arsenal.com/memes/18a3373dd7ab767f3cf1ad96b5cbc204.jpg',
14 | description: ` Lorem Aliquam sit amet porta justo. Duis nec lorem vel risus
15 | molestie mattis. Ut id velit et felis imperdiet euismod. Sed a leo
16 | sed urna egestas viverra. Donec dignissim sem eu sapien fringilla
17 | vestibulum.`,
18 | github: 'https://github.com/L05Dhruv',
19 | linkedin: 'https://www.linkedin.com/in/dhruv-thota/',
20 | },
21 | {
22 | name: 'Andy Zheng',
23 | img: { andy },
24 | description: ` Lorem Aliquam sit amet porta justo. Duis nec lorem vel risus
25 | molestie mattis. Ut id velit et felis imperdiet euismod. Sed a leo
26 | sed urna egestas viverra. Donec dignissim sem eu sapien fringilla
27 | vestibulum.`,
28 | github: 'https://github.com/andy5313',
29 | linkedin: 'https://www.linkedin.com/in/andyzheng5313/',
30 | },
31 | {
32 | name: 'Roman Darker',
33 | img: { roman },
34 | description: ` Lorem Aliquam sit amet porta justo. Duis nec lorem vel risus
35 | molestie mattis. Ut id velit et felis imperdiet euismod. Sed a leo
36 | sed urna egestas viverra. Donec dignissim sem eu sapien fringilla
37 | vestibulum.`,
38 | github: 'https://github.com/romanjamesd',
39 | linkedin: 'https://www.linkedin.com/in/roman-darker-707147175/',
40 | },
41 | {
42 | name: 'Jonathan Chen',
43 | img: { jonathan },
44 | description: ` Lorem Aliquam sit amet porta justo. Duis nec lorem vel risus
45 | molestie mattis. Ut id velit et felis imperdiet euismod. Sed a leo
46 | sed urna egestas viverra. Donec dignissim sem eu sapien fringilla
47 | vestibulum.`,
48 | github: 'https://github.com/jchen0903i',
49 | linkedin: 'https://www.linkedin.com/in/jonathan-chen3/',
50 | },
51 | {
52 | name: 'Kaju Sarkar',
53 | img: 'https://www.meme-arsenal.com/memes/18a3373dd7ab767f3cf1ad96b5cbc204.jpg',
54 | description: ` Lorem Aliquam sit amet porta justo. Duis nec lorem vel risus
55 | molestie mattis. Ut id velit et felis imperdiet euismod. Sed a leo
56 | sed urna egestas viverra. Donec dignissim sem eu sapien fringilla
57 | vestibulum.`,
58 | github: 'https://github.com/kajusarkar',
59 | linkedin: 'https://www.linkedin.com/in/kaju-sarkar-a6329862/',
60 | },
61 | ];
62 |
63 | return (
64 |
65 |
69 | Our Team
70 |
71 |
72 |
73 |
74 |
75 |
76 |
77 |
78 |
79 | );
80 | };
81 |
82 | export default About;
83 |
--------------------------------------------------------------------------------
/demo/client/pages/Demo.js:
--------------------------------------------------------------------------------
1 | import React, { useState, useEffect } from 'react';
2 | import BarChart from '../components/BarChart';
3 | import QueueVisualizer from '../components/QueueVisualizer';
4 | import '../styles/Demo.css';
5 | import { clientSideCache } from '../../../clientSideCache';
6 |
7 | const cachierFetch = clientSideCache(4, 2);
8 |
9 | const Demo = () => {
10 | // const [queryData, setQueryData] = useState([]);
11 | const [clientSideTime, setClientSideTime] = useState(0);
12 |
13 | //state for the GraphQL query result once the fetch is down
14 | const [queryResult, setQueryResult] = useState('');
15 |
16 | //query string that is displayed in GraphQL format
17 | const [queryString, setQueryString] = useState('');
18 |
19 | //Array storing the linkedlist data use to display our linked list
20 | const [llData, setLLData] = useState([]);
21 |
22 | const [loading, setLoading] = useState(false);
23 | const [clientSideLoading, setClientSideLoading] = useState(false);
24 |
25 | const [removedNode, setRemovedNode] = useState({ num: 0, latency: 0 });
26 | const [currGroupSize, setCurrGroupSize] = useState(0);
27 |
28 | const [queryGraphQLString, setQueryGraphQLString] = useState(
29 | '{ clients { id name email phone } }'
30 | );
31 | const [queryTime, setQueryTime] = useState(0);
32 | const [queryTimeArray, setQueryTimeArray] = useState([
33 | { latency: 0, cached: false },
34 | ]);
35 |
36 | const [clientChecked, setClientChecked] = useState(false);
37 | const [clientIdChecked, setClientIdChecked] = useState(true);
38 | const [clientNameChecked, setClientNameChecked] = useState(true);
39 | const [clientEmailChecked, setClientEmailChecked] = useState(true);
40 | const [clientPhoneChecked, setClientPhoneChecked] = useState(true);
41 |
42 | const [demoRegularCacheChecked, setDemoRegularCacheChecked] = useState(true);
43 | const [demoPartialCacheChecked, setDemoPartialCacheChecked] = useState(false);
44 |
45 | const [chartData, setChartData] = useState({
46 | labels: [],
47 | datasets: [
48 | {
49 | label: 'Query Time in Milliseconds',
50 | data: queryTimeArray.latency,
51 | backgroundColor: ['blue'],
52 | borderColor: 'black',
53 | borderWidth: 2,
54 | },
55 | ],
56 | options: {
57 | indexAxis: 'y',
58 | },
59 | });
60 |
61 | useEffect(() => {
62 | const string = `{ clients { ${clientIdChecked ? 'id' : ''}${
63 | clientNameChecked ? ' name' : ''
64 | }${clientEmailChecked ? ' email' : ''}${
65 | clientPhoneChecked ? ' phone' : ''
66 | } } }`;
67 | setQueryGraphQLString(string);
68 | }, [
69 | clientChecked,
70 | clientIdChecked,
71 | clientNameChecked,
72 | clientEmailChecked,
73 | clientPhoneChecked,
74 | ]);
75 |
76 | const chartLatency = () => {
77 | let latencyArray = queryTimeArray.map((el) => el.latency);
78 | let result = latencyArray.slice(1);
79 | return result;
80 | };
81 |
82 | useEffect(() => {
83 | setChartData({
84 | labels: queryTimeArray
85 | .map((data, i) => {
86 | return !data.cached ? 'Uncached Query' : `Cached Query`;
87 | })
88 | .slice(1),
89 | datasets: [
90 | {
91 | axis: 'y',
92 | label: 'Query Time in Milliseconds',
93 | data: chartLatency(),
94 | fill: true,
95 | backgroundColor: [
96 | 'rgba(255, 99, 132, 0.2)',
97 | 'rgba(255, 159, 64, 0.2)',
98 | 'rgba(255, 205, 86, 0.2)',
99 | 'rgba(75, 192, 192, 0.2)',
100 | 'rgba(54, 162, 235, 0.2)',
101 | 'rgba(153, 102, 255, 0.2)',
102 | 'rgba(201, 203, 207, 0.2)',
103 | ],
104 | borderColor: 'black',
105 | borderWidth: 2,
106 | },
107 | ],
108 | });
109 | }, [queryTimeArray]);
110 |
111 | const fetchData = async () => {
112 | if (demoRegularCacheChecked) {
113 | const startTime = performance.now();
114 | cachierFetch('http://localhost:3000/graphql', {
115 | method: 'POST',
116 | headers: {
117 | 'Content-Type': 'application/json',
118 | Accept: 'application/json',
119 | },
120 | body: JSON.stringify({
121 | query: queryGraphQLString,
122 | }),
123 | }).then((data) => {
124 | setClientSideTime((performance.now() - startTime).toFixed(2));
125 | console.log('DATA', data);
126 | });
127 |
128 | fetch('http://localhost:3000/cacheMoney', {
129 | method: 'POST',
130 | headers: {
131 | 'Content-Type': 'application/json',
132 | Accept: 'application/json',
133 | },
134 | body: JSON.stringify({
135 | query: queryGraphQLString,
136 | }),
137 | })
138 | .then((res) => {
139 | return res.json();
140 | })
141 | .then((data) => {
142 | const endTime = (performance.now() - startTime).toFixed(2); // records end time for front-end latency measure
143 | setLLData(data.queue); // updates state linked list object
144 | if (data.removedNode) {
145 | setRemovedNode(data.removedNode);
146 | }
147 | setCurrGroupSize(data.currGroupSize);
148 | setQueryTime(endTime);
149 |
150 | setQueryTimeArray([
151 | ...queryTimeArray,
152 | { latency: endTime, cached: data.cached },
153 | ]); // updates data points for charts
154 | setQueryResult(JSON.stringify(data.data, null, 2));
155 | setLoading(false);
156 | });
157 | } else {
158 | const startTime = performance.now();
159 | fetch('http://localhost:3000/partialCache/', {
160 | method: 'POST',
161 | headers: {
162 | 'Content-Type': 'application/json',
163 | Accept: 'application/json',
164 | },
165 | body: JSON.stringify({
166 | query: queryGraphQLString,
167 | uniques: { clients: 'id' },
168 | }),
169 | })
170 | .then((res) => {
171 | return res.json();
172 | })
173 | .then((data) => {
174 | const endTime = (performance.now() - startTime).toFixed(2); // records end time for front-end latency measure
175 | setQueryTime(endTime);
176 | setQueryTimeArray([...queryTimeArray]); // updates data points for charts
177 | setQueryResult(JSON.stringify(data.data, null, 2));
178 | setLoading(false);
179 | });
180 | }
181 | };
182 |
183 | const handleQuery = () => {
184 | setLoading(true);
185 | setClientSideLoading(true);
186 | fetchData();
187 | };
188 |
189 | const handleUploadAndQuery = () => {
190 | setQueryString(`{ clients { id name email phone } }`);
191 | setLoading(true);
192 | fetchData();
193 | };
194 |
195 | const testQuery = () => {
196 | if (clientChecked) {
197 | return (
198 |
199 |
{
200 |
clients {
201 | {clientIdChecked && (
202 |
id
203 | )}
204 | {clientNameChecked && (
205 |
name
206 | )}
207 | {clientEmailChecked && (
208 |
email
209 | )}
210 | {clientPhoneChecked && (
211 |
phone
212 | )}
213 |
}
214 |
}
215 |
216 | );
217 | }
218 | };
219 |
220 | return (
221 |
222 |
223 | Cachier Demo
224 |
225 |
226 |
227 |
228 |
229 |
230 |
Query Result
231 |
232 |
{queryResult}
233 |
234 |
235 |
Server Side:
236 |
237 | {loading ? (
238 | Loading...
239 | ) : (
240 |
241 | {queryTime}
242 | ms
243 |
244 | )}
245 |
246 |
247 | {demoRegularCacheChecked && (
248 |
249 | Client Side:
250 |
251 | {clientSideTime}
252 | ms
253 |
254 |
255 | )}
256 |
257 |
258 | Cache Hit:
259 |
268 | {queryTimeArray[queryTimeArray.length - 1].cached
269 | ? 'Hit'
270 | : 'Miss'}
271 |
272 |
273 |
274 |
275 |
276 |
277 |
278 |
281 | clientChecked
282 | ? setClientChecked(false)
283 | : setClientChecked(true)
284 | }
285 | id='clients'
286 | name='clients'
287 | value='clients'
288 | />
289 |
Clients
290 |
291 | {clientChecked === true && (
292 |
354 | )}
355 |
356 |
357 |
358 |
{testQuery()}
359 |
360 |
361 |
366 | Run Query
367 |
368 |
369 |
370 |
371 |
372 |
373 |
374 |
375 |
376 | {loading && (
377 |
378 |
383 |
390 |
394 |
398 |
399 | Loading...
400 |
401 |
402 | )}
403 |
404 |
405 |
406 |
407 |
Query Cache Performance Chart
408 |
409 |
410 |
411 |
412 |
{
416 | setDemoRegularCacheChecked(true);
417 | setDemoPartialCacheChecked(false);
418 | }}
419 | >
420 |
421 | Cachier Direct Server & Client-side Cache
422 |
423 |
424 |
425 | {
429 | setDemoRegularCacheChecked(false);
430 | setDemoPartialCacheChecked(true);
431 | }}
432 | >
433 |
434 | Cachier Normalized Server-side Cache
435 |
436 |
437 |
438 |
439 |
440 |
441 |
442 |
443 |
444 | {demoRegularCacheChecked && (
445 |
450 | )}
451 |
452 |
453 |
454 | );
455 | };
456 |
457 | export default Demo;
458 |
--------------------------------------------------------------------------------
/demo/client/pages/Landing.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import LandingContent from '../components/LandingContent';
3 | import Testimonials from '../components/Testimonials';
4 | import { useNavigate } from 'react-router-dom';
5 | import LandingContent2 from '../components/LandingContent2';
6 | import logo from '../styles/logo.png';
7 | import installIcon from '../styles/installIcon.png';
8 | import FastIcon from '../styles/FastIcon.png';
9 | import customizableIcon from '../styles/customizableIcon.png';
10 | import memoryIcon from '../styles/memoryIcon.png';
11 | import demoIcon from '../styles/Demo.png';
12 |
13 | const Landing = () => {
14 | const navigate = useNavigate();
15 |
16 | return (
17 |
18 |
19 |
20 |
Welcome to Cachier
21 |
22 |
23 | A highly customizable, lightweight, open-source GraphQL caching tool
24 |
25 |
26 | that intelligently manages and delivers data at lightning speeds.
27 |
28 |
29 |
30 | navigate('/demo')}
32 | type='button'
33 | className='inline-block px-6 py-2.5 bg-blue-600 text-white font-medium text-xs leading-tight uppercase rounded-full shadow-md hover:bg-blue-700 hover:shadow-lg focus:bg-blue-700 focus:shadow-lg focus:outline-none focus:ring-0 active:bg-blue-800 active:shadow-lg transition duration-150 ease-in-out'
34 | >
35 | Run Demo
36 |
37 | navigate('/documentation')}
39 | type='button'
40 | className='inline-block px-6 py-2.5 bg-gray-200 text-gray-700 font-medium text-xs leading-tight uppercase rounded-full shadow-md hover:bg-gray-300 hover:shadow-lg focus:bg-gray-300 focus:shadow-lg focus:outline-none focus:ring-0 active:bg-gray-400 active:shadow-lg transition duration-150 ease-in-out'
41 | >
42 | Documents
43 |
44 |
45 |
46 |
47 |
48 |
49 |
50 |
51 | );
52 | };
53 |
54 | export default Landing;
55 |
--------------------------------------------------------------------------------
/demo/client/style.css:
--------------------------------------------------------------------------------
1 | @tailwind base;
2 | @tailwind components;
3 | @tailwind utilities;
4 |
5 | .welcomeBox {
6 | display: flex;
7 | flex-direction: column;
8 | justify-content: center;
9 | align-items: center;
10 | background-color: rgb(210, 240, 251);
11 | padding: 8vw;
12 | color: #0a405f;
13 | }
14 |
15 | .logoGIF {
16 | width: 70%;
17 | }
18 |
19 | .welcome {
20 | font-size: 4vw;
21 | font-weight: bold;
22 | display: flex;
23 | justify-content: center;
24 | }
25 |
26 | .productDescription {
27 | text-align: center;
28 | font-size: 1.1vw;
29 | }
30 |
31 | /* .navbar.bg-neutral.text-neutral-content.sticky.top-0.NavBarContainer {
32 | background: linear-gradient(10deg, rgb(3, 129, 187), rgb(192, 20, 129));
33 | box-shadow: rgba(0, 0, 0, 0.45) 0px 25px 20px -20px;
34 | padding: none;
35 | } */
36 |
37 | .carousel.w-full {
38 | width: 100vw;
39 | height: 300px;
40 | }
41 |
42 | .imageContainer {
43 | display: flex;
44 | flex-direction: column;
45 | justify-content: center;
46 | align-items: center;
47 | background: linear-gradient(10deg, rgb(3, 129, 187), rgb(192, 20, 129));
48 | width: 100vw;
49 | height: 300px;
50 | }
51 |
52 | .carousel-item.relative.w-full {
53 | display: flex;
54 | flex-direction: column;
55 | justify-content: center;
56 | align-items: center;
57 | }
58 |
59 | .teamCard {
60 | box-shadow: 0 0 30px 2px #48abe0;
61 | border-radius: 15px;
62 | }
63 |
--------------------------------------------------------------------------------
/demo/client/styles/CachierNavBar(1).png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/oslabs-beta/Cachier/2e267d1765a9bf5f3d62e7a3c258ea0d3159ff9a/demo/client/styles/CachierNavBar(1).png
--------------------------------------------------------------------------------
/demo/client/styles/Demo.css:
--------------------------------------------------------------------------------
1 | .demoContainer {
2 | background-color: #e2f7ff;
3 | }
4 |
5 | .heading {
6 | color: #121f4e;
7 | font-family: Georgia, serif;
8 | }
9 |
10 | .demoDiv {
11 | display: flex;
12 | align-items: center;
13 | justify-content: center;
14 | gap: 5vw;
15 | padding-top: 2vw;
16 | padding-bottom: 2vw;
17 | }
18 |
19 | .clientFields {
20 | padding-left: 1vw;
21 | display: flex;
22 | gap: 1vw;
23 | }
24 |
25 | body {
26 | margin: 0;
27 | padding: 0;
28 | }
29 |
30 | .Visualizers {
31 | margin-bottom: 1vw;
32 | }
33 |
34 | .query {
35 | display: flex;
36 | flex-direction: column;
37 | gap: 1vw;
38 | padding-top: 1vw;
39 | }
40 |
41 | .queryResult {
42 | overflow-y: auto;
43 | overflow-x: hidden;
44 | text-align: start;
45 | height: 15vw;
46 | width: 100%;
47 | background-color: black;
48 | display: flex;
49 | justify-content: flex-start;
50 | font-family: 'Roboto', 'Helvetica', 'Arial', sans-serif;
51 | }
52 |
53 | ::-webkit-scrollbar {
54 | background-color: black;
55 | }
56 |
57 | .padding0 {
58 | padding: 0;
59 | }
60 |
61 | .queryResultContainer {
62 | margin: 0;
63 | padding: 0px !important;
64 | border: black 2px;
65 | border-radius: 10px;
66 | background-color: #121f4e;
67 | color: 'white';
68 | box-shadow: rgba(0, 0, 0, 0.25) 0px 54px 55px,
69 | rgba(0, 0, 0, 0.12) 0px -12px 30px, rgba(0, 0, 0, 0.12) 0px 4px 6px,
70 | rgba(0, 0, 0, 0.17) 0px 12px 13px, rgba(0, 0, 0, 0.09) 0px -3px 5px;
71 | }
72 |
73 | .queryResultHeading {
74 | font-family: Georgia, serif;
75 | justify-content: center;
76 | padding: 1vw 0;
77 | display: flex;
78 | color: #d2f0fb;
79 | font-size: 2vw;
80 | margin: 0;
81 | }
82 |
83 | .code {
84 | color: white !important;
85 | font-size: 1vw;
86 | font-family: Georgia, serif;
87 | padding-left: 1vw;
88 | }
89 |
90 | .queryResultMetrics {
91 | padding-top: 0.4vw;
92 | display: flex;
93 | justify-content: space-between;
94 | align-items: center;
95 | }
96 |
97 | .serverSide {
98 | font-family: Georgia, serif;
99 | font-size: 1.1vw;
100 | padding-left: 1.1vw;
101 | color: #d2f0fb;
102 | }
103 |
104 | .metrics {
105 | font-family: Georgia, serif;
106 | font-size: 1.4vw;
107 | padding-right: 1.5vw;
108 | color: #ff4c4c;
109 | }
110 |
111 | .ms {
112 | font-size: 0.7vw;
113 | }
114 |
115 | .queryResultMetricsDiv {
116 | display: flex;
117 | justify-content: space-between;
118 | align-items: center;
119 | }
120 |
121 | .cacheHitDiv {
122 | padding-bottom: '.4vw';
123 | display: flex;
124 | justify-content: space-between;
125 | }
126 |
127 | .cacheHitMetric {
128 | font-family: Georgia, serif;
129 | font-size: 25;
130 | color: #ff4c4c;
131 | padding-right: 1.5vw;
132 | }
133 |
134 | .queryContainer {
135 | display: flex;
136 | flex-direction: column;
137 | box-shadow: rgba(0, 0, 0, 0.25) 0px 54px 55px,
138 | rgba(0, 0, 0, 0.12) 0px -12px 30px, rgba(0, 0, 0, 0.12) 0px 4px 6px,
139 | rgba(0, 0, 0, 0.17) 0px 12px 13px, rgba(0, 0, 0, 0.09) 0px -3px 5px;
140 | }
141 |
142 | .queryString {
143 | border: black 2px;
144 | border-radius: 10px;
145 | background-color: #121f4e;
146 | padding: 1vw;
147 | font-family: Georgia, serif;
148 | color: #d2f0fb;
149 | }
150 |
151 | .queryDisplayStringContainer {
152 | overflow-x: hidden;
153 | overflow-y: hidden;
154 | height: 15vw;
155 | width: 25vw;
156 | background-color: white;
157 | display: flex;
158 | justify-content: flex-start;
159 | color: #9c528b;
160 | padding-left: 1vw;
161 | }
162 |
163 | .testQuery {
164 | font-size: 1.2vw;
165 | }
166 |
167 | .queryButton {
168 | background-color: #121f4e;
169 | color: #d2f0fb;
170 | padding: 0.6vw;
171 | font-size: 1vw;
172 | }
173 |
174 | .visualizersDiv {
175 | padding-top: 1vw;
176 | align-items: center;
177 | justify-content: center;
178 | flex-direction: column;
179 | display: flex;
180 | }
181 |
182 | .loadingDiv {
183 | padding-bottom: 1vw;
184 | height: 5vw;
185 | }
186 |
187 | .barChart {
188 | width: 45vw;
189 | border: black 1px solid;
190 | border-radius: 10px;
191 | background-color: white;
192 | padding: 1vw;
193 | color: #121f4e;
194 | box-shadow: rgba(0, 0, 0, 0.25) 0px 54px 55px,
195 | rgba(0, 0, 0, 0.12) 0px -12px 30px, rgba(0, 0, 0, 0.12) 0px 4px 6px,
196 | rgba(0, 0, 0, 0.17) 0px 12px 13px, rgba(0, 0, 0, 0.09) 0px -3px 5px;
197 | }
198 |
199 | .evictedText {
200 | color: #121f4e;
201 | }
202 |
203 | .demoLeftContainer {
204 | width: 25vw;
205 | }
206 |
207 | .barChartHeading {
208 | font-family: 'Georgia, serif';
209 | display: flex;
210 | align-items: center;
211 | justify-content: center;
212 | gap: 1vw;
213 | text-align: center;
214 | font-size: 2vw;
215 | margin: 0.7vw;
216 | }
217 |
218 | .queryTextInput {
219 | margin-top: 30px;
220 | }
221 |
222 | span.label-text {
223 | color: grey;
224 | }
225 |
226 | span.label-text-alt {
227 | color: grey;
228 | }
229 |
230 | .quertyTextInput {
231 | display: flex;
232 | flex-direction: column;
233 | justify-content: center;
234 | align-content: center;
235 | }
236 |
237 | textarea.textarea.textarea-bordered.h-24 {
238 | background-color: midnightblue;
239 | width: auto;
240 | height: auto;
241 | min-width: 175px;
242 | min-height: 225px;
243 | }
244 |
245 | .chartCheckBoxDiv {
246 | display: flex;
247 | flex-direction: column;
248 | align-items: flex-start;
249 | padding: 0;
250 | }
251 |
252 | .cacheLabel {
253 | font-size: 16px;
254 | text-align: center;
255 | }
256 |
257 | .checkFieldDiv {
258 | display: flex;
259 | justify-content: center;
260 | align-items: center;
261 | gap: 5px;
262 | }
263 |
--------------------------------------------------------------------------------
/demo/client/styles/Demo.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/oslabs-beta/Cachier/2e267d1765a9bf5f3d62e7a3c258ea0d3159ff9a/demo/client/styles/Demo.png
--------------------------------------------------------------------------------
/demo/client/styles/FastIcon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/oslabs-beta/Cachier/2e267d1765a9bf5f3d62e7a3c258ea0d3159ff9a/demo/client/styles/FastIcon.png
--------------------------------------------------------------------------------
/demo/client/styles/LLNode.css:
--------------------------------------------------------------------------------
1 | .link {
2 | display: flex;
3 | }
4 |
5 | .node {
6 | font-size: 0.5vw;
7 | display: flex;
8 | flex-direction: column;
9 | justify-content: center;
10 | align-items: center;
11 | border-radius: 10px;
12 | width: 5vw;
13 | height: 5vw;
14 |
15 | box-shadow: rgba(0, 0, 0, 0.16) 0px 1px 4px, rgb(51, 51, 51) 0px 0px 0px 3px;
16 | }
17 |
18 | .latency {
19 | font-size: 0.8vw;
20 | margin: 0;
21 | }
22 |
23 | .arrowDiv {
24 | display: flex;
25 | align-items: center;
26 | padding: 1vw;
27 | }
28 |
--------------------------------------------------------------------------------
/demo/client/styles/QueueVisualizer.css:
--------------------------------------------------------------------------------
1 | .Eviction {
2 | text-align: center;
3 | background-color: transparent;
4 | margin-top: 2vw;
5 | border-top-left-radius: 10px;
6 | border-top-right-radius: 10;
7 | padding-bottom: 1vw;
8 | color: black;
9 | }
10 |
11 | .queueContainer {
12 | display: flex;
13 | justify-content: flex-start;
14 | align-items: center;
15 | }
16 |
17 | .Evicted {
18 | display: flex;
19 | flex-direction: column;
20 | justify-content: flex-start;
21 | align-items: center;
22 | margin-bottom: 1.5vw;
23 | margin-left: 3vw;
24 | }
25 |
26 | .EvictionHeading {
27 | font-family: Georgia, serif;
28 | padding: 2;
29 | font-size: 2vw;
30 | color: #121f4e;
31 | }
32 |
33 | .queueContainerDiv {
34 | display: flex;
35 | justify-content: space-around;
36 | align-items: center;
37 | margin-top: 1.5vw;
38 | }
39 |
40 | .NavBarContainer {
41 | z-index: 1;
42 | }
43 |
--------------------------------------------------------------------------------
/demo/client/styles/andrew.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/oslabs-beta/Cachier/2e267d1765a9bf5f3d62e7a3c258ea0d3159ff9a/demo/client/styles/andrew.png
--------------------------------------------------------------------------------
/demo/client/styles/andy.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/oslabs-beta/Cachier/2e267d1765a9bf5f3d62e7a3c258ea0d3159ff9a/demo/client/styles/andy.png
--------------------------------------------------------------------------------
/demo/client/styles/arrow.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/oslabs-beta/Cachier/2e267d1765a9bf5f3d62e7a3c258ea0d3159ff9a/demo/client/styles/arrow.png
--------------------------------------------------------------------------------
/demo/client/styles/cachierlogo.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/oslabs-beta/Cachier/2e267d1765a9bf5f3d62e7a3c258ea0d3159ff9a/demo/client/styles/cachierlogo.png
--------------------------------------------------------------------------------
/demo/client/styles/customizableIcon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/oslabs-beta/Cachier/2e267d1765a9bf5f3d62e7a3c258ea0d3159ff9a/demo/client/styles/customizableIcon.png
--------------------------------------------------------------------------------
/demo/client/styles/dhruv.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/oslabs-beta/Cachier/2e267d1765a9bf5f3d62e7a3c258ea0d3159ff9a/demo/client/styles/dhruv.png
--------------------------------------------------------------------------------
/demo/client/styles/github.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/oslabs-beta/Cachier/2e267d1765a9bf5f3d62e7a3c258ea0d3159ff9a/demo/client/styles/github.png
--------------------------------------------------------------------------------
/demo/client/styles/graphqlLogo.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/oslabs-beta/Cachier/2e267d1765a9bf5f3d62e7a3c258ea0d3159ff9a/demo/client/styles/graphqlLogo.png
--------------------------------------------------------------------------------
/demo/client/styles/graphqlgif.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/oslabs-beta/Cachier/2e267d1765a9bf5f3d62e7a3c258ea0d3159ff9a/demo/client/styles/graphqlgif.gif
--------------------------------------------------------------------------------
/demo/client/styles/hua.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/oslabs-beta/Cachier/2e267d1765a9bf5f3d62e7a3c258ea0d3159ff9a/demo/client/styles/hua.png
--------------------------------------------------------------------------------
/demo/client/styles/installIcon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/oslabs-beta/Cachier/2e267d1765a9bf5f3d62e7a3c258ea0d3159ff9a/demo/client/styles/installIcon.png
--------------------------------------------------------------------------------
/demo/client/styles/jonathan.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/oslabs-beta/Cachier/2e267d1765a9bf5f3d62e7a3c258ea0d3159ff9a/demo/client/styles/jonathan.png
--------------------------------------------------------------------------------
/demo/client/styles/kaju.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/oslabs-beta/Cachier/2e267d1765a9bf5f3d62e7a3c258ea0d3159ff9a/demo/client/styles/kaju.png
--------------------------------------------------------------------------------
/demo/client/styles/linkedin.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/oslabs-beta/Cachier/2e267d1765a9bf5f3d62e7a3c258ea0d3159ff9a/demo/client/styles/linkedin.png
--------------------------------------------------------------------------------
/demo/client/styles/logo.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/oslabs-beta/Cachier/2e267d1765a9bf5f3d62e7a3c258ea0d3159ff9a/demo/client/styles/logo.png
--------------------------------------------------------------------------------
/demo/client/styles/memoryIcon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/oslabs-beta/Cachier/2e267d1765a9bf5f3d62e7a3c258ea0d3159ff9a/demo/client/styles/memoryIcon.png
--------------------------------------------------------------------------------
/demo/client/styles/partialExampleFetch.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/oslabs-beta/Cachier/2e267d1765a9bf5f3d62e7a3c258ea0d3159ff9a/demo/client/styles/partialExampleFetch.png
--------------------------------------------------------------------------------
/demo/client/styles/roman.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/oslabs-beta/Cachier/2e267d1765a9bf5f3d62e7a3c258ea0d3159ff9a/demo/client/styles/roman.png
--------------------------------------------------------------------------------
/demo/client/styles/spritesheet (1).json:
--------------------------------------------------------------------------------
1 | {
2 | "frames": {
3 | "Demo.png": {
4 | "frame": {
5 | "x": 1,
6 | "y": 1,
7 | "w": 512,
8 | "h": 512
9 | },
10 | "rotated": false,
11 | "trimmed": false,
12 | "spriteSourceSize": {
13 | "x": 0,
14 | "y": 0,
15 | "w": 512,
16 | "h": 512
17 | },
18 | "sourceSize": {
19 | "w": 512,
20 | "h": 512
21 | }
22 | },
23 | "DocsIcon.png": {
24 | "frame": {
25 | "x": 515,
26 | "y": 1,
27 | "w": 640,
28 | "h": 640
29 | },
30 | "rotated": false,
31 | "trimmed": false,
32 | "spriteSourceSize": {
33 | "x": 0,
34 | "y": 0,
35 | "w": 640,
36 | "h": 640
37 | },
38 | "sourceSize": {
39 | "w": 640,
40 | "h": 640
41 | }
42 | },
43 | "GithubLogo.png": {
44 | "frame": {
45 | "x": 1157,
46 | "y": 1,
47 | "w": 225,
48 | "h": 225
49 | },
50 | "rotated": false,
51 | "trimmed": false,
52 | "spriteSourceSize": {
53 | "x": 0,
54 | "y": 0,
55 | "w": 225,
56 | "h": 225
57 | },
58 | "sourceSize": {
59 | "w": 225,
60 | "h": 225
61 | }
62 | },
63 | "Logo.png": {
64 | "frame": {
65 | "x": 1157,
66 | "y": 228,
67 | "w": 464,
68 | "h": 639
69 | },
70 | "rotated": false,
71 | "trimmed": false,
72 | "spriteSourceSize": {
73 | "x": 0,
74 | "y": 0,
75 | "w": 464,
76 | "h": 639
77 | },
78 | "sourceSize": {
79 | "w": 464,
80 | "h": 639
81 | }
82 | },
83 | "background.png": {
84 | "frame": {
85 | "x": 1,
86 | "y": 643,
87 | "w": 1065,
88 | "h": 1004
89 | },
90 | "rotated": false,
91 | "trimmed": false,
92 | "spriteSourceSize": {
93 | "x": 0,
94 | "y": 0,
95 | "w": 1065,
96 | "h": 1004
97 | },
98 | "sourceSize": {
99 | "w": 1065,
100 | "h": 1004
101 | }
102 | },
103 | "customizableIcon.png": {
104 | "frame": {
105 | "x": 1384,
106 | "y": 1,
107 | "w": 200,
108 | "h": 200
109 | },
110 | "rotated": false,
111 | "trimmed": false,
112 | "spriteSourceSize": {
113 | "x": 0,
114 | "y": 0,
115 | "w": 200,
116 | "h": 200
117 | },
118 | "sourceSize": {
119 | "w": 200,
120 | "h": 200
121 | }
122 | },
123 | "download.png": {
124 | "frame": {
125 | "x": 1068,
126 | "y": 869,
127 | "w": 348,
128 | "h": 348
129 | },
130 | "rotated": false,
131 | "trimmed": false,
132 | "spriteSourceSize": {
133 | "x": 0,
134 | "y": 0,
135 | "w": 348,
136 | "h": 348
137 | },
138 | "sourceSize": {
139 | "w": 348,
140 | "h": 348
141 | }
142 | },
143 | "installIcon.png": {
144 | "frame": {
145 | "x": 1418,
146 | "y": 869,
147 | "w": 256,
148 | "h": 256
149 | },
150 | "rotated": false,
151 | "trimmed": false,
152 | "spriteSourceSize": {
153 | "x": 0,
154 | "y": 0,
155 | "w": 256,
156 | "h": 256
157 | },
158 | "sourceSize": {
159 | "w": 256,
160 | "h": 256
161 | }
162 | },
163 | "memoryIcon.png": {
164 | "frame": {
165 | "x": 1623,
166 | "y": 1,
167 | "w": 512,
168 | "h": 512
169 | },
170 | "rotated": false,
171 | "trimmed": false,
172 | "spriteSourceSize": {
173 | "x": 0,
174 | "y": 0,
175 | "w": 512,
176 | "h": 512
177 | },
178 | "sourceSize": {
179 | "w": 512,
180 | "h": 512
181 | }
182 | },
183 | "speedIcon.png": {
184 | "frame": {
185 | "x": 1623,
186 | "y": 515,
187 | "w": 348,
188 | "h": 348
189 | },
190 | "rotated": false,
191 | "trimmed": false,
192 | "spriteSourceSize": {
193 | "x": 0,
194 | "y": 0,
195 | "w": 348,
196 | "h": 348
197 | },
198 | "sourceSize": {
199 | "w": 348,
200 | "h": 348
201 | }
202 | },
203 | "storageIcon.png": {
204 | "frame": {
205 | "x": 1676,
206 | "y": 865,
207 | "w": 512,
208 | "h": 512
209 | },
210 | "rotated": false,
211 | "trimmed": false,
212 | "spriteSourceSize": {
213 | "x": 0,
214 | "y": 0,
215 | "w": 512,
216 | "h": 512
217 | },
218 | "sourceSize": {
219 | "w": 512,
220 | "h": 512
221 | }
222 | }
223 | },
224 | "meta": {
225 | "app": "http://www.codeandweb.com/texturepacker",
226 | "version": "1.0",
227 | "image": "spritesheet.png",
228 | "format": "RGBA8888",
229 | "size": {
230 | "w": 2189,
231 | "h": 1648
232 | },
233 | "scale": "1"
234 | }
235 | }
--------------------------------------------------------------------------------
/demo/client/styles/spritesheet (1).png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/oslabs-beta/Cachier/2e267d1765a9bf5f3d62e7a3c258ea0d3159ff9a/demo/client/styles/spritesheet (1).png
--------------------------------------------------------------------------------
/demo/client/styles/stephen.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/oslabs-beta/Cachier/2e267d1765a9bf5f3d62e7a3c258ea0d3159ff9a/demo/client/styles/stephen.png
--------------------------------------------------------------------------------
/demo/server/.DS_Store:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/oslabs-beta/Cachier/2e267d1765a9bf5f3d62e7a3c258ea0d3159ff9a/demo/server/.DS_Store
--------------------------------------------------------------------------------
/demo/server/DemoFunc.js:
--------------------------------------------------------------------------------
1 | /* @description
2 |
3 | Queries to /cacheMoney endpoint reach this middleware. Locally stored Eviction Queue tracks what is in the cache (cacheMoneyCache or Redis).
4 | The doubly linked list ("EvictionQueue") has a head - tracks the newest query, and a tail - tracks the oldest (least recently accessed) query.
5 | A locally stored object cache (cache) stores queries for O(1) look up.
6 | When a query arrives, the cache is queried for that key. If it's living in the cache, the query result is returned = Cached Result.
7 | If a query doesn't exist in the cache, the database (URI provided via parameter "endpoint") is queried and returned = Uncached Result.
8 | That query result is stored in Redis, and at the head of our linked list.
9 | Developers can choose to store the cache in Object Storage, simply by not providing a "redisClient" parameter. In this case, "cacheMoneyCache"
10 | stores queries in object form, just like Redis. We only recommend this for development purposes.
11 | The parameters "capacity" and "groupSize" determine the max capacity of the cache and the amount of queries in that cache that will be selected for
12 | eviction scrutiny, respectively. Ex: capacity = 50, groupSize = 5 -> whenever cache capacity is exceeded (51 unique queries have been made and cached),
13 | the 5 least recently accessed queries are selected, and the one with the highest latency (time delay) is evicted from the cache.
14 | This custom eviction policy accounts for both query recency and latency.
15 |
16 | @params: endpoint (str), capacity (int), groupSize (int), redisClient (bool)
17 | */
18 |
19 | const fetch = (...args) =>
20 | import('node-fetch').then(({ default: fetch }) => fetch(...args));
21 |
22 | function CacheMoney(endpoint, capacity, groupSize, redisClient = null) {
23 |
24 | //Return list of cached items (for demo purposes)
25 | const traverse = (list) => {
26 | let currNode = list.head;
27 | const output = [];
28 | let counter = 1;
29 |
30 | while (currNode) {
31 | output.push({
32 | latency: currNode.latency,
33 | key: currNode.key,
34 | num: currNode.num,
35 | });
36 | currNode = currNode.next;
37 | counter++;
38 | }
39 | return output;
40 | };
41 |
42 | let queue = new EvictionQueue(); //Initalize a new eviction queue (linked list) for the server
43 | let currGroupSize = groupSize; //Track of the current group size
44 | let cacheMoneyCache = {}; //Initialize a the cache (Object Storge) for O(1) lookup
45 |
46 | if (capacity < 1 || groupSize < 1 || groupSize > capacity) {
47 | throw new Error(
48 | 'Capacity and groupSize needs to be a number greater than 1 and groupsize cannot exceed capacity'
49 | );
50 | }
51 |
52 | return async function checkCache(req, res, next) {
53 | const query = req.body.query.trim();
54 | let { variables } = req.body;
55 |
56 | const isMutation = query.startsWith('mutation');
57 |
58 | //Account for client not including a variables object in the body of the post request
59 | if (!variables) {
60 | variables = {};
61 | }
62 |
63 | //Stringify variables and concats with queryStr to form a unique key in the cache
64 | const variablesStr = JSON.stringify(variables);
65 | const cacheKey = JSON.stringify(`${query}${variablesStr}`);
66 | let valueFromCache;
67 |
68 | //Check if query is already in our Redis cache or Object Storage (cacheMoneyCache)
69 | if (redisClient) {
70 | try {
71 | valueFromCache = await redisClient.get(cacheKey);
72 | } catch (error) {
73 | return next({ log: 'invalid RedisPort' });
74 | }
75 | } else {
76 | console.log('running local cache');
77 | valueFromCache = cacheMoneyCache[cacheKey];
78 | }
79 |
80 | if (valueFromCache) {
81 | //Update recency of the accessed cacheKey by moving it to the front of the linked list
82 | queue.updateRecencyOfExistingCache(cacheKey);
83 | //If cache contains the requested data, return data from the cache
84 | const parsedValue = JSON.parse(valueFromCache);
85 | const listArray = traverse(queue);
86 |
87 | res.send({
88 | data: parsedValue,
89 | queue: listArray,
90 | currGroupSize,
91 | cached: true,
92 | });
93 | } else {
94 | const start = performance.now();
95 | fetch(endpoint, {
96 | method: 'POST',
97 | headers: { 'Content-type': 'application/json' },
98 | body: JSON.stringify({
99 | query,
100 | variables,
101 | }),
102 | })
103 | .then((response) => response.json())
104 | .then((data) => {
105 | if (!isMutation) {
106 | const end = performance.now();
107 | const latency = end - start;
108 |
109 | redisClient
110 | ? redisClient.set(cacheKey, JSON.stringify(data))
111 | : (cacheMoneyCache[cacheKey] = JSON.stringify(data));
112 |
113 | queue.add(cacheKey, latency);
114 | let removedNode = { latency: 0, num: 0 };
115 | if (queue.length > capacity) {
116 | removedNode = queue.removeSmallestLatencyFromGroup(currGroupSize);
117 | const removedQueryKey = removedNode.key;
118 |
119 | redisClient
120 | ? redisClient.del(removedQueryKey)
121 | : delete cacheMoneyCache[removedQueryKey];
122 |
123 | currGroupSize -= 1;
124 | if (currGroupSize <= 0) currGroupSize = groupSize;
125 | }
126 | const listArray = traverse(queue);
127 | res.json({
128 | data,
129 | queue: listArray,
130 | removedNode: {
131 | latency: removedNode.latency,
132 | num: removedNode.num,
133 | },
134 | currGroupSize,
135 | cached: false,
136 | });
137 | } else { //Mutation -> Cache invalidation
138 | if (redisClient){
139 | //Clear out cache in Redis
140 | redisClient.flushall((err, success) => {
141 | if (err) console.log('Error clearing Redis cache: ', err);
142 | else console.log(success);
143 | });
144 | redisClient.set(cacheKey, JSON.stringify(data))
145 | } else {
146 | //Delete cache (Object Storage) and instantiate a new one
147 | delete cacheMoneyCache;
148 | let cacheMoneyCache = {};
149 | (cacheMoneyCache[cacheKey] = JSON.stringify(data));
150 | }
151 | return res.json(data);
152 | }
153 | })
154 | .catch((err) => {
155 | next({
156 | log: `Error:${err} unable to fetch query from specified endpoint`,
157 | });
158 | });
159 | }
160 | };
161 | }
162 |
163 | //Node constructor for our doubly linkedlist;
164 | class Node {
165 | constructor(key, latency, num) {
166 | this.key = key;
167 | this.latency = latency;
168 | this.next = null;
169 | this.prev = null;
170 | this.num = num;
171 | }
172 | }
173 |
174 | //Keep track of eviction order
175 | class EvictionQueue {
176 | constructor() {
177 | //Track front of queue (where newest queries go)
178 | this.head = null;
179 | //Track back of queue (oldest accessed query)
180 | this.tail = null;
181 | //Track the # of nodes in the queue
182 | this.length = 0;
183 | //Keep all nodes of linkedlist in a hashmap associated with their query keys to allow O(1) updates to node positions in the queue
184 | this.cache = {};
185 | this.nodeNum = 1;
186 | }
187 |
188 | //Add a node to the queue (at the head).
189 | add(cacheKey, latency) {
190 | //Create a new Node containing the latency of the query and its key in the cache.
191 | const newNode = new Node(cacheKey, latency, this.nodeNum++);
192 | this.cache[cacheKey] = newNode;
193 | //Account for if the queue is empty.
194 | if (this.head === null && this.tail === null) {
195 | this.head = newNode;
196 | this.tail = newNode;
197 | } else {
198 | //Add new node to the front of the list by making it the new head - make old head prev point to the newNode.
199 | this.head.prev = newNode;
200 | //Make newNode point to old head and relabel newNode as the new head.
201 | newNode.next = this.head;
202 | this.head = newNode;
203 | }
204 |
205 | //Update length of the EvictionQueue.
206 | this.length += 1;
207 | }
208 |
209 | // Remove node with lowest latency (fastest query) from eviction queue
210 | removeSmallestLatencyFromGroup(groupSize) {
211 | if (this.tail === null) return undefined;
212 |
213 | //Initialize smallest latency tracker and current node tracker at the tail
214 | let minLatencyNodeInGroup = this.tail;
215 | let currentNode = this.tail;
216 |
217 | //Account for edge case: eviction policy capacity is set to 1 and the only node in queue is being removed
218 | if (this.head === this.tail) {
219 | this.head = null;
220 | this.tail = null;
221 | this.length--;
222 | delete this.cache[currentNode.key];
223 | return currentNode;
224 | }
225 |
226 | //Iterate through the least recent group and find the smallest latency node.
227 | while (groupSize > 0 && currentNode) {
228 | if (currentNode.latency < minLatencyNodeInGroup.latency)
229 | minLatencyNodeInGroup = currentNode;
230 | currentNode = currentNode.prev;
231 | groupSize -= 1;
232 | }
233 |
234 | //Account for: the smallest latency query in group is the head of the Eviction Queue
235 | if (this.head === minLatencyNodeInGroup) {
236 | this.head = this.head.next;
237 | this.head.prev = null;
238 | this.length--;
239 | delete this.cache[minLatencyNodeInGroup.key];
240 | return minLatencyNodeInGroup;
241 | }
242 |
243 | //Account for: the smallest latency query in group is the tail of the Eviction Queue
244 | if (this.tail === minLatencyNodeInGroup) {
245 | this.tail = this.tail.prev;
246 | this.tail.next = null;
247 | this.length--;
248 | delete this.cache[minLatencyNodeInGroup.key];
249 | return minLatencyNodeInGroup;
250 | }
251 |
252 | //Remove smallest latency query in group from the Eviction Queue if node is not the tail or head
253 | const removedNode = minLatencyNodeInGroup;
254 | minLatencyNodeInGroup.prev.next = minLatencyNodeInGroup.next;
255 | minLatencyNodeInGroup.next.prev = minLatencyNodeInGroup.prev;
256 | this.length--;
257 | //delete this.cache[minLatencyNodeInGroup.key];
258 | delete this.cache[removedNode.key];
259 | return removedNode;
260 | }
261 |
262 | //Move node to front of the Eviction Queue when its cache is accessed again inorder to update recency
263 | updateRecencyOfExistingCache(cacheKey) { //O(n) lookup
264 | //hashmap (cache) is used to lookup node in O(1) time and return the node
265 | const node = this.cache[cacheKey];
266 |
267 | //Edge case: if node being updated is already at the head of list -> its already in the most recent position
268 | if (this.head === node) {
269 | return;
270 | }
271 |
272 | //Unlink Node being updated
273 | //Account for: if the tail node is getting updated
274 | if (this.tail === node) {
275 | node.prev.next = node.next;
276 | this.tail = node.prev;
277 | } else {
278 | node.next.prev = node.prev;
279 | node.prev.next = node.next;
280 | }
281 |
282 | //Move node to the head of the queue inorder to update the recency.
283 | this.head.prev = node;
284 | node.next = this.head;
285 | this.head = node;
286 | this.head.prev = null;
287 | }
288 | }
289 |
290 | module.exports = CacheMoney;
--------------------------------------------------------------------------------
/demo/server/DemoFunc.ts:
--------------------------------------------------------------------------------
1 | /* @description
2 |
3 | Queries to /cacheMoney endpoint (from frontend) reach here. Locally stored linked list tracks what is in Redis cache.
4 | The doubly linked list ("EvictionQueue") has a head that tracks the newest query, and a tail which tracks the oldest (least recently accessed) query.
5 | A locally stored object cache (cache) stores queries for O(1) look up.
6 | When a query reaches the backend, Redis (server-side) cache is queried for that key. If it is living in the Redis cache, the query result is returned
7 | = Cached Result. If a query is non-existent in Redis cache, the database (URI provided via the parameter "endpoint") is queried and returned = Uncached Result
8 | That query result is stored in Redis, and at the head of our linked list.
9 | Optionally, developers can choose to store the cache fully locally, simply by not providing a "redisClient" parameter. In this case, "cacheMoneyCache"
10 | stores queries in object form, just like Redis.
11 | The parameters "capacity" and "groupSize" determine the max capacity of the cache and the amount of queries in that cache that will be selected for
12 | eviction scrutiny, respectively. Ex: capacity = 50, groupSize = 5 -> whenever cache capacity is reached (50 unique queries have been made anc cached),
13 | the 5 least recently accessed queries are selected, and the one with the highest latency (time delay) is evicted from the Redis cache, local cache and linked list.
14 | This custom eviction policy accounts for both query recency and latency.
15 |
16 | */
17 | import { EvQ, Nde, removedQueryKey } from "../types";
18 | import { Request, Response, NextFunction } from "express";
19 | const fetch:any = (endpoint: string, options: any) =>
20 | import('node-fetch').then(({ default: fetch }) => fetch(endpoint, options));
21 |
22 | function CacheMoney(endpoint: string, capacity: number, groupSize: number, redisClient: any = null) {
23 | const traverse = (list: EvQ) => {
24 | let currNode = list.head;
25 | const output = [];
26 | let counter:number = 1;
27 |
28 | while (currNode) {
29 | output.push({
30 | latency: currNode.latency,
31 | key: currNode.key,
32 | num: currNode.num,
33 | });
34 | currNode = currNode.next;
35 | counter++;
36 | }
37 | return output;
38 | };
39 | //initalizes a new eviction queue (linked list) for the server
40 | let queue: EvQ = new EvictionQueue();
41 | //keeps track of the current group size
42 | let currGroupSize = groupSize;
43 | let cacheMoneyCache:any = {};
44 |
45 | if (capacity < 1 || groupSize < 1 || groupSize > capacity) {
46 | throw new Error(
47 | 'Capacity and groupSize needs to be a number greater than 1 and groupsize cannot exceed capacity'
48 | );
49 | }
50 |
51 | return async function checkCache(req: Request, res: Response, next: NextFunction) {
52 | const query = req.body.query.trim();
53 | let { variables } = req.body;
54 |
55 | const isMutation = query.startsWith('mutation');
56 |
57 | //accounts for if client did not include a variables object in the body of the post.
58 | if (!variables) {
59 | variables = {};
60 | }
61 |
62 | //Stringifies variables and concats with queryStr to form a unique key in the cache.
63 | const variablesStr = JSON.stringify(variables);
64 | const cacheKey:string = JSON.stringify(`${query}${variablesStr}`);
65 | let valueFromCache;
66 |
67 | //checks if query is already in our redis cache or cacheMoneyCache if redis is not being used.
68 | if (redisClient) {
69 | try {
70 | valueFromCache = await redisClient.get(cacheKey);
71 | } catch (error) {
72 | return next({ log: 'invalid RedisPort' });
73 | }
74 | } else {
75 | console.log('running local cache');
76 | valueFromCache = cacheMoneyCache[cacheKey];
77 | }
78 |
79 | if (valueFromCache) {
80 | // update recency of the accessed cacheKey by moving it to the front of the linked list.
81 |
82 | queue.updateRecencyOfExistingCache(cacheKey);
83 | // if cache contains the requested data return data from the cache.
84 | const parsedValue = JSON.parse(valueFromCache);
85 | const listArray = traverse(queue);
86 |
87 | res.send({
88 | data: parsedValue,
89 | queue: listArray,
90 | currGroupSize,
91 | cached: true,
92 | });
93 | } else {
94 | const start = performance.now();
95 | console.log('before fetch');
96 | fetch(endpoint, {
97 | method: 'POST',
98 | headers: { 'Content-type': 'application/json' },
99 | body: JSON.stringify({
100 | query,
101 | variables,
102 | }),
103 | })
104 | .then((response: any) => response.json())
105 | .then((data: any) => {
106 | console.log('data: ', data);
107 | if (!isMutation) {
108 | const end: number = performance.now();
109 | const latency: number = end - start;
110 |
111 | redisClient
112 | ? redisClient.set(cacheKey, JSON.stringify(data))
113 | : (cacheMoneyCache[cacheKey] = JSON.stringify(data));
114 |
115 | queue.add(cacheKey, latency);
116 | let removedNode: any = { latency: 0, num: 0 };
117 | if (queue.length > capacity) {
118 | removedNode = queue.removeSmallestLatencyFromGroup(currGroupSize);
119 | const removedQueryKey: any = removedNode.key;
120 |
121 | redisClient
122 | ? redisClient.del(removedQueryKey)
123 | : delete cacheMoneyCache[removedQueryKey];
124 |
125 | currGroupSize -= 1;
126 | if (currGroupSize <= 0) currGroupSize = groupSize;
127 | }
128 | const listArray = traverse(queue);
129 | // console.log('This one!');
130 | console.log('GROUP', currGroupSize);
131 | res.json({
132 | data,
133 | queue: listArray,
134 | removedNode: {
135 | latency: removedNode.latency,
136 | num: removedNode.num,
137 | },
138 | currGroupSize,
139 | cached: false,
140 | });
141 | } else {
142 | return res.json(data);
143 | }
144 | })
145 | .catch((err: any) => {
146 | next({
147 | log: `Error:${err} unable to fetch query from specified endpoint`,
148 | });
149 | });
150 | }
151 | };
152 | }
153 |
154 | //Node constructor for our doubly linkedlist;
155 | class Node {
156 |
157 | public key: any;
158 | public latency: number;
159 | public next: any;
160 | public prev: any;
161 | public num: number;
162 |
163 | constructor(key: string, latency: number, num: number) {
164 | this.key = key;
165 | this.latency = latency;
166 | this.next = null;
167 | this.prev = null;
168 | this.num = num;
169 | }
170 | }
171 |
172 | //Doubly linked list to keep track of our eviction order
173 | class EvictionQueue {
174 |
175 | public head: any;
176 | public tail: any;
177 | public length: number;
178 | public cache: any;
179 | public nodeNum: number;
180 |
181 | constructor() {
182 | // tracks front of queue (where newest queries go)
183 | this.head = null;
184 | // tracks back of queue (oldest accessed query)
185 | this.tail = null;
186 | //keeps track of the number of nodes in the queue
187 | this.length = 0;
188 | //Keeps all nodes of linkedlist in a hashmap associated with their redis keys to allow O(1) updates to node positions in the queue
189 | this.cache = {};
190 | this.nodeNum = 1;
191 | }
192 |
193 | //adds a node to the queue (at the head).
194 | add(cacheKey: string, latency: number) {
195 | //creates a new Node containing the latency of the query and its key in the cache.
196 | const newNode = new Node(cacheKey, latency, this.nodeNum++);
197 | this.cache[cacheKey] = newNode;
198 | //accounts for if the queue is empty.
199 | if (this.head === null && this.tail === null) {
200 | this.head = newNode;
201 | this.tail = newNode;
202 | } else {
203 | //Will add new node to the front of the list by making it the new head.
204 | //makes old head prev point to the newNode.
205 | this.head.prev = newNode;
206 | // makes newNode point to old head and relabels newNode as the new head.
207 | newNode.next = this.head;
208 | this.head = newNode;
209 | }
210 |
211 | //updates length of the EvictionQueue.
212 | this.length += 1;
213 | }
214 |
215 | removeSmallestLatencyFromGroup(groupSize: number): any {
216 | if (this.tail === null) return undefined;
217 |
218 | //keeps track of smallest latency node.
219 | let minLatencyNodeInGroup = this.tail;
220 | let currentNode = this.tail;
221 |
222 | //accounts for edge case if eviction policy capacity is set to 1 and the only node in queue is being removed.
223 | if (this.head === this.tail) {
224 | this.head = null;
225 | this.tail = null;
226 | this.length--;
227 | delete this.cache[currentNode.key];
228 | return currentNode;
229 | }
230 |
231 | //iterates through the least recent group and finds the smallest latency node.
232 | while (groupSize > 0 && currentNode) {
233 | if (currentNode.latency < minLatencyNodeInGroup.latency)
234 | minLatencyNodeInGroup = currentNode;
235 | currentNode = currentNode.prev;
236 | groupSize -= 1;
237 | }
238 |
239 | //accounts for if the minLatencyNodeInGroup is the head of the list
240 | if (this.head === minLatencyNodeInGroup) {
241 | this.head = this.head.next;
242 | this.head.prev = null;
243 | this.length--;
244 | delete this.cache[minLatencyNodeInGroup.key];
245 | return minLatencyNodeInGroup;
246 | }
247 |
248 | //accounts for if the minLatencyNodeInGroup is the tail of the list
249 | if (this.tail === minLatencyNodeInGroup) {
250 | this.tail = this.tail.prev;
251 | this.tail.next = null;
252 | this.length--;
253 | delete this.cache[minLatencyNodeInGroup.key];
254 | return minLatencyNodeInGroup;
255 | }
256 |
257 | //Removes minLatencyNodeInGroup from the Eviction Queue if node is not the tail or head.
258 | const removedNode = minLatencyNodeInGroup;
259 | minLatencyNodeInGroup.prev.next = minLatencyNodeInGroup.next;
260 | minLatencyNodeInGroup.next.prev = minLatencyNodeInGroup.prev;
261 | this.length--;
262 | delete this.cache[removedNode.key];
263 | return removedNode;
264 | }
265 |
266 | //Moves node to front of the linkedList if its cache is accessed again inorder to update recency.
267 | updateRecencyOfExistingCache(cacheKey: string) {
268 | const node = this.cache[cacheKey];
269 | //if node being updated is already at the head of list, we can just return since it is already in the most recent position.
270 | if (this.head === node) {
271 | console.log('hit head');
272 | return;
273 | }
274 | //remove linkage of the node at the current position.
275 | //accounts for if the tail node is the one getting updated since it wouldnt have a next node in the list.
276 | if (this.tail === node) {
277 | node.prev.next = node.next;
278 | this.tail = node.prev;
279 | } else {
280 | node.next.prev = node.prev;
281 | node.prev.next = node.next;
282 | }
283 |
284 | //move node to the head of the queue in order to update the recency.
285 | this.head.prev = node;
286 | node.next = this.head;
287 | this.head = node;
288 | this.head.prev = null;
289 | }
290 | }
291 |
292 | module.exports = CacheMoney;
293 |
294 |
295 |
296 |
--------------------------------------------------------------------------------
/demo/server/cacheMoney.js:
--------------------------------------------------------------------------------
1 | // import the node fetch library
2 | // create a function fetch that takes in an array & returns a promise
3 | // two functions are then chained to the returned promise object
4 | // the first function is a default calling fetch with its arguments
5 | // the second one is a function passed onto then
6 | const fetch = (...args) =>
7 | import('node-fetch').then(
8 | ({ default: fetch }) => fetch(...args));
9 |
10 | // the function CacheMoney analyzes the endpoint, capacity & groupSize
11 | // CacheMoney determines what kind of eviction queue it should create.
12 | // redisClient variable is optional and can be use instead of Node's built-in queues.
13 |
14 | // The function initializes a new eviction queue (linked List) for the server
15 | // it assigns grouSize to currGroupSize to keep track of Current Group Size.
16 | // it creates a constant cacheMoneyCache Object
17 |
18 |
19 | function CacheMoney(endpoint, capacity, groupSize, redisClient = null) {
20 | let currGroupSize = groupSize;
21 | const cacheMoneyCache = {};
22 | if (capacity < 1 || groupSize < 1 || groupSize > capacity) {
23 | throw new Error(
24 | 'Capacity and groupSize needs to be a number greater than 1 and groupsize cannot exceed capacity'
25 | );
26 | }
27 | // Function returns an async function
28 | // the purpose of this function is to check for cache hits using req res next paramerters
29 | // assign query to query key in req.body ad trims whitespace
30 | // destructure the variables key from req.body
31 | // if the destructured query starts with the string 'mutation',assign to isMutation.
32 |
33 | // check to see if the variables exists in req.body
34 | // if it does not, create a empty object called variable
35 | // stringify the variable object
36 |
37 | // the destructured variables object is stringified and assigned to variablesStr
38 | // cacheKey is assigned to the query and variablesStr are stringified, concatenated
39 | // declare an empty variable called valueFromCache for later use
40 |
41 | // if redisClient is not null from line 18, insert a try and catch block
42 | // use valueFromCache to await the result of querying Redis with cacheKey
43 | // if the value is present in the catch, assign it to valueFromCache
44 | // if the value is not present in the catch,
45 |
46 |
47 |
48 | return async function checkCache(req, res, next) {
49 | const query = req.body.query.trim();
50 | let { variables } = req.body;
51 | const isMutation = query.startsWith('mutation');
52 | if (!variables) {
53 | variables = {};
54 | }
55 | //Stringifies variables and concats with queryStr to form a unique key in the cache.
56 | const variablesStr = JSON.stringify(variables);
57 | const cacheKey = JSON.stringify(`${query}${variablesStr}`);
58 | let valueFromCache;
59 |
60 |
61 | // If redisClient exists then enter the try block
62 | // Assign valueFromCache to await the result of querying Redis with cacheKey
63 | // cacheKey is result of stringifying the query and variablesStr
64 | // Where query is the data from req.body.query, trimmed for whitespace.
65 | // variablesStr is the stringified variables object from req.body
66 | // If an error occurs in the try block,
67 | // The next function is returned with an object containing the error string.
68 | // if there is no error, log 'running local cache' to the console
69 | // Assign valueFromCache the cachekey in the cacheMoneyCache Object.
70 | // if valueFromCache exists, then send valueFromCache in the response body
71 | // update
72 |
73 |
74 | if (redisClient) {
75 | try {
76 | valueFromCache = await redisClient.get(cacheKey);
77 | } catch (error) {
78 | return next({ log: 'invalid RedisPort' });
79 | }
80 | } else {
81 | console.log('running local cache');
82 | valueFromCache = cacheMoneyCache[cacheKey];
83 | }
84 | // invoke updateRecencyofExistingCache with cacheKey as an argument
85 | // do the linked list thing
86 | // otherwise, create a variable called start and assign start recording current time
87 | // makes a POST request to the endpoint
88 | // setting the content type of the header as 'application/json'
89 | // the stringifying the body containing the query and variables
90 | // then the response is assigned to the variable response
91 | // the response is parsed and assigned to the variable data
92 | // if the data is not a Mutation,
93 | // create a variable called end and stop recording the current time
94 | // create a variable called latency and subtract start time from the end
95 | // and data is sent after converting it into a JSON object
96 |
97 | // was in code
98 | //// update recency of the accessed cacheKey by moving it to the front of the linked list.
99 |
100 |
101 | if (valueFromCache) {
102 | res.send(valueFromCache);
103 | queue.updateRecencyOfExistingCache(cacheKey);
104 | } else {
105 | const start = performance.now();
106 | fetch(endpoint, {
107 | method: 'POST',
108 | headers: { 'Content-type': 'application/json' },
109 | body: JSON.stringify({
110 | query,
111 | variables,
112 | }),
113 | })
114 | .then((response) => response.json())
115 | .then((data) => {
116 | //if query is a mutation do not cache
117 | if (!isMutation) {
118 | const end = performance.now();
119 | const latency = end - start;
120 | res.json(data);
121 |
122 | //check if the redisClient exists
123 | // if it does, set cacheKey to the stringified form of data.
124 | // if it does not, set cacheKey in cacheMoneyCache Object to stringified data
125 | // add the cacheKey to the linked list queue
126 | // if the length linked list queue is greater than the capacity,
127 | // assign the variable evicted to the result of invoking the evict function
128 | // if the redisClient exists, delete the evicted key from the redis cache
129 | // if the redisClient does not exist, delete the evicted key from the cacheMoneyCache[removedQueryKey]
130 | // if the currGroupSize is greater than 0, decrement currGroupSize by 1
131 | // if the currGroupSize is less than or equal to 0,
132 | // assign currGroupSize to groupSize
133 | // otherwise, log the data to the console
134 | // if there is an error, invoke the next function with an object containing the error string
135 |
136 | redisClient
137 | ? redisClient.set(cacheKey, JSON.stringify(data))
138 | : (cacheMoneyCache[cacheKey] = JSON.stringify(data));
139 |
140 | queue.add(cacheKey, latency);
141 | if (queue.length > capacity) {
142 | const removedQueryKey =
143 | queue.removeSmallestLatencyFromGroup(currGroupSize).key;
144 |
145 | redisClient
146 | ? redisClient.del(removedQueryKey)
147 | : delete cacheMoneyCache[removedQueryKey];
148 |
149 | currGroupSize -= 1;
150 | if (currGroupSize <= 0) currGroupSize = groupSize;
151 | }
152 | } else {
153 | return res.json(data);
154 | }
155 | })
156 | .catch((err) => {
157 | next({
158 | log: `Error:${err} unable to fetch query from specified endpoint`,
159 | });
160 | });
161 | }
162 | };
163 | }
164 | // create a new eviction queue
165 | // creat a class called NOde
166 | // create a constructor that takes in a key and latency
167 | // assign the key to the key property
168 | // assign the latency to the latency property
169 | // assign the next property to null
170 | // assign the prev property to null
171 |
172 | class Node {
173 | constructor(key, latency) {
174 | this.key = key;
175 | this.latency = latency;
176 | this.next = null;
177 | this.prev = null;
178 | }
179 | }
180 | // create a new class called EvictionQueue
181 | // create a constructor function
182 | // assign the head property to null to keep track of the head of the linked list
183 | // assign the tail property to null to back of the linked list
184 | // assign the length property to 0 to keep track of the length of the linked list
185 | // create a cache property
186 | // the cache property is an object to keep all nodes in LL in a hashmap
187 | // the nodes are associated with their Redis keys to allow for O(1) access && update positions in the LL
188 |
189 |
190 | //Doubly linked list to keep track of our eviction order.
191 | //keeps track of front of queue.
192 | //keeps track of back of queue.
193 | //keeps track of the number of nodes in the queue
194 | //keeps all nodes of linkedlist in a hashmap associated with their redis keys to allow O(1) updates to node positions in the queue.
195 |
196 | class EvictionQueue {
197 | constructor() {
198 | this.head = null;
199 | this.tail = null;
200 | this.length = 0;
201 | this.cache = {};
202 | }
203 |
204 |
205 | //Create a method add to add a new node with the key and latency.
206 | //creates a new Node w/ the latency of the query and its key in the cache.
207 | // if the head is null and the tail is null,
208 | // assign the head to the new node
209 | // assign the tail to the new node
210 | // otherwise, assign the previous head to the new node to the new node
211 | // adding the new node to the head of the linked list
212 | // point the new node to the previous head
213 | // relabel the newNode as the head
214 | // increment the length by 1
215 | //Will add new node to the front of the list by making it the new head.
216 | //makes old head prev point to the newNode.
217 | // makes newNode point to old head and relabels newNode as the new head.
218 | //accounts for if the queue is empty.
219 |
220 | add(cacheKey, latency) {
221 | const newNode = new Node(cacheKey, latency);
222 | this.cache[cacheKey] = newNode;
223 | if (this.head === null && this.tail === null) {
224 | this.head = newNode;
225 | this.tail = newNode;
226 | } else {
227 | this.head.prev = newNode;
228 | newNode.next = this.head;
229 | this.head = newNode;
230 | }
231 | this.length += 1;
232 | }
233 |
234 | //create a method called removeSmallestLatencyFromGroup
235 | // if the tail is null return undefined
236 | // create a variable called minLatencyNodeInGroup and assign it to the tail
237 | // create a variable called currNode and assign it to the tail
238 | //accounts for edge case if eviction policy capacity is set to 1 and the only node in queue is being removed.
239 | //keeps track of smallest latency node.
240 | // if the head is equal to the tail
241 | // assign the head to null
242 | // assign the tail to null
243 | // decrease the length by 1
244 | // delete the cacheKey from the cache
245 | // return the currentNode
246 |
247 | removeSmallestLatencyFromGroup(groupSize) {
248 | if (this.tail === null) return undefined;
249 | let minLatencyNodeInGroup = this.tail;
250 | let currentNode = this.tail;
251 | if (this.head === this.tail) {
252 | this.head = null;
253 | this.tail = null;
254 | this.length--;
255 | delete this.cache[currentNode.key];
256 | return currentNode;
257 | }
258 |
259 | //iterates through the least recent group and finds the smallest latency node.
260 | // while the groupSize is greater than and the current node exists
261 | //if the current node latency is less than the minLatencyNodeInGroup latency
262 | // assign the minLatencyNodeInGroup to the current node
263 | // set the current node to the previous current node
264 | // decrement the groupSize by 1
265 | while (groupSize > 0 && currentNode) {
266 | if (currentNode.latency < minLatencyNodeInGroup.latency)
267 | minLatencyNodeInGroup = currentNode;
268 | currentNode = currentNode.prev;
269 | groupSize -= 1;
270 | }
271 |
272 | //accounts for if the minLatencyNodeInGroup is the head of the list
273 | //if the head is equal to the minLatencyNodeInGroup
274 | // assign the head to the next node
275 | // assign the previous head to null
276 | // delete the minLatencyNodeInGroup from the cache
277 | // return the minLatencyNodeInGroup
278 | if (this.head === minLatencyNodeInGroup) {
279 | this.head = this.head.next;
280 | this.head.prev = null;
281 | this.length--;
282 | delete this.cache[minLatencyNodeInGroup.key];
283 | return minLatencyNodeInGroup;
284 | }
285 |
286 | //accounts for if the minLatencyNodeInGroup is the tail of the list
287 | // if the tail is equal to the minLatencyNodeInGroup
288 | // assign the tail to the previous node
289 | // assign the next tail to null
290 | // decrease the length by 1
291 | // delete the minLatencyNodeInGroup from the cache
292 | // return the minLatencyNodeInGroup
293 |
294 | if (this.tail === minLatencyNodeInGroup) {
295 | this.tail = this.tail.prev;
296 | this.tail.next = null;
297 | this.length--;
298 | delete this.cache[minLatencyNodeInGroup.key];
299 | return minLatencyNodeInGroup;
300 | }
301 |
302 | //Removes minLatencyNodeInGroup from the Eviction Queue if node is not the tail or head.
303 | // assign removedNode to the minLatencyNodeInGroup
304 | // assign the next node of the previous node to the next node of minLatencyNodeInGroup
305 | // assign the previous node of the next node to the previous node of minLatencyNodeInGroup
306 | // decrease the length by 1
307 | // delete the minLatencyNodeInGroup from the cache
308 | // return the removedNode
309 | const removedNode = minLatencyNodeInGroup;
310 | minLatencyNodeInGroup.prev.next = minLatencyNodeInGroup.next;
311 | minLatencyNodeInGroup.next.prev = minLatencyNodeInGroup.prev;
312 | this.length--;
313 | delete this.cache[removedNode.key];
314 | return removedNode;
315 | }
316 |
317 | //Moves node to front of the linkedList if its cache is accessed again inorder to update recency.
318 | // create a method called updateRecencyofExistingCache
319 | // create a variable called node and assign it to the cacheKey in cache Object
320 | // if node being updated is the head of the linked list
321 | // log that the node is already the head of the linked list
322 | //if node being updated is already at the head of list, we can just return since it is already in the most recent position.
323 | updateRecencyOfExistingCache(cacheKey) {
324 | const node = this.cache[cacheKey];
325 | if (this.head === node) {
326 | console.log('hit head');
327 | return;
328 | }
329 |
330 | // if the node being updated is the tail of the linked list
331 | //remove linkage of the node at the current position.
332 | //accounts for if the tail node is the one getting updated since it wouldnt have a next node in the list.
333 | // we check if the node being updated is at the tail of the linked list.
334 | // If so, we remove linkage of that node and account for if the tail node was getting updated since it wouldn't have a next node in its list
335 | // the node gets updated and moved to the head position
336 | //move node to the head of the queue inorder to update the recency.
337 | if (this.tail === node) {
338 | node.prev.next = node.next;
339 | this.tail = node.prev;
340 | } else {
341 | node.next.prev = node.prev;
342 | node.prev.next = node.next;
343 | }
344 | this.head.prev = node;
345 | node.next = this.head;
346 | this.head = node;
347 | this.head.prev = null;
348 | }
349 | }
350 |
351 | module.exports = CacheMoney;
352 |
--------------------------------------------------------------------------------
/demo/server/config/db.js:
--------------------------------------------------------------------------------
1 |
2 | //require ('dotenv').config()
3 | const mongoose = require ('mongoose');
4 | const connectDB = async () => {
5 | const conn = await mongoose.connect (process.env.MONGO_URI);
6 | console.log (`DB connected `)
7 | }
8 | module.exports = connectDB
9 | // require the mongoose model mongoose library
10 | // create an async function called ConnectDB
11 | // when ConnectDB is invoked it returns a promise
12 | // the promise is assigned to the variable conn
13 | // log the connection to the console "DB connected"
14 | // export the ConnectDB function
15 |
--------------------------------------------------------------------------------
/demo/server/models/Client.js:
--------------------------------------------------------------------------------
1 | const mongoose = require('mongoose');
2 | const ClientSchema = new mongoose.Schema({
3 | name: {
4 | type: String,
5 | },
6 | email: {
7 | type: String,
8 | },
9 | phone: {
10 | type: String,
11 | },
12 | });
13 |
14 | module.exports = mongoose.model('Client', ClientSchema);
15 | // require in the mongoose module from the library
16 | // create a new schema called ClientSchema
17 | // ClientSchema is a mongoose.schema object
18 | // Client has a name of type string
19 | // Client has an email of type string
20 | // Client has a phone of type string
21 | // the module.exports line exports the ClientSchema object as a module
--------------------------------------------------------------------------------
/demo/server/models/Project.js:
--------------------------------------------------------------------------------
1 | const mongoose = require('mongoose');
2 | const ProjectSchema = new mongoose.Schema({
3 | name :{
4 | type: String,
5 | },
6 | description :{
7 | type: String,
8 | },
9 | status :{
10 | type: String,
11 | enum: ['Not Started','In Progress','Completed'],
12 | },
13 | clientId: {
14 | type : mongoose.Schema.Types.ObjectId,
15 | ref:'Client',
16 | },
17 | });
18 |
19 | module.exports = mongoose.model('Project', ProjectSchema);
20 |
21 | // require in the mongoose module from the library
22 | // create a new schema called ClientSchema
23 | // ClientSchema is a mongoose.schema object
24 | // Project has a name of type string
25 | // Project has an description of type string
26 | // Project has a status of type string & array of options for the status
27 | // Project has a clientId of type mongoose.Schema.Types.ObjectId
28 | // Client references the Client model from demo/server/models/Client.js
29 | // the module.exports line exports the ClientSchema object as a module
--------------------------------------------------------------------------------
/demo/server/schema.js:
--------------------------------------------------------------------------------
1 | //const {projects, clients} = require ('../sampleData.js')
2 | //Mongoose Models
3 | const Project = require('./models/Project');
4 | const Client = require('./models/Client');
5 |
6 | const {
7 | GraphQLObjectType,
8 | GraphQLID,
9 | GraphQLString,
10 | GraphQLSchema,
11 | GraphQLList,
12 | GraphQLNonNull,
13 | GraphQLEnumType,
14 | GraphQLUnionType,
15 | } = require('graphql');
16 | const { setgroups } = require('process');
17 |
18 | const ClientType = new GraphQLObjectType({
19 | name: 'Client',
20 | fields: () => ({
21 | id: { type: GraphQLID },
22 | name: { type: GraphQLString },
23 | email: { type: GraphQLString },
24 | phone: { type: GraphQLString },
25 | }),
26 | });
27 | //Project Type
28 | const ProjectType = new GraphQLObjectType({
29 | name: 'Project',
30 | fields: () => ({
31 | id: { type: GraphQLID },
32 | name: { type: GraphQLString },
33 | description: { type: GraphQLString },
34 | status: { type: GraphQLString },
35 | client: {
36 | type: ClientType,
37 | resolve(parent, args) {
38 | return Client.findById(parent.clientId);
39 | //((client) =>client.id === parent.clientId)
40 | },
41 | },
42 | }),
43 | });
44 |
45 | // root query object:
46 | // modify root query object to get all clients
47 |
48 | const RootQuery = new GraphQLObjectType({
49 | name: 'RootQueryType',
50 | fields: {
51 | projects: {
52 | type: new GraphQLList(ProjectType),
53 | resolve(parent, args) {
54 | return Project.find();
55 | },
56 | },
57 | project: {
58 | type: ProjectType,
59 | args: { id: { type: GraphQLID } },
60 | resolve(parent, args) {
61 | return Project.findbyId(args.id);
62 | //return projects.find ((project) => project.id === args.id)
63 | },
64 | },
65 | clients: {
66 | type: new GraphQLList(ClientType),
67 | resolve(parent, args) {
68 | return Client.find();
69 | },
70 | },
71 | client: {
72 | type: ClientType,
73 | args: { id: { type: GraphQLID } },
74 | resolve(parent, args) {
75 | return Client.findOne({ _id: args.id });
76 | //return clients.find ((client) => client.id === args.id)
77 | },
78 | },
79 | },
80 | });
81 |
82 | //Mutations
83 | const mutation = new GraphQLObjectType({
84 | name: 'Mutation',
85 | fields: {
86 | addClient: {
87 | //Add a Client
88 | type: ClientType,
89 | args: {
90 | name: { type: new GraphQLNonNull(GraphQLString) },
91 | email: { type: new GraphQLNonNull(GraphQLString) },
92 | phone: { type: new GraphQLNonNull(GraphQLString) },
93 | },
94 | resolve(parent, args) {
95 | const client = new Client({
96 | id: args.id,
97 | name: args.name,
98 | email: args.email,
99 | phone: args.phone,
100 | });
101 | return client.save();
102 | },
103 | }, //Delete a Client
104 | deleteClient: {
105 | type: ClientType,
106 | args: {
107 | id: { type: new GraphQLNonNull(GraphQLID) },
108 | },
109 | resolve(parent, args) {
110 | return Client.findByIdAndDelete(args.id);
111 | },
112 | },
113 |
114 | addProject: {
115 | //Add a project
116 | type: ProjectType,
117 | args: {
118 | name: { type: new GraphQLNonNull(GraphQLString) },
119 | description: { type: new GraphQLNonNull(GraphQLString) },
120 | status: {
121 | type: new GraphQLEnumType({
122 | name: 'ProjectStatus',
123 | values: {
124 | new: { value: 'Not Started' },
125 | progress: { value: 'In Progress' },
126 | completed: { value: 'Completed' },
127 | },
128 | }),
129 | defaultValue: ' Not Started',
130 | },
131 | clientId: { type: new GraphQLNonNull(GraphQLID) },
132 | },
133 | resolve(parent, args) {
134 | const project = new Project({
135 | name: args.name,
136 | description: args.description,
137 | status: args.status,
138 | clientId: args.clientId,
139 | });
140 | return project.save();
141 | },
142 | },
143 | deleteProject: {
144 | //Delete Project
145 | type: ProjectType,
146 | args: {
147 | id: { type: new GraphQLNonNull(GraphQLID) },
148 | },
149 | resolve(parent, args) {
150 | return Project.findByIdAndRemove(args.id);
151 | },
152 | },
153 |
154 | updateProject: {
155 | // UpdateProject
156 | type: ProjectType,
157 | args: {
158 | id: { type: new GraphQLNonNull(GraphQLID) },
159 | name: { type: GraphQLString },
160 | description: { type: GraphQLString },
161 | status: {
162 | type: new GraphQLEnumType({
163 | name: 'ProjectStatusUpdate',
164 | values: {
165 | new: { value: 'Not Started' },
166 | progress: { value: 'In Progress' },
167 | completed: { value: 'Completed' },
168 | },
169 | }),
170 | },
171 | },
172 | resolve(parent, args) {
173 | return Project.findByIdAndUpdate(
174 | args.id,
175 | {
176 | $set: {
177 | name: args.name,
178 | description: args.description,
179 | status: args.status,
180 | },
181 | },
182 | { new: true }
183 | );
184 | },
185 | },
186 | },
187 | });
188 | // could also use client.create method ...
189 | module.exports = new GraphQLSchema({
190 | query: RootQuery,
191 | mutation,
192 | });
193 |
194 | //https://share.codesmith.io/ECRIseniorgroup8
195 | //691511
196 |
--------------------------------------------------------------------------------
/demo/server/server.ts:
--------------------------------------------------------------------------------
1 | import { Request, Response } from 'express';
2 |
3 | require('dotenv').config();
4 | const path = require('path');
5 | const express = require('express');
6 | const expressGraphQL = require('express-graphql').graphqlHTTP;
7 | const schema = require('./schema.js');
8 | const redis = require('redis');
9 | const demoFunc = require('./DemoFunc.js');
10 |
11 | const cors = require('cors');
12 | const connectDB = require('./config/db');
13 | const port = process.env.PORT || 3000;
14 | const app = express();
15 | const partialQueryCache = require('@cachier/cache-partials');
16 |
17 | connectDB();
18 | // Changed package.json to "start": "server2.js"
19 | app.use(cors());
20 | app.use(express.json());
21 | app.use(express.urlencoded({ extended: true }));
22 |
23 | //app.use(express.static(path.resolve(__dirname, '../client')));
24 | app.use(express.static(path.resolve(__dirname, '../../dist')));
25 |
26 | app.use('/cacheMoney', demoFunc('http://localhost:3000/graphql', 4, 2));
27 |
28 | app.use(
29 | '/partialCache',
30 | partialQueryCache('http://localhost:3000/graphql', 60)
31 | );
32 |
33 | app.use(
34 | '/graphql',
35 | expressGraphQL({
36 | schema,
37 | graphiql: true,
38 | })
39 | );
40 |
41 | app.get('/*', (req: Request, res: Response) => {
42 | res.sendFile(
43 | path.resolve(__dirname, '../../dist/index.html'),
44 | function (err) {
45 | if (err) {
46 | res.status(500).send(err);
47 | }
48 | }
49 | );
50 | });
51 |
52 | app.use((req: Request, res: Response) =>
53 | res.status(404).send('Cannot get route')
54 | );
55 |
56 | app.listen(port, console.log(`Server listening on ${port}`));
57 |
58 | module.exports = app;
59 |
--------------------------------------------------------------------------------
/demo/types.ts:
--------------------------------------------------------------------------------
1 | export type GlobalServerError = {
2 | log: string;
3 | status: number;
4 | message: { err: string };
5 | }
6 |
7 | export type Nde = {
8 | key: any;
9 | latency: number;
10 | next: any;
11 | prev: any;
12 | num: number;
13 | }
14 |
15 | export type EvQ = {
16 | head: any;
17 | tail: any;
18 | length: number;
19 | cache: any;
20 | nodeNum: number;
21 | add: any;
22 | removeSmallestLatencyFromGroup: any;
23 | updateRecencyOfExistingCache: any;
24 | }
25 |
26 | export type removedQueryKey = {
27 | latency: number;
28 | num: number;
29 | key: any;
30 | }
--------------------------------------------------------------------------------
/dist/1eb4c1e9323b71c1279e966e284e1416.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/oslabs-beta/Cachier/2e267d1765a9bf5f3d62e7a3c258ea0d3159ff9a/dist/1eb4c1e9323b71c1279e966e284e1416.gif
--------------------------------------------------------------------------------
/dist/29f842ac51c3b17ca07d25a7a09e9735.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/oslabs-beta/Cachier/2e267d1765a9bf5f3d62e7a3c258ea0d3159ff9a/dist/29f842ac51c3b17ca07d25a7a09e9735.png
--------------------------------------------------------------------------------
/dist/2de6e7ef4ae9042ffd405c8dcbea9aa2.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/oslabs-beta/Cachier/2e267d1765a9bf5f3d62e7a3c258ea0d3159ff9a/dist/2de6e7ef4ae9042ffd405c8dcbea9aa2.png
--------------------------------------------------------------------------------
/dist/2f4208e044c4212969a90d801ef2def8.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/oslabs-beta/Cachier/2e267d1765a9bf5f3d62e7a3c258ea0d3159ff9a/dist/2f4208e044c4212969a90d801ef2def8.png
--------------------------------------------------------------------------------
/dist/3734ecb3aeb09570f47139362d249be2.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/oslabs-beta/Cachier/2e267d1765a9bf5f3d62e7a3c258ea0d3159ff9a/dist/3734ecb3aeb09570f47139362d249be2.png
--------------------------------------------------------------------------------
/dist/5039e39a6d6ed8a3d80d43ca1d8c21ee.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/oslabs-beta/Cachier/2e267d1765a9bf5f3d62e7a3c258ea0d3159ff9a/dist/5039e39a6d6ed8a3d80d43ca1d8c21ee.png
--------------------------------------------------------------------------------
/dist/56ea5f568f82e563dfd64ee6a897ef81.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/oslabs-beta/Cachier/2e267d1765a9bf5f3d62e7a3c258ea0d3159ff9a/dist/56ea5f568f82e563dfd64ee6a897ef81.png
--------------------------------------------------------------------------------
/dist/6b51cea952681842ad6491b49cc56ac2.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/oslabs-beta/Cachier/2e267d1765a9bf5f3d62e7a3c258ea0d3159ff9a/dist/6b51cea952681842ad6491b49cc56ac2.png
--------------------------------------------------------------------------------
/dist/7615be16eed41f806def1ba19d38b46d.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/oslabs-beta/Cachier/2e267d1765a9bf5f3d62e7a3c258ea0d3159ff9a/dist/7615be16eed41f806def1ba19d38b46d.png
--------------------------------------------------------------------------------
/dist/7664632ce349168853d72e7bb255189b.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/oslabs-beta/Cachier/2e267d1765a9bf5f3d62e7a3c258ea0d3159ff9a/dist/7664632ce349168853d72e7bb255189b.png
--------------------------------------------------------------------------------
/dist/7a6a6c54279a54d1977c9127957a503b.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/oslabs-beta/Cachier/2e267d1765a9bf5f3d62e7a3c258ea0d3159ff9a/dist/7a6a6c54279a54d1977c9127957a503b.png
--------------------------------------------------------------------------------
/dist/852e2634ae6b8384f6fb01d089d92ff6.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/oslabs-beta/Cachier/2e267d1765a9bf5f3d62e7a3c258ea0d3159ff9a/dist/852e2634ae6b8384f6fb01d089d92ff6.png
--------------------------------------------------------------------------------
/dist/8b89ce01929a419c56256c919e9a61c5.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/oslabs-beta/Cachier/2e267d1765a9bf5f3d62e7a3c258ea0d3159ff9a/dist/8b89ce01929a419c56256c919e9a61c5.png
--------------------------------------------------------------------------------
/dist/8ee3e3d00d4f2f2b77e5cdf5c8d6fafd.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/oslabs-beta/Cachier/2e267d1765a9bf5f3d62e7a3c258ea0d3159ff9a/dist/8ee3e3d00d4f2f2b77e5cdf5c8d6fafd.png
--------------------------------------------------------------------------------
/dist/9a93576d1efc4d5a58034a34d531ec54.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/oslabs-beta/Cachier/2e267d1765a9bf5f3d62e7a3c258ea0d3159ff9a/dist/9a93576d1efc4d5a58034a34d531ec54.png
--------------------------------------------------------------------------------
/dist/9e46cba70d2c686f09a050cbce09c62b.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/oslabs-beta/Cachier/2e267d1765a9bf5f3d62e7a3c258ea0d3159ff9a/dist/9e46cba70d2c686f09a050cbce09c62b.png
--------------------------------------------------------------------------------
/dist/a8a8f596e6871332d41dd5a6117a6427.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/oslabs-beta/Cachier/2e267d1765a9bf5f3d62e7a3c258ea0d3159ff9a/dist/a8a8f596e6871332d41dd5a6117a6427.png
--------------------------------------------------------------------------------
/dist/a8dc608259608215f26a72fd33e96291.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/oslabs-beta/Cachier/2e267d1765a9bf5f3d62e7a3c258ea0d3159ff9a/dist/a8dc608259608215f26a72fd33e96291.png
--------------------------------------------------------------------------------
/dist/ac2e1e60fb0e4e4f77e02f9d9121c657.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/oslabs-beta/Cachier/2e267d1765a9bf5f3d62e7a3c258ea0d3159ff9a/dist/ac2e1e60fb0e4e4f77e02f9d9121c657.png
--------------------------------------------------------------------------------
/dist/bundle.js.LICENSE.txt:
--------------------------------------------------------------------------------
1 | /*!
2 | * @kurkle/color v0.2.1
3 | * https://github.com/kurkle/color#readme
4 | * (c) 2022 Jukka Kurkela
5 | * Released under the MIT License
6 | */
7 |
8 | /*!
9 | * Chart.js v3.9.1
10 | * https://www.chartjs.org
11 | * (c) 2022 Chart.js Contributors
12 | * Released under the MIT License
13 | */
14 |
15 | /*! regenerator-runtime -- Copyright (c) 2014-present, Facebook, Inc. -- license (MIT): https://github.com/facebook/regenerator/blob/main/LICENSE */
16 |
17 | /**
18 | * @license React
19 | * react-dom.production.min.js
20 | *
21 | * Copyright (c) Facebook, Inc. and its affiliates.
22 | *
23 | * This source code is licensed under the MIT license found in the
24 | * LICENSE file in the root directory of this source tree.
25 | */
26 |
27 | /**
28 | * @license React
29 | * react.production.min.js
30 | *
31 | * Copyright (c) Facebook, Inc. and its affiliates.
32 | *
33 | * This source code is licensed under the MIT license found in the
34 | * LICENSE file in the root directory of this source tree.
35 | */
36 |
37 | /**
38 | * @license React
39 | * scheduler.production.min.js
40 | *
41 | * Copyright (c) Facebook, Inc. and its affiliates.
42 | *
43 | * This source code is licensed under the MIT license found in the
44 | * LICENSE file in the root directory of this source tree.
45 | */
46 |
47 | /**
48 | * @remix-run/router v1.0.3
49 | *
50 | * Copyright (c) Remix Software Inc.
51 | *
52 | * This source code is licensed under the MIT license found in the
53 | * LICENSE.md file in the root directory of this source tree.
54 | *
55 | * @license MIT
56 | */
57 |
58 | /**
59 | * React Router DOM v6.4.3
60 | *
61 | * Copyright (c) Remix Software Inc.
62 | *
63 | * This source code is licensed under the MIT license found in the
64 | * LICENSE.md file in the root directory of this source tree.
65 | *
66 | * @license MIT
67 | */
68 |
69 | /**
70 | * React Router v6.4.3
71 | *
72 | * Copyright (c) Remix Software Inc.
73 | *
74 | * This source code is licensed under the MIT license found in the
75 | * LICENSE.md file in the root directory of this source tree.
76 | *
77 | * @license MIT
78 | */
79 |
--------------------------------------------------------------------------------
/dist/c2206b27d7cd6ce4c5fef6b5c60389a6.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/oslabs-beta/Cachier/2e267d1765a9bf5f3d62e7a3c258ea0d3159ff9a/dist/c2206b27d7cd6ce4c5fef6b5c60389a6.png
--------------------------------------------------------------------------------
/dist/index.html:
--------------------------------------------------------------------------------
1 | CacheQL
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "cache-money",
3 | "description": "Welcome to Cachier, a lightweight GraphQL caching tool that is configured specifically for GraphQL to reduce load times and minimize data fetching.",
4 | "version": "1.0.0",
5 | "main": "webpack.config.js",
6 | "scripts": {
7 | "start": "NODE_ENV=production npx ts-node ./demo/server/server.ts",
8 | "build": "webpack --mode production",
9 | "dev": "NODE_ENV=development webpack-dev-server --open & nodemon ./demo/server/server.ts"
10 | },
11 | "dependencies": {
12 | "@cachier/cache-partials": "^1.0.0",
13 | "@cachier/client-side": "^1.0.1",
14 | "@cachier/server-side": "^1.0.2",
15 | "@tailwindcss/typography": "^0.5.8",
16 | "babel-core": "^6.26.3",
17 | "chart.js": "^3.9.1",
18 | "concurrently": "^7.5.0",
19 | "cors": "^2.8.5",
20 | "daisyui": "^2.42.1",
21 | "depcheck": "^1.4.3",
22 | "dotenv": "^16.0.3",
23 | "express": "^4.18.2",
24 | "express-graphql": "^0.12.0",
25 | "mongoose": "^6.7.2",
26 | "node": "^19.0.1",
27 | "node-fetch": "^3.3.0",
28 | "prop-types": "^15.8.1",
29 | "react": "^18.2.0",
30 | "react-chartjs-2": "^4.3.1",
31 | "react-dom": "^18.2.0",
32 | "react-router-dom": "^6.4.3",
33 | "redis": "^4.5.0",
34 | "ts-node": "^10.9.1",
35 | "typescript": "^4.9.3"
36 | },
37 | "devDependencies": {
38 | "@babel/core": "^7.20.2",
39 | "@babel/preset-env": "^7.20.2",
40 | "@babel/preset-react": "^7.18.6",
41 | "autoprefixer": "^10.4.13",
42 | "babel-loader": "^9.1.0",
43 | "css-loader": "^6.7.1",
44 | "depcheck": "^1.4.3",
45 | "file-loader": "^6.2.0",
46 | "html-webpack-plugin": "^5.5.0",
47 | "jest": "^29.3.1",
48 | "nodemon": "^2.0.20",
49 | "postcss": "^8.4.19",
50 | "postcss-loader": "^7.0.1",
51 | "react-svg-loader": "^3.0.3",
52 | "sass-loader": "^13.1.0",
53 | "style-loader": "^3.3.1",
54 | "supertest": "^6.3.1",
55 | "tailwindcss": "^3.2.4",
56 | "ts-loader": "^9.4.1",
57 | "webpack": "^5.75.0",
58 | "webpack-cli": "^5.0.0",
59 | "webpack-dev-server": "^4.11.1"
60 | },
61 | "repository": {
62 | "type": "git",
63 | "url": "git+https://github.com/oslabs-beta/Cache-MoneyQL.git"
64 | },
65 | "keywords": [],
66 | "author": "",
67 | "license": "ISC",
68 | "bugs": {
69 | "url": "https://github.com/oslabs-beta/Cache-MoneyQL/issues"
70 | }
71 | }
72 |
--------------------------------------------------------------------------------
/postcss.config.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | plugins: [
3 | require('tailwindcss'),
4 | require('./tailwind.config.js'),
5 | require('autoprefixer'),
6 | ],
7 | };
8 |
--------------------------------------------------------------------------------
/tailwind.config.js:
--------------------------------------------------------------------------------
1 | /** @type {import('tailwindcss').Config} */
2 | module.exports = {
3 | content: ['./demo/**/*.{js,jsx,ts,tsx}'],
4 | theme: {
5 | fontFamily: {
6 | 'serif': ['Georgia, serif'],
7 | },
8 | extend: {},
9 | },
10 | daisyui: {
11 | styled: true,
12 | themes: true,
13 | darkTheme: 'dark',
14 | },
15 | plugins: [require('@tailwindcss/typography'), require('daisyui')],
16 | };
17 |
--------------------------------------------------------------------------------
/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | /* Visit https://aka.ms/tsconfig to read more about this file */
4 | /* Projects */
5 | // "incremental": true, /* Save .tsbuildinfo files to allow for incremental compilation of projects. */
6 | // "composite": true, /* Enable constraints that allow a TypeScript project to be used with project references. */
7 | // "tsBuildInfoFile": "./.tsbuildinfo", /* Specify the path to .tsbuildinfo incremental compilation file. */
8 | // "disableSourceOfProjectReferenceRedirect": true, /* Disable preferring source files instead of declaration files when referencing composite projects. */
9 | // "disableSolutionSearching": true, /* Opt a project out of multi-project reference checking when editing. */
10 | // "disableReferencedProjectLoad": true, /* Reduce the number of projects loaded automatically by TypeScript. */
11 | /* Language and Environment */
12 | "target": "es2020", /* Set the JavaScript language version for emitted JavaScript and include compatible library declarations. */
13 | "lib": [
14 | "es2018",
15 | "esnext.asynciterable",
16 | "DOM"
17 | ], /* Specify a set of bundled library declaration files that describe the target runtime environment. */
18 | "jsx": "preserve", /* Specify what JSX code is generated. */
19 | "experimentalDecorators": true, /* Enable experimental support for TC39 stage 2 draft decorators. */
20 | "emitDecoratorMetadata": true, /* Emit design-type metadata for decorated declarations in source files. */
21 | // "jsxFactory": "", /* Specify the JSX factory function used when targeting React JSX emit, e.g. 'React.createElement' or 'h'. */
22 | // "jsxFragmentFactory": "", /* Specify the JSX Fragment reference used for fragments when targeting React JSX emit e.g. 'React.Fragment' or 'Fragment'. */
23 | // "jsxImportSource": "", /* Specify module specifier used to import the JSX factory functions when using 'jsx: react-jsx*'. */
24 | // "reactNamespace": "", /* Specify the object invoked for 'createElement'. This only applies when targeting 'react' JSX emit. */
25 | // "noLib": true, /* Disable including any library files, including the default lib.d.ts. */
26 | // "useDefineForClassFields": true, /* Emit ECMAScript-standard-compliant class fields. */
27 | // "moduleDetection": "auto", /* Control what method is used to detect module-format JS files. */
28 | /* Modules */
29 | "module": "commonjs", /* Specify what module code is generated. */
30 | // "rootDir": "./", /* Specify the root folder within your source files. */
31 | // "moduleResolution": "node", /* Specify how TypeScript looks up a file from a given module specifier. */
32 | // "baseUrl": "./", /* Specify the base directory to resolve non-relative module names. */
33 | // "paths": {}, /* Specify a set of entries that re-map imports to additional lookup locations. */
34 | // "rootDirs": [], /* Allow multiple folders to be treated as one when resolving modules. */
35 | // "typeRoots": [], /* Specify multiple folders that act like './node_modules/@types'. */
36 | // "types": [], /* Specify type package names to be included without being referenced in a source file. */
37 | // "allowUmdGlobalAccess": true, /* Allow accessing UMD globals from modules. */
38 | // "moduleSuffixes": [], /* List of file name suffixes to search when resolving a module. */
39 | // "resolveJsonModule": true, /* Enable importing .json files. */
40 | // "noResolve": true, /* Disallow 'import's, 'require's or ''s from expanding the number of files TypeScript should add to a project. */
41 | /* JavaScript Support */
42 | // "allowJs": true, /* Allow JavaScript files to be a part of your program. Use the 'checkJS' option to get errors from these files. */
43 | // "checkJs": true, /* Enable error reporting in type-checked JavaScript files. */
44 | // "maxNodeModuleJsDepth": 1, /* Specify the maximum folder depth used for checking JavaScript files from 'node_modules'. Only applicable with 'allowJs'. */
45 | /* Emit */
46 | // "declaration": true, /* Generate .d.ts files from TypeScript and JavaScript files in your project. */
47 | // "declarationMap": true, /* Create sourcemaps for d.ts files. */
48 | // "emitDeclarationOnly": true, /* Only output d.ts files and not JavaScript files. */
49 | // "sourceMap": true, /* Create source map files for emitted JavaScript files. */
50 | // "outFile": "./", /* Specify a file that bundles all outputs into one JavaScript file. If 'declaration' is true, also designates a file that bundles all .d.ts output. */
51 | // "outDir": "./", /* Specify an output folder for all emitted files. */
52 | // "removeComments": true, /* Disable emitting comments. */
53 | // "noEmit": true, /* Disable emitting files from a compilation. */
54 | // "importHelpers": true, /* Allow importing helper functions from tslib once per project, instead of including them per-file. */
55 | // "importsNotUsedAsValues": "remove", /* Specify emit/checking behavior for imports that are only used for types. */
56 | // "downlevelIteration": true, /* Emit more compliant, but verbose and less performant JavaScript for iteration. */
57 | // "sourceRoot": "", /* Specify the root path for debuggers to find the reference source code. */
58 | // "mapRoot": "", /* Specify the location where debugger should locate map files instead of generated locations. */
59 | // "inlineSourceMap": true, /* Include sourcemap files inside the emitted JavaScript. */
60 | // "inlineSources": true, /* Include source code in the sourcemaps inside the emitted JavaScript. */
61 | // "emitBOM": true, /* Emit a UTF-8 Byte Order Mark (BOM) in the beginning of output files. */
62 | // "newLine": "crlf", /* Set the newline character for emitting files. */
63 | // "stripInternal": true, /* Disable emitting declarations that have '@internal' in their JSDoc comments. */
64 | // "noEmitHelpers": true, /* Disable generating custom helper functions like '__extends' in compiled output. */
65 | // "noEmitOnError": true, /* Disable emitting files if any type checking errors are reported. */
66 | // "preserveConstEnums": true, /* Disable erasing 'const enum' declarations in generated code. */
67 | // "declarationDir": "./", /* Specify the output directory for generated declaration files. */
68 | // "preserveValueImports": true, /* Preserve unused imported values in the JavaScript output that would otherwise be removed. */
69 | /* Interop Constraints */
70 | // "isolatedModules": true, /* Ensure that each file can be safely transpiled without relying on other imports. */
71 | // "allowSyntheticDefaultImports": true, /* Allow 'import x from y' when a module doesn't have a default export. */
72 | "esModuleInterop": true, /* Emit additional JavaScript to ease support for importing CommonJS modules. This enables 'allowSyntheticDefaultImports' for type compatibility. */
73 | // "preserveSymlinks": true, /* Disable resolving symlinks to their realpath. This correlates to the same flag in node. */
74 | "forceConsistentCasingInFileNames": true, /* Ensure that casing is correct in imports. */
75 | /* Type Checking */
76 | "strict": true, /* Enable all strict type-checking options. */
77 | // "noImplicitAny": true, /* Enable error reporting for expressions and declarations with an implied 'any' type. */
78 | // "strictNullChecks": true, /* When type checking, take into account 'null' and 'undefined'. */
79 | // "strictFunctionTypes": true, /* When assigning functions, check to ensure parameters and the return values are subtype-compatible. */
80 | // "strictBindCallApply": true, /* Check that the arguments for 'bind', 'call', and 'apply' methods match the original function. */
81 | // "strictPropertyInitialization": true, /* Check for class properties that are declared but not set in the constructor. */
82 | // "noImplicitThis": true, /* Enable error reporting when 'this' is given the type 'any'. */
83 | // "useUnknownInCatchVariables": true, /* Default catch clause variables as 'unknown' instead of 'any'. */
84 | // "alwaysStrict": true, /* Ensure 'use strict' is always emitted. */
85 | // "noUnusedLocals": true, /* Enable error reporting when local variables aren't read. */
86 | // "noUnusedParameters": true, /* Raise an error when a function parameter isn't read. */
87 | // "exactOptionalPropertyTypes": true, /* Interpret optional property types as written, rather than adding 'undefined'. */
88 | // "noImplicitReturns": true, /* Enable error reporting for codepaths that do not explicitly return in a function. */
89 | // "noFallthroughCasesInSwitch": true, /* Enable error reporting for fallthrough cases in switch statements. */
90 | // "noUncheckedIndexedAccess": true, /* Add 'undefined' to a type when accessed using an index. */
91 | // "noImplicitOverride": true, /* Ensure overriding members in derived classes are marked with an override modifier. */
92 | // "noPropertyAccessFromIndexSignature": true, /* Enforces using indexed accessors for keys declared using an indexed type. */
93 | // "allowUnusedLabels": true, /* Disable error reporting for unused labels. */
94 | // "allowUnreachableCode": true, /* Disable error reporting for unreachable code. */
95 | /* Completeness */
96 | // "skipDefaultLibCheck": true, /* Skip type checking .d.ts files that are included with TypeScript. */
97 | "skipLibCheck": true /* Skip type checking all .d.ts files. */
98 | },
99 | "files": [
100 | "demo/server/server.ts",
101 | "demo/server/DemoFunc.ts",
102 | "demo/types.ts"
103 | ],
104 | "include": ["./demo/", "__tests__/demoFuncTests.js"],
105 | "exclude": ["node_modules"]
106 | }
--------------------------------------------------------------------------------
/webpack.config.js:
--------------------------------------------------------------------------------
1 | const path = require('path');
2 | const HtmlWebpackPlugin = require('html-webpack-plugin');
3 |
4 | const config = {
5 | entry: './demo/client/index.js',
6 | output: {
7 | path: path.resolve(__dirname, 'dist'),
8 | filename: 'bundle.js',
9 | publicPath: '/',
10 | },
11 | mode: process.env.NODE_ENV,
12 | devServer: {
13 | historyApiFallback: true,
14 | static: {
15 | directory: path.join(__dirname, './dist'),
16 | publicPath: '/',
17 | },
18 | // port: 3000,
19 | proxy: {
20 | '/': 'http://localhost:3000/',
21 | },
22 | },
23 | module: {
24 | rules: [
25 | {
26 | test: /\.(js|jsx)$/,
27 | use: {
28 | loader: 'babel-loader',
29 | options: {
30 | presets: ['@babel/preset-env', '@babel/preset-react'],
31 | },
32 | },
33 | exclude: /node_modules/,
34 | },
35 | {
36 | test: /\.(ts|tsx)$/,
37 | use: ['ts-loader'],
38 | exclude: '/node_modules',
39 | },
40 | {
41 | test: /\.css$/,
42 | use: ['style-loader', 'css-loader', 'postcss-loader'],
43 | },
44 | {
45 | test: /\.scss$/,
46 | use: ['style-loader', 'css-loader', 'sass-loader'],
47 | },
48 | {
49 | test: /\.png|jpg|gif$/,
50 | use: ['file-loader'],
51 | },
52 | {
53 | test: /\.svg$/,
54 | use: [
55 | { loader: 'babel-loader' },
56 | {
57 | loader: 'react-svg-loader',
58 | options: {
59 | jsx: true,
60 | },
61 | },
62 | ],
63 | },
64 | ],
65 | },
66 | resolve: { extensions: ['*', '.js', '.jsx', '.ts', '.tsx'] },
67 | plugins: [new HtmlWebpackPlugin({ template: './demo/client/index.html' })],
68 | };
69 |
70 | module.exports = config;
71 |
--------------------------------------------------------------------------------