mirror of
https://github.com/lingble/twenty.git
synced 2025-11-18 15:05:00 +00:00
feat: add memory cache to boost performance (#2620)
* feat: add memory cache to boost performance * fix: tests * fix: logging * fix: missing commented stuff
This commit is contained in:
@@ -41,6 +41,7 @@
|
||||
"@nestjs/common": "^9.0.0",
|
||||
"@nestjs/config": "^2.3.2",
|
||||
"@nestjs/core": "^9.0.0",
|
||||
"@nestjs/event-emitter": "^2.0.3",
|
||||
"@nestjs/graphql": "^12.0.8",
|
||||
"@nestjs/jwt": "^10.0.3",
|
||||
"@nestjs/passport": "^9.0.3",
|
||||
|
||||
@@ -2,6 +2,7 @@ import { Module } from '@nestjs/common';
|
||||
import { GraphQLModule } from '@nestjs/graphql';
|
||||
import { ConfigModule } from '@nestjs/config';
|
||||
import { APP_FILTER, ContextIdFactory, ModuleRef } from '@nestjs/core';
|
||||
import { EventEmitterModule } from '@nestjs/event-emitter';
|
||||
|
||||
import { YogaDriver, YogaDriverConfig } from '@graphql-yoga/nestjs';
|
||||
import GraphQLJSON from 'graphql-type-json';
|
||||
@@ -9,6 +10,8 @@ import { GraphQLError, GraphQLSchema } from 'graphql';
|
||||
import { ExtractJwt } from 'passport-jwt';
|
||||
import { TokenExpiredError, JsonWebTokenError, verify } from 'jsonwebtoken';
|
||||
|
||||
import { WorkspaceFactory } from 'src/workspace/workspace.factory';
|
||||
|
||||
import { AppService } from './app.service';
|
||||
|
||||
import { CoreModule } from './core/core.module';
|
||||
@@ -20,7 +23,6 @@ import {
|
||||
JwtAuthStrategy,
|
||||
JwtPayload,
|
||||
} from './core/auth/strategies/jwt.auth.strategy';
|
||||
import { WorkspaceFactory } from './workspace/workspace.factory';
|
||||
import { ExceptionFilter } from './filters/exception.filter';
|
||||
|
||||
@Module({
|
||||
@@ -102,6 +104,7 @@ import { ExceptionFilter } from './filters/exception.filter';
|
||||
resolvers: { JSON: GraphQLJSON },
|
||||
plugins: [],
|
||||
}),
|
||||
EventEmitterModule.forRoot(),
|
||||
HealthModule,
|
||||
IntegrationsModule,
|
||||
CoreModule,
|
||||
|
||||
@@ -12,6 +12,7 @@ import { RefreshToken } from 'src/core/refresh-token/refresh-token.entity';
|
||||
export class TypeORMService implements OnModuleInit, OnModuleDestroy {
|
||||
private mainDataSource: DataSource;
|
||||
private dataSources: Map<string, DataSource> = new Map();
|
||||
private isDatasourceInitializing: Map<string, boolean> = new Map();
|
||||
|
||||
constructor(private readonly environmentService: EnvironmentService) {
|
||||
this.mainDataSource = new DataSource({
|
||||
@@ -35,23 +36,46 @@ export class TypeORMService implements OnModuleInit, OnModuleDestroy {
|
||||
public async connectToDataSource(
|
||||
dataSource: DataSourceEntity,
|
||||
): Promise<DataSource | undefined> {
|
||||
// Wait for a bit before trying again if another initialization is in progress
|
||||
while (this.isDatasourceInitializing.get(dataSource.id)) {
|
||||
await new Promise((resolve) => setTimeout(resolve, 10));
|
||||
}
|
||||
|
||||
if (this.dataSources.has(dataSource.id)) {
|
||||
return this.dataSources.get(dataSource.id);
|
||||
}
|
||||
|
||||
this.isDatasourceInitializing.set(dataSource.id, true);
|
||||
|
||||
try {
|
||||
const dataSourceInstance = await this.createAndInitializeDataSource(
|
||||
dataSource,
|
||||
);
|
||||
|
||||
this.dataSources.set(dataSource.id, dataSourceInstance);
|
||||
|
||||
return dataSourceInstance;
|
||||
} finally {
|
||||
this.isDatasourceInitializing.delete(dataSource.id);
|
||||
}
|
||||
}
|
||||
|
||||
private async createAndInitializeDataSource(
|
||||
dataSource: DataSourceEntity,
|
||||
): Promise<DataSource> {
|
||||
const schema = dataSource.schema;
|
||||
|
||||
const workspaceDataSource = new DataSource({
|
||||
url: dataSource.url ?? this.environmentService.getPGDatabaseUrl(),
|
||||
type: 'postgres',
|
||||
logging: ['query'],
|
||||
logging: this.environmentService.isDebugMode()
|
||||
? ['query', 'error']
|
||||
: ['error'],
|
||||
schema,
|
||||
});
|
||||
|
||||
await workspaceDataSource.initialize();
|
||||
|
||||
this.dataSources.set(dataSource.id, workspaceDataSource);
|
||||
|
||||
return workspaceDataSource;
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,3 @@
|
||||
export enum MemoryStorageType {
|
||||
Local = 'local',
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
import { Inject } from '@nestjs/common';
|
||||
|
||||
import { createMemoryStorageInjectionToken } from 'src/integrations/memory-storage/memory-storage.util';
|
||||
|
||||
export const InjectMemoryStorage = (identifier: string) => {
|
||||
const injectionToken = createMemoryStorageInjectionToken(identifier);
|
||||
|
||||
return Inject(injectionToken);
|
||||
};
|
||||
@@ -0,0 +1,5 @@
|
||||
export interface MemoryStorageDriver<T> {
|
||||
read(params: { key: string }): Promise<T | null>;
|
||||
write(params: { key: string; data: T }): Promise<void>;
|
||||
delete(params: { key: string }): Promise<void>;
|
||||
}
|
||||
@@ -0,0 +1,57 @@
|
||||
import { MemoryStorageSerializer } from 'src/integrations/memory-storage/serializers/interfaces/memory-storage-serializer.interface';
|
||||
|
||||
import { MemoryStorageDriver } from './interfaces/memory-storage-driver.interface';
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/no-empty-interface
|
||||
export interface LocalMemoryDriverOptions {}
|
||||
|
||||
export class LocalMemoryDriver<T> implements MemoryStorageDriver<T> {
|
||||
private identifier: string;
|
||||
private options: LocalMemoryDriverOptions;
|
||||
private serializer: MemoryStorageSerializer<T>;
|
||||
private storage: Map<string, string> = new Map();
|
||||
|
||||
constructor(
|
||||
identifier: string,
|
||||
options: LocalMemoryDriverOptions,
|
||||
serializer: MemoryStorageSerializer<T>,
|
||||
) {
|
||||
this.identifier = identifier;
|
||||
this.options = options;
|
||||
this.serializer = serializer;
|
||||
}
|
||||
|
||||
async write(params: { key: string; data: T }): Promise<void> {
|
||||
const compositeKey = this.generateCompositeKey(params.key);
|
||||
const serializedData = this.serializer.serialize(params.data);
|
||||
|
||||
this.storage.set(compositeKey, serializedData);
|
||||
}
|
||||
|
||||
async read(params: { key: string }): Promise<T | null> {
|
||||
const compositeKey = this.generateCompositeKey(params.key);
|
||||
|
||||
if (!this.storage.has(compositeKey)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const data = this.storage.get(compositeKey)!;
|
||||
const deserializeData = this.serializer.deserialize(data);
|
||||
|
||||
return deserializeData;
|
||||
}
|
||||
|
||||
async delete(params: { key: string }): Promise<void> {
|
||||
const compositeKey = this.generateCompositeKey(params.key);
|
||||
|
||||
if (!this.storage.has(compositeKey)) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.storage.delete(compositeKey);
|
||||
}
|
||||
|
||||
private generateCompositeKey(key: string): string {
|
||||
return `${this.identifier}:${key}`;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1 @@
|
||||
export * from './memory-storage.interface';
|
||||
@@ -0,0 +1,29 @@
|
||||
import { FactoryProvider, ModuleMetadata } from '@nestjs/common';
|
||||
|
||||
import { MemoryStorageType } from 'src/integrations/environment/interfaces/memory-storage.interface';
|
||||
import { MemoryStorageSerializer } from 'src/integrations/memory-storage/serializers/interfaces/memory-storage-serializer.interface';
|
||||
|
||||
import { LocalMemoryDriverOptions } from 'src/integrations/memory-storage/drivers/local.driver';
|
||||
|
||||
export interface LocalMemoryDriverFactoryOptions {
|
||||
type: MemoryStorageType.Local;
|
||||
options: LocalMemoryDriverOptions;
|
||||
}
|
||||
|
||||
interface MemoryStorageModuleBaseOptions {
|
||||
identifier: string;
|
||||
serializer?: MemoryStorageSerializer<any>;
|
||||
}
|
||||
|
||||
export type MemoryStorageModuleOptions = MemoryStorageModuleBaseOptions &
|
||||
LocalMemoryDriverFactoryOptions;
|
||||
|
||||
export type MemoryStorageModuleAsyncOptions = {
|
||||
identifier: string;
|
||||
useFactory: (
|
||||
...args: any[]
|
||||
) =>
|
||||
| Omit<MemoryStorageModuleOptions, 'identifier'>
|
||||
| Promise<Omit<MemoryStorageModuleOptions, 'identifier'>>;
|
||||
} & Pick<ModuleMetadata, 'imports'> &
|
||||
Pick<FactoryProvider, 'inject'>;
|
||||
@@ -0,0 +1 @@
|
||||
export const MEMORY_STORAGE_SERVICE = 'MEMORY_STORAGE_SERVICE';
|
||||
@@ -0,0 +1,73 @@
|
||||
import { DynamicModule, Global } from '@nestjs/common';
|
||||
|
||||
import { MemoryStorageType } from 'src/integrations/environment/interfaces/memory-storage.interface';
|
||||
|
||||
import { MemoryStorageDefaultSerializer } from 'src/integrations/memory-storage/serializers/default.serializer';
|
||||
import { createMemoryStorageInjectionToken } from 'src/integrations/memory-storage/memory-storage.util';
|
||||
|
||||
import {
|
||||
MemoryStorageModuleAsyncOptions,
|
||||
MemoryStorageModuleOptions,
|
||||
} from './interfaces';
|
||||
|
||||
import { LocalMemoryDriver } from './drivers/local.driver';
|
||||
|
||||
@Global()
|
||||
export class MemoryStorageModule {
|
||||
static forRoot(options: MemoryStorageModuleOptions): DynamicModule {
|
||||
// Dynamic injection token to allow multiple instances of the same driver
|
||||
const injectionToken = createMemoryStorageInjectionToken(
|
||||
options.identifier,
|
||||
);
|
||||
const provider = {
|
||||
provide: injectionToken,
|
||||
useValue: this.createStorageDriver(options),
|
||||
};
|
||||
|
||||
return {
|
||||
module: MemoryStorageModule,
|
||||
providers: [provider],
|
||||
exports: [provider],
|
||||
};
|
||||
}
|
||||
|
||||
static forRootAsync(options: MemoryStorageModuleAsyncOptions): DynamicModule {
|
||||
// Dynamic injection token to allow multiple instances of the same driver
|
||||
const injectionToken = createMemoryStorageInjectionToken(
|
||||
options.identifier,
|
||||
);
|
||||
const provider = {
|
||||
provide: injectionToken,
|
||||
useFactory: async (...args: any[]) => {
|
||||
const config = await options.useFactory(...args);
|
||||
|
||||
return this.createStorageDriver({
|
||||
identifier: options.identifier,
|
||||
...config,
|
||||
});
|
||||
},
|
||||
inject: options.inject || [],
|
||||
};
|
||||
|
||||
return {
|
||||
module: MemoryStorageModule,
|
||||
imports: options.imports || [],
|
||||
providers: [provider],
|
||||
exports: [provider],
|
||||
};
|
||||
}
|
||||
|
||||
private static createStorageDriver(options: MemoryStorageModuleOptions) {
|
||||
switch (options.type) {
|
||||
case MemoryStorageType.Local:
|
||||
return new LocalMemoryDriver(
|
||||
options.identifier,
|
||||
options.options,
|
||||
options.serializer ?? new MemoryStorageDefaultSerializer<string>(),
|
||||
);
|
||||
// Future case for Redis or other types
|
||||
default:
|
||||
throw new Error(`Unsupported storage type: ${options.type}`);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,19 @@
|
||||
import { Test, TestingModule } from '@nestjs/testing';
|
||||
|
||||
import { MemoryStorageService } from './memory-storage.service';
|
||||
|
||||
describe('MemoryStorageService', () => {
|
||||
let service: MemoryStorageService<any>;
|
||||
|
||||
beforeEach(async () => {
|
||||
const module: TestingModule = await Test.createTestingModule({
|
||||
providers: [MemoryStorageService],
|
||||
}).compile();
|
||||
|
||||
service = module.get<MemoryStorageService<any>>(MemoryStorageService);
|
||||
});
|
||||
|
||||
it('should be defined', () => {
|
||||
expect(service).toBeDefined();
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,21 @@
|
||||
import { MemoryStorageDriver } from 'src/integrations/memory-storage/drivers/interfaces/memory-storage-driver.interface';
|
||||
|
||||
export class MemoryStorageService<T> implements MemoryStorageDriver<T> {
|
||||
private driver: MemoryStorageDriver<T>;
|
||||
|
||||
constructor(driver: MemoryStorageDriver<T>) {
|
||||
this.driver = driver;
|
||||
}
|
||||
|
||||
write(params: { key: string; data: T }): Promise<void> {
|
||||
return this.driver.write(params);
|
||||
}
|
||||
|
||||
read(params: { key: string }): Promise<T | null> {
|
||||
return this.driver.read(params);
|
||||
}
|
||||
|
||||
delete(params: { key: string }): Promise<void> {
|
||||
return this.driver.delete(params);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,5 @@
|
||||
import { MEMORY_STORAGE_SERVICE } from 'src/integrations/memory-storage/memory-storage.constants';
|
||||
|
||||
export const createMemoryStorageInjectionToken = (identifier: string) => {
|
||||
return `${MEMORY_STORAGE_SERVICE}_${identifier}`;
|
||||
};
|
||||
@@ -0,0 +1,16 @@
|
||||
import { MemoryStorageSerializer } from 'src/integrations/memory-storage/serializers/interfaces/memory-storage-serializer.interface';
|
||||
|
||||
export class MemoryStorageDefaultSerializer<T>
|
||||
implements MemoryStorageSerializer<T>
|
||||
{
|
||||
serialize(item: T): string {
|
||||
if (typeof item !== 'string') {
|
||||
throw new Error('DefaultSerializer can only serialize strings');
|
||||
}
|
||||
return item;
|
||||
}
|
||||
|
||||
deserialize(data: string): T {
|
||||
return data as unknown as T;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,4 @@
|
||||
export interface MemoryStorageSerializer<T> {
|
||||
serialize(item: T): string;
|
||||
deserialize(data: string): T;
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
import { MemoryStorageSerializer } from 'src/integrations/memory-storage/serializers/interfaces/memory-storage-serializer.interface';
|
||||
|
||||
export class MemoryStorageJsonSerializer<T>
|
||||
implements MemoryStorageSerializer<T>
|
||||
{
|
||||
serialize(item: T): string {
|
||||
return JSON.stringify(item);
|
||||
}
|
||||
|
||||
deserialize(data: string): T {
|
||||
return JSON.parse(data) as T;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
export class WorkspaceMigrationAppliedEvent {
|
||||
private readonly _workspaceId: string;
|
||||
|
||||
constructor(worskapceId: string) {
|
||||
this._workspaceId = worskapceId;
|
||||
}
|
||||
|
||||
get workspaceId(): string {
|
||||
return this._workspaceId;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
export enum WorkspaceMigrationEvents {
|
||||
MigrationApplied = '@workspace/migration-applied',
|
||||
}
|
||||
@@ -1,4 +1,5 @@
|
||||
import { Injectable } from '@nestjs/common';
|
||||
import { EventEmitter2 } from '@nestjs/event-emitter';
|
||||
|
||||
import {
|
||||
QueryRunner,
|
||||
@@ -17,6 +18,8 @@ import {
|
||||
WorkspaceMigrationColumnCreate,
|
||||
WorkspaceMigrationColumnRelation,
|
||||
} from 'src/metadata/workspace-migration/workspace-migration.entity';
|
||||
import { WorkspaceMigrationEvents } from 'src/workspace/workspace-migration-runner/events/workspace-migration-events';
|
||||
import { WorkspaceMigrationAppliedEvent } from 'src/workspace/workspace-migration-runner/events/workspace-migration-applied.event';
|
||||
|
||||
import { customTableDefaultColumns } from './utils/custom-table-default-column.util';
|
||||
|
||||
@@ -25,6 +28,7 @@ export class WorkspaceMigrationRunnerService {
|
||||
constructor(
|
||||
private readonly workspaceDataSourceService: WorkspaceDataSourceService,
|
||||
private readonly workspaceMigrationService: WorkspaceMigrationService,
|
||||
private readonly eventEmitter: EventEmitter2,
|
||||
) {}
|
||||
|
||||
/**
|
||||
@@ -78,6 +82,12 @@ export class WorkspaceMigrationRunnerService {
|
||||
|
||||
await queryRunner.release();
|
||||
|
||||
// Emit event when migration is applied
|
||||
this.eventEmitter.emit(
|
||||
WorkspaceMigrationEvents.MigrationApplied,
|
||||
new WorkspaceMigrationAppliedEvent(workspaceId),
|
||||
);
|
||||
|
||||
return flattenedPendingMigrations;
|
||||
}
|
||||
|
||||
|
||||
@@ -142,13 +142,9 @@ export class WorkspaceQueryRunnerService {
|
||||
)};
|
||||
`);
|
||||
|
||||
const queryFormatted = query
|
||||
.replace('neq:null', 'is:NOT_NULL')
|
||||
.replace('eq:null', 'is:NULL');
|
||||
|
||||
const results = await workspaceDataSource?.query<PGGraphQLResult>(`
|
||||
SELECT graphql.resolve($$
|
||||
${queryFormatted}
|
||||
${query}
|
||||
$$);
|
||||
`);
|
||||
|
||||
|
||||
@@ -0,0 +1,29 @@
|
||||
import { Module } from '@nestjs/common';
|
||||
|
||||
import { MemoryStorageType } from 'src/integrations/environment/interfaces/memory-storage.interface';
|
||||
|
||||
import { MemoryStorageModule } from 'src/integrations/memory-storage/memory-storage.module';
|
||||
import { MemoryStorageJsonSerializer } from 'src/integrations/memory-storage/serializers/json.serializer';
|
||||
import { ObjectMetadataEntity } from 'src/metadata/object-metadata/object-metadata.entity';
|
||||
import { ObjectMetadataModule } from 'src/metadata/object-metadata/object-metadata.module';
|
||||
import { WorkspaceSchemaStorageService } from 'src/workspace/workspace-schema-storage/workspace-schema-storage.service';
|
||||
|
||||
@Module({
|
||||
imports: [
|
||||
ObjectMetadataModule,
|
||||
MemoryStorageModule.forRoot({
|
||||
identifier: 'objectMetadataCollection',
|
||||
type: MemoryStorageType.Local,
|
||||
options: {},
|
||||
serializer: new MemoryStorageJsonSerializer<ObjectMetadataEntity[]>(),
|
||||
}),
|
||||
MemoryStorageModule.forRoot({
|
||||
identifier: 'typeDefs',
|
||||
type: MemoryStorageType.Local,
|
||||
options: {},
|
||||
}),
|
||||
],
|
||||
providers: [WorkspaceSchemaStorageService],
|
||||
exports: [WorkspaceSchemaStorageService],
|
||||
})
|
||||
export class WorkspaceSchemaStorageModule {}
|
||||
@@ -0,0 +1,60 @@
|
||||
import { Injectable } from '@nestjs/common';
|
||||
import { OnEvent } from '@nestjs/event-emitter';
|
||||
|
||||
import { InjectMemoryStorage } from 'src/integrations/memory-storage/decorators/inject-memory-storage.decorator';
|
||||
import { MemoryStorageService } from 'src/integrations/memory-storage/memory-storage.service';
|
||||
import { ObjectMetadataEntity } from 'src/metadata/object-metadata/object-metadata.entity';
|
||||
import { WorkspaceMigrationAppliedEvent } from 'src/workspace/workspace-migration-runner/events/workspace-migration-applied.event';
|
||||
import { WorkspaceMigrationEvents } from 'src/workspace/workspace-migration-runner/events/workspace-migration-events';
|
||||
|
||||
@Injectable()
|
||||
export class WorkspaceSchemaStorageService {
|
||||
constructor(
|
||||
@InjectMemoryStorage('objectMetadataCollection')
|
||||
private readonly objectMetadataMemoryStorageService: MemoryStorageService<
|
||||
ObjectMetadataEntity[]
|
||||
>,
|
||||
@InjectMemoryStorage('typeDefs')
|
||||
private readonly typeDefsMemoryStorageService: MemoryStorageService<string>,
|
||||
) {}
|
||||
|
||||
setObjectMetadata(
|
||||
workspaceId: string,
|
||||
objectMetadata: ObjectMetadataEntity[],
|
||||
) {
|
||||
return this.objectMetadataMemoryStorageService.write({
|
||||
key: workspaceId,
|
||||
data: objectMetadata,
|
||||
});
|
||||
}
|
||||
|
||||
getObjectMetadata(
|
||||
workspaceId: string,
|
||||
): Promise<ObjectMetadataEntity[] | null> {
|
||||
return this.objectMetadataMemoryStorageService.read({
|
||||
key: workspaceId,
|
||||
});
|
||||
}
|
||||
|
||||
setTypeDefs(workspaceId: string, typeDefs: string): Promise<void> {
|
||||
return this.typeDefsMemoryStorageService.write({
|
||||
key: workspaceId,
|
||||
data: typeDefs,
|
||||
});
|
||||
}
|
||||
|
||||
getTypeDefs(workspaceId: string): Promise<string | null> {
|
||||
return this.typeDefsMemoryStorageService.read({
|
||||
key: workspaceId,
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Clear the workspace schema storage when new migrations are applied for a specific workspace
|
||||
*/
|
||||
@OnEvent(WorkspaceMigrationEvents.MigrationApplied)
|
||||
handleMigrationAppliedEvent({ workspaceId }: WorkspaceMigrationAppliedEvent) {
|
||||
this.objectMetadataMemoryStorageService.delete({ key: workspaceId });
|
||||
this.typeDefsMemoryStorageService.delete({ key: workspaceId });
|
||||
}
|
||||
}
|
||||
@@ -2,6 +2,7 @@ import { Test, TestingModule } from '@nestjs/testing';
|
||||
|
||||
import { DataSourceService } from 'src/metadata/data-source/data-source.service';
|
||||
import { ObjectMetadataService } from 'src/metadata/object-metadata/object-metadata.service';
|
||||
import { WorkspaceSchemaStorageService } from 'src/workspace/workspace-schema-storage/workspace-schema-storage.service';
|
||||
|
||||
import { WorkspaceFactory } from './workspace.factory';
|
||||
|
||||
@@ -31,6 +32,10 @@ describe('WorkspaceFactory', () => {
|
||||
provide: WorkspaceResolverFactory,
|
||||
useValue: {},
|
||||
},
|
||||
{
|
||||
provide: WorkspaceSchemaStorageService,
|
||||
useValue: {},
|
||||
},
|
||||
],
|
||||
}).compile();
|
||||
|
||||
|
||||
@@ -5,6 +5,7 @@ import { makeExecutableSchema } from '@graphql-tools/schema';
|
||||
import { gql } from 'graphql-tag';
|
||||
|
||||
import { DataSourceService } from 'src/metadata/data-source/data-source.service';
|
||||
import { WorkspaceSchemaStorageService } from 'src/workspace/workspace-schema-storage/workspace-schema-storage.service';
|
||||
import { ObjectMetadataService } from 'src/metadata/object-metadata/object-metadata.service';
|
||||
|
||||
import { WorkspaceGraphQLSchemaFactory } from './workspace-schema-builder/workspace-graphql-schema.factory';
|
||||
@@ -18,6 +19,7 @@ export class WorkspaceFactory {
|
||||
private readonly objectMetadataService: ObjectMetadataService,
|
||||
private readonly workspaceGraphQLSchemaFactory: WorkspaceGraphQLSchemaFactory,
|
||||
private readonly workspaceResolverFactory: WorkspaceResolverFactory,
|
||||
private readonly workspaceSchemaStorageService: WorkspaceSchemaStorageService,
|
||||
) {}
|
||||
|
||||
async createGraphQLSchema(
|
||||
@@ -37,15 +39,43 @@ export class WorkspaceFactory {
|
||||
return new GraphQLSchema({});
|
||||
}
|
||||
|
||||
const objectMetadataCollection =
|
||||
await this.objectMetadataService.getObjectMetadataFromWorkspaceId(
|
||||
workspaceId,
|
||||
);
|
||||
// Get object metadata from cache
|
||||
let objectMetadataCollection =
|
||||
await this.workspaceSchemaStorageService.getObjectMetadata(workspaceId);
|
||||
|
||||
const autoGeneratedSchema = await this.workspaceGraphQLSchemaFactory.create(
|
||||
objectMetadataCollection,
|
||||
workspaceResolverBuilderMethodNames,
|
||||
// If object metadata is not cached, get it from the database
|
||||
if (!objectMetadataCollection) {
|
||||
objectMetadataCollection =
|
||||
await this.objectMetadataService.getObjectMetadataFromWorkspaceId(
|
||||
workspaceId,
|
||||
);
|
||||
|
||||
await this.workspaceSchemaStorageService.setObjectMetadata(
|
||||
workspaceId,
|
||||
objectMetadataCollection,
|
||||
);
|
||||
}
|
||||
|
||||
// Get typeDefs from cache
|
||||
let typeDefs = await this.workspaceSchemaStorageService.getTypeDefs(
|
||||
workspaceId,
|
||||
);
|
||||
|
||||
// If typeDefs are not cached, generate them
|
||||
if (!typeDefs) {
|
||||
const autoGeneratedSchema =
|
||||
await this.workspaceGraphQLSchemaFactory.create(
|
||||
objectMetadataCollection,
|
||||
workspaceResolverBuilderMethodNames,
|
||||
);
|
||||
typeDefs = printSchema(autoGeneratedSchema);
|
||||
|
||||
await this.workspaceSchemaStorageService.setTypeDefs(
|
||||
workspaceId,
|
||||
typeDefs,
|
||||
);
|
||||
}
|
||||
|
||||
const autoGeneratedResolvers = await this.workspaceResolverFactory.create(
|
||||
workspaceId,
|
||||
objectMetadataCollection,
|
||||
@@ -53,7 +83,6 @@ export class WorkspaceFactory {
|
||||
);
|
||||
|
||||
// TODO: Cache the generate type definitions
|
||||
const typeDefs = printSchema(autoGeneratedSchema);
|
||||
const executableSchema = makeExecutableSchema({
|
||||
typeDefs: gql`
|
||||
${typeDefs}
|
||||
|
||||
@@ -2,6 +2,7 @@ import { Module } from '@nestjs/common';
|
||||
|
||||
import { MetadataModule } from 'src/metadata/metadata.module';
|
||||
import { DataSourceModule } from 'src/metadata/data-source/data-source.module';
|
||||
import { WorkspaceSchemaStorageModule } from 'src/workspace/workspace-schema-storage/workspace-schema-storage.module';
|
||||
import { ObjectMetadataModule } from 'src/metadata/object-metadata/object-metadata.module';
|
||||
|
||||
import { WorkspaceFactory } from './workspace.factory';
|
||||
@@ -16,6 +17,7 @@ import { WorkspaceResolverBuilderModule } from './workspace-resolver-builder/wor
|
||||
ObjectMetadataModule,
|
||||
WorkspaceSchemaBuilderModule,
|
||||
WorkspaceResolverBuilderModule,
|
||||
WorkspaceSchemaStorageModule,
|
||||
],
|
||||
providers: [WorkspaceFactory],
|
||||
exports: [WorkspaceFactory],
|
||||
|
||||
@@ -1848,6 +1848,13 @@
|
||||
path-to-regexp "3.2.0"
|
||||
tslib "2.5.3"
|
||||
|
||||
"@nestjs/event-emitter@^2.0.3":
|
||||
version "2.0.3"
|
||||
resolved "https://registry.yarnpkg.com/@nestjs/event-emitter/-/event-emitter-2.0.3.tgz#3bfcb7b580f98b2dee3c44c567b45a506767f559"
|
||||
integrity sha512-Pt7KAERrgK0OjvarSI3wfVhwZ8X1iLq1lXuodyRe+Zx3aLLP7fraFUHirASbFkB6KIQ1Zj+gZ1g8a9eu4GfFhw==
|
||||
dependencies:
|
||||
eventemitter2 "6.4.9"
|
||||
|
||||
"@nestjs/graphql@^12.0.8":
|
||||
version "12.0.8"
|
||||
resolved "https://registry.yarnpkg.com/@nestjs/graphql/-/graphql-12.0.8.tgz#15143b76dfb5fa4dc880d68a1bf2f7159ea077b6"
|
||||
@@ -5159,6 +5166,11 @@ etag@~1.8.1:
|
||||
resolved "https://registry.npmjs.org/etag/-/etag-1.8.1.tgz"
|
||||
integrity sha512-aIL5Fx7mawVa300al2BnEE4iNvo1qETxLrPI/o05L7z6go7fCw1J6EQmbK4FmJ2AS7kgVF/KEZWufBfdClMcPg==
|
||||
|
||||
eventemitter2@6.4.9:
|
||||
version "6.4.9"
|
||||
resolved "https://registry.yarnpkg.com/eventemitter2/-/eventemitter2-6.4.9.tgz#41f2750781b4230ed58827bc119d293471ecb125"
|
||||
integrity sha512-JEPTiaOt9f04oa6NOkc4aH+nVp5I3wEjpHbIPqfgCdD5v5bUzy7xQqwcVO2aDQgOWhI28da57HksMrzK9HlRxg==
|
||||
|
||||
eventemitter3@^3.1.0:
|
||||
version "3.1.2"
|
||||
resolved "https://registry.npmjs.org/eventemitter3/-/eventemitter3-3.1.2.tgz"
|
||||
|
||||
Reference in New Issue
Block a user