Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

chore(docs): document subscription type #593

Merged
merged 2 commits into from
Nov 2, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
150 changes: 150 additions & 0 deletions docs/content/015-api/011-subscription-type.mdx
Original file line number Diff line number Diff line change
@@ -0,0 +1,150 @@
---
title: subscriptionType
codeStyle: true
---

## subscriptionType

```ts
subscriptionType(typeName:string, fn: SubscriptionTypeConfig): NexusSubscriptionType
```

Create a GraphQL `Subscription` type.

A subscription type configuration field is like an object type with the following differences:

1. It has a `subscribe` field. You should return an async iterator here. This is called once for each subscription a client sends.
2. The `root` param of the `resolve` field is called for each promise returned by the async iterator setup. The resolver is responsible for transforming the shape of data returned by your subscription stream into types that conform to your GraphQL schema.

Here is a runnable example using Apollo Server and Apollo PubSub with Express.

```ts
import { ApolloServer, PubSub } from 'apollo-server-express'
import express from 'express'
import * as HTTP from 'http'
import * as path from 'path'
import {
arg,
inputObjectType,
makeSchema,
mutationType,
objectType,
subscriptionType,
} from '@nexus/schema'

const pubsub = new PubSub()

const schema = makeSchema({
shouldExitAfterGenerateArtifacts:
process.env.NEXUS_SHOULD_EXIT_AFTER_GENERATE_ARTIFACTS === 'true',
outputs: {
typegen: path.join(__dirname, 'node_modules/@types/nexus-typegen/index.d.ts'),
schema: path.join(__dirname, './api.graphql'),
},
types: [
mutationType({
definition(t) {
t.field('createOneUser', {
type: 'User',
args: {
data: arg({
required: true,
type: inputObjectType({
name: 'CreateOneUserInput',
definition(t) {
t.string('email', { required: true })
},
}),
}),
},
async resolve(_, args) {
const data = args.data
await pubsub.publish('user_added', { data })
return data
},
})
},
}),
subscriptionType({
definition(t) {
t.field('createOneUser', {
type: 'User',
subscribe() {
return pubsub.asyncIterator('user_added')
},
async resolve(userPromise) {
const user = await userPromise.data
return user
},
})
},
}),
objectType({
name: 'User',
definition(t) {
t.string('email')
},
}),
],
})

const apollo = new ApolloServer({ schema })
const app = express()
const http = HTTP.createServer(app)

apollo.applyMiddleware({ app })
apollo.installSubscriptionHandlers(http)

http.listen(4000, () => {
console.log(`🚀 GraphQL service ready at http://localhost:4000/graphql`)
})
```

Here is a runnable example with a minimalistic schema.

```ts
import { ApolloServer } from 'apollo-server-express'
import express from 'express'
import * as HTTP from 'http'
import * as path from 'path'
import { makeSchema, subscriptionType } from '@nexus/schema'

const schema = makeSchema({
shouldExitAfterGenerateArtifacts:
process.env.NEXUS_SHOULD_EXIT_AFTER_GENERATE_ARTIFACTS === 'true',
outputs: {
typegen: path.join(__dirname, 'node_modules/@types/nexus-typegen/index.d.ts'),
schema: path.join(__dirname, './api.graphql'),
},
types: [
subscriptionType({
definition(t) {
t.boolean('truths', {
subscribe() {
return (async function*() {
while (true) {
await new Promise(res => setTimeout(res, 1000))
yield Math.random() > 0.5
}
})()
},
resolve(eventData) {
return eventData
},
})
},
}),
],
})

const apollo = new ApolloServer({ schema })
const app = express()
const http = HTTP.createServer(app)

apollo.applyMiddleware({ app })
apollo.installSubscriptionHandlers(http)

http.listen(4000, () => {
console.log(`🚀 GraphQL service ready at http://localhost:4000/graphql`)
})
```
29 changes: 29 additions & 0 deletions docs/content/015-api/101-subscription-field.mdx
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
---
title: subscriptionField
codeStyle: true
---

## subscriptionField

Often times you want to split up subscription fields into different domains of your application.

Like `queryField` and `mutationField` except that like `subscriptionType` the `subscribe` field is required on the type configuration. Refer to [`subscriptionType`](/api/subscription-type) docs for details

```ts
import { subscriptionField } from '@nexus/schema'

export const SubscriptionField = subscriptionField('truths', {
type: 'Boolean',
subscribe() {
return (async function*() {
while (true) {
await new Promise(res => setTimeout(res, 1000))
yield Math.random() > 0.5
}
})()
},
resolve(eventData) {
return eventData
},
})
```
6 changes: 3 additions & 3 deletions src/definitions/subscriptionField.ts
Original file line number Diff line number Diff line change
@@ -1,14 +1,14 @@
import { extendType } from './extendType'
import { SubscribeFieldConfig } from './subscriptionType'
import { SubscriptionTypeConfig } from './subscriptionType'

/**
* Add one field to the Subscription type
*/
export function subscriptionField<FieldName extends string>(
fieldName: FieldName,
config:
| SubscribeFieldConfig<'Subscription', FieldName>
| (() => SubscribeFieldConfig<'Subscription', FieldName>)
| SubscriptionTypeConfig<'Subscription', FieldName>
| (() => SubscriptionTypeConfig<'Subscription', FieldName>)
) {
return extendType({
type: 'Subscription',
Expand Down
12 changes: 6 additions & 6 deletions src/definitions/subscriptionType.ts
Original file line number Diff line number Diff line change
@@ -1,12 +1,12 @@
import { GraphQLResolveInfo } from 'graphql'
import { ArgsValue, GetGen, MaybePromise, MaybePromiseDeep, ResultValue } from '../typegenTypeHelpers'
import { IsEqual } from '../utils'
import { BaseScalars } from './_types'
import { CommonOutputFieldConfig, NexusOutputFieldDef } from './definitionBlocks'
import { ObjectDefinitionBuilder, objectType } from './objectType'
import { AllNexusOutputTypeDefs } from './wrapping'
import { BaseScalars } from './_types'

export interface SubscribeFieldConfigBase<FieldName extends string, Event = any> {
export interface SubscriptionTypeConfigBase<FieldName extends string, Event = any> {
resolve(
root: Event,
args: ArgsValue<'Subscription', FieldName>,
Expand All @@ -27,11 +27,11 @@ export interface SubscribeFieldConfigBase<FieldName extends string, Event = any>
// prettier-ignore
export type FieldShorthandConfig<FieldName extends string> =
CommonOutputFieldConfig<'Subscription', FieldName>
& SubscribeFieldConfigBase<FieldName>
& SubscriptionTypeConfigBase<FieldName>

// prettier-ignore
export interface SubscribeFieldConfig<TypeName extends string, FieldName extends string>
extends SubscribeFieldConfigBase<FieldName>,
export interface SubscriptionTypeConfig<TypeName extends string, FieldName extends string>
extends SubscriptionTypeConfigBase<FieldName>,
CommonOutputFieldConfig<'Subscription', FieldName>
{
type: GetGen<'allOutputTypes'> | AllNexusOutputTypeDefs
Expand Down Expand Up @@ -71,7 +71,7 @@ export class SubscriptionBuilder {
}

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

Expand Down