feat: drop target column map (#4670)
This PR is dropping the column `targetColumnMap` of fieldMetadata entities. The goal of this column was to properly map field to their respecting column in the table. We decide to drop it and instead compute the column name on the fly when we need it, as it's more easier to support. Some parts of the code has been refactored to try making implementation of composite type more easier to understand and maintain. Fix #3760 --------- Co-authored-by: Charles Bochet <charles@twenty.com>
This commit is contained in:
@ -6,7 +6,6 @@ export const fieldNumberMock = {
|
||||
type: FieldMetadataType.NUMBER,
|
||||
isNullable: false,
|
||||
defaultValue: null,
|
||||
targetColumnMap: { value: 'fieldNumber' },
|
||||
};
|
||||
|
||||
export const fieldStringMock = {
|
||||
@ -14,7 +13,6 @@ export const fieldStringMock = {
|
||||
type: FieldMetadataType.TEXT,
|
||||
isNullable: true,
|
||||
defaultValue: null,
|
||||
targetColumnMap: { value: 'fieldString' },
|
||||
};
|
||||
|
||||
export const fieldLinkMock = {
|
||||
@ -22,23 +20,18 @@ export const fieldLinkMock = {
|
||||
type: FieldMetadataType.LINK,
|
||||
isNullable: false,
|
||||
defaultValue: { label: '', url: '' },
|
||||
targetColumnMap: { label: 'fieldLinkLabel', url: 'fieldLinkUrl' },
|
||||
};
|
||||
|
||||
export const fieldCurrencyMock = {
|
||||
name: 'fieldCurrency',
|
||||
type: FieldMetadataType.CURRENCY,
|
||||
isNullable: true,
|
||||
defaultValue: null,
|
||||
targetColumnMap: {
|
||||
amountMicros: 'fieldCurrencyAmountMicros',
|
||||
currencyCode: 'fieldCurrencyCurrencyCode',
|
||||
},
|
||||
defaultValue: { amountMicros: null, currencyCode: "''" },
|
||||
};
|
||||
|
||||
export const objectMetadataItemMock: DeepPartial<ObjectMetadataEntity> = {
|
||||
export const objectMetadataItemMock = {
|
||||
targetTableName: 'testingObject',
|
||||
nameSingular: 'objectName',
|
||||
namePlural: 'objectsName',
|
||||
fields: [fieldNumberMock, fieldStringMock, fieldLinkMock, fieldCurrencyMock],
|
||||
};
|
||||
} as ObjectMetadataEntity;
|
||||
|
||||
@ -1,9 +1,15 @@
|
||||
import { Injectable } from '@nestjs/common';
|
||||
import { Injectable, Logger } from '@nestjs/common';
|
||||
|
||||
import { FieldMetadataInterface } from 'src/engine/metadata-modules/field-metadata/interfaces/field-metadata.interface';
|
||||
|
||||
import { compositeTypeDefintions } from 'src/engine/metadata-modules/field-metadata/composite-types';
|
||||
import { computeCompositeColumnName } from 'src/engine/metadata-modules/field-metadata/utils/compute-column-name.util';
|
||||
import { isCompositeFieldMetadataType } from 'src/engine/metadata-modules/field-metadata/utils/is-composite-field-metadata-type.util';
|
||||
|
||||
@Injectable()
|
||||
export class ArgsAliasFactory {
|
||||
private readonly logger = new Logger(ArgsAliasFactory.name);
|
||||
|
||||
create(
|
||||
args: Record<string, any>,
|
||||
fieldMetadataCollection: FieldMetadataInterface[],
|
||||
@ -39,25 +45,42 @@ export class ArgsAliasFactory {
|
||||
for (const [key, value] of Object.entries(args)) {
|
||||
const fieldMetadata = fieldMetadataMap.get(key);
|
||||
|
||||
// If it's a special complex field, we need to map all columns
|
||||
// If it's a composite type, we need to transform args to properly map column name
|
||||
if (
|
||||
fieldMetadata &&
|
||||
typeof value === 'object' &&
|
||||
value !== null &&
|
||||
Object.values(fieldMetadata.targetColumnMap).length > 1
|
||||
isCompositeFieldMetadataType(fieldMetadata.type)
|
||||
) {
|
||||
for (const [subKey, subValue] of Object.entries(value)) {
|
||||
const mappedKey = fieldMetadata.targetColumnMap[subKey];
|
||||
// Get composite type definition
|
||||
const compositeType = compositeTypeDefintions.get(fieldMetadata.type);
|
||||
|
||||
if (mappedKey) {
|
||||
newArgs[mappedKey] = subValue;
|
||||
if (!compositeType) {
|
||||
this.logger.error(
|
||||
`Composite type definition not found for type: ${fieldMetadata.type}`,
|
||||
);
|
||||
throw new Error(
|
||||
`Composite type definition not found for type: ${fieldMetadata.type}`,
|
||||
);
|
||||
}
|
||||
|
||||
// Loop through sub values and map them to composite property
|
||||
for (const [subKey, subValue] of Object.entries(value)) {
|
||||
// Find composite property
|
||||
const compositeProperty = compositeType.properties.find(
|
||||
(property) => property.name === subKey,
|
||||
);
|
||||
|
||||
if (compositeProperty) {
|
||||
const columnName = computeCompositeColumnName(
|
||||
fieldMetadata,
|
||||
compositeProperty,
|
||||
);
|
||||
|
||||
newArgs[columnName] = subValue;
|
||||
}
|
||||
}
|
||||
} else if (fieldMetadata) {
|
||||
// Otherwise we just need to map the value
|
||||
const mappedKey = fieldMetadata.targetColumnMap.value;
|
||||
|
||||
newArgs[mappedKey ?? key] = value;
|
||||
newArgs[key] = value;
|
||||
} else {
|
||||
// Recurse if value is a nested object, otherwise append field or alias
|
||||
newArgs[key] = this.createArgsObjectRecursive(value, fieldMetadataMap);
|
||||
|
||||
@ -2,29 +2,49 @@ import { Injectable, Logger } from '@nestjs/common';
|
||||
|
||||
import { FieldMetadataInterface } from 'src/engine/metadata-modules/field-metadata/interfaces/field-metadata.interface';
|
||||
|
||||
import { isCompositeFieldMetadataType } from 'src/engine/metadata-modules/field-metadata/utils/is-composite-field-metadata-type.util';
|
||||
import { compositeTypeDefintions } from 'src/engine/metadata-modules/field-metadata/composite-types';
|
||||
import { createCompositeFieldKey } from 'src/engine/api/graphql/workspace-query-builder/utils/composite-field-metadata.util';
|
||||
import {
|
||||
computeColumnName,
|
||||
computeCompositeColumnName,
|
||||
} from 'src/engine/metadata-modules/field-metadata/utils/compute-column-name.util';
|
||||
|
||||
@Injectable()
|
||||
export class FieldAliasFactory {
|
||||
private readonly logger = new Logger(FieldAliasFactory.name);
|
||||
|
||||
create(fieldKey: string, fieldMetadata: FieldMetadataInterface) {
|
||||
const entries = Object.entries(fieldMetadata.targetColumnMap);
|
||||
|
||||
if (entries.length === 0) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if (entries.length === 1) {
|
||||
// If there is only one value, use it as the alias
|
||||
const alias = entries[0][1];
|
||||
// If it's not a composite field, we can just return the alias
|
||||
if (!isCompositeFieldMetadataType(fieldMetadata.type)) {
|
||||
const alias = computeColumnName(fieldMetadata);
|
||||
|
||||
return `${fieldKey}: ${alias}`;
|
||||
}
|
||||
|
||||
// Otherwise it means it's a special type with multiple values, so we need map all columns
|
||||
return `
|
||||
${entries
|
||||
.map(([key, value]) => `___${fieldMetadata.name}_${key}: ${value}`)
|
||||
.join('\n')}
|
||||
`;
|
||||
// If it's a composite field, we need to get the definition
|
||||
const compositeType = compositeTypeDefintions.get(fieldMetadata.type);
|
||||
|
||||
if (!compositeType) {
|
||||
this.logger.error(
|
||||
`Composite type not found for field metadata type: ${fieldMetadata.type}`,
|
||||
);
|
||||
throw new Error(
|
||||
`Composite type not found for field metadata type: ${fieldMetadata.type}`,
|
||||
);
|
||||
}
|
||||
|
||||
return compositeType.properties
|
||||
.map((property) => {
|
||||
// Generate a prefixed key for the composite field, this will be computed when the query has ran
|
||||
const compositeKey = createCompositeFieldKey(
|
||||
fieldMetadata.name,
|
||||
property.name,
|
||||
);
|
||||
const alias = computeCompositeColumnName(fieldMetadata, property);
|
||||
|
||||
return `${compositeKey}: ${alias}`;
|
||||
})
|
||||
.join('\n');
|
||||
}
|
||||
}
|
||||
|
||||
@ -14,6 +14,7 @@ import {
|
||||
import { getFieldArgumentsByKey } from 'src/engine/api/graphql/workspace-query-builder/utils/get-field-arguments-by-key.util';
|
||||
import { ObjectMetadataService } from 'src/engine/metadata-modules/object-metadata/object-metadata.service';
|
||||
import { computeObjectTargetTable } from 'src/engine/utils/compute-object-target-table.util';
|
||||
import { computeColumnName } from 'src/engine/metadata-modules/field-metadata/utils/compute-column-name.util';
|
||||
|
||||
import { FieldsStringFactory } from './fields-string.factory';
|
||||
import { ArgsStringFactory } from './args-string.factory';
|
||||
@ -118,9 +119,7 @@ export class RelationFieldAliasFactory {
|
||||
`;
|
||||
}
|
||||
|
||||
let relationAlias = fieldMetadata.isCustom
|
||||
? `${fieldKey}: _${fieldMetadata.name}`
|
||||
: fieldKey;
|
||||
let relationAlias = `${fieldKey}: ${computeColumnName(fieldMetadata)}`;
|
||||
|
||||
// For one to one relations, pg_graphql use the target TableName on the side that is not storing the foreign key
|
||||
// so we need to alias it to the field key
|
||||
|
||||
@ -0,0 +1,38 @@
|
||||
/**
|
||||
* Composite key are structured as follows:
|
||||
* COMPOSITE___{parentFieldName}_{childFieldName}
|
||||
* This util are here to pre-process and post-process the composite keys before and after querying the database
|
||||
*/
|
||||
|
||||
export const compositeFieldPrefix = 'COMPOSITE___';
|
||||
|
||||
export const createCompositeFieldKey = (
|
||||
fieldName: string,
|
||||
propertyName: string,
|
||||
): string => {
|
||||
return `${compositeFieldPrefix}${fieldName}_${propertyName}`;
|
||||
};
|
||||
|
||||
export const isPrefixedCompositeField = (key: string): boolean => {
|
||||
return key.startsWith(compositeFieldPrefix);
|
||||
};
|
||||
|
||||
export const parseCompositeFieldKey = (
|
||||
key: string,
|
||||
): {
|
||||
parentFieldName: string;
|
||||
childFieldName: string;
|
||||
} | null => {
|
||||
const [parentFieldName, childFieldName] = key
|
||||
.replace(compositeFieldPrefix, '')
|
||||
.split('_');
|
||||
|
||||
if (!parentFieldName || !childFieldName) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return {
|
||||
parentFieldName,
|
||||
childFieldName,
|
||||
};
|
||||
};
|
||||
@ -1,19 +1,9 @@
|
||||
import { createCompositeFieldKey } from 'src/engine/api/graphql/workspace-query-builder/utils/composite-field-metadata.util';
|
||||
import {
|
||||
isSpecialKey,
|
||||
handleSpecialKey,
|
||||
handleCompositeKey,
|
||||
parseResult,
|
||||
} from 'src/engine/api/graphql/workspace-query-runner/utils/parse-result.util';
|
||||
|
||||
describe('isSpecialKey', () => {
|
||||
test('should return true if the key starts with "___"', () => {
|
||||
expect(isSpecialKey('___specialKey')).toBe(true);
|
||||
});
|
||||
|
||||
test('should return false if the key does not start with "___"', () => {
|
||||
expect(isSpecialKey('normalKey')).toBe(false);
|
||||
});
|
||||
});
|
||||
|
||||
describe('handleSpecialKey', () => {
|
||||
let result;
|
||||
|
||||
@ -21,8 +11,12 @@ describe('handleSpecialKey', () => {
|
||||
result = {};
|
||||
});
|
||||
|
||||
test('should correctly process a special key and add it to the result object', () => {
|
||||
handleSpecialKey(result, '___complexField_link', 'value1');
|
||||
test('should correctly process a composite key and add it to the result object', () => {
|
||||
handleCompositeKey(
|
||||
result,
|
||||
createCompositeFieldKey('complexField', 'link'),
|
||||
'value1',
|
||||
);
|
||||
expect(result).toEqual({
|
||||
complexField: {
|
||||
link: 'value1',
|
||||
@ -31,8 +25,16 @@ describe('handleSpecialKey', () => {
|
||||
});
|
||||
|
||||
test('should add values under the same newKey if called multiple times', () => {
|
||||
handleSpecialKey(result, '___complexField_link', 'value1');
|
||||
handleSpecialKey(result, '___complexField_text', 'value2');
|
||||
handleCompositeKey(
|
||||
result,
|
||||
createCompositeFieldKey('complexField', 'link'),
|
||||
'value1',
|
||||
);
|
||||
handleCompositeKey(
|
||||
result,
|
||||
createCompositeFieldKey('complexField', 'text'),
|
||||
'value2',
|
||||
);
|
||||
expect(result).toEqual({
|
||||
complexField: {
|
||||
link: 'value1',
|
||||
@ -41,8 +43,8 @@ describe('handleSpecialKey', () => {
|
||||
});
|
||||
});
|
||||
|
||||
test('should not create a new field if the special key is not correctly formed', () => {
|
||||
handleSpecialKey(result, '___complexField', 'value1');
|
||||
test('should not create a new field if the composite key is not correctly formed', () => {
|
||||
handleCompositeKey(result, 'COMPOSITE___complexField', 'value1');
|
||||
expect(result).toEqual({});
|
||||
});
|
||||
});
|
||||
@ -51,9 +53,9 @@ describe('parseResult', () => {
|
||||
test('should recursively parse an object and handle special keys', () => {
|
||||
const obj = {
|
||||
normalField: 'value1',
|
||||
___specialField_part1: 'value2',
|
||||
COMPOSITE___specialField_part1: 'value2',
|
||||
nested: {
|
||||
___specialFieldNested_part2: 'value3',
|
||||
COMPOSITE___specialFieldNested_part2: 'value3',
|
||||
},
|
||||
};
|
||||
|
||||
@ -75,10 +77,10 @@ describe('parseResult', () => {
|
||||
test('should handle arrays and parse each element', () => {
|
||||
const objArray = [
|
||||
{
|
||||
___specialField_part1: 'value1',
|
||||
COMPOSITE___specialField_part1: 'value1',
|
||||
},
|
||||
{
|
||||
___specialField_part2: 'value2',
|
||||
COMPOSITE___specialField_part2: 'value2',
|
||||
},
|
||||
];
|
||||
|
||||
|
||||
@ -1,27 +1,25 @@
|
||||
export const isSpecialKey = (key: string): boolean => {
|
||||
return key.startsWith('___');
|
||||
};
|
||||
import {
|
||||
isPrefixedCompositeField,
|
||||
parseCompositeFieldKey,
|
||||
} from 'src/engine/api/graphql/workspace-query-builder/utils/composite-field-metadata.util';
|
||||
|
||||
export const handleSpecialKey = (
|
||||
export const handleCompositeKey = (
|
||||
result: any,
|
||||
key: string,
|
||||
value: any,
|
||||
): void => {
|
||||
const parts = key.split('_').filter((part) => part);
|
||||
const parsedFieldKey = parseCompositeFieldKey(key);
|
||||
|
||||
// If parts don't contain enough information, return without altering result
|
||||
if (parts.length < 2) {
|
||||
// If composite field key can't be parsed, return
|
||||
if (!parsedFieldKey) {
|
||||
return;
|
||||
}
|
||||
|
||||
const newKey = parts.slice(0, -1).join('');
|
||||
const subKey = parts[parts.length - 1];
|
||||
|
||||
if (!result[newKey]) {
|
||||
result[newKey] = {};
|
||||
if (!result[parsedFieldKey.parentFieldName]) {
|
||||
result[parsedFieldKey.parentFieldName] = {};
|
||||
}
|
||||
|
||||
result[newKey][subKey] = value;
|
||||
result[parsedFieldKey.parentFieldName][parsedFieldKey.childFieldName] = value;
|
||||
};
|
||||
|
||||
export const parseResult = (obj: any): any => {
|
||||
@ -41,8 +39,8 @@ export const parseResult = (obj: any): any => {
|
||||
result[key] = parseResult(obj[key]);
|
||||
} else if (key === '__typename') {
|
||||
result[key] = obj[key].replace(/^_*/, '');
|
||||
} else if (isSpecialKey(key)) {
|
||||
handleSpecialKey(result, key, obj[key]);
|
||||
} else if (isPrefixedCompositeField(key)) {
|
||||
handleCompositeKey(result, key, obj[key]);
|
||||
} else {
|
||||
result[key] = obj[key];
|
||||
}
|
||||
|
||||
@ -0,0 +1,84 @@
|
||||
import { Injectable, Logger } from '@nestjs/common';
|
||||
|
||||
import { GraphQLInputFieldConfigMap, GraphQLInputObjectType } from 'graphql';
|
||||
|
||||
import { WorkspaceBuildSchemaOptions } from 'src/engine/api/graphql/workspace-schema-builder/interfaces/workspace-build-schema-optionts.interface';
|
||||
import { CompositeType } from 'src/engine/metadata-modules/field-metadata/interfaces/composite-type.interface';
|
||||
|
||||
import { pascalCase } from 'src/utils/pascal-case';
|
||||
import { isRelationFieldMetadataType } from 'src/engine/utils/is-relation-field-metadata-type.util';
|
||||
import { FieldMetadataType } from 'src/engine/metadata-modules/field-metadata/field-metadata.entity';
|
||||
import {
|
||||
InputTypeDefinition,
|
||||
InputTypeDefinitionKind,
|
||||
} from 'src/engine/api/graphql/workspace-schema-builder/factories/input-type-definition.factory';
|
||||
|
||||
import { InputTypeFactory } from './input-type.factory';
|
||||
|
||||
@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 (isRelationFieldMetadataType(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 type = this.inputTypeFactory.create(
|
||||
property.name,
|
||||
property.type,
|
||||
kind,
|
||||
options,
|
||||
{
|
||||
nullable: !property.isRequired,
|
||||
isArray: property.type === FieldMetadataType.MULTI_SELECT,
|
||||
},
|
||||
);
|
||||
|
||||
fields[property.name] = {
|
||||
type,
|
||||
description: property.description,
|
||||
};
|
||||
}
|
||||
|
||||
return fields;
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,84 @@
|
||||
import { Injectable, Logger } from '@nestjs/common';
|
||||
|
||||
import { GraphQLFieldConfigMap, GraphQLObjectType } from 'graphql';
|
||||
|
||||
import { WorkspaceBuildSchemaOptions } from 'src/engine/api/graphql/workspace-schema-builder/interfaces/workspace-build-schema-optionts.interface';
|
||||
import { CompositeType } from 'src/engine/metadata-modules/field-metadata/interfaces/composite-type.interface';
|
||||
|
||||
import { pascalCase } from 'src/utils/pascal-case';
|
||||
import { FieldMetadataType } from 'src/engine/metadata-modules/field-metadata/field-metadata.entity';
|
||||
import { OutputTypeFactory } from 'src/engine/api/graphql/workspace-schema-builder/factories/output-type.factory';
|
||||
import {
|
||||
ObjectTypeDefinition,
|
||||
ObjectTypeDefinitionKind,
|
||||
} from 'src/engine/api/graphql/workspace-schema-builder/factories/object-type-definition.factory';
|
||||
import { isRelationFieldMetadataType } from 'src/engine/utils/is-relation-field-metadata-type.util';
|
||||
|
||||
@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,
|
||||
): GraphQLFieldConfigMap<any, any> {
|
||||
const fields: GraphQLFieldConfigMap<any, any> = {};
|
||||
|
||||
for (const property of compositeType.properties) {
|
||||
// Relation fields are not supported in composite types
|
||||
if (isRelationFieldMetadataType(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 type = this.outputTypeFactory.create(
|
||||
property.name,
|
||||
property.type,
|
||||
kind,
|
||||
options,
|
||||
{
|
||||
nullable: !property.isRequired,
|
||||
isArray: property.type === FieldMetadataType.MULTI_SELECT,
|
||||
},
|
||||
);
|
||||
|
||||
fields[property.name] = {
|
||||
type,
|
||||
description: property.description,
|
||||
};
|
||||
}
|
||||
|
||||
return fields;
|
||||
}
|
||||
}
|
||||
@ -1,4 +1,6 @@
|
||||
import { EnumTypeDefinitionFactory } from 'src/engine/api/graphql/workspace-schema-builder/factories/enum-type-definition.factory';
|
||||
import { CompositeObjectTypeDefinitionFactory } from 'src/engine/api/graphql/workspace-schema-builder/factories/composite-object-type-definition.factory';
|
||||
import { CompositeInputTypeDefinitionFactory } from 'src/engine/api/graphql/workspace-schema-builder/factories/composite-input-type-definition.factory';
|
||||
|
||||
import { ArgsFactory } from './args.factory';
|
||||
import { InputTypeFactory } from './input-type.factory';
|
||||
@ -7,15 +9,11 @@ import { ObjectTypeDefinitionFactory } from './object-type-definition.factory';
|
||||
import { OutputTypeFactory } from './output-type.factory';
|
||||
import { QueryTypeFactory } from './query-type.factory';
|
||||
import { RootTypeFactory } from './root-type.factory';
|
||||
import { FilterTypeFactory } from './filter-type.factory';
|
||||
import { FilterTypeDefinitionFactory } from './filter-type-definition.factory';
|
||||
import { ConnectionTypeFactory } from './connection-type.factory';
|
||||
import { ConnectionTypeDefinitionFactory } from './connection-type-definition.factory';
|
||||
import { EdgeTypeFactory } from './edge-type.factory';
|
||||
import { EdgeTypeDefinitionFactory } from './edge-type-definition.factory';
|
||||
import { MutationTypeFactory } from './mutation-type.factory';
|
||||
import { OrderByTypeFactory } from './order-by-type.factory';
|
||||
import { OrderByTypeDefinitionFactory } from './order-by-type-definition.factory';
|
||||
import { RelationTypeFactory } from './relation-type.factory';
|
||||
import { ExtendObjectTypeDefinitionFactory } from './extend-object-type-definition.factory';
|
||||
import { OrphanedTypesFactory } from './orphaned-types.factory';
|
||||
@ -24,15 +22,13 @@ export const workspaceSchemaBuilderFactories = [
|
||||
ArgsFactory,
|
||||
InputTypeFactory,
|
||||
InputTypeDefinitionFactory,
|
||||
CompositeInputTypeDefinitionFactory,
|
||||
OutputTypeFactory,
|
||||
ObjectTypeDefinitionFactory,
|
||||
CompositeObjectTypeDefinitionFactory,
|
||||
EnumTypeDefinitionFactory,
|
||||
RelationTypeFactory,
|
||||
ExtendObjectTypeDefinitionFactory,
|
||||
FilterTypeFactory,
|
||||
FilterTypeDefinitionFactory,
|
||||
OrderByTypeFactory,
|
||||
OrderByTypeDefinitionFactory,
|
||||
ConnectionTypeFactory,
|
||||
ConnectionTypeDefinitionFactory,
|
||||
EdgeTypeFactory,
|
||||
|
||||
@ -1,91 +0,0 @@
|
||||
import { Injectable } from '@nestjs/common';
|
||||
|
||||
import { GraphQLInputFieldConfigMap, GraphQLInputObjectType } 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 { pascalCase } from 'src/utils/pascal-case';
|
||||
import { TypeMapperService } from 'src/engine/api/graphql/workspace-schema-builder/services/type-mapper.service';
|
||||
import { isRelationFieldMetadataType } from 'src/engine/utils/is-relation-field-metadata-type.util';
|
||||
|
||||
import { FilterTypeFactory } from './filter-type.factory';
|
||||
import {
|
||||
InputTypeDefinition,
|
||||
InputTypeDefinitionKind,
|
||||
} from './input-type-definition.factory';
|
||||
|
||||
@Injectable()
|
||||
export class FilterTypeDefinitionFactory {
|
||||
constructor(
|
||||
private readonly filterTypeFactory: FilterTypeFactory,
|
||||
private readonly typeMapperService: TypeMapperService,
|
||||
) {}
|
||||
|
||||
public create(
|
||||
objectMetadata: ObjectMetadataInterface,
|
||||
options: WorkspaceBuildSchemaOptions,
|
||||
): InputTypeDefinition {
|
||||
const kind = InputTypeDefinitionKind.Filter;
|
||||
const filterInputType = new GraphQLInputObjectType({
|
||||
name: `${pascalCase(objectMetadata.nameSingular)}${kind.toString()}Input`,
|
||||
description: objectMetadata.description,
|
||||
fields: () => {
|
||||
const andOrType = this.typeMapperService.mapToGqlType(filterInputType, {
|
||||
isArray: true,
|
||||
arrayDepth: 1,
|
||||
nullable: true,
|
||||
});
|
||||
|
||||
return {
|
||||
...this.generateFields(objectMetadata, options),
|
||||
and: {
|
||||
type: andOrType,
|
||||
},
|
||||
or: {
|
||||
type: andOrType,
|
||||
},
|
||||
not: {
|
||||
type: this.typeMapperService.mapToGqlType(filterInputType, {
|
||||
nullable: true,
|
||||
}),
|
||||
},
|
||||
};
|
||||
},
|
||||
});
|
||||
|
||||
return {
|
||||
target: objectMetadata.id,
|
||||
kind,
|
||||
type: filterInputType,
|
||||
};
|
||||
}
|
||||
|
||||
private generateFields(
|
||||
objectMetadata: ObjectMetadataInterface,
|
||||
options: WorkspaceBuildSchemaOptions,
|
||||
): GraphQLInputFieldConfigMap {
|
||||
const fields: GraphQLInputFieldConfigMap = {};
|
||||
|
||||
for (const fieldMetadata of objectMetadata.fields) {
|
||||
// Relation types are generated during extension of object type definition
|
||||
if (isRelationFieldMetadataType(fieldMetadata.type)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
const type = this.filterTypeFactory.create(fieldMetadata, options, {
|
||||
nullable: fieldMetadata.isNullable,
|
||||
defaultValue: fieldMetadata.defaultValue,
|
||||
});
|
||||
|
||||
fields[fieldMetadata.name] = {
|
||||
type,
|
||||
description: fieldMetadata.description,
|
||||
// TODO: Add default value
|
||||
defaultValue: undefined,
|
||||
};
|
||||
}
|
||||
|
||||
return fields;
|
||||
}
|
||||
}
|
||||
@ -1,102 +0,0 @@
|
||||
import { Injectable, Logger } from '@nestjs/common';
|
||||
|
||||
import {
|
||||
GraphQLInputObjectType,
|
||||
GraphQLInputType,
|
||||
GraphQLList,
|
||||
GraphQLScalarType,
|
||||
} from 'graphql';
|
||||
|
||||
import { WorkspaceBuildSchemaOptions } from 'src/engine/api/graphql/workspace-schema-builder/interfaces/workspace-build-schema-optionts.interface';
|
||||
import { FieldMetadataInterface } from 'src/engine/metadata-modules/field-metadata/interfaces/field-metadata.interface';
|
||||
|
||||
import {
|
||||
TypeMapperService,
|
||||
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 { 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 { FilterIs } from 'src/engine/api/graphql/workspace-schema-builder/graphql-types/input/filter-is.input-type';
|
||||
|
||||
import { InputTypeDefinitionKind } from './input-type-definition.factory';
|
||||
|
||||
@Injectable()
|
||||
export class FilterTypeFactory {
|
||||
private readonly logger = new Logger(FilterTypeFactory.name);
|
||||
|
||||
constructor(
|
||||
private readonly typeMapperService: TypeMapperService,
|
||||
private readonly typeDefinitionsStorage: TypeDefinitionsStorage,
|
||||
) {}
|
||||
|
||||
public create(
|
||||
fieldMetadata: FieldMetadataInterface,
|
||||
buildOptions: WorkspaceBuildSchemaOptions,
|
||||
typeOptions: TypeOptions,
|
||||
): GraphQLInputType {
|
||||
const target = isCompositeFieldMetadataType(fieldMetadata.type)
|
||||
? fieldMetadata.type.toString()
|
||||
: fieldMetadata.id;
|
||||
let filterType: GraphQLInputObjectType | GraphQLScalarType | undefined =
|
||||
undefined;
|
||||
|
||||
if (isEnumFieldMetadataType(fieldMetadata.type)) {
|
||||
filterType = this.createEnumFilterType(fieldMetadata);
|
||||
} else {
|
||||
filterType = this.typeMapperService.mapToFilterType(
|
||||
fieldMetadata.type,
|
||||
buildOptions.dateScalarMode,
|
||||
buildOptions.numberScalarMode,
|
||||
);
|
||||
|
||||
filterType ??= this.typeDefinitionsStorage.getInputTypeByKey(
|
||||
target,
|
||||
InputTypeDefinitionKind.Filter,
|
||||
);
|
||||
}
|
||||
|
||||
if (!filterType) {
|
||||
this.logger.error(`Could not find a GraphQL type for ${target}`, {
|
||||
fieldMetadata,
|
||||
buildOptions,
|
||||
typeOptions,
|
||||
});
|
||||
|
||||
throw new Error(`Could not find a GraphQL type for ${target}`);
|
||||
}
|
||||
|
||||
return this.typeMapperService.mapToGqlType(filterType, typeOptions);
|
||||
}
|
||||
|
||||
private createEnumFilterType(
|
||||
fieldMetadata: FieldMetadataInterface,
|
||||
): GraphQLInputObjectType {
|
||||
const enumType = this.typeDefinitionsStorage.getEnumTypeByKey(
|
||||
fieldMetadata.id,
|
||||
);
|
||||
|
||||
if (!enumType) {
|
||||
this.logger.error(
|
||||
`Could not find a GraphQL enum type for ${fieldMetadata.id}`,
|
||||
{
|
||||
fieldMetadata,
|
||||
},
|
||||
);
|
||||
|
||||
throw new Error(
|
||||
`Could not find a GraphQL enum type for ${fieldMetadata.id}`,
|
||||
);
|
||||
}
|
||||
|
||||
return new GraphQLInputObjectType({
|
||||
name: `${enumType.name}Filter`,
|
||||
fields: () => ({
|
||||
eq: { type: enumType },
|
||||
neq: { type: enumType },
|
||||
in: { type: new GraphQLList(enumType) },
|
||||
is: { type: FilterIs },
|
||||
}),
|
||||
});
|
||||
}
|
||||
}
|
||||
@ -1,4 +1,4 @@
|
||||
import { Injectable } from '@nestjs/common';
|
||||
import { Inject, Injectable, forwardRef } from '@nestjs/common';
|
||||
|
||||
import { GraphQLInputFieldConfigMap, GraphQLInputObjectType } from 'graphql';
|
||||
|
||||
@ -8,6 +8,8 @@ import { ObjectMetadataInterface } from 'src/engine/metadata-modules/field-metad
|
||||
import { pascalCase } from 'src/utils/pascal-case';
|
||||
import { isRelationFieldMetadataType } from 'src/engine/utils/is-relation-field-metadata-type.util';
|
||||
import { FieldMetadataType } 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 { TypeMapperService } from 'src/engine/api/graphql/workspace-schema-builder/services/type-mapper.service';
|
||||
|
||||
import { InputTypeFactory } from './input-type.factory';
|
||||
|
||||
@ -26,23 +28,60 @@ export interface InputTypeDefinition {
|
||||
|
||||
@Injectable()
|
||||
export class InputTypeDefinitionFactory {
|
||||
constructor(private readonly inputTypeFactory: InputTypeFactory) {}
|
||||
constructor(
|
||||
@Inject(forwardRef(() => InputTypeFactory))
|
||||
private readonly inputTypeFactory: InputTypeFactory,
|
||||
private readonly typeMapperService: TypeMapperService,
|
||||
) {}
|
||||
|
||||
public create(
|
||||
objectMetadata: ObjectMetadataInterface,
|
||||
kind: InputTypeDefinitionKind,
|
||||
options: WorkspaceBuildSchemaOptions,
|
||||
): InputTypeDefinition {
|
||||
const inputType = new GraphQLInputObjectType({
|
||||
name: `${pascalCase(objectMetadata.nameSingular)}${kind.toString()}Input`,
|
||||
description: objectMetadata.description,
|
||||
fields: () => {
|
||||
switch (kind) {
|
||||
/**
|
||||
* Filter input type has additional fields for filtering and is self referencing
|
||||
*/
|
||||
case InputTypeDefinitionKind.Filter: {
|
||||
const andOrType = this.typeMapperService.mapToGqlType(inputType, {
|
||||
isArray: true,
|
||||
arrayDepth: 1,
|
||||
nullable: true,
|
||||
});
|
||||
|
||||
return {
|
||||
...this.generateFields(objectMetadata, kind, options),
|
||||
and: {
|
||||
type: andOrType,
|
||||
},
|
||||
or: {
|
||||
type: andOrType,
|
||||
},
|
||||
not: {
|
||||
type: this.typeMapperService.mapToGqlType(inputType, {
|
||||
nullable: true,
|
||||
}),
|
||||
},
|
||||
};
|
||||
}
|
||||
/**
|
||||
* Other input types are generated with fields only
|
||||
*/
|
||||
default:
|
||||
return this.generateFields(objectMetadata, kind, options);
|
||||
}
|
||||
},
|
||||
});
|
||||
|
||||
return {
|
||||
target: objectMetadata.id,
|
||||
kind,
|
||||
type: new GraphQLInputObjectType({
|
||||
name: `${pascalCase(
|
||||
objectMetadata.nameSingular,
|
||||
)}${kind.toString()}Input`,
|
||||
description: objectMetadata.description,
|
||||
fields: this.generateFields(objectMetadata, kind, options),
|
||||
}),
|
||||
type: inputType,
|
||||
};
|
||||
}
|
||||
|
||||
@ -59,17 +98,25 @@ export class InputTypeDefinitionFactory {
|
||||
continue;
|
||||
}
|
||||
|
||||
const type = this.inputTypeFactory.create(fieldMetadata, kind, options, {
|
||||
nullable: fieldMetadata.isNullable,
|
||||
defaultValue: fieldMetadata.defaultValue,
|
||||
isArray: fieldMetadata.type === FieldMetadataType.MULTI_SELECT,
|
||||
});
|
||||
const target = isCompositeFieldMetadataType(fieldMetadata.type)
|
||||
? fieldMetadata.type.toString()
|
||||
: fieldMetadata.id;
|
||||
|
||||
const type = this.inputTypeFactory.create(
|
||||
target,
|
||||
fieldMetadata.type,
|
||||
kind,
|
||||
options,
|
||||
{
|
||||
nullable: fieldMetadata.isNullable,
|
||||
defaultValue: fieldMetadata.defaultValue,
|
||||
isArray: fieldMetadata.type === FieldMetadataType.MULTI_SELECT,
|
||||
},
|
||||
);
|
||||
|
||||
fields[fieldMetadata.name] = {
|
||||
type,
|
||||
description: fieldMetadata.description,
|
||||
// TODO: Add default value
|
||||
defaultValue: undefined,
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
@ -1,16 +1,17 @@
|
||||
import { Injectable, Logger } from '@nestjs/common';
|
||||
|
||||
import { GraphQLInputType } from 'graphql';
|
||||
import { GraphQLInputObjectType, GraphQLInputType, GraphQLList } from 'graphql';
|
||||
|
||||
import { WorkspaceBuildSchemaOptions } from 'src/engine/api/graphql/workspace-schema-builder/interfaces/workspace-build-schema-optionts.interface';
|
||||
import { FieldMetadataInterface } from 'src/engine/metadata-modules/field-metadata/interfaces/field-metadata.interface';
|
||||
|
||||
import {
|
||||
TypeMapperService,
|
||||
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 { isCompositeFieldMetadataType } from 'src/engine/metadata-modules/field-metadata/utils/is-composite-field-metadata-type.util';
|
||||
import { FieldMetadataType } from 'src/engine/metadata-modules/field-metadata/field-metadata.entity';
|
||||
import { FilterIs } from 'src/engine/api/graphql/workspace-schema-builder/graphql-types/input/filter-is.input-type';
|
||||
import { isEnumFieldMetadataType } from 'src/engine/metadata-modules/field-metadata/utils/is-enum-field-metadata-type.util';
|
||||
|
||||
import { InputTypeDefinitionKind } from './input-type-definition.factory';
|
||||
|
||||
@ -24,30 +25,60 @@ export class InputTypeFactory {
|
||||
) {}
|
||||
|
||||
public create(
|
||||
fieldMetadata: FieldMetadataInterface,
|
||||
target: string,
|
||||
type: FieldMetadataType,
|
||||
kind: InputTypeDefinitionKind,
|
||||
buildOtions: WorkspaceBuildSchemaOptions,
|
||||
buildOptions: WorkspaceBuildSchemaOptions,
|
||||
typeOptions: TypeOptions,
|
||||
): GraphQLInputType {
|
||||
const target = isCompositeFieldMetadataType(fieldMetadata.type)
|
||||
? fieldMetadata.type.toString()
|
||||
: fieldMetadata.id;
|
||||
let inputType: GraphQLInputType | undefined =
|
||||
this.typeMapperService.mapToScalarType(
|
||||
fieldMetadata.type,
|
||||
buildOtions.dateScalarMode,
|
||||
buildOtions.numberScalarMode,
|
||||
);
|
||||
let inputType: GraphQLInputType | undefined;
|
||||
|
||||
switch (kind) {
|
||||
/**
|
||||
* Create and Update input types are classic scalar types
|
||||
*/
|
||||
case InputTypeDefinitionKind.Create:
|
||||
case InputTypeDefinitionKind.Update:
|
||||
inputType = this.typeMapperService.mapToScalarType(
|
||||
type,
|
||||
buildOptions.dateScalarMode,
|
||||
buildOptions.numberScalarMode,
|
||||
);
|
||||
break;
|
||||
/**
|
||||
* Filter input maps to special filter type
|
||||
*/
|
||||
case InputTypeDefinitionKind.Filter: {
|
||||
if (isEnumFieldMetadataType(type)) {
|
||||
inputType = this.createEnumFilterType(target);
|
||||
} else {
|
||||
inputType = this.typeMapperService.mapToFilterType(
|
||||
type,
|
||||
buildOptions.dateScalarMode,
|
||||
buildOptions.numberScalarMode,
|
||||
);
|
||||
}
|
||||
|
||||
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);
|
||||
|
||||
inputType ??= this.typeDefinitionsStorage.getEnumTypeByKey(target);
|
||||
|
||||
if (!inputType) {
|
||||
this.logger.error(`Could not find a GraphQL type for ${target}`, {
|
||||
fieldMetadata,
|
||||
type,
|
||||
kind,
|
||||
buildOtions,
|
||||
buildOptions,
|
||||
typeOptions,
|
||||
});
|
||||
|
||||
@ -56,4 +87,24 @@ export class InputTypeFactory {
|
||||
|
||||
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) },
|
||||
is: { type: FilterIs },
|
||||
}),
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@ -8,6 +8,7 @@ import { ObjectMetadataInterface } from 'src/engine/metadata-modules/field-metad
|
||||
import { pascalCase } from 'src/utils/pascal-case';
|
||||
import { isRelationFieldMetadataType } from 'src/engine/utils/is-relation-field-metadata-type.util';
|
||||
import { FieldMetadataType } 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 { OutputTypeFactory } from './output-type.factory';
|
||||
|
||||
@ -56,10 +57,20 @@ export class ObjectTypeDefinitionFactory {
|
||||
continue;
|
||||
}
|
||||
|
||||
const type = this.outputTypeFactory.create(fieldMetadata, kind, options, {
|
||||
nullable: fieldMetadata.isNullable,
|
||||
isArray: fieldMetadata.type === FieldMetadataType.MULTI_SELECT,
|
||||
});
|
||||
const target = isCompositeFieldMetadataType(fieldMetadata.type)
|
||||
? fieldMetadata.type.toString()
|
||||
: fieldMetadata.id;
|
||||
|
||||
const type = this.outputTypeFactory.create(
|
||||
target,
|
||||
fieldMetadata.type,
|
||||
kind,
|
||||
options,
|
||||
{
|
||||
nullable: fieldMetadata.isNullable,
|
||||
isArray: fieldMetadata.type === FieldMetadataType.MULTI_SELECT,
|
||||
},
|
||||
);
|
||||
|
||||
fields[fieldMetadata.name] = {
|
||||
type,
|
||||
|
||||
@ -1,66 +0,0 @@
|
||||
import { Injectable } from '@nestjs/common';
|
||||
|
||||
import { GraphQLInputFieldConfigMap, GraphQLInputObjectType } 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 { pascalCase } from 'src/utils/pascal-case';
|
||||
import { isRelationFieldMetadataType } from 'src/engine/utils/is-relation-field-metadata-type.util';
|
||||
|
||||
import {
|
||||
InputTypeDefinition,
|
||||
InputTypeDefinitionKind,
|
||||
} from './input-type-definition.factory';
|
||||
import { OrderByTypeFactory } from './order-by-type.factory';
|
||||
|
||||
@Injectable()
|
||||
export class OrderByTypeDefinitionFactory {
|
||||
constructor(private readonly orderByTypeFactory: OrderByTypeFactory) {}
|
||||
|
||||
public create(
|
||||
objectMetadata: ObjectMetadataInterface,
|
||||
options: WorkspaceBuildSchemaOptions,
|
||||
): InputTypeDefinition {
|
||||
const kind = InputTypeDefinitionKind.OrderBy;
|
||||
|
||||
return {
|
||||
target: objectMetadata.id,
|
||||
kind,
|
||||
type: new GraphQLInputObjectType({
|
||||
name: `${pascalCase(
|
||||
objectMetadata.nameSingular,
|
||||
)}${kind.toString()}Input`,
|
||||
description: objectMetadata.description,
|
||||
fields: this.generateFields(objectMetadata, options),
|
||||
}),
|
||||
};
|
||||
}
|
||||
|
||||
private generateFields(
|
||||
objectMetadata: ObjectMetadataInterface,
|
||||
options: WorkspaceBuildSchemaOptions,
|
||||
): GraphQLInputFieldConfigMap {
|
||||
const fields: GraphQLInputFieldConfigMap = {};
|
||||
|
||||
for (const fieldMetadata of objectMetadata.fields) {
|
||||
// Relation field types are generated during extension of object type definition
|
||||
if (isRelationFieldMetadataType(fieldMetadata.type)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
const type = this.orderByTypeFactory.create(fieldMetadata, options, {
|
||||
nullable: fieldMetadata.isNullable,
|
||||
});
|
||||
|
||||
fields[fieldMetadata.name] = {
|
||||
type,
|
||||
description: fieldMetadata.description,
|
||||
// TODO: Add default value
|
||||
defaultValue: undefined,
|
||||
};
|
||||
}
|
||||
|
||||
return fields;
|
||||
}
|
||||
}
|
||||
@ -1,55 +0,0 @@
|
||||
import { Injectable, Logger } from '@nestjs/common';
|
||||
|
||||
import { GraphQLInputType } from 'graphql';
|
||||
|
||||
import { WorkspaceBuildSchemaOptions } from 'src/engine/api/graphql/workspace-schema-builder/interfaces/workspace-build-schema-optionts.interface';
|
||||
import { FieldMetadataInterface } from 'src/engine/metadata-modules/field-metadata/interfaces/field-metadata.interface';
|
||||
|
||||
import {
|
||||
TypeMapperService,
|
||||
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 { isCompositeFieldMetadataType } from 'src/engine/metadata-modules/field-metadata/utils/is-composite-field-metadata-type.util';
|
||||
|
||||
import { InputTypeDefinitionKind } from './input-type-definition.factory';
|
||||
|
||||
@Injectable()
|
||||
export class OrderByTypeFactory {
|
||||
private readonly logger = new Logger(OrderByTypeFactory.name);
|
||||
|
||||
constructor(
|
||||
private readonly typeMapperService: TypeMapperService,
|
||||
private readonly typeDefinitionsStorage: TypeDefinitionsStorage,
|
||||
) {}
|
||||
|
||||
public create(
|
||||
fieldMetadata: FieldMetadataInterface,
|
||||
buildOtions: WorkspaceBuildSchemaOptions,
|
||||
typeOptions: TypeOptions,
|
||||
): GraphQLInputType {
|
||||
const target = isCompositeFieldMetadataType(fieldMetadata.type)
|
||||
? fieldMetadata.type.toString()
|
||||
: fieldMetadata.id;
|
||||
let orderByType = this.typeMapperService.mapToOrderByType(
|
||||
fieldMetadata.type,
|
||||
);
|
||||
|
||||
orderByType ??= this.typeDefinitionsStorage.getInputTypeByKey(
|
||||
target,
|
||||
InputTypeDefinitionKind.OrderBy,
|
||||
);
|
||||
|
||||
if (!orderByType) {
|
||||
this.logger.error(`Could not find a GraphQL type for ${target}`, {
|
||||
fieldMetadata,
|
||||
buildOtions,
|
||||
typeOptions,
|
||||
});
|
||||
|
||||
throw new Error(`Could not find a GraphQL type for ${target}`);
|
||||
}
|
||||
|
||||
return this.typeMapperService.mapToGqlType(orderByType, typeOptions);
|
||||
}
|
||||
}
|
||||
@ -3,14 +3,13 @@ import { Injectable, Logger } from '@nestjs/common';
|
||||
import { GraphQLOutputType } from 'graphql';
|
||||
|
||||
import { WorkspaceBuildSchemaOptions } from 'src/engine/api/graphql/workspace-schema-builder/interfaces/workspace-build-schema-optionts.interface';
|
||||
import { FieldMetadataInterface } from 'src/engine/metadata-modules/field-metadata/interfaces/field-metadata.interface';
|
||||
|
||||
import {
|
||||
TypeMapperService,
|
||||
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 { isCompositeFieldMetadataType } from 'src/engine/metadata-modules/field-metadata/utils/is-composite-field-metadata-type.util';
|
||||
import { FieldMetadataType } from 'src/engine/metadata-modules/field-metadata/field-metadata.entity';
|
||||
|
||||
import { ObjectTypeDefinitionKind } from './object-type-definition.factory';
|
||||
|
||||
@ -24,28 +23,24 @@ export class OutputTypeFactory {
|
||||
) {}
|
||||
|
||||
public create(
|
||||
fieldMetadata: FieldMetadataInterface,
|
||||
target: string,
|
||||
type: FieldMetadataType,
|
||||
kind: ObjectTypeDefinitionKind,
|
||||
buildOtions: WorkspaceBuildSchemaOptions,
|
||||
typeOptions: TypeOptions,
|
||||
): GraphQLOutputType {
|
||||
const target = isCompositeFieldMetadataType(fieldMetadata.type)
|
||||
? fieldMetadata.type.toString()
|
||||
: fieldMetadata.id;
|
||||
let gqlType: GraphQLOutputType | undefined =
|
||||
this.typeMapperService.mapToScalarType(
|
||||
fieldMetadata.type,
|
||||
type,
|
||||
buildOtions.dateScalarMode,
|
||||
buildOtions.numberScalarMode,
|
||||
);
|
||||
|
||||
gqlType ??= this.typeDefinitionsStorage.getObjectTypeByKey(target, kind);
|
||||
|
||||
gqlType ??= this.typeDefinitionsStorage.getEnumTypeByKey(target);
|
||||
gqlType ??= this.typeDefinitionsStorage.getOutputTypeByKey(target, kind);
|
||||
|
||||
if (!gqlType) {
|
||||
this.logger.error(`Could not find a GraphQL type for ${target}`, {
|
||||
fieldMetadata,
|
||||
type,
|
||||
buildOtions,
|
||||
typeOptions,
|
||||
});
|
||||
|
||||
@ -57,7 +57,6 @@ export class TypeMapperService {
|
||||
const numberScalar =
|
||||
numberScalarMode === 'float' ? GraphQLFloat : GraphQLInt;
|
||||
|
||||
// LINK and CURRENCY are handled in the factories because they are objects
|
||||
const typeScalarMapping = new Map<FieldMetadataType, GraphQLScalarType>([
|
||||
[FieldMetadataType.UUID, GraphQLID],
|
||||
[FieldMetadataType.TEXT, GraphQLString],
|
||||
@ -86,7 +85,6 @@ export class TypeMapperService {
|
||||
const numberScalar =
|
||||
numberScalarMode === 'float' ? FloatFilterType : IntFilterType;
|
||||
|
||||
// LINK and CURRENCY are handled in the factories because they are objects
|
||||
const typeFilterMapping = new Map<
|
||||
FieldMetadataType,
|
||||
GraphQLInputObjectType | GraphQLScalarType
|
||||
@ -111,7 +109,6 @@ export class TypeMapperService {
|
||||
mapToOrderByType(
|
||||
fieldMetadataType: FieldMetadataType,
|
||||
): GraphQLInputType | undefined {
|
||||
// LINK and CURRENCY are handled in the factories because they are objects
|
||||
const typeOrderByMapping = new Map<FieldMetadataType, GraphQLEnumType>([
|
||||
[FieldMetadataType.UUID, OrderByDirectionType],
|
||||
[FieldMetadataType.TEXT, OrderByDirectionType],
|
||||
|
||||
@ -17,7 +17,12 @@ import {
|
||||
ObjectTypeDefinitionKind,
|
||||
} from 'src/engine/api/graphql/workspace-schema-builder/factories/object-type-definition.factory';
|
||||
|
||||
// Must be scoped on REQUEST level
|
||||
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>();
|
||||
@ -68,10 +73,27 @@ export class TypeDefinitionsStorage {
|
||||
getInputTypeByKey(
|
||||
target: string,
|
||||
kind: InputTypeDefinitionKind,
|
||||
): GraphQLInputObjectType | undefined {
|
||||
return this.inputTypeDefinitions.get(
|
||||
this.generateCompositeKey(target, kind),
|
||||
)?.type;
|
||||
): 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 {
|
||||
|
||||
@ -1,15 +1,12 @@
|
||||
import { Injectable, Logger } from '@nestjs/common';
|
||||
|
||||
import { ObjectMetadataInterface } from 'src/engine/metadata-modules/field-metadata/interfaces/object-metadata.interface';
|
||||
import { FieldMetadataInterface } from 'src/engine/metadata-modules/field-metadata/interfaces/field-metadata.interface';
|
||||
import { CompositeType } from 'src/engine/metadata-modules/field-metadata/interfaces/composite-type.interface';
|
||||
|
||||
import { FieldMetadataEntity } from 'src/engine/metadata-modules/field-metadata/field-metadata.entity';
|
||||
import { customTableDefaultColumns } from 'src/engine/workspace-manager/workspace-migration-runner/utils/custom-table-default-column.util';
|
||||
import { fullNameObjectDefinition } from 'src/engine/metadata-modules/field-metadata/composite-types/full-name.composite-type';
|
||||
import { currencyObjectDefinition } from 'src/engine/metadata-modules/field-metadata/composite-types/currency.composite-type';
|
||||
import { linkObjectDefinition } from 'src/engine/metadata-modules/field-metadata/composite-types/link.composite-type';
|
||||
import { EnumTypeDefinitionFactory } from 'src/engine/api/graphql/workspace-schema-builder/factories/enum-type-definition.factory';
|
||||
import { addressObjectDefinition } from 'src/engine/metadata-modules/field-metadata/composite-types/address.composite-type';
|
||||
import { compositeTypeDefintions } from 'src/engine/metadata-modules/field-metadata/composite-types';
|
||||
import { CompositeObjectTypeDefinitionFactory } from 'src/engine/api/graphql/workspace-schema-builder/factories/composite-object-type-definition.factory';
|
||||
import { CompositeInputTypeDefinitionFactory } from 'src/engine/api/graphql/workspace-schema-builder/factories/composite-input-type-definition.factory';
|
||||
|
||||
import { TypeDefinitionsStorage } from './storages/type-definitions.storage';
|
||||
import {
|
||||
@ -20,24 +17,12 @@ import {
|
||||
InputTypeDefinitionFactory,
|
||||
InputTypeDefinitionKind,
|
||||
} from './factories/input-type-definition.factory';
|
||||
import { getFieldMetadataType } from './utils/get-field-metadata-type.util';
|
||||
import { WorkspaceBuildSchemaOptions } from './interfaces/workspace-build-schema-optionts.interface';
|
||||
import { FilterTypeDefinitionFactory } from './factories/filter-type-definition.factory';
|
||||
import { ConnectionTypeDefinitionFactory } from './factories/connection-type-definition.factory';
|
||||
import { EdgeTypeDefinitionFactory } from './factories/edge-type-definition.factory';
|
||||
import { OrderByTypeDefinitionFactory } from './factories/order-by-type-definition.factory';
|
||||
import { ExtendObjectTypeDefinitionFactory } from './factories/extend-object-type-definition.factory';
|
||||
import { objectContainsRelationField } from './utils/object-contains-relation-field';
|
||||
|
||||
// Create a default field for each custom table default column
|
||||
const defaultFields = customTableDefaultColumns.map((column) => {
|
||||
return {
|
||||
type: getFieldMetadataType(column.type),
|
||||
name: column.name,
|
||||
isNullable: true,
|
||||
} as FieldMetadataEntity;
|
||||
});
|
||||
|
||||
@Injectable()
|
||||
export class TypeDefinitionsGenerator {
|
||||
private readonly logger = new Logger(TypeDefinitionsGenerator.name);
|
||||
@ -45,10 +30,10 @@ export class TypeDefinitionsGenerator {
|
||||
constructor(
|
||||
private readonly typeDefinitionsStorage: TypeDefinitionsStorage,
|
||||
private readonly objectTypeDefinitionFactory: ObjectTypeDefinitionFactory,
|
||||
private readonly compositeObjectTypeDefinitionFactory: CompositeObjectTypeDefinitionFactory,
|
||||
private readonly enumTypeDefinitionFactory: EnumTypeDefinitionFactory,
|
||||
private readonly inputTypeDefinitionFactory: InputTypeDefinitionFactory,
|
||||
private readonly filterTypeDefintionFactory: FilterTypeDefinitionFactory,
|
||||
private readonly orderByTypeDefinitionFactory: OrderByTypeDefinitionFactory,
|
||||
private readonly compositeInputTypeDefinitionFactory: CompositeInputTypeDefinitionFactory,
|
||||
private readonly edgeTypeDefinitionFactory: EdgeTypeDefinitionFactory,
|
||||
private readonly connectionTypeDefinitionFactory: ConnectionTypeDefinitionFactory,
|
||||
private readonly extendObjectTypeDefinitionFactory: ExtendObjectTypeDefinitionFactory,
|
||||
@ -58,38 +43,96 @@ export class TypeDefinitionsGenerator {
|
||||
objectMetadataCollection: ObjectMetadataInterface[],
|
||||
options: WorkspaceBuildSchemaOptions,
|
||||
) {
|
||||
// Generate static objects first because they can be used in dynamic objects
|
||||
this.generateStaticObjectTypeDefs(options);
|
||||
// Generate dynamic objects
|
||||
this.generateDynamicObjectTypeDefs(objectMetadataCollection, options);
|
||||
// Generate composite type objects first because they can be used in dynamic objects
|
||||
this.generateCompositeTypeDefs(options);
|
||||
// Generate metadata objects
|
||||
this.generateMetadataTypeDefs(objectMetadataCollection, options);
|
||||
}
|
||||
|
||||
private generateStaticObjectTypeDefs(options: WorkspaceBuildSchemaOptions) {
|
||||
const staticObjectMetadataCollection = [
|
||||
currencyObjectDefinition,
|
||||
linkObjectDefinition,
|
||||
fullNameObjectDefinition,
|
||||
addressObjectDefinition,
|
||||
] satisfies ObjectMetadataInterface[];
|
||||
/**
|
||||
* GENERATE COMPOSITE TYPE OBJECTS
|
||||
*/
|
||||
private generateCompositeTypeDefs(options: WorkspaceBuildSchemaOptions) {
|
||||
const compositeTypeCollection = [...compositeTypeDefintions.values()];
|
||||
|
||||
this.logger.log(
|
||||
`Generating staticObjects: [${staticObjectMetadataCollection
|
||||
.map((object) => object.nameSingular)
|
||||
`Generating composite type objects: [${compositeTypeCollection
|
||||
.map((compositeType) => compositeType.type)
|
||||
.join(', ')}]`,
|
||||
);
|
||||
|
||||
// Generate static objects first because they can be used in dynamic objects
|
||||
this.generateEnumTypeDefs(staticObjectMetadataCollection, options);
|
||||
this.generateObjectTypeDefs(staticObjectMetadataCollection, options);
|
||||
this.generateInputTypeDefs(staticObjectMetadataCollection, options);
|
||||
// Generate composite types first because they can be used in metadata objects
|
||||
this.generateCompositeObjectTypeDefs(compositeTypeCollection, options);
|
||||
this.generateCompositeInputTypeDefs(compositeTypeCollection, options);
|
||||
}
|
||||
|
||||
private generateDynamicObjectTypeDefs(
|
||||
private generateCompositeObjectTypeDefs(
|
||||
compositeTypes: CompositeType[],
|
||||
options: WorkspaceBuildSchemaOptions,
|
||||
) {
|
||||
const compositeObjectTypeDefs = compositeTypes.map((compositeType) =>
|
||||
this.compositeObjectTypeDefinitionFactory.create(compositeType, options),
|
||||
);
|
||||
|
||||
this.typeDefinitionsStorage.addObjectTypes(compositeObjectTypeDefs);
|
||||
}
|
||||
|
||||
private generateCompositeInputTypeDefs(
|
||||
compisteTypes: CompositeType[],
|
||||
options: WorkspaceBuildSchemaOptions,
|
||||
) {
|
||||
const inputTypeDefs = compisteTypes
|
||||
.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 generateMetadataTypeDefs(
|
||||
dynamicObjectMetadataCollection: ObjectMetadataInterface[],
|
||||
options: WorkspaceBuildSchemaOptions,
|
||||
) {
|
||||
this.logger.log(
|
||||
`Generating dynamicObjects: [${dynamicObjectMetadataCollection
|
||||
`Generating metadata objects: [${dynamicObjectMetadataCollection
|
||||
.map((object) => object.nameSingular)
|
||||
.join(', ')}]`,
|
||||
);
|
||||
@ -106,22 +149,16 @@ export class TypeDefinitionsGenerator {
|
||||
}
|
||||
|
||||
private generateObjectTypeDefs(
|
||||
objectMetadataCollection: ObjectMetadataInterface[],
|
||||
objectMetadataCollection: ObjectMetadataInterface[] | CompositeType[],
|
||||
options: WorkspaceBuildSchemaOptions,
|
||||
) {
|
||||
const objectTypeDefs = objectMetadataCollection.map((objectMetadata) => {
|
||||
const fields = this.mergeFieldsWithDefaults(objectMetadata.fields);
|
||||
const extendedObjectMetadata = {
|
||||
...objectMetadata,
|
||||
fields,
|
||||
};
|
||||
|
||||
return this.objectTypeDefinitionFactory.create(
|
||||
extendedObjectMetadata,
|
||||
const objectTypeDefs = objectMetadataCollection.map((objectMetadata) =>
|
||||
this.objectTypeDefinitionFactory.create(
|
||||
objectMetadata,
|
||||
ObjectTypeDefinitionKind.Plain,
|
||||
options,
|
||||
);
|
||||
});
|
||||
),
|
||||
);
|
||||
|
||||
this.typeDefinitionsStorage.addObjectTypes(objectTypeDefs);
|
||||
}
|
||||
@ -130,35 +167,15 @@ export class TypeDefinitionsGenerator {
|
||||
objectMetadataCollection: ObjectMetadataInterface[],
|
||||
options: WorkspaceBuildSchemaOptions,
|
||||
) {
|
||||
const edgeTypeDefs = objectMetadataCollection.map((objectMetadata) => {
|
||||
const fields = this.mergeFieldsWithDefaults(objectMetadata.fields);
|
||||
const extendedObjectMetadata = {
|
||||
...objectMetadata,
|
||||
fields,
|
||||
};
|
||||
|
||||
return this.edgeTypeDefinitionFactory.create(
|
||||
extendedObjectMetadata,
|
||||
options,
|
||||
);
|
||||
});
|
||||
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) => {
|
||||
const fields = this.mergeFieldsWithDefaults(objectMetadata.fields);
|
||||
const extendedObjectMetadata = {
|
||||
...objectMetadata,
|
||||
fields,
|
||||
};
|
||||
|
||||
return this.connectionTypeDefinitionFactory.create(
|
||||
extendedObjectMetadata,
|
||||
options,
|
||||
);
|
||||
},
|
||||
const connectionTypeDefs = objectMetadataCollection.map((objectMetadata) =>
|
||||
this.connectionTypeDefinitionFactory.create(objectMetadata, options),
|
||||
);
|
||||
|
||||
this.typeDefinitionsStorage.addObjectTypes(connectionTypeDefs);
|
||||
@ -170,20 +187,18 @@ export class TypeDefinitionsGenerator {
|
||||
) {
|
||||
const inputTypeDefs = objectMetadataCollection
|
||||
.map((objectMetadata) => {
|
||||
const fields = this.mergeFieldsWithDefaults(objectMetadata.fields);
|
||||
const requiredExtendedObjectMetadata = {
|
||||
...objectMetadata,
|
||||
fields,
|
||||
};
|
||||
const optionalExtendedObjectMetadata = {
|
||||
...objectMetadata,
|
||||
fields: fields.map((field) => ({ ...field, isNullable: true })),
|
||||
fields: objectMetadata.fields.map((field) => ({
|
||||
...field,
|
||||
isNullable: true,
|
||||
})),
|
||||
};
|
||||
|
||||
return [
|
||||
// Input type for create
|
||||
this.inputTypeDefinitionFactory.create(
|
||||
requiredExtendedObjectMetadata,
|
||||
objectMetadata,
|
||||
InputTypeDefinitionKind.Create,
|
||||
options,
|
||||
),
|
||||
@ -194,13 +209,15 @@ export class TypeDefinitionsGenerator {
|
||||
options,
|
||||
),
|
||||
// Filter input type
|
||||
this.filterTypeDefintionFactory.create(
|
||||
this.inputTypeDefinitionFactory.create(
|
||||
optionalExtendedObjectMetadata,
|
||||
InputTypeDefinitionKind.Filter,
|
||||
options,
|
||||
),
|
||||
// OrderBy input type
|
||||
this.orderByTypeDefinitionFactory.create(
|
||||
this.inputTypeDefinitionFactory.create(
|
||||
optionalExtendedObjectMetadata,
|
||||
InputTypeDefinitionKind.OrderBy,
|
||||
options,
|
||||
),
|
||||
];
|
||||
@ -237,16 +254,4 @@ export class TypeDefinitionsGenerator {
|
||||
|
||||
this.typeDefinitionsStorage.addObjectTypes(objectTypeDefs);
|
||||
}
|
||||
|
||||
private mergeFieldsWithDefaults(
|
||||
fields: FieldMetadataInterface[],
|
||||
): FieldMetadataInterface[] {
|
||||
const fieldNames = new Set(fields.map((field) => field.name));
|
||||
|
||||
const uniqueDefaultFields = defaultFields.filter(
|
||||
(defaultField) => !fieldNames.has(defaultField.name),
|
||||
);
|
||||
|
||||
return [...fields, ...uniqueDefaultFields];
|
||||
}
|
||||
}
|
||||
|
||||
@ -12,10 +12,10 @@ import { TypeMapperService } from './services/type-mapper.service';
|
||||
@Module({
|
||||
imports: [ObjectMetadataModule],
|
||||
providers: [
|
||||
...workspaceSchemaBuilderFactories,
|
||||
TypeDefinitionsGenerator,
|
||||
TypeDefinitionsStorage,
|
||||
TypeMapperService,
|
||||
...workspaceSchemaBuilderFactories,
|
||||
TypeDefinitionsGenerator,
|
||||
WorkspaceGraphQLSchemaFactory,
|
||||
],
|
||||
exports: [WorkspaceGraphQLSchemaFactory],
|
||||
|
||||
@ -1,36 +1,55 @@
|
||||
import { BadRequestException } from '@nestjs/common';
|
||||
|
||||
import { compositeTypeDefintions } from 'src/engine/metadata-modules/field-metadata/composite-types';
|
||||
import { FieldMetadataType } 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 { ObjectMetadataEntity } from 'src/engine/metadata-modules/object-metadata/object-metadata.entity';
|
||||
import { computeObjectTargetTable } from 'src/engine/utils/compute-object-target-table.util';
|
||||
|
||||
export const getFieldType = (
|
||||
objectMetadataItem,
|
||||
fieldName,
|
||||
objectMetadata: ObjectMetadataEntity,
|
||||
fieldName: string,
|
||||
): FieldMetadataType | undefined => {
|
||||
for (const itemField of objectMetadataItem.fields) {
|
||||
if (fieldName === itemField.name) {
|
||||
return itemField.type;
|
||||
for (const fieldMetdata of objectMetadata.fields) {
|
||||
if (fieldName === fieldMetdata.name) {
|
||||
return fieldMetdata.type;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
export const checkFields = (objectMetadataItem, fieldNames): void => {
|
||||
export const checkFields = (
|
||||
objectMetadata: ObjectMetadataEntity,
|
||||
fieldNames: string[],
|
||||
): void => {
|
||||
const fieldMetadataNames = objectMetadata.fields
|
||||
.map((field) => {
|
||||
if (isCompositeFieldMetadataType(field.type)) {
|
||||
const compositeType = compositeTypeDefintions.get(field.type);
|
||||
|
||||
if (!compositeType) {
|
||||
throw new BadRequestException(
|
||||
`Composite type '${field.type}' not found`,
|
||||
);
|
||||
}
|
||||
|
||||
// TODO: Don't really know why we need to put fieldName and compositeType name here
|
||||
return [
|
||||
field.name,
|
||||
compositeType.properties.map(
|
||||
(compositeProperty) => compositeProperty.name,
|
||||
),
|
||||
].flat();
|
||||
}
|
||||
|
||||
return field.name;
|
||||
})
|
||||
.flat();
|
||||
|
||||
for (const fieldName of fieldNames) {
|
||||
if (
|
||||
!objectMetadataItem.fields
|
||||
.reduce(
|
||||
(acc, itemField) => [
|
||||
...acc,
|
||||
itemField.name,
|
||||
...Object.keys(itemField.targetColumnMap),
|
||||
],
|
||||
[],
|
||||
)
|
||||
.includes(fieldName)
|
||||
) {
|
||||
if (!fieldMetadataNames.includes(fieldName)) {
|
||||
throw new BadRequestException(
|
||||
`field '${fieldName}' does not exist in '${computeObjectTargetTable(
|
||||
objectMetadataItem,
|
||||
objectMetadata,
|
||||
)}' object`,
|
||||
);
|
||||
}
|
||||
|
||||
@ -3,6 +3,7 @@ import { RelationMetadataType } from 'src/engine/metadata-modules/relation-metad
|
||||
|
||||
const DEFAULT_DEPTH_VALUE = 2;
|
||||
|
||||
// TODO: Should be properly type and based on composite type definitions
|
||||
export const mapFieldMetadataToGraphqlQuery = (
|
||||
objectMetadataItems,
|
||||
field,
|
||||
|
||||
Reference in New Issue
Block a user