simplify gql schema gen (#14426)
closes https://github.com/twentyhq/core-team-issues/issues/1358
This commit is contained in:
@@ -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: {},
|
||||
},
|
||||
{
|
||||
|
||||
+1
-4
@@ -17,7 +17,6 @@ import { ActivityQueryResultGetterHandler } from 'src/engine/api/graphql/workspa
|
||||
import { AttachmentQueryResultGetterHandler } from 'src/engine/api/graphql/workspace-query-runner/factories/query-result-getters/handlers/attachment-query-result-getter.handler';
|
||||
import { PersonQueryResultGetterHandler } from 'src/engine/api/graphql/workspace-query-runner/factories/query-result-getters/handlers/person-query-result-getter.handler';
|
||||
import { WorkspaceMemberQueryResultGetterHandler } from 'src/engine/api/graphql/workspace-query-runner/factories/query-result-getters/handlers/workspace-member-query-result-getter.handler';
|
||||
import { CompositeInputTypeDefinitionFactory } from 'src/engine/api/graphql/workspace-schema-builder/factories/composite-input-type-definition.factory';
|
||||
import { FileService } from 'src/engine/core-modules/file/services/file.service';
|
||||
import { type ObjectMetadataItemWithFieldMaps } from 'src/engine/metadata-modules/types/object-metadata-item-with-field-maps';
|
||||
import { type ObjectMetadataMaps } from 'src/engine/metadata-modules/types/object-metadata-maps';
|
||||
@@ -28,9 +27,7 @@ import { isFieldMetadataEntityOfType } from 'src/engine/utils/is-field-metadata-
|
||||
// Right now the factory will override any change made on relations by the handlers
|
||||
@Injectable()
|
||||
export class QueryResultGettersFactory {
|
||||
private readonly logger = new Logger(
|
||||
CompositeInputTypeDefinitionFactory.name,
|
||||
);
|
||||
private readonly logger = new Logger(QueryResultGettersFactory.name);
|
||||
private handlers: Map<string, QueryResultGetterHandlerInterface>;
|
||||
|
||||
constructor(private readonly fileService: FileService) {
|
||||
|
||||
+1
-1
@@ -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';
|
||||
|
||||
+6
@@ -0,0 +1,6 @@
|
||||
export enum GqlInputTypeDefinitionKind {
|
||||
Create = 'Create',
|
||||
Update = 'Update',
|
||||
Filter = 'Filter',
|
||||
OrderBy = 'OrderBy',
|
||||
}
|
||||
+5
@@ -0,0 +1,5 @@
|
||||
export enum GqlOperation {
|
||||
Query = 'Query',
|
||||
Mutation = 'Mutation',
|
||||
Subscription = 'Subscription',
|
||||
}
|
||||
+5
@@ -0,0 +1,5 @@
|
||||
export enum ObjectTypeDefinitionKind {
|
||||
Connection = 'Connection',
|
||||
Edge = 'Edge',
|
||||
Plain = '',
|
||||
}
|
||||
-102
@@ -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;
|
||||
}
|
||||
}
|
||||
-94
@@ -1,94 +0,0 @@
|
||||
import { Injectable, Logger } from '@nestjs/common';
|
||||
|
||||
import { type GraphQLFieldConfigMap, GraphQLObjectType } from 'graphql';
|
||||
import { FieldMetadataType } from 'twenty-shared/types';
|
||||
|
||||
import { type WorkspaceBuildSchemaOptions } from 'src/engine/api/graphql/workspace-schema-builder/interfaces/workspace-build-schema-options.interface';
|
||||
import { type CompositeType } from 'src/engine/metadata-modules/field-metadata/interfaces/composite-type.interface';
|
||||
|
||||
import {
|
||||
type ObjectTypeDefinition,
|
||||
ObjectTypeDefinitionKind,
|
||||
} from 'src/engine/api/graphql/workspace-schema-builder/factories/object-type-definition.factory';
|
||||
import { OutputTypeFactory } from 'src/engine/api/graphql/workspace-schema-builder/factories/output-type.factory';
|
||||
import { computeCompositePropertyTarget } from 'src/engine/api/graphql/workspace-schema-builder/utils/compute-composite-property-target.util';
|
||||
import { isMorphOrRelationFieldMetadataType } from 'src/engine/utils/is-morph-or-relation-field-metadata-type.util';
|
||||
import { pascalCase } from 'src/utils/pascal-case';
|
||||
|
||||
@Injectable()
|
||||
export class CompositeObjectTypeDefinitionFactory {
|
||||
private readonly logger = new Logger(
|
||||
CompositeObjectTypeDefinitionFactory.name,
|
||||
);
|
||||
|
||||
constructor(private readonly outputTypeFactory: OutputTypeFactory) {}
|
||||
|
||||
public create(
|
||||
compositeType: CompositeType,
|
||||
options: WorkspaceBuildSchemaOptions,
|
||||
): ObjectTypeDefinition {
|
||||
const name = pascalCase(compositeType.type.toString().toLowerCase());
|
||||
const kind = ObjectTypeDefinitionKind.Plain;
|
||||
|
||||
return {
|
||||
target: compositeType.type.toString(),
|
||||
kind,
|
||||
type: new GraphQLObjectType({
|
||||
name: `${name}${kind.toString()}`,
|
||||
fields: this.generateFields(compositeType, kind, options),
|
||||
}),
|
||||
};
|
||||
}
|
||||
|
||||
private generateFields(
|
||||
compositeType: CompositeType,
|
||||
kind: ObjectTypeDefinitionKind,
|
||||
options: WorkspaceBuildSchemaOptions,
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
): GraphQLFieldConfigMap<any, any> {
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
const fields: GraphQLFieldConfigMap<any, any> = {};
|
||||
|
||||
for (const property of compositeType.properties) {
|
||||
// Relation fields are not supported in composite types
|
||||
if (isMorphOrRelationFieldMetadataType(property.type)) {
|
||||
this.logger.error(
|
||||
'Relation fields are not supported in composite types',
|
||||
{ compositeType, property },
|
||||
);
|
||||
|
||||
throw new Error('Relation fields are not supported in composite types');
|
||||
}
|
||||
|
||||
// Skip hidden fields
|
||||
if (property.hidden === true || property.hidden === 'output') {
|
||||
continue;
|
||||
}
|
||||
|
||||
const target = computeCompositePropertyTarget(
|
||||
compositeType.type,
|
||||
property,
|
||||
);
|
||||
const type = this.outputTypeFactory.create(
|
||||
target,
|
||||
property.type,
|
||||
kind,
|
||||
options,
|
||||
{
|
||||
nullable: !property.isRequired,
|
||||
isArray:
|
||||
property.type === FieldMetadataType.MULTI_SELECT ||
|
||||
property.isArray,
|
||||
},
|
||||
);
|
||||
|
||||
fields[property.name] = {
|
||||
type,
|
||||
description: property.description,
|
||||
};
|
||||
}
|
||||
|
||||
return fields;
|
||||
}
|
||||
}
|
||||
-84
@@ -1,84 +0,0 @@
|
||||
import { Injectable } from '@nestjs/common';
|
||||
|
||||
import { type GraphQLFieldConfigMap, GraphQLObjectType } from 'graphql';
|
||||
|
||||
import { type WorkspaceBuildSchemaOptions } from 'src/engine/api/graphql/workspace-schema-builder/interfaces/workspace-build-schema-options.interface';
|
||||
|
||||
import { AggregationTypeFactory } from 'src/engine/api/graphql/workspace-schema-builder/factories/aggregation-type.factory';
|
||||
import { type ObjectMetadataEntity } from 'src/engine/metadata-modules/object-metadata/object-metadata.entity';
|
||||
import { pascalCase } from 'src/utils/pascal-case';
|
||||
|
||||
import { ConnectionTypeFactory } from './connection-type.factory';
|
||||
import {
|
||||
type ObjectTypeDefinition,
|
||||
ObjectTypeDefinitionKind,
|
||||
} from './object-type-definition.factory';
|
||||
|
||||
export enum ConnectionTypeDefinitionKind {
|
||||
Edge = 'Edge',
|
||||
PageInfo = 'PageInfo',
|
||||
}
|
||||
|
||||
@Injectable()
|
||||
export class ConnectionTypeDefinitionFactory {
|
||||
constructor(
|
||||
private readonly connectionTypeFactory: ConnectionTypeFactory,
|
||||
private readonly aggregationTypeFactory: AggregationTypeFactory,
|
||||
) {}
|
||||
|
||||
public create(
|
||||
objectMetadata: ObjectMetadataEntity,
|
||||
options: WorkspaceBuildSchemaOptions,
|
||||
): ObjectTypeDefinition {
|
||||
const kind = ObjectTypeDefinitionKind.Connection;
|
||||
|
||||
return {
|
||||
target: objectMetadata.id,
|
||||
kind,
|
||||
type: new GraphQLObjectType({
|
||||
name: `${pascalCase(objectMetadata.nameSingular)}${kind.toString()}`,
|
||||
description: objectMetadata.description,
|
||||
fields: () => this.generateFields(objectMetadata, options),
|
||||
}),
|
||||
};
|
||||
}
|
||||
|
||||
private generateFields(
|
||||
objectMetadata: ObjectMetadataEntity,
|
||||
options: WorkspaceBuildSchemaOptions,
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
): GraphQLFieldConfigMap<any, any> {
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
const fields: GraphQLFieldConfigMap<any, any> = {};
|
||||
|
||||
const aggregatedFields = this.aggregationTypeFactory.create(objectMetadata);
|
||||
|
||||
Object.assign(fields, aggregatedFields);
|
||||
|
||||
fields.edges = {
|
||||
type: this.connectionTypeFactory.create(
|
||||
objectMetadata,
|
||||
ConnectionTypeDefinitionKind.Edge,
|
||||
options,
|
||||
{
|
||||
isArray: true,
|
||||
arrayDepth: 1,
|
||||
nullable: false,
|
||||
},
|
||||
),
|
||||
};
|
||||
|
||||
fields.pageInfo = {
|
||||
type: this.connectionTypeFactory.create(
|
||||
objectMetadata,
|
||||
ConnectionTypeDefinitionKind.PageInfo,
|
||||
options,
|
||||
{
|
||||
nullable: false,
|
||||
},
|
||||
),
|
||||
};
|
||||
|
||||
return fields;
|
||||
}
|
||||
}
|
||||
-58
@@ -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);
|
||||
}
|
||||
}
|
||||
-75
@@ -1,75 +0,0 @@
|
||||
import { Injectable } from '@nestjs/common';
|
||||
|
||||
import { type GraphQLFieldConfigMap, GraphQLObjectType } from 'graphql';
|
||||
|
||||
import { type WorkspaceBuildSchemaOptions } from 'src/engine/api/graphql/workspace-schema-builder/interfaces/workspace-build-schema-options.interface';
|
||||
|
||||
import { type ObjectMetadataEntity } from 'src/engine/metadata-modules/object-metadata/object-metadata.entity';
|
||||
import { pascalCase } from 'src/utils/pascal-case';
|
||||
|
||||
import { EdgeTypeFactory } from './edge-type.factory';
|
||||
import {
|
||||
type ObjectTypeDefinition,
|
||||
ObjectTypeDefinitionKind,
|
||||
} from './object-type-definition.factory';
|
||||
|
||||
export enum EdgeTypeDefinitionKind {
|
||||
Node = 'Node',
|
||||
Cursor = 'Cursor',
|
||||
}
|
||||
|
||||
@Injectable()
|
||||
export class EdgeTypeDefinitionFactory {
|
||||
constructor(private readonly edgeTypeFactory: EdgeTypeFactory) {}
|
||||
|
||||
public create(
|
||||
objectMetadata: ObjectMetadataEntity,
|
||||
options: WorkspaceBuildSchemaOptions,
|
||||
): ObjectTypeDefinition {
|
||||
const kind = ObjectTypeDefinitionKind.Edge;
|
||||
|
||||
return {
|
||||
target: objectMetadata.id,
|
||||
kind,
|
||||
type: new GraphQLObjectType({
|
||||
name: `${pascalCase(objectMetadata.nameSingular)}${kind.toString()}`,
|
||||
description: objectMetadata.description,
|
||||
fields: () => this.generateFields(objectMetadata, options),
|
||||
}),
|
||||
};
|
||||
}
|
||||
|
||||
private generateFields(
|
||||
objectMetadata: ObjectMetadataEntity,
|
||||
options: WorkspaceBuildSchemaOptions,
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
): GraphQLFieldConfigMap<any, any> {
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
const fields: GraphQLFieldConfigMap<any, any> = {};
|
||||
|
||||
fields.node = {
|
||||
type: this.edgeTypeFactory.create(
|
||||
objectMetadata,
|
||||
EdgeTypeDefinitionKind.Node,
|
||||
options,
|
||||
{
|
||||
nullable: false,
|
||||
},
|
||||
),
|
||||
};
|
||||
|
||||
fields.cursor = {
|
||||
type: this.edgeTypeFactory.create(
|
||||
objectMetadata,
|
||||
EdgeTypeDefinitionKind.Cursor,
|
||||
options,
|
||||
{
|
||||
nullable: false,
|
||||
},
|
||||
),
|
||||
};
|
||||
|
||||
return fields;
|
||||
}
|
||||
}
|
||||
-58
@@ -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);
|
||||
}
|
||||
}
|
||||
-180
@@ -1,180 +0,0 @@
|
||||
import { Injectable, Logger } from '@nestjs/common';
|
||||
|
||||
import {
|
||||
GraphQLObjectType,
|
||||
type GraphQLFieldConfigArgumentMap,
|
||||
type GraphQLFieldConfigMap,
|
||||
} from 'graphql';
|
||||
import { FieldMetadataType } from 'twenty-shared/types';
|
||||
|
||||
import { type WorkspaceBuildSchemaOptions } from 'src/engine/api/graphql/workspace-schema-builder/interfaces/workspace-build-schema-options.interface';
|
||||
import { RelationType } from 'src/engine/metadata-modules/field-metadata/interfaces/relation-type.interface';
|
||||
|
||||
import { RelationTypeV2Factory } from 'src/engine/api/graphql/workspace-schema-builder/factories/relation-type-v2.factory';
|
||||
import { TypeDefinitionsStorage } from 'src/engine/api/graphql/workspace-schema-builder/storages/type-definitions.storage';
|
||||
import { getResolverArgs } from 'src/engine/api/graphql/workspace-schema-builder/utils/get-resolver-args.util';
|
||||
import { objectContainsRelationField } from 'src/engine/api/graphql/workspace-schema-builder/utils/object-contains-relation-field';
|
||||
import { type ObjectMetadataEntity } from 'src/engine/metadata-modules/object-metadata/object-metadata.entity';
|
||||
import { isFieldMetadataEntityOfType } from 'src/engine/utils/is-field-metadata-of-type.util';
|
||||
|
||||
import { ArgsFactory } from './args.factory';
|
||||
|
||||
export enum ObjectTypeDefinitionKind {
|
||||
Connection = 'Connection',
|
||||
Edge = 'Edge',
|
||||
Plain = '',
|
||||
}
|
||||
|
||||
export interface ObjectTypeDefinition {
|
||||
target: string;
|
||||
kind: ObjectTypeDefinitionKind;
|
||||
type: GraphQLObjectType;
|
||||
}
|
||||
|
||||
@Injectable()
|
||||
export class ExtendObjectTypeDefinitionV2Factory {
|
||||
private readonly logger = new Logger(
|
||||
ExtendObjectTypeDefinitionV2Factory.name,
|
||||
);
|
||||
|
||||
constructor(
|
||||
private readonly relationTypeV2Factory: RelationTypeV2Factory,
|
||||
private readonly argsFactory: ArgsFactory,
|
||||
private readonly typeDefinitionsStorage: TypeDefinitionsStorage,
|
||||
) {}
|
||||
|
||||
public create(
|
||||
objectMetadata: ObjectMetadataEntity,
|
||||
options: WorkspaceBuildSchemaOptions,
|
||||
objectMetadataCollection: ObjectMetadataEntity[],
|
||||
): ObjectTypeDefinition {
|
||||
const kind = ObjectTypeDefinitionKind.Plain;
|
||||
const gqlType = this.typeDefinitionsStorage.getObjectTypeByKey(
|
||||
objectMetadata.id,
|
||||
kind,
|
||||
);
|
||||
const containsRelationField = objectContainsRelationField(objectMetadata);
|
||||
|
||||
if (!gqlType) {
|
||||
this.logger.error(
|
||||
`Could not find a GraphQL type for ${objectMetadata.id.toString()}`,
|
||||
{
|
||||
objectMetadata,
|
||||
options,
|
||||
},
|
||||
);
|
||||
|
||||
throw new Error(
|
||||
`Could not find a GraphQL type for ${objectMetadata.id.toString()}`,
|
||||
);
|
||||
}
|
||||
|
||||
// Security check to avoid extending an object that does not need to be extended
|
||||
if (!containsRelationField) {
|
||||
this.logger.error(
|
||||
`This object does not need to be extended: ${objectMetadata.id.toString()}`,
|
||||
{
|
||||
objectMetadata,
|
||||
options,
|
||||
},
|
||||
);
|
||||
|
||||
throw new Error(
|
||||
`This object does not need to be extended: ${objectMetadata.id.toString()}`,
|
||||
);
|
||||
}
|
||||
|
||||
// Extract current object config to extend it
|
||||
const config = gqlType.toConfig();
|
||||
|
||||
// Recreate the same object type with the new fields
|
||||
return {
|
||||
target: objectMetadata.id,
|
||||
kind,
|
||||
type: new GraphQLObjectType({
|
||||
...config,
|
||||
fields: () => ({
|
||||
...config.fields,
|
||||
...this.generateFields(
|
||||
objectMetadata,
|
||||
options,
|
||||
objectMetadataCollection,
|
||||
),
|
||||
}),
|
||||
}),
|
||||
};
|
||||
}
|
||||
|
||||
private generateFields(
|
||||
objectMetadata: ObjectMetadataEntity,
|
||||
options: WorkspaceBuildSchemaOptions,
|
||||
objectMetadataCollection: ObjectMetadataEntity[],
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
): GraphQLFieldConfigMap<any, any> {
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
const fields: GraphQLFieldConfigMap<any, any> = {};
|
||||
|
||||
for (const fieldMetadata of objectMetadata.fields) {
|
||||
// Ignore non-relation fields as they are already defined
|
||||
const isRelation =
|
||||
isFieldMetadataEntityOfType(
|
||||
fieldMetadata,
|
||||
FieldMetadataType.RELATION,
|
||||
) ||
|
||||
isFieldMetadataEntityOfType(
|
||||
fieldMetadata,
|
||||
FieldMetadataType.MORPH_RELATION,
|
||||
);
|
||||
|
||||
if (!isRelation) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!fieldMetadata.settings) {
|
||||
throw new Error(
|
||||
`Field Metadata of type RELATION or MORPH_RELATION with id ${fieldMetadata.id} has no settings`,
|
||||
);
|
||||
}
|
||||
|
||||
if (!fieldMetadata.relationTargetObjectMetadataId) {
|
||||
throw new Error(
|
||||
`Field Metadata of type RELATION or MORPH_RELATION with id ${fieldMetadata.id} has no relation target object metadata id`,
|
||||
);
|
||||
}
|
||||
|
||||
const relationType = this.relationTypeV2Factory.create(fieldMetadata);
|
||||
let argsType: GraphQLFieldConfigArgumentMap | undefined = undefined;
|
||||
|
||||
if (fieldMetadata.settings.relationType === RelationType.ONE_TO_MANY) {
|
||||
const args = getResolverArgs('findMany');
|
||||
|
||||
argsType = this.argsFactory.create(
|
||||
{
|
||||
args,
|
||||
objectMetadataId: fieldMetadata.relationTargetObjectMetadataId,
|
||||
},
|
||||
options,
|
||||
);
|
||||
}
|
||||
|
||||
const objectMetadataTarget = objectMetadataCollection.find(
|
||||
(objectMetadata) =>
|
||||
objectMetadata.id === fieldMetadata.relationTargetObjectMetadataId,
|
||||
);
|
||||
|
||||
if (!objectMetadataTarget) {
|
||||
throw new Error(
|
||||
`Object Metadata Target not found for Id: ${fieldMetadata.relationTargetObjectMetadataId} on fieldMetadata name: ${fieldMetadata.name}`,
|
||||
);
|
||||
}
|
||||
|
||||
fields[fieldMetadata.name] = {
|
||||
type: relationType,
|
||||
args: argsType,
|
||||
description: fieldMetadata.description,
|
||||
};
|
||||
}
|
||||
|
||||
return fields;
|
||||
}
|
||||
}
|
||||
-46
@@ -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,
|
||||
];
|
||||
-102
@@ -1,102 +0,0 @@
|
||||
import { Inject, Injectable, forwardRef } from '@nestjs/common';
|
||||
|
||||
import { GraphQLInputObjectType } from 'graphql';
|
||||
|
||||
import { type WorkspaceBuildSchemaOptions } from 'src/engine/api/graphql/workspace-schema-builder/interfaces/workspace-build-schema-options.interface';
|
||||
|
||||
import { TypeMapperService } from 'src/engine/api/graphql/workspace-schema-builder/services/type-mapper.service';
|
||||
import { generateFields } from 'src/engine/api/graphql/workspace-schema-builder/utils/generate-fields.util';
|
||||
import { type ObjectMetadataEntity } from 'src/engine/metadata-modules/object-metadata/object-metadata.entity';
|
||||
import { pascalCase } from 'src/utils/pascal-case';
|
||||
|
||||
import { InputTypeFactory } from './input-type.factory';
|
||||
|
||||
export enum InputTypeDefinitionKind {
|
||||
Create = 'Create',
|
||||
Update = 'Update',
|
||||
Filter = 'Filter',
|
||||
OrderBy = 'OrderBy',
|
||||
}
|
||||
|
||||
export interface InputTypeDefinition {
|
||||
target: string;
|
||||
kind: InputTypeDefinitionKind;
|
||||
type: GraphQLInputObjectType;
|
||||
}
|
||||
|
||||
@Injectable()
|
||||
export class InputTypeDefinitionFactory {
|
||||
constructor(
|
||||
@Inject(forwardRef(() => InputTypeFactory))
|
||||
private readonly inputTypeFactory: CircularDep<InputTypeFactory>,
|
||||
private readonly typeMapperService: TypeMapperService,
|
||||
) {}
|
||||
|
||||
public create({
|
||||
objectMetadata,
|
||||
kind,
|
||||
options,
|
||||
}: {
|
||||
objectMetadata: ObjectMetadataEntity;
|
||||
kind: InputTypeDefinitionKind;
|
||||
options: WorkspaceBuildSchemaOptions;
|
||||
}): InputTypeDefinition {
|
||||
// @ts-expect-error legacy noImplicitAny
|
||||
const inputType = new GraphQLInputObjectType({
|
||||
name: `${pascalCase(objectMetadata.nameSingular)}${kind.toString()}Input`,
|
||||
description: objectMetadata.description,
|
||||
// @ts-expect-error legacy noImplicitAny
|
||||
fields: () => {
|
||||
switch (kind) {
|
||||
/**
|
||||
* Filter input type has additional fields for filtering and is self referencing
|
||||
*/
|
||||
case InputTypeDefinitionKind.Filter: {
|
||||
// @ts-expect-error legacy noImplicitAny
|
||||
const andOrType = this.typeMapperService.mapToGqlType(inputType, {
|
||||
isArray: true,
|
||||
arrayDepth: 1,
|
||||
nullable: true,
|
||||
});
|
||||
|
||||
return {
|
||||
...generateFields({
|
||||
objectMetadata,
|
||||
kind,
|
||||
options,
|
||||
typeFactory: this.inputTypeFactory,
|
||||
}),
|
||||
and: {
|
||||
type: andOrType,
|
||||
},
|
||||
or: {
|
||||
type: andOrType,
|
||||
},
|
||||
not: {
|
||||
type: this.typeMapperService.mapToGqlType(inputType, {
|
||||
nullable: true,
|
||||
}),
|
||||
},
|
||||
};
|
||||
}
|
||||
/**
|
||||
* Other input types are generated with fields only
|
||||
*/
|
||||
default:
|
||||
return generateFields({
|
||||
objectMetadata,
|
||||
kind,
|
||||
options,
|
||||
typeFactory: this.inputTypeFactory,
|
||||
});
|
||||
}
|
||||
},
|
||||
});
|
||||
|
||||
return {
|
||||
target: objectMetadata.id,
|
||||
kind,
|
||||
type: inputType,
|
||||
};
|
||||
}
|
||||
}
|
||||
-120
@@ -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 },
|
||||
}),
|
||||
});
|
||||
}
|
||||
}
|
||||
-28
@@ -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,
|
||||
);
|
||||
}
|
||||
}
|
||||
-49
@@ -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,
|
||||
}),
|
||||
}),
|
||||
};
|
||||
}
|
||||
}
|
||||
-22
@@ -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)];
|
||||
}
|
||||
}
|
||||
-54
@@ -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);
|
||||
}
|
||||
}
|
||||
-28
@@ -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,
|
||||
);
|
||||
}
|
||||
}
|
||||
-58
@@ -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;
|
||||
}
|
||||
}
|
||||
+23
-21
@@ -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,
|
||||
});
|
||||
+15
-28
@@ -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,
|
||||
},
|
||||
);
|
||||
|
||||
+13
-24
@@ -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,
|
||||
},
|
||||
);
|
||||
|
||||
+93
@@ -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,
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
+33
@@ -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,
|
||||
);
|
||||
}
|
||||
}
|
||||
+102
@@ -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;
|
||||
}
|
||||
}
|
||||
+155
@@ -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;
|
||||
}
|
||||
}
|
||||
+119
@@ -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;
|
||||
}
|
||||
}
|
||||
+165
@@ -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,
|
||||
}),
|
||||
},
|
||||
};
|
||||
}
|
||||
}
|
||||
+32
@@ -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,
|
||||
);
|
||||
}
|
||||
}
|
||||
+103
@@ -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;
|
||||
}
|
||||
}
|
||||
+160
@@ -0,0 +1,160 @@
|
||||
import { Injectable, Logger } from '@nestjs/common';
|
||||
|
||||
import {
|
||||
GraphQLEnumType,
|
||||
GraphQLInputFieldConfigMap,
|
||||
GraphQLInputObjectType,
|
||||
GraphQLInputType,
|
||||
GraphQLList,
|
||||
GraphQLScalarType,
|
||||
isEnumType,
|
||||
isInputObjectType,
|
||||
} from 'graphql';
|
||||
import { isDefined } from 'twenty-shared/utils';
|
||||
|
||||
import { GqlInputTypeDefinitionKind } from 'src/engine/api/graphql/workspace-schema-builder/enums/gql-input-type-definition-kind.enum';
|
||||
import { RelationFieldMetadataGqlInputTypeGenerator } from 'src/engine/api/graphql/workspace-schema-builder/graphql-type-generators/input-types/relation-field-metadata-gql-type.generator';
|
||||
import { TypeMapperService } from 'src/engine/api/graphql/workspace-schema-builder/services/type-mapper.service';
|
||||
import { GqlTypesStorage } from 'src/engine/api/graphql/workspace-schema-builder/storages/gql-types.storage';
|
||||
import { computeFieldInputTypeOptions } from 'src/engine/api/graphql/workspace-schema-builder/utils/compute-field-input-type-options.util';
|
||||
import { computeCompositeFieldInputTypeKey } from 'src/engine/api/graphql/workspace-schema-builder/utils/compute-stored-gql-type-key-utils/compute-composite-field-input-type-key.util';
|
||||
import { computeEnumFieldGqlTypeKey } from 'src/engine/api/graphql/workspace-schema-builder/utils/compute-stored-gql-type-key-utils/compute-enum-field-gql-type-key.util';
|
||||
import { computeObjectMetadataInputTypeKey } from 'src/engine/api/graphql/workspace-schema-builder/utils/compute-stored-gql-type-key-utils/compute-object-metadata-input-type.util';
|
||||
import { isFieldMetadataRelationOrMorphRelation } from 'src/engine/api/graphql/workspace-schema-builder/utils/is-field-metadata-relation-or-morph-relation.utils';
|
||||
import { isCompositeFieldMetadataType } from 'src/engine/metadata-modules/field-metadata/utils/is-composite-field-metadata-type.util';
|
||||
import { isEnumFieldMetadataType } from 'src/engine/metadata-modules/field-metadata/utils/is-enum-field-metadata-type.util';
|
||||
import { ObjectMetadataEntity } from 'src/engine/metadata-modules/object-metadata/object-metadata.entity';
|
||||
import { pascalCase } from 'src/utils/pascal-case';
|
||||
|
||||
@Injectable()
|
||||
export class ObjectMetadataOrderByGqlInputTypeGenerator {
|
||||
private readonly logger = new Logger(
|
||||
ObjectMetadataOrderByGqlInputTypeGenerator.name,
|
||||
);
|
||||
constructor(
|
||||
private readonly gqlTypesStorage: GqlTypesStorage,
|
||||
private readonly relationFieldMetadataGqlInputTypeGenerator: RelationFieldMetadataGqlInputTypeGenerator,
|
||||
private readonly typeMapperService: TypeMapperService,
|
||||
) {}
|
||||
|
||||
public buildAndStore(objectMetadata: ObjectMetadataEntity) {
|
||||
const inputType = new GraphQLInputObjectType({
|
||||
name: `${pascalCase(objectMetadata.nameSingular)}${GqlInputTypeDefinitionKind.OrderBy.toString()}Input`,
|
||||
description: objectMetadata.description,
|
||||
fields: () => this.generateFields(objectMetadata),
|
||||
}) as GraphQLInputObjectType;
|
||||
|
||||
const key = computeObjectMetadataInputTypeKey(
|
||||
objectMetadata.nameSingular,
|
||||
GqlInputTypeDefinitionKind.OrderBy,
|
||||
);
|
||||
|
||||
this.gqlTypesStorage.addGqlType(key, inputType);
|
||||
}
|
||||
|
||||
private generateFields(
|
||||
objectMetadata: ObjectMetadataEntity,
|
||||
): GraphQLInputFieldConfigMap {
|
||||
const allGeneratedFields: GraphQLInputFieldConfigMap = {};
|
||||
|
||||
for (const fieldMetadata of objectMetadata.fields) {
|
||||
fieldMetadata.isNullable = true;
|
||||
|
||||
const typeOptions = computeFieldInputTypeOptions(
|
||||
fieldMetadata,
|
||||
GqlInputTypeDefinitionKind.OrderBy,
|
||||
);
|
||||
|
||||
let generatedFields;
|
||||
|
||||
if (isFieldMetadataRelationOrMorphRelation(fieldMetadata)) {
|
||||
generatedFields = {
|
||||
...this.relationFieldMetadataGqlInputTypeGenerator.generateSimpleRelationFieldCreateOrUpdateInputType(
|
||||
{
|
||||
fieldMetadata,
|
||||
typeOptions,
|
||||
},
|
||||
),
|
||||
...this.relationFieldMetadataGqlInputTypeGenerator.generateConnectRelationFieldInputType(
|
||||
{
|
||||
fieldMetadata,
|
||||
typeOptions,
|
||||
},
|
||||
),
|
||||
};
|
||||
} else {
|
||||
let type:
|
||||
| GraphQLInputObjectType
|
||||
| GraphQLEnumType
|
||||
| GraphQLScalarType
|
||||
| GraphQLList<GraphQLInputType>
|
||||
| undefined;
|
||||
|
||||
if (isEnumFieldMetadataType(fieldMetadata.type)) {
|
||||
const key = computeEnumFieldGqlTypeKey(
|
||||
objectMetadata.nameSingular,
|
||||
fieldMetadata.name,
|
||||
);
|
||||
|
||||
const enumType = this.gqlTypesStorage.getGqlTypeByKey(key);
|
||||
|
||||
if (!isDefined(enumType) || !isEnumType(enumType)) {
|
||||
const message = `Could not find a GraphQL input type for ${fieldMetadata.type} field metadata`;
|
||||
|
||||
this.logger.error(message, {
|
||||
type,
|
||||
typeOptions,
|
||||
});
|
||||
throw new Error(message);
|
||||
}
|
||||
|
||||
type = enumType;
|
||||
} else if (isCompositeFieldMetadataType(fieldMetadata.type)) {
|
||||
const key = computeCompositeFieldInputTypeKey(
|
||||
fieldMetadata.type,
|
||||
GqlInputTypeDefinitionKind.OrderBy,
|
||||
);
|
||||
|
||||
const compositeType = this.gqlTypesStorage.getGqlTypeByKey(key);
|
||||
|
||||
if (!isDefined(compositeType) || !isInputObjectType(compositeType)) {
|
||||
const message = `Could not find a GraphQL input type for ${fieldMetadata.type} field metadata`;
|
||||
|
||||
this.logger.error(message, {
|
||||
type,
|
||||
typeOptions,
|
||||
});
|
||||
throw new Error(message);
|
||||
}
|
||||
|
||||
type = compositeType;
|
||||
} else {
|
||||
const orderByType = this.typeMapperService.mapToOrderByType(
|
||||
fieldMetadata.type,
|
||||
);
|
||||
|
||||
if (!isDefined(orderByType)) {
|
||||
const message = `Could not find a GraphQL input type for ${fieldMetadata.type} field metadata`;
|
||||
|
||||
this.logger.error(message, {
|
||||
type,
|
||||
typeOptions,
|
||||
});
|
||||
throw new Error(message);
|
||||
}
|
||||
|
||||
type = orderByType;
|
||||
}
|
||||
generatedFields = {
|
||||
[fieldMetadata.name]: {
|
||||
type,
|
||||
description: fieldMetadata.description,
|
||||
},
|
||||
};
|
||||
}
|
||||
Object.assign(allGeneratedFields, generatedFields);
|
||||
}
|
||||
|
||||
return allGeneratedFields;
|
||||
}
|
||||
}
|
||||
+13
-26
@@ -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] = {
|
||||
+193
@@ -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,
|
||||
},
|
||||
};
|
||||
}
|
||||
}
|
||||
+103
@@ -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;
|
||||
}
|
||||
}
|
||||
+157
@@ -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;
|
||||
}
|
||||
}
|
||||
+2
-2
@@ -9,8 +9,8 @@ import { type ObjectMetadataEntity } from 'src/engine/metadata-modules/object-me
|
||||
type AggregationGraphQLType = Pick<AggregationField, 'type' | 'description'>;
|
||||
|
||||
@Injectable()
|
||||
export class AggregationTypeFactory {
|
||||
public create(
|
||||
export class AggregationObjectTypeGenerator {
|
||||
public generate(
|
||||
objectMetadata: ObjectMetadataEntity,
|
||||
): Record<string, AggregationGraphQLType> {
|
||||
const availableAggregations = getAvailableAggregationsFromObjectFields(
|
||||
+102
@@ -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;
|
||||
}
|
||||
}
|
||||
+90
@@ -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;
|
||||
}
|
||||
}
|
||||
+83
@@ -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;
|
||||
}
|
||||
}
|
||||
+167
@@ -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;
|
||||
}
|
||||
}
|
||||
+200
@@ -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;
|
||||
}
|
||||
}
|
||||
+62
@@ -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,
|
||||
},
|
||||
};
|
||||
}
|
||||
}
|
||||
+18
@@ -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,
|
||||
]);
|
||||
}
|
||||
}
|
||||
+37
@@ -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;
|
||||
}
|
||||
}
|
||||
+35
@@ -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;
|
||||
}
|
||||
}
|
||||
+43
-48
@@ -1,50 +1,44 @@
|
||||
import { Injectable, Logger } from '@nestjs/common';
|
||||
|
||||
import { type GraphQLFieldConfigMap, GraphQLObjectType } from 'graphql';
|
||||
import { GraphQLObjectType, isObjectType } from 'graphql';
|
||||
import { isDefined } from 'twenty-shared/utils';
|
||||
|
||||
import { type WorkspaceResolverBuilderMethodNames } from 'src/engine/api/graphql/workspace-resolver-builder/interfaces/workspace-resolvers-builder.interface';
|
||||
import { type WorkspaceBuildSchemaOptions } from 'src/engine/api/graphql/workspace-schema-builder/interfaces/workspace-build-schema-options.interface';
|
||||
|
||||
import { WorkspaceResolverBuilderService } from 'src/engine/api/graphql/workspace-resolver-builder/workspace-resolver-builder.service';
|
||||
import { GqlOperation } from 'src/engine/api/graphql/workspace-schema-builder/enums/gql-operation.enum';
|
||||
import { ObjectTypeDefinitionKind } from 'src/engine/api/graphql/workspace-schema-builder/enums/object-type-definition-kind.enum';
|
||||
import { ArgsTypeGenerator } from 'src/engine/api/graphql/workspace-schema-builder/graphql-type-generators/args-type/args-type.generator';
|
||||
import { TypeMapperService } from 'src/engine/api/graphql/workspace-schema-builder/services/type-mapper.service';
|
||||
import { TypeDefinitionsStorage } from 'src/engine/api/graphql/workspace-schema-builder/storages/type-definitions.storage';
|
||||
import { GqlTypesStorage } from 'src/engine/api/graphql/workspace-schema-builder/storages/gql-types.storage';
|
||||
import { GraphQLRootTypeFieldConfigMap } from 'src/engine/api/graphql/workspace-schema-builder/types/graphql-field-config-map.types';
|
||||
import { computeObjectMetadataObjectTypeKey } from 'src/engine/api/graphql/workspace-schema-builder/utils/compute-stored-gql-type-key-utils/compute-object-metadata-object-type-key.util';
|
||||
import { getResolverArgs } from 'src/engine/api/graphql/workspace-schema-builder/utils/get-resolver-args.util';
|
||||
import { type ObjectMetadataEntity } from 'src/engine/metadata-modules/object-metadata/object-metadata.entity';
|
||||
import { getResolverName } from 'src/engine/utils/get-resolver-name.util';
|
||||
|
||||
import { ArgsFactory } from './args.factory';
|
||||
import { ObjectTypeDefinitionKind } from './object-type-definition.factory';
|
||||
|
||||
export enum ObjectTypeName {
|
||||
Query = 'Query',
|
||||
Mutation = 'Mutation',
|
||||
Subscription = 'Subscription',
|
||||
}
|
||||
|
||||
@Injectable()
|
||||
export class RootTypeFactory {
|
||||
private readonly logger = new Logger(RootTypeFactory.name);
|
||||
export class RootTypeGenerator {
|
||||
private readonly logger = new Logger(RootTypeGenerator.name);
|
||||
|
||||
constructor(
|
||||
private readonly typeDefinitionsStorage: TypeDefinitionsStorage,
|
||||
private readonly gqlTypesStorage: GqlTypesStorage,
|
||||
private readonly typeMapperService: TypeMapperService,
|
||||
private readonly argsFactory: ArgsFactory,
|
||||
private readonly argsTypeGenerator: ArgsTypeGenerator,
|
||||
private readonly workspaceResolverBuilderService: WorkspaceResolverBuilderService,
|
||||
) {}
|
||||
|
||||
create(
|
||||
buildAndStore(
|
||||
objectMetadataCollection: ObjectMetadataEntity[],
|
||||
workspaceResolverMethodNames: WorkspaceResolverBuilderMethodNames[],
|
||||
objectTypeName: ObjectTypeName,
|
||||
options: WorkspaceBuildSchemaOptions,
|
||||
): GraphQLObjectType {
|
||||
objectTypeName: GqlOperation,
|
||||
) {
|
||||
if (workspaceResolverMethodNames.length === 0) {
|
||||
this.logger.error(
|
||||
`No resolver methods were found for ${objectTypeName.toString()}`,
|
||||
{
|
||||
workspaceResolverMethodNames,
|
||||
objectTypeName,
|
||||
options,
|
||||
},
|
||||
);
|
||||
|
||||
@@ -53,23 +47,23 @@ export class RootTypeFactory {
|
||||
);
|
||||
}
|
||||
|
||||
return new GraphQLObjectType({
|
||||
name: objectTypeName.toString(),
|
||||
fields: this.generateFields(
|
||||
objectMetadataCollection,
|
||||
workspaceResolverMethodNames,
|
||||
options,
|
||||
),
|
||||
});
|
||||
this.gqlTypesStorage.addGqlType(
|
||||
objectTypeName,
|
||||
new GraphQLObjectType({
|
||||
name: objectTypeName.toString(),
|
||||
fields: this.generateFields(
|
||||
objectMetadataCollection,
|
||||
workspaceResolverMethodNames,
|
||||
),
|
||||
}),
|
||||
);
|
||||
}
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
private generateFields<T = any, U = any>(
|
||||
private generateFields(
|
||||
objectMetadataCollection: ObjectMetadataEntity[],
|
||||
workspaceResolverMethodNames: WorkspaceResolverBuilderMethodNames[],
|
||||
options: WorkspaceBuildSchemaOptions,
|
||||
): GraphQLFieldConfigMap<T, U> {
|
||||
const fieldConfigMap: GraphQLFieldConfigMap<T, U> = {};
|
||||
): GraphQLRootTypeFieldConfigMap {
|
||||
const fieldConfigMap: GraphQLRootTypeFieldConfigMap = {};
|
||||
|
||||
for (const objectMetadata of objectMetadataCollection) {
|
||||
for (const methodName of workspaceResolverMethodNames) {
|
||||
@@ -81,25 +75,23 @@ export class RootTypeFactory {
|
||||
) {
|
||||
const name = getResolverName(objectMetadata, methodName);
|
||||
const args = getResolverArgs(methodName);
|
||||
const objectType = this.typeDefinitionsStorage.getObjectTypeByKey(
|
||||
objectMetadata.id,
|
||||
const key = computeObjectMetadataObjectTypeKey(
|
||||
objectMetadata.nameSingular,
|
||||
this.getObjectTypeDefinitionKindByMethodName(methodName),
|
||||
);
|
||||
const argsType = this.argsFactory.create(
|
||||
{
|
||||
args,
|
||||
objectMetadataId: objectMetadata.id,
|
||||
},
|
||||
options,
|
||||
);
|
||||
const objectType = this.gqlTypesStorage.getGqlTypeByKey(key);
|
||||
|
||||
if (!objectType) {
|
||||
const argsType = this.argsTypeGenerator.generate({
|
||||
args,
|
||||
objectMetadataSingularName: objectMetadata.nameSingular,
|
||||
});
|
||||
|
||||
if (!isDefined(objectType) || !isObjectType(objectType)) {
|
||||
this.logger.error(
|
||||
`Could not find a GraphQL type for ${objectMetadata.id} for method ${methodName}`,
|
||||
{
|
||||
objectMetadata,
|
||||
methodName,
|
||||
options,
|
||||
},
|
||||
);
|
||||
|
||||
@@ -117,9 +109,12 @@ export class RootTypeFactory {
|
||||
'destroyMany',
|
||||
];
|
||||
|
||||
const outputType = this.typeMapperService.mapToGqlType(objectType, {
|
||||
isArray: allowedMethodNames.includes(methodName),
|
||||
});
|
||||
const outputType = this.typeMapperService.applyTypeOptions(
|
||||
objectType,
|
||||
{
|
||||
isArray: allowedMethodNames.includes(methodName),
|
||||
},
|
||||
);
|
||||
|
||||
fieldConfigMap[name] = {
|
||||
type: outputType,
|
||||
+55
@@ -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,
|
||||
];
|
||||
+8
-6
@@ -1,19 +1,21 @@
|
||||
import { type GraphQLScalarType } from 'graphql';
|
||||
import { type FieldMetadataType } from 'twenty-shared/types';
|
||||
|
||||
import { type InputTypeDefinitionKind } from 'src/engine/api/graphql/workspace-schema-builder/factories/input-type-definition.factory';
|
||||
import { type FieldMetadataDefaultValue } from 'src/engine/metadata-modules/field-metadata/interfaces/field-metadata-default-value.interface';
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
export interface ArgMetadata<T = any> {
|
||||
kind?: InputTypeDefinitionKind;
|
||||
import { type GqlInputTypeDefinitionKind } from 'src/engine/api/graphql/workspace-schema-builder/enums/gql-input-type-definition-kind.enum';
|
||||
|
||||
export interface ArgMetadata {
|
||||
kind?: GqlInputTypeDefinitionKind;
|
||||
type?: GraphQLScalarType;
|
||||
isNullable?: boolean;
|
||||
isArray?: boolean;
|
||||
defaultValue?: T;
|
||||
defaultValue?: FieldMetadataDefaultValue<FieldMetadataType>;
|
||||
}
|
||||
|
||||
export interface ArgsMetadata {
|
||||
args: {
|
||||
[key: string]: ArgMetadata;
|
||||
};
|
||||
objectMetadataId: string;
|
||||
objectMetadataSingularName: string;
|
||||
}
|
||||
|
||||
-16
@@ -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;
|
||||
}
|
||||
+14
-14
@@ -16,6 +16,7 @@ import {
|
||||
import GraphQLJSON from 'graphql-type-json';
|
||||
import { FieldMetadataType } from 'twenty-shared/types';
|
||||
|
||||
import { FieldMetadataDefaultValue } from 'src/engine/metadata-modules/field-metadata/interfaces/field-metadata-default-value.interface';
|
||||
import {
|
||||
type FieldMetadataSettings,
|
||||
NumberDataType,
|
||||
@@ -45,12 +46,11 @@ import { PositionScalarType } from 'src/engine/api/graphql/workspace-schema-buil
|
||||
import { getNumberFilterType } from 'src/engine/api/graphql/workspace-schema-builder/utils/get-number-filter-type.util';
|
||||
import { getNumberScalarType } from 'src/engine/api/graphql/workspace-schema-builder/utils/get-number-scalar-type.util';
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
export interface TypeOptions<T = any> {
|
||||
export interface TypeOptions {
|
||||
nullable?: boolean;
|
||||
isArray?: boolean;
|
||||
arrayDepth?: number;
|
||||
defaultValue?: T;
|
||||
defaultValue?: FieldMetadataDefaultValue<FieldMetadataType>;
|
||||
settings?: FieldMetadataSettings<FieldMetadataType>;
|
||||
isIdField?: boolean;
|
||||
isRelationConnectField?: boolean;
|
||||
@@ -62,11 +62,10 @@ const StringArrayScalarType = new GraphQLList(GraphQLString);
|
||||
export class TypeMapperService {
|
||||
mapToScalarType(
|
||||
fieldMetadataType: FieldMetadataType,
|
||||
settings?: FieldMetadataSettings<FieldMetadataType>,
|
||||
isIdField?: boolean,
|
||||
typeOptions?: TypeOptions,
|
||||
): GraphQLScalarType | undefined {
|
||||
if (
|
||||
isIdField ||
|
||||
typeOptions?.isIdField ||
|
||||
fieldMetadataType === FieldMetadataType.RELATION ||
|
||||
fieldMetadataType === FieldMetadataType.MORPH_RELATION
|
||||
) {
|
||||
@@ -81,8 +80,9 @@ export class TypeMapperService {
|
||||
[
|
||||
FieldMetadataType.NUMBER,
|
||||
getNumberScalarType(
|
||||
(settings as FieldMetadataSettings<FieldMetadataType.NUMBER>)
|
||||
?.dataType ?? NumberDataType.FLOAT,
|
||||
(
|
||||
typeOptions?.settings as FieldMetadataSettings<FieldMetadataType.NUMBER>
|
||||
)?.dataType ?? NumberDataType.FLOAT,
|
||||
),
|
||||
],
|
||||
[FieldMetadataType.NUMERIC, BigFloatScalarType],
|
||||
@@ -101,11 +101,10 @@ export class TypeMapperService {
|
||||
|
||||
mapToFilterType(
|
||||
fieldMetadataType: FieldMetadataType,
|
||||
settings?: FieldMetadataSettings<FieldMetadataType>,
|
||||
isIdField?: boolean,
|
||||
typeOptions?: TypeOptions,
|
||||
): GraphQLInputObjectType | GraphQLScalarType | undefined {
|
||||
if (
|
||||
isIdField ||
|
||||
typeOptions?.isIdField ||
|
||||
fieldMetadataType === FieldMetadataType.RELATION ||
|
||||
fieldMetadataType === FieldMetadataType.MORPH_RELATION
|
||||
) {
|
||||
@@ -124,8 +123,9 @@ export class TypeMapperService {
|
||||
[
|
||||
FieldMetadataType.NUMBER,
|
||||
getNumberFilterType(
|
||||
(settings as FieldMetadataSettings<FieldMetadataType.NUMBER>)
|
||||
?.dataType,
|
||||
(
|
||||
typeOptions?.settings as FieldMetadataSettings<FieldMetadataType.NUMBER>
|
||||
)?.dataType,
|
||||
),
|
||||
],
|
||||
[FieldMetadataType.NUMERIC, BigFloatFilterType],
|
||||
@@ -168,7 +168,7 @@ export class TypeMapperService {
|
||||
return typeOrderByMapping.get(fieldMetadataType);
|
||||
}
|
||||
|
||||
mapToGqlType<T extends GraphQLType = GraphQLType>(
|
||||
applyTypeOptions<T extends GraphQLType = GraphQLType>(
|
||||
typeRef: T,
|
||||
options: TypeOptions,
|
||||
): T {
|
||||
|
||||
+38
@@ -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);
|
||||
}
|
||||
}
|
||||
-113
@@ -1,113 +0,0 @@
|
||||
import { Injectable, Scope } from '@nestjs/common';
|
||||
|
||||
import {
|
||||
type GraphQLEnumType,
|
||||
type GraphQLInputObjectType,
|
||||
type GraphQLObjectType,
|
||||
} from 'graphql';
|
||||
import { type FieldMetadataType } from 'twenty-shared/types';
|
||||
|
||||
import { type EnumTypeDefinition } from 'src/engine/api/graphql/workspace-schema-builder/factories/enum-type-definition.factory';
|
||||
import {
|
||||
type InputTypeDefinition,
|
||||
type InputTypeDefinitionKind,
|
||||
} from 'src/engine/api/graphql/workspace-schema-builder/factories/input-type-definition.factory';
|
||||
import {
|
||||
type ObjectTypeDefinition,
|
||||
type ObjectTypeDefinitionKind,
|
||||
} from 'src/engine/api/graphql/workspace-schema-builder/factories/object-type-definition.factory';
|
||||
|
||||
export type GqlInputType = InputTypeDefinition | EnumTypeDefinition;
|
||||
|
||||
export type GqlOutputType = ObjectTypeDefinition | EnumTypeDefinition;
|
||||
|
||||
// Must be scoped on REQUEST level, because we need to recreate it for each workspaces
|
||||
// TODO: Implement properly durable by workspace
|
||||
@Injectable({ scope: Scope.REQUEST, durable: true })
|
||||
export class TypeDefinitionsStorage {
|
||||
private readonly enumTypeDefinitions = new Map<string, EnumTypeDefinition>();
|
||||
private readonly objectTypeDefinitions = new Map<
|
||||
string,
|
||||
ObjectTypeDefinition
|
||||
>();
|
||||
private readonly inputTypeDefinitions = new Map<
|
||||
string,
|
||||
InputTypeDefinition
|
||||
>();
|
||||
|
||||
addEnumTypes(enumDefs: EnumTypeDefinition[]) {
|
||||
enumDefs.forEach((item) => this.enumTypeDefinitions.set(item.target, item));
|
||||
}
|
||||
|
||||
addObjectTypes(objectDefs: ObjectTypeDefinition[]) {
|
||||
objectDefs.forEach((item) =>
|
||||
this.objectTypeDefinitions.set(
|
||||
this.generateCompositeKey(item.target, item.kind),
|
||||
item,
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
getObjectTypeByKey(
|
||||
target: string,
|
||||
kind: ObjectTypeDefinitionKind,
|
||||
): GraphQLObjectType | undefined {
|
||||
return this.objectTypeDefinitions.get(
|
||||
this.generateCompositeKey(target, kind),
|
||||
)?.type;
|
||||
}
|
||||
|
||||
getAllObjectTypeDefinitions(): ObjectTypeDefinition[] {
|
||||
return Array.from(this.objectTypeDefinitions.values());
|
||||
}
|
||||
|
||||
addInputTypes(inputDefs: InputTypeDefinition[]) {
|
||||
inputDefs.forEach((item) =>
|
||||
this.inputTypeDefinitions.set(
|
||||
this.generateCompositeKey(item.target, item.kind),
|
||||
item,
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
getInputTypeByKey(
|
||||
target: string,
|
||||
kind: InputTypeDefinitionKind,
|
||||
): GraphQLInputObjectType | GraphQLEnumType | undefined {
|
||||
const key = this.generateCompositeKey(target, kind);
|
||||
let definition: GqlInputType | undefined;
|
||||
|
||||
definition ??= this.inputTypeDefinitions.get(key);
|
||||
definition ??= this.enumTypeDefinitions.get(target);
|
||||
|
||||
return definition?.type;
|
||||
}
|
||||
|
||||
getOutputTypeByKey(
|
||||
target: string,
|
||||
kind: ObjectTypeDefinitionKind,
|
||||
): GraphQLObjectType | GraphQLEnumType | undefined {
|
||||
const key = this.generateCompositeKey(target, kind);
|
||||
let definition: GqlOutputType | undefined;
|
||||
|
||||
definition ??= this.objectTypeDefinitions.get(key);
|
||||
definition ??= this.enumTypeDefinitions.get(target);
|
||||
|
||||
return definition?.type;
|
||||
}
|
||||
|
||||
getEnumTypeByKey(target: string): GraphQLEnumType | undefined {
|
||||
return this.enumTypeDefinitions.get(target)?.type;
|
||||
}
|
||||
|
||||
getAllInputTypeDefinitions(): InputTypeDefinition[] {
|
||||
return Array.from(this.inputTypeDefinitions.values());
|
||||
}
|
||||
|
||||
private generateCompositeKey(
|
||||
target: string | FieldMetadataType,
|
||||
kind: ObjectTypeDefinitionKind | InputTypeDefinitionKind,
|
||||
): string {
|
||||
return `${target.toString()}_${kind.toString()}`;
|
||||
}
|
||||
}
|
||||
-308
@@ -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);
|
||||
}
|
||||
}
|
||||
+22
@@ -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;
|
||||
}
|
||||
>;
|
||||
+9
-9
@@ -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 },
|
||||
},
|
||||
};
|
||||
|
||||
|
||||
+13
@@ -0,0 +1,13 @@
|
||||
import { FieldMetadataType } from 'twenty-shared/types';
|
||||
|
||||
import { type CompositeProperty } from 'src/engine/metadata-modules/field-metadata/interfaces/composite-type.interface';
|
||||
|
||||
export function computeCompositeFieldTypeOptions(
|
||||
property: CompositeProperty<FieldMetadataType>,
|
||||
) {
|
||||
return {
|
||||
nullable: !property.isRequired,
|
||||
isArray:
|
||||
property.type === FieldMetadataType.MULTI_SELECT || property.isArray,
|
||||
};
|
||||
}
|
||||
+20
@@ -0,0 +1,20 @@
|
||||
import { FieldMetadataType } from 'twenty-shared/types';
|
||||
|
||||
import { GqlInputTypeDefinitionKind } from 'src/engine/api/graphql/workspace-schema-builder/enums/gql-input-type-definition-kind.enum';
|
||||
import { type TypeOptions } from 'src/engine/api/graphql/workspace-schema-builder/services/type-mapper.service';
|
||||
import { type FieldMetadataEntity } from 'src/engine/metadata-modules/field-metadata/field-metadata.entity';
|
||||
|
||||
export const computeFieldInputTypeOptions = <T extends FieldMetadataType>(
|
||||
fieldMetadata: FieldMetadataEntity<T>,
|
||||
kind: GqlInputTypeDefinitionKind,
|
||||
): TypeOptions => {
|
||||
return {
|
||||
nullable: fieldMetadata.isNullable ?? undefined,
|
||||
defaultValue: fieldMetadata.defaultValue,
|
||||
isArray:
|
||||
kind !== GqlInputTypeDefinitionKind.Filter &&
|
||||
fieldMetadata.type === FieldMetadataType.MULTI_SELECT,
|
||||
settings: fieldMetadata.settings,
|
||||
isIdField: fieldMetadata.name === 'id',
|
||||
};
|
||||
};
|
||||
+9
@@ -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',
|
||||
);
|
||||
});
|
||||
});
|
||||
+15
@@ -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');
|
||||
});
|
||||
});
|
||||
+13
@@ -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');
|
||||
});
|
||||
});
|
||||
+7
@@ -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');
|
||||
});
|
||||
});
|
||||
+13
@@ -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');
|
||||
});
|
||||
});
|
||||
+13
@@ -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');
|
||||
});
|
||||
});
|
||||
+11
@@ -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');
|
||||
});
|
||||
});
|
||||
+10
@@ -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`;
|
||||
};
|
||||
+13
@@ -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`;
|
||||
};
|
||||
+10
@@ -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()}`;
|
||||
};
|
||||
+10
@@ -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`;
|
||||
};
|
||||
+9
@@ -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`;
|
||||
};
|
||||
+9
@@ -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()}`;
|
||||
};
|
||||
+3
@@ -0,0 +1,3 @@
|
||||
export const computeRelationConnectInputTypeKey = (
|
||||
objectMetadataNameId: string,
|
||||
) => `${objectMetadataNameId}-ConnectInput`;
|
||||
+25
@@ -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 },
|
||||
}),
|
||||
});
|
||||
};
|
||||
-237
@@ -1,237 +0,0 @@
|
||||
import {
|
||||
type GraphQLFieldConfigMap,
|
||||
type GraphQLInputFieldConfigMap,
|
||||
type GraphQLInputType,
|
||||
type GraphQLOutputType,
|
||||
} from 'graphql';
|
||||
import { FieldMetadataType } from 'twenty-shared/types';
|
||||
import { isDefined } from 'twenty-shared/utils';
|
||||
|
||||
import { type WorkspaceBuildSchemaOptions } from 'src/engine/api/graphql/workspace-schema-builder/interfaces/workspace-build-schema-options.interface';
|
||||
import { RelationType } from 'src/engine/metadata-modules/field-metadata/interfaces/relation-type.interface';
|
||||
|
||||
import { InputTypeDefinitionKind } from 'src/engine/api/graphql/workspace-schema-builder/factories/input-type-definition.factory';
|
||||
import { type ObjectTypeDefinitionKind } from 'src/engine/api/graphql/workspace-schema-builder/factories/object-type-definition.factory';
|
||||
import { formatRelationConnectInputTarget } from 'src/engine/api/graphql/workspace-schema-builder/factories/relation-connect-input-type-definition.factory';
|
||||
import { extractGraphQLRelationFieldNames } from 'src/engine/api/graphql/workspace-schema-builder/utils/extract-graphql-relation-field-names.util';
|
||||
import { isFieldMetadataRelationOrMorphRelation } from 'src/engine/api/graphql/workspace-schema-builder/utils/is-field-metadata-relation-or-morph-relation.utils';
|
||||
import { type FieldMetadataEntity } from 'src/engine/metadata-modules/field-metadata/field-metadata.entity';
|
||||
import { isCompositeFieldMetadataType } from 'src/engine/metadata-modules/field-metadata/utils/is-composite-field-metadata-type.util';
|
||||
import { type ObjectMetadataEntity } from 'src/engine/metadata-modules/object-metadata/object-metadata.entity';
|
||||
|
||||
type TypeFactory<T extends InputTypeDefinitionKind | ObjectTypeDefinitionKind> =
|
||||
{
|
||||
create: (
|
||||
target: string,
|
||||
fieldType: FieldMetadataType,
|
||||
kind: T,
|
||||
options: WorkspaceBuildSchemaOptions,
|
||||
additionalOptions: {
|
||||
nullable?: boolean;
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
defaultValue?: any;
|
||||
isArray: boolean;
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
settings: any;
|
||||
isIdField: boolean;
|
||||
isRelationConnectField?: boolean;
|
||||
},
|
||||
) => T extends InputTypeDefinitionKind
|
||||
? GraphQLInputType
|
||||
: GraphQLOutputType;
|
||||
};
|
||||
|
||||
export const generateFields = <
|
||||
T extends InputTypeDefinitionKind | ObjectTypeDefinitionKind,
|
||||
>({
|
||||
objectMetadata,
|
||||
kind,
|
||||
options,
|
||||
typeFactory,
|
||||
}: {
|
||||
objectMetadata: ObjectMetadataEntity;
|
||||
kind: T;
|
||||
options: WorkspaceBuildSchemaOptions;
|
||||
typeFactory: TypeFactory<T>;
|
||||
}): T extends InputTypeDefinitionKind
|
||||
? GraphQLInputFieldConfigMap
|
||||
: // eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
GraphQLFieldConfigMap<any, any> => {
|
||||
const allGeneratedFields = {};
|
||||
|
||||
for (const fieldMetadata of objectMetadata.fields) {
|
||||
let generatedField;
|
||||
|
||||
const isRelation = isFieldMetadataRelationOrMorphRelation(fieldMetadata);
|
||||
|
||||
if (isRelation) {
|
||||
generatedField = generateRelationField({
|
||||
fieldMetadata: fieldMetadata as FieldMetadataEntity<
|
||||
FieldMetadataType.RELATION | FieldMetadataType.MORPH_RELATION
|
||||
>,
|
||||
kind,
|
||||
options,
|
||||
typeFactory,
|
||||
});
|
||||
} else {
|
||||
generatedField = generateField({
|
||||
fieldMetadata,
|
||||
kind,
|
||||
options,
|
||||
typeFactory,
|
||||
});
|
||||
}
|
||||
|
||||
Object.assign(allGeneratedFields, generatedField);
|
||||
}
|
||||
|
||||
return allGeneratedFields;
|
||||
};
|
||||
|
||||
const getTarget = <T extends FieldMetadataType>(
|
||||
fieldMetadata: FieldMetadataEntity<T>,
|
||||
) => {
|
||||
return isCompositeFieldMetadataType(fieldMetadata.type)
|
||||
? fieldMetadata.type.toString()
|
||||
: fieldMetadata.id;
|
||||
};
|
||||
|
||||
const getTypeFactoryOptions = <T extends FieldMetadataType>(
|
||||
fieldMetadata: FieldMetadataEntity<T>,
|
||||
kind: InputTypeDefinitionKind | ObjectTypeDefinitionKind,
|
||||
) => {
|
||||
return isInputTypeDefinitionKind(kind)
|
||||
? {
|
||||
nullable: fieldMetadata.isNullable ?? undefined,
|
||||
defaultValue: fieldMetadata.defaultValue,
|
||||
isArray:
|
||||
kind !== InputTypeDefinitionKind.Filter &&
|
||||
fieldMetadata.type === FieldMetadataType.MULTI_SELECT,
|
||||
settings: fieldMetadata.settings,
|
||||
isIdField: fieldMetadata.name === 'id',
|
||||
}
|
||||
: {
|
||||
nullable: fieldMetadata.isNullable ?? undefined,
|
||||
isArray: fieldMetadata.type === FieldMetadataType.MULTI_SELECT,
|
||||
settings: fieldMetadata.settings,
|
||||
// Scalar type is already defined in the entity itself.
|
||||
isIdField: false,
|
||||
};
|
||||
};
|
||||
|
||||
const generateField = <
|
||||
T extends InputTypeDefinitionKind | ObjectTypeDefinitionKind,
|
||||
>({
|
||||
fieldMetadata,
|
||||
kind,
|
||||
options,
|
||||
typeFactory,
|
||||
}: {
|
||||
fieldMetadata: FieldMetadataEntity;
|
||||
kind: T;
|
||||
options: WorkspaceBuildSchemaOptions;
|
||||
typeFactory: TypeFactory<T>;
|
||||
}) => {
|
||||
const target = getTarget(fieldMetadata);
|
||||
|
||||
const typeFactoryOptions = getTypeFactoryOptions(fieldMetadata, kind);
|
||||
|
||||
const type = typeFactory.create(
|
||||
target,
|
||||
fieldMetadata.type,
|
||||
kind,
|
||||
options,
|
||||
typeFactoryOptions,
|
||||
);
|
||||
|
||||
return {
|
||||
[fieldMetadata.name]: {
|
||||
type,
|
||||
description: fieldMetadata.description,
|
||||
},
|
||||
};
|
||||
};
|
||||
|
||||
const generateRelationField = <
|
||||
T extends InputTypeDefinitionKind | ObjectTypeDefinitionKind,
|
||||
>({
|
||||
fieldMetadata,
|
||||
kind,
|
||||
options,
|
||||
typeFactory,
|
||||
}: {
|
||||
fieldMetadata: FieldMetadataEntity<
|
||||
FieldMetadataType.RELATION | FieldMetadataType.MORPH_RELATION
|
||||
>;
|
||||
kind: T;
|
||||
options: WorkspaceBuildSchemaOptions;
|
||||
typeFactory: TypeFactory<T>;
|
||||
}) => {
|
||||
const relationFields = {};
|
||||
|
||||
if (fieldMetadata.settings?.relationType === RelationType.ONE_TO_MANY) {
|
||||
return relationFields;
|
||||
}
|
||||
|
||||
const { joinColumnName, fieldMetadataName } =
|
||||
extractGraphQLRelationFieldNames(fieldMetadata);
|
||||
|
||||
const target = getTarget(fieldMetadata);
|
||||
const typeFactoryOptions = getTypeFactoryOptions(fieldMetadata, kind);
|
||||
|
||||
let type = typeFactory.create(
|
||||
target,
|
||||
fieldMetadata.type,
|
||||
kind,
|
||||
options,
|
||||
typeFactoryOptions,
|
||||
);
|
||||
|
||||
// @ts-expect-error legacy noImplicitAny
|
||||
relationFields[joinColumnName] = {
|
||||
type,
|
||||
description: fieldMetadata.description,
|
||||
};
|
||||
|
||||
//TODO : temporary - continue ej/1278 branch (https://github.com/twentyhq/core-team-issues/issues/1278 issue) before removing this
|
||||
if (fieldMetadata.type === FieldMetadataType.MORPH_RELATION)
|
||||
return relationFields;
|
||||
|
||||
if (
|
||||
[InputTypeDefinitionKind.Create, InputTypeDefinitionKind.Update].includes(
|
||||
kind as InputTypeDefinitionKind,
|
||||
) &&
|
||||
isDefined(fieldMetadata.relationTargetObjectMetadataId)
|
||||
) {
|
||||
type = typeFactory.create(
|
||||
formatRelationConnectInputTarget(
|
||||
fieldMetadata.relationTargetObjectMetadataId,
|
||||
),
|
||||
fieldMetadata.type,
|
||||
kind,
|
||||
options,
|
||||
{
|
||||
...typeFactoryOptions,
|
||||
isRelationConnectField: true,
|
||||
},
|
||||
);
|
||||
|
||||
// todo @guillim
|
||||
// @ts-expect-error legacy noImplicitAny
|
||||
relationFields[fieldMetadataName] = {
|
||||
type: type,
|
||||
description: fieldMetadata.description,
|
||||
};
|
||||
}
|
||||
|
||||
return relationFields;
|
||||
};
|
||||
|
||||
// Type guard
|
||||
const isInputTypeDefinitionKind = (
|
||||
kind: InputTypeDefinitionKind | ObjectTypeDefinitionKind,
|
||||
): kind is InputTypeDefinitionKind => {
|
||||
return Object.values(InputTypeDefinitionKind).includes(
|
||||
kind as InputTypeDefinitionKind,
|
||||
);
|
||||
};
|
||||
+12
-12
@@ -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,
|
||||
},
|
||||
};
|
||||
|
||||
+16
-35
@@ -2,50 +2,31 @@ import { Injectable } from '@nestjs/common';
|
||||
|
||||
import { GraphQLSchema } from 'graphql';
|
||||
|
||||
import { type WorkspaceResolverBuilderMethods } from 'src/engine/api/graphql/workspace-resolver-builder/interfaces/workspace-resolvers-builder.interface';
|
||||
|
||||
import { GqlTypeGenerator } from 'src/engine/api/graphql/workspace-schema-builder/graphql-type-generators/gql-type.generator';
|
||||
import { OrphanedTypesGenerator } from 'src/engine/api/graphql/workspace-schema-builder/graphql-type-generators/orphaned-types.generator';
|
||||
import { MutationTypeGenerator } from 'src/engine/api/graphql/workspace-schema-builder/graphql-type-generators/root-types/mutation-type.generator';
|
||||
import { QueryTypeGenerator } from 'src/engine/api/graphql/workspace-schema-builder/graphql-type-generators/root-types/query-type.generator';
|
||||
import { type ObjectMetadataEntity } from 'src/engine/metadata-modules/object-metadata/object-metadata.entity';
|
||||
|
||||
import { TypeDefinitionsGenerator } from './type-definitions.generator';
|
||||
|
||||
import { MutationTypeFactory } from './factories/mutation-type.factory';
|
||||
import { OrphanedTypesFactory } from './factories/orphaned-types.factory';
|
||||
import { QueryTypeFactory } from './factories/query-type.factory';
|
||||
import { type WorkspaceBuildSchemaOptions } from './interfaces/workspace-build-schema-options.interface';
|
||||
|
||||
@Injectable()
|
||||
export class WorkspaceGraphQLSchemaFactory {
|
||||
export class WorkspaceGraphQLSchemaGenerator {
|
||||
constructor(
|
||||
private readonly typeDefinitionsGenerator: TypeDefinitionsGenerator,
|
||||
private readonly queryTypeFactory: QueryTypeFactory,
|
||||
private readonly mutationTypeFactory: MutationTypeFactory,
|
||||
private readonly orphanedTypesFactory: OrphanedTypesFactory,
|
||||
private readonly gqlTypeGenerator: GqlTypeGenerator,
|
||||
private readonly queryTypeGenerator: QueryTypeGenerator,
|
||||
private readonly mutationTypeGenerator: MutationTypeGenerator,
|
||||
private readonly orphanedTypesGenerator: OrphanedTypesGenerator,
|
||||
) {}
|
||||
|
||||
async create(
|
||||
generateSchema(
|
||||
objectMetadataCollection: ObjectMetadataEntity[],
|
||||
workspaceResolverBuilderMethods: WorkspaceResolverBuilderMethods,
|
||||
options: WorkspaceBuildSchemaOptions = {},
|
||||
): Promise<GraphQLSchema> {
|
||||
// Generate type definitions
|
||||
await this.typeDefinitionsGenerator.generate(
|
||||
objectMetadataCollection,
|
||||
options,
|
||||
);
|
||||
): GraphQLSchema {
|
||||
this.gqlTypeGenerator.buildAndStore(objectMetadataCollection);
|
||||
|
||||
// Generate schema
|
||||
// Assemble schema
|
||||
const schema = new GraphQLSchema({
|
||||
query: this.queryTypeFactory.create(
|
||||
objectMetadataCollection,
|
||||
[...workspaceResolverBuilderMethods.queries],
|
||||
options,
|
||||
),
|
||||
mutation: this.mutationTypeFactory.create(
|
||||
objectMetadataCollection,
|
||||
[...workspaceResolverBuilderMethods.mutations],
|
||||
options,
|
||||
),
|
||||
types: this.orphanedTypesFactory.create(),
|
||||
query: this.queryTypeGenerator.fetchQueryType(),
|
||||
mutation: this.mutationTypeGenerator.fetchMutationType(),
|
||||
types: this.orphanedTypesGenerator.fetchOrphanedTypes(),
|
||||
});
|
||||
|
||||
return schema;
|
||||
|
||||
+9
-11
@@ -1,16 +1,14 @@
|
||||
import { Module } from '@nestjs/common';
|
||||
|
||||
import { WorkspaceResolverBuilderModule } from 'src/engine/api/graphql/workspace-resolver-builder/workspace-resolver-builder.module';
|
||||
import { GqlTypeGenerator } from 'src/engine/api/graphql/workspace-schema-builder/graphql-type-generators/gql-type.generator';
|
||||
import { workspaceSchemaBuilderTypeGenerators } from 'src/engine/api/graphql/workspace-schema-builder/graphql-type-generators/type-generators';
|
||||
import { GqlTypesStorage } from 'src/engine/api/graphql/workspace-schema-builder/storages/gql-types.storage';
|
||||
import { WorkspaceGraphQLSchemaGenerator } from 'src/engine/api/graphql/workspace-schema-builder/workspace-graphql-schema.factory';
|
||||
import { FeatureFlagModule } from 'src/engine/core-modules/feature-flag/feature-flag.module';
|
||||
import { ObjectMetadataModule } from 'src/engine/metadata-modules/object-metadata/object-metadata.module';
|
||||
|
||||
import { TypeDefinitionsGenerator } from './type-definitions.generator';
|
||||
import { WorkspaceGraphQLSchemaFactory } from './workspace-graphql-schema.factory';
|
||||
|
||||
import { workspaceSchemaBuilderFactories } from './factories/factories';
|
||||
import { TypeMapperService } from './services/type-mapper.service';
|
||||
import { TypeDefinitionsStorage } from './storages/type-definitions.storage';
|
||||
|
||||
@Module({
|
||||
imports: [
|
||||
ObjectMetadataModule,
|
||||
@@ -18,12 +16,12 @@ import { TypeDefinitionsStorage } from './storages/type-definitions.storage';
|
||||
FeatureFlagModule,
|
||||
],
|
||||
providers: [
|
||||
TypeDefinitionsStorage,
|
||||
GqlTypesStorage,
|
||||
TypeMapperService,
|
||||
...workspaceSchemaBuilderFactories,
|
||||
TypeDefinitionsGenerator,
|
||||
WorkspaceGraphQLSchemaFactory,
|
||||
...workspaceSchemaBuilderTypeGenerators,
|
||||
GqlTypeGenerator,
|
||||
WorkspaceGraphQLSchemaGenerator,
|
||||
],
|
||||
exports: [WorkspaceGraphQLSchemaFactory],
|
||||
exports: [WorkspaceGraphQLSchemaGenerator],
|
||||
})
|
||||
export class WorkspaceSchemaBuilderModule {}
|
||||
|
||||
@@ -8,7 +8,7 @@ import { isDefined } from 'twenty-shared/utils';
|
||||
import { ScalarsExplorerService } from 'src/engine/api/graphql/services/scalars-explorer.service';
|
||||
import { workspaceResolverBuilderMethodNames } from 'src/engine/api/graphql/workspace-resolver-builder/factories/factories';
|
||||
import { WorkspaceResolverFactory } from 'src/engine/api/graphql/workspace-resolver-builder/workspace-resolver.factory';
|
||||
import { WorkspaceGraphQLSchemaFactory } from 'src/engine/api/graphql/workspace-schema-builder/workspace-graphql-schema.factory';
|
||||
import { WorkspaceGraphQLSchemaGenerator } from 'src/engine/api/graphql/workspace-schema-builder/workspace-graphql-schema.factory';
|
||||
import {
|
||||
AuthException,
|
||||
AuthExceptionCode,
|
||||
@@ -30,7 +30,7 @@ export class WorkspaceSchemaFactory {
|
||||
constructor(
|
||||
private readonly dataSourceService: DataSourceService,
|
||||
private readonly scalarsExplorerService: ScalarsExplorerService,
|
||||
private readonly workspaceGraphQLSchemaFactory: WorkspaceGraphQLSchemaFactory,
|
||||
private readonly workspaceGraphQLSchemaGenerator: WorkspaceGraphQLSchemaGenerator,
|
||||
private readonly workspaceResolverFactory: WorkspaceResolverFactory,
|
||||
private readonly workspaceCacheStorageService: WorkspaceCacheStorageService,
|
||||
private readonly workspaceMetadataCacheService: WorkspaceMetadataCacheService,
|
||||
@@ -106,9 +106,8 @@ export class WorkspaceSchemaFactory {
|
||||
// If typeDefs are not cached, generate them
|
||||
if (!typeDefs || !usedScalarNames) {
|
||||
const autoGeneratedSchema =
|
||||
await this.workspaceGraphQLSchemaFactory.create(
|
||||
this.workspaceGraphQLSchemaGenerator.generateSchema(
|
||||
objectMetadataCollection,
|
||||
workspaceResolverBuilderMethodNames,
|
||||
);
|
||||
|
||||
usedScalarNames =
|
||||
|
||||
Reference in New Issue
Block a user