-
-
Notifications
You must be signed in to change notification settings - Fork 555
feat: support GraphQL subscriptions #2357
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
base: main
Are you sure you want to change the base?
Conversation
761b4ce
to
eca6e43
Compare
eca6e43
to
1ddc187
Compare
403e143
to
e24e6db
Compare
@sutt0n, I do quite share your enthusiasm! |
is there an update or roadmap here to getting this into a new release, or suggestions on how to mock gql subscriptions in the interm? |
@thearchitector there's a Roadmap section in the description of this PR. It will get populated as I find more things that have to be done before this is ready. So far, it's that one point. You can follow the PR to see the gist of what GraphQL subscriptions support entails, and how you can use the existing
|
9cd53be
to
585a913
Compare
585a913
to
02bb4d7
Compare
3f659f5
to
a6fba86
Compare
Bypassing GraphQL subscriptionsIt occurred to me that I've never designed a way to bypass a subscription. Here's a proposal: const api = graphql.link('https://api.example.com/graphql')
server.use(
api.subscription('OnCommentAdded', async ({ subscription }) => {
// This creates a NEW subscription for the same event in the actual server.
// - There is no client events to prevent! Subscription intent is sent ONCE.
const onCommentAddedSubscription = subscription.passthrough()
// Event listeners here are modeled after the subscription protocol:
// - acknowledge, server confirmed the subscription.
// - next, server is sending data to the client.
// - error, error happened (is this event a thing?). Server connection errors!
// - complete, server has completed this subscription.
onCommentAddedSubscription.addEventListener('next', (event) => {
// You can still prevent messages from the original server.
// By default, they are forwarded to the client, just like with mocking WebSockets.
event.preventDefault()
// The event data is already parsed to drop the implementation details of the subscription.
subscription.publish({
data: event.data.payload,
})
})
// You can unsubscribe from the original server subscription at any time.
onCommentAddedSubscription.unsubscribe()
}),
) I believe this gives you good ergonomics, supports the existing WebSocket mocking defaults (such as automatic server-to-client forwarding once you establish the connection), and scopes the actual subscription to the handler so you can unsubscribe at any time. If anyone has any feedback on this, please let me know! Thanks. |
26b1faf
to
6af9ab5
Compare
The tests are failing because I haven't procured the original (test) GraphQL server to emulate bypassing subscriptions. Stuck in the infinite hell of |
3209bc1
to
8387bb0
Compare
✅ Extraneous publishesIt should be possible to publish to a subscription (or data source) from anywhere (e.g. from a different handler). That's a common expectation for real-time systems like WebSockets or subscriptions that are based on that. I want to be able to do this: const pubsub = new SomePubSub()
api.subscription('OnCommentAdded', ({ subscription }) => {
// Attach an AsyncIterable to this subscription.
subscription.from(pubsub.subscribe('COMMENTS'))
})
api.mutation('AddComment', ({ variables }) => {
pubsub.publish('COMMENTS', variables.comment)
}) As usual, I should research best practices and user expectations around this pattern. I think I'm fairly close with the example above but still. It would be even better to support extraneous Solution: You can use any PubSub with MSW via |
Important
If your team can benefit from the GraphQL subscriptions support in MSW, please consider sponsoring the project. This will be a big effort, and your contribution will help to see it through. Thank you.
Roadmap
ws.link
supportlogs: false
/quiet: true
to disable the default WebSocket handler logging for GraphQL subscriptions (it's an implementation detail).graphql.subscription()
call.true
and the WebSocket pubsub will not be added. Maybe add them both all the time but dedupe when getting the current handlers?GraphQLSubscriptionHandler
extendsWebSocketHandler
and just propagates the[kDispatchEvent]
to the underlying link and its own subscription handler. They don't have to be present in thehandlers
array.handler.info.header
forGraphQLSubscriptionHandler
so it can be observed nicely with.listHandlers()
.subscription
from anywhere (proposal). You should be able to publish to a subscription within a query/mutation handler, or any other http/ws handler.subscription.from(asyncIterable)
to provide the intercepted subscription with a stream of data..publish({ data })
nesting might be redundant.graphql.operation()
so it catches subscriptions too. Most likely turn it into a custom handler that composes two underlying handlers and give it__kind: [Http, Event]
(an array so it participates in both HTTP and WS resolution).subscription.publish
andevent.data.payload
(in case of bypassed subscriptions).