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

204 lines (203 loc) 8.79 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.extractCacheKeys = exports.shouldCacheResponse = exports.createHandleQueryCaching = exports.createBuildCacheFromMergedResponse = exports.createBuildCacheFromResponse = void 0; /** * Creates a function to build cache from GraphQL query response * This function takes the response from a GraphQL query and stores it in cache * @param config - Configuration object * @returns Bound buildCacheFromResponse function */ function createBuildCacheFromResponse(config) { const { queryMap, writeToCache, normalizeForCache } = config; /** * Builds cache entries from a GraphQL query response * @param queryResponse - The response from GraphQL query execution * @param prototype - The prototype object describing the query structure * @param operationType - The type of operation (query, mutation, etc.) */ return function buildCacheFromResponse(queryResponse, prototype, operationType) { return __awaiter(this, void 0, void 0, function* () { try { // Skip caching for mutations as they're handled separately if (operationType === 'mutation') { return; } // Extract data from the response const responseData = queryResponse.data; if (!responseData) { console.warn('No data in query response to cache'); return; } // Normalize and cache the response data yield normalizeForCache(responseData, queryMap, prototype, 'root' // Default parent name for root queries ); console.log('Successfully built cache from query response'); } catch (error) { const err = { log: `Error building cache from response: ${error}`, status: 500, message: { err: 'Error building cache from response. Check server log for details.', }, }; console.error(err); throw error; } }); }; } exports.createBuildCacheFromResponse = createBuildCacheFromResponse; /** * Creates a function to build cache from merged response * This handles the case where responses are merged from cache and database * @param config - Configuration object * @returns Bound buildCacheFromMergedResponse function */ function createBuildCacheFromMergedResponse(config) { const { queryMap, normalizeForCache } = config; /** * Builds cache entries from a merged response (cache + database) * @param mergedResponse - The merged response containing both cached and fresh data * @param prototype - The prototype object describing the query structure * @param queryMap - Map of queries to their types */ return function buildCacheFromMergedResponse(mergedResponse, prototype, queryMap) { return __awaiter(this, void 0, void 0, function* () { try { // Extract data from the merged response const responseData = mergedResponse.data; if (!responseData) { console.warn('No data in merged response to cache'); return; } // Normalize and cache the merged response data yield normalizeForCache(responseData, queryMap, prototype, 'root' // Default parent name for root queries ); console.log('Successfully built cache from merged response'); } catch (error) { const err = { log: `Error building cache from merged response: ${error}`, status: 500, message: { err: 'Error building cache from merged response. Check server log for details.', }, }; console.error(err); throw error; } }); }; } exports.createBuildCacheFromMergedResponse = createBuildCacheFromMergedResponse; /** * Creates a function to handle caching logic in the query method * This is the main integration point for caching query responses * @param config - Configuration object * @returns Function that handles the caching logic */ function createHandleQueryCaching(config) { const { buildCacheFromResponse, buildCacheFromMergedResponse, queryMap } = config; /** * Handles caching for different query scenarios * @param scenario - The caching scenario (full, partial, none) * @param response - The response to cache * @param prototype - The query prototype * @param operationType - The operation type */ return function handleQueryCaching(scenario, response, prototype, operationType) { return __awaiter(this, void 0, void 0, function* () { try { switch (scenario) { case 'full': // Full response from database, cache everything yield buildCacheFromResponse(response, prototype, operationType); break; case 'partial': // Merged response from cache and database yield buildCacheFromMergedResponse(response, prototype, queryMap); break; case 'none': // Everything was in cache, no need to cache console.log('Response fully served from cache, no caching needed'); break; default: console.warn(`Unknown caching scenario: ${scenario}`); } } catch (error) { console.error('Error in handleQueryCaching:', error); // Don't throw - caching errors shouldn't break the response } }); }; } exports.createHandleQueryCaching = createHandleQueryCaching; /** * Helper function to determine if a response should be cached * @param operationType - The type of GraphQL operation * @param responseData - The response data * @returns Whether the response should be cached */ function shouldCacheResponse(operationType, responseData) { // Don't cache mutations (handled separately) if (operationType === 'mutation') { return false; } // Don't cache if no data if (!responseData || !responseData.data) { return false; } // Don't cache error responses if (responseData.errors && responseData.errors.length > 0) { return false; } // Don't cache unquellable operations if (operationType === 'unQuellable') { return false; } // Don't cache operations without ID if (operationType === 'noID') { return false; } return true; } exports.shouldCacheResponse = shouldCacheResponse; /** * Helper function to extract cache keys from a prototype * @param prototype - The query prototype * @returns Array of cache keys that should be built */ function extractCacheKeys(prototype) { const cacheKeys = []; function traverse(obj, parentKey = '') { for (const key in obj) { if (key.startsWith('__')) continue; // Skip meta fields const value = obj[key]; const fullKey = parentKey ? `${parentKey}.${key}` : key; if (typeof value === 'object' && value !== null) { // If it has __type and __id, it's a cacheable entity if (value.__type && value.__id) { const cacheKey = `${value.__type}--${value.__id}`; cacheKeys.push(cacheKey); } // Recursively traverse nested objects traverse(value, fullKey); } } } traverse(prototype); return cacheKeys; } exports.extractCacheKeys = extractCacheKeys;