fix: "union too complex to represent" for large union types (#571)

Closes #503
Co-authored-by: Jason Kuhrt <jasonkuhrt@me.com>
This commit is contained in:
Flavian Desverne 2020-10-26 15:34:14 +01:00 committed by GitHub
parent de7cdfd396
commit 10c5f8bc8c
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
16 changed files with 1338 additions and 76 deletions

View File

@ -1,9 +1,8 @@
const path = require('path') const path = require('path')
const { env } = require('process') const process = require('process')
/** /** @typedef {import('ts-jest')} */
* @type {jest.InitialOptions} /** @type {import('@jest/types').Config.InitialOptions} */
*/
module.exports = { module.exports = {
setupFilesAfterEnv: ['<rootDir>/tests/_setup.ts'], setupFilesAfterEnv: ['<rootDir>/tests/_setup.ts'],
preset: 'ts-jest', preset: 'ts-jest',
@ -11,8 +10,12 @@ module.exports = {
watchPlugins: ['jest-watch-typeahead/filename', 'jest-watch-typeahead/testname'], watchPlugins: ['jest-watch-typeahead/filename', 'jest-watch-typeahead/testname'],
globals: { globals: {
'ts-jest': { 'ts-jest': {
diagnostics: Boolean(process.env.CI), diagnostics: Boolean(process.env.CI)
tsConfig: path.join(__dirname, 'tests/tsconfig.json'), ? {
pretty: true,
pathRegex: '\\.(spec|test)\\.ts$',
}
: false,
}, },
}, },
collectCoverageFrom: ['src/**/*'], collectCoverageFrom: ['src/**/*'],

View File

@ -164,33 +164,20 @@ export type GetGen<K extends GenTypesShapeKeys, Fallback = any> = NexusGen exten
export type GetGen2< export type GetGen2<
K extends GenTypesShapeKeys, K extends GenTypesShapeKeys,
K2 extends keyof GenTypesShape[K] K2 extends Extract<keyof GenTypesShape[K], string>,
> = NexusGen extends infer GenTypes Fallback = any
? GenTypes extends GenTypesShape > = K2 extends keyof GetGen<K, never> ? GetGen<K>[K2] : Fallback
? K extends keyof GenTypes
? K2 extends keyof GenTypes[K]
? GenTypes[K][K2]
: any
: any
: any
: any
export type GetGen3< export type GetGen3<
K extends GenTypesShapeKeys, K extends GenTypesShapeKeys,
K2 extends Extract<keyof GenTypesShape[K], string>, K2 extends Extract<keyof GenTypesShape[K], string>,
K3 extends Extract<keyof GenTypesShape[K][K2], string>, K3 extends Extract<keyof GenTypesShape[K][K2], string>,
Fallback = any Fallback = any
> = NexusGen extends infer GenTypes > = K2 extends keyof GetGen<K, never>
? GenTypes extends GenTypesShape ? K3 extends keyof GetGen<K>[K2]
? K extends keyof GenTypes ? GetGen<K>[K2][K3]
? K2 extends keyof GenTypes[K] : Fallback
? K3 extends keyof GenTypes[K][K2]
? GenTypes[K][K2][K3]
: Fallback : Fallback
: any
: any
: any
: any
export type HasGen<K extends GenTypesShapeKeys> = NexusGen extends infer GenTypes export type HasGen<K extends GenTypesShapeKeys> = NexusGen extends infer GenTypes
? GenTypes extends GenTypesShape ? GenTypes extends GenTypesShape

View File

@ -22,8 +22,8 @@ import {
specifiedScalarTypes, specifiedScalarTypes,
} from 'graphql' } from 'graphql'
import * as path from 'path' import * as path from 'path'
import { decorateType } from './definitions/decorateType'
import { MissingType, NexusTypes, withNexusSymbol } from './definitions/_types' import { MissingType, NexusTypes, withNexusSymbol } from './definitions/_types'
import { decorateType } from './definitions/decorateType'
import { PluginConfig } from './plugin' import { PluginConfig } from './plugin'
export const isInterfaceField = (type: GraphQLObjectType, fieldName: string) => { export const isInterfaceField = (type: GraphQLObjectType, fieldName: string) => {
@ -420,3 +420,10 @@ export function casesHandled(x: never): never {
* Is the given type equal to the other given type? * Is the given type equal to the other given type?
*/ */
export type IsEqual<A, B> = A extends B ? (B extends A ? true : false) : false export type IsEqual<A, B> = A extends B ? (B extends A ? true : false) : false
/**
* Quickly log objects
*/
export function dump(x: any) {
console.log(require('util').inspect(x, { depth: null }))
}

View File

@ -1,7 +1,7 @@
/// <reference path="../_setup.ts" /> /// <reference path="../_setup.ts" />
import { join } from 'path' import { join } from 'path'
import * as ts from 'typescript'
import { core } from '../../src' import { core } from '../../src'
const { generateSchema, typegenFormatPrettier } = core const { generateSchema, typegenFormatPrettier } = core
type Settings = { type Settings = {
@ -16,16 +16,16 @@ type Settings = {
* - By default looks for an `__app.ts` entrypoint * - By default looks for an `__app.ts` entrypoint
* - All entrypoint exports are expected to be Nexus type definitions * - All entrypoint exports are expected to be Nexus type definitions
* - Except the optional export name "plugins" which is treated as an array of plugins for makeSchema * - Except the optional export name "plugins" which is treated as an array of plugins for makeSchema
* - The typegen module will be automatically included into the TypeScript build, no need to import it * - Outputs a `__typegen.ts` typegen module
* - You must import the typegen module into your entrypoint module
* - If you provide a `tsconfig.json` file in the root dir it will be used.
*/ */
export const testApp = (settings: Settings) => { export const testApp = (settings: Settings) => {
const name = settings?.name ?? 'app' const name = settings?.name ?? 'app'
const rootDir = settings.rootDir const rootDir = settings.rootDir
const typegenModuleName = '__typegen' const typegenModulePath = join(rootDir, '__typegen.ts')
const typegenModulePath = join(rootDir, `${typegenModuleName}.ts`) const entrypointModulePath = join(rootDir, '__app.ts')
const entrypointModuleName = `__app`
const entrypointModulePath = join(rootDir, `${entrypointModuleName}.ts`)
const entrypoint = require(entrypointModulePath) const entrypoint = require(entrypointModulePath)
const { plugins, ...types } = entrypoint const { plugins, ...types } = entrypoint
@ -40,22 +40,16 @@ export const testApp = (settings: Settings) => {
shouldGenerateArtifacts: true, shouldGenerateArtifacts: true,
plugins: plugins || [], plugins: plugins || [],
async formatTypegen(source, type) { async formatTypegen(source, type) {
const content = await typegenFormatPrettier({ const prettierConfigPath = require.resolve('../../.prettierrc')
trailingComma: 'es5', const content = await typegenFormatPrettier(prettierConfigPath)(source, type)
arrowParens: 'always',
})(source, type) return content.replace("'@nexus/schema'", "'../../..'")
return content.replace('"@nexus/schema"', '"../../.."')
}, },
}) })
}) })
it(`can compile ${name} app with its typegen`, async () => { it(`can compile ${name} app with its typegen`, async () => {
expect([entrypointModulePath, typegenModulePath]).toTypeCheck({ expect({ rootDir }).toTypeCheck({
downlevelIteration: true,
noEmitOnError: true,
strict: true,
target: ts.ScriptTarget.ES5,
noErrorTruncation: false,
outDir: `/tmp/nexus-integration-test-${Date.now()}`, outDir: `/tmp/nexus-integration-test-${Date.now()}`,
}) })
}) })

View File

@ -1,5 +1,8 @@
import * as fs from 'fs-jetpack'
import * as Path from 'path'
import * as tsm from 'ts-morph' import * as tsm from 'ts-morph'
import * as ts from 'typescript' import * as ts from 'typescript'
;(global as any).TS_FORMAT_PROJECT_ROOT = 'src/' ;(global as any).TS_FORMAT_PROJECT_ROOT = 'src/'
const formatTSDiagonsticsForJest = (diagnostics: readonly ts.Diagnostic[]): string => { const formatTSDiagonsticsForJest = (diagnostics: readonly ts.Diagnostic[]): string => {
@ -23,9 +26,33 @@ const formatTSDiagonsticsForJest = (diagnostics: readonly ts.Diagnostic[]): stri
} }
expect.extend({ expect.extend({
toTypeCheck(fileNames: string | string[], compilerOptions: tsm.CompilerOptions) { toTypeCheck(input: string | string[] | { rootDir: string }, compilerOptions: tsm.CompilerOptions) {
const project = new tsm.Project({ compilerOptions: compilerOptions }) let rootDir
project.addSourceFilesAtPaths(Array.isArray(fileNames) ? fileNames : [fileNames]) let fileNames: string[]
if (typeof input !== 'string' && !Array.isArray(input)) {
rootDir = input.rootDir
fileNames = []
} else {
fileNames = Array.isArray(input) ? input : [input]
rootDir = Path.dirname(fileNames[0])
}
// check for tsconfig
let tsConfigFilePath
const maybeTsConfigFilePath = `${rootDir}/tsconfig.json`
if (fs.exists(maybeTsConfigFilePath)) {
tsConfigFilePath = maybeTsConfigFilePath
}
const project = new tsm.Project({
tsConfigFilePath,
compilerOptions,
addFilesFromTsConfig: true,
})
if (fileNames.length) {
project.addSourceFilesAtPaths(fileNames)
}
const preEmitDiagnostics = project.getPreEmitDiagnostics() const preEmitDiagnostics = project.getPreEmitDiagnostics()
const emitDiagnostics = project.emitSync().getDiagnostics() const emitDiagnostics = project.emitSync().getDiagnostics()
@ -36,8 +63,9 @@ expect.extend({
message: () => message: () =>
pass pass
? 'expected program to not typecheck' ? 'expected program to not typecheck'
: (project.formatDiagnosticsWithColorAndContext(preEmitDiagnostics), : project.formatDiagnosticsWithColorAndContext(preEmitDiagnostics) +
project.formatDiagnosticsWithColorAndContext(emitDiagnostics)), '\n\n\n' +
project.formatDiagnosticsWithColorAndContext(emitDiagnostics),
pass, pass,
} }
}, },

View File

@ -13,7 +13,6 @@ import {
import { dynamicOutputProperty } from '../src/dynamicProperty' import { dynamicOutputProperty } from '../src/dynamicProperty'
import { CatListFixture } from './_fixtures' import { CatListFixture } from './_fixtures'
let spy: jest.SpyInstance
beforeEach(() => { beforeEach(() => {
jest.clearAllMocks() jest.clearAllMocks()
}) })

View File

@ -1,3 +1,4 @@
import './__typegen'
import { import {
dynamicInputMethod, dynamicInputMethod,
dynamicOutputMethod, dynamicOutputMethod,

View File

@ -97,6 +97,40 @@ export interface NexusGenFieldTypes {
} }
} }
export interface NexusGenFieldTypeNames {
Mutation: {
// field return type name
createUser: 'User'
}
Post: {
// field return type name
body: 'String'
title: 'String'
}
Query: {
// field return type name
foo: 'String'
searchPosts: 'Post'
user: 'User'
}
Subscription: {
// field return type name
someBoolean: 'Boolean'
someField: 'Int'
someFields: 'Int'
someFloat: 'Float'
someID: 'ID'
someInt: 'Int'
someInts: 'Int'
someString: 'String'
}
User: {
// field return type name
firstName: 'String'
lastName: 'String'
}
}
export interface NexusGenArgTypes { export interface NexusGenArgTypes {
Mutation: { Mutation: {
createUser: { createUser: {
@ -139,6 +173,7 @@ export interface NexusGenTypes {
rootTypes: NexusGenRootTypes rootTypes: NexusGenRootTypes
argTypes: NexusGenArgTypes argTypes: NexusGenArgTypes
fieldTypes: NexusGenFieldTypes fieldTypes: NexusGenFieldTypes
fieldTypeNames: NexusGenFieldTypeNames
allTypes: NexusGenAllTypes allTypes: NexusGenAllTypes
inheritedFields: NexusGenInheritedFields inheritedFields: NexusGenInheritedFields
objectNames: NexusGenObjectNames objectNames: NexusGenObjectNames

View File

@ -0,0 +1,9 @@
{
"compilerOptions": {
"strict": true,
"rootDir": "../../..",
"target": "ES2015",
"moduleResolution": "node"
},
"include": ["__app.ts"]
}

View File

@ -1,3 +1,4 @@
import './__typegen'
import { objectType, unionType } from '../../../src' import { objectType, unionType } from '../../../src'
export const typeNames = [ export const typeNames = [
@ -315,7 +316,7 @@ export const types = [
unionType({ unionType({
name: 'BigUnion', name: 'BigUnion',
definition(t) { definition(t) {
t.members(...(typeNames as any)) t.members(...typeNames)
t.resolveType(() => null) t.resolveType(() => null)
}, },
}), }),

View File

@ -0,0 +1,5 @@
import { testApp } from '../../__helpers/testApp'
testApp({
rootDir: __dirname,
})

View File

@ -0,0 +1,9 @@
{
"compilerOptions": {
"strict": true,
"target": "ES2015",
"rootDir": "../../../",
"moduleResolution": "node"
},
"include": ["__app.ts"]
}

View File

@ -1,7 +0,0 @@
// import { testApp } from '../../__helpers/testApp'
// testApp({
// rootDir: __dirname,
// })
it.todo('fixme')

View File

@ -90,14 +90,6 @@ beforeEach(() => {
jest.resetAllMocks() jest.resetAllMocks()
}) })
function toBase64(x: string): string {
return new Buffer(x).toString('base64')
}
function dump(x: any) {
console.log(require('util').inspect(x, { depth: null }))
}
describe('defaults', () => { describe('defaults', () => {
it('hasPreviousPage when paginating backwards assumes that node count equal to page size means there is another page to visit backward', async () => { it('hasPreviousPage when paginating backwards assumes that node count equal to page size means there is another page to visit backward', async () => {
const schema = makeTestSchema( const schema = makeTestSchema(

View File

@ -1,9 +0,0 @@
{
"extends": "../tsconfig.json",
"compilerOptions": {
"noUnusedLocals": false, // FIXME
"noEmit": true,
"rootDir": "."
},
"include": ["**/*.ts"]
}