msw 2.10.5 → 2.11.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 (108) hide show
  1. package/lib/browser/index.js.map +1 -1
  2. package/lib/browser/index.mjs.map +1 -1
  3. package/lib/core/{HttpResponse-CC5tPhLa.d.mts → HttpResponse-B4YmE-GJ.d.mts} +22 -8
  4. package/lib/core/{HttpResponse-CKZrrwKE.d.ts → HttpResponse-BbwAqLE_.d.ts} +22 -8
  5. package/lib/core/HttpResponse.d.mts +1 -1
  6. package/lib/core/HttpResponse.d.ts +1 -1
  7. package/lib/core/HttpResponse.js.map +1 -1
  8. package/lib/core/HttpResponse.mjs.map +1 -1
  9. package/lib/core/SetupApi.d.mts +1 -1
  10. package/lib/core/SetupApi.d.ts +1 -1
  11. package/lib/core/getResponse.d.mts +1 -1
  12. package/lib/core/getResponse.d.ts +1 -1
  13. package/lib/core/graphql.d.mts +2 -2
  14. package/lib/core/graphql.d.ts +2 -2
  15. package/lib/core/graphql.js +2 -8
  16. package/lib/core/graphql.js.map +1 -1
  17. package/lib/core/graphql.mjs +2 -8
  18. package/lib/core/graphql.mjs.map +1 -1
  19. package/lib/core/handlers/GraphQLHandler.d.mts +1 -1
  20. package/lib/core/handlers/GraphQLHandler.d.ts +1 -1
  21. package/lib/core/handlers/GraphQLHandler.js +36 -9
  22. package/lib/core/handlers/GraphQLHandler.js.map +1 -1
  23. package/lib/core/handlers/GraphQLHandler.mjs +36 -9
  24. package/lib/core/handlers/GraphQLHandler.mjs.map +1 -1
  25. package/lib/core/handlers/HttpHandler.d.mts +15 -5
  26. package/lib/core/handlers/HttpHandler.d.ts +15 -5
  27. package/lib/core/handlers/HttpHandler.js +21 -10
  28. package/lib/core/handlers/HttpHandler.js.map +1 -1
  29. package/lib/core/handlers/HttpHandler.mjs +21 -10
  30. package/lib/core/handlers/HttpHandler.mjs.map +1 -1
  31. package/lib/core/handlers/RequestHandler.d.mts +1 -1
  32. package/lib/core/handlers/RequestHandler.d.ts +1 -1
  33. package/lib/core/handlers/RequestHandler.js +1 -1
  34. package/lib/core/handlers/RequestHandler.js.map +1 -1
  35. package/lib/core/handlers/RequestHandler.mjs +1 -1
  36. package/lib/core/handlers/RequestHandler.mjs.map +1 -1
  37. package/lib/core/http.d.mts +4 -4
  38. package/lib/core/http.d.ts +4 -4
  39. package/lib/core/http.js +2 -2
  40. package/lib/core/http.js.map +1 -1
  41. package/lib/core/http.mjs +2 -2
  42. package/lib/core/http.mjs.map +1 -1
  43. package/lib/core/index.d.mts +2 -2
  44. package/lib/core/index.d.ts +2 -2
  45. package/lib/core/index.js.map +1 -1
  46. package/lib/core/index.mjs.map +1 -1
  47. package/lib/core/passthrough.d.mts +1 -1
  48. package/lib/core/passthrough.d.ts +1 -1
  49. package/lib/core/utils/HttpResponse/decorators.d.mts +1 -1
  50. package/lib/core/utils/HttpResponse/decorators.d.ts +1 -1
  51. package/lib/core/utils/cookieStore.d.mts +10 -2
  52. package/lib/core/utils/cookieStore.d.ts +10 -2
  53. package/lib/core/utils/cookieStore.js +49 -146
  54. package/lib/core/utils/cookieStore.js.map +1 -1
  55. package/lib/core/utils/cookieStore.mjs +53 -136
  56. package/lib/core/utils/cookieStore.mjs.map +1 -1
  57. package/lib/core/utils/executeHandlers.d.mts +1 -1
  58. package/lib/core/utils/executeHandlers.d.ts +1 -1
  59. package/lib/core/utils/handleRequest.d.mts +1 -1
  60. package/lib/core/utils/handleRequest.d.ts +1 -1
  61. package/lib/core/utils/handleRequest.js +1 -1
  62. package/lib/core/utils/handleRequest.js.map +1 -1
  63. package/lib/core/utils/handleRequest.mjs +1 -1
  64. package/lib/core/utils/handleRequest.mjs.map +1 -1
  65. package/lib/core/utils/internal/isHandlerKind.d.mts +1 -1
  66. package/lib/core/utils/internal/isHandlerKind.d.ts +1 -1
  67. package/lib/core/utils/internal/parseGraphQLRequest.d.mts +1 -1
  68. package/lib/core/utils/internal/parseGraphQLRequest.d.ts +1 -1
  69. package/lib/core/utils/internal/parseMultipartData.d.mts +1 -1
  70. package/lib/core/utils/internal/parseMultipartData.d.ts +1 -1
  71. package/lib/core/utils/internal/requestHandlerUtils.d.mts +1 -1
  72. package/lib/core/utils/internal/requestHandlerUtils.d.ts +1 -1
  73. package/lib/core/utils/request/getRequestCookies.js +1 -1
  74. package/lib/core/utils/request/getRequestCookies.js.map +1 -1
  75. package/lib/core/utils/request/getRequestCookies.mjs +1 -1
  76. package/lib/core/utils/request/getRequestCookies.mjs.map +1 -1
  77. package/lib/core/utils/request/storeResponseCookies.d.mts +1 -1
  78. package/lib/core/utils/request/storeResponseCookies.d.ts +1 -1
  79. package/lib/core/utils/request/storeResponseCookies.js +2 -2
  80. package/lib/core/utils/request/storeResponseCookies.js.map +1 -1
  81. package/lib/core/utils/request/storeResponseCookies.mjs +2 -2
  82. package/lib/core/utils/request/storeResponseCookies.mjs.map +1 -1
  83. package/lib/core/ws/handleWebSocketEvent.d.mts +1 -1
  84. package/lib/core/ws/handleWebSocketEvent.d.ts +1 -1
  85. package/lib/iife/index.js +2661 -12110
  86. package/lib/iife/index.js.map +1 -1
  87. package/lib/mockServiceWorker.js +1 -1
  88. package/lib/node/index.js.map +1 -1
  89. package/lib/node/index.mjs.map +1 -1
  90. package/package.json +4 -4
  91. package/src/browser/setupWorker/setupWorker.ts +1 -1
  92. package/src/core/HttpResponse.ts +3 -3
  93. package/src/core/graphql.ts +8 -12
  94. package/src/core/handlers/GraphQLHandler.test.ts +86 -51
  95. package/src/core/handlers/GraphQLHandler.ts +81 -17
  96. package/src/core/handlers/HttpHandler.test.ts +60 -30
  97. package/src/core/handlers/HttpHandler.ts +61 -12
  98. package/src/core/handlers/RequestHandler.ts +2 -2
  99. package/src/core/http.ts +5 -5
  100. package/src/core/index.ts +3 -0
  101. package/src/core/utils/cookieStore.ts +56 -198
  102. package/src/core/utils/handleRequest.test.ts +84 -0
  103. package/src/core/utils/handleRequest.ts +1 -1
  104. package/src/core/utils/request/getRequestCookies.ts +1 -1
  105. package/src/core/utils/request/storeResponseCookies.ts +3 -3
  106. package/src/core/utils/request/toPublicUrl.test.ts +1 -1
  107. package/src/core/ws/WebSocketClientManager.test.ts +1 -1
  108. package/src/node/SetupServerApi.ts +1 -1
