@nxtedition/nxt-undici 6.1.2 → 6.1.4

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
@@ -60,17 +60,17 @@ function wrapDispatch(dispatcher) {
60
60
  wrappedDispatcher = compose(
61
61
  dispatcher,
62
62
  interceptors.log({ bindings: { intercept: 'upstream' } }),
63
- interceptors.responseError(),
64
- interceptors.requestBodyFactory(),
65
63
  interceptors.dns(),
64
+ interceptors.requestBodyFactory(),
66
65
  interceptors.lookup(),
67
- interceptors.requestId(),
68
66
  interceptors.responseRetry(),
69
67
  interceptors.responseVerify(),
70
68
  interceptors.proxy(),
71
69
  interceptors.cache(),
72
70
  interceptors.redirect(),
73
71
  interceptors.log({ bindings: { intercept: 'downstream' } }),
72
+ interceptors.requestId(),
73
+ interceptors.responseError(),
74
74
  (dispatch) => (opts, handler) => {
75
75
  const headers = parseHeaders(opts.headers)
76
76
 
@@ -88,7 +88,7 @@ function wrapDispatch(dispatcher) {
88
88
  headers,
89
89
  signal: opts.signal,
90
90
  reset: opts.reset ?? false,
91
- blocking: opts.blocking ?? opts.method !== 'HEAD',
91
+ blocking: opts.blocking ?? true,
92
92
  headersTimeout: opts.headersTimeout,
93
93
  bodyTimeout: opts.bodyTimeout,
94
94
  idempotent: opts.idempotent,
@@ -96,8 +96,8 @@ function wrapDispatch(dispatcher) {
96
96
  proxy: opts.proxy ?? false,
97
97
  cache: opts.cache ?? false,
98
98
  upgrade: opts.upgrade ?? false,
99
- follow: opts.follow ?? 8,
100
- error: opts.error ?? true,
99
+ follow: opts.follow ?? opts.redirect ?? 8,
100
+ error: opts.error ?? opts.throwOnError ?? true,
101
101
  verify: opts.verify ?? false,
102
102
  logger: opts.logger ?? null,
103
103
  dns: opts.dns ?? true,
@@ -23,11 +23,7 @@ class Handler extends DecoratorHandler {
23
23
  }
24
24
 
25
25
  onError(err) {
26
- if (err?.statusCode == null) {
27
- this.#callback(err)
28
- } else if (err.statusCode) {
29
- this.#callback(null, err.statusCode)
30
- }
26
+ this.#callback(err, this.#statusCode)
31
27
  super.onError(err)
32
28
  }
33
29
  }
@@ -124,10 +120,10 @@ export default () => (dispatch) => {
124
120
  new Handler(handler, (err, statusCode) => {
125
121
  record.pending--
126
122
 
127
- if (statusCode != null && statusCode >= 500) {
128
- record.errored++
129
- } else if (err != null && err.name !== 'AbortError') {
123
+ if (err != null && err.name !== 'AbortError') {
130
124
  record.expires = 0
125
+ } else if (statusCode != null && statusCode >= 500) {
126
+ record.errored++
131
127
  }
132
128
  }),
133
129
  )
@@ -172,7 +172,9 @@ export default () => (dispatch) => (opts, handler) => {
172
172
  // semantics do not anticipate such a body.
173
173
  // undici will error if provided an unexpected content-length: 0 header.
174
174
  }
175
- if (key === 'expect') {
175
+ if (key[0] === ':') {
176
+ // strip pseudo headers
177
+ } else if (key === 'expect') {
176
178
  // undici doesn't support expect header.
177
179
  } else {
178
180
  obj[key] = val
@@ -122,6 +122,6 @@ class Handler extends DecoratorHandler {
122
122
  }
123
123
 
124
124
  export default () => (dispatch) => (opts, handler) =>
125
- opts.throwOnError !== false
125
+ opts.throwOnError !== false && opts.error !== false
126
126
  ? dispatch(opts, new Handler(opts, { handler }))
127
127
  : dispatch(opts, handler)
@@ -1,5 +1,6 @@
1
1
  import assert from 'node:assert'
2
- import { DecoratorHandler, isDisturbed, parseRangeHeader, retry as retryFn } from '../utils.js'
2
+ import tp from 'node:timers/promises'
3
+ import { DecoratorHandler, isDisturbed, parseRangeHeader } from '../utils.js'
3
4
 
4
5
  // TODO (fix): What about onUpgrade?
5
6
  class Handler extends DecoratorHandler {
@@ -10,6 +11,8 @@ class Handler extends DecoratorHandler {
10
11
  #headersSent = false
11
12
  #errorSent = false
12
13
 
14
+ #statusCode = 0
15
+
13
16
  #abort
14
17
  #aborted = false
15
18
  #reason
@@ -37,6 +40,8 @@ class Handler extends DecoratorHandler {
37
40
  }
38
41
 
39
42
  onConnect(abort) {
43
+ this.#statusCode = 0
44
+
40
45
  if (!this.#headersSent) {
41
46
  this.#pos = null
42
47
  this.#end = null
@@ -101,6 +106,9 @@ class Handler extends DecoratorHandler {
101
106
  this.#pos = 0
102
107
  this.#end = contentLength
103
108
  this.#etag = headers.etag
109
+ } else if (statusCode >= 400) {
110
+ this.#statusCode = statusCode
111
+ return true
104
112
  } else {
105
113
  return this.#onHeaders(statusCode, headers, resume)
106
114
  }
@@ -144,6 +152,11 @@ class Handler extends DecoratorHandler {
144
152
  }
145
153
 
146
154
  onData(chunk) {
155
+ if (this.#statusCode >= 400) {
156
+ // TODO (fix): Limit the amount of data we read?
157
+ return true
158
+ }
159
+
147
160
  if (this.#pos != null) {
148
161
  this.#pos += chunk.byteLength
149
162
  }
@@ -151,6 +164,18 @@ class Handler extends DecoratorHandler {
151
164
  }
152
165
 
153
166
  onError(err) {
167
+ this.#maybeRetry(err)
168
+ }
169
+
170
+ onComplete(trailers) {
171
+ if (this.#statusCode >= 400) {
172
+ this.#maybeRetry({ statusCode: this.#statusCode })
173
+ } else {
174
+ super.onComplete(trailers)
175
+ }
176
+ }
177
+
178
+ #maybeRetry(err) {
154
179
  if (this.#aborted || isDisturbed(this.#opts.body) || (this.#pos && !this.#etag)) {
155
180
  this.#onError(err)
156
181
  return
@@ -221,7 +246,69 @@ class Handler extends DecoratorHandler {
221
246
  }
222
247
 
223
248
  export default () => (dispatch) => (opts, handler) =>
224
- // TODO (fix): HEAD, PUT, PATCH, DELETE, OPTIONS?
225
- opts.retry !== false && opts.method === 'GET' && !opts.upgrade
249
+ opts.retry !== false &&
250
+ !opts.upgrade &&
251
+ (opts.method === 'HEAD' ||
252
+ opts.method === 'GET' ||
253
+ opts.method === 'PUT' ||
254
+ opts.method === 'PATCH' ||
255
+ opts.idempotent)
226
256
  ? dispatch(opts, new Handler(opts, { handler, dispatch }))
227
257
  : dispatch(opts, handler)
258
+
259
+ async function retryFn(err, retryCount, opts) {
260
+ let retryOpts = opts?.retry
261
+
262
+ if (!retryOpts) {
263
+ throw err
264
+ }
265
+
266
+ if (typeof retryOpts === 'function') {
267
+ return retryOpts(err, retryCount, opts, () => retryFn(err, retryCount, opts))
268
+ }
269
+
270
+ if (typeof retryOpts === 'number') {
271
+ retryOpts = { count: retryOpts }
272
+ }
273
+
274
+ const retryMax = retryOpts?.count ?? 8
275
+
276
+ if (retryCount > retryMax) {
277
+ throw err
278
+ }
279
+
280
+ const statusCode = err.statusCode ?? err.status ?? err.$metadata?.httpStatusCode ?? null
281
+
282
+ if (statusCode && [420, 429, 502, 503, 504].includes(statusCode)) {
283
+ let retryAfter = err.headers?.['retry-after'] ? err.headers['retry-after'] * 1e3 : null
284
+ retryAfter = Number.isFinite(retryAfter) ? retryAfter : Math.min(10e3, retryCount * 1e3)
285
+ if (retryAfter != null && Number.isFinite(retryAfter)) {
286
+ return tp.setTimeout(retryAfter, undefined, { signal: opts.signal })
287
+ } else {
288
+ return null
289
+ }
290
+ }
291
+
292
+ if (
293
+ err.code &&
294
+ [
295
+ 'ECONNRESET',
296
+ 'ECONNREFUSED',
297
+ 'ENOTFOUND',
298
+ 'ENETDOWN',
299
+ 'ENETUNREACH',
300
+ 'EHOSTDOWN',
301
+ 'EHOSTUNREACH',
302
+ 'EPIPE',
303
+ 'UND_ERR_CONNECT_TIMEOUT',
304
+ ].includes(err.code)
305
+ ) {
306
+ return tp.setTimeout(Math.min(10e3, retryCount * 1e3), undefined, { signal: opts.signal })
307
+ }
308
+
309
+ if (err.message && ['other side closed'].includes(err.message)) {
310
+ return tp.setTimeout(Math.min(10e3, retryCount * 1e3), undefined, { signal: opts.signal })
311
+ }
312
+
313
+ throw err
314
+ }
package/lib/utils.js CHANGED
@@ -1,4 +1,3 @@
1
- import tp from 'node:timers/promises'
2
1
  import cacheControlParser from 'cache-control-parser'
3
2
  import stream from 'node:stream'
4
3
  import { util } from '@nxtedition/undici'
@@ -79,63 +78,6 @@ export function parseContentRange(range) {
79
78
  return { start, end: end ? end + 1 : size, size }
80
79
  }
81
80
 
82
- export async function retry(err, retryCount, opts) {
83
- let retryOpts = opts?.retry
84
-
85
- if (!retryOpts) {
86
- throw err
87
- }
88
-
89
- if (typeof retryOpts === 'function') {
90
- return retryOpts(err, retryCount, opts, () => retry(err, retryCount, opts))
91
- }
92
-
93
- if (typeof retryOpts === 'number') {
94
- retryOpts = { count: retryOpts }
95
- }
96
-
97
- const retryMax = retryOpts?.count ?? 8
98
-
99
- if (retryCount > retryMax) {
100
- throw err
101
- }
102
-
103
- const statusCode = err.statusCode ?? err.status ?? err.$metadata?.httpStatusCode ?? null
104
-
105
- if (statusCode && [420, 429, 502, 503, 504].includes(statusCode)) {
106
- let retryAfter = err.headers?.['retry-after'] ? err.headers['retry-after'] * 1e3 : null
107
- retryAfter = Number.isFinite(retryAfter) ? retryAfter : Math.min(10e3, retryCount * 1e3)
108
- if (retryAfter != null && Number.isFinite(retryAfter)) {
109
- return tp.setTimeout(retryAfter, undefined, { signal: opts.signal })
110
- } else {
111
- return null
112
- }
113
- }
114
-
115
- if (
116
- err.code &&
117
- [
118
- 'ECONNRESET',
119
- 'ECONNREFUSED',
120
- 'ENOTFOUND',
121
- 'ENETDOWN',
122
- 'ENETUNREACH',
123
- 'EHOSTDOWN',
124
- 'EHOSTUNREACH',
125
- 'EPIPE',
126
- 'UND_ERR_CONNECT_TIMEOUT',
127
- ].includes(err.code)
128
- ) {
129
- return tp.setTimeout(Math.min(10e3, retryCount * 1e3), undefined, { signal: opts.signal })
130
- }
131
-
132
- if (err.message && ['other side closed'].includes(err.message)) {
133
- return tp.setTimeout(Math.min(10e3, retryCount * 1e3), undefined, { signal: opts.signal })
134
- }
135
-
136
- throw err
137
- }
138
-
139
81
  export function parseURL(url) {
140
82
  if (typeof url === 'string') {
141
83
  url = new URL(url)
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@nxtedition/nxt-undici",
3
- "version": "6.1.2",
3
+ "version": "6.1.4",
4
4
  "license": "MIT",
5
5
  "author": "Robert Nagy <robert.nagy@boffins.se>",
6
6
  "main": "lib/index.js",