From 3c441fc6be88118c849ee78c1e7d9c1b6b811a89 Mon Sep 17 00:00:00 2001 From: Claude Date: Sat, 10 Oct 2026 03:04:48 +0000 Subject: [PATCH 1/5] chore(msw): upgrade msw to v3 and keep testing against v2 - Bump the msw dev dependency to ^3.0.2. The `msw >=2.0.0` peer range already covers v3, and the package source works unchanged on both. - Rename the tests' `onUnhandledRequest` option to `onUnhandledFrame`, which v3 renamed (v3 silently ignored the old name). - Add an `msw-v2` vitest project that reruns the msw package tests with `msw` aliased to v2, so v2 support stays verified. - Note v2/v3 support and the renamed option in the MSW integration docs. Co-Authored-By: Claude Opus 5.5 Claude-Session: https://claude.ai/code/session_01NNXLdknBGr7KSffPGDLHB9 --- apps/content/docs/integrations/msw.mdx | 4 +- packages/msw/package.json | 3 +- .../src/http-procedure-utils.openapi.test.ts | 2 +- packages/msw/src/http-procedure-utils.test.ts | 2 +- packages/msw/src/http-router-utils.test.ts | 2 +- pnpm-lock.yaml | 91 +++++++++++++++++-- vitest.config.ts | 15 +++ 7 files changed, 108 insertions(+), 11 deletions(-) diff --git a/apps/content/docs/integrations/msw.mdx b/apps/content/docs/integrations/msw.mdx index ff07282535..9a2759c02d 100644 --- a/apps/content/docs/integrations/msw.mdx +++ b/apps/content/docs/integrations/msw.mdx @@ -15,6 +15,8 @@ This guide assumes you are already familiar with [MSW](https://mswjs.io/). If yo npm install @orpc/experimental-msw@beta ``` +Both MSW v2 and v3 are supported. + ## Setup Create MSW utils from a [router contract](/docs/contract/router) or an implemented [router](/docs/router) (convert [lazy routers](/docs/router#lazy-router) with [`unlazyRouter`](/docs/contract/router#router-to-contract) first). The `handler` option creates the fetch handler that serves each mock. Configure it like your production handler, so serialization, validation, and error envelopes behave exactly like your real server. @@ -91,7 +93,7 @@ export const Loading: Story = { ## Passthrough -The `.passthrough` method creates an MSW request handler that performs matching requests against the real server as-is, useful to exempt specific procedures from mocking, for example while [onUnhandledRequest](https://mswjs.io/docs/api/setup-server/listen#onunhandledrequest) treats everything else as an error: +The `.passthrough` method creates an MSW request handler that performs matching requests against the real server as-is, useful to exempt specific procedures from mocking, for example while [onUnhandledFrame](https://mswjs.io/docs/api/setup-server/listen) (`onUnhandledRequest` in MSW v2) treats everything else as an error: ```ts const handlers = [ diff --git a/packages/msw/package.json b/packages/msw/package.json index 9128d82518..51ea41d6ec 100644 --- a/packages/msw/package.json +++ b/packages/msw/package.json @@ -52,7 +52,8 @@ }, "devDependencies": { "@orpc/openapi": "workspace:*", - "msw": "^2.15.0", + "msw": "^3.0.2", + "msw-v2": "npm:msw@^2.15.0", "zod": "^4.5.4" } } diff --git a/packages/msw/src/http-procedure-utils.openapi.test.ts b/packages/msw/src/http-procedure-utils.openapi.test.ts index bba132eb7a..4b6fce7427 100644 --- a/packages/msw/src/http-procedure-utils.openapi.test.ts +++ b/packages/msw/src/http-procedure-utils.openapi.test.ts @@ -31,7 +31,7 @@ const contract = { const server = setupServer() -beforeAll(() => server.listen({ onUnhandledRequest: 'error' })) +beforeAll(() => server.listen({ onUnhandledFrame: 'error' })) afterEach(() => server.resetHandlers()) afterAll(() => server.close()) diff --git a/packages/msw/src/http-procedure-utils.test.ts b/packages/msw/src/http-procedure-utils.test.ts index 7303753854..e4934aeb91 100644 --- a/packages/msw/src/http-procedure-utils.test.ts +++ b/packages/msw/src/http-procedure-utils.test.ts @@ -29,7 +29,7 @@ const contract = { const server = setupServer() -beforeAll(() => server.listen({ onUnhandledRequest: 'error' })) +beforeAll(() => server.listen({ onUnhandledFrame: 'error' })) afterEach(() => server.resetHandlers()) afterAll(() => server.close()) diff --git a/packages/msw/src/http-router-utils.test.ts b/packages/msw/src/http-router-utils.test.ts index decc391c3a..6e6507fb45 100644 --- a/packages/msw/src/http-router-utils.test.ts +++ b/packages/msw/src/http-router-utils.test.ts @@ -43,7 +43,7 @@ describe('with an implemented router', () => { const server = setupServer() - beforeAll(() => server.listen({ onUnhandledRequest: 'error' })) + beforeAll(() => server.listen({ onUnhandledFrame: 'error' })) afterEach(() => server.resetHandlers()) afterAll(() => server.close()) diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index d9537a67ad..1eb1cc9426 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -709,8 +709,11 @@ importers: specifier: workspace:* version: link:../openapi msw: - specifier: ^2.15.0 - version: 2.15.0(@types/node@26.4.0)(typescript@6.0.3) + specifier: ^3.0.2 + version: 3.0.2(@types/node@26.4.0)(graphql@17.0.2)(supports-color@10.2.2)(typescript@6.0.3)(vite@8.3.3(@types/node@26.4.0)(esbuild@0.28.2)(jiti@2.7.0)(terser@5.51.1)(yaml@2.9.0)) + msw-v2: + specifier: npm:msw@^2.15.0 + version: msw@2.15.0(@types/node@26.4.0)(typescript@6.0.3) zod: specifier: ^4.5.4 version: 4.5.4 @@ -3691,10 +3694,17 @@ packages: '@cfworker/json-schema': optional: true + '@msw/url@0.1.2': + resolution: {integrity: sha512-DdeBLQxQ+ZrtmwEmaj7GQ7lbzPCgbbbyAqOz/I4ulo68hwBpfdNvQJ5bmihJUkAAKOeCmqCIG3OFZGosqFN4mA==} + '@mswjs/interceptors@0.41.9': resolution: {integrity: sha512-VVPPgHyQ6ShqnrmDWuxjmUIsO9gWyOZFmuOfLd9LfBGQJwZfy0gvv9pbHSJuoFNIYC7ZDX9aoFwowjcdSC4E8w==} engines: {node: '>=18'} + '@mswjs/interceptors@0.45.7': + resolution: {integrity: sha512-PPZQBiojRuy1jJQoUqbrJhoac47GC1GwRazDrYwwbE70zfqMkOY/5a+Du4hp3vlPUXLt/um4TMcmRabmooAkxw==} + engines: {node: '>=22'} + '@napi-rs/lzma-linux-x64-gnu@1.5.1': resolution: {integrity: sha512-oTXEIha4SsuXdTA4Iyskj0kpdx2yVXdhd75c2v3xGrHFfVMsbhTPZU/nMPL4sWKo4pBHm3aucLaqGlF696dTyQ==} engines: {node: ^22.20 || ^24.12 || >=25} @@ -3996,6 +4006,9 @@ packages: '@open-draft/until@2.1.0': resolution: {integrity: sha512-U69T3ItWHvLwGg5eJ0n3I62nWuE6ilHlmz7zM0npLBRvPRd7e6NYmg54vvRtP5mZG7kZqZCFVdsTWo7BPtBujg==} + '@open-draft/until@3.0.1': + resolution: {integrity: sha512-s7/9ELP4aP9YZtW7RJaa0Xf3RISaRH9+EFN18FtykJYRHGNrl8f9ymvoNXzjhrjmftFvkQudtuDU9RYhk0xs3A==} + '@openapi-contrib/openapi-schema-to-json-schema@3.2.0': resolution: {integrity: sha512-Gj6C0JwCr8arj0sYuslWXUBSP/KnUlEGnPW4qxlXvAl543oaNQgMgIgkQUA6vs5BCCvwTEiL8m/wdWzfl4UvSw==} @@ -10443,6 +10456,22 @@ packages: typescript: optional: true + msw@3.0.2: + resolution: {integrity: sha512-/74rddlgCmlHvrXUkuYKoUjDJTiP4Nrz2uyIpOTLtxJNCS+2yY+DlLfzzRUc61cvRjCo6Jvi2+vRNZyZQa1j4A==} + engines: {node: '>=22.12.0'} + hasBin: true + peerDependencies: + graphql: '>=16' + typescript: '>=5.9.x' + vite: '>=6' + peerDependenciesMeta: + graphql: + optional: true + typescript: + optional: true + vite: + optional: true + muggle-string@0.4.1: resolution: {integrity: sha512-VNTrAak/KhO2i8dqqnqnAHOa3cYBwXEZe9h+D5h/1ZqFSTEFHdM65lR7RoIqq3tBBYavsOXV84NoHXZ0AkPyqQ==} @@ -11549,8 +11578,8 @@ packages: retext-smartypants@6.2.0: resolution: {integrity: sha512-kk0jOU7+zGv//kfjXEBjdIryL1Acl4i9XNkHxtM7Tm5lFiCog576fjNC9hjoR7LTKQ0DsPWy09JummSsH1uqfQ==} - rettime@0.11.11: - resolution: {integrity: sha512-ILJRqVWBCTlg9r42fFgwVZx1gnFAcQF8mRoMkbgQfIrjEDf9nbBFDFx00oloOa+Q869FUtaYDXZvEfnecQSCoQ==} + rettime@0.11.12: + resolution: {integrity: sha512-5ExtR1ppiDBQkVoPw5NAqdbfH4Iq2VOmQKHMtUoaFxA9bo2krzFYBtyq6K40xxbYp+2w/YOr6D+1ToF6tTN53A==} reusify@1.1.0: resolution: {integrity: sha512-g6QUff04oZpHs0eG5p83rFLhHeV00ug/Yf9nZM6fLeUrPguBTkTQOdpAWWspMh55TZfVQDPaN3NQJfbVRAxdIw==} @@ -13239,6 +13268,10 @@ packages: resolution: {integrity: sha512-2rAgRKu54VsHkqI0/tYkmluGXHD4KW7yZoycuqDQ15QOTnc2VVfy0nN/1eMhnQLO00A+dwtK20xuCnc1YGeUyg==} engines: {node: ^20.19.0 || ^22.12.0 || >=23} + yargs@18.2.0: + resolution: {integrity: sha512-9OpKOLeaoNFecEp7P6iYbzze/5CqWoH7N3SMs/6y1XF4nMvFMspEGZzJ6uFi9MmQwXhyzqYoSEKO3tvA3Z/o2w==} + engines: {node: ^20.19.0 || ^22.12.0 || >=23} + yocto-queue@0.1.0: resolution: {integrity: sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==} engines: {node: '>=10'} @@ -15348,6 +15381,8 @@ snapshots: transitivePeerDependencies: - supports-color + '@msw/url@0.1.2': {} + '@mswjs/interceptors@0.41.9': dependencies: '@open-draft/deferred-promise': 2.2.0 @@ -15357,6 +15392,17 @@ snapshots: outvariant: 1.4.3 strict-event-emitter: 0.5.1 + '@mswjs/interceptors@0.45.7(supports-color@10.2.2)': + dependencies: + '@open-draft/until': 3.0.1 + '@types/debug': 4.1.13 + debug: 4.4.3(supports-color@10.2.2) + is-node-process: 1.2.0 + outvariant: 1.4.3 + rettime: 0.11.12 + transitivePeerDependencies: + - supports-color + '@napi-rs/lzma-linux-x64-gnu@1.5.1': optional: true @@ -15593,6 +15639,8 @@ snapshots: '@open-draft/until@2.1.0': {} + '@open-draft/until@3.0.1': {} + '@openapi-contrib/openapi-schema-to-json-schema@3.2.0': dependencies: fast-deep-equal: 3.1.3 @@ -23434,7 +23482,7 @@ snapshots: outvariant: 1.4.3 path-to-regexp: 6.3.0 picocolors: 1.1.1 - rettime: 0.11.11 + rettime: 0.11.12 statuses: 2.0.2 strict-event-emitter: 0.5.1 tough-cookie: 6.0.2 @@ -23446,6 +23494,28 @@ snapshots: transitivePeerDependencies: - '@types/node' + msw@3.0.2(@types/node@26.4.0)(graphql@17.0.2)(supports-color@10.2.2)(typescript@6.0.3)(vite@8.3.3(@types/node@26.4.0)(esbuild@0.28.2)(jiti@2.7.0)(terser@5.51.1)(yaml@2.9.0)): + dependencies: + '@inquirer/confirm': 6.3.0(@types/node@26.4.0) + '@msw/url': 0.1.2 + '@mswjs/interceptors': 0.45.7(supports-color@10.2.2) + cookie: 2.0.1 + headers-polyfill: 5.0.1 + is-node-process: 1.2.0 + outvariant: 1.4.3 + rettime: 0.11.12 + tough-cookie: 6.0.2 + type-fest: 5.10.0 + until-async: 3.0.2 + yargs: 18.2.0 + optionalDependencies: + graphql: 17.0.2 + typescript: 6.0.3 + vite: 8.3.3(@types/node@26.4.0)(esbuild@0.28.2)(jiti@2.7.0)(terser@5.51.1)(yaml@2.9.0) + transitivePeerDependencies: + - '@types/node' + - supports-color + muggle-string@0.4.1: {} multer@2.2.0: @@ -24649,7 +24719,7 @@ snapshots: nlcst-to-string: 4.0.0 unist-util-visit: 5.1.0 - rettime@0.11.11: {} + rettime@0.11.12: {} reusify@1.1.0: {} @@ -26648,6 +26718,15 @@ snapshots: y18n: 5.0.8 yargs-parser: 22.0.0 + yargs@18.2.0: + dependencies: + cliui: 9.0.1 + escalade: 3.2.0 + get-caller-file: 2.0.5 + string-width: 8.2.2 + y18n: 5.0.8 + yargs-parser: 22.0.0 + yocto-queue@0.1.0: {} yocto-queue@1.2.2: {} diff --git a/vitest.config.ts b/vitest.config.ts index fb0e5d228b..e53926672c 100644 --- a/vitest.config.ts +++ b/vitest.config.ts @@ -39,6 +39,21 @@ export default defineConfig(({ mode }) => ({ }, }, }, + { + // @orpc/experimental-msw supports both MSW v2 and v3, so its tests also run against v2 + resolve: { + alias: [{ find: /^msw(?=\/|$)/, replacement: 'msw-v2' }], + }, + test: { + name: 'msw-v2', + globals: true, + setupFiles: ['./vitest.javascript.ts'], + include: ['./packages/msw/**/*.test.ts'], + benchmark: { + exclude: ['**/**'], + }, + }, + }, { test: { globals: true, From 4102f57b8701fc3d1b541cb127d9e353cea2a612 Mon Sep 17 00:00:00 2001 From: Claude Date: Sat, 10 Oct 2026 06:08:59 +0000 Subject: [PATCH 2/5] docs(msw): show MSW version support in an info block Co-Authored-By: Claude Opus 5.5 Claude-Session: https://claude.ai/code/session_01NNXLdknBGr7KSffPGDLHB9 --- apps/content/docs/integrations/msw.mdx | 2 ++ 1 file changed, 2 insertions(+) diff --git a/apps/content/docs/integrations/msw.mdx b/apps/content/docs/integrations/msw.mdx index 9a2759c02d..a3c210271e 100644 --- a/apps/content/docs/integrations/msw.mdx +++ b/apps/content/docs/integrations/msw.mdx @@ -15,7 +15,9 @@ This guide assumes you are already familiar with [MSW](https://mswjs.io/). If yo npm install @orpc/experimental-msw@beta ``` +:::info Both MSW v2 and v3 are supported. +::: ## Setup From 80d45a52b0126365efcb1b29c1282da587870b27 Mon Sep 17 00:00:00 2001 From: Claude Date: Sat, 10 Oct 2026 06:10:01 +0000 Subject: [PATCH 3/5] chore(msw): shorten MSW v2 vitest project comment Co-Authored-By: Claude Opus 5.5 Claude-Session: https://claude.ai/code/session_01NNXLdknBGr7KSffPGDLHB9 --- vitest.config.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/vitest.config.ts b/vitest.config.ts index e53926672c..59eba1de39 100644 --- a/vitest.config.ts +++ b/vitest.config.ts @@ -40,7 +40,7 @@ export default defineConfig(({ mode }) => ({ }, }, { - // @orpc/experimental-msw supports both MSW v2 and v3, so its tests also run against v2 + // also test @orpc/experimental-msw against MSW v2 resolve: { alias: [{ find: /^msw(?=\/|$)/, replacement: 'msw-v2' }], }, From a2a3c91faa6862dad87c9a5b95163c670f9e0799 Mon Sep 17 00:00:00 2001 From: Claude Date: Sat, 10 Oct 2026 06:13:57 +0000 Subject: [PATCH 4/5] chore(msw): run MSW v3 tests only on Node.js 22+ MSW v3 requires Node.js 22.12+ (its interceptors call `Promise.withResolvers`), so the msw package tests fail on the Node 20 CI job. On older Node versions, exclude the package from the default (MSW v3) project; the MSW v2 project still runs its full suite there. Co-Authored-By: Claude Opus 5.5 Claude-Session: https://claude.ai/code/session_01NNXLdknBGr7KSffPGDLHB9 --- vitest.config.ts | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/vitest.config.ts b/vitest.config.ts index 59eba1de39..11cfbab157 100644 --- a/vitest.config.ts +++ b/vitest.config.ts @@ -33,7 +33,14 @@ export default defineConfig(({ mode }) => ({ globals: true, setupFiles: ['./vitest.javascript.ts'], include: ['**/*.test.ts'], - exclude: [...defaultExclude, '**/.claude/**', './packages/bun/**', './packages/cloudflare/**'], + exclude: [ + ...defaultExclude, + '**/.claude/**', + './packages/bun/**', + './packages/cloudflare/**', + // MSW v3 requires Node.js 22+, older versions only run the MSW v2 project below + ...(Number.parseInt(process.versions.node) < 22 ? ['./packages/msw/**'] : []), + ], benchmark: { exclude: ['**/**'], }, From 125071d845e40d8c17c03b338bc2f2a9c2bb7720 Mon Sep 17 00:00:00 2001 From: Claude Date: Sat, 10 Oct 2026 06:22:46 +0000 Subject: [PATCH 5/5] chore(msw): use a plain string alias for the MSW v2 project Co-Authored-By: Claude Opus 5.5 Claude-Session: https://claude.ai/code/session_01NNXLdknBGr7KSffPGDLHB9 --- vitest.config.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/vitest.config.ts b/vitest.config.ts index 11cfbab157..c62e5a80d7 100644 --- a/vitest.config.ts +++ b/vitest.config.ts @@ -49,7 +49,7 @@ export default defineConfig(({ mode }) => ({ { // also test @orpc/experimental-msw against MSW v2 resolve: { - alias: [{ find: /^msw(?=\/|$)/, replacement: 'msw-v2' }], + alias: { msw: 'msw-v2' }, }, test: { name: 'msw-v2',