├── .DS_Store ├── .d.ts ├── .eslintrc ├── .gitignore ├── Dockerfile ├── LICENSE ├── README.md ├── __tests__ ├── IntrospectionQuery.js ├── localStorageMock.js ├── magnicache-client.test.ts ├── magnicache-demo.test.ts └── magnicache-server.test.ts ├── assets ├── OGLOGO.png ├── Readme-graph.jpg ├── RoundLogo.png └── readmegif.gif ├── client ├── .d.ts ├── App.tsx ├── assets │ ├── Logo.png │ ├── ahm.png │ ├── aria.png │ ├── tru.png │ └── you.png ├── components │ ├── CacheMetrics.tsx │ ├── NavBar.tsx │ ├── QueryDisplay.tsx │ ├── Response.tsx │ ├── Result.tsx │ └── VisualsDisplay.tsx ├── containers │ ├── AboutPage.tsx │ ├── DocsPage.tsx │ ├── MetricsContainer.tsx │ ├── TeamPage.tsx │ └── XMetrics.tsx ├── index.html ├── index.tsx ├── magnicache-client.js ├── package-lock.json ├── package.json └── scss │ └── styles.scss ├── jest.config.js ├── magnicache-client ├── README.md ├── magnicache-client.js ├── magnicache-client.ts ├── package-lock.json └── package.json ├── magnicache-demo ├── db-model.js ├── magnicache-server │ ├── IntrospectionQuery.js │ └── magnicache-server.js ├── package-lock.json ├── package.json ├── server.js └── types.js ├── magnicache-server ├── IntrospectionQuery.js ├── README.md ├── db │ └── index.js ├── magnicache-server.js ├── magnicache-server.ts ├── mutationAST.json ├── package-lock.json ├── package.json ├── schema.js ├── schema.json └── test.js ├── netlify.toml ├── package-lock.json ├── package.json ├── tsconfig.json ├── types.js ├── types.ts └── webpack.config.js /.DS_Store: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/oslabs-beta/MagniCache/df197f09249d5689b77f3c69d0b21a8c3c0e3643/.DS_Store -------------------------------------------------------------------------------- /.d.ts: -------------------------------------------------------------------------------- 1 | declare module 'react-dom'; 2 | declare module 'react-dom/client'; 3 | declare module '*.png'; 4 | declare module 'lodash.mergewith'; -------------------------------------------------------------------------------- /.eslintrc: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "airbnb", 3 | "root": true 4 | } 5 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | logs 3 | *.log 4 | npm-debug.log* 5 | yarn-debug.log* 6 | yarn-error.log* 7 | lerna-debug.log* 8 | 9 | # Diagnostic reports (https://nodejs.org/api/report.html) 10 | report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json 11 | 12 | # Runtime data 13 | pids 14 | *.pid 15 | *.seed 16 | *.pid.lock 17 | 18 | # Directory for instrumented libs generated by jscoverage/JSCover 19 | lib-cov 20 | 21 | # Coverage directory used by tools like istanbul 22 | coverage 23 | *.lcov 24 | 25 | # nyc test coverage 26 | .nyc_output 27 | 28 | # Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files) 29 | .grunt 30 | 31 | # Bower dependency directory (https://bower.io/) 32 | bower_components 33 | 34 | # node-waf configuration 35 | .lock-wscript 36 | 37 | # Compiled binary addons (https://nodejs.org/api/addons.html) 38 | build/Release 39 | 40 | # Dependency directories 41 | node_modules/ 42 | jspm_packages/ 43 | 44 | # TypeScript v1 declaration files 45 | typings/ 46 | 47 | # TypeScript cache 48 | *.tsbuildinfo 49 | 50 | # Optional npm cache directory 51 | .npm 52 | 53 | # Optional eslint cache 54 | .eslintcache 55 | 56 | # Microbundle cache 57 | .rpt2_cache/ 58 | .rts2_cache_cjs/ 59 | .rts2_cache_es/ 60 | .rts2_cache_umd/ 61 | 62 | # Optional REPL history 63 | .node_repl_history 64 | 65 | # Output of 'npm pack' 66 | *.tgz 67 | 68 | # Yarn Integrity file 69 | .yarn-integrity 70 | 71 | # dotenv environment variables file 72 | .env 73 | .env.test 74 | 75 | # parcel-bundler cache (https://parceljs.org/) 76 | .cache 77 | 78 | # Next.js build output 79 | .next 80 | 81 | # Nuxt.js build / generate output 82 | .nuxt 83 | dist 84 | 85 | # Gatsby files 86 | .cache/ 87 | # Comment in the public line in if your project uses Gatsby and *not* Next.js 88 | # https://nextjs.org/blog/next-9-1#public-directory-support 89 | # public 90 | 91 | # vuepress build output 92 | .vuepress/dist 93 | 94 | # Serverless directories 95 | .serverless/ 96 | 97 | # FuseBox cache 98 | .fusebox/ 99 | 100 | # DynamoDB Local files 101 | .dynamodb/ 102 | 103 | # TernJS port file 104 | .tern-port 105 | 106 | # Build folder 107 | build 108 | 109 | .ds_store -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | FROM node:latest 2 | WORKDIR /usr/src/app 3 | # COPY . ./package.json ./package-lock.json ./magnicache-demo/package.json ./magnicache-server/package.json ./client/package.json /app/ 4 | # COPY ./magnicache-client/package.json ./magnicache-client/package-lock.json /app/magnicache-client/ 5 | COPY . /usr/src/app 6 | RUN npm install 7 | RUN cd client && npm install 8 | # RUN cd magnicache-client && npm install 9 | RUN cd magnicache-demo && npm install 10 | # RUN cd magnicache-server && npm install 11 | RUN npm run build 12 | EXPOSE 3000 13 | ENTRYPOINT [ "node", "./magnicache-demo/server.js" ] 14 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2023 OSLabs Beta 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # MagniCache 2 | 3 |

