msw 2.13.6 → 2.14.1

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 (119) hide show
  1. package/lib/core/{HttpResponse-BMMzfpjG.d.mts → HttpResponse-CxHR1nNN.d.mts} +5 -1
  2. package/lib/core/{HttpResponse-DPDqE4Pb.d.ts → HttpResponse-aGiIzO91.d.ts} +5 -1
  3. package/lib/core/HttpResponse.d.mts +1 -1
  4. package/lib/core/HttpResponse.d.ts +1 -1
  5. package/lib/core/experimental/compat.d.mts +1 -1
  6. package/lib/core/experimental/compat.d.ts +1 -1
  7. package/lib/core/experimental/define-network.d.mts +1 -1
  8. package/lib/core/experimental/define-network.d.ts +1 -1
  9. package/lib/core/experimental/frames/http-frame.d.mts +1 -1
  10. package/lib/core/experimental/frames/http-frame.d.ts +1 -1
  11. package/lib/core/experimental/frames/http-frame.js +3 -2
  12. package/lib/core/experimental/frames/http-frame.js.map +1 -1
  13. package/lib/core/experimental/frames/http-frame.mjs +3 -2
  14. package/lib/core/experimental/frames/http-frame.mjs.map +1 -1
  15. package/lib/core/experimental/frames/network-frame.d.mts +1 -1
  16. package/lib/core/experimental/frames/network-frame.d.ts +1 -1
  17. package/lib/core/experimental/frames/websocket-frame.d.mts +1 -1
  18. package/lib/core/experimental/frames/websocket-frame.d.ts +1 -1
  19. package/lib/core/experimental/handlers-controller.d.mts +1 -1
  20. package/lib/core/experimental/handlers-controller.d.ts +1 -1
  21. package/lib/core/experimental/handlers-controller.js +16 -5
  22. package/lib/core/experimental/handlers-controller.js.map +1 -1
  23. package/lib/core/experimental/handlers-controller.mjs +16 -5
  24. package/lib/core/experimental/handlers-controller.mjs.map +1 -1
  25. package/lib/core/experimental/index.d.mts +3 -3
  26. package/lib/core/experimental/index.d.ts +3 -3
  27. package/lib/core/experimental/index.js +3 -0
  28. package/lib/core/experimental/index.js.map +1 -1
  29. package/lib/core/experimental/index.mjs +6 -0
  30. package/lib/core/experimental/index.mjs.map +1 -1
  31. package/lib/core/experimental/on-unhandled-frame.d.mts +1 -1
  32. package/lib/core/experimental/on-unhandled-frame.d.ts +1 -1
  33. package/lib/core/experimental/setup-api.d.mts +1 -1
  34. package/lib/core/experimental/setup-api.d.ts +1 -1
  35. package/lib/core/experimental/sources/interceptor-source.d.mts +1 -1
  36. package/lib/core/experimental/sources/interceptor-source.d.ts +1 -1
  37. package/lib/core/experimental/sources/network-source.d.mts +1 -1
  38. package/lib/core/experimental/sources/network-source.d.ts +1 -1
  39. package/lib/core/getResponse.d.mts +1 -1
  40. package/lib/core/getResponse.d.ts +1 -1
  41. package/lib/core/graphql.d.mts +1 -1
  42. package/lib/core/graphql.d.ts +1 -1
  43. package/lib/core/handlers/GraphQLHandler.d.mts +1 -1
  44. package/lib/core/handlers/GraphQLHandler.d.ts +1 -1
  45. package/lib/core/handlers/HttpHandler.d.mts +1 -1
  46. package/lib/core/handlers/HttpHandler.d.ts +1 -1
  47. package/lib/core/handlers/RequestHandler.d.mts +1 -1
  48. package/lib/core/handlers/RequestHandler.d.ts +1 -1
  49. package/lib/core/handlers/RequestHandler.js +22 -1
  50. package/lib/core/handlers/RequestHandler.js.map +1 -1
  51. package/lib/core/handlers/RequestHandler.mjs +22 -1
  52. package/lib/core/handlers/RequestHandler.mjs.map +1 -1
  53. package/lib/core/handlers/WebSocketHandler.js +1 -1
  54. package/lib/core/handlers/WebSocketHandler.js.map +1 -1
  55. package/lib/core/handlers/WebSocketHandler.mjs +1 -1
  56. package/lib/core/handlers/WebSocketHandler.mjs.map +1 -1
  57. package/lib/core/http.d.mts +1 -1
  58. package/lib/core/http.d.ts +1 -1
  59. package/lib/core/index.d.mts +1 -1
  60. package/lib/core/index.d.ts +1 -1
  61. package/lib/core/passthrough.d.mts +1 -1
  62. package/lib/core/passthrough.d.ts +1 -1
  63. package/lib/core/sse.d.mts +1 -1
  64. package/lib/core/sse.d.ts +1 -1
  65. package/lib/core/utils/HttpResponse/decorators.d.mts +3 -3
  66. package/lib/core/utils/HttpResponse/decorators.d.ts +3 -3
  67. package/lib/core/utils/HttpResponse/decorators.js +4 -10
  68. package/lib/core/utils/HttpResponse/decorators.js.map +1 -1
  69. package/lib/core/utils/HttpResponse/decorators.mjs +4 -10
  70. package/lib/core/utils/HttpResponse/decorators.mjs.map +1 -1
  71. package/lib/core/utils/executeHandlers.d.mts +1 -1
  72. package/lib/core/utils/executeHandlers.d.ts +1 -1
  73. package/lib/core/utils/handleRequest.d.mts +1 -1
  74. package/lib/core/utils/handleRequest.d.ts +1 -1
  75. package/lib/core/utils/internal/attachSiblingHandlers.d.mts +15 -0
  76. package/lib/core/utils/internal/attachSiblingHandlers.d.ts +15 -0
  77. package/lib/core/utils/internal/attachSiblingHandlers.js +44 -0
  78. package/lib/core/utils/internal/attachSiblingHandlers.js.map +1 -0
  79. package/lib/core/utils/internal/attachSiblingHandlers.mjs +24 -0
  80. package/lib/core/utils/internal/attachSiblingHandlers.mjs.map +1 -0
  81. package/lib/core/utils/internal/isHandlerKind.d.mts +1 -1
  82. package/lib/core/utils/internal/isHandlerKind.d.ts +1 -1
  83. package/lib/core/utils/internal/parseGraphQLRequest.d.mts +1 -1
  84. package/lib/core/utils/internal/parseGraphQLRequest.d.ts +1 -1
  85. package/lib/core/utils/internal/parseMultipartData.d.mts +1 -1
  86. package/lib/core/utils/internal/parseMultipartData.d.ts +1 -1
  87. package/lib/core/utils/request/storeResponseCookies.js +1 -1
  88. package/lib/core/utils/request/storeResponseCookies.js.map +1 -1
  89. package/lib/core/utils/request/storeResponseCookies.mjs +2 -2
  90. package/lib/core/utils/request/storeResponseCookies.mjs.map +1 -1
  91. package/lib/core/ws/handleWebSocketEvent.d.mts +1 -1
  92. package/lib/core/ws/handleWebSocketEvent.d.ts +1 -1
  93. package/lib/core/ws.d.mts +17 -4
  94. package/lib/core/ws.d.ts +17 -4
  95. package/lib/core/ws.js +30 -5
  96. package/lib/core/ws.js.map +1 -1
  97. package/lib/core/ws.mjs +34 -6
  98. package/lib/core/ws.mjs.map +1 -1
  99. package/lib/iife/index.js +1208 -1142
  100. package/lib/iife/index.js.map +1 -1
  101. package/lib/mockServiceWorker.js +1 -1
  102. package/lib/node/index.d.mts +1 -1
  103. package/lib/node/index.d.ts +1 -1
  104. package/lib/node/index.js +2 -0
  105. package/lib/node/index.js.map +1 -1
  106. package/lib/node/index.mjs +1 -0
  107. package/package.json +2 -2
  108. package/src/core/experimental/frames/http-frame.test.ts +6 -1
  109. package/src/core/experimental/frames/http-frame.ts +6 -2
  110. package/src/core/experimental/handlers-controller.test.ts +139 -5
  111. package/src/core/experimental/handlers-controller.ts +24 -9
  112. package/src/core/experimental/index.ts +6 -0
  113. package/src/core/handlers/RequestHandler.ts +36 -1
  114. package/src/core/handlers/WebSocketHandler.ts +1 -1
  115. package/src/core/utils/HttpResponse/decorators.ts +6 -21
  116. package/src/core/utils/internal/attachSiblingHandlers.ts +28 -0
  117. package/src/core/utils/request/storeResponseCookies.ts +2 -4
  118. package/src/core/ws.ts +65 -6
  119. package/src/node/index.ts +1 -0
