@graphql-yoga/plugin-response-cache
Version:
For the documentation check `http://graphql-yoga.com/docs/response-cache`
117 lines (116 loc) • 5.65 kB
JavaScript
;
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;