Skip to content

Commit 720e665

Browse files
committed
fix: implement setupServer and SetupServerCommonApi
1 parent 30f32a8 commit 720e665

File tree

10 files changed

+256
-48
lines changed

10 files changed

+256
-48
lines changed

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -248,7 +248,7 @@
248248
"outvariant": "^1.4.3",
249249
"path-to-regexp": "^6.3.0",
250250
"picocolors": "^1.1.1",
251-
"rettime": "^0.7.0",
251+
"rettime": "^0.9.0",
252252
"statuses": "^2.0.2",
253253
"strict-event-emitter": "^0.5.1",
254254
"tough-cookie": "^6.0.0",

pnpm-lock.yaml

Lines changed: 5 additions & 5 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

src/core/index.ts

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -92,6 +92,13 @@ export { bypass } from './bypass'
9292
export { passthrough } from './passthrough'
9393
export { isCommonAssetRequest } from './isCommonAssetRequest'
9494

95+
export { defineNetwork, type NetworkApi } from './new/define-network'
96+
export {
97+
type AnyHandler,
98+
HandlersController,
99+
InMemoryHandlersController,
100+
} from './new/handlers-controller'
101+
95102
// Validate environmental globals before executing any code.
96103
// This ensures that the library gives user-friendly errors
97104
// when ran in the environments that require additional polyfills

src/core/new/compat.ts

Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,60 @@
1+
/**
2+
* Collection of helpers for briding the compatibility between the old and the new APIs.
3+
*/
4+
import { Emitter } from 'rettime'
5+
import {
6+
Emitter as LegacyEmitter,
7+
EventMap as LegacyEventMap,
8+
} from 'strict-event-emitter'
9+
import { type UnhandledRequestStrategy } from '~/core/utils/request/onUnhandledRequest'
10+
import {
11+
onUnhandledFrame,
12+
type UnhandledFrameCallback,
13+
} from './on-unhandled-frame'
14+
15+
export function toLegacyEmitter<EventMap extends LegacyEventMap>(
16+
emitter: Emitter<any>,
17+
): LegacyEmitter<EventMap> {
18+
const legacy = new LegacyEmitter<EventMap>()
19+
20+
legacy
21+
.addListener('newListener', (type, listener) => {
22+
emitter.on(type as any, (event) => listener(event.data))
23+
})
24+
.addListener('removeListener', (type, listener) => {
25+
emitter.removeListener(type as any, listener)
26+
})
27+
28+
return legacy
29+
}
30+
31+
export function fromLegacyOnUnhandledRequest(
32+
getLegacyValue: () => UnhandledRequestStrategy | undefined,
33+
): UnhandledFrameCallback {
34+
return ({ frame, defaults }) => {
35+
const legacyOnUnhandledRequestStrategy = getLegacyValue()
36+
37+
if (legacyOnUnhandledRequestStrategy === undefined) {
38+
return
39+
}
40+
41+
if (typeof legacyOnUnhandledRequestStrategy === 'function') {
42+
const request =
43+
frame.protocol === 'http'
44+
? frame.data.request
45+
: new Request(frame.data.connection.client.url, {
46+
headers: {
47+
connection: 'upgrade',
48+
upgrade: 'websocket',
49+
},
50+
})
51+
52+
return legacyOnUnhandledRequestStrategy(request, {
53+
warning: defaults.warn,
54+
error: defaults.error,
55+
})
56+
}
57+
58+
return onUnhandledFrame(frame, legacyOnUnhandledRequestStrategy)
59+
}
60+
}

src/core/new/define-network.ts

