Deprecate FieldMetadataInterface (#13264)

# Introduction

From the moment replaced the FieldMetadataInterface definition to:
```ts
import { FieldMetadataType } from 'twenty-shared/types';

import { FieldMetadataEntity } from 'src/engine/metadata-modules/field-metadata/field-metadata.entity';

export type FieldMetadataInterface<
  T extends FieldMetadataType = FieldMetadataType,
> = FieldMetadataEntity<T>;
```
After this PR merge will create a new one removing the type and
replacing it to `FieldMetadataEntity`.
Did not renamed it here to avoid conflicts on naming + type issues fixs
within the same PR

## Field metadata entity RELATION or MORPH
Relations fields cannot be null for those field metadata entity instance
anymore, but are never for the others see
`packages/twenty-server/src/engine/metadata-modules/field-metadata/types/field-metadata-entity-test.type.ts`
( introduced TypeScript tests )

## Concerns
- TS_VECTOR is the most at risk with the `generatedType` and
`asExpression` removal from interface

## What's next
- `FielMetadataInterface` removal and rename ( see introduction )
- Depcrecating `ObjectMetadataInterface`
- Refactor `FieldMetadataEntity` optional fiels to be nullable only
- TO DIG `never` occurences on settings, defaultValue etc
- Some interfaces will be replaced by the `FlatFieldMetadata` when
deprecating the current sync and comparators tools
This commit is contained in:
Paul Rastoin
2025-07-21 11:30:18 +02:00
committed by GitHub
parent c2a5f95675
commit 47b60bd49f
67 changed files with 1780 additions and 769 deletions
@@ -2,6 +2,7 @@ import { Test, TestingModule } from '@nestjs/testing';
import { i18n } from '@lingui/core';
import { UpdateOneInputType } from '@ptc-org/nestjs-query-graphql';
import { FieldMetadataType } from 'twenty-shared/types';
import {
ForbiddenError,
@@ -11,6 +12,7 @@ import { UpdateFieldInput } from 'src/engine/metadata-modules/field-metadata/dto
import { FieldMetadataEntity } from 'src/engine/metadata-modules/field-metadata/field-metadata.entity';
import { BeforeUpdateOneField } from 'src/engine/metadata-modules/field-metadata/hooks/before-update-one-field.hook';
import { FieldMetadataService } from 'src/engine/metadata-modules/field-metadata/services/field-metadata.service';
import { getMockFieldMetadataEntity } from 'src/utils/__test__/get-field-metadata-entity.mock';
jest.mock('@lingui/core', () => ({
i18n: {
@@ -96,14 +98,23 @@ describe('BeforeUpdateOneField', () => {
},
};
const mockField: Partial<FieldMetadataEntity> = {
const mockField = getMockFieldMetadataEntity({
workspaceId: mockWorkspaceId,
objectMetadataId: '20202020-0000-0000-0000-000000000002',
id: mockFieldId,
type: FieldMetadataType.TEXT,
name: 'oldName',
label: 'Old Name',
isNullable: true,
isCustom: true,
};
isLabelSyncedWithName: true,
createdAt: new Date(),
updatedAt: new Date(),
}) as FieldMetadataEntity;
jest
.spyOn(fieldMetadataService, 'findOneWithinWorkspace')
.mockResolvedValue(mockField as FieldMetadataEntity);
.mockResolvedValue(mockField);
const result = await hook.run(
instance as UpdateOneInputType<UpdateFieldInput>,
@@ -124,14 +135,23 @@ describe('BeforeUpdateOneField', () => {
},
};
const mockField: Partial<FieldMetadataEntity> = {
const mockField = getMockFieldMetadataEntity({
workspaceId: mockWorkspaceId,
objectMetadataId: '20202020-0000-0000-0000-000000000002',
id: mockFieldId,
type: FieldMetadataType.TEXT,
name: 'oldName',
label: 'Old Name',
isNullable: true,
isCustom: false,
};
isLabelSyncedWithName: true,
createdAt: new Date(),
updatedAt: new Date(),
}) as FieldMetadataEntity;
jest
.spyOn(fieldMetadataService, 'findOneWithinWorkspace')
.mockResolvedValue(mockField as FieldMetadataEntity);
.mockResolvedValue(mockField);
await expect(
hook.run(instance as UpdateOneInputType<UpdateFieldInput>, {
@@ -149,15 +169,24 @@ describe('BeforeUpdateOneField', () => {
},
};
const mockField: Partial<FieldMetadataEntity> = {
const mockField = getMockFieldMetadataEntity({
workspaceId: mockWorkspaceId,
objectMetadataId: '20202020-0000-0000-0000-000000000002',
id: mockFieldId,
type: FieldMetadataType.TEXT,
name: 'oldName',
label: 'Old Name',
isNullable: true,
isCustom: false,
isActive: true,
};
isLabelSyncedWithName: true,
createdAt: new Date(),
updatedAt: new Date(),
}) as FieldMetadataEntity;
jest
.spyOn(fieldMetadataService, 'findOneWithinWorkspace')
.mockResolvedValue(mockField as FieldMetadataEntity);
.mockResolvedValue(mockField);
const result = await hook.run(
instance as UpdateOneInputType<UpdateFieldInput>,
@@ -186,18 +215,26 @@ describe('BeforeUpdateOneField', () => {
},
};
const mockField: Partial<FieldMetadataEntity> = {
const mockField = getMockFieldMetadataEntity({
workspaceId: mockWorkspaceId,
objectMetadataId: '20202020-0000-0000-0000-000000000002',
id: mockFieldId,
type: FieldMetadataType.TEXT,
name: 'oldName',
label: 'Old Name',
isNullable: true,
isCustom: false,
isLabelSyncedWithName: false,
standardOverrides: {
label: 'Custom Label',
},
};
createdAt: new Date(),
updatedAt: new Date(),
}) as FieldMetadataEntity;
jest
.spyOn(fieldMetadataService, 'findOneWithinWorkspace')
.mockResolvedValue(mockField as FieldMetadataEntity);
.mockResolvedValue(mockField);
const result = await hook.run(
instance as UpdateOneInputType<UpdateFieldInput>,
@@ -228,16 +265,23 @@ describe('BeforeUpdateOneField', () => {
},
};
const mockField: Partial<FieldMetadataEntity> = {
const mockField = getMockFieldMetadataEntity({
workspaceId: mockWorkspaceId,
objectMetadataId: '20202020-0000-0000-0000-000000000002',
id: mockFieldId,
type: FieldMetadataType.TEXT,
name: 'oldName',
label: 'Default Label',
isNullable: true,
isCustom: false,
isLabelSyncedWithName: false,
label: 'Default Label',
};
createdAt: new Date(),
updatedAt: new Date(),
}) as FieldMetadataEntity;
jest
.spyOn(fieldMetadataService, 'findOneWithinWorkspace')
.mockResolvedValue(mockField as FieldMetadataEntity);
.mockResolvedValue(mockField);
const result = await hook.run(
instance as UpdateOneInputType<UpdateFieldInput>,
@@ -207,7 +207,7 @@ export class BeforeUpdateOneField<T extends UpdateFieldInput>
update: StandardFieldUpdate;
overrideKey: 'label' | 'description' | 'icon';
newValue: string;
originalValue: string;
originalValue: string | null;
locale?: keyof typeof APP_LOCALES | undefined;
}): boolean {
// Handle localized overrides
@@ -238,7 +238,7 @@ export class BeforeUpdateOneField<T extends UpdateFieldInput>
update: StandardFieldUpdate,
overrideKey: 'label' | 'description' | 'icon',
newValue: string,
originalValue: string,
originalValue: string | null,
locale: keyof typeof APP_LOCALES,
): boolean {
const messageId = generateMessageId(originalValue ?? '');
@@ -268,7 +268,7 @@ export class BeforeUpdateOneField<T extends UpdateFieldInput>
update: StandardFieldUpdate,
overrideKey: 'label' | 'description' | 'icon',
newValue: string,
originalValue: string,
originalValue: string | null,
): boolean {
if (newValue !== originalValue) {
return false;