From 42c9ae1ebcbf7374d692d5cac5b504e277283bf5 Mon Sep 17 00:00:00 2001 From: Paul Rastoin <45004772+prastoin@users.noreply.github.com> Date: Fri, 2 Jan 2026 16:49:06 +0100 Subject: [PATCH] Centralize metadata relations constant + simplification (#16901) # Introduction As we introduced a new grain on relation extraction thanks to low level `SyncableEntity` and `WorkspaceRelatedEntity` we're able to strictly typesafe extract metadata entity The new constant centralizes both many to one and one to many constants metadata entity constants in a more strictly typesafe way. Remains only the flatEntityForeignKey aggregator which has to be chosen manually across all available targeted flat entity ids properties --- ...ace-flat-cron-trigger-map-cache.service.ts | 6 +- ...atabase-event-trigger-map-cache.service.ts | 6 +- .../field-metadata/field-metadata.entity.ts | 2 +- ...metadata-many-to-one-relations.constant.ts | 142 ------ ...-metadata-relations-properties.constant.ts | 162 ------- .../all-metadata-relations.constant.ts | 436 ++++++++++++++++++ ...quired-metadata-for-validation.constant.ts | 8 +- .../metadata-many-to-one-join-column.type.ts | 8 +- ...many-to-one-related-metadata-names.type.ts | 13 +- ...ity-maps-through-mutation-or-throw.util.ts | 39 +- ...ity-maps-through-mutation-or-throw.util.ts | 39 +- ...etadata-entity-relation-properties.util.ts | 17 + ...data-entity-to-flat-field-metadata.util.ts | 6 +- ...data-entity-to-flat-index-metadata.util.ts | 6 +- ...ata-entity-to-flat-object-metadata.util.ts | 6 +- ...-entity-to-flat-page-layout-widget.util.ts | 6 +- .../workspace-flat-skill-map-cache.service.ts | 6 +- ...> from-skill-entity-to-flat-skill.util.ts} | 4 +- ...ew-field-entity-to-flat-view-field.util.ts | 6 +- ...p-entity-to-flat-view-filter-group.util.ts | 6 +- ...-filter-entity-to-flat-view-filter.util.ts | 6 +- ...ew-group-entity-to-flat-view-group.util.ts | 6 +- .../from-view-entity-to-flat-view.util.ts | 6 +- ...ce-flat-route-trigger-map-cache.service.ts | 6 +- ...level-permission-predicate-group.entity.ts | 6 +- .../entities/view-filter-group.entity.ts | 4 +- ...ce-migration-build-orchestrator.service.ts | 9 +- .../get-field-metadata-entity.mock.ts | 3 +- .../types/ExtractPropertiesThatEndsWithIds.ts | 7 + packages/twenty-shared/src/types/index.ts | 1 + 30 files changed, 569 insertions(+), 409 deletions(-) delete mode 100644 packages/twenty-server/src/engine/metadata-modules/flat-entity/constant/all-metadata-many-to-one-relations.constant.ts delete mode 100644 packages/twenty-server/src/engine/metadata-modules/flat-entity/constant/all-metadata-relations-properties.constant.ts create mode 100644 packages/twenty-server/src/engine/metadata-modules/flat-entity/constant/all-metadata-relations.constant.ts create mode 100644 packages/twenty-server/src/engine/metadata-modules/flat-entity/utils/get-metadata-entity-relation-properties.util.ts rename packages/twenty-server/src/engine/metadata-modules/flat-skill/utils/{transform-skill-entity-to-flat-skill.util.ts => from-skill-entity-to-flat-skill.util.ts} (94%) create mode 100644 packages/twenty-shared/src/types/ExtractPropertiesThatEndsWithIds.ts diff --git a/packages/twenty-server/src/engine/metadata-modules/cron-trigger/services/workspace-flat-cron-trigger-map-cache.service.ts b/packages/twenty-server/src/engine/metadata-modules/cron-trigger/services/workspace-flat-cron-trigger-map-cache.service.ts index fcfd1673ce..62ea587fdf 100644 --- a/packages/twenty-server/src/engine/metadata-modules/cron-trigger/services/workspace-flat-cron-trigger-map-cache.service.ts +++ b/packages/twenty-server/src/engine/metadata-modules/cron-trigger/services/workspace-flat-cron-trigger-map-cache.service.ts @@ -8,9 +8,9 @@ import { WorkspaceCacheProvider } from 'src/engine/workspace-cache/interfaces/wo import { CronTriggerEntity } from 'src/engine/metadata-modules/cron-trigger/entities/cron-trigger.entity'; import { FlatCronTrigger } from 'src/engine/metadata-modules/cron-trigger/types/flat-cron-trigger.type'; -import { ALL_METADATA_RELATION_PROPERTIES } from 'src/engine/metadata-modules/flat-entity/constant/all-metadata-relations-properties.constant'; import { createEmptyFlatEntityMaps } from 'src/engine/metadata-modules/flat-entity/constant/create-empty-flat-entity-maps.constant'; import { FlatEntityMaps } from 'src/engine/metadata-modules/flat-entity/types/flat-entity-maps.type'; +import { getMetadataEntityRelationProperties } from 'src/engine/metadata-modules/flat-entity/utils/get-metadata-entity-relation-properties.util'; import { WorkspaceCache } from 'src/engine/workspace-cache/decorators/workspace-cache.decorator'; import { addFlatEntityToFlatEntityMapsThroughMutationOrThrow } from 'src/engine/workspace-manager/workspace-migration-v2/utils/add-flat-entity-to-flat-entity-maps-through-mutation-or-throw.util'; @@ -41,9 +41,7 @@ export class WorkspaceFlatCronTriggerMapCacheService extends WorkspaceCacheProvi const flatCronTrigger = { ...removePropertiesFromRecord( cronTriggerEntity, - Object.keys( - ALL_METADATA_RELATION_PROPERTIES.cronTrigger, - ) as (keyof typeof ALL_METADATA_RELATION_PROPERTIES.cronTrigger)[], + getMetadataEntityRelationProperties('cronTrigger'), ), universalIdentifier: cronTriggerEntity.universalIdentifier ?? cronTriggerEntity.id, diff --git a/packages/twenty-server/src/engine/metadata-modules/database-event-trigger/services/workspace-flat-database-event-trigger-map-cache.service.ts b/packages/twenty-server/src/engine/metadata-modules/database-event-trigger/services/workspace-flat-database-event-trigger-map-cache.service.ts index 70aee3227f..2c4fbf57ca 100644 --- a/packages/twenty-server/src/engine/metadata-modules/database-event-trigger/services/workspace-flat-database-event-trigger-map-cache.service.ts +++ b/packages/twenty-server/src/engine/metadata-modules/database-event-trigger/services/workspace-flat-database-event-trigger-map-cache.service.ts @@ -8,9 +8,9 @@ import { WorkspaceCacheProvider } from 'src/engine/workspace-cache/interfaces/wo import { DatabaseEventTriggerEntity } from 'src/engine/metadata-modules/database-event-trigger/entities/database-event-trigger.entity'; import { FlatDatabaseEventTrigger } from 'src/engine/metadata-modules/database-event-trigger/types/flat-database-event-trigger.type'; -import { ALL_METADATA_RELATION_PROPERTIES } from 'src/engine/metadata-modules/flat-entity/constant/all-metadata-relations-properties.constant'; import { createEmptyFlatEntityMaps } from 'src/engine/metadata-modules/flat-entity/constant/create-empty-flat-entity-maps.constant'; import { FlatEntityMaps } from 'src/engine/metadata-modules/flat-entity/types/flat-entity-maps.type'; +import { getMetadataEntityRelationProperties } from 'src/engine/metadata-modules/flat-entity/utils/get-metadata-entity-relation-properties.util'; import { WorkspaceCache } from 'src/engine/workspace-cache/decorators/workspace-cache.decorator'; import { addFlatEntityToFlatEntityMapsThroughMutationOrThrow } from 'src/engine/workspace-manager/workspace-migration-v2/utils/add-flat-entity-to-flat-entity-maps-through-mutation-or-throw.util'; @@ -42,9 +42,7 @@ export class WorkspaceFlatDatabaseEventTriggerMapCacheService extends WorkspaceC const flatDatabaseEventTrigger = { ...removePropertiesFromRecord( databaseEventTriggerEntity, - Object.keys( - ALL_METADATA_RELATION_PROPERTIES.databaseEventTrigger, - ) as (keyof typeof ALL_METADATA_RELATION_PROPERTIES.databaseEventTrigger)[], + getMetadataEntityRelationProperties('databaseEventTrigger'), ), universalIdentifier: databaseEventTriggerEntity.universalIdentifier ?? diff --git a/packages/twenty-server/src/engine/metadata-modules/field-metadata/field-metadata.entity.ts b/packages/twenty-server/src/engine/metadata-modules/field-metadata/field-metadata.entity.ts index e64b99fa76..5ed63d78cb 100644 --- a/packages/twenty-server/src/engine/metadata-modules/field-metadata/field-metadata.entity.ts +++ b/packages/twenty-server/src/engine/metadata-modules/field-metadata/field-metadata.entity.ts @@ -177,7 +177,7 @@ export class FieldMetadataEntity< cascade: true, }, ) - indexFieldMetadatas: Relation; + indexFieldMetadatas: Relation; @CreateDateColumn({ type: 'timestamptz' }) createdAt: Date; diff --git a/packages/twenty-server/src/engine/metadata-modules/flat-entity/constant/all-metadata-many-to-one-relations.constant.ts b/packages/twenty-server/src/engine/metadata-modules/flat-entity/constant/all-metadata-many-to-one-relations.constant.ts deleted file mode 100644 index 50fa32fe12..0000000000 --- a/packages/twenty-server/src/engine/metadata-modules/flat-entity/constant/all-metadata-many-to-one-relations.constant.ts +++ /dev/null @@ -1,142 +0,0 @@ -import { type AllMetadataName } from 'twenty-shared/metadata'; -import { type ExtractPropertiesThatEndsWithId } from 'twenty-shared/types'; - -import { type MetadataEntity } from 'src/engine/metadata-modules/flat-entity/types/metadata-entity.type'; -import { type MetadataFlatEntity } from 'src/engine/metadata-modules/flat-entity/types/metadata-flat-entity.type'; - -type ExtractEntityRelations> = { - [K in ExtractPropertiesThatEndsWithId]: K; -}; - -type MetadataRelatedMetadataNames = - keyof ExtractEntityRelations>; - -type MetadataNameAndRelations = { - [TSourceMetadataName in AllMetadataName]: MetadataRelatedMetadataNames extends never - ? Record - : { - [K in MetadataRelatedMetadataNames]?: { - [TTargetMetadataName in AllMetadataName]?: { - metadataName: TTargetMetadataName; - flatEntityForeignKeyAggregator: keyof MetadataFlatEntity; - }; - }[AllMetadataName]; - }; -}; - -export const ALL_METADATA_RELATED_METADATA_BY_FOREIGN_KEY = { - fieldMetadata: { - objectMetadataId: { - metadataName: 'objectMetadata', - flatEntityForeignKeyAggregator: 'fieldMetadataIds', - }, - }, - objectMetadata: {}, - view: { - kanbanAggregateOperationFieldMetadataId: { - metadataName: 'fieldMetadata', - flatEntityForeignKeyAggregator: 'kanbanAggregateOperationViewIds', - }, - calendarFieldMetadataId: { - metadataName: 'fieldMetadata', - flatEntityForeignKeyAggregator: 'calendarViewIds', - }, - mainGroupByFieldMetadataId: { - metadataName: 'fieldMetadata', - flatEntityForeignKeyAggregator: 'mainGroupByFieldMetadataViewIds', - }, - objectMetadataId: { - metadataName: 'objectMetadata', - flatEntityForeignKeyAggregator: 'viewIds', - }, - }, - viewField: { - viewId: { - metadataName: 'view', - flatEntityForeignKeyAggregator: 'viewFieldIds', - }, - fieldMetadataId: { - metadataName: 'fieldMetadata', - flatEntityForeignKeyAggregator: 'viewFieldIds', - }, - }, - viewGroup: { - viewId: { - metadataName: 'view', - flatEntityForeignKeyAggregator: 'viewGroupIds', - }, - }, - index: { - objectMetadataId: { - metadataName: 'objectMetadata', - flatEntityForeignKeyAggregator: 'indexMetadataIds', - }, - }, - serverlessFunction: {}, - cronTrigger: { - serverlessFunctionId: { - metadataName: 'serverlessFunction', - flatEntityForeignKeyAggregator: 'cronTriggerIds', - }, - }, - databaseEventTrigger: { - serverlessFunctionId: { - metadataName: 'serverlessFunction', - flatEntityForeignKeyAggregator: 'databaseEventTriggerIds', - }, - }, - routeTrigger: { - serverlessFunctionId: { - metadataName: 'serverlessFunction', - flatEntityForeignKeyAggregator: 'routeTriggerIds', - }, - }, - viewFilter: { - viewId: { - metadataName: 'view', - flatEntityForeignKeyAggregator: 'viewFilterIds', - }, - fieldMetadataId: { - metadataName: 'fieldMetadata', - flatEntityForeignKeyAggregator: 'viewFilterIds', - }, - viewFilterGroupId: { - metadataName: 'viewFilterGroup', - flatEntityForeignKeyAggregator: 'viewFilterIds', - }, - }, - role: {}, - roleTarget: { - roleId: { - metadataName: 'role', - flatEntityForeignKeyAggregator: 'roleTargetIds', - }, - }, - agent: {}, - skill: {}, - pageLayout: {}, - pageLayoutWidget: { - pageLayoutTabId: { - metadataName: 'pageLayoutTab', - flatEntityForeignKeyAggregator: 'widgetIds', - }, - }, - pageLayoutTab: { - pageLayoutId: { - metadataName: 'pageLayout', - flatEntityForeignKeyAggregator: 'tabIds', - }, - }, - rowLevelPermissionPredicate: {}, - rowLevelPermissionPredicateGroup: {}, - viewFilterGroup: { - viewId: { - metadataName: 'view', - flatEntityForeignKeyAggregator: 'viewFilterGroupIds', - }, - parentViewFilterGroupId: { - metadataName: 'viewFilterGroup', - flatEntityForeignKeyAggregator: 'childViewFilterGroupIds', - }, - }, -} as const satisfies MetadataNameAndRelations; diff --git a/packages/twenty-server/src/engine/metadata-modules/flat-entity/constant/all-metadata-relations-properties.constant.ts b/packages/twenty-server/src/engine/metadata-modules/flat-entity/constant/all-metadata-relations-properties.constant.ts deleted file mode 100644 index f6829e4ca8..0000000000 --- a/packages/twenty-server/src/engine/metadata-modules/flat-entity/constant/all-metadata-relations-properties.constant.ts +++ /dev/null @@ -1,162 +0,0 @@ -import { type AllMetadataName } from 'twenty-shared/metadata'; - -import { type ExtractEntityRelatedEntityProperties } from 'src/engine/metadata-modules/flat-entity/types/extract-entity-related-entity-properties.type'; -import { type MetadataEntity } from 'src/engine/metadata-modules/flat-entity/types/metadata-entity.type'; - -export const ALL_METADATA_RELATION_PROPERTIES = { - agent: { - workspace: true, - application: true, - }, - skill: { - workspace: true, - application: true, - }, - fieldMetadata: { - relationTargetFieldMetadata: true, - relationTargetObjectMetadata: true, - fieldPermissions: true, - indexFieldMetadatas: true, - object: true, - viewFields: true, - viewFilters: true, - kanbanAggregateOperationViews: true, - calendarViews: true, - mainGroupByFieldMetadataViews: true, - workspace: true, - application: true, - }, - objectMetadata: { - fields: true, - indexMetadatas: true, - targetRelationFields: true, - dataSource: true, - objectPermissions: true, - fieldPermissions: true, - views: true, - workspace: true, - application: true, - }, - view: { - objectMetadata: true, - viewFields: true, - viewFilters: true, - viewFilterGroups: true, - viewGroups: true, - viewSorts: true, - workspace: true, - createdBy: true, - application: true, - calendarFieldMetadata: true, - kanbanAggregateOperationFieldMetadata: true, - mainGroupByFieldMetadata: true, - }, - viewField: { - fieldMetadata: true, - view: true, - workspace: true, - application: true, - }, - viewFilter: { - fieldMetadata: true, - view: true, - viewFilterGroup: true, - workspace: true, - application: true, - }, - viewGroup: { - view: true, - workspace: true, - application: true, - }, - index: { - objectMetadata: true, - indexFieldMetadatas: true, - workspace: true, - application: true, - }, - serverlessFunction: { - cronTriggers: true, - databaseEventTriggers: true, - routeTriggers: true, - serverlessFunctionLayer: true, - workspace: true, - application: true, - }, - cronTrigger: { - serverlessFunction: true, - workspace: true, - application: true, - }, - databaseEventTrigger: { - serverlessFunction: true, - workspace: true, - application: true, - }, - routeTrigger: { - serverlessFunction: true, - workspace: true, - application: true, - }, - role: { - roleTargets: true, - objectPermissions: true, - permissionFlags: true, - fieldPermissions: true, - workspace: true, - application: true, - }, - roleTarget: { - role: true, - apiKey: true, - workspace: true, - application: true, - }, - pageLayout: { - workspace: true, - objectMetadata: true, - tabs: true, - application: true, - }, - pageLayoutTab: { - workspace: true, - pageLayout: true, - widgets: true, - application: true, - }, - pageLayoutWidget: { - workspace: true, - pageLayoutTab: true, - objectMetadata: true, - application: true, - }, - rowLevelPermissionPredicate: { - workspace: true, - role: true, - fieldMetadata: true, - workspaceMemberFieldMetadata: true, - objectMetadata: true, - rowLevelPermissionPredicateGroup: true, - application: true, - }, - rowLevelPermissionPredicateGroup: { - workspace: true, - role: true, - parentRowLevelPermissionPredicateGroup: true, - childRowLevelPermissionPredicateGroups: true, - rowLevelPermissionPredicates: true, - application: true, - }, - viewFilterGroup: { - view: true, - viewFilters: true, - parentViewFilterGroup: true, - childViewFilterGroups: true, - workspace: true, - application: true, - }, -} as const satisfies { - [TName in AllMetadataName]: { - [P in ExtractEntityRelatedEntityProperties>]: true; - }; -}; diff --git a/packages/twenty-server/src/engine/metadata-modules/flat-entity/constant/all-metadata-relations.constant.ts b/packages/twenty-server/src/engine/metadata-modules/flat-entity/constant/all-metadata-relations.constant.ts new file mode 100644 index 0000000000..a9eccb54fe --- /dev/null +++ b/packages/twenty-server/src/engine/metadata-modules/flat-entity/constant/all-metadata-relations.constant.ts @@ -0,0 +1,436 @@ +import { type AllMetadataName } from 'twenty-shared/metadata'; +import { type Expect } from 'twenty-shared/testing'; +import { + type ExtractPropertiesThatEndsWithId, + type ExtractPropertiesThatEndsWithIds, +} from 'twenty-shared/types'; +import { type Relation } from 'typeorm'; + +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 MetadataFlatEntity } from 'src/engine/metadata-modules/flat-entity/types/metadata-flat-entity.type'; +import { type SyncableEntity } from 'src/engine/workspace-manager/workspace-sync/types/syncable-entity.interface'; + +type ManyToOneRelationValue< + TSourceMetadataName extends AllMetadataName, + TRelationProperty extends ExtractEntityManyToOneEntityRelationProperties< + MetadataEntity + >, +> = + NonNullable< + MetadataEntity[TRelationProperty] + > extends Relation + ? { + metadataName: FromMetadataEntityToMetadataName; + flatEntityForeignKeyAggregator: ExtractPropertiesThatEndsWithIds< + MetadataFlatEntity> + // Note: In the best of the world should not be nullable, entities should always declare inverside keys + > | null; + foreignKey: ExtractPropertiesThatEndsWithId< + MetadataFlatEntity, + 'id' | 'workspaceId' + >; + } + : null; + +type OneToManyRelationValue< + TSourceMetadataName extends AllMetadataName, + TRelationProperty extends ExtractEntityOneToManyEntityRelationProperties< + MetadataEntity + >, +> = + MetadataEntity[TRelationProperty] extends Relation< + (infer TTargetEntity extends SyncableEntity)[] + > + ? { + metadataName: FromMetadataEntityToMetadataName; + } + : null; + +type MetadataRelationsProperties = { + [TSourceMetadataName in AllMetadataName]: { + manyToOne: { + [TRelationProperty in ExtractEntityManyToOneEntityRelationProperties< + MetadataEntity + >]: ManyToOneRelationValue; + }; + oneToMany: { + [TRelationProperty in ExtractEntityOneToManyEntityRelationProperties< + MetadataEntity + >]: OneToManyRelationValue; + }; + }; +}; + +export const ALL_METADATA_RELATIONS = { + agent: { + manyToOne: { + workspace: null, + application: null, + }, + oneToMany: {}, + }, + skill: { + manyToOne: { + workspace: null, + application: null, + }, + oneToMany: {}, + }, + fieldMetadata: { + manyToOne: { + object: { + metadataName: 'objectMetadata', + flatEntityForeignKeyAggregator: 'fieldMetadataIds', + 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, + }, + }, + serverlessFunction: { + manyToOne: { + workspace: null, + application: null, + serverlessFunctionLayer: null, + }, + oneToMany: { + cronTriggers: { metadataName: 'cronTrigger' }, + databaseEventTriggers: { metadataName: 'databaseEventTrigger' }, + routeTriggers: { metadataName: 'routeTrigger' }, + }, + }, + cronTrigger: { + manyToOne: { + serverlessFunction: { + metadataName: 'serverlessFunction', + flatEntityForeignKeyAggregator: 'cronTriggerIds', + foreignKey: 'serverlessFunctionId', + }, + workspace: null, + application: null, + }, + oneToMany: {}, + }, + databaseEventTrigger: { + manyToOne: { + serverlessFunction: { + metadataName: 'serverlessFunction', + flatEntityForeignKeyAggregator: 'databaseEventTriggerIds', + foreignKey: 'serverlessFunctionId', + }, + workspace: null, + application: null, + }, + oneToMany: {}, + }, + routeTrigger: { + manyToOne: { + serverlessFunction: { + metadataName: 'serverlessFunction', + flatEntityForeignKeyAggregator: 'routeTriggerIds', + foreignKey: 'serverlessFunctionId', + }, + workspace: null, + application: null, + }, + oneToMany: {}, + }, + role: { + manyToOne: { + workspace: null, + application: null, + }, + oneToMany: { + roleTargets: { metadataName: 'roleTarget' }, + objectPermissions: null, + permissionFlags: null, + fieldPermissions: null, + }, + }, + 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: { + workspace: null, + role: { + metadataName: 'role', + foreignKey: 'roleId', + flatEntityForeignKeyAggregator: null, + }, + parentRowLevelPermissionPredicateGroup: { + metadataName: 'rowLevelPermissionPredicateGroup', + foreignKey: 'parentRowLevelPermissionPredicateGroupId', + flatEntityForeignKeyAggregator: + 'childRowLevelPermissionPredicateGroupIds', + }, + 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', + }, + }, + }, +} 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 + >, +]; diff --git a/packages/twenty-server/src/engine/metadata-modules/flat-entity/constant/all-metadata-required-metadata-for-validation.constant.ts b/packages/twenty-server/src/engine/metadata-modules/flat-entity/constant/all-metadata-required-metadata-for-validation.constant.ts index a34a72ed3a..0b484a8239 100644 --- a/packages/twenty-server/src/engine/metadata-modules/flat-entity/constant/all-metadata-required-metadata-for-validation.constant.ts +++ b/packages/twenty-server/src/engine/metadata-modules/flat-entity/constant/all-metadata-required-metadata-for-validation.constant.ts @@ -4,7 +4,7 @@ import { type MetadataManyToOneRelatedMetadataNames } from 'src/engine/metadata- type MetadataRequiredForValidation = { [T in AllMetadataName]: Record< - MetadataManyToOneRelatedMetadataNames, + Exclude, T>, true > & { [K in Exclude]?: true; @@ -52,7 +52,6 @@ export const ALL_METADATA_REQUIRED_METADATA_FOR_VALIDATION = { }, viewFilterGroup: { view: true, - viewFilterGroup: true, }, role: {}, roleTarget: { @@ -63,11 +62,14 @@ export const ALL_METADATA_REQUIRED_METADATA_FOR_VALIDATION = { role: true, }, skill: {}, - pageLayout: {}, + pageLayout: { + objectMetadata: true, + }, pageLayoutTab: { pageLayout: true, }, pageLayoutWidget: { + objectMetadata: true, pageLayoutTab: true, }, rowLevelPermissionPredicate: { diff --git a/packages/twenty-server/src/engine/metadata-modules/flat-entity/types/metadata-many-to-one-join-column.type.ts b/packages/twenty-server/src/engine/metadata-modules/flat-entity/types/metadata-many-to-one-join-column.type.ts index 77025ccdc3..3cd2f0b924 100644 --- a/packages/twenty-server/src/engine/metadata-modules/flat-entity/types/metadata-many-to-one-join-column.type.ts +++ b/packages/twenty-server/src/engine/metadata-modules/flat-entity/types/metadata-many-to-one-join-column.type.ts @@ -1,6 +1,10 @@ import { type AllMetadataName } from 'twenty-shared/metadata'; -import { type ALL_METADATA_RELATED_METADATA_BY_FOREIGN_KEY } from 'src/engine/metadata-modules/flat-entity/constant/all-metadata-many-to-one-relations.constant'; +import { type ALL_METADATA_RELATIONS } from 'src/engine/metadata-modules/flat-entity/constant/all-metadata-relations.constant'; + +type ExtractForeignKeys = { + [K in keyof T]: T[K] extends { foreignKey: infer FK } ? FK : never; +}[keyof T]; export type MetadataManyToOneJoinColumn = - keyof (typeof ALL_METADATA_RELATED_METADATA_BY_FOREIGN_KEY)[T]; + ExtractForeignKeys<(typeof ALL_METADATA_RELATIONS)[T]['manyToOne']>; diff --git a/packages/twenty-server/src/engine/metadata-modules/flat-entity/types/metadata-many-to-one-related-metadata-names.type.ts b/packages/twenty-server/src/engine/metadata-modules/flat-entity/types/metadata-many-to-one-related-metadata-names.type.ts index d05d4d2bdd..94abeb08cf 100644 --- a/packages/twenty-server/src/engine/metadata-modules/flat-entity/types/metadata-many-to-one-related-metadata-names.type.ts +++ b/packages/twenty-server/src/engine/metadata-modules/flat-entity/types/metadata-many-to-one-related-metadata-names.type.ts @@ -1,10 +1,13 @@ import { type AllMetadataName } from 'twenty-shared/metadata'; -import { type ALL_METADATA_RELATED_METADATA_BY_FOREIGN_KEY } from 'src/engine/metadata-modules/flat-entity/constant/all-metadata-many-to-one-relations.constant'; -import { type MetadataManyToOneJoinColumn } from 'src/engine/metadata-modules/flat-entity/types/metadata-many-to-one-join-column.type'; +import { type ALL_METADATA_RELATIONS } from 'src/engine/metadata-modules/flat-entity/constant/all-metadata-relations.constant'; + +type ExtractMetadataNames = { + [K in keyof T]: T[K] extends { metadataName: infer M } ? M : never; +}[keyof T]; export type MetadataManyToOneRelatedMetadataNames = Extract< - (typeof ALL_METADATA_RELATED_METADATA_BY_FOREIGN_KEY)[T][MetadataManyToOneJoinColumn], - { metadataName: AllMetadataName } - >['metadataName']; + ExtractMetadataNames<(typeof ALL_METADATA_RELATIONS)[T]['manyToOne']>, + AllMetadataName + >; diff --git a/packages/twenty-server/src/engine/metadata-modules/flat-entity/utils/add-flat-entity-to-flat-entity-and-related-entity-maps-through-mutation-or-throw.util.ts b/packages/twenty-server/src/engine/metadata-modules/flat-entity/utils/add-flat-entity-to-flat-entity-and-related-entity-maps-through-mutation-or-throw.util.ts index db7bf718be..22a1ebc0da 100644 --- a/packages/twenty-server/src/engine/metadata-modules/flat-entity/utils/add-flat-entity-to-flat-entity-and-related-entity-maps-through-mutation-or-throw.util.ts +++ b/packages/twenty-server/src/engine/metadata-modules/flat-entity/utils/add-flat-entity-to-flat-entity-and-related-entity-maps-through-mutation-or-throw.util.ts @@ -1,7 +1,7 @@ import { type AllMetadataName } from 'twenty-shared/metadata'; import { isDefined } from 'twenty-shared/utils'; -import { ALL_METADATA_RELATED_METADATA_BY_FOREIGN_KEY } from 'src/engine/metadata-modules/flat-entity/constant/all-metadata-many-to-one-relations.constant'; +import { ALL_METADATA_RELATIONS } from 'src/engine/metadata-modules/flat-entity/constant/all-metadata-relations.constant'; import { FlatEntityMapsException, FlatEntityMapsExceptionCode, @@ -36,22 +36,29 @@ export const addFlatEntityToFlatEntityAndRelatedEntityMapsThroughMutationOrThrow flatEntityAndRelatedMapsToMutate[flatEntityMapsKey], }); - const manyToOneRelatedMetadataName = Object.entries( - ALL_METADATA_RELATED_METADATA_BY_FOREIGN_KEY[metadataName], - ) as Array< - [ - keyof MetadataFlatEntity, - { - metadataName: AllMetadataName; - flatEntityForeignKeyAggregator: keyof MetadataFlatEntity; - }, - ] - >; + const manyToOneRelations = Object.values( + ALL_METADATA_RELATIONS[metadataName].manyToOne, + ) as Array<{ + metadataName: AllMetadataName; + flatEntityForeignKeyAggregator: keyof MetadataFlatEntity; + foreignKey: keyof MetadataFlatEntity; + } | null>; + + for (const relation of manyToOneRelations) { + if (!isDefined(relation)) { + continue; + } + + const { + metadataName: relatedMetadataName, + flatEntityForeignKeyAggregator, + foreignKey, + } = relation; + + if (!isDefined(flatEntityForeignKeyAggregator)) { + continue; + } - for (const [ - foreignKey, - { metadataName: relatedMetadataName, flatEntityForeignKeyAggregator }, - ] of manyToOneRelatedMetadataName) { const relatedFlatEntityMapsKey = getMetadataFlatEntityMapsKey(relatedMetadataName); const relatedFlatEntityMetadataMaps = flatEntityAndRelatedMapsToMutate[ diff --git a/packages/twenty-server/src/engine/metadata-modules/flat-entity/utils/delete-flat-entity-from-flat-entity-and-related-entity-maps-through-mutation-or-throw.util.ts b/packages/twenty-server/src/engine/metadata-modules/flat-entity/utils/delete-flat-entity-from-flat-entity-and-related-entity-maps-through-mutation-or-throw.util.ts index 986f36c588..48dfb38396 100644 --- a/packages/twenty-server/src/engine/metadata-modules/flat-entity/utils/delete-flat-entity-from-flat-entity-and-related-entity-maps-through-mutation-or-throw.util.ts +++ b/packages/twenty-server/src/engine/metadata-modules/flat-entity/utils/delete-flat-entity-from-flat-entity-and-related-entity-maps-through-mutation-or-throw.util.ts @@ -1,7 +1,7 @@ import { type AllMetadataName } from 'twenty-shared/metadata'; import { isDefined } from 'twenty-shared/utils'; -import { ALL_METADATA_RELATED_METADATA_BY_FOREIGN_KEY } from 'src/engine/metadata-modules/flat-entity/constant/all-metadata-many-to-one-relations.constant'; +import { ALL_METADATA_RELATIONS } from 'src/engine/metadata-modules/flat-entity/constant/all-metadata-relations.constant'; import { FlatEntityMapsException, FlatEntityMapsExceptionCode, @@ -38,22 +38,29 @@ export const deleteFlatEntityFromFlatEntityAndRelatedEntityMapsThroughMutationOr flatEntityAndRelatedMapsToMutate[flatEntityMapsKey], }); - const manyToOneRelatedMetadataName = Object.entries( - ALL_METADATA_RELATED_METADATA_BY_FOREIGN_KEY[metadataName], - ) as Array< - [ - keyof MetadataFlatEntity, - { - metadataName: AllMetadataName; - flatEntityForeignKeyAggregator: keyof MetadataFlatEntity; - }, - ] - >; + const manyToOneRelations = Object.values( + ALL_METADATA_RELATIONS[metadataName].manyToOne, + ) as Array<{ + metadataName: AllMetadataName; + flatEntityForeignKeyAggregator: keyof MetadataFlatEntity; + foreignKey: keyof MetadataFlatEntity; + } | null>; + + for (const relation of manyToOneRelations) { + if (!isDefined(relation)) { + continue; + } + + const { + metadataName: relatedMetadataName, + flatEntityForeignKeyAggregator, + foreignKey, + } = relation; + + if (!isDefined(flatEntityForeignKeyAggregator)) { + continue; + } - for (const [ - foreignKey, - { metadataName: relatedMetadataName, flatEntityForeignKeyAggregator }, - ] of manyToOneRelatedMetadataName) { const relatedFlatEntityMapsKey = getMetadataFlatEntityMapsKey(relatedMetadataName); diff --git a/packages/twenty-server/src/engine/metadata-modules/flat-entity/utils/get-metadata-entity-relation-properties.util.ts b/packages/twenty-server/src/engine/metadata-modules/flat-entity/utils/get-metadata-entity-relation-properties.util.ts new file mode 100644 index 0000000000..34e83528c6 --- /dev/null +++ b/packages/twenty-server/src/engine/metadata-modules/flat-entity/utils/get-metadata-entity-relation-properties.util.ts @@ -0,0 +1,17 @@ +import { type AllMetadataName } from 'twenty-shared/metadata'; + +import { ALL_METADATA_RELATIONS } from 'src/engine/metadata-modules/flat-entity/constant/all-metadata-relations.constant'; + +export const getMetadataEntityRelationProperties = ( + metadataName: T, +) => { + const relationProperties = ALL_METADATA_RELATIONS[metadataName]; + + return [ + ...Object.keys(relationProperties.manyToOne), + ...Object.keys(relationProperties.oneToMany), + ] as ( + | keyof (typeof ALL_METADATA_RELATIONS)[T]['manyToOne'] + | keyof (typeof ALL_METADATA_RELATIONS)[T]['oneToMany'] + )[]; +}; diff --git a/packages/twenty-server/src/engine/metadata-modules/flat-field-metadata/utils/from-field-metadata-entity-to-flat-field-metadata.util.ts b/packages/twenty-server/src/engine/metadata-modules/flat-field-metadata/utils/from-field-metadata-entity-to-flat-field-metadata.util.ts index 5ea16b872d..af89d6ed7d 100644 --- a/packages/twenty-server/src/engine/metadata-modules/flat-field-metadata/utils/from-field-metadata-entity-to-flat-field-metadata.util.ts +++ b/packages/twenty-server/src/engine/metadata-modules/flat-field-metadata/utils/from-field-metadata-entity-to-flat-field-metadata.util.ts @@ -2,7 +2,7 @@ import { type FieldMetadataType } from 'twenty-shared/types'; import { removePropertiesFromRecord } from 'twenty-shared/utils'; import { type FieldMetadataEntity } from 'src/engine/metadata-modules/field-metadata/field-metadata.entity'; -import { ALL_METADATA_RELATION_PROPERTIES } from 'src/engine/metadata-modules/flat-entity/constant/all-metadata-relations-properties.constant'; +import { getMetadataEntityRelationProperties } from 'src/engine/metadata-modules/flat-entity/utils/get-metadata-entity-relation-properties.util'; import { type FlatFieldMetadata } from 'src/engine/metadata-modules/flat-field-metadata/types/flat-field-metadata.type'; export const fromFieldMetadataEntityToFlatFieldMetadata = < @@ -13,9 +13,7 @@ export const fromFieldMetadataEntityToFlatFieldMetadata = < ): FlatFieldMetadata => { const fieldMetadataWithoutRelations = removePropertiesFromRecord( fieldMetadataEntity, - Object.keys( - ALL_METADATA_RELATION_PROPERTIES.fieldMetadata, - ) as (keyof typeof ALL_METADATA_RELATION_PROPERTIES.fieldMetadata)[], + getMetadataEntityRelationProperties('fieldMetadata'), ); return { diff --git a/packages/twenty-server/src/engine/metadata-modules/flat-index-metadata/utils/from-index-metadata-entity-to-flat-index-metadata.util.ts b/packages/twenty-server/src/engine/metadata-modules/flat-index-metadata/utils/from-index-metadata-entity-to-flat-index-metadata.util.ts index e19f11b0e1..b123a8b2f2 100644 --- a/packages/twenty-server/src/engine/metadata-modules/flat-index-metadata/utils/from-index-metadata-entity-to-flat-index-metadata.util.ts +++ b/packages/twenty-server/src/engine/metadata-modules/flat-index-metadata/utils/from-index-metadata-entity-to-flat-index-metadata.util.ts @@ -1,6 +1,6 @@ import { removePropertiesFromRecord } from 'twenty-shared/utils'; -import { ALL_METADATA_RELATION_PROPERTIES } from 'src/engine/metadata-modules/flat-entity/constant/all-metadata-relations-properties.constant'; +import { getMetadataEntityRelationProperties } from 'src/engine/metadata-modules/flat-entity/utils/get-metadata-entity-relation-properties.util'; import { type FlatIndexMetadata } from 'src/engine/metadata-modules/flat-index-metadata/types/flat-index-metadata.type'; import { type IndexMetadataEntity } from 'src/engine/metadata-modules/index-metadata/index-metadata.entity'; @@ -9,9 +9,7 @@ export const fromIndexMetadataEntityToFlatIndexMetadata = ( ): FlatIndexMetadata => { const indexMetadataEntityWithoutRelations = removePropertiesFromRecord( indexMetadataEntity, - Object.keys( - ALL_METADATA_RELATION_PROPERTIES.index, - ) as (keyof typeof ALL_METADATA_RELATION_PROPERTIES.index)[], + getMetadataEntityRelationProperties('index'), ); return { diff --git a/packages/twenty-server/src/engine/metadata-modules/flat-object-metadata/utils/from-object-metadata-entity-to-flat-object-metadata.util.ts b/packages/twenty-server/src/engine/metadata-modules/flat-object-metadata/utils/from-object-metadata-entity-to-flat-object-metadata.util.ts index 352149e3cd..b655e23394 100644 --- a/packages/twenty-server/src/engine/metadata-modules/flat-object-metadata/utils/from-object-metadata-entity-to-flat-object-metadata.util.ts +++ b/packages/twenty-server/src/engine/metadata-modules/flat-object-metadata/utils/from-object-metadata-entity-to-flat-object-metadata.util.ts @@ -1,6 +1,6 @@ import { removePropertiesFromRecord } from 'twenty-shared/utils'; -import { ALL_METADATA_RELATION_PROPERTIES } from 'src/engine/metadata-modules/flat-entity/constant/all-metadata-relations-properties.constant'; +import { getMetadataEntityRelationProperties } from 'src/engine/metadata-modules/flat-entity/utils/get-metadata-entity-relation-properties.util'; import { type FlatObjectMetadata } from 'src/engine/metadata-modules/flat-object-metadata/types/flat-object-metadata.type'; import { type ObjectMetadataEntity } from 'src/engine/metadata-modules/object-metadata/object-metadata.entity'; @@ -9,9 +9,7 @@ export const fromObjectMetadataEntityToFlatObjectMetadata = ( ): FlatObjectMetadata => { const objectMetadataEntityWithoutRelations = removePropertiesFromRecord( objectMetadataEntity, - Object.keys( - ALL_METADATA_RELATION_PROPERTIES.objectMetadata, - ) as (keyof typeof ALL_METADATA_RELATION_PROPERTIES.objectMetadata)[], + getMetadataEntityRelationProperties('objectMetadata'), ); return { diff --git a/packages/twenty-server/src/engine/metadata-modules/flat-page-layout-widget/utils/from-page-layout-widget-entity-to-flat-page-layout-widget.util.ts b/packages/twenty-server/src/engine/metadata-modules/flat-page-layout-widget/utils/from-page-layout-widget-entity-to-flat-page-layout-widget.util.ts index 5445d8e462..8e801ab2c8 100644 --- a/packages/twenty-server/src/engine/metadata-modules/flat-page-layout-widget/utils/from-page-layout-widget-entity-to-flat-page-layout-widget.util.ts +++ b/packages/twenty-server/src/engine/metadata-modules/flat-page-layout-widget/utils/from-page-layout-widget-entity-to-flat-page-layout-widget.util.ts @@ -1,6 +1,6 @@ import { removePropertiesFromRecord } from 'twenty-shared/utils'; -import { ALL_METADATA_RELATION_PROPERTIES } from 'src/engine/metadata-modules/flat-entity/constant/all-metadata-relations-properties.constant'; +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 { type PageLayoutWidgetEntity } from 'src/engine/metadata-modules/page-layout-widget/entities/page-layout-widget.entity'; @@ -9,9 +9,7 @@ export const fromPageLayoutWidgetEntityToFlatPageLayoutWidget = ( ): FlatPageLayoutWidget => { const pageLayoutWidgetEntityWithoutRelations = removePropertiesFromRecord( pageLayoutWidgetEntity, - Object.keys( - ALL_METADATA_RELATION_PROPERTIES.pageLayoutWidget, - ) as (keyof typeof ALL_METADATA_RELATION_PROPERTIES.pageLayoutWidget)[], + getMetadataEntityRelationProperties('pageLayoutWidget'), ); return { diff --git a/packages/twenty-server/src/engine/metadata-modules/flat-skill/services/workspace-flat-skill-map-cache.service.ts b/packages/twenty-server/src/engine/metadata-modules/flat-skill/services/workspace-flat-skill-map-cache.service.ts index a7e5456429..5a71f03c66 100644 --- a/packages/twenty-server/src/engine/metadata-modules/flat-skill/services/workspace-flat-skill-map-cache.service.ts +++ b/packages/twenty-server/src/engine/metadata-modules/flat-skill/services/workspace-flat-skill-map-cache.service.ts @@ -5,9 +5,9 @@ import { Repository } from 'typeorm'; import { WorkspaceCacheProvider } from 'src/engine/workspace-cache/interfaces/workspace-cache-provider.service'; -import { type FlatSkillMaps } from 'src/engine/metadata-modules/flat-skill/types/flat-skill-maps.type'; -import { transformSkillEntityToFlatSkill } from 'src/engine/metadata-modules/flat-skill/utils/transform-skill-entity-to-flat-skill.util'; import { createEmptyFlatEntityMaps } from 'src/engine/metadata-modules/flat-entity/constant/create-empty-flat-entity-maps.constant'; +import { type FlatSkillMaps } from 'src/engine/metadata-modules/flat-skill/types/flat-skill-maps.type'; +import { fromSkillEntityToFlatSkill } from 'src/engine/metadata-modules/flat-skill/utils/from-skill-entity-to-flat-skill.util'; import { SkillEntity } from 'src/engine/metadata-modules/skill/entities/skill.entity'; import { WorkspaceCache } from 'src/engine/workspace-cache/decorators/workspace-cache.decorator'; import { addFlatEntityToFlatEntityMapsThroughMutationOrThrow } from 'src/engine/workspace-manager/workspace-migration-v2/utils/add-flat-entity-to-flat-entity-maps-through-mutation-or-throw.util'; @@ -31,7 +31,7 @@ export class WorkspaceFlatSkillMapCacheService extends WorkspaceCacheProvider { return { diff --git a/packages/twenty-server/src/engine/metadata-modules/flat-view-field/utils/from-view-field-entity-to-flat-view-field.util.ts b/packages/twenty-server/src/engine/metadata-modules/flat-view-field/utils/from-view-field-entity-to-flat-view-field.util.ts index 499319e9e3..6e10109b30 100644 --- a/packages/twenty-server/src/engine/metadata-modules/flat-view-field/utils/from-view-field-entity-to-flat-view-field.util.ts +++ b/packages/twenty-server/src/engine/metadata-modules/flat-view-field/utils/from-view-field-entity-to-flat-view-field.util.ts @@ -1,6 +1,6 @@ import { removePropertiesFromRecord } from 'twenty-shared/utils'; -import { ALL_METADATA_RELATION_PROPERTIES } from 'src/engine/metadata-modules/flat-entity/constant/all-metadata-relations-properties.constant'; +import { getMetadataEntityRelationProperties } from 'src/engine/metadata-modules/flat-entity/utils/get-metadata-entity-relation-properties.util'; import { type FlatViewField } from 'src/engine/metadata-modules/flat-view-field/types/flat-view-field.type'; import { type ViewFieldEntity } from 'src/engine/metadata-modules/view-field/entities/view-field.entity'; @@ -9,9 +9,7 @@ export const fromViewFieldEntityToFlatViewField = ( ): FlatViewField => { const viewFieldEntityWithoutRelations = removePropertiesFromRecord( viewFieldEntity, - Object.keys( - ALL_METADATA_RELATION_PROPERTIES.viewField, - ) as (keyof typeof ALL_METADATA_RELATION_PROPERTIES.viewField)[], + getMetadataEntityRelationProperties('viewField'), ); return { diff --git a/packages/twenty-server/src/engine/metadata-modules/flat-view-filter-group/utils/from-view-filter-group-entity-to-flat-view-filter-group.util.ts b/packages/twenty-server/src/engine/metadata-modules/flat-view-filter-group/utils/from-view-filter-group-entity-to-flat-view-filter-group.util.ts index dee5c6fbfc..40c9649f5a 100644 --- a/packages/twenty-server/src/engine/metadata-modules/flat-view-filter-group/utils/from-view-filter-group-entity-to-flat-view-filter-group.util.ts +++ b/packages/twenty-server/src/engine/metadata-modules/flat-view-filter-group/utils/from-view-filter-group-entity-to-flat-view-filter-group.util.ts @@ -1,6 +1,6 @@ import { removePropertiesFromRecord } from 'twenty-shared/utils'; -import { ALL_METADATA_RELATION_PROPERTIES } from 'src/engine/metadata-modules/flat-entity/constant/all-metadata-relations-properties.constant'; +import { getMetadataEntityRelationProperties } from 'src/engine/metadata-modules/flat-entity/utils/get-metadata-entity-relation-properties.util'; import { type FlatViewFilterGroup } from 'src/engine/metadata-modules/flat-view-filter-group/types/flat-view-filter-group.type'; import { type ViewFilterGroupEntity } from 'src/engine/metadata-modules/view-filter-group/entities/view-filter-group.entity'; @@ -9,9 +9,7 @@ export const fromViewFilterGroupEntityToFlatViewFilterGroup = ( ): FlatViewFilterGroup => { const viewFilterGroupEntityWithoutRelations = removePropertiesFromRecord( viewFilterGroupEntity, - Object.keys( - ALL_METADATA_RELATION_PROPERTIES.viewFilterGroup, - ) as (keyof typeof ALL_METADATA_RELATION_PROPERTIES.viewFilterGroup)[], + getMetadataEntityRelationProperties('viewFilterGroup'), ); return { diff --git a/packages/twenty-server/src/engine/metadata-modules/flat-view-filter/utils/from-view-filter-entity-to-flat-view-filter.util.ts b/packages/twenty-server/src/engine/metadata-modules/flat-view-filter/utils/from-view-filter-entity-to-flat-view-filter.util.ts index 59578cfed4..793a81ad84 100644 --- a/packages/twenty-server/src/engine/metadata-modules/flat-view-filter/utils/from-view-filter-entity-to-flat-view-filter.util.ts +++ b/packages/twenty-server/src/engine/metadata-modules/flat-view-filter/utils/from-view-filter-entity-to-flat-view-filter.util.ts @@ -1,6 +1,6 @@ import { removePropertiesFromRecord } from 'twenty-shared/utils'; -import { ALL_METADATA_RELATION_PROPERTIES } from 'src/engine/metadata-modules/flat-entity/constant/all-metadata-relations-properties.constant'; +import { getMetadataEntityRelationProperties } from 'src/engine/metadata-modules/flat-entity/utils/get-metadata-entity-relation-properties.util'; import { type FlatViewFilter } from 'src/engine/metadata-modules/flat-view-filter/types/flat-view-filter.type'; import { type ViewFilterEntity } from 'src/engine/metadata-modules/view-filter/entities/view-filter.entity'; @@ -9,9 +9,7 @@ export const fromViewFilterEntityToFlatViewFilter = ( ): FlatViewFilter => { const viewFilterEntityWithoutRelations = removePropertiesFromRecord( viewFilterEntity, - Object.keys( - ALL_METADATA_RELATION_PROPERTIES.viewFilter, - ) as (keyof typeof ALL_METADATA_RELATION_PROPERTIES.viewFilter)[], + getMetadataEntityRelationProperties('viewFilter'), ); return { diff --git a/packages/twenty-server/src/engine/metadata-modules/flat-view-group/utils/from-view-group-entity-to-flat-view-group.util.ts b/packages/twenty-server/src/engine/metadata-modules/flat-view-group/utils/from-view-group-entity-to-flat-view-group.util.ts index d403ee79ec..73708e1625 100644 --- a/packages/twenty-server/src/engine/metadata-modules/flat-view-group/utils/from-view-group-entity-to-flat-view-group.util.ts +++ b/packages/twenty-server/src/engine/metadata-modules/flat-view-group/utils/from-view-group-entity-to-flat-view-group.util.ts @@ -1,6 +1,6 @@ import { removePropertiesFromRecord } from 'twenty-shared/utils'; -import { ALL_METADATA_RELATION_PROPERTIES } from 'src/engine/metadata-modules/flat-entity/constant/all-metadata-relations-properties.constant'; +import { getMetadataEntityRelationProperties } from 'src/engine/metadata-modules/flat-entity/utils/get-metadata-entity-relation-properties.util'; import { type FlatViewGroup } from 'src/engine/metadata-modules/flat-view-group/types/flat-view-group.type'; import { type ViewGroupEntity } from 'src/engine/metadata-modules/view-group/entities/view-group.entity'; @@ -9,9 +9,7 @@ export const fromViewGroupEntityToFlatViewGroup = ( ): FlatViewGroup => { const viewGroupEntityWithoutRelations = removePropertiesFromRecord( viewGroupEntity, - Object.keys( - ALL_METADATA_RELATION_PROPERTIES.viewGroup, - ) as (keyof typeof ALL_METADATA_RELATION_PROPERTIES.viewGroup)[], + getMetadataEntityRelationProperties('viewGroup'), ); return { diff --git a/packages/twenty-server/src/engine/metadata-modules/flat-view/utils/from-view-entity-to-flat-view.util.ts b/packages/twenty-server/src/engine/metadata-modules/flat-view/utils/from-view-entity-to-flat-view.util.ts index 89497870da..6929bda5df 100644 --- a/packages/twenty-server/src/engine/metadata-modules/flat-view/utils/from-view-entity-to-flat-view.util.ts +++ b/packages/twenty-server/src/engine/metadata-modules/flat-view/utils/from-view-entity-to-flat-view.util.ts @@ -1,15 +1,13 @@ import { removePropertiesFromRecord } from 'twenty-shared/utils'; -import { ALL_METADATA_RELATION_PROPERTIES } from 'src/engine/metadata-modules/flat-entity/constant/all-metadata-relations-properties.constant'; +import { getMetadataEntityRelationProperties } from 'src/engine/metadata-modules/flat-entity/utils/get-metadata-entity-relation-properties.util'; import { type FlatView } from 'src/engine/metadata-modules/flat-view/types/flat-view.type'; import { type ViewEntity } from 'src/engine/metadata-modules/view/entities/view.entity'; export const fromViewEntityToFlatView = (viewEntity: ViewEntity): FlatView => { const viewEntityWithoutRelations = removePropertiesFromRecord( viewEntity, - Object.keys( - ALL_METADATA_RELATION_PROPERTIES.view, - ) as (keyof typeof ALL_METADATA_RELATION_PROPERTIES.view)[], + getMetadataEntityRelationProperties('view'), ); return { diff --git a/packages/twenty-server/src/engine/metadata-modules/route-trigger/services/workspace-flat-route-trigger-map-cache.service.ts b/packages/twenty-server/src/engine/metadata-modules/route-trigger/services/workspace-flat-route-trigger-map-cache.service.ts index 26f030cec4..8a09965e61 100644 --- a/packages/twenty-server/src/engine/metadata-modules/route-trigger/services/workspace-flat-route-trigger-map-cache.service.ts +++ b/packages/twenty-server/src/engine/metadata-modules/route-trigger/services/workspace-flat-route-trigger-map-cache.service.ts @@ -6,9 +6,9 @@ import { Repository } from 'typeorm'; import { WorkspaceCacheProvider } from 'src/engine/workspace-cache/interfaces/workspace-cache-provider.service'; -import { ALL_METADATA_RELATION_PROPERTIES } from 'src/engine/metadata-modules/flat-entity/constant/all-metadata-relations-properties.constant'; import { createEmptyFlatEntityMaps } from 'src/engine/metadata-modules/flat-entity/constant/create-empty-flat-entity-maps.constant'; import { FlatEntityMaps } from 'src/engine/metadata-modules/flat-entity/types/flat-entity-maps.type'; +import { getMetadataEntityRelationProperties } from 'src/engine/metadata-modules/flat-entity/utils/get-metadata-entity-relation-properties.util'; import { RouteTriggerEntity } from 'src/engine/metadata-modules/route-trigger/route-trigger.entity'; import { FlatRouteTrigger } from 'src/engine/metadata-modules/route-trigger/types/flat-route-trigger.type'; import { WorkspaceCache } from 'src/engine/workspace-cache/decorators/workspace-cache.decorator'; @@ -40,9 +40,7 @@ export class WorkspaceFlatRouteTriggerMapCacheService extends WorkspaceCacheProv for (const routeTriggerEntity of routeTriggers) { const routeTriggerEntityWithoutRelations = removePropertiesFromRecord( routeTriggerEntity, - Object.keys( - ALL_METADATA_RELATION_PROPERTIES.routeTrigger, - ) as (keyof typeof ALL_METADATA_RELATION_PROPERTIES.routeTrigger)[], + getMetadataEntityRelationProperties('routeTrigger'), ); const flatRouteTrigger = { ...routeTriggerEntityWithoutRelations, diff --git a/packages/twenty-server/src/engine/metadata-modules/row-level-permission-predicate/entities/row-level-permission-predicate-group.entity.ts b/packages/twenty-server/src/engine/metadata-modules/row-level-permission-predicate/entities/row-level-permission-predicate-group.entity.ts index a3323ecae8..07225e5079 100644 --- a/packages/twenty-server/src/engine/metadata-modules/row-level-permission-predicate/entities/row-level-permission-predicate-group.entity.ts +++ b/packages/twenty-server/src/engine/metadata-modules/row-level-permission-predicate/entities/row-level-permission-predicate-group.entity.ts @@ -72,11 +72,13 @@ export class RowLevelPermissionPredicateGroupEntity (rowLevelPermissionPredicateGroup) => rowLevelPermissionPredicateGroup.parentRowLevelPermissionPredicateGroup, ) - childRowLevelPermissionPredicateGroups: Relation[]; + childRowLevelPermissionPredicateGroups: Relation< + RowLevelPermissionPredicateGroupEntity[] + >; @OneToMany( () => RowLevelPermissionPredicateEntity, (predicate) => predicate.rowLevelPermissionPredicateGroup, ) - rowLevelPermissionPredicates: Relation[]; + rowLevelPermissionPredicates: Relation; } diff --git a/packages/twenty-server/src/engine/metadata-modules/view-filter-group/entities/view-filter-group.entity.ts b/packages/twenty-server/src/engine/metadata-modules/view-filter-group/entities/view-filter-group.entity.ts index f83de3e81a..3a00957945 100644 --- a/packages/twenty-server/src/engine/metadata-modules/view-filter-group/entities/view-filter-group.entity.ts +++ b/packages/twenty-server/src/engine/metadata-modules/view-filter-group/entities/view-filter-group.entity.ts @@ -59,7 +59,7 @@ export class ViewFilterGroupEntity extends SyncableEntity { view: Relation; @OneToMany(() => ViewFilterEntity, (viewFilter) => viewFilter.viewFilterGroup) - viewFilters: Relation[]; + viewFilters: Relation; @ManyToOne( () => ViewFilterGroupEntity, @@ -75,5 +75,5 @@ export class ViewFilterGroupEntity extends SyncableEntity { () => ViewFilterGroupEntity, (viewFilterGroup) => viewFilterGroup.parentViewFilterGroup, ) - childViewFilterGroups: Relation[]; + childViewFilterGroups: Relation; } diff --git a/packages/twenty-server/src/engine/workspace-manager/workspace-migration-v2/services/workspace-migration-build-orchestrator.service.ts b/packages/twenty-server/src/engine/workspace-manager/workspace-migration-v2/services/workspace-migration-build-orchestrator.service.ts index 9bbd3b432c..6d0e819169 100644 --- a/packages/twenty-server/src/engine/workspace-manager/workspace-migration-v2/services/workspace-migration-build-orchestrator.service.ts +++ b/packages/twenty-server/src/engine/workspace-manager/workspace-migration-v2/services/workspace-migration-build-orchestrator.service.ts @@ -368,8 +368,6 @@ export class WorkspaceMigrationBuildOrchestratorService { buildOptions, dependencyOptimisticFlatEntityMaps: { flatViewMaps: optimisticAllFlatEntityMaps.flatViewMaps, - flatViewFilterGroupMaps: - optimisticAllFlatEntityMaps.flatViewFilterGroupMaps, }, workspaceId, }, @@ -825,7 +823,10 @@ export class WorkspaceMigrationBuildOrchestratorService { from: fromFlatPageLayoutMaps, to: toFlatPageLayoutMaps, buildOptions, - dependencyOptimisticFlatEntityMaps: undefined, + dependencyOptimisticFlatEntityMaps: { + flatObjectMetadataMaps: + optimisticAllFlatEntityMaps.flatObjectMetadataMaps, + }, workspaceId, }, ); @@ -898,6 +899,8 @@ export class WorkspaceMigrationBuildOrchestratorService { to: toFlatPageLayoutWidgetMaps, buildOptions, dependencyOptimisticFlatEntityMaps: { + flatObjectMetadataMaps: + optimisticAllFlatEntityMaps.flatObjectMetadataMaps, flatPageLayoutTabMaps: optimisticAllFlatEntityMaps.flatPageLayoutTabMaps, }, diff --git a/packages/twenty-server/src/utils/__test__/get-field-metadata-entity.mock.ts b/packages/twenty-server/src/utils/__test__/get-field-metadata-entity.mock.ts index be9be295a9..fa2360cdf5 100644 --- a/packages/twenty-server/src/utils/__test__/get-field-metadata-entity.mock.ts +++ b/packages/twenty-server/src/utils/__test__/get-field-metadata-entity.mock.ts @@ -4,7 +4,6 @@ import { type FieldMetadataType } from 'twenty-shared/types'; import { type ApplicationEntity } from 'src/engine/core-modules/application/application.entity'; import { type WorkspaceEntity } from 'src/engine/core-modules/workspace/workspace.entity'; import { type FieldMetadataEntity } from 'src/engine/metadata-modules/field-metadata/field-metadata.entity'; -import { type IndexFieldMetadataEntity } from 'src/engine/metadata-modules/index-metadata/index-field-metadata.entity'; import { type ObjectMetadataEntity } from 'src/engine/metadata-modules/object-metadata/object-metadata.entity'; type GetMockFieldMetadataEntityOverride< @@ -30,7 +29,7 @@ export const getMockFieldMetadataEntity = < morphId: null, fieldPermissions: [], icon: null, - indexFieldMetadatas: {} as IndexFieldMetadataEntity, + indexFieldMetadatas: [], isCustom: true, isLabelSyncedWithName: false, isNullable: null, diff --git a/packages/twenty-shared/src/types/ExtractPropertiesThatEndsWithIds.ts b/packages/twenty-shared/src/types/ExtractPropertiesThatEndsWithIds.ts new file mode 100644 index 0000000000..b25508c56f --- /dev/null +++ b/packages/twenty-shared/src/types/ExtractPropertiesThatEndsWithIds.ts @@ -0,0 +1,7 @@ +export type ExtractPropertiesThatEndsWithIds = { + [K in keyof T]: K extends `${infer _}Ids` + ? T[K] extends string[] + ? K + : never + : never; +}[keyof T]; diff --git a/packages/twenty-shared/src/types/index.ts b/packages/twenty-shared/src/types/index.ts index 4e242d4bd6..1e38d93f21 100644 --- a/packages/twenty-shared/src/types/index.ts +++ b/packages/twenty-shared/src/types/index.ts @@ -53,6 +53,7 @@ export { ConnectedAccountProvider } from './ConnectedAccountProvider'; export type { EnumFieldMetadataType } from './EnumFieldMetadataType'; export type { ExcludeFunctions } from './ExcludeFunctions'; export type { ExtractPropertiesThatEndsWithId } from './ExtractPropertiesThatEndsWithId'; +export type { ExtractPropertiesThatEndsWithIds } from './ExtractPropertiesThatEndsWithIds'; export type { FieldMetadataDefaultValueFunctionNames, FieldMetadataClassValidation,