import { FieldMetadataType, type ObjectRecord, type ObjectRecordOrderByForCompositeField, type ObjectRecordOrderByForScalarField, OrderByDirection, } from 'twenty-shared/types'; import { isDefined } from 'twenty-shared/utils'; import { type ObjectRecordOrderBy } from 'src/engine/api/graphql/workspace-query-builder/interfaces/object-record.interface'; import { STANDARD_ERROR_MESSAGE } from 'src/engine/api/common/common-query-runners/errors/standard-error-message.constant'; import { GraphqlQueryRunnerException, GraphqlQueryRunnerExceptionCode, } from 'src/engine/api/graphql/graphql-query-runner/errors/graphql-query-runner.exception'; import { type FlatEntityMaps } from 'src/engine/metadata-modules/flat-entity/types/flat-entity-maps.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'; const isOrderByDirection = (value: unknown): value is OrderByDirection => { return Object.values(OrderByDirection).includes(value as OrderByDirection); }; const isOrderByForScalarField = ( orderByLeaf: Record, key: keyof ObjectRecord, ): orderByLeaf is ObjectRecordOrderByForScalarField => { const value = orderByLeaf[key as string]; return isDefined(value) && isOrderByDirection(value); }; const isOrderByForCompositeField = ( orderByLeaf: Record, key: keyof ObjectRecord, ): orderByLeaf is ObjectRecordOrderByForCompositeField => { const value = orderByLeaf[key as string]; return ( isDefined(value) && typeof value === 'object' && value !== null && !isOrderByDirection(value) && Object.values(value as Record).every(isOrderByDirection) ); }; export const validateAndGetOrderByForScalarField = ( key: keyof ObjectRecord, orderBy: ObjectRecordOrderBy, ): ObjectRecordOrderByForScalarField => { const keyOrderBy = orderBy.find((order) => key in order); if (!isDefined(keyOrderBy)) { throw new GraphqlQueryRunnerException( 'Invalid cursor', GraphqlQueryRunnerExceptionCode.INVALID_CURSOR, { userFriendlyMessage: STANDARD_ERROR_MESSAGE }, ); } if (!isOrderByForScalarField(keyOrderBy, key)) { throw new GraphqlQueryRunnerException( 'Expected non-composite field order by', GraphqlQueryRunnerExceptionCode.INVALID_CURSOR, { userFriendlyMessage: STANDARD_ERROR_MESSAGE }, ); } return keyOrderBy; }; export const validateAndGetOrderByForCompositeField = ( key: keyof ObjectRecord, orderBy: ObjectRecordOrderBy, ): ObjectRecordOrderByForCompositeField => { const matchingOrderBys = orderBy.filter((order) => key in order); if (matchingOrderBys.length === 0) { throw new GraphqlQueryRunnerException( 'Invalid cursor', GraphqlQueryRunnerExceptionCode.INVALID_CURSOR, { userFriendlyMessage: STANDARD_ERROR_MESSAGE }, ); } // Merge all orderBy entries for the same composite field key so that // separate { fullName: { firstName } } and { fullName: { lastName } } entries // are treated as a single composite orderBy const mergedValue = matchingOrderBys.reduce( (acc, orderByEntry) => ({ ...acc, ...(orderByEntry[key as string] as Record), }), {} as Record, ); const mergedOrderBy = { [key as string]: mergedValue }; if (!isOrderByForCompositeField(mergedOrderBy, key)) { throw new GraphqlQueryRunnerException( 'Expected composite field order by', GraphqlQueryRunnerExceptionCode.INVALID_CURSOR, { userFriendlyMessage: STANDARD_ERROR_MESSAGE }, ); } return mergedOrderBy; }; export const countRelationFieldsInOrderBy = ( orderBy: ObjectRecordOrderBy, flatFieldMetadataMaps: FlatEntityMaps, fieldIdByName: Record, ): number => { return orderBy.filter((orderByItem) => { const fieldName = Object.keys(orderByItem)[0]; const fieldMetadataId = fieldIdByName[fieldName]; const fieldMetadata = findFlatEntityByIdInFlatEntityMaps({ flatEntityId: fieldMetadataId, flatEntityMaps: flatFieldMetadataMaps, }); return fieldMetadata?.type === FieldMetadataType.RELATION; }).length; }; export const hasRelationFieldInOrderBy = ( orderBy: ObjectRecordOrderBy, flatFieldMetadataMaps: FlatEntityMaps, fieldIdByName: Record, ): boolean => { return ( countRelationFieldsInOrderBy( orderBy, flatFieldMetadataMaps, fieldIdByName, ) > 0 ); };