msw
Version:
180 lines (168 loc) • 5.56 kB
text/typescript
import type {
ResponseResolver,
RequestHandlerOptions,
} from '#core/handlers/request-handler'
import {
GraphQLHandler,
type GraphQLVariables,
type GraphQLOperationType,
type GraphQLResolverExtras,
type GraphQLResponseBody,
type GraphQLQuery,
type GraphQLPredicate,
} from './graphql-handler'
import { createRequestId } from '@mswjs/interceptors'
import { getAllRequestCookies } from '#core/utils/request/get-request-cookies'
import type { Path } from '#core/utils/matching/match-request-url'
import { attachSiblingHandlers } from '#core/utils/internal/attach-sibling-handlers'
import {
createGraphQLSubscriptionHandler,
type GraphQLSubscriptionHandlerFactory,
} from './graphql-subscription'
export type GraphQLRequestHandler = <
Query extends GraphQLQuery = GraphQLQuery,
Variables extends GraphQLVariables = GraphQLVariables,
>(
predicate: GraphQLPredicate<Query, Variables>,
resolver: GraphQLResponseResolver<
[Query] extends [never] ? GraphQLQuery : Query,
Variables
>,
options?: RequestHandlerOptions,
) => GraphQLHandler
export type GraphQLOperationHandler = <
Query extends GraphQLQuery = GraphQLQuery,
Variables extends GraphQLVariables = GraphQLVariables,
>(
resolver: GraphQLResponseResolver<
[Query] extends [never] ? GraphQLQuery : Query,
Variables
>,
options?: RequestHandlerOptions,
) => GraphQLHandler
export type GraphQLResponseResolver<
Query extends GraphQLQuery = GraphQLQuery,
Variables extends GraphQLVariables = GraphQLVariables,
> = ResponseResolver<
GraphQLResolverExtras<Variables>,
null,
GraphQLResponseBody<[Query] extends [never] ? GraphQLQuery : Query>
>
function createScopedGraphQLHandler(
operationType: GraphQLOperationType,
url: Path,
): GraphQLRequestHandler {
return (predicate, resolver, options = {}) => {
return new GraphQLHandler(operationType, predicate, url, resolver, options)
}
}
function createGraphQLOperationHandler(
url: Path,
subscriptionFactory?: GraphQLSubscriptionHandlerFactory,
): GraphQLOperationHandler {
/**
* @note An explicitly generic function so the subscription sibling
* can be created with the same `Query`/`Variables` types as the
* operation resolver, without casting its resolver info.
*/
return <
Query extends GraphQLQuery = GraphQLQuery,
Variables extends GraphQLVariables = GraphQLVariables,
>(
resolver: GraphQLResponseResolver<
[Query] extends [never] ? GraphQLQuery : Query,
Variables
>,
options?: RequestHandlerOptions,
): GraphQLHandler => {
const handler = new GraphQLHandler(
'all',
new RegExp('.*'),
url,
resolver,
options,
)
if (!subscriptionFactory) {
return handler
}
// Attach a catch-all subscription handler as a sibling so the
// operation handler also matches GraphQL subscriptions over WebSocket.
// The subscription transport guarantees only actual GraphQL
// subscriptions are dispatched to it.
const subscriptionCatchAllHandler = subscriptionFactory<Query, Variables>(
new RegExp('.*'),
({ operationName, subscription, request, finalize }) => {
/**
* @note Subscriptions are resolved imperatively, so the return
* value of the resolver is ignored. The request describes the
* WebSocket connection this subscription is multiplexed over.
*/
resolver({
operationName,
query: subscription.query,
variables: subscription.variables,
cookies: getAllRequestCookies(request),
request,
requestId: createRequestId(),
finalize,
})
},
// Forward the handler options so a one-time operation handler is
// also consumed by the first subscription it matches.
options,
)
return attachSiblingHandlers(handler, [subscriptionCatchAllHandler])
}
}
export interface GraphQLLink {
query: GraphQLRequestHandler
mutation: GraphQLRequestHandler
operation: GraphQLOperationHandler
/**
* Intercept a GraphQL subscription.
*
* @example
* const api = graphql.link('https://api.example.com/graphql')
* api.subscription('OnPostAdded', ({ subscription }) => {
* subscription.publish({
* data: { postAdded: { id: 'abc-123' } },
* })
* })
*/
subscription: GraphQLSubscriptionHandlerFactory
}
/**
* A namespace to intercept and mock GraphQL operations
*
* @example
* const api = graphql.link('https://api.example.com/graphql')
* api.query('GetUser', resolver)
* api.mutation('DeletePost', resolver)
*
* @see {@link https://mswjs.io/docs/api/graphql `graphql` API reference}
*/
export const graphql = {
/**
* Intercepts GraphQL operations scoped by the given URL.
*
* @example
* const github = graphql.link('https://api.github.com/graphql')
* github.query('GetRepo', resolver)
*
* @see {@link https://mswjs.io/docs/api/graphql#graphqllinkurl `graphql.link()` API reference}
*/
link(url: Path): GraphQLLink {
/**
* @note Create the subscription handler factory once per link so
* the `subscription()` and `operation()` handlers share the same
* underlying subscription transport (deduped by reference).
*/
const subscription = createGraphQLSubscriptionHandler(url)
return {
operation: createGraphQLOperationHandler(url, subscription),
query: createScopedGraphQLHandler('query', url),
mutation: createScopedGraphQLHandler('mutation', url),
subscription,
}
},
}