1- import { DocumentNode , OperationTypeNode } from 'graphql'
1+ import { OperationTypeNode } from 'graphql'
22import type {
33 GraphQLHandlerNameSelector ,
44 GraphQLQuery ,
@@ -11,10 +11,11 @@ import { WebSocketHandler } from './WebSocketHandler'
1111import { jsonParse } from '../utils/internal/jsonParse'
1212import type { TypedDocumentNode } from '../graphql'
1313import { BatchHandler } from './BatchHandler'
14+ import { createRequestId } from '@mswjs/interceptors'
1415
1516export interface GraphQLPubsub {
1617 /**
17- * A WebSocket handler to intercept GraphQL subscription events .
18+ * A WebSocket handler associated with this pubsub .
1819 */
1920 handler : WebSocketHandler
2021
@@ -32,11 +33,13 @@ export interface GraphQLPubsub {
3233type GraphQLWebSocketInitMessage = {
3334 type : 'connection_init'
3435}
36+
3537type GraphQLWebSocketSubscribeMessage = {
3638 type : 'subscribe'
3739 id : string
3840 payload : GraphQLWebSocketSubscribeMessagePayload
3941}
42+
4043type GraphQLWebSocketCompleteMessage = {
4144 type : 'complete'
4245 id : string
@@ -50,6 +53,7 @@ type GraphQLWebSocketOutgoingMessage =
5053type GraphQLWebSocketAcknowledgeMessage = {
5154 type : 'connection_ack'
5255}
56+
5357type GraphQLWebSocketNextMessage = {
5458 type : 'next'
5559 id : string
@@ -192,37 +196,73 @@ export class GraphQLSubscription<
192196 }
193197}
194198
195- export type GraphQLSubscriptionHandler = <
199+ export type GraphQLSubscriptionName <
200+ Query extends GraphQLQuery ,
201+ Variables extends GraphQLVariables ,
202+ > = GraphQLHandlerNameSelector | TypedDocumentNode < Query , Variables >
203+
204+ export type GraphQLSubscriptionResolver <
205+ Query extends GraphQLQuery = GraphQLQuery ,
206+ Variables extends GraphQLVariables = GraphQLVariables ,
207+ > = ( info : GraphQLSubscriptionResolverInfo < Query , Variables > ) => void
208+
209+ export type GraphQLSubscriptionHandlerFactory = <
196210 Query extends GraphQLQuery = GraphQLQuery ,
197211 Variables extends GraphQLVariables = GraphQLVariables ,
198212> (
199- operationName :
200- | GraphQLHandlerNameSelector
201- | DocumentNode
202- | TypedDocumentNode < Query , Variables > ,
203- resolver : ( info : GraphQLSubscriptionHandlerInfo < Query , Variables > ) => void ,
204- ) => BatchHandler | WebSocketHandler
205-
206- export interface GraphQLSubscriptionHandlerInfo <
213+ operationName : GraphQLSubscriptionName < Query , Variables > ,
214+ resolver : GraphQLSubscriptionResolver < Query , Variables > ,
215+ ) => GraphQLSubscriptionHandler < Query , Variables >
216+
217+ export interface GraphQLSubscriptionResolverInfo <
207218 Query extends GraphQLQuery ,
208219 Variables extends GraphQLVariables ,
209220> {
210221 params : PathParams
211222 operationName : string
212223
213224 /**
214- * An object representing the intercepted GraphQL subscription.
225+ * Intercepted GraphQL subscription.
215226 */
216227 subscription : GraphQLSubscription < Query , Variables >
217228}
218229
219- export function createGraphQLSubscriptionHandler (
220- internalPubsub : GraphQLInternalPubsub ,
221- ) : GraphQLSubscriptionHandler {
222- return ( operationName , resolver ) => {
223- const subscriptionHandler = internalPubsub . webSocketLink . addEventListener (
230+ interface GraphQLSubscriptionHandlerArgs <
231+ Query extends GraphQLQuery ,
232+ Variables extends GraphQLVariables ,
233+ > {
234+ info : GraphQLSubscriptionHandlerInfo < Query , Variables >
235+ pubsub : GraphQLInternalPubsub
236+ resolver : GraphQLSubscriptionResolver < any , any >
237+ }
238+
239+ interface GraphQLSubscriptionHandlerInfo <
240+ Query extends GraphQLQuery ,
241+ Variables extends GraphQLVariables ,
242+ > {
243+ header : string
244+ operationName : GraphQLSubscriptionName < Query , Variables >
245+ }
246+
247+ export class GraphQLSubscriptionHandler <
248+ Query extends GraphQLQuery ,
249+ Variables extends GraphQLVariables ,
250+ /**
251+ * @fixme This CANNOT be a batch handler.
252+ * I'm not sure a batch handler is a good concept overall.
253+ * It "unfolds" its entries, making it super unclear who
254+ * actually originated them. Consider "fallthrough" WebSocket handler here.
255+ */
256+ > extends BatchHandler {
257+ public id : string
258+ public info : GraphQLSubscriptionHandlerInfo < Query , Variables >
259+
260+ constructor (
261+ private readonly args : GraphQLSubscriptionHandlerArgs < Query , Variables > ,
262+ ) {
263+ const subscriptionHandler = args . pubsub . webSocketLink . addEventListener (
224264 'connection' ,
225- ( { client , params } ) => {
265+ ( { params , client } ) => {
226266 client . addEventListener ( 'message' , async ( event ) => {
227267 if ( typeof event . data !== 'string' ) {
228268 return
@@ -241,14 +281,14 @@ export function createGraphQLSubscriptionHandler(
241281
242282 if (
243283 node . operationType === OperationTypeNode . SUBSCRIPTION &&
244- node . operationName === operationName
284+ node . operationName === args . info . operationName
245285 ) {
246286 const subscription = new GraphQLSubscription < any , any > ( {
247287 message,
248- internalPubsub,
288+ internalPubsub : args . pubsub ,
249289 } )
250290
251- resolver ( {
291+ args . resolver ( {
252292 params,
253293 operationName : node . operationName ,
254294 subscription,
@@ -259,19 +299,56 @@ export function createGraphQLSubscriptionHandler(
259299 } ,
260300 )
261301
302+ const isPubsubHandlerAdded = Reflect . get (
303+ args . pubsub . pubsub . handler ,
304+ kPubsubHandlerAdded ,
305+ )
306+
262307 // Skip adding the internal pubsub WebSocket handler if it was already added.
263- // We only need that handler once, and there's no need to pollute the handlers.
264- if ( Reflect . get ( internalPubsub . pubsub . handler , kPubSubHandlerAdded ) ) {
265- return subscriptionHandler
308+ // We only need that handler once, and there's no need to pollute the handlers.
309+ if ( isPubsubHandlerAdded ) {
310+ super ( [ subscriptionHandler ] )
311+ } else {
312+ super ( [ args . pubsub . pubsub . handler , subscriptionHandler ] )
313+
314+ /**
315+ * @todo @fixme This will NOT reset on `.resetHandlers()`,
316+ * breaking the subscriptions interception.
317+ */
318+ Reflect . set ( args . pubsub . pubsub . handler , kPubSubHandlerAdded , true )
266319 }
267320
268- // If this is the first time using this subscription, add a batch handler.
269- const batchSubscriptionHandler = new BatchHandler ( [
270- internalPubsub . pubsub . handler ,
271- subscriptionHandler ,
272- ] )
273- Reflect . set ( internalPubsub . pubsub . handler , kPubSubHandlerAdded , true )
321+ this . id = createRequestId ( )
322+ this . info = args . info
323+ }
324+ }
325+
326+ function getDisplayOperationName (
327+ _operationName : GraphQLSubscriptionName < any , any > ,
328+ ) : string {
329+ /**
330+ * @todo
331+ */
332+ return '???'
333+ }
334+
335+ const kPubsubHandlerAdded = Symbol ( 'pubsubHandlerAdded' )
274336
275- return batchSubscriptionHandler
337+ export function createGraphQLSubscriptionHandler (
338+ internalPubsub : GraphQLInternalPubsub ,
339+ ) : GraphQLSubscriptionHandlerFactory {
340+ return ( operationName , resolver ) => {
341+ return new GraphQLSubscriptionHandler ( {
342+ /**
343+ * @fixme Remove this and instead add `info` support to
344+ * BatchHandler. Fill in the values in GraphQLSubscriptionHandler.
345+ */
346+ info : {
347+ header : getDisplayOperationName ( operationName ) ,
348+ operationName,
349+ } ,
350+ pubsub : internalPubsub ,
351+ resolver,
352+ } )
276353 }
277354}
0 commit comments