Files
twenty/packages/twenty-server/src/engine/metadata-modules/flat-entity/constant/all-metadata-relations.constant.ts
T
Paul Rastoin fe9d6f34ff [REQUIRES_FULL_CACHE_FLUSH_WHEN_RELEASED] Refactor FlatEntity to be UniversalFlatEntity superset (#17452)
# Introduction
In this PR we're refactoring the `FlatEntity` type to become a superset
of the `UniversalFlatEntity`.
Right now we're storing all the extra properties in `__universal`
property, at some point it might just be sibling to other entity and we
might rely on the `propertiesToCompare` constants and TypeScript
allowing passing a superset type into a smaller subset type

## FromTo utils
The entity to flat entity method now computes the universal information,
standardized a typing and pattern to do

## Example
Also strictly type
```ts
    "bbb019ea-6205-498c-aea5-67bc53bce8a9": {
      "workspaceId": "20202020-1c25-4d02-bf25-6aeccf7ea419",
      "universalIdentifier": "20202020-d111-4d11-8d11-da5ab0a11002",
      "applicationId": "d01b010d-b984-465b-b40b-370e954e5188",
      "id": "bbb019ea-6205-498c-aea5-67bc53bce8a9",
      "pageLayoutTabId": "791a512f-169f-4209-b731-aa86716668c6",
      "title": "Deals by Company",
      "type": "GRAPH",
      "objectMetadataId": "9e14efea-df5b-4c0e-aba9-cfe455f32397",
      "gridPosition": { "row": 0, "column": 6, "rowSpan": 6, "columnSpan": 6 },
      "configuration": {
        "color": "orange",
        "orderBy": "FIELD_ASC",
        "timezone": "UTC",
        "displayLegend": true,
        "displayDataLabel": false,
        "showCenterMetric": true,
        "configurationType": "PIE_CHART",
        "firstDayOfTheWeek": 0,
        "aggregateOperation": "COUNT",
        "groupBySubFieldName": "name",
        "groupByFieldMetadataId": "6673ff18-63d2-47a1-8f85-2b9b09ca27a5",
        "aggregateFieldMetadataId": "8d64ee41-5dd4-4de6-945a-7c0c18399715"
      },
      "createdAt": "2026-01-28T14:08:52.140Z",
      "updatedAt": "2026-01-28T14:08:52.140Z",
      "deletedAt": null,
      "__universal": {
        "universalIdentifier": "20202020-d111-4d11-8d11-da5ab0a11002",
        "applicationUniversalIdentifier": "20202020-64aa-4b6f-b003-9c74b97cee20",
        "pageLayoutTabUniversalIdentifier": "20202020-d011-4d11-8d11-da5ab0a01001",
        "objectMetadataUniversalIdentifier": "20202020-9549-49dd-b2b2-883999db8938",
        "gridPosition": {
          "row": 0,
          "column": 6,
          "rowSpan": 6,
          "columnSpan": 6
        },
        "configuration": {
          "color": "orange",
          "orderBy": "FIELD_ASC",
          "timezone": "UTC",
          "displayLegend": true,
          "displayDataLabel": false,
          "showCenterMetric": true,
          "configurationType": "PIE_CHART",
          "firstDayOfTheWeek": 0,
          "aggregateOperation": "COUNT",
          "groupBySubFieldName": "name",
          "aggregateFieldMetadataUniversalIdentifier": "20202020-d01a-4131-8a31-f123456789ab",
          "groupByFieldMetadataUniversalIdentifier": "20202020-cbac-457e-b565-adece5fc815f"
        }
      }
    },
```
2026-01-28 16:46:34 +00:00

454 lines
13 KiB
TypeScript

import { type AllMetadataName } from 'twenty-shared/metadata';
import { type Expect } from 'twenty-shared/testing';
import { type ExtractPropertiesThatEndsWithId } from 'twenty-shared/types';
import { type Relation } from 'typeorm';
import { type AddSuffixToEntityOneToManyProperties } from 'src/engine/metadata-modules/flat-entity/types/add-suffix-to-entity-one-to-many-properties.type';
import { type ExtractEntityManyToOneEntityRelationProperties } from 'src/engine/metadata-modules/flat-entity/types/extract-entity-many-to-one-entity-relation-properties.type';
import { type ExtractEntityOneToManyEntityRelationProperties } from 'src/engine/metadata-modules/flat-entity/types/extract-entity-one-to-many-entity-relation-properties.type';
import { type FromMetadataEntityToMetadataName } from 'src/engine/metadata-modules/flat-entity/types/from-metadata-entity-to-metadata-name.type';
import { type MetadataEntity } from 'src/engine/metadata-modules/flat-entity/types/metadata-entity.type';
import { type SyncableEntity } from 'src/engine/workspace-manager/types/syncable-entity.interface';
type ManyToOneRelationValue<
TSourceMetadataName extends AllMetadataName,
TRelationProperty extends ExtractEntityManyToOneEntityRelationProperties<
MetadataEntity<TSourceMetadataName>
>,
> =
NonNullable<
MetadataEntity<TSourceMetadataName>[TRelationProperty]
> extends Relation<infer TTargetEntity extends SyncableEntity>
? {
metadataName: FromMetadataEntityToMetadataName<TTargetEntity>;
flatEntityForeignKeyAggregator:
| keyof AddSuffixToEntityOneToManyProperties<TTargetEntity, 'ids'>
| null;
// Note: In the best of the world should not be nullable, entities should always declare inverside keys
foreignKey: ExtractPropertiesThatEndsWithId<
MetadataEntity<TSourceMetadataName>,
'id' | 'workspaceId'
>;
}
: null;
type OneToManyRelationValue<
TSourceMetadataName extends AllMetadataName,
TRelationProperty extends ExtractEntityOneToManyEntityRelationProperties<
MetadataEntity<TSourceMetadataName>
>,
> =
MetadataEntity<TSourceMetadataName>[TRelationProperty] extends Relation<
(infer TTargetEntity extends SyncableEntity)[]
>
? {
metadataName: FromMetadataEntityToMetadataName<TTargetEntity>;
}
: null;
type MetadataRelationsProperties = {
[TSourceMetadataName in AllMetadataName]: {
manyToOne: {
[TRelationProperty in ExtractEntityManyToOneEntityRelationProperties<
MetadataEntity<TSourceMetadataName>
>]: ManyToOneRelationValue<TSourceMetadataName, TRelationProperty>;
};
oneToMany: {
[TRelationProperty in ExtractEntityOneToManyEntityRelationProperties<
MetadataEntity<TSourceMetadataName>
>]: OneToManyRelationValue<TSourceMetadataName, TRelationProperty>;
};
};
};
export const ALL_METADATA_RELATIONS = {
agent: {
manyToOne: {
workspace: null,
application: null,
},
oneToMany: {},
},
skill: {
manyToOne: {
workspace: null,
application: null,
},
oneToMany: {},
},
commandMenuItem: {
manyToOne: {
workspace: null,
application: null,
availabilityObjectMetadata: {
metadataName: 'objectMetadata',
flatEntityForeignKeyAggregator: null,
foreignKey: 'availabilityObjectMetadataId',
},
},
oneToMany: {},
},
navigationMenuItem: {
manyToOne: {
workspace: null,
userWorkspace: null,
application: null,
targetObjectMetadata: {
metadataName: 'objectMetadata',
flatEntityForeignKeyAggregator: null,
foreignKey: 'targetObjectMetadataId',
},
folder: {
metadataName: 'navigationMenuItem',
flatEntityForeignKeyAggregator: null,
foreignKey: 'folderId',
},
view: {
metadataName: 'view',
flatEntityForeignKeyAggregator: null,
foreignKey: 'viewId',
},
},
oneToMany: {},
},
fieldMetadata: {
manyToOne: {
object: {
metadataName: 'objectMetadata',
flatEntityForeignKeyAggregator: 'fieldIds',
foreignKey: 'objectMetadataId',
},
workspace: null,
application: null,
relationTargetFieldMetadata: {
metadataName: 'fieldMetadata',
flatEntityForeignKeyAggregator: null,
foreignKey: 'relationTargetFieldMetadataId',
},
relationTargetObjectMetadata: {
metadataName: 'objectMetadata',
flatEntityForeignKeyAggregator: null,
foreignKey: 'relationTargetObjectMetadataId',
},
},
oneToMany: {
fieldPermissions: null,
indexFieldMetadatas: null,
viewFields: { metadataName: 'viewField' },
viewFilters: { metadataName: 'viewFilter' },
kanbanAggregateOperationViews: { metadataName: 'view' },
calendarViews: { metadataName: 'view' },
mainGroupByFieldMetadataViews: { metadataName: 'view' },
},
},
objectMetadata: {
manyToOne: {
dataSource: null,
workspace: null,
application: null,
},
oneToMany: {
fields: { metadataName: 'fieldMetadata' },
indexMetadatas: { metadataName: 'index' },
targetRelationFields: { metadataName: 'fieldMetadata' },
objectPermissions: null,
fieldPermissions: null,
views: { metadataName: 'view' },
},
},
view: {
manyToOne: {
objectMetadata: {
foreignKey: 'objectMetadataId',
metadataName: 'objectMetadata',
flatEntityForeignKeyAggregator: 'viewIds',
},
workspace: null,
createdBy: null,
application: null,
calendarFieldMetadata: {
foreignKey: 'calendarFieldMetadataId',
metadataName: 'fieldMetadata',
flatEntityForeignKeyAggregator: 'calendarViewIds',
},
kanbanAggregateOperationFieldMetadata: {
foreignKey: 'kanbanAggregateOperationFieldMetadataId',
metadataName: 'fieldMetadata',
flatEntityForeignKeyAggregator: 'kanbanAggregateOperationViewIds',
},
mainGroupByFieldMetadata: {
foreignKey: 'mainGroupByFieldMetadataId',
metadataName: 'fieldMetadata',
flatEntityForeignKeyAggregator: 'mainGroupByFieldMetadataViewIds',
},
},
oneToMany: {
viewFields: { metadataName: 'viewField' },
viewFilters: { metadataName: 'viewFilter' },
viewFilterGroups: {
metadataName: 'viewFilterGroup',
},
viewGroups: { metadataName: 'viewGroup' },
// @ts-expect-error TODO migrate viewSort to v2
viewSorts: null,
},
},
viewField: {
manyToOne: {
fieldMetadata: {
metadataName: 'fieldMetadata',
flatEntityForeignKeyAggregator: 'viewFieldIds',
foreignKey: 'fieldMetadataId',
},
view: {
metadataName: 'view',
flatEntityForeignKeyAggregator: 'viewFieldIds',
foreignKey: 'viewId',
},
workspace: null,
application: null,
},
oneToMany: {},
},
viewFilter: {
manyToOne: {
fieldMetadata: {
metadataName: 'fieldMetadata',
flatEntityForeignKeyAggregator: 'viewFilterIds',
foreignKey: 'fieldMetadataId',
},
view: {
metadataName: 'view',
flatEntityForeignKeyAggregator: 'viewFilterIds',
foreignKey: 'viewId',
},
viewFilterGroup: {
flatEntityForeignKeyAggregator: 'viewFilterIds',
foreignKey: 'viewFilterGroupId',
metadataName: 'viewFilterGroup',
},
workspace: null,
application: null,
},
oneToMany: {},
},
viewGroup: {
manyToOne: {
view: {
metadataName: 'view',
flatEntityForeignKeyAggregator: 'viewGroupIds',
foreignKey: 'viewId',
},
workspace: null,
application: null,
},
oneToMany: {},
},
index: {
manyToOne: {
objectMetadata: {
metadataName: 'objectMetadata',
flatEntityForeignKeyAggregator: 'indexMetadataIds',
foreignKey: 'objectMetadataId',
},
workspace: null,
application: null,
},
oneToMany: {
indexFieldMetadatas: null,
},
},
logicFunction: {
manyToOne: {
workspace: null,
application: null,
logicFunctionLayer: null,
},
oneToMany: {},
},
role: {
manyToOne: {
workspace: null,
application: null,
},
oneToMany: {
roleTargets: { metadataName: 'roleTarget' },
objectPermissions: null,
permissionFlags: null,
fieldPermissions: null,
rowLevelPermissionPredicates: {
metadataName: 'rowLevelPermissionPredicate',
},
rowLevelPermissionPredicateGroups: {
metadataName: 'rowLevelPermissionPredicateGroup',
},
},
},
roleTarget: {
manyToOne: {
role: {
metadataName: 'role',
flatEntityForeignKeyAggregator: 'roleTargetIds',
foreignKey: 'roleId',
},
apiKey: null,
workspace: null,
application: null,
},
oneToMany: {},
},
pageLayout: {
manyToOne: {
workspace: null,
objectMetadata: {
metadataName: 'objectMetadata',
flatEntityForeignKeyAggregator: null,
foreignKey: 'objectMetadataId',
},
application: null,
},
oneToMany: {
tabs: { metadataName: 'pageLayoutTab' },
},
},
pageLayoutTab: {
manyToOne: {
workspace: null,
pageLayout: {
metadataName: 'pageLayout',
flatEntityForeignKeyAggregator: 'tabIds',
foreignKey: 'pageLayoutId',
},
application: null,
},
oneToMany: {
widgets: { metadataName: 'pageLayoutWidget' },
},
},
pageLayoutWidget: {
manyToOne: {
workspace: null,
pageLayoutTab: {
metadataName: 'pageLayoutTab',
flatEntityForeignKeyAggregator: 'widgetIds',
foreignKey: 'pageLayoutTabId',
},
objectMetadata: {
metadataName: 'objectMetadata',
flatEntityForeignKeyAggregator: null,
foreignKey: 'objectMetadataId',
},
application: null,
},
oneToMany: {},
},
rowLevelPermissionPredicate: {
manyToOne: {
workspace: null,
role: {
metadataName: 'role',
flatEntityForeignKeyAggregator: null,
foreignKey: 'roleId',
},
fieldMetadata: {
metadataName: 'fieldMetadata',
flatEntityForeignKeyAggregator: null,
foreignKey: 'fieldMetadataId',
},
workspaceMemberFieldMetadata: {
metadataName: 'fieldMetadata',
flatEntityForeignKeyAggregator: null,
foreignKey: 'workspaceMemberFieldMetadataId',
},
objectMetadata: {
metadataName: 'objectMetadata',
flatEntityForeignKeyAggregator: null,
foreignKey: 'objectMetadataId',
},
rowLevelPermissionPredicateGroup: {
metadataName: 'rowLevelPermissionPredicateGroup',
flatEntityForeignKeyAggregator: null,
foreignKey: 'rowLevelPermissionPredicateGroupId',
},
application: null,
},
oneToMany: {},
},
rowLevelPermissionPredicateGroup: {
manyToOne: {
objectMetadata: {
metadataName: 'objectMetadata',
flatEntityForeignKeyAggregator: null,
foreignKey: 'objectMetadataId',
},
role: {
metadataName: 'role',
foreignKey: 'roleId',
flatEntityForeignKeyAggregator: null,
},
parentRowLevelPermissionPredicateGroup: {
metadataName: 'rowLevelPermissionPredicateGroup',
foreignKey: 'parentRowLevelPermissionPredicateGroupId',
flatEntityForeignKeyAggregator:
'childRowLevelPermissionPredicateGroupIds',
},
workspace: null,
application: null,
},
oneToMany: {
childRowLevelPermissionPredicateGroups: {
metadataName: 'rowLevelPermissionPredicateGroup',
},
rowLevelPermissionPredicates: {
metadataName: 'rowLevelPermissionPredicate',
},
},
},
viewFilterGroup: {
manyToOne: {
application: null,
parentViewFilterGroup: {
flatEntityForeignKeyAggregator: 'childViewFilterGroupIds',
foreignKey: 'parentViewFilterGroupId',
metadataName: 'viewFilterGroup',
},
view: {
metadataName: 'view',
flatEntityForeignKeyAggregator: 'viewFilterGroupIds',
foreignKey: 'viewId',
},
workspace: null,
},
oneToMany: {
childViewFilterGroups: {
metadataName: 'viewFilterGroup',
},
viewFilters: {
metadataName: 'viewFilter',
},
},
},
frontComponent: {
manyToOne: {
workspace: null,
application: null,
},
oneToMany: {},
},
webhook: {
manyToOne: {
workspace: null,
application: null,
},
oneToMany: {},
},
} as const satisfies MetadataRelationsProperties;
// Note: satisfies with complex mapped types involving nested generics doesn't always catch missing required keys
// eslint-disable-next-line unused-imports/no-unused-vars
type Assertions = [
Expect<
AllMetadataName extends keyof typeof ALL_METADATA_RELATIONS ? true : false
>,
];