Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion deps/undici/src/.gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -93,8 +93,9 @@ test/request-timeout.10mb.bin
CLAUDE.md
.claude

# Ignore .pi
# Local tooling
.pi
AGENTS.md

# Ignore .githuman
.githuman
4 changes: 4 additions & 0 deletions deps/undici/src/CONTRIBUTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -96,6 +96,10 @@ Create a commit which includes all of the updated files in lib/llhttp.

### Steps:

`npm run test:wpt` and `node test/web-platform-tests/wpt-runner.mjs setup` will initialize the WPT submodule automatically when it is missing.

If you want to prepare the checkout explicitly, run:

```bash
git submodule update --init --recursive
```
Expand Down
62 changes: 62 additions & 0 deletions deps/undici/src/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -154,6 +154,57 @@ const { statusCode, body } = await request('https://api.example.com/data');
const data = await body.json();
```

### Keep `fetch` and `FormData` together

When you send a `FormData` body, keep `fetch` and `FormData` from the same
implementation.

Use one of these patterns:

```js
// Built-in globals
const body = new FormData()
body.set('name', 'some')
await fetch('https://example.com', {
method: 'POST',
body
})
```

```js
// undici module imports
import { fetch, FormData } from 'undici'

const body = new FormData()
body.set('name', 'some')
await fetch('https://example.com', {
method: 'POST',
body
})
```

If you want the installed `undici` package to provide the globals, call
`install()` first:

```js
import { install } from 'undici'

install()

const body = new FormData()
body.set('name', 'some')
await fetch('https://example.com', {
method: 'POST',
body
})
```

`install()` replaces the global `fetch`, `Headers`, `Response`, `Request`, and
`FormData` implementations with undici's versions, so they all match.

Avoid mixing a global `FormData` with `undici.fetch()`, or `undici.FormData`
with the built-in global `fetch()`.

### Version Compatibility

You can check which version of undici is bundled with your Node.js version:
Expand Down Expand Up @@ -263,6 +314,11 @@ The `install()` function adds the following classes to `globalThis`:
- `CloseEvent`, `ErrorEvent`, `MessageEvent` - WebSocket events
- `EventSource` - Server-sent events client

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.

This is useful for:
- Polyfilling environments that don't have fetch
- Ensuring consistent fetch behavior across different Node.js versions
Expand Down Expand Up @@ -563,6 +619,12 @@ See [Dispatcher.upgrade](./docs/docs/api/Dispatcher.md#dispatcherupgradeoptions-
Sets the global dispatcher used by Common API Methods. Global dispatcher is shared among compatible undici modules,
including undici that is bundled internally with node.js.

Undici stores this dispatcher under `Symbol.for('undici.globalDispatcher.2')`.

On Node.js 22, `setGlobalDispatcher()` also mirrors the configured dispatcher to
`Symbol.for('undici.globalDispatcher.1')` using `Dispatcher1Wrapper`, so Node.js built-in `fetch`
can keep using the legacy handler contract while Undici uses the new handler API.

### `undici.getGlobalDispatcher()`

Gets the global dispatcher used by Common API Methods.
Expand Down
4 changes: 2 additions & 2 deletions deps/undici/src/docs/docs/api/Client.md
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ Returns: `Client`
* **strictContentLength** `Boolean` (optional) - Default: `true` - Whether to treat request content length mismatches as errors. If true, an error is thrown when the request content-length header doesn't match the length of the request body. **Security Warning:** Disabling this option can expose your application to HTTP Request Smuggling attacks, where mismatched content-length headers cause servers and proxies to interpret request boundaries differently. This can lead to cache poisoning, credential hijacking, and bypassing security controls. Only disable this in controlled environments where you fully trust the request source.
* **autoSelectFamily**: `boolean` (optional) - Default: depends on local Node version, on Node 18.13.0 and above is `false`. Enables a family autodetection algorithm that loosely implements section 5 of [RFC 8305](https://tools.ietf.org/html/rfc8305#section-5). See [here](https://nodejs.org/api/net.html#socketconnectoptions-connectlistener) for more details. This option is ignored if not supported by the current Node version.
* **autoSelectFamilyAttemptTimeout**: `number` - Default: depends on local Node version, on Node 18.13.0 and above is `250`. The amount of time in milliseconds to wait for a connection attempt to finish before trying the next address when using the `autoSelectFamily` option. See [here](https://nodejs.org/api/net.html#socketconnectoptions-connectlistener) for more details.
* **allowH2**: `boolean` - Default: `false`. Enables support for H2 if the server has assigned bigger priority to it through ALPN negotiation.
* **allowH2**: `boolean` - Default: `true`. Enables support for H2 if the server has assigned bigger priority to it through ALPN negotiation.
* **useH2c**: `boolean` - Default: `false`. Enforces h2c for non-https connections.
* **maxConcurrentStreams**: `number` - Default: `100`. Dictates the maximum number of concurrent streams for a single H2 session. It can be overridden by a SETTINGS remote frame.
* **initialWindowSize**: `number` (optional) - Default: `262144` (256KB). Sets the HTTP/2 stream-level flow-control window size (SETTINGS_INITIAL_WINDOW_SIZE). Must be a positive integer greater than 0. This default is higher than Node.js core's default (65535 bytes) to improve throughput, Node's choice is very conservative for current high-bandwith networks. See [RFC 7540 Section 6.9.2](https://datatracker.ietf.org/doc/html/rfc7540#section-6.9.2) for more details.
Expand Down Expand Up @@ -282,4 +282,4 @@ console.log('requests completed')

### Event: `'error'`

Invoked for users errors such as throwing in the `onError` handler.
Invoked for user errors such as throwing in the `onResponseError` handler.
14 changes: 8 additions & 6 deletions deps/undici/src/docs/docs/api/DiagnosticsChannel.md
Original file line number Diff line number Diff line change
Expand Up @@ -182,22 +182,24 @@ diagnosticsChannel.channel('undici:websocket:open').subscribe(({
console.log(websocket) // the WebSocket instance

// Handshake response details
console.log(handshakeResponse.status) // 101 for successful WebSocket upgrade
console.log(handshakeResponse.statusText) // 'Switching Protocols'
console.log(handshakeResponse.status) // 101 for HTTP/1.1, 200 for HTTP/2 extended CONNECT
console.log(handshakeResponse.statusText) // 'Switching Protocols' for HTTP/1.1, commonly 'OK' for HTTP/2 in Node.js
console.log(handshakeResponse.headers) // Object containing response headers
})
```

