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

@ -0,0 +1,47 @@
type CacheKey = `${string}-${string}`;
type AsyncFactoryCallback<T> = () => Promise<T | null>;
export class CacheManager<T> {
private cache = new Map<CacheKey, T>();
async execute(
cacheKey: CacheKey,
factory: AsyncFactoryCallback<T>,
onDelete?: (value: T) => Promise<void> | void,
): Promise<T | null> {
const [workspaceId] = cacheKey.split('-');
// If the cacheKey exists, return the cached value
if (this.cache.has(cacheKey)) {
return this.cache.get(cacheKey)!;
}
// Remove old entries with the same workspaceId
for (const key of this.cache.keys()) {
if (key.startsWith(`${workspaceId}-`)) {
await onDelete?.(this.cache.get(key)!);
this.cache.delete(key);
}
}
// Create a new value using the factory callback
const value = await factory();
if (!value) {
return null;
}
this.cache.set(cacheKey, value);
return value;
}
async clear(onDelete?: (value: T) => Promise<void> | void): Promise<void> {
for (const value of this.cache.values()) {
await onDelete?.(value);
this.cache.delete(value as any);
}
this.cache.clear();
}
}

View File

@ -1,23 +0,0 @@
import { DataSource } from 'typeorm';
import { WorkspaceDataSource } from 'src/engine/twenty-orm/datasource/workspace.datasource';
export class DataSourceStorage {
private static readonly dataSources: Map<string, WorkspaceDataSource> =
new Map();
public static getDataSource(key: string): WorkspaceDataSource | undefined {
return this.dataSources.get(key);
}
public static setDataSource(
key: string,
dataSource: WorkspaceDataSource,
): void {
this.dataSources.set(key, dataSource);
}
public static getAllDataSources(): DataSource[] {
return Array.from(this.dataSources.values());
}
}

View File

@ -1,30 +0,0 @@
import { Type } from '@nestjs/common';
import { EntitySchema } from 'typeorm';
export class ObjectLiteralStorage {
private static readonly objects: Map<EntitySchema, Type<any>> = new Map();
public static getObjectLiteral(target: EntitySchema): Type<any> | undefined {
return this.objects.get(target);
}
public static setObjectLiteral(
target: EntitySchema,
objectLiteral: Type<any>,
): void {
this.objects.set(target, objectLiteral);
}
public static getAllObjects(): Type<any>[] {
return Array.from(this.objects.values());
}
public static getAllEntitySchemas(): EntitySchema[] {
return Array.from(this.objects.keys());
}
public static clear(): void {
this.objects.clear();
}
}

View File

@ -0,0 +1,49 @@
import { EntitySchema } from 'typeorm';
export class WorkspaceEntitiesStorage {
private static workspaceEntities = new Map<
string,
Map<string, EntitySchema>
>();
static getEntitySchema(
workspaceId: string,
objectMetadataName: string,
): EntitySchema | undefined {
const workspace = this.workspaceEntities.get(workspaceId);
return workspace?.get(objectMetadataName);
}
static setEntitySchema(
workspaceId: string,
objectMetadataName: string,
schema: EntitySchema,
): void {
if (!this.workspaceEntities.has(workspaceId)) {
this.workspaceEntities.set(workspaceId, new Map<string, EntitySchema>());
}
const workspace = this.workspaceEntities.get(workspaceId);
workspace?.set(objectMetadataName, schema);
}
static getObjectMetadataName(
workspaceId: string,
target: EntitySchema,
): string | undefined {
const workspace = this.workspaceEntities.get(workspaceId);
return Array.from(workspace?.entries() || []).find(
([, schema]) => schema.options.name === target.options.name,
)?.[0];
}
static getEntities(workspaceId: string): EntitySchema[] {
return Array.from(this.workspaceEntities.get(workspaceId)?.values() || []);
}
static clearWorkspace(workspaceId: string): void {
this.workspaceEntities.delete(workspaceId);
}
}