feat: new relation schema generation (#9882)

Fix https://github.com/twentyhq/core-team-issues/issues/295

Based on the feature-flag `IsNewRelationEnabled` the schema will be
marked as outdated and regenerated, this will cause an error on the
front-end on the first request on the following ones schema will be well
generated ans request will work.
This commit is contained in:
Jérémy M
2025-01-29 10:33:17 +01:00
committed by GitHub
parent f74bb5a60b
commit fbb67d74c8
18 changed files with 361 additions and 24 deletions

View File

@ -4,6 +4,7 @@ import { ScalarsExplorerService } from 'src/engine/api/graphql/services/scalars-
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 { WorkspaceSchemaFactory } from 'src/engine/api/graphql/workspace-schema.factory';
import { FeatureFlagService } from 'src/engine/core-modules/feature-flag/services/feature-flag.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';
@ -44,6 +45,10 @@ describe('WorkspaceSchemaFactory', () => {
provide: WorkspaceMetadataCacheService,
useValue: {},
},
{
provide: FeatureFlagService,
useValue: {},
},
],
}).compile();

View File

@ -3,6 +3,7 @@ import { Module } from '@nestjs/common';
import { ScalarsExplorerService } from 'src/engine/api/graphql/services/scalars-explorer.service';
import { WorkspaceResolverBuilderModule } from 'src/engine/api/graphql/workspace-resolver-builder/workspace-resolver-builder.module';
import { WorkspaceSchemaBuilderModule } from 'src/engine/api/graphql/workspace-schema-builder/workspace-schema-builder.module';
import { FeatureFlagModule } from 'src/engine/core-modules/feature-flag/feature-flag.module';
import { MetadataEngineModule } from 'src/engine/metadata-modules/metadata-engine.module';
import { WorkspaceMetadataCacheModule } from 'src/engine/metadata-modules/workspace-metadata-cache/workspace-metadata-cache.module';
import { WorkspaceCacheStorageModule } from 'src/engine/workspace-cache-storage/workspace-cache-storage.module';
@ -16,6 +17,7 @@ import { WorkspaceSchemaFactory } from './workspace-schema.factory';
WorkspaceResolverBuilderModule,
WorkspaceCacheStorageModule,
WorkspaceMetadataCacheModule,
FeatureFlagModule,
],
providers: [WorkspaceSchemaFactory, ScalarsExplorerService],
exports: [WorkspaceSchemaFactory],

View File

@ -20,4 +20,5 @@ export enum GraphqlQueryRunnerExceptionCode {
INVALID_ARGS_FIRST = 'INVALID_ARGS_FIRST',
INVALID_ARGS_LAST = 'INVALID_ARGS_LAST',
METADATA_CACHE_VERSION_NOT_FOUND = 'METADATA_CACHE_VERSION_NOT_FOUND',
METADATA_CACHE_FEATURE_FLAG_RECOMPUTATION_REQUIRED = 'METADATA_CACHE_FEATURE_FLAG_RECOMPUTATION_REQUIRED',
}

View File