### Handshake Response Object

The `handshakeResponse` object contains the HTTP response that upgraded the connection to WebSocket:
The `handshakeResponse` object contains the HTTP response that established the WebSocket connection:

- `status` (number): The HTTP status code (101 for successful WebSocket upgrade)
- `statusText` (string): The HTTP status message ('Switching Protocols' for successful upgrade)
- `status` (number): The HTTP status code (`101` for HTTP/1.1 upgrade, `200` for HTTP/2 extended CONNECT)
- `statusText` (string): The HTTP status message (`'Switching Protocols'` for HTTP/1.1, commonly `'OK'` for HTTP/2 in Node.js)
- `headers` (object): The HTTP response headers from the server, including:
- `sec-websocket-accept` and other WebSocket-related headers
- `upgrade: 'websocket'`
- `connection: 'upgrade'`
- `sec-websocket-accept` and other WebSocket-related headers

The `upgrade` and `connection` headers are only present for HTTP/1.1 handshakes.

This information is particularly useful for debugging and monitoring WebSocket connections, as it provides access to the initial HTTP handshake response that established the WebSocket connection.

Expand Down
89 changes: 62 additions & 27 deletions deps/undici/src/docs/docs/api/Dispatcher.md
Original file line number Diff line number Diff line change
Expand Up @@ -212,6 +212,41 @@ Returns: `Boolean` - `false` if dispatcher is busy and further dispatch calls wo
* **onResponseEnd** `(controller: DispatchController, trailers: Record<string, string | string[]>) => void` - Invoked when response payload and trailers have been received and the request has completed. Not required for `upgrade` requests.
* **onResponseError** `(controller: DispatchController, error: Error) => void` - Invoked when an error has occurred. May not throw.

#### Migration from legacy handler API

If you were previously using `onConnect/onHeaders/onData/onComplete/onError`, switch to the new callbacks:

- `onConnect(abort)` → `onRequestStart(controller)` and call `controller.abort(reason)`
- `onHeaders(status, rawHeaders, resume, statusText)` → `onResponseStart(controller, status, headers, statusText)`
- `onData(chunk)` → `onResponseData(controller, chunk)`
- `onComplete(trailers)` → `onResponseEnd(controller, trailers)`
- `onError(err)` → `onResponseError(controller, err)`
- `onUpgrade(status, rawHeaders, socket)` → `onRequestUpgrade(controller, status, headers, socket)`

To access raw header arrays (for preserving duplicates/casing), read them from the controller:

- `controller.rawHeaders` for response headers
- `controller.rawTrailers` for trailers

Pause/resume now uses the controller:

- Call `controller.pause()` and `controller.resume()` instead of returning `false` from handlers.

