Skip to content

Commit

Permalink
fix(response-cache): respect subgraph cache control TTL value (#8334)
Browse files Browse the repository at this point in the history
* fix(response-cache): respect subgraph cache control TTL value

* chore(dependencies): updated changesets for modified dependencies

* Better code

* Respect if it is 0 seconds

* Reduce diff

* Comments

---------

Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
  • Loading branch information
ardatan and github-actions[bot] authored Jan 29, 2025
1 parent 529bf90 commit fafb1c9
Show file tree
Hide file tree
Showing 6 changed files with 75 additions and 189 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@graphql-mesh/plugin-response-cache": patch
---
dependencies updates:
- Added dependency [`cache-control-parser@^2.0.6` ↗︎](https://www.npmjs.com/package/cache-control-parser/v/2.0.6) (to `dependencies`)
5 changes: 5 additions & 0 deletions .changeset/wicked-schools-wink.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'@graphql-mesh/plugin-response-cache': patch
---

When a subgraph returns cache control headers with a certain TTL, respect that during the calculation of overall TTL in the response cache plugin
5 changes: 0 additions & 5 deletions e2e/cache-control/gateway.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,11 +16,6 @@ if (process.env.CACHE_PLUGIN === 'HTTP Caching') {
} else if (process.env.CACHE_PLUGIN === 'Response Caching') {
config.responseCaching = {
session: () => null,
ttlPerType: {
// Just like a default TTL set in Apollo Server,
// We do the same thing in response caching plugin to replicate the behavior
Comment: 5_000,
},
};
} else {
throw new Error(`Unknown caching plugin: ${process.env.CACHE_PLUGIN}`);
Expand Down
1 change: 1 addition & 0 deletions packages/plugins/response-cache/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,7 @@
"@graphql-mesh/utils": "^0.103.12",
"@graphql-tools/utils": "^10.6.2",
"@graphql-yoga/plugin-response-cache": "^3.1.1",
"cache-control-parser": "^2.0.6",
"graphql-yoga": "^5.7.0",
"tslib": "^2.4.0"
},
Expand Down
56 changes: 51 additions & 5 deletions packages/plugins/response-cache/src/index.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import type { Plugin } from 'graphql-yoga';
import CacheControlParser from 'cache-control-parser';
import { defaultBuildResponseCacheKey } from '@envelop/response-cache';
import type { GatewayContext, GatewayPlugin } from '@graphql-hive/gateway-runtime';
import { process } from '@graphql-mesh/cross-helpers';
import { stringInterpolator } from '@graphql-mesh/string-interpolation';
import type { KeyValueCache, YamlConfig } from '@graphql-mesh/types';
Expand Down Expand Up @@ -166,7 +167,7 @@ export type ResponseCacheConfig = Omit<UseResponseCacheParameter, 'cache'> & {
* Response cache plugin for GraphQL Mesh
* @param options
*/
export default function useMeshResponseCache(options: ResponseCacheConfig): Plugin;
export default function useMeshResponseCache(options: ResponseCacheConfig): GatewayPlugin;
/**
* @deprecated Use new configuration format `ResponseCacheConfig`
* @param options
Expand All @@ -175,15 +176,15 @@ export default function useMeshResponseCache(
options: YamlConfig.ResponseCacheConfig & {
cache: KeyValueCache;
},
): Plugin;
): GatewayPlugin;
export default function useMeshResponseCache(
options:
| ResponseCacheConfig
// TODO: This is for v0 compatibility, remove once v1 is released
| (YamlConfig.ResponseCacheConfig & {
cache: KeyValueCache;
}),
): Plugin {
): GatewayPlugin {
const ttlPerType: Record<string, number> = { ...(options as ResponseCacheConfig).ttlPerType };
const ttlPerSchemaCoordinate: Record<string, number> = {
...(options as ResponseCacheConfig).ttlPerSchemaCoordinate,
Expand All @@ -196,7 +197,12 @@ export default function useMeshResponseCache(
}
}

return useResponseCache({
// Stored TTL by the context
// To be compared with the calculated one later in `onTtl`
const ttlByContext = new WeakMap<any, number>();

// @ts-expect-error - GatewayPlugin types
const plugin: GatewayPlugin = useResponseCache({
includeExtensionMetadata:
options.includeExtensionMetadata != null
? options.includeExtensionMetadata
Expand All @@ -215,5 +221,45 @@ export default function useMeshResponseCache(
cache: getCacheForResponseCache(options.cache),
ttlPerType,
ttlPerSchemaCoordinate,
// Checks the TTL stored in the context
// Compares it to the calculated one
// Then it takes the lowest value
onTtl({ ttl, context }) {
const ttlForThisContext = ttlByContext.get(context);
if (ttlForThisContext != null && ttlForThisContext < ttl) {
return ttlForThisContext;
}
return ttl;
},
});
// Checks the TTL stored in the context
// Takes the lowest value
function checkTtl(context: GatewayContext, ttl: number) {
const ttlForThisContext = ttlByContext.get(context);
if (ttlForThisContext == null || ttl < ttlForThisContext) {
ttlByContext.set(context, ttl);
}
}
plugin.onFetch = function ({ executionRequest, context }) {
// Only if it is a subgraph request
if (executionRequest && context) {
return function onFetchDone({ response }) {
const cacheControlHeader = response.headers.get('cache-control');
if (cacheControlHeader != null) {
const parsedCacheControl = CacheControlParser.parse(cacheControlHeader);
if (parsedCacheControl['max-age'] != null) {
const maxAgeInSeconds = parsedCacheControl['max-age'];
const maxAgeInMs = maxAgeInSeconds * 1000;
checkTtl(context, maxAgeInMs);
}
if (parsedCacheControl['s-maxage'] != null) {
const sMaxAgeInSeconds = parsedCacheControl['s-maxage'];
const sMaxAgeInMs = sMaxAgeInSeconds * 1000;
checkTtl(context, sMaxAgeInMs);
}
}
};
}
};
return plugin;
}
Loading

0 comments on commit fafb1c9

Please sign in to comment.