@ -0,0 +1,150 @@
import { Injectable, Logger } from '@nestjs/common';
import {
GraphQLFieldConfigArgumentMap,
GraphQLFieldConfigMap,
GraphQLObjectType,
} from 'graphql';
import { WorkspaceBuildSchemaOptions } from 'src/engine/api/graphql/workspace-schema-builder/interfaces/workspace-build-schema-optionts.interface';
import { ObjectMetadataInterface } from 'src/engine/metadata-modules/field-metadata/interfaces/object-metadata.interface';
import { RelationType } from 'src/engine/metadata-modules/field-metadata/interfaces/relation-type.interface';
import { 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 { isRelationFieldMetadata } from 'src/engine/utils/is-relation-field-metadata.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: ObjectMetadataInterface,
options: WorkspaceBuildSchemaOptions,
): 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),
}),
}),
};
}
private generateFields(
objectMetadata: ObjectMetadataInterface,
options: WorkspaceBuildSchemaOptions,
): GraphQLFieldConfigMap<any, any> {
const fields: GraphQLFieldConfigMap<any, any> = {};
for (const fieldMetadata of objectMetadata.fields) {
// Ignore non-relation fields as they are already defined
if (!isRelationFieldMetadata(fieldMetadata)) {
continue;
}
if (!fieldMetadata.settings) {
throw new Error(
`Field Metadata of type RELATION with id ${fieldMetadata.id} has no settings`,
);
}
if (!fieldMetadata.relationTargetObjectMetadataId) {
throw new Error(
`Field Metadata of type 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,
);
}
fields[fieldMetadata.name] = {
type: relationType,
args: argsType,
description: fieldMetadata.description,
};
}
return fields;
}
}

View File

@ -10,17 +10,17 @@ import { WorkspaceBuildSchemaOptions } from 'src/engine/api/graphql/workspace-sc
import { ObjectMetadataInterface } from 'src/engine/metadata-modules/field-metadata/interfaces/object-metadata.interface';
import { TypeDefinitionsStorage } from 'src/engine/api/graphql/workspace-schema-builder/storages/type-definitions.storage';
import { objectContainsRelationField } from 'src/engine/api/graphql/workspace-schema-builder/utils/object-contains-relation-field';
import { getResolverArgs } from 'src/engine/api/graphql/workspace-schema-builder/utils/get-resolver-args.util';
import { isRelationFieldMetadataType } from 'src/engine/utils/is-relation-field-metadata-type.util';
import { objectContainsRelationField } from 'src/engine/api/graphql/workspace-schema-builder/utils/object-contains-relation-field';
import { RelationMetadataType } from 'src/engine/metadata-modules/relation-metadata/relation-metadata.entity';
import {
RelationDirection,
deduceRelationDirection,
} from 'src/engine/utils/deduce-relation-direction.util';
import { RelationMetadataType } from 'src/engine/metadata-modules/relation-metadata/relation-metadata.entity';
import { isRelationFieldMetadataType } from 'src/engine/utils/is-relation-field-metadata-type.util';
import { RelationTypeFactory } from './relation-type.factory';
import { ArgsFactory } from './args.factory';
import { RelationTypeFactory } from './relation-type.factory';
export enum ObjectTypeDefinitionKind {
Connection = 'Connection',

View File

@ -3,6 +3,8 @@ import { CompositeEnumTypeDefinitionFactory } from 'src/engine/api/graphql/works
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 { 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';
@ -31,7 +33,9 @@ export const workspaceSchemaBuilderFactories = [
EnumTypeDefinitionFactory,
CompositeEnumTypeDefinitionFactory,
RelationTypeFactory,
RelationTypeV2Factory,
ExtendObjectTypeDefinitionFactory,
ExtendObjectTypeDefinitionV2Factory,
ConnectionTypeFactory,
ConnectionTypeDefinitionFactory,
EdgeTypeFactory,

View File

@ -0,0 +1,56 @@
import { Injectable, Logger } from '@nestjs/common';
import { GraphQLOutputType } from 'graphql';
import { FieldMetadataType } from 'twenty-shared';
import { FieldMetadataInterface } from 'src/engine/metadata-modules/field-metadata/interfaces/field-metadata.interface';
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 { 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: FieldMetadataInterface<FieldMetadataType.RELATION>,
): GraphQLOutputType {
if (!fieldMetadata.settings) {
throw new Error(
`Field Metadata of type RELATION with id ${fieldMetadata.id} has no settings`,
);
}
if (!fieldMetadata.relationTargetObjectMetadataId) {
throw new Error(
`Field Metadata of type 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;
}
}

View File

@ -5,8 +5,8 @@ import { GraphQLOutputType } from 'graphql';
import { FieldMetadataInterface } from 'src/engine/metadata-modules/field-metadata/interfaces/field-metadata.interface';
import { RelationMetadataInterface } from 'src/engine/metadata-modules/field-metadata/interfaces/relation-metadata.interface';
import { RelationMetadataType } from 'src/engine/metadata-modules/relation-metadata/relation-metadata.entity';
import { TypeDefinitionsStorage } from 'src/engine/api/graphql/workspace-schema-builder/storages/type-definitions.storage';
import { RelationMetadataType } from 'src/engine/metadata-modules/relation-metadata/relation-metadata.entity';
import { RelationDirection } from 'src/engine/utils/deduce-relation-direction.util';
import { ObjectTypeDefinitionKind } from './object-type-definition.factory';
@ -24,13 +24,13 @@ export class RelationTypeFactory {
relationMetadata: RelationMetadataInterface,
relationDirection: RelationDirection,
): GraphQLOutputType {
let relationQqlType: GraphQLOutputType | undefined = undefined;
let relationGqlType: GraphQLOutputType | undefined = undefined;
if (
relationDirection === RelationDirection.FROM &&
relationMetadata.relationType === RelationMetadataType.ONE_TO_MANY
) {
relationQqlType = this.typeDefinitionsStorage.getObjectTypeByKey(
relationGqlType = this.typeDefinitionsStorage.getObjectTypeByKey(
relationMetadata.toObjectMetadataId,
ObjectTypeDefinitionKind.Connection,
);
@ -40,13 +40,13 @@ export class RelationTypeFactory {
? relationMetadata.toObjectMetadataId
: relationMetadata.fromObjectMetadataId;
relationQqlType = this.typeDefinitionsStorage.getObjectTypeByKey(
relationGqlType = this.typeDefinitionsStorage.getObjectTypeByKey(
relationObjectId,
ObjectTypeDefinitionKind.Plain,
);
}
if (!relationQqlType) {
if (!relationGqlType) {
this.logger.error(
`Could not find a relation type for ${fieldMetadata.id}`,
{
@ -57,6 +57,6 @@ export class RelationTypeFactory {
throw new Error(`Could not find a relation type for ${fieldMetadata.id}`);
}
return relationQqlType;
return relationGqlType;
}
}

View File

@ -1,5 +1,7 @@
import { Injectable, Logger } from '@nestjs/common';
import chalk from 'chalk';
import { CompositeType } from 'src/engine/metadata-modules/field-metadata/interfaces/composite-type.interface';
import { ObjectMetadataInterface } from 'src/engine/metadata-modules/field-metadata/interfaces/object-metadata.interface';
@ -7,6 +9,9 @@ import { CompositeEnumTypeDefinitionFactory } from 'src/engine/api/graphql/works
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 { FeatureFlagKey } from 'src/engine/core-modules/feature-flag/enums/feature-flag-key.enum';
import { FeatureFlagService } from 'src/engine/core-modules/feature-flag/services/feature-flag.service';
import { compositeTypeDefinitions } from 'src/engine/metadata-modules/field-metadata/composite-types';
import { ConnectionTypeDefinitionFactory } from './factories/connection-type-definition.factory';
@ -39,6 +44,8 @@ export class TypeDefinitionsGenerator {
private readonly edgeTypeDefinitionFactory: EdgeTypeDefinitionFactory,
private readonly connectionTypeDefinitionFactory: ConnectionTypeDefinitionFactory,
private readonly extendObjectTypeDefinitionFactory: ExtendObjectTypeDefinitionFactory,
private readonly extendObjectTypeDefinitionV2Factory: ExtendObjectTypeDefinitionV2Factory,
private readonly featureFlagService: FeatureFlagService,
) {}
generate(
@ -256,18 +263,48 @@ export class TypeDefinitionsGenerator {
this.typeDefinitionsStorage.addEnumTypes(enumTypeDefs);
}
private generateExtendedObjectTypeDefs(
private async generateExtendedObjectTypeDefs(
objectMetadataCollection: ObjectMetadataInterface[],
options: WorkspaceBuildSchemaOptions,
) {
// Generate extended object type defs only for objects that contain composite fields
const objectMetadataCollectionWithCompositeFields =
objectMetadataCollection.filter(objectContainsRelationField);
const objectTypeDefs = objectMetadataCollectionWithCompositeFields.map(
(objectMetadata) =>
this.extendObjectTypeDefinitionFactory.create(objectMetadata, options),
const workspaceId =
objectMetadataCollectionWithCompositeFields[0]?.workspaceId;
if (!workspaceId) {
throw new Error('Workspace ID not found');
}
const isNewRelationEnabled = await this.featureFlagService.isFeatureEnabled(
FeatureFlagKey.IsNewRelationEnabled,
workspaceId,
);
this.typeDefinitionsStorage.addObjectTypes(objectTypeDefs);
if (!isNewRelationEnabled) {
const objectTypeDefs = objectMetadataCollectionWithCompositeFields.map(
(objectMetadata) =>
this.extendObjectTypeDefinitionFactory.create(
objectMetadata,
options,
),
);
this.typeDefinitionsStorage.addObjectTypes(objectTypeDefs);
} else {
this.logger.log(
chalk.green('Extend object type definition with new relation fields'),
);
const objectTypeDefsV2 = objectMetadataCollectionWithCompositeFields.map(
(objectMetadata) =>
this.extendObjectTypeDefinitionV2Factory.create(
objectMetadata,
options,
),
);
this.typeDefinitionsStorage.addObjectTypes(objectTypeDefsV2);
}
}
}

View File

@ -1,16 +1,17 @@
import { Module } from '@nestjs/common';
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 { TypeDefinitionsStorage } from './storages/type-definitions.storage';
import { TypeMapperService } from './services/type-mapper.service';
import { TypeDefinitionsStorage } from './storages/type-definitions.storage';
@Module({
imports: [ObjectMetadataModule],
imports: [ObjectMetadataModule, FeatureFlagModule],
providers: [
TypeDefinitionsStorage,
TypeMapperService,

View File

@ -1,6 +1,7 @@
import { Injectable } from '@nestjs/common';
import { makeExecutableSchema } from '@graphql-tools/schema';
import chalk from 'chalk';
import { GraphQLSchema, printSchema } from 'graphql';
import { gql } from 'graphql-tag';
@ -13,9 +14,12 @@ import { workspaceResolverBuilderMethodNames } from 'src/engine/api/graphql/work
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 { AuthContext } from 'src/engine/core-modules/auth/types/auth-context.type';
import { FeatureFlagKey } from 'src/engine/core-modules/feature-flag/enums/feature-flag-key.enum';
import { FeatureFlagService } from 'src/engine/core-modules/feature-flag/services/feature-flag.service';
import { DataSourceService } from 'src/engine/metadata-modules/data-source/data-source.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';
@Injectable()
export class WorkspaceSchemaFactory {
constructor(
@ -25,6 +29,7 @@ export class WorkspaceSchemaFactory {
private readonly workspaceResolverFactory: WorkspaceResolverFactory,
private readonly workspaceCacheStorageService: WorkspaceCacheStorageService,
private readonly workspaceMetadataCacheService: WorkspaceMetadataCacheService,
private readonly featureFlagService: FeatureFlagService,
) {}
async createGraphQLSchema(authContext: AuthContext): Promise<GraphQLSchema> {
@ -32,6 +37,22 @@ export class WorkspaceSchemaFactory {
return new GraphQLSchema({});
}
const cachedIsNewRelationEnabled =
await this.workspaceCacheStorageService.getIsNewRelationEnabled(
authContext.workspace.id,
);
const isNewRelationEnabled = await this.featureFlagService.isFeatureEnabled(
FeatureFlagKey.IsNewRelationEnabled,
authContext.workspace.id,
);
if (isNewRelationEnabled) {
console.log(
chalk.yellow('🚧 New relation schema generation is enabled 🚧'),
);
}
const dataSourcesMetadata =
await this.dataSourceService.getDataSourcesMetadataFromWorkspaceId(
authContext.workspace.id,
@ -50,12 +71,41 @@ export class WorkspaceSchemaFactory {
await this.workspaceMetadataCacheService.recomputeMetadataCache({
workspaceId: authContext.workspace.id,
});
throw new GraphqlQueryRunnerException(
'Metadata cache version not found',
GraphqlQueryRunnerExceptionCode.METADATA_CACHE_VERSION_NOT_FOUND,
);
}
// TODO: remove this after the feature flag is droped
if (
(isNewRelationEnabled && cachedIsNewRelationEnabled === undefined) ||
(isNewRelationEnabled !== cachedIsNewRelationEnabled &&
cachedIsNewRelationEnabled !== undefined)
) {
console.log(
chalk.yellow('Recomputing due to new relation feature flag'),
{
isNewRelationEnabled,
},
);
await this.workspaceCacheStorageService.setIsNewRelationEnabled(
authContext.workspace.id,
isNewRelationEnabled,
);
await this.workspaceMetadataCacheService.recomputeMetadataCache({
workspaceId: authContext.workspace.id,
});
throw new GraphqlQueryRunnerException(
'Metadata cache recomputation required due to relation feature flag change',
GraphqlQueryRunnerExceptionCode.METADATA_CACHE_FEATURE_FLAG_RECOMPUTATION_REQUIRED,
);
}
const objectMetadataMaps =
await this.workspaceCacheStorageService.getObjectMetadataMaps(
authContext.workspace.id,

View File

@ -10,6 +10,7 @@ const mockObjectMetadata: ObjectMetadataInterface = {
labelPlural: 'Objects',
description: 'Test object metadata',
targetTableName: 'test_table',
workspaceId: '1',
fromRelations: [],
toRelations: [],
fields: [],

View File

@ -6,6 +6,7 @@ import { RelationMetadataInterface } from './relation-metadata.interface';
export interface ObjectMetadataInterface {
id: string;
standardId?: string | null;
workspaceId: string;
nameSingular: string;
namePlural: string;
labelSingular: string;

View File

@ -8,7 +8,7 @@ import {
FieldMetadataExceptionCode,
} from 'src/engine/metadata-modules/field-metadata/field-metadata.exception';
import { ObjectMetadataEntity } from 'src/engine/metadata-modules/object-metadata/object-metadata.entity';
import { cleanObjectMetadata } from 'src/engine/metadata-modules/utils/clean-object-metadata.util';
import { removeFieldMapsFromObjectMetadata } from 'src/engine/metadata-modules/utils/remove-field-maps-from-object-metadata.util';
import { WorkspaceCacheStorageService } from 'src/engine/workspace-cache-storage/workspace-cache-storage.service';
@Injectable()
@ -95,11 +95,11 @@ export class FieldMetadataRelationService {
}
return {
sourceObjectMetadata: cleanObjectMetadata(
sourceObjectMetadata: removeFieldMapsFromObjectMetadata(
sourceObjectMetadata,
) as ObjectMetadataEntity,
sourceFieldMetadata: sourceFieldMetadata as FieldMetadataEntity,
targetObjectMetadata: cleanObjectMetadata(
targetObjectMetadata: removeFieldMapsFromObjectMetadata(
targetObjectMetadata,
) as ObjectMetadataEntity,
targetFieldMetadata: targetFieldMetadata as FieldMetadataEntity,

View File

@ -1,8 +1,6 @@
import { Injectable } from '@nestjs/common';
import { InjectRepository } from '@nestjs/typeorm';
import console from 'console';
import { i18n } from '@lingui/core';
import { Query, QueryOptions } from '@ptc-org/nestjs-query-core';
import { TypeOrmQueryService } from '@ptc-org/nestjs-query-typeorm';

View File

@ -4,7 +4,7 @@ import { ObjectMetadataInterface } from 'src/engine/metadata-modules/field-metad
import { ObjectMetadataItemWithFieldMaps } from 'src/engine/metadata-modules/types/object-metadata-item-with-field-maps';
export const cleanObjectMetadata = (
export const removeFieldMapsFromObjectMetadata = (
objectMetadata: ObjectMetadataItemWithFieldMaps,
): ObjectMetadataInterface =>
omit(objectMetadata, ['fieldsById', 'fieldsByName']);

View File

@ -0,0 +1,9 @@
import { FieldMetadataType } from 'twenty-shared';
import { FieldMetadataInterface } from 'src/engine/metadata-modules/field-metadata/interfaces/field-metadata.interface';
export const isRelationFieldMetadata = (
fieldMetadata: FieldMetadataInterface<'default' | FieldMetadataType.RELATION>,
): fieldMetadata is FieldMetadataInterface<FieldMetadataType.RELATION> => {
return fieldMetadata.type === FieldMetadataType.RELATION;
};

View File

@ -5,6 +5,7 @@ import { EntitySchemaOptions } from 'typeorm';
import { InjectCacheStorage } from 'src/engine/core-modules/cache-storage/decorators/cache-storage.decorator';
import { CacheStorageService } from 'src/engine/core-modules/cache-storage/services/cache-storage.service';
import { CacheStorageNamespace } from 'src/engine/core-modules/cache-storage/types/cache-storage-namespace.enum';
import { FeatureFlagKey } from 'src/engine/core-modules/feature-flag/enums/feature-flag-key.enum';
import { ObjectMetadataMaps } from 'src/engine/metadata-modules/types/object-metadata-maps';
export enum WorkspaceCacheKeys {
@ -12,6 +13,7 @@ export enum WorkspaceCacheKeys {
GraphQLUsedScalarNames = 'graphql:used-scalar-names',
GraphQLOperations = 'graphql:operations',
ORMEntitySchemas = 'orm:entity-schemas',
GraphQLFeatureFlag = 'graphql:feature-flag',
MetadataObjectMetadataMaps = 'metadata:object-metadata-maps',
MetadataObjectMetadataOngoingCachingLock = 'metadata:object-metadata-ongoing-caching-lock',
MetadataVersion = 'metadata:workspace-metadata-version',
@ -156,6 +158,22 @@ export class WorkspaceCacheStorageService {
);
}
// TODO: remove this after the feature flag is droped
setIsNewRelationEnabled(workspaceId: string, isNewRelationEnabled: boolean) {
return this.cacheStorageService.set<boolean>(
`${WorkspaceCacheKeys.GraphQLFeatureFlag}:${workspaceId}:${FeatureFlagKey.IsNewRelationEnabled}`,
isNewRelationEnabled,
TTL_INFINITE,
);
}
// TODO: remove this after the feature flag is droped
getIsNewRelationEnabled(workspaceId: string): Promise<boolean | undefined> {
return this.cacheStorageService.get<boolean>(
`${WorkspaceCacheKeys.GraphQLFeatureFlag}:${workspaceId}:${FeatureFlagKey.IsNewRelationEnabled}`,
);
}
async flush(workspaceId: string, metadataVersion: number): Promise<void> {
await this.cacheStorageService.del(
`${WorkspaceCacheKeys.MetadataObjectMetadataMaps}:${workspaceId}:${metadataVersion}`,
@ -172,9 +190,13 @@ export class WorkspaceCacheStorageService {
await this.cacheStorageService.del(
`${WorkspaceCacheKeys.ORMEntitySchemas}:${workspaceId}:${metadataVersion}`,
);
await this.cacheStorageService.del(
`${WorkspaceCacheKeys.MetadataObjectMetadataOngoingCachingLock}:${workspaceId}:${metadataVersion}`,
);
// TODO: remove this after the feature flag is droped
await this.cacheStorageService.del(
`${FeatureFlagKey.IsNewRelationEnabled}:${workspaceId}`,
);
}
}