morph backend CORE api (#13517)

# Morph Relation needs some work on the core API

Especially on the Morph types

<img width="682" height="394" alt="Screenshot 2025-07-31 at 12 19 40"
src="https://github.com/user-attachments/assets/8b185ae3-3fe7-4886-97b1-ec143bb1b0f4"
/>


## Focus Areas:
- GraphQL schema generation
- Query parsing and field selection
- Morph relation field support
- Input type definition preparation
This commit is contained in:
Guillim
2025-08-12 11:47:02 +02:00
committed by GitHub
parent 6cc947ea01
commit 63c8bb9530
32 changed files with 742 additions and 111 deletions
@@ -1,9 +1,9 @@
import { Injectable, Logger } from '@nestjs/common';
import {
GraphQLObjectType,
type GraphQLFieldConfigArgumentMap,
type GraphQLFieldConfigMap,
GraphQLObjectType,
} from 'graphql';
import { FieldMetadataType } from 'twenty-shared/types';
@@ -14,6 +14,7 @@ import { RelationTypeV2Factory } from 'src/engine/api/graphql/workspace-schema-b
import { TypeDefinitionsStorage } from 'src/engine/api/graphql/workspace-schema-builder/storages/type-definitions.storage';
import { getResolverArgs } from 'src/engine/api/graphql/workspace-schema-builder/utils/get-resolver-args.util';
import { objectContainsRelationField } from 'src/engine/api/graphql/workspace-schema-builder/utils/object-contains-relation-field';
import { computeMorphRelationFieldName } from 'src/engine/metadata-modules/field-metadata/utils/compute-morph-relation-field-name.util';
import { type ObjectMetadataEntity } from 'src/engine/metadata-modules/object-metadata/object-metadata.entity';
import { isFieldMetadataEntityOfType } from 'src/engine/utils/is-field-metadata-of-type.util';
@@ -46,6 +47,7 @@ export class ExtendObjectTypeDefinitionV2Factory {
public create(
objectMetadata: ObjectMetadataEntity,
options: WorkspaceBuildSchemaOptions,
objectMetadataCollection: ObjectMetadataEntity[],
): ObjectTypeDefinition {
const kind = ObjectTypeDefinitionKind.Plain;
const gqlType = this.typeDefinitionsStorage.getObjectTypeByKey(
@@ -94,7 +96,11 @@ export class ExtendObjectTypeDefinitionV2Factory {
...config,
fields: () => ({
...config.fields,
...this.generateFields(objectMetadata, options),
...this.generateFields(
objectMetadata,
options,
objectMetadataCollection,
),
}),
}),
};
@@ -103,6 +109,7 @@ export class ExtendObjectTypeDefinitionV2Factory {
private generateFields(
objectMetadata: ObjectMetadataEntity,
options: WorkspaceBuildSchemaOptions,
objectMetadataCollection: ObjectMetadataEntity[],
// eslint-disable-next-line @typescript-eslint/no-explicit-any
): GraphQLFieldConfigMap<any, any> {
// eslint-disable-next-line @typescript-eslint/no-explicit-any
@@ -151,7 +158,27 @@ export class ExtendObjectTypeDefinitionV2Factory {
);
}
fields[fieldMetadata.name] = {
const objectMetadataTarget = objectMetadataCollection.find(
(objectMetadata) =>
objectMetadata.id === fieldMetadata.relationTargetObjectMetadataId,
);
if (!objectMetadataTarget) {
throw new Error(
`Object Metadata Target not found for Id: ${fieldMetadata.relationTargetObjectMetadataId} on fieldMetadata name: ${fieldMetadata.name}`,
);
}
const fieldName =
fieldMetadata.type === FieldMetadataType.MORPH_RELATION
? computeMorphRelationFieldName({
fieldName: fieldMetadata.name,
relationDirection: fieldMetadata.settings.relationType,
targetObjectMetadata: objectMetadataTarget,
})
: fieldMetadata.name;
fields[fieldName] = {
type: relationType,
args: argsType,
description: fieldMetadata.description,
@@ -8,6 +8,7 @@ import { CompositeObjectTypeDefinitionFactory } from 'src/engine/api/graphql/wor
import { EnumTypeDefinitionFactory } from 'src/engine/api/graphql/workspace-schema-builder/factories/enum-type-definition.factory';
import { ExtendObjectTypeDefinitionV2Factory } from 'src/engine/api/graphql/workspace-schema-builder/factories/extend-object-type-definition-v2.factory';
import { RelationConnectInputTypeDefinitionFactory } from 'src/engine/api/graphql/workspace-schema-builder/factories/relation-connect-input-type-definition.factory';
import { objectContainsMorphRelationField } from 'src/engine/api/graphql/workspace-schema-builder/utils/object-contains-morph-relation-field.util';
import { compositeTypeDefinitions } from 'src/engine/metadata-modules/field-metadata/composite-types';
import { type ObjectMetadataEntity } from 'src/engine/metadata-modules/object-metadata/object-metadata.entity';
@@ -269,7 +270,11 @@ export class TypeDefinitionsGenerator {
) {
// Generate extended object type defs only for objects that contain composite fields
const objectMetadataCollectionWithCompositeFields =
objectMetadataCollection.filter(objectContainsRelationField);
objectMetadataCollection.filter(
(obj) =>
objectContainsRelationField(obj) ||
objectContainsMorphRelationField(obj),
);
const workspaceId =
objectMetadataCollectionWithCompositeFields[0]?.workspaceId;
@@ -282,6 +287,7 @@ export class TypeDefinitionsGenerator {
this.extendObjectTypeDefinitionV2Factory.create(
objectMetadata,
options,
objectMetadataCollection,
),
);
@@ -167,10 +167,10 @@ const generateRelationField = <
options: WorkspaceBuildSchemaOptions;
typeFactory: TypeFactory<T>;
}) => {
const relationField = {};
const relationFields = {};
if (fieldMetadata.settings?.relationType === RelationType.ONE_TO_MANY) {
return relationField;
return relationFields;
}
const { joinColumnName, fieldMetadataName } =
@@ -188,14 +188,14 @@ const generateRelationField = <
);
// @ts-expect-error legacy noImplicitAny
relationField[joinColumnName] = {
relationFields[joinColumnName] = {
type,
description: fieldMetadata.description,
};
//TODO : temporary - continue ej/1278 branch (https://github.com/twentyhq/core-team-issues/issues/1278 issue) before removing this
if (fieldMetadata.type === FieldMetadataType.MORPH_RELATION)
return relationField;
return relationFields;
if (
[InputTypeDefinitionKind.Create, InputTypeDefinitionKind.Update].includes(
@@ -215,15 +215,16 @@ const generateRelationField = <
isRelationConnectField: true,
},
);
// todo @guillim
// @ts-expect-error legacy noImplicitAny
relationFields[fieldMetadataName] = {
type: type,
description: fieldMetadata.description,
};
}
// @ts-expect-error legacy noImplicitAny
relationField[fieldMetadataName] = {
type: type,
description: fieldMetadata.description,
};
return relationField;
return relationFields;
};
// Type guard
@@ -0,0 +1,10 @@
import { type ObjectMetadataEntity } from 'src/engine/metadata-modules/object-metadata/object-metadata.entity';
import { isMorphRelationFieldMetadataType } from 'src/engine/utils/is-morph-relation-field-metadata-type.util';
export const objectContainsMorphRelationField = (
objectMetadata: ObjectMetadataEntity,
): boolean => {
return objectMetadata.fields.some((field) =>
isMorphRelationFieldMetadataType(field.type),
);
};