-
Notifications
You must be signed in to change notification settings - Fork 5
Expand file tree
/
Copy pathcacheql.js
More file actions
238 lines (200 loc) · 7.05 KB
/
cacheql.js
File metadata and controls
238 lines (200 loc) · 7.05 KB
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
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
//REDIS NEW
const redis = require("redis");
const { promisify } = require("util");
let redisHost;
let redisPort;
let redisAuth;
let timeToLive;
let client;
let hgetAsync;
let hgetallAsync;
const cacheQL = {};
// Sets the redis database/cache configuration
// User calls the set function and sends:
// redisHost, redisPort, redisAuth, and/or timeToLive
cacheQL.set = data => {
if (data.redisHost) redisHost = data.redisHost;
if (data.redisPort) redisPort = data.redisPort;
if (data.redisAuth) redisAuth = data.redisAuth;
if (data.timeToLive) timeToLive = data.timeToLive;
if (data.redisHost && data.redisPort && data.redisAuth && data.timeToLive) {
return true;
};
};
// Authenticates the redis configuration based on the information set by the user
cacheQL.auth = () => {
client = redis.createClient({
port: redisPort,
host: redisHost
});
hgetAsync = promisify(client.hget).bind(client);
hgetAllAsync = promisify(client.hgetall).bind(client);
client.auth(redisAuth, function(err, response) {
if (err) {
throw err;
}
console.log("Client Authenticated");
return true;
});
};
cacheQL.checkify = async (query, partial = false) => {
// Checks the query if it is inside the cache
// query is the graphql query from the frontend request
// boolean partial is to enable partial field checking
let myHash = getQuery(query);
// Partial query
// Checks passed in partial parameter if partial query/field detection is true
//
if (partial) {
// Need to check if field is already inside cache
// Comparison of fields
// Probably need to do something with hgetAsync
// Or its
// Use hgetAllAsync to get all specific queries from a base query
return await hgetAllAsync(myHash).then(res => {
if (res === null) return null;
let inCache = false;
let allInCache = false;
const resultObj = {};
// const queryFields = constructQueryChildren(query);
// console.log("QUERY FIELDS: ", queryFields);
const queryFields = constructQueryChildrenObject(query);
Object.keys(res).forEach(key => {
if (!allInCache) {
// field values in key
const keyFields = constructQueryChildrenObject(key);
// Check if fields in key are in query
Object.keys(queryFields).forEach(ke => {
if (keyFields.hasOwnProperty(ke)) {
// Set inCache to true
// To save fields data into result
inCache = true;
// Delete current queryFields key to possibly decrease duplicate keyField queries
delete queryFields[ke];
}
});
// if inCache is true, combine result with current key result
if (inCache) {
const valueObj = JSON.parse(res[key]);
Object.keys(valueObj).forEach(k => {
// Not in the resultObj
// Add it
if (!resultObj[k]) {
resultObj[k] = valueObj[k];
}
});
}
inCache = false;
if (Object.keys(queryFields).length <= 0) {
allInCache = true;
}
}
});
//
// need to fix bug
// if partial fields are in cache then partial fields need to be taken from the end point
// then combined which will be final result
//
// Checks if resultObj is empty
// This means the initial part of the query is the same
// But no fields in the queries in the cache are the same with the graphql query from the client
// Returns null to activate endpoint call afterwards
if (
Object.entries(resultObj).length === 0 &&
resultObj.constructor === Object
) {
return null;
}
return resultObj;
});
} else {
// Use custom getAsync (Redis Get) created in the auth method
const hgetResult = await hgetAsync(myHash, query).then(async response => {
// The query is not inside the cache
// redis returns null if the key is not inside the redis database/cache
if (response === null) {
return response;
}
// the query is in the cache
// Use JSON.parse because the response (value from redis) is saved using JSON stringify
else {
return JSON.parse(response);
}
});
return hgetResult;
}
};
// Need to figure out how to go to db then go back here to save the query and result to cache
// Create another function (cachify)
// Which will be called after the endpoint middleware (like database)
// Then call said function to check if cache is null
// If null then saves query and result in cache
cacheQL.cachify = async (query, dbResult) => {
// This is a case where the query doesn't exist in the database
// In the previous step the user must save the query and the querry response from the initial query to the db
// Stringifies the queryResponse to be able to save deeply nested objects in redis
// build object to be saved in cache
// myHash is the part of graphql query that wont change on similar queries
let myHash = getQuery(query);
// Saves myHash, whole graphql query, and stringified dbResult to Redis cache
client.HSET(myHash, query, JSON.stringify(dbResult), async function(
err,
response
) {
if (err) {
throw err;
} else {
// Sets TTL for myHash key
client.EXPIRE(myHash, timeToLive);
// console.log("successful response in HSET: ", response);
return await response;
}
});
};
// Optional helper function for user to extract fields from a graphql query
cacheQL.queryFields = query => {
query = JSON.stringify(query);
let childArr = [];
let splitQ = query.split("\\n");
splitQ.forEach((ele, index, array) => {
if (index > 1) {
// if there is only one variable in the query
if (array[index - 1].includes("{") && array[index + 1].includes("}")) {
let pushThis = ele.trim();
childArr.push(pushThis.trim());
}
// if the current query has more than one variable
else if (!ele.includes("{") && !ele.includes("}") && ele.trim() != "") {
childArr.push(ele.trim());
}
}
});
return childArr;
};
// Helper function for cacheQL methods to extract query fields
const constructQueryChildrenObject = query => {
query = JSON.stringify(query);
let childObj = {};
let splitQ = query.split("\\n");
splitQ.forEach((ele, index, array) => {
if (index > 1) {
// if there is only one variable in the query
if (array[index - 1].includes("{") && array[index + 1].includes("}")) {
let pushThis = ele.trim();
childObj[pushThis.trim()] = pushThis.trim();
}
// if the current query has more than one variable
else if (!ele.includes("{") && !ele.includes("}") && ele.trim() != "") {
childObj[ele.trim()] = ele.trim();
}
}
});
return childObj;
};
// Takes the original query and obtains the unchangeable part of a graphql query for similar queries
const getQuery = query => {
const split = query.split("\n");
const myHash = split[1].trim();
return myHash;
};
module.exports = cacheQL;