msw 2.1.6 → 2.2.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 (44) hide show
  1. package/cli/init.js +9 -13
  2. package/lib/browser/index.js +29 -24
  3. package/lib/browser/index.js.map +1 -1
  4. package/lib/browser/index.mjs +29 -24
  5. package/lib/browser/index.mjs.map +1 -1
  6. package/lib/core/HttpResponse.js +14 -1
  7. package/lib/core/HttpResponse.js.map +1 -1
  8. package/lib/core/HttpResponse.mjs +14 -1
  9. package/lib/core/HttpResponse.mjs.map +1 -1
  10. package/lib/core/SetupApi.d.mts +15 -3
  11. package/lib/core/SetupApi.d.ts +15 -3
  12. package/lib/core/SetupApi.js +26 -8
  13. package/lib/core/SetupApi.js.map +1 -1
  14. package/lib/core/SetupApi.mjs +26 -8
  15. package/lib/core/SetupApi.mjs.map +1 -1
  16. package/lib/iife/index.js +65 -33
  17. package/lib/iife/index.js.map +1 -1
  18. package/lib/mockServiceWorker.js +1 -1
  19. package/lib/native/index.d.mts +10 -5
  20. package/lib/native/index.d.ts +10 -5
  21. package/lib/native/index.js +12 -8
  22. package/lib/native/index.js.map +1 -1
  23. package/lib/native/index.mjs +12 -8
  24. package/lib/native/index.mjs.map +1 -1
  25. package/lib/node/index.d.mts +26 -4
  26. package/lib/node/index.d.ts +26 -4
  27. package/lib/node/index.js +62 -13
  28. package/lib/node/index.js.map +1 -1
  29. package/lib/node/index.mjs +62 -13
  30. package/lib/node/index.mjs.map +1 -1
  31. package/package.json +16 -8
  32. package/src/browser/setupWorker/glossary.ts +1 -1
  33. package/src/browser/setupWorker/setupWorker.ts +3 -11
  34. package/src/browser/setupWorker/start/createFallbackRequestListener.ts +1 -1
  35. package/src/browser/setupWorker/start/createRequestListener.ts +1 -1
  36. package/src/browser/setupWorker/start/createResponseListener.ts +13 -0
  37. package/src/core/HttpResponse.test.ts +34 -3
  38. package/src/core/HttpResponse.ts +24 -1
  39. package/src/core/SetupApi.ts +33 -9
  40. package/src/native/index.ts +8 -4
  41. package/src/node/SetupServerApi.ts +64 -95
  42. package/src/node/SetupServerCommonApi.ts +116 -0
  43. package/src/node/glossary.ts +17 -3
  44. package/src/node/setupServer.ts +3 -10
@@ -1,113 +1,82 @@
1
- import {
2
- BatchInterceptor,
3
- HttpRequestEventMap,
4
- Interceptor,
5
- InterceptorReadyState,
6
- } from '@mswjs/interceptors'
7
- import { invariant } from 'outvariant'
8
- import { SetupApi } from '~/core/SetupApi'
9
- import { RequestHandler } from '~/core/handlers/RequestHandler'
10
- import { LifeCycleEventsMap, SharedOptions } from '~/core/sharedOptions'
11
- import { RequiredDeep } from '~/core/typeUtils'
12
- import { handleRequest } from '~/core/utils/handleRequest'
13
- import { devUtils } from '~/core/utils/internal/devUtils'
14
- import { mergeRight } from '~/core/utils/internal/mergeRight'
15
- import { SetupServer } from './glossary'
1
+ import { AsyncLocalStorage } from 'node:async_hooks'
2
+ import { ClientRequestInterceptor } from '@mswjs/interceptors/ClientRequest'
3
+ import { XMLHttpRequestInterceptor } from '@mswjs/interceptors/XMLHttpRequest'
4
+ import { FetchInterceptor } from '@mswjs/interceptors/fetch'
5
+ import { HandlersController } from '~/core/SetupApi'
6
+ import type { RequestHandler } from '~/core/handlers/RequestHandler'
7
+ import type { SetupServer } from './glossary'
8
+ import { SetupServerCommonApi } from './SetupServerCommonApi'
16
9
 
