Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions website/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
# README

Building the website requires that the documentation be generated from the root project into the `src/pages/docs` directory _first_. This can be done by `yarn gendocs` from the root of the repo.
10 changes: 7 additions & 3 deletions website/src/pages/get-started.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ npm i graphql-ws

```ts
import { GraphQLObjectType, GraphQLSchema, GraphQLString } from 'graphql';
import { Repeater } from "@repeaterjs/repeater";

/**
* Construct a GraphQL schema and define the necessary resolvers.
Expand Down Expand Up @@ -43,9 +44,12 @@ export const schema = new GraphQLSchema({
greetings: {
type: GraphQLString,
subscribe: async function* () {
for (const hi of ['Hi', 'Bonjour', 'Hola', 'Ciao', 'Zdravo']) {
yield { greetings: hi };
}
const greetings = ['Hi', 'Bonjour', 'Hola', 'Ciao', 'Zdravo'];
return new Repeater(async (push, stop) => {
const interval = setInterval(() => greetings.length > 0 ? push(greetings.shift()) : null, 1000);
await stop;
clearInterval(interval);
});
},
},
},
Expand Down
96 changes: 96 additions & 0 deletions website/src/pages/recipes.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -922,6 +922,102 @@ wsServer.on('connection', (socket, request) => {
});
```

### Server usage with a custom event emitter

You may have a customer event production system that you need to tie into that requires you to write your own event emitter. When using graphql-ws, your `subscribe()` function *can* return an [`AsyncGenerator`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/AsyncGenerator) implementation to do this. Javascript has language-level support for `AsyncGenerator`s through the `async function*()`/`yield` syntax, and it easy easy to work with. However, it is not recommended that you use it in real-world applications. This is due to a constraint of `AsyncGenerators` in general and understanding why will take a little explanation.

The consumer of an `AsyncGenerator` (in this case, `graphql-ws`) awaits the [`next()`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/AsyncGenerator/next) function, which will return the value `yield`ed in your function. If the generator function is *also* `await`ing a promise before `yield`ing it, nothing else can change the state of the `AsyncGenerator` until the promise returns.

The reason this is an issue is that `graphql-ws` will react to client "unsubscribe" requests by calling [`return()`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/AsyncGenerator/return). This method will cause the `AsyncGenerator` to return a value immediately from the the `yield` statement, but if your function is currently `await`ing a promise, the call to `return()` will be blocked until that promise resolves. Until then, your event emitter will not be able to run any cleanup operations and none of its allocated objects will be garbage collected.

Below is a toy implementation that can be used as a template for rolling your own emitter (you may also use a library that does this for you, like [Repeater.js](https://repeater.js.org/)).


```ts
import { GraphQLObjectType, GraphQLSchema, GraphQLString } from 'graphql';

/**
* Construct a GraphQL schema and define the necessary resolvers.
*
* type Query {
* hello: String
* }
* type Subscription {
* greetings: String
* }
*/
export const schema = new GraphQLSchema({
query: new GraphQLObjectType({
name: 'Query',
fields: {
hello: {
type: GraphQLString,
resolve: () => 'world',
},
},
}),
subscription: new GraphQLObjectType({
name: 'Subscription',
fields: {
greetings: {
type: GraphQLString,
subscribe: async function () {
return new EventProvider().iterator();
},
},
},
}),
});

/**
* A toy event provider implementation. Will return a series of greetings with a small
* delay in between each one.
*/
class EventProvider {
private readonly events = ['Hi', 'Bonjour', 'Hola', 'Ciao', 'Zdravo'];
private promiseReject: ((v?: any) => void) | null = null;
private closed = false;

close() {
if (this.closed) { return; }
if (this.promiseReject) { this.promiseReject('closed'); }
this.closed = true;
}

isClosed() { return this.closed || this.events.length === 0; }

async event() {
return await new Promise(async (resolve, reject) => {
this.promiseReject = reject;
setTimeout(() => resolve(this.events.shift()), 100);
});
}

iterator() {
const self = this;
return {
[Symbol.asyncIterator]() { return this; },
async next(): {
try {
if (!self.isClosed()) {
return { value: await self.event(), done: false };
}
} catch (e) {
return { value: undefined, done: true };
}
},
async return(v?: any) {
self.close();
return { value: v, done: true };
},
async throw(v?: any) {
return { value: undefined, done: true };
}
};
}
}
```

### Server usage with [Cloudflare Workers](https://workers.cloudflare.com/)

[Please check the `worker-graphql-ws-template` repo out.](https://github.com/enisdenjo/cloudflare-worker-graphql-ws-template)
Expand Down
Loading