simplify gql schema gen (#14426)

closes https://github.com/twentyhq/core-team-issues/issues/1358
This commit is contained in:
Etienne
2025-09-15 14:01:53 +02:00
committed by GitHub
parent 1cdfc0fd62
commit 06605b2f36
80 changed files with 2728 additions and 2080 deletions
@@ -2,14 +2,14 @@ import { Test, type TestingModule } from '@nestjs/testing';
import { ScalarsExplorerService } from 'src/engine/api/graphql/services/scalars-explorer.service';
import { WorkspaceResolverFactory } from 'src/engine/api/graphql/workspace-resolver-builder/workspace-resolver.factory';
import { WorkspaceGraphQLSchemaFactory } from 'src/engine/api/graphql/workspace-schema-builder/workspace-graphql-schema.factory';
import { WorkspaceGraphQLSchemaGenerator } from 'src/engine/api/graphql/workspace-schema-builder/workspace-graphql-schema.factory';
import { WorkspaceSchemaFactory } from 'src/engine/api/graphql/workspace-schema.factory';
import { FeatureFlagService } from 'src/engine/core-modules/feature-flag/services/feature-flag.service';
import { TwentyConfigService } from 'src/engine/core-modules/twenty-config/twenty-config.service';
import { DataSourceService } from 'src/engine/metadata-modules/data-source/data-source.service';
import { ObjectMetadataService } from 'src/engine/metadata-modules/object-metadata/object-metadata.service';
import { WorkspaceMetadataCacheService } from 'src/engine/metadata-modules/workspace-metadata-cache/services/workspace-metadata-cache.service';
import { WorkspaceCacheStorageService } from 'src/engine/workspace-cache-storage/workspace-cache-storage.service';
import { TwentyConfigService } from 'src/engine/core-modules/twenty-config/twenty-config.service';
describe('WorkspaceSchemaFactory', () => {
let service: WorkspaceSchemaFactory;
@@ -31,7 +31,7 @@ describe('WorkspaceSchemaFactory', () => {
useValue: {},
},
{
provide: WorkspaceGraphQLSchemaFactory,
provide: WorkspaceGraphQLSchemaGenerator,
useValue: {},
},
{
@@ -17,7 +17,6 @@ import { ActivityQueryResultGetterHandler } from 'src/engine/api/graphql/workspa
import { AttachmentQueryResultGetterHandler } from 'src/engine/api/graphql/workspace-query-runner/factories/query-result-getters/handlers/attachment-query-result-getter.handler';
import { PersonQueryResultGetterHandler } from 'src/engine/api/graphql/workspace-query-runner/factories/query-result-getters/handlers/person-query-result-getter.handler';
import { WorkspaceMemberQueryResultGetterHandler } from 'src/engine/api/graphql/workspace-query-runner/factories/query-result-getters/handlers/workspace-member-query-result-getter.handler';
import { CompositeInputTypeDefinitionFactory } from 'src/engine/api/graphql/workspace-schema-builder/factories/composite-input-type-definition.factory';
import { FileService } from 'src/engine/core-modules/file/services/file.service';
import { type ObjectMetadataItemWithFieldMaps } from 'src/engine/metadata-modules/types/object-metadata-item-with-field-maps';
import { type ObjectMetadataMaps } from 'src/engine/metadata-modules/types/object-metadata-maps';
@@ -28,9 +27,7 @@ import { isFieldMetadataEntityOfType } from 'src/engine/utils/is-field-metadata-
// Right now the factory will override any change made on relations by the handlers
@Injectable()
export class QueryResultGettersFactory {
private readonly logger = new Logger(
CompositeInputTypeDefinitionFactory.name,
);
private readonly logger = new Logger(QueryResultGettersFactory.name);
private handlers: Map<string, QueryResultGetterHandlerInterface>;
constructor(private readonly fileService: FileService) {
@@ -17,7 +17,7 @@ import {
} from 'src/engine/core-modules/auth/auth.exception';
import { type AuthContext } from 'src/engine/core-modules/auth/types/auth-context.type';
import { FeatureFlagService } from 'src/engine/core-modules/feature-flag/services/feature-flag.service';
import { type ObjectMetadataMaps } from 'src/engine/metadata-modules/types/object-metadata-maps';
import { ObjectMetadataMaps } from 'src/engine/metadata-modules/types/object-metadata-maps';
import { getResolverName } from 'src/engine/utils/get-resolver-name.util';
import { standardObjectMetadataDefinitions } from 'src/engine/workspace-manager/workspace-sync-metadata/standard-objects';
import { shouldExcludeFromWorkspaceApi } from 'src/engine/workspace-manager/workspace-sync-metadata/utils/should-exclude-from-workspace-api.util';
@@ -0,0 +1,6 @@
export enum GqlInputTypeDefinitionKind {
Create = 'Create',
Update = 'Update',
Filter = 'Filter',
OrderBy = 'OrderBy',
}
@@ -0,0 +1,5 @@
export enum GqlOperation {
Query = 'Query',
Mutation = 'Mutation',
Subscription = 'Subscription',
}
@@ -0,0 +1,5 @@
export enum ObjectTypeDefinitionKind {
Connection = 'Connection',
Edge = 'Edge',
Plain = '',
}
@@ -1,102 +0,0 @@
import { Injectable, Logger } from '@nestjs/common';
import {
type GraphQLInputFieldConfigMap,
GraphQLInputObjectType,
} from 'graphql';
import { FieldMetadataType } from 'twenty-shared/types';
import { type WorkspaceBuildSchemaOptions } from 'src/engine/api/graphql/workspace-schema-builder/interfaces/workspace-build-schema-options.interface';
import { type CompositeType } from 'src/engine/metadata-modules/field-metadata/interfaces/composite-type.interface';
import {
type InputTypeDefinition,
InputTypeDefinitionKind,
} from 'src/engine/api/graphql/workspace-schema-builder/factories/input-type-definition.factory';
import { computeCompositePropertyTarget } from 'src/engine/api/graphql/workspace-schema-builder/utils/compute-composite-property-target.util';
import { isMorphOrRelationFieldMetadataType } from 'src/engine/utils/is-morph-or-relation-field-metadata-type.util';
import { pascalCase } from 'src/utils/pascal-case';
import { InputTypeFactory } from './input-type.factory';
const hiddenAllowListKind = [
InputTypeDefinitionKind.Create,
InputTypeDefinitionKind.Update,
];
@Injectable()
export class CompositeInputTypeDefinitionFactory {
private readonly logger = new Logger(
CompositeInputTypeDefinitionFactory.name,
);
constructor(private readonly inputTypeFactory: InputTypeFactory) {}
public create(
compositeType: CompositeType,
kind: InputTypeDefinitionKind,
options: WorkspaceBuildSchemaOptions,
): InputTypeDefinition {
const name = pascalCase(compositeType.type.toString().toLowerCase());
return {
target: compositeType.type.toString(),
kind,
type: new GraphQLInputObjectType({
name: `${pascalCase(name)}${kind.toString()}Input`,
fields: this.generateFields(compositeType, kind, options),
}),
};
}
private generateFields(
compositeType: CompositeType,
kind: InputTypeDefinitionKind,
options: WorkspaceBuildSchemaOptions,
): GraphQLInputFieldConfigMap {
const fields: GraphQLInputFieldConfigMap = {};
for (const property of compositeType.properties) {
// Relation fields are not supported in composite types
if (isMorphOrRelationFieldMetadataType(property.type)) {
this.logger.error(
'Relation fields are not supported in composite types',
{ compositeType, property },
);
throw new Error('Relation fields are not supported in composite types');
}
// Skip hidden fields
if (
property.hidden === true ||
(property.hidden === 'input' && hiddenAllowListKind.includes(kind))
) {
continue;
}
const target = computeCompositePropertyTarget(
compositeType.type,
property,
);
const type = this.inputTypeFactory.create(
target,
property.type,
kind,
options,
{
nullable: !property.isRequired,
isArray:
property.type === FieldMetadataType.MULTI_SELECT ||
property.isArray,
},
);
fields[property.name] = {
type,
description: property.description,
};
}
return fields;
}
}
@@ -1,94 +0,0 @@
import { Injectable, Logger } from '@nestjs/common';
import { type GraphQLFieldConfigMap, GraphQLObjectType } from 'graphql';
import { FieldMetadataType } from 'twenty-shared/types';
import { type WorkspaceBuildSchemaOptions } from 'src/engine/api/graphql/workspace-schema-builder/interfaces/workspace-build-schema-options.interface';
import { type CompositeType } from 'src/engine/metadata-modules/field-metadata/interfaces/composite-type.interface';
import {
type ObjectTypeDefinition,
ObjectTypeDefinitionKind,
} from 'src/engine/api/graphql/workspace-schema-builder/factories/object-type-definition.factory';
import { OutputTypeFactory } from 'src/engine/api/graphql/workspace-schema-builder/factories/output-type.factory';
import { computeCompositePropertyTarget } from 'src/engine/api/graphql/workspace-schema-builder/utils/compute-composite-property-target.util';
import { isMorphOrRelationFieldMetadataType } from 'src/engine/utils/is-morph-or-relation-field-metadata-type.util';
import { pascalCase } from 'src/utils/pascal-case';
@Injectable()
export class CompositeObjectTypeDefinitionFactory {
private readonly logger = new Logger(
CompositeObjectTypeDefinitionFactory.name,
);
constructor(private readonly outputTypeFactory: OutputTypeFactory) {}
public create(
compositeType: CompositeType,
options: WorkspaceBuildSchemaOptions,
): ObjectTypeDefinition {
const name = pascalCase(compositeType.type.toString().toLowerCase());
const kind = ObjectTypeDefinitionKind.Plain;
return {
target: compositeType.type.toString(),
kind,
type: new GraphQLObjectType({
name: `${name}${kind.toString()}`,
fields: this.generateFields(compositeType, kind, options),
}),
};
}
private generateFields(
compositeType: CompositeType,
kind: ObjectTypeDefinitionKind,
options: WorkspaceBuildSchemaOptions,
// eslint-disable-next-line @typescript-eslint/no-explicit-any
): GraphQLFieldConfigMap<any, any> {
// eslint-disable-next-line @typescript-eslint/no-explicit-any
const fields: GraphQLFieldConfigMap<any, any> = {};
for (const property of compositeType.properties) {
// Relation fields are not supported in composite types
if (isMorphOrRelationFieldMetadataType(property.type)) {
this.logger.error(
'Relation fields are not supported in composite types',
{ compositeType, property },
);
throw new Error('Relation fields are not supported in composite types');
}
// Skip hidden fields
if (property.hidden === true || property.hidden === 'output') {
continue;
}
const target = computeCompositePropertyTarget(
compositeType.type,
property,
);
const type = this.outputTypeFactory.create(
target,
property.type,
kind,
options,
{
nullable: !property.isRequired,
isArray:
property.type === FieldMetadataType.MULTI_SELECT ||
property.isArray,
},
);
fields[property.name] = {
type,
description: property.description,
};
}
return fields;
}
}
@@ -1,84 +0,0 @@
import { Injectable } from '@nestjs/common';
import { type GraphQLFieldConfigMap, GraphQLObjectType } from 'graphql';
import { type WorkspaceBuildSchemaOptions } from 'src/engine/api/graphql/workspace-schema-builder/interfaces/workspace-build-schema-options.interface';
import { AggregationTypeFactory } from 'src/engine/api/graphql/workspace-schema-builder/factories/aggregation-type.factory';
import { type ObjectMetadataEntity } from 'src/engine/metadata-modules/object-metadata/object-metadata.entity';
import { pascalCase } from 'src/utils/pascal-case';
import { ConnectionTypeFactory } from './connection-type.factory';
import {
type ObjectTypeDefinition,
ObjectTypeDefinitionKind,
} from './object-type-definition.factory';
export enum ConnectionTypeDefinitionKind {
Edge = 'Edge',
PageInfo = 'PageInfo',
}
@Injectable()
export class ConnectionTypeDefinitionFactory {
constructor(
private readonly connectionTypeFactory: ConnectionTypeFactory,
private readonly aggregationTypeFactory: AggregationTypeFactory,
) {}
public create(
objectMetadata: ObjectMetadataEntity,
options: WorkspaceBuildSchemaOptions,
): ObjectTypeDefinition {
const kind = ObjectTypeDefinitionKind.Connection;
return {
target: objectMetadata.id,
kind,
type: new GraphQLObjectType({
name: `${pascalCase(objectMetadata.nameSingular)}${kind.toString()}`,
description: objectMetadata.description,
fields: () => this.generateFields(objectMetadata, options),
}),
};
}
private generateFields(
objectMetadata: ObjectMetadataEntity,
options: WorkspaceBuildSchemaOptions,
// eslint-disable-next-line @typescript-eslint/no-explicit-any
): GraphQLFieldConfigMap<any, any> {
// eslint-disable-next-line @typescript-eslint/no-explicit-any
const fields: GraphQLFieldConfigMap<any, any> = {};
const aggregatedFields = this.aggregationTypeFactory.create(objectMetadata);
Object.assign(fields, aggregatedFields);
fields.edges = {
type: this.connectionTypeFactory.create(
objectMetadata,
ConnectionTypeDefinitionKind.Edge,
options,
{
isArray: true,
arrayDepth: 1,
nullable: false,
},
),
};
fields.pageInfo = {
type: this.connectionTypeFactory.create(
objectMetadata,
ConnectionTypeDefinitionKind.PageInfo,
options,
{
nullable: false,
},
),
};
return fields;
}
}
@@ -1,58 +0,0 @@
import { Injectable, Logger } from '@nestjs/common';
import { type GraphQLOutputType } from 'graphql';
import { type WorkspaceBuildSchemaOptions } from 'src/engine/api/graphql/workspace-schema-builder/interfaces/workspace-build-schema-options.interface';
import { PageInfoType } from 'src/engine/api/graphql/workspace-schema-builder/graphql-types/object';
import {
TypeMapperService,
type TypeOptions,
} from 'src/engine/api/graphql/workspace-schema-builder/services/type-mapper.service';
import { TypeDefinitionsStorage } from 'src/engine/api/graphql/workspace-schema-builder/storages/type-definitions.storage';
import { type ObjectMetadataEntity } from 'src/engine/metadata-modules/object-metadata/object-metadata.entity';
import { ConnectionTypeDefinitionKind } from './connection-type-definition.factory';
import { type ObjectTypeDefinitionKind } from './object-type-definition.factory';
@Injectable()
export class ConnectionTypeFactory {
private readonly logger = new Logger(ConnectionTypeFactory.name);
constructor(
private readonly typeMapperService: TypeMapperService,
private readonly typeDefinitionsStorage: TypeDefinitionsStorage,
) {}
public create(
objectMetadata: ObjectMetadataEntity,
kind: ConnectionTypeDefinitionKind,
buildOptions: WorkspaceBuildSchemaOptions,
typeOptions: TypeOptions,
): GraphQLOutputType {
if (kind === ConnectionTypeDefinitionKind.PageInfo) {
return this.typeMapperService.mapToGqlType(PageInfoType, typeOptions);
}
const edgeType = this.typeDefinitionsStorage.getObjectTypeByKey(
objectMetadata.id,
kind as unknown as ObjectTypeDefinitionKind,
);
if (!edgeType) {
this.logger.error(
`Edge type for ${objectMetadata.nameSingular} was not found. Please, check if you have defined it.`,
{
objectMetadata,
buildOptions,
},
);
throw new Error(
`Edge type for ${objectMetadata.nameSingular} was not found. Please, check if you have defined it.`,
);
}
return this.typeMapperService.mapToGqlType(edgeType, typeOptions);
}
}
@@ -1,75 +0,0 @@
import { Injectable } from '@nestjs/common';
import { type GraphQLFieldConfigMap, GraphQLObjectType } from 'graphql';
import { type WorkspaceBuildSchemaOptions } from 'src/engine/api/graphql/workspace-schema-builder/interfaces/workspace-build-schema-options.interface';
import { type ObjectMetadataEntity } from 'src/engine/metadata-modules/object-metadata/object-metadata.entity';
import { pascalCase } from 'src/utils/pascal-case';
import { EdgeTypeFactory } from './edge-type.factory';
import {
type ObjectTypeDefinition,
ObjectTypeDefinitionKind,
} from './object-type-definition.factory';
export enum EdgeTypeDefinitionKind {
Node = 'Node',
Cursor = 'Cursor',
}
@Injectable()
export class EdgeTypeDefinitionFactory {
constructor(private readonly edgeTypeFactory: EdgeTypeFactory) {}
public create(
objectMetadata: ObjectMetadataEntity,
options: WorkspaceBuildSchemaOptions,
): ObjectTypeDefinition {
const kind = ObjectTypeDefinitionKind.Edge;
return {
target: objectMetadata.id,
kind,
type: new GraphQLObjectType({
name: `${pascalCase(objectMetadata.nameSingular)}${kind.toString()}`,
description: objectMetadata.description,
fields: () => this.generateFields(objectMetadata, options),
}),
};
}
private generateFields(
objectMetadata: ObjectMetadataEntity,
options: WorkspaceBuildSchemaOptions,
// eslint-disable-next-line @typescript-eslint/no-explicit-any
): GraphQLFieldConfigMap<any, any> {
// eslint-disable-next-line @typescript-eslint/no-explicit-any
const fields: GraphQLFieldConfigMap<any, any> = {};
fields.node = {
type: this.edgeTypeFactory.create(
objectMetadata,
EdgeTypeDefinitionKind.Node,
options,
{
nullable: false,
},
),
};
fields.cursor = {
type: this.edgeTypeFactory.create(
objectMetadata,
EdgeTypeDefinitionKind.Cursor,
options,
{
nullable: false,
},
),
};
return fields;
}
}
@@ -1,58 +0,0 @@
import { Injectable, Logger } from '@nestjs/common';
import { type GraphQLOutputType } from 'graphql';
import { type WorkspaceBuildSchemaOptions } from 'src/engine/api/graphql/workspace-schema-builder/interfaces/workspace-build-schema-options.interface';
import { CursorScalarType } from 'src/engine/api/graphql/workspace-schema-builder/graphql-types/scalars';
import {
TypeMapperService,
type TypeOptions,
} from 'src/engine/api/graphql/workspace-schema-builder/services/type-mapper.service';
import { TypeDefinitionsStorage } from 'src/engine/api/graphql/workspace-schema-builder/storages/type-definitions.storage';
import { type ObjectMetadataEntity } from 'src/engine/metadata-modules/object-metadata/object-metadata.entity';
import { EdgeTypeDefinitionKind } from './edge-type-definition.factory';
import { ObjectTypeDefinitionKind } from './object-type-definition.factory';
@Injectable()
export class EdgeTypeFactory {
private readonly logger = new Logger(EdgeTypeFactory.name);
constructor(
private readonly typeMapperService: TypeMapperService,
private readonly typeDefinitionsStorage: TypeDefinitionsStorage,
) {}
public create(
objectMetadata: ObjectMetadataEntity,
kind: EdgeTypeDefinitionKind,
buildOptions: WorkspaceBuildSchemaOptions,
typeOptions: TypeOptions,
): GraphQLOutputType {
if (kind === EdgeTypeDefinitionKind.Cursor) {
return this.typeMapperService.mapToGqlType(CursorScalarType, typeOptions);
}
const objectType = this.typeDefinitionsStorage.getObjectTypeByKey(
objectMetadata.id,
ObjectTypeDefinitionKind.Plain,
);
if (!objectType) {
this.logger.error(
`Node type for ${objectMetadata.nameSingular} was not found. Please, check if you have defined it.`,
{
objectMetadata,
buildOptions,
},
);
throw new Error(
`Node type for ${objectMetadata.nameSingular} was not found. Please, check if you have defined it.`,
);
}
return this.typeMapperService.mapToGqlType(objectType, typeOptions);
}
}
@@ -1,180 +0,0 @@
import { Injectable, Logger } from '@nestjs/common';
import {
GraphQLObjectType,
type GraphQLFieldConfigArgumentMap,
type GraphQLFieldConfigMap,
} from 'graphql';
import { FieldMetadataType } from 'twenty-shared/types';
import { type WorkspaceBuildSchemaOptions } from 'src/engine/api/graphql/workspace-schema-builder/interfaces/workspace-build-schema-options.interface';
import { RelationType } from 'src/engine/metadata-modules/field-metadata/interfaces/relation-type.interface';
import { RelationTypeV2Factory } from 'src/engine/api/graphql/workspace-schema-builder/factories/relation-type-v2.factory';
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 { type ObjectMetadataEntity } from 'src/engine/metadata-modules/object-metadata/object-metadata.entity';
import { isFieldMetadataEntityOfType } from 'src/engine/utils/is-field-metadata-of-type.util';
import { ArgsFactory } from './args.factory';
export enum ObjectTypeDefinitionKind {
Connection = 'Connection',
Edge = 'Edge',
Plain = '',
}
export interface ObjectTypeDefinition {
target: string;
kind: ObjectTypeDefinitionKind;
type: GraphQLObjectType;
}
@Injectable()
export class ExtendObjectTypeDefinitionV2Factory {
private readonly logger = new Logger(
ExtendObjectTypeDefinitionV2Factory.name,
);
constructor(
private readonly relationTypeV2Factory: RelationTypeV2Factory,
private readonly argsFactory: ArgsFactory,
private readonly typeDefinitionsStorage: TypeDefinitionsStorage,
) {}
public create(
objectMetadata: ObjectMetadataEntity,
options: WorkspaceBuildSchemaOptions,
objectMetadataCollection: ObjectMetadataEntity[],
): ObjectTypeDefinition {
const kind = ObjectTypeDefinitionKind.Plain;
const gqlType = this.typeDefinitionsStorage.getObjectTypeByKey(
objectMetadata.id,
kind,
);
const containsRelationField = objectContainsRelationField(objectMetadata);
if (!gqlType) {
this.logger.error(
`Could not find a GraphQL type for ${objectMetadata.id.toString()}`,
{
objectMetadata,
options,
},
);
throw new Error(
`Could not find a GraphQL type for ${objectMetadata.id.toString()}`,
);
}
// Security check to avoid extending an object that does not need to be extended
if (!containsRelationField) {
this.logger.error(
`This object does not need to be extended: ${objectMetadata.id.toString()}`,
{
objectMetadata,
options,
},
);
throw new Error(
`This object does not need to be extended: ${objectMetadata.id.toString()}`,
);
}
// Extract current object config to extend it
const config = gqlType.toConfig();
// Recreate the same object type with the new fields
return {
target: objectMetadata.id,
kind,
type: new GraphQLObjectType({
...config,
fields: () => ({
...config.fields,
...this.generateFields(
objectMetadata,
options,
objectMetadataCollection,
),
}),
}),
};
}
private generateFields(
objectMetadata: ObjectMetadataEntity,
options: WorkspaceBuildSchemaOptions,
objectMetadataCollection: ObjectMetadataEntity[],
// eslint-disable-next-line @typescript-eslint/no-explicit-any
): GraphQLFieldConfigMap<any, any> {
// eslint-disable-next-line @typescript-eslint/no-explicit-any
const fields: GraphQLFieldConfigMap<any, any> = {};
for (const fieldMetadata of objectMetadata.fields) {
// Ignore non-relation fields as they are already defined
const isRelation =
isFieldMetadataEntityOfType(
fieldMetadata,
FieldMetadataType.RELATION,
) ||
isFieldMetadataEntityOfType(
fieldMetadata,
FieldMetadataType.MORPH_RELATION,
);
if (!isRelation) {
continue;
}
if (!fieldMetadata.settings) {
throw new Error(
`Field Metadata of type RELATION or MORPH_RELATION with id ${fieldMetadata.id} has no settings`,
);
}
if (!fieldMetadata.relationTargetObjectMetadataId) {
throw new Error(
`Field Metadata of type RELATION or MORPH_RELATION with id ${fieldMetadata.id} has no relation target object metadata id`,
);
}
const relationType = this.relationTypeV2Factory.create(fieldMetadata);
let argsType: GraphQLFieldConfigArgumentMap | undefined = undefined;
if (fieldMetadata.settings.relationType === RelationType.ONE_TO_MANY) {
const args = getResolverArgs('findMany');
argsType = this.argsFactory.create(
{
args,
objectMetadataId: fieldMetadata.relationTargetObjectMetadataId,
},
options,
);
}
const objectMetadataTarget = objectMetadataCollection.find(
(objectMetadata) =>
objectMetadata.id === fieldMetadata.relationTargetObjectMetadataId,
);
if (!objectMetadataTarget) {
throw new Error(
`Object Metadata Target not found for Id: ${fieldMetadata.relationTargetObjectMetadataId} on fieldMetadata name: ${fieldMetadata.name}`,
);
}
fields[fieldMetadata.name] = {
type: relationType,
args: argsType,
description: fieldMetadata.description,
};
}
return fields;
}
}
@@ -1,46 +0,0 @@
import { AggregationTypeFactory } from 'src/engine/api/graphql/workspace-schema-builder/factories/aggregation-type.factory';
import { CompositeEnumTypeDefinitionFactory } from 'src/engine/api/graphql/workspace-schema-builder/factories/composite-enum-type-definition.factory';
import { CompositeInputTypeDefinitionFactory } from 'src/engine/api/graphql/workspace-schema-builder/factories/composite-input-type-definition.factory';
import { CompositeObjectTypeDefinitionFactory } from 'src/engine/api/graphql/workspace-schema-builder/factories/composite-object-type-definition.factory';
import { EnumTypeDefinitionFactory } from 'src/engine/api/graphql/workspace-schema-builder/factories/enum-type-definition.factory';
import { ExtendObjectTypeDefinitionV2Factory } from 'src/engine/api/graphql/workspace-schema-builder/factories/extend-object-type-definition-v2.factory';
import { RelationConnectInputTypeDefinitionFactory } from 'src/engine/api/graphql/workspace-schema-builder/factories/relation-connect-input-type-definition.factory';
import { RelationTypeV2Factory } from 'src/engine/api/graphql/workspace-schema-builder/factories/relation-type-v2.factory';
import { ArgsFactory } from './args.factory';
import { ConnectionTypeDefinitionFactory } from './connection-type-definition.factory';
import { ConnectionTypeFactory } from './connection-type.factory';
import { EdgeTypeDefinitionFactory } from './edge-type-definition.factory';
import { EdgeTypeFactory } from './edge-type.factory';
import { InputTypeDefinitionFactory } from './input-type-definition.factory';
import { InputTypeFactory } from './input-type.factory';
import { MutationTypeFactory } from './mutation-type.factory';
import { ObjectTypeDefinitionFactory } from './object-type-definition.factory';
import { OrphanedTypesFactory } from './orphaned-types.factory';
import { OutputTypeFactory } from './output-type.factory';
import { QueryTypeFactory } from './query-type.factory';
import { RootTypeFactory } from './root-type.factory';
export const workspaceSchemaBuilderFactories = [
ArgsFactory,
InputTypeFactory,
InputTypeDefinitionFactory,
CompositeInputTypeDefinitionFactory,
RelationConnectInputTypeDefinitionFactory,
OutputTypeFactory,
ObjectTypeDefinitionFactory,
CompositeObjectTypeDefinitionFactory,
EnumTypeDefinitionFactory,
CompositeEnumTypeDefinitionFactory,
RelationTypeV2Factory,
ExtendObjectTypeDefinitionV2Factory,
ConnectionTypeFactory,
ConnectionTypeDefinitionFactory,
EdgeTypeFactory,
EdgeTypeDefinitionFactory,
RootTypeFactory,
QueryTypeFactory,
MutationTypeFactory,
OrphanedTypesFactory,
AggregationTypeFactory,
];
@@ -1,102 +0,0 @@
import { Inject, Injectable, forwardRef } from '@nestjs/common';
import { GraphQLInputObjectType } from 'graphql';
import { type WorkspaceBuildSchemaOptions } from 'src/engine/api/graphql/workspace-schema-builder/interfaces/workspace-build-schema-options.interface';
import { TypeMapperService } from 'src/engine/api/graphql/workspace-schema-builder/services/type-mapper.service';
import { generateFields } from 'src/engine/api/graphql/workspace-schema-builder/utils/generate-fields.util';
import { type ObjectMetadataEntity } from 'src/engine/metadata-modules/object-metadata/object-metadata.entity';
import { pascalCase } from 'src/utils/pascal-case';
import { InputTypeFactory } from './input-type.factory';
export enum InputTypeDefinitionKind {
Create = 'Create',
Update = 'Update',
Filter = 'Filter',
OrderBy = 'OrderBy',
}
export interface InputTypeDefinition {
target: string;
kind: InputTypeDefinitionKind;
type: GraphQLInputObjectType;
}
@Injectable()
export class InputTypeDefinitionFactory {
constructor(
@Inject(forwardRef(() => InputTypeFactory))
private readonly inputTypeFactory: CircularDep<InputTypeFactory>,
private readonly typeMapperService: TypeMapperService,
) {}
public create({
objectMetadata,
kind,
options,
}: {
objectMetadata: ObjectMetadataEntity;
kind: InputTypeDefinitionKind;
options: WorkspaceBuildSchemaOptions;
}): InputTypeDefinition {
// @ts-expect-error legacy noImplicitAny
const inputType = new GraphQLInputObjectType({
name: `${pascalCase(objectMetadata.nameSingular)}${kind.toString()}Input`,
description: objectMetadata.description,
// @ts-expect-error legacy noImplicitAny
fields: () => {
switch (kind) {
/**
* Filter input type has additional fields for filtering and is self referencing
*/
case InputTypeDefinitionKind.Filter: {
// @ts-expect-error legacy noImplicitAny
const andOrType = this.typeMapperService.mapToGqlType(inputType, {
isArray: true,
arrayDepth: 1,
nullable: true,
});
return {
...generateFields({
objectMetadata,
kind,
options,
typeFactory: this.inputTypeFactory,
}),
and: {
type: andOrType,
},
or: {
type: andOrType,
},
not: {
type: this.typeMapperService.mapToGqlType(inputType, {
nullable: true,
}),
},
};
}
/**
* Other input types are generated with fields only
*/
default:
return generateFields({
objectMetadata,
kind,
options,
typeFactory: this.inputTypeFactory,
});
}
},
});
return {
target: objectMetadata.id,
kind,
type: inputType,
};
}
}
@@ -1,120 +0,0 @@
import { Injectable, Logger } from '@nestjs/common';
import {
GraphQLBoolean,
GraphQLInputObjectType,
type GraphQLInputType,
GraphQLList,
} from 'graphql';
import { type FieldMetadataType } from 'twenty-shared/types';
import { type WorkspaceBuildSchemaOptions } from 'src/engine/api/graphql/workspace-schema-builder/interfaces/workspace-build-schema-options.interface';
import { FilterIs } from 'src/engine/api/graphql/workspace-schema-builder/graphql-types/input/filter-is.input-type';
import {
TypeMapperService,
type TypeOptions,
} from 'src/engine/api/graphql/workspace-schema-builder/services/type-mapper.service';
import { TypeDefinitionsStorage } from 'src/engine/api/graphql/workspace-schema-builder/storages/type-definitions.storage';
import { isEnumFieldMetadataType } from 'src/engine/metadata-modules/field-metadata/utils/is-enum-field-metadata-type.util';
import { InputTypeDefinitionKind } from './input-type-definition.factory';
@Injectable()
export class InputTypeFactory {
private readonly logger = new Logger(InputTypeFactory.name);
constructor(
private readonly typeMapperService: TypeMapperService,
private readonly typeDefinitionsStorage: TypeDefinitionsStorage,
) {}
public create(
target: string,
type: FieldMetadataType,
kind: InputTypeDefinitionKind,
buildOptions: WorkspaceBuildSchemaOptions,
typeOptions: TypeOptions,
): GraphQLInputType {
let inputType: GraphQLInputType | undefined;
switch (kind) {
/**
* Create and Update input types are classic scalar types
*/
case InputTypeDefinitionKind.Create:
case InputTypeDefinitionKind.Update:
//if it's a relation connect field, type is in storage
if (typeOptions.isRelationConnectField) break;
inputType = this.typeMapperService.mapToScalarType(
type,
typeOptions.settings,
typeOptions.isIdField,
);
break;
/**
* Filter input maps to special filter type
*/
case InputTypeDefinitionKind.Filter: {
if (isEnumFieldMetadataType(type)) {
inputType = this.createEnumFilterType(target);
} else {
inputType = this.typeMapperService.mapToFilterType(
type,
typeOptions.settings,
typeOptions.isIdField,
);
}
break;
}
/**
* OrderBy input maps to special order by type
*/
case InputTypeDefinitionKind.OrderBy:
inputType = this.typeMapperService.mapToOrderByType(type);
break;
}
/**
* If input type is not scalar, we're looking for it in the type definitions storage as it can be an object type
*/
inputType ??= this.typeDefinitionsStorage.getInputTypeByKey(target, kind);
if (!inputType) {
this.logger.error(`Could not find a GraphQL type for ${target}`, {
type,
kind,
buildOptions,
typeOptions,
});
throw new Error(`Could not find a GraphQL type for ${target}`);
}
return this.typeMapperService.mapToGqlType(inputType, typeOptions);
}
private createEnumFilterType(target: string): GraphQLInputObjectType {
const enumType = this.typeDefinitionsStorage.getEnumTypeByKey(target);
if (!enumType) {
this.logger.error(`Could not find a GraphQL enum type for ${target}`);
throw new Error(`Could not find a GraphQL enum type for ${target}`);
}
return new GraphQLInputObjectType({
name: `${enumType.name}Filter`,
fields: () => ({
eq: { type: enumType },
neq: { type: enumType },
in: { type: new GraphQLList(enumType) },
containsAny: { type: new GraphQLList(enumType) },
is: { type: FilterIs },
isEmptyArray: { type: GraphQLBoolean },
}),
});
}
}
@@ -1,28 +0,0 @@
import { Injectable } from '@nestjs/common';
import { type GraphQLObjectType } from 'graphql';
import { type WorkspaceResolverBuilderMutationMethodNames } from 'src/engine/api/graphql/workspace-resolver-builder/interfaces/workspace-resolvers-builder.interface';
import { type WorkspaceBuildSchemaOptions } from 'src/engine/api/graphql/workspace-schema-builder/interfaces/workspace-build-schema-options.interface';
import { type ObjectMetadataEntity } from 'src/engine/metadata-modules/object-metadata/object-metadata.entity';
import { ObjectTypeName, RootTypeFactory } from './root-type.factory';
@Injectable()
export class MutationTypeFactory {
constructor(private readonly rootTypeFactory: RootTypeFactory) {}
create(
objectMetadataCollection: ObjectMetadataEntity[],
workspaceResolverMethodNames: WorkspaceResolverBuilderMutationMethodNames[],
options: WorkspaceBuildSchemaOptions,
): GraphQLObjectType {
return this.rootTypeFactory.create(
objectMetadataCollection,
workspaceResolverMethodNames,
ObjectTypeName.Mutation,
options,
);
}
}
@@ -1,49 +0,0 @@
import { Injectable } from '@nestjs/common';
import { GraphQLObjectType } from 'graphql';
import { type WorkspaceBuildSchemaOptions } from 'src/engine/api/graphql/workspace-schema-builder/interfaces/workspace-build-schema-options.interface';
import { generateFields } from 'src/engine/api/graphql/workspace-schema-builder/utils/generate-fields.util';
import { type ObjectMetadataEntity } from 'src/engine/metadata-modules/object-metadata/object-metadata.entity';
import { pascalCase } from 'src/utils/pascal-case';
import { OutputTypeFactory } from './output-type.factory';
export enum ObjectTypeDefinitionKind {
Connection = 'Connection',
Edge = 'Edge',
Plain = '',
}
export interface ObjectTypeDefinition {
target: string;
kind: ObjectTypeDefinitionKind;
type: GraphQLObjectType;
}
@Injectable()
export class ObjectTypeDefinitionFactory {
constructor(private readonly outputTypeFactory: OutputTypeFactory) {}
public create(
objectMetadata: ObjectMetadataEntity,
kind: ObjectTypeDefinitionKind,
options: WorkspaceBuildSchemaOptions,
): ObjectTypeDefinition {
return {
target: objectMetadata.id,
kind,
type: new GraphQLObjectType({
name: `${pascalCase(objectMetadata.nameSingular)}${kind.toString()}`,
description: objectMetadata.description,
fields: generateFields({
objectMetadata,
kind,
options,
typeFactory: this.outputTypeFactory,
}),
}),
};
}
}
@@ -1,22 +0,0 @@
import { Injectable } from '@nestjs/common';
import { type GraphQLNamedType } from 'graphql';
import { TypeDefinitionsStorage } from 'src/engine/api/graphql/workspace-schema-builder/storages/type-definitions.storage';
@Injectable()
export class OrphanedTypesFactory {
constructor(
private readonly typeDefinitionsStorage: TypeDefinitionsStorage,
) {}
public create(): GraphQLNamedType[] {
const objectTypeDefs =
this.typeDefinitionsStorage.getAllObjectTypeDefinitions();
const inputTypeDefs =
this.typeDefinitionsStorage.getAllInputTypeDefinitions();
const classTypeDefs = [...objectTypeDefs, ...inputTypeDefs];
return [...classTypeDefs.map(({ type }) => type)];
}
}
@@ -1,54 +0,0 @@
import { Injectable, Logger } from '@nestjs/common';
import { type GraphQLOutputType } from 'graphql';
import { type FieldMetadataType } from 'twenty-shared/types';
import { type WorkspaceBuildSchemaOptions } from 'src/engine/api/graphql/workspace-schema-builder/interfaces/workspace-build-schema-options.interface';
import {
TypeMapperService,
type TypeOptions,
} from 'src/engine/api/graphql/workspace-schema-builder/services/type-mapper.service';
import { TypeDefinitionsStorage } from 'src/engine/api/graphql/workspace-schema-builder/storages/type-definitions.storage';
import { type ObjectTypeDefinitionKind } from './object-type-definition.factory';
@Injectable()
export class OutputTypeFactory {
private readonly logger = new Logger(OutputTypeFactory.name);
constructor(
private readonly typeMapperService: TypeMapperService,
private readonly typeDefinitionsStorage: TypeDefinitionsStorage,
) {}
public create(
target: string,
type: FieldMetadataType,
kind: ObjectTypeDefinitionKind,
buildOptions: WorkspaceBuildSchemaOptions,
typeOptions: TypeOptions,
): GraphQLOutputType {
let gqlType: GraphQLOutputType | undefined =
this.typeMapperService.mapToScalarType(
type,
typeOptions.settings,
typeOptions.isIdField,
);
gqlType ??= this.typeDefinitionsStorage.getOutputTypeByKey(target, kind);
if (!gqlType) {
this.logger.error(`Could not find a GraphQL type for ${target}`, {
kind,
type,
buildOptions,
typeOptions,
});
throw new Error(`Could not find a GraphQL type for ${target}`);
}
return this.typeMapperService.mapToGqlType(gqlType, typeOptions);
}
}
@@ -1,28 +0,0 @@
import { Injectable } from '@nestjs/common';
import { type GraphQLObjectType } from 'graphql';
import { type WorkspaceResolverBuilderQueryMethodNames } from 'src/engine/api/graphql/workspace-resolver-builder/interfaces/workspace-resolvers-builder.interface';
import { type WorkspaceBuildSchemaOptions } from 'src/engine/api/graphql/workspace-schema-builder/interfaces/workspace-build-schema-options.interface';
import { type ObjectMetadataEntity } from 'src/engine/metadata-modules/object-metadata/object-metadata.entity';
import { ObjectTypeName, RootTypeFactory } from './root-type.factory';
@Injectable()
export class QueryTypeFactory {
constructor(private readonly rootTypeFactory: RootTypeFactory) {}
create(
objectMetadataCollection: ObjectMetadataEntity[],
workspaceResolverMethodNames: WorkspaceResolverBuilderQueryMethodNames[],
options: WorkspaceBuildSchemaOptions,
): GraphQLObjectType {
return this.rootTypeFactory.create(
objectMetadataCollection,
workspaceResolverMethodNames,
ObjectTypeName.Query,
options,
);
}
}
@@ -1,58 +0,0 @@
import { Injectable, Logger } from '@nestjs/common';
import { type GraphQLOutputType } from 'graphql';
import { type FieldMetadataType } from 'twenty-shared/types';
import { RelationType } from 'src/engine/metadata-modules/field-metadata/interfaces/relation-type.interface';
import { TypeDefinitionsStorage } from 'src/engine/api/graphql/workspace-schema-builder/storages/type-definitions.storage';
import { type FieldMetadataEntity } from 'src/engine/metadata-modules/field-metadata/field-metadata.entity';
import { ObjectTypeDefinitionKind } from './object-type-definition.factory';
@Injectable()
export class RelationTypeV2Factory {
private readonly logger = new Logger(RelationTypeV2Factory.name);
constructor(
private readonly typeDefinitionsStorage: TypeDefinitionsStorage,
) {}
public create(
fieldMetadata: FieldMetadataEntity<
FieldMetadataType.RELATION | FieldMetadataType.MORPH_RELATION
>,
): GraphQLOutputType {
if (!fieldMetadata.settings) {
throw new Error(
`Field Metadata of type RELATION or MORPH_RELATION with id ${fieldMetadata.id} has no settings`,
);
}
if (!fieldMetadata.relationTargetObjectMetadataId) {
throw new Error(
`Field Metadata of type RELATION or MORPH_RELATION with id ${fieldMetadata.id} has no relation target object metadata id`,
);
}
const relationGqlType = this.typeDefinitionsStorage.getObjectTypeByKey(
fieldMetadata.relationTargetObjectMetadataId,
fieldMetadata.settings.relationType === RelationType.ONE_TO_MANY
? ObjectTypeDefinitionKind.Connection
: ObjectTypeDefinitionKind.Plain,
);
if (!relationGqlType) {
this.logger.error(
`Could not find a relation type for ${fieldMetadata.id}`,
{
fieldMetadata,
},
);
throw new Error(`Could not find a relation type for ${fieldMetadata.id}`);
}
return relationGqlType;
}
}
@@ -1,26 +1,27 @@
import { Injectable, Logger } from '@nestjs/common';
import { type GraphQLFieldConfigArgumentMap } from 'graphql';
import { isInputObjectType, type GraphQLFieldConfigArgumentMap } from 'graphql';
import { isDefined } from 'twenty-shared/utils';
import { type ArgsMetadata } from 'src/engine/api/graphql/workspace-schema-builder/interfaces/param-metadata.interface';
import { type WorkspaceBuildSchemaOptions } from 'src/engine/api/graphql/workspace-schema-builder/interfaces/workspace-build-schema-options.interface';
import { TypeMapperService } from 'src/engine/api/graphql/workspace-schema-builder/services/type-mapper.service';
import { TypeDefinitionsStorage } from 'src/engine/api/graphql/workspace-schema-builder/storages/type-definitions.storage';
import { GqlTypesStorage } from 'src/engine/api/graphql/workspace-schema-builder/storages/gql-types.storage';
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 ArgsFactory {
private readonly logger = new Logger(ArgsFactory.name);
export class ArgsTypeGenerator {
private readonly logger = new Logger(ArgsTypeGenerator.name);
constructor(
private readonly typeDefinitionsStorage: TypeDefinitionsStorage,
private readonly typeMapperService: TypeMapperService,
private readonly gqlTypesStorage: GqlTypesStorage,
) {}
public create(
{ args, objectMetadataId }: ArgsMetadata,
options: WorkspaceBuildSchemaOptions,
): GraphQLFieldConfigArgumentMap {
public generate({
args,
objectMetadataSingularName,
}: ArgsMetadata): GraphQLFieldConfigArgumentMap {
const fieldConfigMap: GraphQLFieldConfigArgumentMap = {};
for (const key in args) {
@@ -31,8 +32,8 @@ export class ArgsFactory {
const arg = args[key];
// Argument is a scalar type
if (arg.type) {
const gqlType = this.typeMapperService.mapToGqlType(arg.type, {
if (isDefined(arg.type)) {
const gqlType = this.typeMapperService.applyTypeOptions(arg.type, {
defaultValue: arg.defaultValue,
nullable: arg.isNullable,
isArray: arg.isArray,
@@ -44,27 +45,28 @@ export class ArgsFactory {
}
// Argument is an input type
if (arg.kind) {
const inputType = this.typeDefinitionsStorage.getInputTypeByKey(
objectMetadataId,
if (isDefined(arg.kind)) {
const storageKey = computeObjectMetadataInputTypeKey(
objectMetadataSingularName,
arg.kind,
);
if (!inputType) {
const inputType = this.gqlTypesStorage.getGqlTypeByKey(storageKey);
if (!isDefined(inputType) || !isInputObjectType(inputType)) {
this.logger.error(
`Could not find a GraphQL input type for ${objectMetadataId}`,
`Could not find a GraphQL input type for ${objectMetadataSingularName}`,
{
objectMetadataId,
options,
objectMetadataSingularName,
},
);
throw new Error(
`Could not find a GraphQL input type for ${objectMetadataId}`,
`Could not find a GraphQL input type for ${objectMetadataSingularName}`,
);
}
const gqlType = this.typeMapperService.mapToGqlType(inputType, {
const gqlType = this.typeMapperService.applyTypeOptions(inputType, {
nullable: arg.isNullable,
isArray: arg.isArray,
});
@@ -3,14 +3,13 @@ import { Injectable, Logger } from '@nestjs/common';
import { GraphQLEnumType } from 'graphql';
import { type FieldMetadataType } from 'twenty-shared/types';
import { type WorkspaceBuildSchemaOptions } from 'src/engine/api/graphql/workspace-schema-builder/interfaces/workspace-build-schema-options.interface';
import {
type CompositeProperty,
type CompositeType,
} from 'src/engine/metadata-modules/field-metadata/interfaces/composite-type.interface';
import { EnumTypeDefinitionFactory } from 'src/engine/api/graphql/workspace-schema-builder/factories/enum-type-definition.factory';
import { computeCompositePropertyTarget } from 'src/engine/api/graphql/workspace-schema-builder/utils/compute-composite-property-target.util';
import { GqlTypesStorage } from 'src/engine/api/graphql/workspace-schema-builder/storages/gql-types.storage';
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 {
type FieldMetadataComplexOption,
type FieldMetadataDefaultOption,
@@ -18,44 +17,33 @@ import {
import { isEnumFieldMetadataType } from 'src/engine/metadata-modules/field-metadata/utils/is-enum-field-metadata-type.util';
import { pascalCase } from 'src/utils/pascal-case';
export interface EnumTypeDefinition {
target: string;
type: GraphQLEnumType;
}
@Injectable()
export class CompositeEnumTypeDefinitionFactory {
private readonly logger = new Logger(EnumTypeDefinitionFactory.name);
export class CompositeFieldMetadataGqlEnumTypeGenerator {
private readonly logger = new Logger(
CompositeFieldMetadataGqlEnumTypeGenerator.name,
);
public create(
compositeType: CompositeType,
options: WorkspaceBuildSchemaOptions,
): EnumTypeDefinition[] {
const enumTypeDefinitions: EnumTypeDefinition[] = [];
constructor(private readonly gqlTypesStorage: GqlTypesStorage) {}
public buildAndStore(compositeType: CompositeType) {
for (const compositeProperty of compositeType.properties) {
if (!isEnumFieldMetadataType(compositeProperty.type)) {
continue;
}
const target = computeCompositePropertyTarget(
compositeType.type,
compositeProperty,
this.gqlTypesStorage.addGqlType(
computeCompositeFieldEnumTypeKey(
compositeType.type,
compositeProperty.name,
),
this.generate(compositeType.type, compositeProperty),
);
enumTypeDefinitions.push({
target,
type: this.generateEnum(compositeType.type, compositeProperty, options),
});
}
return enumTypeDefinitions;
}
private generateEnum(
private generate(
compositeType: FieldMetadataType,
compositeProperty: CompositeProperty,
options: WorkspaceBuildSchemaOptions,
): GraphQLEnumType {
// FixMe: It's a hack until Typescript get fixed on union types for reduce function
// https://github.com/microsoft/TypeScript/issues/36390
@@ -68,7 +56,6 @@ export class CompositeEnumTypeDefinitionFactory {
`Enum options are not defined for ${compositeProperty.name}`,
{
compositeProperty,
options,
},
);
@@ -3,8 +3,8 @@ import { Injectable, Logger } from '@nestjs/common';
import { GraphQLEnumType } from 'graphql';
import { isDefined } from 'twenty-shared/utils';
import { type WorkspaceBuildSchemaOptions } from 'src/engine/api/graphql/workspace-schema-builder/interfaces/workspace-build-schema-options.interface';
import { GqlTypesStorage } from 'src/engine/api/graphql/workspace-schema-builder/storages/gql-types.storage';
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 {
type FieldMetadataComplexOption,
type FieldMetadataDefaultOption,
@@ -15,43 +15,33 @@ import { type ObjectMetadataEntity } from 'src/engine/metadata-modules/object-me
import { transformEnumValue } from 'src/engine/utils/transform-enum-value';
import { pascalCase } from 'src/utils/pascal-case';
export interface EnumTypeDefinition {
target: string;
type: GraphQLEnumType;
}
@Injectable()
export class EnumTypeDefinitionFactory {
private readonly logger = new Logger(EnumTypeDefinitionFactory.name);
export class EnumFieldMetadataGqlEnumTypeGenerator {
private readonly logger = new Logger(
EnumFieldMetadataGqlEnumTypeGenerator.name,
);
public create(
objectMetadata: ObjectMetadataEntity,
options: WorkspaceBuildSchemaOptions,
): EnumTypeDefinition[] {
const enumTypeDefinitions: EnumTypeDefinition[] = [];
constructor(private readonly gqlTypesStorage: GqlTypesStorage) {}
public buildAndStore(objectMetadata: ObjectMetadataEntity) {
for (const fieldMetadata of objectMetadata.fields) {
if (!isEnumFieldMetadataType(fieldMetadata.type)) {
continue;
}
enumTypeDefinitions.push({
target: fieldMetadata.id,
type: this.generateEnum(
this.gqlTypesStorage.addGqlType(
computeEnumFieldGqlTypeKey(
objectMetadata.nameSingular,
fieldMetadata,
options,
fieldMetadata.name,
),
});
this.generateEnum(objectMetadata.nameSingular, fieldMetadata),
);
}
return enumTypeDefinitions;
}
private generateEnum(
objectName: string,
fieldMetadata: FieldMetadataEntity,
options: WorkspaceBuildSchemaOptions,
): GraphQLEnumType {
// FixMe: It's a hack until Typescript get fixed on union types for reduce function
// https://github.com/microsoft/TypeScript/issues/36390
@@ -66,7 +56,6 @@ export class EnumTypeDefinitionFactory {
`Enum options are not defined for ${fieldMetadata.name}`,
{
fieldMetadata,
options,
},
);
@@ -0,0 +1,93 @@
import { Injectable, Logger } from '@nestjs/common';
import { type CompositeType } from 'src/engine/metadata-modules/field-metadata/interfaces/composite-type.interface';
import { CompositeFieldMetadataGqlEnumTypeGenerator } from 'src/engine/api/graphql/workspace-schema-builder/graphql-type-generators/enum-types/composite-field-metadata-gql-enum-type.generator';
import { EnumFieldMetadataGqlEnumTypeGenerator } from 'src/engine/api/graphql/workspace-schema-builder/graphql-type-generators/enum-types/enum-field-metadata-gql-enum-type.generator';
import { CompositeFieldMetadataGqlInputTypeGenerator } from 'src/engine/api/graphql/workspace-schema-builder/graphql-type-generators/input-types/composite-field-metadata-gql-input-type.generator';
import { ObjectMetadataGqlInputTypeGenerator } from 'src/engine/api/graphql/workspace-schema-builder/graphql-type-generators/input-types/object-metadata-gql-input-type.generator';
import { RelationConnectGqlInputTypeGenerator } from 'src/engine/api/graphql/workspace-schema-builder/graphql-type-generators/input-types/relation-connect-gql-input-type.generator';
import { CompositeFieldMetadataGqlObjectTypeGenerator } from 'src/engine/api/graphql/workspace-schema-builder/graphql-type-generators/object-types/composite-field-metadata-gql-object-type.generator';
import { ConnectionGqlObjectTypeGenerator } from 'src/engine/api/graphql/workspace-schema-builder/graphql-type-generators/object-types/connection-gql-object-type.generator';
import { EdgeGqlObjectTypeGenerator } from 'src/engine/api/graphql/workspace-schema-builder/graphql-type-generators/object-types/edge-gql-object-type.generator';
import { ObjectMetadataGqlObjectTypeGenerator } from 'src/engine/api/graphql/workspace-schema-builder/graphql-type-generators/object-types/object-metadata-gql-object-type.generator';
import { ObjectMetadataWithRelationsGqlObjectTypeGenerator } from 'src/engine/api/graphql/workspace-schema-builder/graphql-type-generators/object-types/object-metadata-with-relations-gql-object-type.generator';
import { MutationTypeGenerator } from 'src/engine/api/graphql/workspace-schema-builder/graphql-type-generators/root-types/mutation-type.generator';
import { QueryTypeGenerator } from 'src/engine/api/graphql/workspace-schema-builder/graphql-type-generators/root-types/query-type.generator';
import { objectContainsMorphRelationField } from 'src/engine/api/graphql/workspace-schema-builder/utils/object-contains-morph-relation-field.util';
import { objectContainsRelationField } from 'src/engine/api/graphql/workspace-schema-builder/utils/object-contains-relation-field';
import { compositeTypeDefinitions } from 'src/engine/metadata-modules/field-metadata/composite-types';
import { ObjectMetadataEntity } from 'src/engine/metadata-modules/object-metadata/object-metadata.entity';
@Injectable()
export class GqlTypeGenerator {
private readonly logger = new Logger(GqlTypeGenerator.name);
constructor(
private readonly objectMetadataGqlObjectTypeGenerator: ObjectMetadataGqlObjectTypeGenerator,
private readonly objectMetadataGqlInputTypeGenerator: ObjectMetadataGqlInputTypeGenerator,
private readonly compositeFieldMetadataGqlObjectTypeGenerator: CompositeFieldMetadataGqlObjectTypeGenerator,
private readonly enumFieldMetadataGqlEnumTypeGenerator: EnumFieldMetadataGqlEnumTypeGenerator,
private readonly compositeFieldMetadataGqlEnumTypeGenerator: CompositeFieldMetadataGqlEnumTypeGenerator,
private readonly compositeFieldMetadataGqlInputTypeGenerator: CompositeFieldMetadataGqlInputTypeGenerator,
private readonly edgeGqlObjectTypeGenerator: EdgeGqlObjectTypeGenerator,
private readonly connectionGqlObjectTypeGenerator: ConnectionGqlObjectTypeGenerator,
private readonly objectMetadataWithRelationsGqlObjectTypeGenerator: ObjectMetadataWithRelationsGqlObjectTypeGenerator,
private readonly relationConnectGqlInputTypeGenerator: RelationConnectGqlInputTypeGenerator,
private readonly queryTypeGenerator: QueryTypeGenerator,
private readonly mutationTypeGenerator: MutationTypeGenerator,
) {}
buildAndStore(objectMetadataCollection: ObjectMetadataEntity[]) {
const compositeTypeCollection = [...compositeTypeDefinitions.values()];
this.buildAndStoreCompositeFieldMetadataGqlTypes(compositeTypeCollection);
this.buildAndStoreObjectMetadataGqlTypes(objectMetadataCollection);
this.queryTypeGenerator.buildAndStore(objectMetadataCollection);
this.mutationTypeGenerator.buildAndStore(objectMetadataCollection);
}
private buildAndStoreCompositeFieldMetadataGqlTypes(
compositeTypes: CompositeType[],
) {
for (const compositeType of compositeTypes) {
this.compositeFieldMetadataGqlEnumTypeGenerator.buildAndStore(
compositeType,
);
this.compositeFieldMetadataGqlObjectTypeGenerator.buildAndStore(
compositeType,
);
this.compositeFieldMetadataGqlInputTypeGenerator.buildAndStore(
compositeType,
);
}
}
private buildAndStoreObjectMetadataGqlTypes(
dynamicObjectMetadataCollection: ObjectMetadataEntity[],
) {
this.logger.log(
`Generating metadata objects: [${dynamicObjectMetadataCollection
.map((object) => object.nameSingular)
.join(', ')}]`,
);
for (const objectMetadata of dynamicObjectMetadataCollection) {
this.enumFieldMetadataGqlEnumTypeGenerator.buildAndStore(objectMetadata);
this.objectMetadataGqlObjectTypeGenerator.buildAndStore(objectMetadata);
this.edgeGqlObjectTypeGenerator.buildAndStore(objectMetadata);
this.connectionGqlObjectTypeGenerator.buildAndStore(objectMetadata);
this.relationConnectGqlInputTypeGenerator.buildAndStore(objectMetadata);
this.objectMetadataGqlInputTypeGenerator.buildAndStore(objectMetadata);
if (
objectContainsRelationField(objectMetadata) ||
objectContainsMorphRelationField(objectMetadata)
)
this.objectMetadataWithRelationsGqlObjectTypeGenerator.buildAndStore(
objectMetadata,
dynamicObjectMetadataCollection,
);
}
}
}
@@ -0,0 +1,33 @@
import { Injectable } from '@nestjs/common';
import { type CompositeType } from 'src/engine/metadata-modules/field-metadata/interfaces/composite-type.interface';
import { CompositeFieldMetadataCreateGqlInputTypeGenerator } from 'src/engine/api/graphql/workspace-schema-builder/graphql-type-generators/input-types/create-input/composite-field-metadata-create-gql-input-type.generator';
import { CompositeFieldMetadataFilterGqlInputTypeGenerator } from 'src/engine/api/graphql/workspace-schema-builder/graphql-type-generators/input-types/filter-input/composite-field-metadata-filter-gql-input-types.generator';
import { CompositeFieldMetadataOrderByGqlInputTypeGenerator } from 'src/engine/api/graphql/workspace-schema-builder/graphql-type-generators/input-types/order-by-input/composite-field-metadata-order-by-gql-input-type.generator';
import { CompositeFieldMetadataUpdateGqlInputTypeGenerator } from 'src/engine/api/graphql/workspace-schema-builder/graphql-type-generators/input-types/update-input/composite-field-metadata-update-gql-input-type.generator';
@Injectable()
export class CompositeFieldMetadataGqlInputTypeGenerator {
constructor(
private readonly compositeFieldMetadataCreateGqlInputTypeGenerator: CompositeFieldMetadataCreateGqlInputTypeGenerator,
private readonly compositeFieldMetadataUpdateGqlInputTypeGenerator: CompositeFieldMetadataUpdateGqlInputTypeGenerator,
private readonly compositeFieldMetadataFilterGqlInputTypeGenerator: CompositeFieldMetadataFilterGqlInputTypeGenerator,
private readonly compositeFieldMetadataOrderByGqlInputTypeGenerator: CompositeFieldMetadataOrderByGqlInputTypeGenerator,
) {}
public buildAndStore(compositeType: CompositeType) {
this.compositeFieldMetadataCreateGqlInputTypeGenerator.buildAndStore(
compositeType,
);
this.compositeFieldMetadataUpdateGqlInputTypeGenerator.buildAndStore(
compositeType,
);
this.compositeFieldMetadataFilterGqlInputTypeGenerator.buildAndStore(
compositeType,
);
this.compositeFieldMetadataOrderByGqlInputTypeGenerator.buildAndStore(
compositeType,
);
}
}
@@ -0,0 +1,102 @@
import { Injectable, Logger } from '@nestjs/common';
import {
GraphQLInputFieldConfigMap,
GraphQLInputObjectType,
isObjectType,
} from 'graphql';
import { isDefined } from 'twenty-shared/utils';
import { CompositeType } from 'src/engine/metadata-modules/field-metadata/interfaces/composite-type.interface';
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 { 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';
import { isEnumFieldMetadataType } from 'src/engine/metadata-modules/field-metadata/utils/is-enum-field-metadata-type.util';
import { isMorphOrRelationFieldMetadataType } from 'src/engine/utils/is-morph-or-relation-field-metadata-type.util';
import { pascalCase } from 'src/utils/pascal-case';
@Injectable()
export class CompositeFieldMetadataCreateGqlInputTypeGenerator {
private readonly logger = new Logger(
CompositeFieldMetadataCreateGqlInputTypeGenerator.name,
);
constructor(
private readonly gqlTypesStorage: GqlTypesStorage,
private readonly typeMapperService: TypeMapperService,
) {}
public buildAndStore(compositeType: CompositeType) {
const key = computeCompositeFieldInputTypeKey(
compositeType.type,
GqlInputTypeDefinitionKind.Create,
);
const type = new GraphQLInputObjectType({
name: `${pascalCase(compositeType.type)}${GqlInputTypeDefinitionKind.Create.toString()}Input`,
fields: this.generateFields(compositeType),
});
this.gqlTypesStorage.addGqlType(key, type);
}
public generateFields(
compositeType: CompositeType,
): GraphQLInputFieldConfigMap {
const fields: GraphQLInputFieldConfigMap = {};
for (const property of compositeType.properties) {
// Relation fields are not supported in composite types
if (isMorphOrRelationFieldMetadataType(property.type)) {
this.logger.error(
'Relation fields are not supported in composite types',
{ compositeType, property },
);
throw new Error('Relation fields are not supported in composite types');
}
// Skip hidden fields
if (property.hidden === true || property.hidden === 'input') {
continue;
}
const key = computeCompositeFieldEnumTypeKey(
compositeType.type,
property.name,
);
const typeOptions = computeCompositeFieldTypeOptions(property);
const type = isEnumFieldMetadataType(property.type)
? this.gqlTypesStorage.getGqlTypeByKey(key)
: this.typeMapperService.mapToScalarType(property.type, typeOptions);
if (!isDefined(type) || isObjectType(type)) {
const message = `Could not find a GraphQL input type for ${compositeType.type} ${property.name}`;
this.logger.error(message, {
type,
typeOptions,
});
throw new Error(message);
}
const modifiedType = this.typeMapperService.applyTypeOptions(
type,
typeOptions,
);
fields[property.name] = {
type: modifiedType,
description: property.description,
};
}
return fields;
}
}
@@ -0,0 +1,155 @@
import { Injectable, Logger } from '@nestjs/common';
import { isDefined } from 'class-validator';
import {
GraphQLEnumType,
GraphQLInputFieldConfigMap,
GraphQLInputObjectType,
GraphQLScalarType,
isEnumType,
isInputObjectType,
isObjectType,
} from 'graphql';
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 } 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';
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 { computeObjectMetadataInputTypeKey } from 'src/engine/api/graphql/workspace-schema-builder/utils/compute-stored-gql-type-key-utils/compute-object-metadata-input-type.util';
import { isFieldMetadataRelationOrMorphRelation } from 'src/engine/api/graphql/workspace-schema-builder/utils/is-field-metadata-relation-or-morph-relation.utils';
import { isCompositeFieldMetadataType } from 'src/engine/metadata-modules/field-metadata/utils/is-composite-field-metadata-type.util';
import { isEnumFieldMetadataType } from 'src/engine/metadata-modules/field-metadata/utils/is-enum-field-metadata-type.util';
import { ObjectMetadataEntity } from 'src/engine/metadata-modules/object-metadata/object-metadata.entity';
import { pascalCase } from 'src/utils/pascal-case';
@Injectable()
export class ObjectMetadataCreateGqlInputTypeGenerator {
private readonly logger = new Logger(
ObjectMetadataCreateGqlInputTypeGenerator.name,
);
constructor(
private readonly gqlTypesStorage: GqlTypesStorage,
private readonly relationFieldMetadataGqlInputTypeGenerator: RelationFieldMetadataGqlInputTypeGenerator,
private readonly typeMapperService: TypeMapperService,
) {}
public buildAndStore(objectMetadata: ObjectMetadataEntity) {
const inputType = new GraphQLInputObjectType({
name: `${pascalCase(objectMetadata.nameSingular)}${GqlInputTypeDefinitionKind.Create.toString()}Input`,
description: objectMetadata.description,
fields: () => this.generateFields(objectMetadata),
}) as GraphQLInputObjectType;
const key = computeObjectMetadataInputTypeKey(
objectMetadata.nameSingular,
GqlInputTypeDefinitionKind.Create,
);
this.gqlTypesStorage.addGqlType(key, inputType);
}
private generateFields(
objectMetadata: ObjectMetadataEntity,
): GraphQLInputFieldConfigMap {
const allGeneratedFields: GraphQLInputFieldConfigMap = {};
for (const fieldMetadata of objectMetadata.fields) {
const typeOptions = computeFieldInputTypeOptions(
fieldMetadata,
GqlInputTypeDefinitionKind.Create,
);
let generatedFields;
if (isFieldMetadataRelationOrMorphRelation(fieldMetadata)) {
generatedFields = {
...this.relationFieldMetadataGqlInputTypeGenerator.generateSimpleRelationFieldCreateOrUpdateInputType(
{
fieldMetadata,
typeOptions,
},
),
...this.relationFieldMetadataGqlInputTypeGenerator.generateConnectRelationFieldInputType(
{
fieldMetadata,
typeOptions,
},
),
};
} else {
let type:
| GraphQLInputObjectType
| GraphQLEnumType
| GraphQLScalarType
| undefined;
if (isEnumFieldMetadataType(fieldMetadata.type)) {
const key = computeEnumFieldGqlTypeKey(
objectMetadata.nameSingular,
fieldMetadata.name,
);
const enumType = this.gqlTypesStorage.getGqlTypeByKey(key);
if (!isDefined(enumType) || !isEnumType(enumType)) {
const message = `Could not find a GraphQL input type for ${fieldMetadata.type} field metadata`;
this.logger.error(message, {
type,
typeOptions,
});
throw new Error(message);
}
type = enumType;
} else if (isCompositeFieldMetadataType(fieldMetadata.type)) {
const key = computeCompositeFieldInputTypeKey(
fieldMetadata.type,
GqlInputTypeDefinitionKind.Create,
);
const compositeType = this.gqlTypesStorage.getGqlTypeByKey(key);
if (!isDefined(compositeType) || !isInputObjectType(compositeType)) {
const message = `Could not find a GraphQL input type for ${fieldMetadata.type} field metadata`;
this.logger.error(message, {
type,
typeOptions,
});
throw new Error(message);
}
type = compositeType;
} else {
type = this.typeMapperService.mapToScalarType(
fieldMetadata.type,
typeOptions,
);
if (!isDefined(type) || isObjectType(type)) {
const message = `Could not find a GraphQL input type for ${fieldMetadata.type} field metadata`;
this.logger.error(message, {
type,
typeOptions,
});
throw new Error(message);
}
}
generatedFields = {
[fieldMetadata.name]: {
type,
description: fieldMetadata.description,
},
};
}
Object.assign(allGeneratedFields, generatedFields);
}
return allGeneratedFields;
}
}
@@ -0,0 +1,119 @@
import { Injectable, Logger } from '@nestjs/common';
import {
GraphQLInputFieldConfigMap,
GraphQLInputObjectType,
GraphQLInputType,
isEnumType,
isObjectType,
} from 'graphql';
import { isDefined } from 'twenty-shared/utils';
import { CompositeType } from 'src/engine/metadata-modules/field-metadata/interfaces/composite-type.interface';
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 { 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';
import { createGqlEnumFilterType } from 'src/engine/api/graphql/workspace-schema-builder/utils/create-gql-enum-filter-type.util';
import { isEnumFieldMetadataType } from 'src/engine/metadata-modules/field-metadata/utils/is-enum-field-metadata-type.util';
import { isMorphOrRelationFieldMetadataType } from 'src/engine/utils/is-morph-or-relation-field-metadata-type.util';
import { pascalCase } from 'src/utils/pascal-case';
@Injectable()
export class CompositeFieldMetadataFilterGqlInputTypeGenerator {
private readonly logger = new Logger(
CompositeFieldMetadataFilterGqlInputTypeGenerator.name,
);
constructor(
private readonly gqlTypesStorage: GqlTypesStorage,
private readonly typeMapperService: TypeMapperService,
) {}
public buildAndStore(compositeType: CompositeType) {
const key = computeCompositeFieldInputTypeKey(
compositeType.type,
GqlInputTypeDefinitionKind.Filter,
);
const type = new GraphQLInputObjectType({
name: `${pascalCase(compositeType.type)}${GqlInputTypeDefinitionKind.Filter.toString()}Input`,
fields: this.generateFields(compositeType),
});
this.gqlTypesStorage.addGqlType(key, type);
}
public generateFields(
compositeType: CompositeType,
): GraphQLInputFieldConfigMap {
const fields: GraphQLInputFieldConfigMap = {};
for (const property of compositeType.properties) {
property.isRequired = false;
// Relation fields are not supported in composite types
if (isMorphOrRelationFieldMetadataType(property.type)) {
this.logger.error(
'Relation fields are not supported in composite types',
{ compositeType, property },
);
throw new Error('Relation fields are not supported in composite types');
}
// Skip hidden fields
if (property.hidden === true) continue;
const key = computeCompositeFieldEnumTypeKey(
compositeType.type,
property.name,
);
const typeOptions = computeCompositeFieldTypeOptions(property);
let type: GraphQLInputType | undefined;
if (isEnumFieldMetadataType(property.type)) {
const enumType = this.gqlTypesStorage.getGqlTypeByKey(key);
if (!isDefined(enumType) || !isEnumType(enumType)) {
const message = `Could not find a GraphQL enum type for ${key}`;
this.logger.error(message, { key });
throw new Error(message);
}
type = createGqlEnumFilterType(enumType);
} else {
type = this.typeMapperService.mapToFilterType(
property.type,
typeOptions,
);
}
if (!isDefined(type) || isObjectType(type)) {
const message = `Could not find a GraphQL input type for ${compositeType.type} ${property.name}`;
this.logger.error(message, {
type,
typeOptions,
});
throw new Error(message);
}
const modifiedType = this.typeMapperService.applyTypeOptions(
type,
typeOptions,
);
fields[property.name] = {
type: modifiedType,
description: property.description,
};
}
return fields;
}
}
@@ -0,0 +1,165 @@
import { Injectable, Logger } from '@nestjs/common';
import {
GraphQLInputFieldConfigMap,
GraphQLInputObjectType,
GraphQLInputType,
isEnumType,
isInputObjectType,
isObjectType,
} from 'graphql';
import { isDefined } 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 } 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';
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 { computeObjectMetadataInputTypeKey } from 'src/engine/api/graphql/workspace-schema-builder/utils/compute-stored-gql-type-key-utils/compute-object-metadata-input-type.util';
import { createGqlEnumFilterType } from 'src/engine/api/graphql/workspace-schema-builder/utils/create-gql-enum-filter-type.util';
import { isFieldMetadataRelationOrMorphRelation } from 'src/engine/api/graphql/workspace-schema-builder/utils/is-field-metadata-relation-or-morph-relation.utils';
import { isCompositeFieldMetadataType } from 'src/engine/metadata-modules/field-metadata/utils/is-composite-field-metadata-type.util';
import { isEnumFieldMetadataType } from 'src/engine/metadata-modules/field-metadata/utils/is-enum-field-metadata-type.util';
import { ObjectMetadataEntity } from 'src/engine/metadata-modules/object-metadata/object-metadata.entity';
import { pascalCase } from 'src/utils/pascal-case';
@Injectable()
export class ObjectMetadataFilterGqlInputTypeGenerator {
private readonly logger = new Logger(
ObjectMetadataFilterGqlInputTypeGenerator.name,
);
constructor(
private readonly gqlTypesStorage: GqlTypesStorage,
private readonly relationFieldMetadataGqlInputTypeGenerator: RelationFieldMetadataGqlInputTypeGenerator,
private readonly typeMapperService: TypeMapperService,
) {}
public buildAndStore(objectMetadata: ObjectMetadataEntity) {
const inputType = new GraphQLInputObjectType({
name: `${pascalCase(objectMetadata.nameSingular)}${GqlInputTypeDefinitionKind.Filter.toString()}Input`,
description: objectMetadata.description,
fields: () => this.generateFields(objectMetadata, inputType),
}) as GraphQLInputObjectType;
const key = computeObjectMetadataInputTypeKey(
objectMetadata.nameSingular,
GqlInputTypeDefinitionKind.Filter,
);
this.gqlTypesStorage.addGqlType(key, inputType);
}
private generateFields(
objectMetadata: ObjectMetadataEntity,
inputType: GraphQLInputObjectType,
): GraphQLInputFieldConfigMap {
const andOrType = this.typeMapperService.applyTypeOptions(inputType, {
isArray: true,
arrayDepth: 1,
nullable: true,
});
const allGeneratedFields: GraphQLInputFieldConfigMap = {};
for (const fieldMetadata of objectMetadata.fields) {
fieldMetadata.isNullable = true;
const typeOptions = computeFieldInputTypeOptions(
fieldMetadata,
GqlInputTypeDefinitionKind.Update,
);
let generatedFields;
if (isFieldMetadataRelationOrMorphRelation(fieldMetadata)) {
generatedFields =
this.relationFieldMetadataGqlInputTypeGenerator.generateSimpleRelationFieldFilterInputType(
{
fieldMetadata,
typeOptions,
},
);
} else {
let type: GraphQLInputType | undefined;
if (isEnumFieldMetadataType(fieldMetadata.type)) {
const key = computeEnumFieldGqlTypeKey(
objectMetadata.nameSingular,
fieldMetadata.name,
);
const enumType = this.gqlTypesStorage.getGqlTypeByKey(key);
if (!isDefined(enumType) || !isEnumType(enumType)) {
const message = `Could not find a GraphQL input type for ${fieldMetadata.type} field metadata`;
this.logger.error(message, {
type,
typeOptions,
});
throw new Error(message);
}
type = createGqlEnumFilterType(enumType);
} else if (isCompositeFieldMetadataType(fieldMetadata.type)) {
const key = computeCompositeFieldInputTypeKey(
fieldMetadata.type,
GqlInputTypeDefinitionKind.Filter,
);
const compositeType = this.gqlTypesStorage.getGqlTypeByKey(key);
if (!isDefined(compositeType) || !isInputObjectType(compositeType)) {
const message = `Could not find a GraphQL input type for ${fieldMetadata.type} field metadata`;
this.logger.error(message, {
type,
typeOptions,
});
throw new Error(message);
}
type = compositeType;
} else {
type = this.typeMapperService.mapToFilterType(
fieldMetadata.type,
typeOptions,
);
if (!isDefined(type) || isObjectType(type)) {
const message = `Could not find a GraphQL input type for ${fieldMetadata.type} field metadata`;
this.logger.error(message, {
type,
typeOptions,
});
throw new Error(message);
}
}
generatedFields = {
[fieldMetadata.name]: {
type,
description: fieldMetadata.description,
},
};
}
Object.assign(allGeneratedFields, generatedFields);
}
return {
...allGeneratedFields,
and: {
type: andOrType,
},
or: {
type: andOrType,
},
not: {
type: this.typeMapperService.applyTypeOptions(inputType, {
nullable: true,
}),
},
};
}
}
@@ -0,0 +1,32 @@
import { Injectable } from '@nestjs/common';
import { ObjectMetadataCreateGqlInputTypeGenerator } from 'src/engine/api/graphql/workspace-schema-builder/graphql-type-generators/input-types/create-input/object-metadata-create-gql-input-type.generator';
import { ObjectMetadataFilterGqlInputTypeGenerator } from 'src/engine/api/graphql/workspace-schema-builder/graphql-type-generators/input-types/filter-input/object-metadata-filter-gql-input-type.generator';
import { ObjectMetadataOrderByGqlInputTypeGenerator } from 'src/engine/api/graphql/workspace-schema-builder/graphql-type-generators/input-types/order-by-input/object-metadata-order-by-gql-input-type.generator';
import { ObjectMetadataUpdateGqlInputTypeGenerator } from 'src/engine/api/graphql/workspace-schema-builder/graphql-type-generators/input-types/update-input/object-metadata-update-gql-input-type.generator';
import { ObjectMetadataEntity } from 'src/engine/metadata-modules/object-metadata/object-metadata.entity';
@Injectable()
export class ObjectMetadataGqlInputTypeGenerator {
constructor(
private readonly objectMetadataCreateGqlInputTypeGenerator: ObjectMetadataCreateGqlInputTypeGenerator,
private readonly objectMetadataUpdateGqlInputTypeGenerator: ObjectMetadataUpdateGqlInputTypeGenerator,
private readonly objectMetadataFilterGqlInputTypeGenerator: ObjectMetadataFilterGqlInputTypeGenerator,
private readonly objectMetadataOrderByGqlInputTypeGenerator: ObjectMetadataOrderByGqlInputTypeGenerator,
) {}
public buildAndStore(objectMetadata: ObjectMetadataEntity) {
this.objectMetadataCreateGqlInputTypeGenerator.buildAndStore(
objectMetadata,
);
this.objectMetadataUpdateGqlInputTypeGenerator.buildAndStore(
objectMetadata,
);
this.objectMetadataFilterGqlInputTypeGenerator.buildAndStore(
objectMetadata,
);
this.objectMetadataOrderByGqlInputTypeGenerator.buildAndStore(
objectMetadata,
);
}
}
@@ -0,0 +1,103 @@
import { Injectable, Logger } from '@nestjs/common';
import {
GraphQLInputFieldConfigMap,
GraphQLInputObjectType,
isObjectType,
} from 'graphql';
import { isDefined } from 'twenty-shared/utils';
import { CompositeType } from 'src/engine/metadata-modules/field-metadata/interfaces/composite-type.interface';
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 { 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';
import { isEnumFieldMetadataType } from 'src/engine/metadata-modules/field-metadata/utils/is-enum-field-metadata-type.util';
import { isMorphOrRelationFieldMetadataType } from 'src/engine/utils/is-morph-or-relation-field-metadata-type.util';
import { pascalCase } from 'src/utils/pascal-case';
@Injectable()
export class CompositeFieldMetadataOrderByGqlInputTypeGenerator {
private readonly logger = new Logger(
CompositeFieldMetadataOrderByGqlInputTypeGenerator.name,
);
constructor(
private readonly gqlTypesStorage: GqlTypesStorage,
private readonly typeMapperService: TypeMapperService,
) {}
public buildAndStore(compositeType: CompositeType) {
const key = computeCompositeFieldInputTypeKey(
compositeType.type,
GqlInputTypeDefinitionKind.OrderBy,
);
const type = new GraphQLInputObjectType({
name: `${pascalCase(compositeType.type)}${GqlInputTypeDefinitionKind.OrderBy.toString()}Input`,
fields: this.generateFields(compositeType),
});
this.gqlTypesStorage.addGqlType(key, type);
}
public generateFields(
compositeType: CompositeType,
): GraphQLInputFieldConfigMap {
const fields: GraphQLInputFieldConfigMap = {};
for (const property of compositeType.properties) {
property.isRequired = false;
// Relation fields are not supported in composite types
if (isMorphOrRelationFieldMetadataType(property.type)) {
this.logger.error(
'Relation fields are not supported in composite types',
{ compositeType, property },
);
throw new Error('Relation fields are not supported in composite types');
}
// Skip hidden fields
if (property.hidden === true || property.hidden === 'input') {
continue;
}
const key = computeCompositeFieldEnumTypeKey(
compositeType.type,
property.name,
);
const typeOptions = computeCompositeFieldTypeOptions(property);
const type = isEnumFieldMetadataType(property.type)
? this.gqlTypesStorage.getGqlTypeByKey(key)
: this.typeMapperService.mapToOrderByType(property.type);
if (!isDefined(type) || isObjectType(type)) {
const message = `Could not find a GraphQL input type for ${compositeType.type} ${property.name}`;
this.logger.error(message, {
type,
typeOptions,
});
throw new Error(message);
}
const modifiedType = this.typeMapperService.applyTypeOptions(
type,
typeOptions,
);
fields[property.name] = {
type: modifiedType,
description: property.description,
};
}
return fields;
}
}
@@ -0,0 +1,160 @@
import { Injectable, Logger } from '@nestjs/common';
import {
GraphQLEnumType,
GraphQLInputFieldConfigMap,
GraphQLInputObjectType,
GraphQLInputType,
GraphQLList,
GraphQLScalarType,
isEnumType,
isInputObjectType,
} from 'graphql';
import { isDefined } 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 } 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';
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 { computeObjectMetadataInputTypeKey } from 'src/engine/api/graphql/workspace-schema-builder/utils/compute-stored-gql-type-key-utils/compute-object-metadata-input-type.util';
import { isFieldMetadataRelationOrMorphRelation } from 'src/engine/api/graphql/workspace-schema-builder/utils/is-field-metadata-relation-or-morph-relation.utils';
import { isCompositeFieldMetadataType } from 'src/engine/metadata-modules/field-metadata/utils/is-composite-field-metadata-type.util';
import { isEnumFieldMetadataType } from 'src/engine/metadata-modules/field-metadata/utils/is-enum-field-metadata-type.util';
import { ObjectMetadataEntity } from 'src/engine/metadata-modules/object-metadata/object-metadata.entity';
import { pascalCase } from 'src/utils/pascal-case';
@Injectable()
export class ObjectMetadataOrderByGqlInputTypeGenerator {
private readonly logger = new Logger(
ObjectMetadataOrderByGqlInputTypeGenerator.name,
);
constructor(
private readonly gqlTypesStorage: GqlTypesStorage,
private readonly relationFieldMetadataGqlInputTypeGenerator: RelationFieldMetadataGqlInputTypeGenerator,
private readonly typeMapperService: TypeMapperService,
) {}
public buildAndStore(objectMetadata: ObjectMetadataEntity) {
const inputType = new GraphQLInputObjectType({
name: `${pascalCase(objectMetadata.nameSingular)}${GqlInputTypeDefinitionKind.OrderBy.toString()}Input`,
description: objectMetadata.description,
fields: () => this.generateFields(objectMetadata),
}) as GraphQLInputObjectType;
const key = computeObjectMetadataInputTypeKey(
objectMetadata.nameSingular,
GqlInputTypeDefinitionKind.OrderBy,
);
this.gqlTypesStorage.addGqlType(key, inputType);
}
private generateFields(
objectMetadata: ObjectMetadataEntity,
): GraphQLInputFieldConfigMap {
const allGeneratedFields: GraphQLInputFieldConfigMap = {};
for (const fieldMetadata of objectMetadata.fields) {
fieldMetadata.isNullable = true;
const typeOptions = computeFieldInputTypeOptions(
fieldMetadata,
GqlInputTypeDefinitionKind.OrderBy,
);
let generatedFields;
if (isFieldMetadataRelationOrMorphRelation(fieldMetadata)) {
generatedFields = {
...this.relationFieldMetadataGqlInputTypeGenerator.generateSimpleRelationFieldCreateOrUpdateInputType(
{
fieldMetadata,
typeOptions,
},
),
...this.relationFieldMetadataGqlInputTypeGenerator.generateConnectRelationFieldInputType(
{
fieldMetadata,
typeOptions,
},
),
};
} else {
let type:
| GraphQLInputObjectType
| GraphQLEnumType
| GraphQLScalarType
| GraphQLList<GraphQLInputType>
| undefined;
if (isEnumFieldMetadataType(fieldMetadata.type)) {
const key = computeEnumFieldGqlTypeKey(
objectMetadata.nameSingular,
fieldMetadata.name,
);
const enumType = this.gqlTypesStorage.getGqlTypeByKey(key);
if (!isDefined(enumType) || !isEnumType(enumType)) {
const message = `Could not find a GraphQL input type for ${fieldMetadata.type} field metadata`;
this.logger.error(message, {
type,
typeOptions,
});
throw new Error(message);
}
type = enumType;
} else if (isCompositeFieldMetadataType(fieldMetadata.type)) {
const key = computeCompositeFieldInputTypeKey(
fieldMetadata.type,
GqlInputTypeDefinitionKind.OrderBy,
);
const compositeType = this.gqlTypesStorage.getGqlTypeByKey(key);
if (!isDefined(compositeType) || !isInputObjectType(compositeType)) {
const message = `Could not find a GraphQL input type for ${fieldMetadata.type} field metadata`;
this.logger.error(message, {
type,
typeOptions,
});
throw new Error(message);
}
type = compositeType;
} else {
const orderByType = this.typeMapperService.mapToOrderByType(
fieldMetadata.type,
);
if (!isDefined(orderByType)) {
const message = `Could not find a GraphQL input type for ${fieldMetadata.type} field metadata`;
this.logger.error(message, {
type,
typeOptions,
});
throw new Error(message);
}
type = orderByType;
}
generatedFields = {
[fieldMetadata.name]: {
type,
description: fieldMetadata.description,
},
};
}
Object.assign(allGeneratedFields, generatedFields);
}
return allGeneratedFields;
}
}
@@ -10,40 +10,28 @@ import {
import { RELATION_NESTED_QUERY_KEYWORDS } from 'twenty-shared/constants';
import { getUniqueConstraintsFields } from 'twenty-shared/utils';
import {
type InputTypeDefinition,
InputTypeDefinitionKind,
} from 'src/engine/api/graphql/workspace-schema-builder/factories/input-type-definition.factory';
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 { computeRelationConnectInputTypeKey } from 'src/engine/api/graphql/workspace-schema-builder/utils/compute-stored-gql-type-key-utils/compute-relation-connect-input-type-key.util';
import { compositeTypeDefinitions } from 'src/engine/metadata-modules/field-metadata/composite-types';
import { type FieldMetadataEntity } from 'src/engine/metadata-modules/field-metadata/field-metadata.entity';
import { isCompositeFieldMetadataType } from 'src/engine/metadata-modules/field-metadata/utils/is-composite-field-metadata-type.util';
import { type ObjectMetadataEntity } from 'src/engine/metadata-modules/object-metadata/object-metadata.entity';
import { pascalCase } from 'src/utils/pascal-case';
export const formatRelationConnectInputTarget = (objectMetadataId: string) =>
`${objectMetadataId}-connect-input`;
@Injectable()
export class RelationConnectInputTypeDefinitionFactory {
constructor(private readonly typeMapperService: TypeMapperService) {}
export class RelationConnectGqlInputTypeGenerator {
constructor(
private readonly typeMapperService: TypeMapperService,
private readonly gqlTypesStorage: GqlTypesStorage,
) {}
public create(objectMetadata: ObjectMetadataEntity): InputTypeDefinition[] {
const fields = this.generateRelationConnectInputType(objectMetadata);
const target = formatRelationConnectInputTarget(objectMetadata.id);
public buildAndStore(objectMetadata: ObjectMetadataEntity) {
const relationConnectGqlInputType =
this.generateRelationConnectInputType(objectMetadata);
const key = computeRelationConnectInputTypeKey(objectMetadata.id);
return [
{
target,
kind: InputTypeDefinitionKind.Create,
type: fields,
},
{
target,
kind: InputTypeDefinitionKind.Update,
type: fields,
},
];
this.gqlTypesStorage.addGqlType(key, relationConnectGqlInputType);
}
private generateRelationConnectInputType(
@@ -125,8 +113,7 @@ export class RelationConnectInputTypeDefinitionFactory {
} else {
const scalarType = this.typeMapperService.mapToScalarType(
field.type,
field.settings ?? undefined,
field.name === 'id',
{ settings: field.settings, isIdField: field.name === 'id' },
);
fields[field.name] = {
@@ -0,0 +1,193 @@
import { Injectable, Logger } from '@nestjs/common';
import { isObjectType } from 'graphql';
import { FieldMetadataType, RelationType } from 'twenty-shared/types';
import { isDefined } from 'twenty-shared/utils';
import {
TypeMapperService,
TypeOptions,
} 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 { 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';
import { FieldMetadataEntity } from 'src/engine/metadata-modules/field-metadata/field-metadata.entity';
@Injectable()
export class RelationFieldMetadataGqlInputTypeGenerator {
private readonly logger = new Logger(
RelationFieldMetadataGqlInputTypeGenerator.name,
);
constructor(
private readonly typeMapperService: TypeMapperService,
private readonly gqlTypesStorage: GqlTypesStorage,
) {}
public generateSimpleRelationFieldCreateOrUpdateInputType({
fieldMetadata,
typeOptions,
}: {
fieldMetadata: FieldMetadataEntity<
FieldMetadataType.RELATION | FieldMetadataType.MORPH_RELATION
>;
typeOptions: TypeOptions;
}) {
if (fieldMetadata.settings?.relationType === RelationType.ONE_TO_MANY)
return {};
const { joinColumnName } = extractGraphQLRelationFieldNames(fieldMetadata);
const type = this.typeMapperService.mapToScalarType(
fieldMetadata.type,
typeOptions,
);
if (!isDefined(type)) {
const message = `Could not find a GraphQL input type for ${type} field metadata`;
this.logger.error(message, {
type,
typeOptions,
});
throw new Error(message);
}
const modifiedType = this.typeMapperService.applyTypeOptions(
type,
typeOptions,
);
return {
[joinColumnName]: {
type: modifiedType,
description: fieldMetadata.description,
},
};
}
public generateSimpleRelationFieldFilterInputType({
fieldMetadata,
typeOptions,
}: {
fieldMetadata: FieldMetadataEntity<
FieldMetadataType.RELATION | FieldMetadataType.MORPH_RELATION
>;
typeOptions: TypeOptions;
}) {
if (fieldMetadata.settings?.relationType === RelationType.ONE_TO_MANY)
return {};
const { joinColumnName } = extractGraphQLRelationFieldNames(fieldMetadata);
const type = this.typeMapperService.mapToFilterType(
fieldMetadata.type,
typeOptions,
);
if (!isDefined(type)) {
const message = `Could not find a GraphQL input type for ${type} field metadata`;
this.logger.error(message, {
type,
typeOptions,
});
throw new Error(message);
}
const modifiedType = this.typeMapperService.applyTypeOptions(
type,
typeOptions,
);
return {
[joinColumnName]: {
type: modifiedType,
description: fieldMetadata.description,
},
};
}
public generateSimpleRelationFieldOrderByInputType({
fieldMetadata,
typeOptions,
}: {
fieldMetadata: FieldMetadataEntity<
FieldMetadataType.RELATION | FieldMetadataType.MORPH_RELATION
>;
typeOptions: TypeOptions;
}) {
if (fieldMetadata.settings?.relationType === RelationType.ONE_TO_MANY)
return {};
const { joinColumnName } = extractGraphQLRelationFieldNames(fieldMetadata);
const type = this.typeMapperService.mapToOrderByType(fieldMetadata.type);
if (!isDefined(type)) {
const message = `Could not find a GraphQL input type for ${type} field metadata`;
this.logger.error(message, {
type,
typeOptions,
});
throw new Error(message);
}
const modifiedType = this.typeMapperService.applyTypeOptions(
type,
typeOptions,
);
return {
[joinColumnName]: {
type: modifiedType,
description: fieldMetadata.description,
},
};
}
public generateConnectRelationFieldInputType({
fieldMetadata,
typeOptions,
}: {
fieldMetadata: FieldMetadataEntity<
FieldMetadataType.RELATION | FieldMetadataType.MORPH_RELATION
>;
typeOptions: TypeOptions;
}) {
if (
fieldMetadata.settings?.relationType === RelationType.ONE_TO_MANY ||
//TODO : Enable connect on morph relation - @guillim
fieldMetadata.type === FieldMetadataType.MORPH_RELATION
) {
return {};
}
const { fieldMetadataName } =
extractGraphQLRelationFieldNames(fieldMetadata);
if (!isDefined(fieldMetadata.relationTargetObjectMetadataId)) {
throw new Error(
`Target object metadata not found for field metadata ${fieldMetadata.id}`,
);
}
const key = computeRelationConnectInputTypeKey(
fieldMetadata.relationTargetObjectMetadataId,
);
const type = this.gqlTypesStorage.getGqlTypeByKey(key);
if (!isDefined(type) || isObjectType(type)) {
throw new Error(`Input type ${key} not found`);
}
return {
[fieldMetadataName]: {
type: this.typeMapperService.applyTypeOptions(type, typeOptions),
description: fieldMetadata.description,
},
};
}
}
@@ -0,0 +1,103 @@
import { Injectable, Logger } from '@nestjs/common';
import {
GraphQLInputFieldConfigMap,
GraphQLInputObjectType,
isObjectType,
} from 'graphql';
import { isDefined } from 'twenty-shared/utils';
import { CompositeType } from 'src/engine/metadata-modules/field-metadata/interfaces/composite-type.interface';
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 { 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';
import { isEnumFieldMetadataType } from 'src/engine/metadata-modules/field-metadata/utils/is-enum-field-metadata-type.util';
import { isMorphOrRelationFieldMetadataType } from 'src/engine/utils/is-morph-or-relation-field-metadata-type.util';
import { pascalCase } from 'src/utils/pascal-case';
@Injectable()
export class CompositeFieldMetadataUpdateGqlInputTypeGenerator {
private readonly logger = new Logger(
CompositeFieldMetadataUpdateGqlInputTypeGenerator.name,
);
constructor(
private readonly gqlTypesStorage: GqlTypesStorage,
private readonly typeMapperService: TypeMapperService,
) {}
public buildAndStore(compositeType: CompositeType) {
const key = computeCompositeFieldInputTypeKey(
compositeType.type,
GqlInputTypeDefinitionKind.Update,
);
const type = new GraphQLInputObjectType({
name: `${pascalCase(compositeType.type)}${GqlInputTypeDefinitionKind.Update.toString()}Input`,
fields: this.generateFields(compositeType),
});
this.gqlTypesStorage.addGqlType(key, type);
}
public generateFields(
compositeType: CompositeType,
): GraphQLInputFieldConfigMap {
const fields: GraphQLInputFieldConfigMap = {};
for (const property of compositeType.properties) {
property.isRequired = false;
// Relation fields are not supported in composite types
if (isMorphOrRelationFieldMetadataType(property.type)) {
this.logger.error(
'Relation fields are not supported in composite types',
{ compositeType, property },
);
throw new Error('Relation fields are not supported in composite types');
}
// Skip hidden fields
if (property.hidden === true || property.hidden === 'input') {
continue;
}
const key = computeCompositeFieldEnumTypeKey(
compositeType.type,
property.name,
);
const typeOptions = computeCompositeFieldTypeOptions(property);
const type = isEnumFieldMetadataType(property.type)
? this.gqlTypesStorage.getGqlTypeByKey(key)
: this.typeMapperService.mapToScalarType(property.type, typeOptions);
if (!isDefined(type) || isObjectType(type)) {
const message = `Could not find a GraphQL input type for ${compositeType.type} ${property.name}`;
this.logger.error(message, {
type,
typeOptions,
});
throw new Error(message);
}
const modifiedType = this.typeMapperService.applyTypeOptions(
type,
typeOptions,
);
fields[property.name] = {
type: modifiedType,
description: property.description,
};
}
return fields;
}
}
@@ -0,0 +1,157 @@
import { Injectable, Logger } from '@nestjs/common';
import {
GraphQLEnumType,
GraphQLInputFieldConfigMap,
GraphQLInputObjectType,
GraphQLScalarType,
isEnumType,
isInputObjectType,
isObjectType,
} from 'graphql';
import { isDefined } 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 } 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';
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 { computeObjectMetadataInputTypeKey } from 'src/engine/api/graphql/workspace-schema-builder/utils/compute-stored-gql-type-key-utils/compute-object-metadata-input-type.util';
import { isFieldMetadataRelationOrMorphRelation } from 'src/engine/api/graphql/workspace-schema-builder/utils/is-field-metadata-relation-or-morph-relation.utils';
import { isCompositeFieldMetadataType } from 'src/engine/metadata-modules/field-metadata/utils/is-composite-field-metadata-type.util';
import { isEnumFieldMetadataType } from 'src/engine/metadata-modules/field-metadata/utils/is-enum-field-metadata-type.util';
import { ObjectMetadataEntity } from 'src/engine/metadata-modules/object-metadata/object-metadata.entity';
import { pascalCase } from 'src/utils/pascal-case';
@Injectable()
export class ObjectMetadataUpdateGqlInputTypeGenerator {
private readonly logger = new Logger(
ObjectMetadataUpdateGqlInputTypeGenerator.name,
);
constructor(
private readonly gqlTypesStorage: GqlTypesStorage,
private readonly relationFieldMetadataGqlInputTypeGenerator: RelationFieldMetadataGqlInputTypeGenerator,
private readonly typeMapperService: TypeMapperService,
) {}
public buildAndStore(objectMetadata: ObjectMetadataEntity) {
const inputType = new GraphQLInputObjectType({
name: `${pascalCase(objectMetadata.nameSingular)}${GqlInputTypeDefinitionKind.Update.toString()}Input`,
description: objectMetadata.description,
fields: () => this.generateFields(objectMetadata),
}) as GraphQLInputObjectType;
const key = computeObjectMetadataInputTypeKey(
objectMetadata.nameSingular,
GqlInputTypeDefinitionKind.Update,
);
this.gqlTypesStorage.addGqlType(key, inputType);
}
private generateFields(
objectMetadata: ObjectMetadataEntity,
): GraphQLInputFieldConfigMap {
const allGeneratedFields: GraphQLInputFieldConfigMap = {};
for (const fieldMetadata of objectMetadata.fields) {
fieldMetadata.isNullable = true;
const typeOptions = computeFieldInputTypeOptions(
fieldMetadata,
GqlInputTypeDefinitionKind.Update,
);
let generatedFields;
if (isFieldMetadataRelationOrMorphRelation(fieldMetadata)) {
generatedFields = {
...this.relationFieldMetadataGqlInputTypeGenerator.generateSimpleRelationFieldCreateOrUpdateInputType(
{
fieldMetadata,
typeOptions,
},
),
...this.relationFieldMetadataGqlInputTypeGenerator.generateConnectRelationFieldInputType(
{
fieldMetadata,
typeOptions,
},
),
};
} else {
let type:
| GraphQLInputObjectType
| GraphQLEnumType
| GraphQLScalarType
| undefined;
if (isEnumFieldMetadataType(fieldMetadata.type)) {
const key = computeEnumFieldGqlTypeKey(
objectMetadata.nameSingular,
fieldMetadata.name,
);
const enumType = this.gqlTypesStorage.getGqlTypeByKey(key);
if (!isDefined(enumType) || !isEnumType(enumType)) {
const message = `Could not find a GraphQL input type for ${fieldMetadata.type} field metadata`;
this.logger.error(message, {
type,
typeOptions,
});
throw new Error(message);
}
type = enumType;
} else if (isCompositeFieldMetadataType(fieldMetadata.type)) {
const key = computeCompositeFieldInputTypeKey(
fieldMetadata.type,
GqlInputTypeDefinitionKind.Update,
);
const compositeType = this.gqlTypesStorage.getGqlTypeByKey(key);
if (!isDefined(compositeType) || !isInputObjectType(compositeType)) {
const message = `Could not find a GraphQL input type for ${fieldMetadata.type} field metadata`;
this.logger.error(message, {
type,
typeOptions,
});
throw new Error(message);
}
type = compositeType;
} else {
type = this.typeMapperService.mapToScalarType(
fieldMetadata.type,
typeOptions,
);
if (!isDefined(type) || isObjectType(type)) {
const message = `Could not find a GraphQL input type for ${fieldMetadata.type} field metadata`;
this.logger.error(message, {
type,
typeOptions,
});
throw new Error(message);
}
}
generatedFields = {
[fieldMetadata.name]: {
type,
description: fieldMetadata.description,
},
};
}
Object.assign(allGeneratedFields, generatedFields);
}
return allGeneratedFields;
}
}
@@ -9,8 +9,8 @@ import { type ObjectMetadataEntity } from 'src/engine/metadata-modules/object-me
type AggregationGraphQLType = Pick<AggregationField, 'type' | 'description'>;
@Injectable()
export class AggregationTypeFactory {
public create(
export class AggregationObjectTypeGenerator {
public generate(
objectMetadata: ObjectMetadataEntity,
): Record<string, AggregationGraphQLType> {
const availableAggregations = getAvailableAggregationsFromObjectFields(
@@ -0,0 +1,102 @@
import { Injectable, Logger } from '@nestjs/common';
import { GraphQLObjectType, isInputObjectType } from 'graphql';
import { FieldMetadataType } from 'twenty-shared/types';
import { isDefined } from 'twenty-shared/utils';
import { type CompositeType } from 'src/engine/metadata-modules/field-metadata/interfaces/composite-type.interface';
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 { GraphQLOutputTypeFieldConfigMap } from 'src/engine/api/graphql/workspace-schema-builder/types/graphql-field-config-map.types';
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';
import { isMorphOrRelationFieldMetadataType } from 'src/engine/utils/is-morph-or-relation-field-metadata-type.util';
import { pascalCase } from 'src/utils/pascal-case';
@Injectable()
export class CompositeFieldMetadataGqlObjectTypeGenerator {
private readonly logger = new Logger(
CompositeFieldMetadataGqlObjectTypeGenerator.name,
);
constructor(
private readonly gqlTypesStorage: GqlTypesStorage,
private readonly typeMapperService: TypeMapperService,
) {}
public buildAndStore(compositeType: CompositeType) {
const kind = ObjectTypeDefinitionKind.Plain;
const key = computeCompositeFieldObjectTypeKey(compositeType.type);
const type = new GraphQLObjectType({
name: `${pascalCase(compositeType.type)}${kind.toString()}`,
fields: this.generateFields(compositeType),
});
this.gqlTypesStorage.addGqlType(key, type);
}
private generateFields(
compositeType: CompositeType,
): GraphQLOutputTypeFieldConfigMap {
const fields: GraphQLOutputTypeFieldConfigMap = {};
for (const property of compositeType.properties) {
// Relation fields are not supported in composite types
if (isMorphOrRelationFieldMetadataType(property.type)) {
this.logger.error(
'Relation fields are not supported in composite types',
{ compositeType, property },
);
throw new Error('Relation fields are not supported in composite types');
}
// Skip hidden fields
if (property.hidden === true || property.hidden === 'output') {
continue;
}
const typeOptions = {
nullable: !property.isRequired,
isArray:
property.type === FieldMetadataType.MULTI_SELECT || property.isArray,
};
const key = computeCompositeFieldEnumTypeKey(
compositeType.type,
property.name,
);
const type = isEnumFieldMetadataType(property.type)
? this.gqlTypesStorage.getGqlTypeByKey(key)
: this.typeMapperService.mapToScalarType(property.type, typeOptions);
if (!isDefined(type) || isInputObjectType(type)) {
const message = `Could not find a GraphQL object type for ${compositeType.type} ${property.name}`;
this.logger.error(message, {
type,
typeOptions,
});
throw new Error(message);
}
const modifiedType = this.typeMapperService.applyTypeOptions(
type,
typeOptions,
);
fields[property.name] = {
type: modifiedType,
description: property.description,
};
}
return fields;
}
}
@@ -0,0 +1,90 @@
import { Injectable, Logger } from '@nestjs/common';
import { GraphQLObjectType, isInputObjectType } from 'graphql';
import { isDefined } 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 { 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 ObjectMetadataEntity } from 'src/engine/metadata-modules/object-metadata/object-metadata.entity';
import { pascalCase } from 'src/utils/pascal-case';
@Injectable()
export class ConnectionGqlObjectTypeGenerator {
private readonly logger = new Logger(ConnectionGqlObjectTypeGenerator.name);
constructor(
private readonly aggregationObjectTypeGenerator: AggregationObjectTypeGenerator,
private readonly typeMapperService: TypeMapperService,
private readonly gqlTypesStorage: GqlTypesStorage,
) {}
public buildAndStore(objectMetadata: ObjectMetadataEntity) {
const kind = ObjectTypeDefinitionKind.Connection;
const key = computeObjectMetadataObjectTypeKey(
objectMetadata.nameSingular,
kind,
);
this.gqlTypesStorage.addGqlType(
key,
new GraphQLObjectType({
name: `${pascalCase(objectMetadata.nameSingular)}${kind.toString()}`,
description: objectMetadata.description,
fields: () => this.generateFields(objectMetadata),
}),
);
}
private generateFields(
objectMetadata: ObjectMetadataEntity,
): GraphQLOutputTypeFieldConfigMap {
const fields: GraphQLOutputTypeFieldConfigMap = {};
const aggregatedFields =
this.aggregationObjectTypeGenerator.generate(objectMetadata);
Object.assign(fields, aggregatedFields);
const edgeType = this.gqlTypesStorage.getGqlTypeByKey(
computeObjectMetadataObjectTypeKey(
objectMetadata.nameSingular,
ObjectTypeDefinitionKind.Edge,
),
);
if (!isDefined(edgeType) || isInputObjectType(edgeType)) {
this.logger.error(
`Edge type for ${objectMetadata.nameSingular} was not found. Please, check if you have defined it.`,
{
objectMetadata,
},
);
throw new Error(
`Edge type for ${objectMetadata.nameSingular} was not found. Please, check if you have defined it.`,
);
}
fields.edges = {
type: this.typeMapperService.applyTypeOptions(edgeType, {
isArray: true,
arrayDepth: 1,
nullable: false,
}),
};
fields.pageInfo = {
type: this.typeMapperService.applyTypeOptions(PageInfoType, {
nullable: false,
}),
};
return fields;
}
}
@@ -0,0 +1,83 @@
import { Injectable, Logger } from '@nestjs/common';
import { GraphQLObjectType, isInputObjectType } from 'graphql';
import { isDefined } 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 { 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 ObjectMetadataEntity } from 'src/engine/metadata-modules/object-metadata/object-metadata.entity';
import { pascalCase } from 'src/utils/pascal-case';
@Injectable()
export class EdgeGqlObjectTypeGenerator {
private readonly logger = new Logger(EdgeGqlObjectTypeGenerator.name);
constructor(
private readonly typeMapperService: TypeMapperService,
private readonly gqlTypesStorage: GqlTypesStorage,
) {}
public buildAndStore(objectMetadata: ObjectMetadataEntity) {
const kind = ObjectTypeDefinitionKind.Edge;
const key = computeObjectMetadataObjectTypeKey(
objectMetadata.nameSingular,
kind,
);
this.gqlTypesStorage.addGqlType(
key,
new GraphQLObjectType({
name: `${pascalCase(objectMetadata.nameSingular)}${kind.toString()}`,
description: objectMetadata.description,
fields: () => this.generateFields(objectMetadata),
}),
);
}
private generateFields(
objectMetadata: ObjectMetadataEntity,
): GraphQLOutputTypeFieldConfigMap {
const fields: GraphQLOutputTypeFieldConfigMap = {};
const key = computeObjectMetadataObjectTypeKey(
objectMetadata.nameSingular,
ObjectTypeDefinitionKind.Plain,
);
const objectType = this.gqlTypesStorage.getGqlTypeByKey(key);
if (!isDefined(objectType) || isInputObjectType(objectType)) {
this.logger.error(
`Node type for ${objectMetadata.nameSingular} was not found. Please, check if you have defined it.`,
{
objectMetadata,
},
);
throw new Error(
`Node type for ${objectMetadata.nameSingular} was not found. Please, check if you have defined it.`,
);
}
const typeOptions = {
nullable: false,
};
fields.node = {
type: this.typeMapperService.applyTypeOptions(objectType, typeOptions),
};
fields.cursor = {
type: this.typeMapperService.applyTypeOptions(
CursorScalarType,
typeOptions,
),
};
return fields;
}
}
@@ -0,0 +1,167 @@
import { Injectable, Logger } from '@nestjs/common';
import {
GraphQLObjectType,
GraphQLOutputType,
isInputObjectType,
} from 'graphql';
import { FieldMetadataType } from 'twenty-shared/types';
import { isDefined } from 'twenty-shared/utils';
import { ObjectTypeDefinitionKind } from 'src/engine/api/graphql/workspace-schema-builder/enums/object-type-definition-kind.enum';
import { RelationFieldMetadataGqlObjectTypeGenerator } from 'src/engine/api/graphql/workspace-schema-builder/graphql-type-generators/object-types/relation-field-metadata-gql-object-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 { GraphQLOutputTypeFieldConfigMap } from 'src/engine/api/graphql/workspace-schema-builder/types/graphql-field-config-map.types';
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';
import { FieldMetadataEntity } from 'src/engine/metadata-modules/field-metadata/field-metadata.entity';
import { isCompositeFieldMetadataType } from 'src/engine/metadata-modules/field-metadata/utils/is-composite-field-metadata-type.util';
import { isEnumFieldMetadataType } from 'src/engine/metadata-modules/field-metadata/utils/is-enum-field-metadata-type.util';
import { type ObjectMetadataEntity } from 'src/engine/metadata-modules/object-metadata/object-metadata.entity';
import { isMorphOrRelationFieldMetadataType } from 'src/engine/utils/is-morph-or-relation-field-metadata-type.util';
import { pascalCase } from 'src/utils/pascal-case';
@Injectable()
export class ObjectMetadataGqlObjectTypeGenerator {
private readonly logger = new Logger(
ObjectMetadataGqlObjectTypeGenerator.name,
);
constructor(
private readonly relationFieldMetadataGqlObjectTypeGenerator: RelationFieldMetadataGqlObjectTypeGenerator,
private readonly gqlTypesStorage: GqlTypesStorage,
private readonly typeMapperService: TypeMapperService,
) {}
public buildAndStore(objectMetadata: ObjectMetadataEntity) {
const kind = ObjectTypeDefinitionKind.Plain;
const key = computeObjectMetadataObjectTypeKey(
objectMetadata.nameSingular,
ObjectTypeDefinitionKind.Plain,
);
this.gqlTypesStorage.addGqlType(
key,
new GraphQLObjectType({
name: `${pascalCase(objectMetadata.nameSingular)}${kind.toString()}`,
description: objectMetadata.description,
fields: this.generateFields(objectMetadata),
}),
);
}
private generateFields(
objectMetadata: ObjectMetadataEntity,
): GraphQLOutputTypeFieldConfigMap {
const allGeneratedFields: GraphQLOutputTypeFieldConfigMap = {};
for (const field of objectMetadata.fields) {
const typeFactoryOptions = {
nullable: field.isNullable ?? undefined,
isArray: field.type === FieldMetadataType.MULTI_SELECT,
settings: field.settings,
// Scalar type is already defined in the entity itself.
isIdField: false,
};
if (isMorphOrRelationFieldMetadataType(field.type)) {
const relationFieldObjectType =
this.relationFieldMetadataGqlObjectTypeGenerator.generateRelationFieldObjectType(
{
fieldMetadata: field as FieldMetadataEntity<
FieldMetadataType.RELATION | FieldMetadataType.MORPH_RELATION
>,
typeOptions: typeFactoryOptions,
},
);
Object.assign(allGeneratedFields, relationFieldObjectType);
continue;
}
let type: GraphQLOutputType | undefined;
if (isCompositeFieldMetadataType(field.type)) {
const compositeFieldObjectTypeKey = computeCompositeFieldObjectTypeKey(
field.type,
);
const compositeFieldObjectType = this.gqlTypesStorage.getGqlTypeByKey(
compositeFieldObjectTypeKey,
);
if (
!isDefined(compositeFieldObjectType) ||
isInputObjectType(compositeFieldObjectType)
) {
const message = `Could not find a GraphQL output type for ${field.name} composite field metadata of object ${objectMetadata.nameSingular}`;
this.logger.error(message, {
type: field.type,
typeOptions: typeFactoryOptions,
});
throw new Error(message);
}
type = compositeFieldObjectType;
} else if (isEnumFieldMetadataType(field.type)) {
const enumFieldEnumTypeKey = computeEnumFieldGqlTypeKey(
objectMetadata.nameSingular,
field.name,
);
const enumFieldEnumType =
this.gqlTypesStorage.getGqlTypeByKey(enumFieldEnumTypeKey);
if (
!isDefined(enumFieldEnumType) ||
isInputObjectType(enumFieldEnumType)
) {
const message = `Could not find a GraphQL output type for ${field.name} enum field metadata of object ${objectMetadata.nameSingular}`;
this.logger.error(message, {
type: field.type,
typeOptions: typeFactoryOptions,
});
throw new Error(message);
}
type = enumFieldEnumType;
} else {
type = this.typeMapperService.mapToScalarType(
field.type,
typeFactoryOptions,
);
if (!isDefined(type)) {
const message = `Could not find a GraphQL output type for ${field.name} scalar field metadata of object ${objectMetadata.nameSingular}`;
this.logger.error(message, {
type: field.type,
typeOptions: typeFactoryOptions,
});
throw new Error(message);
}
}
const modifiedType = this.typeMapperService.applyTypeOptions(
type,
typeFactoryOptions,
);
Object.assign(allGeneratedFields, {
[field.name]: {
type: modifiedType,
description: field.description,
},
});
}
return allGeneratedFields;
}
}
@@ -0,0 +1,200 @@
import { Injectable, Logger } from '@nestjs/common';
import {
GraphQLObjectType,
GraphQLOutputType,
isObjectType,
type GraphQLFieldConfigArgumentMap,
} from 'graphql';
import { FieldMetadataType } from 'twenty-shared/types';
import { isDefined } from 'twenty-shared/utils';
import { RelationType } from 'src/engine/metadata-modules/field-metadata/interfaces/relation-type.interface';
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 { 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 { computeObjectMetadataObjectTypeKey } from 'src/engine/api/graphql/workspace-schema-builder/utils/compute-stored-gql-type-key-utils/compute-object-metadata-object-type-key.util';
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 { FieldMetadataEntity } from 'src/engine/metadata-modules/field-metadata/field-metadata.entity';
import { ObjectMetadataEntity } from 'src/engine/metadata-modules/object-metadata/object-metadata.entity';
import { isFieldMetadataEntityOfType } from 'src/engine/utils/is-field-metadata-of-type.util';
@Injectable()
export class ObjectMetadataWithRelationsGqlObjectTypeGenerator {
private readonly logger = new Logger(
ObjectMetadataWithRelationsGqlObjectTypeGenerator.name,
);
constructor(
private readonly argsTypeGenerator: ArgsTypeGenerator,
private readonly gqlTypesStorage: GqlTypesStorage,
) {}
public buildAndStore(
objectMetadata: ObjectMetadataEntity,
objectMetadataCollection: ObjectMetadataEntity[],
) {
const key = computeObjectMetadataObjectTypeKey(
objectMetadata.nameSingular,
ObjectTypeDefinitionKind.Plain,
);
const gqlType = this.gqlTypesStorage.getGqlTypeByKey(key);
if (!isDefined(gqlType) || !isObjectType(gqlType)) {
this.logger.error(
`Could not find a GraphQL type for ${objectMetadata.id.toString()}`,
{
objectMetadata,
},
);
throw new Error(
`Could not find a GraphQL type for ${objectMetadata.id.toString()}`,
);
}
// Security check to avoid extending an object that does not need to be extended
if (!objectContainsRelationField(objectMetadata)) {
this.logger.error(
`This object does not need to be extended: ${objectMetadata.id.toString()}`,
{
objectMetadata,
},
);
throw new Error(
`This object does not need to be extended: ${objectMetadata.id.toString()}`,
);
}
// Extract current object config to extend it
const config = gqlType.toConfig();
// Recreate the same object type with the new fields
this.gqlTypesStorage.addGqlType(
key,
new GraphQLObjectType({
...config,
fields: () => ({
...config.fields,
...this.generateFields(objectMetadata, objectMetadataCollection),
}),
}),
);
}
private generateFields(
objectMetadata: ObjectMetadataEntity,
objectMetadataCollection: ObjectMetadataEntity[],
): GraphQLOutputTypeFieldConfigMap {
const fields: GraphQLOutputTypeFieldConfigMap = {};
for (const fieldMetadata of objectMetadata.fields) {
// Ignore non-relation fields as they are already defined
const isRelation =
isFieldMetadataEntityOfType(
fieldMetadata,
FieldMetadataType.RELATION,
) ||
isFieldMetadataEntityOfType(
fieldMetadata,
FieldMetadataType.MORPH_RELATION,
);
if (!isRelation) {
continue;
}
if (!fieldMetadata.settings) {
throw new Error(
`Field Metadata of type RELATION or MORPH_RELATION with id ${fieldMetadata.id} has no settings`,
);
}
if (!fieldMetadata.relationTargetObjectMetadataId) {
throw new Error(
`Field Metadata of type RELATION or MORPH_RELATION with id ${fieldMetadata.id} has no relation target object metadata id`,
);
}
const objectMetadataTarget = objectMetadataCollection.find(
(objectMetadata) =>
objectMetadata.id === fieldMetadata.relationTargetObjectMetadataId,
);
if (!isDefined(objectMetadataTarget)) {
throw new Error(
`Field Metadata of type RELATION or MORPH_RELATION with id ${fieldMetadata.id} has no relation target object metadata`,
);
}
const targetObjectMetadataGqlObjectType =
this.fetchTargetObjectMetadataGqlObjectType(
fieldMetadata,
objectMetadataTarget,
);
let argsType: GraphQLFieldConfigArgumentMap | undefined = undefined;
if (fieldMetadata.settings.relationType === RelationType.ONE_TO_MANY) {
const args = getResolverArgs('findMany');
argsType = this.argsTypeGenerator.generate({
args,
objectMetadataSingularName: objectMetadataTarget.nameSingular,
});
}
fields[fieldMetadata.name] = {
type: targetObjectMetadataGqlObjectType,
args: argsType,
description: fieldMetadata.description,
};
}
return fields;
}
private fetchTargetObjectMetadataGqlObjectType(
fieldMetadata: FieldMetadataEntity<
FieldMetadataType.RELATION | FieldMetadataType.MORPH_RELATION
>,
objectMetadataTarget: ObjectMetadataEntity,
): GraphQLOutputType {
if (!fieldMetadata.settings) {
throw new Error(
`Field Metadata of type RELATION or MORPH_RELATION with id ${fieldMetadata.id} has no settings`,
);
}
const key = computeObjectMetadataObjectTypeKey(
objectMetadataTarget.nameSingular,
fieldMetadata.settings.relationType === RelationType.ONE_TO_MANY
? ObjectTypeDefinitionKind.Connection
: ObjectTypeDefinitionKind.Plain,
);
const targetObjectMetadataGqlObjectType =
this.gqlTypesStorage.getGqlTypeByKey(key);
if (
!isDefined(targetObjectMetadataGqlObjectType) ||
!isObjectType(targetObjectMetadataGqlObjectType)
) {
this.logger.error(
`Could not find a relation type for ${fieldMetadata.id}`,
{
fieldMetadata,
},
);
throw new Error(`Could not find a relation type for ${fieldMetadata.id}`);
}
return targetObjectMetadataGqlObjectType;
}
}
@@ -0,0 +1,62 @@
import { Injectable, Logger } from '@nestjs/common';
import { FieldMetadataType, RelationType } from 'twenty-shared/types';
import { isDefined } from 'twenty-shared/utils';
import {
TypeMapperService,
TypeOptions,
} from 'src/engine/api/graphql/workspace-schema-builder/services/type-mapper.service';
import { extractGraphQLRelationFieldNames } from 'src/engine/api/graphql/workspace-schema-builder/utils/extract-graphql-relation-field-names.util';
import { FieldMetadataEntity } from 'src/engine/metadata-modules/field-metadata/field-metadata.entity';
@Injectable()
export class RelationFieldMetadataGqlObjectTypeGenerator {
private readonly logger = new Logger(
RelationFieldMetadataGqlObjectTypeGenerator.name,
);
constructor(private readonly typeMapperService: TypeMapperService) {}
public generateRelationFieldObjectType({
fieldMetadata,
typeOptions,
}: {
fieldMetadata: FieldMetadataEntity<
FieldMetadataType.RELATION | FieldMetadataType.MORPH_RELATION
>;
typeOptions: TypeOptions;
}) {
if (fieldMetadata.settings?.relationType === RelationType.ONE_TO_MANY)
return {};
const { joinColumnName } = extractGraphQLRelationFieldNames(fieldMetadata);
const type = this.typeMapperService.mapToScalarType(
fieldMetadata.type,
typeOptions,
);
if (!isDefined(type)) {
const message = `Could not find a GraphQL output type for ${type} field metadata`;
this.logger.error(message, {
type,
typeOptions,
});
throw new Error(message);
}
const modifiedType = this.typeMapperService.applyTypeOptions(
type,
typeOptions,
);
return {
[joinColumnName]: {
type: modifiedType,
description: fieldMetadata.description,
},
};
}
}
@@ -0,0 +1,18 @@
import { Injectable } from '@nestjs/common';
import { GraphQLNamedType } from 'graphql';
import { GqlOperation } from 'src/engine/api/graphql/workspace-schema-builder/enums/gql-operation.enum';
import { GqlTypesStorage } from 'src/engine/api/graphql/workspace-schema-builder/storages/gql-types.storage';
@Injectable()
export class OrphanedTypesGenerator {
constructor(private readonly gqlTypesStorage: GqlTypesStorage) {}
fetchOrphanedTypes(): GraphQLNamedType[] {
return this.gqlTypesStorage.getAllGqlTypesExcept([
GqlOperation.Query,
GqlOperation.Mutation,
]);
}
}
@@ -0,0 +1,37 @@
import { Injectable } from '@nestjs/common';
import { isObjectType, type GraphQLObjectType } from 'graphql';
import { workspaceResolverBuilderMethodNames } from 'src/engine/api/graphql/workspace-resolver-builder/factories/factories';
import { GqlOperation } from 'src/engine/api/graphql/workspace-schema-builder/enums/gql-operation.enum';
import { RootTypeGenerator } from 'src/engine/api/graphql/workspace-schema-builder/graphql-type-generators/root-types/root-type.generator';
import { GqlTypesStorage } from 'src/engine/api/graphql/workspace-schema-builder/storages/gql-types.storage';
import { ObjectMetadataEntity } from 'src/engine/metadata-modules/object-metadata/object-metadata.entity';
@Injectable()
export class MutationTypeGenerator {
constructor(
private readonly rootTypeGenerator: RootTypeGenerator,
private readonly gqlTypesStorage: GqlTypesStorage,
) {}
buildAndStore(objectMetadataCollection: ObjectMetadataEntity[]) {
return this.rootTypeGenerator.buildAndStore(
objectMetadataCollection,
[...workspaceResolverBuilderMethodNames.mutations],
GqlOperation.Mutation,
);
}
fetchMutationType(): GraphQLObjectType {
const mutationType = this.gqlTypesStorage.getGqlTypeByKey(
GqlOperation.Mutation,
);
if (!mutationType || !isObjectType(mutationType)) {
throw new Error('Mutation type not found');
}
return mutationType;
}
}
@@ -0,0 +1,35 @@
import { Injectable } from '@nestjs/common';
import { GraphQLObjectType, isObjectType } from 'graphql';
import { workspaceResolverBuilderMethodNames } from 'src/engine/api/graphql/workspace-resolver-builder/factories/factories';
import { GqlOperation } from 'src/engine/api/graphql/workspace-schema-builder/enums/gql-operation.enum';
import { RootTypeGenerator } from 'src/engine/api/graphql/workspace-schema-builder/graphql-type-generators/root-types/root-type.generator';
import { GqlTypesStorage } from 'src/engine/api/graphql/workspace-schema-builder/storages/gql-types.storage';
import { type ObjectMetadataEntity } from 'src/engine/metadata-modules/object-metadata/object-metadata.entity';
@Injectable()
export class QueryTypeGenerator {
constructor(
private readonly rootTypeGenerator: RootTypeGenerator,
private readonly gqlTypesStorage: GqlTypesStorage,
) {}
buildAndStore(objectMetadataCollection: ObjectMetadataEntity[]) {
return this.rootTypeGenerator.buildAndStore(
objectMetadataCollection,
[...workspaceResolverBuilderMethodNames.queries],
GqlOperation.Query,
);
}
fetchQueryType(): GraphQLObjectType {
const queryType = this.gqlTypesStorage.getGqlTypeByKey(GqlOperation.Query);
if (!queryType || !isObjectType(queryType)) {
throw new Error('Query type not found');
}
return queryType;
}
}
@@ -1,50 +1,44 @@
import { Injectable, Logger } from '@nestjs/common';
import { type GraphQLFieldConfigMap, GraphQLObjectType } from 'graphql';
import { GraphQLObjectType, isObjectType } from 'graphql';
import { isDefined } from 'twenty-shared/utils';
import { type WorkspaceResolverBuilderMethodNames } from 'src/engine/api/graphql/workspace-resolver-builder/interfaces/workspace-resolvers-builder.interface';
import { type WorkspaceBuildSchemaOptions } from 'src/engine/api/graphql/workspace-schema-builder/interfaces/workspace-build-schema-options.interface';
import { WorkspaceResolverBuilderService } from 'src/engine/api/graphql/workspace-resolver-builder/workspace-resolver-builder.service';
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 { TypeDefinitionsStorage } from 'src/engine/api/graphql/workspace-schema-builder/storages/type-definitions.storage';
import { GqlTypesStorage } from 'src/engine/api/graphql/workspace-schema-builder/storages/gql-types.storage';
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';
import { getResolverArgs } from 'src/engine/api/graphql/workspace-schema-builder/utils/get-resolver-args.util';
import { type ObjectMetadataEntity } from 'src/engine/metadata-modules/object-metadata/object-metadata.entity';
import { getResolverName } from 'src/engine/utils/get-resolver-name.util';
import { ArgsFactory } from './args.factory';
import { ObjectTypeDefinitionKind } from './object-type-definition.factory';
export enum ObjectTypeName {
Query = 'Query',
Mutation = 'Mutation',
Subscription = 'Subscription',
}
@Injectable()
export class RootTypeFactory {
private readonly logger = new Logger(RootTypeFactory.name);
export class RootTypeGenerator {
private readonly logger = new Logger(RootTypeGenerator.name);
constructor(
private readonly typeDefinitionsStorage: TypeDefinitionsStorage,
private readonly gqlTypesStorage: GqlTypesStorage,
private readonly typeMapperService: TypeMapperService,
private readonly argsFactory: ArgsFactory,
private readonly argsTypeGenerator: ArgsTypeGenerator,
private readonly workspaceResolverBuilderService: WorkspaceResolverBuilderService,
) {}
create(
buildAndStore(
objectMetadataCollection: ObjectMetadataEntity[],
workspaceResolverMethodNames: WorkspaceResolverBuilderMethodNames[],
objectTypeName: ObjectTypeName,
options: WorkspaceBuildSchemaOptions,
): GraphQLObjectType {
objectTypeName: GqlOperation,
) {
if (workspaceResolverMethodNames.length === 0) {
this.logger.error(
`No resolver methods were found for ${objectTypeName.toString()}`,
{
workspaceResolverMethodNames,
objectTypeName,
options,
},
);
@@ -53,23 +47,23 @@ export class RootTypeFactory {
);
}
return new GraphQLObjectType({
name: objectTypeName.toString(),
fields: this.generateFields(
objectMetadataCollection,
workspaceResolverMethodNames,
options,
),
});
this.gqlTypesStorage.addGqlType(
objectTypeName,
new GraphQLObjectType({
name: objectTypeName.toString(),
fields: this.generateFields(
objectMetadataCollection,
workspaceResolverMethodNames,
),
}),
);
}
// eslint-disable-next-line @typescript-eslint/no-explicit-any
private generateFields<T = any, U = any>(
private generateFields(
objectMetadataCollection: ObjectMetadataEntity[],
workspaceResolverMethodNames: WorkspaceResolverBuilderMethodNames[],
options: WorkspaceBuildSchemaOptions,
): GraphQLFieldConfigMap<T, U> {
const fieldConfigMap: GraphQLFieldConfigMap<T, U> = {};
): GraphQLRootTypeFieldConfigMap {
const fieldConfigMap: GraphQLRootTypeFieldConfigMap = {};
for (const objectMetadata of objectMetadataCollection) {
for (const methodName of workspaceResolverMethodNames) {
@@ -81,25 +75,23 @@ export class RootTypeFactory {
) {
const name = getResolverName(objectMetadata, methodName);
const args = getResolverArgs(methodName);
const objectType = this.typeDefinitionsStorage.getObjectTypeByKey(
objectMetadata.id,
const key = computeObjectMetadataObjectTypeKey(
objectMetadata.nameSingular,
this.getObjectTypeDefinitionKindByMethodName(methodName),
);
const argsType = this.argsFactory.create(
{
args,
objectMetadataId: objectMetadata.id,
},
options,
);
const objectType = this.gqlTypesStorage.getGqlTypeByKey(key);
if (!objectType) {
const argsType = this.argsTypeGenerator.generate({
args,
objectMetadataSingularName: objectMetadata.nameSingular,
});
if (!isDefined(objectType) || !isObjectType(objectType)) {
this.logger.error(
`Could not find a GraphQL type for ${objectMetadata.id} for method ${methodName}`,
{
objectMetadata,
methodName,
options,
},
);
@@ -117,9 +109,12 @@ export class RootTypeFactory {
'destroyMany',
];
const outputType = this.typeMapperService.mapToGqlType(objectType, {
isArray: allowedMethodNames.includes(methodName),
});
const outputType = this.typeMapperService.applyTypeOptions(
objectType,
{
isArray: allowedMethodNames.includes(methodName),
},
);
fieldConfigMap[name] = {
type: outputType,
@@ -0,0 +1,55 @@
import { ArgsTypeGenerator } from 'src/engine/api/graphql/workspace-schema-builder/graphql-type-generators/args-type/args-type.generator';
import { CompositeFieldMetadataGqlEnumTypeGenerator } from 'src/engine/api/graphql/workspace-schema-builder/graphql-type-generators/enum-types/composite-field-metadata-gql-enum-type.generator';
import { EnumFieldMetadataGqlEnumTypeGenerator } from 'src/engine/api/graphql/workspace-schema-builder/graphql-type-generators/enum-types/enum-field-metadata-gql-enum-type.generator';
import { CompositeFieldMetadataGqlInputTypeGenerator } from 'src/engine/api/graphql/workspace-schema-builder/graphql-type-generators/input-types/composite-field-metadata-gql-input-type.generator';
import { CompositeFieldMetadataCreateGqlInputTypeGenerator } from 'src/engine/api/graphql/workspace-schema-builder/graphql-type-generators/input-types/create-input/composite-field-metadata-create-gql-input-type.generator';
import { ObjectMetadataCreateGqlInputTypeGenerator } from 'src/engine/api/graphql/workspace-schema-builder/graphql-type-generators/input-types/create-input/object-metadata-create-gql-input-type.generator';
import { CompositeFieldMetadataFilterGqlInputTypeGenerator } from 'src/engine/api/graphql/workspace-schema-builder/graphql-type-generators/input-types/filter-input/composite-field-metadata-filter-gql-input-types.generator';
import { ObjectMetadataFilterGqlInputTypeGenerator } from 'src/engine/api/graphql/workspace-schema-builder/graphql-type-generators/input-types/filter-input/object-metadata-filter-gql-input-type.generator';
import { ObjectMetadataGqlInputTypeGenerator } from 'src/engine/api/graphql/workspace-schema-builder/graphql-type-generators/input-types/object-metadata-gql-input-type.generator';
import { CompositeFieldMetadataOrderByGqlInputTypeGenerator } from 'src/engine/api/graphql/workspace-schema-builder/graphql-type-generators/input-types/order-by-input/composite-field-metadata-order-by-gql-input-type.generator';
import { ObjectMetadataOrderByGqlInputTypeGenerator } from 'src/engine/api/graphql/workspace-schema-builder/graphql-type-generators/input-types/order-by-input/object-metadata-order-by-gql-input-type.generator';
import { RelationConnectGqlInputTypeGenerator } from 'src/engine/api/graphql/workspace-schema-builder/graphql-type-generators/input-types/relation-connect-gql-input-type.generator';
import { RelationFieldMetadataGqlInputTypeGenerator } from 'src/engine/api/graphql/workspace-schema-builder/graphql-type-generators/input-types/relation-field-metadata-gql-type.generator';
import { CompositeFieldMetadataUpdateGqlInputTypeGenerator } from 'src/engine/api/graphql/workspace-schema-builder/graphql-type-generators/input-types/update-input/composite-field-metadata-update-gql-input-type.generator';
import { ObjectMetadataUpdateGqlInputTypeGenerator } from 'src/engine/api/graphql/workspace-schema-builder/graphql-type-generators/input-types/update-input/object-metadata-update-gql-input-type.generator';
import { AggregationObjectTypeGenerator } from 'src/engine/api/graphql/workspace-schema-builder/graphql-type-generators/object-types/aggregation-type.generator';
import { CompositeFieldMetadataGqlObjectTypeGenerator } from 'src/engine/api/graphql/workspace-schema-builder/graphql-type-generators/object-types/composite-field-metadata-gql-object-type.generator';
import { ConnectionGqlObjectTypeGenerator } from 'src/engine/api/graphql/workspace-schema-builder/graphql-type-generators/object-types/connection-gql-object-type.generator';
import { EdgeGqlObjectTypeGenerator } from 'src/engine/api/graphql/workspace-schema-builder/graphql-type-generators/object-types/edge-gql-object-type.generator';
import { ObjectMetadataGqlObjectTypeGenerator } from 'src/engine/api/graphql/workspace-schema-builder/graphql-type-generators/object-types/object-metadata-gql-object-type.generator';
import { ObjectMetadataWithRelationsGqlObjectTypeGenerator } from 'src/engine/api/graphql/workspace-schema-builder/graphql-type-generators/object-types/object-metadata-with-relations-gql-object-type.generator';
import { RelationFieldMetadataGqlObjectTypeGenerator } from 'src/engine/api/graphql/workspace-schema-builder/graphql-type-generators/object-types/relation-field-metadata-gql-object-type.generator';
import { OrphanedTypesGenerator } from 'src/engine/api/graphql/workspace-schema-builder/graphql-type-generators/orphaned-types.generator';
import { MutationTypeGenerator } from 'src/engine/api/graphql/workspace-schema-builder/graphql-type-generators/root-types/mutation-type.generator';
import { QueryTypeGenerator } from 'src/engine/api/graphql/workspace-schema-builder/graphql-type-generators/root-types/query-type.generator';
import { RootTypeGenerator } from 'src/engine/api/graphql/workspace-schema-builder/graphql-type-generators/root-types/root-type.generator';
export const workspaceSchemaBuilderTypeGenerators = [
CompositeFieldMetadataGqlEnumTypeGenerator,
EnumFieldMetadataGqlEnumTypeGenerator,
CompositeFieldMetadataGqlInputTypeGenerator,
CompositeFieldMetadataCreateGqlInputTypeGenerator,
CompositeFieldMetadataUpdateGqlInputTypeGenerator,
CompositeFieldMetadataFilterGqlInputTypeGenerator,
CompositeFieldMetadataOrderByGqlInputTypeGenerator,
RelationFieldMetadataGqlInputTypeGenerator,
RelationFieldMetadataGqlObjectTypeGenerator,
ObjectMetadataGqlInputTypeGenerator,
ObjectMetadataCreateGqlInputTypeGenerator,
ObjectMetadataUpdateGqlInputTypeGenerator,
ObjectMetadataFilterGqlInputTypeGenerator,
ObjectMetadataOrderByGqlInputTypeGenerator,
CompositeFieldMetadataGqlObjectTypeGenerator,
ObjectMetadataGqlObjectTypeGenerator,
RelationConnectGqlInputTypeGenerator,
ConnectionGqlObjectTypeGenerator,
EdgeGqlObjectTypeGenerator,
ObjectMetadataWithRelationsGqlObjectTypeGenerator,
AggregationObjectTypeGenerator,
ArgsTypeGenerator,
RootTypeGenerator,
QueryTypeGenerator,
MutationTypeGenerator,
OrphanedTypesGenerator,
];
@@ -1,19 +1,21 @@
import { type GraphQLScalarType } from 'graphql';
import { type FieldMetadataType } from 'twenty-shared/types';
import { type InputTypeDefinitionKind } from 'src/engine/api/graphql/workspace-schema-builder/factories/input-type-definition.factory';
import { type FieldMetadataDefaultValue } from 'src/engine/metadata-modules/field-metadata/interfaces/field-metadata-default-value.interface';
// eslint-disable-next-line @typescript-eslint/no-explicit-any
export interface ArgMetadata<T = any> {
kind?: InputTypeDefinitionKind;
import { type GqlInputTypeDefinitionKind } from 'src/engine/api/graphql/workspace-schema-builder/enums/gql-input-type-definition-kind.enum';
export interface ArgMetadata {
kind?: GqlInputTypeDefinitionKind;
type?: GraphQLScalarType;
isNullable?: boolean;
isArray?: boolean;
defaultValue?: T;
defaultValue?: FieldMetadataDefaultValue<FieldMetadataType>;
}
export interface ArgsMetadata {
args: {
[key: string]: ArgMetadata;
};
objectMetadataId: string;
objectMetadataSingularName: string;
}
@@ -1,16 +0,0 @@
export type DateScalarMode = 'isoDate' | 'timestamp';
export type NumberScalarMode = 'float' | 'integer';
export interface WorkspaceBuildSchemaOptions {
/**
* Date scalar mode
* @default 'isoDate'
*/
dateScalarMode?: DateScalarMode;
/**
* Number scalar mode
* @default 'float'
*/
numberScalarMode?: NumberScalarMode;
}
@@ -16,6 +16,7 @@ import {
import GraphQLJSON from 'graphql-type-json';
import { FieldMetadataType } from 'twenty-shared/types';
import { FieldMetadataDefaultValue } from 'src/engine/metadata-modules/field-metadata/interfaces/field-metadata-default-value.interface';
import {
type FieldMetadataSettings,
NumberDataType,
@@ -45,12 +46,11 @@ import { PositionScalarType } from 'src/engine/api/graphql/workspace-schema-buil
import { getNumberFilterType } from 'src/engine/api/graphql/workspace-schema-builder/utils/get-number-filter-type.util';
import { getNumberScalarType } from 'src/engine/api/graphql/workspace-schema-builder/utils/get-number-scalar-type.util';
// eslint-disable-next-line @typescript-eslint/no-explicit-any
export interface TypeOptions<T = any> {
export interface TypeOptions {
nullable?: boolean;
isArray?: boolean;
arrayDepth?: number;
defaultValue?: T;
defaultValue?: FieldMetadataDefaultValue<FieldMetadataType>;
settings?: FieldMetadataSettings<FieldMetadataType>;
isIdField?: boolean;
isRelationConnectField?: boolean;
@@ -62,11 +62,10 @@ const StringArrayScalarType = new GraphQLList(GraphQLString);
export class TypeMapperService {
mapToScalarType(
fieldMetadataType: FieldMetadataType,
settings?: FieldMetadataSettings<FieldMetadataType>,
isIdField?: boolean,
typeOptions?: TypeOptions,
): GraphQLScalarType | undefined {
if (
isIdField ||
typeOptions?.isIdField ||
fieldMetadataType === FieldMetadataType.RELATION ||
fieldMetadataType === FieldMetadataType.MORPH_RELATION
) {
@@ -81,8 +80,9 @@ export class TypeMapperService {
[
FieldMetadataType.NUMBER,
getNumberScalarType(
(settings as FieldMetadataSettings<FieldMetadataType.NUMBER>)
?.dataType ?? NumberDataType.FLOAT,
(
typeOptions?.settings as FieldMetadataSettings<FieldMetadataType.NUMBER>
)?.dataType ?? NumberDataType.FLOAT,
),
],
[FieldMetadataType.NUMERIC, BigFloatScalarType],
@@ -101,11 +101,10 @@ export class TypeMapperService {
mapToFilterType(
fieldMetadataType: FieldMetadataType,
settings?: FieldMetadataSettings<FieldMetadataType>,
isIdField?: boolean,
typeOptions?: TypeOptions,
): GraphQLInputObjectType | GraphQLScalarType | undefined {
if (
isIdField ||
typeOptions?.isIdField ||
fieldMetadataType === FieldMetadataType.RELATION ||
fieldMetadataType === FieldMetadataType.MORPH_RELATION
) {
@@ -124,8 +123,9 @@ export class TypeMapperService {
[
FieldMetadataType.NUMBER,
getNumberFilterType(
(settings as FieldMetadataSettings<FieldMetadataType.NUMBER>)
?.dataType,
(
typeOptions?.settings as FieldMetadataSettings<FieldMetadataType.NUMBER>
)?.dataType,
),
],
[FieldMetadataType.NUMERIC, BigFloatFilterType],
@@ -168,7 +168,7 @@ export class TypeMapperService {
return typeOrderByMapping.get(fieldMetadataType);
}
mapToGqlType<T extends GraphQLType = GraphQLType>(
applyTypeOptions<T extends GraphQLType = GraphQLType>(
typeRef: T,
options: TypeOptions,
): T {
@@ -0,0 +1,38 @@
import { Injectable, Scope } from '@nestjs/common';
import {
type GraphQLEnumType,
type GraphQLInputObjectType,
type GraphQLObjectType,
} from 'graphql';
// Must be scoped on REQUEST level, because we need to recreate it for each workspaces
// TODO: Implement properly durable by workspace
@Injectable({ scope: Scope.REQUEST, durable: true })
export class GqlTypesStorage {
private readonly gqlTypes = new Map<
string,
GraphQLEnumType | GraphQLInputObjectType | GraphQLObjectType
>();
addGqlType(
key: string,
type: GraphQLEnumType | GraphQLInputObjectType | GraphQLObjectType,
) {
this.gqlTypes.set(key, type);
}
getGqlTypeByKey(
key: string,
): GraphQLEnumType | GraphQLInputObjectType | GraphQLObjectType | undefined {
return this.gqlTypes.get(key);
}
getAllGqlTypesExcept(
keysToExclude: string[],
): (GraphQLEnumType | GraphQLInputObjectType | GraphQLObjectType)[] {
return Array.from(this.gqlTypes.entries())
.filter(([key]) => !keysToExclude.includes(key))
.map(([, value]) => value);
}
}
@@ -1,113 +0,0 @@
import { Injectable, Scope } from '@nestjs/common';
import {
type GraphQLEnumType,
type GraphQLInputObjectType,
type GraphQLObjectType,
} from 'graphql';
import { type FieldMetadataType } from 'twenty-shared/types';
import { type EnumTypeDefinition } from 'src/engine/api/graphql/workspace-schema-builder/factories/enum-type-definition.factory';
import {
type InputTypeDefinition,
type InputTypeDefinitionKind,
} from 'src/engine/api/graphql/workspace-schema-builder/factories/input-type-definition.factory';
import {
type ObjectTypeDefinition,
type ObjectTypeDefinitionKind,
} from 'src/engine/api/graphql/workspace-schema-builder/factories/object-type-definition.factory';
export type GqlInputType = InputTypeDefinition | EnumTypeDefinition;
export type GqlOutputType = ObjectTypeDefinition | EnumTypeDefinition;
// Must be scoped on REQUEST level, because we need to recreate it for each workspaces
// TODO: Implement properly durable by workspace
@Injectable({ scope: Scope.REQUEST, durable: true })
export class TypeDefinitionsStorage {
private readonly enumTypeDefinitions = new Map<string, EnumTypeDefinition>();
private readonly objectTypeDefinitions = new Map<
string,
ObjectTypeDefinition
>();
private readonly inputTypeDefinitions = new Map<
string,
InputTypeDefinition
>();
addEnumTypes(enumDefs: EnumTypeDefinition[]) {
enumDefs.forEach((item) => this.enumTypeDefinitions.set(item.target, item));
}
addObjectTypes(objectDefs: ObjectTypeDefinition[]) {
objectDefs.forEach((item) =>
this.objectTypeDefinitions.set(
this.generateCompositeKey(item.target, item.kind),
item,
),
);
}
getObjectTypeByKey(
target: string,
kind: ObjectTypeDefinitionKind,
): GraphQLObjectType | undefined {
return this.objectTypeDefinitions.get(
this.generateCompositeKey(target, kind),
)?.type;
}
getAllObjectTypeDefinitions(): ObjectTypeDefinition[] {
return Array.from(this.objectTypeDefinitions.values());
}
addInputTypes(inputDefs: InputTypeDefinition[]) {
inputDefs.forEach((item) =>
this.inputTypeDefinitions.set(
this.generateCompositeKey(item.target, item.kind),
item,
),
);
}
getInputTypeByKey(
target: string,
kind: InputTypeDefinitionKind,
): GraphQLInputObjectType | GraphQLEnumType | undefined {
const key = this.generateCompositeKey(target, kind);
let definition: GqlInputType | undefined;
definition ??= this.inputTypeDefinitions.get(key);
definition ??= this.enumTypeDefinitions.get(target);
return definition?.type;
}
getOutputTypeByKey(
target: string,
kind: ObjectTypeDefinitionKind,
): GraphQLObjectType | GraphQLEnumType | undefined {
const key = this.generateCompositeKey(target, kind);
let definition: GqlOutputType | undefined;
definition ??= this.objectTypeDefinitions.get(key);
definition ??= this.enumTypeDefinitions.get(target);
return definition?.type;
}
getEnumTypeByKey(target: string): GraphQLEnumType | undefined {
return this.enumTypeDefinitions.get(target)?.type;
}
getAllInputTypeDefinitions(): InputTypeDefinition[] {
return Array.from(this.inputTypeDefinitions.values());
}
private generateCompositeKey(
target: string | FieldMetadataType,
kind: ObjectTypeDefinitionKind | InputTypeDefinitionKind,
): string {
return `${target.toString()}_${kind.toString()}`;
}
}
@@ -1,308 +0,0 @@
import { Injectable, Logger } from '@nestjs/common';
import { type CompositeType } from 'src/engine/metadata-modules/field-metadata/interfaces/composite-type.interface';
import { CompositeEnumTypeDefinitionFactory } from 'src/engine/api/graphql/workspace-schema-builder/factories/composite-enum-type-definition.factory';
import { CompositeInputTypeDefinitionFactory } from 'src/engine/api/graphql/workspace-schema-builder/factories/composite-input-type-definition.factory';
import { CompositeObjectTypeDefinitionFactory } from 'src/engine/api/graphql/workspace-schema-builder/factories/composite-object-type-definition.factory';
import { EnumTypeDefinitionFactory } from 'src/engine/api/graphql/workspace-schema-builder/factories/enum-type-definition.factory';
import { ExtendObjectTypeDefinitionV2Factory } from 'src/engine/api/graphql/workspace-schema-builder/factories/extend-object-type-definition-v2.factory';
import { RelationConnectInputTypeDefinitionFactory } from 'src/engine/api/graphql/workspace-schema-builder/factories/relation-connect-input-type-definition.factory';
import { objectContainsMorphRelationField } from 'src/engine/api/graphql/workspace-schema-builder/utils/object-contains-morph-relation-field.util';
import { compositeTypeDefinitions } from 'src/engine/metadata-modules/field-metadata/composite-types';
import { type ObjectMetadataEntity } from 'src/engine/metadata-modules/object-metadata/object-metadata.entity';
import { ConnectionTypeDefinitionFactory } from './factories/connection-type-definition.factory';
import { EdgeTypeDefinitionFactory } from './factories/edge-type-definition.factory';
import {
InputTypeDefinitionFactory,
InputTypeDefinitionKind,
} from './factories/input-type-definition.factory';
import {
ObjectTypeDefinitionFactory,
ObjectTypeDefinitionKind,
} from './factories/object-type-definition.factory';
import { type WorkspaceBuildSchemaOptions } from './interfaces/workspace-build-schema-options.interface';
import { TypeDefinitionsStorage } from './storages/type-definitions.storage';
import { objectContainsRelationField } from './utils/object-contains-relation-field';
@Injectable()
export class TypeDefinitionsGenerator {
private readonly logger = new Logger(TypeDefinitionsGenerator.name);
constructor(
private readonly typeDefinitionsStorage: TypeDefinitionsStorage,
private readonly objectTypeDefinitionFactory: ObjectTypeDefinitionFactory,
private readonly compositeObjectTypeDefinitionFactory: CompositeObjectTypeDefinitionFactory,
private readonly enumTypeDefinitionFactory: EnumTypeDefinitionFactory,
private readonly compositeEnumTypeDefinitionFactory: CompositeEnumTypeDefinitionFactory,
private readonly inputTypeDefinitionFactory: InputTypeDefinitionFactory,
private readonly compositeInputTypeDefinitionFactory: CompositeInputTypeDefinitionFactory,
private readonly edgeTypeDefinitionFactory: EdgeTypeDefinitionFactory,
private readonly connectionTypeDefinitionFactory: ConnectionTypeDefinitionFactory,
private readonly extendObjectTypeDefinitionV2Factory: ExtendObjectTypeDefinitionV2Factory,
private readonly relationConnectInputTypeDefinitionFactory: RelationConnectInputTypeDefinitionFactory,
) {}
async generate(
objectMetadataCollection: ObjectMetadataEntity[],
options: WorkspaceBuildSchemaOptions,
) {
// 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);
this.generateRelationConnectInputTypeDefs(objectMetadataCollection);
}
/**
* GENERATE COMPOSITE TYPE OBJECTS
*/
private async generateCompositeTypeDefs(
options: WorkspaceBuildSchemaOptions,
) {
const compositeTypeCollection = [...compositeTypeDefinitions.values()];
this.logger.log(
`Generating composite type objects: [${compositeTypeCollection
.map((compositeType) => compositeType.type)
.join(', ')}]`,
);
// Generate composite types first because they can be used in metadata objects
this.generateCompositeEnumTypeDefs(compositeTypeCollection, options);
this.generateCompositeObjectTypeDefs(compositeTypeCollection, options);
this.generateCompositeInputTypeDefs(compositeTypeCollection, options);
}
private generateCompositeEnumTypeDefs(
compositeTypes: CompositeType[],
options: WorkspaceBuildSchemaOptions,
) {
const enumTypeDefs = compositeTypes
.map((compositeType) =>
this.compositeEnumTypeDefinitionFactory.create(compositeType, options),
)
.flat();
this.typeDefinitionsStorage.addEnumTypes(enumTypeDefs);
}
private generateCompositeObjectTypeDefs(
compositeTypes: CompositeType[],
options: WorkspaceBuildSchemaOptions,
) {
const compositeObjectTypeDefs = compositeTypes.map((compositeType) =>
this.compositeObjectTypeDefinitionFactory.create(compositeType, options),
);
this.typeDefinitionsStorage.addObjectTypes(compositeObjectTypeDefs);
}
private generateCompositeInputTypeDefs(
compositeTypes: CompositeType[],
options: WorkspaceBuildSchemaOptions,
) {
const inputTypeDefs = compositeTypes
.map((compositeType) => {
const optionalExtendedObjectMetadata = {
...compositeType,
properties: compositeType.properties.map((property) => ({
...property,
isRequired: false,
})),
};
return [
// Input type for create
this.compositeInputTypeDefinitionFactory.create(
compositeType,
InputTypeDefinitionKind.Create,
options,
),
// Input type for update
this.compositeInputTypeDefinitionFactory.create(
optionalExtendedObjectMetadata,
InputTypeDefinitionKind.Update,
options,
),
// Filter input type
this.compositeInputTypeDefinitionFactory.create(
optionalExtendedObjectMetadata,
InputTypeDefinitionKind.Filter,
options,
),
// OrderBy input type
this.compositeInputTypeDefinitionFactory.create(
optionalExtendedObjectMetadata,
InputTypeDefinitionKind.OrderBy,
options,
),
];
})
.flat();
this.typeDefinitionsStorage.addInputTypes(inputTypeDefs);
}
/**
* GENERATE METADATA OBJECTS
*/
private async generateMetadataTypeDefs(
dynamicObjectMetadataCollection: ObjectMetadataEntity[],
options: WorkspaceBuildSchemaOptions,
) {
this.logger.log(
`Generating metadata objects: [${dynamicObjectMetadataCollection
.map((object) => object.nameSingular)
.join(', ')}]`,
);
// Generate dynamic objects
this.generateEnumTypeDefs(dynamicObjectMetadataCollection, options);
this.generateObjectTypeDefs(dynamicObjectMetadataCollection, options);
this.generatePaginationTypeDefs(dynamicObjectMetadataCollection, options);
await this.generateInputTypeDefs(dynamicObjectMetadataCollection, options);
await this.generateExtendedObjectTypeDefs(
dynamicObjectMetadataCollection,
options,
);
}
private generateObjectTypeDefs(
objectMetadataCollection: ObjectMetadataEntity[] | CompositeType[],
options: WorkspaceBuildSchemaOptions,
) {
const objectTypeDefs = objectMetadataCollection.map((objectMetadata) =>
this.objectTypeDefinitionFactory.create(
// @ts-expect-error legacy noImplicitAny
objectMetadata,
ObjectTypeDefinitionKind.Plain,
options,
),
);
this.typeDefinitionsStorage.addObjectTypes(objectTypeDefs);
}
private generatePaginationTypeDefs(
objectMetadataCollection: ObjectMetadataEntity[],
options: WorkspaceBuildSchemaOptions,
) {
const edgeTypeDefs = objectMetadataCollection.map((objectMetadata) =>
this.edgeTypeDefinitionFactory.create(objectMetadata, options),
);
this.typeDefinitionsStorage.addObjectTypes(edgeTypeDefs);
// Connection type defs are using edge type defs
const connectionTypeDefs = objectMetadataCollection.map((objectMetadata) =>
this.connectionTypeDefinitionFactory.create(objectMetadata, options),
);
this.typeDefinitionsStorage.addObjectTypes(connectionTypeDefs);
}
private async generateInputTypeDefs(
objectMetadataCollection: ObjectMetadataEntity[],
options: WorkspaceBuildSchemaOptions,
) {
const inputTypeDefs = objectMetadataCollection
.map((objectMetadata) => {
const optionalExtendedObjectMetadata = {
...objectMetadata,
fields: objectMetadata.fields.map((field) => ({
...field,
isNullable: true,
})),
};
return [
// Input type for create
this.inputTypeDefinitionFactory.create({
objectMetadata,
kind: InputTypeDefinitionKind.Create,
options,
}),
// Input type for update
this.inputTypeDefinitionFactory.create({
objectMetadata: optionalExtendedObjectMetadata,
kind: InputTypeDefinitionKind.Update,
options,
}),
// Filter input type
this.inputTypeDefinitionFactory.create({
objectMetadata: optionalExtendedObjectMetadata,
kind: InputTypeDefinitionKind.Filter,
options,
}),
// OrderBy input type
this.inputTypeDefinitionFactory.create({
objectMetadata: optionalExtendedObjectMetadata,
kind: InputTypeDefinitionKind.OrderBy,
options,
}),
];
})
.flat();
this.typeDefinitionsStorage.addInputTypes(inputTypeDefs);
}
private generateEnumTypeDefs(
objectMetadataCollection: ObjectMetadataEntity[],
options: WorkspaceBuildSchemaOptions,
) {
const enumTypeDefs = objectMetadataCollection
.map((objectMetadata) =>
this.enumTypeDefinitionFactory.create(objectMetadata, options),
)
.flat();
this.typeDefinitionsStorage.addEnumTypes(enumTypeDefs);
}
private async generateExtendedObjectTypeDefs(
objectMetadataCollection: ObjectMetadataEntity[],
options: WorkspaceBuildSchemaOptions,
) {
// Generate extended object type defs only for objects that contain composite fields
const objectMetadataCollectionWithCompositeFields =
objectMetadataCollection.filter(
(obj) =>
objectContainsRelationField(obj) ||
objectContainsMorphRelationField(obj),
);
const workspaceId =
objectMetadataCollectionWithCompositeFields[0]?.workspaceId;
if (!workspaceId) {
throw new Error('Workspace ID not found');
}
const objectTypeDefs = objectMetadataCollectionWithCompositeFields.map(
(objectMetadata) =>
this.extendObjectTypeDefinitionV2Factory.create(
objectMetadata,
options,
objectMetadataCollection,
),
);
this.typeDefinitionsStorage.addObjectTypes(objectTypeDefs);
}
private generateRelationConnectInputTypeDefs(
objectMetadataCollection: ObjectMetadataEntity[],
) {
const relationWhereInputTypeDefs = objectMetadataCollection
.map((objectMetadata) =>
this.relationConnectInputTypeDefinitionFactory.create(objectMetadata),
)
.flat();
this.typeDefinitionsStorage.addInputTypes(relationWhereInputTypeDefs);
}
}
@@ -0,0 +1,22 @@
import {
type GraphQLFieldConfigArgumentMap,
type GraphQLFieldConfigMap,
type GraphQLOutputType,
} from 'graphql';
export type GraphQLOutputTypeFieldConfigMap = GraphQLFieldConfigMap<
string,
{
type: GraphQLOutputType;
description: string | null;
}
>;
export type GraphQLRootTypeFieldConfigMap = GraphQLFieldConfigMap<
string,
{
type: GraphQLOutputType;
args: GraphQLFieldConfigArgumentMap;
resolve: undefined;
}
>;
@@ -2,7 +2,7 @@ import { GraphQLBoolean, GraphQLInt, GraphQLString } from 'graphql';
import { type WorkspaceResolverBuilderMethodNames } from 'src/engine/api/graphql/workspace-resolver-builder/interfaces/workspace-resolvers-builder.interface';
import { InputTypeDefinitionKind } from 'src/engine/api/graphql/workspace-schema-builder/factories/input-type-definition.factory';
import { GqlInputTypeDefinitionKind } from 'src/engine/api/graphql/workspace-schema-builder/enums/gql-input-type-definition-kind.enum';
import { UUIDScalarType } from 'src/engine/api/graphql/workspace-schema-builder/graphql-types/scalars';
import { getResolverArgs } from 'src/engine/api/graphql/workspace-schema-builder/utils/get-resolver-args.util';
@@ -13,20 +13,20 @@ describe('getResolverArgs', () => {
last: { type: GraphQLInt, isNullable: true },
before: { type: GraphQLString, isNullable: true },
after: { type: GraphQLString, isNullable: true },
filter: { kind: InputTypeDefinitionKind.Filter, isNullable: true },
filter: { kind: GqlInputTypeDefinitionKind.Filter, isNullable: true },
orderBy: {
kind: InputTypeDefinitionKind.OrderBy,
kind: GqlInputTypeDefinitionKind.OrderBy,
isNullable: true,
isArray: true,
},
limit: { type: GraphQLInt, isNullable: true },
},
findOne: {
filter: { kind: InputTypeDefinitionKind.Filter, isNullable: false },
filter: { kind: GqlInputTypeDefinitionKind.Filter, isNullable: false },
},
createMany: {
data: {
kind: InputTypeDefinitionKind.Create,
kind: GqlInputTypeDefinitionKind.Create,
isNullable: false,
isArray: true,
},
@@ -37,7 +37,7 @@ describe('getResolverArgs', () => {
},
},
createOne: {
data: { kind: InputTypeDefinitionKind.Create, isNullable: false },
data: { kind: GqlInputTypeDefinitionKind.Create, isNullable: false },
upsert: {
isArray: false,
isNullable: true,
@@ -46,16 +46,16 @@ describe('getResolverArgs', () => {
},
updateOne: {
id: { type: UUIDScalarType, isNullable: false },
data: { kind: InputTypeDefinitionKind.Update, isNullable: false },
data: { kind: GqlInputTypeDefinitionKind.Update, isNullable: false },
},
deleteOne: {
id: { type: UUIDScalarType, isNullable: false },
},
restoreMany: {
filter: { kind: InputTypeDefinitionKind.Filter, isNullable: false },
filter: { kind: GqlInputTypeDefinitionKind.Filter, isNullable: false },
},
destroyMany: {
filter: { kind: InputTypeDefinitionKind.Filter, isNullable: false },
filter: { kind: GqlInputTypeDefinitionKind.Filter, isNullable: false },
},
};
@@ -0,0 +1,13 @@
import { FieldMetadataType } from 'twenty-shared/types';
import { type CompositeProperty } from 'src/engine/metadata-modules/field-metadata/interfaces/composite-type.interface';
export function computeCompositeFieldTypeOptions(
property: CompositeProperty<FieldMetadataType>,
) {
return {
nullable: !property.isRequired,
isArray:
property.type === FieldMetadataType.MULTI_SELECT || property.isArray,
};
}
@@ -0,0 +1,20 @@
import { FieldMetadataType } from 'twenty-shared/types';
import { GqlInputTypeDefinitionKind } from 'src/engine/api/graphql/workspace-schema-builder/enums/gql-input-type-definition-kind.enum';
import { type TypeOptions } from 'src/engine/api/graphql/workspace-schema-builder/services/type-mapper.service';
import { type FieldMetadataEntity } from 'src/engine/metadata-modules/field-metadata/field-metadata.entity';
export const computeFieldInputTypeOptions = <T extends FieldMetadataType>(
fieldMetadata: FieldMetadataEntity<T>,
kind: GqlInputTypeDefinitionKind,
): TypeOptions => {
return {
nullable: fieldMetadata.isNullable ?? undefined,
defaultValue: fieldMetadata.defaultValue,
isArray:
kind !== GqlInputTypeDefinitionKind.Filter &&
fieldMetadata.type === FieldMetadataType.MULTI_SELECT,
settings: fieldMetadata.settings,
isIdField: fieldMetadata.name === 'id',
};
};
@@ -0,0 +1,9 @@
import { computeCompositeFieldEnumTypeKey } from 'src/engine/api/graphql/workspace-schema-builder/utils/compute-stored-gql-type-key-utils/compute-composite-field-enum-type-key.util';
describe('computeCompositeFieldEnumTypeKey', () => {
it('should compute the correct key', () => {
expect(computeCompositeFieldEnumTypeKey('Actor', 'source')).toBe(
'ActorSourceEnum',
);
});
});
@@ -0,0 +1,15 @@
import { FieldMetadataType } from 'twenty-shared/types';
import { GqlInputTypeDefinitionKind } from 'src/engine/api/graphql/workspace-schema-builder/enums/gql-input-type-definition-kind.enum';
import { computeCompositeFieldInputTypeKey } from 'src/engine/api/graphql/workspace-schema-builder/utils/compute-stored-gql-type-key-utils/compute-composite-field-input-type-key.util';
describe('computeCompositeFieldInputTypeKey', () => {
it('should compute the correct key for FULL_NAME field', () => {
expect(
computeCompositeFieldInputTypeKey(
FieldMetadataType.FULL_NAME,
GqlInputTypeDefinitionKind.Create,
),
).toBe('FullNameCreateInput');
});
});
@@ -0,0 +1,13 @@
import { FieldMetadataType } from 'twenty-shared/types';
import { computeCompositeFieldObjectTypeKey } from 'src/engine/api/graphql/workspace-schema-builder/utils/compute-stored-gql-type-key-utils/compute-composite-field-object-type-key.util';
describe('computeCompositeFieldObjectTypeKey', () => {
it('should return the correct composite field object type key', () => {
const fieldMetadataType = FieldMetadataType.LINKS;
const result = computeCompositeFieldObjectTypeKey(fieldMetadataType);
expect(result).toBe('Links');
});
});
@@ -0,0 +1,7 @@
import { computeEnumFieldGqlTypeKey } from 'src/engine/api/graphql/workspace-schema-builder/utils/compute-stored-gql-type-key-utils/compute-enum-field-gql-type-key.util';
describe('computeEnumFieldGqlTypeKey', () => {
it('should compute the correct key', () => {
expect(computeEnumFieldGqlTypeKey('User', 'role')).toBe('UserRoleEnum');
});
});
@@ -0,0 +1,13 @@
import { GqlInputTypeDefinitionKind } from 'src/engine/api/graphql/workspace-schema-builder/enums/gql-input-type-definition-kind.enum';
import { computeObjectMetadataInputTypeKey } from 'src/engine/api/graphql/workspace-schema-builder/utils/compute-stored-gql-type-key-utils/compute-object-metadata-input-type.util';
describe('computeObjectMetadataInputTypeKey', () => {
it('should compute the correct key for user object with Create kind', () => {
expect(
computeObjectMetadataInputTypeKey(
'user',
GqlInputTypeDefinitionKind.Create,
),
).toBe('UserCreateInput');
});
});
@@ -0,0 +1,13 @@
import { ObjectTypeDefinitionKind } from 'src/engine/api/graphql/workspace-schema-builder/enums/object-type-definition-kind.enum';
import { computeObjectMetadataObjectTypeKey } from 'src/engine/api/graphql/workspace-schema-builder/utils/compute-stored-gql-type-key-utils/compute-object-metadata-object-type-key.util';
describe('computeObjectMetadataObjectTypeKey', () => {
it('should compute the correct key for user object with Plain kind', () => {
expect(
computeObjectMetadataObjectTypeKey(
'user',
ObjectTypeDefinitionKind.Plain,
),
).toBe('User');
});
});
@@ -0,0 +1,11 @@
import { computeRelationConnectInputTypeKey } from 'src/engine/api/graphql/workspace-schema-builder/utils/compute-stored-gql-type-key-utils/compute-relation-connect-input-type-key.util';
describe('computeRelationConnectInputTypeKey', () => {
it('should return the correct relation connect input type key', () => {
const objectMetadataNameId = '20202020-f401-4d8a-a731-64d007c27bad';
const result = computeRelationConnectInputTypeKey(objectMetadataNameId);
expect(result).toBe('20202020-f401-4d8a-a731-64d007c27bad-ConnectInput');
});
});
@@ -0,0 +1,10 @@
import { pascalCase } from 'src/utils/pascal-case';
export const computeCompositeFieldEnumTypeKey = (
fieldMetadataType: string,
compositePropertyName: string,
): string => {
return `${pascalCase(fieldMetadataType)}${pascalCase(
compositePropertyName,
)}Enum`;
};
@@ -0,0 +1,13 @@
import { type FieldMetadataType } from 'twenty-shared/types';
import { type GqlInputTypeDefinitionKind } from 'src/engine/api/graphql/workspace-schema-builder/enums/gql-input-type-definition-kind.enum';
import { pascalCase } from 'src/utils/pascal-case';
export const computeCompositeFieldInputTypeKey = (
fieldType: FieldMetadataType,
kind: GqlInputTypeDefinitionKind,
): string => {
const name = pascalCase(fieldType.toString().toLowerCase());
return `${pascalCase(name)}${kind.toString()}Input`;
};
@@ -0,0 +1,10 @@
import { type FieldMetadataType } from 'twenty-shared/types';
import { ObjectTypeDefinitionKind } from 'src/engine/api/graphql/workspace-schema-builder/enums/object-type-definition-kind.enum';
import { pascalCase } from 'src/utils/pascal-case';
export const computeCompositeFieldObjectTypeKey = (
compositeFieldMetadataType: FieldMetadataType,
): string => {
return `${pascalCase(compositeFieldMetadataType)}${ObjectTypeDefinitionKind.Plain.toString()}`;
};
@@ -0,0 +1,10 @@
import { pascalCase } from 'src/utils/pascal-case';
export const computeEnumFieldGqlTypeKey = (
objectMetadataName: string,
fieldMetadataName: string,
): string => {
return `${pascalCase(objectMetadataName)}${pascalCase(
fieldMetadataName,
)}Enum`;
};
@@ -0,0 +1,9 @@
import { type GqlInputTypeDefinitionKind } from 'src/engine/api/graphql/workspace-schema-builder/enums/gql-input-type-definition-kind.enum';
import { pascalCase } from 'src/utils/pascal-case';
export const computeObjectMetadataInputTypeKey = (
objectMetadataNameSingular: string,
kind: GqlInputTypeDefinitionKind,
) => {
return `${pascalCase(objectMetadataNameSingular)}${kind.toString()}Input`;
};
@@ -0,0 +1,9 @@
import { type ObjectTypeDefinitionKind } from 'src/engine/api/graphql/workspace-schema-builder/enums/object-type-definition-kind.enum';
import { pascalCase } from 'src/utils/pascal-case';
export const computeObjectMetadataObjectTypeKey = (
objectMetadataNameSingular: string,
kind: ObjectTypeDefinitionKind,
) => {
return `${pascalCase(objectMetadataNameSingular)}${kind.toString()}`;
};
@@ -0,0 +1,3 @@
export const computeRelationConnectInputTypeKey = (
objectMetadataNameId: string,
) => `${objectMetadataNameId}-ConnectInput`;
@@ -0,0 +1,25 @@
import {
GraphQLBoolean,
type GraphQLEnumType,
GraphQLInputObjectType,
type GraphQLInputType,
GraphQLList,
} from 'graphql';
import { FilterIs } from 'src/engine/api/graphql/workspace-schema-builder/graphql-types/input/filter-is.input-type';
export const createGqlEnumFilterType = (
enumType: GraphQLEnumType,
): GraphQLInputType => {
return new GraphQLInputObjectType({
name: `${enumType.name}Filter`,
fields: () => ({
eq: { type: enumType },
neq: { type: enumType },
in: { type: new GraphQLList(enumType) },
containsAny: { type: new GraphQLList(enumType) },
is: { type: FilterIs },
isEmptyArray: { type: GraphQLBoolean },
}),
});
};
@@ -1,237 +0,0 @@
import {
type GraphQLFieldConfigMap,
type GraphQLInputFieldConfigMap,
type GraphQLInputType,
type GraphQLOutputType,
} from 'graphql';
import { FieldMetadataType } from 'twenty-shared/types';
import { isDefined } from 'twenty-shared/utils';
import { type WorkspaceBuildSchemaOptions } from 'src/engine/api/graphql/workspace-schema-builder/interfaces/workspace-build-schema-options.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 { type ObjectTypeDefinitionKind } from 'src/engine/api/graphql/workspace-schema-builder/factories/object-type-definition.factory';
import { formatRelationConnectInputTarget } from 'src/engine/api/graphql/workspace-schema-builder/factories/relation-connect-input-type-definition.factory';
import { extractGraphQLRelationFieldNames } from 'src/engine/api/graphql/workspace-schema-builder/utils/extract-graphql-relation-field-names.util';
import { isFieldMetadataRelationOrMorphRelation } from 'src/engine/api/graphql/workspace-schema-builder/utils/is-field-metadata-relation-or-morph-relation.utils';
import { type FieldMetadataEntity } from 'src/engine/metadata-modules/field-metadata/field-metadata.entity';
import { isCompositeFieldMetadataType } from 'src/engine/metadata-modules/field-metadata/utils/is-composite-field-metadata-type.util';
import { type ObjectMetadataEntity } from 'src/engine/metadata-modules/object-metadata/object-metadata.entity';
type TypeFactory<T extends InputTypeDefinitionKind | ObjectTypeDefinitionKind> =
{
create: (
target: string,
fieldType: FieldMetadataType,
kind: T,
options: WorkspaceBuildSchemaOptions,
additionalOptions: {
nullable?: boolean;
// eslint-disable-next-line @typescript-eslint/no-explicit-any
defaultValue?: any;
isArray: boolean;
// eslint-disable-next-line @typescript-eslint/no-explicit-any
settings: any;
isIdField: boolean;
isRelationConnectField?: boolean;
},
) => T extends InputTypeDefinitionKind
? GraphQLInputType
: GraphQLOutputType;
};
export const generateFields = <
T extends InputTypeDefinitionKind | ObjectTypeDefinitionKind,
>({
objectMetadata,
kind,
options,
typeFactory,
}: {
objectMetadata: ObjectMetadataEntity;
kind: T;
options: WorkspaceBuildSchemaOptions;
typeFactory: TypeFactory<T>;
}): T extends InputTypeDefinitionKind
? GraphQLInputFieldConfigMap
: // eslint-disable-next-line @typescript-eslint/no-explicit-any
GraphQLFieldConfigMap<any, any> => {
const allGeneratedFields = {};
for (const fieldMetadata of objectMetadata.fields) {
let generatedField;
const isRelation = isFieldMetadataRelationOrMorphRelation(fieldMetadata);
if (isRelation) {
generatedField = generateRelationField({
fieldMetadata: fieldMetadata as FieldMetadataEntity<
FieldMetadataType.RELATION | FieldMetadataType.MORPH_RELATION
>,
kind,
options,
typeFactory,
});
} else {
generatedField = generateField({
fieldMetadata,
kind,
options,
typeFactory,
});
}
Object.assign(allGeneratedFields, generatedField);
}
return allGeneratedFields;
};
const getTarget = <T extends FieldMetadataType>(
fieldMetadata: FieldMetadataEntity<T>,
) => {
return isCompositeFieldMetadataType(fieldMetadata.type)
? fieldMetadata.type.toString()
: fieldMetadata.id;
};
const getTypeFactoryOptions = <T extends FieldMetadataType>(
fieldMetadata: FieldMetadataEntity<T>,
kind: InputTypeDefinitionKind | ObjectTypeDefinitionKind,
) => {
return isInputTypeDefinitionKind(kind)
? {
nullable: fieldMetadata.isNullable ?? undefined,
defaultValue: fieldMetadata.defaultValue,
isArray:
kind !== InputTypeDefinitionKind.Filter &&
fieldMetadata.type === FieldMetadataType.MULTI_SELECT,
settings: fieldMetadata.settings,
isIdField: fieldMetadata.name === 'id',
}
: {
nullable: fieldMetadata.isNullable ?? undefined,
isArray: fieldMetadata.type === FieldMetadataType.MULTI_SELECT,
settings: fieldMetadata.settings,
// Scalar type is already defined in the entity itself.
isIdField: false,
};
};
const generateField = <
T extends InputTypeDefinitionKind | ObjectTypeDefinitionKind,
>({
fieldMetadata,
kind,
options,
typeFactory,
}: {
fieldMetadata: FieldMetadataEntity;
kind: T;
options: WorkspaceBuildSchemaOptions;
typeFactory: TypeFactory<T>;
}) => {
const target = getTarget(fieldMetadata);
const typeFactoryOptions = getTypeFactoryOptions(fieldMetadata, kind);
const type = typeFactory.create(
target,
fieldMetadata.type,
kind,
options,
typeFactoryOptions,
);
return {
[fieldMetadata.name]: {
type,
description: fieldMetadata.description,
},
};
};
const generateRelationField = <
T extends InputTypeDefinitionKind | ObjectTypeDefinitionKind,
>({
fieldMetadata,
kind,
options,
typeFactory,
}: {
fieldMetadata: FieldMetadataEntity<
FieldMetadataType.RELATION | FieldMetadataType.MORPH_RELATION
>;
kind: T;
options: WorkspaceBuildSchemaOptions;
typeFactory: TypeFactory<T>;
}) => {
const relationFields = {};
if (fieldMetadata.settings?.relationType === RelationType.ONE_TO_MANY) {
return relationFields;
}
const { joinColumnName, fieldMetadataName } =
extractGraphQLRelationFieldNames(fieldMetadata);
const target = getTarget(fieldMetadata);
const typeFactoryOptions = getTypeFactoryOptions(fieldMetadata, kind);
let type = typeFactory.create(
target,
fieldMetadata.type,
kind,
options,
typeFactoryOptions,
);
// @ts-expect-error legacy noImplicitAny
relationFields[joinColumnName] = {
type,
description: fieldMetadata.description,
};
//TODO : temporary - continue ej/1278 branch (https://github.com/twentyhq/core-team-issues/issues/1278 issue) before removing this
if (fieldMetadata.type === FieldMetadataType.MORPH_RELATION)
return relationFields;
if (
[InputTypeDefinitionKind.Create, InputTypeDefinitionKind.Update].includes(
kind as InputTypeDefinitionKind,
) &&
isDefined(fieldMetadata.relationTargetObjectMetadataId)
) {
type = typeFactory.create(
formatRelationConnectInputTarget(
fieldMetadata.relationTargetObjectMetadataId,
),
fieldMetadata.type,
kind,
options,
{
...typeFactoryOptions,
isRelationConnectField: true,
},
);
// todo @guillim
// @ts-expect-error legacy noImplicitAny
relationFields[fieldMetadataName] = {
type: type,
description: fieldMetadata.description,
};
}
return relationFields;
};
// Type guard
const isInputTypeDefinitionKind = (
kind: InputTypeDefinitionKind | ObjectTypeDefinitionKind,
): kind is InputTypeDefinitionKind => {
return Object.values(InputTypeDefinitionKind).includes(
kind as InputTypeDefinitionKind,
);
};
@@ -3,7 +3,7 @@ import { GraphQLBoolean, GraphQLInt, GraphQLString } from 'graphql';
import { type WorkspaceResolverBuilderMethodNames } from 'src/engine/api/graphql/workspace-resolver-builder/interfaces/workspace-resolvers-builder.interface';
import { type ArgMetadata } from 'src/engine/api/graphql/workspace-schema-builder/interfaces/param-metadata.interface';
import { InputTypeDefinitionKind } from 'src/engine/api/graphql/workspace-schema-builder/factories/input-type-definition.factory';
import { GqlInputTypeDefinitionKind } from 'src/engine/api/graphql/workspace-schema-builder/enums/gql-input-type-definition-kind.enum';
import { UUIDScalarType } from 'src/engine/api/graphql/workspace-schema-builder/graphql-types/scalars';
export const getResolverArgs = (
@@ -33,11 +33,11 @@ export const getResolverArgs = (
isNullable: true,
},
filter: {
kind: InputTypeDefinitionKind.Filter,
kind: GqlInputTypeDefinitionKind.Filter,
isNullable: true,
},
orderBy: {
kind: InputTypeDefinitionKind.OrderBy,
kind: GqlInputTypeDefinitionKind.OrderBy,
isNullable: true,
isArray: true,
},
@@ -46,14 +46,14 @@ export const getResolverArgs = (
case 'deleteMany':
return {
filter: {
kind: InputTypeDefinitionKind.Filter,
kind: GqlInputTypeDefinitionKind.Filter,
isNullable: false,
},
};
case 'createMany':
return {
data: {
kind: InputTypeDefinitionKind.Create,
kind: GqlInputTypeDefinitionKind.Create,
isNullable: false,
isArray: true,
},
@@ -66,7 +66,7 @@ export const getResolverArgs = (
case 'createOne':
return {
data: {
kind: InputTypeDefinitionKind.Create,
kind: GqlInputTypeDefinitionKind.Create,
isNullable: false,
},
upsert: {
@@ -82,7 +82,7 @@ export const getResolverArgs = (
isNullable: false,
},
data: {
kind: InputTypeDefinitionKind.Update,
kind: GqlInputTypeDefinitionKind.Update,
isNullable: false,
},
};
@@ -94,7 +94,7 @@ export const getResolverArgs = (
isArray: true,
},
data: {
kind: InputTypeDefinitionKind.Create,
kind: GqlInputTypeDefinitionKind.Create,
isNullable: true,
isArray: true,
},
@@ -116,18 +116,18 @@ export const getResolverArgs = (
case 'updateMany':
return {
data: {
kind: InputTypeDefinitionKind.Update,
kind: GqlInputTypeDefinitionKind.Update,
isNullable: false,
},
filter: {
kind: InputTypeDefinitionKind.Filter,
kind: GqlInputTypeDefinitionKind.Filter,
isNullable: false,
},
};
case 'restoreMany':
return {
filter: {
kind: InputTypeDefinitionKind.Filter,
kind: GqlInputTypeDefinitionKind.Filter,
isNullable: false,
},
};
@@ -141,7 +141,7 @@ export const getResolverArgs = (
case 'destroyMany':
return {
filter: {
kind: InputTypeDefinitionKind.Filter,
kind: GqlInputTypeDefinitionKind.Filter,
isNullable: false,
},
};
@@ -2,50 +2,31 @@ import { Injectable } from '@nestjs/common';
import { GraphQLSchema } from 'graphql';
import { type WorkspaceResolverBuilderMethods } from 'src/engine/api/graphql/workspace-resolver-builder/interfaces/workspace-resolvers-builder.interface';
import { GqlTypeGenerator } from 'src/engine/api/graphql/workspace-schema-builder/graphql-type-generators/gql-type.generator';
import { OrphanedTypesGenerator } from 'src/engine/api/graphql/workspace-schema-builder/graphql-type-generators/orphaned-types.generator';
import { MutationTypeGenerator } from 'src/engine/api/graphql/workspace-schema-builder/graphql-type-generators/root-types/mutation-type.generator';
import { QueryTypeGenerator } from 'src/engine/api/graphql/workspace-schema-builder/graphql-type-generators/root-types/query-type.generator';
import { type ObjectMetadataEntity } from 'src/engine/metadata-modules/object-metadata/object-metadata.entity';
import { TypeDefinitionsGenerator } from './type-definitions.generator';
import { MutationTypeFactory } from './factories/mutation-type.factory';
import { OrphanedTypesFactory } from './factories/orphaned-types.factory';
import { QueryTypeFactory } from './factories/query-type.factory';
import { type WorkspaceBuildSchemaOptions } from './interfaces/workspace-build-schema-options.interface';
@Injectable()
export class WorkspaceGraphQLSchemaFactory {
export class WorkspaceGraphQLSchemaGenerator {
constructor(
private readonly typeDefinitionsGenerator: TypeDefinitionsGenerator,
private readonly queryTypeFactory: QueryTypeFactory,
private readonly mutationTypeFactory: MutationTypeFactory,
private readonly orphanedTypesFactory: OrphanedTypesFactory,
private readonly gqlTypeGenerator: GqlTypeGenerator,
private readonly queryTypeGenerator: QueryTypeGenerator,
private readonly mutationTypeGenerator: MutationTypeGenerator,
private readonly orphanedTypesGenerator: OrphanedTypesGenerator,
) {}
async create(
generateSchema(
objectMetadataCollection: ObjectMetadataEntity[],
workspaceResolverBuilderMethods: WorkspaceResolverBuilderMethods,
options: WorkspaceBuildSchemaOptions = {},
): Promise<GraphQLSchema> {
// Generate type definitions
await this.typeDefinitionsGenerator.generate(
objectMetadataCollection,
options,
);
): GraphQLSchema {
this.gqlTypeGenerator.buildAndStore(objectMetadataCollection);
// Generate schema
// Assemble schema
const schema = new GraphQLSchema({
query: this.queryTypeFactory.create(
objectMetadataCollection,
[...workspaceResolverBuilderMethods.queries],
options,
),
mutation: this.mutationTypeFactory.create(
objectMetadataCollection,
[...workspaceResolverBuilderMethods.mutations],
options,
),
types: this.orphanedTypesFactory.create(),
query: this.queryTypeGenerator.fetchQueryType(),
mutation: this.mutationTypeGenerator.fetchMutationType(),
types: this.orphanedTypesGenerator.fetchOrphanedTypes(),
});
return schema;
@@ -1,16 +1,14 @@
import { Module } from '@nestjs/common';
import { WorkspaceResolverBuilderModule } from 'src/engine/api/graphql/workspace-resolver-builder/workspace-resolver-builder.module';
import { GqlTypeGenerator } from 'src/engine/api/graphql/workspace-schema-builder/graphql-type-generators/gql-type.generator';
import { workspaceSchemaBuilderTypeGenerators } from 'src/engine/api/graphql/workspace-schema-builder/graphql-type-generators/type-generators';
import { GqlTypesStorage } from 'src/engine/api/graphql/workspace-schema-builder/storages/gql-types.storage';
import { WorkspaceGraphQLSchemaGenerator } from 'src/engine/api/graphql/workspace-schema-builder/workspace-graphql-schema.factory';
import { FeatureFlagModule } from 'src/engine/core-modules/feature-flag/feature-flag.module';
import { ObjectMetadataModule } from 'src/engine/metadata-modules/object-metadata/object-metadata.module';
import { TypeDefinitionsGenerator } from './type-definitions.generator';
import { WorkspaceGraphQLSchemaFactory } from './workspace-graphql-schema.factory';
import { workspaceSchemaBuilderFactories } from './factories/factories';
import { TypeMapperService } from './services/type-mapper.service';
import { TypeDefinitionsStorage } from './storages/type-definitions.storage';
@Module({
imports: [
ObjectMetadataModule,
@@ -18,12 +16,12 @@ import { TypeDefinitionsStorage } from './storages/type-definitions.storage';
FeatureFlagModule,
],
providers: [
TypeDefinitionsStorage,
GqlTypesStorage,
TypeMapperService,
...workspaceSchemaBuilderFactories,
TypeDefinitionsGenerator,
WorkspaceGraphQLSchemaFactory,
...workspaceSchemaBuilderTypeGenerators,
GqlTypeGenerator,
WorkspaceGraphQLSchemaGenerator,
],
exports: [WorkspaceGraphQLSchemaFactory],
exports: [WorkspaceGraphQLSchemaGenerator],
})
export class WorkspaceSchemaBuilderModule {}
@@ -8,7 +8,7 @@ import { isDefined } from 'twenty-shared/utils';
import { ScalarsExplorerService } from 'src/engine/api/graphql/services/scalars-explorer.service';
import { workspaceResolverBuilderMethodNames } from 'src/engine/api/graphql/workspace-resolver-builder/factories/factories';
import { WorkspaceResolverFactory } from 'src/engine/api/graphql/workspace-resolver-builder/workspace-resolver.factory';
import { WorkspaceGraphQLSchemaFactory } from 'src/engine/api/graphql/workspace-schema-builder/workspace-graphql-schema.factory';
import { WorkspaceGraphQLSchemaGenerator } from 'src/engine/api/graphql/workspace-schema-builder/workspace-graphql-schema.factory';
import {
AuthException,
AuthExceptionCode,
@@ -30,7 +30,7 @@ export class WorkspaceSchemaFactory {
constructor(
private readonly dataSourceService: DataSourceService,
private readonly scalarsExplorerService: ScalarsExplorerService,
private readonly workspaceGraphQLSchemaFactory: WorkspaceGraphQLSchemaFactory,
private readonly workspaceGraphQLSchemaGenerator: WorkspaceGraphQLSchemaGenerator,
private readonly workspaceResolverFactory: WorkspaceResolverFactory,
private readonly workspaceCacheStorageService: WorkspaceCacheStorageService,
private readonly workspaceMetadataCacheService: WorkspaceMetadataCacheService,
@@ -106,9 +106,8 @@ export class WorkspaceSchemaFactory {
// If typeDefs are not cached, generate them
if (!typeDefs || !usedScalarNames) {
const autoGeneratedSchema =
await this.workspaceGraphQLSchemaFactory.create(
this.workspaceGraphQLSchemaGenerator.generateSchema(
objectMetadataCollection,
workspaceResolverBuilderMethodNames,
);
usedScalarNames =