msw 2.12.11 → 2.12.13

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 (92) hide show
  1. package/config/scripts/postinstall.js +2 -2
  2. package/lib/core/{HttpResponse-Cw4ELwIN.d.mts → HttpResponse-Be4eT3x6.d.mts} +8 -2
  3. package/lib/core/{HttpResponse-CVs3ngx3.d.ts → HttpResponse-Dj6ibgFJ.d.ts} +8 -2
  4. package/lib/core/HttpResponse.d.mts +1 -1
  5. package/lib/core/HttpResponse.d.ts +1 -1
  6. package/lib/core/HttpResponse.js +53 -11
  7. package/lib/core/HttpResponse.js.map +1 -1
  8. package/lib/core/HttpResponse.mjs +53 -11
  9. package/lib/core/HttpResponse.mjs.map +1 -1
  10. package/lib/core/SetupApi.d.mts +1 -1
  11. package/lib/core/SetupApi.d.ts +1 -1
  12. package/lib/core/getResponse.d.mts +1 -1
  13. package/lib/core/getResponse.d.ts +1 -1
  14. package/lib/core/graphql.d.mts +1 -1
  15. package/lib/core/graphql.d.ts +1 -1
  16. package/lib/core/handlers/GraphQLHandler.d.mts +1 -1
  17. package/lib/core/handlers/GraphQLHandler.d.ts +1 -1
  18. package/lib/core/handlers/GraphQLHandler.js +29 -1
  19. package/lib/core/handlers/GraphQLHandler.js.map +1 -1
  20. package/lib/core/handlers/GraphQLHandler.mjs +29 -1
  21. package/lib/core/handlers/GraphQLHandler.mjs.map +1 -1
  22. package/lib/core/handlers/HttpHandler.d.mts +1 -1
  23. package/lib/core/handlers/HttpHandler.d.ts +1 -1
  24. package/lib/core/handlers/RequestHandler.d.mts +1 -1
  25. package/lib/core/handlers/RequestHandler.d.ts +1 -1
  26. package/lib/core/handlers/RequestHandler.js.map +1 -1
  27. package/lib/core/handlers/RequestHandler.mjs.map +1 -1
  28. package/lib/core/http.d.mts +1 -1
  29. package/lib/core/http.d.ts +1 -1
  30. package/lib/core/index.d.mts +3 -3
  31. package/lib/core/index.d.ts +3 -3
  32. package/lib/core/index.js +6 -4
  33. package/lib/core/index.js.map +1 -1
  34. package/lib/core/index.mjs +8 -3
  35. package/lib/core/index.mjs.map +1 -1
  36. package/lib/core/passthrough.d.mts +1 -1
  37. package/lib/core/passthrough.d.ts +1 -1
  38. package/lib/core/sse.d.mts +1 -1
  39. package/lib/core/sse.d.ts +1 -1
  40. package/lib/core/sse.js +1 -1
  41. package/lib/core/sse.js.map +1 -1
  42. package/lib/core/sse.mjs +1 -1
  43. package/lib/core/sse.mjs.map +1 -1
  44. package/lib/core/utils/HttpResponse/decorators.d.mts +1 -1
  45. package/lib/core/utils/HttpResponse/decorators.d.ts +1 -1
  46. package/lib/core/utils/executeHandlers.d.mts +1 -1
  47. package/lib/core/utils/executeHandlers.d.ts +1 -1
  48. package/lib/core/utils/handleRequest.d.mts +1 -1
  49. package/lib/core/utils/handleRequest.d.ts +1 -1
  50. package/lib/core/utils/internal/isHandlerKind.d.mts +1 -1
  51. package/lib/core/utils/internal/isHandlerKind.d.ts +1 -1
  52. package/lib/core/utils/internal/parseGraphQLRequest.d.mts +1 -1
  53. package/lib/core/utils/internal/parseGraphQLRequest.d.ts +1 -1
  54. package/lib/core/utils/internal/parseGraphQLRequest.js +1 -1
  55. package/lib/core/utils/internal/parseGraphQLRequest.js.map +1 -1
  56. package/lib/core/utils/internal/parseGraphQLRequest.mjs +1 -1
  57. package/lib/core/utils/internal/parseGraphQLRequest.mjs.map +1 -1
  58. package/lib/core/utils/internal/parseMultipartData.d.mts +1 -1
  59. package/lib/core/utils/internal/parseMultipartData.d.ts +1 -1
  60. package/lib/core/utils/internal/parseMultipartData.js +3 -1
  61. package/lib/core/utils/internal/parseMultipartData.js.map +1 -1
  62. package/lib/core/utils/internal/parseMultipartData.mjs +3 -1
  63. package/lib/core/utils/internal/parseMultipartData.mjs.map +1 -1
  64. package/lib/core/utils/internal/requestHandlerUtils.d.mts +1 -1
  65. package/lib/core/utils/internal/requestHandlerUtils.d.ts +1 -1
  66. package/lib/core/utils/request/getAllAcceptedMimeTypes.d.mts +14 -0
  67. package/lib/core/utils/request/getAllAcceptedMimeTypes.d.ts +14 -0
  68. package/lib/core/utils/request/getAllAcceptedMimeTypes.js +61 -0
  69. package/lib/core/utils/request/getAllAcceptedMimeTypes.js.map +1 -0
  70. package/lib/core/utils/request/getAllAcceptedMimeTypes.mjs +41 -0
  71. package/lib/core/utils/request/getAllAcceptedMimeTypes.mjs.map +1 -0
  72. package/lib/core/ws/WebSocketIndexedDBClientStore.js +1 -1
  73. package/lib/core/ws/WebSocketIndexedDBClientStore.js.map +1 -1
  74. package/lib/core/ws/WebSocketIndexedDBClientStore.mjs +1 -1
  75. package/lib/core/ws/WebSocketIndexedDBClientStore.mjs.map +1 -1
  76. package/lib/core/ws/handleWebSocketEvent.d.mts +1 -1
  77. package/lib/core/ws/handleWebSocketEvent.d.ts +1 -1
  78. package/lib/iife/index.js +262 -159
  79. package/lib/iife/index.js.map +1 -1
  80. package/lib/mockServiceWorker.js +1 -1
  81. package/package.json +2 -2
  82. package/src/core/HttpResponse.test.ts +25 -9
  83. package/src/core/HttpResponse.ts +62 -10
  84. package/src/core/handlers/GraphQLHandler.ts +54 -2
  85. package/src/core/handlers/RequestHandler.ts +1 -1
  86. package/src/core/index.ts +8 -3
  87. package/src/core/sse.ts +1 -1
  88. package/src/core/utils/internal/parseGraphQLRequest.ts +1 -1
  89. package/src/core/utils/internal/parseMultipartData.ts +3 -1
  90. package/src/core/utils/request/getAllAcceptedMimeTypes.test.ts +86 -0
  91. package/src/core/utils/request/getAllAcceptedMimeTypes.ts +70 -0
  92. package/src/core/ws/WebSocketIndexedDBClientStore.ts +1 -2
