@nxtedition/nxt-undici 2.0.51 → 2.0.53

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.
package/lib/index.js CHANGED
@@ -1,31 +1,18 @@
1
1
  import assert from 'node:assert'
2
2
  import createError from 'http-errors'
3
3
  import undici from 'undici'
4
- import { parseHeaders, AbortError, headerNameToString, isStream } from './utils.js'
4
+ import { parseHeaders, AbortError, isStream } from './utils.js'
5
5
  import { BodyReadable } from './readable.js'
6
6
 
7
7
  const dispatcherCache = new WeakMap()
8
8
 
9
- // https://github.com/fastify/fastify/blob/main/lib/reqIdGenFactory.js
10
- // 2,147,483,647 (2^31 − 1) stands for max SMI value (an internal optimization of V8).
11
- // With this upper bound, if you'll be generating 1k ids/sec, you're going to hit it in ~25 days.
12
- // This is very likely to happen in real-world applications, hence the limit is enforced.
13
- // Growing beyond this value will make the id generation slower and cause a deopt.
14
- // In the worst cases, it will become a float, losing accuracy.
15
- const maxInt = 2147483647
16
- let nextReqId = Math.floor(Math.random() * maxInt)
17
- function genReqId() {
18
- nextReqId = (nextReqId + 1) & maxInt
19
- return `req-${nextReqId.toString(36)}`
20
- }
21
-
22
9
  export const interceptors = {
23
- responseError: (await import('./interceptor/response-error.js')).default,
24
10
  requestBodyFactory: (await import('./interceptor/request-body-factory.js')).default,
25
- responseContent: (await import('./interceptor/response-content.js')).default,
11
+ responseError: (await import('./interceptor/response-error.js')).default,
12
+ responseRetry: (await import('./interceptor/response-retry.js')).default,
13
+ responseVerify: (await import('./interceptor/response-verify.js')).default,
26
14
  log: (await import('./interceptor/log.js')).default,
27
15
  redirect: (await import('./interceptor/redirect.js')).default,
28
- responseRetry: (await import('./interceptor/response-retry.js')).default,
29
16
  proxy: (await import('./interceptor/proxy.js')).default,
30
17
  cache: (await import('./interceptor/cache.js')).default,
31
18
  requestId: (await import('./interceptor/request-id.js')).default,
@@ -59,16 +46,7 @@ export async function request(url, opts) {
59
46
  }
60
47
 
61
48
  const method = opts.method ?? (opts.body ? 'POST' : 'GET')
62
- const idempotent = opts.idempotent ?? (method === 'GET' || method === 'HEAD')
63
-
64
- let headers = {}
65
- if (Array.isArray(opts.headers)) {
66
- headers = parseHeaders(opts.headers)
67
- } else if (opts.headers != null) {
68
- for (const [key, val] of Object.entries(opts.headers)) {
69
- headers[headerNameToString(key)] = val
70
- }
71
- }
49
+ const headers = parseHeaders(opts.headers)
72
50
 
73
51
  const userAgent = opts.userAgent ?? globalThis.userAgent
74
52
  if (userAgent && headers?.['user-agent'] !== userAgent) {
@@ -79,6 +57,7 @@ export async function request(url, opts) {
79
57
  throw new createError.MethodNotAllowed()
80
58
  }
81
59
 
60
+ // TODO (fix): Move into undici?
82
61
  if (
83
62
  headers != null &&
84
63
  (method === 'HEAD' || method === 'GET') &&
@@ -87,6 +66,7 @@ export async function request(url, opts) {
87
66
  throw new createError.BadRequest('HEAD and GET cannot have body')
88
67
  }
89
68
 
69
+ // TODO (fix): Move into undici?
90
70
  if (
91
71
  opts.body != null &&
92
72
  (opts.body.size > 0 || opts.body.length > 0) &&
@@ -104,11 +84,11 @@ export async function request(url, opts) {
104
84
  // semantics do not anticipate such a body.
105
85
 
106
86
  // undici will error if provided an unexpected content-length: 0 header.
107
- headers = { ...headers }
108
87
  delete headers['content-length']
109
88
  }
110
89
 
111
90
  if (isStream(opts.body)) {
91
+ // TODO (fix): Remove this somehow?
112
92
  // Workaround: https://github.com/nodejs/undici/pull/2497
113
93
  opts.body.on('error', () => {})
114
94
  }
@@ -123,7 +103,7 @@ export async function request(url, opts) {
123
103
  dispatch = interceptors.log(dispatch)
124
104
  dispatch = interceptors.requestId(dispatch)
125
105
  dispatch = interceptors.responseRetry(dispatch)
126
- dispatch = interceptors.responseContent(dispatch)
106
+ dispatch = interceptors.responseVerify(dispatch)
127
107
  dispatch = interceptors.cache(dispatch)
128
108
  dispatch = interceptors.redirect(dispatch)
129
109
  dispatch = interceptors.proxy(dispatch)
@@ -133,7 +113,7 @@ export async function request(url, opts) {
133
113
  return await new Promise((resolve, reject) =>
134
114
  dispatch(
135
115
  {
136
- id: opts.id ?? headers['request-id'] ?? headers['Request-Id'] ?? genReqId(),
116
+ id: opts.id,
137
117
  url,
138
118
  method,
139
119
  body: opts.body,
@@ -145,13 +125,15 @@ export async function request(url, opts) {
145
125
  blocking: opts.blocking ?? false,
146
126
  headersTimeout: opts.headersTimeout,
147
127
  bodyTimeout: opts.bodyTimeout,
148
- idempotent,
128
+ idempotent: opts.idempotent,
149
129
  retry: opts.retry ?? 8,
150
- proxy: opts.proxy,
151
- cache: opts.cache,
152
- upgrade: opts.upgrade,
130
+ proxy: opts.proxy ?? false,
131
+ cache: opts.cache ?? false,
132
+ upgrade: opts.upgrade ?? false,
153
133
  follow: opts.follow ?? 8,
154
- logger: opts.logger,
134
+ error: opts.error ?? true,
135
+ verify: opts.verify ?? true,
136
+ logger: opts.logger ?? null,
155
137
  },
156
138
  {
157
139
  resolve,
@@ -117,6 +117,7 @@ export default (dispatch) => (opts, handler) => {
117
117
  return dispatch(opts, handler)
118
118
  }
119
119
 
120
+ // TODO (fix): Cache other methods?
120
121
  if (opts.method !== 'GET' && opts.method !== 'HEAD') {
121
122
  dispatch(opts, handler)
122
123
  return
@@ -17,6 +17,7 @@ export default (dispatch) => (opts, handler) => {
17
17
  dispatch({ ...opts, body }, handler)
18
18
  },
19
19
  (err) => {
20
+ handler.onConnect(() => {})
20
21
  handler.onError(err)
21
22
  },
22
23
  )
@@ -95,4 +95,7 @@ class Handler extends DecoratorHandler {
95
95
  }
96
96
  }
97
97
 
98
- export default (dispatch) => (opts, handler) => dispatch(opts, new Handler(opts, { handler }))
98
+ export default (dispatch) => (opts, handler) =>
99
+ opts.error !== false && opts.throwOnError !== false
100
+ ? dispatch(opts, new Handler(opts, { handler }))
101
+ : dispatch(opts, handler)
@@ -200,7 +200,7 @@ class Handler extends DecoratorHandler {
200
200
 
201
201
  export default (dispatch) => (opts, handler) => {
202
202
  // TODO (fix): HEAD, PUT, PATCH, DELETE, OPTIONS?
203
- return opts.retry && opts.method === 'GET' && !opts.upgrade
203
+ return opts.retry !== false && opts.method === 'GET' && !opts.upgrade
204
204
  ? dispatch(opts, new Handler(opts, { handler, dispatch }))
205
205
  : dispatch(opts, handler)
206
206
  }
@@ -6,6 +6,7 @@ import { DecoratorHandler } from 'undici'
6
6
  class Handler extends DecoratorHandler {
7
7
  #handler
8
8
 
9
+ #verifyOpts
9
10
  #contentMD5
10
11
  #contentLength
11
12
  #hasher
@@ -16,6 +17,7 @@ class Handler extends DecoratorHandler {
16
17
  super(handler)
17
18
 
18
19
  this.#handler = handler
20
+ this.#verifyOpts = opts.verify === true ? { hash: true, size: true } : opts.verify
19
21
  }
20
22
 
21
23
  onConnect(abort) {
@@ -31,8 +33,8 @@ class Handler extends DecoratorHandler {
31
33
  }
32
34
 
33
35
  onHeaders(statusCode, rawHeaders, resume, statusMessage, headers = parseHeaders(rawHeaders)) {
34
- this.#contentMD5 = headers['content-md5']
35
- this.#contentLength = headers['content-length']
36
+ this.#contentMD5 = this.#verifyOpts.hash ? headers['content-md5'] : null
37
+ this.#contentLength = this.#verifyOpts.hash ? headers['content-length'] : null
36
38
  this.#hasher = this.#contentMD5 != null ? crypto.createHash('md5') : null
37
39
 
38
40
  return this.#handler.onHeaders(statusCode, rawHeaders, resume, statusMessage, headers)
@@ -79,4 +81,6 @@ class Handler extends DecoratorHandler {
79
81
  }
80
82
 
81
83
  export default (dispatch) => (opts, handler) =>
82
- !opts.upgrade ? dispatch(opts, new Handler(opts, { handler })) : dispatch(opts, handler)
84
+ !opts.upgrade && opts.verify !== false
85
+ ? dispatch(opts, new Handler(opts, { handler }))
86
+ : dispatch(opts, handler)
package/lib/readable.js CHANGED
@@ -1,11 +1,8 @@
1
1
  import assert from 'node:assert'
2
2
  import { Readable, isDisturbed } from 'node:stream'
3
- import {
4
- RequestAbortedError,
5
- NotSupportedError,
6
- InvalidArgumentError,
7
- AbortError,
8
- } from './errors.js'
3
+ import { errors as undiciErrors } from 'undici'
4
+
5
+ const { RequestAbortedError, NotSupportedError, InvalidArgumentError, AbortError } = undiciErrors
9
6
 
10
7
  const kConsume = Symbol('kConsume')
11
8
  const kReading = Symbol('kReading')
package/lib/utils.js CHANGED
@@ -201,8 +201,86 @@ export function parseOrigin(url) {
201
201
  return url
202
202
  }
203
203
 
204
- export function parseHeaders(headers, obj = {}) {
205
- return Array.isArray(headers) ? util.parseHeaders(headers, obj) : headers
204
+ /**
205
+ * @param {Record<string, string | string[] | null | undefined> | (Buffer | string | (Buffer | string)[])[]} headers
206
+ * @param {Record<string, string | string[]>} [obj]
207
+ * @returns {Record<string, string | string[]>}
208
+ */
209
+ export function parseHeaders(headers, obj) {
210
+ if (obj == null) {
211
+ obj = {}
212
+ } else {
213
+ // TODO (fix): assert obj values type?
214
+ }
215
+
216
+ if (Array.isArray(headers)) {
217
+ for (let i = 0; i < headers.length; i += 2) {
218
+ const key2 = headers[i]
219
+ const val2 = headers[i + 1]
220
+
221
+ // TODO (fix): assert key2 type?
222
+ // TODO (fix): assert val2 type?
223
+
224
+ if (!val2) {
225
+ continue
226
+ }
227
+
228
+ const key = headerNameToString(key2)
229
+ let val = obj[key]
230
+
231
+ if (val) {
232
+ if (typeof val === 'string') {
233
+ val = [val]
234
+ obj[key] = val
235
+ }
236
+
237
+ if (Array.isArray(val2)) {
238
+ val.push(...val2.map((x) => x.toString()))
239
+ } else {
240
+ val.push(val2.toString())
241
+ }
242
+ } else {
243
+ obj[key] = Array.isArray(val2) ? val2.map((x) => x.toString()) : val2.toString()
244
+ }
245
+ }
246
+ } else if (typeof headers === 'object' && headers !== null) {
247
+ for (const key2 of Object.keys(headers)) {
248
+ const val2 = headers[key2]
249
+
250
+ // TODO (fix): assert key2 type?
251
+ // TODO (fix): assert val2 type?
252
+
253
+ if (!val2) {
254
+ continue
255
+ }
256
+
257
+ const key = headerNameToString(key2)
258
+ let val = obj[key]
259
+
260
+ if (val) {
261
+ if (typeof val === 'string') {
262
+ val = [val]
263
+ obj[key] = val
264
+ }
265
+ if (Array.isArray(val2)) {
266
+ val.push(...val2.map((x) => x.toString()))
267
+ } else {
268
+ val.push(val2.toString())
269
+ }
270
+ } else if (val2 != null) {
271
+ obj[key] = Array.isArray(val2) ? val2.map((x) => x.toString()) : val2.toString()
272
+ }
273
+ }
274
+ } else if (headers != null) {
275
+ throw new Error('invalid argument: headers')
276
+ }
277
+
278
+ // See https://github.com/nodejs/node/pull/46528
279
+ if ('content-length' in obj && 'content-disposition' in obj) {
280
+ obj['content-disposition'] = Buffer.from(obj['content-disposition']).toString('latin1')
281
+ }
282
+
283
+ return obj
206
284
  }
207
285
 
208
286
  export class AbortError extends Error {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@nxtedition/nxt-undici",
3
- "version": "2.0.51",
3
+ "version": "2.0.53",
4
4
  "license": "MIT",
5
5
  "author": "Robert Nagy <robert.nagy@boffins.se>",
6
6
  "main": "lib/index.js",
@@ -13,15 +13,15 @@
13
13
  "cacheable-lookup": "^7.0.0",
14
14
  "http-errors": "^2.0.0",
15
15
  "lru-cache": "^10.2.0",
16
- "undici": "^6.13.0"
16
+ "undici": "^6.16.1"
17
17
  },
18
18
  "devDependencies": {
19
- "@types/node": "^20.12.7",
19
+ "@types/node": "^20.12.11",
20
20
  "eslint": "^8.0.0",
21
21
  "eslint-config-prettier": "^9.1.0",
22
22
  "eslint-config-standard": "^17.0.0",
23
23
  "eslint-plugin-import": "^2.29.1",
24
- "eslint-plugin-n": "^17.2.0",
24
+ "eslint-plugin-n": "^17.6.0",
25
25
  "eslint-plugin-promise": "^6.1.1",
26
26
  "husky": "^9.0.11",
27
27
  "lint-staged": "^15.2.2",
package/lib/errors.js DELETED
@@ -1,214 +0,0 @@
1
- export class UndiciError extends Error {
2
- constructor(message) {
3
- super(message)
4
- this.name = 'UndiciError'
5
- this.code = 'UND_ERR'
6
- }
7
- }
8
-
9
- export class ConnectTimeoutError extends UndiciError {
10
- constructor(message) {
11
- super(message)
12
- Error.captureStackTrace(this, ConnectTimeoutError)
13
- this.name = 'ConnectTimeoutError'
14
- this.message = message || 'Connect Timeout Error'
15
- this.code = 'UND_ERR_CONNECT_TIMEOUT'
16
- }
17
- }
18
-
19
- export class HeadersTimeoutError extends UndiciError {
20
- constructor(message) {
21
- super(message)
22
- Error.captureStackTrace(this, HeadersTimeoutError)
23
- this.name = 'HeadersTimeoutError'
24
- this.message = message || 'Headers Timeout Error'
25
- this.code = 'UND_ERR_HEADERS_TIMEOUT'
26
- }
27
- }
28
-
29
- export class HeadersOverflowError extends UndiciError {
30
- constructor(message) {
31
- super(message)
32
- Error.captureStackTrace(this, HeadersOverflowError)
33
- this.name = 'HeadersOverflowError'
34
- this.message = message || 'Headers Overflow Error'
35
- this.code = 'UND_ERR_HEADERS_OVERFLOW'
36
- }
37
- }
38
-
39
- export class BodyTimeoutError extends UndiciError {
40
- constructor(message) {
41
- super(message)
42
- Error.captureStackTrace(this, BodyTimeoutError)
43
- this.name = 'BodyTimeoutError'
44
- this.message = message || 'Body Timeout Error'
45
- this.code = 'UND_ERR_BODY_TIMEOUT'
46
- }
47
- }
48
-
49
- export class ResponseStatusCodeError extends UndiciError {
50
- constructor(message, statusCode, headers, body) {
51
- super(message)
52
- Error.captureStackTrace(this, ResponseStatusCodeError)
53
- this.name = 'ResponseStatusCodeError'
54
- this.message = message || 'Response Status Code Error'
55
- this.code = 'UND_ERR_RESPONSE_STATUS_CODE'
56
- this.body = body
57
- this.status = statusCode
58
- this.statusCode = statusCode
59
- this.headers = headers
60
- }
61
- }
62
-
63
- export class InvalidArgumentError extends UndiciError {
64
- constructor(message) {
65
- super(message)
66
- Error.captureStackTrace(this, InvalidArgumentError)
67
- this.name = 'InvalidArgumentError'
68
- this.message = message || 'Invalid Argument Error'
69
- this.code = 'UND_ERR_INVALID_ARG'
70
- }
71
- }
72
-
73
- export class InvalidReturnValueError extends UndiciError {
74
- constructor(message) {
75
- super(message)
76
- Error.captureStackTrace(this, InvalidReturnValueError)
77
- this.name = 'InvalidReturnValueError'
78
- this.message = message || 'Invalid Return Value Error'
79
- this.code = 'UND_ERR_INVALID_RETURN_VALUE'
80
- }
81
- }
82
-
83
- export class AbortError extends UndiciError {
84
- constructor(message) {
85
- super(message)
86
- Error.captureStackTrace(this, AbortError)
87
- this.name = 'AbortError'
88
- this.message = message || 'The operation was aborted'
89
- }
90
- }
91
-
92
- export class RequestAbortedError extends AbortError {
93
- constructor(message) {
94
- super(message)
95
- Error.captureStackTrace(this, RequestAbortedError)
96
- this.name = 'AbortError'
97
- this.message = message || 'Request aborted'
98
- this.code = 'UND_ERR_ABORTED'
99
- }
100
- }
101
-
102
- export class InformationalError extends UndiciError {
103
- constructor(message) {
104
- super(message)
105
- Error.captureStackTrace(this, InformationalError)
106
- this.name = 'InformationalError'
107
- this.message = message || 'Request information'
108
- this.code = 'UND_ERR_INFO'
109
- }
110
- }
111
-
112
- export class RequestContentLengthMismatchError extends UndiciError {
113
- constructor(message) {
114
- super(message)
115
- Error.captureStackTrace(this, RequestContentLengthMismatchError)
116
- this.name = 'RequestContentLengthMismatchError'
117
- this.message = message || 'Request body length does not match content-length header'
118
- this.code = 'UND_ERR_REQ_CONTENT_LENGTH_MISMATCH'
119
- }
120
- }
121
-
122
- export class ResponseContentLengthMismatchError extends UndiciError {
123
- constructor(message) {
124
- super(message)
125
- Error.captureStackTrace(this, ResponseContentLengthMismatchError)
126
- this.name = 'ResponseContentLengthMismatchError'
127
- this.message = message || 'Response body length does not match content-length header'
128
- this.code = 'UND_ERR_RES_CONTENT_LENGTH_MISMATCH'
129
- }
130
- }
131
-
132
- export class ClientDestroyedError extends UndiciError {
133
- constructor(message) {
134
- super(message)
135
- Error.captureStackTrace(this, ClientDestroyedError)
136
- this.name = 'ClientDestroyedError'
137
- this.message = message || 'The client is destroyed'
138
- this.code = 'UND_ERR_DESTROYED'
139
- }
140
- }
141
-
142
- export class ClientClosedError extends UndiciError {
143
- constructor(message) {
144
- super(message)
145
- Error.captureStackTrace(this, ClientClosedError)
146
- this.name = 'ClientClosedError'
147
- this.message = message || 'The client is closed'
148
- this.code = 'UND_ERR_CLOSED'
149
- }
150
- }
151
-
152
- export class SocketError extends UndiciError {
153
- constructor(message, socket) {
154
- super(message)
155
- Error.captureStackTrace(this, SocketError)
156
- this.name = 'SocketError'
157
- this.message = message || 'Socket error'
158
- this.code = 'UND_ERR_SOCKET'
159
- this.socket = socket
160
- }
161
- }
162
-
163
- export class NotSupportedError extends UndiciError {
164
- constructor(message) {
165
- super(message)
166
- Error.captureStackTrace(this, NotSupportedError)
167
- this.name = 'NotSupportedError'
168
- this.message = message || 'Not supported error'
169
- this.code = 'UND_ERR_NOT_SUPPORTED'
170
- }
171
- }
172
-
173
- export class BalancedPoolMissingUpstreamError extends UndiciError {
174
- constructor(message) {
175
- super(message)
176
- Error.captureStackTrace(this, NotSupportedError)
177
- this.name = 'MissingUpstreamError'
178
- this.message = message || 'No upstream has been added to the BalancedPool'
179
- this.code = 'UND_ERR_BPL_MISSING_UPSTREAM'
180
- }
181
- }
182
-
183
- export class HTTPParserError extends Error {
184
- constructor(message, code, data) {
185
- super(message)
186
- Error.captureStackTrace(this, HTTPParserError)
187
- this.name = 'HTTPParserError'
188
- this.code = code ? `HPE_${code}` : undefined
189
- this.data = data ? data.toString() : undefined
190
- }
191
- }
192
-
193
- export class ResponseExceededMaxSizeError extends UndiciError {
194
- constructor(message) {
195
- super(message)
196
- Error.captureStackTrace(this, ResponseExceededMaxSizeError)
197
- this.name = 'ResponseExceededMaxSizeError'
198
- this.message = message || 'Response content exceeded max size'
199
- this.code = 'UND_ERR_RES_EXCEEDED_MAX_SIZE'
200
- }
201
- }
202
-
203
- export class RequestRetryError extends UndiciError {
204
- constructor(message, code, { headers, data }) {
205
- super(message)
206
- Error.captureStackTrace(this, RequestRetryError)
207
- this.name = 'RequestRetryError'
208
- this.message = message || 'Request retry error'
209
- this.code = 'UND_ERR_REQ_RETRY'
210
- this.statusCode = code
211
- this.data = data
212
- this.headers = headers
213
- }
214
- }