Skip to content

Commit

Permalink
Add generateCacheKey to ApolloServerPluginResponseCache to allow …
Browse files Browse the repository at this point in the history
…for custom cache keys (#6655)

Create a new hook (`generateCacheKey`) on the `ApolloServerPluginResponseCache`
constructor options which allows users to customize the cache key used by returning
their own `string`.

The hook receives two arguments: the `requestContext` and an object (`cacheKeyData`)
which contains the following properties:
`source`
`operationName`
`variables`
`extra` (computed by `options.extraCacheKeyData`)
`sessionId`
`sessionMode`
  • Loading branch information
kschrade committed Jul 11, 2022
1 parent e9ae0f2 commit 7d6d3ce
Show file tree
Hide file tree
Showing 3 changed files with 30 additions and 10 deletions.
1 change: 1 addition & 0 deletions CHANGELOG.md
Expand Up @@ -11,6 +11,7 @@ The version headers in this history reflect the versions of Apollo Server itself
## vNEXT

- Add `document`, `variables`, `headers` as an option in the `ApolloServerPluginLandingPageLocalDefault` plugins. The embedded version of Apollo Sandbox can now use these options as an initial state. [PR #6628](https://github.com/apollographql/apollo-server/pull/6628)
- Add `generateCacheKey` to `ApolloServerPluginResponseCache` to allow for custom cache keys. [PR #6655](https://github.com/apollographql/apollo-server/pull/6655)

## v3.9.0

Expand Down
1 change: 1 addition & 0 deletions docs/source/performance/caching.md
Expand Up @@ -472,3 +472,4 @@ In addition to [the `sessionId` function](#identifying-users-for-private-respons
| `extraCacheKeyData` | This function's return value (any JSON-stringifiable object) is added to the key for the cached response. For example, if your API includes translatable text, this function can return a string derived from `requestContext.request.http.headers.get('Accept-Language')`. |
| `shouldReadFromCache` | If this function returns `false`, Apollo Server _skips_ the cache for the incoming operation, even if a valid response is available. |
| `shouldWriteToCache` | If this function returns `false`, Apollo Server doesn't cache its response for the incoming operation, even if the response's `maxAge` is greater than `0`. |
| `generateCacheKey` | Customize generation of the cache key. By default, this is the SHA256 hash of the JSON encoding of an object containing relevant data. |
Expand Up @@ -86,8 +86,23 @@ interface Options<TContext = Record<string, any>> {
shouldWriteToCache?(
requestContext: GraphQLRequestContext<TContext>,
): ValueOrPromise<boolean>;

// This hook allows one to replace the function that is used to create a cache
// key. By default, it is the SHA-256 (from the Node `crypto` package) of the result of
// calling `JSON.stringify(keyData)`. You can override this to customize the serialization
// or the hash, or to make other changes like adding a prefix to keys to allow for
// app-specific prefix-based cache invalidation. You may assume that `keyData` is an object
// and that all relevant data will be found by the kind of iteration performed by
// `JSON.stringify`, but you should not assume anything about the particular fields on
// `keyData`.
generateCacheKey?: GenerateCacheKeyFunction;
}

type GenerateCacheKeyFunction = (
requestContext: GraphQLRequestContext<Record<string, any>>,
keyData: unknown,
) => string;

enum SessionMode {
NoSession,
Private,
Expand Down Expand Up @@ -126,12 +141,6 @@ interface CacheValue {
cacheTime: number; // epoch millis, used to calculate Age header
}

type CacheKey = BaseCacheKey & ContextualCacheKey;

function cacheKeyString(key: CacheKey) {
return sha(JSON.stringify(key));
}

function isGraphQLQuery(requestContext: GraphQLRequestContext<any>) {
return requestContext.operation?.operation === 'query';
}
Expand All @@ -148,6 +157,9 @@ export default function plugin(
'fqc:',
);

const generateCacheKey: GenerateCacheKeyFunction =
options.generateCacheKey ?? ((_, key) => sha(JSON.stringify(key)));

let sessionId: string | null = null;
let baseCacheKey: BaseCacheKey | null = null;
let age: number | null = null;
Expand All @@ -165,10 +177,13 @@ export default function plugin(
async function cacheGet(
contextualCacheKeyFields: ContextualCacheKey,
): Promise<GraphQLResponse | null> {
const key = cacheKeyString({
const cacheKeyData = {
...baseCacheKey!,
...contextualCacheKeyFields,
});
};

const key = generateCacheKey(requestContext, cacheKeyData);

const serializedValue = await cache.get(key);
if (serializedValue === undefined) {
return null;
Expand Down Expand Up @@ -278,10 +293,13 @@ export default function plugin(
const cacheSetInBackground = (
contextualCacheKeyFields: ContextualCacheKey,
): void => {
const key = cacheKeyString({
const cacheKeyData = {
...baseCacheKey!,
...contextualCacheKeyFields,
});
};

const key = generateCacheKey(requestContext, cacheKeyData);

const value: CacheValue = {
data,
cachePolicy: policyIfCacheable,
Expand Down

0 comments on commit 7d6d3ce

Please sign in to comment.