4 | 5 |

6 | 7 | ## What is MagniCache? 8 | 9 | MagniCache is a lightweight and performant GraphQL caching solution. Packaged and shipped as two separate npm packages, MagniCache can be implemented seamlessly into projects as an Express middleware for server-side caching of queries and mutations, or as a localStorage manipulation device for client-side caching. 10 | 11 | ## Why use MagniCache? 12 | 13 | MagniCache is unique in its exceptional efficiency. MagniCache's caching algorithm methodically parses GraphQL queries and divides them into atomic components, allowing for systematic and fully coherent caching of queries and improvements in performance. Unlike other GraphQL caching layers, subsequent GraphQL queries do not have to be exact duplicates of cached queries in order to benefit from cached response speeds. In addition, MagniCache was developed with compactness as the priority, so you can rest assured that implementing MagniCache into your projects will add zero unnecessary bulk. 14 | 15 |

16 | 17 |

Caching of queries leads to nearly instantaneous response times!

18 |

19 | 20 | ## How to use MagniCache 21 | 22 | Click here to demo MagniCache! 23 | 24 | Type your GraphQL queries in the Query field on the left and click Run. Check out the query response and metrics below to observe caching in action! 25 | 26 |

27 | 28 |

29 | 30 |
31 | 32 | ## Installing MagniCache 33 | 34 | ### Server-Side Caching 35 | 36 | ```bash 37 | npm i @magnicache/server 38 | ``` 39 | 40 | ### Client-Side Caching 41 | 42 | ```bash 43 | npm i @magnicache/client 44 | ``` 45 | 46 | ## Documentation 47 | 48 | After deciding on how to implement MagniCache into your project, follow the links below for detailed installation steps. 49 | 50 | 54 | 55 | ## How to Contribute 56 | 57 | As an Open Source Product, we are always welcoming contributions! To be apart of MagniCache, you can follow the steps below: 58 | 59 | 1. Fork this repository, copying the `dev` branch. 60 | 61 | 2. Create your feature branch from `dev`. 62 | 63 | 3. Once you have finished contributing to your feature branch, add and commit all changes. 64 | 65 | 4. Locally merge your branch with with the `dev` branch. 66 | 67 | 5. Push your branch to GitHub and open a pull request. 68 | 69 | ## License 70 | 71 | MIT 72 | 73 | ## Contributors 74 | 75 | Ahmed Chami / Github / LinkedIn 76 | 77 | Aria Soltankhah / Github / LinkedIn 78 | 79 | Truman Miller / Github / LinkedIn 80 | 81 | Yousuf Elkhoga / Github / LinkedIn 82 | -------------------------------------------------------------------------------- /__tests__/IntrospectionQuery.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | IntrospectionQuery: `query IntrospectionQuery { 3 | __schema { 4 | 5 | queryType { name fields{name type{...TypeRef}} } 6 | mutationType { name fields{name type{...TypeRef}} } 7 | types { 8 | ...FullType 9 | } 10 | } 11 | } 12 | 13 | fragment FullType on __Type { 14 | kind 15 | name 16 | description 17 | 18 | fields(includeDeprecated: true) { 19 | name 20 | description 21 | args { 22 | ...InputValue 23 | } 24 | type { 25 | ...TypeRef 26 | } 27 | isDeprecated 28 | deprecationReason 29 | } 30 | inputFields { 31 | ...InputValue 32 | } 33 | interfaces { 34 | ...TypeRef 35 | } 36 | enumValues(includeDeprecated: true) { 37 | name 38 | description 39 | isDeprecated 40 | deprecationReason 41 | } 42 | possibleTypes { 43 | ...TypeRef 44 | } 45 | } 46 | 47 | fragment InputValue on __InputValue { 48 | name 49 | description 50 | type { ...TypeRef } 51 | defaultValue 52 | 53 | 54 | } 55 | 56 | fragment TypeRef on __Type { 57 | kind 58 | name 59 | ofType { 60 | kind 61 | name 62 | ofType { 63 | kind 64 | name 65 | ofType { 66 | kind 67 | name 68 | ofType { 69 | kind 70 | name 71 | ofType { 72 | kind 73 | name 74 | ofType { 75 | kind 76 | name 77 | ofType { 78 | kind 79 | name 80 | } 81 | } 82 | } 83 | } 84 | } 85 | } 86 | } 87 | }`, 88 | }; 89 | -------------------------------------------------------------------------------- /__tests__/localStorageMock.js: -------------------------------------------------------------------------------- 1 | class LocalStorageMock { 2 | constructor() { 3 | this.store = {}; 4 | } 5 | 6 | clear() { 7 | this.store = {}; 8 | } 9 | 10 | getItem(key) { 11 | return this.store[key] || null; 12 | } 13 | 14 | setItem(key, value) { 15 | this.store[key] = String(value); 16 | } 17 | 18 | removeItem(key) { 19 | delete this.store[key]; 20 | } 21 | } 22 | 23 | module.exports = LocalStorageMock; 24 | -------------------------------------------------------------------------------- /__tests__/magnicache-client.test.ts: -------------------------------------------------------------------------------- 1 | const MagniClient = require('../magnicache-client/magnicache-client.ts'); 2 | const LocalStorageMock = require('./localStorageMock.js'); 3 | global.localStorage = new LocalStorageMock(); 4 | 5 | describe('MagniClient', () => { 6 | let magniClient: any; 7 | 8 | beforeEach(() => { 9 | localStorage.clear(); 10 | magniClient = new MagniClient(2); 11 | }); 12 | 13 | describe('constructor', () => { 14 | it('should instantiate the cache with the provided maxSize or default', () => { 15 | expect(magniClient.maxSize).toBe(2); 16 | }); 17 | 18 | it('should initialize the cache from localStorage, if available', () => { 19 | localStorage.setItem('MagniClient', JSON.stringify(['query1', 'query2'])); 20 | 21 | const newMagniClient = new MagniClient(); 22 | expect(newMagniClient.cache).toEqual(['query1', 'query2']); 23 | }); 24 | 25 | it('should not add duplicates to the cache', () => { 26 | localStorage.setItem( 27 | 'MagniClient', 28 | JSON.stringify(['query1', 'query2', 'query2']) 29 | ); 30 | const newMagniClient = new MagniClient(); 31 | 32 | expect(newMagniClient.cache).toEqual(['query1', 'query2']); 33 | }); 34 | 35 | // TODO: test for MagniClient's methods 36 | }); 37 | 38 | describe('set', () => { 39 | it('should store the provided query and value in localStorage', () => { 40 | magniClient.set('query1', { data: 'some data' }); 41 | 42 | expect(localStorage.getItem('query1')).toBe('{"data":"some data"}'); 43 | }); 44 | 45 | it('should add the query to the cache', () => { 46 | magniClient.set('query1', { data: 'some data' }); 47 | 48 | expect(magniClient.cache).toEqual(['query1']); 49 | }); 50 | 51 | it('should remove the least recently used query from the cache if the cache is at maxSize', () => { 52 | magniClient.set('query1', { data: 'some data' }); 53 | magniClient.set('query2', { data: 'some more data' }); 54 | magniClient.set('query3', { data: 'even more data' }); 55 | 56 | expect(magniClient.cache).toEqual(['query2', 'query3']); 57 | expect(localStorage.getItem('query1')).toBe(null); 58 | }); 59 | }); 60 | 61 | describe('get', () => { 62 | it('should return the value stored in localStorage for the provided query', () => { 63 | localStorage.setItem('query1', '{"data":"some data"}'); 64 | 65 | expect(magniClient.get('query1')).toEqual({ data: 'some data' }); 66 | }); 67 | 68 | it('should move the query to the back of the cache', () => { 69 | magniClient.set('query1', { data: 'some data' }); 70 | magniClient.set('query2', { data: 'some more data' }); 71 | 72 | magniClient.get('query1'); 73 | expect(magniClient.cache).toEqual(['query2', 'query1']); 74 | }); 75 | 76 | it('should return an empty object if the query is not in localStorage', () => { 77 | expect(magniClient.get('query1')).toEqual({}); 78 | }); 79 | }); 80 | }); 81 | 82 | describe('MagniClient.magniParser', () => { 83 | let magniClient: any; 84 | 85 | beforeAll(() => { 86 | magniClient = new MagniClient(); 87 | }); 88 | 89 | it('should parse single selection without arguments', () => { 90 | const selections = [ 91 | { 92 | kind: 'Field', 93 | name: { 94 | kind: 'Name', 95 | value: 'allMessages', 96 | }, 97 | arguments: [], 98 | }, 99 | ]; 100 | 101 | const result = magniClient.magniParser(selections); 102 | expect(result).toEqual(['{allMessages}']); 103 | }); 104 | 105 | it('should parse single selection with arguments', () => { 106 | const selections = [ 107 | { 108 | kind: 'Field', 109 | name: { 110 | kind: 'Name', 111 | value: 'messageById', 112 | }, 113 | arguments: [ 114 | { 115 | kind: 'Argument', 116 | name: { 117 | kind: 'Name', 118 | value: 'id', 119 | }, 120 | value: { 121 | kind: 'StringValue', 122 | value: '4', 123 | }, 124 | }, 125 | ], 126 | }, 127 | ]; 128 | 129 | const result = magniClient.magniParser(selections); 130 | expect(result).toEqual(['{messageById(id:4)}']); 131 | }); 132 | 133 | it('should parse nested selections without arguments', () => { 134 | const selections = [ 135 | { 136 | kind: 'Field', 137 | name: { 138 | kind: 'Name', 139 | value: 'allMessages', 140 | }, 141 | arguments: [], 142 | selectionSet: { 143 | kind: 'SelectionSet', 144 | selections: [ 145 | { 146 | kind: 'Field', 147 | name: { 148 | kind: 'Name', 149 | value: 'message', 150 | }, 151 | arguments: [], 152 | }, 153 | ], 154 | }, 155 | }, 156 | ]; 157 | 158 | const result = magniClient.magniParser(selections); 159 | expect(result).toEqual(['{allMessages{message}}']); 160 | }); 161 | 162 | it('should parse complex nested selections with arguments', () => { 163 | const selections = [ 164 | { 165 | kind: 'Field', 166 | name: { 167 | kind: 'Name', 168 | value: 'messageById', 169 | }, 170 | arguments: [ 171 | { 172 | kind: 'Argument', 173 | name: { 174 | kind: 'Name', 175 | value: 'id', 176 | }, 177 | value: { 178 | kind: 'StringValue', 179 | value: '4', 180 | }, 181 | }, 182 | ], 183 | selectionSet: { 184 | kind: 'SelectionSet', 185 | selections: [ 186 | { 187 | kind: 'Field', 188 | name: { 189 | kind: 'Name', 190 | value: 'message', 191 | }, 192 | arguments: [], 193 | }, 194 | ], 195 | }, 196 | }, 197 | ]; 198 | 199 | const result = magniClient.magniParser(selections); 200 | expect(result).toEqual(['{messageById(id:4){message}}']); 201 | }); 202 | 203 | it('should handle multiple selections', () => { 204 | const selections = [ 205 | { 206 | kind: 'Field', 207 | name: { 208 | kind: 'Name', 209 | value: 'allMessages', 210 | }, 211 | arguments: [], 212 | }, 213 | { 214 | kind: 'Field', 215 | name: { 216 | kind: 'Name', 217 | value: 'allUsers', 218 | }, 219 | arguments: [], 220 | }, 221 | ]; 222 | 223 | const result = magniClient.magniParser(selections); 224 | expect(result).toEqual(['{allMessages}', '{allUsers}']); 225 | }); 226 | }); 227 | -------------------------------------------------------------------------------- /__tests__/magnicache-demo.test.ts: -------------------------------------------------------------------------------- 1 | const request = require('supertest'); 2 | // const express = require('express'); 3 | const app = require('../magnicache-demo/server.js'); 4 | const { IntrospectionQuery } = require('./IntrospectionQuery.js'); 5 | 6 | describe('root Endpoint', () => { 7 | it('should respond with a html file', async () => { 8 | const response = await request(app) 9 | .get('/') 10 | .send() 11 | .set('Accept', 'application/html') 12 | .expect('Content-Type', /html/) 13 | .expect(200); 14 | }); 15 | }); 16 | 17 | describe('Graphql Endpoint', () => { 18 | it('should be a valid graphql endpoint', async () => { 19 | const response = await request(app) 20 | .post('/graphql') 21 | .send({ query: IntrospectionQuery }) 22 | .set('Accept', 'application/json') 23 | .expect('Content-Type', /json/) 24 | .expect(200); 25 | 26 | expect(response.body.data).toBeDefined(); 27 | }); 28 | }); 29 | -------------------------------------------------------------------------------- /__tests__/magnicache-server.test.ts: -------------------------------------------------------------------------------- 1 | const MagniCache = require('../magnicache-server/magnicache-server.ts'); 2 | const schema = require('../magnicache-server/schema.js'); 3 | import { NextFunction, Request, Response } from 'express'; 4 | const { IntrospectionQuery } = require('./IntrospectionQuery'); 5 | const { graphql } = require('graphql'); 6 | 7 | //TODO: Set up proper error handling for invalid params 8 | describe('MagniCache Setup', () => { 9 | let magnicache: any; 10 | beforeEach((done) => { 11 | magnicache = new MagniCache(schema); 12 | done(); 13 | }); 14 | 15 | it('Should return a new magnicache object of type magnicache-server when invoked with a valid schema', () => { 16 | expect(magnicache).toBeInstanceOf(MagniCache); 17 | }); 18 | 19 | it('Should have a query function on its prototype', () => { 20 | expect(typeof magnicache.query).toBe('function'); 21 | }); 22 | 23 | it('Should have a schemaParser function on its prototype', () => { 24 | expect(typeof magnicache.schemaParser).toBe('function'); 25 | }); 26 | }); 27 | 28 | describe('Magnicache.query execution', () => { 29 | let magnicache: any; 30 | let mockReq: Partial; 31 | let mockRes: Partial; 32 | let nextFn: NextFunction = jest.fn(); 33 | 34 | beforeEach((done) => { 35 | magnicache = new MagniCache(schema); 36 | mockReq = {}; 37 | mockRes = { 38 | json: jest.fn(), 39 | cookie: jest.fn(), 40 | locals: {}, 41 | }; 42 | done(); 43 | }); 44 | 45 | //TODO: make the error handling for this better 46 | it('responds when missing query property', async () => { 47 | const expectedRes = { 48 | queryResponse: 'Invalid query', 49 | }; 50 | 51 | mockReq = { 52 | body: {}, 53 | }; 54 | 55 | await magnicache.query(mockReq as Request, mockRes as Response, nextFn); 56 | expect(mockRes.locals!.queryResponse).toBe(expectedRes.queryResponse); 57 | }); 58 | 59 | it('responds with a message when query value is empty', () => { 60 | const expectedRes = { 61 | queryResponse: 'Invalid query', 62 | }; 63 | 64 | mockReq = { 65 | body: { query: '' }, 66 | }; 67 | 68 | magnicache.query(mockReq as Request, mockRes as Response, nextFn); 69 | expect(mockRes.locals!.queryResponse).toBe(expectedRes.queryResponse); 70 | }); 71 | 72 | it('responds with a schema when query value is the Introspection query', async () => { 73 | const expectedRes = { 74 | queryResponse: { data: { __schema: {} } }, 75 | }; 76 | 77 | mockReq = { 78 | body: { query: IntrospectionQuery }, 79 | }; 80 | 81 | await magnicache.query(mockReq as Request, mockRes as Response, nextFn); 82 | expect(mockRes.locals!.queryResponse.data).toHaveProperty('__schema'); 83 | }); 84 | 85 | it('returns the right data when the query is one field ', async () => { 86 | // let data = await graphql(schema, 'query{customers{name}}'); 87 | 88 | const expectedRes = { 89 | locals: { queryResponse: 'data' }, 90 | }; 91 | 92 | const mockReq: Partial = { 93 | body: { query: 'query{customers{name}}' }, 94 | }; 95 | 96 | const mockRes: any = { 97 | json: jest.fn(), 98 | cookie: jest.fn(), 99 | locals: { queryResponse: '' }, 100 | }; 101 | 102 | await magnicache.query(mockReq as Request, mockRes as Response, nextFn); 103 | expect(mockRes.locals.queryResponse).toEqual( 104 | expectedRes.locals.queryResponse 105 | ); 106 | }); 107 | }); 108 | 109 | describe('MagniParser', () => { 110 | let magnicache: any; 111 | beforeEach(() => { 112 | magnicache = new MagniCache(schema); 113 | }); 114 | 115 | it('should parse a simple query without arguments', () => { 116 | const selections = [ 117 | { 118 | kind: 'Field', 119 | name: { 120 | kind: 'Name', 121 | value: 'allMessages', 122 | }, 123 | arguments: [], 124 | }, 125 | ]; 126 | 127 | const result = magnicache.magniParser(selections); 128 | expect(result).toEqual(['{allMessages}']); 129 | }); 130 | 131 | it('should parse a simple query with arguments', () => { 132 | const selections = [ 133 | { 134 | kind: 'Field', 135 | name: { 136 | kind: 'Name', 137 | value: 'messageById', 138 | }, 139 | arguments: [ 140 | { 141 | kind: 'Argument', 142 | name: { 143 | kind: 'Name', 144 | value: 'id', 145 | }, 146 | value: { 147 | kind: 'IntValue', 148 | value: '4', 149 | }, 150 | }, 151 | ], 152 | }, 153 | ]; 154 | 155 | const result = magnicache.magniParser(selections); 156 | expect(result).toEqual(['{messageById(id:4)}']); 157 | }); 158 | }); 159 | -------------------------------------------------------------------------------- /assets/OGLOGO.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/oslabs-beta/MagniCache/df197f09249d5689b77f3c69d0b21a8c3c0e3643/assets/OGLOGO.png -------------------------------------------------------------------------------- /assets/Readme-graph.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/oslabs-beta/MagniCache/df197f09249d5689b77f3c69d0b21a8c3c0e3643/assets/Readme-graph.jpg -------------------------------------------------------------------------------- /assets/RoundLogo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/oslabs-beta/MagniCache/df197f09249d5689b77f3c69d0b21a8c3c0e3643/assets/RoundLogo.png -------------------------------------------------------------------------------- /assets/readmegif.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/oslabs-beta/MagniCache/df197f09249d5689b77f3c69d0b21a8c3c0e3643/assets/readmegif.gif -------------------------------------------------------------------------------- /client/.d.ts: -------------------------------------------------------------------------------- 1 | declare module 'react-dom'; 2 | declare module 'react-dom/client'; 3 | declare module 'bootstrap'; 4 | declare module 'magnicache-client.js'; 5 | -------------------------------------------------------------------------------- /client/App.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { useState, useEffect, useContext } from 'react'; 3 | import NavBar from './components/NavBar'; 4 | import MetricsContainer from './containers/MetricsContainer'; 5 | import AboutPage from './containers/AboutPage'; 6 | import DocsPage from './containers/DocsPage'; 7 | import TeamPage from './containers/TeamPage'; 8 | import './scss/styles.scss'; 9 | 10 | import 'bootstrap/dist/css/bootstrap.min.css'; 11 | import { Routes, Route, useNavigate, Link } from 'react-router-dom'; 12 | 13 | // Create a type to easily add types to variables 14 | type Rtype = React.FC; 15 | 16 | const App: Rtype = () => { 17 | return ( 18 |
19 | 20 | 21 | } /> 22 | } /> 23 | } /> 24 | } /> 25 | 26 |
27 | ); 28 | }; 29 | 30 | export default App; 31 | -------------------------------------------------------------------------------- /client/assets/Logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/oslabs-beta/MagniCache/df197f09249d5689b77f3c69d0b21a8c3c0e3643/client/assets/Logo.png -------------------------------------------------------------------------------- /client/assets/ahm.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/oslabs-beta/MagniCache/df197f09249d5689b77f3c69d0b21a8c3c0e3643/client/assets/ahm.png -------------------------------------------------------------------------------- /client/assets/aria.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/oslabs-beta/MagniCache/df197f09249d5689b77f3c69d0b21a8c3c0e3643/client/assets/aria.png -------------------------------------------------------------------------------- /client/assets/tru.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/oslabs-beta/MagniCache/df197f09249d5689b77f3c69d0b21a8c3c0e3643/client/assets/tru.png -------------------------------------------------------------------------------- /client/assets/you.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/oslabs-beta/MagniCache/df197f09249d5689b77f3c69d0b21a8c3c0e3643/client/assets/you.png -------------------------------------------------------------------------------- /client/components/CacheMetrics.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { Pie, Doughnut } from 'react-chartjs-2'; 3 | import { CacheMetricsType } from '../../types'; 4 | 5 | // Define interface for the props that will be passed to the component 6 | interface CacheProps { 7 | // Specify the type of the metrics prop to be used in this component 8 | metrics: CacheMetricsType; 9 | } 10 | 11 | // Create a new component named CacheMetrics which takes the CacheProps object as an input and returns JSX (template) 12 | const CacheMetrics = (props: CacheProps) => { 13 | // Destructure the metrics object from CacheProps object to avoid unnecessary repetition 14 | const { metrics } = props; 15 | 16 | // Deconstruct different properties from the metrics object to use later on 17 | const { 18 | cacheUsage, 19 | sizeLeft, 20 | totalHits, 21 | totalMisses, 22 | AvgCacheTime, 23 | AvgMissTime, 24 | AvgMemAccTime, 25 | } = metrics; 26 | 27 | // Define usageData object used to create data representation using chart.js library 28 | const usageData = { 29 | labels: ['Space Used', 'Space Left'], 30 | datasets: [ 31 | { 32 | labels: ['Space Used', 'Space Left'], 33 | data: [cacheUsage, sizeLeft - cacheUsage], 34 | backgroundColor: ['#5b2af0', '#00CC99'], 35 | borderColor: ['white'], 36 | borderWidth: 1, 37 | maintainAspectRatio: true, 38 | responsive: true, 39 | }, 40 | ], 41 | }; 42 | 43 | // Define avgData object used to create data representation using chart.js library 44 | const avgData = { 45 | labels: ['Avg. Cached in ms', 'Avg. Uncached in ms'], 46 | datasets: [ 47 | { 48 | data: [AvgCacheTime, AvgMissTime], 49 | backgroundColor: ['#5b2af0', '#b3001b'], 50 | borderColor: ['white'], 51 | borderWidth: 1, 52 | maintainAspectRatio: true, 53 | responsive: true, 54 | }, 55 | ], 56 | }; 57 | 58 | // Return JSX template to display metrics data in the HTML structure with dynamically updated data based on the data passed 59 | return ( 60 |
61 |
Cache Capacity Used: {cacheUsage}
62 |

