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
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
36 changed files with 1279 additions and 1345 deletions

View file

@ -56,7 +56,7 @@ Follow these skills in order:
- Create TypeORM entity (extends `SyncableEntity`)
- Define flat entity types
- Define action types (universal + flat)
- Register in 4 central constants
- Register in 5 central constants
**Why first:** Everything else depends on these types
@ -177,9 +177,12 @@ packages/twenty-server/src/engine/metadata-modules/
│ ├── services/
│ └── utils/
└── flat-entity/constant/ # Step 1 (central registries)
├── all-entity-properties-configuration-by-metadata-name.constant.ts
├── all-one-to-many-metadata-relations.constant.ts
├── all-many-to-one-metadata-foreign-key.constant.ts
└── all-many-to-one-metadata-relations.constant.ts
packages/twenty-server/src/engine/workspace-manager/workspace-migration/
├── universal-flat-entity/constants/ # Step 1
├── workspace-migration-builder/ # Step 3
│ ├── builders/my-entity/
│ └── validators/services/
@ -192,7 +195,7 @@ packages/twenty-server/src/engine/workspace-manager/workspace-migration/
Before considering complete:
- [ ] All 6 guides completed
- [ ] TypeORM entity extends `SyncableEntity`
- [ ] All constants registered (4 central registries)
- [ ] All constants registered (5 central registries)
- [ ] Cache service with correct decorator
- [ ] Transform utils return universal flat entities
- [ ] Validator never throws/mutates

View file

@ -1,6 +1,6 @@
---
name: syncable-entity-types-and-constants
description: Define types, entities, and central constant registrations for syncable entities in Twenty's workspace migration system. Use when creating new syncable entities, defining TypeORM entities, flat entity types, or registering in central constants (ALL_ENTITY_PROPERTIES_CONFIGURATION_BY_METADATA_NAME, ALL_METADATA_RELATIONS, ALL_UNIVERSAL_METADATA_RELATIONS).
description: Define types, entities, and central constant registrations for syncable entities in Twenty's workspace migration system. Use when creating new syncable entities, defining TypeORM entities, flat entity types, or registering in central constants (ALL_ENTITY_PROPERTIES_CONFIGURATION_BY_METADATA_NAME, ALL_ONE_TO_MANY_METADATA_RELATIONS, ALL_MANY_TO_ONE_METADATA_FOREIGN_KEY, ALL_MANY_TO_ONE_METADATA_RELATIONS).
---
# Syncable Entity: Types & Constants (Step 1/6)
@ -18,7 +18,7 @@ This step creates:
2. TypeORM entity (extends `SyncableEntity`)
3. Flat entity types
4. Action types (universal + flat)
5. Central constant registrations (4 constants)
5. Central constant registrations (5 constants)
---
@ -225,61 +225,91 @@ export const ALL_ENTITY_PROPERTIES_CONFIGURATION_BY_METADATA_NAME = {
- `toStringify: true` → JSONB/object property (needs JSON serialization)
- `universalProperty` → Maps to universal version (for foreign keys & JSONB with `SerializedRelation`)
### 6c. ALL_METADATA_RELATIONS
### 6c. ALL_ONE_TO_MANY_METADATA_RELATIONS
**File**: `src/engine/metadata-modules/flat-entity/constant/all-metadata-relations.constant.ts`
**File**: `src/engine/metadata-modules/flat-entity/constant/all-one-to-many-metadata-relations.constant.ts`
This constant is **type-checked** — values for `metadataName`, `flatEntityForeignKeyAggregator`, and `universalFlatEntityForeignKeyAggregator` are derived from entity type definitions. The aggregator names follow the pattern: remove trailing `'s'` from the relation property name, then append `Ids` or `UniversalIdentifiers`.
```typescript
export const ALL_METADATA_RELATIONS = {
export const ALL_ONE_TO_MANY_METADATA_RELATIONS = {
// ... existing entries
myEntity: {
manyToOne: {
workspace: null,
application: null,
parentEntity: {
metadataName: 'parentEntity',
flatEntityForeignKeyAggregator: 'myEntityIds',
foreignKey: 'parentEntityId',
isNullable: false,
},
// If myEntity has a `childEntities: ChildEntityEntity[]` property:
childEntities: {
metadataName: 'childEntity',
flatEntityForeignKeyAggregator: 'childEntityIds',
universalFlatEntityForeignKeyAggregator: 'childEntityUniversalIdentifiers',
},
oneToMany: {
childEntities: { metadataName: 'childEntity' },
},
// Only if JSONB contains SerializedRelation fields
serializedRelations: {
fieldMetadata: true,
// null for relations to non-syncable entities
someNonSyncableRelation: null,
},
} as const;
```
### 6d. ALL_MANY_TO_ONE_METADATA_FOREIGN_KEY
**File**: `src/engine/metadata-modules/flat-entity/constant/all-many-to-one-metadata-foreign-key.constant.ts`
Low-level primitive constant. Only contains `foreignKey` — the column name ending in `Id` that stores the foreign key. Type-checked against entity properties.
```typescript
export const ALL_MANY_TO_ONE_METADATA_FOREIGN_KEY = {
// ... existing entries
myEntity: {
workspace: null,
application: null,
parentEntity: {
foreignKey: 'parentEntityId',
},
},
} as const;
```
### 6d. ALL_UNIVERSAL_METADATA_RELATIONS
### 6e. ALL_MANY_TO_ONE_METADATA_RELATIONS
**File**: `src/engine/workspace-manager/workspace-migration/universal-flat-entity/constants/all-universal-metadata-relations.constant.ts`
**File**: `src/engine/metadata-modules/flat-entity/constant/all-many-to-one-metadata-relations.constant.ts`
Derived from both `ALL_MANY_TO_ONE_METADATA_FOREIGN_KEY` (for `foreignKey` type and `universalForeignKey` derivation) and `ALL_ONE_TO_MANY_METADATA_RELATIONS` (for `inverseOneToManyProperty` key constraint). This is the main constant consumed by utils and optimistic tooling.
```typescript
export const ALL_UNIVERSAL_METADATA_RELATIONS = {
export const ALL_MANY_TO_ONE_METADATA_RELATIONS = {
// ... existing entries
myEntity: {
manyToOne: {
workspace: null,
application: null,
parentEntity: {
metadataName: 'parentEntity',
foreignKey: 'parentEntityId',
universalForeignKey: 'parentEntityUniversalIdentifier',
universalFlatEntityForeignKeyAggregator: 'myEntityUniversalIdentifiers',
isNullable: false,
},
},
oneToMany: {
childEntities: { metadataName: 'childEntity' },
workspace: null,
application: null,
parentEntity: {
metadataName: 'parentEntity',
foreignKey: 'parentEntityId',
inverseOneToManyProperty: 'myEntities', // key in ALL_ONE_TO_MANY_METADATA_RELATIONS['parentEntity'], or null if no inverse
isNullable: false,
universalForeignKey: 'parentEntityUniversalIdentifier',
},
},
} as const;
```
**Derivation dependency graph**:
```
ALL_MANY_TO_ONE_METADATA_FOREIGN_KEY ALL_ONE_TO_MANY_METADATA_RELATIONS
(foreignKey only) (metadataName, aggregators)
│ │
│ FK type + universalFK derivation │ inverseOneToManyProperty keys
│ │
└────────────────┬───────────────────────┘
ALL_MANY_TO_ONE_METADATA_RELATIONS
(metadataName, foreignKey, inverseOneToManyProperty,
isNullable, universalForeignKey)
```
**Rules**:
- `workspace: null`, `application: null` — always present, always null (non-syncable relations)
- `inverseOneToManyProperty` — must be a key in `ALL_ONE_TO_MANY_METADATA_RELATIONS[targetMetadataName]`, or `null` if the target entity doesn't expose an inverse one-to-many relation
- `universalForeignKey` — derived from `foreignKey` by replacing the `Id` suffix with `UniversalIdentifier`
- Optimistic utils resolve `flatEntityForeignKeyAggregator` / `universalFlatEntityForeignKeyAggregator` at runtime by looking up `inverseOneToManyProperty` in `ALL_ONE_TO_MANY_METADATA_RELATIONS`
---
## Checklist
@ -295,8 +325,9 @@ Before moving to Step 2:
- [ ] Universal and flat action types defined
- [ ] Registered in `AllFlatEntityTypesByMetadataName`
- [ ] Registered in `ALL_ENTITY_PROPERTIES_CONFIGURATION_BY_METADATA_NAME`
- [ ] Registered in `ALL_METADATA_RELATIONS`
- [ ] Registered in `ALL_UNIVERSAL_METADATA_RELATIONS`
- [ ] Registered in `ALL_ONE_TO_MANY_METADATA_RELATIONS` (if entity has one-to-many relations)
- [ ] Registered in `ALL_MANY_TO_ONE_METADATA_FOREIGN_KEY`
- [ ] Registered in `ALL_MANY_TO_ONE_METADATA_RELATIONS`
- [ ] TypeScript compiles without errors
---

View file

@ -151,11 +151,10 @@ export class FieldMetadataEntity<
TFieldMetadataType
>;
@ManyToOne(
() => ObjectMetadataEntity,
(objectMetadata) => objectMetadata.targetRelationFields,
{ onDelete: 'CASCADE', nullable: true },
)
@ManyToOne(() => ObjectMetadataEntity, {
onDelete: 'CASCADE',
nullable: true,
})
@JoinColumn({ name: 'relationTargetObjectMetadataId' })
relationTargetObjectMetadata: AssignTypeIfIsMorphOrRelationFieldMetadataType<
Relation<ObjectMetadataEntity>,

View file

@ -4,8 +4,8 @@ import { type MetadataEntity } from 'src/engine/metadata-modules/flat-entity/typ
import { type MetadataManyToOneJoinColumn } from 'src/engine/metadata-modules/flat-entity/types/metadata-many-to-one-join-column.type';
import { type ScalarFlatEntity } from 'src/engine/metadata-modules/flat-entity/types/scalar-flat-entity.type';
import { type AllJsonbPropertiesWithSerializedPropertiesForMetadataName } from 'src/engine/workspace-manager/workspace-migration/universal-flat-entity/constants/all-jsonb-properties-with-serialized-relation-by-metadata-name.constant';
import { type ToUniversalForeignKey } from 'src/engine/workspace-manager/workspace-migration/universal-flat-entity/constants/all-universal-metadata-relations.constant';
import { type ExtractJsonbProperties } from 'src/engine/workspace-manager/workspace-migration/universal-flat-entity/types/extract-jsonb-properties.type';
import { type ToUniversalForeignKey } from 'src/engine/workspace-manager/workspace-migration/universal-flat-entity/types/to-universal-foreign-key.type';
type HasObjectInUnion<T> = T extends unknown
? T extends object

View file

@ -0,0 +1,253 @@
import { type AllMetadataName } from 'twenty-shared/metadata';
import { type Expect } from 'twenty-shared/testing';
import { type ExtractPropertiesThatEndsWithId } from 'twenty-shared/types';
import { type Relation } from 'typeorm';
import { type 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 SyncableEntity } from 'src/engine/workspace-manager/types/syncable-entity.interface';
type ManyToOneRelationConfiguration<
TSourceMetadataName extends AllMetadataName,
TRelationProperty extends ExtractEntityManyToOneEntityRelationProperties<
MetadataEntity<TSourceMetadataName>
>,
> =
NonNullable<
MetadataEntity<TSourceMetadataName>[TRelationProperty]
> extends Relation<infer _TTargetEntity extends SyncableEntity>
? {
foreignKey: ExtractPropertiesThatEndsWithId<
MetadataEntity<TSourceMetadataName>,
'id' | 'workspaceId'
>;
}
: null;
type ManyToOneMetadataRelationsProperties = {
[TSourceMetadataName in AllMetadataName]: {
[TRelationProperty in ExtractEntityManyToOneEntityRelationProperties<
MetadataEntity<TSourceMetadataName>
>]: ManyToOneRelationConfiguration<TSourceMetadataName, TRelationProperty>;
};
};
export const ALL_MANY_TO_ONE_METADATA_FOREIGN_KEY = {
agent: {
workspace: null,
application: null,
},
skill: {
workspace: null,
application: null,
},
commandMenuItem: {
workspace: null,
application: null,
availabilityObjectMetadata: {
foreignKey: 'availabilityObjectMetadataId',
},
frontComponent: {
foreignKey: 'frontComponentId',
},
},
navigationMenuItem: {
workspace: null,
userWorkspace: null,
application: null,
targetObjectMetadata: {
foreignKey: 'targetObjectMetadataId',
},
folder: {
foreignKey: 'folderId',
},
view: {
foreignKey: 'viewId',
},
},
fieldMetadata: {
object: {
foreignKey: 'objectMetadataId',
},
workspace: null,
application: null,
relationTargetFieldMetadata: {
foreignKey: 'relationTargetFieldMetadataId',
},
relationTargetObjectMetadata: {
foreignKey: 'relationTargetObjectMetadataId',
},
},
objectMetadata: {
dataSource: null,
workspace: null,
application: null,
},
view: {
objectMetadata: {
foreignKey: 'objectMetadataId',
},
workspace: null,
createdBy: null,
application: null,
calendarFieldMetadata: {
foreignKey: 'calendarFieldMetadataId',
},
kanbanAggregateOperationFieldMetadata: {
foreignKey: 'kanbanAggregateOperationFieldMetadataId',
},
mainGroupByFieldMetadata: {
foreignKey: 'mainGroupByFieldMetadataId',
},
},
viewField: {
fieldMetadata: {
foreignKey: 'fieldMetadataId',
},
view: {
foreignKey: 'viewId',
},
viewFieldGroup: {
foreignKey: 'viewFieldGroupId',
},
workspace: null,
application: null,
},
viewFieldGroup: {
view: {
foreignKey: 'viewId',
},
workspace: null,
application: null,
},
viewFilter: {
fieldMetadata: {
foreignKey: 'fieldMetadataId',
},
view: {
foreignKey: 'viewId',
},
viewFilterGroup: {
foreignKey: 'viewFilterGroupId',
},
workspace: null,
application: null,
},
viewGroup: {
view: {
foreignKey: 'viewId',
},
workspace: null,
application: null,
},
index: {
objectMetadata: {
foreignKey: 'objectMetadataId',
},
workspace: null,
application: null,
},
logicFunction: {
workspace: null,
application: null,
},
role: {
workspace: null,
application: null,
},
roleTarget: {
role: {
foreignKey: 'roleId',
},
apiKey: null,
workspace: null,
application: null,
},
pageLayout: {
workspace: null,
objectMetadata: {
foreignKey: 'objectMetadataId',
},
application: null,
defaultTabToFocusOnMobileAndSidePanel: {
foreignKey: 'defaultTabToFocusOnMobileAndSidePanelId',
},
},
pageLayoutTab: {
workspace: null,
pageLayout: {
foreignKey: 'pageLayoutId',
},
application: null,
},
pageLayoutWidget: {
workspace: null,
pageLayoutTab: {
foreignKey: 'pageLayoutTabId',
},
objectMetadata: {
foreignKey: 'objectMetadataId',
},
application: null,
},
rowLevelPermissionPredicate: {
workspace: null,
role: {
foreignKey: 'roleId',
},
fieldMetadata: {
foreignKey: 'fieldMetadataId',
},
workspaceMemberFieldMetadata: {
foreignKey: 'workspaceMemberFieldMetadataId',
},
objectMetadata: {
foreignKey: 'objectMetadataId',
},
rowLevelPermissionPredicateGroup: {
foreignKey: 'rowLevelPermissionPredicateGroupId',
},
application: null,
},
rowLevelPermissionPredicateGroup: {
objectMetadata: {
foreignKey: 'objectMetadataId',
},
role: {
foreignKey: 'roleId',
},
parentRowLevelPermissionPredicateGroup: {
foreignKey: 'parentRowLevelPermissionPredicateGroupId',
},
workspace: null,
application: null,
},
viewFilterGroup: {
application: null,
parentViewFilterGroup: {
foreignKey: 'parentViewFilterGroupId',
},
view: {
foreignKey: 'viewId',
},
workspace: null,
},
frontComponent: {
workspace: null,
application: null,
},
webhook: {
workspace: null,
application: null,
},
} as const satisfies ManyToOneMetadataRelationsProperties;
// 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_MANY_TO_ONE_METADATA_FOREIGN_KEY
? true
: false
>,
];

