From 2f9d9b4bc216078fde0963593ab45b432e424d8f Mon Sep 17 00:00:00 2001 From: kschrade Date: Thu, 7 Jul 2022 15:22:16 -0400 Subject: [PATCH 01/10] =?UTF-8?q?=E2=9C=A8=20Adding=20new=20hook=20for=20c?= =?UTF-8?q?hanging=20the=20cachekey=20for=20the=20response=20cache?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../src/ApolloServerPluginResponseCache.ts | 33 ++++++++++++++++--- 1 file changed, 29 insertions(+), 4 deletions(-) diff --git a/packages/apollo-server-plugin-response-cache/src/ApolloServerPluginResponseCache.ts b/packages/apollo-server-plugin-response-cache/src/ApolloServerPluginResponseCache.ts index 45009f88c65..e3d2093e0e2 100644 --- a/packages/apollo-server-plugin-response-cache/src/ApolloServerPluginResponseCache.ts +++ b/packages/apollo-server-plugin-response-cache/src/ApolloServerPluginResponseCache.ts @@ -86,6 +86,13 @@ interface Options> { shouldWriteToCache?( requestContext: GraphQLRequestContext, ): ValueOrPromise; + + // This hook allows one to replace the function that is used to create a cache + // key. If not it will fall back to what happens today. + generateCacheKey?( + requestContext: GraphQLRequestContext, + keyData: unknown, + ): string; } enum SessionMode { @@ -165,10 +172,19 @@ export default function plugin( async function cacheGet( contextualCacheKeyFields: ContextualCacheKey, ): Promise { - const key = cacheKeyString({ + let key; + + const cacheKeyData = { ...baseCacheKey!, ...contextualCacheKeyFields, - }); + }; + + if (options.generateCacheKey) { + key = options.generateCacheKey(requestContext, cacheKeyData); + } else { + key = cacheKeyString(cacheKeyData); + } + const serializedValue = await cache.get(key); if (serializedValue === undefined) { return null; @@ -278,10 +294,19 @@ export default function plugin( const cacheSetInBackground = ( contextualCacheKeyFields: ContextualCacheKey, ): void => { - const key = cacheKeyString({ + let key; + + const cacheKeyData = { ...baseCacheKey!, ...contextualCacheKeyFields, - }); + }; + + if (options.generateCacheKey) { + key = options.generateCacheKey(requestContext, cacheKeyData); + } else { + key = cacheKeyString(cacheKeyData); + } + const value: CacheValue = { data, cachePolicy: policyIfCacheable, From d34fdfbb2e1036a72204ada9b247b957fd6268c8 Mon Sep 17 00:00:00 2001 From: kschrade Date: Fri, 8 Jul 2022 08:38:21 -0400 Subject: [PATCH 02/10] little clean up --- .../src/ApolloServerPluginResponseCache.ts | 20 ++++++------------- 1 file changed, 6 insertions(+), 14 deletions(-) diff --git a/packages/apollo-server-plugin-response-cache/src/ApolloServerPluginResponseCache.ts b/packages/apollo-server-plugin-response-cache/src/ApolloServerPluginResponseCache.ts index e3d2093e0e2..fac27757569 100644 --- a/packages/apollo-server-plugin-response-cache/src/ApolloServerPluginResponseCache.ts +++ b/packages/apollo-server-plugin-response-cache/src/ApolloServerPluginResponseCache.ts @@ -172,18 +172,14 @@ export default function plugin( async function cacheGet( contextualCacheKeyFields: ContextualCacheKey, ): Promise { - let key; - const cacheKeyData = { ...baseCacheKey!, ...contextualCacheKeyFields, }; - if (options.generateCacheKey) { - key = options.generateCacheKey(requestContext, cacheKeyData); - } else { - key = cacheKeyString(cacheKeyData); - } + const key = options.generateCacheKey + ? options.generateCacheKey(requestContext, cacheKeyData) + : cacheKeyString(cacheKeyData); const serializedValue = await cache.get(key); if (serializedValue === undefined) { @@ -294,18 +290,14 @@ export default function plugin( const cacheSetInBackground = ( contextualCacheKeyFields: ContextualCacheKey, ): void => { - let key; - const cacheKeyData = { ...baseCacheKey!, ...contextualCacheKeyFields, }; - if (options.generateCacheKey) { - key = options.generateCacheKey(requestContext, cacheKeyData); - } else { - key = cacheKeyString(cacheKeyData); - } + const key = options.generateCacheKey + ? options.generateCacheKey(requestContext, cacheKeyData) + : cacheKeyString(cacheKeyData); const value: CacheValue = { data, From 5318f98772c23635d44e699aa76d44e914fb80d6 Mon Sep 17 00:00:00 2001 From: Kyle Schrade Date: Fri, 8 Jul 2022 14:45:17 -0400 Subject: [PATCH 03/10] Update packages/apollo-server-plugin-response-cache/src/ApolloServerPluginResponseCache.ts Co-authored-by: David Glasser --- .../src/ApolloServerPluginResponseCache.ts | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/packages/apollo-server-plugin-response-cache/src/ApolloServerPluginResponseCache.ts b/packages/apollo-server-plugin-response-cache/src/ApolloServerPluginResponseCache.ts index fac27757569..9dd846b374d 100644 --- a/packages/apollo-server-plugin-response-cache/src/ApolloServerPluginResponseCache.ts +++ b/packages/apollo-server-plugin-response-cache/src/ApolloServerPluginResponseCache.ts @@ -88,7 +88,13 @@ interface Options> { ): ValueOrPromise; // This hook allows one to replace the function that is used to create a cache - // key. If not it will fall back to what happens today. + // 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?( requestContext: GraphQLRequestContext, keyData: unknown, From 980e46b1bb17536aa9f69f3eefefe90ced8a81a0 Mon Sep 17 00:00:00 2001 From: kschrade Date: Fri, 8 Jul 2022 14:59:15 -0400 Subject: [PATCH 04/10] PR updates --- docs/source/performance/caching.md | 1 + .../src/ApolloServerPluginResponseCache.ts | 12 ++++++------ 2 files changed, 7 insertions(+), 6 deletions(-) diff --git a/docs/source/performance/caching.md b/docs/source/performance/caching.md index e362c3de3f7..9672e1ad8ea 100644 --- a/docs/source/performance/caching.md +++ b/docs/source/performance/caching.md @@ -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` | This function's return is used as the key for the cache. If it is not overrode a sha256 hash is used | diff --git a/packages/apollo-server-plugin-response-cache/src/ApolloServerPluginResponseCache.ts b/packages/apollo-server-plugin-response-cache/src/ApolloServerPluginResponseCache.ts index fac27757569..8039ada8fb3 100644 --- a/packages/apollo-server-plugin-response-cache/src/ApolloServerPluginResponseCache.ts +++ b/packages/apollo-server-plugin-response-cache/src/ApolloServerPluginResponseCache.ts @@ -155,6 +155,10 @@ export default function plugin( 'fqc:', ); + const generateCacheKey = options.generateCacheKey + ? options.generateCacheKey + : (_: any, key: any) => sha(JSON.stringify(key)); + let sessionId: string | null = null; let baseCacheKey: BaseCacheKey | null = null; let age: number | null = null; @@ -177,9 +181,7 @@ export default function plugin( ...contextualCacheKeyFields, }; - const key = options.generateCacheKey - ? options.generateCacheKey(requestContext, cacheKeyData) - : cacheKeyString(cacheKeyData); + const key = generateCacheKey(requestContext, cacheKeyData); const serializedValue = await cache.get(key); if (serializedValue === undefined) { @@ -295,9 +297,7 @@ export default function plugin( ...contextualCacheKeyFields, }; - const key = options.generateCacheKey - ? options.generateCacheKey(requestContext, cacheKeyData) - : cacheKeyString(cacheKeyData); + const key = generateCacheKey(requestContext, cacheKeyData); const value: CacheValue = { data, From f321d3d5f7afd807448d358728c6a3fd2c75ba30 Mon Sep 17 00:00:00 2001 From: Trevor Scheer Date: Mon, 11 Jul 2022 09:54:59 -0700 Subject: [PATCH 05/10] Remove unused code, fix build --- .../src/ApolloServerPluginResponseCache.ts | 6 ------ 1 file changed, 6 deletions(-) diff --git a/packages/apollo-server-plugin-response-cache/src/ApolloServerPluginResponseCache.ts b/packages/apollo-server-plugin-response-cache/src/ApolloServerPluginResponseCache.ts index 7dad825051b..79011a7cfe6 100644 --- a/packages/apollo-server-plugin-response-cache/src/ApolloServerPluginResponseCache.ts +++ b/packages/apollo-server-plugin-response-cache/src/ApolloServerPluginResponseCache.ts @@ -139,12 +139,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) { return requestContext.operation?.operation === 'query'; } From 90f03222e8691cc04acd7972006f6efb84d05933 Mon Sep 17 00:00:00 2001 From: Kyle Schrade Date: Mon, 11 Jul 2022 12:58:49 -0400 Subject: [PATCH 06/10] Update docs/source/performance/caching.md Co-authored-by: David Glasser --- docs/source/performance/caching.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/source/performance/caching.md b/docs/source/performance/caching.md index 9672e1ad8ea..b6d47385acd 100644 --- a/docs/source/performance/caching.md +++ b/docs/source/performance/caching.md @@ -472,4 +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` | This function's return is used as the key for the cache. If it is not overrode a sha256 hash is used | +| `generateCacheKey` | Customize generation of the cache key. By default, this is the SHA256 hash of the JSON encoding of an object containing relevant data. | From 5bd53a68fb1f36431b98883a421226e9f4146dac Mon Sep 17 00:00:00 2001 From: kschrade Date: Mon, 11 Jul 2022 13:04:45 -0400 Subject: [PATCH 07/10] removing any from function --- .../src/ApolloServerPluginResponseCache.ts | 17 ++++++++++------- 1 file changed, 10 insertions(+), 7 deletions(-) diff --git a/packages/apollo-server-plugin-response-cache/src/ApolloServerPluginResponseCache.ts b/packages/apollo-server-plugin-response-cache/src/ApolloServerPluginResponseCache.ts index 79011a7cfe6..c79584656e5 100644 --- a/packages/apollo-server-plugin-response-cache/src/ApolloServerPluginResponseCache.ts +++ b/packages/apollo-server-plugin-response-cache/src/ApolloServerPluginResponseCache.ts @@ -95,12 +95,14 @@ interface Options> { // 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?( - requestContext: GraphQLRequestContext, - keyData: unknown, - ): string; + generateCacheKey?: GenerateCacheKeyFunction; } +type GenerateCacheKeyFunction = ( + requestContext: GraphQLRequestContext>, + keyData: unknown, +) => string; + enum SessionMode { NoSession, Private, @@ -155,9 +157,10 @@ export default function plugin( 'fqc:', ); - const generateCacheKey = options.generateCacheKey - ? options.generateCacheKey - : (_: any, key: any) => sha(JSON.stringify(key)); + const generateCacheKey: GenerateCacheKeyFunction = + options.generateCacheKey + ? options.generateCacheKey + : (_, key) => sha(JSON.stringify(key)); let sessionId: string | null = null; let baseCacheKey: BaseCacheKey | null = null; From d5f90aa3bc66f5b23563dff7ebf40e82da81075f Mon Sep 17 00:00:00 2001 From: Trevor Scheer Date: Mon, 11 Jul 2022 11:21:13 -0700 Subject: [PATCH 08/10] empty commit to trigger builds (sorry) From b0bcf9b59a3d30b52a5cac1dbe03854698a02adb Mon Sep 17 00:00:00 2001 From: Kyle Schrade Date: Mon, 11 Jul 2022 14:27:30 -0400 Subject: [PATCH 09/10] Update packages/apollo-server-plugin-response-cache/src/ApolloServerPluginResponseCache.ts Co-authored-by: Trevor Scheer --- .../src/ApolloServerPluginResponseCache.ts | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/packages/apollo-server-plugin-response-cache/src/ApolloServerPluginResponseCache.ts b/packages/apollo-server-plugin-response-cache/src/ApolloServerPluginResponseCache.ts index c79584656e5..2f5f123cf45 100644 --- a/packages/apollo-server-plugin-response-cache/src/ApolloServerPluginResponseCache.ts +++ b/packages/apollo-server-plugin-response-cache/src/ApolloServerPluginResponseCache.ts @@ -158,9 +158,7 @@ export default function plugin( ); const generateCacheKey: GenerateCacheKeyFunction = - options.generateCacheKey - ? options.generateCacheKey - : (_, key) => sha(JSON.stringify(key)); + options.generateCacheKey ?? ((_, key) => sha(JSON.stringify(key))); let sessionId: string | null = null; let baseCacheKey: BaseCacheKey | null = null; From 1c84a40d19e6b343ef361b557a93b4e73207d9be Mon Sep 17 00:00:00 2001 From: kschrade Date: Mon, 11 Jul 2022 14:30:58 -0400 Subject: [PATCH 10/10] =?UTF-8?q?=F0=9F=93=9D=20addition=20to=20change=20l?= =?UTF-8?q?og?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index a88e999bbc9..d33874d27cd 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -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