FlatObjectMetadataMaps and non FP style refactor (#13590)

# Introduction
- Retrieveing flat-object-metadata-maps for input transpilation, we
decided not to introduce map merge for remaining validation operation
for the moment
- Refactored the field metadata service v2 to sequentially
optimistically render on each iteration
This commit is contained in:
Paul Rastoin
2025-08-05 10:46:28 +02:00
committed by GitHub
parent ca9464e19d
commit 6af99ab1ea
14 changed files with 316 additions and 94 deletions
@@ -2,6 +2,7 @@ import { Injectable } from '@nestjs/common';
import { InjectRepository } from '@nestjs/typeorm';
import { TypeOrmQueryService } from '@ptc-org/nestjs-query-typeorm';
import { FieldMetadataType } from 'twenty-shared/types';
import { isDefined } from 'twenty-shared/utils';
import { Repository } from 'typeorm';
@@ -13,10 +14,13 @@ import {
FieldMetadataExceptionCode,
} from 'src/engine/metadata-modules/field-metadata/field-metadata.exception';
import { FlatFieldMetadataValidatorService } from 'src/engine/metadata-modules/flat-field-metadata/services/flat-field-metadata-validator.service';
import { FailedFlatFieldMetadataValidationExceptions } from 'src/engine/metadata-modules/flat-field-metadata/types/failed-flat-field-metadata-validation.type';
import { fromCreateFieldInputToFlatFieldAndItsFlatObjectMetadata } from 'src/engine/metadata-modules/flat-field-metadata/utils/from-create-field-input-to-flat-field-and-its-flat-object-metadata.util';
import { isFlatFieldMetadataEntityOfType } from 'src/engine/metadata-modules/flat-field-metadata/utils/is-flat-field-metadata-of-type.util';
import { mergeFlatFieldMetadatasInFlatObjectMetadata } from 'src/engine/metadata-modules/flat-field-metadata/utils/merge-flat-field-metadatas-in-flat-object-metadata.util';
import { FlatObjectMetadata } from 'src/engine/metadata-modules/flat-object-metadata/types/flat-object-metadata.type';
import { dispatchAndMergeFlatFieldMetadatasInFlatObjectMetadatas } from 'src/engine/metadata-modules/flat-object-metadata/utils/dispatch-and-merge-flat-field-metadatas-in-flat-object-metadatas.util';
import { fromObjectMetadataMapsToFlatObjectMetadatas } from 'src/engine/metadata-modules/flat-object-metadata/utils/from-object-metadata-maps-to-flat-object-metadatas.util';
import { removeFlatFieldMetadataFromFlatObjectMetadatas } from 'src/engine/metadata-modules/flat-object-metadata/utils/remove-flat-field-metadata-from-flat-object-metadatas.util';
import { fromFlatObjectMetadataWithFlatFieldMapsToFlatObjectMetadata } from 'src/engine/metadata-modules/flat-object-metadata/utils/from-flat-object-metadata-with-flat-field-maps-to-flat-object-metadatas.util';
import { WorkspaceMetadataCacheService } from 'src/engine/metadata-modules/workspace-metadata-cache/services/workspace-metadata-cache.service';
import { WorkspaceMigrationBuilderV2Service } from 'src/engine/workspace-manager/workspace-migration-v2/workspace-migration-builder-v2/workspace-migration-builder-v2.service';
import { WorkspaceMigrationRunnerV2Service } from 'src/engine/workspace-manager/workspace-migration-v2/workspace-migration-runner-v2/workspace-migration-runner-v2.service';
@@ -58,62 +62,132 @@ export class FieldMetadataServiceV2 extends TypeOrmQueryService<FieldMetadataEnt
const workspaceId = fieldMetadataInputs[0].workspaceId;
const { objectMetadataMaps } =
await this.workspaceMetadataCacheService.getExistingOrRecomputeMetadataMaps(
const { flatObjectMetadataMaps: existingFlatObjectMetadataMaps } =
await this.workspaceMetadataCacheService.getExistingOrRecomputeFlatObjectMetadataMaps(
{ workspaceId },
);
const existingFlatObjectMetadatas =
fromObjectMetadataMapsToFlatObjectMetadatas(objectMetadataMaps);
const flatFieldMetadatasToCreate = (
await Promise.all(
fieldMetadataInputs.map(
async (fieldMetadataInput) =>
await fromCreateFieldInputToFlatFieldAndItsFlatObjectMetadata({
existingFlatObjectMetadatas,
existingFlatObjectMetadataMaps,
rawCreateFieldInput: fieldMetadataInput,
}),
),
)
).flat();
const optimisticRenderedFlatObjectMetadatas =
dispatchAndMergeFlatFieldMetadatasInFlatObjectMetadatas({
flatObjectMetadatas: existingFlatObjectMetadatas,
flatFieldMetadatas: flatFieldMetadatasToCreate,
});
const existingFlatObjectMetadatas = Object.values(
existingFlatObjectMetadataMaps.byId,
).map(fromFlatObjectMetadataWithFlatFieldMapsToFlatObjectMetadata);
const flatFieldMetadataValidationPromises = flatFieldMetadatasToCreate.map(
(flatFieldMetadataToValidate) =>
this.flatFieldMetadataValidatorService.validateOneFlatFieldMetadata({
existingFlatObjectMetadatas:
removeFlatFieldMetadataFromFlatObjectMetadatas({
flatFieldMetadata: flatFieldMetadataToValidate,
flatObjectMetadatas: optimisticRenderedFlatObjectMetadatas,
}),
flatFieldMetadataToValidate,
workspaceId,
}),
const impactedObjectMetadataIds = Array.from(
new Set(
flatFieldMetadatasToCreate.map(
(flatFieldMetadata) => flatFieldMetadata.objectMetadataId,
),
),
);
const filterFlatObjectMetadatasByImpactedIds = (
flatObjectMetadatas: FlatObjectMetadata[],
) =>
flatObjectMetadatas.filter((flatObjectMetadata) =>
impactedObjectMetadataIds.includes(flatObjectMetadata.id),
);
const impactedExistingFlatObjectMetadatas =
filterFlatObjectMetadatasByImpactedIds(existingFlatObjectMetadatas);
const allValidationErrors: FailedFlatFieldMetadataValidationExceptions[] =
[];
let sequentiallyOptimisticallyRenderedFlatObjectMetadatas = structuredClone(
existingFlatObjectMetadatas,
);
const createdFlatFieldMetadataValidationResult = (
await Promise.all(flatFieldMetadataValidationPromises)
)
.flat()
.filter(isDefined);
for (const flatFieldMetadataToCreate of flatFieldMetadatasToCreate) {
let otherFlatObjectMetadataToValidate: FlatObjectMetadata | undefined =
undefined;
if (createdFlatFieldMetadataValidationResult.length > 0) {
if (
isFlatFieldMetadataEntityOfType(
flatFieldMetadataToCreate,
FieldMetadataType.RELATION,
) ||
isFlatFieldMetadataEntityOfType(
flatFieldMetadataToCreate,
FieldMetadataType.MORPH_RELATION,
)
) {
const relatedFlatFieldMetadataToCreate =
flatFieldMetadatasToCreate.find(
(relatedFlatFieldMetadata) =>
isFlatFieldMetadataEntityOfType(
relatedFlatFieldMetadata,
FieldMetadataType.RELATION,
) &&
relatedFlatFieldMetadata.id ===
flatFieldMetadataToCreate.relationTargetFieldMetadataId,
);
const relatedFlatObjectMetadata = isDefined(
relatedFlatFieldMetadataToCreate,
)
? existingFlatObjectMetadataMaps.byId[
relatedFlatFieldMetadataToCreate.objectMetadataId
]
: undefined;
otherFlatObjectMetadataToValidate =
isDefined(relatedFlatObjectMetadata) &&
isDefined(relatedFlatFieldMetadataToCreate)
? mergeFlatFieldMetadatasInFlatObjectMetadata({
flatFieldMetadatas: [relatedFlatFieldMetadataToCreate],
flatObjectMetadata: relatedFlatObjectMetadata,
})
: undefined;
}
const validationErrors =
await this.flatFieldMetadataValidatorService.validateOneFlatFieldMetadata(
{
existingFlatObjectMetadatas:
sequentiallyOptimisticallyRenderedFlatObjectMetadatas,
flatFieldMetadataToValidate: flatFieldMetadataToCreate,
workspaceId,
othersFlatObjectMetadataToValidate: isDefined(
otherFlatObjectMetadataToValidate,
)
? [otherFlatObjectMetadataToValidate]
: undefined,
},
);
if (validationErrors.length > 0) {
allValidationErrors.push(...validationErrors);
continue;
}
sequentiallyOptimisticallyRenderedFlatObjectMetadatas =
dispatchAndMergeFlatFieldMetadatasInFlatObjectMetadatas({
flatFieldMetadatas: [flatFieldMetadataToCreate],
flatObjectMetadatas:
sequentiallyOptimisticallyRenderedFlatObjectMetadatas,
});
}
if (allValidationErrors.length > 0) {
throw new MultipleMetadataValidationErrors(
createdFlatFieldMetadataValidationResult,
allValidationErrors,
'Multiple validation errors occurred while creating field',
);
}
const workspaceMigration = this.workspaceMigrationBuilderV2.build({
objectMetadataFromToInputs: {
from: existingFlatObjectMetadatas,
to: optimisticRenderedFlatObjectMetadatas,
from: impactedExistingFlatObjectMetadatas,
to: filterFlatObjectMetadatasByImpactedIds(
sequentiallyOptimisticallyRenderedFlatObjectMetadatas,
),
},
inferDeletionFromMissingObjectFieldIndex: false,
workspaceId,
@@ -18,16 +18,16 @@ import { generateRatingOptions } from 'src/engine/metadata-modules/field-metadat
import { FlatFieldMetadata } from 'src/engine/metadata-modules/flat-field-metadata/types/flat-field-metadata.type';
import { fromRelationCreateFieldInputToFlatFieldMetadata } from 'src/engine/metadata-modules/flat-field-metadata/utils/from-relation-create-field-input-to-flat-field-metadata.util';
import { getDefaultFlatFieldMetadata } from 'src/engine/metadata-modules/flat-field-metadata/utils/get-default-flat-field-metadata-from-create-field-input.util';
import { FlatObjectMetadata } from 'src/engine/metadata-modules/flat-object-metadata/types/flat-object-metadata.type';
import { FlatObjectMetadataMaps } from 'src/engine/metadata-modules/flat-object-metadata-maps/types/flat-object-metadata-maps.type';
type FromCreateFieldInputToFlatObjectMetadata = {
rawCreateFieldInput: CreateFieldInput;
existingFlatObjectMetadatas: FlatObjectMetadata[];
existingFlatObjectMetadataMaps: FlatObjectMetadataMaps;
};
export const fromCreateFieldInputToFlatFieldAndItsFlatObjectMetadata = async ({
existingFlatObjectMetadatas,
rawCreateFieldInput,
existingFlatObjectMetadataMaps,
}: FromCreateFieldInputToFlatObjectMetadata): Promise<FlatFieldMetadata[]> => {
if (rawCreateFieldInput.isRemoteCreation) {
throw new FieldMetadataException(
@@ -40,10 +40,8 @@ export const fromCreateFieldInputToFlatFieldAndItsFlatObjectMetadata = async ({
rawCreateFieldInput,
['description', 'icon', 'label', 'name', 'objectMetadataId', 'type'],
);
const parentFlatObjectMetadata = existingFlatObjectMetadatas.find(
(existingFlatObjectMetadata) =>
existingFlatObjectMetadata.id === createFieldInput.objectMetadataId,
);
const parentFlatObjectMetadata =
existingFlatObjectMetadataMaps.byId[createFieldInput.objectMetadataId];
if (!isDefined(parentFlatObjectMetadata)) {
throw new FieldMetadataException(
@@ -70,7 +68,7 @@ export const fromCreateFieldInputToFlatFieldAndItsFlatObjectMetadata = async ({
}
case FieldMetadataType.RELATION: {
return fromRelationCreateFieldInputToFlatFieldMetadata({
existingFlatObjectMetadatas,
existingFlatObjectMetadataMaps,
sourceParentFlatObjectMetadata: parentFlatObjectMetadata,
createFieldInput,
});
@@ -12,6 +12,7 @@ import {
import { validateRelationCreationPayloadOrThrow } from 'src/engine/metadata-modules/field-metadata/utils/validate-relation-creation-payload.util';
import { FlatFieldMetadata } from 'src/engine/metadata-modules/flat-field-metadata/types/flat-field-metadata.type';
import { getDefaultFlatFieldMetadata } from 'src/engine/metadata-modules/flat-field-metadata/utils/get-default-flat-field-metadata-from-create-field-input.util';
import { FlatObjectMetadataMaps } from 'src/engine/metadata-modules/flat-object-metadata-maps/types/flat-object-metadata-maps.type';
import { FlatObjectMetadata } from 'src/engine/metadata-modules/flat-object-metadata/types/flat-object-metadata.type';
import { RelationOnDeleteAction } from 'src/engine/metadata-modules/relation-metadata/relation-on-delete-action.type';
import { computeMetadataNameFromLabel } from 'src/engine/metadata-modules/utils/validate-name-and-label-are-sync-or-throw.util';
@@ -39,11 +40,11 @@ const computeFieldMetadataRelationSettingsForRelationType = ({
type FromRelationCreateFieldInputToFlatFieldMetadataArgs = {
createFieldInput: CreateFieldInput;
existingFlatObjectMetadatas: FlatObjectMetadata[];
existingFlatObjectMetadataMaps: FlatObjectMetadataMaps;
sourceParentFlatObjectMetadata: FlatObjectMetadata;
};
export const fromRelationCreateFieldInputToFlatFieldMetadata = async ({
existingFlatObjectMetadatas,
existingFlatObjectMetadataMaps,
sourceParentFlatObjectMetadata,
createFieldInput,
}: FromRelationCreateFieldInputToFlatFieldMetadataArgs): Promise<
@@ -59,10 +60,10 @@ export const fromRelationCreateFieldInputToFlatFieldMetadata = async ({
}
await validateRelationCreationPayloadOrThrow(relationCreationPayload);
const targetParentFlatObjectMetadata = existingFlatObjectMetadatas.find(
(existingFlatObject) =>
existingFlatObject.id === relationCreationPayload.targetObjectMetadataId,
);
const targetParentFlatObjectMetadata =
existingFlatObjectMetadataMaps.byId[
relationCreationPayload.targetObjectMetadataId
];
if (!isDefined(targetParentFlatObjectMetadata)) {
throw new FieldMetadataException(
@@ -0,0 +1,19 @@
import { FlatFieldMetadata } from 'src/engine/metadata-modules/flat-field-metadata/types/flat-field-metadata.type';
import { mergeTwoFlatFieldMetadatas } from 'src/engine/metadata-modules/flat-field-metadata/utils/merge-two-flat-field-metadatas.util';
import { FlatObjectMetadata } from 'src/engine/metadata-modules/flat-object-metadata/types/flat-object-metadata.type';
export const mergeFlatFieldMetadatasInFlatObjectMetadata = ({
flatFieldMetadatas,
flatObjectMetadata,
}: {
flatObjectMetadata: FlatObjectMetadata;
flatFieldMetadatas: FlatFieldMetadata[];
}) => {
return {
...flatObjectMetadata,
flatFieldMetadatas: mergeTwoFlatFieldMetadatas({
destFlatFieldMetadatas: flatObjectMetadata.flatFieldMetadatas,
toMergeFlatFieldMetadatas: flatFieldMetadatas,
}),
};
};
@@ -0,0 +1,6 @@
import { FlatObjectMetadataWithFlatFieldMaps } from 'src/engine/metadata-modules/flat-object-metadata-maps/types/flat-object-metadata-with-flat-field-metadata-maps.type';
export type FlatObjectMetadataMaps = {
byId: Partial<Record<string, FlatObjectMetadataWithFlatFieldMaps>>;
idByNameSingular: Partial<Record<string, string>>;
};
@@ -0,0 +1,11 @@
import { FlatFieldMetadata } from 'src/engine/metadata-modules/flat-field-metadata/types/flat-field-metadata.type';
import { FlatObjectMetadata } from 'src/engine/metadata-modules/flat-object-metadata/types/flat-object-metadata.type';
export type FlatFieldMetadataMaps = {
fieldsById: Partial<Record<string, FlatFieldMetadata>>;
fieldIdByJoinColumnName: Partial<Record<string, string>>;
fieldIdByName: Partial<Record<string, string>>;
};
export type FlatObjectMetadataWithFlatFieldMaps = FlatObjectMetadata &
FlatFieldMetadataMaps;
@@ -0,0 +1,42 @@
import { FlatFieldMetadata } from 'src/engine/metadata-modules/flat-field-metadata/types/flat-field-metadata.type';
import { FlatObjectMetadataWithFlatFieldMaps } from 'src/engine/metadata-modules/flat-object-metadata-maps/types/flat-object-metadata-with-flat-field-metadata-maps.type';
import { fromObjectMetadataItemWithFieldMapsToFlatObjectMetadata } from 'src/engine/metadata-modules/flat-object-metadata/utils/from-object-metadata-item-with-field-maps-to-flat-object-metadata.util';
import { ObjectMetadataItemWithFieldMaps } from 'src/engine/metadata-modules/types/object-metadata-item-with-field-maps';
import { ObjectMetadataMaps } from 'src/engine/metadata-modules/types/object-metadata-maps';
type FromObjectMetadataItemWithFieldMapsToFlatObjectWithFlatFieldMetadataMapsArgs =
{
objectMetadataItemWithFieldMaps: ObjectMetadataItemWithFieldMaps;
objectMetadataMaps: ObjectMetadataMaps;
};
export const fromObjectMetadataItemWithFieldMapsToFlatObjectWithFlatFieldMetadataMaps =
({
objectMetadataItemWithFieldMaps,
objectMetadataMaps,
}: FromObjectMetadataItemWithFieldMapsToFlatObjectWithFlatFieldMetadataMapsArgs): FlatObjectMetadataWithFlatFieldMaps => {
const flatObjectMetadata =
fromObjectMetadataItemWithFieldMapsToFlatObjectMetadata({
objectMetadataItemWithFieldMaps,
objectMetadataMaps,
});
const initialAccumulator: Record<string, FlatFieldMetadata> = {};
const fieldsById = flatObjectMetadata.flatFieldMetadatas.reduce(
(acc, flatFieldMetadata) => ({
...acc,
[flatFieldMetadata.id]: flatFieldMetadata,
}),
initialAccumulator,
);
const { fieldIdByJoinColumnName, fieldIdByName } =
objectMetadataItemWithFieldMaps;
return {
...flatObjectMetadata,
fieldsById,
fieldIdByJoinColumnName,
fieldIdByName,
};
};
@@ -0,0 +1,30 @@
import { isDefined } from 'twenty-shared/utils';
import { FlatObjectMetadataMaps } from 'src/engine/metadata-modules/flat-object-metadata-maps/types/flat-object-metadata-maps.type';
import { fromObjectMetadataItemWithFieldMapsToFlatObjectWithFlatFieldMetadataMaps } from 'src/engine/metadata-modules/flat-object-metadata-maps/utils/from-flat-field-metadata-to-flat-field-metadata-maps.util';
import { ObjectMetadataMaps } from 'src/engine/metadata-modules/types/object-metadata-maps';
export const fromObjectMetadataMapsToFlatObjectMetadataMaps = (
objectMetadataMaps: ObjectMetadataMaps,
): FlatObjectMetadataMaps => {
const initialAccumulator: FlatObjectMetadataMaps['byId'] = {};
const byId = Object.values(objectMetadataMaps.byId)
.filter(isDefined)
.reduce((acc, objectMetadataItemWithFieldMaps) => {
const { id: objectMetadataId } = objectMetadataItemWithFieldMaps;
return {
...acc,
[objectMetadataId]:
fromObjectMetadataItemWithFieldMapsToFlatObjectWithFlatFieldMetadataMaps(
{ objectMetadataItemWithFieldMaps, objectMetadataMaps },
),
};
}, initialAccumulator);
return {
idByNameSingular: objectMetadataMaps.idByNameSingular,
byId,
};
};
@@ -1,7 +1,7 @@
import { isDefined } from 'twenty-shared/utils';
import { fromArrayToValuesByKeyRecord, isDefined } from 'twenty-shared/utils';
import { FlatFieldMetadata } from 'src/engine/metadata-modules/flat-field-metadata/types/flat-field-metadata.type';
import { mergeTwoFlatFieldMetadatas } from 'src/engine/metadata-modules/flat-field-metadata/utils/merge-two-flat-field-metadatas.util';
import { mergeFlatFieldMetadatasInFlatObjectMetadata } from 'src/engine/metadata-modules/flat-field-metadata/utils/merge-flat-field-metadatas-in-flat-object-metadata.util';
import { FlatObjectMetadata } from 'src/engine/metadata-modules/flat-object-metadata/types/flat-object-metadata.type';
type DispatchAndMergeFlatFieldMetadatasInFlatObjectMetadatasArgs = {
@@ -12,23 +12,15 @@ export const dispatchAndMergeFlatFieldMetadatasInFlatObjectMetadatas = ({
flatFieldMetadatas,
flatObjectMetadatas,
}: DispatchAndMergeFlatFieldMetadatasInFlatObjectMetadatasArgs): FlatObjectMetadata[] => {
const initialAccumulator: Record<string, FlatFieldMetadata[]> = {};
if (flatFieldMetadatas.length === 0) {
return flatObjectMetadatas;
}
const flatFieldMetadataGroupedByFlatObjectMetadataId =
flatFieldMetadatas.reduce((acc, flatFieldMetadata) => {
const { objectMetadataId } = flatFieldMetadata;
const occurrence = acc[objectMetadataId];
if (isDefined(occurrence)) {
return {
...acc,
[objectMetadataId]: [...occurrence, flatFieldMetadata],
};
}
return {
[flatFieldMetadata.objectMetadataId]: [flatFieldMetadata],
};
}, initialAccumulator);
fromArrayToValuesByKeyRecord({
array: flatFieldMetadatas,
key: 'objectMetadataId',
});
return flatObjectMetadatas.map((flatObjectMetadata) => {
const toMergeFlatFieldMetadatas =
@@ -38,12 +30,9 @@ export const dispatchAndMergeFlatFieldMetadatasInFlatObjectMetadatas = ({
return flatObjectMetadata;
}
return {
...flatObjectMetadata,
flatFieldMetadatas: mergeTwoFlatFieldMetadatas({
destFlatFieldMetadatas: flatObjectMetadata.flatFieldMetadatas,
toMergeFlatFieldMetadatas,
}),
};
return mergeFlatFieldMetadatasInFlatObjectMetadata({
flatFieldMetadatas: toMergeFlatFieldMetadatas,
flatObjectMetadata,
});
});
};
@@ -0,0 +1,14 @@
import { removePropertiesFromRecord } from 'twenty-shared/utils';
import { FlatObjectMetadataWithFlatFieldMaps } from 'src/engine/metadata-modules/flat-object-metadata-maps/types/flat-object-metadata-with-flat-field-metadata-maps.type';
import { FlatObjectMetadata } from 'src/engine/metadata-modules/flat-object-metadata/types/flat-object-metadata.type';
export const fromFlatObjectMetadataWithFlatFieldMapsToFlatObjectMetadata = (
flatObjectMetadataWithFlatFieldMaps: FlatObjectMetadataWithFlatFieldMaps,
): FlatObjectMetadata => {
return removePropertiesFromRecord(flatObjectMetadataWithFlatFieldMaps, [
'fieldsById',
'fieldIdByName',
'fieldIdByJoinColumnName',
]);
};
@@ -3,30 +3,16 @@ import { isDefined } from 'twenty-shared/utils';
import { FlatObjectMetadata } from 'src/engine/metadata-modules/flat-object-metadata/types/flat-object-metadata.type';
import { fromObjectMetadataItemWithFieldMapsToFlatObjectMetadata } from 'src/engine/metadata-modules/flat-object-metadata/utils/from-object-metadata-item-with-field-maps-to-flat-object-metadata.util';
import { ObjectMetadataMaps } from 'src/engine/metadata-modules/types/object-metadata-maps';
import {
WorkspaceMetadataCacheException,
WorkspaceMetadataCacheExceptionCode,
} from 'src/engine/metadata-modules/workspace-metadata-cache/exceptions/workspace-metadata-cache.exception';
export const fromObjectMetadataMapsToFlatObjectMetadatas = (
objectMetadataMaps: ObjectMetadataMaps,
): FlatObjectMetadata[] => {
const objectMetadataIds = Object.keys(objectMetadataMaps.byId);
return objectMetadataIds.flatMap<FlatObjectMetadata>((objectMetadataId) => {
const objectMetadataItemWithFieldMaps =
objectMetadataMaps.byId[objectMetadataId];
if (!isDefined(objectMetadataItemWithFieldMaps)) {
throw new WorkspaceMetadataCacheException(
'Object metadata not found in cache',
WorkspaceMetadataCacheExceptionCode.OBJECT_METADATA_MAP_NOT_FOUND,
);
}
return fromObjectMetadataItemWithFieldMapsToFlatObjectMetadata({
objectMetadataItemWithFieldMaps,
objectMetadataMaps,
});
});
return Object.values(objectMetadataMaps.byId)
.filter(isDefined)
.map((objectMetadataItemWithFieldMaps) =>
fromObjectMetadataItemWithFieldMapsToFlatObjectMetadata({
objectMetadataItemWithFieldMaps,
objectMetadataMaps,
}),
);
};
@@ -5,6 +5,8 @@ import { isDefined } from 'twenty-shared/utils';
import { In, Repository } from 'typeorm';
import { Workspace } from 'src/engine/core-modules/workspace/workspace.entity';
import { FlatObjectMetadataMaps } from 'src/engine/metadata-modules/flat-object-metadata-maps/types/flat-object-metadata-maps.type';
import { fromObjectMetadataMapsToFlatObjectMetadataMaps } from 'src/engine/metadata-modules/flat-object-metadata-maps/utils/from-flat-object-metadata-to-flat-object-metadata-with-flat-field-maps.util';
import { IndexMetadataEntity } from 'src/engine/metadata-modules/index-metadata/index-metadata.entity';
import { ObjectMetadataEntity } from 'src/engine/metadata-modules/object-metadata/object-metadata.entity';
import { ObjectMetadataMaps } from 'src/engine/metadata-modules/types/object-metadata-maps';
@@ -20,6 +22,11 @@ type GetExistingOrRecomputeMetadataMapsResult = {
metadataVersion: number;
};
type GetExistingOrRecomputeFlatObjectMetadataMapsResult = {
flatObjectMetadataMaps: FlatObjectMetadataMaps;
metadataVersion: number;
};
@Injectable()
export class WorkspaceMetadataCacheService {
logger = new Logger(WorkspaceMetadataCacheService.name);
@@ -34,6 +41,23 @@ export class WorkspaceMetadataCacheService {
private readonly indexMetadataRepository: Repository<IndexMetadataEntity>,
) {}
async getExistingOrRecomputeFlatObjectMetadataMaps({
workspaceId,
}: {
workspaceId: string;
}): Promise<GetExistingOrRecomputeFlatObjectMetadataMapsResult> {
const { objectMetadataMaps, metadataVersion } =
await this.getExistingOrRecomputeMetadataMaps({
workspaceId,
});
return {
flatObjectMetadataMaps:
fromObjectMetadataMapsToFlatObjectMetadataMaps(objectMetadataMaps),
metadataVersion,
};
}
async getExistingOrRecomputeMetadataMaps({
workspaceId,
}: {
@@ -0,0 +1,27 @@
import { StringPropertyKeys } from '@/utils/trim-and-remove-duplicated-whitespaces-from-object-string-properties';
import { isDefined } from '@/utils/validation';
export const fromArrayToValuesByKeyRecord = <T extends object>({
array,
key,
}: {
array: T[];
key: StringPropertyKeys<T>;
}) => {
return array.reduce<Record<string, T[]>>((acc, value) => {
const computedKey = value[key] as string;
const occurrence = acc[computedKey];
if (isDefined(occurrence)) {
return {
...acc,
[computedKey]: [...occurrence, value],
};
}
return {
...acc,
[computedKey]: [value],
};
}, {});
};
@@ -12,6 +12,7 @@ export { deepMerge } from './deepMerge';
export { extractAndSanitizeObjectStringFields } from './extractAndSanitizeObjectStringFields';
export { isFieldMetadataDateKind } from './fieldMetadata/isFieldMetadataDateKind';
export { fromArrayToUniqueKeyRecord } from './from-array-to-unique-key-record.util';
export { fromArrayToValuesByKeyRecord } from './fromArrayToValuesByKeyRecord.util';
export { getURLSafely } from './getURLSafely';
export { getImageAbsoluteURI } from './image/getImageAbsoluteURI';
export {