feat: new relation sync-metadata, twenty-orm, create/update (#10217)

Fix
https://github.com/twentyhq/core-team-issues/issues/330#issue-2827026606
and
https://github.com/twentyhq/core-team-issues/issues/327#issue-2827001814

What this PR does when `isNewRelationEnabled` is set to `true`:
- [x] Drop the creation of the  foreign key as a `FieldMetadata`
- [x] Stop creating `RelationMetadata`
- [x] Properly fill `FieldMetadata` of type `RELATION` during the sync
command
- [x] Use new relation settings in TwentyORM
- [x] Properly create `FieldMetadata` relations when we create a new
object
- [x] Handle `database:reset` with new relations

---------

Co-authored-by: Charles Bochet <charles@twenty.com>
Co-authored-by: Charles Bochet <charlesBochet@users.noreply.github.com>
This commit is contained in:
Jérémy M
2025-04-22 19:01:39 +02:00
committed by GitHub
parent de1489aabb
commit cc29c25176
160 changed files with 3247 additions and 711 deletions

View File

@ -29,6 +29,7 @@ export const mockPersonObjectMetadata = (
fields: [],
indexMetadatas: [],
fieldsById: {},
fieldsByJoinColumnName: {},
fieldsByName: {
name: {
id: '',

View File

@ -14,15 +14,19 @@ import { GraphqlQueryFilterFieldParser } from './graphql-query-filter-field.pars
export class GraphqlQueryFilterConditionParser {
private fieldMetadataMapByName: FieldMetadataMap;
private fieldMetadataMapByJoinColumnName: FieldMetadataMap;
private queryFilterFieldParser: GraphqlQueryFilterFieldParser;
constructor(
fieldMetadataMapByName: FieldMetadataMap,
fieldMetadataMapByJoinColumnName: FieldMetadataMap,
featureFlagsMap: FeatureFlagMap,
) {
this.fieldMetadataMapByName = fieldMetadataMapByName;
this.fieldMetadataMapByJoinColumnName = fieldMetadataMapByJoinColumnName;
this.queryFilterFieldParser = new GraphqlQueryFilterFieldParser(
this.fieldMetadataMapByName,
this.fieldMetadataMapByJoinColumnName,
featureFlagsMap,
);
}

View File

@ -1,5 +1,5 @@
import { WhereExpressionBuilder } from 'typeorm';
import { capitalize } from 'twenty-shared/utils';
import { WhereExpressionBuilder } from 'typeorm';
import { FeatureFlagMap } from 'src/engine/core-modules/feature-flag/interfaces/feature-flag-map.interface';
import { FieldMetadataInterface } from 'src/engine/metadata-modules/field-metadata/interfaces/field-metadata.interface';
@ -18,13 +18,16 @@ const ARRAY_OPERATORS = ['in', 'contains', 'notContains'];
export class GraphqlQueryFilterFieldParser {
private fieldMetadataMapByName: FieldMetadataMap;
private fieldMetadataMapByJoinColumnName: FieldMetadataMap;
private featureFlagsMap: FeatureFlagMap;
constructor(
fieldMetadataMapByName: FieldMetadataMap,
fieldMetadataMapByJoinColumnName: FieldMetadataMap,
featureFlagsMap: FeatureFlagMap,
) {
this.fieldMetadataMapByName = fieldMetadataMapByName;
this.fieldMetadataMapByJoinColumnName = fieldMetadataMapByJoinColumnName;
this.featureFlagsMap = featureFlagsMap;
}
@ -35,7 +38,9 @@ export class GraphqlQueryFilterFieldParser {
filterValue: any,
isFirst = false,
): void {
const fieldMetadata = this.fieldMetadataMapByName[`${key}`];
const fieldMetadata =
this.fieldMetadataMapByName[`${key}`] ||
this.fieldMetadataMapByJoinColumnName[`${key}`];
if (!fieldMetadata) {
throw new Error(`Field metadata not found for field: ${key}`);

View File

@ -24,6 +24,7 @@ import { getObjectMetadataMapItemByNameSingular } from 'src/engine/metadata-modu
export class GraphqlQueryParser {
private fieldMetadataMapByName: FieldMetadataMap;
private fieldMetadataMapByJoinColumnName: FieldMetadataMap;
private objectMetadataMaps: ObjectMetadataMaps;
private filterConditionParser: GraphqlQueryFilterConditionParser;
private orderFieldParser: GraphqlQueryOrderFieldParser;
@ -31,14 +32,17 @@ export class GraphqlQueryParser {
constructor(
fieldMetadataMapByName: FieldMetadataMap,
fieldMetadataMapByJoinColumnName: FieldMetadataMap,
objectMetadataMaps: ObjectMetadataMaps,
featureFlagsMap: FeatureFlagMap,
) {
this.objectMetadataMaps = objectMetadataMaps;
this.fieldMetadataMapByName = fieldMetadataMapByName;
this.fieldMetadataMapByJoinColumnName = fieldMetadataMapByJoinColumnName;
this.featureFlagsMap = featureFlagsMap;
this.filterConditionParser = new GraphqlQueryFilterConditionParser(
this.fieldMetadataMapByName,
this.fieldMetadataMapByJoinColumnName,
featureFlagsMap,
);
this.orderFieldParser = new GraphqlQueryOrderFieldParser(

View File

@ -23,7 +23,7 @@ import { ObjectMetadataMaps } from 'src/engine/metadata-modules/types/object-met
import { getObjectMetadataMapItemByNameSingular } from 'src/engine/metadata-modules/utils/get-object-metadata-map-item-by-name-singular.util';
import { WorkspaceDataSource } from 'src/engine/twenty-orm/datasource/workspace.datasource';
import { formatResult } from 'src/engine/twenty-orm/utils/format-result.util';
import { isFieldMetadataOfType } from 'src/engine/utils/is-field-metadata-of-type.util';
import { isFieldMetadataInterfaceOfType } from 'src/engine/utils/is-field-metadata-of-type.util';
@Injectable()
export class ProcessNestedRelationsV2Helper {
@ -103,7 +103,10 @@ export class ProcessNestedRelationsV2Helper {
parentObjectMetadataItem.fieldsByName[sourceFieldName];
if (
!isFieldMetadataOfType(sourceFieldMetadata, FieldMetadataType.RELATION)
!isFieldMetadataInterfaceOfType(
sourceFieldMetadata,
FieldMetadataType.RELATION,
)
) {
// TODO: Maybe we should throw an error here ?
return;
@ -149,7 +152,7 @@ export class ProcessNestedRelationsV2Helper {
? `"${targetRelationName}Id"`
: 'id',
ids: relationIds,
limit,
limit: limit * parentObjectRecords.length,
objectMetadataMaps,
targetObjectMetadata,
aggregate,
@ -313,6 +316,7 @@ export class ProcessNestedRelationsV2Helper {
result,
targetObjectMetadata,
objectMetadataMaps,
true,
);
return { relationResults, relationAggregatedFieldsResult };

View File

@ -453,6 +453,7 @@ export class ProcessNestedRelationsHelper {
result,
referenceObjectMetadata,
objectMetadataMaps,
false,
);
return { relationResults, relationAggregatedFieldsResult };

View File

@ -130,6 +130,7 @@ export abstract class GraphqlQueryBaseResolverService<
const graphqlQueryParser = new GraphqlQueryParser(
objectMetadataItemWithFieldMaps.fieldsByName,
objectMetadataItemWithFieldMaps.fieldsByJoinColumnName,
options.objectMetadataMaps,
featureFlagsMap,
);

View File

@ -44,6 +44,7 @@ export class GraphqlQueryCreateManyResolverService extends GraphqlQueryBaseResol
objectRecords,
objectMetadataItemWithFieldMaps,
objectMetadataMaps,
featureFlagsMap,
);
this.apiEventEmitterService.emitCreateEvents(
@ -301,6 +302,7 @@ export class GraphqlQueryCreateManyResolverService extends GraphqlQueryBaseResol
objectRecords: InsertResult,
objectMetadataItemWithFieldMaps: ObjectMetadataItemWithFieldMaps,
objectMetadataMaps: ObjectMetadataMaps,
featureFlagsMap: Record<FeatureFlagKey, boolean>,
): Promise<ObjectRecord[]> {
const queryBuilder = executionArgs.repository.createQueryBuilder(
objectMetadataItemWithFieldMaps.nameSingular,
@ -317,6 +319,7 @@ export class GraphqlQueryCreateManyResolverService extends GraphqlQueryBaseResol
nonFormattedUpsertedRecords,
objectMetadataItemWithFieldMaps,
objectMetadataMaps,
featureFlagsMap[FeatureFlagKey.IsNewRelationEnabled],
);
}

View File

@ -53,6 +53,7 @@ export class GraphqlQueryCreateOneResolverService extends GraphqlQueryBaseResolv
nonFormattedUpsertedRecords,
objectMetadataItemWithFieldMaps,
objectMetadataMaps,
featureFlagsMap[FeatureFlagKey.IsNewRelationEnabled],
);
this.apiEventEmitterService.emitCreateEvents(

View File

@ -54,6 +54,7 @@ export class GraphqlQueryDeleteManyResolverService extends GraphqlQueryBaseResol
nonFormattedDeletedObjectRecords.raw,
objectMetadataItemWithFieldMaps,
objectMetadataMaps,
featureFlagsMap[FeatureFlagKey.IsNewRelationEnabled],
);
this.apiEventEmitterService.emitDeletedEvents(

View File

@ -47,6 +47,7 @@ export class GraphqlQueryDeleteOneResolverService extends GraphqlQueryBaseResolv
nonFormattedDeletedObjectRecords.raw,
objectMetadataItemWithFieldMaps,
objectMetadataMaps,
featureFlagsMap[FeatureFlagKey.IsNewRelationEnabled],
);
this.apiEventEmitterService.emitDeletedEvents(

View File

@ -52,6 +52,7 @@ export class GraphqlQueryDestroyManyResolverService extends GraphqlQueryBaseReso
nonFormattedDeletedObjectRecords.raw,
objectMetadataItemWithFieldMaps,
objectMetadataMaps,
featureFlagsMap[FeatureFlagKey.IsNewRelationEnabled],
);
this.apiEventEmitterService.emitDestroyEvents(

View File

@ -52,6 +52,7 @@ export class GraphqlQueryDestroyOneResolverService extends GraphqlQueryBaseResol
nonFormattedDeletedObjectRecords.raw,
objectMetadataItemWithFieldMaps,
objectMetadataMaps,
featureFlagsMap[FeatureFlagKey.IsNewRelationEnabled],
);
this.apiEventEmitterService.emitDestroyEvents(

View File

@ -64,6 +64,7 @@ export class GraphqlQueryFindDuplicatesResolverService extends GraphqlQueryBaseR
const graphqlQueryParser = new GraphqlQueryParser(
objectMetadataItemWithFieldsMaps?.fieldsByName,
objectMetadataItemWithFieldsMaps?.fieldsByJoinColumnName,
objectMetadataMaps,
featureFlagsMap,
);
@ -85,6 +86,7 @@ export class GraphqlQueryFindDuplicatesResolverService extends GraphqlQueryBaseR
nonFormattedObjectRecords,
objectMetadataItemWithFieldMaps,
objectMetadataMaps,
featureFlagsMap[FeatureFlagKey.IsNewRelationEnabled],
);
} else if (executionArgs.args.data && !isEmpty(executionArgs.args.data)) {
objectRecords = formatData(
@ -131,6 +133,7 @@ export class GraphqlQueryFindDuplicatesResolverService extends GraphqlQueryBaseR
nonFormattedDuplicates,
objectMetadataItemWithFieldMaps,
objectMetadataMaps,
featureFlagsMap[FeatureFlagKey.IsNewRelationEnabled],
);
return typeORMObjectRecordsParser.createConnection({

View File

@ -130,6 +130,7 @@ export class GraphqlQueryFindManyResolverService extends GraphqlQueryBaseResolve
nonFormattedObjectRecords,
objectMetadataItemWithFieldMaps,
objectMetadataMaps,
featureFlagsMap[FeatureFlagKey.IsNewRelationEnabled],
);
const { hasNextPage, hasPreviousPage } = getPaginationInfo(

View File

@ -59,6 +59,7 @@ export class GraphqlQueryFindOneResolverService extends GraphqlQueryBaseResolver
nonFormattedObjectRecord,
objectMetadataItemWithFieldMaps,
objectMetadataMaps,
featureFlagsMap[FeatureFlagKey.IsNewRelationEnabled],
);
if (!objectRecord) {

View File

@ -54,6 +54,7 @@ export class GraphqlQueryRestoreManyResolverService extends GraphqlQueryBaseReso
nonFormattedRestoredObjectRecords.raw,
objectMetadataItemWithFieldMaps,
objectMetadataMaps,
featureFlagsMap[FeatureFlagKey.IsNewRelationEnabled],
);
this.apiEventEmitterService.emitRestoreEvents(

View File

@ -47,6 +47,7 @@ export class GraphqlQueryRestoreOneResolverService extends GraphqlQueryBaseResol
nonFormattedRestoredObjectRecords.raw,
objectMetadataItemWithFieldMaps,
objectMetadataMaps,
featureFlagsMap[FeatureFlagKey.IsNewRelationEnabled],
);
this.apiEventEmitterService.emitRestoreEvents(

View File

@ -55,6 +55,7 @@ export class GraphqlQueryUpdateManyResolverService extends GraphqlQueryBaseResol
existingRecords,
objectMetadataItemWithFieldMaps,
objectMetadataMaps,
featureFlagsMap[FeatureFlagKey.IsNewRelationEnabled],
);
if (isEmpty(formattedExistingRecords)) {
@ -89,6 +90,7 @@ export class GraphqlQueryUpdateManyResolverService extends GraphqlQueryBaseResol
nonFormattedUpdatedObjectRecords.raw,
objectMetadataItemWithFieldMaps,
objectMetadataMaps,
featureFlagsMap[FeatureFlagKey.IsNewRelationEnabled],
);
this.apiEventEmitterService.emitUpdateEvents(

View File

@ -55,6 +55,7 @@ export class GraphqlQueryUpdateOneResolverService extends GraphqlQueryBaseResolv
existingRecords,
objectMetadataItemWithFieldMaps,
objectMetadataMaps,
featureFlagsMap[FeatureFlagKey.IsNewRelationEnabled],
);
if (isEmpty(formattedExistingRecords)) {
@ -74,6 +75,7 @@ export class GraphqlQueryUpdateOneResolverService extends GraphqlQueryBaseResolv
nonFormattedUpdatedObjectRecords.raw,
objectMetadataItemWithFieldMaps,
objectMetadataMaps,
featureFlagsMap[FeatureFlagKey.IsNewRelationEnabled],
);
this.apiEventEmitterService.emitUpdateEvents(

View File

@ -22,7 +22,7 @@ import { CompositeInputTypeDefinitionFactory } from 'src/engine/api/graphql/work
import { FeatureFlagService } from 'src/engine/core-modules/feature-flag/services/feature-flag.service';
import { FileService } from 'src/engine/core-modules/file/services/file.service';
import { ObjectMetadataMaps } from 'src/engine/metadata-modules/types/object-metadata-maps';
import { isFieldMetadataOfType } from 'src/engine/utils/is-field-metadata-of-type.util';
import { isFieldMetadataInterfaceOfType } from 'src/engine/utils/is-field-metadata-of-type.util';
// TODO: find a way to prevent conflict between handlers executing logic on object relations
// And this factory that is also executing logic on object relations
@ -153,7 +153,10 @@ export class QueryResultGettersFactory {
)
.filter(isDefined)
.filter((fieldMetadata) =>
isFieldMetadataOfType(fieldMetadata, FieldMetadataType.RELATION),
isFieldMetadataInterfaceOfType(
fieldMetadata,
FieldMetadataType.RELATION,
),
);
const relationFieldsProcessedMap = {} as Record<

View File

@ -15,7 +15,7 @@ import { RelationTypeV2Factory } from 'src/engine/api/graphql/workspace-schema-b
import { TypeDefinitionsStorage } from 'src/engine/api/graphql/workspace-schema-builder/storages/type-definitions.storage';
import { getResolverArgs } from 'src/engine/api/graphql/workspace-schema-builder/utils/get-resolver-args.util';
import { objectContainsRelationField } from 'src/engine/api/graphql/workspace-schema-builder/utils/object-contains-relation-field';
import { isFieldMetadataOfType } from 'src/engine/utils/is-field-metadata-of-type.util';
import { isFieldMetadataInterfaceOfType } from 'src/engine/utils/is-field-metadata-of-type.util';
import { ArgsFactory } from './args.factory';
@ -108,7 +108,12 @@ export class ExtendObjectTypeDefinitionV2Factory {
for (const fieldMetadata of objectMetadata.fields) {
// Ignore non-relation fields as they are already defined
if (!isFieldMetadataOfType(fieldMetadata, FieldMetadataType.RELATION)) {
if (
!isFieldMetadataInterfaceOfType(
fieldMetadata,
FieldMetadataType.RELATION,
)
) {
continue;
}

View File

@ -36,6 +36,7 @@ export class InputTypeDefinitionFactory {
objectMetadata: ObjectMetadataInterface,
kind: InputTypeDefinitionKind,
options: WorkspaceBuildSchemaOptions,
isNewRelationEnabled = false,
): InputTypeDefinition {
const inputType = new GraphQLInputObjectType({
name: `${pascalCase(objectMetadata.nameSingular)}${kind.toString()}Input`,
@ -58,6 +59,7 @@ export class InputTypeDefinitionFactory {
kind,
options,
this.inputTypeFactory,
isNewRelationEnabled,
),
and: {
type: andOrType,
@ -81,6 +83,7 @@ export class InputTypeDefinitionFactory {
kind,
options,
this.inputTypeFactory,
isNewRelationEnabled,
);
}
},

View File

@ -30,6 +30,7 @@ export class ObjectTypeDefinitionFactory {
objectMetadata: ObjectMetadataInterface,
kind: ObjectTypeDefinitionKind,
options: WorkspaceBuildSchemaOptions,
isNewRelationEnabled = false,
): ObjectTypeDefinition {
return {
target: objectMetadata.id,
@ -42,6 +43,7 @@ export class ObjectTypeDefinitionFactory {
kind,
options,
this.outputTypeFactory,
isNewRelationEnabled,
),
}),
};

View File

@ -58,7 +58,7 @@ export class TypeMapperService {
settings?: FieldMetadataSettings<FieldMetadataType>,
isIdField?: boolean,
): GraphQLScalarType | undefined {
if (isIdField || settings?.isForeignKey) {
if (isIdField || fieldMetadataType === FieldMetadataType.RELATION) {
return GraphQLID;
}
const typeScalarMapping = new Map<FieldMetadataType, GraphQLScalarType>([
@ -93,7 +93,7 @@ export class TypeMapperService {
settings?: FieldMetadataSettings<FieldMetadataType>,
isIdField?: boolean,
): GraphQLInputObjectType | GraphQLScalarType | undefined {
if (isIdField || settings?.isForeignKey) {
if (isIdField || fieldMetadataType === FieldMetadataType.RELATION) {
return IDFilterType;
}
@ -132,6 +132,7 @@ export class TypeMapperService {
): GraphQLInputType | undefined {
const typeOrderByMapping = new Map<FieldMetadataType, GraphQLEnumType>([
[FieldMetadataType.UUID, OrderByDirectionType],
[FieldMetadataType.RELATION, OrderByDirectionType],
[FieldMetadataType.TEXT, OrderByDirectionType],
[FieldMetadataType.DATE_TIME, OrderByDirectionType],
[FieldMetadataType.DATE, OrderByDirectionType],

View File

@ -51,11 +51,16 @@ export class TypeDefinitionsGenerator {
async generate(
objectMetadataCollection: ObjectMetadataInterface[],
options: WorkspaceBuildSchemaOptions,
isNewRelationEnabled: boolean,
) {
// Generate composite type objects first because they can be used in dynamic objects
await this.generateCompositeTypeDefs(options);
// Generate metadata objects
await this.generateMetadataTypeDefs(objectMetadataCollection, options);
await this.generateMetadataTypeDefs(
objectMetadataCollection,
options,
isNewRelationEnabled,
);
}
/**
@ -155,6 +160,7 @@ export class TypeDefinitionsGenerator {
private async generateMetadataTypeDefs(
dynamicObjectMetadataCollection: ObjectMetadataInterface[],
options: WorkspaceBuildSchemaOptions,
isNewRelationEnabled: boolean,
) {
this.logger.log(
`Generating metadata objects: [${dynamicObjectMetadataCollection
@ -164,9 +170,17 @@ export class TypeDefinitionsGenerator {
// Generate dynamic objects
this.generateEnumTypeDefs(dynamicObjectMetadataCollection, options);
this.generateObjectTypeDefs(dynamicObjectMetadataCollection, options);
this.generateObjectTypeDefs(
dynamicObjectMetadataCollection,
options,
isNewRelationEnabled,
);
this.generatePaginationTypeDefs(dynamicObjectMetadataCollection, options);
this.generateInputTypeDefs(dynamicObjectMetadataCollection, options);
this.generateInputTypeDefs(
dynamicObjectMetadataCollection,
options,
isNewRelationEnabled,
);
await this.generateExtendedObjectTypeDefs(
dynamicObjectMetadataCollection,
options,
@ -176,12 +190,14 @@ export class TypeDefinitionsGenerator {
private generateObjectTypeDefs(
objectMetadataCollection: ObjectMetadataInterface[] | CompositeType[],
options: WorkspaceBuildSchemaOptions,
isNewRelationEnabled: boolean,
) {
const objectTypeDefs = objectMetadataCollection.map((objectMetadata) =>
this.objectTypeDefinitionFactory.create(
objectMetadata,
ObjectTypeDefinitionKind.Plain,
options,
isNewRelationEnabled,
),
);
@ -209,6 +225,7 @@ export class TypeDefinitionsGenerator {
private generateInputTypeDefs(
objectMetadataCollection: ObjectMetadataInterface[],
options: WorkspaceBuildSchemaOptions,
isNewRelationEnabled: boolean,
) {
const inputTypeDefs = objectMetadataCollection
.map((objectMetadata) => {
@ -226,24 +243,28 @@ export class TypeDefinitionsGenerator {
objectMetadata,
InputTypeDefinitionKind.Create,
options,
isNewRelationEnabled,
),
// Input type for update
this.inputTypeDefinitionFactory.create(
optionalExtendedObjectMetadata,
InputTypeDefinitionKind.Update,
options,
isNewRelationEnabled,
),
// Filter input type
this.inputTypeDefinitionFactory.create(
optionalExtendedObjectMetadata,
InputTypeDefinitionKind.Filter,
options,
isNewRelationEnabled,
),
// OrderBy input type
this.inputTypeDefinitionFactory.create(
optionalExtendedObjectMetadata,
InputTypeDefinitionKind.OrderBy,
options,
isNewRelationEnabled,
),
];
})

View File

@ -8,11 +8,12 @@ import { FieldMetadataType } from 'twenty-shared/types';
import { WorkspaceBuildSchemaOptions } from 'src/engine/api/graphql/workspace-schema-builder/interfaces/workspace-build-schema-optionts.interface';
import { ObjectMetadataInterface } from 'src/engine/metadata-modules/field-metadata/interfaces/object-metadata.interface';
import { RelationType } from 'src/engine/metadata-modules/field-metadata/interfaces/relation-type.interface';
import { InputTypeDefinitionKind } from 'src/engine/api/graphql/workspace-schema-builder/factories/input-type-definition.factory';
import { ObjectTypeDefinitionKind } from 'src/engine/api/graphql/workspace-schema-builder/factories/object-type-definition.factory';
import { isCompositeFieldMetadataType } from 'src/engine/metadata-modules/field-metadata/utils/is-composite-field-metadata-type.util';
import { isRelationFieldMetadataType } from 'src/engine/utils/is-relation-field-metadata-type.util';
import { isFieldMetadataInterfaceOfType } from 'src/engine/utils/is-field-metadata-of-type.util';
type TypeFactory<T extends InputTypeDefinitionKind | ObjectTypeDefinitionKind> =
{
@ -40,13 +41,21 @@ export const generateFields = <
kind: T,
options: WorkspaceBuildSchemaOptions,
typeFactory: TypeFactory<T>,
isNewRelationEnabled = false,
): T extends InputTypeDefinitionKind
? GraphQLInputFieldConfigMap
: GraphQLFieldConfigMap<any, any> => {
const fields = {};
for (const fieldMetadata of objectMetadata.fields) {
if (isRelationFieldMetadataType(fieldMetadata.type)) {
if (
isFieldMetadataInterfaceOfType(
fieldMetadata,
FieldMetadataType.RELATION,
) &&
(fieldMetadata.settings?.relationType !== RelationType.MANY_TO_ONE ||
!isNewRelationEnabled)
) {
continue;
}
@ -80,6 +89,25 @@ export const generateFields = <
typeFactoryOptions,
);
if (
isFieldMetadataInterfaceOfType(
fieldMetadata,
FieldMetadataType.RELATION,
) &&
fieldMetadata.settings?.relationType === RelationType.MANY_TO_ONE
) {
const joinColumnName = fieldMetadata.settings?.joinColumnName;
if (!joinColumnName) {
throw new Error('Join column name is not defined');
}
fields[joinColumnName] = {
type,
description: fieldMetadata.description,
};
}
fields[fieldMetadata.name] = {
type,
description: fieldMetadata.description,

View File

@ -25,11 +25,13 @@ export class WorkspaceGraphQLSchemaFactory {
objectMetadataCollection: ObjectMetadataInterface[],
workspaceResolverBuilderMethods: WorkspaceResolverBuilderMethods,
options: WorkspaceBuildSchemaOptions = {},
isNewRelationEnabled = false,
): Promise<GraphQLSchema> {
// Generate type definitions
await this.typeDefinitionsGenerator.generate(
objectMetadataCollection,
options,
isNewRelationEnabled,
);
// Generate schema

View File

@ -149,6 +149,8 @@ export class WorkspaceSchemaFactory {
await this.workspaceGraphQLSchemaFactory.create(
objectMetadataCollection,
workspaceResolverBuilderMethodNames,
{},
isNewRelationEnabled,
);
usedScalarNames =

View File

@ -31,6 +31,7 @@ describe('checkFields', () => {
...objectMetadataItemMock,
fieldsById,
fieldsByName,
fieldsByJoinColumnName: {},
};
it('should check field types', () => {

View File

@ -32,6 +32,7 @@ describe('getFieldType', () => {
...objectMetadataItemMock,
fieldsById,
fieldsByName,
fieldsByJoinColumnName: {},
};
it('should get field type', () => {

View File

@ -61,6 +61,7 @@ describe('mapFieldMetadataToGraphqlQuery', () => {
...objectMetadataItemMock,
fieldsById,
fieldsByName,
fieldsByJoinColumnName: {},
};
const objectMetadataMapsMock: ObjectMetadataMaps = {

View File

@ -33,6 +33,7 @@ describe('checkFilterEnumValues', () => {
...objectMetadataItemMock,
fieldsById,
fieldsByName,
fieldsByJoinColumnName: {},
};
it('should check properly', () => {

View File

@ -43,6 +43,7 @@ describe('parseFilter', () => {
...objectMetadataItemMock,
fieldsById,
fieldsByName,
fieldsByJoinColumnName: {},
};
it('should parse string filter test 1', () => {