@nxtedition/nxt-undici 6.2.12 → 6.2.14

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.
@@ -42,8 +42,11 @@ export default () => (dispatch) => {
42
42
 
43
43
  if (err) {
44
44
  logger?.error({ err, dns: { hostname } }, 'lookup failed')
45
+
45
46
  resolve([err, null])
46
47
  } else {
48
+ logger?.debug({ dns: { hostname, records } }, 'lookup completed')
49
+
47
50
  const now = getFastNow()
48
51
  const val = records.map(({ address, ttl }) => ({
49
52
  address,
@@ -53,8 +56,6 @@ export default () => (dispatch) => {
53
56
  counter: 0,
54
57
  }))
55
58
 
56
- logger?.debug({ err, dns: { hostname, records } }, 'lookup completed')
57
-
58
59
  cache.set(hostname, val)
59
60
 
60
61
  resolve([null, val])
@@ -1,11 +1,10 @@
1
- import createHttpError from 'http-errors'
2
- import { DecoratorHandler } from '../utils.js'
1
+ import { DecoratorHandler, decorateError } from '../utils.js'
3
2
 
4
3
  class Handler extends DecoratorHandler {
5
4
  #statusCode = 0
6
- #contentType
7
5
  #decoder
8
6
  #headers
7
+ #trailers
9
8
  #body = ''
10
9
  #opts
11
10
 
@@ -15,13 +14,8 @@ class Handler extends DecoratorHandler {
15
14
  this.#opts = opts
16
15
  }
17
16
 
18
- #checkContentType(contentType) {
19
- return (this.#contentType ?? '').indexOf(contentType) === 0
20
- }
21
-
22
17
  onConnect(abort) {
23
18
  this.#statusCode = 0
24
- this.#contentType = null
25
19
  this.#decoder = null
26
20
  this.#headers = null
27
21
  this.#body = ''
@@ -32,14 +26,17 @@ class Handler extends DecoratorHandler {
32
26
  onHeaders(statusCode, headers, resume) {
33
27
  this.#statusCode = statusCode
34
28
  this.#headers = headers
35
- this.#contentType = headers['content-type']
36
29
 
37
30
  if (this.#statusCode < 400) {
38
31
  return super.onHeaders(statusCode, headers, resume)
39
32
  }
40
33
 
41
- if (this.#checkContentType('application/json') || this.#checkContentType('text/plain')) {
34
+ if (
35
+ this.#headers['content-type']?.startsWith('application/json') ||
36
+ this.#headers['content-type']?.startsWith('text/plain')
37
+ ) {
42
38
  this.#decoder = new TextDecoder('utf-8')
39
+ this.#body = ''
43
40
  }
44
41
  }
45
42
 
@@ -52,72 +49,33 @@ class Handler extends DecoratorHandler {
52
49
  }
53
50
 
54
51
  onComplete(trailers) {
55
- if (this.#statusCode >= 400) {
56
- this.#body += this.#decoder?.decode(undefined, { stream: false }) ?? ''
57
-
58
- if (this.#checkContentType('application/json')) {
59
- try {
60
- this.#body = JSON.parse(this.#body)
61
- } catch {
62
- // Do nothing...
63
- }
64
- }
65
-
66
- let err
67
- const stackTraceLimit = Error.stackTraceLimit
68
- Error.stackTraceLimit = 0
69
- try {
70
- err = createHttpError(this.#statusCode)
71
- } finally {
72
- Error.stackTraceLimit = stackTraceLimit
73
- }
74
-
75
- super.onError(this.#decorateError(err))
76
- } else {
77
- super.onComplete(trailers)
52
+ this.#trailers = trailers
53
+
54
+ if (this.#statusCode < 400) {
55
+ return super.onComplete(trailers)
78
56
  }
79
- }
80
57
 
81
- onError(err) {
82
- super.onError(this.#decorateError(err))
58
+ this.#body += this.#decoder?.decode(undefined, { stream: false }) ?? ''
59
+
60
+ super.onError(
61
+ decorateError(null, this.#opts, {
62
+ statusCode: this.#statusCode,
63
+ headers: this.#headers,
64
+ trailers: this.#trailers,
65
+ body: this.#body,
66
+ }),
67
+ )
83
68
  }
84
69
 
85
- #decorateError(err) {
86
- try {
87
- err.url ??= this.#opts.origin ? new URL(this.#opts.path, this.#opts.origin).href : null
88
-
89
- err.req = {
90
- method: this.#opts?.method,
91
- headers: this.#opts?.headers,
92
- body:
93
- // TODO (fix): JSON.stringify POJO
94
- typeof this.#opts?.body !== 'string' || this.#opts.body.length > 1024
95
- ? undefined
96
- : this.#opts.body,
97
- }
98
-
99
- err.res = {
70
+ onError(err) {
71
+ super.onError(
72
+ decorateError(err, this.#opts, {
73
+ statusCode: this.#statusCode,
100
74
  headers: this.#headers,
101
- // TODO (fix): JSON.stringify POJO
102
- body: typeof this.#body !== 'string' || this.#body.length < 1024 ? undefined : this.#body,
103
- }
104
-
105
- if (this.#body) {
106
- if (this.#body.reason != null) {
107
- err.reason ??= this.#body.reason
108
- }
109
- if (this.#body.code != null) {
110
- err.code ??= this.#body.code
111
- }
112
- if (this.#body.error != null) {
113
- err.error ??= this.#body.error
114
- }
115
- }
116
-
117
- return err
118
- } catch (er) {
119
- return new AggregateError([er, err])
120
- }
75
+ trailers: this.#trailers,
76
+ body: null,
77
+ }),
78
+ )
121
79
  }
122
80
  }
123
81
 
@@ -1,7 +1,6 @@
1
1
  import assert from 'node:assert'
2
2
  import tp from 'node:timers/promises'
3
- import { DecoratorHandler, isDisturbed, parseRangeHeader } from '../utils.js'
4
- import createHttpError from 'http-errors'
3
+ import { DecoratorHandler, isDisturbed, decorateError, parseRangeHeader } from '../utils.js'
5
4
 
6
5
  function noop() {}
7
6
 
@@ -11,12 +10,14 @@ class Handler extends DecoratorHandler {
11
10
  #opts
12
11
 
13
12
  #retryCount = 0
13
+ #retryError = null
14
14
  #headersSent = false
15
15
  #errorSent = false
16
16
 
17
17
  #statusCode = 0
18
- #headers = null
19
- #trailers = null
18
+ #headers
19
+ #trailers
20
+ #body
20
21
 
21
22
  #abort
22
23
  #aborted = false
@@ -26,7 +27,6 @@ class Handler extends DecoratorHandler {
26
27
  #pos
27
28
  #end
28
29
  #etag
29
- #error
30
30
 
31
31
  constructor(opts, { handler, dispatch }) {
32
32
  super(handler)
@@ -47,13 +47,13 @@ class Handler extends DecoratorHandler {
47
47
  onConnect(abort) {
48
48
  this.#statusCode = 0
49
49
  this.#headers = null
50
+ this.#body = null
50
51
  this.#trailers = null
51
52
 
52
53
  if (!this.#headersSent) {
53
54
  this.#pos = null
54
55
  this.#end = null
55
56
  this.#etag = null
56
- this.#error = null
57
57
  this.#resume = null
58
58
 
59
59
  super.onConnect((reason) => {
@@ -79,25 +79,28 @@ class Handler extends DecoratorHandler {
79
79
  this.#statusCode = statusCode
80
80
  this.#headers = headers
81
81
 
82
- if (this.#error == null) {
82
+ if (!this.#headersSent) {
83
83
  assert(this.#etag == null)
84
84
  assert(this.#pos == null)
85
85
  assert(this.#end == null)
86
86
  assert(this.#headersSent === false)
87
87
 
88
88
  if (headers.trailer) {
89
- return this.#onHeaders(statusCode, headers, resume)
89
+ this.#headersSent = true
90
+ return super.onHeaders(statusCode, headers, resume)
90
91
  }
91
92
 
92
93
  const contentLength = headers['content-length'] ? Number(headers['content-length']) : null
93
94
  if (contentLength != null && !Number.isFinite(contentLength)) {
94
- return this.#onHeaders(statusCode, headers, resume)
95
+ this.#headersSent = true
96
+ return super.onHeaders(statusCode, headers, resume)
95
97
  }
96
98
 
97
99
  if (statusCode === 206) {
98
100
  const range = parseRangeHeader(headers['content-range'])
99
101
  if (!range) {
100
- return this.#onHeaders(statusCode, headers, resume)
102
+ this.#headersSent = true
103
+ return super.onHeaders(statusCode, headers, resume)
101
104
  }
102
105
 
103
106
  const { start, size, end = size } = range
@@ -117,9 +120,11 @@ class Handler extends DecoratorHandler {
117
120
  this.#end = contentLength
118
121
  this.#etag = headers.etag
119
122
  } else if (statusCode >= 400) {
123
+ this.#body = []
120
124
  return true
121
125
  } else {
122
- return this.#onHeaders(statusCode, headers, resume)
126
+ this.#headersSent = true
127
+ return super.onHeaders(statusCode, headers, resume)
123
128
  }
124
129
 
125
130
  // Weak etags are not useful for comparison nor cache
@@ -134,17 +139,20 @@ class Handler extends DecoratorHandler {
134
139
 
135
140
  this.#resume = resume
136
141
 
137
- return this.#onHeaders(statusCode, headers, () => this.#resume?.())
142
+ this.#headersSent = true
143
+ return super.onHeaders(statusCode, headers, () => this.#resume?.())
138
144
  } else if (statusCode === 206 || (this.#pos === 0 && statusCode === 200)) {
139
145
  assert(this.#etag != null || !this.#pos)
140
146
 
141
147
  if (this.#pos > 0 && this.#etag !== headers.etag) {
142
- throw this.#error
148
+ this.#maybeError(null)
149
+ return null
143
150
  }
144
151
 
145
152
  const contentRange = parseRangeHeader(headers['content-range'])
146
153
  if (!contentRange) {
147
- throw this.#error
154
+ this.#maybeError(null)
155
+ return null
148
156
  }
149
157
 
150
158
  const { start, size, end = size } = contentRange
@@ -156,72 +164,93 @@ class Handler extends DecoratorHandler {
156
164
  // TODO (fix): What if we were paused before the error?
157
165
  return true
158
166
  } else {
159
- throw this.#error
167
+ this.#maybeError(this.#retryError)
160
168
  }
161
169
  }
162
170
 
163
171
  onData(chunk) {
164
- if (this.#statusCode >= 400) {
165
- // TODO (fix): Limit the amount of data we read?
166
- return true
167
- }
168
-
169
172
  if (this.#pos != null) {
170
173
  this.#pos += chunk.byteLength
171
174
  }
172
- return super.onData(chunk)
173
- }
174
175
 
175
- onError(err) {
176
- this.#maybeRetry(err)
176
+ if (this.#statusCode < 400) {
177
+ return super.onData(chunk)
178
+ }
179
+
180
+ this.#body?.push(chunk)
177
181
  }
178
182
 
179
183
  onComplete(trailers) {
180
184
  this.#trailers = trailers
181
185
 
182
- if (this.#statusCode >= 400) {
183
- this.#maybeRetry(null, this.#statusCode)
184
- } else {
185
- super.onComplete(trailers)
186
+ if (this.#statusCode < 400) {
187
+ return super.onComplete(trailers)
188
+ }
189
+
190
+ this.#maybeRetry(null)
191
+ }
192
+
193
+ onError(err) {
194
+ this.#maybeRetry(err)
195
+ }
196
+
197
+ #maybeAbort(err) {
198
+ if (this.#abort && !this.#aborted) {
199
+ this.#aborted = true
200
+ this.#abort(err)
186
201
  }
187
202
  }
188
203
 
189
204
  #maybeError(err) {
190
205
  if (err) {
191
- this.#onError(err)
192
- } else {
193
- assert(this.#aborted === false, 'unexpected aborted')
194
- assert(this.#statusCode, 'unexpected statusCode')
195
- assert(this.#headers, 'unexpected headers')
196
-
197
- this.#onHeaders(this.#statusCode, this.#headers, noop)
206
+ if (!this.#errorSent) {
207
+ this.#errorSent = true
208
+ super.onError(err)
209
+ }
210
+ } else if (!this.#headersSent) {
211
+ super.onHeaders(this.#statusCode, this.#headers, noop)
212
+ if (this.#aborted) {
213
+ return
214
+ }
198
215
 
199
- if (!this.#aborted) {
200
- super.onComplete(this.#trailers)
216
+ if (this.#body) {
217
+ for (const chunk of this.#body) {
218
+ super.onData(chunk)
219
+ if (this.#aborted) {
220
+ return
221
+ }
222
+ }
201
223
  }
224
+
225
+ super.onComplete(this.#trailers)
202
226
  }
227
+
228
+ this.#maybeAbort(err)
203
229
  }
204
230
 
205
- #maybeRetry(err, statusCode) {
231
+ #maybeRetry(err) {
206
232
  if (this.#aborted || isDisturbed(this.#opts.body) || (this.#pos && !this.#etag)) {
207
233
  this.#maybeError(err)
208
234
  return
209
235
  }
210
236
 
211
- if (!err) {
212
- // TOOD (fix): Avoid creating an Error and do onHeaders + onComplete.
213
- const stackTraceLimit = Error.stackTraceLimit
214
- Error.stackTraceLimit = 0
215
- try {
216
- err = createHttpError(statusCode ?? 500)
217
- } finally {
218
- Error.stackTraceLimit = stackTraceLimit
219
- }
220
- }
221
-
222
237
  let retryPromise
223
238
  try {
224
- retryPromise = retryFn(err, this.#retryCount, this.#opts)
239
+ if (typeof this.#opts.retry === 'function') {
240
+ retryPromise = this.#opts.retry(
241
+ decorateError(err, this.#opts, {
242
+ statusCode: this.#statusCode,
243
+ headers: this.#headers,
244
+ trailers: this.#trailers,
245
+ body: this.#body,
246
+ }),
247
+ this.#retryCount,
248
+ this.#opts,
249
+ () => this.#retryFn(err, this.#retryCount, this.#opts),
250
+ )
251
+ } else {
252
+ retryPromise = this.#retryFn(err, this.#retryCount, this.#opts)
253
+ }
225
254
  } catch (err) {
226
255
  retryPromise = Promise.reject(err)
227
256
  }
@@ -231,123 +260,97 @@ class Handler extends DecoratorHandler {
231
260
  return
232
261
  }
233
262
 
234
- this.#error = err
235
-
236
263
  retryPromise
237
- .then((opts) => {
264
+ .then((shouldRetry) => {
238
265
  if (this.#aborted) {
239
- this.#onError(this.#reason)
240
- } else if (isDisturbed(this.#opts.body)) {
241
- this.#onError(this.#error)
266
+ this.#maybeError(this.#reason)
267
+ } else if (shouldRetry === false || isDisturbed(this.#opts.body)) {
268
+ this.#maybeError(err)
242
269
  } else if (!this.#headersSent) {
243
- this.#retryCount++
244
270
  this.#opts.logger?.debug({ err, retryCount: this.#retryCount }, 'retry response headers')
271
+
272
+ this.#retryCount++
273
+ this.#retryError = err
274
+
245
275
  this.#dispatch(this.#opts, this)
246
276
  } else {
247
277
  assert(Number.isFinite(this.#pos))
248
278
  assert(this.#end == null || (Number.isFinite(this.#end) && this.#end > 0))
249
279
 
250
- this.#opts = {
251
- ...this.#opts,
252
- ...opts,
253
- headers: {
254
- ...this.#opts.headers,
255
- ...opts?.headers,
256
- 'if-match': this.#etag,
257
- range: `bytes=${this.#pos}-${this.#end ? this.#end - 1 : ''}`,
258
- },
259
- }
280
+ this.#opts.headers['if-match'] = this.#etag
281
+ this.#opts.headers.range = `bytes=${this.#pos}-${this.#end ? this.#end - 1 : ''}`
282
+ this.#opts.logger?.debug({ err, retryCount: this.#retryCount }, 'retry response body')
260
283
 
261
284
  this.#retryCount++
262
- this.#opts.logger?.debug({ err, retryCount: this.#retryCount }, 'retry response body')
285
+ this.#retryError = err
286
+
263
287
  this.#dispatch(this.#opts, this)
264
288
  }
265
289
  })
266
290
  .catch((err) => {
267
- if (!this.#errorSent) {
268
- this.#onError(err)
269
- }
291
+ this.#maybeError(err)
270
292
  })
271
293
  }
272
294
 
273
- #onError(err) {
274
- assert(!this.#errorSent)
275
- this.#errorSent = true
276
- super.onError(err)
277
- }
278
-
279
- #onHeaders(statusCode, headers, resume) {
280
- assert(!this.#headersSent)
281
- this.#headersSent = true
282
- return super.onHeaders(statusCode, headers, resume)
283
- }
284
- }
285
-
286
- export default () => (dispatch) => (opts, handler) =>
287
- opts.retry !== false &&
288
- !opts.upgrade &&
289
- (opts.method === 'HEAD' ||
290
- opts.method === 'GET' ||
291
- opts.method === 'PUT' ||
292
- opts.method === 'PATCH' ||
293
- opts.idempotent)
294
- ? dispatch(opts, new Handler(opts, { handler, dispatch }))
295
- : dispatch(opts, handler)
296
-
297
- async function retryFn(err, retryCount, opts) {
298
- let retryOpts = opts?.retry
299
-
300
- if (!retryOpts) {
301
- throw err
302
- }
295
+ async #retryFn(err, retryCount, opts) {
296
+ let retryOpts = opts?.retry
303
297
 
304
- if (typeof retryOpts === 'function') {
305
- return retryOpts(err, retryCount, opts, () => retryFn(err, retryCount, opts))
306
- }
298
+ if (!retryOpts) {
299
+ return false
300
+ }
307
301
 
308
- if (typeof retryOpts === 'number') {
309
- retryOpts = { count: retryOpts }
310
- }
302
+ if (typeof retryOpts === 'number') {
303
+ retryOpts = { count: retryOpts }
304
+ }
311
305
 
312
- const retryMax = retryOpts?.count ?? 8
306
+ const retryMax = retryOpts?.count ?? 8
313
307
 
314
- if (retryCount > retryMax) {
315
- throw err
316
- }
308
+ if (retryCount > retryMax) {
309
+ return false
310
+ }
317
311
 
318
- const statusCode = err.statusCode ?? err.status ?? err.$metadata?.httpStatusCode ?? null
312
+ const statusCode =
313
+ err?.statusCode ?? err?.status ?? err?.$metadata?.httpStatusCode ?? this.#statusCode
314
+ const headers = err?.headers ?? this.#headers
315
+
316
+ if (statusCode && [420, 429, 502, 503, 504].includes(statusCode)) {
317
+ const retryAfter = headers?.['retry-after'] ? Number(headers['retry-after']) * 1e3 : null
318
+ const delay =
319
+ retryAfter != null && Number.isFinite(retryAfter)
320
+ ? retryAfter
321
+ : Math.min(10e3, retryCount * 1e3)
322
+ return tp.setTimeout(delay, true, { signal: opts.signal })
323
+ }
319
324
 
320
- if (statusCode && [420, 429, 502, 503, 504].includes(statusCode)) {
321
- let retryAfter = err.headers?.['retry-after'] ? err.headers['retry-after'] * 1e3 : null
322
- retryAfter = Number.isFinite(retryAfter) ? retryAfter : Math.min(10e3, retryCount * 1e3)
323
- if (retryAfter != null && Number.isFinite(retryAfter)) {
324
- return tp.setTimeout(retryAfter, undefined, { signal: opts.signal })
325
- } else {
326
- return null
325
+ if (
326
+ err?.code &&
327
+ [
328
+ 'ECONNRESET',
329
+ 'ECONNREFUSED',
330
+ 'ENOTFOUND',
331
+ 'ENETDOWN',
332
+ 'ENETUNREACH',
333
+ 'EHOSTDOWN',
334
+ 'EHOSTUNREACH',
335
+ 'EPIPE',
336
+ 'ENODATA',
337
+ 'UND_ERR_CONNECT_TIMEOUT',
338
+ ].includes(err.code)
339
+ ) {
340
+ return tp.setTimeout(Math.min(10e3, retryCount * 1e3), true, { signal: opts.signal })
327
341
  }
328
- }
329
342
 
330
- if (
331
- err.code &&
332
- [
333
- 'ECONNRESET',
334
- 'ECONNREFUSED',
335
- 'ENOTFOUND',
336
- 'ENETDOWN',
337
- 'ENETUNREACH',
338
- 'EHOSTDOWN',
339
- 'EHOSTUNREACH',
340
- 'EPIPE',
341
- 'ENODATA',
342
- 'UND_ERR_CONNECT_TIMEOUT',
343
- ].includes(err.code)
344
- ) {
345
- return tp.setTimeout(Math.min(10e3, retryCount * 1e3), undefined, { signal: opts.signal })
346
- }
343
+ if (err?.message && ['other side closed'].includes(err.message)) {
344
+ return tp.setTimeout(Math.min(10e3, retryCount * 1e3), true, { signal: opts.signal })
345
+ }
347
346
 
348
- if (err.message && ['other side closed'].includes(err.message)) {
349
- return tp.setTimeout(Math.min(10e3, retryCount * 1e3), undefined, { signal: opts.signal })
347
+ return false
350
348
  }
351
-
352
- throw err
353
349
  }
350
+
351
+ export default () => (dispatch) => (opts, handler) =>
352
+ opts.retry !== false &&
353
+ !opts.upgrade &&
354
+ (/^(HEAD|GET|PUT|PATCH)$/.test(opts.method) || opts.idempotent)
355
+ ? dispatch(opts, new Handler(opts, { handler, dispatch }))
356
+ : dispatch(opts, handler)
package/lib/utils.js CHANGED
@@ -2,6 +2,7 @@ import cacheControlParser from 'cache-control-parser'
2
2
  import stream from 'node:stream'
3
3
  import assert from 'node:assert'
4
4
  import { util } from '@nxtedition/undici'
5
+ import createHttpError from 'http-errors'
5
6
 
6
7
  let fastNow = Date.now()
7
8
 
@@ -351,9 +352,75 @@ export function parseHeaders(headers, obj) {
351
352
  }
352
353
 
353
354
  // See https://github.com/nodejs/node/pull/46528
354
- if ('content-length' in obj && 'content-disposition' in obj) {
355
+ if (obj != null && 'content-length' in obj && 'content-disposition' in obj) {
355
356
  obj['content-disposition'] = Buffer.from(obj['content-disposition']).toString('latin1')
356
357
  }
357
358
 
358
359
  return obj
359
360
  }
361
+
362
+ export function decorateError(err, opts, { statusCode, headers, trailers, body }) {
363
+ try {
364
+ if (err == null) {
365
+ const stackTraceLimit = Error.stackTraceLimit
366
+ Error.stackTraceLimit = 0
367
+ try {
368
+ err = createHttpError(statusCode)
369
+ } finally {
370
+ Error.stackTraceLimit = stackTraceLimit
371
+ }
372
+ }
373
+
374
+ if (statusCode != null) {
375
+ err.statusCode = statusCode
376
+ }
377
+
378
+ err.url ??= opts.origin ? new URL(opts.path, opts.origin).href : null
379
+
380
+ err.req = {
381
+ method: opts?.method,
382
+ headers: opts?.headers,
383
+ body:
384
+ // TODO (fix): JSON.stringify POJO
385
+ typeof opts?.body !== 'string' || opts.body.length > 1024 ? undefined : opts.body,
386
+ }
387
+
388
+ if (Array.isArray(body) && body.every((x) => Buffer.isBuffer(x))) {
389
+ body = Buffer.concat(body).toString()
390
+ } else if (typeof body !== 'string') {
391
+ body = null
392
+ }
393
+
394
+ if (typeof body === 'string' && headers?.['content-type']?.startsWith('application/json')) {
395
+ try {
396
+ body = JSON.parse(body)
397
+ } catch {
398
+ // Do nothing...
399
+ }
400
+ }
401
+
402
+ err.res = {
403
+ headers,
404
+ trailers,
405
+ // TODO (fix): JSON.stringify POJO
406
+ body: typeof body !== 'string' || body.length < 1024 ? undefined : body,
407
+ statusCode,
408
+ }
409
+
410
+ if (body) {
411
+ if (body.reason != null) {
412
+ err.reason ??= body.reason
413
+ }
414
+ if (body.code != null) {
415
+ err.code ??= body.code
416
+ }
417
+ if (body.error != null) {
418
+ err.error ??= body.error
419
+ }
420
+ }
421
+
422
+ return err
423
+ } catch (er) {
424
+ return new AggregateError([er, err])
425
+ }
426
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@nxtedition/nxt-undici",
3
- "version": "6.2.12",
3
+ "version": "6.2.14",
4
4
  "license": "MIT",
5
5
  "author": "Robert Nagy <robert.nagy@boffins.se>",
6
6
  "main": "lib/index.js",