Direct execution - Remove conditional schema (#19383)
This commit is contained in:
+2
-3
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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<TContext> = PromiseOrValue<GraphQLSchemaWithContext<TContext>> | ((context: TContext & YogaInitialContext) => PromiseOrValue<GraphQLSchemaWithContext<TContext>>);
|
||||
export type YogaDriverPlatform = 'express' | 'fastify';
|
||||
export type YogaDriverServerContext<Platform extends YogaDriverPlatform> = Platform extends 'fastify' ? {
|
||||
req: FastifyRequest;
|
||||
@@ -10,7 +11,10 @@ export type YogaDriverServerContext<Platform extends YogaDriverPlatform> = Platf
|
||||
req: ExpressRequest;
|
||||
res: ExpressResponse;
|
||||
};
|
||||
-export type YogaDriverServerOptions<Platform extends YogaDriverPlatform> = Omit<YogaServerOptions<YogaDriverServerContext<Platform>, never>, 'context' | 'schema'>;
|
||||
+
|
||||
+export type YogaDriverServerOptions<Platform extends YogaDriverPlatform> = Omit<YogaServerOptions<YogaDriverServerContext<Platform>, never>, 'context' | 'schema'> & {
|
||||
+ conditionalSchema?: YogaSchemaDefinition<YogaDriverServerContext<Platform>> | undefined;
|
||||
+};
|
||||
export type YogaDriverServerInstance<Platform extends YogaDriverPlatform> = YogaServerInstance<YogaDriverServerContext<Platform>, never>;
|
||||
export type YogaDriverConfig<Platform extends YogaDriverPlatform = 'express'> = GqlModuleOptions & YogaDriverServerOptions<Platform> & {
|
||||
/**
|
||||
@@ -26,10 +30,10 @@ export declare abstract class AbstractYogaDriver<Platform extends YogaDriverPlat
|
||||
protected yoga: YogaDriverServerInstance<Platform>;
|
||||
start(options: YogaDriverConfig<Platform>): Promise<void>;
|
||||
stop(): Promise<void>;
|
||||
- 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<TPayload, TVariables, TContext>(instanceRef: unknown, filterFn: (payload: TPayload, variables: TVariables, context: TContext) => boolean | Promise<boolean>, createSubscribeContext: Function): (args_0: TPayload, args_1: TVariables, args_2: TContext) => Promise<import("graphql-yoga").Repeater<TPayload, void, unknown>>;
|
||||
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<TContext> = PromiseOrValue<GraphQLSchemaWithContext<TContext>> | ((context: TContext & YogaInitialContext) => PromiseOrValue<GraphQLSchemaWithContext<TContext>>);
|
||||
export type YogaDriverPlatform = 'express' | 'fastify';
|
||||
export type YogaDriverServerContext<Platform extends YogaDriverPlatform> = Platform extends 'fastify' ? {
|
||||
req: FastifyRequest;
|
||||
@@ -10,7 +11,9 @@ export type YogaDriverServerContext<Platform extends YogaDriverPlatform> = Platf
|
||||
req: ExpressRequest;
|
||||
res: ExpressResponse;
|
||||
};
|
||||
-export type YogaDriverServerOptions<Platform extends YogaDriverPlatform> = Omit<YogaServerOptions<YogaDriverServerContext<Platform>, never>, 'context' | 'schema'>;
|
||||
+export type YogaDriverServerOptions<Platform extends YogaDriverPlatform> = Omit<YogaServerOptions<YogaDriverServerContext<Platform>, never>, 'context' | 'schema'> & {
|
||||
+ conditionalSchema?: YogaSchemaDefinition<YogaDriverServerContext<Platform>> | undefined;
|
||||
+};
|
||||
export type YogaDriverServerInstance<Platform extends YogaDriverPlatform> = YogaServerInstance<YogaDriverServerContext<Platform>, never>;
|
||||
export type YogaDriverConfig<Platform extends YogaDriverPlatform = 'express'> = GqlModuleOptions & YogaDriverServerOptions<Platform> & {
|
||||
/**
|
||||
@@ -26,10 +29,10 @@ export declare abstract class AbstractYogaDriver<Platform extends YogaDriverPlat
|
||||
protected yoga: YogaDriverServerInstance<Platform>;
|
||||
start(options: YogaDriverConfig<Platform>): Promise<void>;
|
||||
stop(): Promise<void>;
|
||||
- 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<TPayload, TVariables, TContext>(instanceRef: unknown, filterFn: (payload: TPayload, variables: TVariables, context: TContext) => boolean | Promise<boolean>, createSubscribeContext: Function): (args_0: TPayload, args_1: TVariables, args_2: TContext) => Promise<import("graphql-yoga").Repeater<TPayload, void, unknown>>;
|
||||
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<TContext> =
|
||||
+ | PromiseOrValue<GraphQLSchemaWithContext<TContext>>
|
||||
+ | ((
|
||||
+ context: TContext & YogaInitialContext,
|
||||
+ ) => PromiseOrValue<GraphQLSchemaWithContext<TContext>>);
|
||||
+
|
||||
export type YogaDriverPlatform = 'express' | 'fastify';
|
||||
|
||||
export type YogaDriverServerContext<Platform extends YogaDriverPlatform> =
|
||||
Platform extends 'fastify'
|
||||
- ? {
|
||||
- req: FastifyRequest;
|
||||
- reply: FastifyReply;
|
||||
- }
|
||||
- : {
|
||||
- req: ExpressRequest;
|
||||
- res: ExpressResponse;
|
||||
- };
|
||||
+ ? {
|
||||
+ req: FastifyRequest;
|
||||
+ reply: FastifyReply;
|
||||
+ }
|
||||
+ : {
|
||||
+ req: ExpressRequest;
|
||||
+ res: ExpressResponse;
|
||||
+ };
|
||||
|
||||
export type YogaDriverServerOptions<Platform extends YogaDriverPlatform> = Omit<
|
||||
YogaServerOptions<YogaDriverServerContext<Platform>, never>,
|
||||
'context' | 'schema'
|
||||
->;
|
||||
+> & {
|
||||
+ conditionalSchema?: YogaSchemaDefinition<YogaDriverServerContext<Platform>> | undefined;
|
||||
+};
|
||||
|
||||
export type YogaDriverServerInstance<Platform extends YogaDriverPlatform> = YogaServerInstance<
|
||||
YogaDriverServerContext<Platform>,
|
||||
@@ -53,6 +62,8 @@ export type YogaDriverSubscriptionConfig = {
|
||||
export abstract class AbstractYogaDriver<
|
||||
Platform extends YogaDriverPlatform,
|
||||
> extends AbstractGraphQLDriver<YogaDriverConfig<Platform>> {
|
||||
+ schemaCache = new Map();
|
||||
+
|
||||
protected yoga!: YogaDriverServerInstance<Platform>;
|
||||
|
||||
public async start(options: YogaDriverConfig<Platform>) {
|
||||
@@ -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<YogaDriverServerContext<'express'>>({
|
||||
...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<Platform>;
|
||||
@@ -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<YogaDriverServerContext<'fastify'>>({
|
||||
...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']) {
|
||||
+7
-2
@@ -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) {
|
||||
|
||||
+1
-68
@@ -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<YogaDriverConfig<'express'>>
|
||||
{
|
||||
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: () => ({
|
||||
|
||||
Reference in New Issue
Block a user