UNPKG

@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
"use strict"; 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;