diff --git a/CHANGELOG.md b/CHANGELOG.md
index e42ee7f..88cec9e 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,9 +1,19 @@
# Changelog
-### 2.0.1 (not yet released)
-
+### 3.0.0
+
+- **[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)
+- Support `readonly` arrays of event names.
+ [@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)
- `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
@@ -25,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 9f30062..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 `"esnext.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
@@ -25,11 +25,11 @@ To begin with GraphQL subscriptions, start by defining a GraphQL `Subscription`
```graphql
type Subscription {
- somethingChanged: Result
+ somethingChanged: Result
}
type Result {
- id: String
+ id: String
}
```
@@ -47,31 +47,51 @@ 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();
```
-Now, implement your Subscriptions type resolver, using the `pubsub.asyncIterator` to map the event you need:
+If you're using TypeScript you can use the optional generic parameter for added type-safety:
+
+```ts
+import { PubSub } from "graphql-subscriptions";
+
+const pubsub = new PubSub<{
+ 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("EVENT_TWO", { data: "hello" });
+
+pubsub.subscribe("EVENT_ONE", () => {});
+pubsub.subscribe("EVENTONE", () => {}); // ! ERROR
+pubsub.subscribe("EVENT_TWO", () => {});
+```
+
+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`.
-Now, the GraphQL engine knows that `somethingChanged` is a subscription, and every time we use `pubsub.publish` over this topic - it will publish it using the transport we use:
+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.
@@ -84,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.
@@ -112,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: {
@@ -139,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.
@@ -167,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: {
@@ -175,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 }) => {
@@ -210,7 +244,7 @@ export const withStaticFields = (asyncIterator: AsyncIterator, staticFields
throw(error) {
return Promise.reject(error);
},
- [$$asyncIterator]() {
+ [Symbol.asyncIterator]() {
return this;
},
};
@@ -220,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
diff --git a/package.json b/package.json
index 57596f1..124685c 100644
--- a/package.json
+++ b/package.json
@@ -7,9 +7,7 @@
"type": "git",
"url": "https://github.com/apollostack/graphql-subscriptions.git"
},
- "dependencies": {
- "iterall": "^1.3.0"
- },
+ "dependencies": {},
"peerDependencies": {
"graphql": "^15.7.2 || ^16.0.0"
},
diff --git a/src/index.ts b/src/index.ts
index 5d64e13..ba3b9a3 100644
--- a/src/index.ts
+++ b/src/index.ts
@@ -1,3 +1,3 @@
export { PubSubEngine } from './pubsub-engine';
export { PubSub, PubSubOptions } from './pubsub';
-export { withFilter, ResolverFn, FilterFn } from './with-filter';
+export { withFilter, ResolverFn, FilterFn, IterableResolverFn } from './with-filter';
diff --git a/src/pubsub-async-iterator.ts b/src/pubsub-async-iterable-iterator.ts
similarity index 92%
rename from src/pubsub-async-iterator.ts
rename to src/pubsub-async-iterable-iterator.ts
index 31bca41..3e0f99e 100644
--- a/src/pubsub-async-iterator.ts
+++ b/src/pubsub-async-iterable-iterator.ts
@@ -1,4 +1,3 @@
-import { $$asyncIterator } from 'iterall';
import { PubSubEngine } from './pubsub-engine';
/**
@@ -17,7 +16,7 @@ import { PubSubEngine } from './pubsub-engine';
* A queue of PubSubEngine events waiting for next() calls to be made, which returns the queued events
* for handling. This queue expands as PubSubEngine events arrive without next() calls occurring in-between.
*
- * @property eventsArray @type {string[]}
+ * @property eventsArray @type {readonly string[]}
* An array of PubSubEngine event names that this PubSubAsyncIterator should watch.
*
* @property allSubscribed @type {Promise}
@@ -33,16 +32,16 @@ import { PubSubEngine } from './pubsub-engine';
* @property pubsub @type {PubSubEngine}
* The PubSubEngine whose events will be observed.
*/
-export class PubSubAsyncIterator implements AsyncIterator {
+export class PubSubAsyncIterableIterator implements AsyncIterableIterator {
private pullQueue: ((value: IteratorResult) => void)[];
private pushQueue: T[];
- private eventsArray: string[];
+ private eventsArray: readonly string[];
private allSubscribed: Promise;
private running: boolean;
private pubsub: PubSubEngine;
- constructor(pubsub: PubSubEngine, eventNames: string | string[]) {
+ constructor(pubsub: PubSubEngine, eventNames: string | readonly string[]) {
this.pubsub = pubsub;
this.pullQueue = [];
this.pushQueue = [];
@@ -66,7 +65,7 @@ export class PubSubAsyncIterator implements AsyncIterator {
return Promise.reject(error);
}
- public [$$asyncIterator]() {
+ public [Symbol.asyncIterator]() {
return this;
}
@@ -119,5 +118,4 @@ export class PubSubAsyncIterator implements AsyncIterator {
this.pubsub.unsubscribe(subscriptionId);
}
}
-
}
diff --git a/src/pubsub-engine.ts b/src/pubsub-engine.ts
index afe18d7..06b984a 100644
--- a/src/pubsub-engine.ts
+++ b/src/pubsub-engine.ts
@@ -1,10 +1,10 @@
-import {PubSubAsyncIterator} from './pubsub-async-iterator';
+import {PubSubAsyncIterableIterator} from './pubsub-async-iterable-iterator';
export abstract class PubSubEngine {
public abstract publish(triggerName: string, payload: any): Promise;
public abstract subscribe(triggerName: string, onMessage: Function, options: Object): Promise;
public abstract unsubscribe(subId: number);
- public asyncIterator(triggers: string | string[]): AsyncIterator {
- return new PubSubAsyncIterator(this, triggers);
+ public asyncIterableIterator(triggers: string | readonly string[]): PubSubAsyncIterableIterator {
+ return new PubSubAsyncIterableIterator(this, triggers);
}
}
diff --git a/src/pubsub.ts b/src/pubsub.ts
index 7972369..1525efa 100644
--- a/src/pubsub.ts
+++ b/src/pubsub.ts
@@ -5,7 +5,9 @@ export interface PubSubOptions {
eventEmitter?: EventEmitter;
}
-export class PubSub extends PubSubEngine {
+export class PubSub<
+ Events extends { [event: string]: unknown } = Record
+> extends PubSubEngine {
protected ee: EventEmitter;
private subscriptions: { [key: string]: [string, (...args: any[]) => void] };
private subIdCounter: number;
@@ -17,12 +19,18 @@ export class PubSub extends PubSubEngine {
this.subIdCounter = 0;
}
- public publish(triggerName: string, payload: any): Promise {
+ public publish(
+ triggerName: K & string,
+ payload: Events[K] extends never ? any : Events[K]
+ ): Promise {
this.ee.emit(triggerName, payload);
return Promise.resolve();
}
- public subscribe(triggerName: string, onMessage: (...args: any[]) => void): Promise {
+ public subscribe(
+ triggerName: K & string,
+ onMessage: (...args: any[]) => void
+ ): Promise {
this.ee.addListener(triggerName, onMessage);
this.subIdCounter = this.subIdCounter + 1;
this.subscriptions[this.subIdCounter] = [triggerName, onMessage];
diff --git a/src/test/asyncIteratorSubscription.ts b/src/test/asyncIteratorSubscription.ts
index 81d98d4..fcf8ac8 100644
--- a/src/test/asyncIteratorSubscription.ts
+++ b/src/test/asyncIteratorSubscription.ts
@@ -5,11 +5,14 @@ import * as chaiAsPromised from 'chai-as-promised';
import { spy } from 'sinon';
import * as sinonChai from 'sinon-chai';
-import { createAsyncIterator, isAsyncIterable } from 'iterall';
import { PubSub } from '../pubsub';
import { withFilter, FilterFn } from '../with-filter';
import { ExecutionResult } from 'graphql';
+const isAsyncIterableIterator = (input: unknown): input is AsyncIterableIterator => {
+ return input != null && typeof input[Symbol.asyncIterator] === 'function';
+};
+
chai.use(chaiAsPromised);
chai.use(sinonChai);
const expect = chai.expect;
@@ -64,14 +67,13 @@ describe('GraphQL-JS asyncIterator', () => {
}
`);
const pubsub = new PubSub();
- const origIterator = pubsub.asyncIterator(FIRST_EVENT);
+ const origIterator = pubsub.asyncIterableIterator(FIRST_EVENT);
const schema = buildSchema(origIterator);
-
- const results = await subscribe({schema, document: query}) as AsyncIterator;
+ const results = await subscribe({ schema, document: query }) as AsyncIterableIterator;
const payload1 = results.next();
- expect(isAsyncIterable(results)).to.be.true;
+ expect(isAsyncIterableIterator(results)).to.be.true;
const r = payload1.then(res => {
expect(res.value.data.testSubscription).to.equal('FIRST_EVENT');
@@ -90,13 +92,13 @@ describe('GraphQL-JS asyncIterator', () => {
}
`);
const pubsub = new PubSub();
- const origIterator = pubsub.asyncIterator(FIRST_EVENT);
+ const origIterator = pubsub.asyncIterableIterator(FIRST_EVENT);
const schema = buildSchema(origIterator, () => Promise.resolve(true));
- const results = await subscribe({schema, document: query}) as AsyncIterator;
+ const results = await subscribe({ schema, document: query }) as AsyncIterableIterator;
const payload1 = results.next();
- expect(isAsyncIterable(results)).to.be.true;
+ expect(isAsyncIterableIterator(results)).to.be.true;
const r = payload1.then(res => {
expect(res.value.data.testSubscription).to.equal('FIRST_EVENT');
@@ -115,7 +117,7 @@ describe('GraphQL-JS asyncIterator', () => {
`);
const pubsub = new PubSub();
- const origIterator = pubsub.asyncIterator(FIRST_EVENT);
+ const origIterator = pubsub.asyncIterableIterator(FIRST_EVENT);
let counter = 0;
@@ -133,8 +135,8 @@ describe('GraphQL-JS asyncIterator', () => {
const schema = buildSchema(origIterator, filterFn);
- subscribe({schema, document: query}).then((results: AsyncGenerator | ExecutionResult) => {
- expect(isAsyncIterable(results)).to.be.true;
+ Promise.resolve(subscribe({ schema, document: query })).then((results: AsyncIterableIterator | ExecutionResult) => {
+ expect(isAsyncIterableIterator(results)).to.be.true;
(results as AsyncGenerator).next();
(results as AsyncGenerator).return();
@@ -155,7 +157,7 @@ describe('GraphQL-JS asyncIterator', () => {
`);
const pubsub = new PubSub();
- const origIterator = pubsub.asyncIterator(FIRST_EVENT);
+ const origIterator = pubsub.asyncIterableIterator(FIRST_EVENT);
const returnSpy = spy(origIterator, 'return');
const schema = buildSchema(origIterator);
@@ -172,21 +174,22 @@ describe('GraphQL-JS asyncIterator', () => {
});
});
-describe('withFilter', () => {
-
- it('works properly with finite asyncIterators', async () => {
- const isEven = (x: number) => x % 2 === 0;
+function isEven(x: number) {
+ if (x === undefined) {
+ throw Error('Undefined value passed to filterFn');
+ }
+ return x % 2 === 0;
+}
- const testFiniteAsyncIterator: AsyncIterator = createAsyncIterator([1, 2, 3, 4, 5, 6, 7, 8]);
- // Work around https://github.com/leebyron/iterall/issues/48
- testFiniteAsyncIterator.throw = function (error) {
- return Promise.reject(error);
- };
- testFiniteAsyncIterator.return = function () {
- return Promise.resolve({ value: undefined, done: true });
- };
+const testFiniteAsyncIterator: AsyncIterableIterator = (async function * () {
+ for (const value of [1, 2, 3, 4, 5, 6, 7, 8]) {
+ yield value;
+ }
+})();
- const filteredAsyncIterator = withFilter(() => testFiniteAsyncIterator, isEven)();
+describe('withFilter', () => {
+ it('works properly with finite asyncIterators', async () => {
+ const filteredAsyncIterator = await withFilter(() => testFiniteAsyncIterator, isEven)();
for (let i = 1; i <= 4; i++) {
const result = await filteredAsyncIterator.next();
@@ -225,7 +228,8 @@ describe('withFilter', () => {
},
};
- const filteredAsyncIterator = withFilter(() => asyncIterator, () => stopped)();
+ const filteredAsyncIterator =
+ await withFilter(() => asyncIterator, () => stopped)();
global.gc();
const heapUsed = process.memoryUsage().heapUsed;
diff --git a/src/test/tests.ts b/src/test/tests.ts
index 5190b31..fcf5a9c 100644
--- a/src/test/tests.ts
+++ b/src/test/tests.ts
@@ -7,12 +7,15 @@ import * as chaiAsPromised from 'chai-as-promised';
import * as sinonChai from 'sinon-chai';
import { PubSub } from '../pubsub';
-import { isAsyncIterable } from 'iterall';
+
+const isAsyncIterableIterator = (input: unknown): input is AsyncIterableIterator => {
+ return input != null && typeof input[Symbol.asyncIterator] === 'function';
+};
chai.use(chaiAsPromised);
chai.use(sinonChai);
const expect = chai.expect;
-const assert = chai.assert;
+const assert: Chai.AssertStatic = chai.assert;
describe('PubSub', function() {
it('can subscribe and is called when events happen', () => {
@@ -37,15 +40,15 @@ describe('AsyncIterator', () => {
it('should expose valid asyncIterator for a specific event', () => {
const eventName = 'test';
const ps = new PubSub();
- const iterator = ps.asyncIterator(eventName);
+ const iterator = ps.asyncIterableIterator(eventName);
expect(iterator).to.not.be.undefined;
- expect(isAsyncIterable(iterator)).to.be.true;
+ expect(isAsyncIterableIterator(iterator)).to.be.true;
});
it('should trigger event on asyncIterator when published', done => {
const eventName = 'test';
const ps = new PubSub();
- const iterator = ps.asyncIterator(eventName);
+ const iterator = ps.asyncIterableIterator(eventName);
iterator.next().then(result => {
expect(result).to.not.be.undefined;
@@ -60,7 +63,7 @@ describe('AsyncIterator', () => {
it('should not trigger event on asyncIterator when publishing other event', () => {
const eventName = 'test2';
const ps = new PubSub();
- const iterator = ps.asyncIterator('test');
+ const iterator = ps.asyncIterableIterator('test');
const spy = sinon.spy();
iterator.next().then(spy);
@@ -71,7 +74,7 @@ describe('AsyncIterator', () => {
it('register to multiple events', done => {
const eventName = 'test2';
const ps = new PubSub();
- const iterator = ps.asyncIterator(['test', 'test2']);
+ const iterator = ps.asyncIterableIterator(['test', 'test2'] as const);
const spy = sinon.spy();
iterator.next().then(() => {
@@ -85,7 +88,7 @@ describe('AsyncIterator', () => {
it('should not trigger event on asyncIterator already returned', done => {
const eventName = 'test';
const ps = new PubSub();
- const iterator = ps.asyncIterator(eventName);
+ const iterator = ps.asyncIterableIterator(eventName);
iterator.next().then(result => {
expect(result).to.deep.equal({
@@ -117,7 +120,7 @@ describe('AsyncIterator', () => {
}
}
const ps = new TestPubSub();
- ps.asyncIterator(testEventName);
+ ps.asyncIterableIterator(testEventName);
expect(ps.listenerCount(testEventName)).to.equal(0);
});
diff --git a/src/with-filter.ts b/src/with-filter.ts
index 2403a73..c881533 100644
--- a/src/with-filter.ts
+++ b/src/with-filter.ts
@@ -1,23 +1,22 @@
-import { $$asyncIterator } from 'iterall';
-
export type FilterFn = (rootValue?: TSource, args?: TArgs, context?: TContext, info?: any) => boolean | Promise;
-export type ResolverFn = (rootValue?: TSource, args?: TArgs, context?: TContext, info?: any) => AsyncIterator;
+export type ResolverFn = (rootValue?: TSource, args?: TArgs, context?: TContext, info?: any) => AsyncIterator | Promise>;
+export type IterableResolverFn = (rootValue?: TSource, args?: TArgs, context?: TContext, info?: any) => AsyncIterableIterator | Promise>;
-interface IterallAsyncIterator extends AsyncIterator {
- [$$asyncIterator](): IterallAsyncIterator;
+interface IterallAsyncIterator extends AsyncIterableIterator {
+ [Symbol.asyncIterator](): IterallAsyncIterator;
}
export type WithFilter = (
asyncIteratorFn: ResolverFn,
filterFn: FilterFn
-) => ResolverFn;
+) => IterableResolverFn;
export function withFilter(
asyncIteratorFn: ResolverFn,
filterFn: FilterFn
-): ResolverFn {
- return (rootValue: TSource, args: TArgs, context: TContext, info: any): IterallAsyncIterator => {
- const asyncIterator = asyncIteratorFn(rootValue, args, context, info);
+): IterableResolverFn {
+ return async (rootValue: TSource, args: TArgs, context: TContext, info: any): Promise> => {
+ const asyncIterator = await asyncIteratorFn(rootValue, args, context, info);
const getNextPromise = () => {
return new Promise>((resolve, reject) => {
@@ -63,7 +62,7 @@ export function withFilter(
throw(error) {
return asyncIterator.throw(error);
},
- [$$asyncIterator]() {
+ [Symbol.asyncIterator]() {
return this;
},
};