V2 centralize relation optimistic logic (#15552)
# Introduction
This PR aims to deprecate having to manually handle optimistic side
effect foreign key addition in the whole v2 experience.
This PR implements the strong basis + builder refactor of the optimistic
computation of a given flat entity maps with its related flat entity
maps ( runner needs a small refactor on actions type definition first )
Flat entity maps updates through mutations are now only scoped to the
generic entity builder ( very isolated )
## What's next
- Refactor actions v2 type definition to gain grain over `metadataName`
and action operation ( `create` `delete` `update` ).
from `{type: 'create_view_field'}` to `{metadataName: 'view_field',
type: 'create' }`
- Use new optimistic tool computation tools
- Only invalidate impacted flat maps cache
## New tools
Strictly dynamically typed new flat entity maps tools
- `addFlatEntityToFlatEntityAndRelatedEntityMapsThroughMutationOrThrow`
-
`deleteFlatEntityFromFlatEntityAndRelatedEntityMapsThroughMutationOrThrow`
## Unit test
Adding basic unit testing coverage to introduced tools
## `FlatEntityValidationArgs`
From
```ts
export type FlatEntityValidationArgs<T extends AllMetadataName> = {
flatEntityToValidate: MetadataFlatEntity<T>;
optimisticFlatEntityMaps: MetadataFlatEntityMaps<T>;
mutableDependencyOptimisticFlatEntityMaps: MetadataValidationRelatedFlatEntityMaps<T>;
workspaceId: string;
remainingFlatEntityMapsToValidate: MetadataFlatEntityMaps<T>;
buildOptions: WorkspaceMigrationBuilderOptions;
};
```
To
```ts
export type FlatEntityValidationArgs<T extends AllMetadataName> = {
flatEntityToValidate: MetadataFlatEntity<T>;
optimisticFlatEntityMapsAndRelatedFlatEntityMaps: MetadataFlatEntityAndRelatedFlatEntityMapsForValidation<T>;
workspaceId: string;
remainingFlatEntityMapsToValidate: MetadataFlatEntityMaps<T>;
buildOptions: WorkspaceMigrationBuilderOptions;
};
```
This commit is contained in:
+64
-16
@@ -2,6 +2,7 @@ import { type AllMetadataName } from 'twenty-shared/metadata';
|
||||
import { type ExtractPropertiesThatEndsWithId } from 'twenty-shared/types';
|
||||
|
||||
import { type MetadataEntity } from 'src/engine/metadata-modules/flat-entity/types/metadata-entity.type';
|
||||
import { type MetadataFlatEntity } from 'src/engine/metadata-modules/flat-entity/types/metadata-flat-entity.type';
|
||||
|
||||
type ExtractEntityRelations<TEntity extends MetadataEntity<AllMetadataName>> = {
|
||||
[K in ExtractPropertiesThatEndsWithId<TEntity, 'id' | 'workspaceId'>]: K;
|
||||
@@ -11,46 +12,93 @@ type MetadataRelatedMetadataNames<T extends AllMetadataName> =
|
||||
keyof ExtractEntityRelations<MetadataEntity<T>>;
|
||||
|
||||
type MetadataNameAndRelations = {
|
||||
[T in AllMetadataName]: MetadataRelatedMetadataNames<T> extends never
|
||||
[TSourceMetadataName in AllMetadataName]: MetadataRelatedMetadataNames<TSourceMetadataName> extends never
|
||||
? Record<string, never>
|
||||
: {
|
||||
[P in MetadataRelatedMetadataNames<T>]?: AllMetadataName;
|
||||
[K in MetadataRelatedMetadataNames<TSourceMetadataName>]?: {
|
||||
[TTargetMetadataName in AllMetadataName]?: {
|
||||
metadataName: TTargetMetadataName;
|
||||
flatEntityForeignKeyAggregator: keyof MetadataFlatEntity<TTargetMetadataName>;
|
||||
};
|
||||
}[AllMetadataName];
|
||||
};
|
||||
};
|
||||
|
||||
export const ALL_METADATA_RELATED_METADATA_BY_FOREIGN_KEY = {
|
||||
fieldMetadata: {
|
||||
objectMetadataId: 'objectMetadata',
|
||||
objectMetadataId: {
|
||||
metadataName: 'objectMetadata',
|
||||
flatEntityForeignKeyAggregator: 'fieldMetadataIds',
|
||||
},
|
||||
},
|
||||
objectMetadata: {},
|
||||
view: {
|
||||
kanbanAggregateOperationFieldMetadataId: 'fieldMetadata',
|
||||
calendarFieldMetadataId: 'fieldMetadata',
|
||||
objectMetadataId: 'objectMetadata',
|
||||
kanbanAggregateOperationFieldMetadataId: {
|
||||
metadataName: 'fieldMetadata',
|
||||
flatEntityForeignKeyAggregator: 'kanbanAggregateOperationViewIds',
|
||||
},
|
||||
calendarFieldMetadataId: {
|
||||
metadataName: 'fieldMetadata',
|
||||
flatEntityForeignKeyAggregator: 'calendarViewIds',
|
||||
},
|
||||
objectMetadataId: {
|
||||
metadataName: 'objectMetadata',
|
||||
flatEntityForeignKeyAggregator: 'viewIds',
|
||||
},
|
||||
},
|
||||
viewField: {
|
||||
viewId: 'view',
|
||||
fieldMetadataId: 'fieldMetadata',
|
||||
viewId: {
|
||||
metadataName: 'view',
|
||||
flatEntityForeignKeyAggregator: 'viewFieldIds',
|
||||
},
|
||||
fieldMetadataId: {
|
||||
metadataName: 'fieldMetadata',
|
||||
flatEntityForeignKeyAggregator: 'viewFieldIds',
|
||||
},
|
||||
},
|
||||
viewGroup: {
|
||||
viewId: 'view',
|
||||
fieldMetadataId: 'fieldMetadata',
|
||||
viewId: {
|
||||
metadataName: 'view',
|
||||
flatEntityForeignKeyAggregator: 'viewGroupIds',
|
||||
},
|
||||
fieldMetadataId: {
|
||||
metadataName: 'fieldMetadata',
|
||||
flatEntityForeignKeyAggregator: 'viewGroupIds',
|
||||
},
|
||||
},
|
||||
index: {
|
||||
objectMetadataId: 'objectMetadata',
|
||||
objectMetadataId: {
|
||||
metadataName: 'objectMetadata',
|
||||
flatEntityForeignKeyAggregator: 'indexMetadataIds',
|
||||
},
|
||||
},
|
||||
serverlessFunction: {},
|
||||
cronTrigger: {
|
||||
serverlessFunctionId: 'serverlessFunction',
|
||||
serverlessFunctionId: {
|
||||
metadataName: 'serverlessFunction',
|
||||
flatEntityForeignKeyAggregator: 'cronTriggerIds',
|
||||
},
|
||||
},
|
||||
databaseEventTrigger: {
|
||||
serverlessFunctionId: 'serverlessFunction',
|
||||
serverlessFunctionId: {
|
||||
metadataName: 'serverlessFunction',
|
||||
flatEntityForeignKeyAggregator: 'databaseEventTriggerIds',
|
||||
},
|
||||
},
|
||||
routeTrigger: {
|
||||
serverlessFunctionId: 'serverlessFunction',
|
||||
serverlessFunctionId: {
|
||||
metadataName: 'serverlessFunction',
|
||||
flatEntityForeignKeyAggregator: 'routeTriggerIds',
|
||||
},
|
||||
},
|
||||
viewFilter: {
|
||||
viewId: 'view',
|
||||
fieldMetadataId: 'fieldMetadata',
|
||||
viewId: {
|
||||
metadataName: 'view',
|
||||
flatEntityForeignKeyAggregator: 'viewFilterIds',
|
||||
},
|
||||
fieldMetadataId: {
|
||||
metadataName: 'fieldMetadata',
|
||||
flatEntityForeignKeyAggregator: 'viewFilterIds',
|
||||
},
|
||||
},
|
||||
} as const satisfies MetadataNameAndRelations;
|
||||
|
||||
+15
@@ -0,0 +1,15 @@
|
||||
import { type AllMetadataName } from 'twenty-shared/metadata';
|
||||
|
||||
import { type AllFlatEntityMaps } from 'src/engine/metadata-modules/flat-entity/types/all-flat-entity-maps.type';
|
||||
import { type MetadataRelatedFlatEntityMapsKeys } from 'src/engine/metadata-modules/flat-entity/types/metadata-related-flat-entity-maps-keys.type';
|
||||
import { type MetadataToFlatEntityMapsKey } from 'src/engine/metadata-modules/flat-entity/types/metadata-to-flat-entity-maps-key';
|
||||
import { type MetadataValidationRelatedMetadataNames } from 'src/engine/metadata-modules/flat-entity/types/metadata-validation-related-metadata-names.type';
|
||||
|
||||
export type MetadataFlatEntityAndRelatedFlatEntityMapsForValidation<
|
||||
T extends AllMetadataName,
|
||||
> = Pick<
|
||||
AllFlatEntityMaps,
|
||||
| MetadataRelatedFlatEntityMapsKeys<T>
|
||||
| MetadataToFlatEntityMapsKey<T>
|
||||
| MetadataToFlatEntityMapsKey<MetadataValidationRelatedMetadataNames<T>>
|
||||
>;
|
||||
+2
-2
@@ -6,5 +6,5 @@ import { type MetadataManyToOneJoinColumn } from 'src/engine/metadata-modules/fl
|
||||
export type MetadataManyToOneRelatedMetadataNames<T extends AllMetadataName> =
|
||||
Extract<
|
||||
(typeof ALL_METADATA_RELATED_METADATA_BY_FOREIGN_KEY)[T][MetadataManyToOneJoinColumn<T>],
|
||||
AllMetadataName
|
||||
>;
|
||||
{ metadataName: AllMetadataName }
|
||||
>['metadataName'];
|
||||
|
||||
+4
-11
@@ -1,16 +1,9 @@
|
||||
import { type IsEmptyRecord } from 'twenty-shared/types';
|
||||
import { type AllMetadataName } from 'twenty-shared/metadata';
|
||||
|
||||
import { type ALL_METADATA_REQUIRED_METADATA_FOR_VALIDATION } from 'src/engine/metadata-modules/flat-entity/constant/all-metadata-required-metadata-for-validation.constant';
|
||||
|
||||
export type MetadataValidationRelatedMetadataNames<T extends AllMetadataName> =
|
||||
IsEmptyRecord<
|
||||
(typeof ALL_METADATA_REQUIRED_METADATA_FOR_VALIDATION)[T]
|
||||
> extends true
|
||||
? undefined
|
||||
: NonNullable<
|
||||
Extract<
|
||||
keyof (typeof ALL_METADATA_REQUIRED_METADATA_FOR_VALIDATION)[T],
|
||||
AllMetadataName
|
||||
>
|
||||
>;
|
||||
Extract<
|
||||
keyof (typeof ALL_METADATA_REQUIRED_METADATA_FOR_VALIDATION)[T],
|
||||
AllMetadataName
|
||||
>;
|
||||
|
||||
+94
@@ -0,0 +1,94 @@
|
||||
import { FieldMetadataType } from 'twenty-shared/types';
|
||||
|
||||
import { createEmptyFlatEntityMaps } from 'src/engine/metadata-modules/flat-entity/constant/create-empty-flat-entity-maps.constant';
|
||||
import { type MetadataFlatEntityAndRelatedFlatEntityMaps } from 'src/engine/metadata-modules/flat-entity/types/metadata-related-types.type';
|
||||
import { addFlatEntityToFlatEntityAndRelatedEntityMapsThroughMutationOrThrow } from 'src/engine/metadata-modules/flat-entity/utils/add-flat-entity-to-flat-entity-and-related-entity-maps-through-mutation-or-throw.util';
|
||||
import { addFlatEntityToFlatEntityMapsOrThrow } from 'src/engine/metadata-modules/flat-entity/utils/add-flat-entity-to-flat-entity-maps-or-throw.util';
|
||||
import { getFlatFieldMetadataMock } from 'src/engine/metadata-modules/flat-field-metadata/__mocks__/get-flat-field-metadata.mock';
|
||||
import { type FlatFieldMetadata } from 'src/engine/metadata-modules/flat-field-metadata/types/flat-field-metadata.type';
|
||||
import { getFlatObjectMetadataMock } from 'src/engine/metadata-modules/flat-object-metadata/__mocks__/get-flat-object-metadata.mock';
|
||||
import { type FlatObjectMetadata } from 'src/engine/metadata-modules/flat-object-metadata/types/flat-object-metadata.type';
|
||||
import { type FlatView } from 'src/engine/metadata-modules/flat-view/types/flat-view.type';
|
||||
|
||||
describe('addFlatEntityToFlatEntityAndRelatedEntityMapsThroughMutationOrThrow', () => {
|
||||
it('should add a view and update related objectMetadata with viewId', () => {
|
||||
const objectMetadataId = 'object-1';
|
||||
const viewId = 'view-1';
|
||||
const applicationId = '20202020-f3ad-452e-b5b6-2d49d3ea88b1';
|
||||
const workspaceId = '20202020-bc64-4148-8a79-b3144f743694';
|
||||
const mockObjectMetadata = getFlatObjectMetadataMock({
|
||||
id: objectMetadataId,
|
||||
universalIdentifier: 'object-universal-1',
|
||||
viewIds: [],
|
||||
fieldMetadataIds: [],
|
||||
workspaceId,
|
||||
imageIdentifierFieldMetadataId: '20202020-9d65-415f-b0e1-216a2e257ea4',
|
||||
labelIdentifierFieldMetadataId: '20202020-1a62-405c-87fa-4d4fd215851b',
|
||||
applicationId,
|
||||
});
|
||||
|
||||
const mockFieldMEtadata = getFlatFieldMetadataMock({
|
||||
objectMetadataId,
|
||||
id: '202020-71a3-4856-a3d0-d08cea0ecec6',
|
||||
type: FieldMetadataType.DATE,
|
||||
workspaceId,
|
||||
applicationId,
|
||||
universalIdentifier: 'field-universal-1',
|
||||
viewFieldIds: [],
|
||||
viewGroupIds: [],
|
||||
viewFilterIds: [],
|
||||
calendarViewIds: [],
|
||||
});
|
||||
|
||||
const mockView: Pick<FlatView, 'id'> & Partial<FlatView> = {
|
||||
id: viewId,
|
||||
workspaceId,
|
||||
universalIdentifier: 'view-universal-1',
|
||||
objectMetadataId: objectMetadataId,
|
||||
viewFieldIds: [],
|
||||
viewFilterIds: [],
|
||||
viewGroupIds: [],
|
||||
applicationId,
|
||||
calendarFieldMetadataId: mockFieldMEtadata.id,
|
||||
};
|
||||
|
||||
const flatEntityAndRelatedMapsToMutate: MetadataFlatEntityAndRelatedFlatEntityMaps<'view'> =
|
||||
{
|
||||
flatFieldMetadataMaps: addFlatEntityToFlatEntityMapsOrThrow({
|
||||
flatEntity: mockFieldMEtadata,
|
||||
flatEntityMaps: createEmptyFlatEntityMaps(),
|
||||
}),
|
||||
flatObjectMetadataMaps: addFlatEntityToFlatEntityMapsOrThrow({
|
||||
flatEntity: mockObjectMetadata,
|
||||
flatEntityMaps: createEmptyFlatEntityMaps(),
|
||||
}),
|
||||
flatViewMaps: createEmptyFlatEntityMaps(),
|
||||
};
|
||||
|
||||
addFlatEntityToFlatEntityAndRelatedEntityMapsThroughMutationOrThrow({
|
||||
metadataName: 'view',
|
||||
flatEntity: mockView as FlatView,
|
||||
flatEntityAndRelatedMapsToMutate,
|
||||
});
|
||||
|
||||
expect(
|
||||
flatEntityAndRelatedMapsToMutate.flatViewMaps.byId[mockView.id],
|
||||
).toMatchObject(mockView);
|
||||
|
||||
expect(
|
||||
flatEntityAndRelatedMapsToMutate.flatObjectMetadataMaps.byId[
|
||||
objectMetadataId
|
||||
],
|
||||
).toMatchObject<Partial<FlatObjectMetadata>>({
|
||||
viewIds: [mockView.id],
|
||||
});
|
||||
|
||||
expect(
|
||||
flatEntityAndRelatedMapsToMutate.flatFieldMetadataMaps.byId[
|
||||
mockFieldMEtadata.id
|
||||
],
|
||||
).toMatchObject<Partial<FlatFieldMetadata>>({
|
||||
calendarViewIds: [mockView.id],
|
||||
});
|
||||
});
|
||||
});
|
||||
+104
@@ -0,0 +1,104 @@
|
||||
import { FieldMetadataType } from 'twenty-shared/types';
|
||||
|
||||
import { createEmptyFlatEntityMaps } from 'src/engine/metadata-modules/flat-entity/constant/create-empty-flat-entity-maps.constant';
|
||||
import { type MetadataFlatEntityAndRelatedFlatEntityMaps } from 'src/engine/metadata-modules/flat-entity/types/metadata-related-types.type';
|
||||
import { addFlatEntityToFlatEntityMapsOrThrow } from 'src/engine/metadata-modules/flat-entity/utils/add-flat-entity-to-flat-entity-maps-or-throw.util';
|
||||
import { deleteFlatEntityFromFlatEntityAndRelatedEntityMapsThroughMutationOrThrow } from 'src/engine/metadata-modules/flat-entity/utils/delete-flat-entity-from-flat-entity-and-related-entity-maps-through-mutation-or-throw.util';
|
||||
import { getFlatFieldMetadataMock } from 'src/engine/metadata-modules/flat-field-metadata/__mocks__/get-flat-field-metadata.mock';
|
||||
import { type FlatFieldMetadata } from 'src/engine/metadata-modules/flat-field-metadata/types/flat-field-metadata.type';
|
||||
import { getFlatObjectMetadataMock } from 'src/engine/metadata-modules/flat-object-metadata/__mocks__/get-flat-object-metadata.mock';
|
||||
import { type FlatObjectMetadata } from 'src/engine/metadata-modules/flat-object-metadata/types/flat-object-metadata.type';
|
||||
import { type FlatView } from 'src/engine/metadata-modules/flat-view/types/flat-view.type';
|
||||
|
||||
describe('deleteFlatEntityFromFlatEntityAndRelatedEntityMapsThroughMutationOrThrow', () => {
|
||||
it('should delete a view and update related objectMetadata and fieldMetadata by removing viewId', () => {
|
||||
const objectMetadataId = 'object-1';
|
||||
const viewId = 'view-1';
|
||||
const applicationId = '20202020-f3ad-452e-b5b6-2d49d3ea88b1';
|
||||
const workspaceId = '20202020-bc64-4148-8a79-b3144f743694';
|
||||
|
||||
const mockObjectMetadata = getFlatObjectMetadataMock({
|
||||
id: objectMetadataId,
|
||||
universalIdentifier: 'object-universal-1',
|
||||
viewIds: [viewId, 'something-else'],
|
||||
fieldMetadataIds: [],
|
||||
workspaceId,
|
||||
imageIdentifierFieldMetadataId: '20202020-9d65-415f-b0e1-216a2e257ea4',
|
||||
labelIdentifierFieldMetadataId: '20202020-1a62-405c-87fa-4d4fd215851b',
|
||||
applicationId,
|
||||
});
|
||||
|
||||
const mockFieldMetadata = getFlatFieldMetadataMock({
|
||||
objectMetadataId,
|
||||
id: '20202020-4087-423b-852a-91f91acf2df2',
|
||||
type: FieldMetadataType.DATE,
|
||||
universalIdentifier: 'field-universal-1',
|
||||
viewFieldIds: [],
|
||||
viewGroupIds: [],
|
||||
viewFilterIds: [],
|
||||
workspaceId,
|
||||
calendarViewIds: [viewId],
|
||||
applicationId,
|
||||
});
|
||||
|
||||
const mockView: Partial<FlatView> = {
|
||||
id: viewId,
|
||||
universalIdentifier: 'view-universal-1',
|
||||
objectMetadataId: objectMetadataId,
|
||||
viewFieldIds: [],
|
||||
viewFilterIds: [],
|
||||
viewGroupIds: [],
|
||||
workspaceId,
|
||||
calendarFieldMetadataId: mockFieldMetadata.id,
|
||||
createdAt: new Date('2024-01-01'),
|
||||
updatedAt: new Date('2024-01-01'),
|
||||
icon: 'icon',
|
||||
isCompact: false,
|
||||
name: 'View Name',
|
||||
position: 0,
|
||||
applicationId,
|
||||
};
|
||||
|
||||
const flatEntityAndRelatedMapsToMutate: MetadataFlatEntityAndRelatedFlatEntityMaps<'view'> =
|
||||
{
|
||||
flatFieldMetadataMaps: addFlatEntityToFlatEntityMapsOrThrow({
|
||||
flatEntity: mockFieldMetadata,
|
||||
flatEntityMaps: createEmptyFlatEntityMaps(),
|
||||
}),
|
||||
flatObjectMetadataMaps: addFlatEntityToFlatEntityMapsOrThrow({
|
||||
flatEntity: mockObjectMetadata,
|
||||
flatEntityMaps: createEmptyFlatEntityMaps(),
|
||||
}),
|
||||
flatViewMaps: addFlatEntityToFlatEntityMapsOrThrow({
|
||||
flatEntity: mockView as FlatView,
|
||||
flatEntityMaps: createEmptyFlatEntityMaps(),
|
||||
}),
|
||||
};
|
||||
|
||||
deleteFlatEntityFromFlatEntityAndRelatedEntityMapsThroughMutationOrThrow({
|
||||
metadataName: 'view',
|
||||
flatEntity: mockView as FlatView,
|
||||
flatEntityAndRelatedMapsToMutate,
|
||||
});
|
||||
|
||||
expect(
|
||||
flatEntityAndRelatedMapsToMutate.flatViewMaps.byId[viewId],
|
||||
).toBeUndefined();
|
||||
|
||||
expect(
|
||||
flatEntityAndRelatedMapsToMutate.flatObjectMetadataMaps.byId[
|
||||
objectMetadataId
|
||||
],
|
||||
).toMatchObject<Partial<FlatObjectMetadata>>({
|
||||
viewIds: ['something-else'],
|
||||
});
|
||||
|
||||
expect(
|
||||
flatEntityAndRelatedMapsToMutate.flatFieldMetadataMaps.byId[
|
||||
mockFieldMetadata.id
|
||||
],
|
||||
).toMatchObject<Partial<FlatFieldMetadata>>({
|
||||
calendarViewIds: [],
|
||||
});
|
||||
});
|
||||
});
|
||||
-90
@@ -1,90 +0,0 @@
|
||||
import { type AllMetadataName } from 'twenty-shared/metadata';
|
||||
|
||||
import { ALL_METADATA_RELATED_METADATA_BY_FOREIGN_KEY } from 'src/engine/metadata-modules/flat-entity/constant/all-metadata-many-to-one-relations.constant';
|
||||
import {
|
||||
FlatEntityMapsException,
|
||||
FlatEntityMapsExceptionCode,
|
||||
} from 'src/engine/metadata-modules/flat-entity/exceptions/flat-entity-maps.exception';
|
||||
import { type MetadataFlatEntity } from 'src/engine/metadata-modules/flat-entity/types/metadata-flat-entity.type';
|
||||
import { type MetadataFlatEntityAndRelatedFlatEntityMaps } from 'src/engine/metadata-modules/flat-entity/types/metadata-related-types.type';
|
||||
import { addFlatEntityToFlatEntityMapsOrThrow } from 'src/engine/metadata-modules/flat-entity/utils/add-flat-entity-to-flat-entity-maps-or-throw.util';
|
||||
import { findFlatEntityByIdInFlatEntityMapsOrThrow } from 'src/engine/metadata-modules/flat-entity/utils/find-flat-entity-by-id-in-flat-entity-maps-or-throw.util';
|
||||
import { getMetadataFlatEntityMapsKey } from 'src/engine/metadata-modules/flat-entity/utils/get-metadata-flat-entity-maps-key.util';
|
||||
|
||||
type AddFlatEntityToFlatEntityAndRelatedEntityMapsOrThrowArgs<
|
||||
T extends AllMetadataName,
|
||||
> = {
|
||||
metadataName: T;
|
||||
flatEntity: MetadataFlatEntity<T>;
|
||||
flatEntityAndRelatedMaps: MetadataFlatEntityAndRelatedFlatEntityMaps<T>;
|
||||
};
|
||||
export const addFlatEntityToFlatEntityAndRelatedEntityMapsOrThrow = <
|
||||
T extends AllMetadataName,
|
||||
>({
|
||||
metadataName,
|
||||
flatEntity,
|
||||
flatEntityAndRelatedMaps: initialFlatEntityAndRelatedMaps,
|
||||
}: AddFlatEntityToFlatEntityAndRelatedEntityMapsOrThrowArgs<T>): MetadataFlatEntityAndRelatedFlatEntityMaps<T> => {
|
||||
const flatEntityMapsKey: keyof MetadataFlatEntityAndRelatedFlatEntityMaps<T> =
|
||||
getMetadataFlatEntityMapsKey(metadataName);
|
||||
|
||||
const updatedFlatEntityMaps = addFlatEntityToFlatEntityMapsOrThrow({
|
||||
flatEntity,
|
||||
flatEntityMaps: initialFlatEntityAndRelatedMaps[flatEntityMapsKey],
|
||||
});
|
||||
|
||||
// TODO refactor
|
||||
const manyToOneRelatedMetadataName = Object.entries(
|
||||
ALL_METADATA_RELATED_METADATA_BY_FOREIGN_KEY[metadataName],
|
||||
);
|
||||
|
||||
return manyToOneRelatedMetadataName.reduce(
|
||||
(flatEntityAndRelatedMaps, [relatedMetadataName, foreignKey]) => {
|
||||
const relatedFlatEntityMapsKey = getMetadataFlatEntityMapsKey(
|
||||
relatedMetadataName as AllMetadataName,
|
||||
);
|
||||
const relatedFLatEntityMetadataMaps =
|
||||
flatEntityAndRelatedMaps[relatedFlatEntityMapsKey];
|
||||
|
||||
const relatedFlatEntity = findFlatEntityByIdInFlatEntityMapsOrThrow({
|
||||
flatEntityId: flatEntity[
|
||||
foreignKey as keyof MetadataFlatEntity<T>
|
||||
] as string,
|
||||
flatEntityMaps: relatedFLatEntityMetadataMaps,
|
||||
});
|
||||
|
||||
const foreignKeyAggregatorProperty = `${metadataName}Ids`;
|
||||
|
||||
if (
|
||||
!Object.prototype.hasOwnProperty.call(
|
||||
relatedFlatEntity,
|
||||
foreignKeyAggregatorProperty,
|
||||
)
|
||||
) {
|
||||
throw new FlatEntityMapsException(
|
||||
'Should never occur, invalid cached format',
|
||||
FlatEntityMapsExceptionCode.ENTITY_MALFORMED,
|
||||
);
|
||||
}
|
||||
|
||||
const updatedRelatedFlatEntityMetadataMaps = {
|
||||
...relatedFlatEntity,
|
||||
[foreignKeyAggregatorProperty]: [
|
||||
...(relatedFlatEntity[
|
||||
foreignKeyAggregatorProperty as keyof MetadataFlatEntity<T>
|
||||
] as string[]),
|
||||
flatEntity.id,
|
||||
],
|
||||
};
|
||||
|
||||
return {
|
||||
...flatEntityAndRelatedMaps,
|
||||
[relatedFlatEntityMapsKey]: updatedRelatedFlatEntityMetadataMaps,
|
||||
};
|
||||
},
|
||||
{
|
||||
...initialFlatEntityAndRelatedMaps,
|
||||
[flatEntityMapsKey]: updatedFlatEntityMaps,
|
||||
},
|
||||
);
|
||||
};
|
||||
+101
@@ -0,0 +1,101 @@
|
||||
import { type AllMetadataName } from 'twenty-shared/metadata';
|
||||
import { isDefined } from 'twenty-shared/utils';
|
||||
|
||||
import { ALL_METADATA_RELATED_METADATA_BY_FOREIGN_KEY } from 'src/engine/metadata-modules/flat-entity/constant/all-metadata-many-to-one-relations.constant';
|
||||
import {
|
||||
FlatEntityMapsException,
|
||||
FlatEntityMapsExceptionCode,
|
||||
} from 'src/engine/metadata-modules/flat-entity/exceptions/flat-entity-maps.exception';
|
||||
import { type FlatEntityMaps } from 'src/engine/metadata-modules/flat-entity/types/flat-entity-maps.type';
|
||||
import { type MetadataFlatEntity } from 'src/engine/metadata-modules/flat-entity/types/metadata-flat-entity.type';
|
||||
import { type MetadataRelatedFlatEntityMapsKeys } from 'src/engine/metadata-modules/flat-entity/types/metadata-related-flat-entity-maps-keys.type';
|
||||
import { type MetadataFlatEntityAndRelatedFlatEntityMaps } from 'src/engine/metadata-modules/flat-entity/types/metadata-related-types.type';
|
||||
import { findFlatEntityByIdInFlatEntityMapsOrThrow } from 'src/engine/metadata-modules/flat-entity/utils/find-flat-entity-by-id-in-flat-entity-maps-or-throw.util';
|
||||
import { getMetadataFlatEntityMapsKey } from 'src/engine/metadata-modules/flat-entity/utils/get-metadata-flat-entity-maps-key.util';
|
||||
import { addFlatEntityToFlatEntityMapsThroughMutationOrThrow } from 'src/engine/workspace-manager/workspace-migration-v2/utils/add-flat-entity-to-flat-entity-maps-through-mutation-or-throw.util';
|
||||
import { replaceFlatEntityInFlatEntityMapsThroughMutationOrThrow } from 'src/engine/workspace-manager/workspace-migration-v2/utils/replace-flat-entity-in-flat-entity-maps-through-mutation-or-throw.util';
|
||||
|
||||
type AddFlatEntityToFlatEntityAndRelatedEntityMapsThroughMutationOrThrowArgs<
|
||||
T extends AllMetadataName,
|
||||
> = {
|
||||
metadataName: T;
|
||||
flatEntity: MetadataFlatEntity<T>;
|
||||
flatEntityAndRelatedMapsToMutate: MetadataFlatEntityAndRelatedFlatEntityMaps<T>;
|
||||
};
|
||||
export const addFlatEntityToFlatEntityAndRelatedEntityMapsThroughMutationOrThrow =
|
||||
<T extends AllMetadataName>({
|
||||
metadataName,
|
||||
flatEntity,
|
||||
flatEntityAndRelatedMapsToMutate,
|
||||
}: AddFlatEntityToFlatEntityAndRelatedEntityMapsThroughMutationOrThrowArgs<T>) => {
|
||||
const flatEntityMapsKey = getMetadataFlatEntityMapsKey(metadataName);
|
||||
|
||||
addFlatEntityToFlatEntityMapsThroughMutationOrThrow({
|
||||
flatEntity,
|
||||
flatEntityMapsToMutate:
|
||||
flatEntityAndRelatedMapsToMutate[flatEntityMapsKey],
|
||||
});
|
||||
|
||||
const manyToOneRelatedMetadataName = Object.entries(
|
||||
ALL_METADATA_RELATED_METADATA_BY_FOREIGN_KEY[metadataName],
|
||||
) as Array<
|
||||
[
|
||||
keyof MetadataFlatEntity<T>,
|
||||
{
|
||||
metadataName: AllMetadataName;
|
||||
flatEntityForeignKeyAggregator: keyof MetadataFlatEntity<AllMetadataName>;
|
||||
},
|
||||
]
|
||||
>;
|
||||
|
||||
for (const [
|
||||
foreignKey,
|
||||
{ metadataName: relatedMetadataName, flatEntityForeignKeyAggregator },
|
||||
] of manyToOneRelatedMetadataName) {
|
||||
const relatedFlatEntityMapsKey =
|
||||
getMetadataFlatEntityMapsKey(relatedMetadataName);
|
||||
const relatedFlatEntityMetadataMaps = flatEntityAndRelatedMapsToMutate[
|
||||
relatedFlatEntityMapsKey as MetadataRelatedFlatEntityMapsKeys<T>
|
||||
] as FlatEntityMaps<MetadataFlatEntity<typeof relatedMetadataName>>;
|
||||
|
||||
const flatEntityRelatedEntityForeignKeyValue = flatEntity[foreignKey] as
|
||||
| string
|
||||
| undefined;
|
||||
|
||||
if (!isDefined(flatEntityRelatedEntityForeignKeyValue)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
const relatedFlatEntity = findFlatEntityByIdInFlatEntityMapsOrThrow({
|
||||
flatEntityId: flatEntityRelatedEntityForeignKeyValue,
|
||||
flatEntityMaps: relatedFlatEntityMetadataMaps,
|
||||
});
|
||||
|
||||
if (
|
||||
!Object.prototype.hasOwnProperty.call(
|
||||
relatedFlatEntity,
|
||||
flatEntityForeignKeyAggregator,
|
||||
)
|
||||
) {
|
||||
throw new FlatEntityMapsException(
|
||||
`Should never occur, invalid flat entity typing. flat ${metadataName} should contain ${flatEntityForeignKeyAggregator}`,
|
||||
FlatEntityMapsExceptionCode.ENTITY_MALFORMED,
|
||||
);
|
||||
}
|
||||
|
||||
const updatedRelatedEntity = {
|
||||
...relatedFlatEntity,
|
||||
[flatEntityForeignKeyAggregator]: [
|
||||
...(relatedFlatEntity[
|
||||
flatEntityForeignKeyAggregator
|
||||
] as unknown as string[]),
|
||||
flatEntity.id,
|
||||
],
|
||||
};
|
||||
|
||||
replaceFlatEntityInFlatEntityMapsThroughMutationOrThrow({
|
||||
flatEntity: updatedRelatedEntity,
|
||||
flatEntityMapsToMutate: relatedFlatEntityMetadataMaps,
|
||||
});
|
||||
}
|
||||
};
|
||||
+107
@@ -0,0 +1,107 @@
|
||||
import { type AllMetadataName } from 'twenty-shared/metadata';
|
||||
import { isDefined } from 'twenty-shared/utils';
|
||||
|
||||
import { ALL_METADATA_RELATED_METADATA_BY_FOREIGN_KEY } from 'src/engine/metadata-modules/flat-entity/constant/all-metadata-many-to-one-relations.constant';
|
||||
import {
|
||||
FlatEntityMapsException,
|
||||
FlatEntityMapsExceptionCode,
|
||||
} from 'src/engine/metadata-modules/flat-entity/exceptions/flat-entity-maps.exception';
|
||||
import { type FlatEntityMaps } from 'src/engine/metadata-modules/flat-entity/types/flat-entity-maps.type';
|
||||
import { type MetadataFlatEntity } from 'src/engine/metadata-modules/flat-entity/types/metadata-flat-entity.type';
|
||||
import { type MetadataRelatedFlatEntityMapsKeys } from 'src/engine/metadata-modules/flat-entity/types/metadata-related-flat-entity-maps-keys.type';
|
||||
import { type MetadataFlatEntityAndRelatedFlatEntityMaps } from 'src/engine/metadata-modules/flat-entity/types/metadata-related-types.type';
|
||||
import { findFlatEntityByIdInFlatEntityMaps } from 'src/engine/metadata-modules/flat-entity/utils/find-flat-entity-by-id-in-flat-entity-maps.util';
|
||||
import { getMetadataFlatEntityMapsKey } from 'src/engine/metadata-modules/flat-entity/utils/get-metadata-flat-entity-maps-key.util';
|
||||
import { deleteFlatEntityFromFlatEntityMapsThroughMutationOrThrow } from 'src/engine/workspace-manager/workspace-migration-v2/utils/delete-flat-entity-from-flat-entity-maps-through-mutation-or-throw.util';
|
||||
import { replaceFlatEntityInFlatEntityMapsThroughMutationOrThrow } from 'src/engine/workspace-manager/workspace-migration-v2/utils/replace-flat-entity-in-flat-entity-maps-through-mutation-or-throw.util';
|
||||
|
||||
type DeleteFlatEntityFromFlatEntityAndRelatedEntityMapsThroughMutationOrThrowArgs<
|
||||
T extends AllMetadataName,
|
||||
> = {
|
||||
metadataName: T;
|
||||
flatEntity: MetadataFlatEntity<T>;
|
||||
flatEntityAndRelatedMapsToMutate: MetadataFlatEntityAndRelatedFlatEntityMaps<T>;
|
||||
};
|
||||
export const deleteFlatEntityFromFlatEntityAndRelatedEntityMapsThroughMutationOrThrow =
|
||||
<T extends AllMetadataName>({
|
||||
metadataName,
|
||||
flatEntity,
|
||||
flatEntityAndRelatedMapsToMutate,
|
||||
}: DeleteFlatEntityFromFlatEntityAndRelatedEntityMapsThroughMutationOrThrowArgs<T>) => {
|
||||
const flatEntityMapsKey = getMetadataFlatEntityMapsKey(metadataName);
|
||||
|
||||
deleteFlatEntityFromFlatEntityMapsThroughMutationOrThrow<
|
||||
MetadataFlatEntity<T>
|
||||
>({
|
||||
entityToDeleteId: flatEntity.id,
|
||||
flatEntityMapsToMutate:
|
||||
flatEntityAndRelatedMapsToMutate[flatEntityMapsKey],
|
||||
});
|
||||
|
||||
const manyToOneRelatedMetadataName = Object.entries(
|
||||
ALL_METADATA_RELATED_METADATA_BY_FOREIGN_KEY[metadataName],
|
||||
) as Array<
|
||||
[
|
||||
keyof MetadataFlatEntity<T>,
|
||||
{
|
||||
metadataName: AllMetadataName;
|
||||
flatEntityForeignKeyAggregator: keyof MetadataFlatEntity<AllMetadataName>;
|
||||
},
|
||||
]
|
||||
>;
|
||||
|
||||
for (const [
|
||||
foreignKey,
|
||||
{ metadataName: relatedMetadataName, flatEntityForeignKeyAggregator },
|
||||
] of manyToOneRelatedMetadataName) {
|
||||
const relatedFlatEntityMapsKey =
|
||||
getMetadataFlatEntityMapsKey(relatedMetadataName);
|
||||
|
||||
const relatedFlatEntityMetadataMaps = flatEntityAndRelatedMapsToMutate[
|
||||
relatedFlatEntityMapsKey as MetadataRelatedFlatEntityMapsKeys<T>
|
||||
] as FlatEntityMaps<MetadataFlatEntity<typeof relatedMetadataName>>;
|
||||
|
||||
const flatEntityRelatedEntityForeignKeyValue = flatEntity[foreignKey] as
|
||||
| string
|
||||
| undefined;
|
||||
|
||||
if (!isDefined(flatEntityRelatedEntityForeignKeyValue)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
const relatedFlatEntity = findFlatEntityByIdInFlatEntityMaps({
|
||||
flatEntityId: flatEntityRelatedEntityForeignKeyValue,
|
||||
flatEntityMaps: relatedFlatEntityMetadataMaps,
|
||||
});
|
||||
|
||||
if (!isDefined(relatedFlatEntity)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (
|
||||
!Object.prototype.hasOwnProperty.call(
|
||||
relatedFlatEntity,
|
||||
flatEntityForeignKeyAggregator,
|
||||
)
|
||||
) {
|
||||
throw new FlatEntityMapsException(
|
||||
`Should never occur, invalid flat entity typing. flat ${metadataName} should contain ${flatEntityForeignKeyAggregator}`,
|
||||
FlatEntityMapsExceptionCode.ENTITY_MALFORMED,
|
||||
);
|
||||
}
|
||||
|
||||
const updatedRelatedEntity = {
|
||||
...relatedFlatEntity,
|
||||
[flatEntityForeignKeyAggregator]: (
|
||||
relatedFlatEntity[
|
||||
flatEntityForeignKeyAggregator
|
||||
] as unknown as string[]
|
||||
).filter((id) => id !== flatEntity.id),
|
||||
};
|
||||
|
||||
replaceFlatEntityInFlatEntityMapsThroughMutationOrThrow({
|
||||
flatEntity: updatedRelatedEntity,
|
||||
flatEntityMapsToMutate: relatedFlatEntityMetadataMaps,
|
||||
});
|
||||
}
|
||||
};
|
||||
Reference in New Issue
Block a user