twenty / packages /twenty-server /src /engine /api /utils /validate-and-get-order-by.utils.ts
Jules
Initial clean CRM deployment with prebuilt assets
d9494a5
Raw
History Blame Contribute Delete
4.72 kB
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<string, unknown>,
key: keyof ObjectRecord,
): orderByLeaf is ObjectRecordOrderByForScalarField => {
const value = orderByLeaf[key as string];
return isDefined(value) && isOrderByDirection(value);
};
const isOrderByForCompositeField = (
orderByLeaf: Record<string, unknown>,
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<string, unknown>).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<string, OrderByDirection>),
}),
{} as Record<string, OrderByDirection>,
);
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<FlatFieldMetadata>,
fieldIdByName: Record<string, string>,
): 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<FlatFieldMetadata>,
fieldIdByName: Record<string, string>,
): boolean => {
return (
countRelationFieldsInOrderBy(
orderBy,
flatFieldMetadataMaps,
fieldIdByName,
) > 0
);
};