17
- const DEFAULT_LISTEN_OPTIONS: RequiredDeep<SharedOptions> = {
18
- onUnhandledRequest: 'warn',
19
- }
20
-
21
- export class SetupServerApi
22
- extends SetupApi<LifeCycleEventsMap>
23
- implements SetupServer
24
- {
25
- protected readonly interceptor: BatchInterceptor<
26
- Array<Interceptor<HttpRequestEventMap>>,
27
- HttpRequestEventMap
28
- >
29
- private resolvedOptions: RequiredDeep<SharedOptions>
10
+ const store = new AsyncLocalStorage<RequestHandlersContext>()
30
11
 
31
- constructor(
32
- interceptors: Array<{
33
- new (): Interceptor<HttpRequestEventMap>
34
- }>,
35
- ...handlers: Array<RequestHandler>
36
- ) {
37
- super(...handlers)
12
+ type RequestHandlersContext = {
13
+ initialHandlers: Array<RequestHandler>
14
+ handlers: Array<RequestHandler>
15
+ }
38
16
 
39
- this.interceptor = new BatchInterceptor({
40
- name: 'setup-server',
41
- interceptors: interceptors.map((Interceptor) => new Interceptor()),
42
- })
43
- this.resolvedOptions = {} as RequiredDeep<SharedOptions>
17
+ /**
18
+ * A handlers controller that utilizes `AsyncLocalStorage` in Node.js
19
+ * to prevent the request handlers list from being a shared state
20
+ * across mutliple tests.
21
+ */
22
+ class AsyncHandlersController implements HandlersController {
23
+ private rootContext: RequestHandlersContext
44
24
 
45
- this.init()
25
+ constructor(initialHandlers: Array<RequestHandler>) {
26
+ this.rootContext = { initialHandlers, handlers: [] }
46
27
  }
47
28
 
48
- /**
49
- * Subscribe to all requests that are using the interceptor object
50
- */
51
- private init(): void {
52
- this.interceptor.on('request', async ({ request, requestId }) => {
53
- const response = await handleRequest(
54
- request,
55
- requestId,
56
- this.currentHandlers,
57
- this.resolvedOptions,
58
- this.emitter,
59
- )
60
-
61
- if (response) {
62
- request.respondWith(response)
63
- }
29
+ get context(): RequestHandlersContext {
30
+ return store.getStore() || this.rootContext
31
+ }
64
32
 
65
- return
66
- })
33
+ public prepend(runtimeHandlers: Array<RequestHandler>) {
34
+ this.context.handlers.unshift(...runtimeHandlers)
35
+ }
67
36
 
68
- this.interceptor.on(
69
- 'response',
70
- ({ response, isMockedResponse, request, requestId }) => {
71
- this.emitter.emit(
72
- isMockedResponse ? 'response:mocked' : 'response:bypass',
73
- {
74
- response,
75
- request,
76
- requestId,
77
- },
78
- )
79
- },
80
- )
37
+ public reset(nextHandlers: Array<RequestHandler>) {
38
+ const context = this.context
39
+ context.handlers = []
40
+ context.initialHandlers =
41
+ nextHandlers.length > 0 ? nextHandlers : context.initialHandlers
81
42
  }
82
43
 
83
- public listen(options: Partial<SharedOptions> = {}): void {
84
- this.resolvedOptions = mergeRight(
85
- DEFAULT_LISTEN_OPTIONS,
86
- options,
87
- ) as RequiredDeep<SharedOptions>
44
+ public currentHandlers(): Array<RequestHandler> {
45
+ const { initialHandlers, handlers } = this.context
46
+ return handlers.concat(initialHandlers)
47
+ }
48
+ }
88
49
 
89
- // Apply the interceptor when starting the server.
90
- this.interceptor.apply()
50
+ export class SetupServerApi
51
+ extends SetupServerCommonApi
52
+ implements SetupServer
53
+ {
54
+ constructor(handlers: Array<RequestHandler>) {
55
+ super(
56
+ [ClientRequestInterceptor, XMLHttpRequestInterceptor, FetchInterceptor],
57
+ handlers,
58
+ )
91
59
 
92
- this.subscriptions.push(() => {
93
- this.interceptor.dispose()
94
- })
60
+ this.handlersController = new AsyncHandlersController(handlers)
61
+ }
95
62
 
96
- // Assert that the interceptor has been applied successfully.
97
- // Also guards us from forgetting to call "interceptor.apply()"
98
- // as a part of the "listen" method.
99
- invariant(
100
- [InterceptorReadyState.APPLYING, InterceptorReadyState.APPLIED].includes(
101
- this.interceptor.readyState,
102
- ),
103
- devUtils.formatMessage(
104
- 'Failed to start "setupServer": the interceptor failed to apply. This is likely an issue with the library and you should report it at "%s".',
105
- ),
106
- 'https://github.com/mswjs/msw/issues/new/choose',
107
- )
63
+ public boundary<Fn extends (...args: Array<any>) => unknown>(
64
+ callback: Fn,
65
+ ): (...args: Parameters<Fn>) => ReturnType<Fn> {
66
+ return (...args: Parameters<Fn>): ReturnType<Fn> => {
67
+ return store.run<any, any>(
68
+ {
69
+ initialHandlers: this.handlersController.currentHandlers(),
70
+ handlers: [],
71
+ },
72
+ callback,
73
+ ...args,
74
+ )
75
+ }
108
76
  }
109
77
 
110
78
  public close(): void {
111
- this.dispose()
79
+ super.close()
80
+ store.disable()
112
81
  }
113
82
  }
@@ -0,0 +1,116 @@
1
+ /**
2
+ * @note This API is extended by both "msw/node" and "msw/native"
3
+ * so be minding about the things you import!
4
+ */
5
+ import type { RequiredDeep } from 'type-fest'
6
+ import { invariant } from 'outvariant'
7
+ import {
8
+ BatchInterceptor,
9
+ InterceptorReadyState,
10
+ type HttpRequestEventMap,
11
+ type Interceptor,
12
+ } from '@mswjs/interceptors'
13
+ import type { LifeCycleEventsMap, SharedOptions } from '~/core/sharedOptions'
14
+ import { SetupApi } from '~/core/SetupApi'
15
+ import { handleRequest } from '~/core/utils/handleRequest'
16
+ import type { RequestHandler } from '~/core/handlers/RequestHandler'
17
+ import { mergeRight } from '~/core/utils/internal/mergeRight'
18
+ import { devUtils } from '~/core/utils/internal/devUtils'
19
+ import type { SetupServerCommon } from './glossary'
20
+
21
+ export const DEFAULT_LISTEN_OPTIONS: RequiredDeep<SharedOptions> = {
22
+ onUnhandledRequest: 'warn',
23
+ }
24
+
25
+ export class SetupServerCommonApi
26
+ extends SetupApi<LifeCycleEventsMap>
27
+ implements SetupServerCommon
28
+ {
29
+ protected readonly interceptor: BatchInterceptor<
30
+ Array<Interceptor<HttpRequestEventMap>>,
31
+ HttpRequestEventMap
32
+ >
33
+ private resolvedOptions: RequiredDeep<SharedOptions>
34
+
35
+ constructor(
36
+ interceptors: Array<{ new (): Interceptor<HttpRequestEventMap> }>,
37
+ handlers: Array<RequestHandler>,
38
+ ) {
39
+ super(...handlers)
40
+
41
+ this.interceptor = new BatchInterceptor({
42
+ name: 'setup-server',
43
+ interceptors: interceptors.map((Interceptor) => new Interceptor()),
44
+ })
45
+
46
+ this.resolvedOptions = {} as RequiredDeep<SharedOptions>
47
+
48
+ this.init()
49
+ }
50
+
51
+ /**
52
+ * Subscribe to all requests that are using the interceptor object
53
+ */
54
+ private init(): void {
55
+ this.interceptor.on('request', async ({ request, requestId }) => {
56
+ const response = await handleRequest(
57
+ request,
58
+ requestId,
59
+ this.handlersController.currentHandlers(),
60
+ this.resolvedOptions,
61
+ this.emitter,
62
+ )
63
+
64
+ if (response) {
65
+ request.respondWith(response)
66
+ }
67
+
68
+ return
69
+ })
70
+
71
+ this.interceptor.on(
72
+ 'response',
73
+ ({ response, isMockedResponse, request, requestId }) => {
74
+ this.emitter.emit(
75
+ isMockedResponse ? 'response:mocked' : 'response:bypass',
76
+ {
77
+ response,
78
+ request,
79
+ requestId,
80
+ },
81
+ )
82
+ },
83
+ )
84
+ }
85
+
86
+ public listen(options: Partial<SharedOptions> = {}): void {
87
+ this.resolvedOptions = mergeRight(
88
+ DEFAULT_LISTEN_OPTIONS,
89
+ options,
90
+ ) as RequiredDeep<SharedOptions>
91
+
92
+ // Apply the interceptor when starting the server.
93
+ this.interceptor.apply()
94
+
95
+ this.subscriptions.push(() => {
96
+ this.interceptor.dispose()
97
+ })
98
+
99
+ // Assert that the interceptor has been applied successfully.
100
+ // Also guards us from forgetting to call "interceptor.apply()"
101
+ // as a part of the "listen" method.
102
+ invariant(
103
+ [InterceptorReadyState.APPLYING, InterceptorReadyState.APPLIED].includes(
104
+ this.interceptor.readyState,
105
+ ),
106
+ devUtils.formatMessage(
107
+ 'Failed to start "setupServer": the interceptor failed to apply. This is likely an issue with the library and you should report it at "%s".',
108
+ ),
109
+ 'https://github.com/mswjs/msw/issues/new/choose',
110
+ )
111
+ }
112
+
113
+ public close(): void {
114
+ this.dispose()
115
+ }
116
+ }
@@ -1,15 +1,15 @@
1
1
  import type { PartialDeep } from 'type-fest'
2
- import {
2
+ import type {
3
3
  RequestHandler,
4
4
  RequestHandlerDefaultInfo,
5
5
  } from '~/core/handlers/RequestHandler'
6
- import {
6
+ import type {
7
7
  LifeCycleEventEmitter,
8
8
  LifeCycleEventsMap,
9
9
  SharedOptions,
10
10
  } from '~/core/sharedOptions'
11
11
 
12
- export interface SetupServer {
12
+ export interface SetupServerCommon {
13
13
  /**
14
14
  * Starts requests interception based on the previously provided request handlers.
15
15
  *
@@ -60,3 +60,17 @@ export interface SetupServer {
60
60
  */
61
61
  events: LifeCycleEventEmitter<LifeCycleEventsMap>
62
62
  }
63
+
64
+ export interface SetupServer extends SetupServerCommon {
65
+ /**
66
+ * Wraps the given function in a boundary. Any changes to the
67
+ * network behavior (e.g. adding runtime request handlers via
68
+ * `server.use()`) will be scoped to this boundary only.
69
+ * @param callback A function to run (e.g. a test)
70
+ *
71
+ * @see {@link https://mswjs.io/docs/api/setup-server/boundary `server.boundary()` API reference}
72
+ */
73
+ boundary<Fn extends (...args: Array<any>) => unknown>(
74
+ callback: Fn,
75
+ ): (...args: Parameters<Fn>) => ReturnType<Fn>
76
+ }
@@ -1,9 +1,5 @@
1
- import { ClientRequestInterceptor } from '@mswjs/interceptors/ClientRequest'
2
- import { XMLHttpRequestInterceptor } from '@mswjs/interceptors/XMLHttpRequest'
3
- import { FetchInterceptor } from '@mswjs/interceptors/fetch'
4
- import { RequestHandler } from '~/core/handlers/RequestHandler'
1
+ import type { RequestHandler } from '~/core/handlers/RequestHandler'
5
2
  import { SetupServerApi } from './SetupServerApi'
6
- import { SetupServer } from './glossary'
7
3
 
8
4
  /**
9
5
  * Sets up a requests interception in Node.js with the given request handlers.
@@ -13,9 +9,6 @@ import { SetupServer } from './glossary'
13
9
  */
14
10
  export const setupServer = (
15
11
  ...handlers: Array<RequestHandler>
16
- ): SetupServer => {
17
- return new SetupServerApi(
18
- [ClientRequestInterceptor, XMLHttpRequestInterceptor, FetchInterceptor],
19
- ...handlers,
20
- )
12
+ ): SetupServerApi => {
13
+ return new SetupServerApi(handlers)
21
14
  }