Skip to content

Commit b119b40

Browse files
committed
feat: add msw plugin
1 parent e88abc6 commit b119b40

51 files changed

Lines changed: 8014 additions & 415 deletions

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

dev/package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@
2121
"@tanstack/svelte-query": "5.90.2",
2222
"@tanstack/vue-query": "5.92.9",
2323
"arktype": "2.2.0",
24+
"msw": "2.10.2",
2425
"nuxt": "3.21.0",
2526
"swr": "2.4.0",
2627
"tsx": "4.21.0",

dev/typescript/presets.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@ export const presets = {
2828
},
2929
},
3030
],
31+
msw: () => ['@hey-api/typescript', 'msw'],
3132
sdk: () => [
3233
/** SDK with types */
3334
'@hey-api/typescript',

docs/openapi-ts/plugins/msw.md

Lines changed: 127 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -4,15 +4,138 @@ description: MSW plugin for Hey API. Compatible with all our features.
44
---
55

66
<script setup lang="ts">
7-
import FeatureStatus from '@components/FeatureStatus.vue';
87
</script>
98

10-
# MSW <span data-soon>soon</span>
11-
12-
<FeatureStatus issueNumber=1486 name="MSW" />
9+
# MSW
1310

1411
### About
1512

1613
[MSW](https://mswjs.io) is an API mocking library that allows you to write client-agnostic mocks and reuse them across any frameworks, tools, and environments.
1714

15+
The MSW plugin for Hey API generates type-safe mock handler factories from your OpenAPI spec, removing the tedious work of defining mock endpoints and ensuring your mocks stay in sync with your API.
16+
17+
## Features
18+
19+
- type-safe mock handlers generated from your OpenAPI spec
20+
- seamless integration with `@hey-api/openapi-ts` ecosystem
21+
- support for static response values or custom MSW resolver functions
22+
- typed path parameters and request bodies
23+
- automatic base URL inference from OpenAPI `servers` field
24+
- minimal learning curve thanks to extending the underlying technology
25+
26+
## Installation
27+
28+
::: warning
29+
MSW plugin requires `msw@^2` as a peer dependency. Make sure to install it in your project.
30+
:::
31+
32+
In your [configuration](/openapi-ts/get-started), add `msw` to your plugins and you'll be ready to generate MSW artifacts. :tada:
33+
34+
```js
35+
export default {
36+
input: 'hey-api/backend', // sign up at app.heyapi.dev
37+
output: 'src/client',
38+
plugins: [
39+
// ...other plugins
40+
'msw', // [!code ++]
41+
],
42+
};
43+
```
44+
45+
## Output
46+
47+
The MSW plugin will generate a `msw.gen.ts` file containing the following artifacts.
48+
49+
### Handler Factory
50+
51+
The `createMswHandlerFactory` function is the main export. It returns an object with an `of` property containing a handler creator for each operation in your spec.
52+
53+
```ts
54+
import { createMswHandlerFactory } from './client/msw.gen';
55+
56+
const createMock = createMswHandlerFactory({
57+
baseUrl: 'http://localhost:8000', // optional, inferred from spec servers
58+
});
59+
```
60+
61+
If your OpenAPI spec defines a `servers` field, the base URL will be inferred automatically. You can override it by passing `baseUrl` in the configuration.
62+
63+
### Handler Creators
64+
65+
Each operation becomes a handler creator on the `of` object. Handler creators accept either a static response value or a custom MSW `HttpResponseResolver` function.
66+
67+
## Usage
68+
69+
### Static Response
70+
71+
The simplest way to mock an endpoint is to provide a static response value. The value is type-checked against the operation's response type.
72+
73+
```ts
74+
import { setupServer } from 'msw/node';
75+
import { createMswHandlerFactory } from './client/msw.gen';
76+
77+
const createMock = createMswHandlerFactory();
78+
79+
const mockServer = setupServer(
80+
// provide a static response value
81+
createMock.of.getPetById({ id: 1, name: 'Fido' }),
82+
83+
// type error if response type is incorrect
84+
// @ts-expect-error
85+
createMock.of.getPetById('wrong type'),
86+
);
87+
```
88+
89+
### Custom Resolver
90+
91+
For more control, pass an MSW `HttpResponseResolver` function. The resolver receives typed path parameters and request body when available.
92+
93+
```ts
94+
import { delay, HttpResponse } from 'msw';
95+
import { setupServer } from 'msw/node';
96+
import { createMswHandlerFactory } from './client/msw.gen';
97+
98+
const createMock = createMswHandlerFactory();
99+
100+
const mockServer = setupServer(
101+
// custom resolver with typed params and body
102+
createMock.of.updatePet(async ({ request, params }) => {
103+
const body = await request.json();
104+
return HttpResponse.json({ id: Number(params.petId), ...body }, { status: 200 });
105+
}),
106+
107+
// async resolver with delay
108+
createMock.of.getPetById(async () => {
109+
await delay(100);
110+
return HttpResponse.json({ id: 1, name: 'Fido' });
111+
}),
112+
);
113+
```
114+
115+
::: tip
116+
Path parameters are typed as `string | ReadonlyArray<string>` because MSW normalizes all path parameters to strings. Use `Number()` or similar conversions if you need numeric values.
117+
:::
118+
119+
### Operations Without Responses
120+
121+
For operations that don't define a response type, the handler creator can be invoked without argument or a custom resolver function.
122+
123+
```ts
124+
const mockServer = setupServer(
125+
createMock.of.deletePet(),
126+
127+
createMock.of.deletePet(() => new HttpResponse(null, { status: 204 })),
128+
);
129+
```
130+
131+
## Known Limitations
132+
133+
- Query parameters are not typed in the resolver. MSW doesn't provide typed query params natively — use `new URL(request.url).searchParams` instead.
134+
- The response type generic is omitted from `HttpResponseResolver` to avoid MSW's `DefaultBodyType` constraint issues with union and void response types.
135+
136+
## API
137+
138+
You can view the complete list of options in the [UserConfig](https://github.com/hey-api/openapi-ts/blob/main/packages/openapi-ts/src/plugins/msw/types.ts) interface.
139+
140+
<!--@include: ../../partials/examples.md-->
18141
<!--@include: ../../partials/sponsors.md-->

examples/openapi-ts-fetch/openapi-ts.config.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,5 +18,6 @@ export default defineConfig({
1818
enums: 'javascript',
1919
name: '@hey-api/typescript',
2020
},
21+
'msw',
2122
],
2223
});

examples/openapi-ts-fetch/package.json

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99
"lint": "eslint . --report-unused-disable-directives --max-warnings 0",
1010
"openapi-ts": "openapi-ts",
1111
"preview": "vite preview",
12+
"test": "vitest run",
1213
"typecheck": "tsgo --noEmit"
1314
},
1415
"dependencies": {
@@ -29,10 +30,12 @@
2930
"eslint": "9.17.0",
3031
"eslint-plugin-react-hooks": "5.2.0",
3132
"eslint-plugin-react-refresh": "0.4.7",
33+
"msw": "2.10.2",
3234
"oxfmt": "0.27.0",
3335
"postcss": "8.4.41",
3436
"tailwindcss": "3.4.9",
3537
"typescript": "5.9.3",
36-
"vite": "7.3.1"
38+
"vite": "7.3.1",
39+
"vitest": "4.0.18"
3740
}
3841
}

0 commit comments

Comments
 (0)