View file

@ -0,0 +1,433 @@
import { type AllMetadataName } from 'twenty-shared/metadata';
import { type Expect } from 'twenty-shared/testing';
import { type Relation } from 'typeorm';
import { type ALL_MANY_TO_ONE_METADATA_FOREIGN_KEY } from 'src/engine/metadata-modules/flat-entity/constant/all-many-to-one-metadata-foreign-key.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 ExtractEntityManyToOneEntityRelationProperties } from 'src/engine/metadata-modules/flat-entity/types/extract-entity-many-to-one-entity-relation-properties.type';
import { type FromMetadataEntityToMetadataName } from 'src/engine/metadata-modules/flat-entity/types/from-metadata-entity-to-metadata-name.type';
import { type MetadataEntity } from 'src/engine/metadata-modules/flat-entity/types/metadata-entity.type';
import { type SyncableEntity } from 'src/engine/workspace-manager/types/syncable-entity.interface';
import { type ToUniversalForeignKey } from 'src/engine/workspace-manager/workspace-migration/universal-flat-entity/types/to-universal-foreign-key.type';
type FromRelationPropertyToForeignKey<
TMetadataName extends AllMetadataName,
TRelationProperty extends PropertyKey,
> = TRelationProperty extends keyof (typeof ALL_MANY_TO_ONE_METADATA_FOREIGN_KEY)[TMetadataName]
? (typeof ALL_MANY_TO_ONE_METADATA_FOREIGN_KEY)[TMetadataName][TRelationProperty] extends {
foreignKey: infer FK extends string;
}
? FK
: never
: never;
type ManyToOneRelationValue<
TSourceMetadataName extends AllMetadataName,
TRelationProperty extends ExtractEntityManyToOneEntityRelationProperties<
MetadataEntity<TSourceMetadataName>
>,
> =
NonNullable<
MetadataEntity<TSourceMetadataName>[TRelationProperty]
> extends Relation<infer TTargetEntity extends SyncableEntity>
? FromMetadataEntityToMetadataName<TTargetEntity> extends infer TTargetMetadataName extends
AllMetadataName
? FromRelationPropertyToForeignKey<
TSourceMetadataName,
TRelationProperty
> extends infer FK
? {
metadataName: TTargetMetadataName;
foreignKey: FK;
// Should not be nullable, in the best of the world relation should always describe an inverse property
inverseOneToManyProperty:
| keyof (typeof ALL_ONE_TO_MANY_METADATA_RELATIONS)[TTargetMetadataName]
| null;
isNullable: null extends MetadataEntity<TSourceMetadataName>[TRelationProperty]
? true
: false;
universalForeignKey: ToUniversalForeignKey<FK & string>;
}
: never
: never
: null;
type ManyToOneMetadataRelationsProperties = {
[TSourceMetadataName in AllMetadataName]: {
[TRelationProperty in ExtractEntityManyToOneEntityRelationProperties<
MetadataEntity<TSourceMetadataName>
>]: ManyToOneRelationValue<TSourceMetadataName, TRelationProperty>;
};
};
export const ALL_MANY_TO_ONE_METADATA_RELATIONS = {
agent: {
workspace: null,
application: null,
},
skill: {
workspace: null,
application: null,
},
commandMenuItem: {
workspace: null,
application: null,
availabilityObjectMetadata: {
metadataName: 'objectMetadata',
foreignKey: 'availabilityObjectMetadataId',
inverseOneToManyProperty: null,
isNullable: true,
universalForeignKey: 'availabilityObjectMetadataUniversalIdentifier',
},
frontComponent: {
metadataName: 'frontComponent',
foreignKey: 'frontComponentId',
inverseOneToManyProperty: null,
isNullable: true,
universalForeignKey: 'frontComponentUniversalIdentifier',
},
},
navigationMenuItem: {
workspace: null,
userWorkspace: null,
application: null,
targetObjectMetadata: {
metadataName: 'objectMetadata',
foreignKey: 'targetObjectMetadataId',
inverseOneToManyProperty: null,
isNullable: true,
universalForeignKey: 'targetObjectMetadataUniversalIdentifier',
},
folder: {
metadataName: 'navigationMenuItem',
foreignKey: 'folderId',
inverseOneToManyProperty: null,
isNullable: true,
universalForeignKey: 'folderUniversalIdentifier',
},
view: {
metadataName: 'view',
foreignKey: 'viewId',
inverseOneToManyProperty: null,
isNullable: true,
universalForeignKey: 'viewUniversalIdentifier',
},
},
fieldMetadata: {
object: {
metadataName: 'objectMetadata',
foreignKey: 'objectMetadataId',
inverseOneToManyProperty: 'fields',
isNullable: false,
universalForeignKey: 'objectMetadataUniversalIdentifier',
},
workspace: null,
application: null,
relationTargetFieldMetadata: {
metadataName: 'fieldMetadata',
foreignKey: 'relationTargetFieldMetadataId',
inverseOneToManyProperty: null,
isNullable: true,
universalForeignKey: 'relationTargetFieldMetadataUniversalIdentifier',
},
relationTargetObjectMetadata: {
metadataName: 'objectMetadata',
foreignKey: 'relationTargetObjectMetadataId',
inverseOneToManyProperty: null,
isNullable: true,
universalForeignKey: 'relationTargetObjectMetadataUniversalIdentifier',
},
},
objectMetadata: {
dataSource: null,
workspace: null,
application: null,
},
view: {
objectMetadata: {
metadataName: 'objectMetadata',
foreignKey: 'objectMetadataId',
inverseOneToManyProperty: 'views',
isNullable: false,
universalForeignKey: 'objectMetadataUniversalIdentifier',
},
workspace: null,
createdBy: null,
application: null,
calendarFieldMetadata: {
metadataName: 'fieldMetadata',
foreignKey: 'calendarFieldMetadataId',
inverseOneToManyProperty: 'calendarViews',
isNullable: true,
universalForeignKey: 'calendarFieldMetadataUniversalIdentifier',
},
kanbanAggregateOperationFieldMetadata: {
metadataName: 'fieldMetadata',
foreignKey: 'kanbanAggregateOperationFieldMetadataId',
inverseOneToManyProperty: 'kanbanAggregateOperationViews',
isNullable: true,
universalForeignKey:
'kanbanAggregateOperationFieldMetadataUniversalIdentifier',
},
mainGroupByFieldMetadata: {
metadataName: 'fieldMetadata',
foreignKey: 'mainGroupByFieldMetadataId',
inverseOneToManyProperty: 'mainGroupByFieldMetadataViews',
isNullable: true,
universalForeignKey: 'mainGroupByFieldMetadataUniversalIdentifier',
},
},
viewField: {
fieldMetadata: {
metadataName: 'fieldMetadata',
foreignKey: 'fieldMetadataId',
inverseOneToManyProperty: 'viewFields',
isNullable: false,
universalForeignKey: 'fieldMetadataUniversalIdentifier',
},
view: {
metadataName: 'view',
foreignKey: 'viewId',
inverseOneToManyProperty: 'viewFields',
isNullable: false,
universalForeignKey: 'viewUniversalIdentifier',
},
viewFieldGroup: {
metadataName: 'viewFieldGroup',
foreignKey: 'viewFieldGroupId',
inverseOneToManyProperty: 'viewFields',
isNullable: true,
universalForeignKey: 'viewFieldGroupUniversalIdentifier',
},
workspace: null,
application: null,
},
viewFieldGroup: {
view: {
metadataName: 'view',
foreignKey: 'viewId',
inverseOneToManyProperty: 'viewFieldGroups',
isNullable: false,
universalForeignKey: 'viewUniversalIdentifier',
},
workspace: null,
application: null,
},
viewFilter: {
fieldMetadata: {
metadataName: 'fieldMetadata',
foreignKey: 'fieldMetadataId',
inverseOneToManyProperty: 'viewFilters',
isNullable: false,
universalForeignKey: 'fieldMetadataUniversalIdentifier',
},
view: {
metadataName: 'view',
foreignKey: 'viewId',
inverseOneToManyProperty: 'viewFilters',
isNullable: false,
universalForeignKey: 'viewUniversalIdentifier',
},
viewFilterGroup: {
metadataName: 'viewFilterGroup',
foreignKey: 'viewFilterGroupId',
inverseOneToManyProperty: 'viewFilters',
isNullable: true,
universalForeignKey: 'viewFilterGroupUniversalIdentifier',
},
workspace: null,
application: null,
},
viewGroup: {
view: {
metadataName: 'view',
foreignKey: 'viewId',
inverseOneToManyProperty: 'viewGroups',
isNullable: false,
universalForeignKey: 'viewUniversalIdentifier',
},
workspace: null,
application: null,
},
index: {
objectMetadata: {
metadataName: 'objectMetadata',
foreignKey: 'objectMetadataId',
inverseOneToManyProperty: 'indexMetadatas',
isNullable: false,
universalForeignKey: 'objectMetadataUniversalIdentifier',
},
workspace: null,
application: null,
},
logicFunction: {
workspace: null,
application: null,
},
role: {
workspace: null,
application: null,
},
roleTarget: {
role: {
metadataName: 'role',
foreignKey: 'roleId',
inverseOneToManyProperty: 'roleTargets',
isNullable: false,
universalForeignKey: 'roleUniversalIdentifier',
},
apiKey: null,
workspace: null,
application: null,
},
pageLayout: {
workspace: null,
objectMetadata: {
metadataName: 'objectMetadata',
foreignKey: 'objectMetadataId',
inverseOneToManyProperty: null,
isNullable: true,
universalForeignKey: 'objectMetadataUniversalIdentifier',
},
application: null,
defaultTabToFocusOnMobileAndSidePanel: {
metadataName: 'pageLayoutTab',
foreignKey: 'defaultTabToFocusOnMobileAndSidePanelId',
inverseOneToManyProperty: null,
isNullable: true,
universalForeignKey:
'defaultTabToFocusOnMobileAndSidePanelUniversalIdentifier',
},
},
pageLayoutTab: {
workspace: null,
pageLayout: {
metadataName: 'pageLayout',
foreignKey: 'pageLayoutId',
inverseOneToManyProperty: 'tabs',
isNullable: false,
universalForeignKey: 'pageLayoutUniversalIdentifier',
},
application: null,
},
pageLayoutWidget: {
workspace: null,
pageLayoutTab: {
metadataName: 'pageLayoutTab',
foreignKey: 'pageLayoutTabId',
inverseOneToManyProperty: 'widgets',
isNullable: false,
universalForeignKey: 'pageLayoutTabUniversalIdentifier',
},
objectMetadata: {
metadataName: 'objectMetadata',
foreignKey: 'objectMetadataId',
inverseOneToManyProperty: null,
isNullable: true,
universalForeignKey: 'objectMetadataUniversalIdentifier',
},
application: null,
},
rowLevelPermissionPredicate: {
workspace: null,
role: {
metadataName: 'role',
foreignKey: 'roleId',
inverseOneToManyProperty: 'rowLevelPermissionPredicates',
isNullable: false,
universalForeignKey: 'roleUniversalIdentifier',
},
fieldMetadata: {
metadataName: 'fieldMetadata',
foreignKey: 'fieldMetadataId',
inverseOneToManyProperty: null,
isNullable: false,
universalForeignKey: 'fieldMetadataUniversalIdentifier',
},
workspaceMemberFieldMetadata: {
metadataName: 'fieldMetadata',
foreignKey: 'workspaceMemberFieldMetadataId',
inverseOneToManyProperty: null,
isNullable: true,
universalForeignKey: 'workspaceMemberFieldMetadataUniversalIdentifier',
},
objectMetadata: {
metadataName: 'objectMetadata',
foreignKey: 'objectMetadataId',
inverseOneToManyProperty: null,
isNullable: false,
universalForeignKey: 'objectMetadataUniversalIdentifier',
},
rowLevelPermissionPredicateGroup: {
metadataName: 'rowLevelPermissionPredicateGroup',
foreignKey: 'rowLevelPermissionPredicateGroupId',
inverseOneToManyProperty: 'rowLevelPermissionPredicates',
isNullable: true,
universalForeignKey:
'rowLevelPermissionPredicateGroupUniversalIdentifier',
},
application: null,
},
rowLevelPermissionPredicateGroup: {
objectMetadata: {
metadataName: 'objectMetadata',
foreignKey: 'objectMetadataId',
inverseOneToManyProperty: null,
isNullable: false,
universalForeignKey: 'objectMetadataUniversalIdentifier',
},
role: {
metadataName: 'role',
foreignKey: 'roleId',
inverseOneToManyProperty: 'rowLevelPermissionPredicateGroups',
isNullable: false,
universalForeignKey: 'roleUniversalIdentifier',
},
parentRowLevelPermissionPredicateGroup: {
metadataName: 'rowLevelPermissionPredicateGroup',
foreignKey: 'parentRowLevelPermissionPredicateGroupId',
inverseOneToManyProperty: 'childRowLevelPermissionPredicateGroups',
isNullable: true,
universalForeignKey:
'parentRowLevelPermissionPredicateGroupUniversalIdentifier',
},
workspace: null,
application: null,
},
viewFilterGroup: {
application: null,
parentViewFilterGroup: {
metadataName: 'viewFilterGroup',
foreignKey: 'parentViewFilterGroupId',
inverseOneToManyProperty: 'childViewFilterGroups',
isNullable: true,
universalForeignKey: 'parentViewFilterGroupUniversalIdentifier',
},
view: {
metadataName: 'view',
foreignKey: 'viewId',
inverseOneToManyProperty: 'viewFilterGroups',
isNullable: false,
universalForeignKey: 'viewUniversalIdentifier',
},
workspace: null,
},
frontComponent: {
workspace: null,
application: null,
},
webhook: {
workspace: null,
application: null,
},
} as const satisfies ManyToOneMetadataRelationsProperties;
// 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_MANY_TO_ONE_METADATA_RELATIONS
? true
: false
>,
];

