Prevent leak between /metadata and /graphql GQL schemas (#17845)

## Fix resolver schema leaking between `/metadata` and `/graphql`
endpoints

### Summary
- Patch `@nestjs/graphql` to support a `resolverSchemaScope` option that
filters resolvers at both schema generation and runtime, preventing
cross-endpoint leaking
- Introduce `@CoreResolver()` and `@MetadataResolver()` decorators to
explicitly scope each resolver to its endpoint
- Move most resolvers (auth, billing, workspace, user, etc.) to the
metadata schema where the frontend expects them; only workflow and
timeline calendar/messaging resolvers remain on `/graphql`
- Fix frontend `SSEQuerySubscribeEffect` to use the default (metadata)
Apollo client instead of the core client

### Problem
NestJS GraphQL's module-based resolver discovery traverses transitive
imports, causing resolvers from `/metadata` modules to leak into the
`/graphql` schema and vice versa. This made the schemas unpredictable
and tightly coupled to module import order.

### Approach
- Added `resolverSchemaScope` to `GqlModuleOptions` via a patch on
`@nestjs/graphql`, filtering in both `filterResolvers()` (runtime
binding) and `getAllCtors()` (schema generation)
- Each resolver is explicitly decorated with `@CoreResolver()` or
`@MetadataResolver()`
- Organized decorator, constant, and type files under `graphql-config/`
following project conventions


Core GQL Schema: (see: no more fields!)
<img width="827" height="894" alt="image"
src="https://github.com/user-attachments/assets/668f3f0f-485e-43f0-92be-4345aeccacb6"
/>

Metadata GQL Schema (see no more getTimelineCalendarEventsFromCompany)
<img width="827" height="894" alt="image"
src="https://github.com/user-attachments/assets/443913db-e5fe-4161-b0e7-4a971cc80a71"
/>
This commit is contained in:
Charles Bochet
2026-02-11 11:05:24 +01:00
committed by GitHub
parent 859241d237
commit 9e21e55db4
162 changed files with 1920 additions and 5681 deletions
@@ -1,4 +1,4 @@
import { type ConnectionParameters } from '~/generated/graphql';
import { type ConnectionParameters } from '~/generated-metadata/graphql';
export type ImapSmtpCaldavAccount = {
IMAP?: ConnectionParameters;
@@ -7,6 +7,7 @@ import {
import { useState } from 'react';
import { type ActivityTargetableObject } from '@/activities/types/ActivityTargetableEntity';
import { useApolloCoreClient } from '@/object-metadata/hooks/useApolloCoreClient';
import { CoreObjectNameSingular } from '@/object-metadata/types/CoreObjectNameSingular';
import { useSnackBar } from '@/ui/feedback/snack-bar-manager/hooks/useSnackBar';
@@ -37,6 +38,7 @@ export const useCustomResolver = <
fetchMoreRecords: () => Promise<void>;
} => {
const { enqueueErrorSnackBar } = useSnackBar();
const apolloCoreClient = useApolloCoreClient();
const [page, setPage] = useState({
pageNumber: 1,
@@ -62,6 +64,7 @@ export const useCustomResolver = <
loading: firstQueryLoading,
fetchMore,
} = useQuery<CustomResolverQueryResult<T>>(query, {
client: apolloCoreClient,
variables: queryVariables,
onError: (error) => {
enqueueErrorSnackBar({
@@ -8,7 +8,7 @@ import {
ANALYTICS_COOKIE_NAME,
useEventTracker,
} from '@/analytics/hooks/useEventTracker';
import { AnalyticsType } from '~/generated/graphql';
import { AnalyticsType } from '~/generated-metadata/graphql';
// Mock document.cookie
Object.defineProperty(document, 'cookie', {
@@ -37,7 +37,7 @@ import { FocusComponentType } from '@/ui/utilities/focus/types/FocusComponentTyp
import { useRecoilComponentValue } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentValue';
import { AppBasePath, AppPath } from 'twenty-shared/types';
import { isDefined } from 'twenty-shared/utils';
import { AnalyticsType } from '~/generated/graphql';
import { AnalyticsType } from '~/generated-metadata/graphql';
import { usePageChangeEffectNavigateLocation } from '~/hooks/usePageChangeEffectNavigateLocation';
import { useInitializeQueryParamState } from '~/modules/app/hooks/useInitializeQueryParamState';
import { isMatchingLocation } from '~/utils/isMatchingLocation';
@@ -1,7 +1,7 @@
import {
type UpdatePageLayoutWithTabsInput,
useUpdatePageLayoutWithTabsAndWidgetsMutation,
} from '~/generated/graphql';
} from '~/generated-metadata/graphql';
import { useMetadataErrorHandler } from '@/metadata-error-handler/hooks/useMetadataErrorHandler';
import { type MetadataRequestResult } from '@/object-metadata/types/MetadataRequestResult.type';
@@ -1,5 +1,5 @@
import { type DraftPageLayout } from '@/page-layout/types/DraftPageLayout';
import { type UpdatePageLayoutWithTabsInput } from '~/generated/graphql';
import { type UpdatePageLayoutWithTabsInput } from '~/generated-metadata/graphql';
export const convertPageLayoutDraftToUpdateInput = (
pageLayoutDraft: DraftPageLayout,
@@ -1,6 +1,6 @@
import { ACCOUNT_PROTOCOLS } from '@/settings/accounts/constants/AccountProtocols';
import { z } from 'zod';
import { type ConnectionParameters } from '~/generated/graphql';
import { type ConnectionParameters } from '~/generated-metadata/graphql';
const connectionParameters = z
.object({
@@ -1,6 +1,7 @@
import { CoreObjectNameSingular } from '@/object-metadata/types/CoreObjectNameSingular';
import { useObjectRecordSearchRecords } from '@/object-record/hooks/useObjectRecordSearchRecords';
import { SettingsRoleAssignmentWorkspaceMemberPickerDropdownContent } from '@/settings/roles/role-assignment/components/SettingsRoleAssignmentWorkspaceMemberPickerDropdownContent';
import { type PartialWorkspaceMember } from '@/settings/roles/types/RoleWithPartialMembers';
import { DropdownContent } from '@/ui/layout/dropdown/components/DropdownContent';
import { DropdownMenuItemsContainer } from '@/ui/layout/dropdown/components/DropdownMenuItemsContainer';
import { DropdownMenuSearchInput } from '@/ui/layout/dropdown/components/DropdownMenuSearchInput';
@@ -8,7 +9,6 @@ import { DropdownMenuSeparator } from '@/ui/layout/dropdown/components/DropdownM
import { GenericDropdownContentWidth } from '@/ui/layout/dropdown/constants/GenericDropdownContentWidth';
import { useLingui } from '@lingui/react/macro';
import { type ChangeEvent, useState } from 'react';
import { type PartialWorkspaceMember } from '@/settings/roles/types/RoleWithPartialMembers';
type SettingsRoleAssignmentWorkspaceMemberPickerDropdownProps = {
excludedWorkspaceMemberIds: string[];
@@ -1,4 +1,3 @@
import { useApolloCoreClient } from '@/object-metadata/hooks/useApolloCoreClient';
import { ADD_QUERY_TO_EVENT_STREAM_MUTATION } from '@/sse-db-event/graphql/mutations/AddQueryToEventStreamMutation';
import { REMOVE_QUERY_FROM_EVENT_STREAM_MUTATION } from '@/sse-db-event/graphql/mutations/RemoveQueryFromEventStreamMutation';
import { activeQueryListenersState } from '@/sse-db-event/states/activeQueryListenersState';
@@ -18,22 +17,20 @@ import { useDebouncedCallback } from 'use-debounce';
import {
type AddQuerySubscriptionInput,
type RemoveQueryFromEventStreamInput,
} from '~/generated/graphql';
} from '~/generated-metadata/graphql';
export const SSEQuerySubscribeEffect = () => {
const sseEventStreamId = useRecoilValue(sseEventStreamIdState);
const apolloCoreClient = useApolloCoreClient();
const [addQueryToEventStream] = useMutation<
boolean,
{ input: AddQuerySubscriptionInput }
>(ADD_QUERY_TO_EVENT_STREAM_MUTATION, { client: apolloCoreClient });
>(ADD_QUERY_TO_EVENT_STREAM_MUTATION);
const [removeQueryFromEventStream] = useMutation<
void,
{ input: RemoveQueryFromEventStreamInput }
>(REMOVE_QUERY_FROM_EVENT_STREAM_MUTATION, { client: apolloCoreClient });
>(REMOVE_QUERY_FROM_EVENT_STREAM_MUTATION);
const requiredQueryListeners = useRecoilValue(requiredQueryListenersState);
const activeQueryListeners = useRecoilValue(activeQueryListenersState);
@@ -4,7 +4,7 @@ import { useContext, useEffect } from 'react';
import {
type Subscription,
type SubscriptionOnDbEventArgs,
} from '~/generated/graphql';
} from '~/generated-metadata/graphql';
type OnDbEventArgs = SubscriptionOnDbEventArgs & {
skip?: boolean;
@@ -27,10 +27,10 @@ import { isDefined } from 'twenty-shared/utils';
import {
type WorkspaceMember,
useFindAllCoreViewsQuery,
useFindAllRecordPageLayoutsQuery,
useGetCurrentUserQuery,
useGetManyLogicFunctionsQuery,
} from '~/generated-metadata/graphql';
import { useFindAllRecordPageLayoutsQuery } from '~/generated/graphql';
import { dateLocaleState } from '~/localization/states/dateLocaleState';
import { dynamicActivate } from '~/utils/i18n/dynamicActivate';
import { isDeeplyEqual } from '~/utils/isDeeplyEqual';
@@ -15,7 +15,7 @@ import {
useCreateCoreViewMutation,
useDestroyCoreViewMutation,
ViewType,
} from '~/generated/graphql';
} from '~/generated-metadata/graphql';
export const usePerformViewAPIPersist = () => {
const [createCoreViewMutation] = useCreateCoreViewMutation();
@@ -13,7 +13,7 @@ import {
type CoreView,
type UpdateCoreViewMutationVariables,
useUpdateCoreViewMutation,
} from '~/generated/graphql';
} from '~/generated-metadata/graphql';
export const usePerformViewAPIUpdate = () => {
const [updateCoreViewMutation] = useUpdateCoreViewMutation();
@@ -17,7 +17,7 @@ import {
useDeleteCoreViewFieldMutation,
useDestroyCoreViewFieldMutation,
useUpdateCoreViewFieldMutation,
} from '~/generated/graphql';
} from '~/generated-metadata/graphql';
export const usePerformViewFieldAPIPersist = () => {
const { triggerViewFieldOptimisticEffect } =
useTriggerViewFieldOptimisticEffect();
@@ -17,7 +17,7 @@ import {
useDeleteCoreViewFilterMutation,
useDestroyCoreViewFilterMutation,
useUpdateCoreViewFilterMutation,
} from '~/generated/graphql';
} from '~/generated-metadata/graphql';
export const usePerformViewFilterAPIPersist = () => {
const { triggerViewFilterOptimisticEffect } =
@@ -11,7 +11,7 @@ import { isDefined } from 'twenty-shared/utils';
import {
type UpdateCoreViewGroupMutationVariables,
useUpdateCoreViewGroupMutation,
} from '~/generated/graphql';
} from '~/generated-metadata/graphql';
export const usePerformViewGroupAPIPersist = () => {
const { triggerViewGroupOptimisticEffect } =
@@ -3,7 +3,7 @@ import { coreViewsState } from '@/views/states/coreViewState';
import { type FetchPolicy, useApolloClient } from '@apollo/client';
import { useRecoilCallback } from 'recoil';
import { isDefined } from 'twenty-shared/utils';
import { type FindAllCoreViewsQuery } from '~/generated/graphql';
import { type FindAllCoreViewsQuery } from '~/generated-metadata/graphql';
import { isDeeplyEqual } from '~/utils/isDeeplyEqual';
export const useRefreshAllCoreViews = (
@@ -12,7 +12,7 @@ import { mapViewFieldToRecordField } from '@/views/utils/mapViewFieldToRecordFie
import { mapViewFiltersToFilters } from '@/views/utils/mapViewFiltersToFilters';
import { useRecoilCallback } from 'recoil';
import { isDefined, removePropertiesFromRecord } from 'twenty-shared/utils';
import { useFindManyCoreViewsLazyQuery } from '~/generated/graphql';
import { useFindManyCoreViewsLazyQuery } from '~/generated-metadata/graphql';
import { isDeeplyEqual } from '~/utils/isDeeplyEqual';
export const useRefreshCoreViewsByObjectMetadataId = () => {
@@ -9,7 +9,7 @@ import { type ViewField } from '@/views/types/ViewField';
import {
type CreateViewFieldInput,
type UpdateCoreViewFieldMutationVariables,
} from '~/generated/graphql';
} from '~/generated-metadata/graphql';
import { isDeeplyEqual } from '~/utils/isDeeplyEqual';
import { isUndefinedOrNull } from '~/utils/isUndefinedOrNull';
@@ -3,7 +3,7 @@ import { convertViewKeyToCore } from '@/views/utils/convertViewKeyToCore';
import { convertViewOpenRecordInToCore } from '@/views/utils/convertViewOpenRecordInToCore';
import { convertViewTypeToCore } from '@/views/utils/convertViewTypeToCore';
import { isDefined } from 'twenty-shared/utils';
import { type UpdateViewInput } from '~/generated/graphql';
import { type UpdateViewInput } from '~/generated-metadata/graphql';
export const convertUpdateViewInputToCore = (
view: Partial<GraphQLView & { __typename?: string }>,
@@ -19,7 +19,7 @@ import { isDefined } from 'twenty-shared/utils';
import {
type ActivateWorkflowVersionMutation,
type ActivateWorkflowVersionMutationVariables,
} from '~/generated-metadata/graphql';
} from '~/generated/graphql';
export const useActivateWorkflowVersion = () => {
const apolloCoreClient = useApolloCoreClient();
@@ -5,7 +5,7 @@ import {
type ComputeStepOutputSchemaInput,
type ComputeStepOutputSchemaMutation,
type ComputeStepOutputSchemaMutationVariables,
} from '~/generated-metadata/graphql';
} from '~/generated/graphql';
export const useComputeStepOutputSchema = () => {
const apolloCoreClient = useApolloCoreClient();
@@ -4,7 +4,7 @@ import { useFindManyRecordsQuery } from '@/object-record/hooks/useFindManyRecord
import {
type CreateDraftFromWorkflowVersionInput,
useCreateDraftFromWorkflowVersionMutation,
} from '~/generated-metadata/graphql';
} from '~/generated/graphql';
export const useCreateDraftFromWorkflowVersion = () => {
const apolloCoreClient = useApolloCoreClient();
@@ -19,7 +19,7 @@ import { isDefined } from 'twenty-shared/utils';
import {
type DeactivateWorkflowVersionMutation,
type DeactivateWorkflowVersionMutationVariables,
} from '~/generated-metadata/graphql';
} from '~/generated/graphql';
export const useDeactivateWorkflowVersion = () => {
const apolloCoreClient = useApolloCoreClient();
@@ -24,7 +24,7 @@ import { v4 } from 'uuid';
import {
type RunWorkflowVersionMutation,
type RunWorkflowVersionMutationVariables,
} from '~/generated-metadata/graphql';
} from '~/generated/graphql';
export const useRunWorkflowVersion = () => {
const apolloCoreClient = useApolloCoreClient();
@@ -2,7 +2,7 @@ import { useApolloCoreClient } from '@/object-metadata/hooks/useApolloCoreClient
import { useObjectMetadataItem } from '@/object-metadata/hooks/useObjectMetadataItem';
import { CoreObjectNameSingular } from '@/object-metadata/types/CoreObjectNameSingular';
import { dispatchObjectRecordOperationBrowserEvent } from '@/object-record/utils/dispatchObjectRecordOperationBrowserEvent';
import { useStopWorkflowRunMutation } from '~/generated-metadata/graphql';
import { useStopWorkflowRunMutation } from '~/generated/graphql';
export const useStopWorkflowRun = () => {
const apolloCoreClient = useApolloCoreClient();
@@ -3,9 +3,9 @@ import { useMutation } from '@apollo/client';
import {
type CreateWorkflowVersionEdgeMutation,
type CreateWorkflowVersionEdgeMutationVariables,
} from '~/generated-metadata/graphql';
type CreateWorkflowVersionEdgeInput,
} from '~/generated/graphql';
import { CREATE_WORKFLOW_VERSION_EDGE } from '@/workflow/graphql/mutations/createWorkflowVersionEdge';
import { type CreateWorkflowVersionEdgeInput } from '~/generated/graphql';
import { useUpdateWorkflowVersionCache } from '@/workflow/workflow-steps/hooks/useUpdateWorkflowVersionCache';
export const useCreateWorkflowVersionEdge = () => {
@@ -5,7 +5,7 @@ import {
type CreateWorkflowVersionStepInput,
type CreateWorkflowVersionStepMutation,
type CreateWorkflowVersionStepMutationVariables,
} from '~/generated-metadata/graphql';
} from '~/generated/graphql';
import { useUpdateWorkflowVersionCache } from '@/workflow/workflow-steps/hooks/useUpdateWorkflowVersionCache';
import { flowComponentState } from '@/workflow/states/flowComponentState';
import { useSetRecoilComponentState } from '@/ui/utilities/state/component-state/hooks/useSetRecoilComponentState';
@@ -1,11 +1,11 @@
import { useApolloCoreClient } from '@/object-metadata/hooks/useApolloCoreClient';
import { useMutation } from '@apollo/client';
import { type CreateWorkflowVersionEdgeInput } from '~/generated/graphql';
import { DELETE_WORKFLOW_VERSION_EDGE } from '@/workflow/graphql/mutations/deleteWorkflowVersionEdge';
import {
type CreateWorkflowVersionEdgeInput,
type DeleteWorkflowVersionEdgeMutation,
type DeleteWorkflowVersionEdgeMutationVariables,
} from '~/generated-metadata/graphql';
} from '~/generated/graphql';
import { DELETE_WORKFLOW_VERSION_EDGE } from '@/workflow/graphql/mutations/deleteWorkflowVersionEdge';
import { useUpdateWorkflowVersionCache } from '@/workflow/workflow-steps/hooks/useUpdateWorkflowVersionCache';
export const useDeleteWorkflowVersionEdge = () => {
@@ -8,7 +8,7 @@ import {
type DeleteWorkflowVersionStepInput,
type DeleteWorkflowVersionStepMutation,
type DeleteWorkflowVersionStepMutationVariables,
} from '~/generated-metadata/graphql';
} from '~/generated/graphql';
export const useDeleteWorkflowVersionStep = () => {
const apolloCoreClient = useApolloCoreClient();
@@ -9,7 +9,7 @@ import {
type DuplicateWorkflowVersionStepInput,
type DuplicateWorkflowVersionStepMutation,
type DuplicateWorkflowVersionStepMutationVariables,
} from '~/generated-metadata/graphql';
} from '~/generated/graphql';
export const useDuplicateWorkflowVersionStep = () => {
const apolloCoreClient = useApolloCoreClient();
@@ -13,7 +13,7 @@ import {
type UpdateWorkflowRunStepInput,
type UpdateWorkflowRunStepMutation,
type UpdateWorkflowRunStepMutationVariables,
} from '~/generated-metadata/graphql';
} from '~/generated/graphql';
export const useUpdateWorkflowRunStep = () => {
const apolloCoreClient = useApolloCoreClient();
@@ -16,7 +16,7 @@ import {
type UpdateWorkflowVersionStepInput,
type UpdateWorkflowVersionStepMutation,
type UpdateWorkflowVersionStepMutationVariables,
} from '~/generated-metadata/graphql';
} from '~/generated/graphql';
export const useUpdateWorkflowVersionStep = () => {
const apolloCoreClient = useApolloCoreClient();
@@ -7,7 +7,7 @@ import {
type SubmitFormStepInput,
type SubmitFormStepMutation,
type SubmitFormStepMutationVariables,
} from '~/generated-metadata/graphql';
} from '~/generated/graphql';
export const useSubmitFormStep = () => {
const apolloCoreClient = useApolloCoreClient();
@@ -15,7 +15,7 @@ import {
type TestHttpRequestInput,
type TestHttpRequestMutation,
type TestHttpRequestMutationVariables,
} from '~/generated-metadata/graphql';
} from '~/generated/graphql';
const convertFlatVariablesToNestedContext = (flatVariables: {
[variablePath: string]: any;
@@ -5,7 +5,7 @@ import { isDefined } from 'twenty-shared/utils';
import {
type UpdateWorkflowVersionPositionsMutation,
type UpdateWorkflowVersionPositionsMutationVariables,
} from '~/generated-metadata/graphql';
} from '~/generated/graphql';
import { useApolloCoreClient } from '@/object-metadata/hooks/useApolloCoreClient';
import { useObjectMetadataItem } from '@/object-metadata/hooks/useObjectMetadataItem';