Spread in parent and requires FlatEntity.__universal (#17753)

# Introduction
Requiring the spreaded `__universal` record that aggregates all the
universal identifier ( relations fk and aggregators ) of an entity to
its root

It's blockin for https://github.com/twentyhq/twenty/pull/17687 to be
finalized because if we don't we would have to migrated all related
entities at once in order for them to always have the universal
properties

## `resolveEntityRelationUniversalIdentifiers`
Introduced `resolveEntityRelationUniversalIdentifiers` a centralized
utility that resolves foreign key IDs to universal identifiers using
ALL_METADATA_RELATIONS metadata. It provides strict typing for both
input (foreign keys) and output (universal identifiers), with
nullability dynamically inferred from entity relation types.
Strictly and dynamically typed for both output and input

To do so added a new type and const/runtime grain to
ALL_METADATA_RELATIONS `isNullable`to many-to-one entries, derived from
the entity relation property types. And fixed incorrectly typed typeorm
entities

### Usage
```ts
  const {
    availabilityObjectMetadataUniversalIdentifier,
    frontComponentUniversalIdentifier,
  } = resolveEntityRelationUniversalIdentifiers({
    metadataName: 'commandMenuItem',
    foreignKeyValues: {
      availabilityObjectMetadataId:
        createCommandMenuItemInput.availabilityObjectMetadataId,
      frontComponentId: createCommandMenuItemInput.frontComponentId,
    },
    flatEntityMaps: { flatObjectMetadataMaps, flatFrontComponentMaps },
  });
```
This commit is contained in:
Paul Rastoin
2026-02-06 18:02:02 +01:00
committed by GitHub
parent a494a7a902
commit 3dc5b162c7
133 changed files with 2141 additions and 1166 deletions
@@ -1,4 +1,6 @@
import { type FlatEntityMaps } from 'src/engine/metadata-modules/flat-entity/types/flat-entity-maps.type';
import { type FlatPageLayoutWidget } from 'src/engine/metadata-modules/flat-page-layout-widget/types/flat-page-layout-widget.type';
export type FlatPageLayoutWidgetMaps = FlatEntityMaps<FlatPageLayoutWidget>;
export type FlatPageLayoutWidgetMaps = FlatEntityMaps<
Omit<FlatPageLayoutWidget, 'universalConfiguration'>
>;
@@ -4,4 +4,4 @@ import { type WidgetConfigurationType } from 'src/engine/metadata-modules/page-l
export type FlatPageLayoutWidget<
T extends WidgetConfigurationType = WidgetConfigurationType,
> = FlatEntityFrom<PageLayoutWidgetEntity<T>>;
> = Omit<FlatEntityFrom<PageLayoutWidgetEntity<T>>, 'universalConfiguration'>;
@@ -1,6 +1,9 @@
import { trimAndRemoveDuplicatedWhitespacesFromObjectStringProperties } from 'twenty-shared/utils';
import { v4 } from 'uuid';
import { type FlatApplication } from 'src/engine/core-modules/application/types/flat-application.type';
import { type AllFlatEntityMaps } from 'src/engine/metadata-modules/flat-entity/types/all-flat-entity-maps.type';
import { resolveEntityRelationUniversalIdentifiers } from 'src/engine/metadata-modules/flat-entity/utils/resolve-entity-relation-universal-identifiers.util';
import { type FlatPageLayoutWidget } from 'src/engine/metadata-modules/flat-page-layout-widget/types/flat-page-layout-widget.type';
import { type CreatePageLayoutWidgetInput } from 'src/engine/metadata-modules/page-layout-widget/dtos/inputs/create-page-layout-widget.input';
import { validateWidgetConfigurationInput } from 'src/engine/metadata-modules/page-layout-widget/utils/validate-widget-configuration-input.util';
@@ -9,13 +12,18 @@ export type FromCreatePageLayoutWidgetInputToFlatPageLayoutWidgetToCreateArgs =
{
createPageLayoutWidgetInput: CreatePageLayoutWidgetInput;
workspaceId: string;
workspaceCustomApplicationId: string;
};
flatApplication: FlatApplication;
} & Pick<
AllFlatEntityMaps,
'flatPageLayoutTabMaps' | 'flatObjectMetadataMaps'
>;
export const fromCreatePageLayoutWidgetInputToFlatPageLayoutWidgetToCreate = ({
createPageLayoutWidgetInput: rawCreatePageLayoutWidgetInput,
workspaceId,
workspaceCustomApplicationId,
flatApplication,
flatPageLayoutTabMaps,
flatObjectMetadataMaps,
}: FromCreatePageLayoutWidgetInputToFlatPageLayoutWidgetToCreateArgs): FlatPageLayoutWidget => {
const { pageLayoutTabId, ...createPageLayoutWidgetInput } =
trimAndRemoveDuplicatedWhitespacesFromObjectStringProperties(
@@ -30,9 +38,22 @@ export const fromCreatePageLayoutWidgetInputToFlatPageLayoutWidgetToCreate = ({
const createdAt = new Date().toISOString();
const pageLayoutWidgetId = v4();
const {
pageLayoutTabUniversalIdentifier,
objectMetadataUniversalIdentifier,
} = resolveEntityRelationUniversalIdentifiers({
metadataName: 'pageLayoutWidget',
foreignKeyValues: {
pageLayoutTabId,
objectMetadataId: createPageLayoutWidgetInput.objectMetadataId,
},
flatEntityMaps: { flatPageLayoutTabMaps, flatObjectMetadataMaps },
});
return {
id: pageLayoutWidgetId,
pageLayoutTabId,
pageLayoutTabUniversalIdentifier,
workspaceId,
createdAt,
updatedAt: createdAt,
@@ -41,10 +62,12 @@ export const fromCreatePageLayoutWidgetInputToFlatPageLayoutWidgetToCreate = ({
title: createPageLayoutWidgetInput.title,
type: createPageLayoutWidgetInput.type,
objectMetadataId: createPageLayoutWidgetInput.objectMetadataId ?? null,
objectMetadataUniversalIdentifier,
gridPosition: createPageLayoutWidgetInput.gridPosition,
position: createPageLayoutWidgetInput.position ?? null,
configuration: createPageLayoutWidgetInput.configuration,
applicationId: workspaceCustomApplicationId,
applicationId: flatApplication.id,
applicationUniversalIdentifier: flatApplication.universalIdentifier,
conditionalDisplay: null,
};
};
@@ -6,9 +6,9 @@ import { WidgetConfigurationType } from 'src/engine/metadata-modules/page-layout
type PageLayoutWidgetConfiguration = PageLayoutWidgetEntity['configuration'];
type UniversalPageLayoutWidgetConfiguration = NonNullable<
FlatPageLayoutWidget['__universal']
>['universalConfiguration'];
type UniversalPageLayoutWidgetConfiguration =
// @ts-expect-error Ignoring until the pageLayoutWidget has been universally migrated
NonNullable<FlatPageLayoutWidget>['universalConfiguration'];
// Field metadata IDs in widget configurations don't need to reference existing entities
const getFieldMetadataUniversalIdentifier = ({
@@ -6,7 +6,6 @@ import {
} from 'src/engine/metadata-modules/flat-entity/exceptions/flat-entity-maps.exception';
import { getMetadataEntityRelationProperties } from 'src/engine/metadata-modules/flat-entity/utils/get-metadata-entity-relation-properties.util';
import { type FlatPageLayoutWidget } from 'src/engine/metadata-modules/flat-page-layout-widget/types/flat-page-layout-widget.type';
import { fromPageLayoutWidgetConfigurationToUniversalConfiguration } from 'src/engine/metadata-modules/flat-page-layout-widget/utils/from-page-layout-widget-configuration-to-universal-configuration.util';
import { type FromEntityToFlatEntityArgs } from 'src/engine/workspace-cache/types/from-entity-to-flat-entity-args.type';
type FromPageLayoutWidgetEntityToFlatPageLayoutWidgetArgs =
@@ -19,7 +18,6 @@ export const fromPageLayoutWidgetEntityToFlatPageLayoutWidget = ({
applicationIdToUniversalIdentifierMap,
pageLayoutTabIdToUniversalIdentifierMap,
objectMetadataIdToUniversalIdentifierMap,
fieldMetadataIdToUniversalIdentifierMap,
}: FromPageLayoutWidgetEntityToFlatPageLayoutWidgetArgs): FlatPageLayoutWidget => {
const pageLayoutWidgetEntityWithoutRelations = removePropertiesFromRecord(
pageLayoutWidgetEntity,
@@ -66,11 +64,12 @@ export const fromPageLayoutWidgetEntityToFlatPageLayoutWidget = ({
}
}
const configurationWithUniversalIdentifiers =
fromPageLayoutWidgetConfigurationToUniversalConfiguration({
configuration: pageLayoutWidgetEntityWithoutRelations.configuration,
fieldMetadataIdToUniversalIdentifierMap,
});
// TODO uncomment once page layout widget has been migrated
// const configurationWithUniversalIdentifiers =
// fromPageLayoutWidgetConfigurationToUniversalConfiguration({
// configuration: pageLayoutWidgetEntityWithoutRelations.configuration,
// fieldMetadataIdToUniversalIdentifierMap,
// });
return {
...pageLayoutWidgetEntityWithoutRelations,
@@ -80,12 +79,9 @@ export const fromPageLayoutWidgetEntityToFlatPageLayoutWidget = ({
universalIdentifier:
pageLayoutWidgetEntityWithoutRelations.universalIdentifier,
applicationId: pageLayoutWidgetEntityWithoutRelations.applicationId,
__universal: {
universalIdentifier: pageLayoutWidgetEntity.universalIdentifier,
applicationUniversalIdentifier,
pageLayoutTabUniversalIdentifier,
objectMetadataUniversalIdentifier,
universalConfiguration: configurationWithUniversalIdentifiers,
},
applicationUniversalIdentifier,
pageLayoutTabUniversalIdentifier,
objectMetadataUniversalIdentifier,
// universalConfiguration: configurationWithUniversalIdentifiers,
};
};
@@ -4,8 +4,10 @@ import {
isDefined,
} from 'twenty-shared/utils';
import { FLAT_PAGE_LAYOUT_WIDGET_EDITABLE_PROPERTIES } from 'src/engine/metadata-modules/flat-page-layout-widget/constants/flat-page-layout-widget-editable-properties.constant';
import { type AllFlatEntityMaps } from 'src/engine/metadata-modules/flat-entity/types/all-flat-entity-maps.type';
import { findFlatEntityByIdInFlatEntityMaps } from 'src/engine/metadata-modules/flat-entity/utils/find-flat-entity-by-id-in-flat-entity-maps.util';
import { resolveEntityRelationUniversalIdentifiers } from 'src/engine/metadata-modules/flat-entity/utils/resolve-entity-relation-universal-identifiers.util';
import { FLAT_PAGE_LAYOUT_WIDGET_EDITABLE_PROPERTIES } from 'src/engine/metadata-modules/flat-page-layout-widget/constants/flat-page-layout-widget-editable-properties.constant';
import { type FlatPageLayoutWidgetMaps } from 'src/engine/metadata-modules/flat-page-layout-widget/types/flat-page-layout-widget-maps.type';
import { type FlatPageLayoutWidget } from 'src/engine/metadata-modules/flat-page-layout-widget/types/flat-page-layout-widget.type';
import { type UpdatePageLayoutWidgetInput } from 'src/engine/metadata-modules/page-layout-widget/dtos/inputs/update-page-layout-widget.input';
@@ -25,10 +27,14 @@ export const fromUpdatePageLayoutWidgetInputToFlatPageLayoutWidgetToUpdateOrThro
({
updatePageLayoutWidgetInput: rawUpdatePageLayoutWidgetInput,
flatPageLayoutWidgetMaps,
flatObjectMetadataMaps,
}: {
updatePageLayoutWidgetInput: UpdatePageLayoutWidgetInputWithId;
flatPageLayoutWidgetMaps: FlatPageLayoutWidgetMaps;
}): FlatPageLayoutWidget => {
} & Pick<
AllFlatEntityMaps,
'flatObjectMetadataMaps'
>): FlatPageLayoutWidget => {
const { id: pageLayoutWidgetToUpdateId } =
extractAndSanitizeObjectStringFields(rawUpdatePageLayoutWidgetInput, [
'id',
@@ -63,9 +69,25 @@ export const fromUpdatePageLayoutWidgetInputToFlatPageLayoutWidgetToUpdateOrThro
});
}
return mergeUpdateInExistingRecord({
const flatPageLayoutWidgetToUpdate = mergeUpdateInExistingRecord({
existing: existingFlatPageLayoutWidgetToUpdate,
properties: FLAT_PAGE_LAYOUT_WIDGET_EDITABLE_PROPERTIES,
update: updatedEditableFieldProperties,
});
if (updatedEditableFieldProperties.objectMetadataId !== undefined) {
const { objectMetadataUniversalIdentifier } =
resolveEntityRelationUniversalIdentifiers({
metadataName: 'pageLayoutWidget',
foreignKeyValues: {
objectMetadataId: flatPageLayoutWidgetToUpdate.objectMetadataId,
},
flatEntityMaps: { flatObjectMetadataMaps },
});
flatPageLayoutWidgetToUpdate.objectMetadataUniversalIdentifier =
objectMetadataUniversalIdentifier;
}
return flatPageLayoutWidgetToUpdate;
};