msw-trpc
Version:
tRPC API for Mock Service Worker (MSW).
97 lines (79 loc) • 3.22 kB
text/typescript
import { http, HttpResponse } from 'msw'
import { Link } from './links.js'
import { TRPCCombinedDataTransformer, TRPCError } from '@trpc/server'
import {
TRPC_ERROR_CODES_BY_KEY,
TRPC_ERROR_CODE_KEY,
defaultTransformer,
getHTTPStatusCodeFromError,
} from '@trpc/server/unstable-core-do-not-import'
import { TRPCMswConfig } from './types.js'
const getQueryInput = (req: Request, transformer: TRPCCombinedDataTransformer) => {
const inputString = new URL(req.url).searchParams.get('input')
if (inputString == null) return inputString
return transformer.input.deserialize(JSON.parse(inputString))
}
const getMutationInput = async (req: Request, transformer: TRPCCombinedDataTransformer) => {
if (!req.body) return undefined
const body = await req.json()
return transformer.input.deserialize(body)
}
const createTrpcHandler = (
procedureType: 'query' | 'mutation',
path: string,
handler: Function | undefined,
{
links,
transformer = defaultTransformer,
}: {
// Only support a single link for now
links: Link[]
transformer?: TRPCCombinedDataTransformer
}
) => {
const [link] = links
if (!link) {
throw new Error('No link provided')
} else if (links.length > 1) {
throw new Error('Only a single link is supported')
}
const { type: handlerType, url, methodOverride } = link({ type: procedureType, path })
if (!handler && (procedureType === 'query' || procedureType === 'mutation')) {
throw new Error('Handler is required for query and mutation procedures')
}
if (handlerType === 'http') {
if (procedureType === 'query' || procedureType === 'mutation') {
const getInput = procedureType === 'query' ? getQueryInput : getMutationInput
const httpHandler = procedureType === 'mutation' || methodOverride === 'POST' ? http.post : http.get
const urlRegex = new RegExp(`${url}/${path.replace('.', '[/.|.]')}$`)
return httpHandler(urlRegex, async (params) => {
try {
const input = await getInput(params.request, transformer)
const body = await handler!({ input }) // TS doesn't seem to understand that handler is defined here, despite the check above
return HttpResponse.json({ result: { data: transformer.output.serialize(body) } })
} catch (e) {
if (!(e instanceof Error)) {
throw e
}
if (!('code' in e)) {
throw e
}
const status = getHTTPStatusCodeFromError(e as TRPCError)
const { name: _, ...otherErrorData } = e
const jsonError = {
message: e.message,
code: TRPC_ERROR_CODES_BY_KEY[e.code as TRPC_ERROR_CODE_KEY],
data: { ...otherErrorData, code: e.code, httpStatus: status, path },
}
return HttpResponse.json({ error: transformer.output.serialize(jsonError) }, { status })
}
})
}
}
throw new Error('Unknown handler type')
}
export const trpc = {
query: (path: string, handler: Function, opts: TRPCMswConfig) => createTrpcHandler('query', path, handler, opts),
mutation: (path: string, handler: Function, opts: TRPCMswConfig) =>
createTrpcHandler('mutation', path, handler, opts),
}