Migrate Views-xxx Index Field Object Skill to be fully universal ( all actions and metadata runner and builder ) + all metadata update actions runner (#17687)

# What this PR does
Overall naming `universal` versus `flat` is not always the most updated
and so on
Will make a big cleaning tour after I've finished the whole migration
Migrating all `view` and ( filter fields etc ) `field` `object` `index`
to the universal pattern on all `services`, `builder` and `runner`
levels

## Universal and flat optimistic tooling

`addUniversalFlatEntityToUniversalFlatEntityAndRelatedEntityMapsThroughMutationOrThrow`
and its delete counterpart maintain the consistency of
`UniversalFlatEntityMaps` when an entity is created or removed. Beyond
inserting/removing the entity from its own maps, they walk through
`ALL_UNIVERSAL_METADATA_RELATIONS` to update the **aggregator arrays**
on related parent entities — the add appends the new entity's
`universalIdentifier` to the parent's aggregator (e.g. a new viewField's
identifier gets appended to its parent view's
`viewFieldUniversalIdentifiers`), and the delete filters it out. This
keeps the maps in sync so that diff computations and relation lookups
remain accurate throughout the migration building process.

## ALL_UNIVERSAL_METADATA_RELATIONS
`ALL_UNIVERSAL_METADATA_RELATIONS` is the universal counterpart of
`ALL_METADATA_RELATIONS`. It maps each metadata entity to its
many-to-one and one-to-many relations using universal foreign keys
(`*UniversalIdentifier`) instead of database IDs (`*Id`). This allows
migration actions to reference related entities in a workspace-agnostic
way. Relations that are workspace-specific (e.g. `workspace`,
`dataSource`, `userWorkspace`) are set to `null` and skipped during
resolution.

## `workspaceMigrationCreateIdEnrichment`
Reserved to API metadata ( will be able to validate at app installation
lvl )
- Workspace migration `create` actions now carry an optional `id` (and
`fieldIdByUniversalIdentifier` for object/field actions) so that
caller-provided IDs flow through the entire build-validate-run pipeline.
- New `enrichCreateWorkspaceMigrationActionsWithIds` utility resolves
`universalIdentifier → id` mappings after the builder runs and injects
them into the migration actions before the runner persists entities.
- Runner action handlers use the provided IDs instead of generating new
UUIDs, enabling deterministic entity creation for synchronization
workflows.


## `resolveUniversalUpdateRelationIdentifiersToIds`
`resolveUniversalUpdateRelationIdentifiersToIds` converts universal
identifiers (workspace-agnostic, stable keys) in a migration update
payload into concrete database UUIDs, so the update can be applied to a
specific workspace.

It iterates over the many-to-one relations defined in
`ALL_UNIVERSAL_METADATA_RELATIONS` for the given entity type, replaces
each `*UniversalIdentifier` property with its corresponding `*Id` by
looking up the target entity in `allFlatEntityMaps`, and throws if a
non-null identifier can't be resolved.

Used by all `update` action handlers in
`transpileUniversalActionToFlatAction`, avoiding duplicated resolution
logic across handlers.

## What this PR does not
- Migrating twenty-standard declaration to universal
- Migrating all the inputs transpilers to universal
- Migrating all metadata to be fully universal ( we still need to
de-scope the type of all of them and refactor their validator very close
)

---------

