undici 7.0.0-alpha.8 → 7.0.0-alpha.9

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.
@@ -207,7 +207,7 @@ Returns: `Boolean` - `false` if dispatcher is busy and further dispatch calls wo
207
207
 
208
208
  * **onRequestStart** `(controller: DispatchController, context: object) => void` - Invoked before request is dispatched on socket. May be invoked multiple times when a request is retried when the request at the head of the pipeline fails.
209
209
  * **onRequestUpgrade** `(controller: DispatchController, statusCode: number, headers: Record<string, string | string[]>, socket: Duplex) => void` (optional) - Invoked when request is upgraded. Required if `DispatchOptions.upgrade` is defined or `DispatchOptions.method === 'CONNECT'`.
210
- * **onResponseStart** `(controller: DispatchController, statusCode: number, statusMessage?: string, headers: Record<string, string | string []>) => void` - Invoked when statusCode and headers have been received. May be invoked multiple times due to 1xx informational headers. Not required for `upgrade` requests.
210
+ * **onResponseStart** `(controller: DispatchController, statusCode: number, headers: Record<string, string | string []>, statusMessage?: string) => void` - Invoked when statusCode and headers have been received. May be invoked multiple times due to 1xx informational headers. Not required for `upgrade` requests.
211
211
  * **onResponseData** `(controller: DispatchController, chunk: Buffer) => void` - Invoked when response payload data is received. Not required for `upgrade` requests.
212
212
  * **onResponseEnd** `(controller: DispatchController, trailers: Record<string, string | string[]>) => void` - Invoked when response payload and trailers have been received and the request has completed. Not required for `upgrade` requests.
213
213
  * **onResponseError** `(error: Error) => void` - Invoked when an error has occurred. May not throw.
@@ -1054,203 +1054,27 @@ const response = await client.request({
1054
1054
  })
