5c745059ad
## Summary
- **Remove all "core" prefixes** from the views system — the
metadata-based storage migration is complete, so `CoreView`,
`coreViewsSelector`, `getCoreViews`, etc. are now just `View`,
`viewsSelector`, `getViews`
- **Eliminate the entire converter layer** (15 files, ~850 lines
deleted) — `convertCoreViewToView` and all sub-converters were either
no-ops or trivially adding `__typename` / mapping identical enum values.
Local enums now re-export from generated GraphQL types directly (single
source of truth)
- **Unify `View` and `ViewWithRelations`** into one type —
`ViewWithRelations` is now a type alias for `View`, selectors return
data directly without conversion
### Backend
- Rename `@ObjectType('CoreView')` → `@ObjectType('View')` (and all
sub-entities)
- Rename resolver methods: `getCoreViews` → `getViews`, `createCoreView`
→ `createView`, etc.
- Rename `FIND_ALL_CORE_VIEWS_GRAPHQL_OPERATION` →
`FIND_ALL_VIEWS_GRAPHQL_OPERATION`
### Frontend
- Delete 15 converter files (`convertGqlView*ToView*`,
`convertView*ToGql`, `convertViewWithRelationsToView`)
- Re-export `ViewType`, `ViewKey`, `ViewFilterGroupLogicalOperator` from
generated enums (no more duplicate enum definitions with different
casing)
- Replace `ViewOpenRecordInType` with `ViewOpenRecordIn` from generated
- Remove `__typename` from all local view sub-types
- Remove unused `variant` from `ViewFilter`, make `displayValue` and
`definition` optional
- Rename ~45 GraphQL query/mutation files and all selectors to drop
"core" prefix
- Delete unused `viewsWithRelationsSelector`
103 lines
3.1 KiB
TypeScript
103 lines
3.1 KiB
TypeScript
import { createHash } from 'crypto';
|
|
|
|
import { type Request } from 'express';
|
|
import { type Plugin } from 'graphql-yoga';
|
|
import { isDefined } from 'twenty-shared/utils';
|
|
|
|
import { InternalServerError } from 'src/engine/core-modules/graphql/utils/graphql-errors.util';
|
|
|
|
export type CacheMetadataPluginConfig = {
|
|
// oxlint-disable-next-line @typescripttypescript/no-explicit-any
|
|
cacheGetter: (key: string) => any;
|
|
// oxlint-disable-next-line @typescripttypescript/no-explicit-any
|
|
cacheSetter: (key: string, value: any) => void;
|
|
operationsToCache: string[];
|
|
};
|
|
|
|
export function useCachedMetadata(config: CacheMetadataPluginConfig): Plugin {
|
|
const computeCacheKey = ({
|
|
operationName,
|
|
request,
|
|
}: {
|
|
operationName: string;
|
|
request: Pick<Request, 'workspace' | 'locale' | 'body' | 'userWorkspaceId'>;
|
|
}) => {
|
|
const workspace = request.workspace;
|
|
|
|
if (!isDefined(workspace)) {
|
|
throw new InternalServerError('Workspace is not defined');
|
|
}
|
|
|
|
const workspaceMetadataVersion = workspace.metadataVersion ?? '0';
|
|
const locale = request.locale;
|
|
const queryHash = createHash('sha256')
|
|
.update(request.body.query)
|
|
.digest('hex');
|
|
|
|
if (operationName === 'FindAllViews') {
|
|
return `graphql:operations:${operationName}:${workspace.id}:${workspaceMetadataVersion}:${request.userWorkspaceId}:${queryHash}`;
|
|
}
|
|
|
|
return `graphql:operations:${operationName}:${workspace.id}:${workspaceMetadataVersion}:${locale}:${queryHash}`;
|
|
};
|
|
|
|
// oxlint-disable-next-line @typescripttypescript/no-explicit-any
|
|
const getOperationName = (serverContext: any) =>
|
|
serverContext?.req?.body?.operationName;
|
|
|
|
return {
|
|
onRequest: async ({ endResponse, serverContext }) => {
|
|
// TODO: we should probably override the graphql-yoga request type to include the workspace and locale
|
|
const request = (serverContext as unknown as { req: Request }).req;
|
|
|
|
if (!request.workspace?.id) {
|
|
return;
|
|
}
|
|
|
|
if (!config.operationsToCache.includes(getOperationName(serverContext))) {
|
|
return;
|
|
}
|
|
|
|
const cacheKey = computeCacheKey({
|
|
operationName: getOperationName(serverContext),
|
|
request,
|
|
});
|
|
const cachedResponse = await config.cacheGetter(cacheKey);
|
|
|
|
if (cachedResponse) {
|
|
const earlyResponse = Response.json(cachedResponse);
|
|
|
|
return endResponse(earlyResponse);
|
|
}
|
|
},
|
|
onResponse: async ({ response, serverContext }) => {
|
|
const request = (serverContext as unknown as { req: Request }).req;
|
|
|
|
if (!request.workspace?.id) {
|
|
return;
|
|
}
|
|
|
|
if (!config.operationsToCache.includes(getOperationName(serverContext))) {
|
|
return;
|
|
}
|
|
|
|
const cacheKey = computeCacheKey({
|
|
operationName: getOperationName(serverContext),
|
|
request,
|
|
});
|
|
|
|
const cachedResponse = await config.cacheGetter(cacheKey);
|
|
|
|
if (!cachedResponse) {
|
|
const responseBody = await response.json();
|
|
|
|
if (responseBody.errors) {
|
|
return;
|
|
}
|
|
|
|
config.cacheSetter(cacheKey, responseBody);
|
|
}
|
|
},
|
|
};
|
|
}
|