Skip to content

Commit

Permalink
feat(response-cache): onTtl hook to manipulate the cached response (#…
Browse files Browse the repository at this point in the history
…2392)

* feat(response-cache): `onTtl` hook to manipulate the cached response

* Lets go

* Cleanup

* Add tests
  • Loading branch information
ardatan authored Jan 27, 2025
1 parent bf360c1 commit 7882ffb
Show file tree
Hide file tree
Showing 3 changed files with 81 additions and 2 deletions.
5 changes: 5 additions & 0 deletions .changeset/great-bees-change.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'@envelop/response-cache': minor
---

New `onTtl` hook to manipulate TTL of the cached response
21 changes: 19 additions & 2 deletions packages/plugins/response-cache/src/plugin.ts
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ import {
getDirective,
MapperKind,
mapSchema,
MaybePromise,
memoize1,
memoize4,
mergeIncrementalResult,
Expand All @@ -51,7 +52,7 @@ export type BuildResponseCacheKeyFunction = (params: {
sessionId: Maybe<string>;
/** GraphQL Context */
context: ExecutionArgs['contextValue'];
}) => Promise<string>;
}) => MaybePromise<string>;

export type GetDocumentStringFunction = (executionArgs: ExecutionArgs) => string;

Expand Down Expand Up @@ -147,8 +148,17 @@ export type UseResponseCacheParameter<PluginContext extends Record<string, any>
* Use this function to customize the behavior, such as caching results that have an EnvelopError.
*/
shouldCacheResult?: ShouldCacheResultFunction;
/**
* Hook that when TTL is calculated, allows to modify the TTL value.
*/
onTtl?: ResponseCacheOnTtlFunction<PluginContext>;
};

export type ResponseCacheOnTtlFunction<PluginContext> = (payload: {
ttl: number;
context: PluginContext;
}) => number;

/**
* Default function used for building the response cache key.
* It is exported here for advanced use-cases. E.g. if you want to short circuit and serve responses from the cache on a global level in order to completely by-pass the GraphQL flow.
Expand Down Expand Up @@ -306,6 +316,7 @@ export function useResponseCache<PluginContext extends Record<string, any> = {}>
buildResponseCacheKey = defaultBuildResponseCacheKey,
getDocumentString = defaultGetDocumentString,
shouldCacheResult = defaultShouldCacheResult,
onTtl,
includeExtensionMetadata = typeof process !== 'undefined'
? // eslint-disable-next-line dot-notation
process.env['NODE_ENV'] === 'development' || !!process.env['DEBUG']
Expand Down Expand Up @@ -562,7 +573,13 @@ export function useResponseCache<PluginContext extends Record<string, any> = {}>
}

// we only use the global ttl if no currentTtl has been determined.
const finalTtl = currentTtl ?? globalTtl;
let finalTtl = currentTtl ?? globalTtl;
if (onTtl) {
finalTtl = onTtl({
ttl: finalTtl,
context: onExecuteParams.args.contextValue,
});
}

if (skip || !shouldCacheResult({ cacheKey, result }) || finalTtl === 0) {
if (includeExtensionMetadata) {
Expand Down
57 changes: 57 additions & 0 deletions packages/plugins/response-cache/test/response-cache.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ import {
cacheControlDirective,
createInMemoryCache,
defaultBuildResponseCacheKey,
ResponseCacheOnTtlFunction,
useResponseCache,
} from '../src/index.js';

Expand Down Expand Up @@ -4230,3 +4231,59 @@ it('correctly remove cache keys from incremental delivery result', async () => {
},
});
});

it('manipulates the TTL', async () => {
const schema = makeExecutableSchema({
typeDefs: /* GraphQL */ `
type Query {
foo: String
}
`,
resolvers: {
Query: {
foo: () => 'bar',
},
},
});
const expectedFinalTtl = 5000;
const onTtlOriginal: ResponseCacheOnTtlFunction<unknown> = () => expectedFinalTtl;
const onTtl = jest.fn(onTtlOriginal);
const testkit = createTestkit(
[
useResponseCache({
session: () => null,
ttl: 1000,
ttlPerSchemaCoordinate: { 'Query.foo': 2000 },
includeExtensionMetadata: true,
onTtl,
}),
],
schema,
);

const operation = /* GraphQL */ `
query {
foo
}
`;

const context = {};

const result = await testkit.execute(operation, {}, context);
expect(result).toEqual({
data: {
foo: 'bar',
},
extensions: {
responseCache: {
didCache: true,
hit: false,
ttl: expectedFinalTtl,
},
},
});
expect(onTtl).toHaveBeenCalledWith({
ttl: 2000,
context,
});
});

0 comments on commit 7882ffb

Please sign in to comment.