1 | [![npm version](https://badge.fury.io/js/graphql-subscriptions.svg)](https://badge.fury.io/js/graphql-subscriptions) [![GitHub license](https://img.shields.io/github/license/apollostack/graphql-subscriptions.svg)](https://github.com/apollostack/graphql-subscriptions/blob/license/LICENSE)
|
2 |
|
3 | # graphql-subscriptions
|
4 |
|
5 | GraphQL subscriptions is a simple npm package that lets you wire up GraphQL with a pubsub system (like Redis) to implement subscriptions in GraphQL.
|
6 |
|
7 | You can use it with any GraphQL client and server (not only Apollo).
|
8 |
|
9 | ### Installation
|
10 |
|
11 | `npm install graphql-subscriptions` or `yarn add graphql-subscriptions`
|
12 |
|
13 | > This package should be used with a network transport, for example [subscriptions-transport-ws](https://github.com/apollographql/subscriptions-transport-ws).
|
14 |
|
15 | ### Getting started with your first subscritpion
|
16 |
|
17 | To begin with GraphQL subscriptions, start by defining a GraphQL `Subscription` type in your schema:
|
18 |
|
19 | ```graphql
|
20 | type Subscription {
|
21 | somethingChanged: Result
|
22 | }
|
23 |
|
24 | type Result {
|
25 | id: String
|
26 | }
|
27 | ```
|
28 |
|
29 | Next, add the `Subscription` type to your `schema` definition:
|
30 |
|
31 | ```graphql
|
32 | schema {
|
33 | query: Query
|
34 | mutation: Mutation
|
35 | subscription: Subscription
|
36 | }
|
37 | ```
|
38 |
|
39 | Now, let's create a simple `PubSub` instance - it is a simple pubsub implementation, based on `EventEmitter`.
|
40 |
|
41 | To create a simple `PubSub` instance, do the following:
|
42 |
|
43 | ```js
|
44 | import { PubSub } from 'graphql-subscriptions';
|
45 |
|
46 | export const pubsub = new PubSub();
|
47 | ```
|
48 |
|
49 | Now, implement your Subscriptions type resolver, using the `pubsub.asyncIterator` to map the event you need:
|
50 |
|
51 | ```js
|
52 | const SOMETHING_CHANGED_TOPIC = 'something_changed';
|
53 |
|
54 | export const resolvers = {
|
55 | Subscription: {
|
56 | somethingChanged: {
|
57 | subscribe: () => pubsub.asyncIterator(SOMETHING_CHANGED_TOPIC),
|
58 | },
|
59 | },
|
60 | }
|
61 | ```
|
62 |
|
63 | > Subscriptions resolvers are not a function, but an object with `subscribe` method, than returns `AsyncIterable`.
|
64 |
|
65 | Now, GraphQL engine knows that `somethingChanged` is a subscription, and every time we will use `pubsub.publish` over this topic - is will publish it using the transport we use:
|
66 |
|
67 | ```js
|
68 | pubsub.publish(SOMETHING_CHANGED_TOPIC, { somethingChanged: { id: "123" }});
|
69 | ```
|
70 |
|
71 | ### Filters
|
72 |
|
73 | When publishing data to subscribers, we need to make sure that each subscribers get only the data it need.
|
74 |
|
75 | To do so, we can use `withFilter` helper from this package, which wraps `AsyncItrator` with a filter function, and let you control each publication for each user.
|
76 |
|
77 | `withFilter` API:
|
78 | - `asyncIterator: AsyncIterator<any>` : Async iterator you got from your `pubsub.asyncIterator`.
|
79 | - `filterFn: (payload, variables, context, info) => boolean | Promise<boolean>` - A filter function, executed with the payload (the published value), variables, context and operation info, must return `boolean` or `Promise<boolean>` indicating if the payload should pass to the subscriber.
|
80 |
|
81 | For example, if `somethingChanged` would also accept a variable with the ID that is relevant, we can use the following code to filter according to it:
|
82 |
|
83 | ```js
|
84 | import { withFilter } from 'graphql-subscriptions';
|
85 |
|
86 | const SOMETHING_CHANGED_TOPIC = 'something_changed';
|
87 |
|
88 | export const resolvers = {
|
89 | Subscription: {
|
90 | somethingChanged: {
|
91 | subscribe: withFilter(pubsub.asyncIterator(SOMETHING_CHANGED_TOPIC), (payload, variables) => {
|
92 | return payload.somethingChanged.id === variables.relevantId;
|
93 | }),
|
94 | },
|
95 | },
|
96 | }
|
97 | ```
|
98 |
|
99 | > Note that when using `withFilter`, you don't need to wrap your return value with a function.
|
100 |
|
101 | ### Channels Mapping
|
102 |
|
103 | You can map multiple channels into the same subscription, for example when there are multiple events that trigger the same subscription in the GraphQL engine.
|
104 |
|
105 | ```js
|
106 | const SOMETHING_UPDATED = 'something_updated';
|
107 | const SOMETHING_CREATED = 'something_created';
|
108 | const SOMETHING_REMOVED = 'something_removed';
|
109 |
|
110 | export const resolvers = {
|
111 | Subscription: {
|
112 | somethingChanged: {
|
113 | subscribe: () => pubsub.asyncIterator([ SOMETHING_UPDATED, SOMETHING_CREATED, SOMETHING_REMOVED ]),
|
114 | },
|
115 | },
|
116 | }
|
117 | ````
|
118 |
|
119 | ### Payload Manipulation
|
120 |
|
121 | You can also manipulate the published payload, by adding `resovle` methods to your subscription:
|
122 |
|
123 | ```js
|
124 | const SOMETHING_UPDATED = 'something_updated';
|
125 |
|
126 | export const resolvers = {
|
127 | Subscription: {
|
128 | somethingChanged: {
|
129 | resolve: (payload, args, context, info) => {
|
130 | // Manipulate and return the new value
|
131 |
|
132 | return payload;
|
133 | },
|
134 | subscribe: () => pubsub.asyncIterator(SOMETHING_UPDATED),
|
135 | },
|
136 | },
|
137 | }
|
138 | ````
|
139 |
|
140 | ### Custom `AsyncIterator` Wrappers
|
141 |
|
142 | The value you should return from your `subscribe` resolver must be an `AsyncIterator`.
|
143 |
|
144 | You can use this value and wrap it with another `AsyncIterator` to implement custom logic over your subscriptions.
|
145 |
|
146 | For example, the following implementation manipulate the payload by adding some static fields:
|
147 |
|
148 | ```typescript
|
149 | import { $$asyncIterator } from 'iterall';
|
150 |
|
151 | export const withStaticFields = (asyncIterator: AsyncIterator<any>, staticFields: Object): Function => {
|
152 | return (rootValue: any, args: any, context: any, info: any): AsyncIterator<any> => {
|
153 |
|
154 | return {
|
155 | next() {
|
156 | return asyncIterator.next().then(({ value, done }) => {
|
157 | return {
|
158 | value: {
|
159 | ...value,
|
160 | ...staticFields,
|
161 | },
|
162 | done,
|
163 | };
|
164 | });
|
165 | },
|
166 | return() {
|
167 | return Promise.resolve({ value: undefined, done: true });
|
168 | },
|
169 | throw(error) {
|
170 | return Promise.reject(error);
|
171 | },
|
172 | [$$asyncIterator]() {
|
173 | return this;
|
174 | },
|
175 | };
|
176 | };
|
177 | };
|
178 | ```
|
179 |
|
180 | > You can also take a look at `withFilter` for inspiration.
|
181 |
|
182 | For more information about `AsyncIterator`:
|
183 | - [TC39 Proposal](https://github.com/tc39/proposal-async-iteration)
|
184 | - [iterall](https://github.com/leebyron/iterall)
|
185 | - [IxJS](https://github.com/ReactiveX/IxJS)
|
186 |
|
187 | ### PubSub Implementations
|
188 |
|
189 | It can be easily replaced with some other implementations of [PubSubEngine interface](https://github.com/apollographql/graphql-subscriptions/blob/master/src/pubsub.ts#L21-L25). There are a couple of them out there:
|
190 | - Use Redis with https://github.com/davidyaha/graphql-redis-subscriptions
|
191 | - Use MQTT enabled broker with https://github.com/davidyaha/graphql-mqtt-subscriptions
|
192 | - Use RabbitMQ with https://github.com/cdmbase/graphql-rabbitmq-subscriptions
|
193 | - [Add your implementation...](https://github.com/apollographql/graphql-subscriptions/pull/new/master)
|
194 |
|
195 | You can also implement a `PubSub` of your own, by using the exported interface `PubSubEngine` from this package.
|
196 |
|
197 | #### SubscriptionManager **@deprecated**
|
198 |
|
199 | `SubscriptionManager` is the previous alternative for using `graphql-js` subscriptions directly, and it's now deprecated.
|
200 |
|
201 | If you are looking for it's API docs, refer to [a previous commit of the repository](https://github.com/apollographql/graphql-subscriptions/blob/5eaee92cd50060b3f3637f00c53960f51a07d0b2/README.md)
|