Refactor FieldMetadataService and validation to consume FlatObjectMetadataMaps (#13719)

# Introduction
Following recent `FlatObjectMetadataMaps` manipulation, transpilers
utils introduction in https://github.com/twentyhq/twenty/pull/13620
Refactored the field metadata service and validator to iterate over
`FlatObjectMetadataMaps` instead of `FlatObjectMetadata[]`.

The object metadata service v2 is shadow coding
This commit is contained in:
Paul Rastoin
2025-08-07 17:19:07 +02:00
committed by GitHub
parent 05c6805f0a
commit 83a560c936
20 changed files with 557 additions and 165 deletions
@@ -15,9 +15,10 @@ export const expectFlatFieldMetadataToBeInFlatObjectMetadataMaps = ({
flatObjectMetadataMaps,
}: ExpectFlatFieldMetadataToBeInFlatObjectMetadataMapsArgs) => {
const { objectMetadataId, id: flatFieldMetadataId } = flatFieldMetadata;
const petObject = flatObjectMetadataMaps.byId[objectMetadataId];
const matchingFlatObjectMetadata =
flatObjectMetadataMaps.byId[objectMetadataId];
jestExpectToBeDefined(petObject);
jestExpectToBeDefined(matchingFlatObjectMetadata);
if (
isFlatFieldMetadataEntityOfType(
@@ -27,15 +28,22 @@ export const expectFlatFieldMetadataToBeInFlatObjectMetadataMaps = ({
isDefined(flatFieldMetadata.settings.joinColumnName)
) {
expect(
petObject.fieldIdByJoinColumnName[
matchingFlatObjectMetadata.fieldIdByJoinColumnName[
flatFieldMetadata.settings.joinColumnName
],
).toEqual(flatFieldMetadataId);
}
expect(petObject.fieldsById[flatFieldMetadataId]).toBeDefined();
expect(petObject.fieldsById[flatFieldMetadataId]).toEqual(flatFieldMetadata);
expect(petObject.fieldIdByName[flatFieldMetadata.name]).toEqual(
flatFieldMetadataId,
expect(
matchingFlatObjectMetadata.fieldsById[flatFieldMetadataId],
).toBeDefined();
expect(matchingFlatObjectMetadata.fieldsById[flatFieldMetadataId]).toEqual(
flatFieldMetadata,
);
expect(matchingFlatObjectMetadata.flatFieldMetadatas).toContain(
flatFieldMetadata,
);
expect(
matchingFlatObjectMetadata.fieldIdByName[flatFieldMetadata.name],
).toEqual(flatFieldMetadataId);
};
@@ -0,0 +1,5 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`extractFlatObjectMetadataMapsOutOfFlatObjectMetadataMapsOrThrow should throw when extracting twice the same object 1`] = `"addFlatObjectMetadataWithFlatFieldMapsToFlatObjectMetadataMapsOrThrow: flat object metadata with fields maps to add already exists"`;
exports[`extractFlatObjectMetadataMapsOutOfFlatObjectMetadataMapsOrThrow should throw when object metadata id is not found 1`] = `"extractFlatObjectMetadataMapsOutOfFlatObjectMetadataMapsOrThrow object metadata not found"`;
@@ -0,0 +1,65 @@
import {
EachTestingContext,
eachTestingContextFilter,
} from 'twenty-shared/testing';
import { FieldMetadataType } from 'twenty-shared/types';
import { getFlatFieldMetadataMock } from 'src/engine/metadata-modules/flat-field-metadata/__mocks__/get-flat-field-metadata.mock';
import { PET_FLAT_FIELDS_MOCK } from 'src/engine/metadata-modules/flat-field-metadata/__mocks__/pet-flat-fields.mock';
import { FLAT_OBJECT_METADATA_MAPS_MOCKS } from 'src/engine/metadata-modules/flat-object-metadata-maps/mocks/flat-object-metadata-maps.mock';
import { AddFlatFieldMetadataInFlatObjectMetadataMapsOrThrowArgs } from 'src/engine/metadata-modules/flat-object-metadata-maps/utils/add-flat-field-metadata-in-flat-object-metadata-maps-or-throw.util';
import { addFlatFieldMetadataInFlatObjectMetadataMaps } from 'src/engine/metadata-modules/flat-object-metadata-maps/utils/add-flat-field-metadata-in-flat-object-metadata-maps.util';
type AddFlatFieldMetadataInFlatObjectMetadataMapsArgsTestCase = {
input: AddFlatFieldMetadataInFlatObjectMetadataMapsOrThrowArgs;
};
describe('addFlatFieldMetadataInFlatObjectMetadataMaps', () => {
const testCases: EachTestingContext<AddFlatFieldMetadataInFlatObjectMetadataMapsArgsTestCase>[] =
[
{
title:
'should return undefined when field metadata parent object metadata does not exist',
context: {
input: {
flatObjectMetadataMaps: FLAT_OBJECT_METADATA_MAPS_MOCKS,
flatFieldMetadata: getFlatFieldMetadataMock({
objectMetadataId: 'non-existent',
type: FieldMetadataType.TEXT,
uniqueIdentifier: 'unique-id-1',
id: 'unique-id-1',
}),
},
},
},
{
title:
'should return undefined when field metadata to add already exist in object',
context: {
input: {
flatObjectMetadataMaps: FLAT_OBJECT_METADATA_MAPS_MOCKS,
flatFieldMetadata: PET_FLAT_FIELDS_MOCK.species,
},
},
},
];
const filteredTestCases = eachTestingContextFilter(testCases);
it.each(filteredTestCases)(
'$title',
({
context: {
input: { flatFieldMetadata, flatObjectMetadataMaps },
},
}) => {
const updatedFlatObjectMetadataMaps =
addFlatFieldMetadataInFlatObjectMetadataMaps({
flatFieldMetadata,
flatObjectMetadataMaps,
});
expect(updatedFlatObjectMetadataMaps).toBeUndefined();
},
);
});
@@ -78,7 +78,7 @@ describe('addFlatObjectMetadataToFlatObjectMetadataMapsOrThrow', () => {
flatObjectMetadataMaps,
});
expect(updatedFlatObjectMetadataMaps).toEqual(expected);
expect(updatedFlatObjectMetadataMaps).toStrictEqual(expected);
}
},
);
@@ -0,0 +1,109 @@
import { jestExpectToBeDefined } from 'test/utils/expect-to-be-defined.util.test';
import {
EachTestingContext,
eachTestingContextFilter,
} from 'twenty-shared/testing';
import { FLAT_OBJECT_METADATA_MAPS_MOCKS } from 'src/engine/metadata-modules/flat-object-metadata-maps/mocks/flat-object-metadata-maps.mock';
import { FlatObjectMetadataMaps } from 'src/engine/metadata-modules/flat-object-metadata-maps/types/flat-object-metadata-maps.type';
import {
ExtractFlatObjectMetadataMapsOutOfFlatObjectMetadataMapsOrThrowArgs,
extractFlatObjectMetadataMapsOutOfFlatObjectMetadataMapsOrThrow,
} from 'src/engine/metadata-modules/flat-object-metadata-maps/utils/extract-flat-object-metadata-maps-out-of-flat-object-metadata-maps-or-throw.util';
import { PET_FLAT_OBJECT_MOCK } from 'src/engine/metadata-modules/flat-object-metadata/__mocks__/pet-flat-object.mock';
import { ROCKET_FLAT_OBJECT_MOCK } from 'src/engine/metadata-modules/flat-object-metadata/__mocks__/rocket-flat-object.mock';
import { fromFlatObjectMetadatasToFlatObjectMetadataMaps } from 'src/engine/metadata-modules/flat-object-metadata/utils/from-flat-object-metadatas-to-flat-object-metadata-maps.util';
type ExtractFlatObjectMetadataMapsTestCase = {
input: ExtractFlatObjectMetadataMapsOutOfFlatObjectMetadataMapsOrThrowArgs;
shouldThrow?: true;
expected?: FlatObjectMetadataMaps;
};
describe('extractFlatObjectMetadataMapsOutOfFlatObjectMetadataMapsOrThrow', () => {
const testCases: EachTestingContext<ExtractFlatObjectMetadataMapsTestCase>[] =
[
{
title: 'should throw when object metadata id is not found',
context: {
input: {
objectMetadataIds: ['non-existent-id'],
flatObjectMetadataMaps: FLAT_OBJECT_METADATA_MAPS_MOCKS,
},
shouldThrow: true,
},
},
{
title: 'should throw when extracting twice the same object',
context: {
input: {
objectMetadataIds: [
PET_FLAT_OBJECT_MOCK.id,
PET_FLAT_OBJECT_MOCK.id,
],
flatObjectMetadataMaps: FLAT_OBJECT_METADATA_MAPS_MOCKS,
},
shouldThrow: true,
},
},
{
title: 'should extract single object metadata from maps',
context: {
input: {
objectMetadataIds: [PET_FLAT_OBJECT_MOCK.id],
flatObjectMetadataMaps: FLAT_OBJECT_METADATA_MAPS_MOCKS,
},
expected: fromFlatObjectMetadatasToFlatObjectMetadataMaps([
PET_FLAT_OBJECT_MOCK,
]),
},
},
{
title: 'should extract multiple object metadata from maps',
context: {
input: {
objectMetadataIds: [
PET_FLAT_OBJECT_MOCK.id,
ROCKET_FLAT_OBJECT_MOCK.id,
],
flatObjectMetadataMaps: FLAT_OBJECT_METADATA_MAPS_MOCKS,
},
expected: fromFlatObjectMetadatasToFlatObjectMetadataMaps([
PET_FLAT_OBJECT_MOCK,
ROCKET_FLAT_OBJECT_MOCK,
]),
},
},
];
const filteredTestCases = eachTestingContextFilter(testCases);
it.each(filteredTestCases)(
'$title',
({
context: {
input: { objectMetadataIds, flatObjectMetadataMaps },
shouldThrow = false,
expected,
},
}) => {
if (shouldThrow) {
expect(() =>
extractFlatObjectMetadataMapsOutOfFlatObjectMetadataMapsOrThrow({
objectMetadataIds,
flatObjectMetadataMaps,
}),
).toThrowErrorMatchingSnapshot();
} else {
jestExpectToBeDefined(expected);
const result =
extractFlatObjectMetadataMapsOutOfFlatObjectMetadataMapsOrThrow({
objectMetadataIds,
flatObjectMetadataMaps,
});
expect(result).toEqual(expected);
}
},
);
});
@@ -0,0 +1,58 @@
import {
EachTestingContext,
eachTestingContextFilter,
} from 'twenty-shared/testing';
import { FLAT_OBJECT_METADATA_MAPS_MOCKS } from 'src/engine/metadata-modules/flat-object-metadata-maps/mocks/flat-object-metadata-maps.mock';
import { ExtractFlatObjectMetadataMapsOutOfFlatObjectMetadataMapsOrThrowArgs } from 'src/engine/metadata-modules/flat-object-metadata-maps/utils/extract-flat-object-metadata-maps-out-of-flat-object-metadata-maps-or-throw.util';
import { extractFlatObjectMetadataMapsOutOfFlatObjectMetadataMaps } from 'src/engine/metadata-modules/flat-object-metadata-maps/utils/extract-flat-object-metadata-maps-out-of-flat-object-metadata-maps.util';
import { PET_FLAT_OBJECT_MOCK } from 'src/engine/metadata-modules/flat-object-metadata/__mocks__/pet-flat-object.mock';
type ExtractFlatObjectMetadataMapsTestCase = {
input: ExtractFlatObjectMetadataMapsOutOfFlatObjectMetadataMapsOrThrowArgs;
};
describe('extractFlatObjectMetadataMapsOutOfFlatObjectMetadataMaps', () => {
const testCases: EachTestingContext<ExtractFlatObjectMetadataMapsTestCase>[] =
[
{
title: 'should throw when object metadata id is not found',
context: {
input: {
objectMetadataIds: ['non-existent-id'],
flatObjectMetadataMaps: FLAT_OBJECT_METADATA_MAPS_MOCKS,
},
},
},
{
title: 'should throw when extracting twice the same object',
context: {
input: {
objectMetadataIds: [
PET_FLAT_OBJECT_MOCK.id,
PET_FLAT_OBJECT_MOCK.id,
],
flatObjectMetadataMaps: FLAT_OBJECT_METADATA_MAPS_MOCKS,
},
},
},
];
const filteredTestCases = eachTestingContextFilter(testCases);
it.each(filteredTestCases)(
'$title',
({
context: {
input: { objectMetadataIds, flatObjectMetadataMaps },
},
}) => {
const result = extractFlatObjectMetadataMapsOutOfFlatObjectMetadataMaps({
objectMetadataIds,
flatObjectMetadataMaps,
});
expect(result).toBeUndefined();
},
);
});
@@ -55,6 +55,10 @@ describe('findFlatObjectMetadataInFlatObjectMetadataMaps', () => {
flatObjectMetadataMaps,
});
expect(result?.flatFieldMetadatas.length).toEqual(
expected?.flatFieldMetadatas.length,
);
expect(result).toEqual(expected);
},
);
@@ -35,6 +35,6 @@ export const addFlatFieldMetadataInFlatObjectMetadataMapsOrThrow = ({
flatObjectMetadataWithFlatFieldMaps,
}),
},
idByNameSingular: flatObjectMetadataMaps.idByNameSingular,
idByNameSingular: { ...flatObjectMetadataMaps.idByNameSingular },
};
};
@@ -0,0 +1,15 @@
import { FlatObjectMetadataMaps } from 'src/engine/metadata-modules/flat-object-metadata-maps/types/flat-object-metadata-maps.type';
import {
AddFlatFieldMetadataInFlatObjectMetadataMapsOrThrowArgs,
addFlatFieldMetadataInFlatObjectMetadataMapsOrThrow,
} from 'src/engine/metadata-modules/flat-object-metadata-maps/utils/add-flat-field-metadata-in-flat-object-metadata-maps-or-throw.util';
export const addFlatFieldMetadataInFlatObjectMetadataMaps = (
arg: AddFlatFieldMetadataInFlatObjectMetadataMapsOrThrowArgs,
): FlatObjectMetadataMaps | undefined => {
try {
return addFlatFieldMetadataInFlatObjectMetadataMapsOrThrow(arg);
} catch (e) {
return undefined;
}
};
@@ -67,5 +67,9 @@ export const addFlatFieldMetadataToFlatObjectMetadataWithFlatFieldMapsOrThrow =
...flatObjectMetadataWithFlatFieldMaps.fieldsById,
[flatFieldMetadata.id]: flatFieldMetadata,
},
flatFieldMetadatas: [
...flatObjectMetadataWithFlatFieldMaps.flatFieldMetadatas,
flatFieldMetadata,
],
};
};
@@ -0,0 +1,43 @@
import { isDefined } from 'twenty-shared/utils';
import {
FlatObjectMetadataMapsException,
FlatObjectMetadataMapsExceptionCode,
} from 'src/engine/metadata-modules/flat-object-metadata-maps/flat-object-metadata-maps.exception';
import { FlatObjectMetadataMaps } from 'src/engine/metadata-modules/flat-object-metadata-maps/types/flat-object-metadata-maps.type';
import { FlatObjectMetadataWithFlatFieldMaps } from 'src/engine/metadata-modules/flat-object-metadata-maps/types/flat-object-metadata-with-flat-field-metadata-maps.type';
type AddFlatObjectMetadataWithFlatFieldMapsToFlatObjectMetadataMapsOrThrowArgs =
{
flatObjectMetadataWithFlatFieldMaps: FlatObjectMetadataWithFlatFieldMaps;
flatObjectMetadataMaps: FlatObjectMetadataMaps;
};
export const addFlatObjectMetadataWithFlatFieldMapsToFlatObjectMetadataMapsOrThrow =
({
flatObjectMetadataWithFlatFieldMaps,
flatObjectMetadataMaps,
}: AddFlatObjectMetadataWithFlatFieldMapsToFlatObjectMetadataMapsOrThrowArgs): FlatObjectMetadataMaps => {
if (
isDefined(
flatObjectMetadataMaps.byId[flatObjectMetadataWithFlatFieldMaps.id],
)
) {
throw new FlatObjectMetadataMapsException(
'addFlatObjectMetadataWithFlatFieldMapsToFlatObjectMetadataMapsOrThrow: flat object metadata with fields maps to add already exists',
FlatObjectMetadataMapsExceptionCode.OBJECT_METADATA_ALREADY_EXISTS,
);
}
return {
byId: {
...flatObjectMetadataMaps.byId,
[flatObjectMetadataWithFlatFieldMaps.id]:
flatObjectMetadataWithFlatFieldMaps,
},
idByNameSingular: {
...flatObjectMetadataMaps.idByNameSingular,
[flatObjectMetadataWithFlatFieldMaps.nameSingular]:
flatObjectMetadataWithFlatFieldMaps.id,
},
};
};
@@ -0,0 +1,46 @@
import { isDefined } from 'twenty-shared/utils';
import {
FlatObjectMetadataMapsException,
FlatObjectMetadataMapsExceptionCode,
} from 'src/engine/metadata-modules/flat-object-metadata-maps/flat-object-metadata-maps.exception';
import { FlatObjectMetadataMaps } from 'src/engine/metadata-modules/flat-object-metadata-maps/types/flat-object-metadata-maps.type';
import { addFlatObjectMetadataWithFlatFieldMapsToFlatObjectMetadataMapsOrThrow } from 'src/engine/metadata-modules/flat-object-metadata-maps/utils/add-flat-object-metadata-with-field-maps-to-flat-object-metadata-maps-or-throw.util';
export type ExtractFlatObjectMetadataMapsOutOfFlatObjectMetadataMapsOrThrowArgs =
{
objectMetadataIds: string[];
flatObjectMetadataMaps: FlatObjectMetadataMaps;
};
export const extractFlatObjectMetadataMapsOutOfFlatObjectMetadataMapsOrThrow =
({
flatObjectMetadataMaps: sourceFlatObjectMetadataMaps,
objectMetadataIds,
}: ExtractFlatObjectMetadataMapsOutOfFlatObjectMetadataMapsOrThrowArgs): FlatObjectMetadataMaps => {
const emptyFlatObjectMetadataMaps: FlatObjectMetadataMaps = {
byId: {},
idByNameSingular: {},
};
return objectMetadataIds.reduce(
(flatObjectMetadataMaps, objectMetadataId) => {
const flatObjectMetadataWithFlatFieldMaps =
sourceFlatObjectMetadataMaps.byId[objectMetadataId];
if (!isDefined(flatObjectMetadataWithFlatFieldMaps)) {
throw new FlatObjectMetadataMapsException(
'extractFlatObjectMetadataMapsOutOfFlatObjectMetadataMapsOrThrow object metadata not found',
FlatObjectMetadataMapsExceptionCode.OBJECT_METADATA_NOT_FOUND,
);
}
return addFlatObjectMetadataWithFlatFieldMapsToFlatObjectMetadataMapsOrThrow(
{
flatObjectMetadataMaps,
flatObjectMetadataWithFlatFieldMaps,
},
);
},
emptyFlatObjectMetadataMaps,
);
};
@@ -0,0 +1,17 @@
import { FlatObjectMetadataMaps } from 'src/engine/metadata-modules/flat-object-metadata-maps/types/flat-object-metadata-maps.type';
import {
ExtractFlatObjectMetadataMapsOutOfFlatObjectMetadataMapsOrThrowArgs,
extractFlatObjectMetadataMapsOutOfFlatObjectMetadataMapsOrThrow,
} from 'src/engine/metadata-modules/flat-object-metadata-maps/utils/extract-flat-object-metadata-maps-out-of-flat-object-metadata-maps-or-throw.util';
export const extractFlatObjectMetadataMapsOutOfFlatObjectMetadataMaps = (
args: ExtractFlatObjectMetadataMapsOutOfFlatObjectMetadataMapsOrThrowArgs,
): FlatObjectMetadataMaps | undefined => {
try {
return extractFlatObjectMetadataMapsOutOfFlatObjectMetadataMapsOrThrow(
args,
);
} catch {
return undefined;
}
};