@depup/msw 3.0.1-depup.0 → 3.0.3-depup.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (39) hide show
  1. package/README.md +5 -4
  2. package/changes.json +7 -3
  3. package/lib/_chunks/cookie-store.js +3 -2
  4. package/lib/_chunks/cookie-store.js.map +1 -1
  5. package/lib/_chunks/interceptor-source.d.ts +5 -1
  6. package/lib/_chunks/interceptor-source.js +18 -5
  7. package/lib/_chunks/interceptor-source.js.map +1 -1
  8. package/lib/_chunks/request-handler.d.ts +3 -2
  9. package/lib/browser/index.d.ts +4 -9
  10. package/lib/browser/index.js +21 -14
  11. package/lib/browser/index.js.map +1 -1
  12. package/lib/graphql/index.d.ts +19 -4
  13. package/lib/graphql/index.js +24 -12
  14. package/lib/graphql/index.js.map +1 -1
  15. package/lib/iife/index.js +66 -33
  16. package/lib/iife/index.js.map +1 -1
  17. package/lib/mockServiceWorker.js +1 -1
  18. package/lib/vite/index.js +10 -7
  19. package/lib/vite/index.js.map +1 -1
  20. package/lib/vite/runtime.d.ts +2 -3
  21. package/lib/vite/runtime.js +70 -68
  22. package/lib/vite/runtime.js.map +1 -1
  23. package/package.json +12 -9
  24. package/src/browser/default-network-options.ts +1 -1
  25. package/src/browser/setup-worker.ts +1 -4
  26. package/src/browser/sources/fallback-http-source.ts +11 -8
  27. package/src/browser/sources/service-worker-source.ts +12 -5
  28. package/src/browser/utils/should-invalidate-worker.test.ts +0 -16
  29. package/src/core/experimental/define-network.ts +1 -1
  30. package/src/core/experimental/handlers-controller.test.ts +61 -0
  31. package/src/core/experimental/handlers-controller.ts +39 -6
  32. package/src/core/experimental/sources/network-source.ts +8 -1
  33. package/src/core/handlers/handler.ts +3 -2
  34. package/src/graphql/graphql-subscription.ts +30 -12
  35. package/src/vite/plugin.test.ts +1 -1
  36. package/src/vite/plugin.ts +13 -10
  37. package/src/vite/runtime.ts +85 -85
  38. package/src/vite/tsconfig.build.json +1 -1
  39. package/src/vite/options.d.ts +0 -5
@@ -7,7 +7,7 @@
7
7
  * - Please do NOT modify this file.
8
8
  */
9
9
 
10
- const PACKAGE_VERSION = '3.0.1'
10
+ const PACKAGE_VERSION = '3.0.3'
11
11
  const INTEGRITY_CHECKSUM = '5cd5cf8b54c3a90f82960cedcd637772'
12
12
  const IS_MOCKED_RESPONSE = Symbol('isMockedResponse')
13
13
 
package/lib/vite/index.js CHANGED
@@ -4,7 +4,6 @@ import { fileURLToPath } from "node:url";
4
4
  const WORKER_FILENAME = "mockServiceWorker.js";
5
5
  const WORKER_SCRIPT_PATH = new URL("../mockServiceWorker.js", import.meta.url);
6
6
  const VIRTUAL_MODULE_ID = "virtual:msw";
7
- const VIRTUAL_OPTIONS_ID = "virtual:msw/options";
8
7
  const RUNTIME_PATH = fileURLToPath(new URL("./runtime.js", import.meta.url));
9
8
  /**
10
9
  * A Vite plugin for Mock Service Worker.
@@ -41,18 +40,22 @@ function msw(options = {}) {
41
40
  const environmentsUsingNetwork = /* @__PURE__ */ new Set();
