Skip to content

Commit

Permalink
Merge branch 'main' into move-graphql-cookies-to-parse-phase
Browse files Browse the repository at this point in the history
  • Loading branch information
kettanaito authored Jan 12, 2024
2 parents 4527016 + 8624f31 commit 63a857c
Show file tree
Hide file tree
Showing 8 changed files with 168 additions and 39 deletions.
40 changes: 24 additions & 16 deletions src/core/graphql.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import {
GraphQLHandlerNameSelector,
GraphQLResolverExtras,
GraphQLResponseBody,
GraphQLQuery,
} from './handlers/GraphQLHandler'
import type { Path } from './utils/matching/matchRequestUrl'

Expand All @@ -22,25 +23,32 @@ export interface TypedDocumentNode<
__variablesType?: Variables
}

export type GraphQLRequestHandler = <
Query extends GraphQLQuery = GraphQLQuery,
Variables extends GraphQLVariables = GraphQLVariables,
>(
operationName:
| GraphQLHandlerNameSelector
| DocumentNode
| TypedDocumentNode<Query, Variables>,
resolver: GraphQLResponseResolver<Query, Variables>,
options?: RequestHandlerOptions,
) => GraphQLHandler

export type GraphQLResponseResolver<
Query extends GraphQLQuery = GraphQLQuery,
Variables extends GraphQLVariables = GraphQLVariables,
> = ResponseResolver<
GraphQLResolverExtras<Variables>,
null,
GraphQLResponseBody<Query>
>

