From ddc652f94445ae3e9d6a65cf149c985c25e2dffb Mon Sep 17 00:00:00 2001 From: Artem Zakharchenko Date: Wed, 28 Jul 2021 17:02:58 +0200 Subject: [PATCH] WIP Supports relational types in GraphQL --- src/glossary.ts | 12 ++-- src/model/generateGraphQLHandlers.ts | 40 +++++++++++--- test/model/toGraphQLSchema.test.ts | 83 ++++++++++++++++++++++++---- 3 files changed, 111 insertions(+), 24 deletions(-) diff --git a/src/glossary.ts b/src/glossary.ts index 26582d63..44da46be 100644 --- a/src/glossary.ts +++ b/src/glossary.ts @@ -98,13 +98,15 @@ export type InternalEntity< export type ModelDictionary = Limit>> +export type ModelDictionaryValue> = + | (() => BaseTypes) + | PrimaryKeyDeclaration + | OneOf + | ManyOf + export type Limit> = { [RK in keyof T]: { - [SK in keyof T[RK]]: T[RK][SK] extends - | (() => BaseTypes) - | PrimaryKeyDeclaration - | OneOf - | ManyOf + [SK in keyof T[RK]]: T[RK][SK] extends ModelDictionaryValue ? T[RK][SK] : { error: 'expected a value or a relation' diff --git a/src/model/generateGraphQLHandlers.ts b/src/model/generateGraphQLHandlers.ts index b5a3b490..ef6b80a7 100644 --- a/src/model/generateGraphQLHandlers.ts +++ b/src/model/generateGraphQLHandlers.ts @@ -14,9 +14,15 @@ import { GraphQLInputType, GraphQLScalarType, GraphQLFieldConfigArgumentMap, + GraphQLNonNull, } from 'graphql' import { GraphQLHandler, graphql } from 'msw' -import { ModelAPI, ModelDefinition, ModelDictionary } from '../glossary' +import { + ModelAPI, + ModelDefinition, + ModelDictionary, + ModelDictionaryValue, +} from '../glossary' import { capitalize } from '../utils/capitalize' import { QueryToComparator } from '../query/queryTypes' import { booleanComparators } from '../comparators/boolean' @@ -32,16 +38,32 @@ interface GraphQLFieldsMap { /** * Derive a GraphQL scalar type from a variable. */ -export function getGraphQLType(value: any) { +export function getGraphQLType( + value: ModelDictionaryValue, +): GraphQLScalarType { const resolvedValue = typeof value === 'function' ? value() : value - switch (resolvedValue.constructor.name) { - case 'Number': - return GraphQLInt - case 'Boolean': - return GraphQLBoolean - default: - return GraphQLString + + if (typeof resolvedValue === 'number') { + return GraphQLID + } + + if (typeof resolvedValue === 'string') { + return GraphQLString } + + if (typeof resolvedValue === 'boolean') { + return GraphQLBoolean + } + + if ('kind' in resolvedValue) { + return new GraphQLScalarType({ + name: capitalize(resolvedValue.modelName.toString()), + }) + } + + console.log({ resolvedValue }) + + return GraphQLString } /** diff --git a/test/model/toGraphQLSchema.test.ts b/test/model/toGraphQLSchema.test.ts index 33ad4b45..df34dded 100644 --- a/test/model/toGraphQLSchema.test.ts +++ b/test/model/toGraphQLSchema.test.ts @@ -1,17 +1,17 @@ import { datatype } from 'faker' -import { factory, primaryKey } from '@mswjs/data' import { printSchema } from 'graphql' +import { factory, primaryKey, oneOf } from '@mswjs/data' -const db = factory({ - user: { - id: primaryKey(datatype.uuid), - firstName: String, - age: Number, - }, -}) - -test('generates a graphql schema', () => { +it('generates a graphql schema', () => { + const db = factory({ + user: { + id: primaryKey(datatype.uuid), + firstName: String, + age: Number, + }, + }) const schema = db.user.toGraphQLSchema() + expect(printSchema(schema)).toMatchInlineSnapshot(` "type Query { user(where: UserQueryInput): User @@ -75,3 +75,66 @@ test('generates a graphql schema', () => { " `) }) + +it.only('infers relational property types', () => { + const db = factory({ + user: { + id: primaryKey(datatype.uuid), + role: oneOf('role'), + }, + role: { + id: primaryKey(datatype.uuid), + name: String, + }, + }) + const schema = db.user.toGraphQLSchema() + + expect(printSchema(schema)).toMatchInlineSnapshot(` + "type Query { + user(where: UserQueryInput): User + users(take: Int, skip: Int, cursor: ID, where: UserQueryInput): [User] + } + + type User { + id: ID + role: Role + } + + input UserQueryInput { + id: IdQueryType + role: StringQueryType + } + + input IdQueryType { + equals: ID + notEquals: ID + contains: ID + notContains: ID + in: ID + notIn: ID + } + + input StringQueryType { + equals: String + notEquals: String + contains: String + notContains: String + in: String + notIn: String + } + + type Mutation { + createUser(data: UserInput): User + updateUser(where: UserQueryInput, data: UserInput): User + updateUsers(where: UserQueryInput, data: UserInput): [User] + deleteUser(where: UserQueryInput): User + deleteUsers(where: UserQueryInput): [User] + } + + input UserInput { + id: ID + role: String + } + " + `) +})