Deprecate object metadata maps in favor of flat entities (#16080)
## Context Deprecating the old objectMetadataMap type in favour of split flat entities to match with our new caching. In the long run, trying to achieve: - Better performance through caching - Consistent data access patterns across the codebase - Reduced database queries Now that everything is based on flat entities, which are cached, we can finish the refactoring of workspace context cache which should already improve performances. Then the last step will be to consume that new cache in the new global datasource to get rid of the many workspace datasources stored in the server
This commit is contained in:
+16
-10
@@ -17,8 +17,9 @@ import {
|
||||
import { FieldMetadataRelationService } from 'src/engine/metadata-modules/field-metadata/services/field-metadata-relation.service';
|
||||
import { computeMorphOrRelationFieldJoinColumnName } from 'src/engine/metadata-modules/field-metadata/utils/compute-morph-or-relation-field-join-column-name.util';
|
||||
import { prepareCustomFieldMetadataForCreation } from 'src/engine/metadata-modules/field-metadata/utils/prepare-field-metadata-for-creation.util';
|
||||
import { type ObjectMetadataItemWithFieldMaps } from 'src/engine/metadata-modules/types/object-metadata-item-with-field-maps';
|
||||
import { type ObjectMetadataMaps } from 'src/engine/metadata-modules/types/object-metadata-maps';
|
||||
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 { computeMetadataNameFromLabel } from 'src/engine/metadata-modules/utils/validate-name-and-label-are-sync-or-throw.util';
|
||||
|
||||
@Injectable()
|
||||
@@ -30,15 +31,17 @@ export class FieldMetadataMorphRelationService {
|
||||
async createMorphRelationFieldMetadataItems({
|
||||
fieldMetadataForCreate,
|
||||
morphRelationsCreationPayload,
|
||||
objectMetadata,
|
||||
flatObjectMetadata,
|
||||
fieldMetadataRepository,
|
||||
objectMetadataMaps,
|
||||
flatObjectMetadataMaps,
|
||||
flatFieldMetadataMaps,
|
||||
}: {
|
||||
fieldMetadataForCreate: CreateFieldInput;
|
||||
morphRelationsCreationPayload: CreateFieldInput['morphRelationsCreationPayload'];
|
||||
objectMetadata: ObjectMetadataItemWithFieldMaps;
|
||||
flatObjectMetadata: FlatObjectMetadata;
|
||||
fieldMetadataRepository: Repository<FieldMetadataEntity>;
|
||||
objectMetadataMaps: ObjectMetadataMaps;
|
||||
flatObjectMetadataMaps: FlatEntityMaps<FlatObjectMetadata>;
|
||||
flatFieldMetadataMaps: FlatEntityMaps<FlatFieldMetadata>;
|
||||
}): Promise<FieldMetadataEntity[]> {
|
||||
if (
|
||||
!isDefined(morphRelationsCreationPayload) ||
|
||||
@@ -62,7 +65,9 @@ export class FieldMetadataMorphRelationService {
|
||||
|
||||
for (const relationCreationPayload of morphRelationsCreationPayload) {
|
||||
const targetObjectMetadata =
|
||||
objectMetadataMaps.byId[relationCreationPayload.targetObjectMetadataId];
|
||||
flatObjectMetadataMaps.byId[
|
||||
relationCreationPayload.targetObjectMetadataId
|
||||
];
|
||||
|
||||
if (!isDefined(targetObjectMetadata)) {
|
||||
throw new FieldMetadataException(
|
||||
@@ -95,8 +100,9 @@ export class FieldMetadataMorphRelationService {
|
||||
{
|
||||
fieldMetadataInput: relationFieldMetadataForCreate,
|
||||
fieldMetadataType: relationFieldMetadataForCreate.type,
|
||||
objectMetadataMaps,
|
||||
objectMetadata,
|
||||
flatObjectMetadataMaps,
|
||||
flatFieldMetadataMaps,
|
||||
flatObjectMetadata,
|
||||
},
|
||||
);
|
||||
|
||||
@@ -126,7 +132,7 @@ export class FieldMetadataMorphRelationService {
|
||||
{
|
||||
fieldMetadataInput: targetFieldMetadataToCreate,
|
||||
relationCreationPayload: {
|
||||
targetObjectMetadataId: objectMetadata.id,
|
||||
targetObjectMetadataId: flatObjectMetadata.id,
|
||||
targetFieldLabel: fieldMetadataForCreate.label,
|
||||
targetFieldIcon: fieldMetadataForCreate.icon ?? 'Icon123',
|
||||
type:
|
||||
|
||||
+26
-19
@@ -7,9 +7,9 @@ import {
|
||||
FieldMetadataType,
|
||||
RelationOnDeleteAction,
|
||||
} from 'twenty-shared/types';
|
||||
import { isDefined } from 'twenty-shared/utils';
|
||||
import { type Repository } from 'typeorm';
|
||||
import { v4 } from 'uuid';
|
||||
import { isDefined } from 'twenty-shared/utils';
|
||||
|
||||
import { RelationType } from 'src/engine/metadata-modules/field-metadata/interfaces/relation-type.interface';
|
||||
|
||||
@@ -23,8 +23,9 @@ import {
|
||||
import { computeMorphOrRelationFieldJoinColumnName } from 'src/engine/metadata-modules/field-metadata/utils/compute-morph-or-relation-field-join-column-name.util';
|
||||
import { prepareCustomFieldMetadataForCreation } from 'src/engine/metadata-modules/field-metadata/utils/prepare-field-metadata-for-creation.util';
|
||||
import { validateRelationCreationPayloadOrThrow } from 'src/engine/metadata-modules/field-metadata/utils/validate-relation-creation-payload-or-throw.util';
|
||||
import { type ObjectMetadataItemWithFieldMaps } from 'src/engine/metadata-modules/types/object-metadata-item-with-field-maps';
|
||||
import { type ObjectMetadataMaps } from 'src/engine/metadata-modules/types/object-metadata-maps';
|
||||
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 { validateFieldNameAvailabilityOrThrow } from 'src/engine/metadata-modules/utils/validate-field-name-availability.utils';
|
||||
import { validateMetadataNameOrThrow } from 'src/engine/metadata-modules/utils/validate-metadata-name-or-throw.utils';
|
||||
import { computeMetadataNameFromLabel } from 'src/engine/metadata-modules/utils/validate-name-and-label-are-sync-or-throw.util';
|
||||
@@ -49,9 +50,10 @@ type ValidateFieldMetadataArgs<T extends UpdateFieldInput | CreateFieldInput> =
|
||||
{
|
||||
fieldMetadataType: FieldMetadataType;
|
||||
fieldMetadataInput: T;
|
||||
objectMetadata: ObjectMetadataItemWithFieldMaps;
|
||||
flatObjectMetadata: FlatObjectMetadata;
|
||||
flatObjectMetadataMaps: FlatEntityMaps<FlatObjectMetadata>;
|
||||
flatFieldMetadataMaps: FlatEntityMaps<FlatFieldMetadata>;
|
||||
existingFieldMetadata?: FieldMetadataEntity;
|
||||
objectMetadataMaps: ObjectMetadataMaps;
|
||||
};
|
||||
|
||||
@Injectable()
|
||||
@@ -62,11 +64,11 @@ export class FieldMetadataRelationService {
|
||||
|
||||
async createRelationFieldMetadataItems({
|
||||
fieldMetadataInput,
|
||||
objectMetadata,
|
||||
flatObjectMetadata,
|
||||
fieldMetadataRepository,
|
||||
}: {
|
||||
fieldMetadataInput: CreateFieldInput;
|
||||
objectMetadata: ObjectMetadataItemWithFieldMaps;
|
||||
flatObjectMetadata: FlatObjectMetadata;
|
||||
fieldMetadataRepository: Repository<FieldMetadataEntity>;
|
||||
}): Promise<FieldMetadataEntity[]> {
|
||||
const createdFieldMetadataItem =
|
||||
@@ -98,7 +100,7 @@ export class FieldMetadataRelationService {
|
||||
this.computeCustomRelationFieldMetadataForCreation({
|
||||
fieldMetadataInput: targetFieldMetadataToCreate,
|
||||
relationCreationPayload: {
|
||||
targetObjectMetadataId: objectMetadata.id,
|
||||
targetObjectMetadataId: flatObjectMetadata.id,
|
||||
targetFieldLabel: fieldMetadataInput.label,
|
||||
targetFieldIcon: fieldMetadataInput.icon ?? 'Icon123',
|
||||
type:
|
||||
@@ -135,14 +137,16 @@ export class FieldMetadataRelationService {
|
||||
>({
|
||||
fieldMetadataInput,
|
||||
fieldMetadataType,
|
||||
objectMetadataMaps,
|
||||
objectMetadata,
|
||||
flatObjectMetadataMaps,
|
||||
flatFieldMetadataMaps,
|
||||
flatObjectMetadata,
|
||||
}: Pick<
|
||||
ValidateFieldMetadataArgs<T>,
|
||||
| 'fieldMetadataInput'
|
||||
| 'fieldMetadataType'
|
||||
| 'objectMetadataMaps'
|
||||
| 'objectMetadata'
|
||||
| 'flatObjectMetadataMaps'
|
||||
| 'flatFieldMetadataMaps'
|
||||
| 'flatObjectMetadata'
|
||||
>): Promise<T> {
|
||||
// TODO: clean typings, we should try to validate both update and create inputs in the same function
|
||||
const isRelation =
|
||||
@@ -158,7 +162,8 @@ export class FieldMetadataRelationService {
|
||||
) {
|
||||
validateFieldNameAvailabilityOrThrow({
|
||||
name: `${fieldMetadataInput.name}Id`,
|
||||
fieldMetadataMapById: objectMetadata.fieldsById,
|
||||
flatObjectMetadata,
|
||||
flatFieldMetadataMaps,
|
||||
});
|
||||
|
||||
const relationCreationPayload = (
|
||||
@@ -173,12 +178,12 @@ export class FieldMetadataRelationService {
|
||||
|
||||
validateMetadataNameOrThrow(computedMetadataNameFromLabel);
|
||||
|
||||
const objectMetadataTarget =
|
||||
objectMetadataMaps.byId[
|
||||
const targetFlatObjectMetadata =
|
||||
flatObjectMetadataMaps.byId[
|
||||
relationCreationPayload.targetObjectMetadataId
|
||||
];
|
||||
|
||||
if (!isDefined(objectMetadataTarget)) {
|
||||
if (!isDefined(targetFlatObjectMetadata)) {
|
||||
throw new FieldMetadataException(
|
||||
`Object metadata relation target not found for relation creation payload`,
|
||||
FieldMetadataExceptionCode.FIELD_METADATA_RELATION_MALFORMED,
|
||||
@@ -187,17 +192,19 @@ export class FieldMetadataRelationService {
|
||||
|
||||
validateFieldNameAvailabilityOrThrow({
|
||||
name: computedMetadataNameFromLabel,
|
||||
fieldMetadataMapById: objectMetadataTarget.fieldsById,
|
||||
flatObjectMetadata: targetFlatObjectMetadata,
|
||||
flatFieldMetadataMaps,
|
||||
});
|
||||
|
||||
validateFieldNameAvailabilityOrThrow({
|
||||
name: `${computedMetadataNameFromLabel}Id`,
|
||||
fieldMetadataMapById: objectMetadataTarget.fieldsById,
|
||||
flatObjectMetadata: targetFlatObjectMetadata,
|
||||
flatFieldMetadataMaps,
|
||||
});
|
||||
|
||||
if (
|
||||
computedMetadataNameFromLabel === fieldMetadataInput.name &&
|
||||
objectMetadata.id === objectMetadataTarget.id
|
||||
flatObjectMetadata.id === targetFlatObjectMetadata.id
|
||||
) {
|
||||
throw new FieldMetadataException(
|
||||
`Name "${computedMetadataNameFromLabel}" cannot be the same on both side of the relation`,
|
||||
|
||||
+9
-4
@@ -30,7 +30,9 @@ import {
|
||||
} from 'src/engine/metadata-modules/field-metadata/field-metadata.exception';
|
||||
import { FieldMetadataEnumValidationService } from 'src/engine/metadata-modules/field-metadata/services/field-metadata-enum-validation.service';
|
||||
import { isEnumFieldMetadataType } from 'src/engine/metadata-modules/field-metadata/utils/is-enum-field-metadata-type.util';
|
||||
import { type ObjectMetadataItemWithFieldMaps } from 'src/engine/metadata-modules/types/object-metadata-item-with-field-maps';
|
||||
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 { InvalidMetadataException } from 'src/engine/metadata-modules/utils/exceptions/invalid-metadata.exception';
|
||||
import { validateFieldNameAvailabilityOrThrow } from 'src/engine/metadata-modules/utils/validate-field-name-availability.utils';
|
||||
import { validateMetadataNameOrThrow } from 'src/engine/metadata-modules/utils/validate-metadata-name-or-throw.utils';
|
||||
@@ -38,7 +40,8 @@ import { validateMetadataNameOrThrow } from 'src/engine/metadata-modules/utils/v
|
||||
type ValidateFieldMetadataArgs = {
|
||||
fieldMetadataType: FieldMetadataType;
|
||||
fieldMetadataInput: CreateFieldInput | UpdateFieldInput;
|
||||
objectMetadata: ObjectMetadataItemWithFieldMaps;
|
||||
flatObjectMetadata: FlatObjectMetadata;
|
||||
flatFieldMetadataMaps: FlatEntityMaps<FlatFieldMetadata>;
|
||||
existingFieldMetadata?: FieldMetadataEntity;
|
||||
};
|
||||
|
||||
@@ -164,7 +167,8 @@ export class FieldMetadataValidationService {
|
||||
async validateFieldMetadata({
|
||||
fieldMetadataInput,
|
||||
fieldMetadataType,
|
||||
objectMetadata,
|
||||
flatObjectMetadata,
|
||||
flatFieldMetadataMaps,
|
||||
existingFieldMetadata,
|
||||
}: ValidateFieldMetadataArgs): Promise<void> {
|
||||
if (fieldMetadataInput.name) {
|
||||
@@ -187,7 +191,8 @@ export class FieldMetadataValidationService {
|
||||
try {
|
||||
validateFieldNameAvailabilityOrThrow({
|
||||
name: fieldMetadataInput.name,
|
||||
fieldMetadataMapById: objectMetadata.fieldsById,
|
||||
flatObjectMetadata,
|
||||
flatFieldMetadataMaps,
|
||||
});
|
||||
} catch (error) {
|
||||
if (error instanceof InvalidMetadataException) {
|
||||
|
||||
+5
-8
@@ -3,7 +3,6 @@ import {
|
||||
type CompositeProperty,
|
||||
} from 'twenty-shared/types';
|
||||
|
||||
import { type FieldMetadataEntity } from 'src/engine/metadata-modules/field-metadata/field-metadata.entity';
|
||||
import {
|
||||
FieldMetadataException,
|
||||
FieldMetadataExceptionCode,
|
||||
@@ -19,8 +18,8 @@ export type FieldTypeAndNameMetadata = {
|
||||
};
|
||||
|
||||
// TODO: If we need to implement custom name logic for columns, we can do it here
|
||||
export function computeColumnName<T extends FieldMetadataType>(
|
||||
fieldMetadataOrFieldName: FieldMetadataEntity<T> | string,
|
||||
export function computeColumnName(
|
||||
fieldMetadataOrFieldName: FieldTypeAndNameMetadata | string,
|
||||
options?: ComputeColumnNameOptions,
|
||||
): string {
|
||||
const generateName = (name: string) => {
|
||||
@@ -40,11 +39,9 @@ export function computeColumnName<T extends FieldMetadataType>(
|
||||
|
||||
return generateName(fieldMetadataOrFieldName.name);
|
||||
}
|
||||
export function computeCompositeColumnName<T extends FieldMetadataType>(
|
||||
fieldMetadataOrFieldName:
|
||||
| FieldTypeAndNameMetadata
|
||||
| FieldMetadataEntity<T>
|
||||
| string,
|
||||
|
||||
export function computeCompositeColumnName(
|
||||
fieldMetadataOrFieldName: FieldTypeAndNameMetadata | string,
|
||||
compositeProperty: CompositeProperty,
|
||||
): string {
|
||||
const generateName = (name: string) => {
|
||||
|
||||
-77
@@ -1,77 +0,0 @@
|
||||
import { FieldMetadataType } from 'twenty-shared/types';
|
||||
import { isDefined } from 'twenty-shared/utils';
|
||||
|
||||
import { RelationType } from 'src/engine/metadata-modules/field-metadata/interfaces/relation-type.interface';
|
||||
|
||||
import { type FieldMetadataEntity } from 'src/engine/metadata-modules/field-metadata/field-metadata.entity';
|
||||
import {
|
||||
FieldMetadataException,
|
||||
FieldMetadataExceptionCode,
|
||||
} from 'src/engine/metadata-modules/field-metadata/field-metadata.exception';
|
||||
import { type ObjectMetadataMaps } from 'src/engine/metadata-modules/types/object-metadata-maps';
|
||||
import {
|
||||
WorkspaceMigrationColumnActionType,
|
||||
type WorkspaceMigrationTableAction,
|
||||
WorkspaceMigrationTableActionType,
|
||||
} from 'src/engine/metadata-modules/workspace-migration/workspace-migration.entity';
|
||||
import { type WorkspaceMigrationFactory } from 'src/engine/metadata-modules/workspace-migration/workspace-migration.factory';
|
||||
import { computeObjectTargetTable } from 'src/engine/utils/compute-object-target-table.util';
|
||||
import { isFieldMetadataEntityOfType } from 'src/engine/utils/is-field-metadata-of-type.util';
|
||||
|
||||
export const createMigrationActions = async ({
|
||||
createdFieldMetadataItems,
|
||||
objectMetadataMap,
|
||||
isRemoteCreation,
|
||||
workspaceMigrationFactory,
|
||||
}: {
|
||||
createdFieldMetadataItems: FieldMetadataEntity[];
|
||||
objectMetadataMap: ObjectMetadataMaps['byId'];
|
||||
isRemoteCreation: boolean;
|
||||
workspaceMigrationFactory: WorkspaceMigrationFactory;
|
||||
}): Promise<WorkspaceMigrationTableAction[]> => {
|
||||
if (isRemoteCreation) {
|
||||
return [];
|
||||
}
|
||||
|
||||
const migrationActions: WorkspaceMigrationTableAction[] = [];
|
||||
|
||||
for (const createdFieldMetadata of createdFieldMetadataItems) {
|
||||
if (
|
||||
isFieldMetadataEntityOfType(
|
||||
createdFieldMetadata,
|
||||
FieldMetadataType.RELATION,
|
||||
) ||
|
||||
isFieldMetadataEntityOfType(
|
||||
createdFieldMetadata,
|
||||
FieldMetadataType.MORPH_RELATION,
|
||||
)
|
||||
) {
|
||||
const relationType = createdFieldMetadata.settings?.relationType;
|
||||
|
||||
if (relationType === RelationType.ONE_TO_MANY) {
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
const objectMetadata =
|
||||
objectMetadataMap[createdFieldMetadata.objectMetadataId];
|
||||
|
||||
if (!isDefined(objectMetadata)) {
|
||||
throw new FieldMetadataException(
|
||||
'Object metadata does not exist',
|
||||
FieldMetadataExceptionCode.OBJECT_METADATA_NOT_FOUND,
|
||||
);
|
||||
}
|
||||
|
||||
migrationActions.push({
|
||||
name: computeObjectTargetTable(objectMetadata),
|
||||
action: WorkspaceMigrationTableActionType.ALTER,
|
||||
columns: workspaceMigrationFactory.createColumnActions(
|
||||
WorkspaceMigrationColumnActionType.CREATE,
|
||||
createdFieldMetadata,
|
||||
),
|
||||
});
|
||||
}
|
||||
|
||||
return migrationActions;
|
||||
};
|
||||
+2
-1
@@ -1,7 +1,8 @@
|
||||
import { type FieldMetadataEntity } from 'src/engine/metadata-modules/field-metadata/field-metadata.entity';
|
||||
import { type FlatFieldMetadata } from 'src/engine/metadata-modules/flat-field-metadata/types/flat-field-metadata.type';
|
||||
|
||||
export const shouldExcludeFieldFromAgentToolSchema = (
|
||||
field: FieldMetadataEntity,
|
||||
field: FieldMetadataEntity | FlatFieldMetadata,
|
||||
excludeId = true,
|
||||
): boolean => {
|
||||
const excludedFieldNames = [
|
||||
|
||||
Reference in New Issue
Block a user