@envelop/response-cache-cloudflare-kv
Version:
- Supports [Cloudflare KV](https://developers.cloudflare.com/kv/) cache for `@envelop/response-cache` plugin - Suitable for graphql servers running on [Cloudflare Workers](https://workers.cloudflare.com/)
74 lines (73 loc) • 3.79 kB
JavaScript
import { buildOperationKey } from './cache-key.js';
import { invalidate } from './invalidate.js';
import { set } from './set.js';
/**
* Creates a cache object that uses Cloudflare KV to store GraphQL responses.
* This cache is optimized for Cloudflare workers and uses the `ctx.waitUntil` method to perform non-blocking actions where possible
*
* To find out more about how this cache is implemented see https://the-guild.dev/blog/graphql-response-caching-with-envelop
*
* @param config Modify the behavior of the cache as it pertains to Cloudflare KV
* @returns A cache object that can be passed to envelop's `useResponseCache` plugin
*/
export function createKvCache(config) {
if (config.cacheReadTTL && config.cacheReadTTL < 60000) {
// eslint-disable-next-line no-console
console.warn('Cloudflare KV cacheReadTTL must be at least 60000 (60 seconds). Using default value of 60000 instead.');
}
const computedTtlInSeconds = Math.max(Math.floor((config.cacheReadTTL ?? 60000) / 1000), 60); // KV TTL must be at least 60 seconds
return function KVCacheFactory(ctx) {
return {
get(id) {
if (!ctx[config.KVName]) {
// eslint-disable-next-line no-console
console.warn(`Cloudflare KV namespace ${config.KVName} is not available in the server context, skipping cache read.`);
return;
}
const operationKey = buildOperationKey(id, config.keyPrefix);
return ctx[config.KVName].get(operationKey, {
type: 'json',
cacheTtl: computedTtlInSeconds,
});
},
set(
/** id/hash of the operation */
id,
/** the result that should be cached */
data,
/** array of entity records that were collected during execution */
entities,
/** how long the operation should be cached (in milliseconds) */
ttl) {
if (!ctx[config.KVName]) {
// eslint-disable-next-line no-console
console.warn(`Cloudflare KV namespace ${config.KVName} is not available in the server context, skipping cache write.`);
return;
}
const setPromise = set(id, data, entities, ttl, ctx[config.KVName], config.keyPrefix);
if (!ctx.waitUntil) {
// eslint-disable-next-line no-console
console.warn('The server context does not have a waitUntil method. This means that the cache write will not be non-blocking.');
return setPromise;
}
// Do not block execution of the worker while caching the result
ctx.waitUntil(setPromise);
},
invalidate(entities) {
if (!ctx[config.KVName]) {
// eslint-disable-next-line no-console
console.warn(`Cloudflare KV namespace ${config.KVName} is not available in the server context, skipping cache invalidate.`);
return;
}
const invalidatePromise = invalidate(entities, ctx[config.KVName], config.keyPrefix);
if (!ctx.waitUntil) {
// eslint-disable-next-line no-console
console.warn('The server context does not have a waitUntil method. This means that the cache invalidation will not be non-blocking.');
return invalidatePromise;
}
// Do not block execution of the worker while invalidating the cache
ctx.waitUntil(invalidatePromise);
},
};
};
}