Index v2 side effects (#14567)

# Introduction
Honestly this implem is a mess, discussing a potential side effect
handler with @weiko before the build and run that would handle each side
effect per entity and operation

Handling:
- [ ] unique index is generated when a field is updated with the
`isUnique`
- [x] an index is generated when a relation is created
- [x] search vector index creation on custom object creation
- [x] renaming a field metadata or an object should re-create all
related indexes which are composed by their namings
- [x] delete object should remove any related indexes
- [x] delete field should update related indexes ( if index ends up
empty it should be removed )
- [ ] on object renaming that contains morph fields -> triggers update
field -> trigger index recompute
- [x] on update name renaming should recompute all related indexes

## TODO
- [x] Integration testing
- [ ] Refactor the index maps cache to be storing a
`idsByObjectMetadataId`
- [x] Refactor deterministic name to use order sorting
- [x] Remove flat index from flat object

## What's next

Will handle morph indexes in a new dedicated PR for the moment will
stick to this
Same for the cache improvement and uniqueness
This commit is contained in:
Paul Rastoin
2025-09-23 10:57:11 +02:00
committed by GitHub
parent d0b9c7f076
commit fbd3286792
63 changed files with 2591 additions and 1323 deletions
@@ -5,6 +5,9 @@ import { isDefined } from 'twenty-shared/utils';
import { In, Repository } from 'typeorm';
import { WorkspaceManyOrAllFlatEntityMapsCacheService } from 'src/engine/core-modules/common/services/workspace-many-or-all-flat-entity-maps-cache.service.';
import { addFlatEntityToFlatEntityMapsOrThrow } from 'src/engine/core-modules/common/utils/add-flat-entity-to-flat-entity-maps-or-throw.util';
import { deleteFlatEntityFromFlatEntityMapsOrThrow } from 'src/engine/core-modules/common/utils/delete-flat-entity-from-flat-entity-maps-or-throw.util';
import { replaceFlatEntityInFlatEntityMapsOrThrow } from 'src/engine/core-modules/common/utils/replace-flat-entity-in-flat-entity-maps-or-throw.util';
import { type CreateFieldInput } from 'src/engine/metadata-modules/field-metadata/dtos/create-field.input';
import { type DeleteOneFieldInput } from 'src/engine/metadata-modules/field-metadata/dtos/delete-field.input';
import { FieldMetadataDTO } from 'src/engine/metadata-modules/field-metadata/dtos/field-metadata.dto';
@@ -64,19 +67,26 @@ export class FieldMetadataServiceV2 {
deleteOneFieldInput: DeleteOneFieldInput;
workspaceId: string;
}): Promise<FieldMetadataDTO> {
const { flatObjectMetadataMaps: existingFlatObjectMetadataMaps } =
const {
flatObjectMetadataMaps: existingFlatObjectMetadataMaps,
flatIndexMaps: existingFlatIndexMaps,
} =
await this.flatEntityMapsCacheService.getOrRecomputeManyOrAllFlatEntityMaps(
{
workspaceId,
flatEntities: ['flatObjectMetadataMaps'],
flatEntities: ['flatObjectMetadataMaps', 'flatIndexMaps'],
},
);
const flatFieldMetadatasToDelete =
fromDeleteFieldInputToFlatFieldMetadatasToDelete({
deleteOneFieldInput,
existingFlatObjectMetadataMaps,
});
const {
flatFieldMetadatasToDelete,
flatIndexesToDelete,
flatIndexesToUpdate,
} = fromDeleteFieldInputToFlatFieldMetadatasToDelete({
deleteOneFieldInput,
existingFlatObjectMetadataMaps,
existingFlatIndexMaps,
});
const fromFlatObjectMetadataMaps = getSubFlatObjectMetadataMapsOrThrow({
flatObjectMetadataMaps: existingFlatObjectMetadataMaps,
@@ -100,6 +110,23 @@ export class FieldMetadataServiceV2 {
fromFlatObjectMetadataMaps,
);
const toFlatIndexMapsWithUpdatedFlatIndex = flatIndexesToUpdate.reduce(
(flatIndexMaps, flatIndex) =>
replaceFlatEntityInFlatEntityMapsOrThrow({
flatEntity: flatIndex,
flatEntityMaps: flatIndexMaps,
}),
existingFlatIndexMaps,
);
const toFlatIndexMaps = flatIndexesToDelete.reduce(
(flatIndexMaps, flatIndex) =>
deleteFlatEntityFromFlatEntityMapsOrThrow({
entityToDeleteId: flatIndex.id,
flatEntityMaps: flatIndexMaps,
}),
toFlatIndexMapsWithUpdatedFlatIndex,
);
const validateAndBuildResult =
await this.workspaceMigrationValidateBuildAndRunService.validateBuildAndRunWorkspaceMigration(
{
@@ -112,6 +139,10 @@ export class FieldMetadataServiceV2 {
from: fromFlatObjectMetadataMaps,
to: toFlatObjectMetadataMaps,
},
flatIndexMaps: {
from: existingFlatIndexMaps,
to: toFlatIndexMaps,
},
},
workspaceId,
},
@@ -136,16 +167,20 @@ export class FieldMetadataServiceV2 {
updateFieldInput: UpdateFieldInput;
workspaceId: string;
}): Promise<FieldMetadataEntity> {
const { flatObjectMetadataMaps: existingFlatObjectMetadataMaps } =
const {
flatObjectMetadataMaps: existingFlatObjectMetadataMaps,
flatIndexMaps: existingFlatIndexMaps,
} =
await this.flatEntityMapsCacheService.getOrRecomputeManyOrAllFlatEntityMaps(
{
workspaceId,
flatEntities: ['flatObjectMetadataMaps'],
flatEntities: ['flatObjectMetadataMaps', 'flatIndexMaps'],
},
);
const inputTranspilationResult = fromUpdateFieldInputToFlatFieldMetadata({
existingFlatObjectMetadataMaps,
flatIndexMaps: existingFlatIndexMaps,
flatObjectMetadataMaps: existingFlatObjectMetadataMaps,
updateFieldInput,
});
@@ -153,8 +188,10 @@ export class FieldMetadataServiceV2 {
throw inputTranspilationResult.error;
}
const optimisticallyUpdatedFlatFieldMetadatas =
inputTranspilationResult.result;
const {
flatFieldMetadatasToUpdate: optimisticallyUpdatedFlatFieldMetadatas,
flatIndexMetadatasToUpdate,
} = inputTranspilationResult.result;
const objectMetadataIdWithRelatedObjectMetadataIds = [
...new Set(
@@ -181,6 +218,15 @@ export class FieldMetadataServiceV2 {
fromFlatObjectMetadataMaps,
);
const toFlatIndexMaps = flatIndexMetadatasToUpdate.reduce(
(flatIndexMaps, flatIndexMetadata) =>
replaceFlatEntityInFlatEntityMapsOrThrow({
flatEntity: flatIndexMetadata,
flatEntityMaps: flatIndexMaps,
}),
existingFlatIndexMaps,
);
const validateAndBuildResult =
await this.workspaceMigrationValidateBuildAndRunService.validateBuildAndRunWorkspaceMigration(
{
@@ -189,6 +235,10 @@ export class FieldMetadataServiceV2 {
from: fromFlatObjectMetadataMaps,
to: toFlatObjectMetadataMaps,
},
flatIndexMaps: {
from: existingFlatIndexMaps,
to: toFlatIndexMaps,
},
},
buildOptions: {
isSystemBuild: false,
@@ -224,15 +274,20 @@ export class FieldMetadataServiceV2 {
return [];
}
const { flatObjectMetadataMaps: existingFlatObjectMetadataMaps } =
const {
flatObjectMetadataMaps: existingFlatObjectMetadataMaps,
flatIndexMaps: existingFlatIndexMaps,
} =
await this.flatEntityMapsCacheService.getOrRecomputeManyOrAllFlatEntityMaps(
{
workspaceId,
flatEntities: ['flatObjectMetadataMaps'],
flatEntities: ['flatObjectMetadataMaps', 'flatIndexMaps'],
},
);
const allTranspiledTranspilationInputs = [];
const allTranspiledTranspilationInputs: Awaited<
ReturnType<typeof fromCreateFieldInputToFlatFieldMetadatasToCreate>
>[] = [];
for (const createInput of createFieldInputs) {
allTranspiledTranspilationInputs.push(
@@ -249,8 +304,18 @@ export class FieldMetadataServiceV2 {
'Multiple validation errors occurred while creating field',
);
const flatFieldMetadatasToCreate = allTranspiledTranspilationInputs.flatMap(
({ result }) => result,
const {
flatFieldMetadatas: flatFieldMetadatasToCreate,
indexMetadatas: flatIndexMetadatasToCreate,
} = allTranspiledTranspilationInputs.reduce(
(acc, { result }) => ({
flatFieldMetadatas: [
...acc.flatFieldMetadatas,
...result.flatFieldMetadatas,
],
indexMetadatas: [...acc.indexMetadatas, ...result.indexMetadatas],
}),
{ flatFieldMetadatas: [], indexMetadatas: [] },
);
const impactedObjectMetadataIds = Array.from(
@@ -277,6 +342,15 @@ export class FieldMetadataServiceV2 {
}),
);
const toFlatIndexMaps = flatIndexMetadatasToCreate.reduce(
(flatIndexMaps, flatIndex) =>
addFlatEntityToFlatEntityMapsOrThrow({
flatEntity: flatIndex,
flatEntityMaps: flatIndexMaps,
}),
existingFlatIndexMaps,
);
const validateAndBuildResult =
await this.workspaceMigrationValidateBuildAndRunService.validateBuildAndRunWorkspaceMigration(
{
@@ -285,6 +359,10 @@ export class FieldMetadataServiceV2 {
from: fromFlatObjectMetadataMaps,
to: toFlatObjectMetadataMaps,
},
flatIndexMaps: {
from: existingFlatIndexMaps,
to: toFlatIndexMaps,
},
},
buildOptions: {
isSystemBuild: false,
@@ -305,7 +383,7 @@ export class FieldMetadataServiceV2 {
where: {
name: In(
allTranspiledTranspilationInputs.map(
({ result: flatFieldMetadatas }) => flatFieldMetadatas[0].name,
({ result: { flatFieldMetadatas } }) => flatFieldMetadatas[0].name,
),
),
workspaceId,
@@ -83,12 +83,189 @@ exports[`fromCreateFieldInputToFlatFieldMetadatasToCreate MORPH_RELATION test su
exports[`fromCreateFieldInputToFlatFieldMetadatasToCreate MORPH_RELATION test suite Success cases should create morph relation field metadata with valid input on rocket object to pet object 1`] = `
{
"result": [
{
"createdAt": Any<ClockDate>,
"defaultValue": null,
"description": "new field description",
"flatRelationTargetFieldMetadata": {
"result": {
"flatFieldMetadatas": [
{
"createdAt": Any<ClockDate>,
"defaultValue": null,
"description": "new field description",
"flatRelationTargetFieldMetadata": {
"createdAt": Any<ClockDate>,
"defaultValue": null,
"description": null,
"flatRelationTargetFieldMetadata": {
"createdAt": Any<ClockDate>,
"defaultValue": null,
"description": "new field description",
"flatRelationTargetFieldMetadata": null,
"flatRelationTargetObjectMetadata": {
"createdAt": Any<String>,
"description": null,
"duplicateCriteria": null,
"flatFieldMetadatas": [],
"icon": "IconCat",
"id": Any<String>,
"imageIdentifierFieldMetadataId": null,
"isActive": true,
"isAuditLogged": true,
"isCustom": true,
"isLabelSyncedWithName": false,
"isRemote": false,
"isSearchable": true,
"isSystem": false,
"isUIReadOnly": false,
"labelIdentifierFieldMetadataId": Any<String>,
"labelPlural": "Pets",
"labelSingular": "Pet",
"namePlural": "pets",
"nameSingular": "pet",
"shortcut": null,
"standardId": null,
"standardOverrides": null,
"targetTableName": "DEPRECATED",
"universalIdentifier": Any<String>,
"updatedAt": Any<String>,
"workspaceId": Any<String>,
},
"icon": "IconRelationOneToMany",
"id": Any<String>,
"isActive": true,
"isCustom": true,
"isLabelSyncedWithName": false,
"isNullable": true,
"isSystem": false,
"isUIReadOnly": false,
"isUnique": null,
"label": "newFieldLabel",
"morphId": Any<String>,
"name": "newFieldPets",
"objectMetadataId": Any<String>,
"options": null,
"relationTargetFieldMetadataId": Any<String>,
"relationTargetObjectMetadataId": Any<String>,
"settings": {
"relationType": "ONE_TO_MANY",
},
"standardId": null,
"standardOverrides": null,
"type": "MORPH_RELATION",
"universalIdentifier": Any<String>,
"updatedAt": Any<ClockDate>,
"workspaceId": Any<String>,
},
"flatRelationTargetObjectMetadata": {
"createdAt": Any<String>,
"description": "A rocket",
"duplicateCriteria": null,
"flatFieldMetadatas": [],
"icon": "IconRocket",
"id": Any<String>,
"imageIdentifierFieldMetadataId": null,
"isActive": true,
"isAuditLogged": true,
"isCustom": true,
"isLabelSyncedWithName": false,
"isRemote": false,
"isSearchable": true,
"isSystem": false,
"isUIReadOnly": false,
"labelIdentifierFieldMetadataId": Any<String>,
"labelPlural": "Rockets",
"labelSingular": "Rocket",
"namePlural": "rockets",
"nameSingular": "rocket",
"shortcut": null,
"standardId": null,
"standardOverrides": null,
"targetTableName": "DEPRECATED",
"universalIdentifier": Any<String>,
"updatedAt": Any<String>,
"workspaceId": Any<String>,
},
"icon": "IconPet",
"id": Any<String>,
"isActive": true,
"isCustom": true,
"isLabelSyncedWithName": false,
"isNullable": true,
"isSystem": false,
"isUIReadOnly": false,
"isUnique": null,
"label": "Pet",
"morphId": null,
"name": "pet",
"objectMetadataId": Any<String>,
"options": null,
"relationTargetFieldMetadataId": Any<String>,
"relationTargetObjectMetadataId": Any<String>,
"settings": {
"joinColumnName": "petId",
"onDelete": "SET_NULL",
"relationType": "MANY_TO_ONE",
},
"standardId": null,
"standardOverrides": null,
"type": "RELATION",
"universalIdentifier": Any<String>,
"updatedAt": Any<ClockDate>,
"workspaceId": Any<String>,
},
"flatRelationTargetObjectMetadata": {
"createdAt": Any<String>,
"description": null,
"duplicateCriteria": null,
"flatFieldMetadatas": [],
"icon": "IconCat",
"id": Any<String>,
"imageIdentifierFieldMetadataId": null,
"isActive": true,
"isAuditLogged": true,
"isCustom": true,
"isLabelSyncedWithName": false,
"isRemote": false,
"isSearchable": true,
"isSystem": false,
"isUIReadOnly": false,
"labelIdentifierFieldMetadataId": Any<String>,
"labelPlural": "Pets",
"labelSingular": "Pet",
"namePlural": "pets",
"nameSingular": "pet",
"shortcut": null,
"standardId": null,
"standardOverrides": null,
"targetTableName": "DEPRECATED",
"universalIdentifier": Any<String>,
"updatedAt": Any<String>,
"workspaceId": Any<String>,
},
"icon": "IconRelationOneToMany",
"id": Any<String>,
"isActive": true,
"isCustom": true,
"isLabelSyncedWithName": false,
"isNullable": true,
"isSystem": false,
"isUIReadOnly": false,
"isUnique": null,
"label": "newFieldLabel",
"morphId": Any<String>,
"name": "newFieldPets",
"objectMetadataId": Any<String>,
"options": null,
"relationTargetFieldMetadataId": Any<String>,
"relationTargetObjectMetadataId": Any<String>,
"settings": {
"relationType": "ONE_TO_MANY",
},
"standardId": null,
"standardOverrides": null,
"type": "MORPH_RELATION",
"universalIdentifier": Any<String>,
"updatedAt": Any<ClockDate>,
"workspaceId": Any<String>,
},
{
"createdAt": Any<ClockDate>,
"defaultValue": null,
"description": null,
@@ -102,7 +279,6 @@ exports[`fromCreateFieldInputToFlatFieldMetadatasToCreate MORPH_RELATION test su
"description": null,
"duplicateCriteria": null,
"flatFieldMetadatas": [],
"flatIndexMetadatas": [],
"icon": "IconCat",
"id": Any<String>,
"imageIdentifierFieldMetadataId": null,
@@ -158,7 +334,6 @@ exports[`fromCreateFieldInputToFlatFieldMetadatasToCreate MORPH_RELATION test su
"description": "A rocket",
"duplicateCriteria": null,
"flatFieldMetadatas": [],
"flatIndexMetadatas": [],
"icon": "IconRocket",
"id": Any<String>,
"imageIdentifierFieldMetadataId": null,
@@ -211,95 +386,154 @@ exports[`fromCreateFieldInputToFlatFieldMetadatasToCreate MORPH_RELATION test su
"updatedAt": Any<ClockDate>,
"workspaceId": Any<String>,
},
"flatRelationTargetObjectMetadata": {
"createdAt": Any<String>,
"description": null,
"duplicateCriteria": null,
"flatFieldMetadatas": [],
"flatIndexMetadatas": [],
"icon": "IconCat",
"id": Any<String>,
"imageIdentifierFieldMetadataId": null,
"isActive": true,
"isAuditLogged": true,
"isCustom": true,
"isLabelSyncedWithName": false,
"isRemote": false,
"isSearchable": true,
"isSystem": false,
"isUIReadOnly": false,
"labelIdentifierFieldMetadataId": Any<String>,
"labelPlural": "Pets",
"labelSingular": "Pet",
"namePlural": "pets",
"nameSingular": "pet",
"shortcut": null,
"standardId": null,
"standardOverrides": null,
"targetTableName": "DEPRECATED",
"universalIdentifier": Any<String>,
"updatedAt": Any<String>,
"workspaceId": Any<String>,
},
"icon": "IconRelationOneToMany",
"id": Any<String>,
"isActive": true,
"isCustom": true,
"isLabelSyncedWithName": false,
"isNullable": true,
"isSystem": false,
"isUIReadOnly": false,
"isUnique": null,
"label": "newFieldLabel",
"morphId": Any<String>,
"name": "newFieldPets",
"objectMetadataId": Any<String>,
"options": null,
"relationTargetFieldMetadataId": Any<String>,
"relationTargetObjectMetadataId": Any<String>,
"settings": {
"relationType": "ONE_TO_MANY",
},
"standardId": null,
"standardOverrides": null,
"type": "MORPH_RELATION",
"universalIdentifier": Any<String>,
"updatedAt": Any<ClockDate>,
"workspaceId": Any<String>,
},
{
"createdAt": Any<ClockDate>,
"defaultValue": null,
"description": null,
"flatRelationTargetFieldMetadata": {
{
"createdAt": Any<ClockDate>,
"defaultValue": null,
"description": "new field description",
"flatRelationTargetFieldMetadata": null,
"flatRelationTargetFieldMetadata": {
"createdAt": Any<ClockDate>,
"defaultValue": null,
"description": null,
"flatRelationTargetFieldMetadata": {
"createdAt": Any<ClockDate>,
"defaultValue": null,
"description": "new field description",
"flatRelationTargetFieldMetadata": null,
"flatRelationTargetObjectMetadata": {
"createdAt": Any<String>,
"description": "A company",
"duplicateCriteria": null,
"flatFieldMetadatas": [],
"icon": "IconBuildingSkyscraper",
"id": Any<String>,
"imageIdentifierFieldMetadataId": null,
"isActive": true,
"isAuditLogged": true,
"isCustom": false,
"isLabelSyncedWithName": false,
"isRemote": false,
"isSearchable": true,
"isSystem": false,
"isUIReadOnly": false,
"labelIdentifierFieldMetadataId": Any<String>,
"labelPlural": "Companies",
"labelSingular": "Company",
"namePlural": "companies",
"nameSingular": "company",
"shortcut": "C",
"standardId": Any<String>,
"standardOverrides": null,
"targetTableName": "DEPRECATED",
"universalIdentifier": Any<String>,
"updatedAt": Any<String>,
"workspaceId": Any<String>,
},
"icon": "IconRelationOneToMany",
"id": Any<String>,
"isActive": true,
"isCustom": true,
"isLabelSyncedWithName": false,
"isNullable": true,
"isSystem": false,
"isUIReadOnly": false,
"isUnique": null,
"label": "newFieldLabel",
"morphId": Any<String>,
"name": "newFieldCompanies",
"objectMetadataId": Any<String>,
"options": null,
"relationTargetFieldMetadataId": Any<String>,
"relationTargetObjectMetadataId": Any<String>,
"settings": {
"relationType": "ONE_TO_MANY",
},
"standardId": null,
"standardOverrides": null,
"type": "MORPH_RELATION",
"universalIdentifier": Any<String>,
"updatedAt": Any<ClockDate>,
"workspaceId": Any<String>,
},
"flatRelationTargetObjectMetadata": {
"createdAt": Any<String>,
"description": "A rocket",
"duplicateCriteria": null,
"flatFieldMetadatas": [],
"icon": "IconRocket",
"id": Any<String>,
"imageIdentifierFieldMetadataId": null,
"isActive": true,
"isAuditLogged": true,
"isCustom": true,
"isLabelSyncedWithName": false,
"isRemote": false,
"isSearchable": true,
"isSystem": false,
"isUIReadOnly": false,
"labelIdentifierFieldMetadataId": Any<String>,
"labelPlural": "Rockets",
"labelSingular": "Rocket",
"namePlural": "rockets",
"nameSingular": "rocket",
"shortcut": null,
"standardId": null,
"standardOverrides": null,
"targetTableName": "DEPRECATED",
"universalIdentifier": Any<String>,
"updatedAt": Any<String>,
"workspaceId": Any<String>,
},
"icon": "IconBuilding",
"id": Any<String>,
"isActive": true,
"isCustom": true,
"isLabelSyncedWithName": false,
"isNullable": true,
"isSystem": false,
"isUIReadOnly": false,
"isUnique": null,
"label": "Company",
"morphId": null,
"name": "company",
"objectMetadataId": Any<String>,
"options": null,
"relationTargetFieldMetadataId": Any<String>,
"relationTargetObjectMetadataId": Any<String>,
"settings": {
"joinColumnName": "companyId",
"onDelete": "SET_NULL",
"relationType": "MANY_TO_ONE",
},
"standardId": null,
"standardOverrides": null,
"type": "RELATION",
"universalIdentifier": Any<String>,
"updatedAt": Any<ClockDate>,
"workspaceId": Any<String>,
},
"flatRelationTargetObjectMetadata": {
"createdAt": Any<String>,
"description": null,
"description": "A company",
"duplicateCriteria": null,
"flatFieldMetadatas": [],
"flatIndexMetadatas": [],
"icon": "IconCat",
"icon": "IconBuildingSkyscraper",
"id": Any<String>,
"imageIdentifierFieldMetadataId": null,
"isActive": true,
"isAuditLogged": true,
"isCustom": true,
"isCustom": false,
"isLabelSyncedWithName": false,
"isRemote": false,
"isSearchable": true,
"isSystem": false,
"isUIReadOnly": false,
"labelIdentifierFieldMetadataId": Any<String>,
"labelPlural": "Pets",
"labelSingular": "Pet",
"namePlural": "pets",
"nameSingular": "pet",
"shortcut": null,
"standardId": null,
"labelPlural": "Companies",
"labelSingular": "Company",
"namePlural": "companies",
"nameSingular": "company",
"shortcut": "C",
"standardId": Any<String>,
"standardOverrides": null,
"targetTableName": "DEPRECATED",
"universalIdentifier": Any<String>,
@@ -317,7 +551,7 @@ exports[`fromCreateFieldInputToFlatFieldMetadatasToCreate MORPH_RELATION test su
"isUnique": null,
"label": "newFieldLabel",
"morphId": Any<String>,
"name": "newFieldPets",
"name": "newFieldCompanies",
"objectMetadataId": Any<String>,
"options": null,
"relationTargetFieldMetadataId": Any<String>,
@@ -332,69 +566,7 @@ exports[`fromCreateFieldInputToFlatFieldMetadatasToCreate MORPH_RELATION test su
"updatedAt": Any<ClockDate>,
"workspaceId": Any<String>,
},
"flatRelationTargetObjectMetadata": {
"createdAt": Any<String>,
"description": "A rocket",
"duplicateCriteria": null,
"flatFieldMetadatas": [],
"flatIndexMetadatas": [],
"icon": "IconRocket",
"id": Any<String>,
"imageIdentifierFieldMetadataId": null,
"isActive": true,
"isAuditLogged": true,
"isCustom": true,
"isLabelSyncedWithName": false,
"isRemote": false,
"isSearchable": true,
"isSystem": false,
"isUIReadOnly": false,
"labelIdentifierFieldMetadataId": Any<String>,
"labelPlural": "Rockets",
"labelSingular": "Rocket",
"namePlural": "rockets",
"nameSingular": "rocket",
"shortcut": null,
"standardId": null,
"standardOverrides": null,
"targetTableName": "DEPRECATED",
"universalIdentifier": Any<String>,
"updatedAt": Any<String>,
"workspaceId": Any<String>,
},
"icon": "IconPet",
"id": Any<String>,
"isActive": true,
"isCustom": true,
"isLabelSyncedWithName": false,
"isNullable": true,
"isSystem": false,
"isUIReadOnly": false,
"isUnique": null,
"label": "Pet",
"morphId": null,
"name": "pet",
"objectMetadataId": Any<String>,
"options": null,
"relationTargetFieldMetadataId": Any<String>,
"relationTargetObjectMetadataId": Any<String>,
"settings": {
"joinColumnName": "petId",
"onDelete": "SET_NULL",
"relationType": "MANY_TO_ONE",
},
"standardId": null,
"standardOverrides": null,
"type": "RELATION",
"universalIdentifier": Any<String>,
"updatedAt": Any<ClockDate>,
"workspaceId": Any<String>,
},
{
"createdAt": Any<ClockDate>,
"defaultValue": null,
"description": "new field description",
"flatRelationTargetFieldMetadata": {
{
"createdAt": Any<ClockDate>,
"defaultValue": null,
"description": null,
@@ -408,7 +580,6 @@ exports[`fromCreateFieldInputToFlatFieldMetadatasToCreate MORPH_RELATION test su
"description": "A company",
"duplicateCriteria": null,
"flatFieldMetadatas": [],
"flatIndexMetadatas": [],
"icon": "IconBuildingSkyscraper",
"id": Any<String>,
"imageIdentifierFieldMetadataId": null,
@@ -464,7 +635,6 @@ exports[`fromCreateFieldInputToFlatFieldMetadatasToCreate MORPH_RELATION test su
"description": "A rocket",
"duplicateCriteria": null,
"flatFieldMetadatas": [],
"flatIndexMetadatas": [],
"icon": "IconRocket",
"id": Any<String>,
"imageIdentifierFieldMetadataId": null,
@@ -517,186 +687,56 @@ exports[`fromCreateFieldInputToFlatFieldMetadatasToCreate MORPH_RELATION test su
"updatedAt": Any<ClockDate>,
"workspaceId": Any<String>,
},
"flatRelationTargetObjectMetadata": {
"createdAt": Any<String>,
"description": "A company",
"duplicateCriteria": null,
"flatFieldMetadatas": [],
"flatIndexMetadatas": [],
"icon": "IconBuildingSkyscraper",
"id": Any<String>,
"imageIdentifierFieldMetadataId": null,
"isActive": true,
"isAuditLogged": true,
"isCustom": false,
"isLabelSyncedWithName": false,
"isRemote": false,
"isSearchable": true,
"isSystem": false,
"isUIReadOnly": false,
"labelIdentifierFieldMetadataId": Any<String>,
"labelPlural": "Companies",
"labelSingular": "Company",
"namePlural": "companies",
"nameSingular": "company",
"shortcut": "C",
"standardId": Any<String>,
"standardOverrides": null,
"targetTableName": "DEPRECATED",
"universalIdentifier": Any<String>,
"updatedAt": Any<String>,
"workspaceId": Any<String>,
},
"icon": "IconRelationOneToMany",
"id": Any<String>,
"isActive": true,
"isCustom": true,
"isLabelSyncedWithName": false,
"isNullable": true,
"isSystem": false,
"isUIReadOnly": false,
"isUnique": null,
"label": "newFieldLabel",
"morphId": Any<String>,
"name": "newFieldCompanies",
"objectMetadataId": Any<String>,
"options": null,
"relationTargetFieldMetadataId": Any<String>,
"relationTargetObjectMetadataId": Any<String>,
"settings": {
"relationType": "ONE_TO_MANY",
},
"standardId": null,
"standardOverrides": null,
"type": "MORPH_RELATION",
"universalIdentifier": Any<String>,
"updatedAt": Any<ClockDate>,
"workspaceId": Any<String>,
},
{
"createdAt": Any<ClockDate>,
"defaultValue": null,
"description": null,
"flatRelationTargetFieldMetadata": {
],
"indexMetadatas": [
{
"createdAt": Any<ClockDate>,
"defaultValue": null,
"description": "new field description",
"flatRelationTargetFieldMetadata": null,
"flatRelationTargetObjectMetadata": {
"createdAt": Any<String>,
"description": "A company",
"duplicateCriteria": null,
"flatFieldMetadatas": [],
"flatIndexMetadatas": [],
"icon": "IconBuildingSkyscraper",
"id": Any<String>,
"imageIdentifierFieldMetadataId": null,
"isActive": true,
"isAuditLogged": true,
"isCustom": false,
"isLabelSyncedWithName": false,
"isRemote": false,
"isSearchable": true,
"isSystem": false,
"isUIReadOnly": false,
"labelIdentifierFieldMetadataId": Any<String>,
"labelPlural": "Companies",
"labelSingular": "Company",
"namePlural": "companies",
"nameSingular": "company",
"shortcut": "C",
"standardId": Any<String>,
"standardOverrides": null,
"targetTableName": "DEPRECATED",
"universalIdentifier": Any<String>,
"updatedAt": Any<String>,
"workspaceId": Any<String>,
},
"icon": "IconRelationOneToMany",
"flatIndexFieldMetadatas": [
{
"createdAt": Any<ClockDate>,
"fieldMetadataId": Any<String>,
"id": Any<String>,
"indexMetadataId": Any<String>,
"order": 0,
"updatedAt": Any<ClockDate>,
},
],
"id": Any<String>,
"isActive": true,
"indexType": "BTREE",
"indexWhereClause": null,
"isCustom": true,
"isLabelSyncedWithName": false,
"isNullable": true,
"isSystem": false,
"isUIReadOnly": false,
"isUnique": null,
"label": "newFieldLabel",
"morphId": Any<String>,
"name": "newFieldCompanies",
"isUnique": false,
"name": "IDX_f687e4e4252800dddd8e5518362",
"objectMetadataId": Any<String>,
"options": null,
"relationTargetFieldMetadataId": Any<String>,
"relationTargetObjectMetadataId": Any<String>,
"settings": {
"relationType": "ONE_TO_MANY",
},
"standardId": null,
"standardOverrides": null,
"type": "MORPH_RELATION",
"universalIdentifier": Any<String>,
"updatedAt": Any<ClockDate>,
"workspaceId": Any<String>,
},
"flatRelationTargetObjectMetadata": {
"createdAt": Any<String>,
"description": "A rocket",
"duplicateCriteria": null,
"flatFieldMetadatas": [],
"flatIndexMetadatas": [],
"icon": "IconRocket",
{
"createdAt": Any<ClockDate>,
"flatIndexFieldMetadatas": [
{
"createdAt": Any<ClockDate>,
"fieldMetadataId": Any<String>,
"id": Any<String>,
"indexMetadataId": Any<String>,
"order": 0,
"updatedAt": Any<ClockDate>,
},
],
"id": Any<String>,
"imageIdentifierFieldMetadataId": null,
"isActive": true,
"isAuditLogged": true,
"indexType": "BTREE",
"indexWhereClause": null,
"isCustom": true,
"isLabelSyncedWithName": false,
"isRemote": false,
"isSearchable": true,
"isSystem": false,
"isUIReadOnly": false,
"labelIdentifierFieldMetadataId": Any<String>,
"labelPlural": "Rockets",
"labelSingular": "Rocket",
"namePlural": "rockets",
"nameSingular": "rocket",
"shortcut": null,
"standardId": null,
"standardOverrides": null,
"targetTableName": "DEPRECATED",
"isUnique": false,
"name": "IDX_cb15d901e889e25d0a9acecb595",
"objectMetadataId": Any<String>,
"universalIdentifier": Any<String>,
"updatedAt": Any<String>,
"updatedAt": Any<ClockDate>,
"workspaceId": Any<String>,
},
"icon": "IconBuilding",
"id": Any<String>,
"isActive": true,
"isCustom": true,
"isLabelSyncedWithName": false,
"isNullable": true,
"isSystem": false,
"isUIReadOnly": false,
"isUnique": null,
"label": "Company",
"morphId": null,
"name": "company",
"objectMetadataId": Any<String>,
"options": null,
"relationTargetFieldMetadataId": Any<String>,
"relationTargetObjectMetadataId": Any<String>,
"settings": {
"joinColumnName": "companyId",
"onDelete": "SET_NULL",
"relationType": "MANY_TO_ONE",
},
"standardId": null,
"standardOverrides": null,
"type": "RELATION",
"universalIdentifier": Any<String>,
"updatedAt": Any<ClockDate>,
"workspaceId": Any<String>,
},
],
],
},
"status": "success",
}
`;
@@ -87,7 +87,7 @@ describe('fromCreateFieldInputToFlatFieldMetadatasToCreate MORPH_RELATION test s
if (result.status !== 'success') {
throw new Error('Should never occur, typecheck');
}
expect(result.result.length).toBe(
expect(result.result.flatFieldMetadatas.length).toBe(
input.rawCreateFieldInput.morphRelationsCreationPayload.length * 2,
);
expect(result).toMatchSnapshot(
@@ -0,0 +1,14 @@
import { type FlatFieldMetadata } from 'src/engine/metadata-modules/flat-field-metadata/types/flat-field-metadata.type';
export const FLAT_FIELD_METADATA_EDITABLE_PROPERTIES = [
'defaultValue',
'description',
'icon',
'isActive',
'isLabelSyncedWithName',
'isUnique',
'label',
'name',
'options',
'settings',
] as const satisfies (keyof FlatFieldMetadata)[];
@@ -1,15 +1,7 @@
import { FLAT_FIELD_METADATA_EDITABLE_PROPERTIES } from 'src/engine/metadata-modules/flat-field-metadata/constants/flat-field-metadata-editable-properties.constant';
import { type FlatFieldMetadata } from 'src/engine/metadata-modules/flat-field-metadata/types/flat-field-metadata.type';
export const FLAT_FIELD_METADATA_PROPERTIES_TO_COMPARE = [
'defaultValue',
'description',
'icon',
'isActive',
'isLabelSyncedWithName',
'isUnique',
'label',
'name',
'options',
...FLAT_FIELD_METADATA_EDITABLE_PROPERTIES,
'standardOverrides',
'settings',
] as const satisfies (keyof FlatFieldMetadata)[];
@@ -0,0 +1,4 @@
import { type FLAT_FIELD_METADATA_EDITABLE_PROPERTIES } from 'src/engine/metadata-modules/flat-field-metadata/constants/flat-field-metadata-editable-properties.constant';
export type FlatFieldMetadataEditableProperties =
(typeof FLAT_FIELD_METADATA_EDITABLE_PROPERTIES)[number];
@@ -17,6 +17,7 @@ import { type FlatFieldMetadata } from 'src/engine/metadata-modules/flat-field-m
import { fromMorphRelationCreateFieldInputToFlatFieldMetadatas } from 'src/engine/metadata-modules/flat-field-metadata/utils/from-morph-relation-create-field-input-to-flat-field-metadatas.util';
import { fromRelationCreateFieldInputToFlatFieldMetadatas } from 'src/engine/metadata-modules/flat-field-metadata/utils/from-relation-create-field-input-to-flat-field-metadatas.util';
import { getDefaultFlatFieldMetadata } from 'src/engine/metadata-modules/flat-field-metadata/utils/get-default-flat-field-metadata-from-create-field-input.util';
import { type FlatIndexMetadata } from 'src/engine/metadata-modules/flat-index-metadata/types/flat-index-metadata.type';
import { type FlatObjectMetadataMaps } from 'src/engine/metadata-modules/flat-object-metadata-maps/types/flat-object-metadata-maps.type';
import { fromFlatObjectMetadataWithFlatFieldMapsToFlatObjectMetadata } from 'src/engine/metadata-modules/flat-object-metadata/utils/from-flat-object-metadata-with-flat-field-maps-to-flat-object-metadatas.util';
@@ -31,7 +32,10 @@ export const fromCreateFieldInputToFlatFieldMetadatasToCreate = async ({
workspaceId,
existingFlatObjectMetadataMaps,
}: FromCreateFieldInputToFlatObjectMetadataArgs): Promise<
FieldInputTranspilationResult<FlatFieldMetadata[]>
FieldInputTranspilationResult<{
flatFieldMetadatas: FlatFieldMetadata[];
indexMetadatas: FlatIndexMetadata[];
}>
> => {
if (rawCreateFieldInput.isRemoteCreation) {
return {
@@ -99,15 +103,18 @@ export const fromCreateFieldInputToFlatFieldMetadatasToCreate = async ({
case FieldMetadataType.RATING: {
return {
status: 'success',
result: [
{
...commonFlatFieldMetadata,
type: createFieldInput.type,
settings: null,
defaultValue: commonFlatFieldMetadata.defaultValue as string, // Could this be improved ?
options: generateRatingOptions(),
} satisfies FlatFieldMetadata<typeof createFieldInput.type>,
],
result: {
flatFieldMetadatas: [
{
...commonFlatFieldMetadata,
type: createFieldInput.type,
settings: null,
defaultValue: commonFlatFieldMetadata.defaultValue as string, // Could this be improved ?
options: generateRatingOptions(),
} satisfies FlatFieldMetadata<typeof createFieldInput.type>,
],
indexMetadatas: [],
},
};
}
case FieldMetadataType.SELECT:
@@ -124,15 +131,18 @@ export const fromCreateFieldInputToFlatFieldMetadatasToCreate = async ({
return {
status: 'success',
result: [
{
...commonFlatFieldMetadata,
type: createFieldInput.type,
options,
defaultValue: commonFlatFieldMetadata.defaultValue as string, // Could this be improved ?
settings: null,
} satisfies FlatFieldMetadata<typeof createFieldInput.type>,
],
result: {
flatFieldMetadatas: [
{
...commonFlatFieldMetadata,
type: createFieldInput.type,
options,
defaultValue: commonFlatFieldMetadata.defaultValue as string, // Could this be improved ?
settings: null,
} satisfies FlatFieldMetadata<typeof createFieldInput.type>,
],
indexMetadatas: [],
},
};
}
case FieldMetadataType.TS_VECTOR: {
@@ -165,12 +175,15 @@ export const fromCreateFieldInputToFlatFieldMetadatasToCreate = async ({
case FieldMetadataType.ARRAY: {
return {
status: 'success',
result: [
{
...commonFlatFieldMetadata,
type: createFieldInput.type,
},
],
result: {
flatFieldMetadatas: [
{
...commonFlatFieldMetadata,
type: createFieldInput.type,
},
],
indexMetadatas: [],
},
};
}
default: {
@@ -3,6 +3,7 @@ import {
trimAndRemoveDuplicatedWhitespacesFromObjectStringProperties,
} from 'twenty-shared/utils';
import { type FlatEntityMaps } from 'src/engine/core-modules/common/types/flat-entity-maps.type';
import { type DeleteOneFieldInput } from 'src/engine/metadata-modules/field-metadata/dtos/delete-field.input';
import {
FieldMetadataException,
@@ -10,17 +11,27 @@ import {
} from 'src/engine/metadata-modules/field-metadata/field-metadata.exception';
import { type FlatFieldMetadata } from 'src/engine/metadata-modules/flat-field-metadata/types/flat-field-metadata.type';
import { computeFlatFieldMetadataRelatedFlatFieldMetadata } from 'src/engine/metadata-modules/flat-field-metadata/utils/compute-flat-field-metadata-related-flat-field-metadata.util';
import { type FlatIndexMetadata } from 'src/engine/metadata-modules/flat-index-metadata/types/flat-index-metadata.type';
import { type FlatObjectMetadataMaps } from 'src/engine/metadata-modules/flat-object-metadata-maps/types/flat-object-metadata-maps.type';
import { findFlatFieldMetadataInFlatObjectMetadataMapsWithOnlyFieldId } from 'src/engine/metadata-modules/flat-object-metadata-maps/utils/find-flat-field-metadata-in-flat-object-metadata-maps-with-field-id-only.util';
import { findFlatObjectMetadataInFlatObjectMetadataMapsOrThrow } from 'src/engine/metadata-modules/flat-object-metadata-maps/utils/find-flat-object-metadata-in-flat-object-metadata-maps-or-throw.util';
import { generateFlatIndexMetadataWithNameOrThrow } from 'src/engine/metadata-modules/index-metadata/utils/generate-flat-index.util';
type FromDeleteFieldInputToFlatFieldMetadatasToDeleteArgs = {
existingFlatObjectMetadataMaps: FlatObjectMetadataMaps;
existingFlatIndexMaps: FlatEntityMaps<FlatIndexMetadata>;
deleteOneFieldInput: DeleteOneFieldInput;
};
// TODO refactor as a side effect service
export const fromDeleteFieldInputToFlatFieldMetadatasToDelete = ({
existingFlatObjectMetadataMaps,
deleteOneFieldInput: rawDeleteOneInput,
}: FromDeleteFieldInputToFlatFieldMetadatasToDeleteArgs): FlatFieldMetadata[] => {
existingFlatIndexMaps,
}: FromDeleteFieldInputToFlatFieldMetadatasToDeleteArgs): {
flatFieldMetadatasToDelete: FlatFieldMetadata[];
flatIndexesToUpdate: FlatIndexMetadata[];
flatIndexesToDelete: FlatIndexMetadata[];
} => {
const { id: fieldMetadataToDeleteId } =
trimAndRemoveDuplicatedWhitespacesFromObjectStringProperties(
rawDeleteOneInput,
@@ -46,5 +57,96 @@ export const fromDeleteFieldInputToFlatFieldMetadatasToDelete = ({
flatObjectMetadataMaps: existingFlatObjectMetadataMaps,
});
return [flatFieldMetadataToDelete, ...relatedFlatFieldMetadataToDelete];
const flatFieldMetadatasToDelete = [
flatFieldMetadataToDelete,
...relatedFlatFieldMetadataToDelete,
];
const flatIndexMap = new Map<string, FlatIndexMetadata>();
const allFlatIndexes = Object.values(existingFlatIndexMaps.byId).filter(
isDefined,
);
for (const flatFieldMetadata of flatFieldMetadatasToDelete) {
allFlatIndexes.forEach((flatIndex) => {
const flatIndexFromMap = flatIndexMap.get(flatIndex.id);
if (isDefined(flatIndexFromMap)) {
const updatedFlatIndexFields =
flatIndexFromMap.flatIndexFieldMetadatas.filter(
(flatIndexField) =>
flatIndexField.fieldMetadataId !== flatFieldMetadata.id,
);
flatIndexMap.set(flatIndexFromMap.id, {
...flatIndexFromMap,
flatIndexFieldMetadatas: updatedFlatIndexFields,
});
return;
}
if (
flatIndex.objectMetadataId !== flatFieldMetadata.objectMetadataId ||
!flatIndex.flatIndexFieldMetadatas.some(
(flatIndexField) =>
flatIndexField.fieldMetadataId === flatFieldMetadata.id,
)
) {
return;
}
const updatedFlatIndexFields = flatIndex.flatIndexFieldMetadatas.filter(
(flatIndexField) =>
flatIndexField.fieldMetadataId !== flatFieldMetadata.id,
);
flatIndexMap.set(flatIndex.id, {
...flatIndex,
flatIndexFieldMetadatas: updatedFlatIndexFields,
});
});
}
const { flatIndexesToDelete, flatIndexesToUpdate } = [
...flatIndexMap.values(),
].reduce<{
flatIndexesToUpdate: FlatIndexMetadata[];
flatIndexesToDelete: FlatIndexMetadata[];
}>(
(acc, flatIndex) => {
if (flatIndex.flatIndexFieldMetadatas.length === 0) {
return {
...acc,
flatIndexesToDelete: [...acc.flatIndexesToDelete, flatIndex],
};
}
const flatObjectMetadata =
findFlatObjectMetadataInFlatObjectMetadataMapsOrThrow({
flatObjectMetadataMaps: existingFlatObjectMetadataMaps,
objectMetadataId: flatIndex.objectMetadataId,
});
const newIndex = generateFlatIndexMetadataWithNameOrThrow({
flatObjectMetadata,
flatIndex,
});
return {
...acc,
flatIndexesToUpdate: [...acc.flatIndexesToUpdate, newIndex],
};
},
{
flatIndexesToDelete: [],
flatIndexesToUpdate: [],
},
);
return {
flatFieldMetadatasToDelete,
flatIndexesToDelete,
flatIndexesToUpdate,
};
};
@@ -5,13 +5,13 @@ import { v4 } from 'uuid';
import { type CreateFieldInput } from 'src/engine/metadata-modules/field-metadata/dtos/create-field.input';
import { FieldMetadataExceptionCode } from 'src/engine/metadata-modules/field-metadata/field-metadata.exception';
import { type MorphOrRelationFieldMetadataType } from 'src/engine/metadata-modules/field-metadata/types/morph-or-relation-field-metadata-type.type';
import { computeMorphOrRelationFieldJoinColumnName } from 'src/engine/metadata-modules/field-metadata/utils/compute-morph-or-relation-field-join-column-name.util';
import { computeMorphRelationFieldName } from 'src/engine/metadata-modules/field-metadata/utils/compute-morph-relation-field-name.util';
import { type FieldInputTranspilationResult } from 'src/engine/metadata-modules/flat-field-metadata/types/field-input-transpilation-result.type';
import { type FlatFieldMetadata } from 'src/engine/metadata-modules/flat-field-metadata/types/flat-field-metadata.type';
import { generateMorphOrRelationFlatFieldMetadataPair } from 'src/engine/metadata-modules/flat-field-metadata/utils/generate-morph-or-relation-flat-field-metadata-pair.util';
import { validateMorphRelationCreationPayload } from 'src/engine/metadata-modules/flat-field-metadata/validators/utils/validate-morph-relation-creation-payload.util';
import { type FlatIndexMetadata } from 'src/engine/metadata-modules/flat-index-metadata/types/flat-index-metadata.type';
import { type FlatObjectMetadataMaps } from 'src/engine/metadata-modules/flat-object-metadata-maps/types/flat-object-metadata-maps.type';
import { type FlatObjectMetadata } from 'src/engine/metadata-modules/flat-object-metadata/types/flat-object-metadata.type';
@@ -29,9 +29,10 @@ export const fromMorphRelationCreateFieldInputToFlatFieldMetadatas = async ({
sourceFlatObjectMetadata,
workspaceId,
}: FromMorphRelationCreateFieldInputToFlatFieldMetadatasArgs): Promise<
FieldInputTranspilationResult<
FlatFieldMetadata<MorphOrRelationFieldMetadataType>[]
>
FieldInputTranspilationResult<{
flatFieldMetadatas: FlatFieldMetadata[];
indexMetadatas: FlatIndexMetadata[];
}>
> => {
const rawMorphCreationPayload =
createFieldInput.morphRelationsCreationPayload;
@@ -65,8 +66,8 @@ export const fromMorphRelationCreateFieldInputToFlatFieldMetadatas = async ({
const morphRelationCreationPayload =
morphRelationCreationPayloadValidation.result;
const morphId = v4();
const flatFieldMetadatas = morphRelationCreationPayload.flatMap(
({ relationCreationPayload, targetFlatObjectMetadata }) => {
const flatFieldsAndIndexes = morphRelationCreationPayload.reduce(
(acc, { relationCreationPayload, targetFlatObjectMetadata }) => {
const currentMorphRelationFieldName = computeMorphRelationFieldName({
fieldName: createFieldInput.name,
relationType: relationCreationPayload.type,
@@ -77,23 +78,33 @@ export const fromMorphRelationCreateFieldInputToFlatFieldMetadatas = async ({
name: currentMorphRelationFieldName,
});
return generateMorphOrRelationFlatFieldMetadataPair({
createFieldInput: {
...createFieldInput,
relationCreationPayload,
name: currentMorphRelationFieldName,
},
sourceFlatObjectMetadataJoinColumnName,
sourceFlatObjectMetadata,
targetFlatObjectMetadata,
workspaceId,
morphId,
});
const { flatFieldMetadatas, indexMetadatas } =
generateMorphOrRelationFlatFieldMetadataPair({
createFieldInput: {
...createFieldInput,
relationCreationPayload,
name: currentMorphRelationFieldName,
},
sourceFlatObjectMetadataJoinColumnName,
sourceFlatObjectMetadata,
targetFlatObjectMetadata,
workspaceId,
morphId,
});
return {
indexMetadatas: [...acc.indexMetadatas, ...indexMetadatas],
flatFieldMetadatas: [...acc.flatFieldMetadatas, ...flatFieldMetadatas],
};
},
{
indexMetadatas: [],
flatFieldMetadatas: [],
},
);
return {
status: 'success',
result: flatFieldMetadatas,
result: flatFieldsAndIndexes,
};
};
@@ -6,8 +6,10 @@ import { type CreateFieldInput } from 'src/engine/metadata-modules/field-metadat
import { FieldMetadataExceptionCode } from 'src/engine/metadata-modules/field-metadata/field-metadata.exception';
import { computeMorphOrRelationFieldJoinColumnName } from 'src/engine/metadata-modules/field-metadata/utils/compute-morph-or-relation-field-join-column-name.util';
import { type FieldInputTranspilationResult } from 'src/engine/metadata-modules/flat-field-metadata/types/field-input-transpilation-result.type';
import { type FlatFieldMetadata } from 'src/engine/metadata-modules/flat-field-metadata/types/flat-field-metadata.type';
import { generateMorphOrRelationFlatFieldMetadataPair } from 'src/engine/metadata-modules/flat-field-metadata/utils/generate-morph-or-relation-flat-field-metadata-pair.util';
import {
generateMorphOrRelationFlatFieldMetadataPair,
type SourceTargetMorphOrRelationFlatFieldAndFlatIndex,
} from 'src/engine/metadata-modules/flat-field-metadata/utils/generate-morph-or-relation-flat-field-metadata-pair.util';
import { validateRelationCreationPayload } from 'src/engine/metadata-modules/flat-field-metadata/validators/utils/validate-relation-creation-payload.util';
import { type FlatObjectMetadataMaps } from 'src/engine/metadata-modules/flat-object-metadata-maps/types/flat-object-metadata-maps.type';
import { type FlatObjectMetadata } from 'src/engine/metadata-modules/flat-object-metadata/types/flat-object-metadata.type';
@@ -26,7 +28,7 @@ export const fromRelationCreateFieldInputToFlatFieldMetadatas = async ({
createFieldInput,
workspaceId,
}: FromRelationCreateFieldInputToFlatFieldMetadataArgs): Promise<
FieldInputTranspilationResult<FlatFieldMetadata<FieldMetadataType.RELATION>[]>
FieldInputTranspilationResult<SourceTargetMorphOrRelationFlatFieldAndFlatIndex>
> => {
const rawCreationPayload = createFieldInput.relationCreationPayload;
@@ -53,7 +55,7 @@ export const fromRelationCreateFieldInputToFlatFieldMetadatas = async ({
const { relationCreationPayload, targetFlatObjectMetadata } =
relationValidationResult.result;
const flatFieldMetadatas = generateMorphOrRelationFlatFieldMetadataPair({
const generateResult = generateMorphOrRelationFlatFieldMetadataPair({
createFieldInput: {
...createFieldInput,
relationCreationPayload,
@@ -69,7 +71,6 @@ export const fromRelationCreateFieldInputToFlatFieldMetadatas = async ({
return {
status: 'success',
result:
flatFieldMetadatas as FlatFieldMetadata<FieldMetadataType.RELATION>[],
result: generateResult,
};
};
@@ -5,71 +5,120 @@ import {
} from 'twenty-shared/utils';
import { v4 } from 'uuid';
import { type AllFlatEntityMaps } from 'src/engine/core-modules/common/types/all-flat-entity-maps.type';
import { FIELD_METADATA_STANDARD_OVERRIDES_PROPERTIES } from 'src/engine/metadata-modules/field-metadata/constants/field-metadata-standard-overrides-properties.constant';
import { type UpdateFieldInput } from 'src/engine/metadata-modules/field-metadata/dtos/update-field.input';
import { FieldMetadataExceptionCode } from 'src/engine/metadata-modules/field-metadata/field-metadata.exception';
import { type FieldMetadataStandardOverridesProperties } from 'src/engine/metadata-modules/field-metadata/types/field-metadata-standard-overrides-properties.type';
import { FLAT_FIELD_METADATA_PROPERTIES_TO_COMPARE } from 'src/engine/metadata-modules/flat-field-metadata/constants/flat-field-metadata-properties-to-compare.constant';
import { FLAT_FIELD_METADATA_EDITABLE_PROPERTIES } from 'src/engine/metadata-modules/flat-field-metadata/constants/flat-field-metadata-editable-properties.constant';
import { type FieldInputTranspilationResult } from 'src/engine/metadata-modules/flat-field-metadata/types/field-input-transpilation-result.type';
import { type FlatFieldMetadataPropertiesToCompare } from 'src/engine/metadata-modules/flat-field-metadata/types/flat-field-metadata-properties-to-compare.type';
import { type FlatFieldMetadataEditableProperties } from 'src/engine/metadata-modules/flat-field-metadata/types/flat-field-metadata-editable-properties.constant';
import { type FlatFieldMetadata } from 'src/engine/metadata-modules/flat-field-metadata/types/flat-field-metadata.type';
import { computeFlatFieldMetadataRelatedFlatFieldMetadata } from 'src/engine/metadata-modules/flat-field-metadata/utils/compute-flat-field-metadata-related-flat-field-metadata.util';
import { type FlatObjectMetadataMaps } from 'src/engine/metadata-modules/flat-object-metadata-maps/types/flat-object-metadata-maps.type';
import { recomputeIndexOnFlatFieldMetadataNameUpdate } from 'src/engine/metadata-modules/flat-field-metadata/utils/recompute-index-on-flat-field-metadata-name-update.util';
import { type FlatIndexMetadata } from 'src/engine/metadata-modules/flat-index-metadata/types/flat-index-metadata.type';
import { findFlatFieldMetadataInFlatObjectMetadataMapsWithOnlyFieldId } from 'src/engine/metadata-modules/flat-object-metadata-maps/utils/find-flat-field-metadata-in-flat-object-metadata-maps-with-field-id-only.util';
import { findFlatObjectMetadataInFlatObjectMetadataMapsOrThrow } from 'src/engine/metadata-modules/flat-object-metadata-maps/utils/find-flat-object-metadata-in-flat-object-metadata-maps-or-throw.util';
import { fromFlatObjectMetadataWithFlatFieldMapsToFlatObjectMetadata } from 'src/engine/metadata-modules/flat-object-metadata/utils/from-flat-object-metadata-with-flat-field-maps-to-flat-object-metadatas.util';
import { isStandardMetadata } from 'src/engine/metadata-modules/utils/is-standard-metadata.util';
const applyUpdatesToFlatFieldMetadata = ({
updatedEditableFieldProperties,
flatFieldMetadata,
}: {
updatedEditableFieldProperties: SanitizedUpdateFieldInput;
type UpdatedFlatFieldMetadataAndIndexToUpdate = {
flatFieldMetadata: FlatFieldMetadata;
}) => {
return fieldMetadataEditableProperties.reduce((acc, property) => {
let newValue = updatedEditableFieldProperties[property];
if (property === 'options' && isDefined(newValue)) {
newValue = updatedEditableFieldProperties[property]?.map((option) => ({
id: v4(),
...option,
}));
}
return {
...acc,
...(newValue !== undefined ? { [property]: newValue } : {}),
};
}, flatFieldMetadata);
flatIndexMetadataToUpdate: FlatIndexMetadata[];
};
const fieldMetadataEditableProperties =
FLAT_FIELD_METADATA_PROPERTIES_TO_COMPARE.filter(
(
property,
): property is Exclude<
FlatFieldMetadataPropertiesToCompare,
'standardOverrides'
> => property !== 'standardOverrides',
);
type SanitizedUpdateFieldInput = ReturnType<
typeof extractAndSanitizeObjectStringFields<
UpdateFieldInput,
(typeof fieldMetadataEditableProperties)[number][]
FlatFieldMetadataEditableProperties[]
>
>;
type ApplyUpdatesToFlatFieldMetadataArgs = {
updatedEditableFieldProperties: SanitizedUpdateFieldInput;
fromFlatFieldMetadata: FlatFieldMetadata;
} & Pick<AllFlatEntityMaps, 'flatIndexMaps' | 'flatObjectMetadataMaps'>;
const applyUpdatesToFlatFieldMetadata = ({
updatedEditableFieldProperties,
fromFlatFieldMetadata,
flatObjectMetadataMaps,
flatIndexMaps,
}: ApplyUpdatesToFlatFieldMetadataArgs) => {
return FLAT_FIELD_METADATA_EDITABLE_PROPERTIES.reduce<UpdatedFlatFieldMetadataAndIndexToUpdate>(
({ flatFieldMetadata, flatIndexMetadataToUpdate }, property) => {
const updatedPropertyValue = updatedEditableFieldProperties[property];
const isPropertyUpdated =
updatedPropertyValue !== undefined &&
flatFieldMetadata[property] !== updatedPropertyValue;
if (!isPropertyUpdated) {
return {
flatFieldMetadata,
flatIndexMetadataToUpdate,
};
}
const updatedFlatFieldMetadata = {
...flatFieldMetadata,
[property]: updatedPropertyValue,
};
if (property === 'options') {
updatedFlatFieldMetadata.options =
updatedEditableFieldProperties[property]?.map((option) => ({
id: v4(),
...option,
})) ?? [];
}
let newFlatIndexMetadataToUpdate: FlatIndexMetadata[] = [];
if (property === 'name') {
const flatObjectMetadata =
findFlatObjectMetadataInFlatObjectMetadataMapsOrThrow({
flatObjectMetadataMaps,
objectMetadataId: flatFieldMetadata.objectMetadataId,
});
newFlatIndexMetadataToUpdate =
recomputeIndexOnFlatFieldMetadataNameUpdate({
flatObjectMetadata,
fromFlatFieldMetadata,
toFlatFieldMetadata: {
name: updatedFlatFieldMetadata.name,
},
flatIndexMaps,
});
}
return {
flatFieldMetadata: updatedFlatFieldMetadata,
flatIndexMetadataToUpdate: [
...flatIndexMetadataToUpdate,
...newFlatIndexMetadataToUpdate,
],
};
},
{
flatFieldMetadata: structuredClone(fromFlatFieldMetadata),
flatIndexMetadataToUpdate: [],
},
);
};
type FromUpdateFieldInputToFlatFieldMetadataArgs = {
existingFlatObjectMetadataMaps: FlatObjectMetadataMaps;
updateFieldInput: UpdateFieldInput;
} & Pick<AllFlatEntityMaps, 'flatObjectMetadataMaps' | 'flatIndexMaps'>;
type FlatFieldMetadataAndIndexToUpdate = {
flatFieldMetadatasToUpdate: FlatFieldMetadata[];
flatIndexMetadatasToUpdate: FlatIndexMetadata[];
};
export const fromUpdateFieldInputToFlatFieldMetadata = ({
existingFlatObjectMetadataMaps,
flatIndexMaps,
flatObjectMetadataMaps: existingFlatObjectMetadataMaps,
updateFieldInput: rawUpdateFieldInput,
}: FromUpdateFieldInputToFlatFieldMetadataArgs): FieldInputTranspilationResult<
FlatFieldMetadata[]
> => {
}: FromUpdateFieldInputToFlatFieldMetadataArgs): FieldInputTranspilationResult<FlatFieldMetadataAndIndexToUpdate> => {
const updateFieldInputInformalProperties =
extractAndSanitizeObjectStringFields(rawUpdateFieldInput, [
'objectMetadataId',
@@ -77,7 +126,7 @@ export const fromUpdateFieldInputToFlatFieldMetadata = ({
]);
const updatedEditableFieldProperties = extractAndSanitizeObjectStringFields(
rawUpdateFieldInput,
fieldMetadataEditableProperties,
FLAT_FIELD_METADATA_EDITABLE_PROPERTIES,
);
const existingFlatFieldMetadataToUpdate =
@@ -168,7 +217,10 @@ export const fromUpdateFieldInputToFlatFieldMetadata = ({
return {
status: 'success',
result: [updatedStandardFlatFieldMetadata],
result: {
flatFieldMetadatasToUpdate: [updatedStandardFlatFieldMetadata],
flatIndexMetadatasToUpdate: [],
},
};
}
@@ -184,12 +236,32 @@ export const fromUpdateFieldInputToFlatFieldMetadata = ({
];
const optimisticiallyUpdatedFlatFieldMetadatas =
flatFieldMetadatasToUpdate.map((flatFieldMetadata) => {
return applyUpdatesToFlatFieldMetadata({
flatFieldMetadata,
updatedEditableFieldProperties,
});
});
flatFieldMetadatasToUpdate.reduce<FlatFieldMetadataAndIndexToUpdate>(
(acc, fromFlatFieldMetadata) => {
const { flatFieldMetadata, flatIndexMetadataToUpdate } =
applyUpdatesToFlatFieldMetadata({
flatObjectMetadataMaps: existingFlatObjectMetadataMaps,
fromFlatFieldMetadata,
flatIndexMaps,
updatedEditableFieldProperties,
});
return {
flatFieldMetadatasToUpdate: [
...acc.flatFieldMetadatasToUpdate,
flatFieldMetadata,
],
flatIndexMetadatasToUpdate: [
...acc.flatIndexMetadatasToUpdate,
...flatIndexMetadataToUpdate,
],
};
},
{
flatFieldMetadatasToUpdate: [],
flatIndexMetadatasToUpdate: [],
},
);
return {
status: 'success',
@@ -6,7 +6,10 @@ import { type MorphOrRelationFieldMetadataType } from 'src/engine/metadata-modul
import { computeMorphOrRelationFieldJoinColumnName } from 'src/engine/metadata-modules/field-metadata/utils/compute-morph-or-relation-field-join-column-name.util';
import { type FlatFieldMetadata } from 'src/engine/metadata-modules/flat-field-metadata/types/flat-field-metadata.type';
import { getDefaultFlatFieldMetadata } from 'src/engine/metadata-modules/flat-field-metadata/utils/get-default-flat-field-metadata-from-create-field-input.util';
import { type FlatIndexMetadata } from 'src/engine/metadata-modules/flat-index-metadata/types/flat-index-metadata.type';
import { type FlatObjectMetadata } from 'src/engine/metadata-modules/flat-object-metadata/types/flat-object-metadata.type';
import { IndexType } from 'src/engine/metadata-modules/index-metadata/types/indexType.types';
import { generateFlatIndexMetadataWithNameOrThrow } from 'src/engine/metadata-modules/index-metadata/utils/generate-flat-index.util';
import { RelationOnDeleteAction } from 'src/engine/metadata-modules/relation-metadata/relation-on-delete-action.type';
import { computeMetadataNameFromLabel } from 'src/engine/metadata-modules/utils/validate-name-and-label-are-sync-or-throw.util';
@@ -42,6 +45,12 @@ type GenerateMorphOrRelationFlatFieldMetadataPairArgs = {
workspaceId: string;
morphId?: string | null;
};
export type SourceTargetMorphOrRelationFlatFieldAndFlatIndex = {
flatFieldMetadatas: FlatFieldMetadata[];
indexMetadatas: FlatIndexMetadata[];
};
export const generateMorphOrRelationFlatFieldMetadataPair = ({
createFieldInput,
sourceFlatObjectMetadata,
@@ -49,7 +58,7 @@ export const generateMorphOrRelationFlatFieldMetadataPair = ({
workspaceId,
sourceFlatObjectMetadataJoinColumnName,
morphId = null,
}: GenerateMorphOrRelationFlatFieldMetadataPairArgs): FlatFieldMetadata<MorphOrRelationFieldMetadataType>[] => {
}: GenerateMorphOrRelationFlatFieldMetadataPairArgs): SourceTargetMorphOrRelationFlatFieldAndFlatIndex => {
const { relationCreationPayload } = createFieldInput;
const sourceFlatFieldMetadataSettings =
@@ -117,11 +126,64 @@ export const generateMorphOrRelationFlatFieldMetadataPair = ({
flatRelationTargetObjectMetadata: sourceFlatObjectMetadata,
};
return [
{
...sourceFlatFieldMetadata,
flatRelationTargetFieldMetadata: targetFlatFieldMetadata,
},
targetFlatFieldMetadata,
] satisfies FlatFieldMetadata<MorphOrRelationFieldMetadataType>[];
const indexId = v4();
const createdAt = new Date();
const indexMetadata: FlatIndexMetadata =
generateFlatIndexMetadataWithNameOrThrow({
flatIndex: {
createdAt,
flatIndexFieldMetadatas: [
{
createdAt,
fieldMetadataId:
relationCreationPayload.type === RelationType.MANY_TO_ONE
? sourceFlatFieldMetadata.id
: targetFlatFieldMetadata.id,
id: v4(),
indexMetadataId: indexId,
order: 0,
updatedAt: createdAt,
},
],
id: indexId,
indexType: IndexType.BTREE,
indexWhereClause: null,
isCustom: true,
isUnique: false,
objectMetadataId:
relationCreationPayload.type === RelationType.MANY_TO_ONE
? sourceFlatObjectMetadata.id
: targetFlatObjectMetadata.id,
universalIdentifier: indexId,
updatedAt: createdAt,
workspaceId,
},
flatObjectMetadata: (relationCreationPayload.type ===
RelationType.MANY_TO_ONE
? {
...sourceFlatObjectMetadata,
flatFieldMetadatas: [
...sourceFlatObjectMetadata.flatFieldMetadatas,
sourceFlatFieldMetadata,
],
}
: {
...targetFlatObjectMetadata,
flatFieldMetadatas: [
...targetFlatObjectMetadata.flatFieldMetadatas,
targetFlatFieldMetadata,
],
}) as FlatObjectMetadata,
});
return {
flatFieldMetadatas: [
{
...sourceFlatFieldMetadata,
flatRelationTargetFieldMetadata: targetFlatFieldMetadata,
},
targetFlatFieldMetadata,
],
indexMetadatas: [indexMetadata],
};
};
@@ -0,0 +1,60 @@
import { isDefined } from 'twenty-shared/utils';
import { type AllFlatEntityMaps } from 'src/engine/core-modules/common/types/all-flat-entity-maps.type';
import { type FlatFieldMetadata } from 'src/engine/metadata-modules/flat-field-metadata/types/flat-field-metadata.type';
import { type FlatIndexMetadata } from 'src/engine/metadata-modules/flat-index-metadata/types/flat-index-metadata.type';
import { type FlatObjectMetadata } from 'src/engine/metadata-modules/flat-object-metadata/types/flat-object-metadata.type';
import { generateFlatIndexMetadataWithNameOrThrow } from 'src/engine/metadata-modules/index-metadata/utils/generate-flat-index.util';
type RecomputeIndexOnFlatFieldMetadataNameUpdateArgs = {
flatObjectMetadata: FlatObjectMetadata;
fromFlatFieldMetadata: FlatFieldMetadata;
toFlatFieldMetadata: Pick<FlatFieldMetadata, 'name'>;
} & Pick<AllFlatEntityMaps, 'flatIndexMaps'>;
export const recomputeIndexOnFlatFieldMetadataNameUpdate = ({
fromFlatFieldMetadata,
toFlatFieldMetadata,
flatObjectMetadata,
flatIndexMaps,
}: RecomputeIndexOnFlatFieldMetadataNameUpdateArgs): FlatIndexMetadata[] => {
const relatedFlatIndexMetadata = Object.values(flatIndexMaps.byId).filter(
(flatIndexMetadata): flatIndexMetadata is FlatIndexMetadata =>
isDefined(flatIndexMetadata) &&
flatIndexMetadata.objectMetadataId ===
fromFlatFieldMetadata.objectMetadataId &&
flatIndexMetadata.flatIndexFieldMetadatas.some(
(flatIndexField) =>
flatIndexField.fieldMetadataId === fromFlatFieldMetadata.id,
),
);
if (relatedFlatIndexMetadata.length === 0) {
return [];
}
const optimisticFlatObjectMetadata = {
...flatObjectMetadata,
flatFieldMetadatas: flatObjectMetadata.flatFieldMetadatas.map(
(flatFieldMetadata) => {
if (flatFieldMetadata.id === fromFlatFieldMetadata.id) {
return {
...flatFieldMetadata,
name: toFlatFieldMetadata.name,
};
}
return flatFieldMetadata;
},
),
};
return relatedFlatIndexMetadata.map<FlatIndexMetadata>((flatIndex) => {
const newIndex = generateFlatIndexMetadataWithNameOrThrow({
flatObjectMetadata: optimisticFlatObjectMetadata,
flatIndex,
});
return newIndex;
});
};
@@ -9,16 +9,18 @@ export type IndexMetadataRelationProperties =
MetadataEntitiesRelationTarget
>;
export type FlatIndexFieldMetadata = Omit<
IndexFieldMetadataEntity,
ExtractRecordTypeOrmRelationProperties<
IndexFieldMetadataEntity,
MetadataEntitiesRelationTarget
>
>;
export type FlatIndexMetadata = Omit<
IndexMetadataEntity,
IndexMetadataRelationProperties
> & {
universalIdentifier: string;
flatIndexFieldMetadatas: Omit<
IndexFieldMetadataEntity,
ExtractRecordTypeOrmRelationProperties<
IndexFieldMetadataEntity,
MetadataEntitiesRelationTarget
>
>[];
flatIndexFieldMetadatas: FlatIndexFieldMetadata[];
};
@@ -24,6 +24,5 @@ export const ATTACHMENT_FLAT_OBJECT_MOCK = getFlatObjectMetadataMock({
isLabelSyncedWithName: false,
workspaceId: '20202020-1c25-4d02-bf25-6aeccf7ea419',
universalIdentifier: '20202020-bd3d-4c60-8dca-571c71d4447a',
flatIndexMetadatas: [],
flatFieldMetadatas: Object.values(ATTACHMENT_FLAT_FIELDS_MOCK),
});
@@ -24,6 +24,5 @@ export const COMPANY_FLAT_OBJECT_MOCK = getFlatObjectMetadataMock({
isLabelSyncedWithName: false,
workspaceId: '20202020-1c25-4d02-bf25-6aeccf7ea419',
universalIdentifier: '20202020-b374-4779-a561-80086cb2e17f',
flatIndexMetadatas: [],
flatFieldMetadatas: Object.values(COMPANY_FLAT_FIELDS_MOCK),
});
@@ -24,6 +24,5 @@ export const FAVORITE_FLAT_OBJECT_MOCK = getFlatObjectMetadataMock({
isLabelSyncedWithName: false,
workspaceId: '20202020-1c25-4d02-bf25-6aeccf7ea419',
universalIdentifier: '20202020-ab56-4e05-92a3-e2414a499860',
flatIndexMetadatas: [],
flatFieldMetadatas: Object.values(FAVORITE_FLAT_FIELDS_MOCK),
});
@@ -24,6 +24,5 @@ export const FAVORITE_FOLDER_FLAT_OBJECT_MOCK = getFlatObjectMetadataMock({
isLabelSyncedWithName: false,
workspaceId: '20202020-1c25-4d02-bf25-6aeccf7ea419',
universalIdentifier: '20202020-7cf8-401f-8211-a9587d27fd2d',
flatIndexMetadatas: [],
flatFieldMetadatas: Object.values(FAVORITE_FOLDER_FLAT_FIELDS_MOCK),
});
@@ -13,7 +13,7 @@ export const getFlatObjectMetadataMock = (
return {
flatFieldMetadatas: [],
flatIndexMetadatas: [],
description: 'default flat object metadata description',
icon: 'icon',
id: faker.string.uuid(),
@@ -24,6 +24,6 @@ export const NOTE_FLAT_OBJECT_MOCK = getFlatObjectMetadataMock({
isLabelSyncedWithName: false,
workspaceId: '20202020-1c25-4d02-bf25-6aeccf7ea419',
universalIdentifier: '20202020-0b00-45cd-b6f6-6cd806fc6804',
flatIndexMetadatas: [],
flatFieldMetadatas: Object.values(NOTE_FLAT_FIELDS_MOCK),
});
@@ -24,6 +24,6 @@ export const NOTE_TARGET_FLAT_OBJECT_MOCK = getFlatObjectMetadataMock({
isLabelSyncedWithName: false,
workspaceId: '20202020-1c25-4d02-bf25-6aeccf7ea419',
universalIdentifier: '20202020-fff0-4b44-be82-bda313884400',
flatIndexMetadatas: [],
flatFieldMetadatas: Object.values(NOTETARGET_FLAT_FIELDS_MOCK),
});
@@ -24,6 +24,5 @@ export const OPPORTUNITY_FLAT_OBJECT_MOCK = getFlatObjectMetadataMock({
isLabelSyncedWithName: false,
workspaceId: '20202020-1c25-4d02-bf25-6aeccf7ea419',
universalIdentifier: '20202020-9549-49dd-b2b2-883999db8938',
flatIndexMetadatas: [],
flatFieldMetadatas: Object.values(OPPORTUNITY_FLAT_FIELDS_MOCK),
});
@@ -24,6 +24,6 @@ export const PERSON_FLAT_OBJECT_MOCK = getFlatObjectMetadataMock({
isLabelSyncedWithName: false,
workspaceId: '20202020-1c25-4d02-bf25-6aeccf7ea419',
universalIdentifier: '20202020-e674-48e5-a542-72570eee7213',
flatIndexMetadatas: [],
flatFieldMetadatas: Object.values(PERSON_FLAT_FIELDS_MOCK),
});
@@ -24,6 +24,6 @@ export const PET_FLAT_OBJECT_MOCK = getFlatObjectMetadataMock({
isLabelSyncedWithName: false,
workspaceId: '20202020-1c25-4d02-bf25-6aeccf7ea419',
universalIdentifier: 'd34e0f07-1b8c-4de0-938e-599cf05e1f7f',
flatIndexMetadatas: [],
flatFieldMetadatas: Object.values(PET_FLAT_FIELDS_MOCK),
});
@@ -24,6 +24,6 @@ export const ROCKET_FLAT_OBJECT_MOCK = getFlatObjectMetadataMock({
isLabelSyncedWithName: false,
workspaceId: '20202020-1c25-4d02-bf25-6aeccf7ea419',
universalIdentifier: 'd78ec657-74a4-4652-a350-1f44ff62970a',
flatIndexMetadatas: [],
flatFieldMetadatas: Object.values(ROCKET_FLAT_FIELDS_MOCK),
});
@@ -24,6 +24,6 @@ export const TASK_FLAT_OBJECT_MOCK = getFlatObjectMetadataMock({
isLabelSyncedWithName: false,
workspaceId: '20202020-1c25-4d02-bf25-6aeccf7ea419',
universalIdentifier: '20202020-1ba1-48ba-bc83-ef7e5990ed10',
flatIndexMetadatas: [],
flatFieldMetadatas: Object.values(TASK_FLAT_FIELDS_MOCK),
});
@@ -24,6 +24,6 @@ export const TASK_TARGET_FLAT_OBJECT_MOCK = getFlatObjectMetadataMock({
isLabelSyncedWithName: false,
workspaceId: '20202020-1c25-4d02-bf25-6aeccf7ea419',
universalIdentifier: '20202020-5a9a-44e8-95df-771cd06d0fb1',
flatIndexMetadatas: [],
flatFieldMetadatas: Object.values(TASKTARGET_FLAT_FIELDS_MOCK),
});
@@ -24,6 +24,5 @@ export const TIMELINE_ACTIVITY_FLAT_OBJECT_MOCK = getFlatObjectMetadataMock({
isLabelSyncedWithName: false,
workspaceId: '20202020-1c25-4d02-bf25-6aeccf7ea419',
universalIdentifier: '20202020-6736-4337-b5c4-8b39fae325a5',
flatIndexMetadatas: [],
flatFieldMetadatas: Object.values(TIMELINEACTIVITY_FLAT_FIELDS_MOCK),
});
@@ -1,5 +1,4 @@
import { type FlatFieldMetadata } from 'src/engine/metadata-modules/flat-field-metadata/types/flat-field-metadata.type';
import { type FlatIndexMetadata } from 'src/engine/metadata-modules/flat-index-metadata/types/flat-index-metadata.type';
import { type ObjectMetadataEntity } from 'src/engine/metadata-modules/object-metadata/object-metadata.entity';
import { type ExtractRecordTypeOrmRelationProperties } from 'src/engine/workspace-manager/workspace-migration-v2/types/extract-record-typeorm-relation-properties.type';
import { type MetadataEntitiesRelationTarget } from 'src/engine/workspace-manager/workspace-migration-v2/types/metadata-entities-relation-targets.type';
@@ -23,7 +22,6 @@ export type FlatObjectMetadata = Omit<
ObjectMetadataRelationProperties | 'dataSourceId'
> & {
universalIdentifier: string;
flatIndexMetadatas: FlatIndexMetadata[];
flatFieldMetadatas: FlatFieldMetadata[];
};
@@ -5,10 +5,12 @@ import {
import { v4 } from 'uuid';
import { type FlatFieldMetadata } from 'src/engine/metadata-modules/flat-field-metadata/types/flat-field-metadata.type';
import { type FlatIndexMetadata } from 'src/engine/metadata-modules/flat-index-metadata/types/flat-index-metadata.type';
import { type FlatObjectMetadataMaps } from 'src/engine/metadata-modules/flat-object-metadata-maps/types/flat-object-metadata-maps.type';
import { type FlatObjectMetadata } from 'src/engine/metadata-modules/flat-object-metadata/types/flat-object-metadata.type';
import { type CreateObjectInput } from 'src/engine/metadata-modules/object-metadata/dtos/create-object.input';
import { buildDefaultFlatFieldMetadatasForCustomObject } from 'src/engine/metadata-modules/object-metadata/utils/build-default-flat-field-metadatas-for-custom-object.util';
import { buildDefaultIndexesForCustomObject } from 'src/engine/metadata-modules/object-metadata/utils/build-default-index-for-custom-object.util';
import { buildDefaultRelationFlatFieldMetadatasForCustomObject } from 'src/engine/metadata-modules/object-metadata/utils/build-default-relation-flat-field-metadatas-for-custom-object.util';
type FromCreateObjectInputToFlatObjectMetadataAndFlatFieldMetadatasToCreateArgs =
@@ -24,7 +26,8 @@ export const fromCreateObjectInputToFlatObjectMetadataAndFlatFieldMetadatasToCre
existingFlatObjectMetadataMaps,
}: FromCreateObjectInputToFlatObjectMetadataAndFlatFieldMetadatasToCreateArgs): {
flatObjectMetadataToCreate: FlatObjectMetadata;
relationTargetFlatFieldMetadatas: FlatFieldMetadata[];
relationTargetFlatFieldMetadataToCreate: FlatFieldMetadata[];
flatIndexMetadataToCreate: FlatIndexMetadata[];
} => {
const createObjectInput =
trimAndRemoveDuplicatedWhitespacesFromObjectStringProperties(
@@ -41,9 +44,11 @@ export const fromCreateObjectInputToFlatObjectMetadataAndFlatFieldMetadatasToCre
);
const objectMetadataId = v4();
const baseCustomFlatFieldMetadatas =
const defaultFlatFieldForCustomObjectMaps =
buildDefaultFlatFieldMetadatasForCustomObject({
objectMetadataId,
flatObjectMetadata: {
id: objectMetadataId,
},
workspaceId,
});
const createdAt = new Date();
@@ -53,7 +58,6 @@ export const fromCreateObjectInputToFlatObjectMetadataAndFlatFieldMetadatasToCre
updatedAt: createdAt,
duplicateCriteria: null,
description: createObjectInput.description ?? null,
flatIndexMetadatas: [],
icon: createObjectInput.icon ?? null,
id: objectMetadataId,
imageIdentifierFieldMetadataId: null,
@@ -65,7 +69,8 @@ export const fromCreateObjectInputToFlatObjectMetadataAndFlatFieldMetadatasToCre
isSearchable: true,
isUIReadOnly: false,
isSystem: false,
labelIdentifierFieldMetadataId: baseCustomFlatFieldMetadatas.nameField.id,
labelIdentifierFieldMetadataId:
defaultFlatFieldForCustomObjectMaps.fields.nameField.id,
labelPlural: capitalize(createObjectInput.labelPlural),
labelSingular: capitalize(createObjectInput.labelSingular),
namePlural: createObjectInput.namePlural,
@@ -77,6 +82,7 @@ export const fromCreateObjectInputToFlatObjectMetadataAndFlatFieldMetadatasToCre
targetTableName: 'DEPRECATED',
workspaceId,
};
const {
standardSourceFlatFieldMetadatas,
standardTargetFlatFieldMetadatas,
@@ -87,12 +93,21 @@ export const fromCreateObjectInputToFlatObjectMetadataAndFlatFieldMetadatasToCre
});
flatObjectMetadataToCreate.flatFieldMetadatas = [
...Object.values(baseCustomFlatFieldMetadatas),
...Object.values(defaultFlatFieldForCustomObjectMaps.fields),
...standardSourceFlatFieldMetadatas,
];
const defaultIndexesForCustomObject = buildDefaultIndexesForCustomObject({
defaultFlatFieldForCustomObjectMaps,
flatObjectMetadata: flatObjectMetadataToCreate,
workspaceId,
});
return {
flatObjectMetadataToCreate,
relationTargetFlatFieldMetadatas: standardTargetFlatFieldMetadatas,
relationTargetFlatFieldMetadataToCreate: standardTargetFlatFieldMetadatas,
flatIndexMetadataToCreate: Object.values(
defaultIndexesForCustomObject.indexes,
),
};
};
@@ -3,9 +3,11 @@ import {
trimAndRemoveDuplicatedWhitespacesFromObjectStringProperties,
} from 'twenty-shared/utils';
import { type FlatEntityMaps } from 'src/engine/core-modules/common/types/flat-entity-maps.type';
import { type FlatFieldMetadata } from 'src/engine/metadata-modules/flat-field-metadata/types/flat-field-metadata.type';
import { findRelationFlatFieldMetadataTargetFlatFieldMetadataOrThrow } from 'src/engine/metadata-modules/flat-field-metadata/utils/find-relation-flat-field-metadatas-target-flat-field-metadata-or-throw.util';
import { isMorphOrRelationFlatFieldMetadata } from 'src/engine/metadata-modules/flat-field-metadata/utils/is-morph-or-relation-flat-field-metadata.util';
import { type FlatIndexMetadata } from 'src/engine/metadata-modules/flat-index-metadata/types/flat-index-metadata.type';
import { type FlatObjectMetadataMaps } from 'src/engine/metadata-modules/flat-object-metadata-maps/types/flat-object-metadata-maps.type';
import { findFlatObjectMetadataInFlatObjectMetadataMaps } from 'src/engine/metadata-modules/flat-object-metadata-maps/utils/find-flat-object-metadata-in-flat-object-metadata-maps.util';
import { type FlatObjectMetadata } from 'src/engine/metadata-modules/flat-object-metadata/types/flat-object-metadata.type';
@@ -15,15 +17,20 @@ import {
ObjectMetadataExceptionCode,
} from 'src/engine/metadata-modules/object-metadata/object-metadata.exception';
type FromDeleteObjectInputToFlatFieldMetadatasToDeleteArgs = {
deleteObjectInput: DeleteOneObjectInput;
existingFlatObjectMetadataMaps: FlatObjectMetadataMaps;
existingFlatIndexMaps: FlatEntityMaps<FlatIndexMetadata>;
};
export const fromDeleteObjectInputToFlatFieldMetadatasToDelete = ({
deleteObjectInput: rawDeleteObjectInput,
existingFlatObjectMetadataMaps,
}: {
deleteObjectInput: DeleteOneObjectInput;
existingFlatObjectMetadataMaps: FlatObjectMetadataMaps;
}): {
existingFlatIndexMaps,
// This should return an AllFlatEntityMaps
}: FromDeleteObjectInputToFlatFieldMetadatasToDeleteArgs): {
flatFieldMetadatasToDelete: FlatFieldMetadata[];
flatObjectMetadataToDelete: FlatObjectMetadata;
flatIndexToDelete: FlatIndexMetadata[];
} => {
const { id: objectMetadataToDeleteId } =
trimAndRemoveDuplicatedWhitespacesFromObjectStringProperties(
@@ -61,8 +68,18 @@ export const fromDeleteObjectInputToFlatFieldMetadatasToDelete = ({
},
);
// We should maintain a idsByObjectMetadataId maps in the index
const flatIndexMetadataToDelete = Object.values(
existingFlatIndexMaps.byId,
).filter(
(flatIndex): flatIndex is FlatIndexMetadata =>
isDefined(flatIndex) &&
flatIndex.objectMetadataId === flatObjectMetadataToDelete.id,
);
return {
flatFieldMetadatasToDelete,
flatObjectMetadataToDelete,
flatIndexToDelete: flatIndexMetadataToDelete,
};
};
@@ -17,7 +17,6 @@ export const fromObjectMetadataEntityToFlatObjectMetadata = (
return {
...objectMetadataEntityWithoutRelations,
flatIndexMetadatas: [], // TODO prastoin handle indexes
universalIdentifier:
objectMetadataEntityWithoutRelations.standardId ??
objectMetadataEntityWithoutRelations.id,
@@ -35,6 +35,5 @@ export const fromObjectMetadataItemWithFieldMapsToFlatObjectMetadata = ({
...rest,
flatFieldMetadatas,
universalIdentifier: rest.standardId ?? rest.id,
flatIndexMetadatas: [], // prastoin TODO convert from indexMetadatas to flatIndexMetadatas
};
};
@@ -4,12 +4,15 @@ import {
trimAndRemoveDuplicatedWhitespacesFromObjectStringProperties,
} from 'twenty-shared/utils';
import { type AllFlatEntityMaps } from 'src/engine/core-modules/common/types/all-flat-entity-maps.type';
import { type FlatFieldMetadata } from 'src/engine/metadata-modules/flat-field-metadata/types/flat-field-metadata.type';
import { type FlatIndexMetadata } from 'src/engine/metadata-modules/flat-index-metadata/types/flat-index-metadata.type';
import { type FlatObjectMetadataMaps } from 'src/engine/metadata-modules/flat-object-metadata-maps/types/flat-object-metadata-maps.type';
import { findFlatObjectMetadataInFlatObjectMetadataMaps } from 'src/engine/metadata-modules/flat-object-metadata-maps/utils/find-flat-object-metadata-in-flat-object-metadata-maps.util';
import { FLAT_OBJECT_METADATA_PROPERTIES_TO_COMPARE } from 'src/engine/metadata-modules/flat-object-metadata/constants/flat-object-metadata-properties-to-compare.constant';
import { type FlatObjectMetadataPropertiesToCompare } from 'src/engine/metadata-modules/flat-object-metadata/types/flat-object-metadata-properties-to-compare.type';
import { type FlatObjectMetadata } from 'src/engine/metadata-modules/flat-object-metadata/types/flat-object-metadata.type';
import { recomputeIndexAfterFlatObjectMetadataSingularNameUpdate } from 'src/engine/metadata-modules/flat-object-metadata/utils/recompute-index-after-flat-object-metadata-singular-name-update.util';
import { renameRelatedMorphFieldOnObjectNamesUpdate } from 'src/engine/metadata-modules/flat-object-metadata/utils/rename-related-morph-field-on-object-names-update.util';
import { OBJECT_METADATA_STANDARD_OVERRIDES_PROPERTIES } from 'src/engine/metadata-modules/object-metadata/constants/object-metadata-standard-overrides-properties.constant';
import { type UpdateOneObjectInput } from 'src/engine/metadata-modules/object-metadata/dtos/update-object.input';
@@ -23,7 +26,7 @@ import { isStandardMetadata } from 'src/engine/metadata-modules/utils/is-standar
type FromUpdateObjectInputToFlatObjectMetadataArgs = {
existingFlatObjectMetadataMaps: FlatObjectMetadataMaps;
updateObjectInput: UpdateOneObjectInput;
};
} & Pick<AllFlatEntityMaps, 'flatIndexMaps'>;
const objectMetadataEditableProperties =
FLAT_OBJECT_METADATA_PROPERTIES_TO_COMPARE.filter(
@@ -37,12 +40,14 @@ const objectMetadataEditableProperties =
type UpdatedFlatObjectAndOtherObjectFieldMetadatas = {
flatObjectMetadata: FlatObjectMetadata;
otherObjectFlatFieldMetadatas: FlatFieldMetadata[];
otherObjectFlatFieldMetadataToUpdate: FlatFieldMetadata[];
flatIndexMetadataToUpdate: FlatIndexMetadata[];
};
export const fromUpdateObjectInputToFlatObjectMetadata = ({
existingFlatObjectMetadataMaps,
updateObjectInput: rawUpdateObjectInput,
flatIndexMaps,
}: FromUpdateObjectInputToFlatObjectMetadataArgs): UpdatedFlatObjectAndOtherObjectFieldMetadatas => {
const { id: objectMetadataIdToUpdate } =
trimAndRemoveDuplicatedWhitespacesFromObjectStringProperties(
@@ -102,17 +107,26 @@ export const fromUpdateObjectInputToFlatObjectMetadata = ({
return {
flatObjectMetadata: updatedStandardFlatObjectdMetadata,
otherObjectFlatFieldMetadatas: [],
otherObjectFlatFieldMetadataToUpdate: [],
flatIndexMetadataToUpdate: [],
};
}
const initialAccumulator: UpdatedFlatObjectAndOtherObjectFieldMetadatas = {
flatObjectMetadata: flatObjectMetadataToUpdate,
otherObjectFlatFieldMetadatas: [],
otherObjectFlatFieldMetadataToUpdate: [],
flatIndexMetadataToUpdate: [],
};
return objectMetadataEditableProperties.reduce<UpdatedFlatObjectAndOtherObjectFieldMetadatas>(
({ flatObjectMetadata, otherObjectFlatFieldMetadatas }, property) => {
(
{
flatObjectMetadata,
otherObjectFlatFieldMetadataToUpdate: otherObjectFlatFieldMetadatas,
flatIndexMetadataToUpdate,
},
property,
) => {
const updatedPropertyValue = updatedEditableObjectProperties[property];
const isPropertyUpdated =
updatedPropertyValue !== undefined &&
@@ -121,7 +135,8 @@ export const fromUpdateObjectInputToFlatObjectMetadata = ({
if (!isPropertyUpdated) {
return {
flatObjectMetadata,
otherObjectFlatFieldMetadatas,
otherObjectFlatFieldMetadataToUpdate: otherObjectFlatFieldMetadatas,
flatIndexMetadataToUpdate,
};
}
@@ -129,6 +144,7 @@ export const fromUpdateObjectInputToFlatObjectMetadata = ({
...flatObjectMetadata,
[property]: updatedPropertyValue,
};
const newUpdatedOtherObjectFlatFieldMetadatas =
property === 'nameSingular' || property === 'namePlural'
? renameRelatedMorphFieldOnObjectNamesUpdate({
@@ -138,12 +154,25 @@ export const fromUpdateObjectInputToFlatObjectMetadata = ({
})
: [];
const newUpdatedFlatIndexMetadatas =
property === 'nameSingular'
? recomputeIndexAfterFlatObjectMetadataSingularNameUpdate({
existingFlatObjectMetadata: flatObjectMetadataToUpdate,
flatIndexMaps,
updatedSingularName: updatedFlatObjectMetadata.nameSingular,
})
: [];
return {
flatObjectMetadata: updatedFlatObjectMetadata,
otherObjectFlatFieldMetadatas: [
otherObjectFlatFieldMetadataToUpdate: [
...otherObjectFlatFieldMetadatas,
...newUpdatedOtherObjectFlatFieldMetadatas,
],
flatIndexMetadataToUpdate: [
...flatIndexMetadataToUpdate,
...newUpdatedFlatIndexMetadatas,
],
};
},
initialAccumulator,
@@ -0,0 +1,40 @@
import { isDefined } from 'twenty-shared/utils';
import { type AllFlatEntityMaps } from 'src/engine/core-modules/common/types/all-flat-entity-maps.type';
import { type FlatIndexMetadata } from 'src/engine/metadata-modules/flat-index-metadata/types/flat-index-metadata.type';
import { type FlatObjectMetadata } from 'src/engine/metadata-modules/flat-object-metadata/types/flat-object-metadata.type';
import { generateFlatIndexMetadataWithNameOrThrow } from 'src/engine/metadata-modules/index-metadata/utils/generate-flat-index.util';
type RecomputeIndexAfterFlatObjectMetadataSingularNameUpdateArgs = {
existingFlatObjectMetadata: FlatObjectMetadata;
updatedSingularName: string;
} & Pick<AllFlatEntityMaps, 'flatIndexMaps'>;
export const recomputeIndexAfterFlatObjectMetadataSingularNameUpdate = ({
existingFlatObjectMetadata,
flatIndexMaps,
updatedSingularName,
}: RecomputeIndexAfterFlatObjectMetadataSingularNameUpdateArgs): FlatIndexMetadata[] => {
const allRelatedFlatIndexMetadata = Object.values(flatIndexMaps.byId).filter(
(flatIndexMetadata): flatIndexMetadata is FlatIndexMetadata =>
isDefined(flatIndexMetadata) &&
flatIndexMetadata.objectMetadataId === existingFlatObjectMetadata.id,
);
if (allRelatedFlatIndexMetadata.length === 0) {
return [];
}
const optimisticFlatObjectMetadata: FlatObjectMetadata = {
...existingFlatObjectMetadata,
nameSingular: updatedSingularName,
};
return allRelatedFlatIndexMetadata.map<FlatIndexMetadata>((flatIndex) => {
const newIndex = generateFlatIndexMetadataWithNameOrThrow({
flatIndex,
flatObjectMetadata: optimisticFlatObjectMetadata,
});
return newIndex;
});
};
@@ -37,6 +37,7 @@ type RenameRelatedMorphFieldOnObjectNamesUpdateArgs = FromTo<
> & {
existingFlatObjectMetadataMaps: FlatObjectMetadataMaps;
};
// We should recompute each index here too ? YES TODO prastoin
export const renameRelatedMorphFieldOnObjectNamesUpdate = ({
fromFlatObjectMetadata,
existingFlatObjectMetadataMaps,
@@ -0,0 +1,33 @@
import { createHash } from 'crypto';
import { type FlatFieldMetadata } from 'src/engine/metadata-modules/flat-field-metadata/types/flat-field-metadata.type';
import { type FlatObjectMetadata } from 'src/engine/metadata-modules/flat-object-metadata/types/flat-object-metadata.type';
import { computeTableName } from 'src/engine/utils/compute-table-name.util';
type GenerateDeterministicIndexNameArgs = {
flatObjectMetadata: Pick<FlatObjectMetadata, 'nameSingular' | 'isCustom'>;
isUnique?: boolean;
relatedFieldNames: Pick<FlatFieldMetadata, 'name'>[];
};
export const generateDeterministicIndexNameV2 = ({
relatedFieldNames,
flatObjectMetadata,
isUnique = false,
}: GenerateDeterministicIndexNameArgs): string => {
const hash = createHash('sha256');
const tableName = computeTableName(
flatObjectMetadata.nameSingular,
flatObjectMetadata.isCustom,
);
const columnsNames = relatedFieldNames.map(
(flatFieldMetadata) => flatFieldMetadata.name,
);
[tableName, ...columnsNames].forEach((column) => {
hash.update(column);
});
return `IDX_${isUnique ? 'UNIQUE_' : ''}${hash.digest('hex').slice(0, 27)}`;
};
@@ -0,0 +1,56 @@
import { isDefined } from 'twenty-shared/utils';
import {
FlatEntityMapsException,
FlatEntityMapsExceptionCode,
} from 'src/engine/core-modules/common/exceptions/flat-entity-maps.exception';
import { isMorphOrRelationFlatFieldMetadata } from 'src/engine/metadata-modules/flat-field-metadata/utils/is-morph-or-relation-flat-field-metadata.util';
import { type FlatIndexMetadata } from 'src/engine/metadata-modules/flat-index-metadata/types/flat-index-metadata.type';
import { type FlatObjectMetadata } from 'src/engine/metadata-modules/flat-object-metadata/types/flat-object-metadata.type';
import { generateDeterministicIndexNameV2 } from 'src/engine/metadata-modules/index-metadata/utils/generate-deterministic-index-name-v2';
type GenerateFlatIndexArgs = {
flatObjectMetadata: FlatObjectMetadata;
flatIndex: Omit<FlatIndexMetadata, 'name'>;
};
export const generateFlatIndexMetadataWithNameOrThrow = ({
flatObjectMetadata,
flatIndex,
}: GenerateFlatIndexArgs): FlatIndexMetadata => {
const orderedFlatFieldNames = flatIndex.flatIndexFieldMetadatas
.sort((a, b) => a.order - b.order)
.map((flatIndexField) => {
const relatedFlatFieldMetadata =
flatObjectMetadata.flatFieldMetadatas.find(
(flatFieldMetadata) =>
flatFieldMetadata.id === flatIndexField.fieldMetadataId,
);
if (!isDefined(relatedFlatFieldMetadata)) {
throw new FlatEntityMapsException(
'Could not find flat index field related field in cache',
FlatEntityMapsExceptionCode.ENTITY_NOT_FOUND,
);
}
const name = isMorphOrRelationFlatFieldMetadata(relatedFlatFieldMetadata)
? (relatedFlatFieldMetadata.settings.joinColumnName ??
relatedFlatFieldMetadata.name)
: relatedFlatFieldMetadata.name;
return {
name,
};
});
const name = generateDeterministicIndexNameV2({
flatObjectMetadata,
isUnique: flatIndex.isUnique,
relatedFieldNames: orderedFlatFieldNames,
});
return {
...flatIndex,
name,
};
};
@@ -4,6 +4,8 @@ import { isDefined } from 'twenty-shared/utils';
import { WorkspaceManyOrAllFlatEntityMapsCacheService } from 'src/engine/core-modules/common/services/workspace-many-or-all-flat-entity-maps-cache.service.';
import { addFlatEntityToFlatEntityMapsOrThrow } from 'src/engine/core-modules/common/utils/add-flat-entity-to-flat-entity-maps-or-throw.util';
import { deleteFlatEntityFromFlatEntityMapsOrThrow } from 'src/engine/core-modules/common/utils/delete-flat-entity-from-flat-entity-maps-or-throw.util';
import { replaceFlatEntityInFlatEntityMapsOrThrow } from 'src/engine/core-modules/common/utils/replace-flat-entity-in-flat-entity-maps-or-throw.util';
import { ViewKey } from 'src/engine/core-modules/view/enums/view-key.enum';
import { ViewType } from 'src/engine/core-modules/view/enums/view-type.enum';
import { FlatView } from 'src/engine/core-modules/view/flat-view/types/flat-view.type';
@@ -53,20 +55,25 @@ export class ObjectMetadataServiceV2 {
workspaceId: string;
updateObjectInput: UpdateOneObjectInput;
}): Promise<ObjectMetadataDTO> {
const { flatObjectMetadataMaps: existingFlatObjectMetadataMaps } =
const {
flatObjectMetadataMaps: existingFlatObjectMetadataMaps,
flatIndexMaps: existingFlatIndexMaps,
} =
await this.flatEntityMapsCacheService.getOrRecomputeManyOrAllFlatEntityMaps(
{
workspaceId,
flatEntities: ['flatObjectMetadataMaps'],
flatEntities: ['flatObjectMetadataMaps', 'flatIndexMaps'],
},
);
const {
flatObjectMetadata: optimisticallyUpdatedFlatObjectMetadata,
otherObjectFlatFieldMetadatas,
otherObjectFlatFieldMetadataToUpdate: otherObjectFlatFieldMetadatas,
flatIndexMetadataToUpdate,
} = fromUpdateObjectInputToFlatObjectMetadata({
existingFlatObjectMetadataMaps,
updateObjectInput,
flatIndexMaps: existingFlatIndexMaps,
});
const impactedObjectMetadataIds = [
@@ -93,6 +100,15 @@ export class ObjectMetadataServiceV2 {
}),
);
const toFlatIndexMaps = flatIndexMetadataToUpdate.reduce(
(flatIndexMaps, flatIndexMetadata) =>
replaceFlatEntityInFlatEntityMapsOrThrow({
flatEntity: flatIndexMetadata,
flatEntityMaps: flatIndexMaps,
}),
existingFlatIndexMaps,
);
const validateAndBuildResult =
await this.workspaceMigrationValidateBuildAndRunService.validateBuildAndRunWorkspaceMigration(
{
@@ -101,6 +117,10 @@ export class ObjectMetadataServiceV2 {
from: fromFlatObjectMetadataMaps,
to: toFlatObjectMetadataMaps,
},
flatIndexMaps: {
from: existingFlatIndexMaps,
to: toFlatIndexMaps,
},
},
buildOptions: {
isSystemBuild: false,
@@ -155,19 +175,26 @@ export class ObjectMetadataServiceV2 {
deleteObjectInput: DeleteOneObjectInput;
workspaceId: string;
}): Promise<ObjectMetadataDTO> {
const { flatObjectMetadataMaps: existingFlatObjectMetadataMaps } =
const {
flatObjectMetadataMaps: existingFlatObjectMetadataMaps,
flatIndexMaps: existingFlatIndexMaps,
} =
await this.flatEntityMapsCacheService.getOrRecomputeManyOrAllFlatEntityMaps(
{
workspaceId,
flatEntities: ['flatObjectMetadataMaps'],
flatEntities: ['flatObjectMetadataMaps', 'flatIndexMaps'],
},
);
const { flatFieldMetadatasToDelete, flatObjectMetadataToDelete } =
fromDeleteObjectInputToFlatFieldMetadatasToDelete({
deleteObjectInput,
existingFlatObjectMetadataMaps,
});
const {
flatFieldMetadatasToDelete,
flatObjectMetadataToDelete,
flatIndexToDelete,
} = fromDeleteObjectInputToFlatFieldMetadatasToDelete({
existingFlatIndexMaps,
deleteObjectInput,
existingFlatObjectMetadataMaps,
});
const { id: objectMetadataToDeleteId } = flatObjectMetadataToDelete;
const impactedObjectMetadataIds = Array.from(
@@ -202,6 +229,15 @@ export class ObjectMetadataServiceV2 {
}),
);
const toFlatIndexMaps = flatIndexToDelete.reduce(
(flatIndexMaps, flatIndex) =>
deleteFlatEntityFromFlatEntityMapsOrThrow({
entityToDeleteId: flatIndex.id,
flatEntityMaps: flatIndexMaps,
}),
existingFlatIndexMaps,
);
const validateAndBuildResult =
await this.workspaceMigrationValidateBuildAndRunService.validateBuildAndRunWorkspaceMigration(
{
@@ -210,6 +246,10 @@ export class ObjectMetadataServiceV2 {
from: fromFlatObjectMetadataMaps,
to: toFlatObjectMetadataMaps,
},
flatIndexMaps: {
from: existingFlatIndexMaps,
to: toFlatIndexMaps,
},
},
buildOptions: {
inferDeletionFromMissingEntities: true,
@@ -242,6 +282,7 @@ export class ObjectMetadataServiceV2 {
flatObjectMetadataMaps: existingFlatObjectMetadataMaps,
flatViewMaps: existingFlatViewMaps,
flatViewFieldMaps: existingFlatViewFieldMaps,
flatIndexMaps: existingFlatIndexMaps,
} = await this.flatEntityMapsCacheService.getOrRecomputeManyOrAllFlatEntityMaps(
{
workspaceId,
@@ -249,19 +290,23 @@ export class ObjectMetadataServiceV2 {
'flatObjectMetadataMaps',
'flatViewMaps',
'flatViewFieldMaps',
'flatIndexMaps',
],
},
);
const { flatObjectMetadataToCreate, relationTargetFlatFieldMetadatas } =
fromCreateObjectInputToFlatObjectMetadataAndFlatFieldMetadatasToCreate({
createObjectInput,
workspaceId,
existingFlatObjectMetadataMaps,
});
const {
flatObjectMetadataToCreate,
relationTargetFlatFieldMetadataToCreate,
flatIndexMetadataToCreate,
} = fromCreateObjectInputToFlatObjectMetadataAndFlatFieldMetadatasToCreate({
createObjectInput,
workspaceId,
existingFlatObjectMetadataMaps,
});
const existingFlatObjectMetadataMapsWithTargetRelationFlatFieldMetadatas =
relationTargetFlatFieldMetadatas.reduce(
relationTargetFlatFieldMetadataToCreate.reduce(
(flatObjectMetadataMaps, flatFieldMetadata) =>
addFlatFieldMetadataInFlatObjectMetadataMapsOrThrow({
flatFieldMetadata,
@@ -272,7 +317,7 @@ export class ObjectMetadataServiceV2 {
const flatObjectMetadataMapsWithTargetRelationFlatFieldMetadatas =
getSubFlatObjectMetadataMapsOutOfFlatFieldMetadatasOrThrow({
flatFieldMetadatas: relationTargetFlatFieldMetadatas,
flatFieldMetadatas: relationTargetFlatFieldMetadataToCreate,
flatObjectMetadataMaps:
existingFlatObjectMetadataMapsWithTargetRelationFlatFieldMetadatas,
});
@@ -286,7 +331,7 @@ export class ObjectMetadataServiceV2 {
const impactedObjectMetadataIds = [
...new Set(
relationTargetFlatFieldMetadatas.map(
relationTargetFlatFieldMetadataToCreate.map(
({ objectMetadataId }) => objectMetadataId,
),
),
@@ -329,6 +374,15 @@ export class ObjectMetadataServiceV2 {
existingFlatViewFieldMaps,
);
const toFlatIndexMaps = flatIndexMetadataToCreate.reduce(
(flatIndexMaps, flatIndexMetadata) =>
addFlatEntityToFlatEntityMapsOrThrow({
flatEntity: flatIndexMetadata,
flatEntityMaps: flatIndexMaps,
}),
existingFlatIndexMaps,
);
const validateAndBuildResult =
await this.workspaceMigrationValidateBuildAndRunService.validateBuildAndRunWorkspaceMigration(
{
@@ -345,6 +399,10 @@ export class ObjectMetadataServiceV2 {
from: existingFlatViewFieldMaps,
to: toFlatViewFieldMaps,
},
flatIndexMaps: {
from: existingFlatIndexMaps,
to: toFlatIndexMaps,
},
},
buildOptions: {
isSystemBuild: false,
@@ -2,6 +2,7 @@ import { FieldMetadataType } from 'twenty-shared/types';
import { v4 } from 'uuid';
import { type FlatFieldMetadata } from 'src/engine/metadata-modules/flat-field-metadata/types/flat-field-metadata.type';
import { type FlatObjectMetadata } from 'src/engine/metadata-modules/flat-object-metadata/types/flat-object-metadata.type';
import {
BASE_OBJECT_STANDARD_FIELD_IDS,
CUSTOM_OBJECT_STANDARD_FIELD_IDS,
@@ -10,12 +11,16 @@ import { getTsVectorColumnExpressionFromFields } from 'src/engine/workspace-mana
type BuildDefaultFlatFieldMetadataForCustomObjectArgs = {
workspaceId: string;
objectMetadataId: string;
flatObjectMetadata: Pick<FlatObjectMetadata, 'id'>;
};
export type DefaultFlatFieldForCustomObjectMaps = ReturnType<
typeof buildDefaultFlatFieldMetadatasForCustomObject
>;
// This could be replaced totally by an import schema + its transpilation when it's ready
export const buildDefaultFlatFieldMetadatasForCustomObject = ({
workspaceId,
objectMetadataId,
flatObjectMetadata: { id: objectMetadataId },
}: BuildDefaultFlatFieldMetadataForCustomObjectArgs) => {
const createdAt = new Date();
const idField: FlatFieldMetadata<FieldMetadataType.UUID> = {
@@ -278,13 +283,17 @@ export const buildDefaultFlatFieldMetadatasForCustomObject = ({
};
return {
idField,
nameField,
createdAtField,
updatedAtField,
deletedAtField,
createdByField,
positionField,
searchVectorField,
} as const;
fields: {
idField,
nameField,
createdAtField,
updatedAtField,
deletedAtField,
createdByField,
positionField,
searchVectorField,
},
} as const satisfies {
fields: Record<string, FlatFieldMetadata>;
};
};
@@ -0,0 +1,52 @@
import { v4 } from 'uuid';
import { type FlatIndexMetadata } from 'src/engine/metadata-modules/flat-index-metadata/types/flat-index-metadata.type';
import { type FlatObjectMetadata } from 'src/engine/metadata-modules/flat-object-metadata/types/flat-object-metadata.type';
import { IndexType } from 'src/engine/metadata-modules/index-metadata/types/indexType.types';
import { generateFlatIndexMetadataWithNameOrThrow } from 'src/engine/metadata-modules/index-metadata/utils/generate-flat-index.util';
import { type DefaultFlatFieldForCustomObjectMaps } from 'src/engine/metadata-modules/object-metadata/utils/build-default-flat-field-metadatas-for-custom-object.util';
export const buildDefaultIndexesForCustomObject = ({
workspaceId,
flatObjectMetadata,
defaultFlatFieldForCustomObjectMaps,
}: {
workspaceId: string;
flatObjectMetadata: FlatObjectMetadata;
defaultFlatFieldForCustomObjectMaps: DefaultFlatFieldForCustomObjectMaps;
}) => {
const tsFlatVectorIndexId = v4();
const createdAt = new Date();
const tsVectorFlatIndex = generateFlatIndexMetadataWithNameOrThrow({
flatIndex: {
createdAt,
flatIndexFieldMetadatas: [
{
createdAt,
fieldMetadataId:
defaultFlatFieldForCustomObjectMaps.fields.searchVectorField.id,
id: v4(),
indexMetadataId: tsFlatVectorIndexId,
order: 0,
updatedAt: createdAt,
},
],
id: tsFlatVectorIndexId,
indexType: IndexType.GIN,
indexWhereClause: null,
isCustom: false,
isUnique: false,
objectMetadataId: flatObjectMetadata.id,
universalIdentifier: tsFlatVectorIndexId,
updatedAt: createdAt,
workspaceId,
},
flatObjectMetadata,
});
return {
indexes: {
tsVectorFlatIndex,
},
} as const satisfies { indexes: Record<string, FlatIndexMetadata> };
};