fix(server): skip defaultValue null check for relation/morph fields on update (#21875)

## Description

Updating any metadata property (e.g. `description`, `label`) of an
existing **non-nullable RELATION** field fails with:

```
INVALID_FIELD_INPUT: Default value cannot be null for non-nullable fields
```

A relation field has no literal `defaultValue` (it's always `null`), so
the update-path validator rejects every required relation. **Creating**
the same field is fine — only **updates** fail.

This also blocks any incremental app re-sync (`yarn twenty dev --once`)
whose diff touches a required relation field.

## Fix

Added a guard in
`FlatFieldMetadataValidatorService.validateFlatFieldMetadataUpdate()`
using the already-imported `isMorphOrRelationUniversalFlatFieldMetadata`
utility to skip the `defaultValue === null` check for relation/morph
field types:

```diff
 if (
+  !isMorphOrRelationUniversalFlatFieldMetadata(
+    flatFieldMetadataToValidate,
+  ) &&
   flatFieldMetadataToValidate.isNullable === false &&
   flatFieldMetadataToValidate.defaultValue === null
 ) {
```

### Why this works:
- Relation fields represent foreign key relationships, not columns with
literal defaults
- The same guard is already used at line 144 in the same method for
relation-specific validation
- The create path (`validateFlatFieldMetadataCreation`) never had this
check, which is why creation always worked
- No new imports needed — `isMorphOrRelationUniversalFlatFieldMetadata`
is already imported on line 14

## Verification
- `npx nx build twenty-server`  compiles successfully

Fixes #21751

<!-- This is an auto-generated description by cubic. -->
<a
href="https://cubic.dev/pr/twentyhq/twenty/pull/21875?utm_source=github"
target="_blank" rel="noopener noreferrer"
data-no-image-dialog="true"><picture><source
media="(prefers-color-scheme: dark)"
srcset="https://www.cubic.dev/buttons/review-in-cubic-dark.svg"><source
media="(prefers-color-scheme: light)"
srcset="https://www.cubic.dev/buttons/review-in-cubic-light.svg"><img
alt="Review in cubic"
src="https://www.cubic.dev/buttons/review-in-cubic-dark.svg"></picture></a>
<!-- End of auto-generated description by cubic. -->

---------

Co-authored-by: Charles Bochet <charles@twenty.com>
Co-authored-by: prastoin <paul@twenty.com>
This commit is contained in:
Abhinav A P
2026-06-25 13:35:31 +05:30
committed by GitHub
parent feac2df216
commit cf91b87892
5 changed files with 91 additions and 2 deletions
@@ -1,5 +1,5 @@
import { type LinkMetadata } from '@/types/composite-types/links.composite-type';
import { type FieldMetadataType } from '@/types/FieldMetadataType';
import { FieldMetadataType } from '@/types/FieldMetadataType';
import { type IsExactly } from '@/types/IsExactly';
export const fieldMetadataDefaultValueFunctionName = {
@@ -111,3 +111,17 @@ export type FieldMetadataDefaultValue<
: T extends keyof FieldMetadataDefaultValueMapping
? FieldMetadataDefaultValueMapping[T]
: never | null;
export const FIELD_METADATA_TYPES_WITHOUT_DEFAULT_VALUE = [
FieldMetadataType.RELATION,
FieldMetadataType.MORPH_RELATION,
FieldMetadataType.FILES,
FieldMetadataType.TS_VECTOR,
] as const satisfies FieldMetadataType[];
export const isFieldMetadataTypeWithDefaultValue = (
type: FieldMetadataType,
): boolean =>
!(
FIELD_METADATA_TYPES_WITHOUT_DEFAULT_VALUE as readonly FieldMetadataType[]
).includes(type);
@@ -0,0 +1,19 @@
import { type Equal, type Expect } from '@/testing';
import {
type FieldMetadataDefaultValueMapping,
type FIELD_METADATA_TYPES_WITHOUT_DEFAULT_VALUE,
} from '@/types/FieldMetadataDefaultValue';
import { type FieldMetadataType } from '@/types/FieldMetadataType';
type ClassifiedFieldMetadataType =
| keyof FieldMetadataDefaultValueMapping
| (typeof FIELD_METADATA_TYPES_WITHOUT_DEFAULT_VALUE)[number];
type Unclassified = Exclude<FieldMetadataType, ClassifiedFieldMetadataType>;
type ExtraClassified = Exclude<ClassifiedFieldMetadataType, FieldMetadataType>;
// oxlint-disable-next-line unused-imports/no-unused-vars
type Assertions = [
Expect<Equal<Unclassified, never>>,
Expect<Equal<ExtraClassified, never>>,
];
+5 -1
View File
@@ -92,7 +92,11 @@ export type {
FieldMetadataDefaultValueForAnyType,
FieldMetadataDefaultValue,
} from './FieldMetadataDefaultValue';
export { fieldMetadataDefaultValueFunctionName } from './FieldMetadataDefaultValue';
export {
fieldMetadataDefaultValueFunctionName,
FIELD_METADATA_TYPES_WITHOUT_DEFAULT_VALUE,
isFieldMetadataTypeWithDefaultValue,
} from './FieldMetadataDefaultValue';
export type { FieldMetadataMultiItemSettings } from './FieldMetadataMultiItemSettings';
export { FieldMetadataSettingsOnClickAction } from './FieldMetadataMultiItemSettings';
export type {