28 |
29 | ## Installation
30 |
31 | ### Quell-Client and Quell-Server
32 |
33 | Quell is divided into two npm packages:
34 |
35 | - Download @quell/client from npm in your terminal with `npm i @quell/client`
36 | - Download @quell/server from npm in your terminal with `npm i @quell/server`
37 |
38 | ## Features
39 |
40 | - IP-Rate Limiting to protect your server.
41 | - Successfully handling cache mutations with cache invalidation.
42 | - Quell/server offers optional depth and cost limiting middleware to protect your GraphQL endpoint! To use, please explore the [@quell/server readme](./quell-server/README.md).
43 | - Server-side cache now caches entire queries in instances where it is unable to cache individual datapoints.
44 | - Client-side caching utilizing JavaScript's built in Map data structure.
45 | - Client-side caching utilizing Least Recently Used (LRU) caching strategy.
46 | - Server-side caching utilizing a configurable Redis in-memory data store with batching.
47 | - Partial and exact match query caching.
48 | - Programmatic rebuilding of GraphQL queries to fetch only the minimum data necessary to complete the response based upon current cache contents.
49 | - A easy-to-use Chrome Developer Tools extension designed for Quell users. With this extension, users can:
50 | - Inspect and monitor the latency of client-side GraphQL/Quell requests.
51 | - Make and monitor the latency of GraphQL/Quell requests to a specified server endpoint.
52 | - View server-side cache data and contents, with the ability to manually clear the cache
53 | - Features require zero-to-minimal configuration and can work independently of `@quell/client` and `@quell/server`
54 |
55 |
56 |
57 | ### Demo
58 |
59 |
60 | The first bar represents an uncached client-side query and the second and third bars represent the response time of a cached request (0 ms).
61 |
62 |
63 |
64 |
65 |
66 | A demonstration of how Quell can be implemented to protect against costly network requests.
67 |
68 |
69 |
70 |
71 | Try Quell out [here](https://quell.dev/)
72 |
73 | #### Usage Notes
74 |
75 | - Currently, Quell can cache 1) query-type requests without variables or directives and 2) mutation-type requests (add, update, and delete) with cache invalidation implemented. Quell will still process other requests, but will not cache the responses.
76 |
77 | ### Quell Developer Tool
78 |
79 | Quell Developer Tool is currently available as a Chrome Developer Tools extension. The easiest way to get it is to [add it from the Chrome Web Store.](https://chrome.google.com/webstore/detail/quell-developer-tool/jnegkegcgpgfomoolnjjkmkippoellod)
80 |
81 | ## Documentation
82 |
83 | - [@quell/client README](./quell-client/README.md)
84 | - [@quell/server README](./quell-server/README.md)
85 | - [Quell Developer Tool README](./quell-extension/README.md)
86 | - [Quell Demo Repo](https://github.com/oslabs-beta/QuellDemo-ts-7.0)
87 |
88 | ### Contribute to Quell
89 |
90 | Interested in making a contribution to Quell? Find our open-source contribution guidelines [here](./CONTRIBUTING.md). Please feel free to also review the larger future direction for Quell in the Client and Server readme's.
91 |
92 | Thank you for your interest and support!
93 |
94 | -Team Quell
95 |
96 | ## Quell Contributors
97 |
98 | Accelerated by [OS Labs](https://github.com/open-source-labs) and developed by [Andrew Dai](https://github.com/andrewmdai), [Cassidy Komp](https://github.com/mimikomp), [Ian Weinholtz](https://github.com/itsHackinTime), [Stacey Lee](https://github.com/staceyjhlee), [Jonah Weinbum](https://github.com/jonahpw), [Justin Hua](https://github.com/justinfhua), [Lenny Yambao](https://github.com/lennin6), [Michael Lav](https://github.com/mikelav258), [Angelo Chengcuenca](https://github.com/amchengcuenca), [Emily Hoang](https://github.com/emilythoang), [Keely Timms](https://github.com/keelyt), [Yusuf Bhaiyat](https://github.com/yusuf-bha), [Hannah Spencer](https://github.com/Hannahspen), [Garik Asplund](https://github.com/garikAsplund), [Katie Sandfort](https://github.com/katiesandfort), [Sarah Cynn](https://github.com/cynnsarah), [Rylan Wessel](https://github.com/XpIose), [Alex Martinez](https://github.com/alexmartinez123), [Cera Barrow](https://github.com/cerab), [Jackie He](https://github.com/Jckhe), [Zoe Harper](https://github.com/ContraireZoe), [David Lopez](https://github.com/DavidMPLopez), [Sercan Tuna](https://github.com/srcntuna), [Idan Michael](https://github.com/IdanMichael), [Tom Pryor](https://github.com/Turmbeoz), [Chang Cai](https://github.com/ccai89), [Robert Howton](https://github.com/roberthowton), [Joshua Jordan](https://github.com/jjordan-90), [Jinhee Choi](https://github.com/jcroadmovie), [Nayan Parmar](https://github.com/nparmar1), [Tashrif Sanil](https://github.com/tashrifsanil), [Tim Frenzel](https://github.com/TimFrenzel), [Robleh Farah](https://github.com/farahrobleh), [Angela Franco](https://github.com/ajfranco18), [Ken Litton](https://github.com/kenlitton), [Thomas Reeder](https://github.com/nomtomnom), [Andrei Cabrera](https://github.com/Andreicabrerao), [Dasha Kondratenko](https://github.com/dasha-k), [Derek Sirola](https://github.com/dsirola1), [Xiao Yu Omeara](https://github.com/xyomeara), [Nick Kruckenberg](https://github.com/kruckenberg), [Mike Lauri](https://github.com/MichaelLauri), [Rob Nobile](https://github.com/RobNobile) and [Justin Jaeger](https://github.com/justinjaeger).
99 |
--------------------------------------------------------------------------------
/assets/Quell-B2.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/open-source-labs/Quell/cede780bdae7817c4a267e70d746796cc94e5325/assets/Quell-B2.png
--------------------------------------------------------------------------------
/assets/QuellDemo-Client-Side-Demo.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/open-source-labs/Quell/cede780bdae7817c4a267e70d746796cc94e5325/assets/QuellDemo-Client-Side-Demo.gif
--------------------------------------------------------------------------------
/assets/QuellDemo-Client-Side.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/open-source-labs/Quell/cede780bdae7817c4a267e70d746796cc94e5325/assets/QuellDemo-Client-Side.gif
--------------------------------------------------------------------------------
/assets/QuellDemo-Costly.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/open-source-labs/Quell/cede780bdae7817c4a267e70d746796cc94e5325/assets/QuellDemo-Costly.gif
--------------------------------------------------------------------------------
/assets/favicon-16x16.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/open-source-labs/Quell/cede780bdae7817c4a267e70d746796cc94e5325/assets/favicon-16x16.png
--------------------------------------------------------------------------------
/assets/favicon-32x32.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/open-source-labs/Quell/cede780bdae7817c4a267e70d746796cc94e5325/assets/favicon-32x32.png
--------------------------------------------------------------------------------
/assets/mstile-150x150.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/open-source-labs/Quell/cede780bdae7817c4a267e70d746796cc94e5325/assets/mstile-150x150.png
--------------------------------------------------------------------------------
/assets/updatedQuell-diagram.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/open-source-labs/Quell/cede780bdae7817c4a267e70d746796cc94e5325/assets/updatedQuell-diagram.png
--------------------------------------------------------------------------------
/pull_request_template.md:
--------------------------------------------------------------------------------
1 | **What is the problem you were trying to solve?**
2 |
3 |
4 |
5 | **What is your solution and why did you solve this problem the way you did?**
6 |
7 |
8 |
9 | **Provide any additional notes on testing this functionality:**
10 |
11 |
12 |
13 | **Screenshots / gifs of working solution:**
14 |
15 |
16 |
--------------------------------------------------------------------------------
/quell-client/.gitignore:
--------------------------------------------------------------------------------
1 | node_modules
2 | dump.rdb
3 | package-lock.json
4 | coverage
5 | dist/
6 | dist
7 | .tgz
--------------------------------------------------------------------------------
/quell-client/README.md:
--------------------------------------------------------------------------------
1 | [](https://github.com/open-source-labs/Quell/blob/master/LICENSE)
2 | 
3 | 
4 | [](https://github.com/open-source-labs/Quell/issues)
5 |
6 | # @quell/client
7 |
8 | @quell/client is an easy-to-implement JavaScript library providing a client-side caching solution and cache invalidation for GraphQL. Quell's schema-governed, type-level normalization algorithm caches GraphQL query responses as flattened key-value representations of the graph's nodes, making it possible to partially satisfy queries from the client-side cache storage, reformulate the query, and then fetch additional data from other APIs or databases.
9 |
10 | @quell/client is an open-source NPM package accelerated by [OS Labs](https://github.com/open-source-labs) and developed by [Cassidy Komp](https://github.com/mimikomp), [Andrew Dai](https://github.com/andrewmdai), [Stacey Lee](https://github.com/staceyjhlee), [Ian Weinholtz](https://github.com/itsHackinTime), [Angelo Chengcuenca](https://github.com/amchengcuenca), [Emily Hoang](https://github.com/emilythoang), [Keely Timms](https://github.com/keelyt), [Yusuf Bhaiyat](https://github.com/yusuf-bha), [David Lopez](https://github.com/DavidMPLopez), [Sercan Tuna](https://github.com/srcntuna), [Idan Michael](https://github.com/IdanMichael), [Tom Pryor](https://github.com/Turmbeoz), [Chang Cai](https://github.com/ccai89), [Robert Howton](https://github.com/roberthowton), [Joshua Jordan](https://github.com/jjordan-90), [Jinhee Choi](https://github.com/jcroadmovie), [Nayan Parmar](https://github.com/nparmar1), [Tashrif Sanil](https://github.com/tashrifsanil), [Tim Frenzel](https://github.com/TimFrenzel), [Robleh Farah](https://github.com/farahrobleh), [Angela Franco](https://github.com/ajfranco18), [Ken Litton](https://github.com/kenlitton), [Thomas Reeder](https://github.com/nomtomnom), [Andrei Cabrera](https://github.com/Andreicabrerao), [Dasha Kondratenko](https://github.com/dasha-k), [Derek Sirola](https://github.com/dsirola1), [Xiao Yu Omeara](https://github.com/xyomeara), [Nick Kruckenberg](https://github.com/kruckenberg), [Mike Lauri](https://github.com/MichaelLauri), [Rob Nobile](https://github.com/RobNobile), and [Justin Jaeger](https://github.com/justinjaeger).
11 |
12 | ## Installation
13 |
14 | Download @quell/client from npm in your terminal with `npm i @quell/client`.
15 | `@quell/client` will be added as a dependency to your package.json file.
16 |
17 | ## Implementation
18 |
19 | Let's take a look at a typical use case for @quell/client by re-writing a fetch request to a GraphQL endpoint.
20 |
21 | Sample code of fetch request without Quell:
22 |
23 | ```javascript
24 | const sampleQuery = `query {
25 | countries {
26 | id
27 | name
28 | cities {
29 | id
30 | name
31 | population
32 | }
33 | }
34 | }`
35 |
36 |
37 | fetch('/graphQL', {
38 | method: "POST",
39 | body: JSON.stringify(sampleQuery)
40 | })
41 |
42 | costOptions = {
43 | maxCost: 50,
44 | maxDepth: 10,
45 | ipRate: 5
46 | }
47 | ```
48 |
49 | To make that same request with Quell:
50 |
51 | 1. Import Quell with `import { Quellify } from '@quell/client/dist/Quellify'`
52 | 2. Instead of calling `fetch(endpoint)` and passing the query through the request body, replace with `Quellify(endpoint, query, costOptions)`
53 |
54 | - The `Quellify` method takes in three parameters
55 | 1. **_endpoint_** - your GraphQL endpoint as a string (ex. '/graphQL')
56 | 2. **_query_** - your GraphQL query as a string (ex. see sampleQuery, above)
57 | 3. **_costOptions_** - your cost limit, depth limit, and IP rate limit for your queries (ex. see costOptions, above)
58 | 4. **_mutationMap_** - maps mutation names to corresponding parts of the schema.
59 | *(For more information, see the Schema section in @quell/server [README file](https://github.com/open-source-labs/Quell/tree/master/quell-server))*
60 |
61 |
62 | And in the end , your Quell-powered GraphQL fetch would look like this:
63 |
64 | ```javascript
65 | Quellify('/graphQL', sampleQuery, costOptions, mutationMap)
66 | .then( /* use parsed response */ );
67 | ```
68 |
69 | Note: Quell will return a promise that resolves into an array with two elements. The first element will be a JS object containing your data; this is in the same form as the response found on the 'data' key of a typical GraphQL response `{ data: // response }`. The second element will be a boolean indicating whether or not the data was found in the client-side cache.
70 |
71 | That's it! You're now caching your GraphQL queries in the client-side cache storage.
72 |
73 | ### Usage Notes
74 |
75 | - @quell/client now client-side caching speed is 4-5 times faster than it used to be.
76 |
77 | - Currently, Quell can cache any non-mutative query. Quell will still process other requests, but all mutations will cause cache invalidation for the entire client-side cache. Please report edge cases, issues, and other user stories to us, we would be grateful to expand on Quells use cases!
78 |
79 | #### For information on @quell/server, please visit the corresponding [README file](https://github.com/open-source-labs/Quell/tree/master/quell-server).
80 |
--------------------------------------------------------------------------------
/quell-client/__tests__/quellify.test.d.ts:
--------------------------------------------------------------------------------
1 | export {};
2 |
--------------------------------------------------------------------------------
/quell-client/__tests__/quellify.test.js:
--------------------------------------------------------------------------------
1 | "use strict";
2 | var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
3 | function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
4 | return new (P || (P = Promise))(function (resolve, reject) {
5 | function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
6 | function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
7 | function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
8 | step((generator = generator.apply(thisArg, _arguments || [])).next());
9 | });
10 | };
11 | Object.defineProperty(exports, "__esModule", { value: true });
12 | const Quellify_1 = require("../src/Quellify");
13 | const defaultCostOptions = {
14 | maxCost: 5000,
15 | mutationCost: 5,
16 | objectCost: 2,
17 | scalarCost: 1,
18 | depthCostFactor: 1.5,
19 | maxDepth: 10,
20 | ipRate: 3
21 | };
22 | // Command to run jest tests:
23 | // npx jest client/src/quell-client/client-tests/__tests__/quellify.test.ts
24 | describe('Quellify', () => {
25 | beforeEach(() => {
26 | // Clear the client cache before each test
27 | (0, Quellify_1.clearCache)();
28 | });
29 | it('checks that caching queries is working correctly', () => __awaiter(void 0, void 0, void 0, function* () {
30 | const endPoint = 'http://localhost:3000/api/graphql';
31 | const query = 'query { artist(name: "Frank Ocean") { id name albums { id name } } }';
32 | const costOptions = defaultCostOptions;
33 | const [data, foundInCache] = yield (0, Quellify_1.Quellify)(endPoint, query, costOptions);
34 | // Assertion: the data should not be found in the cache
35 | expect(foundInCache).toBe(false);
36 | // Invoke Quellify on query again
37 | const [cachedData, updatedCache] = yield (0, Quellify_1.Quellify)(endPoint, query, costOptions);
38 | // Assertion: Cached data should be the same as the original query
39 | expect(cachedData).toBe(data);
40 | // Assertion: The boolean should return true if it is found in the cache
41 | expect(updatedCache).toEqual(true);
42 | }));
43 | it('should update the cache for edit mutation queries', () => __awaiter(void 0, void 0, void 0, function* () {
44 | const endPoint = 'http://localhost:3000/api/graphql';
45 | const addQuery = 'mutation { addCity(name: "San Diego", country: "United States") { id name } }';
46 | const costOptions = defaultCostOptions;
47 | // Perform add mutation query to the cache
48 | const [addMutationData, addMutationfoundInCache] = yield (0, Quellify_1.Quellify)(endPoint, addQuery, costOptions);
49 | // Get the cityId on the mutation query
50 | const cityId = addMutationData.addCity.id;
51 | const city = "Las Vegas";
52 | // Perform edit mutation on query to update the name
53 | const editQuery = `mutation { editCity(id: "${cityId}", name: "${city}", country: "United States") { id name } }`;
54 | const [editMutationData, editMutationDataFoundInCache] = yield (0, Quellify_1.Quellify)(endPoint, editQuery, costOptions);
55 | //Assertion: The first mutation query name should be updated by the second edit mutation
56 | expect(addMutationData.name).toEqual(editMutationData.name);
57 | }));
58 | it('should delete an item from the server and invalidate the cache', () => __awaiter(void 0, void 0, void 0, function* () {
59 | const endPoint = 'http://localhost:3000/api/graphql';
60 | const addQuery = 'mutation { addCity(name: "Irvine", country: "United States") { id name } }';
61 | const costOptions = defaultCostOptions;
62 | // Perform add mutation query to the server
63 | const [addMutationData, addMutationfoundInCache] = yield (0, Quellify_1.Quellify)(endPoint, addQuery, costOptions);
64 | // Get the cityId on the mutation query
65 | const cityId = addMutationData.addCity.id;
66 | // Perform a delete mutation on the city
67 | const deleteQuery = `mutation { deleteCity(id: "${cityId}") { id name } }`;
68 | const [deleteMutationData, deleteMutationDataFoundInCache] = yield (0, Quellify_1.Quellify)(endPoint, deleteQuery, costOptions);
69 | //Assertion: The item should be removed from the cache
70 | expect(deleteMutationDataFoundInCache).toBe(false);
71 | }));
72 | it('should evict the LRU item from cache if cache size is exceeded', () => __awaiter(void 0, void 0, void 0, function* () {
73 | const endPoint = 'http://localhost:3000/api/graphql';
74 | const costOptions = defaultCostOptions;
75 | const query1 = 'query { artist(name: "Frank Ocean") { id name albums { id name } } }';
76 | const query2 = 'query { country(name: "United States") { id name cities { id name attractions { id name } } } }';
77 | const query3 = 'mutation { addCity(name: "San Diego", country: "United States") { id name } }';
78 | // Invoke Quellify on each query to add to cache
79 | yield (0, Quellify_1.Quellify)(endPoint, query1, costOptions);
80 | yield (0, Quellify_1.Quellify)(endPoint, query2, costOptions);
81 | // Assertion: lruCache should contain the queries
82 | expect(Quellify_1.lruCache.has(query1)).toBe(true);
83 | expect(Quellify_1.lruCache.has(query2)).toBe(true);
84 | // Invoke Quellify again on third query to exceed max cache size
85 | yield (0, Quellify_1.Quellify)(endPoint, query3, costOptions);
86 | // Assertion: lruCache should evict the LRU item
87 | expect(Quellify_1.lruCache.has(query1)).toBe(false);
88 | // Assertion: lruCache should still contain the most recently used items
89 | expect(Quellify_1.lruCache.has(query2)).toBe(true);
90 | expect(Quellify_1.lruCache.has(query3)).toBe(true);
91 | }));
92 | });
93 |
--------------------------------------------------------------------------------
/quell-client/__tests__/quellify.test.ts:
--------------------------------------------------------------------------------
1 | import { Quellify, clearCache, lruCache } from '../src/Quellify';
2 | import { CostParamsType } from '../src/types';
3 |
4 | const defaultCostOptions: CostParamsType = {
5 | maxCost: 5000,
6 | mutationCost: 5,
7 | objectCost: 2,
8 | scalarCost: 1,
9 | depthCostFactor: 1.5,
10 | maxDepth: 10,
11 | ipRate: 3
12 | };
13 |
14 | // Command to run jest tests:
15 | // npx jest client/src/quell-client/client-tests/__tests__/quellify.test.ts
16 |
17 | describe('Quellify', () => {
18 | beforeEach(() => {
19 | // Clear the client cache before each test
20 | clearCache();
21 | });
22 |
23 | it('checks that caching queries is working correctly', async () => {
24 | const endPoint = 'http://localhost:3000/api/graphql';
25 | const query = 'query { artist(name: "Frank Ocean") { id name albums { id name } } }';
26 | const costOptions = defaultCostOptions;
27 | const [data, foundInCache] = await Quellify(endPoint, query, costOptions) as [any, boolean];
28 |
29 | // Assertion: the data should not be found in the cache
30 | expect(foundInCache).toBe(false);
31 |
32 | // Invoke Quellify on query again
33 | const [cachedData, updatedCache] = await Quellify(endPoint, query, costOptions) as [any, boolean];
34 | // Assertion: Cached data should be the same as the original query
35 | expect(cachedData).toBe(data);
36 | // Assertion: The boolean should return true if it is found in the cache
37 | expect(updatedCache).toEqual(true);
38 |
39 | });
40 |
41 | it('should update the cache for edit mutation queries', async () => {
42 | const endPoint = 'http://localhost:3000/api/graphql';
43 | const addQuery = 'mutation { addCity(name: "San Diego", country: "United States") { id name } }';
44 | const costOptions = defaultCostOptions;
45 |
46 | // Perform add mutation query to the cache
47 | const [addMutationData, addMutationfoundInCache] = await Quellify(endPoint, addQuery, costOptions) as [any, boolean];
48 | // Get the cityId on the mutation query
49 | const cityId = addMutationData.addCity.id;
50 | const city = "Las Vegas";
51 | // Perform edit mutation on query to update the name
52 | const editQuery = `mutation { editCity(id: "${cityId}", name: "${city}", country: "United States") { id name } }`;
53 | const [editMutationData, editMutationDataFoundInCache] = await Quellify(endPoint, editQuery, costOptions) as [any, boolean];
54 |
55 | //Assertion: The first mutation query name should be updated by the second edit mutation
56 | expect(addMutationData.name).toEqual(editMutationData.name);
57 |
58 | });
59 |
60 |
61 | it('should delete an item from the server and invalidate the cache', async () => {
62 | const endPoint = 'http://localhost:3000/api/graphql';
63 | const addQuery = 'mutation { addCity(name: "Irvine", country: "United States") { id name } }';
64 | const costOptions = defaultCostOptions;
65 |
66 | // Perform add mutation query to the server
67 | const [addMutationData, addMutationfoundInCache] = await Quellify(endPoint, addQuery, costOptions) as [any, boolean];
68 | // Get the cityId on the mutation query
69 | const cityId = addMutationData.addCity.id;
70 |
71 | // Perform a delete mutation on the city
72 | const deleteQuery = `mutation { deleteCity(id: "${cityId}") { id name } }`;
73 | const [deleteMutationData, deleteMutationDataFoundInCache] = await Quellify(endPoint, deleteQuery, costOptions) as [any, boolean];
74 |
75 | //Assertion: The item should be removed from the cache
76 | expect(deleteMutationDataFoundInCache).toBe(false);
77 |
78 | });
79 |
80 |
81 | it('should evict the LRU item from cache if cache size is exceeded', async () => {
82 | const endPoint = 'http://localhost:3000/api/graphql';
83 | const costOptions = defaultCostOptions;
84 | const query1 = 'query { artist(name: "Frank Ocean") { id name albums { id name } } }';
85 | const query2 = 'query { country(name: "United States") { id name cities { id name attractions { id name } } } }';
86 | const query3 = 'mutation { addCity(name: "San Diego", country: "United States") { id name } }';
87 |
88 |
89 | // Invoke Quellify on each query to add to cache
90 | await Quellify(endPoint, query1, costOptions);
91 | await Quellify(endPoint, query2, costOptions);
92 |
93 | // Assertion: lruCache should contain the queries
94 | expect(lruCache.has(query1)).toBe(true);
95 | expect(lruCache.has(query2)).toBe(true);
96 |
97 | // Invoke Quellify again on third query to exceed max cache size
98 | await Quellify(endPoint, query3, costOptions);
99 |
100 | // Assertion: lruCache should evict the LRU item
101 | expect(lruCache.has(query1)).toBe(false);
102 |
103 | // Assertion: lruCache should still contain the most recently used items
104 | expect(lruCache.has(query2)).toBe(true);
105 | expect(lruCache.has(query3)).toBe(true);
106 |
107 | });
108 |
109 | });
110 |
--------------------------------------------------------------------------------
/quell-client/eslintrc.json:
--------------------------------------------------------------------------------
1 | {
2 | "env": {
3 | "browser": true,
4 | "commonjs": true,
5 | "es2021": true,
6 | "node": true
7 | },
8 | "extends": [
9 | "eslint:recommended",
10 | "plugin:@typescript-eslint/eslint-recommended",
11 | "plugin:@typescript-eslint/recommended",
12 | "prettier",
13 | "plugin:prettier/recommended"
14 | ],
15 | "overrides": [],
16 | "parserOptions": {
17 | "ecmaVersion": "latest"
18 | },
19 | "plugins": ["@typescript-eslint", "prettier"],
20 | "rules": {
21 | "no-console": "off",
22 | "prefer-const": "warn",
23 | "quotes": ["warn", "single"],
24 | "semi": ["warn", "always"],
25 | "prettier/prettier": [
26 | "error",
27 | {
28 | "printWidth": 80,
29 | "semi": true,
30 | "singleQuote": true,
31 | "tabWidth": 2,
32 | "bracketSpacing": true,
33 | "trailingComma": "none"
34 | }
35 | ],
36 | "space-before-function-paren": 0
37 | }
38 | }
39 |
--------------------------------------------------------------------------------
/quell-client/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "@quell/client",
3 | "version": "9.0.2",
4 | "description": "Quell is an open-source NPM package providing a light-weight caching layer implementation and cache invalidation for GraphQL responses on both the client- and server-side. Use Quell to prevent redundant client-side API requests and to minimize costly server-side response latency.",
5 | "main": "./dist/Quellify.js",
6 | "types": "./dist/types.d.ts",
7 | "files": [
8 | "dist/**/*",
9 | "package.json",
10 | "README.md"
11 | ],
12 | "scripts": {
13 | "start": "node ./dist/Quellify.js"
14 | },
15 | "engines": {
16 | "node": ">=14.17.5",
17 | "npm": ">=8.1.0"
18 | },
19 | "author": "Quell",
20 | "license": "MIT",
21 | "repository": {
22 | "type": "git",
23 | "url": "https://github.com/open-source-labs/Quell/tree/main/quell-client"
24 | },
25 | "bugs": {
26 | "url": "https://github.com/open-source-labs/Quell/issues"
27 | },
28 | "keywords": [
29 | "cache-invalidation",
30 | "graphQL",
31 | "cache",
32 | "caching",
33 | "client-side",
34 | "quell"
35 | ],
36 | "contributors": [
37 | {
38 | "name": "Alexander Martinez",
39 | "url": "https://github.com/alexmartinez123"
40 | },
41 | {
42 | "name": "Cera Barrow",
43 | "url": "https://github.com/cerab"
44 | },
45 | {
46 | "name": "Jackie He",
47 | "url": "https://github.com/Jckhe"
48 | },
49 | {
50 | "name": "Zoe Harper",
51 | "url": "https://github.com/ContraireZoe"
52 | },
53 | {
54 | "name": "Alexander Martinez",
55 | "url": "https://github.com/alexmartinez123"
56 | },
57 | {
58 | "name": "Cera Barrow",
59 | "url": "https://github.com/cerab"
60 | },
61 | {
62 | "name": "Jackie He",
63 | "url": "https://github.com/Jckhe"
64 | },
65 | {
66 | "name": "Zoe Harper",
67 | "url": "https://github.com/ContraireZoe"
68 | },
69 | {
70 | "name": "Idan Michael",
71 | "url": "https://github.com/idanmichael"
72 | },
73 | {
74 | "name": "Sercan Tuna",
75 | "url": "https://github.com/srcntuna"
76 | },
77 | {
78 | "name": "Thomas Pryor",
79 | "url": " https://github.com/Turmbeoz"
80 | },
81 | {
82 | "name": "David Lopez",
83 | "url": "https://github.com/DavidMPLopez"
84 | },
85 | {
86 | "name": "Chang Cai",
87 | "url": "https://github.com/ccai89"
88 | },
89 | {
90 | "name": "Robert Howton",
91 | "url": "https://github.com/roberthowton"
92 | },
93 | {
94 | "name": "Joshua Jordan",
95 | "url": "https://github.com/jjordan-90"
96 | },
97 | {
98 | "name": "Jinhee Choi",
99 | "url": "https://github.com/jcroadmovie"
100 | },
101 | {
102 | "name": "Nayan Parmar",
103 | "url": "https://github.com/nparmar1"
104 | },
105 | {
106 | "name": "Tashrif Sanil",
107 | "url": "https://github.com/tashrifsanil"
108 | },
109 | {
110 | "name": "Tim Frenzel",
111 | "url": "(https://github.com/TimFrenzel"
112 | },
113 | {
114 | "name": "Thomas Reeder",
115 | "url": "https://github.com/nomtomnom"
116 | },
117 | {
118 | "name": "Ken Litton",
119 | "url": "https://github.com/kenlitton"
120 | },
121 | {
122 | "name": "Robleh Farah",
123 | "url": "https://github.com/farahrobleh"
124 | },
125 | {
126 | "name": "Angela Franco",
127 | "url": "https://github.com/ajfranco18"
128 | },
129 | {
130 | "name": "Andrei Cabrera",
131 | "url": "https://github.com/Andreicabrerao"
132 | },
133 | {
134 | "name": "Dasha Kondratenko",
135 | "url": "https://github.com/dasha-k"
136 | },
137 | {
138 | "name": "Derek Sirola",
139 | "url": "https://github.com/dsirola1"
140 | },
141 | {
142 | "name": "Xiao Yu Omeara",
143 | "url": "https://github.com/xyomeara"
144 | },
145 | {
146 | "name": "Mike Lauri",
147 | "url": "https://github.com/MichaelLauri"
148 | },
149 | {
150 | "name": "Rob Nobile",
151 | "url": "https://github.com/RobNobile"
152 | },
153 | {
154 | "name": "Justin Jaeger",
155 | "url": "https://github.com/justinjaeger"
156 | },
157 | {
158 | "name": "Nick Kruckenberg",
159 | "url": "https://github.com/kruckenberg"
160 | },
161 | {
162 | "name": "Cassidy Komp",
163 | "url": "https://github.com/mimikomp"
164 | },
165 | {
166 | "name": "Andrew Dai",
167 | "url": "https://github.com/andrewmdai"
168 | },
169 | {
170 | "name": "Stacey Lee",
171 | "url": "https://github.com/staceyjhlee"
172 | },
173 | {
174 | "name": "Ian Weinholtz",
175 | "url": "https://github.com/itsHackinTime"
176 | }
177 | ],
178 | "homepage": "https://www.quell.dev/",
179 | "devDependencies": {
180 | "@types/jest": "^29.5.2",
181 | "@types/node": "^18.15.3",
182 | "@typescript-eslint/eslint-plugin": "^5.54.1",
183 | "@typescript-eslint/parser": "^5.54.1",
184 | "eslint": "^8.35.0",
185 | "eslint-config-prettier": "^8.7.0",
186 | "eslint-config-standard-with-typescript": "^34.0.0",
187 | "eslint-plugin-import": "^2.27.5",
188 | "eslint-plugin-n": "^15.6.1",
189 | "eslint-plugin-prettier": "^4.2.1",
190 | "eslint-plugin-promise": "^6.1.1",
191 | "prettier": "^2.8.4",
192 | "typescript": "^4.9.5"
193 | },
194 | "dependencies": {
195 | "lru-cache": "^10.0.0"
196 | },
197 | "peerDependencies": {
198 | "graphql": "^16.7.1"
199 | }
200 | }
201 |
--------------------------------------------------------------------------------
/quell-client/src/types.ts:
--------------------------------------------------------------------------------
1 | import type {
2 | IntValueNode,
3 | FloatValueNode,
4 | StringValueNode,
5 | BooleanValueNode,
6 | EnumValueNode,
7 | OperationDefinitionNode,
8 | VariableDefinitionNode,
9 | FieldNode,
10 | FragmentSpreadNode,
11 | InlineFragmentNode,
12 | FragmentDefinitionNode,
13 | SchemaDefinitionNode,
14 | ScalarTypeDefinitionNode,
15 | ObjectTypeDefinitionNode,
16 | FieldDefinitionNode,
17 | InputValueDefinitionNode,
18 | InterfaceTypeDefinitionNode,
19 | UnionTypeDefinitionNode,
20 | EnumTypeDefinitionNode,
21 | EnumValueDefinitionNode,
22 | InputObjectTypeDefinitionNode,
23 | SchemaExtensionNode,
24 | ScalarTypeExtensionNode,
25 | ObjectTypeExtensionNode,
26 | InterfaceTypeExtensionNode,
27 | UnionTypeExtensionNode,
28 | EnumTypeExtensionNode,
29 | InputObjectTypeExtensionNode
30 | } from 'graphql';
31 |
32 | // Interface for prototype object type
33 | export interface ProtoObjType {
34 | [key: string]: string | boolean | null | ProtoObjType;
35 | }
36 |
37 | // Interface for fragments type
38 | export interface FragsType {
39 | [fragName: string]: {
40 | [fieldName: string]: boolean;
41 | };
42 | }
43 |
44 | // Interface for argument object type
45 | export interface ArgsObjType {
46 | [fieldName: string]: string | boolean | null;
47 | }
48 |
49 | // Interface for field arguments type
50 | export interface FieldArgsType {
51 | [fieldName: string]: AuxObjType;
52 | }
53 |
54 | // Interface for auxiliary object type
55 | export interface AuxObjType {
56 | __type?: string | boolean | null;
57 | __alias?: string | boolean | null;
58 | __args?: ArgsObjType | null;
59 | __id?: string | boolean | null;
60 | }
61 |
62 | // Define a type that represents a GraphQL node with directives
63 | export type GQLNodeWithDirectivesType =
64 | | OperationDefinitionNode
65 | | VariableDefinitionNode
66 | | FieldNode
67 | | FragmentSpreadNode
68 | | InlineFragmentNode
69 | | FragmentDefinitionNode
70 | | SchemaDefinitionNode
71 | | ScalarTypeDefinitionNode
72 | | ObjectTypeDefinitionNode
73 | | FieldDefinitionNode
74 | | InputValueDefinitionNode
75 | | InterfaceTypeDefinitionNode
76 | | UnionTypeDefinitionNode
77 | | EnumTypeDefinitionNode
78 | | EnumValueDefinitionNode
79 | | InputObjectTypeDefinitionNode
80 | | SchemaExtensionNode
81 | | ScalarTypeExtensionNode
82 | | ObjectTypeExtensionNode
83 | | InterfaceTypeExtensionNode
84 | | UnionTypeExtensionNode
85 | | EnumTypeExtensionNode
86 | | InputObjectTypeExtensionNode;
87 |
88 | // Define a type for valid argument node types
89 | export type ValidArgumentNodeType =
90 | | IntValueNode
91 | | FloatValueNode
92 | | StringValueNode
93 | | BooleanValueNode
94 | | EnumValueNode;
95 |
96 | // Interface for fields values type
97 | export interface FieldsValuesType {
98 | [fieldName: string]: boolean;
99 | }
100 |
101 | // Interface for fields object type
102 | export interface FieldsObjectType {
103 | [fieldName: string]: string | boolean | null | ArgsObjType;
104 | }
105 |
106 | // Interface for cost parameters type
107 | export interface CostParamsType {
108 | [key: string]: number | undefined;
109 | maxCost: number;
110 | mutationCost?: number;
111 | objectCost?: number;
112 | scalarCost?: number;
113 | depthCostFactor?: number;
114 | maxDepth: number;
115 | ipRate: number;
116 | }
117 |
118 | // Interface for map cache type
119 | export interface MapCacheType {
120 | data: JSONObject;
121 | fieldNames: string[];
122 | }
123 |
124 | // Interface for fetch object type
125 | export interface FetchObjType {
126 | method?: string;
127 | headers: { 'Content-Type': string };
128 | body: string;
129 | }
130 |
131 | // Interface for JSON object
132 | export interface JSONObject {
133 | [k: string]: JSONValue;
134 | }
135 |
136 | // Interface for JSON object with 'id' property
137 | export interface JSONObjectWithId {
138 | id?: string;
139 | }
140 |
141 | // Type for JSON value
142 | export type JSONValue = JSONObject | JSONArray | JSONPrimitive;
143 | type JSONPrimitive = number | string | boolean | null;
144 | type JSONArray = JSONValue[];
145 |
146 | // Type for client error
147 | export type ClientErrorType = {
148 | log: string;
149 | status: number;
150 | message: { err: string };
151 | };
152 |
153 | // Type for query response
154 | export type QueryResponse = {
155 | queryResponse: { data: JSONObject }
156 | }
--------------------------------------------------------------------------------
/quell-extension/.babelrc:
--------------------------------------------------------------------------------
1 | {
2 | "presets": ["@babel/preset-env",
3 | [
4 | "@babel/preset-react",
5 | {
6 | "runtime":"automatic"
7 | }
8 | ],
9 | "@babel/preset-typescript"
10 | ]
11 | }
--------------------------------------------------------------------------------
/quell-extension/.gitignore:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/open-source-labs/Quell/cede780bdae7817c4a267e70d746796cc94e5325/quell-extension/.gitignore
--------------------------------------------------------------------------------
/quell-extension/README.md:
--------------------------------------------------------------------------------
1 | [](https://github.com/open-source-labs/Quell/blob/master/LICENSE)
2 | 
3 | 
4 | [](https://github.com/open-source-labs/Quell/issues)
5 |
6 | # Quell Developer Tool
7 |
8 | The Quell Developer Tool is an easy-to-use Chrome Developer Tools extension designed for Quell users. With this extension, users can:
9 |
10 | - Inspect and monitor the latency of client-side GraphQL/Quell requests
11 | - View their GraphQL query execution context using our visualizer tool which includes resolution times of resolver fields
12 | - Make and monitor the latency of GraphQL/Quell requests to a specified server endpoint
13 | - View server-side cache data and contents, with the ability to manually clear the cache
14 |
15 | These features require zero-to-minimal configuration and can work independently of `@quell/client` and `@quell/server`, but are designed with the needs of Quell users especially in mind.
16 |
17 | The Quell Developer Tool is an open-source developer tool accelerated by [OS Labs](https://github.com/open-source-labs) and developed by [Jonah Weinbum](https://github.com/jonahpw), [Justin Hua](https://github.com/justinfhua), [Lenny Yambao](https://github.com/lennin6), [Michael Lav](https://github.com/mikelav258), [Angelo Chengcuenca](https://github.com/amchengcuenca), [Emily Hoang](https://github.com/emilythoang), [Keely Timms](https://github.com/keelyt), [Yusuf Bhaiyat](https://github.com/yusuf-bha), [Hannah Spencer](https://github.com/Hannahspen), [Garik Asplund](https://github.com/garikAsplund), [Katie Sandfort](https://github.com/katiesandfort), [Sarah Cynn](https://github.com/cynnsarah), [Rylan Wessel](https://github.com/XpIose), [Chang Cai](https://github.com/ccai89), [Robert Howton](https://github.com/roberthowton), [Joshua Jordan](https://github.com/jjordan-90), [Jinhee Choi](https://github.com/jcroadmovie), [Nayan Parmar](https://github.com/nparmar1), [Tashrif Sanil](https://github.com/tashrifsanil), [Tim Frenzel](https://github.com/TimFrenzel), [Robleh Farah](https://github.com/farahrobleh), [Angela Franco](https://github.com/ajfranco18), [Ken Litton](https://github.com/kenlitton), [Thomas Reeder](https://github.com/nomtomnom), [Andrei Cabrera](https://github.com/Andreicabrerao), [Dasha Kondratenko](https://github.com/dasha-k), [Derek Sirola](https://github.com/dsirola1), [Xiao Yu Omeara](https://github.com/xyomeara), [Nick Kruckenberg](https://github.com/kruckenberg), [Mike Lauri](https://github.com/MichaelLauri), [Rob Nobile](https://github.com/RobNobile) and [Justin Jaeger](https://github.com/justinjaeger).
18 |
19 | ## Installation
20 |
21 | The Quell Developer Tool is currently available as a Chrome Developer Tools extension. The easiest way to install it is to [add it from the Chrome Web Store.](https://chrome.google.com/webstore/detail/quell-developer-tool/jnegkegcgpgfomoolnjjkmkippoellod)
22 |
23 | The latest build can also be built from source and added manually as a Chrome extension. To build the latest version, execute the following commands:
24 |
25 | ```
26 | git clone https://github.com/open-source-labs/Quell.git Quell
27 | cd Quell/quell-extension
28 | npm install
29 | npm run build
30 | ```
31 |
32 | Then, in the Chrome Extensions Page (`chrome://extensions/`), click on "Load unpacked" and navigate to `.../Quell/quell-extension/dist/` and click "Select". (You may need to toggle on "Developer mode" to do this.) The extension should now be loaded and available in the Chrome Developer Tools.
33 |
34 | ## Usage and Configuration
35 |
36 | The Quell Developer Tool will work out-of-the-box as a GraphQL network monitor from its **Client** tab. Minimal configuration as described below is required to benefit from Quell Developer Tool's other features.
37 |
38 | ### Server
39 |
40 | To enable the features on the **Server** tab, navigate to the **Settings** tab and complete the following fields:
41 |
42 | - _GraphQL Route_. Your server's GraphQL endpoint (default: `http://localhost:3000`)
43 | - _Server Address_. The HTTP address of server (default: `/graphQL`)
44 |
45 | With this information the Quell Developer Tool will retrieve your GraphQL schema (and display it on the **Settings** tab) and permit you to make and view the latency of GraphQL queries from the **Server** tab.
46 |
47 | To enable the "Clear Cache" button, you can additionally specify a server endpoint configured with `@quell/server`'s `clearCache` middleware.
48 |
49 | - _Clear Cache Route_. Endpoint which `QuellCache.clearCache` middleware is configured (default: `/clearCache`)
50 |
51 | Here is an example configuration
52 |
53 | ```javascript
54 | app.get('/clearCache', quellCache.clearCache, (req, res) => {
55 | return res.status(200).send('Redis cache successfully cleared');
56 | });
57 | ```
58 |
59 | ### Cache
60 |
61 | The **Cache** tab will display data from the Redis-based `@quell/server` cache. For it to do so, Quell Developer Tool requires an endpoint at which `@quell/server`'s `getRedisInfo` is configured. This enpoint can be specified in the **Settings** tab:
62 |
63 | - _Redis Route_. Endpoint at which `QuellCache.getRedisInfo` is configured. (default: `/redis`)
64 |
65 | The `getRedisInfo` middleware accepts an options object with the following keys:
66 |
67 | - `getStats` (`true`/`false`) - return a suite of statistics from Redis cache
68 | - `getKeys` (`true`/`false`) - return a list of keys currently stored in Redis cache
69 | - `getValues` (`true`/`false`) - return list of keys from Redis cache with their values
70 |
71 | Here is an example configuration:
72 |
73 | ```javascript
74 | app.use(
75 | '/redis',
76 | ...quellCache.getRedisInfo({
77 | getStats: true,
78 | getKeys: true,
79 | getValues: true
80 | })
81 | );
82 | ```
83 |
84 | ### Usage Note
85 |
86 | Use of `QuellCache.getRedisInfo` requires `@quell/server` at version `2.3.1` or greater.
87 |
88 | ## More information
89 |
90 | For more on `@quell/client` and `@quell/server`, see their documentation:
91 |
92 | - [@quell/client README](../quell-client/README.md)
93 | - [@quell/server README](../quell-server/README.md)
94 |
--------------------------------------------------------------------------------
/quell-extension/__mocks__/fileTransformer.js:
--------------------------------------------------------------------------------
1 | module.exports = 'test-file-stub';
--------------------------------------------------------------------------------
/quell-extension/__mocks__/styleMock.js:
--------------------------------------------------------------------------------
1 | module.exports = {};
--------------------------------------------------------------------------------
/quell-extension/__tests__/extension.test.js:
--------------------------------------------------------------------------------
1 | /* eslint-disable no-undef */
2 | import React from 'react';
3 | import { render, fireEvent, screen } from '@testing-library/react';
4 | import { jest } from '@jest/globals';
5 | import { enableFetchMocks, fetchMock } from 'jest-fetch-mock';
6 | import '@testing-library/jest-dom';
7 | import userEvent from '@testing-library/user-event';
8 |
9 | // import App from '../src/pages/Panel/App';
10 | // import CacheTab from '../src/pages/Panel/Components/CacheTab';
11 | // import InputEditor from '../src/pages/Panel/Components/InputEditor';
12 | // import Metrics from '../src/pages/Panel/Components/Metrics';
13 | // import NavButton from '../src/pages/Panel/Components/NavButton';
14 | // import OutputEditor from '../src/pages/Panel/Components/OutputEditor';
15 | // import PrimaryNavBar from '../src/pages/Panel/Components/PrimaryNavBar';
16 | // import QueryTab from '../src/pages/Panel/Components/QueryTab';
17 | // import Settings from '../src/pages/Panel/Components/Settings';
18 | import { act } from 'react-dom/test-utils';
19 |
20 | enableFetchMocks();
21 |
22 | //workaround for TypeError: range(...).getBoundingClientRect is not a function
23 | document.createRange = () => {
24 | const range = new Range();
25 |
26 | range.getBoundingClientRect = jest.fn();
27 |
28 | range.getClientRects = () => {
29 | return {
30 | item: () => null,
31 | length: 0,
32 | [Symbol.iterator]: jest.fn(),
33 | };
34 | };
35 |
36 | return range;
37 | };
38 |
39 | describe('App', () => {
40 | it('renders App component correctly', () => {
41 | fetch.mockResponseOnce(
42 | JSON.stringify({
43 | data: {
44 | __schema: { types: [{ name: 'String' }] },
45 | },
46 | })
47 | );
48 | render();
49 |
50 | const tabs = screen.queryAllByRole('button', /tab/i);
51 | expect(tabs).toHaveLength(10);
52 | });
53 |
54 | it('renders correct component when tab is clicked', () => {
55 | const app = render();
56 | const querybtn = app.container.querySelector('#queryButton');
57 | const networkbtn = app.container.querySelector('#networkButton');
58 | const cachebtn = app.container.querySelector('#cacheButton');
59 | const settingsbtn = app.container.querySelector('#settingsButton');
60 |
61 | fireEvent.click(networkbtn);
62 | expect(activeTab).toEqual('network');
63 | fireEvent.click(cachebtn);
64 | expect(activeTab).toEqual('cache');
65 | fireEvent.click(settingsbtn);
66 | expect(activeTab).toEqual('settings');
67 | fireEvent.click(querybtn);
68 | expect(activeTab).toEqual('query');
69 | });
70 | });
71 | //test the nav button
72 |
73 | describe('CacheTab', () => {
74 | it('renders CacheTab component correctly', () => {
75 | act(() => {
76 | fetch.mockResponseOnce(
77 | JSON.stringify({
78 | server: [
79 | {
80 | name: 'Redis version',
81 | },
82 | ],
83 | })
84 | );
85 | render();
86 | });
87 | });
88 | });
89 |
--------------------------------------------------------------------------------
/quell-extension/babel.config.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | presets: [
3 | ['@babel/preset-env', {targets: {node: 'current'}}],
4 | '@babel/preset-typescript',
5 | ],
6 | };
--------------------------------------------------------------------------------
/quell-extension/dist/assets/icon128.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/open-source-labs/Quell/cede780bdae7817c4a267e70d746796cc94e5325/quell-extension/dist/assets/icon128.png
--------------------------------------------------------------------------------
/quell-extension/dist/assets/icon16.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/open-source-labs/Quell/cede780bdae7817c4a267e70d746796cc94e5325/quell-extension/dist/assets/icon16.png
--------------------------------------------------------------------------------
/quell-extension/dist/assets/icon32.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/open-source-labs/Quell/cede780bdae7817c4a267e70d746796cc94e5325/quell-extension/dist/assets/icon32.png
--------------------------------------------------------------------------------
/quell-extension/dist/assets/icon48.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/open-source-labs/Quell/cede780bdae7817c4a267e70d746796cc94e5325/quell-extension/dist/assets/icon48.png
--------------------------------------------------------------------------------
/quell-extension/dist/assets/search.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/open-source-labs/Quell/cede780bdae7817c4a267e70d746796cc94e5325/quell-extension/dist/assets/search.png
--------------------------------------------------------------------------------
/quell-extension/dist/background.bundle.js:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/open-source-labs/Quell/cede780bdae7817c4a267e70d746796cc94e5325/quell-extension/dist/background.bundle.js
--------------------------------------------------------------------------------
/quell-extension/dist/devtools.bundle.js:
--------------------------------------------------------------------------------
1 | chrome.devtools.panels.create("Quell",null,"panel.html");
--------------------------------------------------------------------------------
/quell-extension/dist/devtools.html:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/quell-extension/dist/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 | Quell Devtools
7 |
8 |
9 |
10 |
11 |
12 |
--------------------------------------------------------------------------------
/quell-extension/dist/manifest.json:
--------------------------------------------------------------------------------
1 | {"description":"Developer tool for the Quell JavaScript library: https://quell.dev","version":"2.0","manifest_version":3,"name":"Quell Developer Tool","homepage_url":"https://quell.dev","author":"Michael Lav, Lenny Yambao, Jonah Weinbaum, Justin Hua, Chang Cai, Robert Howton, Joshua Jordan, Angelo Chengcuenca, Emily Hoang, Keely Timms, Yusuf Bhaiyat","action":{"default_icon":{"16":"./assets/icon16.png","48":"./assets/icon48.png","128":"./assets/icon128.png"},"default_title":"Quell Developer Tool"},"devtools_page":"devtools.html","icons":{"16":"./assets/icon16.png","48":"./assets/icon48.png","128":"./assets/icon128.png"},"background":{"service_worker":"background.bundle.js","type":"module"}}
--------------------------------------------------------------------------------
/quell-extension/dist/panel.bundle.js.LICENSE.txt:
--------------------------------------------------------------------------------
1 | /*
2 | object-assign
3 | (c) Sindre Sorhus
4 | @license MIT
5 | */
6 |
7 | /**
8 | * @license React
9 | * use-sync-external-store-shim.production.min.js
10 | *
11 | * Copyright (c) Facebook, Inc. and its affiliates.
12 | *
13 | * This source code is licensed under the MIT license found in the
14 | * LICENSE file in the root directory of this source tree.
15 | */
16 |
17 | /**
18 | * @license React
19 | * use-sync-external-store-shim/with-selector.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 | /** @license React v0.20.2
28 | * scheduler.production.min.js
29 | *
30 | * Copyright (c) Facebook, Inc. and its affiliates.
31 | *
32 | * This source code is licensed under the MIT license found in the
33 | * LICENSE file in the root directory of this source tree.
34 | */
35 |
36 | /** @license React v17.0.2
37 | * react-dom.production.min.js
38 | *
39 | * Copyright (c) Facebook, Inc. and its affiliates.
40 | *
41 | * This source code is licensed under the MIT license found in the
42 | * LICENSE file in the root directory of this source tree.
43 | */
44 |
45 | /** @license React v17.0.2
46 | * react-jsx-runtime.production.min.js
47 | *
48 | * Copyright (c) Facebook, Inc. and its affiliates.
49 | *
50 | * This source code is licensed under the MIT license found in the
51 | * LICENSE file in the root directory of this source tree.
52 | */
53 |
54 | /** @license React v17.0.2
55 | * react.production.min.js
56 | *
57 | * Copyright (c) Facebook, Inc. and its affiliates.
58 | *
59 | * This source code is licensed under the MIT license found in the
60 | * LICENSE file in the root directory of this source tree.
61 | */
62 |
--------------------------------------------------------------------------------
/quell-extension/dist/panel.html:
--------------------------------------------------------------------------------
1 | Quell Devtools
--------------------------------------------------------------------------------
/quell-extension/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "quell_devtools_extension",
3 | "jest": {
4 | "verbose": true,
5 | "moduleNameMapper": {
6 | "\\.(jpg|jpeg|png|gif|eot|otf|webp|svg|ttf|woff|woff2|mp4|webm|wav|mp3|m4a|aac|oga)$": "./__mocks__/fileMock.js",
7 | "\\.(css|less|scss|sass)$": "identity-obj-proxy"
8 | },
9 | "testEnvironment": "jsdom"
10 | },
11 | "version": "2.0.0",
12 | "description": "Quell devtool to help visualize and query GraphQL ",
13 | "main": "index.js",
14 | "scripts": {
15 | "serve": "webpack serve --mode development",
16 | "build": "webpack --mode production",
17 | "test": "jest --forceExit"
18 | },
19 | "author": "Quell",
20 | "license": "MIT",
21 | "repository": {
22 | "type": "git",
23 | "url": "https://github.com/open-source-labs/Quell/tree/main/quell-extension"
24 | },
25 | "bugs": {
26 | "url": "https://github.com/open-source-labs/Quell/issues"
27 | },
28 | "keywords": [
29 | "cache-invalidation",
30 | "LokiJS",
31 | "graphQL",
32 | "cache",
33 | "caching",
34 | "redis",
35 | "batching",
36 | "client-side",
37 | "server-side",
38 | "apollo",
39 | "quell"
40 | ],
41 | "contributors": [
42 | {
43 | "name": "Chang Cai",
44 | "url": "https://github.com/ccai89"
45 | },
46 | {
47 | "name": "Robert Howton",
48 | "url": "https://github.com/roberthowton"
49 | },
50 | {
51 | "name": "Joshua Jordan",
52 | "url": "https://github.com/jjordan-90"
53 | },
54 | {
55 | "name": "Jinhee Choi",
56 | "url": "https://github.com/jcroadmovie"
57 | },
58 | {
59 | "name": "Nayan Parmar",
60 | "url": "https://github.com/nparmar1"
61 | },
62 | {
63 | "name": "Tashrif Sanil",
64 | "url": "https://github.com/tashrifsanil"
65 | },
66 | {
67 | "name": "Tim Frenzel",
68 | "url": "(https://github.com/TimFrenzel"
69 | },
70 | {
71 | "name": "Thomas Reeder",
72 | "url": "https://github.com/nomtomnom"
73 | },
74 | {
75 | "name": "Ken Litton",
76 | "url": "https://github.com/kenlitton"
77 | },
78 | {
79 | "name": "Robleh Farah",
80 | "url": "https://github.com/farahrobleh"
81 | },
82 | {
83 | "name": "Angela Franco",
84 | "url": "https://github.com/ajfranco18"
85 | },
86 | {
87 | "name": "Andrei Cabrera",
88 | "url": "https://github.com/Andreicabrerao"
89 | },
90 | {
91 | "name": "Dasha Kondratenko",
92 | "url": "https://github.com/dasha-k"
93 | },
94 | {
95 | "name": "Derek Sirola",
96 | "url": "https://github.com/dsirola1"
97 | },
98 | {
99 | "name": "Xiao Yu Omeara",
100 | "url": "https://github.com/xyomeara"
101 | },
102 | {
103 | "name": "Mike Lauri",
104 | "url": "https://github.com/MichaelLauri"
105 | },
106 | {
107 | "name": "Rob Nobile",
108 | "url": "https://github.com/RobNobile"
109 | },
110 | {
111 | "name": "Justin Jaeger",
112 | "url": "https://github.com/justinjaeger"
113 | },
114 | {
115 | "name": "Nick Kruckenberg",
116 | "url": "https://github.com/kruckenberg"
117 | },
118 | {
119 | "name": "Angelo Chengcuenca",
120 | "url": "https://github.com/amchengcuenca"
121 | },
122 | {
123 | "name": "Emily Hoang",
124 | "url": "https://github.com/emilythoang"
125 | },
126 | {
127 | "name": "Keely Timms",
128 | "url": "https://github.com/keelyt"
129 | },
130 | {
131 | "name": "Yusuf Bhaiyat",
132 | "url": "https://github.com/yusuf-bha"
133 | },
134 | {
135 | "name": "Jonah Weinbaum",
136 | "url": "https://github.com/jonahpw"
137 | },
138 | {
139 | "name": "Justin Hua",
140 | "url": "https://github.com/justinfhua"
141 | },
142 | {
143 | "name": "Lenny Yambao",
144 | "url": "https://github.com/lennin6"
145 | },
146 | {
147 | "name": "Michael Lav",
148 | "url": "https://github.com/mikelav258"
149 | }
150 | ],
151 | "homepage": "http://quell.dev",
152 | "dependencies": {
153 | "@monaco-editor/react": "^4.5.1",
154 | "@testing-library/jest-dom": "^5.16.1",
155 | "@testing-library/user-event": "^13.5.0",
156 | "@types/chrome": "^0.0.172",
157 | "codemirror": "^5.64.0",
158 | "codemirror-graphql": "^1.2.4",
159 | "graphql": "^16.6.0",
160 | "graphql-tag": "^2.12.6",
161 | "jest-environment-jsdom": "^27.4.4",
162 | "jest-fetch-mock": "^3.0.3",
163 | "react": "^17.0.2",
164 | "react-codemirror2-react-17": "^1.0.0",
165 | "react-dom": "^17.0.2",
166 | "react-split-pane": "^0.1.92",
167 | "react-table": "^7.7.0",
168 | "react-trend": "^1.2.5",
169 | "reactflow": "^11.7.2"
170 | },
171 | "devDependencies": {
172 | "@babel/core": "^7.12.13",
173 | "@babel/preset-env": "^7.12.13",
174 | "@babel/preset-react": "^7.12.13",
175 | "@babel/preset-typescript": "^7.16.0",
176 | "@emotion/react": "^11.7.0",
177 | "@emotion/styled": "^11.6.0",
178 | "@testing-library/dom": "^8.11.1",
179 | "@testing-library/jest-dom": "^5.16.1",
180 | "@testing-library/user-event": "^13.5.0",
181 | "@types/jest": "^27.0.3",
182 | "@types/react": "^17.0.37",
183 | "@types/react-dom": "^17.0.11",
184 | "antd": "^4.17.2",
185 | "babel-jest": "^27.4.4",
186 | "babel-loader": "^8.2.2",
187 | "codemirror": "^5.64.0",
188 | "codemirror-graphql": "^1.2.4",
189 | "copy-webpack-plugin": "^10.0.0",
190 | "css-loader": "^5.0.1",
191 | "css-modules-typescript-loader": "^4.0.1",
192 | "file-loader": "^6.2.0",
193 | "fs": "^0.0.1-security",
194 | "html-webpack-plugin": "^5.5.0",
195 | "identity-obj-proxy": "^3.0.0",
196 | "jest": "^27.4.4",
197 | "jest-dom": "^4.0.0",
198 | "sass": "^1.32.6",
199 | "sass-loader": "^10.1.1",
200 | "source-map-loader": "^3.0.0",
201 | "style-loader": "^2.0.0",
202 | "typescript": "^4.5.2",
203 | "url-loader": "^4.1.1",
204 | "webpack": "^5.64.4",
205 | "webpack-cli": "^4.9.1",
206 | "webpack-dev-server": "^4.6.0"
207 | }
208 | }
209 |
--------------------------------------------------------------------------------
/quell-extension/src/manifest.json:
--------------------------------------------------------------------------------
1 | {
2 | "manifest_version": 3,
3 | "name": "Quell Developer Tool",
4 | "description": "Developer tool for the Quell JavaScript library: https://quell.dev",
5 | "version": "2.0",
6 | "homepage_url": "https://quell.dev",
7 | "author": "Michael Lav, Lenny Yambao, Jonah Weinbaum, Justin Hua, Chang Cai, Robert Howton, Joshua Jordan, Angelo Chengcuenca, Emily Hoang, Keely Timms, Yusuf Bhaiyat",
8 | "action": {
9 | "default_icon": {
10 | "16": "./assets/icon16.png",
11 | "48": "./assets/icon48.png",
12 | "128": "./assets/icon128.png"
13 | },
14 | "default_title": "Quell Developer Tool"
15 | },
16 | "devtools_page": "devtools.html",
17 | "icons": {
18 | "16": "./assets/icon16.png",
19 | "48": "./assets/icon48.png",
20 | "128": "./assets/icon128.png"
21 | },
22 | "background": {
23 | "service_worker": "background.bundle.js",
24 | "type": "module"
25 | }
26 | }
27 |
--------------------------------------------------------------------------------
/quell-extension/src/pages/Background/background.tsx:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/open-source-labs/Quell/cede780bdae7817c4a267e70d746796cc94e5325/quell-extension/src/pages/Background/background.tsx
--------------------------------------------------------------------------------
/quell-extension/src/pages/Background/index.tsx:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/open-source-labs/Quell/cede780bdae7817c4a267e70d746796cc94e5325/quell-extension/src/pages/Background/index.tsx
--------------------------------------------------------------------------------
/quell-extension/src/pages/Devtools/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
--------------------------------------------------------------------------------
/quell-extension/src/pages/Devtools/index.tsx:
--------------------------------------------------------------------------------
1 | // Leave this alone - simply to create a tab in Chrome DevTools
2 |
3 | chrome.devtools.panels.create(
4 | 'Quell', //input dev tool name
5 | null, //icon for the dev tool if any - may use null
6 | 'panel.html' //panel code
7 | );
8 |
--------------------------------------------------------------------------------
/quell-extension/src/pages/Panel/App.tsx:
--------------------------------------------------------------------------------
1 | /* eslint-disable react/react-in-jsx-scope */
2 | import { useState, useEffect } from 'react';
3 | import PrimaryNavBar from './Components/PrimaryNavBar';
4 | import ServerTab from './Components/ServerTab';
5 | import CacheTab from './Components/CacheTab';
6 | import ClientTab from './Components/ClientTab';
7 | import isGQLQuery from './helpers/isGQLQuery';
8 | import { handleNavigate, handleRequestFinished } from './helpers/listeners';
9 |
10 | // GraphQL
11 | import { getIntrospectionQuery, buildClientSchema } from "graphql";
12 | import Settings from "./Components/Settings";
13 |
14 | // Sample clientRequest data for building Network component
15 | import data from "./data/sampleClientRequests";
16 | import { ClientRequest } from './interfaces/ClientRequest';
17 | // import { IgnorePlugin } from "webpack";
18 |
19 | const App = () => {
20 | // sets active tab - default: 'client'
21 | // other options - 'server', 'cache', 'settings'
22 | const [activeTab, setActiveTab] = useState("client");
23 |
24 | // queried data results
25 | const [results, setResults] = useState({});
26 | const [schema, setSchema] = useState({});
27 | const [queryString, setQueryString] = useState("");
28 | const [queryTimes, setQueryTimes] = useState([]);
29 | const [clientRequests, setClientRequests] = useState([]);
30 |
31 | // various routes to get information
32 | const [graphQLRoute, setGraphQLRoute] = useState("/api/graphql");
33 | const [clientAddress, setClientAddress] = useState(
34 | "http://localhost:8080"
35 | );
36 | const [serverAddress, setServerAddress] = useState(
37 | "http://localhost:3000"
38 | );
39 | const [redisRoute, setRedisRoute] = useState("/api/redis");
40 | const [clearCacheRoute, setClearCacheRoute] = useState("/api/clearCache");
41 |
42 | // function to clear front end cache
43 | const handleClearCache = (): void => {
44 | const address = `${serverAddress}${clearCacheRoute}`;
45 | fetch(address)
46 | .then((data) => console.log(data))
47 | .catch((err) => console.log(err));
48 | };
49 |
50 | // function used to listen to network requests and return any graphQL/Quell queries to populate client page
51 | const gqlListener = (request: ClientRequest): void => {
52 | if (isGQLQuery(request)) {
53 | request.getContent((body) => {
54 | const responseData = JSON.parse(body);
55 | request.responseData = responseData;
56 | setClientRequests((prev) => prev.concat([request]));
57 | });
58 | }
59 | };
60 |
61 | // function to listen to network requests and add query times to state
62 | const timeListener = (request: ClientRequest): void => {
63 | // if request was sent to the /api/queryTime route, add response body to queryTimes state
64 | request.getContent((body) => {
65 | const responseData = JSON.parse(body);
66 | if (responseData.time) {
67 | setQueryTimes((prev) => prev.concat([responseData.time]));
68 | }
69 | });
70 | };
71 |
72 | // COMMENT OUT IF WORKING FROM DEV SERVER
73 | useEffect(() => {
74 | handleRequestFinished(gqlListener);
75 | handleNavigate(gqlListener);
76 |
77 | handleRequestFinished(timeListener);
78 | handleNavigate(timeListener);
79 | }, []);
80 |
81 | useEffect(() => {
82 | const introspectionQuery = getIntrospectionQuery();
83 | const address = `${serverAddress}${graphQLRoute}`;
84 | fetch(address, {
85 | method: "POST",
86 | headers: {
87 | Accept: "application/json",
88 | "Content-Type": "application/json",
89 | },
90 | body: JSON.stringify({
91 | query: introspectionQuery,
92 | costOptions: { maxDepth: 15, maxCost: 6000, ipRate: 22}
93 | }),
94 | })
95 | .then((response) => response.json())
96 | .then((data) => {
97 | const schema = buildClientSchema(data.queryResponse.data);
98 | setSchema(schema || 'No schema retreived');
99 | })
100 | .catch((err) => console.log(err));
101 | }, [clientAddress, serverAddress, graphQLRoute]);
102 |
103 | // generates the page
104 | return (
105 |
29 | No keys or values returned. Your Redis cache may be empty, or the
30 | specified endpoint may not be configured to return keys and/or values.
31 | See the{" "}
32 |
33 | Quell docs
34 | {" "}
35 | for configuration instructions.
36 |
37 | );
38 | return temp;
39 | };
40 |
41 | return <>{getFilteredCache()}>;
42 | };
43 |
44 | export default CacheView;
45 |
--------------------------------------------------------------------------------
/quell-extension/src/pages/Panel/Components/ClientTab.tsx:
--------------------------------------------------------------------------------
1 | import { useState, useMemo } from 'react';
2 | import { useTable } from 'react-table';
3 | import Metrics from './Metrics';
4 | import SplitPane from 'react-split-pane';
5 | import { Controlled as CodeMirror } from "react-codemirror2-react-17";
6 | import 'codemirror/lib/codemirror.css';
7 | import 'codemirror/theme/material-darker.css';
8 | import 'codemirror/theme/xq-light.css';
9 | import 'codemirror';
10 | import 'codemirror/addon/lint/lint';
11 | import 'codemirror/addon/hint/show-hint';
12 | import 'codemirror-graphql/lint';
13 | import 'codemirror-graphql/hint';
14 | import 'codemirror-graphql/mode';
15 | import NavButton from './NavButton';
16 | import { getResponseStatus } from '../helpers/getResponseStatus';
17 | import { getQueryString, getOperationNames } from '../helpers/parseQuery';
18 | import { useEffect } from 'react';
19 | import {Visualizer} from './Visualizer/Visualizer'
20 |
21 | const ClientTab = ({ graphQLRoute, clientAddress, clientRequests, queryTimes } = props) => {
22 | // allows for highlighting of selected row and saves row data in state to display upon clicking for more information
23 | // A value of '-1' indicates row is not selected and will display metrics, otherwise >= 0 is the index of the row
24 | const [activeRow, setActiveRow] = useState(-1);
25 | const [clickedRowData, setClickedRowData] = useState({});
26 |
27 | return (
28 |
29 |
Client Quell Requests
30 |
31 |
42 |
43 |
50 |
51 | {/* conditionally renders either the metrics or additional info about specific query*/}
52 | {activeRow > -1 ? (
53 |
54 | ) : (
55 |