feat: twenty orm for standard and custom objects (#6178)

### Overview

This PR builds upon #5153, adding the ability to get a repository for
custom objects. The `entitySchema` is now generated for both standard
and custom objects based on metadata stored in the database instead of
the decorated `WorkspaceEntity` in the code. This change ensures that
standard objects with custom fields and relations can also support
custom objects.

### Implementation Details

#### Key Changes:

- **Dynamic Schema Generation:** The `entitySchema` for standard and
custom objects is now dynamically generated from the metadata stored in
the database. This shift allows for greater flexibility and
adaptability, particularly for standard objects with custom fields and
relations.
  
- **Custom Object Repository Retrieval:** A repository for a custom
object can be retrieved using `TwentyORMManager` based on the object's
name. Here's an example of how this can be achieved:

  ```typescript
const repository = await this.twentyORMManager.getRepository('custom');
  /*
* `repository` variable will be typed as follows, ensuring that standard
fields and relations are properly typed:
   * const repository: WorkspaceRepository<CustomWorkspaceEntity & {
   *    [key: string]: any;
   * }>
   */
  const res = await repository.find({});
  ```

Fix #6179

---------

Co-authored-by: Charles Bochet <charles@twenty.com>
Co-authored-by: Weiko <corentin@twenty.com>
This commit is contained in:
Jérémy M
2024-07-19 18:23:52 +02:00
committed by GitHub
parent a86cc5cb9c
commit 088d061b3e
33 changed files with 947 additions and 587 deletions

View File

@ -2,11 +2,11 @@ import { Test, TestingModule } from '@nestjs/testing';
import { DataSourceService } from 'src/engine/metadata-modules/data-source/data-source.service';
import { ObjectMetadataService } from 'src/engine/metadata-modules/object-metadata/object-metadata.service';
import { WorkspaceSchemaStorageService } from 'src/engine/api/graphql/workspace-schema-storage/workspace-schema-storage.service';
import { ScalarsExplorerService } from 'src/engine/api/graphql/services/scalars-explorer.service';
import { WorkspaceResolverFactory } from 'src/engine/api/graphql/workspace-resolver-builder/workspace-resolver.factory';
import { WorkspaceGraphQLSchemaFactory } from 'src/engine/api/graphql/workspace-schema-builder/workspace-graphql-schema.factory';
import { WorkspaceSchemaFactory } from 'src/engine/api/graphql/workspace-schema.factory';
import { WorkspaceCacheStorageService } from 'src/engine/workspace-cache-storage/workspace-cache-storage.service';
describe('WorkspaceSchemaFactory', () => {
let service: WorkspaceSchemaFactory;
@ -36,7 +36,7 @@ describe('WorkspaceSchemaFactory', () => {
useValue: {},
},
{
provide: WorkspaceSchemaStorageService,
provide: WorkspaceCacheStorageService,
useValue: {},
},
],

View File

@ -1,6 +1,6 @@
import { Module } from '@nestjs/common';
import { WorkspaceSchemaStorageModule } from 'src/engine/api/graphql/workspace-schema-storage/workspace-schema-storage.module';
import { WorkspaceCacheStorageModule } from 'src/engine/workspace-cache-storage/workspace-cache-storage.module';
import { ScalarsExplorerService } from 'src/engine/api/graphql/services/scalars-explorer.service';
import { WorkspaceSchemaBuilderModule } from 'src/engine/api/graphql/workspace-schema-builder/workspace-schema-builder.module';
import { WorkspaceResolverBuilderModule } from 'src/engine/api/graphql/workspace-resolver-builder/workspace-resolver-builder.module';
@ -20,7 +20,7 @@ import { WorkspaceSchemaFactory } from './workspace-schema.factory';
MetadataEngineModule,
WorkspaceSchemaBuilderModule,
WorkspaceResolverBuilderModule,
WorkspaceSchemaStorageModule,
WorkspaceCacheStorageModule,
],
providers: [WorkspaceSchemaFactory, ScalarsExplorerService],
exports: [WorkspaceSchemaFactory],

View File

@ -1,12 +0,0 @@
import { Module } from '@nestjs/common';
import { WorkspaceCacheVersionModule } from 'src/engine/metadata-modules/workspace-cache-version/workspace-cache-version.module';
import { WorkspaceSchemaStorageService } from 'src/engine/api/graphql/workspace-schema-storage/workspace-schema-storage.service';
import { ObjectMetadataModule } from 'src/engine/metadata-modules/object-metadata/object-metadata.module';
@Module({
imports: [ObjectMetadataModule, WorkspaceCacheVersionModule],
providers: [WorkspaceSchemaStorageService],
exports: [WorkspaceSchemaStorageService],
})
export class WorkspaceSchemaStorageModule {}

View File

@ -1,95 +0,0 @@
import { Injectable } from '@nestjs/common';
import { CacheStorageService } from 'src/engine/integrations/cache-storage/cache-storage.service';
import { InjectCacheStorage } from 'src/engine/integrations/cache-storage/decorators/cache-storage.decorator';
import { CacheStorageNamespace } from 'src/engine/integrations/cache-storage/types/cache-storage-namespace.enum';
import { ObjectMetadataEntity } from 'src/engine/metadata-modules/object-metadata/object-metadata.entity';
import { WorkspaceCacheVersionService } from 'src/engine/metadata-modules/workspace-cache-version/workspace-cache-version.service';
@Injectable()
export class WorkspaceSchemaStorageService {
constructor(
@InjectCacheStorage(CacheStorageNamespace.WorkspaceSchema)
private readonly workspaceSchemaCache: CacheStorageService,
private readonly workspaceCacheVersionService: WorkspaceCacheVersionService,
) {}
async validateCacheVersion(workspaceId: string): Promise<void> {
const currentVersion =
(await this.workspaceSchemaCache.get<string>(
`cacheVersion:${workspaceId}`,
)) ?? '0';
let latestVersion =
await this.workspaceCacheVersionService.getVersion(workspaceId);
if (!latestVersion || currentVersion !== latestVersion) {
// Invalidate cache if version mismatch is detected
await this.invalidateCache(workspaceId);
// If the latest version is not found, increment the version
latestVersion ??=
await this.workspaceCacheVersionService.incrementVersion(workspaceId);
// Update the cache version after invalidation
await this.workspaceSchemaCache.set<string>(
`cacheVersion:${workspaceId}`,
latestVersion,
);
}
}
setObjectMetadataCollection(
workspaceId: string,
objectMetadataCollection: ObjectMetadataEntity[],
) {
return this.workspaceSchemaCache.set<ObjectMetadataEntity[]>(
`objectMetadataCollection:${workspaceId}`,
objectMetadataCollection,
);
}
getObjectMetadataCollection(
workspaceId: string,
): Promise<ObjectMetadataEntity[] | undefined> {
return this.workspaceSchemaCache.get<ObjectMetadataEntity[]>(
`objectMetadataCollection:${workspaceId}`,
);
}
setTypeDefs(workspaceId: string, typeDefs: string): Promise<void> {
return this.workspaceSchemaCache.set<string>(
`typeDefs:${workspaceId}`,
typeDefs,
);
}
getTypeDefs(workspaceId: string): Promise<string | undefined> {
return this.workspaceSchemaCache.get<string>(`typeDefs:${workspaceId}`);
}
setUsedScalarNames(
workspaceId: string,
scalarsUsed: string[],
): Promise<void> {
return this.workspaceSchemaCache.set<string[]>(
`usedScalarNames:${workspaceId}`,
scalarsUsed,
);
}
getUsedScalarNames(workspaceId: string): Promise<string[] | undefined> {
return this.workspaceSchemaCache.get<string[]>(
`usedScalarNames:${workspaceId}`,
);
}
async invalidateCache(workspaceId: string): Promise<void> {
await this.workspaceSchemaCache.del(
`objectMetadataCollection:${workspaceId}`,
);
await this.workspaceSchemaCache.del(`typeDefs:${workspaceId}`);
await this.workspaceSchemaCache.del(`usedScalarNames:${workspaceId}`);
}
}

View File

@ -5,7 +5,7 @@ import { makeExecutableSchema } from '@graphql-tools/schema';
import { gql } from 'graphql-tag';
import { DataSourceService } from 'src/engine/metadata-modules/data-source/data-source.service';
import { WorkspaceSchemaStorageService } from 'src/engine/api/graphql/workspace-schema-storage/workspace-schema-storage.service';
import { WorkspaceCacheStorageService } from 'src/engine/workspace-cache-storage/workspace-cache-storage.service';
import { ObjectMetadataService } from 'src/engine/metadata-modules/object-metadata/object-metadata.service';
import { ScalarsExplorerService } from 'src/engine/api/graphql/services/scalars-explorer.service';
import { WorkspaceGraphQLSchemaFactory } from 'src/engine/api/graphql/workspace-schema-builder/workspace-graphql-schema.factory';
@ -20,7 +20,7 @@ export class WorkspaceSchemaFactory {
private readonly scalarsExplorerService: ScalarsExplorerService,
private readonly workspaceGraphQLSchemaFactory: WorkspaceGraphQLSchemaFactory,
private readonly workspaceResolverFactory: WorkspaceResolverFactory,
private readonly workspaceSchemaStorageService: WorkspaceSchemaStorageService,
private readonly workspaceCacheStorageService: WorkspaceCacheStorageService,
) {}
async createGraphQLSchema(
@ -42,11 +42,11 @@ export class WorkspaceSchemaFactory {
}
// Validate cache version
await this.workspaceSchemaStorageService.validateCacheVersion(workspaceId);
await this.workspaceCacheStorageService.validateCacheVersion(workspaceId);
// Get object metadata from cache
let objectMetadataCollection =
await this.workspaceSchemaStorageService.getObjectMetadataCollection(
await this.workspaceCacheStorageService.getObjectMetadataCollection(
workspaceId,
);
@ -55,7 +55,7 @@ export class WorkspaceSchemaFactory {
objectMetadataCollection =
await this.objectMetadataService.findManyWithinWorkspace(workspaceId);
await this.workspaceSchemaStorageService.setObjectMetadataCollection(
await this.workspaceCacheStorageService.setObjectMetadataCollection(
workspaceId,
objectMetadataCollection,
);
@ -63,9 +63,9 @@ export class WorkspaceSchemaFactory {
// Get typeDefs from cache
let typeDefs =
await this.workspaceSchemaStorageService.getTypeDefs(workspaceId);
await this.workspaceCacheStorageService.getTypeDefs(workspaceId);
let usedScalarNames =
await this.workspaceSchemaStorageService.getUsedScalarNames(workspaceId);
await this.workspaceCacheStorageService.getUsedScalarNames(workspaceId);
// If typeDefs are not cached, generate them
if (!typeDefs || !usedScalarNames) {
@ -79,11 +79,11 @@ export class WorkspaceSchemaFactory {
this.scalarsExplorerService.getUsedScalarNames(autoGeneratedSchema);
typeDefs = printSchema(autoGeneratedSchema);
await this.workspaceSchemaStorageService.setTypeDefs(
await this.workspaceCacheStorageService.setTypeDefs(
workspaceId,
typeDefs,
);
await this.workspaceSchemaStorageService.setUsedScalarNames(
await this.workspaceCacheStorageService.setUsedScalarNames(
workspaceId,
usedScalarNames,
);