Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
25 changes: 25 additions & 0 deletions .changeset/twenty-camels-wonder.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
---
'@envelop/response-cache': minor
---

Deprecate `ttlPerType` in favor of `ttlPerSchemaCoordinate`, for a more streamlined API

## Migration instructions

If you where using `ttlPerType`, you can merge the object into the `ttlPerSchemaCoordinate`, the
syntax doesn't change.

```diff
useResponseCache({
session: null,
- ttlPerType: {
- User: 10_000,
- Profile: 600_000,
- },
ttlPerSchemaCoordinate: {
'Query.me': 0
+ User: 10_000,
+ Profile: 600_000,
}
})
```
26 changes: 17 additions & 9 deletions packages/plugins/response-cache/src/plugin.ts
Original file line number Diff line number Diff line change
Expand Up @@ -68,9 +68,7 @@ export type UseResponseCacheParameter<PluginContext extends Record<string, any>
*/
ttl?: number;
/**
* Overwrite the ttl for query operations whose execution result contains a specific object type.
* Useful if the occurrence of a object time in the execution result should reduce or increase the TTL of the query operation.
* The TTL per type is always favored over the global TTL.
* @deprecated Use `ttlPerSchemaCoordinate` instead.
*/
ttlPerType?: Record<string, number>;
/**
Expand Down Expand Up @@ -308,7 +306,7 @@ export function useResponseCache<PluginContext extends Record<string, any> = {}>
session,
enabled,
ignoredTypes = [],
ttlPerType = {},
ttlPerType,
ttlPerSchemaCoordinate = {},
scopePerSchemaCoordinate = {},
idFields = ['id'],
Expand All @@ -329,6 +327,16 @@ export function useResponseCache<PluginContext extends Record<string, any> = {}>

// never cache Introspections
ttlPerSchemaCoordinate = { 'Query.__schema': 0, ...ttlPerSchemaCoordinate };
if (ttlPerType) {
// eslint-disable-next-line no-console
console.warn(
'[useResponseCache] `ttlForType` is deprecated. To migrate, merge it with `ttlForSchemaCoordinate` option',
);
for (const [typeName, ttl] of Object.entries(ttlPerType)) {
ttlPerSchemaCoordinate[typeName] = ttl;
}
}

const documentMetadataOptions = {
queries: { invalidateViaMutation, ttlPerSchemaCoordinate },
mutations: { invalidateViaMutation }, // remove ttlPerSchemaCoordinate for mutations to skip TTL calculation
Expand Down Expand Up @@ -366,7 +374,7 @@ export function useResponseCache<PluginContext extends Record<string, any> = {}>
) as unknown as CacheControlDirective[] | undefined;
cacheControlAnnotations?.forEach(cacheControl => {
if (cacheControl.maxAge != null) {
ttlPerType[type.name] = cacheControl.maxAge * 1000;
ttlPerSchemaCoordinate[type.name] = cacheControl.maxAge * 1000;
}
if (cacheControl.scope) {
scopePerSchemaCoordinate[type.name] = cacheControl.scope;
Expand Down Expand Up @@ -461,8 +469,8 @@ export function useResponseCache<PluginContext extends Record<string, any> = {}>
}

types.add(entity.typename);
if (entity.typename in ttlPerType) {
const maybeTtl = ttlPerType[entity.typename] as unknown;
if (entity.typename in ttlPerSchemaCoordinate) {
const maybeTtl = ttlPerSchemaCoordinate[entity.typename] as unknown;
currentTtl = calculateTtl(maybeTtl, currentTtl);
}
if (entity.id != null) {
Expand All @@ -473,8 +481,8 @@ export function useResponseCache<PluginContext extends Record<string, any> = {}>
if (fieldData == null || (Array.isArray(fieldData) && fieldData.length === 0)) {
const inferredTypes = typePerSchemaCoordinateMap.get(`${entity.typename}.${fieldName}`);
inferredTypes?.forEach(inferredType => {
if (inferredType in ttlPerType) {
const maybeTtl = ttlPerType[inferredType] as unknown;
if (inferredType in ttlPerSchemaCoordinate) {
const maybeTtl = ttlPerSchemaCoordinate[inferredType] as unknown;
currentTtl = calculateTtl(maybeTtl, currentTtl);
}
identifier.set(inferredType, { typename: inferredType });
Expand Down
88 changes: 87 additions & 1 deletion packages/plugins/response-cache/test/response-cache.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ import {
describe('useResponseCache', () => {
beforeEach(() => jest.useRealTimers());

it('custom ttl per type is used instead of the global ttl - only enable caching for a specific type when the global ttl is 0', async () => {
it('deprecated ttlForType still work until removed', async () => {
jest.useFakeTimers();
const spy = jest.fn(() => [
{
Expand Down Expand Up @@ -113,6 +113,92 @@ describe('useResponseCache', () => {
expect(spy).toHaveBeenCalledTimes(2);
});

it('custom ttl per type is used instead of the global ttl - only enable caching for a specific type when the global ttl is 0', async () => {
jest.useFakeTimers();
const spy = jest.fn(() => [
{
id: 1,
name: 'User 1',
comments: [
{
id: 1,
text: 'Comment 1 of User 1',
},
],
},
{
id: 2,
name: 'User 2',
comments: [
{
id: 2,
text: 'Comment 2 of User 2',
},
],
},
]);

const schema = makeExecutableSchema({
typeDefs: /* GraphQL */ `
type Query {
users: [User!]!
}

type User {
id: ID!
name: String!
comments: [Comment!]!
recentComment: Comment
}

type Comment {
id: ID!
text: String!
}
`,
resolvers: {
Query: {
users: spy,
},
},
});

const testInstance = createTestkit(
[
useResponseCache({
session: () => null,
ttl: 0,
ttlPerSchemaCoordinate: {
User: 200,
},
}),
],
schema,
);

const query = /* GraphQL */ `
query test {
users {
id
name
comments {
id
text
}
}
}
`;

await testInstance.execute(query);
await testInstance.execute(query);
expect(spy).toHaveBeenCalledTimes(1);
await testInstance.execute(query);
expect(spy).toHaveBeenCalledTimes(1);
jest.advanceTimersByTime(201);
await testInstance.execute(query);
expect(spy).toHaveBeenCalledTimes(2);
});

it('reuses the cache if the same query operation is executed in sequence without a TTL', async () => {
const spy = jest.fn(() => [
{
Expand Down