Skip to content

Commit

Permalink
fix: subscription type static typings (#564)
Browse files Browse the repository at this point in the history
fixes #559
  • Loading branch information
Jason Kuhrt committed Oct 20, 2020
1 parent e65f6ef commit 2edfcfa
Show file tree
Hide file tree
Showing 14 changed files with 232 additions and 142 deletions.
2 changes: 1 addition & 1 deletion examples/zeit-typescript/package.json
Expand Up @@ -13,7 +13,7 @@
"trailingComma": "all"
},
"dependencies": {
"@nexus/schema": "^0.16.0",
"@nexus/schema": "0.17.0-next.2",
"graphql": "^15.3.0"
},
"devDependencies": {
Expand Down
8 changes: 4 additions & 4 deletions examples/zeit-typescript/yarn.lock
Expand Up @@ -2,10 +2,10 @@
# yarn lockfile v1


"@nexus/schema@^0.16.0":
version "0.16.0"
resolved "https://registry.yarnpkg.com/@nexus/schema/-/schema-0.16.0.tgz#cbf2b70eb47a854e4bcec849583368d7e7048087"
integrity sha512-H+JJDycjcilvsRtLySpwfVXp1gYiOqPPLQdq04R4Vp5S400PpdADQlKMV4EvpBJ/BCxwQ8XwP8+nrOKvDZLLUw==
"@nexus/schema@0.17.0-next.2":
version "0.17.0-next.2"
resolved "https://registry.yarnpkg.com/@nexus/schema/-/schema-0.17.0-next.2.tgz#b85fcb1cd35d4fd65618404f406d0e1e309ffda7"
integrity sha512-EMUYhEvo6DkbZSBC/ErlSN59KWDVrZbuBbRY+QbRn2EsOcOhA3Y6g+/SDYEfJwqJzKjiC9AjqOmOqzmBXBNJQA==
dependencies:
iterall "^1.2.2"
tslib "^1.9.3"
Expand Down
21 changes: 9 additions & 12 deletions jest.config.js
@@ -1,21 +1,18 @@
const path = require("path");
const path = require('path')

/**
* @type {jest.InitialOptions}
*/
module.exports = {
setupFilesAfterEnv: ["<rootDir>/tests/_setup.ts"],
preset: "ts-jest",
testEnvironment: "node",
watchPlugins: [
"jest-watch-typeahead/filename",
"jest-watch-typeahead/testname",
],
setupFilesAfterEnv: ['<rootDir>/tests/_setup.ts'],
preset: 'ts-jest',
testEnvironment: 'node',
watchPlugins: ['jest-watch-typeahead/filename', 'jest-watch-typeahead/testname'],
globals: {
"ts-jest": {
'ts-jest': {
diagnostics: true, // (temp) true
tsConfig: path.join(__dirname, "tests/tsconfig.json"),
tsConfig: path.join(__dirname, 'tests/tsconfig.json'),
},
},
collectCoverageFrom: ["src/**/*"],
};
collectCoverageFrom: ['src/**/*'],
}
49 changes: 23 additions & 26 deletions src/definitions/definitionBlocks.ts
Expand Up @@ -64,28 +64,24 @@ export type NexusOutputFieldDef = NexusOutputFieldConfig<string, any> & {
subscribe?: GraphQLFieldResolver<any, any>
}

/**
* Ensure type-safety by checking
*/
export type ScalarOutSpread<TypeName extends string, FieldName extends string> = NeedsResolver<
TypeName,
FieldName
> extends true
? HasGen3<'argTypes', TypeName, FieldName> extends true
? [ScalarOutConfig<TypeName, FieldName>]
: [ScalarOutConfig<TypeName, FieldName>] | [FieldResolver<TypeName, FieldName>]
: HasGen3<'argTypes', TypeName, FieldName> extends true
? [ScalarOutConfig<TypeName, FieldName>]
: [] | [FieldResolver<TypeName, FieldName>] | [ScalarOutConfig<TypeName, FieldName>]

export type ScalarOutConfig<TypeName extends string, FieldName extends string> = NeedsResolver<
TypeName,
FieldName
> extends true
? OutputScalarConfig<TypeName, FieldName> & {
resolve: FieldResolver<TypeName, FieldName>
}
: OutputScalarConfig<TypeName, FieldName>
// prettier-ignore
export type ScalarOutSpread<TypeName extends string, FieldName extends string> =
NeedsResolver<TypeName, FieldName> extends true
? HasGen3<'argTypes', TypeName, FieldName> extends true
? [ScalarOutConfig<TypeName, FieldName>]
: [ScalarOutConfig<TypeName, FieldName>] | [FieldResolver<TypeName, FieldName>]
: HasGen3<'argTypes', TypeName, FieldName> extends true
? [ScalarOutConfig<TypeName, FieldName>]
: [] | [FieldResolver<TypeName, FieldName>] | [ScalarOutConfig<TypeName, FieldName>]

// prettier-ignore
export type ScalarOutConfig<TypeName extends string, FieldName extends string> =
NeedsResolver<TypeName, FieldName> extends true
? OutputScalarConfig<TypeName, FieldName> &
{
resolve: FieldResolver<TypeName, FieldName>
}
: OutputScalarConfig<TypeName, FieldName>

export type FieldOutConfig<TypeName extends string, FieldName extends string> = NeedsResolver<
TypeName,
Expand All @@ -110,9 +106,11 @@ export interface InputDefinitionBuilder {
warn(msg: string): void
}

// prettier-ignore
export interface OutputDefinitionBlock<TypeName extends string>
extends NexusGenCustomOutputMethods<TypeName>,
NexusGenCustomOutputProperties<TypeName> {}
extends NexusGenCustomOutputMethods<TypeName>,
NexusGenCustomOutputProperties<TypeName>
{}

/**
* The output definition block is passed to the "definition"
Expand Down Expand Up @@ -158,8 +156,7 @@ export class OutputDefinitionBlock<TypeName extends string> {
// 2. NexusOutputFieldDef is contrained to be be a string
// 3. so `name` is not compatible
// 4. and changing FieldOutConfig to FieldOutConfig<string breaks types in other places
const field: any = { name, ...fieldConfig }
this.typeBuilder.addField(this.decorateField(field))
this.typeBuilder.addField(this.decorateField({ name, ...fieldConfig } as any))
}

protected addScalarField(
Expand Down
5 changes: 4 additions & 1 deletion src/definitions/extendType.ts
Expand Up @@ -5,7 +5,10 @@ import { NexusTypes, withNexusSymbol } from './_types'

export interface NexusExtendTypeConfig<TypeName extends string> {
type: TypeName
definition(t: OutputDefinitionBlock<TypeName>): void
definition(
// t: IsSubscriptionType<TypeName> extends true ? SubscriptionBuilder : OutputDefinitionBlock<TypeName>
t: OutputDefinitionBlock<TypeName>
): void
}

export class NexusExtendTypeDef<TypeName extends string> {
Expand Down
2 changes: 1 addition & 1 deletion src/definitions/subscriptionField.ts
Expand Up @@ -14,7 +14,7 @@ export function subscriptionField<FieldName extends string>(
type: 'Subscription',
definition(t) {
const finalConfig = typeof config === 'function' ? config() : config
t.field(fieldName, finalConfig)
t.field(fieldName, finalConfig as any)
},
})
}
162 changes: 71 additions & 91 deletions src/definitions/subscriptionType.ts
@@ -1,63 +1,15 @@
import { GraphQLResolveInfo } from 'graphql'
import {
ArgsValue,
FieldResolver,
GetGen,
HasGen3,
MaybePromise,
MaybePromiseDeep,
NeedsResolver,
ResultValue,
} from '../typegenTypeHelpers'
import { CommonOutputFieldConfig } from './definitionBlocks'
import { ObjectDefinitionBlock, ObjectDefinitionBuilder, objectType } from './objectType'
import { ArgsValue, GetGen, MaybePromise, MaybePromiseDeep, ResultValue } from '../typegenTypeHelpers'
import { IsEqual } from '../utils'
import { CommonOutputFieldConfig, NexusOutputFieldDef } from './definitionBlocks'
import { ObjectDefinitionBuilder, objectType } from './objectType'
import { AllNexusOutputTypeDefs } from './wrapping'
import { BaseScalars } from './_types'

export interface SubscriptionScalarConfig<TypeName extends string, FieldName extends string, T = any>
extends CommonOutputFieldConfig<TypeName, FieldName> {
/**
* Resolve method for the field
*/
resolve?: FieldResolver<TypeName, FieldName>

subscribe(
root: object,
args: ArgsValue<TypeName, FieldName>,
ctx: GetGen<'context'>,
info: GraphQLResolveInfo
): MaybePromise<AsyncIterator<T>> | MaybePromiseDeep<AsyncIterator<T>>
}