42
41
  return {
43
42
  name: "msw",
44
- async resolveId(id) {
43
+ resolveId(id) {
45
44
  if (mode === "worker-only") return;
46
- if (id === VIRTUAL_OPTIONS_ID) return `\0${VIRTUAL_OPTIONS_ID}`;
47
- if (id === VIRTUAL_MODULE_ID) return this.resolve(RUNTIME_PATH);
45
+ if (id === VIRTUAL_MODULE_ID) return `\0${VIRTUAL_MODULE_ID}`;
48
46
  },
49
47
  load(id) {
50
- if (mode === "worker-only" || id !== `\0${VIRTUAL_OPTIONS_ID}`) return;
51
- if (this.environment.config.consumer === "server") return `export { defaultNetworkOptions } from 'msw/node'`;
48
+ if (mode === "worker-only" || id !== `\0${VIRTUAL_MODULE_ID}`) return;
49
+ if (this.environment.config.consumer === "server") return `
50
+ import { defaultNetworkOptions } from 'msw/node'
51
+ import { createNetwork } from ${JSON.stringify(RUNTIME_PATH)}
52
+ export const network = await createNetwork(defaultNetworkOptions)
53
+ `;
52
54
  environmentsUsingNetwork.add(this.environment.name);
53
55
  return `
54
56
  import { createDefaultNetworkOptions } from 'msw/browser'
55
- export const defaultNetworkOptions = createDefaultNetworkOptions(${JSON.stringify(workerUrl)})
57
+ import { createNetwork } from ${JSON.stringify(RUNTIME_PATH)}
58
+ export const network = await createNetwork(createDefaultNetworkOptions(${JSON.stringify(workerUrl)}))
56
59
  `;
57
60
  },
58
61
  configResolved(config) {
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","names":[],"sources":["../../src/vite/plugin.ts"],"sourcesContent":["import * as fs from 'node:fs'\nimport { fileURLToPath } from 'node:url'\nimport type { Plugin } from 'vite'\n\nconst WORKER_FILENAME = 'mockServiceWorker.js'\nconst WORKER_SCRIPT_PATH = new URL('../mockServiceWorker.js', import.meta.url)\nconst VIRTUAL_MODULE_ID = 'virtual:msw'\nconst VIRTUAL_OPTIONS_ID = 'virtual:msw/options'\nconst RUNTIME_PATH = fileURLToPath(new URL('./runtime.js', import.meta.url))\n\nexport interface MswPluginOptions {\n /**\n * Use automatic integration or only provide the worker script for manual setup.\n * In worker-only mode, virtual modules are disabled.\n * @default \"auto\"\n */\n mode?: 'auto' | 'worker-only'\n}\n\n/**\n * A Vite plugin for Mock Service Worker.\n *\n * @example\n * // vite.config.ts\n * import { msw } from 'msw/vite'\n *\n * export default defineConfig({\n * plugins: [msw()]\n * })\n *\n * @example\n * // src/{client,server}.ts\n * if (import.meta.env.DEV) {\n * const { network } = await import('virtual:msw')\n * const { handlers } = await import('./mocks/handlers')\n *\n * network.configure({ handlers })\n * await network.enable()\n * }\n *\n * @remarks\n * The worker script is served during development and emitted next to any\n * client bundle that imports `virtual:msw`. Guard mocking setup with\n * `import.meta.env.DEV` to exclude both from production builds.\n *\n * For application TypeScript projects that do not include the Vite config,\n * add `/// <reference types=\"msw/vite/client\" />` to an included declaration file.\n */\nexport function msw(options: MswPluginOptions = {}): Plugin {\n const mode = options.mode ?? 'auto'\n let workerUrl = `/${WORKER_FILENAME}`\n const environmentsUsingNetwork = new Set<string>()\n\n return {\n name: 'msw',\n async resolveId(id) {\n if (mode === 'worker-only') {\n return\n }\n\n if (id === VIRTUAL_OPTIONS_ID) {\n return `\\0${VIRTUAL_OPTIONS_ID}`\n }\n\n if (id === VIRTUAL_MODULE_ID) {\n return this.resolve(RUNTIME_PATH)\n }\n },\n load(id) {\n if (mode === 'worker-only' || id !== `\\0${VIRTUAL_OPTIONS_ID}`) {\n return\n }\n\n if (this.environment.config.consumer === 'server') {\n return `export { defaultNetworkOptions } from 'msw/node'`\n }\n\n environmentsUsingNetwork.add(this.environment.name)\n\n return `\nimport { createDefaultNetworkOptions } from 'msw/browser'\nexport const defaultNetworkOptions = createDefaultNetworkOptions(${JSON.stringify(workerUrl)})\n`\n },\n configResolved(config) {\n // Keep relative build bases relative and service workers on the app's origin.\n const base =\n config.base === './'\n ? config.base\n : new URL(config.base, 'http://localhost').pathname\n workerUrl = `${base}${WORKER_FILENAME}`\n },\n generateBundle() {\n if (this.environment.config.consumer !== 'client') {\n return\n }\n\n // In \"auto\" mode, the worker is only useful to bundles that contain the\n // network. A user guard (e.g. `import.meta.env.DEV`) that drops the\n // `virtual:msw` import drops the worker too.\n if (\n mode === 'auto' &&\n !environmentsUsingNetwork.has(this.environment.name)\n ) {\n return\n }\n\n this.emitFile({\n type: 'asset',\n fileName: WORKER_FILENAME,\n source: fs.readFileSync(WORKER_SCRIPT_PATH, 'utf8'),\n })\n },\n configureServer(server) {\n const workerScript = fs.readFileSync(WORKER_SCRIPT_PATH, 'utf8')\n server.middlewares.use((request, response, next) => {\n const requestUrl = new URL(request.url ?? '/', 'http://localhost')\n\n if (requestUrl.pathname !== workerUrl) {\n next()\n return\n }\n\n response.writeHead(200, {\n 'cache-control': 'no-cache',\n 'content-type': 'application/javascript; charset=utf-8',\n 'service-worker-allowed': '/',\n })\n response.end(workerScript)\n })\n },\n }\n}\n"],"mappings":";;;AAIA,MAAM,kBAAkB;AACxB,MAAM,qBAAqB,IAAI,IAAI,2BAA2B,YAAY,GAAG;AAC7E,MAAM,oBAAoB;AAC1B,MAAM,qBAAqB;AAC3B,MAAM,eAAe,cAAc,IAAI,IAAI,gBAAgB,YAAY,GAAG,CAAC;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAwC3E,SAAgB,IAAI,UAA4B,CAAC,GAAW;CAC1D,MAAM,OAAO,QAAQ,QAAQ;CAC7B,IAAI,YAAY,IAAI;CACpB,MAAM,2CAA2B,IAAI,IAAY;CAEjD,OAAO;EACL,MAAM;EACN,MAAM,UAAU,IAAI;GAClB,IAAI,SAAS,eACX;GAGF,IAAI,OAAO,oBACT,OAAO,KAAK;GAGd,IAAI,OAAO,mBACT,OAAO,KAAK,QAAQ,YAAY;EAEpC;EACA,KAAK,IAAI;GACP,IAAI,SAAS,iBAAiB,OAAO,KAAK,sBACxC;GAGF,IAAI,KAAK,YAAY,OAAO,aAAa,UACvC,OAAO;GAGT,yBAAyB,IAAI,KAAK,YAAY,IAAI;GAElD,OAAO;;mEAEsD,KAAK,UAAU,SAAS,EAAE;;EAEzF;EACA,eAAe,QAAQ;GAMrB,YAAY,GAHV,OAAO,SAAS,OACZ,OAAO,OACP,IAAI,IAAI,OAAO,MAAM,kBAAkB,CAAC,CAAC,WACzB;EACxB;EACA,iBAAiB;GACf,IAAI,KAAK,YAAY,OAAO,aAAa,UACvC;GAMF,IACE,SAAS,UACT,CAAC,yBAAyB,IAAI,KAAK,YAAY,IAAI,GAEnD;GAGF,KAAK,SAAS;IACZ,MAAM;IACN,UAAU;IACV,QAAQ,GAAG,aAAa,oBAAoB,MAAM;GACpD,CAAC;EACH;EACA,gBAAgB,QAAQ;GACtB,MAAM,eAAe,GAAG,aAAa,oBAAoB,MAAM;GAC/D,OAAO,YAAY,KAAK,SAAS,UAAU,SAAS;IAGlD,IAAI,IAFmB,IAAI,QAAQ,OAAO,KAAK,kBAElC,CAAC,CAAC,aAAa,WAAW;KACrC,KAAK;KACL;IACF;IAEA,SAAS,UAAU,KAAK;KACtB,iBAAiB;KACjB,gBAAgB;KAChB,0BAA0B;IAC5B,CAAC;IACD,SAAS,IAAI,YAAY;GAC3B,CAAC;EACH;CACF;AACF"}
1
+ {"version":3,"file":"index.js","names":[],"sources":["../../src/vite/plugin.ts"],"sourcesContent":["import * as fs from 'node:fs'\nimport { fileURLToPath } from 'node:url'\nimport type { Plugin } from 'vite'\n\nconst WORKER_FILENAME = 'mockServiceWorker.js'\nconst WORKER_SCRIPT_PATH = new URL('../mockServiceWorker.js', import.meta.url)\nconst VIRTUAL_MODULE_ID = 'virtual:msw'\nconst RUNTIME_PATH = fileURLToPath(new URL('./runtime.js', import.meta.url))\n\nexport interface MswPluginOptions {\n /**\n * Use automatic integration or only provide the worker script for manual setup.\n * In worker-only mode, virtual modules are disabled.\n * @default \"auto\"\n */\n mode?: 'auto' | 'worker-only'\n}\n\n/**\n * A Vite plugin for Mock Service Worker.\n *\n * @example\n * // vite.config.ts\n * import { msw } from 'msw/vite'\n *\n * export default defineConfig({\n * plugins: [msw()]\n * })\n *\n * @example\n * // src/{client,server}.ts\n * if (import.meta.env.DEV) {\n * const { network } = await import('virtual:msw')\n * const { handlers } = await import('./mocks/handlers')\n *\n * network.configure({ handlers })\n * await network.enable()\n * }\n *\n * @remarks\n * The worker script is served during development and emitted next to any\n * client bundle that imports `virtual:msw`. Guard mocking setup with\n * `import.meta.env.DEV` to exclude both from production builds.\n *\n * For application TypeScript projects that do not include the Vite config,\n * add `/// <reference types=\"msw/vite/client\" />` to an included declaration file.\n */\nexport function msw(options: MswPluginOptions = {}): Plugin {\n const mode = options.mode ?? 'auto'\n let workerUrl = `/${WORKER_FILENAME}`\n const environmentsUsingNetwork = new Set<string>()\n\n return {\n name: 'msw',\n resolveId(id) {\n if (mode === 'worker-only') {\n return\n }\n\n if (id === VIRTUAL_MODULE_ID) {\n return `\\0${VIRTUAL_MODULE_ID}`\n }\n },\n load(id) {\n if (mode === 'worker-only' || id !== `\\0${VIRTUAL_MODULE_ID}`) {\n return\n }\n\n // Tools like Vitest import the runtime natively when it's installed\n // in \"node_modules\". Provide it with the environment's options from here\n // so it never has to import a virtual module that only Vite can resolve.\n if (this.environment.config.consumer === 'server') {\n return `\nimport { defaultNetworkOptions } from 'msw/node'\nimport { createNetwork } from ${JSON.stringify(RUNTIME_PATH)}\nexport const network = await createNetwork(defaultNetworkOptions)\n`\n }\n\n environmentsUsingNetwork.add(this.environment.name)\n\n return `\nimport { createDefaultNetworkOptions } from 'msw/browser'\nimport { createNetwork } from ${JSON.stringify(RUNTIME_PATH)}\nexport const network = await createNetwork(createDefaultNetworkOptions(${JSON.stringify(workerUrl)}))\n`\n },\n configResolved(config) {\n // Keep relative build bases relative and service workers on the app's origin.\n const base =\n config.base === './'\n ? config.base\n : new URL(config.base, 'http://localhost').pathname\n workerUrl = `${base}${WORKER_FILENAME}`\n },\n generateBundle() {\n if (this.environment.config.consumer !== 'client') {\n return\n }\n\n // In \"auto\" mode, the worker is only useful to bundles that contain the\n // network. A user guard (e.g. `import.meta.env.DEV`) that drops the\n // `virtual:msw` import drops the worker too.\n if (\n mode === 'auto' &&\n !environmentsUsingNetwork.has(this.environment.name)\n ) {\n return\n }\n\n this.emitFile({\n type: 'asset',\n fileName: WORKER_FILENAME,\n source: fs.readFileSync(WORKER_SCRIPT_PATH, 'utf8'),\n })\n },\n configureServer(server) {\n const workerScript = fs.readFileSync(WORKER_SCRIPT_PATH, 'utf8')\n server.middlewares.use((request, response, next) => {\n const requestUrl = new URL(request.url ?? '/', 'http://localhost')\n\n if (requestUrl.pathname !== workerUrl) {\n next()\n return\n }\n\n response.writeHead(200, {\n 'cache-control': 'no-cache',\n 'content-type': 'application/javascript; charset=utf-8',\n 'service-worker-allowed': '/',\n })\n response.end(workerScript)\n })\n },\n }\n}\n"],"mappings":";;;AAIA,MAAM,kBAAkB;AACxB,MAAM,qBAAqB,IAAI,IAAI,2BAA2B,YAAY,GAAG;AAC7E,MAAM,oBAAoB;AAC1B,MAAM,eAAe,cAAc,IAAI,IAAI,gBAAgB,YAAY,GAAG,CAAC;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAwC3E,SAAgB,IAAI,UAA4B,CAAC,GAAW;CAC1D,MAAM,OAAO,QAAQ,QAAQ;CAC7B,IAAI,YAAY,IAAI;CACpB,MAAM,2CAA2B,IAAI,IAAY;CAEjD,OAAO;EACL,MAAM;EACN,UAAU,IAAI;GACZ,IAAI,SAAS,eACX;GAGF,IAAI,OAAO,mBACT,OAAO,KAAK;EAEhB;EACA,KAAK,IAAI;GACP,IAAI,SAAS,iBAAiB,OAAO,KAAK,qBACxC;GAMF,IAAI,KAAK,YAAY,OAAO,aAAa,UACvC,OAAO;;gCAEiB,KAAK,UAAU,YAAY,EAAE;;;GAKvD,yBAAyB,IAAI,KAAK,YAAY,IAAI;GAElD,OAAO;;gCAEmB,KAAK,UAAU,YAAY,EAAE;yEACY,KAAK,UAAU,SAAS,EAAE;;EAE/F;EACA,eAAe,QAAQ;GAMrB,YAAY,GAHV,OAAO,SAAS,OACZ,OAAO,OACP,IAAI,IAAI,OAAO,MAAM,kBAAkB,CAAC,CAAC,WACzB;EACxB;EACA,iBAAiB;GACf,IAAI,KAAK,YAAY,OAAO,aAAa,UACvC;GAMF,IACE,SAAS,UACT,CAAC,yBAAyB,IAAI,KAAK,YAAY,IAAI,GAEnD;GAGF,KAAK,SAAS;IACZ,MAAM;IACN,UAAU;IACV,QAAQ,GAAG,aAAa,oBAAoB,MAAM;GACpD,CAAC;EACH;EACA,gBAAgB,QAAQ;GACtB,MAAM,eAAe,GAAG,aAAa,oBAAoB,MAAM;GAC/D,OAAO,YAAY,KAAK,SAAS,UAAU,SAAS;IAGlD,IAAI,IAFmB,IAAI,QAAQ,OAAO,KAAK,kBAElC,CAAC,CAAC,aAAa,WAAW;KACrC,KAAK;KACL;IACF;IAEA,SAAS,UAAU,KAAK;KACtB,iBAAiB;KACjB,gBAAgB;KAChB,0BAA0B;IAC5B,CAAC;IACD,SAAS,IAAI,YAAY;GAC3B,CAAC;EACH;CACF;AACF"}
@@ -1,6 +1,5 @@
1
- import { NetworkApi } from "msw/experimental";
2
- import { defaultNetworkOptions } from "virtual:msw/options";
1
+ import { defineNetwork } from "msw/experimental";
3
2
  //#region src/vite/runtime.d.ts
4
- export declare const network: NetworkApi<typeof defaultNetworkOptions.sources>;
3
+ export declare function createNetwork(options: Parameters<typeof defineNetwork>[0]): Promise<ReturnType<typeof defineNetwork>>;
5
4
  //#endregion
6
5
  //# sourceMappingURL=runtime.d.ts.map
@@ -1,77 +1,79 @@
1
1
  import { NetworkReadyState, defineNetwork } from "msw/experimental";
2
- import { defaultNetworkOptions } from "virtual:msw/options";
3
2
  //#region src/vite/runtime.ts
4
3
  const hot = import.meta.hot;
5
4
  const hotData = hot?.data;
6
- while (hotData?.disposeNetwork) {
7
- const disposePrevious = hotData.disposeNetwork;
8
- await disposePrevious();
9
- if (hotData.disposeNetwork === disposePrevious) break;
10
- }
11
- const network = defineNetwork(defaultNetworkOptions);
12
- if (hot && hotData) {
13
- let resumeAfterUpdate = false;
14
- let retired = false;
15
- let pendingOperation;
16
- const configure = network.configure;
17
- const enable = network.enable;
18
- const disable = network.disable;
19
- const runOperation = (action) => {
20
- const result = pendingOperation ? pendingOperation.then(action) : action();
21
- if (result instanceof Promise) {
22
- const operation = result.finally(() => {
23
- if (pendingOperation === operation) pendingOperation = void 0;
5
+ async function createNetwork(options) {
6
+ while (hotData?.disposeNetwork) {
7
+ const disposePrevious = hotData.disposeNetwork;
8
+ await disposePrevious();
9
+ if (hotData.disposeNetwork === disposePrevious) break;
10
+ }
11
+ const network = defineNetwork(options);
12
+ if (hot && hotData) {
13
+ let resumeAfterUpdate = false;
14
+ let retired = false;
15
+ let pendingOperation;
16
+ const configure = network.configure;
17
+ const enable = network.enable;
18
+ const disable = network.disable;
19
+ const runOperation = (action) => {
20
+ const result = pendingOperation ? pendingOperation.then(action) : action();
21
+ if (result instanceof Promise) {
22
+ const operation = result.finally(() => {
23
+ if (pendingOperation === operation) pendingOperation = void 0;
24
+ });
25
+ pendingOperation = operation;
26
+ return operation;
27
+ }
28
+ };
29
+ const stop = () => {
30
+ if (network.readyState === NetworkReadyState.ENABLED) return disable();
31
+ };
32
+ network.configure = (options) => {
33
+ runOperation(() => {
34
+ if (retired) return;
35
+ const stopped = stop();
36
+ if (stopped instanceof Promise) return stopped.then(() => configure(options));
37
+ configure(options);
38
+ });
39
+ };
40
+ network.enable = async () => {
41
+ await runOperation(() => {
42
+ if (!retired && network.readyState === NetworkReadyState.DISABLED) return enable();
24
43
  });
25
- pendingOperation = operation;
26
- return operation;
27
- }
28
- };
29
- const stop = () => {
30
- if (network.readyState === NetworkReadyState.ENABLED) return disable();
31
- };
32
- network.configure = (options) => {
33
- runOperation(() => {
34
- if (retired) return;
35
- const stopped = stop();
36
- if (stopped instanceof Promise) return stopped.then(() => configure(options));
37
- configure(options);
38
- });
39
- };
40
- network.enable = async () => {
41
- await runOperation(() => {
42
- if (!retired && network.readyState === NetworkReadyState.DISABLED) return enable();
43
- });
44
- };
45
- network.disable = async () => {
46
- await runOperation(stop);
47
- };
48
- const disableNetwork = async () => {
49
- await network.disable();
50
- };
51
- const beforeUpdate = async () => {
52
- resumeAfterUpdate = network.readyState === NetworkReadyState.ENABLED;
53
- await disableNetwork();
54
- };
55
- const afterUpdate = async () => {
56
- const shouldResume = resumeAfterUpdate;
57
- resumeAfterUpdate = false;
58
- if (shouldResume && network.readyState === NetworkReadyState.DISABLED) await network.enable();
59
- };
60
- hot.on("vite:beforeUpdate", beforeUpdate);
61
- hot.on("vite:afterUpdate", afterUpdate);
62
- hot.on("vite:beforeFullReload", disableNetwork);
63
- const disposeNetwork = async () => {
64
- retired = true;
65
- resumeAfterUpdate = false;
66
- hot.off("vite:beforeUpdate", beforeUpdate);
67
- hot.off("vite:afterUpdate", afterUpdate);
68
- hot.off("vite:beforeFullReload", disableNetwork);
69
- await disableNetwork();
70
- };
71
- hotData.disposeNetwork = disposeNetwork;
72
- hot.dispose(disposeNetwork);
44
+ };
45
+ network.disable = async () => {
46
+ await runOperation(stop);
47
+ };
48
+ const disableNetwork = async () => {
49
+ await network.disable();
50
+ };
51
+ const beforeUpdate = async () => {
52
+ resumeAfterUpdate = network.readyState === NetworkReadyState.ENABLED;
53
+ await disableNetwork();
54
+ };
55
+ const afterUpdate = async () => {
56
+ const shouldResume = resumeAfterUpdate;
57
+ resumeAfterUpdate = false;
58
+ if (shouldResume && network.readyState === NetworkReadyState.DISABLED) await network.enable();
59
+ };
60
+ hot.on("vite:beforeUpdate", beforeUpdate);
61
+ hot.on("vite:afterUpdate", afterUpdate);
62
+ hot.on("vite:beforeFullReload", disableNetwork);
63
+ const disposeNetwork = async () => {
64
+ retired = true;
65
+ resumeAfterUpdate = false;
66
+ hot.off("vite:beforeUpdate", beforeUpdate);
67
+ hot.off("vite:afterUpdate", afterUpdate);
68
+ hot.off("vite:beforeFullReload", disableNetwork);
69
+ await disableNetwork();
70
+ };
71
+ hotData.disposeNetwork = disposeNetwork;
72
+ hot.dispose(disposeNetwork);
73
+ }
74
+ return network;
73
75
  }
74
76
  //#endregion
75
- export { network };
77
+ export { createNetwork };
76
78
 
77
79
  //# sourceMappingURL=runtime.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"runtime.js","names":[],"sources":["../../src/vite/runtime.ts"],"sourcesContent":["/// <reference types=\"vite/client\" />\n\nimport {\n defineNetwork,\n NetworkReadyState,\n type NetworkApi,\n} from 'msw/experimental'\nimport { defaultNetworkOptions } from 'virtual:msw/options'\n\nconst hot = import.meta.hot\nconst hotData: { disposeNetwork?: () => Promise<void> } | undefined = hot?.data\n\n// SSR imports can re-evaluate this module before the old HMR listener runs.\nwhile (hotData?.disposeNetwork) {\n const disposePrevious = hotData.disposeNetwork\n await disposePrevious()\n\n if (hotData.disposeNetwork === disposePrevious) {\n break\n }\n}\n\nexport const network: NetworkApi<typeof defaultNetworkOptions.sources> =\n defineNetwork<typeof defaultNetworkOptions.sources>(defaultNetworkOptions)\n\nif (hot && hotData) {\n let resumeAfterUpdate = false\n let retired = false\n let pendingOperation: Promise<void> | undefined\n const configure = network.configure\n const enable = network.enable\n const disable = network.disable\n\n const runOperation = (action: () => void | Promise<void>) => {\n const result = pendingOperation ? pendingOperation.then(action) : action()\n\n if (result instanceof Promise) {\n const operation = result.finally(() => {\n if (pendingOperation === operation) {\n pendingOperation = undefined\n }\n })\n pendingOperation = operation\n\n return operation\n }\n }\n\n const stop = () => {\n if (network.readyState === NetworkReadyState.ENABLED) {\n return disable()\n }\n }\n\n // An SSR entry may be re-evaluated while this module remains cached.\n network.configure = (options) => {\n void runOperation(() => {\n if (retired) {\n return\n }\n\n const stopped = stop()\n\n if (stopped instanceof Promise) {\n return stopped.then(() => configure(options))\n }\n\n configure(options)\n })\n }\n network.enable = async () => {\n await runOperation(() => {\n if (!retired && network.readyState === NetworkReadyState.DISABLED) {\n return enable()\n }\n })\n }\n network.disable = async () => {\n await runOperation(stop)\n }\n\n const disableNetwork = async () => {\n await network.disable()\n }\n\n const beforeUpdate = async () => {\n resumeAfterUpdate = network.readyState === NetworkReadyState.ENABLED\n await disableNetwork()\n }\n\n const afterUpdate = async () => {\n const shouldResume = resumeAfterUpdate\n resumeAfterUpdate = false\n\n if (shouldResume && network.readyState === NetworkReadyState.DISABLED) {\n await network.enable()\n }\n }\n\n hot.on('vite:beforeUpdate', beforeUpdate)\n hot.on('vite:afterUpdate', afterUpdate)\n hot.on('vite:beforeFullReload', disableNetwork)\n\n const disposeNetwork = async () => {\n retired = true\n resumeAfterUpdate = false\n\n hot.off('vite:beforeUpdate', beforeUpdate)\n hot.off('vite:afterUpdate', afterUpdate)\n hot.off('vite:beforeFullReload', disableNetwork)\n\n await disableNetwork()\n }\n\n hotData.disposeNetwork = disposeNetwork\n hot.dispose(disposeNetwork)\n}\n"],"mappings":";;;AASA,MAAM,MAAM,YAAY;AACxB,MAAM,UAAgE,KAAK;AAG3E,OAAO,SAAS,gBAAgB;CAC9B,MAAM,kBAAkB,QAAQ;CAChC,MAAM,gBAAgB;CAEtB,IAAI,QAAQ,mBAAmB,iBAC7B;AAEJ;AAEA,MAAa,UACX,cAAoD,qBAAqB;AAE3E,IAAI,OAAO,SAAS;CAClB,IAAI,oBAAoB;CACxB,IAAI,UAAU;CACd,IAAI;CACJ,MAAM,YAAY,QAAQ;CAC1B,MAAM,SAAS,QAAQ;CACvB,MAAM,UAAU,QAAQ;CAExB,MAAM,gBAAgB,WAAuC;EAC3D,MAAM,SAAS,mBAAmB,iBAAiB,KAAK,MAAM,IAAI,OAAO;EAEzE,IAAI,kBAAkB,SAAS;GAC7B,MAAM,YAAY,OAAO,cAAc;IACrC,IAAI,qBAAqB,WACvB,mBAAmB,KAAA;GAEvB,CAAC;GACD,mBAAmB;GAEnB,OAAO;EACT;CACF;CAEA,MAAM,aAAa;EACjB,IAAI,QAAQ,eAAe,kBAAkB,SAC3C,OAAO,QAAQ;CAEnB;CAGA,QAAQ,aAAa,YAAY;EAC/B,mBAAwB;GACtB,IAAI,SACF;GAGF,MAAM,UAAU,KAAK;GAErB,IAAI,mBAAmB,SACrB,OAAO,QAAQ,WAAW,UAAU,OAAO,CAAC;GAG9C,UAAU,OAAO;EACnB,CAAC;CACH;CACA,QAAQ,SAAS,YAAY;EAC3B,MAAM,mBAAmB;GACvB,IAAI,CAAC,WAAW,QAAQ,eAAe,kBAAkB,UACvD,OAAO,OAAO;EAElB,CAAC;CACH;CACA,QAAQ,UAAU,YAAY;EAC5B,MAAM,aAAa,IAAI;CACzB;CAEA,MAAM,iBAAiB,YAAY;EACjC,MAAM,QAAQ,QAAQ;CACxB;CAEA,MAAM,eAAe,YAAY;EAC/B,oBAAoB,QAAQ,eAAe,kBAAkB;EAC7D,MAAM,eAAe;CACvB;CAEA,MAAM,cAAc,YAAY;EAC9B,MAAM,eAAe;EACrB,oBAAoB;EAEpB,IAAI,gBAAgB,QAAQ,eAAe,kBAAkB,UAC3D,MAAM,QAAQ,OAAO;CAEzB;CAEA,IAAI,GAAG,qBAAqB,YAAY;CACxC,IAAI,GAAG,oBAAoB,WAAW;CACtC,IAAI,GAAG,yBAAyB,cAAc;CAE9C,MAAM,iBAAiB,YAAY;EACjC,UAAU;EACV,oBAAoB;EAEpB,IAAI,IAAI,qBAAqB,YAAY;EACzC,IAAI,IAAI,oBAAoB,WAAW;EACvC,IAAI,IAAI,yBAAyB,cAAc;EAE/C,MAAM,eAAe;CACvB;CAEA,QAAQ,iBAAiB;CACzB,IAAI,QAAQ,cAAc;AAC5B"}
1
+ {"version":3,"file":"runtime.js","names":[],"sources":["../../src/vite/runtime.ts"],"sourcesContent":["/// <reference types=\"vite/client\" />\n\nimport { defineNetwork, NetworkReadyState } from 'msw/experimental'\n\nconst hot = import.meta.hot\nconst hotData: { disposeNetwork?: () => Promise<void> } | undefined = hot?.data\n\nexport async function createNetwork(\n options: Parameters<typeof defineNetwork>[0],\n): Promise<ReturnType<typeof defineNetwork>> {\n // SSR imports can re-evaluate the network before the old HMR listener runs.\n while (hotData?.disposeNetwork) {\n const disposePrevious = hotData.disposeNetwork\n await disposePrevious()\n\n if (hotData.disposeNetwork === disposePrevious) {\n break\n }\n }\n\n const network = defineNetwork(options)\n\n if (hot && hotData) {\n let resumeAfterUpdate = false\n let retired = false\n let pendingOperation: Promise<void> | undefined\n const configure = network.configure\n const enable = network.enable\n const disable = network.disable\n\n const runOperation = (action: () => void | Promise<void>) => {\n const result = pendingOperation ? pendingOperation.then(action) : action()\n\n if (result instanceof Promise) {\n const operation = result.finally(() => {\n if (pendingOperation === operation) {\n pendingOperation = undefined\n }\n })\n pendingOperation = operation\n\n return operation\n }\n }\n\n const stop = () => {\n if (network.readyState === NetworkReadyState.ENABLED) {\n return disable()\n }\n }\n\n // An SSR entry may be re-evaluated while this module remains cached.\n network.configure = (options) => {\n void runOperation(() => {\n if (retired) {\n return\n }\n\n const stopped = stop()\n\n if (stopped instanceof Promise) {\n return stopped.then(() => configure(options))\n }\n\n configure(options)\n })\n }\n network.enable = async () => {\n await runOperation(() => {\n if (!retired && network.readyState === NetworkReadyState.DISABLED) {\n return enable()\n }\n })\n }\n network.disable = async () => {\n await runOperation(stop)\n }\n\n const disableNetwork = async () => {\n await network.disable()\n }\n\n const beforeUpdate = async () => {\n resumeAfterUpdate = network.readyState === NetworkReadyState.ENABLED\n await disableNetwork()\n }\n\n const afterUpdate = async () => {\n const shouldResume = resumeAfterUpdate\n resumeAfterUpdate = false\n\n if (shouldResume && network.readyState === NetworkReadyState.DISABLED) {\n await network.enable()\n }\n }\n\n hot.on('vite:beforeUpdate', beforeUpdate)\n hot.on('vite:afterUpdate', afterUpdate)\n hot.on('vite:beforeFullReload', disableNetwork)\n\n const disposeNetwork = async () => {\n retired = true\n resumeAfterUpdate = false\n\n hot.off('vite:beforeUpdate', beforeUpdate)\n hot.off('vite:afterUpdate', afterUpdate)\n hot.off('vite:beforeFullReload', disableNetwork)\n\n await disableNetwork()\n }\n\n hotData.disposeNetwork = disposeNetwork\n hot.dispose(disposeNetwork)\n }\n\n return network\n}\n"],"mappings":";;AAIA,MAAM,MAAM,YAAY;AACxB,MAAM,UAAgE,KAAK;AAE3E,eAAsB,cACpB,SAC2C;CAE3C,OAAO,SAAS,gBAAgB;EAC9B,MAAM,kBAAkB,QAAQ;EAChC,MAAM,gBAAgB;EAEtB,IAAI,QAAQ,mBAAmB,iBAC7B;CAEJ;CAEA,MAAM,UAAU,cAAc,OAAO;CAErC,IAAI,OAAO,SAAS;EAClB,IAAI,oBAAoB;EACxB,IAAI,UAAU;EACd,IAAI;EACJ,MAAM,YAAY,QAAQ;EAC1B,MAAM,SAAS,QAAQ;EACvB,MAAM,UAAU,QAAQ;EAExB,MAAM,gBAAgB,WAAuC;GAC3D,MAAM,SAAS,mBAAmB,iBAAiB,KAAK,MAAM,IAAI,OAAO;GAEzE,IAAI,kBAAkB,SAAS;IAC7B,MAAM,YAAY,OAAO,cAAc;KACrC,IAAI,qBAAqB,WACvB,mBAAmB,KAAA;IAEvB,CAAC;IACD,mBAAmB;IAEnB,OAAO;GACT;EACF;EAEA,MAAM,aAAa;GACjB,IAAI,QAAQ,eAAe,kBAAkB,SAC3C,OAAO,QAAQ;EAEnB;EAGA,QAAQ,aAAa,YAAY;GAC/B,mBAAwB;IACtB,IAAI,SACF;IAGF,MAAM,UAAU,KAAK;IAErB,IAAI,mBAAmB,SACrB,OAAO,QAAQ,WAAW,UAAU,OAAO,CAAC;IAG9C,UAAU,OAAO;GACnB,CAAC;EACH;EACA,QAAQ,SAAS,YAAY;GAC3B,MAAM,mBAAmB;IACvB,IAAI,CAAC,WAAW,QAAQ,eAAe,kBAAkB,UACvD,OAAO,OAAO;GAElB,CAAC;EACH;EACA,QAAQ,UAAU,YAAY;GAC5B,MAAM,aAAa,IAAI;EACzB;EAEA,MAAM,iBAAiB,YAAY;GACjC,MAAM,QAAQ,QAAQ;EACxB;EAEA,MAAM,eAAe,YAAY;GAC/B,oBAAoB,QAAQ,eAAe,kBAAkB;GAC7D,MAAM,eAAe;EACvB;EAEA,MAAM,cAAc,YAAY;GAC9B,MAAM,eAAe;GACrB,oBAAoB;GAEpB,IAAI,gBAAgB,QAAQ,eAAe,kBAAkB,UAC3D,MAAM,QAAQ,OAAO;EAEzB;EAEA,IAAI,GAAG,qBAAqB,YAAY;EACxC,IAAI,GAAG,oBAAoB,WAAW;EACtC,IAAI,GAAG,yBAAyB,cAAc;EAE9C,MAAM,iBAAiB,YAAY;GACjC,UAAU;GACV,oBAAoB;GAEpB,IAAI,IAAI,qBAAqB,YAAY;GACzC,IAAI,IAAI,oBAAoB,WAAW;GACvC,IAAI,IAAI,yBAAyB,cAAc;GAE/C,MAAM,eAAe;EACvB;EAEA,QAAQ,iBAAiB;EACzB,IAAI,QAAQ,cAAc;CAC5B;CAEA,OAAO;AACT"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@depup/msw",
3
- "version": "3.0.1-depup.0",
3
+ "version": "3.0.3-depup.0",
4
4
  "description": "The industry standard for API mocking in JavaScript. (with updated dependencies)",
5
5
  "type": "module",
6
6
  "main": "./lib/core/index.js",
@@ -94,7 +94,6 @@
94
94
  "funding": "https://github.com/sponsors/mswjs",
95
95
  "files": [
96
96
  "config/constants.js",
97
- "config/scripts/postinstall.js",
98
97
  "cli",
99
98
  "lib",
100
99
  "src",
@@ -123,9 +122,9 @@
123
122
  ],
124
123
  "sideEffects": false,
125
124
  "dependencies": {
126
- "@inquirer/confirm": "^6.3.2",
125
+ "@inquirer/confirm": "^6.3.3",
127
126
  "@msw/url": "^0.1.2",
128
- "@mswjs/interceptors": "^0.45.6",
127
+ "@mswjs/interceptors": "^0.45.7",
129
128
  "cookie": "^2.0.1",
130
129
  "headers-polyfill": "^5.0.1",
131
130
  "is-node-process": "^1.2.0",
@@ -167,7 +166,7 @@
167
166
  "json-bigint": "^1.0.0",
168
167
  "knip": "^6.34.0",
169
168
  "lint-staged": "^17.1.1",
170
- "msw": "^3.0.1",
169
+ "msw": "^3.0.3",
171
170
  "oxlint": "^1.75.0",
172
171
  "page-with": "^0.6.1",
173
172
  "prettier": "^3.9.6",
@@ -229,17 +228,21 @@
229
228
  "changes": {
230
229
  "@inquirer/confirm": {
231
230
  "from": "^6.0.11",
232
- "to": "^6.3.2"
231
+ "to": "^6.3.3"
232
+ },
233
+ "@mswjs/interceptors": {
234
+ "from": "^0.45.6",
235
+ "to": "^0.45.7"
233
236
  },
234
237
  "type-fest": {
235
238
  "from": "^5.5.0",
236
239
  "to": "^5.10.0"
237
240
  }
238
241
  },
239
- "depsUpdated": 2,
242
+ "depsUpdated": 3,
240
243
  "originalPackage": "msw",
241
- "originalVersion": "3.0.1",
242
- "processedAt": "2026-09-30T16:13:04.601Z",
244
+ "originalVersion": "3.0.3",
245
+ "processedAt": "2026-10-10T04:42:39.550Z",
243
246
  "smokeTest": "passed"
244
247
  }
245
248
  }
@@ -17,7 +17,7 @@ export function createDefaultNetworkOptions(
17
17
  sources: [
18
18
  supportsServiceWorker()
19
19
  ? new ServiceWorkerSource({ serviceWorker: { url: workerUrl } })
20
- : new FallbackHttpSource({}),
20
+ : new FallbackHttpSource(),
21
21
  new InterceptorSource({
22
22
  interceptors: [
23
23
  new WebSocketInterceptor() as InterceptorSourceOptions['interceptors'][number],
@@ -56,11 +56,8 @@ export function setupWorker(...handlers: Array<AnyHandler>): SetupWorker {
56
56
  options: options?.serviceWorker?.options,
57
57
  },
58
58
  findWorker: options?.findWorker,
59
- quiet: options?.quiet,
60
- })
61
- : new FallbackHttpSource({
62
- quiet: options?.quiet,
63
59
  })
60
+ : new FallbackHttpSource()
64
61
 
65
62
  network.configure({
66
63
  sources: [
@@ -1,23 +1,26 @@
1
1
  import { FetchInterceptor } from '@mswjs/interceptors/fetch'
2
2
  import { XMLHttpRequestInterceptor } from '@mswjs/interceptors/XMLHttpRequest'
3
3
  import { InterceptorSource } from '#core/experimental/sources/interceptor-source'
4
+ import type { NetworkFrameResolutionContext } from '#core/experimental/frames/network-frame'
4
5
  import { devUtils } from '#core/utils/internal/dev-utils'
5
6
 
6
- interface FallbackHttpSourceOptions {
7
- quiet?: boolean
8
- }
9
-
10
7
  export class FallbackHttpSource extends InterceptorSource {
11
- constructor(private readonly options: FallbackHttpSourceOptions) {
8
+ /**
9
+ * The resolved context of the network this source was last enabled with.
10
+ */
11
+ #context?: NetworkFrameResolutionContext
12
+
13
+ constructor() {
12
14
  super({
13
15
  interceptors: [new XMLHttpRequestInterceptor(), new FetchInterceptor()],
14
16
  })
15
17
  }
16
18
 
17
- public enable(): void {
19
+ public enable(context?: NetworkFrameResolutionContext): void {
20
+ this.#context = context
18
21
  super.enable()
19
22
 
20
- if (!this.options.quiet) {
23
+ if (!this.#context?.quiet) {
21
24
  this.#printStartMessage()
22
25
  }
23
26
  }
@@ -25,7 +28,7 @@ export class FallbackHttpSource extends InterceptorSource {
25
28
  public disable(): void {
26
29
  super.disable()
27
30
 
28
- if (!this.options.quiet) {
31
+ if (!this.#context?.quiet) {
29
32
  this.#printStopMessage()
30
33
  }
31
34
  }
@@ -2,6 +2,7 @@ import { invariant } from 'outvariant'
2
2
  import type { Emitter } from 'rettime'
3
3
  import { FetchResponse } from '@mswjs/interceptors'
4
4
  import { NetworkSource } from '#core/experimental/sources/network-source'
5
+ import type { NetworkFrameResolutionContext } from '#core/experimental/frames/network-frame'
5
6
  import { RequestHandler } from '#core/handlers/request-handler'
6
7
  import {
7
8
  HttpNetworkFrame,
@@ -23,7 +24,6 @@ import { validateWorkerScope } from '../utils/validate-worker-scope'
23
24
  import { shouldInvalidateWorker } from '../utils/should-invalidate-worker'
24
25
 
25
26
  export interface ServiceWorkerSourceOptions {
26
- quiet?: boolean
27
27
  serviceWorker: {
28
28
  url: string
29
29
  options?: RegistrationOptions
@@ -75,6 +75,10 @@ export class ServiceWorkerSource extends NetworkSource<ServiceWorkerHttpNetworkF
75
75
  }
76
76
 
77
77
  #options: ServiceWorkerSourceOptions
78
+ /**
79
+ * The resolved context of the network this source was last enabled with.
80
+ */
81
+ #context?: NetworkFrameResolutionContext
78
82
  /**
79
83
  * @note We cannot use `WeakMap` here as request/response
80
84
  * identity cannot be preserved through the client-worker channel.
@@ -109,7 +113,10 @@ export class ServiceWorkerSource extends NetworkSource<ServiceWorkerHttpNetworkF
109
113
  })
110
114
  }
111
115
 
112
- public async enable(): Promise<ServiceWorkerRegistration> {
116
+ public async enable(
117
+ context?: NetworkFrameResolutionContext,
118
+ ): Promise<ServiceWorkerRegistration> {
119
+ this.#context = context
113
120
  this.#channel.removeAllListeners()
114
121
  this.#frames.clear()
115
122
 
@@ -147,7 +154,7 @@ export class ServiceWorkerSource extends NetworkSource<ServiceWorkerHttpNetworkF
147
154
  })
148
155
  await clientConfirmationPromise.promise
149
156
 
150
- if (!this.#options.quiet) {
157
+ if (!this.#context?.quiet) {
151
158
  this.#printStartMessage()
152
159
  }
153
160
 
@@ -178,7 +185,7 @@ export class ServiceWorkerSource extends NetworkSource<ServiceWorkerHttpNetworkF
178
185
  * `#handleRequest`. `#startWorker` swaps in a fresh deferred on re-enable.
179
186
  */
180
187
 
181
- if (!this.#options.quiet) {
188
+ if (!this.#context?.quiet) {
182
189
  this.#printStopMessage()
183
190
  }
184
191
  }
@@ -294,7 +301,7 @@ Please consider using a custom "serviceWorker.url" option to point to the actual
294
301
  this.#channel.postMessage('KEEPALIVE_REQUEST')
295
302
  }, 5000)
296
303
 
297
- if (!this.#options.quiet) {
304
+ if (!this.#context?.quiet) {
298
305
  validateWorkerScope(registration)
299
306
  }
300
307
 
@@ -104,19 +104,3 @@ test('returns false when findWorker is the same reference', () => {
104
104
  ),
105
105
  ).toBe(false)
106
106
  })
107
-
108
- test('returns false regardless of the "quiet" option', () => {
109
- expect(
110
- shouldInvalidateWorker(
111
- createOptions({ quiet: true }),
112
- createOptions({ quiet: true }),
113
- ),
114
- ).toBe(false)
115
-
116
- expect(
117
- shouldInvalidateWorker(
118
- createOptions({ quiet: false }),
119
- createOptions({ quiet: true }),
120
- ),
121
- ).toBe(false)
122
- })
@@ -203,7 +203,7 @@ export function defineNetwork<Sources extends Array<NetworkSource<any>>>(
203
203
  )
204
204
  })
205
205
 
206
- return source.enable()
206
+ return source.enable(resolvedOptions.context)
207
207
  })
208
208
 
209
209
  return colorlessPromiseAll(result) as MaybePromise<
@@ -308,6 +308,67 @@ describe(InMemoryHandlersController.prototype.reset, () => {
308
308
  expect(controller.getHandlersByKind('websocket')).toEqual([wsOne, wsTwo])
309
309
  expect(controller.getHandlersByKind('request')).toEqual([upgradeHandler])
310
310
  })
311
+
312
+ test('resets the kept handlers and disposes of the removed ones', () => {
313
+ const initialHandler = http.get('/initial', () => {})
314
+ initialHandler.reset = vi.fn()
315
+ initialHandler.dispose = vi.fn()
316
+ const runtimeHandler = http.get('/runtime', () => {})
317
+ runtimeHandler.reset = vi.fn()
318
+ runtimeHandler.dispose = vi.fn()
319
+
320
+ const controller = new InMemoryHandlersController([initialHandler])
321
+ controller.use([runtimeHandler])
322
+ controller.reset([])
323
+
324
+ expect.soft(initialHandler.reset).toHaveBeenCalledOnce()
325
+ expect.soft(initialHandler.dispose).not.toHaveBeenCalled()
326
+ expect.soft(runtimeHandler.reset).not.toHaveBeenCalled()
327
+ expect(runtimeHandler.dispose).toHaveBeenCalledOnce()
328
+ })
329
+
330
+ test('reports a rejected disposal of a removed handler', async () => {
331
+ vi.spyOn(console, 'error').mockImplementation(() => {})
332
+ onTestFinished(() => vi.restoreAllMocks())
333
+
334
+ const runtimeHandler = http.get('/runtime', () => {})
335
+ runtimeHandler.dispose = () =>
336
+ Promise.reject(new Error('Handler disposal error'))
337
+
338
+ const controller = new InMemoryHandlersController([])
339
+ controller.use([runtimeHandler])
340
+ controller.reset([])
341
+
342
+ await expect
343
+ .poll(() => console.error)
344
+ .toHaveBeenCalledWith(
345
+ `[MSW] Failed to dispose of "request" handler removed during reset. Please see the original error below.\nError: Handler disposal error`,
346
+ )
347
+ })
348
+
349
+ test('removes the remaining handlers when a disposal throws', () => {
350
+ vi.spyOn(console, 'error').mockImplementation(() => {})
351
+ onTestFinished(() => vi.restoreAllMocks())
352
+
353
+ const throwingHandler = http.get('/throwing', () => {})
354
+ throwingHandler.dispose = () => {
355
+ throw new Error('Handler disposal error')
356
+ }
357
+ const runtimeHandler = http.get('/runtime', () => {})
358
+ runtimeHandler.dispose = vi.fn()
359
+
360
+ const controller = new InMemoryHandlersController([])
361
+ controller.use([throwingHandler, runtimeHandler])
362
+ controller.reset([])
363
+
364
+ expect
365
+ .soft(console.error)
366
+ .toHaveBeenCalledWith(
367
+ `[MSW] Failed to dispose of "request" handler removed during reset. Please see the original error below.\nError: Handler disposal error`,
368
+ )
369
+ expect.soft(runtimeHandler.dispose).toHaveBeenCalledOnce()
370
+ expect(controller.currentHandlers()).toEqual([])
371
+ })
311
372
  })
312
373
 
313
374
  describe(InMemoryHandlersController.prototype.listHandlers, () => {
@@ -168,11 +168,38 @@ export abstract class HandlersController {
168
168
  ),
169
169
  )
170
170
 
171
- for (const handler of this.currentHandlers()) {
171
+ const { initialHandlers } = this.getState()
172
+ const nextInitialHandlers =
173
+ nextHandlers.length > 0
174
+ ? groupHandlersByKind(nextHandlers)
175
+ : initialHandlers
176
+ const currentHandlers = new Set(this.currentHandlers())
177
+ const nextHandlersSet = new Set(
178
+ Object.values(nextInitialHandlers).flat().filter(Boolean),
179
+ )
180
+
181
+ for (const handler of currentHandlers.intersection(nextHandlersSet)) {
172
182
  handler.reset()
173
183
  }
174
184
 
175
- const { initialHandlers } = this.getState()
185
+ // The handlers that leave the network with this reset release
186
+ // whatever they hold (e.g. live connection bindings).
187
+ // Resetting is synchronous, so their disposal is not awaited.
188
+ for (const handler of currentHandlers.difference(nextHandlersSet)) {
189
+ try {
190
+ const disposal = handler.dispose()
191
+
192
+ if (disposal instanceof Promise) {
193
+ disposal.catch((error) => {
194
+ this.#printDisposalError(handler, error)
195
+ })
196
+ }
197
+ } catch (error) {
198
+ // A failing disposal must not prevent the remaining handlers
199
+ // from being disposed of, nor the removed handlers from being cleared.
200
+ this.#printDisposalError(handler, error)
201
+ }
202
+ }
176
203
 
177
204
  if (nextHandlers.length === 0) {
178
205
  this.setState({
@@ -182,11 +209,9 @@ export abstract class HandlersController {
182
209
  return
183
210
  }
184
211
 
185
- const normalizedNextHandlers = groupHandlersByKind(nextHandlers)
186
-
187
212
  this.setState({
188
- initialHandlers: normalizedNextHandlers,
189
- handlers: { ...normalizedNextHandlers },
213
+ initialHandlers: nextInitialHandlers,
214
+ handlers: { ...nextInitialHandlers },
190
215
  })
191
216
  }
192
217
 
@@ -217,6 +242,14 @@ export abstract class HandlersController {
217
242
  #validateHandlers(handlers: Array<AnyHandler>): boolean {
218
243
  return handlers.every((handler) => !Array.isArray(handler))
219
244
  }
245
+
246
+ #printDisposalError(handler: AnyHandler, error: unknown): void {
247
+ devUtils.error(
248
+ 'Failed to dispose of "%s" handler removed during reset. Please see the original error below.\n%s',
249
+ handler.kind,
250
+ error,
251
+ )
252
+ }
220
253
  }
221
254
 
222
255
  export class InMemoryHandlersController extends HandlersController {
@@ -2,6 +2,7 @@ import { Emitter, TypedEvent, type TypedListenerOptions } from 'rettime'
2
2
  import type {
3
3
  AnyNetworkFrame,
4
4
  ExtractFrameEvents,
5
+ NetworkFrameResolutionContext,
5
6
  } from '../frames/network-frame'
6
7
 
7
8
  class NetworkFrameEvent<
@@ -33,7 +34,13 @@ export abstract class NetworkSource<
33
34
  this.emitter = new Emitter()
34
35
  }
35
36
 
36
- public abstract enable(): unknown | Promise<unknown>
37
+ /**
38
+ * Enable this network source.
39
+ * @param context The resolved context of the network this source belongs to.
40
+ */
41
+ public abstract enable(
42
+ context?: NetworkFrameResolutionContext,
43
+ ): unknown | Promise<unknown>
37
44
 
38
45
  public async queue(frame: Frame): Promise<void> {
39
46
  await this.emitter.emitAsPromise(