@@ -7,7 +7,7 @@
7
7
  * - Please do NOT modify this file.
8
8
  */
9
9
 
10
- const PACKAGE_VERSION = '2.13.6'
10
+ const PACKAGE_VERSION = '2.14.1'
11
11
  const INTEGRITY_CHECKSUM = '4db4a41e972cec1b64cc569c66952d82'
12
12
  const IS_MOCKED_RESPONSE = Symbol('isMockedResponse')
13
13
  const activeClientIds = new Set()
@@ -110,4 +110,4 @@ declare class SetupServerApi extends SetupServerCommonApi implements SetupServer
110
110
  constructor(handlers: Array<AnyHandler>, interceptors: Array<Interceptor<any>>);
111
111
  }
112
112
 
113
- export { type SetupServer, SetupServerApi, SetupServerCommonApi, defaultNetworkOptions, setupServer };
113
+ export { AsyncHandlersController, type SetupServer, SetupServerApi, SetupServerCommonApi, defaultNetworkOptions, setupServer };
@@ -110,4 +110,4 @@ declare class SetupServerApi extends SetupServerCommonApi implements SetupServer
110
110
  constructor(handlers: Array<AnyHandler>, interceptors: Array<Interceptor<any>>);
111
111
  }
112
112
 
113
- export { type SetupServer, SetupServerApi, SetupServerCommonApi, defaultNetworkOptions, setupServer };
113
+ export { AsyncHandlersController, type SetupServer, SetupServerApi, SetupServerCommonApi, defaultNetworkOptions, setupServer };
package/lib/node/index.js CHANGED
@@ -20,6 +20,7 @@ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: tru
20
20
  // src/node/index.ts
21
21
  var index_exports = {};
