Field metadata and object metadata v1 relicas (#16230)

# Introduction

Related https://github.com/twentyhq/core-team-issues/issues/1911

Nearly done with all functional v1 implemen removal
Will remain dead code methods that I will detect using knip
This commit is contained in:
Paul Rastoin
2025-12-02 12:06:28 +01:00
committed by GitHub
parent 59f0f6f9db
commit eedb163131
45 changed files with 61 additions and 2504 deletions
@@ -1,11 +0,0 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`validateFieldNameAvailabilityOrThrow throws error with ACTOR suffixes 1`] = `"Name "fieldActorName" is not available"`;
exports[`validateFieldNameAvailabilityOrThrow throws error with ADDRESS suffixes 1`] = `"Name "fieldAddressAddressStreet1" is not available"`;
exports[`validateFieldNameAvailabilityOrThrow throws error with CURRENCY suffixes 1`] = `"Name "fieldCurrencyAmountMicros" is not available"`;
exports[`validateFieldNameAvailabilityOrThrow throws error with FULL_NAME suffixes 1`] = `"Name "fieldFullNameFirstName" is not available"`;
exports[`validateFieldNameAvailabilityOrThrow throws error with LINKS suffixes 1`] = `"Name "fieldLinksPrimaryLinkLabel" is not available"`;
@@ -1,17 +0,0 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`validateMetadataNameOrThrow throw error when string is not in camel case 1`] = `"TestName should be in camelCase"`;
exports[`validateMetadataNameOrThrow throws error when starts with digits 1`] = `"123string should be in camelCase"`;
exports[`validateMetadataNameOrThrow throws error when string has non latin characters 1`] = `"String "בְרִבְרִ" is not valid: must start with lowercase letter and contain only alphanumeric letters"`;
exports[`validateMetadataNameOrThrow throws error when string has spaces 1`] = `"name with spaces should be in camelCase"`;
exports[`validateMetadataNameOrThrow throws error when string is a reserved word 1`] = `"The name "role" is not available"`;
exports[`validateMetadataNameOrThrow throws error when string is above 63 characters 1`] = `"Name is too long: it exceeds the 63 characters limit."`;
exports[`validateMetadataNameOrThrow throws error when string is empty 1`] = `"Input is too short: """`;
exports[`validateMetadataNameOrThrow throws error when string starts with capital letter 1`] = `"StringStartingWithCapitalLetter should be in camelCase"`;
@@ -1,6 +1,6 @@
import { type EachTestingContext } from 'twenty-shared/testing';
import { computeMetadataNameFromLabel } from 'src/engine/metadata-modules/utils/validate-name-and-label-are-sync-or-throw.util';
import { computeMetadataNameFromLabelOrThrow } from 'src/engine/metadata-modules/utils/compute-metadata-name-from-label-or-throw.util';
import {
InvalidMetadataException,
InvalidMetadataExceptionCode,
@@ -124,7 +124,7 @@ describe('computeMetadataNameFromLabel', () => {
describe('successful cases', () => {
it.each(successfulTestCases)('$title', ({ context }) => {
const result = computeMetadataNameFromLabel(context.input);
const result = computeMetadataNameFromLabelOrThrow(context.input);
expect(result).toBe(context.expected);
});
@@ -132,7 +132,7 @@ describe('computeMetadataNameFromLabel', () => {
describe('failing cases', () => {
it.each(failingTestCases)('$title', ({ context }) => {
expect(() => computeMetadataNameFromLabel(context.input)).toThrow(
expect(() => computeMetadataNameFromLabelOrThrow(context.input)).toThrow(
context.expectToThrow?.error,
);
});
@@ -1,180 +0,0 @@
import { FieldMetadataType } from 'twenty-shared/types';
import { type EachTestingContext } from 'twenty-shared/testing';
import { createEmptyFlatEntityMaps } from 'src/engine/metadata-modules/flat-entity/constant/create-empty-flat-entity-maps.constant';
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 { getFlatFieldMetadataMock } from 'src/engine/metadata-modules/flat-field-metadata/__mocks__/get-flat-field-metadata.mock';
import { getFlatObjectMetadataMock } from 'src/engine/metadata-modules/flat-object-metadata/__mocks__/get-flat-object-metadata.mock';
import { validateFieldNameAvailabilityOrThrow } from 'src/engine/metadata-modules/utils/validate-field-name-availability.utils';
const FIELD_LINKS_MOCK_NAME = 'fieldLinks';
const FIELD_CURRENCY_MOCK_NAME = 'fieldCurrency';
const FIELD_ADDRESS_MOCK_NAME = 'fieldAddress';
const FIELD_ACTOR_MOCK_NAME = 'fieldActor';
const FIELD_FULL_NAME_MOCK_NAME = 'fieldFullName';
const objectMetadataId = '20202020-0000-0000-0000-000000000001';
const workspaceId = '20202020-0000-0000-0000-000000000000';
const createFlatFieldMetadata = (
id: string,
name: string,
type: FieldMetadataType,
): FlatFieldMetadata => {
return getFlatFieldMetadataMock({
id,
name,
type,
objectMetadataId,
universalIdentifier: id,
workspaceId,
});
};
const fieldLinksMock = createFlatFieldMetadata(
'fieldLinksId',
FIELD_LINKS_MOCK_NAME,
FieldMetadataType.LINKS,
);
const fieldCurrencyMock = createFlatFieldMetadata(
'fieldCurrencyId',
FIELD_CURRENCY_MOCK_NAME,
FieldMetadataType.CURRENCY,
);
const fieldFullNameMock = createFlatFieldMetadata(
'fieldFullNameId',
FIELD_FULL_NAME_MOCK_NAME,
FieldMetadataType.FULL_NAME,
);
const fieldActorMock = createFlatFieldMetadata(
'fieldActorId',
FIELD_ACTOR_MOCK_NAME,
FieldMetadataType.ACTOR,
);
const fieldAddressMock = createFlatFieldMetadata(
'fieldAddressId',
FIELD_ADDRESS_MOCK_NAME,
FieldMetadataType.ADDRESS,
);
const FIELDS_MOCK = [
fieldLinksMock,
fieldCurrencyMock,
fieldFullNameMock,
fieldActorMock,
fieldAddressMock,
];
const flatFieldMetadataMaps: FlatEntityMaps<FlatFieldMetadata> =
FIELDS_MOCK.reduce(
(acc, field) => ({
...acc,
byId: {
...acc.byId,
[field.id]: field,
},
}),
createEmptyFlatEntityMaps() as FlatEntityMaps<FlatFieldMetadata>,
);
const flatObjectMetadata = getFlatObjectMetadataMock({
id: objectMetadataId,
workspaceId,
nameSingular: 'objectName',
namePlural: 'objectNames',
labelSingular: 'Object Name',
labelPlural: 'Object Names',
description: 'Object description',
icon: 'Icon123',
isCustom: false,
isRemote: false,
isActive: true,
isSystem: false,
isAuditLogged: true,
isSearchable: true,
fieldMetadataIds: FIELDS_MOCK.map((f) => f.id),
indexMetadataIds: [],
viewIds: [],
universalIdentifier: objectMetadataId,
applicationId: null,
labelIdentifierFieldMetadataId: null,
imageIdentifierFieldMetadataId: null,
shortcut: null,
isLabelSyncedWithName: true,
standardId: null,
standardOverrides: null,
targetTableName: 'DEPRECATED',
duplicateCriteria: null,
});
type ValidateFieldNameAvailabilityTestContext = EachTestingContext<{
input: string;
shouldNotThrow?: true;
}>;
const validateFieldNameAvailabilityTestCases: ValidateFieldNameAvailabilityTestContext[] =
[
{
title: 'does not throw if name is not reserved',
context: {
input: 'testName',
shouldNotThrow: true,
},
},
{
title: 'throws error with LINKS suffixes',
context: {
input: `${FIELD_LINKS_MOCK_NAME}PrimaryLinkLabel`,
},
},
{
title: 'throws error with CURRENCY suffixes',
context: {
input: `${FIELD_CURRENCY_MOCK_NAME}AmountMicros`,
},
},
{
title: 'throws error with FULL_NAME suffixes',
context: {
input: `${FIELD_FULL_NAME_MOCK_NAME}FirstName`,
},
},
{
title: 'throws error with ACTOR suffixes',
context: {
input: `${FIELD_ACTOR_MOCK_NAME}Name`,
},
},
{
title: 'throws error with ADDRESS suffixes',
context: {
input: `${FIELD_ADDRESS_MOCK_NAME}AddressStreet1`,
},
},
];
describe('validateFieldNameAvailabilityOrThrow', () => {
it.each(validateFieldNameAvailabilityTestCases)(
'$title',
({ context: { input, shouldNotThrow } }) => {
if (shouldNotThrow) {
expect(() =>
validateFieldNameAvailabilityOrThrow({
name: input,
flatObjectMetadata,
flatFieldMetadataMaps,
}),
).not.toThrow();
} else {
expect(() =>
validateFieldNameAvailabilityOrThrow({
name: input,
flatObjectMetadata,
flatFieldMetadataMaps,
}),
).toThrowErrorMatchingSnapshot();
}
},
);
});
@@ -1,88 +0,0 @@
import { type EachTestingContext } from 'twenty-shared/testing';
import { validateMetadataNameOrThrow } from 'src/engine/metadata-modules/utils/validate-metadata-name-or-throw.utils';
type ValidateMetadataNameTestContext = EachTestingContext<{
input: string;
shouldNotThrow?: true;
}>;
const validateMetadataNameTestCases: ValidateMetadataNameTestContext[] = [
{
title: 'validates when string is valid',
context: {
input: 'testName',
shouldNotThrow: true,
},
},
{
title: 'throw error when string is not in camel case',
context: {
input: 'TestName',
},
},
{
title: 'throws error when string has spaces',
context: {
input: 'name with spaces',
},
},
{
title: 'throws error when string is a reserved word',
context: {
input: 'role',
},
},
{
title: 'throws error when string starts with capital letter',
context: {
input: 'StringStartingWithCapitalLetter',
},
},
{
title: 'throws error when string has non latin characters',
context: {
input: 'בְרִבְרִ',
},
},
{
title: 'throws error when starts with digits',
context: {
input: '123string',
},
},
{
title: 'validates when string is less than 63 characters',
context: {
input: 'a'.repeat(63),
shouldNotThrow: true,
},
},
{
title: 'throws error when string is above 63 characters',
context: {
input: 'a'.repeat(64),
},
},
{
title: 'throws error when string is empty',
context: {
input: '',
},
},
];
describe('validateMetadataNameOrThrow', () => {
it.each(validateMetadataNameTestCases)(
'$title',
({ context: { input, shouldNotThrow } }) => {
if (shouldNotThrow) {
expect(() => validateMetadataNameOrThrow(input)).not.toThrow();
} else {
expect(() =>
validateMetadataNameOrThrow(input),
).toThrowErrorMatchingSnapshot();
}
},
);
});
@@ -8,7 +8,7 @@ import {
// Server-specific wrapper that converts generic errors to InvalidMetadataException
// This provides consistent error handling with proper exception codes for the server
export const computeMetadataNameFromLabel = (label: string): string => {
export const computeMetadataNameFromLabelOrThrow = (label: string): string => {
if (!isDefined(label)) {
throw new InvalidMetadataException(
'Label is required',
@@ -28,20 +28,3 @@ export const computeMetadataNameFromLabel = (label: string): string => {
throw error;
}
};
export const validateNameAndLabelAreSyncOrThrow = ({
label,
name,
}: {
label: string;
name: string;
}) => {
const computedName = computeMetadataNameFromLabel(label);
if (name !== computedName) {
throw new InvalidMetadataException(
`Name is not synced with label. Expected name: "${computedName}", got ${name}`,
InvalidMetadataExceptionCode.NAME_NOT_SYNCED_WITH_LABEL,
);
}
};
@@ -7,7 +7,9 @@ import {
beneathDatabaseIdentifierMinimumLength,
exceedsDatabaseIdentifierMaximumLength,
} from 'src/engine/metadata-modules/utils/validate-database-identifier-length.utils';
import { STARTS_WITH_LOWER_CASE_AND_CONTAINS_ONLY_CAPS_AND_LOWER_LETTERS_AND_NUMBER_STRING_REGEX } from 'src/engine/metadata-modules/utils/validate-metadata-name-start-with-lowercase-letter-and-contain-digits-nor-letters.utils';
const STARTS_WITH_LOWER_CASE_AND_CONTAINS_ONLY_CAPS_AND_LOWER_LETTERS_AND_NUMBER_STRING_REGEX =
/^[a-z][a-zA-Z0-9]*$/;
export const METADATA_NAME_VALIDATORS: FlatMetadataValidator<string>[] = [
{
@@ -1,15 +0,0 @@
import { isDefined } from 'twenty-shared/utils';
export const isArgDefinedIfProvidedOrThrow = ({
input,
key,
value,
}: {
input: object;
key: string;
// eslint-disable-next-line @typescript-eslint/no-explicit-any
value: any;
}) => {
if (key in input && !isDefined(value)) {
throw new Error(`${key} must be defined when provided`);
}
};
@@ -1,89 +0,0 @@
import { msg } from '@lingui/core/macro';
import {
FieldMetadataType,
compositeTypeDefinitions,
} from 'twenty-shared/types';
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 { type FlatFieldMetadata } from 'src/engine/metadata-modules/flat-field-metadata/types/flat-field-metadata.type';
import { type FlatObjectMetadata } from 'src/engine/metadata-modules/flat-object-metadata/types/flat-object-metadata.type';
import {
InvalidMetadataException,
InvalidMetadataExceptionCode,
} from 'src/engine/metadata-modules/utils/exceptions/invalid-metadata.exception';
const getReservedCompositeFieldNames = (
flatObjectMetadata: FlatObjectMetadata,
flatFieldMetadataMaps: FlatEntityMaps<FlatFieldMetadata>,
) => {
const reservedCompositeFieldsNames: string[] = [];
for (const fieldId of flatObjectMetadata.fieldMetadataIds) {
const field = findFlatEntityByIdInFlatEntityMapsOrThrow({
flatEntityMaps: flatFieldMetadataMaps,
flatEntityId: fieldId,
});
if (isCompositeFieldMetadataType(field.type)) {
const base = field.name;
const compositeType = compositeTypeDefinitions.get(field.type);
compositeType?.properties.map((property) =>
reservedCompositeFieldsNames.push(
computeCompositeColumnName(base, property),
),
);
}
}
return reservedCompositeFieldsNames;
};
type ValidateFieldNameAvailabilityOrThrowArgs = {
name: string;
flatObjectMetadata: FlatObjectMetadata;
flatFieldMetadataMaps: FlatEntityMaps<FlatFieldMetadata>;
};
export const validateFieldNameAvailabilityOrThrow = ({
name,
flatObjectMetadata,
flatFieldMetadataMaps,
}: ValidateFieldNameAvailabilityOrThrowArgs) => {
const reservedCompositeFieldsNames = getReservedCompositeFieldNames(
flatObjectMetadata,
flatFieldMetadataMaps,
);
for (const fieldId of flatObjectMetadata.fieldMetadataIds) {
const field = findFlatEntityByIdInFlatEntityMapsOrThrow({
flatEntityMaps: flatFieldMetadataMaps,
flatEntityId: fieldId,
});
if (
field.name === name ||
(field.type === FieldMetadataType.RELATION && `${field.name}Id` === name)
) {
throw new InvalidMetadataException(
`Name "${name}" is not available as it is already used by another field`,
InvalidMetadataExceptionCode.NOT_AVAILABLE,
{
userFriendlyMessage: msg`This name is not available as it is already used by another field.`,
},
);
}
}
if (reservedCompositeFieldsNames.includes(name)) {
throw new InvalidMetadataException(
`Name "${name}" is not available`,
InvalidMetadataExceptionCode.RESERVED_KEYWORD,
{
userFriendlyMessage: msg`This name is not available.`,
},
);
}
};
@@ -1,92 +0,0 @@
import {
isDefined,
isLabelIdentifierFieldMetadataTypes,
} from 'twenty-shared/utils';
import { type FieldMetadataEntity } from 'src/engine/metadata-modules/field-metadata/field-metadata.entity';
import {
ObjectMetadataException,
ObjectMetadataExceptionCode,
} from 'src/engine/metadata-modules/object-metadata/object-metadata.exception';
type Validator = {
validator: (args: {
fieldMetadataId: string;
matchingFieldMetadata?: FieldMetadataEntity;
}) => boolean;
label: string;
};
type ValidateMetadataIdentifierFieldMetadataIdOrThrowArgs = {
fieldMetadataId: string;
fieldMetadataItems: FieldMetadataEntity[];
validators: Validator[];
};
const validatorRunner = ({
fieldMetadataId,
fieldMetadataItems,
validators,
}: ValidateMetadataIdentifierFieldMetadataIdOrThrowArgs): void => {
const matchingFieldMetadata = fieldMetadataItems.find(
(fieldMetadata) => fieldMetadata.id === fieldMetadataId,
);
validators.forEach(({ label, validator }) => {
if (validator({ fieldMetadataId, matchingFieldMetadata })) {
throw new ObjectMetadataException(
label,
ObjectMetadataExceptionCode.INVALID_OBJECT_INPUT,
);
}
});
};
type ValidateMetadataIdentifierFieldMetadataIdsArgs = {
labelIdentifierFieldMetadataId: string | undefined;
imageIdentifierFieldMetadataId: string | undefined;
fieldMetadataItems: FieldMetadataEntity[];
};
export const validateMetadataIdentifierFieldMetadataIds = ({
imageIdentifierFieldMetadataId,
labelIdentifierFieldMetadataId,
fieldMetadataItems,
}: ValidateMetadataIdentifierFieldMetadataIdsArgs) => {
const isMatchingFieldMetadataDefined: Validator['validator'] = ({
matchingFieldMetadata,
}) => !isDefined(matchingFieldMetadata);
if (isDefined(labelIdentifierFieldMetadataId)) {
validatorRunner({
fieldMetadataId: labelIdentifierFieldMetadataId,
fieldMetadataItems,
validators: [
{
validator: isMatchingFieldMetadataDefined,
label:
'labelIdentifierFieldMetadataId validation failed: related field metadata not found',
},
{
validator: ({ matchingFieldMetadata }) =>
isDefined(matchingFieldMetadata) &&
!isLabelIdentifierFieldMetadataTypes(matchingFieldMetadata.type),
label:
'labelIdentifierFieldMetadataId validation failed: it must be a TEXT or FULL_NAME field metadata type id',
},
],
});
}
if (isDefined(imageIdentifierFieldMetadataId)) {
validatorRunner({
fieldMetadataId: imageIdentifierFieldMetadataId,
fieldMetadataItems,
validators: [
{
validator: isMatchingFieldMetadataDefined,
label:
'imageIdentifierFieldMetadataId validation failed: related field metadata not found',
},
],
});
}
};
@@ -1,16 +0,0 @@
import { t } from '@lingui/core/macro';
import camelCase from 'lodash.camelcase';
import {
InvalidMetadataException,
InvalidMetadataExceptionCode,
} from 'src/engine/metadata-modules/utils/exceptions/invalid-metadata.exception';
export const validateMetadataNameIsCamelCaseOrThrow = (name: string) => {
if (name !== camelCase(name)) {
throw new InvalidMetadataException(
t`${name} should be in camelCase`,
InvalidMetadataExceptionCode.NOT_CAMEL_CASE,
);
}
};
@@ -1,21 +0,0 @@
import { msg } from '@lingui/core/macro';
import { RESERVED_METADATA_NAME_KEYWORDS } from 'twenty-shared/metadata';
import {
InvalidMetadataException,
InvalidMetadataExceptionCode,
} from 'src/engine/metadata-modules/utils/exceptions/invalid-metadata.exception';
export const validateMetadataNameIsNotReservedKeywordOrThrow = (
name: string,
) => {
if (RESERVED_METADATA_NAME_KEYWORDS.includes(name)) {
throw new InvalidMetadataException(
`The name "${name}" is not available`,
InvalidMetadataExceptionCode.RESERVED_KEYWORD,
{
userFriendlyMessage: msg`This name is not available.`,
},
);
}
};
@@ -1,16 +0,0 @@
import { t } from '@lingui/core/macro';
import {
InvalidMetadataException,
InvalidMetadataExceptionCode,
} from 'src/engine/metadata-modules/utils/exceptions/invalid-metadata.exception';
import { exceedsDatabaseIdentifierMaximumLength } from 'src/engine/metadata-modules/utils/validate-database-identifier-length.utils';
export const validateMetadataNameIsNotTooLongOrThrow = (name: string) => {
if (exceedsDatabaseIdentifierMaximumLength(name)) {
throw new InvalidMetadataException(
t`Name is too long: it exceeds the 63 characters limit.`,
InvalidMetadataExceptionCode.EXCEEDS_MAX_LENGTH,
);
}
};
@@ -1,16 +0,0 @@
import { t } from '@lingui/core/macro';
import {
InvalidMetadataException,
InvalidMetadataExceptionCode,
} from 'src/engine/metadata-modules/utils/exceptions/invalid-metadata.exception';
import { beneathDatabaseIdentifierMinimumLength } from 'src/engine/metadata-modules/utils/validate-database-identifier-length.utils';
export const validateMetadataNameIsNotTooShortOrThrow = (name: string) => {
if (beneathDatabaseIdentifierMinimumLength(name)) {
throw new InvalidMetadataException(
t`Input is too short: "${name}"`,
InvalidMetadataExceptionCode.INPUT_TOO_SHORT,
);
}
};
@@ -1,17 +0,0 @@
import { validateMetadataNameIsCamelCaseOrThrow } from 'src/engine/metadata-modules/utils/validate-metadata-name-is-camel-case.utils';
import { validateMetadataNameIsNotReservedKeywordOrThrow } from 'src/engine/metadata-modules/utils/validate-metadata-name-is-not-reserved-keyword';
import { validateMetadataNameIsNotTooLongOrThrow } from 'src/engine/metadata-modules/utils/validate-metadata-name-is-not-too-long.utils';
import { validateMetadataNameIsNotTooShortOrThrow } from 'src/engine/metadata-modules/utils/validate-metadata-name-is-not-too-short.utils';
import { validateMetadataNameStartWithLowercaseLetterAndContainDigitsNorLettersOrThrow } from 'src/engine/metadata-modules/utils/validate-metadata-name-start-with-lowercase-letter-and-contain-digits-nor-letters.utils';
export const validateMetadataNameOrThrow = (name: string): void => {
const validators = [
validateMetadataNameIsNotTooLongOrThrow,
validateMetadataNameIsNotTooShortOrThrow,
validateMetadataNameIsCamelCaseOrThrow,
validateMetadataNameStartWithLowercaseLetterAndContainDigitsNorLettersOrThrow,
validateMetadataNameIsNotReservedKeywordOrThrow,
];
validators.forEach((validator) => validator(name));
};
@@ -1,23 +0,0 @@
import { t } from '@lingui/core/macro';
import {
InvalidMetadataException,
InvalidMetadataExceptionCode,
} from 'src/engine/metadata-modules/utils/exceptions/invalid-metadata.exception';
export const STARTS_WITH_LOWER_CASE_AND_CONTAINS_ONLY_CAPS_AND_LOWER_LETTERS_AND_NUMBER_STRING_REGEX =
/^[a-z][a-zA-Z0-9]*$/;
export const validateMetadataNameStartWithLowercaseLetterAndContainDigitsNorLettersOrThrow =
(name: string) => {
if (
!name.match(
STARTS_WITH_LOWER_CASE_AND_CONTAINS_ONLY_CAPS_AND_LOWER_LETTERS_AND_NUMBER_STRING_REGEX,
)
) {
throw new InvalidMetadataException(
t`String "${name}" is not valid: must start with lowercase letter and contain only alphanumeric letters`,
InvalidMetadataExceptionCode.INVALID_STRING,
);
}
};
@@ -1,49 +0,0 @@
import { msg } from '@lingui/core/macro';
import { isDefined } from 'twenty-shared/utils';
import { type FlatEntityMaps } from 'src/engine/metadata-modules/flat-entity/types/flat-entity-maps.type';
import { type FlatObjectMetadata } from 'src/engine/metadata-modules/flat-object-metadata/types/flat-object-metadata.type';
import {
ObjectMetadataException,
ObjectMetadataExceptionCode,
} from 'src/engine/metadata-modules/object-metadata/object-metadata.exception';
type ValidateNoOtherObjectWithSameNameExistsOrThrowsParams = {
objectMetadataNameSingular: string;
objectMetadataNamePlural: string;
existingObjectMetadataId?: string;
objectMetadataMaps: FlatEntityMaps<FlatObjectMetadata>;
};
export const doesOtherObjectWithSameNameExists = ({
objectMetadataMaps,
objectMetadataNamePlural,
objectMetadataNameSingular,
existingObjectMetadataId,
}: ValidateNoOtherObjectWithSameNameExistsOrThrowsParams) =>
Object.values(objectMetadataMaps.byId)
.filter(isDefined)
.some(
(objectMetadata) =>
(objectMetadata.nameSingular === objectMetadataNameSingular ||
objectMetadata.namePlural === objectMetadataNamePlural ||
objectMetadata.nameSingular === objectMetadataNamePlural ||
objectMetadata.namePlural === objectMetadataNameSingular) &&
objectMetadata.id !== existingObjectMetadataId,
);
export const validatesNoOtherObjectWithSameNameExistsOrThrows = (
args: ValidateNoOtherObjectWithSameNameExistsOrThrowsParams,
) => {
const objectAlreadyExists = doesOtherObjectWithSameNameExists(args);
if (objectAlreadyExists) {
throw new ObjectMetadataException(
'Object already exists',
ObjectMetadataExceptionCode.OBJECT_ALREADY_EXISTS,
{
userFriendlyMessage: msg`Object already exists`,
},
);
}
};