function createScopedGraphQLHandler(
operationType: ExpectedOperationTypeNode,
url: Path,
) {
return <
Query extends Record<string, any>,
Variables extends GraphQLVariables = GraphQLVariables,
>(
operationName:
| GraphQLHandlerNameSelector
| DocumentNode
| TypedDocumentNode<Query, Variables>,
resolver: ResponseResolver<
GraphQLResolverExtras<Variables>,
null,
GraphQLResponseBody<Query>
>,
options: RequestHandlerOptions = {},
) => {
): GraphQLRequestHandler {
return (operationName, resolver, options = {}) => {
return new GraphQLHandler(
operationType,
operationName,
Expand Down
1 change: 1 addition & 0 deletions src/core/handlers/GraphQLHandler.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ import { getAllRequestCookies } from '../utils/request/getRequestCookies'
export type ExpectedOperationTypeNode = OperationTypeNode | 'all'
export type GraphQLHandlerNameSelector = DocumentNode | RegExp | string

export type GraphQLQuery = Record<string, any>
export type GraphQLVariables = Record<string, any>

export interface GraphQLHandlerInfo extends RequestHandlerDefaultInfo {
Expand Down
23 changes: 14 additions & 9 deletions src/core/handlers/RequestHandler.ts
Original file line number Diff line number Diff line change
Expand Up @@ -36,23 +36,28 @@ export interface RequestHandlerInternalInfo {
}

export type ResponseResolverReturnType<
BodyType extends DefaultBodyType = undefined,
ResponseBodyType extends DefaultBodyType = undefined,
> =
| ([BodyType] extends [undefined] ? Response : StrictResponse<BodyType>)
| ([ResponseBodyType] extends [undefined]
? Response
: StrictResponse<ResponseBodyType>)
| undefined
| void

export type MaybeAsyncResponseResolverReturnType<
BodyType extends DefaultBodyType,
> = MaybePromise<ResponseResolverReturnType<BodyType>>
ResponseBodyType extends DefaultBodyType,
> = MaybePromise<ResponseResolverReturnType<ResponseBodyType>>

export type AsyncResponseResolverReturnType<BodyType extends DefaultBodyType> =
| MaybeAsyncResponseResolverReturnType<BodyType>
export type AsyncResponseResolverReturnType<
ResponseBodyType extends DefaultBodyType,
> = MaybePromise<
| ResponseResolverReturnType<ResponseBodyType>
| Generator<
MaybeAsyncResponseResolverReturnType<BodyType>,
MaybeAsyncResponseResolverReturnType<BodyType>,
MaybeAsyncResponseResolverReturnType<BodyType>
MaybeAsyncResponseResolverReturnType<ResponseBodyType>,
MaybeAsyncResponseResolverReturnType<ResponseBodyType>,
MaybeAsyncResponseResolverReturnType<ResponseBodyType>
>
>

export type ResponseResolverInfo<
ResolverExtraInfo extends Record<string, unknown>,
Expand Down
41 changes: 27 additions & 14 deletions src/core/http.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,22 +10,35 @@ import {
} from './handlers/HttpHandler'
import type { Path, PathParams } from './utils/matching/matchRequestUrl'

export type HttpRequestHandler = <
Params extends PathParams<keyof Params> = PathParams,
RequestBodyType extends DefaultBodyType = DefaultBodyType,
// Response body type MUST be undefined by default.
// This is how we can distinguish between a handler that
// returns plain "Response" and the one returning "HttpResponse"
// to enforce a stricter response body type.
ResponseBodyType extends DefaultBodyType = undefined,
RequestPath extends Path = Path,
>(
path: RequestPath,
resolver: HttpResponseResolver<Params, RequestBodyType, ResponseBodyType>,
options?: RequestHandlerOptions,
) => HttpHandler

export type HttpResponseResolver<
Params extends PathParams<keyof Params> = PathParams,
RequestBodyType extends DefaultBodyType = DefaultBodyType,
ResponseBodyType extends DefaultBodyType = DefaultBodyType,
> = ResponseResolver<
HttpRequestResolverExtras<Params>,
RequestBodyType,
ResponseBodyType
>

function createHttpHandler<Method extends HttpMethods | RegExp>(
method: Method,
) {
return <
Params extends PathParams<keyof Params> = PathParams,
RequestBodyType extends DefaultBodyType = DefaultBodyType,
ResponseBodyType extends DefaultBodyType = undefined,
>(
path: Path,
resolver: ResponseResolver<
HttpRequestResolverExtras<Params>,
RequestBodyType,
ResponseBodyType
>,
options: RequestHandlerOptions = {},
) => {
): HttpRequestHandler {
return (path, resolver, options = {}) => {
return new HttpHandler(method, path, resolver, options)
}
}
Expand Down
3 changes: 3 additions & 0 deletions src/core/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -34,12 +34,15 @@ export type {
RequestQuery,
HttpRequestParsedResult,
} from './handlers/HttpHandler'
export type { HttpResponseResolver } from './http'

export type {
GraphQLQuery,
GraphQLVariables,
GraphQLRequestBody,
GraphQLJsonRequestBody,
} from './handlers/GraphQLHandler'
export type { GraphQLResponseResolver } from './graphql'

export type { Path, PathParams, Match } from './utils/matching/matchRequestUrl'
export type { ParsedGraphQLRequest } from './utils/internal/parseGraphQLRequest'
Expand Down
14 changes: 14 additions & 0 deletions test/typings/custom-handler.test-d.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
import { http, HttpHandler, GraphQLHandler, graphql } from 'msw'
import { setupWorker } from 'msw/browser'
import { setupServer } from 'msw/node'

function generateHttpHandler(): HttpHandler {
return http.get('/user', () => {})
}

function generateGraphQLHandler(): GraphQLHandler {
return graphql.query('GetUser', () => {})
}

setupWorker(generateHttpHandler(), generateGraphQLHandler())
setupServer(generateHttpHandler(), generateGraphQLHandler())
85 changes: 85 additions & 0 deletions test/typings/custom-resolver.test-d.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@
import {
http,
HttpResponseResolver,
delay,
PathParams,
DefaultBodyType,
HttpResponse,
graphql,
GraphQLQuery,
GraphQLVariables,
GraphQLResponseResolver,
} from 'msw'

/**
* A higher-order resolver that injects a fixed
* delay before calling the provided resolver.
*/
function withDelay<
// Recreate the generic signature of the default resolver
// so the arguments passed to "http.get" propagate here.
Params extends PathParams,
RequestBodyType extends DefaultBodyType,
ResponseBodyType extends DefaultBodyType,
>(
delayMs: number,
resolver: HttpResponseResolver<Params, RequestBodyType, ResponseBodyType>,
): HttpResponseResolver<Params, RequestBodyType, ResponseBodyType> {
return async (...args) => {
await delay(delayMs)
return resolver(...args)
}
}

http.get<{ id: string }, never, 'hello'>(
'/user/:id',
// @ts-expect-error Response body doesn't match the response type.
withDelay(250, ({ params }) => {
params.id.toUpperCase()
// @ts-expect-error Unknown path parameter.
params.nonexistent

return HttpResponse.text('non-matching')
}),
)

function identityGraphQLResolver<
Query extends GraphQLQuery,
Variables extends GraphQLVariables,
>(
resolver: GraphQLResponseResolver<Query, Variables>,
): GraphQLResponseResolver<Query, Variables> {
return async (...args) => {
return resolver(...args)
}
}

graphql.query<{ number: number }, { id: string }>(
'GetUser',
identityGraphQLResolver(({ variables }) => {
variables.id.toUpperCase()

return HttpResponse.json({
data: {
number: 1,
},
})
}),
)

graphql.query<{ number: number }, { id: string }>(
'GetUser',
// @ts-expect-error Incompatible response query type.
identityGraphQLResolver(({ variables }) => {
// @ts-expect-error Unknown variable.
variables.nonexistent

return HttpResponse.json({
data: {
user: {
id: variables.id,
},
},
})
}),
)
File renamed without changes.

0 comments on commit 63a857c

Please sign in to comment.