twenty/packages/twenty-server/patches/@graphql-yoga-nestjs-npm-2.1.0-cb509e6047.patch
Charles Bochet 17a1760afd
Improve performance twenty orm (#6691)
## Context

As we grow, the messaging scripts are experiencing performance issues
forcing us to temporarily disable them on the cloud.
While investigating the performance, I have noticed that generating the
entity schema (for twentyORM) in the repository is taking ~500ms locally
on my Mac M2 so likely more on pods. Caching the entitySchema then!

I'm also clarifying naming around schemaVersion and cacheVersions ==>
both are renamed workspaceMetadataVersion and migrated to the workspace
table (the workspaceCacheVersion table is dropped).
2024-08-20 19:42:02 +02:00

497 lines
22 KiB
Diff

diff --git a/dist/cjs/index.js b/dist/cjs/index.js
index 16843949d8589a299d8195b0a349ac4dac0bacbf..21e7fe2bbcba36b04a274be9d2219fd38790b508 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,40 @@ 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 workspaceUserId = request.req.user?.id ?? 'anonymous'
+ const url = request.req.baseUrl
+
+ const cacheKey = `${workspaceId}-${workspaceCacheVersion}-${workspaceUserId}-${url}`
+
+ 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,
+ });
+
+ this.schemaCache.set(cacheKey, mergedSchemas)
+
+ return mergedSchemas;
+ },
graphqlEndpoint: options.path,
// disable logging by default
// however, if `true` use nest logger
@@ -54,11 +91,45 @@ 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 workspaceUserId = request.req.user?.id ?? 'anonymous'
+ const url = request.req.baseUrl
+
+ const cacheKey = `${workspaceId}-${workspaceCacheVersion}-${workspaceUserId}-${url}`
+
+ 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,
+ });
+
+ 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 7068c519320b379917c46763cd280b1cdd3e48f0..418e1030373fc1e0fb85a932ac8da9b39f580570 100644
--- a/dist/esm/index.js
+++ b/dist/esm/index.js
@@ -2,8 +2,12 @@ import { __decorate } from "tslib";
import { printSchema } from 'graphql';
import { createYoga, filter, pipe } from 'graphql-yoga';
import { Injectable, Logger } from '@nestjs/common';
+import { mergeSchemas } from '@graphql-tools/schema';
import { AbstractGraphQLDriver, GqlSubscriptionService, } from '@nestjs/graphql';
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,40 @@ 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 workspaceUserId = request.req.user?.id ?? 'anonymous'
+ const url = request.req.baseUrl
+
+ const cacheKey = `${workspaceId}-${workspaceCacheVersion}-${workspaceUserId}-${url}`
+
+ 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,
+ });
+
+ this.schemaCache.set(cacheKey, mergedSchemas)
+
+ return mergedSchemas;
+ },
graphqlEndpoint: options.path,
// disable logging by default
// however, if `true` use nest logger
@@ -51,11 +88,45 @@ 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 workspaceUserId = request.req.user?.id ?? 'anonymous'
+ const url = request.req.baseUrl
+
+ const cacheKey = `${workspaceId}-${workspaceCacheVersion}-${workspaceUserId}-${url}`
+
+ 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,
+ });
+
+ 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 2c6a9656193392680121487c7147db459d6b69ab..2f2b59f0e311f0526a7cfdad97372229301aabd7 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 2c6a9656193392680121487c7147db459d6b69ab..fd86daccf3e5a93ff44b568c9793c16d761f4f53 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 ce142f61ede52499485b19d8af057f4cb828d0f7..5888d31cae1b7aca57ed0819209812ac941edabb 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 workspaceUserId = request.req.user?.id ?? 'anonymous'
+ const url = request.req.baseUrl
+
+ const cacheKey = `${workspaceId}-${workspaceCacheVersion}-${workspaceUserId}-${url}`
+
+ 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 +149,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 +159,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 +168,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 workspaceUserId = request.req.user?.id ?? 'anonymous'
+ const url = request.req.baseUrl
+
+ const cacheKey = `${workspaceId}-${workspaceCacheVersion}-${workspaceUserId}-${url}`
+
+ 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 +268,8 @@ export class YogaDriver<
const config: SubscriptionConfig =
options.subscriptions === true
? {
- 'graphql-ws': true,
- }
+ 'graphql-ws': true,
+ }
: options.subscriptions;
if (config['graphql-ws']) {