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

Support GraphQL subscriptions #285

Open
kettanaito opened this issue Jul 18, 2020 · 4 comments
Open

Support GraphQL subscriptions #285

kettanaito opened this issue Jul 18, 2020 · 4 comments

Comments

@kettanaito
Copy link
Member

kettanaito commented Jul 18, 2020

What

I suggest to add support for GraphQL subscriptions (example tutorial from Apollo).

Why

To support all three main GraphQL operations: query, mutation, and subscription.

How

  1. Research what this new feature would imply technically.
  2. Design and discuss a public API of the library for GraphQL subscriptions.
  3. Implement, document, and release.

Technical notes

  • GraphQL subscriptions are often implemented via WebSockets. There is a WebSockets support feature request open, which may be a pre-requisite for this task.
@hsavit1
Copy link

hsavit1 commented Aug 13, 2020

are there any stopgap solutions that you can recommend until this feature is built @kettanaito ?

@kettanaito
Copy link
Member Author

@hsavit1 as far as I know, GraphQL subscription is an abstraction over WebSocket. Subscriptions support would require a WebSocket support as a pre-requisite (#156). I cannot recommend any solution to achieve that at the moment. WebSocket support is on the roadmap, but I wouldn't expect it any time soon, unless you wish to contribute to it.

@Nabrok
Copy link

Nabrok commented Aug 25, 2022

I finally followed the advice from apollo and moved from subscription-transport-ws to graphql-ws.

Everything is working and tests even pass, but unfortunately they also spit out ...

      Unhandled GraphQL subscription error ApolloError: undefined
          at runNextTicks (node:internal/process/task_queues:61:5)
          at listOnTimeout (node:internal/timers:528:9)
          at processTimers (node:internal/timers:502:7) {
        graphQLErrors: [ Event { isTrusted: [Getter] } ],
        clientErrors: [],
        networkError: null,
        extraInfo: undefined
      }

With subscription-transport-ws I just had an unhandled GET request, which I could ignore with ...

rest.get(graphql_subscription_uri, (_req, res, ctx) => res(ctx.status(200)))

The best I could come up with so far to have graphql-ws run cleanly was to do this in the client in order to avoid sending to graphql-ws during tests ...

const link = process.env.NODE_ENV === 'test' ? httpLink : split(({ query }) => {
//const link = split(({ query }) => {
	const definition = getMainDefinition(query);
	return (
		definition.kind === 'OperationDefinition' &&
		definition.operation === 'subscription'
	);
}, wsLink, authHttpLink);

And then a handler like this ...

	graphql.operation(async (req, res, ctx) => {
		const { query } = await req.json();
		const definition = getMainDefinition(gql`${query}`);
		if (definition.kind !== 'OperationDefinition' || definition.operation !== 'subscription') {
			console.error(query);
			throw new Error("Not a subscription!");
		}
		return res(ctx.data({ all: null, possible: null, subscription: null, fields: null }));
	}),

This is kind of ugly and I don't like it. There must be a better way to just ignore subscriptions?

@Nabrok
Copy link

Nabrok commented Aug 26, 2022

Okay, cleaner work around ...

In __mocks__/@apollo/client/link/subscriptions.ts

import { ApolloLink, FetchResult, Observable, Operation } from '@apollo/client/core';
import { print } from 'graphql';
import { Client } from 'graphql-ws';
  
export class GraphQLWsLink extends ApolloLink {
	constructor(private client: Client) {
		super();
	}
  
	public request(operation: Operation): Observable<FetchResult> {
		return new Observable((sink) => {
			return this.client.subscribe<FetchResult>(
				{ ...operation, query: print(operation.query) },
				{
					next: sink.next.bind(sink),
					complete: sink.complete.bind(sink),
					error: () => null
				},
			);
		});
	}
}

This is basically just the code for apollo versions < 3.5.10 but with the error function doing nothing.

Then just need the rest.get handler in my earlier post.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Projects
None yet
Development

No branches or pull requests

3 participants