From 06605b2f36e51334a04e1716ace523d3e02baab9 Mon Sep 17 00:00:00 2001 From: Etienne <45695613+etiennejouan@users.noreply.github.com> Date: Mon, 15 Sep 2025 14:01:53 +0200 Subject: [PATCH] simplify gql schema gen (#14426) closes https://github.com/twentyhq/core-team-issues/issues/1358 --- .../__tests__/workspace.factory.spec.ts | 6 +- .../query-result-getters.factory.ts | 5 +- .../workspace-resolver.factory.ts | 2 +- .../gql-input-type-definition-kind.enum.ts | 6 + .../enums/gql-operation.enum.ts | 5 + .../enums/object-type-definition-kind.enum.ts | 5 + ...composite-input-type-definition.factory.ts | 102 ------ ...omposite-object-type-definition.factory.ts | 94 ------ .../connection-type-definition.factory.ts | 84 ----- .../factories/connection-type.factory.ts | 58 ---- .../factories/edge-type-definition.factory.ts | 75 ----- .../factories/edge-type.factory.ts | 58 ---- ...xtend-object-type-definition-v2.factory.ts | 180 ---------- .../factories/factories.ts | 46 --- .../input-type-definition.factory.ts | 102 ------ .../factories/input-type.factory.ts | 120 ------- .../factories/mutation-type.factory.ts | 28 -- .../object-type-definition.factory.ts | 49 --- .../factories/orphaned-types.factory.ts | 22 -- .../factories/output-type.factory.ts | 54 --- .../factories/query-type.factory.ts | 28 -- .../factories/relation-type-v2.factory.ts | 58 ---- .../args-type/args-type.generator.ts} | 44 +-- ...field-metadata-gql-enum-type.generator.ts} | 43 +-- ...field-metadata-gql-enum-type.generator.ts} | 37 +-- .../gql-type.generator.ts | 93 ++++++ ...field-metadata-gql-input-type.generator.ts | 33 ++ ...etadata-create-gql-input-type.generator.ts | 102 ++++++ ...etadata-create-gql-input-type.generator.ts | 155 +++++++++ ...tadata-filter-gql-input-types.generator.ts | 119 +++++++ ...etadata-filter-gql-input-type.generator.ts | 165 ++++++++++ ...bject-metadata-gql-input-type.generator.ts | 32 ++ ...adata-order-by-gql-input-type.generator.ts | 103 ++++++ ...adata-order-by-gql-input-type.generator.ts | 160 +++++++++ ...ation-connect-gql-input-type.generator.ts} | 39 +-- ...ation-field-metadata-gql-type.generator.ts | 193 +++++++++++ ...etadata-update-gql-input-type.generator.ts | 103 ++++++ ...etadata-update-gql-input-type.generator.ts | 157 +++++++++ .../aggregation-type.generator.ts} | 4 +- ...ield-metadata-gql-object-type.generator.ts | 102 ++++++ .../connection-gql-object-type.generator.ts | 90 +++++ .../edge-gql-object-type.generator.ts | 83 +++++ ...ject-metadata-gql-object-type.generator.ts | 167 ++++++++++ ...ith-relations-gql-object-type.generator.ts | 200 ++++++++++++ ...ield-metadata-gql-object-type.generator.ts | 62 ++++ .../orphaned-types.generator.ts | 18 + .../root-types/mutation-type.generator.ts | 37 +++ .../root-types/query-type.generator.ts | 35 ++ .../root-types/root-type.generator.ts} | 91 +++--- .../type-generators.ts | 55 ++++ .../interfaces/param-metadata.interface.ts | 14 +- ...orkspace-build-schema-options.interface.ts | 16 - .../services/type-mapper.service.ts | 28 +- .../storages/gql-types.storage.ts | 38 +++ .../storages/type-definitions.storage.ts | 113 ------- .../type-definitions.generator.ts | 308 ------------------ .../types/graphql-field-config-map.types.ts | 22 ++ .../utils/__tests__/get-resolver-args.spec.ts | 18 +- ...mpute-composite-field-type-options.util.ts | 13 + .../compute-field-input-type-options.util.ts | 20 ++ ...composite-field-enum-type-key.util.spec.ts | 9 + ...omposite-field-input-type-key.util.spec.ts | 15 + ...mposite-field-object-type-key.util.spec.ts | 13 + ...mpute-enum-field-gql-type-key.util.spec.ts | 7 + ...te-object-metadata-input-type.util.spec.ts | 13 + ...ject-metadata-object-type-key.util.spec.ts | 13 + ...lation-connect-input-type-key.util.spec.ts | 11 + ...pute-composite-field-enum-type-key.util.ts | 10 + ...ute-composite-field-input-type-key.util.ts | 13 + ...te-composite-field-object-type-key.util.ts | 10 + .../compute-enum-field-gql-type-key.util.ts | 10 + ...compute-object-metadata-input-type.util.ts | 9 + ...te-object-metadata-object-type-key.util.ts | 9 + ...te-relation-connect-input-type-key.util.ts | 3 + .../utils/create-gql-enum-filter-type.util.ts | 25 ++ .../utils/generate-fields.util.ts | 237 -------------- .../utils/get-resolver-args.util.ts | 24 +- .../workspace-graphql-schema.factory.ts | 51 +-- .../workspace-schema-builder.module.ts | 20 +- .../api/graphql/workspace-schema.factory.ts | 7 +- 80 files changed, 2728 insertions(+), 2080 deletions(-) create mode 100644 packages/twenty-server/src/engine/api/graphql/workspace-schema-builder/enums/gql-input-type-definition-kind.enum.ts create mode 100644 packages/twenty-server/src/engine/api/graphql/workspace-schema-builder/enums/gql-operation.enum.ts create mode 100644 packages/twenty-server/src/engine/api/graphql/workspace-schema-builder/enums/object-type-definition-kind.enum.ts delete mode 100644 packages/twenty-server/src/engine/api/graphql/workspace-schema-builder/factories/composite-input-type-definition.factory.ts delete mode 100644 packages/twenty-server/src/engine/api/graphql/workspace-schema-builder/factories/composite-object-type-definition.factory.ts delete mode 100644 packages/twenty-server/src/engine/api/graphql/workspace-schema-builder/factories/connection-type-definition.factory.ts delete mode 100644 packages/twenty-server/src/engine/api/graphql/workspace-schema-builder/factories/connection-type.factory.ts delete mode 100644 packages/twenty-server/src/engine/api/graphql/workspace-schema-builder/factories/edge-type-definition.factory.ts delete mode 100644 packages/twenty-server/src/engine/api/graphql/workspace-schema-builder/factories/edge-type.factory.ts delete mode 100644 packages/twenty-server/src/engine/api/graphql/workspace-schema-builder/factories/extend-object-type-definition-v2.factory.ts delete mode 100644 packages/twenty-server/src/engine/api/graphql/workspace-schema-builder/factories/factories.ts delete mode 100644 packages/twenty-server/src/engine/api/graphql/workspace-schema-builder/factories/input-type-definition.factory.ts delete mode 100644 packages/twenty-server/src/engine/api/graphql/workspace-schema-builder/factories/input-type.factory.ts delete mode 100644 packages/twenty-server/src/engine/api/graphql/workspace-schema-builder/factories/mutation-type.factory.ts delete mode 100644 packages/twenty-server/src/engine/api/graphql/workspace-schema-builder/factories/object-type-definition.factory.ts delete mode 100644 packages/twenty-server/src/engine/api/graphql/workspace-schema-builder/factories/orphaned-types.factory.ts delete mode 100644 packages/twenty-server/src/engine/api/graphql/workspace-schema-builder/factories/output-type.factory.ts delete mode 100644 packages/twenty-server/src/engine/api/graphql/workspace-schema-builder/factories/query-type.factory.ts delete mode 100644 packages/twenty-server/src/engine/api/graphql/workspace-schema-builder/factories/relation-type-v2.factory.ts rename packages/twenty-server/src/engine/api/graphql/workspace-schema-builder/{factories/args.factory.ts => graphql-type-generators/args-type/args-type.generator.ts} (52%) rename packages/twenty-server/src/engine/api/graphql/workspace-schema-builder/{factories/composite-enum-type-definition.factory.ts => graphql-type-generators/enum-types/composite-field-metadata-gql-enum-type.generator.ts} (63%) rename packages/twenty-server/src/engine/api/graphql/workspace-schema-builder/{factories/enum-type-definition.factory.ts => graphql-type-generators/enum-types/enum-field-metadata-gql-enum-type.generator.ts} (74%) create mode 100644 packages/twenty-server/src/engine/api/graphql/workspace-schema-builder/graphql-type-generators/gql-type.generator.ts create mode 100644 packages/twenty-server/src/engine/api/graphql/workspace-schema-builder/graphql-type-generators/input-types/composite-field-metadata-gql-input-type.generator.ts create mode 100644 packages/twenty-server/src/engine/api/graphql/workspace-schema-builder/graphql-type-generators/input-types/create-input/composite-field-metadata-create-gql-input-type.generator.ts create mode 100644 packages/twenty-server/src/engine/api/graphql/workspace-schema-builder/graphql-type-generators/input-types/create-input/object-metadata-create-gql-input-type.generator.ts create mode 100644 packages/twenty-server/src/engine/api/graphql/workspace-schema-builder/graphql-type-generators/input-types/filter-input/composite-field-metadata-filter-gql-input-types.generator.ts create mode 100644 packages/twenty-server/src/engine/api/graphql/workspace-schema-builder/graphql-type-generators/input-types/filter-input/object-metadata-filter-gql-input-type.generator.ts create mode 100644 packages/twenty-server/src/engine/api/graphql/workspace-schema-builder/graphql-type-generators/input-types/object-metadata-gql-input-type.generator.ts create mode 100644 packages/twenty-server/src/engine/api/graphql/workspace-schema-builder/graphql-type-generators/input-types/order-by-input/composite-field-metadata-order-by-gql-input-type.generator.ts create mode 100644 packages/twenty-server/src/engine/api/graphql/workspace-schema-builder/graphql-type-generators/input-types/order-by-input/object-metadata-order-by-gql-input-type.generator.ts rename packages/twenty-server/src/engine/api/graphql/workspace-schema-builder/{factories/relation-connect-input-type-definition.factory.ts => graphql-type-generators/input-types/relation-connect-gql-input-type.generator.ts} (83%) create mode 100644 packages/twenty-server/src/engine/api/graphql/workspace-schema-builder/graphql-type-generators/input-types/relation-field-metadata-gql-type.generator.ts create mode 100644 packages/twenty-server/src/engine/api/graphql/workspace-schema-builder/graphql-type-generators/input-types/update-input/composite-field-metadata-update-gql-input-type.generator.ts create mode 100644 packages/twenty-server/src/engine/api/graphql/workspace-schema-builder/graphql-type-generators/input-types/update-input/object-metadata-update-gql-input-type.generator.ts rename packages/twenty-server/src/engine/api/graphql/workspace-schema-builder/{factories/aggregation-type.factory.ts => graphql-type-generators/object-types/aggregation-type.generator.ts} (93%) create mode 100644 packages/twenty-server/src/engine/api/graphql/workspace-schema-builder/graphql-type-generators/object-types/composite-field-metadata-gql-object-type.generator.ts create mode 100644 packages/twenty-server/src/engine/api/graphql/workspace-schema-builder/graphql-type-generators/object-types/connection-gql-object-type.generator.ts create mode 100644 packages/twenty-server/src/engine/api/graphql/workspace-schema-builder/graphql-type-generators/object-types/edge-gql-object-type.generator.ts create mode 100644 packages/twenty-server/src/engine/api/graphql/workspace-schema-builder/graphql-type-generators/object-types/object-metadata-gql-object-type.generator.ts create mode 100644 packages/twenty-server/src/engine/api/graphql/workspace-schema-builder/graphql-type-generators/object-types/object-metadata-with-relations-gql-object-type.generator.ts create mode 100644 packages/twenty-server/src/engine/api/graphql/workspace-schema-builder/graphql-type-generators/object-types/relation-field-metadata-gql-object-type.generator.ts create mode 100644 packages/twenty-server/src/engine/api/graphql/workspace-schema-builder/graphql-type-generators/orphaned-types.generator.ts create mode 100644 packages/twenty-server/src/engine/api/graphql/workspace-schema-builder/graphql-type-generators/root-types/mutation-type.generator.ts create mode 100644 packages/twenty-server/src/engine/api/graphql/workspace-schema-builder/graphql-type-generators/root-types/query-type.generator.ts rename packages/twenty-server/src/engine/api/graphql/workspace-schema-builder/{factories/root-type.factory.ts => graphql-type-generators/root-types/root-type.generator.ts} (61%) create mode 100644 packages/twenty-server/src/engine/api/graphql/workspace-schema-builder/graphql-type-generators/type-generators.ts delete mode 100644 packages/twenty-server/src/engine/api/graphql/workspace-schema-builder/interfaces/workspace-build-schema-options.interface.ts create mode 100644 packages/twenty-server/src/engine/api/graphql/workspace-schema-builder/storages/gql-types.storage.ts delete mode 100644 packages/twenty-server/src/engine/api/graphql/workspace-schema-builder/storages/type-definitions.storage.ts delete mode 100644 packages/twenty-server/src/engine/api/graphql/workspace-schema-builder/type-definitions.generator.ts create mode 100644 packages/twenty-server/src/engine/api/graphql/workspace-schema-builder/types/graphql-field-config-map.types.ts create mode 100644 packages/twenty-server/src/engine/api/graphql/workspace-schema-builder/utils/compute-composite-field-type-options.util.ts create mode 100644 packages/twenty-server/src/engine/api/graphql/workspace-schema-builder/utils/compute-field-input-type-options.util.ts create mode 100644 packages/twenty-server/src/engine/api/graphql/workspace-schema-builder/utils/compute-stored-gql-type-key-utils/__tests__/compute-composite-field-enum-type-key.util.spec.ts create mode 100644 packages/twenty-server/src/engine/api/graphql/workspace-schema-builder/utils/compute-stored-gql-type-key-utils/__tests__/compute-composite-field-input-type-key.util.spec.ts create mode 100644 packages/twenty-server/src/engine/api/graphql/workspace-schema-builder/utils/compute-stored-gql-type-key-utils/__tests__/compute-composite-field-object-type-key.util.spec.ts create mode 100644 packages/twenty-server/src/engine/api/graphql/workspace-schema-builder/utils/compute-stored-gql-type-key-utils/__tests__/compute-enum-field-gql-type-key.util.spec.ts create mode 100644 packages/twenty-server/src/engine/api/graphql/workspace-schema-builder/utils/compute-stored-gql-type-key-utils/__tests__/compute-object-metadata-input-type.util.spec.ts create mode 100644 packages/twenty-server/src/engine/api/graphql/workspace-schema-builder/utils/compute-stored-gql-type-key-utils/__tests__/compute-object-metadata-object-type-key.util.spec.ts create mode 100644 packages/twenty-server/src/engine/api/graphql/workspace-schema-builder/utils/compute-stored-gql-type-key-utils/__tests__/compute-relation-connect-input-type-key.util.spec.ts create mode 100644 packages/twenty-server/src/engine/api/graphql/workspace-schema-builder/utils/compute-stored-gql-type-key-utils/compute-composite-field-enum-type-key.util.ts create mode 100644 packages/twenty-server/src/engine/api/graphql/workspace-schema-builder/utils/compute-stored-gql-type-key-utils/compute-composite-field-input-type-key.util.ts create mode 100644 packages/twenty-server/src/engine/api/graphql/workspace-schema-builder/utils/compute-stored-gql-type-key-utils/compute-composite-field-object-type-key.util.ts create mode 100644 packages/twenty-server/src/engine/api/graphql/workspace-schema-builder/utils/compute-stored-gql-type-key-utils/compute-enum-field-gql-type-key.util.ts create mode 100644 packages/twenty-server/src/engine/api/graphql/workspace-schema-builder/utils/compute-stored-gql-type-key-utils/compute-object-metadata-input-type.util.ts create mode 100644 packages/twenty-server/src/engine/api/graphql/workspace-schema-builder/utils/compute-stored-gql-type-key-utils/compute-object-metadata-object-type-key.util.ts create mode 100644 packages/twenty-server/src/engine/api/graphql/workspace-schema-builder/utils/compute-stored-gql-type-key-utils/compute-relation-connect-input-type-key.util.ts create mode 100644 packages/twenty-server/src/engine/api/graphql/workspace-schema-builder/utils/create-gql-enum-filter-type.util.ts delete mode 100644 packages/twenty-server/src/engine/api/graphql/workspace-schema-builder/utils/generate-fields.util.ts diff --git a/packages/twenty-server/src/engine/api/graphql/__tests__/workspace.factory.spec.ts b/packages/twenty-server/src/engine/api/graphql/__tests__/workspace.factory.spec.ts index 020e252ce3..4b62aff9fb 100644 --- a/packages/twenty-server/src/engine/api/graphql/__tests__/workspace.factory.spec.ts +++ b/packages/twenty-server/src/engine/api/graphql/__tests__/workspace.factory.spec.ts @@ -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: {}, }, { diff --git a/packages/twenty-server/src/engine/api/graphql/workspace-query-runner/factories/query-result-getters/query-result-getters.factory.ts b/packages/twenty-server/src/engine/api/graphql/workspace-query-runner/factories/query-result-getters/query-result-getters.factory.ts index b4d827eef4..cc79b0c8a8 100644 --- a/packages/twenty-server/src/engine/api/graphql/workspace-query-runner/factories/query-result-getters/query-result-getters.factory.ts +++ b/packages/twenty-server/src/engine/api/graphql/workspace-query-runner/factories/query-result-getters/query-result-getters.factory.ts @@ -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; constructor(private readonly fileService: FileService) { diff --git a/packages/twenty-server/src/engine/api/graphql/workspace-resolver-builder/workspace-resolver.factory.ts b/packages/twenty-server/src/engine/api/graphql/workspace-resolver-builder/workspace-resolver.factory.ts index a6fbb38601..c40063ff40 100644 --- a/packages/twenty-server/src/engine/api/graphql/workspace-resolver-builder/workspace-resolver.factory.ts +++ b/packages/twenty-server/src/engine/api/graphql/workspace-resolver-builder/workspace-resolver.factory.ts @@ -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'; diff --git a/packages/twenty-server/src/engine/api/graphql/workspace-schema-builder/enums/gql-input-type-definition-kind.enum.ts b/packages/twenty-server/src/engine/api/graphql/workspace-schema-builder/enums/gql-input-type-definition-kind.enum.ts new file mode 100644 index 0000000000..8c7e7bda15 --- /dev/null +++ b/packages/twenty-server/src/engine/api/graphql/workspace-schema-builder/enums/gql-input-type-definition-kind.enum.ts @@ -0,0 +1,6 @@ +export enum GqlInputTypeDefinitionKind { + Create = 'Create', + Update = 'Update', + Filter = 'Filter', + OrderBy = 'OrderBy', +} diff --git a/packages/twenty-server/src/engine/api/graphql/workspace-schema-builder/enums/gql-operation.enum.ts b/packages/twenty-server/src/engine/api/graphql/workspace-schema-builder/enums/gql-operation.enum.ts new file mode 100644 index 0000000000..b3d2bbbe64 --- /dev/null +++ b/packages/twenty-server/src/engine/api/graphql/workspace-schema-builder/enums/gql-operation.enum.ts @@ -0,0 +1,5 @@ +export enum GqlOperation { + Query = 'Query', + Mutation = 'Mutation', + Subscription = 'Subscription', +} diff --git a/packages/twenty-server/src/engine/api/graphql/workspace-schema-builder/enums/object-type-definition-kind.enum.ts b/packages/twenty-server/src/engine/api/graphql/workspace-schema-builder/enums/object-type-definition-kind.enum.ts new file mode 100644 index 0000000000..e6338e4f49 --- /dev/null +++ b/packages/twenty-server/src/engine/api/graphql/workspace-schema-builder/enums/object-type-definition-kind.enum.ts @@ -0,0 +1,5 @@ +export enum ObjectTypeDefinitionKind { + Connection = 'Connection', + Edge = 'Edge', + Plain = '', +} diff --git a/packages/twenty-server/src/engine/api/graphql/workspace-schema-builder/factories/composite-input-type-definition.factory.ts b/packages/twenty-server/src/engine/api/graphql/workspace-schema-builder/factories/composite-input-type-definition.factory.ts deleted file mode 100644 index 61313d8945..0000000000 --- a/packages/twenty-server/src/engine/api/graphql/workspace-schema-builder/factories/composite-input-type-definition.factory.ts +++ /dev/null @@ -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; - } -} diff --git a/packages/twenty-server/src/engine/api/graphql/workspace-schema-builder/factories/composite-object-type-definition.factory.ts b/packages/twenty-server/src/engine/api/graphql/workspace-schema-builder/factories/composite-object-type-definition.factory.ts deleted file mode 100644 index 503cf149cb..0000000000 --- a/packages/twenty-server/src/engine/api/graphql/workspace-schema-builder/factories/composite-object-type-definition.factory.ts +++ /dev/null @@ -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 { - // eslint-disable-next-line @typescript-eslint/no-explicit-any - const fields: GraphQLFieldConfigMap = {}; - - 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; - } -} diff --git a/packages/twenty-server/src/engine/api/graphql/workspace-schema-builder/factories/connection-type-definition.factory.ts b/packages/twenty-server/src/engine/api/graphql/workspace-schema-builder/factories/connection-type-definition.factory.ts deleted file mode 100644 index fa1400b521..0000000000 --- a/packages/twenty-server/src/engine/api/graphql/workspace-schema-builder/factories/connection-type-definition.factory.ts +++ /dev/null @@ -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 { - // eslint-disable-next-line @typescript-eslint/no-explicit-any - const fields: GraphQLFieldConfigMap = {}; - - 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; - } -} diff --git a/packages/twenty-server/src/engine/api/graphql/workspace-schema-builder/factories/connection-type.factory.ts b/packages/twenty-server/src/engine/api/graphql/workspace-schema-builder/factories/connection-type.factory.ts deleted file mode 100644 index 73f1597833..0000000000 --- a/packages/twenty-server/src/engine/api/graphql/workspace-schema-builder/factories/connection-type.factory.ts +++ /dev/null @@ -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); - } -} diff --git a/packages/twenty-server/src/engine/api/graphql/workspace-schema-builder/factories/edge-type-definition.factory.ts b/packages/twenty-server/src/engine/api/graphql/workspace-schema-builder/factories/edge-type-definition.factory.ts deleted file mode 100644 index ee8748817c..0000000000 --- a/packages/twenty-server/src/engine/api/graphql/workspace-schema-builder/factories/edge-type-definition.factory.ts +++ /dev/null @@ -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 { - // eslint-disable-next-line @typescript-eslint/no-explicit-any - const fields: GraphQLFieldConfigMap = {}; - - 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; - } -} diff --git a/packages/twenty-server/src/engine/api/graphql/workspace-schema-builder/factories/edge-type.factory.ts b/packages/twenty-server/src/engine/api/graphql/workspace-schema-builder/factories/edge-type.factory.ts deleted file mode 100644 index 4efb1ebc83..0000000000 --- a/packages/twenty-server/src/engine/api/graphql/workspace-schema-builder/factories/edge-type.factory.ts +++ /dev/null @@ -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); - } -} diff --git a/packages/twenty-server/src/engine/api/graphql/workspace-schema-builder/factories/extend-object-type-definition-v2.factory.ts b/packages/twenty-server/src/engine/api/graphql/workspace-schema-builder/factories/extend-object-type-definition-v2.factory.ts deleted file mode 100644 index af10041623..0000000000 --- a/packages/twenty-server/src/engine/api/graphql/workspace-schema-builder/factories/extend-object-type-definition-v2.factory.ts +++ /dev/null @@ -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 { - // eslint-disable-next-line @typescript-eslint/no-explicit-any - const fields: GraphQLFieldConfigMap = {}; - - 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; - } -} diff --git a/packages/twenty-server/src/engine/api/graphql/workspace-schema-builder/factories/factories.ts b/packages/twenty-server/src/engine/api/graphql/workspace-schema-builder/factories/factories.ts deleted file mode 100644 index 3bb57aaa51..0000000000 --- a/packages/twenty-server/src/engine/api/graphql/workspace-schema-builder/factories/factories.ts +++ /dev/null @@ -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, -]; diff --git a/packages/twenty-server/src/engine/api/graphql/workspace-schema-builder/factories/input-type-definition.factory.ts b/packages/twenty-server/src/engine/api/graphql/workspace-schema-builder/factories/input-type-definition.factory.ts deleted file mode 100644 index 9834ae7bef..0000000000 --- a/packages/twenty-server/src/engine/api/graphql/workspace-schema-builder/factories/input-type-definition.factory.ts +++ /dev/null @@ -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, - 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, - }; - } -} diff --git a/packages/twenty-server/src/engine/api/graphql/workspace-schema-builder/factories/input-type.factory.ts b/packages/twenty-server/src/engine/api/graphql/workspace-schema-builder/factories/input-type.factory.ts deleted file mode 100644 index c7ae54e191..0000000000 --- a/packages/twenty-server/src/engine/api/graphql/workspace-schema-builder/factories/input-type.factory.ts +++ /dev/null @@ -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 }, - }), - }); - } -} diff --git a/packages/twenty-server/src/engine/api/graphql/workspace-schema-builder/factories/mutation-type.factory.ts b/packages/twenty-server/src/engine/api/graphql/workspace-schema-builder/factories/mutation-type.factory.ts deleted file mode 100644 index 25b7c94be5..0000000000 --- a/packages/twenty-server/src/engine/api/graphql/workspace-schema-builder/factories/mutation-type.factory.ts +++ /dev/null @@ -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, - ); - } -} diff --git a/packages/twenty-server/src/engine/api/graphql/workspace-schema-builder/factories/object-type-definition.factory.ts b/packages/twenty-server/src/engine/api/graphql/workspace-schema-builder/factories/object-type-definition.factory.ts deleted file mode 100644 index 4842d9b56f..0000000000 --- a/packages/twenty-server/src/engine/api/graphql/workspace-schema-builder/factories/object-type-definition.factory.ts +++ /dev/null @@ -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, - }), - }), - }; - } -} diff --git a/packages/twenty-server/src/engine/api/graphql/workspace-schema-builder/factories/orphaned-types.factory.ts b/packages/twenty-server/src/engine/api/graphql/workspace-schema-builder/factories/orphaned-types.factory.ts deleted file mode 100644 index 1a2db0017f..0000000000 --- a/packages/twenty-server/src/engine/api/graphql/workspace-schema-builder/factories/orphaned-types.factory.ts +++ /dev/null @@ -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)]; - } -} diff --git a/packages/twenty-server/src/engine/api/graphql/workspace-schema-builder/factories/output-type.factory.ts b/packages/twenty-server/src/engine/api/graphql/workspace-schema-builder/factories/output-type.factory.ts deleted file mode 100644 index 8d8b68c104..0000000000 --- a/packages/twenty-server/src/engine/api/graphql/workspace-schema-builder/factories/output-type.factory.ts +++ /dev/null @@ -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); - } -} diff --git a/packages/twenty-server/src/engine/api/graphql/workspace-schema-builder/factories/query-type.factory.ts b/packages/twenty-server/src/engine/api/graphql/workspace-schema-builder/factories/query-type.factory.ts deleted file mode 100644 index e2096d126d..0000000000 --- a/packages/twenty-server/src/engine/api/graphql/workspace-schema-builder/factories/query-type.factory.ts +++ /dev/null @@ -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, - ); - } -} diff --git a/packages/twenty-server/src/engine/api/graphql/workspace-schema-builder/factories/relation-type-v2.factory.ts b/packages/twenty-server/src/engine/api/graphql/workspace-schema-builder/factories/relation-type-v2.factory.ts deleted file mode 100644 index 933d747970..0000000000 --- a/packages/twenty-server/src/engine/api/graphql/workspace-schema-builder/factories/relation-type-v2.factory.ts +++ /dev/null @@ -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; - } -} diff --git a/packages/twenty-server/src/engine/api/graphql/workspace-schema-builder/factories/args.factory.ts b/packages/twenty-server/src/engine/api/graphql/workspace-schema-builder/graphql-type-generators/args-type/args-type.generator.ts similarity index 52% rename from packages/twenty-server/src/engine/api/graphql/workspace-schema-builder/factories/args.factory.ts rename to packages/twenty-server/src/engine/api/graphql/workspace-schema-builder/graphql-type-generators/args-type/args-type.generator.ts index bb4e6e2f68..42c30b6a70 100644 --- a/packages/twenty-server/src/engine/api/graphql/workspace-schema-builder/factories/args.factory.ts +++ b/packages/twenty-server/src/engine/api/graphql/workspace-schema-builder/graphql-type-generators/args-type/args-type.generator.ts @@ -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, }); diff --git a/packages/twenty-server/src/engine/api/graphql/workspace-schema-builder/factories/composite-enum-type-definition.factory.ts b/packages/twenty-server/src/engine/api/graphql/workspace-schema-builder/graphql-type-generators/enum-types/composite-field-metadata-gql-enum-type.generator.ts similarity index 63% rename from packages/twenty-server/src/engine/api/graphql/workspace-schema-builder/factories/composite-enum-type-definition.factory.ts rename to packages/twenty-server/src/engine/api/graphql/workspace-schema-builder/graphql-type-generators/enum-types/composite-field-metadata-gql-enum-type.generator.ts index 55bde47e34..50619dd899 100644 --- a/packages/twenty-server/src/engine/api/graphql/workspace-schema-builder/factories/composite-enum-type-definition.factory.ts +++ b/packages/twenty-server/src/engine/api/graphql/workspace-schema-builder/graphql-type-generators/enum-types/composite-field-metadata-gql-enum-type.generator.ts @@ -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, }, ); diff --git a/packages/twenty-server/src/engine/api/graphql/workspace-schema-builder/factories/enum-type-definition.factory.ts b/packages/twenty-server/src/engine/api/graphql/workspace-schema-builder/graphql-type-generators/enum-types/enum-field-metadata-gql-enum-type.generator.ts similarity index 74% rename from packages/twenty-server/src/engine/api/graphql/workspace-schema-builder/factories/enum-type-definition.factory.ts rename to packages/twenty-server/src/engine/api/graphql/workspace-schema-builder/graphql-type-generators/enum-types/enum-field-metadata-gql-enum-type.generator.ts index 34c13a5422..a11a78ff1e 100644 --- a/packages/twenty-server/src/engine/api/graphql/workspace-schema-builder/factories/enum-type-definition.factory.ts +++ b/packages/twenty-server/src/engine/api/graphql/workspace-schema-builder/graphql-type-generators/enum-types/enum-field-metadata-gql-enum-type.generator.ts @@ -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, }, ); diff --git a/packages/twenty-server/src/engine/api/graphql/workspace-schema-builder/graphql-type-generators/gql-type.generator.ts b/packages/twenty-server/src/engine/api/graphql/workspace-schema-builder/graphql-type-generators/gql-type.generator.ts new file mode 100644 index 0000000000..bc0d677817 --- /dev/null +++ b/packages/twenty-server/src/engine/api/graphql/workspace-schema-builder/graphql-type-generators/gql-type.generator.ts @@ -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, + ); + } + } +} diff --git a/packages/twenty-server/src/engine/api/graphql/workspace-schema-builder/graphql-type-generators/input-types/composite-field-metadata-gql-input-type.generator.ts b/packages/twenty-server/src/engine/api/graphql/workspace-schema-builder/graphql-type-generators/input-types/composite-field-metadata-gql-input-type.generator.ts new file mode 100644 index 0000000000..5cd5181fc3 --- /dev/null +++ b/packages/twenty-server/src/engine/api/graphql/workspace-schema-builder/graphql-type-generators/input-types/composite-field-metadata-gql-input-type.generator.ts @@ -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, + ); + } +} diff --git a/packages/twenty-server/src/engine/api/graphql/workspace-schema-builder/graphql-type-generators/input-types/create-input/composite-field-metadata-create-gql-input-type.generator.ts b/packages/twenty-server/src/engine/api/graphql/workspace-schema-builder/graphql-type-generators/input-types/create-input/composite-field-metadata-create-gql-input-type.generator.ts new file mode 100644 index 0000000000..b4f6083d35 --- /dev/null +++ b/packages/twenty-server/src/engine/api/graphql/workspace-schema-builder/graphql-type-generators/input-types/create-input/composite-field-metadata-create-gql-input-type.generator.ts @@ -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; + } +} diff --git a/packages/twenty-server/src/engine/api/graphql/workspace-schema-builder/graphql-type-generators/input-types/create-input/object-metadata-create-gql-input-type.generator.ts b/packages/twenty-server/src/engine/api/graphql/workspace-schema-builder/graphql-type-generators/input-types/create-input/object-metadata-create-gql-input-type.generator.ts new file mode 100644 index 0000000000..519db7a2a0 --- /dev/null +++ b/packages/twenty-server/src/engine/api/graphql/workspace-schema-builder/graphql-type-generators/input-types/create-input/object-metadata-create-gql-input-type.generator.ts @@ -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; + } +} diff --git a/packages/twenty-server/src/engine/api/graphql/workspace-schema-builder/graphql-type-generators/input-types/filter-input/composite-field-metadata-filter-gql-input-types.generator.ts b/packages/twenty-server/src/engine/api/graphql/workspace-schema-builder/graphql-type-generators/input-types/filter-input/composite-field-metadata-filter-gql-input-types.generator.ts new file mode 100644 index 0000000000..4fc03f2de8 --- /dev/null +++ b/packages/twenty-server/src/engine/api/graphql/workspace-schema-builder/graphql-type-generators/input-types/filter-input/composite-field-metadata-filter-gql-input-types.generator.ts @@ -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; + } +} diff --git a/packages/twenty-server/src/engine/api/graphql/workspace-schema-builder/graphql-type-generators/input-types/filter-input/object-metadata-filter-gql-input-type.generator.ts b/packages/twenty-server/src/engine/api/graphql/workspace-schema-builder/graphql-type-generators/input-types/filter-input/object-metadata-filter-gql-input-type.generator.ts new file mode 100644 index 0000000000..301c83802b --- /dev/null +++ b/packages/twenty-server/src/engine/api/graphql/workspace-schema-builder/graphql-type-generators/input-types/filter-input/object-metadata-filter-gql-input-type.generator.ts @@ -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, + }), + }, + }; + } +} diff --git a/packages/twenty-server/src/engine/api/graphql/workspace-schema-builder/graphql-type-generators/input-types/object-metadata-gql-input-type.generator.ts b/packages/twenty-server/src/engine/api/graphql/workspace-schema-builder/graphql-type-generators/input-types/object-metadata-gql-input-type.generator.ts new file mode 100644 index 0000000000..147729bdc7 --- /dev/null +++ b/packages/twenty-server/src/engine/api/graphql/workspace-schema-builder/graphql-type-generators/input-types/object-metadata-gql-input-type.generator.ts @@ -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, + ); + } +} diff --git a/packages/twenty-server/src/engine/api/graphql/workspace-schema-builder/graphql-type-generators/input-types/order-by-input/composite-field-metadata-order-by-gql-input-type.generator.ts b/packages/twenty-server/src/engine/api/graphql/workspace-schema-builder/graphql-type-generators/input-types/order-by-input/composite-field-metadata-order-by-gql-input-type.generator.ts new file mode 100644 index 0000000000..a7efc4c306 --- /dev/null +++ b/packages/twenty-server/src/engine/api/graphql/workspace-schema-builder/graphql-type-generators/input-types/order-by-input/composite-field-metadata-order-by-gql-input-type.generator.ts @@ -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; + } +} diff --git a/packages/twenty-server/src/engine/api/graphql/workspace-schema-builder/graphql-type-generators/input-types/order-by-input/object-metadata-order-by-gql-input-type.generator.ts b/packages/twenty-server/src/engine/api/graphql/workspace-schema-builder/graphql-type-generators/input-types/order-by-input/object-metadata-order-by-gql-input-type.generator.ts new file mode 100644 index 0000000000..b51a443003 --- /dev/null +++ b/packages/twenty-server/src/engine/api/graphql/workspace-schema-builder/graphql-type-generators/input-types/order-by-input/object-metadata-order-by-gql-input-type.generator.ts @@ -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 + | 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; + } +} diff --git a/packages/twenty-server/src/engine/api/graphql/workspace-schema-builder/factories/relation-connect-input-type-definition.factory.ts b/packages/twenty-server/src/engine/api/graphql/workspace-schema-builder/graphql-type-generators/input-types/relation-connect-gql-input-type.generator.ts similarity index 83% rename from packages/twenty-server/src/engine/api/graphql/workspace-schema-builder/factories/relation-connect-input-type-definition.factory.ts rename to packages/twenty-server/src/engine/api/graphql/workspace-schema-builder/graphql-type-generators/input-types/relation-connect-gql-input-type.generator.ts index 8d8ce77984..513812987c 100644 --- a/packages/twenty-server/src/engine/api/graphql/workspace-schema-builder/factories/relation-connect-input-type-definition.factory.ts +++ b/packages/twenty-server/src/engine/api/graphql/workspace-schema-builder/graphql-type-generators/input-types/relation-connect-gql-input-type.generator.ts @@ -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] = { diff --git a/packages/twenty-server/src/engine/api/graphql/workspace-schema-builder/graphql-type-generators/input-types/relation-field-metadata-gql-type.generator.ts b/packages/twenty-server/src/engine/api/graphql/workspace-schema-builder/graphql-type-generators/input-types/relation-field-metadata-gql-type.generator.ts new file mode 100644 index 0000000000..914f089036 --- /dev/null +++ b/packages/twenty-server/src/engine/api/graphql/workspace-schema-builder/graphql-type-generators/input-types/relation-field-metadata-gql-type.generator.ts @@ -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, + }, + }; + } +} diff --git a/packages/twenty-server/src/engine/api/graphql/workspace-schema-builder/graphql-type-generators/input-types/update-input/composite-field-metadata-update-gql-input-type.generator.ts b/packages/twenty-server/src/engine/api/graphql/workspace-schema-builder/graphql-type-generators/input-types/update-input/composite-field-metadata-update-gql-input-type.generator.ts new file mode 100644 index 0000000000..fd1d51fb05 --- /dev/null +++ b/packages/twenty-server/src/engine/api/graphql/workspace-schema-builder/graphql-type-generators/input-types/update-input/composite-field-metadata-update-gql-input-type.generator.ts @@ -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; + } +} diff --git a/packages/twenty-server/src/engine/api/graphql/workspace-schema-builder/graphql-type-generators/input-types/update-input/object-metadata-update-gql-input-type.generator.ts b/packages/twenty-server/src/engine/api/graphql/workspace-schema-builder/graphql-type-generators/input-types/update-input/object-metadata-update-gql-input-type.generator.ts new file mode 100644 index 0000000000..f1af9bde64 --- /dev/null +++ b/packages/twenty-server/src/engine/api/graphql/workspace-schema-builder/graphql-type-generators/input-types/update-input/object-metadata-update-gql-input-type.generator.ts @@ -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; + } +} diff --git a/packages/twenty-server/src/engine/api/graphql/workspace-schema-builder/factories/aggregation-type.factory.ts b/packages/twenty-server/src/engine/api/graphql/workspace-schema-builder/graphql-type-generators/object-types/aggregation-type.generator.ts similarity index 93% rename from packages/twenty-server/src/engine/api/graphql/workspace-schema-builder/factories/aggregation-type.factory.ts rename to packages/twenty-server/src/engine/api/graphql/workspace-schema-builder/graphql-type-generators/object-types/aggregation-type.generator.ts index b136e493d9..ad9617e42e 100644 --- a/packages/twenty-server/src/engine/api/graphql/workspace-schema-builder/factories/aggregation-type.factory.ts +++ b/packages/twenty-server/src/engine/api/graphql/workspace-schema-builder/graphql-type-generators/object-types/aggregation-type.generator.ts @@ -9,8 +9,8 @@ import { type ObjectMetadataEntity } from 'src/engine/metadata-modules/object-me type AggregationGraphQLType = Pick; @Injectable() -export class AggregationTypeFactory { - public create( +export class AggregationObjectTypeGenerator { + public generate( objectMetadata: ObjectMetadataEntity, ): Record { const availableAggregations = getAvailableAggregationsFromObjectFields( diff --git a/packages/twenty-server/src/engine/api/graphql/workspace-schema-builder/graphql-type-generators/object-types/composite-field-metadata-gql-object-type.generator.ts b/packages/twenty-server/src/engine/api/graphql/workspace-schema-builder/graphql-type-generators/object-types/composite-field-metadata-gql-object-type.generator.ts new file mode 100644 index 0000000000..e5f6915cb1 --- /dev/null +++ b/packages/twenty-server/src/engine/api/graphql/workspace-schema-builder/graphql-type-generators/object-types/composite-field-metadata-gql-object-type.generator.ts @@ -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; + } +} diff --git a/packages/twenty-server/src/engine/api/graphql/workspace-schema-builder/graphql-type-generators/object-types/connection-gql-object-type.generator.ts b/packages/twenty-server/src/engine/api/graphql/workspace-schema-builder/graphql-type-generators/object-types/connection-gql-object-type.generator.ts new file mode 100644 index 0000000000..690772e892 --- /dev/null +++ b/packages/twenty-server/src/engine/api/graphql/workspace-schema-builder/graphql-type-generators/object-types/connection-gql-object-type.generator.ts @@ -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; + } +} diff --git a/packages/twenty-server/src/engine/api/graphql/workspace-schema-builder/graphql-type-generators/object-types/edge-gql-object-type.generator.ts b/packages/twenty-server/src/engine/api/graphql/workspace-schema-builder/graphql-type-generators/object-types/edge-gql-object-type.generator.ts new file mode 100644 index 0000000000..aa9f1f418c --- /dev/null +++ b/packages/twenty-server/src/engine/api/graphql/workspace-schema-builder/graphql-type-generators/object-types/edge-gql-object-type.generator.ts @@ -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; + } +} diff --git a/packages/twenty-server/src/engine/api/graphql/workspace-schema-builder/graphql-type-generators/object-types/object-metadata-gql-object-type.generator.ts b/packages/twenty-server/src/engine/api/graphql/workspace-schema-builder/graphql-type-generators/object-types/object-metadata-gql-object-type.generator.ts new file mode 100644 index 0000000000..834d2ca87e --- /dev/null +++ b/packages/twenty-server/src/engine/api/graphql/workspace-schema-builder/graphql-type-generators/object-types/object-metadata-gql-object-type.generator.ts @@ -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; + } +} diff --git a/packages/twenty-server/src/engine/api/graphql/workspace-schema-builder/graphql-type-generators/object-types/object-metadata-with-relations-gql-object-type.generator.ts b/packages/twenty-server/src/engine/api/graphql/workspace-schema-builder/graphql-type-generators/object-types/object-metadata-with-relations-gql-object-type.generator.ts new file mode 100644 index 0000000000..1bda5a5e23 --- /dev/null +++ b/packages/twenty-server/src/engine/api/graphql/workspace-schema-builder/graphql-type-generators/object-types/object-metadata-with-relations-gql-object-type.generator.ts @@ -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; + } +} diff --git a/packages/twenty-server/src/engine/api/graphql/workspace-schema-builder/graphql-type-generators/object-types/relation-field-metadata-gql-object-type.generator.ts b/packages/twenty-server/src/engine/api/graphql/workspace-schema-builder/graphql-type-generators/object-types/relation-field-metadata-gql-object-type.generator.ts new file mode 100644 index 0000000000..89cbcc8b2d --- /dev/null +++ b/packages/twenty-server/src/engine/api/graphql/workspace-schema-builder/graphql-type-generators/object-types/relation-field-metadata-gql-object-type.generator.ts @@ -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, + }, + }; + } +} diff --git a/packages/twenty-server/src/engine/api/graphql/workspace-schema-builder/graphql-type-generators/orphaned-types.generator.ts b/packages/twenty-server/src/engine/api/graphql/workspace-schema-builder/graphql-type-generators/orphaned-types.generator.ts new file mode 100644 index 0000000000..4461d554c3 --- /dev/null +++ b/packages/twenty-server/src/engine/api/graphql/workspace-schema-builder/graphql-type-generators/orphaned-types.generator.ts @@ -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, + ]); + } +} diff --git a/packages/twenty-server/src/engine/api/graphql/workspace-schema-builder/graphql-type-generators/root-types/mutation-type.generator.ts b/packages/twenty-server/src/engine/api/graphql/workspace-schema-builder/graphql-type-generators/root-types/mutation-type.generator.ts new file mode 100644 index 0000000000..cb0d144643 --- /dev/null +++ b/packages/twenty-server/src/engine/api/graphql/workspace-schema-builder/graphql-type-generators/root-types/mutation-type.generator.ts @@ -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; + } +} diff --git a/packages/twenty-server/src/engine/api/graphql/workspace-schema-builder/graphql-type-generators/root-types/query-type.generator.ts b/packages/twenty-server/src/engine/api/graphql/workspace-schema-builder/graphql-type-generators/root-types/query-type.generator.ts new file mode 100644 index 0000000000..cf1e228719 --- /dev/null +++ b/packages/twenty-server/src/engine/api/graphql/workspace-schema-builder/graphql-type-generators/root-types/query-type.generator.ts @@ -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; + } +} diff --git a/packages/twenty-server/src/engine/api/graphql/workspace-schema-builder/factories/root-type.factory.ts b/packages/twenty-server/src/engine/api/graphql/workspace-schema-builder/graphql-type-generators/root-types/root-type.generator.ts similarity index 61% rename from packages/twenty-server/src/engine/api/graphql/workspace-schema-builder/factories/root-type.factory.ts rename to packages/twenty-server/src/engine/api/graphql/workspace-schema-builder/graphql-type-generators/root-types/root-type.generator.ts index 81dcda7b73..78b29af3b2 100644 --- a/packages/twenty-server/src/engine/api/graphql/workspace-schema-builder/factories/root-type.factory.ts +++ b/packages/twenty-server/src/engine/api/graphql/workspace-schema-builder/graphql-type-generators/root-types/root-type.generator.ts @@ -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( + private generateFields( objectMetadataCollection: ObjectMetadataEntity[], workspaceResolverMethodNames: WorkspaceResolverBuilderMethodNames[], - options: WorkspaceBuildSchemaOptions, - ): GraphQLFieldConfigMap { - const fieldConfigMap: GraphQLFieldConfigMap = {}; + ): 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, diff --git a/packages/twenty-server/src/engine/api/graphql/workspace-schema-builder/graphql-type-generators/type-generators.ts b/packages/twenty-server/src/engine/api/graphql/workspace-schema-builder/graphql-type-generators/type-generators.ts new file mode 100644 index 0000000000..0b60eff973 --- /dev/null +++ b/packages/twenty-server/src/engine/api/graphql/workspace-schema-builder/graphql-type-generators/type-generators.ts @@ -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, +]; diff --git a/packages/twenty-server/src/engine/api/graphql/workspace-schema-builder/interfaces/param-metadata.interface.ts b/packages/twenty-server/src/engine/api/graphql/workspace-schema-builder/interfaces/param-metadata.interface.ts index d4d11546ec..b60d741e13 100644 --- a/packages/twenty-server/src/engine/api/graphql/workspace-schema-builder/interfaces/param-metadata.interface.ts +++ b/packages/twenty-server/src/engine/api/graphql/workspace-schema-builder/interfaces/param-metadata.interface.ts @@ -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 { - 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; } export interface ArgsMetadata { args: { [key: string]: ArgMetadata; }; - objectMetadataId: string; + objectMetadataSingularName: string; } diff --git a/packages/twenty-server/src/engine/api/graphql/workspace-schema-builder/interfaces/workspace-build-schema-options.interface.ts b/packages/twenty-server/src/engine/api/graphql/workspace-schema-builder/interfaces/workspace-build-schema-options.interface.ts deleted file mode 100644 index 19bd21e278..0000000000 --- a/packages/twenty-server/src/engine/api/graphql/workspace-schema-builder/interfaces/workspace-build-schema-options.interface.ts +++ /dev/null @@ -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; -} diff --git a/packages/twenty-server/src/engine/api/graphql/workspace-schema-builder/services/type-mapper.service.ts b/packages/twenty-server/src/engine/api/graphql/workspace-schema-builder/services/type-mapper.service.ts index a159c6cb7b..0fb3382608 100644 --- a/packages/twenty-server/src/engine/api/graphql/workspace-schema-builder/services/type-mapper.service.ts +++ b/packages/twenty-server/src/engine/api/graphql/workspace-schema-builder/services/type-mapper.service.ts @@ -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 { +export interface TypeOptions { nullable?: boolean; isArray?: boolean; arrayDepth?: number; - defaultValue?: T; + defaultValue?: FieldMetadataDefaultValue; settings?: FieldMetadataSettings; isIdField?: boolean; isRelationConnectField?: boolean; @@ -62,11 +62,10 @@ const StringArrayScalarType = new GraphQLList(GraphQLString); export class TypeMapperService { mapToScalarType( fieldMetadataType: FieldMetadataType, - settings?: FieldMetadataSettings, - 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) - ?.dataType ?? NumberDataType.FLOAT, + ( + typeOptions?.settings as FieldMetadataSettings + )?.dataType ?? NumberDataType.FLOAT, ), ], [FieldMetadataType.NUMERIC, BigFloatScalarType], @@ -101,11 +101,10 @@ export class TypeMapperService { mapToFilterType( fieldMetadataType: FieldMetadataType, - settings?: FieldMetadataSettings, - 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) - ?.dataType, + ( + typeOptions?.settings as FieldMetadataSettings + )?.dataType, ), ], [FieldMetadataType.NUMERIC, BigFloatFilterType], @@ -168,7 +168,7 @@ export class TypeMapperService { return typeOrderByMapping.get(fieldMetadataType); } - mapToGqlType( + applyTypeOptions( typeRef: T, options: TypeOptions, ): T { diff --git a/packages/twenty-server/src/engine/api/graphql/workspace-schema-builder/storages/gql-types.storage.ts b/packages/twenty-server/src/engine/api/graphql/workspace-schema-builder/storages/gql-types.storage.ts new file mode 100644 index 0000000000..92e0483e19 --- /dev/null +++ b/packages/twenty-server/src/engine/api/graphql/workspace-schema-builder/storages/gql-types.storage.ts @@ -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); + } +} diff --git a/packages/twenty-server/src/engine/api/graphql/workspace-schema-builder/storages/type-definitions.storage.ts b/packages/twenty-server/src/engine/api/graphql/workspace-schema-builder/storages/type-definitions.storage.ts deleted file mode 100644 index 82c569a7c1..0000000000 --- a/packages/twenty-server/src/engine/api/graphql/workspace-schema-builder/storages/type-definitions.storage.ts +++ /dev/null @@ -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(); - 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()}`; - } -} diff --git a/packages/twenty-server/src/engine/api/graphql/workspace-schema-builder/type-definitions.generator.ts b/packages/twenty-server/src/engine/api/graphql/workspace-schema-builder/type-definitions.generator.ts deleted file mode 100644 index 4436803e46..0000000000 --- a/packages/twenty-server/src/engine/api/graphql/workspace-schema-builder/type-definitions.generator.ts +++ /dev/null @@ -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); - } -} diff --git a/packages/twenty-server/src/engine/api/graphql/workspace-schema-builder/types/graphql-field-config-map.types.ts b/packages/twenty-server/src/engine/api/graphql/workspace-schema-builder/types/graphql-field-config-map.types.ts new file mode 100644 index 0000000000..78413f03e4 --- /dev/null +++ b/packages/twenty-server/src/engine/api/graphql/workspace-schema-builder/types/graphql-field-config-map.types.ts @@ -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; + } +>; diff --git a/packages/twenty-server/src/engine/api/graphql/workspace-schema-builder/utils/__tests__/get-resolver-args.spec.ts b/packages/twenty-server/src/engine/api/graphql/workspace-schema-builder/utils/__tests__/get-resolver-args.spec.ts index 1379b2cfd8..9207fa7eaa 100644 --- a/packages/twenty-server/src/engine/api/graphql/workspace-schema-builder/utils/__tests__/get-resolver-args.spec.ts +++ b/packages/twenty-server/src/engine/api/graphql/workspace-schema-builder/utils/__tests__/get-resolver-args.spec.ts @@ -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 }, }, }; diff --git a/packages/twenty-server/src/engine/api/graphql/workspace-schema-builder/utils/compute-composite-field-type-options.util.ts b/packages/twenty-server/src/engine/api/graphql/workspace-schema-builder/utils/compute-composite-field-type-options.util.ts new file mode 100644 index 0000000000..6c3676a13d --- /dev/null +++ b/packages/twenty-server/src/engine/api/graphql/workspace-schema-builder/utils/compute-composite-field-type-options.util.ts @@ -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, +) { + return { + nullable: !property.isRequired, + isArray: + property.type === FieldMetadataType.MULTI_SELECT || property.isArray, + }; +} diff --git a/packages/twenty-server/src/engine/api/graphql/workspace-schema-builder/utils/compute-field-input-type-options.util.ts b/packages/twenty-server/src/engine/api/graphql/workspace-schema-builder/utils/compute-field-input-type-options.util.ts new file mode 100644 index 0000000000..706c73fa25 --- /dev/null +++ b/packages/twenty-server/src/engine/api/graphql/workspace-schema-builder/utils/compute-field-input-type-options.util.ts @@ -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 = ( + fieldMetadata: FieldMetadataEntity, + 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', + }; +}; diff --git a/packages/twenty-server/src/engine/api/graphql/workspace-schema-builder/utils/compute-stored-gql-type-key-utils/__tests__/compute-composite-field-enum-type-key.util.spec.ts b/packages/twenty-server/src/engine/api/graphql/workspace-schema-builder/utils/compute-stored-gql-type-key-utils/__tests__/compute-composite-field-enum-type-key.util.spec.ts new file mode 100644 index 0000000000..9706c5f1b4 --- /dev/null +++ b/packages/twenty-server/src/engine/api/graphql/workspace-schema-builder/utils/compute-stored-gql-type-key-utils/__tests__/compute-composite-field-enum-type-key.util.spec.ts @@ -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', + ); + }); +}); diff --git a/packages/twenty-server/src/engine/api/graphql/workspace-schema-builder/utils/compute-stored-gql-type-key-utils/__tests__/compute-composite-field-input-type-key.util.spec.ts b/packages/twenty-server/src/engine/api/graphql/workspace-schema-builder/utils/compute-stored-gql-type-key-utils/__tests__/compute-composite-field-input-type-key.util.spec.ts new file mode 100644 index 0000000000..48c75add39 --- /dev/null +++ b/packages/twenty-server/src/engine/api/graphql/workspace-schema-builder/utils/compute-stored-gql-type-key-utils/__tests__/compute-composite-field-input-type-key.util.spec.ts @@ -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'); + }); +}); diff --git a/packages/twenty-server/src/engine/api/graphql/workspace-schema-builder/utils/compute-stored-gql-type-key-utils/__tests__/compute-composite-field-object-type-key.util.spec.ts b/packages/twenty-server/src/engine/api/graphql/workspace-schema-builder/utils/compute-stored-gql-type-key-utils/__tests__/compute-composite-field-object-type-key.util.spec.ts new file mode 100644 index 0000000000..9d0178069f --- /dev/null +++ b/packages/twenty-server/src/engine/api/graphql/workspace-schema-builder/utils/compute-stored-gql-type-key-utils/__tests__/compute-composite-field-object-type-key.util.spec.ts @@ -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'); + }); +}); diff --git a/packages/twenty-server/src/engine/api/graphql/workspace-schema-builder/utils/compute-stored-gql-type-key-utils/__tests__/compute-enum-field-gql-type-key.util.spec.ts b/packages/twenty-server/src/engine/api/graphql/workspace-schema-builder/utils/compute-stored-gql-type-key-utils/__tests__/compute-enum-field-gql-type-key.util.spec.ts new file mode 100644 index 0000000000..a68a4692a9 --- /dev/null +++ b/packages/twenty-server/src/engine/api/graphql/workspace-schema-builder/utils/compute-stored-gql-type-key-utils/__tests__/compute-enum-field-gql-type-key.util.spec.ts @@ -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'); + }); +}); diff --git a/packages/twenty-server/src/engine/api/graphql/workspace-schema-builder/utils/compute-stored-gql-type-key-utils/__tests__/compute-object-metadata-input-type.util.spec.ts b/packages/twenty-server/src/engine/api/graphql/workspace-schema-builder/utils/compute-stored-gql-type-key-utils/__tests__/compute-object-metadata-input-type.util.spec.ts new file mode 100644 index 0000000000..68f9c055ff --- /dev/null +++ b/packages/twenty-server/src/engine/api/graphql/workspace-schema-builder/utils/compute-stored-gql-type-key-utils/__tests__/compute-object-metadata-input-type.util.spec.ts @@ -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'); + }); +}); diff --git a/packages/twenty-server/src/engine/api/graphql/workspace-schema-builder/utils/compute-stored-gql-type-key-utils/__tests__/compute-object-metadata-object-type-key.util.spec.ts b/packages/twenty-server/src/engine/api/graphql/workspace-schema-builder/utils/compute-stored-gql-type-key-utils/__tests__/compute-object-metadata-object-type-key.util.spec.ts new file mode 100644 index 0000000000..cd949bef47 --- /dev/null +++ b/packages/twenty-server/src/engine/api/graphql/workspace-schema-builder/utils/compute-stored-gql-type-key-utils/__tests__/compute-object-metadata-object-type-key.util.spec.ts @@ -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'); + }); +}); diff --git a/packages/twenty-server/src/engine/api/graphql/workspace-schema-builder/utils/compute-stored-gql-type-key-utils/__tests__/compute-relation-connect-input-type-key.util.spec.ts b/packages/twenty-server/src/engine/api/graphql/workspace-schema-builder/utils/compute-stored-gql-type-key-utils/__tests__/compute-relation-connect-input-type-key.util.spec.ts new file mode 100644 index 0000000000..c03e015e67 --- /dev/null +++ b/packages/twenty-server/src/engine/api/graphql/workspace-schema-builder/utils/compute-stored-gql-type-key-utils/__tests__/compute-relation-connect-input-type-key.util.spec.ts @@ -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'); + }); +}); diff --git a/packages/twenty-server/src/engine/api/graphql/workspace-schema-builder/utils/compute-stored-gql-type-key-utils/compute-composite-field-enum-type-key.util.ts b/packages/twenty-server/src/engine/api/graphql/workspace-schema-builder/utils/compute-stored-gql-type-key-utils/compute-composite-field-enum-type-key.util.ts new file mode 100644 index 0000000000..331aee24f7 --- /dev/null +++ b/packages/twenty-server/src/engine/api/graphql/workspace-schema-builder/utils/compute-stored-gql-type-key-utils/compute-composite-field-enum-type-key.util.ts @@ -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`; +}; diff --git a/packages/twenty-server/src/engine/api/graphql/workspace-schema-builder/utils/compute-stored-gql-type-key-utils/compute-composite-field-input-type-key.util.ts b/packages/twenty-server/src/engine/api/graphql/workspace-schema-builder/utils/compute-stored-gql-type-key-utils/compute-composite-field-input-type-key.util.ts new file mode 100644 index 0000000000..0a0d963a8a --- /dev/null +++ b/packages/twenty-server/src/engine/api/graphql/workspace-schema-builder/utils/compute-stored-gql-type-key-utils/compute-composite-field-input-type-key.util.ts @@ -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`; +}; diff --git a/packages/twenty-server/src/engine/api/graphql/workspace-schema-builder/utils/compute-stored-gql-type-key-utils/compute-composite-field-object-type-key.util.ts b/packages/twenty-server/src/engine/api/graphql/workspace-schema-builder/utils/compute-stored-gql-type-key-utils/compute-composite-field-object-type-key.util.ts new file mode 100644 index 0000000000..0bf6f35772 --- /dev/null +++ b/packages/twenty-server/src/engine/api/graphql/workspace-schema-builder/utils/compute-stored-gql-type-key-utils/compute-composite-field-object-type-key.util.ts @@ -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()}`; +}; diff --git a/packages/twenty-server/src/engine/api/graphql/workspace-schema-builder/utils/compute-stored-gql-type-key-utils/compute-enum-field-gql-type-key.util.ts b/packages/twenty-server/src/engine/api/graphql/workspace-schema-builder/utils/compute-stored-gql-type-key-utils/compute-enum-field-gql-type-key.util.ts new file mode 100644 index 0000000000..68f21ea430 --- /dev/null +++ b/packages/twenty-server/src/engine/api/graphql/workspace-schema-builder/utils/compute-stored-gql-type-key-utils/compute-enum-field-gql-type-key.util.ts @@ -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`; +}; diff --git a/packages/twenty-server/src/engine/api/graphql/workspace-schema-builder/utils/compute-stored-gql-type-key-utils/compute-object-metadata-input-type.util.ts b/packages/twenty-server/src/engine/api/graphql/workspace-schema-builder/utils/compute-stored-gql-type-key-utils/compute-object-metadata-input-type.util.ts new file mode 100644 index 0000000000..90068581f2 --- /dev/null +++ b/packages/twenty-server/src/engine/api/graphql/workspace-schema-builder/utils/compute-stored-gql-type-key-utils/compute-object-metadata-input-type.util.ts @@ -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`; +}; diff --git a/packages/twenty-server/src/engine/api/graphql/workspace-schema-builder/utils/compute-stored-gql-type-key-utils/compute-object-metadata-object-type-key.util.ts b/packages/twenty-server/src/engine/api/graphql/workspace-schema-builder/utils/compute-stored-gql-type-key-utils/compute-object-metadata-object-type-key.util.ts new file mode 100644 index 0000000000..502a9d83f6 --- /dev/null +++ b/packages/twenty-server/src/engine/api/graphql/workspace-schema-builder/utils/compute-stored-gql-type-key-utils/compute-object-metadata-object-type-key.util.ts @@ -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()}`; +}; diff --git a/packages/twenty-server/src/engine/api/graphql/workspace-schema-builder/utils/compute-stored-gql-type-key-utils/compute-relation-connect-input-type-key.util.ts b/packages/twenty-server/src/engine/api/graphql/workspace-schema-builder/utils/compute-stored-gql-type-key-utils/compute-relation-connect-input-type-key.util.ts new file mode 100644 index 0000000000..00c2bf067b --- /dev/null +++ b/packages/twenty-server/src/engine/api/graphql/workspace-schema-builder/utils/compute-stored-gql-type-key-utils/compute-relation-connect-input-type-key.util.ts @@ -0,0 +1,3 @@ +export const computeRelationConnectInputTypeKey = ( + objectMetadataNameId: string, +) => `${objectMetadataNameId}-ConnectInput`; diff --git a/packages/twenty-server/src/engine/api/graphql/workspace-schema-builder/utils/create-gql-enum-filter-type.util.ts b/packages/twenty-server/src/engine/api/graphql/workspace-schema-builder/utils/create-gql-enum-filter-type.util.ts new file mode 100644 index 0000000000..442a06e0b2 --- /dev/null +++ b/packages/twenty-server/src/engine/api/graphql/workspace-schema-builder/utils/create-gql-enum-filter-type.util.ts @@ -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 }, + }), + }); +}; diff --git a/packages/twenty-server/src/engine/api/graphql/workspace-schema-builder/utils/generate-fields.util.ts b/packages/twenty-server/src/engine/api/graphql/workspace-schema-builder/utils/generate-fields.util.ts deleted file mode 100644 index 1e0675264b..0000000000 --- a/packages/twenty-server/src/engine/api/graphql/workspace-schema-builder/utils/generate-fields.util.ts +++ /dev/null @@ -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 = - { - 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 extends InputTypeDefinitionKind - ? GraphQLInputFieldConfigMap - : // eslint-disable-next-line @typescript-eslint/no-explicit-any - GraphQLFieldConfigMap => { - 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 = ( - fieldMetadata: FieldMetadataEntity, -) => { - return isCompositeFieldMetadataType(fieldMetadata.type) - ? fieldMetadata.type.toString() - : fieldMetadata.id; -}; - -const getTypeFactoryOptions = ( - fieldMetadata: FieldMetadataEntity, - 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; -}) => { - 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; -}) => { - 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, - ); -}; diff --git a/packages/twenty-server/src/engine/api/graphql/workspace-schema-builder/utils/get-resolver-args.util.ts b/packages/twenty-server/src/engine/api/graphql/workspace-schema-builder/utils/get-resolver-args.util.ts index 1975e9e4d7..2eb6c9bd03 100644 --- a/packages/twenty-server/src/engine/api/graphql/workspace-schema-builder/utils/get-resolver-args.util.ts +++ b/packages/twenty-server/src/engine/api/graphql/workspace-schema-builder/utils/get-resolver-args.util.ts @@ -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, }, }; diff --git a/packages/twenty-server/src/engine/api/graphql/workspace-schema-builder/workspace-graphql-schema.factory.ts b/packages/twenty-server/src/engine/api/graphql/workspace-schema-builder/workspace-graphql-schema.factory.ts index 648ced60ff..1aac4fcc64 100644 --- a/packages/twenty-server/src/engine/api/graphql/workspace-schema-builder/workspace-graphql-schema.factory.ts +++ b/packages/twenty-server/src/engine/api/graphql/workspace-schema-builder/workspace-graphql-schema.factory.ts @@ -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 { - // 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; diff --git a/packages/twenty-server/src/engine/api/graphql/workspace-schema-builder/workspace-schema-builder.module.ts b/packages/twenty-server/src/engine/api/graphql/workspace-schema-builder/workspace-schema-builder.module.ts index d56143e3cb..f1c1860a02 100644 --- a/packages/twenty-server/src/engine/api/graphql/workspace-schema-builder/workspace-schema-builder.module.ts +++ b/packages/twenty-server/src/engine/api/graphql/workspace-schema-builder/workspace-schema-builder.module.ts @@ -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 {} diff --git a/packages/twenty-server/src/engine/api/graphql/workspace-schema.factory.ts b/packages/twenty-server/src/engine/api/graphql/workspace-schema.factory.ts index 021dd4d1c6..8ce76f31e1 100644 --- a/packages/twenty-server/src/engine/api/graphql/workspace-schema.factory.ts +++ b/packages/twenty-server/src/engine/api/graphql/workspace-schema.factory.ts @@ -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 =