diff --git a/deps/undici/src/.gitignore b/deps/undici/src/.gitignore index 2af62573ea7ee8..fc02456981b799 100644 --- a/deps/undici/src/.gitignore +++ b/deps/undici/src/.gitignore @@ -91,3 +91,5 @@ AGENTS.md # Ignore .githuman .githuman + +benchmarks/package-lock.json diff --git a/deps/undici/src/README.md b/deps/undici/src/README.md index c15ffd0cc86910..b5a1cc17c04fd5 100644 --- a/deps/undici/src/README.md +++ b/deps/undici/src/README.md @@ -200,7 +200,9 @@ await fetch('https://example.com', { ``` `install()` replaces the global `fetch`, `Headers`, `Response`, `Request`, and -`FormData` implementations with undici's versions, so they all match. +`FormData` implementations with undici's versions, so they all match. It also +installs undici's `WebSocket`, `CloseEvent`, `ErrorEvent`, `MessageEvent`, and +`EventSource` globals. Avoid mixing a global `FormData` with `undici.fetch()`, or `undici.FormData` with the built-in global `fetch()`. @@ -283,12 +285,12 @@ const data2 = await getData(); ## Global Installation -Undici provides an `install()` function to add all WHATWG fetch classes to `globalThis`, making them available globally: +Undici provides an `install()` function to add fetch-related and other web API classes to `globalThis`, making them available globally: ```js import { install } from 'undici' -// Install all WHATWG fetch classes globally +// Install undici's global web APIs install() // Now you can use fetch classes globally without importing @@ -316,8 +318,9 @@ The `install()` function adds the following classes to `globalThis`: When you call `install()`, these globals come from the same undici implementation. For example, global `fetch` and global `FormData` will both be -undici's versions, which is the recommended setup if you want to use undici -through globals. +undici's versions, and `WebSocket` and `EventSource` will also come from +undici, which is the recommended setup if you want to use undici through +globals. This is useful for: - Polyfilling environments that don't have fetch diff --git a/deps/undici/src/docs/docs/api/GlobalInstallation.md b/deps/undici/src/docs/docs/api/GlobalInstallation.md index 2fc5bc2fd86ce3..d2f8f77e7b8b0f 100644 --- a/deps/undici/src/docs/docs/api/GlobalInstallation.md +++ b/deps/undici/src/docs/docs/api/GlobalInstallation.md @@ -1,17 +1,17 @@ # Global Installation -Undici provides an `install()` function to add all WHATWG fetch classes to `globalThis`, making them available globally without requiring imports. +Undici provides an `install()` function to add fetch-related and other web API classes to `globalThis`, making them available globally without requiring imports. ## `install()` -Install all WHATWG fetch classes globally on `globalThis`. +Install undici's global web APIs on `globalThis`. **Example:** ```js import { install } from 'undici' -// Install all WHATWG fetch classes globally +// Install undici's global web APIs install() // Now you can use fetch classes globally without importing @@ -74,6 +74,8 @@ await fetch('https://example.com', { After `install()`, `fetch`, `Headers`, `Response`, `Request`, and `FormData` all come from the installed `undici` package, so they work as a matching set. +`WebSocket`, `CloseEvent`, `ErrorEvent`, `MessageEvent`, and `EventSource` +also come from the installed `undici` package. If you do not want to install globals, import both from `undici` instead: @@ -135,5 +137,5 @@ test('fetch API test', async () => { - The `install()` function overwrites any existing global implementations - Classes installed are undici's implementations, not Node.js built-ins -- This provides access to undici's latest features and performance improvements -- The global installation persists for the lifetime of the process \ No newline at end of file +- This provides access to undici's latest fetch, WebSocket, and EventSource features and performance improvements +- The global installation persists for the lifetime of the process diff --git a/deps/undici/src/docs/docs/api/SnapshotAgent.md b/deps/undici/src/docs/docs/api/SnapshotAgent.md index e4c8f2484a5408..1de74fb73f97c3 100644 --- a/deps/undici/src/docs/docs/api/SnapshotAgent.md +++ b/deps/undici/src/docs/docs/api/SnapshotAgent.md @@ -27,7 +27,9 @@ new SnapshotAgent([options]) - **ignoreHeaders** `Array` - Headers to ignore during request matching - **excludeHeaders** `Array` - Headers to exclude from snapshots (for security) - **matchBody** `Boolean` - Whether to include request body in matching. Default: `true` + - **normalizeBody** `Function` - Optional function `(body) => string` to normalize the request body before matching (e.g. strip volatile fields like timestamps). Only used when `matchBody` is `true`. - **matchQuery** `Boolean` - Whether to include query parameters in matching. Default: `true` + - **normalizeQuery** `Function` - Optional function `(query: URLSearchParams) => string` to normalize query parameters before matching (e.g. strip volatile params like cache-busters). Only used when `matchQuery` is `true`. - **caseSensitive** `Boolean` - Whether header matching is case-sensitive. Default: `false` - **shouldRecord** `Function` - Callback to determine if a request should be recorded - **shouldPlayback** `Function` - Callback to determine if a request should be played back @@ -108,6 +110,27 @@ await agent.saveSnapshots('./custom-snapshots.json') ## Advanced Configuration +### Body Matching + +By default (`matchBody: true`) the full request body string is included in the snapshot key. Set it to `false` to ignore the body entirely, or use `normalizeBody` to strip volatile fields (like timestamps) before matching: + +```javascript +const agent = new SnapshotAgent({ + mode: 'playback', + snapshotPath: './snapshots.json', + + // Match on everything except the timestamp field + normalizeBody: (body) => { + if (!body) return '' + const parsed = JSON.parse(String(body)) + delete parsed.timestamp + return JSON.stringify(parsed) + } +}) +``` + +`normalizeBody` receives the raw body (`string | Buffer | null | undefined`) and must return a `string`. It runs at both record and playback time so the hash is consistent. Two requests match the same snapshot whenever their normalized strings are identical. + ### Header Filtering Control which headers are used for request matching and what gets stored in snapshots: diff --git a/deps/undici/src/lib/api/api-pipeline.js b/deps/undici/src/lib/api/api-pipeline.js index 6e61710b90741a..6af3913440a490 100644 --- a/deps/undici/src/lib/api/api-pipeline.js +++ b/deps/undici/src/lib/api/api-pipeline.js @@ -13,6 +13,7 @@ const { RequestAbortedError } = require('../core/errors') const util = require('../core/util') +const { kBodyUsed } = require('../core/symbols') const { addSignal, removeSignal } = require('./abort-signal') function noop () {} @@ -24,6 +25,9 @@ class PipelineRequest extends Readable { super({ autoDestroy: true }) this[kResume] = null + // Pipeline request bodies come from a live writable side and cannot be + // replayed across redirects or retries, even before any bytes are read. + this[kBodyUsed] = true } _read () { diff --git a/deps/undici/src/lib/api/api-stream.js b/deps/undici/src/lib/api/api-stream.js index 249d6774f6bea8..f111d70efc17cd 100644 --- a/deps/undici/src/lib/api/api-stream.js +++ b/deps/undici/src/lib/api/api-stream.js @@ -1,7 +1,6 @@ 'use strict' const assert = require('node:assert') -const { finished } = require('node:stream') const { AsyncResource } = require('node:async_hooks') const { InvalidArgumentError, InvalidReturnValueError } = require('../core/errors') const util = require('../core/util') @@ -9,6 +8,54 @@ const { addSignal, removeSignal } = require('./abort-signal') function noop () {} +function getWritableError (stream) { + return stream.errored ?? stream.writableErrored ?? stream._writableState?.errored +} + +function createPrematureCloseError () { + const err = new Error('Premature close') + err.code = 'ERR_STREAM_PREMATURE_CLOSE' + return err +} + +function trackWritableLifecycle (stream, callback) { + let done = false + + const cleanup = () => { + stream.removeListener('close', onClose) + stream.removeListener('error', onError) + stream.removeListener('finish', onFinish) + } + + const finish = (err, fromErrorEvent = false) => { + if (done) { + return + } + + done = true + cleanup() + callback(err, fromErrorEvent) + } + + const onClose = () => { + const err = getWritableError(stream) + finish(err ?? (!stream.writableFinished ? createPrematureCloseError() : undefined)) + } + + const onError = (err) => finish(err, true) + const onFinish = () => finish() + + stream.on('close', onClose) + stream.on('error', onError) + stream.on('finish', onFinish) + + if (stream.closed) { + process.nextTick(onClose) + } else if (stream.writableFinished) { + process.nextTick(onFinish) + } +} + class StreamHandler extends AsyncResource { constructor (opts, factory, callback) { if (!opts || typeof opts !== 'object') { @@ -117,20 +164,19 @@ class StreamHandler extends AsyncResource { throw new InvalidReturnValueError('expected Writable') } - // TODO: Avoid finished. It registers an unnecessary amount of listeners. - finished(res, { readable: false }, (err) => { + trackWritableLifecycle(res, (err, fromErrorEvent) => { const { callback, res, opaque, trailers, abort } = this this.res = null if (err || !res?.readable) { - util.destroy(res, err) + util.destroy(res, fromErrorEvent ? undefined : err) } this.callback = null this.runInAsyncScope(callback, null, err || null, { opaque, trailers }) if (err) { - abort() + abort(err) } }) diff --git a/deps/undici/src/lib/core/symbols.js b/deps/undici/src/lib/core/symbols.js index ff37adc0448f1b..8bad25eed9fdb9 100644 --- a/deps/undici/src/lib/core/symbols.js +++ b/deps/undici/src/lib/core/symbols.js @@ -62,6 +62,7 @@ module.exports = { kListeners: Symbol('listeners'), kHTTPContext: Symbol('http context'), kMaxConcurrentStreams: Symbol('max concurrent streams'), + kHostAuthority: Symbol('host authority'), kHTTP2InitialWindowSize: Symbol('http2 initial window size'), kHTTP2ConnectionWindowSize: Symbol('http2 connection window size'), kEnableConnectProtocol: Symbol('http2session connect protocol'), diff --git a/deps/undici/src/lib/core/util.js b/deps/undici/src/lib/core/util.js index 54346a2567c2d2..98337c596c76c7 100644 --- a/deps/undici/src/lib/core/util.js +++ b/deps/undici/src/lib/core/util.js @@ -776,7 +776,7 @@ function isValidHeaderValue (characters) { return !headerCharRegex.test(characters) } -const rangeHeaderRegex = /^bytes (\d+)-(\d+)\/(\d+)?$/ +const rangeHeaderRegex = /^bytes (\d+)-(\d+)\/(\d+|\*)?$/ /** * @typedef {object} RangeHeader @@ -799,7 +799,7 @@ function parseRangeHeader (range) { ? { start: parseInt(m[1]), end: m[2] ? parseInt(m[2]) : null, - size: m[3] ? parseInt(m[3]) : null + size: m[3] && m[3] !== '*' ? parseInt(m[3]) : null } : null } diff --git a/deps/undici/src/lib/dispatcher/client-h1.js b/deps/undici/src/lib/dispatcher/client-h1.js index f64d3f11324c7a..f1c52fb5f116ec 100644 --- a/deps/undici/src/lib/dispatcher/client-h1.js +++ b/deps/undici/src/lib/dispatcher/client-h1.js @@ -360,16 +360,7 @@ class Parser { this.paused = true socket.unshift(data) } else { - const ptr = llhttp.llhttp_get_error_reason(this.ptr) - let message = '' - if (ptr) { - const len = new Uint8Array(llhttp.memory.buffer, ptr).indexOf(0) - message = - 'Response does not match the HTTP/1.1 protocol (' + - Buffer.from(llhttp.memory.buffer, ptr, len).toString() + - ')' - } - throw new HTTPParserError(message, constants.ERROR[ret], data) + throw this.createError(ret, data) } } } catch (err) { @@ -377,6 +368,54 @@ class Parser { } } + finish () { + assert(currentParser === null) + assert(this.ptr != null) + assert(!this.paused) + + const { llhttp } = this + + let ret + + try { + currentParser = this + ret = llhttp.llhttp_finish(this.ptr) + } finally { + currentParser = null + } + + if (ret === constants.ERROR.OK) { + return null + } + + if (ret === constants.ERROR.PAUSED || ret === constants.ERROR.PAUSED_UPGRADE) { + this.paused = true + return null + } + + return this.createError(ret, EMPTY_BUF) + } + + createError (ret, data) { + const { llhttp, contentLength, bytesRead } = this + + if (contentLength !== -1 && bytesRead !== contentLength) { + return new ResponseContentLengthMismatchError() + } + + const ptr = llhttp.llhttp_get_error_reason(this.ptr) + let message = '' + if (ptr) { + const len = new Uint8Array(llhttp.memory.buffer, ptr).indexOf(0) + message = + 'Response does not match the HTTP/1.1 protocol (' + + Buffer.from(llhttp.memory.buffer, ptr, len).toString() + + ')' + } + + return new HTTPParserError(message, constants.ERROR[ret], data) + } + destroy () { assert(currentParser === null) assert(this.ptr != null) @@ -888,8 +927,11 @@ function onHttpSocketError (err) { // On Mac OS, we get an ECONNRESET even if there is a full body to be forwarded // to the user. if (err.code === 'ECONNRESET' && parser.statusCode && !parser.shouldKeepAlive) { - // We treat all incoming data so for as a valid response. - parser.onMessageComplete() + const parserErr = parser.finish() + if (parserErr) { + this[kError] = parserErr + this[kClient][kOnError](parserErr) + } return } @@ -906,8 +948,10 @@ function onHttpSocketEnd () { const parser = this[kParser] if (parser.statusCode && !parser.shouldKeepAlive) { - // We treat all incoming data so far as a valid response. - parser.onMessageComplete() + const parserErr = parser.finish() + if (parserErr) { + util.destroy(this, parserErr) + } return } @@ -919,8 +963,7 @@ function onHttpSocketClose () { if (parser) { if (!this[kError] && parser.statusCode && !parser.shouldKeepAlive) { - // We treat all incoming data so far as a valid response. - parser.onMessageComplete() + this[kError] = parser.finish() || this[kError] } this[kParser].destroy() @@ -1382,8 +1425,6 @@ function writeBuffer (abort, body, client, request, socket, contentLength, heade * @returns {Promise} */ async function writeBlob (abort, body, client, request, socket, contentLength, header, expectsPayload) { - assert(contentLength === body.size, 'blob body must have content length') - try { if (contentLength != null && contentLength !== body.size) { throw new RequestContentLengthMismatchError() diff --git a/deps/undici/src/lib/dispatcher/client-h2.js b/deps/undici/src/lib/dispatcher/client-h2.js index b77013df98da46..ba9157cab5b145 100644 --- a/deps/undici/src/lib/dispatcher/client-h2.js +++ b/deps/undici/src/lib/dispatcher/client-h2.js @@ -28,6 +28,7 @@ const { kHTTP2Session, kHTTP2InitialWindowSize, kHTTP2ConnectionWindowSize, + kHostAuthority, kResume, kSize, kHTTPContext, @@ -44,8 +45,7 @@ const kOpenStreams = Symbol('open streams') const kRequestStreamId = Symbol('request stream id') const kRequestStream = Symbol('request stream') const kRequestStreamCleanup = Symbol('request stream cleanup') -const kRequestStreamOnData = Symbol('request stream on data') -const kRequestStreamOnCloseError = Symbol('request stream on close error') +const kRequestStreamState = Symbol('request stream state') const kReceivedGoAway = Symbol('received goaway') let extractBody @@ -107,8 +107,9 @@ function detachRequestFromStream (request) { function bindRequestToStream (request, stream, cleanup) { const previousCleanup = request[kRequestStreamCleanup] + const previousStream = request[kRequestStream] detachRequestFromStream(request) - previousCleanup?.() + previousCleanup?.(previousStream) request[kRequestStreamId] = stream.id request[kRequestStream] = stream request[kRequestStreamCleanup] = cleanup @@ -116,8 +117,9 @@ function bindRequestToStream (request, stream, cleanup) { function clearRequestStream (request) { const cleanup = request[kRequestStreamCleanup] + const stream = request[kRequestStream] detachRequestFromStream(request) - cleanup?.() + cleanup?.(stream) } function canRetryRequestAfterGoAway (request) { @@ -516,20 +518,18 @@ function closeStreamSession (stream) { function onUpgradeStreamClose () { this.off('error', noop) - const failUpgradeStream = this[kRequestStreamOnCloseError] - this[kRequestStreamOnCloseError] = null + const state = this[kRequestStreamState] + this[kRequestStreamState] = null - failUpgradeStream(new InformationalError('HTTP/2: stream closed before response headers')) + failUpgradeStream(state, new InformationalError('HTTP/2: stream closed before response headers')) closeStreamSession(this) } function onRequestStreamClose () { - const onData = this[kRequestStreamOnData] - - this[kRequestStreamOnData] = null this.off('data', onData) this.off('error', noop) closeStreamSession(this) + this[kRequestStreamState] = null } // https://www.rfc-editor.org/rfc/rfc7230#section-3.3.2 @@ -537,25 +537,17 @@ function shouldSendContentLength (method) { return method !== 'GET' && method !== 'HEAD' && method !== 'OPTIONS' && method !== 'TRACE' && method !== 'CONNECT' } -function writeH2 (client, request) { - const requestTimeout = request.bodyTimeout ?? client[kBodyTimeout] - const session = client[kHTTP2Session] - const { method, path, host, upgrade, expectContinue, signal, protocol, headers: reqHeaders } = request - let { body } = request - - if (upgrade != null && upgrade !== 'websocket') { - util.errorRequest(client, request, new InvalidArgumentError(`Custom upgrade "${upgrade}" not supported over HTTP/2`)) - return false - } - +function buildRequestHeaders (reqHeaders) { const headers = {} + for (let n = 0; n < reqHeaders.length; n += 2) { const key = reqHeaders[n + 0] const val = reqHeaders[n + 1] + const current = headers[key] if (key === 'cookie') { - if (headers[key] != null) { - headers[key] = Array.isArray(headers[key]) ? (headers[key].push(val), headers[key]) : [headers[key], val] + if (current != null) { + headers[key] = Array.isArray(current) ? (current.push(val), current) : [current, val] } else { headers[key] = val } @@ -563,27 +555,140 @@ function writeH2 (client, request) { continue } - if (Array.isArray(val)) { - for (let i = 0; i < val.length; i++) { - if (headers[key]) { - headers[key] += `, ${val[i]}` - } else { - headers[key] = val[i] - } - } - } else if (headers[key]) { - headers[key] += `, ${val}` - } else { - headers[key] = val + if (typeof val === 'string') { + headers[key] = current ? `${current}, ${val}` : val + continue } + + for (let i = 0; i < val.length; i++) { + headers[key] = headers[key] ? `${headers[key]}, ${val[i]}` : val[i] + } + } + + return headers +} + +function removeUpgradeStreamListeners (stream) { + stream.off('response', onUpgradeResponse) + stream.off('error', onUpgradeStreamError) + stream.off('end', onUpgradeStreamEnd) + stream.off('timeout', onUpgradeStreamTimeout) + stream.off('error', noop) +} + +function releaseUpgradeStream (stream) { + if (stream == null) { + return + } + + const state = stream[kRequestStreamState] + if (state == null) { + return + } + + const { request } = state + + if (request[kRequestStream] === stream) { + detachRequestFromStream(request) + } + + removeUpgradeStreamListeners(stream) + + if (!stream.destroyed && !stream.closed) { + stream.once('error', noop) + } +} + +function failUpgradeStream (state, err) { + if (state == null) { + return + } + + const { request } = state + if (state.responseReceived || request.aborted || request.completed) { + return + } + + releaseUpgradeStream(state.stream) + state.abort(err, true) +} + +function onUpgradeStreamError () { + const state = this[kRequestStreamState] + + if (typeof this.rstCode === 'number' && this.rstCode !== 0) { + failUpgradeStream(state, new InformationalError(`HTTP/2: "stream error" received - code ${this.rstCode}`)) + } else { + failUpgradeStream(state, new InformationalError('HTTP/2: stream errored before response headers')) + } +} + +function onUpgradeStreamEnd () { + failUpgradeStream(this[kRequestStreamState], new InformationalError('HTTP/2: stream half-closed (remote)')) +} + +function onUpgradeStreamTimeout () { + const state = this[kRequestStreamState] + failUpgradeStream(state, new InformationalError(`HTTP/2: "stream timeout after ${state.requestTimeout}"`)) +} + +function onUpgradeResponse (headers, _flags) { + const stream = this + const state = stream[kRequestStreamState] + const { request } = state + + state.responseReceived = true + + const statusCode = headers[HTTP2_HEADER_STATUS] + delete headers[HTTP2_HEADER_STATUS] + + request.onRequestUpgrade(statusCode, headers, stream) + + if (request.aborted || request.completed) { + return } + removeUpgradeStreamListeners(stream) + detachRequestFromStream(request) + state.finalizeRequest() +} + +function setupUpgradeStream (stream, state) { + const { request, requestTimeout, session } = state + + stream[kHTTP2Stream] = true + stream[kHTTP2Session] = session + stream[kRequestStreamState] = state + state.stream = stream + + bindRequestToStream(request, stream, releaseUpgradeStream) + stream.once('response', onUpgradeResponse) + stream.on('error', onUpgradeStreamError) + stream.once('end', onUpgradeStreamEnd) + stream.on('timeout', onUpgradeStreamTimeout) + stream.once('close', onUpgradeStreamClose) + + ++session[kOpenStreams] + stream.setTimeout(requestTimeout) +} + +function writeH2 (client, request) { + const requestTimeout = request.bodyTimeout ?? client[kBodyTimeout] + const session = client[kHTTP2Session] + const { method, path, host, upgrade, expectContinue, signal, protocol, headers: reqHeaders } = request + let { body } = request + + if (upgrade != null && upgrade !== 'websocket') { + util.errorRequest(client, request, new InvalidArgumentError(`Custom upgrade "${upgrade}" not supported over HTTP/2`)) + return false + } + + const headers = buildRequestHeaders(reqHeaders) + /** @type {import('node:http2').ClientHttp2Stream} */ let stream = null - const { hostname, port } = client[kUrl] - - headers[HTTP2_HEADER_AUTHORITY] = host || `${hostname}${port ? `:${port}` : ''}` + headers[HTTP2_HEADER_AUTHORITY] = host || client[kHostAuthority] headers[HTTP2_HEADER_METHOD] = method let requestFinalized = false @@ -662,82 +767,14 @@ function writeH2 (client, request) { if (upgrade || method === 'CONNECT') { session.ref() - const setupUpgradeStream = (stream) => { - let responseReceived = false - - const removeUpgradeStreamListeners = () => { - stream.off('response', onUpgradeResponse) - stream.off('error', onUpgradeStreamError) - stream.off('end', onUpgradeStreamEnd) - stream.off('timeout', onUpgradeStreamTimeout) - stream.off('error', noop) - } - - const releaseUpgradeStream = () => { - if (request[kRequestStream] === stream) { - detachRequestFromStream(request) - } - - removeUpgradeStreamListeners() - - if (!stream.destroyed && !stream.closed) { - stream.once('error', noop) - } - } - - const failUpgradeStream = (err) => { - if (responseReceived || request.aborted || request.completed) { - return - } - - releaseUpgradeStream() - abort(err, true) - } - - const onUpgradeStreamError = () => { - if (typeof stream.rstCode === 'number' && stream.rstCode !== 0) { - failUpgradeStream(new InformationalError(`HTTP/2: "stream error" received - code ${stream.rstCode}`)) - } else { - failUpgradeStream(new InformationalError('HTTP/2: stream errored before response headers')) - } - } - - const onUpgradeStreamEnd = () => { - failUpgradeStream(new InformationalError('HTTP/2: stream half-closed (remote)')) - } - - const onUpgradeStreamTimeout = () => { - failUpgradeStream(new InformationalError(`HTTP/2: "stream timeout after ${requestTimeout}"`)) - } - - const onUpgradeResponse = (headers, _flags) => { - responseReceived = true - - const statusCode = headers[HTTP2_HEADER_STATUS] - delete headers[HTTP2_HEADER_STATUS] - - request.onRequestUpgrade(statusCode, headers, stream) - - if (request.aborted || request.completed) { - return - } - - removeUpgradeStreamListeners() - detachRequestFromStream(request) - finalizeRequest() - } - - bindRequestToStream(request, stream, releaseUpgradeStream) - stream.once('response', onUpgradeResponse) - stream.on('error', onUpgradeStreamError) - stream.once('end', onUpgradeStreamEnd) - stream.on('timeout', onUpgradeStreamTimeout) - stream[kHTTP2Session] = session - stream[kRequestStreamOnCloseError] = failUpgradeStream - stream.once('close', onUpgradeStreamClose) - - ++session[kOpenStreams] - stream.setTimeout(requestTimeout) + const upgradeState = { + abort, + finalizeRequest, + request, + requestTimeout, + responseReceived: false, + session, + stream: null } if (upgrade === 'websocket') { @@ -767,8 +804,7 @@ function writeH2 (client, request) { session.unref() return false } - stream[kHTTP2Stream] = true - setupUpgradeStream(stream) + setupUpgradeStream(stream, upgradeState) return true } @@ -782,8 +818,7 @@ function writeH2 (client, request) { session.unref() return false } - stream[kHTTP2Stream] = true - setupUpgradeStream(stream) + setupUpgradeStream(stream, upgradeState) return true } @@ -805,7 +840,10 @@ function writeH2 (client, request) { const expectsPayload = ( method === 'PUT' || method === 'POST' || - method === 'PATCH' + method === 'PATCH' || + method === 'QUERY' || + method === 'PROPFIND' || + method === 'PROPPATCH' ) if (body && typeof body.read === 'function') { @@ -865,230 +903,251 @@ function writeH2 (client, request) { } // TODO(metcoder95): add support for sending trailers - const shouldEndStream = body === null + const shouldEndStream = body === null || contentLength === 0 + const state = { + abort, + body, + client, + contentLength, + expectsPayload, + finalizeRequest, + request, + requestTimeout, + responseReceived: false, + session, + stream: null + } + if (expectContinue) { headers[HTTP2_HEADER_EXPECT] = '100-continue' - stream = requestStream(headers, { endStream: shouldEndStream, signal }) - if (stream == null) { - return false - } - stream[kHTTP2Stream] = true - bindRequestToStream(request, stream, null) - } else { - stream = requestStream(headers, { - endStream: shouldEndStream, - signal - }) - if (stream == null) { - return false - } - stream[kHTTP2Stream] = true - bindRequestToStream(request, stream, null) } + stream = requestStream(headers, { endStream: shouldEndStream, signal }) + if (stream == null) { + return false + } + stream[kHTTP2Stream] = true + stream[kRequestStreamState] = state + state.stream = stream + bindRequestToStream(request, stream, null) + // Increment counter as we have new streams open ++session[kOpenStreams] stream.setTimeout(requestTimeout) - // Track whether we received a response (headers) - let responseReceived = false - const onData = (chunk) => { - if (request.aborted || request.completed) { - return - } + stream[kHTTP2Session] = session + stream.once('close', onRequestStreamClose) - if (request.onResponseData(chunk) === false) { - stream.pause() - } + bindRequestToStream(request, stream, releaseRequestStream) + if (expectContinue) { + stream.once('continue', writeBodyH2) } + stream.once('response', onResponse) + stream.once('end', onEnd) + stream.once('error', onError) + stream.once('frameError', onFrameError) + stream.on('aborted', onAborted) + stream.on('timeout', onTimeout) + stream.once('trailers', onTrailers) - const removeRequestStreamListeners = () => { - stream.off('error', noop) - stream.off('continue', writeBodyH2) - stream.off('response', onResponse) - stream.off('end', onEnd) - stream.off('error', onError) - stream.off('frameError', onFrameError) - stream.off('aborted', onAborted) - stream.off('timeout', onTimeout) - stream.off('trailers', onTrailers) - stream.off('data', onData) + if (!expectContinue) { + writeBodyH2.call(stream) } - const releaseRequestStream = () => { - if (request[kRequestStream] === stream) { - detachRequestFromStream(request) - } + return true +} - removeRequestStreamListeners() +function removeRequestStreamListeners (stream) { + stream.off('error', noop) + stream.off('continue', writeBodyH2) + stream.off('response', onResponse) + stream.off('end', onEnd) + stream.off('error', onError) + stream.off('frameError', onFrameError) + stream.off('aborted', onAborted) + stream.off('timeout', onTimeout) + stream.off('trailers', onTrailers) + stream.off('data', onData) +} - if (!stream.destroyed && !stream.closed) { - stream.once('error', noop) - } +function releaseRequestStream (stream) { + if (stream == null) { + return } - const onResponse = (headers) => { - stream.off('response', onResponse) + const state = stream[kRequestStreamState] + if (state == null) { + return + } - const statusCode = headers[HTTP2_HEADER_STATUS] - delete headers[HTTP2_HEADER_STATUS] - request.onResponseStarted() - responseReceived = true + const { request } = state - // Due to the stream nature, it is possible we face a race condition - // where the stream has been assigned, but the request has been aborted - // the request remains in-flight and headers hasn't been received yet - // for those scenarios, best effort is to destroy the stream immediately - // as there's no value to keep it open. - if (request.aborted) { - releaseRequestStream() - return - } + if (request[kRequestStream] === stream) { + detachRequestFromStream(request) + } - if (request.onResponseStart(Number(statusCode), headers, stream.resume.bind(stream), '') === false) { - stream.pause() - } + removeRequestStreamListeners(stream) - stream.on('data', onData) + if (!stream.destroyed && !stream.closed) { + stream.once('error', noop) } +} - const onEnd = () => { - stream.off('end', onEnd) - - releaseRequestStream() - // If we received a response, this is a normal completion - if (responseReceived) { - if (!request.aborted && !request.completed) { - request.onResponseEnd({}) - } +function onData (chunk) { + const stream = this + const { request } = stream[kRequestStreamState] - finalizeRequest() - } else { - // Stream ended without receiving a response - this is an error - // (e.g., server destroyed the stream before sending headers) - abort(new InformationalError('HTTP/2: stream half-closed (remote)'), true) - } + if (request.aborted || request.completed) { + return } - stream[kHTTP2Session] = session - stream[kRequestStreamOnData] = onData - stream.once('close', onRequestStreamClose) + if (request.onResponseData(chunk) === false) { + stream.pause() + } +} - const onError = function (err) { - stream.off('error', onError) +function onResponse (headers) { + const stream = this + const state = stream[kRequestStreamState] + const { request } = state - releaseRequestStream() - abort(err) - } + stream.off('response', onResponse) - const onFrameError = (type, code) => { - stream.off('frameError', onFrameError) + const statusCode = headers[HTTP2_HEADER_STATUS] + delete headers[HTTP2_HEADER_STATUS] + request.onResponseStarted() + state.responseReceived = true - releaseRequestStream() - abort(new InformationalError(`HTTP/2: "frameError" received - type ${type}, code ${code}`)) + // Due to the stream nature, it is possible we face a race condition + // where the stream has been assigned, but the request has been aborted + // the request remains in-flight and headers hasn't been received yet + // for those scenarios, best effort is to destroy the stream immediately + // as there's no value to keep it open. + if (request.aborted) { + releaseRequestStream(stream) + return } - const onAborted = () => { - stream.off('data', onData) + if (request.onResponseStart(Number(statusCode), headers, stream.resume.bind(stream), '') === false) { + stream.pause() } - const onTimeout = () => { - releaseRequestStream() + stream.on('data', onData) +} - const err = new InformationalError(`HTTP/2: "stream timeout after ${requestTimeout}"`) - abort(err) - } +function onEnd () { + const stream = this + const state = stream[kRequestStreamState] + const { request } = state - const onTrailers = (trailers) => { - stream.off('trailers', onTrailers) + stream.off('end', onEnd) - if (request.aborted || request.completed) { - return + releaseRequestStream(stream) + // If we received a response, this is a normal completion + if (state.responseReceived) { + if (!request.aborted && !request.completed) { + request.onResponseEnd({}) } - releaseRequestStream() - request.onResponseEnd(trailers) - finalizeRequest() + state.finalizeRequest() + } else { + // Stream ended without receiving a response - this is an error + // (e.g., server destroyed the stream before sending headers) + state.abort(new InformationalError('HTTP/2: stream half-closed (remote)'), true) } +} - bindRequestToStream(request, stream, releaseRequestStream) - if (expectContinue) { - stream.once('continue', writeBodyH2) - } - stream.once('response', onResponse) - stream.once('end', onEnd) - stream.once('error', onError) - stream.once('frameError', onFrameError) - stream.on('aborted', onAborted) - stream.on('timeout', onTimeout) - stream.once('trailers', onTrailers) +function onError (err) { + const stream = this + const state = stream[kRequestStreamState] - if (!expectContinue) { - writeBodyH2() + stream.off('error', onError) + + releaseRequestStream(stream) + state.abort(err) +} + +function onFrameError (type, code) { + const stream = this + const state = stream[kRequestStreamState] + + stream.off('frameError', onFrameError) + + releaseRequestStream(stream) + state.abort(new InformationalError(`HTTP/2: "frameError" received - type ${type}, code ${code}`)) +} + +function onAborted () { + this.off('data', onData) +} + +function onTimeout () { + const stream = this + const state = stream[kRequestStreamState] + + releaseRequestStream(stream) + + const err = new InformationalError(`HTTP/2: "stream timeout after ${state.requestTimeout}"`) + state.abort(err) +} + +function onTrailers (trailers) { + const stream = this + const state = stream[kRequestStreamState] + const { request } = state + + stream.off('trailers', onTrailers) + + if (request.aborted || request.completed) { + return } - return true + releaseRequestStream(stream) + request.onResponseEnd(trailers) + state.finalizeRequest() +} - function writeBodyH2 () { - if (!body || contentLength === 0) { - writeBuffer( - abort, - stream, - null, - client, - request, - client[kSocket], - contentLength, - expectsPayload - ) - } else if (util.isBuffer(body)) { - writeBuffer( +function writeBodyH2 () { + const stream = this + const state = stream[kRequestStreamState] + const { abort, body, client, contentLength, expectsPayload, request } = state + + if (!body || contentLength === 0) { + writeBuffer( + abort, + stream, + null, + client, + request, + client[kSocket], + contentLength, + expectsPayload + ) + } else if (util.isBuffer(body)) { + writeBuffer( + abort, + stream, + body, + client, + request, + client[kSocket], + contentLength, + expectsPayload + ) + } else if (util.isBlobLike(body)) { + if (typeof body.stream === 'function') { + writeIterable( abort, stream, - body, + body.stream(), client, request, client[kSocket], contentLength, expectsPayload ) - } else if (util.isBlobLike(body)) { - if (typeof body.stream === 'function') { - writeIterable( - abort, - stream, - body.stream(), - client, - request, - client[kSocket], - contentLength, - expectsPayload - ) - } else { - writeBlob( - abort, - stream, - body, - client, - request, - client[kSocket], - contentLength, - expectsPayload - ) - } - } else if (util.isStream(body)) { - writeStream( - abort, - client[kSocket], - expectsPayload, - stream, - body, - client, - request, - contentLength - ) - } else if (util.isIterable(body)) { - writeIterable( + } else { + writeBlob( abort, stream, body, @@ -1098,9 +1157,31 @@ function writeH2 (client, request) { contentLength, expectsPayload ) - } else { - assert(false) } + } else if (util.isStream(body)) { + writeStream( + abort, + client[kSocket], + expectsPayload, + stream, + body, + client, + request, + contentLength + ) + } else if (util.isIterable(body)) { + writeIterable( + abort, + stream, + body, + client, + request, + client[kSocket], + contentLength, + expectsPayload + ) + } else { + assert(false) } } @@ -1159,8 +1240,6 @@ function writeStream (abort, socket, expectsPayload, h2stream, body, client, req } async function writeBlob (abort, h2stream, body, client, request, socket, contentLength, expectsPayload) { - assert(contentLength === body.size, 'blob body must have content length') - try { if (contentLength != null && contentLength !== body.size) { throw new RequestContentLengthMismatchError() diff --git a/deps/undici/src/lib/dispatcher/client.js b/deps/undici/src/lib/dispatcher/client.js index 6ef97ba0a369a9..c2195f3dd37503 100644 --- a/deps/undici/src/lib/dispatcher/client.js +++ b/deps/undici/src/lib/dispatcher/client.js @@ -52,6 +52,7 @@ const { kOnError, kHTTPContext, kMaxConcurrentStreams, + kHostAuthority, kHTTP2InitialWindowSize, kHTTP2ConnectionWindowSize, kResume, @@ -246,6 +247,7 @@ class Client extends DispatcherBase { } this[kUrl] = util.parseOrigin(url) + this[kHostAuthority] = `${this[kUrl].hostname}${this[kUrl].port ? `:${this[kUrl].port}` : ''}` this[kConnector] = connect this[kPipelining] = pipelining != null ? pipelining : 1 this[kMaxHeadersSize] = maxHeaderSize @@ -257,7 +259,7 @@ class Client extends DispatcherBase { this[kLocalAddress] = localAddress != null ? localAddress : null this[kResuming] = 0 // 0, idle, 1, scheduled, 2 resuming this[kNeedDrain] = 0 // 0, idle, 1, scheduled, 2 resuming - this[kHostHeader] = `host: ${this[kUrl].hostname}${this[kUrl].port ? `:${this[kUrl].port}` : ''}\r\n` + this[kHostHeader] = `host: ${this[kHostAuthority]}\r\n` this[kBodyTimeout] = bodyTimeout != null ? bodyTimeout : 300e3 this[kHeadersTimeout] = headersTimeout != null ? headersTimeout : 300e3 this[kStrictContentLength] = strictContentLength == null ? true : strictContentLength diff --git a/deps/undici/src/lib/dispatcher/pool-base.js b/deps/undici/src/lib/dispatcher/pool-base.js index 1395cd7f2772ec..c8a91d42ba5254 100644 --- a/deps/undici/src/lib/dispatcher/pool-base.js +++ b/deps/undici/src/lib/dispatcher/pool-base.js @@ -14,6 +14,7 @@ const kOnConnect = Symbol('onConnect') const kOnDisconnect = Symbol('onDisconnect') const kOnConnectionError = Symbol('onConnectionError') const kGetDispatcher = Symbol('get dispatcher') +const kHasDispatcher = Symbol('has dispatcher') const kAddClient = Symbol('add client') const kRemoveClient = Symbol('remove client') @@ -162,12 +163,28 @@ class PoolBase extends DispatcherBase { this[kQueued]++ } else if (!dispatcher.dispatch(opts, handler)) { dispatcher[kNeedDrain] = true - this[kNeedDrain] = !this[kGetDispatcher]() + this[kNeedDrain] = !this[kHasDispatcher]() } return !this[kNeedDrain] } + [kHasDispatcher] () { + for (let i = 0; i < this[kClients].length; i++) { + const dispatcher = this[kClients][i] + + if ( + !dispatcher[kNeedDrain] && + dispatcher.closed !== true && + dispatcher.destroyed !== true + ) { + return true + } + } + + return false + } + [kAddClient] (client) { client .on('drain', this[kOnDrain].bind(this, client)) @@ -196,7 +213,7 @@ class PoolBase extends DispatcherBase { client.close(() => {}) - this[kNeedDrain] = this[kClients].some(dispatcher => ( + this[kNeedDrain] = !this[kClients].some(dispatcher => ( !dispatcher[kNeedDrain] && dispatcher.closed !== true && dispatcher.destroyed !== true @@ -210,5 +227,6 @@ module.exports = { kNeedDrain, kAddClient, kRemoveClient, - kGetDispatcher + kGetDispatcher, + kHasDispatcher } diff --git a/deps/undici/src/lib/dispatcher/pool.js b/deps/undici/src/lib/dispatcher/pool.js index f5898b02b3551c..39bf7403236698 100644 --- a/deps/undici/src/lib/dispatcher/pool.js +++ b/deps/undici/src/lib/dispatcher/pool.js @@ -6,6 +6,7 @@ const { kNeedDrain, kAddClient, kGetDispatcher, + kHasDispatcher, kRemoveClient } = require('./pool-base') const Client = require('./client') @@ -115,6 +116,28 @@ class Pool extends PoolBase { return dispatcher } } + + [kHasDispatcher] () { + const clientTtlOption = this[kOptions].clientTtl + for (let i = 0; i < this[kClients].length; i++) { + const client = this[kClients][i] + + if (clientTtlOption != null && clientTtlOption > 0 && client.ttl && ((Date.now() - client.ttl) > clientTtlOption)) { + this[kRemoveClient](client) + i-- + } else if (!client[kNeedDrain]) { + return true + } + } + + if (!this[kConnections] || this[kClients].length < this[kConnections]) { + const dispatcher = this[kFactory](this[kUrl], this[kOptions]) + this[kAddClient](dispatcher) + return true + } + + return false + } } module.exports = Pool diff --git a/deps/undici/src/lib/dispatcher/proxy-agent.js b/deps/undici/src/lib/dispatcher/proxy-agent.js index b89b88bde969d2..8522bdd586d1b7 100644 --- a/deps/undici/src/lib/dispatcher/proxy-agent.js +++ b/deps/undici/src/lib/dispatcher/proxy-agent.js @@ -18,6 +18,7 @@ const kProxyTls = Symbol('proxy tls settings') const kConnectEndpoint = Symbol('connect endpoint function') const kConnectEndpointHTTP1 = Symbol('connect endpoint function (http/1.1 only)') const kTunnelProxy = Symbol('tunnel proxy') +const proxyAuthorization = 'proxy-authorization' function defaultProtocolPort (protocol) { return protocol === 'https:' ? 443 : 80 @@ -298,6 +299,10 @@ function buildHeaders (headers) { const headersPair = {} for (let i = 0; i < headers.length; i += 2) { + if (isProxyAuthorizationHeader(headers[i])) { + throwProxyAuthError() + } + headersPair[headers[i]] = headers[i + 1] } @@ -316,11 +321,23 @@ function buildHeaders (headers) { * It should be removed in the next major version for performance reasons */ function throwIfProxyAuthIsSent (headers) { - const existProxyAuth = headers && Object.keys(headers) - .find((key) => key.toLowerCase() === 'proxy-authorization') - if (existProxyAuth) { - throw new InvalidArgumentError('Proxy-Authorization should be sent in ProxyAgent constructor') + for (const key in headers) { + if (isProxyAuthorizationHeader(key)) { + throwProxyAuthError() + } } } +/** + * @param {string} key + * @returns {boolean} + */ +function isProxyAuthorizationHeader (key) { + return key.length === proxyAuthorization.length && key.toLowerCase() === proxyAuthorization +} + +function throwProxyAuthError () { + throw new InvalidArgumentError('Proxy-Authorization should be sent in ProxyAgent constructor') +} + module.exports = ProxyAgent diff --git a/deps/undici/src/lib/dispatcher/round-robin-pool.js b/deps/undici/src/lib/dispatcher/round-robin-pool.js index 17daea7a4a3274..b1f4763530f5a2 100644 --- a/deps/undici/src/lib/dispatcher/round-robin-pool.js +++ b/deps/undici/src/lib/dispatcher/round-robin-pool.js @@ -6,6 +6,7 @@ const { kNeedDrain, kAddClient, kGetDispatcher, + kHasDispatcher, kRemoveClient } = require('./pool-base') const Client = require('./client') @@ -128,6 +129,31 @@ class RoundRobinPool extends PoolBase { return dispatcher } } + + [kHasDispatcher] () { + const clientTtlOption = this[kOptions].clientTtl + for (let i = 0; i < this[kClients].length; i++) { + const client = this[kClients][i] + + if (clientTtlOption != null && clientTtlOption > 0 && client.ttl && ((Date.now() - client.ttl) > clientTtlOption)) { + this[kRemoveClient](client) + if (i <= this[kIndex]) { + this[kIndex]-- + } + i-- + } else if (!client[kNeedDrain]) { + return true + } + } + + if (!this[kConnections] || this[kClients].length < this[kConnections]) { + const dispatcher = this[kFactory](this[kUrl], this[kOptions]) + this[kAddClient](dispatcher) + return true + } + + return false + } } module.exports = RoundRobinPool diff --git a/deps/undici/src/lib/dispatcher/socks5-proxy-agent.js b/deps/undici/src/lib/dispatcher/socks5-proxy-agent.js index c0b681929ac499..3cc5b19f0be214 100644 --- a/deps/undici/src/lib/dispatcher/socks5-proxy-agent.js +++ b/deps/undici/src/lib/dispatcher/socks5-proxy-agent.js @@ -1,6 +1,5 @@ 'use strict' -const net = require('node:net') const { URL } = require('node:url') let tls // include tls conditionally since it is not always available @@ -17,6 +16,7 @@ const debug = debuglog('undici:socks5-proxy') const kProxyUrl = Symbol('proxy url') const kProxyHeaders = Symbol('proxy headers') const kProxyAuth = Symbol('proxy auth') +const kProxyProtocol = Symbol('proxy protocol') const kPools = Symbol('pools') const kConnector = Symbol('connector') @@ -52,6 +52,7 @@ class Socks5ProxyAgent extends DispatcherBase { this[kProxyUrl] = url this[kProxyHeaders] = options.headers || {} + this[kProxyProtocol] = options.proxyTls ? 'https:' : 'http:' // Extract auth from URL or options this[kProxyAuth] = { @@ -81,25 +82,20 @@ class Socks5ProxyAgent extends DispatcherBase { // Connect to the SOCKS5 proxy const socketReady = Promise.withResolvers() - const onSocketConnect = () => { - socket.removeListener('error', onSocketError) - socketReady.resolve(socket) - } - - const onSocketError = (err) => { - socket.removeListener('connect', onSocketConnect) - socketReady.reject(err) - } - - const socket = net.connect({ + this[kConnector]({ + hostname: proxyHost, host: proxyHost, - port: proxyPort + port: proxyPort, + protocol: this[kProxyProtocol] + }, (err, socket) => { + if (err) { + socketReady.reject(err) + } else { + socketReady.resolve(socket) + } }) - socket.once('connect', onSocketConnect) - socket.once('error', onSocketError) - - await socketReady.promise + const socket = await socketReady.promise // Create SOCKS5 client const socks5Client = new Socks5Client(socket, this[kProxyAuth]) @@ -177,7 +173,7 @@ class Socks5ProxyAgent extends DispatcherBase { /** * Dispatch a request through the SOCKS5 proxy */ - async [kDispatch] (opts, handler) { + [kDispatch] (opts, handler) { const { origin } = opts debug('dispatching request to', origin, 'via SOCKS5') @@ -234,8 +230,12 @@ class Socks5ProxyAgent extends DispatcherBase { return pool[kDispatch](opts, handler) } catch (err) { debug('dispatch error:', err) - if (typeof handler.onError === 'function') { + if (typeof handler.onResponseError === 'function') { + handler.onResponseError(null, err) + return false + } else if (typeof handler.onError === 'function') { handler.onError(err) + return false } else { throw err } diff --git a/deps/undici/src/lib/handler/retry-handler.js b/deps/undici/src/lib/handler/retry-handler.js index ee2f69a2043e69..7ec506e21ebf5b 100644 --- a/deps/undici/src/lib/handler/retry-handler.js +++ b/deps/undici/src/lib/handler/retry-handler.js @@ -68,6 +68,8 @@ class RetryHandler { this.start = 0 this.end = null this.etag = null + this.statusCode = null + this.headers = null } onResponseStartWithRetry (controller, statusCode, headers, statusMessage, err) { @@ -183,6 +185,8 @@ class RetryHandler { onResponseStart (controller, statusCode, headers, statusMessage) { this.error = null this.retryCount += 1 + this.statusCode = statusCode + this.headers = headers if (statusCode >= 300) { const err = new RequestRetryError('Request failed', statusCode, { @@ -320,6 +324,16 @@ class RetryHandler { } if (!this.error) { + // Verify that the received body length matches the expected range + // when we have a finite end position (from Content-Length or Content-Range) + if (this.end != null && Number.isFinite(this.end)) { + if (this.start !== this.end + 1) { + throw new RequestRetryError('Content-Range mismatch', this.statusCode, { + headers: this.headers, + data: { count: this.retryCount } + }) + } + } this.retryCount = 0 return this.handler.onResponseEnd?.(controller, trailers) } diff --git a/deps/undici/src/lib/llhttp/wasm_build_env.txt b/deps/undici/src/lib/llhttp/wasm_build_env.txt index 7440bdcb1b3820..3ba57f70d8d37e 100644 --- a/deps/undici/src/lib/llhttp/wasm_build_env.txt +++ b/deps/undici/src/lib/llhttp/wasm_build_env.txt @@ -1,5 +1,5 @@ -> undici@8.2.0 build:wasm +> undici@8.3.0 build:wasm > node build/wasm.js --docker > docker run --rm --platform=linux/x86_64 --user 1001:1001 --mount type=bind,source=/home/runner/work/node/node/deps/undici/src/lib/llhttp,target=/home/node/build/lib/llhttp --mount type=bind,source=/home/runner/work/node/node/deps/undici/src/build,target=/home/node/build/build --mount type=bind,source=/home/runner/work/node/node/deps/undici/src/deps,target=/home/node/build/deps -t ghcr.io/nodejs/wasm-builder@sha256:975f391d907e42a75b8c72eb77c782181e941608687d4d8694c3e9df415a0970 node build/wasm.js diff --git a/deps/undici/src/lib/mock/mock-utils.js b/deps/undici/src/lib/mock/mock-utils.js index 1022405d239aff..5b6b5bd79cfa56 100644 --- a/deps/undici/src/lib/mock/mock-utils.js +++ b/deps/undici/src/lib/mock/mock-utils.js @@ -424,7 +424,9 @@ function buildMockDispatch () { throw new MockNotMatchedError(`${error.message}: subsequent request to origin ${origin} was not allowed (net.connect disabled)${interceptsMessage}`) } if (checkNetConnect(netConnect, origin)) { - originalDispatch.call(this, opts, handler) + originalDispatch.call(this, '__mockAgentBodyForDispatch' in opts + ? { ...opts, body: opts.__mockAgentBodyForDispatch } + : opts, handler) } else { throw new MockNotMatchedError(`${error.message}: subsequent request to origin ${origin} was not allowed (net.connect is not enabled for this origin)${interceptsMessage}`) } diff --git a/deps/undici/src/lib/mock/snapshot-agent.js b/deps/undici/src/lib/mock/snapshot-agent.js index a3aee68cb2db0f..362745c414c158 100644 --- a/deps/undici/src/lib/mock/snapshot-agent.js +++ b/deps/undici/src/lib/mock/snapshot-agent.js @@ -55,7 +55,9 @@ class SnapshotAgent extends MockAgent { ignoreHeaders: opts.ignoreHeaders, excludeHeaders: opts.excludeHeaders, matchBody: opts.matchBody, + normalizeBody: opts.normalizeBody, matchQuery: opts.matchQuery, + normalizeQuery: opts.normalizeQuery, caseSensitive: opts.caseSensitive, shouldRecord: opts.shouldRecord, shouldPlayback: opts.shouldPlayback, diff --git a/deps/undici/src/lib/mock/snapshot-recorder.js b/deps/undici/src/lib/mock/snapshot-recorder.js index b5d07fae381cd2..57a7e7a41dd0dd 100644 --- a/deps/undici/src/lib/mock/snapshot-recorder.js +++ b/deps/undici/src/lib/mock/snapshot-recorder.js @@ -46,7 +46,9 @@ const { hashId, isUrlExcludedFactory, normalizeHeaders, createHeaderFilters } = * @property {Array} [ignoreHeaders=[]] - Headers to ignore for matching * @property {Array} [excludeHeaders=[]] - Headers to exclude from matching * @property {boolean} [matchBody=true] - Whether to match request body - * @property {boolean} [matchQuery=true] - Whether to match query properties + * @property {(body: string|Buffer|null|undefined) => string} [normalizeBody] - Function to normalize the body before matching (e.g. strip timestamps) + * @property {boolean} [matchQuery=true] - Whether to match query parameters + * @property {(query: URLSearchParams) => string} [normalizeQuery] - Function to normalize query parameters before matching (e.g. strip volatile params) * @property {boolean} [caseSensitive=false] - Whether header matching is case-sensitive */ @@ -79,6 +81,37 @@ const { hashId, isUrlExcludedFactory, normalizeHeaders, createHeaderFilters } = * @property {string} timestamp - ISO timestamp of when the snapshot was created */ +/** + * Normalizes the URL string used for request matching. + * + * @param {URL} url - Parsed request URL + * @param {boolean} matchQuery - Whether to include query parameters in matching + * @param {((query: URLSearchParams) => string)|undefined} normalizeQuery - Optional normalization function + * @returns {string} - URL string for hashing + */ +function normalizeUrlForMatching (url, matchQuery, normalizeQuery) { + if (matchQuery === false) return `${url.origin}${url.pathname}` + if (normalizeQuery) { + const normalized = String(normalizeQuery(url.searchParams) ?? '') + return normalized ? `${url.origin}${url.pathname}?${normalized}` : `${url.origin}${url.pathname}` + } + return url.toString() +} + +/** + * Normalizes the body value used for request matching. + * + * @param {string|Buffer|null|undefined} body - Raw request body + * @param {boolean} matchBody - Whether to include the body in matching + * @param {((body: string|Buffer|null|undefined) => string)|undefined} normalizeBody - Optional normalization function + * @returns {string} - Body string for hashing + */ +function normalizeBodyForMatching (body, matchBody, normalizeBody) { + if (matchBody === false) return '' + if (normalizeBody) return String(normalizeBody(body) ?? '') + return body ? String(body) : '' +} + /** * Formats a request for consistent snapshot storage * Caches normalized headers to avoid repeated processing @@ -99,9 +132,9 @@ function formatRequestKey (opts, headerFilters, matchOptions = {}) { return { method: opts.method || 'GET', - url: matchOptions.matchQuery !== false ? url.toString() : `${url.origin}${url.pathname}`, + url: normalizeUrlForMatching(url, matchOptions.matchQuery, matchOptions.normalizeQuery), headers: filterHeadersForMatching(normalized, headerFilters, matchOptions), - body: matchOptions.matchBody !== false && opts.body ? String(opts.body) : '' + body: normalizeBodyForMatching(opts.body, matchOptions.matchBody, matchOptions.normalizeBody) } } @@ -250,7 +283,9 @@ class SnapshotRecorder { ignoreHeaders: options.ignoreHeaders || [], excludeHeaders: options.excludeHeaders || [], matchBody: options.matchBody !== false, // default: true + normalizeBody: options.normalizeBody || undefined, matchQuery: options.matchQuery !== false, // default: true + normalizeQuery: options.normalizeQuery || undefined, caseSensitive: options.caseSensitive || false } diff --git a/deps/undici/src/lib/web/fetch/body.js b/deps/undici/src/lib/web/fetch/body.js index 7e08b29fd6c9e9..525bafb2c3c56e 100644 --- a/deps/undici/src/lib/web/fetch/body.js +++ b/deps/undici/src/lib/web/fetch/body.js @@ -7,7 +7,7 @@ const { fullyReadBody, extractMimeType } = require('./util') -const { FormData, setFormDataState } = require('./formdata') +const { FormData, setFormDataState, getFormDataBoundary } = require('./formdata') const { webidl } = require('../webidl') const assert = require('node:assert') const { isErrored, isDisturbed } = require('node:stream') @@ -16,11 +16,6 @@ const { serializeAMimeType } = require('./data-url') const { multipartFormDataParser } = require('./formdata-parser') const { parseJSONFromBytes } = require('../infra') const { utf8DecodeBytes } = require('../../encoding') -const { runtimeFeatures } = require('../../util/runtime-features.js') - -const random = runtimeFeatures.has('crypto') - ? require('node:crypto').randomInt - : (max) => Math.floor(Math.random() * max) const textEncoder = new TextEncoder() function noop () {} @@ -106,7 +101,7 @@ function extractBody (object, keepalive = false) { // Set source to a copy of the bytes held by object. source = webidl.util.getCopyOfBytesHeldByBufferSource(object) } else if (webidl.is.FormData(object)) { - const boundary = `----formdata-undici-0${`${random(1e11)}`.padStart(11, '0')}` + const boundary = getFormDataBoundary(object) const prefix = `--${boundary}\r\nContent-Disposition: form-data` /*! formdata-polyfill. MIT License. Jimmy Wärting */ diff --git a/deps/undici/src/lib/web/fetch/formdata.js b/deps/undici/src/lib/web/fetch/formdata.js index c21fb06a3eeb62..226bd32958775b 100644 --- a/deps/undici/src/lib/web/fetch/formdata.js +++ b/deps/undici/src/lib/web/fetch/formdata.js @@ -4,10 +4,16 @@ const { iteratorMixin } = require('./util') const { kEnumerableProperty } = require('../../core/util') const { webidl } = require('../webidl') const nodeUtil = require('node:util') +const { runtimeFeatures } = require('../../util/runtime-features.js') + +const random = runtimeFeatures.has('crypto') + ? require('node:crypto').randomInt + : (max) => Math.floor(Math.random() * max) // https://xhr.spec.whatwg.org/#formdata class FormData { #state = [] + #boundary = null constructor (form = undefined) { webidl.util.markAsUncloneable(this) @@ -192,11 +198,24 @@ class FormData { static setFormDataState (formData, newState) { formData.#state = newState } + + /** + * @param {FormData} formData + * @returns {string | null} + */ + static getFormDataBoundary (formData) { + const boundary = formData.#boundary + if (boundary != null) return boundary + + // eslint-disable-next-line no-return-assign + return formData.#boundary = `----formdata-undici-0${`${random(1e11)}`.padStart(11, '0')}` + } } -const { getFormDataState, setFormDataState } = FormData +const { getFormDataState, setFormDataState, getFormDataBoundary } = FormData Reflect.deleteProperty(FormData, 'getFormDataState') Reflect.deleteProperty(FormData, 'setFormDataState') +Reflect.deleteProperty(FormData, 'getFormDataBoundary') iteratorMixin('FormData', FormData, getFormDataState, 'name', 'value') @@ -256,4 +275,4 @@ function makeEntry (name, value, filename) { webidl.is.FormData = webidl.util.MakeTypeAssertion(FormData) -module.exports = { FormData, makeEntry, setFormDataState } +module.exports = { FormData, makeEntry, setFormDataState, getFormDataBoundary } diff --git a/deps/undici/src/lib/web/fetch/index.js b/deps/undici/src/lib/web/fetch/index.js index 33d7761cecda9f..1959f27c04aa96 100644 --- a/deps/undici/src/lib/web/fetch/index.js +++ b/deps/undici/src/lib/web/fetch/index.js @@ -2184,6 +2184,8 @@ async function httpNetworkFetch ( origin: url.origin, method: request.method, body: agent.isMockActive ? request.body && (request.body.source || request.body.stream) : body, + // Preserve the serialized fetch body for MockAgent net-connect fallthroughs. + __mockAgentBodyForDispatch: body, headers: request.headersList.entries, maxRedirections: 0, upgrade: request.mode === 'websocket' ? 'websocket' : undefined, diff --git a/deps/undici/src/package-lock.json b/deps/undici/src/package-lock.json index 30097844c87d07..dcfc6ca55f2bff 100644 --- a/deps/undici/src/package-lock.json +++ b/deps/undici/src/package-lock.json @@ -1,12 +1,12 @@ { "name": "undici", - "version": "8.2.0", + "version": "8.3.0", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "undici", - "version": "8.2.0", + "version": "8.3.0", "license": "MIT", "devDependencies": { "@fastify/busboy": "3.2.0", @@ -15,8 +15,8 @@ "@sinonjs/fake-timers": "^12.0.0", "@types/node": "^22.0.0", "abort-controller": "^3.0.0", - "borp": "^0.20.0", - "c8": "^10.0.0", + "borp": "^1.0.0", + "c8": "^11.0.0", "cross-env": "^10.0.0", "dns-packet": "^5.4.0", "esbuild": "^0.28.0", @@ -25,7 +25,7 @@ "husky": "^9.0.7", "jest": "^30.0.5", "jsondiffpatch": "^0.7.3", - "neostandard": "^0.12.0", + "neostandard": "^0.13.0", "node-forge": "^1.3.1", "proxy": "^4.0.0", "tsd": "^0.33.0", @@ -91,9 +91,9 @@ } }, "node_modules/@babel/compat-data": { - "version": "7.29.0", - "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.29.0.tgz", - "integrity": "sha512-T1NCJqT/j9+cn8fvkt7jtwbLBfLC/1y1c7NtCeXFRgzGTsafi68MRv8yzkYSapBnFA6L3U2VSc02ciDzoAJhJg==", + "version": "7.29.3", + "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.29.3.tgz", + "integrity": "sha512-LIVqM46zQWZhj17qA8wb4nW/ixr2y1Nw+r1etiAWgRM6U1IqP+LNhL1yg440jYZR72jCWcWbLWzIosH+uP1fqg==", "dev": true, "license": "MIT", "engines": { @@ -262,9 +262,9 @@ } }, "node_modules/@babel/parser": { - "version": "7.29.2", - "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.29.2.tgz", - "integrity": "sha512-4GgRzy/+fsBa72/RZVJmGKPmZu9Byn8o4MoLpmNe1m8ZfYnz5emHLQz3U4gLud6Zwl0RZIcgiLD7Uq7ySFuDLA==", + "version": "7.29.3", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.29.3.tgz", + "integrity": "sha512-b3ctpQwp+PROvU/cttc4OYl4MzfJUWy6FZg+PMXfzmt/+39iHVF0sDfqay8TQM3JA2EUOyKcFZt75jWriQijsA==", "dev": true, "license": "MIT", "dependencies": { @@ -582,9 +582,9 @@ "license": "Apache-2.0" }, "node_modules/@emnapi/core": { - "version": "1.9.2", - "resolved": "https://registry.npmjs.org/@emnapi/core/-/core-1.9.2.tgz", - "integrity": "sha512-UC+ZhH3XtczQYfOlu3lNEkdW/p4dsJ1r/bP7H8+rhao3TTTMO1ATq/4DdIi23XuGoFY+Cz0JmCbdVl0hz9jZcA==", + "version": "1.10.0", + "resolved": "https://registry.npmjs.org/@emnapi/core/-/core-1.10.0.tgz", + "integrity": "sha512-yq6OkJ4p82CAfPl0u9mQebQHKPJkY7WrIuk205cTYnYe+k2Z8YBh11FrbRG/H6ihirqcacOgl2BIO8oyMQLeXw==", "dev": true, "license": "MIT", "optional": true, @@ -594,9 +594,9 @@ } }, "node_modules/@emnapi/runtime": { - "version": "1.9.2", - "resolved": "https://registry.npmjs.org/@emnapi/runtime/-/runtime-1.9.2.tgz", - "integrity": "sha512-3U4+MIWHImeyu1wnmVygh5WlgfYDtyf0k8AbLhMFxOipihf6nrWC4syIm/SwEeec0mNSafiiNnMJwbza/Is6Lw==", + "version": "1.10.0", + "resolved": "https://registry.npmjs.org/@emnapi/runtime/-/runtime-1.10.0.tgz", + "integrity": "sha512-ewvYlk86xUoGI0zQRNq/mC+16R1QeDlKQy21Ki3oSYXNgLb45GV1P6A0M+/s6nyCuNDqe5VpaY84BzXGwVbwFA==", "dev": true, "license": "MIT", "optional": true, @@ -1424,17 +1424,17 @@ } }, "node_modules/@jest/console": { - "version": "30.3.0", - "resolved": "https://registry.npmjs.org/@jest/console/-/console-30.3.0.tgz", - "integrity": "sha512-PAwCvFJ4696XP2qZj+LAn1BWjZaJ6RjG6c7/lkMaUJnkyMS34ucuIsfqYvfskVNvUI27R/u4P1HMYFnlVXG/Ww==", + "version": "30.4.1", + "resolved": "https://registry.npmjs.org/@jest/console/-/console-30.4.1.tgz", + "integrity": "sha512-v3bhyxUh9Hgmo5p6hAOXe14/R3ZxZDOsvHleh4B07z3m/x4/ngPUXEm9XwK4sF4u+f+P2ORb0Ge+MgpaqRMVDA==", "dev": true, "license": "MIT", "dependencies": { - "@jest/types": "30.3.0", + "@jest/types": "30.4.1", "@types/node": "*", "chalk": "^4.1.2", - "jest-message-util": "30.3.0", - "jest-util": "30.3.0", + "jest-message-util": "30.4.1", + "jest-util": "30.4.1", "slash": "^3.0.0" }, "engines": { @@ -1442,38 +1442,39 @@ } }, "node_modules/@jest/core": { - "version": "30.3.0", - "resolved": "https://registry.npmjs.org/@jest/core/-/core-30.3.0.tgz", - "integrity": "sha512-U5mVPsBxLSO6xYbf+tgkymLx+iAhvZX43/xI1+ej2ZOPnPdkdO1CzDmFKh2mZBn2s4XZixszHeQnzp1gm/DIxw==", + "version": "30.4.2", + "resolved": "https://registry.npmjs.org/@jest/core/-/core-30.4.2.tgz", + "integrity": "sha512-TZJA6cPJUFxoWhxaLo8t0VX/MZX2wPWr0uIDvLSHIvN4gu9h02vSzqI2kBADG1ExqQlC+cY09xKMSreivvrChQ==", "dev": true, "license": "MIT", "dependencies": { - "@jest/console": "30.3.0", - "@jest/pattern": "30.0.1", - "@jest/reporters": "30.3.0", - "@jest/test-result": "30.3.0", - "@jest/transform": "30.3.0", - "@jest/types": "30.3.0", + "@jest/console": "30.4.1", + "@jest/pattern": "30.4.0", + "@jest/reporters": "30.4.1", + "@jest/test-result": "30.4.1", + "@jest/transform": "30.4.1", + "@jest/types": "30.4.1", "@types/node": "*", "ansi-escapes": "^4.3.2", "chalk": "^4.1.2", "ci-info": "^4.2.0", "exit-x": "^0.2.2", + "fast-json-stable-stringify": "^2.1.0", "graceful-fs": "^4.2.11", - "jest-changed-files": "30.3.0", - "jest-config": "30.3.0", - "jest-haste-map": "30.3.0", - "jest-message-util": "30.3.0", - "jest-regex-util": "30.0.1", - "jest-resolve": "30.3.0", - "jest-resolve-dependencies": "30.3.0", - "jest-runner": "30.3.0", - "jest-runtime": "30.3.0", - "jest-snapshot": "30.3.0", - "jest-util": "30.3.0", - "jest-validate": "30.3.0", - "jest-watcher": "30.3.0", - "pretty-format": "30.3.0", + "jest-changed-files": "30.4.1", + "jest-config": "30.4.2", + "jest-haste-map": "30.4.1", + "jest-message-util": "30.4.1", + "jest-regex-util": "30.4.0", + "jest-resolve": "30.4.1", + "jest-resolve-dependencies": "30.4.2", + "jest-runner": "30.4.2", + "jest-runtime": "30.4.2", + "jest-snapshot": "30.4.1", + "jest-util": "30.4.1", + "jest-validate": "30.4.1", + "jest-watcher": "30.4.1", + "pretty-format": "30.4.1", "slash": "^3.0.0" }, "engines": { @@ -1489,9 +1490,9 @@ } }, "node_modules/@jest/diff-sequences": { - "version": "30.3.0", - "resolved": "https://registry.npmjs.org/@jest/diff-sequences/-/diff-sequences-30.3.0.tgz", - "integrity": "sha512-cG51MVnLq1ecVUaQ3fr6YuuAOitHK1S4WUJHnsPFE/quQr33ADUx1FfrTCpMCRxvy0Yr9BThKpDjSlcTi91tMA==", + "version": "30.4.0", + "resolved": "https://registry.npmjs.org/@jest/diff-sequences/-/diff-sequences-30.4.0.tgz", + "integrity": "sha512-zOpzlfUs45l6u7jm39qr87JCHUDsaeCtvL+kQe/Vn9jSnRB4/5IPXISm0h9I1vZW/o00Kn4UTJ2MOlhnUGwv3g==", "dev": true, "license": "MIT", "engines": { @@ -1499,39 +1500,39 @@ } }, "node_modules/@jest/environment": { - "version": "30.3.0", - "resolved": "https://registry.npmjs.org/@jest/environment/-/environment-30.3.0.tgz", - "integrity": "sha512-SlLSF4Be735yQXyh2+mctBOzNDx5s5uLv88/j8Qn1wH679PDcwy67+YdADn8NJnGjzlXtN62asGH/T4vWOkfaw==", + "version": "30.4.1", + "resolved": "https://registry.npmjs.org/@jest/environment/-/environment-30.4.1.tgz", + "integrity": "sha512-AK9yNRqgKxiabqMoe4oW+3/TSSeV8vkdC7BGaxZdU0AFXfOpofTLqdru2GXKZghP3sdgwE9XXpnVwfZ8JnFV4w==", "dev": true, "license": "MIT", "dependencies": { - "@jest/fake-timers": "30.3.0", - "@jest/types": "30.3.0", + "@jest/fake-timers": "30.4.1", + "@jest/types": "30.4.1", "@types/node": "*", - "jest-mock": "30.3.0" + "jest-mock": "30.4.1" }, "engines": { "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" } }, "node_modules/@jest/expect": { - "version": "30.3.0", - "resolved": "https://registry.npmjs.org/@jest/expect/-/expect-30.3.0.tgz", - "integrity": "sha512-76Nlh4xJxk2D/9URCn3wFi98d2hb19uWE1idLsTt2ywhvdOldbw3S570hBgn25P4ICUZ/cBjybrBex2g17IDbg==", + "version": "30.4.1", + "resolved": "https://registry.npmjs.org/@jest/expect/-/expect-30.4.1.tgz", + "integrity": "sha512-ginrj6TMgh2GshLUGCjO94Ptx9HhdZA/I6A9iUfyeLKFtdAjnKzHDgzgP9HYQgbxM1lbXScQ2eUBz2lGeVDPWA==", "dev": true, "license": "MIT", "dependencies": { - "expect": "30.3.0", - "jest-snapshot": "30.3.0" + "expect": "30.4.1", + "jest-snapshot": "30.4.1" }, "engines": { "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" } }, "node_modules/@jest/expect-utils": { - "version": "30.3.0", - "resolved": "https://registry.npmjs.org/@jest/expect-utils/-/expect-utils-30.3.0.tgz", - "integrity": "sha512-j0+W5iQQ8hBh7tHZkTQv3q2Fh/M7Je72cIsYqC4OaktgtO7v1So9UTjp6uPBHIaB6beoF/RRsCgMJKvti0wADA==", + "version": "30.4.1", + "resolved": "https://registry.npmjs.org/@jest/expect-utils/-/expect-utils-30.4.1.tgz", + "integrity": "sha512-ZBn5CglH8fBsQsvs4VWNzD4aWfUYks+IdOOQU3MEK71ol/BcVm+P+rtb1KpiFBpSWSCE27uOahyyf1vfqOVbcQ==", "dev": true, "license": "MIT", "dependencies": { @@ -1542,27 +1543,27 @@ } }, "node_modules/@jest/fake-timers": { - "version": "30.3.0", - "resolved": "https://registry.npmjs.org/@jest/fake-timers/-/fake-timers-30.3.0.tgz", - "integrity": "sha512-WUQDs8SOP9URStX1DzhD425CqbN/HxUYCTwVrT8sTVBfMvFqYt/s61EK5T05qnHu0po6RitXIvP9otZxYDzTGQ==", + "version": "30.4.1", + "resolved": "https://registry.npmjs.org/@jest/fake-timers/-/fake-timers-30.4.1.tgz", + "integrity": "sha512-iW5umdmfPeWzehrVhugFQZqCchSCud5S1l2YT0O9ZhjRR0ExclANDZkiSBwzqtnlOn0J1JXvO+HZ6rkuyOVOgQ==", "dev": true, "license": "MIT", "dependencies": { - "@jest/types": "30.3.0", - "@sinonjs/fake-timers": "^15.0.0", + "@jest/types": "30.4.1", + "@sinonjs/fake-timers": "^15.4.0", "@types/node": "*", - "jest-message-util": "30.3.0", - "jest-mock": "30.3.0", - "jest-util": "30.3.0" + "jest-message-util": "30.4.1", + "jest-mock": "30.4.1", + "jest-util": "30.4.1" }, "engines": { "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" } }, "node_modules/@jest/fake-timers/node_modules/@sinonjs/fake-timers": { - "version": "15.3.2", - "resolved": "https://registry.npmjs.org/@sinonjs/fake-timers/-/fake-timers-15.3.2.tgz", - "integrity": "sha512-mrn35Jl2pCpns+mE3HaZa1yPN5EYCRgiMI+135COjr2hr8Cls9DXqIZ57vZe2cz7y2XVSq92tcs6kGQcT1J8Rw==", + "version": "15.4.0", + "resolved": "https://registry.npmjs.org/@sinonjs/fake-timers/-/fake-timers-15.4.0.tgz", + "integrity": "sha512-DsG+8/LscQIQg68J6Ef3dv10u6nVyetYn923s3/sus5eaGfTo1of5WMZSLf0UJc9KDuKPilPH0UDJCjvNbDNCA==", "dev": true, "license": "BSD-3-Clause", "dependencies": { @@ -1580,47 +1581,47 @@ } }, "node_modules/@jest/globals": { - "version": "30.3.0", - "resolved": "https://registry.npmjs.org/@jest/globals/-/globals-30.3.0.tgz", - "integrity": "sha512-+owLCBBdfpgL3HU+BD5etr1SvbXpSitJK0is1kiYjJxAAJggYMRQz5hSdd5pq1sSggfxPbw2ld71pt4x5wwViA==", + "version": "30.4.1", + "resolved": "https://registry.npmjs.org/@jest/globals/-/globals-30.4.1.tgz", + "integrity": "sha512-ZbuY4cmXC8DkxYjfvT2DbcHWL2T6vmsMhXCDcmTB2T0y0gaezBI77ufq5ZAIdcRkYZ7NEQEDg1xFeKbxUJ5v5Q==", "dev": true, "license": "MIT", "dependencies": { - "@jest/environment": "30.3.0", - "@jest/expect": "30.3.0", - "@jest/types": "30.3.0", - "jest-mock": "30.3.0" + "@jest/environment": "30.4.1", + "@jest/expect": "30.4.1", + "@jest/types": "30.4.1", + "jest-mock": "30.4.1" }, "engines": { "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" } }, "node_modules/@jest/pattern": { - "version": "30.0.1", - "resolved": "https://registry.npmjs.org/@jest/pattern/-/pattern-30.0.1.tgz", - "integrity": "sha512-gWp7NfQW27LaBQz3TITS8L7ZCQ0TLvtmI//4OwlQRx4rnWxcPNIYjxZpDcN4+UlGxgm3jS5QPz8IPTCkb59wZA==", + "version": "30.4.0", + "resolved": "https://registry.npmjs.org/@jest/pattern/-/pattern-30.4.0.tgz", + "integrity": "sha512-RAWn3+f9u8BsHijKJ71uHcFp6vmyEt6VvoWXkl6hKF3qVIuWNmudVjg12DlBPGup/frIl5UcUlH5HfEuvHpEXg==", "dev": true, "license": "MIT", "dependencies": { "@types/node": "*", - "jest-regex-util": "30.0.1" + "jest-regex-util": "30.4.0" }, "engines": { "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" } }, "node_modules/@jest/reporters": { - "version": "30.3.0", - "resolved": "https://registry.npmjs.org/@jest/reporters/-/reporters-30.3.0.tgz", - "integrity": "sha512-a09z89S+PkQnL055bVj8+pe2Caed2PBOaczHcXCykW5ngxX9EWx/1uAwncxc/HiU0oZqfwseMjyhxgRjS49qPw==", + "version": "30.4.1", + "resolved": "https://registry.npmjs.org/@jest/reporters/-/reporters-30.4.1.tgz", + "integrity": "sha512-/SnkPCzEQpUaBH81kjdEdDdo2WZl5hxw+BmLDGWjRkm8o7XlhjwsU36cqwe5PGBE5WYpBvDzRSdXx9rbGuJtNA==", "dev": true, "license": "MIT", "dependencies": { "@bcoe/v8-coverage": "^0.2.3", - "@jest/console": "30.3.0", - "@jest/test-result": "30.3.0", - "@jest/transform": "30.3.0", - "@jest/types": "30.3.0", + "@jest/console": "30.4.1", + "@jest/test-result": "30.4.1", + "@jest/transform": "30.4.1", + "@jest/types": "30.4.1", "@jridgewell/trace-mapping": "^0.3.25", "@types/node": "*", "chalk": "^4.1.2", @@ -1633,9 +1634,9 @@ "istanbul-lib-report": "^3.0.0", "istanbul-lib-source-maps": "^5.0.0", "istanbul-reports": "^3.1.3", - "jest-message-util": "30.3.0", - "jest-util": "30.3.0", - "jest-worker": "30.3.0", + "jest-message-util": "30.4.1", + "jest-util": "30.4.1", + "jest-worker": "30.4.1", "slash": "^3.0.0", "string-length": "^4.0.2", "v8-to-istanbul": "^9.0.1" @@ -1660,9 +1661,9 @@ "license": "MIT" }, "node_modules/@jest/schemas": { - "version": "30.0.5", - "resolved": "https://registry.npmjs.org/@jest/schemas/-/schemas-30.0.5.tgz", - "integrity": "sha512-DmdYgtezMkh3cpU8/1uyXakv3tJRcmcXxBOcO0tbaozPwpmh4YMsnWrQm9ZmZMfa5ocbxzbFk6O4bDPEc/iAnA==", + "version": "30.4.1", + "resolved": "https://registry.npmjs.org/@jest/schemas/-/schemas-30.4.1.tgz", + "integrity": "sha512-i6b4qw5qnP8c5FEeBJg/uZQ4ddrkN6Ca8qISJh0pr7a5hfn3h3v5x60BEbOC7OYAGZNMs1LfFLwnW2CuK8F57Q==", "dev": true, "license": "MIT", "dependencies": { @@ -1673,13 +1674,13 @@ } }, "node_modules/@jest/snapshot-utils": { - "version": "30.3.0", - "resolved": "https://registry.npmjs.org/@jest/snapshot-utils/-/snapshot-utils-30.3.0.tgz", - "integrity": "sha512-ORbRN9sf5PP82v3FXNSwmO1OTDR2vzR2YTaR+E3VkSBZ8zadQE6IqYdYEeFH1NIkeB2HIGdF02dapb6K0Mj05g==", + "version": "30.4.1", + "resolved": "https://registry.npmjs.org/@jest/snapshot-utils/-/snapshot-utils-30.4.1.tgz", + "integrity": "sha512-ObY4ljvQ95mt6iwKtVLetR/4yXiAgl3H4nJxhztr0MTjrN97TwDYrnCp/kF60Ec9HdhkWTHSu+Hg05aXfngpOA==", "dev": true, "license": "MIT", "dependencies": { - "@jest/types": "30.3.0", + "@jest/types": "30.4.1", "chalk": "^4.1.2", "graceful-fs": "^4.2.11", "natural-compare": "^1.4.0" @@ -1704,14 +1705,14 @@ } }, "node_modules/@jest/test-result": { - "version": "30.3.0", - "resolved": "https://registry.npmjs.org/@jest/test-result/-/test-result-30.3.0.tgz", - "integrity": "sha512-e/52nJGuD74AKTSe0P4y5wFRlaXP0qmrS17rqOMHeSwm278VyNyXE3gFO/4DTGF9w+65ra3lo3VKj0LBrzmgdQ==", + "version": "30.4.1", + "resolved": "https://registry.npmjs.org/@jest/test-result/-/test-result-30.4.1.tgz", + "integrity": "sha512-/ZG7pgEiOmmWkN9TplKbOu4id2N5lh7FHwRwlkgBVAzGdRH+OkkQ8wX/kIxg4zmd3ZQvAL1RwL2yWsvNYYECTw==", "dev": true, "license": "MIT", "dependencies": { - "@jest/console": "30.3.0", - "@jest/types": "30.3.0", + "@jest/console": "30.4.1", + "@jest/types": "30.4.1", "@types/istanbul-lib-coverage": "^2.0.6", "collect-v8-coverage": "^1.0.2" }, @@ -1720,15 +1721,15 @@ } }, "node_modules/@jest/test-sequencer": { - "version": "30.3.0", - "resolved": "https://registry.npmjs.org/@jest/test-sequencer/-/test-sequencer-30.3.0.tgz", - "integrity": "sha512-dgbWy9b8QDlQeRZcv7LNF+/jFiiYHTKho1xirauZ7kVwY7avjFF6uTT0RqlgudB5OuIPagFdVtfFMosjVbk1eA==", + "version": "30.4.1", + "resolved": "https://registry.npmjs.org/@jest/test-sequencer/-/test-sequencer-30.4.1.tgz", + "integrity": "sha512-PeYE+4td5rKjoRPxztObrXU+H8hsjZfxKMXOcmrr34JerSyB/ROOxbbicz8B7A5j9R9VayDnVPvBmedqCsFCdw==", "dev": true, "license": "MIT", "dependencies": { - "@jest/test-result": "30.3.0", + "@jest/test-result": "30.4.1", "graceful-fs": "^4.2.11", - "jest-haste-map": "30.3.0", + "jest-haste-map": "30.4.1", "slash": "^3.0.0" }, "engines": { @@ -1736,23 +1737,23 @@ } }, "node_modules/@jest/transform": { - "version": "30.3.0", - "resolved": "https://registry.npmjs.org/@jest/transform/-/transform-30.3.0.tgz", - "integrity": "sha512-TLKY33fSLVd/lKB2YI1pH69ijyUblO/BQvCj566YvnwuzoTNr648iE0j22vRvVNk2HsPwByPxATg3MleS3gf5A==", + "version": "30.4.1", + "resolved": "https://registry.npmjs.org/@jest/transform/-/transform-30.4.1.tgz", + "integrity": "sha512-Wz0LyktlTvRefoymh+n64hQ84KNXsRGcwdoZ8CSa0Ea+fgYcHZlnk+hDP7v2MS7il2bQ5uTEIxf4/NNfhMN4KQ==", "dev": true, "license": "MIT", "dependencies": { "@babel/core": "^7.27.4", - "@jest/types": "30.3.0", + "@jest/types": "30.4.1", "@jridgewell/trace-mapping": "^0.3.25", "babel-plugin-istanbul": "^7.0.1", "chalk": "^4.1.2", "convert-source-map": "^2.0.0", "fast-json-stable-stringify": "^2.1.0", "graceful-fs": "^4.2.11", - "jest-haste-map": "30.3.0", - "jest-regex-util": "30.0.1", - "jest-util": "30.3.0", + "jest-haste-map": "30.4.1", + "jest-regex-util": "30.4.0", + "jest-util": "30.4.1", "pirates": "^4.0.7", "slash": "^3.0.0", "write-file-atomic": "^5.0.1" @@ -1762,14 +1763,14 @@ } }, "node_modules/@jest/types": { - "version": "30.3.0", - "resolved": "https://registry.npmjs.org/@jest/types/-/types-30.3.0.tgz", - "integrity": "sha512-JHm87k7bA33hpBngtU8h6UBub/fqqA9uXfw+21j5Hmk7ooPHlboRNxHq0JcMtC+n8VJGP1mcfnD3Mk+XKe1oSw==", + "version": "30.4.1", + "resolved": "https://registry.npmjs.org/@jest/types/-/types-30.4.1.tgz", + "integrity": "sha512-f1x/vJXIfjOlEmejYpbkbgw1gOqpPECwMvMEtBqe47j7H2Hg8h8w3o3ikhSXq3MI15kg+oQ0exWO0uCtTNJLoQ==", "dev": true, "license": "MIT", "dependencies": { - "@jest/pattern": "30.0.1", - "@jest/schemas": "30.0.5", + "@jest/pattern": "30.4.0", + "@jest/schemas": "30.4.1", "@types/istanbul-lib-coverage": "^2.0.6", "@types/istanbul-reports": "^3.0.4", "@types/node": "*", @@ -1909,23 +1910,6 @@ "node": ">= 8" } }, - "node_modules/@nolyfill/is-core-module": { - "version": "1.0.39", - "resolved": "https://registry.npmjs.org/@nolyfill/is-core-module/-/is-core-module-1.0.39.tgz", - "integrity": "sha512-nn5ozdjYQpUCZlWGuxcJY/KpxkWQs4DcbMCmKojjyrYDEAGy4Ce19NN4v5MduafTwJlbKc99UA8YhSVqq9yPZA==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=12.4.0" - } - }, - "node_modules/@package-json/types": { - "version": "0.0.12", - "resolved": "https://registry.npmjs.org/@package-json/types/-/types-0.0.12.tgz", - "integrity": "sha512-uu43FGU34B5VM9mCNjXCwLaGHYjXdNincqKLaraaCW+7S2+SmiBg1Nv8bPnmschrIfZmfKNY9f3fC376MRrObw==", - "dev": true, - "license": "MIT" - }, "node_modules/@pkgjs/parseargs": { "version": "0.11.0", "resolved": "https://registry.npmjs.org/@pkgjs/parseargs/-/parseargs-0.11.0.tgz", @@ -2039,9 +2023,9 @@ } }, "node_modules/@tybys/wasm-util": { - "version": "0.10.1", - "resolved": "https://registry.npmjs.org/@tybys/wasm-util/-/wasm-util-0.10.1.tgz", - "integrity": "sha512-9tTaPJLSiejZKx+Bmog4uSubteqTvFrVrURwkmHixBo0G4seD0zUxp98E1DzUBJxLQ3NPwXrGKDiVjwx/DpPsg==", + "version": "0.10.2", + "resolved": "https://registry.npmjs.org/@tybys/wasm-util/-/wasm-util-0.10.2.tgz", + "integrity": "sha512-RoBvJ2X0wuKlWFIjrwffGw1IqZHKQqzIchKaadZZfnNpsAYp2mM0h36JtPCjNDAHGgYez/15uMBpfGwchhiMgg==", "dev": true, "license": "MIT", "optional": true, @@ -2490,9 +2474,9 @@ } }, "node_modules/@ungap/structured-clone": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/@ungap/structured-clone/-/structured-clone-1.3.0.tgz", - "integrity": "sha512-WmoN8qaIAo7WTYWbAZuG8PYEhn5fkz7dZrqTBZ7dtt//lL2Gwms1IcnQ5yHqjDfX8Ft5j4YzDM23f87zBfDe9g==", + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/@ungap/structured-clone/-/structured-clone-1.3.1.tgz", + "integrity": "sha512-mUFwbeTqrVgDQxFveS+df2yfap6iuP20NAKAsBt5jDEoOTDew+zwLAOilHCeQJOVSvmgCX4ogqIrA0mnyr08yQ==", "dev": true, "license": "ISC" }, @@ -2602,9 +2586,6 @@ "arm64" ], "dev": true, - "libc": [ - "glibc" - ], "license": "MIT", "optional": true, "os": [ @@ -2619,9 +2600,6 @@ "arm64" ], "dev": true, - "libc": [ - "musl" - ], "license": "MIT", "optional": true, "os": [ @@ -2636,9 +2614,6 @@ "ppc64" ], "dev": true, - "libc": [ - "glibc" - ], "license": "MIT", "optional": true, "os": [ @@ -2653,9 +2628,6 @@ "riscv64" ], "dev": true, - "libc": [ - "glibc" - ], "license": "MIT", "optional": true, "os": [ @@ -2670,9 +2642,6 @@ "riscv64" ], "dev": true, - "libc": [ - "musl" - ], "license": "MIT", "optional": true, "os": [ @@ -2687,9 +2656,6 @@ "s390x" ], "dev": true, - "libc": [ - "glibc" - ], "license": "MIT", "optional": true, "os": [ @@ -2704,9 +2670,6 @@ "x64" ], "dev": true, - "libc": [ - "glibc" - ], "license": "MIT", "optional": true, "os": [ @@ -2721,9 +2684,6 @@ "x64" ], "dev": true, - "libc": [ - "musl" - ], "license": "MIT", "optional": true, "os": [ @@ -3220,16 +3180,16 @@ } }, "node_modules/babel-jest": { - "version": "30.3.0", - "resolved": "https://registry.npmjs.org/babel-jest/-/babel-jest-30.3.0.tgz", - "integrity": "sha512-gRpauEU2KRrCox5Z296aeVHR4jQ98BCnu0IO332D/xpHNOsIH/bgSRk9k6GbKIbBw8vFeN6ctuu6tV8WOyVfYQ==", + "version": "30.4.1", + "resolved": "https://registry.npmjs.org/babel-jest/-/babel-jest-30.4.1.tgz", + "integrity": "sha512-fATAbM8piYxkiXQp3RBXmZHxZVNJZAVXXfyeyCN2Tida3+qJ8ea9UxhiJ2y4fLO90ZImKt6k9FlcH2+rLkJGhw==", "dev": true, "license": "MIT", "dependencies": { - "@jest/transform": "30.3.0", + "@jest/transform": "30.4.1", "@types/babel__core": "^7.20.5", "babel-plugin-istanbul": "^7.0.1", - "babel-preset-jest": "30.3.0", + "babel-preset-jest": "30.4.0", "chalk": "^4.1.2", "graceful-fs": "^4.2.11", "slash": "^3.0.0" @@ -3299,9 +3259,9 @@ } }, "node_modules/babel-plugin-jest-hoist": { - "version": "30.3.0", - "resolved": "https://registry.npmjs.org/babel-plugin-jest-hoist/-/babel-plugin-jest-hoist-30.3.0.tgz", - "integrity": "sha512-+TRkByhsws6sfPjVaitzadk1I0F5sPvOVUH5tyTSzhePpsGIVrdeunHSw/C36QeocS95OOk8lunc4rlu5Anwsg==", + "version": "30.4.0", + "resolved": "https://registry.npmjs.org/babel-plugin-jest-hoist/-/babel-plugin-jest-hoist-30.4.0.tgz", + "integrity": "sha512-9EdtWM/sSfXLOGLwSn+GS6pIXyBnL07/8gyJlwFXjWy4DxMOyItqyUT29d4lQiS380EZwYlX7/At4PgBS+m2aA==", "dev": true, "license": "MIT", "dependencies": { @@ -3339,13 +3299,13 @@ } }, "node_modules/babel-preset-jest": { - "version": "30.3.0", - "resolved": "https://registry.npmjs.org/babel-preset-jest/-/babel-preset-jest-30.3.0.tgz", - "integrity": "sha512-6ZcUbWHC+dMz2vfzdNwi87Z1gQsLNK2uLuK1Q89R11xdvejcivlYYwDlEv0FHX3VwEXpbBQ9uufB/MUNpZGfhQ==", + "version": "30.4.0", + "resolved": "https://registry.npmjs.org/babel-preset-jest/-/babel-preset-jest-30.4.0.tgz", + "integrity": "sha512-lBY4jxsNmCnSiu7kquw8ZC9F4+XLMOKypT3RnNHPvU2Kpd4W0xaPuLr5ZkRyOsvLYAY4yaW1ZwTW4xB7NIiZzg==", "dev": true, "license": "MIT", "dependencies": { - "babel-plugin-jest-hoist": "30.3.0", + "babel-plugin-jest-hoist": "30.4.0", "babel-preset-current-node-syntax": "^1.2.0" }, "engines": { @@ -3363,9 +3323,9 @@ "license": "MIT" }, "node_modules/baseline-browser-mapping": { - "version": "2.10.18", - "resolved": "https://registry.npmjs.org/baseline-browser-mapping/-/baseline-browser-mapping-2.10.18.tgz", - "integrity": "sha512-VSnGQAOLtP5mib/DPyg2/t+Tlv65NTBz83BJBJvmLVHHuKJVaDOBvJJykiT5TR++em5nfAySPccDZDa4oSrn8A==", + "version": "2.10.29", + "resolved": "https://registry.npmjs.org/baseline-browser-mapping/-/baseline-browser-mapping-2.10.29.tgz", + "integrity": "sha512-Asa2krT+XTPZINCS+2QcyS8WTkObE77RwkydwF7h6DmnKqbvlalz93m/dnphUyCa6SWSP51VgtEUf2FN+gelFQ==", "dev": true, "license": "Apache-2.0", "bin": { @@ -3382,9 +3342,9 @@ "dev": true }, "node_modules/borp": { - "version": "0.20.2", - "resolved": "https://registry.npmjs.org/borp/-/borp-0.20.2.tgz", - "integrity": "sha512-WJl/vYU6KoD0SyppDWPCdgrrTt2e+hCqTk5443fOUD92cbHLDLIcVRVRh8FIP0uhJjcZQ82Ky0XhB7Kyh32d/w==", + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/borp/-/borp-1.0.0.tgz", + "integrity": "sha512-ooi3bLC8hQRGfthud3BWqS1A98X7NuQqAgGgX6mn5l6Gu6aeHJ7sjpS9gQhHLx+ju026hcc0BEPLRg1obxB1gw==", "dev": true, "license": "MIT", "dependencies": { @@ -3397,6 +3357,156 @@ }, "bin": { "borp": "borp.js" + }, + "engines": { + "node": ">=22.6.0" + } + }, + "node_modules/borp/node_modules/balanced-match": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-4.0.4.tgz", + "integrity": "sha512-BLrgEcRTwX2o6gGxGOCNyMvGSp35YofuYzw9h1IMTRmKqttAZZVU67bdb9Pr2vUHA8+j3i2tJfjO6C6+4myGTA==", + "dev": true, + "license": "MIT", + "engines": { + "node": "18 || 20 || >=22" + } + }, + "node_modules/borp/node_modules/brace-expansion": { + "version": "5.0.5", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-5.0.5.tgz", + "integrity": "sha512-VZznLgtwhn+Mact9tfiwx64fA9erHH/MCXEUfB/0bX/6Fz6ny5EGTXYltMocqg4xFAQZtnO3DHWWXi8RiuN7cQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "balanced-match": "^4.0.2" + }, + "engines": { + "node": "18 || 20 || >=22" + } + }, + "node_modules/borp/node_modules/c8": { + "version": "10.1.3", + "resolved": "https://registry.npmjs.org/c8/-/c8-10.1.3.tgz", + "integrity": "sha512-LvcyrOAaOnrrlMpW22n690PUvxiq4Uf9WMhQwNJ9vgagkL/ph1+D4uvjvDA5XCbykrc0sx+ay6pVi9YZ1GnhyA==", + "dev": true, + "license": "ISC", + "dependencies": { + "@bcoe/v8-coverage": "^1.0.1", + "@istanbuljs/schema": "^0.1.3", + "find-up": "^5.0.0", + "foreground-child": "^3.1.1", + "istanbul-lib-coverage": "^3.2.0", + "istanbul-lib-report": "^3.0.1", + "istanbul-reports": "^3.1.6", + "test-exclude": "^7.0.1", + "v8-to-istanbul": "^9.0.0", + "yargs": "^17.7.2", + "yargs-parser": "^21.1.1" + }, + "bin": { + "c8": "bin/c8.js" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "monocart-coverage-reports": "^2" + }, + "peerDependenciesMeta": { + "monocart-coverage-reports": { + "optional": true + } + } + }, + "node_modules/borp/node_modules/c8/node_modules/find-up": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-5.0.0.tgz", + "integrity": "sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==", + "dev": true, + "license": "MIT", + "dependencies": { + "locate-path": "^6.0.0", + "path-exists": "^4.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/borp/node_modules/locate-path": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-6.0.0.tgz", + "integrity": "sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==", + "dev": true, + "license": "MIT", + "dependencies": { + "p-locate": "^5.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/borp/node_modules/minimatch": { + "version": "10.2.5", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-10.2.5.tgz", + "integrity": "sha512-MULkVLfKGYDFYejP07QOurDLLQpcjk7Fw+7jXS2R2czRQzR56yHRveU5NDJEOviH+hETZKSkIk5c+T23GjFUMg==", + "dev": true, + "license": "BlueOak-1.0.0", + "dependencies": { + "brace-expansion": "^5.0.5" + }, + "engines": { + "node": "18 || 20 || >=22" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/borp/node_modules/p-locate": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-5.0.0.tgz", + "integrity": "sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==", + "dev": true, + "license": "MIT", + "dependencies": { + "p-limit": "^3.0.2" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/borp/node_modules/path-exists": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", + "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/borp/node_modules/test-exclude": { + "version": "7.0.2", + "resolved": "https://registry.npmjs.org/test-exclude/-/test-exclude-7.0.2.tgz", + "integrity": "sha512-u9E6A+ZDYdp7a4WnarkXPZOx8Ilz46+kby6p1yZ8zsGTz9gYa6FIS7lj2oezzNKmtdyyJNNmmXDppga5GB7kSw==", + "dev": true, + "license": "ISC", + "dependencies": { + "@istanbuljs/schema": "^0.1.2", + "glob": "^10.4.1", + "minimatch": "^10.2.2" + }, + "engines": { + "node": ">=18" } }, "node_modules/brace-expansion": { @@ -3475,9 +3585,9 @@ "license": "MIT" }, "node_modules/c8": { - "version": "10.1.3", - "resolved": "https://registry.npmjs.org/c8/-/c8-10.1.3.tgz", - "integrity": "sha512-LvcyrOAaOnrrlMpW22n690PUvxiq4Uf9WMhQwNJ9vgagkL/ph1+D4uvjvDA5XCbykrc0sx+ay6pVi9YZ1GnhyA==", + "version": "11.0.0", + "resolved": "https://registry.npmjs.org/c8/-/c8-11.0.0.tgz", + "integrity": "sha512-e/uRViGHSVIJv7zsaDKM7VRn2390TgHXqUSvYwPHBQaU6L7E9L0n9JbdkwdYPvshDT0KymBmmlwSpms3yBaMNg==", "dev": true, "license": "ISC", "dependencies": { @@ -3488,7 +3598,7 @@ "istanbul-lib-coverage": "^3.2.0", "istanbul-lib-report": "^3.0.1", "istanbul-reports": "^3.1.6", - "test-exclude": "^7.0.1", + "test-exclude": "^8.0.0", "v8-to-istanbul": "^9.0.0", "yargs": "^17.7.2", "yargs-parser": "^21.1.1" @@ -3497,7 +3607,7 @@ "c8": "bin/c8.js" }, "engines": { - "node": ">=18" + "node": "20 || >=22" }, "peerDependencies": { "monocart-coverage-reports": "^2" @@ -3656,9 +3766,9 @@ } }, "node_modules/caniuse-lite": { - "version": "1.0.30001788", - "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001788.tgz", - "integrity": "sha512-6q8HFp+lOQtcf7wBK+uEenxymVWkGKkjFpCvw5W25cmMwEDU45p1xQFBQv8JDlMMry7eNxyBaR+qxgmTUZkIRQ==", + "version": "1.0.30001792", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001792.tgz", + "integrity": "sha512-hVLMUZFgR4JJ6ACt1uEESvQN1/dBVqPAKY0hgrV70eN3391K6juAfTjKZLKvOMsx8PxA7gsY1/tLMMTcfFLLpw==", "dev": true, "funding": [ { @@ -3842,16 +3952,6 @@ "dev": true, "license": "MIT" }, - "node_modules/comment-parser": { - "version": "1.4.6", - "resolved": "https://registry.npmjs.org/comment-parser/-/comment-parser-1.4.6.tgz", - "integrity": "sha512-ObxuY6vnbWTN6Od72xfwN9DbzC7Y2vv8u1Soi9ahRKL37gb6y1qk6/dgjs+3JWuXJHWvsg3BXIwzd/rkmAwavg==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 12.0.0" - } - }, "node_modules/concat-map": { "version": "0.0.1", "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", @@ -4158,9 +4258,9 @@ "license": "MIT" }, "node_modules/electron-to-chromium": { - "version": "1.5.336", - "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.336.tgz", - "integrity": "sha512-AbH9q9J455r/nLmdNZes0G0ZKcRX73FicwowalLs6ijwOmCJSRRrLX63lcAlzy9ux3dWK1w1+1nsBJEWN11hcQ==", + "version": "1.5.354", + "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.354.tgz", + "integrity": "sha512-JaBHwWcfIdmSAfWM5l3uwjGd431j8YEMikZ+K/2nXVuBqJKyZ0f+2h4n4JY5AyNiZmnY9qQr2RU3v9DxDmHMNg==", "dev": true, "license": "ISC" }, @@ -4607,66 +4707,6 @@ "node": ">=8" } }, - "node_modules/eslint-import-context": { - "version": "0.1.9", - "resolved": "https://registry.npmjs.org/eslint-import-context/-/eslint-import-context-0.1.9.tgz", - "integrity": "sha512-K9Hb+yRaGAGUbwjhFNHvSmmkZs9+zbuoe3kFQ4V1wYjrepUFYM2dZAfNtjbbj3qsPfUfsA68Bx/ICWQMi+C8Eg==", - "dev": true, - "license": "MIT", - "dependencies": { - "get-tsconfig": "^4.10.1", - "stable-hash-x": "^0.2.0" - }, - "engines": { - "node": "^12.20.0 || ^14.18.0 || >=16.0.0" - }, - "funding": { - "url": "https://opencollective.com/eslint-import-context" - }, - "peerDependencies": { - "unrs-resolver": "^1.0.0" - }, - "peerDependenciesMeta": { - "unrs-resolver": { - "optional": true - } - } - }, - "node_modules/eslint-import-resolver-typescript": { - "version": "3.10.1", - "resolved": "https://registry.npmjs.org/eslint-import-resolver-typescript/-/eslint-import-resolver-typescript-3.10.1.tgz", - "integrity": "sha512-A1rHYb06zjMGAxdLSkN2fXPBwuSaQ0iO5M/hdyS0Ajj1VBaRp0sPD3dn1FhME3c/JluGFbwSxyCfqdSbtQLAHQ==", - "dev": true, - "license": "ISC", - "dependencies": { - "@nolyfill/is-core-module": "1.0.39", - "debug": "^4.4.0", - "get-tsconfig": "^4.10.0", - "is-bun-module": "^2.0.0", - "stable-hash": "^0.0.5", - "tinyglobby": "^0.2.13", - "unrs-resolver": "^1.6.2" - }, - "engines": { - "node": "^14.18.0 || >=16.0.0" - }, - "funding": { - "url": "https://opencollective.com/eslint-import-resolver-typescript" - }, - "peerDependencies": { - "eslint": "*", - "eslint-plugin-import": "*", - "eslint-plugin-import-x": "*" - }, - "peerDependenciesMeta": { - "eslint-plugin-import": { - "optional": true - }, - "eslint-plugin-import-x": { - "optional": true - } - } - }, "node_modules/eslint-plugin-es-x": { "version": "7.8.0", "resolved": "https://registry.npmjs.org/eslint-plugin-es-x/-/eslint-plugin-es-x-7.8.0.tgz", @@ -4689,96 +4729,6 @@ "eslint": ">=8" } }, - "node_modules/eslint-plugin-import-x": { - "version": "4.16.2", - "resolved": "https://registry.npmjs.org/eslint-plugin-import-x/-/eslint-plugin-import-x-4.16.2.tgz", - "integrity": "sha512-rM9K8UBHcWKpzQzStn1YRN2T5NvdeIfSVoKu/lKF41znQXHAUcBbYXe5wd6GNjZjTrP7viQ49n1D83x/2gYgIw==", - "dev": true, - "license": "MIT", - "dependencies": { - "@package-json/types": "^0.0.12", - "@typescript-eslint/types": "^8.56.0", - "comment-parser": "^1.4.1", - "debug": "^4.4.1", - "eslint-import-context": "^0.1.9", - "is-glob": "^4.0.3", - "minimatch": "^9.0.3 || ^10.1.2", - "semver": "^7.7.2", - "stable-hash-x": "^0.2.0", - "unrs-resolver": "^1.9.2" - }, - "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" - }, - "funding": { - "url": "https://opencollective.com/eslint-plugin-import-x" - }, - "peerDependencies": { - "@typescript-eslint/utils": "^8.56.0", - "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0", - "eslint-import-resolver-node": "*" - }, - "peerDependenciesMeta": { - "@typescript-eslint/utils": { - "optional": true - }, - "eslint-import-resolver-node": { - "optional": true - } - } - }, - "node_modules/eslint-plugin-import-x/node_modules/balanced-match": { - "version": "4.0.4", - "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-4.0.4.tgz", - "integrity": "sha512-BLrgEcRTwX2o6gGxGOCNyMvGSp35YofuYzw9h1IMTRmKqttAZZVU67bdb9Pr2vUHA8+j3i2tJfjO6C6+4myGTA==", - "dev": true, - "license": "MIT", - "engines": { - "node": "18 || 20 || >=22" - } - }, - "node_modules/eslint-plugin-import-x/node_modules/brace-expansion": { - "version": "5.0.5", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-5.0.5.tgz", - "integrity": "sha512-VZznLgtwhn+Mact9tfiwx64fA9erHH/MCXEUfB/0bX/6Fz6ny5EGTXYltMocqg4xFAQZtnO3DHWWXi8RiuN7cQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "balanced-match": "^4.0.2" - }, - "engines": { - "node": "18 || 20 || >=22" - } - }, - "node_modules/eslint-plugin-import-x/node_modules/minimatch": { - "version": "10.2.5", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-10.2.5.tgz", - "integrity": "sha512-MULkVLfKGYDFYejP07QOurDLLQpcjk7Fw+7jXS2R2czRQzR56yHRveU5NDJEOviH+hETZKSkIk5c+T23GjFUMg==", - "dev": true, - "license": "BlueOak-1.0.0", - "dependencies": { - "brace-expansion": "^5.0.5" - }, - "engines": { - "node": "18 || 20 || >=22" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, - "node_modules/eslint-plugin-import-x/node_modules/semver": { - "version": "7.7.4", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.4.tgz", - "integrity": "sha512-vFKC2IEtQnVhpT78h1Yp8wzwrf8CM+MzKMHGJZfBtzhZNycRFnXsHk6E5TxIkkMsgNS7mdX3AGB7x2QM2di4lA==", - "dev": true, - "license": "ISC", - "bin": { - "semver": "bin/semver.js" - }, - "engines": { - "node": ">=10" - } - }, "node_modules/eslint-plugin-n": { "version": "17.24.0", "resolved": "https://registry.npmjs.org/eslint-plugin-n/-/eslint-plugin-n-17.24.0.tgz", @@ -5106,27 +5056,27 @@ } }, "node_modules/expect": { - "version": "30.3.0", - "resolved": "https://registry.npmjs.org/expect/-/expect-30.3.0.tgz", - "integrity": "sha512-1zQrciTiQfRdo7qJM1uG4navm8DayFa2TgCSRlzUyNkhcJ6XUZF3hjnpkyr3VhAqPH7i/9GkG7Tv5abz6fqz0Q==", + "version": "30.4.1", + "resolved": "https://registry.npmjs.org/expect/-/expect-30.4.1.tgz", + "integrity": "sha512-PMARsyh/JtqC20HoGqlFcIlQAyqUtW4PlI1rup1uhYJtKuwAjbvWi3GQMAn+STdHum/dk8xrKfUM1+5SAwpolA==", "dev": true, "license": "MIT", "dependencies": { - "@jest/expect-utils": "30.3.0", + "@jest/expect-utils": "30.4.1", "@jest/get-type": "30.1.0", - "jest-matcher-utils": "30.3.0", - "jest-message-util": "30.3.0", - "jest-mock": "30.3.0", - "jest-util": "30.3.0" + "jest-matcher-utils": "30.4.1", + "jest-message-util": "30.4.1", + "jest-mock": "30.4.1", + "jest-util": "30.4.1" }, "engines": { "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" } }, "node_modules/fast-check": { - "version": "4.6.0", - "resolved": "https://registry.npmjs.org/fast-check/-/fast-check-4.6.0.tgz", - "integrity": "sha512-h7H6Dm0Fy+H4ciQYFxFjXnXkzR2kr9Fb22c0UBpHnm59K2zpr2t13aPTHlltFiNT6zuxp6HMPAVVvgur4BLdpA==", + "version": "4.8.0", + "resolved": "https://registry.npmjs.org/fast-check/-/fast-check-4.8.0.tgz", + "integrity": "sha512-GOJ158CUMnN6cSahsv4+ExARvIDuzzinFjkp0E9WtiBa5zcVeLozVkWaE4IzFcc+Y48Wp1EDlUZsXRyAztQcSg==", "dev": true, "funding": [ { @@ -6037,29 +5987,6 @@ "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/is-bun-module": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/is-bun-module/-/is-bun-module-2.0.0.tgz", - "integrity": "sha512-gNCGbnnnnFAUGKeZ9PdbyeGYJqewpmc2aKHUEMO5nQPWU9lOmv7jcmQIv+qHD8fXW6W7qfuCwX4rY9LNRjXrkQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "semver": "^7.7.1" - } - }, - "node_modules/is-bun-module/node_modules/semver": { - "version": "7.7.4", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.4.tgz", - "integrity": "sha512-vFKC2IEtQnVhpT78h1Yp8wzwrf8CM+MzKMHGJZfBtzhZNycRFnXsHk6E5TxIkkMsgNS7mdX3AGB7x2QM2di4lA==", - "dev": true, - "license": "ISC", - "bin": { - "semver": "bin/semver.js" - }, - "engines": { - "node": ">=10" - } - }, "node_modules/is-callable": { "version": "1.2.7", "resolved": "https://registry.npmjs.org/is-callable/-/is-callable-1.2.7.tgz", @@ -6482,9 +6409,9 @@ } }, "node_modules/istanbul-lib-instrument/node_modules/semver": { - "version": "7.7.4", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.4.tgz", - "integrity": "sha512-vFKC2IEtQnVhpT78h1Yp8wzwrf8CM+MzKMHGJZfBtzhZNycRFnXsHk6E5TxIkkMsgNS7mdX3AGB7x2QM2di4lA==", + "version": "7.8.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.8.0.tgz", + "integrity": "sha512-AcM7dV/5ul4EekoQ29Agm5vri8JNqRyj39o0qpX6vDF2GZrtutZl5RwgD1XnZjiTAfncsJhMI48QQH3sN87YNA==", "dev": true, "license": "ISC", "bin": { @@ -6573,16 +6500,16 @@ } }, "node_modules/jest": { - "version": "30.3.0", - "resolved": "https://registry.npmjs.org/jest/-/jest-30.3.0.tgz", - "integrity": "sha512-AkXIIFcaazymvey2i/+F94XRnM6TsVLZDhBMLsd1Sf/W0wzsvvpjeyUrCZD6HGG4SDYPgDJDBKeiJTBb10WzMg==", + "version": "30.4.2", + "resolved": "https://registry.npmjs.org/jest/-/jest-30.4.2.tgz", + "integrity": "sha512-Yi1jqNC/Oq0N4hBgNH/YvBpP1P57QqundgytzYqy3yqAa7NZPNjSoi4SGbRAXDMdBzNE6xBCi5U7RgfrvMEUVQ==", "dev": true, "license": "MIT", "dependencies": { - "@jest/core": "30.3.0", - "@jest/types": "30.3.0", + "@jest/core": "30.4.2", + "@jest/types": "30.4.1", "import-local": "^3.2.0", - "jest-cli": "30.3.0" + "jest-cli": "30.4.2" }, "bin": { "jest": "bin/jest.js" @@ -6600,14 +6527,14 @@ } }, "node_modules/jest-changed-files": { - "version": "30.3.0", - "resolved": "https://registry.npmjs.org/jest-changed-files/-/jest-changed-files-30.3.0.tgz", - "integrity": "sha512-B/7Cny6cV5At6M25EWDgf9S617lHivamL8vl6KEpJqkStauzcG4e+WPfDgMMF+H4FVH4A2PLRyvgDJan4441QA==", + "version": "30.4.1", + "resolved": "https://registry.npmjs.org/jest-changed-files/-/jest-changed-files-30.4.1.tgz", + "integrity": "sha512-IuctmYrxi21iOSOaIXpJWalHyPAsVv0GeBHKDn8C1CA4W5htHn7INL+wdnL4Bo0+olEndvAFkmb++tIQJG+vvg==", "dev": true, "license": "MIT", "dependencies": { "execa": "^5.1.1", - "jest-util": "30.3.0", + "jest-util": "30.4.1", "p-limit": "^3.1.0" }, "engines": { @@ -6705,29 +6632,29 @@ } }, "node_modules/jest-circus": { - "version": "30.3.0", - "resolved": "https://registry.npmjs.org/jest-circus/-/jest-circus-30.3.0.tgz", - "integrity": "sha512-PyXq5szeSfR/4f1lYqCmmQjh0vqDkURUYi9N6whnHjlRz4IUQfMcXkGLeEoiJtxtyPqgUaUUfyQlApXWBSN1RA==", + "version": "30.4.2", + "resolved": "https://registry.npmjs.org/jest-circus/-/jest-circus-30.4.2.tgz", + "integrity": "sha512-rvHH7VlY6LgbJXJTQ87GW62g1FntOtbhh0zT+v04kC+pgL6aBKyYINXxWukCpj3dcIBMw5/XUbtDS9dU9JTXeQ==", "dev": true, "license": "MIT", "dependencies": { - "@jest/environment": "30.3.0", - "@jest/expect": "30.3.0", - "@jest/test-result": "30.3.0", - "@jest/types": "30.3.0", + "@jest/environment": "30.4.1", + "@jest/expect": "30.4.1", + "@jest/test-result": "30.4.1", + "@jest/types": "30.4.1", "@types/node": "*", "chalk": "^4.1.2", "co": "^4.6.0", "dedent": "^1.6.0", "is-generator-fn": "^2.1.0", - "jest-each": "30.3.0", - "jest-matcher-utils": "30.3.0", - "jest-message-util": "30.3.0", - "jest-runtime": "30.3.0", - "jest-snapshot": "30.3.0", - "jest-util": "30.3.0", + "jest-each": "30.4.1", + "jest-matcher-utils": "30.4.1", + "jest-message-util": "30.4.1", + "jest-runtime": "30.4.2", + "jest-snapshot": "30.4.1", + "jest-util": "30.4.1", "p-limit": "^3.1.0", - "pretty-format": "30.3.0", + "pretty-format": "30.4.1", "pure-rand": "^7.0.0", "slash": "^3.0.0", "stack-utils": "^2.0.6" @@ -6754,21 +6681,21 @@ "license": "MIT" }, "node_modules/jest-cli": { - "version": "30.3.0", - "resolved": "https://registry.npmjs.org/jest-cli/-/jest-cli-30.3.0.tgz", - "integrity": "sha512-l6Tqx+j1fDXJEW5bqYykDQQ7mQg+9mhWXtnj+tQZrTWYHyHoi6Be8HPumDSA+UiX2/2buEgjA58iJzdj146uCw==", + "version": "30.4.2", + "resolved": "https://registry.npmjs.org/jest-cli/-/jest-cli-30.4.2.tgz", + "integrity": "sha512-jfA2ocvVHMXS2QijrJ0d31ektP+d/W0T5RpcTX2Pq+3sVqHlsXVCM2+FmwpL+bdY8OfHpIg9xMxLF17Zg0U49Q==", "dev": true, "license": "MIT", "dependencies": { - "@jest/core": "30.3.0", - "@jest/test-result": "30.3.0", - "@jest/types": "30.3.0", + "@jest/core": "30.4.2", + "@jest/test-result": "30.4.1", + "@jest/types": "30.4.1", "chalk": "^4.1.2", "exit-x": "^0.2.2", "import-local": "^3.2.0", - "jest-config": "30.3.0", - "jest-util": "30.3.0", - "jest-validate": "30.3.0", + "jest-config": "30.4.2", + "jest-util": "30.4.1", + "jest-validate": "30.4.1", "yargs": "^17.7.2" }, "bin": { @@ -6787,33 +6714,33 @@ } }, "node_modules/jest-config": { - "version": "30.3.0", - "resolved": "https://registry.npmjs.org/jest-config/-/jest-config-30.3.0.tgz", - "integrity": "sha512-WPMAkMAtNDY9P/oKObtsRG/6KTrhtgPJoBTmk20uDn4Uy6/3EJnnaZJre/FMT1KVRx8cve1r7/FlMIOfRVWL4w==", + "version": "30.4.2", + "resolved": "https://registry.npmjs.org/jest-config/-/jest-config-30.4.2.tgz", + "integrity": "sha512-rNHAShJQqQwFNoL0hbf3BphSBOWnpOUAKvidLS/AjNVLPfoj5mSf4jQMfW3cYOs6hXeZC7nF7mDHaBnbxELOzg==", "dev": true, "license": "MIT", "dependencies": { "@babel/core": "^7.27.4", "@jest/get-type": "30.1.0", - "@jest/pattern": "30.0.1", - "@jest/test-sequencer": "30.3.0", - "@jest/types": "30.3.0", - "babel-jest": "30.3.0", + "@jest/pattern": "30.4.0", + "@jest/test-sequencer": "30.4.1", + "@jest/types": "30.4.1", + "babel-jest": "30.4.1", "chalk": "^4.1.2", "ci-info": "^4.2.0", "deepmerge": "^4.3.1", "glob": "^10.5.0", "graceful-fs": "^4.2.11", - "jest-circus": "30.3.0", - "jest-docblock": "30.2.0", - "jest-environment-node": "30.3.0", - "jest-regex-util": "30.0.1", - "jest-resolve": "30.3.0", - "jest-runner": "30.3.0", - "jest-util": "30.3.0", - "jest-validate": "30.3.0", + "jest-circus": "30.4.2", + "jest-docblock": "30.4.0", + "jest-environment-node": "30.4.1", + "jest-regex-util": "30.4.0", + "jest-resolve": "30.4.1", + "jest-runner": "30.4.2", + "jest-util": "30.4.1", + "jest-validate": "30.4.1", "parse-json": "^5.2.0", - "pretty-format": "30.3.0", + "pretty-format": "30.4.1", "slash": "^3.0.0", "strip-json-comments": "^3.1.1" }, @@ -6838,25 +6765,25 @@ } }, "node_modules/jest-diff": { - "version": "30.3.0", - "resolved": "https://registry.npmjs.org/jest-diff/-/jest-diff-30.3.0.tgz", - "integrity": "sha512-n3q4PDQjS4LrKxfWB3Z5KNk1XjXtZTBwQp71OP0Jo03Z6V60x++K5L8k6ZrW8MY8pOFylZvHM0zsjS1RqlHJZQ==", + "version": "30.4.1", + "resolved": "https://registry.npmjs.org/jest-diff/-/jest-diff-30.4.1.tgz", + "integrity": "sha512-CRpFK0RtLriVDGcPPAnR6HMVI8bSR2jnUIgralhauzYQZIb4RH9AtEInTuQr65LmmGggGcRT6HIASxwqsVsmlA==", "dev": true, "license": "MIT", "dependencies": { - "@jest/diff-sequences": "30.3.0", + "@jest/diff-sequences": "30.4.0", "@jest/get-type": "30.1.0", "chalk": "^4.1.2", - "pretty-format": "30.3.0" + "pretty-format": "30.4.1" }, "engines": { "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" } }, "node_modules/jest-docblock": { - "version": "30.2.0", - "resolved": "https://registry.npmjs.org/jest-docblock/-/jest-docblock-30.2.0.tgz", - "integrity": "sha512-tR/FFgZKS1CXluOQzZvNH3+0z9jXr3ldGSD8bhyuxvlVUwbeLOGynkunvlTMxchC5urrKndYiwCFC0DLVjpOCA==", + "version": "30.4.0", + "resolved": "https://registry.npmjs.org/jest-docblock/-/jest-docblock-30.4.0.tgz", + "integrity": "sha512-ZPMabUZCx5MpbZ2eBYSvZ0J8fvo3dR9oM+eeUpb3aKNQFuS2tu3Duw1TNlMoP8k3WQgKGJuhcMFvwcVuq6T7oA==", "dev": true, "license": "MIT", "dependencies": { @@ -6867,36 +6794,36 @@ } }, "node_modules/jest-each": { - "version": "30.3.0", - "resolved": "https://registry.npmjs.org/jest-each/-/jest-each-30.3.0.tgz", - "integrity": "sha512-V8eMndg/aZ+3LnCJgSm13IxS5XSBM22QSZc9BtPK8Dek6pm+hfUNfwBdvsB3d342bo1q7wnSkC38zjX259qZNA==", + "version": "30.4.1", + "resolved": "https://registry.npmjs.org/jest-each/-/jest-each-30.4.1.tgz", + "integrity": "sha512-/8MJbH6fuj48TstjrMf+u/pd06Qezz5xOXvZA6442heNOWr8bdeoGZX2d9fCn028CoMgYmroH9//zky5GfyYmA==", "dev": true, "license": "MIT", "dependencies": { "@jest/get-type": "30.1.0", - "@jest/types": "30.3.0", + "@jest/types": "30.4.1", "chalk": "^4.1.2", - "jest-util": "30.3.0", - "pretty-format": "30.3.0" + "jest-util": "30.4.1", + "pretty-format": "30.4.1" }, "engines": { "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" } }, "node_modules/jest-environment-node": { - "version": "30.3.0", - "resolved": "https://registry.npmjs.org/jest-environment-node/-/jest-environment-node-30.3.0.tgz", - "integrity": "sha512-4i6HItw/JSiJVsC5q0hnKIe/hbYfZLVG9YJ/0pU9Hz2n/9qZe3Rhn5s5CUZA5ORZlcdT/vmAXRMyONXJwPrmYQ==", + "version": "30.4.1", + "resolved": "https://registry.npmjs.org/jest-environment-node/-/jest-environment-node-30.4.1.tgz", + "integrity": "sha512-4FZYVOk85hz2AyT6BbarKy9u37g6DbrDyCdFhsnDdXqyrueYQvB+0zO4f/kqLCRD0BsPRXPMNJeQwihKZV8naw==", "dev": true, "license": "MIT", "dependencies": { - "@jest/environment": "30.3.0", - "@jest/fake-timers": "30.3.0", - "@jest/types": "30.3.0", + "@jest/environment": "30.4.1", + "@jest/fake-timers": "30.4.1", + "@jest/types": "30.4.1", "@types/node": "*", - "jest-mock": "30.3.0", - "jest-util": "30.3.0", - "jest-validate": "30.3.0" + "jest-mock": "30.4.1", + "jest-util": "30.4.1", + "jest-validate": "30.4.1" }, "engines": { "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" @@ -6913,20 +6840,20 @@ } }, "node_modules/jest-haste-map": { - "version": "30.3.0", - "resolved": "https://registry.npmjs.org/jest-haste-map/-/jest-haste-map-30.3.0.tgz", - "integrity": "sha512-mMi2oqG4KRU0R9QEtscl87JzMXfUhbKaFqOxmjb2CKcbHcUGFrJCBWHmnTiUqi6JcnzoBlO4rWfpdl2k/RfLCA==", + "version": "30.4.1", + "resolved": "https://registry.npmjs.org/jest-haste-map/-/jest-haste-map-30.4.1.tgz", + "integrity": "sha512-rFrcONd8jeFsyw+Z9CrScJgglRf2+NFmNam8dKu7n+SoHqNYT47mn0DdEcVUZJpvh7Iz6/si7f7yUH7GJHVgnw==", "dev": true, "license": "MIT", "dependencies": { - "@jest/types": "30.3.0", + "@jest/types": "30.4.1", "@types/node": "*", "anymatch": "^3.1.3", "fb-watchman": "^2.0.2", "graceful-fs": "^4.2.11", - "jest-regex-util": "30.0.1", - "jest-util": "30.3.0", - "jest-worker": "30.3.0", + "jest-regex-util": "30.4.0", + "jest-util": "30.4.1", + "jest-worker": "30.4.1", "picomatch": "^4.0.3", "walker": "^1.0.8" }, @@ -6938,49 +6865,50 @@ } }, "node_modules/jest-leak-detector": { - "version": "30.3.0", - "resolved": "https://registry.npmjs.org/jest-leak-detector/-/jest-leak-detector-30.3.0.tgz", - "integrity": "sha512-cuKmUUGIjfXZAiGJ7TbEMx0bcqNdPPI6P1V+7aF+m/FUJqFDxkFR4JqkTu8ZOiU5AaX/x0hZ20KaaIPXQzbMGQ==", + "version": "30.4.1", + "resolved": "https://registry.npmjs.org/jest-leak-detector/-/jest-leak-detector-30.4.1.tgz", + "integrity": "sha512-IpmyiioeHxiWDhesHnUFmOxcTzwCwKpgACgWajtAP+nYQXiY7DakTxB6Bx9JFiRMljr0AX1PvnQdaU1KFoz6NQ==", "dev": true, "license": "MIT", "dependencies": { "@jest/get-type": "30.1.0", - "pretty-format": "30.3.0" + "pretty-format": "30.4.1" }, "engines": { "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" } }, "node_modules/jest-matcher-utils": { - "version": "30.3.0", - "resolved": "https://registry.npmjs.org/jest-matcher-utils/-/jest-matcher-utils-30.3.0.tgz", - "integrity": "sha512-HEtc9uFQgaUHkC7nLSlQL3Tph4Pjxt/yiPvkIrrDCt9jhoLIgxaubo1G+CFOnmHYMxHwwdaSN7mkIFs6ZK8OhA==", + "version": "30.4.1", + "resolved": "https://registry.npmjs.org/jest-matcher-utils/-/jest-matcher-utils-30.4.1.tgz", + "integrity": "sha512-zvYfX5CaeEkFrrLS9suWe9rvJrm9J1Iv3ua8kIBv9GEPzcnsfBf0bob37la7s67fs0nlBC3EuvkOLnXQKxtx4A==", "dev": true, "license": "MIT", "dependencies": { "@jest/get-type": "30.1.0", "chalk": "^4.1.2", - "jest-diff": "30.3.0", - "pretty-format": "30.3.0" + "jest-diff": "30.4.1", + "pretty-format": "30.4.1" }, "engines": { "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" } }, "node_modules/jest-message-util": { - "version": "30.3.0", - "resolved": "https://registry.npmjs.org/jest-message-util/-/jest-message-util-30.3.0.tgz", - "integrity": "sha512-Z/j4Bo+4ySJ+JPJN3b2Qbl9hDq3VrXmnjjGEWD/x0BCXeOXPTV1iZYYzl2X8c1MaCOL+ewMyNBcm88sboE6YWw==", + "version": "30.4.1", + "resolved": "https://registry.npmjs.org/jest-message-util/-/jest-message-util-30.4.1.tgz", + "integrity": "sha512-kwCKIvq0MCW1HzLoGola9Te6JUdzgV0loyKJ3Qghrkz9i5/RRIHsL95BMQc2HBBhlBKC4j22K9p11TGHH8RBpQ==", "dev": true, "license": "MIT", "dependencies": { "@babel/code-frame": "^7.27.1", - "@jest/types": "30.3.0", + "@jest/types": "30.4.1", "@types/stack-utils": "^2.0.3", "chalk": "^4.1.2", "graceful-fs": "^4.2.11", + "jest-util": "30.4.1", "picomatch": "^4.0.3", - "pretty-format": "30.3.0", + "pretty-format": "30.4.1", "slash": "^3.0.0", "stack-utils": "^2.0.6" }, @@ -6989,15 +6917,15 @@ } }, "node_modules/jest-mock": { - "version": "30.3.0", - "resolved": "https://registry.npmjs.org/jest-mock/-/jest-mock-30.3.0.tgz", - "integrity": "sha512-OTzICK8CpE+t4ndhKrwlIdbM6Pn8j00lvmSmq5ejiO+KxukbLjgOflKWMn3KE34EZdQm5RqTuKj+5RIEniYhog==", + "version": "30.4.1", + "resolved": "https://registry.npmjs.org/jest-mock/-/jest-mock-30.4.1.tgz", + "integrity": "sha512-/i8SVb8/NSB7RfNi8gfqu8gxLV23KaL5EpAttyb9iz8qWRIqXRLflycz/32wXsYkOnaUlx8NAKnJYtpsmXUmfw==", "dev": true, "license": "MIT", "dependencies": { - "@jest/types": "30.3.0", + "@jest/types": "30.4.1", "@types/node": "*", - "jest-util": "30.3.0" + "jest-util": "30.4.1" }, "engines": { "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" @@ -7022,9 +6950,9 @@ } }, "node_modules/jest-regex-util": { - "version": "30.0.1", - "resolved": "https://registry.npmjs.org/jest-regex-util/-/jest-regex-util-30.0.1.tgz", - "integrity": "sha512-jHEQgBXAgc+Gh4g0p3bCevgRCVRkB4VB70zhoAE48gxeSr1hfUOsM/C2WoJgVL7Eyg//hudYENbm3Ne+/dRVVA==", + "version": "30.4.0", + "resolved": "https://registry.npmjs.org/jest-regex-util/-/jest-regex-util-30.4.0.tgz", + "integrity": "sha512-mWlvLviKIgIQ8VCuM1xRdD0TWp3zlzionlmDBjuXVBs+VkmXq6FgW9T4Emr7oGz/Rk6feDCGyiugolcQEyp3mg==", "dev": true, "license": "MIT", "engines": { @@ -7032,18 +6960,18 @@ } }, "node_modules/jest-resolve": { - "version": "30.3.0", - "resolved": "https://registry.npmjs.org/jest-resolve/-/jest-resolve-30.3.0.tgz", - "integrity": "sha512-NRtTAHQlpd15F9rUR36jqwelbrDV/dY4vzNte3S2kxCKUJRYNd5/6nTSbYiak1VX5g8IoFF23Uj5TURkUW8O5g==", + "version": "30.4.1", + "resolved": "https://registry.npmjs.org/jest-resolve/-/jest-resolve-30.4.1.tgz", + "integrity": "sha512-Zry8Yq/yJcNAZ7dJ5F2heic8AheXvbFZ7XI5V+h28nrYZ7Qoyy4dItq8OodjnYD270mvX+ZudmrNV9cysqhW5Q==", "dev": true, "license": "MIT", "dependencies": { "chalk": "^4.1.2", "graceful-fs": "^4.2.11", - "jest-haste-map": "30.3.0", + "jest-haste-map": "30.4.1", "jest-pnp-resolver": "^1.2.3", - "jest-util": "30.3.0", - "jest-validate": "30.3.0", + "jest-util": "30.4.1", + "jest-validate": "30.4.1", "slash": "^3.0.0", "unrs-resolver": "^1.7.11" }, @@ -7052,46 +6980,46 @@ } }, "node_modules/jest-resolve-dependencies": { - "version": "30.3.0", - "resolved": "https://registry.npmjs.org/jest-resolve-dependencies/-/jest-resolve-dependencies-30.3.0.tgz", - "integrity": "sha512-9ev8s3YN6Hsyz9LV75XUwkCVFlwPbaFn6Wp75qnI0wzAINYWY8Fb3+6y59Rwd3QaS3kKXffHXsZMziMavfz/nw==", + "version": "30.4.2", + "resolved": "https://registry.npmjs.org/jest-resolve-dependencies/-/jest-resolve-dependencies-30.4.2.tgz", + "integrity": "sha512-gDiVh1I+GxYzz9oXlyw+1wv6VOYX1WYxMOfjsA3iGKePV2oxmbHhwxfkALxNxYy1ciw6APWwkW2zZONwP97aEQ==", "dev": true, "license": "MIT", "dependencies": { - "jest-regex-util": "30.0.1", - "jest-snapshot": "30.3.0" + "jest-regex-util": "30.4.0", + "jest-snapshot": "30.4.1" }, "engines": { "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" } }, "node_modules/jest-runner": { - "version": "30.3.0", - "resolved": "https://registry.npmjs.org/jest-runner/-/jest-runner-30.3.0.tgz", - "integrity": "sha512-gDv6C9LGKWDPLia9TSzZwf4h3kMQCqyTpq+95PODnTRDO0g9os48XIYYkS6D236vjpBir2fF63YmJFtqkS5Duw==", + "version": "30.4.2", + "resolved": "https://registry.npmjs.org/jest-runner/-/jest-runner-30.4.2.tgz", + "integrity": "sha512-2dw0PslVYXxffXGpLo+Ejad+KcI1Qkjn7f4X4619gf21oCUmL+SPfjqIa/losUem3yEOvfNZe/F1HWUcNpODcg==", "dev": true, "license": "MIT", "dependencies": { - "@jest/console": "30.3.0", - "@jest/environment": "30.3.0", - "@jest/test-result": "30.3.0", - "@jest/transform": "30.3.0", - "@jest/types": "30.3.0", + "@jest/console": "30.4.1", + "@jest/environment": "30.4.1", + "@jest/test-result": "30.4.1", + "@jest/transform": "30.4.1", + "@jest/types": "30.4.1", "@types/node": "*", "chalk": "^4.1.2", "emittery": "^0.13.1", "exit-x": "^0.2.2", "graceful-fs": "^4.2.11", - "jest-docblock": "30.2.0", - "jest-environment-node": "30.3.0", - "jest-haste-map": "30.3.0", - "jest-leak-detector": "30.3.0", - "jest-message-util": "30.3.0", - "jest-resolve": "30.3.0", - "jest-runtime": "30.3.0", - "jest-util": "30.3.0", - "jest-watcher": "30.3.0", - "jest-worker": "30.3.0", + "jest-docblock": "30.4.0", + "jest-environment-node": "30.4.1", + "jest-haste-map": "30.4.1", + "jest-leak-detector": "30.4.1", + "jest-message-util": "30.4.1", + "jest-resolve": "30.4.1", + "jest-runtime": "30.4.2", + "jest-util": "30.4.1", + "jest-watcher": "30.4.1", + "jest-worker": "30.4.1", "p-limit": "^3.1.0", "source-map-support": "0.5.13" }, @@ -7100,32 +7028,32 @@ } }, "node_modules/jest-runtime": { - "version": "30.3.0", - "resolved": "https://registry.npmjs.org/jest-runtime/-/jest-runtime-30.3.0.tgz", - "integrity": "sha512-CgC+hIBJbuh78HEffkhNKcbXAytQViplcl8xupqeIWyKQF50kCQA8J7GeJCkjisC6hpnC9Muf8jV5RdtdFbGng==", + "version": "30.4.2", + "resolved": "https://registry.npmjs.org/jest-runtime/-/jest-runtime-30.4.2.tgz", + "integrity": "sha512-3/5e8iPz2k/VLqlr8DgTftYyLUv8Su3FkCAO2/Od81UsUTpSxOrS6O5x5KkoQwyUjmpYyDJKeyAvg2T2nvpNkQ==", "dev": true, "license": "MIT", "dependencies": { - "@jest/environment": "30.3.0", - "@jest/fake-timers": "30.3.0", - "@jest/globals": "30.3.0", + "@jest/environment": "30.4.1", + "@jest/fake-timers": "30.4.1", + "@jest/globals": "30.4.1", "@jest/source-map": "30.0.1", - "@jest/test-result": "30.3.0", - "@jest/transform": "30.3.0", - "@jest/types": "30.3.0", + "@jest/test-result": "30.4.1", + "@jest/transform": "30.4.1", + "@jest/types": "30.4.1", "@types/node": "*", "chalk": "^4.1.2", "cjs-module-lexer": "^2.1.0", "collect-v8-coverage": "^1.0.2", "glob": "^10.5.0", "graceful-fs": "^4.2.11", - "jest-haste-map": "30.3.0", - "jest-message-util": "30.3.0", - "jest-mock": "30.3.0", - "jest-regex-util": "30.0.1", - "jest-resolve": "30.3.0", - "jest-snapshot": "30.3.0", - "jest-util": "30.3.0", + "jest-haste-map": "30.4.1", + "jest-message-util": "30.4.1", + "jest-mock": "30.4.1", + "jest-regex-util": "30.4.0", + "jest-resolve": "30.4.1", + "jest-snapshot": "30.4.1", + "jest-util": "30.4.1", "slash": "^3.0.0", "strip-bom": "^4.0.0" }, @@ -7134,9 +7062,9 @@ } }, "node_modules/jest-snapshot": { - "version": "30.3.0", - "resolved": "https://registry.npmjs.org/jest-snapshot/-/jest-snapshot-30.3.0.tgz", - "integrity": "sha512-f14c7atpb4O2DeNhwcvS810Y63wEn8O1HqK/luJ4F6M4NjvxmAKQwBUWjbExUtMxWJQ0wVgmCKymeJK6NZMnfQ==", + "version": "30.4.1", + "resolved": "https://registry.npmjs.org/jest-snapshot/-/jest-snapshot-30.4.1.tgz", + "integrity": "sha512-tEOkkfOMppUyeiHwjZswOQ3lcnoTnws/q5FnGIaeIh/jmoU0ZlgMYRR8sTlTj+nNGCoJ0RDq6SfxGxCsyMTPmw==", "dev": true, "license": "MIT", "dependencies": { @@ -7145,20 +7073,20 @@ "@babel/plugin-syntax-jsx": "^7.27.1", "@babel/plugin-syntax-typescript": "^7.27.1", "@babel/types": "^7.27.3", - "@jest/expect-utils": "30.3.0", + "@jest/expect-utils": "30.4.1", "@jest/get-type": "30.1.0", - "@jest/snapshot-utils": "30.3.0", - "@jest/transform": "30.3.0", - "@jest/types": "30.3.0", + "@jest/snapshot-utils": "30.4.1", + "@jest/transform": "30.4.1", + "@jest/types": "30.4.1", "babel-preset-current-node-syntax": "^1.2.0", "chalk": "^4.1.2", - "expect": "30.3.0", + "expect": "30.4.1", "graceful-fs": "^4.2.11", - "jest-diff": "30.3.0", - "jest-matcher-utils": "30.3.0", - "jest-message-util": "30.3.0", - "jest-util": "30.3.0", - "pretty-format": "30.3.0", + "jest-diff": "30.4.1", + "jest-matcher-utils": "30.4.1", + "jest-message-util": "30.4.1", + "jest-util": "30.4.1", + "pretty-format": "30.4.1", "semver": "^7.7.2", "synckit": "^0.11.8" }, @@ -7167,9 +7095,9 @@ } }, "node_modules/jest-snapshot/node_modules/semver": { - "version": "7.7.4", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.4.tgz", - "integrity": "sha512-vFKC2IEtQnVhpT78h1Yp8wzwrf8CM+MzKMHGJZfBtzhZNycRFnXsHk6E5TxIkkMsgNS7mdX3AGB7x2QM2di4lA==", + "version": "7.8.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.8.0.tgz", + "integrity": "sha512-AcM7dV/5ul4EekoQ29Agm5vri8JNqRyj39o0qpX6vDF2GZrtutZl5RwgD1XnZjiTAfncsJhMI48QQH3sN87YNA==", "dev": true, "license": "ISC", "bin": { @@ -7180,13 +7108,13 @@ } }, "node_modules/jest-util": { - "version": "30.3.0", - "resolved": "https://registry.npmjs.org/jest-util/-/jest-util-30.3.0.tgz", - "integrity": "sha512-/jZDa00a3Sz7rdyu55NLrQCIrbyIkbBxareejQI315f/i8HjYN+ZWsDLLpoQSiUIEIyZF/R8fDg3BmB8AtHttg==", + "version": "30.4.1", + "resolved": "https://registry.npmjs.org/jest-util/-/jest-util-30.4.1.tgz", + "integrity": "sha512-vjQb1sACEiv13DKJMDToJpzVW0joCsIQrmbg0fi7CyOOt+g9jTuQl2A216pWRBYhOVt53XbL/2LbMKg1BECWOw==", "dev": true, "license": "MIT", "dependencies": { - "@jest/types": "30.3.0", + "@jest/types": "30.4.1", "@types/node": "*", "chalk": "^4.1.2", "ci-info": "^4.2.0", @@ -7198,18 +7126,18 @@ } }, "node_modules/jest-validate": { - "version": "30.3.0", - "resolved": "https://registry.npmjs.org/jest-validate/-/jest-validate-30.3.0.tgz", - "integrity": "sha512-I/xzC8h5G+SHCb2P2gWkJYrNiTbeL47KvKeW5EzplkyxzBRBw1ssSHlI/jXec0ukH2q7x2zAWQm7015iusg62Q==", + "version": "30.4.1", + "resolved": "https://registry.npmjs.org/jest-validate/-/jest-validate-30.4.1.tgz", + "integrity": "sha512-PDWi4SOwLnwqNDfHZjOcsEFyZ4fc/2W2gVL3DEoyqnB6jCQMLRtfBong8s6omIw3lI0HWOus12xfnFmQtjW3fw==", "dev": true, "license": "MIT", "dependencies": { "@jest/get-type": "30.1.0", - "@jest/types": "30.3.0", + "@jest/types": "30.4.1", "camelcase": "^6.3.0", "chalk": "^4.1.2", "leven": "^3.1.0", - "pretty-format": "30.3.0" + "pretty-format": "30.4.1" }, "engines": { "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" @@ -7229,19 +7157,19 @@ } }, "node_modules/jest-watcher": { - "version": "30.3.0", - "resolved": "https://registry.npmjs.org/jest-watcher/-/jest-watcher-30.3.0.tgz", - "integrity": "sha512-PJ1d9ThtTR8aMiBWUdcownq9mDdLXsQzJayTk4kmaBRHKvwNQn+ANveuhEBUyNI2hR1TVhvQ8D5kHubbzBHR/w==", + "version": "30.4.1", + "resolved": "https://registry.npmjs.org/jest-watcher/-/jest-watcher-30.4.1.tgz", + "integrity": "sha512-/l9UonmvCwjHH7d2h3iAwIloLc1H0S8mJZ/LNK3i86hqwPAz8otUJjP9MfYtz9Tt77Su5FD2xGjZn8d31IZHlw==", "dev": true, "license": "MIT", "dependencies": { - "@jest/test-result": "30.3.0", - "@jest/types": "30.3.0", + "@jest/test-result": "30.4.1", + "@jest/types": "30.4.1", "@types/node": "*", "ansi-escapes": "^4.3.2", "chalk": "^4.1.2", "emittery": "^0.13.1", - "jest-util": "30.3.0", + "jest-util": "30.4.1", "string-length": "^4.0.2" }, "engines": { @@ -7249,15 +7177,15 @@ } }, "node_modules/jest-worker": { - "version": "30.3.0", - "resolved": "https://registry.npmjs.org/jest-worker/-/jest-worker-30.3.0.tgz", - "integrity": "sha512-DrCKkaQwHexjRUFTmPzs7sHQe0TSj9nvDALKGdwmK5mW9v7j90BudWirKAJHt3QQ9Dhrg1F7DogPzhChppkJpQ==", + "version": "30.4.1", + "resolved": "https://registry.npmjs.org/jest-worker/-/jest-worker-30.4.1.tgz", + "integrity": "sha512-SHynN/q/QD++iNyvMdy+WMmbCGk8jIsNcRxycXbWubSOhvo6T+j2afcfUSl+3hYsiBebOTo0cT7c2H7CXugu1g==", "dev": true, "license": "MIT", "dependencies": { "@types/node": "*", "@ungap/structured-clone": "^1.3.0", - "jest-util": "30.3.0", + "jest-util": "30.4.1", "merge-stream": "^2.0.0", "supports-color": "^8.1.1" }, @@ -7779,55 +7707,53 @@ "license": "MIT" }, "node_modules/neostandard": { - "version": "0.12.2", - "resolved": "https://registry.npmjs.org/neostandard/-/neostandard-0.12.2.tgz", - "integrity": "sha512-VZU8EZpSaNadp3rKEwBhVD1Kw8jE3AftQLkCyOaM7bWemL1LwsYRsBnAmXy2LjG9zO8t66qJdqB7ccwwORyrAg==", + "version": "0.13.0", + "resolved": "https://registry.npmjs.org/neostandard/-/neostandard-0.13.0.tgz", + "integrity": "sha512-R3iglFr+Dla/8qFBqsMxBvcYBOgP6rAGw7uRHKMpM3bUP0wLDRzUstxtEI9RfEwn7xszE/UUnh8H090Ru4Z52A==", "dev": true, "license": "MIT", "dependencies": { "@humanwhocodes/gitignore-to-minimatch": "^1.0.2", "@stylistic/eslint-plugin": "2.11.0", - "eslint-import-resolver-typescript": "^3.10.1", - "eslint-plugin-import-x": "^4.16.1", - "eslint-plugin-n": "^17.20.0", + "eslint-plugin-n": "^17.23.2", "eslint-plugin-promise": "^7.2.1", "eslint-plugin-react": "^7.37.5", - "find-up": "^5.0.0", - "globals": "^15.15.0", - "peowly": "^1.3.2", - "typescript-eslint": "^8.35.1" + "find-up": "^8.0.0", + "globals": "^17.3.0", + "peowly": "^1.3.3", + "typescript-eslint": "^8.56.0" }, "bin": { "neostandard": "cli.mjs" }, "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + "node": "^20.19.0 || ^22.13.0 || >=24" }, "peerDependencies": { "eslint": "^9.0.0" } }, "node_modules/neostandard/node_modules/find-up": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/find-up/-/find-up-5.0.0.tgz", - "integrity": "sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==", + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-8.0.0.tgz", + "integrity": "sha512-JGG8pvDi2C+JxidYdIwQDyS/CgcrIdh18cvgxcBge3wSHRQOrooMD3GlFBcmMJAN9M42SAZjDp5zv1dglJjwww==", "dev": true, "license": "MIT", "dependencies": { - "locate-path": "^6.0.0", - "path-exists": "^4.0.0" + "locate-path": "^8.0.0", + "unicorn-magic": "^0.3.0" }, "engines": { - "node": ">=10" + "node": ">=20" }, "funding": { "url": "https://github.com/sponsors/sindresorhus" } }, "node_modules/neostandard/node_modules/globals": { - "version": "15.15.0", - "resolved": "https://registry.npmjs.org/globals/-/globals-15.15.0.tgz", - "integrity": "sha512-7ACyT3wmyp3I61S4fG682L0VA2RGD9otkqGJIwNUMF1SWUombIIk+af1unuDYgMm082aHYwD+mzJvv9Iu8dsgg==", + "version": "17.6.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-17.6.0.tgz", + "integrity": "sha512-sepffkT8stwnIYbsMBpoCHJuJM5l98FUF2AnE07hfvE0m/qp3R586hw4jF4uadbhvg1ooIdzuu7CsfD2jzCaNA==", "dev": true, "license": "MIT", "engines": { @@ -7838,47 +7764,34 @@ } }, "node_modules/neostandard/node_modules/locate-path": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-6.0.0.tgz", - "integrity": "sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==", + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-8.0.0.tgz", + "integrity": "sha512-XT9ewWAC43tiAV7xDAPflMkG0qOPn2QjHqlgX8FOqmWa/rxnyYDulF9T0F7tRy1u+TVTmK/M//6VIOye+2zDXg==", "dev": true, "license": "MIT", "dependencies": { - "p-locate": "^5.0.0" + "p-locate": "^6.0.0" }, "engines": { - "node": ">=10" + "node": ">=20" }, "funding": { "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/neostandard/node_modules/p-locate": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-5.0.0.tgz", - "integrity": "sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==", + "node_modules/neostandard/node_modules/unicorn-magic": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/unicorn-magic/-/unicorn-magic-0.3.0.tgz", + "integrity": "sha512-+QBBXBCvifc56fsbuxZQ6Sic3wqqc3WWaqxs58gvJrcOuN83HGTCwz3oS5phzU9LthRNE9VrJCFCLUgHeeFnfA==", "dev": true, "license": "MIT", - "dependencies": { - "p-limit": "^3.0.2" - }, "engines": { - "node": ">=10" + "node": ">=18" }, "funding": { "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/neostandard/node_modules/path-exists": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", - "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=8" - } - }, "node_modules/node-exports-info": { "version": "1.6.0", "resolved": "https://registry.npmjs.org/node-exports-info/-/node-exports-info-1.6.0.tgz", @@ -7916,9 +7829,9 @@ "license": "MIT" }, "node_modules/node-releases": { - "version": "2.0.37", - "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.37.tgz", - "integrity": "sha512-1h5gKZCF+pO/o3Iqt5Jp7wc9rH3eJJ0+nh/CIoiRwjRxde/hAHyLPXYN4V3CqKAbiZPSeJFSWHmJsbkicta0Eg==", + "version": "2.0.44", + "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.44.tgz", + "integrity": "sha512-5WUyunoPMsvvEhS8AxHtRzP+oA8UCkJ7YRxatWKjngndhDGLiqEVAQKWjFAiAiuL8zMRGzGSJxFnLetoa43qGQ==", "dev": true, "license": "MIT" }, @@ -8525,15 +8438,16 @@ } }, "node_modules/pretty-format": { - "version": "30.3.0", - "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-30.3.0.tgz", - "integrity": "sha512-oG4T3wCbfeuvljnyAzhBvpN45E8iOTXCU/TD3zXW80HA3dQ4ahdqMkWGiPWZvjpQwlbyHrPTWUAqUzGzv4l1JQ==", + "version": "30.4.1", + "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-30.4.1.tgz", + "integrity": "sha512-K6KiKMHTL4jjX4u3Kir2EW07nRfcqVTXIImx50wbjHQTcZPgg+gjVeNTIT3l3L1Rd4UefxfogquC9J37SoFyyw==", "dev": true, "license": "MIT", "dependencies": { - "@jest/schemas": "30.0.5", + "@jest/schemas": "30.4.1", "ansi-styles": "^5.2.0", - "react-is": "^18.3.1" + "react-is-18": "npm:react-is@^18.3.1", + "react-is-19": "npm:react-is@^19.2.5" }, "engines": { "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" @@ -8670,6 +8584,22 @@ "dev": true, "license": "MIT" }, + "node_modules/react-is-18": { + "name": "react-is", + "version": "18.3.1", + "resolved": "https://registry.npmjs.org/react-is/-/react-is-18.3.1.tgz", + "integrity": "sha512-/LLMVyas0ljjAtoYiPqYiL8VWXzUUdThrmU5+n20DZv+a+ClRoevUzw5JxU+Ieh5/c87ytoTBV9G1FiKfNJdmg==", + "dev": true, + "license": "MIT" + }, + "node_modules/react-is-19": { + "name": "react-is", + "version": "19.2.6", + "resolved": "https://registry.npmjs.org/react-is/-/react-is-19.2.6.tgz", + "integrity": "sha512-XjBR15BhXuylgWGuslhDKqlSayuqvqBX91BP8pauG8kd1zY8kotkNWbXksTCNRarse4kuGbe2kIY05ARtwNIvw==", + "dev": true, + "license": "MIT" + }, "node_modules/read-pkg": { "version": "5.2.0", "resolved": "https://registry.npmjs.org/read-pkg/-/read-pkg-5.2.0.tgz", @@ -9325,23 +9255,6 @@ "dev": true, "license": "BSD-3-Clause" }, - "node_modules/stable-hash": { - "version": "0.0.5", - "resolved": "https://registry.npmjs.org/stable-hash/-/stable-hash-0.0.5.tgz", - "integrity": "sha512-+L3ccpzibovGXFK+Ap/f8LOS0ahMrHTf3xu7mMLSpEGU0EO9ucaysSylKo9eRDFNhWve/y275iPmIZ4z39a9iA==", - "dev": true, - "license": "MIT" - }, - "node_modules/stable-hash-x": { - "version": "0.2.0", - "resolved": "https://registry.npmjs.org/stable-hash-x/-/stable-hash-x-0.2.0.tgz", - "integrity": "sha512-o3yWv49B/o4QZk5ZcsALc6t0+eCelPc44zZsLtCQnZPDwFpDYSWcDnrv2TtMmMbQ7uKo3J0HTURCqckw23czNQ==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=12.0.0" - } - }, "node_modules/stack-utils": { "version": "2.0.6", "resolved": "https://registry.npmjs.org/stack-utils/-/stack-utils-2.0.6.tgz", @@ -9738,18 +9651,18 @@ } }, "node_modules/test-exclude": { - "version": "7.0.2", - "resolved": "https://registry.npmjs.org/test-exclude/-/test-exclude-7.0.2.tgz", - "integrity": "sha512-u9E6A+ZDYdp7a4WnarkXPZOx8Ilz46+kby6p1yZ8zsGTz9gYa6FIS7lj2oezzNKmtdyyJNNmmXDppga5GB7kSw==", + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/test-exclude/-/test-exclude-8.0.0.tgz", + "integrity": "sha512-ZOffsNrXYggvU1mDGHk54I96r26P8SyMjO5slMKSc7+IWmtB/MQKnEC2fP51imB3/pT6YK5cT5E8f+Dd9KdyOQ==", "dev": true, "license": "ISC", "dependencies": { "@istanbuljs/schema": "^0.1.2", - "glob": "^10.4.1", + "glob": "^13.0.6", "minimatch": "^10.2.2" }, "engines": { - "node": ">=18" + "node": "20 || >=22" } }, "node_modules/test-exclude/node_modules/balanced-match": { @@ -9775,6 +9688,34 @@ "node": "18 || 20 || >=22" } }, + "node_modules/test-exclude/node_modules/glob": { + "version": "13.0.6", + "resolved": "https://registry.npmjs.org/glob/-/glob-13.0.6.tgz", + "integrity": "sha512-Wjlyrolmm8uDpm/ogGyXZXb1Z+Ca2B8NbJwqBVg0axK9GbBeoS7yGV6vjXnYdGm6X53iehEuxxbyiKp8QmN4Vw==", + "dev": true, + "license": "BlueOak-1.0.0", + "dependencies": { + "minimatch": "^10.2.2", + "minipass": "^7.1.3", + "path-scurry": "^2.0.2" + }, + "engines": { + "node": "18 || 20 || >=22" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/test-exclude/node_modules/lru-cache": { + "version": "11.3.5", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-11.3.5.tgz", + "integrity": "sha512-NxVFwLAnrd9i7KUBxC4DrUhmgjzOs+1Qm50D3oF1/oL+r1NpZ4gA7xvG0/zJ8evR7zIKn4vLf7qTNduWFtCrRw==", + "dev": true, + "license": "BlueOak-1.0.0", + "engines": { + "node": "20 || >=22" + } + }, "node_modules/test-exclude/node_modules/minimatch": { "version": "10.2.5", "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-10.2.5.tgz", @@ -9791,6 +9732,23 @@ "url": "https://github.com/sponsors/isaacs" } }, + "node_modules/test-exclude/node_modules/path-scurry": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/path-scurry/-/path-scurry-2.0.2.tgz", + "integrity": "sha512-3O/iVVsJAPsOnpwWIeD+d6z/7PmqApyQePUtCndjatj/9I5LylHvt5qluFaBT3I5h3r1ejfR056c+FCv+NnNXg==", + "dev": true, + "license": "BlueOak-1.0.0", + "dependencies": { + "lru-cache": "^11.0.0", + "minipass": "^7.1.2" + }, + "engines": { + "node": "18 || 20 || >=22" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, "node_modules/tinyglobby": { "version": "0.2.16", "resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.16.tgz", @@ -10103,9 +10061,9 @@ } }, "node_modules/typescript": { - "version": "6.0.2", - "resolved": "https://registry.npmjs.org/typescript/-/typescript-6.0.2.tgz", - "integrity": "sha512-bGdAIrZ0wiGDo5l8c++HWtbaNCWTS4UTv7RaTH/ThVIgjkveJt83m74bBHMJkuCbslY8ixgLBVZJIOiQlQTjfQ==", + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-6.0.3.tgz", + "integrity": "sha512-y2TvuxSZPDyQakkFRPZHKFm+KKVqIisdg9/CZwm9ftvKXLP8NRWj38/ODjNbr43SsoXqNuAisEf1GdCxqWcdBw==", "dev": true, "license": "Apache-2.0", "bin": { diff --git a/deps/undici/src/package.json b/deps/undici/src/package.json index 082c56a4783ae1..b9d007ade7cf0c 100644 --- a/deps/undici/src/package.json +++ b/deps/undici/src/package.json @@ -1,6 +1,6 @@ { "name": "undici", - "version": "8.2.0", + "version": "8.3.0", "description": "An HTTP/1.1 client, written from scratch for Node.js", "homepage": "https://undici.nodejs.org", "bugs": { @@ -115,8 +115,8 @@ "@sinonjs/fake-timers": "^12.0.0", "@types/node": "^22.0.0", "abort-controller": "^3.0.0", - "borp": "^0.20.0", - "c8": "^10.0.0", + "borp": "^1.0.0", + "c8": "^11.0.0", "cross-env": "^10.0.0", "dns-packet": "^5.4.0", "esbuild": "^0.28.0", @@ -125,7 +125,7 @@ "husky": "^9.0.7", "jest": "^30.0.5", "jsondiffpatch": "^0.7.3", - "neostandard": "^0.12.0", + "neostandard": "^0.13.0", "node-forge": "^1.3.1", "proxy": "^4.0.0", "tsd": "^0.33.0", diff --git a/deps/undici/src/types/client.d.ts b/deps/undici/src/types/client.d.ts index 13b7bf5899454d..530a94583898a9 100644 --- a/deps/undici/src/types/client.d.ts +++ b/deps/undici/src/types/client.d.ts @@ -3,7 +3,7 @@ import Dispatcher from './dispatcher' import buildConnector from './connector' import TClientStats from './client-stats' -type ClientConnectOptions = Omit +type ClientConnectOptions = Omit, 'origin'> /** * A basic HTTP/1.1 client, mapped on top a single TCP/TLS connection. Pipelining is disabled by default. @@ -20,12 +20,12 @@ export class Client extends Dispatcher { readonly stats: TClientStats // Override dispatcher APIs. - override connect ( - options: ClientConnectOptions - ): Promise - override connect ( - options: ClientConnectOptions, - callback: (err: Error | null, data: Dispatcher.ConnectData) => void + override connect ( + options: ClientConnectOptions + ): Promise> + override connect ( + options: ClientConnectOptions, + callback: (err: Error | null, data: Dispatcher.ConnectData) => void ): void } diff --git a/deps/undici/src/types/dispatcher.d.ts b/deps/undici/src/types/dispatcher.d.ts index 40a75de6743506..f732ec45cc97f7 100644 --- a/deps/undici/src/types/dispatcher.d.ts +++ b/deps/undici/src/types/dispatcher.d.ts @@ -121,8 +121,6 @@ declare namespace Dispatcher { bodyTimeout?: number | null; /** Whether the request should stablish a keep-alive or not. Default `false` */ reset?: boolean; - /** Whether Undici should throw an error upon receiving a 4xx or 5xx response from the server. Defaults to false */ - throwOnError?: boolean; /** For H2, it appends the expect: 100-continue header, and halts the request body until a 100-continue is received from the remote server */ expectContinue?: boolean; } diff --git a/deps/undici/src/types/formdata.d.ts b/deps/undici/src/types/formdata.d.ts index 5244e04559fe5a..b9819a7e725d49 100644 --- a/deps/undici/src/types/formdata.d.ts +++ b/deps/undici/src/types/formdata.d.ts @@ -4,12 +4,6 @@ import { File } from 'node:buffer' import { SpecIterableIterator } from './fetch' -declare module 'node:buffer' { - interface File { - readonly [Symbol.toStringTag]: string - } -} - /** * A `string` or `File` that represents a single value from a set of `FormData` key-value pairs. */ diff --git a/deps/undici/src/types/snapshot-agent.d.ts b/deps/undici/src/types/snapshot-agent.d.ts index f1d1ccdbb4d2f2..9696b604f5a7c8 100644 --- a/deps/undici/src/types/snapshot-agent.d.ts +++ b/deps/undici/src/types/snapshot-agent.d.ts @@ -30,7 +30,9 @@ declare namespace SnapshotRecorder { ignoreHeaders?: string[] excludeHeaders?: string[] matchBody?: boolean + normalizeBody?: (body: string | Buffer | null | undefined) => string matchQuery?: boolean + normalizeQuery?: (query: URLSearchParams) => string caseSensitive?: boolean shouldRecord?: (requestOpts: any) => boolean shouldPlayback?: (requestOpts: any) => boolean @@ -98,7 +100,9 @@ declare namespace SnapshotAgent { ignoreHeaders?: string[] excludeHeaders?: string[] matchBody?: boolean + normalizeBody?: (body: string | Buffer | null | undefined) => string matchQuery?: boolean + normalizeQuery?: (query: URLSearchParams) => string caseSensitive?: boolean shouldRecord?: (requestOpts: any) => boolean shouldPlayback?: (requestOpts: any) => boolean diff --git a/deps/undici/undici.js b/deps/undici/undici.js index 86f020015b22c2..20e38982a91d47 100644 --- a/deps/undici/undici.js +++ b/deps/undici/undici.js @@ -556,6 +556,7 @@ var require_symbols = __commonJS({ kListeners: /* @__PURE__ */ Symbol("listeners"), kHTTPContext: /* @__PURE__ */ Symbol("http context"), kMaxConcurrentStreams: /* @__PURE__ */ Symbol("max concurrent streams"), + kHostAuthority: /* @__PURE__ */ Symbol("host authority"), kHTTP2InitialWindowSize: /* @__PURE__ */ Symbol("http2 initial window size"), kHTTP2ConnectionWindowSize: /* @__PURE__ */ Symbol("http2 connection window size"), kEnableConnectProtocol: /* @__PURE__ */ Symbol("http2session connect protocol"), @@ -901,6 +902,7 @@ var require_pool_base = __commonJS({ var kOnDisconnect = /* @__PURE__ */ Symbol("onDisconnect"); var kOnConnectionError = /* @__PURE__ */ Symbol("onConnectionError"); var kGetDispatcher = /* @__PURE__ */ Symbol("get dispatcher"); + var kHasDispatcher = /* @__PURE__ */ Symbol("has dispatcher"); var kAddClient = /* @__PURE__ */ Symbol("add client"); var kRemoveClient = /* @__PURE__ */ Symbol("remove client"); var PoolBase = class extends DispatcherBase { @@ -1026,10 +1028,19 @@ var require_pool_base = __commonJS({ this[kQueued]++; } else if (!dispatcher.dispatch(opts, handler)) { dispatcher[kNeedDrain] = true; - this[kNeedDrain] = !this[kGetDispatcher](); + this[kNeedDrain] = !this[kHasDispatcher](); } return !this[kNeedDrain]; } + [kHasDispatcher]() { + for (let i = 0; i < this[kClients].length; i++) { + const dispatcher = this[kClients][i]; + if (!dispatcher[kNeedDrain] && dispatcher.closed !== true && dispatcher.destroyed !== true) { + return true; + } + } + return false; + } [kAddClient](client) { client.on("drain", this[kOnDrain].bind(this, client)).on("connect", this[kOnConnect]).on("disconnect", this[kOnDisconnect]).on("connectionError", this[kOnConnectionError]); this[kClients].push(client); @@ -1049,7 +1060,7 @@ var require_pool_base = __commonJS({ } client.close(() => { }); - this[kNeedDrain] = this[kClients].some((dispatcher) => !dispatcher[kNeedDrain] && dispatcher.closed !== true && dispatcher.destroyed !== true); + this[kNeedDrain] = !this[kClients].some((dispatcher) => !dispatcher[kNeedDrain] && dispatcher.closed !== true && dispatcher.destroyed !== true); } }; module2.exports = { @@ -1058,7 +1069,8 @@ var require_pool_base = __commonJS({ kNeedDrain, kAddClient, kRemoveClient, - kGetDispatcher + kGetDispatcher, + kHasDispatcher }; } }); @@ -2283,14 +2295,14 @@ var require_util = __commonJS({ return !headerCharRegex.test(characters); } __name(isValidHeaderValue, "isValidHeaderValue"); - var rangeHeaderRegex = /^bytes (\d+)-(\d+)\/(\d+)?$/; + var rangeHeaderRegex = /^bytes (\d+)-(\d+)\/(\d+|\*)?$/; function parseRangeHeader(range) { if (range == null || range === "") return { start: 0, end: null, size: null }; const m = range ? range.match(rangeHeaderRegex) : null; return m ? { start: parseInt(m[1]), end: m[2] ? parseInt(m[2]) : null, - size: m[3] ? parseInt(m[3]) : null + size: m[3] && m[3] !== "*" ? parseInt(m[3]) : null } : null; } __name(parseRangeHeader, "parseRangeHeader"); @@ -6040,6 +6052,86 @@ var require_util2 = __commonJS({ } }); +// lib/util/runtime-features.js +var require_runtime_features = __commonJS({ + "lib/util/runtime-features.js"(exports2, module2) { + "use strict"; + var lazyLoaders = { + __proto__: null, + "node:crypto": /* @__PURE__ */ __name(() => require("node:crypto"), "node:crypto"), + "node:sqlite": /* @__PURE__ */ __name(() => require("node:sqlite"), "node:sqlite") + }; + function detectRuntimeFeatureByNodeModule(moduleName) { + try { + lazyLoaders[moduleName](); + return true; + } catch (err) { + if (err.code !== "ERR_UNKNOWN_BUILTIN_MODULE" && err.code !== "ERR_NO_CRYPTO") { + throw err; + } + return false; + } + } + __name(detectRuntimeFeatureByNodeModule, "detectRuntimeFeatureByNodeModule"); + var runtimeFeaturesAsNodeModule = ( + /** @type {const} */ + ["crypto", "sqlite"] + ); + function detectRuntimeFeature(feature) { + if (runtimeFeaturesAsNodeModule.includes( + /** @type {RuntimeFeatureByNodeModule} */ + feature + )) { + return detectRuntimeFeatureByNodeModule(`node:${feature}`); + } + throw new TypeError(`unknown feature: ${feature}`); + } + __name(detectRuntimeFeature, "detectRuntimeFeature"); + var RuntimeFeatures = class { + static { + __name(this, "RuntimeFeatures"); + } + /** @type {Map} */ + #map = /* @__PURE__ */ new Map(); + /** + * Clears all cached feature detections. + */ + clear() { + this.#map.clear(); + } + /** + * @param {Feature} feature + * @returns {boolean} + */ + has(feature) { + return this.#map.get(feature) ?? this.#detectRuntimeFeature(feature); + } + /** + * @param {Feature} feature + * @param {boolean} value + */ + set(feature, value) { + if (runtimeFeaturesAsNodeModule.includes(feature) === false) { + throw new TypeError(`unknown feature: ${feature}`); + } + this.#map.set(feature, value); + } + /** + * @param {Feature} feature + * @returns {boolean} + */ + #detectRuntimeFeature(feature) { + const result = detectRuntimeFeature(feature); + this.#map.set(feature, result); + return result; + } + }; + var instance = new RuntimeFeatures(); + module2.exports.runtimeFeatures = instance; + module2.exports.default = instance; + } +}); + // lib/web/fetch/formdata.js var require_formdata = __commonJS({ "lib/web/fetch/formdata.js"(exports2, module2) { @@ -6048,11 +6140,14 @@ var require_formdata = __commonJS({ var { kEnumerableProperty } = require_util(); var { webidl } = require_webidl(); var nodeUtil = require("node:util"); + var { runtimeFeatures } = require_runtime_features(); + var random = runtimeFeatures.has("crypto") ? require("node:crypto").randomInt : (max) => Math.floor(Math.random() * max); var FormData = class _FormData { static { __name(this, "FormData"); } #state = []; + #boundary = null; constructor(form = void 0) { webidl.util.markAsUncloneable(this); if (form !== void 0) { @@ -6167,10 +6262,20 @@ var require_formdata = __commonJS({ static setFormDataState(formData, newState) { formData.#state = newState; } + /** + * @param {FormData} formData + * @returns {string | null} + */ + static getFormDataBoundary(formData) { + const boundary = formData.#boundary; + if (boundary != null) return boundary; + return formData.#boundary = `----formdata-undici-0${`${random(1e11)}`.padStart(11, "0")}`; + } }; - var { getFormDataState, setFormDataState } = FormData; + var { getFormDataState, setFormDataState, getFormDataBoundary } = FormData; Reflect.deleteProperty(FormData, "getFormDataState"); Reflect.deleteProperty(FormData, "setFormDataState"); + Reflect.deleteProperty(FormData, "getFormDataBoundary"); iteratorMixin("FormData", FormData, getFormDataState, "name", "value"); Object.defineProperties(FormData.prototype, { append: kEnumerableProperty, @@ -6202,7 +6307,7 @@ var require_formdata = __commonJS({ } __name(makeEntry, "makeEntry"); webidl.is.FormData = webidl.util.MakeTypeAssertion(FormData); - module2.exports = { FormData, makeEntry, setFormDataState }; + module2.exports = { FormData, makeEntry, setFormDataState, getFormDataBoundary }; } }); @@ -6548,86 +6653,6 @@ var require_formdata_parser = __commonJS({ } }); -// lib/util/runtime-features.js -var require_runtime_features = __commonJS({ - "lib/util/runtime-features.js"(exports2, module2) { - "use strict"; - var lazyLoaders = { - __proto__: null, - "node:crypto": /* @__PURE__ */ __name(() => require("node:crypto"), "node:crypto"), - "node:sqlite": /* @__PURE__ */ __name(() => require("node:sqlite"), "node:sqlite") - }; - function detectRuntimeFeatureByNodeModule(moduleName) { - try { - lazyLoaders[moduleName](); - return true; - } catch (err) { - if (err.code !== "ERR_UNKNOWN_BUILTIN_MODULE" && err.code !== "ERR_NO_CRYPTO") { - throw err; - } - return false; - } - } - __name(detectRuntimeFeatureByNodeModule, "detectRuntimeFeatureByNodeModule"); - var runtimeFeaturesAsNodeModule = ( - /** @type {const} */ - ["crypto", "sqlite"] - ); - function detectRuntimeFeature(feature) { - if (runtimeFeaturesAsNodeModule.includes( - /** @type {RuntimeFeatureByNodeModule} */ - feature - )) { - return detectRuntimeFeatureByNodeModule(`node:${feature}`); - } - throw new TypeError(`unknown feature: ${feature}`); - } - __name(detectRuntimeFeature, "detectRuntimeFeature"); - var RuntimeFeatures = class { - static { - __name(this, "RuntimeFeatures"); - } - /** @type {Map} */ - #map = /* @__PURE__ */ new Map(); - /** - * Clears all cached feature detections. - */ - clear() { - this.#map.clear(); - } - /** - * @param {Feature} feature - * @returns {boolean} - */ - has(feature) { - return this.#map.get(feature) ?? this.#detectRuntimeFeature(feature); - } - /** - * @param {Feature} feature - * @param {boolean} value - */ - set(feature, value) { - if (runtimeFeaturesAsNodeModule.includes(feature) === false) { - throw new TypeError(`unknown feature: ${feature}`); - } - this.#map.set(feature, value); - } - /** - * @param {Feature} feature - * @returns {boolean} - */ - #detectRuntimeFeature(feature) { - const result = detectRuntimeFeature(feature); - this.#map.set(feature, result); - return result; - } - }; - var instance = new RuntimeFeatures(); - module2.exports.runtimeFeatures = instance; - module2.exports.default = instance; - } -}); - // lib/web/fetch/body.js var require_body = __commonJS({ "lib/web/fetch/body.js"(exports2, module2) { @@ -6639,7 +6664,7 @@ var require_body = __commonJS({ fullyReadBody, extractMimeType } = require_util2(); - var { FormData, setFormDataState } = require_formdata(); + var { FormData, setFormDataState, getFormDataBoundary } = require_formdata(); var { webidl } = require_webidl(); var assert = require("node:assert"); var { isErrored, isDisturbed } = require("node:stream"); @@ -6648,8 +6673,6 @@ var require_body = __commonJS({ var { multipartFormDataParser } = require_formdata_parser(); var { parseJSONFromBytes } = require_infra(); var { utf8DecodeBytes } = require_encoding(); - var { runtimeFeatures } = require_runtime_features(); - var random = runtimeFeatures.has("crypto") ? require("node:crypto").randomInt : (max) => Math.floor(Math.random() * max); var textEncoder = new TextEncoder(); function noop() { } @@ -6693,7 +6716,7 @@ var require_body = __commonJS({ } else if (webidl.is.BufferSource(object)) { source = webidl.util.getCopyOfBytesHeldByBufferSource(object); } else if (webidl.is.FormData(object)) { - const boundary = `----formdata-undici-0${`${random(1e11)}`.padStart(11, "0")}`; + const boundary = getFormDataBoundary(object); const prefix = `--${boundary}\r Content-Disposition: form-data`; const formdataEscape = /* @__PURE__ */ __name((str) => str.replace(/\n/g, "%0A").replace(/\r/g, "%0D").replace(/"/g, "%22"), "formdataEscape"); @@ -7211,19 +7234,47 @@ var require_client_h1 = __commonJS({ this.paused = true; socket.unshift(data); } else { - const ptr = llhttp.llhttp_get_error_reason(this.ptr); - let message = ""; - if (ptr) { - const len = new Uint8Array(llhttp.memory.buffer, ptr).indexOf(0); - message = "Response does not match the HTTP/1.1 protocol (" + Buffer.from(llhttp.memory.buffer, ptr, len).toString() + ")"; - } - throw new HTTPParserError(message, constants.ERROR[ret], data); + throw this.createError(ret, data); } } } catch (err) { util.destroy(socket, err); } } + finish() { + assert(currentParser === null); + assert(this.ptr != null); + assert(!this.paused); + const { llhttp } = this; + let ret; + try { + currentParser = this; + ret = llhttp.llhttp_finish(this.ptr); + } finally { + currentParser = null; + } + if (ret === constants.ERROR.OK) { + return null; + } + if (ret === constants.ERROR.PAUSED || ret === constants.ERROR.PAUSED_UPGRADE) { + this.paused = true; + return null; + } + return this.createError(ret, EMPTY_BUF); + } + createError(ret, data) { + const { llhttp, contentLength, bytesRead } = this; + if (contentLength !== -1 && bytesRead !== contentLength) { + return new ResponseContentLengthMismatchError(); + } + const ptr = llhttp.llhttp_get_error_reason(this.ptr); + let message = ""; + if (ptr) { + const len = new Uint8Array(llhttp.memory.buffer, ptr).indexOf(0); + message = "Response does not match the HTTP/1.1 protocol (" + Buffer.from(llhttp.memory.buffer, ptr, len).toString() + ")"; + } + return new HTTPParserError(message, constants.ERROR[ret], data); + } destroy() { assert(currentParser === null); assert(this.ptr != null); @@ -7607,7 +7658,11 @@ var require_client_h1 = __commonJS({ assert(err.code !== "ERR_TLS_CERT_ALTNAME_INVALID"); const parser = this[kParser]; if (err.code === "ECONNRESET" && parser.statusCode && !parser.shouldKeepAlive) { - parser.onMessageComplete(); + const parserErr = parser.finish(); + if (parserErr) { + this[kError] = parserErr; + this[kClient][kOnError](parserErr); + } return; } this[kError] = err; @@ -7621,7 +7676,10 @@ var require_client_h1 = __commonJS({ function onHttpSocketEnd() { const parser = this[kParser]; if (parser.statusCode && !parser.shouldKeepAlive) { - parser.onMessageComplete(); + const parserErr = parser.finish(); + if (parserErr) { + util.destroy(this, parserErr); + } return; } util.destroy(this, new SocketError("other side closed", util.getSocketInfo(this))); @@ -7631,7 +7689,7 @@ var require_client_h1 = __commonJS({ const parser = this[kParser]; if (parser) { if (!this[kError] && parser.statusCode && !parser.shouldKeepAlive) { - parser.onMessageComplete(); + this[kError] = parser.finish() || this[kError]; } this[kParser].destroy(); this[kParser] = null; @@ -7920,7 +7978,6 @@ upgrade: ${upgrade}\r } __name(writeBuffer, "writeBuffer"); async function writeBlob(abort, body, client, request, socket, contentLength, header, expectsPayload) { - assert(contentLength === body.size, "blob body must have content length"); try { if (contentLength != null && contentLength !== body.size) { throw new RequestContentLengthMismatchError(); @@ -8149,6 +8206,7 @@ var require_client_h2 = __commonJS({ kHTTP2Session, kHTTP2InitialWindowSize, kHTTP2ConnectionWindowSize, + kHostAuthority, kResume, kSize, kHTTPContext, @@ -8164,8 +8222,7 @@ var require_client_h2 = __commonJS({ var kRequestStreamId = /* @__PURE__ */ Symbol("request stream id"); var kRequestStream = /* @__PURE__ */ Symbol("request stream"); var kRequestStreamCleanup = /* @__PURE__ */ Symbol("request stream cleanup"); - var kRequestStreamOnData = /* @__PURE__ */ Symbol("request stream on data"); - var kRequestStreamOnCloseError = /* @__PURE__ */ Symbol("request stream on close error"); + var kRequestStreamState = /* @__PURE__ */ Symbol("request stream state"); var kReceivedGoAway = /* @__PURE__ */ Symbol("received goaway"); var extractBody; var http2; @@ -8214,8 +8271,9 @@ var require_client_h2 = __commonJS({ __name(detachRequestFromStream, "detachRequestFromStream"); function bindRequestToStream(request, stream, cleanup) { const previousCleanup = request[kRequestStreamCleanup]; + const previousStream = request[kRequestStream]; detachRequestFromStream(request); - previousCleanup?.(); + previousCleanup?.(previousStream); request[kRequestStreamId] = stream.id; request[kRequestStream] = stream; request[kRequestStreamCleanup] = cleanup; @@ -8223,8 +8281,9 @@ var require_client_h2 = __commonJS({ __name(bindRequestToStream, "bindRequestToStream"); function clearRequestStream(request) { const cleanup = request[kRequestStreamCleanup]; + const stream = request[kRequestStream]; detachRequestFromStream(request); - cleanup?.(); + cleanup?.(stream); } __name(clearRequestStream, "clearRequestStream"); function canRetryRequestAfterGoAway(request) { @@ -8530,62 +8589,148 @@ var require_client_h2 = __commonJS({ __name(closeStreamSession, "closeStreamSession"); function onUpgradeStreamClose() { this.off("error", noop); - const failUpgradeStream = this[kRequestStreamOnCloseError]; - this[kRequestStreamOnCloseError] = null; - failUpgradeStream(new InformationalError("HTTP/2: stream closed before response headers")); + const state = this[kRequestStreamState]; + this[kRequestStreamState] = null; + failUpgradeStream(state, new InformationalError("HTTP/2: stream closed before response headers")); closeStreamSession(this); } __name(onUpgradeStreamClose, "onUpgradeStreamClose"); function onRequestStreamClose() { - const onData = this[kRequestStreamOnData]; - this[kRequestStreamOnData] = null; this.off("data", onData); this.off("error", noop); closeStreamSession(this); + this[kRequestStreamState] = null; } __name(onRequestStreamClose, "onRequestStreamClose"); function shouldSendContentLength(method) { return method !== "GET" && method !== "HEAD" && method !== "OPTIONS" && method !== "TRACE" && method !== "CONNECT"; } __name(shouldSendContentLength, "shouldSendContentLength"); - function writeH2(client, request) { - const requestTimeout = request.bodyTimeout ?? client[kBodyTimeout]; - const session = client[kHTTP2Session]; - const { method, path, host, upgrade, expectContinue, signal, protocol, headers: reqHeaders } = request; - let { body } = request; - if (upgrade != null && upgrade !== "websocket") { - util.errorRequest(client, request, new InvalidArgumentError(`Custom upgrade "${upgrade}" not supported over HTTP/2`)); - return false; - } + function buildRequestHeaders(reqHeaders) { const headers = {}; for (let n = 0; n < reqHeaders.length; n += 2) { const key = reqHeaders[n + 0]; const val = reqHeaders[n + 1]; + const current = headers[key]; if (key === "cookie") { - if (headers[key] != null) { - headers[key] = Array.isArray(headers[key]) ? (headers[key].push(val), headers[key]) : [headers[key], val]; + if (current != null) { + headers[key] = Array.isArray(current) ? (current.push(val), current) : [current, val]; } else { headers[key] = val; } continue; } - if (Array.isArray(val)) { - for (let i = 0; i < val.length; i++) { - if (headers[key]) { - headers[key] += `, ${val[i]}`; - } else { - headers[key] = val[i]; - } - } - } else if (headers[key]) { - headers[key] += `, ${val}`; - } else { - headers[key] = val; + if (typeof val === "string") { + headers[key] = current ? `${current}, ${val}` : val; + continue; + } + for (let i = 0; i < val.length; i++) { + headers[key] = headers[key] ? `${headers[key]}, ${val[i]}` : val[i]; } } + return headers; + } + __name(buildRequestHeaders, "buildRequestHeaders"); + function removeUpgradeStreamListeners(stream) { + stream.off("response", onUpgradeResponse); + stream.off("error", onUpgradeStreamError); + stream.off("end", onUpgradeStreamEnd); + stream.off("timeout", onUpgradeStreamTimeout); + stream.off("error", noop); + } + __name(removeUpgradeStreamListeners, "removeUpgradeStreamListeners"); + function releaseUpgradeStream(stream) { + if (stream == null) { + return; + } + const state = stream[kRequestStreamState]; + if (state == null) { + return; + } + const { request } = state; + if (request[kRequestStream] === stream) { + detachRequestFromStream(request); + } + removeUpgradeStreamListeners(stream); + if (!stream.destroyed && !stream.closed) { + stream.once("error", noop); + } + } + __name(releaseUpgradeStream, "releaseUpgradeStream"); + function failUpgradeStream(state, err) { + if (state == null) { + return; + } + const { request } = state; + if (state.responseReceived || request.aborted || request.completed) { + return; + } + releaseUpgradeStream(state.stream); + state.abort(err, true); + } + __name(failUpgradeStream, "failUpgradeStream"); + function onUpgradeStreamError() { + const state = this[kRequestStreamState]; + if (typeof this.rstCode === "number" && this.rstCode !== 0) { + failUpgradeStream(state, new InformationalError(`HTTP/2: "stream error" received - code ${this.rstCode}`)); + } else { + failUpgradeStream(state, new InformationalError("HTTP/2: stream errored before response headers")); + } + } + __name(onUpgradeStreamError, "onUpgradeStreamError"); + function onUpgradeStreamEnd() { + failUpgradeStream(this[kRequestStreamState], new InformationalError("HTTP/2: stream half-closed (remote)")); + } + __name(onUpgradeStreamEnd, "onUpgradeStreamEnd"); + function onUpgradeStreamTimeout() { + const state = this[kRequestStreamState]; + failUpgradeStream(state, new InformationalError(`HTTP/2: "stream timeout after ${state.requestTimeout}"`)); + } + __name(onUpgradeStreamTimeout, "onUpgradeStreamTimeout"); + function onUpgradeResponse(headers, _flags) { + const stream = this; + const state = stream[kRequestStreamState]; + const { request } = state; + state.responseReceived = true; + const statusCode = headers[HTTP2_HEADER_STATUS]; + delete headers[HTTP2_HEADER_STATUS]; + request.onRequestUpgrade(statusCode, headers, stream); + if (request.aborted || request.completed) { + return; + } + removeUpgradeStreamListeners(stream); + detachRequestFromStream(request); + state.finalizeRequest(); + } + __name(onUpgradeResponse, "onUpgradeResponse"); + function setupUpgradeStream(stream, state) { + const { request, requestTimeout, session } = state; + stream[kHTTP2Stream] = true; + stream[kHTTP2Session] = session; + stream[kRequestStreamState] = state; + state.stream = stream; + bindRequestToStream(request, stream, releaseUpgradeStream); + stream.once("response", onUpgradeResponse); + stream.on("error", onUpgradeStreamError); + stream.once("end", onUpgradeStreamEnd); + stream.on("timeout", onUpgradeStreamTimeout); + stream.once("close", onUpgradeStreamClose); + ++session[kOpenStreams]; + stream.setTimeout(requestTimeout); + } + __name(setupUpgradeStream, "setupUpgradeStream"); + function writeH2(client, request) { + const requestTimeout = request.bodyTimeout ?? client[kBodyTimeout]; + const session = client[kHTTP2Session]; + const { method, path, host, upgrade, expectContinue, signal, protocol, headers: reqHeaders } = request; + let { body } = request; + if (upgrade != null && upgrade !== "websocket") { + util.errorRequest(client, request, new InvalidArgumentError(`Custom upgrade "${upgrade}" not supported over HTTP/2`)); + return false; + } + const headers = buildRequestHeaders(reqHeaders); let stream = null; - const { hostname, port } = client[kUrl]; - headers[HTTP2_HEADER_AUTHORITY] = host || `${hostname}${port ? `:${port}` : ""}`; + headers[HTTP2_HEADER_AUTHORITY] = host || client[kHostAuthority]; headers[HTTP2_HEADER_METHOD] = method; let requestFinalized = false; const finalizeRequest = /* @__PURE__ */ __name((resetPendingIdx = false) => { @@ -8639,67 +8784,15 @@ var require_client_h2 = __commonJS({ } if (upgrade || method === "CONNECT") { session.ref(); - const setupUpgradeStream = /* @__PURE__ */ __name((stream2) => { - let responseReceived2 = false; - const removeUpgradeStreamListeners = /* @__PURE__ */ __name(() => { - stream2.off("response", onUpgradeResponse); - stream2.off("error", onUpgradeStreamError); - stream2.off("end", onUpgradeStreamEnd); - stream2.off("timeout", onUpgradeStreamTimeout); - stream2.off("error", noop); - }, "removeUpgradeStreamListeners"); - const releaseUpgradeStream = /* @__PURE__ */ __name(() => { - if (request[kRequestStream] === stream2) { - detachRequestFromStream(request); - } - removeUpgradeStreamListeners(); - if (!stream2.destroyed && !stream2.closed) { - stream2.once("error", noop); - } - }, "releaseUpgradeStream"); - const failUpgradeStream = /* @__PURE__ */ __name((err) => { - if (responseReceived2 || request.aborted || request.completed) { - return; - } - releaseUpgradeStream(); - abort(err, true); - }, "failUpgradeStream"); - const onUpgradeStreamError = /* @__PURE__ */ __name(() => { - if (typeof stream2.rstCode === "number" && stream2.rstCode !== 0) { - failUpgradeStream(new InformationalError(`HTTP/2: "stream error" received - code ${stream2.rstCode}`)); - } else { - failUpgradeStream(new InformationalError("HTTP/2: stream errored before response headers")); - } - }, "onUpgradeStreamError"); - const onUpgradeStreamEnd = /* @__PURE__ */ __name(() => { - failUpgradeStream(new InformationalError("HTTP/2: stream half-closed (remote)")); - }, "onUpgradeStreamEnd"); - const onUpgradeStreamTimeout = /* @__PURE__ */ __name(() => { - failUpgradeStream(new InformationalError(`HTTP/2: "stream timeout after ${requestTimeout}"`)); - }, "onUpgradeStreamTimeout"); - const onUpgradeResponse = /* @__PURE__ */ __name((headers2, _flags) => { - responseReceived2 = true; - const statusCode = headers2[HTTP2_HEADER_STATUS]; - delete headers2[HTTP2_HEADER_STATUS]; - request.onRequestUpgrade(statusCode, headers2, stream2); - if (request.aborted || request.completed) { - return; - } - removeUpgradeStreamListeners(); - detachRequestFromStream(request); - finalizeRequest(); - }, "onUpgradeResponse"); - bindRequestToStream(request, stream2, releaseUpgradeStream); - stream2.once("response", onUpgradeResponse); - stream2.on("error", onUpgradeStreamError); - stream2.once("end", onUpgradeStreamEnd); - stream2.on("timeout", onUpgradeStreamTimeout); - stream2[kHTTP2Session] = session; - stream2[kRequestStreamOnCloseError] = failUpgradeStream; - stream2.once("close", onUpgradeStreamClose); - ++session[kOpenStreams]; - stream2.setTimeout(requestTimeout); - }, "setupUpgradeStream"); + const upgradeState = { + abort, + finalizeRequest, + request, + requestTimeout, + responseReceived: false, + session, + stream: null + }; if (upgrade === "websocket") { if (session[kEnableConnectProtocol] === false) { util.errorRequest(client, request, new InformationalError("HTTP/2: Extended CONNECT protocol not supported by server")); @@ -8719,8 +8812,7 @@ var require_client_h2 = __commonJS({ session.unref(); return false; } - stream[kHTTP2Stream] = true; - setupUpgradeStream(stream); + setupUpgradeStream(stream, upgradeState); return true; } stream = requestStream(headers, { endStream: false, signal }); @@ -8728,13 +8820,12 @@ var require_client_h2 = __commonJS({ session.unref(); return false; } - stream[kHTTP2Stream] = true; - setupUpgradeStream(stream); + setupUpgradeStream(stream, upgradeState); return true; } headers[HTTP2_HEADER_PATH] = path; headers[HTTP2_HEADER_SCHEME] = protocol === "http:" ? "http" : "https"; - const expectsPayload = method === "PUT" || method === "POST" || method === "PATCH"; + const expectsPayload = method === "PUT" || method === "POST" || method === "PATCH" || method === "QUERY" || method === "PROPFIND" || method === "PROPPATCH"; if (body && typeof body.read === "function") { body.read(0); } @@ -8772,115 +8863,35 @@ var require_client_h2 = __commonJS({ } channels.sendHeaders.publish({ request, headers: header, socket: session[kSocket] }); } - const shouldEndStream = body === null; + const shouldEndStream = body === null || contentLength === 0; + const state = { + abort, + body, + client, + contentLength, + expectsPayload, + finalizeRequest, + request, + requestTimeout, + responseReceived: false, + session, + stream: null + }; if (expectContinue) { headers[HTTP2_HEADER_EXPECT] = "100-continue"; - stream = requestStream(headers, { endStream: shouldEndStream, signal }); - if (stream == null) { - return false; - } - stream[kHTTP2Stream] = true; - bindRequestToStream(request, stream, null); - } else { - stream = requestStream(headers, { - endStream: shouldEndStream, - signal - }); - if (stream == null) { - return false; - } - stream[kHTTP2Stream] = true; - bindRequestToStream(request, stream, null); } + stream = requestStream(headers, { endStream: shouldEndStream, signal }); + if (stream == null) { + return false; + } + stream[kHTTP2Stream] = true; + stream[kRequestStreamState] = state; + state.stream = stream; + bindRequestToStream(request, stream, null); ++session[kOpenStreams]; stream.setTimeout(requestTimeout); - let responseReceived = false; - const onData = /* @__PURE__ */ __name((chunk) => { - if (request.aborted || request.completed) { - return; - } - if (request.onResponseData(chunk) === false) { - stream.pause(); - } - }, "onData"); - const removeRequestStreamListeners = /* @__PURE__ */ __name(() => { - stream.off("error", noop); - stream.off("continue", writeBodyH2); - stream.off("response", onResponse); - stream.off("end", onEnd); - stream.off("error", onError); - stream.off("frameError", onFrameError); - stream.off("aborted", onAborted); - stream.off("timeout", onTimeout); - stream.off("trailers", onTrailers); - stream.off("data", onData); - }, "removeRequestStreamListeners"); - const releaseRequestStream = /* @__PURE__ */ __name(() => { - if (request[kRequestStream] === stream) { - detachRequestFromStream(request); - } - removeRequestStreamListeners(); - if (!stream.destroyed && !stream.closed) { - stream.once("error", noop); - } - }, "releaseRequestStream"); - const onResponse = /* @__PURE__ */ __name((headers2) => { - stream.off("response", onResponse); - const statusCode = headers2[HTTP2_HEADER_STATUS]; - delete headers2[HTTP2_HEADER_STATUS]; - request.onResponseStarted(); - responseReceived = true; - if (request.aborted) { - releaseRequestStream(); - return; - } - if (request.onResponseStart(Number(statusCode), headers2, stream.resume.bind(stream), "") === false) { - stream.pause(); - } - stream.on("data", onData); - }, "onResponse"); - const onEnd = /* @__PURE__ */ __name(() => { - stream.off("end", onEnd); - releaseRequestStream(); - if (responseReceived) { - if (!request.aborted && !request.completed) { - request.onResponseEnd({}); - } - finalizeRequest(); - } else { - abort(new InformationalError("HTTP/2: stream half-closed (remote)"), true); - } - }, "onEnd"); stream[kHTTP2Session] = session; - stream[kRequestStreamOnData] = onData; stream.once("close", onRequestStreamClose); - const onError = /* @__PURE__ */ __name(function(err) { - stream.off("error", onError); - releaseRequestStream(); - abort(err); - }, "onError"); - const onFrameError = /* @__PURE__ */ __name((type, code) => { - stream.off("frameError", onFrameError); - releaseRequestStream(); - abort(new InformationalError(`HTTP/2: "frameError" received - type ${type}, code ${code}`)); - }, "onFrameError"); - const onAborted = /* @__PURE__ */ __name(() => { - stream.off("data", onData); - }, "onAborted"); - const onTimeout = /* @__PURE__ */ __name(() => { - releaseRequestStream(); - const err = new InformationalError(`HTTP/2: "stream timeout after ${requestTimeout}"`); - abort(err); - }, "onTimeout"); - const onTrailers = /* @__PURE__ */ __name((trailers) => { - stream.off("trailers", onTrailers); - if (request.aborted || request.completed) { - return; - } - releaseRequestStream(); - request.onResponseEnd(trailers); - finalizeRequest(); - }, "onTrailers"); bindRequestToStream(request, stream, releaseRequestStream); if (expectContinue) { stream.once("continue", writeBodyH2); @@ -8893,69 +8904,169 @@ var require_client_h2 = __commonJS({ stream.on("timeout", onTimeout); stream.once("trailers", onTrailers); if (!expectContinue) { - writeBodyH2(); + writeBodyH2.call(stream); } return true; - function writeBodyH2() { - if (!body || contentLength === 0) { - writeBuffer( - abort, - stream, - null, - client, - request, - client[kSocket], - contentLength, - expectsPayload - ); - } else if (util.isBuffer(body)) { - writeBuffer( + } + __name(writeH2, "writeH2"); + function removeRequestStreamListeners(stream) { + stream.off("error", noop); + stream.off("continue", writeBodyH2); + stream.off("response", onResponse); + stream.off("end", onEnd); + stream.off("error", onError); + stream.off("frameError", onFrameError); + stream.off("aborted", onAborted); + stream.off("timeout", onTimeout); + stream.off("trailers", onTrailers); + stream.off("data", onData); + } + __name(removeRequestStreamListeners, "removeRequestStreamListeners"); + function releaseRequestStream(stream) { + if (stream == null) { + return; + } + const state = stream[kRequestStreamState]; + if (state == null) { + return; + } + const { request } = state; + if (request[kRequestStream] === stream) { + detachRequestFromStream(request); + } + removeRequestStreamListeners(stream); + if (!stream.destroyed && !stream.closed) { + stream.once("error", noop); + } + } + __name(releaseRequestStream, "releaseRequestStream"); + function onData(chunk) { + const stream = this; + const { request } = stream[kRequestStreamState]; + if (request.aborted || request.completed) { + return; + } + if (request.onResponseData(chunk) === false) { + stream.pause(); + } + } + __name(onData, "onData"); + function onResponse(headers) { + const stream = this; + const state = stream[kRequestStreamState]; + const { request } = state; + stream.off("response", onResponse); + const statusCode = headers[HTTP2_HEADER_STATUS]; + delete headers[HTTP2_HEADER_STATUS]; + request.onResponseStarted(); + state.responseReceived = true; + if (request.aborted) { + releaseRequestStream(stream); + return; + } + if (request.onResponseStart(Number(statusCode), headers, stream.resume.bind(stream), "") === false) { + stream.pause(); + } + stream.on("data", onData); + } + __name(onResponse, "onResponse"); + function onEnd() { + const stream = this; + const state = stream[kRequestStreamState]; + const { request } = state; + stream.off("end", onEnd); + releaseRequestStream(stream); + if (state.responseReceived) { + if (!request.aborted && !request.completed) { + request.onResponseEnd({}); + } + state.finalizeRequest(); + } else { + state.abort(new InformationalError("HTTP/2: stream half-closed (remote)"), true); + } + } + __name(onEnd, "onEnd"); + function onError(err) { + const stream = this; + const state = stream[kRequestStreamState]; + stream.off("error", onError); + releaseRequestStream(stream); + state.abort(err); + } + __name(onError, "onError"); + function onFrameError(type, code) { + const stream = this; + const state = stream[kRequestStreamState]; + stream.off("frameError", onFrameError); + releaseRequestStream(stream); + state.abort(new InformationalError(`HTTP/2: "frameError" received - type ${type}, code ${code}`)); + } + __name(onFrameError, "onFrameError"); + function onAborted() { + this.off("data", onData); + } + __name(onAborted, "onAborted"); + function onTimeout() { + const stream = this; + const state = stream[kRequestStreamState]; + releaseRequestStream(stream); + const err = new InformationalError(`HTTP/2: "stream timeout after ${state.requestTimeout}"`); + state.abort(err); + } + __name(onTimeout, "onTimeout"); + function onTrailers(trailers) { + const stream = this; + const state = stream[kRequestStreamState]; + const { request } = state; + stream.off("trailers", onTrailers); + if (request.aborted || request.completed) { + return; + } + releaseRequestStream(stream); + request.onResponseEnd(trailers); + state.finalizeRequest(); + } + __name(onTrailers, "onTrailers"); + function writeBodyH2() { + const stream = this; + const state = stream[kRequestStreamState]; + const { abort, body, client, contentLength, expectsPayload, request } = state; + if (!body || contentLength === 0) { + writeBuffer( + abort, + stream, + null, + client, + request, + client[kSocket], + contentLength, + expectsPayload + ); + } else if (util.isBuffer(body)) { + writeBuffer( + abort, + stream, + body, + client, + request, + client[kSocket], + contentLength, + expectsPayload + ); + } else if (util.isBlobLike(body)) { + if (typeof body.stream === "function") { + writeIterable( abort, stream, - body, + body.stream(), client, request, client[kSocket], contentLength, expectsPayload ); - } else if (util.isBlobLike(body)) { - if (typeof body.stream === "function") { - writeIterable( - abort, - stream, - body.stream(), - client, - request, - client[kSocket], - contentLength, - expectsPayload - ); - } else { - writeBlob( - abort, - stream, - body, - client, - request, - client[kSocket], - contentLength, - expectsPayload - ); - } - } else if (util.isStream(body)) { - writeStream( - abort, - client[kSocket], - expectsPayload, - stream, - body, - client, - request, - contentLength - ); - } else if (util.isIterable(body)) { - writeIterable( + } else { + writeBlob( abort, stream, body, @@ -8965,13 +9076,34 @@ var require_client_h2 = __commonJS({ contentLength, expectsPayload ); - } else { - assert(false); } + } else if (util.isStream(body)) { + writeStream( + abort, + client[kSocket], + expectsPayload, + stream, + body, + client, + request, + contentLength + ); + } else if (util.isIterable(body)) { + writeIterable( + abort, + stream, + body, + client, + request, + client[kSocket], + contentLength, + expectsPayload + ); + } else { + assert(false); } - __name(writeBodyH2, "writeBodyH2"); } - __name(writeH2, "writeH2"); + __name(writeBodyH2, "writeBodyH2"); function writeBuffer(abort, h2stream, body, client, request, socket, contentLength, expectsPayload) { try { if (body != null && util.isBuffer(body)) { @@ -9019,7 +9151,6 @@ var require_client_h2 = __commonJS({ } __name(writeStream, "writeStream"); async function writeBlob(abort, h2stream, body, client, request, socket, contentLength, expectsPayload) { - assert(contentLength === body.size, "blob body must have content length"); try { if (contentLength != null && contentLength !== body.size) { throw new RequestContentLengthMismatchError(); @@ -9144,6 +9275,7 @@ var require_client = __commonJS({ kOnError, kHTTPContext, kMaxConcurrentStreams, + kHostAuthority, kHTTP2InitialWindowSize, kHTTP2ConnectionWindowSize, kResume, @@ -9300,6 +9432,7 @@ var require_client = __commonJS({ }, callback), "connect"); } this[kUrl] = util.parseOrigin(url); + this[kHostAuthority] = `${this[kUrl].hostname}${this[kUrl].port ? `:${this[kUrl].port}` : ""}`; this[kConnector] = connect2; this[kPipelining] = pipelining != null ? pipelining : 1; this[kMaxHeadersSize] = maxHeaderSize; @@ -9311,7 +9444,7 @@ var require_client = __commonJS({ this[kLocalAddress] = localAddress != null ? localAddress : null; this[kResuming] = 0; this[kNeedDrain] = 0; - this[kHostHeader] = `host: ${this[kUrl].hostname}${this[kUrl].port ? `:${this[kUrl].port}` : ""}\r + this[kHostHeader] = `host: ${this[kHostAuthority]}\r `; this[kBodyTimeout] = bodyTimeout != null ? bodyTimeout : 3e5; this[kHeadersTimeout] = headersTimeout != null ? headersTimeout : 3e5; @@ -9635,6 +9768,7 @@ var require_pool = __commonJS({ kNeedDrain, kAddClient, kGetDispatcher, + kHasDispatcher, kRemoveClient } = require_pool_base(); var Client = require_client(); @@ -9729,6 +9863,24 @@ var require_pool = __commonJS({ return dispatcher; } } + [kHasDispatcher]() { + const clientTtlOption = this[kOptions].clientTtl; + for (let i = 0; i < this[kClients].length; i++) { + const client = this[kClients][i]; + if (clientTtlOption != null && clientTtlOption > 0 && client.ttl && Date.now() - client.ttl > clientTtlOption) { + this[kRemoveClient](client); + i--; + } else if (!client[kNeedDrain]) { + return true; + } + } + if (!this[kConnections] || this[kClients].length < this[kConnections]) { + const dispatcher = this[kFactory](this[kUrl], this[kOptions]); + this[kAddClient](dispatcher); + return true; + } + return false; + } }; module2.exports = Pool; } @@ -10531,7 +10683,6 @@ var require_socks5_client = __commonJS({ var require_socks5_proxy_agent = __commonJS({ "lib/dispatcher/socks5-proxy-agent.js"(exports2, module2) { "use strict"; - var net = require("node:net"); var { URL: URL2 } = require("node:url"); var tls; var DispatcherBase = require_dispatcher_base(); @@ -10545,6 +10696,7 @@ var require_socks5_proxy_agent = __commonJS({ var kProxyUrl = /* @__PURE__ */ Symbol("proxy url"); var kProxyHeaders = /* @__PURE__ */ Symbol("proxy headers"); var kProxyAuth = /* @__PURE__ */ Symbol("proxy auth"); + var kProxyProtocol = /* @__PURE__ */ Symbol("proxy protocol"); var kPools = /* @__PURE__ */ Symbol("pools"); var kConnector = /* @__PURE__ */ Symbol("connector"); var experimentalWarningEmitted = false; @@ -10570,6 +10722,7 @@ var require_socks5_proxy_agent = __commonJS({ } this[kProxyUrl] = url; this[kProxyHeaders] = options.headers || {}; + this[kProxyProtocol] = options.proxyTls ? "https:" : "http:"; this[kProxyAuth] = { username: options.username || (url.username ? decodeURIComponent(url.username) : null), password: options.password || (url.password ? decodeURIComponent(url.password) : null) @@ -10588,21 +10741,19 @@ var require_socks5_proxy_agent = __commonJS({ const proxyPort = parseInt(this[kProxyUrl].port) || 1080; debug("creating SOCKS5 connection to", proxyHost, proxyPort); const socketReady = Promise.withResolvers(); - const onSocketConnect = /* @__PURE__ */ __name(() => { - socket.removeListener("error", onSocketError); - socketReady.resolve(socket); - }, "onSocketConnect"); - const onSocketError = /* @__PURE__ */ __name((err) => { - socket.removeListener("connect", onSocketConnect); - socketReady.reject(err); - }, "onSocketError"); - const socket = net.connect({ + this[kConnector]({ + hostname: proxyHost, host: proxyHost, - port: proxyPort + port: proxyPort, + protocol: this[kProxyProtocol] + }, (err, socket2) => { + if (err) { + socketReady.reject(err); + } else { + socketReady.resolve(socket2); + } }); - socket.once("connect", onSocketConnect); - socket.once("error", onSocketError); - await socketReady.promise; + const socket = await socketReady.promise; const socks5Client = new Socks5Client(socket, this[kProxyAuth]); socks5Client.on("error", (err) => { debug("SOCKS5 error:", err); @@ -10655,7 +10806,7 @@ var require_socks5_proxy_agent = __commonJS({ /** * Dispatch a request through the SOCKS5 proxy */ - async [kDispatch](opts, handler) { + [kDispatch](opts, handler) { const { origin } = opts; debug("dispatching request to", origin, "via SOCKS5"); try { @@ -10700,8 +10851,12 @@ var require_socks5_proxy_agent = __commonJS({ return pool[kDispatch](opts, handler); } catch (err) { debug("dispatch error:", err); - if (typeof handler.onError === "function") { + if (typeof handler.onResponseError === "function") { + handler.onResponseError(null, err); + return false; + } else if (typeof handler.onError === "function") { handler.onError(err); + return false; } else { throw err; } @@ -10749,6 +10904,7 @@ var require_proxy_agent = __commonJS({ var kConnectEndpoint = /* @__PURE__ */ Symbol("connect endpoint function"); var kConnectEndpointHTTP1 = /* @__PURE__ */ Symbol("connect endpoint function (http/1.1 only)"); var kTunnelProxy = /* @__PURE__ */ Symbol("tunnel proxy"); + var proxyAuthorization = "proxy-authorization"; function defaultProtocolPort(protocol) { return protocol === "https:" ? 443 : 80; } @@ -10982,6 +11138,9 @@ var require_proxy_agent = __commonJS({ if (Array.isArray(headers)) { const headersPair = {}; for (let i = 0; i < headers.length; i += 2) { + if (isProxyAuthorizationHeader(headers[i])) { + throwProxyAuthError(); + } headersPair[headers[i]] = headers[i + 1]; } return headersPair; @@ -10990,12 +11149,21 @@ var require_proxy_agent = __commonJS({ } __name(buildHeaders, "buildHeaders"); function throwIfProxyAuthIsSent(headers) { - const existProxyAuth = headers && Object.keys(headers).find((key) => key.toLowerCase() === "proxy-authorization"); - if (existProxyAuth) { - throw new InvalidArgumentError("Proxy-Authorization should be sent in ProxyAgent constructor"); + for (const key in headers) { + if (isProxyAuthorizationHeader(key)) { + throwProxyAuthError(); + } } } __name(throwIfProxyAuthIsSent, "throwIfProxyAuthIsSent"); + function isProxyAuthorizationHeader(key) { + return key.length === proxyAuthorization.length && key.toLowerCase() === proxyAuthorization; + } + __name(isProxyAuthorizationHeader, "isProxyAuthorizationHeader"); + function throwProxyAuthError() { + throw new InvalidArgumentError("Proxy-Authorization should be sent in ProxyAgent constructor"); + } + __name(throwProxyAuthError, "throwProxyAuthError"); module2.exports = ProxyAgent; } }); @@ -13944,6 +14112,8 @@ var require_fetch = __commonJS({ origin: url.origin, method: request.method, body: agent.isMockActive ? request.body && (request.body.source || request.body.stream) : body2, + // Preserve the serialized fetch body for MockAgent net-connect fallthroughs. + __mockAgentBodyForDispatch: body2, headers: request.headersList.entries, maxRedirections: 0, upgrade: request.mode === "websocket" ? "websocket" : void 0, @@ -17267,7 +17437,6 @@ var require_api_stream = __commonJS({ "lib/api/api-stream.js"(exports2, module2) { "use strict"; var assert = require("node:assert"); - var { finished } = require("node:stream"); var { AsyncResource } = require("node:async_hooks"); var { InvalidArgumentError, InvalidReturnValueError } = require_errors(); var util = require_util(); @@ -17275,6 +17444,47 @@ var require_api_stream = __commonJS({ function noop() { } __name(noop, "noop"); + function getWritableError(stream2) { + return stream2.errored ?? stream2.writableErrored ?? stream2._writableState?.errored; + } + __name(getWritableError, "getWritableError"); + function createPrematureCloseError() { + const err = new Error("Premature close"); + err.code = "ERR_STREAM_PREMATURE_CLOSE"; + return err; + } + __name(createPrematureCloseError, "createPrematureCloseError"); + function trackWritableLifecycle(stream2, callback) { + let done = false; + const cleanup = /* @__PURE__ */ __name(() => { + stream2.removeListener("close", onClose); + stream2.removeListener("error", onError); + stream2.removeListener("finish", onFinish); + }, "cleanup"); + const finish = /* @__PURE__ */ __name((err, fromErrorEvent = false) => { + if (done) { + return; + } + done = true; + cleanup(); + callback(err, fromErrorEvent); + }, "finish"); + const onClose = /* @__PURE__ */ __name(() => { + const err = getWritableError(stream2); + finish(err ?? (!stream2.writableFinished ? createPrematureCloseError() : void 0)); + }, "onClose"); + const onError = /* @__PURE__ */ __name((err) => finish(err, true), "onError"); + const onFinish = /* @__PURE__ */ __name(() => finish(), "onFinish"); + stream2.on("close", onClose); + stream2.on("error", onError); + stream2.on("finish", onFinish); + if (stream2.closed) { + process.nextTick(onClose); + } else if (stream2.writableFinished) { + process.nextTick(onFinish); + } + } + __name(trackWritableLifecycle, "trackWritableLifecycle"); var StreamHandler = class extends AsyncResource { static { __name(this, "StreamHandler"); @@ -17358,16 +17568,16 @@ var require_api_stream = __commonJS({ if (!res || typeof res.write !== "function" || typeof res.end !== "function" || typeof res.on !== "function") { throw new InvalidReturnValueError("expected Writable"); } - finished(res, { readable: false }, (err) => { + trackWritableLifecycle(res, (err, fromErrorEvent) => { const { callback, res: res2, opaque: opaque2, trailers, abort } = this; this.res = null; if (err || !res2?.readable) { - util.destroy(res2, err); + util.destroy(res2, fromErrorEvent ? void 0 : err); } this.callback = null; this.runInAsyncScope(callback, null, err || null, { opaque: opaque2, trailers }); if (err) { - abort(); + abort(err); } }); res.on("drain", () => controller.resume()); @@ -17457,6 +17667,7 @@ var require_api_pipeline = __commonJS({ RequestAbortedError } = require_errors(); var util = require_util(); + var { kBodyUsed } = require_symbols(); var { addSignal, removeSignal } = require_abort_signal(); function noop() { } @@ -17469,6 +17680,7 @@ var require_api_pipeline = __commonJS({ constructor() { super({ autoDestroy: true }); this[kResume] = null; + this[kBodyUsed] = true; } _read() { const { [kResume]: resume } = this; diff --git a/src/undici_version.h b/src/undici_version.h index 7514ec8d4c236b..c2e57bda323fa5 100644 --- a/src/undici_version.h +++ b/src/undici_version.h @@ -2,5 +2,5 @@ // Refer to tools/dep_updaters/update-undici.sh #ifndef SRC_UNDICI_VERSION_H_ #define SRC_UNDICI_VERSION_H_ -#define UNDICI_VERSION "8.2.0" +#define UNDICI_VERSION "8.3.0" #endif // SRC_UNDICI_VERSION_H_