Simplify and enhance v2 type devxp (#15032)

# Introduction
This PR introduces a huge type refactor that will leverage dynamic intra
entity optimistic flat maps update in the future and also a more
granular cache invalidation enhancing performances

close https://github.com/twentyhq/core-team-issues/issues/1717
close https://github.com/twentyhq/core-team-issues/issues/1716
close https://github.com/twentyhq/core-team-issues/issues/1643

## What's done

### Comparators centralization
Comparator is now done through global configuration as const for each
metadata names
Thanks to 

Note:
Definition of standard is evolving, standard is now scoped to an app.
Meaning that a manifest should be able to update its own standards
objects but on other app standards ones ? Each synchronizable entities
will have a standardOverrides ?

## Typing refactor

### `AllFlatEntityTypesByMetadataName`

**Single source of truth for the complete type ecosystem**, mapping each
metadata name to its entity types, flat entities, and migration actions:

```typescript
export type AllFlatEntityTypesByMetadataName = {
  fieldMetadata: {
    actions: { created: CreateFieldAction; updated: UpdateFieldAction; deleted: DeleteFieldAction; };
    flatEntity: FlatFieldMetadata;
    entity: FieldMetadataEntity;
  };
  objectMetadata: { /* ... */ };
  // ... all 10 metadata types
};
```

### `ALL_METADATA_NAME_MANY_TO_ONE_RELATIONS`

**Explicitly declares database relationships** between entities with
compile-time validation:

```typescript
export const ALL_METADATA_NAME_MANY_TO_ONE_RELATIONS = {
  viewField: {
    view: 'viewId',
    fieldMetadata: 'fieldMetadataId',
  },
  cronTrigger: {
    serverlessFunction: 'serverlessFunctionId',
  },
  // ... all relations
} as const satisfies MetadataNameAndRelations;
```

### `ALL_FLAT_ENTITY_CONFIGURATION`

**Centralizes comparison and serialization logic** for each metadata
type:

```typescript
export const ALL_FLAT_ENTITY_CONFIGURATION = {
  fieldMetadata: {
    propertiesToCompare: ['name', 'type', 'label', 'defaultValue', /* ... */],
    propertiesToStringify: ['options', 'settings', 'defaultValue'],
  },
  objectMetadata: {
    propertiesToCompare: ['nameSingular', 'namePlural', 'isActive', /* ... */],
    propertiesToStringify: [],
  },
  // ... all metadata types
} as const satisfies AllFlatEntityConfiguration;
```

## Combined Impact

These three configurations work together to create a **strongly-typed,
centrally-managed metadata system**:

1. **`AllFlatEntityTypesByMetadataName`** defines *what exists*
2. **`ALL_METADATA_NAME_MANY_TO_ONE_RELATIONS`** defines *how they
relate*
3. **`ALL_FLAT_ENTITY_CONFIGURATION`** defines *how to compare and
serialize them*

**Result:** Builders and validators become thin wrappers around
type-safe, configuration-driven logic instead of containing scattered,
error-prone manual implementations.

## What's next

### StandardOverrides standardization
Every metadata entity can be a standard one for a workspace if it's an
installed app, which means it might not expose the whole entity api to
be editable through an import dynamically
The standard overrides logic should not be applied to Fields and Objects
but to every entities

At the moment we have a logic of `EDITABLE_PROPERTIES` through the api,
and also `STANDARD_OVERRIDEDABLE_PROPERTIES`
This should be configuration centered like `propertiesToCompare` and
`propertiesToStringify`.
Scoping this PR to two last for the moment. As update dispatch to
standardOverrides could be considered as a side effect prefer waiting to
start the side effect refactor

### Granular Optimistic deprecation
With this new grain at runtime we will be able to add a flat entity and
dispatch its addition to related flat maps, so we don't have to describe
an optimistic method for each flat entity operations

See `addFlatEntityToFlatEntityAndRelatedEntityMapsOrThrow`

Note: Still in wip and included in this PR but about to create a new one
to integrate these utils and remove existing methods

### ValidateBuildAndRun dynamic args typed defintion
We should restrain the devxp to send expected flat maps entity as at
least from to or dependency as we now have the grain both a type lvl and
runtime to do so
It should not be possible in the devxp to forgot adding the views to the
v2 builder when passing the view field anymore ( that would lead to
permanent validation error in view field integrity checks )

## Conclusion
Thanks for reading and reviewing !
Any suggestions are more than welcomed ! ( same as for questions too ! )
This commit is contained in:
Paul Rastoin
2025-10-13 10:31:34 +02:00
committed by GitHub
parent 3a6621ef6c
commit 6188c72f74
265 changed files with 2537 additions and 3166 deletions
@@ -1,6 +0,0 @@
import { FLAT_CRON_TRIGGER_EDITABLE_PROPERTIES } from 'src/engine/metadata-modules/cron-trigger/constants/flat-cron-trigger-editable-properties.constant';
import { type FlatCronTrigger } from 'src/engine/metadata-modules/cron-trigger/types/flat-cron-trigger.type';
export const FLAT_CRON_TRIGGER_PROPERTIES_TO_COMPARE = [
...FLAT_CRON_TRIGGER_EDITABLE_PROPERTIES,
] as const satisfies (keyof FlatCronTrigger)[];
@@ -1,7 +1,6 @@
import { Module } from '@nestjs/common';
import { TypeOrmModule } from '@nestjs/typeorm';
import { WorkspaceManyOrAllFlatEntityMapsCacheModule } from 'src/engine/core-modules/common/services/workspace-many-or-all-flat-entity-maps-cache.module';
import { Workspace } from 'src/engine/core-modules/workspace/workspace.entity';
import { CronTriggerCronCommand } from 'src/engine/metadata-modules/cron-trigger/crons/commands/cron-trigger.cron.command';
import { CronTriggerCronJob } from 'src/engine/metadata-modules/cron-trigger/crons/jobs/cron-trigger.cron.job';
@@ -9,6 +8,7 @@ import { CronTrigger } from 'src/engine/metadata-modules/cron-trigger/entities/c
import { CronTriggerResolver } from 'src/engine/metadata-modules/cron-trigger/resolvers/cron-trigger.resolver';
import { CronTriggerV2Service } from 'src/engine/metadata-modules/cron-trigger/services/cron-trigger-v2.service';
import { WorkspaceFlatCronTriggerMapCacheService } from 'src/engine/metadata-modules/cron-trigger/services/workspace-flat-cron-trigger-map-cache.service';
import { WorkspaceManyOrAllFlatEntityMapsCacheModule } from 'src/engine/metadata-modules/flat-entity/services/workspace-many-or-all-flat-entity-maps-cache.module';
import { WorkspaceMigrationV2Module } from 'src/engine/workspace-manager/workspace-migration-v2/workspace-migration-v2.module';
@Module({
@@ -2,12 +2,6 @@ import { Injectable } from '@nestjs/common';
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 { findFlatEntityByIdInFlatEntityMapsOrThrow } from 'src/engine/core-modules/common/utils/find-flat-entity-by-id-in-flat-entity-maps-or-throw.util';
import { getSubFlatEntityMapsOrThrow } from 'src/engine/core-modules/common/utils/get-sub-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 { CreateCronTriggerInput } from 'src/engine/metadata-modules/cron-trigger/dtos/create-cron-trigger.input';
import { CronTriggerIdInput } from 'src/engine/metadata-modules/cron-trigger/dtos/cron-trigger-id.input';
import { UpdateCronTriggerInput } from 'src/engine/metadata-modules/cron-trigger/dtos/update-cron-trigger.input';
@@ -18,6 +12,12 @@ import {
import { FlatCronTrigger } from 'src/engine/metadata-modules/cron-trigger/types/flat-cron-trigger.type';
import { fromCreateCronTriggerInputToFlatCronTrigger } from 'src/engine/metadata-modules/cron-trigger/utils/from-create-cron-trigger-input-to-flat-cron-trigger.util';
import { fromUpdateCronTriggerInputToFlatCronTriggerToUpdateOrThrow } from 'src/engine/metadata-modules/cron-trigger/utils/from-update-cron-trigger-input-to-flat-cron-trigger-to-update-or-throw.util';
import { WorkspaceManyOrAllFlatEntityMapsCacheService } from 'src/engine/metadata-modules/flat-entity/services/workspace-many-or-all-flat-entity-maps-cache.service';
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 { findFlatEntityByIdInFlatEntityMapsOrThrow } from 'src/engine/metadata-modules/flat-entity/utils/find-flat-entity-by-id-in-flat-entity-maps-or-throw.util';
import { getSubFlatEntityMapsOrThrow } from 'src/engine/metadata-modules/flat-entity/utils/get-sub-flat-entity-maps-or-throw.util';
import { replaceFlatEntityInFlatEntityMapsOrThrow } from 'src/engine/metadata-modules/flat-entity/utils/replace-flat-entity-in-flat-entity-maps-or-throw.util';
import { WorkspaceMigrationBuilderExceptionV2 } from 'src/engine/workspace-manager/workspace-migration-v2/exceptions/workspace-migration-builder-exception-v2';
import { WorkspaceMigrationValidateBuildAndRunService } from 'src/engine/workspace-manager/workspace-migration-v2/services/workspace-migration-validate-build-and-run-service';
@@ -7,13 +7,13 @@ import { Repository } from 'typeorm';
import { InjectCacheStorage } from 'src/engine/core-modules/cache-storage/decorators/cache-storage.decorator';
import { CacheStorageService } from 'src/engine/core-modules/cache-storage/services/cache-storage.service';
import { CacheStorageNamespace } from 'src/engine/core-modules/cache-storage/types/cache-storage-namespace.enum';
import { EMPTY_FLAT_ENTITY_MAPS } from 'src/engine/core-modules/common/constant/empty-flat-entity-maps.constant';
import { FlatEntityMaps } from 'src/engine/core-modules/common/types/flat-entity-maps.type';
import {
CRON_TRIGGER_ENTITY_RELATION_PROPERTIES,
CronTrigger,
} from 'src/engine/metadata-modules/cron-trigger/entities/cron-trigger.entity';
import { FlatCronTrigger } from 'src/engine/metadata-modules/cron-trigger/types/flat-cron-trigger.type';
import { EMPTY_FLAT_ENTITY_MAPS } from 'src/engine/metadata-modules/flat-entity/constant/empty-flat-entity-maps.constant';
import { FlatEntityMaps } from 'src/engine/metadata-modules/flat-entity/types/flat-entity-maps.type';
import { WorkspaceFlatMapCache } from 'src/engine/workspace-flat-map-cache/decorators/workspace-flat-map-cache.decorator';
import { WorkspaceFlatMapCacheService } from 'src/engine/workspace-flat-map-cache/services/workspace-flat-map-cache.service';
@@ -1,69 +0,0 @@
import diff from 'microdiff';
import { type FromTo } from 'twenty-shared/types';
import { parseJson } from 'twenty-shared/utils';
import { FLAT_CRON_TRIGGER_PROPERTIES_TO_COMPARE } from 'src/engine/metadata-modules/cron-trigger/constants/flat-cron-trigger-properties-to-compare.constant';
import { type CronTriggerSettings } from 'src/engine/metadata-modules/cron-trigger/entities/cron-trigger.entity';
import { type FlatCronTriggerPropertiesToCompare } from 'src/engine/metadata-modules/cron-trigger/types/flat-cron-trigger-properties-to-compare.type';
import { type FlatCronTrigger } from 'src/engine/metadata-modules/cron-trigger/types/flat-cron-trigger.type';
import { type UpdateCronTriggerAction } from 'src/engine/workspace-manager/workspace-migration-v2/workspace-migration-builder-v2/types/workspace-migration-cron-trigger-action-v2.type';
import { transformMetadataForComparison } from 'src/engine/workspace-manager/workspace-sync-metadata/comparators/utils/transform-metadata-for-comparison.util';
type GetWorkspaceMigrationUpdateCronTriggerActionArgs = FromTo<
FlatCronTrigger,
'FlatCronTrigger'
>;
export const compareTwoFlatCronTrigger = ({
fromFlatCronTrigger,
toFlatCronTrigger,
}: GetWorkspaceMigrationUpdateCronTriggerActionArgs) => {
const transformMetadataForComparisonParameters = {
shouldIgnoreProperty: (property: string) =>
!FLAT_CRON_TRIGGER_PROPERTIES_TO_COMPARE.includes(
property as FlatCronTriggerPropertiesToCompare,
),
propertiesToStringify: ['settings'] as const,
};
const fromCompare = transformMetadataForComparison(
fromFlatCronTrigger,
transformMetadataForComparisonParameters,
);
const toCompare = transformMetadataForComparison(
toFlatCronTrigger,
transformMetadataForComparisonParameters,
);
const flatCronTriggerDifferences = diff(fromCompare, toCompare);
return flatCronTriggerDifferences.flatMap<
UpdateCronTriggerAction['updates'][number]
>((difference) => {
switch (difference.type) {
case 'CHANGE': {
const { oldValue, path, value } = difference;
const property = path[0] as FlatCronTriggerPropertiesToCompare;
if (property === 'settings') {
return {
from: parseJson(oldValue) as CronTriggerSettings,
to: parseJson(value) as CronTriggerSettings,
property,
};
}
return {
from: oldValue,
to: value,
property,
};
}
case 'CREATE':
case 'REMOVE':
default: {
// Should never occurs, we should only provide null never undefined and so on
return [];
}
}
});
};
@@ -1,10 +1,10 @@
import { type FlatEntityMaps } from 'src/engine/core-modules/common/types/flat-entity-maps.type';
import { type UpdateCronTriggerInput } from 'src/engine/metadata-modules/cron-trigger/dtos/update-cron-trigger.input';
import {
CronTriggerException,
CronTriggerExceptionCode,
} from 'src/engine/metadata-modules/cron-trigger/exceptions/cron-trigger.exception';
import { type FlatCronTrigger } from 'src/engine/metadata-modules/cron-trigger/types/flat-cron-trigger.type';
import { type FlatEntityMaps } from 'src/engine/metadata-modules/flat-entity/types/flat-entity-maps.type';
export const fromUpdateCronTriggerInputToFlatCronTriggerToUpdateOrThrow = ({
flatCronTriggerMaps,
@@ -1,6 +0,0 @@
import { FLAT_DATABASE_EVENT_TRIGGER_EDITABLE_PROPERTIES } from 'src/engine/metadata-modules/database-event-trigger/constants/flat-database-event-trigger-editable-properties.constant';
import { type FlatDatabaseEventTrigger } from 'src/engine/metadata-modules/database-event-trigger/types/flat-database-event-trigger.type';
export const FLAT_DATABASE_EVENT_TRIGGER_PROPERTIES_TO_COMPARE = [
...FLAT_DATABASE_EVENT_TRIGGER_EDITABLE_PROPERTIES,
] as const satisfies (keyof FlatDatabaseEventTrigger)[];
@@ -1,13 +1,13 @@
import { Module } from '@nestjs/common';
import { TypeOrmModule } from '@nestjs/typeorm';
import { WorkspaceManyOrAllFlatEntityMapsCacheModule } from 'src/engine/core-modules/common/services/workspace-many-or-all-flat-entity-maps-cache.module';
import { Workspace } from 'src/engine/core-modules/workspace/workspace.entity';
import { DatabaseEventTrigger } from 'src/engine/metadata-modules/database-event-trigger/entities/database-event-trigger.entity';
import { CallDatabaseEventTriggerJobsJob } from 'src/engine/metadata-modules/database-event-trigger/jobs/call-database-event-trigger-jobs.job';
import { DatabaseEventTriggerResolver } from 'src/engine/metadata-modules/database-event-trigger/resolvers/database-event-trigger.resolver';
import { DatabaseEventTriggerV2Service } from 'src/engine/metadata-modules/database-event-trigger/services/database-event-trigger-v2.service';
import { WorkspaceFlatDatabaseEventTriggerMapCacheService } from 'src/engine/metadata-modules/database-event-trigger/services/workspace-flat-database-event-trigger-map-cache.service';
import { WorkspaceManyOrAllFlatEntityMapsCacheModule } from 'src/engine/metadata-modules/flat-entity/services/workspace-many-or-all-flat-entity-maps-cache.module';
import { WorkspaceMigrationV2Module } from 'src/engine/workspace-manager/workspace-migration-v2/workspace-migration-v2.module';
@Module({
@@ -2,12 +2,6 @@ import { Injectable } from '@nestjs/common';
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 { findFlatEntityByIdInFlatEntityMapsOrThrow } from 'src/engine/core-modules/common/utils/find-flat-entity-by-id-in-flat-entity-maps-or-throw.util';
import { getSubFlatEntityMapsOrThrow } from 'src/engine/core-modules/common/utils/get-sub-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 { CreateDatabaseEventTriggerInput } from 'src/engine/metadata-modules/database-event-trigger/dtos/create-database-event-trigger.input';
import { DatabaseEventTriggerIdInput } from 'src/engine/metadata-modules/database-event-trigger/dtos/database-event-trigger-id.input';
import { UpdateDatabaseEventTriggerInput } from 'src/engine/metadata-modules/database-event-trigger/dtos/update-database-event-trigger.input';
@@ -18,6 +12,12 @@ import {
import { FlatDatabaseEventTrigger } from 'src/engine/metadata-modules/database-event-trigger/types/flat-database-event-trigger.type';
import { fromCreateDatabaseEventTriggerInputToFlatDatabaseEventTrigger } from 'src/engine/metadata-modules/database-event-trigger/utils/from-create-database-event-trigger-input-to-flat-database-event-trigger.util';
import { fromUpdateDatabaseEventTriggerInputToFlatDatabaseEventTriggerToUpdateOrThrow } from 'src/engine/metadata-modules/database-event-trigger/utils/from-update-database-event-trigger-input-to-flat-database-event-trigger-to-update-or-throw.util';
import { WorkspaceManyOrAllFlatEntityMapsCacheService } from 'src/engine/metadata-modules/flat-entity/services/workspace-many-or-all-flat-entity-maps-cache.service';
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 { findFlatEntityByIdInFlatEntityMapsOrThrow } from 'src/engine/metadata-modules/flat-entity/utils/find-flat-entity-by-id-in-flat-entity-maps-or-throw.util';
import { getSubFlatEntityMapsOrThrow } from 'src/engine/metadata-modules/flat-entity/utils/get-sub-flat-entity-maps-or-throw.util';
import { replaceFlatEntityInFlatEntityMapsOrThrow } from 'src/engine/metadata-modules/flat-entity/utils/replace-flat-entity-in-flat-entity-maps-or-throw.util';
import { WorkspaceMigrationBuilderExceptionV2 } from 'src/engine/workspace-manager/workspace-migration-v2/exceptions/workspace-migration-builder-exception-v2';
import { WorkspaceMigrationValidateBuildAndRunService } from 'src/engine/workspace-manager/workspace-migration-v2/services/workspace-migration-validate-build-and-run-service';
@@ -7,13 +7,13 @@ import { Repository } from 'typeorm';
import { InjectCacheStorage } from 'src/engine/core-modules/cache-storage/decorators/cache-storage.decorator';
import { CacheStorageService } from 'src/engine/core-modules/cache-storage/services/cache-storage.service';
import { CacheStorageNamespace } from 'src/engine/core-modules/cache-storage/types/cache-storage-namespace.enum';
import { EMPTY_FLAT_ENTITY_MAPS } from 'src/engine/core-modules/common/constant/empty-flat-entity-maps.constant';
import { FlatEntityMaps } from 'src/engine/core-modules/common/types/flat-entity-maps.type';
import {
DATABASE_EVENT_TRIGGER_ENTITY_RELATION_PROPERTIES,
DatabaseEventTrigger,
} from 'src/engine/metadata-modules/database-event-trigger/entities/database-event-trigger.entity';
import { FlatDatabaseEventTrigger } from 'src/engine/metadata-modules/database-event-trigger/types/flat-database-event-trigger.type';
import { EMPTY_FLAT_ENTITY_MAPS } from 'src/engine/metadata-modules/flat-entity/constant/empty-flat-entity-maps.constant';
import { FlatEntityMaps } from 'src/engine/metadata-modules/flat-entity/types/flat-entity-maps.type';
import { WorkspaceFlatMapCache } from 'src/engine/workspace-flat-map-cache/decorators/workspace-flat-map-cache.decorator';
import { WorkspaceFlatMapCacheService } from 'src/engine/workspace-flat-map-cache/services/workspace-flat-map-cache.service';
@@ -1,4 +0,0 @@
import { type FLAT_DATABASE_EVENT_TRIGGER_PROPERTIES_TO_COMPARE } from 'src/engine/metadata-modules/database-event-trigger/constants/flat-database-event-trigger-properties-to-compare.constant';
export type FlatDatabaseEventTriggerPropertiesToCompare =
(typeof FLAT_DATABASE_EVENT_TRIGGER_PROPERTIES_TO_COMPARE)[number];
@@ -1,69 +0,0 @@
import diff from 'microdiff';
import { type FromTo } from 'twenty-shared/types';
import { parseJson } from 'twenty-shared/utils';
import { FLAT_DATABASE_EVENT_TRIGGER_PROPERTIES_TO_COMPARE } from 'src/engine/metadata-modules/database-event-trigger/constants/flat-database-event-trigger-properties-to-compare.constant';
import { type DatabaseEventTriggerSettings } from 'src/engine/metadata-modules/database-event-trigger/entities/database-event-trigger.entity';
import { type FlatDatabaseEventTriggerPropertiesToCompare } from 'src/engine/metadata-modules/database-event-trigger/types/flat-database-event-trigger-properties-to-compare.type';
import { type FlatDatabaseEventTrigger } from 'src/engine/metadata-modules/database-event-trigger/types/flat-database-event-trigger.type';
import { type UpdateDatabaseEventTriggerAction } from 'src/engine/workspace-manager/workspace-migration-v2/workspace-migration-builder-v2/types/workspace-migration-database-event-trigger-action-v2.type';
import { transformMetadataForComparison } from 'src/engine/workspace-manager/workspace-sync-metadata/comparators/utils/transform-metadata-for-comparison.util';
type GetWorkspaceMigrationUpdateDatabaseEventTriggerActionArgs = FromTo<
FlatDatabaseEventTrigger,
'FlatDatabaseEventTrigger'
>;
export const compareTwoFlatDatabaseEventTrigger = ({
fromFlatDatabaseEventTrigger,
toFlatDatabaseEventTrigger,
}: GetWorkspaceMigrationUpdateDatabaseEventTriggerActionArgs) => {
const transformMetadataForComparisonParameters = {
shouldIgnoreProperty: (property: string) =>
!FLAT_DATABASE_EVENT_TRIGGER_PROPERTIES_TO_COMPARE.includes(
property as FlatDatabaseEventTriggerPropertiesToCompare,
),
propertiesToStringify: ['settings'] as const,
};
const fromCompare = transformMetadataForComparison(
fromFlatDatabaseEventTrigger,
transformMetadataForComparisonParameters,
);
const toCompare = transformMetadataForComparison(
toFlatDatabaseEventTrigger,
transformMetadataForComparisonParameters,
);
const flatDatabaseEventTriggerDifferences = diff(fromCompare, toCompare);
return flatDatabaseEventTriggerDifferences.flatMap<
UpdateDatabaseEventTriggerAction['updates'][number]
>((difference) => {
switch (difference.type) {
case 'CHANGE': {
const { oldValue, path, value } = difference;
const property = path[0] as FlatDatabaseEventTriggerPropertiesToCompare;
if (property === 'settings') {
return {
from: parseJson(oldValue) as DatabaseEventTriggerSettings,
to: parseJson(value) as DatabaseEventTriggerSettings,
property,
};
}
return {
from: oldValue,
to: value,
property,
};
}
case 'CREATE':
case 'REMOVE':
default: {
// Should never occurs, we should only provide null never undefined and so on
return [];
}
}
});
};
@@ -1,10 +1,10 @@
import { type FlatEntityMaps } from 'src/engine/core-modules/common/types/flat-entity-maps.type';
import { type UpdateDatabaseEventTriggerInput } from 'src/engine/metadata-modules/database-event-trigger/dtos/update-database-event-trigger.input';
import {
DatabaseEventTriggerException,
DatabaseEventTriggerExceptionCode,
} from 'src/engine/metadata-modules/database-event-trigger/exceptions/database-event-trigger.exception';
import { type FlatDatabaseEventTrigger } from 'src/engine/metadata-modules/database-event-trigger/types/flat-database-event-trigger.type';
import { type FlatEntityMaps } from 'src/engine/metadata-modules/flat-entity/types/flat-entity-maps.type';
export const fromUpdateDatabaseEventTriggerInputToFlatDatabaseEventTriggerToUpdateOrThrow =
({
@@ -1,7 +1,7 @@
import { type FlatFieldMetadataPropertiesToCompare } from 'src/engine/metadata-modules/flat-field-metadata/types/flat-field-metadata-properties-to-compare.type';
import { type FlatEntityPropertiesToCompare } from 'src/engine/metadata-modules/flat-entity/types/flat-entity-properties-to-compare.type';
export const FIELD_METADATA_STANDARD_OVERRIDES_PROPERTIES = [
'label',
'description',
'icon',
] as const satisfies FlatFieldMetadataPropertiesToCompare[];
] as const satisfies FlatEntityPropertiesToCompare<'fieldMetadata'>[];
@@ -9,7 +9,6 @@ import { NestjsQueryTypeOrmModule } from '@ptc-org/nestjs-query-typeorm';
import { TypeORMModule } from 'src/database/typeorm/typeorm.module';
import { ActorModule } from 'src/engine/core-modules/actor/actor.module';
import { WorkspaceManyOrAllFlatEntityMapsCacheModule } from 'src/engine/core-modules/common/services/workspace-many-or-all-flat-entity-maps-cache.module';
import { FeatureFlagModule } from 'src/engine/core-modules/feature-flag/feature-flag.module';
import { WorkspaceAuthGuard } from 'src/engine/guards/workspace-auth.guard';
import { DataSourceModule } from 'src/engine/metadata-modules/data-source/data-source.module';
@@ -25,6 +24,7 @@ import { FieldMetadataValidationService } from 'src/engine/metadata-modules/fiel
import { FieldMetadataServiceV2 } from 'src/engine/metadata-modules/field-metadata/services/field-metadata.service-v2';
import { IsFieldMetadataDefaultValue } from 'src/engine/metadata-modules/field-metadata/validators/is-field-metadata-default-value.validator';
import { IsFieldMetadataOptions } from 'src/engine/metadata-modules/field-metadata/validators/is-field-metadata-options.validator';
import { WorkspaceManyOrAllFlatEntityMapsCacheModule } from 'src/engine/metadata-modules/flat-entity/services/workspace-many-or-all-flat-entity-maps-cache.module';
import { FlatFieldMetadataModule } from 'src/engine/metadata-modules/flat-field-metadata/flat-field-metadata.module';
import { IndexMetadataModule } from 'src/engine/metadata-modules/index-metadata/index-metadata.module';
import { ObjectMetadataEntity } from 'src/engine/metadata-modules/object-metadata/object-metadata.entity';
@@ -4,12 +4,6 @@ import { InjectRepository } from '@nestjs/typeorm';
import { isDefined } from 'twenty-shared/utils';
import { In, Repository } from 'typeorm';
import { EMPTY_FLAT_ENTITY_MAPS } from 'src/engine/core-modules/common/constant/empty-flat-entity-maps.constant';
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 { getSubFlatEntityMapsOrThrow } from 'src/engine/core-modules/common/utils/get-sub-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';
@@ -19,6 +13,12 @@ import {
FieldMetadataException,
FieldMetadataExceptionCode,
} from 'src/engine/metadata-modules/field-metadata/field-metadata.exception';
import { EMPTY_FLAT_ENTITY_MAPS } from 'src/engine/metadata-modules/flat-entity/constant/empty-flat-entity-maps.constant';
import { WorkspaceManyOrAllFlatEntityMapsCacheService } from 'src/engine/metadata-modules/flat-entity/services/workspace-many-or-all-flat-entity-maps-cache.service';
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 { getSubFlatEntityMapsOrThrow } from 'src/engine/metadata-modules/flat-entity/utils/get-sub-flat-entity-maps-or-throw.util';
import { replaceFlatEntityInFlatEntityMapsOrThrow } from 'src/engine/metadata-modules/flat-entity/utils/replace-flat-entity-in-flat-entity-maps-or-throw.util';
import { fromCreateFieldInputToFlatFieldMetadatasToCreate } from 'src/engine/metadata-modules/flat-field-metadata/utils/from-create-field-input-to-flat-field-metadatas-to-create.util';
import { fromDeleteFieldInputToFlatFieldMetadatasToDelete } from 'src/engine/metadata-modules/flat-field-metadata/utils/from-delete-field-input-to-flat-field-metadatas-to-delete.util';
import { fromFlatFieldMetadataToFieldMetadataDto } from 'src/engine/metadata-modules/flat-field-metadata/utils/from-flat-field-metadata-to-field-metadata-dto.util';
@@ -15,7 +15,6 @@ import {
import { RelationType } from 'src/engine/metadata-modules/field-metadata/interfaces/relation-type.interface';
import { WorkspaceManyOrAllFlatEntityMapsCacheService } from 'src/engine/core-modules/common/services/workspace-many-or-all-flat-entity-maps-cache.service';
import { FeatureFlagKey } from 'src/engine/core-modules/feature-flag/enums/feature-flag-key.enum';
import { FeatureFlagService } from 'src/engine/core-modules/feature-flag/services/feature-flag.service';
import { compositeTypeDefinitions } from 'src/engine/metadata-modules/field-metadata/composite-types';
@@ -50,6 +49,7 @@ import { isSelectOrMultiSelectFieldMetadata } from 'src/engine/metadata-modules/
import { isValidUniqueFieldDefaultValueCombination } from 'src/engine/metadata-modules/field-metadata/utils/is-valid-unique-input.util';
import { prepareCustomFieldMetadataOptions } from 'src/engine/metadata-modules/field-metadata/utils/prepare-custom-field-metadata-for-options.util';
import { prepareCustomFieldMetadataForCreation } from 'src/engine/metadata-modules/field-metadata/utils/prepare-field-metadata-for-creation.util';
import { WorkspaceManyOrAllFlatEntityMapsCacheService } from 'src/engine/metadata-modules/flat-entity/services/workspace-many-or-all-flat-entity-maps-cache.service';
import { IndexMetadataService } from 'src/engine/metadata-modules/index-metadata/index-metadata.service';
import { computeUniqueIndexWhereClause } from 'src/engine/metadata-modules/index-metadata/utils/compute-unique-index-where-clause.util';
import { validateCanCreateUniqueIndex } from 'src/engine/metadata-modules/index-metadata/utils/validate-can-create-unique-index.util';
@@ -0,0 +1,14 @@
import { type AllFlatEntityMaps } from 'src/engine/metadata-modules/flat-entity/types/all-flat-entity-maps.type';
export const ALL_FLAT_ENTITY_MAPS_PROPERTIES = [
'flatObjectMetadataMaps',
'flatViewFieldMaps',
'flatViewMaps',
'flatIndexMaps',
'flatServerlessFunctionMaps',
'flatDatabaseEventTriggerMaps',
'flatCronTriggerMaps',
'flatRouteTriggerMaps',
'flatFieldMetadataMaps',
'flatViewFilterMaps',
] as const satisfies (keyof AllFlatEntityMaps)[];
@@ -0,0 +1,91 @@
import { FLAT_CRON_TRIGGER_EDITABLE_PROPERTIES } from 'src/engine/metadata-modules/cron-trigger/constants/flat-cron-trigger-editable-properties.constant';
import { FLAT_DATABASE_EVENT_TRIGGER_EDITABLE_PROPERTIES } from 'src/engine/metadata-modules/database-event-trigger/constants/flat-database-event-trigger-editable-properties.constant';
import { type AllMetadataName } from 'src/engine/metadata-modules/flat-entity/types/all-metadata-name.type';
import { type MetadataFlatEntity } from 'src/engine/metadata-modules/flat-entity/types/metadata-flat-entity.type';
import { FLAT_FIELD_METADATA_EDITABLE_PROPERTIES } from 'src/engine/metadata-modules/flat-field-metadata/constants/flat-field-metadata-editable-properties.constant';
import { FLAT_VIEW_FIELD_EDITABLE_PROPERTIES } from 'src/engine/metadata-modules/flat-view-field/constants/flat-view-field-editable-properties.constant';
import { FLAT_VIEW_FILTER_EDITABLE_PROPERTIES } from 'src/engine/metadata-modules/flat-view-filter/constants/flat-view-filter-editable-properties.constant';
import { FLAT_VIEW_EDITABLE_PROPERTIES } from 'src/engine/metadata-modules/flat-view/constants/flat-view-editable-properties.constant';
import { FLAT_ROUTE_TRIGGER_EDITABLE_PROPERTIES } from 'src/engine/metadata-modules/route-trigger/constants/flat-route-trigger-editable-properties.constant';
import { FLAT_SERVERLESS_FUNCTION_EDITABLE_PROPERTIES } from 'src/engine/metadata-modules/serverless-function/constants/flat-serverless-function-editable-properties.constant';
type OneFlatEntityConfiguration<T extends AllMetadataName> = {
propertiesToCompare: (keyof MetadataFlatEntity<T>)[];
propertiesToStringify: (keyof MetadataFlatEntity<T>)[];
};
export const ALL_FLAT_ENTITY_PROPERTIES_TO_COMPARE_AND_STRINGIFY = {
fieldMetadata: {
propertiesToCompare: [
...FLAT_FIELD_METADATA_EDITABLE_PROPERTIES,
'standardOverrides',
],
propertiesToStringify: [
'options',
'settings',
'standardOverrides',
'defaultValue',
],
},
objectMetadata: {
propertiesToCompare: [
'description',
'icon',
'isActive',
'isLabelSyncedWithName',
'labelPlural',
'labelSingular',
'namePlural',
'nameSingular',
'standardOverrides',
'labelIdentifierFieldMetadataId',
],
propertiesToStringify: ['standardOverrides'],
},
view: {
propertiesToCompare: ['key', 'deletedAt', ...FLAT_VIEW_EDITABLE_PROPERTIES],
propertiesToStringify: [],
},
viewField: {
propertiesToCompare: [...FLAT_VIEW_FIELD_EDITABLE_PROPERTIES, 'deletedAt'],
propertiesToStringify: [],
},
index: {
propertiesToCompare: [
'indexType',
'indexWhereClause',
'flatIndexFieldMetadatas',
'isUnique',
'name',
],
propertiesToStringify: ['flatIndexFieldMetadatas'],
},
serverlessFunction: {
propertiesToCompare: [
...FLAT_SERVERLESS_FUNCTION_EDITABLE_PROPERTIES,
'deletedAt',
],
propertiesToStringify: [],
},
cronTrigger: {
propertiesToCompare: [...FLAT_CRON_TRIGGER_EDITABLE_PROPERTIES],
propertiesToStringify: ['settings'],
},
databaseEventTrigger: {
propertiesToCompare: [...FLAT_DATABASE_EVENT_TRIGGER_EDITABLE_PROPERTIES],
propertiesToStringify: ['settings'],
},
routeTrigger: {
propertiesToCompare: [...FLAT_ROUTE_TRIGGER_EDITABLE_PROPERTIES],
propertiesToStringify: [],
},
viewFilter: {
propertiesToCompare: [
'viewId',
'deletedAt',
...FLAT_VIEW_FILTER_EDITABLE_PROPERTIES,
],
propertiesToStringify: ['value'],
},
} as const satisfies {
[P in AllMetadataName]: OneFlatEntityConfiguration<P>;
};
@@ -0,0 +1,62 @@
import { type ExtractPropertiesThatEndsWithId } from 'twenty-shared/types';
import { type AllMetadataName } from 'src/engine/metadata-modules/flat-entity/types/all-metadata-name.type';
import { type MetadataEntity } from 'src/engine/metadata-modules/flat-entity/types/metadata-entity.type';
type PropertyNameToRelationName<T extends string> = T extends `${infer Name}Id`
? Name
: never;
type ExtractEntityRelations<TEntity extends MetadataEntity<AllMetadataName>> = {
[K in ExtractPropertiesThatEndsWithId<
TEntity,
'id' | 'workspaceId'
> as PropertyNameToRelationName<K>]: K;
};
type MetadataRelatedMetadataNames<T extends AllMetadataName> = Extract<
keyof ExtractEntityRelations<MetadataEntity<T>>,
AllMetadataName
>;
type MetadataNameAndRelations = {
[T in AllMetadataName]: MetadataRelatedMetadataNames<T> extends never
? Record<string, never>
: Record<MetadataRelatedMetadataNames<T>, string> & {
[K in Exclude<
AllMetadataName,
MetadataRelatedMetadataNames<T>
>]?: string;
};
};
export const ALL_METADATA_NAME_MANY_TO_ONE_RELATIONS = {
fieldMetadata: {
objectMetadata: 'objectMetadataId',
},
objectMetadata: {},
view: {
objectMetadata: 'objectMetadataId',
},
viewField: {
view: 'viewId',
fieldMetadata: 'fieldMetadataId',
},
index: {
objectMetadata: 'objectMetadataId',
},
serverlessFunction: {},
cronTrigger: {
serverlessFunction: 'serverlessFunctionId',
},
databaseEventTrigger: {
serverlessFunction: 'serverlessFunctionId',
},
routeTrigger: {
serverlessFunction: 'serverlessFunctionId',
},
viewFilter: {
view: 'viewId',
fieldMetadata: 'fieldMetadataId',
},
} as const satisfies MetadataNameAndRelations;
@@ -0,0 +1,12 @@
export const ALL_METADATA_NAME = {
fieldMetadata: 'fieldMetadata',
objectMetadata: 'objectMetadata',
view: 'view',
viewField: 'viewField',
index: 'index',
serverlessFunction: 'serverlessFunction',
cronTrigger: 'cronTrigger',
databaseEventTrigger: 'databaseEventTrigger',
routeTrigger: 'routeTrigger',
viewFilter: 'viewFilter',
} as const;
@@ -0,0 +1,46 @@
import { type AllMetadataName } from 'src/engine/metadata-modules/flat-entity/types/all-metadata-name.type';
import { type MetadataManyToOneRelatedMetadataNames } from 'src/engine/metadata-modules/flat-entity/types/metadata-many-to-one-related-metadata-names.type';
type MetadataRequiredForValidation = {
[T in AllMetadataName]: Record<
MetadataManyToOneRelatedMetadataNames<T>,
true
> & {
[K in Exclude<AllMetadataName, T>]?: true;
};
};
export const ALL_METADATA_REQUIRED_METADATA_FOR_VALIDATION = {
fieldMetadata: {
objectMetadata: true,
},
objectMetadata: {
fieldMetadata: true,
},
view: {
objectMetadata: true,
},
viewField: {
view: true,
fieldMetadata: true,
objectMetadata: true,
},
index: {
objectMetadata: true,
fieldMetadata: true,
},
serverlessFunction: {},
cronTrigger: {
serverlessFunction: true,
},
databaseEventTrigger: {
serverlessFunction: true,
},
routeTrigger: {
serverlessFunction: true,
},
viewFilter: {
view: true,
fieldMetadata: true,
},
} as const satisfies MetadataRequiredForValidation;
@@ -0,0 +1,15 @@
import { EMPTY_FLAT_ENTITY_MAPS } from 'src/engine/metadata-modules/flat-entity/constant/empty-flat-entity-maps.constant';
import { type AllFlatEntityMaps } from 'src/engine/metadata-modules/flat-entity/types/all-flat-entity-maps.type';
export const EMPTY_ALL_FLAT_ENTITY_MAPS = {
flatObjectMetadataMaps: EMPTY_FLAT_ENTITY_MAPS,
flatFieldMetadataMaps: EMPTY_FLAT_ENTITY_MAPS,
flatIndexMaps: EMPTY_FLAT_ENTITY_MAPS,
flatViewFieldMaps: EMPTY_FLAT_ENTITY_MAPS,
flatViewMaps: EMPTY_FLAT_ENTITY_MAPS,
flatServerlessFunctionMaps: EMPTY_FLAT_ENTITY_MAPS,
flatCronTriggerMaps: EMPTY_FLAT_ENTITY_MAPS,
flatDatabaseEventTriggerMaps: EMPTY_FLAT_ENTITY_MAPS,
flatRouteTriggerMaps: EMPTY_FLAT_ENTITY_MAPS,
flatViewFilterMaps: EMPTY_FLAT_ENTITY_MAPS,
} as const satisfies AllFlatEntityMaps;
@@ -0,0 +1,7 @@
import { type FlatEntityMaps } from 'src/engine/metadata-modules/flat-entity/types/flat-entity-maps.type';
import { type FlatEntity } from 'src/engine/metadata-modules/flat-entity/types/flat-entity.type';
export const EMPTY_FLAT_ENTITY_MAPS = {
byId: {},
idByUniversalIdentifier: {},
} as const satisfies FlatEntityMaps<FlatEntity>;
@@ -0,0 +1,15 @@
import { CustomException } from 'src/utils/custom-exception';
export class FlatEntityMapsException extends CustomException {
code: FlatEntityMapsExceptionCode;
constructor(message: string, code: FlatEntityMapsExceptionCode) {
super(message, code);
}
}
export enum FlatEntityMapsExceptionCode {
ENTITY_ALREADY_EXISTS = 'ENTITY_ALREADY_EXISTS',
ENTITY_NOT_FOUND = 'ENTITY_NOT_FOUND',
ENTITY_MALFORMED = 'ENTITY_MALFORMED',
}
@@ -0,0 +1,11 @@
import { Module } from '@nestjs/common';
import { WorkspaceManyOrAllFlatEntityMapsCacheService } from 'src/engine/metadata-modules/flat-entity/services/workspace-many-or-all-flat-entity-maps-cache.service';
import { WorkspaceFlatMapCacheModule } from 'src/engine/workspace-flat-map-cache/workspace-flat-map-cache.module';
@Module({
imports: [WorkspaceFlatMapCacheModule],
providers: [WorkspaceManyOrAllFlatEntityMapsCacheService],
exports: [WorkspaceManyOrAllFlatEntityMapsCacheService],
})
export class WorkspaceManyOrAllFlatEntityMapsCacheModule {}
@@ -0,0 +1,111 @@
import { Injectable, Logger } from '@nestjs/common';
import { isDefined } from 'twenty-shared/utils';
import { ALL_FLAT_ENTITY_MAPS_PROPERTIES } from 'src/engine/metadata-modules/flat-entity/constant/all-flat-entity-maps-properties.constant';
import { AllFlatEntityMaps } from 'src/engine/metadata-modules/flat-entity/types/all-flat-entity-maps.type';
import { WorkspaceFlatMapCacheRegistryService } from 'src/engine/workspace-flat-map-cache/services/workspace-flat-map-cache-registry.service';
import { WorkspaceFlatMapCacheService } from 'src/engine/workspace-flat-map-cache/services/workspace-flat-map-cache.service';
@Injectable()
export class WorkspaceManyOrAllFlatEntityMapsCacheService {
private readonly logger = new Logger(
WorkspaceManyOrAllFlatEntityMapsCacheService.name,
);
constructor(
private readonly cacheRegistry: WorkspaceFlatMapCacheRegistryService,
) {}
private async executeActionForManyOrAllFlatEntity<
K extends (keyof AllFlatEntityMaps)[] = (keyof AllFlatEntityMaps)[],
>({
action,
flatMapsKeys,
}: {
flatMapsKeys: K | undefined;
action: (args: {
service: WorkspaceFlatMapCacheService<AllFlatEntityMaps[K[number]]>;
flatMapKey: K[number];
}) => Promise<void>;
}): Promise<void> {
const keysToProcess = isDefined(flatMapsKeys)
? flatMapsKeys
: ALL_FLAT_ENTITY_MAPS_PROPERTIES;
for (const flatMapKey of keysToProcess) {
try {
const service = this.cacheRegistry.getCacheServiceOrThrow(
flatMapKey as K[number],
);
await action({
flatMapKey: flatMapKey,
service,
});
} catch (error) {
this.logger.error(
`Failed to run action on flat entity maps of ${flatMapKey}`,
error,
);
throw error;
}
}
}
public async getOrRecomputeManyOrAllFlatEntityMaps<
T extends (keyof AllFlatEntityMaps)[] = (keyof AllFlatEntityMaps)[],
>({
flatMapsKeys,
workspaceId,
}: {
workspaceId: string;
flatMapsKeys?: T;
}): Promise<Pick<AllFlatEntityMaps, T[number]>> {
let pickedFlatEntityMaps = {} as Pick<AllFlatEntityMaps, T[number]>;
await this.executeActionForManyOrAllFlatEntity({
action: async ({ service, flatMapKey }) => {
const cacheResult = await service.getExistingOrRecomputeFlatMaps({
workspaceId,
});
pickedFlatEntityMaps[flatMapKey] = cacheResult;
},
flatMapsKeys,
});
return pickedFlatEntityMaps;
}
public async invalidateFlatEntityMaps<
T extends (keyof AllFlatEntityMaps)[] = (keyof AllFlatEntityMaps)[],
>({
flatMapsKeys,
workspaceId,
}: {
workspaceId: string;
flatMapsKeys?: T;
}): Promise<void> {
await this.executeActionForManyOrAllFlatEntity({
action: async ({ service }) =>
await service.invalidateCache({ workspaceId }),
flatMapsKeys,
});
}
public async flushFlatEntityMaps<
T extends (keyof AllFlatEntityMaps)[] = (keyof AllFlatEntityMaps)[],
>({
flatMapsKeys,
workspaceId,
}: {
workspaceId: string;
flatMapsKeys?: T;
}): Promise<void> {
await this.executeActionForManyOrAllFlatEntity({
action: async ({ service }) => await service.flushCache({ workspaceId }),
flatMapsKeys,
});
}
}
@@ -0,0 +1,4 @@
import { type AllFlatEntityTypesByMetadataName } from 'src/engine/metadata-modules/flat-entity/types/all-flat-entity-types-by-metadata-name';
export type AllFlatEntities =
AllFlatEntityTypesByMetadataName[keyof AllFlatEntityTypesByMetadataName]['flatEntity'];
@@ -0,0 +1,27 @@
import { type Expect } from 'twenty-shared/testing';
import { type AllFlatEntityTypesByMetadataName } from 'src/engine/metadata-modules/flat-entity/types/all-flat-entity-types-by-metadata-name';
import { type FlatEntity } from 'src/engine/metadata-modules/flat-entity/types/flat-entity.type';
import { type WorkspaceMigrationActionV2 } from 'src/engine/workspace-manager/workspace-migration-v2/workspace-migration-builder-v2/types/workspace-migration-action-common-v2';
type ExpectedGenericFlatEntityInformation = {
actions: {
created: WorkspaceMigrationActionV2 | WorkspaceMigrationActionV2[];
deleted: WorkspaceMigrationActionV2 | WorkspaceMigrationActionV2[];
updated: WorkspaceMigrationActionV2 | WorkspaceMigrationActionV2[];
};
flatEntity: FlatEntity;
};
type ExpectedGenericAllFlatEntityInformationByMetadataEngine = {
[P in keyof AllFlatEntityTypesByMetadataName]: ExpectedGenericFlatEntityInformation;
};
// eslint-disable-next-line unused-imports/no-unused-vars
type Assertions = [
Expect<
AllFlatEntityTypesByMetadataName extends ExpectedGenericAllFlatEntityInformationByMetadataEngine
? true
: false
>,
];
@@ -0,0 +1,10 @@
import { type AllFlatEntityTypesByMetadataName } from 'src/engine/metadata-modules/flat-entity/types/all-flat-entity-types-by-metadata-name';
import { type FlatEntityMaps } from 'src/engine/metadata-modules/flat-entity/types/flat-entity-maps.type';
import { type MetadataFlatEntity } from 'src/engine/metadata-modules/flat-entity/types/metadata-flat-entity.type';
import { type MetadataToFlatEntityMapsKey } from 'src/engine/metadata-modules/flat-entity/types/metadata-to-flat-entity-maps-key';
export type AllFlatEntityMaps = {
[P in keyof AllFlatEntityTypesByMetadataName as MetadataToFlatEntityMapsKey<P>]: FlatEntityMaps<
MetadataFlatEntity<P>
>;
};
@@ -0,0 +1,162 @@
import { type CronTrigger } from 'src/engine/metadata-modules/cron-trigger/entities/cron-trigger.entity';
import { type FlatCronTrigger } from 'src/engine/metadata-modules/cron-trigger/types/flat-cron-trigger.type';
import { type DatabaseEventTrigger } from 'src/engine/metadata-modules/database-event-trigger/entities/database-event-trigger.entity';
import { type FlatDatabaseEventTrigger } from 'src/engine/metadata-modules/database-event-trigger/types/flat-database-event-trigger.type';
import { type FieldMetadataEntity } from 'src/engine/metadata-modules/field-metadata/field-metadata.entity';
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 { type FlatViewField } from 'src/engine/metadata-modules/flat-view-field/types/flat-view-field.type';
import { type FlatViewFilter } from 'src/engine/metadata-modules/flat-view-filter/types/flat-view-filter.type';
import { type FlatView } from 'src/engine/metadata-modules/flat-view/types/flat-view.type';
import { type IndexMetadataEntity } from 'src/engine/metadata-modules/index-metadata/index-metadata.entity';
import { type ObjectMetadataEntity } from 'src/engine/metadata-modules/object-metadata/object-metadata.entity';
import { type RouteTrigger } from 'src/engine/metadata-modules/route-trigger/route-trigger.entity';
import { type FlatRouteTrigger } from 'src/engine/metadata-modules/route-trigger/types/flat-route-trigger.type';
import { type ServerlessFunctionEntity } from 'src/engine/metadata-modules/serverless-function/serverless-function.entity';
import { type FlatServerlessFunction } from 'src/engine/metadata-modules/serverless-function/types/flat-serverless-function.type';
import { type ViewFieldEntity } from 'src/engine/metadata-modules/view-field/entities/view-field.entity';
import { type ViewFilterEntity } from 'src/engine/metadata-modules/view-filter/entities/view-filter.entity';
import { type ViewEntity } from 'src/engine/metadata-modules/view/entities/view.entity';
import {
type CreateCronTriggerAction,
type DeleteCronTriggerAction,
type UpdateCronTriggerAction,
} from 'src/engine/workspace-manager/workspace-migration-v2/workspace-migration-builder-v2/builders/cron-trigger/types/workspace-migration-cron-trigger-action-v2.type';
import {
type CreateDatabaseEventTriggerAction,
type DeleteDatabaseEventTriggerAction,
type UpdateDatabaseEventTriggerAction,
} from 'src/engine/workspace-manager/workspace-migration-v2/workspace-migration-builder-v2/builders/database-event-trigger/types/workspace-migration-database-event-trigger-action-v2.type';
import {
type CreateFieldAction,
type DeleteFieldAction,
type UpdateFieldAction,
} from 'src/engine/workspace-manager/workspace-migration-v2/workspace-migration-builder-v2/builders/field/types/workspace-migration-field-action-v2';
import {
type CreateIndexAction,
type DeleteIndexAction,
} from 'src/engine/workspace-manager/workspace-migration-v2/workspace-migration-builder-v2/builders/index/types/workspace-migration-index-action-v2';
import {
type CreateObjectAction,
type DeleteObjectAction,
type UpdateObjectAction,
} from 'src/engine/workspace-manager/workspace-migration-v2/workspace-migration-builder-v2/builders/object/types/workspace-migration-object-action-v2';
import {
type CreateRouteTriggerAction,
type DeleteRouteTriggerAction,
type UpdateRouteTriggerAction,
} from 'src/engine/workspace-manager/workspace-migration-v2/workspace-migration-builder-v2/builders/route-trigger/types/workspace-migration-route-trigger-action-v2.type';
import {
type CreateServerlessFunctionAction,
type DeleteServerlessFunctionAction,
type UpdateServerlessFunctionAction,
} from 'src/engine/workspace-manager/workspace-migration-v2/workspace-migration-builder-v2/builders/serverless-function/types/workspace-migration-serverless-function-action-v2.type';
import {
type CreateViewFieldAction,
type DeleteViewFieldAction,
type UpdateViewFieldAction,
} from 'src/engine/workspace-manager/workspace-migration-v2/workspace-migration-builder-v2/builders/view-field/types/workspace-migration-view-field-action-v2.type';
import {
type CreateViewFilterAction,
type DeleteViewFilterAction,
type UpdateViewFilterAction,
} from 'src/engine/workspace-manager/workspace-migration-v2/workspace-migration-builder-v2/builders/view-filter/types/workspace-migration-view-filter-action-v2.type';
import {
type CreateViewAction,
type DeleteViewAction,
type UpdateViewAction,
} from 'src/engine/workspace-manager/workspace-migration-v2/workspace-migration-builder-v2/builders/view/types/workspace-migration-view-action-v2.type';
export type AllFlatEntityTypesByMetadataName = {
fieldMetadata: {
actions: {
created: CreateFieldAction;
updated: UpdateFieldAction;
deleted: DeleteFieldAction;
};
flatEntity: FlatFieldMetadata;
entity: FieldMetadataEntity;
};
objectMetadata: {
actions: {
created: CreateObjectAction;
updated: UpdateObjectAction;
deleted: DeleteObjectAction;
};
flatEntity: FlatObjectMetadata;
entity: ObjectMetadataEntity;
};
view: {
actions: {
created: CreateViewAction;
updated: UpdateViewAction;
deleted: DeleteViewAction;
};
flatEntity: FlatView;
entity: ViewEntity;
};
viewField: {
actions: {
created: CreateViewFieldAction;
updated: UpdateViewFieldAction;
deleted: DeleteViewFieldAction;
};
flatEntity: FlatViewField;
entity: ViewFieldEntity;
};
index: {
actions: {
created: CreateIndexAction;
updated: [DeleteIndexAction, CreateIndexAction];
deleted: DeleteIndexAction;
};
flatEntity: FlatIndexMetadata;
entity: IndexMetadataEntity;
};
serverlessFunction: {
actions: {
created: CreateServerlessFunctionAction;
updated: UpdateServerlessFunctionAction;
deleted: DeleteServerlessFunctionAction;
};
flatEntity: FlatServerlessFunction;
entity: ServerlessFunctionEntity;
};
cronTrigger: {
actions: {
created: CreateCronTriggerAction;
updated: UpdateCronTriggerAction;
deleted: DeleteCronTriggerAction;
};
flatEntity: FlatCronTrigger;
entity: CronTrigger;
};
databaseEventTrigger: {
actions: {
created: CreateDatabaseEventTriggerAction;
updated: UpdateDatabaseEventTriggerAction;
deleted: DeleteDatabaseEventTriggerAction;
};
flatEntity: FlatDatabaseEventTrigger;
entity: DatabaseEventTrigger;
};
routeTrigger: {
actions: {
created: CreateRouteTriggerAction;
updated: UpdateRouteTriggerAction;
deleted: DeleteRouteTriggerAction;
};
flatEntity: FlatRouteTrigger;
entity: RouteTrigger;
};
viewFilter: {
actions: {
created: CreateViewFilterAction;
updated: UpdateViewFilterAction;
deleted: DeleteViewFilterAction;
};
flatEntity: FlatViewFilter;
entity: ViewFilterEntity;
};
};
@@ -0,0 +1,3 @@
import { type ALL_METADATA_NAME } from 'src/engine/metadata-modules/flat-entity/constant/all-metadata-name.constant';
export type AllMetadataName = keyof typeof ALL_METADATA_NAME;
@@ -0,0 +1,6 @@
import { type FlatEntity } from 'src/engine/metadata-modules/flat-entity/types/flat-entity.type';
export type FlatEntityMaps<T extends FlatEntity> = {
byId: Partial<Record<string, T>>;
idByUniversalIdentifier: Partial<Record<string, string>>;
};
@@ -0,0 +1,5 @@
import { type 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 AllMetadataName } from 'src/engine/metadata-modules/flat-entity/types/all-metadata-name.type';
export type FlatEntityPropertiesToCompare<T extends AllMetadataName> =
(typeof ALL_FLAT_ENTITY_PROPERTIES_TO_COMPARE_AND_STRINGIFY)[T]['propertiesToCompare'][number];
@@ -0,0 +1,5 @@
import { type 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 AllMetadataName } from 'src/engine/metadata-modules/flat-entity/types/all-metadata-name.type';
export type FlatEntityPropertiesToStringify<T extends AllMetadataName> =
(typeof ALL_FLAT_ENTITY_PROPERTIES_TO_COMPARE_AND_STRINGIFY)[T]['propertiesToStringify'][number];
@@ -0,0 +1,11 @@
import { type AllMetadataName } from 'src/engine/metadata-modules/flat-entity/types/all-metadata-name.type';
import { type FlatEntityPropertiesToCompare } from 'src/engine/metadata-modules/flat-entity/types/flat-entity-properties-to-compare.type';
import { type MetadataFlatEntity } from 'src/engine/metadata-modules/flat-entity/types/metadata-flat-entity.type';
import { type PropertyUpdate } from 'src/engine/workspace-manager/workspace-migration-v2/types/property-update.type';
export type FlatEntityPropertiesUpdates<
T extends AllMetadataName,
K extends FlatEntityPropertiesToCompare<T> = FlatEntityPropertiesToCompare<T>,
> = Array<
PropertyUpdate<MetadataFlatEntity<T>, Extract<K, keyof MetadataFlatEntity<T>>>
>;
@@ -0,0 +1,4 @@
export interface FlatEntity {
id: string;
universalIdentifier: string;
}
@@ -0,0 +1,5 @@
import { type AllFlatEntityTypesByMetadataName } from 'src/engine/metadata-modules/flat-entity/types/all-flat-entity-types-by-metadata-name';
import { type AllMetadataName } from 'src/engine/metadata-modules/flat-entity/types/all-metadata-name.type';
export type MetadataEntity<T extends AllMetadataName> =
AllFlatEntityTypesByMetadataName[T]['entity'];
@@ -0,0 +1,7 @@
import { type AllMetadataName } from 'src/engine/metadata-modules/flat-entity/types/all-metadata-name.type';
import { type FlatEntityMaps } from 'src/engine/metadata-modules/flat-entity/types/flat-entity-maps.type';
import { type MetadataFlatEntity } from 'src/engine/metadata-modules/flat-entity/types/metadata-flat-entity.type';
export type MetadataFlatEntityMaps<T extends AllMetadataName> = FlatEntityMaps<
MetadataFlatEntity<T>
>;
@@ -0,0 +1,5 @@
import { type AllFlatEntityTypesByMetadataName } from 'src/engine/metadata-modules/flat-entity/types/all-flat-entity-types-by-metadata-name';
import { type AllMetadataName } from 'src/engine/metadata-modules/flat-entity/types/all-metadata-name.type';
export type MetadataFlatEntity<T extends AllMetadataName> =
AllFlatEntityTypesByMetadataName[T]['flatEntity'];
@@ -0,0 +1,8 @@
import { type ALL_METADATA_NAME_MANY_TO_ONE_RELATIONS } from 'src/engine/metadata-modules/flat-entity/constant/all-metadata-many-to-one-relations.constant';
import { type AllMetadataName } from 'src/engine/metadata-modules/flat-entity/types/all-metadata-name.type';
export type MetadataManyToOneRelatedMetadataNames<T extends AllMetadataName> =
Extract<
keyof (typeof ALL_METADATA_NAME_MANY_TO_ONE_RELATIONS)[T],
AllMetadataName
>;
@@ -0,0 +1,24 @@
import { type AllMetadataName } from 'src/engine/metadata-modules/flat-entity/types/all-metadata-name.type';
import { type MetadataEntity } from 'src/engine/metadata-modules/flat-entity/types/metadata-entity.type';
type ExtractRelationIdProperties<T> = {
[K in keyof T]: K extends `${infer _}Id`
? K extends 'id' | 'workspaceId' | 'standardId'
? never
: T[K] extends string | null | undefined
? K
: never
: never;
}[keyof T];
type PropertyNameToRelationName<T extends string> = T extends `${infer Name}Id`
? Name
: never;
type ExtractEntityRelations<TEntity> = {
[K in ExtractRelationIdProperties<TEntity> as PropertyNameToRelationName<K>]: K;
};
export type MetadataNameAndRelations = {
[T in AllMetadataName]: Partial<ExtractEntityRelations<MetadataEntity<T>>>;
};
@@ -0,0 +1,6 @@
import { type AllMetadataName } from 'src/engine/metadata-modules/flat-entity/types/all-metadata-name.type';
import { type MetadataManyToOneRelatedMetadataNames } from 'src/engine/metadata-modules/flat-entity/types/metadata-many-to-one-related-metadata-names.type';
import { type MetadataToFlatEntityMapsKey } from 'src/engine/metadata-modules/flat-entity/types/metadata-to-flat-entity-maps-key';
export type MetadataRelatedFlatEntityMapsKeys<T extends AllMetadataName> =
MetadataToFlatEntityMapsKey<MetadataManyToOneRelatedMetadataNames<T>>;
@@ -0,0 +1,22 @@
import { type AllFlatEntityMaps } from 'src/engine/metadata-modules/flat-entity/types/all-flat-entity-maps.type';
import { type AllMetadataName } from 'src/engine/metadata-modules/flat-entity/types/all-metadata-name.type';
import { type MetadataRelatedFlatEntityMapsKeys } from 'src/engine/metadata-modules/flat-entity/types/metadata-related-flat-entity-maps-keys.type';
import { type MetadataToFlatEntityMapsKey } from 'src/engine/metadata-modules/flat-entity/types/metadata-to-flat-entity-maps-key';
import { type MetadataValidationRelatedMetadataNames } from 'src/engine/metadata-modules/flat-entity/types/metadata-validation-related-metadata-names.type';
export type MetadataFlatEntityAndRelatedFlatEntityMaps<
T extends AllMetadataName,
> = Pick<
AllFlatEntityMaps,
MetadataRelatedFlatEntityMapsKeys<T> | MetadataToFlatEntityMapsKey<T>
>;
export type MetadataValidationRelatedFlatEntityMaps<T extends AllMetadataName> =
MetadataValidationRelatedMetadataNames<T> extends undefined
? undefined
: Pick<
AllFlatEntityMaps,
MetadataToFlatEntityMapsKey<
NonNullable<MetadataValidationRelatedMetadataNames<T>>
>
>;
@@ -0,0 +1,4 @@
import { type AllMetadataName } from 'src/engine/metadata-modules/flat-entity/types/all-metadata-name.type';
export type MetadataToFlatEntityMapsKey<T extends AllMetadataName> =
T extends AllMetadataName ? `flat${Capitalize<T>}Maps` : never;
@@ -0,0 +1,16 @@
import { type IsEmptyRecord } from 'twenty-shared/types';
import { type ALL_METADATA_REQUIRED_METADATA_FOR_VALIDATION } from 'src/engine/metadata-modules/flat-entity/constant/all-metadata-required-metadata-for-validation.constant';
import { type AllMetadataName } from 'src/engine/metadata-modules/flat-entity/types/all-metadata-name.type';
export type MetadataValidationRelatedMetadataNames<T extends AllMetadataName> =
IsEmptyRecord<
(typeof ALL_METADATA_REQUIRED_METADATA_FOR_VALIDATION)[T]
> extends true
? undefined
: NonNullable<
Extract<
keyof (typeof ALL_METADATA_REQUIRED_METADATA_FOR_VALIDATION)[T],
AllMetadataName
>
>;
@@ -0,0 +1,31 @@
import { type AllFlatEntityTypesByMetadataName } from 'src/engine/metadata-modules/flat-entity/types/all-flat-entity-types-by-metadata-name';
import { type AllMetadataName } from 'src/engine/metadata-modules/flat-entity/types/all-metadata-name.type';
export type MetadataWorkspaceMigrationActionsRecord<T extends AllMetadataName> =
{
[K in 'created' | 'updated' | 'deleted']: MetadataWorkspaceMigrationAction<
T,
K
>[];
};
export type MetadataWorkspaceMigrationAction<
T extends AllMetadataName,
TOperation extends 'created' | 'deleted' | 'updated' =
| 'created'
| 'deleted'
| 'updated',
> = AllFlatEntityTypesByMetadataName[T]['actions'][TOperation] extends infer Action
? Action extends Array<unknown>
? Action[number]
: Action
: never;
export type FromWorkspaceMigrationActionToMetadataName<TAction> = {
[K in AllMetadataName]: TAction extends AllFlatEntityTypesByMetadataName[K]['actions'][
| 'created'
| 'deleted'
| 'updated']
? K
: never;
}[AllMetadataName];
@@ -0,0 +1,88 @@
import { ALL_METADATA_NAME_MANY_TO_ONE_RELATIONS } from 'src/engine/metadata-modules/flat-entity/constant/all-metadata-many-to-one-relations.constant';
import {
FlatEntityMapsException,
FlatEntityMapsExceptionCode,
} from 'src/engine/metadata-modules/flat-entity/exceptions/flat-entity-maps.exception';
import { type AllMetadataName } from 'src/engine/metadata-modules/flat-entity/types/all-metadata-name.type';
import { type MetadataFlatEntity } from 'src/engine/metadata-modules/flat-entity/types/metadata-flat-entity.type';
import { type MetadataFlatEntityAndRelatedFlatEntityMaps } from 'src/engine/metadata-modules/flat-entity/types/metadata-related-types.type';
import { addFlatEntityToFlatEntityMapsOrThrow } from 'src/engine/metadata-modules/flat-entity/utils/add-flat-entity-to-flat-entity-maps-or-throw.util';
import { findFlatEntityByIdInFlatEntityMapsOrThrow } from 'src/engine/metadata-modules/flat-entity/utils/find-flat-entity-by-id-in-flat-entity-maps-or-throw.util';
import { getMetadataFlatEntityMapsKey } from 'src/engine/metadata-modules/flat-entity/utils/get-metadata-flat-entity-maps-key.util';
type AddFlatEntityToFlatEntityAndRelatedEntityMapsOrThrowArgs<
T extends AllMetadataName,
> = {
metadataName: T;
flatEntity: MetadataFlatEntity<T>;
flatEntityAndRelatedMaps: MetadataFlatEntityAndRelatedFlatEntityMaps<T>;
};
export const addFlatEntityToFlatEntityAndRelatedEntityMapsOrThrow = <
T extends AllMetadataName,
>({
metadataName,
flatEntity,
flatEntityAndRelatedMaps: initialFlatEntityAndRelatedMaps,
}: AddFlatEntityToFlatEntityAndRelatedEntityMapsOrThrowArgs<T>): MetadataFlatEntityAndRelatedFlatEntityMaps<T> => {
const flatEntityMapsKey: keyof MetadataFlatEntityAndRelatedFlatEntityMaps<T> =
getMetadataFlatEntityMapsKey(metadataName);
const updatedFlatEntityMaps = addFlatEntityToFlatEntityMapsOrThrow({
flatEntity,
flatEntityMaps: initialFlatEntityAndRelatedMaps[flatEntityMapsKey],
});
const manyToOneRelatedMetadataName = Object.entries(
ALL_METADATA_NAME_MANY_TO_ONE_RELATIONS[metadataName],
);
return manyToOneRelatedMetadataName.reduce(
(flatEntityAndRelatedMaps, [relatedMetadataName, foreignKey]) => {
const relatedFlatEntityMapsKey = getMetadataFlatEntityMapsKey(
relatedMetadataName as AllMetadataName,
);
const relatedFLatEntityMetadataMaps =
flatEntityAndRelatedMaps[relatedFlatEntityMapsKey];
const relatedFlatEntity = findFlatEntityByIdInFlatEntityMapsOrThrow({
flatEntityId: flatEntity[
foreignKey as keyof MetadataFlatEntity<T>
] as string,
flatEntityMaps: relatedFLatEntityMetadataMaps,
});
const foreignKeyAggregatorProperty = `${metadataName}Ids`;
if (
!Object.prototype.hasOwnProperty.call(
relatedFlatEntity,
foreignKeyAggregatorProperty,
)
) {
throw new FlatEntityMapsException(
'Should never occur, invalid cached format',
FlatEntityMapsExceptionCode.ENTITY_MALFORMED,
);
}
const updatedRelatedFlatEntityMetadataMaps = {
...relatedFlatEntity,
[foreignKeyAggregatorProperty]: [
...(relatedFlatEntity[
foreignKeyAggregatorProperty as keyof MetadataFlatEntity<T>
] as string[]),
flatEntity.id,
],
};
return {
...flatEntityAndRelatedMaps,
[relatedFlatEntityMapsKey]: updatedRelatedFlatEntityMetadataMaps,
};
},
{
...initialFlatEntityAndRelatedMaps,
[flatEntityMapsKey]: updatedFlatEntityMaps,
},
);
};
@@ -0,0 +1,36 @@
import { isDefined } from 'class-validator';
import {
FlatEntityMapsException,
FlatEntityMapsExceptionCode,
} from 'src/engine/metadata-modules/flat-entity/exceptions/flat-entity-maps.exception';
import { type FlatEntityMaps } from 'src/engine/metadata-modules/flat-entity/types/flat-entity-maps.type';
import { type FlatEntity } from 'src/engine/metadata-modules/flat-entity/types/flat-entity.type';
type AddFlatEntityToFlatEntityMapsOrThrowArgs<T extends FlatEntity> = {
flatEntity: T;
flatEntityMaps: FlatEntityMaps<T>;
};
export const addFlatEntityToFlatEntityMapsOrThrow = <T extends FlatEntity>({
flatEntity,
flatEntityMaps,
}: AddFlatEntityToFlatEntityMapsOrThrowArgs<T>): FlatEntityMaps<T> => {
if (isDefined(flatEntityMaps.byId[flatEntity.id])) {
throw new FlatEntityMapsException(
'addFlatEntityToFlatEntityMapsOrThrow: flat entity to add already exists',
FlatEntityMapsExceptionCode.ENTITY_ALREADY_EXISTS,
);
}
return {
byId: {
...flatEntityMaps.byId,
[flatEntity.id]: flatEntity,
},
idByUniversalIdentifier: {
...flatEntityMaps.idByUniversalIdentifier,
[flatEntity.universalIdentifier]: flatEntity.id,
},
};
};
@@ -0,0 +1,76 @@
import diff from 'microdiff';
import { type FromTo } from 'twenty-shared/types';
import { parseJson } from 'twenty-shared/utils';
import { type AllMetadataName } from 'src/engine/metadata-modules/flat-entity/types/all-metadata-name.type';
import { type FlatEntityPropertiesToCompare } from 'src/engine/metadata-modules/flat-entity/types/flat-entity-properties-to-compare.type';
import { type FlatEntityPropertiesUpdates } 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[];
}): FlatEntityPropertiesUpdates<T> => {
const [transformedFromFlatEntity, transformedToFlatEntity] = [
fromFlatEntity,
toFlatEntity,
].map((flatEntity) =>
transformFlatEntityForComparison({
flatEntity,
propertiesToCompare,
propertiesToStringify,
}),
);
const flatEntityDifferences = diff(
transformedFromFlatEntity,
transformedToFlatEntity,
);
return flatEntityDifferences.flatMap<FlatEntityPropertiesUpdates<T>[number]>(
(difference) => {
switch (difference.type) {
case 'CHANGE': {
const { oldValue, path, value } = difference;
const property = path[0] as PToCompare;
const isJsonb = propertiesToStringify.includes(
property as unknown as PJsonB,
);
if (isJsonb) {
return {
from: parseJson(oldValue),
to: parseJson(value),
property,
};
}
return {
from: oldValue,
to: value,
property,
};
}
case 'CREATE':
case 'REMOVE':
default: {
// Should never occur, we should only provide null never undefined and so on
return [];
}
}
},
);
};
@@ -0,0 +1,40 @@
import { isDefined, removePropertiesFromRecord } from 'twenty-shared/utils';
import {
FlatEntityMapsException,
FlatEntityMapsExceptionCode,
} from 'src/engine/metadata-modules/flat-entity/exceptions/flat-entity-maps.exception';
import { type FlatEntityMaps } from 'src/engine/metadata-modules/flat-entity/types/flat-entity-maps.type';
import { type FlatEntity } from 'src/engine/metadata-modules/flat-entity/types/flat-entity.type';
export type DeleteFlatEntityFromFlatEntityMapsOrThrowArgs<
T extends FlatEntity,
> = {
entityToDeleteId: string;
flatEntityMaps: FlatEntityMaps<T>;
};
export const deleteFlatEntityFromFlatEntityMapsOrThrow = <
T extends FlatEntity,
>({
flatEntityMaps,
entityToDeleteId,
}: DeleteFlatEntityFromFlatEntityMapsOrThrowArgs<T>): FlatEntityMaps<T> => {
if (!isDefined(flatEntityMaps.byId[entityToDeleteId])) {
throw new FlatEntityMapsException(
'deleteFlatEntityFromFlatEntityMapsOrThrow: entity to delete not found',
FlatEntityMapsExceptionCode.ENTITY_NOT_FOUND,
);
}
const updatedIdByUniversalIdentifierEntries = Object.entries(
flatEntityMaps.idByUniversalIdentifier,
).filter(([_universalIdentifier, id]) => id !== entityToDeleteId);
return {
byId: removePropertiesFromRecord(flatEntityMaps.byId, [entityToDeleteId]),
idByUniversalIdentifier: Object.fromEntries(
updatedIdByUniversalIdentifierEntries,
),
};
};
@@ -0,0 +1,33 @@
import { t } from '@lingui/core/macro';
import { isDefined } from 'twenty-shared/utils';
import {
FlatEntityMapsException,
FlatEntityMapsExceptionCode,
} from 'src/engine/metadata-modules/flat-entity/exceptions/flat-entity-maps.exception';
import { type FlatEntityMaps } from 'src/engine/metadata-modules/flat-entity/types/flat-entity-maps.type';
import { type FlatEntity } from 'src/engine/metadata-modules/flat-entity/types/flat-entity.type';
export type FindFlatEntityByIdInFlatEntityMapsOrThrowArgs<
T extends FlatEntity,
> = {
flatEntityMaps: FlatEntityMaps<T>;
flatEntityId: string;
};
export const findFlatEntityByIdInFlatEntityMapsOrThrow = <
T extends FlatEntity,
>({
flatEntityMaps,
flatEntityId,
}: FindFlatEntityByIdInFlatEntityMapsOrThrowArgs<T>): T => {
const flatEntity = flatEntityMaps.byId[flatEntityId];
if (!isDefined(flatEntity)) {
throw new FlatEntityMapsException(
t`Could not find flat entity in maps`,
FlatEntityMapsExceptionCode.ENTITY_NOT_FOUND,
);
}
return flatEntity;
};
@@ -0,0 +1,15 @@
import { type FlatEntity } from 'src/engine/metadata-modules/flat-entity/types/flat-entity.type';
import {
findFlatEntityByIdInFlatEntityMapsOrThrow,
type FindFlatEntityByIdInFlatEntityMapsOrThrowArgs,
} from 'src/engine/metadata-modules/flat-entity/utils/find-flat-entity-by-id-in-flat-entity-maps-or-throw.util';
export const findFlatEntityByIdInFlatEntityMaps = <T extends FlatEntity>(
args: FindFlatEntityByIdInFlatEntityMapsOrThrowArgs<T>,
): T | undefined => {
try {
return findFlatEntityByIdInFlatEntityMapsOrThrow(args);
} catch {
return undefined;
}
};
@@ -0,0 +1,25 @@
import { isDefined } from 'twenty-shared/utils';
import { type FlatEntityMaps } from 'src/engine/metadata-modules/flat-entity/types/flat-entity-maps.type';
import { type FlatEntity } from 'src/engine/metadata-modules/flat-entity/types/flat-entity.type';
import { getSubFlatEntityMapsOrThrow } from 'src/engine/metadata-modules/flat-entity/utils/get-sub-flat-entity-maps-or-throw.util';
export type FindManyFlatEntityByIdInFlatEntityMapsOrThrowArgs<
T extends FlatEntity,
> = {
flatEntityMaps: FlatEntityMaps<T>;
flatEntityIds: string[];
};
export const findManyFlatEntityByIdInFlatEntityMapsOrThrow = <
T extends FlatEntity,
>({
flatEntityMaps,
flatEntityIds,
}: FindManyFlatEntityByIdInFlatEntityMapsOrThrowArgs<T>): T[] => {
const subFlatEntityMaps = getSubFlatEntityMapsOrThrow<T>({
flatEntityIds,
flatEntityMaps,
});
return Object.values(subFlatEntityMaps.byId).filter(isDefined);
};
@@ -0,0 +1,9 @@
import { capitalize } from 'twenty-shared/utils';
import { type AllMetadataName } from 'src/engine/metadata-modules/flat-entity/types/all-metadata-name.type';
import { type MetadataToFlatEntityMapsKey } from 'src/engine/metadata-modules/flat-entity/types/metadata-to-flat-entity-maps-key';
export const getMetadataFlatEntityMapsKey = <T extends AllMetadataName>(
metadataName: T,
): MetadataToFlatEntityMapsKey<T> =>
`flat${capitalize(metadataName)}Maps` as MetadataToFlatEntityMapsKey<T>;
@@ -0,0 +1,25 @@
import { EMPTY_FLAT_ENTITY_MAPS } from 'src/engine/metadata-modules/flat-entity/constant/empty-flat-entity-maps.constant';
import { type FlatEntityMaps } from 'src/engine/metadata-modules/flat-entity/types/flat-entity-maps.type';
import { type FlatEntity } from 'src/engine/metadata-modules/flat-entity/types/flat-entity.type';
import { addFlatEntityToFlatEntityMapsOrThrow } from 'src/engine/metadata-modules/flat-entity/utils/add-flat-entity-to-flat-entity-maps-or-throw.util';
import { findFlatEntityByIdInFlatEntityMapsOrThrow } from 'src/engine/metadata-modules/flat-entity/utils/find-flat-entity-by-id-in-flat-entity-maps-or-throw.util';
export const getSubFlatEntityMapsOrThrow = <T extends FlatEntity>({
flatEntityIds,
flatEntityMaps,
}: {
flatEntityMaps: FlatEntityMaps<T>;
flatEntityIds: string[];
}): FlatEntityMaps<T> => {
return flatEntityIds.reduce<FlatEntityMaps<T>>((acc, flatEntityId) => {
const flatEntity = findFlatEntityByIdInFlatEntityMapsOrThrow({
flatEntityId,
flatEntityMaps,
});
return addFlatEntityToFlatEntityMapsOrThrow({
flatEntity,
flatEntityMaps: acc,
});
}, EMPTY_FLAT_ENTITY_MAPS);
};
@@ -0,0 +1,25 @@
import { type FlatEntityMaps } from 'src/engine/metadata-modules/flat-entity/types/flat-entity-maps.type';
import { type FlatEntity } from 'src/engine/metadata-modules/flat-entity/types/flat-entity.type';
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';
export type ReplaceFlatEntityInFlatEntityMapsOrThrowArgs<T extends FlatEntity> =
{
flatEntity: T;
flatEntityMaps: FlatEntityMaps<T>;
};
export const replaceFlatEntityInFlatEntityMapsOrThrow = <T extends FlatEntity>({
flatEntity,
flatEntityMaps,
}: ReplaceFlatEntityInFlatEntityMapsOrThrowArgs<T>): FlatEntityMaps<T> => {
const flatEntityMapsToReplace = deleteFlatEntityFromFlatEntityMapsOrThrow({
flatEntityMaps,
entityToDeleteId: flatEntity.id,
});
return addFlatEntityToFlatEntityMapsOrThrow({
flatEntity,
flatEntityMaps: flatEntityMapsToReplace,
});
};
@@ -0,0 +1,37 @@
import { type FlatEntity } from 'src/engine/metadata-modules/flat-entity/types/flat-entity.type';
import { orderObjectProperties } from 'src/engine/workspace-manager/workspace-sync-metadata/comparators/utils/order-object-properties.util';
export function transformFlatEntityForComparison<
TFlatEntity extends FlatEntity,
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>,
);
}
@@ -1,12 +1,12 @@
import { jestExpectToBeDefined } from 'test/utils/expect-to-be-defined.util.test';
import { extractRecordIdsAndDatesAsExpectAny } from 'test/utils/extract-record-ids-and-dates-as-expect-any';
import { type EachTestingContext } from 'twenty-shared/testing';
import { FieldMetadataType } from 'twenty-shared/types';
import { jestExpectToBeDefined } from 'test/utils/expect-to-be-defined.util.test';
import { RelationType } from 'src/engine/metadata-modules/field-metadata/interfaces/relation-type.interface';
import { EMPTY_FLAT_ENTITY_MAPS } from 'src/engine/core-modules/common/constant/empty-flat-entity-maps.constant';
import { addFlatEntityToFlatEntityMapsOrThrow } from 'src/engine/core-modules/common/utils/add-flat-entity-to-flat-entity-maps-or-throw.util';
import { EMPTY_FLAT_ENTITY_MAPS } from 'src/engine/metadata-modules/flat-entity/constant/empty-flat-entity-maps.constant';
import { addFlatEntityToFlatEntityMapsOrThrow } from 'src/engine/metadata-modules/flat-entity/utils/add-flat-entity-to-flat-entity-maps-or-throw.util';
import {
fromCreateFieldInputToFlatFieldMetadatasToCreate,
type FromCreateFieldInputToFlatObjectMetadataArgs,
@@ -1,8 +0,0 @@
import { type FlatFieldMetadata } from 'src/engine/metadata-modules/flat-field-metadata/types/flat-field-metadata.type';
export const FLAT_FIELD_METADATA_JSONB_PROPERTIES = [
'options',
'settings',
'standardOverrides',
'defaultValue',
] as const satisfies (keyof FlatFieldMetadata)[];
@@ -1,7 +0,0 @@
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 = [
...FLAT_FIELD_METADATA_EDITABLE_PROPERTIES,
'standardOverrides',
] as const satisfies (keyof FlatFieldMetadata)[];
@@ -1,8 +1,8 @@
import { type FlatFieldMetadataPropertiesToCompare } from 'src/engine/metadata-modules/flat-field-metadata/types/flat-field-metadata-properties-to-compare.type';
import { type 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';
export const FLAT_FIELD_METADATA_RELATION_PROPERTIES_TO_COMPARE = [
'label',
'description',
'isActive',
'standardOverrides',
] as const satisfies FlatFieldMetadataPropertiesToCompare[];
] as const satisfies (typeof ALL_FLAT_ENTITY_PROPERTIES_TO_COMPARE_AND_STRINGIFY.fieldMetadata.propertiesToCompare)[number][];
@@ -2,7 +2,6 @@ import { Injectable } from '@nestjs/common';
import { msg } from '@lingui/core/macro';
import { isDefined } from 'class-validator';
import { FieldMetadataType } from 'twenty-shared/types';
import { FeatureFlagKey } from 'src/engine/core-modules/feature-flag/enums/feature-flag-key.enum';
import { FeatureFlagService } from 'src/engine/core-modules/feature-flag/services/feature-flag.service';
@@ -11,54 +10,44 @@ import { type FlatFieldMetadataTypeValidator } from 'src/engine/metadata-modules
import { FlatFieldMetadataValidationError } from 'src/engine/metadata-modules/flat-field-metadata/types/flat-field-metadata-validation-error.type';
import { validateEnumSelectFlatFieldMetadata } from 'src/engine/metadata-modules/flat-field-metadata/validators/utils/validate-enum-flat-field-metadata.util';
import { validateMorphOrRelationFlatFieldMetadata } from 'src/engine/metadata-modules/flat-field-metadata/validators/utils/validate-morph-or-relation-flat-field-metadata.util';
import { ValidateOneFieldMetadataArgs } from 'src/engine/workspace-manager/workspace-migration-v2/workspace-migration-builder-v2/validators/services/flat-field-metadata-validator.service';
import { FlatEntityValidationArgs } from 'src/engine/workspace-manager/workspace-migration-v2/workspace-migration-builder-v2/types/flat-entity-validation-args.type';
const DEFAULT_NO_VALIDATION = async (): Promise<
FlatFieldMetadataValidationError[]
> => [];
@Injectable()
export class FlatFieldMetadataTypeValidatorService {
constructor(private readonly featureFlagService: FeatureFlagService) {}
private readonly FIELD_METADATA_TYPE_VALIDATOR_HASHMAP: FlatFieldMetadataTypeValidator =
{
ACTOR: async (_args) => {
return [];
},
ADDRESS: async (_args) => {
return [];
},
ARRAY: async (_args) => {
return [];
},
BOOLEAN: async (_args) => {
return [];
},
CURRENCY: async (_args) => {
return [];
},
DATE: async (_args) => {
return [];
},
DATE_TIME: async (_args) => {
return [];
},
EMAILS: async (_args) => {
return [];
},
FULL_NAME: async (_args) => {
return [];
},
LINKS: async (_args) => {
return [];
},
MORPH_RELATION: async ({
dependencyOptimisticFlatEntityMaps,
flatFieldMetadataToValidate,
optimisticFlatFieldMetadataMaps,
remainingFlatEntityMapsToValidate,
workspaceId,
}) => {
ACTOR: DEFAULT_NO_VALIDATION,
ADDRESS: DEFAULT_NO_VALIDATION,
ARRAY: DEFAULT_NO_VALIDATION,
BOOLEAN: DEFAULT_NO_VALIDATION,
CURRENCY: DEFAULT_NO_VALIDATION,
DATE: DEFAULT_NO_VALIDATION,
DATE_TIME: DEFAULT_NO_VALIDATION,
EMAILS: DEFAULT_NO_VALIDATION,
FULL_NAME: DEFAULT_NO_VALIDATION,
LINKS: DEFAULT_NO_VALIDATION,
NUMBER: DEFAULT_NO_VALIDATION,
NUMERIC: DEFAULT_NO_VALIDATION,
PHONES: DEFAULT_NO_VALIDATION,
POSITION: DEFAULT_NO_VALIDATION,
RAW_JSON: DEFAULT_NO_VALIDATION,
RICH_TEXT: DEFAULT_NO_VALIDATION,
RICH_TEXT_V2: DEFAULT_NO_VALIDATION,
TEXT: DEFAULT_NO_VALIDATION,
TS_VECTOR: DEFAULT_NO_VALIDATION,
UUID: DEFAULT_NO_VALIDATION,
MORPH_RELATION: async (args) => {
const isMorphRelationEnabled =
await this.featureFlagService.isFeatureEnabled(
FeatureFlagKey.IS_MORPH_RELATION_ENABLED,
workspaceId,
args.workspaceId,
);
if (!isMorphRelationEnabled) {
@@ -71,122 +60,23 @@ export class FlatFieldMetadataTypeValidatorService {
];
}
return validateMorphOrRelationFlatFieldMetadata({
dependencyOptimisticFlatEntityMaps,
flatFieldMetadataToValidate,
optimisticFlatFieldMetadataMaps,
remainingFlatEntityMapsToValidate,
workspaceId,
});
},
MULTI_SELECT: ({
dependencyOptimisticFlatEntityMaps,
flatFieldMetadataToValidate,
optimisticFlatFieldMetadataMaps,
remainingFlatEntityMapsToValidate,
workspaceId,
}) =>
validateEnumSelectFlatFieldMetadata({
dependencyOptimisticFlatEntityMaps,
flatFieldMetadataToValidate,
optimisticFlatFieldMetadataMaps,
remainingFlatEntityMapsToValidate,
workspaceId,
}),
NUMBER: async (_args) => {
return [];
},
NUMERIC: async (_args) => {
return [];
},
PHONES: async (_args) => {
return [];
},
POSITION: async (_args) => {
return [];
},
RATING: ({
dependencyOptimisticFlatEntityMaps,
flatFieldMetadataToValidate,
optimisticFlatFieldMetadataMaps,
remainingFlatEntityMapsToValidate,
workspaceId,
}) =>
validateEnumSelectFlatFieldMetadata({
dependencyOptimisticFlatEntityMaps,
flatFieldMetadataToValidate,
optimisticFlatFieldMetadataMaps,
remainingFlatEntityMapsToValidate,
workspaceId,
}),
RAW_JSON: async (_args) => {
return [];
},
RELATION: ({
dependencyOptimisticFlatEntityMaps,
flatFieldMetadataToValidate,
optimisticFlatFieldMetadataMaps,
remainingFlatEntityMapsToValidate,
workspaceId,
}) =>
validateMorphOrRelationFlatFieldMetadata({
dependencyOptimisticFlatEntityMaps,
flatFieldMetadataToValidate,
optimisticFlatFieldMetadataMaps,
remainingFlatEntityMapsToValidate,
workspaceId,
}),
RICH_TEXT: async (_args) => {
return [];
},
RICH_TEXT_V2: async (_args) => {
return [];
},
SELECT: ({
dependencyOptimisticFlatEntityMaps,
flatFieldMetadataToValidate,
optimisticFlatFieldMetadataMaps,
remainingFlatEntityMapsToValidate,
workspaceId,
}) =>
validateEnumSelectFlatFieldMetadata({
dependencyOptimisticFlatEntityMaps,
flatFieldMetadataToValidate,
optimisticFlatFieldMetadataMaps,
remainingFlatEntityMapsToValidate,
workspaceId,
}),
TEXT: async (_args) => {
return [];
},
TS_VECTOR: async (_args) => {
return [];
},
UUID: async (_args) => {
return [];
return validateMorphOrRelationFlatFieldMetadata(args);
},
MULTI_SELECT: validateEnumSelectFlatFieldMetadata,
RATING: validateEnumSelectFlatFieldMetadata,
RELATION: validateMorphOrRelationFlatFieldMetadata,
SELECT: validateEnumSelectFlatFieldMetadata,
};
public async validateFlatFieldMetadataTypeSpecificities<
T extends FieldMetadataType = FieldMetadataType,
>({
dependencyOptimisticFlatEntityMaps,
flatFieldMetadataToValidate,
optimisticFlatFieldMetadataMaps,
remainingFlatEntityMapsToValidate,
workspaceId,
}: ValidateOneFieldMetadataArgs<T> & { workspaceId: string }): Promise<
FlatFieldMetadataValidationError[]
> {
public async validateFlatFieldMetadataTypeSpecificities(
args: FlatEntityValidationArgs<'fieldMetadata'>,
): Promise<FlatFieldMetadataValidationError[]> {
const { flatEntityToValidate } = args;
const fieldType = flatEntityToValidate.type;
const fieldMetadataTypeValidator =
this.FIELD_METADATA_TYPE_VALIDATOR_HASHMAP[
flatFieldMetadataToValidate.type
];
this.FIELD_METADATA_TYPE_VALIDATOR_HASHMAP[fieldType];
if (!isDefined(fieldMetadataTypeValidator)) {
const fieldType = flatFieldMetadataToValidate.type;
return [
{
code: FieldMetadataExceptionCode.UNCOVERED_FIELD_METADATA_TYPE_VALIDATION,
@@ -197,12 +87,9 @@ export class FlatFieldMetadataTypeValidatorService {
];
}
return await fieldMetadataTypeValidator({
dependencyOptimisticFlatEntityMaps,
flatFieldMetadataToValidate,
optimisticFlatFieldMetadataMaps,
remainingFlatEntityMapsToValidate,
workspaceId,
});
return await fieldMetadataTypeValidator(
// @ts-expect-error TODO could be improved
args,
);
}
}
@@ -6,10 +6,10 @@ import { Repository } from 'typeorm';
import { InjectCacheStorage } from 'src/engine/core-modules/cache-storage/decorators/cache-storage.decorator';
import { CacheStorageService } from 'src/engine/core-modules/cache-storage/services/cache-storage.service';
import { CacheStorageNamespace } from 'src/engine/core-modules/cache-storage/types/cache-storage-namespace.enum';
import { EMPTY_FLAT_ENTITY_MAPS } from 'src/engine/core-modules/common/constant/empty-flat-entity-maps.constant';
import { FlatEntityMaps } from 'src/engine/core-modules/common/types/flat-entity-maps.type';
import { addFlatEntityToFlatEntityMapsOrThrow } from 'src/engine/core-modules/common/utils/add-flat-entity-to-flat-entity-maps-or-throw.util';
import { FieldMetadataEntity } from 'src/engine/metadata-modules/field-metadata/field-metadata.entity';
import { EMPTY_FLAT_ENTITY_MAPS } from 'src/engine/metadata-modules/flat-entity/constant/empty-flat-entity-maps.constant';
import { FlatEntityMaps } from 'src/engine/metadata-modules/flat-entity/types/flat-entity-maps.type';
import { addFlatEntityToFlatEntityMapsOrThrow } from 'src/engine/metadata-modules/flat-entity/utils/add-flat-entity-to-flat-entity-maps-or-throw.util';
import { FlatFieldMetadata } from 'src/engine/metadata-modules/flat-field-metadata/types/flat-field-metadata.type';
import { fromFieldMetadataEntityToFlatFieldMetadata } from 'src/engine/metadata-modules/flat-field-metadata/utils/from-field-metadata-entity-to-flat-field-metadata.util';
import { WorkspaceFlatMapCache } from 'src/engine/workspace-flat-map-cache/decorators/workspace-flat-map-cache.decorator';
@@ -1,4 +0,0 @@
import { type FLAT_FIELD_METADATA_JSONB_PROPERTIES } from 'src/engine/metadata-modules/flat-field-metadata/constants/flat-field-metadata-jsonb-properties.constant';
export type FlatFieldMetadataEntityJsonbProperties =
(typeof FLAT_FIELD_METADATA_JSONB_PROPERTIES)[number];
@@ -1,4 +0,0 @@
import { type FLAT_FIELD_METADATA_PROPERTIES_TO_COMPARE } from 'src/engine/metadata-modules/flat-field-metadata/constants/flat-field-metadata-properties-to-compare.constant';
export type FlatFieldMetadataPropertiesToCompare =
(typeof FLAT_FIELD_METADATA_PROPERTIES_TO_COMPARE)[number];
@@ -1,11 +1,17 @@
import { type FieldMetadataType } from 'twenty-shared/types';
import { type FlatFieldMetadataValidationError } from 'src/engine/metadata-modules/flat-field-metadata/types/flat-field-metadata-validation-error.type';
import { type ValidateOneFieldMetadataArgs } from 'src/engine/workspace-manager/workspace-migration-v2/workspace-migration-builder-v2/validators/services/flat-field-metadata-validator.service';
import { type FlatFieldMetadata } from 'src/engine/metadata-modules/flat-field-metadata/types/flat-field-metadata.type';
import { type FlatEntityValidationArgs } from 'src/engine/workspace-manager/workspace-migration-v2/workspace-migration-builder-v2/types/flat-entity-validation-args.type';
export type FlatFieldMetadataTypeValidationArgs<T extends FieldMetadataType> =
Omit<FlatEntityValidationArgs<'fieldMetadata'>, 'flatEntityToValidate'> & {
flatEntityToValidate: FlatFieldMetadata<T>;
};
export type FlatFieldMetadataTypeValidator = {
[P in FieldMetadataType]: (
args: ValidateOneFieldMetadataArgs<P> & { workspaceId: string },
[T in FieldMetadataType]: (
args: FlatFieldMetadataTypeValidationArgs<T>,
) =>
| FlatFieldMetadataValidationError[]
| Promise<FlatFieldMetadataValidationError[]>;
@@ -1,94 +0,0 @@
import diff from 'microdiff';
import { type FromTo } from 'twenty-shared/types';
import { parseJson } from 'twenty-shared/utils';
import { FLAT_FIELD_METADATA_JSONB_PROPERTIES } from 'src/engine/metadata-modules/flat-field-metadata/constants/flat-field-metadata-jsonb-properties.constant';
import { FLAT_FIELD_METADATA_PROPERTIES_TO_COMPARE } from 'src/engine/metadata-modules/flat-field-metadata/constants/flat-field-metadata-properties-to-compare.constant';
import { type FlatFieldMetadataPropertiesToCompare } from 'src/engine/metadata-modules/flat-field-metadata/types/flat-field-metadata-properties-to-compare.type';
import { type FlatFieldMetadata } from 'src/engine/metadata-modules/flat-field-metadata/types/flat-field-metadata.type';
import { isFlatFieldMetadataJsonbProperty } from 'src/engine/metadata-modules/flat-field-metadata/utils/is-flat-field-metadata-jsonb-property.util';
import { isStandardMetadata } from 'src/engine/metadata-modules/utils/is-standard-metadata.util';
import { type UpdateFieldAction } from 'src/engine/workspace-manager/workspace-migration-v2/workspace-migration-builder-v2/types/workspace-migration-field-action-v2';
import { transformMetadataForComparison } from 'src/engine/workspace-manager/workspace-sync-metadata/comparators/utils/transform-metadata-for-comparison.util';
type GetWorkspaceMigrationUpdateFieldActionArgs = FromTo<
FlatFieldMetadata,
'FlatFieldMetadata'
>;
/**
* This comparator handles update on colliding universalIdentifier flatFieldMetadata
*/
export const compareTwoFlatFieldMetadata = ({
fromFlatFieldMetadata,
toFlatFieldMetadata,
}: GetWorkspaceMigrationUpdateFieldActionArgs) => {
const transformMetadataForComparisonParameters = {
shouldIgnoreProperty: (
property: string,
fieldMetadata: FlatFieldMetadata,
) => {
if (
!FLAT_FIELD_METADATA_PROPERTIES_TO_COMPARE.includes(
property as FlatFieldMetadataPropertiesToCompare,
)
) {
return true;
}
if (
isStandardMetadata(fieldMetadata) &&
property !== 'standardOverrides'
) {
return true;
}
return false;
},
propertiesToStringify: FLAT_FIELD_METADATA_JSONB_PROPERTIES,
};
const fromCompare = transformMetadataForComparison(
fromFlatFieldMetadata,
transformMetadataForComparisonParameters,
);
const toCompare = transformMetadataForComparison(
toFlatFieldMetadata,
transformMetadataForComparisonParameters,
);
const flatFieldMetadataDifferences = diff(fromCompare, toCompare);
return flatFieldMetadataDifferences.flatMap<
UpdateFieldAction['updates'][number]
>((difference) => {
switch (difference.type) {
case 'CHANGE': {
const { oldValue, path, value } = difference;
const property = path[0] as FlatFieldMetadataPropertiesToCompare;
const isJsonb = isFlatFieldMetadataJsonbProperty({
flatFieldMetadata: toFlatFieldMetadata,
property,
});
if (isJsonb) {
return {
from: parseJson(oldValue),
to: parseJson(value),
property,
};
}
return {
from: oldValue,
to: value,
property,
};
}
case 'CREATE':
case 'REMOVE':
default: {
// Should never occurs, we should only provide null never undefined and so on
return [];
}
}
});
};
@@ -1,6 +1,6 @@
import { FieldMetadataType } from 'twenty-shared/types';
import { type FlatEntityMaps } from 'src/engine/core-modules/common/types/flat-entity-maps.type';
import { type FlatEntityMaps } from 'src/engine/metadata-modules/flat-entity/types/flat-entity-maps.type';
import { type FlatFieldMetadata } from 'src/engine/metadata-modules/flat-field-metadata/types/flat-field-metadata.type';
import { findFlatFieldMetadatasRelatedToMorphRelationOrThrow } from 'src/engine/metadata-modules/flat-field-metadata/utils/find-flat-field-metadatas-related-to-morph-relation-or-throw.util';
import { findRelationFlatFieldMetadataTargetFlatFieldMetadataOrThrow } from 'src/engine/metadata-modules/flat-field-metadata/utils/find-relation-flat-field-metadatas-target-flat-field-metadata-or-throw.util';
@@ -4,9 +4,9 @@ import { isDefined } from 'twenty-shared/utils';
import {
FlatEntityMapsException,
FlatEntityMapsExceptionCode,
} from 'src/engine/core-modules/common/exceptions/flat-entity-maps.exception';
import { type FlatEntityMaps } from 'src/engine/core-modules/common/types/flat-entity-maps.type';
import { findManyFlatEntityByIdInFlatEntityMapsOrThrow } from 'src/engine/core-modules/common/utils/find-many-flat-entity-by-id-in-flat-entity-maps-or-throw.util';
} from 'src/engine/metadata-modules/flat-entity/exceptions/flat-entity-maps.exception';
import { type FlatEntityMaps } from 'src/engine/metadata-modules/flat-entity/types/flat-entity-maps.type';
import { findManyFlatEntityByIdInFlatEntityMapsOrThrow } from 'src/engine/metadata-modules/flat-entity/utils/find-many-flat-entity-by-id-in-flat-entity-maps-or-throw.util';
import { type FlatFieldMetadata } from 'src/engine/metadata-modules/flat-field-metadata/types/flat-field-metadata.type';
import { isFlatFieldMetadataOfType } from 'src/engine/metadata-modules/flat-field-metadata/utils/is-flat-field-metadata-of-type.util';
import { type FlatObjectMetadata } from 'src/engine/metadata-modules/flat-object-metadata/types/flat-object-metadata.type';
@@ -1,7 +1,7 @@
import { type FieldMetadataType } from 'twenty-shared/types';
import { type FlatEntityMaps } from 'src/engine/core-modules/common/types/flat-entity-maps.type';
import { type MorphOrRelationFieldMetadataType } from 'src/engine/metadata-modules/field-metadata/types/morph-or-relation-field-metadata-type.type';
import { type FlatEntityMaps } from 'src/engine/metadata-modules/flat-entity/types/flat-entity-maps.type';
import { type FlatFieldMetadata } from 'src/engine/metadata-modules/flat-field-metadata/types/flat-field-metadata.type';
import { findAllOthersMorphRelationFlatFieldMetadatasOrThrow } from 'src/engine/metadata-modules/flat-field-metadata/utils/find-all-others-morph-relation-flat-field-metadatas-or-throw.util';
import { findRelationFlatFieldMetadataTargetFlatFieldMetadataOrThrow } from 'src/engine/metadata-modules/flat-field-metadata/utils/find-relation-flat-field-metadatas-target-flat-field-metadata-or-throw.util';
@@ -1,11 +1,11 @@
import { isDefined } from 'twenty-shared/utils';
import { type FlatEntityMaps } from 'src/engine/core-modules/common/types/flat-entity-maps.type';
import {
FieldMetadataException,
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 { type FlatEntityMaps } from 'src/engine/metadata-modules/flat-entity/types/flat-entity-maps.type';
import { type FlatFieldMetadata } from 'src/engine/metadata-modules/flat-field-metadata/types/flat-field-metadata.type';
import { isMorphOrRelationFlatFieldMetadata } from 'src/engine/metadata-modules/flat-field-metadata/utils/is-morph-or-relation-flat-field-metadata.util';
@@ -9,10 +9,10 @@ import { v4 } from 'uuid';
import { type FieldMetadataOptions } from 'src/engine/metadata-modules/field-metadata/interfaces/field-metadata-options.interface';
import { type AllFlatEntityMaps } from 'src/engine/core-modules/common/types/all-flat-entity-maps.type';
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 { generateRatingOptions } from 'src/engine/metadata-modules/field-metadata/utils/generate-rating-optionts.util';
import { type AllFlatEntityMaps } from 'src/engine/metadata-modules/flat-entity/types/all-flat-entity-maps.type';
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 { fromMorphRelationCreateFieldInputToFlatFieldMetadatas } from 'src/engine/metadata-modules/flat-field-metadata/utils/from-morph-relation-create-field-input-to-flat-field-metadatas.util';
@@ -3,15 +3,15 @@ import {
trimAndRemoveDuplicatedWhitespacesFromObjectStringProperties,
} from 'twenty-shared/utils';
import { type AllFlatEntityMaps } from 'src/engine/core-modules/common/types/all-flat-entity-maps.type';
import { findFlatEntityByIdInFlatEntityMapsOrThrow } from 'src/engine/core-modules/common/utils/find-flat-entity-by-id-in-flat-entity-maps-or-throw.util';
import { findFlatEntityByIdInFlatEntityMaps } from 'src/engine/core-modules/common/utils/find-flat-entity-by-id-in-flat-entity-maps.util';
import { findManyFlatEntityByIdInFlatEntityMapsOrThrow } from 'src/engine/core-modules/common/utils/find-many-flat-entity-by-id-in-flat-entity-maps-or-throw.util';
import { type DeleteOneFieldInput } from 'src/engine/metadata-modules/field-metadata/dtos/delete-field.input';
import {
FieldMetadataException,
FieldMetadataExceptionCode,
} from 'src/engine/metadata-modules/field-metadata/field-metadata.exception';
import { type AllFlatEntityMaps } from 'src/engine/metadata-modules/flat-entity/types/all-flat-entity-maps.type';
import { findFlatEntityByIdInFlatEntityMapsOrThrow } from 'src/engine/metadata-modules/flat-entity/utils/find-flat-entity-by-id-in-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 { findManyFlatEntityByIdInFlatEntityMapsOrThrow } from 'src/engine/metadata-modules/flat-entity/utils/find-many-flat-entity-by-id-in-flat-entity-maps-or-throw.util';
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';
@@ -3,10 +3,10 @@ import { type FieldMetadataType } from 'twenty-shared/types';
import { computeMorphRelationFieldName, isDefined } from 'twenty-shared/utils';
import { v4 } from 'uuid';
import { type FlatEntityMaps } from 'src/engine/core-modules/common/types/flat-entity-maps.type';
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 { computeMorphOrRelationFieldJoinColumnName } from 'src/engine/metadata-modules/field-metadata/utils/compute-morph-or-relation-field-join-column-name.util';
import { type FlatEntityMaps } from 'src/engine/metadata-modules/flat-entity/types/flat-entity-maps.type';
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';
@@ -2,10 +2,10 @@ import { msg } from '@lingui/core/macro';
import { type FieldMetadataType } from 'twenty-shared/types';
import { isDefined } from 'twenty-shared/utils';
import { type FlatEntityMaps } from 'src/engine/core-modules/common/types/flat-entity-maps.type';
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 { computeMorphOrRelationFieldJoinColumnName } from 'src/engine/metadata-modules/field-metadata/utils/compute-morph-or-relation-field-join-column-name.util';
import { type FlatEntityMaps } from 'src/engine/metadata-modules/flat-entity/types/flat-entity-maps.type';
import { type FieldInputTranspilationResult } from 'src/engine/metadata-modules/flat-field-metadata/types/field-input-transpilation-result.type';
import {
generateMorphOrRelationFlatFieldMetadataPair,
@@ -5,9 +5,6 @@ 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 { findFlatEntityByIdInFlatEntityMapsOrThrow } from 'src/engine/core-modules/common/utils/find-flat-entity-by-id-in-flat-entity-maps-or-throw.util';
import { findFlatEntityByIdInFlatEntityMaps } from 'src/engine/core-modules/common/utils/find-flat-entity-by-id-in-flat-entity-maps.util';
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 {
@@ -15,6 +12,9 @@ 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 { type AllFlatEntityMaps } from 'src/engine/metadata-modules/flat-entity/types/all-flat-entity-maps.type';
import { findFlatEntityByIdInFlatEntityMapsOrThrow } from 'src/engine/metadata-modules/flat-entity/utils/find-flat-entity-by-id-in-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 { 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 FlatFieldMetadataEditableProperties } from 'src/engine/metadata-modules/flat-field-metadata/types/flat-field-metadata-editable-properties.constant';
@@ -1,34 +0,0 @@
import { FieldMetadataType } from 'twenty-shared/types';
import { FLAT_FIELD_METADATA_JSONB_PROPERTIES } from 'src/engine/metadata-modules/flat-field-metadata/constants/flat-field-metadata-jsonb-properties.constant';
import { type FlatFieldMetadata } from 'src/engine/metadata-modules/flat-field-metadata/types/flat-field-metadata.type';
import { isFlatFieldMetadataOfTypes } from 'src/engine/metadata-modules/flat-field-metadata/utils/is-flat-field-metadata-of-types.util';
export const isFlatFieldMetadataJsonbProperty = ({
flatFieldMetadata,
property,
}: {
flatFieldMetadata: FlatFieldMetadata;
property: keyof FlatFieldMetadata;
}): boolean => {
if (property === 'defaultValue') {
return isFlatFieldMetadataOfTypes(flatFieldMetadata, [
FieldMetadataType.LINKS,
FieldMetadataType.PHONES,
FieldMetadataType.EMAILS,
FieldMetadataType.CURRENCY,
FieldMetadataType.FULL_NAME,
FieldMetadataType.ADDRESS,
FieldMetadataType.MULTI_SELECT,
FieldMetadataType.RAW_JSON,
FieldMetadataType.ACTOR,
FieldMetadataType.ARRAY,
]);
}
const isJsonB = FLAT_FIELD_METADATA_JSONB_PROPERTIES.includes(
property as (typeof FLAT_FIELD_METADATA_JSONB_PROPERTIES)[number],
);
return isJsonB;
};
@@ -1,7 +1,7 @@
import { isDefined } from 'twenty-shared/utils';
import { type AllFlatEntityMaps } from 'src/engine/core-modules/common/types/all-flat-entity-maps.type';
import { findManyFlatEntityByIdInFlatEntityMapsOrThrow } from 'src/engine/core-modules/common/utils/find-many-flat-entity-by-id-in-flat-entity-maps-or-throw.util';
import { type AllFlatEntityMaps } from 'src/engine/metadata-modules/flat-entity/types/all-flat-entity-maps.type';
import { findManyFlatEntityByIdInFlatEntityMapsOrThrow } from 'src/engine/metadata-modules/flat-entity/utils/find-many-flat-entity-by-id-in-flat-entity-maps-or-throw.util';
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';
@@ -16,6 +16,7 @@ import {
type FieldMetadataDefaultOption,
} from 'src/engine/metadata-modules/field-metadata/dtos/options.input';
import { FieldMetadataExceptionCode } from 'src/engine/metadata-modules/field-metadata/field-metadata.exception';
import { type FlatFieldMetadataTypeValidationArgs } from 'src/engine/metadata-modules/flat-field-metadata/types/flat-field-metadata-type-validator.type';
import { type FlatFieldMetadataValidationError } from 'src/engine/metadata-modules/flat-field-metadata/types/flat-field-metadata-validation-error.type';
import { type FlatFieldMetadata } from 'src/engine/metadata-modules/flat-field-metadata/types/flat-field-metadata.type';
import { runFlatFieldMetadataValidators } from 'src/engine/metadata-modules/flat-field-metadata/utils/run-flat-field-metadata-validators.util';
@@ -24,7 +25,6 @@ import {
beneathDatabaseIdentifierMinimumLength,
exceedsDatabaseIdentifierMaximumLength,
} from 'src/engine/metadata-modules/utils/validate-database-identifier-length.utils';
import { type ValidateOneFieldMetadataArgs } from 'src/engine/workspace-manager/workspace-migration-v2/workspace-migration-builder-v2/validators/services/flat-field-metadata-validator.service';
import { isSnakeCaseString } from 'src/utils/is-snake-case-string';
const validateMetadataOptionId = (sanitizedId?: string) => {
@@ -305,8 +305,8 @@ const validateFieldMetadataDefaultValue = ({
};
export const validateEnumSelectFlatFieldMetadata = ({
flatFieldMetadataToValidate,
}: ValidateOneFieldMetadataArgs<EnumFieldMetadataType>): FlatFieldMetadataValidationError[] => {
flatEntityToValidate: flatFieldMetadataToValidate,
}: FlatFieldMetadataTypeValidationArgs<EnumFieldMetadataType>): FlatFieldMetadataValidationError[] => {
const optionsValidationErrors = validateFieldMetadataInputOptions(
flatFieldMetadataToValidate,
);
@@ -2,13 +2,13 @@ import { msg } from '@lingui/core/macro';
import { FieldMetadataType } from 'twenty-shared/types';
import { isDefined } from 'twenty-shared/utils';
import { type FlatEntityMaps } from 'src/engine/core-modules/common/types/flat-entity-maps.type';
import { findFlatEntityByIdInFlatEntityMapsOrThrow } from 'src/engine/core-modules/common/utils/find-flat-entity-by-id-in-flat-entity-maps-or-throw.util';
import { findManyFlatEntityByIdInFlatEntityMapsOrThrow } from 'src/engine/core-modules/common/utils/find-many-flat-entity-by-id-in-flat-entity-maps-or-throw.util';
import { compositeTypeDefinitions } from 'src/engine/metadata-modules/field-metadata/composite-types';
import { FieldMetadataExceptionCode } from 'src/engine/metadata-modules/field-metadata/field-metadata.exception';
import { computeCompositeColumnName } from 'src/engine/metadata-modules/field-metadata/utils/compute-column-name.util';
import { isCompositeFieldMetadataType } from 'src/engine/metadata-modules/field-metadata/utils/is-composite-field-metadata-type.util';
import { type FlatEntityMaps } from 'src/engine/metadata-modules/flat-entity/types/flat-entity-maps.type';
import { findFlatEntityByIdInFlatEntityMapsOrThrow } from 'src/engine/metadata-modules/flat-entity/utils/find-flat-entity-by-id-in-flat-entity-maps-or-throw.util';
import { findManyFlatEntityByIdInFlatEntityMapsOrThrow } from 'src/engine/metadata-modules/flat-entity/utils/find-many-flat-entity-by-id-in-flat-entity-maps-or-throw.util';
import { type FlatFieldMetadataValidationError } from 'src/engine/metadata-modules/flat-field-metadata/types/flat-field-metadata-validation-error.type';
import { type FlatFieldMetadata } from 'src/engine/metadata-modules/flat-field-metadata/types/flat-field-metadata.type';
import { isFlatFieldMetadataOfType } from 'src/engine/metadata-modules/flat-field-metadata/utils/is-flat-field-metadata-of-type.util';
@@ -3,15 +3,15 @@ import { isDefined, isValidUuid } from 'twenty-shared/utils';
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 { type FlatFieldMetadataTypeValidationArgs } from 'src/engine/metadata-modules/flat-field-metadata/types/flat-field-metadata-type-validator.type';
import { type FlatFieldMetadataValidationError } from 'src/engine/metadata-modules/flat-field-metadata/types/flat-field-metadata-validation-error.type';
import { type ValidateOneFieldMetadataArgs } from 'src/engine/workspace-manager/workspace-migration-v2/workspace-migration-builder-v2/validators/services/flat-field-metadata-validator.service';
export const validateMorphOrRelationFlatFieldMetadata = async ({
dependencyOptimisticFlatEntityMaps: { flatObjectMetadataMaps },
flatFieldMetadataToValidate,
optimisticFlatFieldMetadataMaps,
flatEntityToValidate: flatFieldMetadataToValidate,
optimisticFlatEntityMaps: optimisticFlatFieldMetadataMaps,
remainingFlatEntityMapsToValidate,
}: ValidateOneFieldMetadataArgs<MorphOrRelationFieldMetadataType>): Promise<
}: FlatFieldMetadataTypeValidationArgs<MorphOrRelationFieldMetadataType>): Promise<
FlatFieldMetadataValidationError[]
> => {
const { relationTargetFieldMetadataId, relationTargetObjectMetadataId } =
@@ -2,8 +2,8 @@ import { msg } from '@lingui/core/macro';
import { isDefined } from 'class-validator';
import { type RelationCreationPayload } from 'twenty-shared/types';
import { type FlatEntityMaps } from 'src/engine/core-modules/common/types/flat-entity-maps.type';
import { FieldMetadataExceptionCode } from 'src/engine/metadata-modules/field-metadata/field-metadata.exception';
import { type FlatEntityMaps } from 'src/engine/metadata-modules/flat-entity/types/flat-entity-maps.type';
import {
type FailedFieldInputTranspilation,
type FieldInputTranspilationResult,
@@ -5,12 +5,12 @@ import {
trimAndRemoveDuplicatedWhitespacesFromObjectStringProperties,
} from 'twenty-shared/utils';
import { type FlatEntityMaps } from 'src/engine/core-modules/common/types/flat-entity-maps.type';
import {
FieldMetadataException,
FieldMetadataExceptionCode,
} from 'src/engine/metadata-modules/field-metadata/field-metadata.exception';
import { validateRelationCreationPayloadOrThrow } from 'src/engine/metadata-modules/field-metadata/utils/validate-relation-creation-payload-or-throw.util';
import { type FlatEntityMaps } from 'src/engine/metadata-modules/flat-entity/types/flat-entity-maps.type';
import { type FieldInputTranspilationResult } from 'src/engine/metadata-modules/flat-field-metadata/types/field-input-transpilation-result.type';
import { type FlatObjectMetadata } from 'src/engine/metadata-modules/flat-object-metadata/types/flat-object-metadata.type';
@@ -1,9 +0,0 @@
import { type FlatIndexMetadata } from 'src/engine/metadata-modules/flat-index-metadata/types/flat-index-metadata.type';
export const FLAT_INDEX_METADATA_PROPERTIES_TO_COMPARE = [
'indexType',
'indexWhereClause',
'flatIndexFieldMetadatas',
'isUnique',
'name',
] as const satisfies (keyof FlatIndexMetadata)[];
@@ -6,9 +6,9 @@ import { Repository } from 'typeorm';
import { InjectCacheStorage } from 'src/engine/core-modules/cache-storage/decorators/cache-storage.decorator';
import { CacheStorageService } from 'src/engine/core-modules/cache-storage/services/cache-storage.service';
import { CacheStorageNamespace } from 'src/engine/core-modules/cache-storage/types/cache-storage-namespace.enum';
import { EMPTY_FLAT_ENTITY_MAPS } from 'src/engine/core-modules/common/constant/empty-flat-entity-maps.constant';
import { FlatEntityMaps } from 'src/engine/core-modules/common/types/flat-entity-maps.type';
import { addFlatEntityToFlatEntityMapsOrThrow } from 'src/engine/core-modules/common/utils/add-flat-entity-to-flat-entity-maps-or-throw.util';
import { EMPTY_FLAT_ENTITY_MAPS } from 'src/engine/metadata-modules/flat-entity/constant/empty-flat-entity-maps.constant';
import { FlatEntityMaps } from 'src/engine/metadata-modules/flat-entity/types/flat-entity-maps.type';
import { addFlatEntityToFlatEntityMapsOrThrow } from 'src/engine/metadata-modules/flat-entity/utils/add-flat-entity-to-flat-entity-maps-or-throw.util';
import { FlatIndexMetadata } from 'src/engine/metadata-modules/flat-index-metadata/types/flat-index-metadata.type';
import { fromIndexMetadataEntityToFlatIndexMetadata } from 'src/engine/metadata-modules/flat-index-metadata/utils/from-index-metadata-entity-to-flat-index-metadata.util';
import { IndexMetadataEntity } from 'src/engine/metadata-modules/index-metadata/index-metadata.entity';
@@ -1,4 +0,0 @@
import { type FLAT_INDEX_METADATA_PROPERTIES_TO_COMPARE } from 'src/engine/metadata-modules/flat-index-metadata/constants/flat-index-metadata-properties-to-compare.constant';
export type FlatIndexMetadataPropertiesToCompare =
(typeof FLAT_INDEX_METADATA_PROPERTIES_TO_COMPARE)[number];
@@ -1,61 +0,0 @@
import diff from 'microdiff';
import { type FromTo } from 'twenty-shared/types';
import { parseJson } from 'twenty-shared/utils';
import { FLAT_INDEX_METADATA_PROPERTIES_TO_COMPARE } from 'src/engine/metadata-modules/flat-index-metadata/constants/flat-index-metadata-properties-to-compare.constant';
import { type FlatIndexMetadataPropertiesToCompare } from 'src/engine/metadata-modules/flat-index-metadata/types/flat-index-metadata-properties-to-compare.type';
import { type FlatIndexMetadata } from 'src/engine/metadata-modules/flat-index-metadata/types/flat-index-metadata.type';
import { transformMetadataForComparison } from 'src/engine/workspace-manager/workspace-sync-metadata/comparators/utils/transform-metadata-for-comparison.util';
export const compareTwoFlatIndexMetadata = ({
fromFlatIndexMetadata,
toFlatIndexMetadata,
}: FromTo<FlatIndexMetadata, 'flatIndexMetadata'>) => {
const transformOptions = {
propertiesToStringify: ['flatIndexFieldMetadatas'] as const,
shouldIgnoreProperty: (property: string) =>
!FLAT_INDEX_METADATA_PROPERTIES_TO_COMPARE.includes(
property as FlatIndexMetadataPropertiesToCompare,
),
};
const fromCompare = transformMetadataForComparison(
fromFlatIndexMetadata,
transformOptions,
);
const toCompare = transformMetadataForComparison(
toFlatIndexMetadata,
transformOptions,
);
const flatIndexeDifferences = diff(fromCompare, toCompare);
return flatIndexeDifferences.flatMap<{ property: string } & FromTo<unknown>>(
(difference) => {
switch (difference.type) {
case 'CHANGE': {
const { oldValue, path, value } = difference;
const property = path[0] as FlatIndexMetadataPropertiesToCompare;
if (property === 'flatIndexFieldMetadatas') {
return {
from: parseJson(oldValue),
to: parseJson(value),
property,
};
}
return {
from: oldValue,
to: value,
property,
};
}
case 'CREATE':
case 'REMOVE':
default:
return [];
}
},
);
};
@@ -1,5 +1,5 @@
import { EMPTY_FLAT_ENTITY_MAPS } from 'src/engine/core-modules/common/constant/empty-flat-entity-maps.constant';
import { addFlatEntityToFlatEntityMapsOrThrow } from 'src/engine/core-modules/common/utils/add-flat-entity-to-flat-entity-maps-or-throw.util';
import { EMPTY_FLAT_ENTITY_MAPS } from 'src/engine/metadata-modules/flat-entity/constant/empty-flat-entity-maps.constant';
import { addFlatEntityToFlatEntityMapsOrThrow } from 'src/engine/metadata-modules/flat-entity/utils/add-flat-entity-to-flat-entity-maps-or-throw.util';
import { ALL_FLAT_OBJECT_METADATA_MOCKS } from 'src/engine/metadata-modules/flat-object-metadata/__mocks__/all-flat-object-metadatas.mock';
export const FLAT_OBJECT_METADATA_MAPS_MOCKS = [
@@ -1,5 +0,0 @@
import { type FlatObjectMetadata } from 'src/engine/metadata-modules/flat-object-metadata/types/flat-object-metadata.type';
export const FLAT_OBJECT_METADATA_JSONB_PROPERTIES = [
'standardOverrides',
] as const satisfies (keyof FlatObjectMetadata)[];
@@ -1,14 +0,0 @@
import { type FlatObjectMetadata } from 'src/engine/metadata-modules/flat-object-metadata/types/flat-object-metadata.type';
export const FLAT_OBJECT_METADATA_PROPERTIES_TO_COMPARE = [
'description',
'icon',
'isActive',
'isLabelSyncedWithName',
'labelPlural',
'labelSingular',
'namePlural',
'nameSingular',
'standardOverrides', // Only if standard
'labelIdentifierFieldMetadataId',
] as const satisfies (keyof FlatObjectMetadata)[];
@@ -6,8 +6,8 @@ import { Repository } from 'typeorm';
import { InjectCacheStorage } from 'src/engine/core-modules/cache-storage/decorators/cache-storage.decorator';
import { CacheStorageService } from 'src/engine/core-modules/cache-storage/services/cache-storage.service';
import { CacheStorageNamespace } from 'src/engine/core-modules/cache-storage/types/cache-storage-namespace.enum';
import { EMPTY_FLAT_ENTITY_MAPS } from 'src/engine/core-modules/common/constant/empty-flat-entity-maps.constant';
import { FlatEntityMaps } from 'src/engine/core-modules/common/types/flat-entity-maps.type';
import { EMPTY_FLAT_ENTITY_MAPS } from 'src/engine/metadata-modules/flat-entity/constant/empty-flat-entity-maps.constant';
import { FlatEntityMaps } from 'src/engine/metadata-modules/flat-entity/types/flat-entity-maps.type';
import { FlatObjectMetadata } from 'src/engine/metadata-modules/flat-object-metadata/types/flat-object-metadata.type';
import { fromObjectMetadataEntityToFlatObjectMetadata } from 'src/engine/metadata-modules/flat-object-metadata/utils/from-object-metadata-entity-to-flat-object-metadata.util';
import { ObjectMetadataEntity } from 'src/engine/metadata-modules/object-metadata/object-metadata.entity';
@@ -1,4 +0,0 @@
import { type FLAT_OBJECT_METADATA_JSONB_PROPERTIES } from 'src/engine/metadata-modules/flat-object-metadata/constants/flat-object-metadata-jsonb-properties.constant';
export type FlatObjectMetadataEntityJsonbProperties =
(typeof FLAT_OBJECT_METADATA_JSONB_PROPERTIES)[number];
@@ -1,4 +0,0 @@
import { type FLAT_OBJECT_METADATA_PROPERTIES_TO_COMPARE } from 'src/engine/metadata-modules/flat-object-metadata/constants/flat-object-metadata-properties-to-compare.constant';
export type FlatObjectMetadataPropertiesToCompare =
(typeof FLAT_OBJECT_METADATA_PROPERTIES_TO_COMPARE)[number];
@@ -1,91 +0,0 @@
import omit from 'lodash.omit';
import diff from 'microdiff';
import { type FromTo } from 'twenty-shared/types';
import { assertUnreachable, parseJson } from 'twenty-shared/utils';
import { FLAT_OBJECT_METADATA_JSONB_PROPERTIES } from 'src/engine/metadata-modules/flat-object-metadata/constants/flat-object-metadata-jsonb-properties.constant';
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 FlatObjectMetadataEntityJsonbProperties } from 'src/engine/metadata-modules/flat-object-metadata/types/flat-object-metadata-jsonb-properties.type';
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 { isStandardMetadata } from 'src/engine/metadata-modules/utils/is-standard-metadata.util';
import { type FlatObjectPropertiesUpdates } from 'src/engine/workspace-manager/workspace-migration-v2/workspace-migration-builder-v2/types/workspace-migration-object-action-v2';
import { transformMetadataForComparison } from 'src/engine/workspace-manager/workspace-sync-metadata/comparators/utils/transform-metadata-for-comparison.util';
export const compareTwoFlatObjectMetadata = ({
fromFlatObjectMetadata,
toFlatObjectMetadata,
}: FromTo<FlatObjectMetadata, 'flatObjectMetadata'>) => {
const transformMetadataForComparisonParameters = {
propertiesToStringify: FLAT_OBJECT_METADATA_JSONB_PROPERTIES,
shouldIgnoreProperty: (
property: string,
flatObjectMetadata: FlatObjectMetadata,
) => {
if (
!FLAT_OBJECT_METADATA_PROPERTIES_TO_COMPARE.includes(
property as FlatObjectMetadataPropertiesToCompare,
)
) {
return true;
}
if (
isStandardMetadata(flatObjectMetadata) &&
property !== 'standardOverrides'
) {
return true;
}
return false;
},
};
const fromCompare = transformMetadataForComparison(
fromFlatObjectMetadata,
transformMetadataForComparisonParameters,
);
const toCompare = transformMetadataForComparison(
toFlatObjectMetadata,
transformMetadataForComparisonParameters,
);
const objectMetadataDifference = diff(fromCompare, omit(toCompare, 'fields'));
return objectMetadataDifference.flatMap<FlatObjectPropertiesUpdates[number]>(
(difference) => {
switch (difference.type) {
case 'CHANGE': {
const { oldValue, path, value } = difference;
const property = path[0] as FlatObjectMetadataPropertiesToCompare;
const isJsonb = FLAT_OBJECT_METADATA_JSONB_PROPERTIES.includes(
property as FlatObjectMetadataEntityJsonbProperties,
);
if (isJsonb) {
return {
from: parseJson(oldValue),
to: parseJson(value),
property,
};
}
return {
from: oldValue,
to: value,
property,
};
}
case 'CREATE':
case 'REMOVE': {
// Should never occurs ? should throw ?
return [];
}
default: {
assertUnreachable(
difference,
`Unexpected difference type: ${difference['type']}`,
);
}
}
},
);
};
@@ -4,7 +4,7 @@ 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 { type AllFlatEntityMaps } from 'src/engine/metadata-modules/flat-entity/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';
@@ -3,9 +3,9 @@ import {
trimAndRemoveDuplicatedWhitespacesFromObjectStringProperties,
} from 'twenty-shared/utils';
import { type AllFlatEntityMaps } from 'src/engine/core-modules/common/types/all-flat-entity-maps.type';
import { findFlatEntityByIdInFlatEntityMapsOrThrow } from 'src/engine/core-modules/common/utils/find-flat-entity-by-id-in-flat-entity-maps-or-throw.util';
import { findManyFlatEntityByIdInFlatEntityMapsOrThrow } from 'src/engine/core-modules/common/utils/find-many-flat-entity-by-id-in-flat-entity-maps-or-throw.util';
import { type AllFlatEntityMaps } from 'src/engine/metadata-modules/flat-entity/types/all-flat-entity-maps.type';
import { findFlatEntityByIdInFlatEntityMapsOrThrow } from 'src/engine/metadata-modules/flat-entity/utils/find-flat-entity-by-id-in-flat-entity-maps-or-throw.util';
import { findManyFlatEntityByIdInFlatEntityMapsOrThrow } from 'src/engine/metadata-modules/flat-entity/utils/find-many-flat-entity-by-id-in-flat-entity-maps-or-throw.util';
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';
@@ -4,12 +4,12 @@ import {
trimAndRemoveDuplicatedWhitespacesFromObjectStringProperties,
} from 'twenty-shared/utils';
import { type AllFlatEntityMaps } from 'src/engine/core-modules/common/types/all-flat-entity-maps.type';
import { findFlatEntityByIdInFlatEntityMaps } from 'src/engine/core-modules/common/utils/find-flat-entity-by-id-in-flat-entity-maps.util';
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 AllFlatEntityMaps } from 'src/engine/metadata-modules/flat-entity/types/all-flat-entity-maps.type';
import { type FlatEntityPropertiesToCompare } from 'src/engine/metadata-modules/flat-entity/types/flat-entity-properties-to-compare.type';
import { findFlatEntityByIdInFlatEntityMaps } from 'src/engine/metadata-modules/flat-entity/utils/find-flat-entity-by-id-in-flat-entity-maps.util';
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 { 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 { recomputeViewFieldIdentifierAfterFlatObjectIdentifierUpdate } from 'src/engine/metadata-modules/flat-object-metadata/utils/recompute-view-field-identifier-after-flat-object-identifier-update.util';
@@ -36,11 +36,11 @@ type FromUpdateObjectInputToFlatObjectMetadataArgs = {
>;
const objectMetadataEditableProperties =
FLAT_OBJECT_METADATA_PROPERTIES_TO_COMPARE.filter(
ALL_FLAT_ENTITY_PROPERTIES_TO_COMPARE_AND_STRINGIFY.objectMetadata.propertiesToCompare.filter(
(
property,
): property is Exclude<
FlatObjectMetadataPropertiesToCompare,
FlatEntityPropertiesToCompare<'objectMetadata'>,
'standardOverrides'
> => property !== 'standardOverrides',
);
@@ -1,7 +1,7 @@
import { FieldMetadataType } from 'twenty-shared/types';
import { type AllFlatEntityMaps } from 'src/engine/core-modules/common/types/all-flat-entity-maps.type';
import { findFlatEntityByIdInFlatEntityMapsOrThrow } from 'src/engine/core-modules/common/utils/find-flat-entity-by-id-in-flat-entity-maps-or-throw.util';
import { type AllFlatEntityMaps } from 'src/engine/metadata-modules/flat-entity/types/all-flat-entity-maps.type';
import { findFlatEntityByIdInFlatEntityMapsOrThrow } from 'src/engine/metadata-modules/flat-entity/utils/find-flat-entity-by-id-in-flat-entity-maps-or-throw.util';
import { type FlatFieldMetadata } from 'src/engine/metadata-modules/flat-field-metadata/types/flat-field-metadata.type';
import { isFlatFieldMetadataOfType } from 'src/engine/metadata-modules/flat-field-metadata/utils/is-flat-field-metadata-of-type.util';
@@ -1,10 +1,10 @@
import { type FlatEntity } from 'src/engine/core-modules/common/types/flat-entity.type';
import { type FlatEntity } from 'src/engine/metadata-modules/flat-entity/types/flat-entity.type';
import { type FlatObjectMetadata } from 'src/engine/metadata-modules/flat-object-metadata/types/flat-object-metadata.type';
import { type FailedFlatEntityValidation } from 'src/engine/workspace-manager/workspace-migration-v2/workspace-migration-builder-v2/builders/types/failed-flat-entity-validation.type';
import {
WORKSPACE_MIGRATION_OBJECT_ACTION_TYPES,
type WorkspaceMigrationObjectActionTypeV2,
} from 'src/engine/workspace-manager/workspace-migration-v2/workspace-migration-builder-v2/types/workspace-migration-object-action-v2';
} from 'src/engine/workspace-manager/workspace-migration-v2/workspace-migration-builder-v2/builders/object/types/workspace-migration-object-action-v2';
import { type FailedFlatEntityValidation } from 'src/engine/workspace-manager/workspace-migration-v2/workspace-migration-builder-v2/builders/types/failed-flat-entity-validation.type';
export const isFailedFlatObjectMetadataValidation = (
failedValidation: FailedFlatEntityValidation<FlatEntity>,

Some files were not shown because too many files have changed in this diff Show More