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
@@ -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,
});
});
};