Scalar and universal flat entity transpilers (#17891)
# Introduction
## Use `flatEntityTranspilers.toScalarFlatEntity` in create action
handler
**Changes:**
- Modified
`BaseWorkspaceMigrationRunnerActionHandlerService.insertFlatEntitiesInRepository()`
to transform flat entities using `toScalarFlatEntity()` before database
insertion
**What it does:**
Strips out TypeORM relation objects and metadata-only properties,
ensuring only scalar values (primitives, IDs, dates) are inserted into
the database.
**Benefits:**
- **Type Safety:** Prevents accidental insertion of nested objects that
TypeORM can't persist
- **Consistency:** All 17+ create action handlers automatically benefit
from proper data transformation
- **Single Source of Truth:** Centralized logic for what constitutes a
database-insertable entity
- **Prevents Errors:** Uses entity configuration schema to ensure only
valid properties are included
## Usage
```ts
protected async insertFlatEntitiesInRepository({
flatEntities,
queryRunner,
}: {
queryRunner: QueryRunner;
flatEntities: MetadataFlatEntity<TMetadataName>[];
}) {
const metadataEntity =
ALL_METADATA_ENTITY_BY_METADATA_NAME[this.metadataName];
const repository = queryRunner.manager.getRepository(metadataEntity);
const scalarFlatEntities = flatEntities.map((flatEntity) =>
flatEntityTranspilers.toScalarFlatEntity({
flatEntity,
metadataName: this.metadataName,
}),
);
await repository.insert(scalarFlatEntities);
}
```
## Upcoming refactor
About to completely split the
`packages/twenty-server/src/engine/workspace-manager/workspace-migration/workspace-migration-runner/interfaces/workspace-migration-runner-action-handler-service.interface.ts`
into three dedicated boilerplate one for each action type `create`
`delete` `update` will provide a better interfacing and typing + will
allow not requiring the user to provide the metadata execute handler as
required
This commit is contained in:
+9
-3
@@ -1,7 +1,10 @@
|
||||
import { type AllMetadataName } from 'twenty-shared/metadata';
|
||||
import { type Equal, type Expect } from 'twenty-shared/testing';
|
||||
|
||||
import { type ALL_ENTITY_PROPERTIES_CONFIGURATION_BY_METADATA_NAME } from 'src/engine/metadata-modules/flat-entity/constant/all-entity-properties-configuration-by-metadata-name.constant';
|
||||
import {
|
||||
type ALL_ENTITY_PROPERTIES_CONFIGURATION_BY_METADATA_NAME,
|
||||
type MetadataEntityComparablePropertyName,
|
||||
} from 'src/engine/metadata-modules/flat-entity/constant/all-entity-properties-configuration-by-metadata-name.constant';
|
||||
import { type MetadataUniversalFlatEntity } from 'src/engine/metadata-modules/flat-entity/types/metadata-universal-flat-entity.type';
|
||||
|
||||
type ExtractPropertyToCompare<
|
||||
@@ -14,9 +17,12 @@ type ExtractPropertyToCompare<
|
||||
export type MetadataUniversalFlatEntityPropertiesToCompare<
|
||||
T extends AllMetadataName,
|
||||
MetadataConfig = (typeof ALL_ENTITY_PROPERTIES_CONFIGURATION_BY_METADATA_NAME)[T],
|
||||
TComparedKeys extends
|
||||
keyof MetadataConfig = MetadataEntityComparablePropertyName<T> &
|
||||
keyof MetadataConfig,
|
||||
> = {
|
||||
[P in keyof MetadataConfig]: ExtractPropertyToCompare<MetadataConfig, P>;
|
||||
}[keyof MetadataConfig] &
|
||||
[P in TComparedKeys]: ExtractPropertyToCompare<MetadataConfig, P>;
|
||||
}[TComparedKeys] &
|
||||
keyof MetadataUniversalFlatEntity<T>;
|
||||
|
||||
// eslint-disable-next-line unused-imports/no-unused-vars
|
||||
|
||||
+5
-3
@@ -2,12 +2,14 @@ import { type AllMetadataName } from 'twenty-shared/metadata';
|
||||
|
||||
import { type ALL_ENTITY_PROPERTIES_CONFIGURATION_BY_METADATA_NAME } from 'src/engine/metadata-modules/flat-entity/constant/all-entity-properties-configuration-by-metadata-name.constant';
|
||||
|
||||
type ExtractKeysWithToStringify<T> = {
|
||||
[K in keyof T]: T[K] extends { toStringify: true } ? K : never;
|
||||
type ExtractComparableKeysWithToStringify<T> = {
|
||||
[K in keyof T]: T[K] extends { toCompare: true; toStringify: true }
|
||||
? K
|
||||
: never;
|
||||
}[keyof T];
|
||||
|
||||
export type MetadataUniversalFlatEntityPropertiesToStringify<
|
||||
T extends AllMetadataName,
|
||||
> = ExtractKeysWithToStringify<
|
||||
> = ExtractComparableKeysWithToStringify<
|
||||
(typeof ALL_ENTITY_PROPERTIES_CONFIGURATION_BY_METADATA_NAME)[T]
|
||||
>;
|
||||
|
||||
+1
-1
@@ -3,7 +3,7 @@ import { type IndexFieldMetadataEntity } from 'src/engine/metadata-modules/index
|
||||
import { type IndexMetadataEntity } from 'src/engine/metadata-modules/index-metadata/index-metadata.entity';
|
||||
import { type UniversalFlatEntityFrom } from 'src/engine/workspace-manager/workspace-migration/universal-flat-entity/types/universal-flat-entity-from.type';
|
||||
|
||||
// Note: IndexFieldMetadataEntity is just in between a SyncableEntity and a jsonb we should decide one of both
|
||||
// Note: IndexFieldMetadataEntity is just in between a SyncableEntity and a jsonb we should decide one of both https://github.com/twentyhq/core-team-issues/issues/2227
|
||||
export type UniversalFlatIndexFieldMetadata = Omit<
|
||||
IndexFieldMetadataEntity,
|
||||
| 'indexMetadataId'
|
||||
|
||||
+1
-1
@@ -22,7 +22,7 @@ export function transformUniversalFlatEntityForComparison<
|
||||
|
||||
if (
|
||||
propertiesToStringify.includes(
|
||||
propertyToCompare as MetadataUniversalFlatEntityPropertiesToStringify<T>,
|
||||
propertyToCompare as string as MetadataUniversalFlatEntityPropertiesToStringify<T>,
|
||||
)
|
||||
) {
|
||||
const orderedValue = orderObjectProperties(currentValue);
|
||||
|
||||
Reference in New Issue
Block a user