Co-authored-by: Charles Bochet <charles@twenty.com>
This commit is contained in:
Paul Rastoin
2026-02-09 19:02:38 +01:00
committed by GitHub
parent 2a76e1791e
commit 8c951d3623
366 changed files with 10036 additions and 6606 deletions
@@ -1,13 +0,0 @@
// Jest Snapshot v1, https://jestjs.io/docs/snapshot-testing
exports[`compareTwoFlatEntity It should detect flat field metadata isActive diff from true to false 1`] = `
{
"isActive": false,
}
`;
exports[`compareTwoFlatEntity It should detect flat field metadata isActive diff from true to false 2`] = `
{
"isActive": true,
}
`;
@@ -4,7 +4,7 @@ import { createEmptyFlatEntityMaps } from 'src/engine/metadata-modules/flat-enti
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 { findFlatEntityByIdInFlatEntityMaps } from 'src/engine/metadata-modules/flat-entity/utils/find-flat-entity-by-id-in-flat-entity-maps.util';
import { findFlatEntityByUniversalIdentifier } from 'src/engine/metadata-modules/flat-entity/utils/find-flat-entity-by-universal-identifier.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';
@@ -12,14 +12,18 @@ import { type FlatObjectMetadata } from 'src/engine/metadata-modules/flat-object
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', () => {
it('should add a view and update related objectMetadata and fieldMetadata with both id and universal identifier aggregators', () => {
const objectMetadataId = 'object-1';
const viewId = 'view-1';
const applicationId = '20202020-f3ad-452e-b5b6-2d49d3ea88b1';
const workspaceId = '20202020-bc64-4148-8a79-b3144f743694';
const objectUniversalIdentifier = 'object-universal-1';
const fieldUniversalIdentifier = 'field-universal-1';
const viewUniversalIdentifier = 'view-universal-1';
const mockObjectMetadata = getFlatObjectMetadataMock({
id: objectMetadataId,
universalIdentifier: 'object-universal-1',
universalIdentifier: objectUniversalIdentifier,
viewIds: [],
fieldIds: [],
workspaceId,
@@ -34,7 +38,7 @@ describe('addFlatEntityToFlatEntityAndRelatedEntityMapsThroughMutationOrThrow',
type: FieldMetadataType.DATE,
workspaceId,
applicationId,
universalIdentifier: 'field-universal-1',
universalIdentifier: fieldUniversalIdentifier,
viewFieldIds: [],
viewFilterIds: [],
calendarViewIds: [],
@@ -44,13 +48,15 @@ describe('addFlatEntityToFlatEntityAndRelatedEntityMapsThroughMutationOrThrow',
const mockView: Pick<FlatView, 'id'> & Partial<FlatView> = {
id: viewId,
workspaceId,
universalIdentifier: 'view-universal-1',
universalIdentifier: viewUniversalIdentifier,
objectMetadataId: objectMetadataId,
objectMetadataUniversalIdentifier: objectUniversalIdentifier,
viewFieldIds: [],
viewFilterIds: [],
viewGroupIds: [],
applicationId,
calendarFieldMetadataId: mockFieldMetadata.id,
calendarFieldMetadataUniversalIdentifier: fieldUniversalIdentifier,
};
const flatEntityAndRelatedMapsToMutate: MetadataFlatEntityAndRelatedFlatEntityMaps<'view'> =
@@ -73,28 +79,30 @@ describe('addFlatEntityToFlatEntityAndRelatedEntityMapsThroughMutationOrThrow',
});
expect(
findFlatEntityByIdInFlatEntityMaps({
flatEntityId: mockView.id,
findFlatEntityByUniversalIdentifier({
universalIdentifier: viewUniversalIdentifier,
flatEntityMaps: flatEntityAndRelatedMapsToMutate.flatViewMaps,
}),
).toMatchObject(mockView);
expect(
findFlatEntityByIdInFlatEntityMaps({
flatEntityId: objectMetadataId,
findFlatEntityByUniversalIdentifier({
universalIdentifier: objectUniversalIdentifier,
flatEntityMaps: flatEntityAndRelatedMapsToMutate.flatObjectMetadataMaps,
}),
).toMatchObject<Partial<FlatObjectMetadata>>({
viewIds: [mockView.id],
viewUniversalIdentifiers: [viewUniversalIdentifier],
});
expect(
findFlatEntityByIdInFlatEntityMaps({
flatEntityId: mockFieldMetadata.id,
findFlatEntityByUniversalIdentifier({
universalIdentifier: fieldUniversalIdentifier,
flatEntityMaps: flatEntityAndRelatedMapsToMutate.flatFieldMetadataMaps,
}),
).toMatchObject<Partial<FlatFieldMetadata>>({
calendarViewIds: [mockView.id],
calendarViewUniversalIdentifiers: [viewUniversalIdentifier],
});
});
});
@@ -1,80 +0,0 @@
import { extractRecordIdsAndDatesAsExpectAny } from 'test/utils/extract-record-ids-and-dates-as-expect-any';
import { type AllMetadataName } from 'twenty-shared/metadata';
import {
eachTestingContextFilter,
type EachTestingContext,
} from 'twenty-shared/testing';
import { FieldMetadataType, type FromTo } from 'twenty-shared/types';
import { ALL_FLAT_ENTITY_PROPERTIES_TO_COMPARE_AND_STRINGIFY } from 'src/engine/metadata-modules/flat-entity/constant/all-flat-entity-properties-to-compare-and-stringify.constant';
import { type MetadataFlatEntity } from 'src/engine/metadata-modules/flat-entity/types/metadata-flat-entity.type';
import { compareTwoFlatEntity } from 'src/engine/metadata-modules/flat-entity/utils/compare-two-flat-entity.util';
import { getFlatFieldMetadataMock } from 'src/engine/metadata-modules/flat-field-metadata/__mocks__/get-flat-field-metadata.mock';
type TestContext<T extends AllMetadataName = AllMetadataName> = FromTo<
MetadataFlatEntity<T>,
'flatEntity'
> & { metadataName: T };
describe('compareTwoFlatEntity', () => {
const testCases = [
{
title:
'It should detect flat field metadata isActive diff from true to false',
context: {
fromFlatEntity: getFlatFieldMetadataMock({
objectMetadataId: 'object-metadata-id',
type: FieldMetadataType.TEXT,
universalIdentifier: 'universal-identifier',
isActive: true,
}),
metadataName: 'fieldMetadata',
toFlatEntity: getFlatFieldMetadataMock({
objectMetadataId: 'object-metadata-id',
type: FieldMetadataType.TEXT,
universalIdentifier: 'universal-identifier',
isActive: false,
}),
},
},
{
title:
'It should detect flat field metadata isActive diff from true to false',
context: {
fromFlatEntity: getFlatFieldMetadataMock({
objectMetadataId: 'object-metadata-id',
type: FieldMetadataType.TEXT,
universalIdentifier: 'universal-identifier',
isActive: false,
}),
metadataName: 'fieldMetadata',
toFlatEntity: getFlatFieldMetadataMock({
objectMetadataId: 'object-metadata-id',
type: FieldMetadataType.TEXT,
universalIdentifier: 'universal-identifier',
isActive: true,
}),
},
},
] as const satisfies EachTestingContext<TestContext>[];
test.each(eachTestingContextFilter(testCases))(
'$title',
({ context: { metadataName, fromFlatEntity, toFlatEntity } }) => {
const result = compareTwoFlatEntity({
fromFlatEntity,
propertiesToCompare:
ALL_FLAT_ENTITY_PROPERTIES_TO_COMPARE_AND_STRINGIFY[metadataName]
.propertiesToCompare as any,
propertiesToStringify:
ALL_FLAT_ENTITY_PROPERTIES_TO_COMPARE_AND_STRINGIFY[metadataName]
.propertiesToStringify as any,
toFlatEntity,
});
expect(result).toMatchSnapshot(
extractRecordIdsAndDatesAsExpectAny({ ...result }),
);
},
);
});
@@ -4,7 +4,7 @@ import { createEmptyFlatEntityMaps } from 'src/engine/metadata-modules/flat-enti
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 { findFlatEntityByIdInFlatEntityMaps } from 'src/engine/metadata-modules/flat-entity/utils/find-flat-entity-by-id-in-flat-entity-maps.util';
import { findFlatEntityByUniversalIdentifier } from 'src/engine/metadata-modules/flat-entity/utils/find-flat-entity-by-universal-identifier.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';
@@ -12,16 +12,20 @@ import { type FlatObjectMetadata } from 'src/engine/metadata-modules/flat-object
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', () => {
it('should delete a view and update related objectMetadata and fieldMetadata by removing both id and universal identifier aggregators', () => {
const objectMetadataId = 'object-1';
const viewId = 'view-1';
const applicationId = '20202020-f3ad-452e-b5b6-2d49d3ea88b1';
const workspaceId = '20202020-bc64-4148-8a79-b3144f743694';
const objectUniversalIdentifier = 'object-universal-1';
const fieldUniversalIdentifier = 'field-universal-1';
const viewUniversalIdentifier = 'view-universal-1';
const mockObjectMetadata = getFlatObjectMetadataMock({
id: objectMetadataId,
universalIdentifier: 'object-universal-1',
universalIdentifier: objectUniversalIdentifier,
viewIds: [viewId, 'something-else'],
viewUniversalIdentifiers: [viewUniversalIdentifier, 'something-else-ui'],
fieldIds: [],
workspaceId,
imageIdentifierFieldMetadataId: '20202020-9d65-415f-b0e1-216a2e257ea4',
@@ -33,24 +37,27 @@ describe('deleteFlatEntityFromFlatEntityAndRelatedEntityMapsThroughMutationOrThr
objectMetadataId,
id: '20202020-4087-423b-852a-91f91acf2df2',
type: FieldMetadataType.DATE,
universalIdentifier: 'field-universal-1',
universalIdentifier: fieldUniversalIdentifier,
viewFieldIds: [],
viewFilterIds: [],
workspaceId,
calendarViewIds: [viewId],
calendarViewUniversalIdentifiers: [viewUniversalIdentifier],
mainGroupByFieldMetadataViewIds: [],
applicationId,
});
const mockView: Partial<FlatView> = {
id: viewId,
universalIdentifier: 'view-universal-1',
universalIdentifier: viewUniversalIdentifier,
objectMetadataId: objectMetadataId,
objectMetadataUniversalIdentifier: objectUniversalIdentifier,
viewFieldIds: [],
viewFilterIds: [],
viewGroupIds: [],
workspaceId,
calendarFieldMetadataId: mockFieldMetadata.id,
calendarFieldMetadataUniversalIdentifier: fieldUniversalIdentifier,
createdAt: new Date('2024-01-01').toISOString(),
updatedAt: new Date('2024-01-01').toISOString(),
icon: 'icon',
@@ -83,28 +90,30 @@ describe('deleteFlatEntityFromFlatEntityAndRelatedEntityMapsThroughMutationOrThr
});
expect(
findFlatEntityByIdInFlatEntityMaps({
flatEntityId: viewId,
findFlatEntityByUniversalIdentifier({
universalIdentifier: viewUniversalIdentifier,
flatEntityMaps: flatEntityAndRelatedMapsToMutate.flatViewMaps,
}),
).toBeUndefined();
expect(
findFlatEntityByIdInFlatEntityMaps({
flatEntityId: objectMetadataId,
findFlatEntityByUniversalIdentifier({
universalIdentifier: objectUniversalIdentifier,
flatEntityMaps: flatEntityAndRelatedMapsToMutate.flatObjectMetadataMaps,
}),
).toMatchObject<Partial<FlatObjectMetadata>>({
viewIds: ['something-else'],
viewUniversalIdentifiers: ['something-else-ui'],
});
expect(
findFlatEntityByIdInFlatEntityMaps({
flatEntityId: mockFieldMetadata.id,
findFlatEntityByUniversalIdentifier({
universalIdentifier: fieldUniversalIdentifier,
flatEntityMaps: flatEntityAndRelatedMapsToMutate.flatFieldMetadataMaps,
}),
).toMatchObject<Partial<FlatFieldMetadata>>({
calendarViewIds: [],
calendarViewUniversalIdentifiers: [],
});
});
});
@@ -12,7 +12,7 @@ import { type MetadataRelatedFlatEntityMapsKeys } from 'src/engine/metadata-modu
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/utils/add-flat-entity-to-flat-entity-maps-through-mutation-or-throw.util';
import { addUniversalFlatEntityToUniversalFlatEntityAndRelatedEntityMapsThroughMutationOrThrow } from 'src/engine/workspace-manager/workspace-migration/universal-flat-entity/utils/add-universal-flat-entity-to-universal-flat-entity-and-related-entity-maps-through-mutation-or-throw.util';
import { replaceFlatEntityInFlatEntityMapsThroughMutationOrThrow } from 'src/engine/workspace-manager/workspace-migration/utils/replace-flat-entity-in-flat-entity-maps-through-mutation-or-throw.util';
type AddFlatEntityToFlatEntityAndRelatedEntityMapsThroughMutationOrThrowArgs<
@@ -22,21 +22,30 @@ type AddFlatEntityToFlatEntityAndRelatedEntityMapsThroughMutationOrThrowArgs<
flatEntity: MetadataFlatEntity<T>;
flatEntityAndRelatedMapsToMutate: MetadataFlatEntityAndRelatedFlatEntityMaps<T>;
};
export const addFlatEntityToFlatEntityAndRelatedEntityMapsThroughMutationOrThrow =
<T extends AllMetadataName>({
metadataName,
flatEntity,
flatEntityAndRelatedMapsToMutate,
}: AddFlatEntityToFlatEntityAndRelatedEntityMapsThroughMutationOrThrowArgs<T>) => {
addUniversalFlatEntityToUniversalFlatEntityAndRelatedEntityMapsThroughMutationOrThrow(
{
metadataName,
universalFlatEntity: flatEntity,
universalFlatEntityAndRelatedMapsToMutate:
flatEntityAndRelatedMapsToMutate,
},
);
const flatEntityMapsKey = getMetadataFlatEntityMapsKey(metadataName);
const selfFlatEntityMaps =
flatEntityAndRelatedMapsToMutate[flatEntityMapsKey];
addFlatEntityToFlatEntityMapsThroughMutationOrThrow({
flatEntity,
flatEntityMapsToMutate:
flatEntityAndRelatedMapsToMutate[flatEntityMapsKey],
});
selfFlatEntityMaps.universalIdentifierById[flatEntity.id] =
flatEntity.universalIdentifier;
const manyToOneRelations = Object.values(
const idBasedManyToOneRelations = Object.values(
ALL_METADATA_RELATIONS[metadataName].manyToOne,
) as Array<{
metadataName: AllMetadataName;
@@ -44,8 +53,8 @@ export const addFlatEntityToFlatEntityAndRelatedEntityMapsThroughMutationOrThrow
foreignKey: keyof MetadataFlatEntity<T>;
} | null>;
for (const relation of manyToOneRelations) {
if (!isDefined(relation)) {
for (const idBasedRelation of idBasedManyToOneRelations) {
if (!isDefined(idBasedRelation)) {
continue;
}
@@ -53,7 +62,7 @@ export const addFlatEntityToFlatEntityAndRelatedEntityMapsThroughMutationOrThrow
metadataName: relatedMetadataName,
flatEntityForeignKeyAggregator,
foreignKey,
} = relation;
} = idBasedRelation;
if (!isDefined(flatEntityForeignKeyAggregator)) {
continue;
@@ -61,6 +70,7 @@ export const addFlatEntityToFlatEntityAndRelatedEntityMapsThroughMutationOrThrow
const relatedFlatEntityMapsKey =
getMetadataFlatEntityMapsKey(relatedMetadataName);
const relatedFlatEntityMetadataMaps = flatEntityAndRelatedMapsToMutate[
relatedFlatEntityMapsKey as MetadataRelatedFlatEntityMapsKeys<T>
] as FlatEntityMaps<MetadataFlatEntity<typeof relatedMetadataName>>;
@@ -85,7 +95,7 @@ export const addFlatEntityToFlatEntityAndRelatedEntityMapsThroughMutationOrThrow
)
) {
throw new FlatEntityMapsException(
`Should never occur, invalid flat entity typing. flat ${metadataName} should contain ${flatEntityForeignKeyAggregator}`,
`Should never occur, invalid flat entity typing. flat ${relatedMetadataName} should contain ${String(flatEntityForeignKeyAggregator)}`,
FlatEntityMapsExceptionCode.ENTITY_MALFORMED,
);
}
@@ -1,78 +0,0 @@
import diff from 'microdiff';
import { type AllMetadataName } from 'twenty-shared/metadata';
import { type FromTo } from 'twenty-shared/types';
import { parseJson } from 'twenty-shared/utils';
import { type FlatEntityPropertiesToCompare } from 'src/engine/metadata-modules/flat-entity/types/flat-entity-properties-to-compare.type';
import { type FlatEntityUpdate } from 'src/engine/metadata-modules/flat-entity/types/flat-entity-properties-updates.type';
import { type MetadataFlatEntity } from 'src/engine/metadata-modules/flat-entity/types/metadata-flat-entity.type';
import { transformFlatEntityForComparison } from 'src/engine/metadata-modules/flat-entity/utils/transform-flat-entity-for-comparison.util';
export const compareTwoFlatEntity = <
T extends AllMetadataName,
PToCompare extends Extract<
FlatEntityPropertiesToCompare<T>,
keyof MetadataFlatEntity<T>
>,
PJsonB extends PToCompare = PToCompare,
>({
fromFlatEntity,
toFlatEntity,
propertiesToCompare,
propertiesToStringify,
}: FromTo<MetadataFlatEntity<T>, 'flatEntity'> & {
propertiesToCompare: readonly PToCompare[];
propertiesToStringify: readonly PJsonB[];
}): FlatEntityUpdate<T> | undefined => {
const [transformedFromFlatEntity, transformedToFlatEntity] = [
fromFlatEntity,
toFlatEntity,
].map((flatEntity) =>
transformFlatEntityForComparison({
flatEntity,
propertiesToCompare,
propertiesToStringify,
}),
);
const flatEntityDifferences = diff(
transformedFromFlatEntity,
transformedToFlatEntity,
);
if (flatEntityDifferences.length === 0) {
return undefined;
}
const initialAccumulator: FlatEntityUpdate<T> = {};
return flatEntityDifferences.reduce((accumulator, difference) => {
switch (difference.type) {
case 'CHANGE': {
const { path, value } = difference;
const property = path[0] as PToCompare;
const isJsonb = propertiesToStringify.includes(
property as unknown as PJsonB,
);
if (isJsonb) {
return {
...accumulator,
[property]: parseJson(value),
};
}
return {
...accumulator,
[property]: value,
};
}
case 'CREATE':
case 'REMOVE':
default: {
// Should never occur, we should only provide null never undefined and so on
return accumulator;
}
}
}, initialAccumulator);
};
@@ -1,63 +0,0 @@
import { type AllMetadataName } from 'twenty-shared/metadata';
import { type FlatEntityToCreateDeleteUpdate } from 'src/engine/metadata-modules/flat-entity/types/flat-entity-to-create-delete-update.type';
import { type MetadataFlatEntityMaps } from 'src/engine/metadata-modules/flat-entity/types/metadata-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 { deleteFlatEntityFromFlatEntityMapsOrThrow } from 'src/engine/metadata-modules/flat-entity/utils/delete-flat-entity-from-flat-entity-maps-or-throw.util';
import { getSubFlatEntityByIdsMapsOrThrow } from 'src/engine/metadata-modules/flat-entity/utils/get-sub-flat-entity-by-ids-maps-or-throw.util';
import { replaceFlatEntityInFlatEntityMapsOrThrow } from 'src/engine/metadata-modules/flat-entity/utils/replace-flat-entity-in-flat-entity-maps-or-throw.util';
export type ComputeFlatEntityMapsFromToArgs<T extends AllMetadataName> = {
flatEntityMaps: MetadataFlatEntityMaps<T>;
} & FlatEntityToCreateDeleteUpdate<T>;
export const computeFlatEntityMapsFromTo = <T extends AllMetadataName>({
flatEntityMaps,
flatEntityToCreate,
flatEntityToDelete,
flatEntityToUpdate,
}: ComputeFlatEntityMapsFromToArgs<T>): {
from: MetadataFlatEntityMaps<T>;
to: MetadataFlatEntityMaps<T>;
} => {
const fromFlatEntityMaps =
flatEntityToDelete.length > 0
? getSubFlatEntityByIdsMapsOrThrow({
flatEntityIds: [...flatEntityToDelete, ...flatEntityToUpdate].map(
({ id }) => id,
),
flatEntityMaps,
})
: flatEntityMaps;
const toFlatEntityMapsWithDeleted = flatEntityToDelete.reduce(
(flatEntityMaps, flatEntity) =>
deleteFlatEntityFromFlatEntityMapsOrThrow({
entityToDeleteId: flatEntity.id,
flatEntityMaps,
}),
fromFlatEntityMaps,
);
const toFlatEntityMapsWithUpdated = flatEntityToUpdate.reduce(
(flatEntityMaps, flatEntity) =>
replaceFlatEntityInFlatEntityMapsOrThrow({
flatEntity,
flatEntityMaps,
}),
toFlatEntityMapsWithDeleted,
);
const toFlatEntityMaps = flatEntityToCreate.reduce(
(flatEntityMaps, flatEntity) =>
addFlatEntityToFlatEntityMapsOrThrow({
flatEntity,
flatEntityMaps,
}),
toFlatEntityMapsWithUpdated,
);
return {
from: fromFlatEntityMaps,
to: toFlatEntityMaps,
};
};
@@ -12,7 +12,7 @@ import { type MetadataRelatedFlatEntityMapsKeys } from 'src/engine/metadata-modu
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/utils/delete-flat-entity-from-flat-entity-maps-through-mutation-or-throw.util';
import { deleteUniversalFlatEntityFromUniversalFlatEntityAndRelatedEntityMapsThroughMutationOrThrow } from 'src/engine/workspace-manager/workspace-migration/universal-flat-entity/utils/delete-universal-flat-entity-from-universal-flat-entity-and-related-entity-maps-through-mutation-or-throw.util';
import { replaceFlatEntityInFlatEntityMapsThroughMutationOrThrow } from 'src/engine/workspace-manager/workspace-migration/utils/replace-flat-entity-in-flat-entity-maps-through-mutation-or-throw.util';
type DeleteFlatEntityFromFlatEntityAndRelatedEntityMapsThroughMutationOrThrowArgs<
@@ -22,23 +22,23 @@ type DeleteFlatEntityFromFlatEntityAndRelatedEntityMapsThroughMutationOrThrowArg
flatEntity: MetadataFlatEntity<T>;
flatEntityAndRelatedMapsToMutate: MetadataFlatEntityAndRelatedFlatEntityMaps<T>;
};
export const deleteFlatEntityFromFlatEntityAndRelatedEntityMapsThroughMutationOrThrow =
<T extends AllMetadataName>({
metadataName,
flatEntity,
flatEntityAndRelatedMapsToMutate,
}: DeleteFlatEntityFromFlatEntityAndRelatedEntityMapsThroughMutationOrThrowArgs<T>) => {
const flatEntityMapsKey = getMetadataFlatEntityMapsKey(metadataName);
deleteUniversalFlatEntityFromUniversalFlatEntityAndRelatedEntityMapsThroughMutationOrThrow(
{
metadataName,
universalFlatEntity: flatEntity,
universalFlatEntityAndRelatedMapsToMutate:
flatEntityAndRelatedMapsToMutate,
},
);
deleteFlatEntityFromFlatEntityMapsThroughMutationOrThrow<
MetadataFlatEntity<T>
>({
entityToDeleteId: flatEntity.id,
flatEntityMapsToMutate:
flatEntityAndRelatedMapsToMutate[flatEntityMapsKey],
});
const manyToOneRelations = Object.values(
const idBasedManyToOneRelations = Object.values(
ALL_METADATA_RELATIONS[metadataName].manyToOne,
) as Array<{
metadataName: AllMetadataName;
@@ -46,8 +46,8 @@ export const deleteFlatEntityFromFlatEntityAndRelatedEntityMapsThroughMutationOr
foreignKey: keyof MetadataFlatEntity<T>;
} | null>;
for (const relation of manyToOneRelations) {
if (!isDefined(relation)) {
for (const idBasedRelation of idBasedManyToOneRelations) {
if (!isDefined(idBasedRelation)) {
continue;
}
@@ -55,7 +55,7 @@ export const deleteFlatEntityFromFlatEntityAndRelatedEntityMapsThroughMutationOr
metadataName: relatedMetadataName,
flatEntityForeignKeyAggregator,
foreignKey,
} = relation;
} = idBasedRelation;
if (!isDefined(flatEntityForeignKeyAggregator)) {
continue;
@@ -92,7 +92,7 @@ export const deleteFlatEntityFromFlatEntityAndRelatedEntityMapsThroughMutationOr
)
) {
throw new FlatEntityMapsException(
`Should never occur, invalid flat entity typing. flat ${metadataName} should contain ${flatEntityForeignKeyAggregator}`,
`Should never occur, invalid flat entity typing. flat ${relatedMetadataName} should contain ${flatEntityForeignKeyAggregator}`,
FlatEntityMapsExceptionCode.ENTITY_MALFORMED,
);
}
@@ -5,9 +5,9 @@ import {
FlatEntityMapsExceptionCode,
} from 'src/engine/metadata-modules/flat-entity/exceptions/flat-entity-maps.exception';
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 { findFlatEntityByUniversalIdentifier } from 'src/engine/metadata-modules/flat-entity/utils/find-flat-entity-by-universal-identifier.util';
import { type UniversalSyncableFlatEntity } from 'src/engine/workspace-manager/workspace-migration/universal-flat-entity/types/universal-flat-entity-from.type';
import { type UniversalFlatEntityMaps } from 'src/engine/workspace-manager/workspace-migration/universal-flat-entity/types/universal-flat-entity-maps.type';
export const findFlatEntityByUniversalIdentifierOrThrow = <
T extends SyncableFlatEntity | UniversalSyncableFlatEntity,
@@ -15,7 +15,7 @@ export const findFlatEntityByUniversalIdentifierOrThrow = <
flatEntityMaps,
universalIdentifier,
}: {
flatEntityMaps: FlatEntityMaps<T>;
flatEntityMaps: UniversalFlatEntityMaps<T>;
universalIdentifier: string;
}): T => {
const flatEntity = findFlatEntityByUniversalIdentifier({
@@ -1,6 +1,6 @@
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 { type UniversalSyncableFlatEntity } from 'src/engine/workspace-manager/workspace-migration/universal-flat-entity/types/universal-flat-entity-from.type';
import { type UniversalFlatEntityMaps } from 'src/engine/workspace-manager/workspace-migration/universal-flat-entity/types/universal-flat-entity-maps.type';
export const findFlatEntityByUniversalIdentifier = <
T extends SyncableFlatEntity | UniversalSyncableFlatEntity,
@@ -8,7 +8,7 @@ export const findFlatEntityByUniversalIdentifier = <
flatEntityMaps,
universalIdentifier,
}: {
flatEntityMaps: FlatEntityMaps<T>;
flatEntityMaps: UniversalFlatEntityMaps<T>;
universalIdentifier: string;
}): T | undefined => {
return flatEntityMaps.byUniversalIdentifier[universalIdentifier];
@@ -1,12 +1,12 @@
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 { findFlatEntityByUniversalIdentifierOrThrow } from 'src/engine/metadata-modules/flat-entity/utils/find-flat-entity-by-universal-identifier-or-throw.util';
import { type UniversalSyncableFlatEntity } from 'src/engine/workspace-manager/workspace-migration/universal-flat-entity/types/universal-flat-entity-from.type';
import { type UniversalFlatEntityMaps } from 'src/engine/workspace-manager/workspace-migration/universal-flat-entity/types/universal-flat-entity-maps.type';
export type FindManyFlatEntityByUniversalIdentifierInUniversalFlatEntityMapsOrThrowArgs<
T extends SyncableFlatEntity | UniversalSyncableFlatEntity,
> = {
flatEntityMaps: FlatEntityMaps<T>;
flatEntityMaps: UniversalFlatEntityMaps<T>;
universalIdentifiers: string[];
};
@@ -0,0 +1,15 @@
import { type AllMetadataName } from 'twenty-shared/metadata';
import { ALL_METADATA_RELATIONS } from 'src/engine/metadata-modules/flat-entity/constant/all-metadata-relations.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[];
};
@@ -0,0 +1,33 @@
import { type SyncableFlatEntity } from 'src/engine/metadata-modules/flat-entity/types/flat-entity-from.type';
import { findFlatEntityByUniversalIdentifierOrThrow } from 'src/engine/metadata-modules/flat-entity/utils/find-flat-entity-by-universal-identifier-or-throw.util';
import { type UniversalSyncableFlatEntity } from 'src/engine/workspace-manager/workspace-migration/universal-flat-entity/types/universal-flat-entity-from.type';
import { type UniversalFlatEntityMaps } from 'src/engine/workspace-manager/workspace-migration/universal-flat-entity/types/universal-flat-entity-maps.type';
import { addUniversalFlatEntityToUniversalFlatEntityMapsThroughMutationOrThrow } from 'src/engine/workspace-manager/workspace-migration/universal-flat-entity/utils/add-universal-flat-entity-to-universal-flat-entity-maps-through-mutation-or-throw.util';
export const getSubUniversalFlatEntityByUniversalIdentifiersMapsOrThrow = <
T extends SyncableFlatEntity | UniversalSyncableFlatEntity,
>({
universalIdentifiers,
universalFlatEntityMaps,
}: {
universalFlatEntityMaps: UniversalFlatEntityMaps<T>;
universalIdentifiers: string[];
}): UniversalFlatEntityMaps<T> => {
const subUniversalFlatEntityMaps: UniversalFlatEntityMaps<T> = {
byUniversalIdentifier: {},
};
for (const universalIdentifier of universalIdentifiers) {
const flatEntity = findFlatEntityByUniversalIdentifierOrThrow({
flatEntityMaps: universalFlatEntityMaps,
universalIdentifier,
});
addUniversalFlatEntityToUniversalFlatEntityMapsThroughMutationOrThrow({
universalFlatEntity: flatEntity,
universalFlatEntityMapsToMutate: subUniversalFlatEntityMaps,
});
}
return subUniversalFlatEntityMaps;
};
@@ -4,7 +4,7 @@ import { isDefined } from 'twenty-shared/utils';
import {
ALL_METADATA_RELATIONS,
type ManyToOneRelationValue,
type MetadataManyToOneRelationConfiguration,
} from 'src/engine/metadata-modules/flat-entity/constant/all-metadata-relations.constant';
import {
FlatEntityMapsException,
@@ -76,7 +76,9 @@ export const resolveEntityRelationUniversalIdentifiers = <
const relations = ALL_METADATA_RELATIONS[metadataName].manyToOne;
const result: Record<string, string | null> = {};
for (const relation of Object.values(relations) as ManyToOneRelationValue<
for (const relation of Object.values(
relations,
) as MetadataManyToOneRelationConfiguration<
T,
ExtractEntityManyToOneEntityRelationProperties<MetadataEntity<T>>
>[]) {
@@ -1,60 +0,0 @@
import { t } from '@lingui/core/macro';
import { type AllMetadataName } from 'twenty-shared/metadata';
import { isDefined } from 'twenty-shared/utils';
import {
FlatEntityMapsException,
FlatEntityMapsExceptionCode,
} from 'src/engine/metadata-modules/flat-entity/exceptions/flat-entity-maps.exception';
import { type MetadataFlatEntityMaps } from 'src/engine/metadata-modules/flat-entity/types/metadata-flat-entity-maps.type';
import { findFlatEntityByIdInFlatEntityMaps } from 'src/engine/metadata-modules/flat-entity/utils/find-flat-entity-by-id-in-flat-entity-maps.util';
// @deprecated Use resolveEntityRelationUniversalIdentifiers instead
export const resolveUniversalIdentifierFromFlatEntityIdOrThrow = <
T extends AllMetadataName,
>({
flatEntityMaps,
flatEntityId,
metadataName,
}: {
flatEntityMaps: MetadataFlatEntityMaps<T>;
flatEntityId: string;
metadataName: T;
}): string => {
const flatEntity = findFlatEntityByIdInFlatEntityMaps({
flatEntityMaps,
flatEntityId,
});
if (!isDefined(flatEntity)) {
throw new FlatEntityMapsException(
t`Could not find ${metadataName}`,
FlatEntityMapsExceptionCode.ENTITY_NOT_FOUND,
);
}
return flatEntity.universalIdentifier;
};
// @deprecated Use resolveEntityRelationUniversalIdentifiers instead
export const resolveNullableUniversalIdentifierFromFlatEntityId = <
T extends AllMetadataName,
>({
flatEntityMaps,
flatEntityId,
metadataName,
}: {
flatEntityMaps: MetadataFlatEntityMaps<T>;
flatEntityId: string | null | undefined;
metadataName: T;
}): string | null => {
if (!isDefined(flatEntityId)) {
return null;
}
return resolveUniversalIdentifierFromFlatEntityIdOrThrow({
flatEntityMaps,
flatEntityId,
metadataName,
});
};
@@ -1,37 +0,0 @@
import { type SyncableFlatEntity } from 'src/engine/metadata-modules/flat-entity/types/flat-entity-from.type';
import { orderObjectProperties } from 'src/engine/metadata-modules/flat-entity/utils/order-object-properties.util';
export function transformFlatEntityForComparison<
TFlatEntity extends SyncableFlatEntity,
PToCompare extends keyof TFlatEntity,
PJsonB extends PToCompare,
>({
flatEntity,
propertiesToCompare,
propertiesToStringify,
}: {
flatEntity: TFlatEntity;
propertiesToCompare: readonly PToCompare[];
propertiesToStringify: readonly PJsonB[];
}): Pick<TFlatEntity, PToCompare> {
return propertiesToCompare.reduce(
(flatEntityAccumulator, propertyToCompare) => {
const currentValue = flatEntity[propertyToCompare];
if (propertiesToStringify.includes(propertyToCompare as PJsonB)) {
const orderedValue = orderObjectProperties(currentValue);
return {
...flatEntityAccumulator,
[propertyToCompare]: JSON.stringify(orderedValue),
};
}
return {
...flatEntityAccumulator,
[propertyToCompare]: currentValue,
};
},
{} as Pick<TFlatEntity, PToCompare>,
);
}