Skip to content
Draft
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
2 changes: 1 addition & 1 deletion .github/workflows/main.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ jobs:
timeout-minutes: 10
strategy:
matrix:
node-version: [22.x, 24.x]
node-version: [22.x, 24.x, 26.x]
steps:
- uses: actions/checkout@v7
with:
Expand Down
33 changes: 31 additions & 2 deletions lib/agentCompatibility.js
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
/*!
* Copyright (c) 2022 Digital Bazaar, Inc. All rights reserved.
*/
import {Agent} from 'undici';
import {Agent, fetch as undiciFetch} from 'undici';

// as long as an agent has a reference to it, its associated dispatcher will
// be kept in this cache for reuse
Expand Down Expand Up @@ -34,10 +34,39 @@ export function convertAgent(options) {

// create fetch override uses custom `dispatcher`; since `ky` does not pass
// the dispatcher option through to `fetch`, we must use this override
//
// this uses undici's own `fetch` rather than `globalThis.fetch`. a dispatcher
// is only usable by the undici that created it: node bundles its own undici
// (node 22: 6.x, node 24: 7.x, node 26: 8.x) and the handler interface changed
// between majors, so handing an installed-undici dispatcher to node's built-in
// `fetch` throws `UND_ERR_INVALID_ARG` ("invalid onError method" on node 26).
// pairing the dispatcher with the `fetch` from the same module keeps the two
// in sync on any node version, whichever undici is installed
function createFetch(dispatcher) {
return function fetch(...args) {
dispatcher = (args[1] && args[1].dispatcher) || dispatcher;
args[1] = {...args[1], dispatcher};
return globalThis.fetch(...args);
// `ky` builds a global `Request`, which undici's `fetch` does not accept
// as one of its own; unpack it into a url and init pair instead
if(args[0] instanceof globalThis.Request) {
args[1] = {..._requestToInit(args[0]), ...args[1]};
args[0] = args[0].url;
}
return undiciFetch(...args);
};
}

// converts a `Request` into an equivalent `fetch` init object
function _requestToInit(request) {
const {body} = request;
return {
method: request.method,
// an entry list is understood by every `Headers` implementation
headers: [...request.headers],
body,
// undici requires `duplex` whenever a stream body is sent
duplex: body ? 'half' : undefined,
signal: request.signal,
redirect: request.redirect
};
}