63 | 64 |
65 | Remaining Capacity: {sizeLeft - cacheUsage} 66 |
67 |

68 | 69 |
Total Hits: {totalHits}
70 |

71 |
72 |
Total Misses: {totalMisses}
73 |

74 | 75 |
76 | Average Miss Response Time: {Math.round(AvgMissTime)}ms 77 |
78 |

79 | 80 |
81 | Average Memory Access Time: {AvgMemAccTime}ms 82 |
83 |

84 |
85 | 86 | 87 |
88 | ); 89 | }; 90 | 91 | export default CacheMetrics; 92 | -------------------------------------------------------------------------------- /client/components/NavBar.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import Container from 'react-bootstrap/Container'; 3 | import Nav from 'react-bootstrap/Nav'; 4 | import Navbar from 'react-bootstrap/Navbar'; 5 | import { Link } from 'react-router-dom'; 6 | import logo from '../assets/Logo.png'; 7 | 8 | // NavBar created with Bootstrap Navbar, Container, .Brand, .Link 9 | const NavBar: React.FC = () => { 10 | return ( 11 | 12 | 13 | 14 | MagniCache 15 | 16 | 17 | 28 | 29 | 30 | ); 31 | }; 32 | 33 | export default NavBar; 34 | -------------------------------------------------------------------------------- /client/components/QueryDisplay.tsx: -------------------------------------------------------------------------------- 1 | import React, { useRef } from 'react'; 2 | import ToggleButton from 'react-bootstrap/ToggleButton'; 3 | import ToggleButtonGroup from 'react-bootstrap/ToggleButtonGroup'; 4 | import Result from './Result'; 5 | import XMetrics from '../containers/XMetrics'; 6 | import { Metrics } from '../../types'; 7 | 8 | // Create an interface for props to be destructered 9 | interface QueryProps { 10 | metrics: Metrics[]; 11 | queryValue: string; 12 | setQueryValue: React.Dispatch>; 13 | queryResponse: Object; 14 | fetchTime: number; 15 | handleClickRun: () => void; 16 | handleClickClear: () => void; 17 | handleClearCache: () => void; 18 | key: string; 19 | clientMode: boolean; 20 | setClientMode: React.Dispatch>; 21 | handleSwitchMode: () => void; 22 | } 23 | 24 | // Destructure all props from the props object 25 | const QueryDisplay = (props: QueryProps) => { 26 | const { 27 | queryValue, 28 | setQueryValue, 29 | queryResponse, 30 | handleClickClear, 31 | handleClickRun, 32 | handleClearCache, 33 | handleSwitchMode, 34 | clientMode, 35 | } = props; 36 | 37 | const textAreaRef = useRef(null); 38 | 39 | const handleKeyDown: React.KeyboardEventHandler = ( 40 | e: React.KeyboardEvent 41 | ): void => { 42 | if (e.key === 'Tab') { 43 | e.preventDefault(); 44 | 45 | const textArea = textAreaRef.current; 46 | if (!textArea) return; 47 | 48 | const { selectionStart, selectionEnd } = textArea; 49 | 50 | const newValue = 51 | textArea.value.substring(0, selectionStart) + 52 | ' ' + 53 | textArea.value.substring(selectionEnd); 54 | 55 | textArea.value = newValue; 56 | textArea.selectionStart = textArea.selectionEnd = selectionStart + 1; 57 | setQueryValue(newValue); 58 | } else if (e.key === '{') { 59 | const textArea = textAreaRef.current; 60 | if (!textArea) return; 61 | const { selectionStart, selectionEnd } = textArea; 62 | 63 | const newValue = 64 | textArea.value.substring(0, selectionStart) + 65 | '}' + 66 | textArea.value.substring(selectionEnd); 67 | textArea.value = newValue; 68 | textArea.selectionStart = textArea.selectionEnd = selectionStart; 69 | setQueryValue(newValue); 70 | } else if (e.key === '(') { 71 | const textArea = textAreaRef.current; 72 | if (!textArea) return; 73 | const { selectionStart, selectionEnd } = textArea; 74 | 75 | const newValue = 76 | textArea.value.substring(0, selectionStart) + 77 | ')' + 78 | textArea.value.substring(selectionEnd); 79 | textArea.value = newValue; 80 | textArea.selectionStart = textArea.selectionEnd = selectionStart; 81 | setQueryValue(newValue); 82 | } else if (e.key === 'Enter') { 83 | const textArea = textAreaRef.current; 84 | if (!textArea) return; 85 | const { selectionStart, selectionEnd } = textArea; 86 | if (textArea.value[selectionStart - 1] === '{') { 87 | const newValue = 88 | textArea.value.substring(0, selectionStart) + 89 | '\n' + 90 | textArea.value.substring(selectionEnd); 91 | textArea.value = newValue; 92 | textArea.selectionStart = textArea.selectionEnd = selectionStart; 93 | setQueryValue(newValue); 94 | } 95 | } 96 | }; 97 | 98 | return ( 99 |
100 |
101 |

Query

102 |
103 |