Add local only cache to cache service and cache typeorm entity metadata (#16287)
## Problem buildEntityMetadatas in GlobalWorkspaceOrmManager is computationally expensive and was running on every executeInWorkspaceContext call. This method uses TypeORM's EntitySchemaTransformer and EntityMetadataBuilder to build metadata for all workspace entities (30-50+ objects with many fields each). The resulting EntityMetadata[] is not serialisable which means it cannot be cached in Redis because they contain: - Circular references - Functions/methods - References to the DataSource instance ## Solution Extended the workspace cache system to support local-only caching, then created a cache provider for entityMetadatas. ## Implementation details Updated @WorkspaceCache decorator (workspace-cache.decorator.ts) - Added localOnly?: boolean option to skip Redis storage for non-serializable data Created WorkspaceEntityMetadatasCacheService - Computes entity metadatas from DB to avoid race condition, this is acceptable Simplified GlobalWorkspaceOrmManager - Now fetches entityMetadatas from cache instead of rebuilding on every call Updated Workspace migration runner - the only entry point where metadata can change - Now invalidate the new 'entityMetadata' local cache when shouldIncrementMetadataGraphqlSchemaVersion is true (== field/object mutations)
This commit is contained in:
+11
-11
@@ -9,19 +9,20 @@ import { type ColumnType, type EntitySchemaColumnOptions } from 'typeorm';
|
||||
|
||||
import { RelationType } from 'src/engine/metadata-modules/field-metadata/interfaces/relation-type.interface';
|
||||
|
||||
import { getFlatFieldsFromFlatObjectMetadata } from 'src/engine/api/graphql/workspace-schema-builder/utils/get-flat-fields-for-flat-object-metadata.util';
|
||||
import { computeCompositeColumnName } from 'src/engine/metadata-modules/field-metadata/utils/compute-column-name.util';
|
||||
import { isCompositeFieldMetadataType } from 'src/engine/metadata-modules/field-metadata/utils/is-composite-field-metadata-type.util';
|
||||
import { isEnumFieldMetadataType } from 'src/engine/metadata-modules/field-metadata/utils/is-enum-field-metadata-type.util';
|
||||
import { serializeDefaultValue } from 'src/engine/metadata-modules/field-metadata/utils/serialize-default-value';
|
||||
import { type FlatEntityMaps } from 'src/engine/metadata-modules/flat-entity/types/flat-entity-maps.type';
|
||||
import { type FlatFieldMetadata } from 'src/engine/metadata-modules/flat-field-metadata/types/flat-field-metadata.type';
|
||||
import { type FlatObjectMetadata } from 'src/engine/metadata-modules/flat-object-metadata/types/flat-object-metadata.type';
|
||||
import { fieldMetadataTypeToColumnType } from 'src/engine/metadata-modules/workspace-migration/utils/field-metadata-type-to-column-type.util';
|
||||
import {
|
||||
TwentyORMException,
|
||||
TwentyORMExceptionCode,
|
||||
} from 'src/engine/twenty-orm/exceptions/twenty-orm.exception';
|
||||
import {
|
||||
type EntitySchemaFieldMetadata,
|
||||
type EntitySchemaFieldMetadataMaps,
|
||||
type EntitySchemaObjectMetadata,
|
||||
} from 'src/engine/twenty-orm/global-workspace-datasource/types/entity-schema-metadata.type';
|
||||
import { isFieldMetadataEntityOfType } from 'src/engine/utils/is-field-metadata-of-type.util';
|
||||
|
||||
type EntitySchemaColumnMap = {
|
||||
@@ -31,15 +32,14 @@ type EntitySchemaColumnMap = {
|
||||
@Injectable()
|
||||
export class EntitySchemaColumnFactory {
|
||||
create(
|
||||
flatObjectMetadata: FlatObjectMetadata,
|
||||
flatFieldMetadataMaps: FlatEntityMaps<FlatFieldMetadata>,
|
||||
objectMetadata: EntitySchemaObjectMetadata,
|
||||
fieldMetadataMaps: EntitySchemaFieldMetadataMaps,
|
||||
): EntitySchemaColumnMap {
|
||||
let entitySchemaColumnMap: EntitySchemaColumnMap = {};
|
||||
|
||||
const fieldMetadataCollection = getFlatFieldsFromFlatObjectMetadata(
|
||||
flatObjectMetadata,
|
||||
flatFieldMetadataMaps,
|
||||
);
|
||||
const fieldMetadataCollection = objectMetadata.fieldMetadataIds
|
||||
.map((fieldId) => fieldMetadataMaps.byId[fieldId])
|
||||
.filter(isDefined);
|
||||
|
||||
for (const fieldMetadata of fieldMetadataCollection) {
|
||||
const key = fieldMetadata.name;
|
||||
@@ -121,7 +121,7 @@ export class EntitySchemaColumnFactory {
|
||||
}
|
||||
|
||||
private createCompositeColumns(
|
||||
fieldMetadata: FlatFieldMetadata,
|
||||
fieldMetadata: EntitySchemaFieldMetadata,
|
||||
): EntitySchemaColumnMap {
|
||||
const entitySchemaColumnMap: EntitySchemaColumnMap = {};
|
||||
const compositeType = compositeTypeDefinitions.get(fieldMetadata.type);
|
||||
|
||||
+38
-40
@@ -1,70 +1,68 @@
|
||||
import { Injectable } from '@nestjs/common';
|
||||
|
||||
import { FieldMetadataType } from 'twenty-shared/types';
|
||||
import { isDefined } from 'twenty-shared/utils';
|
||||
import { type EntitySchemaRelationOptions } from 'typeorm';
|
||||
|
||||
import { getFlatFieldsFromFlatObjectMetadata } from 'src/engine/api/graphql/workspace-schema-builder/utils/get-flat-fields-for-flat-object-metadata.util';
|
||||
import { type FlatEntityMaps } from 'src/engine/metadata-modules/flat-entity/types/flat-entity-maps.type';
|
||||
import { type FlatFieldMetadata } from 'src/engine/metadata-modules/flat-field-metadata/types/flat-field-metadata.type';
|
||||
import { isMorphOrRelationFlatFieldMetadata } from 'src/engine/metadata-modules/flat-field-metadata/utils/is-morph-or-relation-flat-field-metadata.util';
|
||||
import { type FlatObjectMetadata } from 'src/engine/metadata-modules/flat-object-metadata/types/flat-object-metadata.type';
|
||||
import {
|
||||
type EntitySchemaFieldMetadata,
|
||||
type EntitySchemaFieldMetadataMaps,
|
||||
type EntitySchemaObjectMetadata,
|
||||
type EntitySchemaObjectMetadataMaps,
|
||||
} from 'src/engine/twenty-orm/global-workspace-datasource/types/entity-schema-metadata.type';
|
||||
import { determineSchemaRelationDetails } from 'src/engine/twenty-orm/utils/determine-schema-relation-details.util';
|
||||
|
||||
type EntitySchemaRelationMap = {
|
||||
[key: string]: EntitySchemaRelationOptions;
|
||||
};
|
||||
|
||||
type RelationFieldMetadata = EntitySchemaFieldMetadata<
|
||||
FieldMetadataType.RELATION | FieldMetadataType.MORPH_RELATION
|
||||
>;
|
||||
|
||||
@Injectable()
|
||||
export class EntitySchemaRelationFactory {
|
||||
constructor() {}
|
||||
|
||||
create(
|
||||
flatObjectMetadata: FlatObjectMetadata,
|
||||
flatObjectMetadataMaps: FlatEntityMaps<FlatObjectMetadata>,
|
||||
flatFieldMetadataMaps: FlatEntityMaps<FlatFieldMetadata>,
|
||||
objectMetadata: EntitySchemaObjectMetadata,
|
||||
objectMetadataMaps: EntitySchemaObjectMetadataMaps,
|
||||
fieldMetadataMaps: EntitySchemaFieldMetadataMaps,
|
||||
): EntitySchemaRelationMap {
|
||||
const entitySchemaRelationMap: EntitySchemaRelationMap = {};
|
||||
|
||||
const flatFieldMetadatas = getFlatFieldsFromFlatObjectMetadata(
|
||||
flatObjectMetadata,
|
||||
flatFieldMetadataMaps,
|
||||
);
|
||||
const fieldMetadatas = objectMetadata.fieldMetadataIds
|
||||
.map((fieldId) => fieldMetadataMaps.byId[fieldId])
|
||||
.filter(isDefined);
|
||||
|
||||
for (const flatFieldMetadata of flatFieldMetadatas) {
|
||||
if (!isMorphOrRelationFlatFieldMetadata(flatFieldMetadata)) {
|
||||
for (const fieldMetadata of fieldMetadatas) {
|
||||
if (!this.isRelationField(fieldMetadata)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!flatFieldMetadata.settings) {
|
||||
throw new Error(
|
||||
`Field metadata settings are missing for field ${flatFieldMetadata.name}`,
|
||||
);
|
||||
}
|
||||
|
||||
const schemaRelationDetails = determineSchemaRelationDetails(
|
||||
flatFieldMetadata,
|
||||
flatObjectMetadataMaps,
|
||||
flatFieldMetadataMaps,
|
||||
const relationDetails = determineSchemaRelationDetails(
|
||||
fieldMetadata,
|
||||
objectMetadataMaps,
|
||||
fieldMetadataMaps,
|
||||
);
|
||||
|
||||
const targetObjectMetadata =
|
||||
flatObjectMetadataMaps.byId[
|
||||
flatFieldMetadata.relationTargetObjectMetadataId
|
||||
];
|
||||
|
||||
if (!targetObjectMetadata) {
|
||||
throw new Error(
|
||||
`Target object metadata not found for field ${flatFieldMetadata.name}`,
|
||||
);
|
||||
}
|
||||
|
||||
entitySchemaRelationMap[flatFieldMetadata.name] = {
|
||||
type: schemaRelationDetails.relationType,
|
||||
target: schemaRelationDetails.target,
|
||||
inverseSide: schemaRelationDetails.inverseSide,
|
||||
joinColumn: schemaRelationDetails.joinColumn,
|
||||
entitySchemaRelationMap[fieldMetadata.name] = {
|
||||
type: relationDetails.relationType,
|
||||
target: relationDetails.target,
|
||||
inverseSide: relationDetails.inverseSide,
|
||||
joinColumn: relationDetails.joinColumn,
|
||||
} satisfies EntitySchemaRelationOptions;
|
||||
}
|
||||
|
||||
return entitySchemaRelationMap;
|
||||
}
|
||||
|
||||
private isRelationField(
|
||||
fieldMetadata: EntitySchemaFieldMetadata,
|
||||
): fieldMetadata is RelationFieldMetadata {
|
||||
return (
|
||||
fieldMetadata.type === FieldMetadataType.RELATION ||
|
||||
fieldMetadata.type === FieldMetadataType.MORPH_RELATION
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,11 +2,13 @@ import { Injectable } from '@nestjs/common';
|
||||
|
||||
import { EntitySchema } from 'typeorm';
|
||||
|
||||
import { type FlatEntityMaps } from 'src/engine/metadata-modules/flat-entity/types/flat-entity-maps.type';
|
||||
import { type FlatFieldMetadata } from 'src/engine/metadata-modules/flat-field-metadata/types/flat-field-metadata.type';
|
||||
import { type FlatObjectMetadata } from 'src/engine/metadata-modules/flat-object-metadata/types/flat-object-metadata.type';
|
||||
import { EntitySchemaColumnFactory } from 'src/engine/twenty-orm/factories/entity-schema-column.factory';
|
||||
import { EntitySchemaRelationFactory } from 'src/engine/twenty-orm/factories/entity-schema-relation.factory';
|
||||
import {
|
||||
type EntitySchemaFieldMetadataMaps,
|
||||
type EntitySchemaObjectMetadata,
|
||||
type EntitySchemaObjectMetadataMaps,
|
||||
} from 'src/engine/twenty-orm/global-workspace-datasource/types/entity-schema-metadata.type';
|
||||
import { computeTableName } from 'src/engine/utils/compute-table-name.util';
|
||||
import { getWorkspaceSchemaName } from 'src/engine/workspace-datasource/utils/get-workspace-schema-name.util';
|
||||
|
||||
@@ -19,28 +21,28 @@ export class EntitySchemaFactory {
|
||||
|
||||
create(
|
||||
workspaceId: string,
|
||||
flatObjectMetadata: FlatObjectMetadata,
|
||||
flatObjectMetadataMaps: FlatEntityMaps<FlatObjectMetadata>,
|
||||
flatFieldMetadataMaps: FlatEntityMaps<FlatFieldMetadata>,
|
||||
objectMetadata: EntitySchemaObjectMetadata,
|
||||
objectMetadataMaps: EntitySchemaObjectMetadataMaps,
|
||||
fieldMetadataMaps: EntitySchemaFieldMetadataMaps,
|
||||
): EntitySchema {
|
||||
const columns = this.entitySchemaColumnFactory.create(
|
||||
flatObjectMetadata,
|
||||
flatFieldMetadataMaps,
|
||||
objectMetadata,
|
||||
fieldMetadataMaps,
|
||||
);
|
||||
|
||||
const relations = this.entitySchemaRelationFactory.create(
|
||||
flatObjectMetadata,
|
||||
flatObjectMetadataMaps,
|
||||
flatFieldMetadataMaps,
|
||||
objectMetadata,
|
||||
objectMetadataMaps,
|
||||
fieldMetadataMaps,
|
||||
);
|
||||
|
||||
const schemaName = getWorkspaceSchemaName(workspaceId);
|
||||
|
||||
const entitySchema = new EntitySchema({
|
||||
name: flatObjectMetadata.nameSingular,
|
||||
name: objectMetadata.nameSingular,
|
||||
tableName: computeTableName(
|
||||
flatObjectMetadata.nameSingular,
|
||||
flatObjectMetadata.isCustom,
|
||||
objectMetadata.nameSingular,
|
||||
objectMetadata.isCustom,
|
||||
),
|
||||
columns,
|
||||
relations,
|
||||
|
||||
+9
-1
@@ -5,13 +5,16 @@ import { FeatureFlagModule } from 'src/engine/core-modules/feature-flag/feature-
|
||||
import { TwentyConfigModule } from 'src/engine/core-modules/twenty-config/twenty-config.module';
|
||||
import { WorkspaceEntity } from 'src/engine/core-modules/workspace/workspace.entity';
|
||||
import { DataSourceModule } from 'src/engine/metadata-modules/data-source/data-source.module';
|
||||
import { FieldMetadataEntity } from 'src/engine/metadata-modules/field-metadata/field-metadata.entity';
|
||||
import { WorkspaceManyOrAllFlatEntityMapsCacheModule } from 'src/engine/metadata-modules/flat-entity/services/workspace-many-or-all-flat-entity-maps-cache.module';
|
||||
import { ObjectMetadataEntity } from 'src/engine/metadata-modules/object-metadata/object-metadata.entity';
|
||||
import { WorkspaceFeatureFlagsMapCacheModule } from 'src/engine/metadata-modules/workspace-feature-flags-map-cache/workspace-feature-flags-map-cache.module';
|
||||
import { EntitySchemaColumnFactory } from 'src/engine/twenty-orm/factories/entity-schema-column.factory';
|
||||
import { EntitySchemaRelationFactory } from 'src/engine/twenty-orm/factories/entity-schema-relation.factory';
|
||||
import { EntitySchemaFactory } from 'src/engine/twenty-orm/factories/entity-schema.factory';
|
||||
import { GlobalWorkspaceDataSourceService } from 'src/engine/twenty-orm/global-workspace-datasource/global-workspace-datasource.service';
|
||||
import { GlobalWorkspaceOrmManager } from 'src/engine/twenty-orm/global-workspace-datasource/global-workspace-orm.manager';
|
||||
import { WorkspaceORMEntityMetadatasCacheService } from 'src/engine/twenty-orm/global-workspace-datasource/workspace-orm-entity-metadatas-cache.service';
|
||||
import { WorkspaceCacheStorageModule } from 'src/engine/workspace-cache-storage/workspace-cache-storage.module';
|
||||
import { WorkspaceCacheModule } from 'src/engine/workspace-cache/workspace-cache.module';
|
||||
import { WorkspaceEventEmitterModule } from 'src/engine/workspace-event-emitter/workspace-event-emitter.module';
|
||||
@@ -19,7 +22,11 @@ import { WorkspaceEventEmitterModule } from 'src/engine/workspace-event-emitter/
|
||||
@Global()
|
||||
@Module({
|
||||
imports: [
|
||||
TypeOrmModule.forFeature([WorkspaceEntity]),
|
||||
TypeOrmModule.forFeature([
|
||||
WorkspaceEntity,
|
||||
ObjectMetadataEntity,
|
||||
FieldMetadataEntity,
|
||||
]),
|
||||
DataSourceModule,
|
||||
WorkspaceCacheStorageModule,
|
||||
WorkspaceManyOrAllFlatEntityMapsCacheModule,
|
||||
@@ -35,6 +42,7 @@ import { WorkspaceEventEmitterModule } from 'src/engine/workspace-event-emitter/
|
||||
EntitySchemaFactory,
|
||||
EntitySchemaColumnFactory,
|
||||
EntitySchemaRelationFactory,
|
||||
WorkspaceORMEntityMetadatasCacheService,
|
||||
],
|
||||
exports: [GlobalWorkspaceDataSourceService, GlobalWorkspaceOrmManager],
|
||||
})
|
||||
|
||||
+3
-49
@@ -1,18 +1,10 @@
|
||||
import { Injectable, type Type } from '@nestjs/common';
|
||||
|
||||
import { isDefined } from 'twenty-shared/utils';
|
||||
import { EntitySchema, ObjectLiteral } from 'typeorm';
|
||||
import { EntitySchemaTransformer } from 'typeorm/entity-schema/EntitySchemaTransformer';
|
||||
import { EntityMetadataBuilder } from 'typeorm/metadata-builder/EntityMetadataBuilder';
|
||||
import { ObjectLiteral } from 'typeorm';
|
||||
|
||||
import { WorkspaceAuthContext } from 'src/engine/api/common/interfaces/workspace-auth-context.interface';
|
||||
|
||||
import { WorkspaceManyOrAllFlatEntityMapsCacheService } from 'src/engine/metadata-modules/flat-entity/services/workspace-many-or-all-flat-entity-maps-cache.service';
|
||||
import { type FlatEntityMaps } from 'src/engine/metadata-modules/flat-entity/types/flat-entity-maps.type';
|
||||
import { type FlatFieldMetadata } from 'src/engine/metadata-modules/flat-field-metadata/types/flat-field-metadata.type';
|
||||
import { type FlatObjectMetadata } from 'src/engine/metadata-modules/flat-object-metadata/types/flat-object-metadata.type';
|
||||
import { buildObjectIdByNameMaps } from 'src/engine/metadata-modules/flat-object-metadata/utils/build-object-id-by-name-maps.util';
|
||||
import { EntitySchemaFactory } from 'src/engine/twenty-orm/factories/entity-schema.factory';
|
||||
import { GlobalWorkspaceDataSourceService } from 'src/engine/twenty-orm/global-workspace-datasource/global-workspace-datasource.service';
|
||||
import { type WorkspaceRepository } from 'src/engine/twenty-orm/repository/workspace.repository';
|
||||
import {
|
||||
@@ -27,8 +19,6 @@ import { convertClassNameToObjectMetadataName } from 'src/engine/workspace-manag
|
||||
export class GlobalWorkspaceOrmManager {
|
||||
constructor(
|
||||
private readonly globalWorkspaceDataSourceService: GlobalWorkspaceDataSourceService,
|
||||
private readonly workspaceManyOrAllFlatEntityMapsCacheService: WorkspaceManyOrAllFlatEntityMapsCacheService,
|
||||
private readonly entitySchemaFactory: EntitySchemaFactory,
|
||||
private readonly workspaceCacheService: WorkspaceCacheService,
|
||||
) {}
|
||||
|
||||
@@ -92,26 +82,19 @@ export class GlobalWorkspaceOrmManager {
|
||||
flatIndexMaps,
|
||||
featureFlagsMap,
|
||||
rolesPermissions: permissionsPerRoleId,
|
||||
ORMEntityMetadatas: entityMetadatas,
|
||||
} = await this.workspaceCacheService.getOrRecompute(workspaceId, [
|
||||
'flatObjectMetadataMaps',
|
||||
'flatFieldMetadataMaps',
|
||||
'flatIndexMaps',
|
||||
'featureFlagsMap',
|
||||
'rolesPermissions',
|
||||
'userWorkspaceRoleMap',
|
||||
'ORMEntityMetadatas',
|
||||
]);
|
||||
|
||||
const { idByNameSingular: objectIdByNameSingular } =
|
||||
buildObjectIdByNameMaps(flatObjectMetadataMaps);
|
||||
|
||||
const entitySchemas = this.buildEntitySchemas(
|
||||
workspaceId,
|
||||
flatObjectMetadataMaps,
|
||||
flatFieldMetadataMaps,
|
||||
);
|
||||
|
||||
const entityMetadatas = this.buildEntityMetadatas(entitySchemas);
|
||||
|
||||
return {
|
||||
authContext,
|
||||
flatObjectMetadataMaps,
|
||||
@@ -123,33 +106,4 @@ export class GlobalWorkspaceOrmManager {
|
||||
entityMetadatas,
|
||||
};
|
||||
}
|
||||
|
||||
private buildEntitySchemas(
|
||||
workspaceId: string,
|
||||
flatObjectMetadataMaps: FlatEntityMaps<FlatObjectMetadata>,
|
||||
flatFieldMetadataMaps: FlatEntityMaps<FlatFieldMetadata>,
|
||||
) {
|
||||
return Object.values(flatObjectMetadataMaps.byId)
|
||||
.filter(isDefined)
|
||||
.map((flatObjectMetadata) =>
|
||||
this.entitySchemaFactory.create(
|
||||
workspaceId,
|
||||
flatObjectMetadata,
|
||||
flatObjectMetadataMaps,
|
||||
flatFieldMetadataMaps,
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
private buildEntityMetadatas(entitySchemas: EntitySchema[]) {
|
||||
const transformer = new EntitySchemaTransformer();
|
||||
const metadataArgsStorage = transformer.transform(entitySchemas);
|
||||
|
||||
const entityMetadataBuilder = new EntityMetadataBuilder(
|
||||
this.globalWorkspaceDataSourceService.getGlobalWorkspaceDataSource(),
|
||||
metadataArgsStorage,
|
||||
);
|
||||
|
||||
return entityMetadataBuilder.build();
|
||||
}
|
||||
}
|
||||
|
||||
+85
@@ -0,0 +1,85 @@
|
||||
import { type FieldMetadataType } from 'twenty-shared/types';
|
||||
|
||||
import { type FieldMetadataEntity } from 'src/engine/metadata-modules/field-metadata/field-metadata.entity';
|
||||
import { type ObjectMetadataEntity } from 'src/engine/metadata-modules/object-metadata/object-metadata.entity';
|
||||
|
||||
export type EntitySchemaObjectMetadata = Pick<
|
||||
ObjectMetadataEntity,
|
||||
'id' | 'nameSingular' | 'isCustom'
|
||||
> & {
|
||||
fieldMetadataIds: string[];
|
||||
};
|
||||
|
||||
export type EntitySchemaFieldMetadata<
|
||||
TFieldMetadataType extends FieldMetadataType = FieldMetadataType,
|
||||
> = Pick<
|
||||
FieldMetadataEntity<TFieldMetadataType>,
|
||||
| 'id'
|
||||
| 'name'
|
||||
| 'type'
|
||||
| 'settings'
|
||||
| 'isNullable'
|
||||
| 'defaultValue'
|
||||
| 'options'
|
||||
| 'objectMetadataId'
|
||||
| 'relationTargetObjectMetadataId'
|
||||
| 'relationTargetFieldMetadataId'
|
||||
>;
|
||||
|
||||
export type EntitySchemaObjectMetadataMaps = {
|
||||
byId: Partial<Record<string, EntitySchemaObjectMetadata>>;
|
||||
};
|
||||
|
||||
export type EntitySchemaFieldMetadataMaps = {
|
||||
byId: Partial<Record<string, EntitySchemaFieldMetadata>>;
|
||||
};
|
||||
|
||||
export const buildEntitySchemaMetadataMaps = (
|
||||
objectMetadatas: ObjectMetadataEntity[],
|
||||
fieldMetadatas: FieldMetadataEntity[],
|
||||
): {
|
||||
objectMetadataMaps: EntitySchemaObjectMetadataMaps;
|
||||
fieldMetadataMaps: EntitySchemaFieldMetadataMaps;
|
||||
} => {
|
||||
const fieldIdsByObjectId = new Map<string, string[]>();
|
||||
|
||||
for (const field of fieldMetadatas) {
|
||||
const existing = fieldIdsByObjectId.get(field.objectMetadataId);
|
||||
|
||||
if (existing) {
|
||||
existing.push(field.id);
|
||||
} else {
|
||||
fieldIdsByObjectId.set(field.objectMetadataId, [field.id]);
|
||||
}
|
||||
}
|
||||
|
||||
const objectMetadataMaps: EntitySchemaObjectMetadataMaps = { byId: {} };
|
||||
|
||||
for (const object of objectMetadatas) {
|
||||
objectMetadataMaps.byId[object.id] = {
|
||||
id: object.id,
|
||||
nameSingular: object.nameSingular,
|
||||
isCustom: object.isCustom,
|
||||
fieldMetadataIds: fieldIdsByObjectId.get(object.id) ?? [],
|
||||
};
|
||||
}
|
||||
|
||||
const fieldMetadataMaps: EntitySchemaFieldMetadataMaps = { byId: {} };
|
||||
|
||||
for (const field of fieldMetadatas) {
|
||||
fieldMetadataMaps.byId[field.id] = {
|
||||
id: field.id,
|
||||
name: field.name,
|
||||
type: field.type,
|
||||
settings: field.settings,
|
||||
isNullable: field.isNullable,
|
||||
defaultValue: field.defaultValue,
|
||||
options: field.options,
|
||||
objectMetadataId: field.objectMetadataId,
|
||||
relationTargetObjectMetadataId: field.relationTargetObjectMetadataId,
|
||||
relationTargetFieldMetadataId: field.relationTargetFieldMetadataId,
|
||||
};
|
||||
}
|
||||
|
||||
return { objectMetadataMaps, fieldMetadataMaps };
|
||||
};
|
||||
+76
@@ -0,0 +1,76 @@
|
||||
import { Injectable } from '@nestjs/common';
|
||||
import { InjectRepository } from '@nestjs/typeorm';
|
||||
|
||||
import { isDefined } from 'twenty-shared/utils';
|
||||
import { type EntityMetadata, EntitySchema, Repository } from 'typeorm';
|
||||
import { EntitySchemaTransformer } from 'typeorm/entity-schema/EntitySchemaTransformer';
|
||||
import { EntityMetadataBuilder } from 'typeorm/metadata-builder/EntityMetadataBuilder';
|
||||
|
||||
import { WorkspaceCacheProvider } from 'src/engine/workspace-cache/interfaces/workspace-cache-provider.service';
|
||||
|
||||
import { FieldMetadataEntity } from 'src/engine/metadata-modules/field-metadata/field-metadata.entity';
|
||||
import { ObjectMetadataEntity } from 'src/engine/metadata-modules/object-metadata/object-metadata.entity';
|
||||
import { EntitySchemaFactory } from 'src/engine/twenty-orm/factories/entity-schema.factory';
|
||||
import { GlobalWorkspaceDataSourceService } from 'src/engine/twenty-orm/global-workspace-datasource/global-workspace-datasource.service';
|
||||
import { buildEntitySchemaMetadataMaps } from 'src/engine/twenty-orm/global-workspace-datasource/types/entity-schema-metadata.type';
|
||||
import { WorkspaceCache } from 'src/engine/workspace-cache/decorators/workspace-cache.decorator';
|
||||
|
||||
@Injectable()
|
||||
@WorkspaceCache('ORMEntityMetadatas', { localDataOnly: true })
|
||||
export class WorkspaceORMEntityMetadatasCacheService extends WorkspaceCacheProvider<
|
||||
EntityMetadata[]
|
||||
> {
|
||||
constructor(
|
||||
@InjectRepository(ObjectMetadataEntity)
|
||||
private readonly objectMetadataRepository: Repository<ObjectMetadataEntity>,
|
||||
@InjectRepository(FieldMetadataEntity)
|
||||
private readonly fieldMetadataRepository: Repository<FieldMetadataEntity>,
|
||||
private readonly entitySchemaFactory: EntitySchemaFactory,
|
||||
private readonly globalWorkspaceDataSourceService: GlobalWorkspaceDataSourceService,
|
||||
) {
|
||||
super();
|
||||
}
|
||||
|
||||
async computeForCache(workspaceId: string): Promise<EntityMetadata[]> {
|
||||
const [objectMetadatas, fieldMetadatas] = await Promise.all([
|
||||
this.objectMetadataRepository.find({
|
||||
where: { workspaceId },
|
||||
withDeleted: true,
|
||||
}),
|
||||
this.fieldMetadataRepository.find({
|
||||
where: { workspaceId },
|
||||
withDeleted: true,
|
||||
}),
|
||||
]);
|
||||
|
||||
const { objectMetadataMaps, fieldMetadataMaps } =
|
||||
buildEntitySchemaMetadataMaps(objectMetadatas, fieldMetadatas);
|
||||
|
||||
const entitySchemas = Object.values(objectMetadataMaps.byId)
|
||||
.filter(isDefined)
|
||||
.map((objectMetadata) =>
|
||||
this.entitySchemaFactory.create(
|
||||
workspaceId,
|
||||
objectMetadata,
|
||||
objectMetadataMaps,
|
||||
fieldMetadataMaps,
|
||||
),
|
||||
);
|
||||
|
||||
return this.buildEntityMetadatas(entitySchemas);
|
||||
}
|
||||
|
||||
private buildEntityMetadatas(
|
||||
entitySchemas: EntitySchema[],
|
||||
): EntityMetadata[] {
|
||||
const transformer = new EntitySchemaTransformer();
|
||||
const metadataArgsStorage = transformer.transform(entitySchemas);
|
||||
|
||||
const entityMetadataBuilder = new EntityMetadataBuilder(
|
||||
this.globalWorkspaceDataSourceService.getGlobalWorkspaceDataSource(),
|
||||
metadataArgsStorage,
|
||||
);
|
||||
|
||||
return entityMetadataBuilder.build();
|
||||
}
|
||||
}
|
||||
@@ -1,8 +1,6 @@
|
||||
import { type Milliseconds } from 'cache-manager';
|
||||
import { isDefined } from 'twenty-shared/utils';
|
||||
|
||||
import { NodeEnvironment } from 'src/engine/core-modules/twenty-config/interfaces/node-environment.interface';
|
||||
|
||||
import { type CacheKey } from 'src/engine/twenty-orm/storage/types/cache-key.type';
|
||||
|
||||
type AsyncFactoryCallback<T> = () => Promise<T | null>;
|
||||
@@ -39,13 +37,6 @@ export class PromiseMemoizer<T> {
|
||||
return existingPromise;
|
||||
}
|
||||
|
||||
if (process.env.NODE_ENV !== NodeEnvironment.TEST) {
|
||||
// eslint-disable-next-line no-console
|
||||
console.log(
|
||||
`Computing new Datasource for cacheKey: ${cacheKey} out of ${this.cache.size}`,
|
||||
);
|
||||
}
|
||||
|
||||
const newPromise = (async () => {
|
||||
try {
|
||||
const value = await factory();
|
||||
|
||||
+11
-11
@@ -1,13 +1,15 @@
|
||||
import { type FieldMetadataType } from 'twenty-shared/types';
|
||||
import { type RelationType } from 'typeorm/metadata/types/RelationTypes';
|
||||
|
||||
import { type FlatEntityMaps } from 'src/engine/metadata-modules/flat-entity/types/flat-entity-maps.type';
|
||||
import { type FlatFieldMetadata } from 'src/engine/metadata-modules/flat-field-metadata/types/flat-field-metadata.type';
|
||||
import { type FlatObjectMetadata } from 'src/engine/metadata-modules/flat-object-metadata/types/flat-object-metadata.type';
|
||||
import {
|
||||
RelationException,
|
||||
RelationExceptionCode,
|
||||
} from 'src/engine/twenty-orm/exceptions/relation.exception';
|
||||
import {
|
||||
type EntitySchemaFieldMetadata,
|
||||
type EntitySchemaFieldMetadataMaps,
|
||||
type EntitySchemaObjectMetadataMaps,
|
||||
} from 'src/engine/twenty-orm/global-workspace-datasource/types/entity-schema-metadata.type';
|
||||
import { converRelationTypeToTypeORMRelationType } from 'src/engine/twenty-orm/utils/convert-relation-type-to-typeorm-relation-type.util';
|
||||
|
||||
interface RelationDetails {
|
||||
@@ -18,11 +20,11 @@ interface RelationDetails {
|
||||
}
|
||||
|
||||
export function determineSchemaRelationDetails(
|
||||
fieldMetadata: FlatFieldMetadata<
|
||||
fieldMetadata: EntitySchemaFieldMetadata<
|
||||
FieldMetadataType.RELATION | FieldMetadataType.MORPH_RELATION
|
||||
>,
|
||||
flatObjectMetadataMaps: FlatEntityMaps<FlatObjectMetadata>,
|
||||
flatFieldMetadataMaps: FlatEntityMaps<FlatFieldMetadata>,
|
||||
objectMetadataMaps: EntitySchemaObjectMetadataMaps,
|
||||
fieldMetadataMaps: EntitySchemaFieldMetadataMaps,
|
||||
): RelationDetails {
|
||||
if (!fieldMetadata.settings) {
|
||||
throw new Error('Field metadata settings are missing');
|
||||
@@ -39,12 +41,10 @@ export function determineSchemaRelationDetails(
|
||||
);
|
||||
}
|
||||
|
||||
const sourceObjectMetadata =
|
||||
flatObjectMetadataMaps.byId[fieldMetadata.objectMetadataId];
|
||||
const targetObjectMetadata =
|
||||
flatObjectMetadataMaps.byId[fieldMetadata.relationTargetObjectMetadataId];
|
||||
objectMetadataMaps.byId[fieldMetadata.relationTargetObjectMetadataId];
|
||||
|
||||
if (!sourceObjectMetadata || !targetObjectMetadata) {
|
||||
if (!targetObjectMetadata) {
|
||||
throw new RelationException(
|
||||
`Object metadata not found for field ${fieldMetadata.id}`,
|
||||
RelationExceptionCode.RELATION_OBJECT_METADATA_NOT_FOUND,
|
||||
@@ -59,7 +59,7 @@ export function determineSchemaRelationDetails(
|
||||
}
|
||||
|
||||
const targetFieldMetadata =
|
||||
flatFieldMetadataMaps.byId[fieldMetadata.relationTargetFieldMetadataId];
|
||||
fieldMetadataMaps.byId[fieldMetadata.relationTargetFieldMetadataId];
|
||||
|
||||
if (!targetFieldMetadata) {
|
||||
throw new Error('Target field metadata not found');
|
||||
|
||||
Reference in New Issue
Block a user