View file

@ -1,537 +0,0 @@
import { type AllMetadataName } from 'twenty-shared/metadata';
import { type Expect } from 'twenty-shared/testing';
import { type ExtractPropertiesThatEndsWithId } from 'twenty-shared/types';
import { type Relation } from 'typeorm';
import { type AddSuffixToEntityOneToManyProperties } from 'src/engine/metadata-modules/flat-entity/types/add-suffix-to-entity-one-to-many-properties.type';
import { type ExtractEntityManyToOneEntityRelationProperties } from 'src/engine/metadata-modules/flat-entity/types/extract-entity-many-to-one-entity-relation-properties.type';
import { type ExtractEntityOneToManyEntityRelationProperties } from 'src/engine/metadata-modules/flat-entity/types/extract-entity-one-to-many-entity-relation-properties.type';
import { type FromMetadataEntityToMetadataName } from 'src/engine/metadata-modules/flat-entity/types/from-metadata-entity-to-metadata-name.type';
import { type MetadataEntity } from 'src/engine/metadata-modules/flat-entity/types/metadata-entity.type';
import { type SyncableEntity } from 'src/engine/workspace-manager/types/syncable-entity.interface';
import { type AllJsonbPropertiesWithSerializedPropertiesForMetadataName } from 'src/engine/workspace-manager/workspace-migration/universal-flat-entity/constants/all-jsonb-properties-with-serialized-relation-by-metadata-name.constant';
export type MetadataManyToOneRelationConfiguration<
TSourceMetadataName extends AllMetadataName,
TRelationProperty extends ExtractEntityManyToOneEntityRelationProperties<
MetadataEntity<TSourceMetadataName>
>,
> =
NonNullable<
MetadataEntity<TSourceMetadataName>[TRelationProperty]
> extends Relation<infer TTargetEntity extends SyncableEntity>
? {
metadataName: FromMetadataEntityToMetadataName<TTargetEntity>;
flatEntityForeignKeyAggregator:
| keyof AddSuffixToEntityOneToManyProperties<TTargetEntity, 'ids'>
| null;
foreignKey: ExtractPropertiesThatEndsWithId<
MetadataEntity<TSourceMetadataName>,
'id' | 'workspaceId'
>;
isNullable: null extends MetadataEntity<TSourceMetadataName>[TRelationProperty]
? true
: false;
}
: // Note: In the best of the world should not be nullable, entities should always declare inverside keys
null;
type OneToManyRelationValue<
TSourceMetadataName extends AllMetadataName,
TRelationProperty extends ExtractEntityOneToManyEntityRelationProperties<
MetadataEntity<TSourceMetadataName>
>,
> = MetadataEntity<TSourceMetadataName>[TRelationProperty] extends (infer TTargetEntity extends
SyncableEntity)[]
? {
metadataName: FromMetadataEntityToMetadataName<TTargetEntity>;
}
: null;
type MetadataRelationsProperties = {
[TSourceMetadataName in AllMetadataName]: {
manyToOne: {
[TRelationProperty in ExtractEntityManyToOneEntityRelationProperties<
MetadataEntity<TSourceMetadataName>
>]: MetadataManyToOneRelationConfiguration<
TSourceMetadataName,
TRelationProperty
>;
};
oneToMany: {
[TRelationProperty in ExtractEntityOneToManyEntityRelationProperties<
MetadataEntity<TSourceMetadataName>
>]: OneToManyRelationValue<TSourceMetadataName, TRelationProperty>;
};
} & ([
AllJsonbPropertiesWithSerializedPropertiesForMetadataName<TSourceMetadataName>,
] extends [never]
? // eslint-disable-next-line @typescript-eslint/no-empty-object-type
{}
: {
serializedRelations: Partial<Record<AllMetadataName, true>>;
});
};
export const ALL_METADATA_RELATIONS = {
agent: {
manyToOne: {
workspace: null,
application: null,
},
oneToMany: {},
},
skill: {
manyToOne: {
workspace: null,
application: null,
},
oneToMany: {},
},
commandMenuItem: {
manyToOne: {
workspace: null,
application: null,
availabilityObjectMetadata: {
metadataName: 'objectMetadata',
flatEntityForeignKeyAggregator: null,
foreignKey: 'availabilityObjectMetadataId',
isNullable: true,
},
frontComponent: {
metadataName: 'frontComponent',
flatEntityForeignKeyAggregator: null,
foreignKey: 'frontComponentId',
isNullable: true,
},
},
oneToMany: {},
},
navigationMenuItem: {
manyToOne: {
workspace: null,
userWorkspace: null,
application: null,
targetObjectMetadata: {
metadataName: 'objectMetadata',
flatEntityForeignKeyAggregator: null,
foreignKey: 'targetObjectMetadataId',
isNullable: true,
},
folder: {
metadataName: 'navigationMenuItem',
flatEntityForeignKeyAggregator: null,
foreignKey: 'folderId',
isNullable: true,
},
view: {
metadataName: 'view',
flatEntityForeignKeyAggregator: null,
foreignKey: 'viewId',
isNullable: true,
},
},
oneToMany: {},
},
fieldMetadata: {
manyToOne: {
object: {
metadataName: 'objectMetadata',
flatEntityForeignKeyAggregator: 'fieldIds',
foreignKey: 'objectMetadataId',
isNullable: false,
},
workspace: null,
application: null,
relationTargetFieldMetadata: {
metadataName: 'fieldMetadata',
flatEntityForeignKeyAggregator: null,
foreignKey: 'relationTargetFieldMetadataId',
isNullable: true,
},
relationTargetObjectMetadata: {
metadataName: 'objectMetadata',
flatEntityForeignKeyAggregator: null,
foreignKey: 'relationTargetObjectMetadataId',
isNullable: true,
},
},
oneToMany: {
fieldPermissions: null,
indexFieldMetadatas: null,
viewFields: { metadataName: 'viewField' },
viewFilters: { metadataName: 'viewFilter' },
kanbanAggregateOperationViews: { metadataName: 'view' },
calendarViews: { metadataName: 'view' },
mainGroupByFieldMetadataViews: { metadataName: 'view' },
},
serializedRelations: {
fieldMetadata: true,
},
},
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',
isNullable: false,
},
workspace: null,
createdBy: null,
application: null,
calendarFieldMetadata: {
foreignKey: 'calendarFieldMetadataId',
metadataName: 'fieldMetadata',
flatEntityForeignKeyAggregator: 'calendarViewIds',
isNullable: true,
},
kanbanAggregateOperationFieldMetadata: {
foreignKey: 'kanbanAggregateOperationFieldMetadataId',
metadataName: 'fieldMetadata',
flatEntityForeignKeyAggregator: 'kanbanAggregateOperationViewIds',
isNullable: true,
},
mainGroupByFieldMetadata: {
foreignKey: 'mainGroupByFieldMetadataId',
metadataName: 'fieldMetadata',
flatEntityForeignKeyAggregator: 'mainGroupByFieldMetadataViewIds',
isNullable: true,
},
},
oneToMany: {
viewFields: { metadataName: 'viewField' },
viewFilters: { metadataName: 'viewFilter' },
viewFilterGroups: {
metadataName: 'viewFilterGroup',
},
viewGroups: { metadataName: 'viewGroup' },
viewFieldGroups: { metadataName: 'viewFieldGroup' },
// @ts-expect-error TODO migrate viewSort to v2
viewSorts: null,
},
},
viewField: {
manyToOne: {
fieldMetadata: {
metadataName: 'fieldMetadata',
flatEntityForeignKeyAggregator: 'viewFieldIds',
foreignKey: 'fieldMetadataId',
isNullable: false,
},
view: {
metadataName: 'view',
flatEntityForeignKeyAggregator: 'viewFieldIds',
foreignKey: 'viewId',
isNullable: false,
},
viewFieldGroup: {
metadataName: 'viewFieldGroup',
flatEntityForeignKeyAggregator: 'viewFieldIds',
foreignKey: 'viewFieldGroupId',
isNullable: true,
},
workspace: null,
application: null,
},
oneToMany: {},
},
viewFieldGroup: {
manyToOne: {
view: {
metadataName: 'view',
flatEntityForeignKeyAggregator: 'viewFieldGroupIds',
foreignKey: 'viewId',
isNullable: false,
},
workspace: null,
application: null,
},
oneToMany: {
viewFields: { metadataName: 'viewField' },
},
},
viewFilter: {
manyToOne: {
fieldMetadata: {
metadataName: 'fieldMetadata',
flatEntityForeignKeyAggregator: 'viewFilterIds',
foreignKey: 'fieldMetadataId',
isNullable: false,
},
view: {
metadataName: 'view',
flatEntityForeignKeyAggregator: 'viewFilterIds',
foreignKey: 'viewId',
isNullable: false,
},
viewFilterGroup: {
flatEntityForeignKeyAggregator: 'viewFilterIds',
foreignKey: 'viewFilterGroupId',
metadataName: 'viewFilterGroup',
isNullable: true,
},
workspace: null,
application: null,
},
oneToMany: {},
},
viewGroup: {
manyToOne: {
view: {
metadataName: 'view',
flatEntityForeignKeyAggregator: 'viewGroupIds',
foreignKey: 'viewId',
isNullable: false,
},
workspace: null,
application: null,
},
oneToMany: {},
},
index: {
manyToOne: {
objectMetadata: {
metadataName: 'objectMetadata',
flatEntityForeignKeyAggregator: 'indexMetadataIds',
foreignKey: 'objectMetadataId',
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',
flatEntityForeignKeyAggregator: 'roleTargetIds',
foreignKey: 'roleId',
isNullable: false,
},
apiKey: null,
workspace: null,
application: null,
},
oneToMany: {},
},
pageLayout: {
manyToOne: {
workspace: null,
objectMetadata: {
metadataName: 'objectMetadata',
flatEntityForeignKeyAggregator: null,
foreignKey: 'objectMetadataId',
isNullable: true,
},
application: null,
defaultTabToFocusOnMobileAndSidePanel: {
metadataName: 'pageLayoutTab',
flatEntityForeignKeyAggregator: null,
foreignKey: 'defaultTabToFocusOnMobileAndSidePanelId',
isNullable: true,
},
},
oneToMany: {
tabs: { metadataName: 'pageLayoutTab' },
},
},
pageLayoutTab: {
manyToOne: {
workspace: null,
pageLayout: {
metadataName: 'pageLayout',
flatEntityForeignKeyAggregator: 'tabIds',
foreignKey: 'pageLayoutId',
isNullable: false,
},
application: null,
},
oneToMany: {
widgets: { metadataName: 'pageLayoutWidget' },
},
},
pageLayoutWidget: {
manyToOne: {
workspace: null,
pageLayoutTab: {
metadataName: 'pageLayoutTab',
flatEntityForeignKeyAggregator: 'widgetIds',
foreignKey: 'pageLayoutTabId',
isNullable: false,
},
objectMetadata: {
metadataName: 'objectMetadata',
flatEntityForeignKeyAggregator: null,
foreignKey: 'objectMetadataId',
isNullable: true,
},
application: null,
},
oneToMany: {},
serializedRelations: {
fieldMetadata: true,
},
},
rowLevelPermissionPredicate: {
manyToOne: {
workspace: null,
role: {
metadataName: 'role',
flatEntityForeignKeyAggregator: null,
foreignKey: 'roleId',
isNullable: false,
},
fieldMetadata: {
metadataName: 'fieldMetadata',
flatEntityForeignKeyAggregator: null,
foreignKey: 'fieldMetadataId',
isNullable: false,
},
workspaceMemberFieldMetadata: {
metadataName: 'fieldMetadata',
flatEntityForeignKeyAggregator: null,
foreignKey: 'workspaceMemberFieldMetadataId',
isNullable: true,
},
objectMetadata: {
metadataName: 'objectMetadata',
flatEntityForeignKeyAggregator: null,
foreignKey: 'objectMetadataId',
isNullable: false,
},
rowLevelPermissionPredicateGroup: {
metadataName: 'rowLevelPermissionPredicateGroup',
flatEntityForeignKeyAggregator: null,
foreignKey: 'rowLevelPermissionPredicateGroupId',
isNullable: true,
},
application: null,
},
oneToMany: {},
},
rowLevelPermissionPredicateGroup: {
manyToOne: {
objectMetadata: {
metadataName: 'objectMetadata',
flatEntityForeignKeyAggregator: null,
foreignKey: 'objectMetadataId',
isNullable: false,
},
role: {
metadataName: 'role',
foreignKey: 'roleId',
flatEntityForeignKeyAggregator: null,
isNullable: false,
},
parentRowLevelPermissionPredicateGroup: {
metadataName: 'rowLevelPermissionPredicateGroup',
foreignKey: 'parentRowLevelPermissionPredicateGroupId',
flatEntityForeignKeyAggregator:
'childRowLevelPermissionPredicateGroupIds',
isNullable: true,
},
workspace: null,
application: null,
},
oneToMany: {
childRowLevelPermissionPredicateGroups: {
metadataName: 'rowLevelPermissionPredicateGroup',
},
rowLevelPermissionPredicates: {
metadataName: 'rowLevelPermissionPredicate',
},
},
},
viewFilterGroup: {
manyToOne: {
application: null,
parentViewFilterGroup: {
flatEntityForeignKeyAggregator: 'childViewFilterGroupIds',
foreignKey: 'parentViewFilterGroupId',
metadataName: 'viewFilterGroup',
isNullable: true,
},
view: {
metadataName: 'view',
flatEntityForeignKeyAggregator: 'viewFilterGroupIds',
foreignKey: 'viewId',
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 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
>,
];

View file

@ -11,7 +11,7 @@ type MetadataRequiredForValidation = {
};
};
// TODO deprecate in favor of ALL_METADATA_RELATIONS
// TODO deprecate in favor of ALL_METADATA_SERIALIZED_RELATION
export const ALL_METADATA_REQUIRED_METADATA_FOR_VALIDATION = {
fieldMetadata: {
objectMetadata: true,

View file

@ -0,0 +1,53 @@
import { type AllMetadataName } from 'twenty-shared/metadata';
import { type Expect } from 'twenty-shared/testing';
import { type AllJsonbPropertiesWithSerializedPropertiesForMetadataName } from 'src/engine/workspace-manager/workspace-migration/universal-flat-entity/constants/all-jsonb-properties-with-serialized-relation-by-metadata-name.constant';
type MetadataSerializedRelationProperties = {
[TSourceMetadataName in AllMetadataName]: [
AllJsonbPropertiesWithSerializedPropertiesForMetadataName<TSourceMetadataName>,
] extends [never]
? // eslint-disable-next-line @typescript-eslint/no-empty-object-type
{}
: Partial<Record<AllMetadataName, true>>;
};
export const ALL_METADATA_SERIALIZED_RELATION = {
agent: {},
skill: {},
commandMenuItem: {},
navigationMenuItem: {},
fieldMetadata: {
fieldMetadata: true,
},
objectMetadata: {},
view: {},
viewField: {},
viewFieldGroup: {},
viewFilter: {},
viewGroup: {},
index: {},
logicFunction: {},
role: {},
roleTarget: {},
pageLayout: {},
pageLayoutTab: {},
pageLayoutWidget: {
fieldMetadata: true,
},
rowLevelPermissionPredicate: {},
rowLevelPermissionPredicateGroup: {},
viewFilterGroup: {},
frontComponent: {},
webhook: {},
} as const satisfies MetadataSerializedRelationProperties;
// 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_SERIALIZED_RELATION
? true
: false
>,
];

View file

@ -0,0 +1,215 @@
import { type AllMetadataName } from 'twenty-shared/metadata';
import { type Expect } from 'twenty-shared/testing';
import { type RemoveSuffix } from 'twenty-shared/types';
import { type ExtractEntityOneToManyEntityRelationProperties } from 'src/engine/metadata-modules/flat-entity/types/extract-entity-one-to-many-entity-relation-properties.type';
import { type FromMetadataEntityToMetadataName } from 'src/engine/metadata-modules/flat-entity/types/from-metadata-entity-to-metadata-name.type';
import { type MetadataEntity } from 'src/engine/metadata-modules/flat-entity/types/metadata-entity.type';
import { type SyncableEntity } from 'src/engine/workspace-manager/types/syncable-entity.interface';
type OneToManyRelationValue<
TSourceMetadataName extends AllMetadataName,
TRelationProperty extends ExtractEntityOneToManyEntityRelationProperties<
MetadataEntity<TSourceMetadataName>
>,
> = MetadataEntity<TSourceMetadataName>[TRelationProperty] extends (infer TTargetEntity extends
SyncableEntity)[]
? TRelationProperty extends string
? {
metadataName: FromMetadataEntityToMetadataName<TTargetEntity>;
flatEntityForeignKeyAggregator: `${RemoveSuffix<TRelationProperty, 's'>}Ids`;
universalFlatEntityForeignKeyAggregator: `${RemoveSuffix<TRelationProperty, 's'>}UniversalIdentifiers`;
}
: never
: null;
type OneToManyMetadataRelationsProperties = {
[TSourceMetadataName in AllMetadataName]: {
[TRelationProperty in ExtractEntityOneToManyEntityRelationProperties<
MetadataEntity<TSourceMetadataName>
>]: OneToManyRelationValue<TSourceMetadataName, TRelationProperty>;
};
};
export const ALL_ONE_TO_MANY_METADATA_RELATIONS = {
agent: {},
skill: {},
commandMenuItem: {},
navigationMenuItem: {},
fieldMetadata: {
fieldPermissions: null,
indexFieldMetadatas: null,
viewFields: {
metadataName: 'viewField',
flatEntityForeignKeyAggregator: 'viewFieldIds',
universalFlatEntityForeignKeyAggregator: 'viewFieldUniversalIdentifiers',
},
viewFilters: {
metadataName: 'viewFilter',
flatEntityForeignKeyAggregator: 'viewFilterIds',
universalFlatEntityForeignKeyAggregator: 'viewFilterUniversalIdentifiers',
},
kanbanAggregateOperationViews: {
metadataName: 'view',
flatEntityForeignKeyAggregator: 'kanbanAggregateOperationViewIds',
universalFlatEntityForeignKeyAggregator:
'kanbanAggregateOperationViewUniversalIdentifiers',
},
calendarViews: {
metadataName: 'view',
flatEntityForeignKeyAggregator: 'calendarViewIds',
universalFlatEntityForeignKeyAggregator:
'calendarViewUniversalIdentifiers',
},
mainGroupByFieldMetadataViews: {
metadataName: 'view',
flatEntityForeignKeyAggregator: 'mainGroupByFieldMetadataViewIds',
universalFlatEntityForeignKeyAggregator:
'mainGroupByFieldMetadataViewUniversalIdentifiers',
},
},
objectMetadata: {
fields: {
metadataName: 'fieldMetadata',
flatEntityForeignKeyAggregator: 'fieldIds',
universalFlatEntityForeignKeyAggregator: 'fieldUniversalIdentifiers',
},
indexMetadatas: {
metadataName: 'index',
flatEntityForeignKeyAggregator: 'indexMetadataIds',
universalFlatEntityForeignKeyAggregator:
'indexMetadataUniversalIdentifiers',
},
objectPermissions: null,
fieldPermissions: null,
views: {
metadataName: 'view',
flatEntityForeignKeyAggregator: 'viewIds',
universalFlatEntityForeignKeyAggregator: 'viewUniversalIdentifiers',
},
},
view: {
viewFields: {
metadataName: 'viewField',
flatEntityForeignKeyAggregator: 'viewFieldIds',
universalFlatEntityForeignKeyAggregator: 'viewFieldUniversalIdentifiers',
},
viewFilters: {
metadataName: 'viewFilter',
flatEntityForeignKeyAggregator: 'viewFilterIds',
universalFlatEntityForeignKeyAggregator: 'viewFilterUniversalIdentifiers',
},
viewFilterGroups: {
metadataName: 'viewFilterGroup',
flatEntityForeignKeyAggregator: 'viewFilterGroupIds',
universalFlatEntityForeignKeyAggregator:
'viewFilterGroupUniversalIdentifiers',
},
viewGroups: {
metadataName: 'viewGroup',
flatEntityForeignKeyAggregator: 'viewGroupIds',
universalFlatEntityForeignKeyAggregator: 'viewGroupUniversalIdentifiers',
},
viewFieldGroups: {
metadataName: 'viewFieldGroup',
flatEntityForeignKeyAggregator: 'viewFieldGroupIds',
universalFlatEntityForeignKeyAggregator:
'viewFieldGroupUniversalIdentifiers',
},
// @ts-expect-error TODO migrate viewSort to v2
viewSorts: null,
},
viewField: {},
viewFieldGroup: {
viewFields: {
metadataName: 'viewField',
flatEntityForeignKeyAggregator: 'viewFieldIds',
universalFlatEntityForeignKeyAggregator: 'viewFieldUniversalIdentifiers',
},
},
viewFilter: {},
viewGroup: {},
index: {
indexFieldMetadatas: null,
},
logicFunction: {},
role: {
roleTargets: {
metadataName: 'roleTarget',
flatEntityForeignKeyAggregator: 'roleTargetIds',
universalFlatEntityForeignKeyAggregator: 'roleTargetUniversalIdentifiers',
},
objectPermissions: null,
permissionFlags: null,
fieldPermissions: null,
rowLevelPermissionPredicates: {
metadataName: 'rowLevelPermissionPredicate',
flatEntityForeignKeyAggregator: 'rowLevelPermissionPredicateIds',
universalFlatEntityForeignKeyAggregator:
'rowLevelPermissionPredicateUniversalIdentifiers',
},
rowLevelPermissionPredicateGroups: {
metadataName: 'rowLevelPermissionPredicateGroup',
flatEntityForeignKeyAggregator: 'rowLevelPermissionPredicateGroupIds',
universalFlatEntityForeignKeyAggregator:
'rowLevelPermissionPredicateGroupUniversalIdentifiers',
},
},
roleTarget: {},
pageLayout: {
tabs: {
metadataName: 'pageLayoutTab',
flatEntityForeignKeyAggregator: 'tabIds',
universalFlatEntityForeignKeyAggregator: 'tabUniversalIdentifiers',
},
},
pageLayoutTab: {
widgets: {
metadataName: 'pageLayoutWidget',
flatEntityForeignKeyAggregator: 'widgetIds',
universalFlatEntityForeignKeyAggregator: 'widgetUniversalIdentifiers',
},
},
pageLayoutWidget: {},
rowLevelPermissionPredicate: {},
rowLevelPermissionPredicateGroup: {
childRowLevelPermissionPredicateGroups: {
metadataName: 'rowLevelPermissionPredicateGroup',
flatEntityForeignKeyAggregator:
'childRowLevelPermissionPredicateGroupIds',
universalFlatEntityForeignKeyAggregator:
'childRowLevelPermissionPredicateGroupUniversalIdentifiers',
},
rowLevelPermissionPredicates: {
metadataName: 'rowLevelPermissionPredicate',
flatEntityForeignKeyAggregator: 'rowLevelPermissionPredicateIds',
universalFlatEntityForeignKeyAggregator:
'rowLevelPermissionPredicateUniversalIdentifiers',
},
},
viewFilterGroup: {
childViewFilterGroups: {
metadataName: 'viewFilterGroup',
flatEntityForeignKeyAggregator: 'childViewFilterGroupIds',
universalFlatEntityForeignKeyAggregator:
'childViewFilterGroupUniversalIdentifiers',
},
viewFilters: {
metadataName: 'viewFilter',
flatEntityForeignKeyAggregator: 'viewFilterIds',
universalFlatEntityForeignKeyAggregator: 'viewFilterUniversalIdentifiers',
},
},
frontComponent: {},
webhook: {},
} as const satisfies OneToManyMetadataRelationsProperties;
// 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_ONE_TO_MANY_METADATA_RELATIONS
? true
: false
>,
];

View file

@ -6,7 +6,6 @@ type FieldMetadataJoinColumns = MetadataManyToOneJoinColumn<'fieldMetadata'>;
// eslint-disable-next-line unused-imports/no-unused-vars
type Assertions = [
// fieldMetadata foreign keys from ALL_METADATA_RELATIONS
Expect<
Equal<
FieldMetadataJoinColumns,

View file

@ -1,10 +1,10 @@
import { type AllMetadataName } from 'twenty-shared/metadata';
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';
type ExtractForeignKeys<T> = {
[K in keyof T]: T[K] extends { foreignKey: infer FK } ? FK : never;
}[keyof T];
export type MetadataManyToOneJoinColumn<T extends AllMetadataName> =
ExtractForeignKeys<(typeof ALL_METADATA_RELATIONS)[T]['manyToOne']>;
ExtractForeignKeys<(typeof ALL_MANY_TO_ONE_METADATA_RELATIONS)[T]>;

View file

@ -1,6 +1,6 @@
import { type AllMetadataName } from 'twenty-shared/metadata';
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';
type ExtractMetadataNames<T> = {
[K in keyof T]: T[K] extends { metadataName: infer M } ? M : never;
@ -8,6 +8,6 @@ type ExtractMetadataNames<T> = {
export type MetadataManyToOneRelatedMetadataNames<T extends AllMetadataName> =
Extract<
ExtractMetadataNames<(typeof ALL_METADATA_RELATIONS)[T]['manyToOne']>,
ExtractMetadataNames<(typeof ALL_MANY_TO_ONE_METADATA_RELATIONS)[T]>,
AllMetadataName
>;

View file

@ -1,6 +1,6 @@
import { type AllMetadataName } from 'twenty-shared/metadata';
import { type ALL_METADATA_RELATIONS } from 'src/engine/metadata-modules/flat-entity/constant/all-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';
type ExtractMetadataNames<T> = {
[K in keyof T]: T[K] extends { metadataName: infer M } ? M : never;
@ -8,6 +8,6 @@ type ExtractMetadataNames<T> = {
export type MetadataOneToManyRelatedMetadataNames<T extends AllMetadataName> =
Extract<
ExtractMetadataNames<(typeof ALL_METADATA_RELATIONS)[T]['oneToMany']>,
ExtractMetadataNames<(typeof ALL_ONE_TO_MANY_METADATA_RELATIONS)[T]>,
AllMetadataName
>;

View file

@ -1,4 +1,4 @@
// Jest Snapshot v1, https://jestjs.io/docs/snapshot-testing
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`sortMetadataNamesChildrenFirst should return metadata names sorted with children first (most manyToOne relations first) 1`] = `
[
@ -23,7 +23,7 @@ exports[`sortMetadataNamesChildrenFirst should return metadata names sorted with
"webhook",
"view",
"fieldMetadata",
"role",
"objectMetadata",
"role",
]
`;

View file

@ -1,7 +1,8 @@
import { type AllMetadataName } from 'twenty-shared/metadata';
import { isDefined } from 'twenty-shared/utils';
import { ALL_METADATA_RELATIONS } 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 { ALL_ONE_TO_MANY_METADATA_RELATIONS } from 'src/engine/metadata-modules/flat-entity/constant/all-one-to-many-metadata-relations.constant';
import {
FlatEntityMapsException,
FlatEntityMapsExceptionCode,
@ -45,29 +46,46 @@ export const addFlatEntityToFlatEntityAndRelatedEntityMapsThroughMutationOrThrow
selfFlatEntityMaps.universalIdentifierById[flatEntity.id] =
flatEntity.universalIdentifier;
const idBasedManyToOneRelations = Object.values(
ALL_METADATA_RELATIONS[metadataName].manyToOne,
) as Array<{
metadataName: AllMetadataName;
flatEntityForeignKeyAggregator: keyof MetadataFlatEntity<AllMetadataName>;
foreignKey: keyof MetadataFlatEntity<T>;
} | null>;
const manyToOneRelations = ALL_MANY_TO_ONE_METADATA_RELATIONS[metadataName];
for (const idBasedRelation of idBasedManyToOneRelations) {
if (!isDefined(idBasedRelation)) {
for (const relationPropertyName of Object.keys(manyToOneRelations)) {
const relation = manyToOneRelations[
relationPropertyName as keyof typeof manyToOneRelations
] as {
metadataName: AllMetadataName;
foreignKey: string;
inverseOneToManyProperty: string | null;
} | null;
if (!isDefined(relation)) {
continue;
}
const {
metadataName: relatedMetadataName,
flatEntityForeignKeyAggregator,
foreignKey,
} = idBasedRelation;
inverseOneToManyProperty,
} = relation;
if (!isDefined(flatEntityForeignKeyAggregator)) {
if (!isDefined(inverseOneToManyProperty)) {
continue;
}
const oneToManyRelations =
ALL_ONE_TO_MANY_METADATA_RELATIONS[relatedMetadataName];
const inverseRelation = oneToManyRelations[
inverseOneToManyProperty as keyof typeof oneToManyRelations
] as {
flatEntityForeignKeyAggregator: string;
} | null;
if (!isDefined(inverseRelation)) {
continue;
}
const { flatEntityForeignKeyAggregator } = inverseRelation;
const relatedFlatEntityMapsKey =
getMetadataFlatEntityMapsKey(relatedMetadataName);
@ -75,9 +93,9 @@ export const addFlatEntityToFlatEntityAndRelatedEntityMapsThroughMutationOrThrow
relatedFlatEntityMapsKey as MetadataRelatedFlatEntityMapsKeys<T>
] as FlatEntityMaps<MetadataFlatEntity<typeof relatedMetadataName>>;
const flatEntityRelatedEntityForeignKeyValue = flatEntity[foreignKey] as
| string
| undefined;
const flatEntityRelatedEntityForeignKeyValue = (
flatEntity as unknown as Record<string, string | undefined>
)[foreignKey];
if (!isDefined(flatEntityRelatedEntityForeignKeyValue)) {
continue;
@ -103,9 +121,9 @@ export const addFlatEntityToFlatEntityAndRelatedEntityMapsThroughMutationOrThrow
const updatedRelatedEntity = {
...relatedFlatEntity,
[flatEntityForeignKeyAggregator]: [
...(relatedFlatEntity[
...((relatedFlatEntity as unknown as Record<string, string[]>)[
flatEntityForeignKeyAggregator
] as unknown as string[]),
] ?? []),
flatEntity.id,
],
};

View file

@ -1,7 +1,8 @@
import { type AllMetadataName } from 'twenty-shared/metadata';
import { isDefined } from 'twenty-shared/utils';
import { ALL_METADATA_RELATIONS } 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 { ALL_ONE_TO_MANY_METADATA_RELATIONS } from 'src/engine/metadata-modules/flat-entity/constant/all-one-to-many-metadata-relations.constant';
import {
FlatEntityMapsException,
FlatEntityMapsExceptionCode,
@ -38,29 +39,46 @@ export const deleteFlatEntityFromFlatEntityAndRelatedEntityMapsThroughMutationOr
},
);
const idBasedManyToOneRelations = Object.values(
ALL_METADATA_RELATIONS[metadataName].manyToOne,
) as Array<{
metadataName: AllMetadataName;
flatEntityForeignKeyAggregator: keyof MetadataFlatEntity<AllMetadataName>;
foreignKey: keyof MetadataFlatEntity<T>;
} | null>;
const manyToOneRelations = ALL_MANY_TO_ONE_METADATA_RELATIONS[metadataName];
for (const idBasedRelation of idBasedManyToOneRelations) {
if (!isDefined(idBasedRelation)) {
for (const relationPropertyName of Object.keys(manyToOneRelations)) {
const relation = manyToOneRelations[
relationPropertyName as keyof typeof manyToOneRelations
] as {
metadataName: AllMetadataName;
foreignKey: string;
inverseOneToManyProperty: string | null;
} | null;
if (!isDefined(relation)) {
continue;
}
const {
metadataName: relatedMetadataName,
flatEntityForeignKeyAggregator,
foreignKey,
} = idBasedRelation;
inverseOneToManyProperty,
} = relation;
if (!isDefined(flatEntityForeignKeyAggregator)) {
if (!isDefined(inverseOneToManyProperty)) {
continue;
}
const oneToManyRelations =
ALL_ONE_TO_MANY_METADATA_RELATIONS[relatedMetadataName];
const inverseRelation = oneToManyRelations[
inverseOneToManyProperty as keyof typeof oneToManyRelations
] as {
flatEntityForeignKeyAggregator: string;
} | null;
if (!isDefined(inverseRelation)) {
continue;
}
const { flatEntityForeignKeyAggregator } = inverseRelation;
const relatedFlatEntityMapsKey =
getMetadataFlatEntityMapsKey(relatedMetadataName);
@ -68,9 +86,9 @@ export const deleteFlatEntityFromFlatEntityAndRelatedEntityMapsThroughMutationOr
relatedFlatEntityMapsKey as MetadataRelatedFlatEntityMapsKeys<T>
] as FlatEntityMaps<MetadataFlatEntity<typeof relatedMetadataName>>;
const flatEntityRelatedEntityForeignKeyValue = flatEntity[foreignKey] as
| string
| undefined;
const flatEntityRelatedEntityForeignKeyValue = (
flatEntity as unknown as Record<string, string | undefined>
)[foreignKey];
if (!isDefined(flatEntityRelatedEntityForeignKeyValue)) {
continue;
@ -100,9 +118,9 @@ export const deleteFlatEntityFromFlatEntityAndRelatedEntityMapsThroughMutationOr
const updatedRelatedEntity = {
...relatedFlatEntity,
[flatEntityForeignKeyAggregator]: (
relatedFlatEntity[
(relatedFlatEntity as unknown as Record<string, string[]>)[
flatEntityForeignKeyAggregator
] as unknown as string[]
] ?? []
).filter((id) => id !== flatEntity.id),
};

View file

@ -1,17 +1,16 @@
import { type AllMetadataName } from 'twenty-shared/metadata';
import { ALL_METADATA_RELATIONS } 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 { ALL_ONE_TO_MANY_METADATA_RELATIONS } from 'src/engine/metadata-modules/flat-entity/constant/all-one-to-many-metadata-relations.constant';
export const getMetadataEntityRelationProperties = <T extends AllMetadataName>(
metadataName: T,
) => {
const relationProperties = ALL_METADATA_RELATIONS[metadataName];
return [
...Object.keys(relationProperties.manyToOne),
...Object.keys(relationProperties.oneToMany),
...Object.keys(ALL_MANY_TO_ONE_METADATA_RELATIONS[metadataName]),
...Object.keys(ALL_ONE_TO_MANY_METADATA_RELATIONS[metadataName]),
] as (
| keyof (typeof ALL_METADATA_RELATIONS)[T]['manyToOne']
| keyof (typeof ALL_METADATA_RELATIONS)[T]['oneToMany']
| keyof (typeof ALL_MANY_TO_ONE_METADATA_RELATIONS)[T]
| keyof (typeof ALL_ONE_TO_MANY_METADATA_RELATIONS)[T]
)[];
};

View file

@ -1,14 +1,12 @@
import { type AllMetadataName } from 'twenty-shared/metadata';
import { ALL_METADATA_RELATIONS } 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 { type MetadataManyToOneRelatedMetadataNames } from 'src/engine/metadata-modules/flat-entity/types/metadata-many-to-one-related-metadata-names.type';
export const getMetadataManyToOneRelatedNames = <T extends AllMetadataName>(
metadataName: T,
): MetadataManyToOneRelatedMetadataNames<T>[] => {
const relations = ALL_METADATA_RELATIONS[metadataName];
return Object.values(relations.manyToOne)
return Object.values(ALL_MANY_TO_ONE_METADATA_RELATIONS[metadataName])
.filter((relation) => relation !== null)
.map(
(relation) => relation.metadataName,

View file

@ -1,14 +1,12 @@
import { type AllMetadataName } from 'twenty-shared/metadata';
import { ALL_METADATA_RELATIONS } from 'src/engine/metadata-modules/flat-entity/constant/all-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 { type MetadataOneToManyRelatedMetadataNames } from 'src/engine/metadata-modules/flat-entity/types/metadata-one-to-many-related-metadata-names.type';
export const getMetadataOneToManyRelatedNames = <T extends AllMetadataName>(
metadataName: T,
): MetadataOneToManyRelatedMetadataNames<T>[] => {
const relations = ALL_METADATA_RELATIONS[metadataName];
return Object.values(relations.oneToMany)
return Object.values(ALL_ONE_TO_MANY_METADATA_RELATIONS[metadataName])
.filter((relation) => relation !== null)
.map(
(relation) => relation.metadataName,

View file

@ -1,15 +1,11 @@
import { type AllMetadataName } from 'twenty-shared/metadata';
import { ALL_METADATA_RELATIONS } from 'src/engine/metadata-modules/flat-entity/constant/all-metadata-relations.constant';
import { ALL_METADATA_SERIALIZED_RELATION } from 'src/engine/metadata-modules/flat-entity/constant/all-metadata-serialized-relation.constant';
export const getMetadataSerializedRelationNames = (
metadataName: AllMetadataName,
): AllMetadataName[] => {
const relations = ALL_METADATA_RELATIONS[metadataName];
if (!('serializedRelations' in relations)) {
return [];
}
return Object.keys(relations.serializedRelations) as AllMetadataName[];
return Object.keys(
ALL_METADATA_SERIALIZED_RELATION[metadataName],
) as AllMetadataName[];
};

View file

@ -1,25 +1,19 @@
import { t } from '@lingui/core/macro';
import { type AllMetadataName } from 'twenty-shared/metadata';
import { isDefined } from 'twenty-shared/utils';
import { type RemoveSuffix } from 'twenty-shared/types';
import {
ALL_METADATA_RELATIONS,
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 MetadataManyToOneJoinColumn } from 'src/engine/metadata-modules/flat-entity/types/metadata-many-to-one-join-column.type';
import { type MetadataToFlatEntityMapsKey } from 'src/engine/metadata-modules/flat-entity/types/metadata-to-flat-entity-maps-key';
import { getMetadataFlatEntityMapsKey } from 'src/engine/metadata-modules/flat-entity/utils/get-metadata-flat-entity-maps-key.util';
type ManyToOneConfig<T extends AllMetadataName> =
(typeof ALL_METADATA_RELATIONS)[T]['manyToOne'];
(typeof ALL_MANY_TO_ONE_METADATA_RELATIONS)[T];
type TargetMetadataNamesForForeignKeys<
T extends AllMetadataName,
@ -49,9 +43,10 @@ type ResolvedUniversalIdentifiers<
> = {
[K in keyof ManyToOneConfig<T> as ManyToOneConfig<T>[K] extends {
foreignKey: infer FK extends string;
universalForeignKey: infer UFK extends string;
}
? FK extends TProvidedKeys
? `${RemoveSuffix<FK, 'Id'>}UniversalIdentifier`
? UFK
: never
: never]: ManyToOneConfig<T>[K] extends { isNullable: true }
? string | null
@ -73,15 +68,19 @@ export const resolveEntityRelationUniversalIdentifiers = <
foreignKeyValues: Record<TProvidedKeys, string | null | undefined>;
flatEntityMaps: RequiredFlatEntityMapsForForeignKeys<T, TProvidedKeys>;
}): ResolvedUniversalIdentifiers<T, TProvidedKeys> => {
const relations = ALL_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 MetadataManyToOneRelationConfiguration<
T,
ExtractEntityManyToOneEntityRelationProperties<MetadataEntity<T>>
>[]) {
for (const relationPropertyName of Object.keys(relationEntries)) {
const relation = relationEntries[
relationPropertyName as keyof typeof relationEntries
] as {
foreignKey: string;
metadataName: AllMetadataName;
isNullable: boolean;
universalForeignKey: string;
} | null;
if (!isDefined(relation)) {
continue;
}
@ -90,6 +89,7 @@ export const resolveEntityRelationUniversalIdentifiers = <
foreignKey,
metadataName: targetMetadataName,
isNullable,
universalForeignKey,
} = relation;
if (!Object.prototype.hasOwnProperty.call(foreignKeyValues, foreignKey)) {
@ -104,14 +104,8 @@ export const resolveEntityRelationUniversalIdentifiers = <
) as keyof RequiredFlatEntityMapsForForeignKeys<T, TProvidedKeys>;
const targetFlatEntityMaps = flatEntityMaps[mapsKey];
// TODO refactor using the new ALL_METADATA_UNIVERSAL_RELATION afterwards
const universalIdentifierKey = foreignKey.replace(
/Id$/,
'UniversalIdentifier',
);
if (isNullable && !isDefined(foreignKeyValue)) {
result[universalIdentifierKey] = null;
result[universalForeignKey] = null;
continue;
}
@ -126,7 +120,7 @@ export const resolveEntityRelationUniversalIdentifiers = <
);
}
result[universalIdentifierKey] = resolvedUniversalIdentifier;
result[universalForeignKey] = resolvedUniversalIdentifier;
}
return result as ResolvedUniversalIdentifiers<T, TProvidedKeys>;

View file

@ -2,7 +2,7 @@ import { type FlatEntityFrom } from 'src/engine/metadata-modules/flat-entity/typ
import { type ObjectMetadataEntity } from 'src/engine/metadata-modules/object-metadata/object-metadata.entity';
type BaseFlatObjectMetadata = FlatEntityFrom<
Omit<ObjectMetadataEntity, 'targetRelationFields' | 'dataSourceId'>
Omit<ObjectMetadataEntity, 'dataSourceId'>
>;
export type FlatObjectMetadata = BaseFlatObjectMetadata & {
// NOTE: below fields are not reflected on the final UniversalFlatEntity either they should we should define a common source

View file

@ -117,12 +117,6 @@ export class ObjectMetadataEntity
})
indexMetadatas: Relation<IndexMetadataEntity[]>;
@OneToMany(
() => FieldMetadataEntity,
(field) => field.relationTargetObjectMetadataId,
)
targetRelationFields: Relation<FieldMetadataEntity[]>;
@ManyToOne(() => DataSourceEntity, (dataSource) => dataSource.objects, {
onDelete: 'CASCADE',
})

View file

@ -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": [],

View file

@ -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>,
);
}
}

View file

@ -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;

View file

@ -0,0 +1,2 @@
export type ToUniversalForeignKey<T extends string> =
T extends `${infer Prefix}Id` ? `${Prefix}UniversalIdentifier` : never;

View file

@ -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>

View file

@ -4,7 +4,6 @@ import { type UniversalFlatEntityFrom } from 'src/engine/workspace-manager/works
export type UniversalFlatObjectMetadata = UniversalFlatEntityFrom<
Omit<
ObjectMetadataEntity,
| 'targetRelationFields'
| 'dataSourceId'
| 'labelIdentifierFieldMetadataId'
| 'imageIdentifierFieldMetadataId'

View file

@ -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);

View file

@ -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);

View file

@ -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(

View file

@ -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;
}

View file

@ -46,7 +46,6 @@ export const getMockObjectMetadataEntity = (
universalIdentifier: faker.string.uuid(),
applicationId: faker.string.uuid(),
application: {} as ApplicationEntity,
targetRelationFields: [],
standardOverrides: null,
targetTableName: faker.string.uuid(),
views: [],

View file

@ -79,8 +79,6 @@ exports[`syncApplication should return workspace migration actions on initial sy
"icon": null,
"isEditable": true,
"label": "Test Role",
"rowLevelPermissionPredicateGroupUniversalIdentifiers": [],
"rowLevelPermissionPredicateUniversalIdentifiers": [],
"universalIdentifier": Any<String>,
"updatedAt": Any<String>,
},
@ -123,8 +121,6 @@ exports[`syncApplication should return workspace migration actions on initial sy
"icon": null,
"isEditable": true,
"label": "Viewer Role",
"rowLevelPermissionPredicateGroupUniversalIdentifiers": [],
"rowLevelPermissionPredicateUniversalIdentifiers": [],
"universalIdentifier": Any<String>,
"updatedAt": Any<String>,
},