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
This commit is contained in:
Paul Rastoin
2026-01-02 16:49:06 +01:00
committed by GitHub
parent 3cab1bf80b
commit 42c9ae1ebc
30 changed files with 569 additions and 409 deletions
@@ -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<TEntity extends MetadataEntity<AllMetadataName>> = {
[K in ExtractPropertiesThatEndsWithId<TEntity, 'id' | 'workspaceId'>]: K;
};
type MetadataRelatedMetadataNames<T extends AllMetadataName> =
keyof ExtractEntityRelations<MetadataEntity<T>>;
type MetadataNameAndRelations = {
[TSourceMetadataName in AllMetadataName]: MetadataRelatedMetadataNames<TSourceMetadataName> extends never
? Record<string, never>
: {
[K in MetadataRelatedMetadataNames<TSourceMetadataName>]?: {
[TTargetMetadataName in AllMetadataName]?: {
metadataName: TTargetMetadataName;
flatEntityForeignKeyAggregator: keyof MetadataFlatEntity<TTargetMetadataName>;
};
}[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;
@@ -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<MetadataEntity<TName>>]: true;
};
};
@@ -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<TSourceMetadataName>
>,
> =
NonNullable<
MetadataEntity<TSourceMetadataName>[TRelationProperty]
> extends Relation<infer TTargetEntity extends SyncableEntity>
? {
metadataName: FromMetadataEntityToMetadataName<TTargetEntity>;
flatEntityForeignKeyAggregator: ExtractPropertiesThatEndsWithIds<
MetadataFlatEntity<FromMetadataEntityToMetadataName<TTargetEntity>>
// Note: In the best of the world should not be nullable, entities should always declare inverside keys
> | null;
foreignKey: ExtractPropertiesThatEndsWithId<
MetadataFlatEntity<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: {},
},
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
>,
];
@@ -4,7 +4,7 @@ import { type MetadataManyToOneRelatedMetadataNames } from 'src/engine/metadata-
type MetadataRequiredForValidation = {
[T in AllMetadataName]: Record<
MetadataManyToOneRelatedMetadataNames<T>,
Exclude<MetadataManyToOneRelatedMetadataNames<T>, T>,
true
> & {
[K in Exclude<AllMetadataName, T>]?: 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: {