From e27eb2e97b92684079952aaddb7c8d1ed0ab3677 Mon Sep 17 00:00:00 2001 From: Laurin Quast Date: Thu, 25 Nov 2021 18:52:49 +0100 Subject: [PATCH] feat: replace iterall with native Symbol.asyncIterator + fix return types (#232) * fix(typings): return AsyncIterableIterator instead of AsyncIterator BREAKING fixes the type annotation of the abstract class PubSubEngine. According to the TypeScript type-defintion a `PubSubAsyncIterator` instance is actually a `AsyncIterableIterator` instead of an `AsyncIterator`. The typing of `PubSubAsyncIterator` is way more convenient as it allows iterating over it with the `for await (const foo of iterator) { doSth() }` syntax, which is super handy for filtering or mapping (See https://gist.github.com/n1ru4l/127178705cc0942cad0e45d425e2eb63 for some example operators). * remove iterall * rename asyncIterator method to asyncIterableIterator. * Slight tweaks based on graphql-js 16 changes * Changelog update Co-authored-by: hwillson --- CHANGELOG.md | 2 + package.json | 4 +- ...r.ts => pubsub-async-iterable-iterator.ts} | 6 +-- src/pubsub-engine.ts | 6 +-- src/test/asyncIteratorSubscription.ts | 51 ++++++++++--------- src/test/tests.ts | 19 ++++--- src/with-filter.ts | 7 ++- 7 files changed, 49 insertions(+), 46 deletions(-) rename src/{pubsub-async-iterator.ts => pubsub-async-iterable-iterator.ts} (96%) diff --git a/CHANGELOG.md b/CHANGELOG.md index 6c2ee11..bbca927 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,8 @@ - Add an optional generic type map to `PubSub`.
[@cursorsdottsx](https://github.com/cursorsdottsx) in [#245](https://github.com/apollographql/graphql-subscriptions/pull/245) +- Replace `iterall` use with native `Symbol.asyncIterator`.
+ [@n1ru4l](https://github.com/n1ru4l) in [#232](https://github.com/apollographql/graphql-subscriptions/pull/232) ### 2.0.1 (not yet released) 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/pubsub-async-iterator.ts b/src/pubsub-async-iterable-iterator.ts similarity index 96% rename from src/pubsub-async-iterator.ts rename to src/pubsub-async-iterable-iterator.ts index 31bca41..9284902 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'; /** @@ -33,7 +32,7 @@ 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[]; @@ -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..1b0575e 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 | string[]): PubSubAsyncIterableIterator { + return new PubSubAsyncIterableIterator(this, triggers); } } diff --git a/src/test/asyncIteratorSubscription.ts b/src/test/asyncIteratorSubscription.ts index 81d98d4..d0f669d 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,20 +174,21 @@ 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; + } +})(); +describe('withFilter', () => { + it('works properly with finite asyncIterators', async () => { const filteredAsyncIterator = withFilter(() => testFiniteAsyncIterator, isEven)(); for (let i = 1; i <= 4; i++) { diff --git a/src/test/tests.ts b/src/test/tests.ts index 5190b31..a84d605 100644 --- a/src/test/tests.ts +++ b/src/test/tests.ts @@ -7,7 +7,10 @@ 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); @@ -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']); 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..f5c93e5 100644 --- a/src/with-filter.ts +++ b/src/with-filter.ts @@ -1,10 +1,9 @@ -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; -interface IterallAsyncIterator extends AsyncIterator { - [$$asyncIterator](): IterallAsyncIterator; +interface IterallAsyncIterator extends AsyncIterableIterator { + [Symbol.asyncIterator](): IterallAsyncIterator; } export type WithFilter = ( @@ -63,7 +62,7 @@ export function withFilter( throw(error) { return asyncIterator.throw(error); }, - [$$asyncIterator]() { + [Symbol.asyncIterator]() { return this; }, };