[BREAKING_CHANGE_NESTED_WORKSPACE]Refactor FlatEntity typing in aim of introducing UniversalFlatEntity (#16701)

# Introduction
Added a `WorkspaceRelated` and `AllNonWorkspaceRelatedEntity` to
simplify the `FlatEntityFrom` that now do not expect a string literal to
omit and itself builds the related many to one entities foreign key
aggregators

We now have the type grain over relation to syncable or just workspace
related entities

Added a migrations that sets the fk on missing entities

## Next
In upcoming PR we will be able to introduce such below type
```ts
import { type CastRecordTypeOrmDatePropertiesToString } from 'src/engine/metadata-modules/flat-entity/types/cast-record-typeorm-date-properties-to-string.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 ExtractEntityRelatedEntityProperties } from 'src/engine/metadata-modules/flat-entity/types/extract-entity-related-entity-properties.type';
import { type RemoveSuffix } from 'src/engine/workspace-manager/workspace-migration-v2/workspace-migration-builder-v2/types/remove-suffix.type';
import { type SyncableEntity } from 'src/engine/workspace-manager/workspace-sync/types/syncable-entity.interface';

export type UniversalFlatEntityFrom<TEntity extends SyncableEntity> = Omit<
  TEntity,
  | `${ExtractEntityManyToOneEntityRelationProperties<TEntity> & string}Id`
  | ExtractEntityRelatedEntityProperties<TEntity>
  | 'application'
  | 'workspaceId'
  | 'applicationId'
  | keyof CastRecordTypeOrmDatePropertiesToString<TEntity>
> &
  CastRecordTypeOrmDatePropertiesToString<TEntity> & {
    [P in ExtractEntityManyToOneEntityRelationProperties<TEntity> &
      string as `${RemoveSuffix<P, 's'>}UniversalIdentifier`]: string;
  } & {
    [P in ExtractEntityOneToManyEntityRelationProperties<
      TEntity,
      SyncableEntity
    > &
      string as `${RemoveSuffix<P, 's'>}UniversalIdentifiers`]: string[];
  };

```
This commit is contained in:
Paul Rastoin
2025-12-30 13:47:02 +00:00
committed by GitHub
parent 720348c583
commit e3ffdb0c2b
101 changed files with 559 additions and 874 deletions
@@ -0,0 +1,133 @@
import { type AllMetadataName } from 'twenty-shared/metadata';
import { type ExtractEntityRelatedEntityProperties } from 'src/engine/metadata-modules/flat-entity/types/extract-entity-related-entity-properties.type';
import { type MetadataEntity } from 'src/engine/metadata-modules/flat-entity/types/metadata-entity.type';
export const ALL_METADATA_RELATION_PROPERTIES = {
agent: {
workspace: true,
application: true,
},
fieldMetadata: {
relationTargetFieldMetadata: true,
relationTargetObjectMetadata: true,
fieldPermissions: true,
indexFieldMetadatas: true,
object: true,
viewFields: true,
viewFilters: true,
kanbanAggregateOperationViews: true,
calendarViews: true,
mainGroupByFieldMetadataViews: true,
workspace: true,
application: true,
},
objectMetadata: {
fields: true,
indexMetadatas: true,
targetRelationFields: true,
dataSource: true,
objectPermissions: true,
fieldPermissions: true,
views: true,
workspace: true,
application: true,
},
view: {
objectMetadata: true,
viewFields: true,
viewFilters: true,
viewFilterGroups: true,
viewGroups: true,
viewSorts: true,
workspace: true,
createdBy: true,
application: true,
calendarFieldMetadata: true,
kanbanAggregateOperationFieldMetadata: true,
mainGroupByFieldMetadata: true,
},
viewField: {
fieldMetadata: true,
view: true,
workspace: true,
application: true,
},
viewFilter: {
fieldMetadata: true,
view: true,
viewFilterGroup: true,
workspace: true,
application: true,
},
viewGroup: {
view: true,
workspace: true,
application: true,
},
index: {
objectMetadata: true,
indexFieldMetadatas: true,
workspace: true,
application: true,
},
serverlessFunction: {
cronTriggers: true,
databaseEventTriggers: true,
routeTriggers: true,
serverlessFunctionLayer: true,
workspace: true,
application: true,
},
cronTrigger: {
serverlessFunction: true,
workspace: true,
application: true,
},
databaseEventTrigger: {
serverlessFunction: true,
workspace: true,
application: true,
},
routeTrigger: {
serverlessFunction: true,
workspace: true,
application: true,
},
role: {
roleTargets: true,
objectPermissions: true,
permissionFlags: true,
fieldPermissions: true,
workspace: true,
application: true,
},
roleTarget: {
role: true,
apiKey: true,
workspace: true,
application: true,
},
pageLayout: {
workspace: true,
objectMetadata: true,
tabs: true,
application: true,
},
pageLayoutTab: {
workspace: true,
pageLayout: true,
widgets: true,
application: true,
},
pageLayoutWidget: {
workspace: true,
pageLayoutTab: true,
objectMetadata: true,
application: true,
},
} as const satisfies {
[TName in AllMetadataName]: {
[P in ExtractEntityRelatedEntityProperties<MetadataEntity<TName>>]: true;
};
};
@@ -0,0 +1,17 @@
import { type Relation } from 'typeorm';
import { type AllNonWorkspaceRelatedEntity } from 'src/engine/workspace-manager/workspace-sync/types/all-non-workspace-related-entity.type';
import { type WorkspaceRelatedEntity } from 'src/engine/workspace-manager/workspace-sync/types/workspace-related-entity';
export type ExtractEntityManyToOneEntityRelationProperties<
T,
TTarget = WorkspaceRelatedEntity | AllNonWorkspaceRelatedEntity,
> = NonNullable<
{
[P in keyof T]: [NonNullable<T[P]>] extends [never]
? never
: NonNullable<T[P]> extends Relation<TTarget>
? P
: never;
}[keyof T]
>;
@@ -0,0 +1,17 @@
import { type Relation } from 'typeorm';
import { type AllNonWorkspaceRelatedEntity } from 'src/engine/workspace-manager/workspace-sync/types/all-non-workspace-related-entity.type';
import { type WorkspaceRelatedEntity } from 'src/engine/workspace-manager/workspace-sync/types/workspace-related-entity';
export type ExtractEntityOneToManyEntityRelationProperties<
T,
TTarget = WorkspaceRelatedEntity | AllNonWorkspaceRelatedEntity,
> = NonNullable<
{
[P in keyof T]: NonNullable<T[P]> extends Array<infer U>
? U extends Relation<TTarget>
? P
: never
: never;
}[keyof T]
>;
@@ -0,0 +1,11 @@
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 AllNonWorkspaceRelatedEntity } from 'src/engine/workspace-manager/workspace-sync/types/all-non-workspace-related-entity.type';
import { type WorkspaceRelatedEntity } from 'src/engine/workspace-manager/workspace-sync/types/workspace-related-entity';
export type ExtractEntityRelatedEntityProperties<
T,
TTarget = WorkspaceRelatedEntity | AllNonWorkspaceRelatedEntity,
> =
| ExtractEntityManyToOneEntityRelationProperties<T, TTarget>
| ExtractEntityOneToManyEntityRelationProperties<T, TTarget>;
@@ -1,22 +1,28 @@
import { type SyncableEntity } from 'src/engine/workspace-manager/workspace-sync/interfaces/syncable-entity.interface';
import { type CastRecordTypeOrmDatePropertiesToString } from 'src/engine/metadata-modules/flat-entity/types/cast-record-typeorm-date-properties-to-string.type';
import { type ExtractEntityOneToManyEntityRelationProperties } from 'src/engine/metadata-modules/flat-entity/types/extract-entity-one-to-many-entity-relation-properties.type';
import { type ExtractEntityRelatedEntityProperties } from 'src/engine/metadata-modules/flat-entity/types/extract-entity-related-entity-properties.type';
import { type RemoveSuffix } from 'src/engine/workspace-manager/workspace-migration-v2/workspace-migration-builder-v2/types/remove-suffix.type';
import { type SyncableEntity } from 'src/engine/workspace-manager/workspace-sync/types/syncable-entity.interface';
import { type NonNullableProperties } from 'src/types/non-nullable-properties.type';
export type SyncableFlatEntity = NonNullableProperties<
Omit<SyncableEntity, 'application' | 'applicationId'>
Omit<SyncableEntity, 'application' | 'applicationId' | 'workspace'>
> & {
id: string;
applicationId: string | null;
};
export type FlatEntityFrom<
export type FlatEntityFrom<TEntity> = Omit<
TEntity,
TEntityRelationProperties extends keyof TEntity,
> = Omit<
TEntity,
| TEntityRelationProperties
| ExtractEntityRelatedEntityProperties<TEntity>
| 'application'
| 'workspace'
| keyof CastRecordTypeOrmDatePropertiesToString<TEntity>
> &
CastRecordTypeOrmDatePropertiesToString<TEntity>;
CastRecordTypeOrmDatePropertiesToString<TEntity> & {
[P in ExtractEntityOneToManyEntityRelationProperties<
TEntity,
SyncableEntity
> &
string as `${RemoveSuffix<P, 's'>}Ids`]: string[];
};
@@ -0,0 +1,12 @@
import { type AllMetadataName } from 'twenty-shared/metadata';
import { type AllFlatEntityTypesByMetadataName } from 'src/engine/metadata-modules/flat-entity/types/all-flat-entity-types-by-metadata-name';
import { type SyncableEntity } from 'src/engine/workspace-manager/workspace-sync/types/syncable-entity.interface';
export type FromMetadataEntityToMetadataName<T extends SyncableEntity> = {
[P in AllMetadataName]: AllFlatEntityTypesByMetadataName[P] extends {
entity: T;
}
? P
: never;
}[AllMetadataName];