-
-
Notifications
You must be signed in to change notification settings - Fork 111
/
quickCache.js
191 lines (173 loc) · 6.65 KB
/
quickCache.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
/** @format */
import "https://deno.land/x/dotenv@v3.2.2/load.ts";
import { connect } from "https://deno.land/x/redis@v0.29.2/mod.ts";
import { gql } from "https://deno.land/x/oak_graphql@0.6.4/mod.ts";
import { print, visit } from "https://deno.land/x/graphql_deno@v15.0.0/mod.ts";
import { destructureQueries } from './Browser/destructure.js';
export class Cache {
constructor(
initialCache = {
ROOT_QUERY: {},
ROOT_MUTATION: {},
}
) {
this.ROOT_QUERY = initialCache.ROOT_QUERY;
this.ROOT_MUTATION = initialCache.ROOT_MUTATION;
}
// METHOD TO CONNECT TO CACHE
async connect(port, policy, maxmemory) {
this.redis = await connect({
hostname: Deno.env.get('REDIS_HOST'),
port: port,
});
console.log('connecting to redis');
this.cacheClear();
this.redis.configSet('maxmemory-policy', policy);
this.redis.configSet('maxmemory', maxmemory);
}
// METHOD TO READ FROM REDIS CACHE & RESTRUCTURE THE DATA
async read(queryStr) {
// destructure the query string into an object
const queries = destructureQueries(queryStr).queries;
if (!queries) return;
const responseObject = {};
// iterate through each query in the input object
for (const query in queries) {
const queryHash = queries[query].name.concat(queries[query].arguments);
if (this.ROOT_QUERY[queryHash]) {
const hashArray = this.ROOT_QUERY[queryHash];
const respObjProp = queries[query].alias ?? queries[query].name;
// invoke populateAllHashes to add data object to the response object
responseObject[respObjProp] = await this.populateAllHashes(hashArray, queries[query].fields);
if (!responseObject[respObjProp]) return;
} else {
return null;
}
}
return { data: responseObject };
}
populateAllHashes(allHashes, fields){
if (!allHashes.length) return [];
const tildeInd = allHashes[0].indexOf('~');
const typeName = allHashes[0].slice(0, tildeInd);
const reduction = allHashes.reduce(async (acc, hash) => {
const readStr = await this.redis.get(hash);
const readVal = await JSON.parse(readStr);
if (!readVal) return;
const dataObj = {};
// iterate over the fields object to populate with data from cache
for (const field in fields) {
if (typeof fields[field] !== 'object') {
if (field === '__typename') {
dataObj[field] = typeName;
} else {
dataObj[field] = readVal[field] || 'n/a';
}
} else {
// if the field from the input query is an array of hashes, recursively invoke
dataObj[field] = await this.populateAllHashes(readVal[field], fields[field]);
if (dataObj[field] === undefined) return;
}
}
// at this point acc should be an array of response objects for each hash
const resolvedProm = await Promise.resolve(acc);
resolvedProm.push(dataObj);
return resolvedProm;
}, []);
return reduction;
};
// METHOD TO WRITE TO REDIS CACHE
async write(queryStr, respObj, searchTerms, deleteFlag) {
const hash = this.createQueryKey(queryStr);
const array = Object.keys(respObj);
// isolate type of of query - 'person,' 'book,' etc.
const tildeInd = array[0].indexOf('~');
const typeName = array[0].slice(0, tildeInd);
// store the array of keys to ROOT_QUERY
this.ROOT_QUERY[hash] = array;
// write each item in the array to the cache
for (let i = 0; i < array.length; i++) {
await this.redis.set(array[i], JSON.stringify(respObj[array[i]]));
// if using searchTerms, iterate throuogh those and also store each item
// according to those terms in ROOT_QUERY
if (searchTerms.length && queryStr.slice(8 , 11) === 'all') {
searchTerms.forEach(el => {
const elVal = respObj[array[i]][el].replaceAll(' ', '');
const hashKey = `one${typeName}(${el}:"${elVal}")`
if (!this.ROOT_QUERY[hashKey]) this.ROOT_QUERY[hashKey] = [];
this.ROOT_QUERY[hashKey].push(array[i]);
})
}
}
}
// CURRENTLY BEING UTILIZED BY invalidateCacheCheck.ts, WHICH IS A FILE THAT SHOULD BE REFACTORED IN FUTURE ITERATION
cacheWriteObject = async (hash, obj) => {
let entries = Object.entries(obj).flat();
entries = entries.map((entry) => JSON.stringify(entry));
// adding as nested strings? take out one layer for clarity.
await this.redis.hset(hash, ...entries);
};
// CURRENTLY BEING UTILIZED BY invalidateCacheCheck.ts, WHICH IS A FILE THAT SHOULD BE REFACTORED IN FUTURE ITERATION
cacheReadObject = async (hash, fields = []) => {
// Checks for the fields requested, then queries cache for those specific keys in the hashes
if (fields.length !== 0) {
const fieldObj = {};
for (const field of fields) {
const rawCacheValue = await this.redisdb.hget(hash, JSON.stringify(field));
fieldObj[field] = JSON.parse(rawCacheValue);
}
return fieldObj;
} else {
let objArray = await this.redisdb.hgetall(hash);
if (objArray.length == 0) return undefined;
let parsedArray = objArray.map((entry) => JSON.parse(entry));
if (parsedArray.length % 2 !== 0) {
return undefined;
}
let returnObj = {};
for (let i = 0; i < parsedArray.length; i += 2) {
returnObj[parsedArray[i]] = parsedArray[i + 1];
}
return returnObj;
}
};
/*
Creates a string to search the cache or add as a key in the cache.
*/
createQueryKey(queryStr) {
// traverses AST and gets object name, and any filter keys in the query
const ast = gql(queryStr);
const tableName = ast.definitions[0].selectionSet.selections[0].name.value;
let queryKey = `${tableName}`;
if (ast.definitions[0].operation === 'mutation') return queryKey;
if (ast.definitions[0].selectionSet.selections[0].arguments.length) {
const fieldsArray =
ast.definitions[0].selectionSet.selections[0].arguments;
const resultsObj = {};
fieldsArray.forEach((el) => {
const name = el.name.value;
const value = el.value.value;
resultsObj[name] = value;
});
let parens = '' // name:"Yoda"
for (const key in resultsObj) {
parens += `${key}:"${resultsObj[key]}"`;
}
queryKey = queryKey + '(' + parens + ')';
}
return queryKey;
}
async cacheDelete(hash) {
await this.redis.del(hash);
}
async cacheClear() {
await this.redis.flushdb((err, successful) => {
if (err) console.log('redis error', err);
console.log(successful, 'clear');
});
}
// functionality to stop polling
stopPollInterval(interval) {
clearInterval(interval);
}
}