Skip to content
Merged
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
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
Original file line number Diff line number Diff line change
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. |
Original file line number Diff line number Diff line change
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