Add applicationId to syncableEntity and fix syncApp deletion (#15170)

## Context
- All flatEntity should extend SyncableEntity
- SyncableEntity should now have applicationId and application relation
- Fix syncApp deletion, should now properly use migration v2 to delete
syncable entities
This commit is contained in:
Weiko
2025-10-17 17:23:00 +00:00
committed by GitHub
parent 84e7fabaab
commit cceeb6ed4d
79 changed files with 734 additions and 271 deletions
@@ -4,4 +4,5 @@ import { type FlatEntity } from 'src/engine/metadata-modules/flat-entity/types/f
export const EMPTY_FLAT_ENTITY_MAPS = {
byId: {},
idByUniversalIdentifier: {},
universalIdentifiersByApplicationId: {},
} as const satisfies FlatEntityMaps<FlatEntity>;
@@ -3,4 +3,5 @@ import { type FlatEntity } from 'src/engine/metadata-modules/flat-entity/types/f
export type FlatEntityMaps<T extends FlatEntity> = {
byId: Partial<Record<string, T>>;
idByUniversalIdentifier: Partial<Record<string, string>>;
universalIdentifiersByApplicationId: Partial<Record<string, string[]>>;
};
@@ -1,4 +1,22 @@
export interface FlatEntity {
import { type SyncableEntity } from 'src/engine/workspace-manager/workspace-sync/interfaces/syncable-entity.interface';
import { type ApplicationEntity } from 'src/engine/core-modules/application/application.entity';
import { type ExtractRecordTypeOrmRelationProperties } from 'src/engine/workspace-manager/workspace-migration-v2/types/extract-record-typeorm-relation-properties.type';
import { type NonNullableProperties } from 'src/types/non-nullable-properties.type';
export type SyncableEntityRelationProperties =
ExtractRecordTypeOrmRelationProperties<SyncableEntity, ApplicationEntity>;
export interface FlatEntity
extends NonNullableProperties<
Omit<SyncableEntity, SyncableEntityRelationProperties | 'applicationId'>
> {
id: string;
universalIdentifier: string;
applicationId: string | null;
}
export type FlatEntityFrom<
TEntity extends SyncableEntity,
TEntityRelationProperties extends keyof TEntity,
> = FlatEntity &
Omit<TEntity, TEntityRelationProperties | SyncableEntityRelationProperties>;
@@ -1,4 +1,4 @@
import { isDefined } from 'class-validator';
import { isDefined } from 'twenty-shared/utils';
import {
FlatEntityMapsException,
@@ -32,5 +32,20 @@ export const addFlatEntityToFlatEntityMapsOrThrow = <T extends FlatEntity>({
...flatEntityMaps.idByUniversalIdentifier,
[flatEntity.universalIdentifier]: flatEntity.id,
},
universalIdentifiersByApplicationId: {
...flatEntityMaps.universalIdentifiersByApplicationId,
...(isDefined(flatEntity.applicationId)
? {
[flatEntity.applicationId]: Array.from(
new Set([
...(flatEntityMaps.universalIdentifiersByApplicationId?.[
flatEntity.applicationId
] ?? []),
flatEntity.universalIdentifier,
]),
),
}
: {}),
},
};
};
@@ -1,3 +1,4 @@
import isEmpty from 'lodash.isempty';
import { isDefined, removePropertiesFromRecord } from 'twenty-shared/utils';
import {
@@ -31,10 +32,36 @@ export const deleteFlatEntityFromFlatEntityMapsOrThrow = <
flatEntityMaps.idByUniversalIdentifier,
).filter(([_universalIdentifier, id]) => id !== entityToDeleteId);
const updatedUniversalIdentifiersByApplicationIdEntries = Object.entries(
flatEntityMaps.universalIdentifiersByApplicationId,
)
.map(([applicationId, universalIdentifiers]) => {
const stillPresentUniversalIdentifiers = universalIdentifiers?.filter(
(universalIdentifier) =>
updatedIdByUniversalIdentifierEntries.some(
([existingUniversalIdentifier]) =>
existingUniversalIdentifier === universalIdentifier,
),
);
if (
isDefined(stillPresentUniversalIdentifiers) ||
isEmpty(stillPresentUniversalIdentifiers)
) {
return undefined;
}
return [applicationId, stillPresentUniversalIdentifiers];
})
.filter(isDefined);
return {
byId: removePropertiesFromRecord(flatEntityMaps.byId, [entityToDeleteId]),
idByUniversalIdentifier: Object.fromEntries(
updatedIdByUniversalIdentifierEntries,
),
universalIdentifiersByApplicationId: Object.fromEntries(
updatedUniversalIdentifiersByApplicationIdEntries,
),
};
};
@@ -0,0 +1,38 @@
import { isDefined } from 'twenty-shared/utils';
import { type FlatEntityMaps } from 'src/engine/metadata-modules/flat-entity/types/flat-entity-maps.type';
import { type FlatEntity } from 'src/engine/metadata-modules/flat-entity/types/flat-entity.type';
export const getFlatEntitiesByApplicationId = <T extends FlatEntity>(
maps: FlatEntityMaps<T>,
applicationId: string,
): T[] => {
const universalIdentifiers =
maps.universalIdentifiersByApplicationId[applicationId];
if (!isDefined(universalIdentifiers)) {
return [];
}
return universalIdentifiers
.map((universalId) => {
const id = maps.idByUniversalIdentifier[universalId];
if (!isDefined(id)) {
return undefined;
}
const entity = maps.byId[id];
if (!isDefined(entity)) {
return undefined;
}
if (entity.applicationId !== applicationId) {
return undefined;
}
return entity;
})
.filter(isDefined);
};