[POC] add graphql query runner (#6747)

## Context
The goal is to replace pg_graphql with our own ORM wrapper (TwentyORM).
This PR tries to add some parsing logic to convert graphql requests to
send to the ORM to replace pg_graphql implementation.

---------

Co-authored-by: Charles Bochet <charles@twenty.com>
This commit is contained in:
Weiko
2024-08-27 17:06:39 +02:00
committed by GitHub
parent ef4f2e43b0
commit f6fd92adcb
51 changed files with 1397 additions and 249 deletions

View File

@ -57,7 +57,7 @@ export class EntitySchemaRelationFactory {
target: relationDetails.target,
inverseSide: relationDetails.inverseSide,
joinColumn: relationDetails.joinColumn,
};
} satisfies EntitySchemaRelationOptions;
}
return entitySchemaRelationMap;

View File

@ -9,11 +9,13 @@ import { ObjectMetadataEntity } from 'src/engine/metadata-modules/object-metadat
import { WorkspaceMetadataVersionService } from 'src/engine/metadata-modules/workspace-metadata-version/workspace-metadata-version.service';
import { WorkspaceDataSource } from 'src/engine/twenty-orm/datasource/workspace.datasource';
import { EntitySchemaFactory } from 'src/engine/twenty-orm/factories/entity-schema.factory';
import { workspaceDataSourceCacheInstance } from 'src/engine/twenty-orm/twenty-orm-core.module';
import { CacheManager } from 'src/engine/twenty-orm/storage/cache-manager.storage';
import { WorkspaceCacheStorageService } from 'src/engine/workspace-cache-storage/workspace-cache-storage.service';
@Injectable()
export class WorkspaceDatasourceFactory {
private cacheManager = new CacheManager<WorkspaceDataSource>();
constructor(
private readonly dataSourceService: DataSourceService,
private readonly environmentService: EnvironmentService,
@ -48,7 +50,7 @@ export class WorkspaceDatasourceFactory {
);
}
const workspaceDataSource = await workspaceDataSourceCacheInstance.execute(
const workspaceDataSource = await this.cacheManager.execute(
`${workspaceId}-${latestWorkspaceMetadataVersion}`,
async () => {
let cachedObjectMetadataCollection =

View File

@ -1,81 +0,0 @@
import {
DynamicModule,
Global,
Logger,
Module,
OnApplicationShutdown,
} from '@nestjs/common';
import { TypeOrmModule } from '@nestjs/typeorm';
import {
TwentyORMModuleAsyncOptions,
TwentyORMOptions,
} from 'src/engine/twenty-orm/interfaces/twenty-orm-options.interface';
import { DataSourceModule } from 'src/engine/metadata-modules/data-source/data-source.module';
import { ObjectMetadataEntity } from 'src/engine/metadata-modules/object-metadata/object-metadata.entity';
import { WorkspaceMetadataVersionModule } from 'src/engine/metadata-modules/workspace-metadata-version/workspace-metadata-version.module';
import { WorkspaceDataSource } from 'src/engine/twenty-orm/datasource/workspace.datasource';
import { entitySchemaFactories } from 'src/engine/twenty-orm/factories';
import { EntitySchemaFactory } from 'src/engine/twenty-orm/factories/entity-schema.factory';
import { CacheManager } from 'src/engine/twenty-orm/storage/cache-manager.storage';
import { TwentyORMGlobalManager } from 'src/engine/twenty-orm/twenty-orm-global.manager';
import { TwentyORMManager } from 'src/engine/twenty-orm/twenty-orm.manager';
import { ConfigurableModuleClass } from 'src/engine/twenty-orm/twenty-orm.module-definition';
import { WorkspaceCacheStorageModule } from 'src/engine/workspace-cache-storage/workspace-cache-storage.module';
export const workspaceDataSourceCacheInstance =
new CacheManager<WorkspaceDataSource>();
@Global()
@Module({
imports: [
TypeOrmModule.forFeature([ObjectMetadataEntity], 'metadata'),
DataSourceModule,
WorkspaceCacheStorageModule,
WorkspaceMetadataVersionModule,
],
providers: [
...entitySchemaFactories,
TwentyORMManager,
TwentyORMGlobalManager,
],
exports: [EntitySchemaFactory, TwentyORMManager, TwentyORMGlobalManager],
})
export class TwentyORMCoreModule
extends ConfigurableModuleClass
implements OnApplicationShutdown
{
private static readonly logger = new Logger(TwentyORMCoreModule.name);
static register(options: TwentyORMOptions): DynamicModule {
const dynamicModule = super.register(options);
return {
...dynamicModule,
providers: [...(dynamicModule.providers ?? [])],
exports: [...(dynamicModule.exports ?? [])],
};
}
static registerAsync(
asyncOptions: TwentyORMModuleAsyncOptions,
): DynamicModule {
const dynamicModule = super.registerAsync(asyncOptions);
return {
...dynamicModule,
providers: [...(dynamicModule.providers ?? [])],
exports: [...(dynamicModule.exports ?? [])],
};
}
/**
* Destroys all data sources on application shutdown
*/
async onApplicationShutdown() {
workspaceDataSourceCacheInstance.clear((dataSource) =>
dataSource.destroy(),
);
}
}

View File

@ -1,40 +1,28 @@
import { DynamicModule, Global, Module } from '@nestjs/common';
import { EntityClassOrSchema } from '@nestjs/typeorm/dist/interfaces/entity-class-or-schema.type';
import { Global, Module } from '@nestjs/common';
import { TypeOrmModule } from '@nestjs/typeorm';
import {
TwentyORMModuleAsyncOptions,
TwentyORMOptions,
} from 'src/engine/twenty-orm/interfaces/twenty-orm-options.interface';
import { DataSourceModule } from 'src/engine/metadata-modules/data-source/data-source.module';
import { ObjectMetadataEntity } from 'src/engine/metadata-modules/object-metadata/object-metadata.entity';
import { WorkspaceMetadataVersionModule } from 'src/engine/metadata-modules/workspace-metadata-version/workspace-metadata-version.module';
import { entitySchemaFactories } from 'src/engine/twenty-orm/factories';
import { EntitySchemaFactory } from 'src/engine/twenty-orm/factories/entity-schema.factory';
import { TwentyORMGlobalManager } from 'src/engine/twenty-orm/twenty-orm-global.manager';
import { TwentyORMManager } from 'src/engine/twenty-orm/twenty-orm.manager';
import { WorkspaceCacheStorageModule } from 'src/engine/workspace-cache-storage/workspace-cache-storage.module';
import { TwentyORMCoreModule } from 'src/engine/twenty-orm/twenty-orm-core.module';
// Todo: remove this file
@Global()
@Module({})
export class TwentyORMModule {
static register(options: TwentyORMOptions): DynamicModule {
return {
module: TwentyORMModule,
imports: [TwentyORMCoreModule.register(options)],
};
}
static forFeature(_objects: EntityClassOrSchema[] = []): DynamicModule {
const providers = [];
return {
module: TwentyORMModule,
providers: providers,
exports: providers,
};
}
static registerAsync(
asyncOptions: TwentyORMModuleAsyncOptions,
): DynamicModule {
return {
module: TwentyORMModule,
imports: [TwentyORMCoreModule.registerAsync(asyncOptions)],
};
}
}
@Module({
imports: [
TypeOrmModule.forFeature([ObjectMetadataEntity], 'metadata'),
DataSourceModule,
WorkspaceCacheStorageModule,
WorkspaceMetadataVersionModule,
],
providers: [
...entitySchemaFactories,
TwentyORMManager,
TwentyORMGlobalManager,
],
exports: [EntitySchemaFactory, TwentyORMManager, TwentyORMGlobalManager],
})
export class TwentyORMModule {}

View File

@ -21,7 +21,10 @@ export async function determineRelationDetails(
let fromObjectMetadata: ObjectMetadataEntity | undefined =
fieldMetadata.object;
let toObjectMetadata: ObjectMetadataEntity | undefined =
relationMetadata.toObjectMetadata;
objectMetadataCollection.find(
(objectMetadata) =>
objectMetadata.id === relationMetadata.toObjectMetadataId,
);
// RelationMetadata always store the relation from the perspective of the `from` object, MANY_TO_ONE relations are not stored yet
if (relationType === 'many-to-one') {
@ -37,6 +40,16 @@ export async function determineRelationDetails(
throw new Error('Object metadata not found');
}
const toFieldMetadata = toObjectMetadata.fields.find((field) =>
relationType === 'many-to-one'
? field.id === relationMetadata.fromFieldMetadataId
: field.id === relationMetadata.toFieldMetadataId,
);
if (!toFieldMetadata) {
throw new Error('To field metadata not found');
}
// TODO: Support many to many relations
if (relationType === 'many-to-many') {
throw new Error('Many to many relations are not supported yet');
@ -45,7 +58,7 @@ export async function determineRelationDetails(
return {
relationType,
target: toObjectMetadata.nameSingular,
inverseSide: fromObjectMetadata.nameSingular,
inverseSide: toFieldMetadata.name,
joinColumn:
// TODO: This will work for now but we need to handle this better in the future for custom names on the join column
relationType === 'many-to-one' ||