#### Compatibility notes

Undici now stores the global dispatcher under `Symbol.for('undici.globalDispatcher.2')`.
This avoids conflicts with runtimes (such as Node.js built-in `fetch`) that still rely on the legacy dispatcher handler interface.

On Node.js 22, `setGlobalDispatcher()` also mirrors the configured dispatcher to `Symbol.for('undici.globalDispatcher.1')` using a `Dispatcher1Wrapper`, so Node's built-in `fetch` can keep using the legacy handler contract.

If you need to expose a new dispatcher/agent to legacy v1 handler consumers (`onConnect/onHeaders/onData/onComplete/onError/onUpgrade`), use `Dispatcher1Wrapper`:

```js
import { Agent, Dispatcher1Wrapper } from 'undici'

const legacyCompatibleDispatcher = new Dispatcher1Wrapper(new Agent())
```

#### Example 1 - Dispatch GET request

```js
Expand All @@ -236,21 +271,21 @@ client.dispatch({
'x-foo': 'bar'
}
}, {
onConnect: () => {
onRequestStart: () => {
console.log('Connected!')
},
onError: (error) => {
onResponseError: (_controller, error) => {
console.error(error)
},
onHeaders: (statusCode, headers) => {
console.log(`onHeaders | statusCode: ${statusCode} | headers: ${headers}`)
onResponseStart: (_controller, statusCode, headers) => {
console.log(`onResponseStart | statusCode: ${statusCode} | headers: ${JSON.stringify(headers)}`)
},
onData: (chunk) => {
console.log('onData: chunk received')
onResponseData: (_controller, chunk) => {
console.log('onResponseData: chunk received')
data.push(chunk)
},
onComplete: (trailers) => {
console.log(`onComplete | trailers: ${trailers}`)
onResponseEnd: (_controller, trailers) => {
console.log(`onResponseEnd | trailers: ${JSON.stringify(trailers)}`)
const res = Buffer.concat(data).toString('utf8')
console.log(`Data: ${res}`)
client.close()
Expand Down Expand Up @@ -288,15 +323,15 @@ client.dispatch({
method: 'GET',
upgrade: 'websocket'
}, {
onConnect: () => {
console.log('Undici Client - onConnect')
onRequestStart: () => {
console.log('Undici Client - onRequestStart')
},
onError: (error) => {
console.log('onError') // shouldn't print
onResponseError: () => {
console.log('onResponseError') // shouldn't print
},
onUpgrade: (statusCode, headers, socket) => {
console.log('Undici Client - onUpgrade')
console.log(`onUpgrade Headers: ${headers}`)
onRequestUpgrade: (_controller, statusCode, headers, socket) => {
console.log('Undici Client - onRequestUpgrade')
console.log(`onRequestUpgrade Headers: ${JSON.stringify(headers)}`)
socket.on('data', buffer => {
console.log(buffer.toString('utf8'))
})
Expand Down Expand Up @@ -339,21 +374,21 @@ client.dispatch({
},
body: JSON.stringify({ message: 'Hello' })
}, {
onConnect: () => {
onRequestStart: () => {
console.log('Connected!')
},
onError: (error) => {
onResponseError: (_controller, error) => {
console.error(error)
},
onHeaders: (statusCode, headers) => {
console.log(`onHeaders | statusCode: ${statusCode} | headers: ${headers}`)
onResponseStart: (_controller, statusCode, headers) => {
console.log(`onResponseStart | statusCode: ${statusCode} | headers: ${JSON.stringify(headers)}`)
},
onData: (chunk) => {
console.log('onData: chunk received')
onResponseData: (_controller, chunk) => {
console.log('onResponseData: chunk received')
data.push(chunk)
},
onComplete: (trailers) => {
console.log(`onComplete | trailers: ${trailers}`)
onResponseEnd: (_controller, trailers) => {
console.log(`onResponseEnd | trailers: ${JSON.stringify(trailers)}`)
const res = Buffer.concat(data).toString('utf8')
console.log(`Response Data: ${res}`)
client.close()
Expand All @@ -364,7 +399,7 @@ client.dispatch({

### `Dispatcher.pipeline(options, handler)`

For easy use with [stream.pipeline](https://nodejs.org/api/stream.html#stream_stream_pipeline_source_transforms_destination_callback). The `handler` argument should return a `Readable` from which the result will be read. Usually it should just return the `body` argument unless some kind of transformation needs to be performed based on e.g. `headers` or `statusCode`. The `handler` should validate the response and save any required state. If there is an error, it should be thrown. The function returns a `Duplex` which writes to the request and reads from the response.
For easy use with [stream.pipeline](https://nodejs.org/api/stream.html#streampipelinesource-transforms-destination-options). The `handler` argument should return a `Readable` from which the result will be read. Usually it should just return the `body` argument unless some kind of transformation needs to be performed based on e.g. `headers` or `statusCode`. The `handler` should validate the response and save any required state. If there is an error, it should be thrown. The function returns a `Duplex` which writes to the request and reads from the response.

Arguments:

Expand Down Expand Up @@ -963,7 +998,7 @@ const { Client, interceptors } = require("undici");
const { redirect } = interceptors;

const client = new Client("http://service.example").compose(
redirect({ maxRedirections: 3, throwOnMaxRedirects: true })
redirect({ maxRedirections: 3, throwOnMaxRedirect: true })
);
client.request({ path: "/" })
```
Expand Down Expand Up @@ -1036,10 +1071,10 @@ The `dns` interceptor enables you to cache DNS lookups for a given duration, per
- `dualStack` - Whether to resolve both IPv4 and IPv6 addresses. Default: `true`.
- It will also attempt a happy-eyeballs-like approach to connect to the available addresses in case of a connection failure.
- `affinity` - Whether to use IPv4 or IPv6 addresses. Default: `4`.
- It can be either `'4` or `6`.
- It can be either `4` or `6`.
- It will only take effect if `dualStack` is `false`.
- `lookup: (hostname: string, options: LookupOptions, callback: (err: NodeJS.ErrnoException | null, addresses: DNSInterceptorRecord[]) => void) => void` - Custom lookup function. Default: `dns.lookup`.
- For more info see [dns.lookup](https://nodejs.org/api/dns.html#dns_dns_lookup_hostname_options_callback).
- For more info see [dns.lookup](https://nodejs.org/api/dns.html#dnslookuphostname-options-callback).
- `pick: (origin: URL, records: DNSInterceptorRecords, affinity: 4 | 6) => DNSInterceptorRecord` - Custom pick function. Default: `RoundRobin`.
- The function should return a single record from the records array.
- By default a simplified version of Round Robin is used.
Expand Down
8 changes: 8 additions & 0 deletions deps/undici/src/docs/docs/api/Fetch.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,14 @@ This API is implemented as per the standard, you can find documentation on [MDN]

If any parameters are passed to the FormData constructor other than `undefined`, an error will be thrown. Other parameters are ignored.

When you use `FormData` as a request body, keep `fetch` and `FormData` from the
same implementation. Use the built-in global `FormData` with the built-in
global `fetch()`, and use `undici`'s `FormData` with `undici.fetch()`.

If you want the installed `undici` package to provide the globals, call
[`install()`](/docs/api/GlobalInstallation.md) so `fetch`, `Headers`,
`Response`, `Request`, and `FormData` are installed together as a matching set.

## Response

This API is implemented as per the standard, you can find documentation on [MDN](https://developer.mozilla.org/en-US/docs/Web/API/Response)
Expand Down
48 changes: 48 additions & 0 deletions deps/undici/src/docs/docs/api/GlobalInstallation.md
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,54 @@ The `install()` function adds the following classes to `globalThis`:
| `MessageEvent` | WebSocket message event |
| `EventSource` | Server-sent events client |

## Using `FormData` with `fetch`

If you send a `FormData` body, use matching implementations for `fetch` and
`FormData`.

These two patterns are safe:

```js
// Built-in globals from Node.js
const body = new FormData()
await fetch('https://example.com', {
method: 'POST',
body
})
```

```js
// Globals installed from the undici package
import { install } from 'undici'

install()

const body = new FormData()
await fetch('https://example.com', {
method: 'POST',
body
})
```

After `install()`, `fetch`, `Headers`, `Response`, `Request`, and `FormData`
all come from the installed `undici` package, so they work as a matching set.

If you do not want to install globals, import both from `undici` instead:

```js
import { fetch, FormData } from 'undici'

const body = new FormData()
await fetch('https://example.com', {
method: 'POST',
body
})
```

Avoid mixing a global `FormData` with `undici.fetch()`, or `undici.FormData`
with the built-in global `fetch()`. Keeping them paired avoids surprising
multipart behavior across Node.js and undici versions.

## Use Cases

Global installation is useful for:
Expand Down
2 changes: 1 addition & 1 deletion deps/undici/src/docs/docs/api/H2CClient.md
Original file line number Diff line number Diff line change
Expand Up @@ -260,4 +260,4 @@ console.log("requests completed");

### Event: `'error'`

Invoked for users errors such as throwing in the `onError` handler.
Invoked for user errors such as throwing in the `onResponseError` handler.
Loading
Loading