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:
Weiko
2025-11-27 13:43:34 +01:00
committed by GitHub
parent 978c0acb90
commit 1607aebcc6
281 changed files with 6510 additions and 6627 deletions
@@ -3,115 +3,185 @@ import { FieldMetadataType } from 'twenty-shared/types';
import { RelationType } from 'src/engine/metadata-modules/field-metadata/interfaces/relation-type.interface';
import { buildColumnsToSelect } from 'src/engine/api/graphql/graphql-query-runner/utils/build-columns-to-select';
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';
describe('buildColumnsToSelect', () => {
const mockObjectMetadataItemWithFieldMaps: any = {
id: 'a55d8cad-4c3d-4c8a-82b5-539a36de5605',
standardId: '20202020-e674-48e5-a542-72570eee7213',
dataSourceId: 'd161a12f-1daa-4a0b-887c-96a48a3d9ecf',
nameSingular: 'person',
namePlural: 'people',
labelSingular: 'Person',
labelPlural: 'People',
description: 'A person',
icon: 'IconUser',
standardOverrides: null,
targetTableName: 'DEPRECATED',
isCustom: false,
isRemote: false,
isActive: true,
isSystem: false,
isAuditLogged: true,
isSearchable: true,
duplicateCriteria: [
['nameFirstName', 'nameLastName'],
['linkedinLinkPrimaryLinkUrl'],
['emailsPrimaryEmail'],
],
shortcut: 'P',
labelIdentifierFieldMetadataId: '92414583-c6a6-4c98-bae7-6ce318bd3423',
imageIdentifierFieldMetadataId: '6bfcecc4-1866-4254-ba6b-6f22246819bb',
isLabelSyncedWithName: false,
workspaceId: '20202020-1c25-4d02-bf25-6aeccf7ea419',
createdAt: new Date('2025-07-10T10:58:54.536Z'),
updatedAt: new Date('2025-07-10T10:58:54.536Z'),
indexMetadatas: [],
fieldsById: {
'92414583-c6a6-4c98-bae7-6ce318bd3423': {
id: '92414583-c6a6-4c98-bae7-6ce318bd3423',
type: FieldMetadataType.FULL_NAME,
name: 'name',
label: 'Name',
defaultValue: {
lastName: "''",
firstName: "''",
},
description: "Contact's name",
icon: 'IconUser',
standardOverrides: null,
options: undefined,
settings: undefined,
isCustom: false,
isActive: true,
isSystem: false,
isNullable: true,
isUnique: false,
workspaceId: '20202020-1c25-4d02-bf25-6aeccf7ea419',
isLabelSyncedWithName: true,
relationTargetFieldMetadataId: undefined,
relationTargetObjectMetadataId: undefined,
objectMetadataId: 'a55d8cad-4c3d-4c8a-82b5-539a36de5605',
createdAt: new Date('2025-07-10T10:58:54.536Z'),
updatedAt: new Date('2025-07-10T10:58:54.536Z'),
},
'30dc1370-bb1a-42e3-abbc-a40edf1c6796': {
id: '30dc1370-bb1a-42e3-abbc-a40edf1c6796',
type: FieldMetadataType.RELATION,
name: 'company',
label: 'Company',
defaultValue: null,
description: "Contact's company",
icon: 'IconBuildingSkyscraper',
standardOverrides: null,
options: undefined,
settings: {
relationType: RelationType.MANY_TO_ONE,
joinColumnName: 'companyId',
},
isCustom: false,
isActive: true,
isSystem: false,
isNullable: true,
isUnique: false,
workspaceId: '20202020-1c25-4d02-bf25-6aeccf7ea419',
isLabelSyncedWithName: true,
relationTargetFieldMetadataId: '83cd9e7f-dfbc-4b93-a0a7-4c04b76d2009',
relationTargetObjectMetadataId: '9af20778-2f2c-4e22-ae83-2e77e479b57c',
objectMetadataId: 'a55d8cad-4c3d-4c8a-82b5-539a36de5605',
createdAt: new Date('2025-07-10T10:58:54.536Z'),
updatedAt: new Date('2025-07-10T10:58:54.536Z'),
},
},
fieldIdByName: {
name: '92414583-c6a6-4c98-bae7-6ce318bd3423',
company: '30dc1370-bb1a-42e3-abbc-a40edf1c6796',
},
fieldIdByJoinColumnName: {
companyId: '30dc1370-bb1a-42e3-abbc-a40edf1c6796',
},
};
const workspaceId = '20202020-1c25-4d02-bf25-6aeccf7ea419';
const personObjectId = 'a55d8cad-4c3d-4c8a-82b5-539a36de5605';
const companyObjectId = '9af20778-2f2c-4e22-ae83-2e77e479b57c';
const mockObjectMetadataMaps = {
byId: {
'9af20778-2f2c-4e22-ae83-2e77e479b57c': {
id: '9af20778-2f2c-4e22-ae83-2e77e479b57c',
nameSingular: 'company',
namePlural: 'companies',
},
const nameFieldId = '92414583-c6a6-4c98-bae7-6ce318bd3423';
const companyFieldId = '30dc1370-bb1a-42e3-abbc-a40edf1c6796';
const createMockField = (
overrides: Partial<FlatFieldMetadata> & {
id: string;
name: string;
type: FieldMetadataType;
objectMetadataId: string;
},
};
): FlatFieldMetadata =>
({
workspaceId,
isNullable: true,
isLabelSyncedWithName: true,
createdAt: new Date('2025-07-10T10:58:54.536Z'),
updatedAt: new Date('2025-07-10T10:58:54.536Z'),
universalIdentifier: overrides.id,
viewFieldIds: [],
viewFilterIds: [],
viewGroupIds: [],
kanbanAggregateOperationViewIds: [],
calendarViewIds: [],
applicationId: null,
label: overrides.name,
isCustom: false,
isActive: true,
isSystem: false,
isUnique: false,
...overrides,
}) as FlatFieldMetadata;
const nameField = createMockField({
id: nameFieldId,
type: FieldMetadataType.FULL_NAME,
name: 'name',
label: 'Name',
objectMetadataId: personObjectId,
defaultValue: {
lastName: "''",
firstName: "''",
},
description: "Contact's name",
icon: 'IconUser',
});
const companyRelationField = createMockField({
id: companyFieldId,
type: FieldMetadataType.RELATION,
name: 'company',
label: 'Company',
objectMetadataId: personObjectId,
defaultValue: null,
description: "Contact's company",
icon: 'IconBuildingSkyscraper',
settings: {
relationType: RelationType.MANY_TO_ONE,
joinColumnName: 'companyId',
},
relationTargetFieldMetadataId: '83cd9e7f-dfbc-4b93-a0a7-4c04b76d2009',
relationTargetObjectMetadataId: companyObjectId,
});
const buildMockFlatObjectMetadata = (
fieldIds: string[],
): FlatObjectMetadata =>
({
id: personObjectId,
standardId: '20202020-e674-48e5-a542-72570eee7213',
nameSingular: 'person',
namePlural: 'people',
labelSingular: 'Person',
labelPlural: 'People',
description: 'A person',
icon: 'IconUser',
targetTableName: 'DEPRECATED',
isCustom: false,
isRemote: false,
isActive: true,
isSystem: false,
isAuditLogged: true,
isSearchable: true,
duplicateCriteria: [
['nameFirstName', 'nameLastName'],
['linkedinLinkPrimaryLinkUrl'],
['emailsPrimaryEmail'],
],
shortcut: 'P',
labelIdentifierFieldMetadataId: nameFieldId,
imageIdentifierFieldMetadataId: '6bfcecc4-1866-4254-ba6b-6f22246819bb',
isLabelSyncedWithName: false,
workspaceId,
createdAt: new Date('2025-07-10T10:58:54.536Z'),
updatedAt: new Date('2025-07-10T10:58:54.536Z'),
universalIdentifier: personObjectId,
fieldMetadataIds: fieldIds,
indexMetadataIds: [],
viewIds: [],
applicationId: null,
}) as unknown as FlatObjectMetadata;
const buildFlatFieldMetadataMaps = (
fields: FlatFieldMetadata[],
): FlatEntityMaps<FlatFieldMetadata> => ({
byId: fields.reduce(
(acc, field) => {
acc[field.id] = field;
return acc;
},
{} as Record<string, FlatFieldMetadata>,
),
idByUniversalIdentifier: fields.reduce(
(acc, field) => {
acc[field.universalIdentifier] = field.id;
return acc;
},
{} as Record<string, string>,
),
universalIdentifiersByApplicationId: {},
});
const buildFlatObjectMetadataMaps = (
objects: FlatObjectMetadata[],
): FlatEntityMaps<FlatObjectMetadata> => ({
byId: objects.reduce(
(acc, obj) => {
acc[obj.id] = obj;
return acc;
},
{} as Record<string, FlatObjectMetadata>,
),
idByUniversalIdentifier: objects.reduce(
(acc, obj) => {
if (obj.universalIdentifier) {
acc[obj.universalIdentifier] = obj.id;
}
return acc;
},
{} as Record<string, string>,
),
universalIdentifiersByApplicationId: {},
});
const companyObjectMetadata: FlatObjectMetadata = {
id: companyObjectId,
nameSingular: 'company',
namePlural: 'companies',
fieldMetadataIds: [],
indexMetadataIds: [],
viewIds: [],
} as unknown as FlatObjectMetadata;
it('should build columns to select with relation fields', () => {
const flatObjectMetadata = buildMockFlatObjectMetadata([
nameFieldId,
companyFieldId,
]);
const flatFieldMetadataMaps = buildFlatFieldMetadataMaps([
nameField,
companyRelationField,
]);
const flatObjectMetadataMaps = buildFlatObjectMetadataMaps([
flatObjectMetadata,
companyObjectMetadata,
]);
const select = {
nameFirstName: true,
company: {
@@ -127,8 +197,9 @@ describe('buildColumnsToSelect', () => {
const result = buildColumnsToSelect({
select,
relations,
objectMetadataItemWithFieldMaps: mockObjectMetadataItemWithFieldMaps,
objectMetadataMaps: mockObjectMetadataMaps,
flatObjectMetadata,
flatObjectMetadataMaps,
flatFieldMetadataMaps,
});
expect(result).toEqual({
@@ -139,6 +210,19 @@ describe('buildColumnsToSelect', () => {
});
it('should build columns to select without relation fields', () => {
const flatObjectMetadata = buildMockFlatObjectMetadata([
nameFieldId,
companyFieldId,
]);
const flatFieldMetadataMaps = buildFlatFieldMetadataMaps([
nameField,
companyRelationField,
]);
const flatObjectMetadataMaps = buildFlatObjectMetadataMaps([
flatObjectMetadata,
companyObjectMetadata,
]);
const select = {
nameFirstName: true,
nameLastName: true,
@@ -149,8 +233,9 @@ describe('buildColumnsToSelect', () => {
const result = buildColumnsToSelect({
select,
relations,
objectMetadataItemWithFieldMaps: mockObjectMetadataItemWithFieldMaps,
objectMetadataMaps: mockObjectMetadataMaps,
flatObjectMetadata,
flatObjectMetadataMaps,
flatFieldMetadataMaps,
});
expect(result).toEqual({
@@ -161,6 +246,19 @@ describe('buildColumnsToSelect', () => {
});
it('should filter out non-boolean values from select', () => {
const flatObjectMetadata = buildMockFlatObjectMetadata([
nameFieldId,
companyFieldId,
]);
const flatFieldMetadataMaps = buildFlatFieldMetadataMaps([
nameField,
companyRelationField,
]);
const flatObjectMetadataMaps = buildFlatObjectMetadataMaps([
flatObjectMetadata,
companyObjectMetadata,
]);
const select = {
nameFirstName: true,
nameLastName: false,
@@ -172,8 +270,9 @@ describe('buildColumnsToSelect', () => {
const result = buildColumnsToSelect({
select,
relations,
objectMetadataItemWithFieldMaps: mockObjectMetadataItemWithFieldMaps,
objectMetadataMaps: mockObjectMetadataMaps,
flatObjectMetadata,
flatObjectMetadataMaps,
flatFieldMetadataMaps,
});
expect(result).toEqual({
@@ -183,6 +282,19 @@ describe('buildColumnsToSelect', () => {
});
it('should handle relation field that is not a relation type', () => {
const flatObjectMetadata = buildMockFlatObjectMetadata([
nameFieldId,
companyFieldId,
]);
const flatFieldMetadataMaps = buildFlatFieldMetadataMaps([
nameField,
companyRelationField,
]);
const flatObjectMetadataMaps = buildFlatObjectMetadataMaps([
flatObjectMetadata,
companyObjectMetadata,
]);
const select = {
nameFirstName: true,
};
@@ -194,8 +306,9 @@ describe('buildColumnsToSelect', () => {
const result = buildColumnsToSelect({
select,
relations,
objectMetadataItemWithFieldMaps: mockObjectMetadataItemWithFieldMaps,
objectMetadataMaps: mockObjectMetadataMaps,
flatObjectMetadata,
flatObjectMetadataMaps,
flatFieldMetadataMaps,
});
expect(result).toEqual({
@@ -205,21 +318,32 @@ describe('buildColumnsToSelect', () => {
});
it('should handle relation field that is not MANY_TO_ONE', () => {
const mockObjectMetadataWithOneToMany: any = {
...mockObjectMetadataItemWithFieldMaps,
fieldsById: {
...mockObjectMetadataItemWithFieldMaps.fieldsById,
'30dc1370-bb1a-42e3-abbc-a40edf1c6796': {
...mockObjectMetadataItemWithFieldMaps.fieldsById[
'30dc1370-bb1a-42e3-abbc-a40edf1c6796'
],
settings: {
relationType: RelationType.ONE_TO_MANY,
joinColumnName: 'companyId',
},
},
const oneToManyCompanyField = createMockField({
id: companyFieldId,
type: FieldMetadataType.RELATION,
name: 'company',
label: 'Company',
objectMetadataId: personObjectId,
defaultValue: null,
settings: {
relationType: RelationType.ONE_TO_MANY,
joinColumnName: 'companyId',
},
};
relationTargetObjectMetadataId: companyObjectId,
});
const flatObjectMetadata = buildMockFlatObjectMetadata([
nameFieldId,
companyFieldId,
]);
const flatFieldMetadataMaps = buildFlatFieldMetadataMaps([
nameField,
oneToManyCompanyField,
]);
const flatObjectMetadataMaps = buildFlatObjectMetadataMaps([
flatObjectMetadata,
companyObjectMetadata,
]);
const select = {
nameFirstName: true,
@@ -232,8 +356,9 @@ describe('buildColumnsToSelect', () => {
const result = buildColumnsToSelect({
select,
relations,
objectMetadataItemWithFieldMaps: mockObjectMetadataWithOneToMany,
objectMetadataMaps: mockObjectMetadataMaps,
flatObjectMetadata,
flatObjectMetadataMaps,
flatFieldMetadataMaps,
});
expect(result).toEqual({
@@ -243,21 +368,32 @@ describe('buildColumnsToSelect', () => {
});
it('should handle relation field without joinColumnName', () => {
const mockObjectMetadataWithoutJoinColumn: any = {
...mockObjectMetadataItemWithFieldMaps,
fieldsById: {
...mockObjectMetadataItemWithFieldMaps.fieldsById,
'30dc1370-bb1a-42e3-abbc-a40edf1c6796': {
...mockObjectMetadataItemWithFieldMaps.fieldsById[
'30dc1370-bb1a-42e3-abbc-a40edf1c6796'
],
settings: {
relationType: RelationType.MANY_TO_ONE,
joinColumnName: null,
},
},
const noJoinColumnCompanyField = createMockField({
id: companyFieldId,
type: FieldMetadataType.RELATION,
name: 'company',
label: 'Company',
objectMetadataId: personObjectId,
defaultValue: null,
settings: {
relationType: RelationType.MANY_TO_ONE,
joinColumnName: null,
},
};
relationTargetObjectMetadataId: companyObjectId,
});
const flatObjectMetadata = buildMockFlatObjectMetadata([
nameFieldId,
companyFieldId,
]);
const flatFieldMetadataMaps = buildFlatFieldMetadataMaps([
nameField,
noJoinColumnCompanyField,
]);
const flatObjectMetadataMaps = buildFlatObjectMetadataMaps([
flatObjectMetadata,
companyObjectMetadata,
]);
const select = {
nameFirstName: true,
@@ -270,8 +406,9 @@ describe('buildColumnsToSelect', () => {
const result = buildColumnsToSelect({
select,
relations,
objectMetadataItemWithFieldMaps: mockObjectMetadataWithoutJoinColumn,
objectMetadataMaps: mockObjectMetadataMaps,
flatObjectMetadata,
flatObjectMetadataMaps,
flatFieldMetadataMaps,
});
expect(result).toEqual({
@@ -1,24 +1,28 @@
import { buildColumnsToSelect } from 'src/engine/api/graphql/graphql-query-runner/utils/build-columns-to-select';
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';
export const buildColumnsToReturn = ({
select,
relations,
objectMetadataItemWithFieldMaps,
objectMetadataMaps,
flatObjectMetadata,
flatObjectMetadataMaps,
flatFieldMetadataMaps,
}: {
select: Record<string, unknown>;
relations: Record<string, unknown>;
objectMetadataItemWithFieldMaps: ObjectMetadataItemWithFieldMaps;
objectMetadataMaps: ObjectMetadataMaps;
flatObjectMetadata: FlatObjectMetadata;
flatObjectMetadataMaps: FlatEntityMaps<FlatObjectMetadata>;
flatFieldMetadataMaps: FlatEntityMaps<FlatFieldMetadata>;
}): string[] => {
return Object.entries(
buildColumnsToSelect({
select,
relations,
objectMetadataItemWithFieldMaps,
objectMetadataMaps,
flatObjectMetadata,
flatObjectMetadataMaps,
flatFieldMetadataMaps,
}),
)
.filter(([_columnName, value]) => value === true)
@@ -1,32 +1,32 @@
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 { isFieldMetadataTypeMorphRelation } from 'src/engine/metadata-modules/field-metadata/utils/is-field-metadata-type-morph-relation.util';
import { isFieldMetadataTypeRelation } from 'src/engine/metadata-modules/field-metadata/utils/is-field-metadata-type-relation.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 { findFlatEntityByIdInFlatEntityMapsOrThrow } from 'src/engine/metadata-modules/flat-entity/utils/find-flat-entity-by-id-in-flat-entity-maps-or-throw.util';
import { type FlatFieldMetadata } from 'src/engine/metadata-modules/flat-field-metadata/types/flat-field-metadata.type';
import { isFlatFieldMetadataOfType } from 'src/engine/metadata-modules/flat-field-metadata/utils/is-flat-field-metadata-of-type.util';
import { type FlatObjectMetadata } from 'src/engine/metadata-modules/flat-object-metadata/types/flat-object-metadata.type';
export const buildColumnsToSelect = ({
select,
relations,
objectMetadataItemWithFieldMaps,
objectMetadataMaps,
flatObjectMetadata,
flatObjectMetadataMaps,
flatFieldMetadataMaps,
}: {
select: Record<string, unknown>;
relations: Record<string, unknown>;
objectMetadataItemWithFieldMaps: ObjectMetadataItemWithFieldMaps;
objectMetadataMaps: {
byId: Partial<
Record<
string,
Pick<ObjectMetadataItemWithFieldMaps, 'nameSingular' | 'namePlural'>
>
>;
};
flatObjectMetadata: FlatObjectMetadata;
flatObjectMetadataMaps: FlatEntityMaps<FlatObjectMetadata>;
flatFieldMetadataMaps: FlatEntityMaps<FlatFieldMetadata>;
}) => {
const requiredRelationColumns = getRequiredRelationColumns(
relations,
objectMetadataItemWithFieldMaps,
objectMetadataMaps,
flatObjectMetadata,
flatObjectMetadataMaps,
flatFieldMetadataMaps,
);
const fieldsToSelect: Record<string, boolean> = Object.entries(select)
@@ -44,20 +44,19 @@ export const buildColumnsToSelect = ({
const getRequiredRelationColumns = (
relations: Record<string, unknown>,
objectMetadataItem: Pick<ObjectMetadataItemWithFieldMaps, 'fieldsById'>,
objectMetadataMaps: {
byId: Partial<
Record<
string,
Pick<ObjectMetadataItemWithFieldMaps, 'nameSingular' | 'namePlural'>
>
>;
},
flatObjectMetadata: FlatObjectMetadata,
flatObjectMetadataMaps: FlatEntityMaps<FlatObjectMetadata>,
flatFieldMetadataMaps: FlatEntityMaps<FlatFieldMetadata>,
): string[] => {
const requiredColumns: string[] = [];
for (const fieldMetadata of Object.values(objectMetadataItem.fieldsById)) {
if (isFieldMetadataTypeRelation(fieldMetadata)) {
for (const fieldId of flatObjectMetadata.fieldMetadataIds) {
const fieldMetadata = findFlatEntityByIdInFlatEntityMapsOrThrow({
flatEntityId: fieldId,
flatEntityMaps: flatFieldMetadataMaps,
});
if (isFlatFieldMetadataOfType(fieldMetadata, FieldMetadataType.RELATION)) {
const relationValue = relations[fieldMetadata.name];
if (
@@ -71,9 +70,14 @@ const getRequiredRelationColumns = (
requiredColumns.push(fieldMetadata.settings.joinColumnName);
}
if (isFieldMetadataTypeMorphRelation(fieldMetadata)) {
const targetObjectMetadata =
objectMetadataMaps.byId[fieldMetadata.relationTargetObjectMetadataId];
if (
isFlatFieldMetadataOfType(fieldMetadata, FieldMetadataType.MORPH_RELATION)
) {
const targetObjectMetadata = fieldMetadata.relationTargetObjectMetadataId
? flatObjectMetadataMaps.byId[
fieldMetadata.relationTargetObjectMetadataId
]
: undefined;
if (
!fieldMetadata.settings?.relationType ||
@@ -2,56 +2,71 @@ import { FieldMetadataType } from 'twenty-shared/types';
import { isDefined } from 'twenty-shared/utils';
import { getTargetObjectMetadataOrThrow } from 'src/engine/api/graphql/graphql-query-runner/utils/get-target-object-metadata.util';
import { type FieldMetadataEntity } from 'src/engine/metadata-modules/field-metadata/field-metadata.entity';
import { isFieldMetadataTypeMorphRelation } from 'src/engine/metadata-modules/field-metadata/utils/is-field-metadata-type-morph-relation.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 { 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 { buildFieldMapsFromFlatObjectMetadata } from 'src/engine/metadata-modules/flat-field-metadata/utils/build-field-maps-from-flat-object-metadata.util';
import { isFlatFieldMetadataOfType } from 'src/engine/metadata-modules/flat-field-metadata/utils/is-flat-field-metadata-of-type.util';
import { type FlatObjectMetadata } from 'src/engine/metadata-modules/flat-object-metadata/types/flat-object-metadata.type';
type GetFieldMetadataFromGraphQLFieldArgs = {
objectMetadataItem: ObjectMetadataItemWithFieldMaps;
flatObjectMetadata: FlatObjectMetadata;
graphQLField: string;
objectMetadataMaps: ObjectMetadataMaps;
flatObjectMetadataMaps: FlatEntityMaps<FlatObjectMetadata>;
flatFieldMetadataMaps: FlatEntityMaps<FlatFieldMetadata>;
};
export function getFieldMetadataFromGraphQLField({
objectMetadataItem,
flatObjectMetadata,
graphQLField,
objectMetadataMaps,
}: GetFieldMetadataFromGraphQLFieldArgs) {
const sourceFieldMetadataId = objectMetadataItem.fieldIdByName[graphQLField];
let sourceFieldMetadata =
objectMetadataItem.fieldsById[sourceFieldMetadataId];
flatObjectMetadataMaps,
flatFieldMetadataMaps,
}: GetFieldMetadataFromGraphQLFieldArgs): FlatFieldMetadata | undefined {
const { fieldIdByName } = buildFieldMapsFromFlatObjectMetadata(
flatFieldMetadataMaps,
flatObjectMetadata,
);
const sourceFieldMetadataId = fieldIdByName[graphQLField];
let sourceFieldMetadata = sourceFieldMetadataId
? flatFieldMetadataMaps.byId[sourceFieldMetadataId]
: undefined;
// If empty, it could be a morph relation
if (!isDefined(sourceFieldMetadata)) {
const morphRelationsWithTargetObjectMetadata = Object.values(
objectMetadataItem.fieldsById,
)
.filter(
(fieldMetadata) =>
fieldMetadata.type === FieldMetadataType.MORPH_RELATION,
const morphRelationsWithTargetObjectMetadata =
getFlatFieldsFromFlatObjectMetadata(
flatObjectMetadata,
flatFieldMetadataMaps,
)
.map((fieldMetadata) => {
const targetObjectMetadata = getTargetObjectMetadataOrThrow(
fieldMetadata,
objectMetadataMaps,
);
.filter(
(fieldMetadata) =>
fieldMetadata.type === FieldMetadataType.MORPH_RELATION,
)
.map((fieldMetadata) => {
const targetObjectMetadata = getTargetObjectMetadataOrThrow(
fieldMetadata,
flatObjectMetadataMaps,
);
return {
fieldMetadata,
targetObjectMetadata,
};
});
return {
fieldMetadata,
targetObjectMetadata,
};
});
const possibleGraphQLFieldNames: {
graphQLField: string;
fieldMetadata: FieldMetadataEntity;
targetObjectMetadata: ObjectMetadataItemWithFieldMaps;
fieldMetadata: FlatFieldMetadata;
targetObjectMetadata: FlatObjectMetadata;
}[] = [];
morphRelationsWithTargetObjectMetadata.map((morphRelation) => {
if (
!isFieldMetadataTypeMorphRelation(morphRelation.fieldMetadata) ||
!isFlatFieldMetadataOfType(
morphRelation.fieldMetadata,
FieldMetadataType.MORPH_RELATION,
) ||
!morphRelation.fieldMetadata.settings?.relationType
) {
return;
@@ -2,13 +2,14 @@ import {
GraphqlQueryRunnerException,
GraphqlQueryRunnerExceptionCode,
} from 'src/engine/api/graphql/graphql-query-runner/errors/graphql-query-runner.exception';
import { type FieldMetadataEntity } from 'src/engine/metadata-modules/field-metadata/field-metadata.entity';
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';
export const getTargetObjectMetadataOrThrow = (
fieldMetadata: FieldMetadataEntity,
objectMetadataMaps: ObjectMetadataMaps,
) => {
fieldMetadata: FlatFieldMetadata,
flatObjectMetadataMaps: FlatEntityMaps<FlatObjectMetadata>,
): FlatObjectMetadata => {
if (!fieldMetadata.relationTargetObjectMetadataId) {
throw new GraphqlQueryRunnerException(
`Relation target object metadata id not found for field ${fieldMetadata.name}`,
@@ -17,7 +18,7 @@ export const getTargetObjectMetadataOrThrow = (
}
const targetObjectMetadata =
objectMetadataMaps.byId[fieldMetadata.relationTargetObjectMetadataId];
flatObjectMetadataMaps.byId[fieldMetadata.relationTargetObjectMetadataId];
if (!targetObjectMetadata) {
throw new GraphqlQueryRunnerException(