Run npm ci --ignore-scripts to update dependencies (#254)

* Initial plan

* Run npm ci --ignore-scripts to update dependencies

Co-authored-by: dawidd6 <9713907+dawidd6@users.noreply.github.com>

* Convert CommonJS to ESM (#255)

* Initial plan

* Convert CommonJS imports to ESM

Co-authored-by: dawidd6 <9713907+dawidd6@users.noreply.github.com>

* Use node: protocol prefix for built-in modules

Co-authored-by: dawidd6 <9713907+dawidd6@users.noreply.github.com>

---------

Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com>
Co-authored-by: dawidd6 <9713907+dawidd6@users.noreply.github.com>

---------

Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com>
Co-authored-by: dawidd6 <9713907+dawidd6@users.noreply.github.com>
This commit is contained in:
Copilot
2026-01-30 13:31:20 +01:00
committed by GitHub
parent 85c1af852f
commit afe9786629
330 changed files with 13024 additions and 14665 deletions

View File

@@ -1,35 +0,0 @@
'use strict'
module.exports = class DecoratorHandler {
constructor (handler) {
this.handler = handler
}
onConnect (...args) {
return this.handler.onConnect(...args)
}
onError (...args) {
return this.handler.onError(...args)
}
onUpgrade (...args) {
return this.handler.onUpgrade(...args)
}
onHeaders (...args) {
return this.handler.onHeaders(...args)
}
onData (...args) {
return this.handler.onData(...args)
}
onComplete (...args) {
return this.handler.onComplete(...args)
}
onBodySent (...args) {
return this.handler.onBodySent(...args)
}
}

44
node_modules/undici/lib/handler/decorator-handler.js generated vendored Normal file
View File

@@ -0,0 +1,44 @@
'use strict'
module.exports = class DecoratorHandler {
#handler
constructor (handler) {
if (typeof handler !== 'object' || handler === null) {
throw new TypeError('handler must be an object')
}
this.#handler = handler
}
onConnect (...args) {
return this.#handler.onConnect?.(...args)
}
onError (...args) {
return this.#handler.onError?.(...args)
}
onUpgrade (...args) {
return this.#handler.onUpgrade?.(...args)
}
onResponseStarted (...args) {
return this.#handler.onResponseStarted?.(...args)
}
onHeaders (...args) {
return this.#handler.onHeaders?.(...args)
}
onData (...args) {
return this.#handler.onData?.(...args)
}
onComplete (...args) {
return this.#handler.onComplete?.(...args)
}
onBodySent (...args) {
return this.#handler.onBodySent?.(...args)
}
}

View File

@@ -2,9 +2,9 @@
const util = require('../core/util')
const { kBodyUsed } = require('../core/symbols')
const assert = require('assert')
const assert = require('node:assert')
const { InvalidArgumentError } = require('../core/errors')
const EE = require('events')
const EE = require('node:events')
const redirectableStatusCodes = [300, 301, 302, 303, 307, 308]
@@ -38,6 +38,7 @@ class RedirectHandler {
this.maxRedirections = maxRedirections
this.handler = handler
this.history = []
this.redirectionLimitReached = false
if (util.isStream(this.opts.body)) {
// TODO (fix): Provide some way for the user to cache the file to e.g. /tmp
@@ -91,6 +92,16 @@ class RedirectHandler {
? null
: parseLocation(statusCode, headers)
if (this.opts.throwOnMaxRedirect && this.history.length >= this.maxRedirections) {
if (this.request) {
this.request.abort(new Error('max redirects'))
}
this.redirectionLimitReached = true
this.abort(new Error('max redirects'))
return
}
if (this.opts.origin) {
this.history.push(new URL(this.opts.path, this.opts.origin))
}
@@ -135,7 +146,7 @@ class RedirectHandler {
For status 300, which is "Multiple Choices", the spec mentions both generating a Location
response header AND a response body with the other possible location to follow.
Since the spec explicitily chooses not to specify a format for such body and leave it to
Since the spec explicitly chooses not to specify a format for such body and leave it to
servers and browsers implementors, we ignore the body as there is no specified way to eventually parse it.
*/
} else {
@@ -151,7 +162,7 @@ class RedirectHandler {
TLDR: undici always ignores 3xx response trailers as they are not expected in case of redirections
and neither are useful if present.
See comment on onData method above for more detailed informations.
See comment on onData method above for more detailed information.
*/
this.location = null
@@ -176,7 +187,7 @@ function parseLocation (statusCode, headers) {
}
for (let i = 0; i < headers.length; i += 2) {
if (headers[i].toString().toLowerCase() === 'location') {
if (headers[i].length === 8 && util.headerNameToString(headers[i]) === 'location') {
return headers[i + 1]
}
}

View File

@@ -1,14 +1,18 @@
const assert = require('assert')
'use strict'
const assert = require('node:assert')
const { kRetryHandlerDefaultRetry } = require('../core/symbols')
const { RequestRetryError } = require('../core/errors')
const { isDisturbed, parseHeaders, parseRangeHeader } = require('../core/util')
const {
isDisturbed,
parseHeaders,
parseRangeHeader,
wrapRequestBody
} = require('../core/util')
function calculateRetryAfterHeader (retryAfter) {
const current = Date.now()
const diff = new Date(retryAfter).getTime() - current
return diff
return new Date(retryAfter).getTime() - current
}
class RetryHandler {
@@ -30,14 +34,14 @@ class RetryHandler {
this.dispatch = handlers.dispatch
this.handler = handlers.handler
this.opts = dispatchOpts
this.opts = { ...dispatchOpts, body: wrapRequestBody(opts.body) }
this.abort = null
this.aborted = false
this.retryOpts = {
retry: retryFn ?? RetryHandler[kRetryHandlerDefaultRetry],
retryAfter: retryAfter ?? true,
maxTimeout: maxTimeout ?? 30 * 1000, // 30s,
timeout: minTimeout ?? 500, // .5s
minTimeout: minTimeout ?? 500, // .5s
timeoutFactor: timeoutFactor ?? 2,
maxRetries: maxRetries ?? 5,
// What errors we should retry
@@ -53,11 +57,13 @@ class RetryHandler {
'ENETUNREACH',
'EHOSTDOWN',
'EHOSTUNREACH',
'EPIPE'
'EPIPE',
'UND_ERR_SOCKET'
]
}
this.retryCount = 0
this.retryCountCheckpoint = 0
this.start = 0
this.end = null
this.etag = null
@@ -103,25 +109,17 @@ class RetryHandler {
const { method, retryOptions } = opts
const {
maxRetries,
timeout,
minTimeout,
maxTimeout,
timeoutFactor,
statusCodes,
errorCodes,
methods
} = retryOptions
let { counter, currentTimeout } = state
currentTimeout =
currentTimeout != null && currentTimeout > 0 ? currentTimeout : timeout
const { counter } = state
// Any code that is not a Undici's originated and allowed to retry
if (
code &&
code !== 'UND_ERR_REQ_RETRY' &&
code !== 'UND_ERR_SOCKET' &&
!errorCodes.includes(code)
) {
if (code && code !== 'UND_ERR_REQ_RETRY' && !errorCodes.includes(code)) {
cb(err)
return
}
@@ -148,10 +146,10 @@ class RetryHandler {
return
}
let retryAfterHeader = headers != null && headers['retry-after']
let retryAfterHeader = headers?.['retry-after']
if (retryAfterHeader) {
retryAfterHeader = Number(retryAfterHeader)
retryAfterHeader = isNaN(retryAfterHeader)
retryAfterHeader = Number.isNaN(retryAfterHeader)
? calculateRetryAfterHeader(retryAfterHeader)
: retryAfterHeader * 1e3 // Retry-After is in seconds
}
@@ -159,9 +157,7 @@ class RetryHandler {
const retryTimeout =
retryAfterHeader > 0
? Math.min(retryAfterHeader, maxTimeout)
: Math.min(currentTimeout * timeoutFactor ** counter, maxTimeout)
state.currentTimeout = retryTimeout
: Math.min(minTimeout * timeoutFactor ** (counter - 1), maxTimeout)
setTimeout(() => cb(null), retryTimeout)
}
@@ -172,21 +168,42 @@ class RetryHandler {
this.retryCount += 1
if (statusCode >= 300) {
this.abort(
new RequestRetryError('Request failed', statusCode, {
headers,
count: this.retryCount
})
)
return false
if (this.retryOpts.statusCodes.includes(statusCode) === false) {
return this.handler.onHeaders(
statusCode,
rawHeaders,
resume,
statusMessage
)
} else {
this.abort(
new RequestRetryError('Request failed', statusCode, {
headers,
data: {
count: this.retryCount
}
})
)
return false
}
}
// Checkpoint for resume from where we left it
if (this.resume != null) {
this.resume = null
if (statusCode !== 206) {
return true
// Only Partial Content 206 supposed to provide Content-Range,
// any other status code that partially consumed the payload
// should not be retry because it would result in downstream
// wrongly concatanete multiple responses.
if (statusCode !== 206 && (this.start > 0 || statusCode !== 200)) {
this.abort(
new RequestRetryError('server does not support the range header and the payload was partially consumed', statusCode, {
headers,
data: { count: this.retryCount }
})
)
return false
}
const contentRange = parseRangeHeader(headers['content-range'])
@@ -195,7 +212,7 @@ class RetryHandler {
this.abort(
new RequestRetryError('Content-Range mismatch', statusCode, {
headers,
count: this.retryCount
data: { count: this.retryCount }
})
)
return false
@@ -206,13 +223,13 @@ class RetryHandler {
this.abort(
new RequestRetryError('ETag mismatch', statusCode, {
headers,
count: this.retryCount
data: { count: this.retryCount }
})
)
return false
}
const { start, size, end = size } = contentRange
const { start, size, end = size - 1 } = contentRange
assert(this.start === start, 'content-range mismatch')
assert(this.end == null || this.end === end, 'content-range mismatch')
@@ -235,17 +252,12 @@ class RetryHandler {
)
}
const { start, size, end = size } = range
const { start, size, end = size - 1 } = range
assert(
start != null && Number.isFinite(start) && this.start !== start,
start != null && Number.isFinite(start),
'content-range mismatch'
)
assert(Number.isFinite(start))
assert(
end != null && Number.isFinite(end) && this.end !== end,
'invalid content-length'
)
assert(end != null && Number.isFinite(end), 'invalid content-length')
this.start = start
this.end = end
@@ -254,7 +266,7 @@ class RetryHandler {
// We make our best to checkpoint the body for further range headers
if (this.end == null) {
const contentLength = headers['content-length']
this.end = contentLength != null ? Number(contentLength) : null
this.end = contentLength != null ? Number(contentLength) - 1 : null
}
assert(Number.isFinite(this.start))
@@ -266,6 +278,13 @@ class RetryHandler {
this.resume = resume
this.etag = headers.etag != null ? headers.etag : null
// Weak etags are not useful for comparison nor cache
// for instance not safe to assume if the response is byte-per-byte
// equal
if (this.etag != null && this.etag.startsWith('W/')) {
this.etag = null
}
return this.handler.onHeaders(
statusCode,
rawHeaders,
@@ -276,7 +295,7 @@ class RetryHandler {
const err = new RequestRetryError('Request failed', statusCode, {
headers,
count: this.retryCount
data: { count: this.retryCount }
})
this.abort(err)
@@ -300,10 +319,21 @@ class RetryHandler {
return this.handler.onError(err)
}
// We reconcile in case of a mix between network errors
// and server error response
if (this.retryCount - this.retryCountCheckpoint > 0) {
// We count the difference between the last checkpoint and the current retry count
this.retryCount =
this.retryCountCheckpoint +
(this.retryCount - this.retryCountCheckpoint)
} else {
this.retryCount += 1
}
this.retryOpts.retry(
err,
{
state: { counter: this.retryCount++, currentTimeout: this.retryAfter },
state: { counter: this.retryCount },
opts: { retryOptions: this.retryOpts, ...this.opts }
},
onRetry.bind(this)
@@ -315,16 +345,24 @@ class RetryHandler {
}
if (this.start !== 0) {
const headers = { range: `bytes=${this.start}-${this.end ?? ''}` }
// Weak etag check - weak etags will make comparison algorithms never match
if (this.etag != null) {
headers['if-match'] = this.etag
}
this.opts = {
...this.opts,
headers: {
...this.opts.headers,
range: `bytes=${this.start}-${this.end ?? ''}`
...headers
}
}
}
try {
this.retryCountCheckpoint = this.retryCount
this.dispatch(this.opts, this)
} catch (err) {
this.handler.onError(err)