@quell/server
Version:
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 serv
243 lines (239 loc) • 13.2 kB
JavaScript
;
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
return new (P || (P = Promise))(function (resolve, reject) {
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
step((generator = generator.apply(thisArg, _arguments || [])).next());
});
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.findFieldsListKey = exports.extractDataFromResponse = exports.isDeleteMutation = exports.createUpdateCacheByMutation = void 0;
const redisHelpers_1 = require("../helpers/redisHelpers");
/**
* Creates an updateCacheByMutation function with the provided configuration
* MIGRATED FROM: QuellCache.updateCacheByMutation
* @param config - Configuration object
* @returns Bound updateCacheByMutation function
*/
function createUpdateCacheByMutation(config) {
const { redisCache, queryMap, writeToCache, deleteCacheById } = config;
/**
* Helper function to delete field keys from cached field list.
* @param {Set<string> | Array<string>} fieldKeysToRemove - Field keys to be removed from the cached field list.
* @param {string} fieldsListKey - The key for the fields list in cache
*/
const removeFromFieldKeysList = (fieldKeysToRemove, fieldsListKey) => __awaiter(this, void 0, void 0, function* () {
if (fieldsListKey) {
const cachedFieldKeysListRaw = yield (0, redisHelpers_1.getFromRedis)(fieldsListKey, redisCache);
if (cachedFieldKeysListRaw !== null &&
cachedFieldKeysListRaw !== undefined) {
const cachedFieldKeysList = JSON.parse(cachedFieldKeysListRaw);
fieldKeysToRemove.forEach((fieldKey) => {
// Index position of field key to remove from list of field keys
const removalFieldKeyIdx = cachedFieldKeysList.indexOf(fieldKey);
if (removalFieldKeyIdx !== -1) {
cachedFieldKeysList.splice(removalFieldKeyIdx, 1);
}
});
writeToCache(fieldsListKey, cachedFieldKeysList);
}
}
});
/**
* Helper function that loops through the cachedFieldKeysList and helps determine which
* fieldKeys should be deleted and passes those fields to removeFromFieldKeysList for removal.
* @param {string} fieldsListKey - The key for the fields list in cache
* @param {ProtoObjType} mutationQueryObject - The mutation query object containing arguments
*/
const deleteApprFieldKeys = (fieldsListKey, mutationQueryObject) => __awaiter(this, void 0, void 0, function* () {
if (fieldsListKey) {
const cachedFieldKeysListRaw = yield (0, redisHelpers_1.getFromRedis)(fieldsListKey, redisCache);
if (cachedFieldKeysListRaw !== null &&
cachedFieldKeysListRaw !== undefined) {
const cachedFieldKeysList = JSON.parse(cachedFieldKeysListRaw);
const fieldKeysToRemove = new Set();
for (let i = 0; i < cachedFieldKeysList.length; i++) {
const fieldKey = cachedFieldKeysList[i];
const fieldKeyValueRaw = yield (0, redisHelpers_1.getFromRedis)(fieldKey.toLowerCase(), redisCache);
if (fieldKeyValueRaw !== null && fieldKeyValueRaw !== undefined) {
const fieldKeyValue = JSON.parse(fieldKeyValueRaw);
let remove = true;
for (const arg in mutationQueryObject.__args) {
if (Object.prototype.hasOwnProperty.call(fieldKeyValue, arg)) {
const argValue = mutationQueryObject.__args[arg];
if (fieldKeyValue[arg] !== argValue) {
remove = false;
break;
}
}
else {
remove = false;
break;
}
}
if (remove === true) {
fieldKeysToRemove.add(fieldKey);
yield deleteCacheById(fieldKey.toLowerCase());
}
}
}
yield removeFromFieldKeysList(fieldKeysToRemove, fieldsListKey);
}
}
});
/**
* Helper function that loops through the cachedFieldKeysList and updates the appropriate
* field key values and writes the updated values to the redis cache
* @param {string} fieldsListKey - The key for the fields list in cache
* @param {ProtoObjType} mutationQueryObject - The mutation query object containing arguments
*/
const updateApprFieldKeys = (fieldsListKey, mutationQueryObject) => __awaiter(this, void 0, void 0, function* () {
const cachedFieldKeysListRaw = yield (0, redisHelpers_1.getFromRedis)(fieldsListKey, redisCache);
// conditional just in case the resolver wants to throw an error. instead of making quellCache invoke it's caching functions, we break here.
if (cachedFieldKeysListRaw === undefined)
return;
// list of field keys stored on redis
if (cachedFieldKeysListRaw !== null) {
const cachedFieldKeysList = JSON.parse(cachedFieldKeysListRaw);
// Iterate through field key field key values in Redis, and compare to user
// specified mutation args to determine which fields are used to update by
// and which fields need to be updated.
cachedFieldKeysList.forEach((fieldKey) => __awaiter(this, void 0, void 0, function* () {
const fieldKeyValueRaw = yield (0, redisHelpers_1.getFromRedis)(fieldKey.toLowerCase(), redisCache);
if (fieldKeyValueRaw !== null && fieldKeyValueRaw !== undefined) {
const fieldKeyValue = JSON.parse(fieldKeyValueRaw);
const fieldsToUpdateBy = [];
const updatedFieldKeyValue = fieldKeyValue;
Object.entries(mutationQueryObject.__args).forEach(([arg, argVal]) => {
if (arg in fieldKeyValue && fieldKeyValue[arg] === argVal) {
// Foreign keys are not fields to update by
if (arg.toLowerCase().includes("id") === false) {
fieldsToUpdateBy.push(arg);
}
}
else {
if (typeof argVal === "string")
updatedFieldKeyValue[arg] = argVal;
}
});
if (fieldsToUpdateBy.length > 0) {
writeToCache(fieldKey, updatedFieldKeyValue);
}
}
}));
}
});
/**
* Updates the Redis cache when the operation is a mutation.
* - For update and delete mutations, checks if the mutation query includes an id.
* If so, it will update the cache at that id. If not, it will iterate through the cache
* to find the appropriate fields to update/delete.
* @param {Object} dbRespDataRaw - Raw response from the database returned following mutation.
* @param {string} mutationName - Name of the mutation (e.g. addItem).
* @param {string} mutationType - Type of mutation (add, update, delete).
* @param {Object} mutationQueryObject - Arguments and values for the mutation.
*/
function updateCacheByMutation(dbRespDataRaw, mutationName, mutationType, mutationQueryObject) {
var _a;
return __awaiter(this, void 0, void 0, function* () {
let dbRespId = "";
let dbRespData = {};
if (dbRespDataRaw.data) {
// TODO: Need to modify this logic if ID is not being requested back during
// mutation query.
// dbRespDataRaw.data[mutationName] will always return the value at the mutationName
// in the form of an object.
dbRespId = (_a = dbRespDataRaw.data[mutationName]) === null || _a === void 0 ? void 0 : _a.id;
dbRespData = yield JSON.parse(JSON.stringify(dbRespDataRaw.data[mutationName]));
}
let fieldsListKey = "";
for (const queryKey in queryMap) {
const queryKeyType = queryMap[queryKey];
if (JSON.stringify(queryKeyType) === JSON.stringify([mutationType])) {
fieldsListKey = queryKey;
break;
}
}
// If there is no id property on dbRespDataRaw.data[mutationName]
// dbRespId defaults to an empty string and no redisKey will be found.
const hypotheticalRedisKey = `${mutationType.toLowerCase()}--${dbRespId}`;
const redisKey = yield (0, redisHelpers_1.getFromRedis)(hypotheticalRedisKey, redisCache);
if (redisKey) {
// If the key was found in the Redis server cache, the mutation is either update or delete mutation.
if (mutationQueryObject.__id) {
// If the user specifies dbRespId as an argument in the mutation, then we only need to
// update/delete a single cache entry by dbRespId.
if (mutationName.substring(0, 3) === "del") {
// If the first 3 letters of the mutationName are 'del' then the mutation is a delete mutation.
// Users have to prefix their delete mutations with 'del' so that quell can distinguish between delete/update mutations.
yield deleteCacheById(`${mutationType.toLowerCase()}--${mutationQueryObject.__id}`);
yield removeFromFieldKeysList([`${mutationType}--${dbRespId}`], fieldsListKey);
}
else {
// Update mutation for single dbRespId
writeToCache(`${mutationType.toLowerCase()}--${mutationQueryObject.__id}`, dbRespData);
}
}
else {
// If the user didn't specify dbRespId, we need to iterate through all key value pairs and determine which key values match dbRespData.
// Note that there is a potential edge case here if there are no queries that have type GraphQLList.
// if (!fieldsListKey) throw 'error: schema must have a GraphQLList';
// Unused variable
// eslint-disable-next-line @typescript-eslint/no-unused-vars
const removalFieldKeysList = [];
if (mutationName.substring(0, 3) === "del") {
// Mutation is delete mutation
yield deleteApprFieldKeys(fieldsListKey, mutationQueryObject);
}
else {
yield updateApprFieldKeys(fieldsListKey, mutationQueryObject);
}
}
}
else {
// If the key was not found in the Redis server cache, the mutation is an add mutation.
writeToCache(hypotheticalRedisKey, dbRespData);
}
});
}
return updateCacheByMutation;
}
exports.createUpdateCacheByMutation = createUpdateCacheByMutation;
/**
* Helper function to determine if a mutation is a delete mutation
* based on the mutation name prefix
*/
function isDeleteMutation(mutationName) {
return mutationName.substring(0, 3) === "del";
}
exports.isDeleteMutation = isDeleteMutation;
/**
* Helper function to extract data from database response
*/
function extractDataFromResponse(dbRespDataRaw, mutationName) {
var _a;
let dbRespId = "";
let dbRespData = {};
if (dbRespDataRaw.data) {
dbRespId =
((_a = dbRespDataRaw.data[mutationName]) === null || _a === void 0 ? void 0 : _a.id) || "";
dbRespData = JSON.parse(JSON.stringify(dbRespDataRaw.data[mutationName]));
}
return { dbRespId, dbRespData };
}
exports.extractDataFromResponse = extractDataFromResponse;
/**
* Helper function to find the fields list key for a mutation type
*/
function findFieldsListKey(queryMap, mutationType) {
for (const queryKey in queryMap) {
const queryKeyType = queryMap[queryKey];
if (JSON.stringify(queryKeyType) === JSON.stringify([mutationType])) {
return queryKey;
}
}
return "";
}
exports.findFieldsListKey = findFieldsListKey;