Lines changed: 9 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,5 @@
11
import { Emitter } from 'rettime'
22
import { type NetworkSource } from './sources/network-source'
3-
4-
import { pipeEvents } from '../utils/internal/pipeEvents'
53
import { type NetworkFrameResolutionContext } from './frames/network-frame'
64
import { onUnhandledFrame, UnhandledFrameHandle } from './on-unhandled-frame'
75
import { isHandlerKind } from '../utils/internal/isHandlerKind'
@@ -23,13 +21,14 @@ export interface DefineNetworkOptions {
2321
export interface NetworkApi extends NetworkHandlersApi {
2422
enable: () => Promise<void>
2523
disable: () => Promise<void>
24+
events: Emitter<any>
2625
}
2726

2827
export interface NetworkHandlersApi {
2928
use: (...handlers: Array<AnyHandler>) => void
3029
resetHandlers: (...handlers: Array<AnyHandler>) => void
3130
restoreHandlers: () => void
32-
listHandlers: () => void
31+
listHandlers: () => ReadonlyArray<AnyHandler>
3332
}
3433

3534
export function defineNetwork(options: DefineNetworkOptions): NetworkApi {
@@ -38,11 +37,17 @@ export function defineNetwork(options: DefineNetworkOptions): NetworkApi {
3837
options.controller || new InMemoryHandlersController(options.handlers || [])
3938

4039
return {
40+
events,
4141
async enable() {
4242
await Promise.all(
4343
options.sources.map(async (source) => {
4444
source.on('frame', async ({ data: frame }) => {
45-
pipeEvents(frame.events, events)
45+
/**
46+
* @fixme Rettime has trouble typing `.on` when the emitter
47+
* has a union of eventmap types. Add a type test and a runtime test
48+
* for this use case (piping events).
49+
*/
50+
frame.events.on((event) => events.emit(event))
4651

4752
const handlerPredicate =
4853
frame.protocol === 'http'

src/core/new/on-unhandled-frame.ts

Lines changed: 0 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,3 @@
1-
import { UnhandledRequestStrategy } from 'src/iife'
21
import { isCommonAssetRequest } from '../isCommonAssetRequest'
32
import { devUtils, InternalError } from '../utils/internal/devUtils'
43
import { type NetworkFrame } from './sources/network-source'
@@ -76,34 +75,3 @@ export async function onUnhandledFrame(
7675

7776
return applyStrategy(handle)
7877
}
79-
80-
export function fromLegacyOnUnhandledRequest(
81-
getLegacyValue: () => UnhandledRequestStrategy | undefined,
82-
): UnhandledFrameCallback {
83-
return ({ frame, defaults }) => {
84-
const legacyOnUnhandledRequestStrategy = getLegacyValue()
85-
86-
if (legacyOnUnhandledRequestStrategy === undefined) {
87-
return
88-
}
89-
90-
if (typeof legacyOnUnhandledRequestStrategy === 'function') {
91-
const request =
92-
frame.protocol === 'http'
93-
? frame.data.request
94-
: new Request(frame.data.connection.client.url, {
95-
headers: {
96-
connection: 'upgrade',
97-
upgrade: 'websocket',
98-
},
99-
})
100-
101-
return legacyOnUnhandledRequestStrategy(request, {
102-
warning: defaults.warn,
103-
error: defaults.error,
104-
})
105-
}
106-
107-
return onUnhandledFrame(frame, legacyOnUnhandledRequestStrategy)
108-
}
109-
}
Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
import { AsyncLocalStorage } from 'node:async_hooks'
2+
import { AnyHandler, HandlersController } from '~/core/index'
3+
4+
export interface AsyncHandlersControllerContext {
5+
initialHandlers: Array<AnyHandler>
6+
handlers: Array<AnyHandler>
7+
}
8+
9+
export class AsyncHandlersController extends HandlersController {
10+
public context: AsyncLocalStorage<AsyncHandlersControllerContext>
11+
#fallbackContext: AsyncHandlersControllerContext
12+
13+
constructor(initialHandlers: Array<AnyHandler>) {
14+
super(initialHandlers)
15+
16+
this.context = new AsyncLocalStorage()
17+
this.#fallbackContext = {
18+
initialHandlers: [...initialHandlers],
19+
handlers: [],
20+
}
21+
}
22+
23+
get currentHandlers() {
24+
const { initialHandlers, handlers } = this.#getContext()
25+
return [...initialHandlers, ...handlers]
26+
}
27+
28+
public use(nextHandlers: Array<AnyHandler>): void {
29+
super.use(nextHandlers)
30+
31+
this.#getContext().handlers.unshift(...nextHandlers)
32+
}
33+
34+
public reset(nextHandlers: Array<AnyHandler>): void {
35+
super.reset(nextHandlers)
36+
37+
const context = this.#getContext()
38+
context.handlers = []
39+
40+
if (nextHandlers.length > 0) {
41+
context.initialHandlers = [...nextHandlers]
42+
}
43+
}
44+
45+
#getContext() {
46+
return this.context.getStore() || this.#fallbackContext
47+
}
48+
}

src/node/glossary.ts

Lines changed: 7 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,19 +1,20 @@
11
import type { PartialDeep } from 'type-fest'
2-
import type { RequestHandler } from '~/core/handlers/RequestHandler'
3-
import type { WebSocketHandler } from '~/core/handlers/WebSocketHandler'
2+
import { AnyHandler } from '~/core'
43
import type {
54
LifeCycleEventEmitter,
65
LifeCycleEventsMap,
76
SharedOptions,
87
} from '~/core/sharedOptions'
98

9+
export interface ListenOptions extends SharedOptions {}
10+
1011
export interface SetupServerCommon {
1112
/**
1213
* Starts requests interception based on the previously provided request handlers.
1314
*
1415
* @see {@link https://mswjs.io/docs/api/setup-server/listen `server.listen()` API reference}
1516
*/
16-
listen(options?: PartialDeep<SharedOptions>): void
17+
listen(options?: PartialDeep<ListenOptions>): void
1718

1819
/**
1920
* Stops requests interception by restoring all augmented modules.
@@ -27,7 +28,7 @@ export interface SetupServerCommon {
2728
*
2829
* @see {@link https://mswjs.io/docs/api/setup-server/use `server.use()` API reference}
2930
*/
30-
use(...handlers: Array<RequestHandler | WebSocketHandler>): void
31+
use(...handlers: Array<AnyHandler>): void
3132

3233
/**
3334
* Marks all request handlers that respond using `res.once()` as unused.
@@ -41,14 +42,14 @@ export interface SetupServerCommon {
4142
*
4243
* @see {@link https://mswjs.io/docs/api/setup-server/reset-handlers `server.reset-handlers()` API reference}
4344
*/
44-
resetHandlers(...nextHandlers: Array<RequestHandler | WebSocketHandler>): void
45+
resetHandlers(...nextHandlers: Array<AnyHandler>): void
4546

4647
/**
4748
* Returns a readonly list of currently active request handlers.
4849
*
4950
* @see {@link https://mswjs.io/docs/api/setup-server/list-handlers `server.listHandlers()` API reference}
5051
*/
51-
listHandlers(): ReadonlyArray<RequestHandler | WebSocketHandler>
52+
listHandlers(): ReadonlyArray<AnyHandler>
5253

5354
/**
5455
* Life-cycle events.

src/node/setup-server-common.ts

Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,65 @@
1+
import type { PartialDeep } from 'type-fest'
2+
import { Interceptor } from '@mswjs/interceptors'
3+
import { ListenOptions, SetupServerCommon } from './glossary'
4+
import {
5+
AnyHandler,
6+
defineNetwork,
7+
LifeCycleEventsMap,
8+
NetworkApi,
9+
} from '~/core'
10+
import { HandlersController } from '~/core/new/handlers-controller'
11+
import { InterceptorSource } from '~/core/new/sources/interceptor-source'
12+
import {
13+
fromLegacyOnUnhandledRequest,
14+
toLegacyEmitter,
15+
} from '~/core/new/compat'
16+
17+
export class SetupServerCommonApi implements SetupServerCommon {
18+
#listenOptions?: PartialDeep<ListenOptions>
19+
20+
protected network: NetworkApi
21+
22+
constructor(
23+
interceptors: Array<Interceptor<any>>,
24+
handlers: Array<AnyHandler>,
25+
handlersController?: HandlersController,
26+
) {
27+
this.network = defineNetwork({
28+
sources: [new InterceptorSource({ interceptors })],
29+
handlers,
30+
controller: handlersController,
31+
onUnhandledFrame: fromLegacyOnUnhandledRequest(() => {
32+
return this.#listenOptions?.onUnhandledRequest || 'warn'
33+
}),
34+
})
35+
}
36+
37+
get events() {
38+
return toLegacyEmitter<LifeCycleEventsMap>(this.network.events)
39+
}
40+
41+
public listen(options?: PartialDeep<ListenOptions>): void {
42+
this.#listenOptions = options
43+
this.network.enable()
44+
}
45+
46+
public use(...handlers: Array<AnyHandler>): void {
47+
this.network.use(...handlers)
48+
}
49+
50+
public resetHandlers(...nextHandlers: Array<AnyHandler>): void {
51+
return this.network.resetHandlers(...nextHandlers)
52+
}
53+
54+
public restoreHandlers(): void {
55+
return this.network.restoreHandlers()
56+
}
57+
58+
public listHandlers(): ReadonlyArray<AnyHandler> {
59+
return this.network.listHandlers()
60+
}
61+
62+
public close(): void {
63+
this.network.disable()
64+
}
65+
}

0 commit comments

Comments
 (0)