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:
+1
-1
@@ -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
|
||||
|
||||
+253
@@ -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
|
||||
>,
|
||||
];
|
||||
+433
@@ -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
|
||||
>,
|
||||
];
|
||||
-537
@@ -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
|
||||
>,
|
||||
];
|
||||
+1
-1
@@ -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,
|
||||
|
||||
+53
@@ -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
|
||||
>,
|
||||
];
|
||||
+215
@@ -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
|
||||
>,
|
||||
];
|
||||
-1
@@ -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,
|
||||
|
||||
+2
-2
@@ -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]>;
|
||||
|
||||
+2
-2
@@ -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
|
||||
>;
|
||||
|
||||
+2
-2
@@ -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
|
||||
>;
|
||||
|
||||
+2
-2
@@ -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",
|
||||
]
|
||||
`;
|
||||
|
||||
+36
-18
@@ -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,
|
||||
],
|
||||
};
|
||||
|
||||
+36
-18
@@ -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),
|
||||
};
|
||||
|
||||
|
||||
+6
-7
@@ -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]
|
||||
)[];
|
||||
};
|
||||
|
||||
+2
-4
@@ -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,
|
||||
|
||||
+2
-4
@@ -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,
|
||||
|
||||
+4
-8
@@ -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[];
|
||||
};
|
||||
|
||||
+18
-24
@@ -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>;
|
||||
|
||||
Reference in New Issue
Block a user