import { createOpenApiHttp, type OpenApiHttpHandlers } from 'openapi-msw'; import { type MockOriginMatching, resolveHandlerBase } from './base-url.js'; /** * The msw handler types this package's surface is expressed in. They are * re-exported so a consumer writing a helper around a resolver types against * the copy of msw this package resolves, rather than importing msw types in one * file and ours in another. */ export type { HttpHandler as MockHandler, RequestHandler as MockRequestHandler, } from 'msw'; export type { MockOriginMatching } from './base-url.js'; export type { ResponseResolver as MockResponseResolver, ResponseResolverInfo as MockResponseResolverInfo, } from 'openapi-msw'; export type { PathsFor as MockPathsFor, RequestBodyFor as MockRequestBodyFor, ResponseBodyFor as MockResponseBodyFor, } from 'openapi-msw'; export type MockApiOptions = { /** * Prepended to every handler path, for an API mounted under a prefix. Given * `'/api'`, a handler declared on `/things/{id}` matches `/api/things/:id`. * Pass the same value the app passes to `createApiClient`. * * Either an origin-relative path with a leading slash (`'/api'`) or an absolute * URL (`'https://api.test/v1'`); a trailing slash is tolerated. It is * concatenated with the OpenAPI path, so anything else — a query string, a * fragment, a missing leading slash — yields a pattern that matches nothing, * which surfaces as an unhandled request rather than an error. */ baseUrl?: string; /** * How an origin-relative `baseUrl` is matched. * * - `'any'` (default) prefixes handler paths with msw's `*` origin wildcard, so * one handler array matches both the relative request a browser app makes * and the absolute URL a node test has to issue. That is what makes the same * mocks reusable across dev, vitest, and Playwright. * - `'exact'` leaves paths relative: same-origin matching only, and a node * test then needs an absolute `baseUrl` of its own. * * An absolute `baseUrl` already pins the origin, so this does not apply to * one. */ origin?: MockOriginMatching; }; /** * A typed handler factory per HTTP method, plus `untyped` — msw's own `http` * object, for the rare route that is not in the OpenAPI document at all (an * auth callback on another host, say). */ export type MockApi = OpenApiHttpHandlers; /** * Creates typed msw request-handler factories bound to a generated OpenAPI * `paths` type. * * The generated `TPaths` is the only endpoint definition: which methods exist on * which paths, what path and query params they take, and what body each status * may return are all read off it. A handler for a path the API does not have, or * one that answers with a body the operation does not declare, fails to compile * — which is the whole point of the layer, since a fixture that silently drifts * from the contract makes every test that depends on it a false pass. * * `TPaths` must be passed explicitly; there is no value argument to infer it * from — which is why the constraint is `object` rather than the `{}` that * openapi-msw itself accepts. Every primitive but `null` and `undefined` * satisfies `{}`, so a mistyped type argument compiled into a factory offering * no paths at all. * * ```ts * const mock = createMockApi({ baseUrl: '/api' }); * * const handlers = [ * mock.get('/things/{id}', ({ params, response }) => * response(200).json({ id: params.id, name: 'Thing' }), * ), * mock.get('/things', ({ response }) => response(500).json({ message: 'nope' })), * ]; * ``` */ export function createMockApi( options: MockApiOptions = {}, ): MockApi { return createOpenApiHttp({ baseUrl: resolveHandlerBase(options.baseUrl, options.origin ?? 'any'), }); }