feat: conditional schema based on column map instead of column field (#1978)
* feat: wip conditional schema based on column map instead of column field * feat: conditionalSchema columnMap and singular plural * fix: remove uuid fix * feat: add name and label (singular/plural) drop old tableColumnName
This commit is contained in:
@ -1,6 +1,7 @@
|
||||
import { FieldMetadata } from 'src/metadata/field-metadata/field-metadata.entity';
|
||||
|
||||
export interface SchemaBuilderContext {
|
||||
entityName: string;
|
||||
tableName: string;
|
||||
workspaceId: string;
|
||||
fieldAliases: Record<string, string>;
|
||||
fields: FieldMetadata[];
|
||||
}
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
import { Injectable, InternalServerErrorException } from '@nestjs/common';
|
||||
import { Injectable } from '@nestjs/common';
|
||||
|
||||
import {
|
||||
GraphQLFieldConfigMap,
|
||||
@ -9,9 +9,9 @@ import {
|
||||
GraphQLObjectType,
|
||||
GraphQLSchema,
|
||||
} from 'graphql';
|
||||
import upperFirst from 'lodash.upperfirst';
|
||||
|
||||
import { EntityResolverService } from 'src/tenant/entity-resolver/entity-resolver.service';
|
||||
import { pascalCase } from 'src/utils/pascal-case';
|
||||
import { ObjectMetadata } from 'src/metadata/object-metadata/object-metadata.entity';
|
||||
|
||||
import { generateEdgeType } from './utils/generate-edge-type.util';
|
||||
@ -20,6 +20,7 @@ import { generateObjectType } from './utils/generate-object-type.util';
|
||||
import { generateCreateInputType } from './utils/generate-create-input-type.util';
|
||||
import { generateUpdateInputType } from './utils/generate-update-input-type.util';
|
||||
import { SchemaBuilderContext } from './interfaces/schema-builder-context.interface';
|
||||
import { cleanEntityName } from './utils/clean-entity-name.util';
|
||||
|
||||
@Injectable()
|
||||
export class SchemaBuilderService {
|
||||
@ -28,31 +29,25 @@ export class SchemaBuilderService {
|
||||
constructor(private readonly entityResolverService: EntityResolverService) {}
|
||||
|
||||
private generateQueryFieldForEntity(
|
||||
entityName: string,
|
||||
entityName: {
|
||||
singular: string;
|
||||
plural: string;
|
||||
},
|
||||
tableName: string,
|
||||
ObjectType: GraphQLObjectType,
|
||||
objectDefinition: ObjectMetadata,
|
||||
) {
|
||||
const fieldAliases =
|
||||
objectDefinition?.fields.reduce(
|
||||
(acc, field) => ({
|
||||
...acc,
|
||||
[field.displayName]: field.targetColumnName,
|
||||
}),
|
||||
{},
|
||||
) || {};
|
||||
const schemaBuilderContext: SchemaBuilderContext = {
|
||||
entityName,
|
||||
tableName,
|
||||
workspaceId: this.workspaceId,
|
||||
fieldAliases,
|
||||
fields: objectDefinition.fields,
|
||||
};
|
||||
|
||||
const EdgeType = generateEdgeType(ObjectType);
|
||||
const ConnectionType = generateConnectionType(EdgeType);
|
||||
|
||||
return {
|
||||
[`findMany${pascalCase(entityName)}`]: {
|
||||
[`${entityName.plural}`]: {
|
||||
type: ConnectionType,
|
||||
resolve: async (root, args, context, info) => {
|
||||
return this.entityResolverService.findMany(
|
||||
@ -61,7 +56,7 @@ export class SchemaBuilderService {
|
||||
);
|
||||
},
|
||||
},
|
||||
[`findOne${pascalCase(entityName)}`]: {
|
||||
[`${entityName.singular}`]: {
|
||||
type: ObjectType,
|
||||
args: {
|
||||
id: { type: new GraphQLNonNull(GraphQLID) },
|
||||
@ -78,30 +73,24 @@ export class SchemaBuilderService {
|
||||
}
|
||||
|
||||
private generateMutationFieldForEntity(
|
||||
entityName: string,
|
||||
entityName: {
|
||||
singular: string;
|
||||
plural: string;
|
||||
},
|
||||
tableName: string,
|
||||
ObjectType: GraphQLObjectType,
|
||||
CreateInputType: GraphQLInputObjectType,
|
||||
UpdateInputType: GraphQLInputObjectType,
|
||||
objectDefinition: ObjectMetadata,
|
||||
) {
|
||||
const fieldAliases =
|
||||
objectDefinition?.fields.reduce(
|
||||
(acc, field) => ({
|
||||
...acc,
|
||||
[field.displayName]: field.targetColumnName,
|
||||
}),
|
||||
{},
|
||||
) || {};
|
||||
const schemaBuilderContext: SchemaBuilderContext = {
|
||||
entityName,
|
||||
tableName,
|
||||
workspaceId: this.workspaceId,
|
||||
fieldAliases,
|
||||
fields: objectDefinition.fields,
|
||||
};
|
||||
|
||||
return {
|
||||
[`createOne${pascalCase(entityName)}`]: {
|
||||
[`createOne${upperFirst(entityName.singular)}`]: {
|
||||
type: new GraphQLNonNull(ObjectType),
|
||||
args: {
|
||||
data: { type: new GraphQLNonNull(CreateInputType) },
|
||||
@ -114,7 +103,7 @@ export class SchemaBuilderService {
|
||||
);
|
||||
},
|
||||
},
|
||||
[`createMany${pascalCase(entityName)}`]: {
|
||||
[`createMany${upperFirst(entityName.singular)}`]: {
|
||||
type: new GraphQLList(ObjectType),
|
||||
args: {
|
||||
data: {
|
||||
@ -131,7 +120,7 @@ export class SchemaBuilderService {
|
||||
);
|
||||
},
|
||||
},
|
||||
[`updateOne${pascalCase(entityName)}`]: {
|
||||
[`updateOne${upperFirst(entityName.singular)}`]: {
|
||||
type: new GraphQLNonNull(ObjectType),
|
||||
args: {
|
||||
id: { type: new GraphQLNonNull(GraphQLID) },
|
||||
@ -156,33 +145,29 @@ export class SchemaBuilderService {
|
||||
const mutationFields: any = {};
|
||||
|
||||
for (const objectDefinition of objectMetadata) {
|
||||
if (objectDefinition.fields.length === 0) {
|
||||
// A graphql type must define one or more fields
|
||||
continue;
|
||||
}
|
||||
const entityName = {
|
||||
singular: cleanEntityName(objectDefinition.nameSingular),
|
||||
plural: cleanEntityName(objectDefinition.namePlural),
|
||||
};
|
||||
|
||||
const tableName = objectDefinition?.targetTableName ?? '';
|
||||
const ObjectType = generateObjectType(
|
||||
objectDefinition.displayName,
|
||||
entityName.singular,
|
||||
objectDefinition.fields,
|
||||
);
|
||||
const CreateInputType = generateCreateInputType(
|
||||
objectDefinition.displayName,
|
||||
entityName.singular,
|
||||
objectDefinition.fields,
|
||||
);
|
||||
const UpdateInputType = generateUpdateInputType(
|
||||
objectDefinition.displayName,
|
||||
entityName.singular,
|
||||
objectDefinition.fields,
|
||||
);
|
||||
|
||||
if (!objectDefinition) {
|
||||
throw new InternalServerErrorException('Object definition not found');
|
||||
}
|
||||
|
||||
Object.assign(
|
||||
queryFields,
|
||||
this.generateQueryFieldForEntity(
|
||||
objectDefinition.displayName,
|
||||
entityName,
|
||||
tableName,
|
||||
ObjectType,
|
||||
objectDefinition,
|
||||
@ -192,7 +177,7 @@ export class SchemaBuilderService {
|
||||
Object.assign(
|
||||
mutationFields,
|
||||
this.generateMutationFieldForEntity(
|
||||
objectDefinition.displayName,
|
||||
entityName,
|
||||
tableName,
|
||||
ObjectType,
|
||||
CreateInputType,
|
||||
|
||||
@ -0,0 +1,22 @@
|
||||
import { cleanEntityName } from 'src/tenant/schema-builder/utils/clean-entity-name.util';
|
||||
|
||||
describe('cleanEntityName', () => {
|
||||
test('should camelCase strings', () => {
|
||||
expect(cleanEntityName('hello world')).toBe('helloWorld');
|
||||
expect(cleanEntityName('my name is John')).toBe('myNameIsJohn');
|
||||
});
|
||||
|
||||
test('should remove numbers at the beginning', () => {
|
||||
expect(cleanEntityName('123hello')).toBe('hello');
|
||||
expect(cleanEntityName('456hello world')).toBe('helloWorld');
|
||||
});
|
||||
|
||||
test('should remove special characters', () => {
|
||||
expect(cleanEntityName('hello$world')).toBe('helloWorld');
|
||||
expect(cleanEntityName('some#special&chars')).toBe('someSpecialChars');
|
||||
});
|
||||
|
||||
test('should handle empty strings', () => {
|
||||
expect(cleanEntityName('')).toBe('');
|
||||
});
|
||||
});
|
||||
@ -0,0 +1,52 @@
|
||||
import {
|
||||
GraphQLList,
|
||||
GraphQLNonNull,
|
||||
GraphQLObjectType,
|
||||
GraphQLString,
|
||||
} from 'graphql';
|
||||
|
||||
import { PageInfoType } from 'src/tenant/schema-builder/utils/page-into-type.util';
|
||||
import { generateConnectionType } from 'src/tenant/schema-builder/utils/generate-connection-type.util';
|
||||
|
||||
describe('generateConnectionType', () => {
|
||||
// Create a mock EdgeType for testing
|
||||
const mockEdgeType = new GraphQLObjectType({
|
||||
name: 'MockEdge',
|
||||
fields: {
|
||||
node: { type: GraphQLString },
|
||||
cursor: { type: GraphQLString },
|
||||
},
|
||||
});
|
||||
|
||||
// Generate a connection type using the mock
|
||||
const MockConnectionType = generateConnectionType(mockEdgeType);
|
||||
|
||||
test('should generate a GraphQLObjectType', () => {
|
||||
expect(MockConnectionType).toBeInstanceOf(GraphQLObjectType);
|
||||
});
|
||||
|
||||
test('should generate a type with the correct name', () => {
|
||||
expect(MockConnectionType.name).toBe('MockConnection');
|
||||
});
|
||||
|
||||
test('should include the correct fields', () => {
|
||||
const fields = MockConnectionType.getFields();
|
||||
|
||||
expect(fields).toHaveProperty('edges');
|
||||
if (
|
||||
fields.edges.type instanceof GraphQLList ||
|
||||
fields.edges.type instanceof GraphQLNonNull
|
||||
) {
|
||||
expect(fields.edges.type.ofType).toBe(mockEdgeType);
|
||||
} else {
|
||||
fail('edges.type is not an instance of GraphQLList or GraphQLNonNull');
|
||||
}
|
||||
|
||||
expect(fields).toHaveProperty('pageInfo');
|
||||
if (fields.pageInfo.type instanceof GraphQLNonNull) {
|
||||
expect(fields.pageInfo.type.ofType).toBe(PageInfoType);
|
||||
} else {
|
||||
fail('pageInfo.type is not an instance of GraphQLNonNull');
|
||||
}
|
||||
});
|
||||
});
|
||||
@ -0,0 +1,56 @@
|
||||
import {
|
||||
GraphQLID,
|
||||
GraphQLInputObjectType,
|
||||
GraphQLInt,
|
||||
GraphQLNonNull,
|
||||
GraphQLString,
|
||||
} from 'graphql';
|
||||
|
||||
import { FieldMetadata } from 'src/metadata/field-metadata/field-metadata.entity';
|
||||
import { generateCreateInputType } from 'src/tenant/schema-builder/utils/generate-create-input-type.util';
|
||||
|
||||
describe('generateCreateInputType', () => {
|
||||
test('should generate a GraphQLInputObjectType with correct name', () => {
|
||||
const columns = [];
|
||||
const name = 'testType';
|
||||
const inputType = generateCreateInputType(name, columns);
|
||||
expect(inputType).toBeInstanceOf(GraphQLInputObjectType);
|
||||
expect(inputType.name).toBe('TestTypeCreateInput');
|
||||
});
|
||||
|
||||
test('should include default id field', () => {
|
||||
const columns = [];
|
||||
const name = 'testType';
|
||||
const inputType = generateCreateInputType(name, columns);
|
||||
const fields = inputType.getFields();
|
||||
expect(fields.id).toBeDefined();
|
||||
expect(fields.id.type).toBe(GraphQLID);
|
||||
});
|
||||
|
||||
test('should generate fields with correct types and descriptions', () => {
|
||||
const columns = [
|
||||
{
|
||||
nameSingular: 'firstName',
|
||||
type: 'text',
|
||||
isNullable: false,
|
||||
},
|
||||
{
|
||||
nameSingular: 'age',
|
||||
type: 'number',
|
||||
isNullable: true,
|
||||
},
|
||||
] as FieldMetadata[];
|
||||
|
||||
const name = 'testType';
|
||||
const inputType = generateCreateInputType(name, columns);
|
||||
const fields = inputType.getFields();
|
||||
|
||||
if (fields.firstName.type instanceof GraphQLNonNull) {
|
||||
expect(fields.firstName.type.ofType).toBe(GraphQLString);
|
||||
} else {
|
||||
fail('firstName type is not an instance of GraphQLNonNull');
|
||||
}
|
||||
|
||||
expect(fields.age.type).toBe(GraphQLInt);
|
||||
});
|
||||
});
|
||||
@ -0,0 +1,38 @@
|
||||
import { GraphQLNonNull, GraphQLObjectType, GraphQLString } from 'graphql';
|
||||
|
||||
import { generateEdgeType } from 'src/tenant/schema-builder/utils/generate-edge-type.util';
|
||||
|
||||
describe('generateEdgeType', () => {
|
||||
// Mock GraphQLObjectType for testing
|
||||
const mockObjectType = new GraphQLObjectType({
|
||||
name: 'MockItem',
|
||||
fields: {
|
||||
sampleField: { type: GraphQLString },
|
||||
},
|
||||
});
|
||||
|
||||
test('should generate a GraphQLObjectType', () => {
|
||||
const edgeType = generateEdgeType(mockObjectType);
|
||||
expect(edgeType).toBeInstanceOf(GraphQLObjectType);
|
||||
});
|
||||
|
||||
test('should generate a type with the correct name', () => {
|
||||
const edgeType = generateEdgeType(mockObjectType);
|
||||
expect(edgeType.name).toBe('MockItemEdge');
|
||||
});
|
||||
|
||||
test('should have a "node" field of the provided ObjectType', () => {
|
||||
const edgeType = generateEdgeType(mockObjectType);
|
||||
const fields = edgeType.getFields();
|
||||
expect(fields.node.type).toBe(mockObjectType);
|
||||
});
|
||||
|
||||
test('should have a "cursor" field of type GraphQLNonNull(GraphQLString)', () => {
|
||||
const edgeType = generateEdgeType(mockObjectType);
|
||||
const fields = edgeType.getFields();
|
||||
expect(fields.cursor.type).toBeInstanceOf(GraphQLNonNull);
|
||||
if (fields.cursor.type instanceof GraphQLNonNull) {
|
||||
expect(fields.cursor.type.ofType).toBe(GraphQLString);
|
||||
}
|
||||
});
|
||||
});
|
||||
@ -0,0 +1,72 @@
|
||||
import {
|
||||
GraphQLID,
|
||||
GraphQLInt,
|
||||
GraphQLNonNull,
|
||||
GraphQLObjectType,
|
||||
GraphQLString,
|
||||
} from 'graphql';
|
||||
|
||||
import { FieldMetadata } from 'src/metadata/field-metadata/field-metadata.entity';
|
||||
import { generateObjectType } from 'src/tenant/schema-builder/utils/generate-object-type.util';
|
||||
|
||||
describe('generateObjectType', () => {
|
||||
test('should generate a GraphQLObjectType with correct name', () => {
|
||||
const columns = [];
|
||||
const name = 'testType';
|
||||
const objectType = generateObjectType(name, columns);
|
||||
expect(objectType).toBeInstanceOf(GraphQLObjectType);
|
||||
expect(objectType.name).toBe('TestType');
|
||||
});
|
||||
|
||||
test('should include default fields', () => {
|
||||
const columns = [];
|
||||
const name = 'testType';
|
||||
const objectType = generateObjectType(name, columns);
|
||||
const fields = objectType.getFields();
|
||||
|
||||
if (fields.id.type instanceof GraphQLNonNull) {
|
||||
expect(fields.id.type.ofType).toBe(GraphQLID);
|
||||
} else {
|
||||
fail('id.type is not an instance of GraphQLNonNull');
|
||||
}
|
||||
|
||||
if (fields.createdAt.type instanceof GraphQLNonNull) {
|
||||
expect(fields.createdAt.type.ofType).toBe(GraphQLString);
|
||||
} else {
|
||||
fail('createdAt.type is not an instance of GraphQLNonNull');
|
||||
}
|
||||
|
||||
if (fields.updatedAt.type instanceof GraphQLNonNull) {
|
||||
expect(fields.updatedAt.type.ofType).toBe(GraphQLString);
|
||||
} else {
|
||||
fail('updatedAt.type is not an instance of GraphQLNonNull');
|
||||
}
|
||||
});
|
||||
|
||||
test('should generate fields based on provided columns', () => {
|
||||
const columns = [
|
||||
{
|
||||
nameSingular: 'firstName',
|
||||
type: 'text',
|
||||
isNullable: false,
|
||||
},
|
||||
{
|
||||
nameSingular: 'age',
|
||||
type: 'number',
|
||||
isNullable: true,
|
||||
},
|
||||
] as FieldMetadata[];
|
||||
|
||||
const name = 'testType';
|
||||
const objectType = generateObjectType(name, columns);
|
||||
const fields = objectType.getFields();
|
||||
|
||||
if (fields.firstName.type instanceof GraphQLNonNull) {
|
||||
expect(fields.firstName.type.ofType).toBe(GraphQLString);
|
||||
} else {
|
||||
fail('firstName.type is not an instance of GraphQLNonNull');
|
||||
}
|
||||
|
||||
expect(fields.age.type).toBe(GraphQLInt);
|
||||
});
|
||||
});
|
||||
@ -0,0 +1,51 @@
|
||||
import {
|
||||
GraphQLID,
|
||||
GraphQLInputObjectType,
|
||||
GraphQLInt,
|
||||
GraphQLString,
|
||||
} from 'graphql';
|
||||
|
||||
import { FieldMetadata } from 'src/metadata/field-metadata/field-metadata.entity';
|
||||
import { generateUpdateInputType } from 'src/tenant/schema-builder/utils/generate-update-input-type.util';
|
||||
|
||||
describe('generateUpdateInputType', () => {
|
||||
test('should generate a GraphQLInputObjectType with correct name', () => {
|
||||
const columns = [];
|
||||
const name = 'testType';
|
||||
const inputType = generateUpdateInputType(name, columns);
|
||||
expect(inputType).toBeInstanceOf(GraphQLInputObjectType);
|
||||
expect(inputType.name).toBe('TestTypeUpdateInput');
|
||||
});
|
||||
|
||||
test('should include default id field', () => {
|
||||
const columns = [];
|
||||
const name = 'testType';
|
||||
const inputType = generateUpdateInputType(name, columns);
|
||||
const fields = inputType.getFields();
|
||||
expect(fields.id).toBeDefined();
|
||||
expect(fields.id.type).toBe(GraphQLID);
|
||||
});
|
||||
|
||||
test('should generate fields with correct types and descriptions', () => {
|
||||
const columns = [
|
||||
{
|
||||
nameSingular: 'firstName',
|
||||
type: 'text',
|
||||
isNullable: true,
|
||||
},
|
||||
{
|
||||
nameSingular: 'age',
|
||||
type: 'number',
|
||||
isNullable: true,
|
||||
},
|
||||
] as FieldMetadata[];
|
||||
|
||||
const name = 'testType';
|
||||
const inputType = generateUpdateInputType(name, columns);
|
||||
const fields = inputType.getFields();
|
||||
|
||||
expect(fields.firstName.type).toBe(GraphQLString);
|
||||
|
||||
expect(fields.age.type).toBe(GraphQLInt);
|
||||
});
|
||||
});
|
||||
@ -0,0 +1,77 @@
|
||||
import {
|
||||
GraphQLBoolean,
|
||||
GraphQLEnumType,
|
||||
GraphQLID,
|
||||
GraphQLInt,
|
||||
GraphQLString,
|
||||
} from 'graphql';
|
||||
|
||||
import { FieldMetadata } from 'src/metadata/field-metadata/field-metadata.entity';
|
||||
import { mapColumnTypeToGraphQLType } from 'src/tenant/schema-builder/utils/map-column-type-to-graphql-type.util';
|
||||
|
||||
describe('mapColumnTypeToGraphQLType', () => {
|
||||
test('should map uuid to GraphQLID', () => {
|
||||
const column = new FieldMetadata();
|
||||
column.type = 'uuid';
|
||||
expect(mapColumnTypeToGraphQLType(column)).toBe(GraphQLID);
|
||||
});
|
||||
|
||||
test('should map text, phone, email, and date to GraphQLString', () => {
|
||||
const types = ['text', 'phone', 'email', 'date'];
|
||||
types.forEach((type) => {
|
||||
const column = new FieldMetadata();
|
||||
column.type = type;
|
||||
expect(mapColumnTypeToGraphQLType(column)).toBe(GraphQLString);
|
||||
});
|
||||
});
|
||||
|
||||
test('should map boolean to GraphQLBoolean', () => {
|
||||
const column = new FieldMetadata();
|
||||
column.type = 'boolean';
|
||||
expect(mapColumnTypeToGraphQLType(column)).toBe(GraphQLBoolean);
|
||||
});
|
||||
|
||||
test('should map number to GraphQLInt', () => {
|
||||
const column = new FieldMetadata();
|
||||
column.type = 'number';
|
||||
expect(mapColumnTypeToGraphQLType(column)).toBe(GraphQLInt);
|
||||
});
|
||||
|
||||
test('should create a GraphQLEnumType for enum fields', () => {
|
||||
const column = new FieldMetadata();
|
||||
column.type = 'enum';
|
||||
column.nameSingular = 'Status';
|
||||
column.enums = ['ACTIVE', 'INACTIVE'];
|
||||
const result = mapColumnTypeToGraphQLType(column);
|
||||
|
||||
if (result instanceof GraphQLEnumType) {
|
||||
expect(result.name).toBe('StatusEnum');
|
||||
|
||||
const values = result.getValues().map((value) => value.value);
|
||||
expect(values).toContain('ACTIVE');
|
||||
expect(values).toContain('INACTIVE');
|
||||
} else {
|
||||
fail('Result is not an instance of GraphQLEnumType');
|
||||
}
|
||||
});
|
||||
|
||||
test('should map url to UrlObjectType or UrlInputType based on input flag', () => {
|
||||
const column = new FieldMetadata();
|
||||
column.type = 'url';
|
||||
expect(mapColumnTypeToGraphQLType(column, false).name).toBe('Url');
|
||||
expect(mapColumnTypeToGraphQLType(column, true).name).toBe('UrlInput');
|
||||
});
|
||||
|
||||
test('should map money to MoneyObjectType or MoneyInputType based on input flag', () => {
|
||||
const column = new FieldMetadata();
|
||||
column.type = 'money';
|
||||
expect(mapColumnTypeToGraphQLType(column, false).name).toBe('Money');
|
||||
expect(mapColumnTypeToGraphQLType(column, true).name).toBe('MoneyInput');
|
||||
});
|
||||
|
||||
test('should default to GraphQLString for unknown types', () => {
|
||||
const column = new FieldMetadata();
|
||||
column.type = 'unknown';
|
||||
expect(mapColumnTypeToGraphQLType(column)).toBe(GraphQLString);
|
||||
});
|
||||
});
|
||||
@ -0,0 +1,17 @@
|
||||
import { camelCase } from 'src/utils/camel-case';
|
||||
|
||||
export const cleanEntityName = (entityName: string) => {
|
||||
// Remove all leading numbers
|
||||
let camelCasedEntityName = entityName.replace(/^[0-9]+/, '');
|
||||
|
||||
// Trim the string
|
||||
camelCasedEntityName = camelCasedEntityName.trim();
|
||||
|
||||
// Camel case the string
|
||||
camelCasedEntityName = camelCase(camelCasedEntityName);
|
||||
|
||||
// Remove all special characters but keep alphabets and numbers
|
||||
camelCasedEntityName = camelCasedEntityName.replace(/[^a-zA-Z0-9]/g, '');
|
||||
|
||||
return camelCasedEntityName;
|
||||
};
|
||||
@ -1,4 +1,4 @@
|
||||
import { GraphQLInputObjectType, GraphQLNonNull } from 'graphql';
|
||||
import { GraphQLID, GraphQLInputObjectType, GraphQLNonNull } from 'graphql';
|
||||
|
||||
import { FieldMetadata } from 'src/metadata/field-metadata/field-metadata.entity';
|
||||
import { pascalCase } from 'src/utils/pascal-case';
|
||||
@ -15,14 +15,15 @@ export const generateCreateInputType = (
|
||||
name: string,
|
||||
columns: FieldMetadata[],
|
||||
): GraphQLInputObjectType => {
|
||||
const fields: Record<string, any> = {};
|
||||
const fields: Record<string, any> = {
|
||||
id: { type: GraphQLID },
|
||||
};
|
||||
|
||||
columns.forEach((column) => {
|
||||
const graphqlType = mapColumnTypeToGraphQLType(column);
|
||||
const graphqlType = mapColumnTypeToGraphQLType(column, true);
|
||||
|
||||
fields[column.displayName] = {
|
||||
fields[column.nameSingular] = {
|
||||
type: !column.isNullable ? new GraphQLNonNull(graphqlType) : graphqlType,
|
||||
description: column.targetColumnName,
|
||||
};
|
||||
});
|
||||
|
||||
|
||||
@ -33,9 +33,8 @@ export const generateObjectType = <TSource = any, TContext = any>(
|
||||
columns.forEach((column) => {
|
||||
const graphqlType = mapColumnTypeToGraphQLType(column);
|
||||
|
||||
fields[column.displayName] = {
|
||||
fields[column.nameSingular] = {
|
||||
type: !column.isNullable ? new GraphQLNonNull(graphqlType) : graphqlType,
|
||||
description: column.targetColumnName,
|
||||
};
|
||||
});
|
||||
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
import { GraphQLInputObjectType } from 'graphql';
|
||||
import { GraphQLID, GraphQLInputObjectType } from 'graphql';
|
||||
|
||||
import { FieldMetadata } from 'src/metadata/field-metadata/field-metadata.entity';
|
||||
import { pascalCase } from 'src/utils/pascal-case';
|
||||
@ -15,14 +15,15 @@ export const generateUpdateInputType = (
|
||||
name: string,
|
||||
columns: FieldMetadata[],
|
||||
): GraphQLInputObjectType => {
|
||||
const fields: Record<string, any> = {};
|
||||
const fields: Record<string, any> = {
|
||||
id: { type: GraphQLID },
|
||||
};
|
||||
|
||||
columns.forEach((column) => {
|
||||
const graphqlType = mapColumnTypeToGraphQLType(column);
|
||||
const graphqlType = mapColumnTypeToGraphQLType(column, true);
|
||||
// No GraphQLNonNull wrapping here, so all fields are optional
|
||||
fields[column.displayName] = {
|
||||
fields[column.nameSingular] = {
|
||||
type: graphqlType,
|
||||
description: column.targetColumnName,
|
||||
};
|
||||
});
|
||||
|
||||
|
||||
@ -2,23 +2,61 @@ import {
|
||||
GraphQLBoolean,
|
||||
GraphQLEnumType,
|
||||
GraphQLID,
|
||||
GraphQLInputObjectType,
|
||||
GraphQLInt,
|
||||
GraphQLObjectType,
|
||||
GraphQLString,
|
||||
} from 'graphql';
|
||||
|
||||
import { FieldMetadata } from 'src/metadata/field-metadata/field-metadata.entity';
|
||||
import { pascalCase } from 'src/utils/pascal-case';
|
||||
|
||||
const UrlObjectType = new GraphQLObjectType({
|
||||
name: 'Url',
|
||||
fields: {
|
||||
text: { type: GraphQLString },
|
||||
link: { type: GraphQLString },
|
||||
},
|
||||
});
|
||||
|
||||
const UrlInputType = new GraphQLInputObjectType({
|
||||
name: 'UrlInput',
|
||||
fields: {
|
||||
text: { type: GraphQLString },
|
||||
link: { type: GraphQLString },
|
||||
},
|
||||
});
|
||||
|
||||
const MoneyObjectType = new GraphQLObjectType({
|
||||
name: 'Money',
|
||||
fields: {
|
||||
amount: { type: GraphQLInt },
|
||||
currency: { type: GraphQLString },
|
||||
},
|
||||
});
|
||||
|
||||
const MoneyInputType = new GraphQLInputObjectType({
|
||||
name: 'MoneyInput',
|
||||
fields: {
|
||||
amount: { type: GraphQLInt },
|
||||
currency: { type: GraphQLString },
|
||||
},
|
||||
});
|
||||
|
||||
/**
|
||||
* Map the column type from field-metadata to its corresponding GraphQL type.
|
||||
* @param columnType Type of the column in the database.
|
||||
*/
|
||||
export const mapColumnTypeToGraphQLType = (column: FieldMetadata) => {
|
||||
export const mapColumnTypeToGraphQLType = (
|
||||
column: FieldMetadata,
|
||||
input = false,
|
||||
) => {
|
||||
switch (column.type) {
|
||||
case 'uuid':
|
||||
return GraphQLID;
|
||||
case 'text':
|
||||
case 'url':
|
||||
case 'phone':
|
||||
case 'email':
|
||||
case 'date':
|
||||
return GraphQLString;
|
||||
case 'boolean':
|
||||
@ -27,9 +65,7 @@ export const mapColumnTypeToGraphQLType = (column: FieldMetadata) => {
|
||||
return GraphQLInt;
|
||||
case 'enum': {
|
||||
if (column.enums && column.enums.length > 0) {
|
||||
const enumName = `${pascalCase(column.objectId)}${pascalCase(
|
||||
column.displayName,
|
||||
)}Enum`;
|
||||
const enumName = `${pascalCase(column.nameSingular)}Enum`;
|
||||
|
||||
return new GraphQLEnumType({
|
||||
name: enumName,
|
||||
@ -39,6 +75,12 @@ export const mapColumnTypeToGraphQLType = (column: FieldMetadata) => {
|
||||
});
|
||||
}
|
||||
}
|
||||
case 'url': {
|
||||
return input ? UrlInputType : UrlObjectType;
|
||||
}
|
||||
case 'money': {
|
||||
return input ? MoneyInputType : MoneyObjectType;
|
||||
}
|
||||
default:
|
||||
return GraphQLString;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user