UNPKG

@graphql-yoga/plugin-response-cache

Version:

For the documentation check `http://graphql-yoga.com/docs/response-cache`

117 lines (116 loc) 5.65 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.createInMemoryCache = exports.useResponseCache = void 0; const response_cache_1 = require("@envelop/response-cache"); Object.defineProperty(exports, "createInMemoryCache", { enumerable: true, get: function () { return response_cache_1.createInMemoryCache; } }); const operationIdByRequest = new WeakMap(); // We trick Envelop plugin by passing operationId as sessionId so we can take it from cache key builder we pass to Envelop function sessionFactoryForEnvelop({ request }) { return operationIdByRequest.get(request); } const cacheKeyFactoryForEnvelop = async function cacheKeyFactoryForEnvelop({ sessionId }) { if (sessionId == null) { throw new Error('[useResponseCache] This plugin is not configured correctly. Make sure you use this plugin with GraphQL Yoga'); } return sessionId; }; const getDocumentStringForEnvelop = (executionArgs) => { const context = executionArgs.contextValue; if (context.params?.query == null) { throw new Error('[useResponseCache] This plugin is not configured correctly. Make sure you use this plugin with GraphQL Yoga'); } return context.params.query; }; function useResponseCache(options) { const buildResponseCacheKey = options?.buildResponseCacheKey || response_cache_1.defaultBuildResponseCacheKey; const cache = options.cache ?? (0, response_cache_1.createInMemoryCache)(); const enabled = options.enabled ?? (() => true); let logger; return { onYogaInit({ yoga }) { logger = yoga.logger; }, onPluginInit({ addPlugin }) { addPlugin((0, response_cache_1.useResponseCache)({ ...options, cache, getDocumentString: getDocumentStringForEnvelop, session: sessionFactoryForEnvelop, buildResponseCacheKey: cacheKeyFactoryForEnvelop, shouldCacheResult({ cacheKey, result }) { const shouldCached = options.shouldCacheResult ? options.shouldCacheResult({ cacheKey, result }) : !result.errors?.length; if (shouldCached) { const extensions = (result.extensions || (result.extensions = {})); const httpExtensions = (extensions.http || (extensions.http = {})); const headers = (httpExtensions.headers || (httpExtensions.headers = {})); headers['ETag'] = cacheKey; headers['Last-Modified'] = new Date().toUTCString(); } else { logger.warn('[useResponseCache] Failed to cache due to errors'); } return shouldCached; }, })); }, async onRequest({ request, fetchAPI, endResponse }) { if (enabled(request)) { const operationId = request.headers.get('If-None-Match'); if (operationId) { const cachedResponse = await cache.get(operationId); if (cachedResponse) { const lastModifiedFromClient = request.headers.get('If-Modified-Since'); const lastModifiedFromCache = cachedResponse.extensions?.http?.headers?.['Last-Modified']; if ( // This should be in the extensions already but we check it here to make sure lastModifiedFromCache != null && // If the client doesn't send If-Modified-Since header, we assume the cache is valid (lastModifiedFromClient == null || new Date(lastModifiedFromClient).getTime() >= new Date(lastModifiedFromCache).getTime())) { const okResponse = new fetchAPI.Response(null, { status: 304, headers: { ETag: operationId, }, }); endResponse(okResponse); } } } } }, async onParams({ params, request, setResult }) { if (enabled(request)) { const operationId = await buildResponseCacheKey({ documentString: params.query || '', variableValues: params.variables, operationName: params.operationName, sessionId: await options.session(request), }); operationIdByRequest.set(request, operationId); const cachedResponse = await cache.get(operationId); if (cachedResponse) { if (options.includeExtensionMetadata) { setResult({ ...cachedResponse, extensions: { ...cachedResponse.extensions, responseCache: { hit: true, }, }, }); } else { setResult(cachedResponse); } return; } } }, }; } exports.useResponseCache = useResponseCache;