@@ -7,7 +7,7 @@
7
7
  * - Please do NOT modify this file.
8
8
  */
9
9
 
10
- const PACKAGE_VERSION = '2.12.11'
10
+ const PACKAGE_VERSION = '2.12.13'
11
11
  const INTEGRITY_CHECKSUM = '4db4a41e972cec1b64cc569c66952d82'
12
12
  const IS_MOCKED_RESPONSE = Symbol('isMockedResponse')
13
13
  const activeClientIds = new Set()
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "msw",
3
- "version": "2.12.11",
3
+ "version": "2.12.13",
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",
@@ -285,7 +285,7 @@
285
285
  "vitest-environment-miniflare": "^2.14.4",
286
286
  "webpack": "^5.95.0",
287
287
  "webpack-http-server": "^0.5.0",
288
- "msw": "2.12.11"
288
+ "msw": "2.12.13"
289
289
  },
290
290
  "peerDependencies": {
291
291
  "typescript": ">= 4.8.x"
@@ -2,14 +2,14 @@
2
2
  * @vitest-environment node
3
3
  */
4
4
  import { TextEncoder } from 'util'
5
- import { HttpResponse } from './HttpResponse'
5
+ import { HttpResponse, kDefaultContentType } from './HttpResponse'
6
6
 
7
7
  it('creates a plain response', async () => {
8
8
  const response = new HttpResponse(null, { status: 301 })
9
9
  expect(response.status).toBe(301)
10
10
  expect(response.statusText).toBe('Moved Permanently')
11
11
  expect(response.body).toBe(null)
12
- expect(await response.text()).toBe('')
12
+ await expect(response.text()).resolves.toBe('')
13
13
  expect(Object.fromEntries(response.headers.entries())).toEqual({})
14
14
  })
15
15
 
@@ -24,11 +24,12 @@ describe('HttpResponse.text()', () => {
24
24
  expect(response.status).toBe(201)
25
25
  expect(response.statusText).toBe('Created')
26
26
  expect(response.body).toBeInstanceOf(ReadableStream)
27
- expect(await response.text()).toBe('hello world')
27
+ await expect(response.text()).resolves.toBe('hello world')
28
28
  expect(Object.fromEntries(response.headers.entries())).toEqual({
29
29
  'content-length': '11',
30
30
  'content-type': 'text/plain',
31
31
  })
32
+ expect(kDefaultContentType in response).toBe(true)
32
33
  })
33
34
 
34
35
  it('creates a text response with special characters', async () => {
@@ -37,7 +38,7 @@ describe('HttpResponse.text()', () => {
37
38
  expect(response.status).toBe(201)
38
39
  expect(response.statusText).toBe('Created')
39
40
  expect(response.body).toBeInstanceOf(ReadableStream)
40
- expect(await response.text()).toBe('안녕 세상')
41
+ await expect(response.text()).resolves.toBe('안녕 세상')
41
42
  expect(Object.fromEntries(response.headers.entries())).toEqual({
42
43
  'content-length': '13',
43
44
  'content-type': 'text/plain',
@@ -52,11 +53,12 @@ describe('HttpResponse.text()', () => {
52
53
  expect(response.status).toBe(200)
53
54
  expect(response.statusText).toBe('OK')
54
55
  expect(response.body).toBeInstanceOf(ReadableStream)
55
- expect(await response.text()).toBe('hello world')
56
+ await expect(response.text()).resolves.toBe('hello world')
56
57
  expect(Object.fromEntries(response.headers.entries())).toEqual({
57
58
  'content-length': '11',
58
59
  'content-type': 'text/plain; charset=utf-8',
59
60
  })
61
+ expect(kDefaultContentType in response).toBe(false)
60
62
  })
61
63
 
62
64
  it('allows overriding the "Content-Length" response header', async () => {
@@ -83,6 +85,7 @@ describe('HttpResponse.json()', () => {
83
85
  'content-length': '20',
84
86
  'content-type': 'application/json',
85
87
  })
88
+ expect(kDefaultContentType in response).toBe(true)
86
89
  })
87
90
 
88
91
  it('creates a json response given an object with special characters', async () => {
@@ -172,6 +175,7 @@ describe('HttpResponse.json()', () => {
172
175
  },
173
176
  )
174
177
 
178
+ expect(kDefaultContentType in response).toBe(false)
175
179
  expect(response.status).toBe(200)
176
180
  expect(response.statusText).toBe('OK')
177
181
  expect(response.body).toBeInstanceOf(ReadableStream)
@@ -201,13 +205,15 @@ describe('HttpResponse.xml()', () => {
201
205
  it('creates an xml response', async () => {
202
206
  const response = HttpResponse.xml('<user name="John" />')
203
207
 
208
+ expect(kDefaultContentType in response).toBe(true)
204
209
  expect(response.status).toBe(200)
205
210
  expect(response.statusText).toBe('OK')
206
211
  expect(response.body).toBeInstanceOf(ReadableStream)
207
- expect(await response.text()).toBe('<user name="John" />')
212
+ await expect(response.text()).resolves.toBe('<user name="John" />')
208
213
  expect(Object.fromEntries(response.headers.entries())).toEqual({
209
214
  'content-type': 'text/xml',
210
215
  })
216
+ expect(kDefaultContentType in response).toBe(true)
211
217
  })
212
218
 
213
219
  it('allows overriding the "Content-Type" response header', async () => {
@@ -220,10 +226,11 @@ describe('HttpResponse.xml()', () => {
220
226
  expect(response.status).toBe(200)
221
227
  expect(response.statusText).toBe('OK')
222
228
  expect(response.body).toBeInstanceOf(ReadableStream)
223
- expect(await response.text()).toBe('<user name="John" />')
229
+ await expect(response.text()).resolves.toBe('<user name="John" />')
224
230
  expect(Object.fromEntries(response.headers.entries())).toEqual({
225
231
  'content-type': 'text/xml; charset=utf-8',
226
232
  })
233
+ expect(kDefaultContentType in response).toBe(false)
227
234
  })
228
235
  })
229
236
 
@@ -234,10 +241,13 @@ describe('HttpResponse.html()', () => {
234
241
  expect(response.status).toBe(200)
235
242
  expect(response.statusText).toBe('OK')
236
243
  expect(response.body).toBeInstanceOf(ReadableStream)
237
- expect(await response.text()).toBe('<p class="author">Jane Doe</p>')
244
+ await expect(response.text()).resolves.toBe(
245
+ '<p class="author">Jane Doe</p>',
246
+ )
238
247
  expect(Object.fromEntries(response.headers.entries())).toEqual({
239
248
  'content-type': 'text/html',
240
249
  })
250
+ expect(kDefaultContentType in response).toBe(true)
241
251
  })
242
252
 
243
253
  it('allows overriding the "Content-Type" response header', async () => {
@@ -250,10 +260,13 @@ describe('HttpResponse.html()', () => {
250
260
  expect(response.status).toBe(200)
251
261
  expect(response.statusText).toBe('OK')
252
262
  expect(response.body).toBeInstanceOf(ReadableStream)
253
- expect(await response.text()).toBe('<p class="author">Jane Doe</p>')
263
+ await expect(response.text()).resolves.toBe(
264
+ '<p class="author">Jane Doe</p>',
265
+ )
254
266
  expect(Object.fromEntries(response.headers.entries())).toEqual({
255
267
  'content-type': 'text/html; charset=utf-8',
256
268
  })
269
+ expect(kDefaultContentType in response).toBe(false)
257
270
  })
258
271
  })
259
272
 
@@ -272,6 +285,7 @@ describe('HttpResponse.arrayBuffer()', () => {
272
285
  'content-length': '11',
273
286
  'content-type': 'application/octet-stream',
274
287
  })
288
+ expect(kDefaultContentType in response).toBe(true)
275
289
  })
276
290
 
277
291
  it('allows overriding the "Content-Type" response header', async () => {
@@ -292,6 +306,7 @@ describe('HttpResponse.arrayBuffer()', () => {
292
306
  'content-length': '11',
293
307
  'content-type': 'text/plain; charset=utf-8',
294
308
  })
309
+ expect(kDefaultContentType in response).toBe(false)
295
310
  })
296
311
 
297
312
  it('creates an array buffer response from a shared array buffer', async () => {
@@ -340,6 +355,7 @@ describe('HttpResponse.arrayBuffer()', () => {
340
355
  'content-length': '11',
341
356
  'content-type': 'text/plain; charset=utf-8',
342
357
  })
358
+ expect(kDefaultContentType in response).toBe(false)
343
359
  })
344
360
  })
345
361
 
@@ -28,6 +28,8 @@ export interface StrictRequest<BodyType extends JsonBodyType> extends Request {
28
28
  export type StrictResponse<BodyType extends DefaultBodyType> =
29
29
  HttpResponse<BodyType>
30
30
 
31
+ export const kDefaultContentType = Symbol.for('kDefaultContentType')
32
+
31
33
  /**
32
34
  * A drop-in replacement for the standard `Response` class
33
35
  * to allow additional features, like mocking the response `Set-Cookie` header.
@@ -65,8 +67,9 @@ export class HttpResponse<
65
67
  init?: HttpResponseInit,
66
68
  ): HttpResponse<BodyType> {
67
69
  const responseInit = normalizeResponseInit(init)
70
+ const hasExplicitContentType = responseInit.headers.has('Content-Type')
68
71
 
69
- if (!responseInit.headers.has('Content-Type')) {
72
+ if (!hasExplicitContentType) {
70
73
  responseInit.headers.set('Content-Type', 'text/plain')
71
74
  }
72
75
 
@@ -80,7 +83,16 @@ export class HttpResponse<
80
83
  )
81
84
  }
82
85
 
83
- return new HttpResponse(body, responseInit)
86
+ const response = new HttpResponse(body, responseInit)
87
+
88
+ if (!hasExplicitContentType) {
89
+ Object.defineProperty(response, kDefaultContentType, {
90
+ value: true,
91
+ enumerable: false,
92
+ })
93
+ }
94
+
95
+ return response
84
96
  }
85
97
 
86
98
  /**
@@ -94,8 +106,9 @@ export class HttpResponse<
94
106
  init?: HttpResponseInit,
95
107
  ): HttpResponse<BodyType> {
96
108
  const responseInit = normalizeResponseInit(init)
109
+ const hasExplicitContentType = responseInit.headers.has('Content-Type')
97
110
 
98
- if (!responseInit.headers.has('Content-Type')) {
111
+ if (!hasExplicitContentType) {
99
112
  responseInit.headers.set('Content-Type', 'application/json')
100
113
  }
101
114
 
@@ -112,7 +125,16 @@ export class HttpResponse<
112
125
  )
113
126
  }
114
127
 
115
- return new HttpResponse(responseText as BodyType, responseInit)
128
+ const response = new HttpResponse(responseText, responseInit)
129
+
130
+ if (!hasExplicitContentType) {
131
+ Object.defineProperty(response, kDefaultContentType, {
132
+ value: true,
133
+ enumerable: false,
134
+ })
135
+ }
136
+
137
+ return response as HttpResponse<BodyType>
116
138
  }
117
139
 
118
140
  /**
@@ -126,12 +148,22 @@ export class HttpResponse<
126
148
  init?: HttpResponseInit,
127
149
  ): HttpResponse<BodyType> {
128
150
  const responseInit = normalizeResponseInit(init)
151
+ const hasExplicitContentType = responseInit.headers.has('Content-Type')
129
152
 
130
- if (!responseInit.headers.has('Content-Type')) {
153
+ if (!hasExplicitContentType) {
131
154
  responseInit.headers.set('Content-Type', 'text/xml')
132
155
  }
133
156
 
134
- return new HttpResponse(body, responseInit)
157
+ const response = new HttpResponse(body, responseInit)
158
+
159
+ if (!hasExplicitContentType) {
160
+ Object.defineProperty(response, kDefaultContentType, {
161
+ value: true,
162
+ enumerable: false,
163
+ })
164
+ }
165
+
166
+ return response as HttpResponse<BodyType>
135
167
  }
136
168
 
137
169
  /**
@@ -145,12 +177,22 @@ export class HttpResponse<
145
177
  init?: HttpResponseInit,
146
178
  ): HttpResponse<BodyType> {
147
179
  const responseInit = normalizeResponseInit(init)
180
+ const hasExplicitContentType = responseInit.headers.has('Content-Type')
148
181
 
149
- if (!responseInit.headers.has('Content-Type')) {
182
+ if (!hasExplicitContentType) {
150
183
  responseInit.headers.set('Content-Type', 'text/html')
151
184
  }
152
185
 
153
- return new HttpResponse(body, responseInit)
186
+ const response = new HttpResponse(body, responseInit)
187
+
188
+ if (!hasExplicitContentType) {
189
+ Object.defineProperty(response, kDefaultContentType, {
190
+ value: true,
191
+ enumerable: false,
192
+ })
193
+ }
194
+
195
+ return response as HttpResponse<BodyType>
154
196
  }
155
197
 
156
198
  /**
@@ -167,8 +209,9 @@ export class HttpResponse<
167
209
  init?: HttpResponseInit,
168
210
  ): HttpResponse<BodyType> {
169
211
  const responseInit = normalizeResponseInit(init)
212
+ const hasExplicitContentType = responseInit.headers.has('Content-Type')
170
213
 
171
- if (!responseInit.headers.has('Content-Type')) {
214
+ if (!hasExplicitContentType) {
172
215
  responseInit.headers.set('Content-Type', 'application/octet-stream')
173
216
  }
174
217
 
@@ -176,7 +219,16 @@ export class HttpResponse<
176
219
  responseInit.headers.set('Content-Length', body.byteLength.toString())
177
220
  }
178
221
 
179
- return new HttpResponse(body, responseInit)
222
+ const response = new HttpResponse(body, responseInit)
223
+
224
+ if (!hasExplicitContentType) {
225
+ Object.defineProperty(response, kDefaultContentType, {
226
+ value: true,
227
+ enumerable: false,
228
+ })
229
+ }
230
+
231
+ return response as HttpResponse<BodyType>
180
232
  }
181
233
 
182
234
  /**
@@ -1,3 +1,4 @@
1
+ import { invariant } from 'outvariant'
1
2
  import {
2
3
  parse,
3
4
  type DocumentNode,
@@ -8,6 +9,7 @@ import {
8
9
  DefaultBodyType,
9
10
  RequestHandler,
10
11
  RequestHandlerDefaultInfo,
12
+ RequestHandlerExecutionResult,
11
13
  RequestHandlerOptions,
12
14
  ResponseResolver,
13
15
  } from './RequestHandler'
@@ -26,7 +28,9 @@ import {
26
28
  import { toPublicUrl } from '../utils/request/toPublicUrl'
27
29
  import { devUtils } from '../utils/internal/devUtils'
28
30
  import { getAllRequestCookies } from '../utils/request/getRequestCookies'
29
- import { invariant } from 'outvariant'
31
+ import { ResponseResolutionContext } from 'src/iife'
32
+ import { kDefaultContentType, StrictRequest } from '../HttpResponse'
33
+ import { getAllAcceptedMimeTypes } from '../utils/request/getAllAcceptedMimeTypes'
30
34
 
31
35
  export interface DocumentTypeDecoration<
32
36
  Result = { [key: string]: any },
@@ -120,7 +124,7 @@ export function isDocumentNode(
120
124
  }
121
125
 
122
126
  function isDocumentTypeDecoration(
123
- value: any,
127
+ value: unknown,
124
128
  ): value is DocumentTypeDecoration<any, any> {
125
129
  return value instanceof String
126
130
  }
@@ -309,6 +313,54 @@ Consider naming this operation or using "graphql.operation()" request handler to
309
313
  )
310
314
  }
311
315
 
316
+ public async run(args: {
317
+ request: StrictRequest<any>
318
+ requestId: string
319
+ resolutionContext?: ResponseResolutionContext
320
+ }): Promise<RequestHandlerExecutionResult<GraphQLRequestParsedResult> | null> {
321
+ const result = await super.run(args)
322
+
323
+ if (result?.response == null) {
324
+ return result
325
+ }
326
+
327
+ if (!(kDefaultContentType in result.response)) {
328
+ return result
329
+ }
330
+
331
+ const acceptedMimeTypes = getAllAcceptedMimeTypes(
332
+ args.request.headers.get('accept'),
333
+ )
334
+
335
+ if (acceptedMimeTypes.length === 0) {
336
+ return result
337
+ }
338
+
339
+ const graphqlResponseIndex = acceptedMimeTypes.indexOf(
340
+ 'application/graphql-response+json',
341
+ )
342
+ const jsonIndex = acceptedMimeTypes.indexOf('application/json')
343
+
344
+ /**
345
+ * Use the "application/graphql-response+json" response content type
346
+ * only when the client accepts it AND prefers it over "application/json"
347
+ * (i.e. it appears earlier in the precedence-sorted list, or "application/json"
348
+ * is not listed at all).
349
+ * @see https://github.com/graphql/graphql-over-http/blob/4d1df1fb829ec2dd3ecbf3c6aa4025bd356c270d/spec/GraphQLOverHTTP.md#accept
350
+ */
351
+ if (
352
+ graphqlResponseIndex !== -1 &&
353
+ (jsonIndex === -1 || graphqlResponseIndex <= jsonIndex)
354
+ ) {
355
+ result.response.headers.set(
356
+ 'content-type',
357
+ 'application/graphql-response+json',
358
+ )
359
+ }
360
+
361
+ return result
362
+ }
363
+
312
364
  private async matchOperationName(args: {
313
365
  request: Request
314
366
  parsedResult: GraphQLRequestParsedResult
@@ -113,7 +113,7 @@ export interface RequestHandlerOptions {
113
113
  }
114
114
 
115
115
  export interface RequestHandlerExecutionResult<
116
- ParsedResult extends Record<string, unknown> | undefined,
116
+ ParsedResult extends object | undefined,
117
117
  > {
118
118
  handler: RequestHandler
119
119
  parsedResult?: ParsedResult
package/src/core/index.ts CHANGED
@@ -34,7 +34,7 @@ export type AnyHandler = HttpHandler | GraphQLHandler | WebSocketHandler
34
34
 
35
35
  /* Utils */
36
36
  export { matchRequestUrl } from './utils/matching/matchRequestUrl'
37
- export * from './utils/handleRequest'
37
+ export { handleRequest, type HandleRequestOptions } from './utils/handleRequest'
38
38
  export {
39
39
  onUnhandledRequest,
40
40
  type UnhandledRequestStrategy,
@@ -92,8 +92,13 @@ export type { Path, PathParams, Match } from './utils/matching/matchRequestUrl'
92
92
  export type { ParsedGraphQLRequest } from './utils/internal/parseGraphQLRequest'
93
93
  export type { ResponseResolutionContext } from './utils/executeHandlers'
94
94
 
95
- export * from './HttpResponse'
96
- export * from './delay'
95
+ export {
96
+ HttpResponse,
97
+ type HttpResponseInit,
98
+ type StrictRequest,
99
+ type StrictResponse,
100
+ } from './HttpResponse'
101
+ export { delay, type DelayMode } from './delay'
97
102
  export { bypass } from './bypass'
98
103
  export { passthrough } from './passthrough'
99
104
  export { isCommonAssetRequest } from './isCommonAssetRequest'
package/src/core/sse.ts CHANGED
@@ -333,7 +333,7 @@ class ServerSentEventClient<
333
333
  }
334
334
 
335
335
  if (message.event) {
336
- frames.push(`event:${message.event?.toString()}`)
336
+ frames.push(`event:${message.event.toString()}`)
337
337
  }
338
338
 
339
339
  if (message.data != null) {
@@ -84,7 +84,7 @@ function extractMultipartVariables<VariablesType extends GraphQLVariables>(
84
84
 
85
85
  for (const path of paths) {
86
86
  if (!(path in target)) {
87
- throw new Error(`Property '${paths}' is not in operations.`)
87
+ throw new Error(`Property '${path}' is not in operations.`)
88
88
  }
89
89
 
90
90
  target = target[path]
@@ -62,7 +62,9 @@ export function parseMultipartData<T extends DefaultRequestMultipartBody>(
62
62
  return undefined
63
63
  }
64
64
 
65
- const boundaryRegExp = new RegExp(`--+${boundary}`)
65
+ const boundaryRegExp = new RegExp(
66
+ `--+${boundary.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')}`,
67
+ )
66
68
  const fields = data
67
69
  .split(boundaryRegExp)
68
70
  .filter((chunk) => chunk.startsWith('\r\n') && chunk.endsWith('\r\n'))
@@ -0,0 +1,86 @@
1
+ import { getAllAcceptedMimeTypes } from './getAllAcceptedMimeTypes'
2
+
3
+ it('returns an empty array for null accept header', () => {
4
+ expect(getAllAcceptedMimeTypes(null)).toEqual([])
5
+ })
6
+
7
+ it('returns a single mime type as-is', () => {
8
+ expect(getAllAcceptedMimeTypes('application/json')).toEqual([
9
+ 'application/json',
10
+ ])
11
+ })
12
+
13
+ it('returns multiple mime types in order', () => {
14
+ expect(getAllAcceptedMimeTypes('text/html, application/json')).toEqual([
15
+ 'text/html',
16
+ 'application/json',
17
+ ])
18
+ })
19
+
20
+ it('sorts by quality value (q parameter)', () => {
21
+ expect(
22
+ getAllAcceptedMimeTypes('text/plain;q=0.5, application/json;q=0.9'),
23
+ ).toEqual(['application/json', 'text/plain'])
24
+ })
25
+
26
+ it('excludes types with q=0', () => {
27
+ expect(getAllAcceptedMimeTypes('text/html, text/plain;q=0')).toEqual([
28
+ 'text/html',
29
+ ])
30
+ })
31
+
32
+ it('returns an empty array when all types have q=0', () => {
33
+ expect(getAllAcceptedMimeTypes('text/html;q=0, text/plain;q=0')).toEqual([])
34
+ })
35
+
36
+ it('treats missing q as q=1 (default)', () => {
37
+ expect(getAllAcceptedMimeTypes('text/plain;q=0.5, application/json')).toEqual(
38
+ ['application/json', 'text/plain'],
39
+ )
40
+ })
41
+
42
+ it('sorts by specificity when quality is equal (type/subtype > type/* > */*)', () => {
43
+ expect(getAllAcceptedMimeTypes('*/*, text/*, text/html')).toEqual([
44
+ 'text/html',
45
+ 'text/*',
46
+ '*/*',
47
+ ])
48
+ })
49
+
50
+ it('sorts by parameter count when quality and specificity are equal', () => {
51
+ expect(
52
+ getAllAcceptedMimeTypes(
53
+ 'text/plain;format=fixed;charset=utf-8, text/plain;charset=utf-8',
54
+ ),
55
+ ).toEqual(['text/plain', 'text/plain'])
56
+ })
57
+
58
+ it('applies full precedence: quality > specificity > parameter count', () => {
59
+ expect(
60
+ getAllAcceptedMimeTypes(
61
+ 'text/*;q=0.8, application/json, text/html;q=0.8, */*;q=0.1',
62
+ ),
63
+ ).toEqual(['application/json', 'text/html', 'text/*', '*/*'])
64
+ })
65
+
66
+ it('handles whitespace around values', () => {
67
+ expect(
68
+ getAllAcceptedMimeTypes(' text/html , application/json ; q=0.9 '),
69
+ ).toEqual(['text/html', 'application/json'])
70
+ })
71
+
72
+ it('handles a realistic browser accept header', () => {
73
+ expect(
74
+ getAllAcceptedMimeTypes(
75
+ 'text/html, application/xhtml+xml, application/xml;q=0.9, */*;q=0.8',
76
+ ),
77
+ ).toEqual(['text/html', 'application/xhtml+xml', 'application/xml', '*/*'])
78
+ })
79
+
80
+ it('handles the graphql-over-http accept header', () => {
81
+ expect(
82
+ getAllAcceptedMimeTypes(
83
+ 'application/graphql-response+json, application/json',
84
+ ),
85
+ ).toEqual(['application/graphql-response+json', 'application/json'])
86
+ })
@@ -0,0 +1,70 @@
1
+ /**
2
+ * Returns all accepted mime types, ordered by precedence as defined
3
+ * in [RFC 7231 Section 5.3.2](https://datatracker.ietf.org/doc/html/rfc7231#section-5.3.2).
4
+ *
5
+ * Precedence rules (highest to lowest):
6
+ * 1. Quality value (`q` parameter, default 1).
7
+ * 2. Specificity: `type/subtype` > `type/*` > `*\/*`.
8
+ * 3. Number of media type parameters (more = more specific).
9
+ *
10
+ * Types with `q=0` are excluded (explicitly not acceptable).
11
+ */
12
+ export function getAllAcceptedMimeTypes(
13
+ acceptHeader: string | null,
14
+ ): Array<string> {
15
+ if (acceptHeader == null) {
16
+ return []
17
+ }
18
+
19
+ const accepted: Array<{
20
+ type: string
21
+ quality: number
22
+ specificity: number
23
+ parameterCount: number
24
+ }> = []
25
+
26
+ for (const part of acceptHeader.split(',')) {
27
+ const [type, ...params] = part.split(';').map((v) => v.trim())
28
+
29
+ let quality = 1
30
+ let parameterCount = 0
31
+
32
+ for (const param of params) {
33
+ const [key, value] = param.split('=').map((v) => v.trim())
34
+
35
+ if (key === 'q') {
36
+ quality = Number(value)
37
+ } else {
38
+ parameterCount++
39
+ }
40
+ }
41
+
42
+ // RFC 7231: a quality value of 0 indicates "not acceptable".
43
+ if (quality === 0) {
44
+ continue
45
+ }
46
+
47
+ const [mediaType, mediaSubtype] = type.split('/')
48
+ const specificity = mediaType === '*' ? 0 : mediaSubtype === '*' ? 1 : 2
49
+
50
+ accepted.push({ type, quality, specificity, parameterCount })
51
+ }
52
+
53
+ if (!accepted.length) {
54
+ return []
55
+ }
56
+
57
+ return accepted
58
+ .sort((left, right) => {
59
+ if (right.quality !== left.quality) {
60
+ return right.quality - left.quality
61
+ }
62
+
63
+ if (right.specificity !== left.specificity) {
64
+ return right.specificity - left.specificity
65
+ }
66
+
67
+ return right.parameterCount - left.parameterCount
68
+ })
69
+ .map((entry) => entry.type)
70
+ }
@@ -56,8 +56,7 @@ export class WebSocketIndexedDBClientStore implements WebSocketClientStore {
56
56
  promise.resolve(request.result)
57
57
  }
58
58
  request.onerror = () => {
59
- // eslint-disable-next-line no-console
60
- console.log(request.error)
59
+ console.error(request.error)
61
60
  promise.reject(
62
61
  new Error(
63
62
  `Failed to get all WebSocket clients. There is likely an additional output above.`,