@@ -21,15 +21,15 @@ import { toPublicUrl } from '../utils/request/toPublicUrl'
21
21
  import { devUtils } from '../utils/internal/devUtils'
22
22
  import { getAllRequestCookies } from '../utils/request/getRequestCookies'
23
23
 
24
- export type ExpectedOperationTypeNode = OperationTypeNode | 'all'
24
+ export type GraphQLOperationType = OperationTypeNode | 'all'
25
25
  export type GraphQLHandlerNameSelector = DocumentNode | RegExp | string
26
26
 
27
27
  export type GraphQLQuery = Record<string, any> | null
28
28
  export type GraphQLVariables = Record<string, any>
29
29
 
30
30
  export interface GraphQLHandlerInfo extends RequestHandlerDefaultInfo {
31
- operationType: ExpectedOperationTypeNode
32
- operationName: GraphQLHandlerNameSelector
31
+ operationType: GraphQLOperationType
32
+ operationName: GraphQLHandlerNameSelector | GraphQLCustomPredicate
33
33
  }
34
34
 
35
35
  export type GraphQLRequestParsedResult = {
@@ -77,6 +77,21 @@ export type GraphQLResponseBody<BodyType extends DefaultBodyType> =
77
77
  | null
78
78
  | undefined
79
79
 
80
+ export type GraphQLCustomPredicate = (args: {
81
+ request: Request
82
+ query: string
83
+ operationType: GraphQLOperationType
84
+ operationName: string
85
+ variables: GraphQLVariables
86
+ cookies: Record<string, string>
87
+ }) => GraphQLCustomPredicateResult | Promise<GraphQLCustomPredicateResult>
88
+
89
+ export type GraphQLCustomPredicateResult = boolean | { matches: boolean }
90
+
91
+ export type GraphQLPredicate =
92
+ | GraphQLHandlerNameSelector
93
+ | GraphQLCustomPredicate
94
+
80
95
  export function isDocumentNode(
81
96
  value: DocumentNode | any,
82
97
  ): value is DocumentNode {
@@ -100,16 +115,16 @@ export class GraphQLHandler extends RequestHandler<
100
115
  >()
101
116
 
102
117
  constructor(
103
- operationType: ExpectedOperationTypeNode,
104
- operationName: GraphQLHandlerNameSelector,
118
+ operationType: GraphQLOperationType,
119
+ predicate: GraphQLPredicate,
105
120
  endpoint: Path,
106
121
  resolver: ResponseResolver<GraphQLResolverExtras<any>, any, any>,
107
122
  options?: RequestHandlerOptions,
108
123
  ) {
109
- let resolvedOperationName = operationName
124
+ let resolvedOperationName = predicate
110
125
 
111
- if (isDocumentNode(operationName)) {
112
- const parsedNode = parseDocumentNode(operationName)
126
+ if (isDocumentNode(resolvedOperationName)) {
127
+ const parsedNode = parseDocumentNode(resolvedOperationName)
113
128
 
114
129
  if (parsedNode.operationType !== operationType) {
115
130
  throw new Error(
@@ -126,10 +141,15 @@ export class GraphQLHandler extends RequestHandler<
126
141
  resolvedOperationName = parsedNode.operationName
127
142
  }
128
143
 
144
+ const displayOperationName =
145
+ typeof resolvedOperationName === 'function'
146
+ ? '[custom predicate]'
147
+ : resolvedOperationName
148
+
129
149
  const header =
130
150
  operationType === 'all'
131
151
  ? `${operationType} (origin: ${endpoint.toString()})`
132
- : `${operationType} ${resolvedOperationName} (origin: ${endpoint.toString()})`
152
+ : `${operationType}${displayOperationName ? ` ${displayOperationName}` : ''} (origin: ${endpoint.toString()})`
133
153
 
134
154
  super({
135
155
  info: {
@@ -175,7 +195,10 @@ export class GraphQLHandler extends RequestHandler<
175
195
  const cookies = getAllRequestCookies(args.request)
176
196
 
177
197
  if (!match.matches) {
178
- return { match, cookies }
198
+ return {
199
+ match,
200
+ cookies,
201
+ }
179
202
  }
180
203
 
181
204
  const parsedResult = await this.parseGraphQLRequestOrGetFromCache(
@@ -183,7 +206,10 @@ export class GraphQLHandler extends RequestHandler<
183
206
  )
184
207
 
185
208
  if (typeof parsedResult === 'undefined') {
186
- return { match, cookies }
209
+ return {
210
+ match,
211
+ cookies,
212
+ }
187
213
  }
188
214
 
189
215
  return {
@@ -196,10 +222,10 @@ export class GraphQLHandler extends RequestHandler<
196
222
  }
197
223
  }
198
224
 
199
- predicate(args: {
225
+ async predicate(args: {
200
226
  request: Request
201
227
  parsedResult: GraphQLRequestParsedResult
202
- }) {
228
+ }): Promise<boolean> {
203
229
  if (args.parsedResult.operationType === undefined) {
204
230
  return false
205
231
  }
@@ -218,10 +244,16 @@ Consider naming this operation or using "graphql.operation()" request handler to
218
244
  this.info.operationType === 'all' ||
219
245
  args.parsedResult.operationType === this.info.operationType
220
246
 
221
- const hasMatchingOperationName =
222
- this.info.operationName instanceof RegExp
223
- ? this.info.operationName.test(args.parsedResult.operationName || '')
224
- : args.parsedResult.operationName === this.info.operationName
247
+ /**
248
+ * Check if the operation name matches the outgoing GraphQL request.
249
+ * @note Unlike the HTTP handler, the custom predicate functions are invoked
250
+ * during predicate, not parsing, because GraphQL request parsing happens first,
251
+ * and non-GraphQL requests are filtered out automatically.
252
+ */
253
+ const hasMatchingOperationName = await this.matchOperationName({
254
+ request: args.request,
255
+ parsedResult: args.parsedResult,
256
+ })
225
257
 
226
258
  return (
227
259
  args.parsedResult.match.matches &&
@@ -230,12 +262,44 @@ Consider naming this operation or using "graphql.operation()" request handler to
230
262
  )
231
263
  }
232
264
 
265
+ private async matchOperationName(args: {
266
+ request: Request
267
+ parsedResult: GraphQLRequestParsedResult
268
+ }): Promise<boolean> {
269
+ if (typeof this.info.operationName === 'function') {
270
+ const customPredicateResult = await this.info.operationName({
271
+ request: args.request,
272
+ ...this.extendResolverArgs({
273
+ request: args.request,
274
+ parsedResult: args.parsedResult,
275
+ }),
276
+ })
277
+
278
+ /**
279
+ * @note Keep the { matches } signature in case we decide to support path parameters
280
+ * in GraphQL handlers. If that happens, the custom predicate would have to be moved
281
+ * to the parsing phase, the same as we have for the HttpHandler, and the user will
282
+ * have a possibility to return parsed path parameters from the custom predicate.
283
+ */
284
+ return typeof customPredicateResult === 'boolean'
285
+ ? customPredicateResult
286
+ : customPredicateResult.matches
287
+ }
288
+
289
+ if (this.info.operationName instanceof RegExp) {
290
+ return this.info.operationName.test(args.parsedResult.operationName || '')
291
+ }
292
+
293
+ return args.parsedResult.operationName === this.info.operationName
294
+ }
295
+
233
296
  protected extendResolverArgs(args: {
234
297
  request: Request
235
298
  parsedResult: GraphQLRequestParsedResult
236
299
  }) {
237
300
  return {
238
301
  query: args.parsedResult.query || '',
302
+ operationType: args.parsedResult.operationType!,
239
303
  operationName: args.parsedResult.operationName || '',
240
304
  variables: args.parsedResult.variables || {},
241
305
  cookies: args.parsedResult.cookies,
@@ -1,6 +1,4 @@
1
- /**
2
- * @vitest-environment jsdom
3
- */
1
+ // @vitest-environment jsdom
4
2
  import { createRequestId } from '@mswjs/interceptors'
5
3
  import { HttpHandler, HttpRequestResolverExtras } from './HttpHandler'
6
4
  import { HttpResponse } from '..'
@@ -13,7 +11,7 @@ const resolver: ResponseResolver<
13
11
  }
14
12
 
15
13
  describe('info', () => {
16
- test('exposes request handler information', () => {
14
+ it('exposes request handler information', () => {
17
15
  const handler = new HttpHandler('GET', '/user/:userId', resolver)
18
16
 
19
17
  expect(handler.info.header).toEqual('GET /user/:userId')
@@ -24,7 +22,7 @@ describe('info', () => {
24
22
  })
25
23
 
26
24
  describe('parse', () => {
27
- test('parses a URL given a matching request', async () => {
25
+ it('parses a URL given a matching request', async () => {
28
26
  const handler = new HttpHandler('GET', '/user/:userId', resolver)
29
27
  const request = new Request(new URL('/user/abc-123', location.href))
30
28
 
@@ -39,7 +37,7 @@ describe('parse', () => {
39
37
  })
40
38
  })
41
39
 
42
- test('parses a URL and ignores the request method', async () => {
40
+ it('parses a URL and ignores the request method', async () => {
43
41
  const handler = new HttpHandler('GET', '/user/:userId', resolver)
44
42
  const request = new Request(new URL('/user/def-456', location.href), {
45
43
  method: 'POST',
@@ -56,7 +54,7 @@ describe('parse', () => {
56
54
  })
57
55
  })
58
56
 
59
- test('returns negative match result given a non-matching request', async () => {
57
+ it('returns negative match result given a non-matching request', async () => {
60
58
  const handler = new HttpHandler('GET', '/user/:userId', resolver)
61
59
  const request = new Request(new URL('/login', location.href))
62
60
 
@@ -71,21 +69,21 @@ describe('parse', () => {
71
69
  })
72
70
 
73
71
  describe('predicate', () => {
74
- test('returns true given a matching request', async () => {
72
+ it('returns true given a matching request', async () => {
75
73
  const handler = new HttpHandler('POST', '/login', resolver)
76
74
  const request = new Request(new URL('/login', location.href), {
77
75
  method: 'POST',
78
76
  })
79
77
 
80
- expect(
78
+ await expect(
81
79
  handler.predicate({
82
80
  request,
83
81
  parsedResult: await handler.parse({ request }),
84
82
  }),
85
- ).toBe(true)
83
+ ).resolves.toBe(true)
86
84
  })
87
85
 
88
- test('respects RegExp as the request method', async () => {
86
+ it('supports RegExp as the request method', async () => {
89
87
  const handler = new HttpHandler(/.+/, '/login', resolver)
90
88
  const requests = [
91
89
  new Request(new URL('/login', location.href)),
@@ -94,30 +92,60 @@ describe('predicate', () => {
94
92
  ]
95
93
 
96
94
  for (const request of requests) {
97
- expect(
95
+ await expect(
98
96
  handler.predicate({
99
97
  request,
100
98
  parsedResult: await handler.parse({ request }),
101
99
  }),
102
- ).toBe(true)
100
+ ).resolves.toBe(true)
103
101
  }
104
102
  })
105
103
 
106
- test('returns false given a non-matching request', async () => {
104
+ it('returns false given a non-matching request', async () => {
107
105
  const handler = new HttpHandler('POST', '/login', resolver)
108
106
  const request = new Request(new URL('/user/abc-123', location.href))
109
107
 
110
- expect(
108
+ await expect(
111
109
  handler.predicate({
112
110
  request,
113
111
  parsedResult: await handler.parse({ request }),
114
112
  }),
115
- ).toBe(false)
113
+ ).resolves.toBe(false)
114
+ })
115
+
116
+ it('supports custom predicate function', async () => {
117
+ const handler = new HttpHandler(
118
+ 'GET',
119
+ ({ request }) => {
120
+ return new URL(request.url).searchParams.get('a') === '1'
121
+ },
122
+ resolver,
123
+ )
124
+
125
+ {
126
+ const request = new Request(new URL('/login?a=1', location.href))
127
+ await expect(
128
+ handler.predicate({
129
+ request,
130
+ parsedResult: await handler.parse({ request }),
131
+ }),
132
+ ).resolves.toBe(true)
133
+ }
134
+
135
+ {
136
+ const request = new Request(new URL('/login', location.href))
137
+ await expect(
138
+ handler.predicate({
139
+ request,
140
+ parsedResult: await handler.parse({ request }),
141
+ }),
142
+ ).resolves.toBe(false)
143
+ }
116
144
  })
117
145
  })
118
146
 
119
147
  describe('test', () => {
120
- test('returns true given a matching request', async () => {
148
+ it('returns true given a matching request', async () => {
121
149
  const handler = new HttpHandler('GET', '/user/:userId', resolver)
122
150
  const firstTest = await handler.test({
123
151
  request: new Request(new URL('/user/abc-123', location.href)),
@@ -130,7 +158,7 @@ describe('test', () => {
130
158
  expect(secondTest).toBe(true)
131
159
  })
132
160
 
133
- test('returns false given a non-matching request', async () => {
161
+ it('returns false given a non-matching request', async () => {
134
162
  const handler = new HttpHandler('GET', '/user/:userId', resolver)
135
163
  const firstTest = await handler.test({
136
164
  request: new Request(new URL('/login', location.href)),
@@ -149,7 +177,7 @@ describe('test', () => {
149
177
  })
150
178
 
151
179
  describe('run', () => {
152
- test('returns a mocked response given a matching request', async () => {
180
+ it('returns a mocked response given a matching request', async () => {
153
181
  const handler = new HttpHandler('GET', '/user/:userId', resolver)
154
182
  const request = new Request(new URL('/user/abc-123', location.href))
155
183
  const requestId = createRequestId()
@@ -169,10 +197,12 @@ describe('run', () => {
169
197
  expect(result!.request.url).toBe('http://localhost/user/abc-123')
170
198
  expect(result!.response?.status).toBe(200)
171
199
  expect(result!.response?.statusText).toBe('OK')
172
- expect(await result?.response?.json()).toEqual({ userId: 'abc-123' })
200
+ await expect(result?.response?.json()).resolves.toEqual({
201
+ userId: 'abc-123',
202
+ })
173
203
  })
174
204
 
175
- test('returns null given a non-matching request', async () => {
205
+ it('returns null given a non-matching request', async () => {
176
206
  const handler = new HttpHandler('POST', '/login', resolver)
177
207
  const result = await handler.run({
178
208
  request: new Request(new URL('/users', location.href)),
@@ -182,7 +212,7 @@ describe('run', () => {
182
212
  expect(result).toBeNull()
183
213
  })
184
214
 
185
- test('returns an empty "params" object given request with no URL parameters', async () => {
215
+ it('returns an empty "params" object given request with no URL parameters', async () => {
186
216
  const handler = new HttpHandler('GET', '/users', resolver)
187
217
  const result = await handler.run({
188
218
  request: new Request(new URL('/users', location.href)),
@@ -192,7 +222,7 @@ describe('run', () => {
192
222
  expect(result?.parsedResult?.match?.params).toEqual({})
193
223
  })
194
224
 
195
- test('exhausts resolver until its generator completes', async () => {
225
+ it('exhausts resolver until its generator completes', async () => {
196
226
  const handler = new HttpHandler('GET', '/users', function* () {
197
227
  let count = 0
198
228
 
@@ -212,12 +242,12 @@ describe('run', () => {
212
242
  return result?.response?.text()
213
243
  }
214
244
 
215
- expect(await run()).toBe('pending')
216
- expect(await run()).toBe('pending')
217
- expect(await run()).toBe('pending')
218
- expect(await run()).toBe('pending')
219
- expect(await run()).toBe('pending')
220
- expect(await run()).toBe('complete')
221
- expect(await run()).toBe('complete')
245
+ await expect(run()).resolves.toBe('pending')
246
+ await expect(run()).resolves.toBe('pending')
247
+ await expect(run()).resolves.toBe('pending')
248
+ await expect(run()).resolves.toBe('pending')
249
+ await expect(run()).resolves.toBe('pending')
250
+ await expect(run()).resolves.toBe('complete')
251
+ await expect(run()).resolves.toBe('complete')
222
252
  })
223
253
  })
@@ -25,7 +25,7 @@ type HttpHandlerMethod = string | RegExp
25
25
 
26
26
  export interface HttpHandlerInfo extends RequestHandlerDefaultInfo {
27
27
  method: HttpHandlerMethod
28
- path: Path
28
+ path: HttpRequestPredicate<PathParams>
29
29
  }
30
30
 
31
31
  export enum HttpMethods {
@@ -52,6 +52,24 @@ export type HttpRequestResolverExtras<Params extends PathParams> = {
52
52
  cookies: Record<string, string>
53
53
  }
54
54
 
55
+ export type HttpCustomPredicate<Params extends PathParams> = (args: {
56
+ request: Request
57
+ cookies: Record<string, string>
58
+ }) =>
59
+ | HttpCustomPredicateResult<Params>
60
+ | Promise<HttpCustomPredicateResult<Params>>
61
+
62
+ export type HttpCustomPredicateResult<Params extends PathParams> =
63
+ | boolean
64
+ | {
65
+ matches: boolean
66
+ params: Params
67
+ }
68
+
69
+ export type HttpRequestPredicate<Params extends PathParams> =
70
+ | Path
71
+ | HttpCustomPredicate<Params>
72
+
55
73
  /**
56
74
  * Request handler for HTTP requests.
57
75
  * Provides request matching based on method and URL.
@@ -63,14 +81,17 @@ export class HttpHandler extends RequestHandler<
63
81
  > {
64
82
  constructor(
65
83
  method: HttpHandlerMethod,
66
- path: Path,
84
+ predicate: HttpRequestPredicate<PathParams>,
67
85
  resolver: ResponseResolver<HttpRequestResolverExtras<any>, any, any>,
68
86
  options?: RequestHandlerOptions,
69
87
  ) {
88
+ const displayPath =
89
+ typeof predicate === 'function' ? '[custom predicate]' : predicate
90
+
70
91
  super({
71
92
  info: {
72
- header: `${method} ${path}`,
73
- path,
93
+ header: `${method}${displayPath ? ` ${displayPath}` : ''}`,
94
+ path: predicate,
74
95
  method,
75
96
  },
76
97
  resolver,
@@ -83,7 +104,7 @@ export class HttpHandler extends RequestHandler<
83
104
  private checkRedundantQueryParameters() {
84
105
  const { method, path } = this.info
85
106
 
86
- if (path instanceof RegExp) {
107
+ if (!path || path instanceof RegExp || typeof path === 'function') {
87
108
  return
88
109
  }
89
110
 
@@ -95,7 +116,7 @@ export class HttpHandler extends RequestHandler<
95
116
  }
96
117
 
97
118
  const searchParams = getSearchParams(path)
98
- const queryParams: string[] = []
119
+ const queryParams: Array<string> = []
99
120
 
100
121
  searchParams.forEach((_, paramName) => {
101
122
  queryParams.push(paramName)
@@ -111,20 +132,48 @@ export class HttpHandler extends RequestHandler<
111
132
  resolutionContext?: ResponseResolutionContext
112
133
  }) {
113
134
  const url = new URL(args.request.url)
114
- const match = matchRequestUrl(
115
- url,
116
- this.info.path,
117
- args.resolutionContext?.baseUrl,
118
- )
119
135
  const cookies = getAllRequestCookies(args.request)
120
136
 
137
+ /**
138
+ * Handle custom predicate functions.
139
+ * @note Invoke this during parsing so the user can parse the path parameters
140
+ * manually. Otherwise, `params` is always an empty object, which isn't nice.
141
+ */
142
+ if (typeof this.info.path === 'function') {
143
+ const customPredicateResult = await this.info.path({
144
+ request: args.request,
145
+ cookies,
146
+ })
147
+
148
+ const match =
149
+ typeof customPredicateResult === 'boolean'
150
+ ? {
151
+ matches: customPredicateResult,
152
+ params: {},
153
+ }
154
+ : customPredicateResult
155
+
156
+ return {
157
+ match,
158
+ cookies,
159
+ }
160
+ }
161
+
162
+ const match = this.info.path
163
+ ? matchRequestUrl(url, this.info.path, args.resolutionContext?.baseUrl)
164
+ : { matches: false, params: {} }
165
+
121
166
  return {
122
167
  match,
123
168
  cookies,
124
169
  }
125
170
  }
126
171
 
127
- predicate(args: { request: Request; parsedResult: HttpRequestParsedResult }) {
172
+ async predicate(args: {
173
+ request: Request
174
+ parsedResult: HttpRequestParsedResult
175
+ resolutionContext?: ResponseResolutionContext
176
+ }) {
128
177
  const hasMatchingMethod = this.matchMethod(args.request.method)
129
178
  const hasMatchingUrl = args.parsedResult.match.matches
130
179
  return hasMatchingMethod && hasMatchingUrl
@@ -179,7 +179,7 @@ export abstract class RequestHandler<
179
179
  request: Request
180
180
  parsedResult: ParsedResult
181
181
  resolutionContext?: ResponseResolutionContext
182
- }): boolean
182
+ }): boolean | Promise<boolean>
183
183
 
184
184
  /**
185
185
  * Print out the successfully handled request.
@@ -273,7 +273,7 @@ export abstract class RequestHandler<
273
273
  request: args.request,
274
274
  resolutionContext: args.resolutionContext,
275
275
  })
276
- const shouldInterceptRequest = this.predicate({
276
+ const shouldInterceptRequest = await this.predicate({
277
277
  request: args.request,
278
278
  parsedResult,
279
279
  resolutionContext: args.resolutionContext,
package/src/core/http.ts CHANGED
@@ -7,8 +7,9 @@ import {
7
7
  HttpMethods,
8
8
  HttpHandler,
9
9
  HttpRequestResolverExtras,
10
+ HttpRequestPredicate,
10
11
  } from './handlers/HttpHandler'
11
- import type { Path, PathParams } from './utils/matching/matchRequestUrl'
12
+ import type { PathParams } from './utils/matching/matchRequestUrl'
12
13
 
13
14
  export type HttpRequestHandler = <
14
15
  Params extends PathParams<keyof Params> = PathParams,
@@ -18,9 +19,8 @@ export type HttpRequestHandler = <
18
19
  // returns plain "Response" and the one returning "HttpResponse"
19
20
  // to enforce a stricter response body type.
20
21
  ResponseBodyType extends DefaultBodyType = undefined,
21
- RequestPath extends Path = Path,
22
22
  >(
23
- path: RequestPath,
23
+ predicate: HttpRequestPredicate<Params>,
24
24
  resolver: HttpResponseResolver<Params, RequestBodyType, ResponseBodyType>,
25
25
  options?: RequestHandlerOptions,
26
26
  ) => HttpHandler
@@ -38,8 +38,8 @@ export type HttpResponseResolver<
38
38
  function createHttpHandler<Method extends HttpMethods | RegExp>(
39
39
  method: Method,
40
40
  ): HttpRequestHandler {
41
- return (path, resolver, options = {}) => {
42
- return new HttpHandler(method, path, resolver, options)
41
+ return (predicate, resolver, options = {}) => {
42
+ return new HttpHandler(method, predicate, resolver, options)
43
43
  }
44
44
  }
45
45
 
package/src/core/index.ts CHANGED
@@ -42,6 +42,7 @@ export type {
42
42
  export type {
43
43
  RequestQuery,
44
44
  HttpRequestParsedResult,
45
+ HttpCustomPredicate,
45
46
  } from './handlers/HttpHandler'
46
47
  export type { HttpRequestHandler, HttpResponseResolver } from './http'
47
48
 
@@ -51,6 +52,8 @@ export type {
51
52
  GraphQLRequestBody,
52
53
  GraphQLResponseBody,
53
54
  GraphQLJsonRequestBody,
55
+ GraphQLOperationType,
56
+ GraphQLCustomPredicate,
54
57
  } from './handlers/GraphQLHandler'
55
58
  export type { GraphQLRequestHandler, GraphQLResponseResolver } from './graphql'
56
59