22
22
  __export(index_exports, {
23
+ AsyncHandlersController: () => AsyncHandlersController,
23
24
  SetupServerApi: () => SetupServerApi,
24
25
  SetupServerCommonApi: () => SetupServerCommonApi,
25
26
  defaultNetworkOptions: () => defaultNetworkOptions,
@@ -199,6 +200,7 @@ var SetupServerApi = class extends SetupServerCommonApi {
199
200
  };
200
201
  // Annotate the CommonJS export names for ESM import in node:
201
202
  0 && (module.exports = {
203
+ AsyncHandlersController,
202
204
  SetupServerApi,
203
205
  SetupServerCommonApi,
204
206
  defaultNetworkOptions,
@@ -1 +1 @@
1
- {"version":3,"sources":["../../src/node/index.ts","../../src/node/setup-server.ts","../../src/node/async-handlers-controller.ts","../../src/node/setup-server-common.ts"],"sourcesContent":["export type { SetupServer } from './glossary'\nexport {\n setupServer,\n SetupServerApi,\n defaultNetworkOptions,\n} from './setup-server'\nexport { SetupServerCommonApi } from './setup-server-common'\n","import type { Interceptor } from '@mswjs/interceptors'\nimport { ClientRequestInterceptor } from '@mswjs/interceptors/ClientRequest'\nimport { XMLHttpRequestInterceptor } from '@mswjs/interceptors/XMLHttpRequest'\nimport { FetchInterceptor } from '@mswjs/interceptors/fetch'\nimport { WebSocketInterceptor } from '@mswjs/interceptors/WebSocket'\nimport {\n defineNetwork,\n type DefineNetworkOptions,\n} from '#core/experimental/define-network'\nimport type { AnyHandler } from '#core/experimental/handlers-controller'\nimport { InterceptorSource } from '#core/experimental/sources/interceptor-source'\nimport type { SetupServer } from './glossary'\nimport { AsyncHandlersController } from './async-handlers-controller'\nimport {\n defineSetupServerApi,\n SetupServerCommonApi,\n} from './setup-server-common'\n\nconst defaultInterceptors: Array<Interceptor<any>> = [\n new ClientRequestInterceptor(),\n new XMLHttpRequestInterceptor(),\n new FetchInterceptor(),\n /**\n * @fixme WebSocketInterceptor is in a browser-only export of Interceptors\n * while the Interceptor class imported from the root module points to `lib/node`.\n * An absolute madness to solve as it requires to duplicate the build config we have\n * in MSW: shared core, CJS/ESM patching, .d.ts patching...\n */\n new WebSocketInterceptor() as any,\n]\n\nexport const defaultNetworkOptions: DefineNetworkOptions<[InterceptorSource]> =\n {\n sources: [\n new InterceptorSource({\n interceptors: defaultInterceptors,\n }),\n ],\n onUnhandledFrame: 'warn',\n context: {\n quiet: true,\n },\n }\n\n/**\n * Enables request interception in Node.js with the given request handlers.\n * @see {@link https://mswjs.io/docs/api/setup-server `setupServer()` API reference}\n */\nexport function setupServer(...handlers: Array<AnyHandler>): SetupServer {\n const handlersController = new AsyncHandlersController(handlers)\n const network = defineNetwork({\n ...defaultNetworkOptions,\n handlers: handlersController,\n })\n\n const commonApi = defineSetupServerApi(network)\n\n return {\n ...commonApi,\n boundary: handlersController.boundary.bind(handlersController),\n }\n}\n\n/**\n * @deprecated\n * Please use the `defineNetwork` API instead.\n */\nexport class SetupServerApi\n extends SetupServerCommonApi\n implements SetupServer\n{\n #handlersController: AsyncHandlersController\n\n public boundary: AsyncHandlersController['boundary']\n\n constructor(\n handlers: Array<AnyHandler>,\n interceptors: Array<Interceptor<any>>,\n ) {\n const controller = new AsyncHandlersController(handlers)\n super(interceptors, controller)\n\n const { sources: _, ...networkOptions } = defaultNetworkOptions\n this.network.configure(networkOptions)\n\n this.#handlersController = controller\n this.boundary = this.#handlersController.boundary.bind(\n this.#handlersController,\n )\n }\n}\n","import { AsyncLocalStorage } from 'node:async_hooks'\nimport {\n HandlersController,\n type AnyHandler,\n type HandlersMap,\n type HandlersControllerState,\n} from '#core/experimental/handlers-controller'\n\nexport interface AsyncHandlersControllerContext {\n initialHandlers: HandlersMap\n handlers: HandlersMap\n}\n\nexport class AsyncHandlersController extends HandlersController {\n #asyncContext: AsyncLocalStorage<AsyncHandlersControllerContext>\n #fallbackContext: AsyncHandlersControllerContext\n\n constructor(initialHandlers: Array<AnyHandler>) {\n super()\n\n const initialState = this.getInitialState(initialHandlers)\n\n this.#asyncContext = new AsyncLocalStorage()\n this.#fallbackContext = {\n initialHandlers: initialState.initialHandlers,\n handlers: initialState.handlers,\n }\n }\n\n protected getState() {\n const { initialHandlers, handlers } = this.#getContext()\n\n return {\n initialHandlers,\n handlers,\n }\n }\n\n protected setState(nextState: HandlersControllerState): void {\n const context = this.#getContext()\n\n if (nextState.initialHandlers) {\n context.initialHandlers = nextState.initialHandlers\n }\n\n if (nextState.handlers) {\n context.handlers = nextState.handlers\n }\n }\n\n public boundary<Args extends Array<any>, R>(callback: (...args: Args) => R) {\n return (...args: Args) => {\n const initialHandlers = { ...this.getState().handlers }\n\n return this.#asyncContext.run(\n {\n initialHandlers,\n handlers: { ...initialHandlers },\n },\n callback,\n ...args,\n )\n }\n }\n\n #getContext() {\n return this.#asyncContext.getStore() || this.#fallbackContext\n }\n}\n","import type { PartialDeep } from 'type-fest'\nimport type { Interceptor } from '@mswjs/interceptors'\nimport {\n NetworkReadyState,\n defineNetwork,\n type NetworkApi,\n} from '#core/experimental/define-network'\nimport type { AnyHandler } from '#core/experimental/handlers-controller'\nimport type { HandlersController } from '#core/experimental/handlers-controller'\nimport { InterceptorSource } from '#core/experimental/sources/interceptor-source'\nimport { fromLegacyOnUnhandledRequest } from '#core/experimental/compat'\nimport type { ListenOptions, SetupServerCommon } from './glossary'\n\n/**\n * Define the common `setupServer` API around the given network.\n * This is used by both `msw/node` and `msw/native` to implement the same\n * baseline setup methods, like `.use()`, `.resetHandlers()`, `.close()`, etc.\n */\nexport function defineSetupServerApi(\n network: NetworkApi<any>,\n): SetupServerCommon {\n return {\n events: network.events,\n listen(options) {\n network.configure({\n onUnhandledFrame: fromLegacyOnUnhandledRequest(() => {\n return options?.onUnhandledRequest || 'warn'\n }),\n })\n\n network.enable()\n },\n use: network.use.bind(network),\n resetHandlers: network.resetHandlers.bind(network),\n restoreHandlers: network.restoreHandlers.bind(network),\n listHandlers: network.listHandlers.bind(network),\n close() {\n /**\n * @note Ignore closing after closed for backwards compatibility.\n */\n if (network.readyState === NetworkReadyState.DISABLED) {\n return\n }\n\n network.disable()\n },\n }\n}\n\n/**\n * @deprecated\n * Please use the `defineNetwork` API instead.\n */\nexport class SetupServerCommonApi implements SetupServerCommon {\n protected network: NetworkApi<[InterceptorSource]>\n\n constructor(\n interceptors: Array<Interceptor<any>>,\n handlers: Array<AnyHandler> | HandlersController,\n ) {\n this.network = defineNetwork({\n sources: [new InterceptorSource({ interceptors })],\n handlers,\n })\n }\n\n get events() {\n return this.network.events\n }\n\n public listen(options?: PartialDeep<ListenOptions>): void {\n this.network.configure({\n onUnhandledFrame: fromLegacyOnUnhandledRequest(() => {\n return options?.onUnhandledRequest || 'warn'\n }),\n })\n\n this.network.enable()\n }\n\n public use(...handlers: Array<AnyHandler>): void {\n this.network.use(...handlers)\n }\n\n public resetHandlers(...nextHandlers: Array<AnyHandler>): void {\n return this.network.resetHandlers(...nextHandlers)\n }\n\n public restoreHandlers(): void {\n return this.network.restoreHandlers()\n }\n\n public listHandlers(): ReadonlyArray<AnyHandler> {\n return this.network.listHandlers()\n }\n\n public close(): void {\n this.network.disable()\n }\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;;ACCA,2BAAyC;AACzC,4BAA0C;AAC1C,mBAAiC;AACjC,uBAAqC;AACrC,IAAAA,yBAGO;AAEP,IAAAC,6BAAkC;;;ACVlC,8BAAkC;AAClC,iCAKO;AAOA,IAAM,0BAAN,cAAsC,8CAAmB;AAAA,EAC9D;AAAA,EACA;AAAA,EAEA,YAAY,iBAAoC;AAC9C,UAAM;AAEN,UAAM,eAAe,KAAK,gBAAgB,eAAe;AAEzD,SAAK,gBAAgB,IAAI,0CAAkB;AAC3C,SAAK,mBAAmB;AAAA,MACtB,iBAAiB,aAAa;AAAA,MAC9B,UAAU,aAAa;AAAA,IACzB;AAAA,EACF;AAAA,EAEU,WAAW;AACnB,UAAM,EAAE,iBAAiB,SAAS,IAAI,KAAK,YAAY;AAEvD,WAAO;AAAA,MACL;AAAA,MACA;AAAA,IACF;AAAA,EACF;AAAA,EAEU,SAAS,WAA0C;AAC3D,UAAM,UAAU,KAAK,YAAY;AAEjC,QAAI,UAAU,iBAAiB;AAC7B,cAAQ,kBAAkB,UAAU;AAAA,IACtC;AAEA,QAAI,UAAU,UAAU;AACtB,cAAQ,WAAW,UAAU;AAAA,IAC/B;AAAA,EACF;AAAA,EAEO,SAAqC,UAAgC;AAC1E,WAAO,IAAI,SAAe;AACxB,YAAM,kBAAkB,EAAE,GAAG,KAAK,SAAS,EAAE,SAAS;AAEtD,aAAO,KAAK,cAAc;AAAA,QACxB;AAAA,UACE;AAAA,UACA,UAAU,EAAE,GAAG,gBAAgB;AAAA,QACjC;AAAA,QACA;AAAA,QACA,GAAG;AAAA,MACL;AAAA,IACF;AAAA,EACF;AAAA,EAEA,cAAc;AACZ,WAAO,KAAK,cAAc,SAAS,KAAK,KAAK;AAAA,EAC/C;AACF;;;AClEA,4BAIO;AAGP,gCAAkC;AAClC,oBAA6C;AAQtC,SAAS,qBACd,SACmB;AACnB,SAAO;AAAA,IACL,QAAQ,QAAQ;AAAA,IAChB,OAAO,SAAS;AACd,cAAQ,UAAU;AAAA,QAChB,sBAAkB,4CAA6B,MAAM;AACnD,iBAAO,SAAS,sBAAsB;AAAA,QACxC,CAAC;AAAA,MACH,CAAC;AAED,cAAQ,OAAO;AAAA,IACjB;AAAA,IACA,KAAK,QAAQ,IAAI,KAAK,OAAO;AAAA,IAC7B,eAAe,QAAQ,cAAc,KAAK,OAAO;AAAA,IACjD,iBAAiB,QAAQ,gBAAgB,KAAK,OAAO;AAAA,IACrD,cAAc,QAAQ,aAAa,KAAK,OAAO;AAAA,IAC/C,QAAQ;AAIN,UAAI,QAAQ,eAAe,wCAAkB,UAAU;AACrD;AAAA,MACF;AAEA,cAAQ,QAAQ;AAAA,IAClB;AAAA,EACF;AACF;AAMO,IAAM,uBAAN,MAAwD;AAAA,EACnD;AAAA,EAEV,YACE,cACA,UACA;AACA,SAAK,cAAU,qCAAc;AAAA,MAC3B,SAAS,CAAC,IAAI,4CAAkB,EAAE,aAAa,CAAC,CAAC;AAAA,MACjD;AAAA,IACF,CAAC;AAAA,EACH;AAAA,EAEA,IAAI,SAAS;AACX,WAAO,KAAK,QAAQ;AAAA,EACtB;AAAA,EAEO,OAAO,SAA4C;AACxD,SAAK,QAAQ,UAAU;AAAA,MACrB,sBAAkB,4CAA6B,MAAM;AACnD,eAAO,SAAS,sBAAsB;AAAA,MACxC,CAAC;AAAA,IACH,CAAC;AAED,SAAK,QAAQ,OAAO;AAAA,EACtB;AAAA,EAEO,OAAO,UAAmC;AAC/C,SAAK,QAAQ,IAAI,GAAG,QAAQ;AAAA,EAC9B;AAAA,EAEO,iBAAiB,cAAuC;AAC7D,WAAO,KAAK,QAAQ,cAAc,GAAG,YAAY;AAAA,EACnD;AAAA,EAEO,kBAAwB;AAC7B,WAAO,KAAK,QAAQ,gBAAgB;AAAA,EACtC;AAAA,EAEO,eAA0C;AAC/C,WAAO,KAAK,QAAQ,aAAa;AAAA,EACnC;AAAA,EAEO,QAAc;AACnB,SAAK,QAAQ,QAAQ;AAAA,EACvB;AACF;;;AFjFA,IAAM,sBAA+C;AAAA,EACnD,IAAI,8CAAyB;AAAA,EAC7B,IAAI,gDAA0B;AAAA,EAC9B,IAAI,8BAAiB;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAOrB,IAAI,sCAAqB;AAC3B;AAEO,IAAM,wBACX;AAAA,EACE,SAAS;AAAA,IACP,IAAI,6CAAkB;AAAA,MACpB,cAAc;AAAA,IAChB,CAAC;AAAA,EACH;AAAA,EACA,kBAAkB;AAAA,EAClB,SAAS;AAAA,IACP,OAAO;AAAA,EACT;AACF;AAMK,SAAS,eAAe,UAA0C;AACvE,QAAM,qBAAqB,IAAI,wBAAwB,QAAQ;AAC/D,QAAM,cAAU,sCAAc;AAAA,IAC5B,GAAG;AAAA,IACH,UAAU;AAAA,EACZ,CAAC;AAED,QAAM,YAAY,qBAAqB,OAAO;AAE9C,SAAO;AAAA,IACL,GAAG;AAAA,IACH,UAAU,mBAAmB,SAAS,KAAK,kBAAkB;AAAA,EAC/D;AACF;AAMO,IAAM,iBAAN,cACG,qBAEV;AAAA,EACE;AAAA,EAEO;AAAA,EAEP,YACE,UACA,cACA;AACA,UAAM,aAAa,IAAI,wBAAwB,QAAQ;AACvD,UAAM,cAAc,UAAU;AAE9B,UAAM,EAAE,SAAS,GAAG,GAAG,eAAe,IAAI;AAC1C,SAAK,QAAQ,UAAU,cAAc;AAErC,SAAK,sBAAsB;AAC3B,SAAK,WAAW,KAAK,oBAAoB,SAAS;AAAA,MAChD,KAAK;AAAA,IACP;AAAA,EACF;AACF;","names":["import_define_network","import_interceptor_source"]}
1
+ {"version":3,"sources":["../../src/node/index.ts","../../src/node/setup-server.ts","../../src/node/async-handlers-controller.ts","../../src/node/setup-server-common.ts"],"sourcesContent":["export type { SetupServer } from './glossary'\nexport {\n setupServer,\n SetupServerApi,\n defaultNetworkOptions,\n} from './setup-server'\nexport { SetupServerCommonApi } from './setup-server-common'\nexport { AsyncHandlersController } from './async-handlers-controller'\n","import type { Interceptor } from '@mswjs/interceptors'\nimport { ClientRequestInterceptor } from '@mswjs/interceptors/ClientRequest'\nimport { XMLHttpRequestInterceptor } from '@mswjs/interceptors/XMLHttpRequest'\nimport { FetchInterceptor } from '@mswjs/interceptors/fetch'\nimport { WebSocketInterceptor } from '@mswjs/interceptors/WebSocket'\nimport {\n defineNetwork,\n type DefineNetworkOptions,\n} from '#core/experimental/define-network'\nimport type { AnyHandler } from '#core/experimental/handlers-controller'\nimport { InterceptorSource } from '#core/experimental/sources/interceptor-source'\nimport type { SetupServer } from './glossary'\nimport { AsyncHandlersController } from './async-handlers-controller'\nimport {\n defineSetupServerApi,\n SetupServerCommonApi,\n} from './setup-server-common'\n\nconst defaultInterceptors: Array<Interceptor<any>> = [\n new ClientRequestInterceptor(),\n new XMLHttpRequestInterceptor(),\n new FetchInterceptor(),\n /**\n * @fixme WebSocketInterceptor is in a browser-only export of Interceptors\n * while the Interceptor class imported from the root module points to `lib/node`.\n * An absolute madness to solve as it requires to duplicate the build config we have\n * in MSW: shared core, CJS/ESM patching, .d.ts patching...\n */\n new WebSocketInterceptor() as any,\n]\n\nexport const defaultNetworkOptions: DefineNetworkOptions<[InterceptorSource]> =\n {\n sources: [\n new InterceptorSource({\n interceptors: defaultInterceptors,\n }),\n ],\n onUnhandledFrame: 'warn',\n context: {\n quiet: true,\n },\n }\n\n/**\n * Enables request interception in Node.js with the given request handlers.\n * @see {@link https://mswjs.io/docs/api/setup-server `setupServer()` API reference}\n */\nexport function setupServer(...handlers: Array<AnyHandler>): SetupServer {\n const handlersController = new AsyncHandlersController(handlers)\n const network = defineNetwork({\n ...defaultNetworkOptions,\n handlers: handlersController,\n })\n\n const commonApi = defineSetupServerApi(network)\n\n return {\n ...commonApi,\n boundary: handlersController.boundary.bind(handlersController),\n }\n}\n\n/**\n * @deprecated\n * Please use the `defineNetwork` API instead.\n */\nexport class SetupServerApi\n extends SetupServerCommonApi\n implements SetupServer\n{\n #handlersController: AsyncHandlersController\n\n public boundary: AsyncHandlersController['boundary']\n\n constructor(\n handlers: Array<AnyHandler>,\n interceptors: Array<Interceptor<any>>,\n ) {\n const controller = new AsyncHandlersController(handlers)\n super(interceptors, controller)\n\n const { sources: _, ...networkOptions } = defaultNetworkOptions\n this.network.configure(networkOptions)\n\n this.#handlersController = controller\n this.boundary = this.#handlersController.boundary.bind(\n this.#handlersController,\n )\n }\n}\n","import { AsyncLocalStorage } from 'node:async_hooks'\nimport {\n HandlersController,\n type AnyHandler,\n type HandlersMap,\n type HandlersControllerState,\n} from '#core/experimental/handlers-controller'\n\nexport interface AsyncHandlersControllerContext {\n initialHandlers: HandlersMap\n handlers: HandlersMap\n}\n\nexport class AsyncHandlersController extends HandlersController {\n #asyncContext: AsyncLocalStorage<AsyncHandlersControllerContext>\n #fallbackContext: AsyncHandlersControllerContext\n\n constructor(initialHandlers: Array<AnyHandler>) {\n super()\n\n const initialState = this.getInitialState(initialHandlers)\n\n this.#asyncContext = new AsyncLocalStorage()\n this.#fallbackContext = {\n initialHandlers: initialState.initialHandlers,\n handlers: initialState.handlers,\n }\n }\n\n protected getState() {\n const { initialHandlers, handlers } = this.#getContext()\n\n return {\n initialHandlers,\n handlers,\n }\n }\n\n protected setState(nextState: HandlersControllerState): void {\n const context = this.#getContext()\n\n if (nextState.initialHandlers) {\n context.initialHandlers = nextState.initialHandlers\n }\n\n if (nextState.handlers) {\n context.handlers = nextState.handlers\n }\n }\n\n public boundary<Args extends Array<any>, R>(callback: (...args: Args) => R) {\n return (...args: Args) => {\n const initialHandlers = { ...this.getState().handlers }\n\n return this.#asyncContext.run(\n {\n initialHandlers,\n handlers: { ...initialHandlers },\n },\n callback,\n ...args,\n )\n }\n }\n\n #getContext() {\n return this.#asyncContext.getStore() || this.#fallbackContext\n }\n}\n","import type { PartialDeep } from 'type-fest'\nimport type { Interceptor } from '@mswjs/interceptors'\nimport {\n NetworkReadyState,\n defineNetwork,\n type NetworkApi,\n} from '#core/experimental/define-network'\nimport type { AnyHandler } from '#core/experimental/handlers-controller'\nimport type { HandlersController } from '#core/experimental/handlers-controller'\nimport { InterceptorSource } from '#core/experimental/sources/interceptor-source'\nimport { fromLegacyOnUnhandledRequest } from '#core/experimental/compat'\nimport type { ListenOptions, SetupServerCommon } from './glossary'\n\n/**\n * Define the common `setupServer` API around the given network.\n * This is used by both `msw/node` and `msw/native` to implement the same\n * baseline setup methods, like `.use()`, `.resetHandlers()`, `.close()`, etc.\n */\nexport function defineSetupServerApi(\n network: NetworkApi<any>,\n): SetupServerCommon {\n return {\n events: network.events,\n listen(options) {\n network.configure({\n onUnhandledFrame: fromLegacyOnUnhandledRequest(() => {\n return options?.onUnhandledRequest || 'warn'\n }),\n })\n\n network.enable()\n },\n use: network.use.bind(network),\n resetHandlers: network.resetHandlers.bind(network),\n restoreHandlers: network.restoreHandlers.bind(network),\n listHandlers: network.listHandlers.bind(network),\n close() {\n /**\n * @note Ignore closing after closed for backwards compatibility.\n */\n if (network.readyState === NetworkReadyState.DISABLED) {\n return\n }\n\n network.disable()\n },\n }\n}\n\n/**\n * @deprecated\n * Please use the `defineNetwork` API instead.\n */\nexport class SetupServerCommonApi implements SetupServerCommon {\n protected network: NetworkApi<[InterceptorSource]>\n\n constructor(\n interceptors: Array<Interceptor<any>>,\n handlers: Array<AnyHandler> | HandlersController,\n ) {\n this.network = defineNetwork({\n sources: [new InterceptorSource({ interceptors })],\n handlers,\n })\n }\n\n get events() {\n return this.network.events\n }\n\n public listen(options?: PartialDeep<ListenOptions>): void {\n this.network.configure({\n onUnhandledFrame: fromLegacyOnUnhandledRequest(() => {\n return options?.onUnhandledRequest || 'warn'\n }),\n })\n\n this.network.enable()\n }\n\n public use(...handlers: Array<AnyHandler>): void {\n this.network.use(...handlers)\n }\n\n public resetHandlers(...nextHandlers: Array<AnyHandler>): void {\n return this.network.resetHandlers(...nextHandlers)\n }\n\n public restoreHandlers(): void {\n return this.network.restoreHandlers()\n }\n\n public listHandlers(): ReadonlyArray<AnyHandler> {\n return this.network.listHandlers()\n }\n\n public close(): void {\n this.network.disable()\n }\n}\n"],"mappings":"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","names":["import_define_network","import_interceptor_source"]}
@@ -176,6 +176,7 @@ var SetupServerApi = class extends SetupServerCommonApi {
176
176
  }
177
177
  };
178
178
  export {
179
+ AsyncHandlersController,
179
180
  SetupServerApi,
180
181
  SetupServerCommonApi,
181
182
  defaultNetworkOptions,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "msw",
3
- "version": "2.13.6",
3
+ "version": "2.14.1",
4
4
  "description": "Seamless REST/GraphQL API mocking library for browser and Node.js.",
5
5
  "type": "commonjs",
6
6
  "main": "./lib/core/index.js",
@@ -286,7 +286,7 @@
286
286
  "vitest-environment-miniflare": "^2.14.4",
287
287
  "webpack": "^5.106.2",
288
288
  "webpack-http-server": "^0.5.0",
289
- "msw": "^2.13.6"
289
+ "msw": "^2.14.1"
290
290
  },
291
291
  "peerDependencies": {
292
292
  "typescript": ">= 4.8.x"
@@ -4,7 +4,8 @@ import { ws } from '../../ws'
4
4
  import { bypass } from '../../bypass'
5
5
  import type { HttpNetworkFrameEventMap } from './http-frame'
6
6
  import { HttpNetworkFrame } from './http-frame'
7
- import { InMemoryHandlersController } from '#core/experimental/handlers-controller'
7
+ import { InMemoryHandlersController } from '../../experimental/handlers-controller'
8
+ import { getSiblingHandlers } from '../../utils/internal/attachSiblingHandlers'
8
9
 
9
10
  beforeAll(() => {
10
11
  vi.spyOn(console, 'error').mockImplementation(() => {})
@@ -46,6 +47,9 @@ it('filters only request type handlers', async () => {
46
47
  const webSocketHandlers = [
47
48
  ws.link('ws://localhost').addEventListener('connection', () => {}),
48
49
  ]
50
+ const webSocketSiblingHandlers = webSocketHandlers.flatMap((handler) =>
51
+ getSiblingHandlers(handler),
52
+ )
49
53
 
50
54
  const controller = new InMemoryHandlersController([
51
55
  ...httpHandlers,
@@ -55,6 +59,7 @@ it('filters only request type handlers', async () => {
55
59
 
56
60
  expect(frame.getHandlers(controller)).toEqual([
57
61
  ...httpHandlers,
62
+ ...webSocketSiblingHandlers,
58
63
  ...graphqlHandlers,
59
64
  ])
60
65
  expect(frame.getHandlers(new InMemoryHandlersController([]))).toEqual([])
@@ -251,9 +251,13 @@ export abstract class HttpNetworkFrame extends NetworkFrame<
251
251
  return null
252
252
  }
253
253
 
254
+ const responseCloneForLogs = resolutionContext?.quiet
255
+ ? null
256
+ : response.clone()
257
+
254
258
  await storeResponseCookies(request, response)
255
259
 
256
- this.respondWith(response.clone())
260
+ this.respondWith(response)
257
261
 
258
262
  this.events.emit(
259
263
  new RequestEvent('request:end', {
@@ -265,7 +269,7 @@ export abstract class HttpNetworkFrame extends NetworkFrame<
265
269
  if (!resolutionContext?.quiet) {
266
270
  handler.log({
267
271
  request: requestCloneForLogs!,
268
- response,
272
+ response: responseCloneForLogs!,
269
273
  parsedResult,
270
274
  })
271
275
  }
@@ -1,8 +1,68 @@
1
1
  import { http } from '../http'
2
2
  import { graphql } from '../graphql'
3
3
  import { ws } from '../ws'
4
+ import { getSiblingHandlers } from '../utils/internal/attachSiblingHandlers'
4
5
  import { InMemoryHandlersController } from './handlers-controller'
5
6
 
7
+ describe('constructor', () => {
8
+ it('places the sibling in its own kind bucket', () => {
9
+ const wsHandler = ws.link('*').addEventListener('connection', () => {})
10
+ const [upgradeHandler] = getSiblingHandlers(wsHandler)
11
+
12
+ const controller = new InMemoryHandlersController([wsHandler])
13
+
14
+ expect(controller.getHandlersByKind('websocket')).toEqual([wsHandler])
15
+ expect(controller.getHandlersByKind('request')).toEqual([upgradeHandler])
16
+ })
17
+
18
+ it('interleaves the sibling at the owner position when grouping by kind', () => {
19
+ const httpOne = http.get('/', () => {})
20
+ const wsHandler = ws.link('*').addEventListener('connection', () => {})
21
+ const [upgradeHandler] = getSiblingHandlers(wsHandler)
22
+ const httpTwo = http.get('/', () => {})
23
+
24
+ const controller = new InMemoryHandlersController([
25
+ httpOne,
26
+ wsHandler,
27
+ httpTwo,
28
+ ])
29
+
30
+ expect(controller.getHandlersByKind('request')).toEqual([
31
+ httpOne,
32
+ upgradeHandler,
33
+ httpTwo,
34
+ ])
35
+ expect(controller.getHandlersByKind('websocket')).toEqual([wsHandler])
36
+ })
37
+
38
+ it('extracts siblings from every owner in the input list', () => {
39
+ const wsOne = ws.link('*').addEventListener('connection', () => {})
40
+ const wsTwo = ws.link('*').addEventListener('connection', () => {})
41
+ const [upgradeOne] = getSiblingHandlers(wsOne)
42
+ const [upgradeTwo] = getSiblingHandlers(wsTwo)
43
+
44
+ const controller = new InMemoryHandlersController([wsOne, wsTwo])
45
+
46
+ expect(controller.getHandlersByKind('websocket')).toEqual([wsOne, wsTwo])
47
+ expect(controller.getHandlersByKind('request')).toEqual([
48
+ upgradeOne,
49
+ upgradeTwo,
50
+ ])
51
+ })
52
+
53
+ it('dedupes the shared upgrade sibling across multiple handlers from the same link', () => {
54
+ const chat = ws.link('*')
55
+ const wsOne = chat.addEventListener('connection', () => {})
56
+ const wsTwo = chat.addEventListener('connection', () => {})
57
+ const [upgradeHandler] = getSiblingHandlers(wsOne)
58
+
59
+ const controller = new InMemoryHandlersController([wsOne, wsTwo])
60
+
61
+ expect(controller.getHandlersByKind('websocket')).toEqual([wsOne, wsTwo])
62
+ expect(controller.getHandlersByKind('request')).toEqual([upgradeHandler])
63
+ })
64
+ })
65
+
6
66
  describe(InMemoryHandlersController.prototype.use, () => {
7
67
  it('prepends a handler to an empty controller', () => {
8
68
  const controller = new InMemoryHandlersController([])
@@ -51,6 +111,44 @@ describe(InMemoryHandlersController.prototype.use, () => {
51
111
 
52
112
  expect(controller.currentHandlers()).toEqual([graphqlOne, httpTwo, httpOne])
53
113
  })
114
+
115
+ it('propagates siblings to their kind buckets at runtime', () => {
116
+ const controller = new InMemoryHandlersController([])
117
+ const wsHandler = ws.link('*').addEventListener('connection', () => {})
118
+ const [upgradeHandler] = getSiblingHandlers(wsHandler)
119
+
120
+ controller.use([wsHandler])
121
+
122
+ expect(controller.getHandlersByKind('websocket')).toEqual([wsHandler])
123
+ expect(controller.getHandlersByKind('request')).toEqual([upgradeHandler])
124
+ })
125
+
126
+ it('prepends incoming siblings before existing handlers of the same kind', () => {
127
+ const existingHttp = http.get('/existing', () => {})
128
+ const controller = new InMemoryHandlersController([existingHttp])
129
+ const wsHandler = ws.link('*').addEventListener('connection', () => {})
130
+ const [upgradeHandler] = getSiblingHandlers(wsHandler)
131
+
132
+ controller.use([wsHandler])
133
+
134
+ expect(controller.getHandlersByKind('request')).toEqual([
135
+ upgradeHandler,
136
+ existingHttp,
137
+ ])
138
+ })
139
+
140
+ it('dedupes the shared upgrade sibling when called with multiple handlers from the same link', () => {
141
+ const chat = ws.link('*')
142
+ const wsOne = chat.addEventListener('connection', () => {})
143
+ const wsTwo = chat.addEventListener('connection', () => {})
144
+ const [upgradeHandler] = getSiblingHandlers(wsOne)
145
+
146
+ const controller = new InMemoryHandlersController([])
147
+ controller.use([wsOne, wsTwo])
148
+
149
+ expect(controller.getHandlersByKind('websocket')).toEqual([wsOne, wsTwo])
150
+ expect(controller.getHandlersByKind('request')).toEqual([upgradeHandler])
151
+ })
54
152
  })
55
153
 
56
154
  describe(InMemoryHandlersController.prototype.reset, () => {
@@ -96,6 +194,42 @@ describe(InMemoryHandlersController.prototype.reset, () => {
96
194
  */
97
195
  expect(controller.currentHandlers()).toEqual([httpTwo])
98
196
  })
197
+
198
+ it('places siblings into their kind buckets when resetting to next handlers', () => {
199
+ const controller = new InMemoryHandlersController([])
200
+ const wsHandler = ws.link('*').addEventListener('connection', () => {})
201
+ const [upgradeHandler] = getSiblingHandlers(wsHandler)
202
+
203
+ controller.reset([wsHandler])
204
+
205
+ expect(controller.getHandlersByKind('websocket')).toEqual([wsHandler])
206
+ expect(controller.getHandlersByKind('request')).toEqual([upgradeHandler])
207
+ })
208
+
209
+ it('restores siblings when resetting to the initial handlers', () => {
210
+ const wsHandler = ws.link('*').addEventListener('connection', () => {})
211
+ const [upgradeHandler] = getSiblingHandlers(wsHandler)
212
+ const controller = new InMemoryHandlersController([wsHandler])
213
+
214
+ controller.use([http.get('/runtime', () => {})])
215
+ controller.reset([])
216
+
217
+ expect(controller.getHandlersByKind('websocket')).toEqual([wsHandler])
218
+ expect(controller.getHandlersByKind('request')).toEqual([upgradeHandler])
219
+ })
220
+
221
+ it('dedupes the shared upgrade sibling when reset with multiple handlers from the same link', () => {
222
+ const chat = ws.link('*')
223
+ const wsOne = chat.addEventListener('connection', () => {})
224
+ const wsTwo = chat.addEventListener('connection', () => {})
225
+ const [upgradeHandler] = getSiblingHandlers(wsOne)
226
+
227
+ const controller = new InMemoryHandlersController([])
228
+ controller.reset([wsOne, wsTwo])
229
+
230
+ expect(controller.getHandlersByKind('websocket')).toEqual([wsOne, wsTwo])
231
+ expect(controller.getHandlersByKind('request')).toEqual([upgradeHandler])
232
+ })
99
233
  })
100
234
 
101
235
  describe(InMemoryHandlersController.prototype.getHandlersByKind, () => {
@@ -112,11 +246,10 @@ describe(InMemoryHandlersController.prototype.getHandlersByKind, () => {
112
246
  ]).getHandlersByKind('websocket'),
113
247
  ).toEqual([])
114
248
 
249
+ const wsHandler = ws.link('*').addEventListener('connection', () => {})
115
250
  expect(
116
- new InMemoryHandlersController([
117
- ws.link('*').addEventListener('connection', () => {}),
118
- ]).getHandlersByKind('request'),
119
- ).toEqual([])
251
+ new InMemoryHandlersController([wsHandler]).getHandlersByKind('request'),
252
+ ).toEqual(getSiblingHandlers(wsHandler))
120
253
  })
121
254
 
122
255
  it('returns all handlers if they all match', () => {
@@ -142,6 +275,7 @@ describe(InMemoryHandlersController.prototype.getHandlersByKind, () => {
142
275
  const httpHandler = http.get('/', () => {})
143
276
  const graphqlHandler = graphql.query('', () => {})
144
277
  const wsHandler = ws.link('*').addEventListener('connection', () => {})
278
+ const wsHandlerSiblings = getSiblingHandlers(wsHandler)
145
279
 
146
280
  expect(
147
281
  new InMemoryHandlersController([
@@ -149,7 +283,7 @@ describe(InMemoryHandlersController.prototype.getHandlersByKind, () => {
149
283
  graphqlHandler,
150
284
  wsHandler,
151
285
  ]).getHandlersByKind('request'),
152
- ).toEqual([httpHandler, graphqlHandler])
286
+ ).toEqual([httpHandler, graphqlHandler, ...wsHandlerSiblings])
153
287
 
154
288
  expect(
155
289
  new InMemoryHandlersController([
@@ -2,6 +2,7 @@ import { invariant } from 'outvariant'
2
2
  import { type RequestHandler } from '../handlers/RequestHandler'
3
3
  import { type WebSocketHandler } from '../handlers/WebSocketHandler'
4
4
  import { devUtils } from '../utils/internal/devUtils'
5
+ import { getSiblingHandlers } from '../utils/internal/attachSiblingHandlers'
5
6
 
6
7
  export type AnyHandler = RequestHandler | WebSocketHandler
7
8
  export type HandlersMap = Partial<Record<AnyHandler['kind'], Array<AnyHandler>>>
@@ -9,11 +10,23 @@ export type HandlersMap = Partial<Record<AnyHandler['kind'], Array<AnyHandler>>>
9
10
  export function groupHandlersByKind(handlers: Array<AnyHandler>): HandlersMap {
10
11
  const groups: HandlersMap = {}
11
12
 
13
+ const pushUnique = (kind: AnyHandler['kind'], handler: AnyHandler) => {
14
+ const bucket = (groups[kind] ||= [])
15
+
16
+ if (!bucket.includes(handler)) {
17
+ bucket.push(handler)
18
+ }
19
+ }
20
+
12
21
  /**
13
22
  * @note `Object.groupBy` is not implemented in Node.js v20.
14
23
  */
15
24
  for (const handler of handlers) {
16
- ;(groups[handler.kind] ||= []).push(handler)
25
+ pushUnique(handler.kind, handler)
26
+
27
+ for (const sibling of getSiblingHandlers(handler)) {
28
+ pushUnique(sibling.kind, sibling)
29
+ }
17
30
  }
18
31
 
19
32
  return groups
@@ -69,14 +82,16 @@ export abstract class HandlersController {
69
82
  }
70
83
 
71
84
  const { handlers } = this.getState()
72
-
73
- // Iterate over next handlers and prepend them to their respective lists.
74
- // Iterate in a reverse order to the keep the order of the runtime handlers as provided.
75
- for (let i = nextHandlers.length - 1; i >= 0; i--) {
76
- const handler = nextHandlers[i]
77
- handlers[handler.kind] = handlers[handler.kind]
78
- ? [handler, ...handlers[handler.kind]!]
79
- : [handler]
85
+ const overrides = groupHandlersByKind(nextHandlers)
86
+
87
+ // Prepend overrides to their respective kind buckets so they take
88
+ // priority over existing handlers while preserving input order.
89
+ for (const kind in overrides) {
90
+ const overridesForKind = overrides[kind as AnyHandler['kind']]!
91
+ const existingForKind = handlers[kind as AnyHandler['kind']]
92
+ handlers[kind as AnyHandler['kind']] = existingForKind
93
+ ? [...overridesForKind, ...existingForKind]
94
+ : overridesForKind
80
95
  }
81
96
 
82
97
  this.setState({ handlers })
@@ -14,3 +14,9 @@ export {
14
14
  WebSocketNetworkFrame,
15
15
  type WebSocketNetworkFrameEventMap,
16
16
  } from './frames/websocket-frame'
17
+
18
+ /* Handler controllers */
19
+ export {
20
+ HandlersController,
21
+ InMemoryHandlersController,
22
+ } from './handlers-controller'
@@ -1,3 +1,4 @@
1
+ import { Headers as HeadersPolyfill } from 'headers-polyfill'
1
2
  import { getCallFrame } from '../utils/internal/getCallFrame'
2
3
  import {
3
4
  isIterable,
@@ -12,6 +13,7 @@ import {
12
13
  type DefaultUnsafeFetchResponse,
13
14
  } from '../HttpResponse'
14
15
  import type { GraphQLRequestBody } from './GraphQLHandler'
16
+ import { getRawSetCookie } from '../utils/HttpResponse/decorators'
15
17
 
16
18
  export type DefaultRequestMultipartBody = Record<
17
19
  string,
@@ -335,7 +337,7 @@ export abstract class RequestHandler<
335
337
  ...resolverExtras,
336
338
  requestId: args.requestId,
337
339
  request: args.request,
338
- }) as Promise<Response>
340
+ }) as Promise<Response | undefined>
339
341
  ).catch((errorOrResponse) => {
340
342
  // Allow throwing a Response instance in a response resolver.
341
343
  if (errorOrResponse instanceof Response) {
@@ -348,6 +350,10 @@ export abstract class RequestHandler<
348
350
 
349
351
  const mockedResponse = await mockedResponsePromise
350
352
 
353
+ if (mockedResponse) {
354
+ forwardResponseCookies(mockedResponse)
355
+ }
356
+
351
357
  const executionResult = this.createExecutionResult({
352
358
  // Pass the cloned request to the result so that logging
353
359
  // and other consumers could read its body once more.
@@ -416,3 +422,32 @@ export abstract class RequestHandler<
416
422
  }
417
423
  }
418
424
  }
425
+
426
+ /**
427
+ * Forwards the cookies from the given response to `document.cookie`.
428
+ */
429
+ export function forwardResponseCookies(response: Response): void {
430
+ // Cookie forwarding is only relevant in the browser.
431
+ if (typeof document === 'undefined') {
432
+ return
433
+ }
434
+
435
+ const responseCookies = getRawSetCookie(response)
436
+
437
+ if (!responseCookies) {
438
+ return
439
+ }
440
+
441
+ // Write the mocked response cookies to the document.
442
+ // Use `headers-polyfill` to get the Set-Cookie header value correctly.
443
+ // This is an alternative until TypeScript 5.2
444
+ // and Node.js v20 become the minimum supported versions
445
+ // and "Headers.prototype.getSetCookie" can be used directly.
446
+ const allResponseCookies = HeadersPolyfill.prototype.getSetCookie.call(
447
+ new Headers([['set-cookie', responseCookies]]),
448
+ )
449
+
450
+ for (const cookieString of allResponseCookies) {
451
+ document.cookie = cookieString
452
+ }
453
+ }
@@ -118,7 +118,7 @@ export class WebSocketHandler {
118
118
  params: parsedResult.match.params || {},
119
119
  }
120
120
 
121
- if (resolutionContext?.[kAutoConnect]) {
121
+ if (resolutionContext?.[kAutoConnect] ?? true) {
122
122
  if (this[kConnect](resolvedConnection)) {
123
123
  return resolvedConnection
124
124
  }
@@ -1,10 +1,9 @@
1
1
  import statuses from '../../../shims/statuses'
2
- import { Headers as HeadersPolyfill } from 'headers-polyfill'
3
2
  import type { HttpResponseInit } from '../../HttpResponse'
4
3
 
5
4
  const { message } = statuses
6
5
 
7
- export const kSetCookie = Symbol('kSetCookie')
6
+ const kSetCookie = Symbol('kSetCookie')
8
7
 
9
8
  export interface HttpResponseDecoratedInit extends HttpResponseInit {
10
9
  status: number
@@ -31,7 +30,7 @@ export function decorateResponse(
31
30
  response: Response,
32
31
  init: HttpResponseDecoratedInit,
33
32
  ): Response {
34
- // Allow to mock the response type.
33
+ // Allow mocking the response type.
35
34
  if (init.type) {
36
35
  Object.defineProperty(response, 'type', {
37
36
  value: init.type,
@@ -52,25 +51,11 @@ export function decorateResponse(
52
51
  enumerable: false,
53
52
  writable: false,
54
53
  })
55
-
56
- // Cookie forwarding is only relevant in the browser.
57
- if (typeof document !== 'undefined') {
58
- // Write the mocked response cookies to the document.
59
- // Use `headers-polyfill` to get the Set-Cookie header value correctly.
60
- // This is an alternative until TypeScript 5.2
61
- // and Node.js v20 become the minimum supported version
62
- // and getSetCookie in Headers can be used directly.
63
- const responseCookiePairs = HeadersPolyfill.prototype.getSetCookie.call(
64
- init.headers,
65
- )
66
-
67
- for (const cookieString of responseCookiePairs) {
68
- // No need to parse the cookie headers because it's defined
69
- // as the valid cookie string to begin with.
70
- document.cookie = cookieString
71
- }
72
- }
73
54
  }
74
55
 
75
56
  return response
76
57
  }
58
+
59
+ export function getRawSetCookie(response: Response): string | undefined {
60
+ return Reflect.get(response, kSetCookie)
61
+ }
@@ -0,0 +1,28 @@
1
+ import { invariant } from 'outvariant'
2
+ import type { AnyHandler } from '../../experimental/handlers-controller'
3
+
4
+ const kSiblingHandlers = Symbol('kSiblingHandlers')
5
+
6
+ export function attachSiblingHandlers<T extends AnyHandler>(
7
+ owner: T,
8
+ siblings: Array<AnyHandler>,
9
+ ): T {
10
+ invariant(
11
+ getSiblingHandlers(owner).length === 0,
12
+ 'Failed to merge handlers: the owner "%s" handler is already merged',
13
+ owner.kind,
14
+ )
15
+
16
+ Object.defineProperty(owner, kSiblingHandlers, {
17
+ value: siblings,
18
+ enumerable: false,
19
+ writable: false,
20
+ configurable: false,
21
+ })
22
+
23
+ return owner
24
+ }
25
+
26
+ export function getSiblingHandlers(owner: AnyHandler): Array<AnyHandler> {
27
+ return Reflect.get(owner, kSiblingHandlers) || []
28
+ }
@@ -1,5 +1,5 @@
1
1
  import { cookieStore } from '../cookieStore'
2
- import { kSetCookie } from '../HttpResponse/decorators'
2
+ import { getRawSetCookie } from '../HttpResponse/decorators'
3
3
 
4
4
  export async function storeResponseCookies(
5
5
  request: Request,
@@ -7,9 +7,7 @@ export async function storeResponseCookies(
7
7
  ): Promise<void> {
8
8
  // Grab the raw "Set-Cookie" response header provided
9
9
  // in the HeadersInit for this mocked response.
10
- const responseCookies = Reflect.get(response, kSetCookie) as
11
- | string
12
- | undefined
10
+ const responseCookies = getRawSetCookie(response)
13
11
 
14
12
  if (responseCookies) {
15
13
  await cookieStore.setCookie(responseCookies, request.url)