53bb534af8
# Adding MORPH support to the relationDecorator
Extends the @WorkspaceRelation decorator to support morph relations by
adding isMorphRelation and morphId options
The implementation enforces type safety through TypeScript discriminated
unions, ensuring morphId is required when isMorphRelation: true, and
includes runtime validation that throws a descriptive error if morphId
is missing.
The morph relation metadata is properly stored in metadataArgsStorage
and integrates seamlessly with the existing StandardFieldRelationFactory
to convert decorated fields into FieldMetadataType.MORPH_RELATION
entities with appropriate database constraints.
To test :
in person entity, add
```ts
@WorkspaceRelation({
standardId: PERSON_STANDARD_FIELD_IDS.testRelatedRecordCompany,
type: RelationType.MANY_TO_ONE,
label: msg`Test Related Record of Company`,
description: msg`Test relation to company`,
icon: 'IconLink',
inverseSideTarget: () => CompanyWorkspaceEntity,
inverseSideFieldKey: 'testRelatedPeople',
onDelete: RelationOnDeleteAction.CASCADE,
isMorphRelation: true,
morphId: PERSON_STANDARD_FIELD_IDS.testRelatedRecordMorphId,
})
@WorkspaceIsNullable()
@WorkspaceIsSystem()
testRelatedRecordCompany: Relation<CompanyWorkspaceEntity> | null;
@WorkspaceJoinColumn('testRelatedRecordCompany')
testRelatedRecordCompanyId: string | null;
@WorkspaceRelation({
standardId: PERSON_STANDARD_FIELD_IDS.testRelatedRecordOpportunity,
type: RelationType.MANY_TO_ONE,
label: msg`Test Related Record of Opportunity`,
description: msg`Test relation to opportunity`,
icon: 'IconLink',
inverseSideTarget: () => OpportunityWorkspaceEntity,
inverseSideFieldKey: 'testRelatedPeople',
onDelete: RelationOnDeleteAction.CASCADE,
isMorphRelation: true,
morphId: PERSON_STANDARD_FIELD_IDS.testRelatedRecordMorphId,
})
@WorkspaceIsNullable()
@WorkspaceIsSystem()
testRelatedRecordOpportunity: Relation<OpportunityWorkspaceEntity> | null;
@WorkspaceJoinColumn('testRelatedRecordOpportunity')
testRelatedRecordOpportunityId: string | null;
```
in opportunity,
```ts
@WorkspaceRelation({
standardId: OPPORTUNITY_STANDARD_FIELD_IDS.testRelatedPeople,
type: RelationType.ONE_TO_MANY,
label: msg`Test Related People`,
description: msg`People linked to the opportunity via Test Related Record`,
icon: 'IconUsers',
inverseSideTarget: () => PersonWorkspaceEntity,
inverseSideFieldKey: 'testRelatedRecordOpportunity',
onDelete: RelationOnDeleteAction.SET_NULL,
})
@WorkspaceIsNullable()
@WorkspaceIsSystem()
testRelatedPeople: Relation<PersonWorkspaceEntity[]>;
```
in company,
```ts
@WorkspaceRelation({
standardId: COMPANY_STANDARD_FIELD_IDS.testRelatedPeople,
type: RelationType.ONE_TO_MANY,
label: msg`Test Related People`,
description: msg`People linked to the company via Test Related Record`,
icon: 'IconUsers',
inverseSideTarget: () => PersonWorkspaceEntity,
inverseSideFieldKey: 'testRelatedRecordCompany',
onDelete: RelationOnDeleteAction.SET_NULL,
})
@WorkspaceIsNullable()
@WorkspaceIsSystem()
testRelatedPeople: Relation<PersonWorkspaceEntity[]>;
```
In constants/standard-field-ids.ts, add the standard fields ids
```ts
// COMPANY_STANDARD_FIELD_IDS
testRelatedPeople: '20202020-7a90-47e9-b31f-916a716fd212',
// OPPORTUNITY_STANDARD_FIELD_IDS
testRelatedPeople: '20202020-7a90-47e9-b31f-916a716fd213',
// PERSON_STANDARD_FIELD_IDS
testRelatedRecordOpportunity: '20202020-5a90-47e9-b31f-916a716fd211',
testRelatedRecordCompany: '20202020-1eb2-4298-910b-66b015b36d72',
testRelatedRecordMorphId: '20202020-6ccf-48e3-bb92-bc9961bc011e',
```