export type ScalarSubSpread<TypeName extends string, FieldName extends string> = NeedsResolver<
TypeName,
FieldName
> extends true
? HasGen3<'argTypes', TypeName, FieldName> extends true
? [ScalarSubConfig<TypeName, FieldName>]
: [ScalarSubConfig<TypeName, FieldName>] | [FieldResolver<TypeName, FieldName>]
: HasGen3<'argTypes', TypeName, FieldName> extends true
? [ScalarSubConfig<TypeName, FieldName>]
: [] | [FieldResolver<TypeName, FieldName>] | [ScalarSubConfig<TypeName, FieldName>]

export type ScalarSubConfig<TypeName extends string, FieldName extends string> = NeedsResolver<
TypeName,
FieldName
> extends true
? SubscriptionScalarConfig<TypeName, FieldName> & {
resolve: FieldResolver<TypeName, FieldName>
}
: SubscriptionScalarConfig<TypeName, FieldName>

export interface SubscribeFieldConfig<TypeName extends string, FieldName extends string, T = any>
extends CommonOutputFieldConfig<TypeName, FieldName> {
type: GetGen<'allOutputTypes'> | AllNexusOutputTypeDefs

/**
* Resolve method for the field
*/
export interface SubscribeFieldConfigBase<FieldName extends string, Event = any> {
resolve(
root: T,
args: ArgsValue<TypeName, FieldName>,
root: Event,
args: ArgsValue<'Subscription', FieldName>,
context: GetGen<'context'>,
info: GraphQLResolveInfo
):
Expand All @@ -66,66 +18,94 @@ export interface SubscribeFieldConfig<TypeName extends string, FieldName extends

subscribe(
root: object,
args: ArgsValue<TypeName, FieldName>,
args: ArgsValue<'Subscription', FieldName>,
ctx: GetGen<'context'>,
info: GraphQLResolveInfo
): MaybePromise<AsyncIterator<T>> | MaybePromiseDeep<AsyncIterator<T>>
): MaybePromise<AsyncIterator<Event>> | MaybePromiseDeep<AsyncIterator<Event>>
}