1055
1055
  ```
1056
1056
 
1057
- ##### `Response Error Interceptor`
1057
+ ##### `responseError`
1058
1058
 
1059
- **Introduction**
1059
+ The `responseError` interceptor throws an error for responses with status code errors (>= 400).
1060
1060
 
1061
- The Response Error Interceptor is designed to handle HTTP response errors efficiently. It intercepts responses and throws detailed errors for responses with status codes indicating failure (4xx, 5xx). This interceptor enhances error handling by providing structured error information, including response headers, data, and status codes.
1062
-
1063
- **ResponseError Class**
1064
-
1065
- The `ResponseError` class extends the `UndiciError` class and encapsulates detailed error information. It captures the response status code, headers, and data, providing a structured way to handle errors.
1066
-
1067
- **Definition**
1068
-
1069
- ```js
1070
- class ResponseError extends UndiciError {
1071
- constructor (message, code, { headers, data }) {
1072
- super(message);
1073
- this.name = 'ResponseError';
1074
- this.message = message || 'Response error';
1075
- this.code = 'UND_ERR_RESPONSE';
1076
- this.statusCode = code;
1077
- this.data = data;
1078
- this.headers = headers;
1079
- }
1080
- }
1081
- ```
1082
-
1083
- **Interceptor Handler**
1084
-
1085
- The interceptor's handler class extends `DecoratorHandler` and overrides methods to capture response details and handle errors based on the response status code.
1086
-
1087
- **Methods**
1088
-
1089
- - **onConnect**: Initializes response properties.
1090
- - **onHeaders**: Captures headers and status code. Decodes body if content type is `application/json` or `text/plain`.
1091
- - **onData**: Appends chunks to the body if status code indicates an error.
1092
- - **onComplete**: Finalizes error handling, constructs a `ResponseError`, and invokes the `onError` method.
1093
- - **onError**: Propagates errors to the handler.
1094
-
1095
- **Definition**
1096
-
1097
- ```js
1098
- class Handler extends DecoratorHandler {
1099
- // Private properties
1100
- #handler;
1101
- #statusCode;
1102
- #contentType;
1103
- #decoder;
1104
- #headers;
1105
- #body;
1106
-
1107
- constructor (opts, { handler }) {
1108
- super(handler);
1109
- this.#handler = handler;
1110
- }
1111
-
1112
- onConnect (abort) {
1113
- this.#statusCode = 0;
1114
- this.#contentType = null;
1115
- this.#decoder = null;
1116
- this.#headers = null;
1117
- this.#body = '';
1118
- return this.#handler.onConnect(abort);
1119
- }
1120
-
1121
- onHeaders (statusCode, rawHeaders, resume, statusMessage, headers = parseHeaders(rawHeaders)) {
1122
- this.#statusCode = statusCode;
1123
- this.#headers = headers;
1124
- this.#contentType = headers['content-type'];
1125
-
1126
- if (this.#statusCode < 400) {
1127
- return this.#handler.onHeaders(statusCode, rawHeaders, resume, statusMessage, headers);
1128
- }
1129
-
1130
- if (this.#contentType === 'application/json' || this.#contentType === 'text/plain') {
1131
- this.#decoder = new TextDecoder('utf-8');
1132
- }
1133
- }
1134
-
1135
- onData (chunk) {
1136
- if (this.#statusCode < 400) {
1137
- return this.#handler.onData(chunk);
1138
- }
1139
- this.#body += this.#decoder?.decode(chunk, { stream: true }) ?? '';
1140
- }
1141
-
1142
- onComplete (rawTrailers) {
1143
- if (this.#statusCode >= 400) {
1144
- this.#body += this.#decoder?.decode(undefined, { stream: false }) ?? '';
1145
- if (this.#contentType === 'application/json') {
1146
- try {
1147
- this.#body = JSON.parse(this.#body);
1148
- } catch {
1149
- // Do nothing...
1150
- }
1151
- }
1152
-
1153
- let err;
1154
- const stackTraceLimit = Error.stackTraceLimit;
1155
- Error.stackTraceLimit = 0;
1156
- try {
1157
- err = new ResponseError('Response Error', this.#statusCode, this.#headers, this.#body);
1158
- } finally {
1159
- Error.stackTraceLimit = stackTraceLimit;
1160
- }
1161
-
1162
- this.#handler.onError(err);
1163
- } else {
1164
- this.#handler.onComplete(rawTrailers);
1165
- }
1166
- }
1167
-
1168
- onError (err) {
1169
- this.#handler.onError(err);
1170
- }
1171
- }
1172
-
1173
- module.exports = (dispatch) => (opts, handler) => opts.throwOnError
1174
- ? dispatch(opts, new Handler(opts, { handler }))
1175
- : dispatch(opts, handler);
1176
- ```
1177
-
1178
- **Tests**
1179
-
1180
- Unit tests ensure the interceptor functions correctly, handling both error and non-error responses appropriately.
1181
-
1182
- **Example Tests**
1183
-
1184
- - **No Error if `throwOnError` is False**:
1061
+ **Example**
1185
1062
 
1186
1063
  ```js
1187
- test('should not error if request is not meant to throw error', async (t) => {
1188
- const opts = { throwOnError: false };
1189
- const handler = { onError: () => {}, onData: () => {}, onComplete: () => {} };
1190
- const interceptor = createResponseErrorInterceptor((opts, handler) => handler.onComplete());
1191
- assert.doesNotThrow(() => interceptor(opts, handler));
1192
- });
1193
- ```
1194
-
1195
- - **Error if Status Code is in Specified Error Codes**:
1196
-
1197
- ```js
1198
- test('should error if request status code is in the specified error codes', async (t) => {
1199
- const opts = { throwOnError: true, statusCodes: [500] };
1200
- const response = { statusCode: 500 };
1201
- let capturedError;
1202
- const handler = {
1203
- onError: (err) => { capturedError = err; },
1204
- onData: () => {},
1205
- onComplete: () => {}
1206
- };
1207
-
1208
- const interceptor = createResponseErrorInterceptor((opts, handler) => {
1209
- if (opts.throwOnError && opts.statusCodes.includes(response.statusCode)) {
1210
- handler.onError(new Error('Response Error'));
1211
- } else {
1212
- handler.onComplete();
1213
- }
1214
- });
1215
-
1216
- interceptor({ ...opts, response }, handler);
1217
-
1218
- await new Promise(resolve => setImmediate(resolve));
1219
-
1220
- assert(capturedError, 'Expected error to be captured but it was not.');
1221
- assert.strictEqual(capturedError.message, 'Response Error');
1222
- assert.strictEqual(response.statusCode, 500);
1223
- });
1224
- ```
1225
-
1226
- - **No Error if Status Code is Not in Specified Error Codes**:
1064
+ const { Client, interceptors } = require("undici");
1065
+ const { responseError } = interceptors;
1227
1066
 
1228
- ```js
1229
- test('should not error if request status code is not in the specified error codes', async (t) => {
1230
- const opts = { throwOnError: true, statusCodes: [500] };
1231
- const response = { statusCode: 404 };
1232
- const handler = {
1233
- onError: () => {},
1234
- onData: () => {},
1235
- onComplete: () => {}
1236
- };
1237
-
1238
- const interceptor = createResponseErrorInterceptor((opts, handler) => {
1239
- if (opts.throwOnError && opts.statusCodes.includes(response.statusCode)) {
1240
- handler.onError(new Error('Response Error'));
1241
- } else {
1242
- handler.onComplete();
1243
- }
1244
- });
1067
+ const client = new Client("http://example.com").compose(
1068
+ responseError()
1069
+ );
1245
1070
 
1246
- assert.doesNotThrow(() => interceptor({ ...opts, response }, handler));
1071
+ // Will throw a ResponseError for status codes >= 400
1072
+ await client.request({
1073
+ method: "GET",
1074
+ path: "/"
1247
1075
  });
1248
1076
  ```
1249
1077
 
1250
- **Conclusion**
1251
-
1252
- The Response Error Interceptor provides a robust mechanism for handling HTTP response errors by capturing detailed error information and propagating it through a structured `ResponseError` class. This enhancement improves error handling and debugging capabilities in applications using the interceptor.
1253
-
1254
1078
  ##### `Cache Interceptor`
1255
1079
 
1256
1080
  The `cache` interceptor implements client-side response caching as described in
package/index.js CHANGED
@@ -38,6 +38,7 @@ module.exports.DecoratorHandler = DecoratorHandler
38
38
  module.exports.RedirectHandler = RedirectHandler
39
39
  module.exports.interceptors = {
40
40
  redirect: require('./lib/interceptor/redirect'),
41
+ responseError: require('./lib/interceptor/response-error'),
41
42
  retry: require('./lib/interceptor/retry'),
42
43
  dump: require('./lib/interceptor/dump'),
43
44
  dns: require('./lib/interceptor/dns'),
@@ -196,13 +196,13 @@ class RequestRetryError extends UndiciError {
196
196
  }
197
197
 
198
198
  class ResponseError extends UndiciError {
199
- constructor (message, code, { headers, data }) {
199
+ constructor (message, code, { headers, body }) {
200
200
  super(message)
201
201
  this.name = 'ResponseError'
202
202
  this.message = message || 'Response error'
203
203
  this.code = 'UND_ERR_RESPONSE'
204
204
  this.statusCode = code
205
- this.data = data
205
+ this.body = body
206
206
  this.headers = headers
207
207
  }
208
208
  }
@@ -130,6 +130,8 @@ class DispatcherBase extends Dispatcher {
130
130
  throw new InvalidArgumentError('handler must be an object')
131
131
  }
132
132
 
133
+ handler = UnwrapHandler.unwrap(handler)
134
+
133
135
  try {
134
136
  if (!opts || typeof opts !== 'object') {
135
137
  throw new InvalidArgumentError('opts must be an object.')
@@ -143,10 +145,10 @@ class DispatcherBase extends Dispatcher {
143
145
  throw new ClientClosedError()
144
146
  }
145
147
 
146
- return this[kDispatch](opts, UnwrapHandler.unwrap(handler))
148
+ return this[kDispatch](opts, handler)
147
149
  } catch (err) {
148
150
  if (typeof handler.onError !== 'function') {
149
- throw new InvalidArgumentError('invalid onError method')
151
+ throw err
150
152
  }
151
153
 
152
154
  handler.onError(err)
@@ -71,15 +71,15 @@ class CacheHandler {
71
71
  onResponseStart (
72
72
  controller,
73
73
  statusCode,
74
- statusMessage,
75
- headers
74
+ headers,
75
+ statusMessage
76
76
  ) {
77
77
  const downstreamOnHeaders = () =>
78
78
  this.#handler.onResponseStart?.(
79
79
  controller,
80
80
  statusCode,
81
- statusMessage,
82
- headers
81
+ headers,
82
+ statusMessage
83
83
  )
84
84
 
85
85
  if (
@@ -62,8 +62,8 @@ class CacheRevalidationHandler {
62
62
  onResponseStart (
63
63
  controller,
64
64
  statusCode,
65
- statusMessage,
66
- headers
65
+ headers,
66
+ statusMessage
67
67
  ) {
68
68
  assert(this.#callback != null)
69
69
 
@@ -82,8 +82,8 @@ class CacheRevalidationHandler {
82
82
  this.#handler.onResponseStart?.(
83
83
  controller,
84
84
  statusCode,
85
- statusMessage,
86
- headers
85
+ headers,
86
+ statusMessage
87
87
  )
88
88
  }
89
89
 
@@ -92,7 +92,7 @@ class CacheRevalidationHandler {
92
92
  return
93
93
  }
94
94
 
95
- return this.#handler.onResponseData(controller, chunk)
95
+ return this.#handler.onResponseData?.(controller, chunk)
96
96
  }
97
97
 
98
98
  onResponseEnd (controller, trailers) {
@@ -90,7 +90,7 @@ class RedirectHandler {
90
90
  this.handler.onRequestUpgrade?.(controller, statusCode, headers, socket)
91
91
  }
92
92
 
93
- onResponseStart (controller, statusCode, statusMessage, headers) {
93
+ onResponseStart (controller, statusCode, headers, statusMessage) {
94
94
  if (this.opts.throwOnMaxRedirect && this.history.length >= this.maxRedirections) {
95
95
  throw new Error('max redirects')
96
96
  }
@@ -125,7 +125,7 @@ class RedirectHandler {
125
125
  }
126
126
 
127
127
  if (!this.location) {
128
- this.handler.onResponseStart?.(controller, statusCode, statusMessage, headers)
128
+ this.handler.onResponseStart?.(controller, statusCode, headers, statusMessage)
129
129
  return
130
130
  }
131
131
 
@@ -3,9 +3,9 @@ const assert = require('node:assert')
3
3
 
4
4
  const { kRetryHandlerDefaultRetry } = require('../core/symbols')
5
5
  const { RequestRetryError } = require('../core/errors')
6
+ const WrapHandler = require('./wrap-handler')
6
7
  const {
7
8
  isDisturbed,
8
- parseHeaders,
9
9
  parseRangeHeader,
10
10
  wrapRequestBody
11
11
  } = require('../core/util')
@@ -16,7 +16,7 @@ function calculateRetryAfterHeader (retryAfter) {
16
16
  }
17
17
 
18
18
  class RetryHandler {
19
- constructor (opts, handlers) {
19
+ constructor (opts, { dispatch, handler }) {
20
20
  const { retryOptions, ...dispatchOpts } = opts
21
21
  const {
22
22
  // Retry scoped
@@ -32,12 +32,9 @@ class RetryHandler {
32
32
  statusCodes
33
33
  } = retryOptions ?? {}
34
34
 
35
- this.dispatch = handlers.dispatch
36
- this.handler = handlers.handler
35
+ this.dispatch = dispatch
36
+ this.handler = WrapHandler.wrap(handler)
37
37
  this.opts = { ...dispatchOpts, body: wrapRequestBody(opts.body) }
38
- this.abort = null
39
- this.aborted = false
40
- this.connectCalled = false
41
38
  this.retryOpts = {
42
39
  retry: retryFn ?? RetryHandler[kRetryHandlerDefaultRetry],
43
40
  retryAfter: retryAfter ?? true,
@@ -65,37 +62,20 @@ class RetryHandler {
65
62
 
66
63
  this.retryCount = 0
67
64
  this.retryCountCheckpoint = 0
65
+ this.headersSent = false
68
66
  this.start = 0
69
67
  this.end = null
70
68
  this.etag = null
71
- this.resume = null
72
69
  }
73
70
 
74
- onRequestSent () {
75
- if (this.handler.onRequestSent) {
76
- this.handler.onRequestSent()
71
+ onRequestStart (controller, context) {
72
+ if (!this.headersSent) {
73
+ this.handler.onRequestStart?.(controller, context)
77
74
  }
78
75
  }
79
76
 
80
- onUpgrade (statusCode, headers, socket) {
81
- if (this.handler.onUpgrade) {
82
- this.handler.onUpgrade(statusCode, headers, socket)
83
- }
84
- }
85
-
86
- onConnect (abort, context) {
87
- this.abort = abort
88
- if (!this.connectCalled) {
89
- this.connectCalled = true
90
- this.handler.onConnect(reason => {
91
- this.aborted = true
92
- this.abort(reason)
93
- }, context)
94
- }
95
- }
96
-
97
- onBodySent (chunk) {
98
- if (this.handler.onBodySent) return this.handler.onBodySent(chunk)
77
+ onRequestUpgrade (controller, statusCode, headers, socket) {
78
+ this.handler.onRequestUpgrade?.(controller, statusCode, headers, socket)
99
79
  }
100
80
 
101
81
  static [kRetryHandlerDefaultRetry] (err, { state, opts }, cb) {
@@ -153,83 +133,68 @@ class RetryHandler {
153
133
  ? Math.min(retryAfterHeader, maxTimeout)
154
134
  : Math.min(minTimeout * timeoutFactor ** (counter - 1), maxTimeout)
155
135
 
156
- setTimeout(() => cb(null), retryTimeout)
136
+ setTimeout(() => cb(null), retryTimeout).unref()
157
137
  }
158
138
 
159
- onHeaders (statusCode, rawHeaders, resume, statusMessage) {
160
- const headers = parseHeaders(rawHeaders)
161
-
139
+ onResponseStart (controller, statusCode, headers, statusMessage) {
162
140
  this.retryCount += 1
163
141
 
164
142
  if (statusCode >= 300) {
165
143
  if (this.retryOpts.statusCodes.includes(statusCode) === false) {
166
- return this.handler.onHeaders(
144
+ this.headersSent = true
145
+ this.handler.onResponseStart?.(
146
+ controller,
167
147
  statusCode,
168
- rawHeaders,
169
- resume,
148
+ headers,
170
149
  statusMessage
171
150
  )
151
+ return
172
152
  } else {
173
- this.abort(
174
- new RequestRetryError('Request failed', statusCode, {
175
- headers,
176
- data: {
177
- count: this.retryCount
178
- }
179
- })
180
- )
181
- return false
153
+ throw new RequestRetryError('Request failed', statusCode, {
154
+ headers,
155
+ data: {
156
+ count: this.retryCount
157
+ }
158
+ })
182
159
  }
183
160
  }
184
161
 
185
162
  // Checkpoint for resume from where we left it
186
- if (this.resume != null) {
187
- this.resume = null
188
-
163
+ if (this.headersSent) {
189
164
  // Only Partial Content 206 supposed to provide Content-Range,
190
165
  // any other status code that partially consumed the payload
191
166
  // should not be retried because it would result in downstream
192
167
  // wrongly concatenate multiple responses.
193
168
  if (statusCode !== 206 && (this.start > 0 || statusCode !== 200)) {
194
- this.abort(
195
- new RequestRetryError('server does not support the range header and the payload was partially consumed', statusCode, {
196
- headers,
197
- data: { count: this.retryCount }
198
- })
199
- )
200
- return false
169
+ throw new RequestRetryError('server does not support the range header and the payload was partially consumed', statusCode, {
170
+ headers,
171
+ data: { count: this.retryCount }
172
+ })
201
173
  }
202
174
 
203
175
  const contentRange = parseRangeHeader(headers['content-range'])
204
176
  // If no content range
205
177
  if (!contentRange) {
206
- this.abort(
207
- new RequestRetryError('Content-Range mismatch', statusCode, {
208
- headers,
209
- data: { count: this.retryCount }
210
- })
211
- )
212
- return false
178
+ throw new RequestRetryError('Content-Range mismatch', statusCode, {
179
+ headers,
180
+ data: { count: this.retryCount }
181
+ })
213
182
  }
214
183
 
215
184
  // Let's start with a weak etag check
216
185
  if (this.etag != null && this.etag !== headers.etag) {
217
- this.abort(
218
- new RequestRetryError('ETag mismatch', statusCode, {
219
- headers,
220
- data: { count: this.retryCount }
221
- })
222
- )
223
- return false
186
+ throw new RequestRetryError('ETag mismatch', statusCode, {
187
+ headers,
188
+ data: { count: this.retryCount }
189
+ })
224
190
  }
225
191
 
226
- const { start, size, end = size - 1 } = contentRange
192
+ const { start, size, end = size ? size - 1 : null } = contentRange
227
193
 
228
194
  assert(this.start === start, 'content-range mismatch')
229
195
  assert(this.end == null || this.end === end, 'content-range mismatch')
230
196
 
231
- this.resume = resume
232
- return true
197
+ return
233
198
  }
234
199
 
235
200
  if (this.end == null) {
@@ -238,15 +203,17 @@ class RetryHandler {
238
203
  const range = parseRangeHeader(headers['content-range'])
239
204
 
240
205
  if (range == null) {
241
- return this.handler.onHeaders(
206
+ this.headersSent = true
207
+ this.handler.onResponseStart?.(
208
+ controller,
242
209
  statusCode,
243
- rawHeaders,
244
- resume,
210
+ headers,
245
211
  statusMessage
246
212
  )
213
+ return
247
214
  }
248
215
 
249
- const { start, size, end = size - 1 } = range
216
+ const { start, size, end = size ? size - 1 : null } = range
250
217
  assert(
251
218
  start != null && Number.isFinite(start),
252
219
  'content-range mismatch'
@@ -269,7 +236,7 @@ class RetryHandler {
269
236
  'invalid content-length'
270
237
  )
271
238
 
272
- this.resume = resume
239
+ this.resume = true
273
240
  this.etag = headers.etag != null ? headers.etag : null
274
241
 
275
242
  // Weak etags are not useful for comparison nor cache
@@ -283,38 +250,36 @@ class RetryHandler {
283
250
  this.etag = null
284
251
  }
285
252
 
286
- return this.handler.onHeaders(
253
+ this.headersSent = true
254
+ this.handler.onResponseStart?.(
255
+ controller,
287
256
  statusCode,
288
- rawHeaders,
289
- resume,
257
+ headers,
290
258
  statusMessage
291
259
  )
260
+ } else {
261
+ throw new RequestRetryError('Request failed', statusCode, {
262
+ headers,
263
+ data: { count: this.retryCount }
264
+ })
292
265
  }
293
-
294
- const err = new RequestRetryError('Request failed', statusCode, {
295
- headers,
296
- data: { count: this.retryCount }
297
- })
298
-
299
- this.abort(err)
300
-
301
- return false
302
266
  }
303
267
 
304
- onData (chunk) {
268
+ onResponseData (controller, chunk) {
305
269
  this.start += chunk.length
306
270
 
307
- return this.handler.onData(chunk)
271
+ this.handler.onResponseData?.(controller, chunk)
308
272
  }
309
273
 
310
- onComplete (rawTrailers) {
274
+ onResponseEnd (controller, trailers) {
311
275
  this.retryCount = 0
312
- return this.handler.onComplete(rawTrailers)
276
+ return this.handler.onResponseEnd?.(controller, trailers)
313
277
  }
314
278
 
315
- onError (err) {
316
- if (this.aborted || isDisturbed(this.opts.body)) {
317
- return this.handler.onError(err)
279
+ onResponseError (controller, err) {
280
+ if (!controller || controller.aborted || isDisturbed(this.opts.body)) {
281
+ this.handler.onResponseError?.(controller, err)
282
+ return
318
283
  }
319
284
 
320
285
  // We reconcile in case of a mix between network errors
@@ -343,8 +308,8 @@ class RetryHandler {
343
308
  * @returns
344
309
  */
345
310
  function onRetry (err) {
346
- if (err != null || this.aborted || isDisturbed(this.opts.body)) {
347
- return this.handler.onError(err)
311
+ if (err != null || controller?.aborted || isDisturbed(this.opts.body)) {
312
+ return this.handler.onResponseError?.(controller, err)
348
313
  }
349
314
 
350
315
  if (this.start !== 0) {
@@ -368,7 +333,7 @@ class RetryHandler {
368
333
  this.retryCountCheckpoint = this.retryCount
369
334
  this.dispatch(this.opts, this)
370
335
  } catch (err) {
371
- this.handler.onError(err)
336
+ this.handler.onResponseError?.(controller, err)
372
337
  }
373
338
  }
374
339
  }
@@ -73,7 +73,7 @@ module.exports = class UnwrapHandler {
73
73
 
74
74
  onHeaders (statusCode, rawHeaders, resume, statusMessage) {
75
75
  this.#controller[kResume] = resume
76
- this.#handler.onResponseStart?.(this.#controller, statusCode, statusMessage, parseHeaders(rawHeaders))
76
+ this.#handler.onResponseStart?.(this.#controller, statusCode, parseHeaders(rawHeaders), statusMessage)
77
77
  return !this.#controller.paused
78
78
  }
79
79
 
@@ -38,7 +38,7 @@ module.exports = class WrapHandler {
38
38
 
39
39
  onError (err) {
40
40
  if (!this.#handler.onError) {
41
- throw new InvalidArgumentError('invalid onError method')
41
+ throw err
42
42
  }
43
43
 
44
44
  return this.#handler.onError?.(err)
@@ -60,7 +60,7 @@ module.exports = class WrapHandler {
60
60
  this.#handler.onUpgrade?.(statusCode, rawHeaders, socket)
61
61
  }
62
62
 
63
- onResponseStart (controller, statusCode, statusMessage, headers) {
63
+ onResponseStart (controller, statusCode, headers, statusMessage) {
64
64
  const rawHeaders = []
65
65
  for (const [key, val] of Object.entries(headers)) {
66
66
  // TODO (fix): What if val is Array
@@ -163,7 +163,7 @@ function sendCachedValue (handler, opts, result, age, context) {
163
163
  // TODO (fix): What if headers.age already exists?
164
164
  const headers = age != null ? { ...result.headers, age: String(age) } : result.headers
165
165
 
166
- handler.onResponseStart?.(controller, result.statusCode, result.statusMessage, headers)
166
+ handler.onResponseStart?.(controller, result.statusCode, headers, result.statusMessage)
167
167
 
168
168
  if (opts.method === 'HEAD') {
169
169
  stream.destroy()
@@ -195,9 +195,6 @@ function handleResult (
195
195
  if (!result) {
196
196
  return handleUncachedResponse(dispatch, globalOpts, cacheKey, handler, opts, reqCacheControl)
197
197
  }
198
- if (!result.body && opts.method !== 'HEAD') {
199
- throw new Error('body is undefined but method isn\'t HEAD')
200
- }
201
198
 
202
199
  const now = Date.now()
203
200
  if (now > result.deleteAt) {
@@ -4,7 +4,7 @@ const { parseHeaders } = require('../core/util')
4
4
  const DecoratorHandler = require('../handler/decorator-handler')
5
5
  const { ResponseError } = require('../core/errors')
6
6
 
7
- class Handler extends DecoratorHandler {
7
+ class ResponseErrorHandler extends DecoratorHandler {
8
8
  #handler
9
9
  #statusCode
10
10
  #contentType
@@ -66,7 +66,7 @@ class Handler extends DecoratorHandler {
66
66
  Error.stackTraceLimit = 0
67
67
  try {
68
68
  err = new ResponseError('Response Error', this.#statusCode, {
69
- data: this.#body,
69
+ body: this.#body,
70
70
  headers: this.#headers
71
71
  })
72
72
  } finally {
@@ -84,6 +84,10 @@ class Handler extends DecoratorHandler {
84
84
  }
85
85
  }
86
86
 
87
- module.exports = (dispatch) => (opts, handler) => opts.throwOnError
88
- ? dispatch(opts, new Handler(opts, { handler }))
89
- : dispatch(opts, handler)
87
+ module.exports = () => {
88
+ return (dispatch) => {
89
+ return function Intercept (opts, handler) {
90
+ return dispatch(opts, new ResponseErrorHandler(opts, { handler }))
91
+ }
92
+ }
93
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "undici",
3
- "version": "7.0.0-alpha.8",
3
+ "version": "7.0.0-alpha.9",
4
4
  "description": "An HTTP/1.1 client, written from scratch for Node.js",
5
5
  "homepage": "https://undici.nodejs.org",
6
6
  "bugs": {
@@ -226,7 +226,7 @@ declare namespace Dispatcher {
226
226
  export interface DispatchHandler {
227
227
  onRequestStart?(controller: DispatchController, context: any): void;
228
228
  onRequestUpgrade?(controller: DispatchController, statusCode: number, headers: IncomingHttpHeaders, socket: Duplex): void;
229
- onResponseStart?(controller: DispatchController, statusCode: number, statusMessage: string | null, headers: IncomingHttpHeaders): void;
229
+ onResponseStart?(controller: DispatchController, statusCode: number, headers: IncomingHttpHeaders, statusMessage?: string): void;
230
230
  onResponseData?(controller: DispatchController, chunk: Buffer): void;
231
231
  onResponseEnd?(controller: DispatchController, trailers: IncomingHttpHeaders): void;
232
232
  onResponseError?(controller: DispatchController, error: Error): void;