From ac1ec91f256f7edb4550c92090cc594dd63c1a03 Mon Sep 17 00:00:00 2001 From: Etienne <45695613+etiennejouan@users.noreply.github.com> Date: Tue, 7 Apr 2026 12:22:56 +0200 Subject: [PATCH] Direct execution - Remove conditional schema (#19383) --- packages/twenty-server/@types/express.d.ts | 5 +- .../patches/@graphql-yoga+nestjs+2.1.0.patch | 522 ------------------ .../hooks/use-direct-execution.hook.ts | 9 +- .../graphql-config/graphql-config.service.ts | 69 +-- 4 files changed, 10 insertions(+), 595 deletions(-) delete mode 100644 packages/twenty-server/patches/@graphql-yoga+nestjs+2.1.0.patch diff --git a/packages/twenty-server/@types/express.d.ts b/packages/twenty-server/@types/express.d.ts index 0e8b17ea31..3e7670da6c 100644 --- a/packages/twenty-server/@types/express.d.ts +++ b/packages/twenty-server/@types/express.d.ts @@ -1,11 +1,11 @@ import { type APP_LOCALES } from 'twenty-shared/translations'; import { type FlatApiKey } from 'src/engine/core-modules/api-key/types/flat-api-key.type'; +import { type ApplicationEntity } from 'src/engine/core-modules/application/application.entity'; +import { type RawAuthContext } from 'src/engine/core-modules/auth/types/auth-context.type'; import { type FlatAuthContextUser } from 'src/engine/core-modules/auth/types/flat-auth-context-user.type'; import { type FlatUserWorkspace } from 'src/engine/core-modules/user-workspace/types/flat-user-workspace.type'; import { type FlatWorkspace } from 'src/engine/core-modules/workspace/types/flat-workspace.type'; -import { type ApplicationEntity } from 'src/engine/core-modules/application/application.entity'; -import { type RawAuthContext } from 'src/engine/core-modules/auth/types/auth-context.type'; import { type AuthProviderEnum } from 'src/engine/core-modules/workspace/types/workspace.type'; import { type WorkspaceMemberWorkspaceEntity } from 'src/modules/workspace-member/standard-objects/workspace-member.workspace-entity'; @@ -24,6 +24,5 @@ declare module 'express-serve-static-core' { userWorkspaceId?: string; authProvider?: AuthProviderEnum | null; impersonationContext?: RawAuthContext['impersonationContext']; - skipWorkspaceSchemaCreation?: boolean; } } diff --git a/packages/twenty-server/patches/@graphql-yoga+nestjs+2.1.0.patch b/packages/twenty-server/patches/@graphql-yoga+nestjs+2.1.0.patch deleted file mode 100644 index 5ecfd4f10c..0000000000 --- a/packages/twenty-server/patches/@graphql-yoga+nestjs+2.1.0.patch +++ /dev/null @@ -1,522 +0,0 @@ -diff --git a/dist/cjs/index.js b/dist/cjs/index.js -index 1684394..8604546 100644 ---- a/dist/cjs/index.js -+++ b/dist/cjs/index.js -@@ -3,10 +3,14 @@ Object.defineProperty(exports, "__esModule", { value: true }); - exports.YogaDriver = exports.AbstractYogaDriver = void 0; - const tslib_1 = require("tslib"); - const graphql_1 = require("graphql"); -+const schema_1 = require("@graphql-tools/schema"); - const graphql_yoga_1 = require("graphql-yoga"); - const common_1 = require("@nestjs/common"); - const graphql_2 = require("@nestjs/graphql"); - class AbstractYogaDriver extends graphql_2.AbstractGraphQLDriver { -+ -+ schemaCache = new Map(); -+ - async start(options) { - const platformName = this.httpAdapterHost.httpAdapter.getType(); - options = { -@@ -27,7 +31,7 @@ class AbstractYogaDriver extends graphql_2.AbstractGraphQLDriver { - async stop() { - // noop - } -- registerExpress(options, { preStartHook } = {}) { -+ registerExpress({ conditionalSchema, ...options }, { preStartHook } = {}) { - const app = this.httpAdapterHost.httpAdapter.getInstance(); - preStartHook?.(app); - // nest's logger doesnt have the info method -@@ -42,6 +46,45 @@ class AbstractYogaDriver extends graphql_2.AbstractGraphQLDriver { - } - const yoga = (0, graphql_yoga_1.createYoga)({ - ...options, -+ schema: async (request) => { -+ const workspaceId = request.req.workspace?.id ?? 'anonymous' -+ const workspaceCacheVersion = request.req.workspaceMetadataVersion ?? '0' -+ const url = request.req.baseUrl -+ const applicationId = request.req.application?.id ?? 'all' -+ -+ const cacheKey = `${workspaceId}-${url}-${workspaceCacheVersion}-${applicationId}` -+ -+ if(this.schemaCache.has(cacheKey)) { -+ return this.schemaCache.get(cacheKey) -+ } -+ -+ const schemas = []; -+ -+ if (options.schema) { -+ schemas.push(options.schema); -+ } -+ -+ if (conditionalSchema) { -+ const conditionalSchemaResult = typeof conditionalSchema === 'function' ? await conditionalSchema(request) : await conditionalSchema; -+ if (conditionalSchemaResult) { -+ schemas.push(conditionalSchemaResult); -+ } -+ } -+ -+ const mergedSchemas = (0, schema_1.mergeSchemas)({ -+ schemas, -+ }); -+ -+ for (const key of this.schemaCache.keys()) { -+ if (key.startsWith(`${workspaceId}-${url}-`)) { -+ this.schemaCache.delete(key); -+ } -+ } -+ -+ this.schemaCache.set(cacheKey, mergedSchemas) -+ -+ return mergedSchemas; -+ }, - graphqlEndpoint: options.path, - // disable logging by default - // however, if `true` use nest logger -@@ -54,11 +97,50 @@ class AbstractYogaDriver extends graphql_2.AbstractGraphQLDriver { - this.yoga = yoga; - app.use(yoga.graphqlEndpoint, (req, res) => yoga(req, res, { req, res })); - } -- registerFastify(options, { preStartHook } = {}) { -+ registerFastify({ conditionalSchema, ...options }, { preStartHook } = {}) { - const app = this.httpAdapterHost.httpAdapter.getInstance(); - preStartHook?.(app); - const yoga = (0, graphql_yoga_1.createYoga)({ - ...options, -+ schema: async (request) => { -+ const workspaceId = request.req.workspace?.id ?? 'anonymous' -+ const workspaceCacheVersion = request.req.workspaceMetadataVersion ?? '0' -+ const url = request.req.baseUrl -+ const applicationId = request.req.application?.id ?? 'all' -+ -+ const cacheKey = `${workspaceId}-${url}-${workspaceCacheVersion}-${applicationId}` -+ -+ if(this.schemaCache.has(cacheKey)) { -+ return this.schemaCache.get(cacheKey) -+ } -+ -+ const schemas = []; -+ -+ if (options.schema) { -+ schemas.push(options.schema); -+ } -+ -+ if (conditionalSchema) { -+ const conditionalSchemaResult = typeof conditionalSchema === 'function' ? await conditionalSchema(request) : await conditionalSchema; -+ if (conditionalSchemaResult) { -+ schemas.push(conditionalSchemaResult); -+ } -+ } -+ -+ const mergedSchemas = (0, schema_1.mergeSchemas)({ -+ schemas, -+ }); -+ -+ for (const key of this.schemaCache.keys()) { -+ if (key.startsWith(`${workspaceId}-${url}-`)) { -+ this.schemaCache.delete(key); -+ } -+ } -+ -+ this.schemaCache.set(cacheKey, mergedSchemas) -+ -+ return mergedSchemas; -+ }, - graphqlEndpoint: options.path, - // disable logging by default - // however, if `true` use fastify logger -diff --git a/dist/esm/index.js b/dist/esm/index.js -index 7068c51..95b4fbe 100644 ---- a/dist/esm/index.js -+++ b/dist/esm/index.js -@@ -1,9 +1,13 @@ --import { __decorate } from "tslib"; --import { printSchema } from 'graphql'; --import { createYoga, filter, pipe } from 'graphql-yoga'; -+import { mergeSchemas } from '@graphql-tools/schema'; - import { Injectable, Logger } from '@nestjs/common'; - import { AbstractGraphQLDriver, GqlSubscriptionService, } from '@nestjs/graphql'; -+import { printSchema } from 'graphql'; -+import { createYoga, filter, pipe } from 'graphql-yoga'; -+import { __decorate } from "tslib"; - export class AbstractYogaDriver extends AbstractGraphQLDriver { -+ -+ schemaCache = new Map(); -+ - async start(options) { - const platformName = this.httpAdapterHost.httpAdapter.getType(); - options = { -@@ -24,7 +28,7 @@ export class AbstractYogaDriver extends AbstractGraphQLDriver { - async stop() { - // noop - } -- registerExpress(options, { preStartHook } = {}) { -+ registerExpress({ conditionalSchema, ...options }, { preStartHook } = {}) { - const app = this.httpAdapterHost.httpAdapter.getInstance(); - preStartHook?.(app); - // nest's logger doesnt have the info method -@@ -39,6 +43,46 @@ export class AbstractYogaDriver extends AbstractGraphQLDriver { - } - const yoga = createYoga({ - ...options, -+ schema: async (request) => { -+ const workspaceId = request.req.workspace?.id ?? 'anonymous' -+ const workspaceCacheVersion = request.req.workspaceMetadataVersion ?? '0' -+ const url = request.req.baseUrl -+ const applicationId = request.req.application?.id ?? 'all' -+ -+ const cacheKey = `${workspaceId}-${url}-${workspaceCacheVersion}-${applicationId}` -+ -+ if (this.schemaCache.has(cacheKey)) { -+ return this.schemaCache.get(cacheKey) -+ } -+ -+ const schemas = []; -+ -+ if (options.schema) { -+ schemas.push(options.schema); -+ } -+ -+ if (conditionalSchema) { -+ const conditionalSchemaResult = typeof conditionalSchema === 'function' ? await conditionalSchema(request) : await conditionalSchema; -+ -+ if (conditionalSchemaResult) { -+ schemas.push(conditionalSchemaResult); -+ } -+ } -+ -+ const mergedSchemas = mergeSchemas({ -+ schemas, -+ }); -+ -+ for (const key of this.schemaCache.keys()) { -+ if (key.startsWith(`${workspaceId}-${url}-`)) { -+ this.schemaCache.delete(key); -+ } -+ } -+ -+ this.schemaCache.set(cacheKey, mergedSchemas) -+ -+ return mergedSchemas; -+ }, - graphqlEndpoint: options.path, - // disable logging by default - // however, if `true` use nest logger -@@ -51,11 +95,51 @@ export class AbstractYogaDriver extends AbstractGraphQLDriver { - this.yoga = yoga; - app.use(yoga.graphqlEndpoint, (req, res) => yoga(req, res, { req, res })); - } -- registerFastify(options, { preStartHook } = {}) { -+ registerFastify({ conditionalSchema, ...options }, { preStartHook } = {}) { - const app = this.httpAdapterHost.httpAdapter.getInstance(); - preStartHook?.(app); - const yoga = createYoga({ - ...options, -+ schema: async (request) => { -+ const workspaceId = request.req.workspace?.id ?? 'anonymous' -+ const workspaceCacheVersion = request.req.workspaceMetadataVersion ?? '0' -+ const url = request.req.baseUrl -+ const applicationId = request.req.application?.id ?? 'all' -+ -+ const cacheKey = `${workspaceId}-${url}-${workspaceCacheVersion}-${applicationId}` -+ -+ if (this.schemaCache.has(cacheKey)) { -+ return this.schemaCache.get(cacheKey) -+ } -+ -+ const schemas = []; -+ -+ if (options.schema) { -+ schemas.push(options.schema); -+ } -+ -+ if (conditionalSchema) { -+ const conditionalSchemaResult = typeof conditionalSchema === 'function' ? await conditionalSchema(request) : await conditionalSchema; -+ -+ if (conditionalSchemaResult) { -+ schemas.push(conditionalSchemaResult); -+ } -+ } -+ -+ const mergedSchemas = mergeSchemas({ -+ schemas, -+ }); -+ -+ for (const key of this.schemaCache.keys()) { -+ if (key.startsWith(`${workspaceId}-${url}-`)) { -+ this.schemaCache.delete(key); -+ } -+ } -+ -+ this.schemaCache.set(cacheKey, mergedSchemas) -+ -+ return mergedSchemas; -+ }, - graphqlEndpoint: options.path, - // disable logging by default - // however, if `true` use fastify logger -diff --git a/dist/typings/index.d.cts b/dist/typings/index.d.cts -index 2c6a965..2f2b59f 100644 ---- a/dist/typings/index.d.cts -+++ b/dist/typings/index.d.cts -@@ -1,7 +1,8 @@ - import type { Express, Request as ExpressRequest, Response as ExpressResponse } from 'express'; - import type { FastifyInstance, FastifyReply, FastifyRequest } from 'fastify'; --import { YogaServerInstance, YogaServerOptions } from 'graphql-yoga'; -+import { YogaServerInstance, YogaServerOptions, GraphQLSchemaWithContext, PromiseOrValue, YogaInitialContext } from 'graphql-yoga'; - import { AbstractGraphQLDriver, GqlModuleOptions, SubscriptionConfig } from '@nestjs/graphql'; -+export type YogaSchemaDefinition = PromiseOrValue> | ((context: TContext & YogaInitialContext) => PromiseOrValue>); - export type YogaDriverPlatform = 'express' | 'fastify'; - export type YogaDriverServerContext = Platform extends 'fastify' ? { - req: FastifyRequest; -@@ -10,7 +11,10 @@ export type YogaDriverServerContext = Platf - req: ExpressRequest; - res: ExpressResponse; - }; --export type YogaDriverServerOptions = Omit, never>, 'context' | 'schema'>; -+ -+export type YogaDriverServerOptions = Omit, never>, 'context' | 'schema'> & { -+ conditionalSchema?: YogaSchemaDefinition> | undefined; -+}; - export type YogaDriverServerInstance = YogaServerInstance, never>; - export type YogaDriverConfig = GqlModuleOptions & YogaDriverServerOptions & { - /** -@@ -26,10 +30,10 @@ export declare abstract class AbstractYogaDriver; - start(options: YogaDriverConfig): Promise; - stop(): Promise; -- protected registerExpress(options: YogaDriverConfig<'express'>, { preStartHook }?: { -+ protected registerExpress({ conditionalSchema, ...options }: YogaDriverConfig<'express'>, { preStartHook }?: { - preStartHook?: (app: Express) => void; - }): void; -- protected registerFastify(options: YogaDriverConfig<'fastify'>, { preStartHook }?: { -+ protected registerFastify({ conditionalSchema, ...options }: YogaDriverConfig<'fastify'>, { preStartHook }?: { - preStartHook?: (app: FastifyInstance) => void; - }): void; - subscriptionWithFilter(instanceRef: unknown, filterFn: (payload: TPayload, variables: TVariables, context: TContext) => boolean | Promise, createSubscribeContext: Function): (args_0: TPayload, args_1: TVariables, args_2: TContext) => Promise>; -diff --git a/dist/typings/index.d.ts b/dist/typings/index.d.ts -index 2c6a965..fd86dac 100644 ---- a/dist/typings/index.d.ts -+++ b/dist/typings/index.d.ts -@@ -1,7 +1,8 @@ - import type { Express, Request as ExpressRequest, Response as ExpressResponse } from 'express'; - import type { FastifyInstance, FastifyReply, FastifyRequest } from 'fastify'; --import { YogaServerInstance, YogaServerOptions } from 'graphql-yoga'; -+import { YogaServerInstance, YogaServerOptions, GraphQLSchemaWithContext, PromiseOrValue, YogaInitialContext } from 'graphql-yoga'; - import { AbstractGraphQLDriver, GqlModuleOptions, SubscriptionConfig } from '@nestjs/graphql'; -+export type YogaSchemaDefinition = PromiseOrValue> | ((context: TContext & YogaInitialContext) => PromiseOrValue>); - export type YogaDriverPlatform = 'express' | 'fastify'; - export type YogaDriverServerContext = Platform extends 'fastify' ? { - req: FastifyRequest; -@@ -10,7 +11,9 @@ export type YogaDriverServerContext = Platf - req: ExpressRequest; - res: ExpressResponse; - }; --export type YogaDriverServerOptions = Omit, never>, 'context' | 'schema'>; -+export type YogaDriverServerOptions = Omit, never>, 'context' | 'schema'> & { -+ conditionalSchema?: YogaSchemaDefinition> | undefined; -+}; - export type YogaDriverServerInstance = YogaServerInstance, never>; - export type YogaDriverConfig = GqlModuleOptions & YogaDriverServerOptions & { - /** -@@ -26,10 +29,10 @@ export declare abstract class AbstractYogaDriver; - start(options: YogaDriverConfig): Promise; - stop(): Promise; -- protected registerExpress(options: YogaDriverConfig<'express'>, { preStartHook }?: { -+ protected registerExpress({ conditionalSchema, ...options }: YogaDriverConfig<'express'>, { preStartHook }?: { - preStartHook?: (app: Express) => void; - }): void; -- protected registerFastify(options: YogaDriverConfig<'fastify'>, { preStartHook }?: { -+ protected registerFastify({ conditionalSchema, ...options }: YogaDriverConfig<'fastify'>, { preStartHook }?: { - preStartHook?: (app: FastifyInstance) => void; - }): void; - subscriptionWithFilter(instanceRef: unknown, filterFn: (payload: TPayload, variables: TVariables, context: TContext) => boolean | Promise, createSubscribeContext: Function): (args_0: TPayload, args_1: TVariables, args_2: TContext) => Promise>; -diff --git a/src/index.ts b/src/index.ts -index ce142f6..95a1faf 100644 ---- a/src/index.ts -+++ b/src/index.ts -@@ -1,9 +1,10 @@ - import type { Express, Request as ExpressRequest, Response as ExpressResponse } from 'express'; - import type { FastifyInstance, FastifyReply, FastifyRequest } from 'fastify'; --import { printSchema } from 'graphql'; --import { createYoga, filter, pipe, YogaServerInstance, YogaServerOptions } from 'graphql-yoga'; -+import { printSchema, GraphQLSchema } from 'graphql'; -+import { createYoga, filter, pipe, YogaServerInstance, YogaServerOptions, GraphQLSchemaWithContext, PromiseOrValue, YogaInitialContext } from 'graphql-yoga'; - import type { ExecutionParams } from 'subscriptions-transport-ws'; - import { Injectable, Logger } from '@nestjs/common'; -+import { mergeSchemas } from '@graphql-tools/schema'; - import { - AbstractGraphQLDriver, - GqlModuleOptions, -@@ -11,23 +12,31 @@ import { - SubscriptionConfig, - } from '@nestjs/graphql'; - -+export type YogaSchemaDefinition = -+ | PromiseOrValue> -+ | (( -+ context: TContext & YogaInitialContext, -+ ) => PromiseOrValue>); -+ - export type YogaDriverPlatform = 'express' | 'fastify'; - - export type YogaDriverServerContext = - Platform extends 'fastify' -- ? { -- req: FastifyRequest; -- reply: FastifyReply; -- } -- : { -- req: ExpressRequest; -- res: ExpressResponse; -- }; -+ ? { -+ req: FastifyRequest; -+ reply: FastifyReply; -+ } -+ : { -+ req: ExpressRequest; -+ res: ExpressResponse; -+ }; - - export type YogaDriverServerOptions = Omit< - YogaServerOptions, never>, - 'context' | 'schema' -->; -+> & { -+ conditionalSchema?: YogaSchemaDefinition> | undefined; -+}; - - export type YogaDriverServerInstance = YogaServerInstance< - YogaDriverServerContext, -@@ -53,6 +62,8 @@ export type YogaDriverSubscriptionConfig = { - export abstract class AbstractYogaDriver< - Platform extends YogaDriverPlatform, - > extends AbstractGraphQLDriver> { -+ schemaCache = new Map(); -+ - protected yoga!: YogaDriverServerInstance; - - public async start(options: YogaDriverConfig) { -@@ -78,7 +89,7 @@ export abstract class AbstractYogaDriver< - } - - protected registerExpress( -- options: YogaDriverConfig<'express'>, -+ { conditionalSchema, ...options }: YogaDriverConfig<'express'>, - { preStartHook }: { preStartHook?: (app: Express) => void } = {}, - ) { - const app: Express = this.httpAdapterHost.httpAdapter.getInstance(); -@@ -98,6 +109,40 @@ export abstract class AbstractYogaDriver< - - const yoga = createYoga>({ - ...options, -+ schema: async request => { -+ const workspaceId = request.req.workspace.id -+ const workspaceCacheVersion = request.req.workspaceMetadataVersion -+ const url = request.req.baseUrl -+ const applicationId = request.req.application?.id ?? 'all' -+ -+ const cacheKey = `${workspaceId}-${url}-${workspaceCacheVersion}-${applicationId}` -+ -+ if (this.schemaCache.has(cacheKey)) { -+ return this.schemaCache.get(cacheKey) -+ } -+ -+ const schemas: GraphQLSchema[] = []; -+ -+ if (options.schema) { -+ schemas.push(options.schema); -+ } -+ -+ if (conditionalSchema) { -+ const conditionalSchemaResult = typeof conditionalSchema === 'function' ? await conditionalSchema(request) : await conditionalSchema; -+ -+ if (conditionalSchemaResult) { -+ schemas.push(conditionalSchemaResult); -+ } -+ } -+ -+ const mergedSchemas = mergeSchemas({ -+ schemas, -+ }); -+ -+ this.schemaCache.set(cacheKey, mergedSchemas) -+ -+ return mergedSchemas; -+ }, - graphqlEndpoint: options.path, - // disable logging by default - // however, if `true` use nest logger -@@ -105,8 +150,8 @@ export abstract class AbstractYogaDriver< - options.logging == null - ? false - : options.logging -- ? new LoggerWithInfo('YogaDriver') -- : options.logging, -+ ? new LoggerWithInfo('YogaDriver') -+ : options.logging, - }); - - this.yoga = yoga as YogaDriverServerInstance; -@@ -115,7 +160,7 @@ export abstract class AbstractYogaDriver< - } - - protected registerFastify( -- options: YogaDriverConfig<'fastify'>, -+ { conditionalSchema, ...options }: YogaDriverConfig<'fastify'>, - { preStartHook }: { preStartHook?: (app: FastifyInstance) => void } = {}, - ) { - const app: FastifyInstance = this.httpAdapterHost.httpAdapter.getInstance(); -@@ -124,6 +169,40 @@ export abstract class AbstractYogaDriver< - - const yoga = createYoga>({ - ...options, -+ schema: async request => { -+ const workspaceId = request.req.workspace.id -+ const workspaceCacheVersion = request.req.workspaceMetadataVersion -+ const url = request.req.baseUrl -+ const applicationId = request.req.application?.id ?? 'all' -+ -+ const cacheKey = `${workspaceId}-${url}-${workspaceCacheVersion}-${applicationId}` -+ -+ if (this.schemaCache.has(cacheKey)) { -+ return this.schemaCache.get(cacheKey) -+ } -+ -+ const schemas: GraphQLSchema[] = []; -+ -+ if (options.schema) { -+ schemas.push(options.schema); -+ } -+ -+ if (conditionalSchema) { -+ const conditionalSchemaResult = typeof conditionalSchema === 'function' ? await conditionalSchema(request) : await conditionalSchema; -+ -+ if (conditionalSchemaResult) { -+ schemas.push(conditionalSchemaResult); -+ } -+ } -+ -+ const mergedSchemas = mergeSchemas({ -+ schemas, -+ }); -+ -+ this.schemaCache.set(cacheKey, mergedSchemas) -+ -+ return mergedSchemas; -+ }, - graphqlEndpoint: options.path, - // disable logging by default - // however, if `true` use fastify logger -@@ -191,8 +270,8 @@ export class YogaDriver< - const config: SubscriptionConfig = - options.subscriptions === true - ? { -- 'graphql-ws': true, -- } -+ 'graphql-ws': true, -+ } - : options.subscriptions; - - if (config['graphql-ws']) { diff --git a/packages/twenty-server/src/engine/api/graphql/direct-execution/hooks/use-direct-execution.hook.ts b/packages/twenty-server/src/engine/api/graphql/direct-execution/hooks/use-direct-execution.hook.ts index 68a3437b45..f33b4f0c8d 100644 --- a/packages/twenty-server/src/engine/api/graphql/direct-execution/hooks/use-direct-execution.hook.ts +++ b/packages/twenty-server/src/engine/api/graphql/direct-execution/hooks/use-direct-execution.hook.ts @@ -8,6 +8,7 @@ import { classifyTopLevelFields } from 'src/engine/api/graphql/direct-execution/ import { findOperationDefinition } from 'src/engine/api/graphql/direct-execution/utils/find-operation-definition.util'; import { isSubscriptionOperation } from 'src/engine/api/graphql/direct-execution/utils/is-subscription-operation.util'; import { type FeatureFlagService } from 'src/engine/core-modules/feature-flag/services/feature-flag.service'; +import { UserInputError } from 'src/engine/core-modules/graphql/utils/graphql-errors.util'; export type DirectExecutionPluginConfig = { directExecutionService: DirectExecutionService; @@ -54,8 +55,12 @@ export function useDirectExecution( const { hasIntrospectionFields, hasWorkspaceFields, hasCoreFields } = classifyTopLevelFields(document, operationName, workspaceResolverNames); - if (!hasCoreFields) { - req.skipWorkspaceSchemaCreation = true; + if (hasCoreFields && hasWorkspaceFields) { + const error = new UserInputError( + 'This query cannot be executed as a single request. Please split it into separate queries.', + ); + + return endResponse(Response.json({ errors: [error.toJSON()] })); } if (hasCoreFields) { diff --git a/packages/twenty-server/src/engine/api/graphql/graphql-config/graphql-config.service.ts b/packages/twenty-server/src/engine/api/graphql/graphql-config/graphql-config.service.ts index 1b20a76072..c926a29e96 100644 --- a/packages/twenty-server/src/engine/api/graphql/graphql-config/graphql-config.service.ts +++ b/packages/twenty-server/src/engine/api/graphql/graphql-config/graphql-config.service.ts @@ -1,4 +1,4 @@ -import { Injectable, Logger, UnauthorizedException } from '@nestjs/common'; +import { Injectable } from '@nestjs/common'; import { ContextIdFactory, ModuleRef } from '@nestjs/core'; import { type GqlOptionsFactory } from '@nestjs/graphql'; @@ -7,14 +7,11 @@ import { type YogaDriverServerContext, } from '@graphql-yoga/nestjs'; import * as Sentry from '@sentry/node'; -import { GraphQLError, GraphQLSchema } from 'graphql'; import GraphQLJSON from 'graphql-type-json'; import { type GraphQLSchemaWithContext, type YogaInitialContext, } from 'graphql-yoga'; -import { JsonWebTokenError, TokenExpiredError } from 'jsonwebtoken'; -import { isDefined } from 'twenty-shared/utils'; import { NodeEnvironment } from 'src/engine/core-modules/twenty-config/interfaces/node-environment.interface'; @@ -35,7 +32,6 @@ import { MetricsKeys } from 'src/engine/core-modules/metrics/types/metrics-keys. import { TwentyConfigService } from 'src/engine/core-modules/twenty-config/twenty-config.service'; import { type FlatWorkspace } from 'src/engine/core-modules/workspace/types/flat-workspace.type'; import { DataloaderService } from 'src/engine/dataloaders/dataloader.service'; -import { handleExceptionAndConvertToGraphQLError } from 'src/engine/utils/global-exception-handler.util'; import { renderApolloPlayground } from 'src/engine/utils/render-apollo-playground.util'; export interface GraphQLContext extends YogaDriverServerContext<'express'> { @@ -47,8 +43,6 @@ export interface GraphQLContext extends YogaDriverServerContext<'express'> { export class GraphQLConfigService implements GqlOptionsFactory> { - private readonly logger = new Logger(GraphQLConfigService.name); - constructor( private readonly exceptionHandlerService: ExceptionHandlerService, private readonly twentyConfigService: TwentyConfigService, @@ -96,67 +90,6 @@ export class GraphQLConfigService include: [CoreEngineModule], resolverSchemaScope: 'core', buildSchemaOptions: {}, - conditionalSchema: async (context) => { - const { workspace, user, application, skipWorkspaceSchemaCreation } = - context.req; - - try { - if (!isDefined(workspace) || skipWorkspaceSchemaCreation) { - return new GraphQLSchema({}); - } - - this.logger.log( - `Creating schema for workspace ${workspace.id} for request ${context?.req?.body?.operationName}`, - ); - - return await this.createSchema(context, workspace, application?.id); - } catch (error) { - if (error instanceof UnauthorizedException) { - throw new GraphQLError('Unauthenticated', { - extensions: { - code: 'UNAUTHENTICATED', - }, - }); - } - - if (error instanceof JsonWebTokenError) { - //mockedUserJWT - throw new GraphQLError('Unauthenticated', { - extensions: { - code: 'UNAUTHENTICATED', - }, - }); - } - - if (error instanceof TokenExpiredError) { - throw new GraphQLError('Unauthenticated', { - extensions: { - code: 'UNAUTHENTICATED', - }, - }); - } - - throw handleExceptionAndConvertToGraphQLError( - error, - this.exceptionHandlerService, - isDefined(user) - ? { - id: user.id, - email: user.email, - firstName: user.firstName, - lastName: user.lastName, - } - : undefined, - isDefined(workspace) - ? { - id: workspace.id, - displayName: workspace.displayName, - activationStatus: workspace.activationStatus, - } - : undefined, - ); - } - }, resolvers: { JSON: GraphQLJSON }, plugins: plugins, context: () => ({