Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Implement GraphQL subscription #115

Merged
merged 6 commits into from Sep 29, 2021
Merged

Implement GraphQL subscription #115

merged 6 commits into from Sep 29, 2021

Conversation

hoangvvo
Copy link
Contributor

@hoangvvo hoangvvo commented May 23, 2021

This PR builds upon the previous one #94.

I refactor the implementation and update with recent changes in graphql-js.

The test has also been updated to include the latest test cases from graphql-js using graphql/graphql-js@1bd65a3 (3 days ago). For the diff of our test cases and theirs (mainly because we don't support root resolver), see 6c8cfc6.

Close #74

@hoangvvo hoangvvo mentioned this pull request May 23, 2021
{
message:
// DIFF: 'Variable "$arg" got invalid value "meow"; Int cannot represent non-integer value: "meow"',
'Variable "$arg" got invalid value "meow"; Expected type Int; Int cannot represent non-integer value: "meow"',
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think, if it is not too breaking, we should change this message to stay up to date with graphql-js. Wdyt?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, we can change it.

@hoangvvo
Copy link
Contributor Author

@boopathi I have confirmed that your test case (https://gist.github.com/boopathi/885f41720f258b50a2ba67bed76581af) now works. We are covering it in this case:

it("accepts type definition with sync subscribe function", async () => {
async function* fooGenerator() {
yield { foo: "FooValue" };
}
const schema = new GraphQLSchema({
query: DummyQueryType,
subscription: new GraphQLObjectType({
name: "Subscription",
fields: {
foo: {
type: GraphQLString,
subscribe: fooGenerator
}
}
})
});

@hoangvvo
Copy link
Contributor Author

@ruiaraujo Could you please approve the CI run? Thanks!

@ruiaraujo ruiaraujo requested a review from boopathi May 23, 2021 20:30
@ardatan
Copy link
Contributor

ardatan commented Sep 3, 2021

Any updates for this PR?

@hoangvvo
Copy link
Contributor Author

hoangvvo commented Sep 9, 2021

Any updates for this PR?

On my side, all tests are currently passing, so I'm just waiting for a review actually.

@boopathi
Copy link
Member

@hoangvvo

@boopathi I have confirmed that your test case (https://gist.github.com/boopathi/885f41720f258b50a2ba67bed76581af) now works

I'm trying it out without the resolve function mentioned in my test case. It still gives me an error. Looks like when using makeExecutableSchema, a resolve function that does (x) => x is necessary for subscriptions. I don't understand this limitation.

@hoangvvo
Copy link
Contributor Author

hoangvvo commented Sep 21, 2021

@hoangvvo

@boopathi I have confirmed that your test case (https://gist.github.com/boopathi/885f41720f258b50a2ba67bed76581af) now works

I'm trying it out without the resolve function mentioned in my test case. It still gives me an error. Looks like when using makeExecutableSchema, a resolve function that does (x) => x is necessary for subscriptions. I don't understand this limitation.

Sorry for the late reply, as I mentioned in the other PR, that pattern is not supported by graphql-js either. I think the function can only either be non-async generator function, or a promise that resolved into a async iterator.

import { makeExecutableSchema } from "@graphql-tools/schema";
import { parse, subscribe } from "graphql";

function isAsyncIterable<T>(val: any): val is AsyncIterableIterator<T> {
  return val != null && typeof val[Symbol.asyncIterator] === "function";
}

const schema = makeExecutableSchema({
  typeDefs: `
    type Subscription {
      nodes: Node
    }
    type Node {
      id: ID
    }
    type Query {
      foo: ID
    }
  `,
  resolvers: {
    Query: {
      foo: () => ""
    },
    Subscription: {
      nodes: {
        async *subscribe() {
          for (let i = 0; i < 3; i++) {
            yield {
              id: i.toString()
            };
          }
        },
      }
    }
  }
});

describe("subscription happy case", () => {
  test("nodes and ids", async () => {
    const sub = await subscribe({ document: parse(`
      subscription {
        nodes {
          id
        }
      }
    `) });
    if (isAsyncIterable(sub)) {
      let i = 0;
      for await (const val of sub) {
        expect(val.data?.nodes?.id).toBe((i++).toString());
      }
    }
  });
});

Copy link
Member

@boopathi boopathi left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM 🎉

@boopathi
Copy link
Member

👍

try {
await iterator.return();
} catch (e) {
/* ignore error */
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Would it be possible to extend the comment here to explicitly state why we ignore the error?

@oporkka
Copy link
Member

oporkka commented Sep 29, 2021

👍

@boopathi boopathi merged commit f522b86 into zalando-incubator:master Sep 29, 2021
@boopathi boopathi mentioned this pull request Sep 29, 2021
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Support createSourceEventStream?
4 participants