Files
twenty/packages/twenty-server/src/engine/api/graphql/graphql-config/hooks/use-cached-metadata.ts
Charles Bochet 4ac208cf1c Query dynamic cache key computation (#12814)
In this PR:
- add query hashKey to ObjectMetadataItems query graphql cache to avoid
caching outdated queries
- improve performance by removing ResolveField at FieldLevel and adding
this at resolver level
2025-06-24 12:04:00 +00:00

72 lines
2.3 KiB
TypeScript

import { createHash } from 'crypto';
import { isDefined } from 'class-validator';
import { Plugin } from 'graphql-yoga';
export type CacheMetadataPluginConfig = {
// eslint-disable-next-line @typescript-eslint/no-explicit-any
cacheGetter: (key: string) => any;
// eslint-disable-next-line @typescript-eslint/no-explicit-any
cacheSetter: (key: string, value: any) => void;
operationsToCache: string[];
};
export function useCachedMetadata(config: CacheMetadataPluginConfig): Plugin {
// eslint-disable-next-line @typescript-eslint/no-explicit-any
const computeCacheKey = (serverContext: any) => {
const workspaceId = serverContext.req.workspace?.id ?? 'anonymous';
const workspaceMetadataVersion =
serverContext.req.workspaceMetadataVersion ?? '0';
const operationName = getOperationName(serverContext);
const locale = serverContext.req.headers['x-locale'] ?? '';
const localeCacheKey = isDefined(serverContext.req.headers['x-locale'])
? `:${locale}`
: '';
const queryHash = createHash('sha256')
.update(serverContext.req.body.query)
.digest('hex');
return `graphql:operations:${operationName}:${workspaceId}:${workspaceMetadataVersion}${localeCacheKey}:${queryHash}`;
};
// eslint-disable-next-line @typescript-eslint/no-explicit-any
const getOperationName = (serverContext: any) =>
serverContext?.req?.body?.operationName;
return {
onRequest: async ({ endResponse, serverContext }) => {
if (!config.operationsToCache.includes(getOperationName(serverContext))) {
return;
}
const cacheKey = computeCacheKey(serverContext);
const cachedResponse = await config.cacheGetter(cacheKey);
if (cachedResponse) {
const earlyResponse = Response.json(cachedResponse);
return endResponse(earlyResponse);
}
},
onResponse: async ({ response, serverContext }) => {
if (!config.operationsToCache.includes(getOperationName(serverContext))) {
return;
}
const cacheKey = computeCacheKey(serverContext);
const cachedResponse = await config.cacheGetter(cacheKey);
if (!cachedResponse) {
const responseBody = await response.json();
if (responseBody.errors) {
return;
}
config.cacheSetter(cacheKey, responseBody);
}
},
};
}