Fix and refactor all metadata relation (#17978)

# Introduction
The initial motivation was that in the workspace migration create action
some universal foreign key aggregators weren't correctly deleted before
returned due to constant missconfiguration
<img width="2300" height="972" alt="image"
src="https://github.com/user-attachments/assets/9401eb02-2bb2-4e69-9c5f-9a354ff61079"
/>
It also meant that under the hood some optimistic behavior wasn't
correctly rendered for some aggregators

## Solution
Refactored the `ALL_METADATA_RELATIONS` as follows:

This way we can infer the FK and transpile it to a universalFK, also the
aggregators are one to one instead of one versus all available
Making the only manual configuration to be defined the `foreignKey` and
`inverseOneToManyProperty`

```
┌──────────────────────────────────────┐      ┌─────────────────────────────────────────────┐
│  ALL_MANY_TO_ONE_METADATA_FOREIGN_KEY│      │      ALL_ONE_TO_MANY_METADATA_RELATIONS     │
│──────────────────────────────────────│      │─────────────────────────────────────────────│
│  Derived from: Entity types          │      │  Derived from: Entity types                 │
│                                      │      │                                             │
│  Provides:                           │      │  Provides:                                  │
│   • foreignKey                       │      │   • metadataName                            │
│                                      │      │   • flatEntityForeignKeyAggregator          │
│  Standalone low-level primitive      │      │   • universalFlatEntityForeignKeyAggregator │
└──────────────┬───────────────────────┘      └──────────────┬──────────────────────────────┘
               │                                             │
               │ foreignKey type +                           │ inverseOneToManyProperty
               │ universalForeignKey derivation              │ keys (type constraint)
               │                                             │
               ▼                                             ▼
       ┌───────────────────────────────────────────────────────────────┐
       │              ALL_MANY_TO_ONE_METADATA_RELATIONS              │
       │───────────────────────────────────────────────────────────────│
       │  Derived from:                                               │
       │   • Entity types (metadataName, isNullable)                  │
       │   • ALL_MANY_TO_ONE_METADATA_FOREIGN_KEY (FK → universalFK)  │
       │   • ALL_ONE_TO_MANY_METADATA_RELATIONS (inverse keys)        │
       │                                                              │
       │  Provides:                                                   │
       │   • metadataName                                             │
       │   • foreignKey (replicated from FK constant)                 │
       │   • inverseOneToManyProperty                                 │
       │   • isNullable                                               │
       │   • universalForeignKey                                      │
       └──────────────────────────┬────────────────────────────────────┘
                                  │
               ┌──────────────────┼──────────────────┐
               │                  │                  │
               ▼                  ▼                  ▼
   ┌───────────────────┐ ┌────────────────┐ ┌──────────────────────┐
   │  Type consumers   │ │  Atomic utils  │ │  Optimistic utils    │
   │───────────────────│ │────────────────│ │──────────────────────│
   │ • JoinColumn      │ │ • resolve-*    │ │ • add/delete flat    │
   │ • RelatedNames    │ │ • get-*        │ │   entity maps        │
   │ • UniversalFlat   │ │                │ │ • add/delete         │
   │   EntityFrom      │ │                │ │   universal flat     │
   │                   │ │                │ │   entity maps        │
   └───────────────────┘ └────────────────┘ │                      │
                                            │  (bridge via         │
                                            │   inverseOneToMany   │
                                            │   Property →         │
                                            │   ONE_TO_MANY for    │
                                            │   aggregator lookup) │
                                            └──────────────────────┘
```

### Previously
```
┌─────────────────────────────────────────────────────────────────────┐
│                       ALL_METADATA_RELATIONS                       │
│─────────────────────────────────────────────────────────────────────│
│  Derived from: Entity types                                        │
│                                                                    │
│  Structure: { [metadataName]: { manyToOne: {...}, oneToMany: {...},│
│               serializedRelations?: {...} } }                      │
│                                                                    │
│  manyToOne provides:                                               │
│   • metadataName                                                   │
│   • foreignKey                                                     │
│   • flatEntityForeignKeyAggregator (nullable, often wrong/null)    │
│   • isNullable                                                     │
│                                                                    │
│  oneToMany provides:                                               │
│   • metadataName                                                   │
│                                                                    │
│  Monolithic single source of truth                                 │
└──────────────────────────┬──────────────────────────────────────────┘
                           │
                           │ manyToOne entries transformed via
                           │ ToUniversalMetadataManyToOneRelationConfiguration
                           │
                           ▼
┌─────────────────────────────────────────────────────────────────────┐
│                  ALL_UNIVERSAL_METADATA_RELATIONS                   │
│─────────────────────────────────────────────────────────────────────│
│  Derived from: ALL_METADATA_RELATIONS (type-level transform)       │
│                                                                    │
│  Structure: { [metadataName]: { manyToOne: {...}, oneToMany: {...} │
│  } }                                                               │
│                                                                    │
│  manyToOne provides:                                               │
│   • metadataName                                                   │
│   • foreignKey                                                     │
│   • universalForeignKey (derived: FK → replace Id → UniversalId)   │
│   • universalFlatEntityForeignKeyAggregator (derived from          │
│     flatEntityForeignKeyAggregator → replace Ids → UniversalIds)   │
│   • isNullable                                                     │
│                                                                    │
│  oneToMany: passthrough from ALL_METADATA_RELATIONS                │
│                                                                    │
│  Duplicated monolith with universal key transforms                 │
└──────────────────────────┬──────────────────────────────────────────┘
                           │
        ┌──────────────────┼──────────────────────┐
        │                  │                      │
        ▼                  ▼                      ▼
┌───────────────┐ ┌────────────────────┐ ┌──────────────────────┐
│ Type consumers│ │   Atomic utils     │ │  Optimistic utils    │
│───────────────│ │────────────────────│ │──────────────────────│
│ • JoinColumn  │ │ • resolve-entity-  │ │ • add/delete flat    │
│ • RelatedNames│ │   relation-univ-id │ │   entity maps        │
│ • Universal   │ │   (ALL_METADATA_   │ │   (ALL_METADATA_     │
│   FlatEntity  │ │    RELATIONS       │ │    RELATIONS         │
│   From        │ │    .manyToOne)     │ │    .manyToOne)       │
│               │ │                    │ │                      │
│ Mixed usage   │ │ • resolve-univ-    │ │ • add/delete univ    │
│ of both       │ │   relation-ids     │ │   flat entity maps   │
│ constants     │ │   (ALL_UNIVERSAL_  │ │   (ALL_UNIVERSAL_    │
│               │ │    METADATA_REL    │ │    METADATA_REL      │
│               │ │    .manyToOne)     │ │    .manyToOne)       │
│               │ │                    │ │                      │
│               │ │ • resolve-univ-    │ │ universalFlatEntity  │
│               │ │   update-rel-ids   │ │ ForeignKeyAggregator │
│               │ │   (ALL_UNIVERSAL_  │ │ read directly from   │
│               │ │    METADATA_REL    │ │ the constant         │
│               │ │    .manyToOne)     │ │                      │
│               │ │                    │ │                      │
│               │ │ • regex hack:      │ │                      │
│               │ │   foreignKey       │ │                      │
│               │ │   .replace(/Id$/,  │ │                      │
│               │ │   'UniversalId')   │ │                      │
└───────────────┘ └────────────────────┘ └──────────────────────┘
```
This commit is contained in:
Paul Rastoin
2026-02-17 11:13:54 +01:00
committed by GitHub
parent 8244610bdc
commit 5544b5dcfe
36 changed files with 1279 additions and 1345 deletions
@@ -1,15 +1,15 @@
// Jest Snapshot v1, https://jestjs.io/docs/snapshot-testing
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`ALL_UNIVERSAL_FLAT_ENTITY_FOREIGN_KEY_AGGREGATOR_PROPERTIES should match snapshot 1`] = `
{
"agent": [],
"commandMenuItem": [],
"fieldMetadata": [
"calendarViewUniversalIdentifiers",
"kanbanAggregateOperationViewUniversalIdentifiers",
"mainGroupByFieldMetadataViewUniversalIdentifiers",
"viewFieldUniversalIdentifiers",
"viewFilterUniversalIdentifiers",
"kanbanAggregateOperationViewUniversalIdentifiers",
"calendarViewUniversalIdentifiers",
"mainGroupByFieldMetadataViewUniversalIdentifiers",
],
"frontComponent": [],
"index": [],
@@ -17,8 +17,8 @@ exports[`ALL_UNIVERSAL_FLAT_ENTITY_FOREIGN_KEY_AGGREGATOR_PROPERTIES should matc
"navigationMenuItem": [],
"objectMetadata": [
"fieldUniversalIdentifiers",
"viewUniversalIdentifiers",
"indexMetadataUniversalIdentifiers",
"viewUniversalIdentifiers",
],
"pageLayout": [
"tabUniversalIdentifiers",
@@ -29,19 +29,22 @@ exports[`ALL_UNIVERSAL_FLAT_ENTITY_FOREIGN_KEY_AGGREGATOR_PROPERTIES should matc
"pageLayoutWidget": [],
"role": [
"roleTargetUniversalIdentifiers",
"rowLevelPermissionPredicateUniversalIdentifiers",
"rowLevelPermissionPredicateGroupUniversalIdentifiers",
],
"roleTarget": [],
"rowLevelPermissionPredicate": [],
"rowLevelPermissionPredicateGroup": [
"childRowLevelPermissionPredicateGroupUniversalIdentifiers",
"rowLevelPermissionPredicateUniversalIdentifiers",
],
"skill": [],
"view": [
"viewFieldUniversalIdentifiers",
"viewFieldGroupUniversalIdentifiers",
"viewFilterUniversalIdentifiers",
"viewGroupUniversalIdentifiers",
"viewFilterGroupUniversalIdentifiers",
"viewGroupUniversalIdentifiers",
"viewFieldGroupUniversalIdentifiers",
],
"viewField": [],
"viewFieldGroup": [
@@ -49,8 +52,8 @@ exports[`ALL_UNIVERSAL_FLAT_ENTITY_FOREIGN_KEY_AGGREGATOR_PROPERTIES should matc
],
"viewFilter": [],
"viewFilterGroup": [
"viewFilterUniversalIdentifiers",
"childViewFilterGroupUniversalIdentifiers",
"viewFilterUniversalIdentifiers",
],
"viewGroup": [],
"webhook": [],
@@ -4,30 +4,21 @@ import {
} from 'twenty-shared/metadata';
import { isDefined } from 'twenty-shared/utils';
import { ALL_UNIVERSAL_METADATA_RELATIONS } from 'src/engine/workspace-manager/workspace-migration/universal-flat-entity/constants/all-universal-metadata-relations.constant';
import { ALL_ONE_TO_MANY_METADATA_RELATIONS } from 'src/engine/metadata-modules/flat-entity/constant/all-one-to-many-metadata-relations.constant';
type ExtractForeignKeyAggregatorFromManyToOneRelations<
ManyToOneRelations,
TargetMetadataName extends AllMetadataName,
> = {
[K in keyof ManyToOneRelations]: ManyToOneRelations[K] extends {
metadataName: TargetMetadataName;
universalFlatEntityForeignKeyAggregator: infer Agg;
type ExtractUniversalForeignKeyAggregators<OneToManyRelations> = {
[K in keyof OneToManyRelations]: OneToManyRelations[K] extends {
universalFlatEntityForeignKeyAggregator: infer Agg extends string;
}
? Agg extends string
? Agg
: never
? Agg
: never;
}[keyof ManyToOneRelations];
}[keyof OneToManyRelations];
export type ExtractUniversalForeignKeyAggregatorForMetadataName<
T extends AllMetadataName,
> = {
[M in AllMetadataName]: ExtractForeignKeyAggregatorFromManyToOneRelations<
(typeof ALL_UNIVERSAL_METADATA_RELATIONS)[M]['manyToOne'],
T
>;
}[AllMetadataName];
> = ExtractUniversalForeignKeyAggregators<
(typeof ALL_ONE_TO_MANY_METADATA_RELATIONS)[T]
>;
type UniversalFlatEntityForeignKeyAggregatorProperties = {
[P in AllMetadataName]: ExtractUniversalForeignKeyAggregatorForMetadataName<P>[];
@@ -36,25 +27,20 @@ type UniversalFlatEntityForeignKeyAggregatorProperties = {
const computeForeignKeyAggregatorProperties = <T extends AllMetadataName>(
metadataName: T,
): ExtractUniversalForeignKeyAggregatorForMetadataName<T>[] => {
const oneToManyRelations = ALL_ONE_TO_MANY_METADATA_RELATIONS[metadataName];
const aggregatorProperties: ExtractUniversalForeignKeyAggregatorForMetadataName<T>[] =
[];
for (const relationsEntry of Object.values(
ALL_UNIVERSAL_METADATA_RELATIONS,
)) {
for (const relation of Object.values(relationsEntry.manyToOne)) {
if (!isDefined(relation)) {
continue;
}
for (const relation of Object.values(oneToManyRelations)) {
if (!isDefined(relation)) {
continue;
}
if (
relation.metadataName === metadataName &&
isDefined(relation.universalFlatEntityForeignKeyAggregator)
) {
aggregatorProperties.push(
relation.universalFlatEntityForeignKeyAggregator as ExtractUniversalForeignKeyAggregatorForMetadataName<T>,
);
}
if (isDefined(relation.universalFlatEntityForeignKeyAggregator)) {
aggregatorProperties.push(
relation.universalFlatEntityForeignKeyAggregator as ExtractUniversalForeignKeyAggregatorForMetadataName<T>,
);
}
}
@@ -1,543 +0,0 @@
import { type AllMetadataName } from 'twenty-shared/metadata';
import { type ALL_METADATA_RELATIONS } from 'src/engine/metadata-modules/flat-entity/constant/all-metadata-relations.constant';
export type ToUniversalForeignKey<T extends string> =
T extends `${infer Prefix}Id` ? `${Prefix}UniversalIdentifier` : never;
type ToUniversalAggregator<T extends string> = T extends `${infer Prefix}Ids`
? `${Prefix}UniversalIdentifiers`
: never;
export type ToUniversalMetadataManyToOneRelationConfiguration<T> = T extends {
metadataName: infer M extends AllMetadataName;
foreignKey: infer FK extends string;
flatEntityForeignKeyAggregator: infer Agg;
isNullable: infer N extends boolean;
}
? {
metadataName: M;
foreignKey: FK;
universalForeignKey: ToUniversalForeignKey<FK>;
universalFlatEntityForeignKeyAggregator: Agg extends string
? ToUniversalAggregator<Agg>
: null;
isNullable: N;
}
: null;
type ToUniversalManyToOneRelations<T> = {
[K in keyof T]: ToUniversalMetadataManyToOneRelationConfiguration<T[K]>;
};
type ToUniversalOneToManyRelations<T> = T;
export type UniversalMetadataRelationsProperties = {
[M in AllMetadataName]: {
manyToOne: ToUniversalManyToOneRelations<
(typeof ALL_METADATA_RELATIONS)[M]['manyToOne']
>;
oneToMany: ToUniversalOneToManyRelations<
(typeof ALL_METADATA_RELATIONS)[M]['oneToMany']
>;
};
};
export const ALL_UNIVERSAL_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',
foreignKey: 'availabilityObjectMetadataId',
universalFlatEntityForeignKeyAggregator: null,
universalForeignKey: 'availabilityObjectMetadataUniversalIdentifier',
isNullable: true,
},
frontComponent: {
metadataName: 'frontComponent',
foreignKey: 'frontComponentId',
universalFlatEntityForeignKeyAggregator: null,
universalForeignKey: 'frontComponentUniversalIdentifier',
isNullable: true,
},
},
oneToMany: {},
},
navigationMenuItem: {
manyToOne: {
workspace: null,
userWorkspace: null,
application: null,
targetObjectMetadata: {
metadataName: 'objectMetadata',
foreignKey: 'targetObjectMetadataId',
universalFlatEntityForeignKeyAggregator: null,
universalForeignKey: 'targetObjectMetadataUniversalIdentifier',
isNullable: true,
},
folder: {
metadataName: 'navigationMenuItem',
foreignKey: 'folderId',
universalFlatEntityForeignKeyAggregator: null,
universalForeignKey: 'folderUniversalIdentifier',
isNullable: true,
},
view: {
metadataName: 'view',
foreignKey: 'viewId',
universalFlatEntityForeignKeyAggregator: null,
universalForeignKey: 'viewUniversalIdentifier',
isNullable: true,
},
},
oneToMany: {},
},
fieldMetadata: {
manyToOne: {
object: {
metadataName: 'objectMetadata',
foreignKey: 'objectMetadataId',
universalFlatEntityForeignKeyAggregator: 'fieldUniversalIdentifiers',
universalForeignKey: 'objectMetadataUniversalIdentifier',
isNullable: false,
},
workspace: null,
application: null,
relationTargetFieldMetadata: {
metadataName: 'fieldMetadata',
foreignKey: 'relationTargetFieldMetadataId',
universalFlatEntityForeignKeyAggregator: null,
universalForeignKey: 'relationTargetFieldMetadataUniversalIdentifier',
isNullable: true,
},
relationTargetObjectMetadata: {
metadataName: 'objectMetadata',
foreignKey: 'relationTargetObjectMetadataId',
universalFlatEntityForeignKeyAggregator: null,
universalForeignKey: 'relationTargetObjectMetadataUniversalIdentifier',
isNullable: true,
},
},
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: {
metadataName: 'objectMetadata',
foreignKey: 'objectMetadataId',
universalFlatEntityForeignKeyAggregator: 'viewUniversalIdentifiers',
universalForeignKey: 'objectMetadataUniversalIdentifier',
isNullable: false,
},
workspace: null,
createdBy: null,
application: null,
calendarFieldMetadata: {
metadataName: 'fieldMetadata',
foreignKey: 'calendarFieldMetadataId',
universalFlatEntityForeignKeyAggregator:
'calendarViewUniversalIdentifiers',
universalForeignKey: 'calendarFieldMetadataUniversalIdentifier',
isNullable: true,
},
kanbanAggregateOperationFieldMetadata: {
metadataName: 'fieldMetadata',
foreignKey: 'kanbanAggregateOperationFieldMetadataId',
universalFlatEntityForeignKeyAggregator:
'kanbanAggregateOperationViewUniversalIdentifiers',
universalForeignKey:
'kanbanAggregateOperationFieldMetadataUniversalIdentifier',
isNullable: true,
},
mainGroupByFieldMetadata: {
metadataName: 'fieldMetadata',
foreignKey: 'mainGroupByFieldMetadataId',
universalFlatEntityForeignKeyAggregator:
'mainGroupByFieldMetadataViewUniversalIdentifiers',
universalForeignKey: 'mainGroupByFieldMetadataUniversalIdentifier',
isNullable: true,
},
},
oneToMany: {
viewFields: { metadataName: 'viewField' },
viewFilters: { metadataName: 'viewFilter' },
viewFilterGroups: { metadataName: 'viewFilterGroup' },
viewGroups: { metadataName: 'viewGroup' },
viewFieldGroups: { metadataName: 'viewFieldGroup' },
// TODO migrate viewSort to v2
viewSorts: null,
},
},
viewField: {
manyToOne: {
fieldMetadata: {
metadataName: 'fieldMetadata',
foreignKey: 'fieldMetadataId',
universalFlatEntityForeignKeyAggregator:
'viewFieldUniversalIdentifiers',
universalForeignKey: 'fieldMetadataUniversalIdentifier',
isNullable: false,
},
view: {
metadataName: 'view',
foreignKey: 'viewId',
universalFlatEntityForeignKeyAggregator:
'viewFieldUniversalIdentifiers',
universalForeignKey: 'viewUniversalIdentifier',
isNullable: false,
},
viewFieldGroup: {
metadataName: 'viewFieldGroup',
foreignKey: 'viewFieldGroupId',
universalFlatEntityForeignKeyAggregator:
'viewFieldUniversalIdentifiers',
universalForeignKey: 'viewFieldGroupUniversalIdentifier',
isNullable: true,
},
workspace: null,
application: null,
},
oneToMany: {},
},
viewFieldGroup: {
manyToOne: {
view: {
metadataName: 'view',
foreignKey: 'viewId',
universalFlatEntityForeignKeyAggregator:
'viewFieldGroupUniversalIdentifiers',
universalForeignKey: 'viewUniversalIdentifier',
isNullable: false,
},
workspace: null,
application: null,
},
oneToMany: {
viewFields: { metadataName: 'viewField' },
},
},
viewFilter: {
manyToOne: {
fieldMetadata: {
metadataName: 'fieldMetadata',
foreignKey: 'fieldMetadataId',
universalFlatEntityForeignKeyAggregator:
'viewFilterUniversalIdentifiers',
universalForeignKey: 'fieldMetadataUniversalIdentifier',
isNullable: false,
},
view: {
metadataName: 'view',
foreignKey: 'viewId',
universalFlatEntityForeignKeyAggregator:
'viewFilterUniversalIdentifiers',
universalForeignKey: 'viewUniversalIdentifier',
isNullable: false,
},
viewFilterGroup: {
metadataName: 'viewFilterGroup',
foreignKey: 'viewFilterGroupId',
universalFlatEntityForeignKeyAggregator:
'viewFilterUniversalIdentifiers',
universalForeignKey: 'viewFilterGroupUniversalIdentifier',
isNullable: true,
},
workspace: null,
application: null,
},
oneToMany: {},
},
viewGroup: {
manyToOne: {
view: {
metadataName: 'view',
foreignKey: 'viewId',
universalFlatEntityForeignKeyAggregator:
'viewGroupUniversalIdentifiers',
universalForeignKey: 'viewUniversalIdentifier',
isNullable: false,
},
workspace: null,
application: null,
},
oneToMany: {},
},
index: {
manyToOne: {
objectMetadata: {
metadataName: 'objectMetadata',
foreignKey: 'objectMetadataId',
universalFlatEntityForeignKeyAggregator:
'indexMetadataUniversalIdentifiers',
universalForeignKey: 'objectMetadataUniversalIdentifier',
isNullable: false,
},
workspace: null,
application: null,
},
oneToMany: {
indexFieldMetadatas: null,
},
},
logicFunction: {
manyToOne: {
workspace: null,
application: 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',
foreignKey: 'roleId',
universalFlatEntityForeignKeyAggregator:
'roleTargetUniversalIdentifiers',
universalForeignKey: 'roleUniversalIdentifier',
isNullable: false,
},
apiKey: null,
workspace: null,
application: null,
},
oneToMany: {},
},
pageLayout: {
manyToOne: {
workspace: null,
objectMetadata: {
metadataName: 'objectMetadata',
foreignKey: 'objectMetadataId',
universalFlatEntityForeignKeyAggregator: null,
universalForeignKey: 'objectMetadataUniversalIdentifier',
isNullable: true,
},
application: null,
defaultTabToFocusOnMobileAndSidePanel: {
metadataName: 'pageLayoutTab',
foreignKey: 'defaultTabToFocusOnMobileAndSidePanelId',
universalFlatEntityForeignKeyAggregator: null,
universalForeignKey:
'defaultTabToFocusOnMobileAndSidePanelUniversalIdentifier',
isNullable: true,
},
},
oneToMany: {
tabs: { metadataName: 'pageLayoutTab' },
},
},
pageLayoutTab: {
manyToOne: {
workspace: null,
pageLayout: {
metadataName: 'pageLayout',
foreignKey: 'pageLayoutId',
universalFlatEntityForeignKeyAggregator: 'tabUniversalIdentifiers',
universalForeignKey: 'pageLayoutUniversalIdentifier',
isNullable: false,
},
application: null,
},
oneToMany: {
widgets: { metadataName: 'pageLayoutWidget' },
},
},
pageLayoutWidget: {
manyToOne: {
workspace: null,
pageLayoutTab: {
metadataName: 'pageLayoutTab',
foreignKey: 'pageLayoutTabId',
universalFlatEntityForeignKeyAggregator: 'widgetUniversalIdentifiers',
universalForeignKey: 'pageLayoutTabUniversalIdentifier',
isNullable: false,
},
objectMetadata: {
metadataName: 'objectMetadata',
foreignKey: 'objectMetadataId',
universalFlatEntityForeignKeyAggregator: null,
universalForeignKey: 'objectMetadataUniversalIdentifier',
isNullable: true,
},
application: null,
},
oneToMany: {},
},
rowLevelPermissionPredicate: {
manyToOne: {
workspace: null,
role: {
metadataName: 'role',
foreignKey: 'roleId',
universalFlatEntityForeignKeyAggregator: null,
universalForeignKey: 'roleUniversalIdentifier',
isNullable: false,
},
fieldMetadata: {
metadataName: 'fieldMetadata',
foreignKey: 'fieldMetadataId',
universalFlatEntityForeignKeyAggregator: null,
universalForeignKey: 'fieldMetadataUniversalIdentifier',
isNullable: false,
},
workspaceMemberFieldMetadata: {
metadataName: 'fieldMetadata',
foreignKey: 'workspaceMemberFieldMetadataId',
universalFlatEntityForeignKeyAggregator: null,
universalForeignKey: 'workspaceMemberFieldMetadataUniversalIdentifier',
isNullable: true,
},
objectMetadata: {
metadataName: 'objectMetadata',
foreignKey: 'objectMetadataId',
universalFlatEntityForeignKeyAggregator: null,
universalForeignKey: 'objectMetadataUniversalIdentifier',
isNullable: false,
},
rowLevelPermissionPredicateGroup: {
metadataName: 'rowLevelPermissionPredicateGroup',
foreignKey: 'rowLevelPermissionPredicateGroupId',
universalFlatEntityForeignKeyAggregator: null,
universalForeignKey:
'rowLevelPermissionPredicateGroupUniversalIdentifier',
isNullable: true,
},
application: null,
},
oneToMany: {},
},
rowLevelPermissionPredicateGroup: {
manyToOne: {
objectMetadata: {
metadataName: 'objectMetadata',
foreignKey: 'objectMetadataId',
universalFlatEntityForeignKeyAggregator: null,
universalForeignKey: 'objectMetadataUniversalIdentifier',
isNullable: false,
},
role: {
metadataName: 'role',
foreignKey: 'roleId',
universalFlatEntityForeignKeyAggregator: null,
universalForeignKey: 'roleUniversalIdentifier',
isNullable: false,
},
parentRowLevelPermissionPredicateGroup: {
metadataName: 'rowLevelPermissionPredicateGroup',
foreignKey: 'parentRowLevelPermissionPredicateGroupId',
universalFlatEntityForeignKeyAggregator:
'childRowLevelPermissionPredicateGroupUniversalIdentifiers',
universalForeignKey:
'parentRowLevelPermissionPredicateGroupUniversalIdentifier',
isNullable: true,
},
workspace: null,
application: null,
},
oneToMany: {
childRowLevelPermissionPredicateGroups: {
metadataName: 'rowLevelPermissionPredicateGroup',
},
rowLevelPermissionPredicates: {
metadataName: 'rowLevelPermissionPredicate',
},
},
},
viewFilterGroup: {
manyToOne: {
application: null,
parentViewFilterGroup: {
metadataName: 'viewFilterGroup',
foreignKey: 'parentViewFilterGroupId',
universalFlatEntityForeignKeyAggregator:
'childViewFilterGroupUniversalIdentifiers',
universalForeignKey: 'parentViewFilterGroupUniversalIdentifier',
isNullable: true,
},
view: {
metadataName: 'view',
foreignKey: 'viewId',
universalFlatEntityForeignKeyAggregator:
'viewFilterGroupUniversalIdentifiers',
universalForeignKey: 'viewUniversalIdentifier',
isNullable: false,
},
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 UniversalMetadataRelationsProperties;
@@ -0,0 +1,2 @@
export type ToUniversalForeignKey<T extends string> =
T extends `${infer Prefix}Id` ? `${Prefix}UniversalIdentifier` : never;
@@ -1,7 +1,8 @@
import { type AllMetadataName } from 'twenty-shared/metadata';
import { type FormatRecordSerializedRelationProperties } from 'twenty-shared/types';
import { type ALL_METADATA_RELATIONS } from 'src/engine/metadata-modules/flat-entity/constant/all-metadata-relations.constant';
import { type ALL_MANY_TO_ONE_METADATA_RELATIONS } from 'src/engine/metadata-modules/flat-entity/constant/all-many-to-one-metadata-relations.constant';
import { type ALL_ONE_TO_MANY_METADATA_RELATIONS } from 'src/engine/metadata-modules/flat-entity/constant/all-one-to-many-metadata-relations.constant';
import { type AddSuffixToEntityManyToOneProperties } from 'src/engine/metadata-modules/flat-entity/types/add-suffix-to-entity-many-to-one-properties.type';
import { type AddSuffixToEntityOneToManyProperties } from 'src/engine/metadata-modules/flat-entity/types/add-suffix-to-entity-one-to-many-properties.type';
import { type CastRecordTypeOrmDatePropertiesToString } from 'src/engine/metadata-modules/flat-entity/types/cast-record-typeorm-date-properties-to-string.type';
@@ -46,8 +47,8 @@ export type UniversalFlatEntityFrom<
| 'applicationId'
| 'workspaceId'
| 'id'
| keyof (typeof ALL_METADATA_RELATIONS)[TMetadataName]['manyToOne']
| keyof (typeof ALL_METADATA_RELATIONS)[TMetadataName]['oneToMany']
| keyof (typeof ALL_MANY_TO_ONE_METADATA_RELATIONS)[TMetadataName]
| keyof (typeof ALL_ONE_TO_MANY_METADATA_RELATIONS)[TMetadataName]
| Extract<MetadataManyToOneJoinColumn<TMetadataName>, keyof TEntity>
| keyof CastRecordTypeOrmDatePropertiesToString<TEntity>
| AllJsonbPropertiesWithSerializedPropertiesForMetadataName<TMetadataName>
@@ -4,7 +4,6 @@ import { type UniversalFlatEntityFrom } from 'src/engine/workspace-manager/works
export type UniversalFlatObjectMetadata = UniversalFlatEntityFrom<
Omit<
ObjectMetadataEntity,
| 'targetRelationFields'
| 'dataSourceId'
| 'labelIdentifierFieldMetadataId'
| 'imageIdentifierFieldMetadataId'
@@ -1,16 +1,17 @@
import { type AllMetadataName } from 'twenty-shared/metadata';
import { isDefined } from 'twenty-shared/utils';
import { ALL_MANY_TO_ONE_METADATA_RELATIONS } from 'src/engine/metadata-modules/flat-entity/constant/all-many-to-one-metadata-relations.constant';
import { ALL_ONE_TO_MANY_METADATA_RELATIONS } from 'src/engine/metadata-modules/flat-entity/constant/all-one-to-many-metadata-relations.constant';
import {
FlatEntityMapsException,
FlatEntityMapsExceptionCode,
} from 'src/engine/metadata-modules/flat-entity/exceptions/flat-entity-maps.exception';
import { type MetadataUniversalFlatEntityAndRelatedUniversalFlatEntityMaps } from 'src/engine/metadata-modules/flat-entity/types/metadata-related-types.type';
import { type MetadataRelatedFlatEntityMapsKeys } from 'src/engine/metadata-modules/flat-entity/types/metadata-related-flat-entity-maps-keys.type';
import { type MetadataUniversalFlatEntityAndRelatedUniversalFlatEntityMaps } from 'src/engine/metadata-modules/flat-entity/types/metadata-related-types.type';
import { type MetadataUniversalFlatEntity } from 'src/engine/metadata-modules/flat-entity/types/metadata-universal-flat-entity.type';
import { findFlatEntityByUniversalIdentifierOrThrow } from 'src/engine/metadata-modules/flat-entity/utils/find-flat-entity-by-universal-identifier-or-throw.util';
import { getMetadataFlatEntityMapsKey } from 'src/engine/metadata-modules/flat-entity/utils/get-metadata-flat-entity-maps-key.util';
import { ALL_UNIVERSAL_METADATA_RELATIONS } from 'src/engine/workspace-manager/workspace-migration/universal-flat-entity/constants/all-universal-metadata-relations.constant';
import { type UniversalFlatEntityMaps } from 'src/engine/workspace-manager/workspace-migration/universal-flat-entity/types/universal-flat-entity-maps.type';
import { addUniversalFlatEntityToUniversalFlatEntityMapsThroughMutationOrThrow } from 'src/engine/workspace-manager/workspace-migration/universal-flat-entity/utils/add-universal-flat-entity-to-universal-flat-entity-maps-through-mutation-or-throw.util';
import { replaceUniversalFlatEntityInUniversalFlatEntityMapsThroughMutationOrThrow } from 'src/engine/workspace-manager/workspace-migration/universal-flat-entity/utils/replace-universal-flat-entity-in-universal-flat-entity-maps-through-mutation-or-throw.util';
@@ -37,29 +38,46 @@ export const addUniversalFlatEntityToUniversalFlatEntityAndRelatedEntityMapsThro
universalFlatEntityAndRelatedMapsToMutate[flatEntityMapsKey],
});
const universalManyToOneRelations = Object.values(
ALL_UNIVERSAL_METADATA_RELATIONS[metadataName].manyToOne,
) as Array<{
metadataName: AllMetadataName;
universalFlatEntityForeignKeyAggregator: string | null;
universalForeignKey: string;
} | null>;
const manyToOneRelations = ALL_MANY_TO_ONE_METADATA_RELATIONS[metadataName];
for (const universalRelation of universalManyToOneRelations) {
if (!isDefined(universalRelation)) {
for (const relationPropertyName of Object.keys(manyToOneRelations)) {
const relation = manyToOneRelations[
relationPropertyName as keyof typeof manyToOneRelations
] as {
metadataName: AllMetadataName;
inverseOneToManyProperty: string | null;
universalForeignKey: string;
} | null;
if (!isDefined(relation)) {
continue;
}
const {
metadataName: relatedMetadataName,
universalFlatEntityForeignKeyAggregator,
inverseOneToManyProperty,
universalForeignKey,
} = universalRelation;
} = relation;
if (!isDefined(universalFlatEntityForeignKeyAggregator)) {
if (!isDefined(inverseOneToManyProperty)) {
continue;
}
const oneToManyRelations =
ALL_ONE_TO_MANY_METADATA_RELATIONS[relatedMetadataName];
const inverseRelation = oneToManyRelations[
inverseOneToManyProperty as keyof typeof oneToManyRelations
] as {
universalFlatEntityForeignKeyAggregator: string;
} | null;
if (!isDefined(inverseRelation)) {
continue;
}
const { universalFlatEntityForeignKeyAggregator } = inverseRelation;
const relatedFlatEntityMapsKey =
getMetadataFlatEntityMapsKey(relatedMetadataName);
@@ -1,6 +1,8 @@
import { type AllMetadataName } from 'twenty-shared/metadata';
import { isDefined } from 'twenty-shared/utils';
import { ALL_MANY_TO_ONE_METADATA_RELATIONS } from 'src/engine/metadata-modules/flat-entity/constant/all-many-to-one-metadata-relations.constant';
import { ALL_ONE_TO_MANY_METADATA_RELATIONS } from 'src/engine/metadata-modules/flat-entity/constant/all-one-to-many-metadata-relations.constant';
import {
FlatEntityMapsException,
FlatEntityMapsExceptionCode,
@@ -10,7 +12,6 @@ import { type MetadataUniversalFlatEntityAndRelatedUniversalFlatEntityMaps } fro
import { type MetadataUniversalFlatEntity } from 'src/engine/metadata-modules/flat-entity/types/metadata-universal-flat-entity.type';
import { findFlatEntityByUniversalIdentifier } from 'src/engine/metadata-modules/flat-entity/utils/find-flat-entity-by-universal-identifier.util';
import { getMetadataFlatEntityMapsKey } from 'src/engine/metadata-modules/flat-entity/utils/get-metadata-flat-entity-maps-key.util';
import { ALL_UNIVERSAL_METADATA_RELATIONS } from 'src/engine/workspace-manager/workspace-migration/universal-flat-entity/constants/all-universal-metadata-relations.constant';
import { type UniversalFlatEntityMaps } from 'src/engine/workspace-manager/workspace-migration/universal-flat-entity/types/universal-flat-entity-maps.type';
import { deleteUniversalFlatEntityFromUniversalFlatEntityMapsThroughMutationOrThrow } from 'src/engine/workspace-manager/workspace-migration/universal-flat-entity/utils/delete-universal-flat-entity-from-universal-flat-entity-maps-through-mutation-or-throw.util';
import { replaceUniversalFlatEntityInUniversalFlatEntityMapsThroughMutationOrThrow } from 'src/engine/workspace-manager/workspace-migration/universal-flat-entity/utils/replace-universal-flat-entity-in-universal-flat-entity-maps-through-mutation-or-throw.util';
@@ -41,29 +42,46 @@ export const deleteUniversalFlatEntityFromUniversalFlatEntityAndRelatedEntityMap
>,
});
const universalManyToOneRelations = Object.values(
ALL_UNIVERSAL_METADATA_RELATIONS[metadataName].manyToOne,
) as Array<{
metadataName: AllMetadataName;
universalFlatEntityForeignKeyAggregator: string | null;
universalForeignKey: string;
} | null>;
const manyToOneRelations = ALL_MANY_TO_ONE_METADATA_RELATIONS[metadataName];
for (const universalRelation of universalManyToOneRelations) {
if (!isDefined(universalRelation)) {
for (const relationPropertyName of Object.keys(manyToOneRelations)) {
const relation = manyToOneRelations[
relationPropertyName as keyof typeof manyToOneRelations
] as {
metadataName: AllMetadataName;
inverseOneToManyProperty: string | null;
universalForeignKey: string;
} | null;
if (!isDefined(relation)) {
continue;
}
const {
metadataName: relatedMetadataName,
universalFlatEntityForeignKeyAggregator,
inverseOneToManyProperty,
universalForeignKey,
} = universalRelation;
} = relation;
if (!isDefined(universalFlatEntityForeignKeyAggregator)) {
if (!isDefined(inverseOneToManyProperty)) {
continue;
}
const oneToManyRelations =
ALL_ONE_TO_MANY_METADATA_RELATIONS[relatedMetadataName];
const inverseRelation = oneToManyRelations[
inverseOneToManyProperty as keyof typeof oneToManyRelations
] as {
universalFlatEntityForeignKeyAggregator: string;
} | null;
if (!isDefined(inverseRelation)) {
continue;
}
const { universalFlatEntityForeignKeyAggregator } = inverseRelation;
const relatedFlatEntityMapsKey =
getMetadataFlatEntityMapsKey(relatedMetadataName);
@@ -2,44 +2,38 @@ import { t } from '@lingui/core/macro';
import { type AllMetadataName } from 'twenty-shared/metadata';
import { isDefined } from 'twenty-shared/utils';
import { type MetadataManyToOneRelationConfiguration } from 'src/engine/metadata-modules/flat-entity/constant/all-metadata-relations.constant';
import { ALL_MANY_TO_ONE_METADATA_RELATIONS } from 'src/engine/metadata-modules/flat-entity/constant/all-many-to-one-metadata-relations.constant';
import {
FlatEntityMapsException,
FlatEntityMapsExceptionCode,
} from 'src/engine/metadata-modules/flat-entity/exceptions/flat-entity-maps.exception';
import { type AllFlatEntityMaps } from 'src/engine/metadata-modules/flat-entity/types/all-flat-entity-maps.type';
import { type ExtractEntityManyToOneEntityRelationProperties } from 'src/engine/metadata-modules/flat-entity/types/extract-entity-many-to-one-entity-relation-properties.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 MetadataToFlatEntityMapsKey } from 'src/engine/metadata-modules/flat-entity/types/metadata-to-flat-entity-maps-key';
import { findFlatEntityByUniversalIdentifier } from 'src/engine/metadata-modules/flat-entity/utils/find-flat-entity-by-universal-identifier.util';
import { getMetadataFlatEntityMapsKey } from 'src/engine/metadata-modules/flat-entity/utils/get-metadata-flat-entity-maps-key.util';
import {
ALL_UNIVERSAL_METADATA_RELATIONS,
type ToUniversalMetadataManyToOneRelationConfiguration,
} from 'src/engine/workspace-manager/workspace-migration/universal-flat-entity/constants/all-universal-metadata-relations.constant';
type UniversalManyToOneConfig<T extends AllMetadataName> =
(typeof ALL_UNIVERSAL_METADATA_RELATIONS)[T]['manyToOne'];
type ManyToOneRelationsConfig<T extends AllMetadataName> =
(typeof ALL_MANY_TO_ONE_METADATA_RELATIONS)[T];
type ExtractUniversalForeignKeys<T> = {
[K in keyof T]: T[K] extends { universalForeignKey: infer UFK } ? UFK : never;
}[keyof T];
type MetadataUniversalManyToOneJoinColumn<T extends AllMetadataName> =
ExtractUniversalForeignKeys<UniversalManyToOneConfig<T>>;
ExtractUniversalForeignKeys<ManyToOneRelationsConfig<T>>;
type TargetMetadataNamesForUniversalForeignKeys<
T extends AllMetadataName,
TProvidedKeys extends string,
> = {
[K in keyof UniversalManyToOneConfig<T>]: UniversalManyToOneConfig<T>[K] extends {
[K in keyof ManyToOneRelationsConfig<T>]: ManyToOneRelationsConfig<T>[K] extends {
universalForeignKey: infer _UFK extends TProvidedKeys;
metadataName: infer MN extends AllMetadataName;
}
? MN
: never;
}[keyof UniversalManyToOneConfig<T>];
}[keyof ManyToOneRelationsConfig<T>];
type RequiredFlatEntityMapsForUniversalForeignKeys<
T extends AllMetadataName,
@@ -58,14 +52,14 @@ type ResolvedForeignKeyIds<
string
>,
> = {
[K in keyof UniversalManyToOneConfig<T> as UniversalManyToOneConfig<T>[K] extends {
[K in keyof ManyToOneRelationsConfig<T> as ManyToOneRelationsConfig<T>[K] extends {
universalForeignKey: infer UFK extends string;
foreignKey: infer FK extends string;
}
? UFK extends TProvidedKeys
? FK
: never
: never]: UniversalManyToOneConfig<T>[K] extends { isNullable: true }
: never]: ManyToOneRelationsConfig<T>[K] extends { isNullable: true }
? string | null
: string;
};
@@ -88,18 +82,20 @@ export const resolveUniversalRelationIdentifiersToIds = <
TProvidedKeys
>;
}): ResolvedForeignKeyIds<T, TProvidedKeys> => {
const relations = ALL_UNIVERSAL_METADATA_RELATIONS[metadataName].manyToOne;
const relationEntries = ALL_MANY_TO_ONE_METADATA_RELATIONS[metadataName];
const result: Record<string, string | null> = {};
for (const relation of Object.values(
relations,
) as ToUniversalMetadataManyToOneRelationConfiguration<
MetadataManyToOneRelationConfiguration<
T,
ExtractEntityManyToOneEntityRelationProperties<MetadataEntity<T>>
>
>[]) {
if (!isDefined(relation)) {
for (const relationPropertyName of Object.keys(relationEntries)) {
const relationEntry = relationEntries[
relationPropertyName as keyof typeof relationEntries
] as {
foreignKey: string;
metadataName: AllMetadataName;
isNullable: boolean;
universalForeignKey: string;
} | null;
if (!isDefined(relationEntry)) {
continue;
}
@@ -108,7 +104,7 @@ export const resolveUniversalRelationIdentifiersToIds = <
universalForeignKey,
metadataName: targetMetadataName,
isNullable,
} = relation;
} = relationEntry;
if (
!Object.prototype.hasOwnProperty.call(
@@ -1,22 +1,16 @@
import { type AllMetadataName } from 'twenty-shared/metadata';
import { isDefined } from 'twenty-shared/utils';
import { type MetadataManyToOneRelationConfiguration } from 'src/engine/metadata-modules/flat-entity/constant/all-metadata-relations.constant';
import { ALL_MANY_TO_ONE_METADATA_RELATIONS } from 'src/engine/metadata-modules/flat-entity/constant/all-many-to-one-metadata-relations.constant';
import {
FlatEntityMapsException,
FlatEntityMapsExceptionCode,
} from 'src/engine/metadata-modules/flat-entity/exceptions/flat-entity-maps.exception';
import { type AllFlatEntityMaps } from 'src/engine/metadata-modules/flat-entity/types/all-flat-entity-maps.type';
import { type ExtractEntityManyToOneEntityRelationProperties } from 'src/engine/metadata-modules/flat-entity/types/extract-entity-many-to-one-entity-relation-properties.type';
import { type FlatEntityUpdate } from 'src/engine/metadata-modules/flat-entity/types/flat-entity-update.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 { findFlatEntityByUniversalIdentifier } from 'src/engine/metadata-modules/flat-entity/utils/find-flat-entity-by-universal-identifier.util';
import { getMetadataFlatEntityMapsKey } from 'src/engine/metadata-modules/flat-entity/utils/get-metadata-flat-entity-maps-key.util';
import {
ALL_UNIVERSAL_METADATA_RELATIONS,
type ToUniversalMetadataManyToOneRelationConfiguration,
} from 'src/engine/workspace-manager/workspace-migration/universal-flat-entity/constants/all-universal-metadata-relations.constant';
import { type UniversalFlatEntityUpdate } from 'src/engine/workspace-manager/workspace-migration/universal-flat-entity/types/universal-flat-entity-update.type';
export const resolveUniversalUpdateRelationIdentifiersToIds = <
@@ -30,17 +24,18 @@ export const resolveUniversalUpdateRelationIdentifiersToIds = <
universalUpdate: UniversalFlatEntityUpdate<T>;
allFlatEntityMaps: AllFlatEntityMaps;
}): FlatEntityUpdate<T> => {
const relations = ALL_UNIVERSAL_METADATA_RELATIONS[metadataName].manyToOne;
const relationEntries = ALL_MANY_TO_ONE_METADATA_RELATIONS[metadataName];
const result: Record<string, unknown> = { ...universalUpdate };
for (const relation of Object.values(
relations,
) as ToUniversalMetadataManyToOneRelationConfiguration<
MetadataManyToOneRelationConfiguration<
T,
ExtractEntityManyToOneEntityRelationProperties<MetadataEntity<T>>
>
>[]) {
for (const relationPropertyName of Object.keys(relationEntries)) {
const relation = relationEntries[
relationPropertyName as keyof typeof relationEntries
] as {
foreignKey: string;
universalForeignKey: string;
metadataName: AllMetadataName;
} | null;
if (!isDefined(relation)) {
continue;
}