export interface SubscriptionDefinitionBuilder extends ObjectDefinitionBuilder<'Subscription'> {}

export class SubscriptionDefinitionBlock extends ObjectDefinitionBlock<'Subscription'> {
constructor(protected typeBuilder: SubscriptionDefinitionBuilder, protected isList = false) {
super(typeBuilder)
// prettier-ignore
export type FieldShorthandConfig<FieldName extends string> =
CommonOutputFieldConfig<'Subscription', FieldName>
& SubscribeFieldConfigBase<FieldName>

// prettier-ignore
export interface SubscribeFieldConfig<TypeName extends string, FieldName extends string>
extends SubscribeFieldConfigBase<FieldName>,
CommonOutputFieldConfig<'Subscription', FieldName>
{
type: GetGen<'allOutputTypes'> | AllNexusOutputTypeDefs
}

export interface SubscriptionBuilderInternal extends ObjectDefinitionBuilder<'Subscription'> {}

export class SubscriptionBuilder {
constructor(protected typeBuilder: SubscriptionBuilderInternal, protected isList = false) {}

get list() {
if (this.isList) {
throw new Error('Cannot chain list.list, in the definition block. Use `list: []` config value')
}
return new SubscriptionDefinitionBlock(this.typeBuilder, true)
return new SubscriptionBuilder(this.typeBuilder, true)
}

string<FieldName extends string>(fieldName: FieldName, config: FieldShorthandConfig<FieldName>) {
this.fieldShorthand(fieldName, 'String', config)
}

string<FieldName extends string>(
fieldName: FieldName,
...opts: ScalarSubSpread<'Subscription', FieldName>
) {
this.addScalarField(fieldName, 'String', opts)
int<FieldName extends string>(fieldName: FieldName, config: FieldShorthandConfig<FieldName>) {
this.fieldShorthand(fieldName, 'Int', config)
}

int<FieldName extends string>(fieldName: FieldName, ...opts: ScalarSubSpread<'Subscription', FieldName>) {
this.addScalarField(fieldName, 'Int', opts)
// prettier-ignore
boolean<FieldName extends string>(fieldName: FieldName, opts: FieldShorthandConfig<FieldName>) {
this.fieldShorthand(fieldName, 'Boolean', opts)
}

boolean<FieldName extends string>(
fieldName: FieldName,
...opts: ScalarSubSpread<'Subscription', FieldName>
) {
this.addScalarField(fieldName, 'Boolean', opts)
id<FieldName extends string>(fieldName: FieldName, config: FieldShorthandConfig<FieldName>) {
this.fieldShorthand(fieldName, 'ID', config)
}

id<FieldName extends string>(fieldName: FieldName, ...opts: ScalarSubSpread<'Subscription', FieldName>) {
this.addScalarField(fieldName, 'ID', opts)
float<FieldName extends string>(fieldName: FieldName, config: FieldShorthandConfig<FieldName>) {
this.fieldShorthand(fieldName, 'Float', config)
}

float<FieldName extends string>(fieldName: FieldName, ...opts: ScalarSubSpread<'Subscription', FieldName>) {
this.addScalarField(fieldName, 'Float', opts)
// prettier-ignore
field<FieldName extends string>(name: FieldName, fieldConfig: SubscribeFieldConfig<'Subscription', FieldName>) {
this.typeBuilder.addField(this.decorateField({ name, ...fieldConfig } as any))
}

field<FieldName extends string>(
name: FieldName,
fieldConfig: SubscribeFieldConfig<'Subscription', FieldName>
) {
const field: any = { name, ...fieldConfig }
this.typeBuilder.addField(this.decorateField(field))
protected fieldShorthand(fieldName: string, typeName: BaseScalars, config: FieldShorthandConfig<any>) {
this.typeBuilder.addField(
this.decorateField({
name: fieldName,
type: typeName,
...config,
} as any)
)
}

protected decorateField(config: NexusOutputFieldDef): NexusOutputFieldDef {
if (this.isList) {
if (config.list) {
this.typeBuilder.warn(
`It looks like you chained .list and set list for ${config.name}. ` +
'You should only do one or the other'
)
} else {
config.list = true
}
}
return config
}
}

export type NexusSubscriptionTypeConfig = {
name: 'Subscription'
definition(t: SubscriptionDefinitionBlock): void
export type SubscriptionTypeParams = {
definition(t: SubscriptionBuilder): void
}

export function subscriptionType(config: Omit<NexusSubscriptionTypeConfig, 'name'>) {
return objectType({ name: 'Subscription', ...config })
export function subscriptionType(config: SubscriptionTypeParams) {
return objectType({ name: 'Subscription', ...config } as any)
}

export type IsSubscriptionType<T> = IsEqual<T, 'Subscription'>
5 changes: 5 additions & 0 deletions src/utils.ts
Expand Up @@ -415,3 +415,8 @@ export function getOwnPackage(): { name: string } {
export function casesHandled(x: never): never {
throw new Error(`A case was not handled for value: ${x}`)
}

/**
* Is the given type equal to the other given type?
*/
export type IsEqual<A, B> = A extends B ? (B extends A ? true : false) : false

0 comments on commit 2edfcfa

Please sign in to comment.