Fix cross app installation (#18151)

# Introduction
- Fixing app dependencyFlatEntityMaps load ( to load current app +
dependent app )
- Fix empty flat entity maps and undefined optimistic override
- Refactor the optimistic rendering to be mutation oriented at
orchestrator level
This commit is contained in:
Paul Rastoin
2026-02-23 19:58:06 +01:00
committed by GitHub
parent 0d4fe4575b
commit af0af0a237
24 changed files with 481 additions and 571 deletions
@@ -11,6 +11,10 @@ type MetadataRequiredForValidation = {
};
};
export type MetadataRelatedMetadataNameForValidation<
T extends AllMetadataName,
> = keyof (typeof ALL_METADATA_REQUIRED_METADATA_FOR_VALIDATION)[T];
// TODO deprecate in favor of ALL_METADATA_SERIALIZED_RELATION
export const ALL_METADATA_REQUIRED_METADATA_FOR_VALIDATION = {
fieldMetadata: {
@@ -12,6 +12,9 @@ type MetadataSerializedRelationProperties = {
: Partial<Record<AllMetadataName, true>>;
};
export type MetadataSerializedRelatedMetadataName<T extends AllMetadataName> =
keyof (typeof ALL_METADATA_SERIALIZED_RELATION)[T];
export const ALL_METADATA_SERIALIZED_RELATION = {
agent: {},
skill: {},
@@ -4,5 +4,6 @@ import { type UniversalFlatEntityMaps } from 'src/engine/workspace-manager/works
export type FlatEntityMaps<T extends SyncableFlatEntity> =
UniversalFlatEntityMaps<T> & {
universalIdentifierById: Partial<Record<string, string>>;
// TODO refactor or replicate to be universal and UniversalFlatEntityMaps lvl located
universalIdentifiersByApplicationId: Partial<Record<string, string[]>>;
};
@@ -0,0 +1,16 @@
import { type AllMetadataName } from 'twenty-shared/metadata';
import {
ALL_METADATA_REQUIRED_METADATA_FOR_VALIDATION,
type MetadataRelatedMetadataNameForValidation,
} from 'src/engine/metadata-modules/flat-entity/constant/all-metadata-required-metadata-for-validation.constant';
export const getMetadataRelatedMetadataNamesForValidation = <
T extends AllMetadataName,
>(
metadataName: T,
) => {
return Object.keys(
ALL_METADATA_REQUIRED_METADATA_FOR_VALIDATION[metadataName],
) as MetadataRelatedMetadataNameForValidation<T>[];
};
@@ -1,11 +1,14 @@
import { type AllMetadataName } from 'twenty-shared/metadata';
import { ALL_METADATA_SERIALIZED_RELATION } from 'src/engine/metadata-modules/flat-entity/constant/all-metadata-serialized-relation.constant';
import {
ALL_METADATA_SERIALIZED_RELATION,
type MetadataSerializedRelatedMetadataName,
} from 'src/engine/metadata-modules/flat-entity/constant/all-metadata-serialized-relation.constant';
export const getMetadataSerializedRelationNames = (
metadataName: AllMetadataName,
): AllMetadataName[] => {
export const getMetadataSerializedRelationNames = <T extends AllMetadataName>(
metadataName: T,
) => {
return Object.keys(
ALL_METADATA_SERIALIZED_RELATION[metadataName],
) as AllMetadataName[];
) as MetadataSerializedRelatedMetadataName<T>[];
};
@@ -1,8 +1,8 @@
import { createEmptyFlatEntityMaps } from 'src/engine/metadata-modules/flat-entity/constant/create-empty-flat-entity-maps.constant';
import { type SyncableFlatEntity } from 'src/engine/metadata-modules/flat-entity/types/flat-entity-from.type';
import { type FlatEntityMaps } from 'src/engine/metadata-modules/flat-entity/types/flat-entity-maps.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 { addFlatEntityToFlatEntityMapsThroughMutationOrThrow } from 'src/engine/workspace-manager/workspace-migration/utils/add-flat-entity-to-flat-entity-maps-through-mutation-or-throw.util';
export const getSubFlatEntityByIdsMapsOrThrow = <T extends SyncableFlatEntity>({
flatEntityIds,
@@ -11,15 +11,19 @@ export const getSubFlatEntityByIdsMapsOrThrow = <T extends SyncableFlatEntity>({
flatEntityMaps: FlatEntityMaps<T>;
flatEntityIds: string[];
}): FlatEntityMaps<T> => {
return flatEntityIds.reduce<FlatEntityMaps<T>>((acc, flatEntityId) => {
const flatEntityMapsToMutate = createEmptyFlatEntityMaps();
flatEntityIds.forEach((flatEntityId) => {
const flatEntity = findFlatEntityByIdInFlatEntityMapsOrThrow({
flatEntityId,
flatEntityMaps,
});
return addFlatEntityToFlatEntityMapsOrThrow({
addFlatEntityToFlatEntityMapsThroughMutationOrThrow({
flatEntity,
flatEntityMaps: acc,
flatEntityMapsToMutate,
});
}, createEmptyFlatEntityMaps());
});
return flatEntityMapsToMutate;
};
@@ -0,0 +1,126 @@
import { isDefined } from 'twenty-shared/utils';
import { findFlatEntityByUniversalIdentifierOrThrow } from 'src/engine/metadata-modules/flat-entity/utils/find-flat-entity-by-universal-identifier-or-throw.util';
import { findManyFlatEntityByUniversalIdentifierInUniversalFlatEntityMapsOrThrow } from 'src/engine/metadata-modules/flat-entity/utils/find-many-flat-entity-by-universal-identifier-in-universal-flat-entity-maps-or-throw.util';
import { validateFlatObjectMetadataIdentifiers } from 'src/engine/metadata-modules/flat-object-metadata/validators/utils/validate-flat-object-metadata-identifiers.util';
import { validateObjectMetadataSystemFieldsIntegrity } from 'src/engine/metadata-modules/flat-object-metadata/validators/utils/validate-object-metadata-system-fields-integrity.util';
import {
type OrchestratorActionsReport,
type OrchestratorFailureReport,
} from 'src/engine/workspace-manager/workspace-migration/types/workspace-migration-orchestrator.type';
import { type AllUniversalFlatEntityMaps } from 'src/engine/workspace-manager/workspace-migration/universal-flat-entity/types/all-universal-flat-entity-maps.type';
import { getEmptyFlatEntityValidationError } from 'src/engine/workspace-manager/workspace-migration/workspace-migration-builder/builders/utils/get-flat-entity-validation-error.util';
type ValidateObjectMetadataSystemFieldsIntegrityArgs = {
orchestratorActionsReport: Pick<
OrchestratorActionsReport,
'fieldMetadata' | 'objectMetadata'
>;
optimisticUniversalFlatMaps: Pick<
AllUniversalFlatEntityMaps,
'flatFieldMetadataMaps' | 'flatObjectMetadataMaps'
>;
};
export const validateObjectMetadataCrossEntity = ({
optimisticUniversalFlatMaps,
orchestratorActionsReport,
}: ValidateObjectMetadataSystemFieldsIntegrityArgs): Pick<
OrchestratorFailureReport,
'objectMetadata'
> => {
const metadataValidationErrors: Pick<
OrchestratorFailureReport,
'objectMetadata'
> = {
objectMetadata: [],
};
const createdObjectMetadatas =
findManyFlatEntityByUniversalIdentifierInUniversalFlatEntityMapsOrThrow({
universalIdentifiers: orchestratorActionsReport.objectMetadata.create.map(
(createObjectAction) =>
createObjectAction.flatEntity.universalIdentifier,
),
flatEntityMaps: optimisticUniversalFlatMaps.flatObjectMetadataMaps,
});
for (const universalFlatObjectMetadata of createdObjectMetadatas) {
const createFailedFlatEntityValidations = getEmptyFlatEntityValidationError(
{
flatEntityMinimalInformation: {
universalIdentifier: universalFlatObjectMetadata.universalIdentifier,
namePlural: universalFlatObjectMetadata.namePlural,
nameSingular: universalFlatObjectMetadata.nameSingular,
},
metadataName: 'objectMetadata',
type: 'create',
},
);
createFailedFlatEntityValidations.errors.push(
...validateObjectMetadataSystemFieldsIntegrity({
universalFlatFieldMetadataMaps:
optimisticUniversalFlatMaps.flatFieldMetadataMaps,
universalFlatObjectMetadata,
}),
);
createFailedFlatEntityValidations.errors.push(
...validateFlatObjectMetadataIdentifiers({
universalFlatObjectMetadata,
universalFlatFieldMetadataMaps:
optimisticUniversalFlatMaps.flatFieldMetadataMaps,
}),
);
if (createFailedFlatEntityValidations.errors.length > 0) {
metadataValidationErrors.objectMetadata.push(
createFailedFlatEntityValidations,
);
}
}
for (const objectMetadataUpdate of orchestratorActionsReport.objectMetadata
.update) {
const updatedFlatObjectMetadata =
findFlatEntityByUniversalIdentifierOrThrow({
flatEntityMaps: optimisticUniversalFlatMaps.flatObjectMetadataMaps,
universalIdentifier: objectMetadataUpdate.universalIdentifier,
});
const updateFailedFlatEntityValidations = getEmptyFlatEntityValidationError(
{
flatEntityMinimalInformation: {
universalIdentifier: updatedFlatObjectMetadata.universalIdentifier,
namePlural: updatedFlatObjectMetadata.namePlural,
nameSingular: updatedFlatObjectMetadata.nameSingular,
},
metadataName: 'objectMetadata',
type: 'update',
},
);
if (
isDefined(
objectMetadataUpdate.update
.labelIdentifierFieldMetadataUniversalIdentifier,
)
) {
updateFailedFlatEntityValidations.errors.push(
...validateFlatObjectMetadataIdentifiers({
universalFlatObjectMetadata: updatedFlatObjectMetadata,
universalFlatFieldMetadataMaps:
optimisticUniversalFlatMaps.flatFieldMetadataMaps,
}),
);
}
if (updateFailedFlatEntityValidations.errors.length > 0) {
metadataValidationErrors.objectMetadata.push(
updateFailedFlatEntityValidations,
);
}
}
return metadataValidationErrors;
};
@@ -1,123 +0,0 @@
import { msg } from '@lingui/core/macro';
import { isDefined } from 'twenty-shared/utils';
import { findFlatEntityByUniversalIdentifierOrThrow } from 'src/engine/metadata-modules/flat-entity/utils/find-flat-entity-by-universal-identifier-or-throw.util';
import { findManyFlatEntityByUniversalIdentifierInUniversalFlatEntityMapsOrThrow } from 'src/engine/metadata-modules/flat-entity/utils/find-many-flat-entity-by-universal-identifier-in-universal-flat-entity-maps-or-throw.util';
import { PARTIAL_SYSTEM_FLAT_FIELD_METADATAS } from 'src/engine/metadata-modules/object-metadata/constants/partial-system-flat-field-metadatas.constant';
import { ObjectMetadataExceptionCode } from 'src/engine/metadata-modules/object-metadata/object-metadata.exception';
import {
type OrchestratorActionsReport,
type OrchestratorFailureReport,
} from 'src/engine/workspace-manager/workspace-migration/types/workspace-migration-orchestrator.type';
import { type AllUniversalFlatEntityMaps } from 'src/engine/workspace-manager/workspace-migration/universal-flat-entity/types/all-universal-flat-entity-maps.type';
import { type UniversalFlatFieldMetadata } from 'src/engine/workspace-manager/workspace-migration/universal-flat-entity/types/universal-flat-field-metadata.type';
import { type FailedFlatEntityValidation } from 'src/engine/workspace-manager/workspace-migration/workspace-migration-builder/builders/types/failed-flat-entity-validation.type';
import { getEmptyFlatEntityValidationError } from 'src/engine/workspace-manager/workspace-migration/workspace-migration-builder/builders/utils/get-flat-entity-validation-error.util';
import { buildUniversalFlatObjectFieldByNameAndJoinColumnMaps } from 'src/engine/workspace-manager/workspace-migration/workspace-migration-builder/utils/build-universal-flat-object-field-by-name-and-join-column-maps.util';
type ValidateObjectMetadataSystemFieldsIntegrityArgs = {
orchestratorActionsReport: Pick<
OrchestratorActionsReport,
'fieldMetadata' | 'objectMetadata'
>;
optimisticUniversalFlatMaps: Pick<
AllUniversalFlatEntityMaps,
'flatFieldMetadataMaps' | 'flatObjectMetadataMaps'
>;
};
export const validateObjectMetadataSystemFieldsIntegrity = ({
optimisticUniversalFlatMaps,
orchestratorActionsReport,
}: ValidateObjectMetadataSystemFieldsIntegrityArgs): Pick<
OrchestratorFailureReport,
'objectMetadata'
> => {
const metadataValidationErrors: Pick<
OrchestratorFailureReport,
'objectMetadata'
> = {
objectMetadata: [],
};
const createdObjectMetadatas =
findManyFlatEntityByUniversalIdentifierInUniversalFlatEntityMapsOrThrow({
universalIdentifiers: orchestratorActionsReport.objectMetadata.create.map(
(createObjectAction) =>
createObjectAction.flatEntity.universalIdentifier,
),
flatEntityMaps: optimisticUniversalFlatMaps.flatObjectMetadataMaps,
});
for (const createdObjectMetadata of createdObjectMetadatas) {
const createdFailedFlatEntityValidations: FailedFlatEntityValidation<
'objectMetadata',
'create'
> = getEmptyFlatEntityValidationError({
flatEntityMinimalInformation: {
universalIdentifier: createdObjectMetadata.universalIdentifier,
namePlural: createdObjectMetadata.namePlural,
nameSingular: createdObjectMetadata.nameSingular,
},
metadataName: 'objectMetadata',
type: 'create',
});
const { fieldUniversalIdentifierByName } =
buildUniversalFlatObjectFieldByNameAndJoinColumnMaps({
flatFieldMetadataMaps:
optimisticUniversalFlatMaps.flatFieldMetadataMaps,
flatObjectMetadata: createdObjectMetadata,
});
for (const expectedSystemField of Object.values(
PARTIAL_SYSTEM_FLAT_FIELD_METADATAS,
)) {
const matchingFieldUniversalIdentifier =
fieldUniversalIdentifierByName[expectedSystemField.name];
const expectedFieldName = expectedSystemField.name;
if (!isDefined(matchingFieldUniversalIdentifier)) {
createdFailedFlatEntityValidations.errors.push({
code: ObjectMetadataExceptionCode.MISSING_SYSTEM_FIELD,
message: `System field ${expectedFieldName} is missing`,
userFriendlyMessage: msg`System field ${expectedFieldName} is missing`,
value: expectedFieldName,
});
} else {
const universalFlatFieldMetadata =
findFlatEntityByUniversalIdentifierOrThrow({
flatEntityMaps: optimisticUniversalFlatMaps.flatFieldMetadataMaps,
universalIdentifier: matchingFieldUniversalIdentifier,
});
const propertiesToValidate = [
'type',
'isSystem',
] as const satisfies (keyof UniversalFlatFieldMetadata)[];
for (const property of propertiesToValidate) {
const expectedValue = expectedSystemField[property];
const actualValue = universalFlatFieldMetadata[property];
if (actualValue !== expectedValue) {
createdFailedFlatEntityValidations.errors.push({
code: ObjectMetadataExceptionCode.INVALID_SYSTEM_FIELD,
message: `System field ${expectedFieldName} has invalid ${property}: expected ${String(expectedValue)}, got ${String(actualValue)}`,
userFriendlyMessage: msg`System field ${expectedFieldName} has invalid ${property}`,
value: actualValue,
});
}
}
}
}
if (createdFailedFlatEntityValidations.errors.length > 0) {
metadataValidationErrors.objectMetadata.push(
createdFailedFlatEntityValidations,
);
}
}
return metadataValidationErrors;
};
@@ -0,0 +1,73 @@
import { msg } from '@lingui/core/macro';
import { isDefined } from 'twenty-shared/utils';
import { findFlatEntityByUniversalIdentifierOrThrow } from 'src/engine/metadata-modules/flat-entity/utils/find-flat-entity-by-universal-identifier-or-throw.util';
import { PARTIAL_SYSTEM_FLAT_FIELD_METADATAS } from 'src/engine/metadata-modules/object-metadata/constants/partial-system-flat-field-metadatas.constant';
import { ObjectMetadataExceptionCode } from 'src/engine/metadata-modules/object-metadata/object-metadata.exception';
import { type AllUniversalFlatEntityMaps } from 'src/engine/workspace-manager/workspace-migration/universal-flat-entity/types/all-universal-flat-entity-maps.type';
import { type UniversalFlatFieldMetadata } from 'src/engine/workspace-manager/workspace-migration/universal-flat-entity/types/universal-flat-field-metadata.type';
import { type UniversalFlatObjectMetadata } from 'src/engine/workspace-manager/workspace-migration/universal-flat-entity/types/universal-flat-object-metadata.type';
import { type FlatEntityValidationError } from 'src/engine/workspace-manager/workspace-migration/workspace-migration-builder/builders/types/failed-flat-entity-validation.type';
import { buildUniversalFlatObjectFieldByNameAndJoinColumnMaps } from 'src/engine/workspace-manager/workspace-migration/workspace-migration-builder/utils/build-universal-flat-object-field-by-name-and-join-column-maps.util';
type ValidateObjectMetadataSystemFieldsIntegrityArgs = {
universalFlatObjectMetadata: UniversalFlatObjectMetadata;
universalFlatFieldMetadataMaps: AllUniversalFlatEntityMaps['flatFieldMetadataMaps'];
};
export const validateObjectMetadataSystemFieldsIntegrity = ({
universalFlatFieldMetadataMaps,
universalFlatObjectMetadata,
}: ValidateObjectMetadataSystemFieldsIntegrityArgs): FlatEntityValidationError[] => {
const errors: FlatEntityValidationError[] = [];
const { fieldUniversalIdentifierByName } =
buildUniversalFlatObjectFieldByNameAndJoinColumnMaps({
flatFieldMetadataMaps: universalFlatFieldMetadataMaps,
flatObjectMetadata: universalFlatObjectMetadata,
});
for (const expectedSystemField of Object.values(
PARTIAL_SYSTEM_FLAT_FIELD_METADATAS,
)) {
const matchingFieldUniversalIdentifier =
fieldUniversalIdentifierByName[expectedSystemField.name];
const expectedFieldName = expectedSystemField.name;
if (!isDefined(matchingFieldUniversalIdentifier)) {
errors.push({
code: ObjectMetadataExceptionCode.MISSING_SYSTEM_FIELD,
message: `System field ${expectedFieldName} is missing`,
userFriendlyMessage: msg`System field ${expectedFieldName} is missing`,
value: expectedFieldName,
});
} else {
const universalFlatFieldMetadata =
findFlatEntityByUniversalIdentifierOrThrow({
flatEntityMaps: universalFlatFieldMetadataMaps,
universalIdentifier: matchingFieldUniversalIdentifier,
});
const propertiesToValidate = [
'type',
'isSystem',
] as const satisfies (keyof UniversalFlatFieldMetadata)[];
for (const property of propertiesToValidate) {
const expectedValue = expectedSystemField[property];
const actualValue = universalFlatFieldMetadata[property];
if (actualValue !== expectedValue) {
errors.push({
code: ObjectMetadataExceptionCode.INVALID_SYSTEM_FIELD,
message: `System field ${expectedFieldName} has invalid ${property}: expected ${String(expectedValue)}, got ${String(actualValue)}`,
userFriendlyMessage: msg`System field ${expectedFieldName} has invalid ${property}`,
value: actualValue,
});
}
}
}
}
return errors;
};