diff --git a/CHANGELOG.md b/CHANGELOG.md index b36128f..88cec9e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,8 +1,8 @@ # Changelog -### 3.0.0 (not yet released) +### 3.0.0 -- **[BREAKING]** Replace `iterall` use with native `Symbol.asyncIterator`.
+- **[BREAKING]** Replace `iterall` use with native `Symbol.asyncIterator`. `PubSubEngine.asyncIterator` is now `PubSubEngine.asyncIterableIterator`.
[@n1ru4l](https://github.com/n1ru4l) in [#232](https://github.com/apollographql/graphql-subscriptions/pull/232) - Add an optional generic type map to `PubSub`.
[@cursorsdottsx](https://github.com/cursorsdottsx) in [#245](https://github.com/apollographql/graphql-subscriptions/pull/245) @@ -10,11 +10,10 @@ [@rh389](https://github.com/rh389) in [#234](https://github.com/apollographql/graphql-subscriptions/pull/234) - Support returning a Promise of an `AsyncIterator` as the `withFilter` resolver function.
[@maclockard](https://github.com/maclockard) in [#220](https://github.com/apollographql/graphql-subscriptions/pull/220) - -### 2.0.1 (not yet released) - - `withFilter` TypeScript improvements.
[@HofmannZ](https://github.com/HofmannZ) in [#230](https://github.com/apollographql/graphql-subscriptions/pull/230) +- `withFilter` returns `AsyncIterableIterator` for compatibility with Apollo Server subscriptions.
+ [@tninesling](https://github.com/tninesling) in [#276](https://github.com/apollographql/graphql-subscriptions/pull/276) ### 2.0.0 @@ -36,77 +35,96 @@ ### 1.0.0 -- BREAKING CHANGE: Changed return type of `publish`.
+- BREAKING CHANGE: Changed return type of `publish`.
[@grantwwu](https://github.com/grantwwu) in [#162](https://github.com/apollographql/graphql-subscriptions/pull/162) - Bump versions of various devDependencies to fix security issues, use - newer tslint config.
+ newer tslint config.
[@grantwwu](https://github.com/grantwwu) in [#163](https://github.com/apollographql/graphql-subscriptions/pull/163) - Allows `graphql` 14 as a peer dep, forces `graphql` 14 as a dev dep, and - has been updated to use `@types/graphql` 14.
+ has been updated to use `@types/graphql` 14.
[@hwillson](https://github.com/hwillson) in [#172](https://github.com/apollographql/graphql-subscriptions/pull/172) ### 0.5.8 + - Bump iterall version ### 0.5.7 + - Add `graphql@0.13` to `peerDependencies`. ### 0.5.6 + - Add `graphql@0.12` to `peerDependencies`. ### 0.5.5 + - FilterFn can return a Promise - Allow passing in a custom `EventEmitter` to `PubSub` ### 0.5.4 + - Better define `withFilter` return type [PR #111](https://github.com/apollographql/graphql-subscriptions/pull/111) ### 0.5.3 + - Require iterall ^1.1.3 to address unhandled exceptions ### 0.5.2 + - Require iterall ^1.1.2 to address memory leak [Issue #97] (https://github.com/apollographql/graphql-subscriptions/issues/97) - Remove `@types/graphql` dependency. [PR #105] (https://github.com/apollographql/graphql-subscriptions/pull/105) ### 0.5.1 + - `withFilter` now called with `(rootValue, args, context, info)` [PR #103] (https://github.com/apollographql/graphql-subscriptions/pull/103) ### 0.5.0 + - BREAKING CHANGE: Removed deprecated code. [PR #104] (https://github.com/apollographql/graphql-subscriptions/pull/104) - BREAKING CHANGE: Minimum GraphQL version bumped to 0.10.X. [PR #104] (https://github.com/apollographql/graphql-subscriptions/pull/104) ### 0.4.4 + - Avoid infinite loop after the last consumer unsubscribes, [Issue #81](https://github.com/apollographql/graphql-subscriptions/issues/81) [PR #84](https://github.com/apollographql/graphql-subscriptions/pull/84) ### 0.4.3 + - Properly propagate return() and throw() through withFilter [PR #74](https://github.com/apollographql/graphql-subscriptions/pull/74) ### 0.4.2 + - Fixed issue with `withFilter` causing to use the same iterator [PR #69](https://github.com/apollographql/graphql-subscriptions/pull/69) ### 0.4.1 + - Fixed exports issue with TypeScript [PR #65](https://github.com/apollographql/graphql-subscriptions/pull/65) ### 0.4.0 + - Added `asyncIterator(channelName: string)` to `PubSub` implementation [PR #60](https://github.com/apollographql/graphql-subscriptions/pull/60) - Added `withFilter` to allow `AsyncIterator` filtering [PR #60](https://github.com/apollographql/graphql-subscriptions/pull/60) - Deprecate `SubscriptionManager` [PR #60](https://github.com/apollographql/graphql-subscriptions/pull/60) - Fixed `withFilter` issue caused multiple subscribers to execute with the same AsyncIterator [PR #69](https://github.com/apollographql/graphql-subscriptions/pull/69) ### 0.3.1 + - Add support for `defaultValue`, fixes [#49](https://github.com/apollographql/graphql-subscriptions/issues/49) (https://github.com/apollographql/graphql-subscriptions/pull/50) ### 0.3.0 + - Allow `setupFunctions` to be async (return `Promise`) (https://github.com/apollographql/graphql-subscriptions/pull/41) - Refactor promise chaining in pubsub engine (https://github.com/apollographql/graphql-subscriptions/pull/41) - Fixed a possible bug with managing subscriptions internally (https://github.com/apollographql/graphql-subscriptions/pull/29) - Return the `Promise` from `onMessage` of PubSub engine (https://github.com/apollographql/graphql-subscriptions/pull/33) ### 0.2.3 + - update `graphql` dependency to 0.9.0 ### 0.2.2 + - made `graphql` a peer dependency and updated it to 0.8.2 ### v 0.2.1 + - Fixed a bug that caused subscriptions without operationName to fail diff --git a/README.md b/README.md index 7c6a907..7843d89 100644 --- a/README.md +++ b/README.md @@ -16,8 +16,8 @@ You can use it with any GraphQL client and server (not only Apollo). If you are developing a project that uses this module with TypeScript: -* ensure that your `tsconfig.json` `lib` definition includes `"es2018.asynciterable"` -* `npm install @types/graphql` or `yarn add @types/graphql` +- ensure that your `tsconfig.json` `lib` definition includes `"es2018.asynciterable"` +- `npm install @types/graphql` or `yarn add @types/graphql` ### Getting started with your first subscription @@ -47,7 +47,7 @@ Now, let's create a simple `PubSub` instance - it is a simple pubsub implementat to the `PubSub` constructor. ```js -import { PubSub } from 'graphql-subscriptions'; +import { PubSub } from "graphql-subscriptions"; export const pubsub = new PubSub(); ``` @@ -55,35 +55,35 @@ export const pubsub = new PubSub(); If you're using TypeScript you can use the optional generic parameter for added type-safety: ```ts -import { PubSub } from "apollo-server-express"; +import { PubSub } from "graphql-subscriptions"; const pubsub = new PubSub<{ - EVENT_ONE: { data: number; }; - EVENT_TWO: { data: string; }; + EVENT_ONE: { data: number }; + EVENT_TWO: { data: string }; }>(); pubsub.publish("EVENT_ONE", { data: 42 }); -pubsub.publish("EVENTONE", { data: 42 }); // ! ERROR -pubsub.publish("EVENT_ONE", { data: "42" }); // ! ERROR +pubsub.publish("EVENTONE", { data: 42 }); // ! ERROR +pubsub.publish("EVENT_ONE", { data: "42" }); // ! ERROR pubsub.publish("EVENT_TWO", { data: "hello" }); pubsub.subscribe("EVENT_ONE", () => {}); -pubsub.subscribe("EVENTONE", () => {}); // ! ERROR +pubsub.subscribe("EVENTONE", () => {}); // ! ERROR pubsub.subscribe("EVENT_TWO", () => {}); ``` -Next implement your Subscriptions type resolver using the `pubsub.asyncIterator` to map the event you need: +Next implement your Subscriptions type resolver using the `pubsub.asyncIterableIterator` to map the event you need: ```js -const SOMETHING_CHANGED_TOPIC = 'something_changed'; +const SOMETHING_CHANGED_TOPIC = "something_changed"; export const resolvers = { Subscription: { somethingChanged: { - subscribe: () => pubsub.asyncIterator(SOMETHING_CHANGED_TOPIC), + subscribe: () => pubsub.asyncIterableIterator(SOMETHING_CHANGED_TOPIC), }, }, -} +}; ``` > Subscriptions resolvers are not a function, but an object with `subscribe` method, that returns `AsyncIterable`. @@ -91,7 +91,7 @@ export const resolvers = { The GraphQL engine now knows that `somethingChanged` is a subscription, and every time we use `pubsub.publish` it will publish content using our chosen transport layer: ```js -pubsub.publish(SOMETHING_CHANGED_TOPIC, { somethingChanged: { id: "123" }}); +pubsub.publish(SOMETHING_CHANGED_TOPIC, { somethingChanged: { id: "123" } }); ``` > Note that the default PubSub implementation is intended for demo purposes. It only works if you have a single instance of your server and doesn't scale beyond a couple of connections. @@ -104,25 +104,29 @@ When publishing data to subscribers, we need to make sure that each subscriber g To do so, we can use `withFilter` helper from this package, which wraps `AsyncIterator` with a filter function, and lets you control each publication for each user. `withFilter` API: -- `asyncIteratorFn: (rootValue, args, context, info) => AsyncIterator` : A function that returns `AsyncIterator` you got from your `pubsub.asyncIterator`. + +- `asyncIteratorFn: (rootValue, args, context, info) => AsyncIterator` : A function that returns `AsyncIterator` you got from your `pubsub.asyncIterableIterator`. - `filterFn: (payload, variables, context, info) => boolean | Promise` - A filter function, executed with the payload (the published value), variables, context and operation info, must return `boolean` or `Promise` indicating if the payload should pass to the subscriber. 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: ```js -import { withFilter } from 'graphql-subscriptions'; +import { withFilter } from "graphql-subscriptions"; -const SOMETHING_CHANGED_TOPIC = 'something_changed'; +const SOMETHING_CHANGED_TOPIC = "something_changed"; export const resolvers = { Subscription: { somethingChanged: { - subscribe: withFilter(() => pubsub.asyncIterator(SOMETHING_CHANGED_TOPIC), (payload, variables) => { - return payload.somethingChanged.id === variables.relevantId; - }), + subscribe: withFilter( + () => pubsub.asyncIterableIterator(SOMETHING_CHANGED_TOPIC), + (payload, variables) => { + return payload.somethingChanged.id === variables.relevantId; + } + ), }, }, -} +}; ``` > Note that when using `withFilter`, you don't need to wrap your return value with a function. @@ -132,25 +136,30 @@ export const resolvers = { 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. ```js -const SOMETHING_UPDATED = 'something_updated'; -const SOMETHING_CREATED = 'something_created'; -const SOMETHING_REMOVED = 'something_removed'; +const SOMETHING_UPDATED = "something_updated"; +const SOMETHING_CREATED = "something_created"; +const SOMETHING_REMOVED = "something_removed"; export const resolvers = { Subscription: { somethingChanged: { - subscribe: () => pubsub.asyncIterator([ SOMETHING_UPDATED, SOMETHING_CREATED, SOMETHING_REMOVED ]), + subscribe: () => + pubsub.asyncIterableIterator([ + SOMETHING_UPDATED, + SOMETHING_CREATED, + SOMETHING_REMOVED, + ]), }, }, -} -```` +}; +``` ### Payload Manipulation You can also manipulate the published payload, by adding `resolve` methods to your subscription: ```js -const SOMETHING_UPDATED = 'something_updated'; +const SOMETHING_UPDATED = "something_updated"; export const resolvers = { Subscription: { @@ -159,27 +168,27 @@ export const resolvers = { // Manipulate and return the new value return payload.somethingChanged; }, - subscribe: () => pubsub.asyncIterator(SOMETHING_UPDATED), + subscribe: () => pubsub.asyncIterableIterator(SOMETHING_UPDATED), }, }, -} -```` +}; +``` -Note that `resolve` methods execute *after* `subscribe`, so if the code in `subscribe` depends on a manipulated payload field, you will need to factor out the manipulation and call it from both `subscribe` and `resolve`. +Note that `resolve` methods execute _after_ `subscribe`, so if the code in `subscribe` depends on a manipulated payload field, you will need to factor out the manipulation and call it from both `subscribe` and `resolve`. ### Usage with callback listeners Your database might have callback-based listeners for changes, for example something like this: -```JS +```js const listenToNewMessages = (callback) => { - return db.table('messages').listen(newMessage => callback(newMessage)); -} + return db.table("messages").listen((newMessage) => callback(newMessage)); +}; // Kick off the listener -listenToNewMessages(message => { +listenToNewMessages((message) => { console.log(message); -}) +}); ``` The `callback` function would be called every time a new message is saved in the database. Unfortunately, that doesn't play very well with async iterators out of the box because callbacks are push-based, where async iterators are pull-based. @@ -187,7 +196,7 @@ The `callback` function would be called every time a new message is saved in the We recommend using the [`callback-to-async-iterator`](https://github.com/withspectrum/callback-to-async-iterator) module to convert your callback-based listener into an async iterator: ```js -import asyncify from 'callback-to-async-iterator'; +import asyncify from "callback-to-async-iterator"; export const resolvers = { Subscription: { @@ -195,23 +204,28 @@ export const resolvers = { subscribe: () => asyncify(listenToNewMessages), }, }, -} -```` +}; +``` ### Custom `AsyncIterator` Wrappers -The value you should return from your `subscribe` resolver must be an `AsyncIterator`. +The value you should return from your `subscribe` resolver must be an `AsyncIterable`. -You can use this value and wrap it with another `AsyncIterator` to implement custom logic over your subscriptions. +You can wrap an `AsyncIterator` with custom logic for your subscriptions. For compatibility with APIs that require `AsyncIterator` or `AsyncIterable`, your wrapper can return an `AsyncIterableIterator` to comply with both. For example, the following implementation manipulates the payload by adding some static fields: ```typescript -import { $$asyncIterator } from 'iterall'; - -export const withStaticFields = (asyncIterator: AsyncIterator, staticFields: Object): Function => { - return (rootValue: any, args: any, context: any, info: any): AsyncIterator => { - +export const withStaticFields = ( + asyncIterator: AsyncIterator, + staticFields: Object +): Function => { + return ( + rootValue: any, + args: any, + context: any, + info: any + ): AsyncIterableIterator => { return { next() { return asyncIterator.next().then(({ value, done }) => { @@ -230,7 +244,7 @@ export const withStaticFields = (asyncIterator: AsyncIterator, staticFields throw(error) { return Promise.reject(error); }, - [$$asyncIterator]() { + [Symbol.asyncIterator]() { return this; }, }; @@ -240,14 +254,10 @@ export const withStaticFields = (asyncIterator: AsyncIterator, staticFields > You can also take a look at `withFilter` for inspiration. -For more information about `AsyncIterator`: -- [TC39 Proposal](https://github.com/tc39/proposal-async-iteration) -- [iterall](https://github.com/leebyron/iterall) -- [IxJS](https://github.com/ReactiveX/IxJS) - ### PubSub Implementations It can be easily replaced with some other implementations of [PubSubEngine abstract class](https://github.com/apollographql/graphql-subscriptions/blob/master/src/pubsub-engine.ts). Here are a few of them: + - Use Redis with https://github.com/davidyaha/graphql-redis-subscriptions - Use Google PubSub with https://github.com/axelspringer/graphql-google-pubsub - Use MQTT enabled broker with https://github.com/aerogear/graphql-mqtt-subscriptions