Fix flatFieldMetadata transpiler to fit real cache typing (#13529)

# Introduction
The cache is not accurately typed regarding the `fieldMetadataMaps` they
do not contain relations
In this way we:
- Created dedicated transpilation tools for cache manipulation
- Avoided recursivity in transpilation tools
- Fix and finalized the createMany fields metadata service v2
- Refactor the validator to handle validation :)

## What's next:
- Implem integration tests to make things run 🙃 
- migrate existing settings valdiation
- Finish the create object metadata service
- Handle update input transpilation and validation
This commit is contained in:
Paul Rastoin
2025-08-01 18:39:11 +02:00
committed by GitHub
parent dd47bfe6d5
commit b98d8c4d39
22 changed files with 384 additions and 124 deletions
@@ -13,10 +13,12 @@ import {
FieldMetadataExceptionCode,
} from 'src/engine/metadata-modules/field-metadata/field-metadata.exception';
import { FlatFieldMetadataValidatorService } from 'src/engine/metadata-modules/flat-field-metadata/services/flat-field-metadata-validator.service';
import { fromCreateFieldInputToFlatFieldMetadata } from 'src/engine/metadata-modules/flat-field-metadata/utils/from-create-field-input-to-flat-field-metadata.util';
import { FailedFlatFieldMetadataValidationExceptions } from 'src/engine/metadata-modules/flat-field-metadata/types/failed-flat-field-metadata-validation.type';
import { fromCreateFieldInputToFlatFieldAndItsFlatObjectMetadata } from 'src/engine/metadata-modules/flat-field-metadata/utils/from-create-field-input-to-flat-field-and-its-flat-object-metadata.util';
import { FlatObjectMetadata } from 'src/engine/metadata-modules/flat-object-metadata/types/flat-object-metadata.type';
import { fromObjectMetadataMapsToFlatObjectMetadatas } from 'src/engine/metadata-modules/flat-object-metadata/utils/from-object-metadata-maps-to-flat-object-metadatas.util';
import { mergeTwoFlatObjectMetadatas } from 'src/engine/metadata-modules/flat-object-metadata/utils/merge-two-flat-object-metadatas.util';
import { getFieldMetadataEntityFromCachedObjectMetadataMaps } from 'src/engine/metadata-modules/utils/get-field-metadata-entity-from-cached-object-metadata-maps.util';
import { WorkspaceMetadataCacheService } from 'src/engine/metadata-modules/workspace-metadata-cache/services/workspace-metadata-cache.service';
import { WorkspaceMigrationBuilderV2Service } from 'src/engine/workspace-manager/workspace-migration-v2/workspace-migration-builder-v2/workspace-migration-builder-v2.service';
import { WorkspaceMigrationRunnerV2Service } from 'src/engine/workspace-manager/workspace-migration-v2/workspace-migration-runner-v2/workspace-migration-runner-v2.service';
@@ -66,29 +68,34 @@ export class FieldMetadataServiceV2 extends TypeOrmQueryService<FieldMetadataEnt
const existingFlatObjectMetadatas =
fromObjectMetadataMapsToFlatObjectMetadatas(objectMetadataMaps);
const flatFieldToCreateAndItsFlatObjectMetadataArray = await Promise.all(
fieldMetadataInputs.map(
async (fieldMetadataInput) =>
await fromCreateFieldInputToFlatFieldAndItsFlatObjectMetadata({
existingFlatObjectMetadatas,
rawCreateFieldInput: fieldMetadataInput,
}),
),
);
let flatObjectMetadatasWithNewFields: FlatObjectMetadata[] = [];
for (const fieldMetadataInput of fieldMetadataInputs) {
const createdFlatFieldsMetadataAndParentFlatObjectMetadata =
await fromCreateFieldInputToFlatFieldMetadata({
existingFlatObjectMetadatas,
rawCreateFieldInput: fieldMetadataInput,
});
for (const flatFieldMetadataAndParentPair of flatFieldToCreateAndItsFlatObjectMetadataArray) {
const flatFieldMetadataValidationPromises =
flatFieldMetadataAndParentPair.flatMap<
Promise<FailedFlatFieldMetadataValidationExceptions[]>
>(({ flatFieldMetadata: flatFieldMetadataToValidate }) =>
this.flatFieldMetadataValidatorService.validateOneFlatFieldMetadata({
existingFlatObjectMetadatas,
flatFieldMetadataToValidate,
workspaceId,
}),
);
const createdFlatFieldMetadataValidationResult = (
await Promise.all(
createdFlatFieldsMetadataAndParentFlatObjectMetadata.map(
({ flatFieldMetadata: flatFieldMetadataToValidate }) =>
this.flatFieldMetadataValidatorService.validateOneFlatFieldMetadata(
{
existingFlatObjectMetadatas,
flatFieldMetadataToValidate,
workspaceId,
},
),
),
)
).filter(isDefined);
await Promise.all(flatFieldMetadataValidationPromises)
)
.flat()
.filter(isDefined);
if (createdFlatFieldMetadataValidationResult.length > 0) {
const errors = createdFlatFieldMetadataValidationResult.flat();
@@ -100,7 +107,7 @@ export class FieldMetadataServiceV2 extends TypeOrmQueryService<FieldMetadataEnt
}
const updatedFlatObjectMetadatas =
createdFlatFieldsMetadataAndParentFlatObjectMetadata.map<FlatObjectMetadata>(
flatFieldMetadataAndParentPair.map<FlatObjectMetadata>(
({ flatFieldMetadata, parentFlatObjectMetadata }) => {
return {
...parentFlatObjectMetadata,
@@ -129,11 +136,27 @@ export class FieldMetadataServiceV2 extends TypeOrmQueryService<FieldMetadataEnt
await this.workspaceMigrationRunnerV2Service.run(workspaceMigration);
// const recomputedCache =
// await this.workspaceMetadataCacheService.getExistingOrRecomputeMetadataMaps(
// { workspaceId },
// );
const recomputedCache =
await this.workspaceMetadataCacheService.getExistingOrRecomputeMetadataMaps(
{ workspaceId },
);
return []; //TODO to retrieve from cache or directly from find
return flatFieldToCreateAndItsFlatObjectMetadataArray.flatMap<FieldMetadataEntity>(
(createdFlatFieldAndItsParentFlatObject) => {
return createdFlatFieldAndItsParentFlatObject
.map(
({
flatFieldMetadata: { id: fieldMetadataId },
parentFlatObjectMetadata: { id: objectMetadataId },
}) =>
getFieldMetadataEntityFromCachedObjectMetadataMaps({
fieldMetadataId,
objectMetadataId,
objectMetadataMaps: recomputedCache.objectMetadataMaps,
}),
)
.filter(isDefined);
},
);
}
}
@@ -0,0 +1,7 @@
import { FieldMetadataType } from 'twenty-shared/types';
import { FlatFieldMetadata } from 'src/engine/metadata-modules/flat-field-metadata/types/flat-field-metadata.type';
export type FlatRelationTargetFieldMetadata = FlatFieldMetadata<
FieldMetadataType.RELATION | FieldMetadataType.MORPH_RELATION
>['flatRelationTargetFieldMetadata'];
@@ -24,18 +24,18 @@ type FromCreateFieldInputToFlatObjectMetadata = {
rawCreateFieldInput: CreateFieldInput;
existingFlatObjectMetadatas: FlatObjectMetadata[];
};
export type FlatFieldMetadataAndParentFlatObjectMetadata<
export type FlatFieldAndItsFlatObjectMetadata<
T extends FieldMetadataType = FieldMetadataType,
> = {
flatFieldMetadata: FlatFieldMetadata<T>;
parentFlatObjectMetadata: FlatObjectMetadata;
};
export const fromCreateFieldInputToFlatFieldMetadata = async ({
export const fromCreateFieldInputToFlatFieldAndItsFlatObjectMetadata = async ({
existingFlatObjectMetadatas,
rawCreateFieldInput,
}: FromCreateFieldInputToFlatObjectMetadata): Promise<
FlatFieldMetadataAndParentFlatObjectMetadata[]
FlatFieldAndItsFlatObjectMetadata[]
> => {
if (rawCreateFieldInput.isRemoteCreation) {
throw new FieldMetadataException(
@@ -1,32 +1,42 @@
import { FieldMetadataType } from 'twenty-shared/types';
import { isDefined, removePropertiesFromRecord } from 'twenty-shared/utils';
import {
FieldMetadataType,
RelationAndMorphRelationFieldMetadataType,
} from 'twenty-shared/types';
import { removePropertiesFromRecord } from 'twenty-shared/utils';
import { 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 {
FieldMetadataEntityRelationProperties,
FlatFieldMetadata,
fieldMetadataRelationProperties,
} from 'src/engine/metadata-modules/flat-field-metadata/types/flat-field-metadata.type';
import { fromObjectMetadataEntityToFlatObjectMetadata } from 'src/engine/metadata-modules/flat-object-metadata/utils/from-object-metadata-entity-to-flat-object-metadata.util';
import { FlatRelationTargetFieldMetadata } from 'src/engine/metadata-modules/flat-field-metadata/types/flat-relation-target-field-metadata.type';
import { isFieldMetadataEntityOfType } from 'src/engine/utils/is-field-metadata-of-type.util';
import { fromFlatObjectMetadataToFlatObjectMetadataWithoutFields } from 'src/engine/workspace-manager/workspace-migration-v2/workspace-migration-builder-v2/utils/from-flat-object-metadata-to-flat-object-metadata-without-fields.util';
import { fromObjectMetadataEntityToFlatObjectMetadataWithoutFields } from 'src/engine/workspace-manager/workspace-migration-v2/workspace-migration-builder-v2/utils/from-object-metadata-entity-to-flat-object-metadata-without-fields.util';
export const fromFieldMetadataEntityToFlatRelationTargetFieldMetadata = (
fieldMetadataEntity: FieldMetadataEntity<RelationAndMorphRelationFieldMetadataType>,
): FlatRelationTargetFieldMetadata => {
return {
uniqueIdentifier: fieldMetadataEntity.standardId ?? fieldMetadataEntity.id,
...removePropertiesFromRecord(fieldMetadataEntity, [
'relationTargetObjectMetadata',
'relationTargetFieldMetadata',
]),
};
};
// TODO refactor this method should not be recursive but depth 1
export const fromFieldMetadataEntityToFlatFieldMetadata = <
T extends FieldMetadataType,
>(
fieldMetadataEntity: FieldMetadataEntity<T>,
/**
* private depth bottleneck
*/
_depth?: number,
// This is intended to be abstract
): FlatFieldMetadata => {
if (isDefined(_depth) && _depth > 1) {
throw new Error(
'fromFieldMetadataEntityToFlatFieldMetadata entering a possible infinite loop',
);
}
if (
isFieldMetadataEntityOfType(
fieldMetadataEntity,
@@ -44,20 +54,26 @@ export const fromFieldMetadataEntityToFlatFieldMetadata = <
FieldMetadataEntityRelationProperties
>(fieldMetadataEntity, fieldMetadataRelationProperties);
const newDepth = isDefined(_depth) ? _depth + 1 : 1;
const flatRelationTargetFieldMetadata =
fromFieldMetadataEntityToFlatFieldMetadata(
if (
!isFieldMetadataEntityOfType(
fieldMetadataEntity.relationTargetFieldMetadata,
FieldMetadataType.RELATION,
)
) {
throw new FieldMetadataException(
'Relation target field is not a field metadata type relation',
FieldMetadataExceptionCode.FIELD_METADATA_RELATION_MALFORMED,
);
}
const flatRelationTargetFieldMetadata =
fromFieldMetadataEntityToFlatRelationTargetFieldMetadata(
fieldMetadataEntity.relationTargetFieldMetadata,
newDepth,
);
const flatObjectTargetFieldMetadata =
fromObjectMetadataEntityToFlatObjectMetadata(
fieldMetadataEntity.relationTargetObjectMetadata,
);
const flatRelationTargetObjectMetadata =
fromFlatObjectMetadataToFlatObjectMetadataWithoutFields(
flatObjectTargetFieldMetadata,
fromObjectMetadataEntityToFlatObjectMetadataWithoutFields(
fieldMetadataEntity.relationTargetObjectMetadata,
);
return {
@@ -9,7 +9,7 @@ import {
} from 'src/engine/metadata-modules/field-metadata/field-metadata.exception';
import { validateRelationCreationPayloadOrThrow } from 'src/engine/metadata-modules/field-metadata/utils/validate-relation-creation-payload.util';
import { FlatFieldMetadata } from 'src/engine/metadata-modules/flat-field-metadata/types/flat-field-metadata.type';
import { FlatFieldMetadataAndParentFlatObjectMetadata } from 'src/engine/metadata-modules/flat-field-metadata/utils/from-create-field-input-to-flat-field-metadata.util';
import { FlatFieldAndItsFlatObjectMetadata } from 'src/engine/metadata-modules/flat-field-metadata/utils/from-create-field-input-to-flat-field-and-its-flat-object-metadata.util';
import { getDefaultFlatFieldMetadata } from 'src/engine/metadata-modules/flat-field-metadata/utils/get-default-flat-field-metadata-from-create-field-input.util';
import { 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';
@@ -24,7 +24,7 @@ export const fromRelationCreateFieldInputToFlatFieldMetadata = async ({
sourceParentFlatObjectMetadata,
createFieldInput,
}: FromRelationCreateFieldInputToFlatFieldMetadataArgs): Promise<
FlatFieldMetadataAndParentFlatObjectMetadata[]
FlatFieldAndItsFlatObjectMetadata[]
> => {
const { relationCreationPayload } = createFieldInput;
@@ -104,5 +104,5 @@ export const fromRelationCreateFieldInputToFlatFieldMetadata = async ({
flatFieldMetadata: targetFlatFieldMetadata,
parentFlatObjectMetadata: targetParentFlatObjectMetadata,
},
] satisfies FlatFieldMetadataAndParentFlatObjectMetadata<FieldMetadataType.RELATION>[];
] satisfies FlatFieldAndItsFlatObjectMetadata<FieldMetadataType.RELATION>[];
};
@@ -0,0 +1,118 @@
import { FieldMetadataType } from 'twenty-shared/types';
import { isDefined } from 'twenty-shared/utils';
import { FlatFieldMetadata } from 'src/engine/metadata-modules/flat-field-metadata/types/flat-field-metadata.type';
import { FlatRelationTargetFieldMetadata } from 'src/engine/metadata-modules/flat-field-metadata/types/flat-relation-target-field-metadata.type';
import { fromObjectMetadataItemWithFieldMapsToFlatObjectMetadataWithoutFields } from 'src/engine/metadata-modules/flat-object-metadata/utils/from-object-metadata-item-with-field-maps-to-flat-object-metadata-without-fields.util';
import { CachedFieldMetadataEntity } from 'src/engine/metadata-modules/types/cached-field-metadata-entity';
import { ObjectMetadataMaps } from 'src/engine/metadata-modules/types/object-metadata-maps';
import {
WorkspaceMetadataCacheException,
WorkspaceMetadataCacheExceptionCode,
} from 'src/engine/metadata-modules/workspace-metadata-cache/exceptions/workspace-metadata-cache.exception';
import { isCachedFieldMetadataEntityOfType } from 'src/engine/utils/is-cached-field-metadata-of-type.util';
const fromCachedRelationFieldMetadataEntityToFlatRelationTargetFieldMetadata = <
T extends FieldMetadataType.RELATION | FieldMetadataType.MORPH_RELATION,
>(
cachedFieldMetadataEntity: CachedFieldMetadataEntity<T>,
): FlatRelationTargetFieldMetadata => {
return {
...cachedFieldMetadataEntity,
uniqueIdentifier:
cachedFieldMetadataEntity.standardId ?? cachedFieldMetadataEntity.id,
type: cachedFieldMetadataEntity.type,
};
};
type CachedFieldMetadataEntityAndObjectMetadataMap<
T extends FieldMetadataType,
> = {
objectMetadataMaps: ObjectMetadataMaps;
cachedFieldMetadataEntity: CachedFieldMetadataEntity<T>;
};
export const fromCachedFieldMetadataEntityToFlatFieldMetadata = <
T extends FieldMetadataType,
>(
{
cachedFieldMetadataEntity,
objectMetadataMaps,
}: CachedFieldMetadataEntityAndObjectMetadataMap<T>,
// This is intended to be abstract return type
): FlatFieldMetadata => {
if (
isCachedFieldMetadataEntityOfType(
cachedFieldMetadataEntity,
FieldMetadataType.RELATION,
) ||
isCachedFieldMetadataEntityOfType(
cachedFieldMetadataEntity,
FieldMetadataType.MORPH_RELATION,
)
) {
const relationTargetObjectMetadataItemWithFieldsMaps =
objectMetadataMaps.byId[
cachedFieldMetadataEntity.relationTargetObjectMetadataId
];
if (!isDefined(relationTargetObjectMetadataItemWithFieldsMaps)) {
throw new WorkspaceMetadataCacheException(
'Cache object is not up to date',
WorkspaceMetadataCacheExceptionCode.OBJECT_METADATA_MAP_NOT_FOUND,
);
}
const flatRelationTargetObjectMetadata =
fromObjectMetadataItemWithFieldMapsToFlatObjectMetadataWithoutFields(
relationTargetObjectMetadataItemWithFieldsMaps,
);
const relationTargetCachedFieldMetadata =
relationTargetObjectMetadataItemWithFieldsMaps.fieldsById[
cachedFieldMetadataEntity.relationTargetFieldMetadataId
];
if (!isDefined(relationTargetCachedFieldMetadata)) {
throw new WorkspaceMetadataCacheException(
'Cache field is not up to date',
WorkspaceMetadataCacheExceptionCode.FIELD_METADATA_NOT_FOUND,
);
}
if (
!isCachedFieldMetadataEntityOfType(
relationTargetCachedFieldMetadata,
FieldMetadataType.RELATION,
)
) {
throw new WorkspaceMetadataCacheException(
'Cache field is invalid',
WorkspaceMetadataCacheExceptionCode.FIELD_METADATA_INVALID,
);
}
const flatRelationTargetFieldMetadata =
fromCachedRelationFieldMetadataEntityToFlatRelationTargetFieldMetadata(
relationTargetCachedFieldMetadata,
);
return {
...cachedFieldMetadataEntity,
uniqueIdentifier:
cachedFieldMetadataEntity.standardId ?? cachedFieldMetadataEntity.id,
flatRelationTargetFieldMetadata,
flatRelationTargetObjectMetadata,
type: cachedFieldMetadataEntity.type,
} satisfies FlatFieldMetadata<
FieldMetadataType.RELATION | FieldMetadataType.MORPH_RELATION
>;
}
return {
...cachedFieldMetadataEntity,
uniqueIdentifier:
cachedFieldMetadataEntity.standardId ?? cachedFieldMetadataEntity.id,
flatRelationTargetFieldMetadata: null,
flatRelationTargetObjectMetadata: null,
};
};
@@ -0,0 +1,22 @@
import { FlatObjectMetadataWithoutFields } from 'src/engine/metadata-modules/flat-object-metadata/types/flat-object-metadata.type';
import { ObjectMetadataItemWithFieldMaps } from 'src/engine/metadata-modules/types/object-metadata-item-with-field-maps';
import { fromObjectMetadataEntityToFlatObjectMetadataWithoutFields } from 'src/engine/workspace-manager/workspace-migration-v2/workspace-migration-builder-v2/utils/from-object-metadata-entity-to-flat-object-metadata-without-fields.util';
export const fromObjectMetadataItemWithFieldMapsToFlatObjectMetadataWithoutFields =
(
objectMetadataItemWithFieldMaps: ObjectMetadataItemWithFieldMaps,
): FlatObjectMetadataWithoutFields => {
const {
fieldsById: _fieldsById,
fieldIdByJoinColumnName: _fieldIdByJoinColumnName,
fieldIdByName: _fieldIdByName,
indexMetadatas,
...rest
} = objectMetadataItemWithFieldMaps;
return fromObjectMetadataEntityToFlatObjectMetadataWithoutFields({
...rest,
fields: [],
indexMetadatas,
});
};
@@ -1,21 +1,40 @@
import { FlatObjectMetadata } from 'src/engine/metadata-modules/flat-object-metadata/types/flat-object-metadata.type';
import { fromObjectMetadataEntityToFlatObjectMetadata } from 'src/engine/metadata-modules/flat-object-metadata/utils/from-object-metadata-entity-to-flat-object-metadata.util';
import { fromCachedFieldMetadataEntityToFlatFieldMetadata } from 'src/engine/metadata-modules/flat-object-metadata/utils/from-cached-field-metadata-entity-to-flat-field-metadata.util';
import { CachedFieldMetadataEntity } from 'src/engine/metadata-modules/types/cached-field-metadata-entity';
import { ObjectMetadataItemWithFieldMaps } from 'src/engine/metadata-modules/types/object-metadata-item-with-field-maps';
import { ObjectMetadataMaps } from 'src/engine/metadata-modules/types/object-metadata-maps';
export const fromObjectMetadataItemWithFieldMapsToFlatObjectMetadata = (
objectMetadataItemWithFieldMaps: ObjectMetadataItemWithFieldMaps,
): FlatObjectMetadata => {
type FromObjectMetadataItemWithFieldMapsToFlatObjectMetadataArgs = {
objectMetadataItemWithFieldMaps: ObjectMetadataItemWithFieldMaps;
objectMetadataMaps: ObjectMetadataMaps;
};
export const fromObjectMetadataItemWithFieldMapsToFlatObjectMetadata = ({
objectMetadataItemWithFieldMaps,
objectMetadataMaps,
}: FromObjectMetadataItemWithFieldMapsToFlatObjectMetadataArgs): FlatObjectMetadata => {
const {
fieldsById,
fieldIdByJoinColumnName: _fieldIdByJoinColumnName,
fieldIdByName: _fieldIdByName,
indexMetadatas,
indexMetadatas: _indexMetadatas,
...rest
} = objectMetadataItemWithFieldMaps;
return fromObjectMetadataEntityToFlatObjectMetadata({
const cachedFieldMetadataEntities =
Object.values<CachedFieldMetadataEntity>(fieldsById);
const flatFieldMetadatas = cachedFieldMetadataEntities.map(
(cachedFieldMetadataEntity) =>
fromCachedFieldMetadataEntityToFlatFieldMetadata({
cachedFieldMetadataEntity,
objectMetadataMaps,
}),
);
return {
...rest,
fields: Object.values(fieldsById),
indexMetadatas,
});
flatFieldMetadatas,
uniqueIdentifier: rest.standardId ?? rest.id,
flatIndexMetadatas: [], // prastoin TODO convert from indexMetadatas to flatIndexMetadatas
};
};
@@ -3,6 +3,10 @@ import { isDefined } from 'twenty-shared/utils';
import { FlatObjectMetadata } from 'src/engine/metadata-modules/flat-object-metadata/types/flat-object-metadata.type';
import { fromObjectMetadataItemWithFieldMapsToFlatObjectMetadata } from 'src/engine/metadata-modules/flat-object-metadata/utils/from-object-metadata-item-with-field-maps-to-flat-object-metadata.util';
import { ObjectMetadataMaps } from 'src/engine/metadata-modules/types/object-metadata-maps';
import {
WorkspaceMetadataCacheException,
WorkspaceMetadataCacheExceptionCode,
} from 'src/engine/metadata-modules/workspace-metadata-cache/exceptions/workspace-metadata-cache.exception';
export const fromObjectMetadataMapsToFlatObjectMetadatas = (
objectMetadataMaps: ObjectMetadataMaps,
@@ -10,12 +14,19 @@ export const fromObjectMetadataMapsToFlatObjectMetadatas = (
const objectMetadataIds = Object.keys(objectMetadataMaps.byId);
return objectMetadataIds.flatMap<FlatObjectMetadata>((objectMetadataId) => {
const occurrence = objectMetadataMaps.byId[objectMetadataId];
const objectMetadataItemWithFieldMaps =
objectMetadataMaps.byId[objectMetadataId];
if (!isDefined(occurrence)) {
return [];
if (!isDefined(objectMetadataItemWithFieldMaps)) {
throw new WorkspaceMetadataCacheException(
'Object metadata not found in cache',
WorkspaceMetadataCacheExceptionCode.OBJECT_METADATA_MAP_NOT_FOUND,
);
}
return fromObjectMetadataItemWithFieldMapsToFlatObjectMetadata(occurrence);
return fromObjectMetadataItemWithFieldMapsToFlatObjectMetadata({
objectMetadataItemWithFieldMaps,
objectMetadataMaps,
});
});
};
@@ -41,8 +41,8 @@ export class ObjectMetadataServiceV2 extends TypeOrmQueryService<ObjectMetadataE
fromCreateObjectInputToFlatObjectMetadata(objectMetadataInput);
const existingFlatObjectMetadatas =
fromObjectMetadataMapsToFlatObjectMetadatas(objectMetadataMaps);
// @ts-expect-error TODO implement validateFlatObjectMetadataData
const createdFlatObjectMetadata = validateFlatObjectMetadataData({
// @ts-expect-error TODO implement validateFlatObjectMetadata
const createdFlatObjectMetadata = validateFlatObjectMetadata({
existing:
// Here we assume that EVERYTHING is in cache and up to date, this is very critical, also race condition prone :thinking:
fromObjectMetadataMapsToFlatObjectMetadatas(objectMetadataMaps),
@@ -13,12 +13,8 @@ import {
Repository,
} from 'typeorm';
import { FeatureFlagKey } from 'src/engine/core-modules/feature-flag/enums/feature-flag-key.enum';
import { FeatureFlagService } from 'src/engine/core-modules/feature-flag/services/feature-flag.service';
import { DataSourceService } from 'src/engine/metadata-modules/data-source/data-source.service';
import { FieldMetadataEntity } from 'src/engine/metadata-modules/field-metadata/field-metadata.entity';
import { fromCreateObjectInputToFlatObjectMetadata } from 'src/engine/metadata-modules/flat-object-metadata/utils/from-create-object-input-to-flat-object-metadata.util';
import { fromObjectMetadataMapsToFlatObjectMetadatas } from 'src/engine/metadata-modules/flat-object-metadata/utils/from-object-metadata-maps-to-flat-object-metadatas.util';
import { IndexMetadataService } from 'src/engine/metadata-modules/index-metadata/index-metadata.service';
import { DeleteOneObjectInput } from 'src/engine/metadata-modules/object-metadata/dtos/delete-object.input';
import {
@@ -50,8 +46,6 @@ import { computeObjectTargetTable } from 'src/engine/utils/compute-object-target
import { isFieldMetadataEntityOfType } from 'src/engine/utils/is-field-metadata-of-type.util';
import { WorkspaceDataSourceService } from 'src/engine/workspace-datasource/workspace-datasource.service';
import { WorkspaceMigrationRunnerService } from 'src/engine/workspace-manager/workspace-migration-runner/workspace-migration-runner.service';
import { WorkspaceMigrationBuilderV2Service } from 'src/engine/workspace-manager/workspace-migration-v2/workspace-migration-builder-v2/workspace-migration-builder-v2.service';
import { WorkspaceMigrationRunnerV2Service } from 'src/engine/workspace-manager/workspace-migration-v2/workspace-migration-runner-v2/workspace-migration-runner-v2.service';
import { CUSTOM_OBJECT_STANDARD_FIELD_IDS } from 'src/engine/workspace-manager/workspace-sync-metadata/constants/standard-field-ids';
import { isSearchableFieldType } from 'src/engine/workspace-manager/workspace-sync-metadata/utils/is-searchable-field.util';
@@ -76,9 +70,6 @@ export class ObjectMetadataService extends TypeOrmQueryService<ObjectMetadataEnt
private readonly indexMetadataService: IndexMetadataService,
private readonly workspacePermissionsCacheService: WorkspacePermissionsCacheService,
private readonly workspaceDataSourceService: WorkspaceDataSourceService,
private readonly workspaceMigrationBuilderV2: WorkspaceMigrationBuilderV2Service,
private readonly featureFlagService: FeatureFlagService,
private readonly workspaceMigrationRunnerV2Service: WorkspaceMigrationRunnerV2Service,
) {
super(objectMetadataRepository);
}
@@ -125,41 +116,6 @@ export class ObjectMetadataService extends TypeOrmQueryService<ObjectMetadataEnt
objectMetadataInput.workspaceId,
);
const isWorkspaceMigrationV2Enabled =
await this.featureFlagService.isFeatureEnabled(
FeatureFlagKey.IS_WORKSPACE_MIGRATION_V2_ENABLED,
objectMetadataInput.workspaceId,
);
if (isWorkspaceMigrationV2Enabled) {
const createdRawFlatObjectMetadata =
fromCreateObjectInputToFlatObjectMetadata(objectMetadataInput);
const existingFlatObjectMetadatas =
fromObjectMetadataMapsToFlatObjectMetadatas(objectMetadataMaps);
// @ts-expect-error TODO implement validateFlatObjectMetadataData
const createdFlatObjectMetadata = validateFlatObjectMetadataData({
existing:
// Here we assume that EVERYTHING is in cache and up to date, this is very critical, also race condition prone :thinking:
fromObjectMetadataMapsToFlatObjectMetadatas(objectMetadataMaps),
toValidate: [createdRawFlatObjectMetadata],
});
const workspaceMigration = this.workspaceMigrationBuilderV2.build({
objectMetadataFromToInputs: {
from: existingFlatObjectMetadatas,
to: [createdFlatObjectMetadata],
},
inferDeletionFromMissingObjectFieldIndex: false,
workspaceId: objectMetadataInput.workspaceId,
});
await this.workspaceMigrationRunnerV2Service.run(workspaceMigration);
// What to return exactly ? We now won't have access to the entity directly
// We could still retrieve it afterwards using a find on object metadata id or return a flat now
return createdFlatObjectMetadata;
}
objectMetadataInput.labelSingular = capitalize(
objectMetadataInput.labelSingular,
);
@@ -0,0 +1,8 @@
import { FieldMetadataType } from 'twenty-shared/types';
import { FieldMetadataEntity } from 'src/engine/metadata-modules/field-metadata/field-metadata.entity';
import { FieldMetadataEntityRelationProperties } from 'src/engine/metadata-modules/flat-field-metadata/types/flat-field-metadata.type';
export type CachedFieldMetadataEntity<
T extends FieldMetadataType = FieldMetadataType,
> = Omit<FieldMetadataEntity<T>, FieldMetadataEntityRelationProperties>;
@@ -1,3 +1,3 @@
import { FieldMetadataEntity } from 'src/engine/metadata-modules/field-metadata/field-metadata.entity';
export type FieldMetadataMap = Record<string, FieldMetadataEntity>;
export type FieldMetadataMap = Record<string, FieldMetadataEntity>; // TODO refactor Should be CachedFieldMetadataEntity or best FlatFieldMetadata
@@ -0,0 +1,42 @@
import { isDefined } from 'twenty-shared/utils';
import { FieldMetadataEntity } from 'src/engine/metadata-modules/field-metadata/field-metadata.entity';
import { IndexFieldMetadataEntity } from 'src/engine/metadata-modules/index-metadata/index-field-metadata.entity';
import { ObjectMetadataEntity } from 'src/engine/metadata-modules/object-metadata/object-metadata.entity';
import { CachedFieldMetadataEntity } from 'src/engine/metadata-modules/types/cached-field-metadata-entity';
import { ObjectMetadataMaps } from 'src/engine/metadata-modules/types/object-metadata-maps';
export const getFieldMetadataEntityFromCachedObjectMetadataMaps = ({
fieldMetadataId,
objectMetadataId,
objectMetadataMaps,
}: {
objectMetadataMaps: ObjectMetadataMaps;
objectMetadataId: string;
fieldMetadataId: string;
}): FieldMetadataEntity | undefined => {
const objectMetadataFromCache = objectMetadataMaps.byId[objectMetadataId];
if (!isDefined(objectMetadataFromCache)) {
return undefined;
}
const fieldMetadataFromCache: CachedFieldMetadataEntity =
objectMetadataFromCache.fieldsById[fieldMetadataId];
if (!isDefined(fieldMetadataFromCache)) {
return undefined;
}
// We need to determine if graphql will be able to handle relations crafting byitself
return {
...fieldMetadataFromCache,
object: {} as ObjectMetadataEntity,
relationTargetFieldMetadata: null,
relationTargetObjectMetadata: null,
indexFieldMetadatas: {} as IndexFieldMetadataEntity,
fieldPermissions: [],
createdAt: new Date(fieldMetadataFromCache.createdAt),
updatedAt: new Date(fieldMetadataFromCache.updatedAt),
};
};
@@ -4,5 +4,7 @@ export class WorkspaceMetadataCacheException extends CustomException<WorkspaceMe
export enum WorkspaceMetadataCacheExceptionCode {
OBJECT_METADATA_MAP_NOT_FOUND = 'Object Metadata map not found',
FIELD_METADATA_NOT_FOUND = 'Field Metadata not found',
FIELD_METADATA_INVALID = 'Field Metadata is invalid',
OBJECT_METADATA_COLLECTION_NOT_FOUND = 'Object Metadata collection not found',
}
@@ -15,7 +15,7 @@ import {
} from 'src/engine/metadata-modules/workspace-metadata-version/exceptions/workspace-metadata-version.exception';
import { WorkspaceCacheStorageService } from 'src/engine/workspace-cache-storage/workspace-cache-storage.service';
type getExistingOrRecomputeMetadataMapsResult = {
type GetExistingOrRecomputeMetadataMapsResult = {
objectMetadataMaps: ObjectMetadataMaps;
metadataVersion: number;
};
@@ -38,7 +38,7 @@ export class WorkspaceMetadataCacheService {
workspaceId,
}: {
workspaceId: string;
}): Promise<getExistingOrRecomputeMetadataMapsResult> {
}): Promise<GetExistingOrRecomputeMetadataMapsResult> {
const currentCacheVersion =
await this.getMetadataVersionFromCache(workspaceId);
@@ -84,7 +84,7 @@ export class WorkspaceMetadataCacheService {
workspaceId,
}: {
workspaceId: string;
}): Promise<getExistingOrRecomputeMetadataMapsResult> {
}): Promise<GetExistingOrRecomputeMetadataMapsResult> {
const currentDatabaseVersion =
await this.getMetadataVersionFromDatabase(workspaceId);
@@ -0,0 +1,14 @@
import { FieldMetadataType } from 'twenty-shared/types';
import { CachedFieldMetadataEntity } from 'src/engine/metadata-modules/types/cached-field-metadata-entity';
export function isCachedFieldMetadataEntityOfType<
Field extends CachedFieldMetadataEntity<FieldMetadataType>,
Type extends FieldMetadataType,
>(
fieldMetadata: Pick<Field, 'type'>,
type: Type,
): fieldMetadata is CachedFieldMetadataEntity &
CachedFieldMetadataEntity<Type> {
return fieldMetadata.type === type;
}
@@ -0,0 +1,12 @@
import { FlatObjectMetadataWithoutFields } from 'src/engine/metadata-modules/flat-object-metadata/types/flat-object-metadata.type';
import { fromObjectMetadataEntityToFlatObjectMetadata } from 'src/engine/metadata-modules/flat-object-metadata/utils/from-object-metadata-entity-to-flat-object-metadata.util';
import { ObjectMetadataEntity } from 'src/engine/metadata-modules/object-metadata/object-metadata.entity';
export const fromObjectMetadataEntityToFlatObjectMetadataWithoutFields = (
objectMetadataEntity: ObjectMetadataEntity,
): FlatObjectMetadataWithoutFields => {
return fromObjectMetadataEntityToFlatObjectMetadata({
...objectMetadataEntity,
fields: [],
});
};
@@ -36,6 +36,7 @@ export class WorkspaceMigrationBuilderV2Service {
createdObjectMetadata,
deletedObjectMetadata,
updatedObjectMetadata,
inferDeletionFromMissingObjectFieldIndex,
});
const createdObjectMetadataCreateIndexActions =
@@ -13,11 +13,14 @@ import {
} from 'src/engine/workspace-manager/workspace-migration-v2/workspace-migration-builder-v2/utils/get-workspace-migration-v2-object-actions';
export type CreatedDeletedUpdatedObjectMetadataInputMatrix =
CustomDeletedCreatedUpdatedMatrix<'objectMetadata', FlatObjectMetadata>;
CustomDeletedCreatedUpdatedMatrix<'objectMetadata', FlatObjectMetadata> & {
inferDeletionFromMissingObjectFieldIndex: boolean;
};
export const buildWorkspaceMigrationV2ObjectActions = ({
createdObjectMetadata,
deletedObjectMetadata,
updatedObjectMetadata,
inferDeletionFromMissingObjectFieldIndex,
}: CreatedDeletedUpdatedObjectMetadataInputMatrix): WorkspaceMigrationObjectActionV2[] => {
const createdObjectActions = createdObjectMetadata.map(
(flatObjectMetadata) => {
@@ -36,9 +39,9 @@ export const buildWorkspaceMigrationV2ObjectActions = ({
},
);
const deletedObjectActions = deletedObjectMetadata.map(
getWorkspaceMigrationV2ObjectDeleteAction,
);
const deletedObjectActions = inferDeletionFromMissingObjectFieldIndex
? deletedObjectMetadata.map(getWorkspaceMigrationV2ObjectDeleteAction)
: [];
const updatedObjectActions =
updatedObjectMetadata.flatMap<UpdateObjectAction>(({ from, to }) => {
@@ -0,0 +1,5 @@
import { FieldMetadataType } from './FieldMetadataType';
export type RelationAndMorphRelationFieldMetadataType =
| FieldMetadataType.RELATION
| FieldMetadataType.MORPH_RELATION;
@@ -17,6 +17,7 @@ export type { NonNullableRequired } from './NonNullableRequired';
export type { NullablePartial } from './NullablePartial';
export type { ObjectRecordsPermissions } from './ObjectRecordsPermissions';
export type { ObjectRecordsPermissionsByRoleId } from './ObjectRecordsPermissionsByRoleId';
export type { RelationAndMorphRelationFieldMetadataType } from './RelationAndMorphRelationFieldMetadataType';
export type { RestrictedField } from './RestrictedField';
export type { RestrictedFields } from './RestrictedFields';
export type { StepFilterGroup, StepFilter } from './StepFilters';