msw
Version:
550 lines • 22.2 kB
TypeScript
import { a as RequestHandler, c as RequestHandlerOptions, g as ResponseResolutionContext, h as StrictRequest, l as ResponseResolver, n as DefaultBodyType, o as RequestHandlerDefaultInfo, s as RequestHandlerExecutionResult, u as ResponseResolverFinalizeFunction, y as MaybePromise } from "../_chunks/request-handler.js";
import { c as WebSocketHandler, f as WebSocketResolutionContext, l as WebSocketHandlerConnection, p as kConnect } from "../_chunks/handlers-controller.js";
import { n as Path, r as PathParams, t as Match } from "../_chunks/match-request-url.js";
import { Emitter, TypedEvent } from "rettime";
import { WebSocketClientHandle, WebSocketConnectionEventData, WebSocketServerHandle } from "@mswjs/interceptors/WebSocket";
import { DocumentNode, GraphQLError, OperationTypeNode } from "graphql";
//#region src/graphql/parse-graphql-request.d.ts
interface ParsedGraphQLQuery {
/**
* Undefined if the document has no operation matching the
* requested operation name.
*/
operationType?: OperationTypeNode;
operationName?: string;
}
type ParsedGraphQLRequest<VariablesType extends GraphQLVariables = GraphQLVariables> = (ParsedGraphQLQuery & {
query: string;
variables?: VariablesType;
}) | undefined;
type GraphQLMultipartRequestBody = {
operations: string;
map?: string;
} & {
[fileName: string]: File;
};
//#endregion
//#region src/graphql/graphql-handler.d.ts
interface DocumentTypeDecoration<Result = {
[key: string]: any;
}, Variables = {
[key: string]: any;
}> {
__apiType?: (variables: Variables) => Result;
__resultType?: Result;
__variablesType?: Variables;
}
type GraphQLOperationType = OperationTypeNode | 'all';
type GraphQLHandlerNameSelector = DocumentNode | RegExp | string;
type GraphQLQuery = Record<string, any> | null;
type GraphQLVariables = Record<string, any>;
interface GraphQLHandlerInfo extends RequestHandlerDefaultInfo {
operationType: GraphQLOperationType;
operationName: GraphQLHandlerNameSelector | GraphQLCustomPredicate;
}
type GraphQLRequestParsedResult = {
match: Match;
cookies: Record<string, string>;
} & (ParsedGraphQLRequest<GraphQLVariables> |
/**
* An empty version of the ParsedGraphQLRequest
* which simplifies the return type of the resolver
* when the request is to a non-matching endpoint
*/
{
operationType?: undefined;
operationName?: undefined;
query?: undefined;
variables?: undefined;
});
type GraphQLResolverExtras<Variables extends GraphQLVariables> = {
query: string;
operationName: string;
variables: Variables;
cookies: Record<string, string>;
};
type GraphQLRequestBody<VariablesType extends GraphQLVariables> = GraphQLJsonRequestBody<VariablesType> | GraphQLMultipartRequestBody | Record<string, any> | undefined;
interface GraphQLJsonRequestBody<Variables extends GraphQLVariables> {
query: string;
variables?: Variables;
}
type GraphQLResponseBody<BodyType extends DefaultBodyType> = {
data?: BodyType | null;
errors?: readonly Partial<GraphQLError>[] | null;
extensions?: Record<string, any>;
} | null | undefined;
type GraphQLCustomPredicate = (args: {
request: Request;
query: string;
operationType: GraphQLOperationType;
operationName: string;
variables: GraphQLVariables;
cookies: Record<string, string>;
}) => GraphQLCustomPredicateResult | Promise<GraphQLCustomPredicateResult>;
type GraphQLCustomPredicateResult = boolean | {
matches: boolean;
};
type GraphQLPredicate<Query = any, Variables = any> = GraphQLHandlerNameSelector | DocumentTypeDecoration<Query, Variables> | GraphQLCustomPredicate;
export declare class GraphQLHandler extends RequestHandler<GraphQLHandlerInfo, GraphQLRequestParsedResult, GraphQLResolverExtras<any>> {
#private;
private endpoint;
static parsedRequestCache: WeakMap<Request, ParsedGraphQLRequest<GraphQLVariables>>;
/**
* Creates a GraphQL handler information object from the given
* operation predicate. Normalizes `DocumentNode` and typed document
* string predicates to plain operation names.
*/
static parseGraphQLRequestInfo(args: {
operationType: GraphQLOperationType;
predicate: GraphQLPredicate;
url: Path;
}): GraphQLHandlerInfo;
constructor(operationType: GraphQLOperationType, predicate: GraphQLPredicate, endpoint: Path, resolver: ResponseResolver<GraphQLResolverExtras<any>, any, any>, options?: RequestHandlerOptions);
/**
* Parses the request body, once per request, cached across all
* GraphQL handlers. This is done to avoid multiple parsing of the
* request body, which each requires a clone of the request.
*/
parseGraphQLRequestOrGetFromCache(request: Request): Promise<ParsedGraphQLRequest<GraphQLVariables>>;
parse(args: {
request: Request;
}): Promise<GraphQLRequestParsedResult>;
predicate(args: {
request: Request;
parsedResult: GraphQLRequestParsedResult;
}): Promise<boolean>;
run(args: {
request: StrictRequest<any>;
requestId: string;
resolutionContext?: ResponseResolutionContext;
}): Promise<RequestHandlerExecutionResult<GraphQLRequestParsedResult> | null>;
private matchOperationName;
protected extendResolverArgs(args: {
request: Request;
parsedResult: GraphQLRequestParsedResult;
}): {
query: string;
operationType: OperationTypeNode;
operationName: string;
variables: GraphQLVariables;
cookies: Record<string, string>;
};
log(args: {
request: Request;
response: Response;
parsedResult: GraphQLRequestParsedResult;
}): Promise<void>;
}
//#endregion
//#region src/graphql/graphql-subscription.d.ts
interface GraphQLWebSocketSubscribePayload<Variables extends GraphQLVariables = GraphQLVariables> {
operationName?: string | null;
query: string;
variables?: Variables;
extensions?: Record<string, unknown>;
}
interface GraphQLWebSocketSubscribeMessage<Variables extends GraphQLVariables = GraphQLVariables> {
type: 'subscribe';
id: string;
payload: GraphQLWebSocketSubscribePayload<Variables>;
}
interface GraphQLWebSocketCompleteMessage {
type: 'complete';
id: string;
}
interface GraphQLWebSocketNextMessage {
type: 'next';
id: string;
payload: GraphQLSubscriptionPayload;
}
interface GraphQLWebSocketErrorMessage {
type: 'error';
id: string;
payload: ReadonlyArray<Partial<GraphQLError>>;
}
/**
* A GraphQL execution result published to a subscription.
*/
interface GraphQLSubscriptionPayload<Query extends GraphQLQuery = GraphQLQuery> {
data?: Query | null;
errors?: ReadonlyArray<Partial<GraphQLError>> | null;
extensions?: Record<string, unknown>;
}
/**
* A subscriber provided by a `GraphQLSubscriptionHandler` for a particular
* WebSocket connection. Returns true if the handler matched the parsed
* subscribe operation and resolved it.
*/
type GraphQLSubscriptionSubscriber = (args: {
node: ParsedGraphQLQuery;
message: GraphQLWebSocketSubscribeMessage;
}) => boolean;
interface GraphQLSubscriptionSubscriberEntry {
transport: GraphQLSubscriptionTransportHandler;
subscriber: GraphQLSubscriptionSubscriber;
}
type GraphQLSubscriptionCleanup = () => MaybePromise<void>;
interface GraphQLSubscriptionConnection {
client: WebSocketClientHandle;
server: WebSocketServerHandle;
subscribers: Map<WebSocketHandler, GraphQLSubscriptionSubscriberEntry>;
/**
* The active subscriptions of this connection, mapped to the cleanups
* scheduled for them via the resolver's `finalize()`.
*/
subscriptions: Map<string, Array<GraphQLSubscriptionCleanup>>;
events?: WebSocketResolutionContext['events'];
/**
* The payload of the client's `connection_init` message (i.e. the
* `connectionParams` of the GraphQL client). Kept so it can be
* replayed to the original server on passthrough.
*/
connectionParams?: Record<string, unknown>;
/**
* Resolves once the original server has acknowledged this connection.
* The upstream session is established once per connection, no matter
* how many subscriptions pass through it.
*/
upstreamSession?: Promise<void>;
}
/**
* A WebSocket handler that only matches connections that requested the
* `graphql-transport-ws` protocol. Connections without it (e.g.
* arbitrary non-GraphQL WebSockets whose URL matched a permissive
* endpoint like a wildcard link) are left unhandled so they follow the
* regular unhandled connection flow (warnings, passthrough).
*/
declare abstract class GraphQLWebSocketHandler extends WebSocketHandler {
run(connection: WebSocketConnectionEventData, resolutionContext?: WebSocketResolutionContext): Promise<WebSocketHandlerConnection | null>;
}
/**
* A WebSocket handler implementing the `graphql-transport-ws` protocol
* session for a single GraphQL endpoint. One transport is shared across
* all subscription handlers created from the same `graphql.link()` call
* (attached to each of them as a sibling handler).
*
* The transport owns the protocol/session concerns: connection
* acknowledgement, keep-alive, the per-connection registry of active
* subscriptions, and dispatching parsed `subscribe` operations to the
* matching subscription handler.
*/
declare class GraphQLSubscriptionTransportHandler extends GraphQLWebSocketHandler {
#private;
/**
* Register the given handler as a subscriber to the GraphQL
* subscriptions on the given WebSocket connection. Subscribers are
* dispatched in registration order, which follows the handlers
* resolution order (runtime handlers take precedence).
*/
subscribe(connection: WebSocketHandlerConnection, handler: WebSocketHandler, subscriber: GraphQLSubscriptionSubscriber): void;
getConnection(clientId: string): GraphQLSubscriptionConnection | undefined;
run(connection: WebSocketConnectionEventData, resolutionContext?: WebSocketResolutionContext): Promise<WebSocketHandlerConnection | null>;
/**
* End the given subscription without notifying the client. Used when
* the subscription has already been terminated over the wire (e.g. the
* original server completed it and that frame reached the client).
*/
endSubscription(args: {
clientId: string;
subscriptionId: string;
}): void;
/**
* Schedule a cleanup to run once the given subscription ends.
*
* @note If the subscription has already ended by the time this is
* called, the cleanup runs immediately. The resolver can no longer
* affect that subscription, so there is nothing left to wait for.
*/
finalize(args: {
clientId: string;
subscriptionId: string;
cleanup: GraphQLSubscriptionCleanup;
}): void;
/**
* Send a `next` message with the given payload to the subscription.
*/
publish(args: {
clientId: string;
subscriptionId: string;
payload: GraphQLSubscriptionPayload;
}): void;
/**
* Send a terminal `error` message to the subscription and
* removes it from the registry of active subscriptions.
*/
error(args: {
clientId: string;
subscriptionId: string;
errors: ReadonlyArray<Partial<GraphQLError>>;
}): void;
/**
* Send a `complete` message to the subscription and removes it
* from the registry of active subscriptions.
*/
complete(args: {
clientId: string;
subscriptionId: string;
}): void;
/**
* Drop this transport's subscribers and active subscriptions from the
* sessions it participates in. The sessions themselves are left intact:
* they are shared with the other transports of the same connection and
* own the protocol listeners for as long as the client stays connected.
*
* @note This method is invoked automatically when the handlers
* controller resets the handlers (e.g. `server.resetHandlers()`).
*/
reset(): void;
/**
* Forget the sessions of this transport, ending their subscriptions.
* @note This method is invoked automatically when the network is
* disabled (e.g. `server.close()`).
*/
dispose(): MaybePromise<void>;
/**
* @note The transport is the sole owner of logging for GraphQL
* subscription connections. It logs parsed `graphql-transport-ws`
* frames instead of raw WebSocket messages.
*/
log(connection: WebSocketConnectionEventData): () => void;
protected [kConnect](connection: WebSocketHandlerConnection): boolean;
}
type GraphQLSubscriptionName<Query extends GraphQLQuery = GraphQLQuery, Variables extends GraphQLVariables = GraphQLVariables> = GraphQLHandlerNameSelector | DocumentTypeDecoration<Query, Variables>;
interface GraphQLSubscriptionResolverInfo<Query extends GraphQLQuery = GraphQLQuery, Variables extends GraphQLVariables = GraphQLVariables> {
/**
* Path parameters parsed from the WebSocket connection URL.
*/
params: PathParams;
/**
* The name of the intercepted operation.
*/
operationName: string;
/**
* Intercepted GraphQL subscription.
*/
subscription: GraphQLSubscription<Query, Variables>;
/**
* The request that established the WebSocket connection this
* subscription is multiplexed over.
*/
request: Request;
/**
* Schedule a cleanup to run once this subscription ends and the
* resolver can no longer affect it: it has been completed (by the mock,
* the client, or the original server), terminated with errors, or the
* client has disconnected.
*
* @example
* api.subscription('OnCommentAdded', ({ subscription, finalize }) => {
* const interval = setInterval(() => subscription.publish(payload), 1000)
* finalize(() => clearInterval(interval))
* })
*/
finalize: ResponseResolverFinalizeFunction;
}
type GraphQLSubscriptionResolver<Query extends GraphQLQuery = GraphQLQuery, Variables extends GraphQLVariables = GraphQLVariables> = (info: GraphQLSubscriptionResolverInfo<Query, Variables>) => void;
interface GraphQLSubscriptionHandlerOptions {
/**
* Mark this handler as used after its first match.
* Used handlers do not match subsequent subscriptions.
*/
once?: boolean;
}
/**
* A WebSocket handler intercepting GraphQL subscriptions by their
* operation name. Matching and resolution are delegated to it by the
* subscription transport (its sibling handler) so the first matching
* handler wins, respecting runtime handler overrides.
*/
export declare class GraphQLSubscriptionHandler<Query extends GraphQLQuery = GraphQLQuery, Variables extends GraphQLVariables = GraphQLVariables> extends GraphQLWebSocketHandler {
#private;
info: GraphQLHandlerInfo;
isUsed: boolean;
constructor(args: {
url: Path;
operationName: GraphQLSubscriptionName<Query, Variables>;
transport: GraphQLSubscriptionTransportHandler;
resolver: GraphQLSubscriptionResolver<Query, Variables>;
options?: GraphQLSubscriptionHandlerOptions;
});
reset(): void;
/**
* @note Individual subscription handlers stay silent. The subscription
* transport owns the GraphQL-aware logging for the entire connection
* (a logger is attached once per matching handler otherwise).
*/
log(): () => void;
protected [kConnect](connection: WebSocketHandlerConnection): boolean;
}
/**
* Representation of the intercepted GraphQL subscription.
*/
export declare class GraphQLSubscription<Query extends GraphQLQuery = GraphQLQuery, Variables extends GraphQLVariables = GraphQLVariables> {
#private;
id: string;
query: string;
variables: Variables;
extensions?: Record<string, unknown>;
constructor(args: {
message: GraphQLWebSocketSubscribeMessage;
clientId: string;
transport: GraphQLSubscriptionTransportHandler;
});
/**
* Publish an execution result to the subscribed client.
*
* @example
* subscription.publish({
* data: {
* postAdded: {
* id: 'abc-123'
* }
* }
* })
*/
publish(payload: GraphQLSubscriptionPayload<Query>): void;
/**
* Use the given `Iterable` or `AsyncIterable` as the source
* of data for this subscription. Whenever the iterable yields a
* value, it gets published to this subscription.
*
* @example
* subscription.from(async function* () {
* yield { text: 'hello world' }
* })
*/
from(source: Iterable<Query> | AsyncIterable<Query>): Promise<void>;
/**
* Terminate this subscription with the given errors.
*
* @example
* subscription.error([{ message: 'Something went wrong' }])
*/
error(errors: ReadonlyArray<Partial<GraphQLError>>): void;
/**
* Marks this subscription as complete.
*
* @example
* subscription.complete()
*/
complete(): void;
/**
* Perform this GraphQL subscription as-is.
* This establishes a connection to the actual server, replays
* the intercepted subscription, and forwards the server payloads
* to the GraphQL client. You can intercept, modify, or prevent
* any of the original server messages.
*
* @example
* const postAddedSubscription = subscription.passthrough()
* postAddedSubscription.addEventListener('next', (event) => {
* event.preventDefault()
* event.data.payload.data.postAdded.id = 'mock-id'
* subscription.publish(event.data.payload)
* })
*/
passthrough(): GraphQLPassthroughSubscription;
}
type GraphQLPassthroughSubscriptionEventMap = {
connection_ack: TypedEvent;
next: TypedEvent<GraphQLWebSocketNextMessage>;
error: TypedEvent<GraphQLWebSocketErrorMessage>;
complete: TypedEvent<GraphQLWebSocketCompleteMessage>;
};
/**
* Representation of a GraphQL subscription to the actual server.
* You interface with this object from the client's perspective.
*/
export declare class GraphQLPassthroughSubscription {
#private;
constructor(args: {
server: WebSocketServerHandle;
message: GraphQLWebSocketSubscribeMessage;
upstreamSession: Promise<void>;
onTerminate: () => void;
});
/**
* Add an event listener to the given GraphQL subscription event.
*
* @example
* const onPostAddedSubscription = subscription.passthrough()
* onPostAddedSubscription.addEventListener('next', (event) => {
* console.log(event.data)
* // { id, payload, ... }
* })
*/
addEventListener<EventType extends keyof GraphQLPassthroughSubscriptionEventMap & string>(event: EventType, listener: Emitter.Listener<Emitter<GraphQLPassthroughSubscriptionEventMap>, EventType>): void;
/**
* Unsubscribe from this passthrough GraphQL subscription.
* This stops this subscription on the original server.
*
* @note Unsubscribing from the original subscription has no
* effect on the intercepted `subscription` object.
*
* @example
* const onPostAddedSubscription = subscription.passthrough()
* onPostAddedSubscription.unsubscribe()
*/
unsubscribe(): void;
}
type GraphQLSubscriptionHandlerFactory = <Query extends GraphQLQuery = GraphQLQuery, Variables extends GraphQLVariables = GraphQLVariables>(operationName: GraphQLSubscriptionName<Query, Variables>, resolver: GraphQLSubscriptionResolver<Query, Variables>, options?: GraphQLSubscriptionHandlerOptions) => GraphQLSubscriptionHandler<Query, Variables>;
/**
* Create a `subscription()` handler factory bound to the given GraphQL
* endpoint. All subscription handlers created by the factory share a single
* subscription transport and a single WebSocket upgrade handler, both
* attached to each handler as siblings.
*
* @example
* const subscription = createGraphQLSubscriptionHandler('https://api.example.com/graphql')
* subscription('OnPostAdded', ({ subscription }) => {
* subscription.publish({ data: { postAdded: { id: 'abc-123' } } })
* })
*/
export declare function createGraphQLSubscriptionHandler(url: Path): GraphQLSubscriptionHandlerFactory;
//#endregion
//#region src/graphql/graphql.d.ts
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;
type GraphQLOperationHandler = <Query extends GraphQLQuery = GraphQLQuery, Variables extends GraphQLVariables = GraphQLVariables>(resolver: GraphQLResponseResolver<[Query] extends [never] ? GraphQLQuery : Query, Variables>, options?: RequestHandlerOptions) => GraphQLHandler;
type GraphQLResponseResolver<Query extends GraphQLQuery = GraphQLQuery, Variables extends GraphQLVariables = GraphQLVariables> = ResponseResolver<GraphQLResolverExtras<Variables>, null, GraphQLResponseBody<[Query] extends [never] ? GraphQLQuery : Query>>;
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 declare 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;
};
//#endregion
export type { GraphQLCustomPredicate, GraphQLJsonRequestBody, GraphQLLink, GraphQLOperationHandler, GraphQLOperationType, GraphQLPassthroughSubscriptionEventMap, GraphQLQuery, GraphQLRequestBody, GraphQLRequestHandler, GraphQLResponseBody, GraphQLResponseResolver, GraphQLSubscriptionHandlerFactory, GraphQLSubscriptionHandlerOptions, GraphQLSubscriptionName, GraphQLSubscriptionPayload, GraphQLSubscriptionResolver, GraphQLSubscriptionResolverInfo, GraphQLVariables, ParsedGraphQLRequest };
//# sourceMappingURL=index.d.ts.map