fix blocklist (#18332)

- The schema generator marked both the FK scalar and connect relation
input as required for non-nullable `MANY_TO_ONE` relations, but the
resolver rejects when both are provided making create mutations
impossible
- Fixed by making the connect input always optional in create input
types (the FK scalar still enforces the constraint)
- Added `createOne` pre-query hook for blocklist with ownership
validation


https://github.com/user-attachments/assets/aaae83d4-4747-4d16-a87c-8d8cad79d25d

---------

Co-authored-by: Charles Bochet <charles@twenty.com>
This commit is contained in:
neo773
2026-03-05 20:26:04 +01:00
committed by GitHub
co-authored by Charles Bochet
parent 62a634831d
commit ef003fb929
31 changed files with 340 additions and 204 deletions
@@ -5,18 +5,15 @@ import { isDefined } from 'twenty-shared/utils';
import { type ArgsMetadata } from 'src/engine/api/graphql/workspace-schema-builder/interfaces/param-metadata.interface';
import { TypeMapperService } from 'src/engine/api/graphql/workspace-schema-builder/services/type-mapper.service';
import { GqlTypesStorage } from 'src/engine/api/graphql/workspace-schema-builder/storages/gql-types.storage';
import { applyTypeOptionsForOutputType } from 'src/engine/api/graphql/workspace-schema-builder/utils/apply-type-options-for-output-type.util';
import { computeObjectMetadataInputTypeKey } from 'src/engine/api/graphql/workspace-schema-builder/utils/compute-stored-gql-type-key-utils/compute-object-metadata-input-type.util';
@Injectable()
export class ArgsTypeGenerator {
private readonly logger = new Logger(ArgsTypeGenerator.name);
constructor(
private readonly typeMapperService: TypeMapperService,
private readonly gqlTypesStorage: GqlTypesStorage,
) {}
constructor(private readonly gqlTypesStorage: GqlTypesStorage) {}
public generate({
args,
@@ -33,7 +30,7 @@ export class ArgsTypeGenerator {
// Argument is a scalar type
if (isDefined(arg.type)) {
const gqlType = this.typeMapperService.applyTypeOptions(arg.type, {
const gqlType = applyTypeOptionsForOutputType(arg.type, {
defaultValue: arg.defaultValue,
nullable: arg.isNullable,
isArray: arg.isArray,
@@ -66,7 +63,7 @@ export class ArgsTypeGenerator {
);
}
const gqlType = this.typeMapperService.applyTypeOptions(inputType, {
const gqlType = applyTypeOptionsForOutputType(inputType, {
nullable: arg.isNullable,
isArray: arg.isArray,
});
@@ -11,6 +11,7 @@ import { isDefined, pascalCase } from 'twenty-shared/utils';
import { GqlInputTypeDefinitionKind } from 'src/engine/api/graphql/workspace-schema-builder/enums/gql-input-type-definition-kind.enum';
import { TypeMapperService } from 'src/engine/api/graphql/workspace-schema-builder/services/type-mapper.service';
import { GqlTypesStorage } from 'src/engine/api/graphql/workspace-schema-builder/storages/gql-types.storage';
import { applyTypeOptionsForCreateInput } from 'src/engine/api/graphql/workspace-schema-builder/utils/apply-type-options-for-create-input.util';
import { computeCompositeFieldTypeOptions } from 'src/engine/api/graphql/workspace-schema-builder/utils/compute-composite-field-type-options.util';
import { computeCompositeFieldEnumTypeKey } from 'src/engine/api/graphql/workspace-schema-builder/utils/compute-stored-gql-type-key-utils/compute-composite-field-enum-type-key.util';
import { computeCompositeFieldInputTypeKey } from 'src/engine/api/graphql/workspace-schema-builder/utils/compute-stored-gql-type-key-utils/compute-composite-field-input-type-key.util';
@@ -87,10 +88,7 @@ export class CompositeFieldMetadataCreateGqlInputTypeGenerator {
throw new Error(message);
}
const modifiedType = this.typeMapperService.applyTypeOptions(
type,
typeOptions,
);
const modifiedType = applyTypeOptionsForCreateInput(type, typeOptions);
fields[property.name] = {
type: modifiedType,
@@ -11,12 +11,13 @@ import { isDefined, pascalCase } from 'twenty-shared/utils';
import { GqlInputTypeDefinitionKind } from 'src/engine/api/graphql/workspace-schema-builder/enums/gql-input-type-definition-kind.enum';
import { RelationFieldMetadataGqlInputTypeGenerator } from 'src/engine/api/graphql/workspace-schema-builder/graphql-type-generators/input-types/relation-field-metadata-gql-type.generator';
import {
TypeMapperService,
TypeOptions,
} from 'src/engine/api/graphql/workspace-schema-builder/services/type-mapper.service';
import { TypeMapperService } from 'src/engine/api/graphql/workspace-schema-builder/services/type-mapper.service';
import { GqlTypesStorage } from 'src/engine/api/graphql/workspace-schema-builder/storages/gql-types.storage';
import { type SchemaGenerationContext } from 'src/engine/api/graphql/workspace-schema-builder/types/schema-generation-context.type';
import {
type CreateInputTypeOptions,
applyTypeOptionsForCreateInput,
} from 'src/engine/api/graphql/workspace-schema-builder/utils/apply-type-options-for-create-input.util';
import { computeFieldInputTypeOptions } from 'src/engine/api/graphql/workspace-schema-builder/utils/compute-field-input-type-options.util';
import { computeCompositeFieldInputTypeKey } from 'src/engine/api/graphql/workspace-schema-builder/utils/compute-stored-gql-type-key-utils/compute-composite-field-input-type-key.util';
import { computeEnumFieldGqlTypeKey } from 'src/engine/api/graphql/workspace-schema-builder/utils/compute-stored-gql-type-key-utils/compute-enum-field-gql-type-key.util';
@@ -116,7 +117,7 @@ export class ObjectMetadataCreateGqlInputTypeGenerator {
private generateEnumFieldCreateInputType(
objectNameSingular: string,
fieldMetadata: FlatFieldMetadata,
typeOptions: TypeOptions,
typeOptions: CreateInputTypeOptions,
) {
const key = computeEnumFieldGqlTypeKey(
objectNameSingular,
@@ -135,7 +136,7 @@ export class ObjectMetadataCreateGqlInputTypeGenerator {
throw new Error(message);
}
const modifiedEnumType = this.typeMapperService.applyTypeOptions(
const modifiedEnumType = applyTypeOptionsForCreateInput(
enumType,
typeOptions,
);
@@ -150,7 +151,7 @@ export class ObjectMetadataCreateGqlInputTypeGenerator {
private generateCompositeFieldCreateInputType(
fieldMetadata: FlatFieldMetadata,
typeOptions: TypeOptions,
typeOptions: CreateInputTypeOptions,
) {
const key = computeCompositeFieldInputTypeKey(
fieldMetadata.type,
@@ -169,7 +170,7 @@ export class ObjectMetadataCreateGqlInputTypeGenerator {
throw new Error(message);
}
const modifiedCompositeType = this.typeMapperService.applyTypeOptions(
const modifiedCompositeType = applyTypeOptionsForCreateInput(
compositeType,
typeOptions,
);
@@ -184,7 +185,7 @@ export class ObjectMetadataCreateGqlInputTypeGenerator {
private generateAtomicFieldCreateInputType(
fieldMetadata: FlatFieldMetadata,
typeOptions: TypeOptions,
typeOptions: CreateInputTypeOptions,
) {
const type = this.typeMapperService.mapToPreBuiltGraphQLInputType({
fieldMetadataType: fieldMetadata.type,
@@ -201,10 +202,7 @@ export class ObjectMetadataCreateGqlInputTypeGenerator {
throw new Error(message);
}
const modifiedType = this.typeMapperService.applyTypeOptions(
type,
typeOptions,
);
const modifiedType = applyTypeOptionsForCreateInput(type, typeOptions);
return {
[fieldMetadata.name]: {
@@ -101,13 +101,8 @@ export class CompositeFieldMetadataFilterGqlInputTypeGenerator {
throw new Error(message);
}
const modifiedType = this.typeMapperService.applyTypeOptions(
type,
typeOptions,
);
fields[property.name] = {
type: modifiedType,
type,
description: property.description,
};
}
@@ -3,6 +3,7 @@ import { Injectable, Logger } from '@nestjs/common';
import {
GraphQLInputFieldConfigMap,
GraphQLInputObjectType,
GraphQLList,
isEnumType,
isInputObjectType,
isObjectType,
@@ -11,10 +12,7 @@ import { isDefined, pascalCase } from 'twenty-shared/utils';
import { GqlInputTypeDefinitionKind } from 'src/engine/api/graphql/workspace-schema-builder/enums/gql-input-type-definition-kind.enum';
import { RelationFieldMetadataGqlInputTypeGenerator } from 'src/engine/api/graphql/workspace-schema-builder/graphql-type-generators/input-types/relation-field-metadata-gql-type.generator';
import {
TypeMapperService,
TypeOptions,
} from 'src/engine/api/graphql/workspace-schema-builder/services/type-mapper.service';
import { TypeMapperService } from 'src/engine/api/graphql/workspace-schema-builder/services/type-mapper.service';
import { GqlTypesStorage } from 'src/engine/api/graphql/workspace-schema-builder/storages/gql-types.storage';
import { computeFieldInputTypeOptions } from 'src/engine/api/graphql/workspace-schema-builder/utils/compute-field-input-type-options.util';
import { computeCompositeFieldInputTypeKey } from 'src/engine/api/graphql/workspace-schema-builder/utils/compute-stored-gql-type-key-utils/compute-composite-field-input-type-key.util';
@@ -62,11 +60,6 @@ export class ObjectMetadataFilterGqlInputTypeGenerator {
fields: FlatFieldMetadata[],
inputType: GraphQLInputObjectType,
): GraphQLInputFieldConfigMap {
const andOrType = this.typeMapperService.applyTypeOptions(inputType, {
isArray: true,
arrayDepth: 1,
nullable: true,
});
const allGeneratedFields: GraphQLInputFieldConfigMap = {};
for (const fieldMetadata of fields) {
@@ -94,13 +87,10 @@ export class ObjectMetadataFilterGqlInputTypeGenerator {
generatedFields = this.generateEnumFieldFilterInputType(
objectNameSingular,
fieldMetadata,
typeOptions,
);
} else if (isCompositeFieldMetadataType(fieldMetadata.type)) {
generatedFields = this.generateCompositeFieldFilterInputType(
fieldMetadata,
typeOptions,
);
generatedFields =
this.generateCompositeFieldFilterInputType(fieldMetadata);
} else {
generatedFields = this.generateAtomicFieldFilterInputType(
fieldMetadata,
@@ -113,15 +103,13 @@ export class ObjectMetadataFilterGqlInputTypeGenerator {
return {
...allGeneratedFields,
and: {
type: andOrType,
type: new GraphQLList(inputType),
},
or: {
type: andOrType,
type: new GraphQLList(inputType),
},
not: {
type: this.typeMapperService.applyTypeOptions(inputType, {
nullable: true,
}),
type: inputType,
},
};
}
@@ -129,7 +117,6 @@ export class ObjectMetadataFilterGqlInputTypeGenerator {
private generateEnumFieldFilterInputType(
objectNameSingular: string,
fieldMetadata: FlatFieldMetadata,
typeOptions: TypeOptions,
) {
const key = computeEnumFieldGqlTypeKey(
objectNameSingular,
@@ -143,7 +130,6 @@ export class ObjectMetadataFilterGqlInputTypeGenerator {
this.logger.error(message, {
fieldMetadata,
typeOptions,
});
throw new Error(message);
}
@@ -160,7 +146,6 @@ export class ObjectMetadataFilterGqlInputTypeGenerator {
private generateCompositeFieldFilterInputType(
fieldMetadata: FlatFieldMetadata,
typeOptions: TypeOptions,
) {
const key = computeCompositeFieldInputTypeKey(
fieldMetadata.type,
@@ -174,7 +159,6 @@ export class ObjectMetadataFilterGqlInputTypeGenerator {
this.logger.error(message, {
fieldMetadata,
typeOptions,
});
throw new Error(message);
}
@@ -189,7 +173,7 @@ export class ObjectMetadataFilterGqlInputTypeGenerator {
private generateAtomicFieldFilterInputType(
fieldMetadata: FlatFieldMetadata,
typeOptions: TypeOptions,
typeOptions: { settings?: FlatFieldMetadata['settings'] },
) {
const type = this.typeMapperService.mapToFilterType(
fieldMetadata.type,
@@ -59,7 +59,7 @@ export class CompositeFieldMetadataGroupByGqlInputTypeGenerator {
continue;
}
const type = this.typeMapperService.applyTypeOptions(GraphQLBoolean, {});
const type = GraphQLBoolean;
fields[property.name] = {
type,
@@ -101,7 +101,7 @@ export class ObjectMetadataGroupByGqlInputTypeGenerator {
type = groupByDateGranularityInputType;
} else {
type = this.typeMapperService.applyTypeOptions(GraphQLBoolean, {});
type = GraphQLBoolean;
}
return {
@@ -76,13 +76,8 @@ export class CompositeFieldMetadataOrderByGqlInputTypeGenerator {
throw new Error(message);
}
const modifiedType = this.typeMapperService.applyTypeOptions(
type,
typeOptions,
);
fields[property.name] = {
type: modifiedType,
type,
description: property.description,
};
}
@@ -58,7 +58,6 @@ export class ObjectMetadataOrderByBaseGenerator {
this.relationFieldMetadataGqlInputTypeGenerator.generateSimpleRelationFieldOrderByInputType(
{
fieldMetadata,
typeOptions,
isForGroupBy,
context,
},
@@ -101,7 +101,6 @@ export class ObjectMetadataOrderByWithGroupByGqlInputTypeGenerator {
this.relationFieldMetadataGqlInputTypeGenerator.generateSimpleRelationFieldOrderByInputType(
{
fieldMetadata,
typeOptions,
context,
},
);
@@ -9,12 +9,13 @@ import { FieldMetadataType, RelationType } from 'twenty-shared/types';
import { isDefined } from 'twenty-shared/utils';
import { GqlInputTypeDefinitionKind } from 'src/engine/api/graphql/workspace-schema-builder/enums/gql-input-type-definition-kind.enum';
import {
TypeMapperService,
TypeOptions,
} from 'src/engine/api/graphql/workspace-schema-builder/services/type-mapper.service';
import { TypeMapperService } from 'src/engine/api/graphql/workspace-schema-builder/services/type-mapper.service';
import { GqlTypesStorage } from 'src/engine/api/graphql/workspace-schema-builder/storages/gql-types.storage';
import { type SchemaGenerationContext } from 'src/engine/api/graphql/workspace-schema-builder/types/schema-generation-context.type';
import {
type CreateInputTypeOptions,
applyTypeOptionsForCreateInput,
} from 'src/engine/api/graphql/workspace-schema-builder/utils/apply-type-options-for-create-input.util';
import { computeObjectMetadataInputTypeKey } from 'src/engine/api/graphql/workspace-schema-builder/utils/compute-stored-gql-type-key-utils/compute-object-metadata-input-type.util';
import { computeRelationConnectInputTypeKey } from 'src/engine/api/graphql/workspace-schema-builder/utils/compute-stored-gql-type-key-utils/compute-relation-connect-input-type-key.util';
import { extractGraphQLRelationFieldNames } from 'src/engine/api/graphql/workspace-schema-builder/utils/extract-graphql-relation-field-names.util';
@@ -39,7 +40,7 @@ export class RelationFieldMetadataGqlInputTypeGenerator {
fieldMetadata: FlatFieldMetadata<
FieldMetadataType.RELATION | FieldMetadataType.MORPH_RELATION
>;
typeOptions: TypeOptions;
typeOptions: CreateInputTypeOptions;
}) {
if (fieldMetadata.settings?.relationType === RelationType.ONE_TO_MANY)
return {};
@@ -61,10 +62,10 @@ export class RelationFieldMetadataGqlInputTypeGenerator {
throw new Error(message);
}
const modifiedType = this.typeMapperService.applyTypeOptions(
type,
typeOptions,
);
const modifiedType = applyTypeOptionsForCreateInput(type, {
...typeOptions,
nullable: true,
});
return {
[joinColumnName]: {
@@ -81,7 +82,7 @@ export class RelationFieldMetadataGqlInputTypeGenerator {
fieldMetadata: FlatFieldMetadata<
FieldMetadataType.RELATION | FieldMetadataType.MORPH_RELATION
>;
typeOptions: TypeOptions;
typeOptions: { settings?: FlatFieldMetadata['settings'] };
}) {
if (fieldMetadata.settings?.relationType === RelationType.ONE_TO_MANY)
return {};
@@ -103,14 +104,9 @@ export class RelationFieldMetadataGqlInputTypeGenerator {
throw new Error(message);
}
const modifiedType = this.typeMapperService.applyTypeOptions(
type,
typeOptions,
);
return {
[joinColumnName]: {
type: modifiedType,
type,
description: fieldMetadata.description,
},
};
@@ -118,14 +114,12 @@ export class RelationFieldMetadataGqlInputTypeGenerator {
public generateSimpleRelationFieldOrderByInputType({
fieldMetadata,
typeOptions,
isForGroupBy,
context,
}: {
fieldMetadata: FlatFieldMetadata<
FieldMetadataType.RELATION | FieldMetadataType.MORPH_RELATION
>;
typeOptions: TypeOptions;
isForGroupBy?: boolean;
context?: SchemaGenerationContext;
}) {
@@ -142,19 +136,13 @@ export class RelationFieldMetadataGqlInputTypeGenerator {
this.logger.error(message, {
type,
typeOptions,
});
throw new Error(message);
}
const modifiedType = this.typeMapperService.applyTypeOptions(
type,
typeOptions,
);
const fields: GraphQLInputFieldConfigMap = {
[joinColumnName]: {
type: modifiedType,
type,
description: fieldMetadata.description,
},
};
@@ -250,7 +238,7 @@ export class RelationFieldMetadataGqlInputTypeGenerator {
fieldMetadata: FlatFieldMetadata<
FieldMetadataType.RELATION | FieldMetadataType.MORPH_RELATION
>;
typeOptions: TypeOptions;
typeOptions: CreateInputTypeOptions;
}) {
if (fieldMetadata.settings?.relationType === RelationType.ONE_TO_MANY) {
return {};
@@ -277,7 +265,10 @@ export class RelationFieldMetadataGqlInputTypeGenerator {
return {
[fieldMetadataName]: {
type: this.typeMapperService.applyTypeOptions(type, typeOptions),
type: applyTypeOptionsForCreateInput(type, {
...typeOptions,
nullable: true,
}),
description: fieldMetadata.description,
},
};
@@ -11,6 +11,7 @@ import { isDefined, pascalCase } from 'twenty-shared/utils';
import { GqlInputTypeDefinitionKind } from 'src/engine/api/graphql/workspace-schema-builder/enums/gql-input-type-definition-kind.enum';
import { TypeMapperService } from 'src/engine/api/graphql/workspace-schema-builder/services/type-mapper.service';
import { GqlTypesStorage } from 'src/engine/api/graphql/workspace-schema-builder/storages/gql-types.storage';
import { applyTypeOptionsForUpdateInput } from 'src/engine/api/graphql/workspace-schema-builder/utils/apply-type-options-for-update-input.util';
import { computeCompositeFieldTypeOptions } from 'src/engine/api/graphql/workspace-schema-builder/utils/compute-composite-field-type-options.util';
import { computeCompositeFieldEnumTypeKey } from 'src/engine/api/graphql/workspace-schema-builder/utils/compute-stored-gql-type-key-utils/compute-composite-field-enum-type-key.util';
import { computeCompositeFieldInputTypeKey } from 'src/engine/api/graphql/workspace-schema-builder/utils/compute-stored-gql-type-key-utils/compute-composite-field-input-type-key.util';
@@ -88,10 +89,7 @@ export class CompositeFieldMetadataUpdateGqlInputTypeGenerator {
throw new Error(message);
}
const modifiedType = this.typeMapperService.applyTypeOptions(
type,
typeOptions,
);
const modifiedType = applyTypeOptionsForUpdateInput(type, typeOptions);
fields[property.name] = {
type: modifiedType,
@@ -18,6 +18,7 @@ import { RelationFieldMetadataGqlInputTypeGenerator } from 'src/engine/api/graph
import { TypeMapperService } from 'src/engine/api/graphql/workspace-schema-builder/services/type-mapper.service';
import { GqlTypesStorage } from 'src/engine/api/graphql/workspace-schema-builder/storages/gql-types.storage';
import { type SchemaGenerationContext } from 'src/engine/api/graphql/workspace-schema-builder/types/schema-generation-context.type';
import { applyTypeOptionsForUpdateInput } from 'src/engine/api/graphql/workspace-schema-builder/utils/apply-type-options-for-update-input.util';
import { computeFieldInputTypeOptions } from 'src/engine/api/graphql/workspace-schema-builder/utils/compute-field-input-type-options.util';
import { computeCompositeFieldInputTypeKey } from 'src/engine/api/graphql/workspace-schema-builder/utils/compute-stored-gql-type-key-utils/compute-composite-field-input-type-key.util';
import { computeEnumFieldGqlTypeKey } from 'src/engine/api/graphql/workspace-schema-builder/utils/compute-stored-gql-type-key-utils/compute-enum-field-gql-type-key.util';
@@ -156,10 +157,7 @@ export class ObjectMetadataUpdateGqlInputTypeGenerator {
}
}
const modifiedType = this.typeMapperService.applyTypeOptions(
type,
typeOptions,
);
const modifiedType = applyTypeOptionsForUpdateInput(type, typeOptions);
generatedFields = {
[fieldMetadata.name]: {
@@ -8,6 +8,7 @@ import { ObjectTypeDefinitionKind } from 'src/engine/api/graphql/workspace-schem
import { TypeMapperService } from 'src/engine/api/graphql/workspace-schema-builder/services/type-mapper.service';
import { GqlTypesStorage } from 'src/engine/api/graphql/workspace-schema-builder/storages/gql-types.storage';
import { GraphQLOutputTypeFieldConfigMap } from 'src/engine/api/graphql/workspace-schema-builder/types/graphql-field-config-map.types';
import { applyTypeOptionsForOutputType } from 'src/engine/api/graphql/workspace-schema-builder/utils/apply-type-options-for-output-type.util';
import { computeCompositeFieldEnumTypeKey } from 'src/engine/api/graphql/workspace-schema-builder/utils/compute-stored-gql-type-key-utils/compute-composite-field-enum-type-key.util';
import { computeCompositeFieldObjectTypeKey } from 'src/engine/api/graphql/workspace-schema-builder/utils/compute-stored-gql-type-key-utils/compute-composite-field-object-type-key.util';
import { isEnumFieldMetadataType } from 'src/engine/metadata-modules/field-metadata/utils/is-enum-field-metadata-type.util';
@@ -86,10 +87,7 @@ export class CompositeFieldMetadataGqlObjectTypeGenerator {
throw new Error(message);
}
const modifiedType = this.typeMapperService.applyTypeOptions(
type,
typeOptions,
);
const modifiedType = applyTypeOptionsForOutputType(type, typeOptions);
fields[property.name] = {
type: modifiedType,
@@ -6,8 +6,8 @@ import { isDefined, pascalCase } from 'twenty-shared/utils';
import { ObjectTypeDefinitionKind } from 'src/engine/api/graphql/workspace-schema-builder/enums/object-type-definition-kind.enum';
import { AggregationObjectTypeGenerator } from 'src/engine/api/graphql/workspace-schema-builder/graphql-type-generators/object-types/aggregation-type.generator';
import { PageInfoType } from 'src/engine/api/graphql/workspace-schema-builder/graphql-types/object';
import { TypeMapperService } from 'src/engine/api/graphql/workspace-schema-builder/services/type-mapper.service';
import { GqlTypesStorage } from 'src/engine/api/graphql/workspace-schema-builder/storages/gql-types.storage';
import { applyTypeOptionsForOutputType } from 'src/engine/api/graphql/workspace-schema-builder/utils/apply-type-options-for-output-type.util';
import { GraphQLOutputTypeFieldConfigMap } from 'src/engine/api/graphql/workspace-schema-builder/types/graphql-field-config-map.types';
import { computeObjectMetadataObjectTypeKey } from 'src/engine/api/graphql/workspace-schema-builder/utils/compute-stored-gql-type-key-utils/compute-object-metadata-object-type-key.util';
import { FlatFieldMetadata } from 'src/engine/metadata-modules/flat-field-metadata/types/flat-field-metadata.type';
@@ -19,7 +19,6 @@ export class ConnectionGqlObjectTypeGenerator {
constructor(
private readonly aggregationObjectTypeGenerator: AggregationObjectTypeGenerator,
private readonly typeMapperService: TypeMapperService,
private readonly gqlTypesStorage: GqlTypesStorage,
) {}
@@ -73,7 +72,7 @@ export class ConnectionGqlObjectTypeGenerator {
}
fields.edges = {
type: this.typeMapperService.applyTypeOptions(edgeType, {
type: applyTypeOptionsForOutputType(edgeType, {
isArray: true,
arrayDepth: 1,
nullable: false,
@@ -81,7 +80,7 @@ export class ConnectionGqlObjectTypeGenerator {
};
fields.pageInfo = {
type: this.typeMapperService.applyTypeOptions(PageInfoType, {
type: applyTypeOptionsForOutputType(PageInfoType, {
nullable: false,
}),
};
@@ -5,8 +5,8 @@ import { isDefined, pascalCase } from 'twenty-shared/utils';
import { ObjectTypeDefinitionKind } from 'src/engine/api/graphql/workspace-schema-builder/enums/object-type-definition-kind.enum';
import { CursorScalarType } from 'src/engine/api/graphql/workspace-schema-builder/graphql-types/scalars';
import { TypeMapperService } from 'src/engine/api/graphql/workspace-schema-builder/services/type-mapper.service';
import { GqlTypesStorage } from 'src/engine/api/graphql/workspace-schema-builder/storages/gql-types.storage';
import { applyTypeOptionsForOutputType } from 'src/engine/api/graphql/workspace-schema-builder/utils/apply-type-options-for-output-type.util';
import { GraphQLOutputTypeFieldConfigMap } from 'src/engine/api/graphql/workspace-schema-builder/types/graphql-field-config-map.types';
import { computeObjectMetadataObjectTypeKey } from 'src/engine/api/graphql/workspace-schema-builder/utils/compute-stored-gql-type-key-utils/compute-object-metadata-object-type-key.util';
import { type FlatObjectMetadata } from 'src/engine/metadata-modules/flat-object-metadata/types/flat-object-metadata.type';
@@ -15,10 +15,7 @@ import { type FlatObjectMetadata } from 'src/engine/metadata-modules/flat-object
export class EdgeGqlObjectTypeGenerator {
private readonly logger = new Logger(EdgeGqlObjectTypeGenerator.name);
constructor(
private readonly typeMapperService: TypeMapperService,
private readonly gqlTypesStorage: GqlTypesStorage,
) {}
constructor(private readonly gqlTypesStorage: GqlTypesStorage) {}
public buildAndStore(flatObjectMetadata: FlatObjectMetadata) {
const kind = ObjectTypeDefinitionKind.Edge;
@@ -60,18 +57,15 @@ export class EdgeGqlObjectTypeGenerator {
}
const typeOptions = {
nullable: false,
nullable: false as const,
};
fields.node = {
type: this.typeMapperService.applyTypeOptions(objectType, typeOptions),
type: applyTypeOptionsForOutputType(objectType, typeOptions),
};
fields.cursor = {
type: this.typeMapperService.applyTypeOptions(
CursorScalarType,
typeOptions,
),
type: applyTypeOptionsForOutputType(CursorScalarType, typeOptions),
};
return fields;
@@ -5,8 +5,8 @@ import GraphQLJSON from 'graphql-type-json';
import { isDefined, pascalCase } from 'twenty-shared/utils';
import { ObjectTypeDefinitionKind } from 'src/engine/api/graphql/workspace-schema-builder/enums/object-type-definition-kind.enum';
import { TypeMapperService } from 'src/engine/api/graphql/workspace-schema-builder/services/type-mapper.service';
import { GqlTypesStorage } from 'src/engine/api/graphql/workspace-schema-builder/storages/gql-types.storage';
import { applyTypeOptionsForOutputType } from 'src/engine/api/graphql/workspace-schema-builder/utils/apply-type-options-for-output-type.util';
import { GraphQLOutputTypeFieldConfigMap } from 'src/engine/api/graphql/workspace-schema-builder/types/graphql-field-config-map.types';
import { computeObjectMetadataObjectTypeKey } from 'src/engine/api/graphql/workspace-schema-builder/utils/compute-stored-gql-type-key-utils/compute-object-metadata-object-type-key.util';
import { type FlatObjectMetadata } from 'src/engine/metadata-modules/flat-object-metadata/types/flat-object-metadata.type';
@@ -17,10 +17,7 @@ export class GroupByConnectionGqlObjectTypeGenerator {
GroupByConnectionGqlObjectTypeGenerator.name,
);
constructor(
private readonly typeMapperService: TypeMapperService,
private readonly gqlTypesStorage: GqlTypesStorage,
) {}
constructor(private readonly gqlTypesStorage: GqlTypesStorage) {}
public buildAndStore(flatObjectMetadata: FlatObjectMetadata) {
const kind = ObjectTypeDefinitionKind.GroupByConnection;
@@ -65,7 +62,7 @@ export class GroupByConnectionGqlObjectTypeGenerator {
Object.assign(fields, connection.toConfig().fields);
fields.groupByDimensionValues = {
type: this.typeMapperService.applyTypeOptions(GraphQLJSON, {
type: applyTypeOptionsForOutputType(GraphQLJSON, {
nullable: false,
}),
};
@@ -13,6 +13,7 @@ import { RelationFieldMetadataGqlObjectTypeGenerator } from 'src/engine/api/grap
import { TypeMapperService } from 'src/engine/api/graphql/workspace-schema-builder/services/type-mapper.service';
import { GqlTypesStorage } from 'src/engine/api/graphql/workspace-schema-builder/storages/gql-types.storage';
import { GraphQLOutputTypeFieldConfigMap } from 'src/engine/api/graphql/workspace-schema-builder/types/graphql-field-config-map.types';
import { applyTypeOptionsForOutputType } from 'src/engine/api/graphql/workspace-schema-builder/utils/apply-type-options-for-output-type.util';
import { computeCompositeFieldObjectTypeKey } from 'src/engine/api/graphql/workspace-schema-builder/utils/compute-stored-gql-type-key-utils/compute-composite-field-object-type-key.util';
import { computeEnumFieldGqlTypeKey } from 'src/engine/api/graphql/workspace-schema-builder/utils/compute-stored-gql-type-key-utils/compute-enum-field-gql-type-key.util';
import { computeObjectMetadataObjectTypeKey } from 'src/engine/api/graphql/workspace-schema-builder/utils/compute-stored-gql-type-key-utils/compute-object-metadata-object-type-key.util';
@@ -150,7 +151,7 @@ export class ObjectMetadataGqlObjectTypeGenerator {
}
}
const modifiedType = this.typeMapperService.applyTypeOptions(
const modifiedType = applyTypeOptionsForOutputType(
type,
typeFactoryOptions,
);
@@ -3,10 +3,11 @@ import { Injectable, Logger } from '@nestjs/common';
import { FieldMetadataType, RelationType } from 'twenty-shared/types';
import { isDefined } from 'twenty-shared/utils';
import { TypeMapperService } from 'src/engine/api/graphql/workspace-schema-builder/services/type-mapper.service';
import {
TypeMapperService,
TypeOptions,
} from 'src/engine/api/graphql/workspace-schema-builder/services/type-mapper.service';
type OutputTypeOptions,
applyTypeOptionsForOutputType,
} from 'src/engine/api/graphql/workspace-schema-builder/utils/apply-type-options-for-output-type.util';
import { extractGraphQLRelationFieldNames } from 'src/engine/api/graphql/workspace-schema-builder/utils/extract-graphql-relation-field-names.util';
import { type FlatFieldMetadata } from 'src/engine/metadata-modules/flat-field-metadata/types/flat-field-metadata.type';
@@ -25,7 +26,7 @@ export class RelationFieldMetadataGqlObjectTypeGenerator {
fieldMetadata: FlatFieldMetadata<
FieldMetadataType.RELATION | FieldMetadataType.MORPH_RELATION
>;
typeOptions: TypeOptions;
typeOptions: OutputTypeOptions;
}) {
if (fieldMetadata.settings?.relationType === RelationType.ONE_TO_MANY)
return {};
@@ -47,10 +48,7 @@ export class RelationFieldMetadataGqlObjectTypeGenerator {
throw new Error(message);
}
const modifiedType = this.typeMapperService.applyTypeOptions(
type,
typeOptions,
);
const modifiedType = applyTypeOptionsForOutputType(type, typeOptions);
return {
[joinColumnName]: {
@@ -9,8 +9,8 @@ import { WorkspaceResolverBuilderService } from 'src/engine/api/graphql/workspac
import { GqlOperation } from 'src/engine/api/graphql/workspace-schema-builder/enums/gql-operation.enum';
import { ObjectTypeDefinitionKind } from 'src/engine/api/graphql/workspace-schema-builder/enums/object-type-definition-kind.enum';
import { ArgsTypeGenerator } from 'src/engine/api/graphql/workspace-schema-builder/graphql-type-generators/args-type/args-type.generator';
import { TypeMapperService } from 'src/engine/api/graphql/workspace-schema-builder/services/type-mapper.service';
import { GqlTypesStorage } from 'src/engine/api/graphql/workspace-schema-builder/storages/gql-types.storage';
import { applyTypeOptionsForOutputType } from 'src/engine/api/graphql/workspace-schema-builder/utils/apply-type-options-for-output-type.util';
import { type SchemaGenerationContext } from 'src/engine/api/graphql/workspace-schema-builder/types/schema-generation-context.type';
import { GraphQLRootTypeFieldConfigMap } from 'src/engine/api/graphql/workspace-schema-builder/types/graphql-field-config-map.types';
import { computeObjectMetadataObjectTypeKey } from 'src/engine/api/graphql/workspace-schema-builder/utils/compute-stored-gql-type-key-utils/compute-object-metadata-object-type-key.util';
@@ -24,7 +24,6 @@ export class RootTypeGenerator {
constructor(
private readonly gqlTypesStorage: GqlTypesStorage,
private readonly typeMapperService: TypeMapperService,
private readonly argsTypeGenerator: ArgsTypeGenerator,
private readonly workspaceResolverBuilderService: WorkspaceResolverBuilderService,
) {}
@@ -115,12 +114,9 @@ export class RootTypeGenerator {
'groupBy',
];
const outputType = this.typeMapperService.applyTypeOptions(
objectType,
{
isArray: isMethodReturningArrayObjectType.includes(methodName),
},
);
const outputType = applyTypeOptionsForOutputType(objectType, {
isArray: isMethodReturningArrayObjectType.includes(methodName),
});
fieldConfigMap[name] = {
type: outputType,
@@ -8,21 +8,18 @@ import {
type GraphQLInputObjectType,
type GraphQLInputType,
GraphQLList,
GraphQLNonNull,
type GraphQLOutputType,
type GraphQLScalarType,
GraphQLString,
type GraphQLType,
} from 'graphql';
import GraphQLJSON from 'graphql-type-json';
import {
AggregateOperations,
type FieldMetadataDefaultValue,
type FieldMetadataSettings,
FieldMetadataType,
NumberDataType,
type FieldMetadataDefaultValue,
} from 'twenty-shared/types';
import { isDefined } from 'twenty-shared/utils';
import { OrderByDirectionType } from 'src/engine/api/graphql/workspace-schema-builder/graphql-types/enum';
import {
@@ -52,13 +49,12 @@ import { getNumberFilterType } from 'src/engine/api/graphql/workspace-schema-bui
import { getNumberScalarType } from 'src/engine/api/graphql/workspace-schema-builder/utils/get-number-scalar-type.util';
export interface TypeOptions {
nullable?: boolean;
isArray?: boolean;
arrayDepth?: number;
defaultValue?: FieldMetadataDefaultValue<FieldMetadataType>;
settings?: FieldMetadataSettings<FieldMetadataType>;
isIdField?: boolean;
isRelationConnectField?: boolean;
nullable?: boolean;
defaultValue?: FieldMetadataDefaultValue<FieldMetadataType>;
isArray?: boolean;
arrayDepth?: number;
}
const StringArrayScalarType = new GraphQLList(GraphQLString);
@@ -241,45 +237,4 @@ export class TypeMapperService {
return typeOrderByMapping.get(aggregationType);
}
applyTypeOptions<T extends GraphQLType = GraphQLType>(
typeRef: T,
options: TypeOptions,
): T {
let graphqlType: T | GraphQLList<T> | GraphQLNonNull<T> = typeRef;
if (options.isArray) {
graphqlType = this.mapToGqlList(
graphqlType,
options.arrayDepth ?? 1,
options.nullable ?? false,
);
}
if (options.nullable === false && !isDefined(options.defaultValue)) {
graphqlType = new GraphQLNonNull(graphqlType) as unknown as T;
}
return graphqlType as T;
}
private mapToGqlList<T extends GraphQLType = GraphQLType>(
targetType: T,
depth: number,
nullable: boolean,
): GraphQLList<T> {
const targetTypeNonNull = nullable
? targetType
: new GraphQLNonNull(targetType);
if (depth === 0) {
return targetType as GraphQLList<T>;
}
return this.mapToGqlList<T>(
new GraphQLList(targetTypeNonNull) as unknown as T,
depth - 1,
nullable,
);
}
}
@@ -0,0 +1,38 @@
import { type GraphQLList, GraphQLNonNull, type GraphQLType } from 'graphql';
import {
type FieldMetadataDefaultValue,
type FieldMetadataType,
} from 'twenty-shared/types';
import { isDefined } from 'twenty-shared/utils';
import { wrapTypeInGraphQLList } from 'src/engine/api/graphql/workspace-schema-builder/utils/wrap-type-in-graphql-list.util';
export type CreateInputTypeOptions = {
nullable?: boolean;
defaultValue?: FieldMetadataDefaultValue<FieldMetadataType>;
isArray?: boolean;
arrayDepth?: number;
};
export const applyTypeOptionsForCreateInput = <
T extends GraphQLType = GraphQLType,
>(
typeRef: T,
options: CreateInputTypeOptions,
): T => {
let graphqlType: T | GraphQLList<T> | GraphQLNonNull<T> = typeRef;
if (options.isArray) {
graphqlType = wrapTypeInGraphQLList(
graphqlType,
options.arrayDepth ?? 1,
options.nullable ?? false,
);
}
if (options.nullable === false && !isDefined(options.defaultValue)) {
graphqlType = new GraphQLNonNull(graphqlType) as unknown as T;
}
return graphqlType as T;
};
@@ -0,0 +1,38 @@
import { type GraphQLList, GraphQLNonNull, type GraphQLType } from 'graphql';
import {
type FieldMetadataDefaultValue,
type FieldMetadataType,
} from 'twenty-shared/types';
import { isDefined } from 'twenty-shared/utils';
import { wrapTypeInGraphQLList } from 'src/engine/api/graphql/workspace-schema-builder/utils/wrap-type-in-graphql-list.util';
export type OutputTypeOptions = {
nullable?: boolean;
defaultValue?: FieldMetadataDefaultValue<FieldMetadataType>;
isArray?: boolean;
arrayDepth?: number;
};
export const applyTypeOptionsForOutputType = <
T extends GraphQLType = GraphQLType,
>(
typeRef: T,
options: OutputTypeOptions,
): T => {
let graphqlType: T | GraphQLList<T> | GraphQLNonNull<T> = typeRef;
if (options.isArray) {
graphqlType = wrapTypeInGraphQLList(
graphqlType,
options.arrayDepth ?? 1,
options.nullable ?? false,
);
}
if (options.nullable === false && !isDefined(options.defaultValue)) {
graphqlType = new GraphQLNonNull(graphqlType) as unknown as T;
}
return graphqlType as T;
};
@@ -0,0 +1,26 @@
import { type GraphQLType } from 'graphql';
import { wrapTypeInGraphQLList } from 'src/engine/api/graphql/workspace-schema-builder/utils/wrap-type-in-graphql-list.util';
export type UpdateInputTypeOptions = {
isArray?: boolean;
arrayDepth?: number;
};
// Update inputs are always nullable — only array wrapping is needed
export const applyTypeOptionsForUpdateInput = <
T extends GraphQLType = GraphQLType,
>(
typeRef: T,
options: UpdateInputTypeOptions,
): T => {
if (options.isArray) {
return wrapTypeInGraphQLList(
typeRef,
options.arrayDepth ?? 1,
true,
) as unknown as T;
}
return typeRef;
};
@@ -0,0 +1,21 @@
import { GraphQLList, GraphQLNonNull, type GraphQLType } from 'graphql';
export const wrapTypeInGraphQLList = <T extends GraphQLType = GraphQLType>(
targetType: T,
depth: number,
nullable: boolean,
): GraphQLList<T> => {
const targetTypeNonNull = nullable
? targetType
: new GraphQLNonNull(targetType);
if (depth === 0) {
return targetType as GraphQLList<T>;
}
return wrapTypeInGraphQLList<T>(
new GraphQLList(targetTypeNonNull) as unknown as T,
depth - 1,
nullable,
);
};